From f6e76ae1985ba82141fba67f5a89ffed081753b7 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 10 Mar 2012 19:38:39 +0100 Subject: [PATCH] doc: more examples and comments --- doc/index.rst | 53 ++++++++++++++++++++-------------------- examples/basic2_sim.py | 49 +++++++++++++++++++++++++++++++++++++ examples/basic_sim.py | 32 +++++++++++++++--------- examples/fir.py | 10 +++++++- examples/memory_sim.py | 10 ++++++++ examples/wb_initiator.py | 44 ++++++++++++++++++++++++++++++++- 6 files changed, 159 insertions(+), 39 deletions(-) create mode 100644 examples/basic2_sim.py diff --git a/doc/index.rst b/doc/index.rst index cbd0bd59..7b2060fa 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -567,29 +567,30 @@ Migen comes with a ``migen.sim.generic.TopLevel`` object that implements the abo The main parameters of its constructor are the output VCD file (default: ``None``) and the levels of hierarchy that must be present in the VCD (default: 1). -Basic simulation example -======================== -:: - - from migen.fhdl.structure import * - from migen.sim.generic import Simulator - from migen.sim.icarus import Runner - - class Counter: - def __init__(self): - self.count = Signal(BV(4)) - - def do_simulation(self, s): - print("Count: " + str(s.rd(self.count))) - - def get_fragment(self): - sync = [self.count.eq(self.count + 1)] - sim = [self.do_simulation] - return Fragment(sync=sync, sim=sim) - - def main(): - dut = Counter() - sim = Simulator(dut.get_fragment(), Runner()) - sim.run(20) - - main() +Simulation examples +******************* + +Most basic +========== +.. include:: ../examples/basic_sim.py + :code: python + +A few more features +=================== +.. include:: ../examples/basic2_sim.py + :code: python + +Memory access +============= +.. include:: ../examples/memory_sim.py + :code: python + +A FIR filter +============ +.. include:: ../examples/fir.py + :code: python + +Wishbone +======== +.. include:: ../examples/wb_initiator.py + :code: python diff --git a/examples/basic2_sim.py b/examples/basic2_sim.py new file mode 100644 index 00000000..f7bb0d02 --- /dev/null +++ b/examples/basic2_sim.py @@ -0,0 +1,49 @@ +from migen.fhdl.structure import * +from migen.sim.generic import Simulator, TopLevel +from migen.sim.icarus import Runner + +# A slightly improved counter. +# Has a clock enable (CE) signal, counts on more bits +# and resets with a negative number. +class Counter: + def __init__(self): + self.ce = Signal() + # Demonstrate negative numbers and signals larger than 32 bits. + self.count = Signal(BV(37, True), reset=-5) + + def do_simulation(self, s): + # Only assert CE every second cycle. + # => each counter value is held for two cycles. + if s.cycle_counter % 2: + s.wr(self.ce, 0) # This is how you write to a signal. + else: + s.wr(self.ce, 1) + print("Cycle: " + str(s.cycle_counter) + " Count: " + \ + str(s.rd(self.count))) + # Set the "initialize" property on our simulation function. + # The simulator will call it during the reset cycle, + # with s.cycle_counter == -1. + do_simulation.initialize = True + + # Output is: + # Cycle: -1 Count: 0 + # Cycle: 0 Count: -5 + # Cycle: 1 Count: -5 + # Cycle: 2 Count: -4 + # Cycle: 3 Count: -4 + # Cycle: 4 Count: -3 + # ... + + def get_fragment(self): + sync = [If(self.ce, self.count.eq(self.count + 1))] + sim = [self.do_simulation] + return Fragment(sync=sync, sim=sim) + +def main(): + dut = Counter() + # Instantiating the generic top-level ourselves lets us + # specify a VCD output file. + sim = Simulator(dut.get_fragment(), Runner(), TopLevel("my.vcd")) + sim.run(20) + +main() diff --git a/examples/basic_sim.py b/examples/basic_sim.py index 035440ca..900fcede 100644 --- a/examples/basic_sim.py +++ b/examples/basic_sim.py @@ -1,28 +1,38 @@ from migen.fhdl.structure import * -from migen.sim.generic import Simulator, TopLevel +from migen.sim.generic import Simulator from migen.sim.icarus import Runner +# Our simple counter, which increments at every cycle +# and prints its current value in simulation. class Counter: def __init__(self): - self.ce = Signal() - self.count = Signal(BV(37, True), reset=-5) + self.count = Signal(BV(4)) + # This function will be called at every cycle. def do_simulation(self, s): - if s.cycle_counter % 2: - s.wr(self.ce, 0) - else: - s.wr(self.ce, 1) - print("Cycle: " + str(s.cycle_counter) + " Count: " + str(s.rd(self.count))) - do_simulation.initialize = True + # Simply read the count signal and print it. + # The output is: + # Count: 0 + # Count: 1 + # Count: 2 + # ... + print("Count: " + str(s.rd(self.count))) def get_fragment(self): - sync = [If(self.ce, self.count.eq(self.count + 1))] + # At each cycle, increase the value of the count signal. + # We do it with convertible/synthesizable FHDL code. + sync = [self.count.eq(self.count + 1)] + # List our simulation function in the fragment. sim = [self.do_simulation] return Fragment(sync=sync, sim=sim) def main(): dut = Counter() - sim = Simulator(dut.get_fragment(), Runner(), TopLevel("my.vcd")) + # Use the Icarus Verilog runner. + # We do not specify a top-level object, and use the default. + sim = Simulator(dut.get_fragment(), Runner()) + # Since we do not use sim.interrupt, limit the simulation + # to some number of cycles. sim.run(20) main() diff --git a/examples/fir.py b/examples/fir.py index b5d366e8..3e6490a5 100644 --- a/examples/fir.py +++ b/examples/fir.py @@ -1,5 +1,5 @@ -from scipy import signal from math import cos, pi +from scipy import signal from migen.fhdl.structure import * from migen.fhdl import verilog @@ -8,6 +8,7 @@ from migen.fhdl import autofragment from migen.sim.generic import Simulator from migen.sim.icarus import Runner +# A synthesizable FIR filter. class FIR: def __init__(self, coef, wsize=16): self.coef = coef @@ -31,6 +32,8 @@ class FIR: comb = [self.o.eq(sum_full[self.wsize-1:])] return Fragment(comb, sync) +# A test bench for our FIR filter. +# Generates a sine wave at the input and records the output. class TB: def __init__(self, fir, frequency): self.fir = fir @@ -49,12 +52,17 @@ class TB: return Fragment(sim=[self.do_simulation]) def main(): + # Compute filter coefficients with SciPy. coef = signal.remez(80, [0, 0.1, 0.1, 0.5], [1, 0]) fir = FIR(coef) tb = TB(fir, 0.3) + # Combine the FIR filter with its test bench. fragment = autofragment.from_local() sim = Simulator(fragment, Runner()) sim.run(200) + # Print data from the input and output waveforms. + # When matplotlib works easily with Python 3, we could + # display them graphically here. print(tb.inputs) print(tb.outputs) diff --git a/examples/memory_sim.py b/examples/memory_sim.py index 571bad2e..765b0de8 100644 --- a/examples/memory_sim.py +++ b/examples/memory_sim.py @@ -7,11 +7,20 @@ class Mem: self.a = Signal(BV(12)) self.d = Signal(BV(16)) p = MemoryPort(self.a, self.d) + # Initialize the beginning of the memory with integers + # from 0 to 19. self.mem = Memory(16, 2**12, p, init=list(range(20))) def do_simulation(self, s): + # Read the memory. Use the cycle counter as address. value = s.rd(self.mem, s.cycle_counter) + # Print the result. Output is: + # 0 + # 1 + # 2 + # ... print(value) + # Demonstrate how to interrupt the simulator. if value == 10: s.interrupt = True @@ -21,6 +30,7 @@ class Mem: def main(): dut = Mem() sim = Simulator(dut.get_fragment(), Runner()) + # No need for a cycle limit here, we use sim.interrupt instead. sim.run() main() diff --git a/examples/wb_initiator.py b/examples/wb_initiator.py index 83df271c..04caecd4 100644 --- a/examples/wb_initiator.py +++ b/examples/wb_initiator.py @@ -7,14 +7,21 @@ from migen.bus import wishbone from migen.sim.generic import Simulator from migen.sim.icarus import Runner +# Our bus master. +# Python generators let us program bus transactions in an elegant sequential style. def my_generator(): prng = Random(92837) + + # Write to the first addresses. for x in range(10): t = TWrite(x, 2*x) yield t print("Wrote in " + str(t.latency) + " cycle(s)") + # Insert some dead cycles to simulate bus inactivity. for delay in range(prng.randrange(0, 3)): yield None + + # Read from the first addresses. for x in range(10): t = TRead(x) yield t @@ -22,6 +29,9 @@ def my_generator(): for delay in range(prng.randrange(0, 3)): yield None +# Our bus slave. +# All transactions complete with a random delay. +# Reads return address + 4. Writes are simply acknowledged. class MyPeripheral: def __init__(self): self.bus = wishbone.Interface() @@ -29,7 +39,7 @@ class MyPeripheral: self.prng = Random(763627) def do_simulation(self, s): - # Only authorize acks on certain cycles to simulate variable latency + # Only authorize acks on certain cycles to simulate variable latency. s.wr(self.ack_en, self.prng.randrange(0, 2)) def get_fragment(self): @@ -40,10 +50,18 @@ class MyPeripheral: return Fragment(comb, sim=[self.do_simulation]) def main(): + # The "wishbone.Initiator" library component runs our generator + # and manipulates the bus signals accordingly. master = wishbone.Initiator(my_generator()) + # Our slave. slave = MyPeripheral() + # The "wishbone.Tap" library component examines the bus at the slave port + # and displays the transactions on the console (/). tap = wishbone.Tap(slave.bus) + # Connect the master to the slave. intercon = wishbone.InterconnectPointToPoint(master.bus, slave.bus) + # A small extra simulation function to terminate the process when + # the initiator is done (i.e. our generator is exhausted). def end_simulation(s): s.interrupt = master.done fragment = autofragment.from_local() + Fragment(sim=[end_simulation]) @@ -51,3 +69,27 @@ def main(): sim.run() main() + +# Output: +# +# Wrote in 0 cycle(s) +# +# Wrote in 0 cycle(s) +# +# Wrote in 0 cycle(s) +# +# Wrote in 1 cycle(s) +# +# Wrote in 1 cycle(s) +# +# Wrote in 2 cycle(s) +# ... +# +# Read 4 in 2 cycle(s) +# +# Read 5 in 2 cycle(s) +# +# Read 6 in 1 cycle(s) +# +# Read 7 in 1 cycle(s) +# ... -- 2.30.2