setVCD 0.2.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.
- setvcd-0.2.0/LICENSE +21 -0
- setvcd-0.2.0/PKG-INFO +337 -0
- setvcd-0.2.0/README.md +302 -0
- setvcd-0.2.0/pyproject.toml +119 -0
- setvcd-0.2.0/setVCD/__init__.py +51 -0
- setvcd-0.2.0/setVCD/exceptions.py +101 -0
- setvcd-0.2.0/setVCD/py.typed +2 -0
- setvcd-0.2.0/setVCD/setVCD.py +453 -0
- setvcd-0.2.0/setVCD/types.py +185 -0
- setvcd-0.2.0/setVCD.egg-info/PKG-INFO +337 -0
- setvcd-0.2.0/setVCD.egg-info/SOURCES.txt +14 -0
- setvcd-0.2.0/setVCD.egg-info/dependency_links.txt +1 -0
- setvcd-0.2.0/setVCD.egg-info/requires.txt +9 -0
- setvcd-0.2.0/setVCD.egg-info/top_level.txt +1 -0
- setvcd-0.2.0/setup.cfg +4 -0
- setvcd-0.2.0/tests/test_vcd2set.py +865 -0
setvcd-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 VCD2Set Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
setvcd-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: setVCD
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Convert VCD signals to sets of time points based on conditions
|
|
5
|
+
Author-email: Michail Rontionov <mrontionov@mront.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mrontio/setVCD
|
|
8
|
+
Project-URL: Repository, https://github.com/mrontio/setVCD
|
|
9
|
+
Project-URL: Issues, https://github.com/mrontio/setVCD/issues
|
|
10
|
+
Keywords: vcd,verilog,waveform,signal,testing,verification
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Software Development :: Testing
|
|
14
|
+
Classifier: Topic :: System :: Hardware
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: vcdvcd>=2.0.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
33
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# SetVCD
|
|
37
|
+
Programmatically inspect hardware VCD signals using a high-level functional interface.
|
|
38
|
+
|
|
39
|
+
Higher-order programming constructs and set operations are a natural fit for inspecting VCD signals, and this Python library allows you to easily specify, in text, what simulation timesteps matter to functional correctness.
|
|
40
|
+
|
|
41
|
+
## Motivating Example
|
|
42
|
+
Say you are debugging a streaming interface, you often only care about the values of the data at timesteps meeting the following condition:
|
|
43
|
+
|
|
44
|
+
$\text{Rising edge} \land \text{Reset is 0} \land \text{ready} \land \text{valid}$
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
You can filter through an individual signal with a filter function of this signature:
|
|
49
|
+
|
|
50
|
+
$(\text{Bits}, \text{Bits}, \text{Bits}) \rightarrow \text{Bool}$
|
|
51
|
+
|
|
52
|
+
with the left-hand tuple representing *values* at timestep $t$: $(t-1, t, t+1)$.
|
|
53
|
+
|
|
54
|
+
We then define our `get` method, which takes the name of the signal (as a String), a function with the above signature, and returns a set of *timesteps*:
|
|
55
|
+
|
|
56
|
+
$\texttt{get}: (\text{String}, ((\text{Bits}, \text{Bits}, \text{Bits}) \rightarrow \text{Bool})) \rightarrow \text{Set(Timestep)}$
|
|
57
|
+
|
|
58
|
+
As what is returned is a set, you can then use [set operations](https://en.wikipedia.org/wiki/Set_(mathematics)#Basic_operations) to manipulate them as needed, and finally extract the values from your desired signal using our `get_value` function:
|
|
59
|
+
|
|
60
|
+
$\texttt{get-value}: (\text{String}, \text{Set(Timestep)}) \rightarrow \text{List((Timestep, Bits))}$
|
|
61
|
+
|
|
62
|
+
Here's an example of finding the rising edges of the clock signal `TOP.clk` of our test wavefile `wave.vcd`:
|
|
63
|
+
```python
|
|
64
|
+
from setVCD import SetVCD
|
|
65
|
+
|
|
66
|
+
# Load VCD file
|
|
67
|
+
vcd_path = "./tests/fixtures/wave.vcd"
|
|
68
|
+
sv = SetVCD(vcd_path, clock="TOP.clk")
|
|
69
|
+
|
|
70
|
+
rising_edges = sv.get("TOP.clk", lambda tm1, t, tp1: tm1 == 0 and t == 1)
|
|
71
|
+
print(rising_edges)
|
|
72
|
+
# {34, 36, 38, 40, 42, 44, ...}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Because `rising_edges` is returned as a set, we can use set operations to combine it with other signals:
|
|
76
|
+
```python
|
|
77
|
+
# Get times when the reset signal is 0
|
|
78
|
+
reset_is_0 = sv.get("TOP.reset", lambda tm1, t, tp1: t == 0)
|
|
79
|
+
# Use set intersection to get valid clock updates.
|
|
80
|
+
clock_update = rising_edges & reset_is_0
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Finally, you can search the wavefile with a regex (e.g. "output"), and apply the same operations to it:
|
|
84
|
+
```python
|
|
85
|
+
# Find VCD signals relating to keyword "output"
|
|
86
|
+
sv.search("output")
|
|
87
|
+
|
|
88
|
+
# Get times when output_valid and output_ready are asserted.
|
|
89
|
+
out_valid = sv.get("TOP.Accelerator.io_output_valid", lambda tm1, t, tp1: t == 1)
|
|
90
|
+
out_ready = sv.get("TOP.Accelerator.io_output_ready", lambda tm1, t, tp1: t == 1)
|
|
91
|
+
|
|
92
|
+
# Get timesteps of valid outputs
|
|
93
|
+
valid_output_timesteps = rising_edges & reset0 & out_ready & out_valid
|
|
94
|
+
|
|
95
|
+
# Get the values of the Stream `value` signal (the data) at timesteps when it is valid
|
|
96
|
+
outputs = sv.get_values("TOP.Accelerator.io_output_payload_fragment_value_0[0:0]", valid_output_timesteps)
|
|
97
|
+
print(outputs)
|
|
98
|
+
# [(52, 0), (62, 0), (72, 1), ...] # Integer values
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Overview
|
|
102
|
+
|
|
103
|
+
SetVCD is a Python package for analyzing Verilog VCD files and extracting time points where specific signal conditions are met. It provides a simple, type-safe interface for working with simulation waveforms using set-based operations.
|
|
104
|
+
|
|
105
|
+
## Installation
|
|
106
|
+
The package is available in PyPI:
|
|
107
|
+
```bash
|
|
108
|
+
pip install setVCD
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
### Initialization
|
|
114
|
+
|
|
115
|
+
You can initialize SetVCD with either a filename or a vcdvcd object:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
import setVCD
|
|
119
|
+
from pathlib import Path
|
|
120
|
+
|
|
121
|
+
# From string filename
|
|
122
|
+
sv = SetVCD("simulation.vcd", clock="clk")
|
|
123
|
+
|
|
124
|
+
# From Path object
|
|
125
|
+
sv = SetVCD(Path("simulation.vcd"), clock="clk")
|
|
126
|
+
|
|
127
|
+
# From vcdvcd object
|
|
128
|
+
import vcdvcd
|
|
129
|
+
vcd = vcdvcd.VCDVCD("simulation.vcd")
|
|
130
|
+
sv = SetVCD(vcd, clock="clk")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The `clock` parameter must be the exact name of the clock signal in your VCD file (case-sensitive). This signal determines the time range for queries.
|
|
134
|
+
|
|
135
|
+
### Signal Conditions
|
|
136
|
+
|
|
137
|
+
The `signal_condition` callback receives three arguments representing the signal value at three consecutive time points:
|
|
138
|
+
|
|
139
|
+
- `sm1`: Signal value at time-1 (None at time 0 or if value is x/z)
|
|
140
|
+
- `s`: Signal value at current time (None if value is x/z)
|
|
141
|
+
- `sp1`: Signal value at time+1 (None at last time or if value is x/z)
|
|
142
|
+
|
|
143
|
+
Signal values are `Optional[int]`:
|
|
144
|
+
- Integers: Binary values converted to decimal (e.g., "1010" → 10)
|
|
145
|
+
- None: Represents x/z values or boundary conditions (t-1 at time 0, t+1 at last time)
|
|
146
|
+
|
|
147
|
+
The callback should return `True` to include that time point in the result set.
|
|
148
|
+
|
|
149
|
+
### Examples
|
|
150
|
+
|
|
151
|
+
#### Basic Signal Detection
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# Rising edge: 0 -> 1 transition
|
|
155
|
+
rising = sv.get("clk", lambda sm1, s, sp1: sm1 == 0 and s == 1)
|
|
156
|
+
|
|
157
|
+
# Falling edge: 1 -> 0 transition
|
|
158
|
+
falling = sv.get("clk", lambda sm1, s, sp1: sm1 == 1 and s == 0)
|
|
159
|
+
|
|
160
|
+
# Any edge: value changed
|
|
161
|
+
edges = sv.get("data", lambda sm1, s, sp1: sm1 is not None and sm1 != s)
|
|
162
|
+
|
|
163
|
+
# Level high
|
|
164
|
+
high = sv.get("enable", lambda sm1, s, sp1: s == 1)
|
|
165
|
+
|
|
166
|
+
# Level low
|
|
167
|
+
low = sv.get("reset", lambda sm1, s, sp1: s == 0)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Multi-bit Signals
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Specific pattern on a bus (binary "1010" = decimal 10)
|
|
174
|
+
pattern = sv.get("bus[3:0]", lambda sm1, s, sp1: s == 10)
|
|
175
|
+
|
|
176
|
+
# Bus is non-zero
|
|
177
|
+
active = sv.get("data[7:0]", lambda sm1, s, sp1: s != 0)
|
|
178
|
+
|
|
179
|
+
# Bus transition detection
|
|
180
|
+
bus_changed = sv.get("addr[15:0]", lambda sm1, s, sp1: sm1 is not None and sm1 != s)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Complex Queries with Set Operations
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
# Rising clock edges when enable is high
|
|
187
|
+
clk_rising = sv.get("clk", lambda sm1, s, sp1: sm1 == 0 and s == 1)
|
|
188
|
+
enable_high = sv.get("enable", lambda sm1, s, sp1: s == 1)
|
|
189
|
+
valid_clocks = clk_rising & enable_high
|
|
190
|
+
|
|
191
|
+
# Data changes while not in reset
|
|
192
|
+
data_changes = sv.get("data", lambda sm1, s, sp1: sm1 is not None and sm1 != s)
|
|
193
|
+
not_reset = sv.get("reset", lambda sm1, s, sp1: s == 0)
|
|
194
|
+
valid_changes = data_changes & not_reset
|
|
195
|
+
|
|
196
|
+
# Either signal is high
|
|
197
|
+
sig1_high = sv.get("sig1", lambda sm1, s, sp1: s == 1)
|
|
198
|
+
sig2_high = sv.get("sig2", lambda sm1, s, sp1: s == 1)
|
|
199
|
+
either_high = sig1_high | sig2_high
|
|
200
|
+
|
|
201
|
+
# Exclusive high (one but not both)
|
|
202
|
+
exclusive_high = sig1_high ^ sig2_high
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Advanced Pattern Detection
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
# Detect setup violation: data changes right before clock edge
|
|
209
|
+
data_change = sv.get("data", lambda sm1, s, sp1: sm1 is not None and sm1 != s)
|
|
210
|
+
clk_about_to_rise = sv.get("clk", lambda sm1, s, sp1: s == 0 and sp1 == 1)
|
|
211
|
+
setup_violations = data_change & clk_about_to_rise
|
|
212
|
+
|
|
213
|
+
# Handshake protocol: valid and ready both high
|
|
214
|
+
valid_high = sv.get("valid", lambda sm1, s, sp1: s == 1)
|
|
215
|
+
ready_high = sv.get("ready", lambda sm1, s, sp1: s == 1)
|
|
216
|
+
handshake_times = valid_high & ready_high
|
|
217
|
+
|
|
218
|
+
# State machine transitions (binary "00" = 0, "01" = 1)
|
|
219
|
+
state_a = sv.get("state[1:0]", lambda sm1, s, sp1: s == 0)
|
|
220
|
+
state_b = sv.get("state[1:0]", lambda sm1, s, sp1: s == 1)
|
|
221
|
+
# Times when transitioning from state A to state B
|
|
222
|
+
transition = sv.get("state[1:0]", lambda sm1, s, sp1: sm1 == 0 and s == 1)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Value Types
|
|
226
|
+
|
|
227
|
+
SetVCD supports three ValueType options to control how signal values are converted before being passed to your condition lambdas:
|
|
228
|
+
|
|
229
|
+
### Raw() - Integer Conversion (Default)
|
|
230
|
+
|
|
231
|
+
Converts binary strings to decimal integers. X/Z values become `None`. This is the default behavior.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from setVCD import SetVCD, Raw
|
|
235
|
+
|
|
236
|
+
sv = SetVCD("simulation.vcd", clock="clk")
|
|
237
|
+
|
|
238
|
+
# Default behavior (Raw is implicit)
|
|
239
|
+
rising = sv.get("data[7:0]", lambda sm1, s, sp1: sm1 is not None and sm1 < s)
|
|
240
|
+
|
|
241
|
+
# Explicit Raw (same as above)
|
|
242
|
+
rising = sv.get("data[7:0]", lambda sm1, s, sp1: sm1 is not None and sm1 < s, value_type=Raw())
|
|
243
|
+
|
|
244
|
+
# Multi-bit signals converted to decimal
|
|
245
|
+
# Binary "00001010" → integer 10
|
|
246
|
+
high_values = sv.get("bus[7:0]", lambda sm1, s, sp1: s is not None and s > 128)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### String() - Preserve Raw Strings
|
|
250
|
+
|
|
251
|
+
Keeps vcdvcd's raw string representation, including X/Z values as literal strings. Useful for detecting unknown states.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from setVCD import SetVCD, String
|
|
255
|
+
|
|
256
|
+
sv = SetVCD("simulation.vcd", clock="clk")
|
|
257
|
+
|
|
258
|
+
# Detect X/Z values in data bus
|
|
259
|
+
has_x = sv.get("data[7:0]",
|
|
260
|
+
lambda sm1, s, sp1: s is not None and 'x' in s.lower(),
|
|
261
|
+
value_type=String())
|
|
262
|
+
|
|
263
|
+
# String pattern matching
|
|
264
|
+
all_ones = sv.get("bus[3:0]",
|
|
265
|
+
lambda sm1, s, sp1: s == "1111",
|
|
266
|
+
value_type=String())
|
|
267
|
+
|
|
268
|
+
# Get string values
|
|
269
|
+
values = sv.get_values("data", timesteps, value_type=String())
|
|
270
|
+
# Returns: [(50, "1010"), (60, "1111"), (70, "xxxx"), ...]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**X/Z Handling:** X and Z values are preserved as strings (`"x"`, `"z"`, `"xxxx"`, etc.)
|
|
274
|
+
|
|
275
|
+
### FP() - Fixed-Point to Float
|
|
276
|
+
|
|
277
|
+
Converts binary strings to floating-point by interpreting them as fixed-point numbers with configurable fractional bits and optional sign bit.
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
from setVCD import SetVCD, FP
|
|
281
|
+
|
|
282
|
+
sv = SetVCD("simulation.vcd", clock="clk")
|
|
283
|
+
|
|
284
|
+
# Temperature sensor with 8 fractional bits (Q8.8 format)
|
|
285
|
+
# Binary "0001100100000000" → 25.0 degrees
|
|
286
|
+
above_threshold = sv.get("temp_sensor[15:0]",
|
|
287
|
+
lambda sm1, s, sp1: s is not None and s > 25.5,
|
|
288
|
+
value_type=FP(frac=8, signed=False))
|
|
289
|
+
|
|
290
|
+
# Signed fixed-point (Q3.4 format - 1 sign bit, 3 integer bits, 4 fractional bits)
|
|
291
|
+
# Binary "11111110" → -0.125 (two's complement)
|
|
292
|
+
negative_values = sv.get("signed_value[7:0]",
|
|
293
|
+
lambda sm1, s, sp1: s is not None and s < 0,
|
|
294
|
+
value_type=FP(frac=4, signed=True))
|
|
295
|
+
|
|
296
|
+
# Get fixed-point values
|
|
297
|
+
voltages = sv.get_values("adc_reading[11:0]", timesteps,
|
|
298
|
+
value_type=FP(frac=12, signed=False))
|
|
299
|
+
# Returns: [(50, 1.2), (60, 2.5), (70, 3.8), ...]
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**X/Z Handling:** X and Z values become `float('nan')`. Use `math.isnan()` to detect them:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
import math
|
|
306
|
+
|
|
307
|
+
# Filter out NaN values
|
|
308
|
+
valid_readings = sv.get("sensor",
|
|
309
|
+
lambda sm1, s, sp1: s is not None and not math.isnan(s),
|
|
310
|
+
value_type=FP(frac=8, signed=False))
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Fixed-Point Formula:**
|
|
314
|
+
- Unsigned: `value = int_value / (2^frac)`
|
|
315
|
+
- Signed: Two's complement, then divide by `2^frac`
|
|
316
|
+
|
|
317
|
+
**Examples:**
|
|
318
|
+
- `"00001010"` with `frac=4, signed=False` → `10 / 16 = 0.625`
|
|
319
|
+
- `"11111110"` with `frac=4, signed=True` → `-2 / 16 = -0.125`
|
|
320
|
+
- `"00010000"` with `frac=0, signed=False` → `16 / 1 = 16.0` (integer)
|
|
321
|
+
|
|
322
|
+
### Hardware Use Cases
|
|
323
|
+
|
|
324
|
+
**Raw (Default):** Most general-purpose verification - state machines, counters, addresses, data comparisons
|
|
325
|
+
|
|
326
|
+
**String:** Debugging X/Z propagation, detecting uninitialized signals, bit-pattern analysis
|
|
327
|
+
|
|
328
|
+
**FP:** Analog interfaces (ADC/DAC), sensor data, fixed-point DSP verification, power/temperature monitors
|
|
329
|
+
|
|
330
|
+
## Future Enhancements
|
|
331
|
+
|
|
332
|
+
Planned for future versions:
|
|
333
|
+
|
|
334
|
+
- Higher-order operations for signal conditions
|
|
335
|
+
- Performance optimization for large VCD files
|
|
336
|
+
- Streaming interface for very large files
|
|
337
|
+
- MCP (Model Context Protocol) integration
|
setvcd-0.2.0/README.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# SetVCD
|
|
2
|
+
Programmatically inspect hardware VCD signals using a high-level functional interface.
|
|
3
|
+
|
|
4
|
+
Higher-order programming constructs and set operations are a natural fit for inspecting VCD signals, and this Python library allows you to easily specify, in text, what simulation timesteps matter to functional correctness.
|
|
5
|
+
|
|
6
|
+
## Motivating Example
|
|
7
|
+
Say you are debugging a streaming interface, you often only care about the values of the data at timesteps meeting the following condition:
|
|
8
|
+
|
|
9
|
+
$\text{Rising edge} \land \text{Reset is 0} \land \text{ready} \land \text{valid}$
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
You can filter through an individual signal with a filter function of this signature:
|
|
14
|
+
|
|
15
|
+
$(\text{Bits}, \text{Bits}, \text{Bits}) \rightarrow \text{Bool}$
|
|
16
|
+
|
|
17
|
+
with the left-hand tuple representing *values* at timestep $t$: $(t-1, t, t+1)$.
|
|
18
|
+
|
|
19
|
+
We then define our `get` method, which takes the name of the signal (as a String), a function with the above signature, and returns a set of *timesteps*:
|
|
20
|
+
|
|
21
|
+
$\texttt{get}: (\text{String}, ((\text{Bits}, \text{Bits}, \text{Bits}) \rightarrow \text{Bool})) \rightarrow \text{Set(Timestep)}$
|
|
22
|
+
|
|
23
|
+
As what is returned is a set, you can then use [set operations](https://en.wikipedia.org/wiki/Set_(mathematics)#Basic_operations) to manipulate them as needed, and finally extract the values from your desired signal using our `get_value` function:
|
|
24
|
+
|
|
25
|
+
$\texttt{get-value}: (\text{String}, \text{Set(Timestep)}) \rightarrow \text{List((Timestep, Bits))}$
|
|
26
|
+
|
|
27
|
+
Here's an example of finding the rising edges of the clock signal `TOP.clk` of our test wavefile `wave.vcd`:
|
|
28
|
+
```python
|
|
29
|
+
from setVCD import SetVCD
|
|
30
|
+
|
|
31
|
+
# Load VCD file
|
|
32
|
+
vcd_path = "./tests/fixtures/wave.vcd"
|
|
33
|
+
sv = SetVCD(vcd_path, clock="TOP.clk")
|
|
34
|
+
|
|
35
|
+
rising_edges = sv.get("TOP.clk", lambda tm1, t, tp1: tm1 == 0 and t == 1)
|
|
36
|
+
print(rising_edges)
|
|
37
|
+
# {34, 36, 38, 40, 42, 44, ...}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Because `rising_edges` is returned as a set, we can use set operations to combine it with other signals:
|
|
41
|
+
```python
|
|
42
|
+
# Get times when the reset signal is 0
|
|
43
|
+
reset_is_0 = sv.get("TOP.reset", lambda tm1, t, tp1: t == 0)
|
|
44
|
+
# Use set intersection to get valid clock updates.
|
|
45
|
+
clock_update = rising_edges & reset_is_0
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Finally, you can search the wavefile with a regex (e.g. "output"), and apply the same operations to it:
|
|
49
|
+
```python
|
|
50
|
+
# Find VCD signals relating to keyword "output"
|
|
51
|
+
sv.search("output")
|
|
52
|
+
|
|
53
|
+
# Get times when output_valid and output_ready are asserted.
|
|
54
|
+
out_valid = sv.get("TOP.Accelerator.io_output_valid", lambda tm1, t, tp1: t == 1)
|
|
55
|
+
out_ready = sv.get("TOP.Accelerator.io_output_ready", lambda tm1, t, tp1: t == 1)
|
|
56
|
+
|
|
57
|
+
# Get timesteps of valid outputs
|
|
58
|
+
valid_output_timesteps = rising_edges & reset0 & out_ready & out_valid
|
|
59
|
+
|
|
60
|
+
# Get the values of the Stream `value` signal (the data) at timesteps when it is valid
|
|
61
|
+
outputs = sv.get_values("TOP.Accelerator.io_output_payload_fragment_value_0[0:0]", valid_output_timesteps)
|
|
62
|
+
print(outputs)
|
|
63
|
+
# [(52, 0), (62, 0), (72, 1), ...] # Integer values
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Overview
|
|
67
|
+
|
|
68
|
+
SetVCD is a Python package for analyzing Verilog VCD files and extracting time points where specific signal conditions are met. It provides a simple, type-safe interface for working with simulation waveforms using set-based operations.
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
The package is available in PyPI:
|
|
72
|
+
```bash
|
|
73
|
+
pip install setVCD
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
### Initialization
|
|
79
|
+
|
|
80
|
+
You can initialize SetVCD with either a filename or a vcdvcd object:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import setVCD
|
|
84
|
+
from pathlib import Path
|
|
85
|
+
|
|
86
|
+
# From string filename
|
|
87
|
+
sv = SetVCD("simulation.vcd", clock="clk")
|
|
88
|
+
|
|
89
|
+
# From Path object
|
|
90
|
+
sv = SetVCD(Path("simulation.vcd"), clock="clk")
|
|
91
|
+
|
|
92
|
+
# From vcdvcd object
|
|
93
|
+
import vcdvcd
|
|
94
|
+
vcd = vcdvcd.VCDVCD("simulation.vcd")
|
|
95
|
+
sv = SetVCD(vcd, clock="clk")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `clock` parameter must be the exact name of the clock signal in your VCD file (case-sensitive). This signal determines the time range for queries.
|
|
99
|
+
|
|
100
|
+
### Signal Conditions
|
|
101
|
+
|
|
102
|
+
The `signal_condition` callback receives three arguments representing the signal value at three consecutive time points:
|
|
103
|
+
|
|
104
|
+
- `sm1`: Signal value at time-1 (None at time 0 or if value is x/z)
|
|
105
|
+
- `s`: Signal value at current time (None if value is x/z)
|
|
106
|
+
- `sp1`: Signal value at time+1 (None at last time or if value is x/z)
|
|
107
|
+
|
|
108
|
+
Signal values are `Optional[int]`:
|
|
109
|
+
- Integers: Binary values converted to decimal (e.g., "1010" → 10)
|
|
110
|
+
- None: Represents x/z values or boundary conditions (t-1 at time 0, t+1 at last time)
|
|
111
|
+
|
|
112
|
+
The callback should return `True` to include that time point in the result set.
|
|
113
|
+
|
|
114
|
+
### Examples
|
|
115
|
+
|
|
116
|
+
#### Basic Signal Detection
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# Rising edge: 0 -> 1 transition
|
|
120
|
+
rising = sv.get("clk", lambda sm1, s, sp1: sm1 == 0 and s == 1)
|
|
121
|
+
|
|
122
|
+
# Falling edge: 1 -> 0 transition
|
|
123
|
+
falling = sv.get("clk", lambda sm1, s, sp1: sm1 == 1 and s == 0)
|
|
124
|
+
|
|
125
|
+
# Any edge: value changed
|
|
126
|
+
edges = sv.get("data", lambda sm1, s, sp1: sm1 is not None and sm1 != s)
|
|
127
|
+
|
|
128
|
+
# Level high
|
|
129
|
+
high = sv.get("enable", lambda sm1, s, sp1: s == 1)
|
|
130
|
+
|
|
131
|
+
# Level low
|
|
132
|
+
low = sv.get("reset", lambda sm1, s, sp1: s == 0)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### Multi-bit Signals
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
# Specific pattern on a bus (binary "1010" = decimal 10)
|
|
139
|
+
pattern = sv.get("bus[3:0]", lambda sm1, s, sp1: s == 10)
|
|
140
|
+
|
|
141
|
+
# Bus is non-zero
|
|
142
|
+
active = sv.get("data[7:0]", lambda sm1, s, sp1: s != 0)
|
|
143
|
+
|
|
144
|
+
# Bus transition detection
|
|
145
|
+
bus_changed = sv.get("addr[15:0]", lambda sm1, s, sp1: sm1 is not None and sm1 != s)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Complex Queries with Set Operations
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# Rising clock edges when enable is high
|
|
152
|
+
clk_rising = sv.get("clk", lambda sm1, s, sp1: sm1 == 0 and s == 1)
|
|
153
|
+
enable_high = sv.get("enable", lambda sm1, s, sp1: s == 1)
|
|
154
|
+
valid_clocks = clk_rising & enable_high
|
|
155
|
+
|
|
156
|
+
# Data changes while not in reset
|
|
157
|
+
data_changes = sv.get("data", lambda sm1, s, sp1: sm1 is not None and sm1 != s)
|
|
158
|
+
not_reset = sv.get("reset", lambda sm1, s, sp1: s == 0)
|
|
159
|
+
valid_changes = data_changes & not_reset
|
|
160
|
+
|
|
161
|
+
# Either signal is high
|
|
162
|
+
sig1_high = sv.get("sig1", lambda sm1, s, sp1: s == 1)
|
|
163
|
+
sig2_high = sv.get("sig2", lambda sm1, s, sp1: s == 1)
|
|
164
|
+
either_high = sig1_high | sig2_high
|
|
165
|
+
|
|
166
|
+
# Exclusive high (one but not both)
|
|
167
|
+
exclusive_high = sig1_high ^ sig2_high
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Advanced Pattern Detection
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Detect setup violation: data changes right before clock edge
|
|
174
|
+
data_change = sv.get("data", lambda sm1, s, sp1: sm1 is not None and sm1 != s)
|
|
175
|
+
clk_about_to_rise = sv.get("clk", lambda sm1, s, sp1: s == 0 and sp1 == 1)
|
|
176
|
+
setup_violations = data_change & clk_about_to_rise
|
|
177
|
+
|
|
178
|
+
# Handshake protocol: valid and ready both high
|
|
179
|
+
valid_high = sv.get("valid", lambda sm1, s, sp1: s == 1)
|
|
180
|
+
ready_high = sv.get("ready", lambda sm1, s, sp1: s == 1)
|
|
181
|
+
handshake_times = valid_high & ready_high
|
|
182
|
+
|
|
183
|
+
# State machine transitions (binary "00" = 0, "01" = 1)
|
|
184
|
+
state_a = sv.get("state[1:0]", lambda sm1, s, sp1: s == 0)
|
|
185
|
+
state_b = sv.get("state[1:0]", lambda sm1, s, sp1: s == 1)
|
|
186
|
+
# Times when transitioning from state A to state B
|
|
187
|
+
transition = sv.get("state[1:0]", lambda sm1, s, sp1: sm1 == 0 and s == 1)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Value Types
|
|
191
|
+
|
|
192
|
+
SetVCD supports three ValueType options to control how signal values are converted before being passed to your condition lambdas:
|
|
193
|
+
|
|
194
|
+
### Raw() - Integer Conversion (Default)
|
|
195
|
+
|
|
196
|
+
Converts binary strings to decimal integers. X/Z values become `None`. This is the default behavior.
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from setVCD import SetVCD, Raw
|
|
200
|
+
|
|
201
|
+
sv = SetVCD("simulation.vcd", clock="clk")
|
|
202
|
+
|
|
203
|
+
# Default behavior (Raw is implicit)
|
|
204
|
+
rising = sv.get("data[7:0]", lambda sm1, s, sp1: sm1 is not None and sm1 < s)
|
|
205
|
+
|
|
206
|
+
# Explicit Raw (same as above)
|
|
207
|
+
rising = sv.get("data[7:0]", lambda sm1, s, sp1: sm1 is not None and sm1 < s, value_type=Raw())
|
|
208
|
+
|
|
209
|
+
# Multi-bit signals converted to decimal
|
|
210
|
+
# Binary "00001010" → integer 10
|
|
211
|
+
high_values = sv.get("bus[7:0]", lambda sm1, s, sp1: s is not None and s > 128)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### String() - Preserve Raw Strings
|
|
215
|
+
|
|
216
|
+
Keeps vcdvcd's raw string representation, including X/Z values as literal strings. Useful for detecting unknown states.
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from setVCD import SetVCD, String
|
|
220
|
+
|
|
221
|
+
sv = SetVCD("simulation.vcd", clock="clk")
|
|
222
|
+
|
|
223
|
+
# Detect X/Z values in data bus
|
|
224
|
+
has_x = sv.get("data[7:0]",
|
|
225
|
+
lambda sm1, s, sp1: s is not None and 'x' in s.lower(),
|
|
226
|
+
value_type=String())
|
|
227
|
+
|
|
228
|
+
# String pattern matching
|
|
229
|
+
all_ones = sv.get("bus[3:0]",
|
|
230
|
+
lambda sm1, s, sp1: s == "1111",
|
|
231
|
+
value_type=String())
|
|
232
|
+
|
|
233
|
+
# Get string values
|
|
234
|
+
values = sv.get_values("data", timesteps, value_type=String())
|
|
235
|
+
# Returns: [(50, "1010"), (60, "1111"), (70, "xxxx"), ...]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**X/Z Handling:** X and Z values are preserved as strings (`"x"`, `"z"`, `"xxxx"`, etc.)
|
|
239
|
+
|
|
240
|
+
### FP() - Fixed-Point to Float
|
|
241
|
+
|
|
242
|
+
Converts binary strings to floating-point by interpreting them as fixed-point numbers with configurable fractional bits and optional sign bit.
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from setVCD import SetVCD, FP
|
|
246
|
+
|
|
247
|
+
sv = SetVCD("simulation.vcd", clock="clk")
|
|
248
|
+
|
|
249
|
+
# Temperature sensor with 8 fractional bits (Q8.8 format)
|
|
250
|
+
# Binary "0001100100000000" → 25.0 degrees
|
|
251
|
+
above_threshold = sv.get("temp_sensor[15:0]",
|
|
252
|
+
lambda sm1, s, sp1: s is not None and s > 25.5,
|
|
253
|
+
value_type=FP(frac=8, signed=False))
|
|
254
|
+
|
|
255
|
+
# Signed fixed-point (Q3.4 format - 1 sign bit, 3 integer bits, 4 fractional bits)
|
|
256
|
+
# Binary "11111110" → -0.125 (two's complement)
|
|
257
|
+
negative_values = sv.get("signed_value[7:0]",
|
|
258
|
+
lambda sm1, s, sp1: s is not None and s < 0,
|
|
259
|
+
value_type=FP(frac=4, signed=True))
|
|
260
|
+
|
|
261
|
+
# Get fixed-point values
|
|
262
|
+
voltages = sv.get_values("adc_reading[11:0]", timesteps,
|
|
263
|
+
value_type=FP(frac=12, signed=False))
|
|
264
|
+
# Returns: [(50, 1.2), (60, 2.5), (70, 3.8), ...]
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**X/Z Handling:** X and Z values become `float('nan')`. Use `math.isnan()` to detect them:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
import math
|
|
271
|
+
|
|
272
|
+
# Filter out NaN values
|
|
273
|
+
valid_readings = sv.get("sensor",
|
|
274
|
+
lambda sm1, s, sp1: s is not None and not math.isnan(s),
|
|
275
|
+
value_type=FP(frac=8, signed=False))
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Fixed-Point Formula:**
|
|
279
|
+
- Unsigned: `value = int_value / (2^frac)`
|
|
280
|
+
- Signed: Two's complement, then divide by `2^frac`
|
|
281
|
+
|
|
282
|
+
**Examples:**
|
|
283
|
+
- `"00001010"` with `frac=4, signed=False` → `10 / 16 = 0.625`
|
|
284
|
+
- `"11111110"` with `frac=4, signed=True` → `-2 / 16 = -0.125`
|
|
285
|
+
- `"00010000"` with `frac=0, signed=False` → `16 / 1 = 16.0` (integer)
|
|
286
|
+
|
|
287
|
+
### Hardware Use Cases
|
|
288
|
+
|
|
289
|
+
**Raw (Default):** Most general-purpose verification - state machines, counters, addresses, data comparisons
|
|
290
|
+
|
|
291
|
+
**String:** Debugging X/Z propagation, detecting uninitialized signals, bit-pattern analysis
|
|
292
|
+
|
|
293
|
+
**FP:** Analog interfaces (ADC/DAC), sensor data, fixed-point DSP verification, power/temperature monitors
|
|
294
|
+
|
|
295
|
+
## Future Enhancements
|
|
296
|
+
|
|
297
|
+
Planned for future versions:
|
|
298
|
+
|
|
299
|
+
- Higher-order operations for signal conditions
|
|
300
|
+
- Performance optimization for large VCD files
|
|
301
|
+
- Streaming interface for very large files
|
|
302
|
+
- MCP (Model Context Protocol) integration
|