[sw/silicon_creator] Add unit tests to ePMP library

Signed-off-by: Michael Munday <mike.munday@lowrisc.org>
diff --git a/sw/device/silicon_creator/lib/epmp_unittest.cc b/sw/device/silicon_creator/lib/epmp_unittest.cc
new file mode 100644
index 0000000..5c039ec
--- /dev/null
+++ b/sw/device/silicon_creator/lib/epmp_unittest.cc
@@ -0,0 +1,372 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/epmp.h"
+
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "sw/device/lib/base/bitfield.h"
+#include "sw/device/lib/base/csr.h"
+#include "sw/device/lib/base/testing/mock_csr.h"
+
+namespace epmp_unittest {
+using ::mock_csr::CsrTest;
+using ::testing::Test;
+
+/**
+ * Representation of the hardware PMP control register state.
+ */
+struct PmpCsrs {
+  /**
+   * The unpacked PMP configuration (`pmpNcfg`) registers, one entry for each
+   * PMP entry.
+   */
+  std::array<uint8_t, kEpmpNumRegions> pmpcfg{};
+
+  /**
+   * The PMP address registers, one for each PMP entry.
+   */
+  std::array<uint32_t, kEpmpNumRegions> pmpaddr{};
+
+  /**
+   * The Machine Security Configuration register.
+   */
+  uint64_t mseccfg = 0;
+
+  /**
+   * Pack the individual (`pmpNcfg`) PMP entry configuration register
+   * values into hardware register (`pmpcfgN`) values.
+   *
+   * @returns An array representing the packed `pmpcfgN` values.
+   */
+  std::array<uint32_t, kEpmpNumRegions / 4> PackCfg() const {
+    // There are four 8-bit `pmpNcfg` values packed into each `pmpcfgN`
+    // register.
+    std::array<uint32_t, kEpmpNumRegions / 4> packed{};
+    for (int i = 0; i < pmpcfg.size(); ++i) {
+      bitfield_field32_t field = {.mask = 0xff,
+                                  .index = (static_cast<uint32_t>(i) % 4) * 8};
+      packed.at(i / 4) = bitfield_field32_write(
+          packed.at(i / 4), field, static_cast<uint32_t>(pmpcfg.at(i)));
+    }
+    return packed;
+  }
+
+  /**
+   * Generate a state that is a snapshot of the current PMP control register
+   * values.
+   *
+   * @returns An `epmp_state_t` value.
+   */
+  epmp_state_t State() const {
+    epmp_state_t state;
+    auto packed = PackCfg();
+    for (int i = 0; i < packed.size(); ++i) {
+      state.pmpcfg[i] = packed.at(i);
+    }
+    for (int i = 0; i < pmpaddr.size(); ++i) {
+      state.pmpaddr[i] = pmpaddr.at(i);
+    }
+    state.mseccfg = static_cast<uint32_t>(mseccfg);
+    return state;
+  }
+};
+
+/**
+ * Map from PMP config register index to CSR_REG_PMPCFGN value.
+ */
+const std::vector<uint32_t> kPmpcfgMap = {
+    CSR_REG_PMPCFG0,
+    CSR_REG_PMPCFG1,
+    CSR_REG_PMPCFG2,
+    CSR_REG_PMPCFG3,
+};
+
+/**
+ * Map from PMP address register index to CSR_REG_PMPADDRN value.
+ */
+const std::vector<uint32_t> kPmpaddrMap = {
+    CSR_REG_PMPADDR0,  CSR_REG_PMPADDR1,  CSR_REG_PMPADDR2,  CSR_REG_PMPADDR3,
+    CSR_REG_PMPADDR4,  CSR_REG_PMPADDR5,  CSR_REG_PMPADDR6,  CSR_REG_PMPADDR7,
+    CSR_REG_PMPADDR8,  CSR_REG_PMPADDR9,  CSR_REG_PMPADDR10, CSR_REG_PMPADDR11,
+    CSR_REG_PMPADDR12, CSR_REG_PMPADDR13, CSR_REG_PMPADDR14, CSR_REG_PMPADDR15,
+};
+
+/**
+ * Helper function to call EXPECT_CSR_READ on all pmpcfg registers
+ * returning the values provided.
+ */
+static void ExpectPmpcfgRead(const PmpCsrs &expect) {
+  auto packed = expect.PackCfg();
+  for (int i = 0; i < packed.size(); ++i) {
+    EXPECT_CSR_READ(kPmpcfgMap.at(i), packed.at(i));
+  }
+}
+
+/**
+ * Helper function to call EXPECT_CSR_READ on all pmpaddr registers
+ * returning the values provided.
+ */
+static void ExpectPmpaddrRead(const PmpCsrs &expect) {
+  for (int i = 0; i < expect.pmpaddr.size(); ++i) {
+    EXPECT_CSR_READ(kPmpaddrMap.at(i), expect.pmpaddr.at(i));
+  }
+}
+
+/**
+ * Helper function to call EXPECT_CSR_READ on the mseccfg register
+ * returning the value provided.
+ */
+static void ExpectMseccfgRead(const PmpCsrs &expect) {
+  EXPECT_CSR_READ(CSR_REG_MSECCFG, static_cast<uint32_t>(expect.mseccfg));
+  EXPECT_CSR_READ(CSR_REG_MSECCFGH,
+                  static_cast<uint32_t>(expect.mseccfg >> 32));
+}
+
+/**
+ * Helper function to call EXPECT_CSR_READ as necessary for an
+ * internal `read_state` call with the expected values provided.
+ */
+static void ExpectReadState(const PmpCsrs &expect) {
+  ExpectPmpaddrRead(expect);
+  ExpectPmpcfgRead(expect);
+  ExpectMseccfgRead(expect);
+}
+
+/**
+ * Encode a region using the NAPOT address mode. This does not
+ * do any validation of the values provided.
+ *
+ * Note: other address modes just require the address to be right
+ * shifted by 2.
+ */
+static uint32_t napot(epmp_region_t region) {
+  return (region.start >> 2) | ((region.end - region.start - 1) >> 3);
+}
+
+class EpmpTest : public CsrTest {
+ protected:
+  /**
+   * Default reset value for the PMP CSRs.
+   */
+  const PmpCsrs reset_ = PmpCsrs{};
+  epmp_state_t state_ = reset_.State();
+};
+
+class EpmpCheckTest : public Test, public EpmpTest {};
+
+TEST_F(EpmpCheckTest, Default) {
+  // Check CSRs are set as expected.
+  ExpectReadState(reset_);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+TEST_F(EpmpCheckTest, ErrorPmpaddr) {
+  // Inject an error.
+  PmpCsrs bad = reset_;
+  bad.pmpaddr.at(15) ^= 1 << 31;
+
+  // Check should fail.
+  ExpectReadState(bad);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorEpmpBadCheck);
+}
+
+TEST_F(EpmpCheckTest, ErrorPmpcfg) {
+  // Inject an error.
+  PmpCsrs bad = reset_;
+  bad.pmpcfg.at(0) ^= 1;
+
+  // Check should fail.
+  ExpectReadState(bad);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorEpmpBadCheck);
+}
+
+TEST_F(EpmpCheckTest, ErrorMseccfg) {
+  // Inject an error.
+  PmpCsrs bad = reset_;
+  bad.mseccfg ^= 1;
+
+  // Check should fail.
+  ExpectReadState(bad);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorEpmpBadCheck);
+}
+
+class EpmpTorTest : public Test, public EpmpTest {};
+
+TEST_F(EpmpTorTest, Entry0) {
+  // Region for entry.
+  epmp_region_t region = {
+      .start = 0,
+      .end = 0,
+  };
+
+  // Configure registers for entry 0 (base address implicitly 0).
+  PmpCsrs csrs = reset_;
+  csrs.pmpcfg[0] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R;
+  csrs.pmpaddr[0] = region.end >> 2;
+
+  // Configure same entry in state.
+  epmp_state_configure_tor(&state_, 0, region, kEpmpPermLockedReadOnly);
+
+  // Check CSRs match.
+  ExpectReadState(csrs);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+TEST_F(EpmpTorTest, Entry1) {
+  // Region for entry.
+  epmp_region_t region = {
+      .start = 0x120,
+      .end = 0x140,
+  };
+
+  // Configure registers for entry 1, with base address in entry 0.
+  PmpCsrs csrs = reset_;
+  csrs.pmpaddr[0] = region.start >> 2;
+  csrs.pmpcfg[1] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R;
+  csrs.pmpaddr[1] = region.end >> 2;
+
+  // Configure same entry in state.
+  epmp_state_configure_tor(&state_, 1, region, kEpmpPermLockedReadOnly);
+
+  // Check CSRs match.
+  ExpectReadState(csrs);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+TEST_F(EpmpTorTest, Entry15) {
+  // Region for entry.
+  epmp_region_t region = {
+      .start = 0x120,
+      .end = 0x140,
+  };
+
+  // Configure registers for entry 15, with base address in entry 14.
+  PmpCsrs csrs = reset_;
+  csrs.pmpaddr[14] = region.start >> 2;
+  csrs.pmpcfg[15] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R;
+  csrs.pmpaddr[15] = region.end >> 2;
+
+  // Configure same entry in state.
+  epmp_state_configure_tor(&state_, 15, region, kEpmpPermLockedReadOnly);
+
+  // Check CSRs match.
+  ExpectReadState(csrs);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+TEST_F(EpmpTorTest, SharedAddress) {
+  // Adjacent regions for entries.
+  epmp_region_t region1 = {
+      .start = 0x120,
+      .end = 0x140,
+  };
+  epmp_region_t region2 = {
+      .start = 0x140,
+      .end = 0x180,
+  };
+
+  // Configure registers for entries 1 and 2, with base address in entry 0.
+  PmpCsrs csrs = reset_;
+  csrs.pmpaddr[0] = region1.start >> 2;
+  csrs.pmpcfg[1] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R | EPMP_CFG_X;
+  csrs.pmpaddr[1] = region1.end >> 2;
+  csrs.pmpcfg[2] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R;
+  csrs.pmpaddr[2] = region2.end >> 2;
+
+  // Configure same entry in state.
+  epmp_state_configure_tor(&state_, 1, region1, kEpmpPermLockedReadExecute);
+  epmp_state_configure_tor(&state_, 2, region2, kEpmpPermLockedReadOnly);
+
+  // Check CSRs match.
+  ExpectReadState(csrs);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+class EpmpNa4Test : public Test, public EpmpTest {};
+
+TEST_F(EpmpNa4Test, Entry0) {
+  // Region for entry.
+  epmp_region_t region = {
+      .start = 0xF4,
+      .end = 0xF8,
+  };
+
+  // Configure registers for entry 0.
+  PmpCsrs csrs = reset_;
+  csrs.pmpcfg[0] = EPMP_CFG_L | EPMP_CFG_A_NA4 | EPMP_CFG_R | EPMP_CFG_X;
+  csrs.pmpaddr[0] = region.start >> 2;
+
+  // Configure same entry in state.
+  epmp_state_configure_na4(&state_, 0, region, kEpmpPermLockedReadExecute);
+
+  // Check CSRs match.
+  ExpectReadState(csrs);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+TEST_F(EpmpNa4Test, Entry15) {
+  // Region for entry.
+  epmp_region_t region = {
+      .start = 0x0,
+      .end = 0x4,
+  };
+
+  // Configure registers for entry 15.
+  PmpCsrs csrs = reset_;
+  csrs.pmpcfg[15] = EPMP_CFG_L | EPMP_CFG_A_NA4 | EPMP_CFG_R | EPMP_CFG_W;
+  csrs.pmpaddr[15] = region.start >> 2;
+
+  // Configure same entry in state.
+  epmp_state_configure_na4(&state_, 15, region, kEpmpPermLockedReadWrite);
+
+  // Check CSRs match.
+  ExpectReadState(csrs);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+class EpmpNapotTest : public Test, public EpmpTest {};
+
+TEST_F(EpmpNapotTest, Entry0) {
+  // Region for entry.
+  epmp_region_t region = {
+      .start = 0xFF00,
+      .end = 0xFF10,
+  };
+
+  // Configure registers for entry 0.
+  PmpCsrs csrs = reset_;
+  csrs.pmpcfg[0] = EPMP_CFG_L | EPMP_CFG_A_NAPOT | EPMP_CFG_R | EPMP_CFG_X;
+  csrs.pmpaddr[0] = napot(region);
+
+  // Configure same entry in state.
+  epmp_state_configure_napot(&state_, 0, region, kEpmpPermLockedReadExecute);
+
+  // Check CSRs match.
+  ExpectReadState(csrs);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+TEST_F(EpmpNapotTest, Entry15) {
+  // Region for entry.
+  epmp_region_t region = {
+      .start = 0x0,
+      .end = 0x8,
+  };
+
+  // Configure registers for entry 15.
+  PmpCsrs csrs = reset_;
+  csrs.pmpcfg[15] = EPMP_CFG_L | EPMP_CFG_A_NAPOT | EPMP_CFG_R | EPMP_CFG_W;
+  csrs.pmpaddr[15] = napot(region);
+
+  // Configure same entry in state.
+  epmp_state_configure_napot(&state_, 15, region, kEpmpPermLockedReadWrite);
+
+  // Check CSRs match.
+  ExpectReadState(csrs);
+  EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
+}
+
+}  // namespace epmp_unittest
diff --git a/sw/device/silicon_creator/lib/meson.build b/sw/device/silicon_creator/lib/meson.build
index ce9621c..75f8150 100644
--- a/sw/device/silicon_creator/lib/meson.build
+++ b/sw/device/silicon_creator/lib/meson.build
@@ -176,6 +176,24 @@
   suite: 'mask_rom',
 )
 
+test('sw_silicon_creator_lib_epmp_unittest', executable(
+    'sw_silicon_creator_lib_epmp_unittest',
+    sources: [
+      'epmp_unittest.cc',
+      'epmp.c',
+    ],
+    dependencies: [
+      sw_vendor_gtest,
+      sw_lib_base_testing_mock_csr,
+      sw_lib_testing_bitfield,
+    ],
+    native: true,
+    c_args: ['-DMOCK_CSR'],
+    cpp_args: ['-DMOCK_CSR'],
+    ),
+  suite: 'mask_rom',
+)
+
 sw_silicon_creator_lib_sigverify_functest = declare_dependency(
   link_with: static_library(
     'sw_silicon_creator_lib_sigverify_functest',