text: # 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 .access import SWAccess, HWAccess
  from .bits import Bits
  from .enum_entry import EnumEntry
  from .lib import (check_keys, check_str, check_name,
                    check_list, check_str_list, check_xint)
  from .params import ReggenParams
  
  REQUIRED_FIELDS = {
      'bits': ['b', "bit or bit range (msb:lsb)"]
  }
  
  OPTIONAL_FIELDS = {
      'name': ['s', "name of the field"],
      'desc': ['t', "description of field (required if the field has a name)"],
      'swaccess': [
          's', "software access permission, copied from "
          "register if not provided in field. "
          "(Tool adds if not provided.)"
      ],
      'hwaccess': [
          's', "hardware access permission, copied from "
          "register if not prvided in field. "
          "(Tool adds if not provided.)"
      ],
      'resval': [
          'x', "reset value, comes from register resval "
          "if not provided in field. Zero if neither "
          "are provided and the field is readable, "
          "x if neither are provided and the field "
          "is wo. Must match if both are provided."
      ],
      'enum': ['l', "list of permitted enumeration groups"],
      'tags': [
          's',
          "tags for the field, followed by the format 'tag_name:item1:item2...'"
      ]
  }
  
  
  class Field:
      def __init__(self,
                   name: str,
                   desc: Optional[str],
                   tags: List[str],
                   swaccess: SWAccess,
                   hwaccess: HWAccess,
                   bits: Bits,
                   resval: Optional[int],
                   enum: Optional[List[EnumEntry]]):
          self.name = name
          self.desc = desc
          self.tags = tags
          self.swaccess = swaccess
          self.hwaccess = hwaccess
          self.bits = bits
          self.resval = resval
          self.enum = enum
  
      @staticmethod
      def from_raw(reg_name: str,
                   field_idx: int,
                   num_fields: int,
                   default_swaccess: SWAccess,
                   default_hwaccess: HWAccess,
                   reg_resval: Optional[int],
                   reg_width: int,
                   params: ReggenParams,
                   raw: object) -> 'Field':
          where = 'field {} of {} register'.format(field_idx, reg_name)
          rd = check_keys(raw, where,
                          list(REQUIRED_FIELDS.keys()),
                          list(OPTIONAL_FIELDS.keys()))
  
          raw_name = rd.get('name')
          if raw_name is None:
              name = ('field{}'.format(field_idx + 1)
                      if num_fields > 1 else reg_name)
          else:
              name = check_name(raw_name, 'name of {}'.format(where))
  
          raw_desc = rd.get('desc')
          if raw_desc is None and raw_name is not None:
              raise ValueError('Missing desc field for {}'
                               .format(where))
          if raw_desc is None:
              desc = None
          else:
              desc = check_str(raw_desc, 'desc field for {}'.format(where))
  
          tags = check_str_list(rd.get('tags', []),
                                'tags for {}'.format(where))
  
          raw_swaccess = rd.get('swaccess')
          if raw_swaccess is not None:
              swaccess = SWAccess(where, raw_swaccess)
          else:
              swaccess = default_swaccess
  
          raw_hwaccess = rd.get('hwaccess')
          if raw_hwaccess is not None:
              hwaccess = HWAccess(where, raw_hwaccess)
          else:
              hwaccess = default_hwaccess
  
          bits = Bits.from_raw(where, reg_width, params, rd['bits'])
  
          raw_resval = rd.get('resval')
          if raw_resval is None:
              # The field doesn't define a reset value. Use bits from reg_resval
              # if it's defined, otherwise None (which means "x").
              if reg_resval is None:
                  resval = None
              else:
                  resval = bits.extract_field(reg_resval)
          else:
              # The field does define a reset value. It should be an integer or
              # 'x'. In the latter case, we set resval to None (as above).
              resval = check_xint(raw_resval, 'resval field for {}'.format(where))
              if resval is None:
                  # We don't allow a field to be explicitly 'x' on reset but for
                  # the containing register to have a reset value.
                  if reg_resval is not None:
                      raise ValueError('resval field for {} is "x", but the '
                                       'register defines a resval as well.'
                                       .format(where))
              else:
                  # Check that the reset value is representable with bits
                  if not (0 <= resval <= bits.max_value()):
                      raise ValueError("resval field for {} is {}, which "
                                       "isn't representable as an unsigned "
                                       "{}-bit integer."
                                       .format(where, resval, bits.width()))
  
                  # If the register had a resval, check this value matches it.
                  if reg_resval is not None:
                      resval_from_reg = bits.extract_field(reg_resval)
                      if resval != resval_from_reg:
                          raise ValueError('resval field for {} is {}, but the '
                                           'register defines a resval as well, '
                                           'where bits {}:{} would give {}.'
                                           .format(where, resval,
                                                   bits.msb, bits.lsb,
                                                   resval_from_reg))
  
          raw_enum = rd.get('enum')
          if raw_enum is None:
              enum = None
          else:
              enum = []
              raw_entries = check_list(raw_enum,
                                       'enum field for {}'.format(where))
              enum_val_to_name = {}  # type: Dict[int, str]
              for idx, raw_entry in enumerate(raw_entries):
                  entry = EnumEntry('entry {} in enum list for {}'
                                    .format(idx + 1, where),
                                    bits.max_value(),
                                    raw_entry)
                  if entry.value in enum_val_to_name:
                      raise ValueError('In {}, duplicate enum entries for '
                                       'value {} ({} and {}).'
                                       .format(where,
                                               entry.value,
                                               enum_val_to_name[entry.value],
                                               entry.name))
                  enum.append(entry)
                  enum_val_to_name[entry.value] = entry.name
  
          return Field(name, desc, tags, swaccess, hwaccess, bits, resval, enum)
  
      def has_incomplete_enum(self) -> bool:
          return (self.enum is not None and
                  len(self.enum) != 1 + self.bits.max_value())
  
      def get_n_bits(self, hwext: bool, hwqe: bool, hwre: bool, bittype: List[str]) -> int:
          '''Get the size of this field in bits
  
          bittype should be a list of the types of signals to count. The elements
          should come from the following list:
  
          - 'q': A signal for the value of the field. Only needed if HW can read
            its contents.
  
          - 'd': A signal for the next value of the field. Only needed if HW can
            write its contents.
  
          - 'de': A write enable signal for hardware accesses. Only needed if HW
            can write the field's contents and the register data is stored in the
            register block (true if the hwext flag is false).
  
          '''
          n_bits = 0
          if "q" in bittype and self.hwaccess.allows_read():
              n_bits += self.bits.width()
          if "d" in bittype and self.hwaccess.allows_write():
              n_bits += self.bits.width()
          if "qe" in bittype and self.hwaccess.allows_read():
              n_bits += int(hwqe)
          if "re" in bittype and self.hwaccess.allows_read():
              n_bits += int(hwre)
          if "de" in bittype and self.hwaccess.allows_write():
              n_bits += int(not hwext)
          return n_bits
  
      def make_multi(self,
                     reg_width: int,
                     min_reg_idx: int,
                     max_reg_idx: int,
                     cname: str,
                     creg_idx: int,
                     stripped: bool) -> List['Field']:
          assert 0 <= min_reg_idx <= max_reg_idx
  
          # Check that we won't overflow reg_width. We assume that the LSB should
          # be preserved: if msb=5, lsb=2 then the replicated copies will be
          # [5:2], [11:8] etc.
          num_copies = 1 + max_reg_idx - min_reg_idx
          field_width = self.bits.msb + 1
  
          if field_width * num_copies > reg_width:
              raise ValueError('Cannot replicate field {} {} times: the '
                               'resulting width would be {}, but the register '
                               'width is just {}.'
                               .format(self.name, num_copies,
                                       field_width * num_copies, reg_width))
  
          desc = ('For {}{}'.format(cname, creg_idx)
                  if stripped else self.desc)
          enum = None if stripped else self.enum
  
          ret = []
          for reg_idx in range(min_reg_idx, max_reg_idx + 1):
              name = '{}_{}'.format(self.name, reg_idx)
  
              bit_offset = field_width * (reg_idx - min_reg_idx)
              bits = (self.bits
                      if bit_offset == 0
                      else self.bits.make_translated(bit_offset))
  
              ret.append(Field(name, desc,
                               self.tags, self.swaccess, self.hwaccess,
                               bits, self.resval, enum))
  
          return ret
  
      def make_suffixed(self, suffix: str,
                        cname: str,
                        creg_idx: int,
                        stripped: bool) -> 'Field':
          desc = ('For {}{}'.format(cname, creg_idx)
                  if stripped else self.desc)
          enum = None if stripped else self.enum
  
          return Field(self.name + suffix,
                       desc, self.tags, self.swaccess, self.hwaccess,
                       self.bits, self.resval, enum)
  
      def _asdict(self) -> Dict[str, object]:
          rd = {
              'bits': self.bits.as_str(),
              'name': self.name,
              'swaccess': self.swaccess.key,
              'hwaccess': self.hwaccess.key,
              'resval': 'x' if self.resval is None else str(self.resval),
              'tags': self.tags
          }  # type: Dict[str, object]
  
          if self.desc is not None:
              rd['desc'] = self.desc
          if self.enum is not None:
              rd['enum'] = self.enum
          return rd
  
      def sw_readable(self) -> bool:
          return self.swaccess.key not in ['wo', 'r0w1c']
  
      def sw_writable(self) -> bool:
          return self.swaccess.key != 'ro'
  