commit 8df38e9c11aa74b61bc2224da7f53889c8b6dcca Author: Kevin Hamacher Date: Wed May 25 22:51:40 2016 +0200 Initial commit diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8e6e8ab --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "gbc" +version = "0.1.0" +authors = ["Kevin Hamacher "] + +[dependencies] diff --git a/doc/GBCPUman.pdf b/doc/GBCPUman.pdf new file mode 100644 index 0000000..7cf38e1 Binary files /dev/null and b/doc/GBCPUman.pdf differ diff --git a/doc/memory_map b/doc/memory_map new file mode 100644 index 0000000..21b02d7 --- /dev/null +++ b/doc/memory_map @@ -0,0 +1,24 @@ +0000 7FFF Cartridge +8000 9FFF Video RAM +A000 BFFF External RAM (Cartridge) +C000 DFFF Work RAM +E000 FCFF Copy of the work RAM +FE00 FE9F OAM (Sprite Attribute Table) +FEA0 FEFF Unused +FF00 FF7F Hardware IO +FE80 FFFE High RAM +FFFF FFFF Interrupt switch + +Hardware IO: +F00 Buttons -> 00000000 / state inverted + XX01v^<> + 10SSBA (start select) + +Flags: +ZSHC???? + +Zero +Substraction +Half Carry +Carry + diff --git a/doc/notes.py b/doc/notes.py new file mode 100644 index 0000000..ec28a42 --- /dev/null +++ b/doc/notes.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +""" +Just some doc of GBC opcodes +""" + +OP_CODES = { + '8-bit loads': [0x06, 0x0E, 0x16, 0x1E, 0x26, 0x2E, 0x36], + 'reg-to-reg loads': list(range(0x40, 0x80)), +} + +# for loads: +LD_REGS = ['B', 'C', 'D', 'E', 'H', 'L', '(HL)', 'A'] +def load_source(op): + return LD_REGS[op & 0x7] + +def load_dest(op): + return LD_REGS[(op >> 3) & 0x7] + +def main(): + for cat, ops in OP_CODES.items(): + print("Category: {}".format(cat)) + for op in ops: + print("{} - {} - LD {}, {}".format( + hex(op), + bin(op)[2:].zfill(8), + load_dest(op), + load_source(op) + )) + +if __name__ == '__main__': + main() diff --git a/src/cpu.rs b/src/cpu.rs new file mode 100644 index 0000000..d330e98 --- /dev/null +++ b/src/cpu.rs @@ -0,0 +1,295 @@ +use super::interconnect; + +const REG_B: usize = 0; +const REG_C: usize = 1; +const REG_D: usize = 2; +const REG_E: usize = 3; +const REG_H: usize = 4; +const REG_L: usize = 5; +const REG_A: usize = 6; + +const REG_NAMES: [&'static str; 8] = ["B", "C", "D", "E", "H", "L", "(HL)", "A"]; + +const FLAG_Z: u8 = 1 << 7; +const FLAG_N: u8 = 1 << 6; +const FLAG_H: u8 = 1 << 5; +const FLAG_C: u8 = 1 << 4; + + +pub struct CPU { + // Registers: B, C, D, E, H, L, A + regs: [u8; 7], + flags: u8, + ip: u16, + sp: u16, + + interconnect: interconnect::Interconnect, +} + +fn to_u16(bytes: Box<[u8]>) -> u16 { + (bytes[1] as u16) << 8 | (bytes[0] as u16) +} + +impl CPU { + pub fn new(interconnect: interconnect::Interconnect) -> CPU { + CPU { + flags: 0, + regs: [0, 0, 0, 0, 0, 0, 0], + ip: 0, + sp: 0xFFFE, + interconnect: interconnect + } + } + + fn read_byte(&self, addr: u16) -> u8 { + self.interconnect.read_byte(addr) + } + + fn load_args(&mut self, num_args: u8) -> Box<[u8]> { + let mut args = Vec::new(); + for i in 0..num_args { + args.push(self.read_byte(self.ip + i as u16)); + } + self.ip += num_args as u16; + args.into_boxed_slice() + } + + fn set_8bit_reg(&mut self, reg_id: usize, value: u8) { + // Make sure that we skip the (HL) part. + if reg_id == 7 { + self.regs[REG_A] = value; + } else if reg_id == 6 { + panic!("(HL) not supported yet."); + } else { + self.regs[reg_id] = value; + } + } + + fn get_8bit_reg(&self, reg_id: usize) -> u8 { + // Make sure that we skip the (HL) part. + if reg_id == 7 { + self.regs[REG_A] + } else if reg_id == 6 { + panic!("(HL) not supported yet."); + } else { + self.regs[reg_id] + } + } + + fn run_prefix_instruction(&mut self) { + let instruction = self.read_byte(self.ip); + self.ip += 1; + + match instruction { + 0x10 ... 0x17 => { + let reg_id = (instruction - 0x10) as usize; + let args = self.load_args(1); + let mut val = self.get_8bit_reg(reg_id); + println!("RL {}, {}", REG_NAMES[reg_id], args[0]); + self.set_8bit_reg(reg_id, val.rotate_left(args[0] as u32)); + } + 0x70 ... 0x77 => { + // Test 6th bit + let reg_id = (instruction - 0x70) as usize; + let reg_content = self.get_8bit_reg(reg_id); + println!("BIT 6, {}", REG_NAMES[reg_id]); + if reg_content & (1 << 6) == 0 { + self.flags |= FLAG_Z; + } else { + self.flags &= !FLAG_Z; + } + } + 0x78 ... 0x7F => { + // Test 7th bit + let reg_id = (instruction - 0x78) as usize; + let reg_content = self.get_8bit_reg(reg_id); + println!("BIT 7, {}", REG_NAMES[reg_id]); + if reg_content & (1 << 7) == 0 { + self.flags |= FLAG_Z; + } else { + self.flags &= !FLAG_Z; + } + } + _ => { + println!("Unsupported prefix instruction: {:x}", instruction); + } + } + } + + fn get_pair_value(&self, a: usize, b: usize) -> u16 { + (self.regs[a] as u16) | (self.regs[b] as u16) << 8 + } + + fn set_pair_value(&mut self, a: usize, b: usize, value: u16) { + self.regs[a] = value as u8; + self.regs[b] = (value >> 8) as u8; + } + + pub fn run_instruction(&mut self) { + let instruction = self.read_byte(self.ip); + print!("{:#04x}: {:#04x}: ", &self.ip, &instruction); + self.ip += 1; + + match instruction { + 0x05 => { + println!("DEC B"); + self.regs[REG_B].wrapping_sub(1); + } + 0x06 => { + let args = self.load_args(1); + println!("LD B, {:02x}", args[0]); + self.regs[REG_B] = args[0]; + } + 0x0C => { + println!("INC C"); + self.regs[REG_C] += 1; + } + 0x0E => { + let args = self.load_args(1); + println!("LD C, {:02x}", args[0]); + self.regs[REG_C] = args[0]; + } + 0x11 => { + let args = self.load_args(2); + println!("LD DE, {:02x}{:02x}", args[1], args[0]); + self.regs[REG_D] = args[0]; + self.regs[REG_E] = args[1]; + }, + 0x1A => { + println!("LD A, (DE)"); + self.regs[REG_A] = self.interconnect.read_byte(self.get_pair_value(REG_D, REG_E)); + } + 0x20 => { + let args = self.load_args(1); + println!("JR NZ {:02x}", args[0]); + if self.flags & FLAG_Z == 0 { + let offset = args[0] as i8; + if offset < 0 { + self.ip -= (-offset) as u16 + } else { + self.ip += offset as u16; + } + } + } + 0x21 => { + let args = self.load_args(2); + println!("LD HL, {:02x}{:02x}", args[1], args[0]); + self.regs[REG_H] = args[0]; + self.regs[REG_L] = args[1]; + }, + 0x26 => { + let args = self.load_args(1); + println!("LD H, {:02x}", args[0]); + self.regs[REG_H] = args[0]; + }, + 0x31 => { + let args = self.load_args(2); + self.sp = to_u16(args); + println!("LD SP, {:02x}", self.sp); + }, + 0x32 => { + println!("LD (HL-), A"); + let mut addr: u16 = (self.regs[REG_H] as u16) | ((self.regs[REG_L] as u16) << 8); + self.interconnect.write_byte(addr, self.regs[REG_A]); + + addr -= 1; + self.regs[REG_H] = addr as u8; + self.regs[REG_L] = (addr >> 8) as u8; + }, + 0x3E => { + let args = self.load_args(1); + println!("LD A, {:02x}", args[0]); + self.regs[REG_A] = args[0]; + }, + 0x40 ... 0x47 => { + let reg_id = (instruction - 0x40) as usize; + println!("LD B, {}", REG_NAMES[reg_id]); + self.regs[REG_B] = self.get_8bit_reg(reg_id); + }, + 0x48 ... 0x4F => { + let reg_id = (instruction - 0x48) as usize; + println!("LD C, {}", REG_NAMES[reg_id]); + self.regs[REG_C] = self.get_8bit_reg(reg_id); + }, + 0x50 ... 0x57 => { + let reg_id = (instruction - 0x50) as usize; + println!("LD D, {}", REG_NAMES[reg_id]); + self.regs[REG_D] = self.get_8bit_reg(reg_id); + }, + 0x58 ... 0x5F => { + let reg_id = (instruction - 0x58) as usize; + println!("LD E, {}", REG_NAMES[reg_id]); + self.regs[REG_E] = self.get_8bit_reg(reg_id); + }, + 0x60 ... 0x67 => { + let reg_id = (instruction - 0x60) as usize; + println!("LD H, {}", REG_NAMES[reg_id]); + self.regs[REG_H] = self.get_8bit_reg(reg_id); + }, + 0x68 ... 0x6F => { + let reg_id = (instruction - 0x68) as usize; + println!("LD L, {}", REG_NAMES[reg_id]); + self.regs[REG_L] = self.get_8bit_reg(reg_id); + }, + 0x70 ... 0x75 | 0x77 => { + let reg_id = (instruction - 0x70) as usize; + println!("LD (HL), {}", REG_NAMES[reg_id]); + let reg_value: u8 = self.get_8bit_reg(reg_id); + let addr: u16 = (self.regs[REG_L] as u16) << 8 | (self.regs[REG_H] as u16); + + self.interconnect.write_byte(addr, reg_value); + }, + 0x78 ... 0x7F => { + let reg_id = (instruction - 0x78) as usize; + println!("LD A, {}", REG_NAMES[reg_id]); + self.regs[REG_A] = self.get_8bit_reg(reg_id); + }, + 0xA8 ... 0xAF => { + let reg_id = (instruction - 0xA8) as usize; + println!("XOR {}", REG_NAMES[reg_id]); + self.regs[REG_A] ^= self.get_8bit_reg(reg_id); + }, + 0xC1 => { + println!("POP BC"); + let val: u16 = self.interconnect.read_word(self.sp); + self.sp += 2; + self.set_pair_value(REG_B, REG_C, val); + }, + 0xC5 => { + println!("PUSH BC"); + let val: u16 = self.get_pair_value(REG_B, REG_C); + self.interconnect.write_word(self.sp, val); + self.sp -= 2; + }, + 0xCB => { + // Prefix CB. This is annoying. + self.run_prefix_instruction(); + }, + 0xCD => { + let target = to_u16(self.load_args(2)); + println!("CALL {:04X}", &target); + // Push current IP to the stack + // self.interconnect.write_byte(self.sp, (self.ip & 0xFF) as u8); + // self.interconnect.write_byte(self.sp - 1, (self.ip >> 8) as u8); + self.interconnect.write_word(self.sp - 1, self.ip); + self.sp -= 2; + + self.ip = target; + } + 0xE0 => { + let args = self.load_args(1); + println!("LDH {:02X}, A", args[0]); + self.interconnect.write_byte(0xFF00 + args[0] as u16, self.regs[REG_A]); + } + 0xE2 => { + println!("LD (C), A"); + self.interconnect.write_byte(0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]) + } + 0xFB => { + // Enable interrupts - TODO + println!("EI"); + } + _ => panic!("Unknown instruction: {:02x}", instruction) + } + } +} diff --git a/src/instruction.rs b/src/instruction.rs new file mode 100644 index 0000000..1fb6d5b --- /dev/null +++ b/src/instruction.rs @@ -0,0 +1,71 @@ +#[derive(Clone, Copy)] +pub struct Instruction(pub u32); + +pub enum Operator { + NOP, + INC, + DEC, + RLCA, + RRCA, + RLA + RRA, + JR_NZ, + JR, + JR_NC, + JR_Z, + CPL, + CCF, + HALT, + LD, + ADD, + ADC, + SUB, + SBC, + AND, + XOR, + OR, + CP, + RET +} + +impl Instruction { + pub fn operand(&self) -> Operator { + match self.0 { + // 0x00 - 0x0F + 0x00: Operator::NOP, + 0x01..0x02: Operator::LD, + 0x03..0x04: Operator::INC, + 0x05: Operator::DEC, + 0x06: Operator::LD, + 0x07: Operator::RLCA, + 0x08: Operator::LD, + 0x09: Operator::ADD, + 0x0A: Operator::LD, + 0x0B: Operator::DEC, + 0x0C: Operator::INC, + 0x0D: Operator::DEC, + 0x0E: Operator::LD, + 0x0F: Operator::RRCA, + // 0x10 - 0x1F + + // 0x20 - 0x2F + + // 0x30 - 0x3F + 0x30: Operator::JR_NC, + 0x31: Operator::LD, + + // Higher instructions seem to be grouped better + 0x40..0x75: Operator::LD, + 0x76: Operator::HALT, + 0x77..0x7F: Operator::LD, + 0x80..0x87: Operator::ADD, + 0x88..0x8F: Operator::ADC, + 0x90..0x97: Operator::SUB, + 0x98..0x9F: Operator::SBC, + 0xA0..0xA7: Operator::AND, + 0xA8..0xAF: Operator::XOR, + 0xB0..0xB7: Operator::OR, + 0xB8..0xBF: Operator::CP, + } + } +} diff --git a/src/interconnect.rs b/src/interconnect.rs new file mode 100644 index 0000000..c54cf3f --- /dev/null +++ b/src/interconnect.rs @@ -0,0 +1,92 @@ +const RAM_SIZE: usize = 0x2000; +const VRAM_SIZE: usize = 0x2000; +const HIRAM_SIZE: usize = (0xFFFE - 0xFE80) + 1; + +pub struct Interconnect { + bios: Box<[u8]>, + rom: Box<[u8]>, + ram: Box<[u8]>, + vram: Box<[u8]>, + hiram: Box<[u8]> +} + +impl Interconnect { + pub fn new(bios: Box<[u8]>, rom: Box<[u8]>) -> Interconnect { + Interconnect { + bios: bios, + rom: rom, + ram: vec![0; RAM_SIZE].into_boxed_slice(), + vram: vec![0; VRAM_SIZE].into_boxed_slice(), + hiram: vec![0; HIRAM_SIZE].into_boxed_slice(), + } + } + + pub fn read_byte(&self, addr: u16) -> u8 { + // TODO: Make this more beautiful. + // TODO: if some flag set, use bios, otherwise only use rom + // For now, just use bios + match addr { + 0x0000 ... 0x7FFF => { + // TODO: Check if bios or cartridge (additional condition: isEnabled) + if addr < 0x100 { + self.bios[addr as usize] + } else { + self.rom[addr as usize] + } + } + 0x8000 ... 0x9FFF => { + self.vram[(addr - 0x8000) as usize] + }, + 0xC000 ... 0xDFFF => { + self.ram[(addr - 0xC000) as usize] + }, + 0xFE80 ... 0xFFFE => { + self.hiram[(addr - 0xFE80) as usize] + } + _ => { + panic!("Read from {:04X} not supported.", addr); + } + } + } + + pub fn write_byte(&mut self, addr: u16, val: u8) { + // TODO: Make this more beautful + // TODO: Write byte + /* + 0000 7FFF Cartridge + 8000 9FFF Video RAM + A000 BFFF External RAM (Cartridge) + C000 DFFF Work RAM + E000 FCFF Copy of the work RAM + FE00 FE9F OAM (Sprite Attribute Table) + FEA0 FEFF Unused + FF00 FF7F Hardware IO + FE80 FFFE High RAM + FFFF FFFF Interrupt switch + */ + + match addr { + 0x8000 ... 0x9FFF => { + self.vram[(addr - 0x8000) as usize] = val; + }, + 0xC000 ... 0xDFFF => { + self.ram[(addr - 0xC000) as usize] = val; + }, + 0xFE80 ... 0xFFFE => { + self.hiram[(addr - 0xFE80) as usize] = val; + } + _ => { + panic!("Write {:02X} to {:04X} not supported.", val, addr); + } + } + } + + pub fn write_word(&mut self, addr: u16, val: u16) { + self.write_byte(addr, val as u8); + self.write_byte(addr + 1, (val >> 8) as u8); + } + + pub fn read_word(&self, addr: u16) -> u16 { + self.read_byte(addr) as u16 | (self.read_byte(addr + 1) as u16) << 8 + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a36d051 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,32 @@ +// let's try to write our own, awesome emulator. +// gameboy (color?) + +use std::path::Path; +use std::io::Read; +use std::fs; +use std::env; + +mod cpu; +mod interconnect; + +fn main() { + let bios_path = env::args().nth(1).unwrap(); + let rom_path = env::args().nth(2).unwrap(); + let bios = read_rom(&bios_path); + let rom = read_rom(&rom_path); + + // Now we need to execute commands + let mut interconnect = interconnect::Interconnect::new(bios, rom); + let mut CPU = cpu::CPU::new(interconnect); + + loop { + CPU.run_instruction(); + } +} + +fn read_rom>(rom_path: P) -> Box<[u8]> { + let mut file = fs::File::open(rom_path).unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).expect("Reading file failed"); + buf.into_boxed_slice() +}