Docs / ATPG Coverage Oracle

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.