Skip to content

Commit

Permalink
v2.0.0
Browse files Browse the repository at this point in the history
Properly expose signal random access
  • Loading branch information
cirosantilli committed Sep 3, 2020
1 parent cf08279 commit ba56abd
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 33 deletions.
37 changes: 30 additions & 7 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,24 @@ means that:
* the signal `counter_tb.top.out[1:0]`
* had value `x`

== About this repository

The VCD format is defined by the Verilog standard, and can be generated with `$dumpvars`.
== API usage

Library usage examples can be seen at link:example.py[] and run with:

....
./examples.py
....

By default, data is parsed at once into a per-signal format that allows for efficient random access TODO: convert signals to class and add a binary search helper, for now just do binary search yourself.
By default, data is parsed at once into a per-signal format that allows for efficient random access, for example:

....
from vcdvcd import VCDVCD
vcd = VCDVCD('counter_tb.vcd')
signal = vcd['counter_tb.top.out[1:0]']
print(signal[0])
print(signal[1])
print(signal[3])
....

But you can also use this library in a puerly stream callback fashion as shown in the examples by doing something like:

Expand All @@ -183,15 +190,30 @@ class MyStreamParserCallbacks(vcdvcd.StreamParserCallbacks):
cur_sig_vals,
):
print('{} {} {}'.format(time, value, identifier_code))
vcd = VCDVCD(vcd_path, callbacks=MyStreamParserCallbacks(), store_tvs=False)
vcd = VCDVCD('counter_tb.vcd', callbacks=MyStreamParserCallbacks(), store_tvs=False)
....

`store_tvs` instructs the library to not store its own signal data, which would likely just take up useless space in your streaming application.

== About this repository

The VCD format is defined by the Verilog standard, and can be generated with `$dumpvars`.

This repo was originally forked from Sameer Gauria's version, which is currently only hosted on PyPI with email patches and no public bug tracking: link:https://pypi.python.org/pypi/Verilog_VCD[]. There is also a read-only mirror at: link:https://github.com/zylin/Verilog_VCD[].

Another stream implementation can be seen at: link:https://github.com/GordonMcGregor/vcd_parser[].

== Release procedure

Ensure that basic tests don't blow up:

....
./examples.py
./test.py
./vcdcat counter_tb.py
./vcdcat -d counter_tb.py
....

Update the `version` field in `setup.py`:

....
Expand All @@ -201,9 +223,10 @@ vim setup.py
Create a tag and push it:

....
v=v1.0.1
git add setup.py
git commit -m v1.0.1 v1.0.1
git tag -a v1.0.1 -m v1.0.1
git commit -m $v $v
git tag -a $v -m $v
git push --follow-tags
....

Expand Down
4 changes: 2 additions & 2 deletions examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

print('# __init__(only_sigs=True)')
vcd = VCDVCD(vcd_path, only_sigs=True)
PrettyPrinter().pprint(vcd.data)
pp.pprint(vcd.data)
print()

print('# __init__(signals=)')
Expand Down Expand Up @@ -62,7 +62,7 @@

print('# __init__(callbacks=vcdvcd.PrintDeltasStreamParserCallbacks(), store_tvs=False)')
vcd = VCDVCD(vcd_path, callbacks=vcdvcd.PrintDeltasStreamParserCallbacks(), store_tvs=False)
PrettyPrinter().pprint(vcd.data)
pp.pprint(vcd.data)
print()

print('# __init__(callbacks=vcdvcd.PrintDeltasStreamParserCallbacks(), signals=)')
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def readme():

setup(
name='vcdvcd',
version='1.0.6',
version='2.0.0',
description='Python Verilog value change dump (VCD) parser library + the nifty vcdcat VCD command line viewer',
long_description=readme(),
long_description_content_type='text/plain',
Expand Down
11 changes: 10 additions & 1 deletion test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
class Test(unittest.TestCase):
def test_data(self):
vcd = VCDVCD('counter_tb.vcd')
signal = vcd['counter_tb.out[1:0]']
self.assertEqual(
vcd['counter_tb.out[1:0]']['tv'][:6],
signal.tv[:6],
[
( 0, 'x'),
( 2, '0'),
Expand All @@ -18,6 +19,14 @@ def test_data(self):
(12, '0'),
]
)
self.assertEqual(signal[0], 'x')
self.assertEqual(signal[1], 'x')
self.assertEqual(signal[2], '0')
self.assertEqual(signal[3], '0')
self.assertEqual(signal[5], '0')
self.assertEqual(signal[6], '1')
self.assertEqual(signal[7], '1')
self.assertEqual(signal[8], '10')

if __name__ == '__main__':
unittest.main()
83 changes: 61 additions & 22 deletions vcdvcd/vcdvcd.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from __future__ import print_function

import bisect
import math
import re
from decimal import Decimal

from pprint import PrettyPrinter
pp = PrettyPrinter()

class VCDVCD(object):

# Verilog standard terminology.
Expand All @@ -23,31 +27,29 @@ def __init__(
The bulk of the parsed data can be obtained with :func:`parse_data`.
:type data: Dict[str,Any]
:ivar data: Parsed VCD data presented in an per-signal indexed list of deltas.
Maps short (often signle character) signal names present in the VCD,
to Signal objects.
:vartype data: Dict[str,Signal]
Binary search in this data presentation makes it possible
to efficiently find the value of a given signal at a given time
in random access. TODO implement helper.
:type deltas: Dict[str,Any]
:ivar deltas: Parsed VCD data presented in exactly the same format as the input,
with all signals mixed up, but sorted in time.
:vartype deltas: Dict[str,Any]
:ivar endtime: Last timestamp present in the last parsed VCD.
This can be extracted from the data, but we also cache while parsing.
:type endtime: int
:vartype endtime: int
:ivar references_to_ids: map of long-form human readable signal names to the short
style VCD dump values
:type references_to_ids: Dict[str,str]
:vartype references_to_ids: Dict[str,str]
:ivar signals: The set of unique signal names from the parsed VCD,
in the order they are defined in the file.
This can be extracted from the data, but we also cache while parsing.
:type signals: List[str]
:vartype signals: List[str]
:ivar timescale: A dictionary of key/value pairs describing the timescale.
Expand All @@ -57,7 +59,7 @@ def __init__(
- "number": time number as specified in the VCD file
- "unit": time unit as specified in the VCD file
- "factor": numerical factor derived from the unit
:type timescale: Dict
:vartype timescale: Dict
:type vcd_path: str
:param vcd_path: path to the VCD file to parse
Expand Down Expand Up @@ -146,12 +148,8 @@ def __init__(
if (reference in signals) or all_sigs:
self.signals.append(reference)
if identifier_code not in self.data:
self.data[identifier_code] = {
'references': [],
'size': size,
'var_type': type,
}
self.data[identifier_code]['references'].append(reference)
self.data[identifier_code] = Signal(size, type)
self.data[identifier_code].references.append(reference)
self.references_to_ids[reference] = identifier_code
cur_sig_vals[identifier_code] = 'x'
elif '$timescale' in line:
Expand Down Expand Up @@ -196,9 +194,7 @@ def _add_value_identifier_code(
entry = self.data[identifier_code]
self.signal_changed = True
if self._store_tvs:
if 'tv' not in entry:
entry['tv'] = []
entry['tv'].append((time, value))
entry.tv.append((time, value))
cur_sig_vals[identifier_code] = value

def __getitem__(self, refname):
Expand All @@ -207,6 +203,7 @@ def __getitem__(self, refname):
:param refname: human readable name of a signal (reference)
:return: the signal for the given reference
:rtype: Signal
"""
return self.data[self.references_to_ids[refname]]

Expand Down Expand Up @@ -235,6 +232,48 @@ def get_timescale(self):
"""
return self.timescale

class Signal(object):
"""
Contains signal metadata and all value/updates pairs for a given signal.
Allows for efficient binary search of the value of this signal at a given time.
:param size: number of bits in the signal
:type size: int
:param size: e.g. 'wire' or 'reg'
:type var_type: str
:ivar references: list of human readable long names for the signal
:vartype references: List[str]
:ivar tv: sorted list of time/new value pairs. Signal values are be strings
instead of integers to represents values such as 'x'.
:vartype tv: List[Tuple[int,str]]
"""
def __init__(self, size, var_type):
self.size = size
self.var_type = var_type
self.references = []
self.tv = []

def __getitem__(self, time):
"""
Get the value of a signal at a given time.
:type time: int
:rtype time: str
"""
left = bisect.bisect_left(self.tv, (time, ''))
if self.tv[left][0] == time:
i = left
else:
i = left - 1
return self.tv[i][1]

def __repr__(self):
return pp.pformat(self.__dict__)

class StreamParserCallbacks(object):
def enddefinitions(
self,
Expand Down Expand Up @@ -287,7 +326,7 @@ def value(
print('{} {} {}'.format(
time,
binary_string_to_hex(value),
vcd.data[identifier_code]['references'][0])
vcd.data[identifier_code].references[0])
)

class PrintDumpsStreamParserCallbacks(StreamParserCallbacks):
Expand Down Expand Up @@ -320,13 +359,13 @@ def enddefinitions(
if signals:
self._print_dumps_refs = signals
else:
self._print_dumps_refs = sorted(vcd.data[i]['references'][0] for i in cur_sig_vals.keys())
self._print_dumps_refs = sorted(vcd.data[i].references[0] for i in cur_sig_vals.keys())
for i, ref in enumerate(self._print_dumps_refs, 1):
print('{} {}'.format(i, ref))
if i == 0:
i = 1
identifier_code = vcd.references_to_ids[ref]
size = int(vcd.data[identifier_code]['size'])
size = int(vcd.data[identifier_code].size)
width = max(((size // 4)), int(math.floor(math.log10(i))) + 1)
self._references_to_widths[ref] = width
print()
Expand Down

0 comments on commit ba56abd

Please sign in to comment.