[sw/mmio] Add _shadowed variants of write8 and write32 mmio functions

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/sw/device/lib/base/mmio.c b/sw/device/lib/base/mmio.c
index 22625b6..c129955 100644
--- a/sw/device/lib/base/mmio.c
+++ b/sw/device/lib/base/mmio.c
@@ -128,6 +128,10 @@
                                uint8_t value);
 extern void mmio_region_write32(mmio_region_t base, ptrdiff_t offset,
                                 uint32_t value);
+extern void mmio_region_write8_shadowed(mmio_region_t base, ptrdiff_t offset,
+                                        uint8_t value);
+extern void mmio_region_write32_shadowed(mmio_region_t base, ptrdiff_t offset,
+                                         uint32_t value);
 extern uint32_t mmio_region_read_mask32(mmio_region_t base, ptrdiff_t offset,
                                         uint32_t mask, uint32_t mask_index);
 extern bool mmio_region_get_bit32(mmio_region_t base, ptrdiff_t offset,
diff --git a/sw/device/lib/base/mmio.h b/sw/device/lib/base/mmio.h
index cf1aa11..b1ac07d 100644
--- a/sw/device/lib/base/mmio.h
+++ b/sw/device/lib/base/mmio.h
@@ -54,7 +54,9 @@
  * An mmio_region_t is an opaque handle to an MMIO region; it should only be
  * modified using the functions provided in this header.
  */
-typedef struct mmio_region { volatile void *base; } mmio_region_t;
+typedef struct mmio_region {
+  volatile void *base;
+} mmio_region_t;
 
 /**
  * Create a new `mmio_region_t` from the given address.
@@ -118,6 +120,23 @@
 }
 
 /**
+ * Writes an aligned uint8_t to the MMIO region `base` at the given byte
+ * offset via two subsequent write operations.
+ *
+ * This function is guaranteed to commit a write to memory, and will not be
+ * reordered with respect to other region manipulations.
+ *
+ * @param base the region to write to.
+ * @param offset the offset to write at, in bytes.
+ * @param value the value to write.
+ */
+inline void mmio_region_write8_shadowed(mmio_region_t base, ptrdiff_t offset,
+                                        uint8_t value) {
+  ((volatile uint8_t *)base.base)[offset / sizeof(uint8_t)] = value;
+  ((volatile uint8_t *)base.base)[offset / sizeof(uint8_t)] = value;
+}
+
+/**
  * Writes an aligned uint32_t to the MMIO region `base` at the given byte
  * offset.
  *
@@ -132,6 +151,23 @@
                                 uint32_t value) {
   ((volatile uint32_t *)base.base)[offset / sizeof(uint32_t)] = value;
 }
+
+/**
+ * Writes an aligned uint32_t to the MMIO region `base` at the given byte
+ * offset via two subsequent write operations.
+ *
+ * This function is guaranteed to commit a write to memory, and will not be
+ * reordered with respect to other region manipulations.
+ *
+ * @param base the region to write to.
+ * @param offset the offset to write at, in bytes.
+ * @param value the value to write.
+ */
+inline void mmio_region_write32_shadowed(mmio_region_t base, ptrdiff_t offset,
+                                         uint32_t value) {
+  ((volatile uint32_t *)base.base)[offset / sizeof(uint32_t)] = value;
+  ((volatile uint32_t *)base.base)[offset / sizeof(uint32_t)] = value;
+}
 #else   // MOCK_MMIO
 /**
  * "Instrumented" mmio_region_t.
@@ -141,7 +177,9 @@
  * version of `mmio_region_t`, which prevents users from being able to access
  * the pointer inside.
  */
-typedef struct mmio_region { void *mock; } mmio_region_t;
+typedef struct mmio_region {
+  void *mock;
+} mmio_region_t;
 
 /**
  * Stubbed-out read/write operations for overriding by a testing library.
@@ -155,6 +193,10 @@
 
 void mmio_region_write8(mmio_region_t base, ptrdiff_t offset, uint8_t value);
 void mmio_region_write32(mmio_region_t base, ptrdiff_t offset, uint32_t value);
+void mmio_region_write8_shadowed(mmio_region_t base, ptrdiff_t offset,
+                                 uint8_t value);
+void mmio_region_write32_shadowed(mmio_region_t base, ptrdiff_t offset,
+                                  uint32_t value);
 #endif  // MOCK_MMIO
 
 /**
diff --git a/sw/device/lib/base/testing/mock_mmio.cc b/sw/device/lib/base/testing/mock_mmio.cc
index 602e1a5..fbf397b 100644
--- a/sw/device/lib/base/testing/mock_mmio.cc
+++ b/sw/device/lib/base/testing/mock_mmio.cc
@@ -31,9 +31,23 @@
   dev->Write8(offset, value);
 }
 
+void mmio_region_write8_shadowed(mmio_region_t base, ptrdiff_t offset,
+                                 uint8_t value) {
+  auto *dev = static_cast<MockDevice *>(base.mock);
+  dev->Write8(offset, value);
+  dev->Write8(offset, value);
+}
+
 void mmio_region_write32(mmio_region_t base, ptrdiff_t offset, uint32_t value) {
   auto *dev = static_cast<MockDevice *>(base.mock);
   dev->Write32(offset, value);
 }
+
+void mmio_region_write32_shadowed(mmio_region_t base, ptrdiff_t offset,
+                                  uint32_t value) {
+  auto *dev = static_cast<MockDevice *>(base.mock);
+  dev->Write32(offset, value);
+  dev->Write32(offset, value);
+}
 }  // extern "C"
 }  // namespace mock_mmio
diff --git a/sw/device/lib/base/testing/mock_mmio.h b/sw/device/lib/base/testing/mock_mmio.h
index a9dff55..995f326 100644
--- a/sw/device/lib/base/testing/mock_mmio.h
+++ b/sw/device/lib/base/testing/mock_mmio.h
@@ -202,6 +202,22 @@
   EXPECT_WRITE8_AT(this->dev(), offset, __VA_ARGS__);
 
 /**
+ * Expect a shadowed write to the given offset with the given 8-bit value.
+ *
+ * The value may be given as an integer, a pointer to little-endian data,
+ * or a `std::initializer_list<BitField>`.
+ *
+ * This function is only available in tests using a fixture that derives
+ * `MmioTest`.
+ *
+ * This expectation is sequenced with all other `EXPECT_READ` and `EXPECT_WRITE`
+ * calls.
+ */
+#define EXPECT_WRITE8_SHADOWED(offset, ...) \
+  EXPECT_WRITE8(offset, __VA_ARGS__);       \
+  EXPECT_WRITE8(offset, __VA_ARGS__);
+
+/**
  * Expect a write to the given offset with the given 32-bit value.
  *
  * The value may be given as an integer, a pointer to little-endian data,
@@ -216,6 +232,22 @@
 #define EXPECT_WRITE32(offset, ...) \
   EXPECT_WRITE32_AT(this->dev(), offset, __VA_ARGS__);
 
+/**
+ * Expect a shadowed write to the given offset with the given 32-bit value.
+ *
+ * The value may be given as an integer, a pointer to little-endian data,
+ * or a `std::initializer_list<BitField>`.
+ *
+ * This function is only available in tests using a fixture that derives
+ * `MmioTest`.
+ *
+ * This expectation is sequenced with all other `EXPECT_READ` and `EXPECT_WRITE`
+ * calls.
+ */
+#define EXPECT_WRITE32_SHADOWED(offset, ...) \
+  EXPECT_WRITE32(offset, __VA_ARGS__);       \
+  EXPECT_WRITE32(offset, __VA_ARGS__);
+
 #define EXPECT_MASK_INTERNAL_(width, dev, off, ...)                        \
   do {                                                                     \
     auto &device = dev;                                                    \
diff --git a/sw/device/silicon_creator/lib/base/abs_mmio.c b/sw/device/silicon_creator/lib/base/abs_mmio.c
index bb982b0..b092f86 100644
--- a/sw/device/silicon_creator/lib/base/abs_mmio.c
+++ b/sw/device/silicon_creator/lib/base/abs_mmio.c
@@ -8,5 +8,7 @@
 // header a link location.
 extern uint8_t abs_mmio_read8(uint32_t addr);
 extern void abs_mmio_write8(uint32_t addr, uint8_t value);
+extern void abs_mmio_write8_shadowed(uint32_t addr, uint8_t value);
 extern uint32_t abs_mmio_read32(uint32_t addr);
 extern void abs_mmio_write32(uint32_t addr, uint32_t value);
+extern void abs_mmio_write32_shadowed(uint32_t addr, uint32_t value);
diff --git a/sw/device/silicon_creator/lib/base/abs_mmio.h b/sw/device/silicon_creator/lib/base/abs_mmio.h
index 542a845..b87490c 100644
--- a/sw/device/silicon_creator/lib/base/abs_mmio.h
+++ b/sw/device/silicon_creator/lib/base/abs_mmio.h
@@ -59,6 +59,18 @@
 }
 
 /**
+ * Writes uint8_t to the MMIO `addr` via
+ * two subsequent write operations.
+ *
+ * @param addr the address to write to.
+ * @param value the value to write.
+ */
+inline void abs_mmio_write8_shadowed(uint32_t addr, uint8_t value) {
+  *((volatile uint8_t *)addr) = value;
+  *((volatile uint8_t *)addr) = value;
+}
+
+/**
  * Reads an aligned uint32_t from MMIO `addr`.
  *
  * @param addr the address to read from.
@@ -70,7 +82,7 @@
 }
 
 /**
- * Writes an aligned uint32_t to the MMIO region `addr`.
+ * Writes an aligned uint32_t to the MMIO `addr`.
  *
  * @param addr the address to write to.
  * @param value the value to write.
@@ -79,12 +91,26 @@
   *((volatile uint32_t *)addr) = value;
 }
 
+/**
+ * Writes an aligned uint32_t to the MMIO `addr` via
+ * two subsequent write operations.
+ *
+ * @param addr the address to write to.
+ * @param value the value to write.
+ */
+inline void abs_mmio_write32_shadowed(uint32_t addr, uint32_t value) {
+  *((volatile uint32_t *)addr) = value;
+  *((volatile uint32_t *)addr) = value;
+}
+
 #else  // MOCK_ABS_MMIO
 
 extern uint8_t abs_mmio_read8(uint32_t addr);
 extern void abs_mmio_write8(uint32_t addr, uint8_t value);
+extern void abs_mmio_write8_shadowed(uint32_t addr, uint8_t value);
 extern uint32_t abs_mmio_read32(uint32_t addr);
 extern void abs_mmio_write32(uint32_t addr, uint32_t value);
+extern void abs_mmio_write32_shadowed(uint32_t addr, uint32_t value);
 
 #endif  // MOCK_ABS_MMIO
 
diff --git a/sw/device/silicon_creator/lib/base/mock_abs_mmio.h b/sw/device/silicon_creator/lib/base/mock_abs_mmio.h
index 6cbd800..a470e81 100644
--- a/sw/device/silicon_creator/lib/base/mock_abs_mmio.h
+++ b/sw/device/silicon_creator/lib/base/mock_abs_mmio.h
@@ -57,6 +57,22 @@
   EXPECT_CALL(mmio, Write8(addr, mock_mmio::ToInt<uint8_t>(__VA_ARGS__)));
 
 /**
+ * Expect a shadowed write to the given offset with the given 8-bit value.
+ *
+ * The value may be given as an integer, a pointer to little-endian data,
+ * or a `std::initializer_list<BitField>`.
+ *
+ * This function is only available in tests using a fixture that derives
+ * `MmioTest`.
+ *
+ * This expectation is sequenced with all other `EXPECT_READ` and `EXPECT_WRITE`
+ * calls.
+ */
+#define EXPECT_ABS_WRITE8_SHADOWED(mmio, addr, ...)                        \
+  EXPECT_CALL(mmio, Write8(addr, mock_mmio::ToInt<uint8_t>(__VA_ARGS__))); \
+  EXPECT_CALL(mmio, Write8(addr, mock_mmio::ToInt<uint8_t>(__VA_ARGS__)));
+
+/**
  * Expect a read to the device `dev` at the given offset, returning the given
  * 32-bit value.
  *
@@ -85,6 +101,22 @@
 #define EXPECT_ABS_WRITE32(mmio, addr, ...) \
   EXPECT_CALL(mmio, Write32(addr, mock_mmio::ToInt<uint32_t>(__VA_ARGS__)));
 
+/**
+ * Expect a shadowed write to the given offset with the given 32-bit value.
+ *
+ * The value may be given as an integer, a pointer to little-endian data,
+ * or a `std::initializer_list<BitField>`.
+ *
+ * This function is only available in tests using a fixture that derives
+ * `MmioTest`.
+ *
+ * This expectation is sequenced with all other `EXPECT_READ` and `EXPECT_WRITE`
+ * calls.
+ */
+#define EXPECT_ABS_WRITE32_SHADOWED(mmio, addr, ...)                         \
+  EXPECT_CALL(mmio, Write32(addr, mock_mmio::ToInt<uint32_t>(__VA_ARGS__))); \
+  EXPECT_CALL(mmio, Write32(addr, mock_mmio::ToInt<uint32_t>(__VA_ARGS__)));
+
 extern "C" {
 
 uint8_t abs_mmio_read8(uint32_t addr) {
@@ -95,6 +127,11 @@
   return MockAbsMmio::Instance().Write8(addr, value);
 }
 
+void abs_mmio_write8_shadowed(uint32_t addr, uint8_t value) {
+  MockAbsMmio::Instance().Write8(addr, value);
+  return MockAbsMmio::Instance().Write8(addr, value);
+}
+
 uint32_t abs_mmio_read32(uint32_t addr) {
   return MockAbsMmio::Instance().Read32(addr);
 }
@@ -103,6 +140,11 @@
   return MockAbsMmio::Instance().Write32(addr, value);
 }
 
+void abs_mmio_write32_shadowed(uint32_t addr, uint32_t value) {
+  MockAbsMmio::Instance().Write32(addr, value);
+  return MockAbsMmio::Instance().Write32(addr, value);
+}
+
 }  // extern "C"
 }  // namespace mask_rom_test