[otbn] Use DpiMemutil / MemArea to access memory for otbn_model.cc
This removes the duplicated calls to simutil_get_mem and
simutil_set_mem. It also avoids duplicating the SV paths to the guts
of the memories: they were in otbn_memutil.cc and otbn.sv and
otbn_top_sim.sv; now they're just in otbn_memutil.cc.
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/dv/memutil/otbn_memutil.cc b/hw/ip/otbn/dv/memutil/otbn_memutil.cc
index 9af4837..695960c 100644
--- a/hw/ip/otbn/dv/memutil/otbn_memutil.cc
+++ b/hw/ip/otbn/dv/memutil/otbn_memutil.cc
@@ -10,11 +10,20 @@
#include <limits>
#include <stdexcept>
+// join two, possibly relative, scopes correctly.
+static std::string join_scopes(const std::string &a, const std::string &b) {
+ assert(a.size() && b.size());
+ // If a = ".." and b = "foo.bar", we want "..foo.bar". Otherwise, a
+ // = "..foo" and b = "bar.baz", so we want "..foo.bar.baz"
+ // (inserting a "." between the two)
+ return (a.back() == '.') ? a + b : a + "." + b;
+}
+
OtbnMemUtil::OtbnMemUtil(const std::string &top_scope)
- : imem_(top_scope + ".u_imem.u_mem.gen_generic.u_impl_generic", 4096 / 4,
- 4),
- dmem_(top_scope + ".u_dmem.u_mem.gen_generic.u_impl_generic", 4096 / 32,
- 32) {
+ : imem_(join_scopes(top_scope, "u_imem.u_mem.gen_generic.u_impl_generic"),
+ 4096 / 4, 4),
+ dmem_(join_scopes(top_scope, "u_dmem.u_mem.gen_generic.u_impl_generic"),
+ 4096 / 32, 32) {
RegisterMemoryArea("imem", 0x4000, &imem_);
RegisterMemoryArea("dmem", 0x8000, &dmem_);
}
diff --git a/hw/ip/otbn/dv/memutil/otbn_memutil.h b/hw/ip/otbn/dv/memutil/otbn_memutil.h
index fed8352..286b44b 100644
--- a/hw/ip/otbn/dv/memutil/otbn_memutil.h
+++ b/hw/ip/otbn/dv/memutil/otbn_memutil.h
@@ -23,6 +23,11 @@
// Get access to the segments currently staged for imem/dmem
const StagedMem::SegMap &GetSegs(bool is_imem) const;
+ // Get access to a memory area
+ const MemArea &GetMemArea(bool is_imem) const {
+ return is_imem ? imem_ : dmem_;
+ }
+
private:
MemArea imem_, dmem_;
};
diff --git a/hw/ip/otbn/dv/memutil/otbn_memutil_sim_opts.hjson b/hw/ip/otbn/dv/memutil/otbn_memutil_sim_opts.hjson
index a7e7283..8408b7b 100644
--- a/hw/ip/otbn/dv/memutil/otbn_memutil_sim_opts.hjson
+++ b/hw/ip/otbn/dv/memutil/otbn_memutil_sim_opts.hjson
@@ -8,16 +8,21 @@
memutil_dpi_core: "lowrisc:dv_verilator:memutil_dpi:0"
memutil_dpi_src_dir: "{eval_cmd} echo \"{memutil_dpi_core}\" | tr ':' '_'"
+ otbn_memutil_core: "lowrisc:dv:otbn_memutil:0"
+ otbn_memutil_src_dir: "{eval_cmd} echo \"{otbn_memutil_core}\" | tr ':' '_'"
+
build_modes: [
{
name: vcs_otbn_memutil_build_opts
build_opts: ["-CFLAGS -I{build_dir}/src/{memutil_dpi_src_dir}/cpp",
+ "-CFLAGS -I{build_dir}/src/{otbn_memutil_src_dir}",
"-lelf"]
}
{
name: xcelium_otbn_memutil_build_opts
build_opts: ["-I{build_dir}/src/{memutil_dpi_src_dir}/cpp",
+ "-I{build_dir}/src/{otbn_memutil_src_dir}",
"-lelf"]
}
]
diff --git a/hw/ip/otbn/dv/model/otbn_core_model.sv b/hw/ip/otbn/dv/model/otbn_core_model.sv
index e73cec1..e5bda2c 100644
--- a/hw/ip/otbn/dv/model/otbn_core_model.sv
+++ b/hw/ip/otbn/dv/model/otbn_core_model.sv
@@ -19,10 +19,9 @@
// Size of the data memory, in bytes
parameter int DmemSizeByte = 4096,
- // Scope of the instruction memory (for DPI)
- parameter string ImemScope = "",
- // Scope of the data memory (for DPI)
- parameter string DmemScope = "",
+ // The scope that contains the instruction and data memory (for DPI)
+ parameter string MemScope = "",
+
// Scope of an RTL OTBN implementation (for DPI). If empty, this is a "standalone" model, which
// should update DMEM on completion. If not empty, we assume it's the scope for the top-level of a
// real implementation running alongside and we check DMEM contents on completion.
@@ -43,15 +42,13 @@
output bit err_o // something went wrong
);
- import "DPI-C" function chandle otbn_model_init();
+ 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 handle);
import "DPI-C" context function
int unsigned otbn_model_step(chandle model,
- string imem_scope,
- int unsigned imem_size,
- string dmem_scope,
- int unsigned dmem_size,
- string design_scope,
logic start_i,
int unsigned start_addr,
int unsigned status,
@@ -76,7 +73,7 @@
// Create and destroy an object through which we can talk to the ISS.
chandle model_handle;
initial begin
- model_handle = otbn_model_init();
+ model_handle = otbn_model_init(MemScope, DesignScope, ImemSizeWords, DmemSizeWords);
assert(model_handle != chandle_null);
end
final begin
@@ -109,9 +106,6 @@
end else begin
if (start_i | running | check_due) begin
status <= otbn_model_step(model_handle,
- ImemScope, ImemSizeWords,
- DmemScope, DmemSizeWords,
- DesignScope,
start_i, start_addr_32,
status, raw_err_bits_d);
raw_err_bits_q <= raw_err_bits_d;
diff --git a/hw/ip/otbn/dv/model/otbn_model.cc b/hw/ip/otbn/dv/model/otbn_model.cc
index c9be236..08061f9 100644
--- a/hw/ip/otbn/dv/model/otbn_model.cc
+++ b/hw/ip/otbn/dv/model/otbn_model.cc
@@ -13,40 +13,72 @@
#include <svdpi.h>
#include "iss_wrapper.h"
+#include "otbn_memutil.h"
#include "otbn_trace_checker.h"
#include "sv_scoped.h"
+// Read (the start of) the contents of a file at path as a vector of bytes.
+// Expects num_bytes bytes of data. On failure, throws a std::runtime_error.
+static std::vector<uint8_t> read_vector_from_file(const std::string &path,
+ size_t num_bytes);
+
+// Write a vector of bytes to a new file at path. On failure, throws a
+// std::runtime_error.
+static void write_vector_to_file(const std::string &path,
+ const std::vector<uint8_t> &data);
+
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() {
+ 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) {}
+
+ // This class is a 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 the first time it's
+ // actually needed.
+ //
+ // If ensure is true, this 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 *get_wrapper(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;
+ return nullptr;
}
}
assert(iss);
- return true;
+ 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);
}
std::unique_ptr<ISSWrapper> iss;
+ OtbnMemUtil mem_util;
+ std::string design_scope_;
+ unsigned imem_size_words_, dmem_size_words_;
};
} // namespace
extern "C" {
-// DPI imports, implemented in SystemVerilog
-int simutil_get_mem(int index, svBitVecVal *val);
-int simutil_set_mem(int index, const svBitVecVal *val);
-
// These functions are only implemented if DesignScope != "", i.e. if we're
// running a block-level simulation. Code needs to check at runtime if
// otbn_rf_peek() and otbn_stack_element_peek() are available before calling
@@ -81,78 +113,20 @@
// and running to false.
//
// If nothing goes wrong, but the ISS finishes its run, we set running to
-// false, write out err_bits and do the post-run task. If 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
-// design_scope is the empty string, we grab the contents of DMEM from the ISS
-// and inject them into the memory at dmem_scope.
+// false, write out err_bits 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_i is true, we start the model at start_addr and then step once (as
// described above).
-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,
+extern "C" unsigned otbn_model_step(OtbnModel *model, svLogic start_i,
unsigned start_addr, unsigned status,
svBitVecVal *err_bits /* bit [31:0] */);
-// Use simutil_get_mem to read data one word at a time from the given scope and
-// collect the results up in a vector of uint8_t values.
-static std::vector<uint8_t> get_sim_memory(const char *scope, size_t num_words,
- size_t word_size) {
- SVScoped scoped(scope);
-
- // simutil_get_mem passes data as a packed array of svBitVecVal words. It
- // only works for memories of size at most 312 bits, so we can just allocate
- // 312/8 = 39 bytes as 39/sizeof(svBitVecVal) words on the stack.
- assert(word_size <= 312 / 8);
- svBitVecVal buf[312 / 8 / sizeof(svBitVecVal)];
-
- std::vector<uint8_t> ret;
-
- for (size_t w = 0; w < num_words; w++) {
- if (!simutil_get_mem(w, buf)) {
- std::ostringstream oss;
- oss << "Cannot get memory at word " << w << " from scope " << scope
- << ".\n";
- throw std::runtime_error(oss.str());
- }
-
- // Append the first word_size bytes of data to ret.
- std::copy_n(reinterpret_cast<const char *>(buf), word_size,
- std::back_inserter(ret));
- }
-
- return ret;
-}
-
-// Use simutil_get_mem to write data one word at a time to the given scope.
-static void set_sim_memory(const std::vector<uint8_t> &data, const char *scope,
- size_t num_words, size_t word_size) {
- SVScoped scoped(scope);
-
- assert(num_words * word_size == data.size());
-
- // See get_sim_memory for why this array is sized like this.
- assert(word_size <= 312 / 8);
- svBitVecVal buf[312 / 8 / sizeof(svBitVecVal)];
-
- for (size_t w = 0; w < num_words; w++) {
- const uint8_t *p = &data[w * word_size];
- memcpy(buf, p, word_size);
-
- if (!simutil_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 (the start of) the contents of a file at path as a vector of bytes.
-// Expects num_bytes bytes of data.
static std::vector<uint8_t> read_vector_from_file(const std::string &path,
size_t num_bytes) {
std::filebuf fb;
@@ -198,55 +172,42 @@
}
}
-// Dump the memory at the given scope to a file at path.
-static void dump_memory_to_file(const std::string &path, const char *scope,
- size_t num_words, size_t word_size) {
- write_vector_to_file(path, get_sim_memory(scope, num_words, word_size));
+extern "C" 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);
}
-// Read data from the given file and then write it into the memory at the given
-// scope.
-static void load_memory_from_file(const std::string &path, const char *scope,
- size_t num_words, size_t word_size) {
- size_t num_bytes = num_words * word_size;
- set_sim_memory(read_vector_from_file(path, num_bytes), scope, num_words,
- word_size);
-}
-
-extern "C" OtbnModel *otbn_model_init() { return new OtbnModel; }
-
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(OtbnModel *model, const char *imem_scope,
- unsigned imem_words, const char *dmem_scope,
- unsigned dmem_words, unsigned start_addr) {
- assert(model);
- assert(imem_words >= 0);
- assert(dmem_words >= 0);
- assert(start_addr < (imem_words * 4));
+static int start_model(OtbnModel &model, unsigned start_addr) {
+ const MemArea &imem = model.mem_util.GetMemArea(true);
+ assert(start_addr % 4 == 0);
+ assert(start_addr / 4 < imem.GetSizeWords());
- if (!model->ensure())
+ ISSWrapper *iss = model.get_wrapper(true);
+ if (!iss)
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"));
+ std::string dfname(iss->make_tmp_path("dmem"));
+ std::string ifname(iss->make_tmp_path("imem"));
try {
- dump_memory_to_file(dfname, dmem_scope, dmem_words, 32);
- dump_memory_to_file(ifname, imem_scope, imem_words, 4);
+ write_vector_to_file(dfname, model.get_sim_memory(false));
+ write_vector_to_file(ifname, model.get_sim_memory(true));
} catch (const std::exception &err) {
std::cerr << "Error when dumping memory contents: " << err.what() << "\n";
return -1;
}
try {
- iss.load_d(dfname);
- iss.load_i(ifname);
- iss.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;
@@ -258,17 +219,15 @@
// 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.
-static int step_model(OtbnModel *model, bool gen_trace, uint32_t *err_bits) {
- assert(model);
+static int step_model(OtbnModel &model, bool gen_trace, uint32_t *err_bits) {
assert(err_bits);
- if (!model->ensure())
+ ISSWrapper *iss = model.get_wrapper(true);
+ if (!iss)
return -1;
- assert(model->iss);
- ISSWrapper &iss = *model->iss;
try {
- std::pair<int, uint32_t> ret = iss.step(gen_trace);
+ std::pair<int, uint32_t> ret = iss->step(gen_trace);
switch (ret.first) {
case -1:
// Something went wrong, such as a trace mismatch. We've already printed
@@ -296,19 +255,20 @@
// 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(OtbnModel *model, const char *dmem_scope,
- unsigned dmem_words) {
- assert(model);
- if (!model->iss) {
+static int load_dmem(OtbnModel &model) {
+ ISSWrapper *iss = model.get_wrapper(false);
+ if (!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"));
+ const MemArea &dmem = model.mem_util.GetMemArea(false);
+
+ std::string dfname(iss->make_tmp_path("dmem_out"));
try {
- iss.dump_d(dfname);
- load_memory_from_file(dfname, dmem_scope, dmem_words, 32);
+ iss->dump_d(dfname);
+ model.set_sim_memory(false,
+ read_vector_from_file(dfname, dmem.GetSizeBytes()));
} catch (const std::exception &err) {
std::cerr << "Error when loading dmem from ISS: " << err.what() << "\n";
return -1;
@@ -319,16 +279,17 @@
// 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 &iss, const char *dmem_scope,
- unsigned dmem_words) {
- size_t dmem_bytes = dmem_words * 32;
+static bool check_dmem(OtbnModel &model, ISSWrapper &iss) {
+ const MemArea &dmem = model.mem_util.GetMemArea(false);
+ uint32_t dmem_bytes = dmem.GetSizeBytes();
+
std::string dfname(iss.make_tmp_path("dmem_out"));
iss.dump_d(dfname);
std::vector<uint8_t> iss_data = read_vector_from_file(dfname, dmem_bytes);
assert(iss_data.size() == dmem_bytes);
- std::vector<uint8_t> rtl_data = get_sim_memory(dmem_scope, dmem_words, 32);
+ std::vector<uint8_t> rtl_data = model.get_sim_memory(false);
assert(rtl_data.size() == dmem_bytes);
// If the arrays match, we're done.
@@ -434,12 +395,12 @@
// 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 &iss, const std::string &design_scope) {
+static bool check_regs(OtbnModel &model, ISSWrapper &iss) {
std::string base_scope =
- design_scope +
+ model.design_scope_ +
".u_otbn_rf_base.gen_rf_base_ff.u_otbn_rf_base_inner.u_snooper";
std::string wide_scope =
- design_scope + ".gen_rf_bignum_ff.u_otbn_rf_bignum.u_snooper";
+ model.design_scope_ + ".gen_rf_bignum_ff.u_otbn_rf_bignum.u_snooper";
auto rtl_gprs = get_rtl_regs<uint32_t>(base_scope);
auto rtl_wdrs = get_rtl_regs<ISSWrapper::u256_t>(wide_scope);
@@ -495,9 +456,9 @@
// 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 &iss, const std::string &design_scope) {
+static bool check_call_stack(OtbnModel &model, ISSWrapper &iss) {
std::string call_stack_snooper_scope =
- design_scope + ".u_otbn_rf_base.u_call_stack_snooper";
+ model.design_scope_ + ".u_otbn_rf_base.u_call_stack_snooper";
auto rtl_call_stack = get_stack<uint32_t>(call_stack_snooper_scope);
@@ -534,38 +495,35 @@
// 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(OtbnModel *model, const char *dmem_scope, unsigned dmem_words,
- const char *design_scope) {
+int check_model(OtbnModel *model) {
assert(model);
- assert(dmem_words >= 0);
- assert(design_scope);
- if (!model->iss) {
+ ISSWrapper *iss = model->get_wrapper(false);
+ if (!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(iss, dmem_scope, dmem_words);
+ good &= check_dmem(*model, *iss);
} catch (const std::exception &err) {
std::cerr << "Failed to check DMEM: " << err.what() << "\n";
return -1;
}
try {
- good &= check_regs(iss, design_scope);
+ good &= check_regs(*model, *iss);
} catch (const std::exception &err) {
std::cerr << "Failed to check registers: " << err.what() << "\n";
return -1;
}
try {
- good &= check_call_stack(iss, design_scope);
+ good &= check_call_stack(*model, *iss);
} catch (const std::exception &err) {
std::cerr << "Failed to check call stack: " << err.what() << "\n";
return -1;
@@ -584,19 +542,16 @@
}
}
-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,
+extern "C" unsigned otbn_model_step(OtbnModel *model, svLogic start_i,
unsigned start_addr, unsigned status,
svBitVecVal *err_bits /* bit [31:0] */) {
- assert(model && imem_scope && dmem_scope && design_scope && err_bits);
+ assert(model && err_bits);
// Run model checks if needed. This usually happens just after an operation
// has finished.
- bool check_rtl = (design_scope[0] != '\0');
+ bool check_rtl = (model->design_scope_.size() > 0);
if (check_rtl && (status & CHECK_DUE_BIT)) {
- switch (check_model(model, dmem_scope, dmem_words, design_scope)) {
+ switch (check_model(model)) {
case 1:
// Match (success)
break;
@@ -615,8 +570,7 @@
// Start the model if requested
if (start_i) {
- switch (start_model(model, imem_scope, imem_words, dmem_scope, dmem_words,
- start_addr)) {
+ switch (start_model(*model, start_addr)) {
case 0:
// All good
status |= RUNNING_BIT;
@@ -634,7 +588,7 @@
// Step the model once
uint32_t int_err_bits;
- switch (step_model(model, check_rtl, &int_err_bits)) {
+ switch (step_model(*model, check_rtl, &int_err_bits)) {
case 0:
// Still running: no change
break;
@@ -657,7 +611,7 @@
// If we've just stopped running and there's no RTL, load the contents of
// DMEM back from the ISS
if (!check_rtl) {
- switch (load_dmem(model, dmem_scope, dmem_words)) {
+ switch (load_dmem(*model)) {
case 0:
// Success
break;
diff --git a/hw/ip/otbn/dv/model/otbn_model.core b/hw/ip/otbn/dv/model/otbn_model.core
index 00c40e8..b3b538f 100644
--- a/hw/ip/otbn/dv/model/otbn_model.core
+++ b/hw/ip/otbn/dv/model/otbn_model.core
@@ -10,6 +10,7 @@
depend:
- lowrisc:ip:otbn_pkg
- lowrisc:dv_verilator:memutil_dpi
+ - lowrisc:dv:otbn_memutil
- lowrisc:ip:otbn_tracer
files:
- otbn_model.cc: { file_type: cppSource }
diff --git a/hw/ip/otbn/dv/uvm/tb.sv b/hw/ip/otbn/dv/uvm/tb.sv
index bfa711f..46af9f3 100644
--- a/hw/ip/otbn/dv/uvm/tb.sv
+++ b/hw/ip/otbn/dv/uvm/tb.sv
@@ -68,16 +68,11 @@
// decoding errors).
assign model_if.start = dut.start;
- localparam ImemScope = "..dut.u_imem.u_mem.gen_generic.u_impl_generic";
- localparam DmemScope = "..dut.u_dmem.u_mem.gen_generic.u_impl_generic";
- localparam DesignScope = "..dut.u_otbn_core";
-
otbn_core_model #(
.DmemSizeByte (otbn_reg_pkg::OTBN_DMEM_SIZE),
.ImemSizeByte (otbn_reg_pkg::OTBN_IMEM_SIZE),
- .DmemScope (DmemScope),
- .ImemScope (ImemScope),
- .DesignScope (DesignScope)
+ .MemScope ("..dut"),
+ .DesignScope ("..dut.u_otbn_core")
) u_model (
.clk_i (model_if.clk_i),
.rst_ni (model_if.rst_ni),
diff --git a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
index a8b9496..3c98cca 100644
--- a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
+++ b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
@@ -205,11 +205,8 @@
// The model
//
- // This runs in parallel with the real core above. Eventually, we'll have strong consistency
- // checks between the two. For now, we just check that they have the same "done" signals.
+ // This runs in parallel with the real core above, with consistency checks between the two.
- localparam string ImemScope = "..u_imem.u_mem.gen_generic.u_impl_generic";
- localparam string DmemScope = "..u_dmem.u_mem.gen_generic.u_impl_generic";
localparam string DesignScope = "..u_otbn_core";
logic otbn_model_done;
@@ -219,8 +216,7 @@
otbn_core_model #(
.DmemSizeByte ( DmemSizeByte ),
.ImemSizeByte ( ImemSizeByte ),
- .DmemScope ( DmemScope ),
- .ImemScope ( ImemScope ),
+ .MemScope ( ".." ),
.DesignScope ( DesignScope )
) u_otbn_core_model (
.clk_i ( IO_CLK ),
diff --git a/hw/ip/otbn/rtl/otbn.sv b/hw/ip/otbn/rtl/otbn.sv
index 6a5fd2a..fabf4a8 100644
--- a/hw/ip/otbn/rtl/otbn.sv
+++ b/hw/ip/otbn/rtl/otbn.sv
@@ -519,15 +519,11 @@
assign start_model = start & otbn_use_model;
assign start_rtl = start & ~otbn_use_model;
- // Model (Instruction Set Simulation)
- localparam string ImemScope = "..u_imem.u_mem.gen_generic.u_impl_generic";
- localparam string DmemScope = "..u_dmem.u_mem.gen_generic.u_impl_generic";
-
+ // Model (Instruction Set Simulator)
otbn_core_model #(
.DmemSizeByte(DmemSizeByte),
.ImemSizeByte(ImemSizeByte),
- .DmemScope(DmemScope),
- .ImemScope(ImemScope),
+ .MemScope(".."),
.DesignScope("")
) u_otbn_core_model (
.clk_i,