OR-1 dataflow CPU sketch
at pe-frame-redesign 564 lines 18 kB view raw
1"""Tests for code generation (frame-based model). 2 3Tests verify: 4- pe-frame-redesign.AC6.1: AssemblyResult includes setup_tokens field 5- pe-frame-redesign.AC6.2: Token stream ordering: SM init → IRAM writes → ALLOC → frame slot writes → seeds 6- pe-frame-redesign.AC6.3: IRAM write data uses pack_instruction() 7- pe-frame-redesign.AC6.6: Seed tokens use act_id, no gen field 8 9Also tests original codegen acceptance criteria: 10- Direct mode produces valid PEConfig with correct IRAM contents 11- Direct mode produces valid SMConfig with initial cell values 12- Direct mode produces seed tokens for const nodes with no incoming edges 13- Direct mode PEConfig includes route restrictions matching edge analysis 14- Program with no data_defs produces empty SM init section 15""" 16 17import pytest 18 19from asm.codegen import generate_direct, generate_tokens, AssemblyResult 20from asm.ir import ( 21 IRGraph, 22 IRNode, 23 IREdge, 24 IRDataDef, 25 SystemConfig, 26 SourceLoc, 27 ResolvedDest, 28) 29from cm_inst import ( 30 Instruction, OutputStyle, TokenKind, FrameOp, FrameDest, 31 ArithOp, MemOp, Port, RoutingOp 32) 33from tokens import MonadToken, SMToken, PELocalWriteToken, FrameControlToken 34from emu.types import PEConfig, SMConfig 35from sm_mod import Presence 36 37 38class TestFrameDirectMode: 39 """Frame-based direct mode code generation.""" 40 41 def test_assembly_result_structure(self): 42 """AssemblyResult has all required fields including setup_tokens.""" 43 node = IRNode( 44 name="&add", 45 opcode=ArithOp.ADD, 46 pe=0, 47 iram_offset=0, 48 act_id=0, 49 mode=(OutputStyle.INHERIT, False, 2), 50 wide=False, 51 fref=0, 52 loc=SourceLoc(1, 1), 53 ) 54 system = SystemConfig(pe_count=1, sm_count=1) 55 graph = IRGraph({"&add": node}, system=system) 56 57 result = generate_direct(graph) 58 59 # Verify AssemblyResult structure 60 assert isinstance(result, AssemblyResult) 61 assert hasattr(result, 'pe_configs') 62 assert hasattr(result, 'sm_configs') 63 assert hasattr(result, 'seed_tokens') 64 assert hasattr(result, 'setup_tokens') 65 assert isinstance(result.setup_tokens, list) 66 67 def test_direct_mode_peconfig_structure(self): 68 """PEConfig has Instruction IRAM (not ALUInst/SMInst) and frame config fields.""" 69 node = IRNode( 70 name="&add", 71 opcode=ArithOp.ADD, 72 pe=0, 73 iram_offset=0, 74 act_id=0, 75 mode=(OutputStyle.INHERIT, False, 2), 76 wide=False, 77 fref=7, 78 loc=SourceLoc(1, 1), 79 ) 80 system = SystemConfig(pe_count=1, sm_count=1) 81 graph = IRGraph({"&add": node}, system=system) 82 83 result = generate_direct(graph) 84 85 assert len(result.pe_configs) == 1 86 pe_config = result.pe_configs[0] 87 assert pe_config.pe_id == 0 88 assert 0 in pe_config.iram 89 90 inst = pe_config.iram[0] 91 assert isinstance(inst, Instruction) 92 assert inst.opcode == ArithOp.ADD 93 assert inst.output == OutputStyle.INHERIT 94 assert inst.fref == 7 95 96 # Verify frame config fields 97 assert hasattr(pe_config, 'frame_count') 98 assert hasattr(pe_config, 'frame_slots') 99 assert hasattr(pe_config, 'matchable_offsets') 100 assert pe_config.frame_count > 0 101 assert pe_config.frame_slots > 0 102 assert pe_config.matchable_offsets > 0 103 104 def test_direct_mode_with_data_defs(self): 105 """Direct mode produces SMConfig with initial cell values.""" 106 data_def = IRDataDef( 107 name="@val", 108 sm_id=0, 109 cell_addr=5, 110 value=42, 111 loc=SourceLoc(1, 1), 112 ) 113 node = IRNode( 114 name="&a", 115 opcode=ArithOp.ADD, 116 pe=0, 117 iram_offset=0, 118 act_id=0, 119 mode=(OutputStyle.INHERIT, False, 2), 120 wide=False, 121 fref=0, 122 loc=SourceLoc(1, 1), 123 ) 124 system = SystemConfig(pe_count=1, sm_count=1) 125 graph = IRGraph( 126 {"&a": node}, 127 data_defs=[data_def], 128 system=system, 129 ) 130 131 result = generate_direct(graph) 132 133 # Verify SMConfig 134 assert len(result.sm_configs) == 1 135 sm_config = result.sm_configs[0] 136 assert sm_config.sm_id == 0 137 assert 5 in sm_config.initial_cells 138 pres, val = sm_config.initial_cells[5] 139 assert pres == Presence.FULL 140 assert val == 42 141 142 def test_seed_tokens_use_act_id(self): 143 """Seed tokens use act_id field, not ctx.""" 144 seed_node = IRNode( 145 name="&seed", 146 opcode=RoutingOp.CONST, 147 pe=0, 148 iram_offset=1, 149 act_id=3, 150 const=99, 151 mode=(OutputStyle.INHERIT, True, 1), 152 wide=False, 153 fref=0, 154 seed=True, 155 loc=SourceLoc(2, 1), 156 ) 157 # Monadic destination (not dyadic) 158 dest_node = IRNode( 159 name="&dest", 160 opcode=ArithOp.INC, # INC is monadic 161 pe=0, 162 iram_offset=2, 163 act_id=3, 164 mode=(OutputStyle.INHERIT, False, 1), 165 wide=False, 166 fref=0, 167 loc=SourceLoc(3, 1), 168 ) 169 edge = IREdge(source="&seed", dest="&dest", port=Port.L) 170 system = SystemConfig(pe_count=1, sm_count=1) 171 graph = IRGraph( 172 {"&seed": seed_node, "&dest": dest_node}, 173 edges=[edge], 174 system=system, 175 ) 176 177 result = generate_direct(graph) 178 179 # Verify seed tokens 180 assert len(result.seed_tokens) == 1 181 seed = result.seed_tokens[0] 182 assert isinstance(seed, MonadToken) 183 assert seed.data == 99 184 assert seed.act_id == 3 185 assert not hasattr(seed, 'gen') # No gen field 186 187 def test_const_node_with_no_incoming_edge_is_seed(self): 188 """CONST node with no incoming edges produces seed token.""" 189 const_node = IRNode( 190 name="&const", 191 opcode=RoutingOp.CONST, 192 pe=0, 193 iram_offset=2, 194 act_id=0, 195 const=99, 196 mode=(OutputStyle.INHERIT, True, 1), 197 wide=False, 198 fref=0, 199 loc=SourceLoc(1, 1), 200 ) 201 graph = IRGraph({"&const": const_node}, system=SystemConfig(1, 1)) 202 203 result = generate_direct(graph) 204 205 assert len(result.seed_tokens) == 1 206 token = result.seed_tokens[0] 207 assert isinstance(token, MonadToken) 208 assert token.target == 0 209 assert token.offset == 2 210 assert token.act_id == 0 211 assert token.data == 99 212 213 def test_route_restrictions(self): 214 """Cross-PE edges produce correct allowed_pe_routes.""" 215 node_pe0 = IRNode( 216 name="&a", 217 opcode=ArithOp.ADD, 218 pe=0, 219 iram_offset=0, 220 act_id=0, 221 mode=(OutputStyle.INHERIT, False, 2), 222 wide=False, 223 fref=0, 224 dest_l=ResolvedDest(name="&b", frame_dest=FrameDest( 225 target_pe=1, offset=0, act_id=0, port=Port.L, token_kind=TokenKind.DYADIC 226 )), 227 loc=SourceLoc(1, 1), 228 ) 229 node_pe1 = IRNode( 230 name="&b", 231 opcode=ArithOp.SUB, 232 pe=1, 233 iram_offset=0, 234 act_id=0, 235 mode=(OutputStyle.INHERIT, False, 2), 236 wide=False, 237 fref=0, 238 loc=SourceLoc(2, 1), 239 ) 240 edge = IREdge(source="&a", dest="&b", port=Port.L, loc=SourceLoc(1, 1)) 241 system = SystemConfig(pe_count=2, sm_count=1) 242 graph = IRGraph( 243 {"&a": node_pe0, "&b": node_pe1}, 244 edges=[edge], 245 system=system, 246 ) 247 248 result = generate_direct(graph) 249 250 assert len(result.pe_configs) == 2 251 pe0_config = next(c for c in result.pe_configs if c.pe_id == 0) 252 pe1_config = next(c for c in result.pe_configs if c.pe_id == 1) 253 254 # PE0 should have routes to {0, 1} 255 assert 0 in pe0_config.allowed_pe_routes 256 assert 1 in pe0_config.allowed_pe_routes 257 258 # PE1 should have route to {1} (self only) 259 assert 1 in pe1_config.allowed_pe_routes 260 261 def test_no_data_defs_produces_empty_smconfig(self): 262 """Program with no data_defs produces SMConfig with no initial cells.""" 263 node = IRNode( 264 name="&add", 265 opcode=ArithOp.ADD, 266 pe=0, 267 iram_offset=0, 268 act_id=0, 269 mode=(OutputStyle.INHERIT, False, 2), 270 wide=False, 271 fref=0, 272 loc=SourceLoc(1, 1), 273 ) 274 system = SystemConfig(pe_count=1, sm_count=1) 275 graph = IRGraph({"&add": node}, system=system) 276 277 result = generate_direct(graph) 278 279 assert len(result.sm_configs) == 1 280 sm_config = result.sm_configs[0] 281 assert sm_config.sm_id == 0 282 assert sm_config.initial_cells is None 283 284 def test_memop_instructions(self): 285 """MemOp instructions produce Instruction objects.""" 286 node = IRNode( 287 name="&read", 288 opcode=MemOp.READ, 289 pe=0, 290 iram_offset=0, 291 act_id=0, 292 sm_id=0, 293 mode=(OutputStyle.CHANGE_TAG, False, 1), 294 wide=True, 295 fref=3, 296 loc=SourceLoc(1, 1), 297 ) 298 system = SystemConfig(pe_count=1, sm_count=1) 299 graph = IRGraph({"&read": node}, system=system) 300 301 result = generate_direct(graph) 302 303 assert len(result.pe_configs) == 1 304 pe_config = result.pe_configs[0] 305 assert 0 in pe_config.iram 306 307 inst = pe_config.iram[0] 308 assert isinstance(inst, Instruction) 309 assert inst.opcode == MemOp.READ 310 assert inst.output == OutputStyle.CHANGE_TAG 311 assert inst.wide == True 312 assert inst.fref == 3 313 314 315class TestTokenStream: 316 """Token stream generation and ordering.""" 317 318 def test_setup_tokens_include_sm_init(self): 319 """Setup tokens include SM init (SMToken) entries.""" 320 data_def = IRDataDef( 321 name="@val", 322 sm_id=0, 323 cell_addr=5, 324 value=42, 325 loc=SourceLoc(1, 1), 326 ) 327 node = IRNode( 328 name="&add", 329 opcode=ArithOp.ADD, 330 pe=0, 331 iram_offset=0, 332 act_id=0, 333 mode=(OutputStyle.INHERIT, False, 2), 334 wide=False, 335 fref=0, 336 loc=SourceLoc(1, 1), 337 ) 338 system = SystemConfig(pe_count=1, sm_count=1) 339 graph = IRGraph( 340 {"&add": node}, 341 data_defs=[data_def], 342 system=system, 343 ) 344 345 result = generate_direct(graph) 346 sm_tokens = [t for t in result.setup_tokens if isinstance(t, SMToken)] 347 348 assert len(sm_tokens) > 0 349 token = sm_tokens[0] 350 assert token.target == 0 351 assert token.addr == 5 352 assert token.op == MemOp.WRITE 353 assert token.data == 42 354 355 def test_setup_tokens_include_iram_writes(self): 356 """Setup tokens include IRAM writes (PELocalWriteToken with region=0).""" 357 node = IRNode( 358 name="&add", 359 opcode=ArithOp.ADD, 360 pe=0, 361 iram_offset=0, 362 act_id=0, 363 mode=(OutputStyle.INHERIT, False, 2), 364 wide=False, 365 fref=0, 366 loc=SourceLoc(1, 1), 367 ) 368 system = SystemConfig(pe_count=1, sm_count=1) 369 graph = IRGraph({"&add": node}, system=system) 370 371 result = generate_direct(graph) 372 iram_tokens = [t for t in result.setup_tokens 373 if isinstance(t, PELocalWriteToken) and t.region == 0] 374 375 assert len(iram_tokens) > 0 376 token = iram_tokens[0] 377 assert token.target == 0 378 assert token.region == 0 379 380 def test_setup_tokens_include_alloc(self): 381 """Setup tokens include ALLOC (FrameControlToken) entries.""" 382 node = IRNode( 383 name="&add", 384 opcode=ArithOp.ADD, 385 pe=0, 386 iram_offset=0, 387 act_id=2, 388 mode=(OutputStyle.INHERIT, False, 2), 389 wide=False, 390 fref=0, 391 loc=SourceLoc(1, 1), 392 ) 393 system = SystemConfig(pe_count=1, sm_count=1) 394 graph = IRGraph({"&add": node}, system=system) 395 396 result = generate_direct(graph) 397 alloc_tokens = [t for t in result.setup_tokens if isinstance(t, FrameControlToken)] 398 399 assert len(alloc_tokens) > 0 400 token = alloc_tokens[0] 401 assert token.op == FrameOp.ALLOC 402 assert token.act_id == 2 403 404 def test_generate_tokens_ordering(self): 405 """generate_tokens() returns tokens in proper order.""" 406 data_def = IRDataDef( 407 name="@val", 408 sm_id=0, 409 cell_addr=5, 410 value=42, 411 loc=SourceLoc(1, 1), 412 ) 413 node = IRNode( 414 name="&add", 415 opcode=ArithOp.ADD, 416 pe=0, 417 iram_offset=0, 418 act_id=0, 419 mode=(OutputStyle.INHERIT, False, 2), 420 wide=False, 421 fref=0, 422 loc=SourceLoc(1, 1), 423 ) 424 seed_node = IRNode( 425 name="&const", 426 opcode=RoutingOp.CONST, 427 pe=0, 428 iram_offset=10, 429 act_id=0, 430 const=99, 431 mode=(OutputStyle.INHERIT, True, 1), 432 wide=False, 433 fref=0, 434 seed=True, 435 loc=SourceLoc(2, 1), 436 ) 437 edge = IREdge(source="&const", dest="&add", port=Port.L) 438 system = SystemConfig(pe_count=1, sm_count=1) 439 graph = IRGraph( 440 {"&add": node, "&const": seed_node}, 441 edges=[edge], 442 data_defs=[data_def], 443 system=system, 444 ) 445 446 tokens = generate_tokens(graph) 447 448 # Verify that setup tokens come before seed tokens 449 sm_indices = [i for i, t in enumerate(tokens) if isinstance(t, SMToken)] 450 iram_indices = [i for i, t in enumerate(tokens) 451 if isinstance(t, PELocalWriteToken) and t.region == 0] 452 seed_indices = [i for i, t in enumerate(tokens) if isinstance(t, (MonadToken))] 453 454 if sm_indices and iram_indices: 455 assert max(sm_indices) < min(iram_indices) 456 if iram_indices and seed_indices: 457 assert max(iram_indices) < min(seed_indices) 458 459 460class TestEdgeCases: 461 """Edge case tests.""" 462 463 def test_multiple_data_defs_same_sm(self): 464 """Multiple data_defs targeting same SM are merged.""" 465 data_def1 = IRDataDef( 466 name="@val1", 467 sm_id=0, 468 cell_addr=5, 469 value=42, 470 loc=SourceLoc(1, 1), 471 ) 472 data_def2 = IRDataDef( 473 name="@val2", 474 sm_id=0, 475 cell_addr=10, 476 value=99, 477 loc=SourceLoc(2, 1), 478 ) 479 system = SystemConfig(pe_count=1, sm_count=1) 480 graph = IRGraph({}, data_defs=[data_def1, data_def2], system=system) 481 482 result = generate_direct(graph) 483 484 assert len(result.sm_configs) == 1 485 sm_config = result.sm_configs[0] 486 assert len(sm_config.initial_cells) == 2 487 assert sm_config.initial_cells[5] == (Presence.FULL, 42) 488 assert sm_config.initial_cells[10] == (Presence.FULL, 99) 489 490 def test_const_node_with_incoming_edge_not_seed(self): 491 """CONST node with incoming edge is not a seed token.""" 492 source_node = IRNode( 493 name="&src", 494 opcode=ArithOp.ADD, 495 pe=0, 496 iram_offset=0, 497 act_id=0, 498 mode=(OutputStyle.INHERIT, False, 2), 499 wide=False, 500 fref=0, 501 loc=SourceLoc(1, 1), 502 ) 503 const_node = IRNode( 504 name="&const", 505 opcode=RoutingOp.CONST, 506 pe=0, 507 iram_offset=1, 508 act_id=0, 509 const=5, 510 mode=(OutputStyle.INHERIT, True, 1), 511 wide=False, 512 fref=0, 513 loc=SourceLoc(2, 1), 514 ) 515 edge = IREdge(source="&src", dest="&const", port=Port.L, loc=SourceLoc(1, 1)) 516 system = SystemConfig(pe_count=1, sm_count=1) 517 graph = IRGraph( 518 {"&src": source_node, "&const": const_node}, 519 edges=[edge], 520 system=system, 521 ) 522 523 result = generate_direct(graph) 524 525 # The CONST node has an incoming edge, so it should NOT be a seed 526 assert len(result.seed_tokens) == 0 527 528 def test_multiactivation_alloc_tokens(self): 529 """Multiple activations generate multiple ALLOC tokens.""" 530 node1 = IRNode( 531 name="&a", 532 opcode=ArithOp.ADD, 533 pe=0, 534 iram_offset=0, 535 act_id=1, 536 mode=(OutputStyle.INHERIT, False, 2), 537 wide=False, 538 fref=0, 539 loc=SourceLoc(1, 1), 540 ) 541 node2 = IRNode( 542 name="&b", 543 opcode=ArithOp.SUB, 544 pe=0, 545 iram_offset=1, 546 act_id=2, 547 mode=(OutputStyle.INHERIT, False, 2), 548 wide=False, 549 fref=0, 550 loc=SourceLoc(2, 1), 551 ) 552 system = SystemConfig(pe_count=1, sm_count=1) 553 graph = IRGraph( 554 {"&a": node1, "&b": node2}, 555 system=system, 556 ) 557 558 result = generate_direct(graph) 559 alloc_tokens = [t for t in result.setup_tokens if isinstance(t, FrameControlToken)] 560 561 # Should have 2 ALLOC tokens (one for act_id=1, one for act_id=2) 562 assert len(alloc_tokens) == 2 563 assert all(t.op == FrameOp.ALLOC for t in alloc_tokens) 564 assert {t.act_id for t in alloc_tokens} == {1, 2}