OR-1 dataflow CPU sketch
1"""Tests for IRGraph serialization to dfasm source.
2
3Tests verify the serialize() function converts IRGraphs back to valid dfasm source
4and preserves structural information through round-trip parsing and lowering.
5"""
6
7from tests.pipeline import parse_and_lower, parse_lower_resolve
8
9from asm.ir import (
10 IRGraph, IRNode, IREdge, IRRegion, RegionKind, IRDataDef
11)
12from asm.serialize import serialize
13from asm.opcodes import OP_TO_MNEMONIC
14from cm_inst import ArithOp, LogicOp, MemOp, Port, RoutingOp
15
16
17class TestRoundTrip:
18 """AC11.1, AC11.2: Round-trip parse → lower → serialize → parse → lower"""
19
20 def test_simple_two_node_graph(self, parser):
21 """AC11.1: Simple graph with two nodes and one edge round-trips."""
22 source = """
23 &add|pe0 <| add
24 &const|pe0 <| const, 42
25 &add |> &const:L
26 """
27 # Parse and lower to get baseline IRGraph
28 graph1 = parse_and_lower(parser, source)
29
30 # Serialize and re-parse, lower
31 serialized = serialize(graph1)
32 graph2 = parse_and_lower(parser, serialized)
33
34 # Check structural equivalence
35 assert set(graph1.nodes.keys()) == set(graph2.nodes.keys())
36 assert len(graph1.edges) == len(graph2.edges)
37
38 def test_single_node_roundtrip(self, parser):
39 """AC11.1: Single node without edges round-trips."""
40 source = "&foo|pe0 <| add"
41
42 graph1 = parse_and_lower(parser, source)
43 serialized = serialize(graph1)
44 graph2 = parse_and_lower(parser, serialized)
45
46 assert set(graph1.nodes.keys()) == set(graph2.nodes.keys())
47 assert len(graph1.edges) == len(graph2.edges)
48
49 def test_graph_with_multiple_edges(self, parser):
50 """AC11.2: Graph with multiple edges preserves edge count."""
51 source = """
52 &a|pe0 <| add
53 &b|pe0 <| sub
54 &c|pe1 <| add
55 &a |> &b:L
56 &a |> &c:R
57 &b |> &c:L
58 """
59 graph1 = parse_and_lower(parser, source)
60 serialized = serialize(graph1)
61 graph2 = parse_and_lower(parser, serialized)
62
63 assert len(graph1.edges) == len(graph2.edges)
64 assert set(graph1.nodes.keys()) == set(graph2.nodes.keys())
65
66
67class TestPlacementQualifiers:
68 """AC11.3: All inst_def lines include |pe{N} qualifiers."""
69
70 def test_all_nodes_have_pe_qualifier(self):
71 """AC11.3: Serialized output contains |pe{N} on every inst_def."""
72 # Create a graph with nodes on different PEs
73 nodes = {
74 "&a": IRNode(name="&a", opcode=ArithOp.ADD, pe=0),
75 "&b": IRNode(name="&b", opcode=ArithOp.SUB, pe=1),
76 "&c": IRNode(name="&c", opcode=RoutingOp.CONST, const=42, pe=0),
77 }
78 graph = IRGraph(nodes=nodes)
79
80 serialized = serialize(graph)
81 lines = serialized.strip().split('\n')
82
83 # Filter to inst_def lines (contain '<|')
84 inst_lines = [l for l in lines if '<|' in l]
85
86 # Each should contain |pe{N}
87 for line in inst_lines:
88 assert '|pe' in line, f"Missing PE qualifier in: {line}"
89
90
91class TestFunctionScoping:
92 """AC11.4, AC11.7: FUNCTION regions serialize with scoping blocks."""
93
94 def test_function_region_structure(self):
95 """AC11.4, AC11.7: FUNCTION regions emit $name |> { ... } blocks."""
96 # Create a function region with unqualified node names inside
97 func_nodes = {
98 "&add": IRNode(name="&add", opcode=ArithOp.ADD, pe=0),
99 "&const": IRNode(name="&const", opcode=RoutingOp.CONST, const=10, pe=0),
100 }
101 func_body = IRGraph(nodes=func_nodes)
102 func_region = IRRegion(tag="$main", kind=RegionKind.FUNCTION, body=func_body)
103
104 graph = IRGraph(regions=[func_region])
105 serialized = serialize(graph)
106
107 # Check for function block syntax
108 assert "$main |>" in serialized
109 assert "{" in serialized
110 assert "}" in serialized
111
112 # Check that node names inside are unqualified (no $main. prefix)
113 assert "&add|pe0 <| add" in serialized or "&add|pe0 <| add" in serialized.replace('\n', ' ')
114
115 def test_unqualified_names_in_function(self):
116 """AC11.7: Names inside FUNCTION regions are unqualified."""
117 func_nodes = {
118 "&label": IRNode(name="&label", opcode=ArithOp.ADD, pe=0),
119 }
120 func_body = IRGraph(nodes=func_nodes)
121 func_region = IRRegion(tag="$main", kind=RegionKind.FUNCTION, body=func_body)
122
123 graph = IRGraph(regions=[func_region])
124 serialized = serialize(graph)
125
126 # Should not contain the qualified name $main.&label
127 assert "$main.&label" not in serialized
128 # Should contain unqualified &label
129 assert "&label" in serialized
130
131
132class TestDataDefs:
133 """AC11.5: data_def entries serialize with SM placement and cell addresses."""
134
135 def test_data_def_serialization(self):
136 """AC11.5: Data definitions serialize as @name|sm{id}:{cell} = {value}."""
137 data_defs = [
138 IRDataDef(name="@hello", sm_id=0, cell_addr=5, value=0x2a),
139 IRDataDef(name="@world", sm_id=1, cell_addr=10, value=0x3f),
140 ]
141 graph = IRGraph(data_defs=data_defs)
142
143 serialized = serialize(graph)
144
145 # Check for data def entries
146 assert "@hello|sm0:5 =" in serialized
147 assert "@world|sm1:10 =" in serialized
148 # Check hex values are present
149 assert "0x2a" in serialized or "42" in serialized
150 assert "0x3f" in serialized or "63" in serialized
151
152
153class TestAnonymousNodes:
154 """AC11.6: Anonymous nodes serialize as inst_def + plain_edge, not inline."""
155
156 def test_anonymous_node_not_inline(self):
157 """AC11.6: Nodes with __anon_ in name use inst_def + plain_edge form."""
158 nodes = {
159 "&source": IRNode(name="&source", opcode=ArithOp.ADD, pe=0),
160 "__anon_1": IRNode(name="__anon_1", opcode=ArithOp.SUB, pe=0),
161 "&dest": IRNode(name="&dest", opcode=ArithOp.ADD, pe=1),
162 }
163 edges = [
164 IREdge(source="&source", dest="__anon_1", port=Port.L),
165 IREdge(source="__anon_1", dest="&dest", port=Port.R),
166 ]
167 graph = IRGraph(nodes=nodes, edges=edges)
168
169 serialized = serialize(graph)
170
171 # Anonymous node should be defined as a separate inst_def, not inline
172 assert "__anon_1|pe0 <|" in serialized
173 # Should have explicit edges, not inline syntax
174 assert "&source |> __anon_1:L" in serialized
175 assert "__anon_1 |> &dest:R" in serialized
176
177
178class TestLocationRegions:
179 """AC11.8: LOCATION regions serialize as bare directive + body."""
180
181 def test_location_region_structure(self):
182 """AC11.8: LOCATION regions emit bare directive tag followed by body."""
183 # Create a location region with data definitions inside
184 loc_data = [
185 IRDataDef(name="@data1", sm_id=0, cell_addr=5, value=100),
186 ]
187 loc_body = IRGraph(data_defs=loc_data)
188 loc_region = IRRegion(tag="@data_section", kind=RegionKind.LOCATION, body=loc_body)
189
190 graph = IRGraph(regions=[loc_region])
191 serialized = serialize(graph)
192
193 # Location directive should appear as bare tag with trailing colon (not in $func |> form)
194 assert "@data_section:" in serialized
195 # Body content should follow
196 assert "@data1|sm0:5" in serialized
197
198
199class TestEdgeSerialization:
200 """Edges serialize as plain_edge form."""
201
202 def test_edge_port_notation(self):
203 """Edges serialize with :L and :R port notation."""
204 nodes = {
205 "&a": IRNode(name="&a", opcode=ArithOp.ADD, pe=0),
206 "&b": IRNode(name="&b", opcode=ArithOp.ADD, pe=1),
207 }
208 edges = [
209 IREdge(source="&a", dest="&b", port=Port.L),
210 ]
211 graph = IRGraph(nodes=nodes, edges=edges)
212
213 serialized = serialize(graph)
214
215 # Should contain edge with port notation
216 assert "|> &b:L" in serialized
217
218
219class TestMnemonicUsage:
220 """Opcodes serialize using OP_TO_MNEMONIC."""
221
222 def test_various_opcodes(self):
223 """Different opcodes serialize with correct mnemonics."""
224 nodes = {
225 "&add": IRNode(name="&add", opcode=ArithOp.ADD, pe=0),
226 "&sub": IRNode(name="&sub", opcode=ArithOp.SUB, pe=0),
227 "&and": IRNode(name="&and", opcode=LogicOp.AND, pe=0),
228 "&read": IRNode(name="&read", opcode=MemOp.READ, pe=0),
229 }
230 graph = IRGraph(nodes=nodes)
231
232 serialized = serialize(graph)
233
234 # Check that mnemonics appear
235 assert "add" in serialized
236 assert "sub" in serialized
237 assert "and" in serialized
238 assert "read" in serialized
239
240
241class TestConstValues:
242 """Const values serialize in inst_def format."""
243
244 def test_const_node_with_value(self):
245 """Nodes with const values serialize with const argument."""
246 nodes = {
247 "&c": IRNode(name="&c", opcode=RoutingOp.CONST, const=255, pe=0),
248 }
249 graph = IRGraph(nodes=nodes)
250
251 serialized = serialize(graph)
252
253 # Should include const value
254 assert "const, 255" in serialized or "const, 0xff" in serialized
255
256
257class TestEmptyGraph:
258 """Edge case: empty graph."""
259
260 def test_empty_graph_serializes(self):
261 """AC11.1: Empty graph serializes to empty or whitespace string."""
262 graph = IRGraph()
263 serialized = serialize(graph)
264
265 # Should not raise, should be empty or whitespace
266 assert serialized.strip() == ""