Helper Functions

Cutting and Extending WireVectors

The functions below provide ways of combining, slicing, and extending WireVectors in ways that are often useful in hardware design. The functions below extend those member functions of the WireVector class itself (which provides support for the Python builtin len(), slicing e.g. wire[3:6], zero_extended(), sign_extended(), and many operators such as addition and multiplication).

pyrtl.concat(*args)[source]

Concatenates multiple WireVectors into a single WireVector.

Concatenates any number of arguments. The right-most argument is the least significant bits of the result.

Note

If you have a list of arguments to concat together, you probably want index 0 to be the least significant bit. If so, and you unpack the list into concat’s args, the result will be backwards. The function concat_list() is provided specifically for that case.

Note

Consider using wire_struct() or wire_matrix() instead, which helps with consistently disassembling, naming, and reassembling fields.

Example that concatenates two bytes into a 16-bit output:

>>> msb = pyrtl.Input(name="msb", bitwidth=8)
>>> lsb = pyrtl.Input(name="lsb", bitwidth=8)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.concat(msb, lsb)
>>> output.bitwidth
16

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"msb": 0xab, "lsb": 0xcd})
>>> hex(sim.inspect("output"))
'0xabcd'
Parameters:

args (WireVector | int | str | bool) – Inputs to concatenate, with the most significant bits first. Each input can be a WireVector, or any type that can be coerced to WireVector by as_wires().

Return type:

WireVector

Returns:

WireVector with bitwidth equal to the sum of all argsbitwidths.

pyrtl.concat_list(wire_list)[source]

Concatenates a list of WireVectors into a single WireVector.

This take a list of WireVectors and concats them all into a single WireVector, with the element at index 0 serving as the least significant bits. This is useful when you have a variable number of WireVectors to concatenate, otherwise concat() is prefered.

Note

Consider using wire_struct() or wire_matrix() instead, which helps with consistently disassembling, naming, and reassembling fields.

Example that concatenates two bytes into a 16-bit output:

>>> msb = pyrtl.Input(name="msb", bitwidth=8)
>>> lsb = pyrtl.Input(name="lsb", bitwidth=8)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.concat_list([lsb, msb])
>>> output.bitwidth
16

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"msb": 0xab, "lsb": 0xcd})
>>> hex(sim.inspect("output"))
'0xabcd'
Parameters:

wire_list (list[WireVector | int | str | bool]) – List of inputs to concatenate. Each input can be a WireVector, or any type that can be coerced to WireVector by as_wires().

Return type:

WireVector

Returns:

WireVector with bitwidth equal to the sum of all wire_list bitwidths.

pyrtl.match_bitwidth(*args, signed=False)[source]

Matches multiple WireVector bitwidths via zero- or sign-extension.

WireVectors with shorter bitwidths will be to match the longest bitwidth in args. WireVectors will be sign_extended() or zero_extended(), depending on signed.

Example with sign-extension:

>>> a = pyrtl.Const(-1, name="a_short", signed=True, bitwidth=2)
>>> b = pyrtl.Const(-3, name="b", signed=True, bitwidth=4)

>>> a, b = match_bitwidth(a, b, signed=True)
>>> a.name = "a_long"
>>> a.bitwidth, b.bitwidth
(4, 4)

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> bin(sim.inspect("b"))
'0b1101'
>>> bin(sim.inspect("a_short"))
'0b11'
>>> bin(sim.inspect("a_long"))
'0b1111'
Parameters:
Return type:

tuple[WireVector]

Returns:

tuple of WireVectors, in the same order they appeared in args, all with bitwidth equal to the longest bitwidth in args.

pyrtl.truncate(wirevector_or_integer, bitwidth)[source]

Returns a WireVector or integer truncated to the specified bitwidth.

Truncation removes the most significant bits of wirevector_or_integer, leaving a result that is only bitwidth bits wide. For ints this is performed with a simple bitmask of size bitwidth, and returns an int. For WireVectors the function calls WireVector.truncate() and returns a WireVector with the specified bitwidth.

Examples:

>>> truncate(0b101_001, bitwidth=3)
1

>>> bin(truncate(0b111_101, bitwidth=3))
'0b101'

>>> # -1 is 0b1111111... with the number of 1-bits equal to the bitwidth. Python
>>> # ints are arbitrary-precision, so this can produce any number of 1-bits.
>>> bin(truncate(-1, bitwidth=3))
'0b111'

>>> input = pyrtl.Input(name="input", bitwidth=8)

>>> output = truncate(input, bitwidth=4)
>>> output.name = "output"
>>> output.bitwidth
4

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0xab})
>>> hex(sim.inspect("output"))
'0xb'
Parameters:
  • wirevector_or_integer (WireVector | int) – A WireVector or int to truncate.

  • bitwidth (int) – The length to which wirevector_or_integer should be truncated.

Return type:

WireVector | int

Returns:

A truncated WireVector or int, depending on the input type.

pyrtl.chop(w, *segment_widths)[source]

Returns a list of WireVectors, each a slice of the original w.

This function chops a WireVector into a set of smaller WireVectors of different lengths. It is most useful when multiple “fields” are contained with a single WireVector, for example when breaking apart an instruction.

For example, if you wish to break apart a 32-bit MIPS I-type (Immediate) instruction you know it has an 6-bit opcode, 2 5-bit operands, and 16-bit offset. You could take each of those slices in absolute terms: offset=instr[0:16], rt=instr[16:21] and so on, but then you have to do the arithmetic yourself. With this function you can do all the fields at once which can be seen in the examples below.

As a check, chop will throw an error if the sum of the lengths of the fields given is not the same as the length of the WireVector to chop. Note also that chop assumes that the “rightmost” arguments are the least signficant bits (just like concat()) which is normal for hardware functions but makes the list order a little counter intuitive.

Note

Consider using wire_struct() or wire_matrix() instead, which helps with consistently disassembling, naming, and reassembling fields.

Example:

>>> input = pyrtl.Input(name="input", bitwidth=12)

>>> high, middle, low = pyrtl.chop(input, 4, 4, 4)
>>> high.name = "high"
>>> middle.name = "middle"
>>> low.name = "low"

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0xabc})

>>> hex(sim.inspect("high"))
'0xa'
>>> hex(sim.inspect("middle"))
'0xb'
>>> hex(sim.inspect("low"))
'0xc'
Parameters:
  • w (WireVector) – The WireVector to be chopped up into segments

  • segment_widths (int) – Additional arguments are integers which are bitwidths

Return type:

list[WireVector]

Returns:

A list of WireVectors each with a proper segment width.

pyrtl.wire_struct(wire_struct_spec)[source]

Decorator that assigns names to WireVector slices.

@wire_struct assigns names to non-overlapping WireVector slices. Suppose we have an 8-bit wide WireVector called byte. We can refer to all 8 bits with the name byte, but @wire_struct lets us refer to slices by name, for example we could name the high 4 bits byte.high and the low 4 bits byte.low. Without @wire_struct, we would refer to these slices as byte[4:8] and byte[0:4], which are prone to off-by-one errors and harder to read.

Note

See example1.2-wire-struct.py for additional examples.

The example Byte @wire_struct can be defined as:

>>> @wire_struct
... class Byte:
...     high: 4
...     low: 4

Construction

Once a @wire_struct class is defined, it can be instantiated by providing drivers for all of its wires. This can be done in two ways:

  1. Provide a driver for each component wire, for example:

    >>> byte = Byte(high=0xA, low=0xB)
    

    Note how the component names (high, low) are used as keyword args for the constructor. Drivers must be provided for all components.

  2. Provide a driver for the entire @wire_struct, for example:

    >>> byte = Byte(Byte=0xAB)
    

    Note how the class name (Byte) is used as a keyword arg for the constructor.

Accessing Slices

After instantiating a @wire_struct, the instance functions as a WireVector containing all the wires. For example, byte functions as a WireVector with bitwidth 8:

>>> byte = Byte(Byte=0xAB)
>>> byte.bitwidth
8

The named slice can be accessed through the . operator (__getattr__), for example byte.high and byte.low, which both function as WireVector with bitwidth 4:

>>> byte = Byte(Byte=0xAB)
>>> byte.high.bitwidth
4
>>> byte.low.bitwidth
4

Both the instance and the slices are first-class WireVector, so they can be manipulated with all the usual PyRTL operators.

len() returns the number of components in the @wire_struct:

>>> byte = Byte(Byte=0xAB)
>>> len(byte)
2

Naming

A @wire_struct can be assigned a name in the usual way:

>>> byte = Byte(name="my_byte", high=0xA, low=0xB)

When a @wire_struct is assigned a name (my_byte), its components will be assigned dotted names (my_byte.high, my_byte.low):

>>> byte.high.name
'my_byte.high'
>>> byte.low.name
'my_byte.low'

Warning

All @wire_struct names are only set during construction. You can later rename a @wire_struct or its components, but those changes are local, and will not propagate to other @wire_struct components.

Composition

@wire_struct can be composed with itself, and with wire_matrix(). For example, we can define a Pixel that contains three Bytes:

@pyrtl.wire_struct
class Pixel:
    red: Byte
    green: Byte
    blue: Byte

Drivers must be specified for all components, but they can be specified at any level. All these examples construct an equivalent @wire_struct:

pixel = Pixel(Pixel=0xABCDEF)
pixel = Pixel(red=0xAB, green=0xCD, blue=0xEF)
pixel = Pixel(red=Byte(high=0xA, low=0xB), green=0xCD, blue=0xEF)
pixel = Pixel(red=Byte(high=0xA, low=0xB),
              green=Byte(high=0xC, low=0xD),
              blue=0xEF)

Hierarchical @wire_struct components are accessed by composing . operators:

pixel
pixel.red
pixel.red.high
pixel.red.low
pixel.green
pixel.green.high
pixel.green.low
pixel.blue
pixel.blue.high
pixel.blue.low

@wire_struct can be composed with wire_matrix():

Word = pyrtl.wire_matrix(component_schema=8, size=4)

@pyrtl.wire_struct
class CacheLine:
    address: Word
    data: Word
    valid: 1

cache_line = CacheLine(address=0x01234567, data=0x89ABCDEF, valid=1)

Leaf-level components can be accessed by combining the . and [] operators, for example cache_line.address[3].

Types

You can change the type of a @wire_struct’s components to a WireVector subclass like Input or Output with the component_type constructor argument:

# Generates Outputs named ``output_byte.low`` and ``output_byte.high``.
>>> byte = Byte(name="output_byte", component_type=pyrtl.Output,
...             Byte=0xCD)

>>> isinstance(byte.high, pyrtl.Output)
True
>>> byte.high.name
'output_byte.high'

You can also change the type of the @wire_struct itself with the concatenated_type constructor argument:

# Generates an Input named ``input_byte``.
>>> input_byte = Byte(name="input_byte", concatenated_type=pyrtl.Input)

Note

No values are specified for input_byte because its value is not known until simulation time.

pyrtl.wire_matrix(component_schema, size)[source]

Returns a class that assigns numbered indices to WireVector slices.

wire_matrix assigns numbered indices to non-overlapping WireVector slices. wire_matrix is very similar to wire_struct(), so read wire_struct()’s documentation first.

Note

See example1.2-wire-struct.py for additional examples.

An example 32-bit Word wire_matrix, which represents a group of four bytes, can be defined as:

>>> Word = wire_matrix(component_schema=8, size=4)

Note

wire_matrix returns a class, like namedtuple().

Construction

Once a wire_matrix class is defined, it can be instantiated by providing drivers for all of its wires. This can be done in two ways:

# Provide a driver for each component, most significant bits first.
>>> word = Word(values=[0x89, 0xAB, 0xCD, 0xEF])

# Provide a driver for all components.
>>> word = Word(values=[0x89ABCDEF])

Note

When specifying drivers for each component, the most significant bits are specified first.

After instantiating a wire_matrix, regardless of how it was constructed, the instance functions as a WireVector containing all the wires, so word functions as a WireVector with bitwidth 32. The named slice can be accessed with square brackets (__getitem__), for example word[0] and word[3], which both function as WireVector with bitwidth 8. word[0] refers to the most significant byte, and word[3] refers to the least significant byte. Both the instance and the slices are first-class WireVector, so they can be manipulated with all the usual PyRTL operators.

Naming

A wire_matrix can be assigned a name in the usual way:

# The whole Word is named 'w', so the components will have names
# w[0], w[1], ...
>>> word = Word(name="w", values=[0x89, 0xAB, 0xCD, 0xEF])
>>> word[0].name
'w[0]'

Composition

wire_matrix can be composed with itself and wire_struct(). For example, we can define some multi-dimensional byte arrays:

Array1D = wire_matrix(component_schema=8, size=2)
Array2D = wire_matrix(component_schema=Array1D, size=2)

Drivers must be specified for all components, but they can be specified at any level. All these examples construct an equivalent wire_matrix:

array_2d = Array2D(values=[0x89AB, 0xCDEF])
array_2d = Array2D(values=[Array1D(values=[0x89, 0xAB]),
                           0xCDEF])
array_2d = Array2D(values=[Array1D(values=[0x89, 0xAB]),
                           Array1D(values=[0xCD, 0xEF])])

Accessing Slices

Hierarchical components are accessed by composing [] operators, for example:

print(array_2d[0][0].bitwidth)  # Prints 8.
print(array_2d[0][1].bitwidth)  # Prints 8.

When wire_matrix is composed with wire_struct(), components can be accessed by combining the [] and . operators:

@wire_struct
class Byte:
    high: 4
    low: 4
Array1D = wire_matrix(component_schema=Byte, size=2)
array_1d = Array1D(values=[0xAB, 0xCD])

print(array_1d[0].high.bitwidth)  # Prints 4.

len() returns the number of components in the wire_matrix:

print(len(array_1d))  # Prints '2'.

Types

You can change the type of a wire_matrix’s components with the component_type constructor argument:

# Generates Outputs named ``output_word[0]``, ``output_word[1]``, ...
>>> word = Word(name="output_word",
...             component_type=pyrtl.Output,
...             values=[0x89ABCDEF])

>>> isinstance(word[1], pyrtl.Output)
True
>>> word[1].name
'output_word[1]'

You can change the type of the wire_matrix itself with the concatenated_type cnstructor argument:

# Generates an Input named ``input_word``.
>>> word = Word(name="input_word", concatenated_type=pyrtl.Input)

Note

No values are specified for input_word because its value is not known until simulation time.

Coercion to WireVector

In PyRTL there is only one function in charge of coercing values into WireVectors, and that is as_wires(). This function is called in almost all helper functions and classes to manage the mixture of constants and WireVectors that naturally occur in hardware development.

See WireVector Coercion for examples and more details.

pyrtl.as_wires(val, bitwidth=None, truncating=True, block=None)[source]

Convert val to a WireVector.

val may be a WireVector, int (including IntEnum), str, or bool.

as_wires is mainly used to coerce values into WireVectors (for example, operations such as x + 1 where 1 needs to be converted to a Const WireVector). See WireVector Coercion. An example:

>>> def make_my_hardware(a, b):
...     a = as_wires(a)
...     b = as_wires(b)
...     return (a + b) & 0xf

>>> input = pyrtl.Input(name="input", bitwidth=8)
>>> output = pyrtl.Output(name="output", bitwidth=4)

>>> output <<= make_my_hardware(input, 7)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 20})
>>> sim.inspect("output")
11
>>> (20 + 7) % 16
11

In the example above, as_wires will convert the 7 to Const(7) but keep input unchanged.

Parameters:
Return type:

WireVector

Control Flow Hardware

pyrtl.mux(index, *mux_ins, default=None)[source]

Multiplexer returning a wire from mux_ins according to index.

index 0 corresponds to the first mux_in argument.

Note

If index is a 1-bit predicate (something that is True or False rather than an integer), it is clearer to use select(), whose argument order is consistent with the ternary operators in C-style languages (condition ? truecase : falsecase).

Example of multiplexing between four values:

>>> index = pyrtl.Input(name="index", bitwidth=2)
>>> selected = pyrtl.WireVector(name="selected")

>>> selected <<= pyrtl.mux(index, 4, 5, 6, 7)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"index": 0})
>>> sim.inspect("selected")
4

>>> sim.step(provided_inputs={"index": 3})
>>> sim.inspect("selected")
7

Example with default:

>>> index = pyrtl.Input(name="index", bitwidth=2)
>>> selected = pyrtl.WireVector(name="selected")
>>> selected <<= pyrtl.mux(index, 4, 5, default=9)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"index": 1})
>>> sim.inspect("selected")
5

>>> sim.step(provided_inputs={"index": 2})
>>> sim.inspect("selected")
9
Parameters:
  • index (WireVector | int | str | bool) – Multiplexer’s selection input. Can be a WireVector, or any type that can be coerced to WireVector by as_wires().

  • mux_ins (WireVector | int | str | bool) – WireVector arguments to select from. Can be a WireVector, or any type that can be coerced to WireVector by as_wires().

  • default (WireVector | int | str | bool, default: None) – If you are selecting between fewer items than index can address, default will be used for all remaining items. For example, if you have a 3-bit index but are selecting between 6 mux_ins, you need to specify a value for those other 2 possible values of index (0b110 and 0b111).

Raises:

PyrtlError – If there are not enough mux_ins to select from. If default=None, the number of mux_ins must be exactly 2 ** index.bitwidth.

Return type:

WireVector

Returns:

WireVector with bitwidth matching the length of the longest input (not including index).

pyrtl.select(sel, truecase, falsecase)[source]

Multiplexer returning truecase when sel == 1, otherwise falsecase.

select is equivalent to mux() with a 1-bit index, except that select’s truecase is its first argument, rather than its second. select’s argument order is consistent with the ternary operator in C-style languages, which improves readability, so prefer select over mux() when selecting between exactly two options.

Example that computes min(a, 5):

>>> a = pyrtl.Input(name="a", bitwidth=4)
>>> min = pyrtl.WireVector(name="min")

>>> min <<= pyrtl.select(a < 5, 5, a)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"a": 4})
>>> sim.inspect("min")
5

>>> sim.step(provided_inputs={"a": 6})
>>> sim.inspect("min")
6
Parameters:
Return type:

WireVector

Returns:

WireVector with bitwidth matching the longer of truecase and falsecase.

pyrtl.enum_mux(cntrl, table, default=None, strict=True)[source]

Build a mux for the control signals specified by an enum.IntEnum.

Note

Consider using Conditional Assignment instead.

Example:

>>> from enum import IntEnum
>>> class Command(IntEnum):
...     ADD = 0
...     SUB = 1

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

>>> output <<= pyrtl.enum_mux(cntrl=command, table={
...     Command.ADD: a + b,
...     Command.SUB: a - b,
... })

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"command": Command.ADD, "a": 1, "b": 2})
>>> sim.inspect("output")
3

>>> sim.step(provided_inputs={"command": Command.SUB, "a": 5, "b": 3})
>>> sim.inspect("output")
2
Parameters:
  • cntrl (WireVector) – Control for the mux.

  • table (dict[int, WireVector]) – Maps enum.IntEnum values to WireVector.

  • default (WireVector, default: None) – A WireVector to use when the key is not present. In addition it is possible to use the key otherwise to specify a default value, but it is an error if both are supplied.

  • strict (bool, default: True) – When True, check that the dictionary has an entry for every possible value in the enum.IntEnum. Note that if a default is set, then this check is not performed as the default will provide valid values for any underspecified keys.

Return type:

WireVector

Returns:

Result of the mux.

class pyrtl.helperfuncs.MatchedFields(matched: WireVector, fields: NamedTuple)[source]

Result returned by match_bitpattern().

fields: NamedTuple

NamedTuple containing the matched fields, if any.

matched: WireVector

1-bit WireVector indicating if w matches bitpattern.

pyrtl.match_bitpattern(w, bitpattern, field_map=None)[source]

Returns a single-bit WireVector that is 1 if and only if w matches the bitpattern, and a tuple containing the matched fields, if any. Compatible with the with statement.

This function will compare a multi-bit WireVector to a specified pattern of bits, where some of the pattern can be “wildcard” bits. If any of the 1 or 0 values specified in the bitpattern fail to match the WireVector during execution, a 0 will be produced, otherwise the value carried on the wire will be 1. The wildcard characters can be any other alphanumeric character, with characters other than ? having special functionality (see below). The string must have length equal to the WireVector specified, although whitespace and underscore characters will be ignored and can be used for pattern readability.

For all other characters besides 1, 0, or ?, a tuple of WireVectors will be returned as the second return value. Each character will be treated as the name of a field, and non-consecutive fields with the same name will be concatenated together, left-to-right, into a single field in the resultant tuple. For example, 01aa1?bbb11a will match a string such as 010010100111, and the resultant matched fields are:

(a, b) = (0b001, 0b100)

where the a field is the concatenation of bits 9, 8, and 0, and the b field is the concenation of bits 5, 4, and 3. Thus, arbitrary characters beside ? act as wildcard characters for the purposes of matching, with the additional benefit of returning the WireVectors corresponding to those fields.

A prime example of this is for decoding instructions. Here we decode some RISC-V:

with pyrtl.conditional_assignment:
    with match_bitpattern(
        inst, "iiiiiiiiiiiirrrrr010ddddd0000011"
    ) as (imm, rs1, rd):
        regfile[rd] |= mem[(regfile[rs1] + imm.sign_extended(32)).truncate(32)]
        pc.next |= pc + 1
    with match_bitpattern(
        inst, "iiiiiiirrrrrsssss010iiiii0100011"
    ) as (imm, rs2, rs1):
        mem[(regfile[rs1] + imm.sign_extended(32)).truncate(32)] |= regfile[rs2]
        pc.next |= pc + 1
    with match_bitpattern(
        inst, "0000000rrrrrsssss111ddddd0110011"
    ) as (rs2, rs1, rd):
        regfile[rd] |= regfile[rs1] & regfile[rs2]
        pc.next |= pc + 1
    with match_bitpattern(
        inst, "0000000rrrrrsssss000ddddd0110011")
    as (rs2, rs1, rd):
        regfile[rd] |= (regfile[rs1] + regfile[rs2]).truncate(32)
        pc.next |= pc + 1
    # ...etc...

Some smaller examples:

# Basically the same as w == '0b0101'.
m, _ = match_bitpattern(w, '0101')

# m will be true when w is '0101' or '0111'.
m, _ = match_bitpattern(w, '01?1')

# m will be true when last two bits of w are '01'.
m, _ = match_bitpattern(w, '??01')

# spaces/underscores are ignored, same as line above.
m, _ = match_bitpattern(w, '??_0 1')

# All bits with the same letter make up same field.
m, (a, b) = match_bitpattern(w, '01aa1?bbb11a')

# Fields will be named `fs.foo` and `fs.bar`.
m, fs = match_bitpattern(w, '01aa1?bbb11a', {'a': 'foo', 'b': 'bar'})
Parameters:
  • w (WireVector) – The WireVector to be compared to the bitpattern

  • bitpattern (str) – A string holding the pattern (of bits and wildcards) to match

  • field_map (dict[str, str] | None, default: None) – (optional) A map from single-character field name in the bitpattern to the desired name of field in the returned NamedTuple. If given, all non-1/0/? characters in the bitpattern must be present in the map.

Return type:

MatchedFields

Returns:

A NamedTuple consisting of a 1-bit WireVector carrying the result of the comparison, and a NamedTuple containing the matched fields, if any.

pyrtl.bitfield_update(w, range_start, range_end, newvalue, truncating=False)[source]

Update a WireVector by replacing some of its bits with newvalue.

Given a WireVector w, this function returns a new WireVector that is identical to w except in the range of bits specified by [range_start, range_end). In that range, the value newvalue is swapped in. For example:

bitfield_update(w, range_start=20, range_end=23, newvalue=0b111)

will return a WireVector of the same length as w, and with the same values as w, but with bits 20, 21, and 22 all set to 1.

Note that range_start and range_end will be inputs to a slice and so standard Python slicing rules apply (e.g. negative values for end-relative indexing and support for None):

# Sets bits 20, 21, 22 to 1.
w = bitfield_update(w, 20, 23, 0b111)

# Sets bit 20 to 0, bits 21 and 22 to 1.
w = bitfield_update(w, 20, 23, 0b110)

# Assuming w is 32 bits, sets bits 31..20 = 0x7.
w = bitfield_update(w, 20, None, 0x7)

# Set the MSB (bit) to 1.
w = bitfield_update(w, -1, None, 0x1)

# Set the bits before the MSB (bit) to 9.
w = bitfield_update(w, None, -1, 0x9)

# Set the LSB (bit) to 1.
w = bitfield_update(w, None, 1, 0x1)

# Set the bits after the LSB (bit) to 9.
w = bitfield_update(w, 1, None, 0x9)

Note

Consider using wire_struct() or wire_matrix() instead, which helps with consistently disassembling, naming, and reassembling fields.

Parameters:
  • w (WireVector | int | str | bool) – A WireVector, or any type that can be coerced to WireVector by as_wires(), to use as the starting point for the update

  • range_start (int) – The start of the range of bits to be updated.

  • range_end (int) – The end of the range of bits to be updated.

  • newvalue (int) – The value to be written in to the range_start:range_end range.

  • truncating (bool, default: False) – If True, clip newvalue to the proper bitwidth if newvalue is too large.

Raises:

PyrtlError – If newvalue is too large to fit in the selected range of bits and truncating is False.

Return type:

WireVector

Returns:

w with some of the bits overwritten by newvalue.

pyrtl.bitfield_update_set(w, update_set, truncating=False)[source]

Update a WireVector by replacing the bits specified in update_set.

Given a WireVector w, return a new WireVector that is identical to w except in the ranges of bits specified by update_set. When multiple non-overlapping fields need to be updated in a single cycle, this provides a clearer way to describe that behavior than iterative calls to bitfield_update():

w = bitfield_update_set(w, update_set={
        (20, 23):    0x6,      # sets bit 20 to 0, bits 21 and 22 to 1
        (26, None):  0x7,      # assuming w is 32 bits, sets bits 31..26 to 0x7
        (None, 1):   0x0,      # set the LSB (bit) to 0
    })

Note

Consider using wire_struct() or wire_matrix() instead, which helps with consistently disassembling, naming, and reassembling fields.

Parameters:
Raises:

PyrtlError – If update_set contains overlapping fields.

Return type:

WireVector

Returns:

w with some of its bits updated.

Interpreting Vectors of Bits

Under the hood, every single value a PyRTL design operates on is a bit vector (which is, in turn, simply an integer of bounded power-of-two size. Interpreting these bit vectors as humans, and turning human understandable values into their corresponding bit vectors, can both be a bit of a pain. The functions below do not create any hardware but rather help in the process of reasoning about bit vector representations of human understandable values.

pyrtl.val_to_signed_integer(value, bitwidth)[source]

Return value interpreted as a two’s complement signed integer.

Reinterpret an unsigned integer (not a WireVector!) as a signed integer. This is useful for printing and interpreting two’s complement values:

>>> val_to_signed_integer(0xff, bitwidth=8)
-1

val_to_signed_integer can also be used as an repr_func for SimulationTrace.render_trace(), to display signed integers in traces:

bitwidth = 3
counter = Register(name='counter', bitwidth=bitwidth)
counter.next <<= counter + 1
sim = Simulation()
sim.step_multiple(nsteps=2 ** bitwidth)

# Generates a trace like:
#        │0 │1 │2 │3 │4 │5 │6 │7
#
# counter ──┤1 │2 │3 │-4│-3│-2│-1
sim.tracer.render_trace(repr_func=val_to_signed_integer)

infer_val_and_bitwidth() performs the opposite conversion:

>>> integer = val_to_signed_integer(0xff, bitwidth=8)
>>> hex(infer_val_and_bitwidth(integer, bitwidth=8).value)
'0xff'
Parameters:
  • value (int) – A Python integer holding the value to convert.

  • bitwidth (int) – The length of the integer in bits to assume for conversion.

Return type:

int

Returns:

value as a signed integer

class pyrtl.helperfuncs.ValueBitwidthTuple(value: int, bitwidth: int)[source]

Return type for infer_val_and_bitwidth().

bitwidth: int

Inferred bitwidth.

value: int

Inferred value.

pyrtl.infer_val_and_bitwidth(rawinput, bitwidth=None, signed=False)[source]

Return a (value, bitwidth) tuple inferred from the specified input.

Given a boolean, integer, or Verilog-style string constant, this function returns a ValueBitwidthTuple (value, bitwidth) which are inferred from the specified rawinput. If signed is True, bits will be included to ensure a proper two’s complement representation is possible, otherwise it assumes a standard unsigned representation. Error checks are performed that determine if the bitwidths specified are sufficient and appropriate for the values specified. Examples:

>>> infer_val_and_bitwidth(2, bitwidth=5)
ValueBitwidthTuple(value=2, bitwidth=5)

>>> # Infer bitwidth from value.
>>> infer_val_and_bitwidth(3)
ValueBitwidthTuple(value=3, bitwidth=2)
>>> infer_val_and_bitwidth(3).bitwidth
2

>>> # Signed values need an additional sign bit.
>>> infer_val_and_bitwidth(3, signed=True)
ValueBitwidthTuple(value=3, bitwidth=3)

>>> val, bitwidth = infer_val_and_bitwidth(-1, bitwidth=3)
>>> (bin(val), bitwidth)
('0b111', 3)

>>> infer_val_and_bitwidth("5'd12")
ValueBitwidthTuple(value=12, bitwidth=5)

val_to_signed_integer() performs the opposite conversion:

>>> val, bitwidth = infer_val_and_bitwidth(-1, bitwidth=3)
>>> val_to_signed_integer(val, bitwidth)
-1
Parameters:
  • rawinput (int | bool | str) – a bool, int, or Verilog-style string constant

  • bitwidth (int | None, default: None) – an integer bitwidth or (by default) None

  • signed (bool, default: False) – a bool (by default False) to include bits for proper two’s complement

Return type:

ValueBitwidthTuple

Returns:

tuple of integers (value, bitwidth)

pyrtl.val_to_formatted_str(val, format, enum_set=None)[source]

Return a string representation of the value given format specified.

Given an unsigned integer (not a WireVector!) convert that to a human-readable string. This helps deal with signed/unsigned numbers (simulation operates on values that have been converted via two’s complement), but it also generates hex, binary, and enum types as outputs. It is easiest to see how it works with some examples:

>>> val_to_formatted_str(2, 's3')
'2'
>>> val_to_formatted_str(7, 's3')
'-1'
>>> val_to_formatted_str(5, 'b3')
'101'
>>> val_to_formatted_str(5, 'u3')
'5'
>>> val_to_formatted_str(5, 's3')
'-3'
>>> val_to_formatted_str(10, 'x3')
'a'

>>> from enum import IntEnum
>>> class Ctl(IntEnum):
...     ADD = 5
...     SUB = 12
>>> val_to_formatted_str(5, 'e3/Ctl', [Ctl])
'ADD'
>>> val_to_formatted_str(12, 'e3/Ctl', [Ctl])
'SUB'

formatted_str_to_val() performs the opposite conversion.

Parameters:
  • val (int) – An unsigned integer to convert.

  • format (str) – A string holding a format which will be used to convert the data string.

  • enum_set (default: None) – An iterable of IntEnum which are used as part of the conversion process.

Return type:

str

Returns:

a human-readable string representing val.

pyrtl.formatted_str_to_val(data, format, enum_set=None)[source]

Return an unsigned integer representation of data in a specified format.

Given a string (not a WireVector!) convert that to an unsigned integer ready for input to the simulation environment. This helps deal with signed/unsigned numbers (simulation assumes the values have been converted via two’s complement already), but it also takes hex, binary, and enum types as inputs. It is easiest to see how it works with some examples:

>>> formatted_str_to_val('2', 's3')
2
>>> bin(formatted_str_to_val('-1', 's3'))
'0b111'
>>> bin(formatted_str_to_val('101', 'b3'))
'0b101'
>>> formatted_str_to_val('5', 'u3')
5
>>> bin(formatted_str_to_val('-3', 's3'))
'0b101'
>>> formatted_str_to_val('a', 'x3')
10

>>> from enum import IntEnum
>>> class Ctl(IntEnum):
...     ADD = 5
...     SUB = 12
>>> formatted_str_to_val('ADD', 'e3/Ctl', [Ctl])
5
>>> formatted_str_to_val('SUB', 'e3/Ctl', [Ctl])
12

val_to_formatted_str() performs the opposite conversion.

Parameters:
  • data (str) – A string holding the value to convert.

  • format (str) – A string holding a format which will be used to convert the data string.

  • enum_set (default: None) – An iterable of IntEnum which are used as part of the conversion process.

Return type:

int

Returns:

data as a signed integer

pyrtl.log2(integer_val)[source]

Return the base-2 logarithm of an integer.

Useful when checking that powers of 2 are provided as function inputs.

Examples:

>>> log2(2)
1
>>> log2(256)
8
>>> log2(100)
Traceback (most recent call last):
  ...
pyrtl.pyrtlexceptions.PyrtlError: this function can only take even powers of 2
Parameters:

integer_val (int) – The integer to take the log base 2 of.

Raises:

PyrtlError – If the input is negative, or not an even power of 2.

Return type:

int

Returns:

The log base 2 of integer_val.

Debugging

pyrtl.set_debug_mode(debug=True)[source]

Set the global debug mode.

Sets the debug mode to the specified debug value. Debug mode is off by default, to improve performance. When debug mode is enabled, all temporary WireVectors will be assigned names based on the line of code on which they were created.

Each WireVector will also save a copy of its call stack when constructed. These call stacks can be inspected as WireVector.init_call_stack, and they will appear in Block.sanity_check() error messages.

Parameters:

debug (bool, default: True) – Optional boolean parameter to which the debug mode will be set.

pyrtl.probe(w, name=None)[source]

Print useful information about a WireVector in debug mode.

probe can be inserted into a existing design easily because it returns the original wire unmodified. For example:

y <<= x[0:3] + 4

could be rewritten as:

y <<= probe(x)[0:3] + 4

to give visibility into both the origin of x (including the line that WireVector was originally created) and the run-time values of x (which will be named and thus show up by default in a trace). Likewise:

y <<= probe(x[0:3]) + 4
y <<= probe(x[0:3] + 4)
probe(y) <<= x[0:3] + 4

are all valid uses of probe.

Note

probe actually adds an Output wire to the working_block of w, which can confuse various post-processing transforms such as output_to_verilog().

Parameters:
  • w (WireVector) – WireVector from which to get info

  • name (str | None, default: None) – optional name for probe (defaults to an autogenerated name)

Return type:

WireVector

Returns:

original WireVector w

pyrtl.rtl_assert(w, exp, block=None)[source]

Add a hardware assertion to be checked during simulation.

If at any time during execution the wire w is not True (i.e. when it is asserted low) then Simulation will raise exp.

Parameters:
Raises:

exp – When w is not True.

Return type:

Output

Returns:

The Output wire for the assertion, which can be ignored in most cases.

Reductions

pyrtl.and_all_bits(vector)[source]

Returns the result of bitwise ANDing all the bits in vector.

Example:

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

>>> output <<= pyrtl.and_all_bits(input)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0b0101})
>>> sim.inspect("output")
0
>>> sim.step(provided_inputs={"input": 0b1111})
>>> sim.inspect("output")
1
Parameters:

vector (WireVector) – Takes a single arbitrary length WireVector.

Return type:

WireVector

Returns:

Returns a 1-bit result, the bitwise & of all of the bits in vector.

pyrtl.or_all_bits(vector)[source]

Returns the result of bitwise ORing all the bits in vector.

Example:

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

>>> output <<= pyrtl.or_all_bits(input)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0b0000})
>>> sim.inspect("output")
0
>>> sim.step(provided_inputs={"input": 0b0100})
>>> sim.inspect("output")
1
Parameters:

vector (WireVector) – Takes a single arbitrary length WireVector.

Return type:

WireVector

Returns:

Returns a 1-bit result, the bitwise | of all of the bits in vector.

pyrtl.xor_all_bits(vector)[source]

Returns the result of bitwise XORing all the bits in vector.

Example:

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

>>> output <<= pyrtl.xor_all_bits(input)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0b0100})
>>> sim.inspect("output")
1
>>> sim.step(provided_inputs={"input": 0b0101})
>>> sim.inspect("output")
0
Parameters:

vector (WireVector) – Takes a single arbitrary length WireVector.

Return type:

WireVector

Returns:

Returns a 1-bit result, the bitwise ^ of all of the bits in vector.

pyrtl.parity(vector)

Alias for xor_all_bits().

Return type:

WireVector

pyrtl.rtl_any(*vectorlist)[source]

Hardware equivalent of Python’s any().

Given any number of WireVectors, return a 1-bit WireVector which will hold a 1 if any of the inputs are 1. In other words, this generates a large OR gate. If no inputs are provided, it will return a Const 0 (since there are no 1s present) similar to Python’s any() called with an empty list.

Note

rtl_any is most useful when working with a variable number of WireVectors. For a fixed number of WireVectors, it is clearer to use |:

any_ones = a | b | c

Example:

>>> inputs = [pyrtl.Input(name="input0", bitwidth=1),
...           pyrtl.Input(name="input1", bitwidth=1)]
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtl_any(*inputs)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input0": 0, "input1": 0})
>>> sim.inspect("output")
0
>>> sim.step(provided_inputs={"input0": 0, "input1": 1})
>>> sim.inspect("output")
1
Parameters:

vectorlist (WireVector | int | str | bool) – All arguments are length 1 WireVector, or any type that can be coerced to WireVector by as_wires(), with length 1.

Raises:

PyrtlError – If any argument’s bitwidth is not 1.

Return type:

WireVector

Returns:

Length 1 WireVector indicating if any bits in vectorlist are 1.

pyrtl.rtl_all(*vectorlist)[source]

Hardware equivalent of Python’s all().

Given any number of WireVectors, return a 1-bit WireVector which will hold a 1 only if all of the inputs are 1. In other words, this generates a large AND gate. If no inputs are provided, it will return a Const 1 (since there are no 0s present) similar to Python’s all() called with an empty list.

Note

rtl_all is most useful when working with a variable number of WireVectors. For a fixed number of WireVectors, it is clearer to use &:

all_ones = a & b & c

Example:

>>> inputs = [pyrtl.Input(name="input0", bitwidth=1),
...           pyrtl.Input(name="input1", bitwidth=1)]
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.rtl_all(*inputs)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input0": 0, "input1": 1})
>>> sim.inspect("output")
0
>>> sim.step(provided_inputs={"input0": 1, "input1": 1})
>>> sim.inspect("output")
1
Parameters:

vectorlist (WireVector | int | str | bool) – All arguments are length 1 WireVector, or any type that can be coerced to WireVector by as_wires(), with length 1.

Raises:

PyrtlError – If any argument’s bitwidth is not 1.

Return type:

WireVector

Returns:

Length 1 WireVector indicating if all bits in vectorlist are 1.

Extended Logic and Arithmetic

The functions below provide ways of comparing and arithmetically combining WireVectors in ways that are often useful in hardware design. The functions below extend those member functions of the WireVector class itself (which provides support for unsigned addition, subtraction, multiplication, comparison, and many others).

pyrtl.signed_add(a, b)[source]

Return a WireVector for the result of signed addition.

The inputs are sign_extended() to the result’s bitwidth before adding.

Example:

>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3)
>>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.signed_add(neg_three, neg_five)
>>> output.bitwidth
6

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth)
-8
Parameters:
Return type:

WireVector

Returns:

A WireVector representing the sum of a and b, with bitwidth max(a.bitwidth, b.bitwidth) + 1.

pyrtl.signed_sub(a, b)[source]

Return a WireVector for the result of signed subtraction.

The inputs are sign_extended() to the result’s bitwidth before subtracting.

Example:

>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3)
>>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.signed_sub(neg_three, neg_five)
>>> output.bitwidth
6

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth)
2
Parameters:
Return type:

WireVector

Returns:

A WireVector representing the difference between a and b, with bitwidth max(a.bitwidth, b.bitwidth) + 1.

pyrtl.signed_mult(a, b)[source]

Return a WireVector for the result of signed multiplication.

The inputs are sign_extended() to the result’s bitwidth before multiplying.

Example:

>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3)
>>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.signed_mult(neg_three, neg_five)
>>> output.bitwidth
8

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth)
15
Parameters:
Return type:

WireVector

Returns:

A WireVector representing the product of a and b, with bitwidth a.bitwidth + b.bitwidth.

pyrtl.signed_lt(a, b)[source]

Return a 1-bit WireVector for the result of a signed < comparison.

The inputs are sign_extended() to matching bitwidths before comparing.

Example:

>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3)
>>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.signed_lt(neg_three, neg_five)
>>> output.bitwidth
1

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
0
Parameters:
Return type:

WireVector

Returns:

A 1-bit WireVector indicating if a is less than b.

pyrtl.signed_le(a, b)[source]

Return a 1-bit WireVector for the result of a signed <= comparison.

The inputs are sign_extended() to matching bitwidths before comparing.

Example:

>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3)
>>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.signed_le(neg_three, neg_five)
>>> output.bitwidth
1

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
0
Parameters:
Return type:

WireVector

Returns:

A 1-bit WireVector indicating if a is less than or equal to b.

pyrtl.signed_gt(a, b)[source]

Return a 1-bit WireVector for the result of a signed > comparison.

The inputs are sign_extended() to matching bitwidths before comparing.

Example:

>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3)
>>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.signed_gt(neg_three, neg_five)
>>> output.bitwidth
1

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
1
Parameters:
Return type:

WireVector

Returns:

A 1-bit WireVector indicating if a is greater than b.

pyrtl.signed_ge(a, b)[source]

Return a 1-bit WireVector for the result of a signed >= comparison.

The inputs are sign_extended() to matching bitwidths before comparing.

Example:

>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3)
>>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.signed_ge(neg_three, neg_five)
>>> output.bitwidth
1

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
1
Parameters:
Return type:

WireVector

Returns:

A 1-bit WireVector indicating if a is greater than or equal to b.

pyrtl.shift_left_logical(bits_to_shift, shift_amount)[source]

Logical left shift operation.

Logical shifting treats the bits_to_shift as an unsigned number. Zeroes will be added on the right and the result will be truncated to bits_to_shift.bitwidth.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=6)
>>> shift_amount = pyrtl.Input(name="shift_amount", bitwidth=3)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.shift_left_logical(three, shift_amount)

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

Left shifting by N bits is equivalent to multiplying by 2^N.

Parameters:
  • bits_to_shift (WireVector) – Value to shift left logically.

  • shift_amount (WireVector | int) – Number of bit positions to shift, as an unsigned integer.

Return type:

WireVector

Returns:

A new WireVector with the same bitwidth as bits_to_shift.

pyrtl.shift_left_arithmetic(bits_to_shift, shift_amount)

Alias for shift_left_logical()

Return type:

WireVector

pyrtl.shift_right_logical(bits_to_shift, shift_amount)[source]

Logical right shift operation.

Logical shifting treats the bits_to_shift as an unsigned number, so zeroes will be added on the left.

Example:

>>> forty = pyrtl.Const(val=40, bitwidth=6)
>>> shift_amount = pyrtl.Input(name="shift_amount", bitwidth=3)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.shift_right_logical(forty, shift_amount)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"shift_amount": 3})
>>> sim.inspect("output")
5
>>> int(40 / 2 ** 3)
5

Right shifting by N bits is equivalent to dividing by 2^N.

Parameters:
  • bits_to_shift (WireVector) – Value to shift right logically.

  • shift_amount (WireVector | int) – Number of bit positions to shift, as an unsigned integer.

Return type:

WireVector

Returns:

A new WireVector with the same bitwidth as bits_to_shift.

pyrtl.shift_right_arithmetic(bits_to_shift, shift_amount)[source]

Arithmetic right shift operation.

Arithemetic shifting treats the bits_to_shift as a signed number, so copies of bits_to_shift’s sign bit will be added on the left.

Example:

>>> neg_forty = pyrtl.Const(val=-40, signed=True, bitwidth=7)
>>> shift_amount = pyrtl.Input(name="shift_amount", bitwidth=3)
>>> output = pyrtl.Output(name="output")

>>> output <<= pyrtl.shift_right_arithmetic(neg_forty, shift_amount)

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"shift_amount": 3})
>>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth)
-5
>>> int(-40 / 2 ** 3)
-5

Right shifting by N bits is equivalent to dividing by 2^N.

Parameters:
  • bits_to_shift (WireVector) – Value to shift right arithmetically.

  • shift_amount (WireVector | int) – Number of bit positions to shift, as an unsigned integer.

Return type:

WireVector

Returns:

A new WireVector with the same bitwidth as bits_to_shift.

Encoders and Decoders

pyrtl.one_hot_to_binary(w)[source]

Takes a one-hot input and returns the bit position of the high bit in binary.

If the input contains multiple 1’s, the smallest bit position containing a 1 will be returned. If the input contains no 1’s, 0 will be returned.

Examples:

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

>>> output <<= pyrtl.one_hot_to_binary(input)
>>> output.bitwidth
3
>>> 2 ** 3 == input.bitwidth
True

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0b0010})
>>> sim.inspect("output")
1

>>> sim.step(provided_inputs={"input": 64})
>>> sim.inspect("output")
6
>>> 2 ** 6
64

>>> # Bit positions 2 and 3 contain 1's, but 2 is smaller.
>>> sim.step(provided_inputs={"input": 0b1100})
>>> sim.inspect("output")
2

>>> sim.step(provided_inputs={"input": 0})
>>> sim.inspect("output")
0
Parameters:

w (WireVector | int | str | bool) – A WireVector, or any type that can be coerced to WireVector by as_wires().

Return type:

WireVector

Returns:

a WireVector containing the smallest bit position of the high bit, in binary.

pyrtl.binary_to_one_hot(bit_position, max_bitwidth=None)[source]

Given a bit_position, return a WireVector with only that bit set to 1.

If the max_bitwidth provided is not large enough for the given bit_position, a 0-valued WireVector of size max_bitwidth will be returned.

Examples:

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

>>> output <<= pyrtl.binary_to_one_hot(input)
>>> output.bitwidth
16
>>> 16 == 2 ** input.bitwidth
True

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0})
>>> bin(sim.inspect("output"))
'0b1'

>>> sim.step(provided_inputs={"input": 3})
>>> bin(sim.inspect("output"))
'0b1000'
Parameters:
Return type:

WireVector

Returns:

A WireVector with the bit at bit_position set to 1 and all other bits set to 0. Bit position 0 is the least significant bit.