blob: 54cf7847e7f2d3f4706cd4ccc92e58e082e4b184 [file] [log] [blame]
# 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 Dict, List, Union
from .lib import check_int, check_list, check_str_dict
from .multi_register import MultiRegister
from .params import Params
from .register import Register
from .window import Window
class RegBlock:
def __init__(self, addrsep: int, reg_width: int, params: Params):
self._addrsep = addrsep
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]
# 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 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]
def add_raw_registers(self, raw: object) -> 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)
self.name_to_offset[lname] = reg.offset
self.multiregs.append(mr)
self.all_regs.append(mr)
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.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)
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))
# If the REGWEN bit is SW controlled, check that the register
# defaults to enabled. If this bit is read-only by SW and hence
# hardware controlled, we do not enforce this requirement.
if wen_reg.swaccess.key != "ro" and not wen_reg.resval:
raise ValueError('One or more registers use {} as a '
'write-enable. Since it is SW-controlled '
'it should have a nonzero reset value.'
.format(wenname))
if wen_reg.swaccess.key == "rw0c":
# The register is software managed: all good!
continue
if wen_reg.swaccess.key == "ro" and wen_reg.hwaccess.key == "hwo":
# The register is hardware managed: that's fine too.
continue
raise ValueError('One or more registers use {} as a write-enable. '
'However, it has invalid access permissions '
'({} / {}). It should either have swaccess=RW0C '
'or have swaccess=RO and hwaccess=HWO.'
.format(wenname,
wen_reg.swaccess.key,
wen_reg.hwaccess.key))
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