oscura 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,377 @@
1
+ """High-level vintage logic analysis API.
2
+
3
+ This module provides a unified interface for complete vintage logic system analysis,
4
+ orchestrating all analysis steps and returning consolidated results.
5
+
6
+ Example:
7
+ >>> from oscura.analyzers.digital.vintage import analyze_vintage_logic
8
+ >>> result = analyze_vintage_logic(
9
+ ... traces={"CLK": clk_trace, "DATA": data_trace},
10
+ ... target_frequency=2e6
11
+ ... )
12
+ >>> print(f"Detected: {result.detected_family}")
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import time
18
+ from datetime import datetime
19
+ from typing import TYPE_CHECKING, Any, cast
20
+
21
+ import numpy as np
22
+
23
+ from oscura.analyzers.digital.extraction import (
24
+ LOGIC_FAMILIES,
25
+ detect_logic_family,
26
+ detect_open_collector,
27
+ )
28
+ from oscura.analyzers.digital.ic_database import identify_ic, validate_ic_timing
29
+ from oscura.analyzers.digital.timing import hold_time, propagation_delay, setup_time
30
+ from oscura.analyzers.digital.timing_paths import TimingPathResult, analyze_timing_path
31
+ from oscura.analyzers.digital.vintage_result import (
32
+ BOMEntry,
33
+ ICIdentificationResult,
34
+ ModernReplacementIC,
35
+ VintageLogicAnalysisResult,
36
+ )
37
+ from oscura.core.types import WaveformTrace
38
+
39
+ if TYPE_CHECKING:
40
+ from oscura.core.types import DigitalTrace, WaveformTrace
41
+
42
+
43
+ # Modern IC replacement recommendations
44
+ REPLACEMENT_DATABASE: dict[str, ModernReplacementIC] = {
45
+ "7400": ModernReplacementIC(
46
+ original_ic="7400",
47
+ replacement_ic="74HCT00",
48
+ family="74HCTxx",
49
+ benefits=["Lower power", "Wider voltage range", "TTL-compatible inputs"],
50
+ notes="HCT family maintains TTL compatibility with CMOS benefits",
51
+ ),
52
+ "7474": ModernReplacementIC(
53
+ original_ic="7474",
54
+ replacement_ic="74HCT74",
55
+ family="74HCTxx",
56
+ benefits=["Lower power", "Wider voltage range", "TTL-compatible inputs"],
57
+ notes="HCT family maintains TTL compatibility with CMOS benefits",
58
+ ),
59
+ "74LS00": ModernReplacementIC(
60
+ original_ic="74LS00",
61
+ replacement_ic="74HCT00",
62
+ family="74HCTxx",
63
+ benefits=["Lower power", "Similar speed", "Better availability"],
64
+ notes="Direct pin-compatible replacement",
65
+ ),
66
+ "74LS74": ModernReplacementIC(
67
+ original_ic="74LS74",
68
+ replacement_ic="74HCT74",
69
+ family="74HCTxx",
70
+ benefits=["Lower power", "Similar speed", "Better availability"],
71
+ notes="Direct pin-compatible replacement",
72
+ ),
73
+ "74LS138": ModernReplacementIC(
74
+ original_ic="74LS138",
75
+ replacement_ic="74HCT138",
76
+ family="74HCTxx",
77
+ benefits=["Lower power", "Similar speed", "Better availability"],
78
+ notes="Direct pin-compatible replacement",
79
+ ),
80
+ "74LS244": ModernReplacementIC(
81
+ original_ic="74LS244",
82
+ replacement_ic="74HCT244",
83
+ family="74HCTxx",
84
+ benefits=["Lower power", "Similar speed", "Better availability"],
85
+ notes="Direct pin-compatible replacement",
86
+ ),
87
+ "74LS245": ModernReplacementIC(
88
+ original_ic="74LS245",
89
+ replacement_ic="74HCT245",
90
+ family="74HCTxx",
91
+ benefits=["Lower power", "Similar speed", "Better availability"],
92
+ notes="Direct pin-compatible replacement",
93
+ ),
94
+ "74LS273": ModernReplacementIC(
95
+ original_ic="74LS273",
96
+ replacement_ic="74HCT273",
97
+ family="74HCTxx",
98
+ benefits=["Lower power", "Similar speed", "Better availability"],
99
+ notes="Direct pin-compatible replacement",
100
+ ),
101
+ "74LS374": ModernReplacementIC(
102
+ original_ic="74LS374",
103
+ replacement_ic="74HCT374",
104
+ family="74HCTxx",
105
+ benefits=["Lower power", "Similar speed", "Better availability"],
106
+ notes="Direct pin-compatible replacement",
107
+ ),
108
+ }
109
+
110
+
111
+ def analyze_vintage_logic(
112
+ traces: dict[str, WaveformTrace | DigitalTrace],
113
+ *,
114
+ target_frequency: float | None = None,
115
+ system_description: str | None = None,
116
+ enable_protocol_decode: bool = False,
117
+ timing_paths: list[tuple[str, WaveformTrace, WaveformTrace]] | None = None,
118
+ ) -> VintageLogicAnalysisResult:
119
+ """Complete vintage logic system analysis.
120
+
121
+ High-level API that orchestrates all vintage logic analysis steps and returns
122
+ a comprehensive result object suitable for reporting and export.
123
+
124
+ Args:
125
+ traces: Dictionary mapping channel names to traces.
126
+ target_frequency: Target system clock frequency in Hz.
127
+ system_description: Optional description for documentation.
128
+ enable_protocol_decode: Enable automatic protocol decoding (GPIB, ISA, etc.).
129
+ timing_paths: Optional list of (ic_name, input_trace, output_trace) tuples
130
+ for multi-IC timing path analysis.
131
+
132
+ Returns:
133
+ VintageLogicAnalysisResult object with complete analysis data.
134
+
135
+ Example:
136
+ >>> result = analyze_vintage_logic(
137
+ ... traces={"CLK": clk_trace, "DATA": data_trace},
138
+ ... target_frequency=2e6,
139
+ ... system_description="1976 Microcomputer System"
140
+ ... )
141
+ >>> print(f"Detected: {result.detected_family}")
142
+ >>> print(f"ICs: {[ic.ic_name for ic in result.identified_ics]}")
143
+ """
144
+ start_time = time.time()
145
+ warnings: list[str] = []
146
+ confidence_scores: dict[str, float] = {}
147
+
148
+ # Use first trace for logic family detection
149
+ first_trace_name = next(iter(traces.keys()))
150
+ first_trace = traces[first_trace_name]
151
+
152
+ # Ensure we have a WaveformTrace for voltage-based analysis
153
+ if not hasattr(first_trace.data, "dtype") or first_trace.data.dtype == bool:
154
+ warnings.append(
155
+ f"Digital trace provided for {first_trace_name}, "
156
+ "logic family detection may be inaccurate"
157
+ )
158
+ detected_family = "unknown"
159
+ family_confidence = 0.0
160
+ voltage_levels = {}
161
+ else:
162
+ # Step 1: Detect logic family
163
+ # Type narrowing: we've verified this is a WaveformTrace above
164
+ waveform_trace = cast("WaveformTrace", first_trace)
165
+ detected_family, family_confidence = detect_logic_family(waveform_trace)
166
+ confidence_scores["logic_family"] = family_confidence
167
+
168
+ # Get voltage levels from detected family
169
+ if detected_family in LOGIC_FAMILIES:
170
+ voltage_levels = dict(LOGIC_FAMILIES[detected_family])
171
+ else:
172
+ voltage_levels = {}
173
+ warnings.append(f"Unknown logic family: {detected_family}")
174
+
175
+ # Step 2: Detect open-collector outputs
176
+ open_collector_detected = False
177
+ asymmetry_ratio = 1.0
178
+ if hasattr(first_trace.data, "dtype") and first_trace.data.dtype != bool:
179
+ waveform_trace = cast("WaveformTrace", first_trace)
180
+ open_collector_detected, asymmetry_ratio = detect_open_collector(waveform_trace)
181
+ if open_collector_detected:
182
+ warnings.append(
183
+ "Open-collector output detected - consider 10kΩ pull-up in modern design"
184
+ )
185
+
186
+ # Step 3: Measure timing parameters and identify ICs
187
+ identified_ics: list[ICIdentificationResult] = []
188
+ timing_measurements: dict[str, float] = {}
189
+
190
+ # Analyze each trace pair for timing
191
+ trace_list = list(traces.items())
192
+ for i in range(len(trace_list) - 1):
193
+ name1, trace1 = trace_list[i]
194
+ name2, trace2 = trace_list[i + 1]
195
+
196
+ # Measure propagation delay
197
+ try:
198
+ t_pd_raw = propagation_delay(trace1, trace2)
199
+ t_pd = (
200
+ float(t_pd_raw)
201
+ if isinstance(t_pd_raw, (int, float, np.number))
202
+ else float(t_pd_raw.item())
203
+ )
204
+ if t_pd > 0:
205
+ timing_measurements[f"{name1}→{name2}_t_pd"] = t_pd
206
+ except Exception as e:
207
+ warnings.append(f"Failed to measure propagation delay {name1}→{name2}: {e}")
208
+
209
+ # Measure setup and hold times if clock-like signal
210
+ try:
211
+ t_su_raw = setup_time(trace1, trace2)
212
+ t_su = (
213
+ float(t_su_raw)
214
+ if isinstance(t_su_raw, (int, float, np.number))
215
+ else float(t_su_raw.item())
216
+ )
217
+ if t_su > 0:
218
+ timing_measurements[f"{name2}_t_su"] = t_su
219
+ except Exception:
220
+ pass # Optional measurement
221
+
222
+ try:
223
+ t_h_raw = hold_time(trace1, trace2)
224
+ t_h = (
225
+ float(t_h_raw)
226
+ if isinstance(t_h_raw, (int, float, np.number))
227
+ else float(t_h_raw.item())
228
+ )
229
+ if t_h > 0:
230
+ timing_measurements[f"{name2}_t_h"] = t_h
231
+ except Exception:
232
+ pass # Optional measurement
233
+
234
+ # Attempt IC identification from timing measurements
235
+ if timing_measurements:
236
+ # Extract core timing parameters
237
+ core_params = {}
238
+ for key, value in timing_measurements.items():
239
+ if "_t_pd" in key:
240
+ core_params["t_pd"] = value
241
+ elif "_t_su" in key and "t_su" not in core_params:
242
+ core_params["t_su"] = value
243
+ elif "_t_h" in key and "t_h" not in core_params:
244
+ core_params["t_h"] = value
245
+
246
+ if core_params:
247
+ ic_name, ic_confidence = identify_ic(core_params, tolerance=0.5)
248
+ confidence_scores["ic_identification"] = ic_confidence
249
+
250
+ if ic_name != "unknown":
251
+ # Validate against database
252
+ try:
253
+ validation = validate_ic_timing(ic_name, core_params, tolerance=0.3)
254
+
255
+ identified_ics.append(
256
+ ICIdentificationResult(
257
+ ic_name=ic_name,
258
+ confidence=ic_confidence,
259
+ timing_params=core_params,
260
+ validation=validation,
261
+ family=detected_family,
262
+ )
263
+ )
264
+
265
+ # Check if any parameters fail validation
266
+ failed_params = [k for k, v in validation.items() if v.get("passes") is False]
267
+ if failed_params:
268
+ warnings.append(
269
+ f"IC {ic_name} timing validation failed for: {', '.join(failed_params)}"
270
+ )
271
+ except KeyError:
272
+ warnings.append(f"IC {ic_name} not found in database")
273
+
274
+ # Step 4: Analyze timing paths if provided
275
+ timing_path_results: list[TimingPathResult] | None = None
276
+ if timing_paths:
277
+ try:
278
+ path_result = analyze_timing_path(timing_paths, target_frequency=target_frequency)
279
+ timing_path_results = [path_result]
280
+
281
+ if not path_result.meets_timing:
282
+ warnings.append(
283
+ f"Timing path violation detected at stage {path_result.critical_stage_idx}"
284
+ )
285
+ except Exception as e:
286
+ warnings.append(f"Timing path analysis failed: {e}")
287
+
288
+ # Step 5: Protocol decoding (if enabled)
289
+ decoded_protocols: dict[str, list[Any]] | None = None
290
+ if enable_protocol_decode:
291
+ # Protocol decoding would be implemented here
292
+ # For now, just a placeholder
293
+ decoded_protocols = {}
294
+
295
+ # Step 6: Generate modern replacement recommendations
296
+ modern_replacements: list[ModernReplacementIC] = []
297
+ for ic_result in identified_ics:
298
+ if ic_result.ic_name in REPLACEMENT_DATABASE:
299
+ modern_replacements.append(REPLACEMENT_DATABASE[ic_result.ic_name])
300
+
301
+ # Step 7: Generate BOM
302
+ bom: list[BOMEntry] = []
303
+
304
+ # Add identified ICs
305
+ for ic_result in identified_ics:
306
+ bom.append(
307
+ BOMEntry(
308
+ part_number=ic_result.ic_name,
309
+ description=f"Original IC from {detected_family} family",
310
+ quantity=1,
311
+ category="IC",
312
+ notes=f"Confidence: {ic_result.confidence * 100:.1f}%",
313
+ )
314
+ )
315
+
316
+ # Add modern replacements
317
+ for replacement in modern_replacements:
318
+ bom.append(
319
+ BOMEntry(
320
+ part_number=replacement.replacement_ic,
321
+ description=f"Modern replacement for {replacement.original_ic}",
322
+ quantity=1,
323
+ category="IC",
324
+ notes=f"Benefits: {', '.join(replacement.benefits)}",
325
+ )
326
+ )
327
+
328
+ # Add supporting components
329
+ if open_collector_detected:
330
+ bom.append(
331
+ BOMEntry(
332
+ part_number="Pull-up resistor",
333
+ description="10kΩ resistor for open-collector pull-up",
334
+ quantity=len(list(traces.values())),
335
+ category="Resistor",
336
+ notes="Use for all open-collector outputs",
337
+ )
338
+ )
339
+
340
+ # Add decoupling capacitors (good practice)
341
+ bom.append(
342
+ BOMEntry(
343
+ part_number="0.1µF ceramic capacitor",
344
+ description="Decoupling capacitor",
345
+ quantity=len(identified_ics) * 2,
346
+ category="Capacitor",
347
+ notes="One per IC, place close to VCC/GND pins",
348
+ )
349
+ )
350
+
351
+ # Calculate analysis duration
352
+ analysis_duration = time.time() - start_time
353
+
354
+ return VintageLogicAnalysisResult(
355
+ timestamp=datetime.now(),
356
+ source_file=system_description,
357
+ analysis_duration=analysis_duration,
358
+ detected_family=detected_family,
359
+ family_confidence=family_confidence,
360
+ voltage_levels=voltage_levels,
361
+ identified_ics=identified_ics,
362
+ timing_measurements=timing_measurements,
363
+ timing_paths=timing_path_results,
364
+ decoded_protocols=decoded_protocols,
365
+ open_collector_detected=open_collector_detected,
366
+ asymmetry_ratio=asymmetry_ratio,
367
+ modern_replacements=modern_replacements,
368
+ bom=bom,
369
+ warnings=warnings,
370
+ confidence_scores=confidence_scores,
371
+ )
372
+
373
+
374
+ __all__ = [
375
+ "REPLACEMENT_DATABASE",
376
+ "analyze_vintage_logic",
377
+ ]
@@ -0,0 +1,148 @@
1
+ """Vintage logic analysis result data structures.
2
+
3
+ This module defines dataclasses for aggregating vintage logic analysis results,
4
+ enabling comprehensive reporting and export capabilities.
5
+
6
+ Example:
7
+ >>> from oscura.analyzers.digital.vintage_result import VintageLogicAnalysisResult
8
+ >>> result = VintageLogicAnalysisResult(
9
+ ... timestamp=datetime.now(),
10
+ ... detected_family="TTL",
11
+ ... family_confidence=0.95,
12
+ ... # ... more fields ...
13
+ ... )
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass, field
19
+ from datetime import datetime
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ if TYPE_CHECKING:
23
+ from oscura.analyzers.digital.timing_paths import TimingPathResult
24
+
25
+
26
+ @dataclass
27
+ class ICIdentificationResult:
28
+ """Single IC identification result.
29
+
30
+ Attributes:
31
+ ic_name: Identified IC part number (e.g., "74LS74").
32
+ confidence: Confidence score (0.0-1.0).
33
+ timing_params: Measured timing parameters in seconds.
34
+ validation: Validation results from validate_ic_timing().
35
+ family: Logic family of the IC.
36
+ """
37
+
38
+ ic_name: str
39
+ confidence: float
40
+ timing_params: dict[str, float]
41
+ validation: dict[str, dict[str, Any]]
42
+ family: str
43
+
44
+
45
+ @dataclass
46
+ class ModernReplacementIC:
47
+ """Modern IC recommendation for vintage part.
48
+
49
+ Attributes:
50
+ original_ic: Original vintage IC part number.
51
+ replacement_ic: Recommended modern replacement.
52
+ family: Replacement logic family (e.g., "74HCTxx").
53
+ benefits: List of benefits (speed, power, availability).
54
+ notes: Optional additional notes.
55
+ """
56
+
57
+ original_ic: str
58
+ replacement_ic: str
59
+ family: str
60
+ benefits: list[str]
61
+ notes: str | None = None
62
+
63
+
64
+ @dataclass
65
+ class BOMEntry:
66
+ """Bill of materials entry.
67
+
68
+ Attributes:
69
+ part_number: Component part number.
70
+ description: Component description.
71
+ quantity: Number of components needed.
72
+ category: Component category ("IC", "Capacitor", "Buffer", etc.).
73
+ notes: Optional notes (pinout, alternatives, etc.).
74
+ """
75
+
76
+ part_number: str
77
+ description: str
78
+ quantity: int
79
+ category: str
80
+ notes: str | None = None
81
+
82
+
83
+ @dataclass
84
+ class VintageLogicAnalysisResult:
85
+ """Complete vintage logic analysis result.
86
+
87
+ Aggregates all analysis outputs for comprehensive reporting and export.
88
+
89
+ Attributes:
90
+ timestamp: Analysis timestamp.
91
+ source_file: Source file path if loaded from file.
92
+ analysis_duration: Analysis execution time in seconds.
93
+ detected_family: Detected logic family name.
94
+ family_confidence: Logic family detection confidence (0.0-1.0).
95
+ voltage_levels: Measured voltage levels (VCC, VIL, VIH, VOL, VOH).
96
+ identified_ics: List of identified ICs.
97
+ timing_measurements: Dictionary of timing measurements in seconds.
98
+ timing_paths: Multi-IC timing path analysis results.
99
+ decoded_protocols: Protocol decoder results if applicable.
100
+ open_collector_detected: Whether open-collector output detected.
101
+ asymmetry_ratio: Rise/fall time asymmetry ratio.
102
+ modern_replacements: List of modern IC recommendations.
103
+ bom: Bill of materials entries.
104
+ warnings: List of warning messages.
105
+ confidence_scores: Dictionary of confidence scores by analysis type.
106
+ """
107
+
108
+ # Analysis metadata
109
+ timestamp: datetime
110
+ source_file: str | None
111
+ analysis_duration: float
112
+
113
+ # Logic family detection results
114
+ detected_family: str
115
+ family_confidence: float
116
+ voltage_levels: dict[str, float] # VCC, VIL, VIH, VOL, VOH
117
+
118
+ # IC identification results
119
+ identified_ics: list[ICIdentificationResult] = field(default_factory=list)
120
+
121
+ # Timing measurements
122
+ timing_measurements: dict[str, float] = field(default_factory=dict)
123
+
124
+ # Multi-IC path analysis
125
+ timing_paths: list[TimingPathResult] | None = None
126
+
127
+ # Protocol decoder results (if applicable)
128
+ decoded_protocols: dict[str, list[Any]] | None = None
129
+
130
+ # Open-collector detection
131
+ open_collector_detected: bool = False
132
+ asymmetry_ratio: float = 1.0
133
+
134
+ # Recommendations
135
+ modern_replacements: list[ModernReplacementIC] = field(default_factory=list)
136
+ bom: list[BOMEntry] = field(default_factory=list)
137
+
138
+ # Quality metrics
139
+ warnings: list[str] = field(default_factory=list)
140
+ confidence_scores: dict[str, float] = field(default_factory=dict)
141
+
142
+
143
+ __all__ = [
144
+ "BOMEntry",
145
+ "ICIdentificationResult",
146
+ "ModernReplacementIC",
147
+ "VintageLogicAnalysisResult",
148
+ ]
@@ -2,7 +2,7 @@
2
2
 
3
3
  Provides protocol decoders for common serial and automotive protocols including
4
4
  UART, SPI, I2C, CAN, LIN, FlexRay, JTAG, SWD, I2S, USB, HDLC, Manchester, CAN-FD,
5
- and 1-Wire.
5
+ 1-Wire, and parallel bus protocols (GPIB, Centronics, ISA).
6
6
  """
7
7
 
8
8
  from oscura.analyzers.protocols.base import (
@@ -60,6 +60,18 @@ from oscura.analyzers.protocols.onewire import (
60
60
  OneWireTimings,
61
61
  decode_onewire,
62
62
  )
63
+
64
+ # Parallel bus protocols
65
+ from oscura.analyzers.protocols.parallel_bus import (
66
+ CentronicsFrame,
67
+ GPIBFrame,
68
+ GPIBMessageType,
69
+ ISACycleType,
70
+ ISATransaction,
71
+ decode_centronics,
72
+ decode_gpib,
73
+ decode_isa_bus,
74
+ )
63
75
  from oscura.analyzers.protocols.spi import SPIDecoder, decode_spi
64
76
  from oscura.analyzers.protocols.swd import SWDDecoder, SWDResponse, decode_swd
65
77
  from oscura.analyzers.protocols.uart import UARTDecoder, decode_uart
@@ -91,12 +103,16 @@ __all__ = [
91
103
  "CANFDFrameType",
92
104
  "CANFrame",
93
105
  "CANFrameType",
106
+ # Parallel bus protocols
107
+ "CentronicsFrame",
94
108
  "ChannelDef",
95
109
  "DecoderState",
96
110
  # FlexRay (PRO-016)
97
111
  "FlexRayDecoder",
98
112
  "FlexRayFrame",
99
113
  "FlexRaySegment",
114
+ "GPIBFrame",
115
+ "GPIBMessageType",
100
116
  # HDLC (PRO-013)
101
117
  "HDLCDecoder",
102
118
  # I2C (PRO-004)
@@ -104,6 +120,8 @@ __all__ = [
104
120
  # I2S (PRO-011)
105
121
  "I2SDecoder",
106
122
  "I2SMode",
123
+ "ISACycleType",
124
+ "ISATransaction",
107
125
  # JTAG (PRO-009)
108
126
  "JTAGDecoder",
109
127
  # LIN (PRO-008)
@@ -135,10 +153,13 @@ __all__ = [
135
153
  "USBSpeed",
136
154
  "decode_can",
137
155
  "decode_can_fd",
156
+ "decode_centronics",
138
157
  "decode_flexray",
158
+ "decode_gpib",
139
159
  "decode_hdlc",
140
160
  "decode_i2c",
141
161
  "decode_i2s",
162
+ "decode_isa_bus",
142
163
  "decode_jtag",
143
164
  "decode_lin",
144
165
  "decode_manchester",