Thread: [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 ----------------------------------------------------------------- |
From: <dan...@we...> - 2006-10-03 08:42:54
|
George Pantazopoulos 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. Hi George, That is a good idea. I briefly looked over the code and saw that you are doing the parsing yourself. I wonder whether in the long run using a parser package would help for the code not to get too complex. I found this PyParsing package a while ago http://pyparsing.wikispaces.com/ that can be used very flexible for all kinds of parsing. > I have some improvements planned (such as support for negedges). > Feedback welcome. > I ran over such a tool a while ago -- though not for MyHDL :) --. It did parse the timing data from an ASCII file. I googled for it, but have not found it yet. I just wonder whether there exists already some common character set to specify signal data. Maybe that would allow to use it to use output from other tools in MyHDL? Just a thought. Cheers, Guenter |
From: George P. <ge...@ga...> - 2006-10-05 16:51:16
|
On Tue, October 3, 2006 4:42 am, G=FCnter Dannoritzer wrote: > George Pantazopoulos wrote: >> All, >> I've come up with a solution that made writing unit tests massivel= y >> 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. > > Hi George, > > That is a good idea. I briefly looked over the code and saw that you ar= e > doing the parsing yourself. > > I wonder whether in the long run using a parser package would help for > the code not to get too complex. I found this PyParsing package a while > ago http://pyparsing.wikispaces.com/ that can be used very flexible for > all kinds of parsing. > > > I have some improvements planned (such as support for negedges). >> Feedback welcome. >> > > I ran over such a tool a while ago -- though not for MyHDL :) --. It di= d > parse the timing data from an ASCII file. I googled for it, but have no= t > found it yet. I just wonder whether there exists already some common > character set to specify signal data. Maybe that would allow to use it > to use output from other tools in MyHDL? Just a thought. > I think those are good suggestions Guenter, thanks. If you come across that tool, drop me a line. Thanks, George --=20 George Pantazopoulos http://www.gammaburst.net |
From: <dan...@we...> - 2006-10-11 12:39:28
|
George Pantazopoulos wrote: >> > > I think those are good suggestions Guenter, thanks. If you come across > that tool, drop me a line. George, I have not found the link yet I was thinking about, but found another project that might be interesting: http://sigschege.berlios.de/ Though it seems like they are not very far yet. Cheers, Guenter |
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 > |
From: Thomas H. <th...@py...> - 2006-10-05 15:47:18
|
George Pantazopoulos schrieb: > 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 > Would not '/' for positive edges and '\' for negative edges look nicer? edges = r"/0....\...../1....\...../2....\...." or edges = r"/0----\_____/1----\_____/2----\ " Thomas |
From: George P. <ge...@ga...> - 2006-10-05 16:47:43
|
On Thu, October 5, 2006 11:36 am, Thomas Heller wrote: > George Pantazopoulos schrieb: >> 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 =3D "|0....v.....|1....v.....|2....v...." >> Where 'v' denotes a negedge >> > > Would not '/' for positive edges and '\' for negative edges look nicer? > > edges =3D r"/0....\...../1....\...../2....\...." > or > edges =3D r"/0----\_____/1----\_____/2----\ " > > Thomas > Yeah, thats great! Thanks a lot, Tom. George --=20 George Pantazopoulos http://www.gammaburst.net |
From: M. <jos...@pt...> - 2007-01-22 16:20:16
|
I think it is great! I've altered your code to suport negedges. Hope it works fine! :) # - 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 == '|' or c == '^': # We sample here. # For each signal specifier string in vts for sig in vts: if sig == 'edges': data = ( edgenum , c=='|' ) 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'])): if 1==cts['edges'][i][1]: # 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 if 0==cts['edges'][i][1]: # Setup for negative 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 # 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 if 1==cts['edges'][i][1]: # Edge i yield clk.posedge else: yield clk.negedge # 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 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() # ---------------------------------------------------------------------------- if __name__ == '__main__': def test_up_counter(): tb = up_counter_bench() sim = Simulation(tb) sim.run() test_up_counter() # - End code --------------------------------------------------------------- |
From: M. <jos...@pt...> - 2007-01-23 00:30:14
|
Great. I changed your code to suport "negedge" clk. It seams to work :) ... # - 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 == '/' or c == '\\': # We sample here. # For each signal specifier string in vts for sig in vts: if sig == 'edges': data = ( edgenum , c=='/' ) 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'])): if 1==cts['edges'][i][1]: # 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 if 0==cts['edges'][i][1]: # Setup for negative 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 # 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 if 1==cts['edges'][i][1]: # Edge i yield clk.posedge else: yield clk.negedge # 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 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() # ---------------------------------------------------------------------------- if __name__ == '__main__': def test_up_counter(): tb = up_counter_bench() sim = Simulation(tb) sim.run() test_up_counter() # - End code ----------------------------------------------------------------- |