RTL Library¶
Useful circuits, functions, and utilities.
Multiplexers¶
Basic multiplexers are defined in PyRTL’s core library, see:
select()for a multiplexer that selects between two options.mux()for a multiplexer that selects between an arbitrary number of options.Conditional Assignment for a more readable alternative to nested
selectsandmuxes.
The functions below provide more complex alternatives.
- pyrtl.rtllib.muxes.SparseDefault = 'default'¶
A special key for
sparse_mux()’svalsdictthat 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)is8because2 ** 3 == 8, andoutputs[5]is1because the output index5matches the input value.See
binary_to_one_hot(), which performs a similar operation.Warning
demuxcan create a very large number ofWireVectors. Use with caution.- Parameters:
select (
WireVector) – The value to demultiplex.- Return type:
- Returns:
A tuple of 1-bit wires, where each wire indicates if the value of
selectequals the wire’s index in the tuple. The tuple has length2 ** select.bitwidth.
- pyrtl.rtllib.muxes.prioritized_mux(selects, vals)[source]¶
Returns the value in the first wire for which its
selectbit is1If none of the
selectsare1, the lastvalis 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:
selects (
list[WireVector]) – A list ofWireVectorssignaling whether a wire should be chosen.vals (
list[WireVector]) – Values to return when the correspondingselectvalue is1.
- Return type:
- Returns:
The selected value.
- pyrtl.rtllib.muxes.sparse_mux(sel, vals)[source]¶
Mux that avoids instantiating unnecessary
mux_2swhen possible.sparse_muxsupports 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 inputvalsto output.vals (
dict[int,WireVector]) –dictof mux input values. If the special keySparseDefaultexists, it specifies thesparse_mux’s default value.
- Return type:
- Returns:
The
WireVectorselected fromvalsbysel.
Adders¶
Basic integer addition is defined in PyRTL’s core library, see:
WireVector.__add__()for unsigned integer addition.signed_add()for signed integer addition.
The functions below provide more complex alternatives.
- pyrtl.rtllib.adders.carrysave_adder(a, b, c, final_adder=<function ripple_add>)[source]¶
Adds three
WireVectorsup 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) – AWireVectorto add up. Bitwidths don’t need to match.b (
WireVector) – AWireVectorto add up. Bitwidths don’t need to match.c (
WireVector) – AWireVectorto 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:
- Returns:
A
WireVectorwith 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 askogge_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:
a (
WireVector) – AWireVectorto add up. Bitwidths don’t need to match.b (
WireVector) – AWireVectorto add up. Bitwidths don’t need to match.cin (
WireVector, default:0) – A 1-bit carry-inWireVector.la_unit_len (
int, default:4) – The length of input that every unit processes.
- Return type:
- Returns:
A
WireVectorrepresenting 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 bitwidthWireVectors.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:
- Returns:
WireVectorwithbitwidthresult_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_adderis 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:
wires_to_add (
list[WireVector]) – AlistofWireVectorsto add.reducer (
Callable, default:<function wallace_reducer at 0x722a96585430>) – The tree reducer to use. Seewallace_reducer()anddada_reducer().final_adder (
Callable, default:<function kogge_stone at 0x722a96584bf0>) – The two value adder to use at the end.
- Returns:
A
WireVectorwith the result of the addition.
- 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:
a (
WireVector) – AWireVectorto add up. Bitwidths don’t need to match.b (
WireVector) – AWireVectorto add up. Bitwidths don’t need to match.cin (
WireVector|int|str|bool, default:0) – An optional 1-bit carry-inWireVector. Can be any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
A
WireVectorrepresenting the output of the adder.
- 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 bitwidthWireVectors.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:
- Returns:
WireVectorwithbitwidthresult_bitwidth.
Multipliers¶
Basic integer multiplication is defined in PyRTL’s core library, see:
WireVector.__mul__()for unsigned integer multiplication.signed_mult()for signed integer multiplication.
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 spacesRegisteris to be shifted per clock cycle. Cannot be greater than the length ofAorB.start (
WireVector) – One-bit start signal.
- Return type:
- Returns:
Registercontaining the product, and a 1-bitdonesignal.
- 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
WireVectorstogether and adds a thirdWireVectorto 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 toTrue, so if you want the smallest set of wires in the future, specify this asFalse.reducer (
Callable, default:<function wallace_reducer at 0x722a96585430>) – (advanced) The tree reducer to use. Seedada_reducer()andwallace_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:
- 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 theadd_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]]) – EitherNone(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]) – EitherNone(if there are no individual items to add other than themult_pairsproducts), 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 toTrue, so if you want the smallest set of wires in the future, specify this asFalse.reducer (
Callable, default:<function wallace_reducer at 0x722a96585430>) – (advanced) The tree reducer to use. Seedada_reducer()andwallace_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:
- 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:
- Returns:
A
Registercontaining the product, and a 1-bitdonesignal.
- 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 awallace_reducer()or adada_reducer()determines whether thetree_multiplieris 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:
- 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) –WireVectorwith the value to shift.bit_in (
WireVector|int|str|bool) – A 1-bitWireVectorrepresenting the value to shift in.direction (
WireVector|int|str|bool) – A one bitWireVectorrepresenting the shift direction (0= shift right,1= shift left). Ifdirectionis constant, useDirectionto improve code readability (direction=Direction.RIGHTinstead ofdirection=0).shift_dist (
WireVector) –WireVectorrepresenting the amount to shift.wrap_around (default:
0) – **currently not implemented**
- Return type:
- 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:
- 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:
- Return type:
- Returns:
WireVectororMatrixcontaining 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 perWireVectormatrix 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 theMatrixto. If aWireVector, must be of sizerows * columns * bits. If alist, must haverowsrows andcolumnscolumns, and every element must be representable with abitwidthofbits. IfNone, the matrix initializes to 0.max_bits (
int, default:64) – The maximum number of bits eachWireVectorelement can grow to. Operations like multiplication and addition can produce matrices with morebits, but results will be limited tomax_bits.
- __matmul__(other)[source]¶
Performs the inplace matrix multiplication operation.
Invoked with
a @ b.
- __mul__(other)[source]¶
Perform the elementwise or scalar multiplication operation.
Invoked with
a * b.- Parameters:
other (
Matrix|WireVector) – TheMatrixor scalar to multiply.- Return type:
- Returns:
A
Matrixobject with the product.
- __pow__(power)[source]¶
Performs the matrix power operation.
Invoked with
a ** b.This performs a chain of matrix multiplications, where
selfis matrix multiplied byself,powertimes.
- __reversed__()[source]¶
Invoked with the
reversed()builtin.- Return type:
- Returns:
A
Matrixwith 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.
- property bits: int¶
The number of bits for each matrix element.
Reducing the number of
bitswilltruncate()the most significant bits of each matrix element.
- copy()[source]¶
Constructs a copy of the
Matrix.The returned copy will have new set of
WireVectorsfor its elements, but each newWireVectorwill be wired to the correspondingWireVectorin the originalMatrix.- Return type:
- Returns:
A new instance of
Matrixthat indirectly refers to the same underlyingWireVectorsasself.
- flatten(order='C')[source]¶
Flatten the
Matrixinto a single row.- Parameters:
order (
str, default:'C') –Cmeans row-major order (C-style), andFmeans column-major order (Fortran-style)- Returns:
A copy of the
Matrixflattened into a row vector.
- put(ind, v, mode='raise')[source]¶
Replace specified elements of the
Matrixwith valuesv.Note that the index
indis on the flattened matrix.
- reshape(*newshape, order='C')[source]¶
Create a
Matrixof the given shape fromself.One shape dimension in
newshapecan be-1; in this case, the value for that dimension is inferred from the other given dimension (if any) and the number of elements inself.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. Ifnewshapeis a singleint, the new shape will be a 1-D row-vector of that length. Ifnewshapeis atuple, thetuplespecifies the new number of rows and columns.newshapecan also be varargs.order (
str, default:'C') –Cmeans to read from self using row-major order (C-style), andFmeans 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
Matrixin oneWireVector.This
concatenatesall theMatrix’s elements together, in row-major order.For example, a 2 x 1 matrix
[[wire_a, wire_b]]would becomepyrtl.concat(wire_a, wire_b).- Return type:
- Returns:
A concatenated
WireVectorcontaining all of theMatrix’s elements.
- 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) – TheMatrixto perform argmax operation on. If it is aWireVector, it will return itself.axis (
int|None, default:None) – The axis to perform the operation on.Nonerefers to argmax of all items.0is argmax of the columns.1is argmax of rows. Defaults toNone.bits (
int|None, default:None) – The bits per element of the argmax. Defaults tomatrix.bits.
- Return type:
- Returns:
A
WireVectororMatrixrepresenting the argmax value.
- pyrtl.rtllib.matrix.concatenate(matrices, axis=0)[source]¶
Join a sequence of
matricesalong an existingaxis.This function is just a wrapper around
hstack()andvstack().
- pyrtl.rtllib.matrix.dot(first, second)[source]¶
Performs the dot product on two matrices.
Specifically, the dot product on two matrices is:
If either
firstorsecondareWireVectors, or have both rows and columns equal to 1,dotis equivalent toMatrix.__mul__()If
firstandsecondare both arrays (have rows or columns equal to 1),dotis the inner product of the vectors.Otherwise
dotisMatrix.__matmul__()betweenfirstandsecond.
Note
Row vectors and column vectors are both treated as arrays.
- pyrtl.rtllib.matrix.hstack(*matrices)[source]¶
Stack
matricesin sequence horizontally (column-wise).All the
matricesmust have the same number of rows and the samesignedvalue.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)
m3will look like:[[1, 2, 3, 17], [4, 5, 6, 23]]
And
m3.bitswill be10.
- pyrtl.rtllib.matrix.list_to_int(matrix, n_bits)[source]¶
Convert a Python matrix (a
listoflists) into anint.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
Constthat can be used asMatrix.__init__()’svalueargument, or for passing into aSimulation.step()’sprovided_inputsfor anInputwire.For example, calling
list_to_int([3, 5], [7, 9], n_bits=4)produces13689, 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:
- Return type:
- Returns:
An
intwith bitwidthN * n_bits, containing the elements ofmatrix, whereNis the number of elements inmatrix.
- pyrtl.rtllib.matrix.matrix_wv_to_list(matrix_wv, rows, columns, bits)[source]¶
Convert a
WireVectorrepresenting aMatrixinto a Python list of lists.During
Simulation, this is useful when printing the value of aninspectedwire that represents aMatrix.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 callingMatrix.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:
- 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 aWireVector, it will return itself.axis (
int|None, default:None) – The axis to perform the maximum on.Nonerefers to max of all elements.0is max of columns.1is max of rows. Defaults toNone.bits (
int|None, default:None) – The bits per element of the max. Defaults tomatrix.bits.
- Return type:
- Returns:
A
WireVectororMatrixrepresenting 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 aWireVector, it will return itself.axis (
int|None, default:None) – The axis to perform the minimum on.Nonerefers to min of all elements.0is min of columns.1is min of rows. Defaults toNone.bits (
int|None, default:None) – The bits per element of the min. Defaults tomatrix.bits.
- Return type:
- Returns:
A
WireVectororMatrixrepresenting the min value.
- pyrtl.rtllib.matrix.sum(matrix, axis=None, bits=None)[source]¶
Returns the sum of values in a
Matrixacrossaxis.This performs a reduction, summing over the specified
axis.- Parameters:
matrix (
Matrix|WireVector) – The matrix to perform sum operation on. If it is aWireVector, it will return itself.axis (
int|None, default:None) – The axis to perform the operation on.Nonerefers to sum of all elements.0is sum of column.1is sum of rows. Defaults toNone.bits (
int|None, default:None) – The bits per element of the sum. Defaults tomatrix.bits.
- Return type:
- Returns:
A
WireVectororMatrixrepresenting the sum.
- pyrtl.rtllib.matrix.vstack(*matrices)[source]¶
Stack matrices in sequence vertically (row-wise).
All the
matricesmust have the same number of columns and the samesignedvalue.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)
m3will look like:[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
And
m3.bitswill be10.
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
seedand 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 andbits_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 to2 ** 64bits. If more than2 ** 64bits are needed, must reseed after each generation of2 ** 64bits.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 PRNGreq (
WireVector) – One bit signal to request a random number.seed (
WireVector, default:None) – 160 bitsWireVector(80 bits key + 80 bits IV), defaults toNone(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 to64bits. Must be a power of two, sobits_per_cyclemust be one of1,2,4,8,16,32, or64.
- Return type:
- Returns:
(ready, rand), wherereadyis a one bit signal indicating that the random number has been produced or the seed has been initialized, andrandis aRegistercontaining the random number with the givenbitwidth.
- 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 bitsWireVector, defaults toNone(self-seeding). Refrain from self-seeding if reseeding at run time is required.
- Return type:
- Returns:
Registercontaining the random number with the givenbitwidth.
- 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 of2 ** 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 bitsWireVector, defaults toNone(self-seeding). Refrain from self-seeding if reseeding at run time is required.
- Return type:
- Returns:
(ready, rand), wherereadyis a one bit signal indicating that the random number has been produced, andrandis aRegistercontaining the random number with the givenbitwidth.
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:
- Returns:
A
WireVectorcontaining 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:
- Returns:
(ready, plain_text):readyis 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 currentplaintextandkey.
- Return type:
- Returns:
(ready, cipher_text):readyis 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:
plaintext (
WireVector) – Text to encrypt.key (
WireVector) – AES key to use to encrypt.
- Return type:
- Returns:
A
WireVectorcontaining the ciphertext.
Testing Utilities¶
- pyrtl.rtllib.testingutils.an_input_and_vals(bitwidth, test_vals=20, name='', random_dist=<function uniform_dist>)[source]¶
Generate an
Inputwire and random test values for it.- Parameters:
- Return type:
- Returns:
(input_wire, test_values), whereinput_wireis anInput, andtest_valuesis a list of random test values forinput_wire.
- pyrtl.rtllib.testingutils.make_consts(num_wires, max_bitwidth=None, exact_bitwidth=None, random_dist=<function inverse_power_dist>)[source]¶
Generate random
Constvalues.- Parameters:
max_bitwidth (
int|None, default:None) – If specified, generateConstswith randombitwidthin the range[1, max_bitwidth).exact_bitwidth (
int|None, default:None) – If specified, generateConstswithbitwidthexact_bitwidth.random_dist (
Callable[[int],int], default:<function inverse_power_dist at 0x722a96258ca0>) – Function to generate the randomConstvalues.
- Return type:
- Returns:
(consts, values), whereconstsis alistofConsts, andvaluesis a list of eachconst’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
Inputwires 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
Inputwire, for eachSimulationcycle.- Parameters:
max_bitwidth (
int|None, default:None) – If specified, generateInputswith randombitwidthin the range[1, max_bitwidth).exact_bitwidth (
int|None, default:None) – If specified, generateInputswithbitwidthexact_bitwidth.dist (
Callable[[int],int], default:<function uniform_dist at 0x722a95d740f0>) – Function to generate the randomInputvalues.test_vals (
int, default:20) – Number of randomInputvalues to generate.
- Return type:
- Returns:
(inputs, values), whereinputsis alistofInputs, andvaluesis a list of values for eachinput.