Merge pull request 'Removing mxpic_forge from dependency' (#2) from main into pengkun_main
Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
@@ -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
@@ -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:
|
||||
|
||||
Binary file not shown.
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user