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.wire.WireVector, pyrtl.wire.Input, pyrtl.wire.Output, pyrtl.wire.Const, pyrtl.wire.Register

WireVector#

class pyrtl.wire.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

Addition

a + b

Creates an adder, returns WireVector

Subtraction

a - b

Subtraction (two’s complement)

Multiplication

a * b

Creates an multiplier, returns WireVector

Xor

a ^ b

Bitwise XOR, returns WireVector

Or

a | b

Bitwise OR, returns WireVector

And

a & b

Bitwise AND, returns WireVector

Invert

~a

Bitwise invert, returns WireVector

Less Than

a < b

Less than, return 1-bit WireVector

Less or Eq.

a <= b

Less than or Equal to, return 1-bit WireVector

Greater Than

a > b

Greater than, return 1-bit WireVector

Greater or Eq.

a >= b

Greater or Equal to, return 1-bit WireVector

Equality

a == b

Hardware to check equality, return 1-bit WireVector

Not Equal

a != b

Inverted equality check, return 1-bit WireVector

Bitwidth

len(a)

Return bitwidth of the WireVector

Assignment

a <<= b

Connect from b to a (see note below)

Bit Slice

a[3:6]

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

A note on <<= asssignment: This operator 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.

__add__(other)[source]#

Creates a LogicNet that adds two wires together into a single WireVector.

Return WireVector:

Returns the result wire of the operation. The resulting wire has one more bit than the longer of the two input wires.

Addition is compatible with two’s complement signed numbers.

Examples:

temp = a + b  # simple addition of two WireVectors
temp = a + 5  # you can use integers
temp = a + 0b110  # you can use other integers
temp = a + "3'h7"  # compatable verilog constants work too
__ilshift__(other)[source]#

Wire assignment operator (assign other to self).

Example:

i = pyrtl.Input(8, 'i')
t = pyrtl.WireVector(8, 't')
t <<= i
__init__(bitwidth=None, name='', block=None)[source]#

Construct a generic WireVector.

Parameters:
  • bitwidth (int) – If no bitwidth is provided, it will be set to the minimum number of bits to represent this wire

  • block (Block) – The block under which the wire should be placed. Defaults to the working block

  • name (str) – The name of the wire referred to in some places. Must be unique. If none is provided, one will be autogenerated

Returns:

a WireVector object

Examples:

data = pyrtl.WireVector(8, 'data')  # visible in trace as "data"
ctrl = pyrtl.WireVector(1)  # assigned tmp name, not visible in traces by default
temp = pyrtl.WireVector()  # temporary with width to be defined later
temp <<= data  # this this case temp will get the bitwdith of 8 from data
__len__()[source]#

Get the bitwidth of a WireVector.

Return integer:

Returns the length (i.e. bitwidth) of the WireVector in bits.

Note that WireVectors do not need to have a bitwidth defined when they are first allocated. They can get it from a <<= assignment later. However, if you check the len of WireVector with undefined bitwidth it will throw PyrtlError.

__mul__(other)[source]#

Creates a LogicNet that multiplies two different WireVectors.

Return WireVector:

Returns the result wire of the operation. The resulting wire’s bitwidth is the sum of the two input wires’ bitwidths.

Multiplication is not compatible with two’s complement signed numbers.

__sub__(other)[source]#

Creates a LogicNet that subtracts the right wire from the left one.

Return WireVector:

Returns the result wire of the operation. The resulting wire has one more bit than the longer of the two input wires.

Subtraction is compatible with two’s complement signed numbers.

property bitmask#

A property holding a bitmask of the same length as this WireVector. Specifically it is an integer with a number of bits set to 1 equal to the bitwidth of the WireVector.

It is often times useful to “mask” an integer such that it fits in the the number of bits of a WireVector. As a convenience for this, the bitmask property is provided. As an example, if there was a 3-bit WireVector a, a call to a.bitmask() should return 0b111 or 0x7.

property name#

A property holding the name (a string) of the WireVector, can be read or written. For example: print(a.name) or a.name = 'mywire'.

nand(other)[source]#

Creates a LogicNet that bitwise nands two WireVectors together to a single WireVector.

Return WireVector:

Returns WireVector of the nand operation.

sign_extended(bitwidth)[source]#

Generate a new sign extended WireVector derived from self.

Return WireVector:

Returns a new WireVector equal to the original WireVector sign extended to the specified bitwidth.

If the bitwidth specified is smaller than the bitwidth of self, then PyrtlError is thrown.

truncate(bitwidth)[source]#

Generate a new truncated WireVector derived from self.

Return WireVector:

Returns a new WireVector equal to the original WireVector but truncated to the specified bitwidth.

If the bitwidth specified is larger than the bitwidth of self, then PyrtlError is thrown.

zero_extended(bitwidth)[source]#

Generate a new zero extended WireVector derived from self.

Return WireVector:

Returns a new WireVector equal to the original WireVector zero extended to the specified bitwidth.

If the bitwidth specified is smaller than the bitwidth of self, then PyrtlError is thrown.

Input Pins#

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

Bases: WireVector

A WireVector type denoting inputs to a block (no writers).

__init__(bitwidth=None, name='', block=None)[source]#

Construct a generic WireVector.

Parameters:
  • bitwidth (int) – If no bitwidth is provided, it will be set to the minimum number of bits to represent this wire

  • block (Block) – The block under which the wire should be placed. Defaults to the working block

  • name (str) – The name of the wire referred to in some places. Must be unique. If none is provided, one will be autogenerated

Returns:

a WireVector object

Examples:

data = pyrtl.WireVector(8, 'data')  # visible in trace as "data"
ctrl = pyrtl.WireVector(1)  # assigned tmp name, not visible in traces by default
temp = pyrtl.WireVector()  # temporary with width to be defined later
temp <<= data  # this this case temp will get the bitwdith of 8 from data

Output Pins#

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

Bases: WireVector

A WireVector type denoting outputs of a block (no readers).

Even though Output seems to have valid ops such as __or__ , using them will throw an error.

__init__(bitwidth=None, name='', block=None)[source]#

Construct a generic WireVector.

Parameters:
  • bitwidth (int) – If no bitwidth is provided, it will be set to the minimum number of bits to represent this wire

  • block (Block) – The block under which the wire should be placed. Defaults to the working block

  • name (str) – The name of the wire referred to in some places. Must be unique. If none is provided, one will be autogenerated

Returns:

a WireVector object

Examples:

data = pyrtl.WireVector(8, 'data')  # visible in trace as "data"
ctrl = pyrtl.WireVector(1)  # assigned tmp name, not visible in traces by default
temp = pyrtl.WireVector()  # temporary with width to be defined later
temp <<= data  # this this case temp will get the bitwdith of 8 from data

Constants#

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

Bases: WireVector

A WireVector representation of a constant value.

Converts from bool, integer, or Verilog-style strings to a constant of the specified bitwidth. If the bitwidth is too short to represent the specified constant, then an error is raised. If a positive integer is specified, the bitwidth can be inferred from the constant. If a negative integer is provided in the simulation, it is converted to a two’s complement representation of the specified bitwidth.

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

Construct a constant implementation at initialization.

Parameters:
  • val (int, bool, or str) – the value for the const WireVector

  • bitwidth (int) – the desired bitwidth of the resulting const

  • signed (bool) – specify if bits should be used for two’s complement

Returns:

a WireVector object representing a const wire

Descriptions for all parameters not listed above can be found at WireVector.__init__()

For details of how constants are converted fron int, bool, and strings (for verilog constants), see documentation for the helper function infer_val_and_bitwidth. Please note that a constant generated with signed=True is still just a raw bitvector and all arthimetic 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.

Conditionals#

Conditional assignment of registers and WireVectors based on a predicate.

The management of selected assignments is expected to happen through the “with” blocks which will ensure that the region of execution for which the condition should apply is well defined. It is easiest to see with an example:

r1 = Register()
r2 = Register()
w3 = WireVector()
with conditional_assignment:
    with a:
        r1.next |= i  # set when a is true
        with b:
            r2.next |= j  # set when a and b are true
    with c:
        r1.next |= k  # set when a is false and c is true
        r2.next |= k
    with otherwise:
        r2.next |= l  # a is false and c is false

    with d:
        w3.next |= m  # d is true (assignments must be independent)

This is equivalent to:

r1.next <<= select(a, i, select(c, k, default))
r2.next <<= select(a, select(b, j, default), select(c, k, l))
w3 <<= select(d, m, 0)

This functionality is provided through two instances: conditional_update, which is a context manager (under which conditional assignements can be made), and otherwise, which is an instance that stands in for a ‘fall through’ case. The details of how these should be used, and the difference between normal assignments and condtional assignments, described in more detail in the state machine example in examples/example3-statemachine.py.

There are instances where you might want a wirevector to be set to a certain value in all but certain with blocks. For example, say you have a processor with a PC register that is normally updated to PC + 1 after each cycle, except when the current instruction is a branch or jump. You could represent that as follows:

pc = pyrtl.Register(32)
instr = pyrtl.WireVector(32)
res = pyrtl.WireVector(32)

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

with conditional_assignment(
    defaults={
        pc: pc + 1,
        res: 0
    }
):
    with op == ADD:
        res |= instr[15:20] + instr[20:25]
        # pc will be updated to pc + 1
    with op == JMP:
        pc.next |= pc + instr[7:]
        # res will be set to 0

In addition to the conditional context, there is a helper function currently_under_condition() which will test if the code where it is called is currently elaborating hardware under a condition.

pyrtl.conditional.currently_under_condition()[source]#

Returns True if execution is currently in the context of a _ConditionalAssignment.

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

Context providing functionality of PyRTL otherwise.

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