[dv] Allow using memutil_dpi_scrambled even without prim_ram_1p_scr

This is not going to be very useful at runtime, but we want to be able
to build things that may or may not use prim_ram_1p_scr, depending on
a parameter. If we're using it, we'll want to use this library.

Since fusesoc can't do things like "include this file iff parameter P
has value V", we'll need to include the library unconditionally.
Unfortunately, that then means Verilator simulations don't
link (because prim_ram_1p_scr and its DPI helper functions get
discarded from the design). Making the symbol weak moves the
resolution check to runtime, fixing things.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.cc b/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.cc
index 37ea6b1..4f7fc71 100644
--- a/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.cc
+++ b/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.cc
@@ -81,12 +81,27 @@
   return width;
 }
 
+// These functions come from SV code, exposed over DPI. They are defined inside
+// a module (prim_ram1p_scr) and, awkwardly, if a design doesn't happen to use
+// that module then some simulators (Verilator!) will discard it, together with
+// the DPI functions.
+//
+// We'd like to be able to use the memutil_dpi_scrambled.core whether or not we
+// actually instantiated prim_ram1p_scr: we'll just spit out an error if we
+// call GetScrambleKey() or GetScrambleNonce() if we didn't instantiate it. To
+// make this work, we mark both symbols weak.
 extern "C" {
-int simutil_get_scramble_key(svBitVecVal *key);
-int simutil_get_scramble_nonce(svBitVecVal *nonce);
+int __attribute__((weak)) simutil_get_scramble_key(svBitVecVal *key);
+int __attribute__((weak)) simutil_get_scramble_nonce(svBitVecVal *nonce);
 }
 
 std::vector<uint8_t> ScrambledEcc32MemArea::GetScrambleKey() const {
+  if (!simutil_get_scramble_key) {
+    throw std::runtime_error(
+        "No definition of simutil_get_scramble_key. "
+        "Does the design actually use prim_ram1p_scr?");
+  }
+
   SVScoped scoped(scr_scope_);
   svBitVecVal key_minibuf[((kPrinceWidthByte * 2) + 3) / 4];
 
@@ -102,6 +117,12 @@
 std::vector<uint8_t> ScrambledEcc32MemArea::GetScrambleNonce() const {
   assert(GetNonceWidthByte() <= kScrMaxNonceWidthByte);
 
+  if (!simutil_get_scramble_nonce) {
+    throw std::runtime_error(
+        "No definition of simutil_get_scramble_nonce. "
+        "Does the design actually use prim_ram1p_scr?");
+  }
+
   SVScoped scoped(scr_scope_);
   svBitVecVal nonce_minibuf[(kScrMaxNonceWidthByte + 3) / 4];