blob: cfc5c39f24e0ab5f223ead6e7f28bdcaedb3845d [file] [log] [blame]
# 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 reggen.access import SWAccess, HWAccess
from reggen.clocking import Clocking
from reggen.field import Field
from reggen.lib import (check_keys, check_str, check_name, check_bool,
check_list, check_str_list, check_int)
from reggen.params import ReggenParams
from reggen.reg_base import RegBase
import re
REQUIRED_FIELDS = {
'name': ['s', "name of the register"],
'desc': [
't',
"description of the register. "
"This field supports the markdown syntax."
],
'fields': ['l', "list of register field description groups"]
}
OPTIONAL_FIELDS = {
'alias_target': [
's',
"name of the register to apply the alias definition to."
],
'async': [
's',
"indicates the register must cross to a different "
"clock domain before use. The value shown here "
"should correspond to one of the module's clocks."
],
'sync': [
's',
"indicates the register needs to be on another clock/reset domain."
"The value shown here should correspond to one of the module's clocks."
],
'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,
alias_target: Optional[str],
desc: str,
async_name: str,
async_clk: object,
sync_name: str,
sync_clk: object,
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.alias_target = alias_target
self.name = name
self.desc = desc
self.async_name = async_name
self.async_clk = async_clk
self.sync_name = sync_name
self.sync_clk = sync_clk
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 field.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))
# Shadow registers do not support all swaccess types, hence we
# need to check that here.
if self.shadowed:
for field in self.fields:
if field.swaccess.key not in ['rw', 'ro', 'wo', 'rw1s', 'rw0c']:
raise ValueError("Shadowed register {} has a field ({}) with "
"incompatible type '{}'."
.format(self.name,
field.name,
field.swaccess.key))
# Check that fields will be updated together. This generally comes "for
# free", but there's a slight wrinkle with RC fields: these use the
# register's read-enable input as their write-enable. We want to ensure
# either that every field that is updated as a result of bus accesses
# is RC or that none of them are.
rc_fields = []
we_fields = []
for field in self.fields:
if field.swaccess.key == 'rc':
rc_fields.append(field.name)
elif field.swaccess.allows_write:
we_fields.append(field.name)
if rc_fields and we_fields:
raise ValueError("Register {} has both software writable fields "
"({}) and read-clear fields ({}), meaning it "
"doesn't have a single write-enable signal."
.format(self.name,
', '.join(rc_fields),
', '.join(we_fields)))
# 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,
clocks: Clocking,
is_alias: bool) -> 'Register':
rd = check_keys(raw, 'register',
list(REQUIRED_FIELDS.keys()),
list(OPTIONAL_FIELDS.keys()))
name = check_name(rd['name'], 'name of register')
alias_target = None
if rd.get('alias_target') is not None:
if is_alias:
alias_target = check_name(rd['alias_target'],
'name of alias target register')
else:
raise ValueError('Field {} may not have an alias_target key'
.format(name))
elif is_alias:
raise ValueError('alias register {} does not define the '
'alias_target key.'
.format(name))
desc = check_str(rd['desc'], 'desc for {} register'.format(name))
async_name = check_str(rd.get('async', ''), 'async clock for {} register'.format(name))
async_clk = None
if async_name:
valid_clocks = clocks.clock_signals()
if async_name not in valid_clocks:
raise ValueError('async clock {} defined for {} does not exist '
'in valid module clocks {}.'
.format(async_name,
name,
valid_clocks))
else:
async_clk = clocks.get_by_clock(async_name)
sync_name = check_str(rd.get('sync', ''), 'different sync clock for {} register'
.format(name))
sync_clk = None
if sync_name:
valid_clocks = clocks.clock_signals()
if sync_name not in valid_clocks:
raise ValueError('sync clock {} defined for {} does not exist '
'in valid module clocks {}.'
.format(sync_name,
name,
valid_clocks))
else:
sync_clk = clocks.get_by_clock(sync_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 = []
used_bits = 0
for idx, rf in enumerate(raw_fields):
field = (Field.from_raw(name,
idx,
len(raw_fields),
swaccess,
hwaccess,
resval,
reg_width,
params,
hwext,
hwqe,
shadowed,
is_alias,
rf))
overlap_bits = used_bits & field.bits.bitmask()
if overlap_bits:
raise ValueError(f'Field {field.name} uses bits '
f'{overlap_bits:#x} that appear in other '
f'fields.')
used_bits |= field.bits.bitmask()
fields.append(field)
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, alias_target, desc, async_name,
async_clk, sync_name, sync_clk,
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.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_qe(self) -> bool:
'''Return true if the register or at least one field needs a q-enable'''
if self.hwqe:
return True
for fld in self.fields:
if fld.hwqe:
return True
return False
def needs_int_qe(self) -> bool:
'''Return true if the register or at least one field needs an
internal q-enable. An internal q-enable means the net
may be consumed by other reg logic but will not be exposed
in the package file.'''
if self.async_clk and self.is_hw_writable():
return True
else:
return self.needs_qe()
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 the read has a side effect.
I.e., this puts the register back into Phase 0 (next write will
go to the staged register). This is useful for software in case
it lost track of the current phase. See comportability spec for
more details:
https://docs.opentitan.org/doc/rm/register_tool/#shadow-registers
- 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)
new_alias_target = None
if self.alias_target is not None:
new_alias_target = ('{}_{}'.format(self.alias_target, creg_idx)
if creg_count > 1
else self.alias_target)
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, new_alias_target, self.desc,
self.async_name, self.async_clk,
self.sync_name, self.sync_clk,
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'''
# Async REGWEN registers are currently not supported.
# The register write enable gating is always resolved on the
# bus side.
if self.async_clk is not None:
raise ValueError('Regwen {} cannot be declared as async.'
.format(self.name))
# 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 bitmask(self) -> str:
reg_bitmask = 0
for f in self.fields:
reg_bitmask |= f.bits.bitmask()
return format(reg_bitmask, "x")
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),
} # type: Dict[str, object]
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
if self.alias_target is not None:
rd['alias_target'] = self.alias_target
return rd
def apply_alias(self, alias_reg: 'Register', where: str) -> None:
'''Compare all attributes and replace overridable values.
This updates the overridable register and field attributes with the
alias values and ensures that all non-overridable attributes have
identical values.
'''
# Attributes to be crosschecked
attrs = ['async_name', 'async_clk', 'hwext', 'hwqe', 'hwre',
'update_err_alert', 'storage_err_alert', 'shadowed']
for attr in attrs:
if getattr(self, attr) != getattr(alias_reg, attr):
raise ValueError('Value mismatch for attribute {} between '
'alias register {} and register {} in {}.'
.format(attr, self.name,
alias_reg.name, where))
# These attributes can be overridden by the aliasing mechanism.
self.name = alias_reg.name
self.desc = alias_reg.desc
self.resval = alias_reg.resval
self.tags = alias_reg.tags
self.regwen = alias_reg.regwen
# We also keep track of the alias_target when overriding attributes.
# This gives us a way to check whether a register has been overridden
# or not, and what the name of the original register was.
self.alias_target = alias_reg.alias_target
# Update the fields.
if len(alias_reg.fields) != len(self.fields):
raise ValueError('The number of fields does not match for '
'alias register {} and register {} in {}.'
.format(alias_reg.name, self.name, where))
fields = zip(alias_reg.fields, self.fields)
for k, (alias_field, field) in enumerate(fields):
# Infer the aliased field name if it has not been defined.
if alias_field.alias_target is None:
alias_field.alias_target = field.name
# Otherwise the names need to match
elif alias_field.alias_target != field.name:
raise ValueError('Inconsistent aliased field name {} '
'in alias register {} in {}.'
.format(alias_field.alias_target,
alias_reg.alias_target,
where))
# Validate and override attributes.
field.apply_alias(alias_field, where)
def scrub_alias(self, where: str) -> None:
'''Replaces sensitive fields in register with generic names
This function can be used to create the generic register descriptions
from full alias hjson definitions. It will only work on registers
where the alias_target keys are defined, and otherwise throw an error.
'''
# These attributes are scrubbed
assert self.alias_target is not None
self.name = self.alias_target
self.desc = ''
self.resval = 0
self.tags = []
self.alias_target = None
# First check that custom alias_target names are unique and that
# the numbering is correct if the name is of the form field[0-9]+.
known_field_names = {}
for k, field in enumerate(self.fields):
if field.alias_target is not None:
m = re.match(r'field[0-9]+', field.alias_target)
if m and field.alias_target != f'field{k}':
raise ValueError('Alias field alias_target {} is '
'incorrectly numbered '
'in alias register {} in {}.'
.format(field.alias_target,
self.name,
where))
elif field.alias_target in known_field_names:
raise ValueError('Alias field alias_target {} is not '
'unique in alias register {} in {}.'
.format(field.alias_target,
self.name,
where))
else:
known_field_names.update({field.alias_target: 1})
# Then, assign the field names
for k, field in enumerate(self.fields):
if field.alias_target is not None:
field.name = field.alias_target
field.alias_target = None
# Infer the aliased field name if it has not been defined.
else:
field.name = f'field{k}'
# Scrub field contents.
field.scrub_alias(where)