bus (feat): add cycle accuracy

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2024-06-15 03:49:10 +05:30
parent cb75ebf8ef
commit c22333812e
9 changed files with 657 additions and 108 deletions

View File

@@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span> #include <span>
#include <unordered_map>
#include <vector> #include <vector>
namespace matar { namespace matar {
@@ -22,14 +23,19 @@ class Bus {
static std::shared_ptr<Bus> init(std::array<uint8_t, BIOS_SIZE>&&, static std::shared_ptr<Bus> init(std::array<uint8_t, BIOS_SIZE>&&,
std::vector<uint8_t>&&); std::vector<uint8_t>&&);
uint8_t read_byte(uint32_t); uint8_t read_byte(uint32_t, bool = true);
void write_byte(uint32_t, uint8_t); void write_byte(uint32_t, uint8_t, bool = true);
uint16_t read_halfword(uint32_t); uint16_t read_halfword(uint32_t, bool = true);
void write_halfword(uint32_t, uint16_t); void write_halfword(uint32_t, uint16_t, bool = true);
uint32_t read_word(uint32_t); uint32_t read_word(uint32_t, bool = true);
void write_word(uint32_t, uint32_t); void write_word(uint32_t, uint32_t, bool = true);
// not sure what else to do?
inline void internal_cycle() { cycles++; }
inline uint32_t get_cycles() { return cycles; }
private: private:
template<unsigned int> template<unsigned int>
@@ -38,9 +44,21 @@ class Bus {
template<unsigned int> template<unsigned int>
std::optional<std::span<uint8_t>> write(uint32_t); std::optional<std::span<uint8_t>> write(uint32_t);
uint32_t cycles = 0;
struct cycle_count {
uint8_t n16; // non sequential 8/16 bit width access
uint8_t n32; // non sequential 32 bit width access
uint8_t s16; // seuquential 8/16 bit width access
uint8_t s32; // sequential 32 bit width access
};
std::array<cycle_count, 0x10> cycle_map;
static constexpr decltype(cycle_map) init_cycle_count();
std::unique_ptr<IoDevices> io;
#define MEMORY_REGION(name, start) \ #define MEMORY_REGION(name, start) \
static constexpr uint32_t name##_START = start; \ static constexpr uint32_t name##_START = start; \
static constexpr uint8_t name##_REGION = start >> 24 & 0xFF; static constexpr uint8_t name##_REGION = start >> 24 & 0xF;
#define DECL_MEMORY(name, ident, start, end) \ #define DECL_MEMORY(name, ident, start, end) \
MEMORY_REGION(name, start) \ MEMORY_REGION(name, start) \
@@ -70,12 +88,12 @@ class Bus {
MEMORY_REGION(ROM_1, 0x0A000000) MEMORY_REGION(ROM_1, 0x0A000000)
MEMORY_REGION(ROM_2, 0x0C000000) MEMORY_REGION(ROM_2, 0x0C000000)
MEMORY_REGION(IO, 0x04000000)
static constexpr uint32_t IO_END = 0x040003FE;
#undef MEMORY_REGION #undef MEMORY_REGION
std::vector<uint8_t> rom; std::vector<uint8_t> rom;
std::unique_ptr<IoDevices> io;
Header header; Header header;
void parse_header(); void parse_header();
}; };

View File

@@ -49,4 +49,7 @@ add(uint32_t a, uint32_t b, bool& carry, bool& overflow, bool c = 0);
uint32_t uint32_t
sbc(uint32_t a, uint32_t b, bool& carry, bool& overflow, bool c); sbc(uint32_t a, uint32_t b, bool& carry, bool& overflow, bool c);
uint8_t
multiplier_array_cycles(uint32_t x, bool zeroes_only = false);
} }

View File

@@ -16,6 +16,23 @@ class Cpu {
void step(); void step();
void chg_mode(const Mode to); void chg_mode(const Mode to);
bool is_flushed = false;
inline void flush_pipeline() {
is_flushed = true;
if (cpsr.state() == State::Arm) {
opcodes[0] = bus->read_word(pc, false);
advance_pc_arm();
opcodes[1] = bus->read_word(pc);
advance_pc_arm();
} else {
opcodes[0] = bus->read_halfword(pc, false);
advance_pc_thumb();
opcodes[1] = bus->read_halfword(pc);
advance_pc_thumb();
}
sequential = true;
};
private: private:
friend void arm::Instruction::exec(Cpu& cpu); friend void arm::Instruction::exec(Cpu& cpu);
friend void thumb::Instruction::exec(Cpu& cpu); friend void thumb::Instruction::exec(Cpu& cpu);
@@ -66,26 +83,15 @@ class Cpu {
Psr und; Psr und;
} spsr_banked = {}; // banked saved program status registers } spsr_banked = {}; // banked saved program status registers
inline void internal_cycle() { bus->internal_cycle(); }
// whether read is going to be sequential or not
bool sequential = true;
// raw instructions in the pipeline // raw instructions in the pipeline
std::array<uint32_t, 2> opcodes = {}; std::array<uint32_t, 2> opcodes = {};
inline void advance_pc_arm() { pc += arm::INSTRUCTION_SIZE; }; inline void advance_pc_arm() { pc += arm::INSTRUCTION_SIZE; };
inline void advance_pc_thumb() { pc += thumb::INSTRUCTION_SIZE; } inline void advance_pc_thumb() { pc += thumb::INSTRUCTION_SIZE; }
bool is_flushed = false;
inline void flush_pipeline() {
is_flushed = true;
if (cpsr.state() == State::Arm) {
opcodes[0] = bus->read_word(pc);
advance_pc_arm();
opcodes[1] = bus->read_word(pc);
advance_pc_arm();
} else {
opcodes[0] = bus->read_halfword(pc);
advance_pc_thumb();
opcodes[1] = bus->read_halfword(pc);
advance_pc_thumb();
}
};
}; };
} }

View File

@@ -4,13 +4,11 @@
namespace matar { namespace matar {
static constexpr uint32_t IO_START = 0x4000000;
static constexpr uint32_t IO_END = 0x40003FE;
Bus::Bus(Private, Bus::Bus(Private,
std::array<uint8_t, BIOS_SIZE>&& bios, std::array<uint8_t, BIOS_SIZE>&& bios,
std::vector<uint8_t>&& rom) std::vector<uint8_t>&& rom)
: bios(std::move(bios)) : cycle_map(init_cycle_count())
, bios(std::move(bios))
, rom(std::move(rom)) { , rom(std::move(rom)) {
std::string bios_hash = crypto::sha256(this->bios); std::string bios_hash = crypto::sha256(this->bios);
static constexpr std::string_view expected_hash = static constexpr std::string_view expected_hash =
@@ -38,11 +36,52 @@ Bus::init(std::array<uint8_t, BIOS_SIZE>&& bios, std::vector<uint8_t>&& rom) {
return self; return self;
} }
constexpr decltype(Bus::cycle_map)
Bus::init_cycle_count() {
/*
Region Bus Read Write Cycles
BIOS ROM 32 8/16/32 - 1/1/1
Work RAM 32K 32 8/16/32 8/16/32 1/1/1
I/O 32 8/16/32 8/16/32 1/1/1
OAM 32 8/16/32 16/32 1/1/1 *
Work RAM 256K 16 8/16/32 8/16/32 3/3/6 **
Palette RAM 16 8/16/32 16/32 1/1/2 *
VRAM 16 8/16/32 16/32 1/1/2 *
GamePak ROM 16 8/16/32 - 5/5/8 **|***
GamePak Flash 16 8/16/32 16/32 5/5/8 **|***
GamePak SRAM 8 8 8 5 **
Timing Notes:
* Plus 1 cycle if GBA accesses video memory at the same time.
** Default waitstate settings, see System Control chapter.
*** Separate timings for sequential, and non-sequential accesses.
One cycle equals approx. 59.59ns (ie. 16.78MHz clock).
*/
decltype(cycle_map) map;
map.fill({ 1, 1, 1, 1 });
/* used fill instead of this
map[BIOS_REGION] = { 1, 1, 1, 1 };
map[CHIP_WRAM_REGION] = { 1, 1, 1, 1 };
map[IO_REGION] = { 1, 1, 1, 1 };
map[OAM_REGION] = { 1, 1, 1, 1 };
*/
map[3] = { 1, 1, 1, 1 };
map[BOARD_WRAM_REGION] = { .n16 = 3, .n32 = 6, .s16 = 3, .s32 = 6 };
map[PALETTE_RAM_REGION] = { .n16 = 1, .n32 = 2, .s16 = 1, .s32 = 2 };
map[VRAM_REGION] = { .n16 = 1, .n32 = 2, .s16 = 1, .s32 = 2 };
// TODO: GamePak access cycles
return map;
}
template<unsigned int N> template<unsigned int N>
std::optional<std::span<const uint8_t>> std::optional<std::span<const uint8_t>>
Bus::read(uint32_t address) const { Bus::read(uint32_t address) const {
switch (address >> 24 & 0xFF) { switch (address >> 24 & 0xF) {
#define MATCHES(AREA, area) \ #define MATCHES(AREA, area) \
case AREA##_REGION: \ case AREA##_REGION: \
@@ -80,7 +119,7 @@ template<unsigned int N>
std::optional<std::span<uint8_t>> std::optional<std::span<uint8_t>>
Bus::write(uint32_t address) { Bus::write(uint32_t address) {
switch (address >> 24 & 0xFF) { switch (address >> 24 & 0xF) {
#define MATCHES(AREA, area) \ #define MATCHES(AREA, area) \
case AREA##_REGION: \ case AREA##_REGION: \
@@ -97,12 +136,14 @@ Bus::write(uint32_t address) {
#undef MATCHES #undef MATCHES
} }
glogger.error("Invalid memory region written");
return {}; return {};
} }
uint8_t uint8_t
Bus::read_byte(uint32_t address) { Bus::read_byte(uint32_t address, bool sequential) {
auto cc = cycle_map[address >> 24 & 0xF];
cycles += sequential ? cc.s16 : cc.n16;
if (address >= IO_START && address <= IO_END) if (address >= IO_START && address <= IO_END)
return io->read_byte(address); return io->read_byte(address);
@@ -111,7 +152,10 @@ Bus::read_byte(uint32_t address) {
} }
void void
Bus::write_byte(uint32_t address, uint8_t byte) { Bus::write_byte(uint32_t address, uint8_t byte, bool sequential) {
auto cc = cycle_map[address >> 24 & 0xF];
cycles += sequential ? cc.s16 : cc.n16;
if (address >= IO_START && address <= IO_END) { if (address >= IO_START && address <= IO_END) {
io->write_byte(address, byte); io->write_byte(address, byte);
return; return;
@@ -124,10 +168,13 @@ Bus::write_byte(uint32_t address, uint8_t byte) {
} }
uint16_t uint16_t
Bus::read_halfword(uint32_t address) { Bus::read_halfword(uint32_t address, bool sequential) {
if (address & 0b01) if (address & 0b01)
glogger.warn("Reading a non aligned halfword address"); glogger.warn("Reading a non aligned halfword address");
auto cc = cycle_map[address >> 24 & 0xF];
cycles += sequential ? cc.s16 : cc.n16;
if (address >= IO_START && address <= IO_END) if (address >= IO_START && address <= IO_END)
return io->read_halfword(address); return io->read_halfword(address);
@@ -137,10 +184,13 @@ Bus::read_halfword(uint32_t address) {
} }
void void
Bus::write_halfword(uint32_t address, uint16_t halfword) { Bus::write_halfword(uint32_t address, uint16_t halfword, bool sequential) {
if (address & 0b01) if (address & 0b01)
glogger.warn("Writing to a non aligned halfword address"); glogger.warn("Writing to a non aligned halfword address");
auto cc = cycle_map[address >> 24 & 0xF];
cycles += sequential ? cc.s16 : cc.n16;
if (address >= IO_START && address <= IO_END) { if (address >= IO_START && address <= IO_END) {
io->write_halfword(address, halfword); io->write_halfword(address, halfword);
return; return;
@@ -156,10 +206,13 @@ Bus::write_halfword(uint32_t address, uint16_t halfword) {
} }
uint32_t uint32_t
Bus::read_word(uint32_t address) { Bus::read_word(uint32_t address, bool sequential) {
if (address & 0b11) if (address & 0b11)
glogger.warn("Reading a non aligned word address"); glogger.warn("Reading a non aligned word address");
auto cc = cycle_map[address >> 24 & 0xF];
cycles += sequential ? cc.s32 : cc.n32;
if (address >= IO_START && address <= IO_END) if (address >= IO_START && address <= IO_END)
return io->read_word(address); return io->read_word(address);
@@ -171,10 +224,13 @@ Bus::read_word(uint32_t address) {
} }
void void
Bus::write_word(uint32_t address, uint32_t word) { Bus::write_word(uint32_t address, uint32_t word, bool sequential) {
if (address & 0b11) if (address & 0b11)
glogger.warn("Writing to a non aligned word address"); glogger.warn("Writing to a non aligned word address");
auto cc = cycle_map[address >> 24 & 0xF];
cycles += sequential ? cc.s32 : cc.n32;
if (address >= IO_START && address <= IO_END) { if (address >= IO_START && address <= IO_END) {
io->write_word(address, word); io->write_word(address, word);
return; return;

View File

@@ -88,4 +88,21 @@ sbc(uint32_t a, uint32_t b, bool& carry, bool& overflow, bool c) {
return result & 0xFFFFFFFF; return result & 0xFFFFFFFF;
} }
uint8_t
multiplier_array_cycles(uint32_t x, bool zeroes_only) {
// set zeroes_only to evaluate first condition that checks ones to false
if ((!zeroes_only && (x & 0xFFFFFF00) == 0xFFFFFF00) ||
(x & 0xFFFFFF00) == 0)
return 1;
if ((!zeroes_only && (x & 0xFFFF0000) == 0xFFFF0000) ||
(x & 0xFFFF0000) == 0)
return 2;
if ((!zeroes_only && (x & 0xFF000000) == 0xFF000000) ||
(x & 0xFF000000) == 0)
return 3;
return 4;
};
} }

View File

@@ -24,6 +24,14 @@ Instruction::exec(Cpu& cpu) {
std::visit( std::visit(
overloaded{ overloaded{
[&cpu, pc_warn](BranchAndExchange& data) { [&cpu, pc_warn](BranchAndExchange& data) {
/*
S -> reading instruction in step()
N -> fetch from the new address in branch
S -> last opcode fetch at +L to refill the pipeline
Total = 2S + N cycles
1S done, S+N taken care of by flush_pipeline()
*/
uint32_t addr = cpu.gpr[data.rn]; uint32_t addr = cpu.gpr[data.rn];
State state = static_cast<State>(get_bit(addr, 0)); State state = static_cast<State>(get_bit(addr, 0));
@@ -48,6 +56,14 @@ Instruction::exec(Cpu& cpu) {
cpu.is_flushed = true; cpu.is_flushed = true;
}, },
[&cpu](Branch& data) { [&cpu](Branch& data) {
/*
S -> reading instruction in step()
N -> fetch from the new address in branch
S -> last opcode fetch at +L to refill the pipeline
Total = 2S + N cycles
1S done, S+N taken care of by flush_pipeline()
*/
if (data.link) if (data.link)
cpu.gpr[14] = cpu.pc - INSTRUCTION_SIZE; cpu.gpr[14] = cpu.pc - INSTRUCTION_SIZE;
@@ -57,6 +73,19 @@ Instruction::exec(Cpu& cpu) {
cpu.is_flushed = true; cpu.is_flushed = true;
}, },
[&cpu, pc_error](Multiply& data) { [&cpu, pc_error](Multiply& data) {
/*
S -> reading instruction in step()
mI -> m internal cycles
I -> only when accumulating
let v = data at rn
m = 1 if bits [32:8] of v are all zero or all one
m = 2 [32:16]
m = 3 [32:24]
m = 4 otherwise
Total = S + mI or S + (m+1)I
*/
if (data.rd == data.rm) if (data.rd == data.rm)
glogger.error("rd and rm are not distinct in {}", glogger.error("rd and rm are not distinct in {}",
typeid(data).name()); typeid(data).name());
@@ -65,8 +94,17 @@ Instruction::exec(Cpu& cpu) {
pc_error(data.rd); pc_error(data.rd);
pc_error(data.rd); pc_error(data.rd);
cpu.gpr[data.rd] = cpu.gpr[data.rm] * cpu.gpr[data.rs] + // mI
(data.acc ? cpu.gpr[data.rn] : 0); for (int i = 0; i < multiplier_array_cycles(cpu.gpr[data.rs]); i++)
cpu.internal_cycle();
cpu.gpr[data.rd] = cpu.gpr[data.rm] * cpu.gpr[data.rs];
if (data.acc) {
cpu.gpr[data.rd] += cpu.gpr[data.rn];
// 1I
cpu.internal_cycle();
}
if (data.set) { if (data.set) {
cpu.cpsr.set_z(cpu.gpr[data.rd] == 0); cpu.cpsr.set_z(cpu.gpr[data.rd] == 0);
@@ -75,6 +113,21 @@ Instruction::exec(Cpu& cpu) {
} }
}, },
[&cpu, pc_error](MultiplyLong& data) { [&cpu, pc_error](MultiplyLong& data) {
/*
S -> reading instruction in step()
(m+1)I -> m + 1 internal cycles
I -> only when accumulating
let v = data at rn
m = 1 if bits [32:8] of v are all zeroes (or all ones if signed)
m = 2 [32:16]
m = 3 [32:24]
m = 4 otherwise
Total = S + mI or S + (m+1)I
Total = S + (m+1)I or S + (m+2)I
*/
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)
glogger.error("rdhi, rdlo and rm are not distinct in {}", glogger.error("rdhi, rdlo and rm are not distinct in {}",
@@ -85,6 +138,16 @@ Instruction::exec(Cpu& cpu) {
pc_error(data.rm); pc_error(data.rm);
pc_error(data.rs); pc_error(data.rs);
// 1I
if (data.acc)
cpu.internal_cycle();
// m+1 internal cycles
for (int i = 0;
i <= multiplier_array_cycles(cpu.gpr[data.rs], data.uns);
i++)
cpu.internal_cycle();
if (data.uns) { if (data.uns) {
auto cast = [](uint32_t x) -> uint64_t { auto cast = [](uint32_t x) -> uint64_t {
return static_cast<uint64_t>(x); return static_cast<uint64_t>(x);
@@ -121,21 +184,53 @@ Instruction::exec(Cpu& cpu) {
cpu.cpsr.set_v(0); cpu.cpsr.set_v(0);
} }
}, },
[](Undefined) { glogger.warn("Undefined instruction"); }, [](Undefined) {
// this should be 2S + N + I, should i flush the pipeline? i dont
// know. TODO: study
glogger.warn("Undefined instruction");
},
[&cpu, pc_error](SingleDataSwap& data) { [&cpu, pc_error](SingleDataSwap& data) {
/*
N -> reading instruction in step()
N -> unrelated read
S -> related write
I -> earlier read value is written to register
Total = S + 2N +I
*/
pc_error(data.rm); pc_error(data.rm);
pc_error(data.rn); pc_error(data.rn);
pc_error(data.rd); pc_error(data.rd);
if (data.byte) { if (data.byte) {
cpu.gpr[data.rd] = cpu.bus->read_byte(cpu.gpr[data.rn]); cpu.gpr[data.rd] = cpu.bus->read_byte(cpu.gpr[data.rn], false);
cpu.bus->write_byte(cpu.gpr[data.rn], cpu.gpr[data.rm] & 0xFF); cpu.bus->write_byte(
cpu.gpr[data.rn], cpu.gpr[data.rm] & 0xFF, true);
} else { } else {
cpu.gpr[data.rd] = cpu.bus->read_word(cpu.gpr[data.rn]); cpu.gpr[data.rd] = cpu.bus->read_word(cpu.gpr[data.rn], false);
cpu.bus->write_word(cpu.gpr[data.rn], cpu.gpr[data.rm]); cpu.bus->write_word(cpu.gpr[data.rn], cpu.gpr[data.rm], true);
} }
cpu.internal_cycle();
// last write address is unrelated to next
cpu.sequential = false;
}, },
[&cpu, pc_warn, pc_error](SingleDataTransfer& data) { [&cpu, pc_warn, pc_error](SingleDataTransfer& data) {
/*
Load
====
S -> reading instruction in step()
N -> read from target
I -> stored in register
N+S -> if PC is written - taken care of by flush_pipeline()
Total = S + N + I or 2S + 2N + I
Store
=====
N -> calculating memory address
N -> write at target
Total = 2N
*/
uint32_t offset = 0; uint32_t offset = 0;
uint32_t address = cpu.gpr[data.rn]; uint32_t address = cpu.gpr[data.rn];
@@ -178,10 +273,17 @@ Instruction::exec(Cpu& cpu) {
if (data.load) { if (data.load) {
// byte // byte
if (data.byte) if (data.byte)
cpu.gpr[data.rd] = cpu.bus->read_byte(address); cpu.gpr[data.rd] = cpu.bus->read_byte(address, false);
// word // word
else else
cpu.gpr[data.rd] = cpu.bus->read_word(address); cpu.gpr[data.rd] = cpu.bus->read_word(address, false);
// N + S
if (data.rd == cpu.PC_INDEX)
cpu.is_flushed = true;
// I
cpu.internal_cycle();
// store // store
} else { } else {
// take PC into consideration // take PC into consideration
@@ -190,10 +292,11 @@ Instruction::exec(Cpu& cpu) {
// byte // byte
if (data.byte) if (data.byte)
cpu.bus->write_byte(address, cpu.gpr[data.rd] & 0xFF); cpu.bus->write_byte(
address, cpu.gpr[data.rd] & 0xFF, false);
// word // word
else else
cpu.bus->write_word(address, cpu.gpr[data.rd]); cpu.bus->write_word(address, cpu.gpr[data.rd], false);
} }
if (!data.pre) if (!data.pre)
@@ -202,10 +305,26 @@ Instruction::exec(Cpu& cpu) {
if (!data.pre || data.write) if (!data.pre || data.write)
cpu.gpr[data.rn] = address; cpu.gpr[data.rn] = address;
if (data.rd == cpu.PC_INDEX && data.load) // last read/write is unrelated, this will be overwriten if flushed
cpu.is_flushed = true; cpu.sequential = false;
}, },
[&cpu, pc_warn, pc_error](HalfwordTransfer& data) { [&cpu, pc_warn, pc_error](HalfwordTransfer& data) {
/*
Load
====
S -> reading instruction in step()
N -> read from target
I -> stored in register
N+S -> if PC is written - taken care of by flush_pipeline()
Total = S + N + I or 2S + 2N + I
Store
=====
N -> calculating memory address
N -> write at target
Total = 2N
*/
uint32_t address = cpu.gpr[data.rn]; uint32_t address = cpu.gpr[data.rn];
uint32_t offset = 0; uint32_t offset = 0;
@@ -240,7 +359,8 @@ Instruction::exec(Cpu& cpu) {
if (data.sign) { if (data.sign) {
// halfword // halfword
if (data.half) { if (data.half) {
cpu.gpr[data.rd] = cpu.bus->read_halfword(address); cpu.gpr[data.rd] =
cpu.bus->read_halfword(address, false);
// sign extend the halfword // sign extend the halfword
cpu.gpr[data.rd] = cpu.gpr[data.rd] =
@@ -248,7 +368,7 @@ Instruction::exec(Cpu& cpu) {
// byte // byte
} else { } else {
cpu.gpr[data.rd] = cpu.bus->read_byte(address); cpu.gpr[data.rd] = cpu.bus->read_byte(address, false);
// sign extend the byte // sign extend the byte
cpu.gpr[data.rd] = cpu.gpr[data.rd] =
@@ -256,8 +376,15 @@ Instruction::exec(Cpu& cpu) {
} }
// unsigned halfword // unsigned halfword
} else if (data.half) { } else if (data.half) {
cpu.gpr[data.rd] = cpu.bus->read_halfword(address); cpu.gpr[data.rd] = cpu.bus->read_halfword(address, false);
} }
// I
cpu.internal_cycle();
if (data.rd == cpu.PC_INDEX)
cpu.is_flushed = true;
// store // store
} else { } else {
// take PC into consideration // take PC into consideration
@@ -266,7 +393,7 @@ Instruction::exec(Cpu& cpu) {
// halfword // halfword
if (data.half) if (data.half)
cpu.bus->write_halfword(address, cpu.gpr[data.rd]); cpu.bus->write_halfword(address, cpu.gpr[data.rd], false);
} }
if (!data.pre) if (!data.pre)
@@ -275,15 +402,34 @@ Instruction::exec(Cpu& cpu) {
if (!data.pre || data.write) if (!data.pre || data.write)
cpu.gpr[data.rn] = address; cpu.gpr[data.rn] = address;
if (data.rd == cpu.PC_INDEX && data.load) // last read/write is unrelated, this will be overwriten if flushed
cpu.is_flushed = true; cpu.sequential = false;
}, },
[&cpu, pc_error](BlockDataTransfer& data) { [&cpu, pc_error](BlockDataTransfer& data) {
/*
Load
====
S -> reading instruction in step()
N -> unrelated read from target
(n-1) S -> next n - 1 related reads from target
I -> stored in register
N+S -> if PC is written - taken care of by flush_pipeline()
Total = nS + N + I or (n+1)S + 2N + I
Store
=====
N -> calculating memory address
N -> unrelated write at target
(n-1) S -> next n - 1 related writes
Total = 2N + (n-1)S
*/
static constexpr uint8_t alignment = 4; // word static constexpr uint8_t alignment = 4; // word
uint32_t address = cpu.gpr[data.rn]; uint32_t address = cpu.gpr[data.rn];
Mode mode = cpu.cpsr.mode(); Mode mode = cpu.cpsr.mode();
int8_t i = 0; int8_t i = 0;
bool sequential = false;
pc_error(data.rn); pc_error(data.rn);
@@ -308,40 +454,54 @@ Instruction::exec(Cpu& cpu) {
address += (data.up ? alignment : -alignment); address += (data.up ? alignment : -alignment);
if (data.load) { if (data.load) {
if (get_bit(data.regs, cpu.PC_INDEX) && data.s && data.load) { if (get_bit(data.regs, cpu.PC_INDEX)) {
cpu.is_flushed = true;
// current mode's cpu.spsr is already loaded when it was // current mode's cpu.spsr is already loaded when it was
// switched // switched
if (data.s)
cpu.spsr = cpu.cpsr; cpu.spsr = cpu.cpsr;
} }
if (data.up) { if (data.up) {
for (i = 0; i < cpu.GPR_COUNT; i++) { for (i = 0; i < cpu.GPR_COUNT; i++) {
if (get_bit(data.regs, i)) { if (get_bit(data.regs, i)) {
cpu.gpr[i] = cpu.bus->read_word(address); cpu.gpr[i] =
cpu.bus->read_word(address, sequential);
address += alignment; address += alignment;
sequential = true;
} }
} }
} else { } else {
for (i = cpu.GPR_COUNT - 1; i >= 0; i--) { for (i = cpu.GPR_COUNT - 1; i >= 0; i--) {
if (get_bit(data.regs, i)) { if (get_bit(data.regs, i)) {
cpu.gpr[i] = cpu.bus->read_word(address); cpu.gpr[i] =
cpu.bus->read_word(address, sequential);
address -= alignment; address -= alignment;
sequential = true;
} }
} }
} }
// I
cpu.internal_cycle();
} else { } else {
if (data.up) { if (data.up) {
for (i = 0; i < cpu.GPR_COUNT; i++) { for (i = 0; i < cpu.GPR_COUNT; i++) {
if (get_bit(data.regs, i)) { if (get_bit(data.regs, i)) {
cpu.bus->write_word(address, cpu.gpr[i]); cpu.bus->write_word(
address, cpu.gpr[i], sequential);
address += alignment; address += alignment;
sequential = true;
} }
} }
} else { } else {
for (i = cpu.GPR_COUNT - 1; i >= 0; i--) { for (i = cpu.GPR_COUNT - 1; i >= 0; i--) {
if (get_bit(data.regs, i)) { if (get_bit(data.regs, i)) {
cpu.bus->write_word(address, cpu.gpr[i]); cpu.bus->write_word(
address, cpu.gpr[i], sequential);
address -= alignment; address -= alignment;
sequential = true;
} }
} }
} }
@@ -354,13 +514,18 @@ Instruction::exec(Cpu& cpu) {
if (!data.pre || data.write) if (!data.pre || data.write)
cpu.gpr[data.rn] = address; cpu.gpr[data.rn] = address;
if (data.load && get_bit(data.regs, cpu.PC_INDEX))
cpu.is_flushed = true;
// load back the original mode registers // load back the original mode registers
cpu.chg_mode(mode); cpu.chg_mode(mode);
// last read/write is unrelated, this will be overwriten if flushed
cpu.sequential = false;
}, },
[&cpu, pc_error](PsrTransfer& data) { [&cpu, pc_error](PsrTransfer& data) {
/*
S -> prefetched instruction in step()
Total = 1S cycle
*/
if (data.spsr && cpu.cpsr.mode() == Mode::User) { if (data.spsr && cpu.cpsr.mode() == Mode::User) {
glogger.error("Accessing CPU.SPSR in User mode in {}", glogger.error("Accessing CPU.SPSR in User mode in {}",
typeid(data).name()); typeid(data).name());
@@ -396,6 +561,24 @@ Instruction::exec(Cpu& cpu) {
} }
}, },
[&cpu, pc_error](DataProcessing& data) { [&cpu, pc_error](DataProcessing& data) {
/*
Always
======
S -> prefetched instruction in step()
With Register specified shift
=============================
I -> internal cycle
When PC is written
==================
N -> fetch from the new address in branch
S -> last opcode fetch at +L to refill the pipeline
S+N taken care of by flush_pipeline()
Total = S or S + I or 2S + N + I or 2S + N cycles
*/
using OpCode = DataProcessing::OpCode; using OpCode = DataProcessing::OpCode;
uint32_t op_1 = cpu.gpr[data.rn]; uint32_t op_1 = cpu.gpr[data.rn];
@@ -425,6 +608,10 @@ Instruction::exec(Cpu& cpu) {
// PC is 12 bytes ahead when shifting // PC is 12 bytes ahead when shifting
if (data.rn == cpu.PC_INDEX) if (data.rn == cpu.PC_INDEX)
op_1 += INSTRUCTION_SIZE; op_1 += INSTRUCTION_SIZE;
// 1I when register specified shift
if (shift->data.operand)
cpu.internal_cycle();
} }
bool overflow = cpu.cpsr.v(); bool overflow = cpu.cpsr.v();

View File

@@ -129,11 +129,10 @@ Cpu::step() {
// word align // word align
rst_bit(pc, 1); rst_bit(pc, 1);
uint32_t next_opcode = bus->read_word(pc);
arm::Instruction instruction(opcodes[0]); arm::Instruction instruction(opcodes[0]);
opcodes[0] = opcodes[1]; opcodes[0] = opcodes[1];
opcodes[1] = next_opcode; opcodes[1] = bus->read_word(pc, sequential);
#ifdef DISASSEMBLER #ifdef DISASSEMBLER
glogger.info("0x{:08X} : {}", glogger.info("0x{:08X} : {}",
@@ -149,11 +148,10 @@ Cpu::step() {
} else } else
advance_pc_arm(); advance_pc_arm();
} else { } else {
uint32_t next_opcode = bus->read_halfword(pc);
thumb::Instruction instruction(opcodes[0]); thumb::Instruction instruction(opcodes[0]);
opcodes[0] = opcodes[1]; opcodes[0] = opcodes[1];
opcodes[1] = next_opcode; opcodes[1] = bus->read_halfword(pc, sequential);
#ifdef DISASSEMBLER #ifdef DISASSEMBLER
glogger.info("0x{:08X} : {}", glogger.info("0x{:08X} : {}",
@@ -162,6 +160,7 @@ Cpu::step() {
#endif #endif
instruction.exec(*this); instruction.exec(*this);
if (is_flushed) { if (is_flushed) {
flush_pipeline(); flush_pipeline();
is_flushed = false; is_flushed = false;

View File

@@ -1,3 +1,4 @@
#include "cpu/alu.hh"
#include "cpu/cpu.hh" #include "cpu/cpu.hh"
#include "util/bits.hh" #include "util/bits.hh"
#include "util/log.hh" #include "util/log.hh"
@@ -15,6 +16,11 @@ Instruction::exec(Cpu& cpu) {
std::visit( std::visit(
overloaded{ overloaded{
[&cpu, set_cc](MoveShiftedRegister& data) { [&cpu, set_cc](MoveShiftedRegister& data) {
/*
S -> prefetched instruction in step()
Total = S cycle
*/
if (data.opcode == ShiftType::ROR) if (data.opcode == ShiftType::ROR)
glogger.error("Invalid opcode in {}", typeid(data).name()); glogger.error("Invalid opcode in {}", typeid(data).name());
@@ -28,6 +34,11 @@ Instruction::exec(Cpu& cpu) {
set_cc(carry, cpu.cpsr.v(), get_bit(shifted, 31), shifted == 0); set_cc(carry, cpu.cpsr.v(), get_bit(shifted, 31), shifted == 0);
}, },
[&cpu, set_cc](AddSubtract& data) { [&cpu, set_cc](AddSubtract& data) {
/*
S -> prefetched instruction in step()
Total = S cycle
*/
uint32_t offset = uint32_t offset =
data.imm ? static_cast<uint32_t>(static_cast<int8_t>(data.offset)) data.imm ? static_cast<uint32_t>(static_cast<int8_t>(data.offset))
: cpu.gpr[data.offset]; : cpu.gpr[data.offset];
@@ -48,6 +59,11 @@ Instruction::exec(Cpu& cpu) {
set_cc(carry, overflow, get_bit(result, 31), result == 0); set_cc(carry, overflow, get_bit(result, 31), result == 0);
}, },
[&cpu, set_cc](MovCmpAddSubImmediate& data) { [&cpu, set_cc](MovCmpAddSubImmediate& data) {
/*
S -> prefetched instruction in step()
Total = S cycle
*/
uint32_t result = 0; uint32_t result = 0;
bool carry = cpu.cpsr.c(); bool carry = cpu.cpsr.c();
bool overflow = cpu.cpsr.v(); bool overflow = cpu.cpsr.v();
@@ -73,6 +89,25 @@ Instruction::exec(Cpu& cpu) {
cpu.gpr[data.rd] = result; cpu.gpr[data.rd] = result;
}, },
[&cpu, set_cc](AluOperations& data) { [&cpu, set_cc](AluOperations& data) {
/*
Data Processing
===============
S -> prefetched instruction in step()
I -> only when register specified shift
Total = S or S + I cycles
Multiply
========
S -> reading instruction in step()
mI -> m internal cycles
let v = data at rn
m = 1 if bits [32:8] of v are all zero or all one
m = 2 [32:16]
m = 3 [32:24]
m = 4 otherwise
Total = S + mI cycles
*/
uint32_t op_1 = cpu.gpr[data.rd]; uint32_t op_1 = cpu.gpr[data.rd];
uint32_t op_2 = cpu.gpr[data.rs]; uint32_t op_2 = cpu.gpr[data.rs];
uint32_t result = 0; uint32_t result = 0;
@@ -90,12 +125,15 @@ Instruction::exec(Cpu& cpu) {
break; break;
case AluOperations::OpCode::LSL: case AluOperations::OpCode::LSL:
result = eval_shift(ShiftType::LSL, op_1, op_2, carry); result = eval_shift(ShiftType::LSL, op_1, op_2, carry);
cpu.internal_cycle();
break; break;
case AluOperations::OpCode::LSR: case AluOperations::OpCode::LSR:
result = eval_shift(ShiftType::LSR, op_1, op_2, carry); result = eval_shift(ShiftType::LSR, op_1, op_2, carry);
cpu.internal_cycle();
break; break;
case AluOperations::OpCode::ASR: case AluOperations::OpCode::ASR:
result = eval_shift(ShiftType::ASR, op_1, op_2, carry); result = eval_shift(ShiftType::ASR, op_1, op_2, carry);
cpu.internal_cycle();
break; break;
case AluOperations::OpCode::ADC: case AluOperations::OpCode::ADC:
result = add(op_1, op_2, carry, overflow, carry); result = add(op_1, op_2, carry, overflow, carry);
@@ -105,6 +143,7 @@ Instruction::exec(Cpu& cpu) {
break; break;
case AluOperations::OpCode::ROR: case AluOperations::OpCode::ROR:
result = eval_shift(ShiftType::ROR, op_1, op_2, carry); result = eval_shift(ShiftType::ROR, op_1, op_2, carry);
cpu.internal_cycle();
break; break;
case AluOperations::OpCode::NEG: case AluOperations::OpCode::NEG:
result = -op_2; result = -op_2;
@@ -120,6 +159,9 @@ Instruction::exec(Cpu& cpu) {
break; break;
case AluOperations::OpCode::MUL: case AluOperations::OpCode::MUL:
result = op_1 * op_2; result = op_1 * op_2;
// mI cycles
for (int i = 0; i < multiplier_array_cycles(op_2); i++)
cpu.internal_cycle();
break; break;
case AluOperations::OpCode::BIC: case AluOperations::OpCode::BIC:
result = op_1 & ~op_2; result = op_1 & ~op_2;
@@ -137,6 +179,20 @@ Instruction::exec(Cpu& cpu) {
set_cc(carry, overflow, get_bit(result, 31), result == 0); set_cc(carry, overflow, get_bit(result, 31), result == 0);
}, },
[&cpu, set_cc](HiRegisterOperations& data) { [&cpu, set_cc](HiRegisterOperations& data) {
/*
Always
======
S -> prefetched instruction in step()
When PC is written
==================
N -> fetch from the new address in branch
S -> last opcode fetch at +L to refill the pipeline
S+N taken care of by flush_pipeline()
Total = S or 2S + N cycles
*/
uint32_t op_1 = cpu.gpr[data.rd]; uint32_t op_1 = cpu.gpr[data.rd];
uint32_t op_2 = cpu.gpr[data.rs]; uint32_t op_2 = cpu.gpr[data.rs];
@@ -191,95 +247,157 @@ Instruction::exec(Cpu& cpu) {
} }
}, },
[&cpu](PcRelativeLoad& data) { [&cpu](PcRelativeLoad& data) {
/*
S -> reading instruction in step()
N -> read from target
I -> stored in register
Total = S + N + I cycles
*/
uint32_t pc = cpu.pc; uint32_t pc = cpu.pc;
rst_bit(pc, 0); rst_bit(pc, 0);
rst_bit(pc, 1); rst_bit(pc, 1);
cpu.gpr[data.rd] = cpu.bus->read_word(pc + data.word); cpu.gpr[data.rd] = cpu.bus->read_word(pc + data.word, false);
cpu.internal_cycle();
// last read is unrelated
cpu.sequential = false;
}, },
[&cpu](LoadStoreRegisterOffset& data) { [&cpu](LoadStoreRegisterOffset& data) {
/*
Load
====
S -> reading instruction in step()
N -> read from target
I -> stored in register
Total = S + N + I
Store
=====
N -> calculating memory address
N -> write at target
Total = 2N
*/
uint32_t address = cpu.gpr[data.rb] + cpu.gpr[data.ro]; uint32_t address = cpu.gpr[data.rb] + cpu.gpr[data.ro];
if (data.load) { if (data.load) {
if (data.byte) { if (data.byte) {
cpu.gpr[data.rd] = cpu.bus->read_byte(address); cpu.gpr[data.rd] = cpu.bus->read_byte(address, false);
} else { } else {
cpu.gpr[data.rd] = cpu.bus->read_word(address); cpu.gpr[data.rd] = cpu.bus->read_word(address, false);
} }
cpu.internal_cycle();
} else { } else {
if (data.byte) { if (data.byte) {
cpu.bus->write_byte(address, cpu.gpr[data.rd] & 0xFF); cpu.bus->write_byte(
address, cpu.gpr[data.rd] & 0xFF, false);
} else { } else {
cpu.bus->write_word(address, cpu.gpr[data.rd]); cpu.bus->write_word(address, cpu.gpr[data.rd], false);
} }
} }
// last read/write is unrelated
cpu.sequential = false;
}, },
[&cpu](LoadStoreSignExtendedHalfword& data) { [&cpu](LoadStoreSignExtendedHalfword& data) {
// Same cycles as above
uint32_t address = cpu.gpr[data.rb] + cpu.gpr[data.ro]; uint32_t address = cpu.gpr[data.rb] + cpu.gpr[data.ro];
switch (data.s << 1 | data.h) { switch (data.s << 1 | data.h) {
case 0b00: case 0b00:
cpu.bus->write_halfword(address, cpu.gpr[data.rd] & 0xFFFF); cpu.bus->write_halfword(
address, cpu.gpr[data.rd] & 0xFFFF, false);
break; break;
case 0b01: case 0b01:
cpu.gpr[data.rd] = cpu.bus->read_halfword(address); cpu.gpr[data.rd] = cpu.bus->read_halfword(address, false);
cpu.internal_cycle();
break; break;
case 0b10: case 0b10:
// sign extend and load the byte // sign extend and load the byte
cpu.gpr[data.rd] = cpu.gpr[data.rd] =
(static_cast<int32_t>(cpu.bus->read_byte(address)) (static_cast<int32_t>(cpu.bus->read_byte(address, false))
<< 24) >> << 24) >>
24; 24;
cpu.internal_cycle();
break; break;
case 0b11: case 0b11:
// sign extend the halfword // sign extend the halfword
cpu.gpr[data.rd] = cpu.gpr[data.rd] =
(static_cast<int32_t>(cpu.bus->read_halfword(address)) (static_cast<int32_t>(
cpu.bus->read_halfword(address, false))
<< 16) >> << 16) >>
16; 16;
cpu.internal_cycle();
break; break;
// unreachable // unreachable
default: { default: {
} }
} }
// last read/write is unrelated
cpu.sequential = false;
}, },
[&cpu](LoadStoreImmediateOffset& data) { [&cpu](LoadStoreImmediateOffset& data) {
// Same cycles as above
uint32_t address = cpu.gpr[data.rb] + data.offset; uint32_t address = cpu.gpr[data.rb] + data.offset;
if (data.load) { if (data.load) {
if (data.byte) { if (data.byte) {
cpu.gpr[data.rd] = cpu.bus->read_byte(address); cpu.gpr[data.rd] = cpu.bus->read_byte(address, false);
} else { } else {
cpu.gpr[data.rd] = cpu.bus->read_word(address); cpu.gpr[data.rd] = cpu.bus->read_word(address, false);
} }
cpu.internal_cycle();
} else { } else {
if (data.byte) { if (data.byte) {
cpu.bus->write_byte(address, cpu.gpr[data.rd] & 0xFF); cpu.bus->write_byte(
address, cpu.gpr[data.rd] & 0xFF, false);
} else { } else {
cpu.bus->write_word(address, cpu.gpr[data.rd]); cpu.bus->write_word(address, cpu.gpr[data.rd], false);
} }
} }
// last read/write is unrelated
cpu.sequential = false;
}, },
[&cpu](LoadStoreHalfword& data) { [&cpu](LoadStoreHalfword& data) {
// Same cycles as above
uint32_t address = cpu.gpr[data.rb] + data.offset; uint32_t address = cpu.gpr[data.rb] + data.offset;
if (data.load) { if (data.load) {
cpu.gpr[data.rd] = cpu.bus->read_halfword(address); cpu.gpr[data.rd] = cpu.bus->read_halfword(address);
cpu.internal_cycle();
} else { } else {
cpu.bus->write_halfword(address, cpu.gpr[data.rd] & 0xFFFF); cpu.bus->write_halfword(address, cpu.gpr[data.rd] & 0xFFFF);
} }
// last read/write is unrelated
cpu.sequential = false;
}, },
[&cpu](SpRelativeLoad& data) { [&cpu](SpRelativeLoad& data) {
// Same cycles as above
uint32_t address = cpu.sp + data.word; uint32_t address = cpu.sp + data.word;
if (data.load) { if (data.load) {
cpu.gpr[data.rd] = cpu.bus->read_word(address); cpu.gpr[data.rd] = cpu.bus->read_word(address);
cpu.internal_cycle();
} else { } else {
cpu.bus->write_word(address, cpu.gpr[data.rd]); cpu.bus->write_word(address, cpu.gpr[data.rd]);
} }
// last read/write is unrelated
cpu.sequential = false;
}, },
[&cpu](LoadAddress& data) { [&cpu](LoadAddress& data) {
// 1S cycle in step()
if (data.sp) { if (data.sp) {
cpu.gpr[data.rd] = cpu.sp + data.word; cpu.gpr[data.rd] = cpu.sp + data.word;
} else { } else {
@@ -288,15 +406,38 @@ Instruction::exec(Cpu& cpu) {
cpu.gpr[data.rd] = (cpu.pc & ~(1 << 1)) + data.word; cpu.gpr[data.rd] = (cpu.pc & ~(1 << 1)) + data.word;
} }
}, },
[&cpu](AddOffsetStackPointer& data) { cpu.sp += data.word; }, [&cpu](AddOffsetStackPointer& data) {
// 1S cycle in step()
cpu.sp += data.word;
},
[&cpu](PushPopRegister& data) { [&cpu](PushPopRegister& data) {
/*
Load
====
S -> reading instruction in step()
N -> unrelated read from target
(n-1) S -> next n - 1 related reads from target
I -> stored in register
N+S -> if PC is written - taken care of by flush_pipeline()
Total = nS + N + I or (n+1)S + 2N + I
Store
=====
N -> calculating memory address
N -> unrelated write at target
(n-1) S -> next n - 1 related writes
Total = 2N + (n-1)S
*/
static constexpr uint8_t alignment = 4; static constexpr uint8_t alignment = 4;
bool sequential = false;
if (data.load) { if (data.load) {
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
if (get_bit(data.regs, i)) { if (get_bit(data.regs, i)) {
cpu.gpr[i] = cpu.bus->read_word(cpu.sp); cpu.gpr[i] = cpu.bus->read_word(cpu.sp, sequential);
cpu.sp += alignment; cpu.sp += alignment;
sequential = true;
} }
} }
@@ -305,6 +446,9 @@ Instruction::exec(Cpu& cpu) {
cpu.sp += alignment; cpu.sp += alignment;
cpu.is_flushed = true; cpu.is_flushed = true;
} }
// I
cpu.internal_cycle();
} else { } else {
if (data.pclr) { if (data.pclr) {
cpu.sp -= alignment; cpu.sp -= alignment;
@@ -314,35 +458,68 @@ Instruction::exec(Cpu& cpu) {
for (int8_t i = 7; i >= 0; i--) { for (int8_t i = 7; i >= 0; i--) {
if (get_bit(data.regs, i)) { if (get_bit(data.regs, i)) {
cpu.sp -= alignment; cpu.sp -= alignment;
cpu.bus->write_word(cpu.sp, cpu.gpr[i]); cpu.bus->write_word(cpu.sp, cpu.gpr[i], sequential);
sequential = true;
} }
} }
} }
// last read/write is unrelated
cpu.sequential = false;
}, },
[&cpu](MultipleLoad& data) { [&cpu](MultipleLoad& data) {
/*
Load
====
S -> reading instruction in step()
N -> unrelated read from target
(n-1) S -> next n - 1 related reads from target
I -> stored in register
Total = nS + N + I
Store
=====
N -> calculating memory address
N -> unrelated write at target
(n-1) S -> next n - 1 related writes
Total = 2N + (n-1)S
*/
static constexpr uint8_t alignment = 4; static constexpr uint8_t alignment = 4;
uint32_t rb = cpu.gpr[data.rb]; uint32_t rb = cpu.gpr[data.rb];
bool sequential = false;
if (data.load) { if (data.load) {
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
if (get_bit(data.regs, i)) { if (get_bit(data.regs, i)) {
cpu.gpr[i] = cpu.bus->read_word(rb); cpu.gpr[i] = cpu.bus->read_word(rb, sequential);
rb += alignment; rb += alignment;
sequential = true;
} }
} }
} else { } else {
for (int8_t i = 7; i >= 0; i--) { for (int8_t i = 7; i >= 0; i--) {
if (get_bit(data.regs, i)) { if (get_bit(data.regs, i)) {
rb -= alignment; rb -= alignment;
cpu.bus->write_word(rb, cpu.gpr[i]); cpu.bus->write_word(rb, cpu.gpr[i], sequential);
sequential = true;
} }
} }
} }
cpu.gpr[data.rb] = rb; cpu.gpr[data.rb] = rb;
// last read/write is unrelated
cpu.sequential = false;
}, },
[&cpu](ConditionalBranch& data) { [&cpu](ConditionalBranch& data) {
/*
S -> reading instruction in step()
N+S -> if condition is true, branch and refill pipeline
Total = S or 2S + N
*/
if (data.condition == Condition::AL) if (data.condition == Condition::AL)
glogger.warn("Condition 1110 (AL) is undefined"); glogger.warn("Condition 1110 (AL) is undefined");
@@ -353,6 +530,12 @@ Instruction::exec(Cpu& cpu) {
cpu.is_flushed = true; cpu.is_flushed = true;
}, },
[&cpu](SoftwareInterrupt& data) { [&cpu](SoftwareInterrupt& data) {
/*
S -> reading instruction in step()
N+S -> refill pipeline
Total = 2S + N
*/
// next instruction is one instruction behind PC // next instruction is one instruction behind PC
cpu.lr = cpu.pc - INSTRUCTION_SIZE; cpu.lr = cpu.pc - INSTRUCTION_SIZE;
cpu.spsr = cpu.cpsr; cpu.spsr = cpu.cpsr;
@@ -362,10 +545,24 @@ Instruction::exec(Cpu& cpu) {
cpu.is_flushed = true; cpu.is_flushed = true;
}, },
[&cpu](UnconditionalBranch& data) { [&cpu](UnconditionalBranch& data) {
/*
S -> reading instruction in step()
N+S -> branch and refill pipeline
Total = 2S + N
*/
cpu.pc += data.offset; cpu.pc += data.offset;
cpu.is_flushed = true; cpu.is_flushed = true;
}, },
[&cpu](LongBranchWithLink& data) { [&cpu](LongBranchWithLink& data) {
/*
S -> prefetched instruction in step()
N -> fetch from the new address in branch
S -> last opcode fetch at +L to refill the pipeline
Total = 2S + N cycles
1S done, S+N taken care of by flush_pipeline()
*/
// 12 bit integer // 12 bit integer
int32_t offset = data.offset; int32_t offset = data.offset;

View File

@@ -26,12 +26,18 @@ TEST_CASE("bios", TAG) {
auto bus = auto bus =
Bus::init(std::move(bios), std::vector<uint8_t>(Header::HEADER_SIZE)); Bus::init(std::move(bios), std::vector<uint8_t>(Header::HEADER_SIZE));
uint32_t cycles = bus->get_cycles();
CHECK(bus->read_byte(0) == 0xAC); CHECK(bus->read_byte(0) == 0xAC);
CHECK(bus->read_byte(0x3FFF) == 0x48); CHECK(bus->read_byte(0x3FFF) == 0x48);
CHECK(bus->read_byte(0x2A56) == 0x10); CHECK(bus->read_byte(0x2A56) == 0x10);
CHECK(bus->get_cycles() == cycles + 3);
} }
TEST_CASE_METHOD(BusFixture, "board wram", TAG) { TEST_CASE_METHOD(BusFixture, "board wram", TAG) {
uint32_t cycles = bus->get_cycles();
bus->write_byte(0x2000000, 0xAC); bus->write_byte(0x2000000, 0xAC);
CHECK(bus->read_byte(0x2000000) == 0xAC); CHECK(bus->read_byte(0x2000000) == 0xAC);
@@ -40,9 +46,25 @@ TEST_CASE_METHOD(BusFixture, "board wram", TAG) {
bus->write_byte(0x2022A56, 0x10); bus->write_byte(0x2022A56, 0x10);
CHECK(bus->read_byte(0x2022A56) == 0x10); CHECK(bus->read_byte(0x2022A56) == 0x10);
CHECK(bus->get_cycles() == cycles + 2 * 9);
cycles = bus->get_cycles();
bus->write_halfword(0x2022A56, 0x1009);
CHECK(bus->read_halfword(0x2022A56) == 0x1009);
CHECK(bus->get_cycles() == cycles + 2 * 3);
cycles = bus->get_cycles();
bus->write_word(0x2022A56, 0x10FF9903);
CHECK(bus->read_word(0x2022A56) == 0x10FF9903);
CHECK(bus->get_cycles() == cycles + 2 * 6);
} }
TEST_CASE_METHOD(BusFixture, "chip wram", TAG) { TEST_CASE_METHOD(BusFixture, "chip wram", TAG) {
uint32_t cycles = bus->get_cycles();
bus->write_byte(0x3000000, 0xAC); bus->write_byte(0x3000000, 0xAC);
CHECK(bus->read_byte(0x3000000) == 0xAC); CHECK(bus->read_byte(0x3000000) == 0xAC);
@@ -51,9 +73,25 @@ TEST_CASE_METHOD(BusFixture, "chip wram", TAG) {
bus->write_byte(0x3002A56, 0x10); bus->write_byte(0x3002A56, 0x10);
CHECK(bus->read_byte(0x3002A56) == 0x10); CHECK(bus->read_byte(0x3002A56) == 0x10);
CHECK(bus->get_cycles() == cycles + 2 * 3);
cycles = bus->get_cycles();
bus->write_halfword(0x3002A56, 0xF0F0);
CHECK(bus->read_halfword(0x3002A56) == 0xF0F0);
CHECK(bus->get_cycles() == cycles + 2);
cycles = bus->get_cycles();
bus->write_word(0x3002A56, 0xF9399010);
CHECK(bus->read_word(0x3002A56) == 0xF9399010);
CHECK(bus->get_cycles() == cycles + 2);
} }
TEST_CASE_METHOD(BusFixture, "palette ram", TAG) { TEST_CASE_METHOD(BusFixture, "palette ram", TAG) {
uint32_t cycles = bus->get_cycles();
bus->write_byte(0x5000000, 0xAC); bus->write_byte(0x5000000, 0xAC);
CHECK(bus->read_byte(0x5000000) == 0xAC); CHECK(bus->read_byte(0x5000000) == 0xAC);
@@ -62,9 +100,25 @@ TEST_CASE_METHOD(BusFixture, "palette ram", TAG) {
bus->write_byte(0x5000156, 0x10); bus->write_byte(0x5000156, 0x10);
CHECK(bus->read_byte(0x5000156) == 0x10); CHECK(bus->read_byte(0x5000156) == 0x10);
CHECK(bus->get_cycles() == cycles + 2 * 3);
cycles = bus->get_cycles();
bus->write_halfword(0x5000156, 0xEEE1);
CHECK(bus->read_halfword(0x5000156) == 0xEEE1);
CHECK(bus->get_cycles() == cycles + 2);
cycles = bus->get_cycles();
bus->write_word(0x5000156, 0x938566E0);
CHECK(bus->read_word(0x5000156) == 0x938566E0);
CHECK(bus->get_cycles() == cycles + 2 * 2);
} }
TEST_CASE_METHOD(BusFixture, "video ram", TAG) { TEST_CASE_METHOD(BusFixture, "video ram", TAG) {
uint32_t cycles = bus->get_cycles();
bus->write_byte(0x6000000, 0xAC); bus->write_byte(0x6000000, 0xAC);
CHECK(bus->read_byte(0x6000000) == 0xAC); CHECK(bus->read_byte(0x6000000) == 0xAC);
@@ -73,9 +127,25 @@ TEST_CASE_METHOD(BusFixture, "video ram", TAG) {
bus->write_byte(0x6012A56, 0x10); bus->write_byte(0x6012A56, 0x10);
CHECK(bus->read_byte(0x6012A56) == 0x10); CHECK(bus->read_byte(0x6012A56) == 0x10);
CHECK(bus->get_cycles() == cycles + 2 * 3);
cycles = bus->get_cycles();
bus->write_halfword(0x6012A56, 0xB100);
CHECK(bus->read_halfword(0x6012A56) == 0xB100);
CHECK(bus->get_cycles() == cycles + 2);
cycles = bus->get_cycles();
bus->write_word(0x6012A56, 0x9322093E);
CHECK(bus->read_word(0x6012A56) == 0x9322093E);
CHECK(bus->get_cycles() == cycles + 2 * 2);
} }
TEST_CASE_METHOD(BusFixture, "oam obj ram", TAG) { TEST_CASE_METHOD(BusFixture, "oam obj ram", TAG) {
uint32_t cycles = bus->get_cycles();
bus->write_byte(0x7000000, 0xAC); bus->write_byte(0x7000000, 0xAC);
CHECK(bus->read_byte(0x7000000) == 0xAC); CHECK(bus->read_byte(0x7000000) == 0xAC);
@@ -84,6 +154,20 @@ TEST_CASE_METHOD(BusFixture, "oam obj ram", TAG) {
bus->write_byte(0x7000156, 0x10); bus->write_byte(0x7000156, 0x10);
CHECK(bus->read_byte(0x7000156) == 0x10); CHECK(bus->read_byte(0x7000156) == 0x10);
CHECK(bus->get_cycles() == cycles + 2 * 3);
cycles = bus->get_cycles();
bus->write_halfword(0x7000156, 0x946C);
CHECK(bus->read_halfword(0x7000156) == 0x946C);
CHECK(bus->get_cycles() == cycles + 2);
cycles = bus->get_cycles();
bus->write_word(0x7000156, 0x93C5D1E0);
CHECK(bus->read_word(0x7000156) == 0x93C5D1E0);
CHECK(bus->get_cycles() == cycles + 2);
} }
TEST_CASE("rom", TAG) { TEST_CASE("rom", TAG) {
@@ -116,22 +200,4 @@ TEST_CASE("rom", TAG) {
} }
} }
TEST_CASE_METHOD(BusFixture, "Halfword", TAG) {
CHECK(bus->read_halfword(0x202FED9) == 0);
bus->write_halfword(0x202FED9, 0x1A4A);
CHECK(bus->read_halfword(0x202FED9) == 0x1A4A);
CHECK(bus->read_word(0x202FED9) == 0x1A4A);
CHECK(bus->read_byte(0x202FED9) == 0x4A);
}
TEST_CASE_METHOD(BusFixture, "Word", TAG) {
CHECK(bus->read_word(0x600EE34) == 0);
bus->write_word(0x600EE34, 0x3ACC491D);
CHECK(bus->read_word(0x600EE34) == 0x3ACC491D);
CHECK(bus->read_halfword(0x600EE34) == 0x491D);
CHECK(bus->read_byte(0x600EE34) == 0x1D);
}
#undef TAG #undef TAG