Skip to content

Commit

Permalink
doc: explain how simulation works.
Browse files Browse the repository at this point in the history
whitequark committed Aug 2, 2016
1 parent de2cbb6 commit 9f9b0d0
Showing 1 changed file with 67 additions and 1 deletion.
68 changes: 67 additions & 1 deletion doc/simulation.rst
Original file line number Diff line number Diff line change
@@ -3,4 +3,70 @@ Simulating a Migen design

Migen allows you to easily simulate your FHDL design and interface it with arbitrary Python code. The simulator is written in pure Python and interprets the FHDL structure directly without using an external Verilog simulator.

[To be rewritten]
Migen lets you write testbenches using Python's generator functions. Such testbenches execute concurrently with the FHDL simulator, and communicate with it using the ``yield`` statement. There are four basic patterns:

#. Reads: the state of the signal at the current time can be queried using ``(yield signal)``;
#. Writes: the state of the signal after the next clock cycle can be set using ``(yield signal.eq(value))``;
#. Clocking: simulation can be advanced by one clock cycle using ``yield``;
#. Composition: control can be transferred to another testbench function using ``yield from run_other()``.

A testbench can be run using the ``run_simulation`` function from ``migen.sim``; ``run_simulation(mod, bench)`` runs the generator function ``bench`` against the logic defined in an FHDL module ``mod``.

Examples
********

For example, consider this module::

class ORGate(Module):
def __init__(self):
self.a = Signal()
self.b = Signal()
self.x = Signal()

###

self.comb += self.x.eq(self.a | self.b)

It could be simulated together with the following testbench::

dut = ORGate()

def testbench():
yield dut.a.eq(0)
yield dut.b.eq(0)
yield
assert (yield dut.x) == 0

yield dut.a.eq(0)
yield dut.b.eq(1)
yield
assert (yield dut.x) == 1

run_simulation(dut, testbench())

This is, of course, quite verbose, and individual steps can be factored into a separate function::

dut = ORGate()

def check_case(a, b, x):
yield dut.a.eq(a)
yield dut.b.eq(b)
yield
assert (yield dut.x) == x

def testbench():
yield from check_case(0, 0, 0)
yield from check_case(0, 1, 1)
yield from check_case(1, 0, 1)
yield from check_case(1, 1, 1)

run_simulation(dut, testbench())

Pitfalls
********

There are, unfortunately, some basic mistakes that can produce very puzzling results.

When calling other testbenches, it is important to not forget the ``yield from``. If it is omitted, the call would silently do nothing.

When writing to a signal, it is important that nothing else should drive the signal concurrently. If that is not the case, the write would silently do nothing.

0 comments on commit 9f9b0d0

Please sign in to comment.