OR-1 dataflow CPU sketch
1"""
2Tests for SM T0/T1 memory tier split.
3
4Verifies acceptance criteria:
5- token-migration.AC4.1: SM operations on T1 (< tier_boundary) use I-structure semantics
6- token-migration.AC4.2: SM WRITE to T0 (>= tier_boundary) stores data without presence checking
7- token-migration.AC4.3: SM READ on T0 address returns immediately (no deferral)
8- token-migration.AC4.4: T0 storage is shared across all SMs
9- token-migration.AC4.5: I-structure ops (CLEAR, ALLOC, FREE, atomics) on T0 produce error/warning
10- token-migration.AC4.6: Tier boundary is configurable via SMConfig; default is 256
11- token-migration.AC6.2: Existing I-structure behaviour unchanged (is_wide=False path)
12"""
13
14import simpy
15
16from cm_inst import MemOp
17from emu import build_topology
18from emu.sm import StructureMemory
19from emu.types import SMConfig
20from sm_mod import Presence
21from tokens import CMToken, SMToken
22
23
24def inject_token(env: simpy.Environment, store: simpy.Store, token):
25 """Helper to inject token into store via a process."""
26 def _injector():
27 yield store.put(token)
28
29 env.process(_injector())
30
31
32class TestAC4_1T1IStructureSemantics:
33 """AC4.1: SM READ on T1 (< tier_boundary) uses I-structure semantics."""
34
35 def test_t1_read_on_full_returns_immediately(self):
36 """READ on T1 full cell returns data immediately."""
37 env = simpy.Environment()
38 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
39
40 # Pre-populate T1 cell (addr < 256) to FULL
41 t1_addr = 100
42 sm.cells[t1_addr].pres = Presence.FULL
43 sm.cells[t1_addr].data_l = 0x1111
44
45 collector = simpy.Store(env)
46 sm.route_table[0] = collector
47
48 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
49 read_token = SMToken(target=0, addr=t1_addr, op=MemOp.READ, flags=None, data=None, ret=ret_route)
50 inject_token(env, sm.input_store, read_token)
51
52 env.run(until=100)
53
54 assert len(collector.items) == 1
55 assert collector.items[0].data == 0x1111
56
57 def test_t1_read_on_empty_defers_and_sets_waiting(self):
58 """READ on T1 empty cell sets WAITING and stashes return route."""
59 env = simpy.Environment()
60 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
61
62 t1_addr = 150
63 assert sm.cells[t1_addr].pres == Presence.EMPTY
64
65 collector = simpy.Store(env)
66 sm.route_table[0] = collector
67
68 ret_route = CMToken(target=0, offset=5, act_id=1, data=0)
69 read_token = SMToken(target=0, addr=t1_addr, op=MemOp.READ, flags=None, data=None, ret=ret_route)
70 inject_token(env, sm.input_store, read_token)
71
72 env.run(until=100)
73
74 # Verify deferred read is set
75 assert sm.deferred_read is not None
76 assert sm.deferred_read.cell_addr == t1_addr
77 assert sm.cells[t1_addr].pres == Presence.WAITING
78 # No result token yet
79 assert len(collector.items) == 0
80
81 def test_t1_deferred_read_satisfied_by_write(self):
82 """WRITE on T1 WAITING cell satisfies deferred read."""
83 env = simpy.Environment()
84 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
85
86 t1_addr = 120
87 collector = simpy.Store(env)
88 sm.route_table[0] = collector
89
90 def test_sequence():
91 # First: READ on empty T1 cell to defer
92 ret_route = CMToken(target=0, offset=10, act_id=0, data=0)
93 read_token = SMToken(target=0, addr=t1_addr, op=MemOp.READ, flags=None, data=None, ret=ret_route)
94 yield sm.input_store.put(read_token)
95
96 # Second: WRITE to satisfy the deferred read
97 yield env.timeout(10)
98 write_token = SMToken(target=0, addr=t1_addr, op=MemOp.WRITE, flags=None, data=0x2222, ret=None)
99 yield sm.input_store.put(write_token)
100
101 env.process(test_sequence())
102 env.run(until=100)
103
104 # Verify result token was emitted with correct data
105 assert len(collector.items) == 1
106 assert collector.items[0].data == 0x2222
107
108
109class TestAC4_2T0WriteDirect:
110 """AC4.2: SM WRITE to T0 stores data without presence checking."""
111
112 def test_t0_write_stores_directly(self):
113 """WRITE to T0 (addr >= tier_boundary) stores in t0_store directly."""
114 env = simpy.Environment()
115 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
116
117 t0_addr = 256
118 write_token = SMToken(target=0, addr=t0_addr, op=MemOp.WRITE, flags=None, data=0x3333, ret=None)
119 inject_token(env, sm.input_store, write_token)
120
121 env.run(until=100)
122
123 # Verify data in t0_store at correct index
124 t0_idx = t0_addr - 256
125 assert len(sm.t0_store) > t0_idx
126 assert sm.t0_store[t0_idx] == 0x3333
127
128 def test_t0_write_overwrites_previous_value(self):
129 """WRITE to T0 can overwrite previous value."""
130 env = simpy.Environment()
131 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
132
133 # Pre-populate
134 sm.t0_store.append(0x1111)
135
136 # Overwrite at same index
137 t0_addr = 256
138 write_token = SMToken(target=0, addr=t0_addr, op=MemOp.WRITE, flags=None, data=0x4444, ret=None)
139 inject_token(env, sm.input_store, write_token)
140
141 env.run(until=100)
142
143 # Verify overwrite
144 assert sm.t0_store[0] == 0x4444
145
146
147class TestAC4_3T0ReadImmediate:
148 """AC4.3: SM READ on T0 address returns immediately (no deferral)."""
149
150 def test_t0_read_immediate_no_deferral(self):
151 """READ on T0 address returns immediately without I-structure blocking."""
152 env = simpy.Environment()
153 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
154
155 # Pre-populate t0_store
156 sm.t0_store.append(0x5555)
157
158 collector = simpy.Store(env)
159 sm.route_table[0] = collector
160
161 t0_addr = 256
162 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
163 read_token = SMToken(target=0, addr=t0_addr, op=MemOp.READ, flags=None, data=None, ret=ret_route)
164 inject_token(env, sm.input_store, read_token)
165
166 env.run(until=100)
167
168 # Verify immediate result
169 assert len(collector.items) == 1
170 assert collector.items[0].data == 0x5555
171 # No deferred read
172 assert sm.deferred_read is None
173
174 def test_t0_read_on_empty_returns_zero(self):
175 """READ on empty T0 address returns 0."""
176 env = simpy.Environment()
177 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
178
179 # t0_store is empty
180 collector = simpy.Store(env)
181 sm.route_table[0] = collector
182
183 t0_addr = 256
184 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
185 read_token = SMToken(target=0, addr=t0_addr, op=MemOp.READ, flags=None, data=None, ret=ret_route)
186 inject_token(env, sm.input_store, read_token)
187
188 env.run(until=100)
189
190 # Verify 0 returned
191 assert len(collector.items) == 1
192 assert collector.items[0].data == 0
193
194
195class TestAC4_5T0IStructureOpsRejected:
196 """AC4.5: I-structure ops (CLEAR, ALLOC, FREE, RD_INC, RD_DEC, CMP_SW) on T0 are rejected."""
197
198 def test_t0_clear_rejected(self):
199 """CLEAR on T0 address is rejected (no-op)."""
200 env = simpy.Environment()
201 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
202
203 # Pre-populate t0_store
204 sm.t0_store.append(0xDEAD)
205
206 t0_addr = 256
207 clear_token = SMToken(target=0, addr=t0_addr, op=MemOp.CLEAR, flags=None, data=None, ret=None)
208 inject_token(env, sm.input_store, clear_token)
209
210 env.run(until=100)
211
212 # Verify t0_store unchanged
213 assert sm.t0_store[0] == 0xDEAD
214
215 def test_t0_alloc_rejected(self):
216 """ALLOC on T0 address is rejected."""
217 env = simpy.Environment()
218 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
219
220 t0_addr = 256
221 alloc_token = SMToken(target=0, addr=t0_addr, op=MemOp.ALLOC, flags=None, data=None, ret=None)
222 inject_token(env, sm.input_store, alloc_token)
223
224 env.run(until=100)
225
226 # t0_store should remain empty
227 assert len(sm.t0_store) == 0
228
229 def test_t0_free_rejected(self):
230 """FREE on T0 address is rejected."""
231 env = simpy.Environment()
232 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
233
234 # Pre-populate
235 sm.t0_store.append(0xBEEF)
236
237 t0_addr = 256
238 free_token = SMToken(target=0, addr=t0_addr, op=MemOp.FREE, flags=None, data=None, ret=None)
239 inject_token(env, sm.input_store, free_token)
240
241 env.run(until=100)
242
243 # t0_store unchanged
244 assert sm.t0_store[0] == 0xBEEF
245
246 def test_t0_rd_inc_rejected(self):
247 """RD_INC on T0 address is rejected."""
248 env = simpy.Environment()
249 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
250
251 collector = simpy.Store(env)
252 sm.route_table[0] = collector
253
254 t0_addr = 256
255 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
256 inc_token = SMToken(target=0, addr=t0_addr, op=MemOp.RD_INC, flags=None, data=None, ret=ret_route)
257 inject_token(env, sm.input_store, inc_token)
258
259 env.run(until=100)
260
261 # No result token (operation rejected)
262 assert len(collector.items) == 0
263
264 def test_t0_rd_dec_rejected(self):
265 """RD_DEC on T0 address is rejected."""
266 env = simpy.Environment()
267 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
268
269 collector = simpy.Store(env)
270 sm.route_table[0] = collector
271
272 t0_addr = 256
273 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
274 dec_token = SMToken(target=0, addr=t0_addr, op=MemOp.RD_DEC, flags=None, data=None, ret=ret_route)
275 inject_token(env, sm.input_store, dec_token)
276
277 env.run(until=100)
278
279 # No result token
280 assert len(collector.items) == 0
281
282 def test_t0_cmp_sw_rejected(self):
283 """CMP_SW on T0 address is rejected."""
284 env = simpy.Environment()
285 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256)
286
287 # Pre-populate t0_store
288 sm.t0_store.append(0x100)
289
290 collector = simpy.Store(env)
291 sm.route_table[0] = collector
292
293 t0_addr = 256
294 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
295 cmp_token = SMToken(target=0, addr=t0_addr, op=MemOp.CMP_SW, flags=0x100, data=0x200, ret=ret_route)
296 inject_token(env, sm.input_store, cmp_token)
297
298 env.run(until=100)
299
300 # No result token and t0_store unchanged
301 assert len(collector.items) == 0
302 assert sm.t0_store[0] == 0x100
303
304
305class TestAC4_6ConfigurableTierBoundary:
306 """AC4.6: Tier boundary is configurable via SMConfig; default is 256."""
307
308 def test_default_tier_boundary_is_256(self):
309 """SMConfig creates SM with default tier_boundary=256."""
310 env = simpy.Environment()
311 sm = StructureMemory(env, 0, cell_count=512)
312 assert sm.tier_boundary == 256
313
314 def test_custom_tier_boundary(self):
315 """SMConfig allows custom tier_boundary."""
316 env = simpy.Environment()
317 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=128)
318 assert sm.tier_boundary == 128
319
320 def test_addr_below_custom_tier_boundary_is_t1(self):
321 """Addresses below custom tier_boundary use I-structure semantics."""
322 env = simpy.Environment()
323 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=128)
324
325 # Addr 127 should be T1 (< 128)
326 t1_addr = 127
327 sm.cells[t1_addr].pres = Presence.FULL
328 sm.cells[t1_addr].data_l = 0x7777
329
330 collector = simpy.Store(env)
331 sm.route_table[0] = collector
332
333 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
334 read_token = SMToken(target=0, addr=t1_addr, op=MemOp.READ, flags=None, data=None, ret=ret_route)
335 inject_token(env, sm.input_store, read_token)
336
337 env.run(until=100)
338
339 # Verify I-structure read worked
340 assert len(collector.items) == 1
341 assert collector.items[0].data == 0x7777
342
343 def test_addr_at_custom_tier_boundary_is_t0(self):
344 """Addresses at or above custom tier_boundary use T0 semantics."""
345 env = simpy.Environment()
346 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=128)
347
348 # Addr 128 should be T0 (>= 128)
349 t0_addr = 128
350
351 collector = simpy.Store(env)
352 sm.route_table[0] = collector
353
354 # T0 read on empty should return 0, not defer
355 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
356 read_token = SMToken(target=0, addr=t0_addr, op=MemOp.READ, flags=None, data=None, ret=ret_route)
357 inject_token(env, sm.input_store, read_token)
358
359 env.run(until=100)
360
361 # Should return 0 immediately
362 assert len(collector.items) == 1
363 assert collector.items[0].data == 0
364
365
366class TestAC4_4T0SharedAcrossTopology:
367 """AC4.4: T0 storage is shared — all SMs reference the same T0 store."""
368
369 def test_all_sms_share_same_t0_store(self):
370 """All SMs in topology reference identical t0_store object."""
371 env = simpy.Environment()
372
373 sys = build_topology(
374 env,
375 [],
376 [
377 SMConfig(sm_id=0, cell_count=512, tier_boundary=256),
378 SMConfig(sm_id=1, cell_count=512, tier_boundary=256),
379 SMConfig(sm_id=2, cell_count=512, tier_boundary=256),
380 ],
381 )
382
383 # Verify all SMs share same t0_store object
384 assert sys.sms[0].t0_store is sys.sms[1].t0_store
385 assert sys.sms[1].t0_store is sys.sms[2].t0_store
386
387 def test_t0_write_by_sm0_visible_to_sm1(self):
388 """Data written to T0 by SM0 is visible to SM1 READ."""
389 env = simpy.Environment()
390
391 sys = build_topology(
392 env,
393 [],
394 [
395 SMConfig(sm_id=0, cell_count=512, tier_boundary=256),
396 SMConfig(sm_id=1, cell_count=512, tier_boundary=256),
397 ],
398 )
399
400 # Collector for SM1 results
401 collector = simpy.Store(env)
402 sys.sms[1].route_table[0] = collector
403
404 def test_sequence():
405 # SM0 writes to T0
406 write_token = SMToken(target=0, addr=256, op=MemOp.WRITE, flags=None, data=0x9999, ret=None)
407 yield sys.sms[0].input_store.put(write_token)
408
409 # SM1 reads from same T0
410 yield env.timeout(10)
411 ret_route = CMToken(target=0, offset=0, act_id=0, data=0)
412 read_token = SMToken(target=1, addr=256, op=MemOp.READ, flags=None, data=None, ret=ret_route)
413 yield sys.sms[1].input_store.put(read_token)
414
415 env.process(test_sequence())
416 env.run(until=100)
417
418 # Verify SM1 read returned SM0's written data
419 assert len(collector.items) == 1
420 assert collector.items[0].data == 0x9999
421
422
423class TestAC6_1SMCellIsWideField:
424 """AC6.1: SMCell has is_wide field (default False)."""
425
426 def test_is_wide_defaults_to_false(self):
427 from sm_mod import Presence, SMCell
428 cell = SMCell(Presence.EMPTY, None, None)
429 assert cell.is_wide is False
430
431 def test_is_wide_can_be_set_true(self):
432 from sm_mod import Presence, SMCell
433 cell = SMCell(Presence.EMPTY, None, None, is_wide=True)
434 assert cell.is_wide is True
435
436
437class TestAC6_2IsWideMetadata:
438 """AC6.2: Existing I-structure behaviour unchanged (is_wide=False path)."""
439
440 def test_is_wide_false_preserves_existing_behavior(self):
441 """Frame-based PE can be created with proper configuration.
442
443 Note: This test has been updated for frame-based architecture.
444 The detailed token matching behavior is tested elsewhere (test_integration.py).
445 This test just verifies PE can be initialized with frame config.
446 """
447 env = simpy.Environment()
448
449 from emu.types import PEConfig
450 from emu.pe import ProcessingElement
451
452 # Create PE with frame-based config
453 config = PEConfig(
454 pe_id=0,
455 frame_count=8,
456 frame_slots=64,
457 matchable_offsets=8,
458 )
459 pe = ProcessingElement(env, 0, config)
460
461 # Verify PE was initialized correctly
462 assert pe.pe_id == 0
463 assert pe.frame_count == 8
464 assert len(pe.frames) == 8
465 assert all(len(f) == 64 for f in pe.frames)
466 assert len(pe.free_frames) == 8 # All frames initially free
467 assert len(pe.tag_store) == 0 # No activations allocated yet