diff --git a/docs/source/common-python-api.rst b/docs/source/common-python-api.rst new file mode 100644 index 000000000..3afab7188 --- /dev/null +++ b/docs/source/common-python-api.rst @@ -0,0 +1,90 @@ +Common Python API +================= + +.. contents:: Contents + :local: + +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()``. + +.. code-block:: python + + 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. + +.. code-block:: python + + 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 +^^^^^^^^^ +1. ``generate_verilog(parameters: dict, src_dir: str, out_dir: str) -> None`` + + Reads source Verilog files from ``src_dir`` and generates output Verilog files for synthesis in the OpenROAD flow. The source files are `Mako `_ templates. + + The ``parameters`` argument 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 the ``src_dir`` will be generated in a subdirectory in ``out_dir`` with 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: + .. code-block:: python + + 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 ``name`` is an argument passed to the ``cell()`` def, and ``cell_prefix`` and ``cell_suffix`` are set in the ``parameters`` argument passed to the ``generate_verilog()`` function. + + For example, an inverter cell can be inserted using the syntax ``${cell('inv')}``. If the prefix is ``sky130_fd_sc_hd__`` (sky130hd) and the suffix is ``_1``, the cell will be replaced with ``sky130_fd_sc_hd__inv_1``. The same statement will be replaced with ``sky130_fd_sc_hs__inv_1`` for the sky130hs platform. + + Use the constant ``COMMON_PLATFORMS_PREFIX_MAP`` for mapping a sky130 platform to its platform. + +Constants +^^^^^^^^^ +1. ``COMMON_PLATFORMS_PREFIX_MAP`` + + This is a dictionary of common platforms (currently sky130) and their cell naming prefixes. See the ``cell()`` def in the ``generate_verilog()`` function for more information on how to use it. \ No newline at end of file diff --git a/docs/source/flow-tempsense.rst b/docs/source/flow-tempsense.rst index a493ca2ee..f72e216cf 100644 --- a/docs/source/flow-tempsense.rst +++ b/docs/source/flow-tempsense.rst @@ -45,20 +45,23 @@ Verilog generation Running ``make sky130hd_temp`` (temp for "temperature sensor") executes the `temp-sense-gen.py `_ script from temp-sense-gen/tools/. This file takes the input specifications from `test.json `_ and outputs Verilog files containing the description of the circuit. .. note:: - temp-sense-gen.py calls other modules from temp-sense-gen/tools/ during execution. For example, `readparamgen.py `_ is in charge of reading test.json, checking for correct user input and choosing the correct circuit elements. + temp-sense-gen.py calls other modules from temp-sense-gen/tools/ and generators/common/ during execution. For example, `readparamgen.py `_ is in charge of reading test.json, checking for correct user input and choosing the correct circuit elements. -The generator starts from a Verilog template of the temperature sensor circuit, located in `temp-sense-gen/src/ `_. The ``.v`` template files have lines marked with ``@@``, which are replaced according to the specifications. +The generator starts from a Verilog template of the temperature sensor circuit, located in `temp-sense-gen/src/ `_. The ``.v`` templates follow `Mako `_ syntax. For example, ``${param}`` is replaced with the value of the parameter ``param``. -Example: `counter_generic.v line 31 `_ is replaced during Verilog generation. +See `Common Python API `_ for more information. + +Example: In `counter.v line 31 `_, ``${cell('buf')}`` with the buffer cell which is ``sky130_fd_sc_hd__buf_1`` for sky130hd. .. code-block:: verilog - :emphasize-lines: 3 + :emphasize-lines: 4 :linenos: :lineno-start: 29 assign done_sens = WAKE_pre && doneb; - assign done_ref = WAKE && doneb; - @@ @np Buf_DONE(.A(done_pre), .nbout(DONE)); + assign done_ref = WAKE && doneb; + + ${cell('buf')} Buf_DONE(.A(done_pre), .X(DONE)); always @ (*) begin case (done_pre) @@ -87,15 +90,15 @@ OpenROAD Flow takes a design from the temp-sense-gen/flow/design/ directory and └── design ├── sky130hd │ └── tempsense - │ ├── config.mk <-- + │ ├── config.mk <-- │ └── constraint.sdc └── src └── tempsense - ├── counter.v <-- - ├── TEMP_ANALOG_hv.nl.v <-- - ├── TEMP_ANALOG_lv.nl.v <-- - ├── TEMP_AUTO_def.v <-- - └── tempsenseInst_error.v <-- + ├── counter.v <-- + ├── TEMP_ANALOG_hv.v <-- + ├── TEMP_ANALOG_lv.v <-- + ├── TEMP_AUTO_def.v <-- + └── tempsenseInst.v <-- For more information on OpenROAD Flow, check their `docs `_. diff --git a/docs/source/index.rst b/docs/source/index.rst index 424cc05ba..c0dbe238e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -32,6 +32,7 @@ If you are willing to contribute, please visit the :doc:`developers-guide` secti getting-started generators + common-python-api tapeouts examples developers-guide diff --git a/openfasoc/generators/common/__init__.py b/openfasoc/generators/common/__init__.py new file mode 100644 index 000000000..ae57c6291 --- /dev/null +++ b/openfasoc/generators/common/__init__.py @@ -0,0 +1,10 @@ +"""A common module used in OpenFASOC generators. + +This module exports functions used in OpenFASOC generators. The following submodules and functions are exported. + +- `common.verilog_generation` + 1. `generate_verilog(parameters: dict, src_dir: str, out_dir: str) -> None`: Used to generate synthesizable Verilog files (for OpenROAD flow) from source Mako-based Verilog templates. + 2. `COMMON_PLATFORMS_PREFIX_MAP` (dict): This is a dictionary of common platforms (currently sky130) and their cell naming prefixes. + +See individual function documentation for more information on a particular function. +""" \ No newline at end of file diff --git a/openfasoc/generators/common/verilog_generation.py b/openfasoc/generators/common/verilog_generation.py new file mode 100644 index 000000000..90689c8c3 --- /dev/null +++ b/openfasoc/generators/common/verilog_generation.py @@ -0,0 +1,102 @@ +from mako.template import Template +from os import path, makedirs, listdir + +# TODO: Find a better way to import common used defs in the future. +__COMMON_MAKO_DEFS = ''' +<%def name="cell(name)">${cell_prefix}${name}${cell_suffix} +''' + +def _mako_defs_preprocessor(input: str) -> str: + """A Mako preprocessor that appens commonly used defs to the template. + + Mako templates have a preprocessor argument. See https://docs.makotemplates.org/en/latest/usage.html#mako.template.Template.params.preprocessor. + This preprocessor adds defs commonly used in Verilog files to the template. + TODO: Find a better way to import common used defs in the future. + """ + return __COMMON_MAKO_DEFS + input + +def _generate_file(input_path: str, output_path: str, parameters: dict) -> None: + """Generates a single output Verilog file from its Mako template. + + Arguments: + - `input_path` (str): Path to the input file (Mako template) with the extension. + - `output_path` (str): Path to the output file location with the extension. + - `parameters` (dict): Dictionary of all the parameters used in the Mako template. + """ + + # TODO: Find a better way to import common used defs in the future. + template = Template(filename=input_path, preprocessor=_mako_defs_preprocessor) + + out_file = open(output_path, "w") + out_file.write(template.render(**parameters)) + +def _generate_subdirectory(src_dir: str, out_dir: str, parameters: dict) -> None: + """Generates the output Verilog files of a single subdirectory of Mako templates. + + Reads Mako templates from a subdirectory (`src_dir`), generates the output files in the output directory (`out_dir`), and maintains the directory structure. i.e., templates from a subdirectory of the `src_dir` will be generated in a subdirectory in `out_dir` with the same name. + + This function recursively calls itself for subdirectories. + + Arguments: + - `src_dir` (str): Path to the source directory with Mako templates. + - `out_dir` (str): Path to the output directory. + - `parameters` (dict): Dictionary of all the parameters used in the Mako template. + """ + # generate the output directory if it doesn't exist + if not path.exists(out_dir): + makedirs(out_dir) + + for filename in listdir(src_dir): + input_filepath = path.join(src_dir, filename) + output_filepath = path.join(out_dir, filename) + + if path.isdir(input_filepath): + # if the path is a subdirectory, recursively call the function + _generate_subdirectory( + input_filepath, + output_filepath, + parameters + ) + else: + # if the path is a fine, generate the output + _generate_file( + input_filepath, + output_filepath, + parameters + ) + +def generate_verilog( + parameters: dict, + src_dir: str = "src", + out_dir: str = path.join("flow", "design", "src") +) -> None: + """Generates output Verilog files from source Mako templates. + + Reads source Verilog files from `src_dir` and generates output Verilog files for synthesis in the OpenROAD flow. + The source files are Mako templates. See https://makotemplates.org for syntax and documentation. + + This function maintains the source directory (`src_dir`) structure in the output directory (`out_dir`). i.e., source files from a subdirectory of the `src_dir` will be generated in a subdirectory in `out_dir` with the same name. + + Arguments: + - `parameters` (dict): Dictionary of all the parameters used in the Mako templates. See https://makotemplates.org for documentation. + - `src_dir` (str): Path to the directory with the source Verilog templates. + - `out_dir` (str): Path to the directory in which the output will be generated. + """ + _generate_subdirectory(src_dir, out_dir, parameters) + +# A dictionary of commonly used platforms and the prefix used in their cell naming +# Currently includes only sky130 platform prefixes +COMMON_PLATFORMS_PREFIX_MAP = { + "sky130hd": "sky130_fd_sc_hd__", + "sky130hs": "sky130_fd_sc_hs__", + "sky130hvl": "sky130_fd_sc_hvl__", + "sky130osu12Ths": "sky130_osu_sc_12T_hs__", + "sky130osu12Tms": "sky130_osu_sc_12T_ms__", + "sky130osu12Tls": "sky130_osu_sc_12T_ls__", + "sky130osu15Ths": "sky130_osu_sc_15T_hs__", + "sky130osu15Tms": "sky130_osu_sc_15T_ms__", + "sky130osu15Tls": "sky130_osu_sc_15T_ls__", + "sky130osu18Ths": "sky130_osu_sc_18T_hs__", + "sky130osu18Tms": "sky130_osu_sc_18T_ms__", + "sky130osu18Tls": "sky130_osu_sc_18T_ls__" +} \ No newline at end of file diff --git a/openfasoc/generators/temp-sense-gen/Makefile b/openfasoc/generators/temp-sense-gen/Makefile index 4b220b542..1c2cf3806 100644 --- a/openfasoc/generators/temp-sense-gen/Makefile +++ b/openfasoc/generators/temp-sense-gen/Makefile @@ -69,6 +69,7 @@ clean: rm -f error_within_x.csv golden_error_opt.csv search_result.csv rm -rf work rm -rf tools/*.pyc tools/__pycache__/ + rm -rf flow/design/src/tempsense cd flow && make clean_all cd simulations && rm -rf run diff --git a/openfasoc/generators/temp-sense-gen/flow/design/src/.gitignore b/openfasoc/generators/temp-sense-gen/flow/design/src/.gitignore new file mode 100644 index 000000000..6be86d734 --- /dev/null +++ b/openfasoc/generators/temp-sense-gen/flow/design/src/.gitignore @@ -0,0 +1 @@ +tempsense/ \ No newline at end of file diff --git a/openfasoc/generators/temp-sense-gen/flow/design/src/tempsense/.gitignore b/openfasoc/generators/temp-sense-gen/flow/design/src/tempsense/.gitignore deleted file mode 100644 index ca45c5c8d..000000000 --- a/openfasoc/generators/temp-sense-gen/flow/design/src/tempsense/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# These files are copied to flow/design/src/ by the temp-sense-gen.py tool: -counter.v -TEMP_ANALOG_hv.nl.v -TEMP_ANALOG_lv.nl.v -TEMP_AUTO_def.v -tempsenseInst_error.v diff --git a/openfasoc/generators/temp-sense-gen/src/.gitignore b/openfasoc/generators/temp-sense-gen/src/.gitignore deleted file mode 100644 index 61d41dcc3..000000000 --- a/openfasoc/generators/temp-sense-gen/src/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# These files are generated by the temp-sense-gen.py tool: -counter.v -TEMP_ANALOG_hv.nl.v -TEMP_ANALOG_lv.nl.v diff --git a/openfasoc/generators/temp-sense-gen/src/TEMP_ANALOG_hv.v b/openfasoc/generators/temp-sense-gen/src/TEMP_ANALOG_hv.v index a5875db55..7275003ba 100644 --- a/openfasoc/generators/temp-sense-gen/src/TEMP_ANALOG_hv.v +++ b/openfasoc/generators/temp-sense-gen/src/TEMP_ANALOG_hv.v @@ -26,8 +26,11 @@ counter async_counter_0( ); (* keep *) -@@ @nf a_header_@nh(.VIN(VIN)); -SLC -@@ @no a_buffer_0 (.A(lc_0), .nbout(lc_out)); +% for i in range(nhead): +${header_cell} a_header_${i}(.VIN(VIN)); +% endfor +// SLC +${slc_cell} a_lc_0(.IN(out), .INB(outb), .VOUT(lc_0)); +${cell('buf')} a_buffer_0 (.A(lc_0), .X(lc_out)); endmodule diff --git a/openfasoc/generators/temp-sense-gen/src/TEMP_ANALOG_lv.v b/openfasoc/generators/temp-sense-gen/src/TEMP_ANALOG_lv.v index 5948b250a..1a3d55def 100644 --- a/openfasoc/generators/temp-sense-gen/src/TEMP_ANALOG_lv.v +++ b/openfasoc/generators/temp-sense-gen/src/TEMP_ANALOG_lv.v @@ -3,16 +3,25 @@ module TEMP_ANALOG_lv (EN, OUT, OUTB); // inout in_vin; output OUT, OUTB; wire n; -@@ wire n@nn; + +% for i in range(ninv + 1): +wire n${i + 1}; +% endfor + wire nx1, nx2, nx3, nb1, nb2; -@@ @na a_nand_0 ( .A(EN), .B(n@n0), .Y(n1)); -@@ @nb a_inv_@ni ( .A(n@n1), .Y(n@n2)); -@@ @ng a_inv_m1 ( .A(n@n3), .Y(nx1)); -@@ @nk a_inv_m2 ( .A(n@n4), .Y(nx2)); -@@ @nm a_inv_m3 ( .A(nx2), .Y(nx3)); -@@ @np a_buf_3 ( .A(nx3), .nbout(nb2)); -@@ @nc a_buf_0 ( .A(nx1), .nbout(nb1)); -@@ @nd a_buf_1 ( .A(nb1), .nbout(OUT)); -@@ @ne a_buf_2 ( .A(nb2), .nbout(OUTB)); + +${cell('nand2')} a_nand_0 ( .A(EN), .B(n${ninv + 1}), .Y(n1)); + +% for i in range(ninv): +${cell('inv')} a_inv_${i} ( .A(n${i + 1}), .Y(n${i + 2})); +% endfor + +${cell('inv')} a_inv_m1 ( .A(n${ninv + 1}), .Y(nx1)); +${cell('inv')} a_inv_m2 ( .A(n${ninv + 1}), .Y(nx2)); +${cell('inv')} a_inv_m3 ( .A(nx2), .Y(nx3)); +${cell('buf')} a_buf_3 ( .A(nx3), .X(nb2)); +${cell('buf')} a_buf_0 ( .A(nx1), .X(nb1)); +${cell('buf')} a_buf_1 ( .A(nb1), .X(OUT)); +${cell('buf')} a_buf_2 ( .A(nb2), .X(OUTB)); endmodule diff --git a/openfasoc/generators/temp-sense-gen/src/counter_generic.v b/openfasoc/generators/temp-sense-gen/src/counter.v similarity index 97% rename from openfasoc/generators/temp-sense-gen/src/counter_generic.v rename to openfasoc/generators/temp-sense-gen/src/counter.v index 2c0eef3e3..e119f06c9 100644 --- a/openfasoc/generators/temp-sense-gen/src/counter_generic.v +++ b/openfasoc/generators/temp-sense-gen/src/counter.v @@ -28,7 +28,8 @@ module counter# assign done_ref = WAKE && doneb; // BUFH_X4M_A9TR Buf_DONE(.A(done_pre), .Y(DONE)); // BUF_X0P4N_A10P5PP84TR_C14 Buf_DONE(.A(done_pre), .Y(DONE)); -@@ @np Buf_DONE(.A(done_pre), .nbout(DONE)); + + ${cell('buf')} Buf_DONE(.A(done_pre), .X(DONE)); //assign RESET_CLK_REF = ~q1; always @ (*) begin diff --git a/openfasoc/generators/temp-sense-gen/src/tempsenseInst.v b/openfasoc/generators/temp-sense-gen/src/tempsenseInst.v index d17e54ed5..0ae66e307 100644 --- a/openfasoc/generators/temp-sense-gen/src/tempsenseInst.v +++ b/openfasoc/generators/temp-sense-gen/src/tempsenseInst.v @@ -1,4 +1,4 @@ -module tempsenseInst_error +module ${design_name} ( input CLK_REF, input RESET_COUNTERn, diff --git a/openfasoc/generators/temp-sense-gen/tools/temp-sense-gen.py b/openfasoc/generators/temp-sense-gen/tools/temp-sense-gen.py index 357fba902..ab31b61e0 100755 --- a/openfasoc/generators/temp-sense-gen/tools/temp-sense-gen.py +++ b/openfasoc/generators/temp-sense-gen/tools/temp-sense-gen.py @@ -2,16 +2,18 @@ import json import os -import re import shutil import subprocess as sp import sys -import time +import re -import TEMP_netlist from readparamgen import args, check_search_done, designName from simulation import generate_runs +# TODO: Find a better way to import modules from parent directory +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) +from common.verilog_generation import generate_verilog, COMMON_PLATFORMS_PREFIX_MAP + genDir = os.path.join(os.path.dirname(os.path.relpath(__file__)), "../") srcDir = genDir + "src/" flowDir = genDir + "flow/" @@ -98,46 +100,38 @@ print("# Verilog Generation") print("#----------------------------------------------------------------------") +# The directory in which the output Verilog is generated +verilog_gen_dir=os.path.join('flow', 'design', 'src', 'tempsense') +generate_verilog( + parameters={ + "design_name": designName, + "cell_prefix": COMMON_PLATFORMS_PREFIX_MAP[args.platform], + "cell_suffix": "_1", + "header_cell": "HEADER" if args.platform == "sky130hd" else "HEADER_hs", + "slc_cell": "SLC" if args.platform == "sky130hd" else "SLC_hs", + "ninv": ninv, + "nhead": nhead + }, + out_dir=verilog_gen_dir +) -if args.platform == "sky130hd": - aux1 = "sky130_fd_sc_hd__nand2_1" - aux2 = "sky130_fd_sc_hd__inv_1" - aux3 = "sky130_fd_sc_hd__buf_1" - aux4 = "sky130_fd_sc_hd__buf_1" - aux5 = "HEADER" - aux6 = "SLC" -elif args.platform == "sky130hs": - aux1 = "sky130_fd_sc_hs__nand2_1" - aux2 = "sky130_fd_sc_hs__inv_1" - aux3 = "sky130_fd_sc_hs__buf_1" - aux4 = "sky130_fd_sc_hs__buf_1" - aux5 = "HEADER_hs" - aux6 = "SLC_hs" - -ninv = ninv + 1 -TEMP_netlist.gen_temp_netlist(ninv, nhead, aux1, aux2, aux3, aux4, aux5, srcDir) - -with open(srcDir + "TEMP_ANALOG_hv.nl.v", "r") as rf: +with open(os.path.join(verilog_gen_dir, "TEMP_ANALOG_hv.v"), "r") as rf: filedata = rf.read() header_list = re.findall("HEADER\s+(\w+)\(", filedata) + with open(genDir + "blocks/sky130hd/tempsenseInst_custom_net.txt", "w") as wf: wf.write("r_VIN\n") for header_cell in header_list: wf.write("temp_analog_1." + header_cell + " VIN\n") -with open(srcDir + "TEMP_ANALOG_lv.nl.v", "r") as rf: +with open(os.path.join(verilog_gen_dir, "TEMP_ANALOG_lv.v"), "r") as rf: filedata = rf.read() lv_list = re.findall("\nsky130_fd_sc\w*\s+(\w+)\s+\(", filedata) + with open(genDir + "blocks/sky130hd/tempsenseInst_domain_insts.txt", "w") as wf: for lv_cell in lv_list: wf.write("temp_analog_0." + lv_cell + "\n") -with open(srcDir + "tempsenseInst.v", "r") as rf: - filedata = rf.read() - filedata = re.sub("module\s*(\w+)\s*\n", "module " + designName + "\n", filedata) -with open(srcDir + "tempsenseInst.v", "w") as wf: - wf.write(filedata) - with open(flowDir + "design/sky130hd/tempsense/config.mk", "r") as rf: filedata = rf.read() filedata = re.sub( @@ -146,20 +140,6 @@ with open(flowDir + "design/sky130hd/tempsense/config.mk", "w") as wf: wf.write(filedata) -shutil.copyfile( - srcDir + "TEMP_ANALOG_lv.nl.v", flowDir + "design/src/tempsense/TEMP_ANALOG_lv.nl.v" -) -shutil.copyfile( - srcDir + "TEMP_ANALOG_hv.nl.v", flowDir + "design/src/tempsense/TEMP_ANALOG_hv.nl.v" -) -shutil.copyfile( - srcDir + "TEMP_AUTO_def.v", flowDir + "design/src/tempsense/TEMP_AUTO_def.v" -) -shutil.copyfile( - srcDir + "tempsenseInst.v", flowDir + "design/src/tempsense/" + designName + ".v" -) -shutil.copyfile(srcDir + "counter.v", flowDir + "design/src/tempsense/counter" + ".v") - print("#----------------------------------------------------------------------") print("# Verilog Generated") print("#----------------------------------------------------------------------") @@ -262,7 +242,7 @@ print("# Generating spice netlists for the macro") print("#----------------------------------------------------------------------") -stage_var = [int(ninv) - 1] +stage_var = [int(ninv)] header_var = [int(nhead)] # make a temp list, TODO: get from JSON config diff --git a/requirements.txt b/requirements.txt index cadf3b47a..87d50d37e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ nbsphinx cairosvg scipy ltspice +mako \ No newline at end of file diff --git a/tests/common_api/test-verilog/expected-sky130hd-6inv/TEMP_ANALOG_lv.v b/tests/common_api/test-verilog/expected-sky130hd-6inv/TEMP_ANALOG_lv.v new file mode 100644 index 000000000..e3f8ce9b7 --- /dev/null +++ b/tests/common_api/test-verilog/expected-sky130hd-6inv/TEMP_ANALOG_lv.v @@ -0,0 +1,36 @@ + + +module TEMP_ANALOG_lv (EN, OUT, OUTB); + input EN; +// inout in_vin; + output OUT, OUTB; + wire n; + +wire n1; +wire n2; +wire n3; +wire n4; +wire n5; +wire n6; +wire n7; + +wire nx1, nx2, nx3, nb1, nb2; + +sky130_fd_sc_hd__nand2_1 a_nand_0 ( .A(EN), .B(n7), .Y(n1)); + +sky130_fd_sc_hd__inv_1 a_inv_0 ( .A(n1), .Y(n2)); +sky130_fd_sc_hd__inv_1 a_inv_1 ( .A(n2), .Y(n3)); +sky130_fd_sc_hd__inv_1 a_inv_2 ( .A(n3), .Y(n4)); +sky130_fd_sc_hd__inv_1 a_inv_3 ( .A(n4), .Y(n5)); +sky130_fd_sc_hd__inv_1 a_inv_4 ( .A(n5), .Y(n6)); +sky130_fd_sc_hd__inv_1 a_inv_5 ( .A(n6), .Y(n7)); + +sky130_fd_sc_hd__inv_1 a_inv_m1 ( .A(n7), .Y(nx1)); +sky130_fd_sc_hd__inv_1 a_inv_m2 ( .A(n7), .Y(nx2)); +sky130_fd_sc_hd__inv_1 a_inv_m3 ( .A(nx2), .Y(nx3)); +sky130_fd_sc_hd__buf_1 a_buf_3 ( .A(nx3), .X(nb2)); +sky130_fd_sc_hd__buf_1 a_buf_0 ( .A(nx1), .X(nb1)); +sky130_fd_sc_hd__buf_1 a_buf_1 ( .A(nb1), .X(OUT)); +sky130_fd_sc_hd__buf_1 a_buf_2 ( .A(nb2), .X(OUTB)); + +endmodule diff --git a/tests/common_api/test-verilog/expected-sky130hd-6inv/subdirectory/TEMP_ANALOG_hv.v b/tests/common_api/test-verilog/expected-sky130hd-6inv/subdirectory/TEMP_ANALOG_hv.v new file mode 100644 index 000000000..a2eaf5c55 --- /dev/null +++ b/tests/common_api/test-verilog/expected-sky130hd-6inv/subdirectory/TEMP_ANALOG_hv.v @@ -0,0 +1,38 @@ + + +module TEMP_ANALOG_hv +( +input CLK_REF, +input RESET_COUNTERn, +input [3:0] SEL_CONV_TIME, +input out, outb, + +output [23:0] DOUT, +output DONE, +output lc_out, +inout VIN +); + +wire lc_0; +// reg iso; +// assign iso = 0 ; +counter async_counter_0( + // Input + .CLK_SENS (lc_out), + .CLK_REF (CLK_REF), + .RESET_COUNTERn (RESET_COUNTERn), + .SEL_CONV_TIME (SEL_CONV_TIME), + // Output + .DOUT (DOUT), + .DONE (DONE) +); + +(* keep *) +HEADER a_header_0(.VIN(VIN)); +HEADER a_header_1(.VIN(VIN)); +HEADER a_header_2(.VIN(VIN)); +// SLC +SLC a_lc_0(.IN(out), .INB(outb), .VOUT(lc_0)); +sky130_fd_sc_hd__buf_1 a_buffer_0 (.A(lc_0), .X(lc_out)); + +endmodule diff --git a/tests/common_api/test-verilog/expected-sky130hd-8inv/TEMP_ANALOG_lv.v b/tests/common_api/test-verilog/expected-sky130hd-8inv/TEMP_ANALOG_lv.v new file mode 100644 index 000000000..6290932e3 --- /dev/null +++ b/tests/common_api/test-verilog/expected-sky130hd-8inv/TEMP_ANALOG_lv.v @@ -0,0 +1,40 @@ + + +module TEMP_ANALOG_lv (EN, OUT, OUTB); + input EN; +// inout in_vin; + output OUT, OUTB; + wire n; + +wire n1; +wire n2; +wire n3; +wire n4; +wire n5; +wire n6; +wire n7; +wire n8; +wire n9; + +wire nx1, nx2, nx3, nb1, nb2; + +sky130_fd_sc_hd__nand2_1 a_nand_0 ( .A(EN), .B(n9), .Y(n1)); + +sky130_fd_sc_hd__inv_1 a_inv_0 ( .A(n1), .Y(n2)); +sky130_fd_sc_hd__inv_1 a_inv_1 ( .A(n2), .Y(n3)); +sky130_fd_sc_hd__inv_1 a_inv_2 ( .A(n3), .Y(n4)); +sky130_fd_sc_hd__inv_1 a_inv_3 ( .A(n4), .Y(n5)); +sky130_fd_sc_hd__inv_1 a_inv_4 ( .A(n5), .Y(n6)); +sky130_fd_sc_hd__inv_1 a_inv_5 ( .A(n6), .Y(n7)); +sky130_fd_sc_hd__inv_1 a_inv_6 ( .A(n7), .Y(n8)); +sky130_fd_sc_hd__inv_1 a_inv_7 ( .A(n8), .Y(n9)); + +sky130_fd_sc_hd__inv_1 a_inv_m1 ( .A(n9), .Y(nx1)); +sky130_fd_sc_hd__inv_1 a_inv_m2 ( .A(n9), .Y(nx2)); +sky130_fd_sc_hd__inv_1 a_inv_m3 ( .A(nx2), .Y(nx3)); +sky130_fd_sc_hd__buf_1 a_buf_3 ( .A(nx3), .X(nb2)); +sky130_fd_sc_hd__buf_1 a_buf_0 ( .A(nx1), .X(nb1)); +sky130_fd_sc_hd__buf_1 a_buf_1 ( .A(nb1), .X(OUT)); +sky130_fd_sc_hd__buf_1 a_buf_2 ( .A(nb2), .X(OUTB)); + +endmodule diff --git a/tests/common_api/test-verilog/expected-sky130hd-8inv/subdirectory/TEMP_ANALOG_hv.v b/tests/common_api/test-verilog/expected-sky130hd-8inv/subdirectory/TEMP_ANALOG_hv.v new file mode 100644 index 000000000..a2eaf5c55 --- /dev/null +++ b/tests/common_api/test-verilog/expected-sky130hd-8inv/subdirectory/TEMP_ANALOG_hv.v @@ -0,0 +1,38 @@ + + +module TEMP_ANALOG_hv +( +input CLK_REF, +input RESET_COUNTERn, +input [3:0] SEL_CONV_TIME, +input out, outb, + +output [23:0] DOUT, +output DONE, +output lc_out, +inout VIN +); + +wire lc_0; +// reg iso; +// assign iso = 0 ; +counter async_counter_0( + // Input + .CLK_SENS (lc_out), + .CLK_REF (CLK_REF), + .RESET_COUNTERn (RESET_COUNTERn), + .SEL_CONV_TIME (SEL_CONV_TIME), + // Output + .DOUT (DOUT), + .DONE (DONE) +); + +(* keep *) +HEADER a_header_0(.VIN(VIN)); +HEADER a_header_1(.VIN(VIN)); +HEADER a_header_2(.VIN(VIN)); +// SLC +SLC a_lc_0(.IN(out), .INB(outb), .VOUT(lc_0)); +sky130_fd_sc_hd__buf_1 a_buffer_0 (.A(lc_0), .X(lc_out)); + +endmodule diff --git a/tests/common_api/test-verilog/src/TEMP_ANALOG_lv.v b/tests/common_api/test-verilog/src/TEMP_ANALOG_lv.v new file mode 100644 index 000000000..1a3d55def --- /dev/null +++ b/tests/common_api/test-verilog/src/TEMP_ANALOG_lv.v @@ -0,0 +1,27 @@ +module TEMP_ANALOG_lv (EN, OUT, OUTB); + input EN; +// inout in_vin; + output OUT, OUTB; + wire n; + +% for i in range(ninv + 1): +wire n${i + 1}; +% endfor + +wire nx1, nx2, nx3, nb1, nb2; + +${cell('nand2')} a_nand_0 ( .A(EN), .B(n${ninv + 1}), .Y(n1)); + +% for i in range(ninv): +${cell('inv')} a_inv_${i} ( .A(n${i + 1}), .Y(n${i + 2})); +% endfor + +${cell('inv')} a_inv_m1 ( .A(n${ninv + 1}), .Y(nx1)); +${cell('inv')} a_inv_m2 ( .A(n${ninv + 1}), .Y(nx2)); +${cell('inv')} a_inv_m3 ( .A(nx2), .Y(nx3)); +${cell('buf')} a_buf_3 ( .A(nx3), .X(nb2)); +${cell('buf')} a_buf_0 ( .A(nx1), .X(nb1)); +${cell('buf')} a_buf_1 ( .A(nb1), .X(OUT)); +${cell('buf')} a_buf_2 ( .A(nb2), .X(OUTB)); + +endmodule diff --git a/tests/common_api/test-verilog/src/subdirectory/TEMP_ANALOG_hv.v b/tests/common_api/test-verilog/src/subdirectory/TEMP_ANALOG_hv.v new file mode 100644 index 000000000..7275003ba --- /dev/null +++ b/tests/common_api/test-verilog/src/subdirectory/TEMP_ANALOG_hv.v @@ -0,0 +1,36 @@ +module TEMP_ANALOG_hv +( +input CLK_REF, +input RESET_COUNTERn, +input [3:0] SEL_CONV_TIME, +input out, outb, + +output [23:0] DOUT, +output DONE, +output lc_out, +inout VIN +); + +wire lc_0; +// reg iso; +// assign iso = 0 ; +counter async_counter_0( + // Input + .CLK_SENS (lc_out), + .CLK_REF (CLK_REF), + .RESET_COUNTERn (RESET_COUNTERn), + .SEL_CONV_TIME (SEL_CONV_TIME), + // Output + .DOUT (DOUT), + .DONE (DONE) +); + +(* keep *) +% for i in range(nhead): +${header_cell} a_header_${i}(.VIN(VIN)); +% endfor +// SLC +${slc_cell} a_lc_0(.IN(out), .INB(outb), .VOUT(lc_0)); +${cell('buf')} a_buffer_0 (.A(lc_0), .X(lc_out)); + +endmodule diff --git a/tests/common_api/test_verilog_generation.py b/tests/common_api/test_verilog_generation.py new file mode 100644 index 000000000..efcf29777 --- /dev/null +++ b/tests/common_api/test_verilog_generation.py @@ -0,0 +1,81 @@ +import os +import sys +from shutil import rmtree + +# Add the common API to the path +# TODO: Find a better way to import the modules +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'openfasoc', 'generators', 'common')) + +import verilog_generation + +TEST_VERILOG_DIR = os.path.join(os.path.dirname(__file__), 'test-verilog') +SRC_VERILOG_DIR = os.path.join(TEST_VERILOG_DIR, 'src') +TEST_FILENAME = 'TEMP_ANALOG_lv.v' +TEST_SUBDIR_FILENAME = 'TEMP_ANALOG_hv.v' + +def test_different_params(tmp_path): + PARAMETERS_1={ + "cell_prefix": "sky130_fd_sc_hd__", + "cell_suffix": "_1", + "ninv": 6 + } + + PARAMETERS_2={ + "cell_prefix": "sky130_fd_sc_hd__", + "cell_suffix": "_1", + "ninv": 8 + } + + input_path = os.path.join(SRC_VERILOG_DIR, TEST_FILENAME) + + output_path_1 = os.path.join(tmp_path, TEST_FILENAME + '.1') + output_path_2 = os.path.join(tmp_path, TEST_FILENAME + '.2') + + expected_file_1 = os.path.join(TEST_VERILOG_DIR, 'expected-sky130hd-6inv', TEST_FILENAME) + expected_file_2 = os.path.join(TEST_VERILOG_DIR, 'expected-sky130hd-8inv', TEST_FILENAME) + + verilog_generation._generate_file(input_path, output_path_1, PARAMETERS_1) + verilog_generation._generate_file(input_path, output_path_2, PARAMETERS_2) + + generated_verilog_1 = open(output_path_1).read() + generated_verilog_2 = open(output_path_2).read() + + expected_verilog_1 = open(expected_file_1).read() + expected_verilog_2 = open(expected_file_2).read() + + assert generated_verilog_1 == expected_verilog_1, "Generated Verilog does not match the expected Verilog (6 inverters)." + assert generated_verilog_2 == expected_verilog_2, "Generated Verilog does not match the expected Verilog (8 inverters)." + +def test_directory_structure(tmp_path): + PARAMETERS={ + "cell_prefix": "sky130_fd_sc_hd__", + "cell_suffix": "_1", + "header_cell": "HEADER", + "slc_cell": "SLC", + "ninv": 6, + "nhead": 3 + } + + expected_file_1 = os.path.join(TEST_VERILOG_DIR, 'expected-sky130hd-6inv', TEST_FILENAME) + expected_file_2 = os.path.join(TEST_VERILOG_DIR, 'expected-sky130hd-6inv', 'subdirectory', TEST_SUBDIR_FILENAME) + + output_path_1 = os.path.join(tmp_path, TEST_FILENAME) + output_subdir = os.path.join(tmp_path, 'subdirectory') + output_path_2 = os.path.join(output_subdir, TEST_SUBDIR_FILENAME) + + verilog_generation.generate_verilog(PARAMETERS, SRC_VERILOG_DIR, tmp_path) + + # Check if all the expected files and directories exist + assert os.path.exists(output_path_1), "Generated Verilog file does not exist." + assert os.path.exists(output_subdir), "Subdirectory does not exist in generated Verilog." + assert os.path.exists(output_path_2), "Files from subdirectory do not exist in generated Verilog." + + # Check if all the expected files are correctly generated + generated_verilog_1 = open(output_path_1).read() + generated_verilog_2 = open(output_path_2).read() + + expected_verilog_1 = open(expected_file_1).read() + expected_verilog_2 = open(expected_file_2).read() + + assert generated_verilog_1 == expected_verilog_1, "Generated Verilog does not match the expected Verilog for first file." + assert generated_verilog_2 == expected_verilog_2, "Generated Verilog does not match the expected Verilog for the subdirectory file."