
{%- macro register_class(node) %}
    {%- if node.has_sw_readable and node.has_sw_writable %}
class {{get_fully_qualified_type_name(node)}}_cls(RegReadWrite):
    {%- elif node.has_sw_readable and not node.has_sw_writable %}
class {{get_fully_qualified_type_name(node)}}_cls(RegReadOnly):
    {%- elif not node.has_sw_readable and node.has_sw_writable %}
class {{get_fully_qualified_type_name(node)}}_cls(RegWriteOnly):
    {%- endif %}
    """
    Class to represent a register in the register model

    {{get_table_block(node) | indent}}
    """

    __slots__ : List[str] = [{%- for child_node in node.children(unroll=False) -%}'__{{child_node.inst_name}}'{% if not loop.last %}, {% endif %}{%- endfor %}]

    def __init__(self,
                 callbacks: CallbackSet,
                 address: int,
                 logger_handle: str,
                 inst_name: str,
                 parent: Union[AddressMap,RegFile,Memory]):

        super().__init__(callbacks=callbacks,
                         address=address,
                         {% if 'accesswidth' in node.list_properties() -%}accesswidth={{node.get_property('accesswidth')}}{%- else -%}accesswidth={{node.size*8}}{%- endif -%},
                         width={{node.size*8}},
                         logger_handle=logger_handle,
                         inst_name=inst_name,
                         parent=parent)

        # build the field attributes
        {%- for child_node in node.fields() %}
            {%- if isinstance(child_node, systemrdlFieldNode) %}
        self.__{{child_node.inst_name}}:{{get_fully_qualified_type_name(child_node)}}_cls = {{get_fully_qualified_type_name(child_node)}}_cls(
            parent_register=self,
            size_props=FieldSizeProps(
                width={{child_node.width}},
                lsb={{child_node.lsb}},
                msb={{child_node.msb}},
                low={{child_node.low}},
                high={{child_node.high}}),
            misc_props=FieldMiscProps(
                default={{get_field_default_value(child_node)}},
                is_volatile={{child_node.is_hw_writable}}),
            logger_handle=logger_handle+'.{{child_node.inst_name}}',
            inst_name='{{child_node.inst_name}}')
            {%- endif -%}
        {%- endfor %}

    {% if node.has_sw_readable %}
    def read_fields(self):
        """
        read the register and return a dictionary of the field values
        """
        reg_value = self.read()

        return_dict = {
                        {%- for child_node in get_reg_readable_fields(node) %}
                        '{{child_node.inst_name}}' : self.{{safe_node_name(child_node)}}.decode_read_value(reg_value)
                        {%- if not loop.last -%} ,  {%- endif -%}
                        {% endfor %}
                      }

        return return_dict

    @property
    def readable_fields(self) -> Iterator[Union[FieldReadOnly, FieldReadWrite]]:
        """
        generator that produces has all the readable fields within the register
        """
        {% for child_node in get_reg_readable_fields(node) -%}
        yield self.{{safe_node_name(child_node)}}
        {% endfor %}

    {% endif %}

    {% if node.has_sw_writable %}
    @property
    def writable_fields(self) -> Iterator[Union[FieldWriteOnly, FieldReadWrite]]:
        """
        generator that produces has all the writable fields within the register
        """
        {% for child_node in get_reg_writable_fields(node) -%}
        yield self.{{safe_node_name(child_node)}}
        {% endfor %}

    {% if node.has_sw_readable %}
    def write_fields(self, **kwargs) -> None:  # type
        """
        Do a read-modify-write to the register, updating any field included in
        the arguments
        """

        if len(kwargs) == 0:
            raise ValueError('no command args')

        bit_mask = 0
        reg_value = 0
        {%- for child_node in get_reg_writable_fields(node) %}
        if '{{safe_node_name(child_node)}}' in kwargs:
            reg_value |= self.{{safe_node_name(child_node)}}.encode_write_value(kwargs['{{safe_node_name(child_node)}}'])
            bit_mask |= self.{{safe_node_name(child_node)}}.bitmask
            kwargs.pop('{{safe_node_name(child_node)}}')
        {%- endfor %}
        if len(kwargs) != 0:
            # left over unhandled arguments
            raise ValueError('unrecognised arguments in field')

        inverse_bit_mask = self.max_value ^ bit_mask

        self.write((self.read() & inverse_bit_mask) | reg_value)

    {% else %}
    def write_fields(self, {%- for child_node in node.fields() -%} {{safe_node_name(child_node)}} : {%- if 'encode' in child_node.list_properties() %}{{get_fully_qualified_enum_type(child_node.get_property('encode'), top_node.parent)}}_enumcls{% else %}int{% endif %}{%- if not loop.last -%},{%- endif -%}{%- endfor -%}) -> None: # type: ignore[override]
        """
        Do a write to the register, updating all fields
        """
        reg_value = 0
        {%- for child_node in node.fields() %}
        reg_value |= self.{{safe_node_name(child_node)}}.encode_write_value({{safe_node_name(child_node)}})
        {% endfor %}

        self.write(reg_value)
    {% endif %}
    {% endif %}

    # build the properties for the fields
    {%- for child_node in node.children(unroll=False) -%}
        {%- if isinstance(child_node, systemrdlFieldNode) %}
    @property
    def {{safe_node_name(child_node)}}(self) -> {{get_fully_qualified_type_name(child_node)}}_cls:
        """
        Property to access {{child_node.inst_name}} field of the register

        {{get_table_block(child_node) | indent(8)}}
        """
        return self.__{{child_node.inst_name}}
        {% endif %}
    {%- endfor %}

    @property
    def systemrdl_python_child_name_map(self) -> Dict[str, str]:
        """
        In some cases systemRDL names need to be converted make them python safe, this dictionary
        is used to map the original systemRDL names to the names of the python attributes of this
        class

        Returns: dictionary whose key is the systemRDL names and value it the property name
        """
        return {
            {%- for child_node in node.children(unroll=False) -%}
            '{{child_node.inst_name}}':'{{safe_node_name(child_node)}}',
            {%- endfor %}
            }

    {%- if node.is_array %}
        {%- if node.has_sw_readable and node.has_sw_writable %}
class {{get_fully_qualified_type_name(node)}}_array_cls(RegReadWriteArray):
        {%- elif node.has_sw_readable and not node.has_sw_writable %}
class {{get_fully_qualified_type_name(node)}}_array_cls(RegReadOnlyArray):
        {%- elif not node.has_sw_readable and node.has_sw_writable %}
class {{get_fully_qualified_type_name(node)}}_array_cls(RegWriteOnlyArray):
        {%- endif %}
    """
    Class to represent a register array in the register model
    """
    __slots__: List[str] = []

    def __init__(self, logger_handle: str, inst_name: str,
                 parent: Union[RegFile, AddressMap, Memory],
                 address: int,
                 length: int,
                 stride: int,
                 callbacks: CallbackSet):


        elements = tuple([{{get_fully_qualified_type_name(node)}}_cls(callbacks=callbacks,address=address+(index * stride),
                          logger_handle=f'{logger_handle}.{inst_name}[{index:d}]',
                          inst_name=f'{inst_name}[{index:d}]', parent=parent) for index in range(length)])

        super().__init__(logger_handle=logger_handle, inst_name=inst_name,
                         parent=parent, elements=elements)

    def __getitem__(self, item) -> Union[{{get_fully_qualified_type_name(node)}}_cls, Tuple[{{get_fully_qualified_type_name(node)}}_cls, ...]]:
        # this cast is OK because an explict typing check was done in the __init__
        return cast(Union[{{get_fully_qualified_type_name(node)}}_cls, Tuple[{{get_fully_qualified_type_name(node)}}_cls, ...]], super().__getitem__(item))
    {%- endif %}
{%- endmacro %}
