COP API Reference
COP (Controllability Observability Program) - Probabilistic testability analysis using Bayesian networks for reconvergent fanout correlation modeling.
Overview
The COP module provides probability-based testability metrics:
- C1: Probability that a signal is 1 (controllability to 1)
- C0: Probability that a signal is 0 (controllability to 0, equals 1 - C1)
- Obs: Probability that a signal fault is observable at primary outputs
- Testability: Combined metric (C1 × Obs) indicating ease of testing
Main Entry Point
run_cop()
Main function for COP analysis with integrated reconvergence detection.
from opentestability.core.cop import run_cop
run_cop(
input_filename,
output_filename,
json_flag=False,
reconvergence_algorithm='auto',
reconvergence_data=None,
auto_parallel=True,
max_workers=4
)
Parameters:
input_filename(str): Name of parsed netlist fileoutput_filename(str): Name of output filejson_flag(bool): Whether to generate JSON output (default: False)reconvergence_algorithm(str): Algorithm for reconvergence detection'auto': Automatic selection (default)'simple': Simplified algorithm (O(n² log n), 98% accuracy)'basic': Basic algorithm (O(n²))'advanced': Complete algorithm (O(n³))None: Disable reconvergence handling
reconvergence_data(dict): Pre-computed reconvergence data (optional)auto_parallel(bool): Enable automatic parallel for large circuits (default: True)max_workers(int): Maximum parallel workers (default: 4)
Returns:
str: Path to generated text output file
Example:
from opentestability.core.cop import run_cop
# Basic usage
run_cop('circuit.txt', 'cop_results.txt')
# With reconvergence and JSON output
run_cop('circuit.txt', 'cop_results.txt',
json_flag=True,
reconvergence_algorithm='simple')
# Large circuit with parallel processing
run_cop('large_design.txt', 'results.txt',
reconvergence_algorithm='auto',
auto_parallel=True,
max_workers=8)
Controllability Module
build_controllability()
Compute C1 (probability of signal being 1) for all signals using forward propagation.
from opentestability.core.cop.controllability import build_controllability
C1 = build_controllability(nets, primary_inputs, gates, max_iterations=10000)
Parameters:
nets(List[str]): List of all signal names in circuitprimary_inputs(List[str]): List of primary input signal namesgates(List[Tuple]): List of gate tuples (gate_type, output_signal, [input_signals])max_iterations(int): Maximum iterations for convergence (default: 10000)
Returns:
Dict[str, float]: Dictionary mapping signal names to C1 probabilities
Algorithm:
- Initialize primary inputs to C1 = 0.5 (random test patterns)
- Initialize other signals to None (uncomputed)
- Iteratively compute gate outputs when inputs are ready
- Continue until convergence or max iterations reached
Example:
nets = ['a', 'b', 'n1', 'z']
inputs = ['a', 'b']
gates = [
('AND2X1', 'n1', ['a', 'b']),
('INVX1', 'z', ['n1'])
]
C1 = build_controllability(nets, inputs, gates)
print(f"Signal z has C1 = {C1['z']:.4f}")
get_controllability_metrics()
Package controllability metrics for output.
from opentestability.core.cop.controllability import get_controllability_metrics
metrics = get_controllability_metrics(C1)
Returns:
{
'signal_name': {
'c1': 0.5, # Probability signal is 1
'c0': 0.5 # Probability signal is 0
}
}
Gate Logic Module
Probabilistic transfer functions for various gate types.
calculate_gate_c1()
Main dispatcher for calculating C1 of any gate type.
from opentestability.core.cop.gate_logic import calculate_gate_c1
output_c1 = calculate_gate_c1(gate_type, input_c1_list)
Parameters:
gate_type(str): Gate type (e.g., ‘AND2X1’, ‘NAND’, ‘OR’, ‘XOR’)input_c1_list(List[float]): List of input C1 probabilities
Returns:
float: Probability that output is 1
Supported Gates:
- INV, NOT, BUF
- AND, NAND (multi-input)
- OR, NOR (multi-input)
- XOR, XNOR (multi-input)
- AOI, OAI (approximate)
Example:
# 2-input AND gate with inputs at 0.5 probability
c1_out = calculate_gate_c1('AND2X1', [0.5, 0.5])
# Result: 0.25 (0.5 × 0.5)
Individual Gate Functions
calculate_inv_c1()
Inverter: out.C1 = 1.0 - in.C1
c1_out = calculate_inv_c1(input_c1)
calculate_and_c1()
AND gate: out.C1 = ∏(input.C1)
c1_out = calculate_and_c1([0.5, 0.5, 0.5])
# Result: 0.125
calculate_or_c1()
OR gate: out.C1 = 1.0 - ∏(1.0 - input.C1)
c1_out = calculate_or_c1([0.5, 0.5])
# Result: 0.75
calculate_xor_c1()
XOR gate: out.C1 = (a.C1 × (1-b.C1)) + ((1-a.C1) × b.C1)
c1_out = calculate_xor_c1([0.5, 0.5])
# Result: 0.5
Observability Module
build_observability()
Compute observability for all signals using backward propagation.
from opentestability.core.cop.observability import build_observability
Obs = build_observability(
nets,
primary_outputs,
gates,
C1,
reconvergent_sites=None,
max_iterations=10000
)
Parameters:
nets(List[str]): List of all signal namesprimary_outputs(List[str]): List of primary output signal namesgates(List[Tuple]): List of gate tuplesC1(Dict[str, float]): Dictionary of C1 probabilitiesreconvergent_sites(Set[str]): Set of reconvergent fanout points (optional)max_iterations(int): Maximum iterations (default: 10000)
Returns:
Dict[str, float]: Dictionary mapping signal names to observability probabilities
Algorithm:
- Initialize primary outputs to Obs = 1.0 (fully observable)
- Initialize other signals to 0.0
- Iterate backward through gates, computing input observabilities
- For fanouts, combine branches using union formula (if independent)
- Mark reconvergent sites for Bayesian processing
Example:
Obs = build_observability(nets, outputs, gates, C1)
print(f"Signal 'a' observability: {Obs['a']:.4f}")
calculate_branch_observability()
Calculate observability of a signal through a specific gate branch.
from opentestability.core.cop.observability import calculate_branch_observability
branch_obs = calculate_branch_observability(
signal, gate_type, gate_output, gate_inputs, output_obs, C1
)
Logic by Gate Type:
- AND/NAND: All side inputs must be 1 to observe signal
Obs = output_obs × ∏(side_input.C1)
- OR/NOR: All side inputs must be 0 to observe signal
Obs = output_obs × ∏(1 - side_input.C1)
- XOR/XNOR: Side inputs don’t block observation
Obs = output_obs
- INV/BUF: Direct pass-through
Obs = output_obs
combine_fanout_observabilities()
Combine observabilities from multiple fanout branches.
from opentestability.core.cop.observability import combine_fanout_observabilities
combined_obs = combine_fanout_observabilities(branch_obs_list)
Formula:
For independent branches: Obs = 1 - ∏(1 - Obs_branch)
This represents probability that signal is observable through at least one branch.
Reconvergence Module
Bayesian network-based reconvergence handling with correlation modeling.
extract_reconvergent_sites()
Extract set of reconvergent site signals from reconvergence data.
from opentestability.core.cop.reconvergence import extract_reconvergent_sites
sites = extract_reconvergent_sites(reconvergence_data)
Returns:
Set[str]: Set of signal names that are reconvergent sites
adjust_cop_for_reconvergence()
Adjust COP observability values for reconvergent sites using Bayesian correlation.
from opentestability.core.cop.reconvergence import adjust_cop_for_reconvergence
adjusted_obs = adjust_cop_for_reconvergence(Obs, C1, reconvergence_data, gates)
Parameters:
Obs(Dict): Observability probabilitiesC1(Dict): Controllability probabilitiesreconvergence_data(Dict): Reconvergence analysis datagates(List): List of gate tuples
Returns:
Dict[str, float]: Adjusted observability dictionary
How It Works:
- Identifies reconvergent fanouts from data
- Builds Bayesian network for reconvergent paths
- Calculates correlation coefficients
- Applies correlation-based adjustments instead of independence assumption
BayesianNetwork Class
Simplified Bayesian network for modeling signal dependencies.
from opentestability.core.cop.reconvergence import BayesianNetwork
bn = BayesianNetwork()
bn.build_from_paths(path1, path2)
correlation = bn.calculate_correlation(path1, path2)
Methods:
add_dependency()
bn.add_dependency(parent_signal, child_signal)
build_from_paths()
bn.build_from_paths(path1, path2)
calculate_correlation()
correlation = bn.calculate_correlation(path1, path2)
Returns correlation coefficient in [0, 1] based on shared ancestor signals.
calculate_correlated_observability()
Calculate observability for reconvergent site using correlation.
from opentestability.core.cop.reconvergence import calculate_correlated_observability
adjusted_obs = calculate_correlated_observability(site, branch_obs_list, correlation)
Formula:
- If correlation = 0: Use independent union
1 - ∏(1 - Obs_i) - If correlation = 1: Use maximum
max(Obs_i)(paths identical) - For 0 < correlation < 1: Interpolate between the two
Vectorized Engine
The runtime COP computation uses a NumPy-vectorized engine rather than the pure-Python fixpoint loop.
from opentestability.core.cop.vectorized import build_cop_vectorized
C1, Obs = build_cop_vectorized(nets, inputs, outputs, gates)
How it works:
- Builds a
CircuitIndex(integer-indexed CSR graph) once. - Forward C1 (
forward_c1): single topological pass; gate outputs computed by gate type usingnp.multiply.reduceatover the CSR fan-in. - Backward Obs (
backward_obs): iterative union fixpoint; per-gate side-input blocking products are precomputed once (they depend only on C1, which is fixed) and reused across iterations.
This is the path called by run_cop() and main.py. The pure-Python build_controllability / build_observability functions remain in the codebase as reference oracles for testing only.
Convergence note: The vectorized backward pass uses a Jacobi update (whole-array update per iteration) while the reference uses Gauss-Seidel (in-place). On ultra-stiff feedback circuits with many flip-flops, a small number of signals may converge to slightly different values at the shared iteration cap. Rank correlation is ≥ 0.9998 in practice; TPI rankings are unaffected.
Output Module
Output formatting for COP results.
write_cop()
Write COP results to text file.
from opentestability.core.cop.output import write_cop
write_cop(C1, Obs, filename)
Output Format:
============================================================
COP (Controllability Observability Program) Analysis
============================================================
--- CONTROLLABILITY PROBABILITIES (C1) ---
C1 = Probability that signal is 1
C0 = 1 - C1 (Probability that signal is 0)
signal_a C1: 0.500000 C0: 0.500000
signal_b C1: 0.500000 C0: 0.500000
...
--- OBSERVABILITY PROBABILITIES (Obs) ---
Obs = Probability that signal fault is observable at outputs
signal_a Obs: 0.750000
signal_b Obs: 0.750000
...
--- TESTABILITY METRICS ---
Testability = C1 * Obs (probability of setting and observing)
signal_a Testability: 0.375000
signal_b Testability: 0.375000
...
dump_json()
Write COP results to JSON file.
from opentestability.core.cop.output import dump_json
dump_json(C1, Obs, inputs, outputs, gates, filename)
JSON Schema:
{
"analysis_type": "COP",
"description": "Controllability Observability Program Analysis",
"primary_inputs": ["a", "b"],
"primary_outputs": ["z"],
"metrics": [
{
"gate": "AND2X1_0",
"output": "n1",
"inputs": ["a", "b"],
"c1": 0.25,
"c0": 0.75,
"obs": 1.0,
"testability": 0.25
}
]
}
format_cop_summary()
Generate summary of COP results for display.
from opentestability.core.cop.output import format_cop_summary
summary = format_cop_summary(C1, Obs, primary_inputs, primary_outputs)
print(summary)
Output:
============================================================
COP Analysis Summary
============================================================
Primary Inputs (2):
a C1: 0.5000
b C1: 0.5000
Primary Outputs (1):
z Obs: 1.0000 Testability: 0.7500
Average C1: 0.5625
Average Observability: 0.8750
Testability Statistics:
Average: 0.4531
Minimum: 0.1250
Maximum: 0.7500
============================================================
Complete Example
from opentestability.core.cop import run_cop
from opentestability.core.cop.vectorized import build_cop_vectorized
from opentestability.core.cop.reconvergence import adjust_cop_for_reconvergence
from opentestability.core.cop.output import write_cop, dump_json
# Method 1: Simple high-level API (recommended)
run_cop('circuit.txt', 'cop_results.txt',
json_flag=True,
reconvergence_algorithm='simple')
# Method 2: Vectorized engine directly
# (Assuming you have nets, inputs, outputs, gates from netlist parser)
C1, Obs = build_cop_vectorized(nets, inputs, outputs, gates)
# Apply reconvergence adjustments (if data available)
if reconvergence_data:
Obs = adjust_cop_for_reconvergence(Obs, C1, reconvergence_data, gates)
# Write outputs
write_cop(C1, Obs, 'results.txt')
dump_json(C1, Obs, inputs, outputs, gates, 'results.json')
Performance Considerations
Observed timings (picorv32a, ~14 k gates)
| Engine | Time |
|---|---|
| Pure-Python reference (observability) | ~160 s |
| NumPy vectorized (current runtime) | ~3 s |
The vectorized engine scales to hundreds of thousands of gates without further changes.
At SoC scale (~1 M gates), the dominant cost shifts to CircuitIndex construction
and dict output — the NumPy computation itself remains fast.
Memory usage
CircuitIndexarrays: O(signals + gates) int64/float64 — tens of MB at 1 M gates.- Typical: well under 100 MB for circuits up to 500 k gates.
Optimization tips
- Use
reconvergence_algorithm='simple'for best speed/accuracy tradeoff. - Enable JSON output (
json_flag=True) only when downstream tools need it (adds I/O time). - For very large netlists where
CircuitIndexbuild time dominates, consider pre-building the index once and reusing it across multiple analysis passes.