blob: 36037549e97383956d31c880ff41fe1f59891efc [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 <fstream>
#include <getopt.h>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <svdpi.h>
#include "Votbn_top_sim__Syms.h"
#include "log_trace_listener.h"
#include "otbn_memutil.h"
#include "otbn_model.h"
#include "otbn_trace_checker.h"
#include "otbn_trace_source.h"
#include "sv_scoped.h"
#include "verilated_toplevel.h"
#include "verilator_memutil.h"
#include "verilator_sim_ctrl.h"
extern "C" {
extern unsigned int otbn_base_call_stack_get_size();
extern unsigned int otbn_base_call_stack_get_element(int index);
extern unsigned int otbn_base_reg_get(int index);
extern unsigned int otbn_bignum_reg_get(int index, int quarter);
extern svBit otbn_err_get();
extern int otbn_core_get_stop_pc();
}
/**
* SimCtrlExtension that adds a '--otbn-trace-file' command line option. If set
* it sets up a LogTraceListener that will dump out the trace to the given log
* file
*/
class OtbnTraceUtil : public SimCtrlExtension {
private:
std::unique_ptr<LogTraceListener> log_trace_listener_;
bool SetupTraceLog(const std::string &log_filename) {
try {
log_trace_listener_ = std::make_unique<LogTraceListener>(log_filename);
OtbnTraceSource::get().AddListener(log_trace_listener_.get());
return true;
} catch (const std::runtime_error &err) {
std::cerr << "ERROR: Failed to set up trace log: " << err.what()
<< std::endl;
return false;
}
return false;
}
void PrintHelp() {
std::cout << "Trace log utilities:\n\n"
"--otbn-trace-file=FILE\n"
" Write OTBN trace log to FILE\n\n";
}
public:
virtual bool ParseCLIArguments(int argc, char **argv, bool &exit_app) {
const struct option long_options[] = {
{"otbn-trace-file", required_argument, nullptr, 'l'},
{"help", no_argument, nullptr, 'h'},
{nullptr, no_argument, nullptr, 0}};
// Reset the command parsing index in-case other utils have already parsed
// some arguments
optind = 1;
while (1) {
int c = getopt_long(argc, argv, "-h", long_options, nullptr);
if (c == -1) {
break;
}
switch (c) {
case 0:
case 1:
break;
case 'l':
return SetupTraceLog(optarg);
case 'h':
PrintHelp();
break;
}
}
return true;
}
~OtbnTraceUtil() {
if (log_trace_listener_)
OtbnTraceSource::get().RemoveListener(log_trace_listener_.get());
}
};
static otbn_top_sim *verilator_top;
static OtbnMemUtil otbn_memutil("TOP.otbn_top_sim");
int main(int argc, char **argv) {
VerilatorMemUtil memutil(&otbn_memutil);
OtbnTraceUtil traceutil;
otbn_top_sim top;
// Make the otbn_top_sim object visible to OtbnTopApplyLoopWarp.
// This will leave a dangling pointer when we exit main, but that
// doesn't really matter because we don't have anything that uses it
// running in atexit hooks.
verilator_top = &top;
VerilatorSimCtrl &simctrl = VerilatorSimCtrl::GetInstance();
simctrl.SetTop(&top, &top.IO_CLK, &top.IO_RST_N,
VerilatorSimCtrlFlags::ResetPolarityNegative);
simctrl.RegisterExtension(&memutil);
simctrl.RegisterExtension(&traceutil);
std::cout << "Simulation of OTBN" << std::endl
<< "==================" << std::endl
<< std::endl;
auto pr = simctrl.Exec(argc, argv);
int ret_code = pr.first;
bool ran_simulation = pr.second;
if (ret_code != 0 || !ran_simulation) {
return ret_code;
}
svSetScope(svGetScopeFromName("TOP.otbn_top_sim"));
svBit model_err = otbn_err_get();
if (model_err) {
return 1;
}
int exp_stop_pc = otbn_memutil.GetExpEndAddr();
if (exp_stop_pc >= 0) {
SVScoped core_scope("TOP.otbn_top_sim.u_otbn_core_model");
int act_stop_pc = otbn_core_get_stop_pc();
if (exp_stop_pc != act_stop_pc) {
std::cerr << "ERROR: Expected stop PC from ELF file was 0x" << std::hex
<< exp_stop_pc << ", but simulation actually stopped at 0x"
<< act_stop_pc << ".\n";
return 1;
}
}
return 0;
}
// This is executed over DPI on the first posedge of the clock after each
// reset. It's in charge of telling the model about any loop warp symbols in
// the ELF file.
extern "C" int OtbnTopInstallLoopWarps() {
// Cast to the right base class of otbn_top_sim. Otherwise, you can't access
// the "otbn_top_sim" member because you get the derived class's constructor
// by accident.
Votbn_top_sim &top = *verilator_top;
// Grab the model handle from the otbn_core_model module. This should have
// been initialised by now because it gets set up in an initial block and
// this code doesn't run until the first clock edge.
auto sv_model_handle = top.otbn_top_sim->u_otbn_core_model->model_handle;
// sv_model_handle will be some integer type. Check it's nonzero and, if so,
// convert it to an OtbnModel*.
assert(sv_model_handle != 0);
OtbnModel *model_handle = (OtbnModel *)sv_model_handle;
if (model_handle->take_loop_warps(otbn_memutil) != 0) {
// Something went wrong when trying to update the model. We've already
// written to something to stderr, so should just pass the non-zero return
// value up the stack.
return -1;
}
return 0;
}
template <typename PrimCountT>
void set_up_down_prim_count(PrimCountT *prim_count, uint32_t new_cnt) {
// There are two counters within this primitive: The primary counter gives
// the remaining iterations and the secondary counter counts in reverse
// direction. They must always sum to the constant value 2**Width-1, which is
// 2**32-1 = 0xFFFFFFFF in this case.
// Loop warping occurs on the negative clock edge. When writing signal values
// here that won't trigger any processes within the verilator simulation. At
// the next positive clock edge @(posedge clk) processes will run before any
// _d update processes so write the relevant _d values here.
auto up_cnt_flop = prim_count->gen_cnts__BRA__0__KET____DOT__u_cnt_flop;
auto down_cnt_flop = prim_count->gen_cnts__BRA__1__KET____DOT__u_cnt_flop;
up_cnt_flop->gen_generic__DOT__u_impl_generic->d_i = new_cnt;
down_cnt_flop->gen_generic__DOT__u_impl_generic->d_i = 0xFFFFFFFF - new_cnt;
}
template <typename LoopControllerT>
auto get_loop_counter(LoopControllerT *loop_controller, uint32_t counter_idx)
-> decltype(loop_controller
->g_loop_counters__BRA__0__KET____DOT__u_loop_count) {
assert(counter_idx < 8);
switch (counter_idx) {
case 0:
return loop_controller->g_loop_counters__BRA__0__KET____DOT__u_loop_count;
case 1:
return loop_controller->g_loop_counters__BRA__1__KET____DOT__u_loop_count;
case 2:
return loop_controller->g_loop_counters__BRA__2__KET____DOT__u_loop_count;
case 3:
return loop_controller->g_loop_counters__BRA__3__KET____DOT__u_loop_count;
case 4:
return loop_controller->g_loop_counters__BRA__4__KET____DOT__u_loop_count;
case 5:
return loop_controller->g_loop_counters__BRA__5__KET____DOT__u_loop_count;
case 6:
return loop_controller->g_loop_counters__BRA__6__KET____DOT__u_loop_count;
case 7:
return loop_controller->g_loop_counters__BRA__7__KET____DOT__u_loop_count;
default:
return nullptr;
}
}
// This is executed over DPI on every negedge of the clock and is in charge of
// updating the top of the loop stack if necessary to match loop warp symbols
// in the ELF file.
extern "C" void OtbnTopApplyLoopWarp() {
static std::vector<uint32_t> loop_count_stack;
// See not in OtbnTopInstallLoopWarps for why this upcast is needed.
Votbn_top_sim &top = *verilator_top;
auto loop_controller =
top.otbn_top_sim->u_otbn_core->u_otbn_controller->u_otbn_loop_controller;
// Track loop stack state.
if (loop_controller->current_loop_finish) {
assert(!loop_count_stack.empty());
loop_count_stack.pop_back();
}
if (loop_controller->loop_start_req_i &&
loop_controller->loop_start_commit_i) {
loop_count_stack.push_back(loop_controller->loop_iterations_i);
}
if (!loop_count_stack.empty()) {
// There is a loop that's currently active. Its iteration count for the
// next cycle is provided to the prefetcher via `prefetch_loop_iterations_o`
// which we capture here.
uint32_t total = loop_count_stack.back();
uint32_t old_iters = loop_controller->prefetch_loop_iterations_o;
// The RTL's view of "iterations remaining" counts down rather than up and
// for the final iteration the count will be 1 (not zero). Convert to the
// indexing we use in loop warp symbols (counting up, starting at zero) by
// subtracting old_iters_d from total. The result should never be negative
// (unless we messed up something somewhere).
assert(old_iters <= total);
uint32_t old_cnt = total - old_iters;
uint32_t insn_addr = loop_controller->insn_addr_i;
uint32_t new_cnt = otbn_memutil.GetLoopWarp(insn_addr, old_cnt);
if (old_cnt != new_cnt) {
// Convert from new_cnt back to the "iters" format by subtracting from
// the total, but bottom out at 1 (the last iteration).
uint32_t new_iters = (new_cnt < total) ? (total - new_cnt) : 1;
set_up_down_prim_count(
get_loop_counter(loop_controller,
(uint32_t)loop_controller->loop_stack_rd_idx),
new_iters);
}
}
}
// This is executed over DPI when the model says that execution has just
// finished. We use it to dump out the current RTL state before secure wipe
// zeroes everything out.
extern "C" void OtbnTopDumpState() {
std::cout << "Call Stack:" << std::endl;
std::cout << "-----------" << std::endl;
for (int i = 0; i < otbn_base_call_stack_get_size(); ++i) {
std::cout << std::setfill(' ') << "0x" << std::hex << std::setw(8)
<< std::setfill('0') << std::right
<< otbn_base_call_stack_get_element(i) << std::endl;
}
std::cout << std::endl;
std::cout << "Final Base Register Values:" << std::endl;
std::cout << "Reg | Value" << std::endl;
std::cout << "----------------" << std::endl;
for (int i = 2; i < 32; ++i) {
std::cout << "x" << std::left << std::dec << std::setw(2)
<< std::setfill(' ') << i << " | 0x" << std::hex << std::setw(8)
<< std::setfill('0') << std::right << otbn_base_reg_get(i)
<< std::endl;
}
std::cout << std::endl;
std::cout << "Final Bignum Register Values:" << std::endl;
std::cout << "Reg | Value" << std::endl;
std::cout << "---------------------------------------------------------------"
"----------------"
<< std::endl;
for (int i = 0; i < 32; ++i) {
std::cout << "w" << std::left << std::dec << std::setw(2)
<< std::setfill(' ') << i << " | 0x" << std::hex;
std::cout << std::setw(8) << std::setfill('0') << std::right
<< otbn_bignum_reg_get(i, 7) << "_";
std::cout << std::setw(8) << std::setfill('0') << otbn_bignum_reg_get(i, 6)
<< "_";
std::cout << std::setw(8) << std::setfill('0') << otbn_bignum_reg_get(i, 5)
<< "_";
std::cout << std::setw(8) << std::setfill('0') << otbn_bignum_reg_get(i, 4)
<< "_";
std::cout << std::setw(8) << std::setfill('0') << otbn_bignum_reg_get(i, 3)
<< "_";
std::cout << std::setw(8) << std::setfill('0') << otbn_bignum_reg_get(i, 2)
<< "_";
std::cout << std::setw(8) << std::setfill('0') << otbn_bignum_reg_get(i, 1)
<< "_";
std::cout << std::setw(8) << std::setfill('0') << otbn_bignum_reg_get(i, 0)
<< std::endl;
}
}