Getting Started with Netlist Carpentry¶
This is a simple example notebook that shows how you can use the Netlist Carpentry framework to analyze and modify your digital netlist. This example comprises the basic steps on how to use the framework in general.
Check installation¶
- First check whether the installation of Netlist Carpentry has been successful.
- Execute the cell below to check if the package has been installed correctly!
Info: If you get a warning that Yosys is not found, don't panic. A common solution is found in the Check Yosys cell below.
In [1]:
Copied!
import netlist_carpentry
print("Hello World!")
print(f"Package {netlist_carpentry.__name__} is installed and seems to work!")
import netlist_carpentry
print("Hello World!")
print(f"Package {netlist_carpentry.__name__} is installed and seems to work!")
Hello World! Package netlist_carpentry is installed and seems to work!
Check Yosys¶
- Currently, Yosys is required for Netlist Carpentry, as it performs a small synthesis on the input Verilog code.
- Execute the cell below to check if Yosys is installed and working correctly!
- If the cell raises an OSError, try following the description of the error, by either installing Yosys (if it is not already installed) or letting Jupyter know where to find Yosys by modifying the
PATHvariable via os.environ["PATH"] (this change is only temporary and will not affect the global/system-wide path variable). Workaround for this case:
import os
import shutil
from pathlib import Path
custom_yosys_prog_path = Path(shutil.which("yosys")).parent # "parent", because the directory is required
os.environ["PATH"] += os.pathsep + str(custom_yosys_prog_path)
Info: Theoretically, Netlist Carpentry could be used without Yosys.
In this case, no Verilog files can be read in, and circuits can only be built inside the Netlist Carpentry framework using the corresponding methods to create modules with wires, ports and instances.
However, as this is usually not the case, Yosys is assumed as a prerequisite hereinafter.
In [2]:
Copied!
import shutil
yosys_path = shutil.which("yosys")
if yosys_path is None:
raise OSError('Yosys was not found!\n\tIf Yosys is not installed, please install it first.\n\tAlternatively, if Yosys is installed and Jupyter was just unable to find Yosys, please add Yosys to your PATH variable.\n\tYou could do this at the beginning of each notebook:\n\n\t\timport os\n\n\t\tcustom_yosys_prog_path = <path_to_your_yosys_installation>\n\t\tos.environ["PATH"] += os.pathsep + custom_yosys_prog_path\n\n\tTo find the path to your Yosys installation run "which yosys" in a terminal!')
else:
print(f"Successfully found Yosys! This is the path to Yosys: {yosys_path}")
import shutil
yosys_path = shutil.which("yosys")
if yosys_path is None:
raise OSError('Yosys was not found!\n\tIf Yosys is not installed, please install it first.\n\tAlternatively, if Yosys is installed and Jupyter was just unable to find Yosys, please add Yosys to your PATH variable.\n\tYou could do this at the beginning of each notebook:\n\n\t\timport os\n\n\t\tcustom_yosys_prog_path = \n\t\tos.environ["PATH"] += os.pathsep + custom_yosys_prog_path\n\n\tTo find the path to your Yosys installation run "which yosys" in a terminal!')
else:
print(f"Successfully found Yosys! This is the path to Yosys: {yosys_path}")
Successfully found Yosys! This is the path to Yosys: /progs/linux/EDA/openEDA/bin/yosys
Reading Verilog designs¶
- Netlist Carpentry is able to read Verilog designs and interpret them as circuits.
- For this, a small synthesis is executed using Yosys, resulting in a generic netlist in JSON format.
- Precondition: the Verilog design is syntactically correct.
- Execute the cell below to make Netlist Carpentry read the provided Verilog file and transform it into a Circuit Object.
Info: Netlist Carpentry uses Yosys to preprocess the design.
By default, any output from Yosys will be suppressed.
If you want to see the output from Yosys, add verbose=True as parameter to the netlist_carpentry.read function call.
Warning: Yosys must be installed and executable via yosys in the command line.
In [3]:
Copied!
# Without Yosys output:
print("Reading circuit from Verilog file 'simpleAdder.v' with custom circuit name Adder Circuit...")
circuit = netlist_carpentry.read("files/simpleAdder.v", top="simpleAdder", circuit_name="Adder Circuit")
# With Yosys output -- uncomment the following line by removing the '#':
# circuit = netlist_carpentry.read("files/simpleAdder.v", top="simpleAdder", circuit_name="Adder Circuit", verbose=True)
# Without Yosys output:
print("Reading circuit from Verilog file 'simpleAdder.v' with custom circuit name Adder Circuit...")
circuit = netlist_carpentry.read("files/simpleAdder.v", top="simpleAdder", circuit_name="Adder Circuit")
# With Yosys output -- uncomment the following line by removing the '#':
# circuit = netlist_carpentry.read("files/simpleAdder.v", top="simpleAdder", circuit_name="Adder Circuit", verbose=True)
Reading circuit from Verilog file 'simpleAdder.v' with custom circuit name Adder Circuit...
Structure of the Circuit class¶
The circuit object contains all modules that belong to the circuit as defined in the HDL source. In addition, the Circuit class also supports setting a top module.
classDiagram
class Circuit {
+ modules: Dict[str, Module]
+ top: Module
+ module_count: NonNegativeInt
+ set_top(module_name: str)
}
Circuit "1" *-- "0..*" Module
Access the contents of the read circuit¶
- The circuit object contains all information about the read netlist, i.e. all modules from the netlist and their contents.
- The circuit's content can be accessed via its attributes.
- This is presented exemplarily in the cell below for the number of modules in the circuit and the name of the top module.
In [4]:
Copied!
number_of_modules = circuit.module_count
print(f"The circuit consists of {number_of_modules} module(s)!")
module_names = ", ".join(circuit.modules.keys())
print(f"The circuit contains modules with the following names: {module_names}!")
top_name = circuit.top.name
print(f"The top module of the circuit has the name {top_name}!")
number_of_modules = circuit.module_count
print(f"The circuit consists of {number_of_modules} module(s)!")
module_names = ", ".join(circuit.modules.keys())
print(f"The circuit contains modules with the following names: {module_names}!")
top_name = circuit.top.name
print(f"The top module of the circuit has the name {top_name}!")
The circuit consists of 1 module(s)! The circuit contains modules with the following names: simpleAdder! The top module of the circuit has the name simpleAdder!
Take a look inside¶
- Retrieve a module from the circuit either by using
Circuit.get_module()or directly via the circuit's attributeCircuit.modules[<module_name>], as shown below. - The name of the module can be retrieved via
Module.name, the number of instances (primitive instances and module instances in the netlist) can be retrieved vialen(Module.instances), the number of ports can be retrieved vialen(Module.ports), the number of wires can be retrieved vialen(Module.wires).
Warning: Direct references to modules may throw an error if the requested module does not exist.
In [5]:
Copied!
# Access via `Circuit.get_module()` (safe method)
top_module = circuit.get_module(top_name)
print(f"The top module '{top_module.name}' has {len(top_module.instances)} instances, {len(top_module.ports)} ports and {len(top_module.wires)} wires!")
# Direct access via `Circuit.modules` (unsafe method)
top_module = circuit.modules[top_name]
print(f"The top module '{top_module.name}' still has {len(top_module.instances)} instances, {len(top_module.ports)} ports and {len(top_module.wires)} wires!")
# Direct access with a catched exception, if the requested module does not exist
try:
unknown_module = circuit.modules["unknown_module"]
except KeyError as err_name:
print(f"No module with name {err_name} is present!")
# Access via `Circuit.get_module()` (safe method)
top_module = circuit.get_module(top_name)
print(f"The top module '{top_module.name}' has {len(top_module.instances)} instances, {len(top_module.ports)} ports and {len(top_module.wires)} wires!")
# Direct access via `Circuit.modules` (unsafe method)
top_module = circuit.modules[top_name]
print(f"The top module '{top_module.name}' still has {len(top_module.instances)} instances, {len(top_module.ports)} ports and {len(top_module.wires)} wires!")
# Direct access with a catched exception, if the requested module does not exist
try:
unknown_module = circuit.modules["unknown_module"]
except KeyError as err_name:
print(f"No module with name {err_name} is present!")
The top module 'simpleAdder' has 4 instances, 6 ports and 8 wires! The top module 'simpleAdder' still has 4 instances, 6 ports and 8 wires! No module with name 'unknown_module' is present!
Write circuit to Verilog¶
- Any loaded circuit can be written back to Verilog using the
writefunction, as shown below. - The function takes the circuit object as argument, and optionally the path to write as well as a custom name for the Verilog file.
Warning: If the file already exists and the overwrite argument is set to False (or omitted completely), the function will raise an error.
In [6]:
Copied!
netlist_carpentry.write(circuit, output_file_path="output/adder_output.v", overwrite=True)
print("Wrote Verilog file 'output/adder_output.v', containing the Verilog representation of the provided circuit.")
netlist_carpentry.write(circuit, output_file_path="output/adder_output.v", overwrite=True)
print("Wrote Verilog file 'output/adder_output.v', containing the Verilog representation of the provided circuit.")
Wrote Verilog file 'output/adder_output.v', containing the Verilog representation of the provided circuit.
- Alternatively, use
Circuit.writeas a shortcut for the same effect, as shown below.
In [7]:
Copied!
circuit.write("output/adder_output_2.v", overwrite=True)
print("Wrote Verilog file 'output/adder_output_2.v', containing the Verilog representation of the provided circuit.")
circuit.write("output/adder_output_2.v", overwrite=True)
print("Wrote Verilog file 'output/adder_output_2.v', containing the Verilog representation of the provided circuit.")
Wrote Verilog file 'output/adder_output_2.v', containing the Verilog representation of the provided circuit.