diff --git a/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py b/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py index f623d898..9f2ade83 100644 --- a/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py +++ b/src/glayout/cells/composite/differential_to_single_ended_converter/differential_to_single_ended_converter.py @@ -196,22 +196,29 @@ def differential_to_single_ended_converter_netlist(pdk: MappedPDK, half_pload: t return Netlist( circuit_name="DIFF_TO_SINGLE", nodes=['VIN', 'VOUT', 'VSS', 'VSS2'], - source_netlist=""".subckt {circuit_name} {nodes} """ + f'l={half_pload[1]} w={half_pload[0]} mt={4*2} mb={2 * half_pload[2]} ' + """ -XTOP1 VOUT VIN VSS VSS {model} l={{l}} w={{w}} m={{mt}} -XTOP2 VSS2 VIN VSS VSS {model} l={{l}} w={{w}} m={{mt}} -XBOT1 VIN VIN VOUT VSS {model} l={{l}} w={{w}} m={{mb}} -XBOT2 VOUT VIN VSS2 VSS {model} l={{l}} w={{w}} m={{mb}} -XDUMMY1 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY2 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY3 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY4 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY5 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY6 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY7 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY8 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY9 VSS VSS VSS VSS {model} l={{l}} w={{w}} -XDUMMY10 VSS VSS VSS VSS {model} l={{l}} w={{w}} -.ends {circuit_name}""", + # No parameters on the top-level .subckt line. KLayout's SPICE reader folds a + # subcircuit's defaults into its NAME -- `l=1.0 w=3.0 mt=8 mb=4` made this + # circuit `DIFF_TO_SINGLE(L=1.0,MT=8,MB=4,W=3.0)` -- so the gf180 deck could + # not match it to the layout's top cell and aborted with "Can't find a + # schematic counterpart for the top cell", which the runner reported as the + # catch-all "LVS inconclusive". Unlike two_transistor_interdigitized the body + # here referenced {l}/{w}/{mt}/{mb}, so those are inlined below. + source_netlist=""".subckt {circuit_name} {nodes}""" + f""" +XTOP1 VOUT VIN VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} m={4*2} +XTOP2 VSS2 VIN VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} m={4*2} +XBOT1 VIN VIN VOUT VSS {{model}} l={half_pload[1]} w={half_pload[0]} m={2*half_pload[2]} +XBOT2 VOUT VIN VSS2 VSS {{model}} l={half_pload[1]} w={half_pload[0]} m={2*half_pload[2]} +XDUMMY1 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY2 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY3 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY4 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY5 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY6 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY7 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY8 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY9 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +XDUMMY10 VSS VSS VSS VSS {{model}} l={half_pload[1]} w={half_pload[0]} +.ends {{circuit_name}}""", instance_format="X{name} {nodes} {circuit_name} l={length} w={width} mt={mult_top} mb={mult_bot}", parameters={ 'model': pdk.models['pfet'], diff --git a/src/glayout/placement/two_transistor_interdigitized.py b/src/glayout/placement/two_transistor_interdigitized.py index 0686df73..e35d5f3f 100644 --- a/src/glayout/placement/two_transistor_interdigitized.py +++ b/src/glayout/placement/two_transistor_interdigitized.py @@ -93,7 +93,14 @@ def two_tran_interdigitized_netlist( # X-prefix is sky130's magic+netgen tech expectation; klayout decks # that classify primitive MOSFETs by SPICE prefix get their netlist # X→M-rewritten by the LVS runner. - source_netlist = """.subckt {circuit_name} {nodes} """ + f'l={length} w={width} m={1} '+ f""" + # No parameters on the top-level .subckt line. KLayout's SPICE reader folds a + # subcircuit's default parameters into its *name* -- `l=0.28u w=3u m=1` turns the + # circuit into `TWO_TRANSISTOR_INTERDIGITIZED(L=0.28U,M=1,W=3U)` -- so the gf180 + # klayout deck can no longer match it to the layout's top cell and aborts with + # "Can't find a schematic counterpart for the top cell". The device lines below + # carry l/w/m explicitly, so these defaults were never used. sky130 is unaffected + # (magic+netgen), which is why this only ever showed up on gf180. + source_netlist = """.subckt {circuit_name} {nodes}""" + f""" XA VDD1 VG1 VSS1 VB {model} l={length} w={width} m={mtop} XB VDD2 VG2 VSS2 VB {model} l={length} w={width} m={mtop}""" if with_dummy: diff --git a/tests/lvs/klayout_gf180.py b/tests/lvs/klayout_gf180.py index bb5bbebd..2150f01f 100644 --- a/tests/lvs/klayout_gf180.py +++ b/tests/lvs/klayout_gf180.py @@ -301,7 +301,12 @@ def run_lvs_klayout_gf180( FuseTop / met3) or "B" (met4 / FuseTop / met5). The two are mutually exclusive at process level, so the wrong one extracts no capacitor at all and every MIM shows up as missing from the layout. Defaults to - ``$GF180_MIM_OPTION``, then to "A". + ``$GF180_MIM_OPTION``, then to "B" -- which is what glayout actually builds: + both ``mimcap`` and ``mimcap_array`` default to ``option="B"`` + (primitives/mimcap.py). This used to default to "A", so every gf180 cell with + a MIM extracted zero capacitors and LVS reported "N schematic device(s) + missing from layout [CAP_MIM_2F0FF]". Set $GF180_MIM_OPTION=A for a layout + genuinely built on the met2/met3 stack. """ layout_path = Path(layout) netlist_path = Path(netlist) @@ -310,7 +315,7 @@ def run_lvs_klayout_gf180( rpt_dir.mkdir(parents=True, exist_ok=True) pdk_root = pdk_root or os.environ.get("PDK_ROOT", "/foss/pdks") - mim_option = (mim_option or os.environ.get("GF180_MIM_OPTION") or "A").upper() + mim_option = (mim_option or os.environ.get("GF180_MIM_OPTION") or "B").upper() if mim_option not in ("A", "B"): raise ValueError(f"mim_option must be 'A' or 'B', got {mim_option!r}") deck_dir = _resolve_deck_dir(pdk_root) diff --git a/tests/lvs/run_cell_lvs.py b/tests/lvs/run_cell_lvs.py index 238cad83..c74a17a1 100644 --- a/tests/lvs/run_cell_lvs.py +++ b/tests/lvs/run_cell_lvs.py @@ -227,8 +227,9 @@ def main() -> int: "gf180 MIM stack the deck should extract: A (met2/FuseTop/met3) " "or B (met4/FuseTop/met5). They are mutually exclusive at process " "level, so picking the wrong one extracts no capacitor and every " - "MIM reports as missing from the layout. Default: $GF180_MIM_OPTION, " - "else A." + "MIM reports as missing from the layout. glayout builds option B " + "(mimcap/mimcap_array default to option=\"B\"), so that is the " + "default here too. Default: $GF180_MIM_OPTION, else B." ), ) parser.add_argument( diff --git a/tests/run_ci_locally.py b/tests/run_ci_locally.py index ae8d8ca2..dc56c57a 100755 --- a/tests/run_ci_locally.py +++ b/tests/run_ci_locally.py @@ -52,10 +52,12 @@ uv python install 3.10 >/dev/null 2>&1 PYTHON310="$(uv python find 3.10)" -if [ ! -x /work/.drc-cache/venv/bin/python ]; then +if [ ! -f /work/.drc-cache/venv/.installed ]; then + rm -rf /work/.drc-cache/venv "$PYTHON310" -m venv /work/.drc-cache/venv . /work/.drc-cache/venv/bin/activate - uv pip install -e . >/dev/null + uv pip install -e . + touch /work/.drc-cache/venv/.installed else # Cache hit: glayout's .pth points to /work which is the same on every # run, so no refresh is needed (matches the workflows).