wavekit 0.7.3__tar.gz → 0.8.0__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 (35) hide show
  1. {wavekit-0.7.3 → wavekit-0.8.0}/PKG-INFO +10 -19
  2. {wavekit-0.7.3 → wavekit-0.8.0}/README.md +8 -15
  3. wavekit-0.7.3/build.py → wavekit-0.8.0/build_exts.py +22 -5
  4. {wavekit-0.7.3 → wavekit-0.8.0}/pyproject.toml +4 -5
  5. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/__init__.py +2 -0
  6. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/compiler.py +1 -1
  7. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/result.py +5 -5
  8. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/runtime.py +15 -15
  9. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/base.py +217 -90
  10. wavekit-0.8.0/src/wavekit/readers/clock_domain.py +57 -0
  11. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fsdb/reader.py +8 -7
  12. wavekit-0.8.0/src/wavekit/readers/fst/reader.py +11 -0
  13. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/hierarchy.py +87 -3
  14. wavekit-0.8.0/src/wavekit/readers/vcd/reader.py +11 -0
  15. wavekit-0.8.0/src/wavekit/readers/wellen/__init__.py +1 -0
  16. wavekit-0.8.0/src/wavekit/readers/wellen/reader.py +252 -0
  17. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/waveform.py +204 -113
  18. wavekit-0.7.3/src/wavekit/readers/fst/reader.py +0 -209
  19. wavekit-0.7.3/src/wavekit/readers/vcd/reader.py +0 -181
  20. {wavekit-0.7.3 → wavekit-0.8.0}/LICENSE +0 -0
  21. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/expression.py +0 -0
  22. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/__init__.py +0 -0
  23. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/dsl.py +0 -0
  24. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/errors.py +0 -0
  25. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/steps.py +0 -0
  26. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/__init__.py +0 -0
  27. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/expr_parser.py +0 -0
  28. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fsdb/__init__.py +0 -0
  29. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
  30. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
  31. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fst/__init__.py +0 -0
  32. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/matcher.py +0 -0
  33. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/range.py +0 -0
  34. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/value_change.pyx +0 -0
  35. {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/vcd/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavekit
3
- Version: 0.7.3
3
+ Version: 0.8.0
4
4
  Summary: High-level digital waveform analysis in Python
5
5
  License-File: LICENSE
6
6
  Author: cxzzzz
@@ -15,9 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Programming Language :: Python :: 3.14
16
16
  Requires-Dist: lark (>=1.2,<2)
17
17
  Requires-Dist: numpy (>=2.0.0,<3.0.0)
18
- Requires-Dist: pylibfst (>=0.2.1,<0.3.0)
19
- Requires-Dist: pytest (>=8.2.2,<9.0.0)
20
- Requires-Dist: vcdvcd (>=2.3.5,<3.0.0)
18
+ Requires-Dist: wavekit-pywellen (>=0.25.6)
21
19
  Project-URL: Documentation, https://cxzzzz.github.io/wavekit
22
20
  Project-URL: Repository, https://github.com/cxzzzz/wavekit
23
21
  Description-Content-Type: text/markdown
@@ -33,10 +31,9 @@ Description-Content-Type: text/markdown
33
31
  English | [中文](README_ZH.md)
34
32
 
35
33
  **Wavekit is a Python library for high-level digital waveform analysis.**
36
- Wavekit supports common digital waveform formats; by combining batch signal
37
- queries with analysis at the cycle and transaction levels, it helps users
38
- interpret recorded signal changes in terms of per-cycle state and temporal
39
- relationships.
34
+ It loads signals from waveform files as clock-aligned `Waveform` objects,
35
+ allowing users to query and operate on them at the cycle and transaction levels,
36
+ making complex hardware behavior easier to analyze.
40
37
 
41
38
  > **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
42
39
  > MCP tools for AI-assisted workflows.
@@ -74,18 +71,12 @@ import numpy as np
74
71
  from wavekit import VcdReader
75
72
 
76
73
  with VcdReader('simulation.vcd') as r:
77
- clock = 'tb.clk'
78
- depth = 16
79
-
80
- w_ptr = r.load_waveform(
81
- 'tb.u_fifo.w_ptr',
82
- clock=clock,
83
- )
84
- r_ptr = r.load_waveform(
85
- 'tb.u_fifo.r_ptr',
86
- clock=clock,
87
- )
74
+ fifo = r['tb.u_fifo']
75
+ with r.clock_domain(clock='tb.clk'):
76
+ w_ptr = fifo['w_ptr'].w
77
+ r_ptr = fifo['r_ptr'].w
88
78
 
79
+ depth = 16
89
80
  occupancy = (w_ptr + depth - r_ptr) % depth
90
81
 
91
82
  print('Average occupancy:', np.mean(occupancy.value))
@@ -9,10 +9,9 @@
9
9
  English | [中文](README_ZH.md)
10
10
 
11
11
  **Wavekit is a Python library for high-level digital waveform analysis.**
12
- Wavekit supports common digital waveform formats; by combining batch signal
13
- queries with analysis at the cycle and transaction levels, it helps users
14
- interpret recorded signal changes in terms of per-cycle state and temporal
15
- relationships.
12
+ It loads signals from waveform files as clock-aligned `Waveform` objects,
13
+ allowing users to query and operate on them at the cycle and transaction levels,
14
+ making complex hardware behavior easier to analyze.
16
15
 
17
16
  > **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
18
17
  > MCP tools for AI-assisted workflows.
@@ -50,18 +49,12 @@ import numpy as np
50
49
  from wavekit import VcdReader
51
50
 
52
51
  with VcdReader('simulation.vcd') as r:
53
- clock = 'tb.clk'
54
- depth = 16
55
-
56
- w_ptr = r.load_waveform(
57
- 'tb.u_fifo.w_ptr',
58
- clock=clock,
59
- )
60
- r_ptr = r.load_waveform(
61
- 'tb.u_fifo.r_ptr',
62
- clock=clock,
63
- )
52
+ fifo = r['tb.u_fifo']
53
+ with r.clock_domain(clock='tb.clk'):
54
+ w_ptr = fifo['w_ptr'].w
55
+ r_ptr = fifo['r_ptr'].w
64
56
 
57
+ depth = 16
65
58
  occupancy = (w_ptr + depth - r_ptr) % depth
66
59
 
67
60
  print('Average occupancy:', np.mean(occupancy.value))
@@ -1,3 +1,5 @@
1
+ import os
2
+ import sys
1
3
  from pathlib import Path
2
4
 
3
5
  import numpy as np
@@ -7,22 +9,37 @@ from setuptools import Extension, setup
7
9
  for artifact in Path('src/wavekit').glob('**/*.so'):
8
10
  artifact.unlink()
9
11
 
12
+ if os.environ.get('PYODIDE'):
13
+ compile_args = ['-fpic', '-O3']
14
+ link_args = ['-O3']
15
+ fsdb_libraries = []
16
+ elif sys.platform == 'linux':
17
+ compile_args = ['-fpic', '-O3']
18
+ link_args = ['-O3']
19
+ fsdb_libraries = ['dl']
20
+ elif sys.platform == 'darwin':
21
+ compile_args = ['-fpic', '-O3']
22
+ link_args = ['-O3']
23
+ fsdb_libraries = []
24
+ else:
25
+ raise RuntimeError(f'Unsupported platform: {sys.platform}')
26
+
10
27
  extensions = [
11
28
  Extension(
12
29
  'wavekit.readers.value_change',
13
30
  sources=['src/wavekit/readers/value_change.pyx'],
14
31
  include_dirs=[np.get_include()],
15
- extra_compile_args=['-fpic', '-O3', '-march=native'],
16
- extra_link_args=['-O3', '-march=native'],
32
+ extra_compile_args=compile_args,
33
+ extra_link_args=link_args,
17
34
  language='c++',
18
35
  ),
19
36
  Extension(
20
37
  'wavekit.readers.fsdb.npi_fsdb_reader',
21
38
  sources=['src/wavekit/readers/fsdb/npi_fsdb_reader.pyx'],
22
39
  include_dirs=[np.get_include()],
23
- libraries=['dl'],
24
- extra_compile_args=['-fpic', '-O3', '-march=native'],
25
- extra_link_args=['-O3', '-march=native'],
40
+ libraries=fsdb_libraries,
41
+ extra_compile_args=compile_args,
42
+ extra_link_args=link_args,
26
43
  language='c++',
27
44
  ),
28
45
  ]
@@ -1,12 +1,12 @@
1
1
  [tool.poetry]
2
2
  name = "wavekit"
3
- version = "0.7.3"
3
+ version = "0.8.0"
4
4
  description = "High-level digital waveform analysis in Python"
5
5
  authors = ["cxzzzz <cxz19961010@outlook.com>"]
6
6
  readme = "README.md"
7
7
  repository = "https://github.com/cxzzzz/wavekit"
8
8
  documentation = "https://cxzzzz.github.io/wavekit"
9
- build = "build.py"
9
+ build = "build_exts.py"
10
10
  include = [
11
11
  { path = "src/wavekit/**/*.so", format = "wheel" },
12
12
  ]
@@ -14,12 +14,11 @@ include = [
14
14
  [tool.poetry.dependencies]
15
15
  python = "^3.9"
16
16
  numpy = "^2.0.0"
17
- vcdvcd = "^2.3.5"
18
- pylibfst = "^0.2.1"
19
- pytest = "^8.2.2"
17
+ wavekit-pywellen = ">=0.25.6"
20
18
  lark = ">=1.2,<2"
21
19
 
22
20
  [tool.poetry.group.dev.dependencies]
21
+ pytest = "^8.2.2"
23
22
  ruff = "^0.6.2"
24
23
  mypy = "^1.11.2"
25
24
 
@@ -9,6 +9,7 @@ try:
9
9
  except metadata.PackageNotFoundError:
10
10
  __version__ = 'unknown'
11
11
 
12
+ from .readers.clock_domain import ClockDomain as ClockDomain
12
13
  from .readers.fst.reader import FstReader as FstReader
13
14
  from .readers.hierarchy import Node as Node
14
15
  from .readers.hierarchy import Scope as Scope
@@ -28,6 +29,7 @@ __all__ = [
28
29
  'VcdReader',
29
30
  'FsdbReader',
30
31
  'FstReader',
32
+ 'ClockDomain',
31
33
  'Node',
32
34
  'Scope',
33
35
  'Range',
@@ -28,7 +28,7 @@ from .steps import (
28
28
  def infer_declarative_axis(steps: list[Step]) -> Waveform | None:
29
29
  """Infer a minimal first-static-waveform scan axis hint from steps."""
30
30
  # Axis inference is intentionally minimal. It only finds a static waveform
31
- # so start/end_cycle can be translated to indices; full clock alignment
31
+ # so start/end_cycle can be translated to indices; full cycle-axis alignment
32
32
  # validation remains lazy in PatternRuntime.note_waveform().
33
33
  for step in steps:
34
34
  if isinstance(step, (WaitStep, ConsumeStep)):
@@ -85,7 +85,7 @@ class MatchRecords(Sequence[MatchRecord]):
85
85
  """Columnar batch of pattern match records.
86
86
 
87
87
  ``start`` and ``end`` are point waveforms: ``.value`` stores waveform-array
88
- sample indices, ``.clock`` stores absolute cycle numbers, and ``.time`` stores
88
+ sample indices, ``.cycle`` stores absolute cycle numbers, and ``.time`` stores
89
89
  simulation timestamps. ``duration.value`` is ``end.value - start.value + 1``.
90
90
  """
91
91
 
@@ -116,7 +116,7 @@ class MatchRecords(Sequence[MatchRecord]):
116
116
  value = np.array(
117
117
  [isinstance(status, MatchStatus.OK) for status in self.status.value], dtype=bool
118
118
  )
119
- return Waveform(value, self.start.clock.copy(), self.start.time.copy(), width=1)
119
+ return Waveform(value, self.start.cycle.copy(), self.start.time.copy(), width=1)
120
120
 
121
121
  @property
122
122
  def failed(self) -> Waveform:
@@ -131,7 +131,7 @@ class MatchRecords(Sequence[MatchRecord]):
131
131
  value = np.array(
132
132
  [not isinstance(status, MatchStatus.OK) for status in self.status.value], dtype=bool
133
133
  )
134
- return Waveform(value, self.start.clock.copy(), self.start.time.copy(), width=1)
134
+ return Waveform(value, self.start.cycle.copy(), self.start.time.copy(), width=1)
135
135
 
136
136
  def filter_ok(self) -> MatchRecords:
137
137
  """Return records whose status is ``MatchStatus.OK``.
@@ -219,12 +219,12 @@ class MatchRecords(Sequence[MatchRecord]):
219
219
  return MatchRecord(
220
220
  start=MatchPoint(
221
221
  index=int(self.start.value[index]),
222
- cycle=int(self.start.clock[index]),
222
+ cycle=int(self.start.cycle[index]),
223
223
  time=int(self.start.time[index]),
224
224
  ),
225
225
  end=MatchPoint(
226
226
  index=int(self.end.value[index]),
227
- cycle=int(self.end.clock[index]),
227
+ cycle=int(self.end.cycle[index]),
228
228
  time=int(self.end.time[index]),
229
229
  ),
230
230
  status=self.status.value[index],
@@ -121,7 +121,7 @@ class PatternContext:
121
121
  Parameters
122
122
  ----------
123
123
  waveform:
124
- Waveform to read. It must share the same clock axis as other observed
124
+ Waveform to read. It must share the same cycle axis as other observed
125
125
  pattern waveforms.
126
126
  offset:
127
127
  Relative sample offset from ``ctx.index``. ``0`` reads the current
@@ -142,17 +142,17 @@ class PatternContext:
142
142
  Parameters
143
143
  ----------
144
144
  waveform:
145
- Waveform whose clock axis is read and validated.
145
+ Waveform whose cycle axis is read and validated.
146
146
  offset:
147
147
  Relative sample offset from ``ctx.index``.
148
148
 
149
149
  Returns
150
150
  -------
151
151
  Any
152
- Scalar value from ``waveform.clock[ctx.index + offset]``.
152
+ Scalar value from ``waveform.cycle[ctx.index + offset]``.
153
153
  """
154
154
  index = self._waveform_index(waveform, offset)
155
- return waveform.clock[index]
155
+ return waveform.cycle[index]
156
156
 
157
157
  def time(self, waveform: Waveform, offset: int = 0) -> Any:
158
158
  """Return a waveform timestamp at the current sample plus *offset*.
@@ -400,14 +400,14 @@ class PatternRuntime:
400
400
  if self._start_cycle is None:
401
401
  return 0
402
402
  axis = self._require_axis()
403
- return int(np.searchsorted(axis.clock, self._start_cycle))
403
+ return int(np.searchsorted(axis.cycle, self._start_cycle))
404
404
 
405
405
  @cached_property
406
406
  def scan_end_index(self) -> int:
407
407
  axis = self._require_axis()
408
408
  if self._end_cycle is None:
409
409
  return len(axis.value)
410
- return int(np.searchsorted(axis.clock, self._end_cycle))
410
+ return int(np.searchsorted(axis.cycle, self._end_cycle))
411
411
 
412
412
  def match(self, start_cycle: int | None = None, end_cycle: int | None = None) -> MatchRecords:
413
413
  completed = self._run(start_cycle, end_cycle)
@@ -426,20 +426,20 @@ class PatternRuntime:
426
426
  continue
427
427
  raise PatternError(
428
428
  f'Pattern failed with {inst.status}; '
429
- f'start_cycle={axis.clock[inst.start_index]}, '
430
- f'failure_cycle={axis.clock[inst.end_index]}'
429
+ f'start_cycle={axis.cycle[inst.start_index]}, '
430
+ f'failure_cycle={axis.cycle[inst.end_index]}'
431
431
  )
432
432
  return [inst.return_value for inst in completed if inst.return_value is not None]
433
433
 
434
434
  def _records(self, completed: list[PatternInstance]) -> MatchRecords:
435
435
  axis = self._require_axis()
436
- completed.sort(key=lambda i: (int(axis.clock[i.start_index]), i.order))
436
+ completed.sort(key=lambda i: (int(axis.cycle[i.start_index]), i.order))
437
437
  start_index_arr = np.array([i.start_index for i in completed], dtype=np.int64)
438
438
  end_index_arr = np.array([i.end_index for i in completed], dtype=np.int64)
439
439
  start_cycle_arr = np.array(
440
- [int(axis.clock[i.start_index]) for i in completed], dtype=np.int64
440
+ [int(axis.cycle[i.start_index]) for i in completed], dtype=np.int64
441
441
  )
442
- end_cycle_arr = np.array([int(axis.clock[i.end_index]) for i in completed], dtype=np.int64)
442
+ end_cycle_arr = np.array([int(axis.cycle[i.end_index]) for i in completed], dtype=np.int64)
443
443
  start_time_arr = np.array(
444
444
  [int(axis.time[i.start_index]) for i in completed], dtype=np.int64
445
445
  )
@@ -493,10 +493,10 @@ class PatternRuntime:
493
493
  self._axis = waveform
494
494
  self._validated_waveform_ids.add(waveform_id)
495
495
  return
496
- if len(waveform.clock) != len(self._axis.clock):
497
- raise PatternError('Waveform clock arrays have different lengths')
498
- if not np.array_equal(waveform.clock, self._axis.clock):
499
- raise PatternError('Waveform clock arrays are not aligned')
496
+ if len(waveform.cycle) != len(self._axis.cycle):
497
+ raise PatternError('Waveform cycle arrays have different lengths')
498
+ if not np.array_equal(waveform.cycle, self._axis.cycle):
499
+ raise PatternError('Waveform cycle arrays are not aligned')
500
500
  self._validated_waveform_ids.add(waveform_id)
501
501
 
502
502
  def resolve_channel(