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.WireVector(bitwidth=None, name='', block=None)[source]¶
The main class for describing the connections between operators.
WireVectorsact much like a list of wires, except that there is no “contained” type, each slice of aWireVectoris itself aWireVector(even if it just contains a single “bit” of information). The least significant bit of the wire is at index0and normal list slicing syntax applies (i.e.myvector[0:5]makes a new vector from the bottom5bits ofmyvector,myvector[-1]takes the most significant bit, andmyvector[-4:]takes the4most significant bits).Operation
Syntax
Function
Documentation
Addition
a + bCreates an unsigned adder, returns
WireVectorSubtraction
a - bCreates an unsigned subtracter, returns
WireVectorMultiplication
a * bCreates an unsigned multiplier, returns
WireVectorXor
a ^ bBitwise XOR, returns
WireVectorOr
a | bBitwise OR, returns
WireVectorAnd
a & bBitwise AND, returns
WireVectorInvert
~aBitwise invert, returns
WireVectorLess Than
a < bUnsigned less than, return 1-bit
WireVectorLess or Eq.
a <= bUnsigned less than or equal to, return 1-bit
WireVectorGreater Than
a > bUnsigned greater than, return 1-bit
WireVectorGreater or Eq.
a >= bUnsigned greater or equal to, return 1-bit
WireVectorEquality
a == bHardware to check equality, return 1-bit
WireVectorNot Equal
a != bInverted equality check, return 1-bit
WireVectorBitwidth
len(a)Return bitwidth of the
WireVectorAssignment
a <<= bConnect from b to a (see Note below)
Bit Slice
a[3:6]Selects bits from
WireVector, in this case bits 3,4,5Note
<<=(__ilshift__()) is how you “drive” an already created wire with an existing wire. If you were to doa = bit would lose the old value ofaand simply overwrite it with a new value, in this case with a reference toWireVectorb. In contrasta <<= bdoes not overwritea, but simply wires the two together.WireVectorCoercion¶Most PyRTL functions that accept
WireVectorsas arguments will also accept any type thatas_wires()can coerce toWireVector. Examples includeint,bool, andstr.intwill be coerced to an unsignedConstWireVectorwith the minimum bitwidth required for the integer. In the following example, a 2-bitConstis implicitly created for2:>>> 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
boolwill be coerced to aConstwithbitwidth1. In the following example, a 1-bitConstis implicitly created forTrue:>>> 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
strwill be interpreted as a Verilog-style string constant. In the following example, a 4-bitConstis 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'
WireVectorEquality¶WireVector.__eq__()generates logic that dynamically reports if two wires carry the same values.WireVector.__eq__()returns a 1-bitWireVector, not abool, and attempting to convert aWireVectorto aboolraises aPyrtlError. This behavior is incompatible with Python’s data model, which can cause problems.For example, you can not statically check if two
WireVectorsare equal with==. Statically checking forWireVectorequality 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
WireVectortoboolresults from Python attempting to convert the 1-bitWireVectorreturned by__eq__()toTrueorFalsewhile evaluating theifstatement’s condition.Instead, you can statically check if two
WireVectorsrefer to the same object withis:>>> w1 is not w2 True >>> temp = w1 >>> temp is w1 True >>> temp is w2 False
Be careful when using Python features that depend on
==withWireVectors. This often comes up when checking if aWireVectoris in alistwithin, which does not work becauseinfalls back on checking each item in thelistfor equality with==:>>> l = [w1] >>> w2 in l Traceback (most recent call last): ... pyrtl.pyrtlexceptions.PyrtlError: cannot convert WireVector to compile-time boolean...
Most other
listoperations work, so you can storeWireVectorsin alistif you avoid using theinoperator:>>> len(l) 1 >>> l[0] is w1 True >>> [(w.name, w.bitwidth) for w in l] [('w1', 1)]
WireVectorsdefine a standard__hash__()method, so if you need to check if aWireVectoris in a container, use asetordict. 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
selfandother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A
WireVectorcontaining the result of addingselfandother. The returnedWireVectorhas abitwidthequal to the longer of the two inputWireVectors, plus one.
- __and__(other)[source]¶
Returns the result of bitwise ANDing
selfandother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A
WireVectorcontaining the result of bitwise ANDingselfandother. The returnedWireVectorhas the samebitwidthas the longer of the two inputWireVectors.
- __eq__(other)[source]¶
Checks if
selfis equal toother. Returns a one-bitWireVector.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.Warning
This definition of
__eq__()returnsWireVector, notbool, 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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A one-bit
WireVectorindicating ifselfis equal toother.
- __ge__(other)[source]¶
Checks if
selfis greater than or equal toother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A one-bit
WireVectorindicating ifselfis greater than or equal toother.
- __getitem__(item)[source]¶
Returns a
WireVectorcontaining a subset of the wires inself.There are two ways to retrieve
WireVectorsubsets:By
intindex, for examplewire[2]. This returns aWireVectorwithbitwidth1.By
slice, for examplewire[3:5]. Slicing uses the usual[start:stop:step]notation.If
startis omitted, it defaults to index0, the least significant bit.If
stopis omitted, it defaults tobitwidth - 1, the most significant bit.If
stepis omitted, it defaults to1.
bitwidthis added to negativestartandstopindices, so negative indices count backwards from just beyond the most significant bit. Indexbitwidth - 1and index-1both refer to the most significant bit.If
stepis negative, the wires will be returned in reverse order.Suppose we have a
WireVectorinput, withbitwidth8:input = WireVector(name="input", bitwidth=8)
We can access individual wires of
inputwith 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 anint, specifies the index of a single-bit wire to return. If aslice, specifies a subset of wires to return, asstart:stop:step.- Return type:
- Returns:
A
WireVectorcontaining the wires selected byitemfromself.
- __gt__(other)[source]¶
Checks if
selfis greater thanother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A one-bit
WireVectorindicating ifselfis greater thanother.
- __ilshift__(other)[source]¶
Wire assignment operator
<<=, connectsothertoself.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 nobitwidthis provided, it will be set to the minimum number of bits needed to represent this wire.block (
Block, default:None) – TheBlockunder 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 withSimulation.inspect(), and this wire will appear in traces generated bySimulationTrace.render_trace().
- __invert__()[source]¶
Returns a
WireVectorcontaining the bitwise inversion ofself.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:
- Returns:
A
WireVectorcontaining the result of bitwise invertingself. The returnedWireVectorhas the samebitwidthasself.
- __ior__(other)[source]¶
Conditional assignment operator
|=, only usable underconditional_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
selfis less than or equal toother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A one-bit
WireVectorindicating ifselfis less than or equal toother.
- __len__()[source]¶
Return the
WireVector’sbitwidth.WireVectorscan be constructed without specifying abitwidth. TheseWireVectorswill have abitwidthofNoneuntil they infer abitwidthfrom an<<=(__ilshift__()) assignment.__len__()is equivalent tobitwidth, except that__len__()raises an exception whenbitwidthisNone.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:
- Returns:
Returns the length (
bitwidth) of theWireVector, in bits.- Raises:
PyrtlError – If the
bitwidthis not yet defined.
- __lt__(other)[source]¶
Checks if
selfis less thanother. Returns a one-bitWireVector.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A one-bit
WireVectorindicating ifselfis less thanother.
- __mul__(other)[source]¶
Returns the result of multiplying
selfandother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A
WireVectorcontaining the result of multiplyingselfandother. The returnedWireVectorhas abitwidthequal to twice the length of the longer input.
- __ne__(other)[source]¶
Checks if
selfis not equal toother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A one-bit
WireVectorindicating ifselfis not equal toother.
- __or__(other)[source]¶
Returns the result of bitwise ORing
selfandother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A
WireVectorcontaining the result of bitwise ORingselfandother. The returnedWireVectorhas the samebitwidthas the longer of the two inputWireVectors.
- __sub__(other)[source]¶
Returns the result of subtracting
selfandother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A
WireVectorcontaining the result of subtractingselfandother. The returnedWireVectorhas abitwidthequal to the longer of the two inputWireVectors, plus one.
- __xor__(other)[source]¶
Returns the result of bitwise XORing
selfandother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A
WireVectorcontaining the result of bitwise XORingselfandother. The returnedWireVectorhas the samebitwidthas the longer of the two inputWireVectors.
- property bitmask: int¶
A property holding a bitmask of the same length as this
WireVector.bitmaskis anintwith a number of bits set to 1 equal to thebitwidthof theWireVector.It is often useful to “mask” an integer such that it fits in the number of bits of a
WireVector, so thebitmaskproperty 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.
WireVectorscan be constructed without specifying abitwidth. TheseWireVectorswill have abitwidthofNoneuntil they infer abitwidthfrom an<<=(__ilshift__()) assignment.bitwidthis equivalent to__len__(), except that__len__()raises an exception whenbitwidthisNone.Example:
>>> w = pyrtl.WireVector() >>> w.bitwidth None >>> w <<= pyrtl.Const(val=42, bitwidth=6) >>> w.bitwidth 6
-
block:
Block¶ The
Blockthat thisWireVectorbelongs 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
selfandother.If the inputs do not have the same
bitwidth, the shorter input will bezero_extended()to the longer input’sbitwidth.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) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
A
WireVectorcontaining the result of bitwise NANDingselfandother. The returnedWireVectorhas the samebitwidthas the longer of the two inputWireVectors.
- sign_extended(bitwidth)[source]¶
Return a sign-extended copy of
self.Sign-extension increases
bitwidthby adding copies of the most significant bit ofself. 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:
- Returns:
A new
WireVectorequal to the originalWireVectorbut sign-extended to the specifiedbitwidth.- Raises:
PyrtlError – If the
bitwidthspecified is smaller thanbitwidth.
- truncate(bitwidth)[source]¶
Return a copy of
selfwith its most significant bits removed.Truncation reduces
bitwidthby removing the most significant bits fromself. 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:
- Returns:
A new
WireVectorequal to the originalWireVectorbut truncated to the specifiedbitwidth.- Raises:
PyrtlError – If the
bitwidthspecified is larger thanbitwidth.
- zero_extended(bitwidth)[source]¶
Return a zero-extended copy of
self.Zero-extension increases
bitwidthby adding zero-valued high bits toself. 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:
- Returns:
A new
WireVectorequal to the originalWireVectorbut zero-extended to the specifiedbitwidth.- Raises:
PyrtlError – If the
bitwidthspecified is smaller thanbitwidth.
Input Pins¶
- class pyrtl.Input(bitwidth=None, name='', block=None)[source]¶
Bases:
WireVectorA
WireVectorplaceholder for inputs to aBlock.InputWireVectorsare placeholders for values provided duringSimulation. SeeSimulation.step()’sprovided_inputsargument. 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
InputWireVectorwith the<<=(__ilshift__()) or|=(__ior__()) operators will raisePyrtlError:>>> 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:
WireVectorA
WireVectortype denoting outputs of aBlock.Attempting to use an
OutputWireVectoras the input to any operation, such as__or__(), which implements bitwise or, will raisePyrtlInternalError:>>> 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:
WireVectorA
WireVectorrepresentation of a constant value.Converts from
bool,int, or Verilog-stylestrto a constant of the specifiedbitwidth.- __init__(val, bitwidth=None, name='', signed=False, block=None)[source]¶
Construct a constant
WireVector.Warning
A constant generated with
signed=Trueis still just a raw bitvector. All arithmetic on it is unsigned by default. Thesigned=Trueargument is only used for proper inference ofWireVectorsize and certain bitwidth sanity checks, assuming a two’s complement representation of the constants. For signed arithmetic, use thesigned_*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 forinfer_val_and_bitwidth().bitwidth (
int|None, default:None) – The desired bitwidth of the resultingConst. IfNone, the bitwidth will be inferred fromval.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
bitwidthis too short to represent the specified constant.
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:
conditional_assignment, which provides the framework for specifying conditional assignments.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:
- Returns:
Trueiff execution is currently in the context of aconditional_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:
A condition is
True, but no value is specified for a wire or register in that condition’swithblock. In the example above, no value is specified forr1in theotherwiseblock.No conditions are
True, and there is nootherwiseblock. In the example above, there is nootherwiseblock to for the case whendisFalse, so no value is specified forwwhendisFalse.
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 aconditional_assignmentblock.The
<<=operator is an unconditional assignment, even if it is written in aconditional_assignmentblock.
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.