OR-1 dataflow CPU sketch
1"""Tests for Enhancement 2: Parameterized placement and port qualifiers (macro-enh.E2.*).
2
3Tests verify:
4- macro-enh.E2.1: Grammar accepts param_ref in placement position
5- macro-enh.E2.2: Grammar accepts param_ref in port position
6- macro-enh.E2.3: Context slot bracket syntax parses
7- macro-enh.E2.4: Expand pass resolves placement ParamRef
8- macro-enh.E2.5: Expand pass resolves port ParamRef
9- macro-enh.E2.6: Expand pass resolves context slot ParamRef
10- macro-enh.E2.7: Full pipeline with placement/port params
11
12Note: Placement qualifiers attach to the LHS qualified_ref in inst_def,
13not to the opcode. So `&n|${pe} <| add` is the correct syntax, not
14`&n <| add|${pe}`.
15"""
16
17from pathlib import Path
18
19from lark import Lark
20
21from asm import assemble, run_pipeline
22from asm.expand import expand
23from asm.lower import lower
24from asm.errors import ErrorCategory
25from asm.ir import (
26 IRNode, ParamRef, PlacementRef, PortRef, ActSlotRef, ActSlotRange,
27)
28from cm_inst import ArithOp, Port
29
30
31def _get_parser():
32 grammar_path = Path(__file__).parent.parent / "dfasm.lark"
33 return Lark(
34 grammar_path.read_text(),
35 parser="earley",
36 propagate_positions=True,
37 )
38
39
40def parse_and_lower(source: str):
41 parser = _get_parser()
42 tree = parser.parse(source)
43 return lower(tree)
44
45
46def parse_lower_expand(source: str):
47 graph = parse_and_lower(source)
48 return expand(graph)
49
50
51class TestE21_PlacementParamRef:
52 """E2.1: Grammar accepts param_ref in placement position."""
53
54 def test_placement_param_ref_in_macro_body(self):
55 """&n|${pe} <| add parses and lowers to PlacementRef."""
56 source = """
57 @system pe=1, sm=1
58 #place pe |> {
59 &n|${pe} <| add
60 }
61 """
62 graph = parse_and_lower(source)
63 assert not graph.errors
64 body_nodes = graph.macro_defs[0].body.nodes
65 node = list(body_nodes.values())[0]
66 assert isinstance(node.pe, PlacementRef)
67 assert node.pe.param.param == "pe"
68
69
70class TestE22_PortParamRef:
71 """E2.2: Grammar accepts param_ref in port position."""
72
73 def test_port_param_ref_in_edge(self):
74 """&src |> &dst:${port} parses and lowers to PortRef."""
75 source = """
76 @system pe=1, sm=1
77 #wire port |> {
78 &src <| pass
79 &dst <| add
80 &src |> &dst:${port}
81 }
82 """
83 graph = parse_and_lower(source)
84 assert not graph.errors
85 body_edges = graph.macro_defs[0].body.edges
86 port_ref_edges = [e for e in body_edges if isinstance(e.port, PortRef)]
87 assert len(port_ref_edges) == 1
88 assert port_ref_edges[0].port.param.param == "port"
89
90
91class TestE23_CtxSlotSyntax:
92 """E2.3: Context slot bracket syntax parses."""
93
94 def test_literal_ctx_slot(self):
95 """&node[2] parses — literal context slot in bracket syntax."""
96 source = """
97 @system pe=1, sm=1
98 #slot_macro |> {
99 &n[2] <| add
100 }
101 """
102 graph = parse_and_lower(source)
103 assert not graph.errors
104 body_nodes = graph.macro_defs[0].body.nodes
105 node = list(body_nodes.values())[0]
106 assert isinstance(node.act_slot, ActSlotRange)
107 assert node.act_slot.start == 2
108 assert node.act_slot.end == 2
109
110 def test_full_qualifier_chain(self):
111 """&node|pe0[2]:L parses — placement + ctx_slot + port."""
112 source = """
113 @system pe=1, sm=1
114 &n|pe0[2]:L <| add
115 """
116 graph = parse_and_lower(source)
117 assert not graph.errors
118 node = list(graph.nodes.values())[0]
119 assert node.pe == 0
120 assert isinstance(node.act_slot, ActSlotRange)
121 assert node.act_slot.start == 2
122 assert node.act_slot.end == 2
123
124 def test_parameterized_ctx_slot(self):
125 """&node[${ctx}] parses — parameterized context slot."""
126 source = """
127 @system pe=1, sm=1
128 #ctx_macro ctx |> {
129 &n[${ctx}] <| add
130 }
131 """
132 graph = parse_and_lower(source)
133 assert not graph.errors
134 body_nodes = graph.macro_defs[0].body.nodes
135 node = list(body_nodes.values())[0]
136 assert isinstance(node.act_slot, ActSlotRef)
137 assert node.act_slot.param.param == "ctx"
138
139 def test_ctx_slot_range(self):
140 """&node[0..4] parses — range reservation."""
141 source = """
142 @system pe=1, sm=1
143 &n[0..4] <| add
144 """
145 graph = parse_and_lower(source)
146 assert not graph.errors
147 node = list(graph.nodes.values())[0]
148 assert isinstance(node.act_slot, ActSlotRange)
149 assert node.act_slot.start == 0
150 assert node.act_slot.end == 4
151
152
153class TestE24_ExpandResolvesPlacement:
154 """E2.4: Expand pass resolves placement ParamRef."""
155
156 def test_resolve_pe0(self):
157 """Placement param 'pe0' resolves to PE 0."""
158 source = """
159 @system pe=2, sm=1
160 #place pe |> {
161 &n|${pe} <| add
162 }
163 #place pe0
164 """
165 graph = parse_lower_expand(source)
166 assert not graph.errors
167 node = list(graph.nodes.values())[0]
168 assert node.pe == 0
169
170 def test_resolve_pe1(self):
171 """Placement param 'pe1' resolves to PE 1."""
172 source = """
173 @system pe=2, sm=1
174 #place pe |> {
175 &n|${pe} <| add
176 }
177 #place pe1
178 """
179 graph = parse_lower_expand(source)
180 assert not graph.errors
181 node = list(graph.nodes.values())[0]
182 assert node.pe == 1
183
184 def test_invalid_placement_error(self):
185 """Invalid placement value produces MACRO error."""
186 source = """
187 @system pe=1, sm=1
188 #place pe |> {
189 &n|${pe} <| add
190 }
191 #place &banana
192 """
193 graph = parse_lower_expand(source)
194 macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO]
195 assert len(macro_errors) >= 1
196 assert "placement" in macro_errors[0].message.lower()
197
198
199class TestE25_ExpandResolvesPort:
200 """E2.5: Expand pass resolves port ParamRef."""
201
202 def test_resolve_port_L(self):
203 """Port param 'L' resolves to Port.L."""
204 source = """
205 @system pe=1, sm=1
206 #wire port |> {
207 &src <| pass
208 &dst <| add
209 &src |> &dst:${port}
210 }
211 #wire L
212 """
213 graph = parse_lower_expand(source)
214 assert not graph.errors
215 edges = [e for e in graph.edges if e.dest.endswith("&dst")]
216 assert len(edges) >= 1
217 assert edges[0].port == Port.L
218
219 def test_resolve_port_R(self):
220 """Port param 'R' resolves to Port.R."""
221 source = """
222 @system pe=1, sm=1
223 #wire port |> {
224 &src <| pass
225 &dst <| add
226 &src |> &dst:${port}
227 }
228 #wire R
229 """
230 graph = parse_lower_expand(source)
231 assert not graph.errors
232 edges = [e for e in graph.edges if e.dest.endswith("&dst")]
233 assert len(edges) >= 1
234 assert edges[0].port == Port.R
235
236 def test_invalid_port_value_produces_error(self):
237 """E2.5c: Invalid port value produces MACRO error."""
238 source = """
239 @system pe=1, sm=1
240 #wire port |> {
241 &src <| pass
242 &dst <| add
243 &src |> &dst:${port}
244 }
245 #wire X
246 """
247 graph = parse_lower_expand(source)
248 macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO]
249 assert len(macro_errors) >= 1
250 assert "port" in macro_errors[0].message.lower()
251
252 def test_invalid_source_port_value_produces_error(self):
253 """Invalid source port value produces MACRO error."""
254 source = """
255 @system pe=1, sm=1
256 #wire port |> {
257 &src <| pass
258 &dst <| add
259 &src:${port} |> &dst
260 }
261 #wire Z
262 """
263 graph = parse_lower_expand(source)
264 macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO]
265 assert len(macro_errors) >= 1
266 assert "port" in macro_errors[0].message.lower()
267
268
269class TestE26_ExpandResolvesCtxSlot:
270 """E2.6: Expand pass resolves context slot ParamRef."""
271
272 def test_resolve_ctx_slot_int(self):
273 """Ctx slot param resolves to CtxSlotRange."""
274 source = """
275 @system pe=1, sm=1
276 #ctx_macro ctx |> {
277 &n[${ctx}] <| add
278 }
279 #ctx_macro 2
280 """
281 graph = parse_lower_expand(source)
282 assert not graph.errors
283 node = list(graph.nodes.values())[0]
284 assert isinstance(node.act_slot, ActSlotRange)
285 assert node.act_slot.start == 2
286 assert node.act_slot.end == 2
287
288 def test_non_numeric_ctx_slot_error(self):
289 """Non-numeric ctx slot value produces MACRO error."""
290 source = """
291 @system pe=1, sm=1
292 #ctx_macro ctx |> {
293 &n[${ctx}] <| add
294 }
295 #ctx_macro &banana
296 """
297 graph = parse_lower_expand(source)
298 macro_errors = [e for e in graph.errors if e.category == ErrorCategory.MACRO]
299 assert len(macro_errors) >= 1
300 assert "act_slot" in macro_errors[0].message.lower() or "ctx_slot" in macro_errors[0].message.lower()
301
302
303class TestE27_FullPipeline:
304 """E2.7: Full pipeline with placement/port params."""
305
306 def test_full_pipeline_placement_param(self):
307 """Placement-parameterized macro assembles; node on correct PE."""
308 source = """
309 @system pe=2, sm=1
310 #place pe |> {
311 &n|${pe} <| add
312 }
313 &seed <| const, 5
314 #place pe1
315 &seed |> #place_0.&n:L
316 &seed |> #place_0.&n:R
317 """
318 graph = run_pipeline(source)
319 assert not graph.errors
320 placed_node = graph.nodes["#place_0.&n"]
321 assert placed_node.pe == 1
322
323 def test_full_pipeline_port_param(self):
324 """Port-parameterized macro assembles; edge targets correct port."""
325 source = """
326 @system pe=1, sm=1
327 #wire port |> {
328 &src <| pass
329 &dst <| add
330 &src |> &dst:${port}
331 }
332 &seed <| const, 5
333 #wire L
334 &seed |> #wire_0.&src
335 &seed |> #wire_0.&dst:R
336 """
337 result = assemble(source)
338 assert result is not None