Start of public OpenTitan development history

Code contributors:
Alex Bradbury <asb@lowrisc.org>
Cindy Chen <chencindy@google.com>
Eunchan Kim <eunchan@google.com>
Gaurang Chitroda <gaurangg@google.com>
Mark Hayter <mark.hayter@gmail.com>
Michael Schaffner <msf@google.com>
Miguel Osorio <miguelosorio@google.com>
Nils Graf <nilsg@google.com>
Philipp Wagner <phw@lowrisc.org>
Pirmin Vogel <vogelpi@lowrisc.org>
Ram Babu Penugonda <rampenugonda@google.com>
Scott Johnson <scottdj@google.com>
Shail Kushwah <kushwahs@google.com>
Srikrishna Iyer <sriyer@google.com>
Steve Nelson <Steve.Nelson@wdc.com>
Tao Liu <taliu@google.com>
Timothy Chen <timothytim@google.com>
Tobias Wölfel <tobias.woelfel@mailbox.org>
Weicai Yang <weicai@google.com>
diff --git a/util/topgen.py b/util/topgen.py
new file mode 100755
index 0000000..434fc0c
--- /dev/null
+++ b/util/topgen.py
@@ -0,0 +1,369 @@
+#!/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"""Top Module Generator
+"""
+import argparse
+import logging as log
+import sys
+from io import StringIO
+from pathlib import Path
+
+import hjson
+from mako.template import Template
+
+import tlgen
+from reggen import gen_rtl, gen_dv, validate
+from topgen import get_hjsonobj_xbars, merge_top, search_ips, validate_top
+
+# Filter from IP list but adding generated hjson
+filter_list = ['rv_plic', 'alert_h']
+
+# Common header for generated files
+genhdr = '''// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// ------------------- W A R N I N G: A U T O - G E N E R A T E D   C O D E !! -------------------//
+// PLEASE DO NOT HAND-EDIT THIS FILE. IT HAS BEEN AUTO-GENERATED WITH THE FOLLOWING COMMAND:
+'''
+
+def generate_rtl(top, tpl_filename):
+    top_rtl_tpl = Template(filename=tpl_filename)
+
+    out_rtl = top_rtl_tpl.render(top=top)
+    return out_rtl
+
+
+def generate_xbars(top, out_path):
+    for obj in top["xbar"]:
+        xbar = tlgen.validate(obj)
+
+        if not tlgen.elaborate(xbar):
+            log.error("Elaboration failed." + repr(xbar))
+
+        # Add clocks to the top configuration
+        obj["clocks"] = xbar.clocks
+        out_rtl, out_pkg, out_dv = tlgen.generate(xbar)
+
+        rtl_path = out_path / 'rtl'
+        rtl_path.mkdir(parents=True, exist_ok=True)
+        dv_path = out_path / 'dv'
+        dv_path.mkdir(parents=True, exist_ok=True)
+
+        rtl_filename = "xbar_%s.sv" % (xbar.name)
+        rtl_filepath = rtl_path / rtl_filename
+        with rtl_filepath.open(mode='w', encoding='UTF-8') as fout:
+            fout.write(out_rtl)
+
+        pkg_filename = "tl_%s_pkg.sv" % (xbar.name)
+        pkg_filepath = rtl_path / pkg_filename
+        with pkg_filepath.open(mode='w', encoding='UTF-8') as fout:
+            fout.write(out_pkg)
+
+        dv_filename = "xbar_%s_tb.sv" % (xbar.name)
+        dv_filepath = dv_path / dv_filename
+        with dv_filepath.open(mode='w', encoding='UTF-8') as fout:
+            fout.write(out_dv)
+
+
+def generate_plic(top, out_path):
+    # Count number of interrupts
+    src = sum([x["width"] if "width" in x else 1 for x in top["interrupt"]])
+
+    # Target and priority: Currently fixed
+    target = int(top["num_cores"], 0) if "num_cores" in top else 1
+    prio = 3
+
+    # Define target path
+    #   rtl: rv_plic.sv & rv_plic_reg_pkg.sv & rv_plic_reg_top.sv
+    #   doc: rv_plic.hjson
+    rtl_path = out_path / 'rtl'
+    rtl_path.mkdir(parents=True, exist_ok=True)
+    doc_path = out_path / 'doc'
+    doc_path.mkdir(parents=True, exist_ok=True)
+
+    # Generating IP top module script is not generalized yet.
+    # So, topgen reads template files from rv_plic directory directly.
+    # Next, if the ip top gen tool is placed in util/ we can import the library.
+    tpl_path = out_path / '../ip/rv_plic/doc'
+    hjson_tpl_path = tpl_path / 'rv_plic.tpl.hjson'
+    rtl_tpl_path = tpl_path / 'rv_plic.tpl.sv'
+
+    # Generate Register Package and RTLs
+    out = StringIO()
+    with hjson_tpl_path.open(mode='r', encoding='UTF-8') as fin:
+        hjson_tpl = Template(fin.read())
+        out = hjson_tpl.render(src=src, target=target, prio=prio)
+        log.info("RV_PLIC hjson: %s" % out)
+
+    if out == "":
+        log.error("Cannot generate interrupt controller config file")
+        return
+
+    hjson_gen_path = doc_path / "rv_plic.hjson"
+    gencmd = ("// util/topgen.py -t hw/top_earlgrey/doc/top_earlgrey.hjson --plic-only "
+              "-o hw/top_earlgrey/\n\n")
+    with hjson_gen_path.open(mode='w', encoding='UTF-8') as fout:
+        fout.write(genhdr + gencmd + out)
+
+    # Generate register RTLs (currently using shell execute)
+    # TODO: More secure way to gneerate RTL
+    hjson_obj = hjson.loads(out,
+                            use_decimal=True,
+                            object_pairs_hook=validate.checking_dict)
+    validate.validate(hjson_obj)
+    gen_rtl.gen_rtl(hjson_obj, str(rtl_path))
+
+    # Generate RV_PLIC Top Module
+    with rtl_tpl_path.open(mode='r', encoding='UTF-8') as fin:
+        rtl_tpl = Template(fin.read())
+        out = rtl_tpl.render(src=src, target=target, prio=prio)
+        log.info("RV_PLIC RTL: %s" % out)
+
+    if out == "":
+        log.error("Cannot generate interrupt controller RTL")
+        return
+
+    rtl_gen_path = rtl_path / "rv_plic.sv"
+    with rtl_gen_path.open(mode='w', encoding='UTF-8') as fout:
+        fout.write(genhdr + gencmd + out)
+
+
+def generate_top_ral(top, ip_objs, out_path):
+    # construct top ral block
+    top_block = gen_rtl.Block()
+    top_block.name = "chip"
+    top_block.base_addr = 0
+    top_block.width = int(top["datawidth"])
+
+    # add blocks
+    for ip_obj in ip_objs:
+        top_block.blocks.append(gen_rtl.json_to_reg(ip_obj))
+
+    # add memories
+    if "memory" in top.keys():
+        for item in list(top["memory"]):
+            mem = gen_rtl.Window()
+            mem.name = item["name"]
+            mem.base_addr = int(item["base_addr"], 0)
+            mem.limit_addr = int(item["base_addr"], 0) + int(item["size"], 0)
+            # TODO: need to add mem access info for memories in topcfg
+            mem.dvrights = "RW"
+            mem.n_bits = top_block.width
+            top_block.wins.append(mem)
+
+    # get sub-block base addresses from top cfg
+    for block in top_block.blocks:
+        for module in top["module"]:
+            if block.name == module["name"]:
+                block.base_addr = module["base_addr"]
+                break
+    # generate the top ral model with template
+    gen_dv.gen_ral(top_block, str(out_path))
+
+
+def main():
+    parser = argparse.ArgumentParser(prog="topgen")
+    parser.add_argument(
+        '--topcfg',
+        '-t',
+        required=True,
+        help="`top_{name}.hjson` file.")
+    parser.add_argument('--tpl', '-c', help="`top_{name}.tpl.sv` file.")
+    parser.add_argument(
+        '--outdir',
+        '-o',
+        help='''Target TOP directory.
+             Module is created under rtl/. (default: dir(topcfg)/..)
+             ''') # yapf: disable
+    parser.add_argument('--verbose', '-v', action='store_true', help="Verbose")
+
+    # Generator options: 'no' series. cannot combined with 'only' series
+    parser.add_argument(
+        '--no-top',
+        action='store_true',
+        help="If defined, topgen doesn't generate top_{name} RTLs.")
+    parser.add_argument(
+        '--no-xbar',
+        action='store_true',
+        help="If defined, topgen doesn't generate crossbar RTLs.")
+    parser.add_argument(
+        '--no-plic',
+        action='store_true',
+        help="If defined, topgen doesn't generate the interrup controller RTLs."
+    )
+    parser.add_argument(
+        '--no-gen-hjson',
+        action='store_true',
+        help='''If defined, the tool assumes topcfg as a generated hjson.
+             So it bypasses the validation step and doesn't read ip and
+             xbar configurations
+             ''')
+
+    # Generator options: 'only' series. cannot combined with 'no' series
+    parser.add_argument(
+        '--top-only',
+        action='store_true',
+        help="If defined, the tool generates top RTL only") # yapf:disable
+    parser.add_argument(
+        '--xbar-only',
+        action='store_true',
+        help="If defined, the tool generates crossbar RTLs only")
+    parser.add_argument(
+        '--plic-only',
+        action='store_true',
+        help="If defined, the tool generates RV_PLIC RTL and hjson only")
+    parser.add_argument(
+        '--hjson-only',
+        action='store_true',
+        help="If defined, the tool generates complete hjson only")
+    # Generator options: generate dv ral model
+    parser.add_argument(
+        '--top_ral',
+        '-r',
+        default=False,
+        action='store_true',
+        help="If set, the tool generates top level RAL model for DV")
+
+    args = parser.parse_args()
+
+    # check combinations
+    if args.top_ral:
+        args.hjson_only = True
+        args.no_top = True
+
+    if args.hjson_only:
+        args.no_gen_hjson = False
+
+    if (args.no_top or args.no_xbar or
+            args.no_plic) and (args.top_only or args.xbar_only or
+                               args.plic_only):
+        log.error(
+            "'no' series options cannot be used with 'only' series options")
+        raise SystemExit(sys.exc_info()[1])
+
+    if not args.hjson_only and not args.tpl:
+        log.error(
+            "Template file can be omitted only if '--hjson-only' is true")
+        raise SystemExit(sys.exc_info()[1])
+
+    if args.verbose:
+        log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
+    else:
+        log.basicConfig(format="%(levelname)s: %(message)s")
+
+    if not args.outdir:
+        outdir = Path(args.topcfg).parent / ".."
+        log.info("TOP directory not given. Use %s", (outdir))
+    elif not Path(args.outdir).is_dir():
+        log.error("'--outdir' should point to writable directory")
+        raise SystemExit(sys.exc_info()[1])
+    else:
+        outdir = Path(args.outdir)
+
+    out_path = Path(outdir)
+
+    if not args.no_gen_hjson or args.hjson_only:
+        # load top configuration
+        try:
+            with open(args.topcfg, 'r') as ftop:
+                topcfg = hjson.load(ftop, use_decimal=True)
+        except ValueError:
+            raise SystemExit(sys.exc_info()[1])
+
+        # Sweep the IP directory and gather the config files
+        ip_dir = Path(__file__).parents[1] / 'hw/ip'
+        ips = search_ips(ip_dir)
+
+        # exclude rv_plic (to use top_earlgrey one) and
+        ips = [x for x in ips if not x.parents[1].name in filter_list]
+
+        # It may require two passes to check if the module is needed.
+        # TODO: first run of topgen will fail due to the absent of rv_plic.
+        # It needs to run up to amend_interrupt in merge_top function
+        # then creates rv_plic.hjson then run xbar generation.
+        hjson_dir = Path(args.topcfg).parent
+        ips.append(hjson_dir / 'rv_plic.hjson')
+
+        # load hjson and pass validate from reggen
+        try:
+            ip_objs = []
+            for x in ips:
+                # Skip if it is not in the module list
+                if not x.stem in [ip["type"] for ip in topcfg["module"]]:
+                    log.info(
+                        "Skip module %s as it isn't in the top module list" %
+                        x.stem)
+                    continue
+
+                obj = hjson.load(
+                    x.open('r'),
+                    use_decimal=True,
+                    object_pairs_hook=validate.checking_dict)
+                if validate.validate(obj) != 0:
+                    log.info("Parsing IP %s configuration failed. Skip" % x)
+                    continue
+                ip_objs.append(obj)
+
+        except ValueError:
+            raise SystemExit(sys.exc_info()[1])
+
+        # Read the crossbars under the top directory
+        xbar_objs = get_hjsonobj_xbars(hjson_dir)
+
+        log.info("Detected crossbars: %s" %
+                 (", ".join([x["name"] for x in xbar_objs])))
+
+        # TODO: Add validate
+        topcfg = validate_top(topcfg)
+
+        # TODO: Add conversion logic from top to top.complete.hjson
+        completecfg = merge_top(topcfg, ip_objs, xbar_objs)
+
+        genhjson_path = hjson_dir / ("top_%s.gen.hjson" % completecfg["name"])
+        gencmd = ("// util/topgen.py -t hw/top_earlgrey/doc/top_earlgrey.hjson --hjson-only "
+                  "-o hw/top_earlgrey/\n")
+
+        if args.top_ral:
+            generate_top_ral(completecfg, ip_objs, out_path)
+        else:
+            genhjson_path.write_text(genhdr + gencmd +
+                                     hjson.dumps(completecfg, for_json=True))
+
+    if args.hjson_only:
+        log.info("hjson is generated. Exiting...")
+        sys.exit()
+
+    if args.no_gen_hjson:
+        # load top.complete configuration
+        try:
+            with open(args.topcfg, 'r') as ftop:
+                completecfg = hjson.load(ftop, use_decimal=True)
+        except ValueError:
+            raise SystemExit(sys.exc_info()[1])
+
+    # Generate PLIC
+    if not args.no_plic or args.plic_only:
+        generate_plic(completecfg, out_path)
+
+    # Generate xbars
+    if not args.no_xbar or args.xbar_only:
+        generate_xbars(completecfg, out_path)
+
+    # TODO: Get name from hjson
+    top_name = completecfg["name"]
+
+    if not args.no_top or args.top_only:
+        rtl_path = out_path / 'rtl'
+        rtl_path.mkdir(parents=True, exist_ok=True)
+        rtl_filepath = rtl_path / ("top_%s.sv" % (top_name))
+        out_rtl = generate_rtl(completecfg, args.tpl)
+
+        with rtl_filepath.open(mode='w', encoding='UTF-8') as fout:
+            fout.write(out_rtl)
+
+
+if __name__ == "__main__":
+    main()