Description file added
This commit is contained in:
@@ -1,3 +1,518 @@
|
||||
# mxpic_router
|
||||
# mxpic_router GDS Generation Logic
|
||||
|
||||
routing program for mxpic
|
||||
`mxpic_router` is the external layout build engine used by `mxpic_EDA`.
|
||||
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.
|
||||
|
||||
## High Level Flow
|
||||
|
||||
```text
|
||||
Canvas Build Layout
|
||||
-> frontend/canvas.html handleBuildLayout()
|
||||
-> buildYamlForPage()
|
||||
-> POST /api/save-layout
|
||||
-> backend/server.py save_layout()
|
||||
-> writes <project>/<cell>.yml
|
||||
-> backend/routed_layout_preview.py create_routed_layout_svg()
|
||||
-> mxpic_router.build_project_gds(..., target_cell_name=<cell>)
|
||||
-> temporary .gds
|
||||
-> gdstk.read_gds(...).top_level()[0].write_svg(...)
|
||||
-> <project>/<cell>.svg preview
|
||||
```
|
||||
|
||||
```text
|
||||
Canvas Build GDS
|
||||
-> frontend/canvas.html handleBuildGds()
|
||||
-> POST /api/build-gds
|
||||
-> backend/server.py build_gds()
|
||||
-> backend/gds_builder.py build_project_gds()
|
||||
-> mxpic_router.build_project_gds(...)
|
||||
-> Nazca export_gds(...)
|
||||
-> downloadable <project>.gds
|
||||
```
|
||||
|
||||
If `mxpic_router`, `mxpic_forge`, 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.
|
||||
|
||||
## Runtime Stack
|
||||
|
||||
The EDA backend checks build-time dependencies through
|
||||
`backend/router_dependency.py`.
|
||||
|
||||
```text
|
||||
require_router_stack()
|
||||
-> ensure_router_path()
|
||||
-> adds sibling ../mxpic_router to sys.path if present
|
||||
-> import mxpic_router
|
||||
-> import nazca
|
||||
-> optionally import gdstk for SVG preview
|
||||
-> import 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
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
## Inputs To mxpic_router
|
||||
|
||||
The public entry point is:
|
||||
|
||||
```python
|
||||
mxpic_router.build_project_gds(
|
||||
project_dir,
|
||||
output_path,
|
||||
pdk_root,
|
||||
technology_manifest_path=None,
|
||||
prefer_full_gds=False,
|
||||
target_cell_name=None,
|
||||
)
|
||||
```
|
||||
|
||||
Inputs:
|
||||
|
||||
- `project_dir`: directory containing saved `.yml` or `.yaml` cell documents.
|
||||
- `output_path`: final GDS path.
|
||||
- `pdk_root`: role-scoped PDK root, usually one of:
|
||||
- `opt_pdk_public/foundries`
|
||||
- `opt_pdk_atlas/foundries`
|
||||
- `technology_manifest_path`: selected technology file, for example:
|
||||
- `opt_pdk_public/foundries/Silterra/EMO1_2ML_CU_Al_RDL/technology.yml`
|
||||
- `prefer_full_gds`: manager/atlas users prefer full GDS files before black-box
|
||||
GDS files.
|
||||
- `target_cell_name`: optional. Used by SVG preview to build one saved cell as
|
||||
the top cell.
|
||||
|
||||
The return value is a dictionary:
|
||||
|
||||
```python
|
||||
{
|
||||
"output_path": output_path,
|
||||
"engine": "mxpic_router",
|
||||
"cells_built": [...],
|
||||
"warnings": [...],
|
||||
}
|
||||
```
|
||||
|
||||
## How Technology Is Loaded
|
||||
|
||||
Technology selection happens in `mxpic_EDA`, then the selected manifest path is
|
||||
passed into `mxpic_router`.
|
||||
|
||||
```text
|
||||
backend/server.py list_technologies()
|
||||
-> current_pdk_root()
|
||||
-> scan <pdk_root>/<foundry>/<technology>/
|
||||
-> expose only directories containing technology.yml
|
||||
```
|
||||
|
||||
```text
|
||||
backend/server.py technology_manifest_path_for_project(project)
|
||||
-> read project metadata "technology", such as "Silterra/EMO1_2ML_CU_Al_RDL"
|
||||
-> build <active pdk_root>/<foundry>/<technology>/technology.yml
|
||||
-> pass this path to mxpic_router
|
||||
```
|
||||
|
||||
Inside this repo:
|
||||
|
||||
```text
|
||||
mxpic_router.builder.build_project_gds()
|
||||
-> technology.load_technology_manifest(path)
|
||||
-> yaml.safe_load(technology.yml)
|
||||
-> technology.apply_technology_manifest(manifest, nazca)
|
||||
-> for manifest.layers:
|
||||
nd.add_layer(name=<layer_name>, layer=(layer, datatype), overwrite=True)
|
||||
-> for manifest.xsections:
|
||||
nd.add_xsection(name=<xsection>)
|
||||
nd.add_layer2xsection(
|
||||
xsection=<xsection>,
|
||||
layer=<layer_name>,
|
||||
growx/growy/leftedge/rightedge=...,
|
||||
overwrite=True,
|
||||
)
|
||||
```
|
||||
|
||||
Expected `technology.yml` shape:
|
||||
|
||||
```yaml
|
||||
foundry: Silterra
|
||||
technology: EMO1_2ML_CU_Al_RDL
|
||||
layers:
|
||||
WG_STRIP: {layer: 101, datatype: 251}
|
||||
routing_types:
|
||||
- euler_bend
|
||||
- standard_bend
|
||||
defaults:
|
||||
xsection: strip
|
||||
family: optical
|
||||
width: 0.45
|
||||
radius: 10
|
||||
routing_type: euler_bend
|
||||
xsections:
|
||||
strip:
|
||||
family: optical
|
||||
default_width: 0.45
|
||||
default_radius: 10
|
||||
layers:
|
||||
- {layer: WG_STRIP, growx: 4, growy: 4}
|
||||
```
|
||||
|
||||
The frontend also reads the same manifest through
|
||||
`/api/technologies/<foundry>/<technology>/manifest` so the canvas route editor
|
||||
uses the same xsections, widths, radii, and routing types that Nazca will use
|
||||
during GDS generation.
|
||||
|
||||
## How Project YAML Is Loaded
|
||||
|
||||
The frontend serializes each canvas page into the mxPIC YAML format.
|
||||
|
||||
```text
|
||||
frontend/canvas.html buildYamlForPage()
|
||||
-> buildCanvasPinsYaml()
|
||||
-> buildInstancesYaml()
|
||||
-> buildElementsYaml()
|
||||
-> buildRouteBundlesYaml()
|
||||
-> POST /api/save-layout
|
||||
-> backend/server.py writes <project>/<cell>.yml
|
||||
```
|
||||
|
||||
`mxpic_router` loads all saved YAML files from the project directory:
|
||||
|
||||
```text
|
||||
builder._load_project_specs(project_dir)
|
||||
-> sorted os.listdir(project_dir)
|
||||
-> every .yml/.yaml file
|
||||
-> eda_loader.load_cell_spec(path)
|
||||
-> yaml.safe_load(...)
|
||||
-> eda_loader.parse_cell_dict(...)
|
||||
-> CellSpec dataclass
|
||||
```
|
||||
|
||||
The parser maps YAML into typed dataclasses:
|
||||
|
||||
- `CellSpec`: name, type, version, pins, elements, instances, bundles.
|
||||
- `PinSpec`: top-level exported cell pins.
|
||||
- `ElementSpec`: canvas-only helper elements such as Port and Anchor.
|
||||
- `InstanceSpec`: placed component instance, with x, y, rotation, flip, flop,
|
||||
mirror, and settings.
|
||||
- `BundleSpec`: group of routed links.
|
||||
- `LinkSpec`: one connection, including endpoints, xsection, family, width,
|
||||
radius, routing type, and optional manual points.
|
||||
|
||||
Endpoint formats accepted by the loader:
|
||||
|
||||
```yaml
|
||||
from: instance_name:pin_name
|
||||
to: other_instance:pin_name
|
||||
```
|
||||
|
||||
or equivalent explicit fields:
|
||||
|
||||
```yaml
|
||||
src_inst: instance_name
|
||||
src_pin: pin_name
|
||||
dst_inst: other_instance
|
||||
dst_pin: pin_name
|
||||
```
|
||||
|
||||
If a link xsection starts with `metal`, the loader defaults its route family to
|
||||
`electrical`; otherwise it defaults to `optical`.
|
||||
|
||||
## Build Order
|
||||
|
||||
`builder.build_project_gds()` builds cells in this order:
|
||||
|
||||
```text
|
||||
load all CellSpec objects
|
||||
-> _ordered_specs()
|
||||
-> composite cells first
|
||||
-> project cells last
|
||||
-> _build_cell(...) for each spec
|
||||
-> _select_top_spec(...)
|
||||
-> target_cell_name if provided
|
||||
-> otherwise last ordered spec, normally the project cell
|
||||
-> nd.export_gds(topcells=[built_cells[top.name]], filename=output_path)
|
||||
```
|
||||
|
||||
This allows project-level YAML to instantiate composite cells that were built
|
||||
earlier in the same project directory.
|
||||
|
||||
## How PDK Libraries Are Found And Loaded
|
||||
|
||||
The PDK tree is not stored inside `mxpic_EDA` or `mxpic_router`.
|
||||
The active PDK root is selected by the EDA backend:
|
||||
|
||||
```text
|
||||
backend/pdk_access.py pdk_root_for_group()
|
||||
-> manager -> MXPIC_PDK_ATLAS_ROOT or opt_pdk_atlas/foundries
|
||||
-> developers -> MXPIC_PDK_PUBLIC_ROOT or opt_pdk_public/foundries
|
||||
-> user -> MXPIC_PDK_PUBLIC_ROOT or opt_pdk_public/foundries
|
||||
```
|
||||
|
||||
Expected PDK layout:
|
||||
|
||||
```text
|
||||
opt_pdk_public/
|
||||
foundries/
|
||||
Silterra/
|
||||
EMO1_2ML_CU_Al_RDL/
|
||||
technology.yml
|
||||
primitives/
|
||||
multimode_interferometers/
|
||||
1x2MMI_.../
|
||||
1x2MMI_....yml
|
||||
1x2MMI_...._BB.gds
|
||||
electronics/
|
||||
composites/
|
||||
```
|
||||
|
||||
The EDA library panel scans the selected project technology folder for
|
||||
component folders. A folder containing a component `.yml` or `.yaml` file is a
|
||||
component leaf. `technology.yml` is ignored during component scanning.
|
||||
|
||||
The YAML saved by the canvas stores each PDK component by path relative to the
|
||||
role PDK root. During GDS build:
|
||||
|
||||
```text
|
||||
builder._build_cell()
|
||||
-> for each instance:
|
||||
1. if built-in basic component:
|
||||
-> create local Nazca primitive
|
||||
2. else if instance.component is a previously built project cell:
|
||||
-> place that built Cell
|
||||
3. else:
|
||||
-> _resolve_pdk_asset(pdk_root, instance.component, prefer_full_gds)
|
||||
```
|
||||
|
||||
PDK asset resolution:
|
||||
|
||||
```text
|
||||
_resolve_pdk_asset()
|
||||
-> direct path:
|
||||
<pdk_root>/<component path from YAML>
|
||||
-> fallback:
|
||||
os.walk(pdk_root) until a folder basename matches the component name
|
||||
-> metadata:
|
||||
<component_name>.yml or <component_name>.yaml
|
||||
-> GDS:
|
||||
public/default: prefer <component_name>_BB.gds, then <component_name>.gds
|
||||
manager/atlas: prefer <component_name>.gds, then <component_name>_BB.gds
|
||||
fallback: first .gds in the folder using the same preference order
|
||||
```
|
||||
|
||||
The PDK GDS is loaded and placed with Nazca:
|
||||
|
||||
```text
|
||||
loaded = nd.load_gds(asset["gds_path"])
|
||||
loaded.put(instance.x, instance.y, instance.rotation, flip=..., flop=...)
|
||||
```
|
||||
|
||||
The component metadata YAML is used to recover routable pins:
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
a1:
|
||||
x: -28.5
|
||||
y: 0.0
|
||||
a: 180.0
|
||||
width: 0.7
|
||||
b1:
|
||||
x: 58.5
|
||||
y: 4.35
|
||||
a: 0.0
|
||||
width: 0.7
|
||||
```
|
||||
|
||||
`mxpic_router` prefers a `pins` dictionary. For the external PDK roots
|
||||
`opt_pdk_public` and `opt_pdk_atlas`, it also accepts `ports` as routable pins
|
||||
for compatibility with generated PDK metadata.
|
||||
|
||||
Each metadata pin is transformed by the instance placement:
|
||||
|
||||
```text
|
||||
local pin x/y/a
|
||||
-> apply flip/flop
|
||||
-> rotate by instance.rotation
|
||||
-> translate by instance.x / instance.y
|
||||
-> register as pin_map[(instance_name, pin_name)]
|
||||
```
|
||||
|
||||
## How Ports Are Registered
|
||||
|
||||
During `_build_cell()`, every cell gets a local `pin_map`:
|
||||
|
||||
```python
|
||||
pin_map[(instance_name, pin_name)] = Nazca Pin
|
||||
```
|
||||
|
||||
This map is the central contract between placement and routing.
|
||||
|
||||
Sources of pins:
|
||||
|
||||
1. Built-in basic components
|
||||
- `waveguide`, `90 bend`, `180 bend`, `circle`/`cricle`, `taper`
|
||||
- Created directly with Nazca primitives.
|
||||
- Pins are registered from the placed primitive cell.
|
||||
|
||||
2. Previously built project or composite cells
|
||||
- If `instance.component` matches a cell built earlier in the project, the
|
||||
cell is placed hierarchically.
|
||||
- All placed pins except `org` are registered.
|
||||
|
||||
3. External PDK GDS components
|
||||
- GDS geometry is loaded with `nd.load_gds`.
|
||||
- Routable pins are recreated from component metadata YAML.
|
||||
|
||||
4. Top-level cell pins
|
||||
- YAML `pins:` become Nazca pins.
|
||||
- The builder also creates an inward-facing copy by rotating the pin 180
|
||||
degrees.
|
||||
- Registered as:
|
||||
|
||||
```python
|
||||
pin_map[("this", pin_name)] = inward_pin
|
||||
pin_map[(pin_name, pin_name)] = inward_pin
|
||||
```
|
||||
|
||||
5. Canvas elements
|
||||
- `Port` elements create named external-facing pins and internal `_in` pins.
|
||||
- Routing uses the internal `_in` pins so links connect into the layout.
|
||||
- `Anchor` elements create pairs such as `anchor_a1` and `anchor_b1`.
|
||||
|
||||
## How Bundle Links Become GDS Routes
|
||||
|
||||
The canvas serializes React Flow edges into YAML under:
|
||||
|
||||
```yaml
|
||||
bundles:
|
||||
output_bus:
|
||||
routing_type: euler_bend
|
||||
links:
|
||||
- from: input:input_io1
|
||||
to: mmi:a1
|
||||
xsection: strip
|
||||
family: optical
|
||||
width: 0.45
|
||||
radius: 10
|
||||
routing_type: euler_bend
|
||||
points:
|
||||
- x: 100.0
|
||||
y: 200.0
|
||||
- x: 300.0
|
||||
y: 200.0
|
||||
```
|
||||
|
||||
For each link:
|
||||
|
||||
```text
|
||||
builder._route_link(link, pin_map, Route, warnings)
|
||||
-> route = Route(
|
||||
radius=link.radius or 10,
|
||||
width=link.width,
|
||||
xs=link.xsection,
|
||||
PCB=True for metal_1/metal_2 else False,
|
||||
)
|
||||
-> p1 = pin_map[(link.src_inst, link.src_pin)]
|
||||
-> p2 = pin_map[(link.dst_inst, link.dst_pin)]
|
||||
```
|
||||
|
||||
### Manual Point Routing
|
||||
|
||||
If `link.points` has at least two points:
|
||||
|
||||
```text
|
||||
if link also has pin endpoints:
|
||||
first point is replaced by p1 coordinate
|
||||
last point is replaced by p2 coordinate
|
||||
_route_guided_link()
|
||||
-> Route.strt_p2p(...) for straight segments
|
||||
-> Route.bend_p2p(...) for corners when available
|
||||
-> _guided_route_plan() trims corners by bend radius
|
||||
```
|
||||
|
||||
If the route backend does not provide `bend_p2p`, corners are exported as
|
||||
straight joins and a warning is added.
|
||||
|
||||
### Automatic Pin To Pin Routing
|
||||
|
||||
If there are no manual points, the builder chooses the route method from pin
|
||||
angles:
|
||||
|
||||
```text
|
||||
same direction -> ubend_p2p
|
||||
roughly opposite -> sbend_p2p
|
||||
otherwise -> bend_p2p
|
||||
missing method -> fallback to sbend_p2p
|
||||
```
|
||||
|
||||
Then it calls the selected `mxpic_forge.Route` method:
|
||||
|
||||
```python
|
||||
route_method(
|
||||
pin1=p1,
|
||||
pin2=p2,
|
||||
width=link.width,
|
||||
radius=link.radius or 10,
|
||||
xs=link.xsection,
|
||||
arrow=False,
|
||||
).put()
|
||||
```
|
||||
|
||||
If either endpoint pin is missing, the link is skipped and a warning is added:
|
||||
|
||||
```text
|
||||
Missing route pin for <src_inst>:<src_pin> -> <dst_inst>:<dst_pin>
|
||||
```
|
||||
|
||||
## Current Responsibilities By Module
|
||||
|
||||
```text
|
||||
mxpic_router/__init__.py
|
||||
-> public exports, especially build_project_gds
|
||||
|
||||
mxpic_router/technology.py
|
||||
-> read technology.yml
|
||||
-> register Nazca layers
|
||||
-> register Nazca xsections and layer mappings
|
||||
|
||||
mxpic_router/eda_loader.py
|
||||
-> read saved cell YAML
|
||||
-> parse pins, elements, instances, bundles, and links into dataclasses
|
||||
-> normalize numbers, booleans, endpoints, points, and route family defaults
|
||||
|
||||
mxpic_router/builder.py
|
||||
-> import Nazca
|
||||
-> import mxpic_forge Route
|
||||
-> apply technology manifest
|
||||
-> load all project YAML specs
|
||||
-> build basic, project, and PDK instances
|
||||
-> register routable pins
|
||||
-> route bundle links
|
||||
-> export final GDS
|
||||
|
||||
mxpic_router_legacy/
|
||||
-> old legacy package, renamed so `mxpic` remains free for mxpic_forge
|
||||
```
|
||||
|
||||
## Maintenance Notes
|
||||
|
||||
- Keep `technology.yml` as the single source of truth for layer/xsection
|
||||
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`.
|
||||
- When adding a new routing family or xsection, update `technology.yml` first,
|
||||
then verify that `mxpic_forge.Route` 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.
|
||||
|
||||
Reference in New Issue
Block a user