From 740db96b2c7f4387a605fe65107ef192e2bf1e03 Mon Sep 17 00:00:00 2001 From: Patrick Kappl Date: Sun, 15 Sep 2024 14:10:47 +0000 Subject: [PATCH] Add cache to `fram::RingArray` --- Sts1CobcSw/Periphery/FramRingArray.hpp | 16 +- Sts1CobcSw/Periphery/FramRingArray.ipp | 142 ++++++++++++------ Tests/UnitTests/FramRingArray.test.cpp | 199 ++++++++++++++++--------- 3 files changed, 238 insertions(+), 119 deletions(-) diff --git a/Sts1CobcSw/Periphery/FramRingArray.hpp b/Sts1CobcSw/Periphery/FramRingArray.hpp index 85085318..a27ba8fd 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.hpp +++ b/Sts1CobcSw/Periphery/FramRingArray.hpp @@ -14,6 +14,7 @@ #include +#include #include #include @@ -25,7 +26,7 @@ // TODO: Move to FramSections namespace sts1cobcsw { -template +template requires(serialSize > 0) class RingArray { @@ -33,7 +34,8 @@ class RingArray using ValueType = T; static constexpr auto section = ringArraySection; - [[nodiscard]] static constexpr auto Capacity() -> std::size_t; + [[nodiscard]] static constexpr auto FramCapacity() -> std::size_t; + [[nodiscard]] static constexpr auto CacheCapacity() -> std::size_t; [[nodiscard]] static auto Size() -> std::size_t; [[nodiscard]] static auto Get(std::size_t index) -> T; [[nodiscard]] static auto Front() -> T; @@ -53,15 +55,17 @@ class RingArray PersistentVariables(), PersistentVariableInfo<"iBegin", std::size_t>, PersistentVariableInfo<"iEnd", std::size_t>>{}; - // We reduce the capacity by one to distinguish between an empty and a full ring. See PushBack() - // for details. - static constexpr auto capacity = subsections.template Get<"array">().size / elementSize - 1; + // We reduce the FRAM capacity by one to distinguish between an empty and a full ring. See + // PushBack() for details. + static constexpr auto framCapacity = subsections.template Get<"array">().size / elementSize - 1; static constexpr auto spiTimeout = elementSize < 300U ? 1 * ms : value_of(elementSize) * 3 * us; - using RingIndex = etl::cyclic_value; + + using RingIndex = etl::cyclic_value; static RingIndex iEnd; static RingIndex iBegin; + static etl::circular_buffer, nCachedElements> cache; static RODOS::Semaphore semaphore; static auto LoadIndexes() -> void; diff --git a/Sts1CobcSw/Periphery/FramRingArray.ipp b/Sts1CobcSw/Periphery/FramRingArray.ipp index 34059c60..06a52db1 100644 --- a/Sts1CobcSw/Periphery/FramRingArray.ipp +++ b/Sts1CobcSw/Periphery/FramRingArray.ipp @@ -8,74 +8,92 @@ namespace sts1cobcsw { -template - requires(serialSize > 0) -typename RingArray::RingIndex RingArray::iEnd = - RingIndex{}; +using fram::framIsWorking; -template +template requires(serialSize > 0) -typename RingArray::RingIndex RingArray::iBegin = - RingIndex{}; - - -template - requires(serialSize > 0) -RODOS::Semaphore RingArray::semaphore = RODOS::Semaphore{}; +inline constexpr auto RingArray::FramCapacity() -> std::size_t +{ + return framCapacity; +} -template +template requires(serialSize > 0) -inline constexpr auto RingArray::Capacity() -> std::size_t +inline constexpr auto RingArray::CacheCapacity() + -> std::size_t { - return capacity; + return nCachedElements; } -template +template requires(serialSize > 0) -auto RingArray::Size() -> std::size_t +auto RingArray::Size() -> std::size_t { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + if(not framIsWorking.Load()) + { + return cache.size(); + } LoadIndexes(); return ComputeSize(); } -template +template requires(serialSize > 0) -auto RingArray::Get(std::size_t index) -> T +auto RingArray::Get(std::size_t index) -> T { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) - LoadIndexes(); - auto size = ComputeSize(); + auto size = []() + { + if(framIsWorking.Load()) + { + LoadIndexes(); + return ComputeSize(); + } + return cache.size(); + }(); if(index >= size) { DEBUG_PRINT("Index out of bounds in RingArray::Get: %u >= %u\n", index, size); index = size - 1; } + if(not framIsWorking.Load()) + { + return Deserialize(cache[index]); + } auto i = iBegin; i.advance(static_cast(index)); return ReadElement(i); } -template +template requires(serialSize > 0) -auto RingArray::Front() -> T +auto RingArray::Front() -> T { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + if(not framIsWorking.Load()) + { + return Deserialize(cache.front()); + } LoadIndexes(); return ReadElement(iBegin); } -template +template requires(serialSize > 0) -auto RingArray::Back() -> T +auto RingArray::Back() -> T { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + if(not framIsWorking.Load()) + { + return Deserialize(cache.back()); + } LoadIndexes(); auto i = iEnd; i--; @@ -83,29 +101,45 @@ auto RingArray::Back() -> T } -template +template requires(serialSize > 0) -auto RingArray::Set(std::size_t index, T const & t) -> void +auto RingArray::Set(std::size_t index, T const & t) -> void { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) - LoadIndexes(); - auto size = ComputeSize(); + auto size = []() + { + if(framIsWorking.Load()) + { + LoadIndexes(); + return ComputeSize(); + } + return cache.size(); + }(); if(index >= size) { DEBUG_PRINT("Index out of bounds in RingArray::Set: %u >= %u\n", index, size); return; } + if(not framIsWorking.Load()) + { + cache[index] = Serialize(t); + } auto i = iBegin; i.advance(static_cast(index)); WriteElement(i, t); } -template +template requires(serialSize > 0) -auto RingArray::PushBack(T const & t) -> void +auto RingArray::PushBack(T const & t) -> void { auto protector = RODOS::ScopeProtector(&semaphore); // NOLINT(google-readability-casting) + if(not framIsWorking.Load()) + { + cache.push(Serialize(t)); + return; + } LoadIndexes(); WriteElement(iEnd, t); // We reduce the capacity by one to distinguish between an empty and a full ring: iEnd == iBegin @@ -119,48 +153,72 @@ auto RingArray::PushBack(T const & t) -> void } -template +template + requires(serialSize > 0) +typename RingArray::RingIndex + RingArray::iEnd = {}; + + +template + requires(serialSize > 0) +typename RingArray::RingIndex + RingArray::iBegin = {}; + + +template + requires(serialSize > 0) +etl::circular_buffer, + nCachedElements> RingArray::cache = {}; + + +template + requires(serialSize > 0) +RODOS::Semaphore RingArray::semaphore = {}; + + +template requires(serialSize > 0) -auto RingArray::LoadIndexes() -> void +auto RingArray::LoadIndexes() -> void { iBegin.set(persistentIndexes.template Load<"iBegin">()); iEnd.set(persistentIndexes.template Load<"iEnd">()); } -template +template requires(serialSize > 0) -auto RingArray::StoreIndexes() -> void +auto RingArray::StoreIndexes() -> void { persistentIndexes.template Store<"iBegin">(iBegin.get()); persistentIndexes.template Store<"iEnd">(iEnd.get()); } -template +template requires(serialSize > 0) -auto RingArray::ComputeSize() -> std::size_t +auto RingArray::ComputeSize() -> std::size_t { if(iEnd.get() >= iBegin.get()) { return iEnd.get() - iBegin.get(); } - return capacity + 1 + iEnd.get() - iBegin.get(); + return framCapacity + 1 + iEnd.get() - iBegin.get(); } -template +template requires(serialSize > 0) -auto RingArray::ReadElement(RingIndex index) -> T +auto RingArray::ReadElement(RingIndex index) -> T { auto address = subsections.template Get<"array">().begin + index.get() * elementSize; return Deserialize(fram::ReadFrom>(address, value_of(spiTimeout))); } -template +template requires(serialSize > 0) -auto RingArray::WriteElement(RingIndex index, T const & t) -> void +auto RingArray::WriteElement(RingIndex index, T const & t) + -> void { auto address = subsections.template Get<"array">().begin + index.get() * elementSize; fram::WriteTo(address, Span(Serialize(t)), value_of(spiTimeout)); diff --git a/Tests/UnitTests/FramRingArray.test.cpp b/Tests/UnitTests/FramRingArray.test.cpp index da4f4c9a..babe1dba 100644 --- a/Tests/UnitTests/FramRingArray.test.cpp +++ b/Tests/UnitTests/FramRingArray.test.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -21,10 +22,11 @@ using sts1cobcsw::operator""_b; // NOLINT(misc-unused-using-decls) inline constexpr auto section = sts1cobcsw::Section{}; -inline constexpr auto charRingArray = sts1cobcsw::RingArray{}; +inline constexpr auto charRingArray = sts1cobcsw::RingArray{}; static_assert(std::is_same_v); -static_assert(charRingArray.Capacity() == 3); +static_assert(charRingArray.FramCapacity() == 3); +static_assert(charRingArray.CacheCapacity() == 2); static_assert(charRingArray.section.begin == fram::Address(0)); static_assert(charRingArray.section.end == fram::Address(28)); static_assert(charRingArray.section.size == fram::Size(28)); @@ -36,75 +38,130 @@ auto RunUnitTest() -> void fram::ram::SetAllDoFunctions(); fram::Initialize(); - memory.fill(0x00_b); - - Require(charRingArray.Size() == 0); - - // Trying to set an element in an empty ring prints a debug message and does not set anything - charRingArray.Set(0, 11); - Require(std::all_of(memory.begin(), memory.end(), [](auto x) { return x == 0_b; })); - - charRingArray.PushBack(11); - Require(charRingArray.Size() == 1); - Require(charRingArray.Front() == 11); - Require(charRingArray.Back() == 11); - - charRingArray.PushBack(12); - Require(charRingArray.Size() == 2); - Require(charRingArray.Front() == 11); - Require(charRingArray.Back() == 12); - - charRingArray.PushBack(13); - Require(charRingArray.Size() == 3); - Require(charRingArray.Front() == 11); - Require(charRingArray.Back() == 13); - - Require(charRingArray.Get(0) == 11); - Require(charRingArray.Get(1) == 12); - Require(charRingArray.Get(2) == 13); - - // PushBack writes to memory - constexpr auto ringArrayStartAddress = 3 * 2 * sizeof(std::size_t); - Require(fram::ram::memory[ringArrayStartAddress + 0] == 11_b); - Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); - Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); - - // When pushing to a full ring, the size stays the same and the oldest element is lost - charRingArray.PushBack(14); - Require(charRingArray.Size() == 3); - Require(charRingArray.Front() == 12); - Require(charRingArray.Back() == 14); - - // Only the (size + 2)th element overwrites the first one in memory because we keep a gap of one - // between begin and end indexes - charRingArray.PushBack(15); - Require(charRingArray.Size() == 3); - Require(charRingArray.Front() == 13); - Require(charRingArray.Back() == 15); - Require(fram::ram::memory[ringArrayStartAddress + 0] == 15_b); - Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); - Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); - Require(fram::ram::memory[ringArrayStartAddress + 3] == 14_b); - - // Set() writes to memory - charRingArray.Set(0, 21); - charRingArray.Set(1, 22); - charRingArray.Set(2, 23); - Require(charRingArray.Get(0) == 21); - Require(charRingArray.Get(1) == 22); - Require(charRingArray.Get(2) == 23); - Require(fram::ram::memory[ringArrayStartAddress + 0] == 23_b); - Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); - Require(fram::ram::memory[ringArrayStartAddress + 2] == 21_b); - Require(fram::ram::memory[ringArrayStartAddress + 3] == 22_b); - - // Get() with out-of-bounds index prints a debug message and returns the last element - Require(charRingArray.Get(17) == 23); - // Set() with out-of-bounds index prints a debug message and does not set anything - charRingArray.Set(17, 0); - Require(charRingArray.Get(0) == 21); - Require(charRingArray.Get(1) == 22); - Require(charRingArray.Get(2) == 23); + static constexpr auto ringArrayStartAddress = 3 * 2 * sizeof(std::size_t); + + + // SECTION("FRAM is working") + { + memory.fill(0x00_b); + fram::framIsWorking.Store(true); + + Require(charRingArray.Size() == 0); + + // Trying to set an element in an empty ring prints a debug message and does not set + // anything + charRingArray.Set(0, 11); + Require(std::all_of(memory.begin(), memory.end(), [](auto x) { return x == 0_b; })); + + charRingArray.PushBack(11); + Require(charRingArray.Size() == 1); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 11); + + charRingArray.PushBack(12); + Require(charRingArray.Size() == 2); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 12); + + charRingArray.PushBack(13); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 13); + + Require(charRingArray.Get(0) == 11); + Require(charRingArray.Get(1) == 12); + Require(charRingArray.Get(2) == 13); + + // PushBack writes to memory + Require(fram::ram::memory[ringArrayStartAddress + 0] == 11_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); + + // When pushing to a full ring, the size stays the same and the oldest element is lost + charRingArray.PushBack(14); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 12); + Require(charRingArray.Back() == 14); + + // Only the (size + 2)th element overwrites the first one in memory because we keep a gap of + // one between begin and end indexes + charRingArray.PushBack(15); + Require(charRingArray.Size() == 3); + Require(charRingArray.Front() == 13); + Require(charRingArray.Back() == 15); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 15_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 13_b); + Require(fram::ram::memory[ringArrayStartAddress + 3] == 14_b); + + // Set() writes to memory + charRingArray.Set(0, 21); + charRingArray.Set(1, 22); + charRingArray.Set(2, 23); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + Require(charRingArray.Get(2) == 23); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 23_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 12_b); + Require(fram::ram::memory[ringArrayStartAddress + 2] == 21_b); + Require(fram::ram::memory[ringArrayStartAddress + 3] == 22_b); + + // Get() with out-of-bounds index prints a debug message and returns the last element + Require(charRingArray.Get(17) == 23); + // Set() with out-of-bounds index prints a debug message and does not set anything + charRingArray.Set(17, 0); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + Require(charRingArray.Get(2) == 23); + } + + // SECTION("FRAM is not working") + { + memory.fill(0x00_b); + fram::framIsWorking.Store(false); + + Require(charRingArray.Size() == 0); + // Trying to set an element in an empty ring prints a debug message + charRingArray.Set(0, 11); + + charRingArray.PushBack(11); + Require(charRingArray.Size() == 1); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 11); + + charRingArray.PushBack(12); + Require(charRingArray.Size() == 2); + Require(charRingArray.Front() == 11); + Require(charRingArray.Back() == 12); + + Require(charRingArray.Get(0) == 11); + Require(charRingArray.Get(1) == 12); + + // PushBack does not write to memory + Require(fram::ram::memory[ringArrayStartAddress + 0] == 0_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 0_b); + + // When pushing to a full ring, the size stays the same and the oldest element is lost + charRingArray.PushBack(13); + Require(charRingArray.Size() == 2); + Require(charRingArray.Front() == 12); + Require(charRingArray.Back() == 13); + + // Set() does not write to memory + charRingArray.Set(0, 21); + charRingArray.Set(1, 22); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + Require(fram::ram::memory[ringArrayStartAddress + 0] == 0_b); + Require(fram::ram::memory[ringArrayStartAddress + 1] == 0_b); + + // Get() with out-of-bounds index prints a debug message and returns the last element + Require(charRingArray.Get(17) == 22); + // Set() with out-of-bounds index prints a debug message and does not set anything + charRingArray.Set(17, 0); + Require(charRingArray.Get(0) == 21); + Require(charRingArray.Get(1) == 22); + } // TODO: Add tests with custom types }