OR-1 dataflow CPU sketch
1"""
2Tests for SM T0 raw int storage and EXEC flit-based parsing.
3
4Verifies acceptance criteria:
5- pe-frame-redesign.AC4.1: SM T0 stores list[int] (16-bit words), not Token objects
6- pe-frame-redesign.AC4.2: EXEC reads consecutive ints, uses flit_count() for packet boundaries,
7 reconstitutes tokens via unpack_token()
8- pe-frame-redesign.AC4.3: pack_token() / unpack_token() round-trip for all token types
9- pe-frame-redesign.AC4.4: Malformed flit sequence in T0 (invalid prefix bits) is handled
10 gracefully without crash
11"""
12
13import pytest
14import simpy
15
16from cm_inst import MemOp, Port
17from emu.sm import StructureMemory
18from encoding import pack_token, unpack_token
19from tokens import CMToken, DyadToken, MonadToken, SMToken
20
21
22class TestAC4_1T0RawIntStorage:
23 """AC4.1: SM T0 stores list[int] (16-bit words), not Token objects."""
24
25 def test_t0_store_initialized_as_empty_int_list(self):
26 """t0_store is initialized as empty list[int]."""
27 env = simpy.Environment()
28 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
29
30 assert isinstance(sm.t0_store, list)
31 assert len(sm.t0_store) == 0
32 # Should be list of ints, not list of Tokens
33
34 def test_t0_write_stores_int_values(self):
35 """T0 WRITE stores int values."""
36 env = simpy.Environment()
37 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
38 collector = simpy.Store(env)
39 sm.route_table[0] = collector
40
41 # Write int to T0
42 write_token = SMToken(
43 target=0, addr=256, op=MemOp.WRITE,
44 flags=None, data=0x1234, ret=None
45 )
46
47 def do_write():
48 yield sm.input_store.put(write_token)
49
50 env.process(do_write())
51 env.run()
52
53 # Verify T0[0] (addr 256 = tier_boundary) now contains 0x1234
54 assert len(sm.t0_store) >= 1
55 assert sm.t0_store[0] == 0x1234
56
57
58class TestAC4_2ExecFlitBasedParsing:
59 """AC4.2: EXEC uses flit_count() for packet boundaries, unpack_token() to reconstitute."""
60
61 def test_exec_reconstitutes_single_dyadic_token(self):
62 """EXEC reads packed dyadic token flits, reconstitutes it."""
63 env = simpy.Environment()
64 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
65
66 # Mock system and PE input store for token delivery
67 from unittest.mock import Mock
68 mock_pe_store = simpy.Store(env)
69 mock_system = Mock()
70 sm.system = mock_system
71
72 # Set up system.send() to deliver to mock_pe_store
73 def mock_send(token):
74 yield env.timeout(1)
75 yield mock_pe_store.put(token)
76
77 mock_system.send = mock_send
78
79 # Create a dyadic token and pack it
80 orig_token = DyadToken(
81 target=0, offset=10, act_id=2, data=0xABCD, port=Port.L
82 )
83 flits = pack_token(orig_token)
84
85 # Pre-load T0 with packed flits
86 sm.t0_store.extend(flits)
87
88 # Trigger EXEC at T0 address 256 (tier_boundary)
89 exec_token = SMToken(
90 target=0, addr=256, op=MemOp.EXEC,
91 flags=None, data=None, ret=None
92 )
93
94 def do_exec():
95 yield sm.input_store.put(exec_token)
96
97 env.process(do_exec())
98 env.run()
99
100 # Verify reconstituted token arrived at PE
101 assert len(mock_pe_store.items) >= 1
102 received = mock_pe_store.items[0]
103 assert isinstance(received, DyadToken)
104 assert received.target == 0
105 assert received.offset == 10
106 assert received.act_id == 2
107 assert received.data == 0xABCD
108 assert received.port == Port.L
109
110 def test_exec_reconstitutes_multiple_consecutive_packets(self):
111 """EXEC parses multiple consecutive token packets by flit_count boundary."""
112 env = simpy.Environment()
113 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
114
115 # Mock system and PE input store
116 from unittest.mock import Mock
117 mock_pe_store = simpy.Store(env)
118 mock_system = Mock()
119 sm.system = mock_system
120
121 def mock_send(token):
122 yield env.timeout(1)
123 yield mock_pe_store.put(token)
124
125 mock_system.send = mock_send
126
127 # Create two tokens
128 token1 = DyadToken(target=0, offset=5, act_id=1, data=0x1111, port=Port.L)
129 token2 = MonadToken(target=0, offset=15, act_id=3, data=0x2222, inline=False)
130
131 flits1 = pack_token(token1)
132 flits2 = pack_token(token2)
133
134 # Pre-load both in sequence
135 sm.t0_store.extend(flits1)
136 sm.t0_store.extend(flits2)
137
138 # EXEC from start of T0
139 exec_token = SMToken(
140 target=0, addr=256, op=MemOp.EXEC,
141 flags=None, data=None, ret=None
142 )
143
144 def do_exec():
145 yield sm.input_store.put(exec_token)
146
147 env.process(do_exec())
148 env.run()
149
150 # Verify both tokens arrived
151 assert len(mock_pe_store.items) >= 2
152 received1 = mock_pe_store.items[0]
153 received2 = mock_pe_store.items[1]
154
155 assert isinstance(received1, DyadToken)
156 assert received1.data == 0x1111
157
158 assert isinstance(received2, MonadToken)
159 assert received2.data == 0x2222
160
161 def test_exec_processes_multiple_packets_until_end(self):
162 """EXEC processes multiple consecutive packets until end of T0 store."""
163 env = simpy.Environment()
164 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
165
166 # Mock system and PE input store
167 from unittest.mock import Mock
168 mock_pe_store = simpy.Store(env)
169 mock_system = Mock()
170 sm.system = mock_system
171
172 def mock_send(token):
173 yield env.timeout(1)
174 yield mock_pe_store.put(token)
175
176 mock_system.send = mock_send
177
178 # Create three valid tokens
179 token1 = DyadToken(target=0, offset=5, act_id=1, data=0x1111, port=Port.L)
180 token2 = MonadToken(target=1, offset=10, act_id=2, data=0x2222, inline=False)
181 token3 = DyadToken(target=2, offset=15, act_id=3, data=0x3333, port=Port.R)
182
183 flits1 = pack_token(token1)
184 flits2 = pack_token(token2)
185 flits3 = pack_token(token3)
186
187 # Pre-load all three tokens
188 sm.t0_store.extend(flits1)
189 sm.t0_store.extend(flits2)
190 sm.t0_store.extend(flits3)
191
192 # EXEC from start
193 exec_token = SMToken(
194 target=0, addr=256, op=MemOp.EXEC,
195 flags=None, data=None, ret=None
196 )
197
198 def do_exec():
199 yield sm.input_store.put(exec_token)
200
201 env.process(do_exec())
202 env.run()
203
204 # Should have received all three tokens
205 assert len(mock_pe_store.items) == 3
206 assert mock_pe_store.items[0].data == 0x1111
207 assert mock_pe_store.items[1].data == 0x2222
208 assert mock_pe_store.items[2].data == 0x3333
209
210
211class TestAC4_3RoundTripPackUnpack:
212 """AC4.3: pack_token() / unpack_token() round-trip for all token types."""
213
214 def test_dyadic_round_trip(self):
215 """pack_token() -> unpack_token() preserves DyadToken fields."""
216 token = DyadToken(
217 target=2, offset=42, act_id=5, data=0x3456, port=Port.R
218 )
219 flits = pack_token(token)
220 reconstituted = unpack_token(flits)
221
222 assert isinstance(reconstituted, DyadToken)
223 assert reconstituted.target == 2
224 assert reconstituted.offset == 42
225 assert reconstituted.act_id == 5
226 assert reconstituted.data == 0x3456
227 assert reconstituted.port == Port.R
228
229 def test_monadic_normal_round_trip(self):
230 """pack_token() -> unpack_token() preserves MonadToken (normal) fields."""
231 token = MonadToken(
232 target=1, offset=30, act_id=2, data=0x7890, inline=False
233 )
234 flits = pack_token(token)
235 reconstituted = unpack_token(flits)
236
237 assert isinstance(reconstituted, MonadToken)
238 assert reconstituted.target == 1
239 assert reconstituted.offset == 30
240 assert reconstituted.act_id == 2
241 assert reconstituted.data == 0x7890
242 assert reconstituted.inline == False
243
244 def test_monadic_inline_round_trip(self):
245 """pack_token() -> unpack_token() preserves MonadToken (inline) fields."""
246 token = MonadToken(
247 target=0, offset=20, act_id=1, data=0, inline=True
248 )
249 flits = pack_token(token)
250 assert len(flits) == 1 # inline has only 1 flit
251 reconstituted = unpack_token(flits)
252
253 assert isinstance(reconstituted, MonadToken)
254 assert reconstituted.target == 0
255 assert reconstituted.offset == 20
256 # Note: act_id is not encoded in inline format (hardware constraint)
257 assert reconstituted.act_id == 0
258 assert reconstituted.inline == True
259
260 def test_sm_token_round_trip(self):
261 """pack_token() -> unpack_token() preserves SMToken fields."""
262 token = SMToken(
263 target=3, addr=512, op=MemOp.READ, flags=None, data=0x4567, ret=None
264 )
265 flits = pack_token(token)
266 reconstituted = unpack_token(flits)
267
268 assert isinstance(reconstituted, SMToken)
269 assert reconstituted.target == 3
270 assert reconstituted.addr == 512
271 assert reconstituted.op == MemOp.READ
272 assert reconstituted.data == 0x4567
273
274
275class TestAC4_4GracefulErrorHandling:
276 """AC4.4: Malformed flit sequences are handled gracefully without crash."""
277
278 def test_malformed_flit_invalid_prefix(self):
279 """EXEC stops gracefully when flit count exceeds available data (truncation detection)."""
280 env = simpy.Environment()
281 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
282
283 # Mock system and PE input store
284 from unittest.mock import Mock
285 mock_pe_store = simpy.Store(env)
286 mock_system = Mock()
287 sm.system = mock_system
288
289 def mock_send(token):
290 yield env.timeout(1)
291 yield mock_pe_store.put(token)
292
293 mock_system.send = mock_send
294
295 # Pre-load T0 with 0xFFFF (SM token prefix).
296 # flit_count(0xFFFF) returns 2 (SM token header indicates 2 flits),
297 # but only 1 flit is in store, so truncation check catches it.
298 sm.t0_store.extend([0xFFFF])
299
300 # EXEC from start
301 exec_token = SMToken(
302 target=0, addr=256, op=MemOp.EXEC,
303 flags=None, data=None, ret=None
304 )
305
306 def do_exec():
307 yield sm.input_store.put(exec_token)
308
309 env.process(do_exec())
310 # Should not crash
311 env.run()
312
313 # No tokens should have been injected (parse failed)
314 assert len(mock_pe_store.items) == 0
315
316 def test_truncated_packet_stops_gracefully(self):
317 """EXEC stops gracefully when packet is truncated."""
318 env = simpy.Environment()
319 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
320
321 # Mock system and PE input store
322 from unittest.mock import Mock
323 mock_pe_store = simpy.Store(env)
324 mock_system = Mock()
325 sm.system = mock_system
326
327 def mock_send(token):
328 yield env.timeout(1)
329 yield mock_pe_store.put(token)
330
331 mock_system.send = mock_send
332
333 # Create a 2-flit dyadic token
334 token = DyadToken(target=0, offset=5, act_id=1, data=0x1111, port=Port.L)
335 flits = pack_token(token)
336 assert len(flits) == 2
337
338 # Pre-load with first flit only (truncated)
339 sm.t0_store.append(flits[0])
340
341 # EXEC from start
342 exec_token = SMToken(
343 target=0, addr=256, op=MemOp.EXEC,
344 flags=None, data=None, ret=None
345 )
346
347 def do_exec():
348 yield sm.input_store.put(exec_token)
349
350 env.process(do_exec())
351 # Should not crash
352 env.run()
353
354 # No tokens should have been injected (truncated)
355 assert len(mock_pe_store.items) == 0
356
357 def test_mixed_valid_invalid_packets_stops_at_first_error(self):
358 """EXEC stops at first malformed packet, valid packets before it are processed."""
359 env = simpy.Environment()
360 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
361
362 # Mock system and PE input store
363 from unittest.mock import Mock
364 mock_pe_store = simpy.Store(env)
365 mock_system = Mock()
366 sm.system = mock_system
367
368 def mock_send(token):
369 yield env.timeout(1)
370 yield mock_pe_store.put(token)
371
372 mock_system.send = mock_send
373
374 # Valid token first
375 token1 = DyadToken(target=0, offset=5, act_id=1, data=0x1111, port=Port.L)
376 flits1 = pack_token(token1)
377
378 # Then invalid
379 invalid = [0xFFFF]
380
381 # Pre-load
382 sm.t0_store.extend(flits1)
383 sm.t0_store.extend(invalid)
384
385 # EXEC from start
386 exec_token = SMToken(
387 target=0, addr=256, op=MemOp.EXEC,
388 flags=None, data=None, ret=None
389 )
390
391 def do_exec():
392 yield sm.input_store.put(exec_token)
393
394 env.process(do_exec())
395 env.run()
396
397 # First valid token should have been injected before error
398 assert len(mock_pe_store.items) >= 1
399 assert mock_pe_store.items[0].data == 0x1111