Skip to content

X3: LVS partial and missing rows - #32

Closed
OmarSiwy wants to merge 13 commits into
verification-hardeningfrom
agent/x3
Closed

OmarSiwy wants to merge 13 commits into
verification-hardeningfrom
agent/x3

Conversation

@OmarSiwy

Copy link
Copy Markdown
Contributor

Brings the LVS rows that are partial or missing (outside H13's multi-die rows) as far as public sources define them: deck controls (max_res, min_caps, same_nets), relaxed series passives, a structural Verilog reference, hierarchy reporting, new device kinds, and the rows only Calibre defines recorded with what was checked. Per-row status in the final report.

@OmarSiwy OmarSiwy added the heavy Run the nightly test tier on this pull request label Oct 12, 2026
LVS-034 lacked KLayout's value filters and LVS-032 had no counterpart
to KLayout's same_nets between nets of different names.

`lvs max_res r` and `lvs min_caps c` leave out, on both sides and before
reduction, each resistor stating an r above r and each capacitor
stating a c below c, as KLayout's max_res and min_caps ("the netlist
compare will ignore resistor devices with a resistance value above the
given threshold"): opens. The layout's own value is read before
measures the reference does not declare are dropped, and the deck
interns r and c so the layout computes them. A device stating no value
stays. `lvs same_nets "layout" "reference"` seeds the labelled layout
net and the named reference net as a class of their own, as KLayout's
same_nets! holds them together; a pair whose wiring disagrees is left
unpaired. The deck parses, lowers, decompiles and prints all three.

Tests: compare_with on hand-built graphs (KLayout's definitions by
hand: thresholds either side of the values, each alone, and an
unvalued resistor kept; a symmetric pair held with and against the
reference's wiring); the X5 harness's oracle reads the same controls
from deck.md and the drawn fixtures match it end to end (a drawn
200 ohm resistor and 0.56 fF capacitor either side of each threshold,
a written 1 Mohm, and the same_nets cases); the deck parser's errors
and the printer round trip. Each breaks if a threshold is read the
wrong way round or unit, or if a same_nets row does not seed its pair.
LVS-043, LVS-044 and LVS-049 combined passives in parallel only, while
Netgen's relaxed topology and KLayout's combiners also chain them in
series, so a drawn resistor chain never matched one card of the sum.

Under `lvs topology relaxed` a resistor or capacitor stating its value
now merges in series with another of its model through a net only the
two touch and that is no port, their far ends on two other nets: r and
a resistor's l add, c combines harmonically, area and perim add, as
KLayout's ResistorDeviceCombiner::serial and CapacitorDeviceCombiner::
serial combine them; a resistor's w must agree, as Netgen's sky130
setup states `series {w critical}`. Ordered capacitor plates chain only
plate to plate (plates `lvs permute` joins either way); a ring, or a
chain closing on one net, stays as drawn. Diodes and bipolars never
merge in series, nor anything passive under the strict policy.

Tests: reduce_with on hand-built chains against the closed forms (three
100 ohm resistors are 300 ohm, l and area tripled; two 2 fF capacitors
are 1 fF with their plates kept) and each refusal (strict, a port, w
differing, a ring, tail to tail, no value); the X5 oracle gains the
same law from deck.md and the drawn fixtures agree with it (a drawn
chain against the sum, through a port, a written chain against one
drawn, capacitors in series and tail to tail, diodes); and relaxed
cases join the Netgen differential under `parallel {value par|add}`
and `series {value add|par}`. A merge by the wrong law, or one made
through a port or tail to tail, breaks them.
LVS-075 was missing: a digital block's schematic arrives as gate-level
Verilog placing standard cells whose transistors are in a SPICE
library, and the reference reader took one SPICE or Spectre file.

`--reference` now repeats (Inputs::reference is a list): at most one
SPICE or Spectre file, the cell library, and any number of `.v` files,
read by the new netlist::verilog::read_into into the library's netlist
as one subcircuit per module, as Netgen's `readnet verilog` reads a top
into the cells' netlist. The top is the one module no other module
places (Netlist::top_in), whatever library cells go unused. The subset
is IEEE 1364-2005's structural one: ports in either style, input/
output/inout/wire/supply0/supply1 with constant ranges, assign between
nets as one net, instances by name or in order, bits, part selects,
concatenations, escaped identifiers, attributes; an unconnected port is
its own net, and a blackbox stub of a library cell declares it. Bus
bits are named `d[3]`, and a cell's `D[0]` port takes bit 0 of `.D()`.
Directives, constants, parameters, instance arrays and behavioural code
are refused; two SPICE or Spectre files are NetlistError::Several.

Tests: each construct against the SPICE hierarchy stating the same
connections (flattened devices and ports equal), and every refusal by
construction; X5 fixtures of a drawn inverter chain against a Verilog
top placing a SPICE inverter by name, in order, through a bus and an
assign, and two faults, the verdict the flattening oracle's (so a
hierarchical reference equals its flattening); the same tops join the
Netgen differential, which reads the Verilog over the library with
readnet as OpenLane does. A named port landing by position, a bus bit
on the wrong pin or an assign that does not join breaks them.
…ail caps apart (X3)

Run locally under the oracle shell, the sample differential broke on
four of the new cases. Netgen's Verilog reader drops an instance
connected by position ("Ignoring subcircuit with no pin names",
base/verilog.c), so its copy of each Verilog top now names every
connection; GPurify still reads both forms (IEEE 1364-2005 12.3.6),
proved against the SPICE twin. Netgen combines two capacitors in
series whichever plates meet, where GPurify keeps a capacitor's plates
ordered (F15) and chains them only plate to plate, the conservative
side: a documented difference, TAIL_TO_TAIL, that the full run must
exercise. The open-output fault moves to the first inverter, so the
fault is structural rather than a dangling top port, whose report is
Netgen's pin matching, which the differential does not read.
LVS-017 reported a missing block only as its devices unpaired, with
nothing naming the instance, and LVS-010 had no design for comparing
cell by cell.

Flattening now records each placement it expands (Netlist::placements:
its path, cell and the run of flat device rows it holds, inner
placements included), compare_traced returns, beside compare_with's
verdict and merges, the rows of the given reference each reduced device
stands for, and unpaired_instances names each reference instance none of
whose kept devices paired, the outermost such, as KLayout reports a
subcircuit with no partner. The run records each as an
lvs.unpaired_instance error row and lists it with its path and cell in
the text report. The flat comparison still decides the verdict; this
only names what its mismatch left unpaired. limitations.md gains the
design for cell-by-cell matching that keeps the flat comparison as its
oracle, and why it is not written: the layout is flattened on reading.

Tests: unpaired_instances on hand-built placements (outermost only,
partly paired not named, a merged device's rows all read, left-out rows
never named, layout-side ignored); an X5 test drawing a three-level
reference whole and with each block left out, which names exactly the
missing outermost instance. Breaks if an inner placement is named again
or a paired one is named.
Rebased onto H13: Inputs::reference became a list of files for the
Verilog reference (LVS-075), so the system subcommand now takes exactly
one --reference and refuses two, and reads it from the list. Its parser
test reads the path back from the list.
H13 found that SETUP reads `$asymmetric`, which only netgen_setup
defines, so a caller writing SETUP itself would have Netgen's Tcl error
out of the setup loop and compare with Netgen's defaults: no permute,
no tolerance, a differential weakened without a word. netgen_setup now
takes only the controls and fills SETUP's tolerance itself, so no
caller touches SETUP (the per-case batches and H13's system stacks both
go through it), and a merge-tier test holds every setup the curated
cases write, and the default, to define `$asymmetric` first, leave no
TOL unfilled, and carry the permute and tolerance lines.

Oracle: construct-from-answer: SETUP's own lines and 2t/(2 - t) of the
documented 2 %. Breaks if a setup omits the variable or a line.
Regenerated by tests/catalog.rs from the catalog after the rebase onto
H13: LVS-032, 034, 043, 044, 049 and 075 implemented, 017 partial.
The message the system subcommand gives without exactly one
--reference changed with the list; the refusal test now states it, for
none and for two.
LVS-068 checked a device's parameters against ranges but not how many
devices of a model a layout holds, which foundries limit per chip (IHP
SG13G2's recommended rules: at most 4000 npn13G2 emitters, 800 each of
npn13G2L and npn13G2V).

`lvs count "model" >= min <= max` (either bound or both, whole numbers)
holds the count of the model's layout devices to its bounds, reported
as lvs.device_count: on the first device past the maximum, measured the
count against it, or on the model's first device (nowhere when there is
none) under the minimum. The values are the deck's; a device is one
marker, so a multi-emitter bipolar counts once. The deck parses,
lowers, decompiles and prints it.

Tests: X5 fixtures of three drawn nmos and a pmos, within, over and
under the bounds, a model the layout lacks, and another model left
alone, the counts by construction; the parser's refusals (bounds
admitting nothing, none, a fraction, a second row) and the printer
round trip. A count read off by one, or held to the wrong model,
breaks them.
LVS-065 was missing: there was no inductor, so neither a drawn one nor
an `L` card could be compared.

`device inductor <marker> model "m" terminals [a, b] inductance 2nH`
recognises a two-pin device whose ends are interchangeable, as
KLayout's DeviceClassInductor declares them, its inductance the value
the deck states for every device of the recogniser (a foundry's fixed
inductor cell; the value is the deck's). The SPICE reader takes `L`
cards, the value positional or `l=`, and Spectre's `inductor` primitive,
reading the inductance as `ind` so it never passes for a length; a card
with no model is the deck's one inductor. Strict, parallel inductors
count m; relaxed, they combine harmonically in parallel and add in
series (KLayout's InductorDeviceCombiner). The deck parses, prints and
decompiles the kind and its `inductance` (pH, nH, uH); the extracted
netlist writes it as an `L` card.

Tests: the reader's L and Spectre forms, by construction; the deck's
unit and kind checks and the printer round trip; X5 fixtures (one
inductor either way round, 4.8 % off, two in series and in parallel,
strict and relaxed, and every_model's Spectre and unmeasured-parameter
relations), against the oracle extended with the inductor's law; and
five cases in the Netgen differential, which agrees on all (run locally
under the oracle shell). An inductor read as polar, or its law
misapplied, breaks them.
LVS-047 was missing, recorded as defined only by Calibre. Silvaco's
public Guardian LVS material defines it: its MOS series and parallel
reductions merge same-type transistors "connected in series [or
parallel] with different gate terminals" into one element whose gates
are swappable, and its logic gate recognition (application note, 2020)
builds INV, NAND_N and NOR_N on them, "all N input terminals ...
swappable".

`lvs recognize_gates` (off by default; an analog stack's order is part
of the circuit) turns that reduction on: once neither ordinary law
merges anything, plain transistors of one model and bit-identical
parameters, each on a gate of its own, in parallel on one source/drain
pair or chained through nodes only the two touch and that are no port,
become one device with all their gates as interchangeable Gate
terminals (lvs::gates), and the ordinary laws run again. A merged
device is not merged again, so an AOI's or OAI's groups in another
order still mismatch; transistors of different sizes never merge.
compare_traced's row trace and the merge log cover the new merges.

Tests: X5 fixtures of a NAND2 and a NOR2 wired in the other input
order, a stack of different widths and one through a labelled node,
each with and without the control, against an oracle that tries every
reassignment of each group's gates (the definition, by brute force);
hand-derived verdicts for the fixtures; a metamorphic relation that
every reassignment of a group's inputs keeps a match, over the sample
of the enumeration and the NAND2; the deck parser and printer. A merge
of different sizes, or a swap the tool reads as another circuit, breaks
them.
LVS-074: `lvs lde "model" od <layer> max <length> [well <layer> scref
<length>]` measures BSIM4's sa/sb (gate ends to active-area edges,
BSIM4's effective average over a non-straight edge, compared as a pair
smaller first) and, with a well, sca/scb/scc in Silvaco's public closed
forms from the median gate-to-well-edge distances (lvs::lde), added to
the layout graph in run_lvs. Deck parse, lower, print and decompile;
reduce and gate merges refuse transistors stating sa/sb in series.

Tests written and passing locally: the Silvaco worked example
(lvs::lde unit test, SE), seven X5 fixtures by construction (L-shaped OD,
enclosing well, well beside, sa/sb either way, each value 5 % off), the
deck parser and printer round trip; catalog, doc_counts, oracles,
golden and the merge-depth exhaustive fixtures pass.

Unfinished: one local clippy pedantic warning (items_after_statements on
the `type Case` alias inside lde_fixtures in tests/exhaustive/lvs.rs:
move it above the first statement); the docs/check-sources.md and
docs/limitations.md generated blocks were rewritten by the catalog test
for LVS-047/065/074 and are in this commit; CI has not run on it.
@OmarSiwy

Copy link
Copy Markdown
Contributor Author

Landed on verification-hardening (rebased over X2/X4/X5, WIP lint fixed; workspace tests run locally).

@OmarSiwy OmarSiwy closed this Oct 12, 2026
@OmarSiwy
OmarSiwy deleted the agent/x3 branch October 12, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

heavy Run the nightly test tier on this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant