Python RTL
Python RTL
Release 0.10.2
Timothy Sherwood
1 Quick links 3
2 Installation 5
4 Overview of PyRTL 9
4.1 PyRTL Classes: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
5 PyRTL Functionality: 11
5.1 Basic Logic and Wires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.2 Registers and Memories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
5.3 Conditionals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.4 Simulation and Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.5 Logic Nets and Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.6 Helper Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.7 Analysis and Optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.8 Exporting and Importing Designs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.9 RTL Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
5.10 Transforming Passes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Index 85
i
ii
PyRTL, Release 0.10.2
A collection of classes providing simple RTL specification, simulation, tracing, and testing suitable for teaching and
research. Simplicity, usability, clarity, and extendibility rather than performance or optimization is the overarching
goal. With PyRTL you can use the full power of python to describe complex synthesizable digital designs, simulate
and test them, and export them to verilog.
CONTENTS: 1
PyRTL, Release 0.10.2
2 CONTENTS:
CHAPTER
ONE
QUICK LINKS
3
PyRTL, Release 0.10.2
TWO
INSTALLATION
Automatic installation:
PyRTL is listed in PyPI and can be installed with pip or pip3. If the above command fails due to insufficient permis-
sions, you may need to do sudo pip install pyrtl (to install as superuser) or pip install --user pyrtl (to
install as a normal user).
PyRTL is tested to work with Python 3.8+.
5
PyRTL, Release 0.10.2
6 Chapter 2. Installation
CHAPTER
THREE
1 import pyrtl
2
After you have PyRTL installed, you should be able to cut and paste the above into a file and run it with Python. The
result you should see, drawn right into the terminal, is the output of the simulation. While a great deal of work has gone
into making hardware design in PyRTL as friendly a process as possible, please don’t mistake that for a lack of depth.
You can just as easily export to Verilog or other hardware formats, view results with your favorite waveform viewer,
build hardware transformation passes, run JIT-accelerated simulations, design, test, and even verify hugely complex
digital systems, and much more. Most critically of all it is easy to extend with your own approaches to digital hardware
development as you find necessary.
7
PyRTL, Release 0.10.2
FOUR
OVERVIEW OF PYRTL
If you are brand new to PyRTL we recommend that you start with going through the Examples of PyRTL Code which
will show you most of the core functionality in the context of a complete design.
Perhaps the most important class to understand is WireVector, which is the basic type from which you build all hard-
ware. If you are coming to PyRTL from Verilog, a WireVector is closest to a multi-bit wire. Every new WireVector
builds a set of wires which you can then connect with other WireVector through overloaded operations such as addi-
tion or bitwise or. A bunch of other related classes, including Input, Output, Const, and Register are all derived
from WireVector. Coupled with MemBlock (and RomBlock), this is all a user needs to create a functional hardware
design.
• WireVector
– Input (WireVector)
– Output (WireVector)
– Const (WireVector)
– Register (WireVector)
After specifying a hardware design, there are then options to simulate your design right in PyRTL, synthesize it down
to primitive 1-bit operations, optimize it, and export it to Verilog (along with a testbench),.
To simulate your hardware design one needs to do a simulation, and to view the output we need to capture a “trace”.
Simulation is how your hardware is “executed” for the purposes of testing, and three different classes help you do that:
Simulation, FastSimulation and CompiledSimulation. All three have almost the same interface and, except
for a few debugging cases, can be used interchangeably. Typically one starts with Simulation and then moves up to
FastSimulation when performance begins to matter.
Both Simulation and FastSimulation take an instance of SimulationTrace as an argument (or make a new
blank one by default), which stores a list of the signals as they are simulated. This trace can then be rendered to the
terminal with WaveRenderer, although unless there are some problems with the default configurations, most end users
should not need to even be aware of WaveRenderer. The examples describe other ways that the trace may be handled,
including extraction as a test bench and export to a VCD file.
• Simulation
• FastSimulation
• CompiledSimulation
• SimulationTrace
• WaveRenderer
9
PyRTL, Release 0.10.2
When you are building hardware with the WireVector and MemBlock classes, what is really happening under the hood
is that those classes are just “sugar” over a core set of primitives and a data structure keeps incrementally updating a
graph of those primitives which, when complete, represent the final design. WireVectors connect to “primitives”
which connect to other WireVectors and the class that stores a primitive is LogicNet. The class Block is then a
wrapper for a set of these LogicNets. Typically a full and complete design is stored in a single Block. The function
working_block() will return back the block on which we are implicitly working. When we write hardware transforms
we may wish to make a new Block from an old one and augment the information kept with my hardware block and
PostSynthBlock is one example of this pattern in action.
• LogicNet
• Block
– PostSynthBlock (Block)
Finally, when things go wrong you may hit on one of two Exceptions, neither of which is likely recoverable auto-
matically (which is why we limited them to only two). The intention is that PyrtlError is intended to capture end
user errors such as invalid constant strings and mis-matched bitwidths. In contrast, PyrtlInternalError captures
internal invariants and assertions over the core logic graph which should never be hit when constructing designs in the
normal ways. If you hit a confusing PyrtlError or any PyrtlInternalError feel free to file an issue.
FIVE
PYRTL FUNCTIONALITY:
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, the 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. Wires provide the basic input and
output interfaces to the generated Block which stores the description of the hardware as you build it.
The classes Input, Output, Const, and Register are all derived from WireVector, but extend it with (or restrict
it from) with certain functionality. The Input and Output classes are for those values that will be external to the
entire system once complete (e.g. driving a signal off-chip, rather than the interface to some particular sub-block of
the design). The Const class is useful for specifying hard-wired values, while Register is how sequential elements
are created (the all have an implict clock).
5.1.1 WireVector
11
PyRTL, Release 0.10.2
A note on <<= asssignment: This operator is how you “drive” an already created wire with an existing wire. If
you were to do a = b it would lose the old value of a and simply overwrite it with a new value, in this case with
a reference to wirevector b. In contrast a <<= b does not overwrite a, but simply wires the two together.
__add__(other)
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:
__ilshift__(other)
Wire assignment operator (assign other to self).
Example:
i = pyrtl.Input(8, 'i')
t = pyrtl.WireVector(8, 't')
t <<= i
• block (Block) – The block under which the wire should be placed. Defaults to the working
block
• name (String) – 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:
__len__()
Get the bitwidth of a WireVector.
Return integer
Returns the length (i.e. bitwidth) of the WireVector in bits.
Note that WireVectors do not need to have a bitwidth defined when they are first allocated. They can get it
from a <<= assignment later. However, if you check the len of WireVector with undefined bitwidth it will
throw PyrtlError.
__mul__(other)
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)
Creates a LogicNet that subtracts the right wire from the left one.
Return Wirevector
Returns the result wire of the operation. The resulting wire has one more bit than the longer
of the two input wires.
Subtraction is compatible with two’s complement signed numbers.
property bitmask
A property holding a bitmask of the same length as this WireVector. Specifically it is an integer with a
number of bits set to 1 equal to the bitwidth of the WireVector.
It is often times useful to “mask” an integer such that it fits in the the number of bits of a WireVector. As a
convenience for this, the bitmask property is provided. As an example, if there was a 3-bit WireVector a, a
call to a.bitmask() should return 0b111 or 0x7.
property name
A property holding the name (a string) of the WireVector, can be read or written. For example:
print(a.name) or a.name = ‘mywire’.
nand(other)
Creates a LogicNet that bitwise nands two wirevector together to a single wirevector.
Return WireVector
Returns wirevector of the nand operation.
sign_extended(bitwidth)
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)
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)
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.
5.1.4 Constants
5.2.1 Registers
The class Register is derived from WireVector, and so can be used just like any other WireVector. A read from
the register is the value that is available in the current clock period, and the value for the follow cycle can be set by
assigning to property next with the <<= operator. Registers all, by default, reset to 0, and all reside in the same clock
domain.
class Register(bitwidth, name='', reset_value=None, block=None)
Bases: WireVector
A WireVector with a register state element embedded.
Registers only update their outputs on posedge of an implicit clk signal. The “value” in the current cycle can be
accessed by just referencing the Register itself. To set the value for the next cycle (after the next posedge) you
write to the property .next with the <<= operator. For example, if you want to specify a counter it would look
like: “a.next <<= a + 1”
__init__(bitwidth, name='', reset_value=None, block=None)
Construct a register.
Parameters
• bitwidth (int) – Number of bits to represent this register.
• name (String) – The name of the wire. Must be unique. If none is provided, one will be
autogenerated.
• reset_value – Value to initialize this register to during simulation and in any code (e.g.
Verilog) that is exported. Defaults to 0, but can be explicitly overridden at simulation time.
• block – The block under which the wire should be placed. Defaults to the working block.
Returns
a WireVector object representing a register.
It is an error if the reset_value cannot fit into the specified bitwidth for this register.
property next
This property is the way to set what the wirevector will be the next cycle (aka, it is before the D-Latch)
5.2.2 Memories
When the address of a memory is assigned to using a EnableWrite object items will only be written to the memory
when the enable WireVector is set to high (1).
class EnabledWrite(data, enable)
Bases: tuple
Allows for an enable bit for each write port, where data (the first field in the tuple) is the normal data address,
and enable (the second field) is a one bit signal specifying that the write should happen (i.e. active high).
data
Alias for field number 0
enable
Alias for field number 1
__init__(bitwidth, addrwidth, name='', max_read_ports=2, max_write_ports=1, asynchronous=False,
block=None)
Create a PyRTL read-write memory.
Parameters
• bitwidth (int) – Defines the bitwidth of each element in the memory
• addrwidth (int) – The number of bits used to address an element of the memory. This
also defines the size of the memory
• name (basestring) – The identifier for the memory
• max_read_ports – limits the number of read ports each block can create; passing None
indicates there is no limit
• max_write_ports – limits the number of write ports each block can create; passing None
indicates there is no limit
• asynchronous (bool) – If false make sure that memory reads are only done using values
straight from a register. (aka make sure that the read is synchronous)
• name – Name of the memory. Defaults to an autogenerated name
• block – The block to add it to, defaults to the working block
It is best practice to make sure your block memory/fifos read/write operations start on a clock edge if
you want them to synthesize into efficient hardware. MemBlocks will enforce this by making sure that
you only address them with a register or input, unless you explicitly declare the memory as asynchronous
with asynchronous=True flag. Note that asynchronous mems are, while sometimes very convenient and
tempting, rarely a good idea. They can’t be mapped to block rams in FPGAs and will be converted to
registers by most design tools even though PyRTL can handle them with no problem. For any memory
beyond a few hundred entries it is not a realistic option.
Each read or write to the memory will create a new port (either a read port or write port respectively). By
default memories are limited to 2-read and 1-write port, but to keep designs efficient by default, but those
values can be set as options. Note that memories with high numbers of ports may not be possible to map
to physical memories such as block rams or existing memory hardware macros.
5.2.3 ROMs
5.3 Conditionals
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 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
}
):
(continues on next page)
5.3. Conditionals 19
PyRTL, Release 0.10.2
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.
currently_under_condition()
Returns True if execution is currently in the context of a _ConditionalAssignment.
otherwise = <pyrtl.conditional._Otherwise object>
Context providing functionality of pyrtl “otherwise”.
conditional_assignment = <pyrtl.conditional._ConditionalAssignment object>
5.4.1 Simulation
A step causes the block to be updated as follows, in order: (1) registers are updated with their next values
computed in the previous cycle; (2) block inputs and these new register values propagate through the com-
binational logic; (3) memories are updated; and (4) the next values of the registers are saved for use in step
1 of the next cycle.
All input wires must be in the provided_inputs in order for the simulation to accept these values.
Example: if we have inputs named ‘a’ and ‘x’, we can call:
a = pyrtl.Register(8)
b = pyrtl.Output(8, 'b')
a.next <<= a + 1
b <<= a
sim = pyrtl.Simulation()
sim.step_multiple(nsteps=3)
Using sim.step_multiple(nsteps=3) simulates 3 cycles, after which we would expect the value of ‘b’ to be
2.
inspect_mem(mem)
Get the values in a map during the current simulation cycle.
Parameters
mem – the memory to inspect
Returns
{address: value}
Note that this returns the current memory state. Modifying the dictonary will also modify the state in the
simulator
step(provided_inputs)
Run the simulation for a cycle.
Parameters
provided_inputs – a dictionary mapping WireVectors (or their names) to their values for
this step (eg: {wire: 3, “wire_name”: 17})
A step causes the block to be updated as follows, in order: (1) registers are updated with their next values
computed in the previous cycle; (2) block inputs and these new register values propagate through the com-
binational logic; (3) memories are updated; and (4) the next values of the registers are saved for use in step
1 of the next cycle.
step_multiple(provided_inputs={}, expected_outputs={}, nsteps=None, file=<_io.TextIOWrapper
name='<stdout>' mode='w' encoding='utf-8'>, stop_after_first_error=False)
Parameters
• provided_inputs – a dictionary mapping wirevectors to their values for N steps
• expected_outputs – a dictionary mapping wirevectors to their expected values for N
steps; use ‘?’ to indicate you don’t care what the value at that step is
• nsteps – number of steps to take (defaults to None, meaning step for each supplied input
value)
• file – where to write the output (if there are unexpected outputs detected)
• stop_after_first_error – a boolean flag indicating whether to stop the simulation
after the step where the first errors are encountered (defaults to False)
All input wires must be in the provided_inputs in order for the simulation to accept these values. Addition-
ally, the length of the array of provided values for each input must be the same.
When ‘nsteps’ is specified, then it must be less than or equal to the number of values supplied for each
input when ‘provided_inputs’ is non-empty. When ‘provided_inputs’ is empty (which may be a legitimate
case for a design that takes no inputs), then ‘nsteps’ will be used. When ‘nsteps’ is not specified, then the
simulation will take the number of steps equal to the number of values supplied for each input.
Example: if we have inputs named ‘a’ and ‘b’ and output ‘o’, we can call: sim.step_multiple({‘a’: [0,1],
‘b’: [23,32]}, {‘o’: [42, 43]}) to simulate 2 cycles, where in the first cycle ‘a’ and ‘b’ take on 0 and 23,
respectively, and ‘o’ is expected to have the value 42, and in the second cycle ‘a’ and ‘b’ take on 1 and 32,
respectively, and ‘o’ is expected to have the value 43.
If your values are all single digit, you can also specify them in a single string, e.g. sim.step_multiple({‘a’:
‘01’, ‘b’: ‘01’}) will simulate 2 cycles, with ‘a’ and ‘b’ taking on 0 and 0, respectively, on the first cycle
and ‘1’ and ‘1’, respectively, on the second cycle.
inspect(w)
Get the latest value of the wire given, if possible.
inspect_mem(mem)
Get a view into the contents of a MemBlock.
run(inputs)
Run many steps of the simulation.
Parameters
inputs – A list of input mappings for each step; its length is the number of steps to be
executed.
step(inputs)
Run one step of the simulation.
Parameters
inputs – A mapping from input names to the values for the step.
A step causes the block to be updated as follows, in order: (1) registers are updated with their next values
computed in the previous cycle; (2) block inputs and these new register values propagate through the com-
binational logic; (3) memories are updated; and (4) the next values of the registers are saved for use in step
1 of the next cycle.
step_multiple(provided_inputs={}, expected_outputs={}, nsteps=None, file=<_io.TextIOWrapper
name='<stdout>' mode='w' encoding='utf-8'>, stop_after_first_error=False)
Parameters
• provided_inputs – a dictionary mapping wirevectors to their values for N steps
• expected_outputs – a dictionary mapping wirevectors to their expected values for N
steps; use ‘?’ to indicate you don’t care what the value at that step is
• nsteps – number of steps to take (defaults to None, meaning step for each supplied input
value)
• file – where to write the output (if there are unexpected outputs detected)
• stop_after_first_error – a boolean flag indicating whether to stop the simulation
after the step where the first errors are encountered (defaults to False)
All input wires must be in the provided_inputs in order for the simulation to accept these values. Addition-
ally, the length of the array of provided values for each input must be the same.
When ‘nsteps’ is specified, then it must be less than or equal to the number of values supplied for each
input when ‘provided_inputs’ is non-empty. When ‘provided_inputs’ is empty (which may be a legitimate
case for a design that takes no inputs), then ‘nsteps’ will be used. When ‘nsteps’ is not specified, then the
simulation will take the number of steps equal to the number of values supplied for each input.
Example: if we have inputs named ‘a’ and ‘b’ and output ‘o’, we can call: sim.step_multiple({‘a’: [0,1],
‘b’: [23,32]}, {‘o’: [42, 43]}) to simulate 2 cycles, where in the first cycle ‘a’ and ‘b’ take on 0 and 23,
respectively, and ‘o’ is expected to have the value 42, and in the second cycle ‘a’ and ‘b’ take on 1 and 32,
respectively, and ‘o’ is expected to have the value 43.
If your values are all single digit, you can also specify them in a single string, e.g. sim.step_multiple({‘a’:
‘01’, ‘b’: ‘01’}) will simulate 2 cycles, with ‘a’ and ‘b’ taking on 0 and 0, respectively, on the first cycle
and ‘1’ and ‘1’, respectively, on the second cycle.
Example: if the design had no inputs, like so:
a = pyrtl.Register(8) b = pyrtl.Output(8, ‘b’)
a.next <<= a + 1 b <<= a
sim = pyrtl.Simulation() sim.step_multiple(nsteps=3)
Using sim.step_multiple(nsteps=3) simulates 3 cycles, after which we would expect the value of ‘b’ to be
2.
sim_trace.print_vcd()
sim_trace.print_vcd("my_waveform.vcd", include_clock=True)
class WaveRenderer(constants)
Render a SimulationTrace to the terminal.
See examples/renderer-demo.py, which renders traces with various options. You can choose a default ren-
derer by exporting the PYRTL_RENDERER environment variable. See the documentation for subclasses of
RendererConstants.
__init__(constants)
Instantiate a WaveRenderer.
Parameters
constants – Subclass of RendererConstants that specifies the ASCII/Unicode characters
to use for rendering waveforms.
class RendererConstants
Abstract base class for renderer constants.
These constants determine which characters are used to render waveforms in a terminal.
AsciiRendererConstants
Cp437RendererConstants
RendererConstants
Utf8RendererConstants PowerlineRendererConstants
Utf8AltRendererConstants
class Utf8RendererConstants
UTF-8 renderer constants. These should work in most terminals.
Single-bit WireVectors are rendered as square waveforms, with vertical rising and falling edges. Multi-bit
WireVector values are rendered in reverse-video rectangles.
This is the default renderer on non-Windows platforms.
Enable this renderer by default by setting the PYRTL_RENDERER environment variable to utf-8.
class Utf8AltRendererConstants
Alternative UTF-8 renderer constants.
Single-bit WireVectors are rendered as waveforms with sloped rising and falling edges. Multi-bit WireVector
values are rendered in reverse-video rectangles.
Compared to Utf8RendererConstants, this renderer is more compact because it uses one character between cycles
instead of two.
Enable this renderer by default by setting the PYRTL_RENDERER environment variable to utf-8-alt.
class PowerlineRendererConstants
Powerline renderer constants. Font must include powerline glyphs.
This render is closest to a traditional logic analyzer. Single-bit WireVectors are rendered as square waveforms,
with vertical rising and falling edges. Multi-bit WireVector values are rendered in reverse-video hexagons.
This renderer requires a terminal font that supports Powerline glyphs
Enable this renderer by default by setting the PYRTL_RENDERER environment variable to powerline.
class Cp437RendererConstants
Code page 437 renderer constants (for windows cmd compatibility).
Single-bit WireVectors are rendered as square waveforms, with vertical rising and falling edges. Multi-bit
WireVector values are rendered between vertical bars.
Code page 437 is also known as 8-bit ASCII. This is the default renderer on Windows platforms.
Compared to Utf8RendererConstants, this renderer is more compact because it uses one character between cycles
instead of two, but the wire names are vertically aligned at the bottom of each waveform.
Enable this renderer by default by setting the PYRTL_RENDERER environment variable to cp437.
class AsciiRendererConstants
7-bit ASCII renderer constants. These should work anywhere.
Single-bit WireVectors are rendered as waveforms with sloped rising and falling edges. Multi-bit WireVector
values are rendered between vertical bars.
Enable this renderer by default by setting the PYRTL_RENDERER environment variable to ascii.
5.5.1 LogicNets
Logical Operations:
('&', None, (a1, a2), (out)) => AND two wires together, put result into out
('|', None, (a1, a2), (out)) => OR two wires together, put result into out
('^', None, (a1, a2), (out)) => XOR two wires together, put result into out
('n', None, (a1, a2), (out)) => NAND two wires together, put result into out
('~', None, (a1), (out)) => invert one wire, put result into out
('+', None, (a1, a2), (out)) => add a1 and a2, put result into out
len(out) = max(len(a1), len(a2)) + 1
works with both unsigned and two's complement
('-', None, (a1, a2), (out)) => subtract a2 from a1, put result into out
len(out) = max(len(a1), len(a2)) + 1
works with both unsigned and two's complement
('*', None, (a1, a2), (out)) => multiply a1 & a2, put result into out
len(out) = len(a1) + len(a2)
assumes unsigned, but "signed_mult" provides wrapper
('=', None, (a1, a2), (out)) => check a1 & a2 equal, put result into out (0 | 1)
('<', None, (a1, a2), (out)) => check a2 greater than a1, put result into out (0 |␣
˓→1)
('>', None, (a1, a2), (out)) => check a1 greater than a2, put result into out (0 |␣
˓→1)
('w', None, (w1), (w2) => directional wire w/ no logical operation: connects w1 to␣
˓→w2
('x', None, (x, a1, a2), (out)) => mux: connect a1 (x=0), a2 (x=1) to out;
x must be one bit; len(a1) = len(a2)
('c', None, (*args), (out)) => concatenates *args (wires) into single WireVector;
puts first arg at MSB, last arg at LSB
('s', (sel), (wire), (out)) => selects bits from wire based on sel (std slicing␣
˓→syntax),
5.5.2 Blocks
class Block
Block encapsulates a netlist.
A Block in PyRTL is the class that stores a netlist and provides basic access and error checking members. Each
block has well defined inputs and outputs, and contains both the basic logic elements and references to the wires
and memories that connect them together.
The logic structure is primarily contained in self.logic which holds a set of “LogicNet”s. Each LogicNet describes
a primitive operation (such as an adder or memory). The primitive is described by a 4-tuple of:
1) the op (a single character describing the operation such as ‘+’ or ‘r’),
2) a set of hard parameters to that primitives (such as the constants to select from the “selection” op).
3) the tuple “args” which list the wirevectors hooked up as inputs to this particular net.
4) the tuple “dests” which list the wirevectors hooked up as output for this particular net.
Below is a list of the basic operations. These properties (more formally specified) should all be checked by the
class method ‘sanity_check’.
• Most logical and arithmetic ops are pretty self explanatory. Each takes exactly two arguments, and they
should perform the arithmetic or logical operation specified. OPS: (’&’,’|’,’^’,’n’,’~’,’+’,’-‘,’*’). All inputs
must be the same bitwidth. Logical operations produce as many bits as are in the input, while ‘+’ and ‘-’
produce n+1 bits, and ‘*’ produces 2n bits.
• In addition there are some operations for performing comparisons that should perform the operation spec-
ified. The ‘=’ op is checking to see if the bits of the vectors are equal, while ‘<’ and ‘>’ do unsigned
arithmetic comparison. All comparisons generate a single bit of output (1 for true, 0 for false).
• The ‘w’ operator is simply a directional wire and has no logic function.
• The ‘x’ operator is a mux which takes a select bit and two signals. If the value of the select bit is 0 it selects
the second argument; if it is 1 it selects the third argument. Select must be a single bit, while the other two
arguments must be the same length.
• The ‘c’ operator is the concatenation operator and combines any number of wirevectors (a,b,. . . ,z) into a
single new wirevector with “a” in the MSB and “z” (or whatever is last) in the LSB position.
• The ‘s’ operator is the selection operator and chooses, based on the op_param specified, a subset of the
logic bits from a WireVector to select. Repeats are accepted.
• The ‘r’ operator is a register and on posedge, simply copies the value from the input to the output of the
register.
• The ‘m’ operator is a memory block read port, which supports async reads (acting like combinational
logic). Multiple read (and write) ports are possible to the same memory but each ‘m’ defines only one of
those. The op_param is a tuple containing two references: the mem id, and a reference to the MemBlock
containing this port. The MemBlock should only be used for debug and sanity checks. Each read port has
one addr (an arg) and one data (a dest).
• The ‘@’ (update) operator is a memory block write port, which supports synchronous writes (writes are
“latched” at posedge). Multiple write (and read) ports are possible to the same memory but each ‘@’
defines only one of those. The op_param is a tuple containing two references: the mem id, and a reference
to the MemoryBlock. Writes have three args (addr, data, and write enable). The dests should be an empty
tuple. You will not see a written value change until the following cycle. If multiple writes happen to the
same address in the same cycle the behavior is currently undefined.
The connecting elements (args and dests) should be WireVectors or derived from WireVector, and should be
registered with the block using the method ‘add_wirevector’. Nets should be registered using ‘add_net’.
In addition, there is a member ‘legal_ops’ which defines the set of operations that can be legally added to the
block. By default it is set to all of the above defined operations, but it can be useful in certain cases to only allow
a subset of operations (such as when transforms are being done that are “lowering” the blocks to more primitive
ops).
working_block(block=None)
Convenience function for capturing the current working block.
If a block is not passed, or if the block passed is None, then this will return the “current working block”. However,
if a block is passed in it will simply return that block instead. This feature is useful in allowing functions to
“override” the current working block.
reset_working_block()
Reset the working block to be empty.
set_working_block(block, no_sanity_check=False)
Set the working block to be the block passed as argument. Compatible with the ‘with’ statement.
Sanity checks will only be run if the new block is different from the original block.
temp_working_block()
Set the working block to be new temporary block.
If used with the ‘with’ statement the block will be reset to the original value (at the time of call) at exit of the
context.
add_wirevector(self, wirevector)
Add a wirevector object to the block.
Parameters
wirevector – WireVector object added to block
remove_wirevector(self, wirevector)
Remove a wirevector object from the block.
Parameters
wirevector – WireVector object removed from block
add_net(self, net)
Add a net to the logic of the block.
Parameters
net (LogicNet) – LogicNet object added to block
The passed net, which must be of type LogicNet, is checked and then added to the block. No wires are added by
this member, they must be added seperately with add_wirevector.
get_memblock_by_name(self, name, strict=False)
Get a reference to a memory stored in this block by name.
Parameters
• name (String) – name of memblock object
• strict – Determines if PyrtlError or None is thrown on no match. Defaults to False.
Returns
a memblock object with specified name
By fallthrough, if a matching memblock cannot be found the value None is returned. However, if the argument
strict is set to True, then this will instead throw a PyrtlError when no match is found.
This useful for when a block defines its own internal memory block, and during simulation you want to instantiate
that memory with certain values for testing. Since the Simulation constructor requires a reference to the memory
object itself, but the block you’re testing defines the memory internally, this allows you to get the object reference.
Note that this requires you know the name of the memory block, meaning that you most likely need to have
named it yourself.
Example:
# Can only access it after the `special_memory` block has been instantiated/called
special_mem = pyrtl.working_block().get_memblock_by_name('special_mem')
sim = pyrtl.Simulation(memory_value_map={
special_mem: {
0: 5,
1: 6,
2: 7,
}
})
inputs = {
'read_addr': '012012',
'write_addr': '012012',
'data': '890333',
'wen': '111000',
}
expected = {
'res': '567590',
}
sim.step_multiple(inputs, expected)
inputs = pyrtl.working_block().wirevector_subset(pyrtl.Input)
outputs = pyrtl.working_block().wirevector_subset(pyrtl.Output)
non_inputs = pyrtl.working_block().wirevector_subset(exclude = pyrtl.Input)
#returns set of all non-input wirevectors
logic_subset(self, op=None)
Return set of logicnets, filtered by the type(s) of logic op provided as op.
Parameters
op – Operation of logicnet to filter by. Defaults to None.
Returns
set of logicnets with corresponding op
If no op is specified, the full set of logicnets associated with the Block are returned. This is helpful for getting
all memories of a block for example.
get_wirevector_by_name(self, name, strict=False)
Return the wirevector matching name.
Parameters
• name (String) – name of wirevector object
• strict – Determines if PyrtlError or None is thrown on no match. Defaults to False.
Returns
a wirevector object with specified name
By fallthrough, if a matching wirevector cannot be found the value None is returned. However, if the argument
strict is set to True, then this will instead throw a PyrtlError when no match is found.
net_connections(self, include_virtual_nodes=False)
Returns a representation of the current block useful for creating a graph.
Parameters
include_virtual_nodes – if enabled, the wire itself will be used to signal an external source
or sink (such as the source for an Input net). If disabled, these nodes will be excluded from the
adjacency dictionaries
Returns
wire_src_dict, wire_sink_dict Returns two dictionaries: one that maps WireVectors to the logic
net that creates their signal and one that maps WireVectors to a list of logic nets that use the signal
These dictionaries make the creation of a graph much easier, as well as facilitate other places in which one would
need wire source and wire sink information.
Look at inputoutput.net_graph for one such graph that uses the information from this function.
class PostSynthBlock
This is a block with extra metadata required to maintain the pre-synthesis interface during post-synthesis.
It currently holds the following instance attributes:
• .io_map: a map from old IO wirevector to a list of new IO wirevectors it maps to;
this is a list because for unmerged io vectors, each old N-bit IO wirevector maps to N new 1-bit IO
wirevectors.
• .reg_map: a map from old register to a list of new registers; a list because post-synthesis,
each N-bit register has been mapped to N 1-bit registers
• .mem_map: a map from old memory block to the new memory block
The functions below provide ways of combining, slicing, and extending WireVectors in ways that are often use-
ful in hardware design. The functions below extend those member functions of the WireVector class iself (which
provides support for the python builtin len, slicing as just as in a python list e.g. wire[3:6], zero_extended(),
sign_extended(), and many operators such as addition and multiplication).
concat(*args)
Concatenates multiple WireVectors into a single WireVector.
Parameters
args (WireVector) – inputs to be concatenated
Returns
WireVector with length equal to the sum of the args’ lengths
You can provide multiple arguments and they will be combined with the right-most argument being the least
significant bits of the result. Note that if you have a list of arguments to concat together you will likely want
index 0 to be the least significant bit and so if you unpack the list into the arguments here it will be backwards.
The function concat_list is provided for that case specifically.
Example using concat to combine two bytes into a 16-bit quantity:
concat(msb, lsb)
concat_list(wire_list)
Concatenates a list of WireVectors into a single WireVector.
Parameters
wire_list – list of WireVectors to concat
Returns
WireVector with length equal to the sum of the args’ lengths
This take a list of WireVectors and concats them all into a single WireVector with the element at index 0 serving
as the least significant bits. This is useful when you have a variable number of WireVectors to concatenate,
otherwise “concat” is prefered.
Example using concat to combine two bytes into a 16-bit quantity:
match_bitwidth(*args, **opt)
Matches the argument wires’ bitwidth via zero or sign extension, returning new WireVectors
Parameters
• args – WireVectors of which to match bitwidths
• opt – Optional keyword argument ‘signed=True’ (defaults to False)
Returns
tuple of args in order with extended bits
Example of matching the bitwidths of two WireVectors a and b with with zero extention:
a, b = match_bitwidth(a, b)
Example of matching the bitwidths of three WireVectors a,`b`, and c with with sign extention:
a, b, c = match_bitwidth(a, b, c, signed=True)
truncate(wirevector_or_integer, bitwidth)
Returns a WireVector or integer truncated to the specified bitwidth
Parameters
• wirevector_or_integer – Either a WireVector or an integer to be truncated.
• bitwidth – The length to which the first argument should be truncated.
Returns
A truncated WireVector or integer as appropriate.
This function truncates the most significant bits of the input, leaving a result that is only “bitwidth” bits wide.
For integers this is performed with a simple bitmask of size “bitwidth”. For WireVectors the function calls
‘WireVector.truncate’ and returns a WireVector of the specified bitwidth.
Examples:
chop(w, *segment_widths)
Returns a list of WireVectors each a slice of the original ‘w’
Parameters
• w – The WireVector to be chopped up into segments
• segment_widths – Additional arguments are integers which are bitwidths
Returns
A list of WireVectors each with a proper segment width
This function chops a WireVector into a set of smaller WireVectors of different lengths. It is most useful when
multiple “fields” are contained with a single wirevector, 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, chop will throw an error if the sum of the lengths of the fields given is not the same as the length of
the wirevector to chop. Not also that chop assumes that the “rightmost” arguments are the least signficant bits
(just like pyrtl concat) which is normal for hardware functions but makes the list order a little counter intuitive.
Examples:
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.
as_wires(val, bitwidth=None, truncating=True, block=None)
Return wires from val which may be wires, integers (including IntEnums), strings, or bools.
Parameters
• val – a wirevector-like object or something that can be converted into a Const
• bitwidth – The bitwidth the resulting wire should be
• truncating (bool) – determines whether bits will be dropped to achieve the desired
bitwidth if it is too long (if true, the most-significant bits will be dropped)
• block (Block) – block to use for wire
This function is mainly used to coerce values into WireVectors (for example, operations such as “x+1” where
“1” needs to be converted to a Const WireVector). An example:
The function as_wires will convert the 3 to Const but keep x unchanged assuming it is a WireVector.
index = WireVector(1)
mux(index, a0, a1)
index = WireVector(2)
mux(index, a0, a1, a2, a3)
index = WireVector(3)
mux(index, a0, a1, a2, a3, a4, a5, default=0)
class Command(IntEnum):
ADD = 1
SUB = 2
enum_mux(cntrl, {Command.ADD: a+b, Command.SUB: a-b})
enum_mux(cntrl, {Command.ADD: a+b}, strict=False) # SUB case undefined
enum_mux(cntrl, {Command.ADD: a+b, otherwise: a-b})
enum_mux(cntrl, {Command.ADD: a+b}, default=a-b)
Parameters
• w – a WireVector to use as the starting point for the update
• range_start – the start of the range of bits to be updated
• range_end – the end of the range of bits to be updated
• newvalue – the value to be written in to the start:end range
• truncating – if true, silently clip newvalue to the proper bitwidth rather than throw an error
if the value provided is too large
Given a WireVector w, this function returns a new WireVector that is identical to w except in the range of bits
specified. In that specified range, the value newvalue is swapped in. For example: bitfield_update(w, 20, 23,
0x7) will return return a WireVector of the same length as w, and with the same values as w, but with bits 20, 21,
and 22 all set to 1.
Note that range_start and range_end will be inputs to a slice and so standard Python slicing rules apply (e.g.
negative values for end-relative indexing and support for None).
w = bitfield_update_set(w, {
(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
})
• field_map – (optional) A map from single-character field name in the bitpattern to the
desired name of field in the returned namedtuple. If given, all non-“1”/”0”/”?” characters in
the bitpattern must be present in the map.
Returns
A tuple of 1-bit WireVector carrying the result of the comparison, followed by a named tuple
containing the matched fields, if any.
This function will compare a multi-bit WireVector to a specified pattern of bits, where some of the pattern can
be “wildcard” bits. If any of the “1” or “0” values specified in the bitpattern fail to match the WireVector during
execution, a “0” will be produced, otherwise the value carried on the wire will be “1”. 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 the wirevector specified, 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 of WireVectors will 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?bbb11a”
will match a string such as “010010100111”, and the resultant matched fields are (a, b) = (0b001, 0b100), where
the ‘a’ field is the concenation of bits 9, 8, and 0, and the ‘b’ field 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 the wirevectors corresponding to those fields.
A prime example of this is for decoding an ISA’s instructions. Here we decode some RISC-V:
with pyrtl.conditional_assignment:
with match_bitpattern(inst, "iiiiiiiiiiiirrrrr010ddddd0000011") as (imm, rs1,␣
˓→rd):
m, (a, b) = match_pattern(w, '01aa1?bbb11a') # all bits with same letter make up␣
˓→same field
input_list(names, bitwidth=None)
Allocate and return a list of Inputs.
Parameters
• names – Names for the Inputs. Can be a list or single comma/space-separated string
• bitwidth – The desired bitwidth for the resulting Inputs.
Returns
List of Inputs.
Equivalent to:
output_list(names, bitwidth=None)
Allocate and return a list of Outputs.
Parameters
• names – Names for the Outputs. Can be a list or single comma/space-separated string
• bitwidth – The desired bitwidth for the resulting Outputs.
Returns
List of Outputs.
Equivalent to:
register_list(names, bitwidth=None)
Allocate and return a list of Registers.
Parameters
• names – Names for the Registers. Can be a list or single comma/space-separated string
• bitwidth – The desired bitwidth for the resulting Registers.
Returns
List of Registers.
Equivalent to:
Additionally, the names string can also contain an additional bitwidth specification separated by a / in the name.
This cannot be used in combination with a bitwidth value other than 1.
Examples:
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.
val_to_signed_integer(value, bitwidth)
Return value as intrepreted as a signed integer under twos complement.
Parameters
• value – a python integer holding the value to convert
• bitwidth – the length of the integer in bits to assume for conversion
Given an unsigned integer (not a wirevector!) covert that to a signed integer. This is useful for printing and
interpreting values which are negative numbers in twos complement.
val_to_signed_integer(0xff, 8) == -1
infer_val_and_bitwidth(True) == (1, 1)
infer_val_and_bitwidth(False) == (0, 1)
infer_val_and_bitwidth("5'd12") == (12, 5)
infer_val_and_bitwidth("5'b10") == (2, 5)
infer_val_and_bitwidth("5'b10").bitwidth == 5
infer_val_and_bitwidth("5'b10").value == 2
infer_val_and_bitwidth("8'B 0110_1100") == (108, 8)
log2(integer_val)
Return the log base 2 of the integer provided.
Parameters
integer_val – The integer to take the log base 2 of.
Returns
The log base 2 of integer_val, or throw PyRTL error if not power of 2
This function is useful when checking that powers of 2 are provided on inputs to functions. It throws an error if
a negative value is provided or if the value provided is not an even power of two.
Examples:
log2(2) # returns 1
log2(256) # returns 8
addrwidth = log2(size_of_memory) # will fail if size_of_memory is not a power of␣
˓→two
5.6.6 Debugging
set_debug_mode(debug=True)
Set the global debug mode.
Parameters
debug – Optional boolean paramter to which debug mode will be set
This function will set the debug mode to the specified value. Debug mode is, by default, set to off to keep the
performance of the system. With debug mode set to true, all temporary WireVectors created will be given a name
based on the line of code on which they were created and a snapshot of the call-stack for those WireVectors will
be kept as well.
probe(w, name=None)
Print useful information about a WireVector when in debug mode.
Parameters
• w – WireVector from which to get info
find_and_print_loop(block=None)
5.6.7 Reductions
and_all_bits(vector)
Returns WireVector, the result of “and”ing all items of the argument vector.
Parameters
vector – Takes a single arbitrary length WireVector
Returns
Returns a 1 bit result, the bitwise and of all of the bits in the vector to a single bit.
or_all_bits(vector)
Returns WireVector, the result of “or”ing all items of the argument vector.
Parameters
vector – Takes a single arbitrary length WireVector
Returns
Returns a 1 bit result, the bitwise or of all of the bits in the vector to a single bit.
xor_all_bits(vector)
Returns WireVector, the result of “xor”ing all items of the argument vector.
Parameters
vector – Takes a single arbitrary length WireVector
Returns
Returns a 1 bit result, the bitwise xor of all of the bits in the vector to a single bit.
parity(vector)
Returns WireVector, the result of “xor”ing all items of the argument vector.
Parameters
vector – Takes a single arbitrary length WireVector
Returns
Returns a 1 bit result, the bitwise xor of all of the bits in the vector to a single bit.
rtl_any(*vectorlist)
Hardware equivalent of Python native “any”.
Parameters
vectorlist (WireVector) – all arguments are WireVectors of length 1
Returns
WireVector of length 1
Returns a 1-bit WireVector which will hold a ‘1’ if any of the inputs are ‘1’ (i.e. it is a big ol’ OR gate). If no
inputs are provided it will return a Const 0 (since there are no ‘1’s present) similar to python’s any function called
with an empty list.
Examples:
rtl_all(*vectorlist)
Hardware equivalent of Python native “all”.
Parameters
vectorlist (WireVector) – all arguments are WireVectors of length 1
Returns
WireVector of length 1
Returns a 1-bit WireVector which will hold a ‘1’ only if all of the inputs are ‘1’ (i.e. it is a big ol’ AND gate).
If no inputs are provided it will return a Const 1 (since there are no ‘0’s present) similar to python’s all function
called with an empty list.
Examples:
tree_reduce(op, vector)
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 iself (which
provides support for addition, unsigned multiplication, unsigned comparison, and many others).
signed_add(a, b)
Return a WireVector for result of signed addition.
Parameters
• a – a WireVector to serve as first input to addition
• b – a WireVector to serve as second input to addition
Given a length n and length m WireVector the result of the signed addition is length max(n,m)+1. The inputs are
twos complement sign extended to the same length before adding. If an integer is passed to either a or b, it will
be converted automatically to a two’s complemented constant
signed_mult(a, b)
Return a*b where a and b are treated as signed values.
Parameters
• a – a wirevector to serve as first input to multiplication
• b – a wirevector to serve as second input to multiplication
If an integer is passed to either a or b, it will be converted automatically to a two’s complemented constant
signed_lt(a, b)
Return a single bit result of signed less than comparison.
signed_le(a, b)
Return a single bit result of signed less than or equal comparison.
signed_gt(a, b)
Return a single bit result of signed greater than comparison.
signed_ge(a, b)
Return a single bit result of signed greater than or equal comparison.
shift_left_arithmetic(bits_to_shift, shift_amount)
Shift left arithmetic operation.
Parameters
• bits_to_shift – WireVector to shift left
• shift_amount – WireVector or integer specifying amount to shift
Returns
WireVector of same length as bits_to_shift
This function returns a new WireVector of length equal to the length of the input bits_to_shift but where the bits
have been shifted to the left. An arithemetic shift is one that treats the value as as signed number, although for
left shift arithmetic and logic shift they are identical. Note that shift_amount is treated as unsigned.
shift_right_arithmetic(bits_to_shift, shift_amount)
Shift right arithmetic operation.
Parameters
• bits_to_shift – WireVector to shift right
5.7.1 Estimation
Contains functions to estimate aspects of blocks (like area and delay) by either using internal models or by making
calls out to external tool chains.
class PathsResult
Bases: dict
print(file=<_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>)
Pretty print the result of calling paths()
Parameters
f – the open file to print to (defaults to stdout)
Returns
None
class TimingAnalysis(block=None, gate_delay_funcs=None)
Bases: object
Timing analysis estimates the timing delays in the block
TimingAnalysis has an timing_map object that maps wires to the ‘time’ after a clock edge at which the signal in
the wire settles
__init__(block=None, gate_delay_funcs=None)
Calculates timing delays in the block.
Parameters
• block – pyrtl block to analyze
• gate_delay_funcs – a map with keys corresponding to the gate op and a function return-
ing the delay as the value. It takes the gate as an argument. If the delay is negative (-1), the
gate will be treated as the end of the block
Calculates the timing analysis while allowing for different timing delays of different gates of each type.
Supports all valid presynthesis blocks. Currently doesn’t support memory post synthesis.
critical_path(print_cp=True, cp_limit=100)
Takes a timing map and returns the critical paths of the system.
Parameters
print_cp – Whether to print the critical path to the terminal after calculation
Returns
a list containing tuples with the ‘first’ wire as the first value and the critical paths (which
themselves are lists of nets) as the second
max_freq(tech_in_nm=130, ffoverhead=None)
Estimates the max frequency of a block in MHz.
Parameters
• tech_in_nm – the size of the circuit technology to be estimated (for example, 65 is 65nm
and 250 is 0.25um)
• ffoverhead – setup and ff propagation delay in picoseconds
Returns
a number representing an estimate of the max frequency in Mhz
If a timing_map has already been generated by timing_analysis, it will be used to generate the estimate
(and gate_delay_funcs will be ignored). Regardless, all params are optional and have reasonable default
values. Estimation is based on Dennard Scaling assumption and does not include wiring effect – as a result
the estimates may be optimistic (especially below 65nm).
max_length()
Returns the max timing delay of the circuit in ps.
The result assumes that the circuit is implemented in a 130nm process, and that there is no setup or hold
time associated with the circuit. The resulting value is in picoseconds. If an proper estimation of timing is
required it is recommended to us “max_freq” to determine the clock period as it more accurately considers
scaling and setup/hold.
static print_critical_paths(critical_paths)
Prints the results of the critical path length analysis. Done by default by the TimingAnalysis().critical_path()
function.
print_max_length()
Prints the max timing delay of the circuit
area_estimation(tech_in_nm=130, block=None)
Estimates the total area of the block.
Parameters
tech_in_nm – the size of the circuit technology to be estimated (for example, 65 is 65nm and
250 is 0.25um)
Returns
tuple of estimated areas (logic, mem) in terms of mm^2
The estimations are based off of 130nm stdcell designs for the logic, and custom memory blocks from the lit-
erature. The results are not fully validated and we do not recommend that this function be used in carrying out
science for publication.
distance(src, dst, f, block=None)
Calculate the ‘distance’ along each path from src to dst according to f
Parameters
• src (WireVector) – wire to start from
• dst (WireVector) – wire to end on
• f ((LogicNet -> Int)) – function from a net to number, representing the ‘value’ of a net
that you want to sum across all nets in the path
• block (Block) – block to use (defaults to working block)
Returns
a map from each path (a tuple) to its calculated distance
This calls the given function f on each net in a path, summing the result.
fanout(w)
Get the number of places a wire is used as an argument.
Parameters
w – WireVector to check fanout for
Returns
integer fanout count
paths(src=None, dst=None, dst_nets=None, block=None)
Get the list of all paths from src to dst.
Parameters
• src (Union[WireVector, Iterable[WireVector]]) – source wire(s) from which to
trace your paths; if None, will get paths from all Inputs
• dst (Union[WireVector, Iterable[WireVector]]) – destination wire(s) to which to
trace your paths; if None, will get paths to all Outputs
• {WireVector – {LogicNet}} dst_nets: map from wire to set of nets where the wire is an
argument; will compute it internally if not given via a call to pyrtl.net_connections()
• block (Block) – block to use (defaults to working block)
Returns
a map of the form {src_wire: {dst_wire: [path]}} for each src_wire in src (or all inputs if src is
None), dst_wire in dst (or all outputs if dst is None), where path is a list of nets. This map is also
an instance of PathsResult, so you can call print() on it to pretty print it.
You can provide dst_nets (the result of calling pyrtl.net_connections()), if you plan on calling this function re-
peatedly on a block that hasn’t changed, to speed things up.
This function can accept one or more src wires, and one or more dst wires, such that it returns a map that can be
accessed like so:
paths[src][dst] = [<path>, <path>, . . . ]
where <path> is a list of nets. Thus there can be multiple paths from a given src wire to a given dst wire.
If src and dst are both single wires, you still need to access the result via paths[src][dst].
This also finds and returns the loop paths in the case of registers or memories that feed into themselves, i.e.
paths[src][src] is not necessarily empty.
It does not distinguish between loops that include synchronous vs asynchronous memories.
yosys_area_delay(library, abc_cmd=None, leave_in_dir=None, block=None)
Synthesize with Yosys and return estimate of area and delay.
Parameters
• library – stdcell library file to target in liberty format
• abc_cmd – string of commands for yosys to pass to abc for synthesis
• dir – the directory where temporary files should be left
• block – pyrtl block to analyze
Returns
a tuple of numbers: area, delay
If dir is specified, that directory will be used to create any temporary files, and the resulting files will be left
behind there (which can be useful for manual exploration or debugging)
The area and delay are returned in units as defined by the stdcell library. In the standard vsc 130nm library, the
area is in a number of “tracks”, each of which is about 1.74 square um (see area estimation for more details) and
the delay is in ps.
http://www.vlsitechnology.org/html/vsc_description.html
May raise PyrtlError if yosys is not configured correctly, and PyrtlInternalError if the call to yosys was not
successful
5.7.2 Optimization
5.7.3 Synthesis
• add_reset – If reset logic should be added. Allowable options are: False (meaning no
reset logic is added), True (default, for adding synchronous reset logic), and ‘asynchronous’
(for adding asynchronous reset logic). The value passed in here should match the argument
passed to output_to_verilog().
• block – Block containing design to test.
If add_reset is not False, a rst wire is added and will passed as an input to the instantiated toplevel module. The
rst wire will be held low in the testbench, because initialization here occurs via the initial block. It is provided
for consistency with output_to_verilog().
The test bench does not return any values.
Example 1 (writing testbench to a string):
This is a set of files that contain useful miscellaneous functions and classes
Adders
AES-128
Barrel
Library Utilities
Multipliers
Muxes
Matrix
Testing Utilities
5.9.1 Adders
kogge_stone(a, b, cin=0)
Creates a Kogge-Stone adder given two inputs
Parameters
• b (WireVector a,) – The two WireVectors to add up (bitwidths don’t need to match)
• cin – An optimal carry in WireVector or value
Returns
a Wirevector representing the output of the adder
The Kogge-Stone adder is a fast tree-based adder with O(log(n)) propagation delay, useful for performance critical
designs. However, it has O(n log(n)) area usage, and large fan out.
one_bit_add(a, b, cin=0)
ripple_add(a, b, cin=0)
ripple_half_add(a, cin=0)
5.9.2 AES-128
import pyrtl
from pyrtl.rtllib.aes import AES
aes = AES()
plaintext = pyrtl.Input(bitwidth=128, name='aes_plaintext')
key = pyrtl.Input(bitwidth=128, name='aes_key')
aes_ciphertext = pyrtl.Output(bitwidth=128, name='aes_ciphertext')
reset = pyrtl.Input(1, name='reset')
ready = pyrtl.Output(1, name='ready')
ready_out, aes_cipher = aes.encrypt_state_m(plaintext, key, reset)
ready <<= ready_out
aes_ciphertext <<= aes_cipher
sim_trace = pyrtl.SimulationTrace()
sim = pyrtl.Simulation(tracer=sim_trace)
sim.step ({
'aes_plaintext': 0x00112233445566778899aabbccddeeff,
'aes_key': 0x000102030405060708090a0b0c0d0e0f,
'reset': 1
})
for cycle in range(1,10):
(continues on next page)
class AES
Bases: object
__init__()
decryption(ciphertext, key)
Builds a single cycle AES Decryption circuit
Parameters
• ciphertext (WireVector) – data to decrypt
• key (WireVector) – AES key to use to encrypt (AES is symmetric)
Returns
a WireVector containing the plaintext
decryption_statem(ciphertext_in, key_in, reset)
Builds a multiple cycle AES Decryption state machine circuit
Parameters
reset – a one bit signal telling the state machine to reset and accept the current plaintext and
key
Return ready, plain_text
ready is a one bit signal showing that the decryption result (plain_text) has been calculated.
encrypt_state_m(plaintext_in, key_in, reset)
Builds a multiple cycle AES Encryption state machine circuit
Parameters
reset – a one bit signal telling the state machine to reset and accept the current plaintext and
key
Return ready, cipher_text
ready is a one bit signal showing that the encryption result (cipher_text) has been calculated.
encryption(plaintext, key)
Builds a single cycle AES Encryption circuit
Parameters
• plaintext (WireVector) – text to encrypt
• key (WireVector) – AES key to use to encrypt
Returns
a WireVector containing the ciphertext
Back to top of page
5.9.3 Barrel
match_bitwidth(*args)
Matches the bitwidth of all of the input arguments.
Parameters
args (WireVector) – input arguments
Returns
tuple of args in order with extended bits
partition_wire(wire, partition_size)
Partitions a wire into a list of N wires of size ‘partition_size’.
Parameters
• wire – Wire to partition
• partition_size – Integer representing size of each partition
The wire’s bitwidth must be evenly divisible by ‘parition_size’.
rev_twos_comp_repr(val, bitwidth)
Takes a two’s-complement represented value and converts it to a signed integer based on the provided bitwidth.
For use with Simulation.inspect() etc. when expecting negative numbers, which it does not recognize
str_to_int_array(string, base=16)
Converts a string to an array of integer values according to the base specified (int numbers must be whitespace
delimited).
Example: “13 a3 3c” => [0x13, 0xa3, 0x3c]
Returns
[int]
twos_comp_repr(val, bitwidth)
Converts a value to its two’s-complement (positive) integer representation using a given bitwidth (only converts
the value if it is negative).
Parameters
5.9.5 Multipliers
• signed (Bool) – Currently not supported (will be added in the future) The default will likely
be changed to True, so if you want the smallest set of wires in the future, specify this as False
• reducer – (advanced) The tree reducer to use
• adder_func – (advanced) The adder to use to add the two results at the end
Return WireVector
The result WireVector
signed_tree_multiplier(A, B, reducer=<function wallace_reducer>, adder_func=<function kogge_stone>)
Same as tree_multiplier, but uses two’s-complement signed integers
simple_mult(A, B, start)
Builds a slow, small multiplier using the simple shift-and-add algorithm. Requires very small area (it uses only a
single adder), but has long delay (worst case is len(A) cycles). start is a one-bit input to indicate inputs are ready.
done is a one-bit output signal raised when the multiplication is finished.
Parameters
B (WireVector A,) – two input wires for the multiplication
Returns
Register containing the product; the “done” signal
tree_multiplier(A, B, reducer=<function wallace_reducer>, adder_func=<function kogge_stone>)
Build an fast unclocked multiplier for inputs A and B using a Wallace or Dada Tree.
Parameters
• B (WireVector A,) – two input wires for the multiplication
• reducer (function) – Reduce the tree using either a Dada recuder or a Wallace reducer
determines whether it is a Wallace tree multiplier or a Dada tree multiplier
• adder_func (function) – an adder function that will be used to do the last addition
Return WireVector
The multiplied result
Delay is order logN, while area is order N^2.
Back to top of page
5.9.6 Muxes
This means that when the select wire equals the val1 wire the results will have the values in the coresponding
data wires (all ints are converted to wires)
__enter__()
For compatibility with with statements, which is the recommended method of using a MultiSelector.
__init__(signal_wire, *dest_wires)
default(*data_signals)
finalize()
Connects the wires.
option(select_val, *data_signals)
demux(select)
Demultiplexes a wire of arbitrary bitwidth
Parameters
select (WireVector) – indicates which wire to set on
Return (WireVector, . . . )
a tuple of wires corresponding to each demultiplexed wire
prioritized_mux(selects, vals)
Returns the value in the first wire for which its select bit is 1
Parameters
• selects ([WireVector]) – a list of WireVectors signaling whether a wire should be chosen
• vals ([WireVector]) – values to return when the corresponding select value is 1
Returns
WireVector
If none of the items are high, the last val is returned
sparse_mux(sel, vals)
Mux that avoids instantiating unnecessary mux_2s when possible.
Parameters
• sel (WireVector) – Select wire, determines what is selected on a given cycle
• vals (dictionary) – dictionary of values at mux inputs (of type {int:WireVector})
Returns
WireVector that signifies the change
This mux supports not having a full specification. Indices that are not specified are treated as don’t-cares
It also supports a specified default value, SparseDefault
Back to top of page
5.9.7 Matrix
matrix[1] == [3, 4, 5]
matrix[2, 0] == 6
matrix[(2, 0)] = 6
matrix[slice(0, 2), slice(0, 3)] == [[0, 1, 2], [3, 4, 5]]
matrix[0:2, 0:3] == [[0, 1, 2], [3, 4, 5]]
matrix[:2] == [[0, 1, 2], [3, 4, 5]]
matrix[-1] == [6, 7, 8]
matrix[-2:] == [[3, 4, 5], [6, 7, 8]]
__iadd__(other)
Perform the in-place addition operation.
Returns
a Matrix object with the elementwise addition being preformed
Is used with a += b. Performs an elementwise addition.
__imatmul__(other)
Performs the inplace matrix multiplication operation.
Parameters
other (Matrix) – the second matrix.
Returns
a PyRTL Matrix that contains the matrix multiplication product of this and other
Is used with a @= b.
Note: The matmul symbol (@) only works in python 3.5+. Otherwise you must call __imatmul__(other).
__imul__(other)
Perform the in-place multiplication operation.
Parameters
other (Matrix/Wirevector) – the Matrix or scalar to multiply
Returns
a Matrix object with the resulting multiplication operation being preformed
Is used with a *= b. Performs an elementwise or scalar multiplication.
__init__(rows, columns, bits, signed=False, value=None, max_bits=64)
Constructs a Matrix object.
Parameters
• rows (int) – the number of rows in the matrix. Must be greater than 0
• columns (int) – the number of columns in the matrix. Must be greater than 0
• bits (int) – The amount of bits per wirevector. Must be greater than 0
• signed (bool) – Currently not supported (will be added in the future)
• value ((WireVector/list)) – The value you want to initialize the Matrix with. If a
WireVector, must be of size rows * columns * bits. If a list, must have rows rows and
columns columns, and every element must fit in bits size. If not given, the matrix initializes
to 0
• max_bits (int) – The maximum number of bits each wirevector can have, even after
operations like adding two matrices together results in larger resulting wirevectors
Returns
a constructed Matrix object
__ipow__(power)
Performs the matrix power operation.
Parameters
power (int) – the power to perform the matrix on
Returns
a PyRTL Matrix that contains the matrix power product
Is used with a **= b.
__isub__(other)
Perform the inplace subtraction opperation.
Matrix other
the PyRTL Matrix to subtract
Returns
a Matrix object with the element wise subtraction being performed
Is used with a -= b. Performs an elementwise subtraction.
__len__()
Gets the output WireVector length.
Returns
an integer representing the output WireVector bitwidth
Used with default len() function
__matmul__(other)
Performs the matrix multiplication operation.
Parameters
other (Matrix) – the second matrix.
Returns
a PyRTL Matrix that contains the matrix multiplication product of this and other
Is used with a @ b.
Note: The matmul symbol (@) only works in python 3.5+. Otherwise you must call __matmul__(other).
__mul__(other)
Perform the elementwise or scalar multiplication operation.
Parameters
other (Matrix/Wirevector) – the Matrix to multiply
Returns
a Matrix object with the resulting multiplication operation being performed
Is used with a * b.
__pow__(power)
Performs the matrix power operation.
Parameters
power (int) – the power to perform the matrix on
Returns
a PyRTL Matrix that contains the matrix power product
Is used with a ** b.
__reversed__()
Constructs the reverse of matrix
Returns
a Matrix object representing the reverse
Used with the reversed() method
__setitem__(key, value)
Mutator for the matrix.
Parameters
• key ((slice/int rows, slice/int columns)) – The key value to set
• value (Wirevector/int/Matrix) – The value in which to set the key
Called when setting a value using square brackets (e.g. matrix[a, b] = value).
The value given will be truncated to match the bitwidth of all the elements in the matrix.
__sub__(other)
Perform the subtraction operation.
Matrix other
the PyRTL Matrix to subtract
Returns
a Matrix object with the elementwise subtraction being performed
to_wirevector()
Outputs the PyRTL Matrix as a singular concatenated Wirevector.
Returns
a Wirevector representing the whole PyRTL matrix
For instance, if we had a 2 x 1 matrix [[wire_a, wire_b]] it would return the concatenated wire: wire =
wire_a.wire_b
transpose()
Constructs the transpose of the matrix
Returns
a Matrix object representing the transpose
argmax(matrix, axis=None, bits=None)
Returns the index of the max value of the matrix.
Parameters
• matrix (Matrix/Wirevector) – the matrix to perform argmax operation on. If it is a
WireVector, it will return itself
• axis (None/int) – The axis to perform the operation on. None refers to argmax of all items.
0 is argmax of the columns. 1 is argmax of rows. Defaults to None
• bits (int) – The bits per value of the argmax. Defaults to bits of old matrix
Returns
A WireVector or Matrix representing the argmax value
NOTE: If there are two indices with the same max value, this function picks the first instance.
concatenate(matrices, axis=0)
Join a sequence of matrices along an existing axis.
Parameters
• matrices (list[Matrix]) – a list of matrices to concatenate one after another
• axix (int) – axis along which to join; 0 is horizontally, 1 is vertically (defaults to 0)
Returns
a new Matrix composed of the given matrices joined together
This function essentially wraps hstack/vstack.
dot(first, second)
Performs the dot product on two matrices.
Parameters
• first (Matrix) – the first matrix
• second (Matrix) – the second matrix
Returns
a PyRTL Matrix that contains the dot product of the two PyRTL Matrices
NOTE: Row vectors and column vectors are both treated as arrays
hstack(*matrices)
Stack matrices in sequence horizontally (column-wise).
Parameters
matrices (list[Matrix]) – a list of matrices to concatenate one after another horizontally
Return Matrix
a new Matrix, with the same number of rows as the original, with a bitwidth equal to the max of
the bitwidths of all the matrices
All the matrices must have the same number of rows and same ‘signed’ value.
For example:
m3 looks like:
[[1,2,3,17],
[4,5,6,23]]
list_to_int(matrix, n_bits)
Convert a Python matrix (a list of lists) into an integer.
Parameters
• matrix (list[list[int]]) – a pure Python list of lists representing a matrix
• n_bits (int) – number of bits to be used to represent each element; if an element doesn’t
fit in n_bits, it truncates the most significant bits
Return int
a N*n_bits wide wirevector containing the elements of matrix, where N is the number of elements
in matrix
Integers that are signed will automatically be converted to their two’s complement form.
This function is helpful for turning a pure Python list of lists into a integer suitable for creating a Constant
wirevector that can be passed in to as a Matrix intializer’s value argument, or for passing into a Simulation’s step
function for a particular input wire.
For example, calling Matrix.list_to_int([3, 5], [7, 9], 4) produces 13,689, which in binary looks like this:
Note how the elements of the list of lists were added, 4 bits at a time, in row order, such that the element at row
0, column 0 is in the most significant 4 bits, and the element at row 1, column 1 is in the least significant 4 bits.
Here’s an example of using it in simulation:
sim = pyrtl.Simulation()
sim.step({
'a_in': Matrix.list_to_int(a_vals)
'b_in': Matrix.list_to_int(b_vals)
})
output = Output(name='output')
output <<= m.to_wirevector()
(continues on next page)
sim = Simulation()
sim.step({})
# Produces:
# [[1, 2, 3], [4, 5, 6]]
• axis (None/int) – The axis to perform the operation on None refers to sum of all item. 0
is sum of column. 1 is sum of rows. Defaults to None
• bits (int) – The bits per value of the sum. Defaults to bits of old matrix
Returns
A wirevector or Matrix representing sum
vstack(*matrices)
Stack matrices in sequence vertically (row-wise).
Parameters
matrices (list[Matrix]) – a list of matrices to concatenate one after another vertically
Return Matrix
a new Matrix, with the same number of columns as the original, with a bitwidth equal to the max
of the bitwidths of all the matrices
All the matrices must have the same number of columns and same ‘signed’ value.
For example:
m3 looks like:
[[1,2,3],
[4,5,6],
[7,8,9]]
Returns
[Const_wires]; [Const_vals]
make_inputs_and_values(num_wires, max_bitwidth=None, exact_bitwidth=None, dist=<function
uniform_dist>, test_vals=20)
Generates multiple input wires and sets of test values for testing purposes
Parameters
dist (function) – function to generate the random values
Returns
wires; list of values for the wires
The list of values is a list of lists. The interior lists represent the values of a single wire for all of the simulation
cycles
multi_sim_multicycle(in_dict, hold_dict, hold_cycles, sim=None)
Simulates a circuit that takes multiple cycles to complete multiple times.
Parameters
• in_dict – {in_wire: [in_values, . . . ], . . . }
• hold_dict – {hold_wire: hold_value} The hold values for the
• hold_cycles –
• sim –
Returns
sim_and_ret_out(outwire, inwires, invals)
Simulates the net using inwires and invalues, and returns the output array. Used for rapid test development.
Parameters
• outwire – The wire to return the output of
• inwires – a list of wires to read in from ([Input, . . . ])
• invals – a list of input value lists ([ [int, . . . ], . . . ])
Returns
a list of values from the output wire simulation result
sim_and_ret_outws(inwires, invals)
Simulates the net using inwires and invalues, and returns the output array. Used for rapid test development.
Parameters
• inwires – a list of wires to read in from ([Input, . . . ])
• invals – a list of input value lists ([[int, . . . ], . . . ])
Returns
a list of values from the output wire simulation result
sim_multicycle(in_dict, hold_dict, hold_cycles, sim=None)
Simulation of a circuit that takes multiple cycles to complete.
Parameters
• in_dict –
• hold_dict –
• hold_cycles –
• sim –
Returns
uniform_dist(bitwidth)
Passes contains prebuilt transformantion passes to do optimization, lowering of the design to single wire gates (syn-
thesis), along with other ways to change a block.
and_inverter_synth(net)
Transforms a decomposed block into one consisting of ands and inverters in place
Parameters
block – The block to synthesize
common_subexp_elimination(block=None, abs_thresh=1, percent_thresh=0)
Common Subexpression Elimination for PyRTL blocks.
Parameters
• block – the block to run the subexpression elimination on
• abs_thresh – absolute threshold for stopping optimization
• percent_thresh – percent threshold for stopping optimization
constant_propagation(block, silence_unexpected_net_warnings=False)
Removes excess constants in the block.
Note on resulting block: The output of the block can have wirevectors that are driven but not listened to. This is
to be expected. These are to be removed by the _remove_unlistened_nets function
direct_connect_outputs(block=None)
Remove ‘w’ nets immediately before outputs, if possible.
Parameters
block – block to update (defaults to working block)
The ‘w’ nets that are eligible for removal with this pass meet the following requirements: * The destination
wirevector of the net is an Output * The source wirevector of the net doesn’t go to any other nets.
nand_synth(net)
Synthesizes an Post-Synthesis block into one consisting of nands and inverters in place
Parameters
block – The block to synthesize.
one_bit_selects(net)
Converts arbitrary-sliced selects to concatenations of 1-bit selects.
Parameters
block – The block to transform
This is useful for preparing the netlist for output to other formats, like FIRRTL or BTOR2, whose ‘select’ op-
eration (‘bits’ and ‘slice’, respectively) require contiguous ranges. Python slices are not necessarily contiguous
ranges, e.g. the range [::2] (syntactic sugar for slice(None, None, 2)) produces indices 0, 2, 4, etc. up to the
length of the list on which it is used.
optimize(update_working_block=True, block=None, skip_sanity_check=False)
Return an optimized version of a synthesized hardware block.
Parameters
• update_working_block (bool) – Don’t copy the block and optimize the new block (de-
faults to True)
• block (Block) – the block to optimize (defaults to working block)
• skip_sanity_check (bool) – Don’t perform sanity checks on the block before/during/after
the optimization passes (defaults to False). Sanity checks will always be performed if in
debug mode.
Note: optimize works on all hardware designs, both synthesized and non synthesized
synthesize(update_working_block=True, merge_io_vectors=True, block=None)
Lower the design to just single-bit “and”, “or”, “xor”, and “not” gates.
Parameters
• update_working_block – Boolean specifying if working block should be set to the newly
synthesized block.
• merge_io_wirevectors – if False, turn all N-bit IO wirevectors into N 1-bit IO wirevectors
(i.e. don’t maintain interface).
• block – The block you want to synthesize.
Returns
The newly synthesized block (of type PostSynthesisBlock).
Takes as input a block (default to working block) and creates a new block which is identical in function but
uses only single bit gates and excludes many of the more complicated primitives. The new block should consist
almost exclusively of the combination elements of w, &, |, ^, and ~ and sequential elements of registers (which
are one bit as well). The two exceptions are for inputs/outputs (so that we can keep the same interface) which
are immediately broken down into the individual bits and memories (read and write ports) which require the
reassembly and disassembly of the wirevectors immediately before and after. These are the only two places
where ‘c’ and ‘s’ ops should exist. If merge_io_vectors is False, then these individual bits are not reassembled
and disassembled before and after, and so no ‘c’ and ‘s’ ops will exist. Instead, they will be named <name>[n],
where n is the bit number of original wire to which it corresponds.
The block that results from synthesis is actually of type “PostSynthesisBlock” which contains a mapping from
the original inputs and outputs to the inputs and outputs of this block. This is used during simulation to map
the input/outputs so that the same testbench can be used both pre and post synthesis (see documentation for
Simulation for more details).
two_way_concat(net)
Transforms a block so all n-way (n > 2) concats are replaced with series of 2-way concats.
Parameters
block – The block to transform
This is useful for preparing the netlist for output to other formats, like FIRRTL or BTOR2, whose ‘concatenate’
operation (‘cat’ and ‘concat’, respectively), only allow two arguments (most-significant wire and least-significant
wire).
two_way_fanout(block=None)
Update the block such that no wire goes to more than 2 destination nets
Parameters
block – block to update (defaults to working block)
class PostSynthBlock
This is a block with extra metadata required to maintain the pre-synthesis interface during post-synthesis.
It currently holds the following instance attributes:
• .io_map: a map from old IO wirevector to a list of new IO wirevectors it maps to;
this is a list because for unmerged io vectors, each old N-bit IO wirevector maps to N new 1-bit IO
wirevectors.
• .reg_map: a map from old register to a list of new registers; a list because post-synthesis,
each N-bit register has been mapped to N 1-bit registers
• .mem_map: a map from old memory block to the new memory block
SIX
• genindex
• modindex
• search
81
PyRTL, Release 0.10.2
p
pyrtl.analysis, 50
pyrtl.conditional, 19
pyrtl.rtllib.adders, 61
pyrtl.rtllib.aes, 62
pyrtl.rtllib.barrel, 64
pyrtl.rtllib.libutils, 64
pyrtl.rtllib.matrix, 68
pyrtl.rtllib.multipliers, 65
pyrtl.rtllib.muxes, 66
pyrtl.rtllib.testingutils, 76
83
PyRTL, Release 0.10.2
85
PyRTL, Release 0.10.2
86 Index
PyRTL, Release 0.10.2
Index 87
PyRTL, Release 0.10.2
T
temp_working_block() (in module pyrtl.core), 34
TimingAnalysis (class in pyrtl.analysis), 51
to_wirevector() (Matrix method), 72
trace_to_html() (in module pyrtl.visualization), 59
transpose() (Matrix method), 72
tree_multiplier() (in module pyrtl.rtllib.multipliers),
66
tree_reduce() (in module pyrtl.corecircuits), 48
truncate() (in module pyrtl.helperfuncs), 38
truncate() (WireVector method), 14
two_way_concat() (in module pyrtl.passes), 55
twos_comp_repr() (in module pyrtl.rtllib.libutils), 64
U
uniform_dist() (in module pyrtl.rtllib.testingutils), 78
Utf8AltRendererConstants (class in
pyrtl.simulation), 29
Utf8RendererConstants (class in pyrtl.simulation), 29
V
val_to_formatted_str() (in module
pyrtl.helperfuncs), 44
val_to_signed_integer() (in module
pyrtl.helperfuncs), 44
vstack() (in module pyrtl.rtllib.matrix), 76
W
wallace_reducer() (in module pyrtl.rtllib.adders), 62
WaveRenderer (class in pyrtl.simulation), 28
WireVector (class in pyrtl.wire), 11
88 Index