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

1"""Logic family auto-detection from signal levels. 

2 

3This module analyzes digital signal voltage levels to automatically 

4detect the logic family (TTL, CMOS, LVTTL, LVCMOS, etc.). 

5 

6 

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%}") 

13 

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

18 

19from __future__ import annotations 

20 

21from typing import TYPE_CHECKING, Any 

22 

23import numpy as np 

24 

25from tracekit.core.exceptions import AnalysisError 

26 

27if TYPE_CHECKING: 

28 from numpy.typing import NDArray 

29 

30 from tracekit.core.types import WaveformTrace 

31 

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} 

77 

78 

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. 

86 

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. 

90 

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. 

96 

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) 

106 

107 Raises: 

108 AnalysisError: If signal is not bimodal or levels are ambiguous. 

109 

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%}") 

117 

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) 

124 

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 ) 

130 

131 # Calculate swing and nominal VDD 

132 voh - vol 

133 estimated_vdd = voh + 0.3 # Assume VOH is slightly below VDD 

134 

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 ) 

154 

155 # Sort by confidence 

156 candidates.sort(key=lambda x: x["confidence"], reverse=True) # type: ignore[arg-type, return-value, syntax] 

157 

158 # Primary detection 

159 primary = candidates[0] 

160 

161 # Build result 

162 result = { 

163 "primary": primary, 

164 } 

165 

166 if return_candidates: 

167 result["candidates"] = candidates # type: ignore[assignment] 

168 

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 

173 

174 return result 

175 

176 

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. 

181 

182 Args: 

183 data: Signal data array. 

184 

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 

191 

192 # Find peaks in histogram (expect bimodal for digital signal) 

193 # Smooth histogram slightly 

194 from scipy.ndimage import gaussian_filter1d 

195 

196 hist_smooth = gaussian_filter1d(hist.astype(float), sigma=2) 

197 

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

204 

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] 

215 

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] 

221 

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

226 

227 # Good separation and similar peak heights = high confidence 

228 confidence = min(1.0, (separation / 5.0) * (min_peak_height / max_peak_height)) 

229 

230 return voh, vol, confidence 

231 

232 

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. 

240 

241 Args: 

242 voh: Measured high level. 

243 vol: Measured low level. 

244 vdd: Estimated supply voltage. 

245 specs: Logic family specifications. 

246 

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 

253 

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) 

260 

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) 

267 

268 # Combined score (weighted average) 

269 score = vdd_score * 0.5 + voh_score * 0.25 + vol_score * 0.25 

270 

271 return score 

272 

273 

274__all__ = ["LOGIC_FAMILY_SPECS", "detect_logic_family"]