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).
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 ofmyvector
,myvector[-1]
takes the most significant bit, andmyvector[-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 doa = b
it would lose the old value ofa
and simply overwrite it with a new value, in this case with a reference to WireVectorb
. In contrasta <<= b
does not overwritea
, 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 thelen
of WireVector with undefined bitwidth it will throwPyrtlError
.
- __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 WireVectora
, a call toa.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)
ora.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 withsigned=True
is still just a raw bitvector and all arthimetic on it is unsigned by default. Thesigned=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>#