Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 2 commits
  • 7 files changed
  • 0 commit comments
  • 1 contributor
View
14 README
@@ -1,12 +1,16 @@
Migen (Milkymist Generator)
a Python toolbox for building complex digital hardware
-Migen aims at automating further the VLSI design process. It provides
-tools to build synchronous designs more productively, integrate
-system-on-chips, design dataflow systems, and more. Migen will become
-the foundation for the next-generation Milkymist SoC.
+Migen aims at automating further the VLSI design process. Migen makes it
+possible to apply modern software concepts such as object-oriented
+programming and metaprogramming to design hardware. This results in more
+elegant and easily maintained designs and reduces the incidence of human
+errors. Built on these principles, it also provides tools to build
+synchronous designs more productively, integrate system-on-chips, design
+dataflow systems, and more. Migen will become the foundation for the
+next-generation Milkymist SoC.
-See doc/migen.txt for a more complete description.
+See the doc/ folder for a more complete description.
Code repository:
https://github.com/milkymist/migen
View
57 doc/index.rst
@@ -35,7 +35,7 @@ Even though the Milkymist system-on-chip [mm]_ is technically successful, it suf
#. V*HDL support for composite types is very limited. Signals having a record type in VHDL are unidirectional, which makes them clumsy to use e.g. in bus interfaces. There is no record type support in Verilog, which means that a lot of copy-and-paste has to be done when forwarding grouped signals.
-#. V*HDL support for procedurally generated logic is extremely limited. The most advanced forms of procedural generation of synthesizable logic that V*HDL offers are CPP-style directives in Verilog, combinatorial functions, and generate statements. Nothing really fancy, and it shows. To give a few examples:
+#. V*HDL support for procedurally generated logic is extremely limited. The most advanced forms of procedural generation of synthesizable logic that V*HDL offers are CPP-style directives in Verilog, combinatorial functions, and ``generate`` statements. Nothing really fancy, and it shows. To give a few examples:
#. Building highly flexible bus interconnect is not possible. Even arbitrating any given number of bus masters for commonplace protocols such as Wishbone is difficult with the tools that V*HDL puts at our disposal.
#. Building a memory infrastructure (including bus interconnect, bridges and caches) that can automatically adapt itself at compile-time to any word size of the SDRAM is clumsy and tedious.
@@ -100,7 +100,7 @@ The properties of a signal object are:
* a boolean "variable". If true, the signal will behave like a VHDL variable, or a Verilog reg that uses blocking assignment. This parameter only has an effect when the signal's value is modified in a synchronous statement.
* the signal's reset value. It must be an integer, and defaults to 0. When the signal's value is modified with a synchronous statement, the reset value is the initialization value of the associated register. When the signal is assigned to in a conditional combinatorial statement (``If`` or ``Case``), the reset value is the value that the signal has when no condition that causes the signal to be driven is verified. This enforces the absence of latches in designs. If the signal is permanently driven using a combinatorial statement, the reset value has no effect.
-The sole purpose of the name property is to make the generated V*HDL code easier to understand and debug. From a purely functional point of view, it is perfectly OK to have several signals with the same name property. The back-end will generate a unique name for each object. If no name property is specified, Migen will analyze the code that created the signal object, and try to extract the variable or member name from there. For example, the following statements will create one or several signal named "bar": ::
+The sole purpose of the name property is to make the generated V*HDL code easier to understand and debug. From a purely functional point of view, it is perfectly OK to have several signals with the same name property. The back-end will generate a unique name for each object. If no name property is specified, Migen will analyze the code that created the signal object, and try to extract the variable or member name from there. For example, the following statements will create one or several signals named "bar": ::
bar = Signal()
self.bar = Signal()
@@ -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
View
49 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()
View
32 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()
View
10 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.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 @@ def get_fragment(self):
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 @@ def get_fragment(self):
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)
View
10 examples/memory_sim.py
@@ -7,11 +7,20 @@ def __init__(self):
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 @@ def get_fragment(self):
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()
View
44 examples/wb_initiator.py
@@ -7,14 +7,21 @@
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 @@ def __init__(self):
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 @@ def get_fragment(self):
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 (<TRead...>/<TWrite...>).
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 end_simulation(s):
sim.run()
main()
+
+# Output:
+# <TWrite adr:0x0 dat:0x0>
+# Wrote in 0 cycle(s)
+# <TWrite adr:0x1 dat:0x2>
+# Wrote in 0 cycle(s)
+# <TWrite adr:0x2 dat:0x4>
+# Wrote in 0 cycle(s)
+# <TWrite adr:0x3 dat:0x6>
+# Wrote in 1 cycle(s)
+# <TWrite adr:0x4 dat:0x8>
+# Wrote in 1 cycle(s)
+# <TWrite adr:0x5 dat:0xa>
+# Wrote in 2 cycle(s)
+# ...
+# <TRead adr:0x0 dat:0x4>
+# Read 4 in 2 cycle(s)
+# <TRead adr:0x1 dat:0x5>
+# Read 5 in 2 cycle(s)
+# <TRead adr:0x2 dat:0x6>
+# Read 6 in 1 cycle(s)
+# <TRead adr:0x3 dat:0x7>
+# Read 7 in 1 cycle(s)
+# ...

No commit comments for this range

Something went wrong with that request. Please try again.