OR-1 dataflow CPU sketch
1"""
2Tests for Structure Memory operations.
3
4Verifies all acceptance criteria:
5- or1-emu.AC3.1: READ on FULL cell returns data immediately via result token
6- or1-emu.AC3.2: READ on EMPTY cell with empty deferred register stashes return route, sets WAITING
7- or1-emu.AC3.3: WRITE on WAITING cell satisfies deferred read — emits result token to stashed return route
8- or1-emu.AC3.4: WRITE on EMPTY/RESERVED sets cell to FULL
9- or1-emu.AC3.5: CLEAR sets cell to EMPTY, cancels deferred read if targeting that cell
10- or1-emu.AC3.6: READ_INC/READ_DEC atomically modify and return value (lower 256 cells only)
11- or1-emu.AC3.7: Depth-1 constraint: second blocking READ on different empty cell stalls until first deferred read is satisfied
12- or1-emu.AC3.8: WRITE on FULL cell overwrites data (diagnostic flag set if modelled)
13- or1-emu.AC3.9: CAS on FULL cell: if current value == expected (SMToken.flags), writes new value (SMToken.data) and returns old value; if mismatch, cell unchanged and returns old value (lower 256 cells only)
14"""
15
16import simpy
17from hypothesis import given, strategies as st
18
19from cm_inst import MemOp
20from emu.sm import StructureMemory
21from sm_mod import Presence
22from tests.conftest import sm_token, sm_return_route, uint16
23from tokens import CMToken, SMToken
24
25
26def inject_token(env: simpy.Environment, store: simpy.Store, token):
27 """Helper to inject token into store via a process."""
28 def _injector():
29 yield store.put(token)
30
31 env.process(_injector())
32
33
34class TestAC3_1ReadOnFull:
35 """AC3.1: READ on FULL cell returns data immediately via result token."""
36
37 def test_read_on_full_cell(self):
38 env = simpy.Environment()
39 sm = StructureMemory(env, 0, cell_count=512)
40
41 # Pre-populate cell 10 to FULL with data 0xBEEF
42 sm.cells[10].pres = Presence.FULL
43 sm.cells[10].data_l = 0xBEEF
44
45 # Create collector store for results
46 collector = simpy.Store(env)
47 sm.route_table[0] = collector
48
49 # Inject READ token targeting cell 10
50 ret_route = CMToken(target=0, offset=5, act_id=1, data=0)
51 read_token = SMToken(target=0, addr=10, op=MemOp.READ, flags=None, data=None, ret=ret_route)
52 inject_token(env, sm.input_store, read_token)
53
54 # Run simulation
55 env.run(until=100)
56
57 # Verify result token in collector with correct data
58 assert len(collector.items) == 1
59 result = collector.items[0]
60 assert result.data == 0xBEEF
61 assert result.offset == 5
62 assert result.act_id == 1
63
64
65class TestAC3_2ReadOnEmpty:
66 """AC3.2: READ on EMPTY cell with empty deferred register stashes return route, sets WAITING."""
67
68 def test_read_on_empty_cell(self):
69 env = simpy.Environment()
70 sm = StructureMemory(env, 0, cell_count=512)
71
72 # Cell 20 starts EMPTY
73 assert sm.cells[20].pres == Presence.EMPTY
74
75 # Create collector store
76 collector = simpy.Store(env)
77 sm.route_table[0] = collector
78
79 # Inject READ token targeting cell 20
80 ret_route = CMToken(target=0, offset=7, act_id=2, data=0)
81 read_token = SMToken(target=0, addr=20, op=MemOp.READ, flags=None, data=None, ret=ret_route)
82 inject_token(env, sm.input_store, read_token)
83
84 # Run simulation
85 env.run(until=100)
86
87 # Verify cell is now WAITING
88 assert sm.cells[20].pres == Presence.WAITING
89
90 # Verify deferred_read is set
91 assert sm.deferred_read is not None
92 assert sm.deferred_read.cell_addr == 20
93 assert sm.deferred_read.return_route == ret_route
94
95 # Verify no result token emitted yet
96 assert len(collector.items) == 0
97
98
99class TestAC3_3DeferredReadSatisfaction:
100 """AC3.3: WRITE on WAITING cell satisfies deferred read — emits result token to stashed return route."""
101
102 def test_write_satisfies_deferred_read(self):
103 env = simpy.Environment()
104 sm = StructureMemory(env, 0, cell_count=512)
105
106 # Create collector store
107 collector = simpy.Store(env)
108 sm.route_table[0] = collector
109
110 # Set up deferred read on cell 30
111 ret_route = CMToken(target=0, offset=8, act_id=3, data=0)
112 read_token = SMToken(target=0, addr=30, op=MemOp.READ, flags=None, data=None, ret=ret_route)
113 inject_token(env, sm.input_store, read_token)
114
115 # Run to let deferred read be set up
116 env.run(until=10)
117
118 # Now inject WRITE to cell 30
119 write_token = SMToken(target=0, addr=30, op=MemOp.WRITE, flags=None, data=0xDEAD, ret=None)
120 inject_token(env, sm.input_store, write_token)
121
122 # Continue simulation
123 env.run(until=100)
124
125 # Verify cell is now FULL
126 assert sm.cells[30].pres == Presence.FULL
127 assert sm.cells[30].data_l == 0xDEAD
128
129 # Verify result token was emitted with written data
130 assert len(collector.items) == 1
131 result = collector.items[0]
132 assert result.data == 0xDEAD
133 assert result.offset == 8
134 assert result.act_id == 3
135
136 # Verify deferred_read is cleared
137 assert sm.deferred_read is None
138
139
140class TestAC3_4WriteOnEmptyOrReserved:
141 """AC3.4: WRITE on EMPTY/RESERVED sets cell to FULL."""
142
143 def test_write_on_empty_cell(self):
144 env = simpy.Environment()
145 sm = StructureMemory(env, 0, cell_count=512)
146
147 # Cell 40 starts EMPTY
148 assert sm.cells[40].pres == Presence.EMPTY
149
150 # Inject WRITE
151 write_token = SMToken(target=0, addr=40, op=MemOp.WRITE, flags=None, data=0xCAFE, ret=None)
152 inject_token(env, sm.input_store, write_token)
153
154 env.run(until=100)
155
156 # Verify cell is FULL with correct data
157 assert sm.cells[40].pres == Presence.FULL
158 assert sm.cells[40].data_l == 0xCAFE
159
160 def test_write_on_reserved_cell(self):
161 env = simpy.Environment()
162 sm = StructureMemory(env, 0, cell_count=512)
163
164 # Set cell 50 to RESERVED
165 sm.cells[50].pres = Presence.RESERVED
166
167 # Inject WRITE
168 write_token = SMToken(target=0, addr=50, op=MemOp.WRITE, flags=None, data=0xF00D, ret=None)
169 inject_token(env, sm.input_store, write_token)
170
171 env.run(until=100)
172
173 # Verify cell is FULL with correct data
174 assert sm.cells[50].pres == Presence.FULL
175 assert sm.cells[50].data_l == 0xF00D
176
177
178class TestAC3_5Clear:
179 """AC3.5: CLEAR sets cell to EMPTY, cancels deferred read if targeting that cell."""
180
181 def test_clear_sets_empty(self):
182 env = simpy.Environment()
183 sm = StructureMemory(env, 0, cell_count=512)
184
185 # Set cell 60 to FULL
186 sm.cells[60].pres = Presence.FULL
187 sm.cells[60].data_l = 0x1234
188
189 # Inject CLEAR
190 clear_token = SMToken(target=0, addr=60, op=MemOp.CLEAR, flags=None, data=None, ret=None)
191 inject_token(env, sm.input_store, clear_token)
192
193 env.run(until=100)
194
195 # Verify cell is EMPTY
196 assert sm.cells[60].pres == Presence.EMPTY
197 assert sm.cells[60].data_l is None
198
199 def test_clear_cancels_deferred_read(self):
200 env = simpy.Environment()
201 sm = StructureMemory(env, 0, cell_count=512)
202
203 # Create collector
204 collector = simpy.Store(env)
205 sm.route_table[0] = collector
206
207 # Set up deferred read on cell 70
208 ret_route = CMToken(target=0, offset=10, act_id=0, data=0)
209 read_token = SMToken(target=0, addr=70, op=MemOp.READ, flags=None, data=None, ret=ret_route)
210 inject_token(env, sm.input_store, read_token)
211
212 env.run(until=10)
213
214 # Now inject CLEAR on cell 70
215 clear_token = SMToken(target=0, addr=70, op=MemOp.CLEAR, flags=None, data=None, ret=None)
216 inject_token(env, sm.input_store, clear_token)
217
218 env.run(until=100)
219
220 # Verify cell is EMPTY
221 assert sm.cells[70].pres == Presence.EMPTY
222
223 # Verify deferred_read is cleared
224 assert sm.deferred_read is None
225
226 # Verify no result token was emitted (deferred read was cancelled)
227 assert len(collector.items) == 0
228
229
230class TestAC3_8WriteOnFull:
231 """AC3.8: WRITE on FULL cell overwrites data."""
232
233 def test_write_overwrites_full_cell(self):
234 env = simpy.Environment()
235 sm = StructureMemory(env, 0, cell_count=512)
236
237 # Set cell 80 to FULL with data X
238 sm.cells[80].pres = Presence.FULL
239 sm.cells[80].data_l = 0x5555
240
241 # Inject WRITE with data Y
242 write_token = SMToken(target=0, addr=80, op=MemOp.WRITE, flags=None, data=0xAAAA, ret=None)
243 inject_token(env, sm.input_store, write_token)
244
245 env.run(until=100)
246
247 # Verify cell still FULL but data is overwritten
248 assert sm.cells[80].pres == Presence.FULL
249 assert sm.cells[80].data_l == 0xAAAA
250
251
252class TestAC3_6AtomicOps:
253 """AC3.6: READ_INC/READ_DEC atomically modify and return value (lower 256 cells only)."""
254
255 def test_read_inc_returns_old_value(self):
256 env = simpy.Environment()
257 sm = StructureMemory(env, 0, cell_count=512)
258
259 # Set cell 100 to FULL with value 42
260 sm.cells[100].pres = Presence.FULL
261 sm.cells[100].data_l = 42
262
263 # Create collector
264 collector = simpy.Store(env)
265 sm.route_table[0] = collector
266
267 # Inject RD_INC
268 ret_route = CMToken(target=0, offset=11, act_id=0, data=0)
269 inc_token = SMToken(target=0, addr=100, op=MemOp.RD_INC, flags=None, data=None, ret=ret_route)
270 inject_token(env, sm.input_store, inc_token)
271
272 env.run(until=100)
273
274 # Verify cell was incremented
275 assert sm.cells[100].data_l == 43
276
277 # Verify result token has old value
278 assert len(collector.items) == 1
279 assert collector.items[0].data == 42
280
281 def test_read_inc_wraps_at_0xFFFF(self):
282 env = simpy.Environment()
283 sm = StructureMemory(env, 0, cell_count=512)
284
285 # Set cell 110 to FULL with max value
286 sm.cells[110].pres = Presence.FULL
287 sm.cells[110].data_l = 0xFFFF
288
289 # Create collector
290 collector = simpy.Store(env)
291 sm.route_table[0] = collector
292
293 # Inject RD_INC
294 ret_route = CMToken(target=0, offset=12, act_id=0, data=0)
295 inc_token = SMToken(target=0, addr=110, op=MemOp.RD_INC, flags=None, data=None, ret=ret_route)
296 inject_token(env, sm.input_store, inc_token)
297
298 env.run(until=100)
299
300 # Verify cell wrapped to 0
301 assert sm.cells[110].data_l == 0
302
303 # Verify result token has old value
304 assert len(collector.items) == 1
305 assert collector.items[0].data == 0xFFFF
306
307 def test_read_dec_returns_old_value(self):
308 env = simpy.Environment()
309 sm = StructureMemory(env, 0, cell_count=512)
310
311 # Set cell 120 to FULL with value 100
312 sm.cells[120].pres = Presence.FULL
313 sm.cells[120].data_l = 100
314
315 # Create collector
316 collector = simpy.Store(env)
317 sm.route_table[0] = collector
318
319 # Inject RD_DEC
320 ret_route = CMToken(target=0, offset=13, act_id=0, data=0)
321 dec_token = SMToken(target=0, addr=120, op=MemOp.RD_DEC, flags=None, data=None, ret=ret_route)
322 inject_token(env, sm.input_store, dec_token)
323
324 env.run(until=100)
325
326 # Verify cell was decremented
327 assert sm.cells[120].data_l == 99
328
329 # Verify result token has old value
330 assert len(collector.items) == 1
331 assert collector.items[0].data == 100
332
333 def test_atomic_op_rejected_on_non_full_cell(self):
334 env = simpy.Environment()
335 sm = StructureMemory(env, 0, cell_count=512)
336
337 # Cell 130 starts EMPTY
338 assert sm.cells[130].pres == Presence.EMPTY
339
340 # Inject RD_INC
341 ret_route = CMToken(target=0, offset=14, act_id=0, data=0)
342 inc_token = SMToken(target=0, addr=130, op=MemOp.RD_INC, flags=None, data=None, ret=ret_route)
343 inject_token(env, sm.input_store, inc_token)
344
345 # Create collector
346 collector = simpy.Store(env)
347 sm.route_table[0] = collector
348
349 env.run(until=100)
350
351 # Verify no result token emitted and cell still EMPTY
352 assert sm.cells[130].pres == Presence.EMPTY
353 assert len(collector.items) == 0
354
355 def test_atomic_op_rejected_on_addr_gte_256(self):
356 env = simpy.Environment()
357 sm = StructureMemory(env, 0, cell_count=512)
358
359 # Set cell 256 to FULL
360 sm.cells[256].pres = Presence.FULL
361 sm.cells[256].data_l = 50
362
363 # Create collector
364 collector = simpy.Store(env)
365 sm.route_table[0] = collector
366
367 # Inject RD_INC on cell 256 (should be rejected)
368 ret_route = CMToken(target=0, offset=15, act_id=0, data=0)
369 inc_token = SMToken(target=0, addr=256, op=MemOp.RD_INC, flags=None, data=None, ret=ret_route)
370 inject_token(env, sm.input_store, inc_token)
371
372 env.run(until=100)
373
374 # Verify no result token emitted and cell unchanged
375 assert sm.cells[256].data_l == 50
376 assert len(collector.items) == 0
377
378
379class TestAC3_7DepthOneConstraint:
380 """AC3.7: Depth-1 constraint - SM enforces only one outstanding deferred read."""
381
382 def test_two_blocking_reads_stall_and_unblock(self):
383 """
384 AC3.7: Inject READ A then READ B while deferred register is occupied.
385 SM stalls on second READ. WRITE A satisfies first, SM unblocks and
386 retries READ B. Then WRITE B satisfies second. Both results arrive.
387 """
388 env = simpy.Environment()
389 sm = StructureMemory(env, 0, cell_count=512)
390
391 collector = simpy.Store(env)
392 sm.route_table[0] = collector
393
394 ret_a = CMToken(target=0, offset=20, act_id=0, data=0)
395 ret_b = CMToken(target=0, offset=21, act_id=1, data=0)
396 read_a = SMToken(target=0, addr=140, op=MemOp.READ, flags=None, data=None, ret=ret_a)
397 read_b = SMToken(target=0, addr=150, op=MemOp.READ, flags=None, data=None, ret=ret_b)
398
399 # Inject both READs before any WRITE
400 inject_token(env, sm.input_store, read_a)
401 inject_token(env, sm.input_store, read_b)
402 env.run(until=10)
403
404 # Cell A is WAITING, deferred register holds A's route
405 assert sm.cells[140].pres == Presence.WAITING
406 assert sm.deferred_read is not None
407 assert sm.deferred_read.cell_addr == 140
408 # Cell B still EMPTY — SM stalled on second READ
409 assert sm.cells[150].pres == Presence.EMPTY
410
411 # Satisfy first deferred by writing to A
412 write_a = SMToken(target=0, addr=140, op=MemOp.WRITE, flags=None, data=0x1111, ret=None)
413 inject_token(env, sm.input_store, write_a)
414 env.run(until=50)
415
416 # SM unblocked, retried READ B → cell B now WAITING
417 assert sm.cells[150].pres == Presence.WAITING
418 assert sm.deferred_read is not None
419 assert sm.deferred_read.cell_addr == 150
420
421 # Satisfy second deferred
422 write_b = SMToken(target=0, addr=150, op=MemOp.WRITE, flags=None, data=0x2222, ret=None)
423 inject_token(env, sm.input_store, write_b)
424 env.run(until=200)
425
426 # Both results collected
427 assert len(collector.items) == 2
428 assert collector.items[0].data == 0x1111
429 assert collector.items[1].data == 0x2222
430 assert sm.cells[150].pres == Presence.FULL
431 assert sm.cells[150].data_l == 0x2222
432
433
434class TestAC3_9CAS:
435 """AC3.9: CAS on FULL cell with compare-and-swap semantics."""
436
437 def test_cas_match_swaps_value(self):
438 env = simpy.Environment()
439 sm = StructureMemory(env, 0, cell_count=512)
440
441 # Set cell 160 to FULL with value 10
442 sm.cells[160].pres = Presence.FULL
443 sm.cells[160].data_l = 10
444
445 # Create collector
446 collector = simpy.Store(env)
447 sm.route_table[0] = collector
448
449 # Inject CMP_SW with flags=10 (expected), data=99 (new)
450 ret_route = CMToken(target=0, offset=22, act_id=0, data=0)
451 cas_token = SMToken(target=0, addr=160, op=MemOp.CMP_SW, flags=10, data=99, ret=ret_route)
452 inject_token(env, sm.input_store, cas_token)
453
454 env.run(until=100)
455
456 # Verify cell now has new value
457 assert sm.cells[160].data_l == 99
458
459 # Verify result token has old value
460 assert len(collector.items) == 1
461 assert collector.items[0].data == 10
462
463 def test_cas_mismatch_no_swap(self):
464 env = simpy.Environment()
465 sm = StructureMemory(env, 0, cell_count=512)
466
467 # Set cell 170 to FULL with value 10
468 sm.cells[170].pres = Presence.FULL
469 sm.cells[170].data_l = 10
470
471 # Create collector
472 collector = simpy.Store(env)
473 sm.route_table[0] = collector
474
475 # Inject CMP_SW with flags=20 (mismatch), data=99 (new)
476 ret_route = CMToken(target=0, offset=23, act_id=0, data=0)
477 cas_token = SMToken(target=0, addr=170, op=MemOp.CMP_SW, flags=20, data=99, ret=ret_route)
478 inject_token(env, sm.input_store, cas_token)
479
480 env.run(until=100)
481
482 # Verify cell unchanged
483 assert sm.cells[170].data_l == 10
484
485 # Verify result token has old value
486 assert len(collector.items) == 1
487 assert collector.items[0].data == 10
488
489 def test_cas_rejected_on_addr_gte_256(self):
490 env = simpy.Environment()
491 sm = StructureMemory(env, 0, cell_count=512)
492
493 # Set cell 256 to FULL
494 sm.cells[256].pres = Presence.FULL
495 sm.cells[256].data_l = 10
496
497 # Create collector
498 collector = simpy.Store(env)
499 sm.route_table[0] = collector
500
501 # Inject CMP_SW on cell 256 (should be rejected)
502 ret_route = CMToken(target=0, offset=24, act_id=0, data=0)
503 cas_token = SMToken(target=0, addr=256, op=MemOp.CMP_SW, flags=10, data=99, ret=ret_route)
504 inject_token(env, sm.input_store, cas_token)
505
506 env.run(until=100)
507
508 # Verify no result token and cell unchanged
509 assert sm.cells[256].data_l == 10
510 assert len(collector.items) == 0
511
512
513class TestPresenceStateMachineInvariant:
514 """Property-based test: presence state machine invariant."""
515
516 @given(sm_token())
517 def test_valid_presence_state_after_operations(self, token):
518 """Verify that after any valid operation, cell is in a valid Presence state."""
519 env = simpy.Environment()
520 sm = StructureMemory(env, 0, cell_count=512)
521
522 # Create collector for results
523 collector = simpy.Store(env)
524 sm.route_table[0] = collector
525
526 # Ensure return route is valid
527 if token.ret is None:
528 token = SMToken(
529 target=token.target,
530 addr=token.addr,
531 op=token.op,
532 flags=token.flags,
533 data=token.data,
534 ret=CMToken(target=0, offset=0, act_id=0, data=0),
535 )
536
537 # Pre-populate target cell as FULL if it's an atomic operation
538 if token.op in (MemOp.RD_INC, MemOp.RD_DEC, MemOp.CMP_SW):
539 sm.cells[token.addr].pres = Presence.FULL
540 sm.cells[token.addr].data_l = 0x1234
541
542 inject_token(env, sm.input_store, token)
543
544 # Run simulation
545 env.run(until=100)
546
547 # Verify cell is in valid state and specific transitions per op type
548 cell = sm.cells[token.addr]
549 assert cell.pres in (Presence.EMPTY, Presence.RESERVED, Presence.FULL, Presence.WAITING)
550
551 # Verify specific state transitions per operation type
552 if token.op == MemOp.WRITE:
553 # WRITE should set cell to FULL
554 assert cell.pres == Presence.FULL
555 assert cell.data_l == token.data
556 elif token.op == MemOp.READ:
557 # READ on empty cell should set to WAITING
558 if cell.pres == Presence.WAITING:
559 assert sm.deferred_read is not None
560 else:
561 assert cell.pres in (Presence.EMPTY, Presence.FULL)
562 elif token.op == MemOp.CLEAR:
563 # CLEAR should set to EMPTY
564 assert cell.pres == Presence.EMPTY
565 elif token.op == MemOp.ALLOC:
566 # ALLOC on EMPTY should set to RESERVED
567 assert cell.pres == Presence.RESERVED
568 elif token.op in (MemOp.RD_INC, MemOp.RD_DEC):
569 # Atomic ops on FULL should stay FULL with modified data
570 assert cell.pres == Presence.FULL
571 elif token.op == MemOp.CMP_SW:
572 # CMP_SW on FULL should stay FULL (data may change)
573 assert cell.pres == Presence.FULL
574
575
576class TestWriteAlwaysSetsDataL:
577 """Property-based test: WRITE always sets data_l to the written value."""
578
579 @given(sm_token(op=MemOp.WRITE))
580 def test_write_sets_data_l(self, token):
581 """Verify WRITE operations always set data_l to the written value."""
582 env = simpy.Environment()
583 sm = StructureMemory(env, 0, cell_count=512)
584
585 # Inject token
586 inject_token(env, sm.input_store, token)
587
588 # Run simulation
589 env.run(until=100)
590
591 # Verify data_l is set to token.data
592 assert sm.cells[token.addr].data_l == token.data
593 assert sm.cells[token.addr].pres == Presence.FULL
594
595
596class TestWriteReadRoundtrip:
597 """Property-based test: WRITE→READ roundtrip always returns written value."""
598
599 @given(sm_token(op=MemOp.WRITE), sm_return_route())
600 def test_write_read_roundtrip(self, write_token, read_route):
601 """WRITE→READ roundtrip always returns written value."""
602 env = simpy.Environment()
603 sm = StructureMemory(env, 0, cell_count=512)
604
605 # Create collector for results
606 collector = simpy.Store(env)
607 sm.route_table[0] = collector
608
609 # Inject WRITE
610 inject_token(env, sm.input_store, write_token)
611 env.run(until=10)
612
613 # Now READ from same cell with return route
614 read_token = SMToken(
615 target=write_token.target,
616 addr=write_token.addr,
617 op=MemOp.READ,
618 flags=None,
619 data=None,
620 ret=read_route,
621 )
622 inject_token(env, sm.input_store, read_token)
623 env.run(until=100)
624
625 # Verify result token has written data
626 assert len(collector.items) == 1
627 result = collector.items[0]
628 assert result.data == write_token.data
629
630
631class TestClearAlwaysEmptifies:
632 """Property-based test: CLEAR on any state produces EMPTY."""
633
634 @given(sm_token(op=MemOp.CLEAR))
635 def test_clear_on_any_state(self, token):
636 """CLEAR on any state produces EMPTY."""
637 env = simpy.Environment()
638 sm = StructureMemory(env, 0, cell_count=512)
639
640 # Pre-set cell to a random state (FULL with data)
641 sm.cells[token.addr].pres = Presence.FULL
642 sm.cells[token.addr].data_l = 0xBEEF
643
644 # Inject CLEAR
645 inject_token(env, sm.input_store, token)
646 env.run(until=100)
647
648 # Verify cell is EMPTY
649 assert sm.cells[token.addr].pres == Presence.EMPTY
650 assert sm.cells[token.addr].data_l is None
651
652
653class TestRDIncDecRestores:
654 """Property-based test: RD_INC then RD_DEC restores original value (mod 16-bit)."""
655
656 @given(uint16, sm_return_route(), sm_return_route())
657 def test_rd_inc_dec_restores(self, original_value, return_route_inc, return_route_dec):
658 """RD_INC then RD_DEC restores original value."""
659 env = simpy.Environment()
660 sm = StructureMemory(env, 0, cell_count=512)
661
662 # Create collector for results
663 collector = simpy.Store(env)
664 sm.route_table[0] = collector
665
666 # Set cell to original value
667 sm.cells[100].pres = Presence.FULL
668 sm.cells[100].data_l = original_value
669
670 # Inject RD_INC
671 inc_token = SMToken(
672 target=0,
673 addr=100,
674 op=MemOp.RD_INC,
675 flags=None,
676 data=None,
677 ret=return_route_inc,
678 )
679 inject_token(env, sm.input_store, inc_token)
680 env.run(until=10)
681
682 # Cell now has (original_value + 1) & 0xFFFF
683 # Inject RD_DEC
684 dec_token = SMToken(
685 target=0,
686 addr=100,
687 op=MemOp.RD_DEC,
688 flags=None,
689 data=None,
690 ret=return_route_dec,
691 )
692 inject_token(env, sm.input_store, dec_token)
693 env.run(until=100)
694
695 # Cell should be back to original_value
696 assert sm.cells[100].data_l == original_value
697
698
699class TestAtomicOpsOnNonFullRejected:
700 """Property-based test: Atomic ops on non-FULL cells are rejected."""
701
702 @given(sm_token(op=MemOp.RD_INC), sm_return_route())
703 def test_rd_inc_on_non_full_rejected(self, token, return_route):
704 """RD_INC on non-FULL cell is rejected."""
705 env = simpy.Environment()
706 sm = StructureMemory(env, 0, cell_count=512)
707
708 # Create collector
709 collector = simpy.Store(env)
710 sm.route_table[0] = collector
711
712 # Ensure cell is EMPTY (not FULL)
713 assert sm.cells[token.addr].pres == Presence.EMPTY
714
715 # Create RD_INC token with return route
716 inc_token = SMToken(
717 target=token.target,
718 addr=token.addr,
719 op=MemOp.RD_INC,
720 flags=None,
721 data=None,
722 ret=return_route,
723 )
724
725 inject_token(env, sm.input_store, inc_token)
726 env.run(until=100)
727
728 # Verify no result token and cell still EMPTY
729 assert len(collector.items) == 0
730 assert sm.cells[token.addr].pres == Presence.EMPTY
731
732 @given(sm_token(op=MemOp.RD_DEC), sm_return_route())
733 def test_rd_dec_on_non_full_rejected(self, token, return_route):
734 """RD_DEC on non-FULL cell is rejected."""
735 env = simpy.Environment()
736 sm = StructureMemory(env, 0, cell_count=512)
737
738 # Create collector
739 collector = simpy.Store(env)
740 sm.route_table[0] = collector
741
742 # Ensure cell is EMPTY
743 assert sm.cells[token.addr].pres == Presence.EMPTY
744
745 # Create RD_DEC token
746 dec_token = SMToken(
747 target=token.target,
748 addr=token.addr,
749 op=MemOp.RD_DEC,
750 flags=None,
751 data=None,
752 ret=return_route,
753 )
754
755 inject_token(env, sm.input_store, dec_token)
756 env.run(until=100)
757
758 # Verify no result token and cell still EMPTY
759 assert len(collector.items) == 0
760 assert sm.cells[token.addr].pres == Presence.EMPTY
761
762 @given(sm_token(op=MemOp.CMP_SW), sm_return_route())
763 def test_cmp_sw_on_non_full_rejected(self, token, return_route):
764 """CMP_SW on non-FULL cell is rejected."""
765 env = simpy.Environment()
766 sm = StructureMemory(env, 0, cell_count=512)
767
768 # Create collector
769 collector = simpy.Store(env)
770 sm.route_table[0] = collector
771
772 # Ensure cell is EMPTY
773 assert sm.cells[token.addr].pres == Presence.EMPTY
774
775 # Create CMP_SW token
776 cas_token = SMToken(
777 target=token.target,
778 addr=token.addr,
779 op=MemOp.CMP_SW,
780 flags=0x1234,
781 data=0x5678,
782 ret=return_route,
783 )
784
785 inject_token(env, sm.input_store, cas_token)
786 env.run(until=100)
787
788 # Verify no result token and cell still EMPTY
789 assert len(collector.items) == 0
790 assert sm.cells[token.addr].pres == Presence.EMPTY
791
792
793class TestBoundaryEdgeCases:
794 """Test boundary and edge cases for SM operations."""
795
796 def test_rd_dec_wrapping_0x0000_to_0xffff(self):
797 """RD_DEC on 0x0000 wraps to 0xFFFF."""
798 env = simpy.Environment()
799 sm = StructureMemory(env, 0, cell_count=512)
800
801 # Set cell 150 to FULL with value 0
802 sm.cells[150].pres = Presence.FULL
803 sm.cells[150].data_l = 0x0000
804
805 # Create collector
806 collector = simpy.Store(env)
807 sm.route_table[0] = collector
808
809 # Inject RD_DEC
810 ret_route = CMToken(target=0, offset=16, act_id=0, data=0)
811 dec_token = SMToken(target=0, addr=150, op=MemOp.RD_DEC, flags=None, data=None, ret=ret_route)
812 inject_token(env, sm.input_store, dec_token)
813
814 env.run(until=100)
815
816 # Verify cell wrapped to 0xFFFF
817 assert sm.cells[150].data_l == 0xFFFF
818
819 # Verify result token has old value (0)
820 assert len(collector.items) == 1
821 assert collector.items[0].data == 0x0000
822
823 def test_alloc_empty_to_reserved(self):
824 """ALLOC changes cell from EMPTY to RESERVED."""
825 env = simpy.Environment()
826 sm = StructureMemory(env, 0, cell_count=512)
827
828 # Cell 200 starts EMPTY
829 assert sm.cells[200].pres == Presence.EMPTY
830
831 # Inject ALLOC
832 alloc_token = SMToken(target=0, addr=200, op=MemOp.ALLOC, flags=None, data=None, ret=None)
833 inject_token(env, sm.input_store, alloc_token)
834
835 env.run(until=100)
836
837 # Verify cell is now RESERVED
838 assert sm.cells[200].pres == Presence.RESERVED
839
840 def test_cmp_sw_on_non_full_cell_rejected(self):
841 """CMP_SW on non-FULL cell (EMPTY state) is rejected."""
842 env = simpy.Environment()
843 sm = StructureMemory(env, 0, cell_count=512)
844
845 # Create collector
846 collector = simpy.Store(env)
847 sm.route_table[0] = collector
848
849 # Cell 210 is EMPTY
850 assert sm.cells[210].pres == Presence.EMPTY
851
852 # Inject CMP_SW on EMPTY cell
853 ret_route = CMToken(target=0, offset=17, act_id=0, data=0)
854 cas_token = SMToken(
855 target=0,
856 addr=210,
857 op=MemOp.CMP_SW,
858 flags=0x1111,
859 data=0x2222,
860 ret=ret_route
861 )
862 inject_token(env, sm.input_store, cas_token)
863
864 env.run(until=100)
865
866 # Verify no result token and cell still EMPTY
867 assert len(collector.items) == 0
868 assert sm.cells[210].pres == Presence.EMPTY
869
870
871# Task 3: T0/T1 Tier Split and EXEC Opcode Tests
872