# 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 .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.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] @staticmethod def build_blocks(block: 'RegBlock', raw: object) -> 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. ''' if isinstance(raw, list): # This is the simple syntax block.add_raw_registers(raw, 'registers field at top-level') 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)) block.validate() assert rb_key not in ret ret[rb_key] = block return ret def add_raw_registers(self, raw: object, what: str) -> None: 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) def add_raw(self, where: str, raw: object) -> 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) def _handle_register(self, where: str, body: object) -> None: reg = Register.from_raw(self._reg_width, self.offset, self._params, body) self.add_register(reg) def _handle_reserved(self, where: str, body: object) -> None: 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) -> None: 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) -> None: 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) -> None: mr = MultiRegister(self.offset, self._addrsep, self._reg_width, self._params, body) 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.name_to_offset[lname] = reg.offset 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.name_to_offset[lname] = reg.offset 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: # 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 reg.name not in self.name_to_offset assert reg.name not in self.name_to_flat_reg self.flat_regs.append(reg) self.name_to_flat_reg[reg.name.lower()] = reg 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, field_desc or signal.desc, tags=[], swaccess=swaccess_obj, hwaccess=hwaccess_obj, bits=signal.bits, resval=0, enum=None)) reg = Register(self.offset, reg_name, reg_desc, 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: '''Return boolean indication whether reg block contains shadowed reigsters''' for r in self.flat_regs: if r.shadowed: return True return False