Files
mxpic_router/mxpic_router/eda_loader.py
T
2026-06-01 11:34:00 +08:00

228 lines
7.1 KiB
Python

from dataclasses import dataclass, field
from typing import Dict, List, Optional
import yaml
@dataclass
class PinSpec:
name: str
x: float = 0.0
y: float = 0.0
angle: float = 0.0
width: float = 0.5
layer: str = "WG_CORE"
@dataclass
class InstanceSpec:
name: str
component: str
x: float = 0.0
y: float = 0.0
rotation: float = 0.0
flip: bool = False
flop: bool = False
mirror: bool = False
settings: Dict = field(default_factory=dict)
@dataclass
class ElementSpec:
name: str
type: str
x: float = 0.0
y: float = 0.0
angle: float = 0.0
width: float = 0.5
port_number: int = 1
pitch: float = 10.0
layer: str = "WG_CORE"
description: str = ""
pins: List[Dict[str, str]] = field(default_factory=list)
@dataclass
class LinkSpec:
src_inst: str = ""
src_pin: str = ""
dst_inst: str = ""
dst_pin: str = ""
xsection: str = "strip"
family: str = "optical"
width: Optional[float] = None
radius: Optional[float] = None
routing_type: str = "euler_bend"
points: List[Dict[str, float]] = field(default_factory=list)
@dataclass
class BundleSpec:
name: str
links: List[LinkSpec] = field(default_factory=list)
routing_type: str = "euler_bend"
radius: Optional[float] = None
width: Optional[float] = None
xsection: str = "strip"
@dataclass
class CellSpec:
name: str
type: str = "composite"
version: str = "1.0.0"
pins: Dict[str, PinSpec] = field(default_factory=dict)
elements: Dict[str, ElementSpec] = field(default_factory=dict)
instances: Dict[str, InstanceSpec] = field(default_factory=dict)
bundles: Dict[str, BundleSpec] = field(default_factory=dict)
def load_cell_spec(path: str) -> CellSpec:
with open(path, "r", encoding="utf-8") as file:
return parse_cell_dict(yaml.safe_load(file) or {})
def parse_cell_dict(data: dict) -> CellSpec:
spec = CellSpec(
name=str(data.get("name") or "cell"),
type=str(data.get("type") or "composite"),
version=str(data.get("version") or "1.0.0"),
)
for pin in data.get("pins", []) or []:
name = str(pin.get("name") or "pin")
spec.pins[name] = PinSpec(
name=name,
x=_float(pin.get("x")),
y=_float(pin.get("y")),
angle=_float(pin.get("angle", pin.get("a"))),
width=_float(pin.get("width"), 0.5),
layer=str(pin.get("layer") or "WG_CORE"),
)
for element_name, element in (data.get("elements") or {}).items():
spec.elements[str(element_name)] = ElementSpec(
name=str(element_name),
type=str(element.get("type") or "anchor"),
x=_float(element.get("x")),
y=_float(element.get("y")),
angle=_float(element.get("angle", element.get("a"))),
width=_float(element.get("width"), 0.5),
port_number=_int(element.get("pin_number", element.get("pinNumber", element.get("port_number", element.get("portNumber")))), 1),
pitch=_float(element.get("pitch"), 10.0),
layer=str(element.get("layer") or "WG_CORE"),
description=str(element.get("description") or ""),
pins=_pins(element.get("pins")),
)
for instance_name, instance in (data.get("instances") or {}).items():
spec.instances[str(instance_name)] = InstanceSpec(
name=str(instance_name),
component=str(instance.get("component") or ""),
x=_float(instance.get("x")),
y=_float(instance.get("y")),
rotation=_float(instance.get("rotation")),
flip=_bool(instance.get("flip", instance.get("mirror", False))),
flop=_bool(instance.get("flop", False)),
mirror=_bool(instance.get("mirror", instance.get("flip", False))),
settings=instance.get("settings") or {},
)
for bundle_name, bundle_data in (data.get("bundles") or {}).items():
bundle = BundleSpec(
name=str(bundle_name),
routing_type=str(bundle_data.get("routing_type") or "euler_bend"),
radius=_optional_float(bundle_data.get("radius")),
width=_optional_float(bundle_data.get("width")),
xsection=str(bundle_data.get("xsection") or bundle_data.get("xs") or "strip"),
)
for link_data in bundle_data.get("links", []) or []:
src_inst, src_pin = _endpoint(link_data.get("from"), link_data.get("src_inst"), link_data.get("src_pin"))
dst_inst, dst_pin = _endpoint(link_data.get("to"), link_data.get("dst_inst"), link_data.get("dst_pin"))
xsection = str(link_data.get("xsection") or link_data.get("xs") or bundle.xsection)
bundle.links.append(LinkSpec(
src_inst=src_inst,
src_pin=src_pin,
dst_inst=dst_inst,
dst_pin=dst_pin,
xsection=xsection,
family=str(link_data.get("family") or _family_from_xsection(xsection)),
width=_optional_float(link_data.get("width"), bundle.width),
radius=_optional_float(link_data.get("radius"), bundle.radius),
routing_type=str(link_data.get("routing_type") or bundle.routing_type),
points=_points(link_data.get("points")),
))
spec.bundles[bundle.name] = bundle
return spec
def _endpoint(compact, inst, port):
if compact:
value = str(compact)
if ":" in value:
left, right = value.split(":", 1)
return left, right
if inst or port:
return str(inst or ""), str(port or "")
return "", ""
def _family_from_xsection(xsection: str) -> str:
return "electrical" if str(xsection).startswith("metal") else "optical"
def _points(points) -> List[Dict[str, float]]:
parsed = []
for point in points or []:
if not isinstance(point, dict):
continue
x = _optional_float(point.get("x"))
y = _optional_float(point.get("y"))
if x is None or y is None:
continue
parsed.append({"x": x, "y": y})
return parsed
def _pins(pins) -> List[Dict[str, str]]:
parsed = []
for pin in pins or []:
if not isinstance(pin, dict):
continue
name = str(pin.get("name") or "").strip()
role = str(pin.get("role") or "").strip()
if name:
parsed.append({"name": name, "role": role})
return parsed
def _optional_float(value, default=None):
if value is None or value == "":
return default
return _float(value, default)
def _bool(value) -> bool:
if isinstance(value, str):
return value.strip().lower() in {"1", "true", "yes", "on"}
return bool(value)
def _float(value, default=0.0):
try:
if value is None or value == "":
return default
return float(value)
except (TypeError, ValueError):
return default
def _int(value, default=0):
try:
if value is None or value == "":
return default
return max(1, int(float(value)))
except (TypeError, ValueError):
return default