Explicitly listing instances and instances() give different output (solved)

In the following code, if I use instances() in the testbench, everything works as expected. If I return the instances by listing them explicitly, fall and rise stay low no matter what I do. (I can even explicitly say rise.next = 1, and it will still be low.) I suppose this is a bug.

"""Edge detector for asynchronous signals"""

from myhdl import Signal, ResetSignal
from myhdl import intbv, concat
from myhdl import block, always_seq
from myhdl import instance, delay, StopSimulation, instances
from random import randrange

@block
def edge_detect(clk, reset, async_signal, rise, fall):
    """
    Ports:
        clk:               clock to which all logic is synchronized
        reset:             reset
        async_signal       asynchronous signal input
        rise               rising edge detected
        fall               falling edge detected
    """

    # History shift register
    history = intbv(0)[3:]

    @always_seq(clk.posedge, reset=reset)
    def logic():
        """Detect rising and falling edges in the asynchronous input signal."""
        rise.next = history[1] and not history[2]
        fall.next = history[2] and not history[1]
        history[:] = concat(history[2:], async_signal)

    return logic

# Auxiliary functions

def convert(hdl):
    """Convert the design to HDL"""
    clk = Signal(bool(0))
    reset = ResetSignal(0, active=1, async=True)
    async_signal, rise, fall = [Signal(bool(0)) for _ in range(3)]
    rtl = edge_detect(clk, reset, async_signal, rise, fall)
    rtl.convert(hdl)

@block
def edge_testbench():
    """Testbench for the edge detector logic"""
    clk = Signal(bool(0))
    reset = ResetSignal(0, active=1, async=True)
    async_signal, rise, fall = [Signal(bool(0)) for _ in range(3)]
    rtl = edge_detect(clk, reset, async_signal, rise, fall)
    (lambda *args: None)(rtl)  # Suppress warning

    @instance
    def clk_gen():
        """Generate bus clock"""
        while True:
            yield delay(10)  # ns, clk = 50 MHz
            clk.next = not clk

    @instance
    def stim():
        """Generate random asynchronous signal"""
        for _ in range(100):
            yield clk.negedge
            yield delay(100 + randrange(200))  # ns, clk = 50 MHz
            async_signal.next = not async_signal
        raise StopSimulation

    #return instances()
    return clk_gen, stim

if __name__ == "__main__":
    convert('VHDL')
    TB = edge_testbench()
    TB.config_sim(backend='myhdl', trace=True)
    TB.run_sim(duration=None)
    TB.quit_sim()

You just forgot to return the generators for the device under test, in your case rtl.

    #return instances()
    return rtl, clk_gen, stim

I don’t think the (lambda *args: None)(rtl) # Suppress warning is necessary? At least I don’t understand what it should do. If it was to turn off the warning remember this: “A warning is an error waiting to happen’”

One more thing: I feel that anti-meta-stability registers should not be reset, so it may be purer to split the history into 2 parts.

Ah, of course. Thanks for pointing it out. The (lambda *args: None)(rtl) is there to stop the IDE from nagging me about rtl not being used when using instances(). Obviously when I went to not using instances(), the nagging would have been helpful…

One more thing: I feel that anti-meta-stability registers should not be reset, so it may be purer to split the history into 2 parts.

Thanks for the suggestion!