srdl2sv/srdl2sv/components/component.py

359 lines
11 KiB
Python

import re
import sys
from itertools import chain
from typing import NamedTuple
from systemrdl import node
from dataclasses import dataclass
# Local modules
from log.log import create_logger
# Define NamedTuple
class TypeDef(NamedTuple):
scope: str
width: int
members: tuple
@dataclass
class SWMuxEntry:
data_wire: str
rdy_wire: str
err_wire: str
active_wire: str
@dataclass
class SWMuxEntryDimensioned():
mux_entry: SWMuxEntry
dim: str
class Component():
def __init__(self, obj, config):
self.rtl_header = []
self.rtl_footer = []
self.children = dict()
self.typedefs = dict()
self.ports = dict()
self.resets = set()
self.signals = dict()
self.ports['input'] = dict()
self.ports['output'] = dict()
self.field_type = ''
# Save object
# TODO: should probably be list because of alias registers
self.obj = obj
# Save name
self.name = obj.inst_name
# Create path
self.create_underscored_path()
# Save config
self.config = config.copy()
# By default, registers and fields are not interrupt registers
self.properties = {
'intr': False,
'halt': False,
'swmod': False,
'swacc': False,
'sw_rd': False,
'sw_wr': False,
'sw_rd_wire': False,
'sw_wr_wire': False,
}
# Create logger object
self.create_logger("{}".format(self.full_path), config)
self.logger.debug('Starting to process {} "{}"'.format(
self.__class__.__name__,
obj.inst_name))
def create_logger(self, name: str, config: dict):
self.logger = create_logger(
"{}".format(name),
stream_log_level=config['stream_log_level'],
file_log_level=config['file_log_level'],
file_name=config['file_log_location'])
self.logger.propagate = False
def get_resets(self):
self.logger.debug("Return reset list")
for x in self.children.values():
self.resets |= x.get_resets()
return self.resets
def get_ports(self, port_type: str):
self.logger.debug("Return port list")
for x in self.children.values():
self.ports[port_type] |= x.get_ports(port_type)
return self.ports[port_type]
def get_max_dim_depth(self) -> int:
self.logger.debug("Return depth '{}' for dimensions (including "\
"parents) '{}'".format(self.total_dimensions,
self.total_array_dimensions))
return max([
self.total_dimensions,
*[x.get_max_dim_depth() for x in self.children.values()]
])
def get_signals(self, no_children = False):
self.logger.debug("Return signal list")
if not no_children:
for x in self.children.values():
self.signals |= x.get_signals()
return self.signals
def get_typedefs(self):
self.logger.debug("Return typedef list")
for x in self.children.values():
self.typedefs |= x.get_typedefs()
return self.typedefs
def get_rtl(self, tab_width: int = 0, real_tabs: bool = False) -> str:
self.logger.debug("Return RTL")
# Loop through children and append RTL
rtl_children = []
for x in self.children.values():
rtl_children.append(x.get_rtl())
# Concatenate header, main, and footer
rtl = [*self.rtl_header, *rtl_children, *self.rtl_footer]
# Join lists and return string
if tab_width > 0:
return Component.add_tabs(
'\n'.join(rtl),
tab_width,
real_tabs)
return '\n'.join(rtl)
@staticmethod
def add_tabs(rtl: str, tab_width: int = 4, real_tabs = False) -> str:
indent_lvl = 0
indent_lvl_next = 0
# Define tab style
tab = "\t" if real_tabs else " "
tab = tab_width * tab
# Define triggers for which the indentation level will increment or
# decrement on the next line
trigger_re = re.compile(r"""
.*?(
(?:\bbegin\b|\{|\bcase\b|<<INDENT>>)|
(?:\bend\b|}|\bendcase\b|<<UNINDENT>>)
)([^$]*)
""", flags=re.VERBOSE)
rtl_indented = []
# Go through RTL, line by line
for line in rtl.split('\n', -1):
line_split = line
# This is done because the increment of the indent level must
# be delayed one cycle
indent_lvl = indent_lvl_next
while 1:
# Check if indentation must be decremented
matchObj = trigger_re.match(line_split)
if matchObj:
if matchObj.group(1) in ('begin', '{', 'case', '<<INDENT>>'):
indent_lvl_next += 1
else:
indent_lvl = indent_lvl_next - 1
indent_lvl_next -= 1
line_split = matchObj.group(2)
if not line_split:
break
else:
break
# Add tabs
if line.strip() not in ("<<INDENT>>", "<<UNINDENT>>", "<<SQUASH_NEWLINE>>"):
rtl_indented.append("{}{}".format(tab*indent_lvl, line))
return '\n'.join(rtl_indented)
@staticmethod
def get_underscored_path(path: str, owning_addrmap: str):
return path\
.replace('[]', '')\
.replace('{}.'.format(owning_addrmap), '')\
.replace('.', '__')
@staticmethod
def split_dimensions(path: str):
re_dimensions = re.compile('(\[[^]]*\])')
new_path = re_dimensions.sub('', path)
return (new_path, ''.join(re_dimensions.findall(path)))
def get_signal_name(self, obj):
name = []
try:
child_obj = obj.node
except AttributeError:
child_obj = obj
split_name = Component.split_dimensions(
Component.get_underscored_path(
child_obj.get_path(),
child_obj.owning_addrmap.inst_name)
)
name.append(split_name[0])
if isinstance(obj, node.FieldNode):
name.append('_q')
elif isinstance(obj, node.SignalNode):
# Must add it to signal list
self.ports['input'][obj.inst_name] =\
("logic" if obj.width == 1 else 'logic [{}:0]'.format(obj.width), [])
else:
name.append('_')
name.append(obj.name)
# This is a property. Check if the original field actually has this property
if obj.name == "intr" or obj.name == "halt":
pass
elif not obj.node.get_property(obj.name):
self.logger.fatal("Reference to the property '{}' of instance '{}' found. "
"This instance does hold the reference property! Please "
"fix this if you want me to do my job properly."
.format(obj.name, obj.node.get_path()))
sys.exit(1)
name.append(split_name[1])
return ''.join(name)
def process_yaml(self,
yaml_obj,
values: dict = {},
skip_signals: bool = False,
skip_inputs: bool = False,
skip_outputs: bool = False):
try:
if skip_signals:
raise KeyError
for x in yaml_obj['signals']:
try:
array_dimensions = [] if x['no_unpacked'] else self.total_array_dimensions
except KeyError:
array_dimensions = self.total_array_dimensions
self.signals[x['name'].format(**values)] =\
(x['signal_type'].format(**values),
array_dimensions)
except (TypeError, KeyError):
pass
try:
if skip_inputs:
raise KeyError
for x in yaml_obj['input_ports']:
try:
array_dimensions = [] if x['no_unpacked'] else self.total_array_dimensions
except KeyError:
array_dimensions = self.total_array_dimensions
self.ports['input'][x['name'].format(**values)] =\
(x['signal_type'].format(**values),
array_dimensions)
except (TypeError, KeyError):
pass
try:
if skip_outputs:
raise KeyError
for x in yaml_obj['output_ports']:
try:
array_dimensions = [] if x['no_unpacked'] else self.total_array_dimensions
except KeyError:
array_dimensions = self.total_array_dimensions
self.ports['output'][x['name'].format(**values)] =\
(x['signal_type'].format(**values),
array_dimensions)
except (TypeError, KeyError):
pass
# Return RTL with values
return yaml_obj['rtl'].format(**values)
@staticmethod
def process_reset_signal(reset_signal):
rst = dict()
try:
rst['name'] = reset_signal.inst_name
rst['async'] = reset_signal.get_property("async")
rst['type'] = "asynchronous" if rst['async'] else "synchronous"
# Active low or active high?
if reset_signal.get_property("activelow"):
rst['edge'] = "negedge"
rst['active'] = "active_low"
else:
rst['edge'] = "posedge"
rst['active'] = "active_high"
except:
rst['async'] = False
rst['name'] = None
rst['edge'] = None
rst['value'] = "'x"
rst['active'] = "-"
rst['type'] = "-"
return rst
def create_underscored_path(self):
self.owning_addrmap, self.full_path, self.path, self.path_underscored =\
Component.create_underscored_path_static(self.obj)
@staticmethod
def create_underscored_path_static(obj):
owning_addrmap = obj.owning_addrmap.inst_name
full_path = Component.split_dimensions(obj.get_path())[0]
path = full_path\
.replace('{}.'.format(owning_addrmap), '')
path_underscored = path.replace('.', '__')
return (owning_addrmap, full_path, path, path_underscored)
def get_description(self):
if self.config['descriptions'][self.__class__.__name__]:
if desc := self.obj.get_property('desc'):
return self.process_yaml(
self.templ_dict['description'],
{'desc': desc},
)
return ''