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.

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:

  1. Reads: the state of the signal at the current time can be queried using (yield signal);
  2. Writes: the state of the signal after the next clock cycle can be set using (yield signal.eq(value));
  3. Clocking: simulation can be advanced by one clock cycle using yield;
  4. 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(dut, bench) runs the generator function bench against the logic defined in an FHDL module dut.

Passing the vcd_name="file.vcd" argument to run_simulation will cause it to write a VCD dump of the signals inside dut to file.vcd.


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)
  assert (yield dut.x) == 0

  yield dut.a.eq(0)
  yield dut.b.eq(1)
  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)
  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())


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.