Native GDSII streamout (export-gds)¶
Status: implemented and black-box verified on the routed pipeline fixture for
SKY130 (sky130_fd_sc_hd) and GF180 (gf180mcu_fd_sc_mcu7t5v0) by
scripts/verify_gds.py (below). Scope is a block/macro: no pad ring, no seal
ring; IR/EM/SI/OCV/CPPR are unchecked. export-gds itself reports
drc_status: "unchecked"; the foundry checks run in the harnesses below.
Resource contract: docs/gds-budget.md. Run receipts:
docs/evidence/gds-verification.json (historical evidence, kept for
reproducibility).
Command¶
rustroad --workspace W export-gds \
--cell-gds <PDK standard-cell .gds> \
--layer-map fixtures/pdk/<pdk>-gds-map.json \
--out <file.gds> \
[--cell-lef <cell LEF; must match the imported sha256>] \
[--max-gds-bytes 268435456] [--max-gds-records 16000000]
- Read-only: takes the shared workspace lock, has no
--base-rev, never writes inside the workspace (GDS_OUTPUT_IN_WORKSPACE), never overwrites an input or a non-regular file. HEAD and every revision file are unchanged (checked by tree hash in the harness and intests/gds_export.rs). - Atomic: streamed through a byte/record-capped hashing writer into a hidden temporary file in the destination directory, fsynced, renamed, directory fsynced. Any failure removes the temporary file; nothing partial is published.
- Deterministic: fixed BGNLIB/BGNSTR dates (1970-01-01 00:00:00), LIBNAME = design name, no paths or timestamps. Copies of a workspace at different paths stream identical bytes.
- Result (stdout JSON):
status: "written",revision,top,output {path, sha256, bytes},records,cell_gds/layer_map/cell_lefidentities,foreign_bindings_checked,lef_dbu_per_micron,gds_dbu_per_micron,coordinate_divisor,pdk_structures,via_structures,elements {instances, routed_wires (merged runs), stored_wire_records, routed_vias, routed_patches, supply_wires, supply_vias, pin_shapes, labels, boundary},routing_status,drc_status: "unchecked",lvs_status: "unchecked",scope: "block_level_no_pad_or_seal_ring",workspace_mutated: false. - Errors exit 2 with
{"status":"error","code":...}. GDS codes:GDS_ODD_COORDINATE,GDS_COORDINATE_RANGE,GDS_UNIT_MISMATCH,GDS_UNITS_UNSUPPORTED,GDS_CELL_MISSING,GDS_NAME_COLLISION,GDS_LAYER_MAP,GDS_LAYER_UNMAPPED,GDS_LAYER_KIND,GDS_WIDTH,GDS_VIA_MISSING,GDS_PIN_GEOMETRY,GDS_LABEL_UNSUPPORTED,GDS_FOREIGN_MISSING,GDS_FOREIGN_MISMATCH,GDS_SOURCE_MISSING,GDS_SOURCE_CHANGED,GDS_CELL_LEF,GDS_OUTPUT,GDS_OUTPUT_IN_WORKSPACE,GDS_UNPLACED,GDS_UNSUPPORTED_WORKSPACE,GDS_BUDGET,GDS_IO, plus the reader codes below for a malformed cell library.
What is streamed¶
| Structure / element | Source and semantics |
|---|---|
| PDK cells | Every used master plus its SREF/AREF closure (iterative DFS, depth cap 64, cycle guard), copied byte-identically from the cell GDS in library order. Missing structure: GDS_CELL_MISSING. |
VIA_<name> |
One structure per used fixed LEF VIA (routed + supply), its RECT/POLYGON shapes on the map's via targets. Name collisions with any cell-GDS structure or the top: GDS_NAME_COLLISION. |
| Instances | SREF per instance in instance order. Row N: no STRANS at (x, y). Row FS: STRANS x-reflection at (x, y + height). |
| Routed wires | DEF NETS semantics: layer default WIDTH, half-width extension at both ends (exact wire_rect). One BOUNDARY per maximal collinear same-net run on net targets, the same runs export-def writes (rustroad_core::wire_runs): stored lattice records that touch or overlap on one track line are merged, and a run is split only at a strictly interior via landing (lower or upper metal), pin-access point or same-layer branch. The metal union equals the stored records'. |
| Routed / supply vias | SREF to VIA_<name> at the via point. Every used LEF via variant (LEF-rule routing, e.g. M1M2_PR_MR, Via1_VH, Via2_HH) gets its own structure. |
| Routing patches | Every Routes.patches rectangle (DEF routing RECT, same-net notch/corner fill) as one BOUNDARY on the routing layer's net targets. |
| Supply rails/spines | SPECIALNETS geometry (explicit width, half-width extension) on special_net targets. |
| Pins | I/O and supply pin rectangles on every pin target; one TEXT per pin at the rectangle centre on the label target. Bus bits are labelled a[1]; supply ports VDD/VSS. Escaped/non-printable names: GDS_LABEL_UNSUPPORTED. |
| PR boundary | Die area on the map's boundary layer (SKY130 235/4, GF180 0/0). |
Units: coordinates are integer LEF DBU. The cell-GDS UNITS are copied verbatim
and must be an exact decimal micron grid (dbu_per_micron: user unit 10^-k um,
meter unit 10^-(6+k), compared as correctly rounded binary64). When the LEF grid
is an integer multiple of the GDS grid every coordinate is divided exactly
(GF180: 2000 DBU/um -> 0.001 um, divisor 2); any remainder is
GDS_ODD_COORDINATE. Library units are never rescaled (the GF180 deck has an
explicit 0.001 um DBU rule).
FOREIGN: the cell LEF recorded at import (sha256-pinned; --cell-lef may
relocate it) is re-read; each used master must have exactly one FOREIGN
naming itself, offset 0 0, orientation N/absent, and ORIGIN 0 0. The LEF
reader now retains FOREIGN name [x y [orient]] as LefMacro.foreign:
Vec<LefForeign>; import still uses LEF geometry and records a diagnostic.
Layer map profile (rustroad.gds-layer-map.v1)¶
Tracked profiles: fixtures/pdk/sky130-gds-map.json,
fixtures/pdk/gf180-gds-map.json.
{
"schema": "rustroad.gds-layer-map.v1", "pdk": "sky130A", "library": "sky130_fd_sc_hd",
"sources": [{"id": "klayout_map", "role": "...", "path": "...", "sha256": "<64 hex>", "bytes": 1676}],
"layers": [
{"lef": "met1", "kind": "routing",
"net": [[68, 20]], "special_net": [[68, 20]], "via": [[68, 20]],
"pin": [[68, 20], [68, 16]], "label": [68, 5],
"cite": [{"source": "klayout_map", "sha256": "<same>", "lines": [28, 29, 31]},
{"source": "magic_tech", "sha256": "...", "lines": [1203, 1204, 1206, 1208, 1210, 1212]}]},
{"lef": "via", "kind": "cut", "via": [[68, 44]], "cite": [...]}
],
"boundary": {"layer": [235, 4], "cite": [...]},
"notes": ["..."]
}
Rules (LayerMap::validate, unknown fields rejected): routing layers need
non-empty net, special_net, via; cut layers only via; at most 8 distinct
targets per purpose; every entry and the boundary cite at least one listed
source with a matching sha256 and 1..32 line numbers. export-gds also checks
the LEF layer TYPE against kind (GDS_LAYER_KIND). scripts/verify_gds.py
re-derives every claim from the cited KLayout LEF/DEF map (purposes NET, SPNET,
VIA, PIN, NAME .../LABEL, DIEAREA, and that all relevant lines are cited) and
requires a cited Magic calma <layer> <datatype> line for each target.
Library APIs¶
rustroad_formats::gds:Real8(exactfrom_f64, correctly roundedto_f64,parts,is_exact_f64),RecordReader,parse_library(bytes, &GdsLimits, retain_elements) -> Library,Library::{closure, index, libname},GdsWriter::{begin_library, begin_structure, rectangle, boundary, sref, text, element, raw, end_structure, end_library, into_inner},write_library,dbu_per_micron. Reader codes:GDS_TRUNCATED,GDS_SYNTAX,GDS_UNSUPPORTED,GDS_BUDGET,GDS_NAME,GDS_GEOMETRY,GDS_DUPLICATE_STRUCTURE,GDS_MISSING_STRUCTURE,GDS_REFERENCE_CYCLE,GDS_REAL,GDS_RECORD_LENGTH.rustroad_formats::gdsmap:LayerMap,LayerEntry,GdsLayer,Purpose,LayerKind,SCHEMA.rustroad_workspace::gds:export_gds(&Path, &ExportOptions, &Limits) -> Result<GdsReport>,ExportOptions::new(cell_gds, layer_map, out),Scale,FIXED_DATES,GDS_VERSION(600),VIA_CELL_PREFIX(VIA_).rustroad_workspace::Error::Gds { code, message }(new coded variant).
Verification¶
python3 -I scripts/verify_gds.py --run .work/<new dir> \
--source .work/rc-timing/ready --rustroad target/release/rustroad
The harness copies the routed workspaces (<pdk>_physical) of a finished
flow run and runs every third-party tool only as a black box in the pinned
image 4c48415d8bd0 (KLayout 0.30.9, Magic 8.3.678, Netgen 1.5.323). Exit
codes are not trusted: reports are parsed, the Magic top cell must exist, both
DRC decks must report 0 on the native GDS, and a planted 0.1 x 0.1 um square on
the wire layer must raise both counts (positive control). Every DEF RECT
patch must be streamed on its layer's net purpose, and deleting those
rectangles must make the XOR fail. LVS runs the basic Netgen/KLayout checks and
both LVS checks of scripts/signoff (named-net and port pairing).
Negative controls (each must be detected):
- deleted signal wire (the largest signal wire, a DEF NETS path): XOR difference, and Netgen and the signoff KLayout check fail; the basic KLayout check alone still says match, which is why the signoff checks are required;
- wrong map entry (met2/Metal2
netdatatype changed): XOR differences on the drawing layer plus a spurious layer; the citation verifier rejects it; - missing cell structure: removed from the output -> XOR differences on all
cell layers; removed from the cell library ->
GDS_CELL_MISSING, exit 2, no output file; - patches removed: XOR differences only on the patch layers;
- planted sub-minimum square: both decks count it;
- SKY130 density without the 235/4 boundary -> non-finite densities, reported failed (not a silent pass);
- odd GF180 coordinate (a Via1_HV cut edge planted in a workspace copy):
refused by the workspace reader (
INVALID_WORKSPACE, LEF-rule via enclosure / supply via grid) before streaming, or by the exporter (GDS_ODD_COORDINATE); exit 2, no output file.
KLayout deep-mode items are counted over every cell. Density is reported
separately and is never part of the DRC verdict (owner decision). The
per-run tables of the stream b0 requalification and of the earlier phase-A
run are in docs/evidence/gds-verification.json, kept as historical
evidence for reproducibility, not as current results.
Rust tests cover the GDS codec round trip, exact real8 vectors incl. the PDK
UNITS bytes and round-to-even decoding, budgets, every-prefix truncation,
2,000 single-bit mutations, grammar violations, closure depth/cycles, unit
grids, layer-map validation, FOREIGN parsing, and a routed self-authored
workspace: determinism, read-only, FS/N transforms, labels, caps, missing
cells, collisions, wrong maps, FOREIGN mismatch, changed LEF, 2000-DBU exact
division and odd-coordinate rejection. Ignored real-PDK tests (run with
explicit paths) parse both PDK cell libraries and re-encode them
byte-identically through the typed model, check that every cell-LEF FOREIGN
binding names its own macro at offset 0, and stream verbatim cells from
copies of both routed workspaces. Strict Clippy (-D warnings) and rustfmt
are part of the checks.
Limitations¶
- Only N and FS rows exist in RustROAD; other orientations are not emitted.
- The exporter does not repair DRC; it streams exactly what was routed.
- Labels for escaped Verilog identifiers are rejected rather than guessed.
- The workspace loader does not re-verify stored technology VIA geometry against its LEF byte for byte. With LEF-rule routing it does re-derive the routing vias (cut ENCLOSURE, variant families) and the supply vias (manufacturing grid), which now refuses the planted odd-coordinate via. Export re-verifies the cell LEF (FOREIGN) by sha256 only.
- Netgen/KLayout LVS references are RustROAD's own powered export; the independent RTL-to-powered CEC link is a separate work item (W10).