OR-1 dataflow CPU sketch
1"""Tests for code generation (frame-based model).
2
3Tests verify:
4- pe-frame-redesign.AC6.1: AssemblyResult includes setup_tokens field
5- pe-frame-redesign.AC6.2: Token stream ordering: SM init → IRAM writes → ALLOC → frame slot writes → seeds
6- pe-frame-redesign.AC6.3: IRAM write data uses pack_instruction()
7- pe-frame-redesign.AC6.6: Seed tokens use act_id, no gen field
8
9Also tests original codegen acceptance criteria:
10- Direct mode produces valid PEConfig with correct IRAM contents
11- Direct mode produces valid SMConfig with initial cell values
12- Direct mode produces seed tokens for const nodes with no incoming edges
13- Direct mode PEConfig includes route restrictions matching edge analysis
14- Program with no data_defs produces empty SM init section
15"""
16
17import pytest
18
19from asm.codegen import generate_direct, generate_tokens, AssemblyResult
20from asm.ir import (
21 IRGraph,
22 IRNode,
23 IREdge,
24 IRDataDef,
25 SystemConfig,
26 SourceLoc,
27 ResolvedDest,
28)
29from cm_inst import (
30 Instruction, OutputStyle, TokenKind, FrameOp, FrameDest,
31 ArithOp, MemOp, Port, RoutingOp
32)
33from tokens import MonadToken, SMToken, PELocalWriteToken, FrameControlToken
34from emu.types import PEConfig, SMConfig
35from sm_mod import Presence
36
37
38class TestFrameDirectMode:
39 """Frame-based direct mode code generation."""
40
41 def test_assembly_result_structure(self):
42 """AssemblyResult has all required fields including setup_tokens."""
43 node = IRNode(
44 name="&add",
45 opcode=ArithOp.ADD,
46 pe=0,
47 iram_offset=0,
48 act_id=0,
49 mode=(OutputStyle.INHERIT, False, 2),
50 wide=False,
51 fref=0,
52 loc=SourceLoc(1, 1),
53 )
54 system = SystemConfig(pe_count=1, sm_count=1)
55 graph = IRGraph({"&add": node}, system=system)
56
57 result = generate_direct(graph)
58
59 # Verify AssemblyResult structure
60 assert isinstance(result, AssemblyResult)
61 assert hasattr(result, 'pe_configs')
62 assert hasattr(result, 'sm_configs')
63 assert hasattr(result, 'seed_tokens')
64 assert hasattr(result, 'setup_tokens')
65 assert isinstance(result.setup_tokens, list)
66
67 def test_direct_mode_peconfig_structure(self):
68 """PEConfig has Instruction IRAM (not ALUInst/SMInst) and frame config fields."""
69 node = IRNode(
70 name="&add",
71 opcode=ArithOp.ADD,
72 pe=0,
73 iram_offset=0,
74 act_id=0,
75 mode=(OutputStyle.INHERIT, False, 2),
76 wide=False,
77 fref=7,
78 loc=SourceLoc(1, 1),
79 )
80 system = SystemConfig(pe_count=1, sm_count=1)
81 graph = IRGraph({"&add": node}, system=system)
82
83 result = generate_direct(graph)
84
85 assert len(result.pe_configs) == 1
86 pe_config = result.pe_configs[0]
87 assert pe_config.pe_id == 0
88 assert 0 in pe_config.iram
89
90 inst = pe_config.iram[0]
91 assert isinstance(inst, Instruction)
92 assert inst.opcode == ArithOp.ADD
93 assert inst.output == OutputStyle.INHERIT
94 assert inst.fref == 7
95
96 # Verify frame config fields
97 assert hasattr(pe_config, 'frame_count')
98 assert hasattr(pe_config, 'frame_slots')
99 assert hasattr(pe_config, 'matchable_offsets')
100 assert pe_config.frame_count > 0
101 assert pe_config.frame_slots > 0
102 assert pe_config.matchable_offsets > 0
103
104 def test_direct_mode_with_data_defs(self):
105 """Direct mode produces SMConfig with initial cell values."""
106 data_def = IRDataDef(
107 name="@val",
108 sm_id=0,
109 cell_addr=5,
110 value=42,
111 loc=SourceLoc(1, 1),
112 )
113 node = IRNode(
114 name="&a",
115 opcode=ArithOp.ADD,
116 pe=0,
117 iram_offset=0,
118 act_id=0,
119 mode=(OutputStyle.INHERIT, False, 2),
120 wide=False,
121 fref=0,
122 loc=SourceLoc(1, 1),
123 )
124 system = SystemConfig(pe_count=1, sm_count=1)
125 graph = IRGraph(
126 {"&a": node},
127 data_defs=[data_def],
128 system=system,
129 )
130
131 result = generate_direct(graph)
132
133 # Verify SMConfig
134 assert len(result.sm_configs) == 1
135 sm_config = result.sm_configs[0]
136 assert sm_config.sm_id == 0
137 assert 5 in sm_config.initial_cells
138 pres, val = sm_config.initial_cells[5]
139 assert pres == Presence.FULL
140 assert val == 42
141
142 def test_seed_tokens_use_act_id(self):
143 """Seed tokens use act_id field, not ctx."""
144 seed_node = IRNode(
145 name="&seed",
146 opcode=RoutingOp.CONST,
147 pe=0,
148 iram_offset=1,
149 act_id=3,
150 const=99,
151 mode=(OutputStyle.INHERIT, True, 1),
152 wide=False,
153 fref=0,
154 seed=True,
155 loc=SourceLoc(2, 1),
156 )
157 # Monadic destination (not dyadic)
158 dest_node = IRNode(
159 name="&dest",
160 opcode=ArithOp.INC, # INC is monadic
161 pe=0,
162 iram_offset=2,
163 act_id=3,
164 mode=(OutputStyle.INHERIT, False, 1),
165 wide=False,
166 fref=0,
167 loc=SourceLoc(3, 1),
168 )
169 edge = IREdge(source="&seed", dest="&dest", port=Port.L)
170 system = SystemConfig(pe_count=1, sm_count=1)
171 graph = IRGraph(
172 {"&seed": seed_node, "&dest": dest_node},
173 edges=[edge],
174 system=system,
175 )
176
177 result = generate_direct(graph)
178
179 # Verify seed tokens
180 assert len(result.seed_tokens) == 1
181 seed = result.seed_tokens[0]
182 assert isinstance(seed, MonadToken)
183 assert seed.data == 99
184 assert seed.act_id == 3
185 assert not hasattr(seed, 'gen') # No gen field
186
187 def test_const_node_with_no_incoming_edge_is_seed(self):
188 """CONST node with no incoming edges produces seed token."""
189 const_node = IRNode(
190 name="&const",
191 opcode=RoutingOp.CONST,
192 pe=0,
193 iram_offset=2,
194 act_id=0,
195 const=99,
196 mode=(OutputStyle.INHERIT, True, 1),
197 wide=False,
198 fref=0,
199 loc=SourceLoc(1, 1),
200 )
201 graph = IRGraph({"&const": const_node}, system=SystemConfig(1, 1))
202
203 result = generate_direct(graph)
204
205 assert len(result.seed_tokens) == 1
206 token = result.seed_tokens[0]
207 assert isinstance(token, MonadToken)
208 assert token.target == 0
209 assert token.offset == 2
210 assert token.act_id == 0
211 assert token.data == 99
212
213 def test_route_restrictions(self):
214 """Cross-PE edges produce correct allowed_pe_routes."""
215 node_pe0 = IRNode(
216 name="&a",
217 opcode=ArithOp.ADD,
218 pe=0,
219 iram_offset=0,
220 act_id=0,
221 mode=(OutputStyle.INHERIT, False, 2),
222 wide=False,
223 fref=0,
224 dest_l=ResolvedDest(name="&b", frame_dest=FrameDest(
225 target_pe=1, offset=0, act_id=0, port=Port.L, token_kind=TokenKind.DYADIC
226 )),
227 loc=SourceLoc(1, 1),
228 )
229 node_pe1 = IRNode(
230 name="&b",
231 opcode=ArithOp.SUB,
232 pe=1,
233 iram_offset=0,
234 act_id=0,
235 mode=(OutputStyle.INHERIT, False, 2),
236 wide=False,
237 fref=0,
238 loc=SourceLoc(2, 1),
239 )
240 edge = IREdge(source="&a", dest="&b", port=Port.L, loc=SourceLoc(1, 1))
241 system = SystemConfig(pe_count=2, sm_count=1)
242 graph = IRGraph(
243 {"&a": node_pe0, "&b": node_pe1},
244 edges=[edge],
245 system=system,
246 )
247
248 result = generate_direct(graph)
249
250 assert len(result.pe_configs) == 2
251 pe0_config = next(c for c in result.pe_configs if c.pe_id == 0)
252 pe1_config = next(c for c in result.pe_configs if c.pe_id == 1)
253
254 # PE0 should have routes to {0, 1}
255 assert 0 in pe0_config.allowed_pe_routes
256 assert 1 in pe0_config.allowed_pe_routes
257
258 # PE1 should have route to {1} (self only)
259 assert 1 in pe1_config.allowed_pe_routes
260
261 def test_no_data_defs_produces_empty_smconfig(self):
262 """Program with no data_defs produces SMConfig with no initial cells."""
263 node = IRNode(
264 name="&add",
265 opcode=ArithOp.ADD,
266 pe=0,
267 iram_offset=0,
268 act_id=0,
269 mode=(OutputStyle.INHERIT, False, 2),
270 wide=False,
271 fref=0,
272 loc=SourceLoc(1, 1),
273 )
274 system = SystemConfig(pe_count=1, sm_count=1)
275 graph = IRGraph({"&add": node}, system=system)
276
277 result = generate_direct(graph)
278
279 assert len(result.sm_configs) == 1
280 sm_config = result.sm_configs[0]
281 assert sm_config.sm_id == 0
282 assert sm_config.initial_cells is None
283
284 def test_memop_instructions(self):
285 """MemOp instructions produce Instruction objects."""
286 node = IRNode(
287 name="&read",
288 opcode=MemOp.READ,
289 pe=0,
290 iram_offset=0,
291 act_id=0,
292 sm_id=0,
293 mode=(OutputStyle.CHANGE_TAG, False, 1),
294 wide=True,
295 fref=3,
296 loc=SourceLoc(1, 1),
297 )
298 system = SystemConfig(pe_count=1, sm_count=1)
299 graph = IRGraph({"&read": node}, system=system)
300
301 result = generate_direct(graph)
302
303 assert len(result.pe_configs) == 1
304 pe_config = result.pe_configs[0]
305 assert 0 in pe_config.iram
306
307 inst = pe_config.iram[0]
308 assert isinstance(inst, Instruction)
309 assert inst.opcode == MemOp.READ
310 assert inst.output == OutputStyle.CHANGE_TAG
311 assert inst.wide == True
312 assert inst.fref == 3
313
314
315class TestTokenStream:
316 """Token stream generation and ordering."""
317
318 def test_setup_tokens_include_sm_init(self):
319 """Setup tokens include SM init (SMToken) entries."""
320 data_def = IRDataDef(
321 name="@val",
322 sm_id=0,
323 cell_addr=5,
324 value=42,
325 loc=SourceLoc(1, 1),
326 )
327 node = IRNode(
328 name="&add",
329 opcode=ArithOp.ADD,
330 pe=0,
331 iram_offset=0,
332 act_id=0,
333 mode=(OutputStyle.INHERIT, False, 2),
334 wide=False,
335 fref=0,
336 loc=SourceLoc(1, 1),
337 )
338 system = SystemConfig(pe_count=1, sm_count=1)
339 graph = IRGraph(
340 {"&add": node},
341 data_defs=[data_def],
342 system=system,
343 )
344
345 result = generate_direct(graph)
346 sm_tokens = [t for t in result.setup_tokens if isinstance(t, SMToken)]
347
348 assert len(sm_tokens) > 0
349 token = sm_tokens[0]
350 assert token.target == 0
351 assert token.addr == 5
352 assert token.op == MemOp.WRITE
353 assert token.data == 42
354
355 def test_setup_tokens_include_iram_writes(self):
356 """Setup tokens include IRAM writes (PELocalWriteToken with region=0)."""
357 node = IRNode(
358 name="&add",
359 opcode=ArithOp.ADD,
360 pe=0,
361 iram_offset=0,
362 act_id=0,
363 mode=(OutputStyle.INHERIT, False, 2),
364 wide=False,
365 fref=0,
366 loc=SourceLoc(1, 1),
367 )
368 system = SystemConfig(pe_count=1, sm_count=1)
369 graph = IRGraph({"&add": node}, system=system)
370
371 result = generate_direct(graph)
372 iram_tokens = [t for t in result.setup_tokens
373 if isinstance(t, PELocalWriteToken) and t.region == 0]
374
375 assert len(iram_tokens) > 0
376 token = iram_tokens[0]
377 assert token.target == 0
378 assert token.region == 0
379
380 def test_setup_tokens_include_alloc(self):
381 """Setup tokens include ALLOC (FrameControlToken) entries."""
382 node = IRNode(
383 name="&add",
384 opcode=ArithOp.ADD,
385 pe=0,
386 iram_offset=0,
387 act_id=2,
388 mode=(OutputStyle.INHERIT, False, 2),
389 wide=False,
390 fref=0,
391 loc=SourceLoc(1, 1),
392 )
393 system = SystemConfig(pe_count=1, sm_count=1)
394 graph = IRGraph({"&add": node}, system=system)
395
396 result = generate_direct(graph)
397 alloc_tokens = [t for t in result.setup_tokens if isinstance(t, FrameControlToken)]
398
399 assert len(alloc_tokens) > 0
400 token = alloc_tokens[0]
401 assert token.op == FrameOp.ALLOC
402 assert token.act_id == 2
403
404 def test_generate_tokens_ordering(self):
405 """generate_tokens() returns tokens in proper order."""
406 data_def = IRDataDef(
407 name="@val",
408 sm_id=0,
409 cell_addr=5,
410 value=42,
411 loc=SourceLoc(1, 1),
412 )
413 node = IRNode(
414 name="&add",
415 opcode=ArithOp.ADD,
416 pe=0,
417 iram_offset=0,
418 act_id=0,
419 mode=(OutputStyle.INHERIT, False, 2),
420 wide=False,
421 fref=0,
422 loc=SourceLoc(1, 1),
423 )
424 seed_node = IRNode(
425 name="&const",
426 opcode=RoutingOp.CONST,
427 pe=0,
428 iram_offset=10,
429 act_id=0,
430 const=99,
431 mode=(OutputStyle.INHERIT, True, 1),
432 wide=False,
433 fref=0,
434 seed=True,
435 loc=SourceLoc(2, 1),
436 )
437 edge = IREdge(source="&const", dest="&add", port=Port.L)
438 system = SystemConfig(pe_count=1, sm_count=1)
439 graph = IRGraph(
440 {"&add": node, "&const": seed_node},
441 edges=[edge],
442 data_defs=[data_def],
443 system=system,
444 )
445
446 tokens = generate_tokens(graph)
447
448 # Verify that setup tokens come before seed tokens
449 sm_indices = [i for i, t in enumerate(tokens) if isinstance(t, SMToken)]
450 iram_indices = [i for i, t in enumerate(tokens)
451 if isinstance(t, PELocalWriteToken) and t.region == 0]
452 seed_indices = [i for i, t in enumerate(tokens) if isinstance(t, (MonadToken))]
453
454 if sm_indices and iram_indices:
455 assert max(sm_indices) < min(iram_indices)
456 if iram_indices and seed_indices:
457 assert max(iram_indices) < min(seed_indices)
458
459
460class TestEdgeCases:
461 """Edge case tests."""
462
463 def test_multiple_data_defs_same_sm(self):
464 """Multiple data_defs targeting same SM are merged."""
465 data_def1 = IRDataDef(
466 name="@val1",
467 sm_id=0,
468 cell_addr=5,
469 value=42,
470 loc=SourceLoc(1, 1),
471 )
472 data_def2 = IRDataDef(
473 name="@val2",
474 sm_id=0,
475 cell_addr=10,
476 value=99,
477 loc=SourceLoc(2, 1),
478 )
479 system = SystemConfig(pe_count=1, sm_count=1)
480 graph = IRGraph({}, data_defs=[data_def1, data_def2], system=system)
481
482 result = generate_direct(graph)
483
484 assert len(result.sm_configs) == 1
485 sm_config = result.sm_configs[0]
486 assert len(sm_config.initial_cells) == 2
487 assert sm_config.initial_cells[5] == (Presence.FULL, 42)
488 assert sm_config.initial_cells[10] == (Presence.FULL, 99)
489
490 def test_const_node_with_incoming_edge_not_seed(self):
491 """CONST node with incoming edge is not a seed token."""
492 source_node = IRNode(
493 name="&src",
494 opcode=ArithOp.ADD,
495 pe=0,
496 iram_offset=0,
497 act_id=0,
498 mode=(OutputStyle.INHERIT, False, 2),
499 wide=False,
500 fref=0,
501 loc=SourceLoc(1, 1),
502 )
503 const_node = IRNode(
504 name="&const",
505 opcode=RoutingOp.CONST,
506 pe=0,
507 iram_offset=1,
508 act_id=0,
509 const=5,
510 mode=(OutputStyle.INHERIT, True, 1),
511 wide=False,
512 fref=0,
513 loc=SourceLoc(2, 1),
514 )
515 edge = IREdge(source="&src", dest="&const", port=Port.L, loc=SourceLoc(1, 1))
516 system = SystemConfig(pe_count=1, sm_count=1)
517 graph = IRGraph(
518 {"&src": source_node, "&const": const_node},
519 edges=[edge],
520 system=system,
521 )
522
523 result = generate_direct(graph)
524
525 # The CONST node has an incoming edge, so it should NOT be a seed
526 assert len(result.seed_tokens) == 0
527
528 def test_multiactivation_alloc_tokens(self):
529 """Multiple activations generate multiple ALLOC tokens."""
530 node1 = IRNode(
531 name="&a",
532 opcode=ArithOp.ADD,
533 pe=0,
534 iram_offset=0,
535 act_id=1,
536 mode=(OutputStyle.INHERIT, False, 2),
537 wide=False,
538 fref=0,
539 loc=SourceLoc(1, 1),
540 )
541 node2 = IRNode(
542 name="&b",
543 opcode=ArithOp.SUB,
544 pe=0,
545 iram_offset=1,
546 act_id=2,
547 mode=(OutputStyle.INHERIT, False, 2),
548 wide=False,
549 fref=0,
550 loc=SourceLoc(2, 1),
551 )
552 system = SystemConfig(pe_count=1, sm_count=1)
553 graph = IRGraph(
554 {"&a": node1, "&b": node2},
555 system=system,
556 )
557
558 result = generate_direct(graph)
559 alloc_tokens = [t for t in result.setup_tokens if isinstance(t, FrameControlToken)]
560
561 # Should have 2 ALLOC tokens (one for act_id=1, one for act_id=2)
562 assert len(alloc_tokens) == 2
563 assert all(t.op == FrameOp.ALLOC for t in alloc_tokens)
564 assert {t.act_id for t in alloc_tokens} == {1, 2}