[dv] Add a ReadWithIntegrity method to Ecc32MemArea

The existing Read() method returns a vector of bytes.
ReadWithIntegrity returns a vector of 32-bit words, together with
booleans showing whether the integrity bits for these words are valid.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/dv/verilator/cpp/ecc32_mem_area.cc b/hw/dv/verilator/cpp/ecc32_mem_area.cc
index 0124170..230d448 100644
--- a/hw/dv/verilator/cpp/ecc32_mem_area.cc
+++ b/hw/dv/verilator/cpp/ecc32_mem_area.cc
@@ -29,6 +29,29 @@
       "vmem files are not supported for memories with ECC bits");
 }
 
+Ecc32MemArea::EccWords Ecc32MemArea::ReadWithIntegrity(
+    uint32_t word_offset, uint32_t num_words) const {
+  assert(word_offset + num_words <= num_words_);
+
+  // See MemArea::Write for an explanation for this buffer.
+  uint8_t minibuf[SV_MEM_WIDTH_BYTES];
+  memset(minibuf, 0, sizeof minibuf);
+  assert(width_byte_ <= sizeof minibuf);
+
+  EccWords ret;
+  ret.reserve(num_words);
+
+  for (uint32_t i = 0; i < num_words; ++i) {
+    uint32_t src_word = word_offset + i;
+    uint32_t phys_addr = ToPhysAddr(src_word);
+
+    ReadToMinibuf(minibuf, phys_addr);
+    ReadBufferWithIntegrity(ret, minibuf, src_word);
+  }
+
+  return ret;
+}
+
 // Add bits to buf at bit_idx
 //
 // buf is assumed to be little-endian, so bit_idx 0 will refer to the bottom
@@ -116,3 +139,23 @@
     }
   }
 }
+
+void Ecc32MemArea::ReadBufferWithIntegrity(
+    EccWords &data, const uint8_t buf[SV_MEM_WIDTH_BYTES],
+    uint32_t src_word) const {
+  for (int i = 0; i < width_byte_ / 4; ++i) {
+    uint8_t buf32[4];
+    uint32_t w32 = 0;
+    for (int j = 0; j < 4; ++j) {
+      uint8_t byte = extract_bits(buf, 39 * i + 8 * j, 8);
+      buf32[j] = byte;
+      w32 |= (uint32_t)byte << 8 * j;
+    }
+
+    uint8_t exp_check_bits = enc_secded_39_32(buf32);
+    uint8_t check_bits = extract_bits(buf, 39 * i + 32, 7);
+    bool good = check_bits == exp_check_bits;
+
+    data.push_back(std::make_pair(good, w32));
+  }
+}
diff --git a/hw/dv/verilator/cpp/ecc32_mem_area.h b/hw/dv/verilator/cpp/ecc32_mem_area.h
index 9b0523b..d92948d 100644
--- a/hw/dv/verilator/cpp/ecc32_mem_area.h
+++ b/hw/dv/verilator/cpp/ecc32_mem_area.h
@@ -24,6 +24,22 @@
 
   void LoadVmem(const std::string &path) const override;
 
+  typedef std::pair<bool, uint32_t> EccWord;
+  typedef std::vector<EccWord> EccWords;
+
+  /** Read data with validity bits, starting at the given offset.
+   *
+   * This is equivalent to MemArea's Read method, but returns 32 bit
+   * words, each with a boolean saying whether the integrity bits for
+   * that word are valid or not.
+   *
+   * @param word_offset The offset, in words, of the first word that should be
+   *                    read.
+   *
+   * @param num_words   The number of words to read.
+   */
+  EccWords ReadWithIntegrity(uint32_t word_offset, uint32_t num_words) const;
+
  protected:
   void WriteBuffer(uint8_t buf[SV_MEM_WIDTH_BYTES],
                    const std::vector<uint8_t> &data, size_t start_idx,
@@ -32,6 +48,20 @@
   void ReadBuffer(std::vector<uint8_t> &data,
                   const uint8_t buf[SV_MEM_WIDTH_BYTES],
                   uint32_t src_word) const override;
+
+  /** Extract the logical words corresponding to the physical memory contents
+   * in \p buf, together with validity bits. Append them to \p data.
+   *
+   * @param data     The target, onto which the extracted memory words should
+   *                 be appended.
+   *
+   * @param buf      Source buffer (physical memory bits)
+   *
+   * @param src_word Logical address of the location being read
+   */
+  virtual void ReadBufferWithIntegrity(EccWords &data,
+                                       const uint8_t buf[SV_MEM_WIDTH_BYTES],
+                                       uint32_t src_word) const;
 };
 
 #endif  // OPENTITAN_HW_DV_VERILATOR_CPP_ECC32_MEM_AREA_H_
diff --git a/hw/dv/verilator/cpp/mem_area.cc b/hw/dv/verilator/cpp/mem_area.cc
index 1687840..e84082b 100644
--- a/hw/dv/verilator/cpp/mem_area.cc
+++ b/hw/dv/verilator/cpp/mem_area.cc
@@ -81,21 +81,7 @@
     uint32_t src_word = word_offset + i;
     uint32_t phys_addr = ToPhysAddr(src_word);
 
-    {
-      // Both ToPhysAddr and ReadBuffer might set the scope with `SVScoped`.
-      // Keep the `SVScoped` here confined to an inner scope so they don't
-      // interact causing incorrect relative path behaviour. If this fails to
-      // set scope, it will throw an error which should be caught at this
-      // function's callsite.
-      SVScoped scoped(scope_);
-      if (!simutil_get_mem(phys_addr, (svBitVecVal *)minibuf)) {
-        std::ostringstream oss;
-        oss << "Could not read memory at byte offset 0x" << std::hex
-            << src_word * width_byte_ << ".";
-        throw std::runtime_error(oss.str());
-      }
-    }
-
+    ReadToMinibuf(minibuf, phys_addr);
     ReadBuffer(ret, minibuf, src_word);
   }
 
@@ -126,3 +112,13 @@
   std::copy_n(reinterpret_cast<const char *>(buf), width_byte_,
               std::back_inserter(data));
 }
+
+void MemArea::ReadToMinibuf(uint8_t *minibuf, uint32_t phys_addr) const {
+  SVScoped scoped(scope_);
+  if (!simutil_get_mem(phys_addr, (svBitVecVal *)minibuf)) {
+    std::ostringstream oss;
+    oss << "Could not read memory word at physical index 0x" << std::hex
+        << phys_addr << ".";
+    throw std::runtime_error(oss.str());
+  }
+}
diff --git a/hw/dv/verilator/cpp/mem_area.h b/hw/dv/verilator/cpp/mem_area.h
index f1c8a6b..7f642a8 100644
--- a/hw/dv/verilator/cpp/mem_area.h
+++ b/hw/dv/verilator/cpp/mem_area.h
@@ -109,10 +109,11 @@
    * across. Other implementations might undo scrambling, remove ECC bits or
    * similar.
    *
-   * @param data     The target, onto which the extracted memory contents should
-   * be appended.
+   * @param data     The target, onto which the extracted memory contents
+   *                 should be appended.
    *
    * @param buf      Source buffer (physical memory bits)
+   *
    * @param src_word Logical address of the location being read
    */
   virtual void ReadBuffer(std::vector<uint8_t> &data,
@@ -129,6 +130,13 @@
   virtual uint32_t ToPhysAddr(uint32_t logical_addr) const {
     return logical_addr;
   }
+
+  /** Read the memory word at phys_addr into minibuf
+   *
+   * minibuf should be at least SV_MEM_WIDTH_BYTES in size. See the
+   * implementation of MemArea::Write() for the details.
+   */
+  void ReadToMinibuf(uint8_t *minibuf, uint32_t phys_addr) const;
 };
 
 #endif  // OPENTITAN_HW_DV_VERILATOR_CPP_MEM_AREA_H_
diff --git a/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.cc b/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.cc
index 87069a7..37ea6b1 100644
--- a/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.cc
+++ b/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.cc
@@ -169,20 +169,30 @@
   std::copy(scramble_buf.begin(), scramble_buf.end(), &buf[0]);
 }
 
+std::vector<uint8_t> ScrambledEcc32MemArea::ReadUnscrambled(
+    const uint8_t buf[SV_MEM_WIDTH_BYTES], uint32_t src_word) const {
+  std::vector<uint8_t> scrambled_data(buf, buf + GetPhysWidthByte());
+  return scramble_decrypt_data(
+      scrambled_data, GetPhysWidth(), 39, AddrIntToBytes(src_word, addr_width_),
+      addr_width_, GetScrambleNonce(), GetScrambleKey(), repeat_keystream_);
+}
+
 void ScrambledEcc32MemArea::ReadBuffer(std::vector<uint8_t> &data,
                                        const uint8_t buf[SV_MEM_WIDTH_BYTES],
                                        uint32_t src_word) const {
-  // Unscramble data from read buffer
-  std::vector<uint8_t> scrambled_data =
-      std::vector<uint8_t>(buf, buf + GetPhysWidthByte());
-  std::vector<uint8_t> unscrambled_data = scramble_decrypt_data(
-      scrambled_data, GetPhysWidth(), 39, AddrIntToBytes(src_word, addr_width_),
-      addr_width_, GetScrambleNonce(), GetScrambleKey(), repeat_keystream_);
-
+  std::vector<uint8_t> unscrambled_data = ReadUnscrambled(buf, src_word);
   // Strip integrity to give final result
   Ecc32MemArea::ReadBuffer(data, &unscrambled_data[0], src_word);
 }
 
+void ScrambledEcc32MemArea::ReadBufferWithIntegrity(
+    EccWords &data, const uint8_t buf[SV_MEM_WIDTH_BYTES],
+    uint32_t src_word) const {
+  std::vector<uint8_t> unscrambled_data = ReadUnscrambled(buf, src_word);
+  // Strip integrity to give final result
+  Ecc32MemArea::ReadBufferWithIntegrity(data, &unscrambled_data[0], src_word);
+}
+
 uint32_t ScrambledEcc32MemArea::ToPhysAddr(uint32_t logical_addr) const {
   // Scramble logical address to get physical address
   return AddrBytesToInt(scramble_addr(AddrIntToBytes(logical_addr, addr_width_),
diff --git a/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.h b/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.h
index 98a5a1c..51cf247 100644
--- a/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.h
+++ b/hw/dv/verilator/cpp/scrambled_ecc32_mem_area.h
@@ -37,10 +37,17 @@
                    const std::vector<uint8_t> &data, size_t start_idx,
                    uint32_t dst_word) const override;
 
+  std::vector<uint8_t> ReadUnscrambled(const uint8_t buf[SV_MEM_WIDTH_BYTES],
+                                       uint32_t src_word) const;
+
   void ReadBuffer(std::vector<uint8_t> &data,
                   const uint8_t buf[SV_MEM_WIDTH_BYTES],
                   uint32_t src_word) const override;
 
+  void ReadBufferWithIntegrity(EccWords &data,
+                               const uint8_t buf[SV_MEM_WIDTH_BYTES],
+                               uint32_t src_word) const override;
+
   uint32_t ToPhysAddr(uint32_t logical_addr) const override;
 
   uint32_t GetPhysWidth() const;