Initial commit
This commit is contained in:
commit
8df38e9c11
6
Cargo.toml
Normal file
6
Cargo.toml
Normal 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
BIN
doc/GBCPUman.pdf
Normal file
Binary file not shown.
24
doc/memory_map
Normal file
24
doc/memory_map
Normal 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
31
doc/notes.py
Normal 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
295
src/cpu.rs
Normal 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
71
src/instruction.rs
Normal 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
92
src/interconnect.rs
Normal 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
32
src/main.rs
Normal 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()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user