RTL Library

Useful circuits, functions, and utilities.

Multiplexers

Basic multiplexers are defined in PyRTL’s core library, see:

The functions below provide more complex alternatives.

pyrtl.rtllib.muxes.SparseDefault = 'default'

A special key for sparse_mux()’s vals dict that specifies the mux’s default value.

pyrtl.rtllib.muxes.demux(select)[source]

Demultiplexes a wire of arbitrary bitwidth.

This effectively converts an unsigned binary value into a one-hot encoded value, returning each bit of the one-hot encoded value as a separate WireVector.

Example:

>>> input = pyrtl.Input(bitwidth=3)

>>> outputs = pyrtl.rtllib.muxes.demux(input)
>>> len(outputs)
8
>>> len(outputs[0])
1
>>> for i, wire in enumerate(outputs):
...     wire.name = f"outputs[{i}]"

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={input.name: 5})

>>> sim.inspect("outputs[4]")
0
>>> sim.inspect("outputs[5]")
1
>>> sim.inspect("outputs[6]")
0

In the example above, len(outputs) is 8 because 2 ** 3 == 8, and outputs[5] is 1 because the output index 5 matches the input value.

See binary_to_one_hot(), which performs a similar operation.

Warning

demux can create a very large number of WireVectors. Use with caution.

Parameters:

select (WireVector) – The value to demultiplex.

Return type:

tuple[WireVector, ...]

Returns:

A tuple of 1-bit wires, where each wire indicates if the value of select equals the wire’s index in the tuple. The tuple has length 2 ** select.bitwidth.

pyrtl.rtllib.muxes.prioritized_mux(selects, vals)[source]

Returns the value in the first wire for which its select bit is 1

If none of the selects are 1, the last val is returned.

Example:

>>> selects = [pyrtl.Input(name=f"select{i}", bitwidth=1)
...            for i in range(3)]
>>> vals = [pyrtl.Const(n) for n in range(2, 5)]
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.muxes.prioritized_mux(selects, vals)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"select0": 1, "select1": 0, "select2": 0})
>>> sim.inspect("output")
2

>>> sim.step(provided_inputs={"select0": 0, "select1": 1, "select2": 0})
>>> sim.inspect("output")
3

>>> sim.step(provided_inputs={"select0": 0, "select1": 0, "select2": 0})
>>> sim.inspect("output")
4
Parameters:
Return type:

WireVector

Returns:

The selected value.

pyrtl.rtllib.muxes.sparse_mux(sel, vals)[source]

Mux that avoids instantiating unnecessary mux_2s when possible.

sparse_mux supports not having a full specification. Indices that are not specified are treated as don’t-cares.

Example:

>>> select = pyrtl.Input(name="select", bitwidth=3)
>>> vals = {2: pyrtl.Const(3),
...         4: pyrtl.Const(5),
...         pyrtl.rtllib.muxes.SparseDefault: pyrtl.Const(7)}
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.muxes.sparse_mux(select, vals)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"select": 2})
>>> sim.inspect("output")
3

>>> sim.step(provided_inputs={"select": 4})
>>> sim.inspect("output")
5

>>> sim.step(provided_inputs={"select": 3})
>>> sim.inspect("output")
7
Parameters:
  • sel (WireVector) – Select wire, which chooses one of the mux input vals to output.

  • vals (dict[int, WireVector]) – dict of mux input values. If the special key SparseDefault exists, it specifies the sparse_mux’s default value.

Return type:

WireVector

Returns:

The WireVector selected from vals by sel.

Adders

Basic integer addition is defined in PyRTL’s core library, see:

The functions below provide more complex alternatives.

pyrtl.rtllib.adders.carrysave_adder(a, b, c, final_adder=<function ripple_add>)[source]

Adds three WireVectors up in an efficient manner.

Example:

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> b = pyrtl.Input(name="b", bitwidth=4)
>>> c = pyrtl.Input(name="c", bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.adders.carrysave_adder(a, b, c)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"a": 2, "b": 3, "c": 4})
>>> sim.inspect("output")
9
Parameters:
  • a (WireVector) – A WireVector to add up. Bitwidths don’t need to match.

  • b (WireVector) – A WireVector to add up. Bitwidths don’t need to match.

  • c (WireVector) – A WireVector to add up. Bitwidths don’t need to match.

  • final_adder (Callable, default: <function ripple_add at 0x722a96584eb0>) – The adder to use for the final addition.

Return type:

WireVector

Returns:

A WireVector with bitwidth equal to the longest input, plus 2.

pyrtl.rtllib.adders.cla_adder(a, b, cin=0, la_unit_len=4)[source]

Carry Look-Ahead Adder.

A Carry Look-Ahead Adder is an adder that is faster than ripple_add(), as it calculates the carry bits faster. It is not as fast as kogge_stone(), but uses less area.

Example:

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> b = pyrtl.Input(name="b", bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.adders.cla_adder(a, b)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"a": 2, "b": 3})
>>> sim.inspect("output")
5
Parameters:
Return type:

WireVector

Returns:

A WireVector representing the output of the adder.

pyrtl.rtllib.adders.dada_reducer(wire_array_2, result_bitwidth, final_adder=<function kogge_stone>)[source]

The reduction and final adding part of a dada tree.

Useful for adding many numbers together with fast_group_adder(). The use of single bitwidth wires allows for additional flexibility.

Parameters:
  • wire_array_2 (list[list[WireVector]]) – An array of arrays of single bitwidth WireVectors.

  • result_bitwidth (int) – Bitwidth of the resulting wire. Used to eliminate unnecessary wires.

  • final_adder (Callable, default: <function kogge_stone at 0x722a96584bf0>) – The adder used for the final addition.

Return type:

WireVector

Returns:

WireVector with bitwidth result_bitwidth.

pyrtl.rtllib.adders.fast_group_adder(wires_to_add, reducer=<function wallace_reducer>, final_adder=<function kogge_stone>)[source]

A generalization of carrysave_adder(), fast_group_adder is designed to add many numbers together in a both area and time efficient manner. Uses a tree reducer to achieve this performance.

The length of the result is:

max(len(w) for w in wires_to_add) + ceil(len(wires_to_add))

Example:

>>> wires_to_add = [pyrtl.Const(n) for n in range(10)]
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.adders.fast_group_adder(wires_to_add)

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
45
>>> sum(range(10))
45
Parameters:
Returns:

A WireVector with the result of the addition.

pyrtl.rtllib.adders.half_adder(a, b)[source]
pyrtl.rtllib.adders.kogge_stone(a, b, cin=0)[source]

Creates a Kogge-Stone adder given two inputs.

The Kogge-Stone adder is a fast tree-based adder with O(log(n)) propagation delay, useful for performance critical designs. However, it has O(n log(n)) area usage, and large fan out.

Example:

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> b = pyrtl.Input(name="b", bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.adders.kogge_stone(a, b)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"a": 2, "b": 3})
>>> sim.inspect("output")
5
Parameters:
Return type:

WireVector

Returns:

A WireVector representing the output of the adder.

pyrtl.rtllib.adders.one_bit_add(a, b, cin=0)[source]
pyrtl.rtllib.adders.ripple_add(a, b, cin=0)[source]
pyrtl.rtllib.adders.ripple_half_add(a, cin=0)[source]
pyrtl.rtllib.adders.wallace_reducer(wire_array_2, result_bitwidth, final_adder=<function kogge_stone>)[source]

The reduction and final adding part of a dada tree.

Useful for adding many numbers together with fast_group_adder(). The use of single bitwidth wires allows for additional flexibility.

Parameters:
  • wire_array_2 (list[list[WireVector]]) – An array of arrays of single bitwidth WireVectors.

  • result_bitwidth (int) – Bitwidth of the resulting wire. Used to eliminate unnecessary wires.

  • final_adder (Callable, default: <function kogge_stone at 0x722a96584bf0>) – The adder used for the final addition.

Return type:

WireVector

Returns:

WireVector with bitwidth result_bitwidth.

Multipliers

Basic integer multiplication is defined in PyRTL’s core library, see:

The functions below provide more complex alternatives.

pyrtl.rtllib.multipliers.complex_mult(A, B, shifts, start)[source]

Generate shift-and-add multiplier that can shift and add multiple bits per clock cycle. Uses substantially more space than simple_mult() but is much faster.

Example:

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> b = pyrtl.Input(name="b", bitwidth=4)
>>> start = pyrtl.Input(name="start", bitwidth=1)

>>> output, done = pyrtl.rtllib.multipliers.complex_mult(
...     a, b, shifts=2, start=start)
>>> output.name = "output"
>>> done.name = "done"

>>> sim = pyrtl.Simulation()
>>> sim.step({"a": 2, "b": 3, "start": True})
>>> while not sim.inspect("done"):
...     sim.step({"a": 0, "b": 0, "start": False})
>>> sim.inspect("output")
6
Parameters:
  • A (WireVector) – Input wire for the multiplication.

  • B (WireVector) – Input wire for the multiplication.

  • shifts (int) – Number of spaces Register is to be shifted per clock cycle. Cannot be greater than the length of A or B.

  • start (WireVector) – One-bit start signal.

Return type:

tuple[Register, WireVector]

Returns:

Register containing the product, and a 1-bit done signal.

pyrtl.rtllib.multipliers.fused_multiply_adder(mult_A, mult_B, add, signed=False, reducer=<function wallace_reducer>, adder_func=<function kogge_stone>)[source]

Generate efficient hardware for mult_A * mult_B + add.

Multiplies two WireVectors together and adds a third WireVector to the multiplication result, all in one step. By combining these operations, rather than doing them separately, one reduces both the area and the timing delay of the circuit.

Example:

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> b = pyrtl.Input(name="b", bitwidth=4)
>>> c = pyrtl.Input(name="c", bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.multipliers.fused_multiply_adder(a, b, c)

>>> sim = pyrtl.Simulation()
>>> sim.step({"a": 2, "b": 3, "c": 4})
>>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth)
10
>>> 2 * 3 + 4
10
Parameters:
  • mult_A (WireVector) – Input wire for the multiplication.

  • mult_B (WireVector) – Input wire for the multiplication.

  • add (WireVector) – Input wire for the addition.

  • signed (bool, default: False) – Currently not supported (will be added in the future) The default will likely be changed to True, so if you want the smallest set of wires in the future, specify this as False.

  • reducer (Callable, default: <function wallace_reducer at 0x722a96585430>) – (advanced) The tree reducer to use. See dada_reducer() and wallace_reducer().

  • adder_func (Callable, default: <function kogge_stone at 0x722a96584bf0>) – (advanced) The adder to use to add the two results at the end.

Return type:

WireVector

Returns:

The result WireVector.

pyrtl.rtllib.multipliers.generalized_fma(mult_pairs, add_wires, signed=False, reducer=<function wallace_reducer>, adder_func=<function kogge_stone>)[source]

Generated an optimized fused multiply adder.

A generalized FMA unit that multiplies each pair of numbers in mult_pairs, then adds up the resulting products and all the values of the add_wires. This is faster than multiplying and adding separately because you avoid unnecessary adder structures for intermediate representations.

Example:

>>> mult_pairs = [(pyrtl.Const(2), pyrtl.Const(3)),
...               (pyrtl.Const(4), pyrtl.Const(5))]
>>> add_wires = [pyrtl.Const(6), pyrtl.Const(7)]
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.multipliers.generalized_fma(mult_pairs, add_wires)

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
39
>>> 2 * 3 + 4 * 5 + 6 + 7
39
Parameters:
  • mult_pairs (list[tuple[WireVector, WireVector]]) – Either None (if there are no pairs to multiply) or a list of pairs of wires to multiply together: [(mult1_1, mult1_2), ...]

  • add_wires (list[WireVector]) – Either None (if there are no individual items to add other than the mult_pairs products), or a list of wires to add on top of the result of the pair multiplication.

  • signed (bool, default: False) – Currently not supported (will be added in the future) The default will likely be changed to True, so if you want the smallest set of wires in the future, specify this as False.

  • reducer (Callable, default: <function wallace_reducer at 0x722a96585430>) – (advanced) The tree reducer to use. See dada_reducer() and wallace_reducer().

  • adder_func (Callable, default: <function kogge_stone at 0x722a96584bf0>) – (advanced) The adder to use to add the two results at the end.

Return type:

WireVector

Returns:

The result WireVector.

pyrtl.rtllib.multipliers.signed_tree_multiplier(A, B, reducer=<function wallace_reducer>, adder_func=<function kogge_stone>)[source]

Same as tree_multiplier(), but uses two’s-complement signed integers.

Example:

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> b = pyrtl.Input(name="b", bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.multipliers.signed_tree_multiplier(a, b)

>>> sim = pyrtl.Simulation()
>>> sim.step({"a": -2, "b": 3})
>>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth)
-6
pyrtl.rtllib.multipliers.simple_mult(A, B, start)[source]

Builds a slow, small multiplier using the simple shift-and-add algorithm.

Requires very small area (it uses only a single adder), but has long delay (worst case is len(A) cycles).

Example:

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> b = pyrtl.Input(name="b", bitwidth=4)
>>> start = pyrtl.Input(name="start", bitwidth=1)

>>> output, done = pyrtl.rtllib.multipliers.simple_mult(a, b, start=start)
>>> output.name = "output"
>>> done.name = "done"

>>> sim = pyrtl.Simulation()
>>> sim.step({"a": 2, "b": 3, "start": True})
>>> while not sim.inspect("done"):
...     sim.step({"a": 0, "b": 0, "start": False})
>>> sim.inspect("output")
6
Parameters:
  • A (WireVector) – Input wire for the multiplication.

  • B (WireVector) – Input wire for the multiplication.

  • start (WireVector) – A one-bit input that indicates when the inputs are ready.

Return type:

tuple[Register, WireVector]

Returns:

A Register containing the product, and a 1-bit done signal.

pyrtl.rtllib.multipliers.tree_multiplier(A, B, reducer=<function wallace_reducer>, adder_func=<function kogge_stone>)[source]

Build an fast unclocked multiplier using a Wallace or Dada Tree.

Delay is O(log(N)), while area is O(N^2).

Example:

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> b = pyrtl.Input(name="b", bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.multipliers.tree_multiplier(a, b)

>>> sim = pyrtl.Simulation()
>>> sim.step({"a": 2, "b": 3})
>>> sim.inspect("output")
6
Parameters:
  • A (WireVector) – Input wire for the multiplication.

  • B (WireVector) – Input wire for the multiplication.

  • reducer (Callable, default: <function wallace_reducer at 0x722a96585430>) – Reducing the tree with a wallace_reducer() or a dada_reducer() determines whether the tree_multiplier is a Wallace tree multiplier or a Dada tree multiplier.

  • adder_func (Callable, default: <function kogge_stone at 0x722a96584bf0>) – An adder function that will be used to do the last addition.

Return type:

WireVector

Returns:

The multiplied result.

Barrel Shifter

Basic shifting is defined in PyRTL’s core library, see:

barrel_shifter() should only be used when more complex shifting behavior is required.

class pyrtl.rtllib.barrel.Direction(*values)[source]

Assigns names to each shift direction, to improve code readability.

LEFT = 1
RIGHT = 0
pyrtl.rtllib.barrel.barrel_shifter(bits_to_shift, bit_in, direction, shift_dist, wrap_around=0)[source]

Create a barrel shifter.

Example:

>>> bits_to_shift = pyrtl.Input(name="input", bitwidth=8)
>>> shift_dist = pyrtl.Input(name="shift_dist", bitwidth=3)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtllib.barrel.barrel_shifter(
...     bits_to_shift,
...     bit_in=1,
...     direction=pyrtl.rtllib.barrel.Direction.RIGHT,
...     shift_dist=shift_dist)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0x55, "shift_dist": 4})
>>> hex(sim.inspect("output"))
'0xf5'
Parameters:
  • bits_to_shift (WireVector) – WireVector with the value to shift.

  • bit_in (WireVector | int | str | bool) – A 1-bit WireVector representing the value to shift in.

  • direction (WireVector | int | str | bool) – A one bit WireVector representing the shift direction (0 = shift right, 1 = shift left). If direction is constant, use Direction to improve code readability (direction=Direction.RIGHT instead of direction=0).

  • shift_dist (WireVector) – WireVector representing the amount to shift.

  • wrap_around (default: 0) – **currently not implemented**

Return type:

WireVector

Returns:

The shifted WireVector.

Matrix

class pyrtl.rtllib.matrix.Matrix(rows, columns, bits, signed=False, value=None, max_bits=64)[source]

Class for making a Matrix using PyRTL.

Provides the ability to perform different matrix operations.

__add__(other)[source]

Perform the addition operation.

Invoked with a + b. Performs elementwise addition.

Return type:

Matrix

Returns:

a Matrix object containing the elementwise sum.

__getitem__(key)[source]

Access elements in the Matrix.

Invoked with square brackets, like matrix[...].

Examples:

int_matrix = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
matrix = Matrix(rows=3, columns=3, bits=4, value=int_matrix)

# Retrieve the second row.
matrix[1] == [3, 4, 5]

# Retrieve the last row.
matrix[-1] == [6, 7, 8]

# Retrieve the element in row 2, column 0.
matrix[2, 0] == 6
matrix[(2, 0)] = 6

# Retrieve the first two rows.
matrix[slice(0, 2), slice(0, 3)] == [[0, 1, 2], [3, 4, 5]]
matrix[0:2, 0:3] == [[0, 1, 2], [3, 4, 5]]
matrix[:2] == [[0, 1, 2], [3, 4, 5]]

# Retrieve the last two rows.
matrix[-2:] == [[3, 4, 5], [6, 7, 8]]
Parameters:

key (int | slice | tuple[int, int]) – The key value to get.

Return type:

WireVector | Matrix

Returns:

WireVector or Matrix containing the value of key.

__init__(rows, columns, bits, signed=False, value=None, max_bits=64)[source]

Constructs a Matrix object.

Parameters:
  • rows (int) – The number of rows in the matrix. Must be greater than 0.

  • columns (int) – The number of columns in the matrix. Must be greater than 0.

  • bits (int) – The number of bits per WireVector matrix element. Must be greater than 0.

  • signed (bool, default: False) – Currently not supported (will be added in the future).

  • value (WireVector | list[list[WireVector | int | str | bool]], default: None) – The value you want to initialize the Matrix to. If a WireVector, must be of size rows * columns * bits. If a list, must have rows rows and columns columns, and every element must be representable with a bitwidth of bits. If None, the matrix initializes to 0.

  • max_bits (int, default: 64) – The maximum number of bits each WireVector element can grow to. Operations like multiplication and addition can produce matrices with more bits, but results will be limited to max_bits.

__len__()[source]

Returns the total bitwidth for all elements in the Matrix.

Return type:

int

Returns:

The Matrix’s total bitwidth: rows * columns * bits.

__matmul__(other)[source]

Performs the inplace matrix multiplication operation.

Invoked with a @ b.

Parameters:

other (Matrix) – The second Matrix.

Return type:

Matrix

Returns:

A Matrix that contains the product.

__mul__(other)[source]

Perform the elementwise or scalar multiplication operation.

Invoked with a * b.

Parameters:

other (Matrix | WireVector) – The Matrix or scalar to multiply.

Return type:

Matrix

Returns:

A Matrix object with the product.

__pow__(power)[source]

Performs the matrix power operation.

Invoked with a ** b.

This performs a chain of matrix multiplications, where self is matrix multiplied by self, power times.

Parameters:

power (int) – The power to raise the matrix to.

Return type:

Matrix

Returns:

A Matrix containing the result.

__reversed__()[source]

Invoked with the reversed() builtin.

Return type:

Matrix

Returns:

A Matrix with all row and column indices reversed.

__sub__(other)[source]

Perform the subtraction operation.

Invoked with a - b. Performs elementwise subtraction.

Note

If signed=False, the result will be floored at 0.

Parameters:

other (Matrix) – The Matrix to subtract.

Return type:

Matrix

Returns:

a Matrix object with the result of elementwise subtraction.

property bits: int

The number of bits for each matrix element.

Reducing the number of bits will truncate() the most significant bits of each matrix element.

copy()[source]

Constructs a copy of the Matrix.

The returned copy will have new set of WireVectors for its elements, but each new WireVector will be wired to the corresponding WireVector in the original Matrix.

Return type:

Matrix

Returns:

A new instance of Matrix that indirectly refers to the same underlying WireVectors as self.

flatten(order='C')[source]

Flatten the Matrix into a single row.

Parameters:

order (str, default: 'C') – C means row-major order (C-style), and F means column-major order (Fortran-style)

Returns:

A copy of the Matrix flattened into a row vector.

put(ind, v, mode='raise')[source]

Replace specified elements of the Matrix with values v.

Note that the index ind is on the flattened matrix.

Parameters:
  • ind (int | list[int] | tuple[int]) – Target indices.

  • v (int | list[int] | tuple[int] | Matrix) – Values to place in Matrix at ind. If v is shorter than ind, v will be repeated.

  • mode (str, default: 'raise') – How out-of-bounds indices behave. raise raises an error, wrap wraps around, and clip clips to the range.

reshape(*newshape, order='C')[source]

Create a Matrix of the given shape from self.

One shape dimension in newshape can be -1; in this case, the value for that dimension is inferred from the other given dimension (if any) and the number of elements in self.

Examples:

int_matrix = [[0, 1, 2, 3], [4, 5, 6, 7]]
matrix = Matrix.Matrix(2, 4, 4, value=int_matrix)

matrix.reshape(-1) == [[0, 1, 2, 3, 4, 5, 6, 7]]
matrix.reshape(8) == [[0, 1, 2, 3, 4, 5, 6, 7]]
matrix.reshape(1, 8) == [[0, 1, 2, 3, 4, 5, 6, 7]]
matrix.reshape((1, 8)) == [[0, 1, 2, 3, 4, 5, 6, 7]]
matrix.reshape((1, -1)) == [[0, 1, 2, 3, 4, 5, 6, 7]]

matrix.reshape(4, 2) == [[0, 1], [2, 3], [4, 5], [6, 7]]
matrix.reshape(-1, 2) == [[0, 1], [2, 3], [4, 5], [6, 7]]
matrix.reshape(4, -1) == [[0, 1], [2, 3], [4, 5], [6, 7]]
Parameters:
  • newshape (int | tuple) – Shape of the matrix to return. If newshape is a single int, the new shape will be a 1-D row-vector of that length. If newshape is a tuple, the tuple specifies the new number of rows and columns. newshape can also be varargs.

  • order (str, default: 'C') – C means to read from self using row-major order (C-style), and F means to read from self using column-major order (Fortran-style).

Returns:

A copy of the matrix with same data, with a new number of rows and columns.

to_wirevector()[source]

Returns all elements in the Matrix in one WireVector.

This concatenates all the Matrix’s elements together, in row-major order.

For example, a 2 x 1 matrix [[wire_a, wire_b]] would become pyrtl.concat(wire_a, wire_b).

Return type:

WireVector

Returns:

A concatenated WireVector containing all of the Matrix’s elements.

transpose()[source]
Return type:

Matrix

Returns:

A Matrix representing the transpose of self.

pyrtl.rtllib.matrix.argmax(matrix, axis=None, bits=None)[source]

Returns the index of the max value of the Matrix.

Note

If there are two indices with the same max value, this function picks the first instance.

Parameters:
  • matrix (Matrix | WireVector) – The Matrix to perform argmax operation on. If it is a WireVector, it will return itself.

  • axis (int | None, default: None) – The axis to perform the operation on. None refers to argmax of all items. 0 is argmax of the columns. 1 is argmax of rows. Defaults to None.

  • bits (int | None, default: None) – The bits per element of the argmax. Defaults to matrix.bits.

Return type:

Matrix | WireVector

Returns:

A WireVector or Matrix representing the argmax value.

pyrtl.rtllib.matrix.concatenate(matrices, axis=0)[source]

Join a sequence of matrices along an existing axis.

This function is just a wrapper around hstack() and vstack().

Parameters:
  • matrices (Matrix) – Matrices to concatenate together.

  • axis (int, default: 0) – Axis along which to concatenate. 0 is horizontally, 1 is vertically. Defaults to 0.

Return type:

Matrix

Returns:

A new Matrix composed of the given matrices concatenated together.

pyrtl.rtllib.matrix.dot(first, second)[source]

Performs the dot product on two matrices.

Specifically, the dot product on two matrices is:

  1. If either first or second are WireVectors, or have both rows and columns equal to 1, dot is equivalent to Matrix.__mul__()

  2. If first and second are both arrays (have rows or columns equal to 1), dot is the inner product of the vectors.

  3. Otherwise dot is Matrix.__matmul__() between first and second.

Note

Row vectors and column vectors are both treated as arrays.

Parameters:
  • first (Matrix) – The first matrix.

  • second (Matrix) – The second matrix.

Return type:

Matrix

Returns:

A Matrix that contains the dot product of first and second.

pyrtl.rtllib.matrix.hstack(*matrices)[source]

Stack matrices in sequence horizontally (column-wise).

All the matrices must have the same number of rows and the same signed value.

For example:

m1 = Matrix(rows=2, columns=3, bits=5,
            value=[[1, 2, 3],
                   [4, 5, 6]])
m2 = Matrix(rows=2, columns=1, bits=10,
            value=[[17],
                   [23]]])
m3 = hstack(m1, m2)

m3 will look like:

[[1, 2, 3, 17],
 [4, 5, 6, 23]]

And m3.bits will be 10.

Parameters:

matrices (Matrix) – Matrices to concatenate together horizontally.

Return type:

Matrix

Returns:

A new Matrix, with the same number of rows as the original, and columns equal to the sum of the columns of matrices. The new Matrix’s bitwidth is the max of the bitwidths of all matrices.

pyrtl.rtllib.matrix.list_to_int(matrix, n_bits)[source]

Convert a Python matrix (a list of lists) into an int.

Integers that are signed will automatically be converted to their two’s complement form.

This function is helpful for turning a pure Python list of lists into a very large integer suitable for creating a Const that can be used as Matrix.__init__()’s value argument, or for passing into a Simulation.step()’s provided_inputs for an Input wire.

For example, calling list_to_int([3, 5], [7, 9], n_bits=4) produces 13689, which in binary looks like:

0011 0101 0111 1001

Note how the elements of the list of lists were added, 4 bits at a time, in row order, such that the element at row 0, column 0 is in the most significant 4 bits, and the element at row 1, column 1 is in the least significant 4 bits.

Here’s an example of using it in Simulation:

a_vals = [[0, 1], [2, 3]]
b_vals = [[2, 4, 6], [8, 10, 12]]

a_in = pyrtl.Input(name="a_in", bitwidth=2 * 2 * 4)
b_in = pyrtl.Input(name="b_in", bitwidth=2 * 3 * 4)
a = Matrix.Matrix(rows=2, columns=2, bits=4, value=a_in)
b = Matrix.Matrix(rows=2, columns=3, bits=4, value=b_in)
...

sim = pyrtl.Simulation()
sim.step({
    'a_in': Matrix.list_to_int(a_vals, n_bits=a.bits)
    'b_in': Matrix.list_to_int(b_vals, n_bits=b.bits)
})
Parameters:
  • matrix (list[list[int]]) – A list of lists of ints representing the data in a Matrix.

  • n_bits (int) – The number of bits used to represent each element. If an element doesn’t fit in n_bits, its most significant bits will be truncated.

Return type:

int

Returns:

An int with bitwidth N * n_bits, containing the elements of matrix, where N is the number of elements in matrix.

pyrtl.rtllib.matrix.matrix_wv_to_list(matrix_wv, rows, columns, bits)[source]

Convert a WireVector representing a Matrix into a Python list of lists.

During Simulation, this is useful when printing the value of an inspected wire that represents a Matrix.

Example:

m = Matrix.Matrix(rows=2, columns=3, bits=4,
                  values=[[1, 2, 3],
                          [4, 5, 6]])

output = Output(name="output")
output <<= m.to_wirevector()

sim = Simulation()
sim.step()

raw_matrix = Matrix.matrix_wv_to_list(
    sim.inspect("output"), m.rows, m.columns, m.bits)
print(raw_matrix)

# Produces:
# [[1, 2, 3], [4, 5, 6]]
Parameters:
  • matrix_wv (WireVector) – Result of calling Matrix.to_wirevector().

  • rows (int) – Number of rows in the matrix.

  • columns (int) – Number of columns in the matrix.

  • bits (int) – Number of bits for each element in the matrix.

Return type:

list[list[int]]

Returns:

A Python list of lists.

pyrtl.rtllib.matrix.max(matrix, axis=None, bits=None)[source]

Returns the maximum value in a Matrix.

This performs a reduction, taking the maximum over the specified axis.

Parameters:
  • matrix (Matrix | WireVector) – The matrix to take the mimimum of. If it is a WireVector, it will return itself.

  • axis (int | None, default: None) – The axis to perform the maximum on. None refers to max of all elements. 0 is max of columns. 1 is max of rows. Defaults to None.

  • bits (int | None, default: None) – The bits per element of the max. Defaults to matrix.bits.

Return type:

Matrix | WireVector

Returns:

A WireVector or Matrix representing the max value.

pyrtl.rtllib.matrix.min(matrix, axis=None, bits=None)[source]

Returns the minimum value in a Matrix.

This performs a reduction, taking the minimum over the specified axis.

Parameters:
  • matrix (Matrix | WireVector) – The matrix to take the mimimum of. If it is a WireVector, it will return itself.

  • axis (int | None, default: None) – The axis to perform the minimum on. None refers to min of all elements. 0 is min of columns. 1 is min of rows. Defaults to None.

  • bits (int | None, default: None) – The bits per element of the min. Defaults to matrix.bits.

Return type:

Matrix | WireVector

Returns:

A WireVector or Matrix representing the min value.

pyrtl.rtllib.matrix.sum(matrix, axis=None, bits=None)[source]

Returns the sum of values in a Matrix across axis.

This performs a reduction, summing over the specified axis.

Parameters:
  • matrix (Matrix | WireVector) – The matrix to perform sum operation on. If it is a WireVector, it will return itself.

  • axis (int | None, default: None) – The axis to perform the operation on. None refers to sum of all elements. 0 is sum of column. 1 is sum of rows. Defaults to None.

  • bits (int | None, default: None) – The bits per element of the sum. Defaults to matrix.bits.

Return type:

Matrix | WireVector

Returns:

A WireVector or Matrix representing the sum.

pyrtl.rtllib.matrix.vstack(*matrices)[source]

Stack matrices in sequence vertically (row-wise).

All the matrices must have the same number of columns and the same signed value.

For example:

m1 = Matrix(rows=2, columns=3, bits=5,
            value=[[1, 2, 3],
                   [4, 5, 6]])
m2 = Matrix(rows=1, columns=3, bits=10,
            value=[[7, 8, 9]])
m3 = vstack(m1, m2)

m3 will look like:

[[1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]]

And m3.bits will be 10.

Parameters:

matrices (Matrix) – Matrices to concatenate together vertically

Return type:

Matrix

Returns:

A new Matrix, with the same number of columns as the original, and rows equal to the sum of the rows of matricies. The new Matrix’s bitwidth is the max of the bitwidths of all matrices.

Pseudo-Random Numbers

pyrtl.rtllib.prngs.csprng_trivium(bitwidth, load, req, seed=None, bits_per_cycle=64)[source]

Builds a cyptographically secure PRNG using the Trivium stream cipher.

This PRNG uses Trivium’s key stream as its random bits output. Both seed and the key stream are MSB first (the earliest bit is stored at the MSB). Trivium has a seed initialization stage that discards the first weak 1152 output bits after each loading. Generation stage can take multiple cycles as well depending on the given bitwidth and bits_per_cycle. Has smaller gate area and faster speed than AES-CTR and any other stream cipher. Passes all known statistical tests. Can be used to generate encryption keys or IVs. Designed to securely generate up to 2 ** 64 bits. If more than 2 ** 64 bits are needed, must reseed after each generation of 2 ** 64 bits.

Trivium specifications: http://www.ecrypt.eu.org/stream/ciphers/trivium/trivium.pdf

See also the eSTREAM portfolio page: http://www.ecrypt.eu.org/stream/e2-trivium.html

Example:

load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req')
ready, rand = pyrtl.Output(1, 'ready'), pyrtl.Output(128, 'rand')

ready_out, rand_out = prngs.csprng_trivium(128, load, req)
ready <<= ready_out
rand <<= rand_out

sim = pyrtl.Simulation()
# Seed only in the first cycle.
sim.step({'load': 1, 'req': 0})
while sim.value[ready] == 0:  # or loop 19 cycles
    sim.step({'load': 0, 'req': 0})

sim.step({'load': 0, 'req': 1})
while sim.value[ready] == 0:  # or loop 2 cycles
    sim.step({'load': 0, 'req': 0})

print(sim.inspect(rand))
sim.tracer.render_trace(symbol_len=45, segment_size=5)
Parameters:
  • bitwidth (int) – The desired bitwidth of the random number.

  • load (WireVector) – One bit signal to load the seed into the PRNG

  • req (WireVector) – One bit signal to request a random number.

  • seed (WireVector, default: None) – 160 bits WireVector (80 bits key + 80 bits IV), defaults to None (self-seeding). Refrain from self-seeding if reseeding at run time is needed.

  • bits_per_cycle (int, default: 64) – The number of output bits to generate in parallel each cycle, up to 64 bits. Must be a power of two, so bits_per_cycle must be one of 1, 2, 4, 8, 16, 32, or 64.

Return type:

tuple[WireVector, Register]

Returns:

(ready, rand), where ready is a one bit signal indicating that the random number has been produced or the seed has been initialized, and rand is a Register containing the random number with the given bitwidth.

pyrtl.rtllib.prngs.prng_lfsr(bitwidth, load, req, seed=None)[source]

Builds a single-cycle PRNG using a 127 bits Fibonacci LFSR.

A very fast and compact PRNG that generates a random number using only one clock cycle. Has a period of 2**127 - 1. Its linearity makes it a bit statistically weak, but it should be good enough for any noncryptographic purpose like test pattern generation.

Example:

load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req')
rand = pyrtl.Output(64, 'rand')

rand <<= prngs.prng_lfsr(64, load, req)

sim = pyrtl.Simulation()
sim.step({'load': 1, 'req': 0}) # seed once at the beginning
sim.step({'load': 0, 'req': 1})
sim.step({'load': 0, 'req': 0})
print(sim.inspect(rand))
sim.tracer.render_trace(symbol_len=40, segment_size=1)

Example with explicit seeding:

seed =  pyrtl.Input(127, 'seed')
load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req')
rand = pyrtl.Output(32, 'rand')

rand <<= prngs.prng_lfsr(32, load, req, seed)

sim = pyrtl.Simulation()
sim.step({'load': 1, 'req': 0, 'seed': 0x102030405060708090a0b0c0d0e0f010})
sim.step({'load': 0, 'req': 1, 'seed': 0x102030405060708090a0b0c0d0e0f010})
sim.step({'load': 0, 'req': 0, 'seed': 0x102030405060708090a0b0c0d0e0f010})
print(sim.inspect(rand))
sim.tracer.render_trace(symbol_len=40, segment_size=1)
Parameters:
  • bitwidth (int) – The desired bitwidth of the random number.

  • load (WireVector) – One bit signal to load the seed into the PRNG.

  • req (WireVector) – One bit signal to request a random number.

  • seed (WireVector, default: None) – 127 bits WireVector, defaults to None (self-seeding). Refrain from self-seeding if reseeding at run time is required.

Return type:

Register

Returns:

Register containing the random number with the given bitwidth.

pyrtl.rtllib.prngs.prng_xoroshiro128(bitwidth, load, req, seed=None)[source]

Builds a PRNG using the Xoroshiro128+ algorithm in hardware.

An efficient noncryptographic PRNG, has much smaller area than csprng_trivium(). But it does require a 64-bit adder to compute the output, so it is a bit slower. Has a period of 2 ** 128 - 1. Passes most statistical tests. Outputs a 64-bit random word each cycle, takes multiple cycles if more than 64 bits are requested, and MSBs of the random words are returned if the bitwidth is not a multiple of 64.

See also http://xoroshiro.di.unimi.it/

Example:

load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req')
ready, rand = pyrtl.Output(1, 'ready'), pyrtl.Output(128, 'rand')

ready_out, rand_out = prngs.prng_xoroshiro128(128, load, req)
ready <<= ready_out
rand <<= rand_out

sim = pyrtl.Simulation()
sim.step({'load': 1, 'req': 0})  # seed once at the beginning
sim.step({'load': 0, 'req': 1})
while sim.value[ready] == 0:  # or loop 2 cycles
    sim.step({'load': 0, 'req': 0})

print(sim.inspect(rand))
sim.tracer.render_trace(symbol_len=40, segment_size=1)
Parameters:
  • bitwidth (int) – The desired bitwidth of the random number.

  • load (WireVector) – One bit signal to load the seed into the PRNG.

  • req (WireVector) – One bit signal to request a random number.

  • seed (WireVector, default: None) – 128 bits WireVector, defaults to None (self-seeding). Refrain from self-seeding if reseeding at run time is required.

Return type:

tuple[WireVector, Register]

Returns:

(ready, rand), where ready is a one bit signal indicating that the random number has been produced, and rand is a Register containing the random number with the given bitwidth.

AES-128

class pyrtl.rtllib.aes.AES[source]

A class for building a PyRTL AES circuit.

Currently this class only supports 128 bit AES encryption/decryption.

Example:

import pyrtl
from pyrtl.rtllib.aes import AES

aes = AES()
plaintext = pyrtl.Input(bitwidth=128, name='aes_plaintext')
key = pyrtl.Input(bitwidth=128, name='aes_key')
aes_ciphertext = pyrtl.Output(bitwidth=128, name='aes_ciphertext')
reset = pyrtl.Input(1, name='reset')
ready = pyrtl.Output(1, name='ready')

ready_out, aes_cipher = aes.encrypt_state_m(plaintext, key, reset)
ready <<= ready_out
aes_ciphertext <<= aes_cipher

sim = pyrtl.Simulation()
sim.step ({
    'aes_plaintext': 0x00112233445566778899aabbccddeeff,
    'aes_key': 0x000102030405060708090a0b0c0d0e0f,
    'reset': 1
})
for cycle in range(1,10):
    sim.step ({
        'aes_plaintext': 0x00112233445566778899aabbccddeeff,
        'aes_key': 0x000102030405060708090a0b0c0d0e0f,
        'reset': 0
    })
sim.tracer.render_trace(symbol_len=40, segment_size=1)
decryption(ciphertext, key)[source]

Builds a single cycle AES Decryption circuit.

Parameters:
  • ciphertext (WireVector) – Data to decrypt.

  • key (WireVector) – AES key to use to encrypt (AES is symmetric).

Return type:

WireVector

Returns:

A WireVector containing the plaintext.

decryption_statem(ciphertext_in, key_in, reset)[source]

Builds a multiple cycle AES Decryption state machine circuit.

Parameters:
  • ciphertext – Data to decrypt.

  • key – AES key to use to encrypt (AES is symmetric).

  • reset (WireVector) – a one bit signal telling the state machine to reset and accept the current plaintext and key.

Return type:

tuple[WireVector, WireVector]

Returns:

(ready, plain_text): ready is a one bit signal showing that the decryption result (plain_text) has been calculated.

encrypt_state_m(plaintext_in, key_in, reset)[source]

Builds a multiple cycle AES Encryption state machine circuit.

Parameters:
  • plaintext – Text to encrypt.

  • key – AES key to use to encrypt.

  • reset (WireVector) – a one bit signal telling the state machine to reset and accept the current plaintext and key.

Return type:

tuple[WireVector, WireVector]

Returns:

(ready, cipher_text): ready is a one bit signal showing that the encryption result (cipher_text) has been calculated.

encryption(plaintext, key)[source]

Builds a single cycle AES Encryption circuit.

Parameters:
Return type:

WireVector

Returns:

A WireVector containing the ciphertext.

Testing Utilities

pyrtl.rtllib.testingutils.an_input_and_vals(bitwidth, test_vals=20, name='', random_dist=<function uniform_dist>)[source]

Generate an Input wire and random test values for it.

Parameters:
  • bitwidth (int) – The bitwidth of the random values to generate.

  • test_vals (int, default: 20) – Number of random values to generate per Input.

  • name (str, default: '') – Name of the returned Input.

Return type:

tuple[Input, list[int]]

Returns:

(input_wire, test_values), where input_wire is an Input, and test_values is a list of random test values for input_wire.

pyrtl.rtllib.testingutils.make_consts(num_wires, max_bitwidth=None, exact_bitwidth=None, random_dist=<function inverse_power_dist>)[source]

Generate random Const values.

Parameters:
  • num_wires (int) – Number of Consts to generate.

  • max_bitwidth (int | None, default: None) – If specified, generate Consts with random bitwidth in the range [1, max_bitwidth).

  • exact_bitwidth (int | None, default: None) – If specified, generate Consts with bitwidth exact_bitwidth.

  • random_dist (Callable[[int], int], default: <function inverse_power_dist at 0x722a96258ca0>) – Function to generate the random Const values.

Return type:

tuple[list[Const], list[int]]

Returns:

(consts, values), where consts is a list of Consts, and values is a list of each const’s value.

pyrtl.rtllib.testingutils.make_inputs_and_values(num_wires, max_bitwidth=None, exact_bitwidth=None, dist=<function uniform_dist>, test_vals=20)[source]

Generates multiple Input wires and their test values.

The generated list of test values is a list of lists. The inner lists each represent the values of a single Input wire, for each Simulation cycle.

Parameters:
  • num_wires (int) – Number of Inputs to generate.

  • max_bitwidth (int | None, default: None) – If specified, generate Inputs with random bitwidth in the range [1, max_bitwidth).

  • exact_bitwidth (int | None, default: None) – If specified, generate Inputs with bitwidth exact_bitwidth.

  • dist (Callable[[int], int], default: <function uniform_dist at 0x722a95d740f0>) – Function to generate the random Input values.

  • test_vals (int, default: 20) – Number of random Input values to generate.

Return type:

tuple[list[Input], list[list[int]]]

Returns:

(inputs, values), where inputs is a list of Inputs, and values is a list of values for each input.