OR-1 dataflow CPU sketch
at pe-frame-redesign 698 lines 23 kB view raw
1"""Tests for frame-based code generation (Phase 6). 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.4: Destination frame slot writes use pack_flit1() with is_dest=True 8- pe-frame-redesign.AC6.5: T0 bootstrap data uses pack_token() for packed flits 9- pe-frame-redesign.AC6.6: Seed tokens use act_id, no gen field 10""" 11 12import pytest 13 14from asm.codegen import generate_direct, generate_tokens, AssemblyResult, _build_iram_for_pe 15from asm.ir import ( 16 IRGraph, 17 IRNode, 18 IREdge, 19 IRDataDef, 20 SystemConfig, 21 SourceLoc, 22 ResolvedDest, 23 FrameLayout, 24 FrameSlotMap, 25) 26from cm_inst import ( 27 Instruction, OutputStyle, TokenKind, FrameDest, FrameOp, 28 ArithOp, MemOp, Port, RoutingOp 29) 30from tokens import ( 31 DyadToken, MonadToken, SMToken, 32 PELocalWriteToken, FrameControlToken 33) 34from encoding import pack_instruction, pack_flit1, unpack_instruction, pack_token 35from emu.types import PEConfig, SMConfig 36from sm_mod import Presence 37 38 39class TestTask1BuildIramForPE: 40 """Task 1: Rewrite _build_iram_for_pe() for Instruction objects.""" 41 42 def test_builds_instruction_objects(self): 43 """Produces Instruction objects from IRNodes with mode set.""" 44 node = IRNode( 45 name="&add", 46 opcode=ArithOp.ADD, 47 pe=0, 48 iram_offset=0, 49 act_id=0, 50 mode=(OutputStyle.INHERIT, False, 2), 51 wide=False, 52 fref=5, 53 loc=SourceLoc(1, 1), 54 ) 55 56 iram = _build_iram_for_pe([node], {"&add": node}, []) 57 58 assert len(iram) == 1 59 assert 0 in iram 60 inst = iram[0] 61 assert isinstance(inst, Instruction) 62 assert inst.opcode == ArithOp.ADD 63 assert inst.output == OutputStyle.INHERIT 64 assert inst.has_const == False 65 assert inst.dest_count == 2 66 assert inst.wide == False 67 assert inst.fref == 5 68 69 def test_excludes_seed_nodes(self): 70 """Skips seed nodes from IRAM.""" 71 seed_node = IRNode( 72 name="&const", 73 opcode=RoutingOp.CONST, 74 pe=0, 75 iram_offset=10, 76 act_id=0, 77 mode=(OutputStyle.INHERIT, True, 1), 78 wide=False, 79 fref=0, 80 seed=True, # Mark as seed 81 loc=SourceLoc(1, 1), 82 ) 83 84 iram = _build_iram_for_pe([seed_node], {"&const": seed_node}, []) 85 86 assert len(iram) == 0 87 88 def test_excludes_unallocated_nodes(self): 89 """Skips nodes without iram_offset.""" 90 node = IRNode( 91 name="&unallocated", 92 opcode=ArithOp.ADD, 93 pe=0, 94 iram_offset=None, # Not allocated 95 act_id=0, 96 mode=(OutputStyle.SINK, False, 0), 97 wide=False, 98 fref=0, 99 loc=SourceLoc(1, 1), 100 ) 101 102 iram = _build_iram_for_pe([node], {"&unallocated": node}, []) 103 104 assert len(iram) == 0 105 106 def test_excludes_nodes_without_mode(self): 107 """Skips nodes without mode (output style) allocation.""" 108 node = IRNode( 109 name="&no_mode", 110 opcode=ArithOp.ADD, 111 pe=0, 112 iram_offset=5, 113 act_id=0, 114 mode=None, # No mode set 115 wide=False, 116 fref=0, 117 loc=SourceLoc(1, 1), 118 ) 119 120 iram = _build_iram_for_pe([node], {"&no_mode": node}, []) 121 122 assert len(iram) == 0 123 124 def test_handles_mem_op_instructions(self): 125 """Produces Instruction objects for MemOp opcodes.""" 126 node = IRNode( 127 name="&read", 128 opcode=MemOp.READ, 129 pe=0, 130 iram_offset=1, 131 act_id=0, 132 sm_id=0, 133 mode=(OutputStyle.CHANGE_TAG, False, 1), 134 wide=True, 135 fref=3, 136 loc=SourceLoc(2, 1), 137 ) 138 139 iram = _build_iram_for_pe([node], {"&read": node}, []) 140 141 assert 1 in iram 142 inst = iram[1] 143 assert inst.opcode == MemOp.READ 144 assert inst.output == OutputStyle.CHANGE_TAG 145 assert inst.wide == True 146 assert inst.fref == 3 147 148 149class TestAC63PackInstructionValues: 150 """AC6.3: IRAM write data uses pack_instruction() — verify known values.""" 151 152 def test_known_instruction_pack_value(self): 153 """Verify a known Instruction packs to expected 16-bit value. 154 155 Example from phase design: 156 inst = Instruction(opcode=ArithOp.ADD, output=OutputStyle.INHERIT, has_const=False, dest_count=1, wide=False, fref=0) 157 Should pack to 0x0000 (all bits zero). 158 """ 159 inst = Instruction( 160 opcode=ArithOp.ADD, 161 output=OutputStyle.INHERIT, 162 has_const=False, 163 dest_count=1, 164 wide=False, 165 fref=0, 166 ) 167 packed = pack_instruction(inst) 168 # ADD opcode = 0, INHERIT = 0b000, has_const=False → mode=0b000, wide=0, fref=0 169 # [type:1=0][opcode:5=0][mode:3=0][wide:1=0][fref:6=0] = 0x0000 170 assert packed == 0x0000 171 172 def test_roundtrip_pack_unpack(self): 173 """Verify pack_instruction and unpack_instruction are inverses.""" 174 orig = Instruction( 175 opcode=ArithOp.ADD, 176 output=OutputStyle.INHERIT, 177 has_const=True, 178 dest_count=2, 179 wide=True, 180 fref=15, 181 ) 182 packed = pack_instruction(orig) 183 unpacked = unpack_instruction(packed) 184 assert unpacked == orig 185 186 187class TestTask2FrameSetupTokens: 188 """Task 2: Frame setup token generation.""" 189 190 def test_ac61_assembly_result_has_setup_tokens(self): 191 """AC6.1: AssemblyResult includes setup_tokens field.""" 192 node = IRNode( 193 name="&add", 194 opcode=ArithOp.ADD, 195 pe=0, 196 iram_offset=0, 197 act_id=0, 198 mode=(OutputStyle.INHERIT, False, 2), 199 wide=False, 200 fref=0, 201 loc=SourceLoc(1, 1), 202 ) 203 system = SystemConfig(pe_count=1, sm_count=1) 204 graph = IRGraph({"&add": node}, system=system) 205 206 result = generate_direct(graph) 207 208 assert hasattr(result, 'setup_tokens') 209 assert isinstance(result.setup_tokens, list) 210 211 def test_ac62_token_ordering(self): 212 """AC6.2: Token stream ordering verified. 213 214 Order: SM init → IRAM writes → ALLOC → frame slot writes → seeds 215 """ 216 # Create a simple graph with data def and instruction 217 data_def = IRDataDef( 218 name="@data", 219 sm_id=0, 220 cell_addr=10, 221 value=42, 222 loc=SourceLoc(1, 1), 223 ) 224 node = IRNode( 225 name="&add", 226 opcode=ArithOp.ADD, 227 pe=0, 228 iram_offset=0, 229 act_id=0, 230 mode=(OutputStyle.INHERIT, False, 2), 231 wide=False, 232 fref=0, 233 loc=SourceLoc(2, 1), 234 ) 235 system = SystemConfig(pe_count=1, sm_count=1) 236 graph = IRGraph( 237 {"&add": node}, 238 data_defs=[data_def], 239 system=system 240 ) 241 242 result = generate_direct(graph) 243 tokens = result.setup_tokens 244 245 # Verify ordering: SM init should come before IRAM writes 246 sm_tokens = [t for t in tokens if isinstance(t, SMToken)] 247 iram_tokens = [t for t in tokens if isinstance(t, PELocalWriteToken) and t.region == 0] 248 alloc_tokens = [t for t in tokens if isinstance(t, FrameControlToken)] 249 250 if sm_tokens and iram_tokens: 251 assert tokens.index(sm_tokens[0]) < tokens.index(iram_tokens[0]) 252 if iram_tokens and alloc_tokens: 253 assert tokens.index(iram_tokens[0]) < tokens.index(alloc_tokens[0]) 254 255 def test_ac63_iram_write_uses_pack_instruction(self): 256 """AC6.3: IRAM write PELocalWriteTokens carry pack_instruction() data.""" 257 node = IRNode( 258 name="&add", 259 opcode=ArithOp.ADD, 260 pe=0, 261 iram_offset=5, 262 act_id=0, 263 mode=(OutputStyle.INHERIT, True, 2), 264 wide=False, 265 fref=12, 266 loc=SourceLoc(1, 1), 267 ) 268 system = SystemConfig(pe_count=1, sm_count=1) 269 graph = IRGraph({"&add": node}, system=system) 270 271 result = generate_direct(graph) 272 iram_tokens = [t for t in result.setup_tokens 273 if isinstance(t, PELocalWriteToken) and t.region == 0] 274 275 assert len(iram_tokens) > 0 276 token = iram_tokens[0] 277 278 # Verify that the data matches packed instruction 279 expected_inst = Instruction( 280 opcode=ArithOp.ADD, 281 output=OutputStyle.INHERIT, 282 has_const=True, 283 dest_count=2, 284 wide=False, 285 fref=12, 286 ) 287 expected_data = pack_instruction(expected_inst) 288 assert token.data == expected_data 289 290 def test_alloc_tokens_per_activation(self): 291 """ALLOC tokens generated for each activation on each PE.""" 292 node1 = IRNode( 293 name="&a", 294 opcode=ArithOp.ADD, 295 pe=0, 296 iram_offset=0, 297 act_id=1, 298 mode=(OutputStyle.INHERIT, False, 2), 299 wide=False, 300 fref=0, 301 loc=SourceLoc(1, 1), 302 ) 303 node2 = IRNode( 304 name="&b", 305 opcode=ArithOp.SUB, 306 pe=0, 307 iram_offset=1, 308 act_id=2, 309 mode=(OutputStyle.INHERIT, False, 2), 310 wide=False, 311 fref=0, 312 loc=SourceLoc(2, 1), 313 ) 314 system = SystemConfig(pe_count=1, sm_count=1) 315 graph = IRGraph( 316 {"&a": node1, "&b": node2}, 317 system=system 318 ) 319 320 result = generate_direct(graph) 321 alloc_tokens = [t for t in result.setup_tokens if isinstance(t, FrameControlToken)] 322 323 # Should have 2 ALLOC tokens (one for act_id=1, one for act_id=2) 324 assert len(alloc_tokens) == 2 325 assert all(t.op == FrameOp.ALLOC for t in alloc_tokens) 326 327 # Verify that PE configs have initial_tag_store with tuple values 328 assert len(result.pe_configs) == 1 329 pe_cfg = result.pe_configs[0] 330 assert pe_cfg.initial_tag_store, "initial_tag_store should not be empty for PE with activations" 331 for act_id, val in pe_cfg.initial_tag_store.items(): 332 assert isinstance(val, tuple) and len(val) == 2, \ 333 f"initial_tag_store[{act_id}] should be (frame_id, lane) tuple, got {val}" 334 frame_id, lane = val 335 assert isinstance(frame_id, int), f"frame_id should be int, got {type(frame_id)}" 336 assert isinstance(lane, int), f"lane should be int, got {type(lane)}" 337 338 339class TestTask3SeedTokens: 340 """Task 3: Seed token generation with act_id.""" 341 342 def test_ac66_seed_tokens_use_act_id(self): 343 """AC6.6: Seed tokens use act_id field (not ctx).""" 344 seed_node = IRNode( 345 name="&const", 346 opcode=RoutingOp.CONST, 347 pe=0, 348 iram_offset=10, 349 act_id=5, 350 const=42, 351 mode=(OutputStyle.INHERIT, True, 1), 352 wide=False, 353 fref=0, 354 seed=True, 355 loc=SourceLoc(1, 1), 356 ) 357 edge = IREdge(source="&const", dest="&consumer", port=Port.L) 358 consumer = IRNode( 359 name="&consumer", 360 opcode=ArithOp.ADD, 361 pe=1, 362 iram_offset=0, 363 act_id=3, 364 mode=(OutputStyle.INHERIT, False, 2), 365 wide=False, 366 fref=0, 367 loc=SourceLoc(2, 1), 368 ) 369 system = SystemConfig(pe_count=2, sm_count=1) 370 graph = IRGraph( 371 {"&const": seed_node, "&consumer": consumer}, 372 edges=[edge], 373 system=system 374 ) 375 376 result = generate_direct(graph) 377 seed_tokens = result.seed_tokens 378 379 assert len(seed_tokens) > 0 380 token = seed_tokens[0] 381 # Verify token uses act_id field 382 assert hasattr(token, 'act_id') 383 assert token.act_id == 3 384 385 def test_dyadic_seed_token_no_gen(self): 386 """DyadToken seed tokens have no gen field.""" 387 seed_node = IRNode( 388 name="&const", 389 opcode=RoutingOp.CONST, 390 pe=0, 391 iram_offset=10, 392 act_id=0, 393 const=100, 394 mode=(OutputStyle.INHERIT, True, 1), 395 wide=False, 396 fref=0, 397 seed=True, 398 loc=SourceLoc(1, 1), 399 ) 400 edge = IREdge(source="&const", dest="&dyadic", port=Port.L) 401 dyadic_node = IRNode( 402 name="&dyadic", 403 opcode=ArithOp.ADD, 404 pe=1, 405 iram_offset=0, 406 act_id=1, 407 mode=(OutputStyle.INHERIT, False, 2), 408 wide=False, 409 fref=0, 410 loc=SourceLoc(2, 1), 411 ) 412 system = SystemConfig(pe_count=2, sm_count=1) 413 graph = IRGraph( 414 {"&const": seed_node, "&dyadic": dyadic_node}, 415 edges=[edge], 416 system=system 417 ) 418 419 result = generate_direct(graph) 420 dyadic_tokens = [t for t in result.seed_tokens if isinstance(t, DyadToken)] 421 422 assert len(dyadic_tokens) > 0 423 token = dyadic_tokens[0] 424 # Verify no gen field exists 425 assert not hasattr(token, 'gen') 426 427 def test_generate_direct_produces_configs(self): 428 """generate_direct() produces PEConfigs with Instruction IRAM.""" 429 node = IRNode( 430 name="&add", 431 opcode=ArithOp.ADD, 432 pe=0, 433 iram_offset=3, 434 act_id=0, 435 mode=(OutputStyle.INHERIT, False, 2), 436 wide=False, 437 fref=7, 438 loc=SourceLoc(1, 1), 439 ) 440 system = SystemConfig(pe_count=1, sm_count=1) 441 graph = IRGraph({"&add": node}, system=system) 442 443 result = generate_direct(graph) 444 445 assert len(result.pe_configs) == 1 446 pe_cfg = result.pe_configs[0] 447 assert pe_cfg.pe_id == 0 448 assert 3 in pe_cfg.iram 449 450 inst = pe_cfg.iram[3] 451 assert isinstance(inst, Instruction) 452 assert inst.opcode == ArithOp.ADD 453 454 def test_generate_tokens_ordering(self): 455 """generate_tokens() ordering: SM init → IRAM → ALLOC → frame slots → seeds.""" 456 data_def = IRDataDef( 457 name="@data", 458 sm_id=0, 459 cell_addr=5, 460 value=99, 461 loc=SourceLoc(1, 1), 462 ) 463 node = IRNode( 464 name="&add", 465 opcode=ArithOp.ADD, 466 pe=0, 467 iram_offset=0, 468 act_id=0, 469 mode=(OutputStyle.INHERIT, False, 2), 470 wide=False, 471 fref=0, 472 loc=SourceLoc(2, 1), 473 ) 474 seed_node = IRNode( 475 name="&const", 476 opcode=RoutingOp.CONST, 477 pe=0, 478 iram_offset=10, 479 act_id=0, 480 const=42, 481 mode=(OutputStyle.INHERIT, True, 1), 482 wide=False, 483 fref=0, 484 seed=True, 485 loc=SourceLoc(3, 1), 486 ) 487 edge = IREdge(source="&const", dest="&add", port=Port.L) 488 system = SystemConfig(pe_count=1, sm_count=1) 489 graph = IRGraph( 490 {"&add": node, "&const": seed_node}, 491 edges=[edge], 492 data_defs=[data_def], 493 system=system 494 ) 495 496 tokens = generate_tokens(graph) 497 498 # Verify that setup_tokens come before seeds 499 setup_count = sum(1 for t in tokens 500 if isinstance(t, (SMToken, PELocalWriteToken, FrameControlToken))) 501 seed_count = sum(1 for t in tokens if isinstance(t, (DyadToken, MonadToken))) 502 503 # Find indices 504 if setup_count > 0 and seed_count > 0: 505 first_setup = next(i for i, t in enumerate(tokens) 506 if isinstance(t, (SMToken, PELocalWriteToken, FrameControlToken))) 507 first_seed = next(i for i, t in enumerate(tokens) 508 if isinstance(t, (DyadToken, MonadToken))) 509 assert first_setup < first_seed 510 511 512class TestAC65T0BootstrapPacking: 513 """AC6.5: T0 bootstrap data uses pack_token() for packed flits.""" 514 515 def test_pack_token_for_t0_bootstrap(self): 516 """Verify pack_token() can pack tokens for T0 bootstrap storage. 517 518 In T0 bootstrap, tokens are written to T0 storage as packed flit sequences. 519 Each flit is written via SMToken(WRITE) to T0 addresses. 520 """ 521 # Create a sample dyadic token that might be bootstrapped to T0 522 token = DyadToken( 523 target=1, 524 offset=10, 525 act_id=2, 526 data=0x1234, 527 port=Port.L, 528 ) 529 530 # Pack the token 531 flits = pack_token(token) 532 533 # Verify we get a sequence of flits 534 assert isinstance(flits, list) 535 assert len(flits) >= 1 # At least flit 1 (header) 536 assert all(isinstance(f, int) for f in flits) 537 538 # For T0 bootstrap, each flit would be written as: 539 # SMToken(target=sm_id, addr=t0_offset + i, op=MemOp.WRITE, data=flit) 540 for i, flit in enumerate(flits): 541 # Verify flit is 16-bit 542 assert 0 <= flit <= 0xFFFF, f"Flit {i} out of range: {flit}" 543 544 def test_pack_token_monad_for_t0(self): 545 """MonadToken can also be packed for T0 bootstrap.""" 546 token = MonadToken( 547 target=2, 548 offset=5, 549 act_id=1, 550 data=0x5678, 551 inline=False, 552 ) 553 554 flits = pack_token(token) 555 assert len(flits) >= 1 556 assert all(0 <= f <= 0xFFFF for f in flits) 557 558 559class TestIntegration: 560 """Integration tests for complete codegen pipeline.""" 561 562 def test_multinode_multiactivation(self): 563 """Complex graph with multiple nodes and activations.""" 564 nodes = [ 565 IRNode( 566 name="&a", 567 opcode=ArithOp.ADD, 568 pe=0, 569 iram_offset=0, 570 act_id=0, 571 mode=(OutputStyle.INHERIT, False, 2), 572 wide=False, 573 fref=0, 574 loc=SourceLoc(1, 1), 575 ), 576 IRNode( 577 name="&b", 578 opcode=ArithOp.SUB, 579 pe=0, 580 iram_offset=1, 581 act_id=0, 582 mode=(OutputStyle.INHERIT, False, 2), 583 wide=False, 584 fref=0, 585 loc=SourceLoc(2, 1), 586 ), 587 IRNode( 588 name="&c", 589 opcode=ArithOp.INC, 590 pe=1, 591 iram_offset=0, 592 act_id=1, 593 mode=(OutputStyle.INHERIT, False, 1), 594 wide=False, 595 fref=0, 596 loc=SourceLoc(3, 1), 597 ), 598 ] 599 edges = [ 600 IREdge(source="&a", dest="&b", port=Port.L), 601 IREdge(source="&b", dest="&c", port=Port.L), 602 ] 603 system = SystemConfig(pe_count=2, sm_count=1) 604 graph = IRGraph( 605 {node.name: node for node in nodes}, 606 edges=edges, 607 system=system 608 ) 609 610 result = generate_direct(graph) 611 612 # Check pe_configs 613 assert len(result.pe_configs) == 2 614 assert result.pe_configs[0].pe_id == 0 615 assert result.pe_configs[1].pe_id == 1 616 617 # Check setup_tokens 618 assert len(result.setup_tokens) > 0 619 620 # Check that ALLOC tokens are generated for act_id=1 621 alloc_tokens = [t for t in result.setup_tokens if isinstance(t, FrameControlToken)] 622 assert any(t.act_id == 1 for t in alloc_tokens) 623 624 625class TestAC6_4SetupTokensWithFrameDest: 626 """AC6.4: Verify destination frame slot writes use pack_flit1() with is_dest=True.""" 627 628 def test_setup_tokens_frame_writes_use_pack_flit1(self): 629 """Verify setup_tokens use pack_flit1() for frame destination writes. 630 631 This test verifies that the codegen correctly uses pack_flit1() to encode 632 FrameDest objects when writing to frame slots (region=1, is_dest=True). 633 """ 634 from dataclasses import replace 635 from asm.allocate import allocate 636 from asm.ir import ResolvedDest 637 638 # Create a simple graph with a node that has dest_l/dest_r 639 source_node = IRNode( 640 name="&source", 641 opcode=ArithOp.ADD, 642 pe=0, 643 iram_offset=0, 644 act_id=0, 645 mode=(OutputStyle.INHERIT, False, 1), 646 wide=False, 647 fref=0, 648 loc=SourceLoc(1, 1), 649 ) 650 651 dest_node = IRNode( 652 name="&dest", 653 opcode=ArithOp.SUB, 654 pe=0, 655 iram_offset=1, 656 act_id=0, 657 mode=(OutputStyle.SINK, False, 0), 658 wide=False, 659 fref=0, 660 loc=SourceLoc(2, 1), 661 ) 662 663 edge = IREdge(source="&source", dest="&dest", port=Port.L) 664 665 system = SystemConfig(pe_count=1, sm_count=0) 666 graph = IRGraph( 667 nodes={"&source": source_node, "&dest": dest_node}, 668 edges=[edge], 669 regions=[], 670 system=system, 671 call_sites=[], 672 ) 673 674 # Run allocate to set up frame layouts and dest_l/dest_r 675 allocated_graph = allocate(graph) 676 if allocated_graph.errors: 677 raise ValueError(f"Allocation errors: {allocated_graph.errors}") 678 679 # Now run generate_direct on the allocated graph 680 codegen_result = generate_direct(allocated_graph) 681 682 # Find PELocalWriteToken entries with is_dest=True (frame dest writes) 683 dest_write_tokens = [ 684 t for t in codegen_result.setup_tokens 685 if isinstance(t, PELocalWriteToken) and t.is_dest 686 ] 687 688 # Should have at least one frame dest write 689 assert len(dest_write_tokens) > 0, \ 690 "Should have PELocalWriteToken with is_dest=True for frame destinations" 691 692 # Verify each dest write token has valid packed flit1 data 693 for token in dest_write_tokens: 694 assert token.region == 1, \ 695 f"Frame dest writes should use region=1 (frame), got {token.region}" 696 # Data should be a valid packed flit1 (16-bit) 697 assert 0 <= token.data <= 0xFFFF, \ 698 f"Frame dest data should be 16-bit, got {token.data}"