Skip to content

gdstk backend: close API gaps so the tutorials run - #104

Open
carloscl03 wants to merge 5 commits into
ReaLLMASIC:mainfrom
carloscl03:fix-notebook-backend-imports
Open

gdstk backend: close API gaps so the tutorials run#104
carloscl03 wants to merge 5 commits into
ReaLLMASIC:mainfrom
carloscl03:fix-notebook-backend-imports

Conversation

@carloscl03

Copy link
Copy Markdown
Contributor

Running the tutorial notebooks under GLAYOUT_BACKEND=gdstk fails almost
immediately — 4 of 14 pass. Two causes, one on each side.

The notebooks bypass the backend selection

from gdsfactory import Component        # 10 notebooks

Under the default backend this resolves to the same class as
glayout.backend.Component, so nobody notices. Under gdstk, gdsfactory's
add_ref() receives a gdstk Component and rejects it:

TypeError: type = <class 'type'> needs to be a Component.

Redirected Component, rectangle, rectangular_ring, boolean and
cell to glayout.backend. Symbols it does not re-export
(text_freetype, array) stay on gdsfactory. Imports only — no cell
content touched.

The gdstk backend is missing parts of the surface

Each of these is used by glayout's own cells or by the tutorials:

before after
movex / movey bare delta destination= too, like move() already had
ref.name read-only assignable (ref.name = "pfet_2")
ref.x / ref.y absent centre accessors, next to the existing xmin/xmax
write_gds filename required, no gdsdir both optional, as in gdsfactory
Component.show absent writes a temp GDS, hands it to klive if reachable

ref.name stores the label on the reference rather than renaming the
target cell — renaming the cell would rename every other placement of it.

Result

tutorial notebooks, GLAYOUT_BACKEND=gdstk:   4/14  ->  10/14

The three BJT tutorials still fail on Component indexing and
add_ref(columns=); those need more than a signature and are left alone.

Notebooks under the default gdsfactory backend are unaffected — the
redirected imports resolve to the same objects.

Worth noting for #100: glayout_opamp.ipynb and GLayout_Cells.ipynb,
which currently time out at 180 s under gdsfactory, complete in 89 s and
53 s under gdstk.

@msaligane
msaligane requested a review from AL-255 August 14, 2026 14:34
@msaligane

Copy link
Copy Markdown
Member

Great catch. Thanks!

@msaligane

Copy link
Copy Markdown
Member

Can you please fix the merge conflicts?

@carloscl03
carloscl03 force-pushed the fix-notebook-backend-imports branch 2 times, most recently from 6da8d59 to 2677d02 Compare August 22, 2026 09:17
@carloscl03

Copy link
Copy Markdown
Contributor Author

Rebased onto current main; conflicts resolved and the branch is mergeable again. Commit messages are now in English too.

Two commits were dropped rather than carried over: both touched mimcap(), and #106 rewrote that function with its own CAP_MK and FuseTop handling, so they are superseded. What is left is the four gdstk backend commits the PR title describes.

On the snap-to-grid commit in particular, one measurement that may be worth knowing. The DRC/LVS workflows run the gdsfactory backend, so gdstk is not covered by CI. Building the gf180 cells on gdstk with #102 applied gets them to build, but DRC then reports off-grid geometry:

diff_pair_ibias   3371 violations  [contact_OFFGRID:1752, via1_OFFGRID:600, metal1_OFFGRID:320]
diff_pair          396 violations  [contact_OFFGRID:296, metal2_OFFGRID:28, via2_OFFGRID:20]

That is the class this commit removes: snap_to_grid() took a bare nm: int = 1 default instead of reading the pitch from the active PDK. So #102 and this PR together are what would let the DRC/LVS workflows run on gdstk — #102 to build, this one to be on-grid.

@carloscl03

Copy link
Copy Markdown
Contributor Author

One more thing on the relationship with #102, since this branch is the approved one and will likely land first.

The two conflict in src/glayout/backend/_gdstk.py, and the natural resolution is the wrong one. Both add to ComponentReference:

  1. __init__ — this PR gives the reference its own self.info: dict = {}; Fix order-dependent port renaming; close gdstk backend gaps #102 makes info a property delegating to the parent, because mimcap_array writes info['netlist'] on the Component and opamp_twostage reads it back off the reference returned by <<. Keeping both halves compiles and then fails quietly: the assignment here invokes Fix order-dependent port renaming; close gdstk backend gaps #102's setter, which does self.parent.info = {} and wipes the target's metadata on every placement, netlist included. lvs_netgen then raises KeyError: 'netlist' far from the cause.

    Resolution: keep Fix order-dependent port renaming; close gdstk backend gaps #102's property, drop the self.info = {} line, keep self._name from this PR.

  2. __getitem__ (this PR) vs pprint_ports (Fix order-dependent port renaming; close gdstk backend gaps #102) — distinct methods, keep both.

Verified with that resolution on top of main, together with #100, #103, #112 and #113:

cell result
CapiMagics encoder builds, 0 DRC violations
CapiMagics integrator builds (does not build without #102), 8 violations
our LIF cell builds, Netlists match under klayout LVS

No behaviour change against the tree those were first measured on. Detail left here as well as on #102 so it is visible from whichever side is merged second — I can rebase #102 once this one is in.

@carloscl03

Copy link
Copy Markdown
Contributor Author

Pushed one more commit here: add_ref now takes columns / rows / spacing.

test_bjt_gdsfactory lays its contact rings out with add_ref(reference, columns=, rows=, spacing=). gdsfactory accepts that; this backend did not, so the notebook died on a TypeError partway through. gdstk.Reference has the same notion natively, so the array stays one reference rather than becoming rows*columns of them. Two unit tests come with it.

That was the last gap stopping the tutorial suite from running on gdstk. Measured on this fork's CI, with this PR plus #102 and #113 applied:

gdsfactory   660s   13/14   (GLayout_Cells errors on the 180s per-cell limit at 187.8s)
gdstk        160s   13/14   (only test_bjt_gdsfactory fails — the add_ref gap)
gdstk + this 158s   14/14

GLayout_Cells goes from timing out at 187.8s to passing in 14.2s. #116 has the full table and the run links; it is the one-line workflow change, and it stays red until this PR, #102 and #113 are all in.

This commit touches only the backend, so it does not change what this PR's own CI exercises — the notebooks job here runs gdsfactory either way.

msaligane pushed a commit that referenced this pull request Aug 25, 2026
Two notebooks sit on the 180s per-cell limit and cross it depending on
which runner the job lands on, so the check passes or fails on identical
content. `lvs-pin-filter` has four green runs and two red ones.

    GLayout_Cells   290s pass (#112) / 372s error (#113)
    glayout_opamp   136s pass (#112) / 198s error (#113)

The native backend builds these cells several times faster, which moves
them off the limit instead of raising it. Measured on this fork's CI --
same container, same runners -- with #102, #104 and #113 applied on both
sides, the only difference being this line:

                              gdsfactory        gdstk
    GLayout_Cells          187.8s  ERROR      14.2s  pass
    glayout_opamp          126.4s  pass       19.2s  pass
    5T_OTA_part2            90.8s  pass       16.2s  pass
    test_bjt_custom_pattern 68.2s  pass       11.4s  pass
    whole suite            660s 13/14        158s 14/14

Note the baseline already carries all three PRs and still fails: they are
what lets the notebooks run on gdstk at all, not what fixes the timeouts.

Depends on #102, #104 and #113. Without #113 `GLayout_Cells` raises in
`mimcap.py`; without #102 it raises in `Component.add`; without #104's
`add_ref(columns=)` the BJT notebook raises. This check stays red until
all three land.
Running the tutorial notebooks under GLAYOUT_BACKEND=gdstk fails almost
immediately: 4 of 14 pass. Two reasons, one on each side.

The notebooks import Component, rectangle, boolean and cell straight from
gdsfactory instead of glayout.backend, so they bypass the backend
selection entirely. Under the default backend both names resolve to the
same class and nobody notices; under gdstk, gdsfactory's add_ref() gets a
gdstk Component and rejects it. Symbols glayout.backend does not re-export
(text_freetype, array) are left on gdsfactory.

The gdstk ComponentReference/Component are also missing pieces of the
gdsfactory surface that glayout's own cells and the tutorials use:

  movex/movey     took a bare delta; move() already accepted destination=
  ref.name        read-only, but cells label placements (ref.name = "pfet_2")
  ref.x / ref.y   had xmin/xmax/ymin/ymax but not the centre accessors
  write_gds       required a filename and ignored gdsdir=
  Component.show  absent

ref.name stores the label on the reference rather than renaming the target
cell, which would rename every other placement of it too.

Tutorial notebooks under gdstk: 4/14 -> 10/14. The three BJT tutorials
still fail (Component indexing, add_ref(columns=)) and are untouched here.
Notebooks under the default gdsfactory backend are unaffected: the imports
resolve to the same objects.
Every vertex of a gdstk-generated layout lands off-grid on gf180. The DRC
reports it on all of them -- 1301 violations on the LIF cell used to check
this, split across contact_OFFGRID x348, via1_OFFGRID x276,
metal1_OFFGRID x252, metal2_OFFGRID x132 and comp_OFFGRID x60.

snap_to_grid() took a bare `nm: int = 1` default. gdsfactory's version reads
the pitch from the active PDK instead:

    nm = int(get_grid_size() * 1000 * grid_factor)

which is 5 nm on gf180. Rounding a 5 nm process to 1 nm produces values like
10.246 where the process wants 10.245, and every one of them is a violation.

Two pieces were missing. Pdk.activate() was a no-op, so nothing recorded
which PDK was active; and grid_size kept the class default of 0.001 because
gdsfactory used to fill it in from its own PDK database on activate. The
real pitch is already in gds_write_settings.precision (5e-9 m on both gf180
and sky130), so activate() now derives grid_size from it and registers the
PDK for snap_to_grid to read. `nm=` still overrides when a caller wants a
specific pitch.

After: 0 of 1228 vertices off-grid, same as the gdsfactory backend, and the
1301 OFFGRID violations are gone. snap_to_2xgrid(10.2463) returns 10.25 on
both backends now. DRC on diff_pair, current_mirror_nfet and
transmission_gate under gdsfactory is unchanged.
move(destination=) translated relative to the bbox centre instead of (0,0),
which is gdsfactory's default. c_route places its extension rectangles with
move(destination=...) followed by relative movex, so the route shifted by
half a rectangle: the LIF neuron came out 32.4 um wide instead of 29.9, with
54 extra M2.2a violations.

get_ports_list returned dict order; gdsfactory sorts clockwise (west, north,
east, south). Cells look ports up by substring, so the order changes which
port gets routed.

With both, neurona.ipynb gives 1168.6 um2 and 31 violations on either
backend, identical to gdsfactory.
Two gaps that show up running the team's primitives notebook.

add_ports iterated its argument assuming a sequence of Port, but gdsfactory
also accepts a name->port mapping and glayout passes ref.ports straight in.
Iterating that yields the names, so resistor() died with "'str' object has
no attribute 'name'".

And __getitem__ was missing. bjt indexes the component to read a port's
width, and without it the error is "'Component' object is not
subscriptable", which says nothing about the port it was after. Added on
Component and ComponentReference, with the KeyError listing the ports that
do exist.

With these, resistor, pnp and npn build on the gdstk backend.
`test_bjt_gdsfactory` lays its contact rings out with
`add_ref(reference, columns=, rows=, spacing=)`, which gdsfactory accepts
and this backend did not, so the notebook died on a TypeError partway
through. gdstk.Reference has the same notion natively, so the array stays
one reference rather than becoming rows*columns of them.

Verified in CI on the fork: with this, the tutorial suite runs 14/14 on
gdstk. Without it, 13/14 -- that notebook the only failure.
@carloscl03
carloscl03 force-pushed the fix-notebook-backend-imports branch from 5ebee1d to e6144e0 Compare August 25, 2026 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants