tinyriscv/tools/regtool/reggen/reg_block.py

430 lines
18 KiB
Python

# 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