wavekit 0.2.0__tar.gz → 0.3.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.2.0 → wavekit-0.3.0}/PKG-INFO +60 -3
- {wavekit-0.2.0 → wavekit-0.3.0}/README.md +59 -2
- {wavekit-0.2.0 → wavekit-0.3.0}/pyproject.toml +1 -1
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/__init__.py +7 -0
- wavekit-0.3.0/src/wavekit/readers/base.py +281 -0
- wavekit-0.3.0/src/wavekit/readers/expr_parser.py +65 -0
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/readers/fsdb/npi_fsdb.pxd +9 -2
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +139 -13
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/readers/fsdb/reader.py +15 -2
- wavekit-0.2.0/src/wavekit/readers/base.py +0 -142
- {wavekit-0.2.0 → wavekit-0.3.0}/LICENSE +0 -0
- {wavekit-0.2.0 → wavekit-0.3.0}/build.py +0 -0
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/readers/pattern_parser.py +0 -0
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/readers/value_change.pyx +0 -0
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/readers/vcd/reader.py +0 -0
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/scope.py +0 -0
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/signal.py +0 -0
- {wavekit-0.2.0 → wavekit-0.3.0}/src/wavekit/waveform.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavekit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: a fundamental package for digital circuit waveform analysis
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: cxzzzz
|
|
@@ -78,7 +78,7 @@ with VcdReader("jtag.vcd") as f:
|
|
|
78
78
|
# Returns: { ('state',): Waveform(...), ('next',): Waveform(...) }
|
|
79
79
|
waves = f.load_matched_waveforms(
|
|
80
80
|
"tb.u0.J_{state,next}[3:0]",
|
|
81
|
-
|
|
81
|
+
clock_pattern="tb.tck"
|
|
82
82
|
)
|
|
83
83
|
|
|
84
84
|
for (suffix,), wave in waves.items():
|
|
@@ -96,7 +96,7 @@ with VcdReader("jtag.vcd") as f:
|
|
|
96
96
|
regex_pattern = r"tb.u0.@J_([a-z]+)"
|
|
97
97
|
|
|
98
98
|
# Returns: { ('state',): Waveform(...), ('next',): Waveform(...) }
|
|
99
|
-
waves = f.load_matched_waveforms(regex_pattern,
|
|
99
|
+
waves = f.load_matched_waveforms(regex_pattern, clock_pattern="tb.tck")
|
|
100
100
|
|
|
101
101
|
for (name_part,), wave in waves.items():
|
|
102
102
|
print(f"Matched: {name_part}")
|
|
@@ -149,6 +149,63 @@ with VcdReader("bus_tb.vcd") as f:
|
|
|
149
149
|
print(f"First valid data: {hex(valid_data.value[0])}")
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
### Expression Evaluation
|
|
153
|
+
|
|
154
|
+
Use `eval` to compute waveform expressions directly from signal path strings, without manually loading each signal.
|
|
155
|
+
|
|
156
|
+
**Single mode** — all paths match exactly one signal, returns a single `Waveform`:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from wavekit import VcdReader
|
|
160
|
+
|
|
161
|
+
with VcdReader("fifo_tb.vcd") as f:
|
|
162
|
+
clock = "fifo_tb.clk"
|
|
163
|
+
|
|
164
|
+
# Equivalent to loading w_ptr and r_ptr separately and computing the difference
|
|
165
|
+
occupancy = f.eval(
|
|
166
|
+
"fifo_tb.s_fifo.w_ptr[2:0] - fifo_tb.s_fifo.r_ptr[2:0]",
|
|
167
|
+
clock=clock,
|
|
168
|
+
)
|
|
169
|
+
print(f"Occupancy width: {occupancy.width}")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Bit-slicing on the expression result is also supported:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# Load a 4-bit signal and slice out the lower 2 bits
|
|
176
|
+
low_bits = f.eval("tb.dut.data[3:0][1:0]", clock=clock)
|
|
177
|
+
assert low_bits.width == 2
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Zip mode** — paths with brace/regex patterns expand to multiple signals; the expression is evaluated once per matched key and a `dict` is returned:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
with VcdReader("multi_fifo_tb.vcd") as f:
|
|
184
|
+
clock = "tb.clk"
|
|
185
|
+
|
|
186
|
+
# Evaluate occupancy for fifo_0, fifo_1, fifo_2, fifo_3 in one call
|
|
187
|
+
# Returns: { (0,): Waveform, (1,): Waveform, (2,): Waveform, (3,): Waveform }
|
|
188
|
+
occupancies = f.eval(
|
|
189
|
+
"tb.fifo_{0..3}.w_ptr[2:0] - tb.fifo_{0..3}.r_ptr[2:0]",
|
|
190
|
+
clock=clock,
|
|
191
|
+
mode="zip",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
for (idx,), wave in occupancies.items():
|
|
195
|
+
print(f"fifo_{idx} occupancy width: {wave.width}")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Single-match paths in zip mode are **broadcast** — the same waveform is reused across all keys:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
# base_offset matches one signal; it is reused for every fifo index
|
|
202
|
+
adjusted = f.eval(
|
|
203
|
+
"tb.fifo_{0..3}.w_ptr[2:0] - tb.dut.base_offset[2:0]",
|
|
204
|
+
clock=clock,
|
|
205
|
+
mode="zip",
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
|
|
152
209
|
## 🛠️ Development
|
|
153
210
|
|
|
154
211
|
This project adheres to strict code quality standards using modern Python tooling:
|
|
@@ -57,7 +57,7 @@ with VcdReader("jtag.vcd") as f:
|
|
|
57
57
|
# Returns: { ('state',): Waveform(...), ('next',): Waveform(...) }
|
|
58
58
|
waves = f.load_matched_waveforms(
|
|
59
59
|
"tb.u0.J_{state,next}[3:0]",
|
|
60
|
-
|
|
60
|
+
clock_pattern="tb.tck"
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
for (suffix,), wave in waves.items():
|
|
@@ -75,7 +75,7 @@ with VcdReader("jtag.vcd") as f:
|
|
|
75
75
|
regex_pattern = r"tb.u0.@J_([a-z]+)"
|
|
76
76
|
|
|
77
77
|
# Returns: { ('state',): Waveform(...), ('next',): Waveform(...) }
|
|
78
|
-
waves = f.load_matched_waveforms(regex_pattern,
|
|
78
|
+
waves = f.load_matched_waveforms(regex_pattern, clock_pattern="tb.tck")
|
|
79
79
|
|
|
80
80
|
for (name_part,), wave in waves.items():
|
|
81
81
|
print(f"Matched: {name_part}")
|
|
@@ -128,6 +128,63 @@ with VcdReader("bus_tb.vcd") as f:
|
|
|
128
128
|
print(f"First valid data: {hex(valid_data.value[0])}")
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
### Expression Evaluation
|
|
132
|
+
|
|
133
|
+
Use `eval` to compute waveform expressions directly from signal path strings, without manually loading each signal.
|
|
134
|
+
|
|
135
|
+
**Single mode** — all paths match exactly one signal, returns a single `Waveform`:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from wavekit import VcdReader
|
|
139
|
+
|
|
140
|
+
with VcdReader("fifo_tb.vcd") as f:
|
|
141
|
+
clock = "fifo_tb.clk"
|
|
142
|
+
|
|
143
|
+
# Equivalent to loading w_ptr and r_ptr separately and computing the difference
|
|
144
|
+
occupancy = f.eval(
|
|
145
|
+
"fifo_tb.s_fifo.w_ptr[2:0] - fifo_tb.s_fifo.r_ptr[2:0]",
|
|
146
|
+
clock=clock,
|
|
147
|
+
)
|
|
148
|
+
print(f"Occupancy width: {occupancy.width}")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Bit-slicing on the expression result is also supported:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# Load a 4-bit signal and slice out the lower 2 bits
|
|
155
|
+
low_bits = f.eval("tb.dut.data[3:0][1:0]", clock=clock)
|
|
156
|
+
assert low_bits.width == 2
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Zip mode** — paths with brace/regex patterns expand to multiple signals; the expression is evaluated once per matched key and a `dict` is returned:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
with VcdReader("multi_fifo_tb.vcd") as f:
|
|
163
|
+
clock = "tb.clk"
|
|
164
|
+
|
|
165
|
+
# Evaluate occupancy for fifo_0, fifo_1, fifo_2, fifo_3 in one call
|
|
166
|
+
# Returns: { (0,): Waveform, (1,): Waveform, (2,): Waveform, (3,): Waveform }
|
|
167
|
+
occupancies = f.eval(
|
|
168
|
+
"tb.fifo_{0..3}.w_ptr[2:0] - tb.fifo_{0..3}.r_ptr[2:0]",
|
|
169
|
+
clock=clock,
|
|
170
|
+
mode="zip",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
for (idx,), wave in occupancies.items():
|
|
174
|
+
print(f"fifo_{idx} occupancy width: {wave.width}")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Single-match paths in zip mode are **broadcast** — the same waveform is reused across all keys:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# base_offset matches one signal; it is reused for every fifo index
|
|
181
|
+
adjusted = f.eval(
|
|
182
|
+
"tb.fifo_{0..3}.w_ptr[2:0] - tb.dut.base_offset[2:0]",
|
|
183
|
+
clock=clock,
|
|
184
|
+
mode="zip",
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
131
188
|
## 🛠️ Development
|
|
132
189
|
|
|
133
190
|
This project adheres to strict code quality standards using modern Python tooling:
|
|
@@ -19,3 +19,10 @@ from .signal import Signal as Signal
|
|
|
19
19
|
from .waveform import Waveform as Waveform
|
|
20
20
|
|
|
21
21
|
__all__ = ['Waveform', 'VcdReader', 'FsdbReader', 'Scope', 'Signal']
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from .readers.fsdb.reader import FsdbReader as FsdbReader
|
|
25
|
+
|
|
26
|
+
__all__.append('FsdbReader')
|
|
27
|
+
except ImportError:
|
|
28
|
+
pass
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ..scope import Scope, traverse_scope
|
|
11
|
+
from ..signal import Signal
|
|
12
|
+
from ..waveform import Waveform
|
|
13
|
+
from .expr_parser import extract_wave_paths
|
|
14
|
+
from .pattern_parser import (
|
|
15
|
+
PatternMap,
|
|
16
|
+
expand_brace_pattern,
|
|
17
|
+
split_by_hierarchy,
|
|
18
|
+
)
|
|
19
|
+
from .value_change import value_change_to_value_array
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Reader:
|
|
23
|
+
def __init__(self):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def __enter__(self):
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
30
|
+
self.close()
|
|
31
|
+
# exception wont be suppressed
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def load_waveform(
|
|
36
|
+
self,
|
|
37
|
+
signal: str,
|
|
38
|
+
clock: str,
|
|
39
|
+
xz_value: int = 0,
|
|
40
|
+
signed: bool = False,
|
|
41
|
+
sample_on_posedge: bool = False,
|
|
42
|
+
begin_time: int | None = None,
|
|
43
|
+
end_time: int | None = None,
|
|
44
|
+
) -> Waveform:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def get_signal_width(self, signal: str) -> int:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def get_signal_range(self, signal: str) -> tuple[int]:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def value_change_to_waveform(
|
|
57
|
+
value_change: np.ndarray,
|
|
58
|
+
clock_changes: np.ndarray,
|
|
59
|
+
width: int | None,
|
|
60
|
+
signed: bool,
|
|
61
|
+
sample_on_posedge: bool = False,
|
|
62
|
+
signal: str = '',
|
|
63
|
+
) -> Waveform:
|
|
64
|
+
value, clock, time = value_change_to_value_array(
|
|
65
|
+
value_change, clock_changes, sample_on_posedge=sample_on_posedge
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return Waveform(
|
|
69
|
+
value=value,
|
|
70
|
+
clock=clock,
|
|
71
|
+
time=time,
|
|
72
|
+
signal=Signal(signal, width, signed),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def top_scope_list(self) -> Sequence[Scope]:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def get_matched_signals(
|
|
80
|
+
self,
|
|
81
|
+
pattern: str,
|
|
82
|
+
) -> dict[tuple[Any, ...], str]:
|
|
83
|
+
def combine_dict(
|
|
84
|
+
dict1: dict[tuple[Any, ...], str],
|
|
85
|
+
dict2: dict[tuple[Any, ...], str],
|
|
86
|
+
) -> dict[tuple[Any, ...], str]:
|
|
87
|
+
common_keys = set(dict1.keys()).intersection(dict2.keys())
|
|
88
|
+
|
|
89
|
+
if common_keys:
|
|
90
|
+
signal1s = [dict1[k] for k in common_keys]
|
|
91
|
+
signal2s = [dict2[k] for k in common_keys]
|
|
92
|
+
raise Exception(
|
|
93
|
+
'found more than one signal with the same keys: '
|
|
94
|
+
f'keys:{list(common_keys)} , signals:{signal1s + signal2s}'
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return {**dict1, **dict2}
|
|
98
|
+
|
|
99
|
+
pattern = pattern.strip()
|
|
100
|
+
expanded_pattern_list: list[PatternMap] = [
|
|
101
|
+
expand_brace_pattern(p) for p in split_by_hierarchy(pattern)
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
matched_signals: dict[tuple[Any, ...], str] = {}
|
|
105
|
+
for scope in self.top_scope_list():
|
|
106
|
+
matched_signals = combine_dict(
|
|
107
|
+
matched_signals,
|
|
108
|
+
traverse_scope(scope, expanded_pattern_list),
|
|
109
|
+
)
|
|
110
|
+
return matched_signals
|
|
111
|
+
|
|
112
|
+
def load_matched_waveforms(
|
|
113
|
+
self,
|
|
114
|
+
pattern: str,
|
|
115
|
+
clock_pattern: str,
|
|
116
|
+
xz_value: int = 0,
|
|
117
|
+
signed: bool = False,
|
|
118
|
+
sample_on_posedge: bool = False,
|
|
119
|
+
begin_time: int | None = None,
|
|
120
|
+
end_time: int | None = None,
|
|
121
|
+
) -> dict[tuple[Any, ...], Waveform]:
|
|
122
|
+
matched_clocks = self.get_matched_signals(clock_pattern)
|
|
123
|
+
if not matched_clocks:
|
|
124
|
+
raise Exception(f'clock pattern {clock_pattern} can not match any signal')
|
|
125
|
+
|
|
126
|
+
matched_signals = self.get_matched_signals(pattern)
|
|
127
|
+
|
|
128
|
+
load_kwargs: dict[str, Any] = dict(
|
|
129
|
+
xz_value=xz_value,
|
|
130
|
+
signed=signed,
|
|
131
|
+
sample_on_posedge=sample_on_posedge,
|
|
132
|
+
begin_time=begin_time,
|
|
133
|
+
end_time=end_time,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if len(matched_clocks) == 1:
|
|
137
|
+
# Broadcast: single clock shared by all matched signals
|
|
138
|
+
clock_full_name = next(iter(matched_clocks.values()))
|
|
139
|
+
return {
|
|
140
|
+
k: self.load_waveform(s, clock_full_name, **load_kwargs)
|
|
141
|
+
for k, s in matched_signals.items()
|
|
142
|
+
}
|
|
143
|
+
else:
|
|
144
|
+
# Per-signal clock: keys must match exactly
|
|
145
|
+
if set(matched_clocks.keys()) != set(matched_signals.keys()):
|
|
146
|
+
raise Exception(
|
|
147
|
+
f'clock pattern {clock_pattern!r} matched keys {sorted(matched_clocks.keys())} '
|
|
148
|
+
f'which do not match signal pattern keys {sorted(matched_signals.keys())}'
|
|
149
|
+
)
|
|
150
|
+
return {
|
|
151
|
+
k: self.load_waveform(s, matched_clocks[k], **load_kwargs)
|
|
152
|
+
for k, s in matched_signals.items()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def close(self):
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
# ------------------------------------------------------------------
|
|
160
|
+
# High-level expression APIs
|
|
161
|
+
# ------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
def eval(
|
|
164
|
+
self,
|
|
165
|
+
expr: str,
|
|
166
|
+
clock: str,
|
|
167
|
+
xz_value: int = 0,
|
|
168
|
+
signed: bool = False,
|
|
169
|
+
sample_on_posedge: bool = False,
|
|
170
|
+
begin_time: int | None = None,
|
|
171
|
+
end_time: int | None = None,
|
|
172
|
+
mode: Literal['single', 'zip'] = 'single',
|
|
173
|
+
) -> Waveform | dict[tuple[Any, ...], Waveform]:
|
|
174
|
+
"""Evaluate a waveform expression string.
|
|
175
|
+
|
|
176
|
+
Wave signal paths embedded in *expr* are automatically extracted,
|
|
177
|
+
loaded, and substituted before evaluating the resulting Python
|
|
178
|
+
arithmetic expression.
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
expr:
|
|
183
|
+
Expression string.
|
|
184
|
+
Single mode example: ``"tb.dut.w_ptr[3:0] - tb.dut.r_ptr[3:0]"``.
|
|
185
|
+
Zip mode example: ``"tb.dut.fifo_{0..3}.w_ptr[3:0] - tb.dut.fifo_{0..3}.r_ptr[3:0]"``.
|
|
186
|
+
clock:
|
|
187
|
+
Clock signal used for all waveform loads.
|
|
188
|
+
mode:
|
|
189
|
+
``'single'`` (default) — every path must match exactly one
|
|
190
|
+
signal; returns a single :class:`Waveform`.
|
|
191
|
+
``'zip'`` — paths matching multiple signals must all share the
|
|
192
|
+
same set of pattern keys; single-match paths are broadcast;
|
|
193
|
+
returns ``dict[tuple, Waveform]``.
|
|
194
|
+
"""
|
|
195
|
+
substituted, path_entries = extract_wave_paths(expr)
|
|
196
|
+
|
|
197
|
+
load_kwargs: dict[str, Any] = dict(
|
|
198
|
+
clock=clock,
|
|
199
|
+
xz_value=xz_value,
|
|
200
|
+
signed=signed,
|
|
201
|
+
sample_on_posedge=sample_on_posedge,
|
|
202
|
+
begin_time=begin_time,
|
|
203
|
+
end_time=end_time,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Resolve each path to its matched signal(s)
|
|
207
|
+
matched_per_path: list[tuple[str, str, dict[tuple[Any, ...], str]]] = []
|
|
208
|
+
for placeholder, path in path_entries:
|
|
209
|
+
matched = self.get_matched_signals(path)
|
|
210
|
+
if not matched:
|
|
211
|
+
raise ValueError(f"path '{path}' matched no signals")
|
|
212
|
+
matched_per_path.append((placeholder, path, matched))
|
|
213
|
+
|
|
214
|
+
if mode == 'single':
|
|
215
|
+
for _placeholder, path, matched in matched_per_path:
|
|
216
|
+
if len(matched) > 1:
|
|
217
|
+
raise ValueError(
|
|
218
|
+
f"path '{path}' matched {len(matched)} signals in mode='single',"
|
|
219
|
+
f" use mode='zip'. Matched: {list(matched.values())}"
|
|
220
|
+
)
|
|
221
|
+
ns: dict[str, Any] = {
|
|
222
|
+
placeholder: self.load_waveform(next(iter(matched.values())), **load_kwargs)
|
|
223
|
+
for placeholder, _, matched in matched_per_path
|
|
224
|
+
}
|
|
225
|
+
try:
|
|
226
|
+
code = compile(ast.parse(substituted, mode='eval'), '<eval_expr>', 'eval')
|
|
227
|
+
return eval(code, {'__builtins__': {}}, ns) # noqa: S307
|
|
228
|
+
except Exception as exc:
|
|
229
|
+
raise ValueError(
|
|
230
|
+
f"failed to evaluate expression '{expr}' (substituted: '{substituted}')"
|
|
231
|
+
) from exc
|
|
232
|
+
|
|
233
|
+
elif mode == 'zip':
|
|
234
|
+
multi_paths = [(ph, p, m) for ph, p, m in matched_per_path if len(m) > 1]
|
|
235
|
+
single_paths = [(ph, p, m) for ph, p, m in matched_per_path if len(m) == 1]
|
|
236
|
+
|
|
237
|
+
if multi_paths:
|
|
238
|
+
# All multi-match paths must share identical key sets
|
|
239
|
+
ref_ph, ref_p, ref_matched = multi_paths[0]
|
|
240
|
+
ref_keys = set(ref_matched.keys())
|
|
241
|
+
for _ph, p, matched in multi_paths[1:]:
|
|
242
|
+
if set(matched.keys()) != ref_keys:
|
|
243
|
+
raise ValueError(
|
|
244
|
+
f'inconsistent match keys between paths: '
|
|
245
|
+
f"'{ref_p}' has keys {sorted(ref_keys)}, "
|
|
246
|
+
f"'{p}' has keys {sorted(matched.keys())}"
|
|
247
|
+
)
|
|
248
|
+
zip_keys: list[tuple[Any, ...]] = list(ref_keys)
|
|
249
|
+
else:
|
|
250
|
+
# All paths are single-match; degenerate to single behaviour
|
|
251
|
+
zip_keys = [()]
|
|
252
|
+
|
|
253
|
+
# Pre-load broadcast waveforms (single-match paths)
|
|
254
|
+
broadcast_ns: dict[str, Waveform] = {
|
|
255
|
+
placeholder: self.load_waveform(next(iter(matched.values())), **load_kwargs)
|
|
256
|
+
for placeholder, _, matched in single_paths
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
result: dict[tuple[Any, ...], Waveform] = {}
|
|
260
|
+
try:
|
|
261
|
+
code = compile(ast.parse(substituted, mode='eval'), '<eval_expr>', 'eval')
|
|
262
|
+
except SyntaxError as exc:
|
|
263
|
+
raise ValueError(
|
|
264
|
+
f"invalid expression '{expr}' (substituted: '{substituted}')"
|
|
265
|
+
) from exc
|
|
266
|
+
|
|
267
|
+
for key in zip_keys:
|
|
268
|
+
ns = dict(broadcast_ns)
|
|
269
|
+
for placeholder, _, matched in multi_paths:
|
|
270
|
+
ns[placeholder] = self.load_waveform(matched[key], **load_kwargs)
|
|
271
|
+
try:
|
|
272
|
+
result[key] = eval(code, {'__builtins__': {}}, ns) # noqa: S307
|
|
273
|
+
except Exception as exc:
|
|
274
|
+
raise ValueError(
|
|
275
|
+
f"failed to evaluate expression '{expr}' for key {key}"
|
|
276
|
+
) from exc
|
|
277
|
+
|
|
278
|
+
return result
|
|
279
|
+
|
|
280
|
+
else:
|
|
281
|
+
raise ValueError(f"unknown mode '{mode}', expected 'single' or 'zip'")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
# Matches a waveform signal path token in an expression.
|
|
6
|
+
# A valid path is one of:
|
|
7
|
+
# - Starts with $$ or $ (module search)
|
|
8
|
+
# - Starts with @ (regex pattern)
|
|
9
|
+
# - Contains at least one . separator (hierarchical path)
|
|
10
|
+
#
|
|
11
|
+
# Path structure:
|
|
12
|
+
# [$$|$|@] first_ident (.segment[{brace}])* ([N:M]|[N])?
|
|
13
|
+
#
|
|
14
|
+
# The trailing [N:M] / [N] is consumed as the signal range (not a bit-slice).
|
|
15
|
+
# Any further [N:M] after the placeholder in the resulting expression will be
|
|
16
|
+
# handled as a Python subscript on the Waveform object (i.e. bit-slicing).
|
|
17
|
+
_WAVE_PATH_RE = re.compile(
|
|
18
|
+
r'(?P<path>'
|
|
19
|
+
r'(?:[$]{1,2}|@)?' # optional prefix $$ / $ / @
|
|
20
|
+
r'(?:[A-Za-z_][A-Za-z0-9_]*)' # first identifier segment
|
|
21
|
+
r'(?:[.][A-Za-z_][A-Za-z0-9_{}.,]*)*' # dot-separated segments (allow braces, no [ ])
|
|
22
|
+
r'(?:\[\d+(?::\d+)?\])?' # optional trailing [N:M] or [N] range
|
|
23
|
+
r')'
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _is_wave_path(token: str) -> bool:
|
|
28
|
+
"""Return True if token looks like a waveform signal path."""
|
|
29
|
+
if token.startswith('$') or token.startswith('@'):
|
|
30
|
+
return True
|
|
31
|
+
# Must contain a dot (hierarchy) but not look like a floating-point literal
|
|
32
|
+
if '.' in token:
|
|
33
|
+
try:
|
|
34
|
+
float(token)
|
|
35
|
+
return False # it's a numeric literal like "1.0"
|
|
36
|
+
except ValueError:
|
|
37
|
+
return True
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def extract_wave_paths(expr: str) -> tuple[str, list[tuple[str, str]]]:
|
|
42
|
+
"""Parse *expr* and replace wave path tokens with placeholder identifiers.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
substituted_expr : str
|
|
47
|
+
The expression with every wave path replaced by ``__wave_N__``.
|
|
48
|
+
paths : list of (placeholder, original_path)
|
|
49
|
+
The ordered list of substitutions made.
|
|
50
|
+
"""
|
|
51
|
+
paths: list[tuple[str, str]] = []
|
|
52
|
+
counter = 0
|
|
53
|
+
|
|
54
|
+
def replacer(m: re.Match) -> str:
|
|
55
|
+
nonlocal counter
|
|
56
|
+
token = m.group('path')
|
|
57
|
+
if not _is_wave_path(token):
|
|
58
|
+
return token
|
|
59
|
+
placeholder = f'__wave_{counter}__'
|
|
60
|
+
paths.append((placeholder, token))
|
|
61
|
+
counter += 1
|
|
62
|
+
return placeholder
|
|
63
|
+
|
|
64
|
+
substituted = _WAVE_PATH_RE.sub(replacer, expr)
|
|
65
|
+
return substituted, paths
|
|
@@ -82,8 +82,13 @@ cdef extern from "npi_fsdb.h":
|
|
|
82
82
|
npiFsdbScopeFullName
|
|
83
83
|
npiFsdbScopeDefName
|
|
84
84
|
npiFsdbScopeType
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
|
|
86
|
+
ctypedef enum npiFsdbSigCompositeType_e:
|
|
87
|
+
npiFsdbSigCtArray
|
|
88
|
+
npiFsdbSigCtStruct
|
|
89
|
+
npiFsdbSigCtUnion
|
|
90
|
+
npiFsdbSigCtTaggedUnion
|
|
91
|
+
npiFsdbSigCtRecord
|
|
87
92
|
|
|
88
93
|
|
|
89
94
|
npiFsdbFileHandle npi_fsdb_open( const NPI_BYTE8* name )
|
|
@@ -113,3 +118,5 @@ cdef extern from "npi_fsdb.h":
|
|
|
113
118
|
npiFsdbSigHandle npi_fsdb_iter_sig_next( npiFsdbSigIter iter )
|
|
114
119
|
npiFsdbSigIter npi_fsdb_iter_sig( npiFsdbScopeHandle scope )
|
|
115
120
|
npiFsdbSigIter npi_fsdb_iter_member( npiFsdbSigHandle sig )
|
|
121
|
+
NPI_INT32 npi_fsdb_iter_scope_stop( npiFsdbScopeIter iter )
|
|
122
|
+
NPI_INT32 npi_fsdb_iter_sig_stop( npiFsdbSigIter iter )
|
|
@@ -10,37 +10,53 @@ from libc.stdlib cimport malloc, free
|
|
|
10
10
|
from libc.string cimport strcpy, strlen
|
|
11
11
|
from .npi_fsdb cimport *
|
|
12
12
|
|
|
13
|
+
|
|
13
14
|
cdef class NpiFsdbScope:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
cdef class NpiFsdbNormalScope(NpiFsdbScope):
|
|
14
19
|
cdef npiFsdbScopeHandle scope_handle
|
|
15
20
|
|
|
16
21
|
@staticmethod
|
|
17
22
|
cdef init(npiFsdbScopeHandle scope_handle):
|
|
18
|
-
scope =
|
|
23
|
+
scope = NpiFsdbNormalScope()
|
|
19
24
|
scope.scope_handle = scope_handle
|
|
20
25
|
return scope
|
|
21
26
|
|
|
22
27
|
def __init__(self):
|
|
23
28
|
self.scope_handle = NULL
|
|
24
29
|
|
|
25
|
-
def child_scope_list(self) -> list[NpiFsdbScope]:
|
|
30
|
+
def child_scope_list(self, include_signal_scope:bool = True) -> list[NpiFsdbScope]:
|
|
26
31
|
assert self.scope_handle != NULL
|
|
27
|
-
|
|
28
32
|
res = []
|
|
29
33
|
child_scope_handle_iter = npi_fsdb_iter_child_scope(self.scope_handle)
|
|
34
|
+
|
|
30
35
|
cdef npiFsdbScopeHandle child_scope_handle
|
|
31
36
|
while True:
|
|
32
37
|
child_scope_handle = npi_fsdb_iter_scope_next(child_scope_handle_iter)
|
|
33
38
|
if child_scope_handle == NULL:
|
|
34
39
|
break
|
|
35
|
-
res.append(
|
|
36
|
-
|
|
40
|
+
res.append(NpiFsdbNormalScope.init(child_scope_handle))
|
|
41
|
+
npi_fsdb_iter_scope_stop(child_scope_handle_iter)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
cdef npiFsdbSigHandle child_sig_handle
|
|
45
|
+
if include_signal_scope:
|
|
46
|
+
child_sig_handle_iter = npi_fsdb_iter_sig(self.scope_handle)
|
|
47
|
+
while True:
|
|
48
|
+
child_sig_handle = npi_fsdb_iter_sig_next(child_sig_handle_iter)
|
|
49
|
+
if child_sig_handle == NULL:
|
|
50
|
+
break
|
|
51
|
+
res.append(NpiFsdbSignalScope.init(child_sig_handle))
|
|
52
|
+
npi_fsdb_iter_sig_stop(child_sig_handle_iter)
|
|
37
53
|
return res
|
|
38
54
|
|
|
39
55
|
def signal_list(self) -> list[str]:
|
|
40
56
|
assert self.scope_handle != NULL
|
|
41
57
|
|
|
42
58
|
res = []
|
|
43
|
-
|
|
59
|
+
sig_handle_iter = npi_fsdb_iter_sig(self.scope_handle)
|
|
44
60
|
cdef npiFsdbSigHandle sig_handle
|
|
45
61
|
cdef const char* c_str
|
|
46
62
|
while True:
|
|
@@ -50,21 +66,92 @@ cdef class NpiFsdbScope:
|
|
|
50
66
|
c_str = npi_fsdb_sig_property_str(npiFsdbSigName, sig_handle)
|
|
51
67
|
name = c_str.decode('ascii')
|
|
52
68
|
res.append(name)
|
|
69
|
+
npi_fsdb_iter_sig_stop(sig_handle_iter)
|
|
53
70
|
|
|
54
71
|
return res
|
|
55
72
|
|
|
56
73
|
def name(self) -> str:
|
|
57
74
|
assert self.scope_handle != NULL
|
|
58
|
-
|
|
75
|
+
name = npi_fsdb_scope_property_str(npiFsdbScopeName, self.scope_handle).decode('ascii')
|
|
76
|
+
return name
|
|
59
77
|
|
|
60
78
|
def type(self) -> str:
|
|
61
79
|
assert self.scope_handle != NULL
|
|
62
|
-
|
|
80
|
+
type_str = npi_fsdb_scope_property_str(npiFsdbScopeType, self.scope_handle).decode('ascii')
|
|
81
|
+
return type_str
|
|
63
82
|
|
|
64
83
|
def def_name(self) -> str:
|
|
65
84
|
assert self.scope_handle != NULL
|
|
66
|
-
|
|
85
|
+
def_name = npi_fsdb_scope_property_str(npiFsdbScopeDefName, self.scope_handle).decode('ascii')
|
|
86
|
+
return def_name
|
|
87
|
+
|
|
88
|
+
cdef class NpiFsdbSignalScope(NpiFsdbScope):
|
|
89
|
+
cdef npiFsdbSigHandle sig_handle
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
cdef init(npiFsdbSigHandle sig_handle):
|
|
93
|
+
scope = NpiFsdbSignalScope()
|
|
94
|
+
scope.sig_handle = sig_handle
|
|
95
|
+
return scope
|
|
96
|
+
|
|
97
|
+
def __init__(self):
|
|
98
|
+
self.sig_handle = NULL
|
|
99
|
+
|
|
100
|
+
def child_scope_list(self, include_signal_scope:bool = True) -> list[NpiFsdbScope]:
|
|
101
|
+
assert self.sig_handle != NULL
|
|
102
|
+
|
|
103
|
+
cdef int has_member
|
|
104
|
+
cdef npiFsdbSigIter member_iter
|
|
105
|
+
cdef npiFsdbSigHandle member_handle
|
|
106
|
+
|
|
107
|
+
res = []
|
|
108
|
+
if include_signal_scope:
|
|
109
|
+
if npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member) and has_member:
|
|
110
|
+
member_iter = npi_fsdb_iter_member(self.sig_handle)
|
|
111
|
+
while True:
|
|
112
|
+
member_handle = npi_fsdb_iter_sig_next(member_iter)
|
|
113
|
+
if member_handle == NULL:
|
|
114
|
+
break
|
|
115
|
+
res.append(NpiFsdbSignalScope.init(member_handle))
|
|
116
|
+
npi_fsdb_iter_sig_stop(member_iter)
|
|
117
|
+
return res
|
|
118
|
+
|
|
119
|
+
def signal_list(self) -> list[str]:
|
|
120
|
+
assert self.sig_handle != NULL
|
|
121
|
+
|
|
122
|
+
cdef const char* c_str
|
|
123
|
+
cdef npiFsdbSigIter member_iter
|
|
124
|
+
cdef npiFsdbSigHandle member_handle
|
|
125
|
+
cdef int has_member
|
|
126
|
+
res = []
|
|
67
127
|
|
|
128
|
+
if npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member) and has_member:
|
|
129
|
+
member_iter = npi_fsdb_iter_member(self.sig_handle)
|
|
130
|
+
while True:
|
|
131
|
+
member_handle = npi_fsdb_iter_sig_next(member_iter)
|
|
132
|
+
if member_handle == NULL:
|
|
133
|
+
break
|
|
134
|
+
c_str = npi_fsdb_sig_property_str(npiFsdbSigName, member_handle)
|
|
135
|
+
name = c_str.decode('ascii')
|
|
136
|
+
name = name.split(".")
|
|
137
|
+
name = name[len(name)-1]
|
|
138
|
+
res.append(name)
|
|
139
|
+
npi_fsdb_iter_sig_stop(member_iter)
|
|
140
|
+
return res
|
|
141
|
+
|
|
142
|
+
def name(self) -> str:
|
|
143
|
+
assert self.sig_handle != NULL
|
|
144
|
+
name = npi_fsdb_sig_property_str(npiFsdbSigName, self.sig_handle).decode('ascii')
|
|
145
|
+
#TODO: this only works for non-array type (array's name is like data[0],can't be split by ".")
|
|
146
|
+
name = name.split(".")
|
|
147
|
+
name = name[len(name)-1]
|
|
148
|
+
return name
|
|
149
|
+
|
|
150
|
+
def type(self) -> str:
|
|
151
|
+
raise NotImplementedError("type property of signal scope is not supported")
|
|
152
|
+
|
|
153
|
+
def def_name(self) -> str:
|
|
154
|
+
raise NotImplementedError("def_name property of signal scope is not supported")
|
|
68
155
|
@cython.boundscheck(False) # 关闭边界检查以提升性能
|
|
69
156
|
@cython.wraparound(False) # 关闭负索引检查以提升性能
|
|
70
157
|
cdef inline cstr_to_ull(char* str, unsigned int xz_value, int max_bit_num = 2147483647):
|
|
@@ -150,7 +237,7 @@ cdef inline fsdb_read_value_change(vct_handle: npiFsdbVctHandle, begin_time, end
|
|
|
150
237
|
|
|
151
238
|
return result
|
|
152
239
|
|
|
153
|
-
cdef get_signal_handle_width(
|
|
240
|
+
cdef get_signal_handle_width(npiFsdbSigHandle signal_handle):
|
|
154
241
|
cdef int has_member
|
|
155
242
|
cdef int width
|
|
156
243
|
cdef npiFsdbSigIter sub_signal_iter
|
|
@@ -167,11 +254,33 @@ cdef get_signal_handle_width( npiFsdbSigHandle signal_handle):
|
|
|
167
254
|
width += get_signal_handle_width(sub_signal)
|
|
168
255
|
return width
|
|
169
256
|
|
|
257
|
+
cdef get_signal_handle_range(npiFsdbSigHandle signal_handle):
|
|
258
|
+
cdef int has_member
|
|
259
|
+
cdef int type
|
|
260
|
+
cdef int left_range, right_range
|
|
261
|
+
assert(npi_fsdb_sig_property(npiFsdbSigHasMember, signal_handle, &has_member))
|
|
262
|
+
assert(npi_fsdb_sig_property(npiFsdbSigLeftRange, signal_handle, &left_range))
|
|
263
|
+
assert(npi_fsdb_sig_property(npiFsdbSigRightRange, signal_handle, &right_range))
|
|
264
|
+
return (left_range, right_range)
|
|
265
|
+
|
|
266
|
+
if(has_member != 0):
|
|
267
|
+
assert(npi_fsdb_sig_property(npiFsdbSigCompositeType, signal_handle, &type))
|
|
268
|
+
if(type == <int>npiFsdbSigCtArray):
|
|
269
|
+
return (left_range, right_range)
|
|
270
|
+
return None
|
|
271
|
+
|
|
170
272
|
cdef class NpiFsdbReader:
|
|
171
273
|
cdef npiFsdbFileHandle fsdb_handle
|
|
172
274
|
cdef str file
|
|
173
275
|
|
|
174
276
|
def __init__(self, str file):
|
|
277
|
+
import os
|
|
278
|
+
|
|
279
|
+
# 检查VERDI_HOME环境变量
|
|
280
|
+
verdi_home = os.environ.get('VERDI_HOME')
|
|
281
|
+
if verdi_home:
|
|
282
|
+
npi_lib_path = os.path.join(verdi_home, 'share/NPI/lib/LINUX64/libNPI.so')
|
|
283
|
+
|
|
175
284
|
#cdef int argc = len(sys.argv)
|
|
176
285
|
#cdef char** argv = <char**>malloc((argc + 1) * sizeof(char*))
|
|
177
286
|
|
|
@@ -188,6 +297,8 @@ cdef class NpiFsdbReader:
|
|
|
188
297
|
file_str = file.encode('utf-8')
|
|
189
298
|
cdef char* file_s = file_str
|
|
190
299
|
self.fsdb_handle = npi_fsdb_open(file_s)
|
|
300
|
+
if(self.fsdb_handle == NULL):
|
|
301
|
+
raise OSError(f"Failed to open fsdb file :{file_str}")
|
|
191
302
|
|
|
192
303
|
|
|
193
304
|
|
|
@@ -195,10 +306,22 @@ cdef class NpiFsdbReader:
|
|
|
195
306
|
self,
|
|
196
307
|
str signal
|
|
197
308
|
) -> int:
|
|
309
|
+
|
|
310
|
+
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
311
|
+
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
312
|
+
|
|
313
|
+
cdef int width = get_signal_handle_width(signal_handle)
|
|
314
|
+
return width
|
|
198
315
|
|
|
316
|
+
def get_signal_range(
|
|
317
|
+
self,
|
|
318
|
+
str signal
|
|
319
|
+
) -> tuple[int,int]:
|
|
320
|
+
|
|
199
321
|
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
200
322
|
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
201
|
-
|
|
323
|
+
|
|
324
|
+
return get_signal_handle_range(signal_handle)
|
|
202
325
|
|
|
203
326
|
@cython.boundscheck(False) # 关闭边界检查以提升性能
|
|
204
327
|
@cython.wraparound(False) # 关闭负索引检查以提升性能
|
|
@@ -209,7 +332,7 @@ cdef class NpiFsdbReader:
|
|
|
209
332
|
unsigned long long end_time,
|
|
210
333
|
int xz_value
|
|
211
334
|
) -> np.ndarray:
|
|
212
|
-
|
|
335
|
+
|
|
213
336
|
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
214
337
|
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
215
338
|
cdef int width = get_signal_handle_width(signal_handle)
|
|
@@ -235,6 +358,7 @@ cdef class NpiFsdbReader:
|
|
|
235
358
|
stat = npi_fsdb_goto_time(signal_vct_handle, <npiFsdbTime> begin_time)
|
|
236
359
|
else:
|
|
237
360
|
stat = npi_fsdb_goto_next(signal_vct_handle)
|
|
361
|
+
|
|
238
362
|
if stat == 0:
|
|
239
363
|
break
|
|
240
364
|
|
|
@@ -245,6 +369,7 @@ cdef class NpiFsdbReader:
|
|
|
245
369
|
if cur_time > end_time:
|
|
246
370
|
break
|
|
247
371
|
time_array.push_back(cur_time)
|
|
372
|
+
first = False
|
|
248
373
|
|
|
249
374
|
if width == 1: # opt for clk
|
|
250
375
|
value_array[0].push_back(cstr_to_bit(<char*>cur_value.value.str, xz_value))
|
|
@@ -287,7 +412,8 @@ cdef class NpiFsdbReader:
|
|
|
287
412
|
top_scope_handle = npi_fsdb_iter_scope_next(top_scope_iter)
|
|
288
413
|
if top_scope_handle == NULL:
|
|
289
414
|
break
|
|
290
|
-
res.append(
|
|
415
|
+
res.append(NpiFsdbNormalScope.init(top_scope_handle))
|
|
416
|
+
npi_fsdb_iter_scope_stop(top_scope_iter)
|
|
291
417
|
return res
|
|
292
418
|
|
|
293
419
|
def min_time(self) -> int:
|
|
@@ -10,7 +10,7 @@ from ...scope import Scope
|
|
|
10
10
|
from ...signal import Signal
|
|
11
11
|
from ...waveform import Waveform
|
|
12
12
|
from ..base import Reader
|
|
13
|
-
from .npi_fsdb_reader import NpiFsdbReader, NpiFsdbScope
|
|
13
|
+
from .npi_fsdb_reader import NpiFsdbReader, NpiFsdbScope, NpiFsdbSignalScope
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class FsdbScope(Scope):
|
|
@@ -39,6 +39,13 @@ class FsdbScope(Scope):
|
|
|
39
39
|
def child_scope_list(self) -> Sequence[Scope]:
|
|
40
40
|
return [FsdbScope(c, self, self.reader) for c in self.handle.child_scope_list()]
|
|
41
41
|
|
|
42
|
+
@cached_property
|
|
43
|
+
def child_normal_scope_list(self) -> Sequence[Scope]:
|
|
44
|
+
return [
|
|
45
|
+
FsdbScope(c, self, self.reader)
|
|
46
|
+
for c in self.handle.child_scope_list(include_signal_scope=False)
|
|
47
|
+
]
|
|
48
|
+
|
|
42
49
|
@property
|
|
43
50
|
def type(self) -> str:
|
|
44
51
|
if not hasattr(self, '_type'):
|
|
@@ -65,8 +72,11 @@ class FsdbScope(Scope):
|
|
|
65
72
|
return self.parent_scope.end_time if self.parent_scope else 0
|
|
66
73
|
|
|
67
74
|
def preload_module_scope(self):
|
|
75
|
+
if isinstance(self.handle, NpiFsdbSignalScope):
|
|
76
|
+
return {}
|
|
77
|
+
|
|
68
78
|
preloaded_module_scope = defaultdict(list)
|
|
69
|
-
for c in self.
|
|
79
|
+
for c in self.child_normal_scope_list:
|
|
70
80
|
for module_name, module_scope_list in c.preload_module_scope().items():
|
|
71
81
|
preloaded_module_scope[module_name].extend(module_scope_list)
|
|
72
82
|
|
|
@@ -141,6 +151,9 @@ class FsdbReader(Reader):
|
|
|
141
151
|
def get_signal_width(self, signal: str) -> int:
|
|
142
152
|
return self.file_handle.get_signal_width(signal)
|
|
143
153
|
|
|
154
|
+
def get_signal_range(self, signal: str) -> tuple[int]:
|
|
155
|
+
return self.file_handle.get_signal_range(signal)
|
|
156
|
+
|
|
144
157
|
@property
|
|
145
158
|
def begin_time(self) -> str:
|
|
146
159
|
return self.file_handle.min_time()
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from abc import abstractmethod
|
|
4
|
-
from collections.abc import Sequence
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
import numpy as np
|
|
8
|
-
|
|
9
|
-
from ..scope import Scope, traverse_scope
|
|
10
|
-
from ..signal import Signal
|
|
11
|
-
from ..waveform import Waveform
|
|
12
|
-
from .pattern_parser import (
|
|
13
|
-
PatternMap,
|
|
14
|
-
expand_brace_pattern,
|
|
15
|
-
split_by_hierarchy,
|
|
16
|
-
)
|
|
17
|
-
from .value_change import value_change_to_value_array
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Reader:
|
|
21
|
-
def __init__(self):
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
def __enter__(self):
|
|
25
|
-
return self
|
|
26
|
-
|
|
27
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
28
|
-
self.close()
|
|
29
|
-
# exception wont be suppressed
|
|
30
|
-
return False
|
|
31
|
-
|
|
32
|
-
@abstractmethod
|
|
33
|
-
def load_waveform(
|
|
34
|
-
self,
|
|
35
|
-
signal: str,
|
|
36
|
-
clock: str,
|
|
37
|
-
xz_value: int = 0,
|
|
38
|
-
signed: bool = False,
|
|
39
|
-
sample_on_posedge: bool = False,
|
|
40
|
-
begin_time: int | None = None,
|
|
41
|
-
end_time: int | None = None,
|
|
42
|
-
) -> Waveform:
|
|
43
|
-
pass
|
|
44
|
-
|
|
45
|
-
@abstractmethod
|
|
46
|
-
def get_signal_width(self, signal: str) -> int:
|
|
47
|
-
pass
|
|
48
|
-
|
|
49
|
-
@staticmethod
|
|
50
|
-
def value_change_to_waveform(
|
|
51
|
-
value_change: np.ndarray,
|
|
52
|
-
clock_changes: np.ndarray,
|
|
53
|
-
width: int | None,
|
|
54
|
-
signed: bool,
|
|
55
|
-
sample_on_posedge: bool = False,
|
|
56
|
-
signal: str = '',
|
|
57
|
-
) -> Waveform:
|
|
58
|
-
value, clock, time = value_change_to_value_array(
|
|
59
|
-
value_change, clock_changes, sample_on_posedge=sample_on_posedge
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
return Waveform(
|
|
63
|
-
value=value,
|
|
64
|
-
clock=clock,
|
|
65
|
-
time=time,
|
|
66
|
-
signal=Signal(signal, width, signed),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
@abstractmethod
|
|
70
|
-
def top_scope_list(self) -> Sequence[Scope]:
|
|
71
|
-
pass
|
|
72
|
-
|
|
73
|
-
def get_matched_signals(
|
|
74
|
-
self,
|
|
75
|
-
pattern: str,
|
|
76
|
-
) -> dict[tuple[Any, ...], str]:
|
|
77
|
-
def combine_dict(
|
|
78
|
-
dict1: dict[tuple[Any, ...], str],
|
|
79
|
-
dict2: dict[tuple[Any, ...], str],
|
|
80
|
-
) -> dict[tuple[Any, ...], str]:
|
|
81
|
-
common_keys = set(dict1.keys()).intersection(dict2.keys())
|
|
82
|
-
|
|
83
|
-
if common_keys:
|
|
84
|
-
signal1s = [dict1[k] for k in common_keys]
|
|
85
|
-
signal2s = [dict2[k] for k in common_keys]
|
|
86
|
-
raise Exception(
|
|
87
|
-
'found more than one signal with the same keys: '
|
|
88
|
-
f'keys:{list(common_keys)} , signals:{signal1s + signal2s}'
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
return {**dict1, **dict2}
|
|
92
|
-
|
|
93
|
-
pattern = pattern.strip()
|
|
94
|
-
expanded_pattern_list: list[PatternMap] = [
|
|
95
|
-
expand_brace_pattern(p) for p in split_by_hierarchy(pattern)
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
matched_signals: dict[tuple[Any, ...], str] = {}
|
|
99
|
-
for scope in self.top_scope_list():
|
|
100
|
-
matched_signals = combine_dict(
|
|
101
|
-
matched_signals,
|
|
102
|
-
traverse_scope(scope, expanded_pattern_list),
|
|
103
|
-
)
|
|
104
|
-
return matched_signals
|
|
105
|
-
|
|
106
|
-
def load_matched_waveforms(
|
|
107
|
-
self,
|
|
108
|
-
pattern: str,
|
|
109
|
-
clock_pattern: str,
|
|
110
|
-
xz_value: int = 0,
|
|
111
|
-
signed: bool = False,
|
|
112
|
-
sample_on_posedge: bool = False,
|
|
113
|
-
begin_time: int | None = None,
|
|
114
|
-
end_time: int | None = None,
|
|
115
|
-
) -> dict[tuple[Any, ...], Waveform]:
|
|
116
|
-
clock_patterns = self.get_matched_signals(clock_pattern)
|
|
117
|
-
if not clock_patterns:
|
|
118
|
-
raise Exception(f'clock pattern {clock_pattern} can not match any signal')
|
|
119
|
-
if len(clock_patterns) > 1:
|
|
120
|
-
raise Exception(
|
|
121
|
-
f'clock pattern {clock_pattern} match more than one signal: {clock_patterns}'
|
|
122
|
-
)
|
|
123
|
-
clock_full_name = next(iter(clock_patterns.values()))
|
|
124
|
-
|
|
125
|
-
matched_signals = self.get_matched_signals(pattern)
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
k: self.load_waveform(
|
|
129
|
-
s,
|
|
130
|
-
clock_full_name,
|
|
131
|
-
xz_value=xz_value,
|
|
132
|
-
signed=signed,
|
|
133
|
-
sample_on_posedge=sample_on_posedge,
|
|
134
|
-
begin_time=begin_time,
|
|
135
|
-
end_time=end_time,
|
|
136
|
-
)
|
|
137
|
-
for k, s in matched_signals.items()
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
@abstractmethod
|
|
141
|
-
def close(self):
|
|
142
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|