add a basic structure for disassembler + executor

Instructions added
Branch and Exchange (BX)
Branch and Link (B)
Multiply and Accumulate (MUL, MLA)
Multiply Long and Accumulate (SMULL, SMLAL, UMULL, UMLAL)
Single data swap (SWP)
[WIP] Halfword Transfer (STRH, LDRH)

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-09-13 03:44:36 +05:30
parent 904e2b698e
commit 8a04eade92
22 changed files with 571 additions and 50 deletions

0
.gitmodules vendored
View File

View File

@@ -1,12 +1,18 @@
#include "bus.hh"
#include "cpu/cpu.hh"
#include "memory.hh"
#include <array>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <vector>
int
main(int argc, const char* argv[]) {
std::vector<uint8_t> rom;
std::array<uint8_t, Memory::BIOS_SIZE> bios;
auto usage = [argv]() {
std::cerr << "Usage: " << argv[0] << " <file> [-b <bios>]" << std::endl;
std::exit(EXIT_FAILURE);
@@ -35,8 +41,6 @@ main(int argc, const char* argv[]) {
try {
std::ifstream ifile(rom_file, std::ios::in | std::ios::binary);
std::vector<uint8_t> rom;
std::array<uint8_t, Memory::BIOS_SIZE> bios;
std::streampos bios_size;
if (!ifile.is_open()) {
@@ -68,11 +72,16 @@ main(int argc, const char* argv[]) {
ifile.close();
Memory memory(std::move(bios), std::move(rom));
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}
{
Memory memory(std::move(bios), std::move(rom));
Bus bus(std::make_shared<Memory>(memory));
Cpu cpu(std::make_shared<Bus>(bus));
cpu.step();
}
return 0;
}

1
include/bus.hh Symbolic link
View File

@@ -0,0 +1 @@
../src/bus.hh

1
include/cpu/cpu.hh Symbolic link
View File

@@ -0,0 +1 @@
../../src/cpu/cpu.hh

5
include/cpu/meson.build Normal file
View File

@@ -0,0 +1,5 @@
headers += files(
'cpu.hh',
'psr.hh',
'utility.hh'
)

1
include/cpu/psr.hh Symbolic link
View File

@@ -0,0 +1 @@
../../src/cpu/psr.hh

View File

@@ -1,3 +1,27 @@
#pragma once
#include <fmt/ostream.h>
#include <ostream>
static constexpr size_t ARM_INSTRUCTION_SIZE = 4;
static constexpr size_t THUMB_INSTRUCTION_SIZE = 2;
enum class Mode {
/* M[4:0] in PSR */
User = 0b10000,
Fiq = 0b10001,
Irq = 0b10010,
Supervisor = 0b10011,
Abort = 0b10111,
Undefined = 0b11011,
System = 0b11111,
};
enum class State {
Arm = 0,
Thumb = 1
};
enum class Condition {
EQ = 0b0000,
NE = 0b0001,
@@ -41,3 +65,9 @@ enum class ShiftType {
ASR = 0b10,
ROR = 0b11
};
// https://fmt.dev/dev/api.html#std-ostream-support
std::ostream&
operator<<(std::ostream& os, const Condition cond);
template<>
struct fmt::formatter<Condition> : ostream_formatter {};

View File

@@ -1,6 +1,9 @@
headers = files(
'memory.hh',
'header.hh'
'bus.hh',
'header.hh',
)
subdir('cpu')
install_headers(headers, subdir: meson.project_name(), preserve_path: true)

View File

@@ -1,4 +1,35 @@
#include "bus.hh"
#include <memory>
Bus::Bus(Memory&& memory)
: memory(std::move(memory)) {}
Bus::Bus(std::shared_ptr<Memory> memory)
: memory(memory) {}
uint8_t
Bus::read_byte(size_t address) {
return memory->read(address);
}
void
Bus::write_byte(size_t address, uint8_t byte) {
memory->write(address, byte);
}
uint16_t
Bus::read_halfword(size_t address) {
return memory->read_halfword(address);
}
void
Bus::write_halfword(size_t address, uint16_t halfword) {
memory->write_halfword(address, halfword);
}
uint32_t
Bus::read_word(size_t address) {
return memory->read_word(address);
}
void
Bus::write_word(size_t address, uint32_t word) {
memory->write_halfword(address, word);
}

View File

@@ -1,11 +1,21 @@
#pragma once
#include "memory.hh"
#include <memory>
class Bus {
public:
Bus(Memory&& memory);
Bus(std::shared_ptr<Memory> memory);
uint8_t read_byte(size_t address);
void write_byte(size_t address, uint8_t byte);
uint16_t read_halfword(size_t address);
void write_halfword(size_t address, uint16_t halfword);
uint32_t read_word(size_t address);
void write_word(size_t address, uint32_t word);
private:
Memory memory;
std::shared_ptr<Memory> memory;
};

View File

@@ -1,2 +0,0 @@
lib_sources += files(
)

View File

@@ -1,9 +1,12 @@
#include "cpu.hh"
#include "cpu/utility.hh"
#include "util/log.hh"
#include "utility.hh"
#include <algorithm>
#include <cstdio>
Cpu::Cpu(Bus bus)
using namespace logger;
Cpu::Cpu(std::shared_ptr<Bus> bus)
: gpr(0)
, cpsr(0)
, spsr(0)
@@ -14,6 +17,7 @@ Cpu::Cpu(Bus bus)
cpsr.set_irq_disabled(true);
cpsr.set_fiq_disabled(true);
cpsr.set_state(State::Arm);
log_info("CPU successfully initialised");
}
/* change modes */
@@ -103,15 +107,13 @@ Cpu::chg_mode(Mode from, Mode to) {
cpsr.set_mode(to);
}
// set register
inline uint32_t&
Cpu::operator[](uint8_t idx) {
// avoid unneeded complexity like index checks
return gpr[idx];
}
void
Cpu::step() {
uint32_t insn = 0xffffffff;
// get register
inline const uint32_t&
Cpu::operator[](uint8_t idx) const {
return gpr[idx];
if (cpsr.state() == State::Arm) {
std::string disassembled = exec_arm(insn);
log_info("{:#010X} : {}", gpr[15], disassembled);
gpr[15] += ARM_INSTRUCTION_SIZE;
}
}

View File

@@ -9,7 +9,8 @@ using std::size_t;
class Cpu {
public:
Cpu(Bus bus);
Cpu(std::shared_ptr<Bus> bus);
void step();
private:
static constexpr size_t GPR_FIQ_BANKED_FIRST = 8;
@@ -35,7 +36,7 @@ class Cpu {
uint32_t gpr[GPR_VISIBLE_COUNT]; // general purpose registers
Psr cpsr; // current program status register
Psr spsr; // status program status register
Bus bus;
std::shared_ptr<Bus> bus;
struct {
uint32_t fiq[GPR_FIQ_BANKED_COUNT];
@@ -57,7 +58,5 @@ class Cpu {
} spsr_banked; // banked saved program status registers
void chg_mode(Mode from, Mode to);
uint32_t& operator[](uint8_t idx);
const uint32_t& operator[](uint8_t idx) const;
std::string exec_arm(uint32_t insn);
};

294
src/cpu/instruction.cc Normal file
View File

@@ -0,0 +1,294 @@
#include "cpu.hh"
#include "cpu/utility.hh"
#include "util/bits.hh"
#include "util/log.hh"
#include <cstdint>
using namespace logger;
std::string
Cpu::exec_arm(uint32_t insn) {
Condition cond = static_cast<Condition>(get_bit_range(insn, 28, 31));
std::string disassembled;
auto pc_error = [](uint8_t r, const char* syn) {
if (r == 15)
log_error("Using PC (R15) as operand in {}", syn);
};
auto pc_undefined = [](uint8_t r, const char* syn) {
if (r == 15)
log_warn("Using PC (R15) as operand in {}", syn);
};
// Branch and exhcange
if ((insn & 0x0ffffff0) == 0x012fff10) {
static constexpr char syn[] = "BX";
uint8_t rn = insn & 0b1111;
pc_undefined(rn, syn);
disassembled = fmt::format("{}{} R{:d}", syn, cond, rn);
if (cpsr.condition(cond)) {
State state = static_cast<State>(rn & 1);
// set state
cpsr.set_state(state);
// copy to PC
gpr[15] = gpr[rn];
// ignore [1:0] bits for arm and 0 bit for thumb
rst_nth_bit(gpr[15], 0);
if (state == State::Arm)
rst_nth_bit(gpr[15], 1);
}
// Branch
} else if ((insn & 0x0e000000) == 0x0a000000) {
static constexpr char syn[] = "B";
bool link = get_nth_bit(insn, 24);
uint32_t offset = get_bit_range(insn, 0, 23);
disassembled =
fmt::format("{}{}{} {:#08X}", syn, (link ? "L" : ""), cond, offset);
if (cpsr.condition(cond)) {
// lsh 2 and sign extend the 26 bit offset to 32
// bits
offset <<= 2;
if (get_nth_bit(offset, 25))
offset |= 0xFC000000;
if (link)
gpr[14] = gpr[15] - ARM_INSTRUCTION_SIZE;
gpr[15] += offset;
}
// Multiply
} else if ((insn & 0x0fc000f0) == 0x00000090) {
static constexpr char syn[2][4] = { "MUL", "MLA" };
uint8_t rm = get_bit_range(insn, 0, 3);
uint8_t rs = get_bit_range(insn, 8, 11);
uint8_t rn = get_bit_range(insn, 12, 15);
uint8_t rd = get_bit_range(insn, 16, 19);
bool s = get_nth_bit(insn, 20);
bool a = get_nth_bit(insn, 21);
if (rd == rm)
log_error("rd and rm are not distinct in {} : {:d}", syn[a], rd);
pc_error(rd, syn[a]);
pc_error(rm, syn[a]);
pc_error(rs, syn[a]);
if (a) {
pc_error(rn, syn[a]);
disassembled = fmt::format("{}{}{} R{:d},R{:d},R{:d},R{:d}",
syn[a],
cond,
(s ? "S" : ""),
rd,
rm,
rs,
rn);
} else {
disassembled = fmt::format("{}{}{} R{:d},R{:d},R{:d}",
syn[a],
cond,
(s ? "S" : ""),
rd,
rm,
rs);
}
if (cpsr.condition(cond)) {
gpr[rd] = gpr[rm] * gpr[rs] + (a ? gpr[rn] : 0);
if (s) {
cpsr.set_z(!static_cast<bool>(gpr[rd]));
cpsr.set_n(get_nth_bit(gpr[rd], 31));
cpsr.set_c(0);
}
}
// Multiply long
} else if ((insn & 0x0f8000f0) == 0x00800090) {
static constexpr char syn[2][2][6] = { { "SMULL", "SMLAL" },
{ "UMULL", "UMLAL" } };
uint8_t rm = get_bit_range(insn, 0, 3);
uint8_t rs = get_bit_range(insn, 8, 11);
uint8_t rdlo = get_bit_range(insn, 12, 15);
uint8_t rdhi = get_bit_range(insn, 16, 19);
bool s = get_nth_bit(insn, 20);
bool a = get_nth_bit(insn, 21);
bool u = get_nth_bit(insn, 22);
if (rdhi == rdlo || rdhi == rm || rdlo == rm)
log_error("rdhi, rdlo and rm are not distinct in {}", syn[u][a]);
pc_error(rdhi, syn[u][a]);
pc_error(rdlo, syn[u][a]);
pc_error(rm, syn[u][a]);
pc_error(rs, syn[u][a]);
disassembled = fmt::format("{}{}{} R{:d},R{:d},R{:d},R{:d}",
syn[u][a],
cond,
(s ? "S" : ""),
rdlo,
rdhi,
rm,
rs);
if (cpsr.condition(cond)) {
if (u) {
uint64_t eval = static_cast<uint64_t>(gpr[rm]) *
static_cast<uint64_t>(gpr[rs]) +
(a ? static_cast<uint64_t>(gpr[rdhi]) << 32 |
static_cast<uint64_t>(gpr[rdlo])
: 0);
gpr[rdlo] = get_bit_range(eval, 0, 31);
gpr[rdhi] = get_bit_range(eval, 32, 63);
} else {
int64_t eval = static_cast<uint64_t>(gpr[rm]) *
static_cast<int64_t>(gpr[rs]) +
(a ? static_cast<int64_t>(gpr[rdhi]) << 32 |
static_cast<int64_t>(gpr[rdlo])
: 0);
gpr[rdlo] = get_bit_range(eval, 0, 31);
gpr[rdhi] = get_bit_range(eval, 32, 63);
}
if (s) {
cpsr.set_z(!(static_cast<bool>(gpr[rdhi]) ||
static_cast<bool>(gpr[rdlo])));
cpsr.set_n(get_nth_bit(gpr[rdhi], 31));
cpsr.set_c(0);
cpsr.set_v(0);
}
}
// Single data swap
} else if ((insn & 0x0fb00ff0) == 0x01000090) {
static constexpr char syn[] = "SWP";
uint8_t rm = get_bit_range(insn, 0, 3);
uint8_t rd = get_bit_range(insn, 12, 15);
uint8_t rn = get_bit_range(insn, 16, 19);
bool b = get_nth_bit(insn, 22);
pc_undefined(rm, syn);
pc_undefined(rn, syn);
pc_undefined(rd, syn);
disassembled = fmt::format(
"{}{}{} R{:d},R{:d},[R{:d}]", syn, cond, (b ? "B" : ""), rd, rm, rn);
if (cpsr.condition(cond)) {
if (b) {
gpr[rd] = bus->read_byte(gpr[rn]);
bus->write_byte(gpr[rn], gpr[rm] & 0xFF);
} else {
gpr[rd] = bus->read_word(gpr[rn]);
bus->write_word(gpr[rn], gpr[rm]);
}
}
// Halfword transfer
// TODO: create abstraction to reuse for block data and single data
// transfer
} else if ((insn & 0x0e000090) == 0x00000090) {
static constexpr char syn[2][4] = { "STR", "LDR" };
uint8_t rm = get_bit_range(insn, 0, 3);
uint8_t h = get_nth_bit(insn, 5);
uint8_t s = get_nth_bit(insn, 6);
uint8_t rd = get_bit_range(insn, 12, 15);
uint8_t rn = get_bit_range(insn, 16, 19);
bool l = get_nth_bit(insn, 20);
bool w = get_nth_bit(insn, 21);
bool imm = get_nth_bit(insn, 22);
bool u = get_nth_bit(insn, 23);
bool p = get_nth_bit(insn, 24);
uint32_t offset;
if (!p && w)
log_error("Write-back enabled with post-indexing in {}", syn[l]);
if (s && !l)
log_error("Signed data found in {}", syn[l]);
if (w)
pc_error(rn, syn[l]);
pc_error(rm, syn[l]);
if (rd == 15 && !l && s && h)
;
{
offset = (imm ? get_bit_range(insn, 8, 11) << 4 | rm : gpr[rm]);
std::string offset_str = fmt::format("{}{}{:d}",
(u ? "" : "-"),
(imm ? '#' : 'R'),
(imm ? offset : rm));
disassembled = fmt::format(
"{}{}{}{} R{:d}{}",
syn[l],
cond,
(s ? "S" : ""),
(h ? 'H' : 'B'),
rd,
(!offset ? fmt::format("[R{:d}]", rn)
: p
? fmt::format(",[R{:d},{}]{}", rn, offset_str, (w ? "!" : ""))
: fmt::format(",[R{:d}],{}", rn, offset_str)));
}
if (cpsr.condition(cond)) {
uint32_t address = (u ? gpr[rn] + offset : gpr[rn] - offset);
// load
if (l) {
// signed
if (s) {
// halfword
if (h) {
gpr[rd] = bus->read_halfword(address);
// sign extend the halfword
if (get_nth_bit(gpr[rd], 15))
gpr[rd] |= 0xffff0000;
// byte
} else {
// sign extend the byte
gpr[rd] = bus->read_byte(address);
if (get_nth_bit(gpr[rd], 7))
gpr[rd] |= 0xffffff00;
}
// unsigned halfword
} else if (h) {
gpr[rd] = bus->read_halfword(address);
}
// store
} else {
// halfword
if (h) {
// take PC into consideration
if (rd == 15)
address += 12;
bus->write_halfword(address, gpr[rd]);
}
}
}
}
return disassembled;
}

View File

@@ -1,6 +1,6 @@
lib_sources += files(
'cpu.cc',
'psr.cc'
'instruction.cc',
'psr.cc',
'utility.cc'
)
subdir('arm')

View File

@@ -1,5 +1,6 @@
#include "psr.hh"
#include "util/bits.hh"
#include "util/log.hh"
Psr::Psr(uint32_t raw) {
psr = raw & PSR_CLEAR_RESERVED;
@@ -16,9 +17,9 @@ Psr::set_mode(Mode mode) {
psr |= static_cast<uint32_t>(mode);
}
bool
State
Psr::state() const {
return get_nth_bit(psr, 5);
return static_cast<State>(get_nth_bit(psr, 5));
}
void
@@ -47,3 +48,39 @@ GET_SET_NTH_BIT_FUNCTIONS(z, 30);
GET_SET_NTH_BIT_FUNCTIONS(n, 31);
#undef GET_SET_NTH_BIT_FUNCTIONS
bool
Psr::condition(Condition cond) const {
switch (cond) {
case Condition::EQ:
return z();
case Condition::NE:
return !z();
case Condition::CS:
return c();
case Condition::CC:
return !c();
case Condition::MI:
return n();
case Condition::PL:
return !n();
case Condition::VS:
return v();
case Condition::VC:
return !v();
case Condition::HI:
return c() && !z();
case Condition::LS:
return !c() || z();
case Condition::GE:
return n() == v();
case Condition::LT:
return n() != v();
case Condition::GT:
return !z() && (n() == v());
case Condition::LE:
return z() || (n() != v());
case Condition::AL:
return true;
}
}

View File

@@ -1,6 +1,5 @@
#pragma once
#include "util/bits.hh"
#include "utility.hh"
#include <cstdint>
@@ -14,7 +13,7 @@ class Psr {
void set_mode(Mode mode);
// State : [5]
bool state() const;
State state() const;
void set_state(State state);
#define GET_SET_NTH_BIT_FUNCTIONS(name) \
@@ -43,6 +42,8 @@ class Psr {
#undef GET_SET_NTH_BIT_FUNCTIONS
bool condition(Condition cond) const;
private:
static constexpr uint32_t PSR_CLEAR_RESERVED = 0xf00000ff;
static constexpr uint32_t PSR_CLEAR_MODE = 0x0b00000;

34
src/cpu/utility.cc Normal file
View File

@@ -0,0 +1,34 @@
#include "utility.hh"
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;
}

View File

@@ -1,5 +1,11 @@
#pragma once
#include <fmt/ostream.h>
#include <ostream>
static constexpr size_t ARM_INSTRUCTION_SIZE = 4;
static constexpr size_t THUMB_INSTRUCTION_SIZE = 2;
enum class Mode {
/* M[4:0] in PSR */
User = 0b10000,
@@ -15,3 +21,53 @@ enum class State {
Arm = 0,
Thumb = 1
};
enum class Condition {
EQ = 0b0000,
NE = 0b0001,
CS = 0b0010,
CC = 0b0011,
MI = 0b0100,
PL = 0b0101,
VS = 0b0110,
VC = 0b0111,
HI = 0b1000,
LS = 0b1001,
GE = 0b1010,
LT = 0b1011,
GT = 0b1100,
LE = 0b1101,
AL = 0b1110
};
enum class OpCode {
AND = 0b0000,
EOR = 0b0001,
SUB = 0b0010,
RSB = 0b0011,
ADD = 0b0100,
ADC = 0b0101,
SBC = 0b0110,
RSC = 0b0111,
TST = 0b1000,
TEQ = 0b1001,
CMP = 0b1010,
CMN = 0b1011,
ORR = 0b1100,
MOV = 0b1101,
BIC = 0b1110,
MVN = 0b1111
};
enum class ShiftType {
LSL = 0b00,
LSR = 0b01,
ASR = 0b10,
ROR = 0b11
};
// https://fmt.dev/dev/api.html#std-ostream-support
std::ostream&
operator<<(std::ostream& os, const Condition cond);
template<>
struct fmt::formatter<Condition> : ostream_formatter {};

View File

@@ -1,7 +1,11 @@
#include "memory.hh"
#include "header.hh"
#include "util/bits.hh"
#include "util/log.hh"
#include "util/utils.hh"
#include <bitset>
using namespace logger;
Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
std::vector<uint8_t>&& rom) noexcept
@@ -13,20 +17,21 @@ Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
, oam_obj_attr(0)
, rom(std::move(rom)) {
std::string bios_hash = crypto::sha256(bios.data(), bios.size());
std::string expected_hash =
static constexpr char expected_hash[] =
"fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570";
if (bios_hash != expected_hash) {
log_warn("BIOS hash failed to match, run at your own risk",
"\nExpected : ",
log_warn("BIOS hash failed to match, run at your own risk"
"\nExpected : {} "
"\nGot : {}",
expected_hash,
"\nGot : ",
bios_hash);
}
parse_header();
log("Memory successfully initialised");
log_info("Memory successfully initialised");
log_info("Cartridge Title: {}", header.title);
};
#define MATCHES(area) address >= area##_START&& address <= area##_END
@@ -164,7 +169,7 @@ Memory::parse_header() {
break;
default:
log_error("HEADER: invalid unique code: ", rom[0xAC]);
log_error("HEADER: invalid unique code: {}", rom[0xAC]);
}
header.title_code[0] = rom[0xAD];
@@ -194,7 +199,7 @@ Memory::parse_header() {
break;
default:
log_error("HEADER: invalid destination/language - ", rom[0xAF]);
log_error("HEADER: invalid destination/language: {}", rom[0xAF]);
}
if (rom[0xB2] != 0x96)

View File

@@ -6,10 +6,13 @@ lib_sources = files(
subdir('util')
subdir('cpu')
fmt = dependency('fmt', version : '>=10.1.0')
lib = library(
meson.project_name(),
lib_sources,
install: true
dependencies: [fmt],
install: true,
cpp_args: '-DFMT_HEADER_ONLY'
)
import('pkgconfig').generate(lib)

View File

@@ -8,7 +8,7 @@ using std::size_t;
template<std::integral Int>
inline bool
get_nth_bit(Int num, size_t n) {
return (1 && (num >> n));
return (num >> n) & 1;
}
template<std::integral Int>
@@ -26,14 +26,15 @@ rst_nth_bit(Int& num, size_t n) {
template<std::integral Int>
inline void
chg_nth_bit(Int& num, size_t n, bool x) {
num ^= (num ^ -x) & 1 << n;
num = (num & ~(1 << n)) | (x << n);
}
/// read range of bits from start to end inclusive
template<std::unsigned_integral Int>
template<std::integral Int>
inline Int
get_bit_range(Int& num, size_t start, size_t end) {
// NOTE: we do not require -1 if it is a signed integral (which it is not)
Int left = std::numeric_limits<Int>::digits - 1 - end;
get_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);
}