Removing mxpic_forge from dependency #2
@@ -4,8 +4,8 @@
|
|||||||
The EDA repo owns canvas editing, YAML export, login, and API routing. This
|
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,
|
repo owns the Nazca-based GDS build flow: it reads saved cell YAML files,
|
||||||
loads the selected technology manifest, loads PDK GDS assets, registers
|
loads the selected technology manifest, loads PDK GDS assets, registers
|
||||||
routable pins, connects bundle links through `mxpic_forge.Route`, and exports
|
routable pins, connects bundle links through `mxpic_forge.Route` when available
|
||||||
the final GDS.
|
or Nazca `interconnects.Interconnect` as a fallback, and exports the final GDS.
|
||||||
|
|
||||||
## High Level Flow
|
## High Level Flow
|
||||||
|
|
||||||
@@ -34,10 +34,12 @@ Canvas Build GDS
|
|||||||
-> downloadable <project>.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
|
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
|
router stack is required. `mxpic_forge` improves routing when present, but is
|
||||||
preview is skipped when the router stack is missing.
|
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
|
## Runtime Stack
|
||||||
|
|
||||||
@@ -51,18 +53,21 @@ require_router_stack()
|
|||||||
-> import mxpic_router
|
-> import mxpic_router
|
||||||
-> import nazca
|
-> import nazca
|
||||||
-> optionally import gdstk for SVG preview
|
-> 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
|
-> adds sibling ../mxpic_forge to sys.path
|
||||||
-> removes any already-loaded non-forge mxpic modules
|
-> removes any already-loaded non-forge mxpic modules
|
||||||
-> from mxpic import Route
|
-> from mxpic import Route
|
||||||
|
-> if mxpic_forge is absent:
|
||||||
|
-> uses nazca.interconnects.Interconnect through a Route-compatible adapter
|
||||||
```
|
```
|
||||||
|
|
||||||
Important package naming:
|
Important package naming:
|
||||||
|
|
||||||
- `mxpic_router` is this active router package.
|
- `mxpic_router` is this active router package.
|
||||||
- `mxpic_router_legacy` is the old internal legacy package.
|
- `mxpic_router_legacy` is the old internal legacy package.
|
||||||
- `mxpic` should resolve to `mxpic_forge`, because `mxpic_forge` provides
|
- `mxpic` should resolve to `mxpic_forge` when that checkout is present,
|
||||||
`Route`.
|
because `mxpic_forge` provides the preferred `Route` backend.
|
||||||
|
|
||||||
## Inputs To mxpic_router
|
## Inputs To mxpic_router
|
||||||
|
|
||||||
@@ -415,7 +420,7 @@ bundles:
|
|||||||
For each link:
|
For each link:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
builder._route_link(link, pin_map, Route, warnings)
|
builder._route_link(link, pin_map, RouteBackend, warnings)
|
||||||
-> route = Route(
|
-> route = Route(
|
||||||
radius=link.radius or 10,
|
radius=link.radius or 10,
|
||||||
width=link.width,
|
width=link.width,
|
||||||
@@ -455,7 +460,7 @@ otherwise -> bend_p2p
|
|||||||
missing method -> fallback to sbend_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
|
```python
|
||||||
route_method(
|
route_method(
|
||||||
@@ -492,7 +497,7 @@ mxpic_router/eda_loader.py
|
|||||||
|
|
||||||
mxpic_router/builder.py
|
mxpic_router/builder.py
|
||||||
-> import Nazca
|
-> import Nazca
|
||||||
-> import mxpic_forge Route
|
-> import mxpic_forge Route or Nazca Interconnect fallback
|
||||||
-> apply technology manifest
|
-> apply technology manifest
|
||||||
-> load all project YAML specs
|
-> load all project YAML specs
|
||||||
-> build basic, project, and PDK instances
|
-> 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.
|
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
|
- Keep PDK component metadata YAML beside its GDS files. The router needs that
|
||||||
metadata to recreate pins after `nd.load_gds`.
|
metadata to recreate pins after `nd.load_gds`.
|
||||||
- Avoid reintroducing an internal `mxpic` package inside this repo. The name
|
- Avoid reintroducing an internal `mxpic` package inside this repo. When the
|
||||||
`mxpic` must continue to resolve to `mxpic_forge`.
|
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,
|
- 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
|
- When adding a new component source, make sure it eventually contributes pins
|
||||||
to `pin_map`; otherwise bundle links can load but cannot route.
|
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:
|
) -> dict:
|
||||||
import nazca as nd
|
import nazca as nd
|
||||||
|
|
||||||
Route = _import_mxpic_forge_route()
|
Route = _import_route_backend(nd)
|
||||||
manifest = load_technology_manifest(technology_manifest_path)
|
manifest = load_technology_manifest(technology_manifest_path)
|
||||||
apply_technology_manifest(manifest, nd)
|
apply_technology_manifest(manifest, nd)
|
||||||
|
|
||||||
@@ -573,6 +573,89 @@ def _safe_float(value, default=0.0):
|
|||||||
return default
|
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():
|
def _import_mxpic_forge_route():
|
||||||
forge_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "mxpic_forge"))
|
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:
|
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"), {})
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user