Tap insertion and bounded clock topology — v0.4.0¶
The real-PDK preparation path now reserves and inserts well/substrate tap cells,
then checks their physical contacts independently using original PDK GDS. A
separate clock-tree transaction inserts a level-balanced buffer topology and
rebuilds placement, taps, fillers and the primary supply network. This is an
unrouted CTS prototype. Signal routing, parasitic extraction, propagated-clock
timing and complete RTL-to-GDS signoff remain unfinished.
Native implementation¶
rustroad-core::tap reserves a tap at both ends of every row and distributes
additional site-aligned slots until each clear horizontal gap satisfies the
configured limit. Logical cells pack into the free intervals in deterministic
order. The placer rejects a cell that cannot fit between taps; it does not
silently expand the permitted gap. Tap count, total instances, work and integer
coordinate arithmetic have explicit limits. See the upfront resource
estimate.
PhysicalConfig.tap binds cell, max_gap, well_layer and substrate_layer.
The selected LEF master must be CORE WELLTAP, have only supply pins and expose
the configured primary rails and well/substrate pin geometry. The two checked
profiles use:
| PDK | Tap master | Width × height (µm) | LEF well/substrate |
|---|---|---|---|
| sky130_fd_sc_hd | sky130_fd_sc_hd__tapvpwrvgnd_1 | 0.46 × 2.72 | nwell / pwell |
| gf180mcu_fd_sc_mcu7t5v0 | gf180mcu_fd_sc_mcu7t5v0__filltie | 1.12 × 3.92 | Nwell / Pwell |
Both profiles allow at most 20 µm between tap-cell edges. LEF uses 1,000 DBU/µm for Sky130 and 2,000 DBU/µm for GF180. GDS coordinates are converted exactly; the checked source grids need factors 1 and 2 respectively. Nonintegral conversion fails. LEF layer names are not used as proof of GDS contact presence.
Schema 4 stores generated tap identities and bounded reports, and mirrors tap
positions in the mmap physical table. Opening a snapshot regenerates the
expected placement policy and checks actual tap positions. Repeated place
removes generated taps/fillers and recreates the same layout. Schemas 1–3 remain
readable. Logical Verilog omits physical-only taps/fillers; powered Verilog and
DEF include them and their supply connections. Clock buffers remain in both
Verilog views.
Experimental staggered rows¶
An explicit optional tap.pattern selects a staggered lattice. For example,
"max_gap": 30000, "pattern": {"kind": "staggered", "phase": 15000}
uses DBU, not microns. Absent pattern retains the original planner and JSON
output. No public PDK profile selects this experiment by default.
max_gap still bounds the clear horizontal gap between tap-cell bounding
boxes. The staggered mode also uses it as the nominal lattice pitch; pitch
and odd-row phase round down to whole sites. Phase is relative to the physical
row number from the core origin, never the macro-cut segment number. The
lattice's x origin is the core's left edge. Each row segment keeps taps at both
ends, including macro halo boundaries; a short segment that excludes every
lattice anchor receives a bounded interior repair when needed. Narrow segments
that cannot hold the boundary taps fail explicitly. Placement, free intervals,
persisted-policy validation and repeated placement all use this same policy.
The native gap metric is distinct from foundry diffusion-to-corresponding-tie
distance. The pattern does not establish connected well/body domains, electrical
contact or latch-up compliance. Its report retains
requires_independent_GDS_verification. Candidate acceptance requires original
GDS geometry in connected domains and unchanged foundry rule runs. In the pinned
IHP decks, main emits LU.b and maximal emits LU.a; retain both full category
inventories and their missing-tie negative controls. Neither deck name alone
establishes both rule scopes. No full-flow or default promotion follows from
planner tests or a component placement replay.
Independent physical verification¶
verify_taps.py invokes pinned OpenROAD/OpenDB as an external process to read
the exported DEF and the original technology/cell LEFs. It checks placement,
tap spacing, primary PG connectivity and powered-Verilog endpoint partitions.
It does not accept RustROAD's own success flag as the physical oracle.
check_tap_gds.py runs in external KLayout and reads the pinned original merged
GDS library and its layer-property map. Polygon Boolean operations verify:
- The actual tap has n+ contacts inside nwell and p+ contacts in substrate/pwell.
- Each contact reaches the correct primary rail. Sky130 follows tap → licon → li1 → mcon → met1; GF180 follows COMP → Contact → Metal1. A connection to the opposite rail fails.
- Every assembled well/substrate component has a tap, including empty-row filler wells. Every inspected active polygon lies within the configured 15 µm Manhattan contact coverage in the same component.
- All N/FS instance transformations are applied to actual GDS polygons. Logic input cells must contain active geometry, preventing a vacuous distance pass.
The 15 µm limit is a conservative engineering acceptance policy. It is not a complete foundry latch-up implementation. Sky130's nwell.4 rule requires a contacted tap in each nwell, with stated exceptions. GF180 documents conditional core latch-up limits and separate COMP distance rules DF.13/14. The checker does not encode every voltage, spacing, marker and exception in those rule decks. Deep-well cases are explicitly rejected. I/O guard rings, contact enclosure/area DRC, full LVS, IR drop and electromigration remain unchecked.
Missing contact, missing taps, end-only taps with excessive interior distance and wrong tap supply controls must all fail. The inherited power checker also rejects row polarity, filler-gap, spine-gap, short and interior-I/O defects.
Clock topology and its limits¶
PhysicalConfig.clock explicitly binds the source port, sink pin and a
single-input/single-output buffer master. max_fanout, max_buffers and
max_work cap insertion and checking. The current algorithm sorts sinks by
their existing position and groups them bottom-up into equal buffer depth.
The topology is re-linked and checked before an atomic workspace commit.
Unsupported clock source aliases, non-clock root loads, repeated synthesis,
stale base revisions and insufficient capacity fail explicitly.
The verification harness proves both Boolean input values of the actual Liberty buffer with Yosys and rejects inverted polarity. Whole-design RTL equivalence and 512-vector Verilator checks follow. A separate OpenDB checker traces each sink backwards through unique drivers, checks all inserted buffers and fanout, and rejects bypassed sinks and disconnected buffers. Post-clock tap/GDS/PG checks run again. Repeated placement must produce byte-identical DEF.
The six-sink fixture on each PDK produces six buffers, three levels and fanout
at most two. sky130_fd_sc_hd__clkbuf_1 uses A/X; GF180's corresponding master
uses I/Z. This is not a delay- or skew-optimized clock tree. The source port is
configured directly, not discovered from SDC. Gated/generated clocks, sink
capacitance limits, clock routing, propagated clock latency and skew closure are
future work. clock_topology_status therefore reports
unrouted_topology_checked, while routing remains not_run.
Reproduction¶
Build with the repository's pinned Rust toolchain and resource limits. The local profile pins Ciel/open_pdks source files; adjust paths to identical files on another machine. Do not substitute guessed geometry or unpinned libraries.
free -h
ulimit -v 8388608
export CARGO_BUILD_JOBS=2 RUST_TEST_THREADS=2 RAYON_NUM_THREADS=2
/usr/bin/time -v -o .work/tap-build.time timeout 180s \
cargo build --release --locked --offline --bin rustroad
/usr/bin/time -v -o .work/tap-verification.time timeout 180s \
python3 scripts/verify_taps.py --workspace .work/tap-verification --base-rev empty
The harness uses a fresh evidence workspace, its own workspace.lock and
--base-rev empty. Each EDA mutation also uses the native revision contract.
Children receive 2 GiB memory/30 s guards; OpenROAD has an 8 GiB virtual-address
allowance but a 2 GiB/no-swap container limit. Both PDK cases execute serially.
KLayout and Python belong only to the external test harness, never the core.
For an imported physical design:
rustroad import-pdk --workspace .work/chip --base-rev rev0 \
--tech-lef technology.tlef --cell-lef cells.lef --netlist mapped.v \
--floorplan floor.json --physical-config fixtures/pdk/sky130-tap.json
rustroad place --workspace .work/chip --base-rev rev1
rustroad clock-tree --workspace .work/chip --base-rev rev2
rustroad export-def --workspace .work/chip
rustroad export-verilog --workspace .work/chip --include-power
Use matching GF180 paths and profile for that PDK. Return revisions, rather than the example's fixed revision strings, should drive an automated flow.
Evidence and next gates¶
The HTML report, complete compact result and build/test receipt record exact counts, input and executable hashes, timing scope, negative controls, wall time and peak RSS. Full process logs remain in the recorded local evidence directory.
RustSTA/OpenSTA comparisons are TT, ideal wire and ideal clock. Both retain 14 constrained endpoints and two constant-driven D pins without data arrival; only those independently identified endpoints are accepted. Pulse-width and minimum-period limitations remain explicit. Incremental-vs-full testing is a no-op update, not a changed-ECO test. The original RustSys/RustSTA binaries stay frozen and neither sibling source tree was modified.
Next: derive clock constraints and transition/capacitance targets, legalize under those constraints, implement bounded obstacle-aware signal/clock routing, then extract corner-tagged RC/SPEF and validate propagated timing against an independent tool. GDS streamout and full-deck DRC/LVS are still required to close the complete RTL-to-GDS flow.