Analysis and Optimization#

Tools for analyzing and optimizing aspects of PyRTL designs.

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 pyrtl.analysis.PathsResult[source]#
print(file=<_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>)[source]#

Pretty print the result of calling paths()

Parameters:

f – the open file to print to (defaults to stdout)

Returns:

None

class pyrtl.analysis.TimingAnalysis(block=None, gate_delay_funcs=None)[source]#

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)[source]#

Calculates timing delays in the block.

Parameters:
  • block (Block) – PyRTL block to analyze

  • gate_delay_funcs – a map with keys corresponding to the gate op and a function returning 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)[source]#

Takes a timing map and returns the critical paths of the system.

Parameters:

print_cp (bool) – 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)[source]#

Estimates the max frequency of a block in MHz.

Parameters:
  • tech_in_nm (float) – the size of the circuit technology to be estimated (for example, 65 is 65nm and 250 is 0.25um)

  • ffoverhead (float) – setup and ff propagation delay in picoseconds

Returns:

a number representing an estimate of the max frequency in Mhz

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()[source]#

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 TimingAnalysis.max_freq() to determine the clock period as it more accurately considers scaling and setup/hold.

static print_critical_paths(critical_paths)[source]#

Prints the results of the critical path length analysis. Done by default by the TimingAnalysis.critical_path() function.

print_max_length()[source]#

Prints the max timing delay of the circuit

pyrtl.analysis.area_estimation(tech_in_nm=130, block=None)[source]#

Estimates the total area of the block.

Parameters:

tech_in_nm (float) – 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 standard cell designs for the logic, and custom memory blocks from the literature. The results are not fully validated and we do not recommend that this function be used in carrying out science for publication.

pyrtl.analysis.distance(src, dst, f, block=None)[source]#

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 (Callable[[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.

pyrtl.analysis.fanout(w)[source]#

Get the number of places a wire is used as an argument.

Parameters:

w (WireVector) – WireVector to check fanout for

Returns:

integer fanout count

pyrtl.analysis.paths(src=None, dst=None, dst_nets=None, block=None)[source]#

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

  • dst_nets (dict[WireVector, LogicNet]) – 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 PathsResult.print() on it to pretty print it.

You can provide dst_nets (the result of calling net_connections(), if you plan on calling this function repeatedly 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.

pyrtl.analysis.yosys_area_delay(library, abc_cmd=None, leave_in_dir=None, block=None)[source]#

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

Optimization#

pyrtl.passes.optimize(update_working_block=True, block=None, skip_sanity_check=False)[source]#

Return an optimized version of a synthesized hardware block.

Parameters:
  • update_working_block (bool) – Don’t copy the block and optimize the new block (defaults 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

Synthesis#

pyrtl.passes.synthesize(update_working_block=True, merge_io_vectors=True, block=None)[source]#

Lower the design to just single-bit “and”, “or”, “xor”, and “not” gates.

Parameters:
  • update_working_block (bool) – Boolean specifying if working block should be set to the newly synthesized block.

  • merge_io_wirevectors (bool) – if False, turn all N-bit IO wirevectors into N 1-bit IO wirevectors (i.e. don’t maintain interface).

  • block (Block) – The block you want to synthesize.

Returns:

The newly synthesized block (of type PostSynthBlock).

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 PostSynthBlock 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).

class pyrtl.core.PostSynthBlock[source]#

Bases: Block

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

Individual Passes#

pyrtl.passes.common_subexp_elimination(block=None, abs_thresh=1, percent_thresh=0)[source]#

Common Subexpression Elimination for PyRTL blocks.

Parameters:
  • block (Block) – the block to run the subexpression elimination on

  • abs_thresh (float) – absolute threshold for stopping optimization

  • percent_thresh (float) – percent threshold for stopping optimization

pyrtl.passes.constant_propagation(block, silence_unexpected_net_warnings=False)[source]#

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 _remove_unlistened_nets()

pyrtl.passes.nand_synth(net)[source]#

Synthesizes a PostSynthBlock into one consisting of nands and inverters in place

Parameters:

block (PostSynthBlock) – The block to synthesize.

pyrtl.passes.and_inverter_synth(net)[source]#

Transforms a decomposed block into one consisting of ands and inverters in place

Parameters:

block (Block) – The block to synthesize

pyrtl.passes.one_bit_selects(net)[source]#

Converts arbitrary-sliced selects to concatenations of 1-bit selects.

Parameters:

block (Block) – The block to transform

This is useful for preparing the netlist for output to other formats, like FIRRTL or BTOR2, whose select operation (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.

pyrtl.passes.two_way_concat(net)[source]#

Transforms a block so all n-way (n > 2) concats are replaced with series of 2-way concats.

Parameters:

block (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).