Initial commit

This commit is contained in:
Kevin Hamacher 2016-05-25 22:51:40 +02:00
commit 8df38e9c11
8 changed files with 551 additions and 0 deletions

6
Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "gbc"
version = "0.1.0"
authors = ["Kevin Hamacher <kevin.hamacher@rub.de>"]
[dependencies]

BIN
doc/GBCPUman.pdf Normal file

Binary file not shown.

24
doc/memory_map Normal file
View File

@ -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

31
doc/notes.py Normal file
View File

@ -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()

295
src/cpu.rs Normal file
View File

@ -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)
}
}
}

71
src/instruction.rs Normal file
View File

@ -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,
}
}
}

92
src/interconnect.rs Normal file
View File

@ -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
}
}

32
src/main.rs Normal file
View File

@ -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<P: AsRef<Path>>(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()
}