wavekit 0.5.5__tar.gz → 0.6.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.5.5 → wavekit-0.6.0}/PKG-INFO +66 -10
- {wavekit-0.5.5 → wavekit-0.6.0}/README.md +64 -9
- wavekit-0.6.0/build.py +38 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/pyproject.toml +2 -1
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/__init__.py +27 -24
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/pattern/__init__.py +2 -1
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/pattern/dsl.py +84 -20
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/pattern/engine.py +68 -30
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/pattern/result.py +1 -1
- wavekit-0.6.0/src/wavekit/pattern/steps.py +216 -0
- wavekit-0.6.0/src/wavekit/readers/fsdb/npi_fsdb.pxd +82 -0
- wavekit-0.6.0/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +739 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/readers/fsdb/reader.py +44 -17
- wavekit-0.6.0/src/wavekit/readers/fst/reader.py +261 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/readers/vcd/reader.py +18 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/waveform.py +163 -1
- wavekit-0.5.5/build.py +0 -52
- wavekit-0.5.5/src/wavekit/pattern/steps.py +0 -142
- wavekit-0.5.5/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -122
- wavekit-0.5.5/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -328
- {wavekit-0.5.5 → wavekit-0.6.0}/LICENSE +0 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/pattern/instance.py +0 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/readers/base.py +0 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/readers/expr_parser.py +0 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/readers/pattern_parser.py +0 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/readers/value_change.pyx +0 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/scope.py +0 -0
- {wavekit-0.5.5 → wavekit-0.6.0}/src/wavekit/signal.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavekit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: a fundamental package for digital circuit waveform analysis
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: cxzzzz
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Requires-Dist: numpy (>=2.0.0,<3.0.0)
|
|
17
|
+
Requires-Dist: pylibfst (>=0.2.1,<0.3.0)
|
|
17
18
|
Requires-Dist: pytest (>=8.2.2,<9.0.0)
|
|
18
19
|
Requires-Dist: vcdvcd (>=2.3.5,<3.0.0)
|
|
19
20
|
Project-URL: Repository, https://github.com/cxzzzz/wavekit
|
|
@@ -24,15 +25,21 @@ Description-Content-Type: text/markdown
|
|
|
24
25
|
[](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
|
|
25
26
|
[](https://pypi.org/project/wavekit/)
|
|
26
27
|
[](https://pypi.org/project/wavekit/)
|
|
28
|
+
[](https://pypi.org/project/wavekit/)
|
|
27
29
|
[](LICENSE)
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
English | [中文](README_ZH.md)
|
|
32
|
+
|
|
33
|
+
**Wavekit** is a fundamental Python library for digital waveform analysis. By seamlessly converting VCD, FST, and FSDB data into Numpy arrays, it empowers engineers to perform high-performance signal processing, protocol analysis, and automated verification with ease.
|
|
34
|
+
|
|
35
|
+
> 🤖 **AI Integration**: [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) — MCP server for AI-assisted waveform analysis. Let AI load signals and run pattern matching — no manual coding required.
|
|
30
36
|
|
|
31
37
|
## ✨ Features
|
|
32
38
|
|
|
33
|
-
- **
|
|
39
|
+
- **Flexible Signal Extraction**: Flexible batch signal extraction via brace expansion, integer ranges, and regular expressions — load groups of related signals in one call.
|
|
34
40
|
- **Rich Analysis Tools**: Numpy-like API for arithmetic, masking, bit-field manipulation, edge detection, and time/cycle slicing — compose complex signal queries in just a few lines.
|
|
35
41
|
- **Pattern Matching**: NFA-based temporal pattern engine that scans waveforms in a single pass to extract protocol transactions, measure latencies, and detect timing violations.
|
|
42
|
+
- **High-Performance Parsing & Storage**: VCD, FST, and FSDB readers with Numpy-backed storage for fast loading and memory efficiency, handling large simulation files with ease.
|
|
36
43
|
|
|
37
44
|
## 📦 Installation
|
|
38
45
|
|
|
@@ -40,11 +47,14 @@ Description-Content-Type: text/markdown
|
|
|
40
47
|
pip install wavekit
|
|
41
48
|
```
|
|
42
49
|
|
|
43
|
-
**Note**: To read FSDB files,
|
|
50
|
+
**Note**: To read FSDB files, the Verdi runtime library (`libNPI.so`) must be available at runtime. Configure via:
|
|
51
|
+
- `WAVEKIT_NPI_LIB` — direct path to `libNPI.so`
|
|
52
|
+
- `VERDI_HOME` — Verdi installation directory (searches `$VERDI_HOME/share/NPI/lib/...`)
|
|
53
|
+
- `LD_LIBRARY_PATH` — system library search path
|
|
44
54
|
|
|
45
55
|
## 🚀 Quick Start
|
|
46
56
|
|
|
47
|
-
> The examples below use placeholder filenames such as `sim.vcd`. Replace them with the path to your own VCD or FSDB file, and adjust signal paths to match your design hierarchy.
|
|
57
|
+
> The examples below use placeholder filenames such as `sim.vcd`. Replace them with the path to your own VCD, FST, or FSDB file, and adjust signal paths to match your design hierarchy.
|
|
48
58
|
|
|
49
59
|
### 1. Batch Signal Extraction
|
|
50
60
|
|
|
@@ -157,7 +167,7 @@ print(f"Read data: {valid.captures['rdata'].value}")
|
|
|
157
167
|
**AXI Write Burst (multi-beat)**
|
|
158
168
|
|
|
159
169
|
```python
|
|
160
|
-
beat = Pattern().wait(wvalid & wready).capture("beats
|
|
170
|
+
beat = Pattern().wait(wvalid & wready).capture("beats", wdata, mode="list")
|
|
161
171
|
|
|
162
172
|
result = (
|
|
163
173
|
Pattern()
|
|
@@ -195,7 +205,7 @@ print(f"Stall durations: {stalls.duration.value} cycles")
|
|
|
195
205
|
|
|
196
206
|
| Method | Description |
|
|
197
207
|
|--------|-------------|
|
|
198
|
-
| `VcdReader(file)` / `FsdbReader(file)` | Open a waveform file. Use as a context manager. `FsdbReader` requires `VERDI_HOME
|
|
208
|
+
| `VcdReader(file)` / `FstReader(file)` / `FsdbReader(file)` | Open a waveform file. Use as a context manager. `FsdbReader` requires Verdi runtime (`WAVEKIT_NPI_LIB`, `VERDI_HOME`, or `LD_LIBRARY_PATH`). |
|
|
199
209
|
| `reader.load_waveform(signal, clock, ...)` | Load one signal sampled on every clock edge. Returns `Waveform`. |
|
|
200
210
|
| `reader.load_matched_waveforms(pattern, clock_pattern, ...)` | Batch-load signals matching a brace/regex pattern. Returns `dict[tuple, Waveform]`. |
|
|
201
211
|
| `reader.eval(expr, clock, mode='single'\|'zip', ...)` | Evaluate an arithmetic expression with embedded signal paths. |
|
|
@@ -258,15 +268,33 @@ A `Waveform` wraps three parallel numpy arrays (`.value`, `.clock`, `.time`). Al
|
|
|
258
268
|
| `wave.rising_edge()` | True at 0→1 transitions |
|
|
259
269
|
| `wave.falling_edge()` | True at 1→0 transitions |
|
|
260
270
|
|
|
271
|
+
**Relative time access**
|
|
272
|
+
|
|
273
|
+
| Method | Description |
|
|
274
|
+
|--------|-------------|
|
|
275
|
+
| `wave.relative(offset, pad, pad_value)` | Shift by *offset* cycles (positive = future, negative = past) |
|
|
276
|
+
| `wave.ahead(n, pad, pad_value)` | Look *n* cycles into the future (shorthand for `relative(n)`) |
|
|
277
|
+
| `wave.back(n, pad, pad_value)` | Look *n* cycles into the past (shorthand for `relative(-n)`) |
|
|
278
|
+
|
|
279
|
+
`pad` controls boundary handling: `'repeat'` (default) pads with the first/last value, `'value'` pads with a given `pad_value`.
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
# Rising edge detection
|
|
283
|
+
rising = (wave == 0) & wave.ahead()
|
|
284
|
+
|
|
285
|
+
# Compare current vs 3 cycles ago
|
|
286
|
+
changed = wave != wave.back(3)
|
|
287
|
+
```
|
|
288
|
+
|
|
261
289
|
---
|
|
262
290
|
|
|
263
291
|
### Pattern
|
|
264
292
|
|
|
265
293
|
| Method | Description |
|
|
266
294
|
|--------|-------------|
|
|
267
|
-
| `.wait(cond,
|
|
268
|
-
| `.delay(n,
|
|
269
|
-
| `.capture(name, signal)` | Record signal value at current cycle. `
|
|
295
|
+
| `.wait(cond, *, require=None, channel=None, tick=True)` | Block until `cond` is True. `require` is checked each waiting cycle (failure → `REQUIRE_VIOLATED`). `channel` binds the wait to a shared FIFO consumer group (see [Channels](#channels) below). `tick=False` matches on the current cycle without consuming it. |
|
|
296
|
+
| `.delay(n, *, require=None)` | Advance `n` cycles. `delay(0)` is a no-op. `require` must hold every cycle. |
|
|
297
|
+
| `.capture(name, signal, *, mode='last')` | Record signal value at current cycle. `mode='last'` (default) overwrites; `'first'` keeps the first write; `'list'` appends to a list. |
|
|
270
298
|
| `.require(cond)` | Assert condition; fail with `REQUIRE_VIOLATED` if False. |
|
|
271
299
|
| `.loop(body, *, until=None, when=None)` | `until`: do-while (exit when True after body). `when`: while (exit when False before body). |
|
|
272
300
|
| `.repeat(body, n)` | Execute body exactly `n` times. `n` may be a callable. |
|
|
@@ -274,6 +302,34 @@ A `Waveform` wraps three parallel numpy arrays (`.value`, `.clock`, `.time`). Al
|
|
|
274
302
|
| `.timeout(max_cycles)` | Terminate unfinished instances with `TIMEOUT`. |
|
|
275
303
|
| `.match(start_cycle=None, end_cycle=None)` | Run the engine; return `MatchResult`. |
|
|
276
304
|
|
|
305
|
+
**Channels**
|
|
306
|
+
|
|
307
|
+
A `Channel` is an identity object representing a shared FIFO consumer group: at most one in-flight pattern instance may consume per cycle. Each `wait()` step has its own implicit channel, so multiple instances of the same pattern automatically serialize one-per-cycle on that step. Pass an explicit `Channel` (or `callable(index, captures) -> Channel`) when you need to override the default serialization — typically when events arrive on physically parallel buses (multi-bank memory, multi-lane retire) and several instances should consume *concurrently* by routing each to its own per-key channel.
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from collections import defaultdict
|
|
311
|
+
from wavekit import Channel, Pattern
|
|
312
|
+
|
|
313
|
+
# Multi-bank cache: each bank has its own response port, so two banks
|
|
314
|
+
# can return data in the *same* cycle. Without partitioning, the default
|
|
315
|
+
# serialization rule would force one instance to wait an extra cycle.
|
|
316
|
+
# A per-bank Channel lets each in-flight read consume from its own bank.
|
|
317
|
+
banks = defaultdict(Channel)
|
|
318
|
+
|
|
319
|
+
result = (
|
|
320
|
+
Pattern()
|
|
321
|
+
.wait(req_valid)
|
|
322
|
+
.capture('bank', req_addr & 1)
|
|
323
|
+
.wait(
|
|
324
|
+
lambda i, cap: bank_valid[cap['bank']].value[i],
|
|
325
|
+
channel=lambda i, cap: banks[cap['bank']],
|
|
326
|
+
)
|
|
327
|
+
.capture('rdata',
|
|
328
|
+
lambda i, cap: bank_data[cap['bank']].value[i])
|
|
329
|
+
.match()
|
|
330
|
+
)
|
|
331
|
+
```
|
|
332
|
+
|
|
277
333
|
**`MatchResult`**
|
|
278
334
|
|
|
279
335
|
| Field | Description |
|
|
@@ -3,15 +3,21 @@
|
|
|
3
3
|
[](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
|
|
4
4
|
[](https://pypi.org/project/wavekit/)
|
|
5
5
|
[](https://pypi.org/project/wavekit/)
|
|
6
|
+
[](https://pypi.org/project/wavekit/)
|
|
6
7
|
[](LICENSE)
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
English | [中文](README_ZH.md)
|
|
10
|
+
|
|
11
|
+
**Wavekit** is a fundamental Python library for digital waveform analysis. By seamlessly converting VCD, FST, and FSDB data into Numpy arrays, it empowers engineers to perform high-performance signal processing, protocol analysis, and automated verification with ease.
|
|
12
|
+
|
|
13
|
+
> 🤖 **AI Integration**: [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) — MCP server for AI-assisted waveform analysis. Let AI load signals and run pattern matching — no manual coding required.
|
|
9
14
|
|
|
10
15
|
## ✨ Features
|
|
11
16
|
|
|
12
|
-
- **
|
|
17
|
+
- **Flexible Signal Extraction**: Flexible batch signal extraction via brace expansion, integer ranges, and regular expressions — load groups of related signals in one call.
|
|
13
18
|
- **Rich Analysis Tools**: Numpy-like API for arithmetic, masking, bit-field manipulation, edge detection, and time/cycle slicing — compose complex signal queries in just a few lines.
|
|
14
19
|
- **Pattern Matching**: NFA-based temporal pattern engine that scans waveforms in a single pass to extract protocol transactions, measure latencies, and detect timing violations.
|
|
20
|
+
- **High-Performance Parsing & Storage**: VCD, FST, and FSDB readers with Numpy-backed storage for fast loading and memory efficiency, handling large simulation files with ease.
|
|
15
21
|
|
|
16
22
|
## 📦 Installation
|
|
17
23
|
|
|
@@ -19,11 +25,14 @@
|
|
|
19
25
|
pip install wavekit
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
**Note**: To read FSDB files,
|
|
28
|
+
**Note**: To read FSDB files, the Verdi runtime library (`libNPI.so`) must be available at runtime. Configure via:
|
|
29
|
+
- `WAVEKIT_NPI_LIB` — direct path to `libNPI.so`
|
|
30
|
+
- `VERDI_HOME` — Verdi installation directory (searches `$VERDI_HOME/share/NPI/lib/...`)
|
|
31
|
+
- `LD_LIBRARY_PATH` — system library search path
|
|
23
32
|
|
|
24
33
|
## 🚀 Quick Start
|
|
25
34
|
|
|
26
|
-
> The examples below use placeholder filenames such as `sim.vcd`. Replace them with the path to your own VCD or FSDB file, and adjust signal paths to match your design hierarchy.
|
|
35
|
+
> The examples below use placeholder filenames such as `sim.vcd`. Replace them with the path to your own VCD, FST, or FSDB file, and adjust signal paths to match your design hierarchy.
|
|
27
36
|
|
|
28
37
|
### 1. Batch Signal Extraction
|
|
29
38
|
|
|
@@ -136,7 +145,7 @@ print(f"Read data: {valid.captures['rdata'].value}")
|
|
|
136
145
|
**AXI Write Burst (multi-beat)**
|
|
137
146
|
|
|
138
147
|
```python
|
|
139
|
-
beat = Pattern().wait(wvalid & wready).capture("beats
|
|
148
|
+
beat = Pattern().wait(wvalid & wready).capture("beats", wdata, mode="list")
|
|
140
149
|
|
|
141
150
|
result = (
|
|
142
151
|
Pattern()
|
|
@@ -174,7 +183,7 @@ print(f"Stall durations: {stalls.duration.value} cycles")
|
|
|
174
183
|
|
|
175
184
|
| Method | Description |
|
|
176
185
|
|--------|-------------|
|
|
177
|
-
| `VcdReader(file)` / `FsdbReader(file)` | Open a waveform file. Use as a context manager. `FsdbReader` requires `VERDI_HOME
|
|
186
|
+
| `VcdReader(file)` / `FstReader(file)` / `FsdbReader(file)` | Open a waveform file. Use as a context manager. `FsdbReader` requires Verdi runtime (`WAVEKIT_NPI_LIB`, `VERDI_HOME`, or `LD_LIBRARY_PATH`). |
|
|
178
187
|
| `reader.load_waveform(signal, clock, ...)` | Load one signal sampled on every clock edge. Returns `Waveform`. |
|
|
179
188
|
| `reader.load_matched_waveforms(pattern, clock_pattern, ...)` | Batch-load signals matching a brace/regex pattern. Returns `dict[tuple, Waveform]`. |
|
|
180
189
|
| `reader.eval(expr, clock, mode='single'\|'zip', ...)` | Evaluate an arithmetic expression with embedded signal paths. |
|
|
@@ -237,15 +246,33 @@ A `Waveform` wraps three parallel numpy arrays (`.value`, `.clock`, `.time`). Al
|
|
|
237
246
|
| `wave.rising_edge()` | True at 0→1 transitions |
|
|
238
247
|
| `wave.falling_edge()` | True at 1→0 transitions |
|
|
239
248
|
|
|
249
|
+
**Relative time access**
|
|
250
|
+
|
|
251
|
+
| Method | Description |
|
|
252
|
+
|--------|-------------|
|
|
253
|
+
| `wave.relative(offset, pad, pad_value)` | Shift by *offset* cycles (positive = future, negative = past) |
|
|
254
|
+
| `wave.ahead(n, pad, pad_value)` | Look *n* cycles into the future (shorthand for `relative(n)`) |
|
|
255
|
+
| `wave.back(n, pad, pad_value)` | Look *n* cycles into the past (shorthand for `relative(-n)`) |
|
|
256
|
+
|
|
257
|
+
`pad` controls boundary handling: `'repeat'` (default) pads with the first/last value, `'value'` pads with a given `pad_value`.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
# Rising edge detection
|
|
261
|
+
rising = (wave == 0) & wave.ahead()
|
|
262
|
+
|
|
263
|
+
# Compare current vs 3 cycles ago
|
|
264
|
+
changed = wave != wave.back(3)
|
|
265
|
+
```
|
|
266
|
+
|
|
240
267
|
---
|
|
241
268
|
|
|
242
269
|
### Pattern
|
|
243
270
|
|
|
244
271
|
| Method | Description |
|
|
245
272
|
|--------|-------------|
|
|
246
|
-
| `.wait(cond,
|
|
247
|
-
| `.delay(n,
|
|
248
|
-
| `.capture(name, signal)` | Record signal value at current cycle. `
|
|
273
|
+
| `.wait(cond, *, require=None, channel=None, tick=True)` | Block until `cond` is True. `require` is checked each waiting cycle (failure → `REQUIRE_VIOLATED`). `channel` binds the wait to a shared FIFO consumer group (see [Channels](#channels) below). `tick=False` matches on the current cycle without consuming it. |
|
|
274
|
+
| `.delay(n, *, require=None)` | Advance `n` cycles. `delay(0)` is a no-op. `require` must hold every cycle. |
|
|
275
|
+
| `.capture(name, signal, *, mode='last')` | Record signal value at current cycle. `mode='last'` (default) overwrites; `'first'` keeps the first write; `'list'` appends to a list. |
|
|
249
276
|
| `.require(cond)` | Assert condition; fail with `REQUIRE_VIOLATED` if False. |
|
|
250
277
|
| `.loop(body, *, until=None, when=None)` | `until`: do-while (exit when True after body). `when`: while (exit when False before body). |
|
|
251
278
|
| `.repeat(body, n)` | Execute body exactly `n` times. `n` may be a callable. |
|
|
@@ -253,6 +280,34 @@ A `Waveform` wraps three parallel numpy arrays (`.value`, `.clock`, `.time`). Al
|
|
|
253
280
|
| `.timeout(max_cycles)` | Terminate unfinished instances with `TIMEOUT`. |
|
|
254
281
|
| `.match(start_cycle=None, end_cycle=None)` | Run the engine; return `MatchResult`. |
|
|
255
282
|
|
|
283
|
+
**Channels**
|
|
284
|
+
|
|
285
|
+
A `Channel` is an identity object representing a shared FIFO consumer group: at most one in-flight pattern instance may consume per cycle. Each `wait()` step has its own implicit channel, so multiple instances of the same pattern automatically serialize one-per-cycle on that step. Pass an explicit `Channel` (or `callable(index, captures) -> Channel`) when you need to override the default serialization — typically when events arrive on physically parallel buses (multi-bank memory, multi-lane retire) and several instances should consume *concurrently* by routing each to its own per-key channel.
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from collections import defaultdict
|
|
289
|
+
from wavekit import Channel, Pattern
|
|
290
|
+
|
|
291
|
+
# Multi-bank cache: each bank has its own response port, so two banks
|
|
292
|
+
# can return data in the *same* cycle. Without partitioning, the default
|
|
293
|
+
# serialization rule would force one instance to wait an extra cycle.
|
|
294
|
+
# A per-bank Channel lets each in-flight read consume from its own bank.
|
|
295
|
+
banks = defaultdict(Channel)
|
|
296
|
+
|
|
297
|
+
result = (
|
|
298
|
+
Pattern()
|
|
299
|
+
.wait(req_valid)
|
|
300
|
+
.capture('bank', req_addr & 1)
|
|
301
|
+
.wait(
|
|
302
|
+
lambda i, cap: bank_valid[cap['bank']].value[i],
|
|
303
|
+
channel=lambda i, cap: banks[cap['bank']],
|
|
304
|
+
)
|
|
305
|
+
.capture('rdata',
|
|
306
|
+
lambda i, cap: bank_data[cap['bank']].value[i])
|
|
307
|
+
.match()
|
|
308
|
+
)
|
|
309
|
+
```
|
|
310
|
+
|
|
256
311
|
**`MatchResult`**
|
|
257
312
|
|
|
258
313
|
| Field | Description |
|
wavekit-0.6.0/build.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from Cython.Build import cythonize
|
|
3
|
+
from setuptools import Extension, setup
|
|
4
|
+
|
|
5
|
+
extensions = [
|
|
6
|
+
Extension(
|
|
7
|
+
'src.wavekit.readers.value_change',
|
|
8
|
+
sources=['src/wavekit/readers/value_change.pyx'],
|
|
9
|
+
include_dirs=[np.get_include()],
|
|
10
|
+
extra_compile_args=['-fpic', '-O3', '-march=native'],
|
|
11
|
+
extra_link_args=['-O3', '-march=native'],
|
|
12
|
+
language='c++',
|
|
13
|
+
),
|
|
14
|
+
Extension(
|
|
15
|
+
'src.wavekit.readers.fsdb.npi_fsdb_reader',
|
|
16
|
+
sources=['src/wavekit/readers/fsdb/npi_fsdb_reader.pyx'],
|
|
17
|
+
include_dirs=[np.get_include()],
|
|
18
|
+
libraries=['dl'],
|
|
19
|
+
extra_compile_args=['-fpic', '-O3', '-march=native'],
|
|
20
|
+
extra_link_args=['-O3', '-march=native'],
|
|
21
|
+
language='c++',
|
|
22
|
+
),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
setup(
|
|
26
|
+
ext_modules=cythonize(
|
|
27
|
+
extensions,
|
|
28
|
+
compiler_directives={
|
|
29
|
+
'language_level': 3,
|
|
30
|
+
'embedsignature': True,
|
|
31
|
+
'boundscheck': False,
|
|
32
|
+
'wraparound': False,
|
|
33
|
+
'cdivision': True,
|
|
34
|
+
'nonecheck': False,
|
|
35
|
+
},
|
|
36
|
+
),
|
|
37
|
+
script_args=['build_ext', '--inplace'],
|
|
38
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "wavekit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "a fundamental package for digital circuit waveform analysis"
|
|
5
5
|
authors = ["cxzzzz <cxz19961010@outlook.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -12,6 +12,7 @@ include = ["src/wavekit/**/*.so"]
|
|
|
12
12
|
python = "^3.9"
|
|
13
13
|
numpy = "^2.0.0"
|
|
14
14
|
vcdvcd = "^2.3.5"
|
|
15
|
+
pylibfst = "^0.2.1"
|
|
15
16
|
pytest = "^8.2.2"
|
|
16
17
|
|
|
17
18
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -13,10 +13,12 @@ try:
|
|
|
13
13
|
except metadata.PackageNotFoundError:
|
|
14
14
|
__version__ = 'unknown'
|
|
15
15
|
|
|
16
|
+
from .pattern import Channel as Channel
|
|
16
17
|
from .pattern import MatchResult as MatchResult
|
|
17
18
|
from .pattern import MatchStatus as MatchStatus
|
|
18
19
|
from .pattern import Pattern as Pattern
|
|
19
20
|
from .pattern import PatternError as PatternError
|
|
21
|
+
from .readers.fst.reader import FstReader as FstReader
|
|
20
22
|
from .readers.vcd.reader import VcdReader as VcdReader
|
|
21
23
|
from .scope import Scope as Scope
|
|
22
24
|
from .signal import Signal as Signal
|
|
@@ -27,6 +29,7 @@ __all__ = [
|
|
|
27
29
|
'Waveform',
|
|
28
30
|
'VcdReader',
|
|
29
31
|
'FsdbReader',
|
|
32
|
+
'FstReader',
|
|
30
33
|
'Scope',
|
|
31
34
|
'Signal',
|
|
32
35
|
'SignalCompositeType',
|
|
@@ -34,35 +37,35 @@ __all__ = [
|
|
|
34
37
|
'MatchResult',
|
|
35
38
|
'MatchStatus',
|
|
36
39
|
'PatternError',
|
|
40
|
+
'Channel',
|
|
37
41
|
'has_fsdb_support',
|
|
38
42
|
]
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
import
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def has_fsdb_support() -> bool:
|
|
49
|
-
"""Check if FsdbReader is available in the current installation."""
|
|
50
|
-
return _fsdb_available
|
|
51
|
-
|
|
44
|
+
try:
|
|
45
|
+
from .readers.fsdb.npi_fsdb_reader import fsdb_runtime_available as _fsdb_runtime_available
|
|
46
|
+
from .readers.fsdb.reader import FsdbReader as FsdbReader
|
|
47
|
+
except Exception as _fsdb_import_error:
|
|
48
|
+
_fsdb_available = False
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
def has_fsdb_support() -> bool:
|
|
51
|
+
"""Check whether the Verdi FSDB runtime is available."""
|
|
52
|
+
return False
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'FsdbReader requires Verdi to be installed and configured.\n\n'
|
|
59
|
-
'1. Set VERDI_HOME environment variable to your Verdi installation path.\n'
|
|
60
|
-
'2. Reinstall wavekit:\n'
|
|
61
|
-
' pip install --force-reinstall --no-cache-dir --no-deps wavekit'
|
|
62
|
-
)
|
|
54
|
+
class _FsdbReaderStub:
|
|
55
|
+
"""Placeholder that raises an error when the Verdi FSDB runtime is unavailable."""
|
|
63
56
|
|
|
57
|
+
def __init__(self, *args, **kwargs):
|
|
58
|
+
raise RuntimeError(
|
|
59
|
+
'FsdbReader requires the Verdi FSDB runtime (libNPI.so).\n\n'
|
|
60
|
+
'Set WAVEKIT_NPI_LIB to the library path, set VERDI_HOME to the Verdi '
|
|
61
|
+
'installation directory, or ensure libNPI.so is in LD_LIBRARY_PATH.\n\n'
|
|
62
|
+
f'Import error: {_fsdb_import_error}' # noqa: F821
|
|
63
|
+
)
|
|
64
64
|
|
|
65
|
-
if _fsdb_available:
|
|
66
|
-
from .readers.fsdb.reader import FsdbReader as FsdbReader
|
|
67
|
-
else:
|
|
68
65
|
FsdbReader = _FsdbReaderStub # type: ignore[assignment]
|
|
66
|
+
else:
|
|
67
|
+
_fsdb_available = True
|
|
68
|
+
|
|
69
|
+
def has_fsdb_support() -> bool:
|
|
70
|
+
"""Check whether the Verdi FSDB runtime is available right now."""
|
|
71
|
+
return _fsdb_runtime_available()
|
|
@@ -4,5 +4,6 @@ from .dsl import Pattern as Pattern
|
|
|
4
4
|
from .engine import PatternError as PatternError
|
|
5
5
|
from .result import MatchResult as MatchResult
|
|
6
6
|
from .result import MatchStatus as MatchStatus
|
|
7
|
+
from .steps import Channel as Channel
|
|
7
8
|
|
|
8
|
-
__all__ = ['Pattern', 'MatchResult', 'MatchStatus', 'PatternError']
|
|
9
|
+
__all__ = ['Pattern', 'MatchResult', 'MatchStatus', 'PatternError', 'Channel']
|
|
@@ -3,8 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from .steps import (
|
|
6
|
+
_VALID_CAPTURE_MODES,
|
|
6
7
|
BranchStep,
|
|
7
8
|
CaptureStep,
|
|
9
|
+
ChannelValue,
|
|
8
10
|
DelayStep,
|
|
9
11
|
LoopStep,
|
|
10
12
|
RepeatStep,
|
|
@@ -15,7 +17,7 @@ from .steps import (
|
|
|
15
17
|
|
|
16
18
|
if TYPE_CHECKING:
|
|
17
19
|
from .result import MatchResult
|
|
18
|
-
from .steps import Condition, IntValue, SignalValue
|
|
20
|
+
from .steps import CaptureMode, Condition, IntValue, SignalValue
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
class Pattern:
|
|
@@ -43,25 +45,73 @@ class Pattern:
|
|
|
43
45
|
def wait(
|
|
44
46
|
self,
|
|
45
47
|
cond: Condition,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
*,
|
|
49
|
+
require: Condition | None = None,
|
|
50
|
+
channel: ChannelValue | None = None,
|
|
51
|
+
tick: bool = True,
|
|
48
52
|
) -> Pattern:
|
|
49
53
|
"""Block until *cond* becomes True.
|
|
50
54
|
|
|
55
|
+
By default each ``wait()`` step has its own private FIFO consumer
|
|
56
|
+
group: when multiple in-flight instances reach this step, only one
|
|
57
|
+
(the oldest) advances per cycle. Pass an explicit :class:`Channel`
|
|
58
|
+
via ``channel=`` to share the consumer group across multiple wait
|
|
59
|
+
steps (e.g., for AXI request/response pairing).
|
|
60
|
+
|
|
51
61
|
Parameters
|
|
52
62
|
----------
|
|
53
63
|
cond:
|
|
54
64
|
Waveform (static) or ``callable(index, captures) -> bool`` (dynamic).
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
the instance with ``REQUIRE_VIOLATED``.
|
|
65
|
+
require:
|
|
66
|
+
Optional condition that must hold every cycle while waiting.
|
|
67
|
+
Violation terminates the instance with ``REQUIRE_VIOLATED``.
|
|
58
68
|
channel:
|
|
59
|
-
|
|
69
|
+
Explicit :class:`Channel` (or ``callable(index, captures) -> Channel``
|
|
70
|
+
for dynamic key-based partitioning) that binds this wait to a
|
|
71
|
+
shared FIFO consumer group. ``None`` (default) uses an implicit
|
|
72
|
+
per-step channel.
|
|
73
|
+
tick:
|
|
74
|
+
When ``True`` (default), a successful match advances time by one
|
|
75
|
+
cycle before the next step is evaluated. When ``False`` the next
|
|
76
|
+
step evaluates on the **same** cycle (zero-cycle wait), useful
|
|
77
|
+
for measuring intervals like ``valid → valid & ready``.
|
|
78
|
+
|
|
79
|
+
Examples
|
|
80
|
+
--------
|
|
81
|
+
Static channel shared between request and response steps::
|
|
82
|
+
|
|
83
|
+
rsp_chan = Channel()
|
|
84
|
+
(Pattern()
|
|
85
|
+
.wait(req)
|
|
86
|
+
.wait(rsp, channel=rsp_chan))
|
|
87
|
+
|
|
88
|
+
Dynamic per-ID partitioning for AXI out-of-order reads::
|
|
89
|
+
|
|
90
|
+
from collections import defaultdict
|
|
91
|
+
chans = defaultdict(Channel)
|
|
92
|
+
(Pattern()
|
|
93
|
+
.wait(arvalid & arready)
|
|
94
|
+
.capture('arid', arid)
|
|
95
|
+
.wait(
|
|
96
|
+
lambda i, cap: bool((rvalid & rready)[i]) and rid[i] == cap['arid'],
|
|
97
|
+
channel=lambda i, cap: chans[cap['arid']],
|
|
98
|
+
))
|
|
99
|
+
|
|
100
|
+
Zero-cycle wait::
|
|
101
|
+
|
|
102
|
+
(Pattern()
|
|
103
|
+
.wait(valid)
|
|
104
|
+
.wait(valid & ready, tick=False)) # measures latency in same-cycle case
|
|
60
105
|
"""
|
|
61
|
-
self._steps.append(WaitStep(cond=cond,
|
|
106
|
+
self._steps.append(WaitStep(cond=cond, require=require, channel=channel, tick=tick))
|
|
62
107
|
return self
|
|
63
108
|
|
|
64
|
-
def delay(
|
|
109
|
+
def delay(
|
|
110
|
+
self,
|
|
111
|
+
n: IntValue,
|
|
112
|
+
*,
|
|
113
|
+
require: Condition | None = None,
|
|
114
|
+
) -> Pattern:
|
|
65
115
|
"""Block for exactly *n* cycles.
|
|
66
116
|
|
|
67
117
|
Parameters
|
|
@@ -69,25 +119,43 @@ class Pattern:
|
|
|
69
119
|
n:
|
|
70
120
|
Static ``int`` (>= 0) or ``callable(index, captures) -> int``.
|
|
71
121
|
``delay(0)`` is an epsilon step (no cycle consumed).
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
the instance with ``REQUIRE_VIOLATED``.
|
|
122
|
+
require:
|
|
123
|
+
Optional condition that must hold every cycle during the delay.
|
|
124
|
+
Violation terminates the instance with ``REQUIRE_VIOLATED``.
|
|
75
125
|
"""
|
|
76
126
|
if isinstance(n, int) and n < 0:
|
|
77
127
|
raise ValueError(f'delay() requires n >= 0, got {n}')
|
|
78
|
-
self._steps.append(DelayStep(n=n,
|
|
128
|
+
self._steps.append(DelayStep(n=n, require=require))
|
|
79
129
|
return self
|
|
80
130
|
|
|
81
131
|
# ------------------------------------------------------------------
|
|
82
132
|
# Epsilon steps
|
|
83
133
|
# ------------------------------------------------------------------
|
|
84
134
|
|
|
85
|
-
def capture(
|
|
135
|
+
def capture(
|
|
136
|
+
self,
|
|
137
|
+
name: str,
|
|
138
|
+
signal: SignalValue,
|
|
139
|
+
*,
|
|
140
|
+
mode: CaptureMode = 'last',
|
|
141
|
+
) -> Pattern:
|
|
86
142
|
"""Record a signal value at the current cycle.
|
|
87
143
|
|
|
88
|
-
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
name:
|
|
147
|
+
Capture key.
|
|
148
|
+
signal:
|
|
149
|
+
Waveform (static) or ``callable(index, captures) -> Any``.
|
|
150
|
+
mode:
|
|
151
|
+
* ``'last'`` (default) – ``cap[name]`` holds the most recent value
|
|
152
|
+
* ``'first'`` – ``cap[name]`` holds the first value; subsequent
|
|
153
|
+
writes are ignored
|
|
154
|
+
* ``'list'`` – ``cap[name]`` is a list, each write appends
|
|
89
155
|
"""
|
|
90
|
-
|
|
156
|
+
if mode not in _VALID_CAPTURE_MODES:
|
|
157
|
+
raise ValueError(f'capture mode must be one of {_VALID_CAPTURE_MODES}, got {mode!r}')
|
|
158
|
+
self._steps.append(CaptureStep(name=name, signal=signal, mode=mode))
|
|
91
159
|
return self
|
|
92
160
|
|
|
93
161
|
def require(self, cond: Condition) -> Pattern:
|
|
@@ -166,10 +234,6 @@ class Pattern:
|
|
|
166
234
|
) -> MatchResult:
|
|
167
235
|
"""Run the pattern engine and return a :class:`MatchResult`.
|
|
168
236
|
|
|
169
|
-
If the first step is a ``wait``, its condition is used as the trigger
|
|
170
|
-
(only fork instances when the condition is True). Otherwise, an
|
|
171
|
-
instance is forked on every cycle.
|
|
172
|
-
|
|
173
237
|
Parameters
|
|
174
238
|
----------
|
|
175
239
|
start_cycle:
|