Skip to content

Mocks of combinational circuits and add_process #1193

Closed
@lekcyjna123

Description

@lekcyjna123

Hi,
recently there was implemented RFC 27 and now the yield Settle() in add_process is deprecated. I would like to ask how in the new way of testing, mocks for external combinational circuits should be implemented. As I understand mocks are behavioural replacement so according to RFC 27 they should be implemented with add_process. But in case of combinational circuits there is a race. Here is an example:

from amaranth import *
from amaranth.sim import *


class DUT(Elaboratable):
    def __init__(self):
        self.in1 = Signal(8)
        self.in2 = Signal(8)
        self.out = Signal(8)

        # Signals used to communicate with external module, which
        # isn't a submodule of this Elaboratable.
        self._middle_in = Signal()
        self._middle_out = Signal(8)

    def elaborate(self, platform):
        m = Module()

        # Set input to the external module
        m.d.comb += self._middle_in.eq( (self.in1 + self.in2).any())

        # Process output from the external module in the same cycle
        m.d.sync += self.out.eq(self._middle_out)

        return m

def test_case():
    circ = DUT()

    def proc():
        yield Passive()
        while True:
            yield Settle() # without Settle test fails
            middle_in = yield circ._middle_in
            out = 0
            if middle_in:
                out = 7
            yield circ._middle_out.eq(out)
            yield Tick()

    def testbench():
        yield circ.in1.eq(3)
        yield circ.in2.eq(4)
        yield Tick()
        out = yield circ.out
        assert out == 7

    sim = Simulator(circ)
    sim.add_clock(1e-6)
    sim.add_process(proc)
    sim.add_testbench(testbench)
    sim.run()

The example is based on the fact that first inputs to external module has to be calculated and after that we can safely execute body of proc.

The yield Settle can be removed if proc will be changed to add_testbench, but according to amaranth-lang/rfcs#36 (comment) there are doubts if testbenches should be allowed to be passive. Whats more this doesn't scale up. If we add a second external module in the chain, then testbenches also stops to work:

from amaranth import *
from amaranth.sim import *


class DUT(Elaboratable):
    def __init__(self):
        self.in1 = Signal(8)
        self.in2 = Signal(8)
        self.out = Signal(8)

        # Signals used to communicate with external module, which
        # isn't a submodule of this Elaboratable.
        self._middle_in = Signal()
        self._middle_out = Signal(8)

        # Signals used to communicate with second external module, which
        # isn't a submodule of this Elaboratable.
        self._middle_in_2 = Signal()
        self._middle_out_2 = Signal(8)

    def elaborate(self, platform):
        m = Module()

        # Set input to the external module
        m.d.comb += self._middle_in.eq( (self.in1 + self.in2).any())

        # Set input to the second external module
        m.d.comb += self._middle_in_2.eq( self._middle_out.any() )

        # Process output from the external module in the same cycle
        m.d.sync += self.out.eq(self._middle_out + self._middle_out_2)

        return m

def test_case():
    circ = DUT()

    def proc():
        yield Passive()
        while True:
            yield Settle()
            middle_in = yield circ._middle_in
            out = 0
            if middle_in:
                out = 7
            yield circ._middle_out.eq(out)
            yield Tick()

    def proc2():
        yield Passive()
        while True:
            yield Settle()
            yield Settle() 
            middle_in = yield circ._middle_in_2
            out = 0
            if middle_in:
                out = 10
            yield circ._middle_out_2.eq(out)
            yield Tick()

    def testbench():
        yield circ.in1.eq(3)
        yield circ.in2.eq(4)
        yield Tick()
        out = yield circ.out
        assert out == 17

    sim = Simulator(circ)
    sim.add_clock(1e-6)
    # If both `add_process` below are changed to `add_testbench` the race will occure
    sim.add_process(proc)
    sim.add_process(proc2)
    sim.add_testbench(testbench)
    sim.run()

What is the current way of mocking external combinational stuff after RFC 27?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions