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()