Compare commits
11 Commits
build-syst
...
6822e1255a
| Author | SHA1 | Date | |
|---|---|---|---|
|
6822e1255a
|
|||
|
bd91112509
|
|||
|
1baebd72f6
|
|||
|
b55f6ee16b
|
|||
|
ed01ed80cd
|
|||
|
8e26cadc9a
|
|||
|
6e56828dfd
|
|||
|
5fcc75bc9a
|
|||
|
560bd5bfa1
|
|||
|
9cdfa90acc
|
|||
|
91a82eec7c
|
@@ -6,4 +6,5 @@ Checks: '
|
|||||||
, -cppcoreguidelines-macro-usage
|
, -cppcoreguidelines-macro-usage
|
||||||
, -cppcoreguidelines-avoid-const-or-ref-data-members
|
, -cppcoreguidelines-avoid-const-or-ref-data-members
|
||||||
, -cppcoreguidelines-non-private-member-variables-in-classes
|
, -cppcoreguidelines-non-private-member-variables-in-classes
|
||||||
|
, -cppcoreguidelines-avoid-non-const-global-variables
|
||||||
'
|
'
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "bus.hh"
|
#include "bus.hh"
|
||||||
#include "cpu/cpu.hh"
|
#include "cpu/cpu.hh"
|
||||||
#include "memory.hh"
|
#include "memory.hh"
|
||||||
|
#include "util/loglevel.hh"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -84,6 +85,8 @@ main(int argc, const char* argv[]) {
|
|||||||
std::flush(std::cout);
|
std::flush(std::cout);
|
||||||
std::flush(std::cout);
|
std::flush(std::cout);
|
||||||
|
|
||||||
|
matar::set_log_level(matar::LogLevel::Debug);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
matar::Memory memory(std::move(bios), std::move(rom));
|
matar::Memory memory(std::move(bios), std::move(rom));
|
||||||
matar::Bus bus(memory);
|
matar::Bus bus(memory);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
".hh"
|
".hh"
|
||||||
".cc"
|
".cc"
|
||||||
".build"
|
".build"
|
||||||
"meson_options.txt"
|
".options"
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
rec {
|
rec {
|
||||||
|
|||||||
@@ -17,12 +17,6 @@ class Memory {
|
|||||||
uint8_t read(size_t address) const;
|
uint8_t read(size_t address) const;
|
||||||
void write(size_t address, uint8_t byte);
|
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:
|
private:
|
||||||
#define MEMORY_REGION(name, start, end) \
|
#define MEMORY_REGION(name, start, end) \
|
||||||
static constexpr size_t name##_START = start; \
|
static constexpr size_t name##_START = start; \
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ headers = files(
|
|||||||
inc = include_directories('.')
|
inc = include_directories('.')
|
||||||
|
|
||||||
subdir('cpu')
|
subdir('cpu')
|
||||||
|
subdir('util')
|
||||||
|
|
||||||
install_headers(headers, subdir: meson.project_name(), preserve_path: true)
|
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',
|
default_options : ['warning_level=3',
|
||||||
'werror=true',
|
'werror=true',
|
||||||
'optimization=3',
|
'optimization=3',
|
||||||
'cpp_std=c++20'])
|
'cpp_std=c++20',
|
||||||
|
'default_library=static'])
|
||||||
|
|
||||||
compiler = meson.get_compiler('cpp')
|
compiler = meson.get_compiler('cpp')
|
||||||
|
|
||||||
|
|||||||
2
meson.options
Normal file
2
meson.options
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
option('tests', type : 'boolean', value : true, description: 'enable tests')
|
||||||
|
option('disassembler', type: 'boolean', value: true, description: 'enable disassembler')
|
||||||
@@ -1 +0,0 @@
|
|||||||
option('tests', type : 'boolean', value : true, description: 'enable tests')
|
|
||||||
26
src/bus.cc
26
src/bus.cc
@@ -1,4 +1,5 @@
|
|||||||
#include "bus.hh"
|
#include "bus.hh"
|
||||||
|
#include "util/log.hh"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace matar {
|
namespace matar {
|
||||||
@@ -17,21 +18,38 @@ Bus::write_byte(size_t address, uint8_t byte) {
|
|||||||
|
|
||||||
uint16_t
|
uint16_t
|
||||||
Bus::read_halfword(size_t address) {
|
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
|
void
|
||||||
Bus::write_halfword(size_t address, uint16_t halfword) {
|
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
|
uint32_t
|
||||||
Bus::read_word(size_t address) {
|
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
|
void
|
||||||
Bus::write_word(size_t address, uint32_t word) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,24 +48,4 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
|
|||||||
|
|
||||||
return eval;
|
return eval;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream&
|
|
||||||
operator<<(std::ostream& os, const ShiftType shift_type) {
|
|
||||||
|
|
||||||
#define CASE(type) \
|
|
||||||
case ShiftType::type: \
|
|
||||||
os << #type; \
|
|
||||||
break;
|
|
||||||
|
|
||||||
switch (shift_type) {
|
|
||||||
CASE(LSL)
|
|
||||||
CASE(LSR)
|
|
||||||
CASE(ASR)
|
|
||||||
CASE(ROR)
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef CASE
|
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,24 @@ enum class ShiftType {
|
|||||||
ROR = 0b11
|
ROR = 0b11
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr auto
|
||||||
|
stringify(ShiftType shift_type) {
|
||||||
|
#define CASE(type) \
|
||||||
|
case ShiftType::type: \
|
||||||
|
return #type;
|
||||||
|
|
||||||
|
switch (shift_type) {
|
||||||
|
CASE(LSL)
|
||||||
|
CASE(LSR)
|
||||||
|
CASE(ASR)
|
||||||
|
CASE(ROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CASE
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
struct ShiftData {
|
struct ShiftData {
|
||||||
ShiftType type;
|
ShiftType type;
|
||||||
bool immediate;
|
bool immediate;
|
||||||
@@ -23,13 +41,4 @@ struct Shift {
|
|||||||
|
|
||||||
uint32_t
|
uint32_t
|
||||||
eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry);
|
eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry);
|
||||||
|
|
||||||
// https://fmt.dev/dev/api.html#std-ostream-support
|
|
||||||
std::ostream&
|
|
||||||
operator<<(std::ostream& os, const ShiftType cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace fmt {
|
|
||||||
template<>
|
|
||||||
struct formatter<matar::ShiftType> : ostream_formatter {};
|
|
||||||
}
|
}
|
||||||
|
|||||||
235
src/cpu/arm/disassembler.cc
Normal file
235
src/cpu/arm/disassembler.cc
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
#include "instruction.hh"
|
||||||
|
#include "util/bits.hh"
|
||||||
|
|
||||||
|
namespace matar {
|
||||||
|
namespace arm {
|
||||||
|
std::string
|
||||||
|
Instruction::disassemble() {
|
||||||
|
auto condition = stringify(this->condition);
|
||||||
|
|
||||||
|
return std::visit(
|
||||||
|
overloaded{
|
||||||
|
[condition](BranchAndExchange& data) {
|
||||||
|
return fmt::format("BX{} R{:d}", condition, data.rn);
|
||||||
|
},
|
||||||
|
[condition](Branch& data) {
|
||||||
|
return fmt::format(
|
||||||
|
"B{}{} 0x{:06X}", (data.link ? "L" : ""), condition, data.offset);
|
||||||
|
},
|
||||||
|
[condition](Multiply& data) {
|
||||||
|
if (data.acc) {
|
||||||
|
return fmt::format("MLA{}{} R{:d},R{:d},R{:d},R{:d}",
|
||||||
|
condition,
|
||||||
|
(data.set ? "S" : ""),
|
||||||
|
data.rd,
|
||||||
|
data.rm,
|
||||||
|
data.rs,
|
||||||
|
data.rn);
|
||||||
|
} else {
|
||||||
|
return fmt::format("MUL{}{} R{:d},R{:d},R{:d}",
|
||||||
|
condition,
|
||||||
|
(data.set ? "S" : ""),
|
||||||
|
data.rd,
|
||||||
|
data.rm,
|
||||||
|
data.rs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[condition](MultiplyLong& data) {
|
||||||
|
return fmt::format("{}{}{}{} R{:d},R{:d},R{:d},R{:d}",
|
||||||
|
(data.uns ? 'U' : 'S'),
|
||||||
|
(data.acc ? "MLAL" : "MULL"),
|
||||||
|
condition,
|
||||||
|
(data.set ? "S" : ""),
|
||||||
|
data.rdlo,
|
||||||
|
data.rdhi,
|
||||||
|
data.rm,
|
||||||
|
data.rs);
|
||||||
|
},
|
||||||
|
[](Undefined) { return std::string("UND"); },
|
||||||
|
[condition](SingleDataSwap& data) {
|
||||||
|
return fmt::format("SWP{}{} R{:d},R{:d},[R{:d}]",
|
||||||
|
condition,
|
||||||
|
(data.byte ? "B" : ""),
|
||||||
|
data.rd,
|
||||||
|
data.rm,
|
||||||
|
data.rn);
|
||||||
|
},
|
||||||
|
[condition](SingleDataTransfer& data) {
|
||||||
|
std::string expression;
|
||||||
|
std::string address;
|
||||||
|
|
||||||
|
if (const uint16_t* offset = std::get_if<uint16_t>(&data.offset)) {
|
||||||
|
if (*offset == 0) {
|
||||||
|
expression = "";
|
||||||
|
} else {
|
||||||
|
expression =
|
||||||
|
fmt::format(",{}#{:d}", (data.up ? '+' : '-'), *offset);
|
||||||
|
}
|
||||||
|
} else if (const Shift* shift = std::get_if<Shift>(&data.offset)) {
|
||||||
|
// Shifts are always immediate in single data transfer
|
||||||
|
expression = fmt::format(",{}R{:d},{} #{:d}",
|
||||||
|
(data.up ? '+' : '-'),
|
||||||
|
shift->rm,
|
||||||
|
stringify(shift->data.type),
|
||||||
|
shift->data.operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt::format(
|
||||||
|
"{}{}{}{} R{:d},[R{:d}{}]{}",
|
||||||
|
(data.load ? "LDR" : "STR"),
|
||||||
|
condition,
|
||||||
|
(data.byte ? "B" : ""),
|
||||||
|
(!data.pre && data.write ? "T" : ""),
|
||||||
|
data.rd,
|
||||||
|
data.rn,
|
||||||
|
(data.pre ? expression : ""),
|
||||||
|
(data.pre ? (data.write ? "!" : "") : expression));
|
||||||
|
},
|
||||||
|
[condition](HalfwordTransfer& data) {
|
||||||
|
std::string expression;
|
||||||
|
|
||||||
|
if (data.imm) {
|
||||||
|
if (data.offset == 0) {
|
||||||
|
expression = "";
|
||||||
|
} else {
|
||||||
|
expression = fmt::format(
|
||||||
|
",{}#{:d}", (data.up ? '+' : '-'), data.offset);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expression =
|
||||||
|
fmt::format(",{}R{:d}", (data.up ? '+' : '-'), data.offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt::format(
|
||||||
|
"{}{}{}{} R{:d},[R{:d}{}]{}",
|
||||||
|
(data.load ? "LDR" : "STR"),
|
||||||
|
condition,
|
||||||
|
(data.sign ? "S" : ""),
|
||||||
|
(data.half ? 'H' : 'B'),
|
||||||
|
data.rd,
|
||||||
|
data.rn,
|
||||||
|
(data.pre ? expression : ""),
|
||||||
|
(data.pre ? (data.write ? "!" : "") : expression));
|
||||||
|
},
|
||||||
|
[condition](BlockDataTransfer& data) {
|
||||||
|
std::string regs;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
|
if (get_bit(data.regs, i))
|
||||||
|
fmt::format_to(std::back_inserter(regs), "R{:d},", i);
|
||||||
|
};
|
||||||
|
|
||||||
|
regs.pop_back();
|
||||||
|
|
||||||
|
return fmt::format("{}{}{}{} R{:d}{},{{{}}}{}",
|
||||||
|
(data.load ? "LDM" : "STM"),
|
||||||
|
condition,
|
||||||
|
(data.up ? 'I' : 'D'),
|
||||||
|
(data.pre ? 'B' : 'A'),
|
||||||
|
data.rn,
|
||||||
|
(data.write ? "!" : ""),
|
||||||
|
regs,
|
||||||
|
(data.s ? "^" : ""));
|
||||||
|
},
|
||||||
|
[condition](PsrTransfer& data) {
|
||||||
|
if (data.type == PsrTransfer::Type::Mrs) {
|
||||||
|
return fmt::format("MRS{} R{:d},{}",
|
||||||
|
condition,
|
||||||
|
data.operand,
|
||||||
|
(data.spsr ? "SPSR_all" : "CPSR_all"));
|
||||||
|
} else {
|
||||||
|
return fmt::format(
|
||||||
|
"MSR{} {}_{},{}{}",
|
||||||
|
condition,
|
||||||
|
(data.spsr ? "SPSR" : "CPSR"),
|
||||||
|
(data.type == PsrTransfer::Type::Msr_flg ? "flg" : "all"),
|
||||||
|
(data.imm ? '#' : 'R'),
|
||||||
|
data.operand);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[condition](DataProcessing& data) {
|
||||||
|
using OpCode = DataProcessing::OpCode;
|
||||||
|
|
||||||
|
std::string op_2;
|
||||||
|
|
||||||
|
if (const uint32_t* operand =
|
||||||
|
std::get_if<uint32_t>(&data.operand)) {
|
||||||
|
op_2 = fmt::format("#{:d}", *operand);
|
||||||
|
} else if (const Shift* shift = std::get_if<Shift>(&data.operand)) {
|
||||||
|
op_2 = fmt::format("R{:d},{} {}{:d}",
|
||||||
|
shift->rm,
|
||||||
|
stringify(shift->data.type),
|
||||||
|
(shift->data.immediate ? '#' : 'R'),
|
||||||
|
shift->data.operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data.opcode) {
|
||||||
|
case OpCode::MOV:
|
||||||
|
case OpCode::MVN:
|
||||||
|
return fmt::format("{}{}{} R{:d},{}",
|
||||||
|
stringify(data.opcode),
|
||||||
|
condition,
|
||||||
|
(data.set ? "S" : ""),
|
||||||
|
data.rd,
|
||||||
|
op_2);
|
||||||
|
case OpCode::TST:
|
||||||
|
case OpCode::TEQ:
|
||||||
|
case OpCode::CMP:
|
||||||
|
case OpCode::CMN:
|
||||||
|
return fmt::format("{}{} R{:d},{}",
|
||||||
|
stringify(data.opcode),
|
||||||
|
condition,
|
||||||
|
data.rn,
|
||||||
|
op_2);
|
||||||
|
default:
|
||||||
|
return fmt::format("{}{}{} R{:d},R{:d},{}",
|
||||||
|
stringify(data.opcode),
|
||||||
|
condition,
|
||||||
|
(data.set ? "S" : ""),
|
||||||
|
data.rd,
|
||||||
|
data.rn,
|
||||||
|
op_2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[condition](SoftwareInterrupt) {
|
||||||
|
return fmt::format("SWI{}", condition);
|
||||||
|
},
|
||||||
|
[condition](CoprocessorDataTransfer& data) {
|
||||||
|
std::string expression = fmt::format(",#{:d}", data.offset);
|
||||||
|
return fmt::format(
|
||||||
|
"{}{}{} p{:d},c{:d},[R{:d}{}]{}",
|
||||||
|
(data.load ? "LDC" : "STC"),
|
||||||
|
condition,
|
||||||
|
(data.len ? "L" : ""),
|
||||||
|
data.cpn,
|
||||||
|
data.crd,
|
||||||
|
data.rn,
|
||||||
|
(data.pre ? expression : ""),
|
||||||
|
(data.pre ? (data.write ? "!" : "") : expression));
|
||||||
|
},
|
||||||
|
[condition](CoprocessorDataOperation& data) {
|
||||||
|
return fmt::format("CDP{} p{},{},c{},c{},c{},{}",
|
||||||
|
condition,
|
||||||
|
data.cpn,
|
||||||
|
data.cp_opc,
|
||||||
|
data.crd,
|
||||||
|
data.crn,
|
||||||
|
data.crm,
|
||||||
|
data.cp);
|
||||||
|
},
|
||||||
|
[condition](CoprocessorRegisterTransfer& data) {
|
||||||
|
return fmt::format("{}{} p{},{},R{},c{},c{},{}",
|
||||||
|
(data.load ? "MRC" : "MCR"),
|
||||||
|
condition,
|
||||||
|
data.cpn,
|
||||||
|
data.cp_opc,
|
||||||
|
data.rd,
|
||||||
|
data.crn,
|
||||||
|
data.crm,
|
||||||
|
data.cp);
|
||||||
|
},
|
||||||
|
[](auto) { return std::string("unknown instruction"); } },
|
||||||
|
data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,27 +2,24 @@
|
|||||||
#include "util/bits.hh"
|
#include "util/bits.hh"
|
||||||
#include "util/log.hh"
|
#include "util/log.hh"
|
||||||
|
|
||||||
using namespace logger;
|
|
||||||
|
|
||||||
namespace matar {
|
namespace matar {
|
||||||
void
|
void
|
||||||
CpuImpl::exec_arm(const arm::Instruction instruction) {
|
CpuImpl::exec(const arm::Instruction instruction) {
|
||||||
Condition cond = instruction.condition;
|
Condition cond = instruction.condition;
|
||||||
arm::InstructionData data = instruction.data;
|
arm::InstructionData data = instruction.data;
|
||||||
|
|
||||||
debug(cpsr.condition(cond));
|
|
||||||
if (!cpsr.condition(cond)) {
|
if (!cpsr.condition(cond)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pc_error = [](uint8_t r) {
|
auto pc_error = [](uint8_t r) {
|
||||||
if (r == PC_INDEX)
|
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) {
|
auto pc_warn = [](uint8_t r) {
|
||||||
if (r == PC_INDEX)
|
if (r == PC_INDEX)
|
||||||
log_warn("Using PC (R15) as operand register");
|
glogger.warn("Using PC (R15) as operand register");
|
||||||
};
|
};
|
||||||
|
|
||||||
using namespace arm;
|
using namespace arm;
|
||||||
@@ -62,8 +59,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
},
|
},
|
||||||
[this, pc_error](Multiply& data) {
|
[this, pc_error](Multiply& data) {
|
||||||
if (data.rd == data.rm)
|
if (data.rd == data.rm)
|
||||||
log_error("rd and rm are not distinct in {}",
|
glogger.error("rd and rm are not distinct in {}",
|
||||||
typeid(data).name());
|
typeid(data).name());
|
||||||
|
|
||||||
pc_error(data.rd);
|
pc_error(data.rd);
|
||||||
pc_error(data.rd);
|
pc_error(data.rd);
|
||||||
@@ -81,8 +78,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
[this, pc_error](MultiplyLong& data) {
|
[this, pc_error](MultiplyLong& data) {
|
||||||
if (data.rdhi == data.rdlo || data.rdhi == data.rm ||
|
if (data.rdhi == data.rdlo || data.rdhi == data.rm ||
|
||||||
data.rdlo == data.rm)
|
data.rdlo == data.rm)
|
||||||
log_error("rdhi, rdlo and rm are not distinct in {}",
|
glogger.error("rdhi, rdlo and rm are not distinct in {}",
|
||||||
typeid(data).name());
|
typeid(data).name());
|
||||||
|
|
||||||
pc_error(data.rdhi);
|
pc_error(data.rdhi);
|
||||||
pc_error(data.rdlo);
|
pc_error(data.rdlo);
|
||||||
@@ -123,7 +120,7 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
cpsr.set_v(0);
|
cpsr.set_v(0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[](Undefined) { log_warn("Undefined instruction"); },
|
[](Undefined) { glogger.warn("Undefined instruction"); },
|
||||||
[this, pc_error](SingleDataSwap& data) {
|
[this, pc_error](SingleDataSwap& data) {
|
||||||
pc_error(data.rm);
|
pc_error(data.rm);
|
||||||
pc_error(data.rn);
|
pc_error(data.rn);
|
||||||
@@ -142,12 +139,12 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
uint32_t address = gpr[data.rn];
|
uint32_t address = gpr[data.rn];
|
||||||
|
|
||||||
if (!data.pre && data.write)
|
if (!data.pre && data.write)
|
||||||
log_warn("Write-back enabled with post-indexing in {}",
|
glogger.warn("Write-back enabled with post-indexing in {}",
|
||||||
typeid(data).name());
|
typeid(data).name());
|
||||||
|
|
||||||
if (data.rn == PC_INDEX && data.write)
|
if (data.rn == PC_INDEX && data.write)
|
||||||
log_warn("Write-back enabled with base register as PC {}",
|
glogger.warn("Write-back enabled with base register as PC {}",
|
||||||
typeid(data).name());
|
typeid(data).name());
|
||||||
|
|
||||||
if (data.write)
|
if (data.write)
|
||||||
pc_warn(data.rn);
|
pc_warn(data.rn);
|
||||||
@@ -216,11 +213,11 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
|
|
||||||
if (!data.pre && data.write)
|
if (!data.pre && data.write)
|
||||||
log_error("Write-back enabled with post-indexing in {}",
|
glogger.error("Write-back enabled with post-indexing in {}",
|
||||||
typeid(data).name());
|
typeid(data).name());
|
||||||
|
|
||||||
if (data.sign && !data.load)
|
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)
|
if (data.write)
|
||||||
pc_warn(data.rn);
|
pc_warn(data.rn);
|
||||||
@@ -294,8 +291,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
pc_error(data.rn);
|
pc_error(data.rn);
|
||||||
|
|
||||||
if (cpsr.mode() == Mode::User && data.s) {
|
if (cpsr.mode() == Mode::User && data.s) {
|
||||||
log_error("Bit S is set outside priviliged modes in {}",
|
glogger.error("Bit S is set outside priviliged modes in {}",
|
||||||
typeid(data).name());
|
typeid(data).name());
|
||||||
}
|
}
|
||||||
|
|
||||||
// we just change modes to load user registers
|
// we just change modes to load user registers
|
||||||
@@ -304,8 +301,9 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
chg_mode(Mode::User);
|
chg_mode(Mode::User);
|
||||||
|
|
||||||
if (data.write) {
|
if (data.write) {
|
||||||
log_error("Write-back enable for user bank registers in {}",
|
glogger.error(
|
||||||
typeid(data).name());
|
"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) {
|
[this, pc_error](PsrTransfer& data) {
|
||||||
if (data.spsr && cpsr.mode() == Mode::User) {
|
if (data.spsr && cpsr.mode() == Mode::User) {
|
||||||
log_error("Accessing SPSR in User mode in {}",
|
glogger.error("Accessing SPSR in User mode in {}",
|
||||||
typeid(data).name());
|
typeid(data).name());
|
||||||
}
|
}
|
||||||
|
|
||||||
Psr& psr = data.spsr ? spsr : cpsr;
|
Psr& psr = data.spsr ? spsr : cpsr;
|
||||||
@@ -513,8 +511,8 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
if (data.set) {
|
if (data.set) {
|
||||||
if (data.rd == PC_INDEX) {
|
if (data.rd == PC_INDEX) {
|
||||||
if (cpsr.mode() == Mode::User)
|
if (cpsr.mode() == Mode::User)
|
||||||
log_error("Running {} in User mode",
|
glogger.error("Running {} in User mode",
|
||||||
typeid(data).name());
|
typeid(data).name());
|
||||||
spsr = cpsr;
|
spsr = cpsr;
|
||||||
} else {
|
} else {
|
||||||
set_conditions();
|
set_conditions();
|
||||||
@@ -536,7 +534,7 @@ CpuImpl::exec_arm(const arm::Instruction instruction) {
|
|||||||
spsr = cpsr;
|
spsr = cpsr;
|
||||||
},
|
},
|
||||||
[](auto& data) {
|
[](auto& data) {
|
||||||
log_error("Unimplemented {} instruction", typeid(data).name());
|
glogger.error("Unimplemented {} instruction", typeid(data).name());
|
||||||
} },
|
} },
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
namespace matar {
|
namespace matar {
|
||||||
namespace arm {
|
namespace arm {
|
||||||
|
|
||||||
Instruction::Instruction(uint32_t insn)
|
Instruction::Instruction(uint32_t insn)
|
||||||
: condition(static_cast<Condition>(bit_range(insn, 28, 31))) {
|
: condition(static_cast<Condition>(bit_range(insn, 28, 31))) {
|
||||||
// Branch and exhcange
|
// Branch and exhcange
|
||||||
@@ -275,261 +274,5 @@ Instruction::Instruction(uint32_t insn)
|
|||||||
data = Undefined{};
|
data = Undefined{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
|
||||||
Instruction::disassemble() {
|
|
||||||
// goddamn this is gore
|
|
||||||
// TODO: make this less ugly
|
|
||||||
return std::visit(
|
|
||||||
overloaded{
|
|
||||||
[this](BranchAndExchange& data) {
|
|
||||||
return fmt::format("BX{} R{:d}", condition, data.rn);
|
|
||||||
},
|
|
||||||
[this](Branch& data) {
|
|
||||||
return fmt::format(
|
|
||||||
"B{}{} 0x{:06X}", (data.link ? "L" : ""), condition, data.offset);
|
|
||||||
},
|
|
||||||
[this](Multiply& data) {
|
|
||||||
if (data.acc) {
|
|
||||||
return fmt::format("MLA{}{} R{:d},R{:d},R{:d},R{:d}",
|
|
||||||
condition,
|
|
||||||
(data.set ? "S" : ""),
|
|
||||||
data.rd,
|
|
||||||
data.rm,
|
|
||||||
data.rs,
|
|
||||||
data.rn);
|
|
||||||
} else {
|
|
||||||
return fmt::format("MUL{}{} R{:d},R{:d},R{:d}",
|
|
||||||
condition,
|
|
||||||
(data.set ? "S" : ""),
|
|
||||||
data.rd,
|
|
||||||
data.rm,
|
|
||||||
data.rs);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[this](MultiplyLong& data) {
|
|
||||||
return fmt::format("{}{}{}{} R{:d},R{:d},R{:d},R{:d}",
|
|
||||||
(data.uns ? 'U' : 'S'),
|
|
||||||
(data.acc ? "MLAL" : "MULL"),
|
|
||||||
condition,
|
|
||||||
(data.set ? "S" : ""),
|
|
||||||
data.rdlo,
|
|
||||||
data.rdhi,
|
|
||||||
data.rm,
|
|
||||||
data.rs);
|
|
||||||
},
|
|
||||||
[](Undefined) { return std::string("UND"); },
|
|
||||||
[this](SingleDataSwap& data) {
|
|
||||||
return fmt::format("SWP{}{} R{:d},R{:d},[R{:d}]",
|
|
||||||
condition,
|
|
||||||
(data.byte ? "B" : ""),
|
|
||||||
data.rd,
|
|
||||||
data.rm,
|
|
||||||
data.rn);
|
|
||||||
},
|
|
||||||
[this](SingleDataTransfer& data) {
|
|
||||||
std::string expression;
|
|
||||||
std::string address;
|
|
||||||
|
|
||||||
if (const uint16_t* offset = std::get_if<uint16_t>(&data.offset)) {
|
|
||||||
if (*offset == 0) {
|
|
||||||
expression = "";
|
|
||||||
} else {
|
|
||||||
expression =
|
|
||||||
fmt::format(",{}#{:d}", (data.up ? '+' : '-'), *offset);
|
|
||||||
}
|
|
||||||
} else if (const Shift* shift = std::get_if<Shift>(&data.offset)) {
|
|
||||||
// Shifts are always immediate in single data transfer
|
|
||||||
expression = fmt::format(",{}R{:d},{} #{:d}",
|
|
||||||
(data.up ? '+' : '-'),
|
|
||||||
shift->rm,
|
|
||||||
shift->data.type,
|
|
||||||
shift->data.operand);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt::format(
|
|
||||||
"{}{}{}{} R{:d},[R{:d}{}]{}",
|
|
||||||
(data.load ? "LDR" : "STR"),
|
|
||||||
condition,
|
|
||||||
(data.byte ? "B" : ""),
|
|
||||||
(!data.pre && data.write ? "T" : ""),
|
|
||||||
data.rd,
|
|
||||||
data.rn,
|
|
||||||
(data.pre ? expression : ""),
|
|
||||||
(data.pre ? (data.write ? "!" : "") : expression));
|
|
||||||
},
|
|
||||||
[this](HalfwordTransfer& data) {
|
|
||||||
std::string expression;
|
|
||||||
|
|
||||||
if (data.imm) {
|
|
||||||
if (data.offset == 0) {
|
|
||||||
expression = "";
|
|
||||||
} else {
|
|
||||||
expression = fmt::format(
|
|
||||||
",{}#{:d}", (data.up ? '+' : '-'), data.offset);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
expression =
|
|
||||||
fmt::format(",{}R{:d}", (data.up ? '+' : '-'), data.offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt::format(
|
|
||||||
"{}{}{}{} R{:d},[R{:d}{}]{}",
|
|
||||||
(data.load ? "LDR" : "STR"),
|
|
||||||
condition,
|
|
||||||
(data.sign ? "S" : ""),
|
|
||||||
(data.half ? 'H' : 'B'),
|
|
||||||
data.rd,
|
|
||||||
data.rn,
|
|
||||||
(data.pre ? expression : ""),
|
|
||||||
(data.pre ? (data.write ? "!" : "") : expression));
|
|
||||||
},
|
|
||||||
[this](BlockDataTransfer& data) {
|
|
||||||
std::string regs;
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < 16; i++) {
|
|
||||||
if (get_bit(data.regs, i))
|
|
||||||
fmt::format_to(std::back_inserter(regs), "R{:d},", i);
|
|
||||||
};
|
|
||||||
|
|
||||||
regs.pop_back();
|
|
||||||
|
|
||||||
return fmt::format("{}{}{}{} R{:d}{},{{{}}}{}",
|
|
||||||
(data.load ? "LDM" : "STM"),
|
|
||||||
condition,
|
|
||||||
(data.up ? 'I' : 'D'),
|
|
||||||
(data.pre ? 'B' : 'A'),
|
|
||||||
data.rn,
|
|
||||||
(data.write ? "!" : ""),
|
|
||||||
regs,
|
|
||||||
(data.s ? "^" : ""));
|
|
||||||
},
|
|
||||||
[this](PsrTransfer& data) {
|
|
||||||
if (data.type == PsrTransfer::Type::Mrs) {
|
|
||||||
return fmt::format("MRS{} R{:d},{}",
|
|
||||||
condition,
|
|
||||||
data.operand,
|
|
||||||
(data.spsr ? "SPSR_all" : "CPSR_all"));
|
|
||||||
} else {
|
|
||||||
return fmt::format(
|
|
||||||
"MSR{} {}_{},{}{}",
|
|
||||||
condition,
|
|
||||||
(data.spsr ? "SPSR" : "CPSR"),
|
|
||||||
(data.type == PsrTransfer::Type::Msr_flg ? "flg" : "all"),
|
|
||||||
(data.imm ? '#' : 'R'),
|
|
||||||
data.operand);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[this](DataProcessing& data) {
|
|
||||||
using OpCode = DataProcessing::OpCode;
|
|
||||||
|
|
||||||
std::string op_2;
|
|
||||||
|
|
||||||
if (const uint32_t* operand =
|
|
||||||
std::get_if<uint32_t>(&data.operand)) {
|
|
||||||
op_2 = fmt::format("#{:d}", *operand);
|
|
||||||
} else if (const Shift* shift = std::get_if<Shift>(&data.operand)) {
|
|
||||||
op_2 = fmt::format("R{:d},{} {}{:d}",
|
|
||||||
shift->rm,
|
|
||||||
shift->data.type,
|
|
||||||
(shift->data.immediate ? '#' : 'R'),
|
|
||||||
shift->data.operand);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (data.opcode) {
|
|
||||||
case OpCode::MOV:
|
|
||||||
case OpCode::MVN:
|
|
||||||
return fmt::format("{}{}{} R{:d},{}",
|
|
||||||
data.opcode,
|
|
||||||
condition,
|
|
||||||
(data.set ? "S" : ""),
|
|
||||||
data.rd,
|
|
||||||
op_2);
|
|
||||||
case OpCode::TST:
|
|
||||||
case OpCode::TEQ:
|
|
||||||
case OpCode::CMP:
|
|
||||||
case OpCode::CMN:
|
|
||||||
return fmt::format(
|
|
||||||
"{}{} R{:d},{}", data.opcode, condition, data.rn, op_2);
|
|
||||||
default:
|
|
||||||
return fmt::format("{}{}{} R{:d},R{:d},{}",
|
|
||||||
data.opcode,
|
|
||||||
condition,
|
|
||||||
(data.set ? "S" : ""),
|
|
||||||
data.rd,
|
|
||||||
data.rn,
|
|
||||||
op_2);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[this](SoftwareInterrupt) { return fmt::format("SWI{}", condition); },
|
|
||||||
[this](CoprocessorDataTransfer& data) {
|
|
||||||
std::string expression = fmt::format(",#{:d}", data.offset);
|
|
||||||
return fmt::format(
|
|
||||||
"{}{}{} p{:d},c{:d},[R{:d}{}]{}",
|
|
||||||
(data.load ? "LDC" : "STC"),
|
|
||||||
condition,
|
|
||||||
(data.len ? "L" : ""),
|
|
||||||
data.cpn,
|
|
||||||
data.crd,
|
|
||||||
data.rn,
|
|
||||||
(data.pre ? expression : ""),
|
|
||||||
(data.pre ? (data.write ? "!" : "") : expression));
|
|
||||||
},
|
|
||||||
[this](CoprocessorDataOperation& data) {
|
|
||||||
return fmt::format("CDP{} p{},{},c{},c{},c{},{}",
|
|
||||||
condition,
|
|
||||||
data.cpn,
|
|
||||||
data.cp_opc,
|
|
||||||
data.crd,
|
|
||||||
data.crn,
|
|
||||||
data.crm,
|
|
||||||
data.cp);
|
|
||||||
},
|
|
||||||
[this](CoprocessorRegisterTransfer& data) {
|
|
||||||
return fmt::format("{}{} p{},{},R{},c{},c{},{}",
|
|
||||||
(data.load ? "MRC" : "MCR"),
|
|
||||||
condition,
|
|
||||||
data.cpn,
|
|
||||||
data.cp_opc,
|
|
||||||
data.rd,
|
|
||||||
data.crn,
|
|
||||||
data.crm,
|
|
||||||
data.cp);
|
|
||||||
},
|
|
||||||
[](auto) { return std::string("unknown instruction"); } },
|
|
||||||
data);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream&
|
|
||||||
operator<<(std::ostream& os, const DataProcessing::OpCode opcode) {
|
|
||||||
|
|
||||||
#define CASE(opcode) \
|
|
||||||
case DataProcessing::OpCode::opcode: \
|
|
||||||
os << #opcode; \
|
|
||||||
break;
|
|
||||||
|
|
||||||
switch (opcode) {
|
|
||||||
CASE(AND)
|
|
||||||
CASE(EOR)
|
|
||||||
CASE(SUB)
|
|
||||||
CASE(RSB)
|
|
||||||
CASE(ADD)
|
|
||||||
CASE(ADC)
|
|
||||||
CASE(SBC)
|
|
||||||
CASE(RSC)
|
|
||||||
CASE(TST)
|
|
||||||
CASE(TEQ)
|
|
||||||
CASE(CMP)
|
|
||||||
CASE(CMN)
|
|
||||||
CASE(ORR)
|
|
||||||
CASE(MOV)
|
|
||||||
CASE(BIC)
|
|
||||||
CASE(MVN)
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef CASE
|
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
namespace matar {
|
namespace matar {
|
||||||
namespace arm {
|
namespace arm {
|
||||||
|
|
||||||
|
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||||
template<class... Ts>
|
template<class... Ts>
|
||||||
struct overloaded : Ts... {
|
struct overloaded : Ts... {
|
||||||
using Ts::operator()...;
|
using Ts::operator()...;
|
||||||
@@ -113,6 +114,37 @@ struct DataProcessing {
|
|||||||
OpCode opcode;
|
OpCode opcode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr auto
|
||||||
|
stringify(DataProcessing::OpCode opcode) {
|
||||||
|
|
||||||
|
#define CASE(opcode) \
|
||||||
|
case DataProcessing::OpCode::opcode: \
|
||||||
|
return #opcode;
|
||||||
|
|
||||||
|
switch (opcode) {
|
||||||
|
CASE(AND)
|
||||||
|
CASE(EOR)
|
||||||
|
CASE(SUB)
|
||||||
|
CASE(RSB)
|
||||||
|
CASE(ADD)
|
||||||
|
CASE(ADC)
|
||||||
|
CASE(SBC)
|
||||||
|
CASE(RSC)
|
||||||
|
CASE(TST)
|
||||||
|
CASE(TEQ)
|
||||||
|
CASE(CMP)
|
||||||
|
CASE(CMN)
|
||||||
|
CASE(ORR)
|
||||||
|
CASE(MOV)
|
||||||
|
CASE(BIC)
|
||||||
|
CASE(MVN)
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CASE
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
struct PsrTransfer {
|
struct PsrTransfer {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
Mrs,
|
Mrs,
|
||||||
@@ -186,15 +218,9 @@ struct Instruction {
|
|||||||
: condition(condition)
|
: condition(condition)
|
||||||
, data(data){};
|
, data(data){};
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
std::string disassemble();
|
std::string disassemble();
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream&
|
|
||||||
operator<<(std::ostream& os, const DataProcessing::OpCode cond);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace fmt {
|
|
||||||
template<>
|
|
||||||
struct formatter<matar::arm::DataProcessing::OpCode> : ostream_formatter {};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,3 +2,7 @@ lib_sources += files(
|
|||||||
'instruction.cc',
|
'instruction.cc',
|
||||||
'exec.cc'
|
'exec.cc'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if get_option('disassembler')
|
||||||
|
lib_sources += files('disassembler.cc')
|
||||||
|
endif
|
||||||
@@ -4,8 +4,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
using namespace logger;
|
|
||||||
|
|
||||||
namespace matar {
|
namespace matar {
|
||||||
CpuImpl::CpuImpl(const Bus& bus) noexcept
|
CpuImpl::CpuImpl(const Bus& bus) noexcept
|
||||||
: bus(std::make_shared<Bus>(bus))
|
: bus(std::make_shared<Bus>(bus))
|
||||||
@@ -19,7 +17,7 @@ CpuImpl::CpuImpl(const Bus& bus) noexcept
|
|||||||
cpsr.set_irq_disabled(true);
|
cpsr.set_irq_disabled(true);
|
||||||
cpsr.set_fiq_disabled(true);
|
cpsr.set_fiq_disabled(true);
|
||||||
cpsr.set_state(State::Arm);
|
cpsr.set_state(State::Arm);
|
||||||
log_info("CPU successfully initialised");
|
glogger.info("CPU successfully initialised");
|
||||||
|
|
||||||
// PC always points to two instructions ahead
|
// PC always points to two instructions ahead
|
||||||
// PC - 2 is the instruction being executed
|
// PC - 2 is the instruction being executed
|
||||||
@@ -121,14 +119,15 @@ CpuImpl::step() {
|
|||||||
uint32_t cur_pc = pc - 2 * arm::INSTRUCTION_SIZE;
|
uint32_t cur_pc = pc - 2 * arm::INSTRUCTION_SIZE;
|
||||||
|
|
||||||
if (cpsr.state() == State::Arm) {
|
if (cpsr.state() == State::Arm) {
|
||||||
debug(cur_pc);
|
|
||||||
uint32_t x = bus->read_word(cur_pc);
|
uint32_t x = bus->read_word(cur_pc);
|
||||||
arm::Instruction instruction(x);
|
arm::Instruction instruction(x);
|
||||||
log_info("{:#034b}", x);
|
|
||||||
|
|
||||||
exec_arm(instruction);
|
exec(instruction);
|
||||||
|
|
||||||
log_info("0x{:08X} : {}", cur_pc, instruction.disassemble());
|
#ifdef DISASSEMBLER
|
||||||
|
glogger.info("{:#034b}", x);
|
||||||
|
glogger.info("0x{:08X} : {}", cur_pc, instruction.disassemble());
|
||||||
|
#endif
|
||||||
|
|
||||||
if (is_flushed) {
|
if (is_flushed) {
|
||||||
// if flushed, do not increment the PC, instead set it to two
|
// if flushed, do not increment the PC, instead set it to two
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ class CpuImpl {
|
|||||||
|
|
||||||
void step();
|
void step();
|
||||||
void chg_mode(const Mode to);
|
void chg_mode(const Mode to);
|
||||||
void exec_arm(const arm::Instruction instruction);
|
void exec(const arm::Instruction instruction);
|
||||||
|
|
||||||
|
#ifndef MATAR_CPU_TESTS
|
||||||
|
private:
|
||||||
|
#endif
|
||||||
|
|
||||||
static constexpr uint8_t GPR_COUNT = 16;
|
static constexpr uint8_t GPR_COUNT = 16;
|
||||||
|
|
||||||
|
|||||||
@@ -96,37 +96,4 @@ Psr::condition(Condition cond) const {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream&
|
|
||||||
operator<<(std::ostream& os, const Condition cond) {
|
|
||||||
|
|
||||||
#define CASE(cond) \
|
|
||||||
case Condition::cond: \
|
|
||||||
os << #cond; \
|
|
||||||
break;
|
|
||||||
|
|
||||||
switch (cond) {
|
|
||||||
CASE(EQ)
|
|
||||||
CASE(NE)
|
|
||||||
CASE(CS)
|
|
||||||
CASE(CC)
|
|
||||||
CASE(MI)
|
|
||||||
CASE(PL)
|
|
||||||
CASE(VS)
|
|
||||||
CASE(VC)
|
|
||||||
CASE(HI)
|
|
||||||
CASE(LS)
|
|
||||||
CASE(GE)
|
|
||||||
CASE(LT)
|
|
||||||
CASE(GT)
|
|
||||||
CASE(LE)
|
|
||||||
case Condition::AL: {
|
|
||||||
// empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef CASE
|
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,38 @@ enum class Condition {
|
|||||||
AL = 0b1110
|
AL = 0b1110
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr auto
|
||||||
|
stringify(Condition cond) {
|
||||||
|
|
||||||
|
#define CASE(cond) \
|
||||||
|
case Condition::cond: \
|
||||||
|
return #cond;
|
||||||
|
|
||||||
|
switch (cond) {
|
||||||
|
CASE(EQ)
|
||||||
|
CASE(NE)
|
||||||
|
CASE(CS)
|
||||||
|
CASE(CC)
|
||||||
|
CASE(MI)
|
||||||
|
CASE(PL)
|
||||||
|
CASE(VS)
|
||||||
|
CASE(VC)
|
||||||
|
CASE(HI)
|
||||||
|
CASE(LS)
|
||||||
|
CASE(GE)
|
||||||
|
CASE(LT)
|
||||||
|
CASE(GT)
|
||||||
|
CASE(LE)
|
||||||
|
case Condition::AL: {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CASE
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
class Psr {
|
class Psr {
|
||||||
public:
|
public:
|
||||||
// clear the reserved bits i.e, [8:27]
|
// clear the reserved bits i.e, [8:27]
|
||||||
@@ -88,13 +120,4 @@ class Psr {
|
|||||||
|
|
||||||
uint32_t psr;
|
uint32_t psr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://fmt.dev/dev/api.html#std-ostream-support
|
|
||||||
std::ostream&
|
|
||||||
operator<<(std::ostream& os, const Condition cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace fmt {
|
|
||||||
template<>
|
|
||||||
struct formatter<matar::Condition> : ostream_formatter {};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#include "memory.hh"
|
#include "memory.hh"
|
||||||
#include "header.hh"
|
#include "header.hh"
|
||||||
#include "util/bits.hh"
|
#include "util/bits.hh"
|
||||||
|
#include "util/crypto.hh"
|
||||||
#include "util/log.hh"
|
#include "util/log.hh"
|
||||||
#include "util/utils.hh"
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
using namespace logger;
|
|
||||||
|
|
||||||
namespace matar {
|
namespace matar {
|
||||||
Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
|
Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
|
||||||
std::vector<uint8_t>&& rom)
|
std::vector<uint8_t>&& rom)
|
||||||
@@ -23,17 +21,17 @@ Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
|
|||||||
"fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570";
|
"fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570";
|
||||||
|
|
||||||
if (bios_hash != expected_hash) {
|
if (bios_hash != expected_hash) {
|
||||||
log_warn("BIOS hash failed to match, run at your own risk"
|
glogger.warn("BIOS hash failed to match, run at your own risk"
|
||||||
"\nExpected : {} "
|
"\nExpected : {} "
|
||||||
"\nGot : {}",
|
"\nGot : {}",
|
||||||
expected_hash,
|
expected_hash,
|
||||||
bios_hash);
|
bios_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_header();
|
parse_header();
|
||||||
|
|
||||||
log_info("Memory successfully initialised");
|
glogger.info("Memory successfully initialised");
|
||||||
log_info("Cartridge Title: {}", header.title);
|
glogger.info("Cartridge Title: {}", header.title);
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MATCHES(area) address >= area##_START&& address <= area##_END
|
#define MATCHES(area) address >= area##_START&& address <= area##_END
|
||||||
@@ -59,7 +57,7 @@ Memory::read(size_t address) const {
|
|||||||
} else if (MATCHES(ROM_2)) {
|
} else if (MATCHES(ROM_2)) {
|
||||||
return rom[address - ROM_2_START];
|
return rom[address - ROM_2_START];
|
||||||
} else {
|
} else {
|
||||||
log_error("Invalid memory region accessed");
|
glogger.error("Invalid memory region accessed");
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,49 +83,12 @@ Memory::write(size_t address, uint8_t byte) {
|
|||||||
} else if (MATCHES(ROM_2)) {
|
} else if (MATCHES(ROM_2)) {
|
||||||
rom[address - ROM_2_START] = byte;
|
rom[address - ROM_2_START] = byte;
|
||||||
} else {
|
} else {
|
||||||
log_error("Invalid memory region accessed");
|
glogger.error("Invalid memory region accessed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef MATCHES
|
#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
|
void
|
||||||
Memory::parse_header() {
|
Memory::parse_header() {
|
||||||
|
|
||||||
@@ -142,7 +103,7 @@ Memory::parse_header() {
|
|||||||
|
|
||||||
// nintendo logo
|
// nintendo logo
|
||||||
if (rom[0x9C] != 0x21)
|
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
|
// game info
|
||||||
header.title = std::string(&rom[0xA0], &rom[0xA0 + 12]);
|
header.title = std::string(&rom[0xA0], &rom[0xA0 + 12]);
|
||||||
@@ -177,7 +138,7 @@ Memory::parse_header() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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]);
|
header.title_code = std::string(&rom[0xAD], &rom[0xAE]);
|
||||||
@@ -206,15 +167,16 @@ Memory::parse_header() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log_error("HEADER: invalid destination/language: {}", rom[0xAF]);
|
glogger.error("HEADER: invalid destination/language: {}",
|
||||||
|
rom[0xAF]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rom[0xB2] != 0x96)
|
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++) {
|
for (size_t i = 0xB5; i < 0xBC; i++) {
|
||||||
if (rom[i] != 0x00)
|
if (rom[i] != 0x00)
|
||||||
log_error("HEADER: invalid fixed bytes at 0xB5");
|
glogger.error("HEADER: invalid fixed bytes at 0xB5");
|
||||||
}
|
}
|
||||||
|
|
||||||
header.version = rom[0xBC];
|
header.version = rom[0xBC];
|
||||||
@@ -228,7 +190,7 @@ Memory::parse_header() {
|
|||||||
chk &= 0xFF;
|
chk &= 0xFF;
|
||||||
|
|
||||||
if (chk != rom[0xBD])
|
if (chk != rom[0xBD])
|
||||||
log_error("HEADER: checksum does not match");
|
glogger.error("HEADER: checksum does not match");
|
||||||
}
|
}
|
||||||
|
|
||||||
// multiboot not required right now
|
// multiboot not required right now
|
||||||
|
|||||||
@@ -3,15 +3,19 @@ lib_sources = files(
|
|||||||
'bus.cc'
|
'bus.cc'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subdir('util')
|
||||||
subdir('cpu')
|
subdir('cpu')
|
||||||
|
|
||||||
|
|
||||||
lib_cpp_args = [ ]
|
lib_cpp_args = [ ]
|
||||||
|
|
||||||
fmt = dependency('fmt', version : '>=10.1.0', static: true)
|
fmt = dependency('fmt', version : '>=10.1.0', static: true)
|
||||||
if not fmt.found()
|
if not fmt.found()
|
||||||
fmt = dependency('fmt', version : '>=10.1.0', static: false)
|
fmt = dependency('fmt', version : '>=10.1.0', static: false)
|
||||||
lib_cpp_args += 'DFMT_HEADER_ONLY'
|
lib_cpp_args += '-DFMT_HEADER_ONLY'
|
||||||
|
endif
|
||||||
|
|
||||||
|
if get_option('disassembler')
|
||||||
|
lib_cpp_args += '-DDISASSEMBLER'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
lib = library(
|
lib = library(
|
||||||
|
|||||||
@@ -14,19 +14,19 @@ get_bit(Int num, size_t n) {
|
|||||||
template<std::integral Int>
|
template<std::integral Int>
|
||||||
inline void
|
inline void
|
||||||
set_bit(Int& num, size_t n) {
|
set_bit(Int& num, size_t n) {
|
||||||
num |= (1 << n);
|
num |= (static_cast<Int>(1) << n);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::integral Int>
|
template<std::integral Int>
|
||||||
inline void
|
inline void
|
||||||
rst_bit(Int& num, size_t n) {
|
rst_bit(Int& num, size_t n) {
|
||||||
num &= ~(1 << n);
|
num &= ~(static_cast<Int>(1) << n);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::integral Int>
|
template<std::integral Int>
|
||||||
inline void
|
inline void
|
||||||
chg_bit(Int& num, size_t n, bool x) {
|
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
|
/// 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
|
// NOTE: we do not require -1 if it is a signed integral
|
||||||
Int left =
|
Int left =
|
||||||
std::numeric_limits<Int>::digits - (std::is_unsigned<Int>::value) - end;
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/loglevel.hh"
|
||||||
#include <fmt/ostream.h>
|
#include <fmt/ostream.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using fmt::print;
|
namespace logging {
|
||||||
using std::clog;
|
|
||||||
|
|
||||||
namespace logger {
|
|
||||||
namespace ansi {
|
namespace ansi {
|
||||||
static constexpr std::string_view RED = "\033[31m";
|
static constexpr auto RED = "\033[31m";
|
||||||
static constexpr std::string_view YELLOW = "\033[33m";
|
static constexpr auto YELLOW = "\033[33m";
|
||||||
static constexpr std::string_view MAGENTA = "\033[35m";
|
static constexpr auto MAGENTA = "\033[35m";
|
||||||
static constexpr std::string_view WHITE = "\033[37m";
|
static constexpr auto WHITE = "\033[37m";
|
||||||
static constexpr std::string_view BOLD = "\033[1m";
|
static constexpr auto BOLD = "\033[1m";
|
||||||
static constexpr std::string_view RESET = "\033[0m";
|
static constexpr auto RESET = "\033[0m";
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
using fmt::print;
|
||||||
inline void
|
|
||||||
log_raw(const fmt::format_string<Args...>& fmt, Args&&... args) {
|
class Logger {
|
||||||
fmt::println(clog, fmt, std::forward<Args>(args)...);
|
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>
|
extern logging::Logger glogger;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
#define dbg(x) glogger.debug("{} = {}", #x, x);
|
||||||
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)
|
|
||||||
|
|||||||
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);
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
|
#define MATAR_CPU_TESTS
|
||||||
#include "cpu/cpu-impl.hh"
|
#include "cpu/cpu-impl.hh"
|
||||||
|
#undef MATAR_CPU_TESTS
|
||||||
|
|
||||||
#include "util/bits.hh"
|
#include "util/bits.hh"
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -13,10 +16,9 @@ class CpuFixture {
|
|||||||
std::vector<uint8_t>(Header::HEADER_SIZE)))) {}
|
std::vector<uint8_t>(Header::HEADER_SIZE)))) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// TODO: test with other conditions
|
|
||||||
void exec(arm::InstructionData data, Condition condition = Condition::AL) {
|
void exec(arm::InstructionData data, Condition condition = Condition::AL) {
|
||||||
arm::Instruction instruction(condition, data);
|
arm::Instruction instruction(condition, data);
|
||||||
cpu.exec_arm(instruction);
|
cpu.exec(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset(uint32_t value = 0) {
|
void reset(uint32_t value = 0) {
|
||||||
@@ -32,7 +34,7 @@ class CpuFixture {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#define TAG "arm execution"
|
static constexpr auto TAG = "[arm][execution]";
|
||||||
|
|
||||||
using namespace arm;
|
using namespace arm;
|
||||||
|
|
||||||
@@ -333,7 +335,7 @@ TEST_CASE_METHOD(CpuFixture, "Single Data Transfer", TAG) {
|
|||||||
|
|
||||||
// r15 as rn
|
// r15 as rn
|
||||||
{
|
{
|
||||||
data_transfer->rn = 15;
|
data_transfer->rn = cpu.PC_INDEX;
|
||||||
cpu.gpr[15] = 7577;
|
cpu.gpr[15] = 7577;
|
||||||
|
|
||||||
exec(data);
|
exec(data);
|
||||||
@@ -349,7 +351,7 @@ TEST_CASE_METHOD(CpuFixture, "Single Data Transfer", TAG) {
|
|||||||
// r15 as rd
|
// r15 as rd
|
||||||
{
|
{
|
||||||
// 4088
|
// 4088
|
||||||
data_transfer->rd = 15;
|
data_transfer->rd = cpu.PC_INDEX;
|
||||||
cpu.gpr[15] = 444444;
|
cpu.gpr[15] = 444444;
|
||||||
|
|
||||||
exec(data);
|
exec(data);
|
||||||
@@ -466,7 +468,7 @@ TEST_CASE_METHOD(CpuFixture, "Halfword Transfer", TAG) {
|
|||||||
|
|
||||||
// r15 as rn
|
// r15 as rn
|
||||||
{
|
{
|
||||||
hw_transfer->rn = 15;
|
hw_transfer->rn = cpu.PC_INDEX;
|
||||||
cpu.gpr[15] = 399;
|
cpu.gpr[15] = 399;
|
||||||
|
|
||||||
exec(data);
|
exec(data);
|
||||||
@@ -482,7 +484,7 @@ TEST_CASE_METHOD(CpuFixture, "Halfword Transfer", TAG) {
|
|||||||
|
|
||||||
// r15 as rd
|
// r15 as rd
|
||||||
{
|
{
|
||||||
hw_transfer->rd = 15;
|
hw_transfer->rd = cpu.PC_INDEX;
|
||||||
cpu.gpr[15] = 224;
|
cpu.gpr[15] = 224;
|
||||||
|
|
||||||
exec(data);
|
exec(data);
|
||||||
@@ -793,7 +795,7 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
|||||||
|
|
||||||
// same as above but with rn (oprerand 1) = 15
|
// same as above but with rn (oprerand 1) = 15
|
||||||
{
|
{
|
||||||
processing->rn = 15;
|
processing->rn = cpu.PC_INDEX;
|
||||||
cpu.gpr[15] = -2871;
|
cpu.gpr[15] = -2871;
|
||||||
exec(data);
|
exec(data);
|
||||||
|
|
||||||
@@ -804,29 +806,41 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
|||||||
processing->rn = 7;
|
processing->rn = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto flags = [this](bool n, bool z, bool v, bool c) {
|
auto reset_flags = [this]() {
|
||||||
CHECK(cpu.cpsr.n() == n);
|
|
||||||
CHECK(cpu.cpsr.z() == z);
|
|
||||||
CHECK(cpu.cpsr.v() == v);
|
|
||||||
CHECK(cpu.cpsr.c() == c);
|
|
||||||
|
|
||||||
cpu.cpsr.set_n(false);
|
cpu.cpsr.set_n(false);
|
||||||
cpu.cpsr.set_z(false);
|
cpu.cpsr.set_z(false);
|
||||||
cpu.cpsr.set_v(false);
|
cpu.cpsr.set_v(false);
|
||||||
cpu.cpsr.set_c(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
|
// immediate operand
|
||||||
processing->operand = static_cast<uint32_t>(54924809);
|
processing->operand = static_cast<uint32_t>(54924809);
|
||||||
// rs
|
// rs
|
||||||
cpu.gpr[12] = 2;
|
cpu.gpr[12] = 2;
|
||||||
cpu.gpr[5] = 0;
|
cpu.gpr[5] = 0;
|
||||||
|
reset_flags();
|
||||||
|
|
||||||
SECTION("AND") {
|
SECTION("AND (with condition check)") {
|
||||||
processing->opcode = OpCode::AND;
|
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
|
// -28717 & 54924809
|
||||||
|
// condition is true now
|
||||||
CHECK(cpu.gpr[5] == 54920705);
|
CHECK(cpu.gpr[5] == 54920705);
|
||||||
|
|
||||||
// check set flags
|
// check set flags
|
||||||
@@ -846,11 +860,19 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
|||||||
flags(false, false, false, false);
|
flags(false, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("EOR") {
|
SECTION("EOR (with condition check)") {
|
||||||
processing->opcode = OpCode::EOR;
|
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
|
// -28717 ^ 54924809
|
||||||
|
// condition is true now
|
||||||
CHECK(cpu.gpr[5] == 4240021978);
|
CHECK(cpu.gpr[5] == 4240021978);
|
||||||
|
|
||||||
// check set flags
|
// check set flags
|
||||||
@@ -1038,7 +1060,7 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
|||||||
|
|
||||||
SECTION("R15 as destination") {
|
SECTION("R15 as destination") {
|
||||||
processing->opcode = OpCode::MVN;
|
processing->opcode = OpCode::MVN;
|
||||||
processing->rd = 15;
|
processing->rd = cpu.PC_INDEX;
|
||||||
cpu.gpr[15] = 0;
|
cpu.gpr[15] = 0;
|
||||||
CHECK(cpu.spsr.raw() != cpu.cpsr.raw());
|
CHECK(cpu.spsr.raw() != cpu.cpsr.raw());
|
||||||
exec(data);
|
exec(data);
|
||||||
@@ -1051,5 +1073,3 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
|||||||
CHECK(cpu.spsr.raw() == cpu.cpsr.raw());
|
CHECK(cpu.spsr.raw() == cpu.cpsr.raw());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef TAG
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "cpu/arm/instruction.hh"
|
#include "cpu/arm/instruction.hh"
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
#define TAG "disassembler"
|
static constexpr auto TAG = "[arm][disassembly]";
|
||||||
|
|
||||||
using namespace matar;
|
using namespace matar;
|
||||||
using namespace arm;
|
using namespace arm;
|
||||||
@@ -16,7 +16,9 @@ TEST_CASE("Branch and Exchange", TAG) {
|
|||||||
|
|
||||||
CHECK(bx->rn == 10);
|
CHECK(bx->rn == 10);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "BXGT R10");
|
CHECK(instruction.disassemble() == "BXGT R10");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Branch", TAG) {
|
TEST_CASE("Branch", TAG) {
|
||||||
@@ -33,10 +35,12 @@ TEST_CASE("Branch", TAG) {
|
|||||||
CHECK(b->offset == 0xFE15FF14);
|
CHECK(b->offset == 0xFE15FF14);
|
||||||
CHECK(b->link == true);
|
CHECK(b->link == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "BL 0xFE15FF14");
|
CHECK(instruction.disassemble() == "BL 0xFE15FF14");
|
||||||
|
|
||||||
b->link = false;
|
b->link = false;
|
||||||
CHECK(instruction.disassemble() == "B 0xFE15FF14");
|
CHECK(instruction.disassemble() == "B 0xFE15FF14");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Multiply", TAG) {
|
TEST_CASE("Multiply", TAG) {
|
||||||
@@ -54,11 +58,13 @@ TEST_CASE("Multiply", TAG) {
|
|||||||
CHECK(mul->acc == true);
|
CHECK(mul->acc == true);
|
||||||
CHECK(mul->set == true);
|
CHECK(mul->set == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "MLAEQS R10,R0,R15,R14");
|
CHECK(instruction.disassemble() == "MLAEQS R10,R0,R15,R14");
|
||||||
|
|
||||||
mul->acc = false;
|
mul->acc = false;
|
||||||
mul->set = false;
|
mul->set = false;
|
||||||
CHECK(instruction.disassemble() == "MULEQ R10,R0,R15");
|
CHECK(instruction.disassemble() == "MULEQ R10,R0,R15");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Multiply Long", TAG) {
|
TEST_CASE("Multiply Long", TAG) {
|
||||||
@@ -77,6 +83,7 @@ TEST_CASE("Multiply Long", TAG) {
|
|||||||
CHECK(mull->set == true);
|
CHECK(mull->set == true);
|
||||||
CHECK(mull->uns == true);
|
CHECK(mull->uns == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "UMULLNES R7,R14,R2,R6");
|
CHECK(instruction.disassemble() == "UMULLNES R7,R14,R2,R6");
|
||||||
|
|
||||||
mull->acc = true;
|
mull->acc = true;
|
||||||
@@ -85,6 +92,7 @@ TEST_CASE("Multiply Long", TAG) {
|
|||||||
mull->uns = false;
|
mull->uns = false;
|
||||||
mull->set = false;
|
mull->set = false;
|
||||||
CHECK(instruction.disassemble() == "SMLALNE R7,R14,R2,R6");
|
CHECK(instruction.disassemble() == "SMLALNE R7,R14,R2,R6");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Undefined", TAG) {
|
TEST_CASE("Undefined", TAG) {
|
||||||
@@ -94,7 +102,10 @@ TEST_CASE("Undefined", TAG) {
|
|||||||
Instruction instruction(raw);
|
Instruction instruction(raw);
|
||||||
|
|
||||||
CHECK(instruction.condition == Condition::AL);
|
CHECK(instruction.condition == Condition::AL);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "UND");
|
CHECK(instruction.disassemble() == "UND");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Single Data Swap", TAG) {
|
TEST_CASE("Single Data Swap", TAG) {
|
||||||
@@ -110,10 +121,12 @@ TEST_CASE("Single Data Swap", TAG) {
|
|||||||
CHECK(swp->rn == 9);
|
CHECK(swp->rn == 9);
|
||||||
CHECK(swp->byte == false);
|
CHECK(swp->byte == false);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "SWPGE R5,R6,[R9]");
|
CHECK(instruction.disassemble() == "SWPGE R5,R6,[R9]");
|
||||||
|
|
||||||
swp->byte = true;
|
swp->byte = true;
|
||||||
CHECK(instruction.disassemble() == "SWPGEB R5,R6,[R9]");
|
CHECK(instruction.disassemble() == "SWPGEB R5,R6,[R9]");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Single Data Transfer", TAG) {
|
TEST_CASE("Single Data Transfer", TAG) {
|
||||||
@@ -138,6 +151,7 @@ TEST_CASE("Single Data Transfer", TAG) {
|
|||||||
CHECK(ldr->up == true);
|
CHECK(ldr->up == true);
|
||||||
CHECK(ldr->pre == true);
|
CHECK(ldr->pre == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
ldr->load = true;
|
ldr->load = true;
|
||||||
ldr->byte = true;
|
ldr->byte = true;
|
||||||
ldr->write = false;
|
ldr->write = false;
|
||||||
@@ -153,6 +167,7 @@ TEST_CASE("Single Data Transfer", TAG) {
|
|||||||
|
|
||||||
ldr->pre = true;
|
ldr->pre = true;
|
||||||
CHECK(instruction.disassemble() == "LDRB R10,[R2,-#9023]");
|
CHECK(instruction.disassemble() == "LDRB R10,[R2,-#9023]");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Halfword Transfer", TAG) {
|
TEST_CASE("Halfword Transfer", TAG) {
|
||||||
@@ -176,6 +191,7 @@ TEST_CASE("Halfword Transfer", TAG) {
|
|||||||
CHECK(ldr->up == true);
|
CHECK(ldr->up == true);
|
||||||
CHECK(ldr->pre == true);
|
CHECK(ldr->pre == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "STRCCH R2,[R15,+R6]!");
|
CHECK(instruction.disassemble() == "STRCCH R2,[R15,+R6]!");
|
||||||
|
|
||||||
ldr->pre = false;
|
ldr->pre = false;
|
||||||
@@ -193,6 +209,7 @@ TEST_CASE("Halfword Transfer", TAG) {
|
|||||||
ldr->imm = 1;
|
ldr->imm = 1;
|
||||||
ldr->offset = 90;
|
ldr->offset = 90;
|
||||||
CHECK(instruction.disassemble() == "STRCCSB R2,[R15],-#90");
|
CHECK(instruction.disassemble() == "STRCCSB R2,[R15],-#90");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Block Data Transfer", TAG) {
|
TEST_CASE("Block Data Transfer", TAG) {
|
||||||
@@ -223,6 +240,7 @@ TEST_CASE("Block Data Transfer", TAG) {
|
|||||||
CHECK(ldm->up == false);
|
CHECK(ldm->up == false);
|
||||||
CHECK(ldm->pre == true);
|
CHECK(ldm->pre == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "LDMLSDB R7,{R0,R2,R3,R5,R6,R8,R14}^");
|
CHECK(instruction.disassemble() == "LDMLSDB R7,{R0,R2,R3,R5,R6,R8,R14}^");
|
||||||
|
|
||||||
ldm->write = true;
|
ldm->write = true;
|
||||||
@@ -238,6 +256,7 @@ TEST_CASE("Block Data Transfer", TAG) {
|
|||||||
ldm->pre = false;
|
ldm->pre = false;
|
||||||
|
|
||||||
CHECK(instruction.disassemble() == "STMLSIA R7!,{R0,R2,R5,R14}");
|
CHECK(instruction.disassemble() == "STMLSIA R7!,{R0,R2,R5,R14}");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("PSR Transfer", TAG) {
|
TEST_CASE("PSR Transfer", TAG) {
|
||||||
@@ -256,7 +275,9 @@ TEST_CASE("PSR Transfer", TAG) {
|
|||||||
CHECK(mrs->operand == 10);
|
CHECK(mrs->operand == 10);
|
||||||
CHECK(mrs->spsr == true);
|
CHECK(mrs->spsr == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "MRSMI R10,SPSR_all");
|
CHECK(instruction.disassemble() == "MRSMI R10,SPSR_all");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("MSR") {
|
SECTION("MSR") {
|
||||||
@@ -272,7 +293,9 @@ TEST_CASE("PSR Transfer", TAG) {
|
|||||||
CHECK(msr->operand == 8);
|
CHECK(msr->operand == 8);
|
||||||
CHECK(msr->spsr == false);
|
CHECK(msr->spsr == false);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "MSR CPSR_all,R8");
|
CHECK(instruction.disassemble() == "MSR CPSR_all,R8");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("MSR_flg with register operand") {
|
SECTION("MSR_flg with register operand") {
|
||||||
@@ -287,7 +310,9 @@ TEST_CASE("PSR Transfer", TAG) {
|
|||||||
CHECK(msr->operand == 8);
|
CHECK(msr->operand == 8);
|
||||||
CHECK(msr->spsr == false);
|
CHECK(msr->spsr == false);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "MSRVS CPSR_flg,R8");
|
CHECK(instruction.disassemble() == "MSRVS CPSR_flg,R8");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("MSR_flg with immediate operand") {
|
SECTION("MSR_flg with immediate operand") {
|
||||||
@@ -304,7 +329,9 @@ TEST_CASE("PSR Transfer", TAG) {
|
|||||||
CHECK(msr->operand == 27262976);
|
CHECK(msr->operand == 27262976);
|
||||||
CHECK(msr->spsr == true);
|
CHECK(msr->spsr == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "MSR SPSR_flg,#27262976");
|
CHECK(instruction.disassemble() == "MSR SPSR_flg,#27262976");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +358,7 @@ TEST_CASE("Data Processing", TAG) {
|
|||||||
CHECK(alu->set == true);
|
CHECK(alu->set == true);
|
||||||
CHECK(alu->opcode == OpCode::AND);
|
CHECK(alu->opcode == OpCode::AND);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "ANDS R7,R14,R1,ROR #22");
|
CHECK(instruction.disassemble() == "ANDS R7,R14,R1,ROR #22");
|
||||||
|
|
||||||
shift->data.immediate = false;
|
shift->data.immediate = false;
|
||||||
@@ -392,6 +420,7 @@ TEST_CASE("Data Processing", TAG) {
|
|||||||
alu->opcode = OpCode::MVN;
|
alu->opcode = OpCode::MVN;
|
||||||
CHECK(instruction.disassemble() == "MVN R7,#3300012");
|
CHECK(instruction.disassemble() == "MVN R7,#3300012");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Coprocessor Data Transfer", TAG) {
|
TEST_CASE("Coprocessor Data Transfer", TAG) {
|
||||||
@@ -412,6 +441,7 @@ TEST_CASE("Coprocessor Data Transfer", TAG) {
|
|||||||
CHECK(ldc->up == true);
|
CHECK(ldc->up == true);
|
||||||
CHECK(ldc->pre == true);
|
CHECK(ldc->pre == true);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "STCGE p1,c15,[R5,#70]!");
|
CHECK(instruction.disassemble() == "STCGE p1,c15,[R5,#70]!");
|
||||||
|
|
||||||
ldc->load = true;
|
ldc->load = true;
|
||||||
@@ -420,6 +450,7 @@ TEST_CASE("Coprocessor Data Transfer", TAG) {
|
|||||||
ldc->len = true;
|
ldc->len = true;
|
||||||
|
|
||||||
CHECK(instruction.disassemble() == "LDCGEL p1,c15,[R5],#70");
|
CHECK(instruction.disassemble() == "LDCGEL p1,c15,[R5],#70");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Coprocessor Operand Operation", TAG) {
|
TEST_CASE("Coprocessor Operand Operation", TAG) {
|
||||||
@@ -437,7 +468,9 @@ TEST_CASE("Coprocessor Operand Operation", TAG) {
|
|||||||
CHECK(cdp->crn == 5);
|
CHECK(cdp->crn == 5);
|
||||||
CHECK(cdp->cp_opc == 10);
|
CHECK(cdp->cp_opc == 10);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "CDP p1,10,c15,c5,c6,2");
|
CHECK(instruction.disassemble() == "CDP p1,10,c15,c5,c6,2");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Coprocessor Register Transfer", TAG) {
|
TEST_CASE("Coprocessor Register Transfer", TAG) {
|
||||||
@@ -457,7 +490,9 @@ TEST_CASE("Coprocessor Register Transfer", TAG) {
|
|||||||
CHECK(mrc->load == false);
|
CHECK(mrc->load == false);
|
||||||
CHECK(mrc->cp_opc == 5);
|
CHECK(mrc->cp_opc == 5);
|
||||||
|
|
||||||
|
#ifdef DISASSEMBLER
|
||||||
CHECK(instruction.disassemble() == "MCR p1,5,R15,c5,c6,2");
|
CHECK(instruction.disassemble() == "MCR p1,5,R15,c5,c6,2");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Software Interrupt", TAG) {
|
TEST_CASE("Software Interrupt", TAG) {
|
||||||
@@ -465,7 +500,8 @@ TEST_CASE("Software Interrupt", TAG) {
|
|||||||
Instruction instruction(raw);
|
Instruction instruction(raw);
|
||||||
|
|
||||||
CHECK(instruction.condition == Condition::EQ);
|
CHECK(instruction.condition == Condition::EQ);
|
||||||
CHECK(instruction.disassemble() == "SWIEQ");
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef TAG
|
#ifdef DISASSEMBLER
|
||||||
|
CHECK(instruction.disassemble() == "SWIEQ");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
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,22 @@ tests_deps = [
|
|||||||
|
|
||||||
src = include_directories('../src')
|
src = include_directories('../src')
|
||||||
|
|
||||||
tests_sources = files()
|
tests_sources = files(
|
||||||
|
'main.cc',
|
||||||
|
'bus.cc',
|
||||||
|
'memory.cc'
|
||||||
|
)
|
||||||
|
|
||||||
subdir('cpu')
|
subdir('cpu')
|
||||||
|
subdir('util')
|
||||||
|
|
||||||
catch2 = dependency('catch2-with-main', version: '>=3.4.0', static: true)
|
tests_cpp_args = []
|
||||||
|
|
||||||
|
if get_option('disassembler')
|
||||||
|
tests_cpp_args += '-DDISASSEMBLER'
|
||||||
|
endif
|
||||||
|
|
||||||
|
catch2 = dependency('catch2', version: '>=3.4.0', static: true)
|
||||||
catch2_tests = executable(
|
catch2_tests = executable(
|
||||||
'matar_tests',
|
'matar_tests',
|
||||||
tests_sources,
|
tests_sources,
|
||||||
@@ -16,6 +27,7 @@ catch2_tests = executable(
|
|||||||
link_with: tests_deps,
|
link_with: tests_deps,
|
||||||
include_directories: [inc, src],
|
include_directories: [inc, src],
|
||||||
build_by_default: false,
|
build_by_default: false,
|
||||||
|
cpp_args: tests_cpp_args
|
||||||
)
|
)
|
||||||
|
|
||||||
test('catch2 tests', catch2_tests)
|
test('catch2 tests', catch2_tests)
|
||||||
|
|||||||
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