tests: add some exec tests

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-09-18 08:05:21 +05:30
parent dd9dd5f116
commit b918b75f27
22 changed files with 1331 additions and 604 deletions

227
tests/cpu/arm/exec.cc Normal file
View File

@@ -0,0 +1,227 @@
#include "cpu/cpu.hh"
#include "cpu/utility.hh"
#include <bit>
#include <catch2/catch_test_macros.hpp>
#include <iostream>
#include <limits>
#include <random>
// I could have written some public API but that wouldn't be the best practice,
// so instead I will try to do my best to test these functions using memory
// manipulation. We also use a fake PC to match the current instruction's
// address.
//
// We are going to use some addresses for specific tasks
// - (4 * 400) + 4 => Storing, then reading registers
//
// We are also going to keep some registers reserved for testing
// - R0 is always zero
// - R1 for reading PSR
class CpuFixture {
public:
uint32_t fake_pc = 2 * ARM_INSTRUCTION_SIZE;
CpuFixture()
// BIOS is all zeroes so let's do what we can
: memory(std::array<uint8_t, Memory::BIOS_SIZE>(),
std::vector<uint8_t>(192))
, bus(memory)
, cpu(bus) {}
void write_register(uint8_t rd, uint8_t value, uint8_t rotate = 0) {
// MOV
uint32_t raw = 0b11100011101000000000000000000000;
raw |= rd << 12;
raw |= rotate << 8;
raw |= value;
execute(raw);
}
uint32_t read_register(uint8_t rd) {
// use R0
static constexpr uint16_t offset = MAX_FAKE_PC + ARM_INSTRUCTION_SIZE;
uint32_t raw = 0b11100101100000000000000000000000;
raw |= rd << 12;
raw |= offset;
execute(raw);
return bus.read_word(offset + (rd == 15 ? ARM_INSTRUCTION_SIZE : 0));
}
Psr read_cpsr() {
// use R1
uint32_t raw = 0b11100001000011110001000000000000;
execute(raw);
return Psr(read_register(1));
}
void execute(uint32_t raw) {
bus.write_word(fake_pc - 2 * ARM_INSTRUCTION_SIZE, raw);
step();
}
private:
static constexpr uint32_t MAX_FAKE_PC = 400 * ARM_INSTRUCTION_SIZE;
Memory memory;
void step() {
cpu.step();
fake_pc += ARM_INSTRUCTION_SIZE;
if (fake_pc == MAX_FAKE_PC)
fake_pc = 0;
}
protected:
Bus bus;
Cpu cpu;
};
#define TAG "arm execution"
using namespace arm;
TEST_CASE_METHOD(CpuFixture, "Test fixture", TAG) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint8_t> value_d;
std::uniform_int_distribution<uint8_t> shift_d(0, (1 << 4) - 1);
// R0 is reserved to be 0 so that it can be used as as offset
write_register(0, 0);
REQUIRE(read_register(0) == 0);
for (uint8_t i = 1; i < 15; i++) {
uint8_t value = value_d(gen);
uint8_t shift = shift_d(gen);
uint32_t amount = std::rotr(static_cast<uint32_t>(value), 2 * shift);
write_register(i, value, shift);
REQUIRE(read_register(i) == amount);
}
REQUIRE(read_cpsr().mode() == Mode::Supervisor);
INFO("Fixture is OK");
}
TEST_CASE_METHOD(CpuFixture, "Branch and Exchange", TAG) {
uint32_t raw = 0b11100001001011111111111100011010;
write_register(10, 240);
execute(raw);
fake_pc = 240 + 2 * ARM_INSTRUCTION_SIZE;
REQUIRE(read_register(15) == 240 + 2 * ARM_INSTRUCTION_SIZE);
}
// TODO write BX for when switching to thumb
TEST_CASE_METHOD(CpuFixture, "Branch", TAG) {
uint32_t raw = 0b11101011000000000000000000111100;
uint32_t old_pc = fake_pc;
execute(raw);
fake_pc = old_pc + 240;
// pipeline is flushed
fake_pc += 2 * ARM_INSTRUCTION_SIZE;
REQUIRE(read_register(15) == old_pc + 240 + 2 * ARM_INSTRUCTION_SIZE);
REQUIRE(read_register(14) == old_pc - ARM_INSTRUCTION_SIZE);
}
TEST_CASE_METHOD(CpuFixture, "Multiply", TAG) {
uint32_t raw = 0b11100000001111011100101110011010;
uint32_t result = 0;
write_register(10, 230);
write_register(11, 192);
write_register(12, 37);
execute(raw);
result = 230 * 192 + 37;
REQUIRE(read_register(13) == result);
REQUIRE(read_cpsr().n() == (result >> 31 & 1));
// when product is zero
write_register(10, 230);
write_register(11, 0);
write_register(12, 0);
execute(raw);
REQUIRE(read_register(13) == 0);
REQUIRE(read_cpsr().z() == true);
}
TEST_CASE_METHOD(CpuFixture, "Multiply Long", TAG) {
uint32_t raw = 0b11100000101111011100101110011010;
uint64_t result = 0;
write_register(10, 230, 3); // 2550136835
write_register(11, 192, 12); // 49152
write_register(12, 255, 9); // 4177920
write_register(13, 11, 4); // 184549376
result = 2550136835ull * 49152ull + (184549376ull << 32 | 4177920ull);
execute(raw);
REQUIRE(read_register(12) == (result & 0xFFFFFFFF));
REQUIRE(read_register(13) == (result >> 32 & 0xFFFFFFFF));
REQUIRE(read_cpsr().z() == false);
REQUIRE(read_cpsr().n() == (result >> 63 & 1));
// signed
raw = 0b11100000111111011100101110011010;
write_register(12, 255, 9); // 4177920
write_register(13, 11, 4); // 184549376
execute(raw);
REQUIRE(read_register(12) == (result & 0xFFFFFFFF));
REQUIRE(read_register(13) == (result >> 32 & 0xFFFFFFFF));
REQUIRE(read_cpsr().z() == false);
REQUIRE(read_cpsr().n() == (result >> 63 & 1));
// 0 and no accumulation
raw = 0b11100000110111011100101110011010;
write_register(10, 0);
execute(raw);
REQUIRE(read_register(12) == 0);
REQUIRE(read_register(13) == 0);
REQUIRE(read_cpsr().z() == true);
}
TEST_CASE_METHOD(CpuFixture, "Single Data Swap", TAG) {
write_register(6, 230, 3); // 2550136835
write_register(9, 160, 0); // 160
bus.write_word(read_register(9), 49152);
SECTION("word") {
uint32_t raw = 0b11100001000010010101000010010110;
execute(raw);
REQUIRE(read_register(5) == 49152);
REQUIRE(bus.read_word(read_register(9)) == 2550136835);
}
SECTION("byte") {
uint32_t raw = 0b11100001010010010101000010010110;
execute(raw);
REQUIRE(read_register(5) == (49152 & 0xFF));
REQUIRE(bus.read_byte(read_register(9)) == (2550136835 & 0xFF));
}
}
#undef TAG