DFFs and Registers

This example demonstrates the use of d-flip-flops and registers.


In [1]:
import magma as m
m.set_mantle_target("ice40")

DFF

To use a DFF we import the mantle circuit DFF. Calling DFF() creates an instance of a DFF.

Although a sequential logic element like a DFF has internal state, in Magma it is treated very similar to a combinational logic element like a full adder. Both combinational and sequential circuits have inputs and outputs. The inputs and outputs are wired up in the same way in both cases.


In [2]:
from loam.boards.icestick import IceStick
from mantle import DFF

icestick = IceStick()
icestick.Clock.on() # Need to turn on the clock for sequential logic
icestick.J1[0].input().on()
icestick.J3[0].output().on()

main = icestick.DefineMain()
dff = DFF()
main.J3 <= dff(main.J1)
m.EndDefine()


import lattice ice40
import lattice mantle40

Since a flip-flop is a sequential logic element, it has a clock. The clock generator is a peripheral on the FPGA. We need to turn it on if we want to use the clock. Turning it on creates a global clock signal on the FPGA.

Note that we did not need to wire the clock to the DFF; magma automatically wires the global clock to the flip-flop's clock input.

Let's compile and build.


In [3]:
m.compile("build/dff", main)

In [4]:
%%bash
cd build
yosys -q -p 'synth_ice40 -top main -blif dff.blif' dff.v
arachne-pnr -q -d 1k -o dff.txt -p dff.pcf dff.blif 
icepack dff.txt dff.bin
#iceprog dff.bin


/Users/hanrahan/git/magmathon/notebooks/tutorial/icestick/build

If we inspect the compiled verilog, we see that our mantle DFF uses the SB_DFF ice40 primitive. Notice also that the top-level main module has a CLKIN signal, and that that signal has been wired to the clock of the SB_DFF.


In [5]:
%cat build/dff.v


module main (input  J1, output  J3, input  CLKIN);
wire  SB_DFF_inst0_Q;
SB_DFF SB_DFF_inst0 (.C(CLKIN), .D(J1), .Q(SB_DFF_inst0_Q));
assign J3 = SB_DFF_inst0_Q;
endmodule

Register

A register is simply an array of flip-flops. To create an instance of a register, call Register with the number of bits n in the register.


In [6]:
import magma as m
m.set_mantle_target("ice40")
from loam.boards.icestick import IceStick
from mantle import Register

icestick = IceStick()
icestick.Clock.on() # Need to turn on the clock for sequential logic
for i in range(4):
    icestick.J1[i].input().on()
    icestick.J3[i].output().on()

main = icestick.DefineMain()
register4 = Register(4)
main.J3 <= register4(main.J1)
m.EndDefine()

Registers and DFFs are very similar to each other. The only difference is that the input and output to a DFF are Bit values, whereas the inputs and the outputs to registers are Bits(n).


In [7]:
m.compile("build/register4", main)

In [8]:
%%bash
cd build
yosys -q -p 'synth_ice40 -top main -blif register4.blif' register4.v
arachne-pnr -q -d 1k -o register4.txt -p register4.pcf register4.blif 
icepack register4.txt register4.bin
#iceprog register4.bin


/Users/hanrahan/git/magmathon/notebooks/tutorial/icestick/build

If we inspect the compiled verilog, we see that our register is a module that instances a set of SB_DFFs.


In [9]:
%cat build/register4.v


module Register4 (input [3:0] I, output [3:0] O, input  CLK);
wire  SB_DFF_inst0_Q;
wire  SB_DFF_inst1_Q;
wire  SB_DFF_inst2_Q;
wire  SB_DFF_inst3_Q;
SB_DFF SB_DFF_inst0 (.C(CLK), .D(I[0]), .Q(SB_DFF_inst0_Q));
SB_DFF SB_DFF_inst1 (.C(CLK), .D(I[1]), .Q(SB_DFF_inst1_Q));
SB_DFF SB_DFF_inst2 (.C(CLK), .D(I[2]), .Q(SB_DFF_inst2_Q));
SB_DFF SB_DFF_inst3 (.C(CLK), .D(I[3]), .Q(SB_DFF_inst3_Q));
assign O = {SB_DFF_inst3_Q,SB_DFF_inst2_Q,SB_DFF_inst1_Q,SB_DFF_inst0_Q};
endmodule

module main (input [3:0] J1, output [3:0] J3, input  CLKIN);
wire [3:0] Register4_inst0_O;
Register4 Register4_inst0 (.I(J1), .O(Register4_inst0_O), .CLK(CLKIN));
assign J3 = Register4_inst0_O;
endmodule

Enables and Resets

Flip-flops and registers can have with clock enables and resets. The flip-flop has a clock enable, its state will only be updated if the clock enable is true. Similarly, if a flip-flop has a reset signal, it will be reset to its initial value if reset is true.

To create registers with these additional inputs, set the optional arguments has_ce and/or has_reset when instancing the register.


In [10]:
import magma as m
m.set_mantle_target("ice40")
from loam.boards.icestick import IceStick
from mantle import Register

icestick = IceStick()
icestick.Clock.on()
for i in range(4):
    icestick.J1[i].input().on()
    icestick.J3[i].output().on()
icestick.J1[4].input().on() # ce signal
icestick.J1[5].input().on() # reset signal

main = icestick.DefineMain()
register4 = Register(4, init=5, has_ce=True, has_reset=True )
main.J3 <= register4(main.J1[0:4], ce=main.J1[4], reset=main.J1[5])
m.EndDefine()

To wire the optional clock inputs, clock enable and reset, use named arguments (ce and reset) when you call the register with its inputs. In Magma, clock signals are handled differently than signals.

Compile, build, and upload.


In [11]:
m.compile("build/register4ce", main)

In [12]:
%%bash
cd build
yosys -q -p 'synth_ice40 -top main -blif register4ce.blif' register4ce.v
arachne-pnr -q -d 1k -o register4ce.txt -p register4ce.pcf register4ce.blif 
icepack register4ce.txt register4ce.bin
#iceprog register4ce.bin


/Users/hanrahan/git/magmathon/notebooks/tutorial/icestick/build

Notice in the generated verilog the code uses the SB_DFFESR primitive and that the CE port is wired up to the E (enable) input of the flip flop.


In [13]:
%cat build/register4ce.v


module Register4CER_0005 (input [3:0] I, output [3:0] O, input  CLK, input  CE, input  RESET);
wire  SB_DFFESR_inst0_Q;
wire  SB_LUT4_inst0_O;
wire  SB_LUT4_inst1_O;
wire  SB_DFFESR_inst1_Q;
wire  SB_DFFESR_inst2_Q;
wire  SB_LUT4_inst2_O;
wire  SB_LUT4_inst3_O;
wire  SB_DFFESR_inst3_Q;
SB_DFFESR SB_DFFESR_inst0 (.C(CLK), .R(RESET), .E(CE), .D(SB_LUT4_inst0_O), .Q(SB_DFFESR_inst0_Q));
SB_LUT4 #(.LUT_INIT(16'h5555)) SB_LUT4_inst0 (.I0(I[0]), .I1(1'b0), .I2(1'b0), .I3(1'b0), .O(SB_LUT4_inst0_O));
SB_LUT4 #(.LUT_INIT(16'h5555)) SB_LUT4_inst1 (.I0(SB_DFFESR_inst0_Q), .I1(1'b0), .I2(1'b0), .I3(1'b0), .O(SB_LUT4_inst1_O));
SB_DFFESR SB_DFFESR_inst1 (.C(CLK), .R(RESET), .E(CE), .D(I[1]), .Q(SB_DFFESR_inst1_Q));
SB_DFFESR SB_DFFESR_inst2 (.C(CLK), .R(RESET), .E(CE), .D(SB_LUT4_inst2_O), .Q(SB_DFFESR_inst2_Q));
SB_LUT4 #(.LUT_INIT(16'h5555)) SB_LUT4_inst2 (.I0(I[2]), .I1(1'b0), .I2(1'b0), .I3(1'b0), .O(SB_LUT4_inst2_O));
SB_LUT4 #(.LUT_INIT(16'h5555)) SB_LUT4_inst3 (.I0(SB_DFFESR_inst2_Q), .I1(1'b0), .I2(1'b0), .I3(1'b0), .O(SB_LUT4_inst3_O));
SB_DFFESR SB_DFFESR_inst3 (.C(CLK), .R(RESET), .E(CE), .D(I[3]), .Q(SB_DFFESR_inst3_Q));
assign O = {SB_DFFESR_inst3_Q,SB_LUT4_inst3_O,SB_DFFESR_inst1_Q,SB_LUT4_inst1_O};
endmodule

module main (input [5:0] J1, output [3:0] J3, input  CLKIN);
wire [3:0] Register4CER_0005_inst0_O;
Register4CER_0005 Register4CER_0005_inst0 (.I({J1[3],J1[2],J1[1],J1[0]}), .O(Register4CER_0005_inst0_O), .CLK(CLKIN), .CE(J1[4]), .RESET(J1[5]));
assign J3 = Register4CER_0005_inst0_O;
endmodule


In [ ]: