mirror of
https://github.com/Silicon1602/srdl2sv.git
synced 2024-11-13 02:53:37 +00:00
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:
parent
59b91536ed
commit
cecb73f07a
@ -19,20 +19,6 @@ class CliArguments():
|
||||
self.parser = argparse.ArgumentParser(
|
||||
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(
|
||||
"-o",
|
||||
"--out_dir",
|
||||
@ -55,7 +41,41 @@ class CliArguments():
|
||||
"--recursive_search",
|
||||
action="store_true",
|
||||
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(
|
||||
"IN_RDL",
|
||||
@ -91,4 +111,11 @@ class CliArguments():
|
||||
ts = time.strftime('%Y%m%d_%H%M%S', config['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
|
||||
|
@ -1,56 +1,101 @@
|
||||
import yaml
|
||||
import re
|
||||
import importlib.resources as pkg_resources
|
||||
import yaml
|
||||
|
||||
from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node
|
||||
from systemrdl.node import FieldNode
|
||||
|
||||
# Local packages
|
||||
from components.component import Component
|
||||
from components.register import Register
|
||||
from log.log import create_logger
|
||||
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:
|
||||
def __init__(self, rdlc: RDLCompiler, obj: node.RootNode, config: dict):
|
||||
class AddrMap(Component):
|
||||
# Save YAML template as class variable
|
||||
templ_dict = yaml.load(
|
||||
pkg_resources.read_text(templates, 'addrmap.yaml'),
|
||||
Loader=yaml.FullLoader)
|
||||
|
||||
self.rdlc = rdlc
|
||||
self.name = obj.inst_name
|
||||
def __init__(self, obj: node.RootNode, config: dict):
|
||||
super().__init__()
|
||||
|
||||
# Save and/or process important variables
|
||||
self.__process_variables(obj)
|
||||
|
||||
# Create logger object
|
||||
self.logger = create_logger(
|
||||
"{}.{}".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 addrmap "{}"'.format(obj.inst_name))
|
||||
self.create_logger(self.path, config)
|
||||
self.logger.debug('Starting to process addrmap')
|
||||
|
||||
template = pkg_resources.read_text(templates, 'addrmap.sv')
|
||||
|
||||
# Read template for SystemVerilog module
|
||||
tmpl_addrmap = re.compile("{addrmap_name}")
|
||||
self.rtl = tmpl_addrmap.sub(obj.inst_name, template)
|
||||
|
||||
# Empty list of register logic
|
||||
self.registers = set()
|
||||
# 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()
|
||||
|
||||
# Traverse through children
|
||||
for child in obj.children():
|
||||
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):
|
||||
pass
|
||||
self.logger.error('Regfiles are not implemented yet!')
|
||||
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
|
||||
self.rtl = [x.get_rtl() for x in self.registers]
|
||||
# Add regfiles and registers to children
|
||||
self.children = [x for x in self.registers.values()]
|
||||
|
||||
def get_rtl(self) -> str:
|
||||
return '\n'.join(self.rtl)
|
||||
self.logger.info("Done generating all child-regfiles/registers")
|
||||
|
||||
# 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
|
||||
|
85
srdl2sv/components/component.py
Normal file
85
srdl2sv/components/component.py
Normal 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)
|
@ -1,123 +1,61 @@
|
||||
import math
|
||||
import importlib.resources as pkg_resources
|
||||
import yaml
|
||||
|
||||
from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node
|
||||
from systemrdl.node import FieldNode
|
||||
from systemrdl.rdltypes import PrecedenceType, AccessType
|
||||
from itertools import chain
|
||||
|
||||
# Local modules
|
||||
from log.log import create_logger
|
||||
from components.component import Component
|
||||
from . import templates
|
||||
|
||||
TAB = " "
|
||||
|
||||
class Field:
|
||||
class Field(Component):
|
||||
# Save YAML template as class variable
|
||||
with open('srdl2sv/components/templates/fields.yaml', 'r') as file:
|
||||
templ_dict = yaml.load(file, Loader=yaml.FullLoader)
|
||||
templ_dict = yaml.load(
|
||||
pkg_resources.read_text(templates, 'fields.yaml'),
|
||||
Loader=yaml.FullLoader)
|
||||
|
||||
def __init__(self, obj: node.RootNode, indent_lvl: int, dimensions: int, config:dict):
|
||||
self.obj = obj
|
||||
self.rtl = []
|
||||
self.bytes = math.ceil(obj.width / 8)
|
||||
def __init__(self, obj: node.RootNode, dimensions: int, config:dict):
|
||||
super().__init__()
|
||||
|
||||
# Save and/or process important variables
|
||||
self.__process_variables(obj, dimensions)
|
||||
|
||||
# Create logger object
|
||||
self.logger = create_logger(
|
||||
"{}.{}".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.create_logger("{}.{}".format(self.owning_addrmap, self.path), config)
|
||||
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:
|
||||
# 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
|
||||
# can be found here: https://github.com/SystemRDL/systemrdl-compiler/issues/51
|
||||
##################################################################################
|
||||
|
||||
# Determine resets. This includes checking for async/sync resets,
|
||||
# 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)))
|
||||
# Print a summary
|
||||
self.summary()
|
||||
|
||||
# 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(
|
||||
clk_name = "clk",
|
||||
rst_edge = rst_edge,
|
||||
rst_name = 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)
|
||||
rst_edge = self.rst['edge'],
|
||||
rst_name = self.rst['name']))
|
||||
|
||||
# Add actual reset line
|
||||
if rst_name:
|
||||
indent_lvl += 1
|
||||
|
||||
self.rtl.append(
|
||||
if self.rst['name']:
|
||||
self.rtl_header.append(
|
||||
Field.templ_dict['rst_field_assign'].format(
|
||||
field_name = obj.inst_name,
|
||||
rst_name = rst_name,
|
||||
rst_negl = rst_negl,
|
||||
rst_value = rst_value,
|
||||
genvars = genvars_str,
|
||||
indent = self.indent(indent_lvl)))
|
||||
path = self.path_underscored,
|
||||
rst_name = self.rst['name'],
|
||||
rst_negl = "!" if self.rst['active'] == "active_high" else "",
|
||||
rst_value = self.rst['value'],
|
||||
genvars = self.genvars_str))
|
||||
|
||||
self.rtl.append("{}begin".format(self.indent(indent_lvl)))
|
||||
|
||||
indent_lvl += 1
|
||||
self.rtl_header.append("begin")
|
||||
|
||||
# Not all access types are required and the order might differ
|
||||
# depending on what types are defined and what precedence is
|
||||
@ -133,67 +71,67 @@ class Field:
|
||||
# Define hardware access (if applicable)
|
||||
access_rtl['hw_write'] = []
|
||||
|
||||
if hw_access in (AccessType.rw, AccessType.w):
|
||||
if obj.get_property('we') or obj.get_property('wel'):
|
||||
if self.hw_access in (AccessType.rw, AccessType.w):
|
||||
if self.we_or_wel:
|
||||
access_rtl['hw_write'].append(
|
||||
Field.templ_dict['hw_access_we_wel'].format(
|
||||
negl = '!' if obj.get_property('wel') else '',
|
||||
reg_name = obj.parent.inst_name,
|
||||
field_name = obj.inst_name,
|
||||
genvars = genvars_str,
|
||||
indent = self.indent(indent_lvl)))
|
||||
path = self.path_underscored,
|
||||
genvars = self.genvars_str))
|
||||
else:
|
||||
access_rtl['hw_write'].append(
|
||||
Field.templ_dict['hw_access_no_we_wel'].format(
|
||||
indent = self.indent(indent_lvl)))
|
||||
Field.templ_dict['hw_access_no_we_wel'])
|
||||
|
||||
access_rtl['hw_write'].append(
|
||||
Field.templ_dict['hw_access_field'].format(
|
||||
reg_name = obj.parent.inst_name,
|
||||
field_name = obj.inst_name,
|
||||
genvars = genvars_str,
|
||||
indent = self.indent(indent_lvl)))
|
||||
path = self.path_underscored,
|
||||
genvars = self.genvars_str))
|
||||
|
||||
# Define software access (if applicable)
|
||||
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(
|
||||
Field.templ_dict['sw_access_field'].format(
|
||||
reg_name = obj.parent.inst_name,
|
||||
field_name = obj.inst_name,
|
||||
genvars = genvars_str,
|
||||
indent = self.indent(indent_lvl)))
|
||||
|
||||
indent_lvl += 1
|
||||
path = self.path_underscored,
|
||||
genvars = self.genvars_str))
|
||||
|
||||
# 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(
|
||||
Field.templ_dict['sw_access_byte'].format(
|
||||
reg_name = obj.parent.inst_name,
|
||||
field_name = obj.inst_name,
|
||||
genvars = genvars_str,
|
||||
path = self.path_underscored,
|
||||
genvars = self.genvars_str,
|
||||
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
|
||||
access_rtl['else'] = ["{}else".format(self.indent(indent_lvl))]
|
||||
# Define else
|
||||
access_rtl['else'] = ["else"]
|
||||
|
||||
# Add empty string
|
||||
access_rtl[''] = ['']
|
||||
|
||||
# Check if hardware has precedence (default `precedence = sw`)
|
||||
if precedence == 'PrecedenceType.sw':
|
||||
if self.precedence == 'PrecedenceType.sw':
|
||||
order_list = ['sw_write',
|
||||
'hw_write']
|
||||
'hw_write',
|
||||
'singlepulse']
|
||||
else:
|
||||
order_list = ['hw_write',
|
||||
'sw_write']
|
||||
'sw_write',
|
||||
'singlepulse']
|
||||
|
||||
# Add appropriate else
|
||||
order_list_rtl = []
|
||||
@ -203,57 +141,149 @@ class Field:
|
||||
# get longer in the future and thus become unreadable
|
||||
if len(access_rtl[i]) > 0:
|
||||
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
|
||||
order_list_rtl.pop()
|
||||
|
||||
# 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.append(
|
||||
self.rtl_header.append(
|
||||
Field.templ_dict['end_field_ff'].format(
|
||||
reg_name = obj.parent.inst_name,
|
||||
field_name = obj.inst_name,
|
||||
indent = self.indent(indent_lvl)))
|
||||
path = self.path_underscored))
|
||||
|
||||
#####################
|
||||
# Add combo logic
|
||||
#####################
|
||||
|
||||
self.__add_combo()
|
||||
self.__add_ports()
|
||||
|
||||
def __add_combo(self):
|
||||
operations = []
|
||||
if obj.get_property('anded'):
|
||||
if self.obj.get_property('anded'):
|
||||
operations.append(['anded', '&'])
|
||||
if obj.get_property('ored'):
|
||||
if self.obj.get_property('ored'):
|
||||
operations.append(['ored', '|'])
|
||||
if obj.get_property('xored'):
|
||||
if self.obj.get_property('xored'):
|
||||
operations.append(['xored', '^'])
|
||||
|
||||
if len(operations) > 0:
|
||||
self.rtl.append(
|
||||
self.rtl_header.append(
|
||||
Field.templ_dict['combo_operation_comment'].format(
|
||||
reg_name = obj.parent.inst_name,
|
||||
field_name = obj.inst_name,
|
||||
indent = self.indent(indent_lvl)))
|
||||
path = self.path_underscored))
|
||||
|
||||
self.rtl = [
|
||||
*self.rtl,
|
||||
self.rtl_header = [
|
||||
*self.rtl_header,
|
||||
*[Field.templ_dict['assign_combo_operation'].format(
|
||||
field_name = obj.inst_name,
|
||||
reg_name = obj.parent.inst_name,
|
||||
genvars = genvars_str,
|
||||
path = self.path_underscored,
|
||||
genvars = self.genvars_str,
|
||||
op_name = i[0],
|
||||
op_verilog = i[1],
|
||||
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.
|
||||
op_verilog = i[1]) for i in operations]
|
||||
]
|
||||
|
||||
|
||||
@staticmethod
|
||||
def indent(level):
|
||||
return TAB*level
|
||||
def __process_variables(self, obj: node.RootNode, dimensions: int):
|
||||
# Save object
|
||||
self.obj = obj
|
||||
|
||||
def get_rtl(self) -> str:
|
||||
return '\n'.join(self.rtl)
|
||||
# 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.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.")
|
||||
|
@ -1,32 +1,29 @@
|
||||
import importlib.resources as pkg_resources
|
||||
import yaml
|
||||
|
||||
from systemrdl import RDLCompiler, RDLCompileError, RDLWalker, RDLListener, node
|
||||
from systemrdl.node import FieldNode
|
||||
|
||||
from components.field import Field
|
||||
|
||||
# Local modules
|
||||
from log.log import create_logger
|
||||
from components.component import Component
|
||||
from components.field import Field
|
||||
from . import templates
|
||||
|
||||
TAB = " "
|
||||
|
||||
class Register:
|
||||
class Register(Component):
|
||||
# Save YAML template as class variable
|
||||
with open('srdl2sv/components/templates/regs.yaml', 'r') as file:
|
||||
templ_dict = yaml.load(file, Loader=yaml.FullLoader)
|
||||
templ_dict = yaml.load(
|
||||
pkg_resources.read_text(templates, 'regs.yaml'),
|
||||
Loader=yaml.FullLoader)
|
||||
|
||||
def __init__(self, obj: node.RootNode, config: dict):
|
||||
self.obj = obj
|
||||
self.name = obj.inst_name
|
||||
self.rtl = []
|
||||
super().__init__()
|
||||
|
||||
# Save and/or process important variables
|
||||
self.__process_variables(obj)
|
||||
|
||||
# Create logger object
|
||||
self.logger = create_logger(
|
||||
"{}.{}".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.create_logger("{}.{}".format(self.owning_addrmap, self.path), config)
|
||||
self.logger.debug('Starting to process register "{}"'.format(obj.inst_name))
|
||||
|
||||
if obj.is_array:
|
||||
@ -38,56 +35,55 @@ class Register:
|
||||
|
||||
depth = '[{}]'.format(']['.join(f"{i}" for i in array_dimensions))
|
||||
dimensions = len(array_dimensions)
|
||||
indent_lvl = 0
|
||||
|
||||
# Create comment and provide user information about register he/she
|
||||
# is looking at.
|
||||
self.rtl.append(
|
||||
self.rtl_header.append(
|
||||
Register.templ_dict['reg_comment'].format(
|
||||
name = obj.inst_name,
|
||||
dimensions = dimensions,
|
||||
depth = depth))
|
||||
|
||||
# Create wires every register
|
||||
self.rtl.append(
|
||||
self.rtl_header.append(
|
||||
Register.templ_dict['rw_wire_declare'].format(
|
||||
name = obj.inst_name,
|
||||
depth = depth))
|
||||
|
||||
# Create generate block for register and add comment
|
||||
self.rtl.append("generate")
|
||||
self.rtl_header.append("generate")
|
||||
for i in range(dimensions):
|
||||
self.rtl.append(
|
||||
self.rtl_header.append(
|
||||
Register.templ_dict['generate_for_start'].format(
|
||||
iterator = chr(97+i),
|
||||
limit = array_dimensions[i],
|
||||
indent = self.indent(i)))
|
||||
|
||||
indent_lvl = i
|
||||
|
||||
indent_lvl += 1
|
||||
limit = array_dimensions[i]))
|
||||
|
||||
# Create RTL for fields
|
||||
# Fields should be in order in RTL,therefore, use list
|
||||
self.fields = []
|
||||
|
||||
for field in obj.fields():
|
||||
field_obj = Field(field, indent_lvl, dimensions, config)
|
||||
self.fields.append(field_obj)
|
||||
field_obj = Field(field, dimensions, config)
|
||||
|
||||
self.rtl += field_obj.rtl
|
||||
if not config['disable_sanity']:
|
||||
field_obj.sanity_checks()
|
||||
|
||||
self.children.append(field_obj)
|
||||
|
||||
# End loops
|
||||
for i in range(dimensions-1, -1, -1):
|
||||
self.rtl.append(
|
||||
self.rtl_footer.append(
|
||||
Register.templ_dict['generate_for_end'].format(
|
||||
dimension = chr(97+i),
|
||||
indent = self.indent(i)))
|
||||
dimension = chr(97+i)))
|
||||
|
||||
def __process_variables(self, obj: node.RootNode):
|
||||
# Save object
|
||||
self.obj = obj
|
||||
|
||||
@staticmethod
|
||||
def indent(level):
|
||||
return TAB*level
|
||||
# Create full name
|
||||
self.owning_addrmap = obj.owning_addrmap.inst_name
|
||||
self.path = obj.get_path()\
|
||||
.replace('[]', '')\
|
||||
.replace('{}.'.format(self.owning_addrmap), '')
|
||||
|
||||
def get_rtl(self) -> str:
|
||||
return '\n'.join(self.rtl)
|
||||
self.path_underscored = self.path.replace('.', '_')
|
||||
|
||||
self.name = obj.inst_name
|
||||
|
@ -1,9 +0,0 @@
|
||||
module {addrmap_name} (
|
||||
{bus_io}
|
||||
{io_list}
|
||||
);
|
||||
|
||||
{bus_widget}
|
||||
|
||||
{registers}
|
||||
endmodule
|
21
srdl2sv/components/templates/addrmap.yaml
Normal file
21
srdl2sv/components/templates/addrmap.yaml
Normal 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},
|
@ -1,40 +1,49 @@
|
||||
---
|
||||
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: |-
|
||||
{indent}always_ff @(posedge {clk_name})
|
||||
always_ff @(posedge {clk_name})
|
||||
rst_field_assign: |-
|
||||
{indent}if ({rst_negl}{rst_name})
|
||||
{indent} {field_name}_q{genvars} <= {rst_value};
|
||||
{indent}else
|
||||
if ({rst_negl}{rst_name})
|
||||
begin
|
||||
{path}_q{genvars} <= {rst_value};
|
||||
end
|
||||
else
|
||||
sw_access_field: |-
|
||||
{indent}if ({reg_name}_{field_name}_sw_wr{genvars})
|
||||
{indent}begin
|
||||
if ({path}_sw_wr{genvars})
|
||||
begin
|
||||
sw_access_byte: |-
|
||||
{indent}if (byte_enable[{i}])
|
||||
{indent} {reg_name}_{field_name}_q{genvars}[8*({i}+1)-1 -: 8] <= sw_wr_bus[8*({i}+1)-1 -: 8];
|
||||
if (byte_enable[{i}])
|
||||
begin
|
||||
{path}_q{genvars}[{msb_field}-:{field_w}] <= sw_wr_bus[{msb_bus}-:{bus_w}];
|
||||
end
|
||||
|
||||
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: |-
|
||||
{indent}if (1) // we or wel property not set
|
||||
if (1) // we or wel property not set
|
||||
hw_access_field: |-
|
||||
{indent}begin
|
||||
{indent} {reg_name}_{field_name}_q{genvars} <= {reg_name}_{field_name}_in{genvars};
|
||||
{indent}end
|
||||
begin
|
||||
{path}_q{genvars} <= {path}_in{genvars};
|
||||
end
|
||||
end_field_ff: |-
|
||||
{indent}end // of {reg_name}_{field_name}'s always_ff
|
||||
end // of {path}'s always_ff
|
||||
field_comment: |-
|
||||
|
||||
{indent}//-----------------FIELD SUMMARY-----------------
|
||||
{indent}// name : {name}
|
||||
{indent}// access : hw = {hw_access} {hw_precedence}
|
||||
{indent}// sw = {sw_access} {sw_precedence}
|
||||
{indent}// reset : {rst_active} / {rst_type}
|
||||
{indent}//-----------------------------------------------
|
||||
//-----------------FIELD SUMMARY-----------------
|
||||
// name : {name} ({path_wo_field}[{msb}:{lsb}])
|
||||
// access : hw = {hw_access} {hw_precedence}
|
||||
// sw = {sw_access} {sw_precedence}
|
||||
// reset : {rst_active} / {rst_type}
|
||||
// flags : {misc_flags}
|
||||
//-----------------------------------------------
|
||||
combo_operation_comment: |-
|
||||
|
||||
{indent}// Combinational logic for {reg_name}_{field_name}
|
||||
// Combinational logic for {path}
|
||||
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
|
||||
|
||||
|
@ -14,7 +14,7 @@ reg_comment: |-
|
||||
*******************************************************************
|
||||
*******************************************************************/
|
||||
generate_for_start: |-
|
||||
{indent}for ({iterator} = 0; {iterator} < {limit}; {iterator}++)
|
||||
{indent}begin
|
||||
for ({iterator} = 0; {iterator} < {limit}; {iterator}++)
|
||||
begin
|
||||
generate_for_end: |-
|
||||
{indent}end // of for loop with iterator {dimension}
|
||||
end // of for loop with iterator {dimension}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import logging
|
||||
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):
|
||||
"""Logging Formatter to add colors and count warning / errors"""
|
||||
|
||||
|
@ -40,7 +40,7 @@ if __name__ == "__main__":
|
||||
except RDLCompileError:
|
||||
sys.exit(1)
|
||||
|
||||
addrmap = AddrMap(rdlc, root.top, config)
|
||||
addrmap = AddrMap(root.top, config)
|
||||
|
||||
# Create output directory
|
||||
try:
|
||||
@ -55,7 +55,12 @@ if __name__ == "__main__":
|
||||
out_file_name = "{}/{}.sv".format(config['output_dir'], addrmap.name)
|
||||
|
||||
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))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user