ATPG-guided closed-loop TPI
analyze_and_add_tp can run a closed loop: insert test points, ask an ATPG
backend how much stuck-at fault coverage the modified netlist now achieves, then
insert more where faults are still undetected, repeating until a coverage target
is met or an iteration budget is exhausted.
The loop talks to backends only through the
CoverageOracle interface
(opentestability.core.interfaces.coverage_oracle), so the ATPG engine is an
optional, loosely-coupled dependency.
Enabling the loop
Add --atpg-loop plus exactly one oracle source:
| Flag | Oracle | Needs |
|---|---|---|
--oracle structural |
COP/SCOAP structural proxy (no fault simulation) | nothing extra |
--oracle faultflow |
FaultFlow ATPG (auto-detected) | faultflow on PATH |
--oracle-cmd '<cmd>' |
Any external ATPG via a subprocess handshake | your script |
Other loop flags:
| Flag | Meaning | Default |
|---|---|---|
--coverage-target |
Stop when fault coverage reaches this fraction of [0,1] |
0.90 |
--max-iterations |
Hard cap on TPI+ATPG cycles | 5 |
--oracle-dir |
Directory for the oracle handshake files | alongside the output |
Example:
opentest analyze_and_add_tp -i design.json --scoap --atpg-loop \
--oracle-cmd './my_atpg.sh' --coverage-target 0.95 --max-iterations 8 \
--oracle-dir out/
The structural proxy needs no external tool and is a good smoke test:
opentest analyze_and_add_tp -i design.json --scoap --atpg-loop --oracle structural
The subprocess-oracle handshake
With --oracle-cmd, each iteration OpenTestability writes a
tpi_manifest.json into the oracle directory and then runs your command. Your
command must read the manifest, evaluate the netlist it points at, and write the
response JSON before it exits 0.
tpi_manifest.json (written by OpenTestability) contains, among other fields:
| Field | Meaning |
|---|---|
session_id |
Opaque id; echo it back in the response |
netlist_path |
Yosys JSON to evaluate (already contains this iteration’s test points) |
top_module |
Top module name inside the netlist |
response_path |
Where you must write the response JSON |
structural_hints |
Optional reconvergence hints (fanout stems with Yosys net ids) |
Your command writes oracle_response.json at response_path:
{
"session_id": "<echo the manifest's session_id>",
"coverage_percent": 87.5,
"fault_coverage_percent": 85.0,
"vector_count": 42,
"terminal_reason": "target_reached",
"backend": "my_atpg",
"undetected_faults": [
{ "net_name": "n1", "fault_type": "sa1" }
],
"per_node": [
{ "net_name": "n1", "detected": 1, "undetected": 1 }
]
}
Minimal shell oracle that reports a canned result:
#!/usr/bin/env bash
# my_atpg.sh: reads tpi_manifest.json, writes oracle_response.json
set -euo pipefail
dir="$(dirname "$0")/out"
manifest="$dir/tpi_manifest.json"
session=$(python3 -c "import json,sys;print(json.load(open('$manifest'))['session_id'])")
resp=$(python3 -c "import json,sys;print(json.load(open('$manifest'))['response_path'])")
cat > "$resp" <<JSON
{"session_id": "$session", "coverage_percent": 96.0,
"fault_coverage_percent": 96.0, "vector_count": 1,
"terminal_reason": "target_reached", "backend": "demo",
"undetected_faults": [], "per_node": []}
JSON
CoverageResult schema
OpenTestability parses the response into a CoverageResult
(opentestability.core.interfaces.coverage_oracle):
| Field | Type | Meaning |
|---|---|---|
coverage_percent |
float | Overall coverage in [0, 100] |
fault_coverage_percent |
float | Stuck-at fault coverage in [0, 100] |
vector_count |
int | Test-pattern count |
atpg_terminal_reason |
str | Why ATPG stopped |
undetected_faults |
list | FaultSite entries (net_name, fault_type, fault_site_key) still undetected |
per_node |
list | Per-signal NodeCoverage (drives the next iteration’s targeting) |
backend_id |
str | Which backend produced this result, e.g. "faultflow" (default "") |
raw |
dict | None | Full backend response dict, for consumers that need fields beyond this schema (default None) |
Writing an in-process oracle
To integrate an ATPG engine directly (no subprocess), subclass CoverageOracle
and pass an instance to run_atpg_guided_tpi:
from opentestability.core.interfaces.coverage_oracle import CoverageOracle, CoverageResult
class MyOracle(CoverageOracle):
@property
def backend_id(self) -> str:
return "my_sim"
def is_available(self) -> bool:
return True
def evaluate(
self, yosys_json_path, top_module, *, structural_hints=None
) -> CoverageResult: ... # run ATPG, return a CoverageResult
See also the FaultFlow preflight protocol for the no-TPI structural-hints interface FaultFlow uses before ATPG.