Ripple Counter from Toggle Flip-Flops

In this example we create a ripple counter from toggle flip-flops. We also show how to define new Magma Circuits and introduce generators.


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)


TFF(O: Out(Bit), CLK: In(Clock))

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))


Ripple4 = DefineCircuit("Ripple4", "O", Out(Bits[4]), "CLK", In(Clock))
tff0 = TFF(name="tff0")
tff1 = TFF(name="tff1")
tff2 = TFF(name="tff2")
tff3 = TFF(name="tff3")
wire(Ripple4.CLK, tff0.CLK)
wire(tff0.O, tff1.CLK)
wire(tff1.O, tff2.CLK)
wire(tff2.O, tff3.CLK)
wire(tff0.O, Ripple4.O[0])
wire(tff1.O, Ripple4.O[1])
wire(tff2.O, Ripple4.O[2])
wire(tff3.O, Ripple4.O[3])
EndCircuit()

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")


Running command: verilator -Wall -Wno-INCABSPATH -Wno-DECLFILENAME --cc Ripple4.v --exe Ripple4_driver.cpp --top-module Ripple4
Running command: verilator --version
<STDOUT>
Verilator 4.013 devel rev UNKNOWN_REV
</STDOUT>
Running command: make -C obj_dir -j -f VRipple4.mk VRipple4
<STDOUT>
clang++  -I.  -MMD -I/usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include -I/usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -faligned-new -fbracket-depth=4096 -Qunused-arguments -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-parameter -Wno-unused-variable -Wno-shadow       -c -o Ripple4_driver.o ../Ripple4_driver.cpp
clang++  -I.  -MMD -I/usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include -I/usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -faligned-new -fbracket-depth=4096 -Qunused-arguments -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-parameter -Wno-unused-variable -Wno-shadow       -c -o verilated.o /usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include/verilated.cpp
/usr/bin/perl /usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include VRipple4.cpp > VRipple4__ALLcls.cpp
/usr/bin/perl /usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include VRipple4__Syms.cpp > VRipple4__ALLsup.cpp
clang++  -I.  -MMD -I/usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include -I/usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -faligned-new -fbracket-depth=4096 -Qunused-arguments -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-parameter -Wno-unused-variable -Wno-shadow       -c -o VRipple4__ALLcls.o VRipple4__ALLcls.cpp
clang++  -I.  -MMD -I/usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include -I/usr/local/Cellar/verilator/HEAD-efa6f4c/share/verilator/include/vltstd -DVL_PRINTF=printf -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -faligned-new -fbracket-depth=4096 -Qunused-arguments -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-parameter -Wno-unused-variable -Wno-shadow       -c -o VRipple4__ALLsup.o VRipple4__ALLsup.cpp
      Archiving VRipple4__ALL.a ...
ar r VRipple4__ALL.a VRipple4__ALLcls.o VRipple4__ALLsup.o
ranlib VRipple4__ALL.a
clang++    Ripple4_driver.o verilated.o VRipple4__ALL.a    -o VRipple4 -lm -lstdc++
</STDOUT>
<STDERR>
ar: creating archive VRipple4__ALL.a
</STDERR>
Running command: ./obj_dir/VRipple4
<STDOUT>
O=f
O=e
O=d
O=c
O=b
O=a
O=9
O=8
O=7
O=6
O=5
O=4
O=3
O=2
O=1
O=0
</STDOUT>

We can also look at the generated verilog


In [7]:
m.compile("build/ripple", Ripple4, inline=True)

In [8]:
%%bash
cat build/ripple.v


module coreir_wrap (
    input in,
    output out
);
  assign out = in;
endmodule

module coreir_reg #(
    parameter width = 1,
    parameter clk_posedge = 1,
    parameter init = 1
) (
    input clk,
    input [width-1:0] in,
    output [width-1:0] out
);
  reg [width-1:0] outReg=init;
  wire real_clk;
  assign real_clk = clk_posedge ? clk : ~clk;
  always @(posedge real_clk) begin
    outReg <= in;
  end
  assign out = outReg;
endmodule

module DFF_init0_has_ceFalse_has_resetFalse_has_async_resetFalse (
    input I,
    output O,
    input CLK
);
wire [0:0] reg_P_inst0_out;
coreir_reg #(
    .clk_posedge(1'b1),
    .init(1'h0),
    .width(1)
) reg_P_inst0 (
    .clk(CLK),
    .in(I),
    .out(reg_P_inst0_out)
);
assign O = reg_P_inst0_out[0];
endmodule

module TFF (
    output O,
    input CLK
);
DFF_init0_has_ceFalse_has_resetFalse_has_async_resetFalse DFF_init0_has_ceFalse_has_resetFalse_has_async_resetFalse_inst0 (
    .I(~ O),
    .O(O),
    .CLK(CLK)
);
endmodule

module Ripple4 (
    output [3:0] O,
    input CLK
);
wire coreir_wrapOutClock_inst0_out;
wire coreir_wrapOutClock_inst1_out;
wire coreir_wrapOutClock_inst2_out;
wire tff0_O;
wire tff1_O;
wire tff2_O;
wire tff3_O;
coreir_wrap coreir_wrapOutClock_inst0 (
    .in(tff0_O),
    .out(coreir_wrapOutClock_inst0_out)
);
coreir_wrap coreir_wrapOutClock_inst1 (
    .in(tff1_O),
    .out(coreir_wrapOutClock_inst1_out)
);
coreir_wrap coreir_wrapOutClock_inst2 (
    .in(tff2_O),
    .out(coreir_wrapOutClock_inst2_out)
);
TFF tff0 (
    .O(tff0_O),
    .CLK(CLK)
);
TFF tff1 (
    .O(tff1_O),
    .CLK(coreir_wrapOutClock_inst0_out)
);
TFF tff2 (
    .O(tff2_O),
    .CLK(coreir_wrapOutClock_inst1_out)
);
TFF tff3 (
    .O(tff3_O),
    .CLK(coreir_wrapOutClock_inst2_out)
);
assign O = {tff3_O,tff2_O,tff1_O,tff0_O};
endmodule


In [ ]: