blob: f0822ed85b073b04f42b1e1191054e4002085a7c [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "otbn_model.h"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include "iss_wrapper.h"
#include "otbn_model_dpi.h"
#include "otbn_trace_checker.h"
#include "sv_scoped.h"
#include "sv_utils.h"
extern "C" {
int otbn_rf_peek(int index, svBitVecVal *val);
int otbn_stack_element_peek(int index, svBitVecVal *val);
}
#define RUNNING_BIT (1U << 0)
#define CHECK_DUE_BIT (1U << 1)
#define FAILED_STEP_BIT (1U << 2)
// Values come from otbn_pkg to signify the value for specific operations.
#define CMD_EXECUTE 0xD8
#define CMD_SECWIPE_DMEM 0xC3
#define CMD_SECWIPE_IMEM 0x1E
// Values of the `STATUS` register, as defined in `otbn.hjson`.
#define STATUS_IDLE 0x00
#define STATUS_BUSY_EXECUTE 0x01
#define STATUS_BUSY_SEC_WIPE_DMEM 0x02
#define STATUS_BUSY_SEC_WIPE_IMEM 0x03
#define STATUS_BUSY_SEC_WIPE_INT 0x04
#define STATUS_LOCKED 0xFF
// Read (the start of) the contents of a file at path as a vector of bytes.
// Expects num_bytes bytes of data. On failure, throws a std::runtime_error.
static Ecc32MemArea::EccWords read_words_from_file(const std::string &path,
size_t num_words) {
std::filebuf fb;
if (!fb.open(path.c_str(), std::ios::in | std::ios::binary)) {
std::ostringstream oss;
oss << "Cannot open the file '" << path << "'.";
throw std::runtime_error(oss.str());
}
Ecc32MemArea::EccWords ret;
ret.reserve(num_words);
char minibuf[5];
for (size_t i = 0; i < num_words; ++i) {
std::streamsize chars_in = fb.sgetn(minibuf, 5);
if (chars_in != 5) {
std::ostringstream oss;
oss << "Cannot read word " << i << " from " << path
<< " (expected 5 bytes, but actually got " << chars_in << ").";
throw std::runtime_error(oss.str());
}
// The layout should be a validity byte (either 0 or 1), followed
// by 4 bytes with a little-endian 32-bit word.
uint8_t vld_byte = minibuf[0];
if (vld_byte > 2) {
std::ostringstream oss;
oss << "Word " << i << " at " << path
<< " had a validity byte with value " << (int)vld_byte
<< "; not 0 or 1.";
throw std::runtime_error(oss.str());
}
bool valid = vld_byte == 1;
uint32_t word = 0;
for (int j = 0; j < 4; ++j) {
word |= (uint32_t)(uint8_t)minibuf[j + 1] << 8 * j;
}
ret.push_back(std::make_pair(valid, word));
}
return ret;
}
// Write some words to a new file at path. On failure, throws a
// std::runtime_error.
static void write_words_to_file(const std::string &path,
const Ecc32MemArea::EccWords &words) {
std::filebuf fb;
if (!fb.open(path.c_str(), std::ios::out | std::ios::binary)) {
std::ostringstream oss;
oss << "Cannot open the file '" << path << "'.";
throw std::runtime_error(oss.str());
}
for (const Ecc32MemArea::EccWord &word : words) {
uint8_t bytes[5];
bool valid = word.first;
uint32_t w32 = word.second;
bytes[0] = valid ? 1 : 0;
for (int j = 0; j < 4; ++j) {
bytes[j + 1] = (w32 >> (8 * j)) & 0xff;
}
std::streamsize chars_out =
fb.sputn(reinterpret_cast<const char *>(&bytes), 5);
if (chars_out != 5) {
std::ostringstream oss;
oss << "Failed to write to " << path << ".";
throw std::runtime_error(oss.str());
}
}
}
template <typename T>
static std::array<T, 32> get_rtl_regs(const std::string &reg_scope) {
std::array<T, 32> ret;
static_assert(sizeof(T) <= 256 / 8, "Can only copy 256 bits");
SVScoped scoped(reg_scope);
// otbn_rf_peek passes data as a packed array of svBitVecVal words (for a
// "bit [255:0]" argument). Allocate 256 bits (= 32 bytes) as
// 32/sizeof(svBitVecVal) words on the stack.
svBitVecVal buf[256 / 8 / sizeof(svBitVecVal)];
for (int i = 0; i < 32; ++i) {
if (!otbn_rf_peek(i, buf)) {
std::ostringstream oss;
oss << "Failed to peek into RTL to get value of register " << i
<< " at scope `" << reg_scope << "'.";
throw std::runtime_error(oss.str());
}
memcpy(&ret[i], buf, sizeof(T));
}
return ret;
}
template <typename T>
static std::vector<T> get_stack(const std::string &stack_scope) {
std::vector<T> ret;
static_assert(sizeof(T) <= 256 / 8, "Can only copy 256 bits");
SVScoped scoped(stack_scope);
// otbn_stack_element_peek passes data as a packed array of svBitVecVal words
// (for a "bit [255:0]" argument). Allocate 256 bits (= 32 bytes) as
// 32/sizeof(svBitVecVal) words on the stack.
svBitVecVal buf[256 / 8 / sizeof(svBitVecVal)];
int i = 0;
while (1) {
int peek_result = otbn_stack_element_peek(i, buf);
// otbn_stack_element_peek is defined in otbn_stack_snooper_if.sv. Possible
// return values are: 0 on success, if we've returned an element. 1 if the
// stack doesn't have an element at index i. 2 if something terrible has
// gone wrong (such as a completely bogus index).
assert(peek_result <= 2);
if (peek_result == 2) {
std::ostringstream oss;
oss << "Failed to peek into RTL to get value of stack element " << i
<< " at scope `" << stack_scope << "'.";
throw std::runtime_error(oss.str());
}
if (peek_result == 1) {
// No more elements on stack
break;
}
T stack_element;
memcpy(&stack_element, buf, sizeof(T));
ret.push_back(stack_element);
++i;
}
return ret;
}
OtbnModel::OtbnModel(const std::string &mem_scope,
const std::string &design_scope)
: mem_util_(mem_scope), design_scope_(design_scope) {
assert(mem_scope.size() && design_scope.size());
}
OtbnModel::~OtbnModel() {}
int OtbnModel::take_loop_warps(const OtbnMemUtil &memutil) {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->clear_loop_warps();
} catch (std::runtime_error &err) {
std::cerr << "Error when clearing loop warps: " << err.what() << "\n";
return -1;
}
for (auto &pr : memutil.GetLoopWarps()) {
auto &key = pr.first;
uint32_t addr = key.first;
uint32_t from_cnt = key.second;
uint32_t to_cnt = pr.second;
try {
iss->add_loop_warp(addr, from_cnt, to_cnt);
} catch (const std::runtime_error &err) {
std::cerr << "Error when adding loop warp: " << err.what() << "\n";
return -1;
}
}
return 0;
}
int OtbnModel::start_operation(command_t command) {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
const char *cmd_desc = "unknown";
ISSWrapper::command_t iss_command;
try {
switch (command) {
case Execute: {
cmd_desc = "execute";
iss_command = ISSWrapper::Execute;
std::string dfname(iss->make_tmp_path("dmem"));
std::string ifname(iss->make_tmp_path("imem"));
write_words_to_file(dfname, get_sim_memory(false));
write_words_to_file(ifname, get_sim_memory(true));
iss->load_d(dfname);
iss->load_i(ifname);
} break;
case DmemWipe:
cmd_desc = "DMEM wipe";
iss_command = ISSWrapper::DmemWipe;
break;
case ImemWipe:
cmd_desc = "IMEM wipe";
iss_command = ISSWrapper::ImemWipe;
break;
default:
assert(0);
}
iss->start_operation(iss_command);
} catch (const std::runtime_error &err) {
std::cerr << "Error when starting " << cmd_desc
<< " operation: " << err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::edn_flush() {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->edn_flush();
} catch (const std::runtime_error &err) {
std::cerr << "Error when flushing EDN: " << err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::edn_rnd_step(svLogicVecVal *edn_rnd_data /* logic [31:0] */,
unsigned char fips_err) {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
assert(fips_err == 0 || fips_err == 1);
try {
iss->edn_rnd_step(edn_rnd_data->aval, fips_err != 0);
} catch (const std::runtime_error &err) {
std::cerr << "Error when stepping EDN for RND: " << err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::edn_urnd_step(svLogicVecVal *edn_urnd_data /* logic [31:0] */) {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->edn_urnd_step(edn_urnd_data->aval);
} catch (const std::runtime_error &err) {
std::cerr << "Error when stepping EDN for URND: " << err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::edn_rnd_cdc_done() {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->edn_rnd_cdc_done();
} catch (const std::runtime_error &err) {
std::cerr << "Error when signalling CDC done for RND: " << err.what()
<< "\n";
return -1;
}
return 0;
}
int OtbnModel::edn_urnd_cdc_done() {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->edn_urnd_cdc_done();
} catch (const std::runtime_error &err) {
std::cerr << "Error when signalling CDC done for URND: " << err.what()
<< "\n";
return -1;
}
return 0;
}
int OtbnModel::otp_key_cdc_done() {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->otp_key_cdc_done();
} catch (const std::runtime_error &err) {
std::cerr << "Error when signalling CDC done for OTP key: " << err.what()
<< "\n";
return -1;
}
return 0;
}
int OtbnModel::set_keymgr_value(svLogicVecVal *key0 /* logic [383:0] */,
svLogicVecVal *key1 /* logic [383:0] */,
unsigned char valid) {
ISSWrapper *iss = ensure_wrapper();
std::array<uint32_t, 12> key0_arr;
std::array<uint32_t, 12> key1_arr;
assert(valid == 0 || valid == 1);
for (int i = 0; i < 12; i++) {
key0_arr[i] = key0[i].aval;
key1_arr[i] = key1[i].aval;
}
try {
iss->set_keymgr_value(key0_arr, key1_arr, valid != 0);
} catch (const std::runtime_error &err) {
std::cerr << "Error when setting keymgr value: " << err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::step(svBitVecVal *status /* bit [7:0] */,
svBitVecVal *insn_cnt /* bit [31:0] */,
svBitVecVal *rnd_req /* bit [0:0] */,
svBitVecVal *err_bits /* bit [31:0] */,
svBitVecVal *stop_pc /* bit [31:0] */) {
assert(insn_cnt && err_bits && stop_pc);
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
switch (iss->step(has_rtl())) {
case -1:
// Something went wrong, such as a trace mismatch. We've already printed
// a message to stderr so can just return -1.
return -1;
case 1:
// The simulation has stopped. Fill in status, insn_cnt, err_bits and
// stop_pc. Note that status should never have anything in its top 24
// bits.
if (iss->get_mirrored().status >> 8) {
throw std::runtime_error("STATUS register had non-empty top bits.");
}
set_sv_u8(status, iss->get_mirrored().status);
svPutBitselBit(rnd_req, 0, (iss->get_mirrored().rnd_req & 1));
set_sv_u32(insn_cnt, iss->get_mirrored().insn_cnt);
set_sv_u32(err_bits, iss->get_mirrored().err_bits);
set_sv_u32(stop_pc, iss->get_mirrored().stop_pc);
return 1;
case 0:
// The simulation is still running. Update status, rnd_req and insn_cnt.
if (iss->get_mirrored().status >> 8) {
throw std::runtime_error("STATUS register had non-empty top bits.");
}
set_sv_u8(status, iss->get_mirrored().status);
svPutBitselBit(rnd_req, 0, (iss->get_mirrored().rnd_req & 1));
set_sv_u32(insn_cnt, iss->get_mirrored().insn_cnt);
return 0;
default:
// This shouldn't happen
assert(0);
}
} catch (const std::runtime_error &err) {
std::cerr << "Error when stepping ISS: " << err.what() << "\n";
return -1;
}
}
int OtbnModel::check() const {
if (!has_rtl())
return 1;
ISSWrapper *iss = iss_.get();
if (!iss) {
std::cerr << "Cannot check OTBN model: ISS has not started.\n";
return -1;
}
bool good = true;
good &= OtbnTraceChecker::get().Finish();
// Check DMEM only when we are about to start Secure Wipe because otherwise
// we would not have a valid scrambling key anymore. That would result with
// not getting a valid nonce and therefore an error.
if (iss->get_mirrored().wipe_start) {
try {
good &= check_dmem(*iss);
} catch (const std::exception &err) {
std::cerr << "Failed to check DMEM: " << err.what() << "\n";
return -1;
}
}
try {
good &= check_regs(*iss);
} catch (const std::exception &err) {
std::cerr << "Failed to check registers: " << err.what() << "\n";
return -1;
}
try {
good &= check_call_stack(*iss);
} catch (const std::exception &err) {
std::cerr << "Failed to check call stack: " << err.what() << "\n";
return -1;
}
return good ? 1 : 0;
}
int OtbnModel::load_dmem() {
ISSWrapper *iss = iss_.get();
if (!iss) {
std::cerr << "Cannot load dmem from OTBN model: ISS has not started.\n";
return -1;
}
const MemArea &dmem = mem_util_.GetMemArea(false);
std::string dfname(iss->make_tmp_path("dmem_out"));
try {
// Read DMEM from the ISS
iss->dump_d(dfname);
set_sim_memory(false,
read_words_from_file(dfname, dmem.GetSizeBytes() / 4));
} catch (const std::exception &err) {
std::cerr << "Error when loading dmem from ISS: " << err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::invalidate_imem() {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->invalidate_imem();
} catch (const std::exception &err) {
std::cerr << "Error when invalidating IMEM in ISS: " << err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::invalidate_dmem() {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->invalidate_dmem();
} catch (const std::exception &err) {
std::cerr << "Error when invalidating DMEM in ISS: " << err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::set_software_errs_fatal(unsigned char new_val) {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->set_software_errs_fatal(new_val);
} catch (const std::exception &err) {
std::cerr << "Error when setting software_errs_fatal bit in ISS: "
<< err.what() << "\n";
return -1;
}
return 0;
}
int OtbnModel::set_no_sec_wipe_chk() {
OtbnTraceChecker::get().set_no_sec_wipe_chk();
return 0;
}
int OtbnModel::step_crc(const svBitVecVal *item /* bit [47:0] */,
svBitVecVal *state /* bit [31:0] */) {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
std::array<uint8_t, 6> item_arr;
for (size_t i = 0; i < item_arr.size(); ++i) {
item_arr[i] = item[i / 4] >> 8 * (i % 4);
}
uint32_t state32 = state[0];
try {
state32 = iss->step_crc(item_arr, state32);
} catch (const std::exception &err) {
std::cerr << "Error when stepping CRC in ISS: " << err.what() << "\n";
return -1;
}
// Write back to SV-land
state[0] = state32;
return 0;
}
int OtbnModel::reset(svBitVecVal *status /* bit [7:0] */,
svBitVecVal *insn_cnt /* bit [31:0] */,
svBitVecVal *rnd_req /* bit [0:0] */,
svBitVecVal *err_bits /* bit [31:0] */,
svBitVecVal *stop_pc /* bit [31:0] */) {
ISSWrapper *iss = iss_.get();
if (!iss)
return 0;
try {
iss->reset(has_rtl());
} catch (const std::runtime_error &err) {
std::cerr << "Error when resetting ISS: " << err.what() << "\n";
return -1;
}
set_sv_u8(status, iss->get_mirrored().status);
set_sv_u32(insn_cnt, iss->get_mirrored().insn_cnt);
svPutBitselBit(rnd_req, 0, (iss->get_mirrored().rnd_req & 1));
set_sv_u32(err_bits, iss->get_mirrored().err_bits);
set_sv_u32(stop_pc, iss->get_mirrored().stop_pc);
return 0;
}
int OtbnModel::send_err_escalation(svBitVecVal *err_val /* bit [31:0] */) {
ISSWrapper *iss = ensure_wrapper();
if (!iss)
return -1;
try {
iss->send_err_escalation(err_val[0]);
} catch (const std::exception &err) {
std::cerr << "Error when sending error escalation signal to ISS: "
<< err.what() << "\n";
return -1;
}
return 0;
}
bool OtbnModel::is_at_start_of_wipe() const {
ISSWrapper *iss = iss_.get();
return iss && iss->get_mirrored().wipe_start;
}
ISSWrapper *OtbnModel::ensure_wrapper() {
if (!iss_) {
try {
iss_.reset(new ISSWrapper());
} catch (const std::runtime_error &err) {
std::cerr << "Error when constructing ISS wrapper: " << err.what()
<< "\n";
return nullptr;
}
}
assert(iss_);
return iss_.get();
}
Ecc32MemArea::EccWords OtbnModel::get_sim_memory(bool is_imem) const {
auto &mem_area = mem_util_.GetMemArea(is_imem);
return mem_area.ReadWithIntegrity(0, mem_area.GetSizeWords());
}
void OtbnModel::set_sim_memory(bool is_imem,
const Ecc32MemArea::EccWords &words) {
mem_util_.GetMemArea(is_imem).WriteWithIntegrity(0, words);
}
bool OtbnModel::check_dmem(ISSWrapper &iss) const {
const MemArea &dmem = mem_util_.GetMemArea(false);
uint32_t dmem_bytes = dmem.GetSizeBytes();
std::string dfname(iss.make_tmp_path("dmem_out"));
iss.dump_d(dfname);
Ecc32MemArea::EccWords iss_words =
read_words_from_file(dfname, dmem_bytes / 4);
assert(iss_words.size() == dmem_bytes / 4);
Ecc32MemArea::EccWords rtl_words = get_sim_memory(false);
assert(rtl_words.size() == dmem_bytes / 4);
std::ios old_state(nullptr);
old_state.copyfmt(std::cerr);
int bad_count = 0;
for (size_t i = 0; i < dmem_bytes / 4; ++i) {
bool iss_valid = iss_words[i].first;
bool rtl_valid = rtl_words[i].first;
uint32_t iss_w32 = iss_words[i].second;
uint32_t rtl_w32 = rtl_words[i].second;
// If neither word has valid checksum bits, all is well.
if (!iss_valid && !rtl_valid)
continue;
// If both words have valid checksum bits and equal data, all is well.
if (iss_valid && rtl_valid && iss_w32 == rtl_w32)
continue;
// TODO: At the moment, the ISS doesn't track validity bits properly in
// DMEM, which means that we might have a situation where RTL says a
// word is invalid, but the ISS doesn't. To avoid spurious failures
// until we've implemented things, skip the check in this case. Once
// the ISS handles validity bits properly, delete this block.
if (iss_valid && !rtl_valid)
continue;
// Otherwise, something has gone wrong. Print out a banner if this is the
// first mismatch.
if (bad_count == 0) {
std::cerr << "ERROR: Mismatches in dmem data:\n"
<< std::hex << std::setfill('0');
}
std::cerr << " @offset 0x" << std::setw(3) << 4 * i << ": ";
if (iss_valid != rtl_valid) {
std::cerr << "mismatching validity bits (rtl = " << rtl_valid
<< "; iss = " << iss_valid << ")\n";
} else {
assert(iss_valid && rtl_valid && iss_w32 != rtl_w32);
std::cerr << "rtl has 0x" << std::setw(8) << rtl_w32 << "; iss has 0x"
<< std::setw(8) << iss_w32 << "\n";
}
++bad_count;
if (bad_count == 10) {
std::cerr << " (skipping further errors...)\n";
break;
}
}
std::cerr.copyfmt(old_state);
return bad_count == 0;
}
bool OtbnModel::check_regs(ISSWrapper &iss) const {
std::string base_scope =
design_scope_ +
".u_otbn_rf_base.gen_rf_base_ff.u_otbn_rf_base_inner.u_snooper";
std::string wide_scope =
design_scope_ +
".u_otbn_rf_bignum.gen_rf_bignum_ff.u_otbn_rf_bignum_inner.u_snooper";
auto rtl_gprs = get_rtl_regs<uint32_t>(base_scope);
auto rtl_wdrs = get_rtl_regs<ISSWrapper::u256_t>(wide_scope);
std::array<uint32_t, 32> iss_gprs;
std::array<ISSWrapper::u256_t, 32> iss_wdrs;
iss.get_regs(&iss_gprs, &iss_wdrs);
bool good = true;
for (int i = 0; i < 32; ++i) {
// Register index 1 is call stack, which is checked separately
if (i == 1)
continue;
if (rtl_gprs[i] != iss_gprs[i]) {
std::ios old_state(nullptr);
old_state.copyfmt(std::cerr);
std::cerr << std::setfill('0') << "RTL computed x" << i << " as 0x"
<< std::hex << rtl_gprs[i] << ", but ISS got 0x" << iss_gprs[i]
<< ".\n";
std::cerr.copyfmt(old_state);
good = false;
}
}
for (int i = 0; i < 32; ++i) {
if (0 != memcmp(rtl_wdrs[i].words, iss_wdrs[i].words,
sizeof(rtl_wdrs[i].words))) {
std::ios old_state(nullptr);
old_state.copyfmt(std::cerr);
std::cerr << "RTL computed w" << i << " as 0x" << std::hex
<< std::setfill('0');
for (int j = 0; j < 8; ++j) {
if (j)
std::cerr << "_";
std::cerr << std::setw(8) << rtl_wdrs[i].words[7 - j];
}
std::cerr << ", but ISS got 0x";
for (int j = 0; j < 8; ++j) {
if (j)
std::cerr << "_";
std::cerr << std::setw(8) << iss_wdrs[i].words[7 - j];
}
std::cerr << ".\n";
std::cerr.copyfmt(old_state);
good = false;
}
}
return good;
}
bool OtbnModel::check_call_stack(ISSWrapper &iss) const {
std::string call_stack_snooper_scope =
design_scope_ + ".u_otbn_rf_base.u_call_stack_snooper";
auto rtl_call_stack = get_stack<uint32_t>(call_stack_snooper_scope);
auto iss_call_stack = iss.get_call_stack();
bool good = true;
if (iss_call_stack.size() != rtl_call_stack.size()) {
std::cerr << "Call stack size mismatch, RTL call stack has "
<< rtl_call_stack.size() << " elements and ISS call stack has "
<< iss_call_stack.size() << " elements\n";
good = false;
}
// Iterate through both call stacks where both have elements
std::size_t call_stack_size =
std::min(rtl_call_stack.size(), iss_call_stack.size());
for (std::size_t i = 0; i < call_stack_size; ++i) {
if (iss_call_stack[i] != rtl_call_stack[i]) {
std::ios old_state(nullptr);
old_state.copyfmt(std::cerr);
std::cerr << std::setfill('0') << "RTL call stack element " << i
<< " is 0x" << std::hex << rtl_call_stack[i]
<< ", but ISS has 0x" << iss_call_stack[i] << ".\n";
std::cerr.copyfmt(old_state);
good = false;
}
}
return good;
}
OtbnModel *otbn_model_init(const char *mem_scope, const char *design_scope) {
assert(mem_scope && design_scope);
return new OtbnModel(mem_scope, design_scope);
}
void otbn_model_destroy(OtbnModel *model) { delete model; }
void otbn_take_loop_warps(OtbnModel *model, OtbnMemUtil *memutil) {
assert(model && memutil);
model->take_loop_warps(*memutil);
}
int otbn_has_loop_warps(OtbnMemUtil *memutil) {
assert(memutil);
return memutil->GetLoopWarps().size() != 0;
}
int otbn_model_edn_flush(OtbnModel *model) {
assert(model);
return model->edn_flush();
}
int otbn_model_edn_rnd_step(OtbnModel *model,
svLogicVecVal *edn_rnd_data /* logic [31:0] */,
unsigned char fips_err) {
assert(model && edn_rnd_data);
return model->edn_rnd_step(edn_rnd_data, fips_err);
}
int otbn_model_edn_urnd_step(OtbnModel *model,
svLogicVecVal *edn_urnd_data /* logic [31:0] */) {
assert(model && edn_urnd_data);
return model->edn_urnd_step(edn_urnd_data);
}
int otbn_model_rnd_cdc_done(OtbnModel *model) {
assert(model);
return model->edn_rnd_cdc_done();
}
int otbn_model_urnd_cdc_done(OtbnModel *model) {
assert(model);
return model->edn_urnd_cdc_done();
}
int otbn_model_otp_key_cdc_done(OtbnModel *model) {
assert(model);
return model->otp_key_cdc_done();
}
int otbn_model_set_keymgr_value(OtbnModel *model, svLogicVecVal *key0,
svLogicVecVal *key1, unsigned char valid) {
assert(model && key0 && key1);
return model->set_keymgr_value(key0, key1, valid);
}
unsigned otbn_model_step(OtbnModel *model, unsigned model_state,
svBitVecVal *cmd /* bit [7:0] */,
svBitVecVal *status /* bit [7:0] */,
svBitVecVal *insn_cnt /* bit [31:0] */,
svBitVecVal *rnd_req /* bit [0:0] */,
svBitVecVal *err_bits /* bit [31:0] */,
svBitVecVal *stop_pc /* bit [31:0] */) {
assert(model && status && insn_cnt && err_bits && stop_pc);
// Clear any check due bit (we hopefully ran the check on the previous
// negedge)
model_state = model_state & ~CHECK_DUE_BIT;
int result = 0;
unsigned new_state_bits = 0;
if (*status == STATUS_IDLE) {
// OTBN only executes commands if STATUS is IDLE.
switch (*cmd) {
case CMD_EXECUTE:
result = model->start_operation(OtbnModel::Execute);
new_state_bits = RUNNING_BIT;
break;
case CMD_SECWIPE_DMEM:
result = model->start_operation(OtbnModel::DmemWipe);
new_state_bits = RUNNING_BIT;
break;
case CMD_SECWIPE_IMEM:
result = model->start_operation(OtbnModel::ImemWipe);
new_state_bits = RUNNING_BIT;
break;
}
}
switch (result) {
case 0:
// Starting an operation succeeded. Set any required bits in the
// state.
model_state |= new_state_bits;
break;
default:
// Something went wrong in the model when starting the operation.
// Stop running and set FAILED_STEP_BIT to signal the error back
// to the SV side.
return (model_state & ~RUNNING_BIT) | FAILED_STEP_BIT;
}
// Step the model once
switch (model->step(status, insn_cnt, rnd_req, err_bits, stop_pc)) {
case 0:
// Still running: no change
break;
case 1:
// Finished
model_state = (model_state & ~RUNNING_BIT) | CHECK_DUE_BIT;
break;
default:
// Something went wrong
return (model_state & ~RUNNING_BIT) | FAILED_STEP_BIT;
}
// If we're at the start of a wipe, set the CHECK_DUE_BIT so that we run a
// check before we wipe everything
if (model->is_at_start_of_wipe())
model_state |= CHECK_DUE_BIT;
return model_state;
}
int otbn_model_check(OtbnModel *model, svBitVecVal *mismatch /* bit [0:0] */) {
assert(model && mismatch);
// Run model checks if needed. This usually happens just after an operation
// has finished.
if (model->has_rtl()) {
switch (model->check()) {
case 1:
// Match (success)
break;
case 0:
// Mismatch
*mismatch |= 1;
break;
default:
// Something went wrong
return 0;
}
}
// If there's no RTL, load the contents of DMEM back from the ISS
if (!model->has_rtl()) {
if (model->load_dmem() != 0) {
return 0;
}
}
return 1;
}
int otbn_model_invalidate_imem(OtbnModel *model) {
assert(model);
return model->invalidate_imem();
}
int otbn_model_invalidate_dmem(OtbnModel *model) {
assert(model);
return model->invalidate_dmem();
}
int otbn_model_set_software_errs_fatal(OtbnModel *model,
unsigned char new_val) {
assert(model);
return model->set_software_errs_fatal(new_val);
}
int otbn_set_no_sec_wipe_chk(OtbnModel *model) {
assert(model);
return model->set_no_sec_wipe_chk();
}
int otbn_model_step_crc(OtbnModel *model, svBitVecVal *item /* bit [47:0] */,
svBitVecVal *state /* inout bit [31:0] */) {
assert(model && item && state);
return model->step_crc(item, state);
}
int otbn_model_reset(OtbnModel *model, svBitVecVal *status /* bit [7:0] */,
svBitVecVal *insn_cnt /* bit [31:0] */,
svBitVecVal *rnd_req /* bit [0:0] */,
svBitVecVal *err_bits /* bit [31:0] */,
svBitVecVal *stop_pc /* bit [31:0] */) {
assert(model);
return model->reset(status, insn_cnt, rnd_req, err_bits, stop_pc);
}
int otbn_model_send_err_escalation(OtbnModel *model,
svBitVecVal *err_val /* bit [31:0] */) {
assert(model);
return model->send_err_escalation(err_val);
}