Net Missing in Schematic¶
File: pics/metal_polys_scm.pic.yml
Error caught: A connection between two ports or instances exists in the layout but is not declared in the input netlist.
Error type: LVS.net.missing_in_schematic (plus the LVS.short between each pad and the rectangle — the underlying cause)
Expected: ok=False, error_count=3 (one merged net error + two shorts)
import tempfile
from pathlib import Path
import elvis
import gdsfactory as gf
import yaml
from IPython.display import Markdown
from kwasm import Tool, show
gf.gpdk.PDK.activate()
PICS = Path("../pics")
BUILD_GDS = Path("../build/gds")
PIC = PICS / "metal_polys_scm.pic.yml"
BUILD_GDS.mkdir(parents=True, exist_ok=True)
Layout¶
Two pads 1000 µm apart, with a rectangle instance (with port_type=None, i.e. no ports) placed across both of them on layer M3. Because the rectangle has no ports, gdsfactory can't infer any nets from it — but it physically ties the pads together through geometric overlap. The schematic returned by c.get_netlist() therefore declares no connection between the pads, while the layout has one, so every port-pair the rectangle shorts shows up as a missing net.
c = gf.Component()
c.name = "top"
c.add_ref(gf.c.pad(), name="pad1")
c.add_ref(gf.c.pad(), name="pad2").move((1000, 1))
# c.add_polygon([(0, 0), (1000, 0), (1000, 10), (0, 10)], layer="M3")
c.add_ref(gf.c.rectangle((1000, 10), layer="M3", port_type=None))
gds_path = BUILD_GDS / "metal_polys_py.gds"
c.write_gds(gds_path)
show(gds_path, tools=[Tool.FIT_ALL])
Caution
Electrical layout engineers like hand-drawing polygons. If that's something you like, that's fine they're pretty easily handled by creating your reference netlist manually as you'll see below. Be sure to NOT use polygon instances like rectangle for manual routing though. Why? See the caveats below.
LVS Results¶
schematic = c.get_netlist(recursive=True)
rdb = elvis.lvs_rdb(
gds_path,
schematic,
short_layers=[(49, 0)],
equivalent_ports={"pad": ["pad", "e1", "e2", "e3", "e4"]},
)
# Focus the report on the missing-net errors. The hand-drawn polygon also
# triggers `LVS.short`, but that's the *cause* of the missing nets — here
# we want to look at the *symptom*.
# rdb = elvis.include_from_rdb(rdb, ["LVS.net.missing_in_schematic"])
lyrdb = Path(tempfile.gettempdir()) / "metal_polys_scm.lyrdb"
rdb.save(str(lyrdb))
show(gds_path, lyrdb=lyrdb, netlist=PIC, tools=[Tool.FIT_ALL])
/home/runner/work/elvis/elvis/.venv/lib/python3.12/site-packages/gdsfactory/component.py:566: UserWarning: Unconnected ports: ['pad1,e1', 'pad1,e2', 'pad1,e3', 'pad1,e4', 'pad1,pad', 'pad2,e1', 'pad2,e2', 'pad2,e3', 'pad2,e4', 'pad2,pad']
return get_netlist_recursive(
flags used
The above LVS function is called with the following flags
- short_layers=[(49, 0)]
- equivalent_ports={'pad': ["pad", "e1", "e2", "e3", "e4"]}
this can also be pre-configured.
Net errors in detail:
print(f"ok={rdb.num_items() == 0}, error_count={rdb.num_items()}")
Markdown(elvis.error_summary(rdb))
ok=False, error_count=3
| cell | error type | description |
|---|---|---|
| metal_polys_py | LVS.net.missing_in_schematic | Net pad1,{pad,e1,e2,e3,e4} <-> pad2,{pad,e1,e2,e3,e4} in layout but not in schematic |
| metal_polys_py | LVS.short | Geometric short between 'pad2' and 'rectangle' (1 location) |
| metal_polys_py | LVS.short | Geometric short between 'pad1' and 'rectangle' (1 location) |
Three errors total: one merged LVS.net.missing_in_schematic plus the two LVS.short errors that caused it. The merged net error uses brace notation — pad1,{pad,e1,e2,e3,e4} <-> pad2,{pad,e1,e2,e3,e4} — to say "every port in pad1's equivalence group is connected to every port in pad2's equivalence group, and none of that is declared in the schematic." The full list of underlying cross-pairs is in the rdb item's comment if you want to drill down.
The underlying cause is geometric — a polygon overlap on M3 — but the symptom Elvis surfaces in the schematic comparison is the missing net.
Hand-drawing geometry like this is exactly the case where c.get_netlist() can't infer connectivity for you: gdsfactory derives nets from declared ports and routes, not from arbitrary polygons or from instances without port metadata.
Fix¶
As soon as you start drawing rectangles by hand you'll be required to write the netlist by hand too. This is where GDSFactory gives up. You can't expect GDSFactory to keep track of connectivity of manually drawn polygons where the ports don't match.
Note
Obviously GDSFactory could use elvis to derive connectivity of manually drawn polygons, but that would defeat the purpose. GDSFactory should not use geometric overlaps and connectivities to derive netlists as that's already done on the netlist extraction side, not on the input netlist side. Doing it on both would prevent any LVS errors from bubbling up
yaml_str = """
instances:
pad1:
component: pad
settings: {}
pad2:
component: pad
settings: {}
connections: {}
routes: {}
nets:
- p1: pad1,e3
p2: pad2,e1
"""
schematic2 = {"top": yaml.safe_load(yaml_str)}
rdb = elvis.lvs_rdb(
gds_path,
schematic2,
short_layers=[(49, 0)],
equivalent_ports={"pad": ["pad", "e1", "e2", "e3", "e4"]},
)
rdb.save(str(lyrdb))
show(gds_path, lyrdb=lyrdb, netlist=PIC, tools=[Tool.FIT_ALL])
print(f"ok={rdb.num_items() == 0}, error_count={rdb.num_items()}")
Markdown(elvis.error_summary(rdb))
ok=True, error_count=0
| cell | error type | description |
|---|---|---|
Caveats¶
There are three ways you might want to manually "draw a wire" in gdsfactory (as opposed to using its routing functions). They all produce different LVS reports.
1. Polygon as a route — c.add_polygon(...)¶
Anonymous geometry placed directly into the top cell:
There is no instance — just shapes. Elvis groups all such bare geometry into a single chain it labels top-cell polygons. As soon as the resulting connection is declared in the schematic, elvis's schematic-aware short suppression clears every short and every cross-pair net error, leaving zero LVS errors. This is the right tool for hand-drawn metal traces, fills, and custom shapes.
2. Port-less component as a route — c.add_ref(gf.c.rectangle(..., port_type=None))¶
This is what cell 3 of this notebook does. It places a named instance of rectangle, but with port_type=None so the rectangle has no ports:
The instance shows up in c.get_netlist() (and therefore would normally trigger LVS.instance.missing_in_schematic if you don't list it in your schematic), but elvis treats a zero-port instance as pure routing whenever its shorts are all schematically accounted for: same suppression rule as case 1, plus the instance.missing_in_schematic error is dropped automatically. Net result: declare the connection in the schematic and the report is clean.
3. Port-bearing component as a route — c.add_ref(gf.c.rectangle(...)) (default ports)¶
Here the rectangle has its default e1–e4 ports. Now elvis can't treat it as transparent routing — its ports carry electrical identity, so the schematic-aware suppression refuses to clear shorts whose cross-pairs include ports of an undeclared instance. You'll see LVS.instance.missing_in_schematic for rect1 and the underlying shorts. The fix is stricter: declare rect1 in the schematic too and tie its ports into the surrounding net — or drop down to case 1 or 2.
Rule of thumb¶
If the geometry is purely a wire / fill / metal patch — add_polygon, or add_ref of a component without ports. If it's a component you intend to reuse, parameterize, or simulate — add_ref and mirror it in your schematic.