[myhdl-list] Visual Timing Specification for unit testing - complete example
Brought to you by:
jandecaluwe
|
From: George P. <ge...@ga...> - 2006-10-03 02:47:11
|
All,
I've come up with a solution that made writing unit tests massively
more fun and less tedious and error-prone for me.
I've found a way to make an easily human-readable ascii timing diagram.
However, this timing diagram is special because the very same diagram
can be parsed and used to drive a MyHDL unit test.
I've pasted a complete working example using an up_counter hardware
module. This example also demonstrates the use of Python dicts for
signal grouping. In fact, the test code relies on this functionality.
I have some improvements planned (such as support for negedges).
Feedback welcome.
Enjoy,
George
# - Begin code
---------------------------------------------------------------
# myhdl_vts.py
# George Pantazopoulos http://www.gammaburst.net
from myhdl import *
#
----------------------------------------------------------------------------
def visual_timing_spec_parse(vts, print_cts=False):
"""
Visual Timing Specification parser for MyHDL
Parses a Visual Timing Specification into a condensed
format suitable for driving or checking signals
A Visual Timing Specification (VTS) is an ascii-based
format for conveying signal timing information.
The same timing spec is both human- and machine-readable.
A VTS object can be used for driving signals as well
as checking signal outputs.
Example VTS objects:
signals_to_drive = dict(
edges = "|0....|1....|2....|3....|4....|5....|6....",
rst = "------____________________________________",
we = "______------------________________________",
re = "____________------------__________________",
dwr = "0x00 0b110 0xBB 0xCC 0xDD ... 0xEE ")
correct_outputs = dict(
edges = "|0....|1....|2....|3....|4....|5....|6....",
drd = "X 0 X 0b110 0xBB X X ",
empty = "X ------____________------------------",
full = "X ____________________________________")
- A VTS is a Python dictionary.
- Each dictionary key is the name of a signal in the design.
- Each key's value is an ASCII string describing that signals behavior
- In addition to the signal keys, a special key named 'edges'
is required.
Its string value contains edge markers and padding. The edge markers
are denoted by '|' for positive edges.
Other characters are currently ignored.
How a VTS is parsed:
--------------------
- Only characters "under" the edge markers are looked at.
- '-' = signal is high at this clock edge
- '_' = signal is low at this clock edge
- '.' = no change (used when driving signals)
- 'X' = "don't care" (used when checking signals)
- integer data values are supported.
Their first character needs to be under the edge marker.
Hex values must be preceded by "0x"
Binary values must be preceded by "0b"
Padding may be added to the 'edges' string to accomodate
data values of any length. One whitespace character must follow
the data item and come before the next edge marker.
Timing:
-------
When driving signals with the VTS, assume that the signal
will be valid at the clock edge.
When checking signals with the VTS, the signal value checked for
must be valid by the time the corresponding edge arrives.
Condensed Timing Spec
---------------------
The result of parsing a Visual Timing Spec is a Condensed Timing Spec.
Its format is similar to the VTS, except that it's not restricted to
ASCII and contains only the data for each clock edge, with no padding.
The CTS is to be passed as input to the actual driver and monitor
functions.
TODO:
-----
TODO: Make it possible to optionally specify negedges too. Eg:
edges = "|0....v.....|1....v.....|2....v...."
Where 'v' denotes a negedge
TODO: Make it possible to concatenate multiple VTS's into a list,
so long timing specs remain easily readable.
"""
__author__ = "George Pantazopoulos http://www.gammaburst.net"
__revision__ = ""
__date__ = "2 Oct 2006"
__version__ = "0.1.0"
__requires__ = ('myhdl', '>= 0.5.1')
# Create an empty Condensed Timing Specification
cts = dict()
for sig in vts:
cts[sig] = []
# There should be dictionary item called "edges"
# We'll use this to figure out when to sample
edgenum = 0
# For each character in the 'edges' specifier string
for i in range(len(vts['edges'])):
# Grab the character value
c = vts['edges'][i]
if c == '|':
# We sample here.
# For each signal specifier string in vts
for sig in vts:
if sig == 'edges':
data = edgenum
edgenum += 1
elif sig != 'edges':
# Get the character at the edge sampling index
d = vts[sig][i]
if d == '_':
data = False
elif d == '-':
data = True
# This is a "don't-care".
# Intended for use when checking signals,
# not driving them.
elif d == 'X':
data = ' '
# "dont update this signal" when used for driving
elif (d == '.'):
data = ' '
# A space under an edge marker is illegal, because it
# could mean a formatting error was made by the user.
#
# This type of error can be hard to spot.
# If we don't trap this, it could lead to wasted time
# and misleading test results.
elif d == ' ':
diag = "signal: " + sig + ", offset=" + str(i)
raise Exception, \
"Spaces are not allowed under edge markers " + diag
else:
# Treat this char that's under the edge marker
# as the start of a data value string
#
# We need to parse the data string until we reach
# whitespace or the next edge marker.
#
# grab the next chars until we hit whitespace or the
# next edge marker.
#
# Start at the char 'under' the current edge marker.
# TODO: Stop if we hit the next edge marker
n = 0
dstr = ""
while d != ' ':
d = vts[sig][i+n]
dstr += d
n += 1
# convert to an int.
# The (required) trailing whitespace is ok.
# Hex
if dstr[:2] == "0x":
data = int(dstr[2:],16)
# Binary
elif dstr[:2] == "0b":
data = int(dstr[2:], 2)
# Decimal
else:
data = int(dstr)
# Add the data item to the appropriate signal's
list.
cts[sig].append(data)
if print_cts:
print "cts: "
import pprint
print pprint.pprint(cts)
return cts
#
----------------------------------------------------------------------------
def drive_signals_from_cts(sigs, cts):
"""
drive_signals_from_cts() - unit test tool for MyHDL
Given a Condensed Timing Spec object, drive the signals according to
that specification. See the Visual Timing Spec parser doc.
The signal dictionary must contain a 'clk' signal
"""
__author__ = "George Pantazopoulos http://www.gammaburst.net"
__revision__ = ""
__date__ = "2 Oct 2006"
__version__ = "0.1.0"
__requires__ = ('myhdl', '>= 0.5.1')
if 'clk' not in sigs:
raise Exception, \
"signal dictionary 'sigs' must contain a 'clk' signal"
clk = sigs['clk']
for i in range(len(cts['edges'])):
# Setup for positive edge i
# Drive each signal with the correct value for
# the upcoming positive edge i
for sig in cts:
value = cts[sig][i]
if sig != 'edges':
if value != ' ':
sigs[sig].next = value
# positive edge i
yield clk.posedge
# negative edge i
yield clk.negedge
#
----------------------------------------------------------------------------
def check_signals_against_cts(sigs, cts):
"""
check_signals_against_cts() - unit test tool for MyHDL
Given a Condensed Timing Spec object, ensure the signals match
that specification. See the Visual Timing Spec parser doc.
The signal dictionary must contain a 'clk' signal
"""
__author__ = "George Pantazopoulos http://www.gammaburst.net"
__revision__ = ""
__date__ = "2 Oct 2006"
__version__ = "0.1.0"
__requires__ = ('myhdl', '>= 0.5.1')
if 'clk' not in sigs:
raise Exception, \
"signal dictionary 'sigs' must contain a 'clk' signal"
clk = sigs['clk']
for i in range(len(cts['edges'])):
# Setup for edge i
# Edge i
yield clk.posedge
# Check the outputs that should have been valid by
# the time this edge comes around
for sig in cts:
value = cts[sig][i]
if sig != 'edges':
# Skip this for 'don't-care' values
if value != ' ':
# If the signal is not the value it should have been
if sigs[sig] != value:
# Raise an exception and show some helpful info.
info = "Edge #" + str(i) + \
" Signal \'" + sig + "\'" + \
" = " + str(sigs[sig]) + \
". Correct value = " + str(value)
raise Exception, info
yield clk.negedge
raise StopSimulation()
#
----------------------------------------------------------------------------
def up_counter(sigs):
# Unbundle needed signals. Name them from the perspective of this
module.
clk_i = sigs['clk']
rst_i = sigs['rst']
count_o = sigs['count']
enable_i = sigs['count_en']
@always(clk_i.posedge)
def count_proc():
if rst_i:
count_o.next = 0
else:
if enable_i:
count_o.next = (count_o + 1) % 2**len(count_o)
return instances()
#
----------------------------------------------------------------------------
def up_counter_bench():
# Embedded function definition for clock generator
def clkgen(clk):
while True:
yield delay(10)
clk.next = not clk
# Shared clk signal
clk = Signal(bool(0))
# Create the signal group for the counter
counter_sigs=dict(clk = clk,
rst = Signal(bool(0)),
count = Signal(intbv(0)[4:]),
count_en = Signal(bool(0)))
# Instantiate a clock generator and connect to the shared clock
clkgen_inst = clkgen(clk)
dut = up_counter(counter_sigs)
# Visual Timing Specification
# ---------------------------
signals_to_drive = dict(
edges = "|0....|1....|2....|3....|4....|5....|6....",
rst = "-------------_____________________________",
count_en = "__________________------------------------")
correct_outputs = dict(
edges = "|0....|1....|2....|3....|4....|5....|6....",
count = "X 0 0 0 1 2 3 ")
# Signal driver
driver = drive_signals_from_cts(
sigs = counter_sigs,
cts = visual_timing_spec_parse(signals_to_drive)
)
# Signal monitor
monitor = check_signals_against_cts(
sigs = counter_sigs,
cts = visual_timing_spec_parse(correct_outputs)
)
return instances()
#
----------------------------------------------------------------------------
def test_up_counter():
tb = up_counter_bench()
sim = Simulation(tb)
sim.run()
test_up_counter()
# - End code
-----------------------------------------------------------------
|