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