Description
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?