wavekit 0.7.1__tar.gz → 0.7.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.7.3/PKG-INFO +134 -0
- wavekit-0.7.3/README.md +109 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/pyproject.toml +9 -2
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/__init__.py +0 -2
- wavekit-0.7.3/src/wavekit/expression.py +165 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/dsl.py +17 -9
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/result.py +5 -1
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/runtime.py +1 -1
- wavekit-0.7.3/src/wavekit/readers/__init__.py +1 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/base.py +219 -157
- wavekit-0.7.3/src/wavekit/readers/fsdb/__init__.py +1 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/fsdb/reader.py +5 -2
- wavekit-0.7.3/src/wavekit/readers/fst/__init__.py +1 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/fst/reader.py +7 -4
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/hierarchy.py +8 -11
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/matcher.py +2 -5
- wavekit-0.7.3/src/wavekit/readers/vcd/__init__.py +1 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/vcd/reader.py +8 -5
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/waveform.py +134 -139
- wavekit-0.7.1/PKG-INFO +0 -556
- wavekit-0.7.1/README.md +0 -533
- {wavekit-0.7.1 → wavekit-0.7.3}/LICENSE +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/build.py +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/__init__.py +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/compiler.py +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/errors.py +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/pattern/steps.py +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/expr_parser.py +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/range.py +0 -0
- {wavekit-0.7.1 → wavekit-0.7.3}/src/wavekit/readers/value_change.pyx +0 -0
wavekit-0.7.3/PKG-INFO
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wavekit
|
|
3
|
+
Version: 0.7.3
|
|
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: 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)
|
|
21
|
+
Project-URL: Documentation, https://cxzzzz.github.io/wavekit
|
|
22
|
+
Project-URL: Repository, https://github.com/cxzzzz/wavekit
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# <img src="docs/en/images/logo.svg" alt="" width="30" height="30" valign="middle"> wavekit
|
|
26
|
+
|
|
27
|
+
[](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
|
|
28
|
+
[](https://pypi.org/project/wavekit/)
|
|
29
|
+
[](https://pypi.org/project/wavekit/)
|
|
30
|
+
[](https://pepy.tech/project/wavekit)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
|
|
33
|
+
English | [中文](README_ZH.md)
|
|
34
|
+
|
|
35
|
+
**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.
|
|
40
|
+
|
|
41
|
+
> **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
|
|
42
|
+
> MCP tools for AI-assisted workflows.
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- **Flexible signal queries:** find and batch-load related signals from
|
|
47
|
+
hierarchical waveform data using multiple path-matching options.
|
|
48
|
+
- **Cycle-level analysis:** use a range of waveform operations on clock-sampled
|
|
49
|
+
data to analyze cycle-based behavior such as interface backpressure and FIFO
|
|
50
|
+
occupancy.
|
|
51
|
+
- **Transaction-level analysis:** use temporal pattern matching to describe
|
|
52
|
+
signal relationships across multiple clock cycles for protocol analysis,
|
|
53
|
+
transaction extraction, and latency measurement.
|
|
54
|
+
- **Multi-format waveform support:** load and analyze VCD, FST, and FSDB files
|
|
55
|
+
using the same API.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
python -m pip install wavekit
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
wavekit supports Python 3.9 and newer. FSDB support requires the Verdi NPI
|
|
64
|
+
runtime (`libNPI.so`); see the [installation guide](https://cxzzzz.github.io/wavekit/getting-started/installation/) for
|
|
65
|
+
setup details.
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
Calculate the occupancy of a FIFO from a VCD waveform:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import numpy as np
|
|
73
|
+
|
|
74
|
+
from wavekit import VcdReader
|
|
75
|
+
|
|
76
|
+
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
|
+
)
|
|
88
|
+
|
|
89
|
+
occupancy = (w_ptr + depth - r_ptr) % depth
|
|
90
|
+
|
|
91
|
+
print('Average occupancy:', np.mean(occupancy.value))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
See the [first waveform tutorial](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/) for a
|
|
95
|
+
complete runnable FIFO example.
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
See the [documentation](https://cxzzzz.github.io/wavekit/) for the full reference and guides:
|
|
100
|
+
|
|
101
|
+
- [Installation](https://cxzzzz.github.io/wavekit/getting-started/installation/)
|
|
102
|
+
- [First waveform](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/)
|
|
103
|
+
- [Reader guide](https://cxzzzz.github.io/wavekit/guides/reader/)
|
|
104
|
+
- [Signal queries](https://cxzzzz.github.io/wavekit/guides/signal-query/)
|
|
105
|
+
- [Waveform analysis](https://cxzzzz.github.io/wavekit/guides/waveform-analysis/)
|
|
106
|
+
- [Pattern matching](https://cxzzzz.github.io/wavekit/guides/pattern-matching/)
|
|
107
|
+
- [API reference](https://cxzzzz.github.io/wavekit/reference/api/)
|
|
108
|
+
- [Complete examples](https://cxzzzz.github.io/wavekit/examples/)
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
Install the development dependencies and run the checks with Poetry:
|
|
113
|
+
|
|
114
|
+
```console
|
|
115
|
+
poetry install
|
|
116
|
+
poetry run pytest
|
|
117
|
+
poetry run ruff check .
|
|
118
|
+
poetry run ruff format --check .
|
|
119
|
+
poetry run mypy
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Build the documentation locally with Python 3.10 or newer:
|
|
123
|
+
|
|
124
|
+
```console
|
|
125
|
+
poetry install --with docs
|
|
126
|
+
poetry run zensical build --clean --strict
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See [Contributing](https://cxzzzz.github.io/wavekit/contributing/) for contribution guidelines.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE).
|
|
134
|
+
|
wavekit-0.7.3/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
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.
|
|
16
|
+
|
|
17
|
+
> **AI integration:** [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) exposes wavekit analysis through
|
|
18
|
+
> MCP tools for AI-assisted workflows.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Flexible signal queries:** find and batch-load related signals from
|
|
23
|
+
hierarchical waveform data using multiple path-matching options.
|
|
24
|
+
- **Cycle-level analysis:** use a range of waveform operations on clock-sampled
|
|
25
|
+
data to analyze cycle-based behavior such as interface backpressure and FIFO
|
|
26
|
+
occupancy.
|
|
27
|
+
- **Transaction-level analysis:** use temporal pattern matching to describe
|
|
28
|
+
signal relationships across multiple clock cycles for protocol analysis,
|
|
29
|
+
transaction extraction, and latency measurement.
|
|
30
|
+
- **Multi-format waveform support:** load and analyze VCD, FST, and FSDB files
|
|
31
|
+
using the same API.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```console
|
|
36
|
+
python -m pip install wavekit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
wavekit supports Python 3.9 and newer. FSDB support requires the Verdi NPI
|
|
40
|
+
runtime (`libNPI.so`); see the [installation guide](https://cxzzzz.github.io/wavekit/getting-started/installation/) for
|
|
41
|
+
setup details.
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
Calculate the occupancy of a FIFO from a VCD waveform:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import numpy as np
|
|
49
|
+
|
|
50
|
+
from wavekit import VcdReader
|
|
51
|
+
|
|
52
|
+
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
|
+
)
|
|
64
|
+
|
|
65
|
+
occupancy = (w_ptr + depth - r_ptr) % depth
|
|
66
|
+
|
|
67
|
+
print('Average occupancy:', np.mean(occupancy.value))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See the [first waveform tutorial](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/) for a
|
|
71
|
+
complete runnable FIFO example.
|
|
72
|
+
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
See the [documentation](https://cxzzzz.github.io/wavekit/) for the full reference and guides:
|
|
76
|
+
|
|
77
|
+
- [Installation](https://cxzzzz.github.io/wavekit/getting-started/installation/)
|
|
78
|
+
- [First waveform](https://cxzzzz.github.io/wavekit/getting-started/first-waveform/)
|
|
79
|
+
- [Reader guide](https://cxzzzz.github.io/wavekit/guides/reader/)
|
|
80
|
+
- [Signal queries](https://cxzzzz.github.io/wavekit/guides/signal-query/)
|
|
81
|
+
- [Waveform analysis](https://cxzzzz.github.io/wavekit/guides/waveform-analysis/)
|
|
82
|
+
- [Pattern matching](https://cxzzzz.github.io/wavekit/guides/pattern-matching/)
|
|
83
|
+
- [API reference](https://cxzzzz.github.io/wavekit/reference/api/)
|
|
84
|
+
- [Complete examples](https://cxzzzz.github.io/wavekit/examples/)
|
|
85
|
+
|
|
86
|
+
## Development
|
|
87
|
+
|
|
88
|
+
Install the development dependencies and run the checks with Poetry:
|
|
89
|
+
|
|
90
|
+
```console
|
|
91
|
+
poetry install
|
|
92
|
+
poetry run pytest
|
|
93
|
+
poetry run ruff check .
|
|
94
|
+
poetry run ruff format --check .
|
|
95
|
+
poetry run mypy
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Build the documentation locally with Python 3.10 or newer:
|
|
99
|
+
|
|
100
|
+
```console
|
|
101
|
+
poetry install --with docs
|
|
102
|
+
poetry run zensical build --clean --strict
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
See [Contributing](https://cxzzzz.github.io/wavekit/contributing/) for contribution guidelines.
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE).
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "wavekit"
|
|
3
|
-
version = "0.7.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.7.3"
|
|
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
|
+
documentation = "https://cxzzzz.github.io/wavekit"
|
|
8
9
|
build = "build.py"
|
|
9
10
|
include = [
|
|
10
11
|
{ path = "src/wavekit/**/*.so", format = "wheel" },
|
|
@@ -16,11 +17,17 @@ numpy = "^2.0.0"
|
|
|
16
17
|
vcdvcd = "^2.3.5"
|
|
17
18
|
pylibfst = "^0.2.1"
|
|
18
19
|
pytest = "^8.2.2"
|
|
20
|
+
lark = ">=1.2,<2"
|
|
19
21
|
|
|
20
22
|
[tool.poetry.group.dev.dependencies]
|
|
21
23
|
ruff = "^0.6.2"
|
|
22
24
|
mypy = "^1.11.2"
|
|
23
25
|
|
|
26
|
+
|
|
27
|
+
[tool.poetry.group.docs.dependencies]
|
|
28
|
+
zensical = {version = "^0.0.57", python = ">=3.10,<4.0"}
|
|
29
|
+
mkdocstrings-python = {version = "1.18.2", python = ">=3.10,<4.0"}
|
|
30
|
+
|
|
24
31
|
[tool.ruff]
|
|
25
32
|
target-version = "py39"
|
|
26
33
|
line-length = 100
|
|
@@ -16,7 +16,6 @@ from .readers.hierarchy import Signal as Signal
|
|
|
16
16
|
from .readers.hierarchy import SignalCompositeType as SignalCompositeType
|
|
17
17
|
from .readers.matcher import BraceCapture as BraceCapture
|
|
18
18
|
from .readers.matcher import Capture as Capture
|
|
19
|
-
from .readers.matcher import CaptureKey as CaptureKey
|
|
20
19
|
from .readers.matcher import ExactCapture as ExactCapture
|
|
21
20
|
from .readers.matcher import RegexCapture as RegexCapture
|
|
22
21
|
from .readers.matcher import WildcardCapture as WildcardCapture
|
|
@@ -35,7 +34,6 @@ __all__ = [
|
|
|
35
34
|
'Signal',
|
|
36
35
|
'SignalCompositeType',
|
|
37
36
|
'Capture',
|
|
38
|
-
'CaptureKey',
|
|
39
37
|
'ExactCapture',
|
|
40
38
|
'BraceCapture',
|
|
41
39
|
'RegexCapture',
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Parsing and evaluating WaveKit waveform expressions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from lark import Lark, Transformer
|
|
9
|
+
|
|
10
|
+
_EXPRESSION_GRAMMAR = r"""
|
|
11
|
+
?start: expr
|
|
12
|
+
?expr: bit_or
|
|
13
|
+
?bit_or: bit_xor | bit_or VBAR bit_xor
|
|
14
|
+
?bit_xor: bit_and | bit_xor CIRCUMFLEX bit_and
|
|
15
|
+
?bit_and: comparison | bit_and AMPERSAND comparison
|
|
16
|
+
?comparison: shift
|
|
17
|
+
| comparison EQEQ shift
|
|
18
|
+
| comparison NEQ shift
|
|
19
|
+
| comparison LE shift
|
|
20
|
+
| comparison LT shift
|
|
21
|
+
| comparison GE shift
|
|
22
|
+
| comparison GT shift
|
|
23
|
+
?shift: sum | shift LSHIFT sum | shift RSHIFT sum
|
|
24
|
+
?sum: product | sum PLUS product | sum MINUS product
|
|
25
|
+
?product: unary
|
|
26
|
+
| product STAR unary
|
|
27
|
+
| product SLASH unary
|
|
28
|
+
| product FLOOR unary
|
|
29
|
+
| product PERCENT unary
|
|
30
|
+
| product POWER unary
|
|
31
|
+
?unary: TILDE unary | PLUS unary | MINUS unary | atom
|
|
32
|
+
?atom: call | path | NUMBER | group
|
|
33
|
+
group: LPAR expr RPAR
|
|
34
|
+
call: NAME LPAR [expr (COMMA expr)*] RPAR
|
|
35
|
+
path: segment (DOT segment)* SUBSCRIPT*
|
|
36
|
+
segment: segment_text | WILDCARD
|
|
37
|
+
segment_text: segment_piece+
|
|
38
|
+
?segment_piece: NAME | MODULE | BRACE | REGEX
|
|
39
|
+
|
|
40
|
+
MODULE: /\${1,2}/
|
|
41
|
+
BRACE: /\{[^{}]*\}/
|
|
42
|
+
WILDCARD.3: "**" | "*"
|
|
43
|
+
REGEX: /\/(?:\\.|[^\/\\])*\//
|
|
44
|
+
SUBSCRIPT: /\[\d+(?::\d+)?\]/
|
|
45
|
+
NAME: /[A-Za-z_][A-Za-z0-9_]*/
|
|
46
|
+
HEX_NUMBER.2: /0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*/
|
|
47
|
+
BIN_NUMBER.2: /0[bB][01](?:_?[01])*/
|
|
48
|
+
OCT_NUMBER.2: /0[oO][0-7](?:_?[0-7])*/
|
|
49
|
+
DECIMAL_FLOAT_NUMBER.2: /(?:\d(?:_?\d)*)?\.(?:\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?/
|
|
50
|
+
EXP_NUMBER.2: /\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*/
|
|
51
|
+
DEC_NUMBER.1: /\d(?:_?\d)*/
|
|
52
|
+
NUMBER: HEX_NUMBER | BIN_NUMBER | OCT_NUMBER | DECIMAL_FLOAT_NUMBER | EXP_NUMBER | DEC_NUMBER
|
|
53
|
+
|
|
54
|
+
VBAR: "|"
|
|
55
|
+
CIRCUMFLEX: "^"
|
|
56
|
+
AMPERSAND: "&"
|
|
57
|
+
EQEQ: "=="
|
|
58
|
+
NEQ: "!="
|
|
59
|
+
LE: "<="
|
|
60
|
+
LT: "<"
|
|
61
|
+
GE: ">="
|
|
62
|
+
GT: ">"
|
|
63
|
+
LSHIFT: "<<"
|
|
64
|
+
RSHIFT: ">>"
|
|
65
|
+
PLUS: "+"
|
|
66
|
+
MINUS: "-"
|
|
67
|
+
STAR: "*"
|
|
68
|
+
SLASH: "/"
|
|
69
|
+
FLOOR: "//"
|
|
70
|
+
PERCENT: "%"
|
|
71
|
+
POWER: "**"
|
|
72
|
+
TILDE: "~"
|
|
73
|
+
LPAR: "("
|
|
74
|
+
RPAR: ")"
|
|
75
|
+
COMMA: ","
|
|
76
|
+
DOT: "."
|
|
77
|
+
|
|
78
|
+
%import common.WS_INLINE
|
|
79
|
+
%ignore WS_INLINE
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
_EXPRESSION_PARSER = Lark(
|
|
83
|
+
_EXPRESSION_GRAMMAR,
|
|
84
|
+
parser='lalr',
|
|
85
|
+
lexer='contextual',
|
|
86
|
+
)
|
|
87
|
+
_EXPRESSION_FUNCTIONS: dict[str, Callable[..., Any]] = {}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def expression_function(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
91
|
+
"""Register a Waveform method as callable from an expression string."""
|
|
92
|
+
_EXPRESSION_FUNCTIONS[func.__name__] = func
|
|
93
|
+
return func
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ExpressionTransformer(Transformer):
|
|
97
|
+
def __init__(self, *, function_names: set[str]):
|
|
98
|
+
super().__init__()
|
|
99
|
+
self.function_names = function_names
|
|
100
|
+
self.path_entries: list[tuple[str, str]] = []
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def join(items: list[Any]) -> str:
|
|
104
|
+
return ''.join(str(item) for item in items)
|
|
105
|
+
|
|
106
|
+
def call(self, items: list[Any]) -> str:
|
|
107
|
+
name = str(items[0])
|
|
108
|
+
if name not in self.function_names:
|
|
109
|
+
raise ValueError(f'unknown expression function {name!r}')
|
|
110
|
+
return self.join(items)
|
|
111
|
+
|
|
112
|
+
def path(self, items: list[Any]) -> str:
|
|
113
|
+
path = self.join(items)
|
|
114
|
+
placeholder = f'__wave_{len(self.path_entries)}__'
|
|
115
|
+
self.path_entries.append((placeholder, path))
|
|
116
|
+
return placeholder
|
|
117
|
+
|
|
118
|
+
def __default__(self, data: str, children: list[Any], meta: Any) -> Any:
|
|
119
|
+
return self.join(children)
|
|
120
|
+
|
|
121
|
+
def __default_token__(self, token: Any) -> str:
|
|
122
|
+
return str(token)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def parse_expression(expr: str) -> tuple[str, list[tuple[str, str]]]:
|
|
126
|
+
"""Parse an expression and replace each signal path with a placeholder.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
expr:
|
|
131
|
+
WaveKit expression containing physical signal paths.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
tuple[str, list[tuple[str, str]]]
|
|
136
|
+
The substituted expression and ordered ``(placeholder, path)`` entries.
|
|
137
|
+
"""
|
|
138
|
+
tree = _EXPRESSION_PARSER.parse(expr)
|
|
139
|
+
transformer = ExpressionTransformer(
|
|
140
|
+
function_names=set(_EXPRESSION_FUNCTIONS),
|
|
141
|
+
)
|
|
142
|
+
result = transformer.transform(tree)
|
|
143
|
+
return str(result), transformer.path_entries
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def evaluate_expression(expr: str, namespace: dict[str, Any]) -> Any:
|
|
147
|
+
"""Evaluate an already-parsed expression against preloaded Waveforms.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
expr:
|
|
152
|
+
The substituted expression returned by ``parse_expression``.
|
|
153
|
+
Physical paths must be resolved and replaced before calling this
|
|
154
|
+
function.
|
|
155
|
+
namespace:
|
|
156
|
+
Names available to the expression, normally Waveform objects.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
Any
|
|
161
|
+
The result produced by the Waveform expression.
|
|
162
|
+
"""
|
|
163
|
+
evaluation_namespace = {**_EXPRESSION_FUNCTIONS, **namespace}
|
|
164
|
+
code = compile(expr, '<wavekit-expression>', 'eval')
|
|
165
|
+
return eval(code, {'__builtins__': {}}, evaluation_namespace) # noqa: S307
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Waveform reader backends and hierarchy types."""
|