ucs_hold_fix

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.

1. What it needs

InputWhere it comes from
hold reportsPrimeTime, 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

2. The flow

Three inputs are produced once and reused; the tool carries the pt_shell scripts for all of them, so nothing else needs installing.

  1. Build the scope, once per netlist

    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.

    Keep every level you may want to fix at. A level is nameable as 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.

  2. Characterise the buffers, once per library release

    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
    Hold and setup read the ladder at different percentiles. A hold fix is safe when the buffer is assumed to buy less delay than it does, and safe when its setup cost is assumed to be more. Using one number for both is wrong in one direction or the other.

    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.

  3. Dump the setup margin

    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
    The corner label does not switch corners. It is a column value. You must confirm the session really is in that corner and that mode.
  4. Fix

    ucs_hold_fix -config fix.yaml

3. The config

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

Keys

KeyMeaning
base_dirprefix for relative paths
scope_jsoninstance → master. Omit for a tile-level run
blocknames the single ECO file when there is no scope
report_levelwhich scope view; the level the reports came from
fix_levelthe level a pt_shell is available at. Only affects setup margin
cell_delaythe table from generate_delay
ref_filterregex identifying leaf-cell refs. Required
fix_cellssubset of the table that may be inserted. Globs allowed
forbid_tilestile masters to protect. Globs allowed
blacklistfiles of exact pins or wildcards
targetdefault report cutoff in ps
hold.<corner>target, reports, report_list
setup.<corner>margin, guardband. These keys are the declared setup corners
max_bufferscap per node, default 40
jobsparse threads; 0 = one per report

4. Report format

IO paths are excluded by default. A path whose Startpoint or Endpoint line names a design port — (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.

5. What a run produces

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
The 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.

6. Running at a lower level

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.

Tile-level run, no scope at all

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.

7. Protecting things

forbid_tiles: [gc_sp1_2_t, ccx*]     # tile MASTERS
blacklist:    [dont_touch.lst]        # exact pins or wildcards
Use 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).

8. Reference

Run ucs_hold_fix -h for the full option list. The most used:

FlagMeaning
-mode generate_scopebuild scope json; -emit_tcl writes the dump script
-config <f>yaml, json or line-based
-example <f>write a template
-emit_setup_queryone 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_iokeep paths starting or ending on a design PORT; excluded by default
-debug <1..4>flow / decision / data / deep

9. Known limits


UniversalChip Studio — tech@universalchipstudio.com