[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