VHDL-open equivalent in MyHDL (solved)


#1

How can I keep the output port unconnected?

For example, in the below code, the “clock_tick.py” instantiate the “mod_m_counter.py”; and the output port ‘count’ is not used. Therefore a warning is generated for this “Signal is driven but not read: count”

http://fpga-designs-with-myhdl.readthedocs.io/en/latest/myhdl/vvd.html#clock-ticks

I found this related to this problem,


#2

Create a dummy signal and connect it to the unused signal.
You’ll get a warning.


#3

That’s what the OP has,count is declared but not read and subsequently one gets the warning he wants to get rid off.

Issue 126: I have since then adapted my MyHDL code to accept a SuppressWarning for a Signal. At the end of the conversion I post-edit the VHDL file and remove all lines for the suppressed Signals.

Better is to not declare unused signals. I routinely use the ‘Open’ to tell the callee that I don’t need the output. Of course this has to be handled by the called module. In case of the OP’s example the module mod_m_counter needs adapting to handle this.
I switched to Open as in VHDL as I use None to specify that I want my class-based code to declare the signal internally.


#4

Thank you

Following changes allow the ‘open connection’ and remove the warning,

  1. I added one line, “if count is not None:” in mod_m_counter.py.
    # mod_m_counter.py

    if count is not None:
        @always_comb
        def out_val():
  1. Then add ‘None’ for the open signal in the clock_tick.py as below,
def clock_tick(clk, reset_n, clk_pulse, M, N):
    # count = Signal(intbv(0)[N:0])
    mod_m_counter_inst = mod_m_counter(clk, reset_n, clk_pulse, None, M, N)
    # mod_m_counter_inst = mod_m_counter(clk, reset_n, clk_pulse, count, M, N)
    return mod_m_counter_inst
  1. Lastly, I learned "return instances()’ from the OP :slight_smile: .

  2. The modified complete-code is added below,
    http://fpga-designs-with-myhdl.readthedocs.io/en/latest/myhdl/vvd.html#open-port-in-myhdl


#5

What do you mean by “I switched to Open as in VHDL” ?


#6

I code my MyHDL modules as classes and use None to indicate that the __init__() must allocate the Signal for the Port itself. ``‘Open’``` indicates that I am not going to use that specific output port so I will take care that no code is generated keeping the warnings away both here in MyHDL as later when compiling the generated code with the Vendor’s tools.
I made a gist a couple of years back: https://gist.github.com/josyb/afd84c9a06fdec77f2fd
I don’t have small true example ready but this is a counter I use all the time:

class UpCounter(object):
    ''' a 'generic' upcounter class '''

    def __init__(self, RANGE_COUNT,
                 Clk, Reset,
                 SClr=None, CntEn=None, Max=None,
                 Q=None, IsMaxMinusOne=None, IsMax=None,
                 WRAP_AROUND=False,
                 IsZero=None
                 ):
        ''' RANGE_COUNT
            Clk, Reset: self-explanatory
            SClr: resets the counter to zero, overrides CntEn
            CntEn: (possibly) advances count by one
            Max: value at which either the counter tops out or wraps around,
                 can be either a Signal or an integer number
                 Not that Max must be kept valid, and may only change during SClr active
            Q: the actual count output
            IsMaxMinusOne, IsMax: self-explanatory
            WRAP_AROUND: self-explanatory
        '''
        self.range_count = RANGE_COUNT
        self.wrap_around = WRAP_AROUND
        self.width_counter = hdlutils.widthu(self.range_count)
        self.clk = Clk
        self.reset = Reset
        self.SClr = SClr if SClr is not None else Signal(bool(0))
        self.CntEn = CntEn if CntEn is not None else Signal(bool(1))
        if Max is None:
            self.Max = RANGE_COUNT - 1
        else:
            self.Max = Max
        if isinstance(self.Max, int):
            assert self.Max > 0, 'Not much use counting to 1'

        self.Q = Q if Q is not None else Signal(intbv(0)[self.width_counter:])
        self.IsMaxMinusOne = IsMaxMinusOne if IsMaxMinusOne is not None else Signal(bool(0))
        self.IsMax = IsMax if IsMax is not None else Signal(bool(0))
        self.IsZero = IsZero if IsZero is not None else Signal(bool(0))

    def rtl(self):
        ''' the upcounter rtl '''
        # local Signals
        counter = Signal(intbv(0)[self.width_counter:])
        limmo = Signal(bool(self.range_count == 2))
        lim = Signal(bool(0))
        liz = Signal(bool(1))
        if isinstance(self.Max, int):
            if self.Max == 1:
                # actually counting 2 steps
                # 0: 1 1 0
                # 1: 0 0 1
                if self.wrap_around:
                    @always_seq(self.clk.posedge, reset=self.reset)
                    def upcounterwr():
                        ''' UpCounter: wrap around '''
                        if self.SClr or self.CntEn:
                            if self.SClr:
                                counter.next = 0
                                liz.next = 1
                                limmo.next = 1
                                lim.next = 0

                            else:
                                # CntEn
                                if lim:
                                    counter.next = 0
                                    liz.next = 1
                                    lim.next = 0
                                    limmo.next = 1
                                else:
                                    counter.next = 1
                                    liz.next = 0
                                    limmo.next = 0
                                    lim.next = 1

                else:
                    @always_seq(self.clk.posedge, reset=self.reset)
                    def upcounteros():
                        ''' UpCounter: one-shot '''
                        if self.SClr or self.CntEn:
                            if self.SClr:
                                counter.next = 0
                                limmo.next = 1
                                lim.next = 0
                                liz.next = 1
                            else:
                                counter.next = 1
                                liz.next = 0
                                limmo.next = 0
                                lim.next = 1

            elif self.IsMax == 2:
                # actually counting 3 steps
                # 0: 1 0 0
                # 1: 0 1 0
                # 2: 0 0 1
                if self.wrap_around:
                    @always_seq(self.clk.posedge, reset=self.reset)
                    def upcounterwr():
                        ''' UpCounter: wrap around '''
                        if self.SClr or self.CntEn:
                            if self.SClr:
                                counter.next = 0
                                liz.next = 1
                                limmo.next = 0
                                lim.next = 0

                            else:
                                # CntEn
                                if lim:
                                    # wrap around
                                    counter.next = 0
                                    liz.next = 1
                                    lim.next = 0
                                    limmo.next = 0

                                else:
                                    liz.next = 0
                                    counter.next = counter + 1
                                    if limmo:
                                        limmo.next = 0
                                        lim.next = 1
                                    else:
                                        limmo.next = 1

                else:
                    @always_seq(self.clk.posedge, reset=self.reset)
                    def upcounteros():
                        ''' UpCounter: one-shot '''
                        if self.SClr or self.CntEn:
                            if self.SClr:
                                counter.next = 0
                                limmo.next = 0
                                lim.next = 0
                                liz.next = 1
                            else:
                                liz.next = 0
                                if not lim:
                                    counter.next = counter + 1
                                    if limmo:
                                        limmo.next = 0
                                        lim.next = 1
                                    else:
                                        limmo.next = 1
            else:
                # actually counting N steps with N >= 4
                # 0: 1 0 0
                # 1: 0 0 0
                # ...
                # N - 2: 0 1 0
                # N - 1: 0 0 1
                if self.wrap_around:
                    @always_seq(self.clk.posedge, reset=self.reset)
                    def upcounterwr():
                        ''' UpCounter: wrap around '''
                        if self.SClr or self.CntEn:
                            if self.SClr:
                                counter.next = 0
                                liz.next = 1
                                limmo.next = 0
                                lim.next = 0

                            else:
                                # CntEn
                                if lim:
                                    # wrap around
                                    counter.next = 0
                                    liz.next = 1
                                    lim.next = 0
                                    limmo.next = 0

                                else:
                                    liz.next = 0
                                    counter.next = counter + 1
                                    if limmo:
                                        limmo.next = 0
                                        lim.next = 1
                                    else:
                                        if counter == (self.Max - 2):
                                            limmo.next = 1

                else:
                    @always_seq(self.clk.posedge, reset=self.reset)
                    def upcounteros():
                        ''' UpCounter: one-shot '''
                        if self.SClr or self.CntEn:
                            if self.SClr:
                                counter.next = 0
                                limmo.next = 0
                                lim.next = 0
                                liz.next = 1
                            else:
                                liz.next = 0
                                if not lim:
                                    counter.next = counter + 1
                                    if limmo:
                                        limmo.next = 0
                                        lim.next = 1
                                    else:
                                        if counter == self.Max - 2:
                                            limmo.next = 1
        else:
            # self.Max is a dynamic Signal
            if self.wrap_around:
                @always_seq(self.clk.posedge, reset=self.reset)
                def upcounterwr():
                    ''' UpCounter: wrap around  '''
                    if self.SClr or self.CntEn:
                        if self.SClr:
                            counter.next = 0
                            liz.next = 1
                            limmo.next = 0 if self.Max > 1 else 1
                            lim.next = 0 if self.Max > 0 else 1

                        else:
                            # CntEn
                            if liz:
                                if self.Max > 0:
                                    counter.next = 1
                                    liz.next = 0
                                    if self.Max == 1:
                                        limmo.next = 0
                                        lim.next = 1
                                    else:
                                        if self.Max == 2:
                                            limmo.next = 1

                            elif lim:
                                counter.next = 0
                                liz.next = 1
                                if self.Max > 0:
                                    lim.next = 0
                                else:
                                    lim.next = 1
                                if self.Max > 1:
                                    limmo.next = 0
                                else:
                                    limmo.next = 1

                            else:
                                counter.next = counter + 1
                                if limmo:
                                    limmo.next = 0
                                    lim.next = 1
                                elif self.Max > 1 and counter == (self.Max - 2):
                                    limmo.next = 1

            else:
                @always_seq(self.clk.posedge, reset=self.reset)
                def upcounteros():
                    ''' UpCounter: one-shot '''
                    if self.SClr or self.CntEn:
                        if self.SClr:
                            counter.next = 0
                            limmo.next = 0 if self.Max > 1 else 1
                            lim.next = 0 if self.Max > 0 else 1
                            liz.next = 1
                        else:
                            liz.next = 0
                            if not lim:
                                counter.next = counter + 1
                                if counter == self.Max - 2:
                                    limmo.next = 1
                                if limmo:
                                    limmo.next = 0
                                    lim.next = 1

        if self.Q != 'Open':
            @always_comb
            def assignQ():
                self.Q.next = counter

        if self.IsMaxMinusOne != 'Open':
            @always_comb
            def assignimmo():
                self.IsMaxMinusOne.next = limmo
        else:
            limmo.suppressWarning()

        if self.IsMax != 'Open':
            @always_comb
            def assignim():
                self.IsMax.next = lim

        if self.IsZero != 'Open':
            @always_comb
            def assigniz():
                self.IsZero.next = liz
        else:
            liz.suppressWarning()

        return instances()

The advantage of this coding style is that you can build a structural design with little lines of codes with few (even none) cluttering glue Signals. E.g:

class UART(object):
    '''
        a simple Universal Asynchronous Receiver Transmitter
        here we implement the minimal Transmit/Receive functionality
    '''

    def __init__(self, SYSFREQ_in_Hz,
                 BAUDRATE_in_Bd,
                 Clk, Reset,
                 Sink=None, Source=None,
                 TxD=None, RxD=None,
                 NBROFSTOPBITS=1, PARITY=None,
                 ):

        self.SYSFREQ_in_Hz = SYSFREQ_in_Hz
        self.BAUDRATE_in_Bd = BAUDRATE_in_Bd
        self.NBROFSTOPBITS = NBROFSTOPBITS
        assert PARITY in (None, 'Mark', 'Space', 'Odd', 'Even')
        self.PARITY = PARITY
        self.Clk = Clk
        self.Reset = Reset
        self.Sink = Sink if Sink is not None else SinkSource(8)
        self.Source = Source if Source is not None else SinkSource(8)
        self.TxD = TxD if TxD is not None else Signal(bool(0))
        self.RxD = RxD if RxD is not None else Signal(bool(0))

    def rtl(self):
        ''' the logic '''

        tx = UartTx(self.SYSFREQ_in_Hz, self.BAUDRATE_in_Bd, self.Clk, self.Reset,
                    self.Sink,
                    self.TxD,
                    NBROFSTOPBITS=self.NBROFSTOPBITS, PARITY=self.PARITY
                    )
        txrtl = tx.rtl()

        metarx = Meta(self.Clk, self.RxD)
        metarxrtl = metarx.rtl()

        rx = UartRx(self.SYSFREQ_in_Hz, self.BAUDRATE_in_Bd, self.Clk, self.Reset,
                    metarx.SigOut, self.Source, NBROFSTOPBITS=self.NBROFSTOPBITS, PARITY=self.PARITY)
        rxrtl = rx.rtl()

        return txrtl, metarxrtl, rxrtl

#7

Josy,

That’s interesting. However, I think we should not have to care about opened signals in the source code.

Unrelated to the open problem : On my side, I use class parameters for component configuration and rtl() parameters for component signals. This has the advantage of clearly identifying parameters and signals.


#8

Please explain; instantiated Signals that are driven but not read (==opened) will generate a warning everywhere, and some of us like to get rid of these (unnecessary) warnings.

Perhaps a (if possible small) example?

Regards,
Josy


#9

What I mean is that MyHDL should get rid of the opened signal automatically by using a special syntax at module instantiation (Open() for example).
For example :

    comp = MyComp()
    comp_inst = comp.rtl(Clk, DataIn, DataOut, Open(), LastData)

MyComp source code should not take care of unconnected signals.

Sure (not so small) :

from myhdl import block, Signal, always_seq, always_comb, posedge

EDGE_TYPE_RISING = 0
EDGE_TYPE_FALLING = 1
EDGE_TYPE_ALL = 2


class EdgeDetect():
    def __init__(self, edge_type=EDGE_TYPE_RISING, resync=False):
        self.edge_type = edge_type
        self.resync = resync

    @block
    def hdl(self,
            i_clk,
            i_data,
            o_edge):
    
        #if self.resync :
        data_sync = Signal(i_data.val)
        data_memo = Signal(i_data.val)
        edge = Signal(bool(0))
        
        @always_seq(posedge(i_clk), reset=None)
        def proc_in():
            if self.resync :
                data_sync.next = i_data
                data_memo.next = data_sync
            else :
                data_memo.next = i_data
                
        @always_seq(posedge(i_clk), reset=None)
        def proc_edge():
            edge.next = False
            if (self.resync and (data_memo != data_sync)) or (not self.resync and (data_memo != i_data)):
                if self.edge_type == EDGE_TYPE_ALL :
                    edge.next = True
                if self.edge_type == EDGE_TYPE_RISING :
                    if not data_memo :
                        edge.next = True
                if self.edge_type == EDGE_TYPE_FALLING :
                    if data_memo :
                        edge.next = True
                
        @always_comb
        def logic_out():
            o_edge.next = edge
                
        return proc_in, proc_edge, logic_out

#10

Yes, that would be nice. I thought about that at the time, but probably was in a hurry, but even worse, I’m not that good a programmer, at least I didn’t see an easy solution. Perhaps one of these days with MyHDL 2.0 we can work this out? Or have you already?

I understand your class approach. IMHO it doesn’t bring as much as mine as in a large module you will have to declare many glue variables to connect all sub-modules where in my case these are delegated to the sub-modules itself. An example:

class DrumSpeed(object):
    ''' an important object in a TDI LineScan Camera '''
    
    def __init__(self, Clk, Reset, A, B, Axi):
        self.Clk  = Clk
        self.Reset = Reset
        self.A = A
        self.B = B
        self.Axi = Axi if Axi is not None else AXI4Lite()
        descriptor = (GroupDescriptor('Encoder', (RegDescriptor('Position', 'ReadOnly', Signal(intbv(0)[30:])),
                                                  RegDescriptor('Enable', 'WriteRead', Signal(bool(0))),
                                                  RegDescriptor('Direction', 'WriteRead', Signal(bool(0))),
                                                  )),
                      GroupDescriptor('Fifo', (RegDescriptor('Position', 'ReadOnlyAck', Signal(intbv(0)[30:]), Pulse=Signal(bool(0))),
                                               RegDescriptor('Full', 'ReadOnly', Signal(bool(0))),
                                               RegDescriptor('Empty', 'ReadOnly', Signal(bool(0))),
                                               )),
                      RegDescriptor('Rate', 'WriteRead', Signal(intbv(0)[30:])),
                     )
        self.csr = ControlStatus('DrumSpeed', descriptor, Clk, Reset)

    def rtl(self):
        ''' the logic '''
        sclr =  Signal(bool(0))

        metaa = Meta(self.Clk, self.A)
        metab = Meta(self.Clk, self.B)
        encoder = EncoderABZ(4, self.Clk, self.Reset, metaa.SigOut, metab.SigOut)
        muxud = GenericMux(None, None, [encoder.ClockWiseP, encoder.CounterClockWiseP], self.csr.Encoder.Direction)
        position = UpCounter(2**30, self.Clk, self.Reset, sclr, muxud.Result,
                             Q=self.csr.Encoder.Position,
                             IsZero='Open', IsMaxMinusOne='Open', IsMax='Open',
                             WRAP_AROUND=True)
        rate = UpCounter(2**30, self.Clk, self.Reset, sclr, CntEn=True, Max=self.csr.Rate,
                             Q='Open', IsZero='Open', IsMaxMinusOne='Open',
                             WRAP_AROUND=True)
        fifo = SingleClockFifo(128, self.Clk, self.Reset, sclr,
                               self.csr.Encoder.Position, rate.IsMax, 
                               self.csr.Fifo.PositionPulse, Q=self.csr.Fifo.Position,
                               WrFull=self.csr.Fifo.Full,
                               RdEmpty=self.csr.Fifo.Empty,
                               UsedW='Open')
        a2a = Axi4toAvalonMM(None, self.Clk, self.Reset,self.Axi, self.csr.Avalon)
        
        @always_comb
        def assign():
            ''' connecting a few things ... '''
            sclr.next = not self.csr.Encoder.Enable
            
        csrrtl = self.csr.rtl()
        metaartl = metaa.rtl()
        metabrtl = metab.rtl()
        encoderrtl = encoder.rtl()
        muxudrtl = muxud.rtl()
        postionrtl = position.rtl()
        ratertl = rate.rtl()
        fifortl = fifo.rtl()
        a2artl = a2a.rtl()

        return csrrtl, metaartl, metabrtl, encoderrtl, muxudrtl, postionrtl, ratertl, fifortl, a2artl, assign

I still had had to use one local Signal because not self.csr.Encoder.Enable doesn’t work as an argument.


#11

That would be great.
I’m afraid my Python knowledge is too low for this kind of modification.

I don’t get the point here. Why can’t you move ports in rtl method ?

Are Meta, GroupDescriptor, RegDescriptor and ControlStatus functions written by you ?


#12

Yes, one could do the ports in the rtl() method, but my main goal is to have the class modules declare their output ports (as much as possible) and possibly also some input ports and this is done in the __init__() call. PyLint will issue a warning if you add an attribute outside the __init__() call, see here: https://stackoverflow.com/questions/25158898/why-is-defining-an-object-variable-outside-of-init-frowned-upon

Yes, all these are written by me.

Regards,
Josy


#13

Got it.
Thanks.

Nicolas


#14

Nicolas,

There is one more reason: I wanted to automate the collection as done with instances(). This necessitates that the call to the rtl() method has no arguments. Alas I got stuck in deeper Python too :slight_smile:

Regards,

Josy


#15

I spent few hours on it last week.
I’ve ended up with a working solution. I now have 2 projects without warnings :slight_smile: .
I still have to work on it to cover more cases.
Everything is based on :

  • Minor modifications to _Signal class
  • Slight modifications to _toVHDL module
  • A new NotUsed() function.

NotUsed can be used in 2 ways :

  • Without argument directly in the port list of the instantiation of a module (like open in VHDL)
  • With a signal as argument. This allows to tell a signal of an interface is not used.

The current implementation comments out the signal definition and every line where the not used signal is modified. This way, you can easily check the effects of NotUsed().

What would be really cool is to strip all unused logic following the removal of unused ports. But this is another story.

Nicolas


#16

I have done about exactly that, except the NotUsed() approach, I have modified both _signal.py and _tovhdl.py to accommodate a SuppresWarning method. I post-edit the generated VHDL code and delete the lines for suppressed signals (ruecksichtlos). I have discussed this in issue #126
Can I see your NotUsed() code and an example?

Regards,
Josy


#17

I’ve read this issue many times hopping to find some good inspiration…

Sure. I just need some time to make more tests.