wavekit 0.5.1__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wavekit-0.5.1 → wavekit-0.5.3}/PKG-INFO +1 -1
- {wavekit-0.5.1 → wavekit-0.5.3}/pyproject.toml +1 -1
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/readers/base.py +3 -3
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +126 -252
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/readers/fsdb/reader.py +85 -78
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/readers/vcd/reader.py +0 -3
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/scope.py +84 -82
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/signal.py +9 -4
- {wavekit-0.5.1 → wavekit-0.5.3}/LICENSE +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/README.md +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/build.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/__init__.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/pattern/__init__.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/pattern/dsl.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/pattern/engine.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/pattern/instance.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/pattern/result.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/pattern/steps.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/readers/expr_parser.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/readers/pattern_parser.py +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/readers/value_change.pyx +0 -0
- {wavekit-0.5.1 → wavekit-0.5.3}/src/wavekit/waveform.py +0 -0
|
@@ -38,7 +38,7 @@ class Reader:
|
|
|
38
38
|
If the bit-range suffix is omitted and the file stores the signal with a
|
|
39
39
|
range, the range is appended automatically.
|
|
40
40
|
|
|
41
|
-
Pattern syntax (used by :meth:`get_matched_signals`,
|
|
41
|
+
Pattern syntax (used by :meth:`get_matched_signals`, :meth:`get_matched_scopes`,
|
|
42
42
|
:meth:`load_matched_waveforms`, :meth:`eval`)
|
|
43
43
|
-------------------------------------------------
|
|
44
44
|
* ``{a,b,c}`` — matches ``a``, ``b``, or ``c``; captures each as a key.
|
|
@@ -233,7 +233,7 @@ class Reader:
|
|
|
233
233
|
)
|
|
234
234
|
return matched_signals
|
|
235
235
|
|
|
236
|
-
def
|
|
236
|
+
def get_matched_scopes(
|
|
237
237
|
self,
|
|
238
238
|
pattern: str,
|
|
239
239
|
root_scope: Scope | None = None,
|
|
@@ -270,7 +270,7 @@ class Reader:
|
|
|
270
270
|
::
|
|
271
271
|
|
|
272
272
|
# Find all fifo_N sub-scopes under tb.dut
|
|
273
|
-
scopes = reader.
|
|
273
|
+
scopes = reader.get_matched_scopes("tb.dut.fifo_{0..3}")
|
|
274
274
|
for (idx,), scope in scopes.items():
|
|
275
275
|
waves = reader.load_matched_waveforms(
|
|
276
276
|
"w_ptr[2:0]", clock_pattern="clk", root_scope=scope
|
|
@@ -3,7 +3,6 @@ import math
|
|
|
3
3
|
import sys
|
|
4
4
|
import numpy as np
|
|
5
5
|
cimport numpy as np
|
|
6
|
-
#from cpython import PyBytes_FromString
|
|
7
6
|
import cython
|
|
8
7
|
from libcpp.vector cimport vector
|
|
9
8
|
from libc.stdlib cimport malloc, free
|
|
@@ -17,165 +16,159 @@ NPI_FSDB_CT_TAGGED_UNION = <int>npiFsdbSigCtTaggedUnion
|
|
|
17
16
|
NPI_FSDB_CT_RECORD = <int>npiFsdbSigCtRecord
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
cdef class
|
|
21
|
-
|
|
19
|
+
cdef class NpiFsdbSignal:
|
|
20
|
+
"""Wraps a npiFsdbSigHandle — a single signal (leaf or composite) in the FSDB hierarchy."""
|
|
21
|
+
cdef npiFsdbSigHandle sig_handle
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
cdef init(npiFsdbSigHandle sig_handle):
|
|
25
|
+
sig = NpiFsdbSignal()
|
|
26
|
+
sig.sig_handle = sig_handle
|
|
27
|
+
return sig
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.sig_handle = NULL
|
|
31
|
+
|
|
32
|
+
def name(self) -> str:
|
|
33
|
+
assert self.sig_handle != NULL
|
|
34
|
+
name = npi_fsdb_sig_property_str(npiFsdbSigName, self.sig_handle).decode('ascii')
|
|
35
|
+
# NPI returns fully-qualified names like "tb.dut.field"; take only the last component.
|
|
36
|
+
# Array elements are named "a[0]", "a[0][1]" etc. — splitting by "." is safe since
|
|
37
|
+
# brackets cannot contain ".".
|
|
38
|
+
name = name.split(".")
|
|
39
|
+
name = name[len(name)-1]
|
|
40
|
+
return name
|
|
41
|
+
|
|
42
|
+
def has_member(self) -> bool:
|
|
43
|
+
assert self.sig_handle != NULL
|
|
44
|
+
cdef int has_member
|
|
45
|
+
if npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member):
|
|
46
|
+
return has_member != 0
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
def composite_type(self):
|
|
50
|
+
"""Return the npiFsdbSigCompositeType_e int value, or None if not composite."""
|
|
51
|
+
assert self.sig_handle != NULL
|
|
52
|
+
cdef int has_member
|
|
53
|
+
cdef int ct
|
|
54
|
+
if not (npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member) and has_member):
|
|
55
|
+
return None
|
|
56
|
+
if npi_fsdb_sig_property(npiFsdbSigCompositeType, self.sig_handle, &ct):
|
|
57
|
+
return ct
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def width(self) -> int:
|
|
61
|
+
"""Return the total bit-width of this signal (sum of all leaf members for composites)."""
|
|
62
|
+
assert self.sig_handle != NULL
|
|
63
|
+
cdef int has_member
|
|
64
|
+
cdef int w
|
|
65
|
+
cdef npiFsdbSigIter sub_signal_iter
|
|
66
|
+
cdef npiFsdbSigHandle sub_signal
|
|
67
|
+
assert npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member)
|
|
68
|
+
if has_member == 0:
|
|
69
|
+
assert npi_fsdb_sig_property(npiFsdbSigRangeSize, self.sig_handle, &w)
|
|
70
|
+
return w
|
|
71
|
+
else:
|
|
72
|
+
sub_signal_iter = npi_fsdb_iter_member(self.sig_handle)
|
|
73
|
+
assert sub_signal_iter != NULL
|
|
74
|
+
w = 0
|
|
75
|
+
while (sub_signal := npi_fsdb_iter_sig_next(sub_signal_iter)):
|
|
76
|
+
w += NpiFsdbSignal.init(sub_signal).width()
|
|
77
|
+
return w
|
|
78
|
+
|
|
79
|
+
def range(self):
|
|
80
|
+
"""Return the (high, low) bit-range tuple, or None for non-array composites."""
|
|
81
|
+
assert self.sig_handle != NULL
|
|
82
|
+
cdef int has_member
|
|
83
|
+
cdef int ct
|
|
84
|
+
cdef int left_range, right_range
|
|
85
|
+
assert npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member)
|
|
86
|
+
assert npi_fsdb_sig_property(npiFsdbSigLeftRange, self.sig_handle, &left_range)
|
|
87
|
+
assert npi_fsdb_sig_property(npiFsdbSigRightRange, self.sig_handle, &right_range)
|
|
88
|
+
if has_member != 0:
|
|
89
|
+
assert npi_fsdb_sig_property(npiFsdbSigCompositeType, self.sig_handle, &ct)
|
|
90
|
+
if ct == <int>npiFsdbSigCtArray:
|
|
91
|
+
return (left_range, right_range)
|
|
92
|
+
return None
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def member_list(self) -> list:
|
|
96
|
+
"""Return direct member NpiFsdbSignal objects for composite signals."""
|
|
97
|
+
assert self.sig_handle != NULL
|
|
98
|
+
cdef int has_member
|
|
99
|
+
cdef npiFsdbSigIter member_iter
|
|
100
|
+
cdef npiFsdbSigHandle member_handle
|
|
101
|
+
|
|
102
|
+
res = []
|
|
103
|
+
if npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member) and has_member:
|
|
104
|
+
member_iter = npi_fsdb_iter_member(self.sig_handle)
|
|
105
|
+
while True:
|
|
106
|
+
member_handle = npi_fsdb_iter_sig_next(member_iter)
|
|
107
|
+
if member_handle == NULL:
|
|
108
|
+
break
|
|
109
|
+
res.append(NpiFsdbSignal.init(member_handle))
|
|
110
|
+
npi_fsdb_iter_sig_stop(member_iter)
|
|
111
|
+
return res
|
|
22
112
|
|
|
23
113
|
|
|
24
|
-
cdef class
|
|
114
|
+
cdef class NpiFsdbScope:
|
|
115
|
+
"""Wraps a npiFsdbScopeHandle — a scope node (module/block) in the FSDB hierarchy."""
|
|
25
116
|
cdef npiFsdbScopeHandle scope_handle
|
|
26
117
|
|
|
27
118
|
@staticmethod
|
|
28
119
|
cdef init(npiFsdbScopeHandle scope_handle):
|
|
29
|
-
scope =
|
|
120
|
+
scope = NpiFsdbScope()
|
|
30
121
|
scope.scope_handle = scope_handle
|
|
31
122
|
return scope
|
|
32
123
|
|
|
33
124
|
def __init__(self):
|
|
34
125
|
self.scope_handle = NULL
|
|
35
126
|
|
|
36
|
-
def child_scope_list(self
|
|
127
|
+
def child_scope_list(self) -> list:
|
|
128
|
+
"""Return direct child NpiFsdbScope nodes (not signals)."""
|
|
37
129
|
assert self.scope_handle != NULL
|
|
38
130
|
res = []
|
|
39
131
|
child_scope_handle_iter = npi_fsdb_iter_child_scope(self.scope_handle)
|
|
40
|
-
|
|
132
|
+
|
|
41
133
|
cdef npiFsdbScopeHandle child_scope_handle
|
|
42
134
|
while True:
|
|
43
135
|
child_scope_handle = npi_fsdb_iter_scope_next(child_scope_handle_iter)
|
|
44
136
|
if child_scope_handle == NULL:
|
|
45
137
|
break
|
|
46
|
-
res.append(
|
|
138
|
+
res.append(NpiFsdbScope.init(child_scope_handle))
|
|
47
139
|
npi_fsdb_iter_scope_stop(child_scope_handle_iter)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
cdef npiFsdbSigHandle child_sig_handle
|
|
51
|
-
if include_signal_scope:
|
|
52
|
-
child_sig_handle_iter = npi_fsdb_iter_sig(self.scope_handle)
|
|
53
|
-
while True:
|
|
54
|
-
child_sig_handle = npi_fsdb_iter_sig_next(child_sig_handle_iter)
|
|
55
|
-
if child_sig_handle == NULL:
|
|
56
|
-
break
|
|
57
|
-
res.append(NpiFsdbSignalScope.init(child_sig_handle))
|
|
58
|
-
npi_fsdb_iter_sig_stop(child_sig_handle_iter)
|
|
59
140
|
return res
|
|
60
141
|
|
|
61
|
-
def signal_list(self) -> list
|
|
142
|
+
def signal_list(self) -> list:
|
|
143
|
+
"""Return direct child NpiFsdbSignal objects declared in this scope."""
|
|
62
144
|
assert self.scope_handle != NULL
|
|
63
|
-
|
|
64
145
|
res = []
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
cdef
|
|
146
|
+
child_sig_handle_iter = npi_fsdb_iter_sig(self.scope_handle)
|
|
147
|
+
|
|
148
|
+
cdef npiFsdbSigHandle child_sig_handle
|
|
68
149
|
while True:
|
|
69
|
-
|
|
70
|
-
if
|
|
150
|
+
child_sig_handle = npi_fsdb_iter_sig_next(child_sig_handle_iter)
|
|
151
|
+
if child_sig_handle == NULL:
|
|
71
152
|
break
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
res.append(name)
|
|
75
|
-
npi_fsdb_iter_sig_stop(sig_handle_iter)
|
|
76
|
-
|
|
153
|
+
res.append(NpiFsdbSignal.init(child_sig_handle))
|
|
154
|
+
npi_fsdb_iter_sig_stop(child_sig_handle_iter)
|
|
77
155
|
return res
|
|
78
156
|
|
|
79
157
|
def name(self) -> str:
|
|
80
158
|
assert self.scope_handle != NULL
|
|
81
|
-
|
|
82
|
-
return name
|
|
159
|
+
return npi_fsdb_scope_property_str(npiFsdbScopeName, self.scope_handle).decode('ascii')
|
|
83
160
|
|
|
84
161
|
def type(self) -> str:
|
|
85
162
|
assert self.scope_handle != NULL
|
|
86
|
-
|
|
87
|
-
return type_str
|
|
88
|
-
|
|
89
|
-
def def_name(self) -> str:
|
|
90
|
-
assert self.scope_handle != NULL
|
|
91
|
-
def_name = npi_fsdb_scope_property_str(npiFsdbScopeDefName, self.scope_handle).decode('ascii')
|
|
92
|
-
return def_name
|
|
93
|
-
|
|
94
|
-
cdef class NpiFsdbSignalScope(NpiFsdbScope):
|
|
95
|
-
cdef npiFsdbSigHandle sig_handle
|
|
96
|
-
|
|
97
|
-
@staticmethod
|
|
98
|
-
cdef init(npiFsdbSigHandle sig_handle):
|
|
99
|
-
scope = NpiFsdbSignalScope()
|
|
100
|
-
scope.sig_handle = sig_handle
|
|
101
|
-
return scope
|
|
102
|
-
|
|
103
|
-
def __init__(self):
|
|
104
|
-
self.sig_handle = NULL
|
|
163
|
+
return npi_fsdb_scope_property_str(npiFsdbScopeType, self.scope_handle).decode('ascii')
|
|
105
164
|
|
|
106
|
-
def
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
cdef int has_member
|
|
110
|
-
cdef npiFsdbSigIter member_iter
|
|
111
|
-
cdef npiFsdbSigHandle member_handle
|
|
112
|
-
|
|
113
|
-
res = []
|
|
114
|
-
if include_signal_scope:
|
|
115
|
-
if npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member) and has_member:
|
|
116
|
-
member_iter = npi_fsdb_iter_member(self.sig_handle)
|
|
117
|
-
while True:
|
|
118
|
-
member_handle = npi_fsdb_iter_sig_next(member_iter)
|
|
119
|
-
if member_handle == NULL:
|
|
120
|
-
break
|
|
121
|
-
res.append(NpiFsdbSignalScope.init(member_handle))
|
|
122
|
-
npi_fsdb_iter_sig_stop(member_iter)
|
|
123
|
-
return res
|
|
124
|
-
|
|
125
|
-
def signal_list(self) -> list[str]:
|
|
126
|
-
assert self.sig_handle != NULL
|
|
127
|
-
|
|
128
|
-
cdef const char* c_str
|
|
129
|
-
cdef npiFsdbSigIter member_iter
|
|
130
|
-
cdef npiFsdbSigHandle member_handle
|
|
131
|
-
cdef int has_member
|
|
132
|
-
res = []
|
|
133
|
-
|
|
134
|
-
if npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member) and has_member:
|
|
135
|
-
member_iter = npi_fsdb_iter_member(self.sig_handle)
|
|
136
|
-
while True:
|
|
137
|
-
member_handle = npi_fsdb_iter_sig_next(member_iter)
|
|
138
|
-
if member_handle == NULL:
|
|
139
|
-
break
|
|
140
|
-
c_str = npi_fsdb_sig_property_str(npiFsdbSigName, member_handle)
|
|
141
|
-
name = c_str.decode('ascii')
|
|
142
|
-
name = name.split(".")
|
|
143
|
-
name = name[len(name)-1]
|
|
144
|
-
res.append(name)
|
|
145
|
-
npi_fsdb_iter_sig_stop(member_iter)
|
|
146
|
-
return res
|
|
147
|
-
|
|
148
|
-
def name(self) -> str:
|
|
149
|
-
assert self.sig_handle != NULL
|
|
150
|
-
name = npi_fsdb_sig_property_str(npiFsdbSigName, self.sig_handle).decode('ascii')
|
|
151
|
-
#TODO: this only works for non-array type (array's name is like data[0],can't be split by ".")
|
|
152
|
-
name = name.split(".")
|
|
153
|
-
name = name[len(name)-1]
|
|
154
|
-
return name
|
|
155
|
-
|
|
156
|
-
def has_member(self) -> bool:
|
|
157
|
-
assert self.sig_handle != NULL
|
|
158
|
-
cdef int has_member
|
|
159
|
-
if npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member):
|
|
160
|
-
return has_member != 0
|
|
161
|
-
return False
|
|
162
|
-
|
|
163
|
-
def composite_type(self):
|
|
164
|
-
"""Return the npiFsdbSigCompositeType_e int value, or None if not composite."""
|
|
165
|
-
assert self.sig_handle != NULL
|
|
166
|
-
cdef int has_member
|
|
167
|
-
cdef int ct
|
|
168
|
-
if not (npi_fsdb_sig_property(npiFsdbSigHasMember, self.sig_handle, &has_member) and has_member):
|
|
165
|
+
def def_name(self) -> str | None:
|
|
166
|
+
"""Return the module definition name, or None if this scope is not a module."""
|
|
167
|
+
if self.type() != 'npiFsdbScopeSvModule':
|
|
169
168
|
return None
|
|
170
|
-
|
|
171
|
-
return ct
|
|
172
|
-
return None
|
|
169
|
+
return npi_fsdb_scope_property_str(npiFsdbScopeDefName, self.scope_handle).decode('ascii')
|
|
173
170
|
|
|
174
|
-
def type(self) -> str:
|
|
175
|
-
raise NotImplementedError("type property of signal scope is not supported")
|
|
176
171
|
|
|
177
|
-
def def_name(self) -> str:
|
|
178
|
-
raise NotImplementedError("def_name property of signal scope is not supported")
|
|
179
172
|
@cython.boundscheck(False) # 关闭边界检查以提升性能
|
|
180
173
|
@cython.wraparound(False) # 关闭负索引检查以提升性能
|
|
181
174
|
cdef inline cstr_to_ull(char* str, unsigned int xz_value, int max_bit_num = 2147483647):
|
|
@@ -210,88 +203,6 @@ cdef inline cstr_to_bit(char* str, unsigned int xz_value):
|
|
|
210
203
|
value = xz_value
|
|
211
204
|
return value
|
|
212
205
|
|
|
213
|
-
cdef inline fsdb_read_value_change(vct_handle: npiFsdbVctHandle, begin_time, end_time, width:int, xz_value:int):
|
|
214
|
-
|
|
215
|
-
cdef npiFsdbTime cur_time
|
|
216
|
-
cdef npiFsdbValue cur_value
|
|
217
|
-
cdef int stat
|
|
218
|
-
|
|
219
|
-
cdef vector[unsigned long long] time_array
|
|
220
|
-
|
|
221
|
-
cdef int value_array_num = math.ceil(width/64)
|
|
222
|
-
assert width <= 64*64
|
|
223
|
-
cdef vector[unsigned long long] value_array[64]
|
|
224
|
-
|
|
225
|
-
cdef int first = 1
|
|
226
|
-
cdef int i,j
|
|
227
|
-
cdef unsigned long long cur_int_value
|
|
228
|
-
while True:
|
|
229
|
-
if first:
|
|
230
|
-
if begin_time is None:
|
|
231
|
-
stat = npi_fsdb_goto_first(vct_handle)
|
|
232
|
-
else:
|
|
233
|
-
stat = npi_fsdb_goto_time(vct_handle, <npiFsdbTime> begin_time)
|
|
234
|
-
else:
|
|
235
|
-
stat = npi_fsdb_goto_next(vct_handle)
|
|
236
|
-
if stat == 0:
|
|
237
|
-
break
|
|
238
|
-
|
|
239
|
-
cur_value.format = npiFsdbBinStrVal
|
|
240
|
-
npi_fsdb_vct_time(vct_handle, &cur_time)
|
|
241
|
-
npi_fsdb_vct_value(vct_handle, &cur_value)
|
|
242
|
-
if cur_time > end_time:
|
|
243
|
-
break
|
|
244
|
-
|
|
245
|
-
time_array.push_back(cur_time)
|
|
246
|
-
if width == 1: # opt for clk
|
|
247
|
-
value_array[0].push_back(cstr_to_bit(<char*>cur_value.value.str, xz_value))
|
|
248
|
-
else:
|
|
249
|
-
cur_lllint_value = 0
|
|
250
|
-
for i in range(value_array_num):
|
|
251
|
-
value_array[i].push_back(cstr_to_ull(<char*>cur_value.value.str + 64*i, xz_value, 64))
|
|
252
|
-
|
|
253
|
-
result = np.zeros((value_array[0].size(), 2), dtype=np.uint64 if width <= 64 else np.object_) # 创建一个 (n, 2) 的二维数组,dtype 为 int32
|
|
254
|
-
for j in range(value_array[0].size()):
|
|
255
|
-
result[j][0] = time_array[j]
|
|
256
|
-
if width <= 64:
|
|
257
|
-
result[j][1] = value_array[0][j]
|
|
258
|
-
else:
|
|
259
|
-
for i in range(value_array_num):
|
|
260
|
-
result[j][1] = (result[j][1] << 64) + value_array[i][j]
|
|
261
|
-
|
|
262
|
-
return result
|
|
263
|
-
|
|
264
|
-
cdef get_signal_handle_width(npiFsdbSigHandle signal_handle):
|
|
265
|
-
cdef int has_member
|
|
266
|
-
cdef int width
|
|
267
|
-
cdef npiFsdbSigIter sub_signal_iter
|
|
268
|
-
cdef npiFsdbSigHandle sub_signal
|
|
269
|
-
assert(npi_fsdb_sig_property(npiFsdbSigHasMember, signal_handle, &has_member))
|
|
270
|
-
if(has_member == 0):
|
|
271
|
-
assert(npi_fsdb_sig_property(npiFsdbSigRangeSize, signal_handle, &width))
|
|
272
|
-
return width
|
|
273
|
-
else:
|
|
274
|
-
sub_signal_iter = npi_fsdb_iter_member(signal_handle)
|
|
275
|
-
assert(sub_signal_iter != NULL)
|
|
276
|
-
width = 0
|
|
277
|
-
while(sub_signal := npi_fsdb_iter_sig_next(sub_signal_iter)):
|
|
278
|
-
width += get_signal_handle_width(sub_signal)
|
|
279
|
-
return width
|
|
280
|
-
|
|
281
|
-
cdef get_signal_handle_range(npiFsdbSigHandle signal_handle):
|
|
282
|
-
cdef int has_member
|
|
283
|
-
cdef int type
|
|
284
|
-
cdef int left_range, right_range
|
|
285
|
-
assert(npi_fsdb_sig_property(npiFsdbSigHasMember, signal_handle, &has_member))
|
|
286
|
-
assert(npi_fsdb_sig_property(npiFsdbSigLeftRange, signal_handle, &left_range))
|
|
287
|
-
assert(npi_fsdb_sig_property(npiFsdbSigRightRange, signal_handle, &right_range))
|
|
288
|
-
|
|
289
|
-
if has_member != 0:
|
|
290
|
-
assert(npi_fsdb_sig_property(npiFsdbSigCompositeType, signal_handle, &type))
|
|
291
|
-
if type == <int>npiFsdbSigCtArray:
|
|
292
|
-
return (left_range, right_range)
|
|
293
|
-
return None
|
|
294
|
-
return None
|
|
295
206
|
|
|
296
207
|
cdef class NpiFsdbReader:
|
|
297
208
|
cdef npiFsdbFileHandle fsdb_handle
|
|
@@ -299,69 +210,36 @@ cdef class NpiFsdbReader:
|
|
|
299
210
|
|
|
300
211
|
def __init__(self, str file):
|
|
301
212
|
import os
|
|
302
|
-
|
|
213
|
+
|
|
303
214
|
# 检查VERDI_HOME环境变量
|
|
304
215
|
verdi_home = os.environ.get('VERDI_HOME')
|
|
305
216
|
if verdi_home:
|
|
306
217
|
npi_lib_path = os.path.join(verdi_home, 'share/NPI/lib/LINUX64/libNPI.so')
|
|
307
|
-
|
|
308
|
-
#cdef int argc = len(sys.argv)
|
|
309
|
-
#cdef char** argv = <char**>malloc((argc + 1) * sizeof(char*))
|
|
310
|
-
|
|
311
|
-
#for i in range(argc):
|
|
312
|
-
# argv[i] = <char*>malloc(strlen(sys.argv[i].encode('ascii')))
|
|
313
|
-
# strcpy(argv[i], sys.argv[i].encode('ascii'))
|
|
314
|
-
#argv[argc] = NULL # Null-terminate the array
|
|
315
|
-
|
|
316
|
-
#npi_init(argc, argv)
|
|
317
|
-
#self.file = file
|
|
318
|
-
#free(argv)
|
|
319
218
|
|
|
320
|
-
#cdef npiFsdbFileHandle fsdb_handle
|
|
321
219
|
file_str = file.encode('utf-8')
|
|
322
220
|
cdef char* file_s = file_str
|
|
323
221
|
self.fsdb_handle = npi_fsdb_open(file_s)
|
|
324
222
|
if(self.fsdb_handle == NULL):
|
|
325
223
|
raise OSError(f"Failed to open fsdb file :{file_str}")
|
|
326
224
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
self,
|
|
331
|
-
str signal
|
|
332
|
-
) -> int:
|
|
333
|
-
|
|
334
|
-
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
335
|
-
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
336
|
-
|
|
337
|
-
cdef int width = get_signal_handle_width(signal_handle)
|
|
338
|
-
return width
|
|
339
|
-
|
|
340
|
-
def get_signal_range(
|
|
341
|
-
self,
|
|
342
|
-
str signal
|
|
343
|
-
) -> tuple[int,int]:
|
|
344
|
-
|
|
345
|
-
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
225
|
+
def get_signal(self, str signal) -> NpiFsdbSignal:
|
|
226
|
+
"""Look up a signal by its full hierarchical path and return an NpiFsdbSignal handle."""
|
|
227
|
+
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
346
228
|
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
347
|
-
|
|
348
|
-
return get_signal_handle_range(signal_handle)
|
|
229
|
+
return NpiFsdbSignal.init(signal_handle)
|
|
349
230
|
|
|
350
231
|
@cython.boundscheck(False) # 关闭边界检查以提升性能
|
|
351
232
|
@cython.wraparound(False) # 关闭负索引检查以提升性能
|
|
352
233
|
def load_value_change(
|
|
353
234
|
self,
|
|
354
|
-
|
|
235
|
+
NpiFsdbSignal signal,
|
|
355
236
|
unsigned long long begin_time,
|
|
356
237
|
unsigned long long end_time,
|
|
357
238
|
int xz_value
|
|
358
239
|
) -> np.ndarray:
|
|
359
|
-
|
|
360
|
-
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
361
|
-
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
362
|
-
cdef int width = get_signal_handle_width(signal_handle)
|
|
363
240
|
|
|
364
|
-
cdef
|
|
241
|
+
cdef int width = signal.width()
|
|
242
|
+
cdef npiFsdbVctHandle signal_vct_handle = npi_fsdb_create_vct(signal.sig_handle)
|
|
365
243
|
cdef npiFsdbTime cur_time
|
|
366
244
|
cdef npiFsdbValue cur_value
|
|
367
245
|
cdef int stat
|
|
@@ -382,7 +260,7 @@ cdef class NpiFsdbReader:
|
|
|
382
260
|
stat = npi_fsdb_goto_time(signal_vct_handle, <npiFsdbTime> begin_time)
|
|
383
261
|
else:
|
|
384
262
|
stat = npi_fsdb_goto_next(signal_vct_handle)
|
|
385
|
-
|
|
263
|
+
|
|
386
264
|
if stat == 0:
|
|
387
265
|
break
|
|
388
266
|
|
|
@@ -422,11 +300,7 @@ cdef class NpiFsdbReader:
|
|
|
422
300
|
result_object[:,1] = (result_object[:,1] << shift) + value_np_array
|
|
423
301
|
return result_object
|
|
424
302
|
|
|
425
|
-
|
|
426
|
-
#npi_fsdb_unload_vc(self.fsdb_handle)
|
|
427
|
-
#return result
|
|
428
|
-
|
|
429
|
-
def top_scope_list(self) -> list[NpiFsdbScope]:
|
|
303
|
+
def top_scope_list(self) -> list:
|
|
430
304
|
cdef npiFsdbScopeIter top_scope_iter
|
|
431
305
|
cdef npiFsdbScopeHandle top_scope_handle
|
|
432
306
|
|
|
@@ -436,7 +310,7 @@ cdef class NpiFsdbReader:
|
|
|
436
310
|
top_scope_handle = npi_fsdb_iter_scope_next(top_scope_iter)
|
|
437
311
|
if top_scope_handle == NULL:
|
|
438
312
|
break
|
|
439
|
-
res.append(
|
|
313
|
+
res.append(NpiFsdbScope.init(top_scope_handle))
|
|
440
314
|
npi_fsdb_iter_scope_stop(top_scope_iter)
|
|
441
315
|
return res
|
|
442
316
|
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import importlib
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass, field
|
|
6
7
|
from functools import cached_property
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -20,92 +21,95 @@ from .npi_fsdb_reader import (
|
|
|
20
21
|
NPI_FSDB_CT_UNION,
|
|
21
22
|
NpiFsdbReader,
|
|
22
23
|
NpiFsdbScope,
|
|
23
|
-
|
|
24
|
+
NpiFsdbSignal,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
) ->
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class FsdbSignal(Signal):
|
|
30
|
+
"""FSDB-backed :class:`~wavekit.signal.Signal` with lazy member loading.
|
|
31
|
+
|
|
32
|
+
Extends :class:`~wavekit.signal.Signal` by holding an NPI signal handle
|
|
33
|
+
directly, enabling on-demand loading of composite members without any
|
|
34
|
+
reader reference. Construct via :meth:`from_handle`.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_npi_signal: NpiFsdbSignal | None = field(default=None, repr=False, compare=False)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_handle(cls, npi_sig: NpiFsdbSignal, full_name: str) -> FsdbSignal:
|
|
41
|
+
"""Build an :class:`FsdbSignal` from an NPI signal handle and its full path."""
|
|
42
|
+
_npi_ct_to_enum = {
|
|
43
|
+
NPI_FSDB_CT_ARRAY: SignalCompositeType.ARRAY,
|
|
44
|
+
NPI_FSDB_CT_STRUCT: SignalCompositeType.STRUCT,
|
|
45
|
+
NPI_FSDB_CT_UNION: SignalCompositeType.UNION,
|
|
46
|
+
NPI_FSDB_CT_TAGGED_UNION: SignalCompositeType.TAGGED_UNION,
|
|
47
|
+
NPI_FSDB_CT_RECORD: SignalCompositeType.RECORD,
|
|
48
|
+
}
|
|
49
|
+
ct_raw = npi_sig.composite_type()
|
|
50
|
+
if ct_raw is None:
|
|
51
|
+
composite_type = None
|
|
52
|
+
elif ct_raw in _npi_ct_to_enum:
|
|
53
|
+
composite_type = _npi_ct_to_enum[ct_raw]
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError(f"Unknown NPI composite type value: {ct_raw} for signal '{full_name}'")
|
|
56
|
+
return cls(
|
|
57
|
+
name=npi_sig.name(),
|
|
58
|
+
full_name=full_name,
|
|
59
|
+
width=npi_sig.width(),
|
|
60
|
+
range=npi_sig.range(),
|
|
61
|
+
composite_type=composite_type,
|
|
62
|
+
_npi_signal=npi_sig,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@cached_property
|
|
66
|
+
def member_list(self) -> list[Signal] | None:
|
|
67
|
+
if self.composite_type is None:
|
|
68
|
+
return None
|
|
69
|
+
assert self._npi_signal is not None
|
|
70
|
+
npi_members = self._npi_signal.member_list()
|
|
71
|
+
if self.composite_type == SignalCompositeType.ARRAY:
|
|
72
|
+
# Array members' NPI names already include the array base name at every
|
|
73
|
+
# level, e.g. parent "a" has members "a[0]", "a[1]", and "a[0]" further
|
|
74
|
+
# has members "a[0][0]", "a[0][1]", etc. The member name is therefore an
|
|
75
|
+
# extension of the parent name with no extra "." separator, so we must NOT
|
|
76
|
+
# prepend full_name (which would produce "tb.dut.a.a[0]"). Instead we use
|
|
77
|
+
# the parent scope path (full_name minus the signal's own local name) as
|
|
78
|
+
# the base, giving "tb.dut.a[0]", "tb.dut.a[0][1]", etc.
|
|
79
|
+
scope_path = self.full_name[: -(len(self.name) + 1)]
|
|
80
|
+
return [FsdbSignal.from_handle(m, f'{scope_path}.{m.name()}') for m in npi_members]
|
|
81
|
+
else:
|
|
82
|
+
return [FsdbSignal.from_handle(m, f'{self.full_name}.{m.name()}') for m in npi_members]
|
|
67
83
|
|
|
68
84
|
|
|
69
85
|
class FsdbScope(Scope):
|
|
70
86
|
def __init__(self, handle: NpiFsdbScope, parent_scope: FsdbScope | None, reader: FsdbReader):
|
|
71
87
|
super().__init__(name=handle.name())
|
|
72
|
-
self.
|
|
88
|
+
self._npi_scope = handle
|
|
73
89
|
self.parent_scope = parent_scope
|
|
74
90
|
self.reader = reader
|
|
75
91
|
|
|
76
92
|
@cached_property
|
|
77
93
|
def signal_list(self) -> Sequence[Signal]:
|
|
78
94
|
full_scope_name = self.full_name()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
signal_path = f'{full_scope_name}.{sig_scope.name()}'
|
|
84
|
-
signals.append(_build_signal_from_handle(sig_scope, signal_path, self.reader))
|
|
85
|
-
return signals
|
|
95
|
+
return [
|
|
96
|
+
FsdbSignal.from_handle(npi_sig, f'{full_scope_name}.{npi_sig.name()}')
|
|
97
|
+
for npi_sig in self._npi_scope.signal_list()
|
|
98
|
+
]
|
|
86
99
|
|
|
87
100
|
@cached_property
|
|
88
101
|
def child_scope_list(self) -> Sequence[Scope]:
|
|
89
|
-
return [FsdbScope(c, self, self.reader) for c in self.
|
|
102
|
+
return [FsdbScope(c, self, self.reader) for c in self._npi_scope.child_scope_list()]
|
|
90
103
|
|
|
91
104
|
@cached_property
|
|
92
|
-
def child_normal_scope_list(self) -> Sequence[Scope]:
|
|
93
|
-
return [
|
|
94
|
-
FsdbScope(c, self, self.reader)
|
|
95
|
-
for c in self.handle.child_scope_list(include_signal_scope=False)
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
@property
|
|
99
105
|
def type(self) -> str:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return self._type
|
|
106
|
+
"""Return the NPI scope type string, e.g. 'npiFsdbScopeSvModule'."""
|
|
107
|
+
return self._npi_scope.type()
|
|
103
108
|
|
|
104
|
-
@
|
|
109
|
+
@cached_property
|
|
105
110
|
def def_name(self) -> str | None:
|
|
106
|
-
if not
|
|
107
|
-
|
|
108
|
-
return self._def_name
|
|
111
|
+
"""Return the module definition name, or None if this scope is not a module."""
|
|
112
|
+
return self._npi_scope.def_name()
|
|
109
113
|
|
|
110
114
|
def find_scope_by_module(self, module_name: str, depth: int = 0) -> list[Scope]:
|
|
111
115
|
if not hasattr(self, '_preloaded_module_scope'):
|
|
@@ -113,11 +117,8 @@ class FsdbScope(Scope):
|
|
|
113
117
|
return self._preloaded_module_scope[module_name]
|
|
114
118
|
|
|
115
119
|
def preload_module_scope(self):
|
|
116
|
-
if isinstance(self.handle, NpiFsdbSignalScope):
|
|
117
|
-
return {}
|
|
118
|
-
|
|
119
120
|
preloaded_module_scope = defaultdict(list)
|
|
120
|
-
for c in self.
|
|
121
|
+
for c in self.child_scope_list:
|
|
121
122
|
for module_name, module_scope_list in c.preload_module_scope().items():
|
|
122
123
|
preloaded_module_scope[module_name].extend(module_scope_list)
|
|
123
124
|
|
|
@@ -166,9 +167,21 @@ class FsdbReader(Reader):
|
|
|
166
167
|
signal_path = signal.full_name if isinstance(signal, Signal) else signal
|
|
167
168
|
clock_path = clock.full_name if isinstance(clock, Signal) else clock
|
|
168
169
|
|
|
170
|
+
# Resolve NPI signal handles — reuse the handle if already available
|
|
171
|
+
npi_signal = (
|
|
172
|
+
signal._npi_signal
|
|
173
|
+
if isinstance(signal, FsdbSignal) and signal._npi_signal is not None
|
|
174
|
+
else self.file_handle.get_signal(signal_path)
|
|
175
|
+
)
|
|
176
|
+
npi_clock = (
|
|
177
|
+
clock._npi_signal
|
|
178
|
+
if isinstance(clock, FsdbSignal) and clock._npi_signal is not None
|
|
179
|
+
else self.file_handle.get_signal(clock_path)
|
|
180
|
+
)
|
|
181
|
+
|
|
169
182
|
# Always load the full clock to compute absolute cycle numbers
|
|
170
183
|
all_clock_changes = self.file_handle.load_value_change(
|
|
171
|
-
|
|
184
|
+
npi_clock,
|
|
172
185
|
begin_time=0,
|
|
173
186
|
end_time=2**64 - 1,
|
|
174
187
|
xz_value=0,
|
|
@@ -199,7 +212,7 @@ class FsdbReader(Reader):
|
|
|
199
212
|
# Load signal within the requested window only (FSDB NPI provides the
|
|
200
213
|
# correct initial value at begin_time even if the last change was earlier)
|
|
201
214
|
signal_value_change = self.file_handle.load_value_change(
|
|
202
|
-
|
|
215
|
+
npi_signal,
|
|
203
216
|
begin_time=begin_time_actual,
|
|
204
217
|
end_time=end_time_actual,
|
|
205
218
|
xz_value=xz_value,
|
|
@@ -208,7 +221,7 @@ class FsdbReader(Reader):
|
|
|
208
221
|
full_wave = self.value_change_to_waveform(
|
|
209
222
|
signal_value_change,
|
|
210
223
|
windowed_clock_changes,
|
|
211
|
-
width=
|
|
224
|
+
width=npi_signal.width(),
|
|
212
225
|
signed=signed,
|
|
213
226
|
sample_on_posedge=sample_on_posedge,
|
|
214
227
|
signal=signal_path,
|
|
@@ -229,12 +242,6 @@ class FsdbReader(Reader):
|
|
|
229
242
|
]
|
|
230
243
|
return self._top_scope_list
|
|
231
244
|
|
|
232
|
-
def _get_signal_width(self, signal: str) -> int:
|
|
233
|
-
return self.file_handle.get_signal_width(signal)
|
|
234
|
-
|
|
235
|
-
def _get_signal_range(self, signal: str) -> tuple[int, int]:
|
|
236
|
-
return self.file_handle.get_signal_range(signal)
|
|
237
|
-
|
|
238
245
|
@property
|
|
239
246
|
def begin_time(self) -> str:
|
|
240
247
|
return self.file_handle.min_time()
|
|
@@ -11,7 +11,7 @@ from .readers.pattern_parser import (
|
|
|
11
11
|
PatternMap,
|
|
12
12
|
split_by_range_expr,
|
|
13
13
|
)
|
|
14
|
-
from .signal import Signal
|
|
14
|
+
from .signal import Signal, SignalCompositeType
|
|
15
15
|
|
|
16
16
|
T = TypeVar('T')
|
|
17
17
|
|
|
@@ -84,25 +84,6 @@ class Scope:
|
|
|
84
84
|
raise NotImplementedError()
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
def _range_str_to_tuple(range_str: str) -> tuple[int, int] | None:
|
|
88
|
-
"""Extract the innermost (last) ``[high:low]`` or ``[n]`` from *range_str*.
|
|
89
|
-
|
|
90
|
-
For multi-dimensional suffixes such as ``"[3][7:0]"`` only the final
|
|
91
|
-
bracket pair is returned (``(7, 0)``), because in practice only the last
|
|
92
|
-
dimension carries a bit-range; all preceding dimensions are index
|
|
93
|
-
selectors of the form ``[n]`` and are encoded in the signal name only.
|
|
94
|
-
Returns ``None`` if *range_str* is empty or contains no bracket expression.
|
|
95
|
-
"""
|
|
96
|
-
if not range_str:
|
|
97
|
-
return None
|
|
98
|
-
last = re.search(r'\[(\d+)(?::(\d+))?\]$', range_str)
|
|
99
|
-
if last is None:
|
|
100
|
-
return None
|
|
101
|
-
high = int(last.group(1))
|
|
102
|
-
low = int(last.group(2)) if last.group(2) is not None else high
|
|
103
|
-
return (high, low)
|
|
104
|
-
|
|
105
|
-
|
|
106
87
|
def _prepend_scope_name(value: Any, scope_name: str) -> Any:
|
|
107
88
|
if isinstance(value, Signal):
|
|
108
89
|
return dataclasses.replace(value, full_name=f'{scope_name}.{value.full_name}')
|
|
@@ -212,68 +193,6 @@ def _traverse_scope_tree(
|
|
|
212
193
|
return res
|
|
213
194
|
|
|
214
195
|
|
|
215
|
-
def _match_signals_in_list(
|
|
216
|
-
signals: Sequence[Signal], pattern_list: list[PatternMap]
|
|
217
|
-
) -> dict[tuple[Any, ...], Signal]:
|
|
218
|
-
"""Match *pattern_list* against *signals*, recursing into composite children.
|
|
219
|
-
|
|
220
|
-
The first element of *pattern_list* is matched against signal names in
|
|
221
|
-
*signals*. When more patterns remain and the matched signal is composite
|
|
222
|
-
(``signal.children is not None``), the function recurses into those
|
|
223
|
-
children with the remaining patterns, allowing patterns to address
|
|
224
|
-
struct/union members across multiple levels.
|
|
225
|
-
|
|
226
|
-
.. note::
|
|
227
|
-
Composite member path separators are assumed to be ``"."`` (the same
|
|
228
|
-
as scope separators). For array elements such as ``a[10].b`` this
|
|
229
|
-
means the pattern segment for the array element is ``"a[10]"`` and the
|
|
230
|
-
next segment is ``"b"``.
|
|
231
|
-
|
|
232
|
-
TODO: Verify actual NPI naming for composite members — NPI may return the
|
|
233
|
-
full name from the root signal rather than just the local node name, and
|
|
234
|
-
array-element separators may differ from ``"."``. Adjust accordingly once
|
|
235
|
-
confirmed.
|
|
236
|
-
"""
|
|
237
|
-
if not pattern_list:
|
|
238
|
-
return {}
|
|
239
|
-
|
|
240
|
-
def resolve_leaf(sig: Signal, sig_bare: str, range_suffix: str) -> Signal:
|
|
241
|
-
new_range = _range_str_to_tuple(range_suffix) if range_suffix else sig.range
|
|
242
|
-
new_local = f'{sig_bare}{range_suffix}' if range_suffix else sig.name
|
|
243
|
-
return dataclasses.replace(sig, full_name=new_local, range=new_range)
|
|
244
|
-
|
|
245
|
-
res: dict[tuple[Any, ...], Signal] = {}
|
|
246
|
-
for sig in signals:
|
|
247
|
-
sig_bare, _ = split_by_range_expr(sig.name)
|
|
248
|
-
for k, p in pattern_list[0].items():
|
|
249
|
-
if p[0] == '@':
|
|
250
|
-
name_regex, range_suffix = split_by_range_expr(p[1:])
|
|
251
|
-
if match := re.fullmatch(name_regex, sig.name):
|
|
252
|
-
assert len(k) == 0
|
|
253
|
-
key = (match.groups(),)
|
|
254
|
-
if len(pattern_list) == 1:
|
|
255
|
-
assert key not in res, f'pattern {name_regex} matches more than one signal'
|
|
256
|
-
res[key] = resolve_leaf(sig, sig_bare, range_suffix)
|
|
257
|
-
elif sig.children is not None:
|
|
258
|
-
for ck, cv in _match_signals_in_list(
|
|
259
|
-
sig.children, pattern_list[1:]
|
|
260
|
-
).items():
|
|
261
|
-
res[key + ck] = _prepend_scope_name(cv, sig.name)
|
|
262
|
-
else:
|
|
263
|
-
p_bare, range_suffix = split_by_range_expr(p)
|
|
264
|
-
if p_bare != sig_bare:
|
|
265
|
-
continue
|
|
266
|
-
key = k
|
|
267
|
-
if len(pattern_list) == 1:
|
|
268
|
-
assert key not in res
|
|
269
|
-
res[key] = resolve_leaf(sig, sig_bare, range_suffix)
|
|
270
|
-
elif sig.children is not None:
|
|
271
|
-
for ck, cv in _match_signals_in_list(sig.children, pattern_list[1:]).items():
|
|
272
|
-
res[key + ck] = _prepend_scope_name(cv, sig.name)
|
|
273
|
-
break
|
|
274
|
-
return res
|
|
275
|
-
|
|
276
|
-
|
|
277
196
|
def match_signals(
|
|
278
197
|
scope: Scope, descendant_scope_pattern_list: list[PatternMap]
|
|
279
198
|
) -> dict[tuple[Any, ...], Signal]:
|
|
@@ -286,6 +205,89 @@ def match_signals(
|
|
|
286
205
|
objects whose ``full_name`` is the complete hierarchical path.
|
|
287
206
|
"""
|
|
288
207
|
|
|
208
|
+
def _match_signals_in_list(
|
|
209
|
+
signals: Sequence[Signal], pattern_list: list[PatternMap]
|
|
210
|
+
) -> dict[tuple[Any, ...], Signal]:
|
|
211
|
+
"""Match *pattern_list* against *signals*, recursing into composite members.
|
|
212
|
+
|
|
213
|
+
The first element of *pattern_list* is matched against signal names in
|
|
214
|
+
*signals*. When more patterns remain and the matched signal is composite
|
|
215
|
+
(``signal.member_list is not None``), the function recurses into those
|
|
216
|
+
members with the remaining patterns, allowing patterns to address
|
|
217
|
+
struct/union members across multiple levels.
|
|
218
|
+
|
|
219
|
+
**Array signals** require special treatment because NPI reports each
|
|
220
|
+
element's name as an extension of the parent name with no ``"."``
|
|
221
|
+
separator. For example, signal ``a`` (ARRAY) has members ``a[0]``,
|
|
222
|
+
``a[1]``, which in turn have members ``a[0][0]``, ``a[0][1]``, etc.
|
|
223
|
+
A user pattern like ``a[10][0]`` therefore cannot be resolved by a
|
|
224
|
+
single exact-name match at the top level. Instead, for ARRAY signals
|
|
225
|
+
we check whether the signal name is a string prefix of the pattern:
|
|
226
|
+
if yes, we recurse into members with the **same** pattern (not
|
|
227
|
+
advancing to the next element), letting each member self-select by the
|
|
228
|
+
same prefix rule until an exact match is found.
|
|
229
|
+
"""
|
|
230
|
+
if not pattern_list:
|
|
231
|
+
return {}
|
|
232
|
+
|
|
233
|
+
def resolve_leaf(sig: Signal, sig_bare: str, range_suffix: str) -> Signal:
|
|
234
|
+
if range_suffix:
|
|
235
|
+
# Extract (high, low) from the innermost bracket of range_suffix,
|
|
236
|
+
# e.g. "[7:0]" → (7, 0), "[3]" → (3, 3).
|
|
237
|
+
m = re.search(r'\[(\d+)(?::(\d+))?\]$', range_suffix)
|
|
238
|
+
assert m is not None
|
|
239
|
+
h = int(m.group(1))
|
|
240
|
+
low = int(m.group(2)) if m.group(2) is not None else h
|
|
241
|
+
new_range: tuple[int, int] | None = (h, low)
|
|
242
|
+
else:
|
|
243
|
+
new_range = sig.range
|
|
244
|
+
return dataclasses.replace(
|
|
245
|
+
sig,
|
|
246
|
+
full_name=f'{sig_bare}{range_suffix}' if range_suffix else sig.name,
|
|
247
|
+
range=new_range,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
res: dict[tuple[Any, ...], Signal] = {}
|
|
251
|
+
for sig in signals:
|
|
252
|
+
sig_bare, _ = split_by_range_expr(sig.name)
|
|
253
|
+
for k, p in pattern_list[0].items():
|
|
254
|
+
if p[0] == '@':
|
|
255
|
+
name_regex, range_suffix = split_by_range_expr(p[1:])
|
|
256
|
+
if match := re.fullmatch(name_regex, sig.name):
|
|
257
|
+
assert len(k) == 0
|
|
258
|
+
key = (match.groups(),)
|
|
259
|
+
if len(pattern_list) == 1:
|
|
260
|
+
assert (
|
|
261
|
+
key not in res
|
|
262
|
+
), f'pattern {name_regex} matches more than one signal'
|
|
263
|
+
res[key] = resolve_leaf(sig, sig_bare, range_suffix)
|
|
264
|
+
elif sig.member_list is not None:
|
|
265
|
+
for ck, cv in _match_signals_in_list(
|
|
266
|
+
sig.member_list, pattern_list[1:]
|
|
267
|
+
).items():
|
|
268
|
+
res[key + ck] = _prepend_scope_name(cv, sig.name)
|
|
269
|
+
else:
|
|
270
|
+
p_bare, range_suffix = split_by_range_expr(p)
|
|
271
|
+
if p_bare != sig_bare:
|
|
272
|
+
continue
|
|
273
|
+
key = k
|
|
274
|
+
if sig.composite_type == SignalCompositeType.ARRAY and range_suffix:
|
|
275
|
+
if sig.member_list is not None:
|
|
276
|
+
for ck, cv in _match_signals_in_list(
|
|
277
|
+
sig.member_list, pattern_list
|
|
278
|
+
).items():
|
|
279
|
+
res[key + ck] = cv
|
|
280
|
+
elif len(pattern_list) == 1:
|
|
281
|
+
assert key not in res
|
|
282
|
+
res[key] = resolve_leaf(sig, sig_bare, range_suffix)
|
|
283
|
+
elif sig.member_list is not None:
|
|
284
|
+
for ck, cv in _match_signals_in_list(
|
|
285
|
+
sig.member_list, pattern_list[1:]
|
|
286
|
+
).items():
|
|
287
|
+
res[key + ck] = _prepend_scope_name(cv, sig.name)
|
|
288
|
+
break
|
|
289
|
+
return res
|
|
290
|
+
|
|
289
291
|
def match_signals_in_scope(
|
|
290
292
|
scope: Scope, signal_pattern_list: list[PatternMap]
|
|
291
293
|
) -> dict[tuple[Any, ...], Signal]:
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from enum import Enum
|
|
5
|
+
from functools import cached_property
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class SignalCompositeType(Enum):
|
|
@@ -24,7 +25,7 @@ class Signal:
|
|
|
24
25
|
|
|
25
26
|
Stores the signal's local name, full hierarchical path, bit-width,
|
|
26
27
|
declared bit-range, and signedness. For composite signals (structs,
|
|
27
|
-
unions, arrays) the ``composite_type`` and ``
|
|
28
|
+
unions, arrays) the ``composite_type`` and ``member_list`` fields carry
|
|
28
29
|
the internal structure.
|
|
29
30
|
|
|
30
31
|
Attributes
|
|
@@ -59,12 +60,13 @@ class Signal:
|
|
|
59
60
|
value describing the kind of composite. Not all backends populate
|
|
60
61
|
this field; backends that do not support composite introspection leave
|
|
61
62
|
it as ``None`` (e.g. VCD).
|
|
62
|
-
|
|
63
|
+
member_list:
|
|
63
64
|
``None`` for leaf signals. For composite signals this is the list of
|
|
64
65
|
direct member :class:`Signal` objects, populated in the same order the
|
|
65
66
|
backend reports them. Always ``None`` when ``composite_type`` is
|
|
66
67
|
``None``, and always a list (possibly empty) when ``composite_type``
|
|
67
|
-
is set.
|
|
68
|
+
is set. Lazily evaluated on first access; backend-specific subclasses
|
|
69
|
+
override the ``member_list`` cached property to provide the actual loading logic.
|
|
68
70
|
"""
|
|
69
71
|
|
|
70
72
|
name: str
|
|
@@ -73,7 +75,10 @@ class Signal:
|
|
|
73
75
|
range: tuple[int, int] | None
|
|
74
76
|
signed: bool = False
|
|
75
77
|
composite_type: SignalCompositeType | None = None
|
|
76
|
-
|
|
78
|
+
|
|
79
|
+
@cached_property
|
|
80
|
+
def member_list(self) -> list[Signal] | None:
|
|
81
|
+
return None
|
|
77
82
|
|
|
78
83
|
def __str__(self) -> str:
|
|
79
84
|
return (
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|