Helper Functions¶
Cutting and Extending WireVectors¶
The functions below provide ways of combining, slicing, and extending
WireVectors in ways that are often useful in hardware
design. The functions below extend those member functions of the
WireVector class itself (which provides support for the Python
builtin len(), slicing e.g. wire[3:6],
zero_extended(), sign_extended(), and
many operators such as addition and multiplication).
- pyrtl.concat(*args)[source]¶
Concatenates multiple
WireVectorsinto a singleWireVector.Concatenates any number of arguments. The right-most argument is the least significant bits of the result.
Note
If you have a
listof arguments toconcattogether, you probably want index 0 to be the least significant bit. If so, and you unpack thelistintoconcat’sargs, the result will be backwards. The functionconcat_list()is provided specifically for that case.Note
Consider using
wire_struct()orwire_matrix()instead, which helps with consistently disassembling, naming, and reassembling fields.Example that concatenates two bytes into a 16-bit
output:>>> msb = pyrtl.Input(name="msb", bitwidth=8) >>> lsb = pyrtl.Input(name="lsb", bitwidth=8) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.concat(msb, lsb) >>> output.bitwidth 16 >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"msb": 0xab, "lsb": 0xcd}) >>> hex(sim.inspect("output")) '0xabcd'
- Parameters:
args (
WireVector|int|str|bool) – Inputs to concatenate, with the most significant bits first. Each input can be aWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
WireVectorwithbitwidthequal to the sum of allargs’bitwidths.
- pyrtl.concat_list(wire_list)[source]¶
Concatenates a list of
WireVectorsinto a singleWireVector.This take a
listofWireVectorsand concats them all into a singleWireVector, with the element at index 0 serving as the least significant bits. This is useful when you have a variable number ofWireVectorsto concatenate, otherwiseconcat()is prefered.Note
Consider using
wire_struct()orwire_matrix()instead, which helps with consistently disassembling, naming, and reassembling fields.Example that concatenates two bytes into a 16-bit
output:>>> msb = pyrtl.Input(name="msb", bitwidth=8) >>> lsb = pyrtl.Input(name="lsb", bitwidth=8) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.concat_list([lsb, msb]) >>> output.bitwidth 16 >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"msb": 0xab, "lsb": 0xcd}) >>> hex(sim.inspect("output")) '0xabcd'
- Parameters:
wire_list (
list[WireVector|int|str|bool]) – List of inputs to concatenate. Each input can be aWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
WireVectorwithbitwidthequal to the sum of allwire_listbitwidths.
- pyrtl.match_bitwidth(*args, signed=False)[source]¶
Matches multiple
WireVectorbitwidthsvia zero- or sign-extension.WireVectorswith shorterbitwidthswill be to match the longestbitwidthinargs.WireVectorswill besign_extended()orzero_extended(), depending onsigned.Example with sign-extension:
>>> a = pyrtl.Const(-1, name="a_short", signed=True, bitwidth=2) >>> b = pyrtl.Const(-3, name="b", signed=True, bitwidth=4) >>> a, b = match_bitwidth(a, b, signed=True) >>> a.name = "a_long" >>> a.bitwidth, b.bitwidth (4, 4) >>> sim = pyrtl.Simulation() >>> sim.step() >>> bin(sim.inspect("b")) '0b1101' >>> bin(sim.inspect("a_short")) '0b11' >>> bin(sim.inspect("a_long")) '0b1111'
- Parameters:
args (
WireVector) –WireVectorsof which to matchbitwidthsigned (
bool, default:False) – IfTrue, extend shorterWireVectorswithsign_extended(). Otherwise, extend withzero_extended().
- Return type:
- Returns:
tupleofWireVectors, in the same order they appeared inargs, all withbitwidthequal to the longestbitwidthinargs.
- pyrtl.truncate(wirevector_or_integer, bitwidth)[source]¶
Returns a
WireVectoror integer truncated to the specifiedbitwidth.Truncation removes the most significant bits of
wirevector_or_integer, leaving a result that is onlybitwidthbits wide. Forintsthis is performed with a simple bitmask of sizebitwidth, and returns anint. ForWireVectorsthe function callsWireVector.truncate()and returns aWireVectorwith the specifiedbitwidth.Examples:
>>> truncate(0b101_001, bitwidth=3) 1 >>> bin(truncate(0b111_101, bitwidth=3)) '0b101' >>> # -1 is 0b1111111... with the number of 1-bits equal to the bitwidth. Python >>> # ints are arbitrary-precision, so this can produce any number of 1-bits. >>> bin(truncate(-1, bitwidth=3)) '0b111' >>> input = pyrtl.Input(name="input", bitwidth=8) >>> output = truncate(input, bitwidth=4) >>> output.name = "output" >>> output.bitwidth 4 >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input": 0xab}) >>> hex(sim.inspect("output")) '0xb'
- Parameters:
wirevector_or_integer (
WireVector|int) – AWireVectororintto truncate.bitwidth (
int) – The length to whichwirevector_or_integershould be truncated.
- Return type:
- Returns:
A truncated
WireVectororint, depending on the input type.
- pyrtl.chop(w, *segment_widths)[source]¶
Returns a list of
WireVectors, each a slice of the originalw.This function chops a
WireVectorinto a set of smallerWireVectorsof different lengths. It is most useful when multiple “fields” are contained with a singleWireVector, for example when breaking apart an instruction.For example, if you wish to break apart a 32-bit MIPS I-type (Immediate) instruction you know it has an 6-bit opcode, 2 5-bit operands, and 16-bit offset. You could take each of those slices in absolute terms:
offset=instr[0:16],rt=instr[16:21]and so on, but then you have to do the arithmetic yourself. With this function you can do all the fields at once which can be seen in the examples below.As a check,
chopwill throw an error if the sum of the lengths of the fields given is not the same as the length of theWireVectortochop. Note also thatchopassumes that the “rightmost” arguments are the least signficant bits (just likeconcat()) which is normal for hardware functions but makes the list order a little counter intuitive.Note
Consider using
wire_struct()orwire_matrix()instead, which helps with consistently disassembling, naming, and reassembling fields.Example:
>>> input = pyrtl.Input(name="input", bitwidth=12) >>> high, middle, low = pyrtl.chop(input, 4, 4, 4) >>> high.name = "high" >>> middle.name = "middle" >>> low.name = "low" >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input": 0xabc}) >>> hex(sim.inspect("high")) '0xa' >>> hex(sim.inspect("middle")) '0xb' >>> hex(sim.inspect("low")) '0xc'
- Parameters:
w (
WireVector) – TheWireVectorto be chopped up into segmentssegment_widths (
int) – Additional arguments are integers which are bitwidths
- Return type:
- Returns:
A list of
WireVectorseach with a proper segment width.
- pyrtl.wire_struct(wire_struct_spec)[source]¶
Decorator that assigns names to
WireVectorslices.@wire_structassigns names to non-overlappingWireVectorslices. Suppose we have an 8-bit wideWireVectorcalledbyte. We can refer to all 8 bits with the namebyte, but@wire_structlets us refer to slices by name, for example we could name the high 4 bitsbyte.highand the low 4 bitsbyte.low. Without@wire_struct, we would refer to these slices asbyte[4:8]andbyte[0:4], which are prone to off-by-one errors and harder to read.Note
See example1.2-wire-struct.py for additional examples.
The example
Byte@wire_structcan be defined as:>>> @wire_struct ... class Byte: ... high: 4 ... low: 4
Construction¶
Once a
@wire_structclass is defined, it can be instantiated by providing drivers for all of its wires. This can be done in two ways:Provide a driver for each component wire, for example:
>>> byte = Byte(high=0xA, low=0xB)
Note how the component names (
high,low) are used as keyword args for the constructor. Drivers must be provided for all components.Provide a driver for the entire
@wire_struct, for example:>>> byte = Byte(Byte=0xAB)
Note how the class name (
Byte) is used as a keyword arg for the constructor.
Accessing Slices¶
After instantiating a
@wire_struct, the instance functions as aWireVectorcontaining all the wires. For example,bytefunctions as aWireVectorwith bitwidth 8:>>> byte = Byte(Byte=0xAB) >>> byte.bitwidth 8
The named slice can be accessed through the
.operator (__getattr__), for examplebyte.highandbyte.low, which both function asWireVectorwith bitwidth 4:>>> byte = Byte(Byte=0xAB) >>> byte.high.bitwidth 4 >>> byte.low.bitwidth 4
Both the instance and the slices are first-class
WireVector, so they can be manipulated with all the usual PyRTL operators.len()returns the number of components in the@wire_struct:>>> byte = Byte(Byte=0xAB) >>> len(byte) 2
Naming¶
A
@wire_structcan be assigned a name in the usual way:>>> byte = Byte(name="my_byte", high=0xA, low=0xB)
When a
@wire_structis assigned a name (my_byte), its components will be assigned dotted names (my_byte.high,my_byte.low):>>> byte.high.name 'my_byte.high' >>> byte.low.name 'my_byte.low'
Warning
All
@wire_structnames are only set during construction. You can later rename a@wire_structor its components, but those changes are local, and will not propagate to other@wire_structcomponents.Composition¶
@wire_structcan be composed with itself, and withwire_matrix(). For example, we can define aPixelthat contains threeBytes:@pyrtl.wire_struct class Pixel: red: Byte green: Byte blue: Byte
Drivers must be specified for all components, but they can be specified at any level. All these examples construct an equivalent
@wire_struct:pixel = Pixel(Pixel=0xABCDEF) pixel = Pixel(red=0xAB, green=0xCD, blue=0xEF) pixel = Pixel(red=Byte(high=0xA, low=0xB), green=0xCD, blue=0xEF) pixel = Pixel(red=Byte(high=0xA, low=0xB), green=Byte(high=0xC, low=0xD), blue=0xEF)
Hierarchical
@wire_structcomponents are accessed by composing.operators:pixel pixel.red pixel.red.high pixel.red.low pixel.green pixel.green.high pixel.green.low pixel.blue pixel.blue.high pixel.blue.low
@wire_structcan be composed withwire_matrix():Word = pyrtl.wire_matrix(component_schema=8, size=4) @pyrtl.wire_struct class CacheLine: address: Word data: Word valid: 1 cache_line = CacheLine(address=0x01234567, data=0x89ABCDEF, valid=1)
Leaf-level components can be accessed by combining the
.and[]operators, for examplecache_line.address[3].Types¶
You can change the type of a
@wire_struct’s components to aWireVectorsubclass likeInputorOutputwith thecomponent_typeconstructor argument:# Generates Outputs named ``output_byte.low`` and ``output_byte.high``. >>> byte = Byte(name="output_byte", component_type=pyrtl.Output, ... Byte=0xCD) >>> isinstance(byte.high, pyrtl.Output) True >>> byte.high.name 'output_byte.high'
You can also change the type of the
@wire_structitself with theconcatenated_typeconstructor argument:# Generates an Input named ``input_byte``. >>> input_byte = Byte(name="input_byte", concatenated_type=pyrtl.Input)
Note
No values are specified for
input_bytebecause its value is not known until simulation time.
- pyrtl.wire_matrix(component_schema, size)[source]¶
Returns a class that assigns numbered indices to
WireVectorslices.wire_matrixassigns numbered indices to non-overlappingWireVectorslices.wire_matrixis very similar towire_struct(), so readwire_struct()’s documentation first.Note
See example1.2-wire-struct.py for additional examples.
An example 32-bit
Wordwire_matrix, which represents a group of four bytes, can be defined as:>>> Word = wire_matrix(component_schema=8, size=4)
Note
wire_matrixreturns a class, likenamedtuple().Construction¶
Once a
wire_matrixclass is defined, it can be instantiated by providing drivers for all of its wires. This can be done in two ways:# Provide a driver for each component, most significant bits first. >>> word = Word(values=[0x89, 0xAB, 0xCD, 0xEF]) # Provide a driver for all components. >>> word = Word(values=[0x89ABCDEF])
Note
When specifying drivers for each component, the most significant bits are specified first.
After instantiating a
wire_matrix, regardless of how it was constructed, the instance functions as aWireVectorcontaining all the wires, sowordfunctions as aWireVectorwith bitwidth 32. The named slice can be accessed with square brackets (__getitem__), for exampleword[0]andword[3], which both function asWireVectorwith bitwidth 8.word[0]refers to the most significant byte, andword[3]refers to the least significant byte. Both the instance and the slices are first-classWireVector, so they can be manipulated with all the usual PyRTL operators.Naming¶
A
wire_matrixcan be assigned a name in the usual way:# The whole Word is named 'w', so the components will have names # w[0], w[1], ... >>> word = Word(name="w", values=[0x89, 0xAB, 0xCD, 0xEF]) >>> word[0].name 'w[0]'
Composition¶
wire_matrixcan be composed with itself andwire_struct(). For example, we can define some multi-dimensional byte arrays:Array1D = wire_matrix(component_schema=8, size=2) Array2D = wire_matrix(component_schema=Array1D, size=2)
Drivers must be specified for all components, but they can be specified at any level. All these examples construct an equivalent
wire_matrix:array_2d = Array2D(values=[0x89AB, 0xCDEF]) array_2d = Array2D(values=[Array1D(values=[0x89, 0xAB]), 0xCDEF]) array_2d = Array2D(values=[Array1D(values=[0x89, 0xAB]), Array1D(values=[0xCD, 0xEF])])
Accessing Slices¶
Hierarchical components are accessed by composing
[]operators, for example:print(array_2d[0][0].bitwidth) # Prints 8. print(array_2d[0][1].bitwidth) # Prints 8.
When
wire_matrixis composed withwire_struct(), components can be accessed by combining the[]and.operators:@wire_struct class Byte: high: 4 low: 4 Array1D = wire_matrix(component_schema=Byte, size=2) array_1d = Array1D(values=[0xAB, 0xCD]) print(array_1d[0].high.bitwidth) # Prints 4.
len()returns the number of components in thewire_matrix:print(len(array_1d)) # Prints '2'.
Types¶
You can change the type of a
wire_matrix’s components with thecomponent_typeconstructor argument:# Generates Outputs named ``output_word[0]``, ``output_word[1]``, ... >>> word = Word(name="output_word", ... component_type=pyrtl.Output, ... values=[0x89ABCDEF]) >>> isinstance(word[1], pyrtl.Output) True >>> word[1].name 'output_word[1]'
You can change the type of the
wire_matrixitself with theconcatenated_typecnstructor argument:# Generates an Input named ``input_word``. >>> word = Word(name="input_word", concatenated_type=pyrtl.Input)
Note
No values are specified for
input_wordbecause its value is not known until simulation time.
Coercion to WireVector¶
In PyRTL there is only one function in charge of coercing values into
WireVectors, and that is as_wires(). This function
is called in almost all helper functions and classes to manage the mixture of
constants and WireVectors that naturally occur in
hardware development.
See WireVector Coercion for examples and more details.
- pyrtl.as_wires(val, bitwidth=None, truncating=True, block=None)[source]¶
Convert
valto aWireVector.valmay be aWireVector,int(includingIntEnum),str, orbool.as_wiresis mainly used to coerce values intoWireVectors(for example, operations such asx + 1where1needs to be converted to aConstWireVector). See WireVector Coercion. An example:>>> def make_my_hardware(a, b): ... a = as_wires(a) ... b = as_wires(b) ... return (a + b) & 0xf >>> input = pyrtl.Input(name="input", bitwidth=8) >>> output = pyrtl.Output(name="output", bitwidth=4) >>> output <<= make_my_hardware(input, 7) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input": 20}) >>> sim.inspect("output") 11 >>> (20 + 7) % 16 11
In the example above,
as_wireswill convert the7toConst(7)but keepinputunchanged.- Parameters:
val (
WireVector|int|str|bool) – AWireVector, or a constant value that can be converted into aConst.bitwidth (
int|None, default:None) – Thebitwidthof the resultingWireVector.truncating (
bool, default:True) – Determines whether bits will be dropped to achieve the desiredbitwidthifvalis too long (ifTrue, the most-significant bits will be dropped).block (
Block, default:None) –Blockto use for the returnedWireVector. Defaults to the working_block.
- Return type:
Control Flow Hardware¶
- pyrtl.mux(index, *mux_ins, default=None)[source]¶
Multiplexer returning a wire from
mux_insaccording toindex.index0corresponds to the firstmux_inargument.Note
If
indexis a 1-bit predicate (something that isTrueorFalserather than an integer), it is clearer to useselect(), whose argument order is consistent with the ternary operators in C-style languages (condition ? truecase : falsecase).Example of multiplexing between four values:
>>> index = pyrtl.Input(name="index", bitwidth=2) >>> selected = pyrtl.WireVector(name="selected") >>> selected <<= pyrtl.mux(index, 4, 5, 6, 7) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"index": 0}) >>> sim.inspect("selected") 4 >>> sim.step(provided_inputs={"index": 3}) >>> sim.inspect("selected") 7
Example with
default:>>> index = pyrtl.Input(name="index", bitwidth=2) >>> selected = pyrtl.WireVector(name="selected") >>> selected <<= pyrtl.mux(index, 4, 5, default=9) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"index": 1}) >>> sim.inspect("selected") 5 >>> sim.step(provided_inputs={"index": 2}) >>> sim.inspect("selected") 9
- Parameters:
index (
WireVector|int|str|bool) – Multiplexer’s selection input. Can be aWireVector, or any type that can be coerced toWireVectorbyas_wires().mux_ins (
WireVector|int|str|bool) –WireVectorarguments to select from. Can be aWireVector, or any type that can be coerced toWireVectorbyas_wires().default (
WireVector|int|str|bool, default:None) – If you are selecting between fewer items thanindexcan address,defaultwill be used for all remaining items. For example, if you have a 3-bit index but are selecting between 6mux_ins, you need to specify a value for those other 2 possible values ofindex(0b110and0b111).
- Raises:
PyrtlError – If there are not enough
mux_insto select from. Ifdefault=None, the number ofmux_insmust be exactly2 ** index.bitwidth.- Return type:
- Returns:
WireVectorwithbitwidthmatching the length of the longest input (not includingindex).
- pyrtl.select(sel, truecase, falsecase)[source]¶
Multiplexer returning
truecasewhensel == 1, otherwisefalsecase.selectis equivalent tomux()with a 1-bitindex, except thatselect’struecaseis its first argument, rather than its second.select’s argument order is consistent with the ternary operator in C-style languages, which improves readability, so preferselectovermux()when selecting between exactly two options.Example that computes
min(a, 5):>>> a = pyrtl.Input(name="a", bitwidth=4) >>> min = pyrtl.WireVector(name="min") >>> min <<= pyrtl.select(a < 5, 5, a) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"a": 4}) >>> sim.inspect("min") 5 >>> sim.step(provided_inputs={"a": 6}) >>> sim.inspect("min") 6
- Parameters:
sel (
WireVector|int|str|bool) – Multiplexer’s selection input. Can be aWireVector, or any type that can be coerced toWireVectorbyas_wires().truecase (
WireVector|int|str|bool) – The WireVector selected ifsel == 1. Can be aWireVector, or any type that can be coerced toWireVectorbyas_wires().falsecase (
WireVector|int|str|bool) – The WireVector selected ifsel == 0. Can be aWireVector, or any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
WireVectorwithbitwidthmatching the longer oftruecaseandfalsecase.
- pyrtl.enum_mux(cntrl, table, default=None, strict=True)[source]¶
Build a mux for the control signals specified by an
enum.IntEnum.Note
Consider using Conditional Assignment instead.
Example:
>>> from enum import IntEnum >>> class Command(IntEnum): ... ADD = 0 ... SUB = 1 >>> command = pyrtl.Input(name="command", bitwidth=1) >>> a = pyrtl.Input(name="a", bitwidth=4) >>> b = pyrtl.Input(name="b", bitwidth=4) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.enum_mux(cntrl=command, table={ ... Command.ADD: a + b, ... Command.SUB: a - b, ... }) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"command": Command.ADD, "a": 1, "b": 2}) >>> sim.inspect("output") 3 >>> sim.step(provided_inputs={"command": Command.SUB, "a": 5, "b": 3}) >>> sim.inspect("output") 2
- Parameters:
cntrl (
WireVector) – Control for the mux.table (
dict[int,WireVector]) – Mapsenum.IntEnumvalues toWireVector.default (
WireVector, default:None) – AWireVectorto use when the key is not present. In addition it is possible to use the keyotherwiseto specify a default value, but it is an error if both are supplied.strict (
bool, default:True) – WhenTrue, check that the dictionary has an entry for every possible value in theenum.IntEnum. Note that if adefaultis set, then this check is not performed as thedefaultwill provide valid values for any underspecified keys.
- Return type:
- Returns:
Result of the mux.
- class pyrtl.helperfuncs.MatchedFields(matched: WireVector, fields: NamedTuple)[source]¶
Result returned by
match_bitpattern().-
fields:
NamedTuple¶ NamedTuplecontaining the matched fields, if any.
-
matched:
WireVector¶ 1-bit
WireVectorindicating ifwmatchesbitpattern.
-
fields:
- pyrtl.match_bitpattern(w, bitpattern, field_map=None)[source]¶
Returns a single-bit
WireVectorthat is1if and only ifwmatches thebitpattern, and a tuple containing the matched fields, if any. Compatible with thewithstatement.This function will compare a multi-bit
WireVectorto a specified pattern of bits, where some of the pattern can be “wildcard” bits. If any of the1or0values specified in the bitpattern fail to match theWireVectorduring execution, a0will be produced, otherwise the value carried on the wire will be1. The wildcard characters can be any other alphanumeric character, with characters other than?having special functionality (see below). The string must have length equal to theWireVectorspecified, although whitespace and underscore characters will be ignored and can be used for pattern readability.For all other characters besides
1,0, or?, a tuple ofWireVectorswill be returned as the second return value. Each character will be treated as the name of a field, and non-consecutive fields with the same name will be concatenated together, left-to-right, into a single field in the resultant tuple. For example,01aa1?bbb11awill match a string such as010010100111, and the resultant matched fields are:(a, b) = (0b001, 0b100)
where the
afield is the concatenation of bits 9, 8, and 0, and thebfield is the concenation of bits 5, 4, and 3. Thus, arbitrary characters beside?act as wildcard characters for the purposes of matching, with the additional benefit of returning theWireVectorscorresponding to those fields.A prime example of this is for decoding instructions. Here we decode some RISC-V:
with pyrtl.conditional_assignment: with match_bitpattern( inst, "iiiiiiiiiiiirrrrr010ddddd0000011" ) as (imm, rs1, rd): regfile[rd] |= mem[(regfile[rs1] + imm.sign_extended(32)).truncate(32)] pc.next |= pc + 1 with match_bitpattern( inst, "iiiiiiirrrrrsssss010iiiii0100011" ) as (imm, rs2, rs1): mem[(regfile[rs1] + imm.sign_extended(32)).truncate(32)] |= regfile[rs2] pc.next |= pc + 1 with match_bitpattern( inst, "0000000rrrrrsssss111ddddd0110011" ) as (rs2, rs1, rd): regfile[rd] |= regfile[rs1] & regfile[rs2] pc.next |= pc + 1 with match_bitpattern( inst, "0000000rrrrrsssss000ddddd0110011") as (rs2, rs1, rd): regfile[rd] |= (regfile[rs1] + regfile[rs2]).truncate(32) pc.next |= pc + 1 # ...etc...
Some smaller examples:
# Basically the same as w == '0b0101'. m, _ = match_bitpattern(w, '0101') # m will be true when w is '0101' or '0111'. m, _ = match_bitpattern(w, '01?1') # m will be true when last two bits of w are '01'. m, _ = match_bitpattern(w, '??01') # spaces/underscores are ignored, same as line above. m, _ = match_bitpattern(w, '??_0 1') # All bits with the same letter make up same field. m, (a, b) = match_bitpattern(w, '01aa1?bbb11a') # Fields will be named `fs.foo` and `fs.bar`. m, fs = match_bitpattern(w, '01aa1?bbb11a', {'a': 'foo', 'b': 'bar'})
- Parameters:
w (
WireVector) – TheWireVectorto be compared to thebitpatternbitpattern (
str) – A string holding the pattern (of bits and wildcards) to matchfield_map (
dict[str,str] |None, default:None) – (optional) A map from single-character field name in the bitpattern to the desired name of field in the returnedNamedTuple. If given, all non-1/0/?characters in thebitpatternmust be present in the map.
- Return type:
- Returns:
A
NamedTupleconsisting of a 1-bitWireVectorcarrying the result of the comparison, and aNamedTuplecontaining the matched fields, if any.
- pyrtl.bitfield_update(w, range_start, range_end, newvalue, truncating=False)[source]¶
Update a
WireVectorby replacing some of its bits withnewvalue.Given a
WireVectorw, this function returns a newWireVectorthat is identical towexcept in the range of bits specified by[range_start, range_end). In that range, the valuenewvalueis swapped in. For example:bitfield_update(w, range_start=20, range_end=23, newvalue=0b111)
will return a
WireVectorof the same length asw, and with the same values asw, but with bits 20, 21, and 22 all set to1.Note that
range_startandrange_endwill be inputs to a slice and so standard Python slicing rules apply (e.g. negative values for end-relative indexing and support forNone):# Sets bits 20, 21, 22 to 1. w = bitfield_update(w, 20, 23, 0b111) # Sets bit 20 to 0, bits 21 and 22 to 1. w = bitfield_update(w, 20, 23, 0b110) # Assuming w is 32 bits, sets bits 31..20 = 0x7. w = bitfield_update(w, 20, None, 0x7) # Set the MSB (bit) to 1. w = bitfield_update(w, -1, None, 0x1) # Set the bits before the MSB (bit) to 9. w = bitfield_update(w, None, -1, 0x9) # Set the LSB (bit) to 1. w = bitfield_update(w, None, 1, 0x1) # Set the bits after the LSB (bit) to 9. w = bitfield_update(w, 1, None, 0x9)
Note
Consider using
wire_struct()orwire_matrix()instead, which helps with consistently disassembling, naming, and reassembling fields.- Parameters:
w (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires(), to use as the starting point for the updaterange_start (
int) – The start of the range of bits to be updated.range_end (
int) – The end of the range of bits to be updated.newvalue (
int) – The value to be written in to therange_start:range_endrange.truncating (
bool, default:False) – IfTrue, clipnewvalueto the proper bitwidth ifnewvalueis too large.
- Raises:
PyrtlError – If
newvalueis too large to fit in the selected range of bits andtruncatingisFalse.- Return type:
- Returns:
wwith some of the bits overwritten bynewvalue.
- pyrtl.bitfield_update_set(w, update_set, truncating=False)[source]¶
Update a
WireVectorby replacing the bits specified inupdate_set.Given a WireVector
w, return a newWireVectorthat is identical to w except in the ranges of bits specified byupdate_set. When multiple non-overlapping fields need to be updated in a single cycle, this provides a clearer way to describe that behavior than iterative calls tobitfield_update():w = bitfield_update_set(w, update_set={ (20, 23): 0x6, # sets bit 20 to 0, bits 21 and 22 to 1 (26, None): 0x7, # assuming w is 32 bits, sets bits 31..26 to 0x7 (None, 1): 0x0, # set the LSB (bit) to 0 })
Note
Consider using
wire_struct()orwire_matrix()instead, which helps with consistently disassembling, naming, and reassembling fields.- Parameters:
w (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires(), to use as the starting point for the updateupdate_set (
dict[tuple[int,int],WireVector|int|str|bool]) – A map from tuples of(range_start, range_end)integers to a new value for the range of bits.truncating (
bool, default:False) – IfTrue, clip new values to the proper bitwidth if a new value is too large.
- Raises:
PyrtlError – If
update_setcontains overlapping fields.- Return type:
- Returns:
wwith some of its bits updated.
Interpreting Vectors of Bits¶
Under the hood, every single value a PyRTL design operates on is a bit vector (which is, in turn, simply an integer of bounded power-of-two size. Interpreting these bit vectors as humans, and turning human understandable values into their corresponding bit vectors, can both be a bit of a pain. The functions below do not create any hardware but rather help in the process of reasoning about bit vector representations of human understandable values.
- pyrtl.val_to_signed_integer(value, bitwidth)[source]¶
Return
valueinterpreted as a two’s complement signed integer.Reinterpret an unsigned integer (not a
WireVector!) as a signed integer. This is useful for printing and interpreting two’s complement values:>>> val_to_signed_integer(0xff, bitwidth=8) -1
val_to_signed_integercan also be used as anrepr_funcforSimulationTrace.render_trace(), to display signed integers in traces:bitwidth = 3 counter = Register(name='counter', bitwidth=bitwidth) counter.next <<= counter + 1 sim = Simulation() sim.step_multiple(nsteps=2 ** bitwidth) # Generates a trace like: # │0 │1 │2 │3 │4 │5 │6 │7 # # counter ──┤1 │2 │3 │-4│-3│-2│-1 sim.tracer.render_trace(repr_func=val_to_signed_integer)
infer_val_and_bitwidth()performs the opposite conversion:>>> integer = val_to_signed_integer(0xff, bitwidth=8) >>> hex(infer_val_and_bitwidth(integer, bitwidth=8).value) '0xff'
- class pyrtl.helperfuncs.ValueBitwidthTuple(value: int, bitwidth: int)[source]¶
Return type for
infer_val_and_bitwidth().
- pyrtl.infer_val_and_bitwidth(rawinput, bitwidth=None, signed=False)[source]¶
Return a
(value, bitwidth)tupleinferred from the specified input.Given a boolean, integer, or Verilog-style string constant, this function returns a
ValueBitwidthTuple(value, bitwidth)which are inferred from the specifiedrawinput. IfsignedisTrue, bits will be included to ensure a proper two’s complement representation is possible, otherwise it assumes a standard unsigned representation. Error checks are performed that determine if the bitwidths specified are sufficient and appropriate for the values specified. Examples:>>> infer_val_and_bitwidth(2, bitwidth=5) ValueBitwidthTuple(value=2, bitwidth=5) >>> # Infer bitwidth from value. >>> infer_val_and_bitwidth(3) ValueBitwidthTuple(value=3, bitwidth=2) >>> infer_val_and_bitwidth(3).bitwidth 2 >>> # Signed values need an additional sign bit. >>> infer_val_and_bitwidth(3, signed=True) ValueBitwidthTuple(value=3, bitwidth=3) >>> val, bitwidth = infer_val_and_bitwidth(-1, bitwidth=3) >>> (bin(val), bitwidth) ('0b111', 3) >>> infer_val_and_bitwidth("5'd12") ValueBitwidthTuple(value=12, bitwidth=5)
val_to_signed_integer()performs the opposite conversion:>>> val, bitwidth = infer_val_and_bitwidth(-1, bitwidth=3) >>> val_to_signed_integer(val, bitwidth) -1
- Parameters:
- Return type:
- Returns:
tuple of integers
(value, bitwidth)
- pyrtl.val_to_formatted_str(val, format, enum_set=None)[source]¶
Return a string representation of the value given format specified.
Given an unsigned integer (not a
WireVector!) convert that to a human-readable string. This helps deal with signed/unsigned numbers (simulation operates on values that have been converted via two’s complement), but it also generates hex, binary, and enum types as outputs. It is easiest to see how it works with some examples:>>> val_to_formatted_str(2, 's3') '2' >>> val_to_formatted_str(7, 's3') '-1' >>> val_to_formatted_str(5, 'b3') '101' >>> val_to_formatted_str(5, 'u3') '5' >>> val_to_formatted_str(5, 's3') '-3' >>> val_to_formatted_str(10, 'x3') 'a' >>> from enum import IntEnum >>> class Ctl(IntEnum): ... ADD = 5 ... SUB = 12 >>> val_to_formatted_str(5, 'e3/Ctl', [Ctl]) 'ADD' >>> val_to_formatted_str(12, 'e3/Ctl', [Ctl]) 'SUB'
formatted_str_to_val()performs the opposite conversion.- Parameters:
- Return type:
- Returns:
a human-readable string representing val.
- pyrtl.formatted_str_to_val(data, format, enum_set=None)[source]¶
Return an unsigned integer representation of
datain a specifiedformat.Given a string (not a
WireVector!) convert that to an unsigned integer ready for input to the simulation environment. This helps deal with signed/unsigned numbers (simulation assumes the values have been converted via two’s complement already), but it also takes hex, binary, and enum types as inputs. It is easiest to see how it works with some examples:>>> formatted_str_to_val('2', 's3') 2 >>> bin(formatted_str_to_val('-1', 's3')) '0b111' >>> bin(formatted_str_to_val('101', 'b3')) '0b101' >>> formatted_str_to_val('5', 'u3') 5 >>> bin(formatted_str_to_val('-3', 's3')) '0b101' >>> formatted_str_to_val('a', 'x3') 10 >>> from enum import IntEnum >>> class Ctl(IntEnum): ... ADD = 5 ... SUB = 12 >>> formatted_str_to_val('ADD', 'e3/Ctl', [Ctl]) 5 >>> formatted_str_to_val('SUB', 'e3/Ctl', [Ctl]) 12
val_to_formatted_str()performs the opposite conversion.- Parameters:
- Return type:
- Returns:
dataas a signed integer
- pyrtl.log2(integer_val)[source]¶
Return the base-2 logarithm of an integer.
Useful when checking that powers of 2 are provided as function inputs.
Examples:
>>> log2(2) 1 >>> log2(256) 8 >>> log2(100) Traceback (most recent call last): ... pyrtl.pyrtlexceptions.PyrtlError: this function can only take even powers of 2
- Parameters:
integer_val (
int) – The integer to take the log base 2 of.- Raises:
PyrtlError – If the input is negative, or not an even power of 2.
- Return type:
- Returns:
The log base 2 of
integer_val.
Debugging¶
- pyrtl.set_debug_mode(debug=True)[source]¶
Set the global debug mode.
Sets the debug mode to the specified
debugvalue. Debug mode is off by default, to improve performance. When debug mode is enabled, all temporaryWireVectorswill be assigned names based on the line of code on which they were created.Each
WireVectorwill also save a copy of its call stack when constructed. These call stacks can be inspected asWireVector.init_call_stack, and they will appear inBlock.sanity_check()error messages.- Parameters:
debug (
bool, default:True) – Optional boolean parameter to which the debug mode will be set.
- pyrtl.probe(w, name=None)[source]¶
Print useful information about a
WireVectorin debug mode.probecan be inserted into a existing design easily because it returns the original wire unmodified. For example:y <<= x[0:3] + 4
could be rewritten as:
y <<= probe(x)[0:3] + 4
to give visibility into both the origin of
x(including the line thatWireVectorwas originally created) and the run-time values ofx(which will be named and thus show up by default in a trace). Likewise:y <<= probe(x[0:3]) + 4 y <<= probe(x[0:3] + 4) probe(y) <<= x[0:3] + 4
are all valid uses of
probe.Note
probeactually adds anOutputwire to the working_block ofw, which can confuse various post-processing transforms such asoutput_to_verilog().- Parameters:
w (
WireVector) –WireVectorfrom which to get infoname (
str|None, default:None) – optional name for probe (defaults to an autogenerated name)
- Return type:
- Returns:
original
WireVectorw
- pyrtl.rtl_assert(w, exp, block=None)[source]¶
Add a hardware assertion to be checked during simulation.
If at any time during execution the wire
wis notTrue(i.e. when it is asserted low) thenSimulationwill raiseexp.- Parameters:
w (
WireVector) – A 1-bitWireVectorto assert.exp (
Exception) –Exceptionto throw when the assertion fails duringSimulation.block (
Block, default:None) –Blockto which the assertion should be added (default to working_block).
- Raises:
exp – When
wis notTrue.- Return type:
- Returns:
The
Outputwire for the assertion, which can be ignored in most cases.
Reductions¶
- pyrtl.and_all_bits(vector)[source]¶
Returns the result of bitwise ANDing all the bits in
vector.Example:
>>> input = pyrtl.Input(name="input", bitwidth=4) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.and_all_bits(input) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input": 0b0101}) >>> sim.inspect("output") 0 >>> sim.step(provided_inputs={"input": 0b1111}) >>> sim.inspect("output") 1
- Parameters:
vector (
WireVector) – Takes a single arbitrary lengthWireVector.- Return type:
- Returns:
Returns a 1-bit result, the bitwise
&of all of the bits invector.
- pyrtl.or_all_bits(vector)[source]¶
Returns the result of bitwise ORing all the bits in
vector.Example:
>>> input = pyrtl.Input(name="input", bitwidth=4) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.or_all_bits(input) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input": 0b0000}) >>> sim.inspect("output") 0 >>> sim.step(provided_inputs={"input": 0b0100}) >>> sim.inspect("output") 1
- Parameters:
vector (
WireVector) – Takes a single arbitrary lengthWireVector.- Return type:
- Returns:
Returns a 1-bit result, the bitwise
|of all of the bits invector.
- pyrtl.xor_all_bits(vector)[source]¶
Returns the result of bitwise XORing all the bits in
vector.Example:
>>> input = pyrtl.Input(name="input", bitwidth=4) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.xor_all_bits(input) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input": 0b0100}) >>> sim.inspect("output") 1 >>> sim.step(provided_inputs={"input": 0b0101}) >>> sim.inspect("output") 0
- Parameters:
vector (
WireVector) – Takes a single arbitrary lengthWireVector.- Return type:
- Returns:
Returns a 1-bit result, the bitwise
^of all of the bits invector.
- pyrtl.parity(vector)¶
Alias for
xor_all_bits().- Return type:
- pyrtl.rtl_any(*vectorlist)[source]¶
Hardware equivalent of Python’s
any().Given any number of
WireVectors, return a 1-bitWireVectorwhich will hold a1if any of the inputs are1. In other words, this generates a large OR gate. If no inputs are provided, it will return aConst0(since there are no1spresent) similar to Python’sany()called with an empty list.Note
rtl_anyis most useful when working with a variable number ofWireVectors. For a fixed number ofWireVectors, it is clearer to use|:any_ones = a | b | c
Example:
>>> inputs = [pyrtl.Input(name="input0", bitwidth=1), ... pyrtl.Input(name="input1", bitwidth=1)] >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.rtl_any(*inputs) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input0": 0, "input1": 0}) >>> sim.inspect("output") 0 >>> sim.step(provided_inputs={"input0": 0, "input1": 1}) >>> sim.inspect("output") 1
- Parameters:
vectorlist (
WireVector|int|str|bool) – All arguments are length 1WireVector, or any type that can be coerced toWireVectorbyas_wires(), with length 1.- Raises:
PyrtlError – If any argument’s
bitwidthis not 1.- Return type:
- Returns:
Length 1
WireVectorindicating if any bits invectorlistare1.
- pyrtl.rtl_all(*vectorlist)[source]¶
Hardware equivalent of Python’s
all().Given any number of
WireVectors, return a 1-bitWireVectorwhich will hold a1only if all of the inputs are1. In other words, this generates a large AND gate. If no inputs are provided, it will return aConst1(since there are no0spresent) similar to Python’sall()called with an empty list.Note
rtl_allis most useful when working with a variable number ofWireVectors. For a fixed number ofWireVectors, it is clearer to use&:all_ones = a & b & c
Example:
>>> inputs = [pyrtl.Input(name="input0", bitwidth=1), ... pyrtl.Input(name="input1", bitwidth=1)] >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.rtl_all(*inputs) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input0": 0, "input1": 1}) >>> sim.inspect("output") 0 >>> sim.step(provided_inputs={"input0": 1, "input1": 1}) >>> sim.inspect("output") 1
- Parameters:
vectorlist (
WireVector|int|str|bool) – All arguments are length 1WireVector, or any type that can be coerced toWireVectorbyas_wires(), with length 1.- Raises:
PyrtlError – If any argument’s
bitwidthis not 1.- Return type:
- Returns:
Length 1
WireVectorindicating if all bits invectorlistare1.
Extended Logic and Arithmetic¶
The functions below provide ways of comparing and arithmetically combining
WireVectors in ways that are often useful in hardware
design. The functions below extend those member functions of the
WireVector class itself (which provides support for unsigned
addition, subtraction, multiplication, comparison, and many others).
- pyrtl.signed_add(a, b)[source]¶
Return a
WireVectorfor the result of signed addition.The inputs are
sign_extended()to the result’s bitwidth before adding.Example:
>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3) >>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.signed_add(neg_three, neg_five) >>> output.bitwidth 6 >>> sim = pyrtl.Simulation() >>> sim.step() >>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth) -8
- Parameters:
a (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().b (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
A
WireVectorrepresenting the sum ofaandb, withbitwidthmax(a.bitwidth, b.bitwidth) + 1.
- pyrtl.signed_sub(a, b)[source]¶
Return a
WireVectorfor the result of signed subtraction.The inputs are
sign_extended()to the result’s bitwidth before subtracting.Example:
>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3) >>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.signed_sub(neg_three, neg_five) >>> output.bitwidth 6 >>> sim = pyrtl.Simulation() >>> sim.step() >>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth) 2
- Parameters:
a (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().b (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
A
WireVectorrepresenting the difference betweenaandb, withbitwidthmax(a.bitwidth, b.bitwidth) + 1.
- pyrtl.signed_mult(a, b)[source]¶
Return a
WireVectorfor the result of signed multiplication.The inputs are
sign_extended()to the result’s bitwidth before multiplying.Example:
>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3) >>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.signed_mult(neg_three, neg_five) >>> output.bitwidth 8 >>> sim = pyrtl.Simulation() >>> sim.step() >>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth) 15
- Parameters:
a (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().b (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
A
WireVectorrepresenting the product ofaandb, withbitwidtha.bitwidth + b.bitwidth.
- pyrtl.signed_lt(a, b)[source]¶
Return a 1-bit
WireVectorfor the result of a signed<comparison.The inputs are
sign_extended()to matching bitwidths before comparing.Example:
>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3) >>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.signed_lt(neg_three, neg_five) >>> output.bitwidth 1 >>> sim = pyrtl.Simulation() >>> sim.step() >>> sim.inspect("output") 0
- Parameters:
a (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().b (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
A 1-bit
WireVectorindicating ifais less thanb.
- pyrtl.signed_le(a, b)[source]¶
Return a 1-bit
WireVectorfor the result of a signed<=comparison.The inputs are
sign_extended()to matching bitwidths before comparing.Example:
>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3) >>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.signed_le(neg_three, neg_five) >>> output.bitwidth 1 >>> sim = pyrtl.Simulation() >>> sim.step() >>> sim.inspect("output") 0
- Parameters:
a (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().b (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
A 1-bit
WireVectorindicating ifais less than or equal tob.
- pyrtl.signed_gt(a, b)[source]¶
Return a 1-bit
WireVectorfor the result of a signed>comparison.The inputs are
sign_extended()to matching bitwidths before comparing.Example:
>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3) >>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.signed_gt(neg_three, neg_five) >>> output.bitwidth 1 >>> sim = pyrtl.Simulation() >>> sim.step() >>> sim.inspect("output") 1
- Parameters:
a (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().b (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
A 1-bit
WireVectorindicating ifais greater thanb.
- pyrtl.signed_ge(a, b)[source]¶
Return a 1-bit
WireVectorfor the result of a signed>=comparison.The inputs are
sign_extended()to matching bitwidths before comparing.Example:
>>> neg_three = pyrtl.Const(val=-3, signed=True, bitwidth=3) >>> neg_five = pyrtl.Const(val=-5, signed=True, bitwidth=5) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.signed_ge(neg_three, neg_five) >>> output.bitwidth 1 >>> sim = pyrtl.Simulation() >>> sim.step() >>> sim.inspect("output") 1
- Parameters:
a (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().b (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().
- Return type:
- Returns:
A 1-bit
WireVectorindicating ifais greater than or equal tob.
- pyrtl.shift_left_logical(bits_to_shift, shift_amount)[source]¶
Logical left shift operation.
Logical shifting treats the
bits_to_shiftas an unsigned number. Zeroes will be added on the right and the result will be truncated tobits_to_shift.bitwidth.Example:
>>> three = pyrtl.Const(val=3, bitwidth=6) >>> shift_amount = pyrtl.Input(name="shift_amount", bitwidth=3) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.shift_left_logical(three, shift_amount) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"shift_amount": 3}) >>> sim.inspect("output") 24 >>> 3 * 2 ** 3 24
Left shifting by
Nbits is equivalent to multiplying by2^N.- Parameters:
bits_to_shift (
WireVector) – Value to shift left logically.shift_amount (
WireVector|int) – Number of bit positions to shift, as an unsigned integer.
- Return type:
- Returns:
A new
WireVectorwith the same bitwidth asbits_to_shift.
- pyrtl.shift_left_arithmetic(bits_to_shift, shift_amount)¶
Alias for
shift_left_logical()- Return type:
- pyrtl.shift_right_logical(bits_to_shift, shift_amount)[source]¶
Logical right shift operation.
Logical shifting treats the
bits_to_shiftas an unsigned number, so zeroes will be added on the left.Example:
>>> forty = pyrtl.Const(val=40, bitwidth=6) >>> shift_amount = pyrtl.Input(name="shift_amount", bitwidth=3) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.shift_right_logical(forty, shift_amount) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"shift_amount": 3}) >>> sim.inspect("output") 5 >>> int(40 / 2 ** 3) 5
Right shifting by
Nbits is equivalent to dividing by2^N.- Parameters:
bits_to_shift (
WireVector) – Value to shift right logically.shift_amount (
WireVector|int) – Number of bit positions to shift, as an unsigned integer.
- Return type:
- Returns:
A new
WireVectorwith the same bitwidth asbits_to_shift.
- pyrtl.shift_right_arithmetic(bits_to_shift, shift_amount)[source]¶
Arithmetic right shift operation.
Arithemetic shifting treats the
bits_to_shiftas a signed number, so copies ofbits_to_shift’s sign bit will be added on the left.Example:
>>> neg_forty = pyrtl.Const(val=-40, signed=True, bitwidth=7) >>> shift_amount = pyrtl.Input(name="shift_amount", bitwidth=3) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.shift_right_arithmetic(neg_forty, shift_amount) >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"shift_amount": 3}) >>> pyrtl.val_to_signed_integer(sim.inspect("output"), bitwidth=output.bitwidth) -5 >>> int(-40 / 2 ** 3) -5
Right shifting by
Nbits is equivalent to dividing by2^N.- Parameters:
bits_to_shift (
WireVector) – Value to shift right arithmetically.shift_amount (
WireVector|int) – Number of bit positions to shift, as an unsigned integer.
- Return type:
- Returns:
A new
WireVectorwith the same bitwidth asbits_to_shift.
Encoders and Decoders¶
- pyrtl.one_hot_to_binary(w)[source]¶
Takes a one-hot input and returns the bit position of the high bit in binary.
If the input contains multiple
1’s, the smallest bit position containing a1will be returned. If the input contains no1’s,0will be returned.Examples:
>>> input = pyrtl.Input(name="input", bitwidth=8) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.one_hot_to_binary(input) >>> output.bitwidth 3 >>> 2 ** 3 == input.bitwidth True >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input": 0b0010}) >>> sim.inspect("output") 1 >>> sim.step(provided_inputs={"input": 64}) >>> sim.inspect("output") 6 >>> 2 ** 6 64 >>> # Bit positions 2 and 3 contain 1's, but 2 is smaller. >>> sim.step(provided_inputs={"input": 0b1100}) >>> sim.inspect("output") 2 >>> sim.step(provided_inputs={"input": 0}) >>> sim.inspect("output") 0
- Parameters:
w (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().- Return type:
- Returns:
a
WireVectorcontaining the smallest bit position of the high bit, in binary.
- pyrtl.binary_to_one_hot(bit_position, max_bitwidth=None)[source]¶
Given a
bit_position, return aWireVectorwith only that bit set to1.If the
max_bitwidthprovided is not large enough for the givenbit_position, a0-valuedWireVectorof sizemax_bitwidthwill be returned.Examples:
>>> input = pyrtl.Input(name="input", bitwidth=4) >>> output = pyrtl.Output(name="output") >>> output <<= pyrtl.binary_to_one_hot(input) >>> output.bitwidth 16 >>> 16 == 2 ** input.bitwidth True >>> sim = pyrtl.Simulation() >>> sim.step(provided_inputs={"input": 0}) >>> bin(sim.inspect("output")) '0b1' >>> sim.step(provided_inputs={"input": 3}) >>> bin(sim.inspect("output")) '0b1000'
- Parameters:
bit_position (
WireVector|int|str|bool) – AWireVector, or any type that can be coerced toWireVectorbyas_wires().max_bitwidth (
int|None, default:None) – Optional integer maximum bitwidth for the resulting one-hotWireVector.
- Return type:
- Returns:
A
WireVectorwith the bit atbit_positionset to1and all other bits set to0. Bit position 0 is the least significant bit.