feat: implement fetch, decode, execute loop
This commit is contained in:
parent
e540c86c7e
commit
4d2e0e33f2
|
@ -1,3 +1,4 @@
|
|||
/target
|
||||
/.idea
|
||||
/.vscode
|
||||
/bin
|
24
src/bus.rs
24
src/bus.rs
|
@ -1,20 +1,34 @@
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
const ROM: &[u8; 256] = include_bytes!("../bin/DMG_ROM.bin");
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Bus {}
|
||||
|
||||
impl Bus {
|
||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||
unimplemented!()
|
||||
match addr {
|
||||
0x0000..=0x00FF => {
|
||||
// Restart and Interrupt Vectors
|
||||
ROM[addr as usize]
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_byte(&mut self, addr: u16, byte: u8) {
|
||||
pub fn write_byte(&mut self, _addr: u16, _byte: u8) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn read_word(&self, addr: u16) -> u16 {
|
||||
unimplemented!()
|
||||
match addr {
|
||||
0x0000..=0x00FF => {
|
||||
// Restart and Interrupt Vectors
|
||||
(ROM[(addr + 1) as usize] as u16) << 8 | ROM[addr as usize] as u16
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_word(&mut self, addr: u16, word: u16) {
|
||||
pub fn write_word(&mut self, _addr: u16, _word: u16) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
53
src/cpu.rs
53
src/cpu.rs
|
@ -1,7 +1,7 @@
|
|||
use super::bus::Bus;
|
||||
use super::instruction::Instruction;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Cpu {
|
||||
bus: Bus,
|
||||
reg: Registers,
|
||||
|
@ -11,6 +11,10 @@ pub struct Cpu {
|
|||
}
|
||||
|
||||
impl Cpu {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn ime(&self) -> bool {
|
||||
self.ime
|
||||
}
|
||||
|
@ -18,34 +22,47 @@ impl Cpu {
|
|||
pub fn set_ime(&mut self, enabled: bool) {
|
||||
self.ime = enabled;
|
||||
}
|
||||
|
||||
pub fn inc_pc(&mut self) {
|
||||
self.reg.pc += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
fn fetch(&self) -> u8 {
|
||||
self.bus.read_byte(self.reg.pc)
|
||||
// TODO: Figure out where to increment the program counter.
|
||||
pub fn fetch(&mut self) -> u8 {
|
||||
let opcode = self.bus.read_byte(self.reg.pc);
|
||||
self.inc_pc();
|
||||
|
||||
opcode
|
||||
}
|
||||
|
||||
fn decode(&self, opcode: u8) -> Instruction {
|
||||
pub fn decode(&mut self, opcode: u8) -> Instruction {
|
||||
Instruction::from_byte(self, opcode)
|
||||
}
|
||||
|
||||
fn execute(&mut self, instruction: Instruction) {
|
||||
pub fn execute(&mut self, instruction: Instruction) {
|
||||
Instruction::execute(self, instruction);
|
||||
}
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||
self.bus.read_byte(addr)
|
||||
pub fn read_byte(&mut self, addr: u16) -> u8 {
|
||||
let byte = self.bus.read_byte(addr);
|
||||
self.inc_pc();
|
||||
|
||||
byte
|
||||
}
|
||||
|
||||
pub fn write_byte(&mut self, addr: u16, byte: u8) {
|
||||
self.bus.write_byte(addr, byte)
|
||||
}
|
||||
|
||||
pub fn read_word(&self, addr: u16) -> u16 {
|
||||
self.bus.read_word(addr)
|
||||
pub fn read_word(&mut self, addr: u16) -> u16 {
|
||||
let word = self.bus.read_word(addr);
|
||||
self.inc_pc();
|
||||
self.inc_pc();
|
||||
|
||||
word
|
||||
}
|
||||
|
||||
pub fn write_word(&mut self, addr: u16, word: u16) {
|
||||
|
@ -56,8 +73,14 @@ impl Cpu {
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
enum State {
|
||||
Execute,
|
||||
Halt,
|
||||
Stop,
|
||||
// Halt,
|
||||
// Stop,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self::Execute
|
||||
}
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
|
@ -70,7 +93,7 @@ impl Cpu {
|
|||
Register::E => self.reg.e = value,
|
||||
Register::H => self.reg.h = value,
|
||||
Register::L => self.reg.l = value,
|
||||
Register::Flag => self.reg.a = value.into(),
|
||||
Register::Flag => self.reg.a = value,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +170,7 @@ pub enum RegisterPair {
|
|||
PC,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
struct Registers {
|
||||
a: u8,
|
||||
b: u8,
|
||||
|
@ -160,7 +183,7 @@ struct Registers {
|
|||
pc: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Flags {
|
||||
pub z: bool, // Zero Flag
|
||||
pub n: bool, // Negative Flag
|
||||
|
|
|
@ -106,19 +106,6 @@ pub enum InstrRegister {
|
|||
IndirectHL, // (HL)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum InstrRegisterWithC {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
H,
|
||||
L,
|
||||
IndirectHL, // (HL)
|
||||
IndirectC, // (0xFF00 + C)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum JumpCondition {
|
||||
NotZero,
|
||||
|
@ -150,9 +137,7 @@ impl Instruction {
|
|||
RegisterPair::BC
|
||||
| RegisterPair::DE
|
||||
| RegisterPair::HL
|
||||
| RegisterPair::SP => {
|
||||
cpu.set_register_pair(RegisterPair::try_from(pair).unwrap(), nn)
|
||||
}
|
||||
| RegisterPair::SP => cpu.set_register_pair(pair, nn),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Cycles(12)
|
||||
|
@ -190,19 +175,22 @@ impl Instruction {
|
|||
// LD A, (BC) | Put value at address BC into A
|
||||
// LD A, (DE) | Put value at address DE into A
|
||||
let addr = cpu.register_pair(RegisterPair::try_from(pair).unwrap());
|
||||
cpu.set_register(Register::A, cpu.read_byte(addr));
|
||||
let byte = cpu.read_byte(addr);
|
||||
cpu.set_register(Register::A, byte);
|
||||
}
|
||||
InstrRegisterPair::IncrementHL => {
|
||||
// LD A, (HL+) | Put value at address HL into A, then increment HL
|
||||
let addr = cpu.register_pair(RegisterPair::HL);
|
||||
cpu.set_register(Register::A, cpu.read_byte(addr));
|
||||
let byte = cpu.read_byte(addr);
|
||||
cpu.set_register(Register::A, byte);
|
||||
|
||||
cpu.set_register_pair(RegisterPair::HL, addr + 1);
|
||||
}
|
||||
InstrRegisterPair::DecrementHL => {
|
||||
// LD A, (HL-) | Put value at address HL into A, then increment HL
|
||||
let addr = cpu.register_pair(RegisterPair::HL);
|
||||
cpu.set_register(Register::A, cpu.read_byte(addr));
|
||||
let byte = cpu.read_byte(addr);
|
||||
cpu.set_register(Register::A, byte);
|
||||
|
||||
cpu.set_register_pair(RegisterPair::HL, addr - 1);
|
||||
}
|
||||
|
@ -238,7 +226,8 @@ impl Instruction {
|
|||
}
|
||||
(LDTarget::Register(InstrRegister::A), LDTarget::IndirectC) => {
|
||||
let addr = 0xFF00 + cpu.register(Register::C) as u16;
|
||||
cpu.set_register(Register::A, cpu.read_byte(addr));
|
||||
let byte = cpu.read_byte(addr);
|
||||
cpu.set_register(Register::A, byte);
|
||||
Cycles(8)
|
||||
}
|
||||
(LDTarget::Register(lhs), LDTarget::Register(rhs)) => {
|
||||
|
@ -256,7 +245,8 @@ impl Instruction {
|
|||
}
|
||||
(LDTarget::Register(InstrRegister::A), LDTarget::ByteAtAddressWithOffset(n)) => {
|
||||
// LD A, (0xFF00 + n) | Store value at address (0xFF00 + n) in register A
|
||||
cpu.set_register(Register::A, cpu.read_byte(0xFF00 + (n as u16)));
|
||||
let byte = cpu.read_byte(0xFF00 + (n as u16));
|
||||
cpu.set_register(Register::A, byte);
|
||||
Cycles(12)
|
||||
}
|
||||
(
|
||||
|
@ -272,7 +262,8 @@ impl Instruction {
|
|||
Cycles(16)
|
||||
}
|
||||
(LDTarget::Register(InstrRegister::A), LDTarget::ByteAtAddress(nn)) => {
|
||||
cpu.set_register(Register::A, cpu.read_byte(nn));
|
||||
let byte = cpu.read_byte(nn);
|
||||
cpu.set_register(Register::A, byte);
|
||||
Cycles(16)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -332,7 +323,7 @@ impl Instruction {
|
|||
| RegisterPair::HL
|
||||
| RegisterPair::SP => {
|
||||
let hl_value = cpu.register_pair(RegisterPair::HL);
|
||||
let value = cpu.register_pair(RegisterPair::try_from(pair).unwrap());
|
||||
let value = cpu.register_pair(pair);
|
||||
let sum = Self::add_u16s(hl_value, value, &mut flags);
|
||||
|
||||
cpu.set_register_pair(RegisterPair::HL, sum);
|
||||
|
@ -413,10 +404,8 @@ impl Instruction {
|
|||
}
|
||||
InstrRegister::IndirectHL => {
|
||||
let addr = cpu.register_pair(RegisterPair::HL);
|
||||
cpu.write_byte(
|
||||
addr,
|
||||
Self::inc_register(cpu.read_byte(addr), &mut flags),
|
||||
);
|
||||
let byte = Self::inc_register(cpu.read_byte(addr), &mut flags);
|
||||
cpu.write_byte(addr, byte);
|
||||
cycles = Cycles(12)
|
||||
}
|
||||
}
|
||||
|
@ -471,7 +460,8 @@ impl Instruction {
|
|||
}
|
||||
InstrRegister::IndirectHL => {
|
||||
let addr = cpu.register_pair(RegisterPair::HL);
|
||||
cpu.write_byte(addr, Self::dec_register(cpu.read_byte(addr), &mut flags));
|
||||
let byte = cpu.read_byte(addr);
|
||||
cpu.write_byte(addr, Self::dec_register(byte, &mut flags));
|
||||
cycles = Cycles(12);
|
||||
}
|
||||
}
|
||||
|
@ -1058,7 +1048,7 @@ impl Instruction {
|
|||
// RST n | Push current address onto the stack, jump to 0x0000 + n
|
||||
let addr = cpu.register_pair(RegisterPair::PC);
|
||||
Self::push(cpu, addr);
|
||||
cpu.set_register_pair(RegisterPair::PC, 0x0000 + (n as u16));
|
||||
cpu.set_register_pair(RegisterPair::PC, n as u16);
|
||||
Cycles(16)
|
||||
}
|
||||
Instruction::RLC(reg) => {
|
||||
|
@ -1589,7 +1579,7 @@ impl Instruction {
|
|||
|
||||
fn u16_half_carry(left: u16, right: u16) -> bool {
|
||||
// Self::u8_half_carry((left >> 8) as u8, (right >> 8) as u8)
|
||||
((left & 0xFFFF) + (right & 0xFFFF)) > 0xFFF // Thanks @Nectar Boy#1003
|
||||
left + right > 0xFFF // Thanks @Nectar Boy#1003
|
||||
}
|
||||
|
||||
fn u8_half_carry(left: u8, right: u8) -> bool {
|
||||
|
@ -1619,7 +1609,7 @@ impl Instruction {
|
|||
}
|
||||
|
||||
impl Instruction {
|
||||
pub fn from_byte(cpu: &Cpu, byte: u8) -> Self {
|
||||
pub fn from_byte(cpu: &mut Cpu, byte: u8) -> Self {
|
||||
if byte == 0xCB {
|
||||
Self::from_prefixed_byte(cpu)
|
||||
} else {
|
||||
|
@ -1627,7 +1617,7 @@ impl Instruction {
|
|||
}
|
||||
}
|
||||
|
||||
fn from_unprefixed_byte(cpu: &Cpu, opcode: u8) -> Self {
|
||||
fn from_unprefixed_byte(cpu: &mut Cpu, opcode: u8) -> Self {
|
||||
// https://gb-archive.github.io/salvage/decoding_gbz80_opcodes/Decoding%20Gamboy%20Z80%20Opcodes.html
|
||||
let x = (opcode >> 6) & 0b00000011;
|
||||
let y = (opcode >> 3) & 0b00000111;
|
||||
|
@ -1635,23 +1625,22 @@ impl Instruction {
|
|||
let p = y >> 1;
|
||||
let q = y & 0b00000001;
|
||||
|
||||
let n = cpu.read_byte(cpu.register_pair(RegisterPair::PC) + 1);
|
||||
let nn = cpu.read_word(cpu.register_pair(RegisterPair::PC) + 1);
|
||||
let pc = cpu.register_pair(RegisterPair::PC);
|
||||
|
||||
match (x, z, q, y, p) {
|
||||
(0, 0, _, 0, _) => Self::NOP, // NOP
|
||||
(0, 0, _, 1, _) => Self::LD(
|
||||
// LD (nn), SP
|
||||
LDTarget::ByteAtAddress(nn),
|
||||
LDTarget::ByteAtAddress(cpu.read_word(pc)),
|
||||
LDTarget::RegisterPair(RegisterPair::SP),
|
||||
),
|
||||
(0, 0, _, 2, _) => Self::STOP, // STOP
|
||||
(0, 0, _, 3, _) => Self::JR(JumpCondition::Always, n as i8), // JR d
|
||||
(0, 0, _, 4..=7, _) => Self::JR(Table::cc(y - 4), n as i8), // JR cc[y - 4], d
|
||||
(0, 0, _, 3, _) => Self::JR(JumpCondition::Always, cpu.read_byte(pc) as i8), // JR d
|
||||
(0, 0, _, 4..=7, _) => Self::JR(Table::cc(y - 4), cpu.read_byte(pc) as i8), // JR cc[y - 4], d
|
||||
(0, 1, 0, _, _) => Self::LD(
|
||||
// LD rp[p], nn
|
||||
LDTarget::RegisterPair(Table::rp(p)),
|
||||
LDTarget::ImmediateWord(nn),
|
||||
LDTarget::ImmediateWord(cpu.read_word(pc)),
|
||||
),
|
||||
(0, 1, 1, _, _) => Self::ADD(
|
||||
// ADD HL, rp[p]
|
||||
|
@ -1717,7 +1706,7 @@ impl Instruction {
|
|||
(0, 6, _, _, _) => Self::LD(
|
||||
// LD r[y], n
|
||||
LDTarget::Register(Table::r(y)),
|
||||
LDTarget::ImmediateByte(n),
|
||||
LDTarget::ImmediateByte(cpu.read_byte(pc)),
|
||||
),
|
||||
(0, 7, _, 0, _) => Self::RLCA,
|
||||
(0, 7, _, 1, _) => Self::RRCA,
|
||||
|
@ -1737,22 +1726,22 @@ impl Instruction {
|
|||
(3, 0, _, 0..=3, _) => Self::RET(Table::cc(y)), // RET cc[y]
|
||||
(3, 0, _, 4, _) => Self::LD(
|
||||
// LD (0xFF00 + n), A
|
||||
LDTarget::ByteAtAddressWithOffset(n),
|
||||
LDTarget::ByteAtAddressWithOffset(cpu.read_byte(pc)),
|
||||
LDTarget::Register(InstrRegister::A),
|
||||
),
|
||||
(3, 0, _, 5, _) => Self::ADD(
|
||||
// ADD SP, d
|
||||
MATHTarget::RegisterPair(RegisterPair::SP),
|
||||
MATHTarget::ImmediateByte(n),
|
||||
MATHTarget::ImmediateByte(cpu.read_byte(pc)),
|
||||
),
|
||||
(3, 0, _, 6, _) => Self::LD(
|
||||
// LD A, (0xFF00 + n)
|
||||
LDTarget::Register(InstrRegister::A),
|
||||
LDTarget::ByteAtAddressWithOffset(n),
|
||||
LDTarget::ByteAtAddressWithOffset(cpu.read_byte(pc)),
|
||||
),
|
||||
(3, 0, _, 7, _) => Self::LDHL(n as i8), // LD HL, SP + d
|
||||
(3, 1, 0, _, _) => Self::POP(Table::rp2(p)), // POP rp2[p]
|
||||
(3, 1, 1, _, 0) => Self::RET(JumpCondition::Always), // RET
|
||||
(3, 0, _, 7, _) => Self::LDHL(cpu.read_byte(pc) as i8), // LD HL, SP + d
|
||||
(3, 1, 0, _, _) => Self::POP(Table::rp2(p)), // POP rp2[p]
|
||||
(3, 1, 1, _, 0) => Self::RET(JumpCondition::Always), // RET
|
||||
(3, 1, 1, _, 1) => Self::RETI,
|
||||
(3, 1, 1, _, 2) => Self::JP(
|
||||
// JP HL
|
||||
|
@ -1767,7 +1756,7 @@ impl Instruction {
|
|||
(3, 2, _, 0..=3, _) => Self::JP(
|
||||
// JP cc[y], nn
|
||||
Table::cc(y),
|
||||
JPTarget::ImmediateWord(nn),
|
||||
JPTarget::ImmediateWord(cpu.read_word(pc)),
|
||||
),
|
||||
(3, 2, _, 4, _) => Self::LD(
|
||||
// LD (0xFF00 + C) ,A
|
||||
|
@ -1776,7 +1765,7 @@ impl Instruction {
|
|||
),
|
||||
(3, 2, _, 5, _) => Self::LD(
|
||||
// LD (nn), A
|
||||
LDTarget::ByteAtAddress(nn),
|
||||
LDTarget::ByteAtAddress(cpu.read_word(pc)),
|
||||
LDTarget::Register(InstrRegister::A),
|
||||
),
|
||||
(3, 2, _, 6, _) => Self::LD(
|
||||
|
@ -1787,12 +1776,12 @@ impl Instruction {
|
|||
(3, 2, _, 7, _) => Self::LD(
|
||||
// LD A, (nn)
|
||||
LDTarget::Register(InstrRegister::A),
|
||||
LDTarget::ByteAtAddress(nn),
|
||||
LDTarget::ByteAtAddress(cpu.read_word(pc)),
|
||||
),
|
||||
(3, 3, _, 0, _) => Self::JP(
|
||||
// JP nn
|
||||
JumpCondition::Always,
|
||||
JPTarget::ImmediateWord(nn),
|
||||
JPTarget::ImmediateWord(cpu.read_word(pc)),
|
||||
),
|
||||
(3, 3, _, 1, _) => unreachable!("This is the 0xCB Prefix"),
|
||||
// (3, 3, _, 2, _) => unreachable!(), ("removed" in documentation)
|
||||
|
@ -1801,12 +1790,12 @@ impl Instruction {
|
|||
// (3, 3, _, 5, _) => unreachable!(), ("removed" in documentation)
|
||||
(3, 3, _, 6, _) => Self::DI,
|
||||
(3, 3, _, 7, _) => Self::EI,
|
||||
(3, 4, _, 0..=3, _) => Self::CALL(Table::cc(y), nn), // CALL cc[y], nn
|
||||
(3, 4, _, 0..=3, _) => Self::CALL(Table::cc(y), cpu.read_word(pc)), // CALL cc[y], nn
|
||||
// (3, 4, _, 4..=7, _) => unreachable!(), ("removed" in documentation)
|
||||
(3, 5, 0, _, _) => Self::PUSH(Table::rp2(p)), // PUSH rp2[p]
|
||||
(3, 5, 1, _, 0) => Self::CALL(JumpCondition::Always, nn), // CALL nn
|
||||
(3, 5, 1, _, 0) => Self::CALL(JumpCondition::Always, cpu.read_word(pc)), // CALL nn
|
||||
// (3, 5, 1, _, 1..=3) => unreachable!(), ("removed" in documentation)
|
||||
(3, 6, _, _, _) => Table::x3_alu(y, n),
|
||||
(3, 6, _, _, _) => Table::x3_alu(y, cpu.read_byte(pc)),
|
||||
(3, 7, _, _, _) => Self::RST(y * 8), // RST n
|
||||
_ => panic!(
|
||||
"Unknown Opcode: {:#x?}\n x: {}, z: {}, q: {}, y: {}, p: {}",
|
||||
|
@ -1815,8 +1804,9 @@ impl Instruction {
|
|||
}
|
||||
}
|
||||
|
||||
fn from_prefixed_byte(cpu: &Cpu) -> Self {
|
||||
let opcode = cpu.read_byte(cpu.register_pair(RegisterPair::PC) + 1);
|
||||
fn from_prefixed_byte(cpu: &mut Cpu) -> Self {
|
||||
let pc = cpu.register_pair(RegisterPair::PC);
|
||||
let opcode = cpu.read_byte(pc); // FIXME: Should the PC be incremented here?
|
||||
|
||||
// https://gb-archive.github.io/salvage/decoding_gbz80_opcodes/Decoding%20Gamboy%20Z80%20Opcodes.html
|
||||
let x = (opcode >> 6) & 0b00000011;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
mod cpu;
|
||||
mod bus;
|
||||
pub mod cpu;
|
||||
mod instruction;
|
17
src/main.rs
17
src/main.rs
|
@ -1,3 +1,18 @@
|
|||
use gb::cpu::Cpu as LR35902;
|
||||
|
||||
fn main() {
|
||||
println!("Hello World!");
|
||||
let mut game_boy = LR35902::new();
|
||||
|
||||
loop {
|
||||
let pc = game_boy.register_pair(gb::cpu::RegisterPair::PC);
|
||||
let opcode = game_boy.fetch();
|
||||
let instruction = game_boy.decode(opcode);
|
||||
|
||||
println!(
|
||||
"Addr: {:#06x} | Opcode: {:#x} | Instr: {:x?}",
|
||||
pc, opcode, instruction
|
||||
);
|
||||
|
||||
game_boy.execute(instruction);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue