wavekit 0.8.2__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.2 → wavekit-0.8.3}/PKG-INFO +1 -1
- {wavekit-0.8.2 → wavekit-0.8.3}/pyproject.toml +1 -1
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/base.py +147 -48
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/clock_domain.py +10 -4
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/hierarchy.py +2 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/LICENSE +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/README.md +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/build_exts.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/__init__.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/expression.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/__init__.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/compiler.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/dsl.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/errors.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/result.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/runtime.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/steps.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/__init__.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fsdb/__init__.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fsdb/reader.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fst/__init__.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fst/reader.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/matcher.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/range.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/value_change.pyx +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/vcd/__init__.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/vcd/reader.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/wellen/__init__.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/wellen/reader.py +0 -0
- {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/waveform.py +0 -0
|
@@ -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
|
|
@@ -389,18 +425,40 @@ class Reader:
|
|
|
389
425
|
end_time: int | None,
|
|
390
426
|
start_cycle: int | None,
|
|
391
427
|
end_cycle: int | None,
|
|
428
|
+
reset: Signal | None = None,
|
|
429
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
392
430
|
) -> Waveform:
|
|
393
431
|
"""Sample *signal* on every *clock* edge and return a raw Waveform.
|
|
394
432
|
|
|
395
433
|
Subclasses provide ``_load_value_changes`` for format-specific I/O.
|
|
396
434
|
The returned Waveform is a simple value array — naming is handled by
|
|
397
|
-
the caller.
|
|
435
|
+
the caller. Samples taken while *reset* is active are dropped.
|
|
398
436
|
"""
|
|
399
437
|
if start_time is not None and start_cycle is not None:
|
|
400
438
|
raise ValueError('start_time and start_cycle are mutually exclusive')
|
|
401
439
|
if end_time is not None and end_cycle is not None:
|
|
402
440
|
raise ValueError('end_time and end_cycle are mutually exclusive')
|
|
403
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
|
+
|
|
404
462
|
# Load clock value changes for absolute cycle computation
|
|
405
463
|
clock_mapping = {'0': 0, '1': 1, 'x': 0, 'z': 0}
|
|
406
464
|
all_clock_changes = self._load_value_changes(clock, clock_mapping)
|
|
@@ -465,7 +523,7 @@ class Reader:
|
|
|
465
523
|
clock_offset=clock_offset,
|
|
466
524
|
)
|
|
467
525
|
|
|
468
|
-
return result
|
|
526
|
+
return result if keep is None else result.mask(keep)
|
|
469
527
|
|
|
470
528
|
def get_signal(
|
|
471
529
|
self,
|
|
@@ -663,6 +721,8 @@ class Reader:
|
|
|
663
721
|
start_cycle: int | None = None,
|
|
664
722
|
end_cycle: int | None = None,
|
|
665
723
|
root_scope: Scope | None = None,
|
|
724
|
+
reset_path: str | None = None,
|
|
725
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
666
726
|
) -> dict[tuple[Capture, ...], Waveform]:
|
|
667
727
|
"""Batch-load all signals matching *signal_path*, each paired with its clock.
|
|
668
728
|
|
|
@@ -680,6 +740,8 @@ class Reader:
|
|
|
680
740
|
longest prefix of the signal key is selected. If no clock key is a
|
|
681
741
|
prefix, raises ``ValueError``.
|
|
682
742
|
|
|
743
|
+
*reset_path* is paired with the matched signals by the same rules.
|
|
744
|
+
|
|
683
745
|
Parameters
|
|
684
746
|
----------
|
|
685
747
|
signal_path:
|
|
@@ -693,8 +755,14 @@ class Reader:
|
|
|
693
755
|
when *clock_path* is omitted (the ambient domain's fields apply
|
|
694
756
|
instead).
|
|
695
757
|
root_scope:
|
|
696
|
-
If provided,
|
|
697
|
-
|
|
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.
|
|
698
766
|
|
|
699
767
|
Returns
|
|
700
768
|
-------
|
|
@@ -710,19 +778,26 @@ class Reader:
|
|
|
710
778
|
of a signal key.
|
|
711
779
|
"""
|
|
712
780
|
self._validate_xz_value(xz_value)
|
|
713
|
-
resolved_clock, sampling = self._resolve_ambient_clock(
|
|
781
|
+
resolved_clock, resolved_reset, sampling = self._resolve_ambient_clock(
|
|
714
782
|
clock_path,
|
|
783
|
+
reset_path,
|
|
715
784
|
sample_on_posedge=sample_on_posedge,
|
|
716
785
|
start_time=start_time,
|
|
717
786
|
end_time=end_time,
|
|
718
787
|
start_cycle=start_cycle,
|
|
719
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 {}
|
|
720
796
|
)
|
|
721
|
-
clock_pairing = self._resolve_clock_pairing(signal_path, resolved_clock, root_scope)
|
|
722
797
|
matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
|
|
723
798
|
load_kwargs: dict[str, Any] = dict(xz_value=xz_value, signed=signed, **sampling)
|
|
724
799
|
return {
|
|
725
|
-
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)
|
|
726
801
|
for k, sig in matched_signals.items()
|
|
727
802
|
}
|
|
728
803
|
|
|
@@ -738,13 +813,16 @@ class Reader:
|
|
|
738
813
|
start_cycle: int | None = None,
|
|
739
814
|
end_cycle: int | None = None,
|
|
740
815
|
root_scope: Scope | None = None,
|
|
816
|
+
reset_path: str | None = None,
|
|
817
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
741
818
|
) -> dict[tuple[Capture, ...], Waveform]:
|
|
742
819
|
"""Batch-load X/Z mask waveforms for all signals matching *signal_path*.
|
|
743
820
|
|
|
744
821
|
Clock assignment follows ``load_matched_waveforms``: an omitted
|
|
745
822
|
*clock_path* uses the ambient domain for every match; otherwise a
|
|
746
823
|
single matched clock is broadcast to all signals, or the
|
|
747
|
-
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.
|
|
748
826
|
|
|
749
827
|
Parameters
|
|
750
828
|
----------
|
|
@@ -761,8 +839,10 @@ class Reader:
|
|
|
761
839
|
Same sampling/windowing semantics as ``load_waveform``. Ignored
|
|
762
840
|
when *clock_path* is omitted.
|
|
763
841
|
root_scope:
|
|
764
|
-
If provided,
|
|
765
|
-
|
|
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``.
|
|
766
846
|
|
|
767
847
|
Returns
|
|
768
848
|
-------
|
|
@@ -774,19 +854,28 @@ class Reader:
|
|
|
774
854
|
RuntimeError:
|
|
775
855
|
If *clock_path* is omitted and no ``ClockDomain`` is active.
|
|
776
856
|
"""
|
|
777
|
-
resolved_clock, sampling = self._resolve_ambient_clock(
|
|
857
|
+
resolved_clock, resolved_reset, sampling = self._resolve_ambient_clock(
|
|
778
858
|
clock_path,
|
|
859
|
+
reset_path,
|
|
779
860
|
sample_on_posedge=sample_on_posedge,
|
|
780
861
|
start_time=start_time,
|
|
781
862
|
end_time=end_time,
|
|
782
863
|
start_cycle=start_cycle,
|
|
783
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 {}
|
|
784
872
|
)
|
|
785
|
-
clock_pairing = self._resolve_clock_pairing(signal_path, resolved_clock, root_scope)
|
|
786
873
|
matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
|
|
787
874
|
mask_kwargs: dict[str, Any] = dict(include_x=include_x, include_z=include_z, **sampling)
|
|
788
875
|
return {
|
|
789
|
-
k: self.load_unknown_mask(
|
|
876
|
+
k: self.load_unknown_mask(
|
|
877
|
+
sig, clock_pairing[k], reset=reset_pairing.get(k), **mask_kwargs
|
|
878
|
+
)
|
|
790
879
|
for k, sig in matched_signals.items()
|
|
791
880
|
}
|
|
792
881
|
|
|
@@ -795,48 +884,51 @@ class Reader:
|
|
|
795
884
|
if xz_value not in (0, 1):
|
|
796
885
|
raise ValueError('xz_value must be 0 or 1')
|
|
797
886
|
|
|
798
|
-
def
|
|
887
|
+
def _resolve_pairing(
|
|
799
888
|
self,
|
|
800
889
|
signal_path: str,
|
|
801
|
-
|
|
890
|
+
source: Signal | str,
|
|
802
891
|
root_scope: Scope | None,
|
|
892
|
+
kind: str = 'clock',
|
|
803
893
|
) -> dict[tuple[Capture, ...], Signal]:
|
|
804
|
-
"""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.
|
|
805
897
|
|
|
806
898
|
Rules:
|
|
807
|
-
- A concrete
|
|
808
|
-
- Single
|
|
809
|
-
- 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.
|
|
810
902
|
- No prefix match for a signal: raise ValueError.
|
|
811
903
|
"""
|
|
812
|
-
if isinstance(
|
|
904
|
+
if isinstance(source, Signal):
|
|
813
905
|
matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
|
|
814
|
-
return {k:
|
|
815
|
-
|
|
816
|
-
if not
|
|
817
|
-
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')
|
|
818
910
|
|
|
819
911
|
matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
|
|
820
912
|
|
|
821
|
-
if len(
|
|
822
|
-
|
|
823
|
-
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}
|
|
824
916
|
|
|
825
|
-
|
|
917
|
+
source_keys = list(matched_sources.keys())
|
|
826
918
|
pairing: dict[tuple[Capture, ...], Signal] = {}
|
|
827
919
|
for sig_key in matched_signals:
|
|
828
920
|
best_len = -1
|
|
829
|
-
|
|
830
|
-
for
|
|
831
|
-
if sig_key[: len(
|
|
832
|
-
best_len = len(
|
|
833
|
-
|
|
834
|
-
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:
|
|
835
927
|
raise ValueError(
|
|
836
|
-
f'no
|
|
837
|
-
f'available
|
|
928
|
+
f'no {kind} key is a prefix of signal key {sig_key!r}; '
|
|
929
|
+
f'available {kind} keys: {source_keys!r}'
|
|
838
930
|
)
|
|
839
|
-
pairing[sig_key] =
|
|
931
|
+
pairing[sig_key] = matched_sources[best_source_key]
|
|
840
932
|
return pairing
|
|
841
933
|
|
|
842
934
|
@abstractmethod
|
|
@@ -866,6 +958,8 @@ class Reader:
|
|
|
866
958
|
end_cycle: int | None = None,
|
|
867
959
|
mode: Literal['single', 'zip'] = 'single',
|
|
868
960
|
root_scope: Scope | None = None,
|
|
961
|
+
reset: str | None = None,
|
|
962
|
+
reset_active_level: Literal['high', 'low'] | None = None,
|
|
869
963
|
) -> Waveform | dict[tuple[Capture, ...], Waveform]:
|
|
870
964
|
"""Evaluate a waveform expression containing physical signal paths.
|
|
871
965
|
|
|
@@ -885,6 +979,9 @@ class Reader:
|
|
|
885
979
|
paths.
|
|
886
980
|
root_scope:
|
|
887
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.
|
|
888
985
|
|
|
889
986
|
Returns
|
|
890
987
|
-------
|
|
@@ -902,6 +999,8 @@ class Reader:
|
|
|
902
999
|
start_cycle=start_cycle,
|
|
903
1000
|
end_cycle=end_cycle,
|
|
904
1001
|
root_scope=root_scope,
|
|
1002
|
+
reset_path=reset,
|
|
1003
|
+
reset_active_level=reset_active_level,
|
|
905
1004
|
)
|
|
906
1005
|
|
|
907
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
|
|
File without changes
|