Compare commits
7 Commits
build-syst
...
tests
Author | SHA1 | Date | |
---|---|---|---|
ed01ed80cd
|
|||
8e26cadc9a
|
|||
6e56828dfd
|
|||
5fcc75bc9a
|
|||
560bd5bfa1
|
|||
9cdfa90acc
|
|||
91a82eec7c
|
@@ -6,4 +6,5 @@ Checks: '
|
||||
, -cppcoreguidelines-macro-usage
|
||||
, -cppcoreguidelines-avoid-const-or-ref-data-members
|
||||
, -cppcoreguidelines-non-private-member-variables-in-classes
|
||||
, -cppcoreguidelines-avoid-non-const-global-variables
|
||||
'
|
@@ -1,6 +1,7 @@
|
||||
#include "bus.hh"
|
||||
#include "cpu/cpu.hh"
|
||||
#include "memory.hh"
|
||||
#include "util/loglevel.hh"
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
@@ -84,6 +85,8 @@ main(int argc, const char* argv[]) {
|
||||
std::flush(std::cout);
|
||||
std::flush(std::cout);
|
||||
|
||||
matar::set_log_level(matar::LogLevel::Debug);
|
||||
|
||||
try {
|
||||
matar::Memory memory(std::move(bios), std::move(rom));
|
||||
matar::Bus bus(memory);
|
||||
|
@@ -17,12 +17,6 @@ class Memory {
|
||||
uint8_t read(size_t address) const;
|
||||
void write(size_t address, uint8_t byte);
|
||||
|
||||
uint16_t read_halfword(size_t address) const;
|
||||
void write_halfword(size_t address, uint16_t halfword);
|
||||
|
||||
uint32_t read_word(size_t address) const;
|
||||
void write_word(size_t address, uint32_t word);
|
||||
|
||||
private:
|
||||
#define MEMORY_REGION(name, start, end) \
|
||||
static constexpr size_t name##_START = start; \
|
||||
|
@@ -7,5 +7,6 @@ headers = files(
|
||||
inc = include_directories('.')
|
||||
|
||||
subdir('cpu')
|
||||
subdir('util')
|
||||
|
||||
install_headers(headers, subdir: meson.project_name(), preserve_path: true)
|
14
include/util/loglevel.hh
Normal file
14
include/util/loglevel.hh
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace matar {
|
||||
enum class LogLevel {
|
||||
Off = 1 << 0,
|
||||
Error = 1 << 1,
|
||||
Warn = 1 << 2,
|
||||
Info = 1 << 3,
|
||||
Debug = 1 << 4
|
||||
};
|
||||
|
||||
void
|
||||
set_log_level(LogLevel level);
|
||||
}
|
3
include/util/meson.build
Normal file
3
include/util/meson.build
Normal file
@@ -0,0 +1,3 @@
|
||||
headers += files(
|
||||
'loglevel.hh'
|
||||
)
|
@@ -4,7 +4,8 @@ project('matar', 'cpp',
|
||||
default_options : ['warning_level=3',
|
||||
'werror=true',
|
||||
'optimization=3',
|
||||
'cpp_std=c++20'])
|
||||
'cpp_std=c++20',
|
||||
'default_library=static'])
|
||||
|
||||
compiler = meson.get_compiler('cpp')
|
||||
|
||||
|
26
src/bus.cc
26
src/bus.cc
@@ -1,4 +1,5 @@
|
||||
#include "bus.hh"
|
||||
#include "util/log.hh"
|
||||
#include <memory>
|
||||
|
||||
namespace matar {
|
||||
@@ -17,21 +18,38 @@ Bus::write_byte(size_t address, uint8_t byte) {
|
||||
|
||||
uint16_t
|
||||
Bus::read_halfword(size_t address) {
|
||||
return memory->read_halfword(address);
|
||||
if (address & 0b01)
|
||||
glogger.warn("Reading a non aligned halfword address");
|
||||
|
||||
return memory->read(address) | memory->read(address + 1) << 8;
|
||||
}
|
||||
|
||||
void
|
||||
Bus::write_halfword(size_t address, uint16_t halfword) {
|
||||
memory->write_halfword(address, halfword);
|
||||
if (address & 0b01)
|
||||
glogger.warn("Writing to a non aligned halfword address");
|
||||
|
||||
memory->write(address, halfword & 0xFF);
|
||||
memory->write(address + 1, halfword >> 8 & 0xFF);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Bus::read_word(size_t address) {
|
||||
return memory->read_word(address);
|
||||
if (address & 0b11)
|
||||
glogger.warn("Reading a non aligned word address");
|
||||
|
||||
return memory->read(address) | memory->read(address + 1) << 8 |
|
||||
memory->read(address + 2) << 16 | memory->read(address + 3) << 24;
|
||||
}
|
||||
|
||||
void
|
||||
Bus::write_word(size_t address, uint32_t word) {
|
||||
memory->write_word(address, word);
|
||||
if (address & 0b11)
|
||||
glogger.warn("Writing to a non aligned word address");
|
||||
|
||||
memory->write(address, word & 0xFF);
|
||||
memory->write(address + 1, word >> 8 & 0xFF);
|
||||
memory->write(address + 2, word >> 16 & 0xFF);
|
||||
memory->write(address + 3, word >> 24 & 0xFF);
|
||||
}
|
||||
}
|
||||
|
@@ -2,27 +2,24 @@
|
||||
#include "util/bits.hh"
|
||||
#include "util/log.hh"
|
||||
|
||||
using namespace logger;
|
||||
|
||||
namespace matar {
|
||||
void
|
||||
CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
Condition cond = instruction.condition;
|
||||
arm::InstructionData data = instruction.data;
|
||||
|
||||
debug(cpsr.condition(cond));
|
||||
if (!cpsr.condition(cond)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pc_error = [](uint8_t r) {
|
||||
if (r == PC_INDEX)
|
||||
log_error("Using PC (R15) as operand register");
|
||||
glogger.error("Using PC (R15) as operand register");
|
||||
};
|
||||
|
||||
auto pc_warn = [](uint8_t r) {
|
||||
if (r == PC_INDEX)
|
||||
log_warn("Using PC (R15) as operand register");
|
||||
glogger.warn("Using PC (R15) as operand register");
|
||||
};
|
||||
|
||||
using namespace arm;
|
||||
@@ -62,8 +59,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
},
|
||||
[this, pc_error](Multiply& data) {
|
||||
if (data.rd == data.rm)
|
||||
log_error("rd and rm are not distinct in {}",
|
||||
typeid(data).name());
|
||||
glogger.error("rd and rm are not distinct in {}",
|
||||
typeid(data).name());
|
||||
|
||||
pc_error(data.rd);
|
||||
pc_error(data.rd);
|
||||
@@ -81,8 +78,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
[this, pc_error](MultiplyLong& data) {
|
||||
if (data.rdhi == data.rdlo || data.rdhi == data.rm ||
|
||||
data.rdlo == data.rm)
|
||||
log_error("rdhi, rdlo and rm are not distinct in {}",
|
||||
typeid(data).name());
|
||||
glogger.error("rdhi, rdlo and rm are not distinct in {}",
|
||||
typeid(data).name());
|
||||
|
||||
pc_error(data.rdhi);
|
||||
pc_error(data.rdlo);
|
||||
@@ -123,7 +120,7 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
cpsr.set_v(0);
|
||||
}
|
||||
},
|
||||
[](Undefined) { log_warn("Undefined instruction"); },
|
||||
[](Undefined) { glogger.warn("Undefined instruction"); },
|
||||
[this, pc_error](SingleDataSwap& data) {
|
||||
pc_error(data.rm);
|
||||
pc_error(data.rn);
|
||||
@@ -142,12 +139,12 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
uint32_t address = gpr[data.rn];
|
||||
|
||||
if (!data.pre && data.write)
|
||||
log_warn("Write-back enabled with post-indexing in {}",
|
||||
typeid(data).name());
|
||||
glogger.warn("Write-back enabled with post-indexing in {}",
|
||||
typeid(data).name());
|
||||
|
||||
if (data.rn == PC_INDEX && data.write)
|
||||
log_warn("Write-back enabled with base register as PC {}",
|
||||
typeid(data).name());
|
||||
glogger.warn("Write-back enabled with base register as PC {}",
|
||||
typeid(data).name());
|
||||
|
||||
if (data.write)
|
||||
pc_warn(data.rn);
|
||||
@@ -216,11 +213,11 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
uint32_t offset = 0;
|
||||
|
||||
if (!data.pre && data.write)
|
||||
log_error("Write-back enabled with post-indexing in {}",
|
||||
typeid(data).name());
|
||||
glogger.error("Write-back enabled with post-indexing in {}",
|
||||
typeid(data).name());
|
||||
|
||||
if (data.sign && !data.load)
|
||||
log_error("Signed data found in {}", typeid(data).name());
|
||||
glogger.error("Signed data found in {}", typeid(data).name());
|
||||
|
||||
if (data.write)
|
||||
pc_warn(data.rn);
|
||||
@@ -294,8 +291,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
pc_error(data.rn);
|
||||
|
||||
if (cpsr.mode() == Mode::User && data.s) {
|
||||
log_error("Bit S is set outside priviliged modes in {}",
|
||||
typeid(data).name());
|
||||
glogger.error("Bit S is set outside priviliged modes in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
|
||||
// we just change modes to load user registers
|
||||
@@ -304,8 +301,9 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
chg_mode(Mode::User);
|
||||
|
||||
if (data.write) {
|
||||
log_error("Write-back enable for user bank registers in {}",
|
||||
typeid(data).name());
|
||||
glogger.error(
|
||||
"Write-back enable for user bank registers in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,8 +356,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
},
|
||||
[this, pc_error](PsrTransfer& data) {
|
||||
if (data.spsr && cpsr.mode() == Mode::User) {
|
||||
log_error("Accessing SPSR in User mode in {}",
|
||||
typeid(data).name());
|
||||
glogger.error("Accessing SPSR in User mode in {}",
|
||||
typeid(data).name());
|
||||
}
|
||||
|
||||
Psr& psr = data.spsr ? spsr : cpsr;
|
||||
@@ -513,8 +511,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
if (data.set) {
|
||||
if (data.rd == PC_INDEX) {
|
||||
if (cpsr.mode() == Mode::User)
|
||||
log_error("Running {} in User mode",
|
||||
typeid(data).name());
|
||||
glogger.error("Running {} in User mode",
|
||||
typeid(data).name());
|
||||
spsr = cpsr;
|
||||
} else {
|
||||
set_conditions();
|
||||
@@ -536,7 +534,7 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
||||
spsr = cpsr;
|
||||
},
|
||||
[](auto& data) {
|
||||
log_error("Unimplemented {} instruction", typeid(data).name());
|
||||
glogger.error("Unimplemented {} instruction", typeid(data).name());
|
||||
} },
|
||||
data);
|
||||
}
|
||||
|
@@ -4,8 +4,6 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace logger;
|
||||
|
||||
namespace matar {
|
||||
CpuImpl::CpuImpl(const Bus& bus) noexcept
|
||||
: bus(std::make_shared<Bus>(bus))
|
||||
@@ -19,7 +17,7 @@ CpuImpl::CpuImpl(const Bus& bus) noexcept
|
||||
cpsr.set_irq_disabled(true);
|
||||
cpsr.set_fiq_disabled(true);
|
||||
cpsr.set_state(State::Arm);
|
||||
log_info("CPU successfully initialised");
|
||||
glogger.info("CPU successfully initialised");
|
||||
|
||||
// PC always points to two instructions ahead
|
||||
// PC - 2 is the instruction being executed
|
||||
@@ -121,14 +119,13 @@ CpuImpl::step() {
|
||||
uint32_t cur_pc = pc - 2 * arm::INSTRUCTION_SIZE;
|
||||
|
||||
if (cpsr.state() == State::Arm) {
|
||||
debug(cur_pc);
|
||||
uint32_t x = bus->read_word(cur_pc);
|
||||
arm::Instruction instruction(x);
|
||||
log_info("{:#034b}", x);
|
||||
glogger.info("{:#034b}", x);
|
||||
|
||||
exec_arm(instruction);
|
||||
|
||||
log_info("0x{:08X} : {}", cur_pc, instruction.disassemble());
|
||||
glogger.info("0x{:08X} : {}", cur_pc, instruction.disassemble());
|
||||
|
||||
if (is_flushed) {
|
||||
// if flushed, do not increment the PC, instead set it to two
|
||||
|
@@ -1,13 +1,11 @@
|
||||
#include "memory.hh"
|
||||
#include "header.hh"
|
||||
#include "util/bits.hh"
|
||||
#include "util/crypto.hh"
|
||||
#include "util/log.hh"
|
||||
#include "util/utils.hh"
|
||||
#include <bitset>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace logger;
|
||||
|
||||
namespace matar {
|
||||
Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
|
||||
std::vector<uint8_t>&& rom)
|
||||
@@ -23,17 +21,17 @@ Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
|
||||
"fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570";
|
||||
|
||||
if (bios_hash != expected_hash) {
|
||||
log_warn("BIOS hash failed to match, run at your own risk"
|
||||
"\nExpected : {} "
|
||||
"\nGot : {}",
|
||||
expected_hash,
|
||||
bios_hash);
|
||||
glogger.warn("BIOS hash failed to match, run at your own risk"
|
||||
"\nExpected : {} "
|
||||
"\nGot : {}",
|
||||
expected_hash,
|
||||
bios_hash);
|
||||
}
|
||||
|
||||
parse_header();
|
||||
|
||||
log_info("Memory successfully initialised");
|
||||
log_info("Cartridge Title: {}", header.title);
|
||||
glogger.info("Memory successfully initialised");
|
||||
glogger.info("Cartridge Title: {}", header.title);
|
||||
};
|
||||
|
||||
#define MATCHES(area) address >= area##_START&& address <= area##_END
|
||||
@@ -59,7 +57,7 @@ Memory::read(size_t address) const {
|
||||
} else if (MATCHES(ROM_2)) {
|
||||
return rom[address - ROM_2_START];
|
||||
} else {
|
||||
log_error("Invalid memory region accessed");
|
||||
glogger.error("Invalid memory region accessed");
|
||||
return 0xFF;
|
||||
}
|
||||
}
|
||||
@@ -85,49 +83,12 @@ Memory::write(size_t address, uint8_t byte) {
|
||||
} else if (MATCHES(ROM_2)) {
|
||||
rom[address - ROM_2_START] = byte;
|
||||
} else {
|
||||
log_error("Invalid memory region accessed");
|
||||
glogger.error("Invalid memory region accessed");
|
||||
}
|
||||
}
|
||||
|
||||
#undef MATCHES
|
||||
|
||||
uint16_t
|
||||
Memory::read_halfword(size_t address) const {
|
||||
if (address & 0b01)
|
||||
log_warn("Reading a non aligned halfword address");
|
||||
|
||||
return read(address) | read(address + 1) << 8;
|
||||
}
|
||||
|
||||
void
|
||||
Memory::write_halfword(size_t address, uint16_t halfword) {
|
||||
if (address & 0b01)
|
||||
log_warn("Writing to a non aligned halfword address");
|
||||
|
||||
write(address, halfword & 0xFF);
|
||||
write(address + 1, halfword >> 8 & 0xFF);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Memory::read_word(size_t address) const {
|
||||
if (address & 0b11)
|
||||
log_warn("Reading a non aligned word address");
|
||||
|
||||
return read(address) | read(address + 1) << 8 | read(address + 2) << 16 |
|
||||
read(address + 3) << 24;
|
||||
}
|
||||
|
||||
void
|
||||
Memory::write_word(size_t address, uint32_t word) {
|
||||
if (address & 0b11)
|
||||
log_warn("Writing to a non aligned word address");
|
||||
|
||||
write(address, word & 0xFF);
|
||||
write(address + 1, word >> 8 & 0xFF);
|
||||
write(address + 2, word >> 16 & 0xFF);
|
||||
write(address + 3, word >> 24 & 0xFF);
|
||||
}
|
||||
|
||||
void
|
||||
Memory::parse_header() {
|
||||
|
||||
@@ -142,7 +103,7 @@ Memory::parse_header() {
|
||||
|
||||
// nintendo logo
|
||||
if (rom[0x9C] != 0x21)
|
||||
log_info("HEADER: BIOS debugger bits not set to 0");
|
||||
glogger.info("HEADER: BIOS debugger bits not set to 0");
|
||||
|
||||
// game info
|
||||
header.title = std::string(&rom[0xA0], &rom[0xA0 + 12]);
|
||||
@@ -177,7 +138,7 @@ Memory::parse_header() {
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("HEADER: invalid unique code: {}", rom[0xAC]);
|
||||
glogger.error("HEADER: invalid unique code: {}", rom[0xAC]);
|
||||
}
|
||||
|
||||
header.title_code = std::string(&rom[0xAD], &rom[0xAE]);
|
||||
@@ -206,15 +167,16 @@ Memory::parse_header() {
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("HEADER: invalid destination/language: {}", rom[0xAF]);
|
||||
glogger.error("HEADER: invalid destination/language: {}",
|
||||
rom[0xAF]);
|
||||
}
|
||||
|
||||
if (rom[0xB2] != 0x96)
|
||||
log_error("HEADER: invalid fixed byte at 0xB2");
|
||||
glogger.error("HEADER: invalid fixed byte at 0xB2");
|
||||
|
||||
for (size_t i = 0xB5; i < 0xBC; i++) {
|
||||
if (rom[i] != 0x00)
|
||||
log_error("HEADER: invalid fixed bytes at 0xB5");
|
||||
glogger.error("HEADER: invalid fixed bytes at 0xB5");
|
||||
}
|
||||
|
||||
header.version = rom[0xBC];
|
||||
@@ -228,7 +190,7 @@ Memory::parse_header() {
|
||||
chk &= 0xFF;
|
||||
|
||||
if (chk != rom[0xBD])
|
||||
log_error("HEADER: checksum does not match");
|
||||
glogger.error("HEADER: checksum does not match");
|
||||
}
|
||||
|
||||
// multiboot not required right now
|
||||
|
@@ -3,9 +3,9 @@ lib_sources = files(
|
||||
'bus.cc'
|
||||
)
|
||||
|
||||
subdir('util')
|
||||
subdir('cpu')
|
||||
|
||||
|
||||
lib_cpp_args = [ ]
|
||||
|
||||
fmt = dependency('fmt', version : '>=10.1.0', static: true)
|
||||
|
@@ -14,19 +14,19 @@ get_bit(Int num, size_t n) {
|
||||
template<std::integral Int>
|
||||
inline void
|
||||
set_bit(Int& num, size_t n) {
|
||||
num |= (1 << n);
|
||||
num |= (static_cast<Int>(1) << n);
|
||||
}
|
||||
|
||||
template<std::integral Int>
|
||||
inline void
|
||||
rst_bit(Int& num, size_t n) {
|
||||
num &= ~(1 << n);
|
||||
num &= ~(static_cast<Int>(1) << n);
|
||||
}
|
||||
|
||||
template<std::integral Int>
|
||||
inline void
|
||||
chg_bit(Int& num, size_t n, bool x) {
|
||||
num = (num & ~(1 << n)) | (x << n);
|
||||
num = (num & ~(static_cast<Int>(1) << n)) | (static_cast<Int>(x) << n);
|
||||
}
|
||||
|
||||
/// read range of bits from start to end inclusive
|
||||
@@ -36,5 +36,5 @@ bit_range(Int num, size_t start, size_t end) {
|
||||
// NOTE: we do not require -1 if it is a signed integral
|
||||
Int left =
|
||||
std::numeric_limits<Int>::digits - (std::is_unsigned<Int>::value) - end;
|
||||
return num << left >> (left + start);
|
||||
return static_cast<Int>(num << left) >> (left + start);
|
||||
}
|
||||
|
8
src/util/log.cc
Normal file
8
src/util/log.cc
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "log.hh"
|
||||
|
||||
logging::Logger glogger = logging::Logger();
|
||||
|
||||
void
|
||||
matar::set_log_level(LogLevel level) {
|
||||
glogger.set_level(level);
|
||||
}
|
119
src/util/log.hh
119
src/util/log.hh
@@ -1,58 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/loglevel.hh"
|
||||
#include <fmt/ostream.h>
|
||||
#include <iostream>
|
||||
|
||||
using fmt::print;
|
||||
using std::clog;
|
||||
|
||||
namespace logger {
|
||||
namespace logging {
|
||||
namespace ansi {
|
||||
static constexpr std::string_view RED = "\033[31m";
|
||||
static constexpr std::string_view YELLOW = "\033[33m";
|
||||
static constexpr std::string_view MAGENTA = "\033[35m";
|
||||
static constexpr std::string_view WHITE = "\033[37m";
|
||||
static constexpr std::string_view BOLD = "\033[1m";
|
||||
static constexpr std::string_view RESET = "\033[0m";
|
||||
static constexpr auto RED = "\033[31m";
|
||||
static constexpr auto YELLOW = "\033[33m";
|
||||
static constexpr auto MAGENTA = "\033[35m";
|
||||
static constexpr auto WHITE = "\033[37m";
|
||||
static constexpr auto BOLD = "\033[1m";
|
||||
static constexpr auto RESET = "\033[0m";
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void
|
||||
log_raw(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
fmt::println(clog, fmt, std::forward<Args>(args)...);
|
||||
using fmt::print;
|
||||
|
||||
class Logger {
|
||||
using LogLevel = matar::LogLevel;
|
||||
|
||||
public:
|
||||
Logger(LogLevel level = LogLevel::Debug, FILE* stream = stderr)
|
||||
: level(0)
|
||||
, stream(stream) {
|
||||
set_level(level);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void log(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
fmt::println(stream, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void debug(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
if (level & static_cast<uint8_t>(LogLevel::Debug)) {
|
||||
print(stream, "{}{}[DEBUG] ", ansi::MAGENTA, ansi::BOLD);
|
||||
log(fmt, std::forward<Args>(args)...);
|
||||
print(stream, ansi::RESET);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void info(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
if (level & static_cast<uint8_t>(LogLevel::Info)) {
|
||||
print(stream, "{}[INFO] ", ansi::WHITE);
|
||||
log(fmt, std::forward<Args>(args)...);
|
||||
print(stream, ansi::RESET);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void warn(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
if (level & static_cast<uint8_t>(LogLevel::Warn)) {
|
||||
print(stream, "{}[WARN] ", ansi::YELLOW);
|
||||
log(fmt, std::forward<Args>(args)...);
|
||||
print(stream, ansi::RESET);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void error(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
if (level & static_cast<uint8_t>(LogLevel::Error)) {
|
||||
print(stream, "{}{}[ERROR] ", ansi::RED, ansi::BOLD);
|
||||
log(fmt, std::forward<Args>(args)...);
|
||||
print(stream, ansi::RESET);
|
||||
}
|
||||
}
|
||||
|
||||
void set_level(LogLevel level) {
|
||||
this->level = (static_cast<uint8_t>(level) << 1) - 1;
|
||||
}
|
||||
void set_stream(FILE* stream) { this->stream = stream; }
|
||||
|
||||
private:
|
||||
uint8_t level;
|
||||
FILE* stream;
|
||||
};
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void
|
||||
log_debug(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
print(clog, "{}{}[DEBUG] ", ansi::MAGENTA, ansi::BOLD);
|
||||
log_raw(fmt, std::forward<Args>(args)...);
|
||||
print(clog, ansi::RESET);
|
||||
}
|
||||
extern logging::Logger glogger;
|
||||
|
||||
template<typename... Args>
|
||||
inline void
|
||||
log_info(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
print(clog, "{}[INFO] ", ansi::WHITE);
|
||||
log_raw(fmt, std::forward<Args>(args)...);
|
||||
print(clog, ansi::RESET);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void
|
||||
log_warn(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
print(clog, "{}[WARN] ", ansi::YELLOW);
|
||||
log_raw(fmt, std::forward<Args>(args)...);
|
||||
print(clog, ansi::RESET);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void
|
||||
log_error(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
||||
print(clog, "{}{}[ERROR] ", ansi::RED, ansi::BOLD);
|
||||
log_raw(fmt, std::forward<Args>(args)...);
|
||||
print(clog, ansi::RESET);
|
||||
}
|
||||
}
|
||||
|
||||
#define debug(value) logger::log_debug("{} = {}", #value, value)
|
||||
#define debug(x) glogger.debug("{} = {}", #x, x);
|
||||
|
3
src/util/meson.build
Normal file
3
src/util/meson.build
Normal file
@@ -0,0 +1,3 @@
|
||||
lib_sources += files(
|
||||
'log.cc'
|
||||
)
|
43
tests/bus.cc
Normal file
43
tests/bus.cc
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "bus.hh"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
static constexpr auto TAG = "[bus]";
|
||||
|
||||
using namespace matar;
|
||||
|
||||
class BusFixture {
|
||||
public:
|
||||
BusFixture()
|
||||
: bus(Memory(std::array<uint8_t, Memory::BIOS_SIZE>(),
|
||||
std::vector<uint8_t>(Header::HEADER_SIZE))) {}
|
||||
|
||||
protected:
|
||||
Bus bus;
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(BusFixture, "Byte", TAG) {
|
||||
CHECK(bus.read_byte(3349) == 0);
|
||||
|
||||
bus.write_byte(3349, 0xEC);
|
||||
CHECK(bus.read_byte(3349) == 0xEC);
|
||||
CHECK(bus.read_word(3349) == 0xEC);
|
||||
CHECK(bus.read_halfword(3349) == 0xEC);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(BusFixture, "Halfword", TAG) {
|
||||
CHECK(bus.read_halfword(33750745) == 0);
|
||||
|
||||
bus.write_halfword(33750745, 0x1A4A);
|
||||
CHECK(bus.read_halfword(33750745) == 0x1A4A);
|
||||
CHECK(bus.read_word(33750745) == 0x1A4A);
|
||||
CHECK(bus.read_byte(33750745) == 0x4A);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(BusFixture, "Word", TAG) {
|
||||
CHECK(bus.read_word(100724276) == 0);
|
||||
|
||||
bus.write_word(100724276, 0x3ACC491D);
|
||||
CHECK(bus.read_word(100724276) == 0x3ACC491D);
|
||||
CHECK(bus.read_halfword(100724276) == 0x491D);
|
||||
CHECK(bus.read_byte(100724276) == 0x1D);
|
||||
}
|
@@ -13,7 +13,6 @@ class CpuFixture {
|
||||
std::vector<uint8_t>(Header::HEADER_SIZE)))) {}
|
||||
|
||||
protected:
|
||||
// TODO: test with other conditions
|
||||
void exec(arm::InstructionData data, Condition condition = Condition::AL) {
|
||||
arm::Instruction instruction(condition, data);
|
||||
cpu.exec_arm(instruction);
|
||||
@@ -32,7 +31,7 @@ class CpuFixture {
|
||||
};
|
||||
};
|
||||
|
||||
#define TAG "arm execution"
|
||||
static constexpr auto TAG = "[arm][execution]";
|
||||
|
||||
using namespace arm;
|
||||
|
||||
@@ -804,29 +803,41 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
||||
processing->rn = 7;
|
||||
}
|
||||
|
||||
auto flags = [this](bool n, bool z, bool v, bool c) {
|
||||
CHECK(cpu.cpsr.n() == n);
|
||||
CHECK(cpu.cpsr.z() == z);
|
||||
CHECK(cpu.cpsr.v() == v);
|
||||
CHECK(cpu.cpsr.c() == c);
|
||||
|
||||
auto reset_flags = [this]() {
|
||||
cpu.cpsr.set_n(false);
|
||||
cpu.cpsr.set_z(false);
|
||||
cpu.cpsr.set_v(false);
|
||||
cpu.cpsr.set_c(false);
|
||||
};
|
||||
|
||||
auto flags = [this, reset_flags](bool n, bool z, bool v, bool c) {
|
||||
CHECK(cpu.cpsr.n() == n);
|
||||
CHECK(cpu.cpsr.z() == z);
|
||||
CHECK(cpu.cpsr.v() == v);
|
||||
CHECK(cpu.cpsr.c() == c);
|
||||
reset_flags();
|
||||
};
|
||||
|
||||
// immediate operand
|
||||
processing->operand = static_cast<uint32_t>(54924809);
|
||||
// rs
|
||||
cpu.gpr[12] = 2;
|
||||
cpu.gpr[5] = 0;
|
||||
reset_flags();
|
||||
|
||||
SECTION("AND") {
|
||||
SECTION("AND (with condition check)") {
|
||||
processing->opcode = OpCode::AND;
|
||||
exec(data);
|
||||
cpu.cpsr.set_z(false);
|
||||
exec(data, Condition::EQ);
|
||||
|
||||
// condition is false
|
||||
CHECK(cpu.gpr[5] == 0);
|
||||
|
||||
cpu.cpsr.set_z(true);
|
||||
exec(data, Condition::EQ);
|
||||
|
||||
// -28717 & 54924809
|
||||
// condition is true now
|
||||
CHECK(cpu.gpr[5] == 54920705);
|
||||
|
||||
// check set flags
|
||||
@@ -846,11 +857,19 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
||||
flags(false, false, false, false);
|
||||
}
|
||||
|
||||
SECTION("EOR") {
|
||||
SECTION("EOR (with condition check)") {
|
||||
processing->opcode = OpCode::EOR;
|
||||
exec(data);
|
||||
cpu.cpsr.set_c(true);
|
||||
exec(data, Condition::CC);
|
||||
|
||||
// condition fails
|
||||
CHECK(cpu.gpr[5] == 0);
|
||||
|
||||
cpu.cpsr.set_c(false);
|
||||
exec(data, Condition::CC);
|
||||
|
||||
// -28717 ^ 54924809
|
||||
// condition is true now
|
||||
CHECK(cpu.gpr[5] == 4240021978);
|
||||
|
||||
// check set flags
|
||||
@@ -1051,5 +1070,3 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
||||
CHECK(cpu.spsr.raw() == cpu.cpsr.raw());
|
||||
}
|
||||
}
|
||||
|
||||
#undef TAG
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#include "cpu/arm/instruction.hh"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#define TAG "disassembler"
|
||||
static constexpr auto TAG = "[arm][disassembly]";
|
||||
|
||||
using namespace matar;
|
||||
using namespace arm;
|
||||
@@ -467,5 +467,3 @@ TEST_CASE("Software Interrupt", TAG) {
|
||||
CHECK(instruction.condition == Condition::EQ);
|
||||
CHECK(instruction.disassemble() == "SWIEQ");
|
||||
}
|
||||
|
||||
#undef TAG
|
||||
|
8
tests/main.cc
Normal file
8
tests/main.cc
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "util/loglevel.hh"
|
||||
#include <catch2/catch_session.hpp>
|
||||
|
||||
int
|
||||
main(int argc, char* argv[]) {
|
||||
matar::set_log_level(matar::LogLevel::Off);
|
||||
return Catch::Session().run(argc, argv);
|
||||
}
|
121
tests/memory.cc
Normal file
121
tests/memory.cc
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "memory.hh"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
static constexpr auto TAG = "[memory]";
|
||||
|
||||
using namespace matar;
|
||||
|
||||
class MemFixture {
|
||||
public:
|
||||
MemFixture()
|
||||
: memory(std::array<uint8_t, Memory::BIOS_SIZE>(),
|
||||
std::vector<uint8_t>(Header::HEADER_SIZE)) {}
|
||||
|
||||
protected:
|
||||
Memory memory;
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(MemFixture, "bios", TAG) {
|
||||
memory.write(0, 0xAC);
|
||||
CHECK(memory.read(0) == 0xAC);
|
||||
|
||||
memory.write(0x3FFF, 0x48);
|
||||
CHECK(memory.read(0x3FFF) == 0x48);
|
||||
|
||||
memory.write(0x2A56, 0x10);
|
||||
CHECK(memory.read(0x2A56) == 0x10);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(MemFixture, "board wram", TAG) {
|
||||
memory.write(0x2000000, 0xAC);
|
||||
CHECK(memory.read(0x2000000) == 0xAC);
|
||||
|
||||
memory.write(0x203FFFF, 0x48);
|
||||
CHECK(memory.read(0x203FFFF) == 0x48);
|
||||
|
||||
memory.write(0x2022A56, 0x10);
|
||||
CHECK(memory.read(0x2022A56) == 0x10);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(MemFixture, "chip wram", TAG) {
|
||||
memory.write(0x3000000, 0xAC);
|
||||
CHECK(memory.read(0x3000000) == 0xAC);
|
||||
|
||||
memory.write(0x3007FFF, 0x48);
|
||||
CHECK(memory.read(0x3007FFF) == 0x48);
|
||||
|
||||
memory.write(0x3002A56, 0x10);
|
||||
CHECK(memory.read(0x3002A56) == 0x10);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(MemFixture, "palette ram", TAG) {
|
||||
memory.write(0x5000000, 0xAC);
|
||||
CHECK(memory.read(0x5000000) == 0xAC);
|
||||
|
||||
memory.write(0x50003FF, 0x48);
|
||||
CHECK(memory.read(0x50003FF) == 0x48);
|
||||
|
||||
memory.write(0x5000156, 0x10);
|
||||
CHECK(memory.read(0x5000156) == 0x10);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(MemFixture, "video ram", TAG) {
|
||||
memory.write(0x6000000, 0xAC);
|
||||
CHECK(memory.read(0x6000000) == 0xAC);
|
||||
|
||||
memory.write(0x6017FFF, 0x48);
|
||||
CHECK(memory.read(0x6017FFF) == 0x48);
|
||||
|
||||
memory.write(0x6012A56, 0x10);
|
||||
CHECK(memory.read(0x6012A56) == 0x10);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(MemFixture, "oam obj ram", TAG) {
|
||||
memory.write(0x7000000, 0xAC);
|
||||
CHECK(memory.read(0x7000000) == 0xAC);
|
||||
|
||||
memory.write(0x70003FF, 0x48);
|
||||
CHECK(memory.read(0x70003FF) == 0x48);
|
||||
|
||||
memory.write(0x7000156, 0x10);
|
||||
CHECK(memory.read(0x7000156) == 0x10);
|
||||
}
|
||||
|
||||
TEST_CASE("rom", TAG) {
|
||||
// 32 megabyte ROM
|
||||
Memory memory(std::array<uint8_t, Memory::BIOS_SIZE>(),
|
||||
std::vector<uint8_t>(32 * 1024 * 1024));
|
||||
|
||||
SECTION("ROM1") {
|
||||
memory.write(0x8000000, 0xAC);
|
||||
CHECK(memory.read(0x8000000) == 0xAC);
|
||||
|
||||
memory.write(0x9FFFFFF, 0x48);
|
||||
CHECK(memory.read(0x9FFFFFF) == 0x48);
|
||||
|
||||
memory.write(0x8ef0256, 0x10);
|
||||
CHECK(memory.read(0x8ef0256) == 0x10);
|
||||
}
|
||||
|
||||
SECTION("ROM2") {
|
||||
memory.write(0xA000000, 0xAC);
|
||||
CHECK(memory.read(0xA000000) == 0xAC);
|
||||
|
||||
memory.write(0xBFFFFFF, 0x48);
|
||||
CHECK(memory.read(0xBFFFFFF) == 0x48);
|
||||
|
||||
memory.write(0xAEF0256, 0x10);
|
||||
CHECK(memory.read(0xAEF0256) == 0x10);
|
||||
}
|
||||
|
||||
SECTION("ROM3") {
|
||||
memory.write(0xC000000, 0xAC);
|
||||
CHECK(memory.read(0xC000000) == 0xAC);
|
||||
|
||||
memory.write(0xDFFFFFF, 0x48);
|
||||
CHECK(memory.read(0xDFFFFFF) == 0x48);
|
||||
|
||||
memory.write(0xCEF0256, 0x10);
|
||||
CHECK(memory.read(0xCEF0256) == 0x10);
|
||||
}
|
||||
}
|
@@ -4,11 +4,16 @@ tests_deps = [
|
||||
|
||||
src = include_directories('../src')
|
||||
|
||||
tests_sources = files()
|
||||
tests_sources = files(
|
||||
'main.cc',
|
||||
'bus.cc',
|
||||
'memory.cc'
|
||||
)
|
||||
|
||||
subdir('cpu')
|
||||
subdir('util')
|
||||
|
||||
catch2 = dependency('catch2-with-main', version: '>=3.4.0', static: true)
|
||||
catch2 = dependency('catch2', version: '>=3.4.0', static: true)
|
||||
catch2_tests = executable(
|
||||
'matar_tests',
|
||||
tests_sources,
|
||||
|
106
tests/util/bits.cc
Normal file
106
tests/util/bits.cc
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "util/bits.hh"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
static constexpr auto TAG = "[util][bits]";
|
||||
|
||||
TEST_CASE("8 bits", TAG) {
|
||||
uint8_t num = 45;
|
||||
|
||||
CHECK(get_bit(num, 0));
|
||||
CHECK(!get_bit(num, 1));
|
||||
CHECK(get_bit(num, 5));
|
||||
CHECK(!get_bit(num, 6));
|
||||
CHECK(!get_bit(num, 7));
|
||||
|
||||
set_bit(num, 6);
|
||||
CHECK(get_bit(num, 6));
|
||||
|
||||
rst_bit(num, 6);
|
||||
CHECK(!get_bit(num, 6));
|
||||
|
||||
chg_bit(num, 5, false);
|
||||
CHECK(!get_bit(num, 5));
|
||||
|
||||
chg_bit(num, 5, true);
|
||||
CHECK(get_bit(num, 5));
|
||||
|
||||
// 0b0110
|
||||
CHECK(bit_range(num, 1, 4) == 6);
|
||||
}
|
||||
|
||||
TEST_CASE("16 bits", TAG) {
|
||||
uint16_t num = 34587;
|
||||
|
||||
CHECK(get_bit(num, 0));
|
||||
CHECK(get_bit(num, 1));
|
||||
CHECK(!get_bit(num, 5));
|
||||
CHECK(!get_bit(num, 14));
|
||||
CHECK(get_bit(num, 15));
|
||||
|
||||
set_bit(num, 14);
|
||||
CHECK(get_bit(num, 14));
|
||||
|
||||
rst_bit(num, 14);
|
||||
CHECK(!get_bit(num, 14));
|
||||
|
||||
chg_bit(num, 5, true);
|
||||
CHECK(get_bit(num, 5));
|
||||
|
||||
// num = 45
|
||||
chg_bit(num, 5, false);
|
||||
CHECK(!get_bit(num, 5));
|
||||
|
||||
// 0b1000110
|
||||
CHECK(bit_range(num, 2, 8) == 70);
|
||||
}
|
||||
|
||||
TEST_CASE("32 bits", TAG) {
|
||||
uint32_t num = 3194142523;
|
||||
|
||||
CHECK(get_bit(num, 0));
|
||||
CHECK(get_bit(num, 1));
|
||||
CHECK(get_bit(num, 12));
|
||||
CHECK(get_bit(num, 29));
|
||||
CHECK(!get_bit(num, 30));
|
||||
CHECK(get_bit(num, 31));
|
||||
|
||||
set_bit(num, 30);
|
||||
CHECK(get_bit(num, 30));
|
||||
|
||||
rst_bit(num, 30);
|
||||
CHECK(!get_bit(num, 30));
|
||||
|
||||
chg_bit(num, 12, false);
|
||||
CHECK(!get_bit(num, 12));
|
||||
|
||||
chg_bit(num, 12, true);
|
||||
CHECK(get_bit(num, 12));
|
||||
|
||||
// 0b10011000101011111100111
|
||||
CHECK(bit_range(num, 3, 25) == 5003239);
|
||||
}
|
||||
|
||||
TEST_CASE("64 bits", TAG) {
|
||||
uint64_t num = 58943208889991935;
|
||||
|
||||
CHECK(get_bit(num, 0));
|
||||
CHECK(get_bit(num, 1));
|
||||
CHECK(!get_bit(num, 10));
|
||||
CHECK(get_bit(num, 55));
|
||||
CHECK(!get_bit(num, 60));
|
||||
|
||||
set_bit(num, 63);
|
||||
CHECK(get_bit(num, 63));
|
||||
|
||||
rst_bit(num, 63);
|
||||
CHECK(!get_bit(num, 63));
|
||||
|
||||
chg_bit(num, 10, true);
|
||||
CHECK(get_bit(num, 10));
|
||||
|
||||
chg_bit(num, 10, false);
|
||||
CHECK(!get_bit(num, 10));
|
||||
|
||||
// 0b011010001
|
||||
CHECK(bit_range(num, 39, 47) == 209);
|
||||
}
|
21
tests/util/crypto.cc
Normal file
21
tests/util/crypto.cc
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "util/crypto.hh"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
static constexpr auto TAG = "[util][crypto]";
|
||||
|
||||
TEST_CASE("sha256 matar", TAG) {
|
||||
std::array<uint8_t, 5> data = { 'm', 'a', 't', 'a', 'r' };
|
||||
|
||||
CHECK(crypto::sha256(data) ==
|
||||
"3b02a908fd5743c0e868675bb6ae77d2a62b3b5f7637413238e2a1e0e94b6a53");
|
||||
}
|
||||
|
||||
TEST_CASE("sha256 forgis", TAG) {
|
||||
std::array<uint8_t, 32> data = { 'i', ' ', 'p', 'u', 't', ' ', 't', 'h',
|
||||
'e', ' ', 'n', 'e', 'w', ' ', 'f', 'o',
|
||||
'r', 'g', 'i', 's', ' ', 'o', 'n', ' ',
|
||||
't', 'h', 'e', ' ', 'j', 'e', 'e', 'p' };
|
||||
|
||||
CHECK(crypto::sha256(data) ==
|
||||
"cfddca2ce2673f355518cbe2df2a8522693c54723a469e8b36a4f68b90d2b759");
|
||||
}
|
4
tests/util/meson.build
Normal file
4
tests/util/meson.build
Normal file
@@ -0,0 +1,4 @@
|
||||
tests_sources += files(
|
||||
'bits.cc',
|
||||
'crypto.cc'
|
||||
)
|
Reference in New Issue
Block a user