Testbench conversion (solved)

In the below, a Mod-m counter is implemented along with testbench.

http://fpga-designs-with-myhdl.readthedocs.io/en/latest/myhdl/testbench.html#mod-m-counter

  1. Is it possible to convert the MyHDL testbench to HDL testbench.
  2. Currently, If I add following line at Line 92 of the code, then the error “delay argument in @always decorator” is displayed,
       tb.convert(hdl="Verilog", initial_values=True)
  1. Then I replaced the @always with @instance as shown in below link (to remove the delay), but that did not work as well.

    http://docs.myhdl.org/en/stable/manual/intro.html#parameters-and-hierarchy

  2. How can I resolve this problem?

  3. Also, simulation time is in ‘ns’ at the output at the end of the section. Can we adjust that as well using MyHDL?

You can’t convert a test bench.
mod_m_counter module is convertible but mod_m_counter_tb is not.

@DrPi ,
I am afraid I have to correct you: test-benches can be converted (as this is necessary to run CoSimualtion), but the converted code will not synthesise (which is to be expected).

@pythondsp
The clock generation process is better written as shown in the link under 3.:

    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(int(period//2))

This will convert. If you say that didn’t work that well, what is the symptom?

MyHDL has no notion of time but rather of unit delay and it handles delay in int value only. The '1ns' scale seemed to be the most convenient to represent the stepping rate of the simulator. It can be set to to something else as '1ps' (I suppose).(but I don’t see any advantage?)

Regards,
Josy

  1. Following error is displayed when I made the above changes,
<class 'myhdl._SuspendSimulation'>: Simulated 300 timesteps
reset_n,complete_tick,count
Traceback (most recent call last):
  File "mod_m_counter.py", line 121, in <module>
    main()
  File "mod_m_counter.py", line 118, in main
    tb.convert(hdl="Verilog", initial_values=True)
  File "/home/anaconda3/lib/python3.6/site-packages/myhdl-1.0.dev0-py3.6.egg/myhdl/_block.py", line 276, in convert
    return converter(self)
  File "/home/anaconda3/lib/python3.6/site-packages/myhdl-1.0.dev0-py3.6.egg/myhdl/conversion/_toVerilog.py", line 177, in __call__
    genlist = _analyzeGens(arglist, h.absnames)
  File "/home/anaconda3/lib/python3.6/site-packages/myhdl-1.0.dev0-py3.6.egg/myhdl/conversion/_analyze.py", line 174, in _analyzeGens
    tree.symdict = f.f_globals.copy()
AttributeError: 'NoneType' object has no attribute 'f_globals'
  1. Below is the modified code,
# mod_m_counter.py

from myhdl import *

period = 20 # clk frequency = 50 MHz

@block
def mod_m_counter(clk, reset_n, complete_tick, count, M, N):
    """ M = max count
        N = minimum bits required to represent M
    """

    count_reg = Signal(intbv(0)[N:0])
    count_next = Signal(intbv(0)[N:0])

    @always(clk.posedge, reset_n.negedge)
    def logic_reg():
        if reset_n == 0 :
            count_reg.next = 0
        else :
            count_reg.next = count_next

    @always_comb
    def logic_next():
        if count_reg == M-1 :
            count_next.next = 0
        else :
            count_next.next = count_reg + 1


    # optional complete_tick
    if complete_tick is not None:
        @always_comb
        def complete_tick_logic():
            if count_reg == M-1 :
                complete_tick.next = 1
            else :
                complete_tick.next = 0

    # optional count
    if count is not None:
        @always_comb
        def out_val():
            count.next = count_reg

    return instances()  # return all instances


# testbench
@block
def mod_m_counter_tb():
    N = 3
    M = 5

    # 'intbv' is used instead of 'bool' as 'false' will be displayed
    # in the saved-data for 'bool'. 
    # clk = Signal(bool(0))
    clk = Signal(intbv(0)[1:0]) 
    reset_n = Signal(intbv(0)[1:0])
    complete_tick = Signal(intbv(0)[1:0])
    count = Signal(intbv(0)[N:0])

    mod_m_counter_inst = mod_m_counter(clk, reset_n, complete_tick, count, M, N)

    
    # int is required
    # @always(delay(int(period/2)))
    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(int(period//2))
        
    @instance  # reset signal
    def reset_signal():
        reset_n.next = 0
        yield delay(period)
        reset_n.next = 1
        
    
    # print simulation data on screen and file
    file_data = open("mod_m_counter.csv", 'w') # file for saving data
    # print header on screen
    print("{0},{1},{2}".format("reset_n", "complete_tick", "count"))
    # print header to file
    print("{0},{1},{2}".format("reset_n", "complete_tick", "count")
                , file=file_data)
    # print data on each clock
    @always(clk.posedge)
    def print_data():
        # print on screen
        print("{0}, {1}, {2}".format(reset_n, complete_tick, count))
        # print in file
        print("{0}, {1}, {2}".format(reset_n, complete_tick, count)
                , file=file_data) 


    return instances()


def main():
    N = 3
    M = 5

    clk = Signal(bool(0))
    reset_n = Signal(bool(0))
    complete_tick = Signal(bool(0))
    count = Signal(intbv(0)[N:0])

    mod_m_counter_v = mod_m_counter(clk, reset_n, complete_tick, count, M, N)
    mod_m_counter_v.convert(hdl="Verilog", initial_values=True)

    # test bench
    tb = mod_m_counter_tb()
    tb.config_sim(trace=True)
    tb.run_sim(15*period) # run for 15 clock cycle
    
    tb.convert(hdl="Verilog", initial_values=True)

if __name__ == '__main__':
    main()

It turns out to be what I expected: print() is confusing us …
Our BDFL choose print() (with parenthesis!) as the name of the MyHDL function to implement Verilog’s write statement.
Alas Python’s BDFL broke the print in making Python 3.x and now we get confused …
I never convert test-benches so I have no issue with that
This is how I got the conversion working:

   # print data on each clock
    @always_seq(clk.posedge, reset=None)
    def print_data():
        print(int(reset_n))
        print(int(complete_tick))
        print(int(count))

or

   # print data on each clock
    @instance
    def print_data():
        # print on screen
        while True:
            yield clk.posedge
#             print("{0}, {1}, {2}".format(reset_n, complete_tick, count))
#             print(int(reset_n), int(complete_tick), int(count))
            print(int(reset_n))
            print(int(complete_tick))
            print(int(count))

The 2 commented-out print statements unfortunately don’t work in conversion.

Actually I had a few more changes, I include the complete source

from __future__ import print_function

# mod_m_counter.py

from myhdl import *

from Utilities.hdlutils import simulate

period = 20 # clk frequency = 50 MHz

# @block
def mod_m_counter(clk, reset_n, complete_tick, count, M, N):
    """ M = max count
        N = minimum bits required to represent M
    """

    count_reg = Signal(intbv(0)[N:0])
    count_next = Signal(intbv(0)[N:0])

    @always_seq(clk.posedge, reset=reset_n)
    def logic_reg():
        if reset_n == 0 :
            count_reg.next = 0
        else :
            count_reg.next = count_next

    @always_comb
    def logic_next():
        if count_reg == M-1 :
            count_next.next = 0
        else :
            count_next.next = count_reg + 1


    # optional complete_tick
    if complete_tick is not None:
        @always_comb
        def complete_tick_logic():
            if count_reg == M-1 :
                complete_tick.next = 1
            else :
                complete_tick.next = 0

    # optional count
    if count is not None:
        @always_comb
        def out_val():
            count.next = count_reg

    return instances()  # return all instances


# testbench
# @block
def mod_m_counter_tb():
    N = 3
    M = 5

    # 'intbv' is used instead of 'bool' as 'false' will be displayed
    # in the saved-data for 'bool'. 
    # clk = Signal(bool(0))
    clk = Signal(bool(0))
    reset_n = ResetSignal(1,0,False)
    complete_tick = Signal(intbv(0)[1:])
    count = Signal(intbv(0)[N:])

    mod_m_counter_inst = mod_m_counter(clk, reset_n, complete_tick, count, M, N)

    
    # int is required
    # @always(delay(int(period/2)))
    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(int(period//2))
        
    @instance  # reset signal
    def reset_signal():
        reset_n.next = 0
        yield delay(int(period * 1.5))
        reset_n.next = 1
        
    
    # print simulation data on screen and file
#     file_data = open("mod_m_counter.csv", 'w') # file for saving data
    # print header on screen
#     print("{0},{1},{2}".format("reset_n", "complete_tick", "count"))
    # print header to file
#     print("{0},{1},{2}".format("reset_n", "complete_tick", "count"), file=file_data)

    # print data on each clock
    @always_seq(clk.posedge, reset=None)
    def print_data():
        print(int(reset_n))
        print(int(complete_tick))
        print(int(count))
#     @instance
#     def print_data():
#         # print on screen
#         while True:
#             yield clk.posedge
# #             print("{0}, {1}, {2}".format(reset_n, complete_tick, count))
# #             print(int(reset_n), int(complete_tick), int(count))
#             print(int(reset_n))
#             print(int(complete_tick))
#             print(int(count))
        # print in file
#         print("{0}, {1}, {2}".format(reset_n, complete_tick, count), file=file_data) 


    return instances()

def main():
    N = 3
    M = 5
 
    clk = Signal(bool(0))
    reset_n = ResetSignal(1,0,False)
    complete_tick = Signal(bool(0))
    count = Signal(intbv(0)[N:0])

#     mod_m_counter_v = mod_m_counter(clk, reset_n, complete_tick, count, M, N)
#     mod_m_counter_v.convert(hdl="Verilog", initial_values=True)
    simulate(15 * period, mod_m_counter_tb)
    toVerilog(mod_m_counter, clk, reset_n, complete_tick, count, M, N)
    toVerilog(mod_m_counter_tb)
    toVHDL(mod_m_counter,clk, reset_n, complete_tick, count, M, N)
    toVHDL(mod_m_counter_tb)
    # test bench
#     tb = mod_m_counter_tb()
#     tb.config_sim(trace=True)
#     tb.run_sim(15*period) # run for 15 clock cycle
    
#     tb.convert(hdl="Verilog", initial_values=True)

if __name__ == '__main__':
    main()

I haven’t back-ported the block functionality to my local MyHDL source tree, so I had to revert a few things.

Some observations:
Always use a Signal(bool()) for the clock and a ResetSignal() for the reset, so you can use @always_seq(clock.posedge, reset=reset_n) in stead of @always( clock.posedge, reset_n.negedge)

1 Like
  1. But problem still exists with Python-3.

  2. I commented all the print-statements in “@always_seq(clk.posedge, reset=None) def print_data()” and put ‘pass’ in it; and getting the same error. The code is attached.

  3. Also, thanks for the observations. I tried both in the beginning and the RTL diagram were same. So I stick with ‘reset.negedge’ as it is similar to the Verilog-template. Is there any significant difference between ‘always’ and ‘always_seq’.

from myhdl import *

period = 20 # clk frequency = 50 MHz

@block
def mod_m_counter(clk, reset_n, complete_tick, count, M, N):
    """ M = max count
        N = minimum bits required to represent M
    """

    count_reg = Signal(intbv(0)[N:0])
    count_next = Signal(intbv(0)[N:0])

    @always(clk.posedge, reset_n.negedge)
    def logic_reg():
        if reset_n == 0 :
            count_reg.next = 0
        else :
            count_reg.next = count_next

    @always_comb
    def logic_next():
        if count_reg == M-1 :
            count_next.next = 0
        else :
            count_next.next = count_reg + 1


    # optional complete_tick
    if complete_tick is not None:
        @always_comb
        def complete_tick_logic():
            if count_reg == M-1 :
                complete_tick.next = 1
            else :
                complete_tick.next = 0

    # optional count
    if count is not None:
        @always_comb
        def out_val():
            count.next = count_reg

    return instances()  # return all instances


# testbench
@block
def mod_m_counter_tb():
    N = 3
    M = 5

    # 'intbv' is used instead of 'bool' as 'false' will be displayed
    # in the saved-data for 'bool'. 
    # clk = Signal(bool(0))
    clk = Signal(intbv(0)[1:0]) 
    reset_n = Signal(intbv(0)[1:0])
    complete_tick = Signal(intbv(0)[1:0])
    count = Signal(intbv(0)[N:0])

    mod_m_counter_inst = mod_m_counter(clk, reset_n, complete_tick, count, M, N)

    
    # int is required
    # @always(delay(int(period/2)))
    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(int(period//2))
        
    @instance  # reset signal
    def reset_signal():
        reset_n.next = 0
        yield delay(period)
        reset_n.next = 1
        

    
    # # print simulation data on screen and file
    # # print header on screen
    # print("{0},{1},{2}".format("reset_n", "complete_tick", "count"))
    # # print header to file
    # print("{0},{1},{2}".format("reset_n", "complete_tick", "count")
                # , file=file_data)

    # print data on each clock
    @always_seq(clk.posedge, reset=None)
    def print_data():
        pass
        # # print on screen
        # print(int(reset_n))
        # print(int(complete_tick))
        # print(int(count))

        # print("{0}, {1}, {2}".format(reset_n, complete_tick, count))
        # # print in file
        # print("{0}, {1}, {2}".format(reset_n, complete_tick, count)
                # , file=file_data) 


    return instances()


def main():
    N = 3
    M = 5

    clk = Signal(bool(0))
    reset_n = Signal(bool(0))
    complete_tick = Signal(bool(0))
    count = Signal(intbv(0)[N:0])

    mod_m_counter_v = mod_m_counter(clk, reset_n, complete_tick, count, M, N)
    mod_m_counter_v.convert(hdl="Verilog", initial_values=True)

    # test bench
    tb = mod_m_counter_tb()
    tb.config_sim(trace=True)
    tb.run_sim(15*period) # run for 15 clock cycle
    
    tb.convert(hdl="Verilog", initial_values=True)

if __name__ == '__main__':
    main()

It worked.

  1. I remove the .format from print statement and used @instance for clock-generation, as suggested.
  2. Put the Line "tb.convert(hdl=“Verilog”, initial_values=True) " above the the lines “tb.config_sim(trace=True) etc” as below,
tb = mod_m_counter_tb()
tb.convert(hdl="Verilog", initial_values=True)
tb.config_sim(trace=True)
tb.run_sim(15*period) # run for 15 clock cycle
  1. Following are the problems,
    a. First line is printed twice on the screen (undesirable); but in the file it is printed once, which is desired.
    b. How to suppress the message “<class ‘myhdl._Signal._Signal’> <class ‘_ast.Name’>”
reset_n,complete_tick,count
reset_n,complete_tick,count
<class 'myhdl._Signal._Signal'> <class '_ast.Name'>
<class 'myhdl._Signal._Signal'> <class '_ast.Name'>
<class 'myhdl._Signal._Signal'> <class '_ast.Name'>
<class 'myhdl._Signal._Signal'> <class '_ast.Name'>
<class 'myhdl._Signal._Signal'> <class '_ast.Name'>
<class 'myhdl._Signal._Signal'> <class '_ast.Name'>
0,0,0
  1. Following is the complete working code
# mod_m_counter.py

from myhdl import *

period = 20 # clk frequency = 50 MHz

@block
def mod_m_counter(clk, reset_n, complete_tick, count, M, N):
    """ M = max count
        N = minimum bits required to represent M
    """

    count_reg = Signal(intbv(0)[N:0])
    count_next = Signal(intbv(0)[N:0])

    @always(clk.posedge, reset_n.negedge)
    def logic_reg():
        if reset_n == 0 :
            count_reg.next = 0
        else :
            count_reg.next = count_next

    @always_comb
    def logic_next():
        if count_reg == M-1 :
            count_next.next = 0
        else :
            count_next.next = count_reg + 1


    # optional complete_tick
    if complete_tick is not None:
        @always_comb
        def complete_tick_logic():
            if count_reg == M-1 :
                complete_tick.next = 1
            else :
                complete_tick.next = 0

    # optional count
    if count is not None:
        @always_comb
        def out_val():
            count.next = count_reg

    return instances()  # return all instances


# testbench
@block
def mod_m_counter_tb():
    N = 3
    M = 5

    # 'intbv' is used instead of 'bool' as 'false' will be displayed
    # in the saved-data for 'bool'.
    # clk = Signal(bool(0))
    clk = Signal(intbv(0)[1:0])
    reset_n = Signal(intbv(0)[1:0])
    complete_tick = Signal(intbv(0)[1:0])
    count = Signal(intbv(0)[N:0])

    mod_m_counter_inst = mod_m_counter(clk, reset_n, complete_tick, count, M, N)


    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(period//2)


    @instance  # reset signal
    def reset_signal():
        reset_n.next = 0
        yield delay(period)
        reset_n.next = 1


    # print simulation data on screen and file
    file_data = open("mod_m_counter.csv", 'w') # file for saving data
    # # print header on screen
    print("{0},{1},{2}".format("reset_n", "complete_tick", "count"))
    # # print header to file
    print("{0},{1},{2}".format("reset_n", "complete_tick", "count"),
               file=file_data)
    # print data on each clock
    @always(clk.posedge)
    def print_data():
        # print on screen
        # print.format is not supported in MyHDL 1.0
        # print("{0}, {1}, {2}".format(reset_n, complete_tick, count))
        print(reset_n, ",", complete_tick, ",",  count, sep='')

        # print in file
        # print.format is not supported in MyHDL 1.0
        # print("{0}, {1}, {2}".format(reset_n, complete_tick, count)
                # , file=file_data)
        print(reset_n, ",", complete_tick, ",",  count, sep='', file=file_data)


    return instances()


def main():
    N = 3
    M = 5

    clk = Signal(bool(0))
    reset_n = Signal(bool(0))
    complete_tick = Signal(bool(0))
    count = Signal(intbv(0)[N:0])

    mod_m_counter_v = mod_m_counter(clk, reset_n, complete_tick, count, M, N)
    mod_m_counter_v.convert(hdl="Verilog", initial_values=True)

    # test bench
    tb = mod_m_counter_tb()
    tb.convert(hdl="Verilog", initial_values=True)
    # keep following lines below the 'tb.convert' line
    # otherwise error will be reported
    tb.config_sim(trace=True)
    tb.run_sim(15*period) # run for 15 clock cycle


if __name__ == '__main__':
    main()
  1. In the resultant Verilog code, the data will be printed on the screen (but will not be saved in the file)

When I think conversion, I think synthesis.

Now you are reminded that they are not the same :wink:

Regards,
Josy

1 Like

Many thanks to you Josy :slight_smile:

Best regards,
Nicolas

For conversion, we need to put ‘tb.convert’ above the “tb.config_sim”, otherwise it generates error. Why is it so?