amaranth-lang / amaranth

A modern hardware definition language and toolchain based on Python
https://amaranth-lang.org/docs/amaranth/
BSD 2-Clause "Simplified" License
1.55k stars 170 forks source link

How to simulate with two clock domains? #236

Closed nmigen-issue-migration closed 4 years ago

nmigen-issue-migration commented 5 years ago

Issue by RobertBaruch Thursday Sep 26, 2019 at 02:12 GMT Originally opened as https://github.com/m-labs/nmigen/issues/236


What's the right pattern to use? Here is an example of what I've tried:

from nmigen import *
from nmigen.cli import main
from nmigen.asserts import *
from nmigen.back import pysim
from nmigen.hdl.ast import Tick

# module edgelord outputs the state of the clock without using
# the clock in combinatorial logic. This is a good thing in
# FPGAs, where the clock is a special signal that might get
# badly routed if it has to go through anything other than the
# clock inputs of flipflops.
#
# The reset signal MUST be held high for both edges,
# otherwise the clk_state will be inverted.
class Edgelord(Elaboratable):
    def __init__(self):
        self.clk_state = Signal()

    def elaborate(self, platform):
        pos = Signal()
        neg = Signal()
        rst = ResetSignal("pos")

        m = Module()

        # Verilog equivalent:
        #
        # assign clk_state = reset || !(pos ^ neg);
        m.d.comb += self.clk_state.eq(rst | ~(pos ^ neg))

        # Verilog equivalent:
        #
        # always @(posedge clk) begin
        #     if (reset) pos <= 0;
        #     else pos <= !(pos ^ clk_state);
        # end
        #
        # always @(negedge clk) begin
        #     if (reset) neg <= 0;
        #     else neg <= neg ^ clk_state;
        # end
        with m.If(rst):
            m.d.pos += pos.eq(0)
            m.d.neg += neg.eq(0)
        with m.Else():
            m.d.pos += pos.eq(~(pos ^ self.clk_state))
            m.d.neg += neg.eq(neg ^ self.clk_state)

        if platform == "formal":
            self.formal(m)

        return m

    def formal(self, m):
        cycle = Signal(8, reset_less=True)
        rst = ResetSignal("pos")
        clk = ClockSignal("pos")

        m.d.pos += cycle.eq(cycle + (cycle != 255))

        m.d.comb += Assume(rst == (cycle < 2))
        with m.If(rst == 0):
            m.d.comb += Assert(clk == self.clk_state)

if __name__ == "__main__":
    clk = Signal()
    rst = Signal()

    pos = ClockDomain()
    pos.clk = clk
    pos.rst = rst

    neg = ClockDomain(clk_edge="neg")
    neg.clk = clk
    neg.rst = rst

    edgelord = Edgelord()

    m = Module()
    m.domains.pos = pos
    m.domains.neg = neg
    m.submodules.edgelord = edgelord

    with pysim.Simulator(
            m,
            vcd_file=open("edgelord.vcd", "w"),
            gtkw_file=open("edgelord.gtkw", "w"),
            traces=[clk, rst, edgelord.clk_state]) as sim:
        sim.add_clock(1e-9, domain="pos")
        sim.add_clock(1e-9, domain="neg")

        #sim.add_clock(1e-9)

        def process():
            for i in range(0, 30):
                yield Tick(domain="pos")
                yield Tick(domain="neg")

        #sim.add_sync_process(process(), domain="pos")
        sim.add_process(process())
        sim.run()

    #main(m, ports=[clk, rst, edgelord.clk_state], platform="formal")

This resulted in:

Traceback (most recent call last):
  File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/mnt/f/mz80/mz80/core/edgelord.py", line 104, in <module>
    sim.run()
  File "/home/robertbaruch/.local/lib/python3.6/site-packages/nmigen/back/pysim.py", line 835, in run
    while self.step():
  File "/home/robertbaruch/.local/lib/python3.6/site-packages/nmigen/back/pysim.py", line 800, in step
    raise DeadlineError("Delta cycles exceeded process deadline; combinatorial loop?")
nmigen.back.pysim.DeadlineError: Delta cycles exceeded process deadline; combinatorial loop?
nmigen-issue-migration commented 5 years ago

Comment by whitequark Thursday Sep 26, 2019 at 02:16 GMT


This is currently broken for reasons that are not entirely clear. See #28. I plan to rewrite the simulator entirely, since it has a number of serious flaws that require changing core decisions, but this will take some time.

A possible workaround is to use EnableInserter to sequence the pos and neg actions, and then DomainRenamer to map them both to a single sync domain.

Another possible workaround is to use https://github.com/andresdemski/nmigen-cocotb. (I have not personally used it.) I will also take a good look at cocotb's user interface (see #228), so if later you want to get rid of cocotb in favor of Python-only simulation, it is likely that the migration would not be very painful.

nmigen-issue-migration commented 4 years ago

Comment by whitequark Saturday Oct 12, 2019 at 23:32 GMT


@RobertBaruch Have you been able to use one of these workarounds?

nmigen-issue-migration commented 4 years ago

Comment by RobertBaruch Sunday Oct 13, 2019 at 13:52 GMT


I haven't looked at it yet. I can try nmigen-cocotb.

nmigen-issue-migration commented 4 years ago

Comment by RobertBaruch Sunday Oct 13, 2019 at 16:15 GMT


Tried nmigen-cocotb, but apparently nothing is output. I get a sim_build directory with a vvp file. I did run vvp on that vvp file and got an output.vcd, which contained no signal changes. Maybe there's some documentation out of date -- the test runner doesn't seem to run the function annotated with @cocotb.test().

from nmigen import *
# from nmigen.cli import main
from nmigen.asserts import *
# from nmigen.back import pysim
# from nmigen.hdl.ast import Tick

# module edgelord outputs the state of the clock without using
# the clock in combinatorial logic. This is a good thing in
# FPGAs, where the clock is a special signal that might get
# badly routed if it has to go through anything other than the
# clock inputs of flipflops.
#
# The reset signal MUST be held high for both edges,
# otherwise the clk_state will be inverted.
class Edgelord(Elaboratable):
    def __init__(self):
        self.clk_state = Signal()
        self.unfunf = Signal()

    def elaborate(self, platform):
        pos = Signal()
        neg = Signal()
        rst = ResetSignal("pos")

        m = Module()

        # Verilog equivalent:
        #
        # assign clk_state = reset || !(pos ^ neg);
        m.d.comb += self.clk_state.eq(rst | ~(pos ^ neg))

        # Verilog equivalent:
        #
        # always @(posedge clk) begin
        #     if (reset) pos <= 0;
        #     else pos <= !(pos ^ clk_state);
        # end
        #
        # always @(negedge clk) begin
        #     if (reset) neg <= 0;
        #     else neg <= neg ^ clk_state;
        # end
        m.d.neg += self.unfunf.eq(~self.unfunf)
        with m.If(rst):
            m.d.pos += pos.eq(0)
            m.d.neg += neg.eq(0)
        with m.Else():
            m.d.pos += pos.eq(~(pos ^ self.clk_state))
            m.d.neg += neg.eq(neg ^ self.clk_state)

        return m

from nmigen_cocotb import run, get_current_module
import cocotb
from cocotb.triggers import Timer

def tick(dut):
    print("tick\n")
    dut.clk <= 0
    yield Timer(10, 'ns')
    dut.clk <= 1
    yield Timer(10, 'ns')

@cocotb.test()
def reset_test(dut):
    print("start reset_test\n")
    dut._log.info("Running test!")
    dut.rst <= 1
    tick(dut)
    tick(dut)
    dut.rst <= 0
    tick(dut)
    tick(dut)
    tick(dut)
    dut._log.info("Test complete!")

def test_module():
    clk = Signal()
    rst = Signal()

    pos = ClockDomain()
    pos.clk = clk
    pos.rst = rst

    neg = ClockDomain(clk_edge="neg")
    neg.clk = clk
    neg.rst = rst

    edgelord = Edgelord()

    m = Module()
    m.domains.pos = pos
    m.domains.neg = neg
    m.submodules.edgelord = edgelord

    print("Start run\n")
    run(m, get_current_module(), ports=[clk, rst, edgelord.clk_state], vcd_file='output.vcd')

if __name__ == "__main__":
    print("Running\n")
    test_module()
$ python3 edgelord.py cocotb -m test -v output.vcd

Running

Start run

top [(sig clk), (sig rst), (sig clk_state)]
iverilog -o /mnt/c/Users/rober/Documents/mz80/mz80/core/sim_build/top.vvp -D COCOTB_SIM=1 -s top -g2012 -s cocotb_waveform_module /tmp/tmpeot_rz96/nmigen_output.v
vvp -M /home/robertbaruch/.local/lib/python3.6/site-packages/cocotb_test/libs/icarus -m libvpi /mnt/c/Users/rober/Documents/mz80/mz80/core/sim_build/top.vvp
     -.--ns INFO     cocotb.gpi                         ../embed/gpi_embed.c:111  in embed_init_python               Did not detect Python virtual environment. Using system-wide Python interpreter.
     -.--ns INFO     cocotb.gpi                         ../gpi/GpiCommon.cpp:91   in gpi_print_registered_impl       VPI registered
     0.00ns INFO     cocotb                                      __init__.py:131  in _initialise_testbench           Running tests with Cocotb v1.2.0 from Unknown
     0.00ns INFO     cocotb                                      __init__.py:148  in _initialise_testbench           Seeding Python random module with 1570983084
     0.00ns INFO     cocotb.regression                         regression.py:210  in tear_down                       Passed 0 tests (0 skipped)
     0.00ns INFO     cocotb.regression                         regression.py:392  in _log_sim_summary                *************************************************************************************
                                                                                                                     **                                 ERRORS : 0                                      **
                                                                                                                     *************************************************************************************
                                                                                                                     **                               SIM TIME : 0.00 NS                                **
                                                                                                                     **                              REAL TIME : 0.00 S                                 **
                                                                                                                     **                        SIM / REAL TIME : 0.00 NS/S                              **
                                                                                                                     *************************************************************************************

     0.00ns INFO     cocotb.regression                         regression.py:219  in tear_down                       Shutting down...
$ vvp sim_build top.vvp

output.zip

nmigen-issue-migration commented 4 years ago

Comment by RobertBaruch Sunday Oct 13, 2019 at 16:20 GMT


Hmm, the coctb-nmigen example doesn't even output anything, so there must be some step missing.

nmigen-issue-migration commented 4 years ago

Comment by RobertBaruch Sunday Oct 13, 2019 at 16:43 GMT


Yeah, no.

$ vvp -M /home/robertbaruch/.local/lib/python3.6/site-packages/cocotb_test/libs/icarus -m libvpi /mnt/c/Users/rober/Documents/mz80/mz80/core/sim_build/top.vvp
     -.--ns INFO     cocotb.gpi                         ../embed/gpi_embed.c:111  in embed_init_python               Did not detect Python virtual environment. Using system-wide Python interpreter.
     -.--ns INFO     cocotb.gpi                         ../gpi/GpiCommon.cpp:91   in gpi_print_registered_impl       VPI registered
AttributeError: module 'cocotb' has no attribute 'loggpi'
Error in sys.excepthook:
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/apport_python_hook.py", line 63, in apport_excepthook
    from apport.fileutils import likely_packaged, get_recent_crashes
  File "/usr/lib/python3/dist-packages/apport/__init__.py", line 5, in <module>
    from apport.report import Report
  File "/usr/lib/python3/dist-packages/apport/report.py", line 30, in <module>
    import apport.fileutils
  File "/usr/lib/python3/dist-packages/apport/fileutils.py", line 23, in <module>
    from apport.packaging_impl import impl as packaging
  File "/usr/lib/python3/dist-packages/apport/packaging_impl.py", line 23, in <module>
    import apt
  File "/usr/lib/python3/dist-packages/apt/__init__.py", line 23, in <module>
    import apt_pkg
ModuleNotFoundError: No module named 'apt_pkg'

Original exception was:
AttributeError: module 'cocotb' has no attribute 'loggpi'
Failed to to get simlog object
Segmentation fault (core dumped)
nmigen-issue-migration commented 4 years ago

Comment by whitequark Sunday Oct 13, 2019 at 16:45 GMT


What about my other suggestion?

nmigen-issue-migration commented 4 years ago

Comment by RobertBaruch Sunday Oct 13, 2019 at 16:49 GMT


Working on that now!

nmigen-issue-migration commented 4 years ago

Comment by RobertBaruch Sunday Oct 13, 2019 at 17:05 GMT


Sorry, I'm going to need more explicit instructions on what to do with EnableInserter and DomainRenamer.

nmigen-issue-migration commented 4 years ago

Comment by whitequark Sunday Oct 13, 2019 at 17:09 GMT


Something like:

m = Module()
phase = Signal()
m.d.sync += phase.eq(~phase)
design = EnableInserter({"pos":~phase,"neg":phase})(design)
design = DomainRenamer({"pos":"sync","neg":"sync"})(design)
m.submodules += design
nmigen-issue-migration commented 4 years ago

Comment by RobertBaruch Sunday Oct 13, 2019 at 17:28 GMT


Yes, that seems to have worked, thanks!

nmigen-issue-migration commented 4 years ago

Comment by andresdemski Thursday Oct 17, 2019 at 16:12 GMT


Hi, get_current_module function is not working currently in nmigen-cocotb. Try to use it with harcoded module. Try this commit: https://github.com/andresdemski/nmigen-cocotb/tree/9e5d868587c859820147365a51d38f60e459cbb0