wavekit 0.3.0__tar.gz → 0.5.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.
Files changed (29) hide show
  1. wavekit-0.5.0/PKG-INFO +350 -0
  2. wavekit-0.5.0/README.md +328 -0
  3. {wavekit-0.3.0 → wavekit-0.5.0}/pyproject.toml +1 -1
  4. {wavekit-0.3.0 → wavekit-0.5.0}/src/wavekit/__init__.py +15 -1
  5. wavekit-0.5.0/src/wavekit/pattern/__init__.py +8 -0
  6. wavekit-0.5.0/src/wavekit/pattern/dsl.py +183 -0
  7. wavekit-0.5.0/src/wavekit/pattern/engine.py +438 -0
  8. wavekit-0.5.0/src/wavekit/pattern/instance.py +28 -0
  9. wavekit-0.5.0/src/wavekit/pattern/result.py +79 -0
  10. wavekit-0.5.0/src/wavekit/pattern/steps.py +142 -0
  11. wavekit-0.5.0/src/wavekit/readers/base.py +537 -0
  12. {wavekit-0.3.0 → wavekit-0.5.0}/src/wavekit/readers/fsdb/reader.py +77 -39
  13. {wavekit-0.3.0 → wavekit-0.5.0}/src/wavekit/readers/value_change.pyx +12 -9
  14. wavekit-0.5.0/src/wavekit/readers/vcd/reader.py +189 -0
  15. wavekit-0.5.0/src/wavekit/scope.py +277 -0
  16. wavekit-0.5.0/src/wavekit/signal.py +49 -0
  17. {wavekit-0.3.0 → wavekit-0.5.0}/src/wavekit/waveform.py +468 -23
  18. wavekit-0.3.0/PKG-INFO +0 -230
  19. wavekit-0.3.0/README.md +0 -208
  20. wavekit-0.3.0/src/wavekit/readers/base.py +0 -281
  21. wavekit-0.3.0/src/wavekit/readers/vcd/reader.py +0 -129
  22. wavekit-0.3.0/src/wavekit/scope.py +0 -164
  23. wavekit-0.3.0/src/wavekit/signal.py +0 -35
  24. {wavekit-0.3.0 → wavekit-0.5.0}/LICENSE +0 -0
  25. {wavekit-0.3.0 → wavekit-0.5.0}/build.py +0 -0
  26. {wavekit-0.3.0 → wavekit-0.5.0}/src/wavekit/readers/expr_parser.py +0 -0
  27. {wavekit-0.3.0 → wavekit-0.5.0}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
  28. {wavekit-0.3.0 → wavekit-0.5.0}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +0 -0
  29. {wavekit-0.3.0 → wavekit-0.5.0}/src/wavekit/readers/pattern_parser.py +0 -0
wavekit-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,350 @@
1
+ Metadata-Version: 2.4
2
+ Name: wavekit
3
+ Version: 0.5.0
4
+ Summary: a fundamental package for digital circuit waveform analysis
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: numpy (>=2.0.0,<3.0.0)
17
+ Requires-Dist: pytest (>=8.2.2,<9.0.0)
18
+ Requires-Dist: vcdvcd (>=2.3.5,<3.0.0)
19
+ Project-URL: Repository, https://github.com/cxzzzz/wavekit
20
+ Description-Content-Type: text/markdown
21
+
22
+ # wavekit
23
+
24
+ [![CI](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml/badge.svg)](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
25
+ [![PyPI version](https://img.shields.io/pypi/v/wavekit.svg)](https://pypi.org/project/wavekit/)
26
+ [![Python Versions](https://img.shields.io/pypi/pyversions/wavekit.svg)](https://pypi.org/project/wavekit/)
27
+ [![License](https://img.shields.io/github/license/cxzzzz/wavekit.svg)](LICENSE)
28
+
29
+ **Wavekit** is a fundamental Python library for digital waveform analysis. By seamlessly converting VCD and FSDB data into Numpy arrays, it empowers engineers to perform high-performance signal processing, protocol analysis, and automated verification with ease.
30
+
31
+ ## ✨ Features
32
+
33
+ - **High Performance & Easy Loading**: Cython-optimized VCD/FSDB parsers with Numpy-backed storage for speed and memory efficiency, plus flexible batch signal extraction via brace expansion, integer ranges, and regular expressions.
34
+ - **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
+ - **Pattern Matching**: NFA-based temporal pattern engine that scans waveforms in a single pass to extract protocol transactions, measure latencies, and detect timing violations.
36
+
37
+ ## 📦 Installation
38
+
39
+ ```bash
40
+ pip install wavekit
41
+ ```
42
+
43
+ **Note**: To read FSDB files, ensure the `VERDI_HOME` environment variable is set before installation.
44
+
45
+ ## 🚀 Quick Start
46
+
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.
48
+
49
+ ### 1. Batch Signal Extraction
50
+
51
+ Use brace expansion or regular expressions to load multiple related signals in one call.
52
+
53
+ ```python
54
+ from wavekit import VcdReader
55
+
56
+ with VcdReader("jtag.vcd") as f:
57
+ # Brace expansion: load J_state and J_next in one call
58
+ # Returns: { ('state',): Waveform, ('next',): Waveform }
59
+ waves = f.load_matched_waveforms(
60
+ "tb.u0.J_{state,next}[3:0]",
61
+ clock_pattern="tb.tck",
62
+ )
63
+
64
+ # Regex mode (@ prefix): capture groups become dict keys
65
+ waves = f.load_matched_waveforms(
66
+ r"tb.u0.@J_([a-z]+)",
67
+ clock_pattern="tb.tck",
68
+ )
69
+ ```
70
+
71
+ ---
72
+
73
+ ### 2. Signal Analysis
74
+
75
+ Waveforms support Numpy-style arithmetic, masking, and edge detection out of the box.
76
+
77
+ ```python
78
+ import numpy as np
79
+ from wavekit import VcdReader
80
+
81
+ with VcdReader("fifo_tb.vcd") as f:
82
+ clock = "fifo_tb.clk"
83
+ depth = 8
84
+
85
+ w_ptr = f.load_waveform("fifo_tb.s_fifo.w_ptr[2:0]", clock=clock)
86
+ r_ptr = f.load_waveform("fifo_tb.s_fifo.r_ptr[2:0]", clock=clock)
87
+ wr_en = f.load_waveform("fifo_tb.s_fifo.wr_en", clock=clock)
88
+
89
+ occupancy = (w_ptr + depth - r_ptr) % depth
90
+ print(f"Average occupancy: {np.mean(occupancy.value):.2f}")
91
+
92
+ # Filter to cycles where a write is active
93
+ write_occ = occupancy.mask(wr_en == 1)
94
+
95
+ # Detect write bursts
96
+ burst_cycles = wr_en.rising_edge()
97
+ ```
98
+
99
+ ---
100
+
101
+ ### 3. Expression Evaluation
102
+
103
+ Compute waveform expressions directly from signal path strings without loading each signal manually.
104
+
105
+ ```python
106
+ from wavekit import VcdReader
107
+
108
+ with VcdReader("fifo_tb.vcd") as f:
109
+ # Single mode: paths must each match exactly one signal
110
+ occupancy = f.eval(
111
+ "fifo_tb.s_fifo.w_ptr[2:0] - fifo_tb.s_fifo.r_ptr[2:0]",
112
+ clock="fifo_tb.clk",
113
+ )
114
+
115
+ # Zip mode: brace patterns expand per key, evaluated once per match
116
+ # Returns: { (0,): Waveform, (1,): Waveform, (2,): Waveform, (3,): Waveform }
117
+ occupancies = f.eval(
118
+ "tb.fifo_{0..3}.w_ptr[2:0] - tb.fifo_{0..3}.r_ptr[2:0]",
119
+ clock="tb.clk",
120
+ mode="zip",
121
+ )
122
+ ```
123
+
124
+ ---
125
+
126
+ ### 4. Pattern Matching
127
+
128
+ Describe a temporal sequence of events; the engine finds all matching transactions in one pass.
129
+
130
+ **AXI-lite Read Latency**
131
+
132
+ ```python
133
+ from wavekit import VcdReader, Pattern
134
+
135
+ with VcdReader("axi_tb.vcd") as f:
136
+ clk = "tb.clk"
137
+ arvalid = f.load_waveform("tb.dut.arvalid", clock=clk)
138
+ arready = f.load_waveform("tb.dut.arready", clock=clk)
139
+ rvalid = f.load_waveform("tb.dut.rvalid", clock=clk)
140
+ rready = f.load_waveform("tb.dut.rready", clock=clk)
141
+ rdata = f.load_waveform("tb.dut.rdata[31:0]", clock=clk)
142
+
143
+ result = (
144
+ Pattern()
145
+ .wait(arvalid & arready) # AR handshake → start
146
+ .wait(rvalid & rready) # R handshake → end
147
+ .capture("rdata", rdata)
148
+ .timeout(256)
149
+ .match()
150
+ )
151
+
152
+ valid = result.filter_valid()
153
+ print(f"Read latencies (cycles): {valid.duration.value}")
154
+ print(f"Read data: {valid.captures['rdata'].value}")
155
+ ```
156
+
157
+ **AXI Write Burst (multi-beat)**
158
+
159
+ ```python
160
+ beat = Pattern().wait(wvalid & wready).capture("beats[]", wdata)
161
+
162
+ result = (
163
+ Pattern()
164
+ .wait(awvalid & awready) # AW handshake → burst start
165
+ .loop(beat, until=wlast) # collect beats until wlast
166
+ .timeout(512)
167
+ .match()
168
+ )
169
+
170
+ for i, inst in enumerate(result.filter_valid()):
171
+ print(f"Burst {i}: {len(inst.captures['beats'])} beats")
172
+ ```
173
+
174
+ **Stall Detection**
175
+
176
+ ```python
177
+ stall = valid & (ready == 0)
178
+
179
+ result = (
180
+ Pattern()
181
+ .wait(stall.rising_edge()) # stall begins
182
+ .loop(Pattern().delay(1), when=stall) # wait while stalling
183
+ .match()
184
+ )
185
+
186
+ stalls = result.filter_valid()
187
+ print(f"Stall durations: {stalls.duration.value} cycles")
188
+ ```
189
+
190
+ ---
191
+
192
+ ## 📖 API Reference
193
+
194
+ ### Reader
195
+
196
+ | Method | Description |
197
+ |--------|-------------|
198
+ | `VcdReader(file)` / `FsdbReader(file)` | Open a waveform file. Use as a context manager. `FsdbReader` requires `VERDI_HOME`. |
199
+ | `reader.load_waveform(signal, clock, ...)` | Load one signal sampled on every clock edge. Returns `Waveform`. |
200
+ | `reader.load_matched_waveforms(pattern, clock_pattern, ...)` | Batch-load signals matching a brace/regex pattern. Returns `dict[tuple, Waveform]`. |
201
+ | `reader.eval(expr, clock, mode='single'\|'zip', ...)` | Evaluate an arithmetic expression with embedded signal paths. |
202
+ | `reader.get_matched_signals(pattern)` | Resolve a pattern to signal paths without loading data. |
203
+ | `reader.top_scope_list()` | Return root `Scope` nodes of the signal hierarchy. |
204
+
205
+ **Pattern syntax** used in signal paths:
206
+
207
+ | Syntax | Example | Effect |
208
+ |--------|---------|--------|
209
+ | `{a,b,c}` | `sig_{read,write}` | Enumerate named variants |
210
+ | `{N..M}` | `fifo_{0..3}.ptr` | Integer range |
211
+ | `{N..M..step}` | `lane_{0..6..2}` | Stepped range |
212
+ | `@<regex>` | `@([a-z]+)_valid` | Regex with capture groups |
213
+ | `$ModName` | `tb.$fifo_unit.ptr` | Match a direct-child scope by module/definition name (FSDB only) |
214
+ | `$$ModName` | `tb.$$fifo_unit.ptr` | Match any-depth descendant scope by module/definition name (FSDB only) |
215
+
216
+ ---
217
+
218
+ ### Waveform
219
+
220
+ A `Waveform` wraps three parallel numpy arrays (`.value`, `.clock`, `.time`). All operations return a new `Waveform`.
221
+
222
+ **Arithmetic & comparison**: `+`, `-`, `*`, `//`, `%`, `**`, `/`, `&`, `|`, `^`, `~`, `==`, `!=`, `<<`, `>>`
223
+
224
+ **Filtering & slicing**
225
+
226
+ | Method | Description |
227
+ |--------|-------------|
228
+ | `wave.mask(mask)` | Keep samples where a boolean Waveform or array is True |
229
+ | `wave.filter(fn)` | Keep samples where `fn(value)` is True |
230
+ | `wave.cycle_slice(begin, end)` | Trim to clock cycle range `[begin, end)` |
231
+ | `wave.time_slice(begin, end)` | Trim to simulation time range |
232
+ | `wave.slice(begin_idx, end_idx)` | Trim by array index |
233
+ | `wave.take(indices)` | Select samples at given indices |
234
+
235
+ **Transformation**
236
+
237
+ | Method | Description |
238
+ |--------|-------------|
239
+ | `wave.map(fn, width, signed)` | Element-wise transform |
240
+ | `wave.unique_consecutive()` | Remove consecutive duplicates |
241
+ | `wave.downsample(chunk, fn)` | Aggregate into chunks |
242
+ | `wave.as_signed()` / `wave.as_unsigned()` | Reinterpret signedness |
243
+
244
+ **Bit manipulation**
245
+
246
+ | Method / Syntax | Description |
247
+ |-----------------|-------------|
248
+ | `wave[high:low]` | Extract bit field (Verilog convention, returns unsigned) |
249
+ | `wave[n]` | Extract single bit |
250
+ | `wave.split_bits(n)` | Split into n-bit groups (LSB first) |
251
+ | `Waveform.concatenate([w0, w1, ...])` | Concatenate (w0 = LSB) |
252
+ | `wave.bit_count()` | Population count |
253
+
254
+ **Edge detection** (1-bit only)
255
+
256
+ | Method | Description |
257
+ |--------|-------------|
258
+ | `wave.rising_edge()` | True at 0→1 transitions |
259
+ | `wave.falling_edge()` | True at 1→0 transitions |
260
+
261
+ ---
262
+
263
+ ### Pattern
264
+
265
+ | Method | Description |
266
+ |--------|-------------|
267
+ | `.wait(cond, guard=None, channel=None)` | Block until `cond` is True. `guard` is checked each waiting cycle. `channel` enforces FIFO ordering among concurrent instances. |
268
+ | `.delay(n, guard=None)` | Advance `n` cycles. `delay(0)` is a no-op. |
269
+ | `.capture(name, signal)` | Record signal value at current cycle. `name[]` appends to a list. |
270
+ | `.require(cond)` | Assert condition; fail with `REQUIRE_VIOLATED` if False. |
271
+ | `.loop(body, *, until=None, when=None)` | `until`: do-while (exit when True after body). `when`: while (exit when False before body). |
272
+ | `.repeat(body, n)` | Execute body exactly `n` times. `n` may be a callable. |
273
+ | `.branch(cond, true_body, false_body)` | Conditional branch. |
274
+ | `.timeout(max_cycles)` | Terminate unfinished instances with `TIMEOUT`. |
275
+ | `.match(start_cycle=None, end_cycle=None)` | Run the engine; return `MatchResult`. |
276
+
277
+ **`MatchResult`**
278
+
279
+ | Field | Description |
280
+ |-------|-------------|
281
+ | `.start` / `.end` | Start and end cycle of each match (both inclusive). |
282
+ | `.duration` | `end - start + 1` cycles. |
283
+ | `.status` | `MatchStatus.OK`, `TIMEOUT`, or `REQUIRE_VIOLATED`. |
284
+ | `.captures` | `dict[str, Waveform]` of captured values. |
285
+ | `.filter_valid()` | Return only `OK` matches. |
286
+
287
+ ---
288
+
289
+ ## 🛠️ Development
290
+
291
+ This project uses [Poetry](https://python-poetry.org/) for dependency management and packaging.
292
+
293
+ ### Setup
294
+
295
+ ```bash
296
+ git clone https://github.com/cxzzzz/wavekit.git
297
+ cd wavekit
298
+ poetry install
299
+ ```
300
+
301
+ ### Testing
302
+
303
+ Tests are located in the `tests/` directory and run with [pytest](https://pytest.org/).
304
+
305
+ ```bash
306
+ # Run all tests
307
+ poetry run pytest
308
+
309
+ # Run a specific test file
310
+ poetry run pytest tests/test_pattern.py
311
+
312
+ # Run with verbose output
313
+ poetry run pytest -v
314
+ ```
315
+
316
+ ### Linting & Formatting
317
+
318
+ This project uses [Ruff](https://github.com/astral-sh/ruff) for linting and formatting.
319
+
320
+ ```bash
321
+ # Check for lint errors
322
+ poetry run ruff check .
323
+
324
+ # Check formatting (no changes)
325
+ poetry run ruff format --check .
326
+
327
+ # Auto-fix formatting
328
+ poetry run ruff format .
329
+ ```
330
+
331
+ ### Type Checking
332
+
333
+ ```bash
334
+ poetry run mypy .
335
+ ```
336
+
337
+ ## 🤝 Contributing
338
+
339
+ Contributions are welcome! Please open an issue to discuss a bug or feature request before submitting a pull request. When contributing code, make sure all tests pass and the linter reports no errors:
340
+
341
+ ```bash
342
+ poetry run pytest
343
+ poetry run ruff check .
344
+ poetry run ruff format --check .
345
+ ```
346
+
347
+ ## 📄 License
348
+
349
+ This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
350
+
@@ -0,0 +1,328 @@
1
+ # wavekit
2
+
3
+ [![CI](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml/badge.svg)](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/wavekit.svg)](https://pypi.org/project/wavekit/)
5
+ [![Python Versions](https://img.shields.io/pypi/pyversions/wavekit.svg)](https://pypi.org/project/wavekit/)
6
+ [![License](https://img.shields.io/github/license/cxzzzz/wavekit.svg)](LICENSE)
7
+
8
+ **Wavekit** is a fundamental Python library for digital waveform analysis. By seamlessly converting VCD and FSDB data into Numpy arrays, it empowers engineers to perform high-performance signal processing, protocol analysis, and automated verification with ease.
9
+
10
+ ## ✨ Features
11
+
12
+ - **High Performance & Easy Loading**: Cython-optimized VCD/FSDB parsers with Numpy-backed storage for speed and memory efficiency, plus flexible batch signal extraction via brace expansion, integer ranges, and regular expressions.
13
+ - **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
+ - **Pattern Matching**: NFA-based temporal pattern engine that scans waveforms in a single pass to extract protocol transactions, measure latencies, and detect timing violations.
15
+
16
+ ## 📦 Installation
17
+
18
+ ```bash
19
+ pip install wavekit
20
+ ```
21
+
22
+ **Note**: To read FSDB files, ensure the `VERDI_HOME` environment variable is set before installation.
23
+
24
+ ## 🚀 Quick Start
25
+
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.
27
+
28
+ ### 1. Batch Signal Extraction
29
+
30
+ Use brace expansion or regular expressions to load multiple related signals in one call.
31
+
32
+ ```python
33
+ from wavekit import VcdReader
34
+
35
+ with VcdReader("jtag.vcd") as f:
36
+ # Brace expansion: load J_state and J_next in one call
37
+ # Returns: { ('state',): Waveform, ('next',): Waveform }
38
+ waves = f.load_matched_waveforms(
39
+ "tb.u0.J_{state,next}[3:0]",
40
+ clock_pattern="tb.tck",
41
+ )
42
+
43
+ # Regex mode (@ prefix): capture groups become dict keys
44
+ waves = f.load_matched_waveforms(
45
+ r"tb.u0.@J_([a-z]+)",
46
+ clock_pattern="tb.tck",
47
+ )
48
+ ```
49
+
50
+ ---
51
+
52
+ ### 2. Signal Analysis
53
+
54
+ Waveforms support Numpy-style arithmetic, masking, and edge detection out of the box.
55
+
56
+ ```python
57
+ import numpy as np
58
+ from wavekit import VcdReader
59
+
60
+ with VcdReader("fifo_tb.vcd") as f:
61
+ clock = "fifo_tb.clk"
62
+ depth = 8
63
+
64
+ w_ptr = f.load_waveform("fifo_tb.s_fifo.w_ptr[2:0]", clock=clock)
65
+ r_ptr = f.load_waveform("fifo_tb.s_fifo.r_ptr[2:0]", clock=clock)
66
+ wr_en = f.load_waveform("fifo_tb.s_fifo.wr_en", clock=clock)
67
+
68
+ occupancy = (w_ptr + depth - r_ptr) % depth
69
+ print(f"Average occupancy: {np.mean(occupancy.value):.2f}")
70
+
71
+ # Filter to cycles where a write is active
72
+ write_occ = occupancy.mask(wr_en == 1)
73
+
74
+ # Detect write bursts
75
+ burst_cycles = wr_en.rising_edge()
76
+ ```
77
+
78
+ ---
79
+
80
+ ### 3. Expression Evaluation
81
+
82
+ Compute waveform expressions directly from signal path strings without loading each signal manually.
83
+
84
+ ```python
85
+ from wavekit import VcdReader
86
+
87
+ with VcdReader("fifo_tb.vcd") as f:
88
+ # Single mode: paths must each match exactly one signal
89
+ occupancy = f.eval(
90
+ "fifo_tb.s_fifo.w_ptr[2:0] - fifo_tb.s_fifo.r_ptr[2:0]",
91
+ clock="fifo_tb.clk",
92
+ )
93
+
94
+ # Zip mode: brace patterns expand per key, evaluated once per match
95
+ # Returns: { (0,): Waveform, (1,): Waveform, (2,): Waveform, (3,): Waveform }
96
+ occupancies = f.eval(
97
+ "tb.fifo_{0..3}.w_ptr[2:0] - tb.fifo_{0..3}.r_ptr[2:0]",
98
+ clock="tb.clk",
99
+ mode="zip",
100
+ )
101
+ ```
102
+
103
+ ---
104
+
105
+ ### 4. Pattern Matching
106
+
107
+ Describe a temporal sequence of events; the engine finds all matching transactions in one pass.
108
+
109
+ **AXI-lite Read Latency**
110
+
111
+ ```python
112
+ from wavekit import VcdReader, Pattern
113
+
114
+ with VcdReader("axi_tb.vcd") as f:
115
+ clk = "tb.clk"
116
+ arvalid = f.load_waveform("tb.dut.arvalid", clock=clk)
117
+ arready = f.load_waveform("tb.dut.arready", clock=clk)
118
+ rvalid = f.load_waveform("tb.dut.rvalid", clock=clk)
119
+ rready = f.load_waveform("tb.dut.rready", clock=clk)
120
+ rdata = f.load_waveform("tb.dut.rdata[31:0]", clock=clk)
121
+
122
+ result = (
123
+ Pattern()
124
+ .wait(arvalid & arready) # AR handshake → start
125
+ .wait(rvalid & rready) # R handshake → end
126
+ .capture("rdata", rdata)
127
+ .timeout(256)
128
+ .match()
129
+ )
130
+
131
+ valid = result.filter_valid()
132
+ print(f"Read latencies (cycles): {valid.duration.value}")
133
+ print(f"Read data: {valid.captures['rdata'].value}")
134
+ ```
135
+
136
+ **AXI Write Burst (multi-beat)**
137
+
138
+ ```python
139
+ beat = Pattern().wait(wvalid & wready).capture("beats[]", wdata)
140
+
141
+ result = (
142
+ Pattern()
143
+ .wait(awvalid & awready) # AW handshake → burst start
144
+ .loop(beat, until=wlast) # collect beats until wlast
145
+ .timeout(512)
146
+ .match()
147
+ )
148
+
149
+ for i, inst in enumerate(result.filter_valid()):
150
+ print(f"Burst {i}: {len(inst.captures['beats'])} beats")
151
+ ```
152
+
153
+ **Stall Detection**
154
+
155
+ ```python
156
+ stall = valid & (ready == 0)
157
+
158
+ result = (
159
+ Pattern()
160
+ .wait(stall.rising_edge()) # stall begins
161
+ .loop(Pattern().delay(1), when=stall) # wait while stalling
162
+ .match()
163
+ )
164
+
165
+ stalls = result.filter_valid()
166
+ print(f"Stall durations: {stalls.duration.value} cycles")
167
+ ```
168
+
169
+ ---
170
+
171
+ ## 📖 API Reference
172
+
173
+ ### Reader
174
+
175
+ | Method | Description |
176
+ |--------|-------------|
177
+ | `VcdReader(file)` / `FsdbReader(file)` | Open a waveform file. Use as a context manager. `FsdbReader` requires `VERDI_HOME`. |
178
+ | `reader.load_waveform(signal, clock, ...)` | Load one signal sampled on every clock edge. Returns `Waveform`. |
179
+ | `reader.load_matched_waveforms(pattern, clock_pattern, ...)` | Batch-load signals matching a brace/regex pattern. Returns `dict[tuple, Waveform]`. |
180
+ | `reader.eval(expr, clock, mode='single'\|'zip', ...)` | Evaluate an arithmetic expression with embedded signal paths. |
181
+ | `reader.get_matched_signals(pattern)` | Resolve a pattern to signal paths without loading data. |
182
+ | `reader.top_scope_list()` | Return root `Scope` nodes of the signal hierarchy. |
183
+
184
+ **Pattern syntax** used in signal paths:
185
+
186
+ | Syntax | Example | Effect |
187
+ |--------|---------|--------|
188
+ | `{a,b,c}` | `sig_{read,write}` | Enumerate named variants |
189
+ | `{N..M}` | `fifo_{0..3}.ptr` | Integer range |
190
+ | `{N..M..step}` | `lane_{0..6..2}` | Stepped range |
191
+ | `@<regex>` | `@([a-z]+)_valid` | Regex with capture groups |
192
+ | `$ModName` | `tb.$fifo_unit.ptr` | Match a direct-child scope by module/definition name (FSDB only) |
193
+ | `$$ModName` | `tb.$$fifo_unit.ptr` | Match any-depth descendant scope by module/definition name (FSDB only) |
194
+
195
+ ---
196
+
197
+ ### Waveform
198
+
199
+ A `Waveform` wraps three parallel numpy arrays (`.value`, `.clock`, `.time`). All operations return a new `Waveform`.
200
+
201
+ **Arithmetic & comparison**: `+`, `-`, `*`, `//`, `%`, `**`, `/`, `&`, `|`, `^`, `~`, `==`, `!=`, `<<`, `>>`
202
+
203
+ **Filtering & slicing**
204
+
205
+ | Method | Description |
206
+ |--------|-------------|
207
+ | `wave.mask(mask)` | Keep samples where a boolean Waveform or array is True |
208
+ | `wave.filter(fn)` | Keep samples where `fn(value)` is True |
209
+ | `wave.cycle_slice(begin, end)` | Trim to clock cycle range `[begin, end)` |
210
+ | `wave.time_slice(begin, end)` | Trim to simulation time range |
211
+ | `wave.slice(begin_idx, end_idx)` | Trim by array index |
212
+ | `wave.take(indices)` | Select samples at given indices |
213
+
214
+ **Transformation**
215
+
216
+ | Method | Description |
217
+ |--------|-------------|
218
+ | `wave.map(fn, width, signed)` | Element-wise transform |
219
+ | `wave.unique_consecutive()` | Remove consecutive duplicates |
220
+ | `wave.downsample(chunk, fn)` | Aggregate into chunks |
221
+ | `wave.as_signed()` / `wave.as_unsigned()` | Reinterpret signedness |
222
+
223
+ **Bit manipulation**
224
+
225
+ | Method / Syntax | Description |
226
+ |-----------------|-------------|
227
+ | `wave[high:low]` | Extract bit field (Verilog convention, returns unsigned) |
228
+ | `wave[n]` | Extract single bit |
229
+ | `wave.split_bits(n)` | Split into n-bit groups (LSB first) |
230
+ | `Waveform.concatenate([w0, w1, ...])` | Concatenate (w0 = LSB) |
231
+ | `wave.bit_count()` | Population count |
232
+
233
+ **Edge detection** (1-bit only)
234
+
235
+ | Method | Description |
236
+ |--------|-------------|
237
+ | `wave.rising_edge()` | True at 0→1 transitions |
238
+ | `wave.falling_edge()` | True at 1→0 transitions |
239
+
240
+ ---
241
+
242
+ ### Pattern
243
+
244
+ | Method | Description |
245
+ |--------|-------------|
246
+ | `.wait(cond, guard=None, channel=None)` | Block until `cond` is True. `guard` is checked each waiting cycle. `channel` enforces FIFO ordering among concurrent instances. |
247
+ | `.delay(n, guard=None)` | Advance `n` cycles. `delay(0)` is a no-op. |
248
+ | `.capture(name, signal)` | Record signal value at current cycle. `name[]` appends to a list. |
249
+ | `.require(cond)` | Assert condition; fail with `REQUIRE_VIOLATED` if False. |
250
+ | `.loop(body, *, until=None, when=None)` | `until`: do-while (exit when True after body). `when`: while (exit when False before body). |
251
+ | `.repeat(body, n)` | Execute body exactly `n` times. `n` may be a callable. |
252
+ | `.branch(cond, true_body, false_body)` | Conditional branch. |
253
+ | `.timeout(max_cycles)` | Terminate unfinished instances with `TIMEOUT`. |
254
+ | `.match(start_cycle=None, end_cycle=None)` | Run the engine; return `MatchResult`. |
255
+
256
+ **`MatchResult`**
257
+
258
+ | Field | Description |
259
+ |-------|-------------|
260
+ | `.start` / `.end` | Start and end cycle of each match (both inclusive). |
261
+ | `.duration` | `end - start + 1` cycles. |
262
+ | `.status` | `MatchStatus.OK`, `TIMEOUT`, or `REQUIRE_VIOLATED`. |
263
+ | `.captures` | `dict[str, Waveform]` of captured values. |
264
+ | `.filter_valid()` | Return only `OK` matches. |
265
+
266
+ ---
267
+
268
+ ## 🛠️ Development
269
+
270
+ This project uses [Poetry](https://python-poetry.org/) for dependency management and packaging.
271
+
272
+ ### Setup
273
+
274
+ ```bash
275
+ git clone https://github.com/cxzzzz/wavekit.git
276
+ cd wavekit
277
+ poetry install
278
+ ```
279
+
280
+ ### Testing
281
+
282
+ Tests are located in the `tests/` directory and run with [pytest](https://pytest.org/).
283
+
284
+ ```bash
285
+ # Run all tests
286
+ poetry run pytest
287
+
288
+ # Run a specific test file
289
+ poetry run pytest tests/test_pattern.py
290
+
291
+ # Run with verbose output
292
+ poetry run pytest -v
293
+ ```
294
+
295
+ ### Linting & Formatting
296
+
297
+ This project uses [Ruff](https://github.com/astral-sh/ruff) for linting and formatting.
298
+
299
+ ```bash
300
+ # Check for lint errors
301
+ poetry run ruff check .
302
+
303
+ # Check formatting (no changes)
304
+ poetry run ruff format --check .
305
+
306
+ # Auto-fix formatting
307
+ poetry run ruff format .
308
+ ```
309
+
310
+ ### Type Checking
311
+
312
+ ```bash
313
+ poetry run mypy .
314
+ ```
315
+
316
+ ## 🤝 Contributing
317
+
318
+ Contributions are welcome! Please open an issue to discuss a bug or feature request before submitting a pull request. When contributing code, make sure all tests pass and the linter reports no errors:
319
+
320
+ ```bash
321
+ poetry run pytest
322
+ poetry run ruff check .
323
+ poetry run ruff format --check .
324
+ ```
325
+
326
+ ## 📄 License
327
+
328
+ This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "wavekit"
3
- version = "0.3.0"
3
+ version = "0.5.0"
4
4
  description = "a fundamental package for digital circuit waveform analysis"
5
5
  authors = ["cxzzzz <cxz19961010@outlook.com>"]
6
6
  readme = "README.md"