Reuse-aware hold-fix ECO generation — UCS-1.0.1 — 2026-08-16
Reads multi-corner hold reports, decides where to insert buffers, and writes a tile-relative ECO. The design goal is that one timing path receives buffers at exactly one node, even when the same tile is placed dozens of times — because the ECO is written against the tile master and therefore lands in every instance of it.
| Input | Where it comes from |
|---|---|
| hold reports | PrimeTime, one or more per corner |
| scope json | -mode generate_scope — instance → master. Optional for a tile-level run |
| cell delay table | -mode generate_delay, read from Liberty. No STA licence needed |
| setup margin tsv | -emit_setup_query → source in pt_shell → tsv |
Three inputs are produced once and reused; the tool carries the pt_shell scripts for all of them, so nothing else needs installing.
ucs_hold_fix -mode generate_scope -emit_tcl dump_inst_ref.tcl
pt_shell> set ::SC(top) gc_top_t
pt_shell> source dump_inst_ref.tcl # -> ./inst_ref.txt
ucs_hold_fix -mode generate_scope -inst_ref inst_ref.txt \
-top_name gc_top_t -out scope.json -pretty
instances : 414
views : 2
gc_top_t 414 instance(s), 66 reference(s) [top]
se 109 instance(s), 25 reference(s)
The dump lists every hierarchical cell as <instance>
<reference>. Besides the top view, a view is derived for every reference
that has descendants, holding them with the parent prefix stripped.
fix_level only if the map declares it —
including intermediate ones. se needs the rows se0 se,
se1 se, se2 se; without them only leaf tiles are
selectable.A reference placed twice must contain the same thing both times. If it does not it is not really reused, collapsing it would be unsound, and the build stops naming the two instances that differ.
ucs_hold_fix -mode generate_delay -example delay.cfg # template
ucs_hold_fix -mode generate_delay -config delay.cfg
The config names the cells (globs allowed) and one or more .lib/.lib.gz
per corner. Output is a delay table holding the whole percentile ladder
(p0…p100 in steps of 5), not one resolved number — so the percentile is a
decision made when the table is read, and changing it never means
re-reading the libraries.
out cell_delay.txt
hold_percentile 10 # low: assume the buffer buys less than it does
setup_percentile 50 # high: assume it costs more than it does
jobs 0 # 0 = one thread per (corner, lib) pair
cell BUFFD1BWP136P5M117H3P48CPDLVT
cell BUFFD2BWP136P5M117H3P48CPDLVT
# cell BUFFD*BWP136P5M117H3P48CPDLVT # every drive strength
corner FFG1p05v_Rcbest
lib /path/tcbn03cbwp136p5mh117l3p48cpdbaselvt.ffgnp1p05vm40c.lib.gz
corner SSG0p6v_Rcbest
lib /path/tcbn03cbwp136p5mh117l3p48cpdbaselvt.ssgnp0p6vm40c.lib.gz
A cell whose name matches but which yields no NLDM is an error naming the stage
it failed at — no timing(), no cell_rise/cell_fall,
no values() — rather than silently missing from the table. Set
detail 1 to print the arc/table/sample counts for the cells that did work.
ucs_hold_fix -config fix.yaml -emit_setup_query
Writes one script per declared setup corner into <out>/tcl, covering
exactly the nodes this run would consider — so the query and the fix cannot
drift apart. Each writes an absolute path under
<out>/margin, so it does not matter which directory the pt_shell is
running in.
pt_shell> source <out>/tcl/ucs_setup_margin_TT0p9v.tcl
-> <out>/margin/TT0p9v_20260816.tsv
To dump the same corner again — nosi/si, or another functional mode — set the file stem first. The corner label is unchanged, so the files merge:
pt_shell> set ::SM(name) TT0p9v_si
pt_shell> source <out>/tcl/ucs_setup_margin_TT0p9v.tcl
-> <out>/margin/TT0p9v_si_20260816.tsv
ucs_hold_fix -config fix.yaml
YAML, JSON or the older line-based form — -config picks by
extension for .yaml/.yml, otherwise by content. All three are
read by the same code, so they cannot diverge in behaviour.
-example <name>.yaml writes an annotated template.
# Prefix for every RELATIVE path below. Absolute paths ignore it.
base_dir: /proj/.../FCT0975_20260814_..._SE2
scope_json: /path/scope_gc_top_t.json
report_level: gc_top_t # the level the reports came from
cell_delay: /path/cell_delay.txt
ref_filter: BWP # leaf-cell refs; REQUIRED
out: /path/rundir # default ucs_<date>_<time>_<user>
fix_cells: # which table cells may be inserted
- BUFFD1BWP136P5M117H3P48CPDLVT
- BUFFD2BWP136P5M117H3P48CPDLVT
forbid_tiles: # tile MASTERS not to touch
- gc_sp1_2_t
target: 5 # default cutoff; a corner overrides it
hold:
FFG1p05v_Rcbest:
reports:
- PtFuncFFG1p05v...Rcbest.../classified_bundles.rpt
SSG0p6v_Rcbest:
target: 20
report_list: # or a file of report paths
- ssg_rcbest.lst
setup:
TT0p9v:
margin: # several files fold worst-of-worst
- margin/TT0p9v_nosi_20260816.tsv
- margin/TT0p9v_si_20260816.tsv
guardband: 15
| Key | Meaning |
|---|---|
| base_dir | prefix for relative paths |
| scope_json | instance → master. Omit for a tile-level run |
| block | names the single ECO file when there is no scope |
| report_level | which scope view; the level the reports came from |
| fix_level | the level a pt_shell is available at. Only affects setup margin |
| cell_delay | the table from generate_delay |
| ref_filter | regex identifying leaf-cell refs. Required |
| fix_cells | subset of the table that may be inserted. Globs allowed |
| forbid_tiles | tile masters to protect. Globs allowed |
| blacklist | files of exact pins or wildcards |
| target | default report cutoff in ps |
| hold.<corner> | target, reports, report_list |
| setup.<corner> | margin, guardband. These keys are the declared setup corners |
| max_buffers | cap per node, default 40 |
| jobs | parse threads; 0 = one per report |
(input port …),
(output port), INPORT, OUTPORT — is dropped at
parse time and counted. The fix cannot reach past the block boundary, and an
interface path's slack belongs to whoever budgets that interface, so buffering it
here spends setup margin against someone else's number. -include_io
keeps them.PrimeTime path reports (-report_format pt, the default). The parser reads
the data path only — from after Startpoint through
Endpoint — which excludes roughly 160 of the ~180 rows in a typical
report, all clock network. Increment is taken from the Incr column, and
long pin names wrapped onto their own line are rejoined.
The report list, if you use one instead of the hold: block, is
<corner> <target_slack> <abs_path> per line. Measure the target
from the reports themselves — the maximum slack present — rather than
assuming it. Asking for more than the cutoff is not verifiable from those reports.
ucs_<date>_<time>_<user>/
log/ ucs_hold_fix_<run_id>.log
tcl/ generated pt_shell scripts
margin/ where the tsv dumps land
reports/ path_fix_map.tsv, unfixed_paths.rpt
eco/
per_tile/ <master>.eco one file per tile master
On screen:
paths : 1345 distinct nodes 15877
union : 1345 path(s) grown, node refs 27031 -> 644493
margin : 15266 row(s), 48051 attachment(s), 12194 tightened to a sibling
filter : 24219 candidate(s) [blacklist 0(+0 sibling), non-input 25507, ...]
fix : 134 round(s), 134 node(s) used, 173 buffer(s), 24085 blocked on fixed paths
Corner Original Fixed Remaining
FFG1p05v_Rcbest 39 39 0
SSG0p6v_Rcbest 984 984 0
OVERALL 1345 1345 0
check : 0 path(s) fixed at more than one node -> reports/path_fix_map.tsv
eco : 134 command(s), 173 buffer(s), 0 disagreed (MAX taken), 0 unmapped
check line is the one to read. It counts
paths that received buffers at more than one node — the over-fix this tool
exists to prevent. It must be 0. reports/path_fix_map.tsv has the
per-path evidence.When something cannot be closed, reports/unfixed_paths.rpt lists every
such path per corner, worst first, with each node on it and the reason that node
was unusable. A path is unfixable only when the whole list is blocked.
Violations are reported at top or subsystem level, but the PrimeTime session you
have may be a block. fix_level names that block:
report_level: gc_top_t
fix_level: se # or a leaf tile, e.g. gc_sp1_0_t
It affects only the setup margin: the query is emitted in that level's coordinates and the tsv is read back in them, collapsing reused copies to the worst value per node. The fix still runs on per-instance nodes and the ECO is still written per leaf tile.
fix_level takes a ref, not an instance
— gc_sp1_0_t, not se2/gc_sp1_0_t010 — and the ref must
be declared in the scope json. Naming an instance is diagnosed and the right ref
suggested.block: gc_sp1_0_t # names the one eco file
# scope_json omitted
No reuse is assumed, no sibling union runs, and the ECO carries full pin names. Use it when the reports already come from the block session.
forbid_tiles: [gc_sp1_2_t, ccx*] # tile MASTERS
blacklist: [dont_touch.lst] # exact pins or wildcards
forbid_tiles to protect a tile, not a
blacklist entry. The ECO is tile-relative, so excluding a single
instance achieves nothing on its own — a sibling stays fixable, the
fix lands in the master, and the buffer appears in the instance you excluded.
A blacklist hit on any instance now forbids that pin in all of them, and the
count is reported as blacklist N(+M sibling).Run ucs_hold_fix -h for the full option list. The most used:
| Flag | Meaning |
|---|---|
| -mode generate_scope | build scope json; -emit_tcl writes the dump script |
| -config <f> | yaml, json or line-based |
| -example <f> | write a template |
| -emit_setup_query | one margin script per corner, then stop |
| -setup_corners <c,..> | declared setup corners; each must have data |
| -fix_cells <glob> | restrict insertable cells |
| -forbid_tile <glob> | protect a tile master |
| -hold_percentile <n> | re-read the ladder; no re-extraction |
| -setup_percentile <n> | same, for setup cost |
| -setup_guardband <ps> | or <corner>=<ps> |
| -format <name> | amd (default), icc, icc2, fusion, innovus_legacy, innovus_stylus |
| -out <dir> | run directory |
| -include_io | keep paths starting or ending on a design PORT; excluded by default |
| -debug <1..4> | flow / decision / data / deep |
tests/tcl/), which asserts the script's logic but not PrimeTime's
behaviour. The remaining assumption is that get_timing_paths -through
returns an empty collection, rather than erroring, for an unconstrained pin.-report_format ets is declared but not implemented; only
pt parses today.UniversalChip Studio — tech@universalchipstudio.com