mirror of
https://github.com/Silicon1602/srdl2sv.git
synced 2025-04-19 13:02:57 +00:00
449 lines
18 KiB
Python
449 lines
18 KiB
Python
import math
|
|
import importlib.resources as pkg_resources
|
|
import yaml
|
|
|
|
from systemrdl.node import FieldNode, SignalNode
|
|
from systemrdl.component import Reg, Regfile, Addrmap, Root
|
|
from systemrdl.rdltypes import PrecedenceType, AccessType, OnReadType, OnWriteType
|
|
|
|
# Local modules
|
|
from components.component import Component, TypeDef
|
|
from . import templates
|
|
|
|
class Field(Component):
|
|
# Save YAML template as class variable
|
|
templ_dict = yaml.load(
|
|
pkg_resources.read_text(templates, 'fields.yaml'),
|
|
Loader=yaml.FullLoader)
|
|
|
|
def __init__(self, obj: FieldNode, array_dimensions: list, config:dict):
|
|
super().__init__()
|
|
|
|
# Save and/or process important variables
|
|
self.__process_variables(obj, array_dimensions)
|
|
|
|
# Create logger object
|
|
self.create_logger("{}.{}".format(self.owning_addrmap, self.path), config)
|
|
self.logger.debug('Starting to process field "{}"'.format(obj.inst_name))
|
|
|
|
# Determine field types
|
|
self.__process_fieldtype()
|
|
|
|
##################################################################################
|
|
# 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
|
|
##################################################################################
|
|
# Print a summary
|
|
self.rtl_header.append(self.summary())
|
|
|
|
# Generate RTL
|
|
self.__add_always_ff()
|
|
self.__add_access_rtl()
|
|
self.__add_combo()
|
|
self.__add_ports()
|
|
self.__prepend_signal_declarations()
|
|
|
|
def __add_combo(self):
|
|
operations = []
|
|
if self.obj.get_property('anded'):
|
|
operations.append(['&', 'assign_anded_operation'])
|
|
if self.obj.get_property('ored'):
|
|
operations.append(['|', 'assign_ored_operation'])
|
|
if self.obj.get_property('xored'):
|
|
operations.append(['^', 'assign_xored_operation'])
|
|
|
|
if len(operations) > 0:
|
|
self.rtl_header.append(
|
|
Field.templ_dict['combo_operation_comment']['rtl'].format(
|
|
path = self.path_underscored))
|
|
|
|
self.rtl_header = [
|
|
*self.rtl_header,
|
|
*[Field.templ_dict[i[1]]['rtl'].format(
|
|
path = self.path_underscored,
|
|
genvars = self.genvars_str,
|
|
op_verilog = i[0]) for i in operations]
|
|
]
|
|
|
|
[self.yaml_signals_to_list(Field.templ_dict[i[1]]) for i in operations]
|
|
|
|
|
|
def __process_fieldtype(self):
|
|
try:
|
|
enum = self.obj.get_property('encode')
|
|
|
|
# Rules for scope:
|
|
# - Regfiles or addrmaps have packages
|
|
# - An enum that is not defined within a register will go into the package
|
|
# of the first addrmap or regfile that is found when iterating through
|
|
# the parents
|
|
# - Regfiles don't need to be unique in a design. Therefore, the packages of
|
|
# regfiles shall be prepended by the addrmap name.
|
|
# - When the enum is defined in a register, that register will be prepended
|
|
# to the name of that enum.
|
|
#
|
|
# This procedure is expensive, but None.parent() will not work and therefore
|
|
# kill the try block in most cases
|
|
parent_scope = enum.get_parent_scope()
|
|
|
|
self.logger.debug("Starting to parse '{}'".format(enum))
|
|
|
|
if isinstance(parent_scope, Reg):
|
|
enum_name = '__'.join([enum.get_scope_path().split('::')[-1], enum.__name__])
|
|
parent_scope = parent_scope.parent_scope
|
|
else:
|
|
enum_name = enum.__name__
|
|
|
|
path = []
|
|
|
|
# Open up all parent scopes and append it to scope list
|
|
while 1:
|
|
if isinstance(parent_scope, Regfile):
|
|
if parent_scope.is_instance:
|
|
path.append(parent_scope.inst_name)
|
|
else:
|
|
path.append(parent_scope.type_name)
|
|
|
|
# That's a lot of parent_scope's...
|
|
parent_scope = parent_scope.parent_scope
|
|
else:
|
|
path.append(self.owning_addrmap)
|
|
|
|
break
|
|
|
|
# Create string. Reverse list so that order starts at addrmap
|
|
scope = '__'.join(reversed(path))
|
|
|
|
# Create internal NamedTuple with information on Enum
|
|
self.typedef[enum_name] = TypeDef (
|
|
scope=scope,
|
|
members= [(x.name, x.value) for x in self.obj.get_property('encode')]
|
|
)
|
|
|
|
# Save name of object
|
|
self.field_type =\
|
|
'::'.join([scope, enum_name])
|
|
|
|
self.logger.info("Parsed enum '{}'".format(enum_name))
|
|
|
|
except AttributeError:
|
|
# In case of an AttributeError, the encode property is None. Hence,
|
|
# the field has a simple width
|
|
if self.obj.width > 1:
|
|
self.field_type = 'logic [{}:0]'.format(self.obj.width-1)
|
|
else:
|
|
self.field_type = 'logic'
|
|
|
|
def __process_variables(self, obj: FieldNode, array_dimensions: list):
|
|
# Save object
|
|
self.obj = obj
|
|
|
|
# Create full name
|
|
self.owning_addrmap = obj.owning_addrmap.inst_name
|
|
self.full_path = obj.get_path().replace('[]', '')
|
|
self.path = self.full_path\
|
|
.replace('[]', '')\
|
|
.replace('{}.'.format(self.owning_addrmap), '')
|
|
|
|
self.path_underscored = self.path.replace('.', '_')
|
|
self.path_wo_field = '.'.join(self.path.split('.', -1)[0:-1])
|
|
|
|
# Save dimensions of unpacked dimension
|
|
self.array_dimensions = array_dimensions
|
|
|
|
# Calculate how many genvars shall be added
|
|
genvars = ['[{}]'.format(chr(97+i)) for i in range(len(array_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 self.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
|
|
return \
|
|
Field.templ_dict['field_comment']['rtl'].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_always_ff(self):
|
|
# Handle always_ff
|
|
sense_list = 'sense_list_rst' if self.rst['async'] else 'sense_list_no_rst'
|
|
|
|
self.rtl_header.append(
|
|
Field.templ_dict[sense_list]['rtl'].format(
|
|
clk_name = "clk",
|
|
rst_edge = self.rst['edge'],
|
|
rst_name = self.rst['name']))
|
|
|
|
# Add actual reset line
|
|
if self.rst['name']:
|
|
self.rtl_header.append(
|
|
Field.templ_dict['rst_field_assign']['rtl'].format(
|
|
path = self.path_underscored,
|
|
rst_name = self.rst['name'],
|
|
rst_negl = "!" if self.rst['active'] == "active_low" else "",
|
|
rst_value = self.rst['value'],
|
|
genvars = self.genvars_str))
|
|
|
|
self.yaml_signals_to_list(Field.templ_dict['rst_field_assign'])
|
|
|
|
self.rtl_header.append("begin")
|
|
|
|
# Add name of actual field to Signal field
|
|
# TODO
|
|
|
|
def __prepend_signal_declarations(self):
|
|
pass
|
|
|
|
|
|
def __add_access_rtl(self):
|
|
# Not all access types are required and the order might differ
|
|
# depending on what types are defined and what precedence is
|
|
# set. Therefore, first add all RTL into a dictionary and
|
|
# later place it in the right order.
|
|
#
|
|
# The following RTL blocks are defined:
|
|
# - hw_write --> write access for the hardware interface
|
|
# - sw_write --> write access for the software interface
|
|
#
|
|
access_rtl = dict([])
|
|
|
|
# Define hardware access (if applicable)
|
|
access_rtl['hw_write'] = []
|
|
|
|
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']['rtl'].format(
|
|
negl = '!' if self.obj.get_property('wel') else '',
|
|
path = self.path_underscored,
|
|
genvars = self.genvars_str))
|
|
else:
|
|
access_rtl['hw_write'].append(
|
|
Field.templ_dict['hw_access_no_we_wel']['rtl'])
|
|
|
|
access_rtl['hw_write'].append(
|
|
Field.templ_dict['hw_access_field']['rtl'].format(
|
|
path = self.path_underscored,
|
|
genvars = self.genvars_str))
|
|
|
|
self.yaml_signals_to_list(Field.templ_dict['hw_access_field'])
|
|
|
|
# Define software access (if applicable)
|
|
access_rtl['sw_write'] = []
|
|
|
|
if self.sw_access in (AccessType.rw, AccessType.w):
|
|
swwe = self.obj.get_property('swwe')
|
|
swwel = self.obj.get_property('swwel')
|
|
|
|
if isinstance(swwe, (FieldNode, SignalNode)):
|
|
access_rtl['sw_write'].append(
|
|
Field.templ_dict['sw_access_field_swwe']['rtl'].format(
|
|
path_wo_field = self.path_wo_field,
|
|
genvars = self.genvars_str,
|
|
swwe = Component.get_signal_name(swwe)))
|
|
elif isinstance(swwel, (FieldNode, SignalNode)):
|
|
access_rtl['sw_write'].append(
|
|
Field.templ_dict['sw_access_field_swwel']['rtl'].format(
|
|
path_wo_field = self.path_wo_field,
|
|
genvars = self.genvars_str,
|
|
swwel = Component.get_signal_name(swwel)))
|
|
else:
|
|
access_rtl['sw_write'].append(
|
|
Field.templ_dict['sw_access_field']['rtl'].format(
|
|
path_wo_field = self.path_wo_field,
|
|
genvars = self.genvars_str))
|
|
|
|
# Check if an onwrite property is set
|
|
onwrite = self.obj.get_property('onwrite')
|
|
|
|
if onwrite:
|
|
if onwrite == OnWriteType.wuser:
|
|
self.logger.warning("The OnReadType.wuser is not yet supported!")
|
|
elif onwrite in (OnWriteType.wclr, OnWriteType.wset):
|
|
access_rtl['sw_write'].append(
|
|
Field.templ_dict[str(onwrite)]['rtl'].format(
|
|
path = self.path_underscored,
|
|
genvars = self.genvars_str,
|
|
path_wo_field = self.path_wo_field
|
|
)
|
|
)
|
|
else:
|
|
# If field spans multiple bytes, every byte shall have a seperate enable!
|
|
for j, i in enumerate(range(self.lsbyte, self.msbyte+1)):
|
|
access_rtl['sw_write'].append(
|
|
Field.templ_dict[str(onwrite)]['rtl'].format(
|
|
path = self.path_underscored,
|
|
genvars = self.genvars_str,
|
|
i = i,
|
|
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))))
|
|
else:
|
|
# Normal write
|
|
# If field spans multiple bytes, every byte shall have a seperate enable!
|
|
for j, i in enumerate(range(self.lsbyte, self.msbyte+1)):
|
|
access_rtl['sw_write'].append(
|
|
Field.templ_dict['sw_access_byte']['rtl'].format(
|
|
path = self.path_underscored,
|
|
genvars = self.genvars_str,
|
|
i = i,
|
|
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))))
|
|
|
|
access_rtl['sw_write'].append("end")
|
|
|
|
onread = self.obj.get_property('onread')
|
|
|
|
access_rtl['sw_read'] = []
|
|
if self.sw_access in (AccessType.rw, AccessType.r) and onread:
|
|
if onread == OnReadType.ruser:
|
|
self.logger.warning("The OnReadType.ruser is not yet supported!")
|
|
else:
|
|
access_rtl['sw_read'].append(
|
|
Field.templ_dict[str(onread)]['rtl'].format(
|
|
path = self.path_underscored,
|
|
genvars = self.genvars_str,
|
|
path_wo_field = self.path_wo_field
|
|
)
|
|
)
|
|
|
|
# Add singlepulse property
|
|
if self.obj.get_property('singlepulse'):
|
|
access_rtl['singlepulse'] = [
|
|
Field.templ_dict['singlepulse']['rtl'].format(
|
|
path = self.path_underscored,
|
|
genvars = self.genvars_str)
|
|
]
|
|
else:
|
|
access_rtl['singlepulse'] = []
|
|
|
|
# Define else
|
|
access_rtl['else'] = ["else"]
|
|
|
|
# Add empty string
|
|
access_rtl[''] = ['']
|
|
|
|
# Check if hardware has precedence (default `precedence = sw`)
|
|
if self.precedence == PrecedenceType.sw:
|
|
order_list = [
|
|
'sw_write',
|
|
'sw_read',
|
|
'hw_write',
|
|
'singlepulse'
|
|
]
|
|
else:
|
|
order_list = [
|
|
'hw_write',
|
|
'sw_write',
|
|
'sw_read',
|
|
'singlepulse'
|
|
]
|
|
|
|
# Add appropriate else
|
|
order_list_rtl = []
|
|
|
|
for i in order_list:
|
|
# Still a loop and not a list comprehension since this might
|
|
# 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")
|
|
|
|
# Remove last pop
|
|
order_list_rtl.pop()
|
|
|
|
# Chain access RTL to the rest of the RTL
|
|
self.rtl_header = [*self.rtl_header, *order_list_rtl]
|
|
|
|
self.rtl_header.append(
|
|
Field.templ_dict['end_field_ff']['rtl'].format(
|
|
path = self.path_underscored))
|
|
|
|
|
|
def __add_ports(self):
|
|
if self.hw_access in (AccessType.rw, AccessType.r):
|
|
# Connect flops to output port
|
|
self.rtl_header.append(
|
|
Field.templ_dict['out_port_assign']['rtl'].format(
|
|
genvars = self.genvars_str,
|
|
path = self.path_underscored))
|
|
|
|
self.yaml_signals_to_list(Field.templ_dict['out_port_assign'])
|
|
|
|
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.")
|
|
|
|
|
|
# TODO: Counter & hw=r shouldn't work
|