Debugging generated verilog

Question

How can I improve the readability of the generated Verilog?

Answer

Set the following configuration flags at the top of your top file

m.config.set_debug_mode(True)
m.set_codegen_debug_info(True)

This will configure magma to try to automatically capture instance names from the assigned Python variable as well as tracking the filename and line number that instances and wires are created. This information will be produced in the output verilog file. Please let us know if there are ways we can improve this, or if there are cases when this does not work as expected. Note that this relies on using the inspect module and traversing the stack every time an instance and wire are created, which can have implications on performance for designs with large numbers of instances and wires (although this issue can usually be mitigated by leveraging decomposition, reuse, and cacheing).

Instantiating a parametrized Verilog module

Question

How can I instantiate a Verilog module with a set of parameters?

Answer

Suppose I have the following module:

// ff.v
module FF(input clk, input rst, input d, output q);

parameter init = 0;

reg ff; 
always @(posedge clk or posedge rst) begin
  if (!rst)
    ff <= init;
  else
    ff <= d; 
end

assign q = ff;
endmodule

I can import it into magma as follows:

import magma as m


FF = m.DefineFromVerilogFile(
    "ff.v", type_map={"clk": m.In(m.Clock), "rst": m.In(m.AsyncReset)}
)[0]

class Top(m.Circuit):
    IO = ["I", m.In(m.Bits[2]), "O", m.Out(m.Bits[2])] + \
        m.ClockInterface(has_async_reset=True)
    @classmethod
    def definition(io):
        # keyword arguments to instancing call are passed as verilog parameters
        ff0 = FF(init=0)
        ff1 = FF(init=1)
        io.O <= m.join([ff0, ff1])(d=io.I, rst=io.ASYNCRESET)


m.compile("top", Top, output="coreir-verilog")

This produces the following Verilog, notice how the arguments to the magma instancing call has been passed to the Verilog instancing statement.

// top.v
module FF(input clk, input rst, input d, output q);

parameter init = 0;

reg ff; 
always @(posedge clk or posedge rst) begin
  if (!rst)
    ff <= init;
  else
    ff <= d; 
end

assign q = ff;
endmodule
module Top (input ASYNCRESET, input CLK, input [1:0] I, output [1:0] O);
wire FF_inst0_q;
wire FF_inst1_q;
FF #(.init(0)) FF_inst0(.clk(CLK), .d(I[0]), .q(FF_inst0_q), .rst(ASYNCRESET));
FF #(.init(1)) FF_inst1(.clk(CLK), .d(I[1]), .q(FF_inst1_q), .rst(ASYNCRESET));
assign O = {FF_inst1_q,FF_inst0_q};
endmodule

Statically Elaborated For Loop in Combinational Circuit

Question

How can I use a for loop that is evaluated at compile (Python) time inside a combinational circuit definition? For example:

@m.combinational.circuit
def find_first(I: Bits[n]) -> Bits[n]:
  for i in range(n):
    if I[i]:
      return m.Bits[n](2 ** i)
  return m.Bits[n](0)

Answer

This feature is forthcoming, until then, it is recommended to not use combinational for this kind of metaprogramming, and instead use base magma. Here is an example of writing the above circuit without the combinational syntax.

class FindFirst(m.Circuit):
    IO = ["I", m.In(m.Bits[n]), "O", m.Out(m.Bits[n])]
    @classmethod
    def definition(io):
        out = m.bits(0, n)
        for i in reversed(range(n)):
            out = mantle.mux([out, m.bits(2 ** i, n)], io.I[i])
        io.O <= out

fork

Question

For example, we have in_data as an input to a cell. Then I have a row module which instantiates 10 of those cells. How do I expand in_data to [in_data]*10?

Answer

The most idiomatic way to do this would be to use magma's fork operator, here is an example of taking a single input in_data and forking the input to 10 instances of an Invert cell.

class RowInvert(m.Circuit):
    IO = ["in_data", m.In(m.Bits[8]), "out_data", m.Out(m.Array[10, m.Bits[8]])]
    @classmethod
    def definition(io):
        io.out_data <= m.fork([mantle.Invert(8) for _ in range(10)])(io.in_data)

See the documentation on fork (https://github.com/phanrahan/magma/blob/master/docs/higher_order_circuits.md#join-flat-and-fork) for more information.

join vs array

Question

I tried to instantiate an array of mantle.CounterLoad as follows.

iter_num = m.array(
    [mantle.CounterLoad(cntr_width, cin=False, cout=False, incr=1, has_ce=True,
                        has_reset=True, name=f"iter_num_{i}") 
     for i in range(num_cntrs)]
)

I got the following error.

ValueError: All fields in a Array or a Tuple must be the same typegot CounterLoad5CER(DATA: In(UInt[5]), LOAD: In(Bit), O: Out(UInt[5]), CLK: In(Clock), CE: In(Enable), RESET: In(Reset)) expected CounterLoad5CER(DATA: In(UInt[5]), LOAD: In(Bit), O: Out(UInt[5]), CLK: In(Clock), CE: In(Enable), RESET: In(Reset))

Answer

m.array is used to convert magma types, so one can take a list of m.Bits and convert them into an m.Array type. In this case, you're trying to convert a list of instances and "join" there interfaces into an array. For this pattern, use magma's m.join higher order circuit operator:

iter_num = m.join(
    [mantle.CounterLoad(cntr_width, cin=False, cout=False, incr=1, has_ce=True,
                        has_reset=True, name=f"iter_num_{i}") 
     for i in range(num_cntrs)]
)

See https://github.com/phanrahan/magma/blob/master/docs/higher_order_circuits.md#join-flat-and-fork for more information.

Dynamically Declared Circuit Interface

Question

How do I declare a dynamically constructed circuit interface?

Answer

There are two ways to do this using the standard subclassing m.Circuit syntax and the m.DeclareCircuit syntax:

Option 1:

def make_Declaration(n, ...):
    class Declaration(m.Circuit):
        # Create IO list
        IO = []
        # Dynamically add things to the list
        for i in range(n):
            IO += [f"I{n}", m.In(m.Bits[i + 1])]
        IO += ["O", m.Out(m.Bits[n + 1])]
    return Declaration

Option 2:

def make_Declaration(n, ...):
    # Create IO list
    IO = []
    # Dynamically add things to the list
    for i in range(n):
        IO += [f"I{n}", m.In(m.Bits[i + 1])]
    IO += ["O", m.Out(m.Bits[n + 1])]
    # Use "splat/asterix" (*) operator to turn list into arguments to declare
    # circuit
    return m.DeclareCircuit("Declaration", *IO)

Reduction Tree

Question

How can I implement the following example verilog code for a reduction tree?

always_comb
begin
    out = 0;
    for(int i=0;i<REDUCTION_LENGTH;i=i+1)
        out = out + in_reduction[i]
end

Answer

There are two options, the above example can be directly translated using a for loop, or you can use Python's reduce function:

def make_Reduction(n):
    class Reduction(m.Circuit):
        IO = ["in_reduction", m.In(m.Array[n, m.UInt[8]]),
              "out0", m.Out(m.UInt[8]), "out1", m.Out(m.UInt[8])]
        @classmethod
        def definition(io):
            # Option 1 with for loop
            out = m.uint(0, 8)
            for i in range(n):
                out += io.in_reduction[i]
            io.out0 <= out

            # Option 2 with reduce
            from functools import reduce
            import operator
            io.out1 <= reduce(operator.add, io.in_reduction)
    return Reduction

Transpose Matrix

Question

How can I tranpose a 2-d array?

Answer

def make_Transpose(n0, n1):
    class Transpose(m.Circuit):
        IO = ["a", m.In(m.Array[n0, m.Array[n1, m.Bit]]),
              "b", m.Out(m.Array[n1, m.Array[n0, m.Bit]])]
        @classmethod
        def definition(io):
            for col in range(n0):
                for row in range(n1):
                    m.wire(io.b[row][col], io.a[col][row])

    return Transpose