Skip to content

Commit 3da3c60

Browse files
author
aghose
committed
DFT: add ORFS hooks and scan utilities
1 parent 93c42b2 commit 3da3c60

File tree

7 files changed

+1157
-1
lines changed

7 files changed

+1157
-1
lines changed

doc-DFT-howto.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# DFT / Scan — Quickstart (Before vs After)
2+
3+
This is a short “how to run” guide. For implementation details, limitations, and scan-order benchmarks, see `doc-DFT.md`.
4+
5+
## Before (Baseline: no DFT)
6+
7+
Run the flow normally:
8+
9+
- `make -C flow DESIGN_CONFIG=./designs/nangate45/ibex/config.mk FLOW_VARIANT=baseline_no_dft finish`
10+
11+
## After (DFT Enabled: scan flops + stitched chain)
12+
13+
Enable the two DFT hook scripts:
14+
15+
- `POST_FLOORPLAN_TCL=$(pwd)/flow/scripts/dft_scan_post_floorplan.tcl`
16+
- runs `scan_replace` (functional flops → scan flops)
17+
- creates scan ports: `scan_enable_0`, `scan_in_0`, `scan_out_0`
18+
- sets `set_case_analysis 0 [get_ports scan_enable_0]` (functional-mode timing)
19+
- `PRE_GLOBAL_ROUTE_TCL=$(pwd)/flow/scripts/dft_scan_pre_global_route.tcl`
20+
- runs `execute_dft_plan` (stitches the scan chain using placement)
21+
22+
Example:
23+
24+
- `make -C flow DESIGN_CONFIG=./designs/nangate45/ibex/config.mk FLOW_VARIANT=with_dft POST_FLOORPLAN_TCL=$(pwd)/flow/scripts/dft_scan_post_floorplan.tcl PRE_GLOBAL_ROUTE_TCL=$(pwd)/flow/scripts/dft_scan_pre_global_route.tcl finish`
25+
26+
## Sanity Checks
27+
28+
- Report the plan (from OpenROAD, after `scan_replace`):
29+
- `report_dft_plan -verbose`
30+
- Validate chain integrity from a finished netlist:
31+
- `python3 flow/util/scan_chain_validate.py --verilog flow/results/<platform>/<design>/<variant>/6_final.v`
32+
- Or validate from an ODB (runs `scan_replace` + `execute_dft_plan` in-memory and writes a temp netlist):
33+
- `python3 flow/util/scan_chain_validate.py --odb flow/results/<platform>/<design>/<variant>/3_5_place_dp.odb --openroad $OPENROAD_EXE --liberty <lib> --sdc flow/results/<platform>/<design>/<variant>/3_place.sdc --ensure-ports --scan-replace --execute-dft-plan`
34+
35+
## Compare “Before vs After” QoR
36+
37+
- Routed wirelength / timing: compare `flow/results/<...>/metrics.json` and the OpenROAD/OpenSTA reports between `baseline_no_dft` and `with_dft`.
38+
- Scan-chain wire metric on a fixed placement (also runs an NN heuristic for comparison):
39+
- `python3 flow/util/scan_chain_cost.py --scan-replace --nearest-neighbor --openroad $OPENROAD_EXE --liberty <lib> --odb flow/results/<...>/3_5_place_dp.odb --sdc flow/results/<...>/3_place.sdc`
40+

doc-DFT.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# DFT / Scan in ORFS (OpenROAD-flow-scripts)
2+
3+
Quickstart: `doc-DFT-howto.md`
4+
5+
This repo wires OpenROAD’s DFT scan insertion into the ORFS flow via **opt-in** hook scripts, plus utilities to measure scan-chain wirelength and validate scan stitching.
6+
7+
## Required OpenROAD
8+
9+
This branch pins the `tools/OpenROAD` submodule to **OpenROAD-clean-DFT**:
10+
11+
- Base: `7bc521f36a`
12+
- +1 commit (DFT fixes): `661abebbc3c70c59b4a3991acd176a5cc785f0d4`
13+
14+
The key point: it works with **vanilla OpenSTA** (no OpenSTA parser patch required).
15+
16+
## ORFS Flow Integration (Where DFT Happens)
17+
18+
Two hook scripts are provided:
19+
20+
- `flow/scripts/dft_scan_post_floorplan.tcl`
21+
- Intended use: `POST_FLOORPLAN_TCL=$(pwd)/flow/scripts/dft_scan_post_floorplan.tcl`
22+
- Runs after floorplan, before saving `2_1_floorplan.odb`:
23+
- `set_dft_config -max_chains 1 -clock_mixing clock_mix`
24+
- `scan_replace` (functional flops → scan flops)
25+
- creates scan ports: `scan_enable_0`, `scan_in_0`, `scan_out_0`
26+
- `set_case_analysis 0 [get_ports scan_enable_0]` (functional-mode timing)
27+
28+
- `flow/scripts/dft_scan_pre_global_route.tcl`
29+
- Intended use: `PRE_GLOBAL_ROUTE_TCL=$(pwd)/flow/scripts/dft_scan_pre_global_route.tcl`
30+
- Runs after CTS, before global routing:
31+
- `set_dft_config ...` (must match the post-floorplan config)
32+
- `set_case_analysis 0 [get_ports scan_enable_0]`
33+
- `execute_dft_plan` (stitches the scan chain using placement)
34+
35+
Notes:
36+
- The scripts currently hardcode `-max_chains 1` to keep scan I/O stable for comparisons.
37+
- `set_case_analysis 0` ensures STA uses functional-mode arcs for scan flops.
38+
39+
## OpenROAD-side Fixes (Summary)
40+
41+
The OpenROAD-clean-DFT commit includes the minimum required fixes to make DFT “alive” on top of `7bc521f36a`:
42+
43+
- Scan pin identification works in vanilla STA (`src/dbSta/src/dbSta.cc`):
44+
- removes an overly-strict `extPort()` guard
45+
- adds/uses fallback scan pin inference by common names (`SI/SE/SO`, etc.)
46+
- DFT correctness fixes and functionality (DFT subsystem):
47+
- scan stitching fixes (no dropped links)
48+
- avoids reliance on `sta::TestCell`
49+
- scan-out fallback behavior
50+
- includes a small DFT regression (`scan_architect_no_mix_nangate45`)
51+
52+
## Scan-Ordering Benchmark (OpenROAD vs Nearest-Neighbor)
53+
54+
`flow/util/scan_chain_cost.py` runs OpenROAD’s `report_dft_plan -verbose`, computes total Manhattan scan-chain length, and can also compute a simple nearest-neighbor (NN) heuristic for comparison.
55+
56+
Opt/NN results (lower is better; `openroad_over_nn < 1` means OpenROAD is shorter than NN):
57+
58+
| platform | design | flops | openroad_um | nn_um | openroad_over_nn |
59+
|---|---|---:|---:|---:|---:|
60+
| nangate45 | aes | 562 | 3571.680 | 4178.080 | 0.855 |
61+
| nangate45 | ibex | 1931 | 9197.880 | 10545.640 | 0.872 |
62+
| nangate45 | jpeg | 4390 | 17903.670 | 20815.750 | 0.860 |
63+
| asap7 | aes | 562 | 1053.810 | 1222.344 | 0.862 |
64+
| asap7 | ibex | 273 | 428.652 | 514.404 | 0.833 |
65+
| asap7 | jpeg | 4325 | 5045.058 | 5709.204 | 0.884 |
66+
| sky130hd | aes | 562 | 11050.640 | 13137.940 | 0.841 |
67+
| sky130hd | ibex | 1931 | 21754.680 | 24411.360 | 0.891 |
68+
| sky130hd | jpeg | 4390 | 50973.380 | 57692.340 | 0.884 |
69+
70+
Avg `opt/NN` = `0.865` (~`13.5%` shorter than NN).
71+
72+
Reproduce (single design):
73+
74+
- `python3 flow/util/scan_chain_cost.py --scan-replace --nearest-neighbor --openroad tools/install/OpenROAD/bin/openroad --liberty flow/platforms/nangate45/lib/NangateOpenCellLibrary_typical.lib --odb flow/results/nangate45/ibex/cmp9_or0db856_rp100_20251229_022425/3_5_place_dp.odb --sdc flow/results/nangate45/ibex/cmp9_or0db856_rp100_20251229_022425/3_place.sdc`
75+
76+
Notes:
77+
- ASAP7 needs multiple libs; pass them all, e.g. `--liberty flow/platforms/asap7/lib/NLDM/*_TT_*`.
78+
79+
## Scan-Chain Integrity Validation (Does It Actually Shift?)
80+
81+
QoR deltas and plan reports are necessary but not sufficient; we also want a basic structural check that the scan path is one continuous chain from `scan_in_0` to `scan_out_0`.
82+
83+
- `flow/util/scan_chain_validate.py` validates scan stitching from a gate-level netlist (or from an ODB by writing a temporary netlist via OpenROAD).
84+
- It treats `assign` + inserted `BUF*/CLKBUF*` as transparent, so post-P&R buffering doesn’t cause false failures.
85+
86+
Example usage:
87+
88+
- Validate a finished netlist:
89+
- `python3 flow/util/scan_chain_validate.py --verilog flow/results/nangate45/ibex/with_dft/6_final.v`
90+
- Validate from an ODB (writes a temp netlist first):
91+
- `python3 flow/util/scan_chain_validate.py --odb flow/results/nangate45/ibex/with_dft/6_final.odb --openroad tools/install/OpenROAD/bin/openroad --liberty flow/platforms/nangate45/lib/NangateOpenCellLibrary_typical.lib --sdc flow/results/nangate45/ibex/with_dft/6_final.sdc --ensure-ports`
92+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# DFT scan insertion hook for ORFS.
2+
#
3+
# Intended use: set `POST_FLOORPLAN_TCL` to this file.
4+
#
5+
# This runs after floorplan, before saving `2_1_floorplan.odb`, so subsequent
6+
# stages (place/cts/route) see scan flops and scan ports.
7+
8+
puts "DFT: scan_replace + create scan ports"
9+
10+
# Keep the number of scan ports stable for QoR comparisons.
11+
# With clock mixing enabled, all scan cells share one hash domain, so -max_chains
12+
# applies globally.
13+
set_dft_config -max_chains 1 -clock_mixing clock_mix
14+
15+
# Replace functional flops with scan-capable flops.
16+
scan_replace
17+
18+
proc dft_ensure_scan_port {port_name io_type} {
19+
set block [ord::get_db_block]
20+
21+
set bterm [$block findBTerm $port_name]
22+
if { $bterm != "NULL" } {
23+
return
24+
}
25+
26+
set net [$block findNet $port_name]
27+
if { $net == "NULL" } {
28+
set net [odb::dbNet_create $block $port_name]
29+
$net setSigType SCAN
30+
}
31+
32+
set bterm [odb::dbBTerm_create $net $port_name]
33+
$bterm setSigType SCAN
34+
$bterm setIoType $io_type
35+
}
36+
37+
# One-chain scan I/O + shared enable.
38+
dft_ensure_scan_port "scan_enable_0" INPUT
39+
dft_ensure_scan_port "scan_in_0" INPUT
40+
dft_ensure_scan_port "scan_out_0" OUTPUT
41+
42+
# Functional-mode assumption for STA/power (disable scan path).
43+
set_case_analysis 0 [get_ports scan_enable_0]
44+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# DFT scan insertion hook for ORFS.
2+
#
3+
# Intended use: set `PRE_GLOBAL_ROUTE_TCL` to this file.
4+
#
5+
# This runs after CTS, before global routing, so scan-chain connections are
6+
# included in routing.
7+
8+
puts "DFT: execute_dft_plan (stitch scan chains)"
9+
10+
# Must match `flow/scripts/dft_scan_post_floorplan.tcl`.
11+
set_dft_config -max_chains 1 -clock_mixing clock_mix
12+
13+
# Ensure functional-mode STA/power assumptions in this stage too.
14+
set_case_analysis 0 [get_ports scan_enable_0]
15+
16+
execute_dft_plan
17+

0 commit comments

Comments
 (0)