Exporting and Importing Designs

Exporting Hardware Designs

pyrtl.output_to_verilog(dest_file=None, add_reset=True, block=None, initialize_registers=False, module_name='toplevel')[source]

Output block to dest_file, in Verilog format.

The Verilog module will have a clock input named clk. If add_reset is not False, there will also be a reset input named rst.

When possible, wires keep their names in the Verilog output. Wire names that do not satisfy Verilog’s naming requirements, and wires that conflict with Verilog keywords, are given new temporary names in the Verilog output.

Example:

>>> a = pyrtl.Input(name="a", bitwidth=3)
>>> b = pyrtl.Input(name="b", bitwidth=3)
>>> sum = pyrtl.Output(name="sum", bitwidth=4)
>>> sum <<= a + b

>>> pyrtl.output_to_verilog(add_reset=False)
// Generated automatically via PyRTL
// As one initial test of synthesis, map to FPGA with:
//   yosys -p "synth_xilinx -top toplevel" thisfile.v

module toplevel(clk, a, b, sum);
    input clk;
    input[2:0] a;
    input[2:0] b;
    output[3:0] sum;

    // Combinational logic
    assign sum = (a + b);
endmodule
Parameters:
  • dest_file (TextIO | None, default: None) – Open file where the Verilog output will be written. Defaults to stdout.

  • add_reset (bool | str, default: True) – 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 reset input will be named rst, and when rst is high, registers will be reset to their reset_value.

  • initialize_registers (bool, default: False) –

    Initialize Verilog registers to their reset_value. When this argument is True, this Register:

    pyrtl.Register(name="foo", bitwidth=8, reset_value=4)
    

    generates this Verilog:

    reg[7:0] foo = 8'd4;
    

  • block (Block, default: None) – Block to be exported. Defaults to the working_block.

  • module_name (str, default: 'toplevel') – Name of the Verilog module.

pyrtl.output_to_firrtl(open_file=None, rom_blocks=None, block=None)[source]

Output the block as FIRRTL code to the output file.

If ROM is initialized in PyRTL code, you can pass in the RomBlocks as a list [rom1, rom2, ...].

Example:

>>> a = pyrtl.Input(name="a", bitwidth=3)
>>> b = pyrtl.Input(name="b", bitwidth=3)
>>> sum = a + b
>>> sum.name = "sum"

>>> pyrtl.output_to_firrtl()
circuit Example :
  module Example :
    input clock : Clock
    input reset : UInt<1>
    input a : UInt<3>
    input b : UInt<3>
    wire sum : UInt<4>

    sum <= add(a, b)
Parameters:

Exporting Testbenches

pyrtl.output_verilog_testbench(dest_file=None, simulation_trace=None, toplevel_include=None, vcd='waveform.vcd', cmd=None, add_reset=True, block=None, module_name='toplevel')[source]

Output a Verilog testbench that supplies a Verilog module under test with Input values from a SimulationTrace.

If add_reset is True, output_verilog_testbench assumes the Verilog module under test has an input named rst. But the testbench actually holds rst low, because all initialization is done in an initial block.

output_verilog_testbench only generates the Verilog testbench. The Verilog module under test must be generated separately, by calling output_to_verilog():

  1. Use output_to_verilog() to write the Verilog module under test to a file, like my_module.v

  2. Use output_verilog_testbench to write the testbench to a different file, like my_module_testbench.v

This separates the hardware under test from its simulation test script, which makes it easier to simulate the same hardware with multiple test scripts, and to run the hardware through synthesis tools.

The following example assumes output_to_verilog()’s output will be written to a file named my_module.v:

>>> a = pyrtl.Input(name="a", bitwidth=3)
>>> b = pyrtl.Input(name="b", bitwidth=3)
>>> sum = pyrtl.Output(name="sum", bitwidth=4)
>>> sum <<= a + b

>>> sim = pyrtl.Simulation()
>>> sim.step({"a": 1, "b": 2})

>>> # "my_module.v" can be generated separately with:
>>> #     with open("my_module.v", "w") as file:
>>> #         pyrtl.output_to_verilog(dest_file=file, add_reset=False)

>>> pyrtl.output_verilog_testbench(
...     simulation_trace=sim.tracer,
...     toplevel_include="my_module.v",
...     vcd=None,
...     cmd='$display("sum: %d", sum);',
...     add_reset=False
... )
`include "my_module.v"

module tb();
    reg clk;

    // block Inputs
    reg[2:0] a;
    reg[2:0] b;

    // block Outputs
    wire[3:0] sum;

    toplevel block(.clk(clk), .a(a), .b(b), .sum(sum));

    always
        #5 clk = ~clk;

    initial begin
        clk = 1'd0;
        a = 3'd1;
        b = 3'd2;

        #10
        $display("sum: %d", sum);
        $finish;
    end
endmodule

The Verilog testbench can be run with Verilator, assuming it was written to my_module_testbench.v:

$ verilator --binary my_module_testbench.v
...
- V e r i l a t i o n   R e p o r t: Verilator 5.032 2025-01-01 rev (Debian 5.032-1)
- Verilator: Built from 0.022 MB sources in 3 modules, into 0.029 MB in 8 C++ files needing 0.000 MB
- Verilator: Walltime 3.555 s (elab=0.017, cvt=0.010, bld=3.526); cpu 0.005 s on 1 threads; alloced 7.875 MB

$ ./obj_dir/Vmy_module_testbench
sum:  3
- my_module_testbench.v:25: Verilog $finish
- S i m u l a t i o n   R e p o r t: Verilator 5.032 2025-01-01
- Verilator: $finish at 15ps; walltime 0.001 s; speed 10.483 ns/s
- Verilator: cpu 0.001 s on 1 threads; alloced 249 MB
Parameters:
  • dest_file (TextIO | None, default: None) – An open file to which the test bench will be printed. Defaults to stdout.

  • simulation_trace (SimulationTrace | None, default: None) – A SimulationTrace from which Input values will be extracted for inclusion in the test bench. This parameter is typically set to Simulation.tracer. The generated test bench will replay the Simulation’s Inputs cycle by cycle. The default values for all registers and memories will be based on the SimulationTrace, otherwise they will be initialized to 0.

  • toplevel_include (str | None, default: None) – Name of the file containing the Verilog module generated by output_to_verilog(). If not None, a Verilog include directive will be added for toplevel_include.

  • vcd (str, default: 'waveform.vcd') – By default, the testbench generator will generate a command to write the output of the testbench execution to a .vcd file, via $dumpfile, and vcd is the name of the file to write. If None, then no dumpfile will be used.

  • cmd (str | None, default: None) –

    cmd will be copied verbatim into the testbench just before the end of each cycle. This is useful for printing specific values during testbench evaluation. For example:

    cmd='$display("time: %t out: %d", $time, out);'
    

    will print the current simulation time and the value of out every cycle.

  • add_reset (bool | str, default: True) – 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). Must match output_to_verilog()’s add_reset parameter.

  • block (Block, default: None) – Block containing design to test. Defaults to the working_block.

  • module_name (str, default: 'toplevel') – Name of the Verilog module. Must match output_to_verilog()’s module_name parameter.

Importing Verilog

pyrtl.input_from_verilog(verilog, clock_name='clk', toplevel=None, leave_in_dir=None, block=None)[source]

Read an open Verilog file or string as input via Yosys conversion, updating the block.

Warning

input_from_verilog requires:

  1. yosys, which must be separately installed.

  2. The pyparsing pip package, which is an optional PyRTL dependency. Install with:

    $ pip install pyrtl[blif]
    

This function runs Yosys with a script to convert Verilog to BLIF, then calls input_from_blif() on the converted BLIF file.

If the Yosys conversion fails, we recommend you create your own custom Yosys script to try and produce BLIF yourself. Then you can import BLIF directly via input_from_blif().

Example:

verilog = '''
module toplevel(clk, a, b, sum);
    input clk;
    input[2:0] a;
    input[2:0] b;
    output[3:0] sum;
    assign sum = (a + b);
endmodule
'''

pyrtl.input_from_verilog(verilog)

sim = pyrtl.Simulation()
sim.step({"a": 1, "b": 2})
sim.inspect("sum")
Parameters:
  • verilog (TextIO | str) – An open Verilog file to read, or a string containing Verilog data.

  • clock_name (str, default: 'clk') – The name of the clock (defaults to "clk").

  • toplevel (str | None, default: None) – Name of top-level module to instantiate; if None, defaults to first model defined in the Verilog file.

  • leave_in_dir (bool | None, default: None) – If True, save the intermediate BLIF file created in the given directory.

  • block (Block, default: None) – The block where the logic will be added. Defaults to the working_block.

pyrtl.input_from_blif(blif, block=None, merge_io_vectors=True, clock_name='clk', top_model=None)[source]

Read an open BLIF file or string as input, updating the block appropriately.

Warning

input_from_blif requires the pyparsing pip package, which is an optional PyRTL dependency. Install with:

$ pip install pyrtl[blif]

If merge_io_vectors is True, then given 1-bit Input wires a[0] and a[1], these wires will be combined into a single 2-bit Input wire a that can be accessed by name a in the block. Otherwise if merge_io_vectors is False, the original 1-bit wires will be Input wires of the block. This holds similarly for Output.

input_from_blif assumes the following:

  • There is only one single shared clock and reset.

  • Output is generated by Yosys, with formals in a particular order.

input_from_blif currently supports multi-module (unflattened) BLIF, though we recommend importing a flattened BLIF with a single module when possible. It currently ignores the reset signal (which it assumes is input only to the flip flops).

Parameters:
  • blif (TextIO) – An open BLIF file to read.

  • block (Block, default: None) – The block where the logic will be added. Defaults to the working_block.

  • merge_io_vectors (bool, default: True) – If True, Input/Output wires whose names differ only by a indexing subscript (e.g. 1-bit wires a[0] and a[1]) will be combined into a single Input/Output (e.g. a 2-bit wire a).

  • clock_name (str, default: 'clk') – The name of the clock.

  • top_model (str | None, default: None) – name of top-level model to instantiate; if None, defaults to first model listed in the BLIF.

Visualizing Blocks

Block and GateGraph provides basic __str__ methods for visualizing blocks:

>>> a = pyrtl.Input(name="a", bitwidth=3)
>>> b = pyrtl.Input(name="b", bitwidth=3)
>>> sum = a + b
>>> sum.name = "sum"

>>> print(pyrtl.working_block())
sum/4W <-- + -- a/3I, b/3I

>>> print(pyrtl.GateGraph())
a/3 = Input
b/3 = Input
sum/4 = add(a/3, b/3)

This section covers more advanced visualization methods.

pyrtl.block_to_svg(*args, **kwargs)[source]

Return a SVG rendering of a block.

Warning

block_to_svg requires the graphviz pip package, which is an optional PyRTL dependency. Install with:

$ pip install pyrtl[svg]

This just calls block_to_graphviz_string() and renders the Graphviz string as SVG, using the graphviz pip package. Example:

>>> a = pyrtl.WireVector(name="a", bitwidth=3)
>>> b = pyrtl.WireVector(name="b", bitwidth=3)
>>> sum = a + b
>>> sum.name = "sum"

>>> svg_data = pyrtl.block_to_svg()

svg_data is a str containing the SVG data. This data can be written to a file:

with open("block_to_svg.svg", "w") as file:
    file.write(svg_data)

This creates block_to_svg.svg, which can be opened by most web browsers:

_images/block_to_svg.svg
Parameters:
Return type:

str

Returns:

A SVG representation of the block.

pyrtl.block_to_graphviz_string(block=None, namer=<function _graphviz_default_namer>, split_state=True, maintain_arg_order=False)[source]

Return a Graphviz dot string representing the block.

Example:

>>> a = pyrtl.WireVector(name="a", bitwidth=3)
>>> b = pyrtl.WireVector(name="b", bitwidth=3)
>>> sum = a + b
>>> sum.name = "sum"

>>> graphviz_data = pyrtl.block_to_graphviz_string()

graphviz_data is a str containing the Graphviz data. This data can be written to a file:

with open("block_to_graphviz_string.dot", "w") as file:
    file.write(graphviz_data)

And rendered with Graphviz’s dot tool:

$ dot -Tsvg block_to_graphviz_string.dot > block_to_graphviz_string.svg

This creates block_to_graphviz_string.svg, which can be opened by most web browsers:

_images/block_to_graphviz_string.svg

Note

See block_to_svg() which automates the call to dot.

Parameters:
  • block (Block | None, default: None) – Block to use (defaults to current working_block)

  • namer (Callable, default: <function _graphviz_default_namer at 0x78c041133480>) – A function mapping graph objects (WireVector or LogicNet) to str labels. See graphviz_detailed_namer().

  • split_state (bool, default: True) – If True, split connections to/from a Register update net; this means that registers will be appear as source nodes of the network, and r nets (i.e. the logic for setting Register.next) will be treated as sink nodes of the network.

  • maintain_arg_order (bool, default: False) – If True, will add ordering constraints so incoming edges are ordered left-to-right for nets where argument order matters (e.g. <). Keeping this as False results in a cleaner, though less visually precise, graphical output.

Return type:

str

Returns:

A Graphviz representation of the block.

pyrtl.graphviz_detailed_namer(extra_node_info=None, extra_edge_info=None)[source]

Returns a detailed Graphviz namer for block_to_graphviz_string() that displays extra information about nodes/edges.

If both dict arguments are None, the returned namer behaves identically to PyRTL’s default Graphviz namer. PyRTL’s default namer labels user-named WireVector edges with their names and bitwidths, and labels nodes (LogicNets or Input/Output/Const terminals) with their operator symbol or name/value, respectively.

graphviz_detailed_namer creates a custom namer function that retrieves additional node/edge information from extra_node_info and extra_edge_info. Any additional information in these dicts will be displayed in parentheses in the Graphviz graph.

Example:

>>> a = pyrtl.WireVector(name="a", bitwidth=3)
>>> b = pyrtl.WireVector(name="b", bitwidth=3)
>>> sum = a + b
>>> sum.name = "sum"

>>> add_logicnets = pyrtl.working_block().logic_subset("+")
>>> len(add_logicnets)
1
>>> add_logicnet = add_logicnets.pop()

>>> namer = pyrtl.graphviz_detailed_namer(
...     extra_node_info={add_logicnet: "ADD NODE"},
...     extra_edge_info={a: "EDGE A", b: "EDGE B"}
... )

>>> svg_data = pyrtl.block_to_svg(namer=namer)

svg_data is a str containing the SVG data. This data can be written to a file:

with open("block_to_custom_svg.svg", "w") as file:
    file.write(svg_data)

This creates block_to_custom_svg.svg, which can be opened by most web browsers:

_images/block_to_custom_svg.svg
Parameters:
  • extra_node_info (dict[WireVector | LogicNet, str] | None, default: None) – A dict mapping from (LogicNet or WireVector) node to additional data about that node. The additional data will be converted to str and printed next to the node’s label, in parentheses.

  • extra_edge_info (dict[WireVector, str] | None, default: None) – A dict mapping from WireVector edge to additional data about that edge. The additional data will be converted to str and printed next to the edge’s label, in parentheses.

Return type:

Callable

Returns:

A function to label each element in the graph, which can be used as block_to_graphviz_string()’s namer.

pyrtl.output_to_trivialgraph(file=None, namer=<function _trivialgraph_default_namer>, block=None, split_state=False)[source]

Save block to file in trivial graph format.

Example:

a = pyrtl.WireVector(name="a", bitwidth=3)
b = pyrtl.WireVector(name="b", bitwidth=3)
sum = a + b
sum.name = "sum"

with open("block.tgf", "w") as file:
    pyrtl.output_to_trivialgraph(file)

This creates block.tgf which can be opened with tools like yEd live, which produces this .svg file:

_images/output_to_trivialgraph.svg
Parameters:
  • file (TextIO | None, default: None) – Open file to write to. Defaults to stdout.

  • namer (Callable[[WireVector | LogicNet, bool], str], default: <function _trivialgraph_default_namer at 0x78c041132f00>) – A function that takes in an object (a WireVector or LogicNet) as the first argument and a boolean is_edge as the second that is set True if the object is a WireVector, and returns a string representing that object.

  • block (Block | None, default: None) – Block to use (defaults to current working_block).

  • split_state (bool, default: False) – If True, split connections to/from a register update net; this means that registers will be appear as source nodes of the network, and r LogicNets (i.e. the logic for setting Register.next) will be treated as sink nodes of the network.

Visualizing Simulation Traces

SimulationTrace has render_trace() and print_vcd() methods which cover most common use cases. This section covers an additional visualization method.

pyrtl.trace_to_json(simtrace, trace_list=None, sortkey=None, repr_func=<built-in function hex>, repr_per_name=None)[source]

Return a WaveJSON representation of a SimulationTrace.

The returned WaveJSON is compatible with WaveDrom; see the WaveDrom README for various ways to visualize WaveJSON.

Example:

>>> counter = pyrtl.Register(name="counter", bitwidth=2)
>>> counter.next <<= counter + 1

>>> sim = pyrtl.Simulation()
>>> sim.step_multiple(nsteps=4)

>>> print(pyrtl.trace_to_json(sim.tracer, repr_func=str))
{"signal": [{"name": "counter", "wave": "====", "data": ["0", "1", "2", "3"]}], "config": {"hscale": 1}, "head": {"tick": 0}}

Pasting the WaveJSON output from the example above in the WaveDrom editor produces this SVG:

_images/wavedrom.svg

Note

Consider using render_trace() or print_vcd() instead.

Parameters:
  • simtrace (SimulationTrace) – A trace to convert to WaveJSON.

  • trace_list (list[str] | None, default: None) – (optional) A list of wires to include in the WaveJSON.

  • sortkey (default: None) – (optional) The key with which to sort the trace_list.

  • repr_func (Callable[[int], str], default: <built-in function hex>) – Function to use for representing each value in the trace. Examples include hex(), oct(), bin(), and str (for decimal), val_to_signed_integer() (for signed decimal) or the function returned by enum_name() (for IntEnum). Defaults to hex().

  • repr_per_name (dict[str, Callable[[int], str]] | None, default: None) – Map from signal name to a function that takes in the signal’s value and returns a user-defined representation. If a signal name is not found in the map, the argument repr_func will be used instead.

Return type:

str

Returns:

WaveJSON representing simtrace.