import math import importlib.resources as pkg_resources import sys from typing import Optional import yaml from systemrdl.node import FieldNode, SignalNode from systemrdl.component import Reg, Regfile from systemrdl.rdltypes import PrecedenceType, AccessType, OnReadType, OnWriteType, InterruptType # Local modules from srdl2sv.components.component import Component, TypeDef from srdl2sv.components 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, parents_dimensions: Optional[list], config: dict): super().__init__( obj=obj, config=config, parents_strides=None, parents_dimensions=parents_dimensions) # Generate all variables that have anything to do with dimensions or strides self._init_genvars() # Save and/or process important variables self.__init_variables(obj) # Determine field types self.__init_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()) # Add description self.rtl_header.append(self.get_description()) # HW Access can be handled in __init__ function but SW access # must be handled in a seperate method that can be called # seperately in case of alias registers if not self.config['external']: self.__add_always_ff() # Only add normal hardware access if field is not an interrupt field if not self.__add_interrupt(): self.__add_hw_access() self.__add_combo() self.__add_swmod_swacc() self.__add_counter() self.add_sw_access(obj) def add_sw_access(self, obj, alias = False): access_rtl = {} if alias: owning_addrmap, full_path, path, path_underscored =\ Field.create_underscored_path_static(obj) else: owning_addrmap, full_path, path, path_underscored =\ self.owning_addrmap, self.full_path, self.path, self.path_underscored path_wo_field = '__'.join(path.split('.', -1)[0:-1]) # Define software access (if applicable) access_rtl['sw_write'] = ([], False) if self.properties['sw_wr']: # Append to list of registers that can write self.writable_by.add(path_wo_field) # This will need a wire to indicate that a write is taking place self.properties['sw_wr_wire'] = True swwe = obj.get_property('swwe') swwel = obj.get_property('swwel') if isinstance(swwe, (FieldNode, SignalNode)): access_rtl['sw_write'][0].append( self._process_yaml( Field.templ_dict['sw_access_field_swwe'], {'path_wo_field': path_wo_field, 'genvars': self.genvars_str, 'swwe': self.get_signal_name(swwe), 'field_type': self.field_type} ) ) elif isinstance(swwel, (FieldNode, SignalNode)): access_rtl['sw_write'][0].append( self._process_yaml( Field.templ_dict['sw_access_field_swwel'], {'path_wo_field': path_wo_field, 'genvars': self.genvars_str, 'swwel': self.get_signal_name(swwel), 'field_type': self.field_type} ) ) else: access_rtl['sw_write'][0].append( self._process_yaml( Field.templ_dict['sw_access_field'], {'path_wo_field': path_wo_field, 'genvars': self.genvars_str, 'field_type': self.field_type} ) ) # Check if an onwrite property is set onwrite = obj.get_property('onwrite') if onwrite: if onwrite == OnWriteType.wuser: self.logger.warning("The OnWriteType.wuser is not yet supported!") elif onwrite in (OnWriteType.wclr, OnWriteType.wset): access_rtl['sw_write'][0].append( self._process_yaml( Field.templ_dict[str(onwrite)], {'path': path_underscored, 'genvars': self.genvars_str, 'width': obj.width, 'path_wo_field': path_wo_field, 'field_type': self.field_type} ) ) else: # If field spans multiple bytes, every byte shall have a seperate enable! for i in range(self.lsbyte, self.msbyte+1): msb_bus = 8*(i+1)-1 if i != self.msbyte else obj.msb lsb_bus = 8*i if i != self.lsbyte else obj.inst.lsb access_rtl['sw_write'][0].append( self._process_yaml( Field.templ_dict[str(onwrite)], {'path': path_underscored, 'genvars': self.genvars_str, 'i': i, 'width': obj.width, 'msb_bus': str(msb_bus), 'lsb_bus': str(lsb_bus), 'msb_field': str(msb_bus-obj.inst.lsb), 'lsb_field': str(lsb_bus-obj.inst.lsb), 'field_type': self.field_type} ) ) else: # Normal write # If field spans multiple bytes, every byte shall have a seperate enable! for i in range(self.lsbyte, self.msbyte+1): msb_bus = 8*(i+1)-1 if i != self.msbyte else obj.msb lsb_bus = 8*i if i != self.lsbyte else obj.inst.lsb access_rtl['sw_write'][0].append( self._process_yaml( Field.templ_dict['sw_access_byte'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'i': i, 'msb_bus': str(msb_bus), 'lsb_bus': str(lsb_bus), 'msb_field': str(msb_bus-obj.inst.lsb), 'lsb_field': str(lsb_bus-obj.inst.lsb), 'field_type': self.field_type} ) ) access_rtl['sw_write'][0].append("end") onread = obj.get_property('onread') access_rtl['sw_read'] = ([], False) if obj.get_property('sw') in (AccessType.rw, AccessType.r): # Append to list of registers that can read self.readable_by.add(path_wo_field) self.properties['sw_wr'] = True # Set onread properties if onread == OnReadType.ruser: self.logger.error("The OnReadType.ruser is not yet supported!") elif onread: self.properties['sw_rd_wire'] = True access_rtl['sw_read'][0].append( self._process_yaml( Field.templ_dict[str(onread)], {'width': obj.width, 'path': path_underscored, 'genvars': self.genvars_str, 'path_wo_field': path_wo_field} ) ) # Add singlepulse property # Property cannot be overwritten by alias if obj.get_property('singlepulse'): self.access_rtl['singlepulse'] = ([ self._process_yaml( Field.templ_dict['singlepulse'], {'path': self.path_underscored, 'genvars': self.genvars_str} ) ], True) else: self.access_rtl['singlepulse'] = ([], False) # Add to global dictionary try: # Alias, so add 'else' self.access_rtl['sw_read'] = \ [*self.access_rtl['sw_read'], access_rtl['sw_read']] self.access_rtl['sw_write'] = \ [*self.access_rtl['sw_write'], access_rtl['sw_write']] except KeyError: self.access_rtl['sw_read'] = [access_rtl['sw_read']] self.access_rtl['sw_write'] = [access_rtl['sw_write']] self.path_underscored_vec.append(path_underscored) self.path_wo_field_vec.append(path_wo_field) def __add_counter(self): if self.obj.get_property('counter'): self.logger.debug("Detected counter property") self.rtl_footer.append(Field.templ_dict['counter_comment']['rtl']) # Determine saturation values if isinstance(self.obj.get_property('incrsaturate'), bool): if self.obj.get_property('incrsaturate'): incr_sat_value = 2**self.obj.width-1 overflow_value = incr_sat_value else: incr_sat_value = False overflow_value = 2**self.obj.width-1 else: incr_sat_value = self.obj.get_property('incrsaturate') overflow_value = incr_sat_value if isinstance(self.obj.get_property('decrsaturate'), bool): if self.obj.get_property('decrsaturate'): decr_sat_value = 0 underflow_value = decr_sat_value else: decr_sat_value = False underflow_value = 0 else: decr_sat_value = self.obj.get_property('decrsaturate') underflow_value = decr_sat_value # Determine threshold values if isinstance(self.obj.get_property('incrthreshold'), bool): if self.obj.get_property('incrthreshold'): incr_thr_value = 2**self.obj.width-1 else: incr_thr_value = False else: incr_thr_value = self.obj.get_property('incrthreshold') if isinstance(self.obj.get_property('decrthreshold'), bool): if self.obj.get_property('decrthreshold'): decr_thr_value = 2**self.obj.width-1 else: decr_thr_value = False else: decr_thr_value = self.obj.get_property('decrthreshold') # Determine with what value the counter is incremented # According to the spec, the incrvalue/decrvalue default to '1' obj_incr_value = self.obj.get_property('incrvalue') obj_decr_value = self.obj.get_property('decrvalue') obj_incr_width = self.obj.get_property('incrwidth') obj_decr_width = self.obj.get_property('decrwidth') incr_width_input = False if obj_incr_value == 0: incr_value = 0 incr_width = 1 elif obj_incr_value is None: # Increment value is not set. Check if incrwidth is set and use # that is applicable if obj_incr_width: incr_value = False incr_width = obj_incr_width incr_width_input = True # Doesn't return RTL, only adds input port self._process_yaml( Field.templ_dict['counter_incr_val_input'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr_width': incr_width-1 } ) else: # Otherwise, use default value according to LRM incr_value = '1' incr_width = 1 elif isinstance(obj_incr_value, int): # An explicit width is set for the incr_val incr_value = str(obj_incr_value) incr_width = math.floor(math.log2(obj_incr_value)+1) if obj_incr_width: self.logger.error( "The 'incrwidth' and 'incrvalue' properties are both " "defined. This is not legal and the incrwidth property " "will be ignored!") else: incr_value = self.get_signal_name(obj_incr_value) incr_width = obj_incr_value.width if obj_incr_value.width > self.obj.width: self.logger.error( f"Width of 'incr_value' signal '{obj_incr_value.get_path()}' is " "wider than current counter field. This could potentiall cause " "ugly errors.") if obj_incr_width: self.logger.error( "The 'incrwidth' and 'incrvalue' properties are both " "defined. This is not legal and the incrwidth property " "will be ignored!") # If no input is defined for the increment value, define # an internal signal. It is possible that this is tied to 0. if not incr_width_input: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_internal_incr_val_signal'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr_width': incr_width-1, 'incr_value': incr_value, } ) ) # Handle decrement value decr_width_input = False if obj_decr_value == 0: decr_value = 0 decr_width = 1 elif obj_incr_value is None: # Decrement value is not set. Check if decrwidth is set and use # that is applicable if obj_decr_width: decr_value = False decr_width = obj_decr_width decr_width_input = True # Doesn't return RTL, only adds input port self._process_yaml( Field.templ_dict['counter_decr_val_input'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'decr_width': decr_width-1 } ) else: # Otherwise, use default value according to LRM decr_value = '1' decr_width = 1 elif isinstance(obj_decr_value, int): # An explicit width is set for the decr_val decr_value = str(obj_decr_value) decr_width = math.floor(math.log2(obj_decr_value)+1) if obj_decr_width: self.logger.error( "The 'decrwidth' and 'decrvalue' properties are both " "defined. This is not legal and the decrwidth property " "will be ignored!") else: decr_value = self.get_signal_name(obj_decr_value) decr_width = obj_decr_value.width if obj_decr_value.width > self.obj.width: self.logger.error( f"Width of 'decr_value' signal '{obj_decr_value.get_path()}' is " "wider than current counter field. This could potentiall cause " "ugly errors.") if obj_decr_width: self.logger.error( "The 'decrwidth' and 'decrvalue' properties are both " "defined. This is not legal and the decrwidth property " "will be ignored!") # If no input is defined for the decrement value, define # an internal signal. It is possible that this is tied to 0. if not decr_width_input: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_internal_decr_val_signal'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'decr_width': decr_width-1, 'decr_value': decr_value, } ) ) # Handle the increment/decrement signals. # If the increment or decrement signal is not set, use an input # if the decrement value is bigger than 0 if not incr_value and not decr_value: self.logger.fatal("Illegal counter configuration! Both 'incr_value' "\ "and 'decr_value' are forced to 0. If you intended "\ "to use 'incr_width' or 'decr_width', simply don't "\ "force 'incr_value' or 'decr_value' to any value.") sys.exit(1) if incr_value: incr = self.obj.get_property('incr') if not incr: # Will only add input port but not return any RTL self._process_yaml( Field.templ_dict['counter_incr_input'], {'path': self.path_underscored} ) else: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_internal_incr_signal'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr': self.get_signal_name(incr) } ) ) try: if incr.width > 0: self.logger.error( f"Increment signal '{incr.inst_name}' is wider than " "1 bit. This might result in unwanted behavior and " "will also cause Lint-errors.") except AttributeError: # 'PropRef_overflow' object has no attribute 'width' pass else: # Tie signal to 0 self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_internal_incr_signal'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr': '0' } ) ) if decr_value: decr = self.obj.get_property('decr') if not self.obj.get_property('decr'): # Will only add input port but not return any RTL self._process_yaml( Field.templ_dict['counter_decr_input'], {'path': self.path_underscored} ) else: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_internal_decr_signal'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'decr': self.get_signal_name(decr) } ) ) try: if decr.width > 0: self.logger.error( f"Decrement signal '{decr.inst_name}' is wider than " "1 bit. This might result in unwanted behavior and " "will also cause Lint-errors.") except AttributeError: # 'PropRef_underflow' object has no attribute 'width' pass else: # Tie signal to 0 self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_internal_decr_signal'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'decr': '0' } ) ) # Handle saturation signals if not incr_sat_value: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_incr_sat_tied'], {'path': self.path_underscored, 'genvars': self.genvars_str, } ) ) else: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_incr_sat'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr_width': incr_width, 'decr_width': decr_width, 'sat_value': incr_sat_value, } ) ) if not decr_sat_value: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_decr_sat_tied'], {'path': self.path_underscored, 'genvars': self.genvars_str, } ) ) else: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_decr_sat'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr_width': incr_width, 'decr_width': decr_width, 'sat_value': decr_sat_value, } ) ) # Handle threshold values if incr_thr_value or decr_thr_value: self.rtl_footer.append(Field.templ_dict['counter_thr_comment']['rtl']) if incr_thr_value: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_incr_thr'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr_width': incr_width, 'decr_width': decr_width, 'thr_value': incr_thr_value, } ) ) if decr_thr_value: self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_decr_thr'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr_width': incr_width, 'decr_width': decr_width, 'thr_value': decr_thr_value, } ) ) # Handle overflow & underflow signals if self.obj.get_property('overflow'): self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_overflow'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr_width': incr_width, 'decr_width': decr_width, 'overflow_value': overflow_value, } ) ) if self.obj.get_property('underflow'): self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter_underflow'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'incr_width': incr_width, 'decr_width': decr_width, 'underflow_value': underflow_value, } ) ) # Implement actual counter logic self.rtl_footer.append( self._process_yaml( Field.templ_dict['counter'], {'path': self.path_underscored, 'genvars': self.genvars_str, } ) ) def __add_swmod_swacc(self): if self.obj.get_property('swmod'): self.logger.debug("Field has swmod property") swmod_assigns = [] # Check if read side-effects are defined. if self.obj.get_property('onread'): swmod_assigns.append( self._process_yaml( Field.templ_dict['swmod_assign'], {'path': self.path_underscored, 'path_wo_field': self.path_wo_field, 'genvars': self.genvars_str, 'rd_wr': 'rd', 'msbyte': self.msbyte, 'lsbyte': self.lsbyte, 'swmod_assigns': '\n'.join(swmod_assigns) } ) ) # Check if SW has write access to the field if self.properties['sw_wr']: swmod_assigns.append( self._process_yaml( Field.templ_dict['swmod_assign'], {'path': self.path_underscored, 'path_wo_field': self.path_wo_field, 'genvars': self.genvars_str, 'rd_wr': 'wr', 'msbyte': self.msbyte, 'lsbyte': self.lsbyte, 'swmod_assigns': '\n'.join(swmod_assigns) } ) ) swmod_props = self._process_yaml( Field.templ_dict['swmod_always_comb'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'swmod_assigns': '\n'.join(swmod_assigns) } ) if not swmod_assigns: self.logger.warning("Field has swmod property but the field is never "\ "modified by software.") else: swmod_props = '' if self.obj.get_property('swacc') and \ (self.properties['sw_rd'] or self.properties['sw_wr']): self.logger.debug("Field has swacc property") self.properties['swacc'] = True self.properties['sw_wr_wire'] = True self.properties['sw_rd_wire'] = True swacc_props = self._process_yaml( Field.templ_dict['swacc_assign'], {'path': self.path_underscored, 'path_wo_field': self.path_wo_field, 'genvars': self.genvars_str, 'msbyte': self.msbyte, 'lsbyte': self.lsbyte, } ) elif self.obj.get_property('swacc'): self.logger.warning("Field has swacc property but the field is never "\ "accessed by software.") swacc_props = '' else: swacc_props = '' self.rtl_footer = [*self.rtl_footer, swmod_props, swacc_props] def __add_sticky(self, latch_signal: str, force_trigger_generation: bool = False): bit_type = None trigger_signal = None if self.obj.get_property('stickybit'): bit_type = 'stickybit' elif self.obj.get_property('sticky'): bit_type = 'sticky' # Determine what causes the interrupt to get set, i.e., # is it a trigger that is passed to the module through an # input or is it an internal signal if bit_type or force_trigger_generation: if next_val := self.obj.get_property('next'): trigger_signal = self.get_signal_name(next_val) else: trigger_signal =\ self._process_yaml( Field.templ_dict['trigger_input'], {'path': self.path_underscored, 'field_type': self.field_type, } ) if bit_type: self.access_rtl['hw_write'] = ([ self._process_yaml( Field.templ_dict[bit_type], {'path': self.path_underscored, 'genvars': self.genvars_str, 'width': self.obj.width, 'field_type': self.field_type, 'trigger_signal': trigger_signal, } ) ], False) self.rtl_footer.append( self._process_yaml( Field.templ_dict[str(latch_signal)], {'trigger_signal': trigger_signal, 'path': self.path_underscored, 'genvars': self.genvars_str, 'field_type': self.field_type, } ) ) return (bit_type, trigger_signal) def __add_interrupt(self): if self.obj.get_property('intr'): self.properties['intr'] = True intr_type = self.obj.get_property('intr type') # Check if it is a sticky(bit) interrupt and generate logic sticky_type, trigger_signal = self.__add_sticky( latch_signal = intr_type, force_trigger_generation = True) # Interrupts can have special types of sticky bits: posedge, negedge, bothedge. # Normal fields with the sticky(bit) property are always level. # # The actual sticky field already got generated in __add_sticky() if sticky_type: if self.obj.width > 1 \ and sticky_type == 'sticky' \ and intr_type in \ (InterruptType.posedge, InterruptType.negedge, InterruptType.bothedge): self.logger.info( f"Found '{intr_type}' property for interrupt field that is "\ "wider than 1-bit and has the sticky (rather than the "\ "stickybit property. In this case, the value will be "\ "latched if _any_ bit in the signal changes according to "\ "'{intr_type}'" ) if intr_type != InterruptType.level: if self.rst['name']: reset_intr_header = \ self._process_yaml( Field.templ_dict['rst_intr_header'], {'trigger_signal': trigger_signal, 'rst_name': self.rst['name'], 'rst_negl': "!" if self.rst['active'] == "active_low" else "", 'genvars': self.genvars_str, 'field_type': self.field_type, } ) else: reset_intr_header = "" self.rtl_footer.append( self._process_yaml( Field.templ_dict['always_ff_block_intr'], {'trigger_signal': trigger_signal, 'always_ff_header': self.always_ff_header, 'reset_intr_header': reset_intr_header, 'genvars': self.genvars_str, 'field_type': self.field_type, } ) ) else: self.access_rtl['hw_write'] = ([ self._process_yaml( Field.templ_dict['nonsticky_intr'], {'path': self.path_underscored, 'assignment': trigger_signal, 'genvars': self.genvars_str, } ) ], False) # Generate masked & enabled version of interrupt to be # picked up by the register at the top level if mask := self.obj.get_property('mask'): self.itr_masked = ' & ~'.join([ self.register_name, self.get_signal_name(mask) ]) elif enable := self.obj.get_property('enable'): self.itr_masked = ' & '.join([ self.register_name, self.get_signal_name(enable) ]) else: self.itr_masked = self.register_name # Generate haltmasked & haltenabled version of interrupt to be # picked up by the register at the top level if haltmask := self.obj.get_property('haltmask'): self.itr_haltmasked = ' & ~'.join([ self.register_name, self.get_signal_name(haltmask) ]) self.properties['halt'] = True elif haltenable := self.obj.get_property('haltenable'): self.itr_haltmasked = ' & ~'.join([ self.register_name, self.get_signal_name(haltenable) ]) self.properties['halt'] = True else: self.itr_haltmasked = self.register_name else: self.itr_masked = False self.itr_haltmasked = False return self.properties['intr'] def __add_hw_access(self): # Mutually exclusive. systemrdl-compiler performs check for this enable_mask_negl = '' enable_mask = False if self.obj.get_property('hwenable'): enable_mask = self.obj.get_property('hwenable') elif self.obj.get_property('hwmask'): enable_mask = self.obj.get_property('hwmask') enable_mask_negl = '!' if enable_mask: enable_mask_start_rtl = \ self._process_yaml( Field.templ_dict['hw_enable_mask_start'], {'signal': self.get_signal_name(enable_mask), 'width': self.obj.width, 'negl': enable_mask_negl} ) enable_mask_end_rtl = \ self._process_yaml( Field.templ_dict['hw_enable_mask_end'], {'width': self.obj.width} ) enable_mask_idx = '[idx]' else: enable_mask_start_rtl = '<>' enable_mask_end_rtl = '<>' enable_mask_idx = '' # Define hardware access (if applicable) sticky, _ = self.__add_sticky(latch_signal = InterruptType.level) if sticky: self.logger.info(f"Found {sticky} property.") elif self.obj.get_property('counter'): self.access_rtl['hw_write'] = ([ self._process_yaml( Field.templ_dict['hw_access_counter'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'field_type': self.field_type, 'enable_mask_start': enable_mask_start_rtl, 'enable_mask_end': enable_mask_end_rtl, 'idx': enable_mask_idx} ) ], False) elif self.obj.get_property('hw') in (AccessType.rw, AccessType.w): write_condition = 'hw_access_we_wel' if self.we_or_wel else 'hw_access_no_we_wel' # if-line of hw-access self.access_rtl['hw_write'] = ([ self._process_yaml( Field.templ_dict[write_condition], {'negl': '!' if self.obj.get_property('wel') else '', 'path': self.path_underscored, 'genvars': self.genvars_str, 'field_type': self.field_type} ) ], write_condition == 'hw_access_no_we_wel') # Abort if no condition is set # Actual assignment of register if self.obj.get_property('next'): # 'next' property is used self.logger.debug("Found property 'next'") assignment = self.get_signal_name(self.obj.get_property('next')) skip_inputs = True if self.we_or_wel: self.logger.info("This field has a 'we' or 'wel' property and " "uses the 'next' property. Make sure this is " "is intentional.") else: skip_inputs = False # No special property. Assign input to register assignment = \ self._process_yaml( Field.templ_dict['hw_access_field__assignment__input'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'idx': enable_mask_idx, 'field_type': self.field_type} ) self.access_rtl['hw_write'][0].append( self._process_yaml( Field.templ_dict['hw_access_field'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'enable_mask_start': enable_mask_start_rtl, 'enable_mask_end': enable_mask_end_rtl, 'assignment': assignment, 'idx': enable_mask_idx, 'field_type': self.field_type}, skip_inputs = skip_inputs ) ) else: self.access_rtl['hw_write'] = ([], False) # Check if the hwset or hwclr option is set if self.obj.get_property('hwset'): self.access_rtl['hw_setclr'] = ([ self._process_yaml( Field.templ_dict['hw_access_hwset'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'enable_mask_start': enable_mask_start_rtl, 'enable_mask_end': enable_mask_end_rtl, 'idx': enable_mask_idx, 'constant': f"{{{self.obj.width}{{1'b1}}}}" if not enable_mask else "1'b1" } ) ], False) elif self.obj.get_property('hwclr'): self.access_rtl['hw_setclr'] = ([ self._process_yaml( Field.templ_dict['hw_access_hwclr'], {'path': self.path_underscored, 'genvars': self.genvars_str, 'enable_mask_start': enable_mask_start_rtl, 'enable_mask_end': enable_mask_end_rtl, 'idx': enable_mask_idx, 'constant': f"{{{self.obj.width}{{1'b0}}}}" if not enable_mask else "1'b0" } ) ], False) else: self.access_rtl['hw_setclr'] = ([], False) # Hookup flop to output port in case register is readable by hardware if self.obj.get_property('hw') in (AccessType.rw, AccessType.r): # Connect flops to output port self.rtl_footer.append( self._process_yaml( Field.templ_dict['out_port_assign'], {'genvars': self.genvars_str, 'path': self.path_underscored, 'field_type': self.field_type} ) ) def create_external_rtl(self): if self.properties['sw_wr']: for i, alias in enumerate(self.path_underscored_vec): # Create bit-wise mask so that outside logic knows what # bits it may change mask = [] for byte_idx in range(self.msbyte, self.lsbyte-1, -1): if byte_idx == self.lsbyte: width = (self.lsbyte+1)*8 - self.lsb elif byte_idx == self.msbyte: width = 8 - ((self.msbyte+1)*8-1 - self.msb) else: width = 8 mask.append( Field.templ_dict['external_wr_mask_segment']['rtl'].format( idx = byte_idx, width = width) ) self.rtl_footer.append(self._process_yaml( Field.templ_dict['external_wr_assignments'], {'path': alias, 'path_wo_field': self.path_wo_field_vec[i], 'genvars': self.genvars_str, 'msb_bus': self.msb, 'lsb_bus': self.lsb, 'mask': ','.join(mask), 'width': self.obj.width-1, 'field_type': self.field_type } )) if self.properties['sw_rd']: for i, alias in enumerate(self.path_underscored_vec): self.rtl_footer.append(self._process_yaml( Field.templ_dict['external_rd_assignments'], {'path': alias, 'path_wo_field': self.path_wo_field_vec[i], 'genvars': self.genvars_str, 'field_type': self.field_type } )) def create_internal_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. # # Check if hardware has precedence (default `precedence = sw`) if self.obj.get_property('precedence') == PrecedenceType.sw: order_list = [ 'sw_write', 'sw_read', 'hw_setclr', 'hw_write', 'singlepulse' ] else: order_list = [ 'hw_setclr', 'hw_write', 'sw_write', 'sw_read', 'singlepulse' ] # Add appropriate else order_list_rtl = [] abort_set = False 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 # First check if we need to break or continue the loop if abort_set: break # Check if there is a list that shall be unlooped try: if isinstance(self.access_rtl[i], tuple): access_rtl = [self.access_rtl[i]] else: access_rtl = self.access_rtl[i] except KeyError: continue for unpacked_access_rtl in access_rtl: if len(unpacked_access_rtl[0]) == 0: continue order_list_rtl = [*order_list_rtl, *unpacked_access_rtl[0]] order_list_rtl.append("else") # If the access_rtl entry has an abortion entry, do not print # any further branches of the conditional block abort_set = unpacked_access_rtl[1] # Remove last else 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( self._process_yaml( Field.templ_dict['end_field_ff'], {'path': self.path_underscored} ) ) 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_footer.append( self._process_yaml( Field.templ_dict['combo_operation_comment'], {'path': self.path_underscored} ) ) self.rtl_footer = [ *self.rtl_footer, *[self._process_yaml( Field.templ_dict[i[1]], {'path': self.path_underscored, 'genvars': self.genvars_str, 'op_verilog': i[0], 'field_type': self.field_type} ) for i in operations] ] def __init_fieldtype(self): try: if not self.config['enums']: raise AttributeError 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(f"Starting to parse '{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): path.append(parent_scope._scope_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.typedefs[enum_name] = TypeDef ( scope=scope, width=self.obj.width, members= [(x.name, x.value) for x in self.obj.get_property('encode')] ) # Save name of object self.field_type =\ '::'.join(['_'.join([scope, 'pkg']), enum_name]) self.logger.info(f"Parsed enum '{enum_name}'") except AttributeError: # In case of an AttributeError, the encode property is None. Hence, # the field has a simple width self.field_type = f"logic [{self.obj.width-1}:0]" def __init_variables( self, obj: FieldNode): # Create full name self.path_wo_field = '__'.join(self.path.split('.', -1)[0:-1]) self.register_name = ''.join([self.path_underscored, '_q']) self.path_underscored_vec = [] self.path_wo_field_vec = [] # Set some properties that always must be known self.properties['sw_wr'] = obj.get_property('sw') in (AccessType.rw, AccessType.w) self.properties['sw_rd'] = obj.get_property('sw') in (AccessType.rw, AccessType.r) # In case of an external register, a wire to indicate a read # is always required self.properties['sw_rd_wire'] = self.config['external'] and self.properties['sw_rd'] # 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) self.msb = obj.inst.msb self.lsb = obj.inst.lsb # Set that tells which hierarchies can read/write this field self.readable_by = set() self.writable_by = set() # Determine resets. This includes checking for async/sync resets, # the reset value, and whether the field actually has a reset self.rst = Field.__process_reset_signal(obj.get_property("resetsignal")) if self.rst['name']: self.resets.add(self.rst['name']) elif obj.get_property("reset") is not None: self.logger.warning("Field has a reset value, but no reset "\ "signal was defined and connected to the "\ "field. Note that explicit connecting this "\ "is not required if a field_reset was defined.") # Value of reset must always be determined on field level # Don't use 'not obj.get_property("reset"), since the value # could (and will often be) be '0' self.rst['value'] = \ '\'x' if obj.get_property("reset") == None else\ obj.get_property('reset') # Define dict that holds all RTL self.access_rtl = {} self.access_rtl['else'] = (["else"], False) self.access_rtl[''] = ([''], False) def __summary(self): # Additional flags that are set # Use list, rather than set, to ensure the order stays the same # when compiled multiple times misc_flags = list(self.obj.list_properties()) # Remove some flags that are not interesting # or that are listed elsewhere for rdl_property in ('hw', 'reset'): try: misc_flags.remove(rdl_property) except ValueError: pass precedence = self.obj.get_property('precedence') # Add comment with summary on field's properties return \ Field.templ_dict['field_comment']['rtl'].format( name = self.name, hw_access = str(self.obj.get_property('hw'))[11:], sw_access = str(self.obj.get_property('sw'))[11:], hw_precedence = '(precedence)' if precedence == PrecedenceType.hw else '', sw_precedence = '(precedence)' if precedence == PrecedenceType.sw else '', rst_active = self.rst['active'], rst_type = self.rst['type'], misc_flags = misc_flags if misc_flags else '-', external = self.config['external'], 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.always_ff_header = \ self._process_yaml( Field.templ_dict[sense_list], {'rst_edge': self.rst['edge'], 'rst_name': self.rst['name']} ) self.rtl_header.append(self.always_ff_header) # Add actual reset line if self.rst['name']: self.rtl_header.append( self._process_yaml( Field.templ_dict['rst_field_assign'], {'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, 'field_type': self.field_type} ) ) self.rtl_header.append("begin") 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.obj.get_property('precedence') == PrecedenceType.hw and \ self.obj.get_property('hw') in (AccessType.rw, AccessType.w) and \ self.obj.get_property('sw') 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.") # If hw=ro and the next property is set, throw a fatal if self.obj.get_property('hw') == AccessType.r\ and self.obj.get_property('next'): self.logger.error("Hardware property of field is set to read-only "\ "but simultanously, the next property is set. Since "\ "this would reflect wrong behavior in documentation, "\ "the next property is ignored.") # If a stick(bit) is defined, the counter property will be ignored if (self.obj.get_property('stickybit') or self.obj.get_property('sticky')) \ and self.obj.get_property('counter'): self.logger.error("It's not possible to combine the sticky(bit) "\ "property with the counter property. The counter property "\ "will be ignored.") @staticmethod def __process_reset_signal(reset_signal): rst = {} try: rst['name'] = reset_signal.inst_name rst['async'] = reset_signal.get_property("async") rst['type'] = "asynchronous" if rst['async'] else "synchronous" # Active low or active high? if reset_signal.get_property("activelow"): rst['edge'] = "negedge" rst['active'] = "active_low" else: rst['edge'] = "posedge" rst['active'] = "active_high" except AttributeError: # Catch if reset_signal does not exist rst['async'] = False rst['name'] = None rst['edge'] = None rst['value'] = "'x" rst['active'] = "-" rst['type'] = "-" return rst