[dpi] Make an "ECC32" flavour of MemArea
This will support memories that use a (39,32)-SECDED code. At the
moment, it doesn't actually add the check bits (because we don't check
them in our implementation yet), but it does lay everything out
correctly.
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
new file mode 100644
index 0000000..03ea8c2
--- /dev/null
+++ b/hw/dv/verilator/cpp/ecc32_mem_area.cc
@@ -0,0 +1,124 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "ecc32_mem_area.h"
+
+#include <cassert>
+#include <cstring>
+#include <stdexcept>
+
+Ecc32MemArea::Ecc32MemArea(const std::string &scope, uint32_t size,
+ uint32_t width_32)
+ : MemArea(scope, size, 4 * width_32) {
+ // Check that multiplying by 4 didn't discard a bit
+ assert(4 * width_32 > width_32);
+
+ // No need to worry about ranges here: we've checked in the base class that
+ // width_byte isn't too big.
+ uint32_t phy_width_bits = 39 * width_32;
+
+ // This is a stronger check than the one in the base class (which
+ // makes sure there's enough space for the un-expanded memory width)
+ assert(phy_width_bits <= SV_MEM_WIDTH_BITS);
+}
+
+void Ecc32MemArea::LoadVmem(const std::string &path) const {
+ throw std::runtime_error(
+ "vmem files are not supported for memories with ECC bits");
+}
+
+void Ecc32MemArea::WriteBuffer(uint8_t buf[SV_MEM_WIDTH_BYTES],
+ const std::vector<uint8_t> &data,
+ size_t start_idx) const {
+ int log_width_32 = width_byte_ / 4;
+ int phy_width_bits = 39 * log_width_32;
+ int phy_width_bytes = (phy_width_bits + 7) / 8;
+
+ // Start by collecting our width_byte_ input bytes into (width_byte_ / 4)
+ // 32-bit groupings and adding check bits. (Eventually, we'll be adding
+ // proper ECC check bits here but, since we're not checking yet, let's
+ // zero-pad for now).
+ //
+ // TODO: Add proper ECC check bits here!
+ struct expanded_t {
+ uint8_t bytes[5];
+ };
+
+ std::vector<expanded_t> expanded(log_width_32);
+ for (int i = 0; i < log_width_32; ++i) {
+ // Store things little-endian, so the "real bits" go in bytes 0 to 3 and
+ // the check bits go in byte 4. Bytes 5 to 7 are zero.
+ expanded_t next;
+ memcpy(next.bytes, &data[start_idx + 4 * i], 4);
+ next.bytes[4] = 0;
+ expanded[i] = next;
+ }
+
+ // Now write to buf, pulling 39 bits from each element of in_64.
+ for (int i = 0; i < phy_width_bytes; ++i) {
+ uint8_t acc = 0;
+ int out_lsb = 0;
+
+ int in_word_idx = (8 * i) / 39;
+ int in_word_lsb = (8 * i) % 39;
+
+ int bits_left = 8;
+ while (bits_left) {
+ int in_byte_idx = in_word_lsb / 8;
+ int in_byte_lsb = in_word_lsb % 8;
+
+ int byte_width = (in_byte_idx == 4) ? 7 : 8;
+ int bits_at_byte = std::min(byte_width - in_byte_lsb, bits_left);
+
+ uint8_t in_data = expanded[in_word_idx].bytes[in_byte_idx] >> in_byte_lsb;
+ uint8_t in_mask = (((uint32_t)1 << bits_at_byte) - 1) & 0xff;
+
+ acc |= (in_data & in_mask) << out_lsb;
+
+ out_lsb += bits_at_byte;
+ if (in_byte_idx == 4) {
+ ++in_word_idx;
+ in_word_lsb = 0;
+ } else {
+ in_word_lsb += bits_at_byte;
+ }
+
+ bits_left -= bits_at_byte;
+ }
+ buf[i] = acc;
+ }
+}
+
+void Ecc32MemArea::ReadBuffer(std::vector<uint8_t> &data,
+ const uint8_t buf[SV_MEM_WIDTH_BYTES]) const {
+ for (int i = 0; i < width_byte_; ++i) {
+ int in_word = i / 4;
+
+ int in_idx = (7 * in_word + 8 * i) / 8;
+ int in_lsb = (7 * in_word + 8 * i) % 8;
+
+ uint8_t acc = 0;
+
+ int bits_left = 8;
+ int out_lsb = 0;
+
+ while (bits_left) {
+ uint8_t in_data = buf[in_idx] >> in_lsb;
+
+ int bits_at_idx = std::min(8 - in_lsb, bits_left);
+
+ // The mask for the bits to take from in_data.
+ uint8_t in_mask = ((1u << bits_at_idx) - 1) & 0xff;
+
+ acc |= (in_data & in_mask) << out_lsb;
+
+ in_lsb = 0;
+ out_lsb += bits_at_idx;
+ bits_left -= bits_at_idx;
+ ++in_idx;
+ }
+
+ data.push_back(acc);
+ }
+}
diff --git a/hw/dv/verilator/cpp/ecc32_mem_area.h b/hw/dv/verilator/cpp/ecc32_mem_area.h
new file mode 100644
index 0000000..b0ddc10
--- /dev/null
+++ b/hw/dv/verilator/cpp/ecc32_mem_area.h
@@ -0,0 +1,36 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_HW_DV_VERILATOR_CPP_ECC32_MEM_AREA_H_
+#define OPENTITAN_HW_DV_VERILATOR_CPP_ECC32_MEM_AREA_H_
+
+#include "mem_area.h"
+
+/**
+ * A memory that implements 32-bit ECC, storing 39 = 32 + 7 bits of physical
+ * data for each 32 bits of logical data.
+ */
+class Ecc32MemArea : public MemArea {
+ public:
+ /**
+ * Constructor
+ *
+ * Create an Ecc32MemArea that will connect to a SystemVerilog memory at
+ * scope. It is size words long. Each memory word is 4 * width_32 bytes wide
+ * in the address space and 39 * width_32 bits wide in the physical memory.
+ */
+ Ecc32MemArea(const std::string &scope, uint32_t size, uint32_t width_32);
+
+ void LoadVmem(const std::string &path) const override;
+
+ private:
+ void WriteBuffer(uint8_t buf[SV_MEM_WIDTH_BYTES],
+ const std::vector<uint8_t> &data,
+ size_t start_idx) const override;
+
+ void ReadBuffer(std::vector<uint8_t> &data,
+ const uint8_t buf[SV_MEM_WIDTH_BYTES]) const override;
+};
+
+#endif // OPENTITAN_HW_DV_VERILATOR_CPP_ECC32_MEM_AREA_H_
diff --git a/hw/dv/verilator/memutil_dpi.core b/hw/dv/verilator/memutil_dpi.core
index ce8492a..e3be600 100644
--- a/hw/dv/verilator/memutil_dpi.core
+++ b/hw/dv/verilator/memutil_dpi.core
@@ -10,6 +10,8 @@
files:
- cpp/dpi_memutil.cc
- cpp/dpi_memutil.h: { is_include_file: true }
+ - cpp/ecc32_mem_area.cc
+ - cpp/ecc32_mem_area.h: { is_include_file: true }
- cpp/mem_area.cc
- cpp/mem_area.h: { is_include_file: true }
- cpp/ranged_map.h: { is_include_file: true }