Dennis 9deb28ce4e
Add initial version of widget-code and fix remaining SV compiler errors
This adds initial support for a dynamic bus-protocol to internal
register logic SHIM. The chosen default protocol at this point is AMBA 3
AHB Lite and the logic is still empty.

    -> TODO: Adding the widget instantiation showed that it is required
    to have a better interface to parametrize ports & signals in the
    YAML. At this point, only a limited set of variables are supported.

Furthermore, all remaining Verilator compilation issues in the field are
resolved. Those were mostly related to non-declared wires and wrongly named
wires.
2021-06-05 15:37:09 +02:00

329 lines
12 KiB
Python

import re
import importlib.resources as pkg_resources
from sys import exit
import yaml
from systemrdl import node
from systemrdl.node import FieldNode
# Local packages
from components.component import Component
from components.regfile import RegFile
from components.register import Register
from . import templates
from . import widgets
class AddrMap(Component):
# Save YAML template as class variable
templ_dict = yaml.load(
pkg_resources.read_text(templates, 'addrmap.yaml'),
Loader=yaml.FullLoader)
def __init__(self, obj: node.RootNode, config: dict):
super().__init__(obj, config)
# Create logger object
self.create_logger(self.path, config)
self.logger.debug('Starting to process addrmap')
# Check if global resets are defined
glbl_settings = dict()
(glbl_settings['field_reset'], glbl_settings['cpuif_reset']) = \
self.__process_global_resets()
# Set defaults so that some of the common component methods work
self.total_dimensions = 0
self.total_array_dimensions = []
# Use global settings to define whether a component is already in a generate block
glbl_settings['generate_active'] = False
# Empty dictionary of register objects
# We need a dictionary since it might be required to access the objects later
# by name (for example, in case of aliases)
self.registers = dict()
self.regfiles = []
# Traverse through children
for child in obj.children():
if isinstance(child, node.AddrmapNode):
# This addressmap opens a completely new scope. For example,
# a field_reset does not propagate through to this scope.
self.logger.info('Found hierarchical addrmap. Entering it...')
self.logger.error('Child addrmaps are not implemented yet!')
elif isinstance(child, node.RegfileNode):
self.regfiles.append(RegFile(child, [], [], config, glbl_settings))
elif isinstance(child, node.RegNode):
if child.inst.is_alias:
# If the node we found is an alias, we shall not create a
# new register. Rather, we bury up the old register and add
# additional properties
self.logger.error('Alias registers are not implemented yet!')
else:
self.registers[child.inst_name] = \
Register(child, [], [], config, glbl_settings)
# Add registers to children. This must be done in a last step
# to account for all possible alias combinations
self.children = [
*self.regfiles,
*[x for x in self.registers.values()]
]
self.logger.info("Done generating all child-regfiles/registers")
# Add bus widget ports
self.__add_bus_widget_ports()
# Start assembling addrmap module
self.logger.info("Starting to assemble input & output ports")
# Reset ports
reset_ports_rtl = [
AddrMap.templ_dict['reset_port']['rtl'].format(
name = name)
for name in [x for x in self.get_resets()]
]
# Prefetch dictionaries in local array
input_dict_list = [(key, value) for (key, value) in self.get_ports('input').items()]
output_dict_list = [(key, value) for (key, value) in self.get_ports('output').items()]
input_signal_width = min(
max([len(value[0]) for (_, value) in input_dict_list]), 40)
input_name_width = min(
max([len(key) for (key, _) in input_dict_list]), 40)
output_signal_width = min(
max([len(value[0]) for (_, value) in output_dict_list]), 40)
output_name_width = min(
max([len(key) for (key, _) in output_dict_list]), 40)
# Input ports
input_ports_rtl = [
AddrMap.templ_dict['input_port']['rtl'].format(
name = key,
signal_type = value[0],
signal_width = input_signal_width,
name_width = input_name_width,
unpacked_dim = '[{}]'.format(
']['.join(
[str(y) for y in value[1]]))
if value[1] else '')
for (key, value) in input_dict_list
]
# Output ports
output_ports_rtl = [
AddrMap.templ_dict['output_port']['rtl'].format(
name = key,
signal_width = output_signal_width,
name_width = output_name_width,
signal_type = value[0],
unpacked_dim = '[{}]'.format(
']['.join(
[str(y) for y in value[1]]))
if value[1] else '')
for (key, value) in output_dict_list
]
# Remove comma from last port entry
output_ports_rtl[-1] = output_ports_rtl[-1].rstrip(',')
import_package_list = []
try:
import_package_list = [[
AddrMap.templ_dict['import_package']['rtl'].format(
name = self.name),
',\n'
] for x in self.get_package_names()][0][:-1]
import_package_list.append(';')
except IndexError:
pass
self.rtl_header.append(
AddrMap.templ_dict['module_declaration']['rtl'].format(
name = self.name,
import_package_list = ''.join(import_package_list),
resets = '\n'.join(reset_ports_rtl),
inputs = '\n'.join(input_ports_rtl),
outputs = '\n'.join(output_ports_rtl)))
# Add wire/register instantiations
self.__add_signal_instantiation()
# Add bus widget RTL
self.__add_bus_widget_instantiation()
# Append genvars
self.__append_genvars()
# Add endmodule keyword
self.rtl_footer.append('endmodule')
def __add_signal_instantiation(self):
dict_list = [(key, value) for (key, value) in self.get_signals(True).items()]
signal_width = min(max([len(value[0]) for (_, value) in dict_list]), 40)
name_width = min(max([len(key) for (key, _) in dict_list]), 40)
self.rtl_header = [
*self.rtl_header,
'',
'// Internal signals',
*[AddrMap.templ_dict['signal_declaration'].format(
name = key,
type = value[0],
signal_width = signal_width,
name_width = name_width,
unpacked_dim = '[{}]'.format(
']['.join(
[str(y) for y in value[1]]))
if value[1] else '')
for (key, value) in dict_list],
''
]
def __add_bus_widget_ports(self):
self.widget_templ_dict = yaml.load(
pkg_resources.read_text(widgets, '{}.yaml'.format(self.config['bus'])),
Loader=yaml.FullLoader)
self.yaml_signals_to_list(self.widget_templ_dict['module_instantiation'])
def __add_bus_widget_instantiation(self):
self.rtl_header.append(
self.widget_templ_dict['module_instantiation']['rtl'])
def __append_genvars(self):
genvars = ''.join([
'\ngenvar ',
', '.join([chr(97+i) for i in range(self.get_max_dim_depth())]),
';\n'
])
self.rtl_header.append(genvars)
def __process_global_resets(self):
field_reset_list = \
[x for x in self.obj.signals() if x.get_property('field_reset')]
cpuif_reset_list = \
[x for x in self.obj.signals() if x.get_property('cpuif_reset')]
if field_reset_list:
rst_name = field_reset_list[0].inst_name
self.logger.info("Found field_reset signal '{}'".format(rst_name))
# Save to set to generate input
self.resets.add(rst_name)
# Save position 0 of list
field_reset_item = field_reset_list[0]
else:
field_reset_item = None
if cpuif_reset_list:
rst_name = cpuif_reset_list[0].inst_name
self.logger.info("Found cpuif_reset signal '{}'".format(rst_name))
# Save to set to generate input
self.resets.add(rst_name)
# Save position 0 of list
cpuif_reset_item = cpuif_reset_list[0]
else:
cpuif_reset_item = None
# Method is only called once on a global level. Otherwise, process_reset_signal
# is called several times to calculate the dictionary, although it will always
# return the same result.
field_reset = AddrMap.process_reset_signal(field_reset_item)
cpuif_reset = AddrMap.process_reset_signal(cpuif_reset_item)
return (field_reset, cpuif_reset)
def get_package_names(self) -> set():
names = set()
for i in self.registers.values():
for key, value in i.get_typedefs().items():
names.add(value.scope)
return names
def get_package_rtl(self, tab_width: int = 4, real_tabs = False) -> dict():
if not self.config['enums']:
return dict()
# First go through all registers in this scope to generate a package
package_rtl = []
enum_rtl = []
rtl_return = dict()
# Need to keep track of enum names since they shall be unique
# per scope
enum_members = dict()
for i in self.registers.values():
for key, value in i.get_typedefs().items():
variable_list = []
max_name_width = min(
max([len(x[0]) for x in value.members]), 40)
for var in value.members:
if var[0] not in enum_members:
enum_members[var[0]] = "::".join([self.name, key])
else:
self.logger.fatal(
"Enum member '{}' was found at multiple locations in the same "\
"main scope: \n"\
" -- 1st occurance: '{}'\n"\
" -- 2nd occurance: '{}'\n\n"\
"This is not legal because all these enums will be defined "\
"in the same SystemVerilog scope. To share the same enum among "\
"different registers, define them on a higher level in the "\
"hierarchy.\n\n"\
"Exiting...".format(
var[0],
enum_members[var[0]],
"::".join([self.name, key])))
exit(1)
variable_list.append(
AddrMap.templ_dict['enum_var_list_item']['rtl'].format(
value = var[1],
width = value.width,
max_name_width = max_name_width,
name = var[0]))
enum_rtl.append(
AddrMap.templ_dict['enum_declaration']['rtl'].format(
width=value.width-1,
name = key,
enum_var_list = ',\n'.join(variable_list)))
package_rtl =\
AddrMap.templ_dict['package_declaration']['rtl'].format(
name = self.name,
pkg_content = '\n\n'.join(enum_rtl))
rtl_return[self.name] = AddrMap.add_tabs(
package_rtl,
tab_width,
real_tabs)
# TODO Later, request get_package_rtl()-method of all child regfiles
return rtl_return