# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

from typing import Dict, List, Optional

from reggen.access import SWAccess, HWAccess
from reggen.clocking import Clocking
from reggen.field import Field
from reggen.lib import (check_keys, check_str, check_name, check_bool,
                        check_list, check_str_list, check_int)
from reggen.params import ReggenParams
from reggen.reg_base import RegBase

import re

REQUIRED_FIELDS = {
    'name': ['s', "name of the register"],
    'desc': [
        't',
        "description of the register. "
        "This field supports the markdown syntax."
    ],
    'fields': ['l', "list of register field description groups"]
}

OPTIONAL_FIELDS = {
    'alias_target': [
        's',
        "name of the register to apply the alias definition to."
    ],
    'async': [
        's',
        "indicates the register must cross to a different "
        "clock domain before use.  The value shown here "
        "should correspond to one of the module's clocks."
    ],
    'sync': [
        's',
        "indicates the register needs to be on another clock/reset domain."
        "The value shown here should correspond to one of the module's clocks."
    ],
    'swaccess': [
        's',
        "software access permission to use for "
        "fields that don't specify swaccess"
    ],
    'hwaccess': [
        's',
        "hardware access permission to use for "
        "fields that don't specify hwaccess"
    ],
    'hwext': [
        's',
        "'true' if the register is stored outside "
        "of the register module"
    ],
    'hwqe': [
        's',
        "'true' if hardware uses 'q' enable signal, "
        "which is latched signal of software write pulse."
    ],
    'hwre': [
        's',
        "'true' if hardware uses 're' signal, "
        "which is latched signal of software read pulse."
    ],
    'regwen': [
        's',
        "if register is write-protected by another register, that "
        "register name should be given here. empty-string for no register "
        "write protection"
    ],
    'resval': [
        'd',
        "reset value of full register (default 0)"
    ],
    'tags': [
        's',
        "tags for the register, following the format 'tag_name:item1:item2...'"
    ],
    'shadowed': [
        's',
        "'true' if the register is shadowed"
    ],
    'update_err_alert': [
        's',
        "alert that will be triggered if "
        "this shadowed register has update error"
    ],
    'storage_err_alert': [
        's',
        "alert that will be triggered if "
        "this shadowed register has storage error"
    ]
}


class Register(RegBase):
    '''Code representing a register for reggen'''
    def __init__(self,
                 offset: int,
                 name: str,
                 alias_target: Optional[str],
                 desc: str,
                 async_name: str,
                 async_clk: object,
                 sync_name: str,
                 sync_clk: object,
                 hwext: bool,
                 hwqe: bool,
                 hwre: bool,
                 regwen: Optional[str],
                 tags: List[str],
                 resval: Optional[int],
                 shadowed: bool,
                 fields: List[Field],
                 update_err_alert: Optional[str],
                 storage_err_alert: Optional[str]):
        super().__init__(offset)
        self.alias_target = alias_target
        self.name = name
        self.desc = desc
        self.async_name = async_name
        self.async_clk = async_clk
        self.sync_name = sync_name
        self.sync_clk = sync_clk
        self.hwext = hwext
        self.hwqe = hwqe
        self.hwre = hwre
        if self.hwre and not self.hwext:
            raise ValueError('The {} register specifies hwre but not hwext.'
                             .format(self.name))

        self.regwen = regwen
        self.tags = tags

        self.shadowed = shadowed
        pattern = r'^[a-z0-9_]+_shadowed(?:_[0-9]+)?'
        sounds_shadowy = re.match(pattern, self.name.lower())
        if self.shadowed and not sounds_shadowy:
            raise ValueError("Register {} has the shadowed flag but its name "
                             "doesn't end with the _shadowed suffix."
                             .format(self.name))
        elif sounds_shadowy and not self.shadowed:
            raise ValueError("Register {} has a name ending in _shadowed, but "
                             "the shadowed flag is not set."
                             .format(self.name))

        # Take a copy of fields and then sort by bit index
        assert fields
        self.fields = fields.copy()
        self.fields.sort(key=lambda field: field.bits.lsb)

        # Index fields by name and check for duplicates
        self.name_to_field = {}  # type: Dict[str, Field]
        for field in self.fields:
            if field.name in self.name_to_field:
                raise ValueError('Register {} has duplicate fields called {}.'
                                 .format(self.name, field.name))
            self.name_to_field[field.name] = field

        # Check that fields have compatible access types if we are hwext
        if self.hwext:
            for field in self.fields:
                if field.hwaccess.key == 'hro' and field.sw_readable():
                    raise ValueError('The {} register has hwext set, but '
                                     'field {} has hro hwaccess and the '
                                     'field value is readable by software '
                                     '(mode {}).'
                                     .format(self.name,
                                             field.name,
                                             field.swaccess.key))
                if not field.hwqe and field.sw_writable():
                    raise ValueError('The {} register has hwext set and field '
                                     '{} is writable by software (mode {}), '
                                     'so the register must also enable hwqe.'
                                     .format(self.name,
                                             field.name,
                                             field.swaccess.key))

        # Shadow registers do not support all swaccess types, hence we
        # need to check that here.
        if self.shadowed:
            for field in self.fields:
                if field.swaccess.key not in ['rw', 'ro', 'wo', 'rw1s', 'rw0c']:
                    raise ValueError("Shadowed register {} has a field ({}) with "
                                     "incompatible type '{}'."
                                     .format(self.name,
                                             field.name,
                                             field.swaccess.key))

        # Check that fields will be updated together. This generally comes "for
        # free", but there's a slight wrinkle with RC fields: these use the
        # register's read-enable input as their write-enable. We want to ensure
        # either that every field that is updated as a result of bus accesses
        # is RC or that none of them are.
        rc_fields = []
        we_fields = []
        for field in self.fields:
            if field.swaccess.key == 'rc':
                rc_fields.append(field.name)
            elif field.swaccess.allows_write():
                we_fields.append(field.name)
        if rc_fields and we_fields:
            raise ValueError("Register {} has both software writable fields "
                             "({}) and read-clear fields ({}), meaning it "
                             "doesn't have a single write-enable signal."
                             .format(self.name,
                                     ', '.join(rc_fields),
                                     ', '.join(we_fields)))

        # Check that field bits are disjoint
        bits_used = 0
        for field in self.fields:
            field_mask = field.bits.bitmask()
            if bits_used & field_mask:
                raise ValueError('Register {} has non-disjoint fields: '
                                 '{} uses bits {:#x} used by other fields.'
                                 .format(self.name, field.name,
                                         bits_used & field_mask))

        # Compute a reset value and mask from our constituent fields.
        self.resval = 0
        self.resmask = 0
        for field in self.fields:
            self.resval |= (field.resval or 0) << field.bits.lsb
            self.resmask |= field.bits.bitmask()

        # If the register defined a reset value, make sure it matches. We've
        # already checked that each field matches, but we still need to make
        # sure there weren't any bits unaccounted for.
        if resval is not None and self.resval != resval:
            raise ValueError('Register {} specifies a reset value of {:#x} but '
                             'collecting reset values across its fields yields '
                             '{:#x}.'
                             .format(self.name, resval, self.resval))

        self.update_err_alert = update_err_alert
        self.storage_err_alert = storage_err_alert

    @staticmethod
    def from_raw(reg_width: int,
                 offset: int,
                 params: ReggenParams,
                 raw: object,
                 clocks: Clocking,
                 is_alias: bool) -> 'Register':
        rd = check_keys(raw, 'register',
                        list(REQUIRED_FIELDS.keys()),
                        list(OPTIONAL_FIELDS.keys()))

        name = check_name(rd['name'], 'name of register')

        alias_target = None
        if rd.get('alias_target') is not None:
            if is_alias:
                alias_target = check_name(rd['alias_target'],
                                          'name of alias target register')
            else:
                raise ValueError('Field {} may not have an alias_target key'
                                 .format(name))
        elif is_alias:
            raise ValueError('alias register {} does not define the '
                             'alias_target key.'
                             .format(name))

        desc = check_str(rd['desc'], 'desc for {} register'.format(name))

        async_name = check_str(rd.get('async', ''), 'async clock for {} register'.format(name))
        async_clk = None

        if async_name:
            valid_clocks = clocks.clock_signals()
            if async_name not in valid_clocks:
                raise ValueError('async clock {} defined for {} does not exist '
                                 'in valid module clocks {}.'
                                 .format(async_name,
                                         name,
                                         valid_clocks))
            else:
                async_clk = clocks.get_by_clock(async_name)

        sync_name = check_str(rd.get('sync', ''), 'different sync clock for {} register'
                              .format(name))
        sync_clk = None

        if sync_name:
            valid_clocks = clocks.clock_signals()
            if sync_name not in valid_clocks:
                raise ValueError('sync clock {} defined for {} does not exist '
                                 'in valid module clocks {}.'
                                 .format(sync_name,
                                         name,
                                         valid_clocks))
            else:
                sync_clk = clocks.get_by_clock(sync_name)

        swaccess = SWAccess('{} register'.format(name),
                            rd.get('swaccess', 'none'))
        hwaccess = HWAccess('{} register'.format(name),
                            rd.get('hwaccess', 'hro'))

        hwext = check_bool(rd.get('hwext', False),
                           'hwext flag for {} register'.format(name))

        hwqe = check_bool(rd.get('hwqe', False),
                          'hwqe flag for {} register'.format(name))

        hwre = check_bool(rd.get('hwre', False),
                          'hwre flag for {} register'.format(name))

        raw_regwen = rd.get('regwen', '')
        if not raw_regwen:
            regwen = None
        else:
            regwen = check_name(raw_regwen,
                                'regwen for {} register'.format(name))

        tags = check_str_list(rd.get('tags', []),
                              'tags for {} register'.format(name))

        raw_resval = rd.get('resval')
        if raw_resval is None:
            resval = None
        else:
            resval = check_int(raw_resval,
                               'resval for {} register'.format(name))
            if not 0 <= resval < (1 << reg_width):
                raise ValueError('resval for {} register is {}, '
                                 'not an unsigned {}-bit number.'
                                 .format(name, resval, reg_width))

        shadowed = check_bool(rd.get('shadowed', False),
                              'shadowed flag for {} register'
                              .format(name))

        raw_fields = check_list(rd['fields'],
                                'fields for {} register'.format(name))
        if not raw_fields:
            raise ValueError('Register {} has no fields.'.format(name))

        fields = []
        used_bits = 0
        for idx, rf in enumerate(raw_fields):

            field = (Field.from_raw(name,
                                    idx,
                                    len(raw_fields),
                                    swaccess,
                                    hwaccess,
                                    resval,
                                    reg_width,
                                    params,
                                    hwext,
                                    hwqe,
                                    shadowed,
                                    is_alias,
                                    rf))

            overlap_bits = used_bits & field.bits.bitmask()
            if overlap_bits:
                raise ValueError(f'Field {field.name} uses bits '
                                 f'{overlap_bits:#x} that appear in other '
                                 f'fields.')

            used_bits |= field.bits.bitmask()
            fields.append(field)

        raw_uea = rd.get('update_err_alert')
        if raw_uea is None:
            update_err_alert = None
        else:
            update_err_alert = check_name(raw_uea,
                                          'update_err_alert for {} register'
                                          .format(name))

        raw_sea = rd.get('storage_err_alert')
        if raw_sea is None:
            storage_err_alert = None
        else:
            storage_err_alert = check_name(raw_sea,
                                           'storage_err_alert for {} register'
                                           .format(name))

        return Register(offset, name, alias_target, desc, async_name,
                        async_clk, sync_name, sync_clk,
                        hwext, hwqe, hwre, regwen,
                        tags, resval, shadowed, fields,
                        update_err_alert, storage_err_alert)

    def next_offset(self, addrsep: int) -> int:
        return self.offset + addrsep

    def get_n_bits(self, bittype: List[str]) -> int:
        return sum(field.get_n_bits(self.hwext, self.hwre, bittype)
                   for field in self.fields)

    def get_field_list(self) -> List[Field]:
        return self.fields

    def is_homogeneous(self) -> bool:
        return len(self.fields) == 1

    def is_hw_writable(self) -> bool:
        '''Returns true if any field in this register can be modified by HW'''
        for fld in self.fields:
            if fld.hwaccess.allows_write():
                return True
        return False

    def get_width(self) -> int:
        '''Get the width of the fields in the register in bits

        This counts dead space between and below fields, so it's calculated as
        one more than the highest msb.

        '''
        # self.fields is ordered by (increasing) LSB, so we can find the MSB of
        # the register by taking the MSB of the last field.
        return 1 + self.fields[-1].bits.msb

    def needs_we(self) -> bool:
        '''Return true if at least one field needs a write-enable'''
        for fld in self.fields:
            if fld.swaccess.needs_we():
                return True
        return False

    def needs_qe(self) -> bool:
        '''Return true if the register or at least one field needs a q-enable'''
        if self.hwqe:
            return True
        for fld in self.fields:
            if fld.hwqe:
                return True
        return False

    def needs_int_qe(self) -> bool:
        '''Return true if the register or at least one field needs an
           internal q-enable.  An internal q-enable means the net
           may be consumed by other reg logic but will not be exposed
           in the package file.'''
        if self.async_clk and self.is_hw_writable():
            return True
        else:
            return self.needs_qe()

    def needs_re(self) -> bool:
        '''Return true if at least one field needs a read-enable

        This is true if any of the following are true:

          - The register is shadowed, because the read has a side effect.
            I.e., this puts the register back into Phase 0 (next write will
            go to the staged register). This is useful for software in case
            it lost track of the current phase. See comportability spec for
            more details:
            https://docs.opentitan.org/doc/rm/register_tool/#shadow-registers

          - There's an RC field (where we'll attach the read-enable signal to
            the subreg's we port)

          - The register is hwext and allows reads (in which case the hardware
            side might need the re signal)

        '''
        if self.shadowed:
            return True

        for fld in self.fields:
            if fld.swaccess.key == 'rc':
                return True

            if self.hwext and fld.swaccess.allows_read():
                return True

        return False

    def make_multi(self,
                   reg_width: int,
                   offset: int,
                   creg_idx: int,
                   creg_count: int,
                   regwen_multi: bool,
                   compact: bool,
                   min_reg_idx: int,
                   max_reg_idx: int,
                   cname: str) -> 'Register':
        '''Generate a numbered, packed version of the register'''
        assert 0 <= creg_idx < creg_count
        assert 0 <= min_reg_idx <= max_reg_idx
        assert compact or (min_reg_idx == max_reg_idx)

        new_name = ('{}_{}'.format(self.name, creg_idx)
                    if creg_count > 1
                    else self.name)

        new_alias_target = None
        if self.alias_target is not None:
            new_alias_target = ('{}_{}'.format(self.alias_target, creg_idx)
                                if creg_count > 1
                                else self.alias_target)

        if self.regwen is None or not regwen_multi or creg_count == 1:
            new_regwen = self.regwen
        else:
            new_regwen = '{}_{}'.format(self.regwen, creg_idx)

        strip_field = creg_idx > 0

        if compact:
            # Compacting multiple registers into a single "compacted" register.
            # This is only supported if we have exactly one field (checked at
            # the call-site)
            assert len(self.fields) == 1
            new_fields = self.fields[0].make_multi(reg_width,
                                                   min_reg_idx, max_reg_idx,
                                                   cname, creg_idx,
                                                   strip_field)
        else:
            # No compacting going on, but we still choose to rename the fields
            # to match the registers
            assert creg_idx == min_reg_idx
            new_fields = [field.make_suffixed('_{}'.format(creg_idx),
                                              cname, creg_idx, strip_field)
                          for field in self.fields]

        # Don't specify a reset value for the new register. Any reset value
        # defined for the original register will have propagated to its fields,
        # so when we combine them here, the Register constructor can compute a
        # reset value for us (which might well be different from self.resval if
        # we've replicated fields).
        new_resval = None

        return Register(offset, new_name, new_alias_target, self.desc,
                        self.async_name, self.async_clk,
                        self.sync_name, self.sync_clk,
                        self.hwext, self.hwqe, self.hwre, new_regwen,
                        self.tags, new_resval, self.shadowed, new_fields,
                        self.update_err_alert, self.storage_err_alert)

    def check_valid_regwen(self) -> None:
        '''Check that this register is valid for use as a REGWEN'''
        # Async REGWEN registers are currently not supported.
        # The register write enable gating is always resolved on the
        # bus side.
        if self.async_clk is not None:
            raise ValueError('Regwen {} cannot be declared as async.'
                             .format(self.name))

        # A REGWEN register should have a single field that's just bit zero.
        if len(self.fields) != 1:
            raise ValueError('One or more registers use {} as a '
                             'write-enable so it should have exactly one '
                             'field. It actually has {}.'
                             .format(self.name, len(self.fields)))

        wen_fld = self.fields[0]
        if wen_fld.bits.width() != 1:
            raise ValueError('One or more registers use {} as a '
                             'write-enable so its field should be 1 bit wide, '
                             'not {}.'
                             .format(self.name, wen_fld.bits.width()))
        if wen_fld.bits.lsb != 0:
            raise ValueError('One or more registers use {} as a '
                             'write-enable so its field should have LSB 0, '
                             'not {}.'
                             .format(self.name, wen_fld.bits.lsb))

        # If the REGWEN bit is SW controlled, check that the register
        # defaults to enabled. If this bit is read-only by SW and hence
        # hardware controlled, we do not enforce this requirement.
        if wen_fld.swaccess.key != "ro" and not self.resval:
            raise ValueError('One or more registers use {} as a '
                             'write-enable. Since it is SW-controlled '
                             'it should have a nonzero reset value.'
                             .format(self.name))

        if wen_fld.swaccess.key == "rw0c":
            # The register is software managed: all good!
            return

        if wen_fld.swaccess.key == "ro" and wen_fld.hwaccess.key == "hwo":
            # The register is hardware managed: that's fine too.
            return

        raise ValueError('One or more registers use {} as a write-enable. '
                         'However, its field has invalid access permissions '
                         '({} / {}). It should either have swaccess=RW0C '
                         'or have swaccess=RO and hwaccess=HWO.'
                         .format(self.name,
                                 wen_fld.swaccess.key,
                                 wen_fld.hwaccess.key))

    def bitmask(self) -> str:
        reg_bitmask = 0
        for f in self.fields:
            reg_bitmask |= f.bits.bitmask()

        return format(reg_bitmask, "x")

    def _asdict(self) -> Dict[str, object]:
        rd = {
            'name': self.name,
            'desc': self.desc,
            'fields': self.fields,
            'hwext': str(self.hwext),
            'hwqe': str(self.hwqe),
            'hwre': str(self.hwre),
            'tags': self.tags,
            'shadowed': str(self.shadowed),
        }  # type: Dict[str, object]
        if self.regwen is not None:
            rd['regwen'] = self.regwen
        if self.update_err_alert is not None:
            rd['update_err_alert'] = self.update_err_alert
        if self.storage_err_alert is not None:
            rd['storage_err_alert'] = self.storage_err_alert
        if self.alias_target is not None:
            rd['alias_target'] = self.alias_target

        return rd

    def apply_alias(self, alias_reg: 'Register', where: str) -> None:
        '''Compare all attributes and replace overridable values.

        This updates the overridable register and field attributes with the
        alias values and ensures that all non-overridable attributes have
        identical values.
        '''
        # Attributes to be crosschecked
        attrs = ['async_name', 'async_clk', 'hwext', 'hwqe', 'hwre',
                 'update_err_alert', 'storage_err_alert', 'shadowed']
        for attr in attrs:
            if getattr(self, attr) != getattr(alias_reg, attr):
                raise ValueError('Value mismatch for attribute {} between '
                                 'alias register {} and register {} in {}.'
                                 .format(attr, self.name,
                                         alias_reg.name, where))

        # These attributes can be overridden by the aliasing mechanism.
        self.name = alias_reg.name
        self.desc = alias_reg.desc
        self.resval = alias_reg.resval
        self.tags = alias_reg.tags
        self.regwen = alias_reg.regwen

        # We also keep track of the alias_target when overriding attributes.
        # This gives us a way to check whether a register has been overridden
        # or not, and what the name of the original register was.
        self.alias_target = alias_reg.alias_target

        # Update the fields.
        if len(alias_reg.fields) != len(self.fields):
            raise ValueError('The number of fields does not match for '
                             'alias register {} and register {} in {}.'
                             .format(alias_reg.name, self.name, where))

        fields = zip(alias_reg.fields, self.fields)
        for k, (alias_field, field) in enumerate(fields):
            # Infer the aliased field name if it has not been defined.
            if alias_field.alias_target is None:
                alias_field.alias_target = field.name
            # Otherwise the names need to match
            elif alias_field.alias_target != field.name:
                raise ValueError('Inconsistent aliased field name {} '
                                 'in alias register {} in {}.'
                                 .format(alias_field.alias_target,
                                         alias_reg.alias_target,
                                         where))

            # Validate and override attributes.
            field.apply_alias(alias_field, where)

    def scrub_alias(self, where: str) -> None:
        '''Replaces sensitive fields in register with generic names

        This function can be used to create the generic register descriptions
        from full alias hjson definitions. It will only work on registers
        where the alias_target keys are defined, and otherwise throw an error.
        '''
        # These attributes are scrubbed
        assert self.alias_target is not None
        self.name = self.alias_target
        self.desc = ''
        self.resval = 0
        self.tags = []
        self.alias_target = None

        # First check that custom alias_target names are unique and that
        # the numbering is correct if the name is of the form field[0-9]+.
        known_field_names = {}
        for k, field in enumerate(self.fields):
            if field.alias_target is not None:
                m = re.match(r'field[0-9]+', field.alias_target)
                if m and field.alias_target != f'field{k}':
                    raise ValueError('Alias field alias_target {} is '
                                     'incorrectly numbered '
                                     'in alias register {} in {}.'
                                     .format(field.alias_target,
                                             self.name,
                                             where))
                elif field.alias_target in known_field_names:
                    raise ValueError('Alias field alias_target {} is not '
                                     'unique in alias register {} in {}.'
                                     .format(field.alias_target,
                                             self.name,
                                             where))
                else:
                    known_field_names.update({field.alias_target: 1})

        # Then, assign the field names
        for k, field in enumerate(self.fields):
            if field.alias_target is not None:
                field.name = field.alias_target
                field.alias_target = None
            # Infer the aliased field name if it has not been defined.
            else:
                field.name = f'field{k}'

            # Scrub field contents.
            field.scrub_alias(where)
