Fundamental changes to the architecture of component classes

All components (e.g., fields, registers, addrmaps) are now children
of a common class Component. This common class has certain common
methods such as get_ports(), get_rtl(), or create_logger().

The AddrMap is now prepared to support alias registers by saving the
registers in a dictionary. That way, registers can easily be accessed
once an alias to a register is found. Furthermore, the addrmap template
is now also loaded from a YAML file. Lastly, the first preparements to
insert ports into the addrmap module are made.

For templates, the indents do not need to be added anymore to the
template. Now, a seperate method will automatically indent the RTL
based on simple rules (e.g., increment indent if `begin` is found).
The CLI also supports settings for the tabs (i.e., real tabs or spaces
and the tab width).

A lot of functionality from the __init__() method of the field class
got reorganized. More logic will be reorganized in the future.
This commit is contained in:
Dennis Potter 2021-05-15 00:29:59 +02:00
parent 59b91536ed
commit cecb73f07a
Signed by: Dennis
GPG Key ID: 186A8AD440942BAF
11 changed files with 487 additions and 276 deletions

View File

@ -19,20 +19,6 @@ class CliArguments():
self.parser = argparse.ArgumentParser( self.parser = argparse.ArgumentParser(
description="SystemRDL 2 SystemVerilog compiler") description="SystemRDL 2 SystemVerilog compiler")
self.parser.add_argument(
"--stream_log_level",
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'NONE'],
default='WARNING',
help="Set verbosity level of output to shell. When set to 'NONE',\
nothing will be printed to the shell. (default: %(default)s)")
self.parser.add_argument(
"--file_log_level",
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'NONE'],
default='INFO',
help="Set verbosity level of output to log-file. When set to 'NONE',\
nothing will be printed to the shell. (default: %(default)s)")
self.parser.add_argument( self.parser.add_argument(
"-o", "-o",
"--out_dir", "--out_dir",
@ -55,7 +41,41 @@ class CliArguments():
"--recursive_search", "--recursive_search",
action="store_true", action="store_true",
help="If set, the dependency directories will be\ help="If set, the dependency directories will be\
searched recursively."); searched recursively.")
self.parser.add_argument(
"-x",
"--disable_sanity",
action="store_true",
help="Disable sanity checks or components. This might speed\
up the compiler but is generally not recommended!")
self.parser.add_argument(
"--stream_log_level",
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'NONE'],
default='WARNING',
help="Set verbosity level of output to shell. When set to 'NONE',\
nothing will be printed to the shell. (default: %(default)s)")
self.parser.add_argument(
"--file_log_level",
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'NONE'],
default='INFO',
help="Set verbosity level of output to log-file. When set to 'NONE',\
nothing will be printed to the shell. (default: %(default)s)")
self.parser.add_argument(
"--real_tabs",
action="store_true",
help="Use tabs, rather than spaces, for tabs")
self.parser.add_argument(
"--tab_width",
type=int,
default=4,
help="Define how many tabs or spaces will be contained\
in one level of indentation. (default: %(default)s)")
self.parser.add_argument( self.parser.add_argument(
"IN_RDL", "IN_RDL",
@ -91,4 +111,11 @@ class CliArguments():
ts = time.strftime('%Y%m%d_%H%M%S', config['ts']) ts = time.strftime('%Y%m%d_%H%M%S', config['ts'])
config['file_log_location'] = "srdl2sv_{}.log".format(ts) config['file_log_location'] = "srdl2sv_{}.log".format(ts)
# Tab style
config['real_tabs'] = args.real_tabs
config['tab_width'] = args.tab_width
# Sanity check related
config['disable_sanity'] = args.disable_sanity
return config return config

View File

@ -1,56 +1,101 @@
import yaml
import re import re
import importlib.resources as pkg_resources
import yaml
from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node
from systemrdl.node import FieldNode from systemrdl.node import FieldNode
# Local packages # Local packages
from components.component import Component
from components.register import Register from components.register import Register
from log.log import create_logger from log.log import create_logger
from . import templates from . import templates
# Import templates
try:
import importlib.resources as pkg_resources
except ImportError:
# Try backported to PY<37 `importlib_resources`.
import importlib_resources as pkg_resources
class AddrMap: class AddrMap(Component):
def __init__(self, rdlc: RDLCompiler, obj: node.RootNode, config: dict): # Save YAML template as class variable
templ_dict = yaml.load(
pkg_resources.read_text(templates, 'addrmap.yaml'),
Loader=yaml.FullLoader)
self.rdlc = rdlc def __init__(self, obj: node.RootNode, config: dict):
self.name = obj.inst_name super().__init__()
# Save and/or process important variables
self.__process_variables(obj)
# Create logger object # Create logger object
self.logger = create_logger( self.create_logger(self.path, config)
"{}.{}".format(__name__, obj.inst_name), self.logger.debug('Starting to process addrmap')
stream_log_level=config['stream_log_level'],
file_log_level=config['file_log_level'],
file_name=config['file_log_location'])
self.logger.debug('Starting to process addrmap "{}"'.format(obj.inst_name))
template = pkg_resources.read_text(templates, 'addrmap.sv') template = pkg_resources.read_text(templates, 'addrmap.sv')
# Read template for SystemVerilog module # Empty dictionary of register objects
tmpl_addrmap = re.compile("{addrmap_name}") # We need a dictionary since it might be required to access the objects later
self.rtl = tmpl_addrmap.sub(obj.inst_name, template) # by name (for example, in case of aliases)
self.registers = dict()
# Empty list of register logic
self.registers = set()
# Traverse through children # Traverse through children
for child in obj.children(): for child in obj.children():
if isinstance(child, node.AddrmapNode): if isinstance(child, node.AddrmapNode):
pass self.logger.info('Found hierarchical addrmap. Entering it...')
self.logger.error('Child addrmaps are not implemented yet!')
elif isinstance(child, node.RegfileNode): elif isinstance(child, node.RegfileNode):
pass self.logger.error('Regfiles are not implemented yet!')
elif isinstance(child, node.RegNode): elif isinstance(child, node.RegNode):
self.registers.add(Register(child, config)) 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)
# TODO: Temporarily override RTL # Add regfiles and registers to children
self.rtl = [x.get_rtl() for x in self.registers] self.children = [x for x in self.registers.values()]
def get_rtl(self) -> str: self.logger.info("Done generating all child-regfiles/registers")
return '\n'.join(self.rtl)
# Start assembling addrmap module
self.logger.info("Starting to assemble input/output/inout ports")
# Inout port
inout_ports_rtl = [
AddrMap.templ_dict['inout_port'].format(
name = x) for x in self.get_ports('inout')]
# Input ports
input_ports_rtl = [
AddrMap.templ_dict['input_port'].format(
name = x) for x in self.get_ports('input')]
# Output ports
output_ports_rtl = [
AddrMap.templ_dict['output_port'].format(
name = x) for x in self.get_ports('output')]
# Remove comma from last port entry
output_ports_rtl[-1] = output_ports_rtl[-1].rstrip(',')
self.rtl_header.append(
AddrMap.templ_dict['module_declaration'].format(
name = obj.inst_name,
inouts = '\n'.join(inout_ports_rtl),
inputs = '\n'.join(input_ports_rtl),
outputs = '\n'.join(output_ports_rtl)))
def __process_variables(self, obj: node.RootNode):
# Save object
self.obj = obj
# Create full name
self.owning_addrmap = obj.owning_addrmap.inst_name
self.path = obj.get_path()\
.replace('[]', '')\
.replace('{}.'.format(self.owning_addrmap), '')
self.path_underscored = self.path.replace('.', '_')
self.name = obj.inst_name

View File

@ -0,0 +1,85 @@
import re
from itertools import chain
# Local modules
from log.log import create_logger
class Component():
def __init__(self):
self.rtl_header = []
self.rtl_footer = []
self.children = []
self.ports = dict()
self.ports['input'] = []
self.ports['output'] = []
self.ports['inout'] = []
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'])
def get_ports(self, port_type: str):
self.logger.debug("Return port list")
return [
*self.ports[port_type],
*list(chain(*[x.get_ports(port_type) for x in self.children]))
]
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 child in self.children:
rtl_children.append(child.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
# 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
incr_trigger = re.compile('\\bbegin\\b')
decr_trigger = re.compile('\\bend\\b')
rtl_indented = []
# Go through RTL, line by line
for line in rtl.split('\n', -1):
skip_incr_check = False
# Check if indentation must be decremented
if decr_trigger.search(line):
indent_lvl -= 1
skip_incr_check = True
# Add tabs
rtl_indented.append("{}{}".format(tab*indent_lvl, line))
# Check if tab level must be incremented
if skip_incr_check:
continue
elif incr_trigger.search(line):
indent_lvl += 1
return '\n'.join(rtl_indented)

View File

@ -1,123 +1,61 @@
import math import math
import importlib.resources as pkg_resources
import yaml import yaml
from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node
from systemrdl.node import FieldNode from systemrdl.node import FieldNode
from systemrdl.rdltypes import PrecedenceType, AccessType from systemrdl.rdltypes import PrecedenceType, AccessType
from itertools import chain
# Local modules # Local modules
from log.log import create_logger from log.log import create_logger
from components.component import Component
from . import templates
TAB = " " class Field(Component):
class Field:
# Save YAML template as class variable # Save YAML template as class variable
with open('srdl2sv/components/templates/fields.yaml', 'r') as file: templ_dict = yaml.load(
templ_dict = yaml.load(file, Loader=yaml.FullLoader) pkg_resources.read_text(templates, 'fields.yaml'),
Loader=yaml.FullLoader)
def __init__(self, obj: node.RootNode, indent_lvl: int, dimensions: int, config:dict): def __init__(self, obj: node.RootNode, dimensions: int, config:dict):
self.obj = obj super().__init__()
self.rtl = []
self.bytes = math.ceil(obj.width / 8) # Save and/or process important variables
self.__process_variables(obj, dimensions)
# Create logger object # Create logger object
self.logger = create_logger( self.create_logger("{}.{}".format(self.owning_addrmap, self.path), config)
"{}.{}".format(__name__, obj.inst_name),
stream_log_level=config['stream_log_level'],
file_log_level=config['file_log_level'],
file_name=config['file_log_location'])
self.logger.debug('Starting to process field "{}"'.format(obj.inst_name)) self.logger.debug('Starting to process field "{}"'.format(obj.inst_name))
# Make a list of I/O that shall be added to the addrmap
self.input_ports = []
self.output_ports = []
################################################################################## ##################################################################################
# LIMITATION: # LIMITATION:
# v1.x of the systemrdl-compiler does not support non-homogeneous arrays. # v1.x of the systemrdl-compiler does not support non-homogeneous arrays.
# It is planned, however, for v2.0.0 of the compiler. More information # It is planned, however, for v2.0.0 of the compiler. More information
# can be found here: https://github.com/SystemRDL/systemrdl-compiler/issues/51 # can be found here: https://github.com/SystemRDL/systemrdl-compiler/issues/51
################################################################################## ##################################################################################
# Print a summary
# Determine resets. This includes checking for async/sync resets, self.summary()
# the reset value, and whether the field actually has a reset
try:
rst_signal = obj.get_property("resetsignal")
rst_name = rst_signal.inst_name
rst_async = rst_signal.get_property("async")
rst_type = "asynchronous" if rst_async else "synchronous"
# Active low or active high?
if rst_signal.get_property("activelow"):
rst_edge = "negedge"
rst_negl = "!"
rst_active = "active_low"
else:
rst_edge = "posedge"
rst_negl = ""
rst_active = "active_high"
# Value of reset?
rst_value = '\'x' if obj.get_property("reset") == None else obj.get_property('reset')
except:
rst_async = False
rst_name = None
rst_negl = None
rst_edge = None
rst_value = "'x"
rst_active = "-"
rst_type = "-"
# Get certain properties
hw_access = obj.get_property('hw')
sw_access = obj.get_property('sw')
precedence = obj.get_property('precedence')
# Add comment with summary on field's properties
self.rtl.append(
Field.templ_dict['field_comment'].format(
name = obj.inst_name,
hw_access = str(hw_access)[11:],
sw_access = str(sw_access)[11:],
hw_precedence = '(precedence)' if precedence == PrecedenceType.hw else '',
sw_precedence = '(precedence)' if precedence == PrecedenceType.sw else '',
rst_active = rst_active,
rst_type = rst_type,
indent = self.indent(indent_lvl)))
# Handle always_ff # Handle always_ff
sense_list = 'sense_list_rst' if rst_async else 'sense_list_no_rst' sense_list = 'sense_list_rst' if self.rst['async'] else 'sense_list_no_rst'
self.rtl.append( self.rtl_header.append(
Field.templ_dict[sense_list].format( Field.templ_dict[sense_list].format(
clk_name = "clk", clk_name = "clk",
rst_edge = rst_edge, rst_edge = self.rst['edge'],
rst_name = rst_name, rst_name = self.rst['name']))
indent = self.indent(indent_lvl)))
# Calculate how many genvars shall be added
genvars = ['[{}]'.format(chr(97+i)) for i in range(dimensions)]
genvars_str = ''.join(genvars)
# Add actual reset line # Add actual reset line
if rst_name: if self.rst['name']:
indent_lvl += 1 self.rtl_header.append(
self.rtl.append(
Field.templ_dict['rst_field_assign'].format( Field.templ_dict['rst_field_assign'].format(
field_name = obj.inst_name, path = self.path_underscored,
rst_name = rst_name, rst_name = self.rst['name'],
rst_negl = rst_negl, rst_negl = "!" if self.rst['active'] == "active_high" else "",
rst_value = rst_value, rst_value = self.rst['value'],
genvars = genvars_str, genvars = self.genvars_str))
indent = self.indent(indent_lvl)))
self.rtl.append("{}begin".format(self.indent(indent_lvl))) self.rtl_header.append("begin")
indent_lvl += 1
# Not all access types are required and the order might differ # Not all access types are required and the order might differ
# depending on what types are defined and what precedence is # depending on what types are defined and what precedence is
@ -133,67 +71,67 @@ class Field:
# Define hardware access (if applicable) # Define hardware access (if applicable)
access_rtl['hw_write'] = [] access_rtl['hw_write'] = []
if hw_access in (AccessType.rw, AccessType.w): if self.hw_access in (AccessType.rw, AccessType.w):
if obj.get_property('we') or obj.get_property('wel'): if self.we_or_wel:
access_rtl['hw_write'].append( access_rtl['hw_write'].append(
Field.templ_dict['hw_access_we_wel'].format( Field.templ_dict['hw_access_we_wel'].format(
negl = '!' if obj.get_property('wel') else '', negl = '!' if obj.get_property('wel') else '',
reg_name = obj.parent.inst_name, path = self.path_underscored,
field_name = obj.inst_name, genvars = self.genvars_str))
genvars = genvars_str,
indent = self.indent(indent_lvl)))
else: else:
access_rtl['hw_write'].append( access_rtl['hw_write'].append(
Field.templ_dict['hw_access_no_we_wel'].format( Field.templ_dict['hw_access_no_we_wel'])
indent = self.indent(indent_lvl)))
access_rtl['hw_write'].append( access_rtl['hw_write'].append(
Field.templ_dict['hw_access_field'].format( Field.templ_dict['hw_access_field'].format(
reg_name = obj.parent.inst_name, path = self.path_underscored,
field_name = obj.inst_name, genvars = self.genvars_str))
genvars = genvars_str,
indent = self.indent(indent_lvl)))
# Define software access (if applicable) # Define software access (if applicable)
access_rtl['sw_write'] = [] access_rtl['sw_write'] = []
if sw_access in (AccessType.rw, AccessType.w): if self.sw_access in (AccessType.rw, AccessType.w):
access_rtl['sw_write'].append( access_rtl['sw_write'].append(
Field.templ_dict['sw_access_field'].format( Field.templ_dict['sw_access_field'].format(
reg_name = obj.parent.inst_name, path = self.path_underscored,
field_name = obj.inst_name, genvars = self.genvars_str))
genvars = genvars_str,
indent = self.indent(indent_lvl)))
indent_lvl += 1
# If field spans multiple bytes, every byte shall have a seperate enable! # If field spans multiple bytes, every byte shall have a seperate enable!
for i in range(self.bytes): for j, i in enumerate(range(self.lsbyte, self.msbyte+1)):
access_rtl['sw_write'].append( access_rtl['sw_write'].append(
Field.templ_dict['sw_access_byte'].format( Field.templ_dict['sw_access_byte'].format(
reg_name = obj.parent.inst_name, path = self.path_underscored,
field_name = obj.inst_name, genvars = self.genvars_str,
genvars = genvars_str,
i = i, i = i,
indent = self.indent(indent_lvl))) msb_bus = str(8*(i+1)-1 if i != self.msbyte else self.obj.msb),
bus_w = str(8 if i != self.msbyte else self.obj.width-(8*j)),
msb_field = str(8*(j+1)-1 if i != self.msbyte else self.obj.width-1),
field_w = str(8 if i != self.msbyte else self.obj.width-(8*j))))
indent_lvl -= 1 access_rtl['sw_write'].append("end")
access_rtl['sw_write'].append("{}end".format(self.indent(indent_lvl))) # Add singlepulse property
access_rtl['singlepulse'] = [
Field.templ_dict['singlepulse'].format(
path = self.path_underscored,
genvars = self.genvars_str)
]
# Define else with correct indentation and add to dictionary # Define else
access_rtl['else'] = ["{}else".format(self.indent(indent_lvl))] access_rtl['else'] = ["else"]
# Add empty string # Add empty string
access_rtl[''] = [''] access_rtl[''] = ['']
# Check if hardware has precedence (default `precedence = sw`) # Check if hardware has precedence (default `precedence = sw`)
if precedence == 'PrecedenceType.sw': if self.precedence == 'PrecedenceType.sw':
order_list = ['sw_write', order_list = ['sw_write',
'hw_write'] 'hw_write',
'singlepulse']
else: else:
order_list = ['hw_write', order_list = ['hw_write',
'sw_write'] 'sw_write',
'singlepulse']
# Add appropriate else # Add appropriate else
order_list_rtl = [] order_list_rtl = []
@ -203,57 +141,149 @@ class Field:
# get longer in the future and thus become unreadable # get longer in the future and thus become unreadable
if len(access_rtl[i]) > 0: if len(access_rtl[i]) > 0:
order_list_rtl = [*order_list_rtl, *access_rtl[i]] order_list_rtl = [*order_list_rtl, *access_rtl[i]]
order_list_rtl.append("{}else".format(self.indent(indent_lvl))) order_list_rtl.append("else")
# Remove last pop # Remove last pop
order_list_rtl.pop() order_list_rtl.pop()
# Chain access RTL to the rest of the RTL # Chain access RTL to the rest of the RTL
self.rtl = [*self.rtl, *order_list_rtl] self.rtl_header = [*self.rtl_header, *order_list_rtl]
indent_lvl -= 1 self.rtl_header.append(
self.rtl.append(
Field.templ_dict['end_field_ff'].format( Field.templ_dict['end_field_ff'].format(
reg_name = obj.parent.inst_name, path = self.path_underscored))
field_name = obj.inst_name,
indent = self.indent(indent_lvl)))
#####################
# Add combo logic self.__add_combo()
##################### self.__add_ports()
def __add_combo(self):
operations = [] operations = []
if obj.get_property('anded'): if self.obj.get_property('anded'):
operations.append(['anded', '&']) operations.append(['anded', '&'])
if obj.get_property('ored'): if self.obj.get_property('ored'):
operations.append(['ored', '|']) operations.append(['ored', '|'])
if obj.get_property('xored'): if self.obj.get_property('xored'):
operations.append(['xored', '^']) operations.append(['xored', '^'])
if len(operations) > 0: if len(operations) > 0:
self.rtl.append( self.rtl_header.append(
Field.templ_dict['combo_operation_comment'].format( Field.templ_dict['combo_operation_comment'].format(
reg_name = obj.parent.inst_name, path = self.path_underscored))
field_name = obj.inst_name,
indent = self.indent(indent_lvl)))
self.rtl = [ self.rtl_header = [
*self.rtl, *self.rtl_header,
*[Field.templ_dict['assign_combo_operation'].format( *[Field.templ_dict['assign_combo_operation'].format(
field_name = obj.inst_name, path = self.path_underscored,
reg_name = obj.parent.inst_name, genvars = self.genvars_str,
genvars = genvars_str,
op_name = i[0], op_name = i[0],
op_verilog = i[1], op_verilog = i[1]) for i in operations]
indent = self.indent(indent_lvl)) for i in operations]] ]
# TODO: Set sanity checks. For example, having no we but precedence = hw
# will cause weird behavior.
@staticmethod def __process_variables(self, obj: node.RootNode, dimensions: int):
def indent(level): # Save object
return TAB*level self.obj = obj
def get_rtl(self) -> str: # Create full name
return '\n'.join(self.rtl) self.owning_addrmap = obj.owning_addrmap.inst_name
self.path = obj.get_path()\
.replace('[]', '')\
.replace('{}.'.format(self.owning_addrmap), '')
self.path_underscored = self.path.replace('.', '_')
self.path_wo_field = '.'.join(self.path.split('.', -1)[0:-1])
# Calculate how many genvars shall be added
genvars = ['[{}]'.format(chr(97+i)) for i in range(dimensions)]
self.genvars_str = ''.join(genvars)
# Write enable
self.we_or_wel = self.obj.get_property('we') or self.obj.get_property('wel')
# Save byte boundaries
self.lsbyte = math.floor(obj.inst.lsb / 8)
self.msbyte = math.floor(obj.inst.msb / 8)
# Determine resets. This includes checking for async/sync resets,
# the reset value, and whether the field actually has a reset
self.rst = dict()
try:
rst_signal = obj.get_property("resetsignal")
self.rst['name'] = rst_signal.inst_name
self.rst['async'] = rst_signal.get_property("async")
self.rst['type'] = "asynchronous" if rst['async'] else "synchronous"
# Active low or active high?
if rst_signal.get_property("activelow"):
self.rst['edge'] = "negedge"
self.rst['active'] = "active_low"
else:
self.rst['edge'] = "posedge"
self.rst['active'] = "active_high"
# Value of reset?
self.rst['value'] = '\'x' if obj.get_property("reset") == None else\
obj.get_property('reset')
except:
self.rst['async'] = False
self.rst['name'] = None
self.rst['edge'] = None
self.rst['value'] = "'x"
self.rst['active'] = "-"
self.rst['type'] = "-"
self.hw_access = obj.get_property('hw')
self.sw_access = obj.get_property('sw')
self.precedence = obj.get_property('precedence')
def summary(self):
# Additional flags that are set
misc_flags = set(self.obj.list_properties())
# Remove some flags that are not interesting
# or that are listed elsewhere
misc_flags.discard('hw')
misc_flags.discard('reset')
# Add comment with summary on field's properties
self.rtl_header.append(
Field.templ_dict['field_comment'].format(
name = self.obj.inst_name,
hw_access = str(self.hw_access)[11:],
sw_access = str(self.sw_access)[11:],
hw_precedence = '(precedence)' if self.precedence == PrecedenceType.hw else '',
sw_precedence = '(precedence)' if self.precedence == PrecedenceType.sw else '',
rst_active = self.rst['active'],
rst_type = self.rst['type'],
misc_flags = misc_flags if misc_flags else '-',
lsb = self.obj.lsb,
msb = self.obj.msb,
path_wo_field = self.path_wo_field))
def __add_ports(self):
# Port is writable by hardware --> Input port from hardware
if self.hw_access in (AccessType.rw, AccessType.w):
self.ports['input'].append("{}_in".format(self.path_underscored))
# Port has enable signal --> create such an enable
if self.we_or_wel:
self.ports['input'].append("{}_hw_wr".format(self.path_underscored))
if self.hw_access in (AccessType.rw, AccessType.r):
self.ports['output'].append("{}_r".format(self.path_underscored))
def sanity_checks(self):
# If hw=rw/sw=[r]w and hw has no we/wel, sw will never be able to write
if not self.we_or_wel and\
self.precedence == PrecedenceType.hw and \
self.hw_access in (AccessType.rw, AccessType.w) and \
self.sw_access in (AccessType.rw, AccessType.w):
self.logger.warning("Fields with hw=rw/sw=[r]w, we/wel not set and "\
"precedence for hardware will render software's "\
"write property useless since hardware will "\
"write every cycle.")

View File

@ -1,32 +1,29 @@
import importlib.resources as pkg_resources
import yaml import yaml
from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node
from systemrdl.node import FieldNode from systemrdl.node import FieldNode
from components.field import Field
# Local modules # Local modules
from log.log import create_logger from log.log import create_logger
from components.component import Component
from components.field import Field
from . import templates
TAB = " " class Register(Component):
class Register:
# Save YAML template as class variable # Save YAML template as class variable
with open('srdl2sv/components/templates/regs.yaml', 'r') as file: templ_dict = yaml.load(
templ_dict = yaml.load(file, Loader=yaml.FullLoader) pkg_resources.read_text(templates, 'regs.yaml'),
Loader=yaml.FullLoader)
def __init__(self, obj: node.RootNode, config: dict): def __init__(self, obj: node.RootNode, config: dict):
self.obj = obj super().__init__()
self.name = obj.inst_name
self.rtl = [] # Save and/or process important variables
self.__process_variables(obj)
# Create logger object # Create logger object
self.logger = create_logger( self.create_logger("{}.{}".format(self.owning_addrmap, self.path), config)
"{}.{}".format(__name__, obj.inst_name),
stream_log_level=config['stream_log_level'],
file_log_level=config['file_log_level'],
file_name=config['file_log_location'])
self.logger.debug('Starting to process register "{}"'.format(obj.inst_name)) self.logger.debug('Starting to process register "{}"'.format(obj.inst_name))
if obj.is_array: if obj.is_array:
@ -38,56 +35,55 @@ class Register:
depth = '[{}]'.format(']['.join(f"{i}" for i in array_dimensions)) depth = '[{}]'.format(']['.join(f"{i}" for i in array_dimensions))
dimensions = len(array_dimensions) dimensions = len(array_dimensions)
indent_lvl = 0
# Create comment and provide user information about register he/she # Create comment and provide user information about register he/she
# is looking at. # is looking at.
self.rtl.append( self.rtl_header.append(
Register.templ_dict['reg_comment'].format( Register.templ_dict['reg_comment'].format(
name = obj.inst_name, name = obj.inst_name,
dimensions = dimensions, dimensions = dimensions,
depth = depth)) depth = depth))
# Create wires every register # Create wires every register
self.rtl.append( self.rtl_header.append(
Register.templ_dict['rw_wire_declare'].format( Register.templ_dict['rw_wire_declare'].format(
name = obj.inst_name, name = obj.inst_name,
depth = depth)) depth = depth))
# Create generate block for register and add comment # Create generate block for register and add comment
self.rtl.append("generate") self.rtl_header.append("generate")
for i in range(dimensions): for i in range(dimensions):
self.rtl.append( self.rtl_header.append(
Register.templ_dict['generate_for_start'].format( Register.templ_dict['generate_for_start'].format(
iterator = chr(97+i), iterator = chr(97+i),
limit = array_dimensions[i], limit = array_dimensions[i]))
indent = self.indent(i)))
indent_lvl = i
indent_lvl += 1
# Create RTL for fields # Create RTL for fields
# Fields should be in order in RTL,therefore, use list # Fields should be in order in RTL,therefore, use list
self.fields = []
for field in obj.fields(): for field in obj.fields():
field_obj = Field(field, indent_lvl, dimensions, config) field_obj = Field(field, dimensions, config)
self.fields.append(field_obj)
self.rtl += field_obj.rtl if not config['disable_sanity']:
field_obj.sanity_checks()
self.children.append(field_obj)
# End loops # End loops
for i in range(dimensions-1, -1, -1): for i in range(dimensions-1, -1, -1):
self.rtl.append( self.rtl_footer.append(
Register.templ_dict['generate_for_end'].format( Register.templ_dict['generate_for_end'].format(
dimension = chr(97+i), dimension = chr(97+i)))
indent = self.indent(i)))
def __process_variables(self, obj: node.RootNode):
# Save object
self.obj = obj
@staticmethod # Create full name
def indent(level): self.owning_addrmap = obj.owning_addrmap.inst_name
return TAB*level self.path = obj.get_path()\
.replace('[]', '')\
.replace('{}.'.format(self.owning_addrmap), '')
def get_rtl(self) -> str: self.path_underscored = self.path.replace('.', '_')
return '\n'.join(self.rtl)
self.name = obj.inst_name

View File

@ -1,9 +0,0 @@
module {addrmap_name} (
{bus_io}
{io_list}
);
{bus_widget}
{registers}
endmodule

View File

@ -0,0 +1,21 @@
---
module_declaration: |-
module {name} (
// Bus I/O
// TODO
// InOuts
{inouts}
// Inputs
{inputs}
// Outputs
{outputs}
);
input_port: |-
input {name},
output_port: |-
output {name},
inout_port: |-
inout {name},

View File

@ -1,40 +1,49 @@
--- ---
sense_list_rst: |- sense_list_rst: |-
{indent}always_ff @(posedge {clk_name} or {rst_edge} {rst_name}) always_ff @(posedge {clk_name} or {rst_edge} {rst_name})
sense_list_no_rst: |- sense_list_no_rst: |-
{indent}always_ff @(posedge {clk_name}) always_ff @(posedge {clk_name})
rst_field_assign: |- rst_field_assign: |-
{indent}if ({rst_negl}{rst_name}) if ({rst_negl}{rst_name})
{indent} {field_name}_q{genvars} <= {rst_value}; begin
{indent}else {path}_q{genvars} <= {rst_value};
end
else
sw_access_field: |- sw_access_field: |-
{indent}if ({reg_name}_{field_name}_sw_wr{genvars}) if ({path}_sw_wr{genvars})
{indent}begin begin
sw_access_byte: |- sw_access_byte: |-
{indent}if (byte_enable[{i}]) if (byte_enable[{i}])
{indent} {reg_name}_{field_name}_q{genvars}[8*({i}+1)-1 -: 8] <= sw_wr_bus[8*({i}+1)-1 -: 8]; begin
{path}_q{genvars}[{msb_field}-:{field_w}] <= sw_wr_bus[{msb_bus}-:{bus_w}];
end
hw_access_we_wel: |- hw_access_we_wel: |-
{indent}if ({negl}{reg_name}_{field_name}_hw_wr{genvars}) if ({negl}{path}_hw_wr{genvars})
hw_access_no_we_wel: |- hw_access_no_we_wel: |-
{indent}if (1) // we or wel property not set if (1) // we or wel property not set
hw_access_field: |- hw_access_field: |-
{indent}begin begin
{indent} {reg_name}_{field_name}_q{genvars} <= {reg_name}_{field_name}_in{genvars}; {path}_q{genvars} <= {path}_in{genvars};
{indent}end end
end_field_ff: |- end_field_ff: |-
{indent}end // of {reg_name}_{field_name}'s always_ff end // of {path}'s always_ff
field_comment: |- field_comment: |-
{indent}//-----------------FIELD SUMMARY----------------- //-----------------FIELD SUMMARY-----------------
{indent}// name : {name} // name : {name} ({path_wo_field}[{msb}:{lsb}])
{indent}// access : hw = {hw_access} {hw_precedence} // access : hw = {hw_access} {hw_precedence}
{indent}// sw = {sw_access} {sw_precedence} // sw = {sw_access} {sw_precedence}
{indent}// reset : {rst_active} / {rst_type} // reset : {rst_active} / {rst_type}
{indent}//----------------------------------------------- // flags : {misc_flags}
//-----------------------------------------------
combo_operation_comment: |- combo_operation_comment: |-
{indent}// Combinational logic for {reg_name}_{field_name} // Combinational logic for {path}
assign_combo_operation: |- assign_combo_operation: |-
{indent}assign {reg_name}_{field_name}_{op_name}{genvars} = {op_verilog}{reg_name}_{field_name}_q{genvars} assign {path}_{op_name}{genvars} = {op_verilog}{path}_q{genvars}
singlepulse: |-
begin
{path}{genvars} <= 0;
end

View File

@ -14,7 +14,7 @@ reg_comment: |-
******************************************************************* *******************************************************************
*******************************************************************/ *******************************************************************/
generate_for_start: |- generate_for_start: |-
{indent}for ({iterator} = 0; {iterator} < {limit}; {iterator}++) for ({iterator} = 0; {iterator} < {limit}; {iterator}++)
{indent}begin begin
generate_for_end: |- generate_for_end: |-
{indent}end // of for loop with iterator {dimension} end // of for loop with iterator {dimension}

View File

@ -1,6 +1,8 @@
import logging import logging
from typing import Optional from typing import Optional
# TODO: In general, it would be more useful if the hierarchy of the objects pop up
# in the logs, not the Python objectname and than the name of the SRDL object.
class CustomFormatter(logging.Formatter): class CustomFormatter(logging.Formatter):
"""Logging Formatter to add colors and count warning / errors""" """Logging Formatter to add colors and count warning / errors"""

View File

@ -40,7 +40,7 @@ if __name__ == "__main__":
except RDLCompileError: except RDLCompileError:
sys.exit(1) sys.exit(1)
addrmap = AddrMap(rdlc, root.top, config) addrmap = AddrMap(root.top, config)
# Create output directory # Create output directory
try: try:
@ -55,7 +55,12 @@ if __name__ == "__main__":
out_file_name = "{}/{}.sv".format(config['output_dir'], addrmap.name) out_file_name = "{}/{}.sv".format(config['output_dir'], addrmap.name)
with open(out_file_name, 'w') as file: with open(out_file_name, 'w') as file:
file.write(addrmap.get_rtl()) file.write(
addrmap.get_rtl(
tab_width=config['tab_width'],
real_tabs=config['real_tabs']
)
)
logger.info('Succesfully created "{}"'.format(out_file_name)) logger.info('Succesfully created "{}"'.format(out_file_name))