#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <string.h>
#include "doctest.h"
#include "v4/internal/vm.h"
#include "v4/vm_api.h"
struct Dummy
{
v4_u32 last_write_addr = 0;
v4_u32 last_write_val = 0;
};
static v4_err d_read32(void *user, v4_u32 addr, v4_u32 *out)
{
(void)user;
*out = 0xAABBCCDDu + (addr & 0xFu);
return 0;
}
static v4_err d_write32(void *user, v4_u32 addr, v4_u32 val)
{
Dummy *d = (Dummy *)user;
d->last_write_addr = addr;
d->last_write_val = val;
return 0;
}
TEST_CASE("RAM basic read/write round-trip")
{
uint8_t ram[64] = {};
VmConfig cfg{ram, (v4_u32)sizeof(ram), nullptr, 0};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
v4_err e;
e = vm_mem_write32(vm, 0, 0x12345678u);
CHECK(e == 0);
v4_u32 out = 0;
e = vm_mem_read32(vm, 0, &out);
CHECK(e == 0);
CHECK(out == 0x12345678u);
CHECK(ram[0] == 0x78);
CHECK(ram[1] == 0x56);
CHECK(ram[2] == 0x34);
CHECK(ram[3] == 0x12);
vm_destroy(vm);
}
TEST_CASE("Out-of-bounds access handling")
{
uint8_t ram[16] = {};
VmConfig cfg{ram, 16u, nullptr, 0};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
v4_err e = vm_mem_write32(vm, 12u, 0xDEADBEEFu);
CHECK(e == 0);
v4_u32 out = 0;
e = vm_mem_read32(vm, 13u, &out);
CHECK(e == -13);
vm_destroy(vm);
}
TEST_CASE("Unaligned access detection")
{
uint8_t ram[16] = {};
VmConfig cfg{ram, 16u, nullptr, 0};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
v4_err e = vm_mem_write32(vm, 2u, 0);
CHECK(e == -12);
vm_destroy(vm);
}
TEST_CASE("MMIO callback dispatch")
{
uint8_t ram[64] = {};
Dummy dummy{};
V4_Mmio m{ 0x20u,
0x10u,
d_read32, d_write32,
&dummy};
VmConfig cfg{ram, (v4_u32)sizeof(ram), &m, 1};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
v4_err e = vm_mem_write32(vm, 0x24u, 0xCAFEBABEu);
CHECK(e == 0);
CHECK(dummy.last_write_addr == 0x24u);
CHECK(dummy.last_write_val == 0xCAFEBABEu);
v4_u32 out = 0;
e = vm_mem_read32(vm, 0x28u, &out);
CHECK(e == 0);
CHECK(out == 0xAABBCCDDu + (0x28u & 0xFu));
vm_destroy(vm);
}
TEST_CASE("Mixed RAM/MMIO access")
{
uint8_t ram[64] = {};
Dummy dummy{};
V4_Mmio m{0x10u, 0x10u, d_read32, d_write32, &dummy};
VmConfig cfg{ram, (v4_u32)sizeof(ram), &m, 1};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
CHECK(vm_mem_write32(vm, 0x00u, 0x01020304u) == 0);
CHECK(vm_mem_write32(vm, 0x14u, 0x11111111u) == 0);
v4_u32 v = 0;
CHECK(vm_mem_read32(vm, 0x18u, &v) == 0);
CHECK(vm_mem_write32(vm, 0x2Cu, 0xFFEEDDCCu) == 0);
CHECK(vm_mem_read32(vm, 0x2Cu, &v) == 0);
CHECK(v == 0xFFEEDDCCu);
vm_destroy(vm);
}
TEST_CASE("8-bit memory read/write (LOAD8U/STORE8)")
{
uint8_t ram[64] = {};
VmConfig cfg{ram, (v4_u32)sizeof(ram), nullptr, 0};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
vm_ds_push(vm, 0x42); vm_ds_push(vm, 0); v4_u8 store_code[] = {0x34, 0x51}; CHECK(vm_exec_raw(vm, store_code, 2) == 0);
vm_ds_push(vm, 0); v4_u8 load_code[] = {0x32, 0x51}; CHECK(vm_exec_raw(vm, load_code, 2) == 0);
v4_i32 val;
vm_ds_pop(vm, &val);
CHECK(val == 0x42);
vm_destroy(vm);
}
TEST_CASE("8-bit signed load (LOAD8S)")
{
uint8_t ram[64] = {};
VmConfig cfg{ram, (v4_u32)sizeof(ram), nullptr, 0};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
vm_ds_push(vm, 0xFF);
vm_ds_push(vm, 0);
v4_u8 store_code[] = {0x34, 0x51}; CHECK(vm_exec_raw(vm, store_code, 2) == 0);
vm_ds_push(vm, 0);
v4_u8 load_code[] = {0x36, 0x51}; CHECK(vm_exec_raw(vm, load_code, 2) == 0);
v4_i32 val;
vm_ds_pop(vm, &val);
CHECK(val == -1);
vm_destroy(vm);
}
TEST_CASE("16-bit memory read/write (LOAD16U/STORE16)")
{
uint8_t ram[64] = {};
VmConfig cfg{ram, (v4_u32)sizeof(ram), nullptr, 0};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
vm_ds_push(vm, 0x1234);
vm_ds_push(vm, 0);
v4_u8 store_code[] = {0x35, 0x51}; CHECK(vm_exec_raw(vm, store_code, 2) == 0);
CHECK(ram[0] == 0x34);
CHECK(ram[1] == 0x12);
vm_ds_push(vm, 0);
v4_u8 load_code[] = {0x33, 0x51}; CHECK(vm_exec_raw(vm, load_code, 2) == 0);
v4_i32 val;
vm_ds_pop(vm, &val);
CHECK(val == 0x1234);
vm_destroy(vm);
}
TEST_CASE("16-bit signed load (LOAD16S)")
{
uint8_t ram[64] = {};
VmConfig cfg{ram, (v4_u32)sizeof(ram), nullptr, 0};
Vm *vm = vm_create(&cfg);
REQUIRE(vm);
vm_ds_push(vm, 0xFFFF);
vm_ds_push(vm, 0);
v4_u8 store_code[] = {0x35, 0x51}; CHECK(vm_exec_raw(vm, store_code, 2) == 0);
vm_ds_push(vm, 0);
v4_u8 load_code[] = {0x37, 0x51}; CHECK(vm_exec_raw(vm, load_code, 2) == 0);
v4_i32 val;
vm_ds_pop(vm, &val);
CHECK(val == -1);
vm_destroy(vm);
}