Re: [myhdl-list] Visual Timing Specification for unit testing - complete example
Brought to you by:
jandecaluwe
From: Haitao Z. <ha...@gm...> - 2006-10-04 03:11:08
|
neat idea! On 10/2/06, George Pantazopoulos <ge...@ga...> wrote: > > 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 > ----------------------------------------------------------------- > > ------------------------------------------------------------------------- > Take Surveys. Earn Cash. Influence the Future of IT > Join SourceForge.net's Techsay panel and you'll get the chance to share > your > opinions on IT & business topics through brief surveys -- and earn cash > http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV > _______________________________________________ > myhdl-list mailing list > myh...@li... > https://lists.sourceforge.net/lists/listinfo/myhdl-list > |