Repository navigation
235 commits on main since v0.4.0, cut under the 25-commit backstop in
RELEASING.md's "Release cadence" (operator ruling 2026-09-15,
issue #1563). Minor bump: the range carries no conventional-commit breaking
marker (!: / BREAKING CHANGE) and no schema_version value under src/
changed; compatibility notes, where they matter, are in the entries below.
-
Added:
klt place-and-routeresponse fieldnominal_hold_slack_ns
(issue #1826) — the single, nominal-corner hold WNS
(report_worst_slack_metric -hold), populated from the"place"stage
onward, mirroring the existing setup-sideworst_slack_ns. Distinct from
the route-stage-only, corner-sweptworst_hold_slack_nsaggregate (issue
#949) — this field exists so a caller can get a hold slack number
before a full route, not justhold_violation_count's pass/fail count. -
Added:
klt place-and-routeresponse fieldunrouted_def_path
(issue #1826) — a pre-route DEF, populated whentarget_stageitself is
"place"or"cts", reusing (and, for"cts", newly adding) the
write_defcall each of those stages' own Tcl generator already runs.
klt sta's newdef/geometry_source: "placement_estimate"request
fields (below) accept this path directly, closing the gap where an SDC-
driven setup/hold slack characterization required a full route. -
Added:
klt starequest fieldgeometry_source("routed", the
default, or"placement_estimate"; issue #1826), echoed back verbatim as
the response's owngeometry_sourcefield. Declares whetherdefis a
fully-routed signoff geometry or a pre-route (klt place-and-route
"place"/"cts"-stage) estimate — a caller-supplied label, since a bare
DEF file carries no stage-provenance metadata of its own. See
docs/cli/sta.md's "Pre-route DEFs" section. -
Added:
klt arith-gen— generate a parallel-prefix adder as Verilog
RTL from one of five named architectures (ripple,brent-kung,
han-carlson,sklansky,kogge-stone) or from an explicit N×N binary
cell map (issue #1722, new moduleklayout_tools/arith_gen.py). Pure
Python string generation — no Yosys, no PDK, no engine. Writes the
structural adder, a behaviourala + b + cinreference with identical
ports, a self-checking testbench, a Yosystechmap -maprule file
substituting the adder for same-width$addcells, and a ready-to-run
klt equivrequest pairing the reference (gold) against the adder (gate).
Reportsprefix_cells/logic_levels/max_fanoutso the cell-count vs.
depth vs. fanout trade is visible without synthesizing. The representation
(cell map) and the measured-in-loop selection below are reimplemented from
Lai et al., Scalable and Effective Arithmetic Tree Generation for Adder
and Multiplier Designs (NeurIPS 2024, arXiv:2405.06758); their RL/MCTS
search is deliberately not reimplemented. See
docs/cli/arith-gen.md. -
Added:
klt synthesizerequest fieldarithmetic(issue #1722) —
{"adders": "auto" | "default" | "<architecture>", "min_width": 8, "candidates": [...], "verify_adders": true}. Substitutes a generated
prefix adder for Yosys's own$addexpansion via atechmap -maprule
inserted betweenhierarchyandsynth, guarded by_TECHMAP_FAIL_so
only the probed widths are replaced."auto"runs a full trial synthesis
per candidate plus Yosys's own default expansion, and keeps the
smallest candidate meetingconstraints.clock_period_ns(with an explicit
reported fallback when none does). Every substituted adder is proven
equivalent to a behaviourala + b + cinbyklt equivbefore it is kept
— scoped to the adder module, so the gate works on sequential designs too.
Additive: the new response fieldarithmeticisnullfor every request
without the field, andschema_versionis unchanged. Measured on the
modexpcanary atWIDTH=16(gf180mcu,clock_period_ns: 22): Yosys's
own expansion missed the constraint at 23.79 ns while all five prefix
architectures met it, and the selection kept the smallest of those.
Multipliers (compressor trees) and search over cell maps are explicit
non-goals. Seedocs/cli/synthesize.md's "Arithmetic architecture"
section anddocs/design/synthesize-qor-improvements-survey.md§3.8. -
Added:
klt sim --plot <dir>— renders one self-contained,
dependency-free waveform SVG per non-sweep signal per corner (issue
#1723), reusingklt trajectory --plot's no-plotting-library, string-
built SVG approach (new moduleklayout_tools/sim_plot.py). Forces
options.waveforms/keep_artifactson for the run regardless of the
request's own settings — a waveform can only be plotted once captured.
acplots use a log10-scaled sweep axis; atranmeasurement whose
.meascard's own signal matches a rendered signal additionally gets its
from=/to=window shaded and itsWHEN <signal>=<value>threshold
drawn as a reference line. Every written file is listed in the response's
newplotsfield and each corner'sartifacts.plots; a measurement's
rollup entry additionally gets aplotfield (the SVG for its own
worst_casecorner, ornull) — so a miss report names the picture to
open next, without the numeric threshold-crossing extraction that #56
covers separately. All additive/optional (present only when--plotis
used);schema_versionunchanged. Also hardens the pre-existing
options.waveformscapture path: a corner whose rawfile
parse_ascii_rawfilecannot parse (currentlyac/sp's complex
real,imag-encoded values — tracked as issue #1756, out of this issue's
own scope) now reports a"warning"diagnostic and leaves
artifacts.waveform/plots empty instead of raising and aborting the
whole sweep. Seedocs/cli/sim.md's "Waveform plots" section. -
Added:
klt kb validate --recheck-measured— re-runs every distinct
measured.figures[].testbenchinkb/entries/*.jsonviaklt simand
fails validation when a recordedfigures[].valuehas drifted beyond a
fixed 1% relative tolerance from what that testbench produces now, instead
of only checking that the testbench path exists (issue #1726). Off by
default and absent from the every-PRklt kb validateCI gate — actually
simulating every measured entry is expensive — it is what the new nightly
.github/workflows/kb-drift-canary.ymlworkflow
(schedule+workflow_dispatchonly, mirroringequiv-canary.yml's
posture for an expensive non-blocking-on-PRs simulation gate) runs. A
drifted figure fails that job and is never auto-corrected.klt kb validate's JSON payload shape is unchanged (drift shows up as additional
entries[].errors[]strings, so noschema_versionbump). Also raises
examples/kb/pfd-charge-pump-tri-state/request.json's
options.timeout_sfrom60to900: that transient needs ~4 minutes of
wall clock, so its own checked-in request could not reproduce the figures
the KB entry records from it. Seedocs/cli/kb.md's "Recheck measured
figures" section. -
Added:
klt sim --op-lint— a per-device operating-point sanity lint
("op-sanity") that answers "which MOSFET is not doing its job, and why?"
instead of running the corner sweep (issue #1718). It takes the same
request document (onlynetlistis required), runs one.opat one
corner (--op-lint-cornerpicks which), and walks every MOS instance in
the netlist reporting:off(|Vgs| < |Vth|withIdat/below a noise
floor),triode(|Vds| < |Vdsat|plus a corner-aware margin
defaulting to 2 kT/q at that corner's temperature), wiring smells
(drain_tied_to_rail,gate_shorted_to_source,bulk_not_tied), and
netlist hygiene (missing_nodefor a declared/.meas-referenced node the
netlist never creates,floating_nodefor a single-terminal node). Each
finding names the device, its four terminal nodes, the model-reported
values, and a one-line suggestion; per-deviceVthis read from the model
(@m…[vth]), never a constant. Exit codes reuseklt sim's vocabulary
against this mode's own verdict (0no error-severity finding,3at
least one,4the operating point itself is untrustworthy). The same
entry point is exposed as a newklt evalgate kind,op-sanity— the
cheapest useful analog gate, meant to sit before asimgate in a
descriptor. New moduleklayout_tools/op_sanity.py
(schema_version: 1); no new dependency (it reuses the ngspice
print @m…[param]pathklt sizealready uses). The corner-sweep path is
unchanged when--op-lintis absent. Seedocs/cli/sim.md's
"Operating-point lint" anddocs/cli/eval.md. -
Added:
klt extract --parasiticsnow reports a per-netby_layer[]
breakdown alongside each net's scalarresistance_ohm/capacitance_ff
totals (issue #1701) —{"layer", "resistance_ohm", "capacitance_ff"}per
contributing conductor role ("diffusion"/"poly") or metal level
("metal<i>", 0-based), letting a caller composing a hierarchical netlist
from separately-extracted sub-blocks attribute a net's R/C to a specific
layer instead of hand-subtracting scalar totals. Purely additive (no
schema_versionbump perdocs/json-contract.md's additive-field policy)
and derived entirely from intermediates_compute_parasiticsalready
computed internally — no new geometry pass. Summingby_layer[].resistance_ ohm/.capacitance_ffacross one net's list reproduces that net's own
scalar totals, except for a net named by--mom-net/--mom-rlc-net(those
substitute the scalar totals with a value this lumped-RC breakdown did not
compute). Seedocs/cli/extract.md's "Per-layer breakdown" section. -
Fixed: the curated
gf180mcuDRC deck now checks Metal4's own minimum
width and spacing —metal4.width.1andmetal4.space.1, both 0.28 um
(280 dbu) on layer(46, 0)(issue #1688). Every other routing metal
(Metal1-Metal3,Metal5,MetalTop) already carried both, and Metal4
was otherwise present in the deck (itsVia3/Via4enclosure rules and
the MiM-cap stack), so a sub-0.28 um Metal4 wire — or two Metal4 wires
closer than 0.28 um — previously came backcleanrather than reported.
Both values are transcribed from the same DRM table the sibling rules cite
(google/gf180mcu-pdk,
docs/physical_verification/design_manual/tables_clear/22_Metaln_58.csv,
rulesMn.1/Mn.2a, whose published0.28 (2 <= n <= 5)range covers
n = 4), under the already-registeredscope="7.13 Metaln"— so
coverage.deck_scopeis unchanged. The gf180mcu deck is now 46 rules (was
44), andcoverage.rules_skippedgains the two new ids on a stream with no
Metal4 geometry. -
Fixed:
klt extract --def-net-namesnow recovers the real DEF net names
of a composed layout's block — a DEF-merged P&R macro thatklt gen-composeplaced into a larger layout — instead of silently degrading
every one of that block's nets to a synthesized$<id>placeholder (issue
#1689). Composition never dropped the DEF net-name shape property; the scan
that reads it looked in the wrong cell. KLayout's LEF/DEF reader stamps the
property onto the routed geometry it draws in the merge's own top cell,
andgen-compose's composed GDS places that exact cell one level below the
composed top (<block_id>__<src_cell_name>, hierarchy preserved rather
than flattened) — so the property sat one level below the only cell
_def_net_name_probesscanned. The scan is now depth-1: the extraction top
cell's own shapes (unchanged) plus exactly one level into each direct child
cell instance, with each candidate probe point transformed into the
composed top cell's own coordinate frame before it is resolved. A
standalone (non-composed) layout's output is unchanged, and the original
false-positive guard is kept for anything nested deeper — a standard cell's
own internal annotation several levels down still is not read as a
top-level net name. Known limitation: a block composed more than one
level deep (a macro composed into a macro composed into a macro) is not
reached; nothing in this codebase currently produces that shape, since
gen-composealways places a source block exactly one level below its own
composed top. Seedocs/cli/extract.md's
"DEF-derived net names" section. -
Added:
klt sim --format jsonnow reports
environment.timeout_preflight_warning(part of issue #1686) — an
advisory string emitted before the corner grid starts when the requested
options.timeout_slooks implausible for atrananalysis's own declared
step/window. A per-cornertimeout_sprotects against a hang, but
nothing protected against a budget that can never be met: a real incident
widened atranwindow 33× while raisingtimeout_sonly 6×, and all 45
corners came backtimeoutafter burning their full budget for ~45
wall-clock hours with no usable result. The check comparestimeout_s
against thewindow / steptimepoint count under a deliberately generous
50 timepoints/second floor (not a real per-engine throughput estimate),
is scoped tokind: "tran"only, and is advisory only — it never blocks
the sweep; a budget that looks plausible under this coarse floor can
still be wildly insufficient in practice. Present only when the heuristic
has something to say, absent for the common case — purely additive, and
klt sim'sschema_versionstays1. The stronger dispatch-time abort
scoped by the same issue is not implemented (it needs a timed-out
corner'sreached_s, which no investigated mechanism can recover safely
against this module's per-corner deck shape); issue #1694 tracks that open
design question with the empirical findings. See
docs/cli/sim.md's "Timeout-budget preflight" section. -
Added:
klt extract --parasiticsgains--parasitics-net NET,
repeatable (issue #1700) — it scopes the per-net ground R/C pass to
the named nets instead of measuring every net in the design, and the
request is echoed back verbatim as the additive
parasitics.parasitics_netsfield. The whole-layout default is the
dominant cost of--parasiticson a large block (each net's geometry is
queried once per conductor role), so a mixed-signal top cell can spend
tens of minutes producing R/C for thousands of nets when the caller
wanted it for a handful. Requires--parasitics(the flag alone is an
error, same as--critical-net); a name matching no net with
ground-eligible geometry is reported inwarnings, not raised, and an
anonymous net is named in the same backslash-escaped spelling
parasitics.nets[].netreports (issue #1162). This is the only
--parasiticsflag that removes content from the report, so two
consequences are deliberate rather than layout findings: an unnamed net
gets noparasitics.nets[]entry, no injectedR/CSPICE cards, and
no contribution tototal_resistance_ohm/total_capacitance_ff— the
totals of a scoped run are partial by construction; and because both
coupling passes are computed from the per-net geometry the ground pass
caches, only a pair of both-named nets can couple — naming a
crosstalk victim without its aggressor reports zero coupling for it. A
--critical-netnet left out of--parasitics-netgets its own
warningsentry naming the responsible flag combination, instead of the
generic "matches no net in this layout" wording that would misdirect the
caller at their layout. The flag changes which nets are measured, never
how: a named net'sresistance_ohm/capacitance_ffare bit-for-bit
what the full-layout pass reports for it.parasitics.parasitics_netsis
[]when the flag was never given, and omitting the flag leaves every
field byte-identical to before it existed — purely additive, andklt extract'sschema_versionstays3. See
docs/cli/extract.md's "Scoping the ground R/C
pass to named nets" section. -
Fixed:
klt gen-compose'sconnectivity[]router now retries a leg
ontorouting.cross_block_layer_rolewhen it is rejected for crossing a
different net's already-accepted route, instead of failing the whole net
outright (issue #1680). Two nets sharing onerouting.layer_rolepurely
because it is the request's one primary layer — not because either
genuinely needs that physical plane — could not resolve a crossing between
them before this:route_two_pin()'s own same-block cross-layer retry
(#1168/#1393) never fired, since it only ever engages from its own checks
3/4, one level below where the route-vs-route collision check (#1057/#1386)
actually runs. The rejected leg is now re-routed once with the primary and
cross layers swapped and accepted only if it also clears the collision
check there; a leg that collides on both layers still fails the whole net.
A genuine same-layer short with norouting.cross_block_layer_role
configured is unaffected. See
docs/cli/gen-compose.md's "Known limitations"
section. -
Fixed:
klt gen-composenow warns when a block's declaredbbox_um
understates its own real drawn geometry, and that undeclared excess
geometry physically overlaps another placed block (issue #1679). A
blocks[].generator_reportentry'sbbox_umis trusted verbatim for
placement math (never re-derived from its own stream) — but a block whose
real geometry (e.g. an externally-produced macro's guard/seal ring) extends
past what it declared could have that excess silently overlap a neighbour
placed just outside the declared bbox but still inside the real one.
klt drcreports this clean (a zero-clearance same-layer merge is not an
illegal shape by any spacing rule); the corruption previously only
surfaced later as a spuriousklt extractmerged_net_labelsentry
joining two of the macro's own unrelated nets, even though extracting the
same, unmodified macro GDS directly (no composition) was clean.
gen-composenow reads each block's own stream to compare its real placed
bbox against its declared one, and — only for a block where they disagree —
checks its real per-layer geometry against every other block's for an
actual shape overlap, adding onewarnings[]entry naming both blocks and
the shared layer when found. Advisory-only (never raises, never blocks
composition), applies to"row"/"explicit"placement alike. See
docs/cli/gen-compose.md's "A declared
bbox_umis trusted, not verified" section. -
Fixed:
klt gen-compose's obstacle-overlap routability check is now
layer-scoped instead of layer-agnostic (issue #1656). The check used to
test a candidate route's backbone against every other placed block's
bbox, regardless of which layers that block actually draws shapes on —
so a route on one layer (e.g.metal3, a router-only role noklt gen
generator draws pads on) could be rejected, or detoured around (#1167),
for "crossing" a neighboring block's bbox even when that block draws
nothing at all on that layer: no physical short is possible, only a bbox
intersection on paper.route_two_pin()now looks up each candidate
obstacle block's own drawn geometry on the leg's effective route layer
(reusing the same lazy per-blockread_block_layer_geometrycache
compose()already builds for #1520/#1527's own-block checks) and skips a
block as an obstacle when it draws nothing there. A block that does draw
something on that layer is still rejected/detoured around exactly as
before — this removes false positives only, it does not weaken the check
against a genuine obstacle. See
docs/cli/gen-compose.md's "Known limitations"
section. -
Added:
klt extract --format jsonnow reports
devices[].instance_path(issue #1666) — the chain of GDS-level cell
placements each device's recognition geometry sits inside, outermost
first:[{"cell": str, "array_index": [ia, ib] | null}, ...], with
array_indexgiving the 0-based element position within a regular
CellInstArray(nullfor a plain single placement) and[]meaning the
geometry was drawn directly in the top cell. Extraction flattens the whole
instance tree before device recognition runs, so until now nothing in the
report distinguished devices coming from different repeated copies of the
same leaf cell; this is the device-level counterpart to the net-level
nets[].net_id/pin_index/label_positions_umdisclosure added in issue
#1540, and is resolved positionally after extraction from each device's
own recorded position against the layout's instance tree. Purely additive
disclosure — no device, net, parameter, or written netlist line changes,
andklt extract'sschema_versionstays3. See
docs/cli/extract.md's "Per-device GDS instance
attribution" section for the worked example, the rationale for choosing an
additive field over a non-flattening--preserve-instancesmode, and the
caveat that[ia, ib]'s axis is the GDS file's rather than the authoring
order's. -
Fixed:
klt lvs's internal_prune_extra_top_circuitsnow compares
circuits by identity (circuit is not keep) instead ofcell_index
(issue #1657). Every netlist read directly from SPICE/Verilog text — every
reference netlist, and the pre-extractedlayout.netlistshape — has no
backingkdb.Layout, so every circuit'scell_indexpreviously read back
as0, making the oldcell_index-based comparison always false and
silently pruning nothing. This could surface as a spurious"mismatch"
status when a reference or layout netlist declared an extra, unrelated
top-level.subcktalongside the circuit pinned viatop. Layout-backed
netlists (distinctcell_indexvalues) already pruned correctly and are
unaffected. -
Added:
DrcRule.checkaccepts a new single-layer check kind,
"isolated"(issue #1654), dispatching to KLayout's
Region.isolated_check— spacing measured only between edges of
different polygons, unlike"space"(Region.space_check), which also
measures the width of a concave notch carved into a single merged polygon.
klt drccan therefore emit"isolated"as aviolations[].checkvalue;
this is additive value-set growth on an unchanged shape, soklt drc's
schema_versionstays1(seedocs/json-contract.md's
"Pre-1.0 caveat: value sets within an unchanged shape can grow"). -
Fixed: the
sky130curated deck'snwell.space.1now uses
check="isolated"instead ofcheck="space"(issue #1654), matching the
real semantics of its source rule —sky130.lydrc'snwell.2ais
nwell.isolated(1.27, euclidian), an inter-polygon spacing check.
Transcribing it as"space"also flagged a concave notch within a
single merged nwell island, whichnwell.2adoes not consider a
violation; on a production layout roughly a third of the reported
nwell.space.1violations were this same-polygon false-positive class.
Genuine inter-well spacing violations are still caught unchanged. Two
consumer-visible consequences: violations of this rule now carry
check: "isolated"rather thancheck: "space", andnwell.space.1no
longer appears intests/golden_deck/sky130/manifest.json(that
manifest's sky130 scope is("width", "space")). The rule itself is
unchanged in id, layer, and 1.27 um threshold. -
Fixed:
klt lvs'sreference.form: "gate-level-verilog"compare no
longer reports a falsetopology"circuit could not be matched to a
counterpart" mismatch for a layout-side power-only cell — a filler cell
(sky130_fd_sc_hd__fill_*, inserted unconditionally whenever issue
#1442's row-rail fallback fires) or a tap cell
(sky130_fd_sc_hd__tapvpwrvgnd_1, inserted unconditionally by the
tapcellstage) — that a gate-level-Verilog reference never instantiates
at all (issue #1622).klt lvsnow removes every layout circuit whose
entire declared pin list is a power/ground pin ofreference.library
(derived per run from that library's own.subcktdata, never a
cell-name glob or a hardcoded per-PDK power-pin table), along with every
instance of it, before the compare runs, disclosing each removal as a new
severity: "warning",category: "topology.power_only_pruned"
mismatches[]entry so a"match"reached this way stays auditable from
the report alone. Scoped strictly toreference.form: "gate-level-verilog"— the same inference is unsound against a
"plain-element"/"subckt-call"reference, whose pin names need not
overlap the layout's. Additive;schema_versionstays1. See
docs/cli/lvs.md's "topology.power_only_pruned"
section. -
Fixed:
klt place-and-routeno longer raisesPlaceAndRouteErrorwhen
reaching the"cts"/"route"stage against a
gf180mcu_fd_sc_mcu7t5v0(7-track) netlist (issue #1649). The module's six
per-cell-library reference tables (_CTS_BUFFER_CELLS,
_ROUTING_LAYER_RANGE,_ANTENNA_DIODE_CELLS,_TAPCELL_CELLS,
_FILLER_CELLS,_POWER_PIN_PATTERNS) previously carried only a
gf180mcu_fd_sc_mcu9t5v0(9-track) entry, so any 7-track run's table
lookup failed before OpenROAD did any real work. Each table now also
carries a verifiedgf180mcu_fd_sc_mcu7t5v0entry, sourced the same way
as the existing 9-track ones:platforms/gf180/config.mk's
TRACK_OPTION-templated variables for the CTS buffer/tapcell/filler/power
pin-pattern cells, and that library's own LEF (CLASS .../ANTENNACELL
and pin data) for the antenna-diode and routing-layer-range rationale. -
Fixed:
klt sta'sspef_annotation.annotation_complete(issue #1624)
no longer reportstruefor a SPEF OpenSTA'sread_spefthen discards.
The field previously rested on a name-correlation check measured
beforeread_spef, with a different name resolver (get_nets) than
the SPEF reader's own — so a SPEF whose flattened names contain the SPEF
divider character (e.g. agenerate-block hierarchy flattened to
g_slice[0].u_slice/_08_) could correlate perfectly and still be thrown
away wholesale by the reader, yieldingannotation_complete: true
alongside timing numbers bit-identical to the unannotated run.klt sta
now measures three further pieces of evidence around and after
read_spef, each of which independently forcesannotation_complete: false:reader_warning_count(+ a cappedreader_warning_sample) —
OpenSTA's ownSTA-1650/STA-1648"net/instance not found" reader
diagnostics, matched on both stdout and stderr;delay_changed— the
identicalreport_checks -path_delay min_max -digits 6 -unconstrainedreport taken
before and afterread_spef,falsewhen byte-identical; and
unannotated_driver_count—report_parasitic_annotation's own count of
drivers OpenSTA holds no parasitics for. A companion
partially_unannotated_driver_countis reported but deliberately does
not gate (a complete, correctly-read SPEF routinely reports a non-zero
partial count). Unknown evidence (an OpenROAD build emitting none of these)
degrades tonulland never gates, so the pre-existing name-correlation
verdict still applies. Additive;schema_versionstays1. See
docs/cli/sta.md's "Annotation evidence" section. -
Added:
klt extract --deck sky130now recognises sky130's drawn
metal-resistor device family (issue #1621):res_generic_m1..
res_generic_m5(metN.drawingcovered by the PDK's ownmetN.res
resistor-ID mark,N= 1..5), extracting a boundr_ohmfrom each
layer's own sheet resistance (0.120/0.120/0.047/0.047/0.029 Ω/□ for
met1/met2/met3/met4/met5) instead of leaving the marked segment
unrecognised. Previously, ingesting a subckt-call-form reference netlist
that named one of these devices failed outright — there was no device
class for it at all.--pdkleaves them as the bareR-card form (no
curated subcircuit binding): sky130's own device library defines no
.subcktfor any metal flavour, only a bare.model ... rcard, the
same carve-outres_metal1/res_metal2already document for sg13g2.
klt lvsmatches the new devices through the existing generic
device-comparison path with no LVS code changes.klt genhas no
generator for this family yet — filed as a follow-up. See
docs/cli/extract.md's "Drawn resistors" table. -
Added:
klt extract --deck gf180mcunow recognises gf180mcu's drawn
metal-resistor family (issue #1640, the gf180mcu counterpart of #1621
above):rm1/rm2/rm3(Metal1/Metal2/Metal3covered by the PDK's
ownmetal1_res/metal2_res/metal3_resresistor-ID marks, 0.09 Ω/□
each) plus atm6k/tm9k/tm11k/tm30ktop-metal thickness flavour set
(0.06/0.04/0.04/0.0095 Ω/□, caller-selectable via--deck-option metal_top=,9Kthe PDK's own default — mirroringpoly_res, issue
#595). Previously, a marked metal segment extracted as an ordinary short
and a reference netlist naming one of these devices failed to ingest at
all. Unlike sky130'sres_generic_mNcarve-out, all seven of these
gf180mcu classes have a real two-terminal.subcktin the vendored
sm141064.ngspicedevice library, so--pdkbinds each to a genuineX
subcircuit call rather than a bareRcard.klt lvsmatches the new
devices through the existing generic device-comparison path with no LVS
code changes. Seedocs/cli/extract.md's "Drawn
resistors" table. -
Added:
klt gen-compose'srouting.cross_block_layer_rolefallback
plane (issue #1168) now has its own, independent width knob —
routing.cross_block_width_um(issue #1620). Previously
routing.width_umwas validated against bothrouting.layer_role's own
deck-minimum floor androuting.cross_block_layer_role's (issue #1501),
so naming a cross-block layer with a stricter minimum silently forced
every net's primary-plane routing wider too.routing.width_umis now
floored againstrouting.layer_roleonly; a leg that actually falls back
torouting.cross_block_layer_roledraws atrouting.cross_block_width_um
instead, defaulting to that layer's own deck minimum when omitted and
floored against it the same way, with the identical error shape. Additive;
schema_versionstays1. Same behavior reachable from
layout_plan_execute'srequest.routing.cross_block_width_um. See
docs/cli/gen-compose.md's "Cross-block bus
routing" section. -
Added:
klt synthesizenow reports static leakage power (issue
#1626):leakage_power_nw—sum(cell_leakage_power[cell_type] * instance_count[cell_type])over the response's own
instance_counts_by_type, in nanowatts — plus a per-cell-type
leakage_by_type_nwbreakdown, both read from the same resolved liberty
already loaded fordfflibmap/abc -liberty(no second liberty fetch,
no new PDK lookup). Static leakage only — dynamic/switching power needs an
activity factor this command has no vectors to supply, and stays out of
scope. Both fields arenull— never a fabricated or partial-looking
number — when the resolved liberty reports nocell_leakage_powerscalar
for any instantiated cell type (some libraries, e.g.
gf180mcu_fd_sc_mcu9t5v0, report leakage only via per-input-state
leakage_power()groups this command deliberately does not average into
one number). Additive;schema_versionstays1. See
docs/cli/synthesize.md's
"leakage_power_nw/leakage_by_type_nw" section. -
Fixed:
klt lef-abstract(issue #1613) no longer crashes with an
unhandledAttributeError: 'SimplePolygon' object has no attribute 'each_point_hull'when a routing layer's merged obstruction region
contains a non-rectangular polygon. The OBS formatter called
each_point_hull()— a method that exists only onkdb.Polygon, not on
thekdb.SimplePolygonit was calling it on — so the command always
succeeded on a purely rectangular layout and always failed on any design
with L-shaped/T-shaped metal, or whose pin ports only partially overlap a
metal run. Such shapes now emit as the LEFPOLYGONthey always should
have; rectangular geometry still emits asRECT, and no JSON field
changed. -
Fixed:
klt lef-abstract(issue #1614) now finds a declared pin's
real drawn geometry when a PDK puts a routing layer's drawn metal and its
pin/net text label on two different GDS datatypes of the same LEF layer
(e.g. gf180mcu'sMetal1:(34, 0)drawn,(34, 10)pin label). Pin
resolution previously searched only the exact datatype the socket
descriptor'spins[].layerdeclares — the same datatypeklt socket-checkreads the pin's text label from — so a pin satisfying
socket-check's label-layer contract could never see real drawn metal on
a sibling datatype, and was always synthesized as a placeholder box even
when metal existed._resolve_pinsnow searches every GDS datatype that
shares the pin's resolved LEF layer name, mirroring_resolve_obs's
existing per-LEF-layer union across datatypes. See
docs/cli/lef-abstract.md. -
Fixed:
klt extract --parasitics --spef(issue #1627) no longer
stamps the SPEF header's*DATEline with the write's own wall-clock
time.*DATEis optional and informational-only per the IEEE 1481-1999
SPEF grammar (no downstream reader parses it back), so it is now a fixed
placeholder — two runs of identical extraction against an unchanged input
layout now produce byte-identical.spefoutput, matching the
reproducibility guaranteeklt place-and-route's DEF→GDS merge already
makes for GDS2 output (issue #1367). -
Added:
klt stanow reportsworst_hold_slack_ns/
total_negative_hold_slack_ns(issue #1625), the hold-side counterparts of
the existing setup-onlyworst_slack_ns/total_negative_slack_ns— from
report_worst_slack_metric -hold/report_tns_metric -hold, mirroring
klt place-and-route's ownworst_hold_slack_nsfield name/pairing
convention so the two commands' output correlates without a naming
mismatch. Previously the only hold-side signal in the response was
hold_violation_count, a count that could say a corner has a hold
violation but not rank corners by hold margin or identify the binding hold
corner in a characterization sweep.report_tns_metric -hold->
timing__hold__tnswas verified live against a realopenroad/orfs
container (not assumed by analogy) before shipping. Purely additive; no
schema_versionbump. Seedocs/cli/sta.md. -
Added: new verb
klt clip(issue #1608): writes a subset of an
existing GDSII/OASIS stream back out as its own top-cell stream, either a
micrometre bounding-box region (--region, the same inline-JSON shape
klt ring-check/klt componentsalready accept) or a named cell's full
subtree (--cell, any cell in the stream, not just a top cell). Fills a
gap identified by #1603: handing a single device or region to an external
tool (EM extraction, third-party meshing, or just isolating it for review)
previously had nokltpath —--regiononklt ring-check/klt componentsonly ever restricted analysis, it never wrote the clipped
subset back out. Region-clip mode flattens every layer of the resolved top
cell (clip_box()+region()/texts()) and intersects it with the
window; cell-extraction mode copies the named cell's subtree verbatim
(kdb.Cell.copy_tree()), preserving nested hierarchy exactly. Output is
written via the existing sharedwrite_layout()(deterministic, no
embedded timestamp). Errors clearly on a nonexistent--cell, a--region
that rounds to zero width/height at the input's own database unit, and a
--regionthat matches no geometry. See
docs/cli/clip.md. -
Added:
klt pdk stackup(issue #1609) — the resolved PDK variant's
process cross-section as structured data: per conductor/via layer its
elevation (z0_um/z1_um), thickness, sheet (or per-cut) resistance and
derivedconductivity_S_per_m; per dielectric its z-range and relative
permittivity. This is the z-axis information a GDSII/OASIS file does not
carry and an external E&M/field solver requires, and it closes the open
questiondocs/design/em-field-sim-spike.mddeferred ("where the sky130
stackup table itself lives as aklt-owned asset"). Unlikeklt pdk em-limits, this is not a pure live parse: real tech LEFs carry no
elevation and no dielectric constant, so those come from a curated
per-family table (src/klayout_tools/pdk_stackup.py) whose every entry
cites the open, non-NDA'd published source it was transcribed from
(open_pdks' ownsky130A.techheightstanza /defaultareacap
coefficients,sky130.xs,sky130A.map), while thickness and resistance
are derived live from the install's tech LEFs at the selected--corner.
A variant whose family has no curated entry is an explicit exit-1 error,
not a partial stack.--thickness curated|tech-lefselects which of the
two published film thicknesses drives the emitted geometry (default keeps
the stack gap-free). Curated today:gf180mcu,sky130. See
docs/cli/pdk.md. -
Added:
klt pdk stackup --pdk gf180mcuD(issue #1616) — a second
curated family,gf180mcu, alongsidesky130
(supported_families()now["gf180mcu", "sky130"]). Verified against a
realvolare-fetchedgf180mcuD: elevation/thickness come from
libs.tech/magic/gf180mcuD.tech'sheightstanza (five metal levels of
uniform 0.55 µm,met5at 1.0025 µm, and no local-interconnect layer
—met1is the first conductor, unlike sky130'sli1/mcon), GDS
layer/datatype fromlibs.tech/magic/gf180mcuD-GDS.tech'scalma
statements, and permittivity (ε_r = 4.0, distinct from sky130's 3.9)
corroborated the same arithmetic way against this install's own
defaultareacapcoefficients. gf180mcu ships no KLayout XSection script
analogous to sky130'sxsect/sky130.xs, so dielectric slab boundaries are
induced purely from theheightstanza's contiguity and film material
names are a disclosed generic assumption rather than an install-specific
citation (see_GF180MCU_STACKUP's provenance note in
src/klayout_tools/pdk_stackup.py).ihp-sg13g2remains an uncurated
variant and continues to error clearly, naming both curated families. -
Added:
klayout_tools.lef_header.parse_lef_header()now also reports a
CUTlayer's per-cutRESISTANCEaslayers[].resistance_ohms(additive;
nullonROUTINGlayers, which stateRESISTANCE RPERSQinstead) —
the only resistance a via layer declares, needed byklt pdk stackup. -
Added:
klt functional-verification --mutations <proposals>(issue
#1592) — mutation testing as a test-quality gate: applies a set of
hand- or agent-authored byte-exact single-point RTL mutations, one at a
time, to an isolated from-scratch build+test of the same request, and
reports how many the testbench actually caught. Ported (Apache-2.0
attribution header,src/klayout_tools/_vendor/mutation_variants.py) from
boldaxolotl/booley's
no-HDL-parsing mutation seam, per
docs/design/mutation-testing-spike.md
(issue #1586). The baseline run is reused unchanged from the existing
request/response contract; a baseline that itself fails aborts the whole
run before any proposal is attempted (exit 1 — comparing a mutant against
an already-broken baseline is meaningless). Each isolated per-mutant
build+test is bounded by aklt-level subprocess timeout, independent of
whatever cycle bound the testbench's own code has — a mutation that
wedges the DUT's FSM is classified"killed"on timeout, per booley's own
rule, rather than hanging the whole run. Adds an additive
mutation_testingresponse block (proposal_count,valid_count,
killed_count,survived_count,rejected_count,mutation_score,
results[]) and extends the exit-code table: exit 3 when the baseline
passed but at least one valid proposal survived, matchingklt drc's
"ran fine, found violations" convention. See
docs/cli/functional-verification.md's
new "Mutation testing:--mutations" section, and
examples/functional-verification/proposals-modexp.json
for a worked example reproducing the spike's own live finding: one
comparison-boundary mutation onmodexp.v's modular reduction step that
survives the committed testbench unnoticed, alongside one bit-select
mutation that is correctly killed. -
Added:
klt synthesize's response gains three additive fields (issue
#1588): an always-presentstructuralverdict ({latches, expected_latches, unexpected_latches, comb_loops, multi_driven, has_critical}) over inferred latches, combinational loops, and
multiply-driven nets — all three drawn from the same Yosyssynth/stat
run this command already performs, no extra invocation; an always-present
bounded, deterministicwarningssummary ({total, by_category, representatives}) of the captured Yosys run log; and an optional
baselinecomparison ({ref, instance_count, area_um2, critical_path_ns, delta_pct}) against a prior run named by the request's new
baseline.response_path/baseline.netlist_path. The CLI now exits3
whenstructural.has_criticalistrue(statusstays"ok"),
reversing this command's own prior "no exit code 3" decision — additive
perdocs/json-contract.md's rule that a command may define codes above
2; noschema_versionbump (every new field is additive). See
docs/cli/synthesize.md's "structural"/"warnings"/"baseline"/"Exit
codes" sections. -
Fixed:
klt components's--label-layersentries can now declare an
optional"conductor"name (e.g.{"name": "m1pin", "layer": [34, 10], "conductor": "m1"}), scoping that label layer's texts to only that
conductor's own shapes in a component. Previously every label layer's texts
were matched against the union of every conductor/via layer present in a
component, so a text on a lower conductor's pin/label layer was wrongly
attributed to an unrelated, upper-layer component whose geometry merely
crossed over it in XY with no via joining them — on any real multi-layer
block with a power stitch or long route, this madelabelsan unreliable
net-name/short detector (issue #1579). An unknown"conductor"name now
raisesComponentsError(exit1), matching how an unknown via"between"
conductor is already validated."conductor"is optional and omitting it
keeps the original any-layer matching for backward compatibility — this is
an additive JSON field, not aschema_versionbump. -
Changed:
klt gen guard_ring's (and, since they compose the same
ring-drawing code,mos_array's/diff_pair's/bjt_array's/
esd_device's)gf180mcugeometry now draws a second implant ring,
exactly coincident with the tap/collector ring itself, closing the same
DF.12("COMP not covered by Nplus/Pplus is forbidden") coverage rule
#1577 closed for each unit device's own drawnCompbody but did not
reach the ring'sCompshape (issue #1580, a follow-up to #1577). The
doping matches whether the ring encloses a well: a well-tied ring
(guard_ring's ownadd_welldefault, or aflavor="pfet"well on
mos_array/diff_pair) reuses the"well_tap_implant"role (Nplus)
well_islandalready reuses for its own ring (issue #1421's precedent);
a substrate-tied ring (no enclosing well, includingbjt_array's
collector ring) gets the opposite doping,"pplus"(the same p+ role
#1577 added for aflavor="pfet"unit device's own source/drain). This is
a real, deliberate geometry change ongf180mcuonly, gated on a
ring_implant_present-style flag —sky130/sg13g2/sg13cmos5l
geometry is unaffected. Not verified against gf180mcu's own signoff DRC
tooling (this sandbox has no realrun_drc.pyto check it against) —
seedocs/cli/gen.md's updated gf180mcu note. -
Changed:
klt gen mos_array's (and, since they compose the same
unit-device drawing,diff_pair's/esd_device's)gf180mcugeometry now
margins against six real gf180mcu signoff-DRC rule ids
(DF.6_LV/PL.4_LV/PL.5a_LV/PL.5b_LV/CO.7/DF.12) that this repo's
own curatedgf180mcuDRC deck never transcribed, and that #1575 found
and documented as real violations of the pre-existing defaultgf180mcu
output (issue #1577). The unit device now draws: a gate-pad clearance off
the diffusion edge; a symmetric bottom-edge gate endcap (the top edge
already cleared these rules via the #461 landing pad); an extra
S/D-contact-to-gate clearance floor; a source/drain implant
(Nplus/Pplus, selected byflavor) covering every unit device's
Compbody; and (forvoltage_flavor: "medium_voltage") a wider
Dualgatemarker box. This is a real, deliberate geometry change on
gf180mcuonly — the reportedU<i>_Gport and the array/pair's overall
footprint move —sky130/sg13g2/sg13cmos5lgeometry is unaffected.
Not verified against gf180mcu's own signoff DRC tooling (this sandbox
has no realrun_drc.pyto check it against) — see
docs/cli/gen.md's updated gf180mcu note for exactly
what changed and what remains unverified. -
Added: documented and worked-example recipe for drawing a unit MOS
device narrower thanmos_array/diff_pair'sUNIT_MIN_W_UMcontact-fit
floor (0.42µm) — issue #1574, the follow-on #322's own triage comment
flagged as the trigger for revisiting the hard floor. Rather than adding a
generator-level override,docs/cli/gen.mdnow documents the standard
"dog-bone terminal" hand-drawn recipe (widen only the source/drain pads to
the generator's own contact-fit pad size, keep an unwidened shoulder
between each pad and the gate edge, draw the gate-crossing channel at the
requested narrow width) directly beneath both generators'w_umrows, and
examples/dogbone-terminal/is a new runnable, checked-in worked example
(generate.py) that hand-draws exactly this device withklayout.dbon
the same PDK role layersmos_arrayitself resolves to, places it beside
a realklt gen mos_arrayunit device in one cell, and verifies the
composed resultklt drc-clean on bothsky130andgf180mcu
(example_sky130.gds/example_gf180mcu.gdswith their.drc.json
reports checked in). No generator behavior changes —mos_array/
diff_pairstill rejectw_umbelowUNIT_MIN_W_UMoutright, exactly as
before. -
Added:
klt gen cap_arraynow supports thegf180mcuPDK family
(issue #1555 — the follow-on issue #1117 explicitly deferred when it
scoped this generator to sky130, and the gf180mcu counterpart of #1455's
sg13g2 support). It draws the same layer/datatype numbers
klayout_tools.decks.gf180mcu'sEXTRACTION_DECK.capacitors[0]
(cap_mim_2f0_m4m5_noshield) already declares —FuseToptop plate over a
Metal4bottom plate,Via4/Metal5top-plate via and landing pad — plus
theCAP_MK/MIM_L_MKmasks that entry's owntop_plate_requires
demands, so the output round-trips throughklt extract --deck gf180mcu
to that device class (and to its1f0/1f5density siblings via
--deck-option mim_cap=…, issue #1151) instead of extracting as zero
capacitors. Three per-family geometry floors keep the default output
DRC-clean where the generator's generic values are too small for this
family's MiM rules: the bottom plate is drawn at the DRM's own 1.06µm
"virtual bottom plate" oversize (CapacitorDevice.bottom_plate_oversize_um,
vs.mim.enclosing.fusetop.1's 0.6µm minimum), the top-plate via at
via4.width.1's 0.26µm, and a requestedparams.spacing_umbelow
mim.space.1's 1.2µm is widened to it rather than rejected —
drc_hints.min_spacing_umreports the spacing actually drawn, with a new
drc_hints.notesentry naming the widening. No other PDK family's drawn
geometry, ports, or hints change (every floor applies asmax(generic, floor)and only gf180mcu sets any). One related fix:cap_array's
harness-computedcap_top_via_metal_min_w_umPCell parameter (added by
#1455) was missing fromgen.py's_HIDDEN_PARAMS, so it was advertised
as a request param byklt gen --listdespite never being documented as
one; it and the three new floors are all hidden now. -
Fixed:
options.combine_devices_per_circuit(issue #1552) now carries
the same two post-combine corrections the whole-netlist
options.combine_devicespath already applies (issue #1557): the
deferred resistorfixed_offset_ohmcorrection (issue #559/#585) and the
capacitorCsum-conservation check (issue #1497). Both were previously
gated only inside theif combine_devices_enabled:block, which never
runs for acombine_devices_per_circuit-only request (the two options
are mutually exclusive), so a matched circuit's foldedres_high_po
resistors never received the deferred offset and a folded capacitor'sC
could be left silently inconsistent with its pre-combine sum. Both
corrections are now applied per circuit, scoped to just the circuit(s)
_combine_circuit_devices_safelyactually combined cleanly — a circuit
left uncombined, unmatched, or reporteddevice.combine_incompleteis
never touched. Existingmismatches[].categoryvalues are reused
(device.combine_parameter_corrected); no new category, no response
field changes, noschema_versionbump. -
Fixed:
klt extractno longer writes an unsimulatableCcard for a
recognised capacitor device class (issue #1558). KLayout's default
primitive-card writer appends the device class's own name as a trailing
4th token (C$1 a b 1.119e-13 cap_mim_1f0_m4m5_noshield), which ngspice's
nativeCelement parser reads as a required capacitor.model
reference — and no deck's capacitor class name is a real.modelin any
PDK model library (gf180mcu'scap_mim_*_m4m5_noshield, sky130's
sky130_fd_pr__model__cap_mim*, sg13g2'scap_cmim/rfcmimare
klt-internal class labels or, at best, subcircuit names aCcard
cannot reference), so the extracted netlist failed to elaborate at all
(unknown parameter (…)/could not find a valid modelname). Unbound
capacitor classes now write the bare, value-only card (C$1 a b 1.119e-13). Reachable both with--pdk(a recognised flavour with no
curatedpdk_modelstable row — e.g. gf180mcu's non-default
cap_mim_1f0_m4m5_noshield/cap_mim_1f5_m4m5_noshielddensities,
selected via--deck-option mim_cap=…) and without it. The flavour
identity is unchanged indevices[].classand in the netlist's own
* device instance …comment;--parasitics' ground/coupling capacitor
cards,--pdk-bound capacitorXcards, and theR/Q
bare-primitive carve-outs (whose trailing token is a usable
consumer-supplied-.modelreference) are all byte-identical to before. -
Added:
klt pexnow accepts--deck-option <key>=<value>
(repeatable) and--pins <a,b,…>, passed straight through to the
klt extract --parasiticsrun it drives internally (issue #1558). Before
this, a design committed to a non-default deck flavour (gf180mcu's
poly_res/mim_capaxes — one drawn geometry, several PDK-offered
interpretations) was silently extracted against the deck's default
flavour, with no warning, and everydelta[]row was computed from the
wrong parasitics;--pinsis likewise how a caller resolves a
pin_count_mismatchcaused by flat extraction promoting more top-level
pins than the schematic DUT declares. Both are off by default —
byte-identical to before — and the resolved deck options are echoed in
the existingprovenance.deck.optionsfield (no JSON shape change). Both
flags shareklt extract's own parsing/validation helpers, so an
unrecognised key/value or a malformed entry is the same clean exit-1
error in either command. -
Fixed:
klt extract --check <report> --rerun(issue #1559) no longer
reportsstatus: "drifted"for extractor-internal bookkeeping that is not
a stable contract across builds — a permutednet_id, an anonymous$N
net-name spelling changing, and/orparasitics.nets[]reordering. Both
the committed and freshly re-run reports are canonicalized before
diffing:net_idis stripped, an anonymous net's spelling is resolved to
a name derived from its own sorted<device>.<terminal>attachment list
(not the raw counter), and order-sensitive lists are re-sorted by the
resulting canonical identity. A genuine content change (an R/C value, a
device/pin count, a connectivity change) still reportsstatus: "drifted"exactly as before — the fix only normalizes bookkeeping, never
swallows real drift.docs/cli/extract.mdgains a "Field classes:
content, bookkeeping, tool metadata" table naming which report fields
fall into each class, cross-referenced fromdocs/json-contract.md. -
Added:
klt lvsnow acceptsoptions.combine_devices_per_circuit
(issue #1552): a{"<circuit-name-glob>": <boolean>}map applied via
klayout.db.Circuit.combine_devices()to each side's own matching
circuits, before either side's optionalflatten_layout/
flatten_referencestructural flatten runs. It is a per-macro alternative
to the existing whole-requestoptions.combine_devicesboolean, for a
composed design (e.g. viaklt gen-compose) whose own macros were
each already independently verified under their own — possibly opposite —
combine_devicessetting: one already-verified macro needs folding of
split/interleaved device legs, another needs to stay a literal
device-for-device compare to avoid issue #1497's silent
parameter-corruption risk at scale, and no single whole-request boolean
can satisfy both once composed under a shared top-level rail. Keys are
fnmatch-style glob patterns matched case-sensitively against each side's
own circuit names (NetlistSpiceReaderupper-cases names read back from
SPICE), applied in declaration order (first match wins); a pattern that
matches no circuit on a side is a newseverity: "warning"
mismatches[].category,combine_devices_per_circuit.unmatched—
never an error, since a pattern legitimately naming a circuit that exists
on only one side is not itself a mistake. Mutually exclusive with a truthy
options.combine_devices(a clean application error, exit 1); an explicit
combine_devices: falsealongside it is a harmless no-op. Echoed under
the newoptions.combine_devices_per_circuitresponse field (nullwhen
omitted) and reconstructed by--check --rerun. Noschema_versionbump
— both the request field and the response field are additive. See
docs/cli/lvs.md's "Composing macros with opposingcombine_devices
needs" section for the full worked example. -
Fixed:
klt gen res_arrayno longer draws a 1dbu-short (219nm) end
contact whenlength_umputs the contact centre on an exact half-dbu grid
tie — e.g.length_um=1.4965, which tripped gf180mcu's 220nm
contact.width.1minimum with no signal to the caller (issue #1551). The
unit resistor's two end-contact boxes were built from rawcx ± CONTACT_SIZE_UM / 2floats whose opposite edges were then rounded to the
dbu grid independently, so floating-point noise could resolve one edge up
and the other down (the same failure mode diagnosed forguard_ringin
issue #685). They now go through the existing_snap_square_box_umhelper,
which snaps the centre to the grid before deriving the edges. No response
field changes and noschema_versionbump; drawn geometry is unchanged at
everylength_umthat is not on a tie. -
Added:
klt gen res_arraynow exposes gf180mcu's high-sheet-rho
poly-resistor flavours, not just the baseppolyf_u(issue #1550):
params.flavoraccepts"1k"/"2k"/"3k"alongside the existing
"generic"default. All three new names draw identical geometry —SAB
(49, 0)plus theResistorhigh-sheet-rho marker(62, 0)over the
RES_MKbody, with noPplus— matchingppolyf_u_1k's (and its_2k/
_3ksiblings') ownrequiresset in
klayout_tools.decks.gf180mcu.EXTRACTION_DECK.resistorsexactly; the three
device classes are distinguished only by whichpoly_resvalueklt extract --deck-option poly_res=<1k|2k|3k>selects at extraction time
(1000/2000/3000 Ω/□ respectively), not by any drawn layer. Before this
change, ares_arraycell meant to model a real design'sppolyf_u_1k
resistor extracted as the baseppolyf_uclass at ~350 Ω/□ instead of
1000 Ω/□ — a >2.5x resistance-recognition error with no drawing-side
parameter to fix it. gf180mcu's existing default"generic"flavour is
byte-identical to before. Noschema_versionbump — no field changed
shape, and no request field changed meaning. -
Added:
klt extract'snets[]entries now carrynet_id,
pin_index, andlabel_positions_um(issue #1540) — all additive,
noschema_versionbump. A flat extraction of a block that instantiates
the same leaf cell several times in a chain (e.g. a ring of identical
2-input stages) collides several genuinely distinct nets onto one shared
name(KLayout's ownNetlistSpiceWriterdisambiguates them at write
time with a$1/$2/... suffix this tool does not control), and neither
namenor the existingpinboolean can tell which collided entry is
which.net_idis the net's own KLayoutcluster_id(unique per net
object, the same identityparasitics.nets[].net_idalready uses for
issue #765/#811's identical "several distinct nets, one label" shape);
pin_indexis that net's exact 0-based position in the written
.SUBCKT/instance-line port order when it is a promoted pin (null
otherwise), letting a caller resolve a specific.SUBCKTport index
straight back to anets[]entry with no separateklt lvsrun against a
reference schematic;label_positions_umreports every drawn label's own
(x_um, y_um)position naming that net, so a caller with independent
floorplan knowledge (a known pin location, e.g. fromklt place-and-route's DEF) can positively identify which collided entry is
the one it means. Also fixes_purge_preserving_named_netsto restore a
device-free, pinned net by its owncluster_idrather than byname,
so two distinct same-named survivors on one circuit no longer silently
collapse onto a single recreated net (the identical collision shape at a
smaller scale). Seedocs/cli/extract.md's "Net-name collisions from
internally-repeated sub-cells" section for a worked example. -
Added:
klt gen --list-pdk-pcellsandklt gen --pdk-pcell <library>/<cell>reach the PCell library a resolved PDK ships itself
underlibs.tech/klayout/python/, instead of only klt's own built-in
generators (issue #1535).--list-pdk-pcellsenumerates each vendor
library's registered name, source package, cells, and every cell's
parameter spec (name/type/default/description/hidden/settable);
--pdk-pcellinstantiates one and writes GDS through the same response
envelope a built-in generator emits, plus an additivepdk_pcell
{library, cell, package}object. klt is a thin passthrough here per
docs/ARCHITECTURE.md's "wrap the proven engine" rule: it puts the PDK's
ownpython/directory onsys.path, imports the vendor package, lets the
vendor's ownpya.Libraryregister itself under the vendor's own name, and
drives KLayout's ownLayout.add_pcell_variant()against the vendor's own
declaration — the same sequence the PDK's own KLayout autoload macro
performs. A vendor PCell is instantiated as an opaque cell
(device_countis always 1,portsalways[]), because interpreting
vendor geometry to infer devices or pins would be exactly the
hand-transcription step this feature removes. A package that needs a
third-party compat layer klt has no dependency on is reported, never
vendored or reimplemented:--list-pdk-pcellslists it underunavailable
with the missing module named (and still reports every library that did
load), while--pdk-pcellfails as an application error (exit 1) naming
the same module, never a traceback. Both real installs available at the
time this landed hit that path — sky130A'scellsis a plain-pya
library (not Cadence-DLO/cni-based) but reachesimport gdsfactory,
and ihp-sg13g2's chain reachesfrom cni.tech import Tech. See
docs/cli/gen.md's "PDK-shipped PCells" section. -
Added:
klt pdk findandklt pdk listreport ahas_pcell_library
boolean per resolved variant (issue #1535), so a caller can discover that a
PDK ships its own KLayout PyCell library without invokingklt gen --list-pdk-pcells. Additive field; it reports only that a Python package is
present underlibs.tech/klayout/python/, never that it imports in the
current environment. Seedocs/cli/pdk.md. -
Added:
klt gen-compose'sconnectivity[].legs[]field lets a caller
hand-route individual legs of a bundle (>2-pin) net by name (issue #1529).
waypoints_umsteers a 2-pin net's single backbone but was rejected
outright on any net with more than two pins, since a bundle net's spanning
tree has no single unambiguous leg for a caller-supplied path to belong
to — the only workaround was decomposing the net into several 2-pin
connectivity[]entries sharing onenetname, which then had to stay
pin-adjacent (each entry sharing a literal pin with the next) to avoid the
route-vs-route collision check's accepted-leg exemption — keyed on sharing
a pin, not anetname — rejecting two legs of the same net as if they
were a short between different nets.legs[]is an array of
{from_pin, to_pin, waypoints_um}objects, each naming one leg of the
entry's own net (from_pin/to_pinmust match two of that entry's own
pins[]); every named leg is routed through the same
routability-check path as any other leg (nothing is exempted, only
steered) and seeded into the spanning tree before the automatic
nearest-first search runs, so any pin not named inlegs[]still
completes automatically. Because every leg in onelegs[]array belongs
to the same net, they are never compared against each other by the
route-vs-route collision check, so non-adjacent legs of one bundle net
route cleanly with no pin-adjacency ordering required. Mutually exclusive
with the top-levelwaypoints_umon the same entry (an application
error, exit 1, if both are supplied). A named leg the router rejects
(its path crosses an unrelated block, or collides with an already-routed
net) does not fail the net by itself — the automatic search still runs —
but, unlike a rejected auto-selected candidate, it stays in
nets[].legs[]withrouted: falseand its ownreasoneven when the
net comes backstatus: "routed", so the automatic search can never
silently re-route around the caller's own steering. See
docs/cli/gen-compose.md's "Hand-routing individual legs of a bundle net
withlegs[]" section. -
Fixed:
klt gen-composenow rejects an inter-block leg whose escape
from a coordinate-tappedblocks[].cellport would draw a silent short to
a different net already present inside that same block, instead of
silently drawing it (issue #1527). Ablocks[].cellblock (an existing
GDS/OASIS stream this command did not generate) publishes noports[]of
its own — a caller taps a net by coordinate, hand-declaring a port
directly on one of the block's own internal wires.route_two_pin()
already models the block a leg starts/ends in by itsbbox_umonly, with
an unavoidable margin (_port_edge_margin_um) exempting the port's own
approach stub from that check — so the one region a coordinate-tapped
leg is guaranteed to draw metal in (its own approach stub) was the one
region with no obstacle model, and an escape direction that happened to
run across a different net's metal already drawn inside the same block
composedrouted: trueand DRC-clean (two overlapping shapes on one layer
merge into one polygon — a short, not a spacing violation, so no rule deck
can see it) whileklt extractsilently merged the two nets. This is now
caught the same way the existing route-vs-route check (#1057) is: the leg
is compared against its own block's other drawn shapes on the route layer
(excluding only the shape its own port lands on), and rejected into
unrouted_nets[]with alegs[].reasonnaming the block instead of being
drawn. Scoped toblocks[].cellendpoints only — agenerator_report
block (e.g.mos_array's own unreporteddummymatching columns,
suppressed fromklt extract's netlist by convention) can legitimately
draw real geometry this check cannot tell apart from an obstacle, so it is
left to the existing whole-block bbox check. Seedocs/cli/gen-compose.md's
new bullet under "Known limitations", and the corrected "Geometry is
advisory" note on whatunrouted_nets: []plus a cleanklt drcdoes and
does not prove for a coordinate-tapped composition. -
Fixed:
klt pexno longer fails every extracted-side corner with
"Could not find include file" when-o/--outputand/or--outdirare
given as relative paths (issue #1525). The extracted-side testbench
klt pexgenerates re-points its.includeline at the freshly-extracted
netlist; that line named the caller's-ovalue verbatim, but eachklt simcorner runsngspice -bfrom a corner-scoped working directory
nested under--outdir, which resolves a relative.includeagainst
that directory, not the invocation's own cwd — so a relative-othat
was perfectly valid from the caller's cwd resolved to nothing there. The
extracted netlist path is now resolved to absolute internally before it is
written into the generated testbench, regardless of how-o/--outdir
were spelled; this run's own JSON report continues to rendernetlistas
a repo-relative{path, scope}object (issue #1261), so the fix does not
leak an absolute path into committed evidence. See "The DUT.include
swap" indocs/cli/pex.md. -
Fixed:
klt gen-composenow rejects a via-drop landing pad (or
stub-widen box) that would DRC-violate against its own placed block's
drawn geometry, instead of silently drawing it (issue #1520). A
blocks[].cellblock (an existing GDS stream this command did not
generate) is modelled by itsbbox_umplus its declaredports[]—
hand-declared, since a pre-existing stream never reports its own ports.
Declaring a port on an internal wire is legitimate, but the fixed-size
landing padgen-composedraws there (independent ofwidth_um) could
land close enough to a different part of that same wire (e.g. a
perpendicular leg near a corner) to violate the resolved deck's own
same-layer minimum-spacing rule — a realklt drcfinding even though
both shapes are the same electrical node, since a rule-deck spacing check
is net-agnostic. This is now caught viakdb.Region.notch_check(a
same-polygon self-space check, as opposed to the existing route-vs-route
overlap/inflate check, which cannot see it: the pad merges with the wire
it lands on into one polygon, so there is no separate shape to compare
against) before anything is drawn — the net is reported unroutable
(unrouted_nets[],routed: false, alegs[].reasonentry naming the
block and the violated rule id) rather than composingrouted: true
with the violation left for a laterklt drcrun to discover. See
docs/cli/gen-compose.md's new bullet under "Known limitations". -
Added:
klt mom --touchstone <path>writes a standard 2-port
Touchstone (.s2p) file from the de-embedded S-parameters
report["full_wave_sweep"][i]["s_parameters"]already computes (issue
#1518) — the interchange format SPICE-family tools (ngspice, Xyce, ADS,
…) expect for N-port network data. Follows Touchstone Rev 1.1 (# HZ S RI R <z0>option line,S11 S21 S12 S22-per-line data order, no lossy
magnitude/phase round-trip). Raises a clearMomError(exit code 1) if
the report has no S-parameters (portsunset in the spec file) or if the
two ports'reference_impedance_ohmdiffer — Touchstone v1.1's single
scalarR <z0>cannot represent two different real reference
impedances, and this writer never silently averages them or emits the
inconsistently-supported Touchstone v2.0[Reference]syntax. See
docs/cli/mom.md's "Exporting Touchstone (.s2p)" section. -
Fixed:
klt gen-composeno longer hard-refuses composing blocks whose
GDS inputs disagree ondbu, as long as every disagreement is an exact
integer ratio (issue #1514, a regression from #1512).klt gen's output
dbu is now resolved from the target PDK's own tech LEFDATABASE MICRONS
(#1512) —0.0005umfor a gf180mcu-family PDK instead of the prior fixed
0.001um— butklt draw(deliberately PDK-unaware, #230) and any GDS
from an olderklt genbuild still always write0.001um, so composing
either against a freshly-generated gf180mcu-familyklt genblock used to
hit the same"which does not match the composed cell's dbu"error
#1496 fixed for the opposite pairing.gen-composenow resolves the
composed layout's own dbu as the finest dbu among all blocks, and
rescales every coarser block's geometry (and any internal hierarchy or
array pitch) losslessly onto that grid via akdb.ICplxTransinteger
magnification — the same rescale mechanicsklt place-and-route's
_merge_gds_viewalready uses for the analogous DEF/LEF-merge problem
(#1090). Composing blocks whose dbus differ by a non-integer ratio (a
genuine cross-PDK composition mistake, not a reconcilable grid mismatch)
still raisesGenComposeError. Each rescaled block adds one entry to the
response'swarnings[]naming the block, its original dbu, and the
composed dbu it was rescaled onto — additive field usage only, no
schema_versionbump. -
Added:
klt extract --pin-source-cells CELL[,CELL...](issue #1513) is
a third, positional declared-pin mechanism for composing several
already-independently-verified blocks — at least one of them a
placed-and-routed macro with generic internal pin labels (A,X,Q,
Y, ...) — into one flat top-level layout viaklt gen-composeplus
hand-drawn interconnect, where the composition itself has no single
governing DEF to anchor--def-pinson.--top-cell-pinscannot help
(a composition's own hand-drawn interconnect labels necessarily live in
an instanced sub-cell, not literally in the new top cell, so
--top-cell-pinsdemotes them right alongside the genuine internal
noise it targets);--pins/--def-pinsmatch by string, so two
independently-labelled macros that coincidentally share a generic pin
name (e.g. both useCLKinternally) both stay promoted once that
string is declared, with no way to keep only the intended one.
--pin-source-cellsresolves each drawn pin-name label found anywhere
under the top cell whose immediate owning cell is one of the named
cells to its actual extracted net by probing that label's own
composed-frame position, rather than by matching its text — demoting
every currently-promoted pin not reached this way, exactly as
--pins/--def-pinsdemote on a miss. Applied after--pins's and
--def-pins's own reconciliations (when given), so it can only further
restrict. Off by default (omitting the flag is byte-identical to
extraction before this feature existed).klt lvsexposes the same
control as thelayout.pin_source_cellsrequest field, mirroring
layout.declared_pins/layout.top_cell_pins. See
docs/cli/extract.md's "Pin-source cells" section. The same pass also
documents (indocs/cli/extract.md's "Declared pin set" section) that a
--format json-reportednets[].name(|-joined by
spice_safe_net_name) must not be pasted directly into--pins, whose
own matching reads the promoted net's,-joined internal name instead. -
Fixed:
klt extract --parasitics's substrate DC-tie (issue #1263) now
also declares its tied net(s) SPICE-global (issue #1503), closing the
gap where anX-instantiated testbench's ownvsubs(orvsubs_iso<n>)
node was electrically disconnected from the extracted subcircuit's
internal one -- the tie kept the instance's own node from floating (no
more singular-matrix error), but the parasitic ground-capacitance model it
anchors was silently computed against a node no testbench could reach or
drive. A.GLOBAL <net> ...card is now written once, before the first
.SUBCKT, for every synthesized substrate identity the extraction
produced (the deck-widesubstrate_netand each_iso<n>variant, issue
#1128); the pin interface,pin_count, and every otherparasiticscount
stay byte-identical (klt pex'spin_count_mismatch/flat_dut_mismatch
diagnostics, issue #1258, are unaffected). Additive JSON field:
parasitics.substrate_dc_tie.node_scope(always"global"), so a caller
can detect the node-scoping guarantee without reading the generated SPICE.
Seedocs/cli/extract.md's "Substrate DC reference" section. -
Added:
klt lvs'soptions.combine_devices: truenow checks every
combined capacitor device'sCparameter against the pre-combine sum of
the parallel group KLayout's ownNetlist.combine_devices()folded it
from, and corrects it in place when the two disagree (issue #1497). A
parallel capacitor'sCis mathematically a simple per-device sum — the
same ruleklt extract'sC = area_cap_f_um2 * A + perim_cap_f_um * P
deck formula relies on — so the total is a conserved quantity combining can
only redistribute, never change; a reported observation (10/10 repeat
calls against one real ~1000-device/~20-group extracted netlist) found
KLayout's own combine sometimes leavingCat a single pre-combine
instance's own value instead of the group's summed total, while the same
group's secondaryA/Pparameters combined correctly — with no
exception raised and no existingdevice.combine_incompletewarning to
catch it (that category only covers the unrelated #1185/#466
RuntimeError-on-partial-match failure mode, which never fires here).
Neither that report's own reduction attempt nor a follow-up investigation
here could force a reliable reproduction from a from-scratch synthetic
netlist at a comparable scale, so this check is applied unconditionally as
a defensive invariant rather than gated behind a confirmed repro. New
mismatches[].category:device.combine_parameter_corrected, always
severity: "warning"(never changesstatus), naming every corrected
device and its before/afterCvalues — seedocs/cli/lvs.md's
device.combine_parameter_correctedsection. Independent of the existing
device.combine_incomplete/options.combine_devices_max_attemptsretry
mitigation, which is unaffected. -
Fixed:
klt gen compose's router now validatesrouting.width_umand
sizes its via-drop squares against the resolved PDK deck's own
minimum-width DRC rules — the sameExtractionDeck/DrcRulesetklt drcjudges the composed layout with — instead of the documented0.17um
default and the PDK-independent_VIA_DROP_SIZE_UM(0.22um) constant
(issue #1501). On a family whose minimums exceed those values (e.g.
gf180mcu'smetal1.width.1=0.23um,via1.width.1=0.26um),compose
previously drew guaranteed-illegal geometry with no error or warning at
generation time — 172metal1.width.1violations on the documented default,
284via1.width.1violations on everymetal2route's via-drops, per the
issue's own measured reproduction.composenow rejects arouting.width_um
below the resolved deck's own minimum for the requested layer (and any
cross_block_layer_rolefallback layer), naming the violated rule and its
threshold, and sizes each via-drop's square tomax(_VIA_DROP_SIZE_UM, <deck's own via-layer width floor>)per distinct via layer actually drawn.
A PDK family whose deck cannot be resolved, or a layer with no matching
"width"rule, keeps the prior constants unchanged. Noschema_version
bump —compose's success-path payload shape is unchanged; the new failure
mode reuses the existingGenComposeErrorapplication-error path. -
Fixed:
klt gennow writes its output GDS at the resolved PDK's own
database unit, read from that PDK's tech LEFDATABASE MICRONS
declaration, instead of a fixed0.001umregardless of PDK (issue #1496).
For gf180mcu (DATABASE MICRONS 2000) a generated cell is now written at
0.0005um— the same dbuklt place-and-route's DEF→GDS merge already
derived from the same tech LEF — soklt gen-family blocks and
klt place-and-route-family macros built for the same PDK can finally be
placed in oneklt gen-composerequest, which requires a single shared dbu
across every block and previously refused the mix outright with
has dbu=0.0005, which does not match the composed cell's dbu=0.001. A PDK
whose tech LEF is missing, unreadable, or declares noDATABASE MICRONS
falls back to0.001um, so no request that previously succeeded can now
fail; sky130 (DATABASE MICRONS 1000) output is unchanged. A finer dbu
changes only the integer grid the shapes are stored on — every generator's
documented defaultparamsstaysklt drc --deck gf180mcuclean at
0.0005umand its drawn micrometre geometry is identical. Composing blocks
built against genuinely different PDKs still raises the dbu-mismatch
error: that is a real composition mistake, not a tooling artefact. -
Added:
klt genandklt gen-composeresponses now carrydbu_um, the
database unit their output stream was written at, so a caller can confirm
two blocks agree before composing them without a separateklt stats
round-trip (issue #1496). Additive field on both commands — no
schema_versionbump, perdocs/json-contract.md. -
Documented:
klt gen diff_pairplaces both legs at the same x column
per terminal — the common-centroid checkerboard puts oneQ1and one
Q2sub-instance in every column at the samex0, soQ1_<n>_S/_D/_G
and that column'sQ2_*ports report identicalx_umand differ only in
y_um. Previously a caller only discovered this by readinggen.pyor by
hitting the routing conflict: a one-column-per-pin floorplan (one
horizontal track per net, one vertical routing column per pin) cannot give
the two legs' distinct nets separate columns (issue #1495). The caveat, why
it is inherent to the interleave rather than a fixable oversight, and the
routing styles that do work are now indocs/cli/gen.md'sdiff_pair
section, and surfaced inklt gen --list(thediff_pairsummaryand its
splitsparamdescription— both are free-text fields, so this is not a
schema change). No geometry change: every generated cell is byte-for-byte
what it was. -
Fixed:
klt gen cap_array's reportedC<i>_TOPport now lands on a
routable escape pad instead of the unit cell's own interior centre (issue
#1494). The bottom plate spans almost the entire unit footprint (only
CAP_BOTTOM_PLATE_MARGIN_UM, 0.5um, clear on each side), so the old centre
position sat directly over it -- a caller stepping a via stack down from
there to route on lower metal had no way to avoid landing on the bottom
plate's own sheet, silently merging the two plate nets (invisible to DRC,
since nothing about it violates a same-layer spacing/width rule; only
extraction would report the merge, with the real top-plate net left
anonymous)._cap_unit_layoutnow draws a same-layer stub running north
from the original via-landing pad, clear past the bottom plate's own top
edge, ending in a second pad whose own bounding box no longer touches --
let alone overlaps -- the bottom plate;C<i>_TOPis reported there
instead, with a geometrically realdirection_deg(due north) rather than
the previous fixed placeholder. Both currently-supported families (sky130,
sg13g2) draw this escape by default -- no opt-in param, matching this
repo's existingring_gap_sideprecedent of not gating correct behavior
behind a flag. A hypothetical family with plates but no top-plate-via-metal
layer (not exercised by any currently-supported family) keeps the old,
unroutable centre position and gains adrc_hints.noteswarning instead.
Noschema_versionbump -- no field changed shape, only theC<i>_TOP
port'sx_um/y_um/direction_degvalues. -
Fixed:
klt lvs'sreference.form: "subckt-call"conversion no longer
silently mis-scales a bare (unsuffixed, non-exponent)L=/W=literal
on a device subcircuit call by1e6for a deck whose real schematic-flow
netlists carry an ambient SPICE.option scale=1.0u(issue #1492).
normalize_reference_netlistpreviously always read a bare literal as SI
metres, so a real sky130 xschem/ngspice netlist'sL=0.15(meaning0.15
um under sky130's own.option scale=1.0uconvention, confirmed against a
real fetchedopen_pdksinstall) converted toL=150000U— a plausible-
looking but wildly wrong device geometry that produced an indistinguishable
device.unmatched/net.unmatchedavalanche with no diagnostic pointing at
units. A bare literal is now resolved perreference.deck's own
klayout_tools.pdk_models.geometry_style_for_familyconvention: sky130
reads it as already-micrometres;gf180mcu/sg13g2/sg13cmos5l(which
set no ambient scale) are unchanged, still SI metres. An explicit unit
suffix (L=0.15u) or exponent (L=0.15e-6) was already unambiguous and is
unaffected either way. With noreference.deckgiven, a bare literal is
now a loudNormalizeError(surfacing as the usual application-levelklt lvserror) naming the offending device/parameter/value, instead of a
silent metres assumption. -
Fixed:
klt extract --def-net-namesnow recovers the real DEF net name
for an unrouted single-pin net — a tie-cell output, or any
synthesis-inserted constant driver whose net has one instance pin and
nothing to route to (issue #1488). KLayout's LEF/DEF reader stamps its
net-name shape property only onto the routed geometry it draws in the top
cell, so such a net had no name carrier anywhere and extracted as a
synthesized$<id>; several structurally identical ones in a design are
exactly what makes a downstreamklt lvsrun report an ambiguous-pairing
topologywarning per net.klt place-and-route's DEF→GDS merge now
synthesizes the missing carrier from the net's own pin geometry (the DEF
COMPONENTSinstance name KLayout records on each placement, the LEF
PIN/PORTrectangle, and the instance's placement transform), as a 2 dbu
marker shape drawn inside the already-drawn conductor — a geometric no-op
for DRC/LVS/extraction, needing no change to what--def-net-namesreads.
Nets a routed-metal shape already names are untouched, so issue #951's
behaviour is unchanged. Additive response field onklt place-and-route:def_net_names(single_pin_markers,
unresolved_single_pin_nets),nullunlessstage_reachedis"route",
mirroringlayer_map. A single-pin net whose pin geometry cannot be
resolved (no open_pdks layer-map file, an undeclared LEF macro/pin, a pin
centre not covered by drawn conductor) is named in that field and keeps the
previous$<id>fallback rather than failing the merge. -
Added:
klt gen mos_arraygainsadd_guard_ring(defaultfalse,
preserving byte-for-byte existing geometry) plusring_gap_side/
ring_gap_um/ring_gap_offset_um/ring_padding_um(issue #1493),
matchingdiff_pair's existing param set and composing the same
_ring_layout/ring-gap machinery (#434). This closes the "guard/tap ring
around a matched device group" idiommos_array— unlikediff_pair/
esd_device/bjt_array— previously had no way to draw for itself: a
caller needed a separately generatedguard_ringblock placed inside the
array's cavity, whichklt gen-composecould never route to (every
backbone segment crossing the enclosing block's bbox was rejected as a
collision, with no way to express "this block encloses that one"). With
the ring composed directly into the array's own geometry, reporting its
ownTAP_*/GAP_*ports, a route from the array's own device port to its
own ring's tap port is an ordinary same-block self-net — already covered
by the existing pin-block edge-margin allowance, so nogen-compose
change was needed. Deferred onsg13cmos5l(no"tap"role in that
family's curated deck) only when a request actually sets
add_guard_ring: true— every othermos_arrayrequest on that family is
unaffected. A generalgen-compose-level declared-enclosure relationship
for a ring around a separately-generated block remains a non-goal for
this issue.
Merged since 0.4.0, by commit type
Every commit on main in v0.4.0..v0.5.0 (235 commits), grouped by its
conventional-commit prefix; the trailing (#N) is the squash-merged PR.
The narrative entries above cite the originating issue where one exists.
Added
- feat(sta): from-scratch netlist input mode for klt sta (issue #1825) (#1832)
- feat(place-and-route,sta): pre-route timing checkpoint (nominal hold slack + unrouted DEF) (#1831)
- feat(wave): port VCD ingest and implement klt wave build (#1607)
- feat(wave): port bwave FST query engine, implement klt-wave query (#1602)
- feat(design-agent): device-level sky130 relaxation oscillator, 18/18 corners (#1822)
- feat(synthesize,place-and-route): add sg13g2_stdcell platform-table entries (#1791)
- feat(benchmarks): swap design-agent reference solutions to real sky130 devices (#1782)
- feat(synthesize): generate prefix adders and pick one by measured synthesis (#1772)
- feat(gen): populate res_array metal_level for gf180mcu rm1/rm2/rm3 (#1774)
- feat(gen): populate res_array metal_level for sg13g2 res_metal1/res_metal2 (#1769)
- feat(benchmarks): add charge-pump-pll hard-tier design-agent task (#1767)
- feat(benchmarks): add tool-using interactive design-agent provider (#1763)
- feat(loom): propagate a held PR's loom:operator onto its linked issue(s) (#1762)
- feat(sim): add
klt sim --plotwaveform SVG rendering (issue #1723) (#1757) - feat(benchmarks): add hard-tier voltage-controlled relaxation oscillator (VCO) design-agent task (#1753)
- feat(benchmarks): add medium-tier cascode, integrator and Schmitt trigger tasks (#1750)
- feat(benchmarks): add medium-tier design-agent tasks (5T OTA + two-stage Miller OTA) (#1746)
- feat(benchmarks): add hard-tier RC relaxation oscillator design-agent task (#1735) (#1745)
- feat(kb): nightly CI drift-check that re-runs measured testbenches (#1742)
- feat(benchmarks): add live-agent candidate provider to design-agent benchmark (#1732) (#1740)
- feat(sim): add per-device operating-point sanity lint (klt sim --op-lint, eval op-sanity gate) (#1730)
- feat(benchmarks): add design-agent benchmark harness (schema + runner + easy-tier tasks) (#1729)
- feat(gen-compose): add gf180mcu metal4/via3 routing roles (#1670) (#1675)
- feat(extract): add devices[].instance_path, attributing each device to its originating GDS instance (#1672)
- feat(extract): recognise gf180mcu's drawn metal-resistor family (issue #1640) (#1662)
- feat(drc): add isolated check kind, fix nwell.space.1 notch false positive (#1661)
- feat(gen): add metal_level param to res_array for sky130 res_generic_mN (#1639) (#1660)
- feat(gen-compose): decouple cross_block_layer_role's width from routing.width_um (#1620) (#1648)
- feat(extract): recognise sky130's drawn metal-resistor family (issue #1621) (#1641)
- feat(pdk): provision SG13G2 ngspice/OSDI sim toolchain (issue #1628) (#1644)
- feat(synthesize): report static leakage power (issue #1626) (#1635)
- feat(sta): add hold-side worst/negative slack fields to
klt sta(#1625) (#1634) - feat(pdk): curate a gf180mcu cross-section table for klt pdk stackup (#1629)
- feat(pdk): add
klt pdk stackup— PDK cross-section for field solvers (#1609) (#1615) - feat(clip): add
klt clipto write a bbox region or named cell out as its own stream (#1618) - feat(pdk): surface PDK PCell importability probe via
klt pdk pcell-check(#1611) - feat(functional-verification): add --mutations mutation testing (issue #1592) (#1604)
- feat(docker): add eda-sim overlay image on the loom-worker base (#1584)
- feat(gen-compose): support multi-level via-drop ladders (#1567) (#1570)
- feat(gen): add gf180mcu MiM-capacitor support to cap_array (#1561)
- feat(lvs): add options.combine_devices_per_circuit for composed macros (#1556)
- feat(gen): expose gf180mcu high-sheet-rho poly-resistor flavours in res_array (#1553)
- feat(extract): disclose positional pin/net identity for internally-repeated sub-cell name collisions (#1540) (#1543)
- feat(gen): instantiate a PDK's own shipped PCell library via --list-pdk-pcells/--pdk-pcell (#1538)
- feat(gen-compose): steer individual legs of a bundle net with connectivity[].legs[] (#1529) (#1536)
- feat(mom): add --touchstone (.s2p) export for two-port S-parameters (#1521)
- feat(extract): add --pin-source-cells for gen-compose'd multi-macro pin declaration (#1515)
- feat(gen): add add_guard_ring to mos_array (issue #1493) (#1499)
Fixed
- fix(buildgate): skip real ngspice/openroad tiers locally, raise timeout (#1651) (#1659)
- fix(pdk): resolve IHP sg13g2_stdcell's naming convention in synthesize/place-and-route liberty/LEF resolvers (#1796)
- fix(dep-recheck): recognize curator:named-dependency markers in idempotency check (#1788)
- fix(synthesize): bound _run_yosys with a timeout to avoid indefinite hangs (#1777)
- fix(equiv): apply WASI-sandboxed-yosys hint to equiv.py too (#1755) (#1768)
- fix(sim): parse_ascii_rawfile handles ac/sp Flags: complex rawfiles (#1761)
- fix: scope champion-issue-promo tier backlog counts to loom:issue only (#1716)
- fix(extract): recover DEF net names one level below a composed macro's top (#1696)
- fix(decks): add gf180mcu metal4.width.1 / metal4.space.1 (#1688) (#1692)
- fix(extract): --pins matches any component label of a multi-label net (#1690)
- fix(gen-compose): warn when a block's declared bbox_um understates its real geometry (#1679) (#1684)
- fix(gen-compose): retry route-vs-route collision onto cross_block_layer_role (#1683)
- fix(gen-compose): exempt a hollow block's cavity from the obstacle check (#1682)
- fix(gen-compose): scope obstacle-overlap check to layers a block actually draws (#1678)
- fix(loom): guard against inline-code-quoted closing keywords in PR bodies (#1677)
- fix(loom): match #N anywhere on named-dependency checklist lines (#1669) (#1671)
- fix(lvs): prune power-only layout circuits before gate-level-verilog compare (#1663)
- fix(lvs): compare circuits by identity in _prune_extra_top_circuits (#1664)
- fix(ci): make ci-apt-install.sh's mirror rewrite adaptive, not one-directional (#1665) (#1668)
- fix(place-and-route): add gf180mcu_fd_sc_mcu7t5v0 table entries (#1653)
- fix(lef-abstract): search every GDS datatype sharing a pin's LEF layer name (#1636)
- fix(sta): verify SPEF annotation after read_spef, not just net names (#1647)
- fix(lef-abstract): emit non-rectangular OBS polygons instead of crashing (#1642)
- fix(pdk-pcell): reach ihp-sg13g2's cni compat shim via sys.path hint + tkinter workaround (#1638)
- fix(extract): make SPEF *DATE header a fixed placeholder, not wall-clock (#1631)
- fix: unescape DEF identifiers in extract's --def-net-connections (#1632)
- fix(gen): cover gf180mcu guard/tap/collector-ring Comp shapes with implant (#1580) (#1583)
- fix(components): scope --label-layers texts to their own conductor (#1582)
- fix(gen): close mos_array's gf180mcu unit-device signoff-DRC gap (#1577) (#1581)
- fix(dep-recheck): extract Dependencies section by last match, any heading level (#1571)
- fix(extract): normalize bookkeeping fields before --check --rerun diff (#1562)
- fix: write bare C cards and add klt pex --deck-option/--pins passthrough (#1564)
- fix(lvs): carry #559/#1497 resistor-offset and capacitor-C corrections into combine_devices_per_circuit (#1560)
- fix(gen): snap res_array end contacts to the dbu grid before deriving edges (#1554)
- fix(gen-compose): reject unrecognized connectivity[]/legs[]/endpoint keys (#1549)
- fix(loom): restore FORMULA_VERSION guard reverted by resync (#1546) (#1547)
- fix(loom): add FORMULA_VERSION guard to dep-recheck-fingerprint.sh (#1544) (#1545)
- fix(loom): repair curator.md's dep-recheck-fingerprint.sh recipe after CLI resync (#1542)
- fix(lvs): scope net-mismatch merge/split classification per weakly-connected component (#1539)
- fix(curator): derive dep-recheck CONCLUSION_HASH from canonical state, not prose (#1534)
- fix(gen-compose): reject inter-block legs that short to their own block's other metal (#1527) (#1530)
- fix(pex): resolve extracted-side .include path to absolute before writing testbench (#1526)
- fix(curator): add regression guard for dep-recheck idempotency violations (#1523) (#1524)
- fix(gen-compose): reject via-drop pads that self-notch their own block's wire (#1522)
- fix(gen-compose): reconcile integer-ratio dbu mismatches instead of refusing (#1516)
- fix(gen): resolve output dbu from the PDK's tech LEF DATABASE MICRONS (#1512)
- fix(extract): declare substrate DC-tie nets SPICE-global so instantiated testbenches share the node (#1503) (#1511)
- fix(pnr): skip clock_tree_synthesis for a zero-fanout clock instead of segfaulting (#1506) (#1510)
- fix(lvs): correct capacitor C left unmerged by combine_devices() (#1497) (#1508)
- fix(layout-plan): forward routing.cross_block_layer_role instead of silently dropping it (#1509)
- fix(gen): floor routing width and via-drop size at the resolved deck's own DRC minimums (#1507)
- fix(lvs): resolve bare L=/W= geometry literals per deck's .option scale convention (#1505)
- fix(gen): give cap_array's C_TOP port a routable escape pad (#1500)
- fix(extract): recover DEF net names for unrouted single-pin nets (#1498)
- fix(lvs): expand nf>1 MOS subckt calls into parallel plain-element fingers (#1489)
Changed
- refactor(decks): split decks/init.py into rules/extraction/parasitics submodules (#1824)
- refactor(place-and-route): split DEF->GDS merge subsystem into place_and_route_gds_merge.py (#1823)
- refactor(cli): extract checks-cluster parsers into _add_X_parser() helpers (#1807)
- refactor(place-and-route): split corner-sweep/post-route-SPEF STA into place_and_route_sta.py (#1819)
- refactor(cli): extract misc-cluster parsers into _add_X_parser() helpers (#1811)
- refactor(cli): extract 5 generation-verb parsers into add_parser() helpers (#1818)
- refactor(cli): extract analysis-cluster parsers into add_parser() helpers (#1817)
- refactor(cli): extract 5 inspection-verb parsers into add_parser() helpers (#1802)
- refactor(lvs): split netgen engine subsystem into lvs_netgen.py (#1806)
- refactor(cli): extract 'extraction/verification' cluster parsers into add*_parser() helpers (#1809)
- perf(benchmarks): cross-step cache for design-agent-benchmark reference solutions (#1794)
- perf(benchmarks): cache deterministic reference-provider attempts, re-tune CI timeout from measured run (#1792)
- refactor(cli): consolidate duplicated _print_rerun_text into output.render_rerun_drift (#1787)
- refactor(benchmarks): split interactive agent provider into its own module (#1770)
- refactor(sim): split fleet shard/merge + remote dispatch into sim_remote.py (#1765)
- refactor: dedup SPICE + continuation-line fold into _paths.py (#1741) (#1751)
- refactor(lvs): split mismatch-classification subsystem into lvs_mismatch.py (#1724)
- refactor(gen-compose): split routing subsystem into gen_compose_routing.py (#1717)
- refactor: dedupe _tcl_net_list/_count_spef_nets_annotated into _paths.py (#1715)
- refactor(gen): split ten build_pcell() factories into gen_pcells/ package (#1713)
- refactor: extract _load_block_cell to dedupe gen_compose GDS-load block (#1711)
- extract: add --parasitics-net to scope the ground R/C pass to named nets (#1705)
- extract: expose per-net R/C breakdown by layer in --parasitics JSON (#1701) (#1706)
- sim: add coarse timeout-budget preflight warning (part of #1686) (#1695)
- refactor: deduplicate _validate_vias between power.py and erc.py (#1693)
- refactor(gen): split _build_pcell_classes into per-family factories (#1650)
- refactor: remove unused build_identity.git_commit()/is_release() accessors (#1646)
- refactor: dedupe _run_openroad/_count_violations/_openroad_version into _openroad_engine.py (#1643)
- klt synthesize: additive structural verdict, warnings summary, baseline QoR delta (#1605)
Docs
- docs(design-agent): split-polarity dual comparator clears the sky130A ss/-40C/1.62V headroom ceiling (#1815)
- docs(kb): document sky130 binned nfet L=0.15/nf>=3 BSIM4 fatal footgun (#1812)
- kb: add reference netlist + measured figures for sky130-lc-vco-cross-coupled (#1805)
- kb: add reference netlist + measured figures for multi-ro-xor-entropy-source (#1778)
- kb: add reference netlists + measured figures for ro-puf, metastability-trng, ring-oscillator-jitter-trng (#1776)
- kb: add reference netlists + measured figures for beta-multiplier-bias-cell, cmos-subthreshold-voltage-reference and cmos-ring-vco-current-starved (#1773)
- kb: add reference netlists + measured figures for rx-front-end-termination-buffer and sram-power-up-puf (#1760)
- kb: add reference netlists + measured figures for folded-cascode-ota and cml-tx-line-driver (#1754)
- kb: add reference netlists + measured figures for two-stage-miller-ota and strongarm-latch-comparator (#1747)
- kb: add reference netlists + measured figures for five-transistor-ota and inverter-based-comparator (#1737)
- kb: add measured figures block + numeric search filters (av_db>=40 style) (#1727)
- docs: document daemon dispatch race claiming a reverted promotion (#1710) (#1712)
- docs: design spike — attribute --parasitics R/C to top-cell-drawn vs. instance-inherited geometry (#1707)
- docs(extract): document parasitics star addressing and device-card node order (#1676)
- docs: port per-concern RTL/testbench review guides into docs/guides/digital-review/ (#1612)
- docs(design): survey klt wave query surface and trace formats (#1598)
- docs(guides): port booley's RTL mutation-proposer guide (Apache-2.0) (#1597)
- docs(design): port-vs-build decision record for klt wave (#1596)
- docs(design): propose the klt wave build/query JSON contract (#1595)
- docs(design): survey mutation testing as a test-quality gate for functional-verification (#1594)
- docs(gen,drc): disclose gf180mcu mos_array's real-signoff-DRC gap (#1575) (#1578)
- docs(gen): add dog-bone terminal recipe for W below UNIT_MIN_W_UM (#1576)
- docs(layout_plan): update stale "Phase C not-yet-built" claims (#1566)
- docs(loom): document local Loom test-suite wiring convention (#1532) (#1537)
- docs(gen): state diff_pair's shared per-terminal x column in docs and --list (#1504)
Chores
- test(place-and-route): gate a real ihp-sg13g2 GCD integration test (#1784) (#1816)
- chore(deps-dev): bump the npm-minor-patch group across 1 directory with 5 updates (#1573)
- chore(deps): bump flate2 (#1482)
- ci: declare python3-click for the pinned SymbiYosys launcher (#1685)
- ci: run native-techmap on the dedicated heavy runner (2am#29) (#1246)
- chore(decks): regenerate deck history table for v0.4.0
- chore: resync installed Loom surfaces (72 commits, no PR)