def is_shadowed_port(block: IpBlock, port: str) -> bool: """Return boolean indication whether a port is a shadow reset port """ shadowed_port = block.clocking.primary.reset if block.has_shadowed_reg() \ else None return port == shadowed_port
def generate_flash(topcfg, out_path): log.info("Generating flash") # Define target path rtl_path = out_path / 'ip/flash_ctrl/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) doc_path = out_path / 'ip/flash_ctrl/data/autogen' doc_path.mkdir(parents=True, exist_ok=True) tpl_path = Path(__file__).resolve().parent / '../hw/ip/flash_ctrl/data' # Read template files from ip directory. tpls = [] outputs = [] names = [ 'flash_ctrl.hjson', 'flash_ctrl.sv', 'flash_ctrl_pkg.sv', 'flash_ctrl_region_cfg.sv' ] for x in names: tpls.append(tpl_path / Path(x + ".tpl")) if "hjson" in x: outputs.append(doc_path / Path(x)) else: outputs.append(rtl_path / Path(x)) # Parameters needed for generation flash_mems = [ module for module in topcfg['module'] if module['type'] == 'flash_ctrl' ] if len(flash_mems) > 1: log.error("This design does not currently support multiple flashes") return cfg = flash_mems[0]['memory']['mem']['config'] # Generate templated files for idx, t in enumerate(tpls): out = StringIO() with t.open(mode='r', encoding='UTF-8') as fin: tpl = Template(fin.read()) try: out = tpl.render(cfg=cfg) except: # noqa: E722 log.error(exceptions.text_error_template().render()) if out == "": log.error("Cannot generate {}".format(names[idx])) return with outputs[idx].open(mode='w', encoding='UTF-8') as fout: fout.write(genhdr + out) # Generate reg files hjson_path = outputs[0] gen_rtl.gen_rtl(IpBlock.from_path(str(hjson_path), []), str(rtl_path))
def generate_pwrmgr(top, out_path): log.info("Generating pwrmgr") # Count number of wakeups n_wkups = len(top["wakeups"]) log.info("Found {} wakeup signals".format(n_wkups)) # Count number of reset requests n_rstreqs = len(top["reset_requests"]) log.info("Found {} reset request signals".format(n_rstreqs)) if n_wkups < 1: n_wkups = 1 log.warning( "The design has no wakeup sources. Low power not supported.") if n_rstreqs < 1: n_rstreqs = 1 log.warning( "The design has no reset request sources. " "Reset requests are not supported.") # Define target path rtl_path = out_path / 'ip/pwrmgr/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) doc_path = out_path / 'ip/pwrmgr/data/autogen' doc_path.mkdir(parents=True, exist_ok=True) # So, read template files from ip directory. tpl_path = Path(__file__).resolve().parent / '../hw/ip/pwrmgr/data' hjson_tpl_path = tpl_path / 'pwrmgr.hjson.tpl' # Render and write out hjson out = StringIO() with hjson_tpl_path.open(mode='r', encoding='UTF-8') as fin: hjson_tpl = Template(fin.read()) try: out = hjson_tpl.render(NumWkups=n_wkups, Wkups=top["wakeups"], NumRstReqs=n_rstreqs) except: # noqa: E722 log.error(exceptions.text_error_template().render()) log.info("pwrmgr hjson: %s" % out) if out == "": log.error("Cannot generate pwrmgr config file") return hjson_path = doc_path / "pwrmgr.hjson" with hjson_path.open(mode='w', encoding='UTF-8') as fout: fout.write(genhdr + out) # Generate reg files gen_rtl.gen_rtl(IpBlock.from_path(str(hjson_path), []), str(rtl_path))
def generate_top_only(top_only_list, out_path, topname): log.info("Generating top only modules") for ip in top_only_list: hjson_path = Path(__file__).resolve( ).parent / "../hw/top_{}/ip/{}/data/{}.hjson".format(topname, ip, ip) genrtl_dir = out_path / "ip/{}/rtl".format(ip) genrtl_dir.mkdir(parents=True, exist_ok=True) log.info("Generating top modules {}, hjson: {}, output: {}".format( ip, hjson_path, genrtl_dir)) # Generate reg files gen_rtl.gen_rtl(IpBlock.from_path(str(hjson_path), []), str(genrtl_dir))
def generate_hardware_blocks(): for hardware in config["hardware_definitions"]: regs = IpBlock.from_path(str(SRCTREE_TOP.joinpath(hardware)), []) hw_path = config["outdir-generated"].joinpath(hardware) dst_path = hw_path.parent dst_path.mkdir(parents=True, exist_ok=True) regs_path = dst_path.joinpath(hw_path.name + '.registers') with open(regs_path, 'w') as regs_file: gen_html.gen_html(regs, regs_file) hwcfg_path = dst_path.joinpath(hw_path.name + '.hwcfg') with open(hwcfg_path, 'w') as hwcfg_file: gen_cfg_html.gen_cfg_html(regs, hwcfg_file)
def generate_regfile_from_path(hjson_path: Path, generated_rtl_path: Path, original_rtl_path: Path = None): '''Generate RTL register file from path and check countermeasure labels''' obj = IpBlock.from_path(str(hjson_path), []) # If this block has countermeasures, we grep for RTL annotations in # all .sv implementation files and check whether they match up # with what is defined inside the Hjson. sv_files = generated_rtl_path.glob('*.sv') if original_rtl_path is not None: sv_files = chain(sv_files, original_rtl_path.glob('*.sv')) rtl_names = CounterMeasure.search_rtl_files(sv_files) obj.check_cm_annotations(rtl_names, str(hjson_path)) gen_rtl.gen_rtl(obj, str(generated_rtl_path))
def generate_clkmgr(top, cfg_path, out_path): # Target paths rtl_path = out_path / 'ip/clkmgr/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) data_path = out_path / 'ip/clkmgr/data/autogen' data_path.mkdir(parents=True, exist_ok=True) # Template paths hjson_tpl = cfg_path / '../ip/clkmgr/data/clkmgr.hjson.tpl' rtl_tpl = cfg_path / '../ip/clkmgr/data/clkmgr.sv.tpl' pkg_tpl = cfg_path / '../ip/clkmgr/data/clkmgr_pkg.sv.tpl' hjson_out = data_path / 'clkmgr.hjson' rtl_out = rtl_path / 'clkmgr.sv' pkg_out = rtl_path / 'clkmgr_pkg.sv' tpls = [hjson_tpl, rtl_tpl, pkg_tpl] outputs = [hjson_out, rtl_out, pkg_out] names = ['clkmgr.hjson', 'clkmgr.sv', 'clkmgr_pkg.sv'] clocks = top['clocks'] assert isinstance(clocks, Clocks) typed_clocks = clocks.typed_clocks() hint_names = typed_clocks.hint_names() for idx, tpl in enumerate(tpls): out = "" with tpl.open(mode='r', encoding='UTF-8') as fin: tpl = Template(fin.read()) try: out = tpl.render(cfg=top, clocks=clocks, typed_clocks=typed_clocks, hint_names=hint_names) except: # noqa: E722 log.error(exceptions.text_error_template().render()) if out == "": log.error("Cannot generate {}".format(names[idx])) return with outputs[idx].open(mode='w', encoding='UTF-8') as fout: fout.write(genhdr + out) # Generate reg files gen_rtl.gen_rtl(IpBlock.from_path(str(hjson_out), []), str(rtl_path))
def generate_top_only(top_only_dict, out_path, topname, alt_hjson_path): log.info("Generating top only modules") for ip, reggen_only in top_only_dict.items(): if reggen_only and alt_hjson_path is not None: hjson_dir = Path(alt_hjson_path) else: hjson_dir = Path(__file__).resolve( ).parent / f"../hw/top_{topname}/ip/{ip}/data/" hjson_path = hjson_dir / f"{ip}.hjson" genrtl_dir = out_path / "ip/{}/rtl".format(ip) genrtl_dir.mkdir(parents=True, exist_ok=True) log.info("Generating top modules {}, hjson: {}, output: {}".format( ip, hjson_path, genrtl_dir)) # Generate reg files gen_rtl.gen_rtl(IpBlock.from_path(str(hjson_path), []), str(genrtl_dir))
def _process_top(topcfg, args, cfg_path, out_path, pass_idx): # Create generated list # These modules are generated through topgen templated_list = lib.get_templated_modules(topcfg) log.info("Templated list is {}".format(templated_list)) ipgen_list = lib.get_ipgen_modules(topcfg) log.info("Ip gen list is {}".format(ipgen_list)) generated_list = templated_list + ipgen_list # These modules are NOT generated but belong to a specific top # and therefore not part of "hw/ip" top_only_dict = { module['type']: lib.is_reggen_only(module) for module in topcfg['module'] if lib.is_top_reggen(module) } log.info("Filtered dict is {}".format(top_only_dict)) topname = topcfg["name"] # Sweep the IP directory and gather the config files ip_dir = Path(__file__).parents[1] / 'hw/ip' ips = search_ips(ip_dir) # exclude filtered IPs (to use top_${topname} one) and exclude_list = generated_list + list(top_only_dict.keys()) ips = [x for x in ips if not x.parents[1].name in exclude_list] # Hack alert # Generate clkmgr.hjson here so that it can be included below # Unlike other generated hjsons, clkmgr thankfully does not require # ip.hjson information. All the information is embedded within # the top hjson file topcfg['clocks'] = Clocks(topcfg['clocks']) extract_clocks(topcfg) generate_clkmgr(topcfg, cfg_path, out_path) # 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 for ip in generated_list: # For modules that are generated prior to gathering, we need to take it from # the output path. For modules not generated before, it may exist in a # pre-defined area already. log.info("Appending {}".format(ip)) if ip in ipgen_list: ip_relpath = 'ip_autogen' desc_file_relpath = 'data' else: ip_relpath = 'ip' desc_file_relpath = 'data/autogen' if ip == 'clkmgr' or (pass_idx > 0): ip_hjson = (Path(out_path) / ip_relpath / ip / desc_file_relpath / f"{ip}.hjson") else: ip_hjson = (hjson_dir.parent / ip_relpath / ip / desc_file_relpath / f"{ip}.hjson") ips.append(ip_hjson) for ip, reggen_only in top_only_dict.items(): log.info("Appending {}".format(ip)) if reggen_only and args.hjson_path: ip_hjson = Path(args.hjson_path) / f"{ip}.hjson" else: ip_hjson = hjson_dir.parent / f"ip/{ip}/data/{ip}.hjson" ips.append(ip_hjson) # load Hjson and pass validate from reggen try: ip_objs = [] for ip_desc_file in ips: ip_name = ip_desc_file.stem # Skip if it is not in the module list if ip_name not in [ip["type"] for ip in topcfg["module"]]: log.info("Skip module %s as it isn't in the top module list" % ip_name) continue # The auto-generated hjson might not yet exist. It will be created # later, see generate_{ip_name}() calls below. For the initial # validation, use the Hjson file with default values. # TODO: All of this is a rather ugly hack that we need to get rid # of as soon as we don't arbitrarily template IP description Hjson # files any more. if ip_name in generated_list and not ip_desc_file.is_file(): if ip_name in ipgen_list: log.info( "To-be-auto-generated Hjson %s does not yet exist. " "Falling back to the default configuration of template " "%s for initial validation." % (ip_desc_file, ip_name)) tpl_path = SRCTREE_TOP / 'hw/ip_templates' / ip_name ip_template = IpTemplate.from_template_path(tpl_path) ip_config = IpConfig(ip_template.params, f'top_{topname}_{ip_name}') try: ip_desc = IpDescriptionOnlyRenderer( ip_template, ip_config).render() except TemplateRenderError as e: log.error(e.verbose_str()) sys.exit(1) s = 'default description of IP template {}'.format(ip_name) ip_objs.append(IpBlock.from_text(ip_desc, [], s)) else: # TODO: Remove this block as soon as all IP templates use # ipgen. template_hjson_file = ip_dir / "{}/data/{}.hjson".format( ip_name, ip_name) log.info( "To-be-auto-generated Hjson %s does not yet exist. " "Falling back to Hjson description file %s shipped " "with the IP template for initial validation." % (ip_desc_file, template_hjson_file)) ip_objs.append( IpBlock.from_path(str(template_hjson_file), [])) else: ip_objs.append(IpBlock.from_path(str(ip_desc_file), [])) except ValueError: raise SystemExit(sys.exc_info()[1]) name_to_block = {} # type: Dict[str, IpBlock] for block in ip_objs: lblock = block.name.lower() assert lblock not in name_to_block name_to_block[lblock] = block connect_clocks(topcfg, name_to_block) # 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]))) # If specified, override the seed for random netlist constant computation. if args.rnd_cnst_seed: log.warning('Commandline override of rnd_cnst_seed with {}.'.format( args.rnd_cnst_seed)) topcfg['rnd_cnst_seed'] = args.rnd_cnst_seed # Otherwise, we either take it from the top_{topname}.hjson if present, or # randomly generate a new seed if not. else: random.seed() new_seed = random.getrandbits(64) if topcfg.setdefault('rnd_cnst_seed', new_seed) == new_seed: log.warning( 'No rnd_cnst_seed specified, setting to {}.'.format(new_seed)) topcfg, error = validate_top(topcfg, ip_objs, xbar_objs) if error != 0: raise SystemExit("Error occured while validating top.hjson") completecfg = merge_top(topcfg, name_to_block, xbar_objs) # Generate flash controller and flash memory generate_flash(topcfg, out_path) # Generate PLIC if not args.no_plic and \ not args.alert_handler_only and \ not args.xbar_only: generate_plic(completecfg, out_path) if args.plic_only: sys.exit() # Create Alert Handler LPGs before # generating the Alert Handler create_alert_lpgs(topcfg, name_to_block) # Generate Alert Handler if not args.xbar_only: generate_alert_handler(completecfg, out_path) if args.alert_handler_only: sys.exit() # Generate Pinmux generate_pinmux(completecfg, out_path) # Generate Pwrmgr generate_pwrmgr(completecfg, out_path) # Generate rstmgr generate_rstmgr(completecfg, out_path) # Generate top only modules # These modules are not templated, but are not in hw/ip generate_top_only(top_only_dict, out_path, topname, args.hjson_path) return completecfg, name_to_block
def _process_top(topcfg, args, cfg_path, out_path, pass_idx): # Create generated list # These modules are generated through topgen generated_list = [ module['type'] for module in topcfg['module'] if lib.is_templated(module) ] log.info("Filtered list is {}".format(generated_list)) # These modules are NOT generated but belong to a specific top # and therefore not part of "hw/ip" top_only_list = [ module['type'] for module in topcfg['module'] if lib.is_top_reggen(module) ] log.info("Filtered list is {}".format(top_only_list)) topname = topcfg["name"] # Sweep the IP directory and gather the config files ip_dir = Path(__file__).parents[1] / 'hw/ip' ips = search_ips(ip_dir) # exclude filtered IPs (to use top_${topname} one) and exclude_list = generated_list + top_only_list ips = [x for x in ips if not x.parents[1].name in exclude_list] # Hack alert # Generate clkmgr.hjson here so that it can be included below # Unlike other generated hjsons, clkmgr thankfully does not require # ip.hjson information. All the information is embedded within # the top hjson file topcfg['clocks'] = Clocks(topcfg['clocks']) extract_clocks(topcfg) generate_clkmgr(topcfg, cfg_path, out_path) # 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 for ip in generated_list: # For modules that are generated prior to gathering, we need to take it from # the output path. For modules not generated before, it may exist in a # pre-defined area already. log.info("Appending {}".format(ip)) if ip == 'clkmgr' or (pass_idx > 0): ip_hjson = Path(out_path) / "ip/{}/data/autogen/{}.hjson".format( ip, ip) else: ip_hjson = hjson_dir.parent / "ip/{}/data/autogen/{}.hjson".format( ip, ip) ips.append(ip_hjson) for ip in top_only_list: log.info("Appending {}".format(ip)) ip_hjson = hjson_dir.parent / "ip/{}/data/{}.hjson".format(ip, ip) ips.append(ip_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 x.stem not 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 # The auto-generated hjson might not yet exist. It will be created # later, see generate_{ip_name}() calls below. For the initial # validation, use the template in hw/ip/{ip_name}/data . if x.stem in generated_list and not x.is_file(): hjson_file = ip_dir / "{}/data/{}.hjson".format(x.stem, x.stem) log.info( "Auto-generated hjson %s does not yet exist. " % str(x) + "Falling back to template %s for initial validation." % str(hjson_file)) else: hjson_file = x ip_objs.append(IpBlock.from_path(str(hjson_file), [])) except ValueError: raise SystemExit(sys.exc_info()[1]) name_to_block = {} # type: Dict[str, IpBlock] for block in ip_objs: lblock = block.name.lower() assert lblock not in name_to_block name_to_block[lblock] = block connect_clocks(topcfg, name_to_block) # 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]))) # If specified, override the seed for random netlist constant computation. if args.rnd_cnst_seed: log.warning('Commandline override of rnd_cnst_seed with {}.'.format( args.rnd_cnst_seed)) topcfg['rnd_cnst_seed'] = args.rnd_cnst_seed # Otherwise, we either take it from the top_{topname}.hjson if present, or # randomly generate a new seed if not. else: random.seed() new_seed = random.getrandbits(64) if topcfg.setdefault('rnd_cnst_seed', new_seed) == new_seed: log.warning( 'No rnd_cnst_seed specified, setting to {}.'.format(new_seed)) topcfg, error = validate_top(topcfg, ip_objs, xbar_objs) if error != 0: raise SystemExit("Error occured while validating top.hjson") completecfg = merge_top(topcfg, name_to_block, xbar_objs) # Generate flash controller and flash memory generate_flash(topcfg, out_path) # Generate PLIC if not args.no_plic and \ not args.alert_handler_only and \ not args.xbar_only: generate_plic(completecfg, out_path) if args.plic_only: sys.exit() # Generate Alert Handler if not args.xbar_only: generate_alert_handler(completecfg, out_path) if args.alert_handler_only: sys.exit() # Generate Pinmux generate_pinmux(completecfg, out_path) # Generate Pwrmgr generate_pwrmgr(completecfg, out_path) # Generate rstmgr generate_rstmgr(completecfg, out_path) # Generate top only modules # These modules are not templated, but are not in hw/ip generate_top_only(top_only_list, out_path, topname) if pass_idx > 0 and args.top_ral: exit_code = generate_top_ral(completecfg, name_to_block, args.dv_base_prefix, out_path) sys.exit(exit_code) return completecfg, name_to_block
def generate_rstmgr(topcfg, out_path): log.info("Generating rstmgr") # Define target path rtl_path = out_path / 'ip/rstmgr/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) doc_path = out_path / 'ip/rstmgr/data/autogen' doc_path.mkdir(parents=True, exist_ok=True) tpl_path = Path(__file__).resolve().parent / '../hw/ip/rstmgr/data' # Read template files from ip directory. tpls = [] outputs = [] names = ['rstmgr.hjson', 'rstmgr.sv', 'rstmgr_pkg.sv'] for x in names: tpls.append(tpl_path / Path(x + ".tpl")) if "hjson" in x: outputs.append(doc_path / Path(x)) else: outputs.append(rtl_path / Path(x)) # Parameters needed for generation reset_obj = topcfg['resets'] # unique clocks clks = reset_obj.get_clocks() # resets sent to reset struct output_rsts = reset_obj.get_top_resets() # sw controlled resets sw_rsts = reset_obj.get_sw_resets() # leaf resets leaf_rsts = reset_obj.get_generated_resets() # Number of reset requests n_rstreqs = len(topcfg["reset_requests"]) # Generate templated files for idx, t in enumerate(tpls): out = StringIO() with t.open(mode='r', encoding='UTF-8') as fin: tpl = Template(fin.read()) try: out = tpl.render(clks=clks, power_domains=topcfg['power']['domains'], num_rstreqs=n_rstreqs, sw_rsts=sw_rsts, output_rsts=output_rsts, leaf_rsts=leaf_rsts, export_rsts=topcfg['exported_rsts'], reset_obj=topcfg['resets']) except: # noqa: E722 log.error(exceptions.text_error_template().render()) if out == "": log.error("Cannot generate {}".format(names[idx])) return with outputs[idx].open(mode='w', encoding='UTF-8') as fout: fout.write(genhdr + out) # Generate reg files hjson_path = outputs[0] gen_rtl.gen_rtl(IpBlock.from_path(str(hjson_path), []), str(rtl_path))
def generate_pinmux(top, out_path): topname = top['name'] pinmux = top['pinmux'] # Generation without pinmux and pinout configuration is not supported. assert 'pinmux' in top assert 'pinout' in top # Get number of wakeup detectors if 'num_wkup_detect' in pinmux: num_wkup_detect = pinmux['num_wkup_detect'] else: num_wkup_detect = 1 if num_wkup_detect <= 0: # TODO: add support for no wakeup counter case log.error('Topgen does currently not support generation of a top ' + 'without DIOs.') return if 'wkup_cnt_width' in pinmux: wkup_cnt_width = pinmux['wkup_cnt_width'] else: wkup_cnt_width = 8 if wkup_cnt_width <= 1: log.error('Wakeup counter width must be greater equal 2.') return # MIO Pads n_mio_pads = pinmux['io_counts']['muxed']['pads'] # Total inputs/outputs # Reuse the counts from the merge phase n_mio_periph_in = (pinmux['io_counts']['muxed']['inouts'] + pinmux['io_counts']['muxed']['inputs']) n_mio_periph_out = (pinmux['io_counts']['muxed']['inouts'] + pinmux['io_counts']['muxed']['outputs']) n_dio_periph_in = (pinmux['io_counts']['dedicated']['inouts'] + pinmux['io_counts']['dedicated']['inputs']) n_dio_periph_out = (pinmux['io_counts']['dedicated']['inouts'] + pinmux['io_counts']['dedicated']['outputs']) n_dio_pads = (pinmux['io_counts']['dedicated']['inouts'] + pinmux['io_counts']['dedicated']['inputs'] + pinmux['io_counts']['dedicated']['outputs']) # TODO: derive this value attr_dw = 13 # Generation with zero MIO/DIO pads is currently not supported. assert (n_mio_pads > 0) assert (n_dio_pads > 0) log.info('Generating pinmux with following info from hjson:') log.info('attr_dw: %d' % attr_dw) log.info('num_wkup_detect: %d' % num_wkup_detect) log.info('wkup_cnt_width: %d' % wkup_cnt_width) log.info('n_mio_periph_in: %d' % n_mio_periph_in) log.info('n_mio_periph_out: %d' % n_mio_periph_out) log.info('n_dio_periph_in: %d' % n_dio_periph_in) log.info('n_dio_periph_out: %d' % n_dio_periph_out) log.info('n_dio_pads: %d' % n_dio_pads) # Target path # rtl: pinmux_reg_pkg.sv & pinmux_reg_top.sv # data: pinmux.hjson rtl_path = out_path / 'ip/pinmux/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) data_path = out_path / 'ip/pinmux/data/autogen' data_path.mkdir(parents=True, exist_ok=True) # Template path tpl_path = Path( __file__).resolve().parent / '../hw/ip/pinmux/data/pinmux.hjson.tpl' # Generate register package and RTLs gencmd = ("// util/topgen.py -t hw/top_{topname}/data/top_{topname}.hjson " "-o hw/top_{topname}/\n\n".format(topname=topname)) hjson_gen_path = data_path / "pinmux.hjson" out = StringIO() with tpl_path.open(mode='r', encoding='UTF-8') as fin: hjson_tpl = Template(fin.read()) try: out = hjson_tpl.render( n_mio_periph_in=n_mio_periph_in, n_mio_periph_out=n_mio_periph_out, n_mio_pads=n_mio_pads, # each DIO has in, out and oe wires # some of these have to be tied off in the # top, depending on the type. n_dio_periph_in=n_dio_pads, n_dio_periph_out=n_dio_pads, n_dio_pads=n_dio_pads, attr_dw=attr_dw, n_wkup_detect=num_wkup_detect, wkup_cnt_width=wkup_cnt_width) except: # noqa: E722 log.error(exceptions.text_error_template().render()) log.info("PINMUX HJSON: %s" % out) if out == "": log.error("Cannot generate pinmux HJSON") return with hjson_gen_path.open(mode='w', encoding='UTF-8') as fout: fout.write(genhdr + gencmd + out) gen_rtl.gen_rtl(IpBlock.from_text(out, [], str(hjson_gen_path)), str(rtl_path))
def generate_plic(top, out_path): topname = top["name"] # Count number of interrupts # Interrupt source 0 is tied to 0 to conform RISC-V PLIC spec. # So, total number of interrupts are the number of entries in the list + 1 src = sum([x["width"] if "width" in x else 1 for x in top["interrupt"]]) + 1 # 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 # data: rv_plic.hjson rtl_path = out_path / 'ip/rv_plic/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) doc_path = out_path / 'ip/rv_plic/data/autogen' doc_path.mkdir(parents=True, exist_ok=True) hjson_path = out_path / 'ip/rv_plic/data/autogen' hjson_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 = Path(__file__).resolve().parent / '../hw/ip/rv_plic/data' hjson_tpl_path = tpl_path / 'rv_plic.hjson.tpl' rtl_tpl_path = tpl_path / 'rv_plic.sv.tpl' # Generate Register Package and RTLs out = StringIO() with hjson_tpl_path.open(mode='r', encoding='UTF-8') as fin: hjson_tpl = Template(fin.read()) try: out = hjson_tpl.render(src=src, target=target, prio=prio) except: # noqa: E722 log.error(exceptions.text_error_template().render()) log.info("RV_PLIC hjson: %s" % out) if out == "": log.error("Cannot generate interrupt controller config file") return hjson_gen_path = hjson_path / "rv_plic.hjson" gencmd = ( "// util/topgen.py -t hw/top_{topname}/data/top_{topname}.hjson --plic-only " "-o hw/top_{topname}/\n\n".format(topname=topname)) 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 generate RTL gen_rtl.gen_rtl(IpBlock.from_text(out, [], str(hjson_gen_path)), 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()) try: out = rtl_tpl.render(src=src, target=target, prio=prio) except: # noqa: E722 log.error(exceptions.text_error_template().render()) 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_alert_handler(top, out_path): # default values esc_cnt_dw = 32 accu_cnt_dw = 16 async_on = "'0" # leave this constant n_classes = 4 topname = top["name"] if esc_cnt_dw < 1: log.error("EscCntDw must be larger than 0") if accu_cnt_dw < 1: log.error("AccuCntDw must be larger than 0") # Count number of alerts n_alerts = sum([x["width"] if "width" in x else 1 for x in top["alert"]]) if n_alerts < 1: # set number of alerts to 1 such that the config is still valid # that input will be tied off n_alerts = 1 log.warning("no alerts are defined in the system") else: async_on = "" for alert in top['alert']: for k in range(alert['width']): async_on = str(alert['async']) + async_on # convert to hexstring to shorten line length async_on = ("%d'h" % n_alerts) + hex(int(async_on, 2))[2:] log.info("alert handler parameterization:") log.info("NAlerts = %d" % n_alerts) log.info("EscCntDw = %d" % esc_cnt_dw) log.info("AccuCntDw = %d" % accu_cnt_dw) log.info("AsyncOn = %s" % async_on) # Define target path rtl_path = out_path / 'ip/alert_handler/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) doc_path = out_path / 'ip/alert_handler/data/autogen' doc_path.mkdir(parents=True, exist_ok=True) # Generating IP top module script is not generalized yet. # So, topgen reads template files from alert_handler directory directly. tpl_path = Path(__file__).resolve().parent / '../hw/ip/alert_handler/data' hjson_tpl_path = tpl_path / 'alert_handler.hjson.tpl' # Generate Register Package and RTLs out = StringIO() with hjson_tpl_path.open(mode='r', encoding='UTF-8') as fin: hjson_tpl = Template(fin.read()) try: out = hjson_tpl.render(n_alerts=n_alerts, esc_cnt_dw=esc_cnt_dw, accu_cnt_dw=accu_cnt_dw, async_on=async_on, n_classes=n_classes) except: # noqa: E722 log.error(exceptions.text_error_template().render()) log.info("alert_handler hjson: %s" % out) if out == "": log.error("Cannot generate alert_handler config file") return hjson_gen_path = doc_path / "alert_handler.hjson" gencmd = ( "// util/topgen.py -t hw/top_{topname}/data/top_{topname}.hjson --alert-handler-only " "-o hw/top_{topname}/\n\n".format(topname=topname)) 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 gen_rtl.gen_rtl(IpBlock.from_text(out, [], str(hjson_gen_path)), str(rtl_path))
def render(self, output_dir: Path, overwrite_output_dir: bool) -> None: """ Render the IP template into output_dir. """ # Ensure that we operate on an absolute path for output_dir. output_dir = output_dir.resolve() if not overwrite_output_dir and output_dir.exists(): raise TemplateRenderError( "Output directory '{}' exists and should not be overwritten.". format(output_dir)) # Prepare the IP directory in a staging area to later atomically move it # to the final destination. output_dir_staging = output_dir.parent / f".~{output_dir.stem}.staging" if output_dir_staging.is_dir(): raise TemplateRenderError( "Output staging directory '{}' already exists. Remove it and " "try again.".format(output_dir_staging)) template_path = self.ip_template.template_path try: # Copy everything but the templates and the template description. ignore = shutil.ignore_patterns('*.tpl', '*.tpldesc.hjson') shutil.copytree(template_path, output_dir_staging, ignore=ignore) # Render templates. for template_filepath in template_path.glob('**/*.tpl'): template_filepath_rel = template_filepath.relative_to( template_path) # Put the output file into the same relative directory as the # template. The output file will also have the same name as the # template, just without the '.tpl' suffix. outdir_path = output_dir_staging / template_filepath_rel.parent self._render_mako_template_to_file(template_filepath, outdir_path) # Generate register interface through reggen. hjson_path = (output_dir_staging / 'data' / (self.ip_template.name + '.hjson')) if not hjson_path.exists(): raise TemplateRenderError( "Invalid template: The IP description file " f"{str(hjson_path)!r} does not exist.") rtl_path = output_dir_staging / 'rtl' rtl_path.mkdir(exist_ok=True) obj = IpBlock.from_path(str(hjson_path), []) # If this block has countermeasures, we grep for RTL annotations in # all .sv implementation files and check whether they match up # with what is defined inside the Hjson. sv_files = rtl_path.glob('*.sv') rtl_names = CounterMeasure.search_rtl_files(sv_files) obj.check_cm_annotations(rtl_names, str(hjson_path)) # TODO: Pass on template parameters to reggen? Or enable the user # to set a different set of parameters in the renderer? reggen.gen_rtl.gen_rtl(obj, str(rtl_path)) # Write IP configuration (to reproduce the generation process). # TODO: Should the ipconfig file be written to the instance name, # or the template name? self.ip_config.to_file( output_dir_staging / 'data/{}.ipconfig.hjson'.format(self.ip_config.instance_name), header=_HJSON_LICENSE_HEADER) # Safely overwrite the existing directory if necessary: # # - First move the existing directory out of the way. # - Then move the staging directory with the new content in place. # - Finally remove the old directory. # # If anything goes wrong in the meantime we are left with either # the old or the new directory, and potentially some backups of # outdated files. do_overwrite = overwrite_output_dir and output_dir.exists() output_dir_existing_bak = output_dir.with_suffix( '.bak~' + str(int(time.time()))) if do_overwrite: os.rename(output_dir, output_dir_existing_bak) # Move the staging directory to the final destination. os.rename(output_dir_staging, output_dir) # Remove the old/"overwritten" data. if do_overwrite: try: shutil.rmtree(output_dir_existing_bak) except Exception as e: msg = ( 'Unable to delete the backup directory ' f'{output_dir_existing_bak} of the overwritten data. ' 'Please remove it manually.') raise TemplateRenderError(msg).with_traceback( e.__traceback__) finally: # Ensure that the staging directory is removed at the end. Ignore # errors as the directory should not exist at this point actually. shutil.rmtree(output_dir_staging, ignore_errors=True)
def generate_pinmux(top, out_path): topname = top["name"] # MIO Pads n_mio_pads = top["pinmux"]["num_mio"] if n_mio_pads <= 0: # TODO: add support for no MIO case log.error("Topgen does currently not support generation of a top " + "without a pinmux.") return if "padctrl" not in top: # TODO: add support for no MIO case log.error("Topgen does currently not support generation of a top " + "without a padctrl instance.") return # Get number of wakeup detectors if "num_wkup_detect" in top["pinmux"]: num_wkup_detect = top["pinmux"]["num_wkup_detect"] else: num_wkup_detect = 1 if num_wkup_detect <= 0: # TODO: add support for no wakeup counter case log.error("Topgen does currently not support generation of a top " + "without DIOs.") return if "wkup_cnt_width" in top["pinmux"]: wkup_cnt_width = top["pinmux"]["wkup_cnt_width"] else: wkup_cnt_width = 8 if wkup_cnt_width <= 1: log.error("Wakeup counter width must be greater equal 2.") return # Total inputs/outputs # Validation ensures that the width field is present. num_mio_inputs = sum([x["width"] for x in top["pinmux"]["inputs"]]) num_mio_outputs = sum([x["width"] for x in top["pinmux"]["outputs"]]) num_dio_inputs = sum([ x["width"] if x["type"] == "input" else 0 for x in top["pinmux"]["dio"] ]) num_dio_outputs = sum([ x["width"] if x["type"] == "output" else 0 for x in top["pinmux"]["dio"] ]) num_dio_inouts = sum([ x["width"] if x["type"] == "inout" else 0 for x in top["pinmux"]["dio"] ]) n_mio_periph_in = num_mio_inputs n_mio_periph_out = num_mio_outputs n_dio_periph_in = num_dio_inouts + num_dio_inputs n_dio_periph_out = num_dio_inouts + num_dio_outputs n_dio_pads = num_dio_inouts + num_dio_inputs + num_dio_outputs # TODO: derive this value attr_dw = 10 if n_dio_pads <= 0: # TODO: add support for no DIO case log.error("Topgen does currently not support generation of a top " + "without DIOs.") return # find the start and end pin positions for usbdev usb_pin_pos = _calc_dio_pin_pos(top, "usbdev") usb_start_pos = usb_pin_pos[-1] n_usb_pins = usb_pin_pos[0] - usb_pin_pos[-1] + 1 usb_dp_sel = _find_dio_pin_pos(top, "usbdev_dp") usb_dn_sel = _find_dio_pin_pos(top, "usbdev_dn") usb_dp_pull_sel = _find_dio_pin_pos(top, "usbdev_dp_pullup") usb_dn_pull_sel = _find_dio_pin_pos(top, "usbdev_dn_pullup") log.info("Generating pinmux with following info from hjson:") log.info("num_mio_inputs: %d" % num_mio_inputs) log.info("num_mio_outputs: %d" % num_mio_outputs) log.info("num_dio_inputs: %d" % num_dio_inputs) log.info("num_dio_outputs: %d" % num_dio_outputs) log.info("attr_dw: %d" % attr_dw) log.info("num_wkup_detect: %d" % num_wkup_detect) log.info("wkup_cnt_width: %d" % wkup_cnt_width) log.info("This translates to:") log.info("n_mio_periph_in: %d" % n_mio_periph_in) log.info("n_mio_periph_out: %d" % n_mio_periph_out) log.info("n_dio_periph_in: %d" % n_dio_periph_in) log.info("n_dio_periph_out: %d" % n_dio_periph_out) log.info("n_dio_pads: %d" % n_dio_pads) log.info("usb_start_pos: %d" % usb_start_pos) log.info("n_usb_pins: %d" % n_usb_pins) # Target path # rtl: pinmux_reg_pkg.sv & pinmux_reg_top.sv # data: pinmux.hjson rtl_path = out_path / 'ip/pinmux/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) data_path = out_path / 'ip/pinmux/data/autogen' data_path.mkdir(parents=True, exist_ok=True) # Template path tpl_path = Path( __file__).resolve().parent / '../hw/ip/pinmux/data/pinmux.hjson.tpl' # Generate register package and RTLs gencmd = ("// util/topgen.py -t hw/top_{topname}/data/top_{topname}.hjson " "-o hw/top_{topname}/\n\n".format(topname=topname)) hjson_gen_path = data_path / "pinmux.hjson" out = StringIO() with tpl_path.open(mode='r', encoding='UTF-8') as fin: hjson_tpl = Template(fin.read()) try: # TODO: pass in information about always-on peripherals # TODO: pass in information on which DIOs can be selected # as wakeup signals # TODO: pass in signal names such that we can introduce # named enums for select signals out = hjson_tpl.render( n_mio_periph_in=n_mio_periph_in, n_mio_periph_out=n_mio_periph_out, n_mio_pads=n_mio_pads, # each DIO has in, out and oe wires # some of these have to be tied off in the # top, depending on the type. n_dio_periph_in=n_dio_pads, n_dio_periph_out=n_dio_pads, n_dio_pads=n_dio_pads, attr_dw=attr_dw, n_wkup_detect=num_wkup_detect, wkup_cnt_width=wkup_cnt_width, usb_start_pos=usb_start_pos, n_usb_pins=n_usb_pins, usb_dp_sel=usb_dp_sel, usb_dn_sel=usb_dn_sel, usb_dp_pull_sel=usb_dp_pull_sel, usb_dn_pull_sel=usb_dn_pull_sel ) except: # noqa: E722 log.error(exceptions.text_error_template().render()) log.info("PINMUX HJSON: %s" % out) if out == "": log.error("Cannot generate pinmux HJSON") return with hjson_gen_path.open(mode='w', encoding='UTF-8') as fout: fout.write(genhdr + gencmd + out) gen_rtl.gen_rtl(IpBlock.from_text(out, [], str(hjson_gen_path)), str(rtl_path))
def generate_clkmgr(top, cfg_path, out_path): # Target paths rtl_path = out_path / 'ip/clkmgr/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) data_path = out_path / 'ip/clkmgr/data/autogen' data_path.mkdir(parents=True, exist_ok=True) # Template paths hjson_tpl = cfg_path / '../ip/clkmgr/data/clkmgr.hjson.tpl' rtl_tpl = cfg_path / '../ip/clkmgr/data/clkmgr.sv.tpl' pkg_tpl = cfg_path / '../ip/clkmgr/data/clkmgr_pkg.sv.tpl' hjson_out = data_path / 'clkmgr.hjson' rtl_out = rtl_path / 'clkmgr.sv' pkg_out = rtl_path / 'clkmgr_pkg.sv' tpls = [hjson_tpl, rtl_tpl, pkg_tpl] outputs = [hjson_out, rtl_out, pkg_out] names = ['clkmgr.hjson', 'clkmgr.sv', 'clkmgr_pkg.sv'] # clock classification grps = top['clocks']['groups'] ft_clks = OrderedDict() rg_clks = OrderedDict() sw_clks = OrderedDict() src_aon_attr = OrderedDict() hint_clks = OrderedDict() # construct a dictionary of the aon attribute for easier lookup # ie, src_name_A: True, src_name_B: False for src in top['clocks']['srcs'] + top['clocks']['derived_srcs']: if src['aon'] == 'yes': src_aon_attr[src['name']] = True else: src_aon_attr[src['name']] = False rg_srcs = [src for (src, attr) in src_aon_attr.items() if not attr] # clocks fed through clkmgr but are not disturbed in any way # This maintains the clocking structure consistency # This includes two groups of clocks # Clocks fed from the always-on source # Clocks fed to the powerup group ft_clks = OrderedDict([(clk, src) for grp in grps for (clk, src) in grp['clocks'].items() if src_aon_attr[src] or grp['name'] == 'powerup']) # root-gate clocks rg_clks = OrderedDict([(clk, src) for grp in grps for (clk, src) in grp['clocks'].items() if grp['name'] != 'powerup' and grp['sw_cg'] == 'no' and not src_aon_attr[src]]) # direct sw control clocks sw_clks = OrderedDict([(clk, src) for grp in grps for (clk, src) in grp['clocks'].items() if grp['sw_cg'] == 'yes' and not src_aon_attr[src]]) # sw hint clocks hints = OrderedDict([(clk, src) for grp in grps for (clk, src) in grp['clocks'].items() if grp['sw_cg'] == 'hint' and not src_aon_attr[src]]) # hint clocks dict for clk, src in hints.items(): # the clock is constructed as clk_{src_name}_{module_name}. # so to get the module name we split from the right and pick the last entry hint_clks[clk] = OrderedDict() hint_clks[clk]['name'] = (clk.rsplit('_', 1)[-1]) hint_clks[clk]['src'] = src for idx, tpl in enumerate(tpls): out = "" with tpl.open(mode='r', encoding='UTF-8') as fin: tpl = Template(fin.read()) try: out = tpl.render(cfg=top, div_srcs=top['clocks']['derived_srcs'], rg_srcs=rg_srcs, ft_clks=ft_clks, rg_clks=rg_clks, sw_clks=sw_clks, export_clks=top['exported_clks'], hint_clks=hint_clks) except: # noqa: E722 log.error(exceptions.text_error_template().render()) if out == "": log.error("Cannot generate {}".format(names[idx])) return with outputs[idx].open(mode='w', encoding='UTF-8') as fout: fout.write(genhdr + out) # Generate reg files gen_rtl.gen_rtl(IpBlock.from_path(str(hjson_out), []), str(rtl_path))
def main(): verbose = 0 parser = argparse.ArgumentParser( prog="regtool", formatter_class=argparse.RawDescriptionHelpFormatter, usage=USAGE, description=DESC) parser.add_argument('input', nargs='?', metavar='file', type=argparse.FileType('r'), default=sys.stdin, help='input file in Hjson type') parser.add_argument('-d', action='store_true', help='Output register documentation (html)') parser.add_argument('--cdefines', '-D', action='store_true', help='Output C defines header') parser.add_argument('--doc', action='store_true', help='Output source file documentation (gfm)') parser.add_argument('-j', action='store_true', help='Output as formatted JSON') parser.add_argument('-c', action='store_true', help='Output as JSON') parser.add_argument('-r', action='store_true', help='Output as SystemVerilog RTL') parser.add_argument('-s', action='store_true', help='Output as UVM Register class') parser.add_argument('-f', action='store_true', help='Output as FPV CSR rw assertion module') parser.add_argument('--outdir', '-t', help='Target directory for generated RTL; ' 'tool uses ../rtl if blank.') parser.add_argument('--dv-base-prefix', default='dv_base', help='Prefix for the DV register classes from which ' 'the register models are derived.') parser.add_argument('--outfile', '-o', type=argparse.FileType('w'), default=sys.stdout, help='Target filename for json, html, gfm.') parser.add_argument('--verbose', '-v', action='store_true', help='Verbose and run validate twice') parser.add_argument('--param', '-p', type=str, default="", help='''Change the Parameter values. Only integer value is supported. You can add multiple param arguments. Format: ParamA=ValA;ParamB=ValB ''') parser.add_argument('--version', '-V', action='store_true', help='Show version') parser.add_argument('--novalidate', action='store_true', help='Skip validate, just output json') args = parser.parse_args() if args.version: version.show_and_exit(__file__, ["Hjson", "Mako"]) verbose = args.verbose if (verbose): log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) else: log.basicConfig(format="%(levelname)s: %(message)s") # Entries are triples of the form (arg, (format, dirspec)). # # arg is the name of the argument that selects the format. format is the # name of the format. dirspec is None if the output is a single file; if # the output needs a directory, it is a default path relative to the source # file (used when --outdir is not given). arg_to_format = [('j', ('json', None)), ('c', ('compact', None)), ('d', ('html', None)), ('doc', ('doc', None)), ('r', ('rtl', 'rtl')), ('s', ('dv', 'dv')), ('f', ('fpv', 'fpv/vip')), ('cdefines', ('cdh', None))] format = None dirspec = None for arg_name, spec in arg_to_format: if getattr(args, arg_name): if format is not None: log.error('Multiple output formats specified on ' 'command line ({} and {}).'.format(format, spec[0])) sys.exit(1) format, dirspec = spec if format is None: format = 'hjson' infile = args.input # Split parameters into key=value pairs. raw_params = args.param.split(';') if args.param else [] params = [] for idx, raw_param in enumerate(raw_params): tokens = raw_param.split('=') if len(tokens) != 2: raise ValueError('Entry {} in list of parameter defaults to ' 'apply is {!r}, which is not of the form ' 'param=value.'.format(idx, raw_param)) params.append((tokens[0], tokens[1])) # Define either outfile or outdir (but not both), depending on the output # format. outfile = None outdir = None if dirspec is None: if args.outdir is not None: log.error('The {} format expects an output file, ' 'not an output directory.'.format(format)) sys.exit(1) outfile = args.outfile else: if args.outfile is not sys.stdout: log.error('The {} format expects an output directory, ' 'not an output file.'.format(format)) sys.exit(1) if args.outdir is not None: outdir = args.outdir elif infile is not sys.stdin: outdir = str(PurePath(infile.name).parents[1].joinpath(dirspec)) else: # We're using sys.stdin, so can't infer an output directory name log.error( 'The {} format writes to an output directory, which ' 'cannot be inferred automatically if the input comes ' 'from stdin. Use --outdir to specify it manually.'.format( format)) sys.exit(1) if format == 'doc': with outfile: gen_selfdoc.document(outfile) exit(0) srcfull = infile.read() try: obj = IpBlock.from_text(srcfull, params, infile.name) except ValueError as err: log.error(str(err)) exit(1) if args.novalidate: with outfile: gen_json.gen_json(obj, outfile, format) outfile.write('\n') else: if format == 'rtl': return gen_rtl.gen_rtl(obj, outdir) if format == 'dv': return gen_dv.gen_dv(obj, args.dv_base_prefix, outdir) if format == 'fpv': return gen_fpv.gen_fpv(obj, outdir) src_lic = None src_copy = '' found_spdx = None found_lunder = None copy = re.compile(r'.*(copyright.*)|(.*\(c\).*)', re.IGNORECASE) spdx = re.compile(r'.*(SPDX-License-Identifier:.+)') lunder = re.compile(r'.*(Licensed under.+)', re.IGNORECASE) for line in srcfull.splitlines(): mat = copy.match(line) if mat is not None: src_copy += mat.group(1) mat = spdx.match(line) if mat is not None: found_spdx = mat.group(1) mat = lunder.match(line) if mat is not None: found_lunder = mat.group(1) if found_lunder: src_lic = found_lunder if found_spdx: src_lic += '\n' + found_spdx with outfile: if format == 'html': return gen_html.gen_html(obj, outfile) elif format == 'cdh': return gen_cheader.gen_cdefines(obj, outfile, src_lic, src_copy) else: return gen_json.gen_json(obj, outfile, format) outfile.write('\n')
def generate_clkmgr(top, cfg_path, out_path): # Target paths rtl_path = out_path / 'ip/clkmgr/rtl/autogen' rtl_path.mkdir(parents=True, exist_ok=True) data_path = out_path / 'ip/clkmgr/data/autogen' data_path.mkdir(parents=True, exist_ok=True) # Template paths hjson_tpl = cfg_path / '../ip/clkmgr/data/clkmgr.hjson.tpl' rtl_tpl = cfg_path / '../ip/clkmgr/data/clkmgr.sv.tpl' pkg_tpl = cfg_path / '../ip/clkmgr/data/clkmgr_pkg.sv.tpl' hjson_out = data_path / 'clkmgr.hjson' rtl_out = rtl_path / 'clkmgr.sv' pkg_out = rtl_path / 'clkmgr_pkg.sv' tpls = [hjson_tpl, rtl_tpl, pkg_tpl] outputs = [hjson_out, rtl_out, pkg_out] names = ['clkmgr.hjson', 'clkmgr.sv', 'clkmgr_pkg.sv'] # A dictionary of the aon attribute for easier lookup. src_aon_attr[C] is # True if clock C is always-on and False otherwise. src_aon_attr = { src['name']: (src['aon'] == 'yes') for src in (top['clocks']['srcs'] + top['clocks']['derived_srcs']) } # Classify the various clock signals. Here, we build the following # dictionaries, each mapping the derived clock name to its source. # # ft_clks: Clocks fed through clkmgr but are not disturbed in any way. # This maintains the clocking structure consistency. # This includes two groups of clocks: # - Clocks fed from the always-on source # - Clocks fed to the powerup group # # rg_clks: Non-feedthrough clocks that have no software control. These # clocks are root-gated and the root-gated clock is then exposed # directly in clocks_o. # # sw_clks: Non-feedthrough clocks that have direct software control. These # are root-gated, but (unlike rg_clks) then go through a second # clock gate which is controlled by software. # # hints: Non-feedthrough clocks that have "hint" software control (with a # feedback mechanism to allow blocks to avoid being suspended when # they are not idle). ft_clks = {} rg_clks = {} sw_clks = {} hints = {} # We also build rg_srcs_set, which is the set of non-always-on clock sources # that are exposed without division. This doesn't include clock sources # that are only used to derive divided clocks (we might gate the divided # clocks, but don't bother gating the upstream source). rg_srcs_set = set() for grp in top['clocks']['groups']: if grp['name'] == 'powerup': # All clocks in the "powerup" group are considered feed-throughs. ft_clks.update(grp['clocks']) continue for clk, src in grp['clocks'].items(): if src_aon_attr[src]: # Any always-on clock is a feedthrough ft_clks[clk] = src continue rg_srcs_set.add(src) if grp['sw_cg'] == 'no': # A non-feedthrough clock with no software control rg_clks[clk] = src continue if grp['sw_cg'] == 'yes': # A non-feedthrough clock with direct software control sw_clks[clk] = src continue # The only other valid value for the sw_cg field is "hint", which # means a non-feedthrough clock with "hint" software control. assert grp['sw_cg'] == 'hint' hints[clk] = src continue # hint clocks dict. # # The clock is constructed as clk_{src_name}_{module_name}. So to get the # module name we split from the right and pick the last entry hint_clks = { clk: { 'name': clk.rsplit('_', 1)[-1], 'src': src } for clk, src in hints.items() } # The names of blocks that use one or more sw hint clocks (clkmgr has an # "idle" feedback signal from each), in ascending order. hint_blocks = sorted(set([v['name'] for v in hint_clks.values()])) # Define a canonical ordering for rg_srcs rg_srcs = sorted(rg_srcs_set) for idx, tpl in enumerate(tpls): out = "" with tpl.open(mode='r', encoding='UTF-8') as fin: tpl = Template(fin.read()) try: out = tpl.render(cfg=top, div_srcs=top['clocks']['derived_srcs'], rg_srcs=rg_srcs, ft_clks=ft_clks, rg_clks=rg_clks, sw_clks=sw_clks, export_clks=top['exported_clks'], hint_clks=hint_clks, hint_blocks=hint_blocks) except: # noqa: E722 log.error(exceptions.text_error_template().render()) if out == "": log.error("Cannot generate {}".format(names[idx])) return with outputs[idx].open(mode='w', encoding='UTF-8') as fout: fout.write(genhdr + out) # Generate reg files gen_rtl.gen_rtl(IpBlock.from_path(str(hjson_out), []), str(rtl_path))