[otbn,dv] Expose OtbnModel in a header file and SV package
We were being a bit lazy before, and relying on keeping things in sync
between otbn_model.cc and otbn_core_model.sv. Since we're planning to
expose other DPI functionality from otbn_model, let's be a bit more
explicit.
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 95960dc..2c17db2 100644
--- a/hw/ip/otbn/dv/model/otbn_core_model.sv
+++ b/hw/ip/otbn/dv/model/otbn_core_model.sv
@@ -13,6 +13,7 @@
module otbn_core_model
import otbn_pkg::*;
+ import otbn_model_pkg::*;
#(
// Size of the instruction memory, in bytes
parameter int ImemSizeByte = 4096,
@@ -48,25 +49,6 @@
output bit err_o // something went wrong
);
- import "DPI-C" context function chandle otbn_model_init(string mem_scope,
- string design_scope,
- int unsigned imem_words,
- int unsigned dmem_words);
- import "DPI-C" function void otbn_model_destroy(chandle model);
- import "DPI-C" context function
- int unsigned otbn_model_step(chandle model,
- logic start,
- int unsigned start_addr,
- int unsigned status,
- logic edn_rnd_data_valid,
- logic [WLEN-1:0] edn_rnd_data,
- logic edn_urnd_data_valid,
- inout bit [31:0] insn_cnt,
- inout bit [31:0] err_bits,
- inout bit [31:0] stop_pc);
- import "DPI-C" function void otbn_model_reset(chandle model);
-
-
localparam int ImemSizeWords = ImemSizeByte / 4;
localparam int DmemSizeWords = DmemSizeByte / (WLEN / 8);
diff --git a/hw/ip/otbn/dv/model/otbn_model.cc b/hw/ip/otbn/dv/model/otbn_model.cc
index 01f26e7..7d35b7b 100644
--- a/hw/ip/otbn/dv/model/otbn_model.cc
+++ b/hw/ip/otbn/dv/model/otbn_model.cc
@@ -2,6 +2,8 @@
// 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>
@@ -9,11 +11,9 @@
#include <iomanip>
#include <iostream>
#include <sstream>
-#include <string>
-#include <svdpi.h>
#include "iss_wrapper.h"
-#include "otbn_memutil.h"
+#include "otbn_model_dpi.h"
#include "otbn_trace_checker.h"
#include "sv_scoped.h"
#include "sv_utils.h"
@@ -28,100 +28,6 @@
static void write_vector_to_file(const std::string &path,
const std::vector<uint8_t> &data);
-namespace {
-struct OtbnModel {
- public:
- OtbnModel(const std::string &mem_scope, const std::string &design_scope,
- unsigned imem_size_words, unsigned dmem_size_words)
- : mem_util_(mem_scope),
- design_scope_(design_scope),
- imem_size_words_(imem_size_words),
- dmem_size_words_(dmem_size_words) {}
-
- // True if this model is running in a simulation that has an RTL
- // implementation too (which needs checking).
- bool has_rtl() const { return !design_scope_.empty(); }
-
- // Start a new run with the model, writing IMEM/DMEM and jumping to the given
- // start address. Returns 0 on success; -1 on failure.
- int start(unsigned start_addr);
-
- // 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_BITS to *err_bits.
- int step(svLogic edn_rnd_data_valid,
- svLogicVecVal *edn_rnd_data, /* logic [255:0] */
- svLogic edn_urnd_data_valid, svBitVecVal *insn_cnt /* bit [31:0] */,
- svBitVecVal *err_bits /* bit [31:0] */,
- svBitVecVal *stop_pc /* bit [31:0] */);
-
- // Check model against RTL (if there is any) 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() const;
-
- // Grab contents of dmem from the model and load it back into the RTL
- // simulation. This is used when there's no RTL model of the design. Returns
- // 0 on success; -1 on failure.
- int load_dmem() const;
-
- // Flush any information in the model
- void reset();
-
- private:
- // Constructs an ISS wrapper if necessary. If something goes wrong, this
- // function prints a message and then returns null. If ensure is true, it
- // will never return null without printing a message, so error handling at
- // the callsite can silently return a failure code.
- ISSWrapper *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();
- }
-
- std::vector<uint8_t> get_sim_memory(bool is_imem) const {
- const MemArea &mem_area = mem_util_.GetMemArea(is_imem);
- return mem_area.Read(0, mem_area.GetSizeWords());
- }
-
- void set_sim_memory(bool is_imem, const std::vector<uint8_t> &data) const {
- mem_util_.GetMemArea(is_imem).Write(0, data);
- }
-
- // Grab contents of dmem from the model and compare them 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.
- bool check_dmem(ISSWrapper &iss) const;
-
- // 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.
- bool check_regs(ISSWrapper &iss) const;
-
- // 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.
- bool check_call_stack(ISSWrapper &iss) const;
-
- // 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 the first
- // time it's actually needed. Use ensure_iss() to create as needed.
- std::unique_ptr<ISSWrapper> iss_;
- OtbnMemUtil mem_util_;
- std::string design_scope_;
- unsigned imem_size_words_, dmem_size_words_;
-};
-} // namespace
-
extern "C" {
// These functions are only implemented if DesignScope != "", i.e. if we're
// running a block-level simulation. Code needs to check at runtime if
@@ -274,6 +180,16 @@
return ret;
}
+OtbnModel::OtbnModel(const std::string &mem_scope,
+ const std::string &design_scope, unsigned imem_size_words,
+ unsigned dmem_size_words)
+ : mem_util_(mem_scope),
+ design_scope_(design_scope),
+ imem_size_words_(imem_size_words),
+ dmem_size_words_(dmem_size_words) {}
+
+OtbnModel::~OtbnModel() {}
+
int OtbnModel::start(unsigned start_addr) {
const MemArea &imem = mem_util_.GetMemArea(true);
assert(start_addr % 4 == 0);
@@ -398,7 +314,7 @@
return good ? 1 : 0;
}
-int OtbnModel::load_dmem() const {
+int OtbnModel::load_dmem() {
ISSWrapper *iss = iss_.get();
if (!iss) {
std::cerr << "Cannot load dmem from OTBN model: ISS has not started.\n";
@@ -424,6 +340,29 @@
iss->reset(has_rtl());
}
+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();
+}
+
+std::vector<uint8_t> OtbnModel::get_sim_memory(bool is_imem) const {
+ const MemArea &mem_area = mem_util_.GetMemArea(is_imem);
+ return mem_area.Read(0, mem_area.GetSizeWords());
+}
+
+void OtbnModel::set_sim_memory(bool is_imem, const std::vector<uint8_t> &data) {
+ mem_util_.GetMemArea(is_imem).Write(0, data);
+}
+
bool OtbnModel::check_dmem(ISSWrapper &iss) const {
const MemArea &dmem = mem_util_.GetMemArea(false);
uint32_t dmem_bytes = dmem.GetSizeBytes();
@@ -563,54 +502,21 @@
return good;
}
-extern "C" OtbnModel *otbn_model_init(const char *mem_scope,
- const char *design_scope,
- unsigned imem_words,
- unsigned dmem_words) {
+OtbnModel *otbn_model_init(const char *mem_scope, const char *design_scope,
+ unsigned imem_words, unsigned dmem_words) {
assert(mem_scope && design_scope);
return new OtbnModel(mem_scope, design_scope, imem_words, dmem_words);
}
-extern "C" void otbn_model_destroy(OtbnModel *model) { delete model; }
+void otbn_model_destroy(OtbnModel *model) { delete model; }
-// The main entry point to the OTBN model, exported from here and used in
-// otbn_core_model.sv.
-//
-// This communicates state with otbn_core_model.sv through the status
-// parameter, which has the following bits:
-//
-// Bit 0: running True if the model is currently running
-// Bit 1: check_due True if the model finished running last cycle
-// Bit 2: failed_step Something failed when trying to start/step ISS
-// Bit 3: failed_cmp Consistency check at end of run failed
-//
-// The otbn_model_step function should only be called when either the model is
-// running (bit 0 of status), has a check due (bit 1 of status), or when start
-// is asserted. At other times, it will return immediately (but wastes a DPI
-// call).
-//
-// If the model is running and start is false, otbn_model_step steps the ISS by
-// a single cycle. If something goes wrong, it will set failed_step to true and
-// running to false. Otherwise, it writes the new value of otbn.INSN_CNT to
-// *insn_cnt.
-//
-// If nothing goes wrong and the ISS finishes its run, we set running to false,
-// write out err_bits and stop_pc and do the post-run task. If the model's
-// design_scope is non-empty, it should be the scope of an RTL implementation.
-// In that case, we compare register and memory contents with that
-// implementation, printing to stderr and setting the failed_cmp bit if there
-// are any mismatches. If the model's design_scope is the empty string, we grab
-// the contents of DMEM from the ISS and inject them into the simulation
-// memory.
-//
-// If start is true, we start the model at start_addr and then step once (as
-// described above).
-extern "C" unsigned otbn_model_step(
- OtbnModel *model, svLogic start, unsigned start_addr, unsigned status,
- svLogic edn_rnd_data_valid, svLogicVecVal *edn_rnd_data, /* logic [255:0] */
- svLogic edn_urnd_data_valid, svBitVecVal *insn_cnt /* bit [31:0] */,
- svBitVecVal *err_bits /* bit [31:0] */,
- svBitVecVal *stop_pc /* bit [31:0] */) {
+unsigned otbn_model_step(OtbnModel *model, svLogic start, unsigned start_addr,
+ unsigned status, svLogic edn_rnd_data_valid,
+ svLogicVecVal *edn_rnd_data, /* logic [255:0] */
+ svLogic edn_urnd_data_valid,
+ svBitVecVal *insn_cnt /* bit [31:0] */,
+ svBitVecVal *err_bits /* bit [31:0] */,
+ svBitVecVal *stop_pc /* bit [31:0] */) {
assert(model && insn_cnt && err_bits && stop_pc);
// Run model checks if needed. This usually happens just after an operation
@@ -691,8 +597,7 @@
return status;
}
-// Flush any information in the model
-extern "C" void otbn_model_reset(OtbnModel *model) {
+void otbn_model_reset(OtbnModel *model) {
assert(model);
model->reset();
}
diff --git a/hw/ip/otbn/dv/model/otbn_model.core b/hw/ip/otbn/dv/model/otbn_model.core
index ff78a08..5c8d723 100644
--- a/hw/ip/otbn/dv/model/otbn_model.core
+++ b/hw/ip/otbn/dv/model/otbn_model.core
@@ -14,12 +14,15 @@
- lowrisc:ip:otbn_tracer
files:
- otbn_model.cc: { file_type: cppSource }
+ - otbn_model.h: { file_type: cppSource, is_include_file: true }
+ - otbn_model_dpi.h: { file_type: cppSource, is_include_file: true }
- iss_wrapper.cc: { file_type: cppSource }
- iss_wrapper.h: { file_type: cppSource, is_include_file: true }
- otbn_trace_checker.h: { file_type: cppSource, is_include_file: true }
- otbn_trace_checker.cc: { file_type: cppSource }
- otbn_trace_entry.h: { file_type: cppSource, is_include_file: true }
- otbn_trace_entry.cc: { file_type: cppSource }
+ - otbn_model_pkg.sv
- otbn_core_model.sv
- otbn_rf_snooper_if.sv
- otbn_stack_snooper_if.sv
diff --git a/hw/ip/otbn/dv/model/otbn_model.h b/hw/ip/otbn/dv/model/otbn_model.h
new file mode 100644
index 0000000..518472c
--- /dev/null
+++ b/hw/ip/otbn/dv/model/otbn_model.h
@@ -0,0 +1,91 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+#ifndef OPENTITAN_HW_IP_OTBN_DV_MODEL_OTBN_MODEL_H_
+#define OPENTITAN_HW_IP_OTBN_DV_MODEL_OTBN_MODEL_H_
+
+#include <cstdint>
+#include <stdexcept>
+#include <string>
+#include <svdpi.h>
+#include <vector>
+
+#include "otbn_memutil.h"
+
+class ISSWrapper;
+
+class OtbnModel {
+ public:
+ OtbnModel(const std::string &mem_scope, const std::string &design_scope,
+ unsigned imem_size_words, unsigned dmem_size_words);
+ ~OtbnModel();
+
+ // True if this model is running in a simulation that has an RTL
+ // implementation too (which needs checking).
+ bool has_rtl() const { return !design_scope_.empty(); }
+
+ // Start a new run with the model, writing IMEM/DMEM and jumping to the given
+ // start address. Returns 0 on success; -1 on failure.
+ int start(unsigned start_addr);
+
+ // 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_BITS to *err_bits.
+ int step(svLogic edn_rnd_data_valid,
+ svLogicVecVal *edn_rnd_data, /* logic [255:0] */
+ svLogic edn_urnd_data_valid, svBitVecVal *insn_cnt /* bit [31:0] */,
+ svBitVecVal *err_bits /* bit [31:0] */,
+ svBitVecVal *stop_pc /* bit [31:0] */);
+
+ // Check model against RTL (if there is any) 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() const;
+
+ // Grab contents of dmem from the model and load it back into the RTL
+ // simulation. This is used when there's no RTL model of the design. Returns
+ // 0 on success; -1 on failure.
+ int load_dmem();
+
+ // Flush any information in the model
+ void reset();
+
+ private:
+ // Constructs an ISS wrapper if necessary. If something goes wrong, this
+ // function prints a message and then returns null. If ensure is true, it
+ // will never return null without printing a message, so error handling at
+ // the callsite can silently return a failure code.
+ ISSWrapper *ensure_wrapper();
+
+ // Read the contents of the ISS's memory
+ std::vector<uint8_t> get_sim_memory(bool is_imem) const;
+
+ // Set the contents of the ISS's memory
+ void set_sim_memory(bool is_imem, const std::vector<uint8_t> &data);
+
+ // Grab contents of dmem from the model and compare them 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.
+ bool check_dmem(ISSWrapper &iss) const;
+
+ // 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.
+ bool check_regs(ISSWrapper &iss) const;
+
+ // 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.
+ bool check_call_stack(ISSWrapper &iss) const;
+
+ // 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 the first
+ // time it's actually needed. Use ensure_iss() to create as needed.
+ std::unique_ptr<ISSWrapper> iss_;
+ OtbnMemUtil mem_util_;
+ std::string design_scope_;
+ unsigned imem_size_words_, dmem_size_words_;
+};
+
+#endif // OPENTITAN_HW_IP_OTBN_DV_MODEL_OTBN_MODEL_H_
diff --git a/hw/ip/otbn/dv/model/otbn_model_dpi.h b/hw/ip/otbn/dv/model/otbn_model_dpi.h
new file mode 100644
index 0000000..ce70ce4
--- /dev/null
+++ b/hw/ip/otbn/dv/model/otbn_model_dpi.h
@@ -0,0 +1,69 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+#ifndef OPENTITAN_HW_IP_OTBN_DV_MODEL_OTBN_MODEL_DPI_H_
+#define OPENTITAN_HW_IP_OTBN_DV_MODEL_OTBN_MODEL_DPI_H_
+
+// The DPI exports for OtbnModel. See also otbn_model_pkg.sv, where they are
+// declared for the SystemVerilog side.
+//
+// These are defined in a separate file from otbn_model.h because otherwise
+// something like otbn_top_sim.cc will see both these defines and the
+// auto-generated ones that Verilator produces in e.g. Votbn_top_sim__Dpi.h. The
+// latter use void* for all the chandle arguments and some versions of GCC treat
+// the resulting signatures as incompatible.
+
+extern "C" {
+
+// Create an OtbnModel object. Will always succeed.
+OtbnModel *otbn_model_init(const char *mem_scope, const char *design_scope,
+ unsigned imem_words, unsigned dmem_words);
+
+// Delete an OtbnModel
+void otbn_model_destroy(OtbnModel *model);
+
+// The main entry point to the OTBN model, exported from here and used in
+// otbn_core_model.sv.
+//
+// This communicates state with otbn_core_model.sv through the status
+// parameter, which has the following bits:
+//
+// Bit 0: running True if the model is currently running
+// Bit 1: check_due True if the model finished running last cycle
+// Bit 2: failed_step Something failed when trying to start/step ISS
+// Bit 3: failed_cmp Consistency check at end of run failed
+//
+// The otbn_model_step function should only be called when either the model is
+// running (bit 0 of status), has a check due (bit 1 of status), or when start
+// is asserted. At other times, it will return immediately (but wastes a DPI
+// call).
+//
+// If the model is running and start is false, otbn_model_step steps the ISS by
+// a single cycle. If something goes wrong, it will set failed_step to true and
+// running to false. Otherwise, it writes the new value of otbn.INSN_CNT to
+// *insn_cnt.
+//
+// If nothing goes wrong and the ISS finishes its run, we set running to false,
+// write out err_bits and stop_pc and do the post-run task. If the model's
+// design_scope is non-empty, it should be the scope of an RTL implementation.
+// In that case, we compare register and memory contents with that
+// implementation, printing to stderr and setting the failed_cmp bit if there
+// are any mismatches. If the model's design_scope is the empty string, we grab
+// the contents of DMEM from the ISS and inject them into the simulation
+// memory.
+//
+// If start is true, we start the model at start_addr and then step once (as
+// described above).
+unsigned otbn_model_step(OtbnModel *model, svLogic start, unsigned start_addr,
+ unsigned status, svLogic edn_rnd_data_valid,
+ svLogicVecVal *edn_rnd_data, /* logic [255:0] */
+ svLogic edn_urnd_data_valid,
+ svBitVecVal *insn_cnt /* bit [31:0] */,
+ svBitVecVal *err_bits /* bit [31:0] */,
+ svBitVecVal *stop_pc /* bit [31:0] */);
+
+// Flush any information in the model
+void otbn_model_reset(OtbnModel *model);
+}
+
+#endif // OPENTITAN_HW_IP_OTBN_DV_MODEL_OTBN_MODEL_DPI_H_
diff --git a/hw/ip/otbn/dv/model/otbn_model_pkg.sv b/hw/ip/otbn/dv/model/otbn_model_pkg.sv
new file mode 100644
index 0000000..45ab8a1
--- /dev/null
+++ b/hw/ip/otbn/dv/model/otbn_model_pkg.sv
@@ -0,0 +1,34 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// Imports for the functions defined in otbn_model.h. There are documentation comments explaining
+// what the functions do there.
+`ifndef SYNTHESIS
+package otbn_model_pkg;
+
+ import otbn_pkg::WLEN;
+
+ import "DPI-C" context function chandle otbn_model_init(string mem_scope,
+ string design_scope,
+ int unsigned imem_words,
+ int unsigned dmem_words);
+
+ import "DPI-C" function void otbn_model_destroy(chandle model);
+
+ import "DPI-C" context function
+ int unsigned otbn_model_step(chandle model,
+ logic start,
+ int unsigned start_addr,
+ int unsigned status,
+ logic edn_rnd_data_valid,
+ logic [WLEN-1:0] edn_rnd_data,
+ logic edn_urnd_data_valid,
+ inout bit [31:0] insn_cnt,
+ inout bit [31:0] err_bits,
+ inout bit [31:0] stop_pc);
+
+ import "DPI-C" function void otbn_model_reset(chandle model);
+
+endpackage
+`endif // SYNTHESIS