wavekit 0.7.2__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.8.0/PKG-INFO +125 -0
- wavekit-0.8.0/README.md +102 -0
- wavekit-0.7.2/build.py → wavekit-0.8.0/build_exts.py +22 -5
- {wavekit-0.7.2 → wavekit-0.8.0}/pyproject.toml +11 -6
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/__init__.py +2 -2
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/expression.py +1 -1
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/compiler.py +1 -1
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/dsl.py +17 -9
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/result.py +10 -6
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/runtime.py +16 -16
- wavekit-0.8.0/src/wavekit/readers/__init__.py +1 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/base.py +362 -173
- wavekit-0.8.0/src/wavekit/readers/clock_domain.py +57 -0
- wavekit-0.8.0/src/wavekit/readers/fsdb/__init__.py +1 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/fsdb/reader.py +13 -9
- wavekit-0.8.0/src/wavekit/readers/fst/__init__.py +1 -0
- wavekit-0.8.0/src/wavekit/readers/fst/reader.py +11 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/hierarchy.py +93 -12
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/matcher.py +2 -5
- wavekit-0.8.0/src/wavekit/readers/vcd/__init__.py +1 -0
- 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.2 → wavekit-0.8.0}/src/wavekit/waveform.py +274 -172
- wavekit-0.7.2/PKG-INFO +0 -556
- wavekit-0.7.2/README.md +0 -532
- wavekit-0.7.2/src/wavekit/readers/fst/reader.py +0 -206
- wavekit-0.7.2/src/wavekit/readers/vcd/reader.py +0 -178
- {wavekit-0.7.2 → wavekit-0.8.0}/LICENSE +0 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/__init__.py +0 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/errors.py +0 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/pattern/steps.py +0 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/expr_parser.py +0 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/range.py +0 -0
- {wavekit-0.7.2 → wavekit-0.8.0}/src/wavekit/readers/value_change.pyx +0 -0
wavekit-0.8.0/PKG-INFO
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wavekit
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: High-level digital waveform analysis in Python
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: cxzzzz
|
|
7
|
+
Author-email: cxz19961010@outlook.com
|
|
8
|
+
Requires-Python: >=3.9,<4.0
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Dist: lark (>=1.2,<2)
|
|
17
|
+
Requires-Dist: numpy (>=2.0.0,<3.0.0)
|
|
18
|
+
Requires-Dist: wavekit-pywellen (>=0.25.6)
|
|
19
|
+
Project-URL: Documentation, https://cxzzzz.github.io/wavekit
|
|
20
|
+
Project-URL: Repository, https://github.com/cxzzzz/wavekit
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# <img src="docs/en/images/logo.svg" alt="" width="30" height="30" valign="middle"> wavekit
|
|
24
|
+
|
|
25
|
+
[](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
|
|
26
|
+
[](https://pypi.org/project/wavekit/)
|
|
27
|
+
[](https://pypi.org/project/wavekit/)
|
|
28
|
+
[](https://pepy.tech/project/wavekit)
|
|
29
|
+
[](LICENSE)
|
|
30
|
+
|
|
31
|
+
English | [中文](README_ZH.md)
|
|
32
|
+
|
|
33
|
+
**Wavekit is a Python library for high-level digital waveform analysis.**
|
|
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.
|
|
37
|
+
|
|
38
|
+
> **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
|
|
39
|
+
> MCP tools for AI-assisted workflows.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- **Flexible signal queries:** find and batch-load related signals from
|
|
44
|
+
hierarchical waveform data using multiple path-matching options.
|
|
45
|
+
- **Cycle-level analysis:** use a range of waveform operations on clock-sampled
|
|
46
|
+
data to analyze cycle-based behavior such as interface backpressure and FIFO
|
|
47
|
+
occupancy.
|
|
48
|
+
- **Transaction-level analysis:** use temporal pattern matching to describe
|
|
49
|
+
signal relationships across multiple clock cycles for protocol analysis,
|
|
50
|
+
transaction extraction, and latency measurement.
|
|
51
|
+
- **Multi-format waveform support:** load and analyze VCD, FST, and FSDB files
|
|
52
|
+
using the same API.
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```console
|
|
57
|
+
python -m pip install wavekit
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
wavekit supports Python 3.9 and newer. FSDB support requires the Verdi NPI
|
|
61
|
+
runtime (`libNPI.so`); see the [installation guide](https://cxzzzz.github.io/wavekit/getting-started/installation/) for
|
|
62
|
+
setup details.
|
|
63
|
+
|
|
64
|
+
## Quick start
|
|
65
|
+
|
|
66
|
+
Calculate the occupancy of a FIFO from a VCD waveform:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import numpy as np
|
|
70
|
+
|
|
71
|
+
from wavekit import VcdReader
|
|
72
|
+
|
|
73
|
+
with VcdReader('simulation.vcd') as r:
|
|
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
|
|
78
|
+
|
|
79
|
+
depth = 16
|
|
80
|
+
occupancy = (w_ptr + depth - r_ptr) % depth
|
|
81
|
+
|
|
82
|
+
print('Average occupancy:', np.mean(occupancy.value))
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
See the [first waveform tutorial](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/) for a
|
|
86
|
+
complete runnable FIFO example.
|
|
87
|
+
|
|
88
|
+
## Documentation
|
|
89
|
+
|
|
90
|
+
See the [documentation](https://cxzzzz.github.io/wavekit/) for the full reference and guides:
|
|
91
|
+
|
|
92
|
+
- [Installation](https://cxzzzz.github.io/wavekit/getting-started/installation/)
|
|
93
|
+
- [First waveform](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/)
|
|
94
|
+
- [Reader guide](https://cxzzzz.github.io/wavekit/guides/reader/)
|
|
95
|
+
- [Signal queries](https://cxzzzz.github.io/wavekit/guides/signal-query/)
|
|
96
|
+
- [Waveform analysis](https://cxzzzz.github.io/wavekit/guides/waveform-analysis/)
|
|
97
|
+
- [Pattern matching](https://cxzzzz.github.io/wavekit/guides/pattern-matching/)
|
|
98
|
+
- [API reference](https://cxzzzz.github.io/wavekit/reference/api/)
|
|
99
|
+
- [Complete examples](https://cxzzzz.github.io/wavekit/examples/)
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
Install the development dependencies and run the checks with Poetry:
|
|
104
|
+
|
|
105
|
+
```console
|
|
106
|
+
poetry install
|
|
107
|
+
poetry run pytest
|
|
108
|
+
poetry run ruff check .
|
|
109
|
+
poetry run ruff format --check .
|
|
110
|
+
poetry run mypy
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Build the documentation locally with Python 3.10 or newer:
|
|
114
|
+
|
|
115
|
+
```console
|
|
116
|
+
poetry install --with docs
|
|
117
|
+
poetry run zensical build --clean --strict
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
See [Contributing](https://cxzzzz.github.io/wavekit/contributing/) for contribution guidelines.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE).
|
|
125
|
+
|
wavekit-0.8.0/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# <img src="docs/en/images/logo.svg" alt="" width="30" height="30" valign="middle"> wavekit
|
|
2
|
+
|
|
3
|
+
[](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
|
|
4
|
+
[](https://pypi.org/project/wavekit/)
|
|
5
|
+
[](https://pypi.org/project/wavekit/)
|
|
6
|
+
[](https://pepy.tech/project/wavekit)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
English | [中文](README_ZH.md)
|
|
10
|
+
|
|
11
|
+
**Wavekit is a Python library for high-level digital waveform analysis.**
|
|
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.
|
|
15
|
+
|
|
16
|
+
> **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
|
|
17
|
+
> MCP tools for AI-assisted workflows.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Flexible signal queries:** find and batch-load related signals from
|
|
22
|
+
hierarchical waveform data using multiple path-matching options.
|
|
23
|
+
- **Cycle-level analysis:** use a range of waveform operations on clock-sampled
|
|
24
|
+
data to analyze cycle-based behavior such as interface backpressure and FIFO
|
|
25
|
+
occupancy.
|
|
26
|
+
- **Transaction-level analysis:** use temporal pattern matching to describe
|
|
27
|
+
signal relationships across multiple clock cycles for protocol analysis,
|
|
28
|
+
transaction extraction, and latency measurement.
|
|
29
|
+
- **Multi-format waveform support:** load and analyze VCD, FST, and FSDB files
|
|
30
|
+
using the same API.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```console
|
|
35
|
+
python -m pip install wavekit
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
wavekit supports Python 3.9 and newer. FSDB support requires the Verdi NPI
|
|
39
|
+
runtime (`libNPI.so`); see the [installation guide](https://cxzzzz.github.io/wavekit/getting-started/installation/) for
|
|
40
|
+
setup details.
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
Calculate the occupancy of a FIFO from a VCD waveform:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import numpy as np
|
|
48
|
+
|
|
49
|
+
from wavekit import VcdReader
|
|
50
|
+
|
|
51
|
+
with VcdReader('simulation.vcd') as r:
|
|
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
|
|
56
|
+
|
|
57
|
+
depth = 16
|
|
58
|
+
occupancy = (w_ptr + depth - r_ptr) % depth
|
|
59
|
+
|
|
60
|
+
print('Average occupancy:', np.mean(occupancy.value))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
See the [first waveform tutorial](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/) for a
|
|
64
|
+
complete runnable FIFO example.
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
See the [documentation](https://cxzzzz.github.io/wavekit/) for the full reference and guides:
|
|
69
|
+
|
|
70
|
+
- [Installation](https://cxzzzz.github.io/wavekit/getting-started/installation/)
|
|
71
|
+
- [First waveform](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/)
|
|
72
|
+
- [Reader guide](https://cxzzzz.github.io/wavekit/guides/reader/)
|
|
73
|
+
- [Signal queries](https://cxzzzz.github.io/wavekit/guides/signal-query/)
|
|
74
|
+
- [Waveform analysis](https://cxzzzz.github.io/wavekit/guides/waveform-analysis/)
|
|
75
|
+
- [Pattern matching](https://cxzzzz.github.io/wavekit/guides/pattern-matching/)
|
|
76
|
+
- [API reference](https://cxzzzz.github.io/wavekit/reference/api/)
|
|
77
|
+
- [Complete examples](https://cxzzzz.github.io/wavekit/examples/)
|
|
78
|
+
|
|
79
|
+
## Development
|
|
80
|
+
|
|
81
|
+
Install the development dependencies and run the checks with Poetry:
|
|
82
|
+
|
|
83
|
+
```console
|
|
84
|
+
poetry install
|
|
85
|
+
poetry run pytest
|
|
86
|
+
poetry run ruff check .
|
|
87
|
+
poetry run ruff format --check .
|
|
88
|
+
poetry run mypy
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Build the documentation locally with Python 3.10 or newer:
|
|
92
|
+
|
|
93
|
+
```console
|
|
94
|
+
poetry install --with docs
|
|
95
|
+
poetry run zensical build --clean --strict
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See [Contributing](https://cxzzzz.github.io/wavekit/contributing/) for contribution guidelines.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE).
|
|
@@ -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,11 +1,12 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "wavekit"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.8.0"
|
|
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_exts.py"
|
|
9
10
|
include = [
|
|
10
11
|
{ path = "src/wavekit/**/*.so", format = "wheel" },
|
|
11
12
|
]
|
|
@@ -13,15 +14,19 @@ include = [
|
|
|
13
14
|
[tool.poetry.dependencies]
|
|
14
15
|
python = "^3.9"
|
|
15
16
|
numpy = "^2.0.0"
|
|
16
|
-
|
|
17
|
-
pylibfst = "^0.2.1"
|
|
18
|
-
pytest = "^8.2.2"
|
|
17
|
+
wavekit-pywellen = ">=0.25.6"
|
|
19
18
|
lark = ">=1.2,<2"
|
|
20
19
|
|
|
21
20
|
[tool.poetry.group.dev.dependencies]
|
|
21
|
+
pytest = "^8.2.2"
|
|
22
22
|
ruff = "^0.6.2"
|
|
23
23
|
mypy = "^1.11.2"
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
[tool.poetry.group.docs.dependencies]
|
|
27
|
+
zensical = {version = "^0.0.57", python = ">=3.10,<4.0"}
|
|
28
|
+
mkdocstrings-python = {version = "1.18.2", python = ">=3.10,<4.0"}
|
|
29
|
+
|
|
25
30
|
[tool.ruff]
|
|
26
31
|
target-version = "py39"
|
|
27
32
|
line-length = 100
|
|
@@ -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
|
|
@@ -16,7 +17,6 @@ from .readers.hierarchy import Signal as Signal
|
|
|
16
17
|
from .readers.hierarchy import SignalCompositeType as SignalCompositeType
|
|
17
18
|
from .readers.matcher import BraceCapture as BraceCapture
|
|
18
19
|
from .readers.matcher import Capture as Capture
|
|
19
|
-
from .readers.matcher import CaptureKey as CaptureKey
|
|
20
20
|
from .readers.matcher import ExactCapture as ExactCapture
|
|
21
21
|
from .readers.matcher import RegexCapture as RegexCapture
|
|
22
22
|
from .readers.matcher import WildcardCapture as WildcardCapture
|
|
@@ -29,13 +29,13 @@ __all__ = [
|
|
|
29
29
|
'VcdReader',
|
|
30
30
|
'FsdbReader',
|
|
31
31
|
'FstReader',
|
|
32
|
+
'ClockDomain',
|
|
32
33
|
'Node',
|
|
33
34
|
'Scope',
|
|
34
35
|
'Range',
|
|
35
36
|
'Signal',
|
|
36
37
|
'SignalCompositeType',
|
|
37
38
|
'Capture',
|
|
38
|
-
'CaptureKey',
|
|
39
39
|
'ExactCapture',
|
|
40
40
|
'BraceCapture',
|
|
41
41
|
'RegexCapture',
|
|
@@ -149,7 +149,7 @@ def evaluate_expression(expr: str, namespace: dict[str, Any]) -> Any:
|
|
|
149
149
|
Parameters
|
|
150
150
|
----------
|
|
151
151
|
expr:
|
|
152
|
-
The substituted expression returned by
|
|
152
|
+
The substituted expression returned by ``parse_expression``.
|
|
153
153
|
Physical paths must be resolved and replaced before calling this
|
|
154
154
|
function.
|
|
155
155
|
namespace:
|
|
@@ -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)):
|
|
@@ -28,8 +28,8 @@ class Pattern:
|
|
|
28
28
|
"""Declarative temporal pattern builder over waveform signals.
|
|
29
29
|
|
|
30
30
|
Build steps with ``Pattern().wait(...).capture(...)`` and execute with
|
|
31
|
-
module-level
|
|
32
|
-
programmable checking/extraction uses
|
|
31
|
+
module-level ``match``. ``Pattern`` stores only the declarative step AST;
|
|
32
|
+
programmable checking/extraction uses ``match(body)`` or ``collect(body)``.
|
|
33
33
|
|
|
34
34
|
The first blocking step selects candidate start cycles. Later blocking steps
|
|
35
35
|
wait within a matched transaction.
|
|
@@ -77,7 +77,10 @@ class Pattern:
|
|
|
77
77
|
def consume(
|
|
78
78
|
self,
|
|
79
79
|
cond: Waveform | Callable[[int, dict[str, Any]], bool] | bool,
|
|
80
|
-
channel: Channel
|
|
80
|
+
channel: Channel
|
|
81
|
+
| Hashable
|
|
82
|
+
| Callable[[int, dict[str, Any]], Channel | Hashable]
|
|
83
|
+
| None = None,
|
|
81
84
|
*,
|
|
82
85
|
require: Waveform | Callable[[int, dict[str, Any]], bool] | bool | None = None,
|
|
83
86
|
require_message: str | Callable[[int, dict[str, Any]], str] | None = None,
|
|
@@ -93,10 +96,11 @@ class Pattern:
|
|
|
93
96
|
cycle number. ``captures`` is the current match's capture dict.
|
|
94
97
|
channel:
|
|
95
98
|
``Channel``, hashable key, or ``callable(index, captures) -> Channel | Hashable``.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
If omitted, this consume step receives a private channel created when
|
|
100
|
+
the step is declared. Channel callbacks use the same ``(index, captures)``
|
|
101
|
+
arguments. The resolved ``(channel, cycle)`` can be claimed by at most
|
|
102
|
+
one match; earlier start cycles win. This does not reserve the channel
|
|
103
|
+
while ``cond`` is false.
|
|
100
104
|
require:
|
|
101
105
|
Optional condition checked while ``cond`` is false, or while ``cond`` is
|
|
102
106
|
true but the current ``(channel, cycle)`` was already claimed. It is not
|
|
@@ -110,9 +114,13 @@ class Pattern:
|
|
|
110
114
|
Pattern
|
|
111
115
|
This pattern, for chaining.
|
|
112
116
|
"""
|
|
117
|
+
resolved_channel = Channel() if channel is None else channel
|
|
113
118
|
self._steps.append(
|
|
114
119
|
ConsumeStep(
|
|
115
|
-
cond=cond,
|
|
120
|
+
cond=cond,
|
|
121
|
+
channel=resolved_channel,
|
|
122
|
+
require=require,
|
|
123
|
+
require_message=require_message,
|
|
116
124
|
)
|
|
117
125
|
)
|
|
118
126
|
return self
|
|
@@ -235,7 +243,7 @@ class Pattern:
|
|
|
235
243
|
Notes
|
|
236
244
|
-----
|
|
237
245
|
Exactly one of ``until`` and ``when`` is required. Conditions use the same
|
|
238
|
-
``Waveform`` / ``bool`` / callable forms as
|
|
246
|
+
``Waveform`` / ``bool`` / callable forms as ``wait``.
|
|
239
247
|
"""
|
|
240
248
|
if until is not None and when is not None:
|
|
241
249
|
raise ValueError("Cannot specify both 'until' and 'when' in loop()")
|
|
@@ -19,14 +19,18 @@ class MatchStatus:
|
|
|
19
19
|
|
|
20
20
|
@dataclass(frozen=True)
|
|
21
21
|
class OK(MatchStatusValue):
|
|
22
|
-
|
|
22
|
+
"""Successful completion of a pattern candidate."""
|
|
23
23
|
|
|
24
24
|
@dataclass(frozen=True)
|
|
25
25
|
class Timeout(MatchStatusValue):
|
|
26
|
+
"""A pattern candidate did not complete within its allowed duration."""
|
|
27
|
+
|
|
26
28
|
message: str | None = None
|
|
27
29
|
|
|
28
30
|
@dataclass(frozen=True)
|
|
29
31
|
class RequireViolated(MatchStatusValue):
|
|
32
|
+
"""A non-blocking ``require`` check failed."""
|
|
33
|
+
|
|
30
34
|
message: str | None = None
|
|
31
35
|
|
|
32
36
|
|
|
@@ -81,7 +85,7 @@ class MatchRecords(Sequence[MatchRecord]):
|
|
|
81
85
|
"""Columnar batch of pattern match records.
|
|
82
86
|
|
|
83
87
|
``start`` and ``end`` are point waveforms: ``.value`` stores waveform-array
|
|
84
|
-
sample indices, ``.
|
|
88
|
+
sample indices, ``.cycle`` stores absolute cycle numbers, and ``.time`` stores
|
|
85
89
|
simulation timestamps. ``duration.value`` is ``end.value - start.value + 1``.
|
|
86
90
|
"""
|
|
87
91
|
|
|
@@ -112,7 +116,7 @@ class MatchRecords(Sequence[MatchRecord]):
|
|
|
112
116
|
value = np.array(
|
|
113
117
|
[isinstance(status, MatchStatus.OK) for status in self.status.value], dtype=bool
|
|
114
118
|
)
|
|
115
|
-
return Waveform(value, self.start.
|
|
119
|
+
return Waveform(value, self.start.cycle.copy(), self.start.time.copy(), width=1)
|
|
116
120
|
|
|
117
121
|
@property
|
|
118
122
|
def failed(self) -> Waveform:
|
|
@@ -127,7 +131,7 @@ class MatchRecords(Sequence[MatchRecord]):
|
|
|
127
131
|
value = np.array(
|
|
128
132
|
[not isinstance(status, MatchStatus.OK) for status in self.status.value], dtype=bool
|
|
129
133
|
)
|
|
130
|
-
return Waveform(value, self.start.
|
|
134
|
+
return Waveform(value, self.start.cycle.copy(), self.start.time.copy(), width=1)
|
|
131
135
|
|
|
132
136
|
def filter_ok(self) -> MatchRecords:
|
|
133
137
|
"""Return records whose status is ``MatchStatus.OK``.
|
|
@@ -215,12 +219,12 @@ class MatchRecords(Sequence[MatchRecord]):
|
|
|
215
219
|
return MatchRecord(
|
|
216
220
|
start=MatchPoint(
|
|
217
221
|
index=int(self.start.value[index]),
|
|
218
|
-
cycle=int(self.start.
|
|
222
|
+
cycle=int(self.start.cycle[index]),
|
|
219
223
|
time=int(self.start.time[index]),
|
|
220
224
|
),
|
|
221
225
|
end=MatchPoint(
|
|
222
226
|
index=int(self.end.value[index]),
|
|
223
|
-
cycle=int(self.end.
|
|
227
|
+
cycle=int(self.end.cycle[index]),
|
|
224
228
|
time=int(self.end.time[index]),
|
|
225
229
|
),
|
|
226
230
|
status=self.status.value[index],
|
|
@@ -37,7 +37,7 @@ class PatternContext:
|
|
|
37
37
|
|
|
38
38
|
Programmable bodies run once per scanned start cycle. Use ``ctx.value(...)``
|
|
39
39
|
to read waveforms, ``ctx.wait(...)`` / ``ctx.consume(...)`` / ``ctx.delay(...)``
|
|
40
|
-
to move through time, and return ``ctx.OK`` from
|
|
40
|
+
to move through time, and return ``ctx.OK`` from ``wavekit.pattern.match``
|
|
41
41
|
bodies to record a successful match.
|
|
42
42
|
"""
|
|
43
43
|
|
|
@@ -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(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Waveform reader backends and hierarchy types."""
|