OR-1 dataflow CPU sketch
at main 399 lines 13 kB view raw
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