oscura 0.5.0__py3-none-any.whl → 0.5.1__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.
Files changed (34) hide show
  1. oscura/__init__.py +1 -1
  2. oscura/analyzers/digital/__init__.py +0 -48
  3. oscura/analyzers/digital/extraction.py +0 -195
  4. oscura/analyzers/protocols/__init__.py +1 -22
  5. oscura/automotive/__init__.py +1 -1
  6. oscura/automotive/dtc/data.json +2763 -0
  7. oscura/export/__init__.py +0 -12
  8. oscura/export/wireshark/README.md +15 -15
  9. oscura/exporters/json_export.py +0 -47
  10. oscura/inference/active_learning/README.md +7 -7
  11. oscura/pipeline/composition.py +10 -2
  12. oscura/reporting/__init__.py +0 -7
  13. oscura/reporting/templates/index.md +13 -13
  14. oscura/schemas/bus_configuration.json +322 -0
  15. oscura/schemas/device_mapping.json +182 -0
  16. oscura/schemas/packet_format.json +418 -0
  17. oscura/schemas/protocol_definition.json +363 -0
  18. oscura/utils/autodetect.py +1 -5
  19. oscura-0.5.1.dist-info/METADATA +583 -0
  20. {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/RECORD +23 -28
  21. oscura/analyzers/digital/ic_database.py +0 -498
  22. oscura/analyzers/digital/timing_paths.py +0 -339
  23. oscura/analyzers/digital/vintage.py +0 -377
  24. oscura/analyzers/digital/vintage_result.py +0 -148
  25. oscura/analyzers/protocols/parallel_bus.py +0 -449
  26. oscura/export/wavedrom.py +0 -430
  27. oscura/exporters/vintage_logic_csv.py +0 -247
  28. oscura/reporting/vintage_logic_report.py +0 -523
  29. oscura/visualization/digital_advanced.py +0 -718
  30. oscura/visualization/figure_manager.py +0 -156
  31. oscura-0.5.0.dist-info/METADATA +0 -407
  32. {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/WHEEL +0 -0
  33. {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/entry_points.txt +0 -0
  34. {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,339 +0,0 @@
1
- """Multi-IC timing path analysis.
2
-
3
- Analyzes timing through chains of ICs to identify critical paths and
4
- validate system-level timing budgets.
5
-
6
- Example:
7
- >>> from oscura.analyzers.digital.timing_paths import analyze_timing_path
8
- >>> path = [
9
- ... ("74LS74", clk_trace, q_trace),
10
- ... ("74LS00", q_trace, y_trace),
11
- ... ("74LS74", y_trace, q2_trace),
12
- ... ]
13
- >>> result = analyze_timing_path(path)
14
- >>> print(f"Total propagation delay: {result.total_delay*1e9:.1f}ns")
15
- """
16
-
17
- from __future__ import annotations
18
-
19
- from dataclasses import dataclass
20
- from typing import TYPE_CHECKING
21
-
22
- import numpy as np
23
-
24
- if TYPE_CHECKING:
25
- from oscura.core.types import WaveformTrace
26
-
27
-
28
- @dataclass
29
- class ICStage:
30
- """A single IC stage in a timing path.
31
-
32
- Attributes:
33
- ic_name: IC part number.
34
- input_trace: Input signal trace.
35
- output_trace: Output signal trace.
36
- measured_delay: Measured propagation delay.
37
- spec_delay: Specification delay from database.
38
- margin: Timing margin (positive = meets spec).
39
- """
40
-
41
- ic_name: str
42
- input_trace: WaveformTrace
43
- output_trace: WaveformTrace
44
- measured_delay: float
45
- spec_delay: float | None
46
- margin: float | None
47
-
48
-
49
- @dataclass
50
- class TimingPathResult:
51
- """Result of timing path analysis.
52
-
53
- Attributes:
54
- stages: List of IC stages in the path.
55
- total_delay: Total path propagation delay.
56
- total_spec_delay: Total specification delay.
57
- critical_stage_idx: Index of stage with worst margin.
58
- path_margin: Overall path timing margin.
59
- meets_timing: Whether path meets all timing specs.
60
- """
61
-
62
- stages: list[ICStage]
63
- total_delay: float
64
- total_spec_delay: float | None
65
- critical_stage_idx: int | None
66
- path_margin: float | None
67
- meets_timing: bool
68
-
69
-
70
- def analyze_timing_path(
71
- path: list[tuple[str, WaveformTrace, WaveformTrace]],
72
- *,
73
- target_frequency: float | None = None,
74
- ) -> TimingPathResult:
75
- """Analyze timing through a chain of ICs.
76
-
77
- Args:
78
- path: List of (ic_name, input_trace, output_trace) tuples.
79
- target_frequency: Optional target frequency for setup time validation.
80
-
81
- Returns:
82
- TimingPathResult object.
83
-
84
- Example:
85
- >>> path = [
86
- ... ("74LS74", clk, q1),
87
- ... ("74LS00", q1, y),
88
- ... ("74LS74", y, q2),
89
- ... ]
90
- >>> result = analyze_timing_path(path, target_frequency=10e6)
91
- >>> if not result.meets_timing:
92
- ... print(f"Timing violation in stage {result.critical_stage_idx}")
93
- """
94
- from oscura.analyzers.digital.ic_database import IC_DATABASE
95
- from oscura.analyzers.digital.timing import propagation_delay
96
-
97
- stages: list[ICStage] = []
98
- total_delay = 0.0
99
- total_spec_delay = 0.0
100
- worst_margin = float("inf")
101
- critical_stage_idx = None
102
-
103
- for idx, (ic_name, input_trace, output_trace) in enumerate(path):
104
- # Measure propagation delay
105
- measured_pd_raw = propagation_delay(input_trace, output_trace, edge_type="rising")
106
- measured_pd = (
107
- float(measured_pd_raw)
108
- if isinstance(measured_pd_raw, (int, float, np.number))
109
- else float(measured_pd_raw.item())
110
- )
111
-
112
- # Get spec from database if available
113
- spec_delay = None
114
- margin = None
115
-
116
- if ic_name in IC_DATABASE:
117
- ic_spec = IC_DATABASE[ic_name]
118
- if "t_pd" in ic_spec.timing:
119
- spec_delay = ic_spec.timing["t_pd"]
120
- margin_raw = spec_delay - measured_pd
121
- margin = (
122
- float(margin_raw)
123
- if isinstance(margin_raw, (int, float, np.number))
124
- else float(margin_raw.item())
125
- )
126
- total_spec_delay += spec_delay
127
-
128
- # Track worst margin
129
- if margin < worst_margin:
130
- worst_margin = margin
131
- critical_stage_idx = idx
132
-
133
- total_delay += measured_pd
134
-
135
- stages.append(
136
- ICStage(
137
- ic_name=ic_name,
138
- input_trace=input_trace,
139
- output_trace=output_trace,
140
- measured_delay=measured_pd,
141
- spec_delay=spec_delay,
142
- margin=margin,
143
- )
144
- )
145
-
146
- # Determine if path meets timing
147
- meets_timing = True
148
- for stage in stages:
149
- if stage.margin is not None and stage.margin < 0:
150
- meets_timing = False
151
- break
152
-
153
- # Check against target frequency if provided
154
- path_margin = None
155
- if target_frequency is not None:
156
- target_period = 1.0 / target_frequency
157
- path_margin = target_period - total_delay
158
- if path_margin < 0:
159
- meets_timing = False
160
-
161
- return TimingPathResult(
162
- stages=stages,
163
- total_delay=total_delay,
164
- total_spec_delay=total_spec_delay if total_spec_delay > 0 else None,
165
- critical_stage_idx=critical_stage_idx,
166
- path_margin=path_margin,
167
- meets_timing=meets_timing,
168
- )
169
-
170
-
171
- def find_critical_paths(
172
- ic_graph: dict[str, list[tuple[str, WaveformTrace, WaveformTrace]]],
173
- *,
174
- start_node: str,
175
- end_node: str,
176
- ) -> list[TimingPathResult]:
177
- """Find all timing paths from start to end node.
178
-
179
- Args:
180
- ic_graph: Graph of IC connections.
181
- start_node: Starting IC name.
182
- end_node: Ending IC name.
183
-
184
- Returns:
185
- List of TimingPathResult objects sorted by total delay.
186
- """
187
- # This is a placeholder for a more sophisticated graph traversal
188
- # Would implement DFS/BFS to find all paths
189
- paths: list[TimingPathResult] = []
190
- return paths
191
-
192
-
193
- def calculate_timing_budget(
194
- path_result: TimingPathResult,
195
- *,
196
- target_frequency: float,
197
- margin_target: float = 0.1, # 10% margin
198
- ) -> dict[str, float]:
199
- """Calculate timing budget allocation for each stage.
200
-
201
- Args:
202
- path_result: Timing path analysis result.
203
- target_frequency: Target operating frequency.
204
- margin_target: Target margin fraction (0.0-1.0).
205
-
206
- Returns:
207
- Dictionary mapping stage index to allocated delay budget.
208
-
209
- Example:
210
- >>> budget = calculate_timing_budget(result, target_frequency=10e6)
211
- >>> print(f"Stage 0 budget: {budget[0]*1e9:.1f}ns")
212
- """
213
- target_period = 1.0 / target_frequency
214
-
215
- # Calculate available time (period minus desired margin)
216
- available_time = target_period * (1.0 - margin_target)
217
-
218
- # Allocate proportionally based on spec delays
219
- budget: dict[str, float] = {}
220
-
221
- total_spec = sum(s.spec_delay for s in path_result.stages if s.spec_delay is not None)
222
-
223
- if total_spec > 0:
224
- for idx, stage in enumerate(path_result.stages):
225
- if stage.spec_delay is not None:
226
- # Proportional allocation
227
- budget[str(idx)] = (stage.spec_delay / total_spec) * available_time
228
- else:
229
- # Equal allocation for unspecified stages
230
- budget[str(idx)] = available_time / len(path_result.stages)
231
- else:
232
- # Equal allocation if no specs available
233
- for idx in range(len(path_result.stages)):
234
- budget[str(idx)] = available_time / len(path_result.stages)
235
-
236
- return budget
237
-
238
-
239
- @dataclass
240
- class SetupHoldAnalysis:
241
- """Setup and hold time analysis for synchronous paths.
242
-
243
- Attributes:
244
- clock_period: Clock period in seconds.
245
- data_path_delay: Data path delay in seconds.
246
- clock_path_delay: Clock path delay in seconds.
247
- setup_time: Required setup time in seconds.
248
- hold_time: Required hold time in seconds.
249
- setup_slack: Setup time slack (positive = passes).
250
- hold_slack: Hold time slack (positive = passes).
251
- meets_setup: Whether setup time is met.
252
- meets_hold: Whether hold time is met.
253
- """
254
-
255
- clock_period: float
256
- data_path_delay: float
257
- clock_path_delay: float
258
- setup_time: float
259
- hold_time: float
260
- setup_slack: float
261
- hold_slack: float
262
- meets_setup: bool
263
- meets_hold: bool
264
-
265
-
266
- def analyze_setup_hold(
267
- data_path: list[tuple[str, WaveformTrace, WaveformTrace]],
268
- clock_path: list[tuple[str, WaveformTrace, WaveformTrace]],
269
- *,
270
- clock_period: float,
271
- destination_ic: str,
272
- ) -> SetupHoldAnalysis:
273
- """Analyze setup and hold time for a synchronous path.
274
-
275
- Args:
276
- data_path: Data path IC chain.
277
- clock_path: Clock path IC chain.
278
- clock_period: Clock period in seconds.
279
- destination_ic: Destination IC part number.
280
-
281
- Returns:
282
- SetupHoldAnalysis object.
283
-
284
- Example:
285
- >>> analysis = analyze_setup_hold(
286
- ... data_path=[(\"74LS00\", a, y)],
287
- ... clock_path=[(\"CLK\", clk_in, clk_out)],
288
- ... clock_period=100e-9,
289
- ... destination_ic=\"74LS74\",
290
- ... )
291
- >>> if not analysis.meets_setup:
292
- ... print(f\"Setup violation: {analysis.setup_slack*1e9:.1f}ns\")
293
- """
294
- from oscura.analyzers.digital.ic_database import IC_DATABASE
295
-
296
- # Analyze both paths
297
- data_result = analyze_timing_path(data_path)
298
- clock_result = analyze_timing_path(clock_path)
299
-
300
- # Get destination IC specs
301
- if destination_ic not in IC_DATABASE:
302
- raise ValueError(f"IC '{destination_ic}' not found in database")
303
-
304
- ic_spec = IC_DATABASE[destination_ic]
305
- setup_time = ic_spec.timing.get("t_su", 0.0)
306
- hold_time = ic_spec.timing.get("t_h", 0.0)
307
-
308
- # Calculate setup slack
309
- # Setup: data_delay + setup_time < clock_period + clock_delay
310
- setup_slack = (clock_period + clock_result.total_delay) - (data_result.total_delay + setup_time)
311
- meets_setup = setup_slack >= 0
312
-
313
- # Calculate hold slack
314
- # Hold: data_delay - clock_delay > hold_time
315
- hold_slack = data_result.total_delay - clock_result.total_delay - hold_time
316
- meets_hold = hold_slack >= 0
317
-
318
- return SetupHoldAnalysis(
319
- clock_period=clock_period,
320
- data_path_delay=data_result.total_delay,
321
- clock_path_delay=clock_result.total_delay,
322
- setup_time=setup_time,
323
- hold_time=hold_time,
324
- setup_slack=setup_slack,
325
- hold_slack=hold_slack,
326
- meets_setup=meets_setup,
327
- meets_hold=meets_hold,
328
- )
329
-
330
-
331
- __all__ = [
332
- "ICStage",
333
- "SetupHoldAnalysis",
334
- "TimingPathResult",
335
- "analyze_setup_hold",
336
- "analyze_timing_path",
337
- "calculate_timing_budget",
338
- "find_critical_paths",
339
- ]