| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| '''Code representing the registers, windows etc. for a block''' |
| |
| import re |
| from typing import Callable, Dict, List, Optional, Sequence, Union |
| |
| from .alert import Alert |
| from .access import SWAccess, HWAccess |
| from .bus_interfaces import BusInterfaces |
| from .clocking import Clocking, ClockingItem |
| from .field import Field |
| from .signal import Signal |
| from .lib import check_int, check_list, check_str_dict, check_str |
| from .multi_register import MultiRegister |
| from .params import ReggenParams |
| from .register import Register |
| from .window import Window |
| |
| |
| class RegBlock: |
| def __init__(self, reg_width: int, params: ReggenParams): |
| |
| self._addrsep = (reg_width + 7) // 8 |
| self._reg_width = reg_width |
| self._params = params |
| |
| self.name = "" # type: str |
| self.clocks = {} # type: Dict[str, ClockingItem] |
| self.offset = 0 |
| self.multiregs = [] # type: List[MultiRegister] |
| self.registers = [] # type: List[Register] |
| self.windows = [] # type: List[Window] |
| |
| # Boolean indication whether ANY window in regblock has data integrity passthrough |
| self.has_data_intg_passthru = False |
| |
| # A list of all registers, expanding multiregs, ordered by offset |
| self.flat_regs = [] # type: List[Register] |
| |
| # A list of registers and multiregisters (unexpanded) |
| self.all_regs = [] # type: List[Union[Register, MultiRegister]] |
| |
| # A list of all the underlying register types used in the block. This |
| # has one entry for each actual Register, plus a single entry giving |
| # the underlying register for each MultiRegister. |
| self.type_regs = [] # type: List[Register] |
| |
| # A list with everything in order |
| self.entries = [] # type: List[object] |
| |
| # A dict of named entries, mapping name to offset |
| self.name_to_offset = {} # type: Dict[str, int] |
| |
| # A dict of all registers (expanding multiregs), mapping name to the |
| # register object |
| self.name_to_flat_reg = {} # type: Dict[str, Register] |
| |
| # A list of all write enable names |
| self.wennames = [] # type: List[str] |
| |
| # Boolean indication that the block is fully asynchronous |
| self.async_if = False |
| |
| @staticmethod |
| def build_blocks(block: 'RegBlock', |
| raw: object, |
| bus: BusInterfaces, |
| clocks: Clocking, |
| is_alias: bool) -> Dict[Optional[str], 'RegBlock']: |
| '''Build a dictionary of blocks for a 'registers' field in the hjson |
| |
| There are two different syntaxes we might see here. The simple syntax |
| just consists of a list of entries (register, multireg, window, |
| skipto). If we see that, each entry gets added to init_block and then |
| we return {None: init_block}. |
| |
| The more complicated syntax is a dictionary. This parses from hjson as |
| an OrderedDict which we walk in document order. Entries from the first |
| key/value pair in the dictionary will be added to init_block. Later |
| key/value pairs start empty RegBlocks. The return value is a dictionary |
| mapping the keys we saw to their respective RegBlocks. |
| |
| The flag is_alias determines whether these blocks contain alias |
| register definitions. If that flag is set, the individual register |
| definitions must contain the alias_target key, which specifies the |
| name of the generic register to override. |
| ''' |
| if isinstance(raw, list): |
| # This is the simple syntax |
| block.add_raw_registers(raw, |
| 'registers field at top-level', |
| clocks, |
| bus.device_async.get(None), |
| is_alias) |
| return {None: block} |
| |
| # This is the more complicated syntax |
| if not isinstance(raw, dict): |
| raise ValueError('registers field at top-level is ' |
| 'neither a list or a dictionary.') |
| |
| ret = {} # type: Dict[Optional[str], RegBlock] |
| for idx, (r_key, r_val) in enumerate(raw.items()): |
| if idx > 0: |
| block = RegBlock(block._reg_width, block._params) |
| |
| rb_key = check_str(r_key, |
| 'the key for item {} of ' |
| 'the registers dictionary at top-level' |
| .format(idx + 1)) |
| rb_val = check_list(r_val, |
| 'the value for item {} of ' |
| 'the registers dictionary at top-level' |
| .format(idx + 1)) |
| |
| block.add_raw_registers(rb_val, |
| 'item {} of the registers ' |
| 'dictionary at top-level' |
| .format(idx + 1), |
| clocks, |
| bus.device_async.get(r_key), |
| is_alias) |
| block.validate() |
| |
| assert rb_key not in ret |
| block.name = rb_key |
| ret[rb_key] = block |
| |
| return ret |
| |
| def add_raw_registers(self, |
| raw: object, |
| what: str, |
| clocks: Clocking, |
| async_if: Optional[str], |
| is_alias: bool) -> None: |
| |
| # the interface is fully asynchronous |
| if async_if: |
| self.async_if = True |
| self.clocks[async_if] = clocks.get_by_clock(async_if) |
| |
| rl = check_list(raw, 'registers field at top-level') |
| for entry_idx, entry_raw in enumerate(rl): |
| where = ('entry {} of the top-level registers field' |
| .format(entry_idx + 1)) |
| self.add_raw(where, entry_raw, clocks, is_alias) |
| |
| def add_raw(self, |
| where: str, |
| raw: object, |
| clocks: Clocking, |
| is_alias: bool) -> None: |
| entry = check_str_dict(raw, where) |
| |
| handlers = { |
| 'register': self._handle_register, |
| 'reserved': self._handle_reserved, |
| 'skipto': self._handle_skipto, |
| 'window': self._handle_window, |
| 'multireg': self._handle_multireg |
| } |
| |
| entry_type = 'register' |
| entry_body = entry # type: object |
| |
| for t in ['reserved', 'skipto', 'window', 'multireg']: |
| t_body = entry.get(t) |
| if t_body is not None: |
| # Special entries look like { window: { ... } }, so if we |
| # get a hit, this should be the only key in entry. Note |
| # that this also checks that nothing has more than one |
| # entry type. |
| if len(entry) != 1: |
| other_keys = [k for k in entry if k != t] |
| assert other_keys |
| raise ValueError('At offset {:#x}, {} has key {}, which ' |
| 'should give its type. But it also has ' |
| 'other keys too: {}.' |
| .format(self.offset, |
| where, t, ', '.join(other_keys))) |
| entry_type = t |
| entry_body = t_body |
| |
| entry_where = ('At offset {:#x}, {}, type {!r}' |
| .format(self.offset, where, entry_type)) |
| |
| handlers[entry_type](entry_where, entry_body, clocks, is_alias) |
| |
| def _validate_async(self, name: Optional[str], clk: object) -> None: |
| '''Check for async definition consistency |
| |
| If a reg block is marked fully asynchronous through its bus interface, |
| its register definition cannot also mark individual registers with |
| asynchronous designations. |
| |
| The two asynchronous regfile schemes are mutually exclusive. |
| ''' |
| |
| if self.name: |
| block_name = self.name |
| else: |
| block_name = "Default" |
| |
| if self.async_if and name: |
| raise ValueError(f''' |
| {block_name} register block has incompatible async definitions. |
| The corresponding device interface is marked fully async, however |
| there are individual registers that also contain the async_clk |
| designation, this is not allowed. |
| |
| Either remove all register async_clk designations, or remove |
| async designation of the bus interface. |
| ''') |
| |
| # If there is an asynchronous clock defined, then the clock must be a |
| # valid clocking item |
| if name: |
| assert isinstance(clk, ClockingItem) |
| self.clocks[name] = clk |
| |
| def _handle_register(self, |
| where: str, |
| body: object, |
| clocks: Clocking, |
| is_alias: bool) -> None: |
| reg = Register.from_raw(self._reg_width, |
| self.offset, |
| self._params, |
| body, |
| clocks, |
| is_alias) |
| |
| self._validate_async(reg.async_name, reg.async_clk) |
| |
| self.add_register(reg) |
| |
| def _handle_reserved(self, |
| where: str, |
| body: object, |
| clocks: Optional[Clocking], |
| is_alias: bool) -> None: |
| if is_alias: |
| raise ValueError('Aliasing reserved regions is not supported yet') |
| nreserved = check_int(body, 'body of ' + where) |
| if nreserved <= 0: |
| raise ValueError('Reserved count in {} is {}, ' |
| 'which is not positive.' |
| .format(where, nreserved)) |
| |
| self.offset += self._addrsep * nreserved |
| |
| def _handle_skipto(self, |
| where: str, |
| body: object, |
| clocks: Optional[Clocking], |
| is_alias: bool) -> None: |
| if is_alias: |
| raise ValueError('The skipto command is not supported in ' |
| 'alias register definitions') |
| |
| skipto = check_int(body, 'body of ' + where) |
| if skipto < self.offset: |
| raise ValueError('Destination of skipto in {} is {:#x}, ' |
| 'is less than the current offset, {:#x}.' |
| .format(where, skipto, self.offset)) |
| if skipto % self._addrsep: |
| raise ValueError('Destination of skipto in {} is {:#x}, ' |
| 'not a multiple of addrsep, {:#x}.' |
| .format(where, skipto, self._addrsep)) |
| self.offset = skipto |
| |
| def _handle_window(self, |
| where: str, |
| body: object, |
| clocks: Optional[Clocking], |
| is_alias: bool) -> None: |
| if is_alias: |
| raise ValueError('Aliasing window regions is not supported yet') |
| |
| window = Window.from_raw(self.offset, |
| self._reg_width, self._params, body) |
| if window.name is not None: |
| lname = window.name.lower() |
| if lname in self.name_to_offset: |
| raise ValueError('Window {} (at offset {:#x}) has the ' |
| 'same name as something at offset {:#x}.' |
| .format(window.name, window.offset, |
| self.name_to_offset[lname])) |
| self.add_window(window) |
| |
| def _handle_multireg(self, |
| where: str, |
| body: object, |
| clocks: Clocking, |
| is_alias: bool) -> None: |
| mr = MultiRegister(self.offset, |
| self._addrsep, |
| self._reg_width, |
| self._params, |
| body, |
| clocks, |
| is_alias) |
| |
| # validate async schemes |
| self._validate_async(mr.async_name, mr.async_clk) |
| |
| for reg in mr.regs: |
| lname = reg.name.lower() |
| if lname in self.name_to_offset: |
| raise ValueError('Multiregister {} (at offset {:#x}) expands ' |
| 'to a register with name {} (at offset ' |
| '{:#x}), but this already names something at ' |
| 'offset {:#x}.' |
| .format(mr.reg.name, mr.reg.offset, |
| reg.name, reg.offset, |
| self.name_to_offset[lname])) |
| self._add_flat_reg(reg) |
| if mr.dv_compact is False: |
| self.type_regs.append(reg) |
| |
| self.multiregs.append(mr) |
| self.all_regs.append(mr) |
| if mr.dv_compact is True: |
| self.type_regs.append(mr.reg) |
| self.entries.append(mr) |
| self.offset = mr.next_offset(self._addrsep) |
| |
| def add_register(self, reg: Register) -> None: |
| assert reg.offset == self.offset |
| |
| lname = reg.name.lower() |
| if lname in self.name_to_offset: |
| raise ValueError('Register {} (at offset {:#x}) has the same ' |
| 'name as something at offset {:#x}.' |
| .format(reg.name, reg.offset, |
| self.name_to_offset[lname])) |
| self._add_flat_reg(reg) |
| |
| self.registers.append(reg) |
| self.all_regs.append(reg) |
| self.type_regs.append(reg) |
| self.entries.append(reg) |
| self.offset = reg.next_offset(self._addrsep) |
| |
| if reg.regwen is not None and reg.regwen not in self.wennames: |
| self.wennames.append(reg.regwen) |
| |
| def _add_flat_reg(self, reg: Register) -> None: |
| lname = reg.name.lower() |
| # The first assertion is checked at the call site (where we can print |
| # out a nicer message for multiregs). The second assertion should be |
| # implied by the first. |
| assert lname not in self.name_to_offset |
| assert lname not in self.name_to_flat_reg |
| |
| self.flat_regs.append(reg) |
| self.name_to_flat_reg[lname] = reg |
| self.name_to_offset[lname] = reg.offset |
| |
| def _rename_flat_reg(self, old_name: str, new_name: str) -> None: |
| '''Renames keys in name_to_offset and name_to_flat_reg dicts''' |
| old_lname = old_name.lower() |
| new_lname = new_name.lower() |
| # Only existing regs can be renamed, and new reg name must be unique. |
| assert old_lname in self.name_to_offset |
| assert old_lname in self.name_to_flat_reg |
| assert new_lname not in self.name_to_offset |
| assert new_lname not in self.name_to_flat_reg |
| |
| # Remove old key and reinsert register with new key name. |
| self.name_to_flat_reg[new_lname] = self.name_to_flat_reg.pop(old_lname) |
| self.name_to_offset[new_lname] = self.name_to_offset.pop(old_lname) |
| |
| def add_window(self, window: Window) -> None: |
| if window.name is not None: |
| lname = window.name.lower() |
| assert lname not in self.name_to_offset |
| self.name_to_offset[lname] = window.offset |
| |
| self.windows.append(window) |
| self.entries.append(window) |
| assert self.offset <= window.offset |
| self.offset = window.next_offset(self._addrsep) |
| |
| self.has_data_intg_passthru |= window.data_intg_passthru |
| |
| def validate(self) -> None: |
| '''Run this to check consistency after all registers have been added''' |
| |
| # Check that every write-enable register has a good name, a valid reset |
| # value, and valid access permissions. |
| for wenname in self.wennames: |
| # check the REGWEN naming convention |
| if re.fullmatch(r'(.+_)*REGWEN(_[0-9]+)?', wenname) is None: |
| raise ValueError("Regwen name {} must have the suffix '_REGWEN'" |
| .format(wenname)) |
| |
| wen_reg = self.name_to_flat_reg.get(wenname.lower()) |
| if wen_reg is None: |
| raise ValueError('One or more registers use {} as a ' |
| 'write-enable, but there is no such register.' |
| .format(wenname)) |
| |
| wen_reg.check_valid_regwen() |
| |
| def get_n_bits(self, bittype: List[str] = ["q"]) -> int: |
| '''Returns number of bits in registers in this block. |
| |
| This includes those expanded from multiregs. See Field.get_n_bits for a |
| description of the bittype argument. |
| |
| ''' |
| return sum(reg.get_n_bits(bittype) for reg in self.flat_regs) |
| |
| def as_dicts(self) -> List[object]: |
| entries = [] # type: List[object] |
| offset = 0 |
| for entry in self.entries: |
| assert (isinstance(entry, Register) or |
| isinstance(entry, MultiRegister) or |
| isinstance(entry, Window)) |
| |
| next_off = entry.offset |
| assert offset <= next_off |
| res_bytes = next_off - offset |
| if res_bytes: |
| assert res_bytes % self._addrsep == 0 |
| entries.append({'reserved': res_bytes // self._addrsep}) |
| |
| entries.append(entry) |
| offset = entry.next_offset(self._addrsep) |
| |
| return entries |
| |
| _FieldFormatter = Callable[[bool, str], str] |
| |
| def _add_intr_alert_reg(self, |
| signals: Sequence[Signal], |
| reg_name: str, |
| reg_desc: str, |
| field_desc_fmt: Optional[Union[str, _FieldFormatter]], |
| swaccess: str, |
| hwaccess: str, |
| is_testreg: bool, |
| reg_tags: List[str]) -> None: |
| swaccess_obj = SWAccess('RegBlock._make_intr_alert_reg()', swaccess) |
| hwaccess_obj = HWAccess('RegBlock._make_intr_alert_reg()', hwaccess) |
| |
| fields = [] |
| for signal in signals: |
| if field_desc_fmt is None: |
| field_desc = signal.desc |
| elif isinstance(field_desc_fmt, str): |
| field_desc = field_desc_fmt |
| else: |
| width = signal.bits.width() |
| field_desc = field_desc_fmt(width > 1, signal.name) |
| |
| fields.append(Field(signal.name, |
| None, # no alias target |
| field_desc or signal.desc, |
| tags=[], |
| swaccess=swaccess_obj, |
| hwaccess=hwaccess_obj, |
| hwqe=is_testreg, |
| bits=signal.bits, |
| resval=0, |
| enum=None, |
| mubi=False)) |
| |
| reg = Register(self.offset, |
| reg_name, |
| None, # no alias target |
| reg_desc, |
| async_name="", |
| async_clk=None, |
| hwext=is_testreg, |
| hwqe=is_testreg, |
| hwre=False, |
| regwen=None, |
| tags=reg_tags, |
| resval=None, |
| shadowed=False, |
| fields=fields, |
| update_err_alert=None, |
| storage_err_alert=None) |
| self.add_register(reg) |
| |
| def make_intr_regs(self, interrupts: Sequence[Signal]) -> None: |
| assert interrupts |
| assert interrupts[-1].bits.msb < self._reg_width |
| |
| self._add_intr_alert_reg(interrupts, |
| 'INTR_STATE', |
| 'Interrupt State Register', |
| None, |
| 'rw1c', |
| 'hrw', |
| False, |
| # Some POR routines have the potential to |
| # unpredictably set some `intr_state` fields |
| # for various IPs, so we exclude all |
| # `intr_state` accesses from CSR checks to |
| # prevent this from occurring. |
| # |
| # An example of an `intr_state` mismatch error |
| # occurring due to a POR routine can be seen in |
| # issue #6888. |
| ["excl:CsrAllTests:CsrExclAll"]) |
| self._add_intr_alert_reg(interrupts, |
| 'INTR_ENABLE', |
| 'Interrupt Enable Register', |
| lambda w, n: ('Enable interrupt when ' |
| '{}!!INTR_STATE.{} is set.' |
| .format('corresponding bit in ' |
| if w else '', |
| n)), |
| 'rw', |
| 'hro', |
| False, |
| []) |
| self._add_intr_alert_reg(interrupts, |
| 'INTR_TEST', |
| 'Interrupt Test Register', |
| lambda w, n: ('Write 1 to force ' |
| '{}!!INTR_STATE.{} to 1.' |
| .format('corresponding bit in ' |
| if w else '', |
| n)), |
| 'wo', |
| 'hro', |
| True, |
| # intr_test csr is WO so reads back 0s |
| ["excl:CsrNonInitTests:CsrExclWrite"]) |
| |
| def make_alert_regs(self, alerts: List[Alert]) -> None: |
| assert alerts |
| assert len(alerts) < self._reg_width |
| self._add_intr_alert_reg(alerts, |
| 'ALERT_TEST', |
| 'Alert Test Register', |
| ('Write 1 to trigger ' |
| 'one alert event of this kind.'), |
| 'wo', |
| 'hro', |
| True, |
| []) |
| |
| def get_addr_width(self) -> int: |
| '''Calculate the number of bits to address every byte of the block''' |
| return (self.offset - 1).bit_length() |
| |
| def has_shadowed_reg(self) -> bool: |
| '''Returns true if reg block contains shadowed regs''' |
| for r in self.flat_regs: |
| if r.shadowed: |
| return True |
| |
| return False |
| |
| def has_internal_shadowed_reg(self) -> bool: |
| '''Returns true if reg block contains shadowed regs in internal regs''' |
| for r in self.flat_regs: |
| if r.shadowed and not r.hwext: |
| return True |
| |
| return False |
| |
| def apply_alias(self, alias_block: 'RegBlock', where: str) -> None: |
| '''Validates alias description and overrides values in this block. |
| |
| This updates the overridable register and field attributes with the |
| alias values and ensures that all non-overridable attributes have |
| identical values. |
| ''' |
| # Before doing anything, check that the new alias names do not exist. |
| intersection = (alias_block.name_to_flat_reg.keys() & |
| self.name_to_flat_reg.keys()) |
| if intersection: |
| raise ValueError('Alias register names {} are not unique in alias ' |
| ' {}'.format(list(intersection), where)) |
| |
| # Loop over registers, validate the structure and update the generic |
| # register data structure. Since the internal register |
| # lists "registers", "flat_regs", "all_regs", "type_regs" |
| # and "entries" use references to the reg objects, everything stays in |
| # sync - except when multiregs are involved. To that end, another |
| # update loop over multiregs is required further below. |
| for alias_reg in alias_block.registers: |
| # First, check existence of the register to be aliased |
| if alias_reg.alias_target is None: |
| raise ValueError('No alias target register defined for ' |
| 'alias name {} in {}' |
| .format(alias_reg.name, where)) |
| |
| target = alias_reg.alias_target.lower() |
| if target not in self.name_to_flat_reg: |
| raise ValueError('Aliased target register {} with alias ' |
| 'name {} does not exist in reg ' |
| 'block {} ({}).' |
| .format(target, |
| alias_reg.name, |
| self.name, |
| where)) |
| |
| # This is the register we want to alias over. Check that the |
| # non-overridable attributes match, and override the attributes. |
| reg = self.name_to_flat_reg[target] |
| reg.apply_alias(alias_reg, where) |
| |
| # Build a local index of all multiregs. We don't store this in the |
| # class since it is only used once here. |
| name_to_multiregs = {mr.name.lower(): mr for mr in self.multiregs} |
| |
| # Loop over multiregisters, validate the structure and update the |
| # generic multiregister data structure. |
| for alias_mr in alias_block.multiregs: |
| # First, check existence of the register to be aliased |
| if alias_mr.alias_target is None: |
| raise ValueError('No alias target multiregister defined for ' |
| 'alias name {} in {}' |
| .format(alias_mr.name, where)) |
| |
| target = alias_mr.alias_target.lower() |
| if target not in name_to_multiregs: |
| raise ValueError('Aliased target multiregister {} with alias ' |
| 'name {} does not exist in reg ' |
| 'block {} ({}).' |
| .format(target, |
| alias_mr.name, |
| self.name, |
| where)) |
| |
| # This is the register we want to alias over. Check that the |
| # non-overridable attributes match, and override the attributes. |
| mr = name_to_multiregs[target] |
| mr.apply_alias(alias_mr, where) |
| |
| # Finally, we loop over the flat registers (which includes expanded |
| # multiregs) and update the name_to_offset and name_to_flat_reg maps. |
| for alias_name, alias_reg in alias_block.name_to_flat_reg.items(): |
| assert (alias_reg.alias_target) |
| self._rename_flat_reg(alias_reg.alias_target, alias_name) |
| |
| def scrub_alias(self, where: str) -> None: |
| '''Replaces sensitive fields in reg block with generic names |
| |
| This function can be used to create the generic register descriptions |
| from full alias hjson definitions. It will only work on reg blocks |
| where the alias_target keys are defined, and otherwise throw an error. |
| ''' |
| # Loop over registers, and scrub information. |
| for reg in self.registers: |
| if reg.alias_target is None: |
| raise ValueError('No alias target register defined for ' |
| 'alias name {} in {}' |
| .format(reg.name, where)) |
| reg.scrub_alias(where) |
| |
| # Loop over multiregisters, and scrub information. |
| for alias_mr in self.multiregs: |
| # First, check existence of the register to be aliased |
| if alias_mr.alias_target is None: |
| raise ValueError('No alias target multiregister defined for ' |
| 'alias name {} in {}' |
| .format(alias_mr.name, where)) |
| alias_mr.scrub_alias(where) |
| |
| # Make a shallow copy of this dict, since we are about to modify the |
| # name mapping below. |
| name_to_flat_reg_copy = self.name_to_flat_reg.copy() |
| # Replace all alias with generic names. |
| # Note that the scrubbing above assigns .alias_target to .name. |
| for alias_name, reg in name_to_flat_reg_copy.items(): |
| self._rename_flat_reg(alias_name, reg.name) |