Skip to content

feat(gf180mcu): add logical and geometric electrical pins for SPICE export - #156

Open
das-dias wants to merge 36 commits into
mainfrom
dev/add-logical-pins-gf180mcu
Open

feat(gf180mcu): add logical and geometric electrical pins for SPICE export#156
das-dias wants to merge 36 commits into
mainfrom
dev/add-logical-pins-gf180mcu

Conversation

@das-dias

@das-dias das-dias commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Closes doplaydo/pdks#45

Summary

  • Add add_electric_pins() to all PCells with electrical ports in gf180mcu
  • Define pin layers and add both geometric and logical pins
  • New gf180mcu/cells/_common.py with shared _add_pins partial

Test plan

  • tests/test_electrical_pins.py passes
  • Existing test suite passes without regression

Summary by Sourcery

Add shared electrical pin helper for GF180MCU PCells and wire it into diode, capacitor, resistor, guardring, and via cells, while updating tests and infrastructure to accommodate logical pins and pin layers.

New Features:

  • Introduce a common _add_pins helper that registers logical electrical pins on GF180MCU PCells.
  • Add electrical ports and logical pins to diode, capacitor, resistor, guardring, and via generator/stack cells for SPICE-oriented usage.
  • Add a new electrical pins test suite to validate pin presence, grouping, and naming across key PCells.

Bug Fixes:

  • Ensure XOR-based layout comparison tests ignore pin and block datatypes and pin label layers to avoid spurious failures.

Enhancements:

  • Extend the GF180MCU layer map with dedicated metal pin layers for all metal levels.
  • Adjust component tests and YAML references to account for newly defined electrical ports and pin metadata.
  • Update development tooling to run pre-commit clean and bump the gdsfactory dependency to a newer compatible version.

CI:

  • Add a reusable GitHub Actions workflow for building and optionally releasing a generated PDF artifact.

das-dias added 3 commits July 28, 2026 22:40
…to PCells

- Add pin layer definitions to layers.py (datatype 2, unused in this PDK):
  metal1_pin=(34,2), metal2_pin=(36,2), metal3_pin=(42,2),
  metal4_pin=(46,2), metal5_pin=(81,2), metaltop_pin=(53,2)
- Add cells/_common.py with shared _add_pins helper that draws geometric pin
  rectangles (add_pin_rectangle_inside) on the correct pin layer using a
  metal drawing→pin layer map (metal1→metal1_pin, etc.) and registers
  logical DPins (create_pin) for all electrical ports, grouped by port name
- Apply _add_pins(c) in all @gf.cell layout functions: cap_mim, cap_mos,
  diode (nd2ps, pd2nw), res, via_generator, via_stack, pcmpgr_gen (guardring)
- Add tests/test_electrical_pins.py: 23 tests passing (1 skip: via_generator
  contact layer has no metal pin layer, by design)
…pins

- Replace local _add_pins implementation with gdsfactory.add_electric_pins
- Fixes per-port layer lookup bug (was using ports[0].layer for entire group)
- Fix XOR test to skip pin layers (datatype 2) in addition to label layers

Pin layers (datatype 2) are LVS metadata, not physical geometry. Reference
GDS files from Magic do not contain pin geometry, so XOR tests skip these.

**DEPENDS ON:** gdsfactory PR with add_electric_pins() function
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai

sourcery-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds shared electrical pin infrastructure for GF180MCU PCells, wires key cells into it for SPICE-friendly export, adjusts tests/XOR comparison to tolerate new pin layers, and updates CI/dev workflows and dependencies.

File-Level Changes

Change Details Files
Introduce shared helper for adding logical (and optionally geometric) electrical pins and wire GF180MCU cells into it.
  • Add new gf180mcu/cells/_common.py module with _add_pins and _add_electric_pins that group electrical ports by name, draw pin markers on PDK-specific pin/label layers, and register logical pins via create_pin().
  • Update diode, capacitor, resistor, via, and guardring PCells to import _add_pins and call it with explicit pin-to-port mappings after defining their electrical ports.
  • Ensure logical pin names for each cell match the expected electrical topology (e.g., anode/cathode, top/bottom, r0/r1, e, guardring).
  • Export _common symbols in gf180mcu/cells/init.py so pin helpers are part of the public cells namespace.
gf180mcu/cells/_common.py
gf180mcu/cells/diode.py
gf180mcu/cells/cap_mim.py
gf180mcu/cells/cap_mos.py
gf180mcu/cells/res.py
gf180mcu/cells/via_generator.py
gf180mcu/cells/guardring.py
gf180mcu/cells/__init__.py
Define dedicated pin layers in the PDK layer map and adjust XOR/diff tests to ignore pin and block datatypes while still checking layout geometry.
  • Extend LAYER map with metal*_pin layers for all metal routing layers and metaltop_pin for the top metal.
  • Add SKIP_DATATYPES (including pin/datatype 2 and block/datatype 10) in tests/test_xor.py to avoid failing XOR comparisons on pin or block layers.
  • Introduce a GDS-stripping helper in tests/conftest.py that deletes layers with SKIP_DATATYPES from temporary GDS copies before performing filecmp-based equality checks.
  • Default ignore_sliver_differences to True in difftest to reduce noise from minor sliver variations.
gf180mcu/layers.py
tests/test_xor.py
tests/conftest.py
Ensure PCells expose electrical ports with fully specified metadata and add a dedicated test suite for logical pins and port types.
  • Add electrical ports (anode, cathode, etc.) on explicit metal1 layers in several diode variants and the sc_diode, driving their centers, widths, orientation, and port_type attributes.
  • Populate ports sections in diode-related PDK settings YAMLs to reflect new electrical ports and their attributes.
  • Add tests/test_electrical_pins.py to validate that each target GF180MCU cell has registered logical pins, expected pin names, and consistent electrical port types (geometric pin tests currently skipped pending reference GDS update).
  • Exclude via_generator and via_stack from the generic GDS ref regression suite because their ports/pins are now validated by dedicated tests.
gf180mcu/cells/diode.py
tests/test_components/test_pdk_settings_diode_nw2ps_.yml
tests/test_components/test_pdk_settings_diode_pw2dw_.yml
tests/test_components/test_pdk_settings_sc_diode_.yml
tests/test_components/test_pdk_settings_diode_dw2ps_.yml
tests/test_electrical_pins.py
tests/test_components.py
Align via-related PCells and their tests with the new pin helper and relax strict layer expectations in PDK settings YAML.
  • Modify via_generator and via_stack to call _add_pins with a single logical pin name mapping to their two electrical ports.
  • Update via-related PDK settings YAMLs to drop explicit layer assertions for via ports, focusing instead on positions, orientation, and port_type.
  • Add expected pin names for via_generator and via_stack to the electrical pin tests so logical pins are exercised.
gf180mcu/cells/via_generator.py
tests/test_components/test_pdk_settings_via_generator_.yml
tests/test_components/test_pdk_settings_via_stack_.yml
tests/test_electrical_pins.py
Update development tooling, CI workflows, and dependency tracking to match the shared pdk-ci-workflow and modern Python tooling.
  • Bump gdsfactory dependency from 9.45.0 to 9.47.0 in pyproject.toml to pick up newer pin APIs and behavior.
  • Modify the Makefile dev target to clean pre-commit caches after fetching the shared pre-commit config via curl and before installing hooks.
  • Add a new GitHub Actions workflow to build (and optionally upload) a PDF artifact for releases by reusing the doplaydo/pdk-ci-workflow-public build-pdf workflow.
  • Add uv.lock to track uv-managed dependencies and remove legacy auto-label and release-drafter workflows/configuration that are superseded by the shared CI workflow.
pyproject.toml
Makefile
.github/workflows/build-pdf.yml
uv.lock
.github/release-drafter.yml
.github/workflows/auto-label-pdk.yml
.github/workflows/release-drafter.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@das-dias das-dias self-assigned this Jul 29, 2026
@das-dias
das-dias marked this pull request as ready for review July 29, 2026 20:18
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@das-dias
das-dias marked this pull request as draft July 29, 2026 20:19

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The hardcoded numeric tuples in _PIN_LAYER_MAP in test_electrical_pins.py duplicate the layer information in gf180mcu.layers; consider deriving the pin-layer mapping from LAYER/layer to avoid drift if the layer map changes.
  • Several diodes (diode_nw2ps, diode_pw2dw, diode_dw2ps) call _add_pins but expose no electrical ports and are only mentioned in a comment as a known bug; it would be clearer to either fix their port definitions in this PR or mark them with an explicit TODO in the cell implementations rather than just in the test comment.
  • The release workflow is now fully driven via workflow_dispatch and the shared cut-release workflow instead of tag pushes; if tag-based releases are still expected in your process, consider documenting or asserting the new entrypoint to avoid accidental changes to the release flow.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The hardcoded numeric tuples in `_PIN_LAYER_MAP` in `test_electrical_pins.py` duplicate the layer information in `gf180mcu.layers`; consider deriving the pin-layer mapping from `LAYER`/`layer` to avoid drift if the layer map changes.
- Several diodes (`diode_nw2ps`, `diode_pw2dw`, `diode_dw2ps`) call `_add_pins` but expose no electrical ports and are only mentioned in a comment as a known bug; it would be clearer to either fix their port definitions in this PR or mark them with an explicit TODO in the cell implementations rather than just in the test comment.
- The release workflow is now fully driven via `workflow_dispatch` and the shared `cut-release` workflow instead of tag pushes; if tag-based releases are still expected in your process, consider documenting or asserting the new entrypoint to avoid accidental changes to the release flow.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

das-dias added 14 commits July 29, 2026 22:35
test_pdk_gds was calling remove_layers() directly on the cached kfactory
cell, poisoning the cache for subsequent tests. test_geometric_pin_present
then retrieved the same (now pin-layer-stripped) object and failed. Using
dup() creates an independent copy so the cache is never mutated.
gdsfactoryplus is dev-only and not a core PDK dependency; the test_gfp
jobs in the reusable workflow break when it isn't in the base export.
Inline pre-commit and test_code directly to skip those jobs for now.
Set all _METAL_PIN_LAYERS values to None in _common.py so add_electric_pins
registers logical electrical ports without drawing any pin polygon geometry.
This aligns gf180mcu with every other PDK in the repo (logical-only mode).

Consequences:
- test_geometric_pin_present skipped with explicit reason message
- test_pdk_gds no longer needs dup()+remove_layers(); reverted to simple difftest
- _PIN_LAYERS list removed from test_components.py
- gds_run snapshots updated to reflect cells without pin layer polygons
…ER names

Replace hardcoded (layer, datatype) tuples with symbolic LAYER.metalX names
from gf180mcu.layers. The dict comprehension maps each electrical drawing
layer to None via kcl.layer(*s), aligning with the uniform Pattern B used
across all PDKs in the repo.
…ss diodes

- Add missing imports (AddPinFunction, add_pin_rectangle_inside) to _common.py
- Complete pin_label_layer_map for all metal layers (metal1-metal5 + metaltop)
- Aggregate e1+e2 ports into single "e" pin for via_generator and via_stack
- Add explicit port_pin_mapping to cap_mim, cap_mos, res, pcmpgr_gen, diode_nd2ps, diode_pd2nw
- Add anode/cathode ports to diode_nw2ps, diode_pw2dw, diode_dw2ps, sc_diode
- Expand test suite: add new diode cells to CELL_NAMES, add test_expected_pin_names
@das-dias

Copy link
Copy Markdown
Collaborator Author

Test code / test / test_gfp_no_dev (pull_request)
Test code / test / test_gfp_no_dev (pull_request)Failing after 15s - This makes no sense in open source PDKs. CI workflow needs to change to account for this in public CI workflows.

@das-dias
das-dias marked this pull request as ready for review July 30, 2026 23:11
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@das-dias das-dias added pdk PDK issue pdk:gf180mcu PDK this issue belongs to refactor labels Jul 30, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • Avoid from ._common import * in gf180mcu/cells/__init__.py and instead explicitly re-export the functions you want in the public API to prevent namespace pollution and accidental symbol exposure.
  • The pin/datatype skipping logic is duplicated between tests/conftest.py (SKIP_DATATYPES) and tests/test_xor.py; consider centralizing these layer/datatype constants in a single module or fixture to keep the XOR and file-compare behavior consistent and easier to maintain.
  • In _strip_pin_label_layers you create a NamedTemporaryFile without closing it; using a context manager and explicitly closing the file before writing via KLayout (or switching to TemporaryDirectory/mkstemp) would avoid leaving open file descriptors around.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Avoid `from ._common import *` in `gf180mcu/cells/__init__.py` and instead explicitly re-export the functions you want in the public API to prevent namespace pollution and accidental symbol exposure.
- The pin/datatype skipping logic is duplicated between `tests/conftest.py` (`SKIP_DATATYPES`) and `tests/test_xor.py`; consider centralizing these layer/datatype constants in a single module or fixture to keep the XOR and file-compare behavior consistent and easier to maintain.
- In `_strip_pin_label_layers` you create a `NamedTemporaryFile` without closing it; using a context manager and explicitly closing the file before writing via KLayout (or switching to `TemporaryDirectory`/`mkstemp`) would avoid leaving open file descriptors around.

## Individual Comments

### Comment 1
<location path="gf180mcu/cells/_common.py" line_range="69-82" />
<code_context>
+        pin_function: Function to draw each pin marker.
+        pin_type: Pin type string passed to create_pin().
+    """
+    if port_pin_mapping is not None:
+        by_name: dict[str, list] = {
+            pin_name: [component.ports[pn] for pn in port_names]
+            for pin_name, port_names in port_pin_mapping.items()
+        }
+    else:
+        by_name: dict[str, list] = defaultdict(list)
+        [
+            by_name[port.name].append(port)
+            for port in component.ports
</code_context>
<issue_to_address>
**suggestion:** Using a list comprehension purely for side effects harms readability and creates an unnecessary temporary list.

In the `else` branch, the comprehension is used only for its side effects and its result is discarded, which both hurts readability and allocates an unnecessary list. Please rewrite this as a simple loop:

```python
by_name: dict[str, list] = defaultdict(list)
for port in component.ports:
    if port.port_type == "electrical":
        by_name[port.name].append(port)
```
This preserves behavior while making the intent clearer and avoiding the extra allocation.

```suggestion
    if port_pin_mapping is not None:
        by_name: dict[str, list] = {
            pin_name: [component.ports[pn] for pn in port_names]
            for pin_name, port_names in port_pin_mapping.items()
        }
    else:
        by_name: dict[str, list] = defaultdict(list)
        for port in component.ports:
            if port.port_type == "electrical":
                by_name[port.name].append(port)

    for name, ports in by_name.items():
```
</issue_to_address>

### Comment 2
<location path="gf180mcu/cells/_common.py" line_range="32-40" />
<code_context>
+
+
+# TODO: replace gdsfactory.add_pins:add_electric_pins in next gdsfactory release
+def _add_electric_pins(
+    component: Component,
+    port_pin_mapping: dict[str, list[str]] | None = None,
+    pin_layer_map: dict[LayerSpec, LayerSpec] | None = None,
+    pin_label_layer_map: dict[LayerSpec, LayerSpec] | None = None,
+    default_pin_layer: LayerSpec | None = None,
+    default_label_layer: LayerSpec | None = None,
+    pin_function: AddPinFunction = add_pin_rectangle_inside,  # type: ignore[assignment]
+    pin_type: str = "DC",
+) -> None:
+    """Draw pin markers and register logical pins for all electrical ports.
</code_context>
<issue_to_address>
**suggestion:** The function signature could be tightened to use the existing `Component` type and improve type safety.

`Component` is already imported but `_add_pins` still uses an untyped `component` parameter. Annotating it as `Component` (to match `_add_electric_pins`) will improve type safety and make incorrect usage easier to detect:

```python
def _add_pins(component: Component, port_pin_mapping: dict[str, list[str]] | None = None) -> None:
    ...
```

Suggested implementation:

```python
def _add_pins(
    component: Component,
    port_pin_mapping: dict[str, list[str]] | None = None,
) -> None:

```

If `_add_pins` currently has a different parameter list or formatting (e.g. more parameters, different line breaks), adjust the SEARCH block to exactly match the existing `def _add_pins(...)` line(s), and then apply the same `component: Component` annotation in the REPLACE block. No other changes are required as `Component` is already imported in this module.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread gf180mcu/cells/_common.py
Comment thread gf180mcu/cells/_common.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pdk:gf180mcu PDK this issue belongs to pdk PDK issue refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants