From 7a76cb44cf5da4969c0112c0b7b6f291b5f17075 Mon Sep 17 00:00:00 2001 From: pengkun0129 Date: Tue, 9 Jun 2026 17:47:43 +0000 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8Ctests=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_eda_router_contract.py | 1425 +++++++++++++++-------------- 1 file changed, 725 insertions(+), 700 deletions(-) diff --git a/tests/test_eda_router_contract.py b/tests/test_eda_router_contract.py index 08d4e1f..a91b5dd 100644 --- a/tests/test_eda_router_contract.py +++ b/tests/test_eda_router_contract.py @@ -1,650 +1,675 @@ -import os -import sys -import unittest - - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - - -class EdaRouterPinsContractTest(unittest.TestCase): - def test_loader_uses_top_level_pins_and_ignores_legacy_ports(self): - from mxpic_router.eda_loader import parse_cell_dict - - spec = parse_cell_dict({ - "name": "cell", - "pins": [ - {"name": "input_io1", "x": 1, "y": 2, "angle": 90, "width": 0.6}, - ], - "ports": [ - {"name": "legacy_port", "x": 9, "y": 9, "angle": 0}, - ], - }) - - self.assertIn("input_io1", spec.pins) - self.assertNotIn("legacy_port", spec.pins) - self.assertEqual(spec.pins["input_io1"].angle, 90.0) - - def test_loader_accepts_pin_links_with_route_metadata(self): - from mxpic_router.eda_loader import parse_cell_dict - - spec = parse_cell_dict({ - "name": "cell_a", - "instances": { - "inst_a": {"component": "mmi", "x": 0, "y": 0}, - "inst_b": {"component": "mmi", "x": 100, "y": 0}, - }, - "bundles": { - "output_bus": { - "links": [{ - "from": "inst_a:out", - "to": "inst_b:in", - "xsection": "metal_1", - "family": "electrical", - "width": 5, - "radius": 20, - "routing_type": "standard_bend", - "points": [{"x": 0, "y": 0}, {"x": 50, "y": 0}], - }] - } - } - }) - - link = spec.bundles["output_bus"].links[0] - self.assertEqual(link.src_inst, "inst_a") - self.assertEqual(link.src_pin, "out") - self.assertEqual(link.dst_inst, "inst_b") - 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}) - +import os +import sys +import unittest + + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + + +class EdaRouterPinsContractTest(unittest.TestCase): + def test_loader_uses_top_level_pins_and_ignores_legacy_ports(self): + from mxpic_router.eda_loader import parse_cell_dict + + spec = parse_cell_dict({ + "name": "cell", + "pins": [ + {"name": "input_io1", "x": 1, "y": 2, "angle": 90, "width": 0.6}, + ], + "ports": [ + {"name": "legacy_port", "x": 9, "y": 9, "angle": 0}, + ], + }) + + self.assertIn("input_io1", spec.pins) + self.assertNotIn("legacy_port", spec.pins) + self.assertEqual(spec.pins["input_io1"].angle, 90.0) + + def test_loader_accepts_pin_links_with_route_metadata(self): + from mxpic_router.eda_loader import parse_cell_dict + + spec = parse_cell_dict({ + "name": "cell_a", + "instances": { + "inst_a": {"component": "mmi", "x": 0, "y": 0}, + "inst_b": {"component": "mmi", "x": 100, "y": 0}, + }, + "bundles": { + "output_bus": { + "links": [{ + "from": "inst_a:out", + "to": "inst_b:in", + "xsection": "metal_1", + "family": "electrical", + "width": 5, + "radius": 20, + "routing_type": "standard_bend", + "points": [{"x": 0, "y": 0}, {"x": 50, "y": 0}], + }] + } + } + }) + + link = spec.bundles["output_bus"].links[0] + self.assertEqual(link.src_inst, "inst_a") + self.assertEqual(link.src_pin, "out") + self.assertEqual(link.dst_inst, "inst_b") + 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] + "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_builder_uses_frontend_bundle_key_as_explicit_route_group(self): + from mxpic_router.builder import _link_explicit_route_group + from mxpic_router.eda_loader import LinkSpec + + self.assertEqual( + _link_explicit_route_group(LinkSpec(bundle="optical_bus")), + "optical_bus", + ) + self.assertEqual( + _link_explicit_route_group(LinkSpec(bundle="free_wires")), + "", + ) + self.assertEqual( + _link_explicit_route_group(LinkSpec(bundle="free_wires_metal_1")), + "", + ) + self.assertEqual( + _link_explicit_route_group(LinkSpec(bundle="output_bus")), + "", + ) + self.assertEqual( + _link_explicit_route_group(LinkSpec(bundle="output_bus", route_group="stage_1")), + "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 - - class FakePlacedPin: - def __init__(self, name, x, y, a): - self.name = name - self.x = x - self.y = y - self.a = a - - def move(self, dx, dy, da): - return FakePlacedPin(f"{self.name}_in", self.x + dx, self.y + dy, self.a + da) - - class FakePin: - created_names = [] - current_cell = None - - def __init__(self, name, width=None, xs=None): - self.name = name - self.width = width - self.xs = xs - self.created_names.append(name) - - def put(self, x, y, a): - placed = FakePlacedPin(self.name, x, y, a) - if FakePin.current_cell is not None: - FakePin.current_cell.pin[self.name] = placed - return placed - - class FakeCell: - put_calls = [] - - def __init__(self, name=None, instantiate=True): - self.name = name - self.pin = {} - - def __enter__(self): - FakePin.current_cell = self - return self - - def __exit__(self, exc_type, exc, tb): - FakePin.current_cell = None - return False - - def put(self, x, y, a): - self.put_calls.append((self.name, x, y, a)) - placed = FakeCell(name=f"{self.name}_placed") - placed.pin = { - name: FakePlacedPin(name, pin.x + x, pin.y + y, pin.a + a) - for name, pin in self.pin.items() - } - return placed - - class FakeNazca: - Pin = FakePin - Cell = FakeCell - - spec = parse_cell_dict({ - "name": "cell", - "elements": { - "input": { - "type": "port", - "x": 5, - "y": 10, - "angle": 90, - "width": 0.5, - "port_number": 2, - "pitch": 12, - "pins": [ - {"name": "input_io1", "role": "io1"}, - {"name": "input_io2", "role": "io2"}, - ], - }, - }, - }) - pin_map = {} - - _register_element_pins(pin_map, "input", spec.elements["input"], FakeNazca) - - self.assertEqual(FakePin.created_names, ["input_io1", "input_io1_in", "input_io2", "input_io2_in"]) - self.assertEqual(FakeCell.put_calls, [("element_input", 5.0, 10.0, 90.0)]) - self.assertEqual(pin_map[("input", "input_io1")].a, 450.0) - self.assertEqual(pin_map[("input", "input_io2")].a, 450.0) - self.assertEqual((pin_map[("input", "input_io1")].x, pin_map[("input", "input_io1")].y), (5.0, 16.0)) - self.assertEqual((pin_map[("input", "input_io2")].x, pin_map[("input", "input_io2")].y), (5.0, 4.0)) - - def test_anchor_element_defaults_to_named_a_b_pins(self): - from mxpic_router.builder import _register_element_pins - from mxpic_router.eda_loader import parse_cell_dict - - class FakePlacedPin: - def __init__(self, name, x, y, a): - self.name = name - self.x = x - self.y = y - self.a = a - - class FakePin: - current_cell = None - - def __init__(self, name, width=None, xs=None): - self.name = name - - def put(self, x, y, a): - placed = FakePlacedPin(self.name, x, y, a) - if FakePin.current_cell is not None: - FakePin.current_cell.pin[self.name] = placed - return placed - - class FakeCell: - def __init__(self, name=None, instantiate=True): - self.name = name - self.pin = {} - - def __enter__(self): - FakePin.current_cell = self - return self - - def __exit__(self, exc_type, exc, tb): - FakePin.current_cell = None - return False - - def put(self, x, y, a): - placed = FakeCell(name=f"{self.name}_placed") - placed.pin = { - name: FakePlacedPin(name, pin.x + x, pin.y + y, pin.a + a) - for name, pin in self.pin.items() - } - return placed - - class FakeNazca: - Pin = FakePin - Cell = FakeCell - - spec = parse_cell_dict({ - "name": "cell", - "elements": { - "anchor_1": {"type": "anchor", "x": 10, "y": 20, "angle": 0, "port_number": 2, "pitch": 12}, - }, - }) - pin_map = {} - - _register_element_pins(pin_map, "anchor_1", spec.elements["anchor_1"], FakeNazca) - - self.assertIn(("anchor_1", "anchor_1_a1"), pin_map) - self.assertIn(("anchor_1", "anchor_1_b1"), pin_map) - self.assertIn(("anchor_1", "anchor_1_a2"), pin_map) - self.assertIn(("anchor_1", "anchor_1_b2"), pin_map) - self.assertEqual(pin_map[("anchor_1", "anchor_1_a1")].a, 180.0) - self.assertEqual(pin_map[("anchor_1", "anchor_1_b1")].a, 0.0) - - def test_pdk_metadata_ports_are_registered_as_pins_for_allowed_pdk_roots(self): - from mxpic_router.builder import _metadata_pins - - metadata = {"ports": {"a1": {"x": 0, "y": 0, "a": 180}}} - - self.assertEqual( - _metadata_pins(metadata, r"D:\mxpic\opt_pdk_public\foundries"), - metadata["ports"], - ) - self.assertEqual(_metadata_pins(metadata, r"D:\mxpic\some_project_layout"), {}) - - def test_eda_rotation_is_converted_to_layout_rotation_for_metadata_pins(self): - from mxpic_router.builder import _layout_rotation, _transform_port - - self.assertEqual(_layout_rotation(90), -90.0) - x, y, angle = _transform_port( - 39.25, - 0.0, - 360.0, - 1719.3, - -2080.5, - _layout_rotation(90), - ) - - self.assertAlmostEqual(x, 1719.3) - self.assertAlmostEqual(y, -2119.75) - self.assertEqual(angle, 270.0) - - def test_perpendicular_pin_rotations_use_straight_bend_straight_route(self): - from mxpic_router.builder import _route_method_name_for_pins - - class FakePin: - def __init__(self, angle): - self.a = angle - - self.assertEqual( - _route_method_name_for_pins(FakePin(0), FakePin(90)), - "strt_bend_strt_p2p", - ) - self.assertEqual( - _route_method_name_for_pins(FakePin(0), FakePin(270)), - "strt_bend_strt_p2p", - ) - - def test_route_spacing_detection_handles_crossing_and_endpoint_exceptions(self): - from mxpic_router.builder import ( - ROUTE_ENDPOINT_SPACING_IGNORE, - ROUTE_MIN_SPACING, - _first_spacing_adjusted_route, - _orthogonal_elbow, - _polyline_spacing, - _route_spacing_reference_points, - _route_spacing_check_points, - _spacing_offsets, - _spacing_adjusted_route_points, - ) - from mxpic_router.eda_loader import LinkSpec - - class FakePin: - def __init__(self, x, y, angle): - self.x = x - self.y = y - self.a = angle - - self.assertGreaterEqual( - _polyline_spacing( - [{"x": 0, "y": 0}, {"x": 100, "y": 0}], - [{"x": 0, "y": 12}, {"x": 100, "y": 12}], - ), - ROUTE_MIN_SPACING, - ) - self.assertLess( - _polyline_spacing( - [{"x": 0, "y": 0}, {"x": 100, "y": 0}], - [{"x": 50, "y": -20}, {"x": 50, "y": 20}], - ), - ROUTE_MIN_SPACING, - ) - - device_plan = { - "link": LinkSpec(src_inst="mmi1", src_pin="o1", dst_inst="mmi2", dst_pin="i1"), - "points": [{"x": 0, "y": 0}, {"x": 100, "y": 0}], - } - nearby_port_plan = { - "link": LinkSpec(src_inst="port_1", src_pin="io1", dst_inst="mmi2", dst_pin="i1"), - "points": [{"x": 0, "y": 5}, {"x": 100, "y": 5}], - } - device_check_points = _route_spacing_check_points(device_plan) - nearby_port_check_points = _route_spacing_check_points(nearby_port_plan) - - self.assertEqual(device_check_points[0], {"x": ROUTE_ENDPOINT_SPACING_IGNORE, "y": 0.0}) - self.assertEqual(nearby_port_check_points[0], {"x": ROUTE_ENDPOINT_SPACING_IGNORE, "y": 5.0}) - self.assertLess(_polyline_spacing(device_check_points, nearby_port_check_points), ROUTE_MIN_SPACING) - - adjusted = _spacing_adjusted_route_points( - LinkSpec(width=0.5, radius=10), - FakePin(0, 0, 0), - FakePin(100, 0, 180), - offset=ROUTE_MIN_SPACING, - ) - self.assertEqual(adjusted[0], {"x": 0.0, "y": 0.0}) - self.assertEqual(adjusted[-1], {"x": 100.0, "y": 0.0}) - self.assertEqual(adjusted[2]["y"], ROUTE_MIN_SPACING) - self.assertEqual(adjusted[3]["y"], ROUTE_MIN_SPACING) - - current_plan = { - "link": LinkSpec(src_inst="a", src_pin="o1", dst_inst="b", dst_pin="i1"), - "pin1": FakePin(0, 5, 0), - "pin2": FakePin(100, 5, 180), - "points": [{"x": 0, "y": 5}, {"x": 100, "y": 5}], - } - accepted_below = [{ - "check_points": [{"x": 10, "y": 0}, {"x": 90, "y": 0}], - }] - accepted_above = [{ - "check_points": [{"x": 10, "y": 10}, {"x": 90, "y": 10}], - }] - - self.assertEqual(_spacing_offsets(current_plan, accepted_below)[0], ROUTE_MIN_SPACING / 2.0) - self.assertEqual(_spacing_offsets(current_plan, accepted_above)[0], -ROUTE_MIN_SPACING / 2.0) - adjusted_from_close_route = _first_spacing_adjusted_route(current_plan, accepted_below) - self.assertIsNotNone(adjusted_from_close_route) - self.assertEqual(adjusted_from_close_route["points"][2]["y"], 10.0) - - elbow = _orthogonal_elbow({"x": 0, "y": 0}, 0, {"x": 20, "y": 30}, 270) - self.assertEqual(elbow, {"x": 20.0, "y": 0.0}) - reference = _route_spacing_reference_points( - LinkSpec(), - FakePin(0, 0, 0), - FakePin(20, 30, 270), - ) - self.assertEqual(reference, [{"x": 0.0, "y": 0.0}, {"x": 20.0, "y": 0.0}, {"x": 20.0, "y": 30.0}]) - - def test_automatic_p2p_spacing_detection_does_not_change_geometry_by_default(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 strt_p2p(self, **kwargs): - self.calls.append(("strt_p2p", kwargs)) - return self - - def strt_bend_strt_p2p(self, **kwargs): - self.calls.append(("strt_bend_strt_p2p", kwargs)) - return self - - def put(self): - self.calls.append(("put", {})) - - FakeRoute.calls = [] - links = [ - LinkSpec(src_inst="mmi1", src_pin="b1", dst_inst="port_1", dst_pin="io1"), - LinkSpec(src_inst="mmi2", src_pin="b1", dst_inst="port_1", dst_pin="io2"), - ] - pin_map = { - ("mmi1", "b1"): FakePin(0, 0, 0), - ("port_1", "io1"): FakePin(100, 0, 180), - ("mmi2", "b1"): FakePin(0, 5, 0), - ("port_1", "io2"): FakePin(100, 5, 180), - } - - warnings = [] - _route_bundle_links(links, pin_map, FakeRoute, warnings) - - self.assertEqual([call[0] for call in FakeRoute.calls], ["sbend_p2p", "put", "sbend_p2p", "put"]) - self.assertTrue(any("Detected route spacing below 10um" in warning for warning in warnings)) - self.assertEqual(FakeRoute.calls[0][1]["Lstart"], 10.5) - self.assertEqual(FakeRoute.calls[2][1]["Lstart"], 10.5) - call_names = [call[0] for call in FakeRoute.calls] - self.assertNotIn("strt_bend_strt_p2p", call_names) - self.assertNotIn("strt_p2p", call_names) - - def test_sbend_lstart_spacing_uses_whole_bundle_inner_outer_order(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_top", src_pin="b1", dst_inst="port_top", dst_pin="io1", width=0.5, radius=10), - LinkSpec(src_inst="mmi_top", src_pin="b2", dst_inst="port_mid_top", dst_pin="io1", width=0.5, radius=10), - LinkSpec(src_inst="mmi_bot", src_pin="b1", dst_inst="port_mid_bot", dst_pin="io1", width=0.5, radius=10), - LinkSpec(src_inst="mmi_bot", src_pin="b2", dst_inst="port_bot", dst_pin="io1", width=0.5, radius=10), - ] - pin_map = { - ("mmi_top", "b1"): FakePin(0, 12, 0), - ("port_top", "io1"): FakePin(100, 12, 180), - ("mmi_top", "b2"): FakePin(0, 4, 0), - ("port_mid_top", "io1"): FakePin(100, 4, 180), - ("mmi_bot", "b1"): FakePin(0, -4, 0), - ("port_mid_bot", "io1"): FakePin(100, -4, 180), - ("mmi_bot", "b2"): FakePin(0, -12, 0), - ("port_bot", "io1"): FakePin(100, -12, 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.0, 10.5, 10.5, 21.0]) - - def test_sbend_lstart_spacing_uses_monotonic_order_for_same_direction_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="mmi1", src_pin="b1", dst_inst="port1", dst_pin="io1", width=0.5, radius=10), - LinkSpec(src_inst="mmi1", src_pin="b2", dst_inst="port2", dst_pin="io1", width=0.5, radius=10), - LinkSpec(src_inst="mmi2", src_pin="b1", dst_inst="port3", dst_pin="io1", width=0.5, radius=10), - LinkSpec(src_inst="mmi2", src_pin="b2", dst_inst="port4", dst_pin="io1", width=0.5, radius=10), - ] - pin_map = { - ("mmi1", "b1"): FakePin(0, 40, 0), - ("port1", "io1"): FakePin(100, 20, 180), - ("mmi1", "b2"): FakePin(0, 32, 0), - ("port2", "io1"): FakePin(100, 12, 180), - ("mmi2", "b1"): FakePin(0, 24, 0), - ("port3", "io1"): FakePin(100, 4, 180), - ("mmi2", "b2"): FakePin(0, 16, 0), - ("port4", "io1"): FakePin(100, -4, 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, [42.0, 31.5, 21.0, 10.5]) - - def test_sbend_lstart_step_includes_route_width(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="b1", dst_inst="p1", dst_pin="io1", width=40, radius=10), - LinkSpec(src_inst="r2", src_pin="b1", dst_inst="p2", dst_pin="io1", width=40, radius=10), - ] - pin_map = { - ("r1", "b1"): FakePin(0, 0, 0), - ("p1", "io1"): FakePin(100, 0, 180), - ("r2", "b1"): FakePin(0, 100, 0), - ("p2", "io1"): FakePin(100, 100, 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, [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]) - + + class FakePlacedPin: + def __init__(self, name, x, y, a): + self.name = name + self.x = x + self.y = y + self.a = a + + def move(self, dx, dy, da): + return FakePlacedPin(f"{self.name}_in", self.x + dx, self.y + dy, self.a + da) + + class FakePin: + created_names = [] + current_cell = None + + def __init__(self, name, width=None, xs=None): + self.name = name + self.width = width + self.xs = xs + self.created_names.append(name) + + def put(self, x, y, a): + placed = FakePlacedPin(self.name, x, y, a) + if FakePin.current_cell is not None: + FakePin.current_cell.pin[self.name] = placed + return placed + + class FakeCell: + put_calls = [] + + def __init__(self, name=None, instantiate=True): + self.name = name + self.pin = {} + + def __enter__(self): + FakePin.current_cell = self + return self + + def __exit__(self, exc_type, exc, tb): + FakePin.current_cell = None + return False + + def put(self, x, y, a): + self.put_calls.append((self.name, x, y, a)) + placed = FakeCell(name=f"{self.name}_placed") + placed.pin = { + name: FakePlacedPin(name, pin.x + x, pin.y + y, pin.a + a) + for name, pin in self.pin.items() + } + return placed + + class FakeNazca: + Pin = FakePin + Cell = FakeCell + + spec = parse_cell_dict({ + "name": "cell", + "elements": { + "input": { + "type": "port", + "x": 5, + "y": 10, + "angle": 90, + "width": 0.5, + "port_number": 2, + "pitch": 12, + "pins": [ + {"name": "input_io1", "role": "io1"}, + {"name": "input_io2", "role": "io2"}, + ], + }, + }, + }) + pin_map = {} + + _register_element_pins(pin_map, "input", spec.elements["input"], FakeNazca) + + self.assertEqual(FakePin.created_names, ["input_io1", "input_io1_in", "input_io2", "input_io2_in"]) + self.assertEqual(FakeCell.put_calls, [("element_input", 5.0, 10.0, 90.0)]) + self.assertEqual(pin_map[("input", "input_io1")].a, 450.0) + self.assertEqual(pin_map[("input", "input_io2")].a, 450.0) + self.assertEqual((pin_map[("input", "input_io1")].x, pin_map[("input", "input_io1")].y), (5.0, 16.0)) + self.assertEqual((pin_map[("input", "input_io2")].x, pin_map[("input", "input_io2")].y), (5.0, 4.0)) + + def test_anchor_element_defaults_to_named_a_b_pins(self): + from mxpic_router.builder import _register_element_pins + from mxpic_router.eda_loader import parse_cell_dict + + class FakePlacedPin: + def __init__(self, name, x, y, a): + self.name = name + self.x = x + self.y = y + self.a = a + + class FakePin: + current_cell = None + + def __init__(self, name, width=None, xs=None): + self.name = name + + def put(self, x, y, a): + placed = FakePlacedPin(self.name, x, y, a) + if FakePin.current_cell is not None: + FakePin.current_cell.pin[self.name] = placed + return placed + + class FakeCell: + def __init__(self, name=None, instantiate=True): + self.name = name + self.pin = {} + + def __enter__(self): + FakePin.current_cell = self + return self + + def __exit__(self, exc_type, exc, tb): + FakePin.current_cell = None + return False + + def put(self, x, y, a): + placed = FakeCell(name=f"{self.name}_placed") + placed.pin = { + name: FakePlacedPin(name, pin.x + x, pin.y + y, pin.a + a) + for name, pin in self.pin.items() + } + return placed + + class FakeNazca: + Pin = FakePin + Cell = FakeCell + + spec = parse_cell_dict({ + "name": "cell", + "elements": { + "anchor_1": {"type": "anchor", "x": 10, "y": 20, "angle": 0, "port_number": 2, "pitch": 12}, + }, + }) + pin_map = {} + + _register_element_pins(pin_map, "anchor_1", spec.elements["anchor_1"], FakeNazca) + + self.assertIn(("anchor_1", "anchor_1_a1"), pin_map) + self.assertIn(("anchor_1", "anchor_1_b1"), pin_map) + self.assertIn(("anchor_1", "anchor_1_a2"), pin_map) + self.assertIn(("anchor_1", "anchor_1_b2"), pin_map) + self.assertEqual(pin_map[("anchor_1", "anchor_1_a1")].a, 180.0) + self.assertEqual(pin_map[("anchor_1", "anchor_1_b1")].a, 0.0) + + def test_pdk_metadata_ports_are_registered_as_pins_for_allowed_pdk_roots(self): + from mxpic_router.builder import _metadata_pins + + metadata = {"ports": {"a1": {"x": 0, "y": 0, "a": 180}}} + + self.assertEqual( + _metadata_pins(metadata, r"D:\mxpic\opt_pdk_public\foundries"), + metadata["ports"], + ) + self.assertEqual(_metadata_pins(metadata, r"D:\mxpic\some_project_layout"), {}) + + def test_eda_rotation_is_converted_to_layout_rotation_for_metadata_pins(self): + from mxpic_router.builder import _layout_rotation, _transform_port + + self.assertEqual(_layout_rotation(90), -90.0) + x, y, angle = _transform_port( + 39.25, + 0.0, + 360.0, + 1719.3, + -2080.5, + _layout_rotation(90), + ) + + self.assertAlmostEqual(x, 1719.3) + self.assertAlmostEqual(y, -2119.75) + self.assertEqual(angle, 270.0) + + def test_perpendicular_pin_rotations_use_straight_bend_straight_route(self): + from mxpic_router.builder import _route_method_name_for_pins + + class FakePin: + def __init__(self, angle): + self.a = angle + + self.assertEqual( + _route_method_name_for_pins(FakePin(0), FakePin(90)), + "strt_bend_strt_p2p", + ) + self.assertEqual( + _route_method_name_for_pins(FakePin(0), FakePin(270)), + "strt_bend_strt_p2p", + ) + + def test_route_spacing_detection_handles_crossing_and_endpoint_exceptions(self): + from mxpic_router.builder import ( + ROUTE_ENDPOINT_SPACING_IGNORE, + ROUTE_MIN_SPACING, + _first_spacing_adjusted_route, + _orthogonal_elbow, + _polyline_spacing, + _route_spacing_reference_points, + _route_spacing_check_points, + _spacing_offsets, + _spacing_adjusted_route_points, + ) + from mxpic_router.eda_loader import LinkSpec + + class FakePin: + def __init__(self, x, y, angle): + self.x = x + self.y = y + self.a = angle + + self.assertGreaterEqual( + _polyline_spacing( + [{"x": 0, "y": 0}, {"x": 100, "y": 0}], + [{"x": 0, "y": 12}, {"x": 100, "y": 12}], + ), + ROUTE_MIN_SPACING, + ) + self.assertLess( + _polyline_spacing( + [{"x": 0, "y": 0}, {"x": 100, "y": 0}], + [{"x": 50, "y": -20}, {"x": 50, "y": 20}], + ), + ROUTE_MIN_SPACING, + ) + + device_plan = { + "link": LinkSpec(src_inst="mmi1", src_pin="o1", dst_inst="mmi2", dst_pin="i1"), + "points": [{"x": 0, "y": 0}, {"x": 100, "y": 0}], + } + nearby_port_plan = { + "link": LinkSpec(src_inst="port_1", src_pin="io1", dst_inst="mmi2", dst_pin="i1"), + "points": [{"x": 0, "y": 5}, {"x": 100, "y": 5}], + } + device_check_points = _route_spacing_check_points(device_plan) + nearby_port_check_points = _route_spacing_check_points(nearby_port_plan) + + self.assertEqual(device_check_points[0], {"x": ROUTE_ENDPOINT_SPACING_IGNORE, "y": 0.0}) + self.assertEqual(nearby_port_check_points[0], {"x": ROUTE_ENDPOINT_SPACING_IGNORE, "y": 5.0}) + self.assertLess(_polyline_spacing(device_check_points, nearby_port_check_points), ROUTE_MIN_SPACING) + + adjusted = _spacing_adjusted_route_points( + LinkSpec(width=0.5, radius=10), + FakePin(0, 0, 0), + FakePin(100, 0, 180), + offset=ROUTE_MIN_SPACING, + ) + self.assertEqual(adjusted[0], {"x": 0.0, "y": 0.0}) + self.assertEqual(adjusted[-1], {"x": 100.0, "y": 0.0}) + self.assertEqual(adjusted[2]["y"], ROUTE_MIN_SPACING) + self.assertEqual(adjusted[3]["y"], ROUTE_MIN_SPACING) + + current_plan = { + "link": LinkSpec(src_inst="a", src_pin="o1", dst_inst="b", dst_pin="i1"), + "pin1": FakePin(0, 5, 0), + "pin2": FakePin(100, 5, 180), + "points": [{"x": 0, "y": 5}, {"x": 100, "y": 5}], + } + accepted_below = [{ + "check_points": [{"x": 10, "y": 0}, {"x": 90, "y": 0}], + }] + accepted_above = [{ + "check_points": [{"x": 10, "y": 10}, {"x": 90, "y": 10}], + }] + + self.assertEqual(_spacing_offsets(current_plan, accepted_below)[0], ROUTE_MIN_SPACING / 2.0) + self.assertEqual(_spacing_offsets(current_plan, accepted_above)[0], -ROUTE_MIN_SPACING / 2.0) + adjusted_from_close_route = _first_spacing_adjusted_route(current_plan, accepted_below) + self.assertIsNotNone(adjusted_from_close_route) + self.assertEqual(adjusted_from_close_route["points"][2]["y"], 10.0) + + elbow = _orthogonal_elbow({"x": 0, "y": 0}, 0, {"x": 20, "y": 30}, 270) + self.assertEqual(elbow, {"x": 20.0, "y": 0.0}) + reference = _route_spacing_reference_points( + LinkSpec(), + FakePin(0, 0, 0), + FakePin(20, 30, 270), + ) + self.assertEqual(reference, [{"x": 0.0, "y": 0.0}, {"x": 20.0, "y": 0.0}, {"x": 20.0, "y": 30.0}]) + + def test_automatic_p2p_spacing_detection_does_not_change_geometry_by_default(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 strt_p2p(self, **kwargs): + self.calls.append(("strt_p2p", kwargs)) + return self + + def strt_bend_strt_p2p(self, **kwargs): + self.calls.append(("strt_bend_strt_p2p", kwargs)) + return self + + def put(self): + self.calls.append(("put", {})) + + FakeRoute.calls = [] + links = [ + LinkSpec(src_inst="mmi1", src_pin="b1", dst_inst="port_1", dst_pin="io1"), + LinkSpec(src_inst="mmi2", src_pin="b1", dst_inst="port_1", dst_pin="io2"), + ] + pin_map = { + ("mmi1", "b1"): FakePin(0, 0, 0), + ("port_1", "io1"): FakePin(100, 0, 180), + ("mmi2", "b1"): FakePin(0, 5, 0), + ("port_1", "io2"): FakePin(100, 5, 180), + } + + warnings = [] + _route_bundle_links(links, pin_map, FakeRoute, warnings) + + self.assertEqual([call[0] for call in FakeRoute.calls], ["sbend_p2p", "put", "sbend_p2p", "put"]) + self.assertTrue(any("Detected route spacing below 10um" in warning for warning in warnings)) + self.assertEqual(FakeRoute.calls[0][1]["Lstart"], 10.5) + self.assertEqual(FakeRoute.calls[2][1]["Lstart"], 10.5) + call_names = [call[0] for call in FakeRoute.calls] + self.assertNotIn("strt_bend_strt_p2p", call_names) + self.assertNotIn("strt_p2p", call_names) + + def test_sbend_lstart_spacing_uses_whole_bundle_inner_outer_order(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_top", src_pin="b1", dst_inst="port_top", dst_pin="io1", width=0.5, radius=10), + LinkSpec(src_inst="mmi_top", src_pin="b2", dst_inst="port_mid_top", dst_pin="io1", width=0.5, radius=10), + LinkSpec(src_inst="mmi_bot", src_pin="b1", dst_inst="port_mid_bot", dst_pin="io1", width=0.5, radius=10), + LinkSpec(src_inst="mmi_bot", src_pin="b2", dst_inst="port_bot", dst_pin="io1", width=0.5, radius=10), + ] + pin_map = { + ("mmi_top", "b1"): FakePin(0, 12, 0), + ("port_top", "io1"): FakePin(100, 12, 180), + ("mmi_top", "b2"): FakePin(0, 4, 0), + ("port_mid_top", "io1"): FakePin(100, 4, 180), + ("mmi_bot", "b1"): FakePin(0, -4, 0), + ("port_mid_bot", "io1"): FakePin(100, -4, 180), + ("mmi_bot", "b2"): FakePin(0, -12, 0), + ("port_bot", "io1"): FakePin(100, -12, 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.0, 10.5, 10.5, 21.0]) + + def test_sbend_lstart_spacing_uses_monotonic_order_for_same_direction_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="mmi1", src_pin="b1", dst_inst="port1", dst_pin="io1", width=0.5, radius=10), + LinkSpec(src_inst="mmi1", src_pin="b2", dst_inst="port2", dst_pin="io1", width=0.5, radius=10), + LinkSpec(src_inst="mmi2", src_pin="b1", dst_inst="port3", dst_pin="io1", width=0.5, radius=10), + LinkSpec(src_inst="mmi2", src_pin="b2", dst_inst="port4", dst_pin="io1", width=0.5, radius=10), + ] + pin_map = { + ("mmi1", "b1"): FakePin(0, 40, 0), + ("port1", "io1"): FakePin(100, 20, 180), + ("mmi1", "b2"): FakePin(0, 32, 0), + ("port2", "io1"): FakePin(100, 12, 180), + ("mmi2", "b1"): FakePin(0, 24, 0), + ("port3", "io1"): FakePin(100, 4, 180), + ("mmi2", "b2"): FakePin(0, 16, 0), + ("port4", "io1"): FakePin(100, -4, 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, [42.0, 31.5, 21.0, 10.5]) + + def test_sbend_lstart_step_includes_route_width(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="b1", dst_inst="p1", dst_pin="io1", width=40, radius=10), + LinkSpec(src_inst="r2", src_pin="b1", dst_inst="p2", dst_pin="io1", width=40, radius=10), + ] + pin_map = { + ("r1", "b1"): FakePin(0, 0, 0), + ("p1", "io1"): FakePin(100, 0, 180), + ("r2", "b1"): FakePin(0, 100, 0), + ("p2", "io1"): FakePin(100, 100, 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, [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, []) - + + 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]) @@ -652,69 +677,69 @@ class EdaRouterPinsContractTest(unittest.TestCase): def test_route_backend_falls_back_to_nazca_interconnect_when_forge_is_absent(self): import mxpic_router.builder as builder - class FakeInterconnect: - calls = [] - - def __init__(self, radius=None, width=None, xs=None, PCB=False): - self.radius = radius - self.width = width - self.xs = xs - self.PCB = PCB - - def strt_p2p(self, **kwargs): - self.calls.append(("strt_p2p", kwargs)) - return "straight" - - def sbend_p2p(self, **kwargs): - self.calls.append(("sbend_p2p", kwargs)) - return "sbend" - - def ubend_p2p(self, **kwargs): - self.calls.append(("ubend_p2p", kwargs)) - return "ubend" - - def bend_strt_bend_p2p(self, **kwargs): - self.calls.append(("bend_strt_bend_p2p", kwargs)) - return "bend" - - def strt_bend_strt_p2p(self, **kwargs): - self.calls.append(("strt_bend_strt_p2p", kwargs)) - return "straight_bend_straight" - - class FakeInterconnects: - Interconnect = FakeInterconnect - - class FakeNazca: - interconnects = FakeInterconnects - - original_import = builder._import_mxpic_forge_route - original_create = builder._NazcaInterconnectRoute._create_interconnect - try: - builder._import_mxpic_forge_route = lambda: (_ for _ in ()).throw(ImportError("no forge")) - builder._NazcaInterconnectRoute._create_interconnect = staticmethod( - lambda **kwargs: FakeInterconnect(**kwargs) - ) - - route_backend = builder._import_route_backend(FakeNazca) - route = route_backend(radius=20, width=0.7, xs="strip", PCB=False) - - self.assertEqual(route.backend_name, "nazca Interconnect") - self.assertEqual(route.strt_p2p(pin1="a", pin2="b", arrow=False), "straight") - self.assertEqual( - route.strt_bend_strt_p2p(pin1="a", pin2="b", radius=12, arrow=False), - "straight_bend_straight", - ) - self.assertEqual(route.bend_p2p(pin1="a", pin2="b", radius=10, arrow=False), "bend") - self.assertEqual(FakeInterconnect.calls[0][0], "strt_p2p") - self.assertEqual(FakeInterconnect.calls[0][1]["width"], 0.7) - self.assertEqual(FakeInterconnect.calls[1][0], "strt_bend_strt_p2p") - self.assertEqual(FakeInterconnect.calls[1][1]["radius"], 12) - self.assertEqual(FakeInterconnect.calls[2][0], "bend_strt_bend_p2p") - self.assertEqual(FakeInterconnect.calls[2][1]["radius"], 10) - finally: - builder._import_mxpic_forge_route = original_import - builder._NazcaInterconnectRoute._create_interconnect = original_create - - -if __name__ == "__main__": - unittest.main() + class FakeInterconnect: + calls = [] + + def __init__(self, radius=None, width=None, xs=None, PCB=False): + self.radius = radius + self.width = width + self.xs = xs + self.PCB = PCB + + def strt_p2p(self, **kwargs): + self.calls.append(("strt_p2p", kwargs)) + return "straight" + + def sbend_p2p(self, **kwargs): + self.calls.append(("sbend_p2p", kwargs)) + return "sbend" + + def ubend_p2p(self, **kwargs): + self.calls.append(("ubend_p2p", kwargs)) + return "ubend" + + def bend_strt_bend_p2p(self, **kwargs): + self.calls.append(("bend_strt_bend_p2p", kwargs)) + return "bend" + + def strt_bend_strt_p2p(self, **kwargs): + self.calls.append(("strt_bend_strt_p2p", kwargs)) + return "straight_bend_straight" + + class FakeInterconnects: + Interconnect = FakeInterconnect + + class FakeNazca: + interconnects = FakeInterconnects + + original_import = builder._import_mxpic_forge_route + original_create = builder._NazcaInterconnectRoute._create_interconnect + try: + builder._import_mxpic_forge_route = lambda: (_ for _ in ()).throw(ImportError("no forge")) + builder._NazcaInterconnectRoute._create_interconnect = staticmethod( + lambda **kwargs: FakeInterconnect(**kwargs) + ) + + route_backend = builder._import_route_backend(FakeNazca) + route = route_backend(radius=20, width=0.7, xs="strip", PCB=False) + + self.assertEqual(route.backend_name, "nazca Interconnect") + self.assertEqual(route.strt_p2p(pin1="a", pin2="b", arrow=False), "straight") + self.assertEqual( + route.strt_bend_strt_p2p(pin1="a", pin2="b", radius=12, arrow=False), + "straight_bend_straight", + ) + self.assertEqual(route.bend_p2p(pin1="a", pin2="b", radius=10, arrow=False), "bend") + self.assertEqual(FakeInterconnect.calls[0][0], "strt_p2p") + self.assertEqual(FakeInterconnect.calls[0][1]["width"], 0.7) + self.assertEqual(FakeInterconnect.calls[1][0], "strt_bend_strt_p2p") + self.assertEqual(FakeInterconnect.calls[1][1]["radius"], 12) + self.assertEqual(FakeInterconnect.calls[2][0], "bend_strt_bend_p2p") + self.assertEqual(FakeInterconnect.calls[2][1]["radius"], 10) + finally: + builder._import_mxpic_forge_route = original_import + builder._NazcaInterconnectRoute._create_interconnect = original_create + + +if __name__ == "__main__": + unittest.main()