| #!/usr/bin/env python3 |
| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| r"""Generates boilerplate code for the FPV testbench setup. |
| """ |
| import argparse |
| import sys |
| from io import StringIO |
| from pathlib import Path |
| |
| from mako.template import Template |
| |
| import fpvgen.sv_parse as sv_parse |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| prog="fpvgen", |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| description="""\ |
| Boilerplate code generator for FPV testbenches. Can be used for |
| comportable or non-comportable IPs. |
| |
| The generator creates the FuseSoC core file and two additional |
| subfolders 'tb' and 'vip' in the output directory. It will place stubs |
| for the testbench and bind files into the 'tb' subfolder, and a stub for |
| the FPV assertions into the 'vip' (verification IP) subfolder. |
| |
| The generator needs the path to the top-level module of the IP to be |
| tested. E.g., suppose we would like to generate an FPV testbench for a |
| FIFO primitive located at 'hw/ip/prim/rtl/prim_fifo_sync.sv' we can |
| invoke the generator as follows: |
| |
| util/fpvgen.py hw/ip/prim/rtl/prim_fifo_sync.sv |
| |
| By default, the output directory is assumed to be '../fpv' with respect |
| to the toplevel module, but this can be overriden using the -eo switch. |
| |
| Further if the IP is comportable, this can be indicated using the -c |
| switch, which causes the generator to add a bind statement for the CSR |
| FPV assertions in the testbench.""", |
| add_help=True) |
| parser.add_argument( |
| 'file', |
| type=Path, |
| help="""Relative path to the SystemVerilog file of the module for which |
| the code shall be generated. This can be a primitive or a comportable IP |
| (for which the -c switch should be set).""" |
| ) |
| |
| parser.add_argument( |
| '-o', |
| '--outdir', |
| type=Path, |
| default="", |
| help="""Path where to place the testbench code. This is defaults to |
| '../fpv' w.r.t. to the module path. For instance, if the module path is |
| 'hw/ip/mymod/rtl/mymod.sv', the FPV testbench would be generated under |
| hw/ip/mymod/fpv. """ |
| ) |
| parser.add_argument( |
| '-c', |
| '--is_cip', |
| action="store_true", |
| default=False, |
| help="""Indicates whether this is a comportable IP. If yes, FPV |
| assertions for the TL-UL interface and CSRs are automatically bound in |
| the testbench. Note however that these CSR assertions need to be |
| generated separately using the regtool automation.""" |
| ) |
| |
| args = parser.parse_args() |
| |
| mod_path = args.file |
| if not mod_path.is_file() or mod_path.suffix != ".sv": |
| print("Error: %s is not a module or does not exist" % str(mod_path)) |
| return 1 |
| |
| if not args.outdir: |
| # the default output path is ../fpv with |
| # respect to the module location |
| parentpath = mod_path.absolute().parent.parent |
| outpath = parentpath.joinpath("fpv") |
| else: |
| outpath = args.outdir |
| |
| print("Output path is: %s" % outpath) |
| |
| dut = sv_parse.parse_file(mod_path) |
| dut.is_cip = args.is_cip |
| |
| # always include the prims |
| dut.deps += ["lowrisc:prim:all"] |
| |
| if args.is_cip: |
| # for TL-UL assertions |
| dut.deps += ["lowrisc:ip:tlul"] |
| # in this case the parent directory is |
| # likely the correct basename of the IP |
| dut.deps += ["lowrisc:ip:" + parentpath.stem] |
| |
| # define template files to iterate over |
| template_files = [(Path(__file__).parent.joinpath("fpvgen/tb.sv.tpl"), \ |
| outpath.joinpath("tb").joinpath(mod_path.stem + "_tb.sv")), \ |
| (Path(__file__).parent.joinpath("fpvgen/bind_fpv.sv.tpl"), \ |
| outpath.joinpath("tb").joinpath(mod_path.stem + "_bind_fpv.sv")), \ |
| (Path(__file__).parent.joinpath("fpvgen/assert_fpv.sv.tpl"), \ |
| outpath.joinpath("vip").joinpath(mod_path.stem + "_assert_fpv.sv")), \ |
| (Path(__file__).parent.joinpath("fpvgen/fusesoc.core.tpl"), \ |
| outpath.joinpath(mod_path.stem + "_fpv.core"))] |
| |
| for (tpl_file, out_file) in template_files: |
| print("Generating %s" % str(out_file)) |
| out_file.parent.mkdir(parents=True, exist_ok=True) |
| tpl = Template(tpl_file.read_text()) |
| out_file.write_text(tpl.render(dut=dut)) |
| |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| main() |