ADR 0007: Binary timing result store (sta.rbin) and zero-copy mapped queries¶
- Status: accepted
- Date: 2026-10-17
Context¶
Phase 1 of RustSTA had no persistent binary timing store; every report_checks, report_tns, or
report_wns invocation required re-parsing the gate-level Verilog netlist, Liberty libraries, and
SDC constraints and re-running timing propagation. In Phase 2 (STA-REQ-0302, STA-REQ-0312 to
STA-REQ-0315, WS-REQ-0030 to WS-REQ-0038), ruststa run writes sta.rbin (STA v1) into a
workspace revision (rev/<n>/sta.rbin), and read-only commands (ruststa query, rusteda inspect,
rusteda summary) must answer summary, endpoint, path, and pin queries from a cold process on a
1M-cell netlist in <= 25 ms (warm) / <= 50 ms (cold) without loading the netlist or libraries.
Three layout tensions govern sta.rbin:
- Per-node storage size vs. multi-clock launch tags: most pins in a digital block are reached
by one active clock launch tag, while a minority of pins in clock-muxed or latch-borrowing cones
carry multiple active tags. Allocating
T_maxtag slots inline per node bloatsNODE_RECwhenT_maxis large, whereas storing all tags out-of-line adds an extra cache-line indirection on every single-clock pin query. - Pre-materialized paths vs. arbitrary-
Npath queries: materializingPathPointRecarrays for 10,000 paths bloatssta.rbin, but walking predecessors backward throughNODE_RECon the fly causes dependent pointer-chasing cache misses across the multi-hundred-megabyteNODE_RECtable. - Open latency vs. payload integrity: computing
xxh3-64over a ~250 MBsta.rbinfile at 5–10 GB/s takes 25–50 ms by itself, consuming the entire cold-query latency budget before any section is read.
Decision¶
We adopt the 24-section STA v1 layout specified in docs/specs/sta/store.md:
- Inline tag 0 + CSR overflow (
NODE_REC+TAG_EXTRA_PTR+TAG_EXTRA): NodeRec(152 bytes) groups hot fields into two 64-byte cache lines plus a 24-byte tail:- Cache line 0 (bytes
0..64):slew_ns([[f64; 2]; 2], 32B) and worst nodeslack_ns([[f64; 2]; 2], 32B). - Cache line 1 (bytes
64..128):arr0_ns([[f64; 2]; 2], 32B) andreq0_ns([[f64; 2]; 2], 32B) for the first active launch tag (tag0_id). - Tail (bytes
128..152):pred0([[u32; 2]; 2], 16B),tag0_id(u32),active_tags(u16), andflags(u16).
- Cache line 0 (bytes
- Additional active tags (
active_rank >= 1) are stored in CSR form inTAG_EXTRA_PTRandTAG_EXTRA(56 bytes per extra active tag). Single-clock designs pay zeroTAG_EXTRArecords. - Two-tier path storage (
PATHS/PATH_POINTS+PATH_RANK/PATH_CHAIN): - The top
K(top_k_paths, default100) worst paths are materialized directly inPATHS(56BPathHeaderRec) andPATH_POINTS(32BPathPointRec), answering commonquery pathsrequests with a single contiguous slice read. - Up to
path_limit(default10,000) ranked paths are indexed inPATH_RANK(8B) and stored as compact forward chains of packedu64steps inPATH_CHAIN_PTR+PATH_CHAIN(8 bytesper step:node_id,tr,point_kind,active_tag_rank,corner_node_bucket). - Reconstructing a path with
rank >= Kscans itsPATH_CHAINslice in two passes: Pass 1 prefetches the two 64-byte cache lines of each step'sNodeRecin batches of 8 before Pass 2 reads arrivals and slews, replacing sequential pointer-chasing stalls with pipelined memory reads. - Zero-allocation
"inst/pin"hashing (NAME_HASHandINST_HASH): - Pin names follow the deterministic composite pattern
"<inst_name>/<pin_name>"without storing a separate heap string per pin inSTRINGS.hash_node_namehashes"inst/pin"on a 256-byte stack buffer withxxh3_64and disambiguates hash collisions viaNodeId, keepingSTRINGSproportional toN_ports + N_instances + N_cellsrather thanN_pins. - Two-mode open contract (
O(1)structure validation vs. fullxxh3-64): - Mapped query opens (
StaStore::from_rbin,StaStore::open_rev) validate the 64-byte header, 24-entry section directory, andStaMetainvariants inO(1)time (< 50 us), avoiding a full-file checksum pass on read-only queries (WS-REQ-0031). - Explicit verification (
StaStore::verify_hash,StaStore::open_for_mutation_input) checks the full-filexxh3-64payload digest wheneversta.rbinis verified (rusteda verify) or loaded as an incremental/check input (ruststa check). - Dual storage modes (
StoreMode::FullvsStoreMode::Summary): StoreMode::Full(default) writes all 24 sections for full query and incremental support.StoreMode::SummaryomitsNODE_REC,TAG_EXTRA_PTR,TAG_EXTRA,PATH_CHAIN_PTR, andPATH_CHAIN, reducing artifact size to a few tens of kilobytes while still servingsummary,endpoints, top-Kpaths, andscope/notesqueries (WS-REQ-0035).
Consequences¶
- Opening
sta.rbinviammapand runningquery_summary,endpoint_table(limit=100),query_path_handle(0..100),reconstruct_path_from_chain, andinspect_pinon 500k-cell and 1M-cell stores completes in well under a millisecond warm (far below the25 mswarm /50 mscold budget ofWS-REQ-0030toWS-REQ-0038). - Canonical
-0.0 -> +0.0normalization and deterministic sorting guarantee bitwise-identicalsta.rbinoutput across--threads 1and--threads 32(STA-REQ-0302,STA-REQ-0405).
Amendment (2026-10-23): format version 2¶
Version 1 packed several indices into narrow bit fields and truncated values that did not fit:
PATH_RANK named an endpoint in 20 bits, a PATH_CHAIN step named its node in 24 bits and its
tag slot in 5, ENDPOINTS kept the name bucket in 22 bits of kind_flags, and PATH_CHAIN_PTR
was u32. OpenC910 (4,444,505 endpoints, 3,960,518 instances) did not fit, and the analysis
was rejected with T0022. Version 2 stores each of these at full width: PATH_RANK is 12 bytes
(u32 endpoint, u16 corner), ENDPOINTS is 36 bytes with its own name_bucket_plus1 word, a
chain step is three words [node, step_word, name_bucket] with a 29-bit tag slot, and
PATH_CHAIN_PTR is u64. The remaining fixed-width fields (u32 string offsets and ids, the
20-bit cell-name id and 12-bit pin count of an INST_HASH bucket) are checked before the table
is built, and an analysis that would overflow them is rejected with T0022 rather than written
truncated (specification: docs/specs/sta/store.md, section 6). The
engine reader opens version 2 only; the shared workspace reader also converts version 1 tables.
On VexRiscv (220,450 endpoints) the larger records add about 4 bytes per endpoint and per ranked
path and 4 bytes per chain step.