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.
Files changed (32) hide show
  1. {wavekit-0.8.2 → wavekit-0.8.3}/PKG-INFO +1 -1
  2. {wavekit-0.8.2 → wavekit-0.8.3}/pyproject.toml +1 -1
  3. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/base.py +147 -48
  4. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/clock_domain.py +10 -4
  5. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/hierarchy.py +2 -0
  6. {wavekit-0.8.2 → wavekit-0.8.3}/LICENSE +0 -0
  7. {wavekit-0.8.2 → wavekit-0.8.3}/README.md +0 -0
  8. {wavekit-0.8.2 → wavekit-0.8.3}/build_exts.py +0 -0
  9. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/__init__.py +0 -0
  10. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/expression.py +0 -0
  11. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/__init__.py +0 -0
  12. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/compiler.py +0 -0
  13. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/dsl.py +0 -0
  14. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/errors.py +0 -0
  15. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/result.py +0 -0
  16. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/runtime.py +0 -0
  17. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/pattern/steps.py +0 -0
  18. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/__init__.py +0 -0
  19. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fsdb/__init__.py +0 -0
  20. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
  21. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
  22. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fsdb/reader.py +0 -0
  23. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fst/__init__.py +0 -0
  24. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/fst/reader.py +0 -0
  25. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/matcher.py +0 -0
  26. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/range.py +0 -0
  27. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/value_change.pyx +0 -0
  28. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/vcd/__init__.py +0 -0
  29. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/vcd/reader.py +0 -0
  30. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/wellen/__init__.py +0 -0
  31. {wavekit-0.8.2 → wavekit-0.8.3}/src/wavekit/readers/wellen/reader.py +0 -0
  32. {wavekit-0.8.2 → 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.2
3
+ Version: 0.8.3
4
4
  Summary: High-level digital waveform analysis in Python
5
5
  License-File: LICENSE
6
6
  Author: cxzzzz
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "wavekit"
3
- version = "0.8.2"
3
+ version = "0.8.3"
4
4
  description = "High-level digital waveform analysis in Python"
5
5
  authors = ["cxzzzz <cxz19961010@outlook.com>"]
6
6
  readme = "README.md"
@@ -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 given;
145
- omitted *clock* reads the ambient ``ClockDomain`` for both the
146
- clock and every sampling field, raising ``RuntimeError`` if none
147
- is active.
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, both *signal_path* and *clock_path* are searched within
697
- this scope instead of the file's top-level scopes.
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, both *signal_path* and *clock_path* are searched within
765
- this scope instead of the file's top-level scopes.
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(sig, clock_pairing[k], **mask_kwargs)
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 _resolve_clock_pairing(
887
+ def _resolve_pairing(
799
888
  self,
800
889
  signal_path: str,
801
- clock: Signal | str,
890
+ source: Signal | str,
802
891
  root_scope: Scope | None,
892
+ kind: str = 'clock',
803
893
  ) -> dict[tuple[Capture, ...], Signal]:
804
- """Resolve signal/clock query paths into a {signal_key: clock_signal} map.
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 clock ``Signal``: broadcast to all signals.
808
- - Single clock match: broadcast to all signals.
809
- - Multiple clock matches: longest-prefix clock key per signal key.
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(clock, Signal):
904
+ if isinstance(source, Signal):
813
905
  matched_signals = self.get_matched_signals(signal_path, root_scope=root_scope)
814
- return {k: clock for k in matched_signals}
815
- matched_clocks = self.get_matched_signals(clock, root_scope=root_scope)
816
- if not matched_clocks:
817
- raise ValueError(f'clock path {clock!r} matched no signals')
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(matched_clocks) == 1:
822
- clock_signal = next(iter(matched_clocks.values()))
823
- return {k: clock_signal for k in matched_signals}
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
- clock_keys = list(matched_clocks.keys())
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
- best_clock_key: tuple[Capture, ...] | None = None
830
- for ck in clock_keys:
831
- if sig_key[: len(ck)] == ck and len(ck) > best_len:
832
- best_len = len(ck)
833
- best_clock_key = ck
834
- if best_clock_key is None:
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 clock key is a prefix of signal key {sig_key!r}; '
837
- f'available clock keys: {clock_keys!r}'
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] = matched_clocks[best_clock_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 and time/cycle window."""
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