blob: 33e3392f5c0b4501b75213532abd21e25da5fbe9 [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 Callable, Dict, List, Sequence
from shared.otbn_reggen import HjsonDict, load_registers
from .trace import Trace
class TraceExtRegChange(Trace):
def __init__(self, name: str, op: str, written: int, from_hw: bool, new_value: int):
self.name = name
self.op = op
self.written = written
self.from_hw = from_hw
self.new_value = new_value
def trace(self) -> str:
return ("otbn.{} {} {:#010x}{} (now {:#010x})"
.format(self.name,
self.op,
self.written,
' (from HW)' if self.from_hw else '',
self.new_value))
def rtl_trace(self) -> str:
return '! otbn.{}: {:#010x}'.format(self.name, self.new_value)
class RGField:
'''A wrapper around a field in a register as parsed by reggen'''
def __init__(self, as_dict: HjsonDict):
name = as_dict.get('name')
assert isinstance(name, str)
bitinfo = as_dict.get('bitinfo')
assert isinstance(bitinfo, tuple)
assert len(bitinfo) == 3
mask, width, lsb = bitinfo
reset_value = as_dict.get('genresval')
assert isinstance(reset_value, int)
swaccess = as_dict.get('swaccess')
assert isinstance(swaccess, str)
# We only support some values of swaccess (the ones we need)
assert swaccess in ['rw1c', 'rw', 'wo', 'r0w1c', 'ro']
assert width > 0
assert lsb >= 0
self.name = name
self.width = width
self.lsb = lsb
self.value = reset_value
# swaccess
self.w1c = swaccess in ['rw1c', 'r0w1c']
self.read_only = swaccess == 'ro'
self.read_zero = swaccess in ['wo', 'r0w1c']
self.next_value = reset_value
def _next_sw_read(self) -> int:
return 0 if self.read_zero else self.next_value
def write(self, value: int, from_hw: bool) -> int:
'''Stage the effects of writing a value (see RGReg.write)'''
assert value >= 0
masked = value & ((1 << self.width) - 1)
if self.read_only and not from_hw:
pass
elif self.w1c and not from_hw:
self.next_value &= ~masked
else:
self.next_value = masked
return self._next_sw_read()
def set_bits(self, value: int) -> int:
'''Like write, but |=.'''
masked = value & ((1 << self.width) - 1)
self.next_value |= masked
return self._next_sw_read()
def clear_bits(self, value: int) -> int:
'''Like write, but &= ~.'''
self.next_value &= ~value
return self._next_sw_read()
def read(self, from_hw: bool) -> int:
return 0 if (self.read_zero and not from_hw) else self.value
def commit(self) -> None:
self.value = self.next_value
def abort(self) -> None:
self.next_value = self.value
class RGReg:
'''A wrapper around a register as parsed by reggen'''
def __init__(self, as_dict: HjsonDict):
field_dicts = as_dict.get('fields')
assert field_dicts is not None
assert isinstance(field_dicts, list)
self.fields = [RGField(fd) for fd in field_dicts]
def _apply_fields(self,
func: Callable[[RGField, int], int],
value: int) -> int:
new_val = 0
for field in self.fields:
field_new_val = func(field, value >> field.lsb)
new_val |= field_new_val << field.lsb
return new_val
def write(self, value: int, from_hw: bool) -> int:
'''Stage the effects of writing a value.
If from_hw is true, this write is from OTBN hardware (rather than the
bus). Returns the new value visible to software (which will take effect
after calling commit).
'''
assert value >= 0
return self._apply_fields(lambda fld, fv: fld.write(fv, from_hw),
value)
def set_bits(self, value: int) -> int:
assert value >= 0
return self._apply_fields(lambda fld, fv: fld.set_bits(fv), value)
def clear_bits(self, value: int) -> int:
assert value >= 0
return self._apply_fields(lambda fld, fv: fld.clear_bits(fv), value)
def read(self, from_hw: bool) -> int:
value = 0
for field in self.fields:
value |= field.read(from_hw) << field.lsb
return value
def commit(self) -> None:
for field in self.fields:
field.commit()
def abort(self) -> None:
for field in self.fields:
field.abort()
class OTBNExtRegs:
def __init__(self) -> None:
_, reg_list = load_registers()
self.regs = {} # type: Dict[str, RGReg]
self.trace = [] # type: List[TraceExtRegChange]
# We're interested in the proper registers, and don't care about
# address tracking. So we can just ignore anything without a 'name'
# attribute.
for entry in reg_list:
name = entry.get('name')
if name is None:
continue
assert isinstance(name, str)
# reggen's validation should have checked that we have no
# duplicates.
assert name not in self.regs
self.regs[name] = RGReg(entry)
def _get_reg(self, reg_name: str) -> RGReg:
reg = self.regs.get(reg_name)
if reg is None:
raise ValueError('Unknown register name: {!r}.'.format(reg_name))
return reg
def write(self, reg_name: str, value: int, from_hw: bool) -> None:
'''Stage the effects of writing a value to a register'''
assert value >= 0
new_val = self._get_reg(reg_name).write(value, from_hw)
self.trace.append(TraceExtRegChange(reg_name, '=',
value, from_hw, new_val))
def set_bits(self, reg_name: str, value: int) -> None:
'''Set some bits of a register (HW access only)'''
assert value >= 0
new_val = self._get_reg(reg_name).set_bits(value)
self.trace.append(TraceExtRegChange(reg_name, '|=',
value, True, new_val))
def clear_bits(self, reg_name: str, value: int) -> None:
'''Clear some bits of a register (HW access only)'''
assert value >= 0
new_val = self._get_reg(reg_name).clear_bits(value)
self.trace.append(TraceExtRegChange(reg_name, '&= ~',
value, True, new_val))
def read(self, reg_name: str, from_hw: bool) -> int:
reg = self.regs.get(reg_name)
if reg is None:
raise ValueError('Unknown register name: {!r}.'.format(reg_name))
return reg.read(from_hw)
def changes(self) -> Sequence[Trace]:
return self.trace
def commit(self) -> None:
# We know that we'll only have any pending changes if self.trace is
# nonempty, so needn't bother calling commit on each register if not.
if not self.trace:
return
for reg in self.regs.values():
reg.commit()
self.trace = []
def abort(self) -> None:
for reg in self.regs.values():
reg.abort()
self.trace = []