453 lines
17 KiB
Python
453 lines
17 KiB
Python
# 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 .access import SWAccess, HWAccess
|
|
from .field import Field
|
|
from .lib import (check_keys, check_str, check_name, check_bool,
|
|
check_list, check_str_list, check_int)
|
|
from .params import ReggenParams
|
|
from .reg_base import RegBase
|
|
|
|
import re
|
|
|
|
REQUIRED_FIELDS = {
|
|
'name': ['s', "name of the register"],
|
|
'desc': ['t', "description of the register"],
|
|
'fields': ['l', "list of register field description groups"]
|
|
}
|
|
|
|
OPTIONAL_FIELDS = {
|
|
'swaccess': [
|
|
's',
|
|
"software access permission to use for "
|
|
"fields that don't specify swaccess"
|
|
],
|
|
'hwaccess': [
|
|
's',
|
|
"hardware access permission to use for "
|
|
"fields that don't specify hwaccess"
|
|
],
|
|
'hwext': [
|
|
's',
|
|
"'true' if the register is stored outside "
|
|
"of the register module"
|
|
],
|
|
'hwqe': [
|
|
's',
|
|
"'true' if hardware uses 'q' enable signal, "
|
|
"which is latched signal of software write pulse."
|
|
],
|
|
'hwre': [
|
|
's',
|
|
"'true' if hardware uses 're' signal, "
|
|
"which is latched signal of software read pulse."
|
|
],
|
|
'regwen': [
|
|
's',
|
|
"if register is write-protected by another register, that "
|
|
"register name should be given here. empty-string for no register "
|
|
"write protection"
|
|
],
|
|
'resval': [
|
|
'd',
|
|
"reset value of full register (default 0)"
|
|
],
|
|
'tags': [
|
|
's',
|
|
"tags for the register, following the format 'tag_name:item1:item2...'"
|
|
],
|
|
'shadowed': [
|
|
's',
|
|
"'true' if the register is shadowed"
|
|
],
|
|
'update_err_alert': [
|
|
's',
|
|
"alert that will be triggered if "
|
|
"this shadowed register has update error"
|
|
],
|
|
'storage_err_alert': [
|
|
's',
|
|
"alert that will be triggered if "
|
|
"this shadowed register has storage error"
|
|
]
|
|
}
|
|
|
|
|
|
class Register(RegBase):
|
|
'''Code representing a register for reggen'''
|
|
def __init__(self,
|
|
offset: int,
|
|
name: str,
|
|
desc: str,
|
|
hwext: bool,
|
|
hwqe: bool,
|
|
hwre: bool,
|
|
regwen: Optional[str],
|
|
tags: List[str],
|
|
resval: Optional[int],
|
|
shadowed: bool,
|
|
fields: List[Field],
|
|
update_err_alert: Optional[str],
|
|
storage_err_alert: Optional[str]):
|
|
super().__init__(offset)
|
|
self.name = name
|
|
self.desc = desc
|
|
self.hwext = hwext
|
|
self.hwqe = hwqe
|
|
self.hwre = hwre
|
|
if self.hwre and not self.hwext:
|
|
raise ValueError('The {} register specifies hwre but not hwext.'
|
|
.format(self.name))
|
|
|
|
self.regwen = regwen
|
|
self.tags = tags
|
|
|
|
self.shadowed = shadowed
|
|
pattern = r'^[a-z0-9_]+_shadowed(?:_[0-9]+)?'
|
|
sounds_shadowy = re.match(pattern, self.name.lower())
|
|
if self.shadowed and not sounds_shadowy:
|
|
raise ValueError("Register {} has the shadowed flag but its name "
|
|
"doesn't end with the _shadowed suffix."
|
|
.format(self.name))
|
|
elif sounds_shadowy and not self.shadowed:
|
|
raise ValueError("Register {} has a name ending in _shadowed, but "
|
|
"the shadowed flag is not set."
|
|
.format(self.name))
|
|
|
|
# Take a copy of fields and then sort by bit index
|
|
assert fields
|
|
self.fields = fields.copy()
|
|
self.fields.sort(key=lambda field: field.bits.lsb)
|
|
|
|
# Index fields by name and check for duplicates
|
|
self.name_to_field = {} # type: Dict[str, Field]
|
|
for field in self.fields:
|
|
if field.name in self.name_to_field:
|
|
raise ValueError('Register {} has duplicate fields called {}.'
|
|
.format(self.name, field.name))
|
|
self.name_to_field[field.name] = field
|
|
|
|
# Check that fields have compatible access types if we are hwext
|
|
if self.hwext:
|
|
for field in self.fields:
|
|
if field.hwaccess.key == 'hro' and field.sw_readable():
|
|
raise ValueError('The {} register has hwext set, but '
|
|
'field {} has hro hwaccess and the '
|
|
'field value is readable by software '
|
|
'mode ({}).'
|
|
.format(self.name,
|
|
field.name,
|
|
field.swaccess.key))
|
|
if not self.hwqe and field.sw_writable():
|
|
raise ValueError('The {} register has hwext set and field '
|
|
'{} is writable by software (mode {}), '
|
|
'so the register must also enable hwqe.'
|
|
.format(self.name,
|
|
field.name,
|
|
field.swaccess.key))
|
|
|
|
# Check that field bits are disjoint
|
|
bits_used = 0
|
|
for field in self.fields:
|
|
field_mask = field.bits.bitmask()
|
|
if bits_used & field_mask:
|
|
raise ValueError('Register {} has non-disjoint fields: '
|
|
'{} uses bits {:#x} used by other fields.'
|
|
.format(self.name, field.name,
|
|
bits_used & field_mask))
|
|
|
|
# Compute a reset value and mask from our constituent fields.
|
|
self.resval = 0
|
|
self.resmask = 0
|
|
for field in self.fields:
|
|
self.resval |= (field.resval or 0) << field.bits.lsb
|
|
self.resmask |= field.bits.bitmask()
|
|
|
|
# If the register defined a reset value, make sure it matches. We've
|
|
# already checked that each field matches, but we still need to make
|
|
# sure there weren't any bits unaccounted for.
|
|
if resval is not None and self.resval != resval:
|
|
raise ValueError('Register {} specifies a reset value of {:#x} but '
|
|
'collecting reset values across its fields yields '
|
|
'{:#x}.'
|
|
.format(self.name, resval, self.resval))
|
|
|
|
self.update_err_alert = update_err_alert
|
|
self.storage_err_alert = storage_err_alert
|
|
|
|
@staticmethod
|
|
def from_raw(reg_width: int,
|
|
offset: int,
|
|
params: ReggenParams,
|
|
raw: object) -> 'Register':
|
|
rd = check_keys(raw, 'register',
|
|
list(REQUIRED_FIELDS.keys()),
|
|
list(OPTIONAL_FIELDS.keys()))
|
|
|
|
name = check_name(rd['name'], 'name of register')
|
|
desc = check_str(rd['desc'], 'desc for {} register'.format(name))
|
|
|
|
swaccess = SWAccess('{} register'.format(name),
|
|
rd.get('swaccess', 'none'))
|
|
hwaccess = HWAccess('{} register'.format(name),
|
|
rd.get('hwaccess', 'hro'))
|
|
|
|
hwext = check_bool(rd.get('hwext', False),
|
|
'hwext flag for {} register'.format(name))
|
|
|
|
hwqe = check_bool(rd.get('hwqe', False),
|
|
'hwqe flag for {} register'.format(name))
|
|
|
|
hwre = check_bool(rd.get('hwre', False),
|
|
'hwre flag for {} register'.format(name))
|
|
|
|
raw_regwen = rd.get('regwen', '')
|
|
if not raw_regwen:
|
|
regwen = None
|
|
else:
|
|
regwen = check_name(raw_regwen,
|
|
'regwen for {} register'.format(name))
|
|
|
|
tags = check_str_list(rd.get('tags', []),
|
|
'tags for {} register'.format(name))
|
|
|
|
raw_resval = rd.get('resval')
|
|
if raw_resval is None:
|
|
resval = None
|
|
else:
|
|
resval = check_int(raw_resval,
|
|
'resval for {} register'.format(name))
|
|
if not 0 <= resval < (1 << reg_width):
|
|
raise ValueError('resval for {} register is {}, '
|
|
'not an unsigned {}-bit number.'
|
|
.format(name, resval, reg_width))
|
|
|
|
shadowed = check_bool(rd.get('shadowed', False),
|
|
'shadowed flag for {} register'
|
|
.format(name))
|
|
|
|
raw_fields = check_list(rd['fields'],
|
|
'fields for {} register'.format(name))
|
|
if not raw_fields:
|
|
raise ValueError('Register {} has no fields.'.format(name))
|
|
fields = [Field.from_raw(name,
|
|
idx,
|
|
len(raw_fields),
|
|
swaccess,
|
|
hwaccess,
|
|
resval,
|
|
reg_width,
|
|
params,
|
|
rf)
|
|
for idx, rf in enumerate(raw_fields)]
|
|
|
|
raw_uea = rd.get('update_err_alert')
|
|
if raw_uea is None:
|
|
update_err_alert = None
|
|
else:
|
|
update_err_alert = check_name(raw_uea,
|
|
'update_err_alert for {} register'
|
|
.format(name))
|
|
|
|
raw_sea = rd.get('storage_err_alert')
|
|
if raw_sea is None:
|
|
storage_err_alert = None
|
|
else:
|
|
storage_err_alert = check_name(raw_sea,
|
|
'storage_err_alert for {} register'
|
|
.format(name))
|
|
|
|
return Register(offset, name, desc,
|
|
hwext, hwqe, hwre, regwen,
|
|
tags, resval, shadowed, fields,
|
|
update_err_alert, storage_err_alert)
|
|
|
|
def next_offset(self, addrsep: int) -> int:
|
|
return self.offset + addrsep
|
|
|
|
def get_n_bits(self, bittype: List[str]) -> int:
|
|
return sum(field.get_n_bits(self.hwext, self.hwqe, self.hwre, bittype)
|
|
for field in self.fields)
|
|
|
|
def get_field_list(self) -> List[Field]:
|
|
return self.fields
|
|
|
|
def is_homogeneous(self) -> bool:
|
|
return len(self.fields) == 1
|
|
|
|
def is_hw_writable(self) -> bool:
|
|
'''Returns true if any field in this register can be modified by HW'''
|
|
for fld in self.fields:
|
|
if fld.hwaccess.allows_write():
|
|
return True
|
|
return False
|
|
|
|
def get_width(self) -> int:
|
|
'''Get the width of the fields in the register in bits
|
|
|
|
This counts dead space between and below fields, so it's calculated as
|
|
one more than the highest msb.
|
|
|
|
'''
|
|
# self.fields is ordered by (increasing) LSB, so we can find the MSB of
|
|
# the register by taking the MSB of the last field.
|
|
return 1 + self.fields[-1].bits.msb
|
|
|
|
def needs_we(self) -> bool:
|
|
'''Return true if at least one field needs a write-enable'''
|
|
for fld in self.fields:
|
|
if fld.swaccess.needs_we():
|
|
return True
|
|
return False
|
|
|
|
def needs_re(self) -> bool:
|
|
'''Return true if at least one field needs a read-enable
|
|
|
|
This is true if any of the following are true:
|
|
|
|
- The register is shadowed (because shadow registers need to know
|
|
about reads)
|
|
|
|
- There's an RC field (where we'll attach the read-enable signal to
|
|
the subreg's we port)
|
|
|
|
- The register is hwext and allows reads (in which case the hardware
|
|
side might need the re signal)
|
|
|
|
'''
|
|
if self.shadowed:
|
|
return True
|
|
|
|
for fld in self.fields:
|
|
if fld.swaccess.key == 'rc':
|
|
return True
|
|
|
|
if self.hwext and fld.swaccess.allows_read():
|
|
return True
|
|
|
|
return False
|
|
|
|
def make_multi(self,
|
|
reg_width: int,
|
|
offset: int,
|
|
creg_idx: int,
|
|
creg_count: int,
|
|
regwen_multi: bool,
|
|
compact: bool,
|
|
min_reg_idx: int,
|
|
max_reg_idx: int,
|
|
cname: str) -> 'Register':
|
|
'''Generate a numbered, packed version of the register'''
|
|
assert 0 <= creg_idx < creg_count
|
|
assert 0 <= min_reg_idx <= max_reg_idx
|
|
assert compact or (min_reg_idx == max_reg_idx)
|
|
|
|
new_name = ('{}_{}'.format(self.name, creg_idx)
|
|
if creg_count > 1
|
|
else self.name)
|
|
|
|
if self.regwen is None or not regwen_multi or creg_count == 1:
|
|
new_regwen = self.regwen
|
|
else:
|
|
new_regwen = '{}_{}'.format(self.regwen, creg_idx)
|
|
|
|
strip_field = creg_idx > 0
|
|
|
|
if compact:
|
|
# Compacting multiple registers into a single "compacted" register.
|
|
# This is only supported if we have exactly one field (checked at
|
|
# the call-site)
|
|
assert len(self.fields) == 1
|
|
new_fields = self.fields[0].make_multi(reg_width,
|
|
min_reg_idx, max_reg_idx,
|
|
cname, creg_idx,
|
|
strip_field)
|
|
else:
|
|
# No compacting going on, but we still choose to rename the fields
|
|
# to match the registers
|
|
assert creg_idx == min_reg_idx
|
|
new_fields = [field.make_suffixed('_{}'.format(creg_idx),
|
|
cname, creg_idx, strip_field)
|
|
for field in self.fields]
|
|
|
|
# Don't specify a reset value for the new register. Any reset value
|
|
# defined for the original register will have propagated to its fields,
|
|
# so when we combine them here, the Register constructor can compute a
|
|
# reset value for us (which might well be different from self.resval if
|
|
# we've replicated fields).
|
|
new_resval = None
|
|
|
|
return Register(offset, new_name, self.desc,
|
|
self.hwext, self.hwqe, self.hwre, new_regwen,
|
|
self.tags, new_resval, self.shadowed, new_fields,
|
|
self.update_err_alert, self.storage_err_alert)
|
|
|
|
def check_valid_regwen(self) -> None:
|
|
'''Check that this register is valid for use as a REGWEN'''
|
|
# A REGWEN register should have a single field that's just bit zero.
|
|
if len(self.fields) != 1:
|
|
raise ValueError('One or more registers use {} as a '
|
|
'write-enable so it should have exactly one '
|
|
'field. It actually has {}.'
|
|
.format(self.name, len(self.fields)))
|
|
|
|
wen_fld = self.fields[0]
|
|
if wen_fld.bits.width() != 1:
|
|
raise ValueError('One or more registers use {} as a '
|
|
'write-enable so its field should be 1 bit wide, '
|
|
'not {}.'
|
|
.format(self.name, wen_fld.bits.width()))
|
|
if wen_fld.bits.lsb != 0:
|
|
raise ValueError('One or more registers use {} as a '
|
|
'write-enable so its field should have LSB 0, '
|
|
'not {}.'
|
|
.format(self.name, wen_fld.bits.lsb))
|
|
|
|
# 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_fld.swaccess.key != "ro" and not self.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(self.name))
|
|
|
|
if wen_fld.swaccess.key == "rw0c":
|
|
# The register is software managed: all good!
|
|
return
|
|
|
|
if wen_fld.swaccess.key == "ro" and wen_fld.hwaccess.key == "hwo":
|
|
# The register is hardware managed: that's fine too.
|
|
return
|
|
|
|
raise ValueError('One or more registers use {} as a write-enable. '
|
|
'However, its field has invalid access permissions '
|
|
'({} / {}). It should either have swaccess=RW0C '
|
|
'or have swaccess=RO and hwaccess=HWO.'
|
|
.format(self.name,
|
|
wen_fld.swaccess.key,
|
|
wen_fld.hwaccess.key))
|
|
|
|
def _asdict(self) -> Dict[str, object]:
|
|
rd = {
|
|
'name': self.name,
|
|
'desc': self.desc,
|
|
'fields': self.fields,
|
|
'hwext': str(self.hwext),
|
|
'hwqe': str(self.hwqe),
|
|
'hwre': str(self.hwre),
|
|
'tags': self.tags,
|
|
'shadowed': str(self.shadowed),
|
|
}
|
|
if self.regwen is not None:
|
|
rd['regwen'] = self.regwen
|
|
if self.update_err_alert is not None:
|
|
rd['update_err_alert'] = self.update_err_alert
|
|
if self.storage_err_alert is not None:
|
|
rd['storage_err_alert'] = self.storage_err_alert
|
|
|
|
return rd
|