{%- 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(
            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.{{child_node.inst_name}}.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.{{child_node.inst_name}}
        {% 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.{{child_node.inst_name}}
        {% 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 '{{child_node.inst_name}}' in kwargs:
            reg_value |= self.{{child_node.inst_name}}.encode_write_value(kwargs['{{child_node.inst_name}}'])
            bit_mask |= self.{{child_node.inst_name}}.bitmask
            kwargs.pop('{{child_node.inst_name}}')
        {%- 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() -%} {{child_node.inst_name}} : {%- 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.{{child_node.inst_name}}.encode_write_value({{child_node.inst_name}})
        {% 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 {{child_node.inst_name}}(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 %}

    {%- 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(RegReadWriteArray):
        {%- elif not node.has_sw_readable and node.has_sw_writable %}
class {{get_fully_qualified_type_name(node)}}_array_cls(RegReadWriteArray):
        {%- 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],
                 elements: Tuple[{{get_fully_qualified_type_name(node)}}_cls, ...]):

        for element in elements:
            if not isinstance(element, {{get_fully_qualified_type_name(node)}}_cls):
                raise TypeError(f'All Elements should be of type {{get_fully_qualified_type_name(node)}}_cls, '
                                f'found {type(element)}')

        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 %}
