OR-1 dataflow CPU sketch
at main 788 lines 25 kB view raw
1"""Tests for frame-based allocation in the assembler. 2 3Tests AC5 acceptance criteria for frame layouts, IRAM deduplication, 4activation ID assignment, and mode computation. 5""" 6 7from dataclasses import replace 8from asm.allocate import ( 9 _assign_iram_offsets, 10 _assign_act_ids, 11 _deduplicate_iram, 12 _compute_modes, 13 _compute_frame_layouts, 14 allocate, 15) 16from asm.ir import ( 17 IRNode, 18 IREdge, 19 SourceLoc, 20 CallSite, 21 IRGraph, 22 SystemConfig, 23 FrameLayout, 24 FrameSlotMap, 25) 26from asm.errors import ErrorCategory, ErrorSeverity 27from cm_inst import ( 28 ArithOp, 29 MemOp, 30 Port, 31 RoutingOp, 32 OutputStyle, 33 TokenKind, 34) 35 36 37class TestIRAMDeduplication: 38 """AC5.1: IRAM offsets deduplicated for identical instruction templates.""" 39 40 def test_identical_instructions_share_offset(self): 41 """Two nodes with same opcode, mode, and fref share IRAM offset.""" 42 # Create two nodes with identical template 43 node1 = IRNode( 44 name="&add1", 45 opcode=ArithOp.ADD, 46 pe=0, 47 iram_offset=0, 48 mode=(OutputStyle.INHERIT, False, 1), 49 fref=0, 50 wide=False, 51 ) 52 node2 = IRNode( 53 name="&add2", 54 opcode=ArithOp.ADD, 55 pe=0, 56 iram_offset=1, 57 mode=(OutputStyle.INHERIT, False, 1), 58 fref=0, 59 wide=False, 60 ) 61 62 nodes_on_pe = {"&add1": node1, "&add2": node2} 63 deduped = _deduplicate_iram(nodes_on_pe, pe_id=0) 64 65 # Both should have same offset 66 assert deduped["&add1"].iram_offset == 0 67 assert deduped["&add2"].iram_offset == 0 68 69 def test_different_opcodes_different_offsets(self): 70 """Nodes with different opcodes keep different offsets.""" 71 node1 = IRNode( 72 name="&add", 73 opcode=ArithOp.ADD, 74 pe=0, 75 iram_offset=0, 76 mode=(OutputStyle.INHERIT, False, 1), 77 fref=0, 78 wide=False, 79 ) 80 node2 = IRNode( 81 name="&sub", 82 opcode=ArithOp.SUB, 83 pe=0, 84 iram_offset=1, 85 mode=(OutputStyle.INHERIT, False, 1), 86 fref=0, 87 wide=False, 88 ) 89 90 nodes_on_pe = {"&add": node1, "&sub": node2} 91 deduped = _deduplicate_iram(nodes_on_pe, pe_id=0) 92 93 assert deduped["&add"].iram_offset == 0 94 assert deduped["&sub"].iram_offset == 1 95 96 def test_same_opcode_different_modes_different_offsets(self): 97 """Same opcode but different modes get different offsets.""" 98 node1 = IRNode( 99 name="&add1", 100 opcode=ArithOp.ADD, 101 pe=0, 102 iram_offset=0, 103 mode=(OutputStyle.INHERIT, False, 1), 104 fref=0, 105 wide=False, 106 ) 107 node2 = IRNode( 108 name="&add2", 109 opcode=ArithOp.ADD, 110 pe=0, 111 iram_offset=1, 112 mode=(OutputStyle.INHERIT, True, 1), # has_const differs 113 fref=0, 114 wide=False, 115 ) 116 117 nodes_on_pe = {"&add1": node1, "&add2": node2} 118 deduped = _deduplicate_iram(nodes_on_pe, pe_id=0) 119 120 assert deduped["&add1"].iram_offset == 0 121 assert deduped["&add2"].iram_offset == 1 122 123 def test_seed_nodes_excluded(self): 124 """Seed nodes are excluded from deduplication.""" 125 seed_node = IRNode( 126 name="&seed", 127 opcode=ArithOp.ADD, 128 pe=0, 129 iram_offset=None, 130 seed=True, 131 ) 132 normal_node = IRNode( 133 name="&add", 134 opcode=ArithOp.ADD, 135 pe=0, 136 iram_offset=0, 137 mode=(OutputStyle.INHERIT, False, 1), 138 fref=0, 139 wide=False, 140 ) 141 142 nodes_on_pe = {"&seed": seed_node, "&add": normal_node} 143 deduped = _deduplicate_iram(nodes_on_pe, pe_id=0) 144 145 # Seed node should be unchanged 146 assert deduped["&seed"].seed 147 assert deduped["&seed"].iram_offset is None 148 149 150class TestActivationIDAssignment: 151 """AC5.3, AC5.7: Activation IDs assigned sequentially, exhaustion checked.""" 152 153 def test_single_function_gets_act_id_0(self): 154 """Single function scope gets act_id=0.""" 155 node = IRNode( 156 name="&add", 157 opcode=ArithOp.ADD, 158 pe=0, 159 iram_offset=0, 160 ) 161 162 updated, errors = _assign_act_ids([node], {"&add": node}, frame_count=8, pe_id=0) 163 164 # Root scope node should get act_id=0 165 assert updated["&add"].act_id == 0 166 assert not errors 167 168 def test_multiple_functions_different_act_ids(self): 169 """Different functions on same PE get different act_ids.""" 170 node1 = IRNode( 171 name="$func1.&add", 172 opcode=ArithOp.ADD, 173 pe=0, 174 iram_offset=0, 175 ) 176 node2 = IRNode( 177 name="$func2.&sub", 178 opcode=ArithOp.SUB, 179 pe=0, 180 iram_offset=1, 181 ) 182 183 all_nodes = {"$func1.&add": node1, "$func2.&sub": node2} 184 updated, errors = _assign_act_ids( 185 [node1, node2], all_nodes, frame_count=8, pe_id=0 186 ) 187 188 # Different functions should get different act_ids 189 assert updated["$func1.&add"].act_id == 0 190 assert updated["$func2.&sub"].act_id == 1 191 assert not errors 192 193 def test_activation_exhaustion_error(self): 194 """When >frame_count act_ids needed, error with FRAME category.""" 195 nodes = [] 196 all_nodes = {} 197 for i in range(10): 198 node = IRNode( 199 name=f"$func{i}.&op", 200 opcode=ArithOp.ADD, 201 pe=0, 202 iram_offset=i, 203 ) 204 nodes.append(node) 205 all_nodes[f"$func{i}.&op"] = node 206 207 updated, errors = _assign_act_ids(nodes, all_nodes, frame_count=8, pe_id=0) 208 209 # Should report activation exhaustion error 210 assert len(errors) > 0 211 assert errors[0].category == ErrorCategory.FRAME 212 assert "exhaustion" in errors[0].message.lower() 213 214 def test_same_function_across_pes_reuses_act_id(self): 215 """Same function on different PEs can reuse act_id.""" 216 node1 = IRNode( 217 name="$func.&add", 218 opcode=ArithOp.ADD, 219 pe=0, 220 iram_offset=0, 221 ) 222 node2 = IRNode( 223 name="$func.&sub", 224 opcode=ArithOp.SUB, 225 pe=1, 226 iram_offset=0, 227 ) 228 229 # Process PE 0 230 all_nodes = {"$func.&add": node1, "$func.&sub": node2} 231 updated0, errors0 = _assign_act_ids([node1], all_nodes, frame_count=8, pe_id=0) 232 233 # Process PE 1 234 updated1, errors1 = _assign_act_ids([node2], all_nodes, frame_count=8, pe_id=1) 235 236 # Both can have same act_id within their respective PEs 237 assert updated0["$func.&add"].act_id == 0 238 assert updated1["$func.&sub"].act_id == 0 239 240 241class TestModeComputation: 242 """AC5.5: Mode (OutputStyle + has_const + dest_count) computed from topology.""" 243 244 def test_sink_op_has_zero_dest_count(self): 245 """SM WRITE produces SINK mode with dest_count=0.""" 246 node = IRNode( 247 name="&write", 248 opcode=MemOp.WRITE, 249 const=0x100, 250 pe=0, 251 ) 252 253 edges_by_source = {"&write": []} # No outgoing edges 254 modes = _compute_modes({"&write": node}, edges_by_source) 255 256 mode = modes["&write"].mode 257 assert mode[0] == OutputStyle.SINK # OutputStyle 258 assert mode[2] == 0 # dest_count should be 0 259 260 def test_read_op_has_inherit_style(self): 261 """SM READ produces INHERIT style.""" 262 node = IRNode( 263 name="&read", 264 opcode=MemOp.READ, 265 pe=0, 266 ) 267 268 edges_by_source = {"&read": []} 269 modes = _compute_modes({"&read": node}, edges_by_source) 270 271 mode = modes["&read"].mode 272 assert mode[0] == OutputStyle.INHERIT 273 274 def test_has_const_flag_set(self): 275 """Node with const field has has_const=True.""" 276 node = IRNode( 277 name="&add_const", 278 opcode=ArithOp.ADD, 279 const=42, 280 pe=0, 281 ) 282 283 edges_by_source = {"&add_const": []} 284 modes = _compute_modes({"&add_const": node}, edges_by_source) 285 286 mode = modes["&add_const"].mode 287 assert mode[1] is True # has_const 288 289 def test_dest_count_from_edges(self): 290 """dest_count matches number of outgoing edges.""" 291 node = IRNode( 292 name="&add", 293 opcode=ArithOp.ADD, 294 pe=0, 295 ) 296 edge1 = IREdge(source="&add", dest="&sub", port=Port.L) 297 edge2 = IREdge(source="&add", dest="&mul", port=Port.R) 298 299 edges_by_source = {"&add": [edge1, edge2]} 300 modes = _compute_modes({"&add": node}, edges_by_source) 301 302 mode = modes["&add"].mode 303 assert mode[2] == 2 # dest_count 304 305 def test_free_frame_produces_sink(self): 306 """FREE_FRAME produces SINK style.""" 307 node = IRNode( 308 name="&free", 309 opcode=RoutingOp.FREE_FRAME, 310 pe=0, 311 ) 312 313 edges_by_source = {"&free": []} 314 modes = _compute_modes({"&free": node}, edges_by_source) 315 316 mode = modes["&free"].mode 317 assert mode[0] == OutputStyle.SINK 318 319 def test_ctx_override_produces_change_tag_with_dest_count_1(self): 320 """AC5.5: ctx_override=True on outgoing edge produces CHANGE_TAG mode with dest_count=1. 321 322 When a node has an outgoing edge with ctx_override=True (crossing function boundary), 323 the output mode should be CHANGE_TAG with dest_count=1. 324 """ 325 source_node = IRNode( 326 name="&source", 327 opcode=ArithOp.ADD, 328 pe=0, 329 ) 330 dest_node = IRNode( 331 name="&dest", 332 opcode=ArithOp.SUB, 333 pe=0, 334 ) 335 336 # Create edge with ctx_override=True 337 edge = IREdge( 338 source="&source", 339 dest="&dest", 340 port=Port.L, 341 ctx_override=True, 342 ) 343 344 edges_by_source = {"&source": [edge], "&dest": []} 345 nodes = {"&source": source_node, "&dest": dest_node} 346 modes = _compute_modes(nodes, edges_by_source) 347 348 mode = modes["&source"].mode 349 # ctx_override=True should produce CHANGE_TAG style 350 assert mode[0] == OutputStyle.CHANGE_TAG, \ 351 f"ctx_override=True should produce CHANGE_TAG, got {mode[0]}" 352 # dest_count should be 1 (one outgoing edge) 353 assert mode[2] == 1, \ 354 f"dest_count should be 1 for single ctx_override edge, got {mode[2]}" 355 356 357class TestFrameLayoutComputation: 358 """AC5.2, AC5.4, AC5.6: Frame slot assignment and deduplication.""" 359 360 def test_frame_layout_canonical_per_function(self): 361 """Two activations of same function produce identical FrameLayout.""" 362 # Create nodes with same structure but different names 363 node1 = IRNode( 364 name="&add1", 365 opcode=ArithOp.ADD, 366 pe=0, 367 act_id=0, 368 iram_offset=0, 369 mode=(OutputStyle.INHERIT, False, 1), 370 ) 371 node2 = IRNode( 372 name="&add2", 373 opcode=ArithOp.ADD, 374 pe=0, 375 act_id=1, 376 iram_offset=1, 377 mode=(OutputStyle.INHERIT, False, 1), 378 ) 379 380 nodes_on_pe = {"&add1": node1, "&add2": node2} 381 all_nodes = nodes_on_pe.copy() 382 edges_by_source = {"&add1": [], "&add2": []} 383 edges_by_dest = {} 384 385 updated, errors = _compute_frame_layouts( 386 nodes_on_pe, 387 edges_by_source, 388 edges_by_dest, 389 all_nodes, 390 frame_slots=64, 391 matchable_offsets=8, 392 pe_id=0, 393 ) 394 395 # Both activations should have layouts with same structure 396 layout1 = updated["&add1"].frame_layout 397 layout2 = updated["&add2"].frame_layout 398 assert layout1 is not None 399 assert layout2 is not None 400 # Layouts should have same slot structure (though may be different objects) 401 assert layout1.total_slots == layout2.total_slots 402 403 def test_frame_slot_overflow_error(self): 404 """When total slots > frame_slots, error with FRAME category.""" 405 # Create nodes that would exceed frame slots 406 nodes_on_pe = {} 407 all_nodes = {} 408 for i in range(100): 409 node = IRNode( 410 name=f"&op{i}", 411 opcode=ArithOp.ADD, 412 pe=0, 413 act_id=0, 414 iram_offset=i, 415 mode=(OutputStyle.INHERIT, True, 2), # Each needs 3+ slots 416 ) 417 nodes_on_pe[f"&op{i}"] = node 418 all_nodes[f"&op{i}"] = node 419 420 edges_by_source = {f"&op{i}": [] for i in range(100)} 421 edges_by_dest = {} 422 423 updated, errors = _compute_frame_layouts( 424 nodes_on_pe, 425 edges_by_source, 426 edges_by_dest, 427 all_nodes, 428 frame_slots=64, 429 matchable_offsets=8, 430 pe_id=0, 431 ) 432 433 # Should report frame slot overflow 434 assert len(errors) > 0 435 assert errors[0].category == ErrorCategory.FRAME 436 437 def test_match_slots_at_low_offsets(self): 438 """Match operands occupy slots 0 to matchable_offsets-1.""" 439 # Create dyadic (matchable) node 440 node1 = IRNode( 441 name="&add", 442 opcode=ArithOp.ADD, 443 pe=0, 444 act_id=0, 445 iram_offset=0, 446 mode=(OutputStyle.INHERIT, False, 1), 447 ) 448 # Monadic node shouldn't count as match 449 node2 = IRNode( 450 name="&inc", 451 opcode=ArithOp.INC, 452 pe=0, 453 act_id=0, 454 iram_offset=1, 455 mode=(OutputStyle.INHERIT, False, 1), 456 ) 457 458 nodes_on_pe = {"&add": node1, "&inc": node2} 459 all_nodes = nodes_on_pe.copy() 460 edges_by_source = {"&add": [], "&inc": []} 461 edges_by_dest = {} 462 463 updated, errors = _compute_frame_layouts( 464 nodes_on_pe, 465 edges_by_source, 466 edges_by_dest, 467 all_nodes, 468 frame_slots=64, 469 matchable_offsets=8, 470 pe_id=0, 471 ) 472 473 assert not errors 474 layout = updated["&add"].frame_layout 475 assert layout is not None 476 # Match slots should start at 0 477 assert 0 in layout.slot_map.match_slots 478 479 def test_matchable_offset_exceedance_warning(self): 480 """AC5.8: Warn when dyadic_count exceeds matchable_offsets. 481 482 Creates an activation with 9+ dyadic nodes but matchable_offsets=8. 483 Verifies: (1) errors list contains warning with FRAME category and WARNING severity, 484 (2) the function still returns a valid layout (not a hard error). 485 """ 486 # Create 9 dyadic nodes (more than matchable_offsets=8) 487 nodes_on_pe = {} 488 all_nodes = {} 489 for i in range(9): 490 node = IRNode( 491 name=f"&add{i}", 492 opcode=ArithOp.ADD, # Dyadic 493 pe=0, 494 act_id=0, 495 iram_offset=i, 496 mode=(OutputStyle.INHERIT, False, 1), 497 ) 498 nodes_on_pe[f"&add{i}"] = node 499 all_nodes[f"&add{i}"] = node 500 501 edges_by_source = {f"&add{i}": [] for i in range(9)} 502 edges_by_dest = {} 503 504 updated, errors = _compute_frame_layouts( 505 nodes_on_pe, 506 edges_by_source, 507 edges_by_dest, 508 all_nodes, 509 frame_slots=64, 510 matchable_offsets=8, # Less than 9 dyadic nodes 511 pe_id=0, 512 ) 513 514 # Should generate a warning with FRAME category and WARNING severity 515 assert len(errors) > 0, "Expected warning about matchable offset exceedance" 516 warning = errors[0] 517 assert warning.category == ErrorCategory.FRAME 518 assert warning.severity == ErrorSeverity.WARNING 519 assert "dyadic" in warning.message.lower() 520 assert "match slot" in warning.message.lower() 521 522 # Function should still return valid layout (not a hard error) 523 assert updated is not None 524 assert len(updated) == 9 525 for i in range(9): 526 assert f"&add{i}" in updated 527 assert updated[f"&add{i}"].frame_layout is not None 528 529 def test_constants_deduplicated_by_value(self): 530 """Instructions with same constant value can share slot.""" 531 node1 = IRNode( 532 name="&op1", 533 opcode=ArithOp.ADD, 534 const=42, 535 pe=0, 536 act_id=0, 537 iram_offset=0, 538 mode=(OutputStyle.INHERIT, True, 1), 539 ) 540 node2 = IRNode( 541 name="&op2", 542 opcode=ArithOp.SUB, 543 const=42, # Same constant 544 pe=0, 545 act_id=0, 546 iram_offset=1, 547 mode=(OutputStyle.INHERIT, True, 1), 548 ) 549 550 nodes_on_pe = {"&op1": node1, "&op2": node2} 551 all_nodes = nodes_on_pe.copy() 552 edges_by_source = {"&op1": [], "&op2": []} 553 edges_by_dest = {} 554 555 updated, errors = _compute_frame_layouts( 556 nodes_on_pe, 557 edges_by_source, 558 edges_by_dest, 559 all_nodes, 560 frame_slots=64, 561 matchable_offsets=8, 562 pe_id=0, 563 ) 564 565 assert not errors 566 # Both should have same layout with one const slot 567 layout = updated["&op1"].frame_layout 568 assert len(layout.slot_map.const_slots) == 1 569 570 def test_per_node_fref_differentiation(self): 571 """AC5.2: Nodes in same activation with different modes get different frefs. 572 573 Verifies that each node gets a unique fref based on its mode's slot count. 574 Node A: has_const=True, dest_count=1 → 2 slots → fref=8 575 Node B: has_const=False, dest_count=1 → 1 slot → fref=10 (8+2) 576 """ 577 # Create two nodes with different modes 578 node_a = IRNode( 579 name="&op_a", 580 opcode=ArithOp.ADD, 581 const=42, # has_const=True 582 pe=0, 583 act_id=0, 584 iram_offset=0, 585 mode=(OutputStyle.INHERIT, True, 1), # has_const=True, 1 dest → 2 slots 586 ) 587 node_b = IRNode( 588 name="&op_b", 589 opcode=ArithOp.SUB, 590 pe=0, 591 act_id=0, 592 iram_offset=1, 593 mode=(OutputStyle.INHERIT, False, 1), # has_const=False, 1 dest → 1 slot 594 ) 595 596 # Create edge from &op_a to &op_b 597 edge = IREdge(source="&op_a", dest="&op_b", port=Port.L) 598 599 nodes_on_pe = {"&op_a": node_a, "&op_b": node_b} 600 all_nodes = nodes_on_pe.copy() 601 edges_by_source = {"&op_a": [edge], "&op_b": []} 602 edges_by_dest = {"&op_b": [edge]} 603 604 matchable_offsets = 8 605 updated, errors = _compute_frame_layouts( 606 nodes_on_pe, 607 edges_by_source, 608 edges_by_dest, 609 all_nodes, 610 frame_slots=64, 611 matchable_offsets=matchable_offsets, 612 pe_id=0, 613 ) 614 615 assert not errors, f"Unexpected errors: {errors}" 616 617 # Verify both nodes got frefs 618 assert updated["&op_a"].fref is not None 619 assert updated["&op_b"].fref is not None 620 621 # Verify they have different frefs 622 fref_a = updated["&op_a"].fref 623 fref_b = updated["&op_b"].fref 624 assert fref_a != fref_b, f"Expected different frefs, got {fref_a} and {fref_b}" 625 626 # Verify fref_a starts at matchable_offsets 627 assert fref_a == matchable_offsets, f"Expected fref_a={matchable_offsets}, got {fref_a}" 628 629 # Verify fref_b = fref_a + slot_count_a (where slot_count_a = 2) 630 # Mode A: (INHERIT, True, 1) → 1 (const) + 1 (dest) = 2 slots 631 expected_fref_b = fref_a + 2 632 assert fref_b == expected_fref_b, f"Expected fref_b={expected_fref_b}, got {fref_b}" 633 634 635class TestFrameLayoutIntegration: 636 """Integration tests for full allocation pipeline.""" 637 638 def test_minimal_graph(self): 639 """Minimal graph with one node allocates successfully.""" 640 node = IRNode( 641 name="&add", 642 opcode=ArithOp.ADD, 643 pe=0, 644 act_id=0, 645 iram_offset=0, 646 mode=(OutputStyle.INHERIT, False, 1), 647 ) 648 649 updated, errors = _compute_frame_layouts( 650 {"&add": node}, 651 {"&add": []}, 652 {}, 653 {"&add": node}, 654 frame_slots=64, 655 matchable_offsets=8, 656 pe_id=0, 657 ) 658 659 assert not errors 660 assert updated["&add"].frame_layout is not None 661 assert updated["&add"].fref is not None 662 663 def test_multiple_activations_separate_layouts(self): 664 """Nodes with different act_ids get separate layout configurations.""" 665 node1 = IRNode( 666 name="&op1", 667 opcode=ArithOp.ADD, 668 pe=0, 669 act_id=0, 670 iram_offset=0, 671 mode=(OutputStyle.INHERIT, False, 1), 672 ) 673 node2 = IRNode( 674 name="&op2", 675 opcode=ArithOp.ADD, 676 pe=0, 677 act_id=1, # Different activation 678 iram_offset=1, 679 mode=(OutputStyle.INHERIT, True, 1), # Different mode 680 ) 681 682 updated, errors = _compute_frame_layouts( 683 {"&op1": node1, "&op2": node2}, 684 {"&op1": [], "&op2": []}, 685 {}, 686 {"&op1": node1, "&op2": node2}, 687 frame_slots=64, 688 matchable_offsets=8, 689 pe_id=0, 690 ) 691 692 assert not errors 693 layout1 = updated["&op1"].frame_layout 694 layout2 = updated["&op2"].frame_layout 695 # Layouts can have different sizes depending on mode 696 # (one has const, one doesn't) 697 assert layout1 is not None 698 assert layout2 is not None 699 700 def test_full_allocate_pipeline(self): 701 """Full integration test: allocate() with complete IRGraph. 702 703 Tests that allocate() correctly: 704 - Assigns IRAM offsets 705 - Assigns activation IDs 706 - Computes modes 707 - Computes frame layouts 708 - Deduplicates IRAM entries 709 - Resolves destinations to FrameDest 710 """ 711 # Create a simple graph: two arithmetic nodes with an edge 712 node_add = IRNode( 713 name="&add", 714 opcode=ArithOp.ADD, 715 const=None, 716 pe=0, 717 loc=SourceLoc(1, 1), 718 ) 719 node_inc = IRNode( 720 name="&inc", 721 opcode=ArithOp.INC, 722 const=None, 723 pe=0, 724 loc=SourceLoc(2, 1), 725 ) 726 edge = IREdge( 727 source="&add", 728 dest="&inc", 729 port=Port.L, 730 loc=SourceLoc(1, 5), 731 ) 732 733 # Create the IRGraph 734 system = SystemConfig( 735 pe_count=1, 736 sm_count=0, 737 iram_capacity=64, 738 frame_count=8, 739 frame_slots=64, 740 matchable_offsets=8, 741 ) 742 743 graph = IRGraph( 744 nodes={"&add": node_add, "&inc": node_inc}, 745 edges=[edge], 746 regions=[], 747 system=system, 748 call_sites=[], 749 ) 750 751 # Call allocate() 752 result = allocate(graph) 753 754 # Verify no errors 755 assert not result.errors, f"Unexpected errors: {result.errors}" 756 757 add_node = result.nodes["&add"] 758 inc_node = result.nodes["&inc"] 759 760 # IRAM offsets: &add is dyadic -> offset 0, &inc is monadic -> offset 1 761 assert add_node.iram_offset == 0 762 assert inc_node.iram_offset == 1 763 764 # Both in root scope -> act_id 0 765 assert add_node.act_id == 0 766 assert inc_node.act_id == 0 767 768 # &add has 1 outgoing edge, no const -> INHERIT, False, 1 769 assert add_node.mode == (OutputStyle.INHERIT, False, 1) 770 # &inc has 0 outgoing edges, no const -> SINK, False, 0 771 assert inc_node.mode == (OutputStyle.SINK, False, 0) 772 773 # Frame layouts assigned 774 assert add_node.frame_layout is not None 775 assert inc_node.frame_layout is not None 776 777 # Frefs: both in same activation, &add needs 1 dest slot, &inc needs 0 778 assert add_node.fref is not None 779 assert inc_node.fref is not None 780 781 # &add dest_l resolves to &inc 782 assert add_node.dest_l is not None 783 assert add_node.dest_l.frame_dest is not None 784 assert add_node.dest_l.frame_dest.target_pe == 0 785 assert add_node.dest_l.frame_dest.offset == 1 786 assert add_node.dest_l.frame_dest.act_id == 0 787 assert add_node.dest_l.frame_dest.port == Port.L 788 assert add_node.dest_l.frame_dest.token_kind == TokenKind.MONADIC