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
2 changed files with 274 additions and 215 deletions
Showing only changes of commit d7698d1ee5 - Show all commits
+56 -8
View File
@@ -12,6 +12,7 @@ from .technology import apply_technology_manifest, load_technology_manifest
ROUTE_MIN_SPACING = 10.0 ROUTE_MIN_SPACING = 10.0
ROUTE_ENDPOINT_SPACING_IGNORE = ROUTE_MIN_SPACING * 2.0 ROUTE_ENDPOINT_SPACING_IGNORE = ROUTE_MIN_SPACING * 2.0
ROUTE_SPACING_GEOMETRY_ADJUSTMENT_ENABLED = False ROUTE_SPACING_GEOMETRY_ADJUSTMENT_ENABLED = False
ROUTE_SBEND_GROUP_SPAN_GRID = 100.0
def build_project_gds( def build_project_gds(
@@ -370,7 +371,7 @@ def _assign_sbend_lstart_spacing(plans: list) -> None:
for group in groups.values(): for group in groups.values():
if len(group) < 2: if len(group) < 2:
continue continue
axis = _dominant_spacing_axis(group[0]["points"][0], group[0]["points"][-1]) axis = _sbend_forward_axis(group[0])
coord_key = "y" if axis == "horizontal" else "x" coord_key = "y" if axis == "horizontal" else "x"
step = _sbend_lstart_step(group) step = _sbend_lstart_step(group)
for plan, rank in _sbend_lstart_ranks(group, coord_key): for plan, rank in _sbend_lstart_ranks(group, coord_key):
@@ -386,16 +387,16 @@ def _sbend_lstart_ranks(group: list, coord_key: str) -> list:
deltas = [_route_coord_delta(plan, coord_key) for plan in group] deltas = [_route_coord_delta(plan, coord_key) for plan in group]
nonzero_deltas = [delta for delta in deltas if abs(delta) > 1e-9] nonzero_deltas = [delta for delta in deltas if abs(delta) > 1e-9]
if nonzero_deltas and all(delta > 0 for delta in nonzero_deltas): if nonzero_deltas and all(delta > 0 for delta in nonzero_deltas):
ordered = sorted(group, key=lambda plan: _route_mid_coord(plan, coord_key)) ordered = sorted(group, key=lambda plan: _route_start_coord(plan, coord_key), reverse=True)
return [(plan, len(ordered) - index) for index, plan in enumerate(ordered)] return [(plan, index + 1) for index, plan in enumerate(ordered)]
if nonzero_deltas and all(delta < 0 for delta in nonzero_deltas): if nonzero_deltas and all(delta < 0 for delta in nonzero_deltas):
ordered = sorted(group, key=lambda plan: _route_mid_coord(plan, coord_key), reverse=True) ordered = sorted(group, key=lambda plan: _route_start_coord(plan, coord_key), reverse=True)
return [(plan, len(ordered) - index) for index, plan in enumerate(ordered)] return [(plan, len(ordered) - index) for index, plan in enumerate(ordered)]
center = sum(_route_mid_coord(plan, coord_key) for plan in group) / len(group) center = sum(_route_start_coord(plan, coord_key) for plan in group) / len(group)
distances = sorted({round(abs(_route_mid_coord(plan, coord_key) - center), 6) for plan in group}) distances = sorted({round(abs(_route_start_coord(plan, coord_key) - center), 6) for plan in group})
return [ return [
(plan, distances.index(round(abs(_route_mid_coord(plan, coord_key) - center), 6)) + 1) (plan, distances.index(round(abs(_route_start_coord(plan, coord_key) - center), 6)) + 1)
for plan in group for plan in group
] ]
@@ -408,15 +409,58 @@ def _route_coord_delta(plan: dict, coord_key: str) -> float:
def _sbend_spacing_group_key(plan: dict): def _sbend_spacing_group_key(plan: dict):
link = plan["link"] link = plan["link"]
axis = _dominant_spacing_axis(plan["points"][0], plan["points"][-1]) axis = _sbend_forward_axis(plan)
main_key = "x" if axis == "horizontal" else "y"
main_delta = _route_coord_delta(plan, main_key)
explicit_group = _link_explicit_route_group(link)
if explicit_group:
route_scope = ("explicit", explicit_group)
else:
route_scope = (
"span",
_sbend_span_bucket(plan["points"][0][main_key]),
_sbend_span_bucket(plan["points"][-1][main_key]),
)
return ( return (
axis, axis,
_route_direction_sign(main_delta),
route_scope,
str(link.xsection or ""), str(link.xsection or ""),
_safe_float(link.width, 0.0), _safe_float(link.width, 0.0),
_safe_float(link.radius, 0.0), _safe_float(link.radius, 0.0),
) )
def _sbend_forward_axis(plan: dict) -> str:
angle = _pin_angle(plan["pin1"])
if angle is None:
angle = _pin_angle(plan["pin2"])
if angle is None:
return _dominant_spacing_axis(plan["points"][0], plan["points"][-1])
normalized = angle % 180
if abs(normalized) <= 45 or abs(normalized - 180) <= 45:
return "horizontal"
if abs(normalized - 90) <= 45:
return "vertical"
return _dominant_spacing_axis(plan["points"][0], plan["points"][-1])
def _link_explicit_route_group(link: LinkSpec) -> str:
return str(getattr(link, "route_group", "") or "").strip()
def _route_direction_sign(delta: float) -> int:
if delta > 1e-9:
return 1
if delta < -1e-9:
return -1
return 0
def _sbend_span_bucket(value: float) -> int:
return round(float(value) / ROUTE_SBEND_GROUP_SPAN_GRID)
def _route_group_needs_spacing(group: list) -> bool: def _route_group_needs_spacing(group: list) -> bool:
check_points = [_route_spacing_check_points(plan["points"]) for plan in group] check_points = [_route_spacing_check_points(plan["points"]) for plan in group]
threshold = _sbend_lstart_step(group) threshold = _sbend_lstart_step(group)
@@ -433,6 +477,10 @@ def _route_mid_coord(plan: dict, coord_key: str) -> float:
return (float(start[coord_key]) + float(end[coord_key])) / 2.0 return (float(start[coord_key]) + float(end[coord_key])) / 2.0
def _route_start_coord(plan: dict, coord_key: str) -> float:
return float(plan["points"][0][coord_key])
def _route_spacing_reference_points(link: LinkSpec, pin1, pin2) -> list: def _route_spacing_reference_points(link: LinkSpec, pin1, pin2) -> list:
if len(link.points or []) >= 2: if len(link.points or []) >= 2:
return _route_points_with_pin_endpoints(link.points, pin1, pin2) return _route_points_with_pin_endpoints(link.points, pin1, pin2)
+218 -207
View File
@@ -1,227 +1,238 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, List, Optional from typing import Dict, List, Optional
import yaml import yaml
@dataclass @dataclass
class PinSpec: class PinSpec:
name: str name: str
x: float = 0.0 x: float = 0.0
y: float = 0.0 y: float = 0.0
angle: float = 0.0 angle: float = 0.0
width: float = 0.5 width: float = 0.5
layer: str = "WG_CORE" layer: str = "WG_CORE"
@dataclass @dataclass
class InstanceSpec: class InstanceSpec:
name: str name: str
component: str component: str
x: float = 0.0 x: float = 0.0
y: float = 0.0 y: float = 0.0
rotation: float = 0.0 rotation: float = 0.0
flip: bool = False flip: bool = False
flop: bool = False flop: bool = False
mirror: bool = False mirror: bool = False
settings: Dict = field(default_factory=dict) settings: Dict = field(default_factory=dict)
@dataclass @dataclass
class ElementSpec: class ElementSpec:
name: str name: str
type: str type: str
x: float = 0.0 x: float = 0.0
y: float = 0.0 y: float = 0.0
angle: float = 0.0 angle: float = 0.0
width: float = 0.5 width: float = 0.5
port_number: int = 1 port_number: int = 1
pitch: float = 10.0 pitch: float = 10.0
layer: str = "WG_CORE" layer: str = "WG_CORE"
description: str = "" description: str = ""
pins: List[Dict[str, str]] = field(default_factory=list) pins: List[Dict[str, str]] = field(default_factory=list)
@dataclass @dataclass
class LinkSpec: class LinkSpec:
src_inst: str = "" src_inst: str = ""
src_pin: str = "" src_pin: str = ""
dst_inst: str = "" dst_inst: str = ""
dst_pin: str = "" dst_pin: str = ""
xsection: str = "strip" xsection: str = "strip"
family: str = "optical" family: str = "optical"
width: Optional[float] = None width: Optional[float] = None
radius: Optional[float] = None radius: Optional[float] = None
routing_type: str = "euler_bend" routing_type: str = "euler_bend"
bundle: str = ""
route_group: str = ""
points: List[Dict[str, float]] = field(default_factory=list) points: List[Dict[str, float]] = field(default_factory=list)
@dataclass @dataclass
class BundleSpec: class BundleSpec:
name: str name: str
links: List[LinkSpec] = field(default_factory=list) links: List[LinkSpec] = field(default_factory=list)
routing_type: str = "euler_bend" routing_type: str = "euler_bend"
radius: Optional[float] = None radius: Optional[float] = None
width: Optional[float] = None width: Optional[float] = None
xsection: str = "strip" xsection: str = "strip"
@dataclass @dataclass
class CellSpec: class CellSpec:
name: str name: str
type: str = "composite" type: str = "composite"
version: str = "1.0.0" version: str = "1.0.0"
pins: Dict[str, PinSpec] = field(default_factory=dict) pins: Dict[str, PinSpec] = field(default_factory=dict)
elements: Dict[str, ElementSpec] = field(default_factory=dict) elements: Dict[str, ElementSpec] = field(default_factory=dict)
instances: Dict[str, InstanceSpec] = field(default_factory=dict) instances: Dict[str, InstanceSpec] = field(default_factory=dict)
bundles: Dict[str, BundleSpec] = field(default_factory=dict) bundles: Dict[str, BundleSpec] = field(default_factory=dict)
def load_cell_spec(path: str) -> CellSpec: def load_cell_spec(path: str) -> CellSpec:
with open(path, "r", encoding="utf-8") as file: with open(path, "r", encoding="utf-8") as file:
return parse_cell_dict(yaml.safe_load(file) or {}) return parse_cell_dict(yaml.safe_load(file) or {})
def parse_cell_dict(data: dict) -> CellSpec: def parse_cell_dict(data: dict) -> CellSpec:
spec = CellSpec( spec = CellSpec(
name=str(data.get("name") or "cell"), name=str(data.get("name") or "cell"),
type=str(data.get("type") or "composite"), type=str(data.get("type") or "composite"),
version=str(data.get("version") or "1.0.0"), version=str(data.get("version") or "1.0.0"),
) )
for pin in data.get("pins", []) or []: for pin in data.get("pins", []) or []:
name = str(pin.get("name") or "pin") name = str(pin.get("name") or "pin")
spec.pins[name] = PinSpec( spec.pins[name] = PinSpec(
name=name, name=name,
x=_float(pin.get("x")), x=_float(pin.get("x")),
y=_float(pin.get("y")), y=_float(pin.get("y")),
angle=_float(pin.get("angle", pin.get("a"))), angle=_float(pin.get("angle", pin.get("a"))),
width=_float(pin.get("width"), 0.5), width=_float(pin.get("width"), 0.5),
layer=str(pin.get("layer") or "WG_CORE"), layer=str(pin.get("layer") or "WG_CORE"),
) )
for element_name, element in (data.get("elements") or {}).items(): for element_name, element in (data.get("elements") or {}).items():
spec.elements[str(element_name)] = ElementSpec( spec.elements[str(element_name)] = ElementSpec(
name=str(element_name), name=str(element_name),
type=str(element.get("type") or "anchor"), type=str(element.get("type") or "anchor"),
x=_float(element.get("x")), x=_float(element.get("x")),
y=_float(element.get("y")), y=_float(element.get("y")),
angle=_float(element.get("angle", element.get("a"))), angle=_float(element.get("angle", element.get("a"))),
width=_float(element.get("width"), 0.5), 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), 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), pitch=_float(element.get("pitch"), 10.0),
layer=str(element.get("layer") or "WG_CORE"), layer=str(element.get("layer") or "WG_CORE"),
description=str(element.get("description") or ""), description=str(element.get("description") or ""),
pins=_pins(element.get("pins")), pins=_pins(element.get("pins")),
) )
for instance_name, instance in (data.get("instances") or {}).items(): for instance_name, instance in (data.get("instances") or {}).items():
spec.instances[str(instance_name)] = InstanceSpec( spec.instances[str(instance_name)] = InstanceSpec(
name=str(instance_name), name=str(instance_name),
component=str(instance.get("component") or ""), component=str(instance.get("component") or ""),
x=_float(instance.get("x")), x=_float(instance.get("x")),
y=_float(instance.get("y")), y=_float(instance.get("y")),
rotation=_float(instance.get("rotation")), rotation=_float(instance.get("rotation")),
flip=_bool(instance.get("flip", instance.get("mirror", False))), flip=_bool(instance.get("flip", instance.get("mirror", False))),
flop=_bool(instance.get("flop", False)), flop=_bool(instance.get("flop", False)),
mirror=_bool(instance.get("mirror", instance.get("flip", False))), mirror=_bool(instance.get("mirror", instance.get("flip", False))),
settings=instance.get("settings") or {}, settings=instance.get("settings") or {},
) )
for bundle_name, bundle_data in (data.get("bundles") or {}).items(): for bundle_name, bundle_data in (data.get("bundles") or {}).items():
bundle = BundleSpec( bundle = BundleSpec(
name=str(bundle_name), name=str(bundle_name),
routing_type=str(bundle_data.get("routing_type") or "euler_bend"), routing_type=str(bundle_data.get("routing_type") or "euler_bend"),
radius=_optional_float(bundle_data.get("radius")), radius=_optional_float(bundle_data.get("radius")),
width=_optional_float(bundle_data.get("width")), width=_optional_float(bundle_data.get("width")),
xsection=str(bundle_data.get("xsection") or bundle_data.get("xs") or "strip"), xsection=str(bundle_data.get("xsection") or bundle_data.get("xs") or "strip"),
) )
for link_data in bundle_data.get("links", []) or []: 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")) 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")) 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) xsection = str(link_data.get("xsection") or link_data.get("xs") or bundle.xsection)
bundle.links.append(LinkSpec( bundle.links.append(LinkSpec(
src_inst=src_inst, src_inst=src_inst,
src_pin=src_pin, src_pin=src_pin,
dst_inst=dst_inst, dst_inst=dst_inst,
dst_pin=dst_pin, dst_pin=dst_pin,
xsection=xsection, xsection=xsection,
family=str(link_data.get("family") or _family_from_xsection(xsection)), family=str(link_data.get("family") or _family_from_xsection(xsection)),
width=_optional_float(link_data.get("width"), bundle.width), width=_optional_float(link_data.get("width"), bundle.width),
radius=_optional_float(link_data.get("radius"), bundle.radius), radius=_optional_float(link_data.get("radius"), bundle.radius),
routing_type=str(link_data.get("routing_type") or bundle.routing_type), routing_type=str(link_data.get("routing_type") or bundle.routing_type),
bundle=bundle.name,
route_group=str(
link_data.get("route_group")
or link_data.get("routeGroup")
or link_data.get("group")
or link_data.get("bundle_group")
or link_data.get("bundleGroup")
or ""
),
points=_points(link_data.get("points")), points=_points(link_data.get("points")),
)) ))
spec.bundles[bundle.name] = bundle spec.bundles[bundle.name] = bundle
return spec return spec
def _endpoint(compact, inst, port): def _endpoint(compact, inst, port):
if compact: if compact:
value = str(compact) value = str(compact)
if ":" in value: if ":" in value:
left, right = value.split(":", 1) left, right = value.split(":", 1)
return left, right return left, right
if inst or port: if inst or port:
return str(inst or ""), str(port or "") return str(inst or ""), str(port or "")
return "", "" return "", ""
def _family_from_xsection(xsection: str) -> str: def _family_from_xsection(xsection: str) -> str:
return "electrical" if str(xsection).startswith("metal") else "optical" return "electrical" if str(xsection).startswith("metal") else "optical"
def _points(points) -> List[Dict[str, float]]: def _points(points) -> List[Dict[str, float]]:
parsed = [] parsed = []
for point in points or []: for point in points or []:
if not isinstance(point, dict): if not isinstance(point, dict):
continue continue
x = _optional_float(point.get("x")) x = _optional_float(point.get("x"))
y = _optional_float(point.get("y")) y = _optional_float(point.get("y"))
if x is None or y is None: if x is None or y is None:
continue continue
parsed.append({"x": x, "y": y}) parsed.append({"x": x, "y": y})
return parsed return parsed
def _pins(pins) -> List[Dict[str, str]]: def _pins(pins) -> List[Dict[str, str]]:
parsed = [] parsed = []
for pin in pins or []: for pin in pins or []:
if not isinstance(pin, dict): if not isinstance(pin, dict):
continue continue
name = str(pin.get("name") or "").strip() name = str(pin.get("name") or "").strip()
role = str(pin.get("role") or "").strip() role = str(pin.get("role") or "").strip()
if name: if name:
parsed.append({"name": name, "role": role}) parsed.append({"name": name, "role": role})
return parsed return parsed
def _optional_float(value, default=None): def _optional_float(value, default=None):
if value is None or value == "": if value is None or value == "":
return default return default
return _float(value, default) return _float(value, default)
def _bool(value) -> bool: def _bool(value) -> bool:
if isinstance(value, str): if isinstance(value, str):
return value.strip().lower() in {"1", "true", "yes", "on"} return value.strip().lower() in {"1", "true", "yes", "on"}
return bool(value) return bool(value)
def _float(value, default=0.0): def _float(value, default=0.0):
try: try:
if value is None or value == "": if value is None or value == "":
return default return default
return float(value) return float(value)
except (TypeError, ValueError): except (TypeError, ValueError):
return default return default
def _int(value, default=0): def _int(value, default=0):
try: try:
if value is None or value == "": if value is None or value == "":
return default return default
return max(1, int(float(value))) return max(1, int(float(value)))
except (TypeError, ValueError): except (TypeError, ValueError):
return default return default