Routing problem for multi-pin port and anchors are debugged
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+107
-2
@@ -1,6 +1,6 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Description: Backend integration wrapper for project GDS generation and build result handling.
|
||||
# Inside functions: build_project_gds, _build_with_mxpic_router, _load_project_cells, _ordered_cell_names, _cells_have_links, _build_with_gdstk, _import_public_gds, _build_with_nazca, _safe_cell_name, _library_cell_by_name, _number
|
||||
# Inside functions: build_project_gds, _build_with_mxpic_router, _load_project_cells, _ordered_cell_names, _cells_have_links, _cells_have_elements, _build_with_gdstk, _import_public_gds, _build_with_nazca, _build_nazca_element_cells, _build_nazca_element_cell, _element_port_offset, _safe_cell_name, _library_cell_by_name, _int, _number
|
||||
# Developer : Qin Yue @ 2026
|
||||
# Organization : OptiHK Limited
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -58,7 +58,19 @@ def build_project_gds(
|
||||
registry = PdkRegistry(pdk_public_root, prefer_full_gds=prefer_full_gds)
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
# gdstk is the preferred fallback; Nazca remains a secondary fallback for
|
||||
# Port/Anchor elements are logical pin-bearing devices. Use Nazca for these
|
||||
# layouts so each element can be a placed cell with local nd.Pin metadata.
|
||||
if _cells_have_elements(cells):
|
||||
try:
|
||||
return _build_with_nazca(cells, output_path, registry)
|
||||
except ImportError as nazca_error:
|
||||
raise RuntimeError(
|
||||
"Build GDS with Port/Anchor elements requires Nazca so element cells can preserve nd.Pin metadata. "
|
||||
f"Nazca import failed: {nazca_error}"
|
||||
) from nazca_error
|
||||
|
||||
# gdstk is the preferred fallback for placement-only projects without
|
||||
# Port/Anchor element pin metadata; Nazca remains a secondary fallback for
|
||||
# environments where gdstk is not installed.
|
||||
try:
|
||||
return _build_with_gdstk(cells, output_path, registry)
|
||||
@@ -132,6 +144,16 @@ def _cells_have_links(cells: Dict[str, dict]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _cells_have_elements(cells: Dict[str, dict]) -> bool:
|
||||
"""Detect whether any saved cell contains Port/Anchor element objects."""
|
||||
for data in cells.values():
|
||||
elements = data.get("elements") or {}
|
||||
for element in elements.values():
|
||||
if str((element or {}).get("type") or "").lower() in {"port", "anchor"}:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _build_with_gdstk(cells: Dict[str, dict], output_path: str, registry: PdkRegistry) -> BuildResult:
|
||||
"""Assemble a project GDS with gdstk when Nazca or routed building is unavailable."""
|
||||
import gdstk
|
||||
@@ -196,6 +218,7 @@ def _build_with_nazca(cells: Dict[str, dict], output_path: str, registry: PdkReg
|
||||
# is exported as the top-level GDS.
|
||||
for cell_name in ordered_names:
|
||||
data = cells[cell_name]
|
||||
element_cells = _build_nazca_element_cells(nd, cell_name, data.get("elements") or {}, built_cells)
|
||||
with nd.Cell(cell_name) as current_cell:
|
||||
for instance_name, instance in (data.get("instances") or {}).items():
|
||||
component = str(instance.get("component") or "")
|
||||
@@ -211,6 +234,12 @@ def _build_with_nazca(cells: Dict[str, dict], output_path: str, registry: PdkReg
|
||||
continue
|
||||
loaded = nd.load_gds(asset.gds_path)
|
||||
loaded.put(x, y, rotation)
|
||||
for element_name, element_cell in element_cells.items():
|
||||
element = (data.get("elements") or {}).get(element_name) or {}
|
||||
x = _number(element.get("x"))
|
||||
y = _number(element.get("y"))
|
||||
rotation = _number(element.get("angle"))
|
||||
element_cell.put(x, y, rotation)
|
||||
built_cells[cell_name] = current_cell
|
||||
|
||||
top_name = ordered_names[-1]
|
||||
@@ -218,6 +247,72 @@ def _build_with_nazca(cells: Dict[str, dict], output_path: str, registry: PdkReg
|
||||
return BuildResult(output_path=output_path, engine="nazca", cells_built=ordered_names, warnings=warnings)
|
||||
|
||||
|
||||
def _build_nazca_element_cells(nd, parent_cell_name: str, elements: dict, existing_cells: dict) -> Dict[str, object]:
|
||||
"""Build reusable Nazca cells for built-in Port and Anchor element objects."""
|
||||
element_cells = {}
|
||||
known_cells = dict(existing_cells or {})
|
||||
for element_name, element in (elements or {}).items():
|
||||
element_type = str((element or {}).get("type") or "").lower()
|
||||
if element_type not in {"port", "anchor"}:
|
||||
continue
|
||||
raw_cell_name = f"{parent_cell_name}_{element_name}_{element_type}"
|
||||
cell_name = _safe_cell_name(raw_cell_name, {**known_cells, **element_cells})
|
||||
element_cell = _build_nazca_element_cell(nd, cell_name, {**(element or {}), "name": str(element_name)})
|
||||
element_cells[str(element_name)] = element_cell
|
||||
return element_cells
|
||||
|
||||
|
||||
def _build_nazca_element_cell(nd, cell_name: str, element: dict):
|
||||
"""Create one local Port or Anchor cell whose pins are placed in local coordinates."""
|
||||
element = element or {}
|
||||
element_type = str(element.get("type") or "").lower()
|
||||
width = _number(element.get("width"), 0.5)
|
||||
port_number = max(1, _int(element.get("pin_number", element.get("pinNumber", element.get("port_number", element.get("portNumber")))), 1))
|
||||
pitch = _number(element.get("pitch"), 10.0)
|
||||
with nd.Cell(name=cell_name) as element_cell:
|
||||
if element_type == "port":
|
||||
for index, pin_name in enumerate(_element_pin_names(cell_name, element, port_number, "port")):
|
||||
y = 0.0 if port_number == 1 else _element_port_offset(index, port_number, pitch)
|
||||
nd.Pin(pin_name, width=width).put(0.0, y, 180.0)
|
||||
return element_cell
|
||||
|
||||
anchor_pin_names = _element_pin_names(cell_name, element, port_number, "anchor")
|
||||
for index in range(port_number):
|
||||
y = -15.0 + _element_port_offset(index, port_number, pitch)
|
||||
nd.Pin(anchor_pin_names[index * 2], width=width).put(0.0, y, 180.0)
|
||||
nd.Pin(anchor_pin_names[index * 2 + 1], width=width).put(0.0, y, 0.0)
|
||||
return element_cell
|
||||
|
||||
|
||||
def _element_port_offset(index: int, count: int, pitch: float) -> float:
|
||||
"""Return the local y offset for one repeated Port/Anchor pin."""
|
||||
return ((max(1, count) - 1) / 2 - index) * pitch
|
||||
|
||||
|
||||
def _element_pin_names(cell_name: str, element: dict, count: int, element_type: str) -> List[str]:
|
||||
"""Return local element pin names, using YAML overrides when present."""
|
||||
raw_pins = [pin for pin in (element.get("pins") or []) if isinstance(pin, dict)]
|
||||
default_base = _safe_pin_name(str(element.get("name") or cell_name).replace("_port", "").replace("_anchor", ""))
|
||||
roles = []
|
||||
if element_type == "port":
|
||||
roles = [f"io{index + 1}" for index in range(count)]
|
||||
else:
|
||||
for index in range(count):
|
||||
roles.extend([f"a{index + 1}", f"b{index + 1}"])
|
||||
by_role = {str(pin.get("role") or ""): str(pin.get("name") or "") for pin in raw_pins}
|
||||
by_index = [str(pin.get("name") or "") for pin in raw_pins]
|
||||
names = []
|
||||
for index, role in enumerate(roles):
|
||||
name = by_role.get(role) or (by_index[index] if index < len(by_index) else "") or f"{default_base}_{role}"
|
||||
names.append(_safe_pin_name(name))
|
||||
return names
|
||||
|
||||
|
||||
def _safe_pin_name(name: str) -> str:
|
||||
"""Generate a backend-safe pin name for local Nazca element pins."""
|
||||
return "".join(ch if ch.isalnum() or ch == "_" else "_" for ch in str(name or "pin"))
|
||||
|
||||
|
||||
def _safe_cell_name(name: str, existing: dict) -> str:
|
||||
"""Generate a backend-safe unique cell name for GDS/Nazca libraries."""
|
||||
base = "".join(ch if ch.isalnum() or ch in "._$" else "_" for ch in str(name)) or "cell"
|
||||
@@ -238,6 +333,16 @@ def _library_cell_by_name(library, name: str):
|
||||
return None
|
||||
|
||||
|
||||
def _int(value, default=0) -> int:
|
||||
"""Convert integer YAML values with a stable default."""
|
||||
try:
|
||||
if value is None or value == "":
|
||||
return default
|
||||
return int(float(value))
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def _number(value, default=0.0) -> float:
|
||||
"""Convert numeric YAML values to floats with a stable default."""
|
||||
try:
|
||||
|
||||
+18
-1
@@ -107,7 +107,24 @@ class PdkRegistry:
|
||||
if not yaml_path:
|
||||
return None
|
||||
with open(yaml_path, "r", encoding="utf-8") as file:
|
||||
return yaml.safe_load(file) or {}
|
||||
data = yaml.safe_load(file) or {}
|
||||
return self._normalize_pdk_pins(data)
|
||||
|
||||
def _normalize_pdk_pins(self, data: dict) -> dict:
|
||||
"""Treat legacy PDK `ports` metadata as `pins` inside approved PDK roots."""
|
||||
if (
|
||||
self._allows_legacy_ports_as_pins()
|
||||
and isinstance(data, dict)
|
||||
and "pins" not in data
|
||||
and isinstance(data.get("ports"), dict)
|
||||
):
|
||||
data = dict(data)
|
||||
data["pins"] = data["ports"]
|
||||
return data
|
||||
|
||||
def _allows_legacy_ports_as_pins(self) -> bool:
|
||||
normalized = self.public_root.replace("\\", "/").lower()
|
||||
return "/opt_pdk_public/" in f"{normalized}/" or "/opt_pdk_atlas/" in f"{normalized}/"
|
||||
|
||||
def _inside_root(self, path: str) -> bool:
|
||||
"""Check that a candidate asset path remains inside the permitted PDK root."""
|
||||
|
||||
+7
-1
@@ -360,7 +360,13 @@ def readCompYaml(compName, comps_root=None):
|
||||
if ymlFiles:
|
||||
ymlPath = os.path.join(root, ymlFiles[0])
|
||||
with open(ymlPath, 'r', encoding='utf-8') as f:
|
||||
return yaml.safe_load(f)
|
||||
data = yaml.safe_load(f) or {}
|
||||
normalized_root = os.path.abspath(search_root).replace("\\", "/").lower()
|
||||
pdk_ports_allowed = "/opt_pdk_public/" in f"{normalized_root}/" or "/opt_pdk_atlas/" in f"{normalized_root}/"
|
||||
if pdk_ports_allowed and isinstance(data, dict) and "pins" not in data and isinstance(data.get("ports"), dict):
|
||||
data = dict(data)
|
||||
data["pins"] = data["ports"]
|
||||
return data
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user