OR-1 dataflow CPU sketch
at 00d336d2d4b197bbb9dbbf3641f5f112bf0cf3ec 382 lines 14 kB view raw
1""" 2Tests for EXEC opcode and IRAMWriteToken bootstrap functionality. 3 4Verifies acceptance criteria: 5- token-migration.AC2.1: IRAMWriteToken routes to target PE via network (isinstance CMToken) 6- token-migration.AC2.4: IRAMWriteToken with invalid target PE raises or is dropped 7- token-migration.AC5.1: EXEC reads Token objects from T0 and injects them 8- token-migration.AC5.2: Injected tokens are processed normally by target PEs/SMs 9- token-migration.AC5.3: EXEC can load a program (IRAM + seed tokens) and execute correctly 10- token-migration.AC5.4: EXEC on empty T0 region is a no-op 11""" 12 13import pytest 14import simpy 15 16from cm_inst import ALUInst, Addr, MemOp, Port, RoutingOp 17from emu import build_topology 18from emu.types import PEConfig, SMConfig 19from sm_mod import Presence 20from tokens import DyadToken, IRAMWriteToken, MonadToken, SMToken 21 22 23class TestAC2_1IRAMWriteTokenRouting: 24 """AC2.1: IRAMWriteToken routes to target PE via network (isinstance CMToken).""" 25 26 def test_iram_write_token_routes_to_target_pe_via_system_inject(self): 27 """IRAMWriteToken is routed to correct target PE when injected via system.inject().""" 28 env = simpy.Environment() 29 30 sys = build_topology( 31 env, 32 [ 33 PEConfig(pe_id=0, iram={}), 34 PEConfig(pe_id=1, iram={}), 35 PEConfig(pe_id=2, iram={}), 36 ], 37 [], 38 ) 39 40 # Create IRAMWriteToken targeting PE 2 41 inst = ALUInst( 42 op=RoutingOp.CONST, 43 dest_l=Addr(a=0, port=Port.L, pe=0), 44 dest_r=None, 45 const=0x1234, 46 ) 47 iram_token = IRAMWriteToken( 48 target=2, # Target PE 2 49 offset=10, 50 ctx=0, 51 data=0, 52 instructions=(inst,), 53 ) 54 55 # Inject via system.inject() which appends to PE 2's input_store.items directly 56 sys.inject(iram_token) 57 58 # Verify token arrived at PE 2's input_store (inject appends directly to items) 59 assert len(sys.pes[2].input_store.items) > 0 60 received = sys.pes[2].input_store.items[0] 61 assert isinstance(received, IRAMWriteToken) 62 assert received.target == 2 63 assert received.offset == 10 64 65 # PE 0 and PE 1 should not have received the token 66 assert len(sys.pes[0].input_store.items) == 0 67 assert len(sys.pes[1].input_store.items) == 0 68 69 def test_iram_write_token_multiple_targets(self): 70 """Multiple IRAMWriteTokens can route to different target PEs.""" 71 env = simpy.Environment() 72 73 sys = build_topology( 74 env, 75 [ 76 PEConfig(pe_id=0, iram={}), 77 PEConfig(pe_id=1, iram={}), 78 ], 79 [], 80 ) 81 82 # Create two IRAMWriteTokens targeting different PEs 83 inst0 = ALUInst(op=RoutingOp.CONST, dest_l=None, dest_r=None, const=100) 84 inst1 = ALUInst(op=RoutingOp.CONST, dest_l=None, dest_r=None, const=200) 85 86 token0 = IRAMWriteToken(target=0, offset=0, ctx=0, data=0, instructions=(inst0,)) 87 token1 = IRAMWriteToken(target=1, offset=5, ctx=0, data=0, instructions=(inst1,)) 88 89 # Inject both tokens 90 sys.inject(token0) 91 sys.inject(token1) 92 93 # Verify routing via direct items inspection 94 assert len(sys.pes[0].input_store.items) == 1 95 assert len(sys.pes[1].input_store.items) == 1 96 assert sys.pes[0].input_store.items[0].offset == 0 97 assert sys.pes[1].input_store.items[0].offset == 5 98 99 100class TestAC2_4IRAMWriteTokenInvalidTarget: 101 """AC2.4: IRAMWriteToken with invalid target PE raises or is dropped.""" 102 103 def test_iram_write_token_invalid_target_raises_key_error(self): 104 """IRAMWriteToken with non-existent target PE raises KeyError via system.send().""" 105 env = simpy.Environment() 106 107 # Create topology with only PE 0 108 sys = build_topology( 109 env, 110 [PEConfig(pe_id=0, iram={})], 111 [], 112 ) 113 114 # Create IRAMWriteToken targeting non-existent PE 5 115 inst = ALUInst(op=RoutingOp.CONST, dest_l=None, dest_r=None, const=0x5555) 116 iram_token = IRAMWriteToken( 117 target=5, # PE 5 does not exist 118 offset=0, 119 ctx=0, 120 data=0, 121 instructions=(inst,), 122 ) 123 124 # Attempting to send should raise KeyError 125 def process_token(): 126 yield from sys.send(iram_token) 127 128 with pytest.raises(KeyError): 129 env.process(process_token()) 130 env.run(until=100) 131 132 133class TestAC5_1ExecInjectsTokens: 134 """AC5.1: EXEC reads Token objects from T0 and injects them into the network via send().""" 135 136 def test_exec_injects_single_token_to_pe(self): 137 """EXEC at T0 address reads a DyadToken and injects it via send() which triggers SimPy events.""" 138 env = simpy.Environment() 139 140 sys = build_topology( 141 env, 142 [ 143 PEConfig(pe_id=0, iram={}), 144 PEConfig(pe_id=1, iram={}), 145 ], 146 [SMConfig(sm_id=0, cell_count=512, tier_boundary=256)], 147 ) 148 149 # Create a DyadToken to be injected by EXEC 150 seed_token = DyadToken( 151 target=1, 152 offset=0, 153 ctx=0, 154 data=0x4567, 155 port=Port.L, 156 gen=0, 157 wide=False, 158 ) 159 160 # Pre-populate T0 with the token 161 sys.sms[0].t0_store.append(seed_token) 162 sys.sms[0].system = sys 163 164 def test_sequence(): 165 # SM0 executes EXEC at T0 address 256 (t0_idx=0) 166 exec_token = SMToken(target=0, addr=256, op=MemOp.EXEC, flags=None, data=None, ret=None) 167 yield sys.sms[0].input_store.put(exec_token) 168 169 env.process(test_sequence()) 170 env.run(until=100) 171 172 # Verify token was injected via send() - it will be consumed by PE1's process 173 # and stored in matching_store. The key is that send() triggers the get() event. 174 assert sys.pes[1].matching_store[0][0].occupied is True 175 assert sys.pes[1].matching_store[0][0].data == 0x4567 176 assert sys.pes[1].matching_store[0][0].port == Port.L 177 178 def test_exec_injects_multiple_tokens(self): 179 """EXEC at T0 address reads multiple tokens and injects them in order via send(). 180 181 Verifies that send() properly wakes up pending get() operations, allowing multiple 182 tokens to be delivered in sequence through SimPy's event system. 183 """ 184 env = simpy.Environment() 185 186 sys = build_topology( 187 env, 188 [ 189 PEConfig(pe_id=0, iram={}), 190 PEConfig(pe_id=1, iram={}), 191 ], 192 [ 193 SMConfig(sm_id=0, cell_count=512, tier_boundary=256), 194 SMConfig(sm_id=1, cell_count=512, tier_boundary=256), 195 ], 196 ) 197 198 # Create multiple SMTokens to be injected (write operations don't require IRAM) 199 token1 = SMToken(target=1, addr=100, op=MemOp.WRITE, flags=None, data=0x1111, ret=None) 200 token2 = SMToken(target=1, addr=101, op=MemOp.WRITE, flags=None, data=0x2222, ret=None) 201 202 # Pre-populate T0 203 sys.sms[0].t0_store.extend([token1, token2]) 204 sys.sms[0].system = sys 205 206 def test_sequence(): 207 exec_token = SMToken(target=0, addr=256, op=MemOp.EXEC, flags=None, data=None, ret=None) 208 yield sys.sms[0].input_store.put(exec_token) 209 210 env.process(test_sequence()) 211 env.run(until=100) 212 213 # Verify both tokens were injected via send() and processed by SM1 214 # Both WRITE operations should have updated SM1's cells 215 assert sys.sms[1].cells[100].pres == Presence.FULL 216 assert sys.sms[1].cells[100].data_l == 0x1111 217 assert sys.sms[1].cells[101].pres == Presence.FULL 218 assert sys.sms[1].cells[101].data_l == 0x2222 219 220 221class TestAC5_2ExecTokensProcessedNormally: 222 """AC5.2: Injected tokens are processed normally by target PEs/SMs.""" 223 224 def test_injected_dyad_token_received_by_pe(self): 225 """DyadToken injected by EXEC via send() is received and processed by target PE.""" 226 env = simpy.Environment() 227 228 sys = build_topology( 229 env, 230 [ 231 PEConfig(pe_id=0, iram={}), 232 PEConfig(pe_id=1, iram={}), 233 ], 234 [SMConfig(sm_id=0, cell_count=512, tier_boundary=256)], 235 ) 236 237 # Create dyad token to be injected by EXEC 238 token_l = DyadToken(target=1, offset=0, ctx=0, data=0xABCD, port=Port.L, gen=0, wide=False) 239 240 # Pre-populate T0 241 sys.sms[0].t0_store.append(token_l) 242 sys.sms[0].system = sys 243 244 def test_sequence(): 245 exec_token = SMToken(target=0, addr=256, op=MemOp.EXEC, flags=None, data=None, ret=None) 246 yield sys.sms[0].input_store.put(exec_token) 247 248 env.process(test_sequence()) 249 env.run(until=100) 250 251 # Verify PE1 received and processed the token via matching_store 252 assert sys.pes[1].matching_store[0][0].occupied is True 253 assert sys.pes[1].matching_store[0][0].data == 0xABCD 254 assert sys.pes[1].matching_store[0][0].port == Port.L 255 256 257class TestAC5_3BootstrapProgram: 258 """AC5.3: EXEC can load a program (IRAM writes + seed tokens) from T0 that executes correctly.""" 259 260 def test_bootstrap_with_iram_write_and_seed_tokens(self): 261 """Full bootstrap: T0 contains IRAMWriteToken and seed tokens, EXEC loads and runs them. 262 263 Tests the FULL SimPy execution chain: 264 - Populate T0 with IRAMWriteToken + seed DyadToken pair 265 - Send EXEC SMToken to SM 266 - Run env.run() 267 - Assert on pe.output_log containing the expected ALU result 268 """ 269 env = simpy.Environment() 270 271 sys = build_topology( 272 env, 273 [ 274 PEConfig(pe_id=0, iram={}), # PE0 starts empty, will be loaded by bootstrap 275 PEConfig(pe_id=1, iram={}), # PE1 is output receiver 276 ], 277 [SMConfig(sm_id=0, cell_count=512, tier_boundary=256)], 278 ) 279 280 # Create instruction to be loaded: CONST(0xABCD) to PE1 281 const_inst = ALUInst( 282 op=RoutingOp.CONST, 283 dest_l=Addr(a=0, port=Port.L, pe=1), 284 dest_r=None, 285 const=0xABCD, 286 ) 287 288 # Create IRAMWriteToken to load instruction at offset 0 289 iram_write = IRAMWriteToken( 290 target=0, 291 offset=0, 292 ctx=0, 293 data=0, 294 instructions=(const_inst,), 295 ) 296 297 # Create seed MonadToken to trigger the loaded instruction at PE0 298 seed_token = MonadToken( 299 target=0, 300 offset=0, 301 ctx=0, 302 data=0, 303 inline=False, 304 ) 305 306 # Pre-populate T0 with bootstrap sequence 307 sys.sms[0].t0_store.append(iram_write) 308 sys.sms[0].t0_store.append(seed_token) 309 sys.sms[0].system = sys 310 311 def test_sequence(): 312 # Trigger EXEC to bootstrap 313 exec_token = SMToken(target=0, addr=256, op=MemOp.EXEC, flags=None, data=None, ret=None) 314 yield sys.sms[0].input_store.put(exec_token) 315 316 env.process(test_sequence()) 317 env.run(until=200) 318 319 # Verify FULL SimPy execution chain: 320 # 1. PE0 should have received and processed IRAMWriteToken 321 assert 0 in sys.pes[0].iram, "Instruction not loaded into IRAM by bootstrap" 322 assert sys.pes[0].iram[0].op == RoutingOp.CONST 323 assert sys.pes[0].iram[0].const == 0xABCD 324 325 # 2. PE0 should have received and processed seed token, producing output 326 assert len(sys.pes[0].output_log) > 0, \ 327 "PE0 did not produce output; seed token may not have triggered IRAM execution" 328 329 # 3. The output should be routed to PE1 with the CONST value 330 output_to_pe1 = [t for t in sys.pes[0].output_log if t.target == 1] 331 assert len(output_to_pe1) > 0, "PE0 did not route output to PE1" 332 assert output_to_pe1[0].data == 0xABCD, f"Expected output data 0xABCD, got {output_to_pe1[0].data}" 333 334 335class TestAC5_4ExecOnEmptyT0: 336 """AC5.4: EXEC on empty T0 region is a no-op.""" 337 338 def test_exec_on_addr_beyond_t0_store_length_is_noop(self): 339 """EXEC at address beyond t0_store length produces no output.""" 340 env = simpy.Environment() 341 342 sys = build_topology( 343 env, 344 [PEConfig(pe_id=0, iram={})], 345 [SMConfig(sm_id=0, cell_count=512, tier_boundary=256)], 346 ) 347 348 # t0_store is empty, EXEC on T0 address beyond current length 349 def test_sequence(): 350 exec_token = SMToken(target=0, addr=300, op=MemOp.EXEC, flags=None, data=None, ret=None) 351 yield sys.sms[0].input_store.put(exec_token) 352 353 env.process(test_sequence()) 354 env.run(until=100) 355 356 # Verify no tokens were injected (output stores remain unchanged) 357 assert len(sys.pes[0].input_store.items) == 0 358 359 def test_exec_on_empty_t0_index(self): 360 """EXEC at T0 index with no token is a no-op.""" 361 env = simpy.Environment() 362 363 sys = build_topology( 364 env, 365 [PEConfig(pe_id=0, iram={})], 366 [SMConfig(sm_id=0, cell_count=512, tier_boundary=256)], 367 ) 368 369 # Pre-populate t0_store with one token 370 sys.sms[0].t0_store.append(MonadToken(target=0, offset=0, ctx=0, data=100, inline=False)) 371 372 # EXEC at index 5 which is beyond current t0_store length (1) 373 def test_sequence(): 374 exec_token = SMToken(target=0, addr=261, op=MemOp.EXEC, flags=None, data=None, ret=None) # t0_idx = 5 375 yield sys.sms[0].input_store.put(exec_token) 376 377 env.process(test_sequence()) 378 env.run(until=100) 379 380 # Verify only the pre-existing token remains in PE0 input_store (no injection happened) 381 # The pre-existing token at index 0 should not be re-injected by EXEC at index 5 382 assert len(sys.pes[0].input_store.items) == 0