wavekit 0.7.0a1__tar.gz → 0.7.1__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.0a1 → wavekit-0.7.1}/PKG-INFO +164 -121
- wavekit-0.7.1/README.md +533 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/pyproject.toml +1 -1
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/__init__.py +21 -19
- wavekit-0.7.1/src/wavekit/pattern/__init__.py +23 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/pattern/compiler.py +76 -36
- wavekit-0.7.1/src/wavekit/pattern/dsl.py +414 -0
- wavekit-0.7.1/src/wavekit/pattern/result.py +243 -0
- wavekit-0.7.1/src/wavekit/pattern/runtime.py +587 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/pattern/steps.py +46 -62
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/readers/base.py +230 -284
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +200 -300
- wavekit-0.7.1/src/wavekit/readers/fsdb/reader.py +210 -0
- wavekit-0.7.1/src/wavekit/readers/fst/reader.py +206 -0
- wavekit-0.7.1/src/wavekit/readers/hierarchy.py +368 -0
- wavekit-0.7.1/src/wavekit/readers/matcher.py +410 -0
- wavekit-0.7.1/src/wavekit/readers/range.py +16 -0
- wavekit-0.7.1/src/wavekit/readers/vcd/reader.py +178 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/waveform.py +143 -106
- wavekit-0.7.0a1/README.md +0 -490
- wavekit-0.7.0a1/src/wavekit/pattern/__init__.py +0 -9
- wavekit-0.7.0a1/src/wavekit/pattern/dsl.py +0 -314
- wavekit-0.7.0a1/src/wavekit/pattern/result.py +0 -112
- wavekit-0.7.0a1/src/wavekit/pattern/runtime.py +0 -526
- wavekit-0.7.0a1/src/wavekit/readers/fsdb/reader.py +0 -231
- wavekit-0.7.0a1/src/wavekit/readers/fst/reader.py +0 -206
- wavekit-0.7.0a1/src/wavekit/readers/pattern_parser.py +0 -90
- wavekit-0.7.0a1/src/wavekit/readers/vcd/reader.py +0 -161
- wavekit-0.7.0a1/src/wavekit/scope.py +0 -317
- wavekit-0.7.0a1/src/wavekit/signal.py +0 -87
- {wavekit-0.7.0a1 → wavekit-0.7.1}/LICENSE +0 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/build.py +0 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/pattern/errors.py +0 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/readers/expr_parser.py +0 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
- {wavekit-0.7.0a1 → wavekit-0.7.1}/src/wavekit/readers/value_change.pyx +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavekit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: a fundamental package for digital circuit waveform analysis
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: cxzzzz
|
|
@@ -30,29 +30,29 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
|
|
31
31
|
English | [中文](README_ZH.md)
|
|
32
32
|
|
|
33
|
-
**
|
|
33
|
+
**wavekit** is a high-performance Python library for digital waveform analysis. It loads VCD, FST, and FSDB signals into NumPy-backed `Waveform` objects, and builds on them with efficient signal processing, protocol analysis, and automated verification APIs.
|
|
34
34
|
|
|
35
|
-
> 🤖 **AI
|
|
35
|
+
> 🤖 **AI integration**: [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) provides an MCP server for AI-assisted waveform analysis: load signals and run pattern matching from AI tools without hand-written scripts.
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## Features
|
|
38
38
|
|
|
39
|
-
- **
|
|
40
|
-
- **Rich
|
|
41
|
-
- **
|
|
42
|
-
- **High-
|
|
39
|
+
- **Batch signal extraction**: use brace expansion, integer ranges, regex, and wildcards to load related signals in one call.
|
|
40
|
+
- **Rich analysis API**: use NumPy-style arithmetic, masking, bit slicing, edge detection, and time/cycle slicing to build signal queries in a few lines.
|
|
41
|
+
- **Temporal pattern matching**: use declarative and programmable Pattern APIs to extract protocol transactions, measure handshake latency, and detect timeout or hang failures.
|
|
42
|
+
- **High-performance waveform processing**: read VCD, FST, and FSDB files into compact NumPy-backed arrays for fast loading and efficient memory use.
|
|
43
43
|
|
|
44
|
-
##
|
|
44
|
+
## Installation
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
pip install wavekit
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
**Note**: To read FSDB files, the Verdi runtime library (`libNPI.so`) must be available at runtime. Configure via:
|
|
51
|
-
- `WAVEKIT_NPI_LIB
|
|
52
|
-
- `VERDI_HOME
|
|
53
|
-
- `LD_LIBRARY_PATH
|
|
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
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## Quick start
|
|
56
56
|
|
|
57
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.
|
|
58
58
|
|
|
@@ -64,17 +64,16 @@ Use brace expansion or regular expressions to load multiple related signals in o
|
|
|
64
64
|
from wavekit import VcdReader
|
|
65
65
|
|
|
66
66
|
with VcdReader("jtag.vcd") as f:
|
|
67
|
-
#
|
|
68
|
-
# Returns: { ('state',): Waveform, ('next',): Waveform }
|
|
67
|
+
# Keys contain BraceCapture objects whose groups are ("state",) / ("next",).
|
|
69
68
|
waves = f.load_matched_waveforms(
|
|
70
69
|
"tb.u0.J_{state,next}[3:0]",
|
|
71
|
-
|
|
70
|
+
clock_path="tb.tck",
|
|
72
71
|
)
|
|
73
72
|
|
|
74
|
-
#
|
|
73
|
+
# /regex/ is the canonical regex syntax; groups are stored by RegexCapture.
|
|
75
74
|
waves = f.load_matched_waveforms(
|
|
76
|
-
r"tb.u0
|
|
77
|
-
|
|
75
|
+
r"tb.u0./J_([a-z]+)/",
|
|
76
|
+
clock_path="tb.tck",
|
|
78
77
|
)
|
|
79
78
|
```
|
|
80
79
|
|
|
@@ -82,7 +81,7 @@ with VcdReader("jtag.vcd") as f:
|
|
|
82
81
|
|
|
83
82
|
### 2. Signal Analysis
|
|
84
83
|
|
|
85
|
-
Waveforms support
|
|
84
|
+
Waveforms support NumPy-style arithmetic, masking, and edge detection.
|
|
86
85
|
|
|
87
86
|
```python
|
|
88
87
|
import numpy as np
|
|
@@ -141,8 +140,7 @@ with VcdReader("fifo_tb.vcd") as f:
|
|
|
141
140
|
clock="fifo_tb.clk",
|
|
142
141
|
)
|
|
143
142
|
|
|
144
|
-
# Zip mode: brace patterns expand per
|
|
145
|
-
# Returns: { (0,): Waveform, (1,): Waveform, (2,): Waveform, (3,): Waveform }
|
|
143
|
+
# Zip mode: brace patterns expand per typed CaptureKey and evaluate per match.
|
|
146
144
|
occupancies = f.eval(
|
|
147
145
|
"tb.fifo_{0..3}.w_ptr[2:0] - tb.fifo_{0..3}.r_ptr[2:0]",
|
|
148
146
|
clock="tb.clk",
|
|
@@ -154,19 +152,24 @@ with VcdReader("fifo_tb.vcd") as f:
|
|
|
154
152
|
|
|
155
153
|
### 4. Pattern Matching
|
|
156
154
|
|
|
157
|
-
`Pattern` scans a waveform and extracts
|
|
155
|
+
`Pattern` scans a waveform and extracts matching transactions, such as a request/response pair, a burst, a stall interval, or another repeating timing pattern.
|
|
158
156
|
|
|
159
|
-
|
|
157
|
+
Choose the form that matches the transaction shape:
|
|
160
158
|
|
|
161
|
-
- **Declarative
|
|
162
|
-
- **Programmable
|
|
159
|
+
- **Declarative**: use `.wait()`, `.consume()`, `.capture()`, and `.loop()` steps to describe fixed flows.
|
|
160
|
+
- **Programmable**: use a normal Python function when the flow depends on waveform values, such as dynamic branches or per-ID routing.
|
|
161
|
+
|
|
162
|
+
In declarative patterns, the first blocking step selects transaction start cycles; later blocking steps describe timing within each matched transaction.
|
|
163
|
+
|
|
164
|
+
`match(pattern)` returns `MatchRecords`, while `collect(body)` returns a Python `list` of extracted values.
|
|
163
165
|
|
|
164
166
|
#### Declarative examples
|
|
165
167
|
|
|
166
168
|
**AXI-Lite read latency**
|
|
167
169
|
|
|
168
170
|
```python
|
|
169
|
-
from wavekit import VcdReader
|
|
171
|
+
from wavekit import VcdReader
|
|
172
|
+
from wavekit.pattern import Pattern, match, collect
|
|
170
173
|
|
|
171
174
|
with VcdReader("axi_tb.vcd") as f:
|
|
172
175
|
clk = "tb.clk"
|
|
@@ -176,16 +179,17 @@ with VcdReader("axi_tb.vcd") as f:
|
|
|
176
179
|
rready = f.load_waveform("tb.dut.rready", clock=clk)
|
|
177
180
|
rdata = f.load_waveform("tb.dut.rdata[31:0]", clock=clk)
|
|
178
181
|
|
|
179
|
-
|
|
180
|
-
Pattern(
|
|
182
|
+
pattern = (
|
|
183
|
+
Pattern()
|
|
181
184
|
.wait(arvalid & arready) # AR handshake → transaction starts
|
|
182
185
|
.wait(rvalid & rready) # R handshake → transaction ends
|
|
183
186
|
.capture("rdata", rdata)
|
|
184
|
-
.match()
|
|
185
187
|
)
|
|
188
|
+
result = match(pattern)
|
|
186
189
|
|
|
187
190
|
ok = result.filter_ok()
|
|
188
|
-
|
|
191
|
+
latency = ok.end.clock - ok.start.clock
|
|
192
|
+
print(f"Read latencies (cycles): {latency}")
|
|
189
193
|
print(f"Read data: {ok.captures['rdata'].value}")
|
|
190
194
|
```
|
|
191
195
|
|
|
@@ -194,13 +198,12 @@ with VcdReader("axi_tb.vcd") as f:
|
|
|
194
198
|
```python
|
|
195
199
|
beat = Pattern().consume(wvalid & wready, channel="w").capture("beats", wdata, mode="list")
|
|
196
200
|
|
|
197
|
-
|
|
201
|
+
pattern = (
|
|
198
202
|
Pattern()
|
|
199
203
|
.wait(awvalid & awready) # AW handshake → burst starts
|
|
200
204
|
.loop(beat, until=wlast) # collect each beat until wlast
|
|
201
|
-
.timeout(512)
|
|
202
|
-
.match()
|
|
203
205
|
)
|
|
206
|
+
result = match(pattern)
|
|
204
207
|
|
|
205
208
|
for i, inst in enumerate(result.filter_ok()):
|
|
206
209
|
print(f"Burst {i}: {len(inst.captures['beats'])} beats")
|
|
@@ -211,57 +214,81 @@ for i, inst in enumerate(result.filter_ok()):
|
|
|
211
214
|
```python
|
|
212
215
|
stall = valid & (ready == 0)
|
|
213
216
|
|
|
214
|
-
|
|
217
|
+
pattern = (
|
|
215
218
|
Pattern()
|
|
216
219
|
.wait(stall.rising_edge()) # stall begins
|
|
217
220
|
.loop(Pattern().delay(1), when=stall) # keep waiting until stall ends
|
|
218
|
-
.match()
|
|
219
221
|
)
|
|
222
|
+
result = match(pattern)
|
|
220
223
|
|
|
221
224
|
stalls = result.filter_ok()
|
|
222
|
-
|
|
225
|
+
stall_cycles = stalls.duration.value - 1
|
|
226
|
+
print(f"Stall durations: {stall_cycles} cycles")
|
|
223
227
|
```
|
|
224
228
|
|
|
225
229
|
#### Programmable example
|
|
226
230
|
|
|
227
|
-
**
|
|
231
|
+
**DMA-style command stream**
|
|
228
232
|
|
|
229
|
-
|
|
233
|
+
Use programmable control flow when a command's opcode changes the following timing shape.
|
|
230
234
|
|
|
231
235
|
```python
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
236
|
+
cmd_fire = cmd_valid & cmd_ready # precompute outside the handler
|
|
237
|
+
w_fire = w_valid & w_ready
|
|
238
|
+
rsp_fire = rsp_valid & rsp_ready
|
|
239
|
+
r_fire = r_valid & r_ready
|
|
240
|
+
|
|
241
|
+
OP_READ = 0
|
|
242
|
+
OP_WRITE = 1
|
|
243
|
+
|
|
244
|
+
def read_dma_cmd(ctx):
|
|
245
|
+
if not ctx.value(cmd_fire):
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
op = int(ctx.value(cmd_op))
|
|
249
|
+
addr = int(ctx.value(cmd_addr))
|
|
250
|
+
length = int(ctx.value(cmd_len))
|
|
251
|
+
|
|
252
|
+
if op == OP_WRITE:
|
|
253
|
+
data = []
|
|
254
|
+
for _ in range(length):
|
|
255
|
+
ctx.consume(w_fire, channel="wdata")
|
|
256
|
+
data.append(int(ctx.value(w_data)))
|
|
257
|
+
|
|
258
|
+
ctx.consume(rsp_fire, channel="rsp")
|
|
259
|
+
return {
|
|
260
|
+
"op": "write",
|
|
261
|
+
"addr": addr,
|
|
262
|
+
"data": data,
|
|
263
|
+
"status": int(ctx.value(rsp_status)),
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if op == OP_READ:
|
|
267
|
+
ctx.consume(rsp_fire, channel="rsp")
|
|
268
|
+
data = []
|
|
269
|
+
for _ in range(length):
|
|
270
|
+
ctx.consume(r_fire, channel="rdata")
|
|
271
|
+
data.append(int(ctx.value(r_data)))
|
|
272
|
+
|
|
273
|
+
return {"op": "read", "addr": addr, "data": data}
|
|
274
|
+
|
|
275
|
+
ctx.require(False, message=f"unknown DMA op {op}")
|
|
250
276
|
return None
|
|
251
277
|
|
|
252
|
-
|
|
278
|
+
commands = collect(read_dma_cmd)
|
|
279
|
+
print(f"Captured {len(commands)} commands")
|
|
253
280
|
```
|
|
254
281
|
|
|
255
282
|
Some tips for programmable patterns:
|
|
256
283
|
|
|
257
|
-
- Precompute fixed waveform expressions
|
|
258
|
-
|
|
259
|
-
-
|
|
260
|
-
|
|
284
|
+
- Precompute fixed waveform expressions, such as `fire = valid & ready`, outside the handler so they are not rebuilt every cycle.
|
|
285
|
+
- Start the handler with `if ctx.value(fire): ...` to test whether the current cycle starts a transaction, and `return None` otherwise.
|
|
286
|
+
- Use `ctx.try_consume(...)` for non-blocking polling or arbitration between candidate channels. For a linear burst, `ctx.consume(...)` is more direct.
|
|
287
|
+
- Add `timeout=<cycles>` only when a blocking step needs a wait bound. In `match()`, timeout becomes `MatchStatus.Timeout(...)`; in `collect()`, it raises `PatternError`.
|
|
261
288
|
|
|
262
289
|
---
|
|
263
290
|
|
|
264
|
-
##
|
|
291
|
+
## API reference
|
|
265
292
|
|
|
266
293
|
### Reader
|
|
267
294
|
|
|
@@ -270,22 +297,35 @@ Some tips for programmable patterns:
|
|
|
270
297
|
| `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`). |
|
|
271
298
|
| `reader.load_waveform(signal, clock, ...)` | Load one signal sampled on every clock edge. Returns `Waveform`. |
|
|
272
299
|
| `reader.load_unknown_mask(signal, clock, ...)` | **Experimental.** Load X/Z bit presence as an unsigned mask `Waveform`. |
|
|
273
|
-
| `reader.load_matched_waveforms(
|
|
274
|
-
| `reader.load_matched_unknown_masks(
|
|
300
|
+
| `reader.load_matched_waveforms(signal_path, clock_path, ...)` | Batch-load matching signals. Returns `dict[CaptureKey, Waveform]`. |
|
|
301
|
+
| `reader.load_matched_unknown_masks(signal_path, clock_path, ...)` | **Experimental.** Batch-load X/Z masks for matched signals. Returns `dict[CaptureKey, Waveform]`. |
|
|
275
302
|
| `reader.eval(expr, clock, mode='single'\|'zip', ...)` | Evaluate an arithmetic expression with embedded signal paths. |
|
|
276
|
-
| `reader.get_matched_signals(
|
|
277
|
-
| `reader.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
|
284
|
-
|
|
285
|
-
|
|
|
286
|
-
|
|
|
287
|
-
|
|
|
288
|
-
|
|
|
303
|
+
| `reader.get_matched_signals(path)` | Resolve a query to `Signal` objects without loading data. |
|
|
304
|
+
| `reader.get_matched_scopes(path)` | Resolve a query to `Scope` objects. |
|
|
305
|
+
| `reader.top_scopes` | Immutable tuple of root `Scope` nodes. |
|
|
306
|
+
| `has_fsdb_support()` | Report whether the Verdi FSDB runtime is currently available. |
|
|
307
|
+
|
|
308
|
+
### Signal path patterns
|
|
309
|
+
|
|
310
|
+
| Syntax | Example | Effect | Capture in result key |
|
|
311
|
+
|--------|---------|--------|-----------------------|
|
|
312
|
+
| Plain name | `tb.dut.valid` | Exact-name match | None |
|
|
313
|
+
| `{a,b,c}` | `sig_{read,write}` | Enumerate named variants | `BraceCapture(path=..., groups=...)` |
|
|
314
|
+
| `{N..M}` | `fifo_{0..3}.ptr` | Integer range | `BraceCapture(path=..., groups=...)` |
|
|
315
|
+
| `{N..M..step}` | `lane_{0..6..2}` | Stepped range | `BraceCapture(path=..., groups=...)` |
|
|
316
|
+
| `/<regex>/` | `/([a-z]+)_valid/` | Canonical regex syntax with capture groups | `RegexCapture(path=..., groups=...)` |
|
|
317
|
+
| `@<regex>` | `@([a-z]+)_valid` | Legacy-compatible regex syntax | `RegexCapture(path=..., groups=...)` |
|
|
318
|
+
| `*` / `**` | `tb.*.valid` / `tb.**.valid` | Single-level / recursive wildcard | `WildcardCapture(path=...)` |
|
|
319
|
+
| `$ModName` | `tb.$fifo_unit.ptr` | Match a direct-child scope by module/definition name (FSDB only) | `ExactCapture(path=..., definition=...)` |
|
|
320
|
+
| `$$ModName` | `tb.$$fifo_unit.ptr` | Match any-depth descendant scope by module/definition name (FSDB only) | `ExactCapture(path=..., definition=...)` |
|
|
321
|
+
|
|
322
|
+
`$` and `$$` are path-step modifiers: they can combine with exact names, brace
|
|
323
|
+
expansion, regex, and a trailing range selector in the same query.
|
|
324
|
+
For example: `tb.$/fifo_(in|out)/.data_{0..3}[7:0]`.
|
|
325
|
+
|
|
326
|
+
Matched-reader and hierarchy query APIs return dictionaries keyed by
|
|
327
|
+
`CaptureKey = tuple[Capture, ...]`. Exact-name components are omitted, so a
|
|
328
|
+
fully exact query uses key `()`.
|
|
289
329
|
|
|
290
330
|
---
|
|
291
331
|
|
|
@@ -311,7 +351,8 @@ A `Waveform` wraps three parallel numpy arrays (`.value`, `.clock`, `.time`). Al
|
|
|
311
351
|
| Method | Description |
|
|
312
352
|
|--------|-------------|
|
|
313
353
|
| `wave.map(fn, width, signed)` | Element-wise transform |
|
|
314
|
-
| `wave.unique_consecutive()` | Remove consecutive
|
|
354
|
+
| `wave.unique_consecutive()` | Remove consecutive duplicate values |
|
|
355
|
+
| `wave.compress()` | Compact a waveform while preserving value changes and the final sample |
|
|
315
356
|
| `wave.downsample(chunk, fn)` | Aggregate into chunks |
|
|
316
357
|
| `wave.as_signed()` / `wave.as_unsigned()` | Reinterpret signedness |
|
|
317
358
|
|
|
@@ -358,28 +399,25 @@ changed = wave != wave.back(3)
|
|
|
358
399
|
|
|
359
400
|
| API | Description |
|
|
360
401
|
|-----|-------------|
|
|
361
|
-
| `Pattern(
|
|
362
|
-
| `
|
|
363
|
-
|
|
|
364
|
-
| `.collect(start_cycle=None, end_cycle=None)` | Programmable Pattern only. Collect each non-`None` Python return value. |
|
|
402
|
+
| `Pattern()` | Create a declarative Pattern. Add steps with builder methods; execution options live on `match()` / `collect()`. |
|
|
403
|
+
| `match(pattern_or_body, *, axis=None, timeout=None, timeout_message=None, start_cycle=None, end_cycle=None)` | Run a declarative Pattern or programmable check body and return `MatchRecords`. Check bodies return `ctx.OK` or `None`. |
|
|
404
|
+
| `collect(body, *, axis=None, timeout=None, timeout_message=None, start_cycle=None, end_cycle=None)` | Run a programmable extraction body and collect each non-`None` Python return value. |
|
|
365
405
|
|
|
366
406
|
**Declarative Steps**
|
|
367
407
|
|
|
368
408
|
| Method | Description |
|
|
369
409
|
|--------|-------------|
|
|
370
|
-
| `.wait(cond, *, require=None)` | Block until `cond` is True without consuming the event. Resumes in the same cycle when already true; use `.delay(1)` for next-cycle behavior. `require` is checked each waiting cycle (failure → `
|
|
371
|
-
| `.consume(cond, channel, *, require=None)` | Block until `cond` is True and this instance can exclusively consume from `channel`. Resumes in the same cycle on success. Use this for
|
|
372
|
-
| `.delay(n, *, require=None)` | Advance `n` cycles. `delay(0)` is a no-op. `require` must hold every cycle. |
|
|
410
|
+
| `.wait(cond, *, require=None, require_message=None)` | Block until `cond` is True without consuming the event. Resumes in the same cycle when already true; use `.delay(1)` for next-cycle behavior. `require` is checked each waiting cycle (failure → `MatchStatus.RequireViolated`). |
|
|
411
|
+
| `.consume(cond, channel, *, require=None, require_message=None)` | Block until `cond` is True and this instance can exclusively consume from `channel`. Resumes in the same cycle on success. Use this for request/response pairing and per-key routing. |
|
|
412
|
+
| `.delay(n, *, require=None, require_message=None)` | Advance `n` cycles. `delay(0)` is a no-op. `require` must hold every cycle. |
|
|
373
413
|
| `.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. |
|
|
374
|
-
| `.require(cond)` | Assert condition; fail with `
|
|
414
|
+
| `.require(cond)` | Assert condition; fail with `MatchStatus.RequireViolated` if False. |
|
|
375
415
|
| `.loop(body, *, until=None, when=None)` | `until`: do-while (exit when True after body). `when`: while (exit when False before body). |
|
|
376
416
|
| `.repeat(body, n)` | Execute body exactly `n` times. `n` may be a callable. |
|
|
377
417
|
| `.branch(cond, true_body, false_body)` | Conditional branch. |
|
|
378
|
-
| `.timeout(max_cycles)` | Terminate unfinished instances with `TIMEOUT`. |
|
|
379
418
|
|
|
380
419
|
The same time and ownership operations are available inside Programmable
|
|
381
|
-
|
|
382
|
-
`await ctx.delay(...)`.
|
|
420
|
+
patterns as `ctx.wait(...)`, `ctx.consume(...)`, and `ctx.delay(...)`.
|
|
383
421
|
|
|
384
422
|
**Programmable Context**
|
|
385
423
|
|
|
@@ -388,38 +426,40 @@ Patterns as `await ctx.wait(...)`, `await ctx.consume(...)`, and
|
|
|
388
426
|
| `ctx.value(waveform, offset=0)` | Read a scalar value at the current sample plus optional offset. |
|
|
389
427
|
| `ctx.cycle(waveform, offset=0)` | Read the cycle number at the current sample plus optional offset. |
|
|
390
428
|
| `ctx.time(waveform, offset=0)` | Read the timestamp at the current sample plus optional offset. |
|
|
391
|
-
| `
|
|
392
|
-
| `
|
|
393
|
-
| `
|
|
394
|
-
| `ctx.
|
|
395
|
-
| `ctx.
|
|
429
|
+
| `ctx.wait(cond, require=None, require_message=None)` | Observe cycles until `cond` is true; does not consume the event. |
|
|
430
|
+
| `ctx.consume(cond, channel, require=None, require_message=None)` | Wait for `cond` and exclusively consume from `channel`. |
|
|
431
|
+
| `ctx.try_consume(cond, channel)` | Poll `channel` without blocking. Returns `True` only when both the condition and channel are available. |
|
|
432
|
+
| `ctx.delay(n, require=None, require_message=None)` | Advance `n` cycles. |
|
|
433
|
+
| `ctx.capture(name, value, mode='last')` | Record a capture for programmable `match()`. |
|
|
434
|
+
| `ctx.OK` | Return from programmable `match()` to record a successful match. |
|
|
396
435
|
|
|
397
436
|
**Dynamic callbacks**
|
|
398
437
|
|
|
399
|
-
|
|
400
|
-
|
|
438
|
+
Callback arguments depend on whether the callback is used in the declarative API or the programmable API:
|
|
439
|
+
|
|
440
|
+
- In declarative APIs such as `Pattern().wait(...)` and `Pattern().consume(...)`, callbacks receive `(index, captures)`.
|
|
441
|
+
- In programmable functions, callbacks passed to methods such as `ctx.wait(...)` and `ctx.consume(...)` take no arguments. If they need the current index, captures, or signal values, close over `ctx`.
|
|
401
442
|
|
|
402
443
|
**Channels and consume vs. wait**
|
|
403
444
|
|
|
404
|
-
|
|
405
|
-
`
|
|
406
|
-
|
|
407
|
-
per cycle, in FIFO order.
|
|
445
|
+
`wait()` is observational: every matching instance can see the same event.
|
|
446
|
+
`consume()` adds ownership: only one instance can claim a given `(channel, cycle)`.
|
|
447
|
+
On the same channel, matches with earlier start cycles claim available events first.
|
|
408
448
|
|
|
409
|
-
A `Channel` is
|
|
410
|
-
hashable key, or a dynamic callback to `consume(..., channel=...)`. All
|
|
411
|
-
instances sharing the same channel key
|
|
449
|
+
A `Channel` is an identity token for consume ownership. Pass a `Channel` object,
|
|
450
|
+
a hashable key, or a dynamic callback to `consume(..., channel=...)`. All
|
|
451
|
+
instances sharing the same channel key compete for the same logical channel.
|
|
412
452
|
|
|
413
453
|
```python
|
|
414
454
|
from collections import defaultdict
|
|
415
|
-
from wavekit import Channel, Pattern
|
|
455
|
+
from wavekit.pattern import Channel, Pattern, match
|
|
416
456
|
|
|
417
|
-
# Multi-bank cache: each bank has its own response port, so two banks
|
|
418
|
-
#
|
|
419
|
-
#
|
|
457
|
+
# Multi-bank cache: each bank has its own response port, so two banks can return
|
|
458
|
+
# data in the same cycle. Per-bank channels let independent requests consume
|
|
459
|
+
# independent response streams.
|
|
420
460
|
banks = defaultdict(Channel)
|
|
421
461
|
|
|
422
|
-
|
|
462
|
+
pattern = (
|
|
423
463
|
Pattern()
|
|
424
464
|
.wait(req_valid)
|
|
425
465
|
.capture('bank', req_addr & 1)
|
|
@@ -429,27 +469,30 @@ result = (
|
|
|
429
469
|
)
|
|
430
470
|
.capture('rdata',
|
|
431
471
|
lambda i, cap: bank_data[cap['bank']].value[i])
|
|
432
|
-
.match()
|
|
433
472
|
)
|
|
473
|
+
result = match(pattern)
|
|
434
474
|
```
|
|
435
475
|
|
|
436
|
-
**`
|
|
476
|
+
**`MatchRecords`**
|
|
437
477
|
|
|
438
478
|
| Field | Description |
|
|
439
479
|
|-------|-------------|
|
|
440
|
-
| `.start` / `.end` |
|
|
441
|
-
| `.duration` | `end - start + 1` cycles. |
|
|
442
|
-
| `.status` | `MatchStatus.OK`, `
|
|
480
|
+
| `.start` / `.end` | Point Waveforms. `.value` is the waveform-array sample index, `.clock` is the absolute cycle, and `.time` is the simulation timestamp. End is inclusive. |
|
|
481
|
+
| `.duration` | `end.value - start.value + 1` sampled cycles. |
|
|
482
|
+
| `.status` | `MatchStatus.OK()`, `MatchStatus.Timeout(...)`, or `MatchStatus.RequireViolated(...)`. |
|
|
443
483
|
| `.captures` | `dict[str, Waveform]` of captured values. |
|
|
444
|
-
| `.ok` | Boolean Waveform where `status == MatchStatus.OK`. |
|
|
445
|
-
| `.failed` | Boolean Waveform where `status != MatchStatus.OK`. |
|
|
484
|
+
| `.ok` | Boolean Waveform where `status == MatchStatus.OK()`. |
|
|
485
|
+
| `.failed` | Boolean Waveform where `status != MatchStatus.OK()`. |
|
|
446
486
|
| `.filter_ok()` | Return only `OK` matches. |
|
|
447
|
-
| `.filter_status(
|
|
487
|
+
| `.filter_status(status_class)` | Return only matches with the given status class, such as `MatchStatus.Timeout`. |
|
|
448
488
|
| `.filter_failed()` | Return only non-OK matches. |
|
|
449
489
|
|
|
490
|
+
`MatchRecords[i]` returns a single `MatchRecord`, and slices return another
|
|
491
|
+
`MatchRecords` batch.
|
|
492
|
+
|
|
450
493
|
---
|
|
451
494
|
|
|
452
|
-
##
|
|
495
|
+
## Development
|
|
453
496
|
|
|
454
497
|
This project uses [Poetry](https://python-poetry.org/) for dependency management and packaging.
|
|
455
498
|
|
|
@@ -497,9 +540,9 @@ poetry run ruff format .
|
|
|
497
540
|
poetry run mypy .
|
|
498
541
|
```
|
|
499
542
|
|
|
500
|
-
##
|
|
543
|
+
## Contributing
|
|
501
544
|
|
|
502
|
-
|
|
545
|
+
Issues and pull requests are welcome. Before sending a pull request, run the tests and format checks:
|
|
503
546
|
|
|
504
547
|
```bash
|
|
505
548
|
poetry run pytest
|
|
@@ -507,7 +550,7 @@ poetry run ruff check .
|
|
|
507
550
|
poetry run ruff format --check .
|
|
508
551
|
```
|
|
509
552
|
|
|
510
|
-
##
|
|
553
|
+
## License
|
|
511
554
|
|
|
512
555
|
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
|
|
513
556
|
|