"""Tests for the Placement validation pass. Tests verify: - or1-asm.AC5.1: Valid placements are accepted - or1-asm.AC5.2: Placement on nonexistent PE produces error - or1-asm.AC5.3: Unplaced node produces error """ from asm.place import place from asm.ir import IRGraph, IRNode, SystemConfig, SourceLoc from asm.errors import ErrorCategory from cm_inst import ArithOp class TestValidPlacement: """AC5.1: All nodes with explicit PE placements are accepted when PE exists.""" def test_single_node_pe0(self): """Single node on PE0.""" node = IRNode( name="&add", opcode=ArithOp.ADD, pe=0, loc=SourceLoc(1, 1), ) graph = IRGraph({"&add": node}) result = place(graph) assert len(result.errors) == 0 assert result.system is not None assert result.system.pe_count >= 1 def test_multiple_nodes_different_pes(self): """Multiple nodes on different PEs.""" nodes = { "&add": IRNode(name="&add", opcode=ArithOp.ADD, pe=0, loc=SourceLoc(1, 1)), "&sub": IRNode(name="&sub", opcode=ArithOp.SUB, pe=1, loc=SourceLoc(2, 1)), "&inc": IRNode(name="&inc", opcode=ArithOp.INC, pe=2, loc=SourceLoc(3, 1)), "&dec": IRNode(name="&dec", opcode=ArithOp.DEC, pe=3, loc=SourceLoc(4, 1)), } system = SystemConfig(pe_count=4, sm_count=1, iram_capacity=64, frame_count=4) graph = IRGraph(nodes, system=system) result = place(graph) assert len(result.errors) == 0 assert result.system.pe_count == 4 def test_system_config_inferred(self): """System config is inferred from max PE ID.""" node = IRNode( name="&add", opcode=ArithOp.ADD, pe=3, loc=SourceLoc(1, 1), ) graph = IRGraph({"&add": node}, system=None) result = place(graph) assert len(result.errors) == 0 assert result.system is not None assert result.system.pe_count >= 4 # At least 4 PEs (0-3) class TestNonexistentPE: """AC5.2: Node placed on nonexistent PE produces error.""" def test_node_on_pe9_with_4_pes(self): """Node on PE9 when system only has 4 PEs.""" node = IRNode( name="&add", opcode=ArithOp.ADD, pe=9, loc=SourceLoc(5, 10), ) system = SystemConfig(pe_count=4, sm_count=1, iram_capacity=64, frame_count=4) graph = IRGraph({"&add": node}, system=system) result = place(graph) assert len(result.errors) == 1 error = result.errors[0] assert error.category == ErrorCategory.PLACEMENT assert "PE9" in error.message assert "4 PEs" in error.message assert "0-3" in error.message def test_node_on_pe1_with_1_pe(self): """Node on PE1 when system only has 1 PE.""" node = IRNode( name="&add", opcode=ArithOp.ADD, pe=1, loc=SourceLoc(1, 1), ) system = SystemConfig(pe_count=1, sm_count=1, iram_capacity=64, frame_count=4) graph = IRGraph({"&add": node}, system=system) result = place(graph) assert len(result.errors) == 1 error = result.errors[0] assert error.category == ErrorCategory.PLACEMENT assert "PE1" in error.message assert "0-0" in error.message def test_multiple_nodes_one_invalid_pe(self): """Multiple nodes, one on invalid PE.""" nodes = { "&add": IRNode(name="&add", opcode=ArithOp.ADD, pe=0, loc=SourceLoc(1, 1)), "&sub": IRNode(name="&sub", opcode=ArithOp.SUB, pe=5, loc=SourceLoc(2, 1)), "&inc": IRNode(name="&inc", opcode=ArithOp.INC, pe=1, loc=SourceLoc(3, 1)), } system = SystemConfig(pe_count=4, sm_count=1, iram_capacity=64, frame_count=4) graph = IRGraph(nodes, system=system) result = place(graph) assert len(result.errors) == 1 error = result.errors[0] assert error.category == ErrorCategory.PLACEMENT assert "&sub" in error.message assert "PE5" in error.message class TestUnplacedNode: """AC10.1: Unplaced nodes are auto-placed without explicit annotations (Phase 7).""" def test_single_unplaced_node(self): """Single unplaced node gets auto-placed.""" node = IRNode( name="&add", opcode=ArithOp.ADD, pe=None, loc=SourceLoc(5, 3), ) graph = IRGraph({"&add": node}) result = place(graph) # Phase 7: auto-placement should succeed assert len(result.errors) == 0 assert result.nodes["&add"].pe is not None def test_multiple_nodes_some_unplaced(self): """Unplaced nodes get auto-placed (Phase 7).""" nodes = { "&add": IRNode(name="&add", opcode=ArithOp.ADD, pe=0, loc=SourceLoc(1, 1)), "&sub": IRNode(name="&sub", opcode=ArithOp.SUB, pe=None, loc=SourceLoc(2, 1)), "&inc": IRNode(name="&inc", opcode=ArithOp.INC, pe=None, loc=SourceLoc(3, 1)), } system = SystemConfig(pe_count=2, sm_count=1, iram_capacity=64, frame_count=4) graph = IRGraph(nodes, system=system) result = place(graph) # Phase 7: auto-placement should succeed assert len(result.errors) == 0 assert result.nodes["&sub"].pe is not None assert result.nodes["&inc"].pe is not None def test_all_nodes_unplaced(self): """All unplaced nodes get auto-placed (Phase 7).""" nodes = { "&add": IRNode(name="&add", opcode=ArithOp.ADD, pe=None, loc=SourceLoc(1, 1)), "&sub": IRNode(name="&sub", opcode=ArithOp.SUB, pe=None, loc=SourceLoc(2, 1)), } graph = IRGraph(nodes) result = place(graph) # Phase 7: auto-placement should succeed assert len(result.errors) == 0 for node in result.nodes.values(): assert node.pe is not None class TestMixedErrors: """Combined placement errors.""" def test_unplaced_and_invalid_pe(self): """Invalid PE placement produces error; unplaced nodes are auto-placed.""" nodes = { "&add": IRNode(name="&add", opcode=ArithOp.ADD, pe=0, loc=SourceLoc(1, 1)), "&sub": IRNode(name="&sub", opcode=ArithOp.SUB, pe=None, loc=SourceLoc(2, 1)), "&inc": IRNode(name="&inc", opcode=ArithOp.INC, pe=9, loc=SourceLoc(3, 1)), } system = SystemConfig(pe_count=4, sm_count=1, iram_capacity=64, frame_count=4) graph = IRGraph(nodes, system=system) result = place(graph) # Should have 1 error for invalid PE placement on &inc; &sub is auto-placed assert len(result.errors) == 1 error = result.errors[0] assert error.category == ErrorCategory.PLACEMENT assert "&inc" in error.message assert "PE9" in error.message # &sub should be auto-placed assert result.nodes["&sub"].pe is not None