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
blocktodest_file, in Verilog format.The Verilog module will have a clock input named
clk. Ifadd_resetis notFalse, there will also be a reset input namedrst.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 namedrst, and whenrstis high, registers will be reset to theirreset_value.initialize_registers (
bool, default:False) –Initialize Verilog registers to their
reset_value. When this argument isTrue, thisRegister: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
RomBlocksas 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)
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
Inputvalues from aSimulationTrace.If
add_resetisTrue,output_verilog_testbenchassumes the Verilog module under test has an input namedrst. But the testbench actually holdsrstlow, because all initialization is done in aninitialblock.output_verilog_testbenchonly generates the Verilog testbench. The Verilog module under test must be generated separately, by callingoutput_to_verilog():Use
output_to_verilog()to write the Verilog module under test to a file, likemy_module.vUse
output_verilog_testbenchto write the testbench to a different file, likemy_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 namedmy_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) – ASimulationTracefrom whichInputvalues will be extracted for inclusion in the test bench. This parameter is typically set toSimulation.tracer. The generated test bench will replay theSimulation’sInputscycle by cycle. The default values for all registers and memories will be based on theSimulationTrace, otherwise they will be initialized to 0.toplevel_include (
str|None, default:None) – Name of the file containing the Verilog module generated byoutput_to_verilog(). If notNone, a Verilogincludedirective will be added fortoplevel_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.vcdfile, via$dumpfile, andvcdis the name of the file to write. IfNone, then nodumpfilewill be used.cmd (
str|None, default:None) –cmdwill 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
outevery 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 matchoutput_to_verilog()’sadd_resetparameter.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 matchoutput_to_verilog()’smodule_nameparameter.
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_verilogrequires:yosys, which must be separately installed.The pyparsing
pippackage, 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; ifNone, defaults to first model defined in the Verilog file.leave_in_dir (
bool|None, default:None) – IfTrue, 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_blifrequires the pyparsingpippackage, which is an optional PyRTL dependency. Install with:$ pip install pyrtl[blif]
If
merge_io_vectorsisTrue, then given 1-bitInputwiresa[0]anda[1], these wires will be combined into a single 2-bitInputwireathat can be accessed by nameain the block. Otherwise ifmerge_io_vectorsisFalse, the original 1-bit wires will beInputwires of the block. This holds similarly forOutput.input_from_blifassumes the following:There is only one single shared clock and reset.
Output is generated by Yosys, with formals in a particular order.
input_from_blifcurrently 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) – IfTrue,Input/Outputwires whose names differ only by a indexing subscript (e.g. 1-bit wiresa[0]anda[1]) will be combined into a singleInput/Output(e.g. a 2-bit wirea).clock_name (
str, default:'clk') – The name of the clock.top_model (
str|None, default:None) – name of top-level model to instantiate; ifNone, 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_svgrequires the graphvizpippackage, 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 graphvizpippackage. 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_datais astrcontaining 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:- Parameters:
args – Any
argsare passed toblock_to_graphviz_string().kwargs – Any
kwargsare passed toblock_to_graphviz_string().
- Return type:
- 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_datais astrcontaining 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:Note
See
block_to_svg()which automates the call to dot.- Parameters:
block (
Block|None, default:None) –Blockto use (defaults to current working_block)namer (
Callable, default:<function _graphviz_default_namer at 0x78c041133480>) – A function mapping graph objects (WireVectororLogicNet) tostrlabels. Seegraphviz_detailed_namer().split_state (
bool, default:True) – IfTrue, split connections to/from aRegisterupdate net; this means that registers will be appear as source nodes of the network, andrnets (i.e. the logic for settingRegister.next) will be treated as sink nodes of the network.maintain_arg_order (
bool, default:False) – IfTrue, will add ordering constraints so incoming edges are ordered left-to-right for nets where argument order matters (e.g.<). Keeping this asFalseresults in a cleaner, though less visually precise, graphical output.
- Return type:
- 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
dictarguments areNone, the returned namer behaves identically to PyRTL’s default Graphviz namer. PyRTL’s default namer labels user-namedWireVectoredges with their names and bitwidths, and labels nodes (LogicNetsorInput/Output/Constterminals) with theiroperator symbolor name/value, respectively.graphviz_detailed_namercreates a custom namer function that retrieves additional node/edge information fromextra_node_infoandextra_edge_info. Any additional information in thesedictswill 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_datais astrcontaining 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:- Parameters:
extra_node_info (
dict[WireVector|LogicNet,str] |None, default:None) – Adictmapping from (LogicNetorWireVector) node to additional data about that node. The additional data will be converted tostrand printed next to the node’s label, in parentheses.extra_edge_info (
dict[WireVector,str] |None, default:None) – Adictmapping fromWireVectoredge to additional data about that edge. The additional data will be converted tostrand printed next to the edge’s label, in parentheses.
- Return type:
- Returns:
A function to label each element in the graph, which can be used as
block_to_graphviz_string()’snamer.
- pyrtl.output_to_trivialgraph(file=None, namer=<function _trivialgraph_default_namer>, block=None, split_state=False)[source]¶
Save
blocktofilein 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.tgfwhich can be opened with tools like yEd live, which produces this.svgfile:- 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 (aWireVectororLogicNet) as the first argument and a booleanis_edgeas the second that is setTrueif the object is aWireVector, and returns a string representing that object.block (
Block|None, default:None) –Blockto use (defaults to current working_block).split_state (
bool, default:False) – IfTrue, split connections to/from a register update net; this means that registers will be appear as source nodes of the network, andrLogicNets(i.e. the logic for settingRegister.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:
Note
Consider using
render_trace()orprint_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 thetrace_list.repr_func (
Callable[[int],str], default:<built-in function hex>) – Function to use for representing each value in the trace. Examples includehex(),oct(),bin(), andstr(for decimal),val_to_signed_integer()(for signed decimal) or the function returned byenum_name()(forIntEnum). Defaults tohex().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 argumentrepr_funcwill be used instead.
- Return type:
- Returns:
WaveJSON representing
simtrace.