Wires and Logic

Wires define the relationship between logic blocks in PyRTL. They are treated like normal wires in traditional RTL systems except the Register wire. Logic is then created when wires are combined with one another using the provided operators. For example, if a and b are both of type WireVector, then a + b will make an adder, plug a and b into the inputs of that adder, and return a new WireVector which is the output of that adder. Block stores the description of the hardware as you build it.

Input, Output, Const, and Register all derive from WireVector. Input represents an input pin, serving as a placeholder for an external value provided during Simulation. Output represents an output pin, which does not drive any wires in the design. Const is useful for specifying hard-wired values and Register is how sequential elements are created (they all have an implicit clock).

Inheritance diagram of pyrtl.WireVector, pyrtl.Input, pyrtl.Output, pyrtl.Const, pyrtl.Register

WireVector

class pyrtl.WireVector(bitwidth=None, name='', block=None)[source]

The main class for describing the connections between operators.

WireVectors act much like a list of wires, except that there is no “contained” type, each slice of a WireVector is itself a WireVector (even if it just contains a single “bit” of information). The least significant bit of the wire is at index 0 and normal list slicing syntax applies (i.e. myvector[0:5] makes a new vector from the bottom 5 bits of myvector, myvector[-1] takes the most significant bit, and myvector[-4:] takes the 4 most significant bits).

Operation

Syntax

Function

Documentation

Addition

a + b

Creates an unsigned adder, returns WireVector

__add__()

Subtraction

a - b

Creates an unsigned subtracter, returns WireVector

__sub__()

Multiplication

a * b

Creates an unsigned multiplier, returns WireVector

__mul__()

Xor

a ^ b

Bitwise XOR, returns WireVector

__xor__()

Or

a | b

Bitwise OR, returns WireVector

__or__()

And

a & b

Bitwise AND, returns WireVector

__and__()

Invert

~a

Bitwise invert, returns WireVector

__invert__()

Less Than

a < b

Unsigned less than, return 1-bit WireVector

__lt__()

Less or Eq.

a <= b

Unsigned less than or equal to, return 1-bit WireVector

__le__()

Greater Than

a > b

Unsigned greater than, return 1-bit WireVector

__gt__()

Greater or Eq.

a >= b

Unsigned greater or equal to, return 1-bit WireVector

__ge__()

Equality

a == b

Hardware to check equality, return 1-bit WireVector

__eq__()

Not Equal

a != b

Inverted equality check, return 1-bit WireVector

__ne__()

Bitwidth

len(a)

Return bitwidth of the WireVector

__len__()

Assignment

a <<= b

Connect from b to a (see Note below)

__ilshift__()

Bit Slice

a[3:6]

Selects bits from WireVector, in this case bits 3,4,5

__getitem__()

Note

<<= (__ilshift__()) is how you “drive” an already created wire with an existing wire. If you were to do a = b it would lose the old value of a and simply overwrite it with a new value, in this case with a reference to WireVector b. In contrast a <<= b does not overwrite a, but simply wires the two together.

WireVector Coercion

Most PyRTL functions that accept WireVectors as arguments will also accept any type that as_wires() can coerce to WireVector. Examples include int, bool, and str.

int will be coerced to an unsigned Const WireVector with the minimum bitwidth required for the integer. In the following example, a 2-bit Const is implicitly created for 2:

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

>>> output <<= input + 2

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

bool will be coerced to a Const with bitwidth 1. In the following example, a 1-bit Const is implicitly created for True:

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

>>> output <<= input ^ True

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

str will be interpreted as a Verilog-style string constant. In the following example, a 4-bit Const is implicitly created for "4'hf":

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

>>> output <<= input & "4'hf"

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0xab})
>>> hex(sim.inspect("output"))
'0xb'

WireVector Equality

WireVector.__eq__() generates logic that dynamically reports if two wires carry the same values. WireVector.__eq__() returns a 1-bit WireVector, not a bool, and attempting to convert a WireVector to a bool raises a PyrtlError. This behavior is incompatible with Python’s data model, which can cause problems.

For example, you can not statically check if two WireVectors are equal with ==. Statically checking for WireVector equality can be useful while constructing or analyzing circuits:

>>> w1 = pyrtl.WireVector(name="w1", bitwidth=1)
>>> w2 = pyrtl.WireVector(name="w2", bitwidth=2)
>>> if w1 == w2:
...     print('same')
...
Traceback (most recent call last):
...
pyrtl.pyrtlexceptions.PyrtlError: cannot convert WireVector to compile-time boolean...

The error about converting WireVector to bool results from Python attempting to convert the 1-bit WireVector returned by __eq__() to True or False while evaluating the if statement’s condition.

Instead, you can statically check if two WireVectors refer to the same object with is:

>>> w1 is not w2
True
>>> temp = w1
>>> temp is w1
True
>>> temp is w2
False

Be careful when using Python features that depend on == with WireVectors. This often comes up when checking if a WireVector is in a list with in, which does not work because in falls back on checking each item in the list for equality with ==:

>>> l = [w1]
>>> w2 in l
Traceback (most recent call last):
...
pyrtl.pyrtlexceptions.PyrtlError: cannot convert WireVector to compile-time
boolean...

Most other list operations work, so you can store WireVectors in a list if you avoid using the in operator:

>>> len(l)
1
>>> l[0] is w1
True
>>> [(w.name, w.bitwidth) for w in l]
[('w1', 1)]

WireVectors define a standard __hash__() method, so if you need to check if a WireVector is in a container, use a set or dict. This works because these containers use __hash__() to skip unnecessary equality checks:

>>> s = {w1}
>>> w1 in s
True
>>> w2 in s
False

>>> d = {w1: 'hello'}
>>> w1 in d
True
>>> w2 in d
False
>>> d[w1]
'hello'
__add__(other)[source]

Returns the result of adding self and other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Warning

This addition operation is unsigned. Use signed_add() for signed addition.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three + five
>>> output.bitwidth
5

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
8
Parameters:

other (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 result of adding self and other. The returned WireVector has a bitwidth equal to the longer of the two input WireVectors, plus one.

__and__(other)[source]

Returns the result of bitwise ANDing self and other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Example:

>>> three = pyrtl.Const(val=0b11, bitwidth=2)
>>> five = pyrtl.Const(val=0b101, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three & five
>>> output.bitwidth
4

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

other (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 result of bitwise ANDing self and other. The returned WireVector has the same bitwidth as the longer of the two input WireVectors.

__eq__(other)[source]

Checks if self is equal to other. Returns a one-bit WireVector.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Warning

This definition of __eq__() returns WireVector, not bool, which is not compatible with Python’s data model, which can cause problems. See WireVector Equality.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three == five
>>> output.bitwidth
1

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

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

Return type:

WireVector

Returns:

A one-bit WireVector indicating if self is equal to other.

__ge__(other)[source]

Checks if self is greater than or equal to other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Warning

This comparison is unsigned. Use signed_ge() for signed comparison.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three >= five
>>> output.bitwidth
1

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

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

Return type:

WireVector

Returns:

A one-bit WireVector indicating if self is greater than or equal to other.

__getitem__(item)[source]

Returns a WireVector containing a subset of the wires in self.

There are two ways to retrieve WireVector subsets:

  1. By int index, for example wire[2]. This returns a WireVector with bitwidth 1.

  2. By slice, for example wire[3:5]. Slicing uses the usual [start:stop:step] notation.

    1. If start is omitted, it defaults to index 0, the least significant bit.

    2. If stop is omitted, it defaults to bitwidth - 1, the most significant bit.

    3. If step is omitted, it defaults to 1.

bitwidth is added to negative start and stop indices, so negative indices count backwards from just beyond the most significant bit. Index bitwidth - 1 and index -1 both refer to the most significant bit.

If step is negative, the wires will be returned in reverse order.

Suppose we have a WireVector input, with bitwidth 8:

input = WireVector(name="input", bitwidth=8)

We can access individual wires of input with integer indices:

input[0]                   # Least significant bit.
input[input.bitwidth - 1]  # Most significant bit
input[-1]                  # Another name for the most significant bit.

Example:

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

>>> msb <<= input[-1]
>>> msb.bitwidth
1

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

We can access contiguous subsets of input’s wires with slices:

input[2:6]  # Middle 4 bits.
input[:4]   # Least significant 4 bits.
input[-4:]  # Most significant 4 bits.

Example:

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

>>> middle_bits <<= input[2:6]
>>> middle_bits.bitwidth
4

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0b0011_1100})
>>> bin(sim.inspect("middle_bits"))
'0b1111'

We can skip some of input’s wires with slices:

input[::2]   # Even numbered wires: 0, 2, 4, 6.
input[1::2]  # Odd numbered wires: 1, 3, 5, 7.

Example:

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

>>> odd_bits <<= input[1::2]
>>> odd_bits.bitwidth
4

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0b1010_1010})
>>> bin(sim.inspect("odd_bits"))
'0b1111'

We can reverse input’s wires with slices:

input[::-1]    # Reversed wires: 7, 6, 5, 4, 3, 2, 1, 0.
input[-1::-2]  # Reversed odd wires: 7, 5, 3, 1.

Example:

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

>>> reversed_bits <<= input[::-1]
>>> reversed_bits.bitwidth
8

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 0b0000_1111})
>>> bin(sim.inspect("reversed_bits"))
'0b11110000'

>>> sim.step(provided_inputs={"input": 0b1010_1010})
>>> # `bin` omits the leading 0.
>>> bin(sim.inspect("reversed_bits"))
'0b1010101'
Parameters:

item (int | slice) – If an int, specifies the index of a single-bit wire to return. If a slice, specifies a subset of wires to return, as start:stop:step.

Return type:

WireVector

Returns:

A WireVector containing the wires selected by item from self.

__gt__(other)[source]

Checks if self is greater than other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Warning

This comparison is unsigned. Use signed_gt() for signed comparison.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three > five
>>> output.bitwidth
1

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

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

Return type:

WireVector

Returns:

A one-bit WireVector indicating if self is greater than other.

__ilshift__(other)[source]

Wire assignment operator <<=, connects other to self.

Example:

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

>>> output <<= input

>>> sim = pyrtl.Simulation()
>>> sim.step(provided_inputs={"input": 42})
>>> sim.inspect("output")
42
__init__(bitwidth=None, name='', block=None)[source]

Construct a generic WireVector.

Examples:

>>> # Visible in Simulation traces as "data".
>>> data = pyrtl.WireVector(bitwidth=8, name="data")
>>> data.name
'data'

>>> # `ctrl` is assigned a temporary name, and will not be visible in
>>> # Simulation traces by default.
>>> ctrl = pyrtl.WireVector(bitwidth=1)
>>> ctrl.name.startswith("tmp")
True

>>> # `temp` is a temporary with bitwidth specified later.
>>> temp = pyrtl.WireVector()
>>> temp.bitwidth is None
True
>>> # `temp` infers a bitwidth of 8 from `data`.
>>> temp <<= data
>>> temp.bitwidth
8
Parameters:
  • bitwidth (int | None, default: None) – If no bitwidth is provided, it will be set to the minimum number of bits needed to represent this wire.

  • block (Block, default: None) – The Block under which the wire should be placed. Defaults to the working_block.

  • name (str, default: '') – The name of the wire. Must be unique. If empty, a name will be autogenerated. If non-empty, the wire’s value can be inspected with Simulation.inspect(), and this wire will appear in traces generated by SimulationTrace.render_trace().

__invert__()[source]

Returns a WireVector containing the bitwise inversion of self.

Example:

>>> five = pyrtl.Const(val=0b101, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= ~five
>>> output.bitwidth
4

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

WireVector

Returns:

A WireVector containing the result of bitwise inverting self. The returned WireVector has the same bitwidth as self.

__ior__(other)[source]

Conditional assignment operator |=, only usable under conditional_assignment.

Example:

>>> select = pyrtl.Input(bitwidth=1, name="select")
>>> output = pyrtl.WireVector(name="output", bitwidth=2)

>>> with pyrtl.conditional_assignment:
...     with select:
...         output |= 2
...     with pyrtl.otherwise:
...         output |= 3

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

>>> sim.step(provided_inputs={"select": 1})
>>> sim.inspect("output")
2
__le__(other)[source]

Checks if self is less than or equal to other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Warning

This comparison is unsigned. Use signed_le() for signed comparison.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three <= five
>>> output.bitwidth
1

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

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

Return type:

WireVector

Returns:

A one-bit WireVector indicating if self is less than or equal to other.

__len__()[source]

Return the WireVector’s bitwidth.

WireVectors can be constructed without specifying a bitwidth. These WireVectors will have a bitwidth of None until they infer a bitwidth from an <<= (__ilshift__()) assignment.

__len__() is equivalent to bitwidth, except that __len__() raises an exception when bitwidth is None.

Example:

>>> w = pyrtl.WireVector()
>>> len(w)
Traceback (most recent call last):
...
pyrtl.pyrtlexceptions.PyrtlError: length of WireVector not yet defined

>>> w <<= pyrtl.Const(val=42, bitwidth=6)
>>> len(w)
6
Return type:

int

Returns:

Returns the length (bitwidth) of the WireVector, in bits.

Raises:

PyrtlError – If the bitwidth is not yet defined.

__lt__(other)[source]

Checks if self is less than other. Returns a one-bit WireVector.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Warning

This comparison is unsigned. Use signed_lt() for signed comparison.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three < five
>>> output.bitwidth
1

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

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

Return type:

WireVector

Returns:

A one-bit WireVector indicating if self is less than other.

__mul__(other)[source]

Returns the result of multiplying self and other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Warning

This multiplication operation is unsigned. Use signed_mult() for signed multiplication.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three * five
>>> output.bitwidth
8

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
15
Parameters:

other (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 result of multiplying self and other. The returned WireVector has a bitwidth equal to twice the length of the longer input.

__ne__(other)[source]

Checks if self is not equal to other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three != five
>>> output.bitwidth
1

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

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

Return type:

WireVector

Returns:

A one-bit WireVector indicating if self is not equal to other.

__or__(other)[source]

Returns the result of bitwise ORing self and other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Example:

>>> three = pyrtl.Const(val=0b11, bitwidth=2)
>>> five = pyrtl.Const(val=0b101, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three | five
>>> output.bitwidth
4

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> bin(sim.inspect("output"))
'0b111'
Parameters:

other (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 result of bitwise ORing self and other. The returned WireVector has the same bitwidth as the longer of the two input WireVectors.

__sub__(other)[source]

Returns the result of subtracting self and other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Warning

This subtraction operation is unsigned. Use signed_sub() for signed subtraction.

Example:

>>> three = pyrtl.Const(val=3, bitwidth=2)
>>> five = pyrtl.Const(val=5, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= five - three
>>> output.bitwidth
5

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> sim.inspect("output")
2
Parameters:

other (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 result of subtracting self and other. The returned WireVector has a bitwidth equal to the longer of the two input WireVectors, plus one.

__xor__(other)[source]

Returns the result of bitwise XORing self and other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Example:

>>> three = pyrtl.Const(val=0b11, bitwidth=2)
>>> five = pyrtl.Const(val=0b101, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three ^ five
>>> output.bitwidth
4

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> bin(sim.inspect("output"))
'0b110'
Parameters:

other (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 result of bitwise XORing self and other. The returned WireVector has the same bitwidth as the longer of the two input WireVectors.

property bitmask: int

A property holding a bitmask of the same length as this WireVector.

bitmask is an int with a number of bits set to 1 equal to the bitwidth of the WireVector.

It is often useful to “mask” an integer such that it fits in the number of bits of a WireVector, so the bitmask property is provided as a convenience. Example:

>>> w = pyrtl.WireVector(bitwidth=4)
>>> bin(w.bitmask)
'0b1111'
>>> w.bitmask
15
>>> hex(0xabcd & w.bitmask)
'0xd'
bitwidth: int

The wire’s bitwidth.

WireVectors can be constructed without specifying a bitwidth. These WireVectors will have a bitwidth of None until they infer a bitwidth from an <<= (__ilshift__()) assignment.

bitwidth is equivalent to __len__(), except that __len__() raises an exception when bitwidth is None.

Example:

>>> w = pyrtl.WireVector()
>>> w.bitwidth
None
>>> w <<= pyrtl.Const(val=42, bitwidth=6)
>>> w.bitwidth
6
block: Block

The Block that this WireVector belongs to.

property name: str

A property holding the name of the WireVector.

The name can be read or written. Examples:

>>> a = WireVector(name="foo", bitwidth=1)
>>> a.name
'foo'
>>> a.name = "mywire"
>>> a.name
'mywire'
nand(other)[source]

Returns the result of bitwise NANDing self and other.

If the inputs do not have the same bitwidth, the shorter input will be zero_extended() to the longer input’s bitwidth.

Example:

>>> three = pyrtl.Const(val=0b11, bitwidth=2)
>>> five = pyrtl.Const(val=0b101, bitwidth=4)
>>> output = pyrtl.Output(name="output")

>>> output <<= three.nand(five)
>>> output.bitwidth
4

>>> sim = pyrtl.Simulation()
>>> sim.step()
>>> bin(sim.inspect("output"))
'0b1110'
Parameters:

other (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 result of bitwise NANDing self and other. The returned WireVector has the same bitwidth as the longer of the two input WireVectors.

sign_extended(bitwidth)[source]

Return a sign-extended copy of self.

Sign-extension increases bitwidth by adding copies of the most significant bit of self. Example:

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

>>> output <<= input.sign_extended(bitwidth=output.bitwidth)

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

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

WireVector

Returns:

A new WireVector equal to the original WireVector but sign-extended to the specified bitwidth.

Raises:

PyrtlError – If the bitwidth specified is smaller than bitwidth.

truncate(bitwidth)[source]

Return a copy of self with its most significant bits removed.

Truncation reduces bitwidth by removing the most significant bits from self. Example:

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

>>> output <<= input.truncate(bitwidth=output.bitwidth)

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

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

bitwidth (int) – Number of bits to truncate to. This is the number of bits to keep.

Return type:

WireVector

Returns:

A new WireVector equal to the original WireVector but truncated to the specified bitwidth.

Raises:

PyrtlError – If the bitwidth specified is larger than bitwidth.

zero_extended(bitwidth)[source]

Return a zero-extended copy of self.

Zero-extension increases bitwidth by adding zero-valued high bits to self. Example:

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

>>> output <<= input.zero_extended(bitwidth=output.bitwidth)

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

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

WireVector

Returns:

A new WireVector equal to the original WireVector but zero-extended to the specified bitwidth.

Raises:

PyrtlError – If the bitwidth specified is smaller than bitwidth.

Input Pins

class pyrtl.Input(bitwidth=None, name='', block=None)[source]

Bases: WireVector

A WireVector placeholder for inputs to a Block.

Input WireVectors are placeholders for values provided during Simulation. See Simulation.step()’s provided_inputs argument. For example:

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

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

Attempting to assign an Input WireVector with the <<= (__ilshift__()) or |= (__ior__()) operators will raise PyrtlError:

>>> input = pyrtl.Input(name="input", bitwidth=1)
>>> input <<= True
Traceback (most recent call last):
...
pyrtl.pyrtlexceptions.PyrtlError: Connection ... attempted on Input

Output Pins

class pyrtl.Output(bitwidth=None, name='', block=None)[source]

Bases: WireVector

A WireVector type denoting outputs of a Block.

Attempting to use an Output WireVector as the input to any operation, such as __or__(), which implements bitwise or, will raise PyrtlInternalError:

>>> output = pyrtl.Output(name="output", bitwidth=1)
>>> foo = output | 2
Traceback (most recent call last):
...
pyrtl.pyrtlexceptions.PyrtlInternalError: error, Outputs cannot be arguments for
a net

Constants

class pyrtl.Const(val, bitwidth=None, name='', signed=False, block=None)[source]

Bases: WireVector

A WireVector representation of a constant value.

Converts from bool, int, or Verilog-style str to a constant of the specified bitwidth.

__init__(val, bitwidth=None, name='', signed=False, block=None)[source]

Construct a constant WireVector.

Warning

A constant generated with signed=True is still just a raw bitvector. All arithmetic on it is unsigned by default. The signed=True argument is only used for proper inference of WireVector size and certain bitwidth sanity checks, assuming a two’s complement representation of the constants. For signed arithmetic, use the signed_* functions in the Extended Logic and Arithmetic section.

Parameters:
  • val (int | bool | str) – The constant value. For details of how constants are coerced from int, bool, and strings (for Verilog constants), see documentation for infer_val_and_bitwidth().

  • bitwidth (int | None, default: None) – The desired bitwidth of the resulting Const. If None, the bitwidth will be inferred from val.

  • name (str, default: '') – The name of the wire. Must be unique. If none is provided, one will be autogenerated.

  • signed (bool, default: False) – Specifies if bits should be used for two’s complement.

  • block (Block, default: None) – The block under which the wire should be placed. Defaults to the working_block.

Raises:

PyrtlInternalError – If the bitwidth is too short to represent the specified constant.

val: int

The constant’s value.

This is the constant’s raw bit value, which will not match the val passed to Const’s constructor for negative values.

Example:

>>> pos = pyrtl.Const(val=3)
>>> pos.val
3

>>> neg = pyrtl.Const(val=-3, signed=True)
>>> neg.val
5
>>> bin(neg.val)
'0b101'

Conditional Assignment

pyrtl.conditional_assignment = <pyrtl.conditional._ConditionalAssignment object>

WireVectors, Registers, and MemBlocks can be conditionally assigned values based on predicates.

Conditional assignments are written with Python with statements, using two context managers:

  1. conditional_assignment, which provides the framework for specifying conditional assignments.

  2. otherwise, which specifies the ‘fall through’ case.

Conditional assignments are easiest to understand with an example:

r1 = pyrtl.Register(bitwidth=8)
r2 = pyrtl.Register(bitwidth=8)
w = pyrtl.WireVector(bitwidth=8)
mem = pyrtl.MemBlock(bitwidth=8, addrwidth=4)

a = pyrtl.Input(bitwidth=1)
b = pyrtl.Input(bitwidth=1)
c = pyrtl.Input(bitwidth=1)
d = pyrtl.Input(bitwidth=1)

with pyrtl.conditional_assignment:
    with a:
        # Set when a is True.
        r1.next |= 1
        mem[0] |= 2
        with b:
            # Set when a and b are both True.
            r2.next |= 3
    with c:
        # Set when a is False and c is True.
        r1.next |= 4
        r2.next |= 5
    with pyrtl.otherwise:
        # Set when a and c are both False.
        r2.next |= 6

    with d:
        # Set when d is True. A `with` block after an `otherwise` starts a new
        # set of conditional assignments.
        w |= 7

This conditional_assignment is equivalent to:

r1.next <<= pyrtl.select(a, 1, pyrtl.select(c, 4, r1))
r2.next <<= pyrtl.select(a, pyrtl.select(b, 3, r2), pyrtl.select(c, 5, 6))
w <<= pyrtl.select(d, 7, 0)
mem[0] <<= pyrtl.MemBlock.EnabledWrite(data=2, enable=a)

Conditional assignments are generally recommended over nested select() statements because conditional assignments are easier to read and write.

conditional_assignment accepts an optional default argument that maps from WireVector to its default value for the conditional_assignment block. defaults are not supported for MemBlock. See Conditional Assignment Defaults for more details.

See the state machine example for more examples of conditional_assignment.

pyrtl.otherwise = <pyrtl.conditional._Otherwise object>

Context manager implementing PyRTL’s otherwise under conditional_assignment.

pyrtl.currently_under_condition()[source]
Return type:

bool

Returns:

True iff execution is currently in the context of a conditional_assignment.

Conditional Assignment Defaults

Every PyRTL wire, register, and memory must have a value in every cycle. PyRTL does not support “don’t care” or X values. To satisfy this requirement, conditional assignment must always assign a value to every wire in a conditional_assignment block, even if the conditional_assignment does not specify a value. This can happen when:

  1. A condition is True, but no value is specified for a wire or register in that condition’s with block. In the example above, no value is specified for r1 in the otherwise block.

  2. No conditions are True, and there is no otherwise block. In the example above, there is no otherwise block to for the case when d is False, so no value is specified for w when d is False.

When this happens for a wire, 0 is assigned as a default value. See how a 0 appears in the final select() in the equivalent example above.

When this happens for a register, the register’s current value is assigned as a default value. See how r1 and r2 appear within the select() s in the first and second lines of the example above.

When this happens for a memory, the memory’s write port is disabled. See how the example above uses a EnabledWrite to disable writes to mem[0] when a is False.

These default values can be changed by passing a defaults dict to conditional_assignment, as seen in this example:

# Most instructions advance the program counter (`pc`) by one instruction. A few
# instructions change `pc` in special ways.
pc = pyrtl.Register(bitwidth=32)
instr = pyrtl.WireVector(bitwidth=32)
res = pyrtl.WireVector(bitwidth=32)

op = instr[:7]
ADD = 0b0110011
JMP = 0b1101111

# Use conditional_assignment's `defaults` to advance `pc` by one instruction by
# default.
with pyrtl.conditional_assignment(defaults={pc: pc + 1}):
    with op == ADD:
        res |= instr[15:20] + instr[20:25]
        # pc.next will be updated to pc + 1
    with op == JMP:
        pc.next |= pc + instr[7:]
        # res will be set to 0

Warning

conditional_assignment defaults are not supported for MemBlock.

The Conditional Assigment Operator (|=)

Conditional assignments are written with the |= (__ior__()) operator, and not the usual <<= (__ilshift__()) operator.

  • The |= operator is a conditional assignment. Conditional assignments can only be written in a conditional_assignment block.

  • The <<= operator is an unconditional assignment, even if it is written in a conditional_assignment block.

Consider this example:

w1 = pyrtl.WireVector()
w2 = pyrtl.WireVector()
with pyrtl.conditional_assignment:
    with a:
        w1 |= 1
        w2 <<= 2

Which is equivalent to:

w1 <<= pyrtl.select(a, 1, 0)
w2 <<= 2

This behavior may seem undesirable, but consider this example:

def make_adder(x: pyrtl.WireVector) -> pyrtl.WireVector:
    output = pyrtl.WireVector(bitwidth=a.bitwidth + 1)
    output <<= x + 2
    return output

w = pyrtl.WireVector()
with pyrtl.conditional_assignment:
    with a:
        w |= make_adder(b)

Which is equivalent to:

# The assignment to `output` in `make_adder` is unconditional.
w <<= pyrtl.select(a, make_adder(b), 0)

In this example the <<= in make_adder should be unconditional, even though make_adder is called from a conditional_assignment, because the top-level assignment to w is already conditional. Making the lower-level assignment to output conditional would not make sense, especially if output is used elsewhere in the circuit.