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.
- {wavekit-0.7.3 → wavekit-0.8.0}/PKG-INFO +10 -19
- {wavekit-0.7.3 → wavekit-0.8.0}/README.md +8 -15
- wavekit-0.7.3/build.py → wavekit-0.8.0/build_exts.py +22 -5
- {wavekit-0.7.3 → wavekit-0.8.0}/pyproject.toml +4 -5
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/__init__.py +2 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/compiler.py +1 -1
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/result.py +5 -5
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/runtime.py +15 -15
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/base.py +217 -90
- wavekit-0.8.0/src/wavekit/readers/clock_domain.py +57 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fsdb/reader.py +8 -7
- wavekit-0.8.0/src/wavekit/readers/fst/reader.py +11 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/hierarchy.py +87 -3
- wavekit-0.8.0/src/wavekit/readers/vcd/reader.py +11 -0
- wavekit-0.8.0/src/wavekit/readers/wellen/__init__.py +1 -0
- wavekit-0.8.0/src/wavekit/readers/wellen/reader.py +252 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/waveform.py +204 -113
- wavekit-0.7.3/src/wavekit/readers/fst/reader.py +0 -209
- wavekit-0.7.3/src/wavekit/readers/vcd/reader.py +0 -181
- {wavekit-0.7.3 → wavekit-0.8.0}/LICENSE +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/expression.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/__init__.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/dsl.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/errors.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/pattern/steps.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/__init__.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/expr_parser.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fsdb/__init__.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/fst/__init__.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/matcher.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/range.py +0 -0
- {wavekit-0.7.3 → wavekit-0.8.0}/src/wavekit/readers/value_change.pyx +0 -0
- {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.
|
|
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:
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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=
|
|
16
|
-
extra_link_args=
|
|
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=
|
|
24
|
-
extra_compile_args=
|
|
25
|
-
extra_link_args=
|
|
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.
|
|
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 = "
|
|
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
|
-
|
|
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
|
|
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, ``.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
152
|
+
Scalar value from ``waveform.cycle[ctx.index + offset]``.
|
|
153
153
|
"""
|
|
154
154
|
index = self._waveform_index(waveform, offset)
|
|
155
|
-
return waveform.
|
|
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.
|
|
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.
|
|
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.
|
|
430
|
-
f'failure_cycle={axis.
|
|
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.
|
|
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.
|
|
440
|
+
[int(axis.cycle[i.start_index]) for i in completed], dtype=np.int64
|
|
441
441
|
)
|
|
442
|
-
end_cycle_arr = np.array([int(axis.
|
|
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.
|
|
497
|
-
raise PatternError('Waveform
|
|
498
|
-
if not np.array_equal(waveform.
|
|
499
|
-
raise PatternError('Waveform
|
|
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(
|