wavekit 0.5.0__tar.gz → 0.5.2__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.0 → wavekit-0.5.2}/PKG-INFO +1 -1
- {wavekit-0.5.0 → wavekit-0.5.2}/pyproject.toml +1 -1
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/__init__.py +2 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/readers/base.py +3 -3
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/readers/fsdb/npi_fsdb_reader.pyx +129 -233
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/readers/fsdb/reader.py +93 -41
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/readers/vcd/reader.py +0 -3
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/scope.py +85 -45
- wavekit-0.5.2/src/wavekit/signal.py +87 -0
- wavekit-0.5.0/src/wavekit/signal.py +0 -49
- {wavekit-0.5.0 → wavekit-0.5.2}/LICENSE +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/README.md +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/build.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/pattern/__init__.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/pattern/dsl.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/pattern/engine.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/pattern/instance.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/pattern/result.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/pattern/steps.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/readers/expr_parser.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/readers/fsdb/npi_fsdb.pxd +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/readers/pattern_parser.py +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/readers/value_change.pyx +0 -0
- {wavekit-0.5.0 → wavekit-0.5.2}/src/wavekit/waveform.py +0 -0
|
@@ -20,6 +20,7 @@ from .pattern import PatternError as PatternError
|
|
|
20
20
|
from .readers.vcd.reader import VcdReader as VcdReader
|
|
21
21
|
from .scope import Scope as Scope
|
|
22
22
|
from .signal import Signal as Signal
|
|
23
|
+
from .signal import SignalCompositeType as SignalCompositeType
|
|
23
24
|
from .waveform import Waveform as Waveform
|
|
24
25
|
|
|
25
26
|
__all__ = [
|
|
@@ -28,6 +29,7 @@ __all__ = [
|
|
|
28
29
|
'FsdbReader',
|
|
29
30
|
'Scope',
|
|
30
31
|
'Signal',
|
|
32
|
+
'SignalCompositeType',
|
|
31
33
|
'Pattern',
|
|
32
34
|
'MatchResult',
|
|
33
35
|
'MatchStatus',
|
|
@@ -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,155 +3,170 @@ 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
|
|
10
9
|
from libc.string cimport strcpy, strlen
|
|
11
10
|
from .npi_fsdb cimport *
|
|
12
11
|
|
|
12
|
+
NPI_FSDB_CT_ARRAY = <int>npiFsdbSigCtArray
|
|
13
|
+
NPI_FSDB_CT_STRUCT = <int>npiFsdbSigCtStruct
|
|
14
|
+
NPI_FSDB_CT_UNION = <int>npiFsdbSigCtUnion
|
|
15
|
+
NPI_FSDB_CT_TAGGED_UNION = <int>npiFsdbSigCtTaggedUnion
|
|
16
|
+
NPI_FSDB_CT_RECORD = <int>npiFsdbSigCtRecord
|
|
13
17
|
|
|
14
|
-
cdef class NpiFsdbScope:
|
|
15
|
-
pass
|
|
16
18
|
|
|
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
|
|
17
101
|
|
|
18
|
-
|
|
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
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
cdef class NpiFsdbScope:
|
|
115
|
+
"""Wraps a npiFsdbScopeHandle — a scope node (module/block) in the FSDB hierarchy."""
|
|
19
116
|
cdef npiFsdbScopeHandle scope_handle
|
|
20
117
|
|
|
21
118
|
@staticmethod
|
|
22
119
|
cdef init(npiFsdbScopeHandle scope_handle):
|
|
23
|
-
scope =
|
|
120
|
+
scope = NpiFsdbScope()
|
|
24
121
|
scope.scope_handle = scope_handle
|
|
25
122
|
return scope
|
|
26
123
|
|
|
27
124
|
def __init__(self):
|
|
28
125
|
self.scope_handle = NULL
|
|
29
126
|
|
|
30
|
-
def child_scope_list(self
|
|
127
|
+
def child_scope_list(self) -> list:
|
|
128
|
+
"""Return direct child NpiFsdbScope nodes (not signals)."""
|
|
31
129
|
assert self.scope_handle != NULL
|
|
32
130
|
res = []
|
|
33
131
|
child_scope_handle_iter = npi_fsdb_iter_child_scope(self.scope_handle)
|
|
34
|
-
|
|
132
|
+
|
|
35
133
|
cdef npiFsdbScopeHandle child_scope_handle
|
|
36
134
|
while True:
|
|
37
135
|
child_scope_handle = npi_fsdb_iter_scope_next(child_scope_handle_iter)
|
|
38
136
|
if child_scope_handle == NULL:
|
|
39
137
|
break
|
|
40
|
-
res.append(
|
|
138
|
+
res.append(NpiFsdbScope.init(child_scope_handle))
|
|
41
139
|
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)
|
|
53
140
|
return res
|
|
54
141
|
|
|
55
|
-
def signal_list(self) -> list
|
|
142
|
+
def signal_list(self) -> list:
|
|
143
|
+
"""Return direct child NpiFsdbSignal objects declared in this scope."""
|
|
56
144
|
assert self.scope_handle != NULL
|
|
57
|
-
|
|
58
145
|
res = []
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
cdef
|
|
146
|
+
child_sig_handle_iter = npi_fsdb_iter_sig(self.scope_handle)
|
|
147
|
+
|
|
148
|
+
cdef npiFsdbSigHandle child_sig_handle
|
|
62
149
|
while True:
|
|
63
|
-
|
|
64
|
-
if
|
|
150
|
+
child_sig_handle = npi_fsdb_iter_sig_next(child_sig_handle_iter)
|
|
151
|
+
if child_sig_handle == NULL:
|
|
65
152
|
break
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
res.append(name)
|
|
69
|
-
npi_fsdb_iter_sig_stop(sig_handle_iter)
|
|
70
|
-
|
|
153
|
+
res.append(NpiFsdbSignal.init(child_sig_handle))
|
|
154
|
+
npi_fsdb_iter_sig_stop(child_sig_handle_iter)
|
|
71
155
|
return res
|
|
72
156
|
|
|
73
157
|
def name(self) -> str:
|
|
74
158
|
assert self.scope_handle != NULL
|
|
75
|
-
|
|
76
|
-
return name
|
|
159
|
+
return npi_fsdb_scope_property_str(npiFsdbScopeName, self.scope_handle).decode('ascii')
|
|
77
160
|
|
|
78
161
|
def type(self) -> str:
|
|
79
162
|
assert self.scope_handle != NULL
|
|
80
|
-
|
|
81
|
-
return type_str
|
|
163
|
+
return npi_fsdb_scope_property_str(npiFsdbScopeType, self.scope_handle).decode('ascii')
|
|
82
164
|
|
|
83
165
|
def def_name(self) -> str:
|
|
84
166
|
assert self.scope_handle != NULL
|
|
85
|
-
|
|
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 = []
|
|
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
|
|
167
|
+
return npi_fsdb_scope_property_str(npiFsdbScopeDefName, self.scope_handle).decode('ascii')
|
|
141
168
|
|
|
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
169
|
|
|
153
|
-
def def_name(self) -> str:
|
|
154
|
-
raise NotImplementedError("def_name property of signal scope is not supported")
|
|
155
170
|
@cython.boundscheck(False) # 关闭边界检查以提升性能
|
|
156
171
|
@cython.wraparound(False) # 关闭负索引检查以提升性能
|
|
157
172
|
cdef inline cstr_to_ull(char* str, unsigned int xz_value, int max_bit_num = 2147483647):
|
|
@@ -186,88 +201,6 @@ cdef inline cstr_to_bit(char* str, unsigned int xz_value):
|
|
|
186
201
|
value = xz_value
|
|
187
202
|
return value
|
|
188
203
|
|
|
189
|
-
cdef inline fsdb_read_value_change(vct_handle: npiFsdbVctHandle, begin_time, end_time, width:int, xz_value:int):
|
|
190
|
-
|
|
191
|
-
cdef npiFsdbTime cur_time
|
|
192
|
-
cdef npiFsdbValue cur_value
|
|
193
|
-
cdef int stat
|
|
194
|
-
|
|
195
|
-
cdef vector[unsigned long long] time_array
|
|
196
|
-
|
|
197
|
-
cdef int value_array_num = math.ceil(width/64)
|
|
198
|
-
assert width <= 64*64
|
|
199
|
-
cdef vector[unsigned long long] value_array[64]
|
|
200
|
-
|
|
201
|
-
cdef int first = 1
|
|
202
|
-
cdef int i,j
|
|
203
|
-
cdef unsigned long long cur_int_value
|
|
204
|
-
while True:
|
|
205
|
-
if first:
|
|
206
|
-
if begin_time is None:
|
|
207
|
-
stat = npi_fsdb_goto_first(vct_handle)
|
|
208
|
-
else:
|
|
209
|
-
stat = npi_fsdb_goto_time(vct_handle, <npiFsdbTime> begin_time)
|
|
210
|
-
else:
|
|
211
|
-
stat = npi_fsdb_goto_next(vct_handle)
|
|
212
|
-
if stat == 0:
|
|
213
|
-
break
|
|
214
|
-
|
|
215
|
-
cur_value.format = npiFsdbBinStrVal
|
|
216
|
-
npi_fsdb_vct_time(vct_handle, &cur_time)
|
|
217
|
-
npi_fsdb_vct_value(vct_handle, &cur_value)
|
|
218
|
-
if cur_time > end_time:
|
|
219
|
-
break
|
|
220
|
-
|
|
221
|
-
time_array.push_back(cur_time)
|
|
222
|
-
if width == 1: # opt for clk
|
|
223
|
-
value_array[0].push_back(cstr_to_bit(<char*>cur_value.value.str, xz_value))
|
|
224
|
-
else:
|
|
225
|
-
cur_lllint_value = 0
|
|
226
|
-
for i in range(value_array_num):
|
|
227
|
-
value_array[i].push_back(cstr_to_ull(<char*>cur_value.value.str + 64*i, xz_value, 64))
|
|
228
|
-
|
|
229
|
-
result = np.zeros((value_array[0].size(), 2), dtype=np.uint64 if width <= 64 else np.object_) # 创建一个 (n, 2) 的二维数组,dtype 为 int32
|
|
230
|
-
for j in range(value_array[0].size()):
|
|
231
|
-
result[j][0] = time_array[j]
|
|
232
|
-
if width <= 64:
|
|
233
|
-
result[j][1] = value_array[0][j]
|
|
234
|
-
else:
|
|
235
|
-
for i in range(value_array_num):
|
|
236
|
-
result[j][1] = (result[j][1] << 64) + value_array[i][j]
|
|
237
|
-
|
|
238
|
-
return result
|
|
239
|
-
|
|
240
|
-
cdef get_signal_handle_width(npiFsdbSigHandle signal_handle):
|
|
241
|
-
cdef int has_member
|
|
242
|
-
cdef int width
|
|
243
|
-
cdef npiFsdbSigIter sub_signal_iter
|
|
244
|
-
cdef npiFsdbSigHandle sub_signal
|
|
245
|
-
assert(npi_fsdb_sig_property(npiFsdbSigHasMember, signal_handle, &has_member))
|
|
246
|
-
if(has_member == 0):
|
|
247
|
-
assert(npi_fsdb_sig_property(npiFsdbSigRangeSize, signal_handle, &width))
|
|
248
|
-
return width
|
|
249
|
-
else:
|
|
250
|
-
sub_signal_iter = npi_fsdb_iter_member(signal_handle)
|
|
251
|
-
assert(sub_signal_iter != NULL)
|
|
252
|
-
width = 0
|
|
253
|
-
while(sub_signal := npi_fsdb_iter_sig_next(sub_signal_iter)):
|
|
254
|
-
width += get_signal_handle_width(sub_signal)
|
|
255
|
-
return width
|
|
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
204
|
|
|
272
205
|
cdef class NpiFsdbReader:
|
|
273
206
|
cdef npiFsdbFileHandle fsdb_handle
|
|
@@ -275,69 +208,36 @@ cdef class NpiFsdbReader:
|
|
|
275
208
|
|
|
276
209
|
def __init__(self, str file):
|
|
277
210
|
import os
|
|
278
|
-
|
|
211
|
+
|
|
279
212
|
# 检查VERDI_HOME环境变量
|
|
280
213
|
verdi_home = os.environ.get('VERDI_HOME')
|
|
281
214
|
if verdi_home:
|
|
282
215
|
npi_lib_path = os.path.join(verdi_home, 'share/NPI/lib/LINUX64/libNPI.so')
|
|
283
|
-
|
|
284
|
-
#cdef int argc = len(sys.argv)
|
|
285
|
-
#cdef char** argv = <char**>malloc((argc + 1) * sizeof(char*))
|
|
286
|
-
|
|
287
|
-
#for i in range(argc):
|
|
288
|
-
# argv[i] = <char*>malloc(strlen(sys.argv[i].encode('ascii')))
|
|
289
|
-
# strcpy(argv[i], sys.argv[i].encode('ascii'))
|
|
290
|
-
#argv[argc] = NULL # Null-terminate the array
|
|
291
216
|
|
|
292
|
-
#npi_init(argc, argv)
|
|
293
|
-
#self.file = file
|
|
294
|
-
#free(argv)
|
|
295
|
-
|
|
296
|
-
#cdef npiFsdbFileHandle fsdb_handle
|
|
297
217
|
file_str = file.encode('utf-8')
|
|
298
218
|
cdef char* file_s = file_str
|
|
299
219
|
self.fsdb_handle = npi_fsdb_open(file_s)
|
|
300
220
|
if(self.fsdb_handle == NULL):
|
|
301
221
|
raise OSError(f"Failed to open fsdb file :{file_str}")
|
|
302
222
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
self,
|
|
307
|
-
str signal
|
|
308
|
-
) -> int:
|
|
309
|
-
|
|
310
|
-
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
223
|
+
def get_signal(self, str signal) -> NpiFsdbSignal:
|
|
224
|
+
"""Look up a signal by its full hierarchical path and return an NpiFsdbSignal handle."""
|
|
225
|
+
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
311
226
|
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
312
|
-
|
|
313
|
-
cdef int width = get_signal_handle_width(signal_handle)
|
|
314
|
-
return width
|
|
315
|
-
|
|
316
|
-
def get_signal_range(
|
|
317
|
-
self,
|
|
318
|
-
str signal
|
|
319
|
-
) -> tuple[int,int]:
|
|
320
|
-
|
|
321
|
-
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
322
|
-
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
323
|
-
|
|
324
|
-
return get_signal_handle_range(signal_handle)
|
|
227
|
+
return NpiFsdbSignal.init(signal_handle)
|
|
325
228
|
|
|
326
229
|
@cython.boundscheck(False) # 关闭边界检查以提升性能
|
|
327
230
|
@cython.wraparound(False) # 关闭负索引检查以提升性能
|
|
328
231
|
def load_value_change(
|
|
329
232
|
self,
|
|
330
|
-
|
|
233
|
+
NpiFsdbSignal signal,
|
|
331
234
|
unsigned long long begin_time,
|
|
332
235
|
unsigned long long end_time,
|
|
333
236
|
int xz_value
|
|
334
237
|
) -> np.ndarray:
|
|
335
|
-
|
|
336
|
-
cdef npiFsdbSigHandle signal_handle = npi_fsdb_sig_by_name(self.fsdb_handle, signal.encode('ascii'), NULL)
|
|
337
|
-
assert signal_handle != NULL, f"can't find signal: {signal}"
|
|
338
|
-
cdef int width = get_signal_handle_width(signal_handle)
|
|
339
238
|
|
|
340
|
-
cdef
|
|
239
|
+
cdef int width = signal.width()
|
|
240
|
+
cdef npiFsdbVctHandle signal_vct_handle = npi_fsdb_create_vct(signal.sig_handle)
|
|
341
241
|
cdef npiFsdbTime cur_time
|
|
342
242
|
cdef npiFsdbValue cur_value
|
|
343
243
|
cdef int stat
|
|
@@ -358,7 +258,7 @@ cdef class NpiFsdbReader:
|
|
|
358
258
|
stat = npi_fsdb_goto_time(signal_vct_handle, <npiFsdbTime> begin_time)
|
|
359
259
|
else:
|
|
360
260
|
stat = npi_fsdb_goto_next(signal_vct_handle)
|
|
361
|
-
|
|
261
|
+
|
|
362
262
|
if stat == 0:
|
|
363
263
|
break
|
|
364
264
|
|
|
@@ -398,11 +298,7 @@ cdef class NpiFsdbReader:
|
|
|
398
298
|
result_object[:,1] = (result_object[:,1] << shift) + value_np_array
|
|
399
299
|
return result_object
|
|
400
300
|
|
|
401
|
-
|
|
402
|
-
#npi_fsdb_unload_vc(self.fsdb_handle)
|
|
403
|
-
#return result
|
|
404
|
-
|
|
405
|
-
def top_scope_list(self) -> list[NpiFsdbScope]:
|
|
301
|
+
def top_scope_list(self) -> list:
|
|
406
302
|
cdef npiFsdbScopeIter top_scope_iter
|
|
407
303
|
cdef npiFsdbScopeHandle top_scope_handle
|
|
408
304
|
|
|
@@ -412,7 +308,7 @@ cdef class NpiFsdbReader:
|
|
|
412
308
|
top_scope_handle = npi_fsdb_iter_scope_next(top_scope_iter)
|
|
413
309
|
if top_scope_handle == NULL:
|
|
414
310
|
break
|
|
415
|
-
res.append(
|
|
311
|
+
res.append(NpiFsdbScope.init(top_scope_handle))
|
|
416
312
|
npi_fsdb_iter_scope_stop(top_scope_iter)
|
|
417
313
|
return res
|
|
418
314
|
|
|
@@ -3,65 +3,114 @@ 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
|
|
|
9
10
|
import numpy as np
|
|
10
11
|
|
|
11
12
|
from ...scope import Scope
|
|
12
|
-
from ...signal import Signal
|
|
13
|
+
from ...signal import Signal, SignalCompositeType
|
|
13
14
|
from ...waveform import Waveform
|
|
14
15
|
from ..base import Reader
|
|
15
|
-
from .npi_fsdb_reader import
|
|
16
|
+
from .npi_fsdb_reader import (
|
|
17
|
+
NPI_FSDB_CT_ARRAY,
|
|
18
|
+
NPI_FSDB_CT_RECORD,
|
|
19
|
+
NPI_FSDB_CT_STRUCT,
|
|
20
|
+
NPI_FSDB_CT_TAGGED_UNION,
|
|
21
|
+
NPI_FSDB_CT_UNION,
|
|
22
|
+
NpiFsdbReader,
|
|
23
|
+
NpiFsdbScope,
|
|
24
|
+
NpiFsdbSignal,
|
|
25
|
+
)
|
|
26
|
+
|
|
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]
|
|
16
83
|
|
|
17
84
|
|
|
18
85
|
class FsdbScope(Scope):
|
|
19
86
|
def __init__(self, handle: NpiFsdbScope, parent_scope: FsdbScope | None, reader: FsdbReader):
|
|
20
87
|
super().__init__(name=handle.name())
|
|
21
|
-
self.
|
|
88
|
+
self._npi_scope = handle
|
|
22
89
|
self.parent_scope = parent_scope
|
|
23
90
|
self.reader = reader
|
|
24
91
|
|
|
25
92
|
@cached_property
|
|
26
93
|
def signal_list(self) -> Sequence[Signal]:
|
|
27
94
|
full_scope_name = self.full_name()
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
rng = self.reader._get_signal_range(signal_path)
|
|
33
|
-
signals.append(
|
|
34
|
-
Signal(
|
|
35
|
-
name=s,
|
|
36
|
-
full_name=signal_path,
|
|
37
|
-
width=width,
|
|
38
|
-
range=rng,
|
|
39
|
-
signed=False,
|
|
40
|
-
)
|
|
41
|
-
)
|
|
42
|
-
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
|
+
]
|
|
43
99
|
|
|
44
100
|
@cached_property
|
|
45
101
|
def child_scope_list(self) -> Sequence[Scope]:
|
|
46
|
-
return [FsdbScope(c, self, self.reader) for c in self.
|
|
47
|
-
|
|
48
|
-
@cached_property
|
|
49
|
-
def child_normal_scope_list(self) -> Sequence[Scope]:
|
|
50
|
-
return [
|
|
51
|
-
FsdbScope(c, self, self.reader)
|
|
52
|
-
for c in self.handle.child_scope_list(include_signal_scope=False)
|
|
53
|
-
]
|
|
102
|
+
return [FsdbScope(c, self, self.reader) for c in self._npi_scope.child_scope_list()]
|
|
54
103
|
|
|
55
104
|
@property
|
|
56
105
|
def type(self) -> str:
|
|
57
106
|
if not hasattr(self, '_type'):
|
|
58
|
-
self._type = self.
|
|
107
|
+
self._type = self._npi_scope.type()
|
|
59
108
|
return self._type
|
|
60
109
|
|
|
61
110
|
@property
|
|
62
111
|
def def_name(self) -> str | None:
|
|
63
112
|
if not hasattr(self, '_def_name'):
|
|
64
|
-
self._def_name = self.
|
|
113
|
+
self._def_name = self._npi_scope.def_name()
|
|
65
114
|
return self._def_name
|
|
66
115
|
|
|
67
116
|
def find_scope_by_module(self, module_name: str, depth: int = 0) -> list[Scope]:
|
|
@@ -70,11 +119,8 @@ class FsdbScope(Scope):
|
|
|
70
119
|
return self._preloaded_module_scope[module_name]
|
|
71
120
|
|
|
72
121
|
def preload_module_scope(self):
|
|
73
|
-
if isinstance(self.handle, NpiFsdbSignalScope):
|
|
74
|
-
return {}
|
|
75
|
-
|
|
76
122
|
preloaded_module_scope = defaultdict(list)
|
|
77
|
-
for c in self.
|
|
123
|
+
for c in self.child_scope_list:
|
|
78
124
|
for module_name, module_scope_list in c.preload_module_scope().items():
|
|
79
125
|
preloaded_module_scope[module_name].extend(module_scope_list)
|
|
80
126
|
|
|
@@ -123,9 +169,21 @@ class FsdbReader(Reader):
|
|
|
123
169
|
signal_path = signal.full_name if isinstance(signal, Signal) else signal
|
|
124
170
|
clock_path = clock.full_name if isinstance(clock, Signal) else clock
|
|
125
171
|
|
|
172
|
+
# Resolve NPI signal handles — reuse the handle if already available
|
|
173
|
+
npi_signal = (
|
|
174
|
+
signal._npi_signal
|
|
175
|
+
if isinstance(signal, FsdbSignal) and signal._npi_signal is not None
|
|
176
|
+
else self.file_handle.get_signal(signal_path)
|
|
177
|
+
)
|
|
178
|
+
npi_clock = (
|
|
179
|
+
clock._npi_signal
|
|
180
|
+
if isinstance(clock, FsdbSignal) and clock._npi_signal is not None
|
|
181
|
+
else self.file_handle.get_signal(clock_path)
|
|
182
|
+
)
|
|
183
|
+
|
|
126
184
|
# Always load the full clock to compute absolute cycle numbers
|
|
127
185
|
all_clock_changes = self.file_handle.load_value_change(
|
|
128
|
-
|
|
186
|
+
npi_clock,
|
|
129
187
|
begin_time=0,
|
|
130
188
|
end_time=2**64 - 1,
|
|
131
189
|
xz_value=0,
|
|
@@ -156,7 +214,7 @@ class FsdbReader(Reader):
|
|
|
156
214
|
# Load signal within the requested window only (FSDB NPI provides the
|
|
157
215
|
# correct initial value at begin_time even if the last change was earlier)
|
|
158
216
|
signal_value_change = self.file_handle.load_value_change(
|
|
159
|
-
|
|
217
|
+
npi_signal,
|
|
160
218
|
begin_time=begin_time_actual,
|
|
161
219
|
end_time=end_time_actual,
|
|
162
220
|
xz_value=xz_value,
|
|
@@ -165,7 +223,7 @@ class FsdbReader(Reader):
|
|
|
165
223
|
full_wave = self.value_change_to_waveform(
|
|
166
224
|
signal_value_change,
|
|
167
225
|
windowed_clock_changes,
|
|
168
|
-
width=
|
|
226
|
+
width=npi_signal.width(),
|
|
169
227
|
signed=signed,
|
|
170
228
|
sample_on_posedge=sample_on_posedge,
|
|
171
229
|
signal=signal_path,
|
|
@@ -186,12 +244,6 @@ class FsdbReader(Reader):
|
|
|
186
244
|
]
|
|
187
245
|
return self._top_scope_list
|
|
188
246
|
|
|
189
|
-
def _get_signal_width(self, signal: str) -> int:
|
|
190
|
-
return self.file_handle.get_signal_width(signal)
|
|
191
|
-
|
|
192
|
-
def _get_signal_range(self, signal: str) -> tuple[int, int]:
|
|
193
|
-
return self.file_handle.get_signal_range(signal)
|
|
194
|
-
|
|
195
247
|
@property
|
|
196
248
|
def begin_time(self) -> str:
|
|
197
249
|
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,6 +84,12 @@ class Scope:
|
|
|
84
84
|
raise NotImplementedError()
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def _prepend_scope_name(value: Any, scope_name: str) -> Any:
|
|
88
|
+
if isinstance(value, Signal):
|
|
89
|
+
return dataclasses.replace(value, full_name=f'{scope_name}.{value.full_name}')
|
|
90
|
+
return value
|
|
91
|
+
|
|
92
|
+
|
|
87
93
|
def _traverse_scope_tree(
|
|
88
94
|
scope: Scope,
|
|
89
95
|
descendant_scope_pattern_list: list[PatternMap],
|
|
@@ -111,11 +117,6 @@ def _traverse_scope_tree(
|
|
|
111
117
|
dict mapping expansion key tuples to values produced by *leaf_fn*.
|
|
112
118
|
"""
|
|
113
119
|
|
|
114
|
-
def prepend_scope_name(value: Any, scope_name: str) -> Any:
|
|
115
|
-
if isinstance(value, Signal):
|
|
116
|
-
return dataclasses.replace(value, full_name=f'{scope_name}.{value.full_name}')
|
|
117
|
-
return value
|
|
118
|
-
|
|
119
120
|
res: dict[tuple[Any, ...], T] = {}
|
|
120
121
|
if len(descendant_scope_pattern_list) == 0:
|
|
121
122
|
return res
|
|
@@ -144,11 +145,11 @@ def _traverse_scope_tree(
|
|
|
144
145
|
for lk, lv in leaf_fn(scope, remaining).items():
|
|
145
146
|
key = (scope.name,) + lk
|
|
146
147
|
assert key not in res
|
|
147
|
-
res[key] =
|
|
148
|
+
res[key] = _prepend_scope_name(lv, scope.name)
|
|
148
149
|
|
|
149
150
|
for child_scope in scope.child_scope_list:
|
|
150
151
|
for ck, cv in _traverse_scope_tree(child_scope, remaining, leaf_fn).items():
|
|
151
|
-
res[(scope.name,) + ck] =
|
|
152
|
+
res[(scope.name,) + ck] = _prepend_scope_name(cv, scope.name)
|
|
152
153
|
else:
|
|
153
154
|
for child_scope in module_scopes:
|
|
154
155
|
for ck, cv in _traverse_scope_tree(
|
|
@@ -159,7 +160,7 @@ def _traverse_scope_tree(
|
|
|
159
160
|
raise ValueError('parent scope is None')
|
|
160
161
|
parent_name = parent_scope.full_name(scope)
|
|
161
162
|
key = (f'{parent_name}.{ck[0]}',) + ck[1:]
|
|
162
|
-
res[key] =
|
|
163
|
+
res[key] = _prepend_scope_name(cv, parent_name)
|
|
163
164
|
else:
|
|
164
165
|
# Exact or regex match against current scope name
|
|
165
166
|
matched = False
|
|
@@ -181,14 +182,14 @@ def _traverse_scope_tree(
|
|
|
181
182
|
key = new_k + lk
|
|
182
183
|
if key in res:
|
|
183
184
|
raise Exception(f'pattern {p} match more than one result')
|
|
184
|
-
res[key] =
|
|
185
|
+
res[key] = _prepend_scope_name(lv, scope.name)
|
|
185
186
|
|
|
186
187
|
for child_scope in scope.child_scope_list:
|
|
187
188
|
for ck, cv in _traverse_scope_tree(child_scope, remaining, leaf_fn).items():
|
|
188
189
|
key = new_k + ck
|
|
189
190
|
if key in res:
|
|
190
191
|
raise Exception(f'pattern {p} match more than one result')
|
|
191
|
-
res[key] =
|
|
192
|
+
res[key] = _prepend_scope_name(cv, scope.name)
|
|
192
193
|
return res
|
|
193
194
|
|
|
194
195
|
|
|
@@ -204,55 +205,94 @@ def match_signals(
|
|
|
204
205
|
objects whose ``full_name`` is the complete hierarchical path.
|
|
205
206
|
"""
|
|
206
207
|
|
|
207
|
-
def
|
|
208
|
-
|
|
208
|
+
def _match_signals_in_list(
|
|
209
|
+
signals: Sequence[Signal], pattern_list: list[PatternMap]
|
|
209
210
|
) -> dict[tuple[Any, ...], Signal]:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
+
)
|
|
219
249
|
|
|
220
250
|
res: dict[tuple[Any, ...], Signal] = {}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
sig_bare, _ = split_by_range_expr(signal.name)
|
|
225
|
-
for k, p in signal_pattern_list[0].items():
|
|
251
|
+
for sig in signals:
|
|
252
|
+
sig_bare, _ = split_by_range_expr(sig.name)
|
|
253
|
+
for k, p in pattern_list[0].items():
|
|
226
254
|
if p[0] == '@':
|
|
227
255
|
name_regex, range_suffix = split_by_range_expr(p[1:])
|
|
228
|
-
if match := re.fullmatch(name_regex,
|
|
256
|
+
if match := re.fullmatch(name_regex, sig.name):
|
|
229
257
|
assert len(k) == 0
|
|
230
258
|
key = (match.groups(),)
|
|
231
|
-
if
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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)
|
|
240
269
|
else:
|
|
241
270
|
p_bare, range_suffix = split_by_range_expr(p)
|
|
242
271
|
if p_bare != sig_bare:
|
|
243
272
|
continue
|
|
244
273
|
key = k
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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)
|
|
253
288
|
break
|
|
254
289
|
return res
|
|
255
290
|
|
|
291
|
+
def match_signals_in_scope(
|
|
292
|
+
scope: Scope, signal_pattern_list: list[PatternMap]
|
|
293
|
+
) -> dict[tuple[Any, ...], Signal]:
|
|
294
|
+
return _match_signals_in_list(scope.signal_list, signal_pattern_list)
|
|
295
|
+
|
|
256
296
|
return _traverse_scope_tree(scope, descendant_scope_pattern_list, match_signals_in_scope)
|
|
257
297
|
|
|
258
298
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SignalCompositeType(Enum):
|
|
9
|
+
"""Composite (non-leaf) signal type as reported by the waveform backend.
|
|
10
|
+
|
|
11
|
+
Not all backends support composite signals. When a backend does not
|
|
12
|
+
distinguish composite types the field is ``None``.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
ARRAY = 'array'
|
|
16
|
+
STRUCT = 'struct'
|
|
17
|
+
UNION = 'union'
|
|
18
|
+
TAGGED_UNION = 'tagged_union'
|
|
19
|
+
RECORD = 'record'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Signal:
|
|
24
|
+
"""Metadata descriptor for a single hardware signal.
|
|
25
|
+
|
|
26
|
+
Stores the signal's local name, full hierarchical path, bit-width,
|
|
27
|
+
declared bit-range, and signedness. For composite signals (structs,
|
|
28
|
+
unions, arrays) the ``composite_type`` and ``member_list`` fields carry
|
|
29
|
+
the internal structure.
|
|
30
|
+
|
|
31
|
+
Attributes
|
|
32
|
+
----------
|
|
33
|
+
name:
|
|
34
|
+
Local signal identifier as it appears within its parent scope,
|
|
35
|
+
matching the form used in the waveform file. May include a range
|
|
36
|
+
suffix when the file stores the signal with one, e.g. ``"data[7:0]"``
|
|
37
|
+
or ``"mem[3][7:0]"``. Scalar signals have no suffix, e.g. ``"clk"``.
|
|
38
|
+
Invariant: ``full_name == parent_scope_path + "." + name``.
|
|
39
|
+
full_name:
|
|
40
|
+
Complete hierarchical signal path, e.g. ``"tb.dut.data[7:0]"`` or
|
|
41
|
+
``"tb.dut.mem[3][7:0]"``. Equal to ``parent_scope_path + "." + name``.
|
|
42
|
+
Pass this directly to :meth:`~wavekit.readers.base.Reader.load_waveform`.
|
|
43
|
+
width:
|
|
44
|
+
Bit-width of the signal, e.g. ``8`` for ``[7:0]``. ``None`` if not
|
|
45
|
+
yet resolved.
|
|
46
|
+
range:
|
|
47
|
+
The innermost (last) bit-range of the signal as a ``(high, low)``
|
|
48
|
+
integer tuple. For a plain vector ``data[7:0]`` this is ``(7, 0)``;
|
|
49
|
+
for a multi-dimensional signal ``mem[3][7:0]`` this is ``(7, 0)``
|
|
50
|
+
(the ``[3]`` dimension index is encoded in ``name``/``full_name`` only).
|
|
51
|
+
For a single-bit index ``[n]`` this is ``(n, n)``.
|
|
52
|
+
``None`` if the signal is scalar, composite, or the format does not
|
|
53
|
+
expose range information.
|
|
54
|
+
signed:
|
|
55
|
+
Whether the signal value should be interpreted as a two's-complement
|
|
56
|
+
signed integer. Defaults to ``False``.
|
|
57
|
+
composite_type:
|
|
58
|
+
``None`` for leaf (non-composite) signals. For composite signals
|
|
59
|
+
(struct, union, array, …) this holds the :class:`SignalCompositeType`
|
|
60
|
+
value describing the kind of composite. Not all backends populate
|
|
61
|
+
this field; backends that do not support composite introspection leave
|
|
62
|
+
it as ``None`` (e.g. VCD).
|
|
63
|
+
member_list:
|
|
64
|
+
``None`` for leaf signals. For composite signals this is the list of
|
|
65
|
+
direct member :class:`Signal` objects, populated in the same order the
|
|
66
|
+
backend reports them. Always ``None`` when ``composite_type`` is
|
|
67
|
+
``None``, and always a list (possibly empty) when ``composite_type``
|
|
68
|
+
is set. Lazily evaluated on first access; backend-specific subclasses
|
|
69
|
+
override the ``member_list`` cached property to provide the actual loading logic.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
name: str
|
|
73
|
+
full_name: str
|
|
74
|
+
width: int | None
|
|
75
|
+
range: tuple[int, int] | None
|
|
76
|
+
signed: bool = False
|
|
77
|
+
composite_type: SignalCompositeType | None = None
|
|
78
|
+
|
|
79
|
+
@cached_property
|
|
80
|
+
def member_list(self) -> list[Signal] | None:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def __str__(self) -> str:
|
|
84
|
+
return (
|
|
85
|
+
f"Signal(name='{self.name}', full_name='{self.full_name}', "
|
|
86
|
+
f'width={self.width}, signed={self.signed}, range={self.range})'
|
|
87
|
+
)
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@dataclass
|
|
7
|
-
class Signal:
|
|
8
|
-
"""Metadata descriptor for a single hardware signal.
|
|
9
|
-
|
|
10
|
-
Stores the signal's local name, full hierarchical path, bit-width,
|
|
11
|
-
declared bit-range, and signedness.
|
|
12
|
-
|
|
13
|
-
Attributes
|
|
14
|
-
----------
|
|
15
|
-
name:
|
|
16
|
-
Local signal identifier as it appears within its parent scope,
|
|
17
|
-
matching the form used in the waveform file. May include a range
|
|
18
|
-
suffix when the file stores the signal with one, e.g. ``"data[7:0]"``
|
|
19
|
-
or ``"mem[3][7:0]"``. Scalar signals have no suffix, e.g. ``"clk"``.
|
|
20
|
-
Invariant: ``full_name == parent_scope_path + "." + name``.
|
|
21
|
-
full_name:
|
|
22
|
-
Complete hierarchical signal path, e.g. ``"tb.dut.data[7:0]"`` or
|
|
23
|
-
``"tb.dut.mem[3][7:0]"``. Equal to ``parent_scope_path + "." + name``.
|
|
24
|
-
Pass this directly to :meth:`~wavekit.readers.base.Reader.load_waveform`.
|
|
25
|
-
width:
|
|
26
|
-
Bit-width of the signal, e.g. ``8`` for ``[7:0]``. ``None`` if not
|
|
27
|
-
yet resolved.
|
|
28
|
-
range:
|
|
29
|
-
The declared or user-requested bit-range of the signal as a
|
|
30
|
-
``(high, low)`` integer tuple, e.g. ``(7, 0)`` for ``[7:0]``.
|
|
31
|
-
For single-bit selection ``[n]`` this is stored as ``(n, n)``.
|
|
32
|
-
``None`` if the signal is scalar or the format does not expose
|
|
33
|
-
range information.
|
|
34
|
-
signed:
|
|
35
|
-
Whether the signal value should be interpreted as a two's-complement
|
|
36
|
-
signed integer. Defaults to ``False``.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
name: str
|
|
40
|
-
full_name: str
|
|
41
|
-
width: int | None
|
|
42
|
-
range: tuple[int, int] | None
|
|
43
|
-
signed: bool = False
|
|
44
|
-
|
|
45
|
-
def __str__(self) -> str:
|
|
46
|
-
return (
|
|
47
|
-
f"Signal(name='{self.name}', full_name='{self.full_name}', "
|
|
48
|
-
f'width={self.width}, signed={self.signed}, range={self.range})'
|
|
49
|
-
)
|
|
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
|