amaranth icon indicating copy to clipboard operation
amaranth copied to clipboard

`io.SimulationPort` invites undetected driver-driver conflicts that nondeterministically break semantics

Open whitequark opened this issue 1 year ago • 0 comments

Repro:

from amaranth import *
from amaranth.lib import io
from amaranth.sim import Simulator


port = io.SimulationPort("io", 2)

m = Module()
m.submodules.p0 = p0 = io.FFBuffer("io", port[0])
m.submodules.p1 = p1 = io.FFBuffer("io", port[1])
m.d.comb += p0.o.eq(1)
m.d.comb += p1.o.eq(1)

async def testbench(ctx):
    await ctx.tick()
    print(f"{ctx.get(port.o):02b}")

sim = Simulator(m)
sim.add_clock(1e-6)
sim.add_testbench(testbench)
sim.run()

This should always output 11, but outputs either 01 or 10.

The cause is a driver-driver conflict that isn't detected by pysim (since nothing in pysim can do that). Running verilog.convert(m, ports=[]) on it detects the conflict.

Although it looks like it would be a valid workaround (there is no driver-driver conflict both according to language semantics and according to verilog.convert()), this still fails with the same nondeterministic output:

port_0 = io.SimulationPort("io", 1)
port_1 = io.SimulationPort("io", 1)
port = port_0 + port_1

m = Module()
m.submodules.p0 = p0 = io.FFBuffer("io", port[0])
m.submodules.p1 = p1 = io.FFBuffer("io", port[1])
m.d.comb += p0.o.eq(1)
m.d.comb += p1.o.eq(1)

This does produce the expected 11 output:

port_0 = io.SimulationPort("io", 1)
port_1 = io.SimulationPort("io", 1)
port = port_0 + port_1

m = Module()
m.submodules.p0 = p0 = io.FFBuffer("io", port_0)
m.submodules.p1 = p1 = io.FFBuffer("io", port_1)
m.d.comb += p0.o.eq(1)
m.d.comb += p1.o.eq(1)

whitequark avatar Jul 20 '24 01:07 whitequark