srdl2sv/srdl2sv/components/register.py

570 lines
22 KiB
Python

import importlib.resources as pkg_resources
import math
import sys
import yaml
import itertools
from systemrdl import node
# Local modules
from components.component import Component
from components.field import Field
from . import templates
class Register(Component):
# Save YAML template as class variable
templ_dict = yaml.load(
pkg_resources.read_text(templates, 'register.yaml'),
Loader=yaml.FullLoader)
def __init__(
self,
obj: node.RegNode,
parents_dimensions: list,
parents_stride: list,
config: dict,
glbl_settings: dict):
super().__init__(obj, config)
# Save and/or process important variables
self.__process_variables(obj, parents_dimensions, parents_stride, glbl_settings)
self.config['external'] = obj.external
# Create RTL for fields of initial, non-alias register
for field in obj.fields():
# Use range to save field in an array. Reason is, names are allowed to
# change when using an alias
field_range = ':'.join(map(str, [field.msb, field.lsb]))
self.children[field_range] = Field(field,
self.total_array_dimensions,
self.config,
glbl_settings)
# Get properties from field that apply to whole register
for key in self.properties:
self.properties[key] |= self.children[field_range].properties[key]
# Perform sanity check
self.children[field_range].sanity_checks()
def create_rtl(self):
# Create RTL of children
if self.config['external']:
[x.create_external_rtl() for x in self.children.values()]
else:
[x.create_internal_rtl() for x in self.children.values()]
# Create generate block for register and add comment
if self.dimensions and not self.generate_active:
self.rtl_header.append("generate")
# Add N layers of for-loop starts
for i in range(self.dimensions):
self.rtl_header.append(
Register.templ_dict['generate_for_start'].format(
iterator = ''.join(['gv_', chr(97+i+self.parents_depths)]),
limit = self.array_dimensions[i]))
# Add decoders for all registers & aliases
self.__add_address_decoder()
# Fields will be added by get_rtl()
# Add interrupt logic
self.__add_interrupts()
# Add assignment of read-wires
self.__add_sw_mux_assignments()
# Add N layers of for-loop end
for i in range(self.dimensions-1, -1, -1):
self.rtl_footer.append(
Register.templ_dict['generate_for_end'].format(
dimension = ''.join(['gv_', chr(97+i)])))
if self.dimensions and not self.generate_active:
self.rtl_footer.append("\nendgenerate\n")
# Add wire instantiation
if not self.generate_active:
# We can/should only do this if there is no encapsulating
# regfile which create a generate
self.__add_signal_instantiations()
# Create comment and provide user information about register he/she is looking at
self.rtl_header = [
Register.templ_dict['reg_comment'].format(
name = self.obj.inst_name,
dimensions = self.dimensions,
depth = self.depth),
*self.rtl_header
]
def __add_interrupts(self):
# Semantics on the intr and halt property:
# a) The intr and halt register properties are outputs; they should only
# occur on the right-hand side of an assignment in SystemRDL.
# b) The intr property shall always be present on a intr register even if
# no mask or enables are specified.
# c) The halt property shall only be present if haltmask or haltenable is
# specified on at least one field in the register.
if self.properties['intr']:
self.rtl_footer.append(Register.templ_dict['interrupt_comment']['rtl'])
self.rtl_footer.append(
self.process_yaml(
Register.templ_dict['interrupt_intr'],
{'path': self.path_underscored,
'genvars': self.genvars_str,
'list': ') || |('.join([
x.itr_masked for x in self.children.values() if x.itr_masked])
}
)
)
if self.properties['halt']:
self.rtl_footer.append(
self.process_yaml(
Register.templ_dict['interrupt_halt'],
{'path': self.path_underscored,
'genvars': self.genvars_str,
'list': ') || |('.join([
x.itr_haltmasked for x in self.children.values() if x.itr_haltmasked])
}
)
)
def __add_sw_mux_assignments(self):
accesswidth = self.obj.get_property('accesswidth') - 1
self.rtl_footer.append("")
for na_map in self.name_addr_mappings:
current_bit = 0
# Start tracking errors
# Handle fields
list_of_fields = []
bytes_read = set()
bytes_written = set()
for field in self.children.values():
if na_map[0] in field.readable_by:
empty_bits = field.lsb - current_bit
current_bit = field.msb + 1
if empty_bits > 0:
list_of_fields.append(
f"{{{empty_bits}{{1'b{self.glbl_settings['rsvd_val']}}}}}")
list_of_fields.append("{}_q{}".format(field.path_underscored, self.genvars_str))
# Add to appropriate bytes
[bytes_read.add(x) for x in range(field.lsbyte, field.msbyte+1)]
if na_map[0] in field.writable_by:
# Add to appropriate bytes
[bytes_written.add(x) for x in range(field.lsbyte, field.msbyte+1)]
empty_bits = accesswidth - current_bit + 1
no_reads = not list_of_fields
if empty_bits > 0:
list_of_fields.append(
f"{{{empty_bits}{{1'b{self.glbl_settings['rsvd_val']}}}}}")
# Create list of mux-inputs to later be picked up by carrying addrmap
self.sw_mux_assignment_var_name.append(
(
self.process_yaml(
Register.templ_dict['sw_data_assignment_var_name'],
{'path': na_map[0],
'accesswidth': accesswidth}
),
self.process_yaml(
Register.templ_dict['sw_rdy_assignment_var_name'],
{'path': na_map[0]}
),
self.process_yaml(
Register.templ_dict['sw_err_assignment_var_name'],
{'path': na_map[0]}
),
na_map[1], # Start addr
)
)
# Return an error if *no* read or *no* write can be succesful.
# If some bits cannot be read/write but others are succesful, don't return
# an error.
#
# Furthermore, consider an error indication that is set for external registers
bytes_read_format = ["b2r.byte_en[{}]".format(x) for x in list(map(str, bytes_read))]
bytes_written_format = ["b2r.byte_en[{}]".format(x) for x in list(map(str, bytes_written))]
sw_err_condition_vec = []
sw_err_condition_vec.append(self.process_yaml(
Register.templ_dict['sw_err_condition'],
{'rd_byte_list_ored':
' || '.join(bytes_read_format) if bytes_read else "1'b0",
'wr_byte_list_ored':
' || '.join(bytes_written_format) if bytes_written else "1'b0"}
)
)
if self.config['external']:
if bytes_read:
for field in self.children.values():
sw_err_condition_vec.append(self.process_yaml(
Register.templ_dict['external_err_condition'],
{'path': '__'.join([na_map[0], field.name]),
'genvars': self.genvars_str,
'rd_or_wr': 'r'}
)
)
if bytes_written:
for field in self.children.values():
sw_err_condition_vec.append(self.process_yaml(
Register.templ_dict['external_err_condition'],
{'path': '__'.join([na_map[0], field.name]),
'genvars': self.genvars_str,
'rd_or_wr': 'w'}
)
)
sw_err_condition = ' || '.join(sw_err_condition_vec)
# If registers are implemented in RTL, they will be ready immediately. However,
# if they are defined as 'external', there might be some delay
if self.config['external']:
if bytes_read:
sw_rdy_condition_vec = ['(']
for field in self.children.values():
sw_rdy_condition_vec.append(self.process_yaml(
Register.templ_dict['external_rdy_condition'],
{'path': '__'.join([na_map[0], field.name]),
'genvars': self.genvars_str,
'rd_or_wr': 'r'}
)
)
sw_rdy_condition_vec.append(' && ')
sw_rdy_condition_vec.pop()
sw_rdy_condition_vec.append(' && b2r.r_vld)')
if bytes_read and bytes_written:
sw_rdy_condition_vec.append(' || ')
if bytes_written:
sw_rdy_condition_vec.append('(')
for field in self.children.values():
sw_rdy_condition_vec.append(self.process_yaml(
Register.templ_dict['external_rdy_condition'],
{'path': '__'.join([na_map[0], field.name]),
'genvars': self.genvars_str,
'rd_or_wr': 'w'}
)
)
sw_rdy_condition_vec.append(' && ')
sw_rdy_condition_vec.pop()
sw_rdy_condition_vec.append(' && b2r.w_vld)')
sw_rdy_condition = ''.join(sw_rdy_condition_vec)
else:
sw_rdy_condition = "1'b1"
# Assign all values
self.rtl_footer.append(
self.process_yaml(
Register.templ_dict['sw_data_assignment'],
{'sw_data_assignment_var_name': self.sw_mux_assignment_var_name[-1][0],
'sw_rdy_assignment_var_name': self.sw_mux_assignment_var_name[-1][1],
'sw_err_assignment_var_name': self.sw_mux_assignment_var_name[-1][2],
'genvars': self.genvars_str if not no_reads else '',
'rdy_condition': sw_rdy_condition,
'err_condition': sw_err_condition,
'list_of_fields': ', '.join(reversed(list_of_fields))}
)
)
def create_mux_string(self):
for mux_tuple in self.sw_mux_assignment_var_name:
# Loop through lowest dimension and add stride of higher
# dimension once everything is processed
if self.total_array_dimensions:
vec = [0]*len(self.total_array_dimensions)
for i in self.eval_genvars(vec, 0, self.total_array_dimensions):
yield (mux_tuple, i)
else:
yield(mux_tuple, (0, ''))
def eval_genvars(self, vec, depth, dimensions):
for i in range(dimensions[depth]):
vec[depth] = i
if depth == len(dimensions) - 1:
yield (
eval(self.genvars_sum_str_vectorized),
'[{}]'.format(']['.join(map(str, vec)))
)
else:
yield from self.eval_genvars(vec, depth+1, dimensions)
vec[depth] = 0
def __add_address_decoder(self):
# Assign variables from bus
self.obj.current_idx = [0]
if self.total_dimensions:
access_wire_assign_field = 'access_wire_assign_multi_dim'
else:
access_wire_assign_field = 'access_wire_assign_1_dim'
for i, x in enumerate(self.name_addr_mappings):
self.rtl_header.append(
self.process_yaml(
Register.templ_dict['access_wire_comment'],
{'path': x[0],
'alias': '(alias)' if i > 0 else '',
}
)
)
self.rtl_header.append(
self.process_yaml(
Register.templ_dict[access_wire_assign_field],
{'path': x[0],
'addr': x[1],
'genvars': self.genvars_str,
'genvars_sum': self.genvars_sum_str,
'depth': self.depth,
}
)
)
# A wire that indicates a read is required
if self.properties['sw_rd_wire']:
# Check if a read is actually possible. Otherwise provide a wire
# that is tied to 1'b0
if self.properties['sw_rd']:
self.rtl_header.append(
self.process_yaml(
Register.templ_dict['read_wire_assign'],
{'path': x[0],
'addr': x[1],
'genvars': self.genvars_str,
'genvars_sum': self.genvars_sum_str,
'depth': self.depth,
}
)
)
else:
self.rtl_header.append(
self.process_yaml(
Register.templ_dict['read_wire_assign_0'],
{'path': x[0],
'genvars': self.genvars_str,
}
)
)
# A wire that indicates a write is required
if self.properties['sw_wr_wire']:
# Check if a write is actually possible. Otherwise provide a wire
# that is tied to 1'b0
if self.properties['sw_wr']:
self.rtl_header.append(
self.process_yaml(
Register.templ_dict['write_wire_assign'],
{'path': x[0],
'addr': x[1],
'genvars': self.genvars_str,
'genvars_sum': self.genvars_sum_str,
'depth': self.depth,
}
)
)
else:
self.rtl_header.append(
self.process_yaml(
Register.templ_dict['write_wire_assign_0'],
{'path': x[0],
'genvars': self.genvars_str,
}
)
)
# Add combined signal to be used for general access of the register
if self.properties['swacc']:
self.rtl_header.append(
self.process_yaml(
Register.templ_dict['rw_wire_assign_any_alias'],
{'path': self.name_addr_mappings[0][0],
'genvars': self.genvars_str,
'sw_rds_w_genvars': ' || '.join(
[''.join([x[0], '_sw_rd', self.genvars_str])
for x in self.name_addr_mappings]),
'sw_wrs_w_genvars': ' || '.join(
[''.join([x[0], '_sw_wr', self.genvars_str])
for x in self.name_addr_mappings])
}
)
)
def __add_signal_instantiations(self):
# Add wire/register instantiations
self.rtl_header = [
*self.get_signal_instantiations_list(),
'',
*self.rtl_header
]
def get_signal_instantiations_list(self):
dict_list = [(key, value) for (key, value) in self.get_signals().items()]
signal_width = min(max([len(value[0]) for (_, value) in dict_list]), 40)
name_width = min(max([len(key) for (key, _) in dict_list]), 40)
return [Register.templ_dict['signal_declaration'].format(
name = key,
type = value[0],
signal_width = signal_width,
name_width = name_width,
unpacked_dim = '[{}]'.format(
']['.join(
[str(y) for y in value[1]]))
if value[1] else '')
for (key, value) in dict_list]
def add_alias(self, obj: node.RegNode):
for field in obj.fields():
# Use range to save field in an array. Reason is, names are allowed to
# change when using an alias
field_range = ':'.join(map(str, [field.msb, field.lsb]))
try:
self.children[field_range].add_sw_access(field, alias=True)
except KeyError:
self.logger.fatal("Range of field '{}' in alias register '{}' does "
"not correspond to range of field in original "
"register '{}'. This is illegal according to 10.5.1 b)"
"of the SystemRDL 2.0 LRM.". format(
field.inst_name,
obj.inst_name,
self.name))
sys.exit(1)
# Add name to list
self.obj.current_idx = [0]
self.name_addr_mappings.append(
(self.create_underscored_path_static(obj)[3], obj.absolute_address))
def __process_variables(
self,
obj: node.RegNode,
parents_dimensions: list,
parents_stride: list,
glbl_settings: dict):
# Save name
self.obj.current_idx = [0]
self.name = obj.inst_name
# Save global settings
self.glbl_settings = glbl_settings
# Create mapping between (alias-) name and address
self.name_addr_mappings = [
(self.create_underscored_path_static(obj)[3], obj.absolute_address)
]
# Geneate already started?
self.generate_active = glbl_settings['generate_active']
# Empty array for mux-input signals
self.sw_mux_assignment_var_name = []
# Determine dimensions of register
if obj.is_array:
self.sel_arr = 'array'
self.total_array_dimensions = [*parents_dimensions, *self.obj.array_dimensions]
self.array_dimensions = self.obj.array_dimensions
# Merge parent's stride with stride of this regfile. Before doing so, the
# respective stride of the different dimensions shall be calculated
self.total_stride = [
*parents_stride,
*[math.prod(self.array_dimensions[i+1:])
*self.obj.array_stride
for i, _ in enumerate(self.array_dimensions)]
]
else:
self.sel_arr = 'single'
self.total_array_dimensions = parents_dimensions
self.array_dimensions = []
self.total_stride = parents_stride
# How many dimensions were already part of some higher up hierarchy?
self.parents_depths = len(parents_dimensions)
self.total_depth = '[{}]'.format(']['.join(f"{i}" for i in self.total_array_dimensions))
self.total_dimensions = len(self.total_array_dimensions)
self.depth = '[{}]'.format(']['.join(f"{i}" for i in self.array_dimensions))
self.dimensions = len(self.array_dimensions)
# Calculate how many genvars shall be added
genvars = ['[gv_{}]'.format(chr(97+i)) for i in range(self.total_dimensions)]
self.genvars_str = ''.join(genvars)
# Determine value to compare address with
genvars_sum = []
genvars_sum_vectorized = []
try:
for i, stride in enumerate(self.total_stride):
genvars_sum.append(''.join(['gv_', chr(97+i)]))
genvars_sum.append("*")
genvars_sum.append(str(stride))
genvars_sum.append("+")
genvars_sum_vectorized.append('vec[')
genvars_sum_vectorized.append(str(i))
genvars_sum_vectorized.append(']*')
genvars_sum_vectorized.append(str(stride))
genvars_sum_vectorized.append("+")
genvars_sum.pop()
genvars_sum_vectorized.pop()
self.logger.debug(
"Multidimensional with dimensions '{}' and stride '{}'".format(
self.total_array_dimensions,
self.total_stride))
except TypeError:
self.logger.debug(
"Caught expected TypeError because self.total_stride is empty")
except IndexError:
self.logger.debug(
"Caugt expected IndexError because genvars_sum is empty")
self.genvars_sum_str = ''.join(genvars_sum)
self.genvars_sum_str_vectorized = ''.join(genvars_sum_vectorized)
def get_regwidth(self) -> int:
return self.obj.get_property('regwidth')