Coverage for src / tracekit / workflows / digital.py: 100%
66 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"""Digital buffer characterization workflow.
3This module implements complete TTL/CMOS buffer characterization in a single
4function call, with automatic logic family detection.
7Example:
8 >>> import tracekit as tk
9 >>> trace = tk.load('74hc04_output.wfm')
10 >>> result = tk.characterize_buffer(trace)
11 >>> print(f"Logic Family: {result['logic_family']}")
12 >>> print(f"Rise Time: {result['rise_time']:.2f} ns")
13 >>> print(f"Status: {result['status']}")
15References:
16 IEEE 181-2011: Standard for Transitional Waveform Definitions
17 JEDEC Standard No. 65B: High-Speed Interface Timing
18"""
20from __future__ import annotations
22from typing import TYPE_CHECKING, Any
24import numpy as np
26from tracekit.core.exceptions import AnalysisError
28if TYPE_CHECKING:
29 from tracekit.core.types import WaveformTrace
32def characterize_buffer(
33 trace: WaveformTrace,
34 *,
35 reference_trace: WaveformTrace | None = None,
36 logic_family: str | None = None,
37 thresholds: dict[str, float] | None = None,
38 report: str | None = None,
39) -> dict[str, Any]:
40 """Characterize digital buffer timing and quality.
42 One-call characterization of digital buffer including:
43 - Automatic logic family detection (if not specified)
44 - Rise/fall time measurements
45 - Propagation delay (if reference provided)
46 - Overshoot/undershoot analysis
47 - Noise margin calculation
48 - Pass/fail against logic family specifications
50 Args:
51 trace: Output signal to characterize.
52 reference_trace: Optional reference (input) signal for propagation delay.
53 logic_family: Logic family override (e.g., 'TTL', 'CMOS_3V3', 'CMOS_5V').
54 If None, auto-detected from signal levels.
55 thresholds: Optional dict of custom pass/fail thresholds
56 (e.g., {'rise_time': 10e-9} for 10 ns max).
57 report: Optional path to save HTML report.
59 Returns:
60 Dictionary containing:
61 - logic_family: Detected or specified logic family
62 - rise_time: 10%-90% rise time in seconds
63 - fall_time: 10%-90% fall time in seconds
64 - propagation_delay: Delay from reference (if provided), in seconds
65 - overshoot: Peak overshoot voltage
66 - overshoot_percent: Overshoot as percentage of swing
67 - undershoot: Peak undershoot voltage
68 - undershoot_percent: Undershoot as percentage of swing
69 - noise_margin_high: High-level noise margin in volts
70 - noise_margin_low: Low-level noise margin in volts
71 - status: 'PASS' or 'FAIL' based on logic family specs
72 - reference_comparison: Dict with timing drift if reference provided
73 - confidence: Confidence score for logic family detection (0-1)
75 Raises:
76 AnalysisError: If trace has insufficient transitions for analysis.
78 Example:
79 >>> trace = tk.load('74hc04_output.wfm')
80 >>> result = tk.characterize_buffer(trace, logic_family='CMOS_3V3')
81 >>> print(f"Rise Time: {result['rise_time']*1e9:.2f} ns")
82 >>> print(f"Status: {result['status']}")
84 References:
85 IEEE 181-2011 Section 5.2 (Edge timing)
86 JEDEC Standard No. 65B (Logic family specifications)
87 """
88 # Import here to avoid circular dependencies
89 from tracekit.analyzers.waveform.measurements import (
90 fall_time,
91 overshoot,
92 rise_time,
93 undershoot,
94 )
95 from tracekit.inference.logic import detect_logic_family
97 # Auto-detect logic family if not specified
98 if logic_family is None:
99 detection = detect_logic_family(trace)
100 logic_family = detection["primary"]["name"]
101 confidence = detection["primary"]["confidence"]
102 voh = detection["primary"]["voh"]
103 vol = detection["primary"]["vol"]
104 else:
105 confidence = 1.0
106 # Measure VOH/VOL from trace
107 voh = np.percentile(trace.data, 95)
108 vol = np.percentile(trace.data, 5)
110 # Measure timing parameters
111 try:
112 t_rise = rise_time(trace)
113 t_fall = fall_time(trace)
114 except Exception as e:
115 raise AnalysisError(f"Failed to measure rise/fall time: {e}") from e
117 # Measure overshoot/undershoot
118 v_overshoot = overshoot(trace)
119 v_undershoot = undershoot(trace)
121 # Calculate percentages
122 swing = voh - vol
123 if swing > 0:
124 overshoot_pct = (v_overshoot / swing) * 100.0
125 undershoot_pct = (v_undershoot / swing) * 100.0
126 else:
127 overshoot_pct = 0.0
128 undershoot_pct = 0.0
130 # Calculate noise margins (simplified - uses typical values)
131 # In a real implementation, these would come from LOGIC_FAMILIES constants
132 logic_specs = _get_logic_specs(logic_family)
133 noise_margin_high = voh - logic_specs["vih"]
134 noise_margin_low = logic_specs["vil"] - vol
136 # Propagation delay if reference provided
137 propagation_delay = None
138 timing_drift = None
139 if reference_trace is not None:
140 try:
141 from tracekit.analyzers.digital.timing import (
142 propagation_delay as prop_delay,
143 )
145 propagation_delay = prop_delay(reference_trace, trace)
146 except Exception:
147 # If propagation delay measurement fails, set to None
148 propagation_delay = None
150 # Apply thresholds and determine pass/fail
151 status = "PASS"
152 if thresholds is not None:
153 if "rise_time" in thresholds and t_rise > thresholds["rise_time"]:
154 status = "FAIL"
155 if "fall_time" in thresholds and t_fall > thresholds["fall_time"]:
156 status = "FAIL"
157 if "overshoot_percent" in thresholds and overshoot_pct > thresholds["overshoot_percent"]:
158 status = "FAIL"
159 else:
160 # Use logic family defaults
161 if t_rise > logic_specs.get("max_rise_time", float("inf")):
162 status = "FAIL"
163 if t_fall > logic_specs.get("max_fall_time", float("inf")):
164 status = "FAIL"
166 # Build result dictionary
167 result = {
168 "logic_family": logic_family,
169 "confidence": confidence,
170 "rise_time": t_rise,
171 "fall_time": t_fall,
172 "propagation_delay": propagation_delay,
173 "overshoot": v_overshoot,
174 "overshoot_percent": overshoot_pct,
175 "undershoot": v_undershoot,
176 "undershoot_percent": undershoot_pct,
177 "noise_margin_high": noise_margin_high,
178 "noise_margin_low": noise_margin_low,
179 "voh": voh,
180 "vol": vol,
181 "status": status,
182 "reference_comparison": None,
183 }
185 if reference_trace is not None and propagation_delay is not None:
186 result["reference_comparison"] = {
187 "propagation_delay": propagation_delay,
188 "timing_drift": timing_drift,
189 }
191 # Generate report if requested
192 if report is not None:
193 _generate_buffer_report(result, report)
195 return result
198def _get_logic_specs(family: str) -> dict[str, float]:
199 """Get specifications for a logic family.
201 Args:
202 family: Logic family name.
204 Returns:
205 Dict with VIH, VIL, and timing specs.
206 """
207 specs = {
208 "TTL": {
209 "vih": 2.0,
210 "vil": 0.8,
211 "max_rise_time": 10e-9,
212 "max_fall_time": 10e-9,
213 },
214 "CMOS_5V": {
215 "vih": 3.5,
216 "vil": 1.5,
217 "max_rise_time": 15e-9,
218 "max_fall_time": 15e-9,
219 },
220 "CMOS_3V3": {
221 "vih": 2.0,
222 "vil": 0.8,
223 "max_rise_time": 5e-9,
224 "max_fall_time": 5e-9,
225 },
226 "LVTTL": {
227 "vih": 2.0,
228 "vil": 0.8,
229 "max_rise_time": 3e-9,
230 "max_fall_time": 3e-9,
231 },
232 "LVCMOS": {
233 "vih": 1.7,
234 "vil": 0.7,
235 "max_rise_time": 2e-9,
236 "max_fall_time": 2e-9,
237 },
238 }
239 return specs.get(family, specs["CMOS_3V3"])
242def _generate_buffer_report(result: dict[str, Any], output_path: str) -> None:
243 """Generate HTML report for buffer characterization.
245 Args:
246 result: Characterization result dictionary.
247 output_path: Path to save HTML report.
248 """
249 # Simplified report generation - in real implementation would use
250 # tracekit.reporting module
251 html = f"""
252 <html>
253 <head><title>Buffer Characterization Report</title></head>
254 <body>
255 <h1>Buffer Characterization Report</h1>
256 <h2>Logic Family: {result["logic_family"]} (confidence: {result["confidence"]:.1%})</h2>
257 <table>
258 <tr><th>Parameter</th><th>Value</th><th>Units</th></tr>
259 <tr><td>Rise Time</td><td>{result["rise_time"] * 1e9:.2f}</td><td>ns</td></tr>
260 <tr><td>Fall Time</td><td>{result["fall_time"] * 1e9:.2f}</td><td>ns</td></tr>
261 <tr><td>Overshoot</td><td>{result["overshoot_percent"]:.1f}</td><td>%</td></tr>
262 <tr><td>Undershoot</td><td>{result["undershoot_percent"]:.1f}</td><td>%</td></tr>
263 <tr><td>Status</td><td><b>{result["status"]}</b></td><td></td></tr>
264 </table>
265 </body>
266 </html>
267 """
268 with open(output_path, "w") as f:
269 f.write(html)
272__all__ = ["characterize_buffer"]