blob: 5c28656e6904a6aec37f45dbe911ea570efaa75a [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 List, Optional, Tuple
# The number of words to accumulate
ACC_LEN = 8
# The maximum of beats we allow between when we get the last word and are told
# that CDC has finished.
MAX_CDC_WAIT = 5
class EdnClient:
def __init__(self) -> None:
# An accumulator of 32-bit values that we've read so far. This is
# non-None if we're in the middle of reading a value from the EDN. This
# should always have length <= ACC_LEN (sending an extra beat of data
# causes an error).
self._acc = None # type: Optional[List[int]]
# A counter of the number of beats we've been waiting since self._acc
# became full before being told that CDC was done. This is None unless
# self._acc contains a list of ACC_LEN values.
self._cdc_counter = None # type: Optional[int]
# If true, the next transaction that completes will be discarded.
self._poisoned = False
# If true, self._poisoned should also be true. In this case, after we
# discard the next transaction that completes, we should re-try it.
self._retry = False
self._fips_err = False
self._rep_err = False
self._last_word = None # type: Optional[int]
def request(self) -> None:
'''Start a request if there isn't one pending'''
if self._acc is None:
assert self._cdc_counter is None
self._acc = []
elif self._poisoned:
# This is a request when there was a RND fetch pending (from the
# previous OTBN run), but we now actually want the results.
self._retry = True
def poison(self) -> None:
'''Mark any current request as "poisoned" and clear the retry flag'''
if self._acc is not None:
self._poisoned = True
self._retry = False
self._fips_err = False
self._rep_err = False
def forget(self) -> None:
'''Clear the retry flag, if set.
This is used at the end of an operation to model the situation where a
previous run had started a prefetch that we're still waiting for. In
the meantime, we started an operation that also requested a prefetch.
Normally, we'd want to make sure to send out *that* request as soon as
the first (ignored) result came in. But maybe we've actually finished
this run as well! In that case, we don't care about the prefetch that
was pending and should just discard it.
'''
self._retry = False
def take_word(self, word: int, fips_err: bool) -> None:
'''
Take a 32-bit data word, which we requested.
If there is a reset in between an EDN transaction, request flag will
drop. In that case, incoming word will be ignored. Otherwise, append
it to the internal `_acc` list.
'''
assert 0 <= word < (1 << 32)
if self._acc is None:
return
else:
assert len(self._acc) < ACC_LEN
assert self._cdc_counter is None
# Set the FIPS error for each received word.
self._fips_err = fips_err | self._fips_err
# If the length of accumulated words is nonzero we check for
# repetition between accumulated and received
self._rep_err = (self._last_word == word) | self._rep_err
self._acc.append(word)
self._last_word = word
if len(self._acc) == ACC_LEN:
self._cdc_counter = 0
def edn_reset(self) -> None:
'''Called on a reset signal on the EDN clock domain'''
self._acc = None
self._cdc_counter = None
self._poisoned = False
self._retry = False
self._fips_err = False
self._rep_err = False
self._last_word = None
def cdc_complete(self) -> Tuple[Optional[int], bool, bool, bool]:
'''Called when CDC completes for a transfer
This returns a pair (data, retry). In normal operation (where the
client hasn't been poisoned), data is the 256b word we received and
retry is False.
If the client was poisoned then data is None (meaning that we should
discard the result). In this case, retry might be True, which means
that we are going to retry the operation.
'''
assert self._acc is not None
assert len(self._acc) == ACC_LEN
assert self._cdc_counter is not None
assert self._cdc_counter <= MAX_CDC_WAIT
poisoned = self._poisoned
retry = self._retry
if poisoned:
data = None
fips_err = False
rep_err = False
else:
# Assemble the ACC_LEN words into a single integer "little-endian"
# (so the first word that came in is the bottom 32 bits).
data = 0
for word in reversed(self._acc):
data = (data << 32) | word
fips_err = self._fips_err
rep_err = self._rep_err
self._acc = None
self._cdc_counter = None
self._poisoned = False
self._retry = False
self._fips_err = False
self._rep_err = False
if retry:
assert poisoned
self.request()
return (data, retry, fips_err, rep_err)
def step(self) -> None:
'''Called on each main clock cycle. Increment and check CDC counter'''
if self._cdc_counter is not None:
assert self._acc is not None
assert len(self._acc) == ACC_LEN
self._cdc_counter += 1
assert self._cdc_counter <= MAX_CDC_WAIT