284 lines
11 KiB
Python
284 lines
11 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 .bits import Bits
|
|
from .enum_entry import EnumEntry
|
|
from .lib import (check_keys, check_str, check_name,
|
|
check_list, check_str_list, check_xint)
|
|
from .params import ReggenParams
|
|
|
|
REQUIRED_FIELDS = {
|
|
'bits': ['b', "bit or bit range (msb:lsb)"]
|
|
}
|
|
|
|
OPTIONAL_FIELDS = {
|
|
'name': ['s', "name of the field"],
|
|
'desc': ['t', "description of field (required if the field has a name)"],
|
|
'swaccess': [
|
|
's', "software access permission, copied from "
|
|
"register if not provided in field. "
|
|
"(Tool adds if not provided.)"
|
|
],
|
|
'hwaccess': [
|
|
's', "hardware access permission, copied from "
|
|
"register if not prvided in field. "
|
|
"(Tool adds if not provided.)"
|
|
],
|
|
'resval': [
|
|
'x', "reset value, comes from register resval "
|
|
"if not provided in field. Zero if neither "
|
|
"are provided and the field is readable, "
|
|
"x if neither are provided and the field "
|
|
"is wo. Must match if both are provided."
|
|
],
|
|
'enum': ['l', "list of permitted enumeration groups"],
|
|
'tags': [
|
|
's',
|
|
"tags for the field, followed by the format 'tag_name:item1:item2...'"
|
|
]
|
|
}
|
|
|
|
|
|
class Field:
|
|
def __init__(self,
|
|
name: str,
|
|
desc: Optional[str],
|
|
tags: List[str],
|
|
swaccess: SWAccess,
|
|
hwaccess: HWAccess,
|
|
bits: Bits,
|
|
resval: Optional[int],
|
|
enum: Optional[List[EnumEntry]]):
|
|
self.name = name
|
|
self.desc = desc
|
|
self.tags = tags
|
|
self.swaccess = swaccess
|
|
self.hwaccess = hwaccess
|
|
self.bits = bits
|
|
self.resval = resval
|
|
self.enum = enum
|
|
|
|
@staticmethod
|
|
def from_raw(reg_name: str,
|
|
field_idx: int,
|
|
num_fields: int,
|
|
default_swaccess: SWAccess,
|
|
default_hwaccess: HWAccess,
|
|
reg_resval: Optional[int],
|
|
reg_width: int,
|
|
params: ReggenParams,
|
|
raw: object) -> 'Field':
|
|
where = 'field {} of {} register'.format(field_idx, reg_name)
|
|
rd = check_keys(raw, where,
|
|
list(REQUIRED_FIELDS.keys()),
|
|
list(OPTIONAL_FIELDS.keys()))
|
|
|
|
raw_name = rd.get('name')
|
|
if raw_name is None:
|
|
name = ('field{}'.format(field_idx + 1)
|
|
if num_fields > 1 else reg_name)
|
|
else:
|
|
name = check_name(raw_name, 'name of {}'.format(where))
|
|
|
|
raw_desc = rd.get('desc')
|
|
if raw_desc is None and raw_name is not None:
|
|
raise ValueError('Missing desc field for {}'
|
|
.format(where))
|
|
if raw_desc is None:
|
|
desc = None
|
|
else:
|
|
desc = check_str(raw_desc, 'desc field for {}'.format(where))
|
|
|
|
tags = check_str_list(rd.get('tags', []),
|
|
'tags for {}'.format(where))
|
|
|
|
raw_swaccess = rd.get('swaccess')
|
|
if raw_swaccess is not None:
|
|
swaccess = SWAccess(where, raw_swaccess)
|
|
else:
|
|
swaccess = default_swaccess
|
|
|
|
raw_hwaccess = rd.get('hwaccess')
|
|
if raw_hwaccess is not None:
|
|
hwaccess = HWAccess(where, raw_hwaccess)
|
|
else:
|
|
hwaccess = default_hwaccess
|
|
|
|
bits = Bits.from_raw(where, reg_width, params, rd['bits'])
|
|
|
|
raw_resval = rd.get('resval')
|
|
if raw_resval is None:
|
|
# The field doesn't define a reset value. Use bits from reg_resval
|
|
# if it's defined, otherwise None (which means "x").
|
|
if reg_resval is None:
|
|
resval = None
|
|
else:
|
|
resval = bits.extract_field(reg_resval)
|
|
else:
|
|
# The field does define a reset value. It should be an integer or
|
|
# 'x'. In the latter case, we set resval to None (as above).
|
|
resval = check_xint(raw_resval, 'resval field for {}'.format(where))
|
|
if resval is None:
|
|
# We don't allow a field to be explicitly 'x' on reset but for
|
|
# the containing register to have a reset value.
|
|
if reg_resval is not None:
|
|
raise ValueError('resval field for {} is "x", but the '
|
|
'register defines a resval as well.'
|
|
.format(where))
|
|
else:
|
|
# Check that the reset value is representable with bits
|
|
if not (0 <= resval <= bits.max_value()):
|
|
raise ValueError("resval field for {} is {}, which "
|
|
"isn't representable as an unsigned "
|
|
"{}-bit integer."
|
|
.format(where, resval, bits.width()))
|
|
|
|
# If the register had a resval, check this value matches it.
|
|
if reg_resval is not None:
|
|
resval_from_reg = bits.extract_field(reg_resval)
|
|
if resval != resval_from_reg:
|
|
raise ValueError('resval field for {} is {}, but the '
|
|
'register defines a resval as well, '
|
|
'where bits {}:{} would give {}.'
|
|
.format(where, resval,
|
|
bits.msb, bits.lsb,
|
|
resval_from_reg))
|
|
|
|
raw_enum = rd.get('enum')
|
|
if raw_enum is None:
|
|
enum = None
|
|
else:
|
|
enum = []
|
|
raw_entries = check_list(raw_enum,
|
|
'enum field for {}'.format(where))
|
|
enum_val_to_name = {} # type: Dict[int, str]
|
|
for idx, raw_entry in enumerate(raw_entries):
|
|
entry = EnumEntry('entry {} in enum list for {}'
|
|
.format(idx + 1, where),
|
|
bits.max_value(),
|
|
raw_entry)
|
|
if entry.value in enum_val_to_name:
|
|
raise ValueError('In {}, duplicate enum entries for '
|
|
'value {} ({} and {}).'
|
|
.format(where,
|
|
entry.value,
|
|
enum_val_to_name[entry.value],
|
|
entry.name))
|
|
enum.append(entry)
|
|
enum_val_to_name[entry.value] = entry.name
|
|
|
|
return Field(name, desc, tags, swaccess, hwaccess, bits, resval, enum)
|
|
|
|
def has_incomplete_enum(self) -> bool:
|
|
return (self.enum is not None and
|
|
len(self.enum) != 1 + self.bits.max_value())
|
|
|
|
def get_n_bits(self, hwext: bool, hwqe: bool, hwre: bool, bittype: List[str]) -> int:
|
|
'''Get the size of this field in bits
|
|
|
|
bittype should be a list of the types of signals to count. The elements
|
|
should come from the following list:
|
|
|
|
- 'q': A signal for the value of the field. Only needed if HW can read
|
|
its contents.
|
|
|
|
- 'd': A signal for the next value of the field. Only needed if HW can
|
|
write its contents.
|
|
|
|
- 'de': A write enable signal for hardware accesses. Only needed if HW
|
|
can write the field's contents and the register data is stored in the
|
|
register block (true if the hwext flag is false).
|
|
|
|
'''
|
|
n_bits = 0
|
|
if "q" in bittype and self.hwaccess.allows_read():
|
|
n_bits += self.bits.width()
|
|
if "d" in bittype and self.hwaccess.allows_write():
|
|
n_bits += self.bits.width()
|
|
if "qe" in bittype and self.hwaccess.allows_read():
|
|
n_bits += int(hwqe)
|
|
if "re" in bittype and self.hwaccess.allows_read():
|
|
n_bits += int(hwre)
|
|
if "de" in bittype and self.hwaccess.allows_write():
|
|
n_bits += int(not hwext)
|
|
return n_bits
|
|
|
|
def make_multi(self,
|
|
reg_width: int,
|
|
min_reg_idx: int,
|
|
max_reg_idx: int,
|
|
cname: str,
|
|
creg_idx: int,
|
|
stripped: bool) -> List['Field']:
|
|
assert 0 <= min_reg_idx <= max_reg_idx
|
|
|
|
# Check that we won't overflow reg_width. We assume that the LSB should
|
|
# be preserved: if msb=5, lsb=2 then the replicated copies will be
|
|
# [5:2], [11:8] etc.
|
|
num_copies = 1 + max_reg_idx - min_reg_idx
|
|
field_width = self.bits.msb + 1
|
|
|
|
if field_width * num_copies > reg_width:
|
|
raise ValueError('Cannot replicate field {} {} times: the '
|
|
'resulting width would be {}, but the register '
|
|
'width is just {}.'
|
|
.format(self.name, num_copies,
|
|
field_width * num_copies, reg_width))
|
|
|
|
desc = ('For {}{}'.format(cname, creg_idx)
|
|
if stripped else self.desc)
|
|
enum = None if stripped else self.enum
|
|
|
|
ret = []
|
|
for reg_idx in range(min_reg_idx, max_reg_idx + 1):
|
|
name = '{}_{}'.format(self.name, reg_idx)
|
|
|
|
bit_offset = field_width * (reg_idx - min_reg_idx)
|
|
bits = (self.bits
|
|
if bit_offset == 0
|
|
else self.bits.make_translated(bit_offset))
|
|
|
|
ret.append(Field(name, desc,
|
|
self.tags, self.swaccess, self.hwaccess,
|
|
bits, self.resval, enum))
|
|
|
|
return ret
|
|
|
|
def make_suffixed(self, suffix: str,
|
|
cname: str,
|
|
creg_idx: int,
|
|
stripped: bool) -> 'Field':
|
|
desc = ('For {}{}'.format(cname, creg_idx)
|
|
if stripped else self.desc)
|
|
enum = None if stripped else self.enum
|
|
|
|
return Field(self.name + suffix,
|
|
desc, self.tags, self.swaccess, self.hwaccess,
|
|
self.bits, self.resval, enum)
|
|
|
|
def _asdict(self) -> Dict[str, object]:
|
|
rd = {
|
|
'bits': self.bits.as_str(),
|
|
'name': self.name,
|
|
'swaccess': self.swaccess.key,
|
|
'hwaccess': self.hwaccess.key,
|
|
'resval': 'x' if self.resval is None else str(self.resval),
|
|
'tags': self.tags
|
|
} # type: Dict[str, object]
|
|
|
|
if self.desc is not None:
|
|
rd['desc'] = self.desc
|
|
if self.enum is not None:
|
|
rd['enum'] = self.enum
|
|
return rd
|
|
|
|
def sw_readable(self) -> bool:
|
|
return self.swaccess.key not in ['wo', 'r0w1c']
|
|
|
|
def sw_writable(self) -> bool:
|
|
return self.swaccess.key != 'ro'
|