Hi,

here is how I would implement simulation in Migen.

The FHDL structure would simply be converted into Verilog and then simulated using an external program (Icarus Verilog, GPL Cver, Modelsim, ...). This makes everything much simpler, is intrinsically compatible with VHDL/Verilog instantiations from Migen, and avoids reinventing wheels (like performance optimizations...).

By default, the test bench top-level simply generates the clock (since delays cannot be expressed with FHDL) and instantiates the module containing all the converted FHDL.

The interesting part is how we'll interface this to Python. I would add a VPI task that is called at each clock cycle (e.g. always @(posedge clk) $my_task; - this code would of course be added by the converter) and implements the test bench functionality proper - which can be fully written in Python.

Signals inside the simulator can be read and written using VPI as well. This is how the Python test bench will generate stimulus and obtain the values of signals for processing.

To encapsulate non-synthesizable code, I propose a mechanism a bit similar to how "get_fragment" works. All objects containing simulation-only code will have a method "do_simulation" that gets called at each clock cycle (via the VPI task mechanism) and can read/write signals. Introspection (like in the autofragment module) allows objects with such a method to be listed easily in some practical cases.

Objects can have both synthesizable and non-synthesizable code. For example, here is roughly how one can implement a Wishbone slave that acks all requests in 1 cycle and dumps written data to the console:

class WriteDumper:
        def __init__(self):
                self.bus = wishbone.Interface()

        # Ack requests
        def get_fragment(self):
                sync = [
                        self.bus.ack.eq(0),
                        If(self.bus.stb & self.bus.cyc & ~self.bus.ack,
                                self.bus.ack.eq(0)
                        )
                ]
                return Fragment(sync=sync)

        # Print writes
        def do_simulation(self, s):
                if s.read(self.bus.stb) \
                  and s.read(self.bus.cyc) \
                  and not s.read(self.bus.ack) \
                  and s.read(self.bus.we):
                        print(self.bus.dat_w)

The "s" parameter of the do_simulation method is the simulation context. This object is needed to hold the state of the connection to the Verilog simulator (e.g. mapping between Python IDs of signal objects and corresponding VPI handles in the simulator).

There would also be a "s.write" method, defining the value of the signal for the next clock cycle.

As part of this effort, we need a Python/VPI binding. There already are similar works:
* http://tsheffler.com/site_media/software/apvm_025.pdf (not open source)
* http://embedded.eecs.berkeley.edu/Alumni/pinhong/scriptEDA/ (would be OK but needs review, also this dates from 2001 and bit rot probably has done some damage)
* http://www.veripool.org/wiki/verilog-pli (for Perl)

So I guess I'll implement a small binding, maybe based on the scriptEDA code, with the basic functions that I need to get the Verilog simulator to run a Python routine at each cycle and access signals from Python. Shouldn't be too hard.

Note that even though VPI is standardized, there are in practice some small discrepancies that make different simulators incompatible (for example, a VPI module designed and compiled for Icarus does not work out of the box for GPL CVer because of small annoying details). My work would focus on supporting Icarus Verilog.

Opinions/comments?

Sébastien
_______________________________________________
http://lists.milkymist.org/listinfo.cgi/devel-milkymist.org
IRC: #milkymist@Freenode

Reply via email to