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.
- oscura/__init__.py +1 -1
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/dtc/data.json +2763 -0
- oscura/export/__init__.py +0 -12
- oscura/export/wireshark/README.md +15 -15
- oscura/exporters/json_export.py +0 -47
- oscura/inference/active_learning/README.md +7 -7
- oscura/pipeline/composition.py +10 -2
- oscura/reporting/__init__.py +0 -7
- oscura/reporting/templates/index.md +13 -13
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/utils/autodetect.py +1 -5
- oscura-0.5.1.dist-info/METADATA +583 -0
- {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/RECORD +23 -28
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura-0.5.0.dist-info/METADATA +0 -407
- {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.5.1.dist-info}/entry_points.txt +0 -0
- {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
|
-
]
|