#include "cpu/cpu.hh" #include "cpu/utility.hh" #include #include #include #include #include // 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(), std::vector(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 value_d; std::uniform_int_distribution 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(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