Add initial support for memory type registers

This commit is contained in:
Dennis Potter 2021-10-18 23:40:19 -07:00
parent fd7acae701
commit 4d3f302a54
Signed by: Dennis
GPG Key ID: 186A8AD440942BAF
8 changed files with 442 additions and 61 deletions

View File

@ -9,6 +9,7 @@ from systemrdl import node
from components.component import Component from components.component import Component
from components.regfile import RegFile from components.regfile import RegFile
from components.register import Register from components.register import Register
from components.memory import Memory
from . import templates from . import templates
from . import widgets from . import widgets
@ -45,6 +46,7 @@ class AddrMap(Component):
# by name (for example, in case of aliases) # by name (for example, in case of aliases)
self.registers = dict() self.registers = dict()
self.regfiles = dict() self.regfiles = dict()
self.mems = dict()
self.regwidth = 0 self.regwidth = 0
# Traverse through children # Traverse through children
@ -55,8 +57,11 @@ class AddrMap(Component):
self.logger.info('Found hierarchical addrmap. Entering it...') self.logger.info('Found hierarchical addrmap. Entering it...')
self.logger.error('Child addrmaps are not implemented yet!') self.logger.error('Child addrmaps are not implemented yet!')
elif isinstance(child, node.RegfileNode): elif isinstance(child, node.RegfileNode):
self.regfiles[child.inst_name] = \ new_child = RegFile(child, [], [], config, glbl_settings)
RegFile(child, [], [], config, glbl_settings) self.regfiles[child.inst_name] = new_child
elif isinstance(child, node.MemNode):
new_child = Memory(child, [], [], config, glbl_settings)
self.mems[child.inst_name] = new_child
elif isinstance(child, node.RegNode): elif isinstance(child, node.RegNode):
if child.inst.is_alias: if child.inst.is_alias:
# If the node we found is an alias, we shall not create a # If the node we found is an alias, we shall not create a
@ -65,11 +70,11 @@ class AddrMap(Component):
self.registers[child.inst.alias_primary_inst.inst_name]\ self.registers[child.inst.alias_primary_inst.inst_name]\
.add_alias(child) .add_alias(child)
else: else:
self.registers[child.inst_name] = \ new_child = Register(child, [], [], config, glbl_settings)
Register(child, [], [], config, glbl_settings) self.registers[child.inst_name] = new_child
try: try:
if (regwidth := self.registers[child.inst_name].get_regwidth()) > self.regwidth: if (regwidth := new_child.get_regwidth()) > self.regwidth:
self.regwidth = regwidth self.regwidth = regwidth
except KeyError: except KeyError:
# Simply ignore nodes like SignalNodes # Simply ignore nodes like SignalNodes
@ -80,12 +85,12 @@ class AddrMap(Component):
# Add registers to children. This must be done in a last step # Add registers to children. This must be done in a last step
# to account for all possible alias combinations # to account for all possible alias combinations
self.children = {**self.regfiles, **self.registers} self.children = {**self.regfiles, **self.registers, **self.mems}
self.logger.info("Done generating all child-regfiles/registers") self.logger.info("Done generating all child-regfiles/registers")
# Create RTL of all registers. Registers in regfiles are # Create RTL of all registers. Registers in regfiles are
# already built. # already built and so are memories.
[x.create_rtl() for x in self.registers.values()] [x.create_rtl() for x in self.registers.values()]
# Add bus widget ports # Add bus widget ports
@ -119,6 +124,7 @@ class AddrMap(Component):
# Input ports # Input ports
# Yay for unreadable code....
input_ports_rtl = [ input_ports_rtl = [
AddrMap.templ_dict['input_port']['rtl'].format( AddrMap.templ_dict['input_port']['rtl'].format(
name = key, name = key,
@ -215,6 +221,9 @@ class AddrMap(Component):
self.rtl_footer.append('endmodule') self.rtl_footer.append('endmodule')
def __create_mux_string(self): def __create_mux_string(self):
#TODO: For optimal synthesis results, think about using 1B offsets rather than awkard 4B.
# for byte-access, byte-enables are used anyway
# Define default case # Define default case
list_of_cases = [AddrMap.templ_dict['default_mux_case']['rtl']] list_of_cases = [AddrMap.templ_dict['default_mux_case']['rtl']]
@ -234,12 +243,17 @@ class AddrMap(Component):
r2b_data = ''.join([mux_entry[0][0], mux_entry[1][1]]) r2b_data = ''.join([mux_entry[0][0], mux_entry[1][1]])
r2b_rdy = ''.join([mux_entry[0][1], mux_entry[1][1]]) r2b_rdy = ''.join([mux_entry[0][1], mux_entry[1][1]])
r2b_err = ''.join([mux_entry[0][2], mux_entry[1][1]]) r2b_err = ''.join([mux_entry[0][2], mux_entry[1][1]])
index = mux_entry[0][3] + mux_entry[1][0]
if child.__class__.__name__ == "Memory":
index = \
f"[{self.config['addrwidth']}'d{mux_entry[0][3][0]}:"\
f"{self.config['addrwidth']}'d{mux_entry[0][3][1]}]"
else:
index = f"{self.config['addrwidth']}'d{mux_entry[0][3] + mux_entry[1][0]}"
list_of_cases.append( list_of_cases.append(
AddrMap.templ_dict['list_of_mux_cases']['rtl'].format( AddrMap.templ_dict['list_of_mux_cases']['rtl'].format(
index = index, index = index,
bus_width = self.config['addrwidth'],
r2b_data = r2b_data, r2b_data = r2b_data,
r2b_rdy = r2b_rdy, r2b_rdy = r2b_rdy,
r2b_err = r2b_err) r2b_err = r2b_err)
@ -393,13 +407,3 @@ class AddrMap(Component):
def get_regwidth(self) -> int: def get_regwidth(self) -> int:
return self.regwidth return self.regwidth
def get_description(self):
if self.config['descriptions']['addrmap']:
if desc := self.obj.get_property('desc'):
return self.process_yaml(
AddrMap.templ_dict['addrmap_desc'],
{'desc': desc},
)
return ''

View File

@ -247,9 +247,14 @@ class Component():
raise KeyError raise KeyError
for x in yaml_obj['signals']: for x in yaml_obj['signals']:
try:
array_dimensions = [] if x['no_unpacked'] else self.total_array_dimensions
except KeyError:
array_dimensions = self.total_array_dimensions
self.signals[x['name'].format(**values)] =\ self.signals[x['name'].format(**values)] =\
(x['signal_type'].format(**values), (x['signal_type'].format(**values),
self.total_array_dimensions) array_dimensions)
except (TypeError, KeyError): except (TypeError, KeyError):
pass pass
@ -258,9 +263,14 @@ class Component():
raise KeyError raise KeyError
for x in yaml_obj['input_ports']: for x in yaml_obj['input_ports']:
try:
array_dimensions = [] if x['no_unpacked'] else self.total_array_dimensions
except KeyError:
array_dimensions = self.total_array_dimensions
self.ports['input'][x['name'].format(**values)] =\ self.ports['input'][x['name'].format(**values)] =\
(x['signal_type'].format(**values), (x['signal_type'].format(**values),
self.total_array_dimensions) array_dimensions)
except (TypeError, KeyError): except (TypeError, KeyError):
pass pass
@ -269,9 +279,14 @@ class Component():
raise KeyError raise KeyError
for x in yaml_obj['output_ports']: for x in yaml_obj['output_ports']:
try:
array_dimensions = [] if x['no_unpacked'] else self.total_array_dimensions
except KeyError:
array_dimensions = self.total_array_dimensions
self.ports['output'][x['name'].format(**values)] =\ self.ports['output'][x['name'].format(**values)] =\
(x['signal_type'].format(**values), (x['signal_type'].format(**values),
self.total_array_dimensions) array_dimensions)
except (TypeError, KeyError): except (TypeError, KeyError):
pass pass
@ -319,3 +334,12 @@ class Component():
return (owning_addrmap, full_path, path, path_underscored) return (owning_addrmap, full_path, path, path_underscored)
def get_description(self):
if self.config['descriptions'][self.__class__.__name__]:
if desc := self.obj.get_property('desc'):
return self.process_yaml(
self.templ_dict['description'],
{'desc': desc},
)
return ''

View File

@ -1372,13 +1372,3 @@ class Field(Component):
self.logger.error("It's not possible to combine the sticky(bit) "\ self.logger.error("It's not possible to combine the sticky(bit) "\
"property with the counter property. The counter property "\ "property with the counter property. The counter property "\
"will be ignored.") "will be ignored.")
def get_description(self):
if self.config['descriptions']['field']:
if desc := self.obj.get_property('desc'):
return self.process_yaml(
Field.templ_dict['field_desc'],
{'desc': desc},
)
return ''

View File

@ -0,0 +1,210 @@
import re
import importlib.resources as pkg_resources
import sys
import math
import yaml
from systemrdl import node
from systemrdl.node import FieldNode
from systemrdl.rdltypes import AccessType
# Local packages
from components.component import Component
from . import templates
class Memory(Component):
# Save YAML template as class variable
templ_dict = yaml.load(
pkg_resources.read_text(templates, 'memory.yaml'),
Loader=yaml.FullLoader)
def __init__(
self,
obj: node.RegfileNode,
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)
# Set object to 0 for easy addressing
self.obj.current_idx = [0]
# When in a memory, we are not going to traverse through any of the
# children. This is a simple pass-through between software and a
# fixed memory block
self.rtl_header.append(
self.process_yaml(
Memory.templ_dict['memory_adr_assignments'],
{'path': self.path_underscored,
'bytes_w': int(self.get_regwidth() / 8),
'lower_bound': obj.absolute_address,
'upper_bound': obj.absolute_address + obj.total_size,
'addr_w': self.mementries.bit_length(),
}
)
)
if obj.get_property('sw') in (AccessType.rw, AccessType.r):
self.rtl_header.append(
self.process_yaml(
Memory.templ_dict['memory_rd_assignments'],
{'path': self.path_underscored,
'data_w': self.get_regwidth() - 1,
}
)
)
if obj.get_property('sw') in (AccessType.rw, AccessType.w):
self.rtl_header.append(
self.process_yaml(
Memory.templ_dict['memory_wr_assignments'],
{'path': self.path_underscored,
'data_w': self.get_regwidth() - 1,
}
)
)
# Assign variables that go to register bus multiplexer
self.__add_sw_mux_assignments()
# 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. Also add a description, if applicable
self.rtl_header = [
self.process_yaml(
self.templ_dict['mem_comment'],
{'inst_name': obj.inst_name,
'type_name': obj.type_name,
'memory_width': self.memwidth,
'memory_depth': self.mementries,
'dimensions': self.dimensions,
'depth': self.depth}
),
self.get_description(),
*self.rtl_header
]
def __process_variables(self,
obj: node.RegfileNode,
parents_dimensions: list,
parents_stride: list,
glbl_settings: dict):
self.mementries = obj.get_property('mementries')
self.memwidth = obj.get_property('memwidth')
self.addr_w = self.mementries.bit_length()
if not math.log2(self.memwidth).is_integer():
self.logger.fatal( "The defined memory width must be a power of 2. "\
f"it is now defined as '{self.memwidth}'")
sys.exit(1)
# Geneate already started?
self.generate_active = glbl_settings['generate_active']
# Determine dimensions of register
if obj.is_array:
self.total_array_dimensions = [*parents_dimensions, *self.obj.array_dimensions]
self.array_dimensions = self.obj.array_dimensions
self.logger.warning("The memory is defined as array. The compiler not not "\
"provide any hooks to help here and expects that the user "\
"handles this outside of the memory block.")
if self.obj.array_stride != int(self.mementries * self.memwidth / 8):
self.logger.warning(f"The memory's stride ({self.obj.array_stride}) "\
f"is unequal to the depth of the memory ({self.mementries} "\
f"* {self.memwidth} / 8 = "\
f"{int(self.mementries * self.memwidth / 8)}). This must be "\
"kept in mind when hooking up the memory interface to an "\
"external memory block.")
else:
self.total_array_dimensions = parents_dimensions
self.array_dimensions = []
self.total_stride = parents_stride
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)
def __add_sw_mux_assignments(self):
# Create list of mux-inputs to later be picked up by carrying addrmap
self.sw_mux_assignment_var_name = [
(
self.process_yaml(
Memory.templ_dict['sw_data_assignment_var_name'],
{'path': self.path_underscored,
'accesswidth': self.memwidth - 1}
),
self.process_yaml(
Memory.templ_dict['sw_rdy_assignment_var_name'],
{'path': self.path_underscored}
),
self.process_yaml(
Memory.templ_dict['sw_err_assignment_var_name'],
{'path': self.path_underscored}
),
(self.obj.absolute_address, self.obj.absolute_address + self.obj.total_size)
)
]
if self.obj.get_property('sw') == AccessType.rw:
access_type = 'sw_data_assignment_rw'
elif self.obj.get_property('sw') == AccessType.r:
access_type = 'sw_data_assignment_ro'
else:
access_type = 'sw_data_assignment_wo'
self.rtl_footer = [
self.process_yaml(
self.templ_dict[access_type],
{'path': self.path_underscored,
'sw_data_assignment_var_name': self.sw_mux_assignment_var_name[0][0],
'sw_rdy_assignment_var_name': self.sw_mux_assignment_var_name[0][1],
'sw_err_assignment_var_name': self.sw_mux_assignment_var_name[0][2],
}
),
''
]
def create_mux_string(self):
for mux_tuple in self.sw_mux_assignment_var_name:
yield(mux_tuple, (0, ''))
def get_regwidth(self) -> int:
return self.memwidth
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 [Memory.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]

View File

@ -60,13 +60,13 @@ class RegFile(Component):
elif isinstance(child, node.RegfileNode): elif isinstance(child, node.RegfileNode):
self.obj.current_idx = [0] self.obj.current_idx = [0]
self.regfiles[child.inst_name] = \ new_child = RegFile(
RegFile(
child, child,
self.total_array_dimensions, self.total_array_dimensions,
self.total_stride, self.total_stride,
config, config,
glbl_settings) glbl_settings)
self.regfiles[child.inst_name] = new_child
elif isinstance(child, node.RegNode): elif isinstance(child, node.RegNode):
if child.inst.is_alias: if child.inst.is_alias:
# If the node we found is an alias, we shall not create a # If the node we found is an alias, we shall not create a
@ -76,16 +76,20 @@ class RegFile(Component):
.add_alias(child) .add_alias(child)
else: else:
self.obj.current_idx = [0] self.obj.current_idx = [0]
self.registers[child.inst_name] = \ new_child = Register(
Register(
child, child,
self.total_array_dimensions, self.total_array_dimensions,
self.total_stride, self.total_stride,
config, config,
glbl_settings) glbl_settings)
self.registers[child.inst_name] = new_child
if (regwidth := self.registers[child.inst_name].get_regwidth()) > self.regwidth: try:
if (regwidth := new_child.get_regwidth()) > self.regwidth:
self.regwidth = regwidth self.regwidth = regwidth
except KeyError:
# Simply ignore nodes like SignalNodes
pass
# Add registers to children. This must be done in a last step # Add registers to children. This must be done in a last step
# to account for all possible alias combinations # to account for all possible alias combinations
@ -266,13 +270,3 @@ class RegFile(Component):
def get_regwidth(self) -> int: def get_regwidth(self) -> int:
return self.regwidth return self.regwidth
def get_description(self):
if self.config['descriptions']['regfile']:
if desc := self.obj.get_property('desc'):
return self.process_yaml(
RegFile.templ_dict['regfile_desc'],
{'desc': desc},
)
return ''

View File

@ -572,13 +572,3 @@ class Register(Component):
def get_regwidth(self) -> int: def get_regwidth(self) -> int:
return self.obj.get_property('regwidth') return self.obj.get_property('regwidth')
def get_description(self):
if self.config['descriptions']['register']:
if desc := self.obj.get_property('desc'):
return self.process_yaml(
Register.templ_dict['reg_desc'],
{'desc': desc},
)
return ''

View File

@ -120,7 +120,7 @@ read_mux:
// Read multiplexer // Read multiplexer
always_comb always_comb
begin begin
case(b2r.addr) case (b2r.addr) inside
{list_of_cases} {list_of_cases}
endcase endcase
end end
@ -135,7 +135,7 @@ default_mux_case:
end end
list_of_mux_cases: list_of_mux_cases:
rtl: |- rtl: |-
{bus_width}'d{index}: {index}:
begin begin
r2b.data = {r2b_data}; r2b.data = {r2b_data};
r2b.err = {r2b_err}; r2b.err = {r2b_err};

View File

@ -0,0 +1,169 @@
---
mem_comment:
rtl: |-
/*******************************************************************
*******************************************************************
* MEMORY INSTANCE NAME : {inst_name}
* MEMORY TYPE : {type_name}
* MEMORY WIDTH : {memory_width}
* MEMORY DEPTH : {memory_depth}
* RDL DIMENSION : {dimensions}
* DEPTHS (per dimension): {depth}
*******************************************************************
*******************************************************************/
description:
rtl: |-
/**MEMORY DESCRIPTION***********************************************
{desc}
/*******************************************************************/
generate_for_start:
rtl: |-
for ({iterator} = 0; {iterator} < {limit}; {iterator}++)
begin
generate_for_end:
rtl: |-
end // of for loop with iterator {dimension}
memory_adr_assignments:
rtl: |-
/**********************************
* Address of memory *
**********************************
* This interface provides the address of a read/write,
* relative to the start of the memory instance.
*
* The address is divided so that byte-addresses are
* translated full memory entries
*/
assign {path}_mem_address = (b2r.addr - {lower_bound}) / {bytes_w};
assign {path}_mem_active = {path}_mem_address >= {lower_bound} && {path}_mem_address < {upper_bound};
signals:
- name: '{path}_mem_active'
signal_type: 'logic'
no_unpacked: True
output_ports:
- name: '{path}_mem_address'
signal_type: 'logic [{addr_w}:0]'
no_unpacked: True
memory_rd_assignments:
rtl: |-
/**********************************
* Handle memory read interface *
**********************************
* The '{path}_mem_r_req' output will be asserted once a read
* is requested by the bus and will stay high until '{path}_mem_r_ack'
* gets set. During a read, byte-enables will be ignored.
*
* '{path}_mem_r_ack' shall be held 1'b1 until all fields in the register
* acknowledged the read. In practice, this means until '{path}_mem_r_req'
* goes back to 1'b0.
*
* If '{path}_mem_r_err' gets set, it must also be held during the
* complete time '{path}_mem_r_ack' is high.
*/
// Request read signal
assign {path}_mem_r_req = {path}_mem_active && b2r.r_vld;
input_ports:
- name: '{path}_mem_r_data'
signal_type: '[{data_w}:0]'
no_unpacked: True
- name: '{path}_mem_r_ack'
signal_type: ''
no_unpacked: True
- name: '{path}_mem_r_err'
signal_type: ''
no_unpacked: True
output_ports:
- name: '{path}_mem_r_req'
signal_type: 'logic'
no_unpacked: True
memory_wr_assignments:
rtl: |-
/***********************************
* Handle memory write interface *
***********************************
* The '{path}_mem_w_req' output will be asserted once a write
* is requested by the bus and will stay high until '{path}_mem_w_ack'
* gets set. During a write, hardware shall not touch any bits that
* are not defined in '{path}_mem_w_mask'.
*
* '{path}_mem_w_ack' shall be held 1'b1 until all fields in the register
* acknowledged the read. In practice, this means until '{path}_mem_w_req'
* goes back to 1'b0.
*
* If '{path}_mem_w_err' gets set, it must also be held during the
* complete time '{path}_mem_w_ack' is high.
*/
// Write request
assign {path}_mem_w_req = {path}_mem_active && b2r.w_vld;
// Assign value from bus to output
assign {path}_mem_w_data = b2r.data;
output_ports:
- name: '{path}_mem_w_req'
signal_type: 'logic'
no_unpacked: True
- name: '{path}_mem_w_data'
signal_type: 'logic [{data_w}:0]'
no_unpacked: True
input_ports:
- name: '{path}_mem_w_ack'
signal_type: ''
no_unpacked: True
- name: '{path}_mem_w_err'
signal_type: ''
no_unpacked: True
signal_declaration: |-
{type:{signal_width}} {name:{name_width}}{unpacked_dim};
sw_data_assignment_var_name:
rtl: |-
{path}_data_mux_in
signals:
- name: '{path}_data_mux_in'
signal_type: 'logic [{accesswidth}:0]'
no_unpacked: True
sw_err_assignment_var_name:
rtl: |-
{path}_err_mux_in
signals:
- name: '{path}_err_mux_in'
signal_type: 'logic'
no_unpacked: True
sw_rdy_assignment_var_name:
rtl: |-
{path}_rdy_mux_in
signals:
- name: '{path}_rdy_mux_in'
signal_type: 'logic'
no_unpacked: True
sw_data_assignment_ro:
rtl: |-
/**************************************
* Assign memory to Mux *
**************************************/
assign {sw_data_assignment_var_name} = {path}_mem_r_data;
assign {sw_rdy_assignment_var_name} = {path}_mem_r_ack;
assign {sw_err_assignment_var_name} = {path}_mem_r_err;
sw_data_assignment_wo:
rtl: |-
/**************************************
* Assign memory to Mux *
**************************************/
assign {sw_data_assignment_var_name} = {{{width}{{{default_val}}};
assign {sw_rdy_assignment_var_name} = {path}_mem_w_ack;
assign {sw_err_assignment_var_name} = {path}_mem_w_err;
sw_data_assignment_rw:
rtl: |-
/**************************************
* Assign memory to Mux *
**************************************/
assign {sw_data_assignment_var_name} = {path}_mem_r_data;
assign {sw_rdy_assignment_var_name} = {path}_mem_r_ack || {path}_mem_w_ack;
assign {sw_err_assignment_var_name} = ({path}_mem_r_err && {path}_mem_r_ack) || ({path}_mem_w_err && {path}_mem_w_ack);