Common Python API
Introduction
OpenFASoC generators have a Python script that runs the generator flow (See Generators for more information). Many of the steps in the Python script are common across generators.
A common Python module is exported in ``openfasoc/generators/common/``to reduce code duplication and simplify the creation of new generators. This module exports various functions and constants which may be used in generators.
Importing
Since the common Python module is in the openfasoc/generators/common/ directory, which is a parent directory, the path to this directory must be added using sys.path.append().
import sys
sys.path.append(
# This is the relative path to the `openfasoc/generators/common/` directory.
os.path.join(os.path.dirname(__file__), '..', '..')
)
Once the directory is added to the path, the exported modules can be imported using the standard Python import syntax.
from common.verilog_generation import generate_verilog
See Exported Modules for a list of exported modules and their respective functions and constants.
Exported Modules
The top level module common exports the following submodules.
1. Verilog Generation (common.verilog_generation)
This module exports functions and constants used in the Verilog generation step. See Temperature Sensor Generator for more about Verilog generation.
This module uses the Mako templating library to convert source Verilog templates into final Verilog files used in the OpenROAD flow.
Functions
generate_verilog(parameters: dict, src_dir: str, out_dir: str) -> NoneReads source Verilog files from
src_dirand generates output Verilog files for synthesis in the OpenROAD flow. The source files are Mako templates.The
parametersargument is a dictionary of all the parameters used in the source Verilog Mako templates. Use the${parameter}}syntax in the source files to insert parameters. For example, the number of inverters in the Temperature Sensor Generator is a parameter.This function maintains the source directory (
src_dir) structure in the output directory (out_dir). i.e., source files from a subdirectory of thesrc_dirwill be generated in a subdirectory inout_dirwith the same name.- Arguments:
src_dir(str): Path to the directory with the source Verilog templates. (default:src/)out_dir(str): Path to the directory in which the output will be generated. (default:flow/design/src/)parameters(dict): Dictionary of all the parameters used in the Mako templates.
- Example:
generate_verilog( # Generates the output in flow/design/src/ out_dir=os.path.join('flow', 'design', 'src', 'tempsense'), # Sets the parameters used in the design parameters={ "ninv": 6, "nhead": 3, "design_name": "tempsenseInst_error", } )
See the generators’ Python files in
tools/for more examples.- This function also appends (can be directly used in the source Verilog files) the following Mako defs:
cell(name)This def returns the name of a standard cell for a given platform. Currently, it only supports the sky130 platform. The naming scheme for sky130 is
${cell_prefix}${name}${cell_suffix}.Here
nameis an argument passed to thecell()def, andcell_prefixandcell_suffixare set in theparametersargument passed to thegenerate_verilog()function.For example, an inverter cell can be inserted using the syntax
${cell('inv')}. If the prefix issky130_fd_sc_hd__(sky130hd) and the suffix is_1, the cell will be replaced withsky130_fd_sc_hd__inv_1. The same statement will be replaced withsky130_fd_sc_hs__inv_1for the sky130hs platform.Use the constant
COMMON_PLATFORMS_PREFIX_MAPfor mapping a sky130 platform to its platform.
Constants
COMMON_PLATFORMS_PREFIX_MAPThis is a dictionary of common platforms (currently sky130) and their cell naming prefixes. See the
cell()def in thegenerate_verilog()function for more information on how to use it.
2. Simulation (common.simulation)
This module exports functions used to simulate SPICE testbenches with multiple parameters.
This module supports the use of Mako templating library to insert parameters into SPICE templates.
Functions
run_simulations(parameters: dict, platform: str, simulation_dir: str, template_path: str, runs_dir: str, sim_tool: str, num_concurrent_sims: int, netlist_path: str) -> intGenerates configurations of all combinations of the given
parametersand runs simulations for each case. The testbench SPICE file, configuration parameters, and the ouptut for each run are generated in{simulation_dir}/{runs_dir}.The testbench SPICE file given by
template_pathfollows the Mako templating syntax. Use the${parameter}syntax for inserting parameters in the file. The following parameters are automatically inserted during each run.run_number(int): The number/index of the run/configuration.sim_tool(str): Command for the simulation tool used.platform(str): The platform/PDK.template(str): Path to the SPICE testbench template.netlist_path(str): Absolute path to the SPICE netlist of the design to be simulated.
- Example SPICE template: (From the Temperature Sensor Generator)
.lib '${model_file}' ${model_corner} .include '${netlist_path}' .param temp_var = ${temp} .param vvdd = 1.8 .param sim_end = '800m/exp(0.04*temp_var)'
Each configuration is run/simulated in a directory in the
runs_dir. Each run directory contains the final SPICE testbench with the parameters inserted, aparameters.txtfile containing the values of each parameter, and the output log file.parametersis a dict with keys corresponding to the parameter’s name and the values of one of the following types.1. A constant value. The value of this parameter will be the same for every configuration/run.
{'param': 'value'}
2. Array of integer/float/string constants. Each of the values in the array will be swept.
{'param': [1, 2, 3, 8]} # OR { 'param': { 'values': [1, 2, 3, 8] } }
3. Increments. All values starting from
start(included) and ending atend(included if it isstart + n * step) will be swept with a step ofstep. The default value forstepis1.{'param': { 'start': 10, 'end': 50, 'step': 10 }} # param will take values 10, 20, 30, 40, 50
- Example parameters:
# Runs 10 total simulations # Sweeps through all temperatures from 10 to 100 (both included) with increments of 10. example1 = { 'temp': {'start': 10, 'end': 100, 'step': 10} } # Runs 9 total simulations # Sweeps through all the 3 input voltages as well as all the 3 temperatures example2 = { 'input_voltage': [1, 2, 3], 'temp': [20, 30, 40] } # Runs 4 total simulations # Duty cycle and aux_spice_path remain the same in all simulations # input_voltage is swept example3 = { 'duty_cycle': 10, 'aux_spice_path': 'auxcell.cdl', 'input_voltage': [1, 2, 3] }
See the generators’ Python files in
tools/for more examples.- Arguments:
parameters(dict): Dictionary of parameters. Explained above.platform(str): Platform/PDK. (eg:sky130hd`)simulation_dir(str): Path to the directory where the simulation source files are placed and the outputs will be generated. (Default:simulations)template_path(str): Path to the SPICE template file for the testbench. (Default:templates/template.sp)runs_dir(str): Path to a directory inside thesimulation_dirdirectory where the outputs for the simulations will be generated. (Default:runs)sim_tool(str): Command for the simulation tool.ngspice,xyce, andfinesimare supported. (Default:ngspice)num_concurrent_sims(int): The maximum number of concurrent simulations. (Default:4)netlist_path(str): Path to the SPICE netlist inside thesimulation_dirof the design to be simulated. (Default:netlist.sp)
Returns (int): The total number of simulations run.
- Overall example: (From the Temperature Sensor Generator)
run_simulations( parameters={ 'temp': {'start': tempStart, 'end': tempStop, 'step': tempStep}, 'model_file': model_file, 'model_corner': platformConfig['model_corner'], 'nominal_voltage': platformConfig['nominal_voltage'], 'design_name': designName }, platform="sky130hd", simulation_dir="simulations", template_path=os.path.join("templates", f"tempsenseInst_{simTool}.sp"), runs_dir=f"run/prePEX_inv{num_inv}_header{num_header}/", sim_tool=simTool, netlist_path=dstNetlist )
3. Generated File Check (common.check_gen_files.py)
This module is used to check for the presence of the required non-report files that each generator creates. It gets the module_name (str) from the .json file present in the generator top-level folder.
Functions
check_gen_files(json_filename: str, generator_is: dict, cryo_library: str) -> int:
Arguments:
json_filename (str): String containing the name of the .json filename for each generator
generator_is (dict): Dictionary containing key-value pairs that signify which generator’s flow results are being checked
cryo_library (str): String containing which cryo-gen library (sky130hs, sky130hd, sky130hvl) is being checked for
Uses:
work_dir (str): String containing the directory in which to check files
data (str): String containing data from the .json file
module_name (str): String containing the name of module that the check is being done for (eg. tempsenseInst_error)
extension_file_path (str): Contains the extensions of the files which each generator produces for the flows
Returns:
1: if all checks are successful
else: raises a ValueError: If any of the various checks go wrong (.csv file checks for temp-sense, flow generated files for all generators)
Specifics:
After checking for the existence of the work/ directory, the function does the following -
checks for the presence of files generated by the openroad flow with the following piece of code
if os.path.exists(extension_file_path): with open(extension_file_path) as f: for extension in f: extension = extension.strip() if (generator_is['sky130XX_cryo']) and (extension == ".spice" or extension == "_pex.spice" or extension.strip() == "_sim.spice"): file = "./flow/" + module_name + extension.strip() else: file = "".join([filename, extension]) if (os.path.exists(file) == 0): raise ValueError(file + " does not exist!") else: print("checking flow results with possibly stale list of extensions...") extensions = [".sdc", ".gds", ".def", ".spice", ".v", "_pex.spice"] for extension in extensions: extension = extension.strip() if (generator_is['sky130XX_cryo']) and (extension == ".spice" or extension == "_pex.spice"): file = "./flow/" + module_name + extension else: file = "".join([filename, extension]) if (os.path.exists(file) == 0): raise ValueError(file + " does not exist!")
checks for the presence of the .csv files generated by the header-inverter configuration power/error optimisation, which contain the best configuration and the sensor power/error outputs for run of the configuration optimisation with the following code snipper
if generator_is['sky130hd_temp']: for file in ("error_within_x.csv", "golden_error_opt.csv", "search_result.csv"): if os.path.exists(file) == 0: raise ValueError(file + " does not exist!")