• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Nic30 / pyDigitalWaveTools / 7712ae45-4383-41ee-b902-7a23b4f22dd2

pending completion
7712ae45-4383-41ee-b902-7a23b4f22dd2

push

circleci

Nic30
codestyle

72 of 103 branches covered (69.9%)

Branch coverage included in aggregate %.

7 of 7 new or added lines in 1 file covered. (100.0%)

343 of 410 relevant lines covered (83.66%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.51
/pyDigitalWaveTools/vcd/parser.py
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3

4
'''
1✔
5
:note: inspired by https://github.com/GordonMcGregor/vcd_parser/blob/master/vcd/parser.py
6

7
A basic self contained VCD parser object
8

9
Walks through the definitions constructing the appropriate signal references.
10
Caches XMR paths if and when the signal value changes in the dump for future reference.
11
Needs some hooks for callbacks on signal changes and methods to allow sampling of a signal with an appropriate clock reference
12

13
Refer to IEEE SystemVerilog standard 1800-2009 for VCD details Section 21.7 Value Change Dump (VCD) files
14
'''
15

16
from collections import defaultdict
1✔
17
from io import StringIO
1✔
18
from itertools import dropwhile
1✔
19
from typing import Union
1✔
20

21
from pyDigitalWaveTools.vcd.common import VcdVarScope, VcdVarInfo
1✔
22

23

24
class VcdSyntaxError(Exception):
1✔
25
    pass
1✔
26

27

28
class VcdDuplicatedVariableError(Exception):
1✔
29
    """
30
    This is when multiple definition to one variable happens.
31
    E.g.
32
    $scope module testbench $end
33
    $var reg 3 ! x [2:0] $end
34
    $upscope $end
35
    $scope module testbench $end
36
    $var wire 8 " x [7:0] $end
37
    $upscope $end
38
    """
39
    pass
1✔
40

41

42
class VcdVarParsingInfo(VcdVarInfo):
1✔
43
    """
44
    Container of informations about variable in VCD for parsing of VCD file
45
    """
46

47
    def __init__(self, vcdId: Union[str, VcdVarInfo], name: str, width, sigType, parent):
1✔
48
        super(VcdVarParsingInfo, self).__init__(
1✔
49
            vcdId, name, width, sigType, parent)
50
        self.data = []
1✔
51

52
    def toJson(self):
1✔
53
        return {"name": self.name,
1✔
54
                "type": {"width": self.width,
55
                         "name": self.sigType},
56
                "data": self.data}
57

58

59
class VcdParser(object):
1✔
60
    '''
61
    A parser object for VCD files.
62
    Reads definitions and walks through the value changes
63
    https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=954909&tag=1
64

65
    :ivar ~.keyword_dispatch: dictionary {keyword: parse function}
66
    :ivar ~.scope: actual VcdSignalInfo
67
    :ivar ~.now: actual time (int)
68
    :ivar ~.idcode2series: dictionary {idcode: series} where series are list of tuples (time, value)
69
    :ivar ~.signals: dict {topName: VcdSignalInfo instance}
70
    '''
71
    VECTOR_VALUE_CHANGE_PREFIX = {
1✔
72
        "b", "B", "r", "R"
73
    }
74
    SCOPE_TYPES = {
1✔
75
        "begin", "fork", "function", "module", "task"
76
    }
77

78
    def __init__(self):
1✔
79
        keyword_functions = {
1✔
80
            # declaration_keyword ::=
81
            "$comment": self.drop_while_end,
82
            "$date": self.save_declaration,
83
            "$enddefinitions": self.vcd_enddefinitions,
84
            "$scope": self.vcd_scope,
85
            "$timescale": self.save_declaration,
86
            "$upscope": self.vcd_upscope,
87
            "$var": self.vcd_var,
88
            "$version": self.save_declaration,
89
            # simulation_keyword ::=
90
            "$dumpall": self.vcd_dumpall,
91
            "$dumpoff": self.vcd_dumpoff,
92
            "$dumpon": self.vcd_dumpon,
93
            "$dumpvars": self.vcd_dumpvars,
94
            "$end": self.vcd_end,
95
        }
96

97
        self.keyword_dispatch = defaultdict(
1!
98
            lambda: self.parse_error, keyword_functions)
99

100
        # A root scope is used to deal with situations like
101
        # ------
102
        # $scope module testbench $end
103
        # $var reg 3 ! x [2:0] $end
104
        # $upscope $end
105
        # $scope module testbench $end
106
        # $var wire 8 " y [7:0] $end
107
        # $upscope $end
108
        # $enddefinitions $end
109
        # ------
110
        self.scope = VcdVarScope("root", None)
1✔
111
        self.setNow(0)
1✔
112
        self.idcode2series = {}
1✔
113
        self.end_of_definitions = False
1✔
114

115
    def on_error(self, lineNo, vcdId):
1✔
116
        print ("Wrong vcdId @ line", lineNo, ":", vcdId) 
×
117
        
118
    def value_change(self, vcdId, value, lineNo):
1✔
119
        '''append change from VCD file signal data series'''
120
        try: 
1✔
121
            self.idcode2series[vcdId].append((self.now, value))
1✔
122
        except:
×
123
            self.on_error(lineNo, vcdId)
×
124

125
    def parse_str(self, vcd_string: str):
1✔
126
        """
127
        Same as :func:`~.parse` just for string
128
        """
129
        buff = StringIO(vcd_string)
1✔
130
        return self.parse(buff)
1✔
131
 
132
    def setNow(self, value):
1✔
133
        self.now = int(value)  # TODO: can be float
1✔
134

135
    def parse(self, file_handle):
1✔
136
        '''
137
        Tokenize and parse the VCD file
138

139
        :ivar ~.file_handle: opened file with vcd string
140
        '''
141
        # open the VCD file and create a token generator
142
        lineIterator = iter(enumerate(file_handle))
1✔
143
        tokeniser = ((lineNo, word)
1✔
144
                     for lineNo, line in lineIterator
145
                     for word in line.split() if word)
146
        # def tokeniser_wrap():
147
        #    for t in _tokeniser:
148
        #        print(t)
149
        #        yield t
150
        # tokeniser = tokeniser_wrap()
151

152
        while True:
1✔
153
            token = next(tokeniser)
1✔
154
            # parse VCD until the end of definitions
155
            self.keyword_dispatch[token[1]](tokeniser, token[1])
1✔
156
            if self.end_of_definitions:
1✔
157
                break
1✔
158

159
        while True:
1✔
160
            try:
1✔
161
                lineNo, token = next(tokeniser)
1✔
162
            except StopIteration:
1✔
163
                break
1✔
164

165
            # parse changes
166
            c = token[0]
1✔
167
            if c == '$':
1✔
168
                fn = self.keyword_dispatch[token.strip()]
1✔
169
                fn(tokeniser, token)
1✔
170
            elif c == '#':
1✔
171
                self.setNow (token[1:])
1✔
172
            else:
173
                self.vcd_value_change(lineNo, token, tokeniser)
1✔
174

175
    def vcd_value_change(self, lineNo, token, tokenizer):
1✔
176
        token = token.strip()
1✔
177
        if not token:
1!
178
            return
×
179

180
        if token[0] in self.VECTOR_VALUE_CHANGE_PREFIX:
1✔
181
            # vectors and strings
182
            value = token
1✔
183
            _, vcdId = next(tokenizer)
1✔
184
        elif token[0] == "s":
1✔
185
            # string value
186
            value = token[1:]
1✔
187
            _, vcdId = next(tokenizer)
1✔
188
        elif token[0] == '#':  # In many VCD files there is no $end terminator
1!
189
            self.setNow(token[1:])
×
190
            return
×
191
        else:
192
            # 1 bit value
193
            value = token[0]
1✔
194
            vcdId = token[1:]
1✔
195

196
        self.value_change(vcdId, value, lineNo)
1✔
197

198
    def parse_error(self, tokeniser, keyword):
1✔
199
        raise VcdSyntaxError("Don't understand keyword: ", keyword)
×
200

201
    def drop_while_end(self, tokeniser, keyword):
1✔
202
        next(dropwhile(lambda x: x[1] != "$end", tokeniser))
1✔
203

204
    def read_while_end(self, tokeniser):
1✔
205
        for _, word in tokeniser:
1!
206
            if word == "$end":
1✔
207
                return
1✔
208
            else:
209
                yield word
1✔
210

211
    def save_declaration(self, tokeniser, keyword):
1✔
212
        self.__setattr__(keyword.lstrip('$'),
1✔
213
                         " ".join(self.read_while_end(tokeniser)))
214

215
    def vcd_enddefinitions(self, tokeniser, keyword):
1✔
216
        self.end_of_definitions = True
1✔
217
        self.drop_while_end(tokeniser, keyword)
1✔
218

219
    def vcd_scope(self, tokeniser, keyword):
1✔
220
        scopeType = next(tokeniser)
1✔
221
        scopeTypeName = scopeType[1]
1✔
222
        assert scopeTypeName in self.SCOPE_TYPES, scopeType
1✔
223
        scopeName = next(tokeniser)
1✔
224
        assert next(tokeniser)[1] == "$end"
1✔
225
        s = self.scope
1✔
226
        name = scopeName[1]
1✔
227
        self.scope = VcdVarScope(name, s)
1✔
228
        if isinstance(s, VcdVarScope):
1!
229
            if name in s.children:
1✔
230
                self.scope = s.children[name]
1✔
231
                assert isinstance(self.scope, VcdVarScope), self.scope
1✔
232
            else:
233
                s.children[name] = self.scope
1✔
234

235
    def vcd_upscope(self, tokeniser, keyword):
1✔
236
        self.scope = self.scope.parent
1✔
237
        assert next(tokeniser)[1] == "$end"
1✔
238

239
    def vcd_var(self, tokeniser, keyword):
1✔
240
        data = tuple(self.read_while_end(tokeniser))
1✔
241
        # ignore range on identifier ( TODO  Fix this )
242
        (var_type, size, vcdId, reference) = data[:4]
1✔
243
        parent = self.scope
1✔
244
        size = int(size)
1✔
245
        parent_var = self.idcode2series.get(vcdId, None)
1✔
246
        info = VcdVarParsingInfo(vcdId if parent_var is None else parent_var,
1✔
247
                                 reference, size, var_type, parent)
248
        assert reference not in parent.children
1✔
249
        parent.children[reference] = info
1✔
250
        if parent_var is None:
1!
251
            self.idcode2series[vcdId] = info.data
1✔
252

253
    def _vcd_value_change_list(self, tokeniser):
1✔
254
        while True:
1✔
255
            try:
1✔
256
                lineNo, token = next(tokeniser)
1✔
257
            except StopIteration:
×
258
                break
×
259
            if token and token[0] == "$":
1✔
260
                if token.startswith("$end"):
1!
261
                    return
1✔
262
                else:
263
                    raise VcdSyntaxError(
×
264
                        f"Line {lineNo:d}: Expected $end: {token:s}")
265
            else:
266
                self.vcd_value_change(lineNo, token, tokeniser)
1✔
267

268
    def vcd_dumpall(self, tokeniser, keyword):
1✔
269
        """
270
        specifies current values of all variables dumped
271

272
        vcd_simulation_dumpall ::= $dumpall { value_changes } $end
273

274
        .. code-block:: verilog
275

276
            $dumpall   1*@  x*#   0*$   bx   (k $end
277
        """
278
        self._vcd_value_change_list(tokeniser)
1✔
279

280
    def vcd_dumpoff(self, tokeniser, keyword):
1✔
281
        """
282
        all variables dumped with X values
283

284
        vcd_simulation_dumpoff ::= $dumpoff { value_changes } $end
285

286
        .. code-block:: verilog
287

288
            $dumpoff  x*@  x*#   x*$   bx   (k $end
289
        """
290
        self._vcd_value_change_list(tokeniser)
1✔
291

292
    def vcd_dumpon(self, tokeniser, keyword):
1✔
293
        """
294
        resumption of dumping and lists current values of all variables dumped.
295

296
        vcd_simulation_dumpon ::= $dumpon { value_changes } $end
297

298
        .. code-block:: verilog
299

300
            $dumpon   x*@  0*#   x*$   b1   (k $end
301
        """
302
        self._vcd_value_change_list(tokeniser)
1✔
303

304
    def vcd_dumpvars(self, tokeniser, keyword):
1✔
305
        """
306
        lists initial values of all variables dumped
307

308
        vcd_simulation_dumpvars ::= $dumpvars { value_changes } $end
309

310
        .. code-block:: verilog
311

312
            $dumpvars   x*@   z*$   b0   (k $end
313
        """
314
        self._vcd_value_change_list(tokeniser)
1✔
315

316
    def vcd_end(self, tokeniser, keyword):
1✔
317
        if not self.end_of_definitions:
×
318
            raise VcdSyntaxError("missing end of declaration section")
×
319

320

321
if __name__ == '__main__':
322
    import json
323
    import sys
324
    argc = len(sys.argv)
325
    if argc == 1:
326
        fIn = "../tests/AxiRegTC_test_write.vcd"
327
    elif argc == 2:
328
        fIn = sys.argv[1]
329
    elif argc == 3:
330
        fIn = sys.argv[1]
331
        fOut = sys.argv[2]
332
    else:
333
        raise ValueError(sys.argv)
334

335
    with open(fIn) as vcd_file:
336
        vcd = VcdParser()
337
        vcd.parse(vcd_file)
338
        data = vcd.scope.toJson()
339

340
        if argc == 3:
341
            with open(fOut, 'w') as jsonFile:
342
                jsonFile.write(data)
343
        else:
344
            print(json.dumps(data))
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc