Merge pull request 'Removing mxpic_forge from dependency' (#2) from main into pengkun_main #3

Merged
PotatoMaxwell merged 16 commits from pengkun_main into qinyue_main 2026-06-10 11:34:17 +00:00
8 changed files with 164 additions and 18 deletions
Showing only changes of commit 6272f6ac4f - Show all commits
+21 -14
View File
@@ -4,8 +4,8 @@
The EDA repo owns canvas editing, YAML export, login, and API routing. This
repo owns the Nazca-based GDS build flow: it reads saved cell YAML files,
loads the selected technology manifest, loads PDK GDS assets, registers
routable pins, connects bundle links through `mxpic_forge.Route`, and exports
the final GDS.
routable pins, connects bundle links through `mxpic_forge.Route` when available
or Nazca `interconnects.Interconnect` as a fallback, and exports the final GDS.
## High Level Flow
@@ -34,10 +34,12 @@ Canvas Build GDS
-> downloadable <project>.gds
```
If `mxpic_router`, `mxpic_forge`, Nazca, or optional `gdstk` are absent, the
If `mxpic_router`, Nazca, or optional `gdstk` are absent, the
EDA server can still run canvas and login pages. Build actions are where the
router stack is required. For Build Layout, the YAML is still saved and SVG
preview is skipped when the router stack is missing.
router stack is required. `mxpic_forge` improves routing when present, but is
not required because the router falls back to Nazca Interconnect. For Build
Layout, the YAML is still saved and SVG preview is skipped when the required
router stack is missing.
## Runtime Stack
@@ -51,18 +53,21 @@ require_router_stack()
-> import mxpic_router
-> import nazca
-> optionally import gdstk for SVG preview
-> import mxpic_router.builder._import_mxpic_forge_route()
-> import mxpic_router.builder._import_route_backend()
-> first tries mxpic_router.builder._import_mxpic_forge_route()
-> adds sibling ../mxpic_forge to sys.path
-> removes any already-loaded non-forge mxpic modules
-> from mxpic import Route
-> if mxpic_forge is absent:
-> uses nazca.interconnects.Interconnect through a Route-compatible adapter
```
Important package naming:
- `mxpic_router` is this active router package.
- `mxpic_router_legacy` is the old internal legacy package.
- `mxpic` should resolve to `mxpic_forge`, because `mxpic_forge` provides
`Route`.
- `mxpic` should resolve to `mxpic_forge` when that checkout is present,
because `mxpic_forge` provides the preferred `Route` backend.
## Inputs To mxpic_router
@@ -415,7 +420,7 @@ bundles:
For each link:
```text
builder._route_link(link, pin_map, Route, warnings)
builder._route_link(link, pin_map, RouteBackend, warnings)
-> route = Route(
radius=link.radius or 10,
width=link.width,
@@ -455,7 +460,7 @@ otherwise -> bend_p2p
missing method -> fallback to sbend_p2p
```
Then it calls the selected `mxpic_forge.Route` method:
Then it calls the selected route backend method:
```python
route_method(
@@ -492,7 +497,7 @@ mxpic_router/eda_loader.py
mxpic_router/builder.py
-> import Nazca
-> import mxpic_forge Route
-> import mxpic_forge Route or Nazca Interconnect fallback
-> apply technology manifest
-> load all project YAML specs
-> build basic, project, and PDK instances
@@ -510,9 +515,11 @@ mxpic_router_legacy/
definitions. The frontend route editor and Nazca build path both consume it.
- Keep PDK component metadata YAML beside its GDS files. The router needs that
metadata to recreate pins after `nd.load_gds`.
- Avoid reintroducing an internal `mxpic` package inside this repo. The name
`mxpic` must continue to resolve to `mxpic_forge`.
- Avoid reintroducing an internal `mxpic` package inside this repo. When the
forge checkout is present, the name `mxpic` must continue to resolve to
`mxpic_forge`.
- When adding a new routing family or xsection, update `technology.yml` first,
then verify that `mxpic_forge.Route` supports the required route method.
then verify that the selected route backend supports the required route
method.
- When adding a new component source, make sure it eventually contributes pins
to `pin_map`; otherwise bundle links can load but cannot route.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+84 -1
View File
@@ -19,7 +19,7 @@ def build_project_gds(
) -> dict:
import nazca as nd
Route = _import_mxpic_forge_route()
Route = _import_route_backend(nd)
manifest = load_technology_manifest(technology_manifest_path)
apply_technology_manifest(manifest, nd)
@@ -573,6 +573,89 @@ def _safe_float(value, default=0.0):
return default
class _NazcaInterconnectRoute:
"""Small mxpic_forge.Route-compatible adapter around Nazca Interconnect."""
backend_name = "nazca Interconnect"
def __init__(self, radius=None, width=None, xs=None, PCB=False):
self.radius = radius
self.width = width
self.xs = xs
self.PCB = PCB
self._interconnect = self._create_interconnect(radius=radius, width=width, xs=xs, PCB=PCB)
@staticmethod
def _create_interconnect(radius=None, width=None, xs=None, PCB=False):
import nazca as nd
return nd.interconnects.Interconnect(radius=radius, width=width, xs=xs, PCB=PCB)
def strt_p2p(self, pin1=None, pin2=None, width=None, xs=None, arrow=True, **kwargs):
return self._interconnect.strt_p2p(
pin1=pin1,
pin2=pin2,
width=self._route_width(width),
xs=self._route_xs(xs),
arrow=arrow,
)
def sbend_p2p(self, pin1=None, pin2=None, width=None, radius=None, xs=None, arrow=True, **kwargs):
return self._interconnect.sbend_p2p(
pin1=pin1,
pin2=pin2,
width=self._route_width(width),
radius=self._route_radius(radius),
xs=self._route_xs(xs),
arrow=arrow,
)
def ubend_p2p(self, pin1=None, pin2=None, width=None, radius=None, xs=None, arrow=True, **kwargs):
return self._interconnect.ubend_p2p(
pin1=pin1,
pin2=pin2,
width=self._route_width(width),
radius=self._route_radius(radius),
xs=self._route_xs(xs),
arrow=arrow,
)
def bend_p2p(self, pin1=None, pin2=None, width=None, radius=None, xs=None, arrow=True, **kwargs):
route_method = getattr(self._interconnect, "bend_strt_bend_p2p", None)
if route_method is None:
route_method = getattr(self._interconnect, "strt_bend_strt_p2p")
return route_method(
pin1=pin1,
pin2=pin2,
width=self._route_width(width),
radius=self._route_radius(radius),
xs=self._route_xs(xs),
arrow=arrow,
)
def _route_width(self, width):
return self.width if width is None else width
def _route_radius(self, radius):
return self.radius if radius is None else radius
def _route_xs(self, xs):
return self.xs if xs is None else xs
def _import_route_backend(nd=None):
try:
route = _import_mxpic_forge_route()
setattr(route, "backend_name", getattr(route, "backend_name", "mxpic_forge Route"))
return route
except Exception:
if nd is None:
import nazca as nd
if not hasattr(nd, "interconnects") or not hasattr(nd.interconnects, "Interconnect"):
raise
return _NazcaInterconnectRoute
def _import_mxpic_forge_route():
forge_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "mxpic_forge"))
if os.path.isdir(forge_root) and forge_root not in sys.path:
+56
View File
@@ -221,6 +221,62 @@ class EdaRouterPinsContractTest(unittest.TestCase):
)
self.assertEqual(_metadata_pins(metadata, r"D:\mxpic\some_project_layout"), {})
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"
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.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], "bend_strt_bend_p2p")
self.assertEqual(FakeInterconnect.calls[1][1]["radius"], 10)
finally:
builder._import_mxpic_forge_route = original_import
builder._NazcaInterconnectRoute._create_interconnect = original_create
if __name__ == "__main__":
unittest.main()