OR-1 dataflow CPU sketch

feat(asm): rewrite built-in macros with parameterized opcodes and @ret wiring

- #reduce_add_N replaced with #reduce_N op (parameterized opcode via ${op})
- #loop_counted gains @ret_body/@ret_exit markers with pass fanout node
to avoid 3-edge constraint on brgt compare node
- #loop_while gains @ret_body/@ret_exit markers
- Tests updated for new macro names and invocation syntax

Orual cf93d659 36bbe7d6

+67 -56
+31 -24
asm/builtins.py
··· 3 3 These macros are automatically available in all dfasm programs. 4 4 The BUILTIN_MACROS string is prepended to user source before parsing. 5 5 6 - Macro parameters can appear in edge endpoints via ${param} syntax. 7 - The expand pass handles parameter refs in source/dest positions, 8 - correctly skipping scope qualification for external references. 6 + Macro parameters can appear in edge endpoints via ${param} syntax, 7 + in opcode position via ${op}, and in qualifier positions via |${pe} 8 + and :${port}. The expand pass resolves all ParamRef placeholders. 9 9 10 - The opcode position cannot be parameterized (grammar constraint: 11 - opcode is a keyword terminal). Per-opcode variants are provided 12 - where needed (e.g., reduce_add_N). 10 + Macros use @ret/@ret_name markers for output wiring via the |> syntax 11 + at call sites. 13 12 """ 14 13 15 14 BUILTIN_MACROS = """\ ··· 17 16 ; These macros are automatically available in all dfasm programs. 18 17 19 18 ; --- Counted loop --- 20 - ; Expands to: &counter (add), &compare (brgt), &inc (inc) 21 - ; Internal wiring: counter -> compare:L, compare -> inc:L, inc -> counter:R 22 - ; User wires: init -> counter:L, limit -> compare:R, compare -> body:L, compare -> exit:R 19 + ; Topology: counter -> compare -> body_fan (fanout) -> inc -> feedback 20 + ; compare L -> body_fan (pass as fanout) -> @ret_body + &inc 21 + ; compare R -> @ret_exit 22 + ; User wires: init -> counter:L, limit -> compare:R 23 + ; Call with: #loop_counted |> body=&process, exit=&done 23 24 #loop_counted |> { 24 25 &counter <| add 25 26 &compare <| brgt 26 27 &counter |> &compare:L 28 + &body_fan <| pass 29 + &compare |> &body_fan:L 27 30 &inc <| inc 28 - &compare |> &inc:L 31 + &body_fan |> &inc:L 29 32 &inc |> &counter:R 33 + &body_fan |> @ret_body 34 + &compare |> @ret_exit:R 30 35 } 31 36 32 37 ; --- Condition-tested loop --- 33 - ; Expands to: &gate (gate) 34 - ; User wires: test_node -> gate:L, gate -> body:L, gate -> exit:R 38 + ; Topology: &gate (gate) -> L=body, R=exit 39 + ; User wires: test_value -> gate:L 40 + ; Call with: #loop_while |> body=&process, exit=&done 35 41 #loop_while |> { 36 42 &gate <| gate 43 + &gate |> @ret_body 44 + &gate |> @ret_exit:R 37 45 } 38 46 39 47 ; --- Permit injection (per-arity variants) --- ··· 69 77 &p3 |> &merge_b:R 70 78 } 71 79 72 - ; --- Binary reduction trees (per-arity, per-opcode variants) --- 73 - ; Note: The opcode position cannot be parameterized (grammar constraint). 74 - ; Per-opcode variants are provided instead. 75 - #reduce_add_2 |> { 76 - &r <| add 80 + ; --- Binary reduction trees (parameterized opcode) --- 81 + ; Usage: #reduce_2 add, #reduce_3 sub, etc. 82 + #reduce_2 op |> { 83 + &r <| ${op} 77 84 } 78 85 79 - #reduce_add_3 |> { 80 - &r0 <| add 81 - &r1 <| add 86 + #reduce_3 op |> { 87 + &r0 <| ${op} 88 + &r1 <| ${op} 82 89 &r0 |> &r1:L 83 90 } 84 91 85 - #reduce_add_4 |> { 86 - &r0 <| add 87 - &r1 <| add 88 - &r2 <| add 92 + #reduce_4 op |> { 93 + &r0 <| ${op} 94 + &r1 <| ${op} 95 + &r2 <| ${op} 89 96 &r0 |> &r2:L 90 97 &r1 |> &r2:R 91 98 }
+36 -32
tests/test_builtins.py
··· 82 82 83 83 assert "#loop_counted" in BUILTIN_MACROS 84 84 assert "#permit_inject_1" in BUILTIN_MACROS 85 - assert "#reduce_add_2" in BUILTIN_MACROS 85 + assert "#reduce_2 op" in BUILTIN_MACROS 86 86 87 87 def test_builtins_prepended_to_pipeline(self): 88 88 """Verify that built-in macros are prepended in run_pipeline.""" ··· 154 154 """Built-in macro is used when user doesn't define it.""" 155 155 source = """ 156 156 @system pe=1, sm=0 157 - #reduce_add_2 157 + #reduce_2 add 158 158 """ 159 159 graph = run_pipeline(source) 160 160 assert graph is not None ··· 162 162 163 163 node_names = list(graph.nodes.keys()) 164 164 has_r = any("&r" in n for n in node_names) 165 - assert has_r, f"Expected &r node from #reduce_add_2 expansion in {node_names}" 165 + assert has_r, f"Expected &r node from #reduce_2 expansion in {node_names}" 166 166 167 167 168 168 class TestAC83_LoopCountedTopology: ··· 178 178 """ 179 179 source = """ 180 180 @system pe=1, sm=0 181 - #loop_counted 181 + &body <| pass 182 + &exit <| pass 183 + #loop_counted |> body=&body, exit=&exit 182 184 """ 183 185 graph = run_pipeline(source) 184 186 assert len(graph.errors) == 0 ··· 204 206 """ 205 207 source = """ 206 208 @system pe=1, sm=0 207 - #loop_counted 209 + &body <| pass 210 + &exit <| pass 211 + #loop_counted |> body=&body, exit=&exit 208 212 """ 209 213 graph = run_pipeline(source) 210 214 ··· 232 236 class TestAC84_EndToEnd: 233 237 """AC8.4: Program using built-in macros assembles and runs in emulator.""" 234 238 235 - def test_builtin_reduce_add_2_invoked_assembles(self): 236 - """#reduce_add_2 invocation assembles through the full pipeline.""" 239 + def test_builtin_reduce_2_invoked_assembles(self): 240 + """#reduce_2 add invocation assembles through the full pipeline.""" 237 241 source = """ 238 242 @system pe=1, sm=0 239 - #reduce_add_2 243 + #reduce_2 add 240 244 """ 241 245 result = assemble(source) 242 246 assert result is not None, "assemble() should succeed" 243 247 assert len(result.pe_configs) > 0, "Should have PE configs" 244 248 245 - def test_builtin_reduce_add_2_runs_in_emulator(self): 246 - """#reduce_add_2 runs through emulator without error.""" 249 + def test_builtin_reduce_2_runs_in_emulator(self): 250 + """#reduce_2 add runs through emulator without error.""" 247 251 source = """ 248 252 @system pe=1, sm=0 249 - #reduce_add_2 253 + #reduce_2 add 250 254 """ 251 255 outputs = run_program_direct(source, until=500) 252 256 assert 0 in outputs, "PE 0 should exist in outputs" 253 257 254 - def test_builtin_reduce_add_2_produces_output_when_wired(self): 255 - """#reduce_add_2 produces correct sum when inputs and output are wired.""" 258 + def test_builtin_reduce_2_produces_output_when_wired(self): 259 + """#reduce_2 add produces correct sum when inputs and output are wired.""" 256 260 source = """ 257 261 @system pe=1, sm=0 258 262 &a <| const, 3 259 263 &b <| const, 4 260 264 &out <| pass 261 - #reduce_add_2 262 - &a |> #reduce_add_2_0.&r:L 263 - &b |> #reduce_add_2_0.&r:R 264 - #reduce_add_2_0.&r |> &out:L 265 + #reduce_2 add 266 + &a |> #reduce_2_0.&r:L 267 + &b |> #reduce_2_0.&r:R 268 + #reduce_2_0.&r |> &out:L 265 269 """ 266 270 outputs = run_program_direct(source, until=500) 267 271 all_values = [] ··· 282 286 outputs = run_program_direct(source, until=500) 283 287 assert 0 in outputs, "PE 0 should exist in outputs" 284 288 285 - def test_builtin_reduce_add_3_assembles(self): 286 - """#reduce_add_3 invocation assembles through the full pipeline.""" 289 + def test_builtin_reduce_3_assembles(self): 290 + """#reduce_3 add invocation assembles through the full pipeline.""" 287 291 source = """ 288 292 @system pe=1, sm=0 289 - #reduce_add_3 293 + #reduce_3 add 290 294 """ 291 295 result = assemble(source) 292 296 assert result is not None, "assemble() should succeed" 293 297 assert len(result.pe_configs) > 0, "Should have PE configs" 294 298 295 - def test_builtin_reduce_add_3_runs_in_emulator(self): 296 - """#reduce_add_3 runs through emulator without error.""" 299 + def test_builtin_reduce_3_runs_in_emulator(self): 300 + """#reduce_3 add runs through emulator without error.""" 297 301 source = """ 298 302 @system pe=1, sm=0 299 - #reduce_add_3 303 + #reduce_3 add 300 304 """ 301 305 outputs = run_program_direct(source, until=500) 302 306 assert 0 in outputs, "PE 0 should exist in outputs" ··· 322 326 assert macro_name in BUILTIN_MACROS, \ 323 327 f"Expected {macro_name} definition in BUILTIN_MACROS" 324 328 325 - def test_reduce_add_variants_defined_in_builtins(self): 326 - """All #reduce_add_2 through #reduce_add_4 are defined.""" 329 + def test_reduce_variants_defined_in_builtins(self): 330 + """All #reduce_2 through #reduce_4 are defined with op parameter.""" 327 331 from asm.builtins import BUILTIN_MACROS 328 332 329 333 for i in range(2, 5): 330 - macro_name = f"#reduce_add_{i}" 331 - assert macro_name in BUILTIN_MACROS, \ 332 - f"Expected {macro_name} definition in BUILTIN_MACROS" 334 + macro_name = f"#reduce_{i}" 335 + assert f"{macro_name} op" in BUILTIN_MACROS, \ 336 + f"Expected '{macro_name} op' definition in BUILTIN_MACROS" 333 337 334 - start = BUILTIN_MACROS.find(macro_name) 335 - end = BUILTIN_MACROS.find("#", start + 1) 338 + start = BUILTIN_MACROS.find(f"{macro_name} op") 339 + end = BUILTIN_MACROS.find("\n#", start + 1) 336 340 if end == -1: 337 341 end = len(BUILTIN_MACROS) 338 342 macro_body = BUILTIN_MACROS[start:end] 339 343 340 - assert "add" in macro_body, \ 341 - f"{macro_name} should use 'add' opcode for reduction" 344 + assert "${op}" in macro_body, \ 345 + f"{macro_name} should use '${{op}}' parameter for opcode" 342 346 343 347 344 348 class TestBuiltinComposition: