| # 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, |
| hwext: bool, |
| shadowed: bool, |
| 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 |
| |
| # Currently internal shadow registers do not support hw write type |
| if not hwext and shadowed and hwaccess.allows_write(): |
| raise ValueError('Internal Shadow registers do not currently support ' |
| 'hardware write') |
| |
| 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' |