Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion cmake/utils.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ macro(prot_add_utest src_file)
set(TEST_NAME "${src_file}_test")
add_executable(${TEST_NAME} ${src_file})

target_link_libraries(${TEST_NAME} PRIVATE GMock::gmock_main PROT::defaults)
target_link_libraries(${TEST_NAME} PRIVATE PROT::defaults GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main)
foreach(arg IN LISTS ARGN)
target_link_libraries(${TEST_NAME} PRIVATE ${arg})
endforeach()
Expand All @@ -15,3 +15,18 @@ macro(prot_add_utest src_file)
EXTRA_ARGS --gtest_color=yes
PROPERTIES LABELS unit)
endmacro()

function(prot_add_component_utest COMPONENT_NAME)
file(GLOB TESTLIST RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cc")

foreach(TEST_SRC ${TESTLIST})
set(TEST_NAME "${src_file}_test")
prot_add_utest(${TEST_SRC} ARGN)

set_target_properties(${TEST_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/test/${COMPONENT_NAME}"
)

endforeach()
endfunction()
3 changes: 3 additions & 0 deletions src/memory/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
add_library(prot_mem STATIC memory.cc plain_memory.cc simple_page_mem.cc)

target_link_libraries(
prot_mem
PUBLIC PROT::isa
PRIVATE PROT::defaults fmt::fmt)
target_include_directories(prot_mem PUBLIC include)

add_library(PROT::memory ALIAS prot_mem)

add_subdirectory(tests)
75 changes: 75 additions & 0 deletions src/memory/include/prot/page.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#ifndef PROT_PAGE_HH_INCLUDED
#define PROT_PAGE_HH_INCLUDED

#include <concepts>
#include <cstdint>
#include <stdexcept>
#include <sys/types.h>
#include <type_traits>
#include <vector>

#include "prot/isa.hh"

namespace prot {
template <std::uint64_t PageBits> class PageConfig {
public:
using PadeId = uint32_t;

static constexpr std::uint64_t getPageSize() { return kPageSize; }
static isa::Addr getOffset(isa::Addr addr) { return addr & kOffsetMask; }
static PadeId getId(isa::Addr addr) { return (addr & kIdMask) >> kPageBits; }

private:
static constexpr std::uint64_t kPageBits{PageBits};
static constexpr std::uint64_t kPageSize{1ULL << kPageBits};
static constexpr std::uint64_t kOffsetMask{kPageSize - 1};
static constexpr std::uint64_t kIdMask{~kOffsetMask};
};

template <typename T>
concept PageConfigConc = requires(T, isa::Addr addr) {
{ T::getPageSize() } -> std::same_as<std::uint64_t>;
{ T::getId(addr) } -> std::same_as<typename T::PadeId>;
{ T::getOffset(addr) } -> std::same_as<isa::Addr>;
};

class PageFault : public std::out_of_range {
public:
explicit PageFault(isa::Addr addr)
: std::out_of_range("Page fault at address: 0x" + std::to_string(addr)),
m_addr(addr) {}

isa::Addr addr() const { return m_addr; }

private:
isa::Addr m_addr;
};

template <PageConfigConc PageConfigT> class Page final {
public:
Page() : m_data(PageConfigT::getPageSize()) {}

public:
void writeBlock(const std::byte *src, isa::Addr offset, std::size_t size) {
if (offset >= PageConfigT::getPageSize())
throw std::out_of_range{"Offset out of page"};

std::copy_n(src, size, m_data.data() + offset);
}

void readBlock(isa::Addr offset, std::size_t size, std::byte *dst) const {
if (offset >= PageConfigT::getPageSize())
throw std::out_of_range{"Offset out of page"};

std::copy_n(m_data.data() + offset, size, dst);
}

auto at(isa::Addr addr) const { return m_data.at(addr); }
auto getSize() const { return PageConfigT::getPageSize(); }

private:
std::vector<std::byte> m_data{};
};
} // namespace prot

#endif // PROT_PAGE_HH_INCLUDED
123 changes: 123 additions & 0 deletions src/memory/include/prot/page_memory.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#ifndef PROT_PAGE_MEMORY_HH_INCLUDED
#define PROT_PAGE_MEMORY_HH_INCLUDED

#include "memory.hh"
#include "page.hh"

#include <cstdint>
#include <stdexcept>
#include <sys/types.h>
#include <unordered_map>
#include <vector>

namespace prot {

template <PageConfigConc PageConfigT, typename Func>
bool memWalker(isa::Addr addr, std::size_t size, Func func) {
if (size == 0)
return true;

isa::Addr currentAddr = addr;
std::size_t remainSize = size;

while (remainSize > 0) {
auto pageId = PageConfigT::getId(currentAddr);
auto offset = PageConfigT::getOffset(currentAddr);
auto chunkSize = std::min(remainSize, PageConfigT::getPageSize() - offset);

if (!func(pageId, chunkSize, offset))
return false;

currentAddr += chunkSize;
remainSize -= chunkSize;
}

return true;
}

template <std::uint64_t PageBits, std::uint64_t AddrBits>
class PageMemConfig final {
public:
using PageConfigT = PageConfig<PageBits>;
using PageT = Page<PageConfigT>;

static constexpr std::uint64_t getPagesNum() {
return kAddrSpace / PageConfigT::getPageSize();
}

static constexpr PageConfigT getPageConfig() { return kPageConf; }

private:
static constexpr std::uint64_t kAddrBits{AddrBits};
static constexpr std::uint64_t kAddrSpace{1ULL << AddrBits};
static constexpr PageConfigT kPageConf{};

static_assert(kAddrSpace >= PageConfigT::getPageSize(),
"Total virtual address space is smaller than single page size");
};

template <typename T>
concept PageMemConfigConc = requires(T) {
{ T::getPagesNum() } -> std::same_as<std::uint64_t>;
{ T::getPageConfig() } -> std::same_as<typename T::PageConfigT>;
};

template <PageMemConfigConc PageMemConfigT> class PageMemory : public Memory {
public:
using PageConfigT = typename PageMemConfigT::PageConfigT;
using PageT = typename PageMemConfigT::PageT;
using PageId = typename PageConfigT::PadeId;

PageMemory() : m_config(PageMemConfigT::getPageConfig()) {
m_storage.reserve(PageMemConfigT::getPagesNum());
}

void writeBlock(std::span<const std::byte> src, isa::Addr addr) override {
const auto *cur = src.data();

memWalker<PageConfigT>(
addr, src.size(),
[this, &cur](isa::Addr addr, size_t size, isa::Addr offset) {
auto &page = this->getPage(addr);
page.writeBlock(cur, offset, size);
cur += size;
return true;
});
}

void readBlock(isa::Addr addr, std::span<std::byte> dest) const override {
auto *cur = dest.data();

memWalker<PageConfigT>(
addr, dest.size(),
[this, &cur](isa::Addr addr, size_t size, isa::Addr offset) {
const auto &page = this->getPage(addr);
page.readBlock(offset, size, cur);
cur += size;
return true;
});
}

private:
PageT getPage(isa::Addr addr) const {
auto pageId = m_config.getId(addr);
auto found = m_storage.find(pageId);

if (found == m_storage.end())
throw PageFault{addr};

return found->second;
}

PageT &getPage(isa::Addr addr) {
auto pageId = m_config.getId(addr);
auto [it, inserted] = m_storage.try_emplace(pageId);
return it->second;
}

std::unordered_map<PageId, PageT> m_storage{};
PageConfigT m_config;
};
} // namespace prot

#endif // PROT_PAGE_MEMORY_HH_INCLUDED
5 changes: 5 additions & 0 deletions src/memory/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
set(COMPONENT_NAME memory)

if(BUILD_TESTS)
prot_add_component_utest(${COMPONENT_NAME} PROT::memory)
endif()
95 changes: 95 additions & 0 deletions src/memory/tests/page_memory.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "prot/page_memory.hh"
#include <gtest/gtest.h>

class PageMemTest : public ::testing::Test {
public:
static constexpr std::uint64_t kPageBits = 12;
static constexpr std::uint64_t kAddrBits = 16;

protected:
// RAM -- 64kb, Page -- 4KB
prot::PageMemory<prot::PageMemConfig<kPageBits, kAddrBits>> m_mem{};
};

TEST_F(PageMemTest, writeBlockTest) {
const std::byte test_data[] = {std::byte{0xde}, std::byte{0xad},
std::byte{0xbe}, std::byte{0xef}};

std::byte read_data[sizeof(test_data)];

constexpr prot::isa::Addr addr = 42;

m_mem.writeBlock(test_data, addr);
m_mem.readBlock(addr, read_data);

EXPECT_TRUE(std::equal(std::begin(test_data), std::end(test_data),
std::begin(read_data)));
}

TEST_F(PageMemTest, writeBlockTest2) {
const std::byte test_data[] = {std::byte{0xde}, std::byte{0xad},
std::byte{0xbe}, std::byte{0xef}};

constexpr prot::isa::Addr addr = 42;

m_mem.writeBlock(test_data, addr);

EXPECT_EQ(m_mem.read<prot::isa::Byte>(addr), 0xde);
EXPECT_EQ(m_mem.read<prot::isa::Half>(addr), 0xadde);
EXPECT_EQ(m_mem.read<prot::isa::Word>(addr), 0xefbeadde);
}

TEST_F(PageMemTest, writeBlockTest3) {
constexpr prot::isa::Addr addr = 42;

m_mem.write<prot::isa::Byte>(addr + 0, 0xde);
m_mem.write<prot::isa::Byte>(addr + 1, 0xad);
m_mem.write<prot::isa::Byte>(addr + 2, 0xef);
m_mem.write<prot::isa::Byte>(addr + 3, 0xef);

EXPECT_EQ(m_mem.read<prot::isa::Byte>(addr + 0), 0xde);
EXPECT_EQ(m_mem.read<prot::isa::Byte>(addr + 1), 0xad);
EXPECT_EQ(m_mem.read<prot::isa::Byte>(addr + 2), 0xef);
EXPECT_EQ(m_mem.read<prot::isa::Byte>(addr + 3), 0xef);
}

TEST_F(PageMemTest, crossPageBoundaryTest) {
constexpr prot::isa::Addr boundary_addr = 4096 - 2;
const std::byte test_data[] = {std::byte{0x12}, std::byte{0x34},
std::byte{0x56}, std::byte{0x78}};

m_mem.writeBlock(test_data, boundary_addr);

std::byte read_data[sizeof(test_data)];
m_mem.readBlock(boundary_addr, read_data);

EXPECT_TRUE(std::equal(std::begin(test_data), std::end(test_data),
std::begin(read_data)));
}

TEST_F(PageMemTest, pageFaultTest) {
constexpr prot::isa::Addr unmapped_addr = 0x10000;

std::byte buffer[4];

EXPECT_THROW(m_mem.readBlock(unmapped_addr, buffer), prot::PageFault);

const std::byte test_data[] = {std::byte{0xaa}};
m_mem.writeBlock(test_data, 0x100);

std::byte read_byte[1];
m_mem.readBlock(0x100, read_byte);
EXPECT_EQ(test_data[0], read_byte[0]);
}

TEST_F(PageMemTest, edgeCasesTest) {
constexpr prot::isa::Addr end_addr = (1ULL << 16) - 4;
const std::byte end_data[] = {std::byte{0xfe}, std::byte{0xed},
std::byte{0xfa}, std::byte{0xce}};
m_mem.writeBlock(end_data, end_addr);

std::byte read_end[4];
m_mem.readBlock(end_addr, read_end);
EXPECT_TRUE(std::equal(std::begin(end_data), std::end(end_data),
std::begin(read_end)));
}