blob: 793d1b35cfc4561a0c8cf141bd40398030681e25 [file] [log] [blame]
# 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 .ip_block import IpBlock
from .multi_register import MultiRegister
from .register import Register
from .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