wavekit 0.8.1__tar.gz → 0.8.3__tar.gz
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.
- {wavekit-0.8.1 → wavekit-0.8.3}/PKG-INFO +4 -3
- {wavekit-0.8.1 → wavekit-0.8.3}/README.md +2 -2
- {wavekit-0.8.1 → wavekit-0.8.3}/pyproject.toml +1 -1
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/base.py +149 -55
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/clock_domain.py +10 -4
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/hierarchy.py +2 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/LICENSE +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/build_exts.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/__init__.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/expression.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/pattern/__init__.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/pattern/compiler.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/pattern/dsl.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/pattern/errors.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/pattern/result.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/pattern/runtime.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/pattern/steps.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/__init__.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/fsdb/__init__.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/fsdb/reader.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/fst/__init__.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/fst/reader.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/matcher.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/range.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/value_change.pyx +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/vcd/__init__.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/vcd/reader.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/wellen/__init__.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/readers/wellen/reader.py +0 -0
- {wavekit-0.8.1 → wavekit-0.8.3}/src/wavekit/waveform.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavekit
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.3
|
|
4
4
|
Summary: High-level digital waveform analysis in Python
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: cxzzzz
|
|
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
17
|
Requires-Dist: lark (>=1.2,<2)
|
|
17
18
|
Requires-Dist: numpy (>=2.0.0,<3.0.0)
|
|
18
19
|
Requires-Dist: wavekit-pywellen (>=0.25.6)
|
|
@@ -35,8 +36,8 @@ It loads signals from waveform files as clock-aligned `Waveform` objects,
|
|
|
35
36
|
allowing users to query and operate on them at the cycle and transaction levels,
|
|
36
37
|
making complex hardware behavior easier to analyze.
|
|
37
38
|
|
|
38
|
-
> **
|
|
39
|
-
>
|
|
39
|
+
> **Quick links:** [Online playground](https://cxzzzz.github.io/wavekit-playground/) ·
|
|
40
|
+
> [AI integration](https://github.com/cxzzzz/wavekit-mcp)
|
|
40
41
|
|
|
41
42
|
## Features
|
|
42
43
|
|
|
@@ -13,8 +13,8 @@ It loads signals from waveform files as clock-aligned `Waveform` objects,
|
|
|
13
13
|
allowing users to query and operate on them at the cycle and transaction levels,
|
|
14
14
|
making complex hardware behavior easier to analyze.
|
|
15
15
|
|
|
16
|
-
> **
|
|
17
|
-
>
|
|
16
|
+
> **Quick links:** [Online playground](https://cxzzzz.github.io/wavekit-playground/) ·
|
|
17
|
+
> [AI integration](https://github.com/cxzzzz/wavekit-mcp)
|
|
18
18
|
|
|
19
19
|
## Features
|
|
20
20
|
|
|
@@ -105,6 +105,8 @@ class Reader:
|
|
|
105
105
|
end_time: int | None = None,
|
|
106
106
|
start_cycle: int | None = None,
|
|
107
107
|
end_cycle: int | None = None,
|
|
108
|
+
reset: Signal | str | None = None,
|
|
109
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
108
110
|
) -> ClockDomain:
|
|
109
111
|
"""Build a reusable sampling recipe: a clock plus sampling parameters.
|
|
110
112
|
|
|
@@ -118,6 +120,8 @@ class Reader:
|
|
|
118
120
|
Clock signal as a ``Signal`` or full dotted path string.
|
|
119
121
|
sample_on_posedge, start_time, end_time, start_cycle, end_cycle:
|
|
120
122
|
Same sampling/windowing semantics as ``load_waveform``.
|
|
123
|
+
reset, reset_active_level:
|
|
124
|
+
Same reset semantics as ``load_waveform``.
|
|
121
125
|
|
|
122
126
|
Returns
|
|
123
127
|
-------
|
|
@@ -125,6 +129,9 @@ class Reader:
|
|
|
125
129
|
The constructed sampling recipe.
|
|
126
130
|
"""
|
|
127
131
|
resolved_clock = clock if isinstance(clock, Signal) else self.get_signal(clock)
|
|
132
|
+
resolved_reset = (
|
|
133
|
+
reset if reset is None or isinstance(reset, Signal) else self.get_signal(reset)
|
|
134
|
+
)
|
|
128
135
|
return ClockDomain(
|
|
129
136
|
clock=resolved_clock,
|
|
130
137
|
sample_on_posedge=sample_on_posedge,
|
|
@@ -132,24 +139,27 @@ class Reader:
|
|
|
132
139
|
end_time=end_time,
|
|
133
140
|
start_cycle=start_cycle,
|
|
134
141
|
end_cycle=end_cycle,
|
|
142
|
+
reset=resolved_reset,
|
|
143
|
+
reset_active_level=reset_active_level,
|
|
135
144
|
)
|
|
136
145
|
|
|
137
146
|
@staticmethod
|
|
138
147
|
def _resolve_ambient_clock(
|
|
139
148
|
clock: Signal | str | None,
|
|
149
|
+
reset: Signal | str | None,
|
|
140
150
|
**call_sampling: Any,
|
|
141
|
-
) -> tuple[Signal | str, dict[str, Any]]:
|
|
142
|
-
"""Resolve *clock*, falling back to the ambient domain when ``None``.
|
|
151
|
+
) -> tuple[Signal | str, Signal | str | None, dict[str, Any]]:
|
|
152
|
+
"""Resolve *clock* and *reset*, falling back to the ambient domain when *clock* is ``None``.
|
|
143
153
|
|
|
144
|
-
Explicit *clock* wins outright and keeps *call_sampling* as
|
|
145
|
-
omitted *clock* reads the ambient ``ClockDomain`` for
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
Explicit *clock* wins outright and keeps *reset* and *call_sampling* as
|
|
155
|
+
given; omitted *clock* reads the ambient ``ClockDomain`` for the clock,
|
|
156
|
+
the reset and every sampling field, raising ``RuntimeError`` if none is
|
|
157
|
+
active.
|
|
148
158
|
"""
|
|
149
159
|
if clock is not None:
|
|
150
|
-
return clock, call_sampling
|
|
160
|
+
return clock, reset, call_sampling
|
|
151
161
|
domain = ClockDomain.current()
|
|
152
|
-
return domain.clock, domain.sampling_kwargs()
|
|
162
|
+
return domain.clock, domain.reset, domain.sampling_kwargs()
|
|
153
163
|
|
|
154
164
|
def load_waveform(
|
|
155
165
|
self,
|
|
@@ -162,6 +172,8 @@ class Reader:
|
|
|
162
172
|
end_time: int | None = None,
|
|
163
173
|
start_cycle: int | None = None,
|
|
164
174
|
end_cycle: int | None = None,
|
|
175
|
+
reset: Signal | str | None = None,
|
|
176
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
165
177
|
) -> Waveform:
|
|
166
178
|
"""Load a single signal as a clock-synchronised ``Waveform``.
|
|
167
179
|
|
|
@@ -211,6 +223,15 @@ class Reader:
|
|
|
211
223
|
Absolute clock cycle number to stop loading at (exclusive).
|
|
212
224
|
``None`` means end of simulation. Mutually exclusive with
|
|
213
225
|
*end_time*. Ignored when *clock* is omitted.
|
|
226
|
+
reset:
|
|
227
|
+
Optional reset signal as a ``Signal`` or full dotted path string.
|
|
228
|
+
Samples taken while reset is active are dropped; ``.cycle`` keeps
|
|
229
|
+
absolute cycle numbers. X/Z on the reset signal counts as active.
|
|
230
|
+
Ignored when *clock* is omitted.
|
|
231
|
+
reset_active_level:
|
|
232
|
+
``'high'`` if reset is active while the signal is 1, ``'low'`` if
|
|
233
|
+
active while it is 0. Required when *reset* is given. Ignored
|
|
234
|
+
when *clock* is omitted.
|
|
214
235
|
|
|
215
236
|
Returns
|
|
216
237
|
-------
|
|
@@ -226,26 +247,32 @@ class Reader:
|
|
|
226
247
|
If *clock* is omitted and no ``ClockDomain`` is active.
|
|
227
248
|
ValueError:
|
|
228
249
|
If both *start_time* and *start_cycle* (or both *end_time* and
|
|
229
|
-
*end_cycle*) are provided simultaneously
|
|
250
|
+
*end_cycle*) are provided simultaneously, or if *reset* is given
|
|
251
|
+
without a valid *reset_active_level*.
|
|
230
252
|
"""
|
|
231
253
|
self._validate_xz_value(xz_value)
|
|
232
254
|
resolved_signal = signal if isinstance(signal, Signal) else self.get_signal(signal)
|
|
233
|
-
resolved_clock, sampling = self._resolve_ambient_clock(
|
|
255
|
+
resolved_clock, resolved_reset, sampling = self._resolve_ambient_clock(
|
|
234
256
|
clock,
|
|
257
|
+
reset,
|
|
235
258
|
sample_on_posedge=sample_on_posedge,
|
|
236
259
|
start_time=start_time,
|
|
237
260
|
end_time=end_time,
|
|
238
261
|
start_cycle=start_cycle,
|
|
239
262
|
end_cycle=end_cycle,
|
|
263
|
+
reset_active_level=reset_active_level,
|
|
240
264
|
)
|
|
241
265
|
if not isinstance(resolved_clock, Signal):
|
|
242
266
|
resolved_clock = self.get_signal(resolved_clock)
|
|
267
|
+
if resolved_reset is not None and not isinstance(resolved_reset, Signal):
|
|
268
|
+
resolved_reset = self.get_signal(resolved_reset)
|
|
243
269
|
value_mapping = {'0': 0, '1': 1, 'x': xz_value, 'z': xz_value}
|
|
244
270
|
wf = self._sample_on_clock(
|
|
245
271
|
resolved_signal,
|
|
246
272
|
resolved_clock,
|
|
247
273
|
value_mapping=value_mapping,
|
|
248
274
|
signed=signed,
|
|
275
|
+
reset=resolved_reset,
|
|
249
276
|
**sampling,
|
|
250
277
|
)
|
|
251
278
|
wf.signal = resolved_signal
|
|
@@ -264,6 +291,8 @@ class Reader:
|
|
|
264
291
|
end_time: int | None = None,
|
|
265
292
|
start_cycle: int | None = None,
|
|
266
293
|
end_cycle: int | None = None,
|
|
294
|
+
reset: Signal | str | None = None,
|
|
295
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
267
296
|
) -> Waveform:
|
|
268
297
|
"""Load source X/Z presence as an unsigned bitmask waveform.
|
|
269
298
|
|
|
@@ -286,6 +315,8 @@ class Reader:
|
|
|
286
315
|
If ``True`` (default), mark source ``Z``/``z`` bits.
|
|
287
316
|
sample_on_posedge, start_time, end_time, start_cycle, end_cycle:
|
|
288
317
|
Same sampling/windowing semantics as ``load_waveform``.
|
|
318
|
+
reset, reset_active_level:
|
|
319
|
+
Same reset semantics as ``load_waveform``.
|
|
289
320
|
|
|
290
321
|
Returns
|
|
291
322
|
-------
|
|
@@ -298,16 +329,20 @@ class Reader:
|
|
|
298
329
|
If *clock* is omitted and no ``ClockDomain`` is active.
|
|
299
330
|
"""
|
|
300
331
|
resolved_signal = signal if isinstance(signal, Signal) else self.get_signal(signal)
|
|
301
|
-
resolved_clock, sampling = self._resolve_ambient_clock(
|
|
332
|
+
resolved_clock, resolved_reset, sampling = self._resolve_ambient_clock(
|
|
302
333
|
clock,
|
|
334
|
+
reset,
|
|
303
335
|
sample_on_posedge=sample_on_posedge,
|
|
304
336
|
start_time=start_time,
|
|
305
337
|
end_time=end_time,
|
|
306
338
|
start_cycle=start_cycle,
|
|
307
339
|
end_cycle=end_cycle,
|
|
340
|
+
reset_active_level=reset_active_level,
|
|
308
341
|
)
|
|
309
342
|
if not isinstance(resolved_clock, Signal):
|
|
310
343
|
resolved_clock = self.get_signal(resolved_clock)
|
|
344
|
+
if resolved_reset is not None and not isinstance(resolved_reset, Signal):
|
|
345
|
+
resolved_reset = self.get_signal(resolved_reset)
|
|
311
346
|
|
|
312
347
|
value_mapping = {
|
|
313
348
|
'0': 0,
|
|
@@ -320,6 +355,7 @@ class Reader:
|
|
|
320
355
|
resolved_clock,
|
|
321
356
|
value_mapping=value_mapping,
|
|
322
357
|
signed=False,
|
|
358
|
+
reset=resolved_reset,
|
|
323
359
|
**sampling,
|
|
324
360
|
)
|
|
325
361
|
wf.signal = resolved_signal
|
|
@@ -348,13 +384,8 @@ class Reader:
|
|
|
348
384
|
clock_offset=clock_offset,
|
|
349
385
|
)
|
|
350
386
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
cycle=clock,
|
|
354
|
-
time=time,
|
|
355
|
-
width=width,
|
|
356
|
-
signed=signed,
|
|
357
|
-
)
|
|
387
|
+
wf = Waveform(value=value, cycle=clock, time=time, width=width)
|
|
388
|
+
return wf.as_signed() if signed else wf
|
|
358
389
|
|
|
359
390
|
@abstractmethod
|
|
360
391
|
def _load_value_changes(
|
|
@@ -394,18 +425,40 @@ class Reader:
|
|
|
394
425
|
end_time: int | None,
|
|
395
426
|
start_cycle: int | None,
|
|
396
427
|
end_cycle: int | None,
|
|
428
|
+
reset: Signal | None = None,
|
|
429
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
397
430
|
) -> Waveform:
|
|
398
431
|
"""Sample *signal* on every *clock* edge and return a raw Waveform.
|
|
399
432
|
|
|
400
433
|
Subclasses provide ``_load_value_changes`` for format-specific I/O.
|
|
401
434
|
The returned Waveform is a simple value array — naming is handled by
|
|
402
|
-
the caller.
|
|
435
|
+
the caller. Samples taken while *reset* is active are dropped.
|
|
403
436
|
"""
|
|
404
437
|
if start_time is not None and start_cycle is not None:
|
|
405
438
|
raise ValueError('start_time and start_cycle are mutually exclusive')
|
|
406
439
|
if end_time is not None and end_cycle is not None:
|
|
407
440
|
raise ValueError('end_time and end_cycle are mutually exclusive')
|
|
408
441
|
|
|
442
|
+
keep = None
|
|
443
|
+
if reset is not None:
|
|
444
|
+
if reset_active_level not in ('high', 'low'):
|
|
445
|
+
raise ValueError(
|
|
446
|
+
f"reset_active_level must be 'high' or 'low', got {reset_active_level!r}"
|
|
447
|
+
)
|
|
448
|
+
active = int(reset_active_level == 'high')
|
|
449
|
+
reset_wf = self._sample_on_clock(
|
|
450
|
+
reset,
|
|
451
|
+
clock,
|
|
452
|
+
{'0': 0, '1': 1, 'x': active, 'z': active},
|
|
453
|
+
False,
|
|
454
|
+
sample_on_posedge,
|
|
455
|
+
start_time,
|
|
456
|
+
end_time,
|
|
457
|
+
start_cycle,
|
|
458
|
+
end_cycle,
|
|
459
|
+
)
|
|
460
|
+
keep = reset_wf.value != active
|
|
461
|
+
|
|
409
462
|
# Load clock value changes for absolute cycle computation
|
|
410
463
|
clock_mapping = {'0': 0, '1': 1, 'x': 0, 'z': 0}
|
|
411
464
|
all_clock_changes = self._load_value_changes(clock, clock_mapping)
|
|
@@ -470,7 +523,7 @@ class Reader:
|
|
|
470
523
|
clock_offset=clock_offset,
|
|
471
524
|
)
|
|
472
525
|
|
|
473
|
-
return result
|
|
526
|
+
return result if keep is None else result.mask(keep)
|
|
474
527
|
|
|
475
528
|
def get_signal(
|
|
476
529
|
self,
|
|
@@ -668,6 +721,8 @@ class Reader:
|
|
|
668
721
|
start_cycle: int | None = None,
|
|
669
722
|
end_cycle: int | None = None,
|
|
670
723
|
root_scope: Scope | None = None,
|
|
724
|
+
reset_path: str | None = None,
|
|
725
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
671
726
|
) -> dict[tuple[Capture, ...], Waveform]:
|
|
672
727
|
"""Batch-load all signals matching *signal_path*, each paired with its clock.
|
|
673
728
|
|
|
@@ -685,6 +740,8 @@ class Reader:
|
|
|
685
740
|
longest prefix of the signal key is selected. If no clock key is a
|
|
686
741
|
prefix, raises ``ValueError``.
|
|
687
742
|
|
|
743
|
+
*reset_path* is paired with the matched signals by the same rules.
|
|
744
|
+
|
|
688
745
|
Parameters
|
|
689
746
|
----------
|
|
690
747
|
signal_path:
|
|
@@ -698,8 +755,14 @@ class Reader:
|
|
|
698
755
|
when *clock_path* is omitted (the ambient domain's fields apply
|
|
699
756
|
instead).
|
|
700
757
|
root_scope:
|
|
701
|
-
If provided,
|
|
702
|
-
|
|
758
|
+
If provided, all paths are searched within this scope instead of
|
|
759
|
+
the file's top-level scopes.
|
|
760
|
+
reset_path:
|
|
761
|
+
Reset signal query path, paired with the matched signals like
|
|
762
|
+
*clock_path*. Ignored when *clock_path* is omitted.
|
|
763
|
+
reset_active_level:
|
|
764
|
+
``'high'`` or ``'low'``, shared by every matched reset. Required
|
|
765
|
+
when *reset_path* is given.
|
|
703
766
|
|
|
704
767
|
Returns
|
|
705
768
|
-------
|
|
@@ -715,19 +778,26 @@ class Reader:
|
|
|
715
778
|
of a signal key.
|
|
716
779
|
"""
|
|
717
780
|
self._validate_xz_value(xz_value)
|
|
718
|
-
resolved_clock, sampling = self._resolve_ambient_clock(
|
|
781
|
+
resolved_clock, resolved_reset, sampling = self._resolve_ambient_clock(
|
|
719
782
|
clock_path,
|
|
783
|
+
reset_path,
|
|
720
784
|
sample_on_posedge=sample_on_posedge,
|
|
721
785
|
start_time=start_time,
|
|
722
786
|
end_time=end_time,
|
|
723
787
|
start_cycle=start_cycle,
|
|
724
788
|
end_cycle=end_cycle,
|
|
789
|
+
reset_active_level=reset_active_level,
|
|
790
|
+
)
|
|
791
|
+
clock_pairing = self._resolve_pairing(signal_path, resolved_clock, root_scope)
|
|
792
|
+
reset_pairing = (
|
|
793
|
+
self._resolve_pairing(signal_path, resolved_reset, root_scope, 'reset')
|
|
794
|
+
if resolved_reset is not None
|
|
795
|
+
else {}
|
|
725
796
|
)
|
|
726
|
-
clock_pairing = self._resolve_clock_pairing(signal_path, resolved_clock, root_scope)
|
|
727
797
|
matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
|
|
728
798
|
load_kwargs: dict[str, Any] = dict(xz_value=xz_value, signed=signed, **sampling)
|
|
729
799
|
return {
|
|
730
|
-
k: self.load_waveform(sig, clock_pairing[k], **load_kwargs)
|
|
800
|
+
k: self.load_waveform(sig, clock_pairing[k], reset=reset_pairing.get(k), **load_kwargs)
|
|
731
801
|
for k, sig in matched_signals.items()
|
|
732
802
|
}
|
|
733
803
|
|
|
@@ -743,13 +813,16 @@ class Reader:
|
|
|
743
813
|
start_cycle: int | None = None,
|
|
744
814
|
end_cycle: int | None = None,
|
|
745
815
|
root_scope: Scope | None = None,
|
|
816
|
+
reset_path: str | None = None,
|
|
817
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
746
818
|
) -> dict[tuple[Capture, ...], Waveform]:
|
|
747
819
|
"""Batch-load X/Z mask waveforms for all signals matching *signal_path*.
|
|
748
820
|
|
|
749
821
|
Clock assignment follows ``load_matched_waveforms``: an omitted
|
|
750
822
|
*clock_path* uses the ambient domain for every match; otherwise a
|
|
751
823
|
single matched clock is broadcast to all signals, or the
|
|
752
|
-
longest-prefix clock key is selected for each signal key.
|
|
824
|
+
longest-prefix clock key is selected for each signal key. *reset_path*
|
|
825
|
+
is paired the same way.
|
|
753
826
|
|
|
754
827
|
Parameters
|
|
755
828
|
----------
|
|
@@ -766,8 +839,10 @@ class Reader:
|
|
|
766
839
|
Same sampling/windowing semantics as ``load_waveform``. Ignored
|
|
767
840
|
when *clock_path* is omitted.
|
|
768
841
|
root_scope:
|
|
769
|
-
If provided,
|
|
770
|
-
|
|
842
|
+
If provided, all paths are searched within this scope instead of
|
|
843
|
+
the file's top-level scopes.
|
|
844
|
+
reset_path, reset_active_level:
|
|
845
|
+
Same reset semantics as ``load_matched_waveforms``.
|
|
771
846
|
|
|
772
847
|
Returns
|
|
773
848
|
-------
|
|
@@ -779,19 +854,28 @@ class Reader:
|
|
|
779
854
|
RuntimeError:
|
|
780
855
|
If *clock_path* is omitted and no ``ClockDomain`` is active.
|
|
781
856
|
"""
|
|
782
|
-
resolved_clock, sampling = self._resolve_ambient_clock(
|
|
857
|
+
resolved_clock, resolved_reset, sampling = self._resolve_ambient_clock(
|
|
783
858
|
clock_path,
|
|
859
|
+
reset_path,
|
|
784
860
|
sample_on_posedge=sample_on_posedge,
|
|
785
861
|
start_time=start_time,
|
|
786
862
|
end_time=end_time,
|
|
787
863
|
start_cycle=start_cycle,
|
|
788
864
|
end_cycle=end_cycle,
|
|
865
|
+
reset_active_level=reset_active_level,
|
|
866
|
+
)
|
|
867
|
+
clock_pairing = self._resolve_pairing(signal_path, resolved_clock, root_scope)
|
|
868
|
+
reset_pairing = (
|
|
869
|
+
self._resolve_pairing(signal_path, resolved_reset, root_scope, 'reset')
|
|
870
|
+
if resolved_reset is not None
|
|
871
|
+
else {}
|
|
789
872
|
)
|
|
790
|
-
clock_pairing = self._resolve_clock_pairing(signal_path, resolved_clock, root_scope)
|
|
791
873
|
matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
|
|
792
874
|
mask_kwargs: dict[str, Any] = dict(include_x=include_x, include_z=include_z, **sampling)
|
|
793
875
|
return {
|
|
794
|
-
k: self.load_unknown_mask(
|
|
876
|
+
k: self.load_unknown_mask(
|
|
877
|
+
sig, clock_pairing[k], reset=reset_pairing.get(k), **mask_kwargs
|
|
878
|
+
)
|
|
795
879
|
for k, sig in matched_signals.items()
|
|
796
880
|
}
|
|
797
881
|
|
|
@@ -800,48 +884,51 @@ class Reader:
|
|
|
800
884
|
if xz_value not in (0, 1):
|
|
801
885
|
raise ValueError('xz_value must be 0 or 1')
|
|
802
886
|
|
|
803
|
-
def
|
|
887
|
+
def _resolve_pairing(
|
|
804
888
|
self,
|
|
805
889
|
signal_path: str,
|
|
806
|
-
|
|
890
|
+
source: Signal | str,
|
|
807
891
|
root_scope: Scope | None,
|
|
892
|
+
kind: str = 'clock',
|
|
808
893
|
) -> dict[tuple[Capture, ...], Signal]:
|
|
809
|
-
"""Resolve signal
|
|
894
|
+
"""Resolve signal/*source* query paths into a {signal_key: source_signal} map.
|
|
895
|
+
|
|
896
|
+
*source* is the clock or reset query and *kind* names it in error messages.
|
|
810
897
|
|
|
811
898
|
Rules:
|
|
812
|
-
- A concrete
|
|
813
|
-
- Single
|
|
814
|
-
- Multiple
|
|
899
|
+
- A concrete ``Signal``: broadcast to all signals.
|
|
900
|
+
- Single match: broadcast to all signals.
|
|
901
|
+
- Multiple matches: longest-prefix key per signal key.
|
|
815
902
|
- No prefix match for a signal: raise ValueError.
|
|
816
903
|
"""
|
|
817
|
-
if isinstance(
|
|
904
|
+
if isinstance(source, Signal):
|
|
818
905
|
matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
|
|
819
|
-
return {k:
|
|
820
|
-
|
|
821
|
-
if not
|
|
822
|
-
raise ValueError(f'
|
|
906
|
+
return {k: source for k in matched_signals}
|
|
907
|
+
matched_sources = self.get_matched_signals(source, root_scope=root_scope)
|
|
908
|
+
if not matched_sources:
|
|
909
|
+
raise ValueError(f'{kind} path {source!r} matched no signals')
|
|
823
910
|
|
|
824
911
|
matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
|
|
825
912
|
|
|
826
|
-
if len(
|
|
827
|
-
|
|
828
|
-
return {k:
|
|
913
|
+
if len(matched_sources) == 1:
|
|
914
|
+
source_signal = next(iter(matched_sources.values()))
|
|
915
|
+
return {k: source_signal for k in matched_signals}
|
|
829
916
|
|
|
830
|
-
|
|
917
|
+
source_keys = list(matched_sources.keys())
|
|
831
918
|
pairing: dict[tuple[Capture, ...], Signal] = {}
|
|
832
919
|
for sig_key in matched_signals:
|
|
833
920
|
best_len = -1
|
|
834
|
-
|
|
835
|
-
for
|
|
836
|
-
if sig_key[: len(
|
|
837
|
-
best_len = len(
|
|
838
|
-
|
|
839
|
-
if
|
|
921
|
+
best_source_key: tuple[Capture, ...] | None = None
|
|
922
|
+
for sk in source_keys:
|
|
923
|
+
if sig_key[: len(sk)] == sk and len(sk) > best_len:
|
|
924
|
+
best_len = len(sk)
|
|
925
|
+
best_source_key = sk
|
|
926
|
+
if best_source_key is None:
|
|
840
927
|
raise ValueError(
|
|
841
|
-
f'no
|
|
842
|
-
f'available
|
|
928
|
+
f'no {kind} key is a prefix of signal key {sig_key!r}; '
|
|
929
|
+
f'available {kind} keys: {source_keys!r}'
|
|
843
930
|
)
|
|
844
|
-
pairing[sig_key] =
|
|
931
|
+
pairing[sig_key] = matched_sources[best_source_key]
|
|
845
932
|
return pairing
|
|
846
933
|
|
|
847
934
|
@abstractmethod
|
|
@@ -871,6 +958,8 @@ class Reader:
|
|
|
871
958
|
end_cycle: int | None = None,
|
|
872
959
|
mode: Literal['single', 'zip'] = 'single',
|
|
873
960
|
root_scope: Scope | None = None,
|
|
961
|
+
reset: str | None = None,
|
|
962
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
874
963
|
) -> Waveform | dict[tuple[Capture, ...], Waveform]:
|
|
875
964
|
"""Evaluate a waveform expression containing physical signal paths.
|
|
876
965
|
|
|
@@ -890,6 +979,9 @@ class Reader:
|
|
|
890
979
|
paths.
|
|
891
980
|
root_scope:
|
|
892
981
|
If provided, resolve paths within this scope.
|
|
982
|
+
reset, reset_active_level:
|
|
983
|
+
Forwarded to ``load_matched_waveforms`` as ``reset_path`` and
|
|
984
|
+
``reset_active_level`` for every path.
|
|
893
985
|
|
|
894
986
|
Returns
|
|
895
987
|
-------
|
|
@@ -907,6 +999,8 @@ class Reader:
|
|
|
907
999
|
start_cycle=start_cycle,
|
|
908
1000
|
end_cycle=end_cycle,
|
|
909
1001
|
root_scope=root_scope,
|
|
1002
|
+
reset_path=reset,
|
|
1003
|
+
reset_active_level=reset_active_level,
|
|
910
1004
|
)
|
|
911
1005
|
|
|
912
1006
|
loaded_per_path: list[tuple[str, str, dict[tuple[Capture, ...], Waveform]]] = []
|
|
@@ -4,14 +4,14 @@ import contextvars
|
|
|
4
4
|
import dataclasses
|
|
5
5
|
from contextlib import ContextDecorator
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import Any, ClassVar
|
|
7
|
+
from typing import Any, ClassVar, Literal
|
|
8
8
|
|
|
9
9
|
from .hierarchy import Signal
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@dataclass(eq=False)
|
|
13
13
|
class ClockDomain(ContextDecorator):
|
|
14
|
-
"""A clock signal paired with its sampling edge
|
|
14
|
+
"""A clock signal paired with its sampling edge, time/cycle window and reset."""
|
|
15
15
|
|
|
16
16
|
clock: Signal
|
|
17
17
|
sample_on_posedge: bool = False
|
|
@@ -19,6 +19,8 @@ class ClockDomain(ContextDecorator):
|
|
|
19
19
|
end_time: int | None = None
|
|
20
20
|
start_cycle: int | None = None
|
|
21
21
|
end_cycle: int | None = None
|
|
22
|
+
reset: Signal | None = None
|
|
23
|
+
reset_active_level: Literal['high', 'low'] | None = None
|
|
22
24
|
_tokens: list[contextvars.Token] = field(
|
|
23
25
|
default_factory=list, init=False, repr=False, compare=False
|
|
24
26
|
)
|
|
@@ -42,11 +44,15 @@ class ClockDomain(ContextDecorator):
|
|
|
42
44
|
return domain
|
|
43
45
|
|
|
44
46
|
def sampling_kwargs(self) -> dict[str, Any]:
|
|
45
|
-
"""Return this domain's sampling fields as ``load_waveform``-style kwargs.
|
|
47
|
+
"""Return this domain's sampling fields as ``load_waveform``-style kwargs.
|
|
48
|
+
|
|
49
|
+
The ``clock`` and ``reset`` signals are excluded because loaders resolve
|
|
50
|
+
and pair them separately.
|
|
51
|
+
"""
|
|
46
52
|
return {
|
|
47
53
|
f.name: getattr(self, f.name)
|
|
48
54
|
for f in dataclasses.fields(self)
|
|
49
|
-
if f.name not in ('clock', '_tokens')
|
|
55
|
+
if f.name not in ('clock', 'reset', '_tokens')
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
def __enter__(self) -> ClockDomain:
|
|
@@ -405,6 +405,7 @@ class Signal(Node):
|
|
|
405
405
|
return self.reader.load_waveform(
|
|
406
406
|
self,
|
|
407
407
|
clock=domain.clock,
|
|
408
|
+
reset=domain.reset,
|
|
408
409
|
xz_value=xz_value,
|
|
409
410
|
signed=signed,
|
|
410
411
|
**domain.sampling_kwargs(),
|
|
@@ -430,6 +431,7 @@ class Signal(Node):
|
|
|
430
431
|
return self.reader.load_unknown_mask(
|
|
431
432
|
self,
|
|
432
433
|
clock=domain.clock,
|
|
434
|
+
reset=domain.reset,
|
|
433
435
|
include_x=include_x,
|
|
434
436
|
include_z=include_z,
|
|
435
437
|
**domain.sampling_kwargs(),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|