[otbn] Teach ISS to handle bad address errors
This catches out-of-range or misaligned accesses to dmem and bad PCs
for jump/branch destinations.
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/dv/otbnsim/sim/alert.py b/hw/ip/otbn/dv/otbnsim/sim/alert.py
index 449db83..f399bf2 100644
--- a/hw/ip/otbn/dv/otbnsim/sim/alert.py
+++ b/hw/ip/otbn/dv/otbnsim/sim/alert.py
@@ -24,7 +24,7 @@
value that should be written to the ERR_CODE external register.
'''
- # Subclasses should override this class field
+ # Subclasses should override this class field or the error_code method
err_code = None # type: Optional[int]
def error_code(self) -> int:
@@ -32,6 +32,27 @@
return self.err_code
+class BadAddrError(Alert):
+ '''Raised when loading or storing or setting PC with a bad address'''
+
+ def __init__(self, operation: str, addr: int, what: str):
+ assert operation in ['pc',
+ 'narrow load', 'narrow store',
+ 'wide load', 'wide store']
+ self.operation = operation
+ self.addr = addr
+ self.what = what
+
+ def error_code(self) -> int:
+ return (ERR_CODE_BAD_INSN_ADDR
+ if self.operation == 'fetch'
+ else ERR_CODE_BAD_DATA_ADDR)
+
+ def __str__(self) -> str:
+ return ('Bad {} address of {:#08x}: {}.'
+ .format(self.operation, self.addr, self.what))
+
+
class LoopError(Alert):
'''Raised when doing something wrong with a LOOP/LOOPI'''
diff --git a/hw/ip/otbn/dv/otbnsim/sim/dmem.py b/hw/ip/otbn/dv/otbnsim/sim/dmem.py
index 6358668..39b327a 100644
--- a/hw/ip/otbn/dv/otbnsim/sim/dmem.py
+++ b/hw/ip/otbn/dv/otbnsim/sim/dmem.py
@@ -7,6 +7,7 @@
from shared.mem_layout import get_memory_layout
+from .alert import BadAddrError
from .trace import Trace
@@ -139,13 +140,34 @@
def load_u256(self, addr: int) -> int:
'''Read a u256 little-endian value from an aligned address'''
- assert 0 == addr % 32
- return self.data[addr // 32]
+ assert addr >= 0
+
+ if addr & 31:
+ raise BadAddrError('wide load', addr,
+ 'address is not 32-byte aligned')
+
+ word_addr = addr // 32
+
+ if word_addr >= len(self.data):
+ raise BadAddrError('wide load', addr,
+ 'address is above the top of dmem')
+
+ return self.data[word_addr]
def store_u256(self, addr: int, value: int) -> None:
'''Write a u256 little-endian value to an aligned address'''
- assert 0 == addr % 32
+ assert addr >= 0
assert 0 <= value < (1 << 256)
+
+ if addr & 31:
+ raise BadAddrError('wide store', addr,
+ 'address is not 32-byte aligned')
+
+ word_addr = addr // 32
+ if word_addr >= len(self.data):
+ raise BadAddrError('wide store', addr,
+ 'address is above the top of dmem')
+
self.trace.append(TraceDmemStore(addr, value, True))
def load_u32(self, addr: int) -> int:
@@ -155,8 +177,13 @@
32-bit integer.
'''
- assert 0 == addr % 4
- assert addr < 32 * len(self.data)
+ assert addr >= 0
+ if addr & 3:
+ raise BadAddrError('narrow load', addr,
+ 'address is not 4-byte aligned')
+ if (addr + 31) // 32 >= len(self.data):
+ raise BadAddrError('narrow load', addr,
+ 'address is above the top of dmem')
idx32 = addr // 4
idxW = idx32 // 8
@@ -170,9 +197,16 @@
addr should be 4-byte aligned.
'''
- assert 0 == addr % 4
- assert addr < 32 * len(self.data)
+ assert addr >= 0
assert 0 <= value <= (1 << 32) - 1
+
+ if addr & 3:
+ raise BadAddrError('narrow load', addr,
+ 'address is not 4-byte aligned')
+ if (addr + 31) // 32 >= len(self.data):
+ raise BadAddrError('narrow load', addr,
+ 'address is above the top of dmem')
+
self.trace.append(TraceDmemStore(addr, value, False))
def changes(self) -> Sequence[Trace]:
diff --git a/hw/ip/otbn/dv/otbnsim/sim/state.py b/hw/ip/otbn/dv/otbnsim/sim/state.py
index c591f12..1d93d82 100644
--- a/hw/ip/otbn/dv/otbnsim/sim/state.py
+++ b/hw/ip/otbn/dv/otbnsim/sim/state.py
@@ -4,7 +4,9 @@
from typing import List, Optional, Tuple
-from .alert import LoopError
+from shared.mem_layout import get_memory_layout
+
+from .alert import BadAddrError, LoopError
from .csr import CSRFile
from .dmem import Dmem
from .ext_regs import OTBNExtRegs
@@ -143,6 +145,10 @@
self.pc = 0
self.pc_next = None # type: Optional[int]
+
+ _, imem_size = get_memory_layout()['IMEM']
+ self.imem_size = imem_size
+
self.dmem = Dmem()
# Stall cycle support: if an instruction causes one or more stall
@@ -318,8 +324,32 @@
'''Run before running an instruction'''
self.loop_stack.check_insn(self.pc, insn_affects_control)
+ def check_jump_dest(self) -> None:
+ '''Check whether self.pc_next is a valid jump/branch target
+
+ If not, raises a BadAddrError.
+
+ '''
+ if self.pc_next is None:
+ return
+
+ # The PC should always be non-negative (it's an error in the simulator
+ # if that's come unstuck)
+ assert 0 <= self.pc_next
+
+ # Check the new PC is word-aligned
+ if self.pc_next & 3:
+ raise BadAddrError('pc', self.pc_next,
+ 'address is not 4-byte aligned')
+
+ # Check the new PC lies in instruction memory
+ if self.pc_next >= self.imem_size:
+ raise BadAddrError('pc', self.pc_next,
+ 'address lies above the top of imem')
+
def post_insn(self) -> None:
'''Update state after running an instruction but before commit'''
+ self.check_jump_dest()
self.loop_step()
def read_csr(self, idx: int) -> int: