Added first draft of RAM class
This commit is contained in:
parent
0073ef91f9
commit
8b94e8f55c
8 changed files with 687 additions and 13 deletions
|
|
@ -18,9 +18,9 @@
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "build dbg",
|
"label": "build gdb",
|
||||||
"command": "./build",
|
"command": "./build",
|
||||||
"args": ["dbg"],
|
"args": ["gdb"],
|
||||||
"cwd": "$ZED_WORKTREE_ROOT",
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
"use_new_terminal": false,
|
"use_new_terminal": false,
|
||||||
"allow_concurrent_runs": false,
|
"allow_concurrent_runs": false,
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,14 @@ list(APPEND INCLUDE_DIRS
|
||||||
list(APPEND RUNTIME_SOURCE_FILES
|
list(APPEND RUNTIME_SOURCE_FILES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/runtime/runtime_main.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/runtime/runtime_main.cpp
|
||||||
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/hardware/VirtualCPU.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/hardware/CPU.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/hardware/RAM.cpp
|
||||||
)
|
)
|
||||||
list(APPEND DEBUGGER_SOURCE_FILES
|
list(APPEND DEBUGGER_SOURCE_FILES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/debugger/debugger_main.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/debugger/debugger_main.cpp
|
||||||
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/hardware/VirtualCPU.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/hardware/CPU.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/hardware/RAM.cpp
|
||||||
)
|
)
|
||||||
list(APPEND ASSEMBLER_SOURCE_FILES
|
list(APPEND ASSEMBLER_SOURCE_FILES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/assembler/assembler_main.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/assembler/assembler_main.cpp
|
||||||
|
|
@ -44,6 +46,10 @@ list(APPEND ASSEMBLER_SOURCE_FILES
|
||||||
list(APPEND TOOLS_SOURCE_FILES
|
list(APPEND TOOLS_SOURCE_FILES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/tools/tools_main.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/tools/tools_main.cpp
|
||||||
)
|
)
|
||||||
|
list(APPEND TEST_SOURCE_FILES
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/RAM_test.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/hardware/RAM.cpp
|
||||||
|
)
|
||||||
#-----------------------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
#Targets
|
#Targets
|
||||||
|
|
@ -51,7 +57,7 @@ list(APPEND TOOLS_SOURCE_FILES
|
||||||
set(RUNTIME_TARGET dvm)
|
set(RUNTIME_TARGET dvm)
|
||||||
add_executable(${RUNTIME_TARGET} ${RUNTIME_SOURCE_FILES})
|
add_executable(${RUNTIME_TARGET} ${RUNTIME_SOURCE_FILES})
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(${MAIN_EXECUTABLE} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/dvm_appIcon.rc")
|
target_sources(${RUNTIME_TARGET} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/dvm_appIcon.rc")
|
||||||
endif()
|
endif()
|
||||||
target_include_directories(${RUNTIME_TARGET} PUBLIC ${INCLUDE_DIRS})
|
target_include_directories(${RUNTIME_TARGET} PUBLIC ${INCLUDE_DIRS})
|
||||||
|
|
||||||
|
|
@ -59,7 +65,7 @@ target_include_directories(${RUNTIME_TARGET} PUBLIC ${INCLUDE_DIRS})
|
||||||
set(DEBUGGER_TARGET ddb)
|
set(DEBUGGER_TARGET ddb)
|
||||||
add_executable(${DEBUGGER_TARGET} ${DEBUGGER_SOURCE_FILES})
|
add_executable(${DEBUGGER_TARGET} ${DEBUGGER_SOURCE_FILES})
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(${MAIN_EXECUTABLE} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/ddb_appIcon.rc")
|
target_sources(${DEBUGGER_TARGET} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/ddb_appIcon.rc")
|
||||||
endif()
|
endif()
|
||||||
target_include_directories(${DEBUGGER_TARGET} PUBLIC ${INCLUDE_DIRS})
|
target_include_directories(${DEBUGGER_TARGET} PUBLIC ${INCLUDE_DIRS})
|
||||||
|
|
||||||
|
|
@ -67,7 +73,7 @@ target_include_directories(${DEBUGGER_TARGET} PUBLIC ${INCLUDE_DIRS})
|
||||||
set(ASSEMBLER_TARGET dasm)
|
set(ASSEMBLER_TARGET dasm)
|
||||||
add_executable(${ASSEMBLER_TARGET} ${ASSEMBLER_SOURCE_FILES})
|
add_executable(${ASSEMBLER_TARGET} ${ASSEMBLER_SOURCE_FILES})
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(${MAIN_EXECUTABLE} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/dasm_appIcon.rc")
|
target_sources(${ASSEMBLER_TARGET} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/dasm_appIcon.rc")
|
||||||
endif()
|
endif()
|
||||||
target_include_directories(${ASSEMBLER_TARGET} PUBLIC ${INCLUDE_DIRS})
|
target_include_directories(${ASSEMBLER_TARGET} PUBLIC ${INCLUDE_DIRS})
|
||||||
|
|
||||||
|
|
@ -75,14 +81,23 @@ target_include_directories(${ASSEMBLER_TARGET} PUBLIC ${INCLUDE_DIRS})
|
||||||
set(TOOLS_TARGET dtools)
|
set(TOOLS_TARGET dtools)
|
||||||
add_executable(${TOOLS_TARGET} ${TOOLS_SOURCE_FILES})
|
add_executable(${TOOLS_TARGET} ${TOOLS_SOURCE_FILES})
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(${MAIN_EXECUTABLE} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/dtools_appIcon.rc")
|
target_sources(${TOOLS_TARGET} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/dtools_appIcon.rc")
|
||||||
endif()
|
endif()
|
||||||
target_include_directories(${TOOLS_TARGET} PUBLIC ${INCLUDE_DIRS})
|
target_include_directories(${TOOLS_TARGET} PUBLIC ${INCLUDE_DIRS})
|
||||||
|
|
||||||
|
|
||||||
|
set(TEST_TARGET test_exec)
|
||||||
|
add_executable(${TEST_TARGET} ${TEST_SOURCE_FILES})
|
||||||
|
if(WIN32)
|
||||||
|
target_sources(${TEST_TARGET} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/dtools_appIcon.rc")
|
||||||
|
endif()
|
||||||
|
target_include_directories(${TEST_TARGET} PUBLIC ${INCLUDE_DIRS})
|
||||||
|
|
||||||
target_compile_definitions(${RUNTIME_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
target_compile_definitions(${RUNTIME_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
||||||
target_compile_definitions(${DEBUGGER_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
target_compile_definitions(${DEBUGGER_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
||||||
target_compile_definitions(${ASSEMBLER_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
target_compile_definitions(${ASSEMBLER_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
||||||
target_compile_definitions(${TOOLS_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
target_compile_definitions(${TOOLS_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
||||||
|
target_compile_definitions(${TEST_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(MSYS2_UCRT64 "C:/msys64/ucrt64")
|
set(MSYS2_UCRT64 "C:/msys64/ucrt64")
|
||||||
|
|
@ -108,6 +123,12 @@ if (WIN32)
|
||||||
"${MSYS2_UCRT64}/include/c++/13.2.0"
|
"${MSYS2_UCRT64}/include/c++/13.2.0"
|
||||||
"${MSYS2_UCRT64}/include/c++/13.2.0/x86_64-w64-mingw32"
|
"${MSYS2_UCRT64}/include/c++/13.2.0/x86_64-w64-mingw32"
|
||||||
|
|
||||||
|
)
|
||||||
|
target_include_directories(${TEST_TARGET} PRIVATE
|
||||||
|
"${MSYS2_UCRT64}/include"
|
||||||
|
"${MSYS2_UCRT64}/include/c++/13.2.0"
|
||||||
|
"${MSYS2_UCRT64}/include/c++/13.2.0/x86_64-w64-mingw32"
|
||||||
|
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -116,12 +137,14 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
target_compile_definitions(${DEBUGGER_TARGET} PUBLIC BUILD_CONFIG_DEBUG OX_DEBUG_BUILD)
|
target_compile_definitions(${DEBUGGER_TARGET} PUBLIC BUILD_CONFIG_DEBUG OX_DEBUG_BUILD)
|
||||||
target_compile_definitions(${ASSEMBLER_TARGET} PUBLIC BUILD_CONFIG_DEBUG OX_DEBUG_BUILD)
|
target_compile_definitions(${ASSEMBLER_TARGET} PUBLIC BUILD_CONFIG_DEBUG OX_DEBUG_BUILD)
|
||||||
target_compile_definitions(${TOOLS_TARGET} PUBLIC BUILD_CONFIG_DEBUG OX_DEBUG_BUILD)
|
target_compile_definitions(${TOOLS_TARGET} PUBLIC BUILD_CONFIG_DEBUG OX_DEBUG_BUILD)
|
||||||
|
target_compile_definitions(${TEST_TARGET} PUBLIC BUILD_CONFIG_DEBUG OX_DEBUG_BUILD)
|
||||||
add_compile_options(-O3 -MMD -MP -Wall -ggdb)
|
add_compile_options(-O3 -MMD -MP -Wall -ggdb)
|
||||||
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
|
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
target_compile_definitions(${RUNTIME_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
target_compile_definitions(${RUNTIME_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
||||||
target_compile_definitions(${DEBUGGER_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
target_compile_definitions(${DEBUGGER_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
||||||
target_compile_definitions(${ASSEMBLER_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
target_compile_definitions(${ASSEMBLER_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
||||||
target_compile_definitions(${TOOLS_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
target_compile_definitions(${TOOLS_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
||||||
|
target_compile_definitions(${TEST_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
|
||||||
add_compile_options(-MMD -MP -Wall)
|
add_compile_options(-MMD -MP -Wall)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -148,6 +171,7 @@ if (WIN32)
|
||||||
target_link_libraries(${ASSEMBLER_TARGET} mingw32)
|
target_link_libraries(${ASSEMBLER_TARGET} mingw32)
|
||||||
target_link_libraries(${TOOLS_TARGET} mingw32)
|
target_link_libraries(${TOOLS_TARGET} mingw32)
|
||||||
target_link_libraries(${DEBUGGER_TARGET} mingw32 SDL3 SDL3_ttf SDL3_image)
|
target_link_libraries(${DEBUGGER_TARGET} mingw32 SDL3 SDL3_ttf SDL3_image)
|
||||||
|
target_link_libraries(${TEST_TARGET} mingw32)
|
||||||
endif (WIN32)
|
endif (WIN32)
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
|
@ -157,6 +181,7 @@ if (APPLE)
|
||||||
target_link_libraries(${ASSEMBLER_TARGET} Boost::regex)
|
target_link_libraries(${ASSEMBLER_TARGET} Boost::regex)
|
||||||
target_link_libraries(${TOOLS_TARGET} Boost::regex)
|
target_link_libraries(${TOOLS_TARGET} Boost::regex)
|
||||||
target_link_libraries(${DEBUGGER_TARGET} Boost::regex)
|
target_link_libraries(${DEBUGGER_TARGET} Boost::regex)
|
||||||
|
target_link_libraries(${TEST_TARGET} Boost::regex)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(SDL3 REQUIRED sdl3)
|
pkg_check_modules(SDL3 REQUIRED sdl3)
|
||||||
pkg_check_modules(SDL3_IMAGE REQUIRED sdl3-image)
|
pkg_check_modules(SDL3_IMAGE REQUIRED sdl3-image)
|
||||||
|
|
@ -197,10 +222,12 @@ elseif (UNIX)
|
||||||
target_link_libraries(${ASSEMBLER_TARGET} xcb xcb-randr boost_regex)
|
target_link_libraries(${ASSEMBLER_TARGET} xcb xcb-randr boost_regex)
|
||||||
target_link_libraries(${TOOLS_TARGET} xcb xcb-randr boost_regex)
|
target_link_libraries(${TOOLS_TARGET} xcb xcb-randr boost_regex)
|
||||||
target_link_libraries(${DEBUGGER_TARGET} xcb xcb-randr boost_regex SDL3 SDL3_image)
|
target_link_libraries(${DEBUGGER_TARGET} xcb xcb-randr boost_regex SDL3 SDL3_image)
|
||||||
|
target_link_libraries(${TEST_TARGET} xcb xcb-randr boost_regex)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
target_link_libraries(${RUNTIME_TARGET} ostd ogfx)
|
target_link_libraries(${RUNTIME_TARGET} ostd ogfx)
|
||||||
target_link_libraries(${DEBUGGER_TARGET} ostd ogfx)
|
target_link_libraries(${DEBUGGER_TARGET} ostd ogfx)
|
||||||
target_link_libraries(${ASSEMBLER_TARGET} ostd)
|
target_link_libraries(${ASSEMBLER_TARGET} ostd)
|
||||||
target_link_libraries(${TOOLS_TARGET} ostd)
|
target_link_libraries(${TOOLS_TARGET} ostd)
|
||||||
|
target_link_libraries(${TEST_TARGET} ostd)
|
||||||
#-----------------------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
7
|
9
|
||||||
|
|
|
||||||
422
src/RAM_test.cpp
Normal file
422
src/RAM_test.cpp
Normal file
|
|
@ -0,0 +1,422 @@
|
||||||
|
/*
|
||||||
|
DragonV2 RAM test harness.
|
||||||
|
|
||||||
|
Single-threaded tests prove the bit-level correctness of the
|
||||||
|
load/store/shift/mask logic. Multi-threaded tests prove the
|
||||||
|
atomicity claims: under hammering contention, no update is lost
|
||||||
|
and no neighbour byte is corrupted.
|
||||||
|
|
||||||
|
Build (example, adjust include paths to your tree):
|
||||||
|
g++ -std=c++20 -O2 -pthread RAM.cpp RAM_test.cpp -o ram_test
|
||||||
|
then run:
|
||||||
|
./ram_test
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "hardware/RAM.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using dragon::hw::RAM;
|
||||||
|
|
||||||
|
// --- tiny test framework ------------------------------------------------------
|
||||||
|
|
||||||
|
static int g_passed = 0;
|
||||||
|
static int g_failed = 0;
|
||||||
|
static bool g_currentFailed = false;
|
||||||
|
|
||||||
|
#define TEST(name) \
|
||||||
|
do { \
|
||||||
|
const char* __test_name = #name; \
|
||||||
|
g_currentFailed = false; \
|
||||||
|
std::cout << "[ RUN ] " << __test_name << "\n"; \
|
||||||
|
auto __start = std::chrono::steady_clock::now(); \
|
||||||
|
name(); \
|
||||||
|
auto __end = std::chrono::steady_clock::now(); \
|
||||||
|
auto __ms = std::chrono::duration_cast<std::chrono::milliseconds>(__end - __start).count(); \
|
||||||
|
if (g_currentFailed) { ++g_failed; std::cout << "[ FAIL ] " << __test_name << " (" << __ms << " ms)\n"; } \
|
||||||
|
else { ++g_passed; std::cout << "[ PASS ] " << __test_name << " (" << __ms << " ms)\n"; } \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define CHECK(cond) \
|
||||||
|
do { \
|
||||||
|
if (!(cond)) { \
|
||||||
|
g_currentFailed = true; \
|
||||||
|
std::cout << " CHECK failed: " #cond " at " << __FILE__ << ":" << __LINE__ << "\n"; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define CHECK_EQ(a, b) \
|
||||||
|
do { \
|
||||||
|
auto __a = (a); auto __b = (b); \
|
||||||
|
if (!(__a == __b)) { \
|
||||||
|
g_currentFailed = true; \
|
||||||
|
std::cout << " CHECK_EQ failed: " #a " == " #b \
|
||||||
|
<< " got 0x" << std::hex << (u64)__a << " vs 0x" << (u64)__b << std::dec \
|
||||||
|
<< " at " << __FILE__ << ":" << __LINE__ << "\n"; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
// --- single-threaded correctness ---------------------------------------------
|
||||||
|
|
||||||
|
// If you store, then load, do you get what you stored back? This is the
|
||||||
|
// most basic sanity check and proves the shift/mask math is right at the
|
||||||
|
// bit level. Multi-threading can't fix bit-level bugs.
|
||||||
|
static void test_roundtrip_u8(void)
|
||||||
|
{
|
||||||
|
RAM ram(1024);
|
||||||
|
for (u32 a = 0; a < 256; ++a) ram.store_u8(a, static_cast<u8>(a ^ 0x5A));
|
||||||
|
for (u32 a = 0; a < 256; ++a) CHECK_EQ(ram.load_u8(a), static_cast<u8>(a ^ 0x5A));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_roundtrip_u16(void)
|
||||||
|
{
|
||||||
|
RAM ram(1024);
|
||||||
|
for (u32 a = 0; a < 256; a += 2) ram.store_u16(a, static_cast<u16>(0xBEEF ^ a));
|
||||||
|
for (u32 a = 0; a < 256; a += 2) CHECK_EQ(ram.load_u16(a), static_cast<u16>(0xBEEF ^ a));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_roundtrip_u32(void)
|
||||||
|
{
|
||||||
|
RAM ram(1024);
|
||||||
|
for (u32 a = 0; a < 256; a += 4) ram.store_u32(a, 0xDEADBEEFu ^ a);
|
||||||
|
for (u32 a = 0; a < 256; a += 4) CHECK_EQ(ram.load_u32(a), 0xDEADBEEFu ^ a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_roundtrip_u64(void)
|
||||||
|
{
|
||||||
|
RAM ram(1024);
|
||||||
|
for (u32 a = 0; a < 256; a += 8) ram.store_u64(a, 0xCAFEBABEDEADBEEFull ^ a);
|
||||||
|
for (u32 a = 0; a < 256; a += 8) CHECK_EQ(ram.load_u64(a), 0xCAFEBABEDEADBEEFull ^ a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing one byte/half-word must not disturb its neighbours within the
|
||||||
|
// same 64-bit word. This is the property the CAS loop is designed to
|
||||||
|
// guarantee, but we can prove it single-threaded too: do the writes in a
|
||||||
|
// known order and check every byte after each step.
|
||||||
|
static void test_no_neighbour_corruption_singlethread(void)
|
||||||
|
{
|
||||||
|
RAM ram(64);
|
||||||
|
|
||||||
|
// Fill word 0 with a known pattern using a single u64 store.
|
||||||
|
ram.store_u64(0, 0x0807060504030201ull);
|
||||||
|
|
||||||
|
// Overwrite just byte 3 with a CAS-loop store.
|
||||||
|
ram.store_u8(3, 0xFF);
|
||||||
|
|
||||||
|
// All other bytes must be untouched.
|
||||||
|
CHECK_EQ(ram.load_u8(0), 0x01);
|
||||||
|
CHECK_EQ(ram.load_u8(1), 0x02);
|
||||||
|
CHECK_EQ(ram.load_u8(2), 0x03);
|
||||||
|
CHECK_EQ(ram.load_u8(3), 0xFF);
|
||||||
|
CHECK_EQ(ram.load_u8(4), 0x05);
|
||||||
|
CHECK_EQ(ram.load_u8(5), 0x06);
|
||||||
|
CHECK_EQ(ram.load_u8(6), 0x07);
|
||||||
|
CHECK_EQ(ram.load_u8(7), 0x08);
|
||||||
|
|
||||||
|
// And reading the whole word should show one byte changed.
|
||||||
|
CHECK_EQ(ram.load_u64(0), 0x08070605FF030201ull);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Little-endian layout: the byte at address N occupies bits [N*8 .. N*8+7]
|
||||||
|
// of the host atomic word. If the shift math is backwards, this fails.
|
||||||
|
static void test_endianness_layout(void)
|
||||||
|
{
|
||||||
|
RAM ram(64);
|
||||||
|
// Write a u32 = 0x44332211 at address 0. In little-endian, byte 0 should
|
||||||
|
// be 0x11, byte 1 should be 0x22, byte 2 should be 0x33, byte 3 should be 0x44.
|
||||||
|
ram.store_u32(0, 0x44332211u);
|
||||||
|
CHECK_EQ(ram.load_u8(0), 0x11);
|
||||||
|
CHECK_EQ(ram.load_u8(1), 0x22);
|
||||||
|
CHECK_EQ(ram.load_u8(2), 0x33);
|
||||||
|
CHECK_EQ(ram.load_u8(3), 0x44);
|
||||||
|
|
||||||
|
// And reading u16 at addr 0 should give the low half: 0x2211.
|
||||||
|
CHECK_EQ(ram.load_u16(0), 0x2211);
|
||||||
|
// u16 at addr 2 should give the high half: 0x4433.
|
||||||
|
CHECK_EQ(ram.load_u16(2), 0x4433);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Misaligned and out-of-bounds accesses should fault rather than
|
||||||
|
// corrupting memory. We can't observe the fault directly (it just prints
|
||||||
|
// for now), but we can observe that they don't change neighbouring bytes.
|
||||||
|
static void test_faults_dont_modify_memory(void)
|
||||||
|
{
|
||||||
|
RAM ram(64);
|
||||||
|
ram.store_u64(0, 0xAAAAAAAAAAAAAAAAull);
|
||||||
|
|
||||||
|
// Misaligned u32 store should be rejected.
|
||||||
|
ram.store_u32(1, 0x55555555u);
|
||||||
|
CHECK_EQ(ram.load_u64(0), 0xAAAAAAAAAAAAAAAAull);
|
||||||
|
|
||||||
|
// Out-of-bounds store should be rejected.
|
||||||
|
ram.store_u8(64, 0x55);
|
||||||
|
// And the last in-bounds word is unchanged.
|
||||||
|
CHECK_EQ(ram.load_u64(56), 0x0000000000000000ull);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- multi-threaded atomicity -------------------------------------------------
|
||||||
|
|
||||||
|
// The classic atomicity proof. N threads each increment the same u32
|
||||||
|
// location M times using a CAS retry loop in user code. If atomic stores
|
||||||
|
// were not actually atomic (or if our CAS loop were broken), updates
|
||||||
|
// would be lost and the final count would be less than N*M.
|
||||||
|
//
|
||||||
|
// Note this test uses store_u32 indirectly via a read-modify-write pattern
|
||||||
|
// from user code, which is NOT atomic by itself. We instead drive the
|
||||||
|
// guarantee directly: we expect each store_u32 to commit as a single
|
||||||
|
// SC-ordered update. The right test for this is "concurrent disjoint
|
||||||
|
// writes preserve neighbours" (next test). What this test actually
|
||||||
|
// stresses is that the RAM survives concurrent access on the same word
|
||||||
|
// without crashing or producing impossible values.
|
||||||
|
static void test_concurrent_same_word_smoke(void)
|
||||||
|
{
|
||||||
|
RAM ram(64);
|
||||||
|
ram.store_u32(0, 0);
|
||||||
|
|
||||||
|
constexpr int kThreads = 8;
|
||||||
|
constexpr int kIters = 100000;
|
||||||
|
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
threads.reserve(kThreads);
|
||||||
|
std::atomic<int> ready { 0 };
|
||||||
|
std::atomic<bool> go { false };
|
||||||
|
|
||||||
|
for (int t = 0; t < kThreads; ++t) {
|
||||||
|
threads.emplace_back([&, t]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { /* spin */ }
|
||||||
|
for (int i = 0; i < kIters; ++i) {
|
||||||
|
// Each thread writes its own ID into addr 0.
|
||||||
|
ram.store_u32(0, static_cast<u32>(t));
|
||||||
|
u32 v = ram.load_u32(0);
|
||||||
|
// The only valid values are 0..kThreads-1
|
||||||
|
CHECK(v < static_cast<u32>(kThreads));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ready.load(std::memory_order_seq_cst) < kThreads) { /* spin */ }
|
||||||
|
go.store(true, std::memory_order_seq_cst);
|
||||||
|
|
||||||
|
for (auto& th : threads) th.join();
|
||||||
|
|
||||||
|
u32 final = ram.load_u32(0);
|
||||||
|
CHECK(final < static_cast<u32>(kThreads));
|
||||||
|
}
|
||||||
|
|
||||||
|
// THE critical test for sub-word atomicity. 8 threads each write a
|
||||||
|
// distinct constant value to a distinct byte of the same 64-bit word,
|
||||||
|
// hammering on it for many iterations. After they all stop, the word
|
||||||
|
// must contain exactly one byte from each thread, with no corruption.
|
||||||
|
//
|
||||||
|
// If the CAS loop is wrong, some writer's byte will get clobbered by a
|
||||||
|
// neighbour's read-modify-write cycle and the final word will contain
|
||||||
|
// the wrong byte for one of the slots.
|
||||||
|
static void test_concurrent_neighbour_byte_writes(void)
|
||||||
|
{
|
||||||
|
RAM ram(64);
|
||||||
|
ram.store_u64(0, 0); // start clean
|
||||||
|
|
||||||
|
constexpr int kIters = 200000;
|
||||||
|
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
std::atomic<int> ready { 0 };
|
||||||
|
std::atomic<bool> go { false };
|
||||||
|
|
||||||
|
// Each thread owns one byte slot (0..7) and a unique 8-bit value to write.
|
||||||
|
for (u32 slot = 0; slot < 8; ++slot) {
|
||||||
|
const u8 my_value = static_cast<u8>(0x10 + slot); // 0x10, 0x11, ..., 0x17
|
||||||
|
threads.emplace_back([&, slot, my_value]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { /* spin */ }
|
||||||
|
for (int i = 0; i < kIters; ++i) {
|
||||||
|
ram.store_u8(slot, my_value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ready.load(std::memory_order_seq_cst) < 8) { /* spin */ }
|
||||||
|
go.store(true, std::memory_order_seq_cst);
|
||||||
|
for (auto& th : threads) th.join();
|
||||||
|
|
||||||
|
// After the dust settles, each byte slot must hold the value its owner wrote.
|
||||||
|
for (u32 slot = 0; slot < 8; ++slot) {
|
||||||
|
CHECK_EQ(ram.load_u8(slot), static_cast<u8>(0x10 + slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same property, scaled up: across many words, many threads, mixed widths.
|
||||||
|
// One thread per byte slot across 16 words. If anything is broken
|
||||||
|
// anywhere in the shift/mask/CAS pipeline, this should expose it.
|
||||||
|
static void test_concurrent_many_words(void)
|
||||||
|
{
|
||||||
|
constexpr u32 kWords = 16;
|
||||||
|
RAM ram(kWords * 8);
|
||||||
|
for (u32 w = 0; w < kWords; ++w) ram.store_u64(w * 8, 0);
|
||||||
|
|
||||||
|
constexpr int kIters = 50000;
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
std::atomic<int> ready { 0 };
|
||||||
|
std::atomic<bool> go { false };
|
||||||
|
|
||||||
|
for (u32 w = 0; w < kWords; ++w) {
|
||||||
|
for (u32 slot = 0; slot < 8; ++slot) {
|
||||||
|
const u32 addr = w * 8 + slot;
|
||||||
|
const u8 val = static_cast<u8>((w * 17 + slot * 31) & 0xFF);
|
||||||
|
threads.emplace_back([&, addr, val]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { /* spin */ }
|
||||||
|
for (int i = 0; i < kIters; ++i) {
|
||||||
|
ram.store_u8(addr, val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int total = static_cast<int>(kWords * 8);
|
||||||
|
while (ready.load(std::memory_order_seq_cst) < total) { /* spin */ }
|
||||||
|
go.store(true, std::memory_order_seq_cst);
|
||||||
|
for (auto& th : threads) th.join();
|
||||||
|
|
||||||
|
for (u32 w = 0; w < kWords; ++w) {
|
||||||
|
for (u32 slot = 0; slot < 8; ++slot) {
|
||||||
|
const u8 expected = static_cast<u8>((w * 17 + slot * 31) & 0xFF);
|
||||||
|
CHECK_EQ(ram.load_u8(w * 8 + slot), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixed-width contention. Two threads compete on the same 64-bit word
|
||||||
|
// but at different widths: one writes a u32 to the low half, another
|
||||||
|
// writes a u16 to byte offset 4 (inside the high half), another writes
|
||||||
|
// individual bytes to offsets 6 and 7. After many iterations the final
|
||||||
|
// state must be self-consistent (each writer's last value visible in its
|
||||||
|
// own slot).
|
||||||
|
static void test_concurrent_mixed_widths(void)
|
||||||
|
{
|
||||||
|
RAM ram(64);
|
||||||
|
ram.store_u64(0, 0);
|
||||||
|
|
||||||
|
constexpr int kIters = 100000;
|
||||||
|
std::atomic<int> ready { 0 };
|
||||||
|
std::atomic<bool> go { false };
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
|
threads.emplace_back([&]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { }
|
||||||
|
for (int i = 0; i < kIters; ++i) ram.store_u32(0, 0xAABBCCDDu);
|
||||||
|
});
|
||||||
|
threads.emplace_back([&]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { }
|
||||||
|
for (int i = 0; i < kIters; ++i) ram.store_u16(4, 0xEEFFu);
|
||||||
|
});
|
||||||
|
threads.emplace_back([&]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { }
|
||||||
|
for (int i = 0; i < kIters; ++i) ram.store_u8(6, 0x11);
|
||||||
|
});
|
||||||
|
threads.emplace_back([&]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { }
|
||||||
|
for (int i = 0; i < kIters; ++i) ram.store_u8(7, 0x22);
|
||||||
|
});
|
||||||
|
|
||||||
|
while (ready.load(std::memory_order_seq_cst) < 4) { }
|
||||||
|
go.store(true, std::memory_order_seq_cst);
|
||||||
|
for (auto& th : threads) th.join();
|
||||||
|
|
||||||
|
// Each writer wrote the same value every iteration so each slot must hold its writer's value.
|
||||||
|
CHECK_EQ(ram.load_u32(0), 0xAABBCCDDu);
|
||||||
|
CHECK_EQ(ram.load_u16(4), 0xEEFFu);
|
||||||
|
CHECK_EQ(ram.load_u8 (6), 0x11);
|
||||||
|
CHECK_EQ(ram.load_u8 (7), 0x22);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readers + writers. If the load path were non-atomic (e.g. read-modify
|
||||||
|
// of underlying storage during a load) you could see torn values. With
|
||||||
|
// seq_cst atomics, every load sees exactly one consistent prior store.
|
||||||
|
static void test_concurrent_readers_writers(void)
|
||||||
|
{
|
||||||
|
RAM ram(64);
|
||||||
|
ram.store_u64(0, 0);
|
||||||
|
|
||||||
|
constexpr int kIters = 200000;
|
||||||
|
std::atomic<int> ready { 0 };
|
||||||
|
std::atomic<bool> go { false };
|
||||||
|
std::atomic<bool> stop { false };
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
|
// Two writers alternate between two known patterns.
|
||||||
|
threads.emplace_back([&]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { }
|
||||||
|
for (int i = 0; i < kIters; ++i) ram.store_u64(0, 0x1111111111111111ull);
|
||||||
|
});
|
||||||
|
threads.emplace_back([&]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { }
|
||||||
|
for (int i = 0; i < kIters; ++i) ram.store_u64(0, 0x2222222222222222ull);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Readers should never observe anything other than the two patterns.
|
||||||
|
std::atomic<int> bad_observations { 0 };
|
||||||
|
for (int r = 0; r < 4; ++r) {
|
||||||
|
threads.emplace_back([&]() {
|
||||||
|
ready.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
while (!go.load(std::memory_order_seq_cst)) { }
|
||||||
|
while (!stop.load(std::memory_order_seq_cst)) {
|
||||||
|
u64 v = ram.load_u64(0);
|
||||||
|
if (v != 0x1111111111111111ull && v != 0x2222222222222222ull && v != 0)
|
||||||
|
bad_observations.fetch_add(1, std::memory_order_seq_cst);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ready.load(std::memory_order_seq_cst) < 6) { }
|
||||||
|
go.store(true, std::memory_order_seq_cst);
|
||||||
|
|
||||||
|
// Wait for writers to finish, then stop readers.
|
||||||
|
threads[0].join();
|
||||||
|
threads[1].join();
|
||||||
|
stop.store(true, std::memory_order_seq_cst);
|
||||||
|
for (size_t i = 2; i < threads.size(); ++i) threads[i].join();
|
||||||
|
|
||||||
|
CHECK_EQ(bad_observations.load(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- main --------------------------------------------------------------------
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
std::cout << "DragonV2 RAM test harness\n";
|
||||||
|
std::cout << "=========================\n\n";
|
||||||
|
|
||||||
|
std::cout << "--- single-threaded correctness ---\n";
|
||||||
|
TEST(test_roundtrip_u8);
|
||||||
|
TEST(test_roundtrip_u16);
|
||||||
|
TEST(test_roundtrip_u32);
|
||||||
|
TEST(test_roundtrip_u64);
|
||||||
|
TEST(test_no_neighbour_corruption_singlethread);
|
||||||
|
TEST(test_endianness_layout);
|
||||||
|
TEST(test_faults_dont_modify_memory);
|
||||||
|
|
||||||
|
std::cout << "\n--- multi-threaded atomicity ---\n";
|
||||||
|
TEST(test_concurrent_same_word_smoke);
|
||||||
|
TEST(test_concurrent_neighbour_byte_writes);
|
||||||
|
TEST(test_concurrent_many_words);
|
||||||
|
TEST(test_concurrent_mixed_widths);
|
||||||
|
TEST(test_concurrent_readers_writers);
|
||||||
|
|
||||||
|
std::cout << "\n=========================\n";
|
||||||
|
std::cout << "Passed: " << g_passed << " Failed: " << g_failed << "\n";
|
||||||
|
return g_failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
@ -18,13 +18,13 @@
|
||||||
along with DragonV2. If not, see <https://www.gnu.org/licenses/>.
|
along with DragonV2. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "VirtualCPU.hpp"
|
#include "CPU.hpp"
|
||||||
|
|
||||||
namespace dragon
|
namespace dragon
|
||||||
{
|
{
|
||||||
namespace hw
|
namespace hw
|
||||||
{
|
{
|
||||||
VirtualCPU::VirtualCPU(void)
|
CPU::CPU(void)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,10 +26,10 @@ namespace dragon
|
||||||
{
|
{
|
||||||
namespace hw
|
namespace hw
|
||||||
{
|
{
|
||||||
class VirtualCPU
|
class CPU
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VirtualCPU(void);
|
CPU(void);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
159
src/hardware/RAM.cpp
Normal file
159
src/hardware/RAM.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
DragonV2 - A collection of useful functionality
|
||||||
|
Copyright (C) 2026 OmniaX-Dev
|
||||||
|
|
||||||
|
This file is part of DragonV2.
|
||||||
|
|
||||||
|
DragonV2 is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
DragonV2 is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with DragonV2. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RAM.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <ostd/string/String.hpp>
|
||||||
|
|
||||||
|
namespace dragon
|
||||||
|
{
|
||||||
|
namespace hw
|
||||||
|
{
|
||||||
|
RAM::RAM(u64 size)
|
||||||
|
{
|
||||||
|
m_wordCount = (size + 7) / 8; // Round up to multiple of 8 bytes for the word-granular backing
|
||||||
|
m_size = size;
|
||||||
|
m_words = new std::atomic<u64>[m_wordCount];
|
||||||
|
for (u64 i = 0; i < m_wordCount; ++i)
|
||||||
|
m_words[i].store(0, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
RAM::~RAM(void)
|
||||||
|
{
|
||||||
|
delete[] m_words;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 RAM::load_u64(u32 addr) const
|
||||||
|
{
|
||||||
|
if (addr + 8 > m_size || (addr & 7))
|
||||||
|
{
|
||||||
|
fault(addr, 8);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_words[addr >> 3].load(std::memory_order_seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 RAM::load_u32(u32 addr) const
|
||||||
|
{
|
||||||
|
if (addr + 4 > m_size || (addr & 3))
|
||||||
|
{
|
||||||
|
fault(addr, 4);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
u64 word = m_words[addr >> 3].load(std::memory_order_seq_cst);
|
||||||
|
u32 shift = (addr & 4) * 8; // 0 or 32
|
||||||
|
return static_cast<u32>(word >> shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 RAM::load_u16(u32 addr) const
|
||||||
|
{
|
||||||
|
if (addr + 2 > m_size || (addr & 1))
|
||||||
|
{
|
||||||
|
fault(addr, 2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
u64 word = m_words[addr >> 3].load(std::memory_order_seq_cst);
|
||||||
|
u32 shift = (addr & 6) * 8; // 0, 16, 32, or 48
|
||||||
|
return static_cast<u16>(word >> shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 RAM::load_u8(u32 addr) const
|
||||||
|
{
|
||||||
|
if (addr >= m_size)
|
||||||
|
{
|
||||||
|
fault(addr, 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
u64 word = m_words[addr >> 3].load(std::memory_order_seq_cst);
|
||||||
|
u32 shift = (addr & 7) * 8;
|
||||||
|
return static_cast<u8>(word >> shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAM::store_u64(u32 addr, u64 value)
|
||||||
|
{
|
||||||
|
if (addr + 8 > m_size || (addr & 7))
|
||||||
|
{
|
||||||
|
fault(addr, 8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_words[addr >> 3].store(value, std::memory_order_seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAM::store_u32(u32 addr, u32 value)
|
||||||
|
{
|
||||||
|
if (addr + 4 > m_size || (addr & 3))
|
||||||
|
{
|
||||||
|
fault(addr, 4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
u32 shift = (addr & 4) * 8;
|
||||||
|
u64 mask = u64(0xFFFFFFFFu) << shift;
|
||||||
|
std::atomic<u64>& word = m_words[addr >> 3];
|
||||||
|
u64 expected = word.load(std::memory_order_relaxed);
|
||||||
|
u64 desired;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
desired = (expected & ~mask) | (u64(value) << shift);
|
||||||
|
} while (!word.compare_exchange_weak(expected, desired, std::memory_order_seq_cst, std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAM::store_u16(u32 addr, u16 value)
|
||||||
|
{
|
||||||
|
if (addr + 2 > m_size || (addr & 1))
|
||||||
|
{
|
||||||
|
fault(addr, 2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
u32 shift = (addr & 6) * 8;
|
||||||
|
u64 mask = u64(0xFFFFu) << shift;
|
||||||
|
std::atomic<u64>& word = m_words[addr >> 3];
|
||||||
|
u64 expected = word.load(std::memory_order_relaxed);
|
||||||
|
u64 desired;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
desired = (expected & ~mask) | (u64(value) << shift);
|
||||||
|
} while (!word.compare_exchange_weak(expected, desired, std::memory_order_seq_cst, std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAM::store_u8(u32 addr, u8 value)
|
||||||
|
{
|
||||||
|
if (addr >= m_size)
|
||||||
|
{
|
||||||
|
fault(addr, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
u32 shift = (addr & 7) * 8;
|
||||||
|
u64 mask = u64(0xFFu) << shift;
|
||||||
|
std::atomic<u64>& word = m_words[addr >> 3];
|
||||||
|
u64 expected = word.load(std::memory_order_relaxed);
|
||||||
|
u64 desired;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
desired = (expected & ~mask) | (u64(value) << shift);
|
||||||
|
} while (!word.compare_exchange_weak(expected, desired, std::memory_order_seq_cst, std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAM::fault(u32 addr, u32 size) const
|
||||||
|
{
|
||||||
|
std::cout << "Memory Fault: " << ostd::String::getHexStr(addr, true, 4) << "\n";
|
||||||
|
/* TODO: raise guest trap */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/hardware/RAM.hpp
Normal file
66
src/hardware/RAM.hpp
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
DragonV2 - A collection of useful functionality
|
||||||
|
Copyright (C) 2026 OmniaX-Dev
|
||||||
|
|
||||||
|
This file is part of DragonV2.
|
||||||
|
|
||||||
|
DragonV2 is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
DragonV2 is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with DragonV2. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ostd/data/Types.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace dragon
|
||||||
|
{
|
||||||
|
namespace hw
|
||||||
|
{
|
||||||
|
class RAM
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RAM(u64 size);
|
||||||
|
~RAM(void);
|
||||||
|
RAM(const RAM&) = delete;
|
||||||
|
RAM& operator=(const RAM&) = delete;
|
||||||
|
|
||||||
|
// ---- Aligned loads ----
|
||||||
|
u64 load_u64(u32 addr) const;
|
||||||
|
u32 load_u32(u32 addr) const;
|
||||||
|
u16 load_u16(u32 addr) const;
|
||||||
|
u8 load_u8 (u32 addr) const;
|
||||||
|
inline i64 load_i64(u32 addr) const { return cast<i64>(load_u64(addr)); }
|
||||||
|
inline i32 load_i32(u32 addr) const { return cast<i32>(load_u32(addr)); }
|
||||||
|
inline i16 load_i16(u32 addr) const { return cast<i16>(load_u16(addr)); }
|
||||||
|
inline i8 load_i8 (u32 addr) const { return cast<i8 >(load_u8 (addr)); }
|
||||||
|
|
||||||
|
// ---- Aligned stores ----
|
||||||
|
void store_u64(u32 addr, u64 value);
|
||||||
|
void store_u32(u32 addr, u32 value);
|
||||||
|
void store_u16(u32 addr, u16 value);
|
||||||
|
void store_u8 (u32 addr, u8 value);
|
||||||
|
inline void store_i64(u32 addr, u64 value) { store_u64(addr, cast<i64>(value)); }
|
||||||
|
inline void store_i32(u32 addr, u32 value) { store_u32(addr, cast<i32>(value)); }
|
||||||
|
inline void store_i16(u32 addr, u16 value) { store_u16(addr, cast<i16>(value)); }
|
||||||
|
inline void store_i8 (u32 addr, u8 value) { store_u8 (addr, cast<i8 >(value)); }
|
||||||
|
|
||||||
|
void fault(u32 addr, u32 size) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<u64>* m_words;
|
||||||
|
u64 m_wordCount;
|
||||||
|
u64 m_size;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue