diff --git a/srdl2sv/components/addrmap.py b/srdl2sv/components/addrmap.py index a47483f..366445b 100644 --- a/srdl2sv/components/addrmap.py +++ b/srdl2sv/components/addrmap.py @@ -9,6 +9,7 @@ from systemrdl import node from components.component import Component from components.regfile import RegFile from components.register import Register +from components.memory import Memory from . import templates from . import widgets @@ -45,6 +46,7 @@ class AddrMap(Component): # by name (for example, in case of aliases) self.registers = dict() self.regfiles = dict() + self.mems = dict() self.regwidth = 0 # Traverse through children @@ -55,8 +57,11 @@ class AddrMap(Component): self.logger.info('Found hierarchical addrmap. Entering it...') self.logger.error('Child addrmaps are not implemented yet!') elif isinstance(child, node.RegfileNode): - self.regfiles[child.inst_name] = \ - RegFile(child, [], [], config, glbl_settings) + new_child = 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): if child.inst.is_alias: # 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]\ .add_alias(child) else: - self.registers[child.inst_name] = \ - Register(child, [], [], config, glbl_settings) + new_child = Register(child, [], [], config, glbl_settings) + self.registers[child.inst_name] = new_child try: - if (regwidth := self.registers[child.inst_name].get_regwidth()) > self.regwidth: + if (regwidth := new_child.get_regwidth()) > self.regwidth: self.regwidth = regwidth except KeyError: # Simply ignore nodes like SignalNodes @@ -80,12 +85,12 @@ class AddrMap(Component): # Add registers to children. This must be done in a last step # 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") # 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()] # Add bus widget ports @@ -119,6 +124,7 @@ class AddrMap(Component): # Input ports + # Yay for unreadable code.... input_ports_rtl = [ AddrMap.templ_dict['input_port']['rtl'].format( name = key, @@ -169,7 +175,7 @@ class AddrMap(Component): pass import_package_list.pop() - + import getpass import socket import time @@ -215,6 +221,9 @@ class AddrMap(Component): self.rtl_footer.append('endmodule') 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 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_rdy = ''.join([mux_entry[0][1], 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( AddrMap.templ_dict['list_of_mux_cases']['rtl'].format( index = index, - bus_width = self.config['addrwidth'], r2b_data = r2b_data, r2b_rdy = r2b_rdy, r2b_err = r2b_err) @@ -393,13 +407,3 @@ class AddrMap(Component): def get_regwidth(self) -> int: 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 '' diff --git a/srdl2sv/components/component.py b/srdl2sv/components/component.py index fd3bcbc..0698e8c 100644 --- a/srdl2sv/components/component.py +++ b/srdl2sv/components/component.py @@ -247,9 +247,14 @@ class Component(): raise KeyError 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)] =\ (x['signal_type'].format(**values), - self.total_array_dimensions) + array_dimensions) except (TypeError, KeyError): pass @@ -258,9 +263,14 @@ class Component(): raise KeyError 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)] =\ (x['signal_type'].format(**values), - self.total_array_dimensions) + array_dimensions) except (TypeError, KeyError): pass @@ -269,9 +279,14 @@ class Component(): raise KeyError 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)] =\ (x['signal_type'].format(**values), - self.total_array_dimensions) + array_dimensions) except (TypeError, KeyError): pass @@ -319,3 +334,12 @@ class Component(): 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 '' diff --git a/srdl2sv/components/field.py b/srdl2sv/components/field.py index 71aaecc..809d0de 100644 --- a/srdl2sv/components/field.py +++ b/srdl2sv/components/field.py @@ -1372,13 +1372,3 @@ class Field(Component): self.logger.error("It's not possible to combine the sticky(bit) "\ "property with the counter property. The counter property "\ "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 '' diff --git a/srdl2sv/components/memory.py b/srdl2sv/components/memory.py new file mode 100644 index 0000000..5a1fff0 --- /dev/null +++ b/srdl2sv/components/memory.py @@ -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] diff --git a/srdl2sv/components/regfile.py b/srdl2sv/components/regfile.py index 9f4e741..147a064 100644 --- a/srdl2sv/components/regfile.py +++ b/srdl2sv/components/regfile.py @@ -60,13 +60,13 @@ class RegFile(Component): elif isinstance(child, node.RegfileNode): self.obj.current_idx = [0] - self.regfiles[child.inst_name] = \ - RegFile( + new_child = RegFile( child, self.total_array_dimensions, self.total_stride, config, glbl_settings) + self.regfiles[child.inst_name] = new_child elif isinstance(child, node.RegNode): if child.inst.is_alias: # If the node we found is an alias, we shall not create a @@ -76,16 +76,20 @@ class RegFile(Component): .add_alias(child) else: self.obj.current_idx = [0] - self.registers[child.inst_name] = \ - Register( + new_child = Register( child, self.total_array_dimensions, self.total_stride, config, glbl_settings) + self.registers[child.inst_name] = new_child - if (regwidth := self.registers[child.inst_name].get_regwidth()) > self.regwidth: - self.regwidth = regwidth + try: + if (regwidth := new_child.get_regwidth()) > self.regwidth: + self.regwidth = regwidth + except KeyError: + # Simply ignore nodes like SignalNodes + pass # Add registers to children. This must be done in a last step # to account for all possible alias combinations @@ -266,13 +270,3 @@ class RegFile(Component): def get_regwidth(self) -> int: 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 '' diff --git a/srdl2sv/components/register.py b/srdl2sv/components/register.py index 43cdf21..2ec333e 100644 --- a/srdl2sv/components/register.py +++ b/srdl2sv/components/register.py @@ -572,13 +572,3 @@ class Register(Component): def get_regwidth(self) -> int: 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 '' diff --git a/srdl2sv/components/templates/addrmap.yaml b/srdl2sv/components/templates/addrmap.yaml index 4fe021c..e4c063f 100644 --- a/srdl2sv/components/templates/addrmap.yaml +++ b/srdl2sv/components/templates/addrmap.yaml @@ -120,7 +120,7 @@ read_mux: // Read multiplexer always_comb begin - case(b2r.addr) + case (b2r.addr) inside {list_of_cases} endcase end @@ -135,7 +135,7 @@ default_mux_case: end list_of_mux_cases: rtl: |- - {bus_width}'d{index}: + {index}: begin r2b.data = {r2b_data}; r2b.err = {r2b_err}; diff --git a/srdl2sv/components/templates/memory.yaml b/srdl2sv/components/templates/memory.yaml new file mode 100644 index 0000000..644a7d9 --- /dev/null +++ b/srdl2sv/components/templates/memory.yaml @@ -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);