Circuit Declarations

Circuit declarations are used to declare primitive built-in circuits. The most common use for a circuit declaration is to declare a built-in verilog module.

The function

C = DeclareCircuit( name, typesignature )

returns a class which is a subclass of Circuit. This circuit can be instanced. The type signature has the form (name0, type0, name1, type1, ..., namen, typen) The types should be qualified as inputs or outputs.

For example,

SB_LUT4 = DeclareCircuit('SB_LUT4',
               "I0", In(Bit),
               "I1", In(Bit),
               "I2", In(Bit),
               "I3", In(Bit),
               "O",  Out(Bit))

declares the Silicon Blue LUT4 primitive that is built-in to the Lattice ice40 and yosys verilog compilers. This primitive has 4 inputs each declared as In(Bit), and a single output declared as Out(Bit).

To create lut4 circuit instance on the FPGA, we instance SB_LUT4.

lut4 = SB_LUT4(LUT_INIT=0xffff)

Here we pass in the 16-bit value which is used to initialize the LUT.

Circuit Definitions

it is easy to add new circuit classes to Magma using circuit definitions.

For example,

FA = DefineCircuit('FullAdder', 'a', In(Bit), 'b', In(Bit), 'cin', In(Bit), 's', Out(Bit), 'co', Out(Bit))
s = FA.a ^ FA.b ^ FA.cin
wire(s, FA.s)
cout = (FA.a & FA.b) | (FA.b & FA.cin) | (FA.a & FA.cin)
wire(cout, FA.cout)
EndCircuit()

FA is a subclass of Circuit. It has been enhanced to include the arguments in the function signature, in this case the inputs a, b, cin and the outputs s and cout. Inside the circuit definition we can instance circuits and wire them together.

Once FA has been defined, we can create circuit instances from it

fa = FA()

DefineCircuit has the same function signature as DeclareCircuit.

C = DefineCircuit( name, typesignature )

EndCircuit is needed to end the current circuit definition. Note that it is possible to nest circuit definitions.

Note that circuit definitions are cached using the name of the circuit. Defining a circuit a second time with the same name returns the same circuit class.

Circuit subclasses

The recommended way to create new circuits is to subclass Circuit.

class FullAdder(Circuit):
    name = "FullAdder"
    IO = ["a", In(Bit), "b", In(Bit), "cin", In(Bit), "s", Out(Bit), "cout", Out(Bit)]
    @classmethod
    def definition(io):
        # Generate the sum
        s = io.a ^ io.b ^ io.cin
        wire(s, io.s)
        # Generate the carry
        cout = (io.a & io.b) | (io.b & io.cin) | (io.a & io.cin)
        wire(cout, io.cout)

The circuit signature is contained in the attributes of FullAdder, in particular, name and IO. The actual definition is inside the method definition. Note that this is a class method. Note also that the circuit being defined is passed as an argument to definition, and the body of that function is identical to the code inside DefineCircuit and EndCircuit.

This magic is all done with python metaclasses.

Verilog

It is useful to be able to declare circuits from verilog source.

We provide two utilities for doing this.

modules = declare_from_verilog(source)
modules = declare_from_verilog_file(filename)

These functions return a list of Circuits, one for each module in the verilog file. The circuits will have the same name and interface as the modules in the verilog file. The verilog file is parsed using the python module pyverilog, which must be installed.

Here is a simple example,

from magma import declare_from_verilog

source = '''\
module CSA4 ( input [3:0] a,b,c, output [3:0] s, co);
   assign s = a ^ b ^c;
   assign co = a&b | b&c | a&c;
endmodule'''

CSA4 = declare_from_verilog(source)[0]

Another useful technique is to run a text templating engine over the verilog file before parsing.

modules = DefineFromTemplatedVerilog(template, **kwargs)
modules = DefineFromTemplatedVerilogFile(templatefilename, **kwargs)

We use the mako templating engine. The arguments kwargs appear in the global name space of the python code in the template. These functions return a list of Circuits, one for each module in the verilog file. The circuits will have the same name and interface as the modules in the verilog file.

from magma import DefineFromTemplatedVerilog

source = '''\
module CSA${N} ( input [${N-1}:0] a,b,c, output [${N-1}:0] s, co );
   assign s = a ^ b ^c;
   assign co = a&b | b&c | a&c;
endmodule'''

CSA4 = DefineFromTemplatedVerilog(source, **dict(N=4))[0]

Examples using these two functions are in a jupyter notebook