| # 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 design.mubi import prim_mubi # type: ignore |
| |
| from reggen.access import SWAccess, HWAccess |
| from reggen.bits import Bits |
| from reggen.enum_entry import EnumEntry |
| from reggen.lib import (check_keys, check_str, check_name, check_bool, |
| check_list, check_str_list, check_xint) |
| from reggen.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). " |
| "This field supports the markdown syntax." |
| ], |
| 'alias_target': [ |
| 's', |
| "name of the field to apply the alias definition to." |
| ], |
| '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 provided in field. " |
| "(Tool adds if not provided.)" |
| ], |
| 'hwqe': [ |
| 'b', "'true' if hardware uses 'q' enable signal, " |
| "which is latched signal of software write pulse. " |
| "Copied from register if not provided 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...'" |
| ], |
| 'mubi': [ |
| 'b', |
| "boolean flag for whether the field is a multi-bit type" |
| ], |
| 'auto_split': [ |
| 'b', |
| "boolean flag which determines whether the field " |
| "should be automatically separated into 1-bit sub-fields." |
| "This flag is used as a hint for automatically generated " |
| "software headers with register description." |
| ] |
| } |
| |
| |
| class Field: |
| def __init__(self, |
| name: str, |
| alias_target: Optional[str], |
| desc: Optional[str], |
| tags: List[str], |
| swaccess: SWAccess, |
| hwaccess: HWAccess, |
| hwqe: bool, |
| bits: Bits, |
| resval: Optional[int], |
| enum: Optional[List[EnumEntry]], |
| mubi: bool, |
| auto_split: bool): |
| self.name = name |
| self.alias_target = alias_target |
| self.desc = desc |
| self.tags = tags |
| self.swaccess = swaccess |
| self.hwaccess = hwaccess |
| self.hwqe = hwqe |
| self.bits = bits |
| self.resval = resval |
| self.enum = enum |
| self.mubi = mubi |
| self.auto_split = auto_split |
| |
| @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, |
| default_hwqe: bool, |
| shadowed: bool, |
| is_alias: 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)) |
| |
| alias_target = None |
| if rd.get('alias_target') is not None: |
| if is_alias: |
| alias_target = check_name(rd.get('alias_target'), |
| 'name of alias target register') |
| else: |
| raise ValueError('Field {} may not have an alias_target key.' |
| .format(name)) |
| |
| 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 |
| |
| raw_hwqe = rd.get('hwqe', default_hwqe) |
| hwqe = check_bool(raw_hwqe, 'hwqe field for {}'.format(where)) |
| raw_mubi = rd.get('mubi', False) |
| is_mubi = check_bool(raw_mubi, 'mubi field for {}'.format(where)) |
| raw_auto_split = rd.get('auto_split', False) |
| is_auto_split = check_bool(raw_auto_split, 'auto_split field for {}'.format(where)) |
| |
| # 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 is_mubi: |
| # When mubi type, the resval supplied is a boolean which is converted |
| # to a mubi value |
| chk_resval = check_bool(raw_resval, 'resval field for {}'.format(where)) |
| |
| # Check mubi width is supported |
| if not prim_mubi.is_width_valid(bits.width()): |
| raise ValueError(f'mubi field for {name} does not support width ' |
| f'of {bits.width()}') |
| |
| # Get actual integer value based on mubi selection |
| raw_resval = prim_mubi.mubi_value_as_int(chk_resval, bits.width()) |
| |
| if raw_resval is None: |
| # The field doesn't define a reset value. Use bits from reg_resval |
| # if it's defined. If not, we assume that a hwext register comes up |
| # as "x" (because the reggen code doesn't have any way to predict |
| # it). That's represented as None. A non-hwext register is reset to |
| # zero. |
| if reg_resval is not None: |
| resval = bits.extract_field(reg_resval) # type: Optional[int] |
| elif hwext: |
| resval = None |
| else: |
| resval = 0 |
| 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, alias_target, desc, tags, swaccess, hwaccess, |
| hwqe, bits, resval, enum, is_mubi, is_auto_split) |
| |
| 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, 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(self.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) |
| # In case this is an alias register, we need to make sure that |
| # the alias_target name is expanded as well. |
| alias_target = None |
| if self.alias_target is not None: |
| alias_target = '{}_{}'.format(self.alias_target, 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, alias_target, desc, |
| self.tags, self.swaccess, self.hwaccess, self.hwqe, |
| bits, self.resval, enum, self.mubi, self.auto_split)) |
| |
| 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 |
| |
| alias_target = None |
| if self.alias_target is not None: |
| alias_target = self.alias_target + suffix |
| |
| return Field(self.name + suffix, alias_target, |
| desc, self.tags, self.swaccess, self.hwaccess, self.hwqe, |
| self.bits, self.resval, enum, self.mubi, self.auto_split) |
| |
| 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 |
| if self.alias_target is not None: |
| rd['alias_target'] = self.alias_target |
| 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' |
| |
| def apply_alias(self, alias_field: 'Field', where: str) -> None: |
| '''Compare all attributes and replace overridable values. |
| |
| This updates the overridable field attributes with the alias values and |
| ensures that all non-overridable attributes have identical values. |
| ''' |
| |
| # Attributes to be crosschecked |
| attrs = ['bits', 'swaccess', 'hwaccess', 'hwqe', 'mubi'] |
| for attr in attrs: |
| if getattr(self, attr) != getattr(alias_field, attr): |
| raise ValueError('Value mismatch for attribute {} between ' |
| 'alias field {} and field {} in {}.' |
| .format(attr, self.name, |
| alias_field.name, where)) |
| |
| # These attributes can be overridden by the aliasing mechanism. |
| self.name = alias_field.name |
| self.desc = alias_field.desc |
| self.enum = alias_field.enum |
| self.resval = alias_field.resval |
| self.tags = alias_field.tags |
| # 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_field.alias_target |
| |
| def scrub_alias(self, where: str) -> None: |
| '''Replaces sensitive fields in field with generic names |
| |
| This function can be used to create the generic field descriptions |
| from full alias hjson definitions. |
| ''' |
| # These attributes are scrubbed. Note that the name is scrubbed in |
| # register.py already. |
| self.desc = '' |
| self.enum = [] |
| self.resval = 0 |
| self.tags = [] |
| self.alias_target = None |