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

1"""Digital buffer characterization workflow. 

2 

3This module implements complete TTL/CMOS buffer characterization in a single 

4function call, with automatic logic family detection. 

5 

6 

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']}") 

14 

15References: 

16 IEEE 181-2011: Standard for Transitional Waveform Definitions 

17 JEDEC Standard No. 65B: High-Speed Interface Timing 

18""" 

19 

20from __future__ import annotations 

21 

22from typing import TYPE_CHECKING, Any 

23 

24import numpy as np 

25 

26from tracekit.core.exceptions import AnalysisError 

27 

28if TYPE_CHECKING: 

29 from tracekit.core.types import WaveformTrace 

30 

31 

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. 

41 

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 

49 

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. 

58 

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) 

74 

75 Raises: 

76 AnalysisError: If trace has insufficient transitions for analysis. 

77 

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']}") 

83 

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 

96 

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) 

109 

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 

116 

117 # Measure overshoot/undershoot 

118 v_overshoot = overshoot(trace) 

119 v_undershoot = undershoot(trace) 

120 

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 

129 

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 

135 

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 ) 

144 

145 propagation_delay = prop_delay(reference_trace, trace) 

146 except Exception: 

147 # If propagation delay measurement fails, set to None 

148 propagation_delay = None 

149 

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" 

165 

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 } 

184 

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 } 

190 

191 # Generate report if requested 

192 if report is not None: 

193 _generate_buffer_report(result, report) 

194 

195 return result 

196 

197 

198def _get_logic_specs(family: str) -> dict[str, float]: 

199 """Get specifications for a logic family. 

200 

201 Args: 

202 family: Logic family name. 

203 

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"]) 

240 

241 

242def _generate_buffer_report(result: dict[str, Any], output_path: str) -> None: 

243 """Generate HTML report for buffer characterization. 

244 

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) 

270 

271 

272__all__ = ["characterize_buffer"]