blob: 5322ad2adc7b59b742b854a8579cdc78307a2f81 [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 <cassert>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <svdpi.h>
#include <sys/stat.h>
// Candidate for hw/dv/verilator/cpp
/**
* Guard class for SV Scope
*
* This guard ensures that all functions in the context where it is instantiated
* are executed in an SVScope. It will restore the previous scope at destruction
* and thereby make sure that the previous scope will be restored for all paths
* that exit the scope.
*/
class SVScoped {
private:
svScope prev_scope_ = 0;
public:
SVScoped(std::string scopeName) : SVScoped(scopeName.c_str()) {}
SVScoped(const char *scopeName) : SVScoped(svGetScopeFromName(scopeName)) {}
SVScoped(svScope scope) { prev_scope_ = svSetScope(scope); }
~SVScoped() { svSetScope(prev_scope_); }
};
// Guard class to safely delete C strings
struct CStrDeleter {
void operator()(char *p) const { std::free(p); }
};
typedef std::unique_ptr<char, CStrDeleter> c_str_ptr;
extern "C" int simutil_verilator_get_mem(int index, const svBitVecVal *val);
extern "C" int simutil_verilator_set_mem(int index, const svBitVecVal *val);
// Use simutil_verilator_get_mem to read data one word at a time from the given
// scope, writing it to a file at path. Each word is word_size bytes long.
//
// Raises a runtime_error on failure.
static void dump_memory(const char *path, const char *scope, size_t num_words,
size_t word_size) {
std::filebuf fb;
if (!fb.open(path, std::ios::out | std::ios::binary)) {
std::ostringstream oss;
oss << "Cannot open the file '" << path << "'.";
throw std::runtime_error(oss.str());
}
SVScoped scoped(scope);
// simutil_verilator_get_mem passes data as a packed array of svBitVecVal
// words. It only works for memories of size at most 256 bits, so we can just
// allocate 256/8 = 32 bytes as 32/sizeof(svBitVecVal) words on the stack.
assert(word_size <= 256 / 8);
svBitVecVal buf[256 / 8 / sizeof(svBitVecVal)];
for (size_t w = 0; w < num_words; w++) {
if (!simutil_verilator_get_mem(w, buf)) {
std::ostringstream oss;
oss << "Cannot get memory at word " << w << " from scope " << scope
<< ".\n";
throw std::runtime_error(oss.str());
}
// Write out the first word_size bytes of data
std::streamsize chars_out =
fb.sputn(reinterpret_cast<const char *>(buf), word_size);
if (chars_out != word_size) {
std::ostringstream oss;
oss << "Cannot write word " << w << " to '" << path << "'.\n";
throw std::runtime_error(oss.str());
}
}
}
// Read data from the given file and then use simutil_verilator_set_mem to
// write it into the simulation one word at a time. Each word is word_size
// bytes long.
//
// Raises a runtime_error on failure.
static void load_memory(const char *path, const char *scope, size_t num_words,
size_t word_size) {
std::filebuf fb;
if (!fb.open(path, std::ios::in | std::ios::binary)) {
std::ostringstream oss;
oss << "Cannot open the file '" << path << "'.";
throw std::runtime_error(oss.str());
}
SVScoped scoped(scope);
// See dump_memory, above, for why this array is sized like this.
assert(word_size <= 256 / 8);
svBitVecVal buf[256 / 8 / sizeof(svBitVecVal)];
for (size_t w = 0; w < num_words; w++) {
std::streamsize chars_in =
fb.sgetn(reinterpret_cast<char *>(buf), word_size);
if (chars_in != word_size) {
std::ostringstream oss;
oss << "Cannot read word " << w << " from '" << path << "'.\n";
throw std::runtime_error(oss.str());
}
if (!simutil_verilator_set_mem(w, buf)) {
std::ostringstream oss;
oss << "Cannot set memory at word " << w << " for scope " << scope
<< ".\n";
throw std::runtime_error(oss.str());
}
}
}
// Read a 4-byte little-endian unsigned value from the given file which is
// supposed to represent the number of cycles taken by the simulation and
// return it.
//
// Raises a runtime_error on failure.
static uint32_t load_cycles(const char *path) {
std::filebuf fb;
if (!fb.open(path, std::ios::in | std::ios::binary)) {
std::ostringstream oss;
oss << "Cannot open the file '" << path << "'.";
throw std::runtime_error(oss.str());
}
char buf[4];
if (fb.sgetn(buf, 4) != 4) {
std::ostringstream oss;
oss << "Failed to read 4 bytes from '" << path << "'.";
throw std::runtime_error(oss.str());
}
// Re-pack the little-endian value we just read.
uint32_t ret = 0;
for (int i = 0; i < 4; ++i) {
ret += (uint32_t)buf[i] << 8 * i;
}
return ret;
}
// Find the otbn Python model, based on our executable path, and
// return it. On failure, throw a std::runtime_error with a
// description of what went wrong.
//
// This works by searching upwards from the binary location to find a git
// directory (which is assumed to be the OpenTitan toplevel). It won't work if
// you copy the binary somewhere else: if we need to support that sort of
// thing, we'll have to figure out a "proper" installation procedure.
static std::string find_otbn_model() {
c_str_ptr self_path(realpath("/proc/self/exe", NULL));
if (!self_path) {
std::ostringstream oss;
oss << "Cannot resolve /proc/self/exe: " << strerror(errno);
throw std::runtime_error(oss.str());
}
// Take a copy of self_path as a std::string and modify it, walking backwards
// over '/' characters and appending .git each time. After the first
// iteration, last_pos is the position of the character before the final
// slash (where the path looks something like "/path/to/check/.git")
std::string path_buf(self_path.get());
struct stat git_dir_stat;
size_t last_pos = std::string::npos;
for (;;) {
size_t last_slash = path_buf.find_last_of('/', last_pos);
// self_path was absolute, so there should always be a '/' at position
// zero.
assert(last_slash != std::string::npos);
if (last_slash == 0) {
// We've got to the slash at the start of an absolute path (and "/.git"
// is probably not the path we want!). Give up.
std::ostringstream oss;
oss << "Cannot find a git top-level directory containing "
<< self_path.get() << ".\n";
throw std::runtime_error(oss.str());
}
// Replace everything after last_slash with ".git". The first time around,
// this will turn "/path/to/elf-file" to "/path/to/.git". After that, it
// will turn "/path/to/check/.git" to "/path/to/.git". Note that last_slash
// is strictly less than the string length (because it's an element index),
// so last_slash + 1 won't fall off the end.
path_buf.replace(last_slash + 1, std::string::npos, ".git");
last_pos = last_slash - 1;
// Does path_buf name a directory? If so, we've found the enclosing git
// directory.
if (stat(path_buf.c_str(), &git_dir_stat) == 0 &&
S_ISDIR(git_dir_stat.st_mode)) {
break;
}
}
// If we get here, path_buf points at a .git directory. Resolve from there to
// the expected model name, then use realpath to canonicalise the path. If it
// fails, there was no script there.
path_buf += "/../hw/ip/otbn/dv/otbnsim/otbnsim.py";
char *model_path = realpath(path_buf.c_str(), NULL);
if (!model_path) {
std::ostringstream oss;
oss << "Cannot find otbnsim.py, at '" << path_buf
<< "' (guessed by searching upwards from '" << self_path.get()
<< "').\n";
throw std::runtime_error(oss.str());
}
return std::string(model_path);
}
extern "C" int run_model(const char *imem_scope, int imem_words,
const char *dmem_scope, int dmem_words,
int start_addr) {
assert(imem_words >= 0);
assert(dmem_words >= 0);
assert(start_addr >= 0 && start_addr < (imem_words * 4));
char dir[] = "/tmp/otbn_XXXXXX";
char ifname[] = "/tmp/otbn_XXXXXX/imem";
char dfname[] = "/tmp/otbn_XXXXXX/dmem";
char cfname[] = "/tmp/otbn_XXXXXX/cycles";
char tfname[] = "/tmp/otbn_XXXXXX/trace";
if (mkdtemp(dir) == nullptr) {
std::cerr << "Cannot create temporary directory" << std::endl;
return 0;
}
std::memcpy(ifname, dir, strlen(dir));
std::memcpy(dfname, dir, strlen(dir));
std::memcpy(cfname, dir, strlen(dir));
std::memcpy(tfname, dir, strlen(dir));
try {
dump_memory(dfname, dmem_scope, dmem_words, 32);
dump_memory(ifname, imem_scope, imem_words, 4);
} catch (const std::runtime_error &err) {
std::cerr << "Error when dumping memory contents: " << err.what() << "\n";
return 0;
}
std::string model_path;
try {
model_path = find_otbn_model();
} catch (const std::runtime_error &err) {
std::cerr << "Error when searching for OTBN model: " << err.what() << "\n";
return 0;
}
std::ostringstream cmd;
cmd << model_path << " " << imem_words << " " << ifname << " " << dmem_words
<< " " << dfname << " " << cfname << " " << tfname << " " << start_addr;
if (std::system(cmd.str().c_str()) != 0) {
std::cerr << "Failed to execute model (cmd was: '" << cmd.str() << "').\n";
return 0;
}
try {
load_memory(dfname, dmem_scope, dmem_words, 32);
return load_cycles(cfname);
} catch (const std::runtime_error &err) {
std::cerr << "Error when loading sim results: " << err.what() << "\n";
return 0;
}
}