In [1]:
import magma as m
In the last example, we defined a function that created a
toggle flip-flop (TFF) from a DFF and an XOR gate.
Let's convert the TFF to a Circuit.
In Magma a Circuit is equivalent to a verilog module.
Circuits can be instanced and then wired to other circuits.
m.ClockIO() appends Magma's standard clock interface ports to the interface. When no parameters are specified, this just adds the port CLK with type In(Clock).
In [2]:
from mantle import DFF
class TFF(m.Circuit):
io = m.IO(O=m.Out(m.Bit)) + m.ClockIO()
ff = DFF()
m.wire( ff(~ff.O), io.O )
Let's inspect the interface to see the result of appending m.ClockIO().
In [3]:
print(TFF)
Now we'll define a generator for our RippleCounter that accepts a single argument width. A generator in magma is a subclass of m.Generator that defines a static method generate which returns Magma Circuit.
In [4]:
class RippleCounter(m.Generator):
@staticmethod
def generate(width: int):
class _RippleCounter(m.Circuit):
name = f'Ripple{width}'
io = m.IO(O=m.Out(m.Bits[width])) + m.ClockIO()
tffs = [TFF(name=f"tff{i}") for i in range(width)]
O = io.CLK
for i in range(width):
m.wire(m.clock(O), tffs[i].CLK)
O = tffs[i].O
m.wire(O, io.O[i])
return _RippleCounter
Now we can generate a 4-bit RippleCounter by calling the generate function directly.
In [5]:
Ripple4 = RippleCounter.generate(4)
print(repr(Ripple4))
Let's test our circuit using fault. Magma's Python simulator does not support asynchronous logic, so we'll use verilator.
In [6]:
import fault
tester = fault.Tester(Ripple4, Ripple4.CLK)
for i in range(1 << 4):
tester.step(2)
tester.print("O=%x\n", Ripple4.O)
tester.compile_and_run(target="verilator", disp_type="realtime")
We can also look at the generated verilog
In [7]:
m.compile("build/ripple", Ripple4, inline=True)
In [8]:
%%bash
cat build/ripple.v
In [ ]: