| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| '''Generate DV code for an IP block''' |
| |
| import logging as log |
| import os |
| import sys |
| from collections import defaultdict |
| from typing import Dict, List, Union, Optional |
| |
| import yaml |
| |
| from mako import exceptions # type: ignore |
| from mako.lookup import TemplateLookup # type: ignore |
| from pkg_resources import resource_filename |
| |
| from reggen.ip_block import IpBlock |
| from reggen.multi_register import MultiRegister |
| from reggen.register import Register |
| from reggen.window import Window |
| |
| |
| class DvBaseNames: |
| # Class global attributes |
| valid_types = ["pkg", "block", "reg", "field", "mem", "all"] |
| |
| def __init__(self) -> None: |
| self.with_prefix("dv_base") |
| |
| def with_prefix(self, prefix: str) -> None: |
| self.pkg = prefix + "_reg_pkg" |
| self.block = prefix + "_reg_block" |
| self.reg = prefix + "_reg" |
| self.field = prefix + "_reg_field" |
| self.mem = prefix + "_mem" |
| |
| def set_entity(self, base_type: str, entity: str) -> None: |
| assert base_type in self.valid_types, f"Invalid argument type: {base_type}" |
| if base_type == "all": |
| self.with_prefix(entity) |
| else: |
| setattr(self, base_type, entity) |
| |
| |
| def bcname(esc_if_name: str) -> str: |
| '''Get the name of the dv_base_reg_block subclass for this device interface''' |
| return esc_if_name + "_reg_block" |
| |
| |
| def rcname(esc_if_name: str, r: Union[Register, MultiRegister]) -> str: |
| '''Get the name of the dv_base_reg subclass for this register''' |
| return '{}_reg_{}'.format(esc_if_name, r.name.lower()) |
| |
| |
| def alias_rcname(esc_if_name: str, |
| r: Union[Register, MultiRegister]) -> Optional[str]: |
| '''Get the name of the dv_base_reg subclass for this alias register''' |
| if r.alias_target is not None: |
| return '{}_reg_{}'.format(esc_if_name, r.alias_target.lower()) |
| else: |
| return None |
| |
| |
| def mcname(esc_if_name: str, m: Window) -> str: |
| '''Get the name of the dv_base_mem subclass for this memory''' |
| return '{}_mem_{}'.format(esc_if_name, m.name.lower()) |
| |
| |
| def miname(m: Window) -> str: |
| '''Get the lower-case name of a memory block''' |
| return m.name.lower() |
| |
| |
| def gen_core_file(outdir: str, |
| lblock: str, |
| dv_base_names: List[str], |
| paths: List[str]) -> None: |
| depends = ["lowrisc:dv:dv_base_reg"] |
| blocks_base_names = get_dv_base_names_objects(dv_base_names) |
| |
| if blocks_base_names is not None: |
| # Assume the core file naming convetion is the package name without `_pkg` |
| # suffix. |
| for block in blocks_base_names: |
| pkg_name = blocks_base_names[block].pkg |
| depends.append("lowrisc:dv:{}".format(pkg_name[:-4])) |
| |
| # Generate a fusesoc core file that points at the files we've just |
| # generated. |
| core_data = { |
| 'name': "lowrisc:dv:{}_ral_pkg".format(lblock), |
| 'filesets': { |
| 'files_dv': { |
| 'depend': depends, |
| 'files': paths, |
| 'file_type': 'systemVerilogSource' |
| }, |
| }, |
| 'targets': { |
| 'default': { |
| 'filesets': [ |
| 'files_dv', |
| ], |
| }, |
| }, |
| } |
| core_file_path = os.path.join(outdir, lblock + '_ral_pkg.core') |
| with open(core_file_path, 'w') as core_file: |
| core_file.write('CAPI=2:\n') |
| yaml.dump(core_data, core_file, encoding='utf-8') |
| |
| |
| def get_dv_base_names_objects(dv_base_names: List[str]) -> Dict[str, DvBaseNames]: |
| '''Returns a dictionary mapping a `DvBaseNames` object to a block. |
| |
| `dv_bave_names` is a list of base class entity names provided on the command-line, in the |
| following format: |
| ast:block:ast_base_reg_block ast:pkg:ast_base_reg_pkg otp_ctrl:all:otp_ctrl_base |
| |
| This function creates a dictionary that wraps the provided base class overrides for each block |
| within a `DvBaseNames` object and returns a dictionary mapping the object to the block. |
| ''' |
| if dv_base_names is None: |
| return None |
| |
| dv_base_names_dict = defaultdict(DvBaseNames) # type: Dict[str, DvBaseNames] |
| for item in dv_base_names: |
| try: |
| block, base_type, entity = item.split(":") |
| except ValueError: |
| log.error(f"Bad input arg: {item}") |
| sys.exit(1) |
| dv_base_names_dict[block].set_entity(base_type, entity) |
| return dv_base_names_dict |
| |
| |
| def get_block_base_name(dv_base_names_map: Dict[str, DvBaseNames], block: str) -> DvBaseNames: |
| '''Given a dictionary of `DvBaseNames` and return a `DvBaseNames` object for a specific block. |
| |
| If the given dictionary is empty, or cannot find the block name in the list of dictionary keys, |
| this function will return the default `DvBaseNames` object. |
| ''' |
| if dv_base_names_map is None: |
| return DvBaseNames() |
| try: |
| return dv_base_names_map[block] |
| except KeyError: |
| return DvBaseNames() |
| |
| |
| def gen_dv(block: IpBlock, dv_base_names: List[str], outdir: str) -> int: |
| '''Generate DV files for an IpBlock''' |
| |
| lookup = TemplateLookup(directories=[resource_filename('reggen', '.')]) |
| uvm_reg_tpl = lookup.get_template('uvm_reg.sv.tpl') |
| |
| # Generate the RAL package(s). For a device interface with no name we |
| # generate the package "<block>_ral_pkg" (writing to <block>_ral_pkg.sv). |
| # In any other case, we also need the interface name, giving |
| # <block>_<ifname>_ral_pkg. |
| generated = [] |
| |
| lblock = block.name.lower() |
| dv_base_names_map = get_dv_base_names_objects(dv_base_names) |
| block_dv_base_names = get_block_base_name(dv_base_names_map, lblock) |
| device_hier_paths = block.bus_interfaces.device_hier_paths |
| |
| for if_name, rb in block.reg_blocks.items(): |
| |
| hier_path = device_hier_paths[if_name] |
| if_suffix = '' if if_name is None else '_' + if_name.lower() |
| mod_base = lblock + if_suffix |
| reg_block_path = hier_path + if_suffix |
| |
| file_name = mod_base + '_ral_pkg.sv' |
| generated.append(file_name) |
| reg_top_path = os.path.join(outdir, file_name) |
| with open(reg_top_path, 'w', encoding='UTF-8') as fout: |
| try: |
| fout.write(uvm_reg_tpl.render(rb=rb, |
| block=block, |
| esc_if_name=mod_base, |
| reg_block_path=reg_block_path, |
| dv_base_names=block_dv_base_names)) |
| except: # noqa F722 for template Exception handling |
| log.error(exceptions.text_error_template().render()) |
| return 1 |
| |
| gen_core_file(outdir, lblock, dv_base_names, generated) |
| return 0 |