diff --git a/tests/test_eda_router_contract.py b/tests/test_eda_router_contract.py index 425eb16..08d4e1f 100644 --- a/tests/test_eda_router_contract.py +++ b/tests/test_eda_router_contract.py @@ -56,8 +56,29 @@ class EdaRouterPinsContractTest(unittest.TestCase): self.assertEqual(link.dst_pin, "in") self.assertEqual(link.xsection, "metal_1") self.assertEqual(link.width, 5) + self.assertEqual(link.bundle, "output_bus") self.assertEqual(link.points[1], {"x": 50.0, "y": 0.0}) + def test_loader_accepts_explicit_link_route_group_metadata(self): + from mxpic_router.eda_loader import parse_cell_dict + + spec = parse_cell_dict({ + "name": "cell_a", + "bundles": { + "output_bus": { + "links": [{ + "from": "inst_a:out", + "to": "inst_b:in", + "route_group": "stage_1", + }] + } + } + }) + + link = spec.bundles["output_bus"].links[0] + self.assertEqual(link.bundle, "output_bus") + self.assertEqual(link.route_group, "stage_1") + def test_port_element_creates_named_io_pins_and_inside_route_pins(self): from mxpic_router.builder import _register_element_pins from mxpic_router.eda_loader import parse_cell_dict @@ -529,6 +550,105 @@ class EdaRouterPinsContractTest(unittest.TestCase): lstarts = [call[1]["Lstart"] for call in FakeRoute.calls if call[0] == "sbend_p2p"] self.assertEqual(lstarts, [50.0, 50.0]) + def test_sbend_lstart_spacing_handles_same_source_mmi_fanout(self): + from mxpic_router.builder import _route_bundle_links + from mxpic_router.eda_loader import LinkSpec + + class FakePin: + def __init__(self, x, y, angle): + self.x = x + self.y = y + self.a = angle + + class FakeRoute: + calls = [] + + def __init__(self, **kwargs): + pass + + def sbend_p2p(self, **kwargs): + self.calls.append(("sbend_p2p", kwargs)) + return self + + def put(self): + self.calls.append(("put", {})) + + FakeRoute.calls = [] + links = [ + LinkSpec(src_inst="MMI_1", src_pin="b1", dst_inst="port_1", dst_pin="port_1_io2", width=0.7, radius=10), + LinkSpec(src_inst="MMI_1", src_pin="b2", dst_inst="port_1", dst_pin="port_1_io1", width=0.7, radius=10), + ] + pin_map = { + ("MMI_1", "b1"): FakePin(1963.1, -1931.15, 0), + ("port_1", "port_1_io2"): FakePin(2047.2, -2056.3, 180), + ("MMI_1", "b2"): FakePin(1963.1, -1939.85, 0), + ("port_1", "port_1_io1"): FakePin(2047.2, -2066.3, 180), + } + + _route_bundle_links(links, pin_map, FakeRoute, []) + + lstarts = [call[1]["Lstart"] for call in FakeRoute.calls if call[0] == "sbend_p2p"] + self.assertEqual(lstarts, [21.4, 10.7]) + + def test_sbend_lstart_spacing_keeps_separate_route_stages_ordered(self): + from mxpic_router.builder import _route_bundle_links + from mxpic_router.eda_loader import LinkSpec + + class FakePin: + def __init__(self, x, y, angle): + self.x = x + self.y = y + self.a = angle + + class FakeRoute: + calls = [] + + def __init__(self, **kwargs): + pass + + def sbend_p2p(self, **kwargs): + self.calls.append(("sbend_p2p", kwargs)) + return self + + def put(self): + self.calls.append(("put", {})) + + FakeRoute.calls = [] + links = [ + LinkSpec(src_inst="r1", src_pin="en1", dst_inst="r5", dst_pin="ep1", width=40, radius=10), + LinkSpec(src_inst="r2", src_pin="en1", dst_inst="r6", dst_pin="ep1", width=40, radius=10), + LinkSpec(src_inst="r3", src_pin="en1", dst_inst="r7", dst_pin="ep1", width=40, radius=10), + LinkSpec(src_inst="r4", src_pin="en1", dst_inst="r8", dst_pin="ep1", width=40, radius=10), + LinkSpec(src_inst="r6", src_pin="en1", dst_inst="port", dst_pin="io3", width=40, radius=10), + LinkSpec(src_inst="r5", src_pin="en1", dst_inst="port", dst_pin="io4", width=40, radius=10), + LinkSpec(src_inst="r7", src_pin="en1", dst_inst="port", dst_pin="io2", width=40, radius=10), + LinkSpec(src_inst="r8", src_pin="en1", dst_inst="port", dst_pin="io1", width=40, radius=10), + ] + pin_map = { + ("r1", "en1"): FakePin(0, 40, 0), + ("r5", "ep1"): FakePin(100, 70, 180), + ("r2", "en1"): FakePin(0, 30, 0), + ("r6", "ep1"): FakePin(100, 60, 180), + ("r3", "en1"): FakePin(0, 20, 0), + ("r7", "ep1"): FakePin(100, 50, 180), + ("r4", "en1"): FakePin(0, 10, 0), + ("r8", "ep1"): FakePin(100, 40, 180), + ("r6", "en1"): FakePin(100, 60, 0), + ("port", "io3"): FakePin(200, 30, 180), + ("r5", "en1"): FakePin(100, 70, 0), + ("port", "io4"): FakePin(200, 40, 180), + ("r7", "en1"): FakePin(100, 50, 0), + ("port", "io2"): FakePin(200, 20, 180), + ("r8", "en1"): FakePin(100, 40, 0), + ("port", "io1"): FakePin(200, 10, 180), + } + + _route_bundle_links(links, pin_map, FakeRoute, []) + + lstarts = [call[1]["Lstart"] for call in FakeRoute.calls if call[0] == "sbend_p2p"] + self.assertEqual(lstarts[:4], [50.0, 100.0, 150.0, 200.0]) + self.assertEqual(lstarts[4:], [150.0, 200.0, 100.0, 50.0]) + def test_route_backend_falls_back_to_nazca_interconnect_when_forge_is_absent(self): import mxpic_router.builder as builder