OR-1 dataflow CPU sketch
at 5df3bf850390e76fdfe4e03946f1bc9a16e5ee71 674 lines 21 kB view raw
1""" 2Tests for initialization API smoke tests. 3 4Verifies: 5- or1-emu.AC5.1: IRAM initialization — PE has expected instructions at expected offsets 6- or1-emu.AC5.2: SM cell initialization — SM cells match config 7- or1-emu.AC5.3: Token injection — inject() routes tokens to correct stores by type 8""" 9 10import simpy 11 12from cm_inst import ALUInst, Addr, ArithOp, CfgOp, MemOp, Port, RoutingOp, SMInst 13from emu import build_topology, PEConfig, SMConfig 14from sm_mod import Presence 15from tokens import CMToken, CfgToken, DyadToken, LoadInstToken, MonadToken, SMToken 16 17 18class TestAC51IRAMInitialization: 19 """Test AC5.1: IRAM initialization""" 20 21 def test_iram_contains_instructions_at_configured_offsets(self): 22 """IRAM contains ALUInst at offsets specified in PEConfig.""" 23 env = simpy.Environment() 24 25 pe_iram = { 26 0: ALUInst( 27 op=ArithOp.ADD, 28 dest_l=None, 29 dest_r=None, 30 const=None, 31 ), 32 5: ALUInst( 33 op=RoutingOp.CONST, 34 dest_l=None, 35 dest_r=None, 36 const=99, 37 ), 38 } 39 40 sys = build_topology( 41 env, 42 [PEConfig(pe_id=0, iram=pe_iram)], 43 [], 44 ) 45 46 # Verify instructions are at expected offsets 47 assert 0 in sys.pes[0].iram 48 assert sys.pes[0].iram[0].op == ArithOp.ADD 49 assert 5 in sys.pes[0].iram 50 assert sys.pes[0].iram[5].op == RoutingOp.CONST 51 assert sys.pes[0].iram[5].const == 99 52 53 def test_iram_does_not_contain_uninitialized_offsets(self): 54 """IRAM does not contain offsets not specified in config.""" 55 env = simpy.Environment() 56 57 pe_iram = { 58 0: ALUInst(op=ArithOp.ADD, dest_l=None, dest_r=None, const=None), 59 5: ALUInst(op=RoutingOp.CONST, dest_l=None, dest_r=None, const=99), 60 } 61 62 sys = build_topology( 63 env, 64 [PEConfig(pe_id=0, iram=pe_iram)], 65 [], 66 ) 67 68 # Verify uninitialized offsets are NOT in IRAM 69 assert 3 not in sys.pes[0].iram 70 assert 10 not in sys.pes[0].iram 71 72 73class TestAC52SMCellInitialization: 74 """Test AC5.2: SM cell initialization""" 75 76 def test_sm_cells_initialized_with_presence_and_data(self): 77 """SM cells initialized via config contain expected presence and data.""" 78 env = simpy.Environment() 79 80 sm_config = SMConfig( 81 sm_id=0, 82 cell_count=512, 83 initial_cells={ 84 0: (Presence.FULL, 42), 85 10: (Presence.RESERVED, None), 86 }, 87 ) 88 89 sys = build_topology( 90 env, 91 [], 92 [sm_config], 93 ) 94 95 # Verify cell 0: FULL with data 42 96 assert sys.sms[0].cells[0].pres == Presence.FULL 97 assert sys.sms[0].cells[0].data_l == 42 98 99 # Verify cell 10: RESERVED with no data 100 assert sys.sms[0].cells[10].pres == Presence.RESERVED 101 assert sys.sms[0].cells[10].data_l is None 102 103 def test_uninitialized_sm_cells_are_empty(self): 104 """SM cells not in initial_cells config are EMPTY.""" 105 env = simpy.Environment() 106 107 sm_config = SMConfig( 108 sm_id=0, 109 cell_count=512, 110 initial_cells={ 111 0: (Presence.FULL, 42), 112 }, 113 ) 114 115 sys = build_topology( 116 env, 117 [], 118 [sm_config], 119 ) 120 121 # Verify uninitialized cells are EMPTY 122 assert sys.sms[0].cells[1].pres == Presence.EMPTY 123 assert sys.sms[0].cells[1].data_l is None 124 assert sys.sms[0].cells[100].pres == Presence.EMPTY 125 assert sys.sms[0].cells[100].data_l is None 126 127 128class TestAC53TokenInjection: 129 """Test AC5.3: Token injection API""" 130 131 def test_inject_monad_token_to_pe(self): 132 """inject() delivers MonadToken to correct PE's input_store.""" 133 env = simpy.Environment() 134 135 sys = build_topology( 136 env, 137 [PEConfig(pe_id=0, iram={}), PEConfig(pe_id=1, iram={})], 138 [], 139 ) 140 141 # Create and inject token to PE0 142 token = MonadToken( 143 target=0, 144 offset=0, 145 ctx=0, 146 data=42, 147 inline=False, 148 ) 149 150 sys.inject(token) 151 152 # Verify token is in PE0's input_store 153 assert len(sys.pes[0].input_store.items) == 1 154 assert sys.pes[0].input_store.items[0] == token 155 156 def test_inject_multiple_tokens_to_correct_pes(self): 157 """inject() places tokens in correct PE based on target.""" 158 env = simpy.Environment() 159 160 sys = build_topology( 161 env, 162 [PEConfig(pe_id=0, iram={}), PEConfig(pe_id=1, iram={})], 163 [], 164 ) 165 166 # Inject token to PE0 167 token0 = MonadToken(target=0, offset=0, ctx=0, data=10, inline=False) 168 sys.inject(token0) 169 170 # Inject token to PE1 171 token1 = MonadToken(target=1, offset=0, ctx=0, data=20, inline=False) 172 sys.inject(token1) 173 174 # Verify tokens arrived at correct PEs 175 assert len(sys.pes[0].input_store.items) == 1 176 assert sys.pes[0].input_store.items[0].data == 10 177 178 assert len(sys.pes[1].input_store.items) == 1 179 assert sys.pes[1].input_store.items[0].data == 20 180 181 def test_inject_sm_token_to_sm(self): 182 """inject() delivers SMToken to correct SM's input_store.""" 183 env = simpy.Environment() 184 185 sys = build_topology( 186 env, 187 [PEConfig(pe_id=0, iram={})], 188 [SMConfig(sm_id=0, cell_count=512), SMConfig(sm_id=1, cell_count=512)], 189 ) 190 191 # Create and inject token to SM0 192 token = SMToken( 193 target=0, 194 addr=5, 195 op=MemOp.READ, 196 flags=None, 197 data=None, 198 ret=CMToken(target=0, offset=0, ctx=0, data=0), 199 ) 200 201 sys.inject(token) 202 203 # Verify token is in SM0's input_store 204 assert len(sys.sms[0].input_store.items) == 1 205 assert sys.sms[0].input_store.items[0] == token 206 207 def test_inject_sm_multiple_tokens_to_correct_sms(self): 208 """inject() routes SMTokens to correct SM based on token.target.""" 209 env = simpy.Environment() 210 211 sys = build_topology( 212 env, 213 [PEConfig(pe_id=0, iram={})], 214 [SMConfig(sm_id=0, cell_count=512), SMConfig(sm_id=1, cell_count=512)], 215 ) 216 217 # Inject token to SM0 218 token0 = SMToken( 219 target=0, 220 addr=10, 221 op=MemOp.WRITE, 222 flags=None, 223 data=42, 224 ret=None, 225 ) 226 sys.inject(token0) 227 228 # Inject token to SM1 229 token1 = SMToken( 230 target=1, 231 addr=20, 232 op=MemOp.READ, 233 flags=None, 234 data=None, 235 ret=CMToken(target=0, offset=0, ctx=0, data=0), 236 ) 237 sys.inject(token1) 238 239 # Verify tokens arrived at correct SMs 240 assert len(sys.sms[0].input_store.items) == 1 241 assert sys.sms[0].input_store.items[0].addr == 10 242 243 assert len(sys.sms[1].input_store.items) == 1 244 assert sys.sms[1].input_store.items[0].addr == 20 245 246 247class TestAC51GenCounterInitialization: 248 """Test AC5.1 extended: gen_counter initialization""" 249 250 def test_gen_counters_initialized_from_config(self): 251 """PEConfig with gen_counters list initializes PE's gen_counters.""" 252 env = simpy.Environment() 253 254 sys = build_topology( 255 env, 256 [PEConfig(pe_id=0, iram={}, gen_counters=[1, 0, 2, 3])], 257 [], 258 ) 259 260 # Verify gen_counters match config 261 assert sys.pes[0].gen_counters == [1, 0, 2, 3] 262 263 def test_gen_counters_default_to_zero_when_none(self): 264 """PEConfig with gen_counters=None (default) initializes all to 0.""" 265 env = simpy.Environment() 266 267 sys = build_topology( 268 env, 269 [PEConfig(pe_id=0, iram={}, gen_counters=None)], 270 [], 271 ) 272 273 # Verify all gen_counters are 0 (ctx_slots default is 16) 274 assert sys.pes[0].gen_counters == [0] * 16 275 276 def test_gen_counters_with_custom_ctx_slots(self): 277 """gen_counters list length matches ctx_slots.""" 278 env = simpy.Environment() 279 280 sys = build_topology( 281 env, 282 [PEConfig(pe_id=0, iram={}, ctx_slots=8, gen_counters=[1, 2, 3, 4, 5, 6, 7, 8])], 283 [], 284 ) 285 286 # Verify gen_counters match provided list 287 assert sys.pes[0].gen_counters == [1, 2, 3, 4, 5, 6, 7, 8] 288 assert len(sys.pes[0].gen_counters) == 8 289 290 291class TestAC61E2EConstFedsAdd: 292 """Test AC6.1: CONST on PE0 feeds ADD on PE1""" 293 294 def test_const_feeds_add(self): 295 """CONST on PE0 emits tokens that arrive at PE1, trigger ADD, produce correct result.""" 296 env = simpy.Environment() 297 298 # PE0 IRAM: offset 0 = CONST(7), offset 1 = CONST(3) 299 pe0_iram = { 300 0: ALUInst( 301 op=RoutingOp.CONST, 302 dest_l=Addr(a=0, port=Port.L, pe=1), 303 dest_r=None, 304 const=7, 305 ), 306 1: ALUInst( 307 op=RoutingOp.CONST, 308 dest_l=Addr(a=0, port=Port.R, pe=1), 309 dest_r=None, 310 const=3, 311 ), 312 } 313 314 # PE1 IRAM: offset 0 = ADD routing to PE2 (collector) 315 pe1_iram = { 316 0: ALUInst( 317 op=ArithOp.ADD, 318 dest_l=Addr(a=0, port=Port.L, pe=2), 319 dest_r=None, 320 const=None, 321 ), 322 } 323 324 # Build topology with 3 PEs (PE2 has no IRAM, acts as collector) 325 sys = build_topology( 326 env, 327 [ 328 PEConfig(pe_id=0, iram=pe0_iram), 329 PEConfig(pe_id=1, iram=pe1_iram), 330 PEConfig(pe_id=2, iram={}), 331 ], 332 [], 333 ) 334 335 # Set up PE1's routing to direct output to a collector store (not PE2's input_store) 336 # This simulates a sink where results are collected without being consumed 337 collector_store = simpy.Store(env, capacity=100) 338 sys.pes[1].route_table[2] = collector_store 339 340 # Inject tokens via SimPy process 341 def injector(): 342 yield sys.pes[0].input_store.put(MonadToken(target=0, offset=0, ctx=0, data=0, inline=False)) 343 yield sys.pes[0].input_store.put(MonadToken(target=0, offset=1, ctx=0, data=0, inline=False)) 344 345 env.process(injector()) 346 347 # Run simulation until quiescence 348 env.run(until=1000) 349 350 # Verify collector receives exactly one token with data=10 (7+3) 351 assert len(collector_store.items) == 1 352 result_token = collector_store.items[0] 353 assert result_token.data == 10 354 355 356class TestAC62E2ESMRoundTrip: 357 """Test AC6.2: SM round-trip (PE writes, PE reads)""" 358 359 def test_sm_round_trip(self): 360 """PE writes to SM, another PE reads from SM, receives correct data.""" 361 env = simpy.Environment() 362 363 # PE0 IRAM: offset 0 = SM WRITE, offset 1 = SM READ 364 pe0_iram = { 365 0: SMInst(op=MemOp.WRITE, sm_id=0, const=0), # Write to cell 0 366 1: SMInst( 367 op=MemOp.READ, 368 sm_id=0, 369 const=0, # Read from cell 0 370 ret=Addr(a=0, port=Port.L, pe=1), 371 ), 372 } 373 374 # PE1 IRAM: offset 0 = PASS (to pass through SM READ result) 375 pe1_iram = { 376 0: ALUInst( 377 op=RoutingOp.PASS, 378 dest_l=Addr(a=0, port=Port.L, pe=1), 379 dest_r=None, 380 const=None, 381 ), 382 } 383 384 # Build topology 385 sys = build_topology( 386 env, 387 [ 388 PEConfig(pe_id=0, iram=pe0_iram), 389 PEConfig(pe_id=1, iram=pe1_iram), 390 ], 391 [ 392 # SM0: cell 0 starts EMPTY 393 SMConfig(sm_id=0, cell_count=512, initial_cells={}), 394 ], 395 ) 396 397 # Set up collector for PE1's output 398 collector_store = simpy.Store(env, capacity=100) 399 sys.pes[1].route_table[1] = collector_store 400 401 # Inject tokens via SimPy process (FIFO order ensures WRITE before READ) 402 def injector(): 403 yield sys.pes[0].input_store.put(MonadToken(target=0, offset=0, ctx=0, data=42, inline=False)) 404 yield sys.pes[0].input_store.put(MonadToken(target=0, offset=1, ctx=0, data=0, inline=False)) 405 406 env.process(injector()) 407 408 # Run simulation 409 env.run(until=1000) 410 411 # Verify collector receives a token with data=42 (the value written and read back) 412 assert len(collector_store.items) >= 1 413 result_token = collector_store.items[0] 414 assert result_token.data == 42 415 416 417class TestAC63E2EDualFanout: 418 """Test AC6.3: DUAL mode fan-out to two consumers""" 419 420 def test_dual_fanout(self): 421 """DUAL mode emits same result to both PE1 and PE2.""" 422 env = simpy.Environment() 423 424 # PE0 IRAM: offset 0 = PASS with dual destinations 425 pe0_iram = { 426 0: ALUInst( 427 op=RoutingOp.PASS, 428 dest_l=Addr(a=0, port=Port.L, pe=1), 429 dest_r=Addr(a=0, port=Port.L, pe=2), 430 const=None, 431 ), 432 } 433 434 # Build topology 435 sys = build_topology( 436 env, 437 [ 438 PEConfig(pe_id=0, iram=pe0_iram), 439 PEConfig(pe_id=1, iram={}), # PE1: collector 440 PEConfig(pe_id=2, iram={}), # PE2: collector 441 ], 442 [], 443 ) 444 445 # Set up collectors for PE1 and PE2 446 collector_1 = simpy.Store(env, capacity=100) 447 collector_2 = simpy.Store(env, capacity=100) 448 sys.pes[0].route_table[1] = collector_1 449 sys.pes[0].route_table[2] = collector_2 450 451 # Inject token via SimPy process 452 def injector(): 453 yield sys.pes[0].input_store.put(MonadToken(target=0, offset=0, ctx=0, data=99, inline=False)) 454 455 env.process(injector()) 456 457 # Run simulation 458 env.run(until=1000) 459 460 # Verify each collector receives one token with data=99 461 assert len(collector_1.items) == 1 462 assert collector_1.items[0].data == 99 463 464 assert len(collector_2.items) == 1 465 assert collector_2.items[0].data == 99 466 467 468class TestAC64E2ESwitchRouting: 469 """Test AC6.4: SWITCH mode conditional routing""" 470 471 def test_switch_routing_condition_true(self): 472 """SWEQ with equal operands routes data to dest_l, trigger to dest_r.""" 473 env = simpy.Environment() 474 475 # PE0 IRAM: offset 0 = SWEQ 476 pe0_iram = { 477 0: ALUInst( 478 op=RoutingOp.SWEQ, 479 dest_l=Addr(a=0, port=Port.L, pe=1), 480 dest_r=Addr(a=0, port=Port.L, pe=2), 481 const=None, 482 ), 483 } 484 485 # Build topology with gen_counters initialized for dyadic matching 486 sys = build_topology( 487 env, 488 [ 489 PEConfig(pe_id=0, iram=pe0_iram, gen_counters=[0, 0, 0, 0]), 490 PEConfig(pe_id=1, iram={}), # PE1: receives data token 491 PEConfig(pe_id=2, iram={}), # PE2: receives inline trigger 492 ], 493 [], 494 ) 495 496 # Set up collectors 497 collector_1 = simpy.Store(env, capacity=100) 498 collector_2 = simpy.Store(env, capacity=100) 499 sys.pes[0].route_table[1] = collector_1 500 sys.pes[0].route_table[2] = collector_2 501 502 # Inject two DyadTokens with same data (5, 5) via SimPy process 503 def injector(): 504 yield sys.pes[0].input_store.put( 505 DyadToken( 506 target=0, 507 offset=0, 508 ctx=0, 509 data=5, 510 port=Port.L, 511 gen=0, 512 wide=False, 513 ) 514 ) 515 yield sys.pes[0].input_store.put( 516 DyadToken( 517 target=0, 518 offset=0, 519 ctx=0, 520 data=5, 521 port=Port.R, 522 gen=0, 523 wide=False, 524 ) 525 ) 526 527 env.process(injector()) 528 529 # Run simulation 530 env.run(until=1000) 531 532 # When bool_out=True (equal): data → dest_l (collector_1), trigger → dest_r (collector_2) 533 assert len(collector_1.items) == 1 534 data_token = collector_1.items[0] 535 assert data_token.data == 5 536 537 assert len(collector_2.items) == 1 538 trigger_token = collector_2.items[0] 539 assert trigger_token.inline is True 540 assert trigger_token.data == 0 541 542 def test_switch_routing_condition_false(self): 543 """SWEQ with unequal operands routes data to dest_r, trigger to dest_l.""" 544 env = simpy.Environment() 545 546 # PE0 IRAM: offset 0 = SWEQ 547 pe0_iram = { 548 0: ALUInst( 549 op=RoutingOp.SWEQ, 550 dest_l=Addr(a=0, port=Port.L, pe=1), 551 dest_r=Addr(a=0, port=Port.L, pe=2), 552 const=None, 553 ), 554 } 555 556 # Build topology 557 sys = build_topology( 558 env, 559 [ 560 PEConfig(pe_id=0, iram=pe0_iram, gen_counters=[0, 0, 0, 0]), 561 PEConfig(pe_id=1, iram={}), # PE1: receives inline trigger 562 PEConfig(pe_id=2, iram={}), # PE2: receives data token 563 ], 564 [], 565 ) 566 567 # Set up collectors 568 collector_1 = simpy.Store(env, capacity=100) 569 collector_2 = simpy.Store(env, capacity=100) 570 sys.pes[0].route_table[1] = collector_1 571 sys.pes[0].route_table[2] = collector_2 572 573 # Inject two DyadTokens with different data (5, 10) via SimPy process 574 def injector(): 575 yield sys.pes[0].input_store.put( 576 DyadToken( 577 target=0, 578 offset=0, 579 ctx=0, 580 data=5, 581 port=Port.L, 582 gen=0, 583 wide=False, 584 ) 585 ) 586 yield sys.pes[0].input_store.put( 587 DyadToken( 588 target=0, 589 offset=0, 590 ctx=0, 591 data=10, 592 port=Port.R, 593 gen=0, 594 wide=False, 595 ) 596 ) 597 598 env.process(injector()) 599 600 # Run simulation 601 env.run(until=1000) 602 603 # When bool_out=False (not equal): data → dest_r (collector_2), trigger → dest_l (collector_1) 604 assert len(collector_2.items) == 1 605 data_token = collector_2.items[0] 606 assert data_token.data == 5 # First operand goes to dest_r 607 608 assert len(collector_1.items) == 1 609 trigger_token = collector_1.items[0] 610 assert trigger_token.inline is True 611 assert trigger_token.data == 0 612 613 614class TestTask5CfgTokenLoadInst: 615 """Test CfgToken LOAD_INST handling in PE""" 616 617 def test_cfg_token_load_inst(self): 618 """CfgToken with LOAD_INST dynamically loads instructions into PE IRAM.""" 619 env = simpy.Environment() 620 621 # Build topology: PE0 starts with empty IRAM, PE1 is collector 622 sys = build_topology( 623 env, 624 [ 625 PEConfig(pe_id=0, iram={}), # Empty IRAM initially 626 PEConfig(pe_id=1, iram={}), # Collector 627 ], 628 [], 629 ) 630 631 # Set up collector for output 632 collector_store = simpy.Store(env, capacity=100) 633 sys.pes[0].route_table[1] = collector_store 634 635 # Create instruction to load: CONST(42) routing to PE1 636 const_inst = ALUInst( 637 op=RoutingOp.CONST, 638 dest_l=Addr(a=0, port=Port.L, pe=1), 639 dest_r=None, 640 const=42, 641 ) 642 643 # Create LoadInstToken to load instruction at offset 0 644 cfg_token = LoadInstToken( 645 target=0, 646 addr=0, 647 op=CfgOp.LOAD_INST, 648 instructions=(const_inst,), 649 ) 650 651 # Function to send CfgToken then seed token 652 def injector(): 653 # First, send CfgToken to load the instruction 654 yield sys.pes[0].input_store.put(cfg_token) 655 # Then inject a MonadToken seed to trigger the loaded instruction 656 yield sys.pes[0].input_store.put( 657 MonadToken(target=0, offset=0, ctx=0, data=0, inline=False) 658 ) 659 660 env.process(injector()) 661 662 # Run simulation 663 env.run(until=1000) 664 665 # Verify: 666 # 1. The instruction was loaded into IRAM 667 assert 0 in sys.pes[0].iram 668 assert sys.pes[0].iram[0].op == RoutingOp.CONST 669 assert sys.pes[0].iram[0].const == 42 670 671 # 2. The loaded instruction executed and produced output 672 assert len(collector_store.items) == 1 673 result_token = collector_store.items[0] 674 assert result_token.data == 42