[otbn] Remove enable_i input from otbn_core_model
The previous code had a race in the initial blocks when used in
otbn.sv. The obvious way to fix this in the SystemVerilog code would
be to call otbn_model_init() just before we first call
otbn_model_step. Unfortunately, you can't do that (for Verilator, at
least) because that ends up mixing blocking and non-blocking
assignments.
Rather than think hard about how to do this properly in Verilog, we
can just put in a simple layer of indirection in the C++ and only
start the subprocess when we see start_i for the first time.
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/dv/model/otbn_core_model.sv b/hw/ip/otbn/dv/model/otbn_core_model.sv
index 126bfcb..527dcba 100644
--- a/hw/ip/otbn/dv/model/otbn_core_model.sv
+++ b/hw/ip/otbn/dv/model/otbn_core_model.sv
@@ -34,10 +34,6 @@
input logic clk_i,
input logic rst_ni,
- // Enable the model. This signal is a "runtime parameter", it is only honored if set in an initial
- // block.
- input logic enable_i,
-
input logic start_i, // start the operation
output bit done_o, // operation done
@@ -79,21 +75,13 @@
`endif
// Create and destroy an object through which we can talk to the ISS.
- // If enable_i is not set, do not initialize the ISS (and, by consequence, do not start the Python
- // process).
- chandle model_handle = chandle_null;
+ chandle model_handle;
initial begin
- if (enable_i) begin
- model_handle = otbn_model_init();
- assert(model_handle != chandle_null);
- $display("OTBN: Model enabled.");
- end
+ model_handle = otbn_model_init();
+ assert(model_handle != chandle_null);
end
final begin
- if (model_handle != chandle_null) begin
- otbn_model_destroy(model_handle);
- model_handle = chandle_null;
- end
+ otbn_model_destroy(model_handle);
end
// A packed "status" value. This gets assigned by DPI function calls that need to update both
@@ -115,7 +103,7 @@
bit [31:0] raw_err_bits_d, raw_err_bits_q;
bit unused_raw_err_bits;
always_ff @(posedge clk_i or negedge rst_ni) begin
- if (!rst_ni || !enable_i) begin
+ if (!rst_ni) begin
// Clear status (stop running, and forget any errors)
status <= 0;
raw_err_bits_q <= 0;
diff --git a/hw/ip/otbn/dv/model/otbn_model.cc b/hw/ip/otbn/dv/model/otbn_model.cc
index ae361ad..0cc4b5f 100644
--- a/hw/ip/otbn/dv/model/otbn_model.cc
+++ b/hw/ip/otbn/dv/model/otbn_model.cc
@@ -16,6 +16,32 @@
#include "otbn_trace_checker.h"
#include "sv_scoped.h"
+namespace {
+// An extremely thin wrapper around ISSWrapper. The point is that we want to
+// create the model in an initial block in the SystemVerilog simulation, but
+// might not actually want to spawn the ISS. To handle that in a non-racy
+// way, the most convenient thing is to spawn the ISS on the first call to
+// otbn_model_step.
+struct OtbnModel {
+ public:
+ bool ensure() {
+ if (!iss) {
+ try {
+ iss.reset(new ISSWrapper());
+ } catch (const std::runtime_error &err) {
+ std::cerr << "Error when constructing ISS wrapper: " << err.what()
+ << "\n";
+ return false;
+ }
+ }
+ assert(iss);
+ return true;
+ }
+
+ std::unique_ptr<ISSWrapper> iss;
+};
+} // namespace
+
extern "C" {
// DPI imports, implemented in SystemVerilog
int simutil_get_mem(int index, svBitVecVal *val);
@@ -64,7 +90,7 @@
//
// If start_i is true, we start the model at start_addr and then step once (as
// described above).
-extern "C" unsigned otbn_model_step(ISSWrapper *model, const char *imem_scope,
+extern "C" unsigned otbn_model_step(OtbnModel *model, const char *imem_scope,
unsigned imem_words, const char *dmem_scope,
unsigned dmem_words,
const char *design_scope, svLogic start_i,
@@ -187,20 +213,13 @@
word_size);
}
-extern "C" ISSWrapper *otbn_model_init() {
- try {
- return new ISSWrapper();
- } catch (const std::runtime_error &err) {
- std::cerr << "Error when constructing ISS wrapper: " << err.what() << "\n";
- return nullptr;
- }
-}
+extern "C" OtbnModel *otbn_model_init() { return new OtbnModel; }
-extern "C" void otbn_model_destroy(ISSWrapper *model) { delete model; }
+extern "C" void otbn_model_destroy(OtbnModel *model) { delete model; }
// Start a new run with the model, writing IMEM/DMEM and jumping to the given
// start address. Returns 0 on success; -1 on failure.
-static int start_model(ISSWrapper *model, const char *imem_scope,
+static int start_model(OtbnModel *model, const char *imem_scope,
unsigned imem_words, const char *dmem_scope,
unsigned dmem_words, unsigned start_addr) {
assert(model);
@@ -208,8 +227,13 @@
assert(dmem_words >= 0);
assert(start_addr < (imem_words * 4));
- std::string ifname(model->make_tmp_path("imem"));
- std::string dfname(model->make_tmp_path("dmem"));
+ if (!model->ensure())
+ return -1;
+ assert(model->iss);
+ ISSWrapper &iss = *model->iss;
+
+ std::string ifname(iss.make_tmp_path("imem"));
+ std::string dfname(iss.make_tmp_path("dmem"));
try {
dump_memory_to_file(dfname, dmem_scope, dmem_words, 32);
@@ -220,9 +244,9 @@
}
try {
- model->load_d(dfname);
- model->load_i(ifname);
- model->start(start_addr);
+ iss.load_d(dfname);
+ iss.load_i(ifname);
+ iss.start(start_addr);
} catch (const std::runtime_error &err) {
std::cerr << "Error when starting ISS: " << err.what() << "\n";
return -1;
@@ -234,12 +258,17 @@
// Step once in the model. Returns 1 if the model has finished, 0 if not and -1
// on failure. If gen_trace is true, pass trace entries to the trace checker.
// If the model has finished, writes otbn.ERR_CODE to *err_code.
-static int step_model(ISSWrapper *model, bool gen_trace, uint32_t *err_code) {
+static int step_model(OtbnModel *model, bool gen_trace, uint32_t *err_code) {
assert(model);
assert(err_code);
+ if (!model->ensure())
+ return -1;
+ assert(model->iss);
+ ISSWrapper &iss = *model->iss;
+
try {
- std::pair<bool, uint32_t> ret = model->step(gen_trace);
+ std::pair<bool, uint32_t> ret = iss.step(gen_trace);
if (ret.first) {
*err_code = ret.second;
return 1;
@@ -254,12 +283,18 @@
// Grab contents of dmem from the model and load it back into the RTL. Returns
// 0 on success; -1 on failure.
-static int load_dmem(ISSWrapper *model, const char *dmem_scope,
+static int load_dmem(OtbnModel *model, const char *dmem_scope,
unsigned dmem_words) {
assert(model);
- std::string dfname(model->make_tmp_path("dmem_out"));
+ if (!model->iss) {
+ std::cerr << "Cannot load dmem from OTBN model: ISS has not started.\n";
+ return -1;
+ }
+ ISSWrapper &iss = *model->iss;
+
+ std::string dfname(iss.make_tmp_path("dmem_out"));
try {
- model->dump_d(dfname);
+ iss.dump_d(dfname);
load_memory_from_file(dfname, dmem_scope, dmem_words, 32);
} catch (const std::exception &err) {
std::cerr << "Error when loading dmem from ISS: " << err.what() << "\n";
@@ -271,14 +306,12 @@
// Grab contents of dmem from the model and compare it with the RTL.
// Prints messages to stderr on failure or mismatch. Returns true on
// success; false on mismatch. Throws a std::runtime_error on failure.
-static bool check_dmem(ISSWrapper *model, const char *dmem_scope,
+static bool check_dmem(ISSWrapper &iss, const char *dmem_scope,
unsigned dmem_words) {
- assert(model);
-
size_t dmem_bytes = dmem_words * 32;
- std::string dfname(model->make_tmp_path("dmem_out"));
+ std::string dfname(iss.make_tmp_path("dmem_out"));
- model->dump_d(dfname);
+ iss.dump_d(dfname);
std::vector<uint8_t> iss_data = read_vector_from_file(dfname, dmem_bytes);
assert(iss_data.size() == dmem_bytes);
@@ -388,9 +421,7 @@
// Compare contents of ISS registers with those from the design. Prints
// messages to stderr on failure or mismatch. Returns true on success; false on
// mismatch. Throws a std::runtime_error on failure.
-static bool check_regs(ISSWrapper *model, const std::string &design_scope) {
- assert(model);
-
+static bool check_regs(ISSWrapper &iss, const std::string &design_scope) {
std::string base_scope =
design_scope +
".u_otbn_rf_base.gen_rf_base_ff.u_otbn_rf_base_inner.u_snooper";
@@ -402,7 +433,7 @@
std::array<uint32_t, 32> iss_gprs;
std::array<ISSWrapper::u256_t, 32> iss_wdrs;
- model->get_regs(&iss_gprs, &iss_wdrs);
+ iss.get_regs(&iss_gprs, &iss_wdrs);
bool good = true;
@@ -451,16 +482,13 @@
// Compare contents of ISS call stack with those from the design. Prints
// messages to stderr on failure or mismatch. Returns true on success; false on
// mismatch. Throws a std::runtime_error on failure.
-static bool check_call_stack(ISSWrapper *model,
- const std::string &design_scope) {
- assert(model);
-
+static bool check_call_stack(ISSWrapper &iss, const std::string &design_scope) {
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 = model->get_call_stack();
+ auto iss_call_stack = iss.get_call_stack();
bool good = true;
@@ -493,32 +521,38 @@
// Check model against RTL when a run has finished. Prints messages to stderr
// on failure or mismatch. Returns 1 for a match, 0 for a mismatch, -1 for some
// other failure.
-int check_model(ISSWrapper *model, const char *dmem_scope, unsigned dmem_words,
+int check_model(OtbnModel *model, const char *dmem_scope, unsigned dmem_words,
const char *design_scope) {
assert(model);
assert(dmem_words >= 0);
assert(design_scope);
+ if (!model->iss) {
+ std::cerr << "Cannot check OTBN model: ISS has not started.\n";
+ return -1;
+ }
+ ISSWrapper &iss = *model->iss;
+
bool good = true;
good &= OtbnTraceChecker::get().Finish();
try {
- good &= check_dmem(model, dmem_scope, dmem_words);
+ good &= check_dmem(iss, dmem_scope, dmem_words);
} catch (const std::exception &err) {
std::cerr << "Failed to check DMEM: " << err.what() << "\n";
return -1;
}
try {
- good &= check_regs(model, design_scope);
+ good &= check_regs(iss, design_scope);
} catch (const std::exception &err) {
std::cerr << "Failed to check registers: " << err.what() << "\n";
return -1;
}
try {
- good &= check_call_stack(model, design_scope);
+ good &= check_call_stack(iss, design_scope);
} catch (const std::exception &err) {
std::cerr << "Failed to check call stack: " << err.what() << "\n";
return -1;
@@ -537,7 +571,7 @@
}
}
-extern "C" unsigned otbn_model_step(ISSWrapper *model, const char *imem_scope,
+extern "C" unsigned otbn_model_step(OtbnModel *model, const char *imem_scope,
unsigned imem_words, const char *dmem_scope,
unsigned dmem_words,
const char *design_scope, svLogic start_i,
diff --git a/hw/ip/otbn/dv/uvm/tb.sv b/hw/ip/otbn/dv/uvm/tb.sv
index b9324fb..bfa711f 100644
--- a/hw/ip/otbn/dv/uvm/tb.sv
+++ b/hw/ip/otbn/dv/uvm/tb.sv
@@ -82,8 +82,6 @@
.clk_i (model_if.clk_i),
.rst_ni (model_if.rst_ni),
- .enable_i (1'b1),
-
.start_i (model_if.start),
.done_o (model_if.done),
.start_addr_i (model_if.start_addr),
diff --git a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
index ef1cbef..d6f639b 100644
--- a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
+++ b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
@@ -221,8 +221,6 @@
.clk_i ( IO_CLK ),
.rst_ni ( IO_RST_N ),
- .enable_i ( 1'b1 ),
-
.start_i ( otbn_start ),
.done_o ( otbn_model_done ),
diff --git a/hw/ip/otbn/rtl/otbn.sv b/hw/ip/otbn/rtl/otbn.sv
index 6b4b0a8..031d731 100644
--- a/hw/ip/otbn/rtl/otbn.sv
+++ b/hw/ip/otbn/rtl/otbn.sv
@@ -507,8 +507,6 @@
.clk_i,
.rst_ni,
- .enable_i (otbn_use_model),
-
.start_i (start_model),
.done_o (done_model),