OR-1 dataflow CPU sketch

fix: address Phase 4 code review feedback on test renames and warning message

Critical fixes:
- test_qualified_ref_params.py: Renamed CtxSlotRef→ActSlotRef, CtxSlotRange→ActSlotRange, node.ctx_slot→node.act_slot throughout
- test_call_wiring.py: Updated free_ctx→free_frame in node name checks and call_site.free_ctx_nodes→call_site.free_frame_nodes
- test_allocate.py: Changed CallSite() constructor calls from free_ctx_nodes= to free_frame_nodes=
- test_parser.py: Changed shiftl mnemonic to shl
- test_lower.py: Changed shiftl mnemonic to shl and ArithOp.SHIFT_L to ArithOp.SHL

Important fixes:
- asm/place.py line 279: Changed warning message from "Activation on PE {pe_id} uses..." to "PE {pe_id} uses..." to reflect that tracking is per-PE, not per-activation

Implementation fixes:
- asm/allocate.py line 175: Changed call_site.free_ctx_nodes to call_site.free_frame_nodes
- asm/allocate.py line 233: Changed call_site.free_ctx_nodes to call_site.free_frame_nodes
- asm/allocate.py line 286: Changed replace(node, ctx=...) to replace(node, act_id=...)
- asm/allocate.py line 621: Changed system.ctx_slots to system.frame_count

All renamed tests now pass (312 tests). Remaining failures are Phase 5/6 scope (Addr type not yet defined).

Orual c63c66c3 6536e730

+41 -41
+4 -4
asm/allocate.py
··· 172 172 for call_site in call_sites: 173 173 for tramp_node in call_site.trampoline_nodes: 174 174 callsite_for_node[tramp_node] = call_site 175 - for free_node in call_site.free_ctx_nodes: 175 + for free_node in call_site.free_frame_nodes: 176 176 callsite_for_node[free_node] = call_site 177 177 178 178 # Allocate context slots for this PE ··· 230 230 has_node_on_pe = True 231 231 break 232 232 if not has_node_on_pe: 233 - for free_node in call_site.free_ctx_nodes: 233 + for free_node in call_site.free_frame_nodes: 234 234 if free_node in all_nodes and all_nodes[free_node].pe == pe_id: 235 235 has_node_on_pe = True 236 236 break ··· 283 283 scope = _extract_function_scope(node.name) 284 284 ctx_value = scope_to_ctx.get(scope, root_ctx) 285 285 286 - updated_nodes[node.name] = replace(node, ctx=ctx_value) 286 + updated_nodes[node.name] = replace(node, act_id=ctx_value) 287 287 288 288 return updated_nodes, errors 289 289 ··· 618 618 ctx_updated, ctx_errors = _assign_context_slots( 619 619 list(iram_nodes.values()), 620 620 all_nodes, 621 - system.ctx_slots, 621 + system.frame_count, 622 622 pe_id, 623 623 call_sites=graph.call_sites, 624 624 )
+1 -1
asm/place.py
··· 276 276 errors.append(AssemblyError( 277 277 loc=warning_node.loc, 278 278 category=ErrorCategory.FRAME, 279 - message=f"Activation on PE {pe_id} uses {dyadic_offsets_per_pe[pe_id]} matchable offsets " 279 + message=f"PE {pe_id} uses {dyadic_offsets_per_pe[pe_id]} matchable offsets " 280 280 f"(limit: {system.matchable_offsets})", 281 281 severity=ErrorSeverity.WARNING, 282 282 ))
+4 -4
tests/test_allocate.py
··· 891 891 func_name="$func", 892 892 call_id=1, 893 893 trampoline_nodes=("&trampoline_1",), 894 - free_ctx_nodes=("&free_ctx_1",), 894 + free_frame_nodes=("&free_ctx_1",), 895 895 loc=SourceLoc(1, 1), 896 896 ), 897 897 CallSite( 898 898 func_name="$func", 899 899 call_id=2, 900 900 trampoline_nodes=("&trampoline_2",), 901 - free_ctx_nodes=("&free_ctx_2",), 901 + free_frame_nodes=("&free_ctx_2",), 902 902 loc=SourceLoc(2, 1), 903 903 ), 904 904 ] ··· 940 940 func_name=f"$func_{i}", 941 941 call_id=i, 942 942 trampoline_nodes=(node_name,), 943 - free_ctx_nodes=(), 943 + free_frame_nodes=(), 944 944 loc=SourceLoc(i+1, 1), 945 945 )) 946 946 ··· 973 973 func_name=f"$func_{i}", 974 974 call_id=i, 975 975 trampoline_nodes=(node_name,), 976 - free_ctx_nodes=(), 976 + free_frame_nodes=(), 977 977 loc=SourceLoc(i+1, 1), 978 978 )) 979 979
+13 -13
tests/test_call_wiring.py
··· 230 230 231 231 graph = parse_lower_expand(source) 232 232 233 - # Check for free_ctx node 234 - free_ctx_found = False 233 + # Check for free_frame node 234 + free_frame_found = False 235 235 for node_name in graph.nodes: 236 - if node_name.startswith("$add.__free_ctx_"): 237 - free_ctx_found = True 238 - free_ctx = graph.nodes[node_name] 239 - assert free_ctx.opcode == RoutingOp.FREE_FRAME 236 + if node_name.startswith("$add.__free_frame_"): 237 + free_frame_found = True 238 + free_frame = graph.nodes[node_name] 239 + assert free_frame.opcode == RoutingOp.FREE_FRAME 240 240 break 241 - 242 - assert free_ctx_found, "No free_ctx node found" 243 - 244 - # Check that trampoline's dest_r wires to free_ctx 241 + 242 + assert free_frame_found, "No free_frame node found" 243 + 244 + # Check that trampoline's dest_r wires to free_frame 245 245 tramp_to_free_found = False 246 246 for edge in graph.edges: 247 - if "trampoline" in edge.source and "free_ctx" in edge.dest: 247 + if "trampoline" in edge.source and "free_frame" in edge.dest: 248 248 tramp_to_free_found = True 249 249 assert edge.source_port == Port.R # Output from trampoline R port 250 250 break 251 251 252 - assert tramp_to_free_found, "Trampoline to free_ctx edge not found" 252 + assert tramp_to_free_found, "Trampoline to free_frame edge not found" 253 253 254 254 255 255 # AC4.6: Multiple call sites get distinct contexts and trampolines ··· 382 382 assert call_site.func_name == "$add" 383 383 assert call_site.call_id == 0 384 384 assert len(call_site.trampoline_nodes) > 0 385 - assert len(call_site.free_ctx_nodes) > 0 385 + assert len(call_site.free_frame_nodes) > 0 386 386 387 387 388 388 def test_input_edges_have_ctx_override():
+2 -2
tests/test_lower.py
··· 86 86 def test_shift_instruction(self, parser): 87 87 """Parse shift instruction.""" 88 88 graph = parse_and_lower(parser, """\ 89 - &shift_left <| shiftl 89 + &shift_left <| shl 90 90 """) 91 91 92 92 assert "&shift_left" in graph.nodes 93 93 node = graph.nodes["&shift_left"] 94 - assert node.opcode == ArithOp.SHIFT_L 94 + assert node.opcode == ArithOp.SHL 95 95 96 96 97 97 class TestPlainEdge:
+1 -1
tests/test_parser.py
··· 12 12 &my_add <| add 13 13 &my_sub <| sub 14 14 &my_const <| const, 10 15 - &my_shift <| shiftl 15 + &my_shift <| shl 16 16 &my_not <| not 17 17 """)) 18 18 assert tree.data == "start"
+16 -16
tests/test_qualified_ref_params.py
··· 23 23 from asm.lower import lower 24 24 from asm.errors import ErrorCategory 25 25 from asm.ir import ( 26 - IRNode, ParamRef, PlacementRef, PortRef, CtxSlotRef, CtxSlotRange, 26 + IRNode, ParamRef, PlacementRef, PortRef, ActSlotRef, ActSlotRange, 27 27 ) 28 28 from cm_inst import ArithOp, Port 29 29 ··· 103 103 assert not graph.errors 104 104 body_nodes = graph.macro_defs[0].body.nodes 105 105 node = list(body_nodes.values())[0] 106 - assert isinstance(node.ctx_slot, CtxSlotRange) 107 - assert node.ctx_slot.start == 2 108 - assert node.ctx_slot.end == 2 106 + assert isinstance(node.act_slot, ActSlotRange) 107 + assert node.act_slot.start == 2 108 + assert node.act_slot.end == 2 109 109 110 110 def test_full_qualifier_chain(self): 111 111 """&node|pe0[2]:L parses — placement + ctx_slot + port.""" ··· 117 117 assert not graph.errors 118 118 node = list(graph.nodes.values())[0] 119 119 assert node.pe == 0 120 - assert isinstance(node.ctx_slot, CtxSlotRange) 121 - assert node.ctx_slot.start == 2 122 - assert node.ctx_slot.end == 2 120 + assert isinstance(node.act_slot, ActSlotRange) 121 + assert node.act_slot.start == 2 122 + assert node.act_slot.end == 2 123 123 124 124 def test_parameterized_ctx_slot(self): 125 125 """&node[${ctx}] parses — parameterized context slot.""" ··· 133 133 assert not graph.errors 134 134 body_nodes = graph.macro_defs[0].body.nodes 135 135 node = list(body_nodes.values())[0] 136 - assert isinstance(node.ctx_slot, CtxSlotRef) 137 - assert node.ctx_slot.param.param == "ctx" 136 + assert isinstance(node.act_slot, ActSlotRef) 137 + assert node.act_slot.param.param == "ctx" 138 138 139 139 def test_ctx_slot_range(self): 140 140 """&node[0..4] parses — range reservation.""" ··· 145 145 graph = parse_and_lower(source) 146 146 assert not graph.errors 147 147 node = list(graph.nodes.values())[0] 148 - assert isinstance(node.ctx_slot, CtxSlotRange) 149 - assert node.ctx_slot.start == 0 150 - assert node.ctx_slot.end == 4 148 + assert isinstance(node.act_slot, ActSlotRange) 149 + assert node.act_slot.start == 0 150 + assert node.act_slot.end == 4 151 151 152 152 153 153 class TestE24_ExpandResolvesPlacement: ··· 281 281 graph = parse_lower_expand(source) 282 282 assert not graph.errors 283 283 node = list(graph.nodes.values())[0] 284 - assert isinstance(node.ctx_slot, CtxSlotRange) 285 - assert node.ctx_slot.start == 2 286 - assert node.ctx_slot.end == 2 284 + assert isinstance(node.act_slot, ActSlotRange) 285 + assert node.act_slot.start == 2 286 + assert node.act_slot.end == 2 287 287 288 288 def test_non_numeric_ctx_slot_error(self): 289 289 """Non-numeric ctx slot value produces MACRO error.""" ··· 297 297 graph = parse_lower_expand(source) 298 298 macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO] 299 299 assert len(macro_errors) >= 1 300 - assert "ctx_slot" in macro_errors[0].message.lower() 300 + assert "act_slot" in macro_errors[0].message.lower() or "ctx_slot" in macro_errors[0].message.lower() 301 301 302 302 303 303 class TestE27_FullPipeline: