NOTE: This is a work in progress, please let us know via issue/gitter/email if you'd like to see anything added to this.
This is inspired by the chisel cheatsheet and will be rendered in a similar single page layout soon.
Types
Bit
m.Bit
: boolean valuem.VCC, m.GND
: boolean literals
Bits and Integers
m.Bits[N]
: lengthN
bit vector with bitwise logical operators definedm.UInt[N]
: lengthN
unsigned integer that includes Bits operators and unsigned arithmetic (e.g.+
,-
, ...) and comparison operators (e.g.<
,<=
, ...)m.SInt[N]
: lengthN
signed integer that includes Bits operators and signed arithmetic (e.g.+
,-
, ...) and comparison operators (e.g.<
,<=
, ...)
Array
m.Array[N, T]
: fixed length array of lengthN
containing values of typeT
with equality operator (==
) defined
Tuples and Products
m.Tuple[t0, t1, ...]
: heterogenous compound data type containing members of typet0
,t1
, ... Values of such types can be indexed using integer keys like Python tuples (e.g. for a tuple valuex
,type(x[0]) == t0
).m.Product.from_fields(name, dict(k0=t0, k1=t1, ...))
: heterogenous compound data type containing members of typet0
,t1
, ... Values of such types can be indexed using attributes (e.g. for a product valuex
,type(x.k0) == t0
).name="anon"
makes this type anonymous.- Alternate syntax:
python class <name>(m.Product): k0 = t0 k1 = t1 ...
Enum
Statically typed enumerated type:
class <name>(m.Enum):
k0 = v0
k1 = v1
...
Type qualifiers
m.In(T)
, m.Out(T)
, m.InOut(T)
qualify type T
to be an input, output, and
inout respectively.
Type conversions
m.bits(value, n=None)
: convertvalue
to anBits
(same rules asm.array
except if the input is a magma type or list, the members must be convertible to a Bit (e.g.Bit
,bool
,0
,1
))m.array(value, n=None)
: convertvalue
to an array.value
must be a magma type (Bit
,Tuple
,Array
), a Python integer (e.g. literal), or a Python sequence (e.g. list). Ifn
isNone
, the width of the array is inferred fromvalue
(e.g. length of the list, bit length of the integer, length of the magma type)m.uint(value, n=None)
: convertvalue
to anUInt
(same rules asm.bits
, except will not convertm.SInt
tom.UInt
)m.sint(value, n=None)
: convertvalue
to anSInt
(same rules asm.bits
, except will not convertm.UInt
tom.SInt
)
Math Helpers
Contained in the module magma.math
* m.math.log2_ceil(x: int) -> int
: log2(x)
rounded up
* m.math.log2_floor(x: int) -> int
: log2(x)
rounded down
* m.math.is_pow2(x: int) -> bool
: True
if x
is a power of 2
Registers
Retain state until updated:
my_reg = mantle.Register(32)
my_reg_init_7 = mantle.Register(32, init=7)
my_reg_clock_enable = mantle.Register(32, init=7, has_ce=True)
my_reg_uint = mantle.Register(32, init=7, has_ce=True, T=m.UInt)
Define update value by wiring to the input I
port
my_reg.I @= next_val
Set N
to None
for a Register of a single Bit
(setting N
to 1
will
produce a register of Bits[1]
)
bit_reg = mantle.Register(None)
Memories
MyMemCircuit = mantle.DefineMemory(height, width, readonly=False, read_latency=0)
my_mem_inst = MyMemCircuit()
height
: number of elementswidth
: width of each elementreadonly
: ROM if True else RAMread_latency
: number of registers to append to read out port
Memory ports (where addr_width = max(clog2(height - 1), 1)
):
RADDR
:m.In(m.Bits[addr_width])
RDATA
:m.Out(m.Bits[width])
WADDR
:m.In(m.Bits[addr_width])
WDATA
:m.In(m.Bits[width])
CLK
:m.In(m.Clock)
WE
:m.In(m.Bit)
Circuits
Defining: subclass m.Circuit
class Accum16(m.Circuit):
io = m.IO(I=m.In(m.UInt[16]), O=m.Out(m.UInt[16])) + m.ClockIO()
sum_ = mantle.Register(16)
io.O @= sum_(m.uint(sum_.O) + io.I)
Usage: circuits are used by instancing them inside another definitions and their ports are accessed using dot notation
my_module = Accum16()
my_module.I @= some_data
sum_ = my_module.O
Wiring: wire an output to an input using @=
operator (statically typed)
Metaprogramming: abstract over parameters by generating a circuit definition inside a closure
def define_accum(width: int):
class Accum(m.Circuit):
name = f"Accum{width}"
io = m.IO(I=m.In(m.UInt[width]), O=m.Out(m.UInt[width])) + m.ClockIO()
sum_ = mantle.Register(width)
io.O @= sum_(m.uint(sum_.O) + io.I)
return Accum
Operators
Infix operators
All types support the following operators:
- Equal ==
- Not Equal !=
The Bit
type supports the following logical operators.
- And &
- Or |
- Exclusive or ^
- Not ~
The Array
type family supports the following operator.
- Dynamic bit selection my_arry[add.O]
(select a bit dynamically using a magma value).
The Bits
type family supports the following logical operators.
- And &
(element-wise)
- Or |
(element-wise)
- Exclusive or ^
(element-wise)
- Not ~
(element-wise)
- Logical right shift (with zeros) >>
- Logical left shift (with zeros) <<
The UInt
and SInt
types support all the logical operators
as well as arithmetic and comparison operators.
- Add +
- Subtract/Negate -
- Multiply *
- Divide /
- Less than <
- Less than or equal <=
- Greater than >
- Greater than or equal >=
Note that the the right shift operator when applied to an SInt
becomes
an arithmetic shift right operator (which replicates the sign bit as it shifts right).
Functional operators
mantle.mux(*I, S)
(constraint:len(S) == log2_ceil(len(I))
): selectI[S]
.m.zext(v, n)
: zero extend arrayv
byn
m.sext(v, n)
: sign extend arrayv
byn
m.concat(*arrays)
: concat arrays togetherm.repeat(value, n)
: create an array repeatingvalue
n
times
Combinational
@m.circuit.combinational
def basic_if(I: m.Bits[2], S: m.Bit) -> m.Bit:
if S:
return I[0]
else:
return I[1]
produces
basic_if = DefineCircuit("basic_if", "I", In(Bits[2]), "S", In(Bit), "O", Out(Bit))
Mux2xOutBit_inst0 = Mux2xOutBit()
wire(basic_if.I[1], Mux2xOutBit_inst0.I0)
wire(basic_if.I[0], Mux2xOutBit_inst0.I1)
wire(basic_if.S, Mux2xOutBit_inst0.S)
wire(Mux2xOutBit_inst0.O, basic_if.O)
EndCircuit()
See here for more details.
Sequential
@m.circuit.sequential(async_reset=True)
class DelayBy2:
def __init__(self):
self.x: m.Bits[2] = m.bits(0, 2)
self.y: m.Bits[2] = m.bits(0, 2)
def __call__(self, I: m.Bits[2]) -> m.Bits[2]:
O = self.y
self.y = self.x
self.x = I
return O
See here for more details.