Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to simulate with two clock domains? #236

Closed
RobertBaruch opened this issue Sep 26, 2019 · 12 comments
Closed

How to simulate with two clock domains? #236

RobertBaruch opened this issue Sep 26, 2019 · 12 comments
Labels

Comments

@RobertBaruch
Copy link

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?
@whitequark
Copy link
Contributor

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.

@whitequark
Copy link
Contributor

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

@RobertBaruch
Copy link
Author

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

@RobertBaruch
Copy link
Author

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

@RobertBaruch
Copy link
Author

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

@RobertBaruch
Copy link
Author

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)

@whitequark
Copy link
Contributor

What about my other suggestion?

@RobertBaruch
Copy link
Author

Working on that now!

@RobertBaruch
Copy link
Author

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

@whitequark
Copy link
Contributor

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

@RobertBaruch
Copy link
Author

Yes, that seems to have worked, thanks!

@ydnatag
Copy link

ydnatag commented Oct 17, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants