Coverage for src / tracekit / inference / logic.py: 96%
61 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""Logic family auto-detection from signal levels.
3This module analyzes digital signal voltage levels to automatically
4detect the logic family (TTL, CMOS, LVTTL, LVCMOS, etc.).
7Example:
8 >>> import tracekit as tk
9 >>> trace = tk.load('digital_signal.wfm')
10 >>> family = tk.detect_logic_family(trace)
11 >>> print(f"Detected: {family['primary']['name']}")
12 >>> print(f"Confidence: {family['primary']['confidence']:.1%}")
14References:
15 JEDEC Standard definitions for logic families
16 TTL: 5V, CMOS: 3.3V/5V, LVTTL: 3.3V, LVCMOS: 1.8V/2.5V/3.3V
17"""
19from __future__ import annotations
21from typing import TYPE_CHECKING, Any
23import numpy as np
25from tracekit.core.exceptions import AnalysisError
27if TYPE_CHECKING:
28 from numpy.typing import NDArray
30 from tracekit.core.types import WaveformTrace
32# Logic family specifications (VOH, VOL, VIH, VIL, typical VDD)
33LOGIC_FAMILY_SPECS = {
34 "TTL": {
35 "vdd": 5.0,
36 "voh_min": 2.4,
37 "vol_max": 0.4,
38 "vih_min": 2.0,
39 "vil_max": 0.8,
40 },
41 "CMOS_5V": {
42 "vdd": 5.0,
43 "voh_min": 4.4,
44 "vol_max": 0.5,
45 "vih_min": 3.5,
46 "vil_max": 1.5,
47 },
48 "CMOS_3V3": {
49 "vdd": 3.3,
50 "voh_min": 2.4,
51 "vol_max": 0.4,
52 "vih_min": 2.0,
53 "vil_max": 0.8,
54 },
55 "LVTTL": {
56 "vdd": 3.3,
57 "voh_min": 2.4,
58 "vol_max": 0.4,
59 "vih_min": 2.0,
60 "vil_max": 0.8,
61 },
62 "LVCMOS_2V5": {
63 "vdd": 2.5,
64 "voh_min": 2.0,
65 "vol_max": 0.2,
66 "vih_min": 1.7,
67 "vil_max": 0.7,
68 },
69 "LVCMOS_1V8": {
70 "vdd": 1.8,
71 "voh_min": 1.35,
72 "vol_max": 0.45,
73 "vih_min": 1.17,
74 "vil_max": 0.63,
75 },
76}
79def detect_logic_family(
80 trace: WaveformTrace,
81 *,
82 min_confidence: float = 0.7,
83 return_candidates: bool = False,
84) -> dict[str, Any]:
85 """Auto-detect logic family from signal levels.
87 Analyzes the signal histogram to detect bimodal distribution
88 corresponding to logic high and low levels, then maps to the
89 nearest logic family specification.
91 Args:
92 trace: Digital signal to analyze.
93 min_confidence: Minimum confidence threshold (0-1).
94 Returns all candidates if confidence below threshold.
95 return_candidates: If True, return all candidate families with scores.
97 Returns:
98 Dictionary containing:
99 - primary: Dict with detected family info:
100 - name: Logic family name (e.g., 'TTL', 'CMOS_3V3')
101 - confidence: Detection confidence (0-1)
102 - voh: Measured high-level voltage
103 - vol: Measured low-level voltage
104 - thresholds: Dict with VIH, VIL, VOH, VOL thresholds
105 - candidates: List of all candidates (if return_candidates=True)
107 Raises:
108 AnalysisError: If signal is not bimodal or levels are ambiguous.
110 Example:
111 >>> trace = tk.load('cmos_signal.wfm')
112 >>> result = tk.detect_logic_family(trace, return_candidates=True)
113 >>> print(f"Primary: {result['primary']['name']}")
114 >>> print(f"Confidence: {result['primary']['confidence']:.1%}")
115 >>> for candidate in result['candidates']:
116 ... print(f" {candidate['name']}: {candidate['confidence']:.1%}")
118 References:
119 JEDEC Standard No. 8 (Interface Standards)
120 TTL: Texas Instruments SN54/74 Series
121 """
122 # Analyze histogram to find bimodal distribution
123 voh, vol, confidence_bimodal = _detect_logic_levels(trace.data)
125 if np.isnan(voh) or np.isnan(vol): 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true
126 raise AnalysisError(
127 "Could not detect distinct logic levels. "
128 "Signal may not be digital or has insufficient transitions."
129 )
131 # Calculate swing and nominal VDD
132 voh - vol
133 estimated_vdd = voh + 0.3 # Assume VOH is slightly below VDD
135 # Score each logic family
136 candidates = []
137 for family_name, specs in LOGIC_FAMILY_SPECS.items():
138 score = _score_logic_family(voh, vol, estimated_vdd, specs)
139 candidates.append(
140 {
141 "name": family_name,
142 "confidence": score * confidence_bimodal,
143 "voh": voh,
144 "vol": vol,
145 "vdd_estimated": estimated_vdd,
146 "thresholds": {
147 "vih": specs["vih_min"],
148 "vil": specs["vil_max"],
149 "voh": specs["voh_min"],
150 "vol": specs["vol_max"],
151 },
152 }
153 )
155 # Sort by confidence
156 candidates.sort(key=lambda x: x["confidence"], reverse=True) # type: ignore[arg-type, return-value, syntax]
158 # Primary detection
159 primary = candidates[0]
161 # Build result
162 result = {
163 "primary": primary,
164 }
166 if return_candidates:
167 result["candidates"] = candidates # type: ignore[assignment]
169 # Warn if confidence is low
170 if primary["confidence"] < min_confidence: # type: ignore[operator]
171 # In real implementation, might prompt user or return multiple candidates
172 pass
174 return result
177def _detect_logic_levels(
178 data: NDArray[np.floating[Any]],
179) -> tuple[float, float, float]:
180 """Detect logic high and low levels from signal histogram.
182 Args:
183 data: Signal data array.
185 Returns:
186 Tuple of (VOH, VOL, confidence) where confidence is 0-1.
187 """
188 # Create histogram
189 hist, bin_edges = np.histogram(data, bins=100)
190 bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
192 # Find peaks in histogram (expect bimodal for digital signal)
193 # Smooth histogram slightly
194 from scipy.ndimage import gaussian_filter1d
196 hist_smooth = gaussian_filter1d(hist.astype(float), sigma=2)
198 # Find local maxima
199 peaks = []
200 for i in range(1, len(hist_smooth) - 1):
201 if hist_smooth[i] > hist_smooth[i - 1] and hist_smooth[i] > hist_smooth[i + 1]:
202 if hist_smooth[i] > np.max(hist_smooth) * 0.1: # At least 10% of max 202 ↛ 200line 202 didn't jump to line 200 because the condition on line 202 was always true
203 peaks.append((bin_centers[i], hist_smooth[i]))
205 # Should have 2 peaks for digital signal
206 if len(peaks) < 2:
207 # Fall back to percentile method
208 vol = np.percentile(data, 5)
209 voh = np.percentile(data, 95)
210 confidence = 0.5
211 else:
212 # Take two highest peaks
213 peaks.sort(key=lambda x: x[1], reverse=True)
214 peak1, peak2 = peaks[0], peaks[1]
216 # Lower voltage is VOL, higher is VOH
217 if peak1[0] < peak2[0]:
218 vol, voh = peak1[0], peak2[0]
219 else:
220 vol, voh = peak2[0], peak1[0]
222 # Confidence based on peak separation and height
223 separation = abs(voh - vol)
224 min_peak_height = min(peak1[1], peak2[1])
225 max_peak_height = max(peak1[1], peak2[1])
227 # Good separation and similar peak heights = high confidence
228 confidence = min(1.0, (separation / 5.0) * (min_peak_height / max_peak_height))
230 return voh, vol, confidence
233def _score_logic_family(
234 voh: float,
235 vol: float,
236 vdd: float,
237 specs: dict[str, float],
238) -> float:
239 """Score how well measured levels match a logic family.
241 Args:
242 voh: Measured high level.
243 vol: Measured low level.
244 vdd: Estimated supply voltage.
245 specs: Logic family specifications.
247 Returns:
248 Score from 0 (no match) to 1 (perfect match).
249 """
250 # Check VDD match
251 vdd_error = abs(vdd - specs["vdd"]) / specs["vdd"]
252 vdd_score = max(0, 1.0 - vdd_error * 2) # Within 50% gets some score
254 # Check VOH is above spec minimum
255 if voh >= specs["voh_min"]:
256 voh_score = 1.0
257 else:
258 voh_error = (specs["voh_min"] - voh) / specs["voh_min"]
259 voh_score = max(0, 1.0 - voh_error * 5)
261 # Check VOL is below spec maximum
262 if vol <= specs["vol_max"]:
263 vol_score = 1.0
264 else:
265 vol_error = (vol - specs["vol_max"]) / specs["vol_max"]
266 vol_score = max(0, 1.0 - vol_error * 5)
268 # Combined score (weighted average)
269 score = vdd_score * 0.5 + voh_score * 0.25 + vol_score * 0.25
271 return score
274__all__ = ["LOGIC_FAMILY_SPECS", "detect_logic_family"]