Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 79 additions & 13 deletions tests/lvs/klayout_gf180.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,34 @@ def _classify_log(log: str) -> Dict[str, Any]:
return {"is_pass": False, "conclusion": "LVS inconclusive"}


# La rama de la opcion A de mimcap_connections.lvs conecta el plato de abajo a
# `metal2` en vez de a `metal2_con` -- que es la capa que forma el grafo de
# conectividad -- y le falta el salto de via2_cap a metal3_con. La opcion B, al
# lado, si tiene los tres connect. Sin arreglarlo las dos placas del MIM quedan
# sueltas: en el opamp son 10 mismatches de 19.
_MIM_A_ROTO = """ connect(metal2, mim_virtual)
connect(fuse_cap, via2_cap)"""
_MIM_A_SANO = """ connect(metal2_con, mim_virtual)
connect(fuse_cap, via2_cap)
connect(via2_cap, metal3_con)"""


def _deck_con_opcion_a_arreglada(deck: Path, destino: Path) -> Path:
"""Copia el deck del PDK y arregla su rama de la opcion A del MIM.

Devuelve el deck original si ya viene corregido o si no se reconoce el
texto, para que una version distinta del PDK no rompa la corrida.
"""
conexiones = deck.parent / "rule_decks" / "mimcap_connections.lvs"
if not conexiones.is_file() or _MIM_A_ROTO not in conexiones.read_text():
return deck

shutil.copytree(deck.parent, destino, dirs_exist_ok=True)
parcheado = destino / "rule_decks" / "mimcap_connections.lvs"
parcheado.write_text(parcheado.read_text().replace(_MIM_A_ROTO, _MIM_A_SANO))
return destino / deck.name


def run_lvs_klayout_gf180(
layout: str,
design_name: str,
Expand Down Expand Up @@ -205,19 +233,57 @@ def run_lvs_klayout_gf180(
spice_staged = _stage_inputs(tmpdir, design_name, layout_path, netlist_path)
sub_name = _detect_substrate_name(spice_staged, design_name)

cmd = [
"python3", str(run_lvs),
f"--layout={layout_path}",
f"--netlist={spice_staged}",
"--variant=D",
f"--topcell={design_name}",
"--run_mode=flat",
"--combine",
"--schematic_simplify",
"--top_lvl_pins",
f"--lvs_sub={sub_name}",
f"--run_dir={tmpdir}",
]
# Se llama al deck directamente en vez de a run_lvs.py, que solo acepta
# cuatro variantes predefinidas y ninguna sirve aqui:
#
# A -> mim_option=A metal_level=3LM
# B -> mim_option=B metal_level=4LM
# C -> mim_option=B metal_level=5LM
# D -> mim_option=B metal_level=5LM
#
# glayout dibuja el MIM en opcion A (met2 / FuseTop / met3) y rutea
# hasta met5. Esa combinacion es un proceso real -- el 1P5M (TM 6KA
# with MIM) del DRM la documenta -- pero no esta entre las variantes.
# Con D el extractor busca el MIM entre metal4 y metal5 y encuentra
# uno de los seis condensadores del opamp; con A pierde todo el ruteo
# por encima de met3. El deck si acepta las opciones sueltas.
# GF180_LVS_DECK permite apuntar a un deck corregido. El del PDK tiene
# rota la rama de la opcion A:
# connect(metal2, mim_virtual) <- deberia ser metal2_con, que
# es lo que usa el grafo
# falta connect(via2_cap, metal3_con)
# Sin las dos, la placa inferior del MIM no se une al resto de met2 y
# la superior no llega a met3. En el opamp eso son 10 mismatches: los
# seis condensadores sin emparejar y el nodo que los cuelga.
lvs_deck = Path(os.environ.get("GF180_LVS_DECK")
or _deck_con_opcion_a_arreglada(run_lvs.parent / "gf180mcu.lvs",
tmpdir / "deck"))
sws = {
"input": str(layout_path),
"schematic": str(spice_staged),
"topcell": design_name,
"target_netlist": str(tmpdir / f"{design_name}.cir"),
"report": str(tmpdir / f"{design_name}.lvsdb"),
"mim_option": "A",
"metal_level": "5LM",
"metal_top": "11K",
"poly_res": "1k",
"mim_cap": "2",
"run_mode": "flat",
"combine": "true",
"schematic_simplify": "true",
"top_lvl_pins": "true",
# El deck lo trae en false, asi que el extraido sale con numeros de
# red en vez de nombres y las etiquetas del GDS no llegan al
# informe. run_lvs.py lo pone en true salvo que se pida lo
# contrario; aqui hace falta lo mismo.
"spice_net_names": "true",
"lvs_sub": sub_name,
"thr": "2",
}
cmd = ["klayout", "-b", "-r", str(lvs_deck)]
for k, v in sws.items():
cmd += ["-rd", f"{k}={v}"]
proc = subprocess.run(cmd, cwd=tmpdir, capture_output=True, text=True)

# Even on klayout-exit-nonzero we want the log preserved for triage.
Expand Down
8 changes: 6 additions & 2 deletions tests/lvs/run_cell_lvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,16 @@ def main() -> int:
)
parser.add_argument(
"--skip-cells",
default="differential_to_single_ended_converter",
default="differential_to_single_ended_converter,nmos_narrow,pmos_narrow",
help=(
"Comma-separated cell names to skip when --cells is not specified. "
"Default skips differential_to_single_ended_converter (Magic mis-extracts "
"its PMOS bulk; the cell can still be tested by passing --cells "
"differential_to_single_ended_converter)."
"differential_to_single_ended_converter) and nmos_narrow/pmos_narrow, "
"which are DRC regression cells for the dogbone diffusion: they are bare "
"primitives, their reference subckt is named after the device rather than "
"the cell, and LVS aborts with 'no schematic counterpart' instead of "
"reporting anything useful."
),
)
parser.add_argument(
Expand Down