Add cartridge mod; Add sprite support
This commit is contained in:
parent
f10744b2a6
commit
e3fce0bcc5
151
src/cartridge.rs
Normal file
151
src/cartridge.rs
Normal file
@ -0,0 +1,151 @@
|
||||
#[derive(Debug,PartialEq)]
|
||||
enum MemoryBankControllerType {
|
||||
None,
|
||||
MBC1,
|
||||
MBC2,
|
||||
MBC3,
|
||||
HuC1,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum RamSize {
|
||||
None,
|
||||
Ram2KB,
|
||||
Ram8KB,
|
||||
Ram32KB,
|
||||
}
|
||||
|
||||
pub struct Cartridge {
|
||||
rom: Box<[u8]>,
|
||||
ram: Box<[u8]>,
|
||||
bank_no: u8,
|
||||
mbc_type: MemoryBankControllerType,
|
||||
ram_rtc_enabled: bool,
|
||||
ram_bank_no: u8,
|
||||
|
||||
ram_size: RamSize,
|
||||
rom_banks: u16,
|
||||
}
|
||||
|
||||
impl Cartridge {
|
||||
pub fn new(rom: Box<[u8]>) -> Cartridge {
|
||||
let mbc: MemoryBankControllerType = match rom[0x0147] {
|
||||
0x00 | 0x08 ... 0x09 => MemoryBankControllerType::None,
|
||||
0x01 ... 0x03 => MemoryBankControllerType::MBC1,
|
||||
0x05 ... 0x06 => MemoryBankControllerType::MBC2,
|
||||
0x0F ... 0x13 => MemoryBankControllerType::MBC3,
|
||||
0xFF => MemoryBankControllerType::HuC1,
|
||||
_ => panic!("Unsupported MBC type: {:02X}", rom[0x0147]),
|
||||
};
|
||||
|
||||
println!("MBC Type: {:?}", &mbc);
|
||||
|
||||
let rom_banks: u16 = match rom[0x0148] {
|
||||
0x00 => 0,
|
||||
0x01 ... 0x07 => 2u16.pow(rom[0x0148] as u32 + 1),
|
||||
0x52 => 72,
|
||||
0x53 => 80,
|
||||
0x54 => 96,
|
||||
_ => panic!("Unknown rom size: {:?}", rom[0x148])
|
||||
};
|
||||
|
||||
let ram_size: RamSize = match rom[0x0149] {
|
||||
0x00 => RamSize::None,
|
||||
0x01 => RamSize::Ram2KB,
|
||||
0x02 => RamSize::Ram8KB,
|
||||
0x03 => RamSize::Ram32KB,
|
||||
_ => panic!("Unknown ram size: {:?}", rom[0x149])
|
||||
};
|
||||
|
||||
println!("Rom size: {} banks", rom_banks);
|
||||
println!("Ram size: {:?}", ram_size);
|
||||
|
||||
let ram = match ram_size {
|
||||
RamSize::None => vec![0u8; 0].into_boxed_slice(),
|
||||
RamSize::Ram2KB => vec![0u8; 2048].into_boxed_slice(),
|
||||
RamSize::Ram8KB => vec![0u8; 4 * 2048].into_boxed_slice(),
|
||||
RamSize::Ram32KB => vec![0u8; 16 * 2048].into_boxed_slice(),
|
||||
};
|
||||
|
||||
Cartridge {
|
||||
rom: rom,
|
||||
mbc_type: mbc,
|
||||
bank_no: 1,
|
||||
|
||||
ram_rtc_enabled: false,
|
||||
ram_bank_no: 0,
|
||||
ram_size: ram_size,
|
||||
rom_banks: rom_banks,
|
||||
|
||||
ram: ram
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||
match addr {
|
||||
0x0000 ... 0x3FFF => self.rom[addr as usize],
|
||||
0x4000 ... 0x7FFF => {
|
||||
let abs_addr: usize = addr as usize + self.bank_no as usize * 0x4000;
|
||||
let val: u8 = self.rom[abs_addr];
|
||||
// println!("Access {:04X}{:04X} = {:02X}", self.bank_no, addr, val);
|
||||
val
|
||||
},
|
||||
0xA000 ... 0xBFFF => {
|
||||
// TODO: Safty checks? Or let rust handle this?
|
||||
let addr = addr - 0xA000;
|
||||
if self.ram_bank_no < 4 {
|
||||
self.ram[self.ram_bank_no as usize * 0x2000 + addr as usize]
|
||||
} else {
|
||||
println!("Ignoring RTC read");
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("Cartride: Unable to read from {:04X}", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_byte_mbc3(&mut self, addr: u16, val: u8) {
|
||||
match addr {
|
||||
0x0000 ... 0x1FFF => {
|
||||
match val {
|
||||
0x0A => self.ram_rtc_enabled = true,
|
||||
0x00 => self.ram_rtc_enabled = false,
|
||||
_ => panic!("Unknown MBC value {:02X} for {:04X}", val, addr)
|
||||
}
|
||||
},
|
||||
0x2000 ... 0x3FFF => self.bank_no = val & 0x7F,
|
||||
0x4000 ... 0x5FFF => {
|
||||
// RAM bank select
|
||||
match val {
|
||||
0x00 ... 0x03 => self.ram_bank_no = val,
|
||||
0x08 ... 0x0C => self.ram_bank_no = val, // RTC clock values, TODO
|
||||
_ => panic!("Unknown MBC3 RAM BANK NO: {:02X}", val)
|
||||
}
|
||||
|
||||
},
|
||||
0x6000 ... 0x7FFF => {
|
||||
// Latch clock data, ignore.
|
||||
},
|
||||
0xA000 ... 0xBFFF => {
|
||||
// TODO: Safty checks? Or let rust handle this?
|
||||
let addr = addr - 0xA000;
|
||||
if self.ram_bank_no < 4 {
|
||||
self.ram[self.ram_bank_no as usize * 0x2000 + addr as usize] = val;
|
||||
} else {
|
||||
println!("Ignoring RTC write");
|
||||
}
|
||||
}
|
||||
_ => panic!("MBC3: Writing {:02X} to {:04X} not supported", val, addr),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
||||
match self.mbc_type {
|
||||
MemoryBankControllerType::None => panic!("Cartridge: No MBC found, can not write to ROM"),
|
||||
MemoryBankControllerType::MBC3 => self.write_byte_mbc3(addr, val),
|
||||
_ => panic!("MBC not supported.")
|
||||
}
|
||||
}
|
||||
}
|
||||
259
src/cpu.rs
259
src/cpu.rs
@ -16,7 +16,8 @@ const REG_N_H: usize = 4;
|
||||
const REG_N_L: usize = 5;
|
||||
const REG_N_HL: usize = 6;
|
||||
const REG_N_A: usize = 7;
|
||||
const REG_NAMES: [&'static str; 8] = ["B", "C", "D", "E", "H", "L", "(HL)", "A"];
|
||||
const REG_N_F: usize = 8;
|
||||
const REG_NAMES: [&'static str; 9] = ["B", "C", "D", "E", "H", "L", "(HL)", "A", "F"];
|
||||
|
||||
const FLAG_Z: u8 = 1 << 7;
|
||||
const FLAG_N: u8 = 1 << 6;
|
||||
@ -33,7 +34,7 @@ pub struct CPU {
|
||||
|
||||
interconnect: interconnect::Interconnect,
|
||||
|
||||
interrupts_enabled: bool,
|
||||
ime: bool,
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
@ -49,7 +50,7 @@ impl CPU {
|
||||
ip: 0,
|
||||
sp: 0xFFFE,
|
||||
interconnect: interconnect,
|
||||
interrupts_enabled: false, // Is this correct?
|
||||
ime: false, // Is this correct?
|
||||
debug: false,
|
||||
}
|
||||
}
|
||||
@ -69,9 +70,11 @@ impl CPU {
|
||||
|
||||
fn set_8bit_reg(&mut self, reg_id: usize, value: u8) {
|
||||
// Make sure that we skip the (HL) part.
|
||||
if reg_id == 7 {
|
||||
if reg_id == REG_N_A {
|
||||
self.regs[REG_A] = value;
|
||||
} else if reg_id == 6 {
|
||||
} else if reg_id == REG_N_F {
|
||||
self.flags = value;
|
||||
} else if reg_id == REG_N_HL {
|
||||
let addr: u16 = self.get_pair_value(REG_H, REG_L);
|
||||
self.interconnect.write_byte(addr, value);
|
||||
} else {
|
||||
@ -81,9 +84,11 @@ impl CPU {
|
||||
|
||||
fn get_8bit_reg(&self, reg_id: usize) -> u8 {
|
||||
// Make sure that we skip the (HL) part.
|
||||
if reg_id == 7 {
|
||||
if reg_id == REG_N_A {
|
||||
self.regs[REG_A]
|
||||
} else if reg_id == 6 {
|
||||
} else if reg_id == REG_N_F {
|
||||
self.flags
|
||||
} else if reg_id == REG_N_HL {
|
||||
let addr: u16 = self.get_pair_value(REG_H, REG_L);
|
||||
self.interconnect.read_byte(addr)
|
||||
} else {
|
||||
@ -120,6 +125,14 @@ impl CPU {
|
||||
self.clear_flag(FLAG_N);
|
||||
self.clear_flag(FLAG_H);
|
||||
},
|
||||
0x30 ... 0x37 => {
|
||||
let reg_id = (instruction - 0x30) as usize;
|
||||
if self.debug {
|
||||
println!("SWAP {}", REG_NAMES[reg_id]);
|
||||
}
|
||||
let v: u8 = self.get_8bit_reg(reg_id);
|
||||
self.set_8bit_reg(reg_id, v << 4 | v >> 4);
|
||||
}
|
||||
0x40 ... 0x47 => {
|
||||
// Test 0th bit
|
||||
let reg_id = (instruction - 0x40) as usize;
|
||||
@ -225,7 +238,7 @@ impl CPU {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("Unsupported prefix instruction: {:x}", instruction);
|
||||
panic!("Unsupported prefix instruction: {:x}", instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -245,12 +258,26 @@ impl CPU {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, dst: u16) {
|
||||
let ip = self.ip;
|
||||
self.push(ip);
|
||||
self.ip = dst;
|
||||
}
|
||||
|
||||
fn rst(&mut self, val: u8) -> u8 {
|
||||
// Make sure this is correct.
|
||||
if self.debug {
|
||||
println!("RST {:02X}", val);
|
||||
}
|
||||
self.call(val as u16);
|
||||
16
|
||||
}
|
||||
|
||||
fn pop_rr(&mut self, r1: usize, r2: usize) -> u8 {
|
||||
if self.debug {
|
||||
println!("POP {}{}", REG_NAMES[r1], REG_NAMES[r2]);
|
||||
}
|
||||
let val: u16 = self.interconnect.read_word(self.sp);
|
||||
self.sp += 2;
|
||||
let val: u16 = self.pop();
|
||||
self.set_pair_value(r1, r2, val);
|
||||
12
|
||||
}
|
||||
@ -284,10 +311,16 @@ impl CPU {
|
||||
}
|
||||
|
||||
fn push(&mut self, val: u16) {
|
||||
self.interconnect.write_word(self.sp - 2, val);
|
||||
self.interconnect.write_word(self.sp - 1, val);
|
||||
self.sp -= 2;
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> u16 {
|
||||
let v: u16 = self.interconnect.read_word(self.sp + 1);
|
||||
self.sp += 2;
|
||||
v
|
||||
}
|
||||
|
||||
fn ld_r_r(&mut self, reg_dst: usize, reg_src: usize) -> u8 {
|
||||
if self.debug {
|
||||
println!("LD {}, {}", REG_NAMES[reg_dst], REG_NAMES[reg_src])
|
||||
@ -336,6 +369,13 @@ impl CPU {
|
||||
4
|
||||
}
|
||||
|
||||
fn add_rr_rr(&mut self, r1: usize, r2: usize, r3: usize, r4: usize) -> u8 {
|
||||
let val1 = self.get_pair_value(r1, r2);
|
||||
let val2 = self.get_pair_value(r3, r4);
|
||||
self.set_pair_value(r1, r2, val1.wrapping_add(val2));
|
||||
8
|
||||
}
|
||||
|
||||
fn reg_dec(&mut self, reg_id: usize) -> u8 {
|
||||
if self.debug {
|
||||
println!("DEC {}", REG_NAMES[reg_id]);
|
||||
@ -369,7 +409,50 @@ impl CPU {
|
||||
self.flags &= !flag;
|
||||
}
|
||||
|
||||
fn int_(&mut self, val: u8){
|
||||
if self.debug {
|
||||
println!("INT {:02X}", val);
|
||||
}
|
||||
// TODO: Clear interrupt register
|
||||
self.ime = false;
|
||||
self.call(val as u16);
|
||||
}
|
||||
|
||||
fn ret(&mut self) {
|
||||
let new_ip: u16 = self.pop();
|
||||
self.ip = new_ip;
|
||||
}
|
||||
|
||||
fn reti(&mut self) -> u8 {
|
||||
self.ret();
|
||||
self.ime = true;
|
||||
16
|
||||
}
|
||||
|
||||
pub fn run_instruction(&mut self) {
|
||||
// self.debug = !self.interconnect.is_boot_rom();
|
||||
// Check for interrupts.
|
||||
// TODO: Make this right
|
||||
if self.ime {
|
||||
// read pending interrupts
|
||||
let pending = self.interconnect.read_byte(0xFF0F);
|
||||
let enabled = self.interconnect.read_byte(0xFFFF);
|
||||
let e_pending = pending & enabled;
|
||||
|
||||
if e_pending & interconnect::INTERRUPT_DISPLAY_VBLANK > 0 {
|
||||
println!("Handling vblank interrupt");
|
||||
self.interconnect.write_byte(0xFF0F, pending & !interconnect::INTERRUPT_DISPLAY_VBLANK);
|
||||
self.int_(0x40);
|
||||
return;
|
||||
} else if e_pending & interconnect::INTERRUPT_DISPLAY_STAT > 0 {
|
||||
println!("Handling display stat interrupt");
|
||||
self.interconnect.write_byte(0xFF0F, pending & !interconnect::INTERRUPT_DISPLAY_STAT);
|
||||
self.int_(0x48);
|
||||
return;
|
||||
} else if e_pending > 0 {
|
||||
panic!("Unknown pending interrupt: {:02X}", e_pending);
|
||||
}
|
||||
}
|
||||
// We need to double-check the flags
|
||||
let instruction = self.read_byte(self.ip);
|
||||
if self.debug {
|
||||
@ -386,7 +469,8 @@ impl CPU {
|
||||
print!("H={} ", self.flags & FLAG_H != 0);
|
||||
print!("C={} ", self.flags & FLAG_C != 0);
|
||||
}
|
||||
self.ip += 1;
|
||||
|
||||
self.ip = self.ip.wrapping_add(1);
|
||||
|
||||
let cycles: u8 = match instruction {
|
||||
0x00 => {
|
||||
@ -400,6 +484,16 @@ impl CPU {
|
||||
0x04 => self.reg_inc(REG_N_B),
|
||||
0x05 => self.reg_dec(REG_N_B),
|
||||
0x06 => self.ld_r_v(REG_N_B),
|
||||
|
||||
0x08 => {
|
||||
let a: u16 = to_u16(self.load_args(2));
|
||||
if self.debug{
|
||||
println!("LD ({:04X}), sp", a);
|
||||
}
|
||||
self.interconnect.write_word(a, self.sp);
|
||||
20
|
||||
}
|
||||
0x09 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_B, REG_N_C),
|
||||
0x0B => self.dec_rr(REG_N_B, REG_N_C),
|
||||
0x0C => self.reg_inc(REG_N_C),
|
||||
0x0D => self.reg_dec(REG_N_C),
|
||||
@ -445,6 +539,7 @@ impl CPU {
|
||||
}
|
||||
12
|
||||
},
|
||||
0x19 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_D, REG_N_E),
|
||||
0x1A => {
|
||||
if self.debug {
|
||||
println!("LD A, (DE)");
|
||||
@ -505,6 +600,7 @@ impl CPU {
|
||||
8
|
||||
}
|
||||
},
|
||||
0x29 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_H, REG_N_L),
|
||||
0x2A => {
|
||||
if self.debug {
|
||||
println!("LD A, (HL+)");
|
||||
@ -524,6 +620,23 @@ impl CPU {
|
||||
self.regs[REG_A] = !self.regs[REG_A];
|
||||
4
|
||||
},
|
||||
0x30 => {
|
||||
let args = self.load_args(1);
|
||||
if self.debug {
|
||||
println!("JR NC {:02x}", args[0] as i8);
|
||||
}
|
||||
if self.flags & FLAG_C == 0 {
|
||||
let offset = args[0] as i8;
|
||||
if offset < 0 {
|
||||
self.ip -= (-offset) as u16
|
||||
} else {
|
||||
self.ip += offset as u16;
|
||||
}
|
||||
12
|
||||
} else {
|
||||
8
|
||||
}
|
||||
},
|
||||
0x31 => {
|
||||
let args = self.load_args(2);
|
||||
self.sp = to_u16(args);
|
||||
@ -552,6 +665,39 @@ impl CPU {
|
||||
self.interconnect.write_byte(addr, args[0]);
|
||||
12
|
||||
},
|
||||
0x37 => {
|
||||
if self.debug {
|
||||
println!("SCF");
|
||||
}
|
||||
self.set_flag(FLAG_C);
|
||||
4
|
||||
},
|
||||
0x38 => {
|
||||
let args = self.load_args(1);
|
||||
if self.debug {
|
||||
println!("JR C {:02x}", args[0] as i8);
|
||||
}
|
||||
if self.flags & FLAG_C > 0 {
|
||||
let offset = args[0] as i8;
|
||||
if offset < 0 {
|
||||
self.ip -= (-offset) as u16
|
||||
} else {
|
||||
self.ip += offset as u16;
|
||||
}
|
||||
12
|
||||
} else {
|
||||
8
|
||||
}
|
||||
},
|
||||
0x39 => {
|
||||
if self.debug {
|
||||
println!("ADD HL, SP");
|
||||
}
|
||||
let sp = self.sp;
|
||||
let v = self.get_pair_value(REG_N_H, REG_N_L).wrapping_add(sp);
|
||||
self.set_pair_value(REG_N_H, REG_N_L, v);
|
||||
8
|
||||
}
|
||||
0x3C => self.reg_inc(REG_N_A),
|
||||
0x3D => self.reg_dec(REG_N_A),
|
||||
0x3E => self.ld_r_v(REG_N_A),
|
||||
@ -706,6 +852,17 @@ impl CPU {
|
||||
}
|
||||
4
|
||||
},
|
||||
0xC0 => {
|
||||
if self.debug {
|
||||
println!("RET NZ");
|
||||
}
|
||||
if self.flags & FLAG_Z == 0 {
|
||||
self.ret();
|
||||
20
|
||||
} else {
|
||||
8
|
||||
}
|
||||
},
|
||||
0xC1 => self.pop_rr(REG_B, REG_C),
|
||||
0xC2 => {
|
||||
let addr: u16 = to_u16(self.load_args(2));
|
||||
@ -733,10 +890,7 @@ impl CPU {
|
||||
println!("CALL NZ {:04X}", &target);
|
||||
}
|
||||
if self.flags & FLAG_Z == 0 {
|
||||
// Push current IP to the stack
|
||||
let ip = self.ip;
|
||||
self.push(ip);
|
||||
self.ip = target;
|
||||
self.call(target);
|
||||
24
|
||||
} else {
|
||||
12
|
||||
@ -751,13 +905,24 @@ impl CPU {
|
||||
self.regs[REG_A] = self.regs[REG_A].wrapping_add(val);
|
||||
8
|
||||
},
|
||||
0xC7 => self.rst(0x00),
|
||||
0xC8 => {
|
||||
if self.debug {
|
||||
println!("RET Z");
|
||||
}
|
||||
if self.flags & FLAG_Z > 0 {
|
||||
self.ret();
|
||||
20
|
||||
} else {
|
||||
8
|
||||
}
|
||||
},
|
||||
0xC9 => {
|
||||
if self.debug {
|
||||
println!("RET");
|
||||
self.dump_stack();
|
||||
}
|
||||
self.ip = self.interconnect.read_word(self.sp+1);
|
||||
self.sp += 2;
|
||||
let new_ip: u16 = self.pop();
|
||||
self.ip = new_ip;
|
||||
16
|
||||
},
|
||||
0xCB => {
|
||||
@ -771,10 +936,7 @@ impl CPU {
|
||||
println!("CALL Z {:04X}", &target);
|
||||
}
|
||||
if self.flags & FLAG_Z > 0 {
|
||||
// Push current IP to the stack
|
||||
self.interconnect.write_word(self.sp - 1, self.ip);
|
||||
self.sp -= 2;
|
||||
self.ip = target;
|
||||
self.call(target);
|
||||
24
|
||||
} else {
|
||||
12
|
||||
@ -785,12 +947,12 @@ impl CPU {
|
||||
if self.debug {
|
||||
println!("CALL {:04X}", &target);
|
||||
}
|
||||
// Push current IP to the stack
|
||||
self.interconnect.write_word(self.sp - 1, self.ip);
|
||||
self.sp -= 2;
|
||||
self.ip = target;
|
||||
self.call(target);
|
||||
24
|
||||
},
|
||||
0xCF => self.rst(0x08),
|
||||
0xD1 => self.pop_rr(REG_D, REG_E),
|
||||
0xD5 => self.push_rr(REG_D, REG_E),
|
||||
0xD6 => {
|
||||
let val = self.load_args(1)[0];
|
||||
if self.debug {
|
||||
@ -799,6 +961,8 @@ impl CPU {
|
||||
self.regs[REG_A] = self.regs[REG_A].wrapping_sub(val);
|
||||
8
|
||||
},
|
||||
0xD7 => self.rst(0x10),
|
||||
0xDF => self.rst(0x18),
|
||||
0xE0 => {
|
||||
let args = self.load_args(1);
|
||||
if self.debug {
|
||||
@ -816,6 +980,7 @@ impl CPU {
|
||||
self.interconnect.write_byte(0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]);
|
||||
8
|
||||
},
|
||||
0xE5 => self.push_rr(REG_H, REG_L),
|
||||
0xE6 => {
|
||||
let val = self.load_args(1)[0];
|
||||
if self.debug {
|
||||
@ -824,6 +989,15 @@ impl CPU {
|
||||
self.regs[REG_A] &= val;
|
||||
8
|
||||
},
|
||||
0xE7 => self.rst(0x20),
|
||||
0xE9 => {
|
||||
if self.debug {
|
||||
println!("JP (HL)");
|
||||
}
|
||||
let new_ip = (self.ip as i16 + self.get_8bit_reg(REG_N_HL) as i16) as u16;
|
||||
self.ip = new_ip;
|
||||
4
|
||||
},
|
||||
0xEA => {
|
||||
let addr = to_u16(self.load_args(2));
|
||||
if self.debug{
|
||||
@ -832,16 +1006,7 @@ impl CPU {
|
||||
self.interconnect.write_byte(addr, self.regs[REG_A]);
|
||||
16
|
||||
},
|
||||
0xEF => {
|
||||
// Make sure this is correct.
|
||||
if self.debug {
|
||||
println!("RST 0x28");
|
||||
}
|
||||
self.interconnect.write_word(self.sp - 1, self.ip);
|
||||
self.sp -= 2;
|
||||
self.ip = 0x28;
|
||||
16
|
||||
},
|
||||
0xEF => self.rst(0x28),
|
||||
0xF0 => {
|
||||
let args = self.load_args(1);
|
||||
if self.debug {
|
||||
@ -850,6 +1015,7 @@ impl CPU {
|
||||
self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + args[0] as u16);
|
||||
12
|
||||
},
|
||||
0xF1 => self.pop_rr(REG_N_A, REG_N_F),
|
||||
0xF2 => {
|
||||
if self.debug{
|
||||
println!("LD A, (C)");
|
||||
@ -861,9 +1027,10 @@ impl CPU {
|
||||
if self.debug {
|
||||
println!("DI");
|
||||
}
|
||||
self.interrupts_enabled = false;
|
||||
self.ime = false;
|
||||
4
|
||||
},
|
||||
0xF5 => self.push_rr(REG_N_A, REG_N_F),
|
||||
0xF6 => {
|
||||
let val = self.load_args(1)[0];
|
||||
if self.debug {
|
||||
@ -872,15 +1039,24 @@ impl CPU {
|
||||
self.regs[REG_A] |= val;
|
||||
8
|
||||
},
|
||||
0xF7 => self.rst(0x30),
|
||||
0xFB => {
|
||||
// Enable interrupts - TODO
|
||||
if self.debug {
|
||||
println!("EI");
|
||||
}
|
||||
self.interrupts_enabled = true;
|
||||
println!("ENABLING INTERRUPTS - TODO");
|
||||
self.ime = true; // interrupt master enable
|
||||
4
|
||||
},
|
||||
0xFA => {
|
||||
let addr: u16 = to_u16(self.load_args(2));
|
||||
if self.debug {
|
||||
println!("LD A, ({:04X})", addr);
|
||||
}
|
||||
let val: u8 = self.interconnect.read_byte(addr);
|
||||
self.set_8bit_reg(REG_N_A, val);
|
||||
16
|
||||
},
|
||||
0xFE => {
|
||||
let args = self.load_args(1);
|
||||
if self.debug {
|
||||
@ -900,7 +1076,8 @@ impl CPU {
|
||||
|
||||
// TODO H
|
||||
8
|
||||
}
|
||||
},
|
||||
0xFF => self.rst(0x38),
|
||||
_ => panic!("Unknown instruction: {:02x}", instruction)
|
||||
};
|
||||
// self.dump_stack();
|
||||
|
||||
109
src/display.rs
109
src/display.rs
@ -4,6 +4,9 @@ extern crate libc;
|
||||
// Internal ram size
|
||||
const VRAM_SIZE: usize = 0x2000;
|
||||
|
||||
// OAM size
|
||||
const OAM_SIZE: usize = (0xFE9F - 0xFE00) + 1;
|
||||
|
||||
// Timing values
|
||||
const TICKS_END_SCANLINE: u16 = 80;
|
||||
const TICKS_END_READMODE: u16 = 172;
|
||||
@ -50,14 +53,21 @@ pub struct Display {
|
||||
windowx: u8,
|
||||
windowy: u8,
|
||||
curline: u8,
|
||||
lyc: u8,
|
||||
|
||||
vram: Box<[u8]>,
|
||||
oam: Box<[u8]>,
|
||||
|
||||
current_ticks: u16,
|
||||
current_mode: DisplayMode,
|
||||
// TODO
|
||||
|
||||
renderer: sdl2::render::Renderer<'static>,
|
||||
|
||||
vblank_fired: bool,
|
||||
stat_fired: bool,
|
||||
vblank_interrupt: bool,
|
||||
stat_interrupt: bool,
|
||||
}
|
||||
|
||||
impl Display {
|
||||
@ -78,11 +88,18 @@ impl Display {
|
||||
windowx: 0,
|
||||
windowy: 0,
|
||||
curline: 0,
|
||||
lyc: 255,
|
||||
|
||||
current_ticks: 0,
|
||||
current_mode: DisplayMode::default(),
|
||||
vram: vec![0; VRAM_SIZE].into_boxed_slice(),
|
||||
renderer: renderer
|
||||
oam: vec![0; OAM_SIZE].into_boxed_slice(),
|
||||
renderer: renderer,
|
||||
|
||||
vblank_fired: false,
|
||||
stat_fired: false,
|
||||
vblank_interrupt: false,
|
||||
stat_interrupt: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,17 +108,42 @@ impl Display {
|
||||
self.renderer.fill_rect(sdl2::rect::Rect::new((x as i32) * SCALE as i32, (y as i32) * SCALE as i32, SCALE as u32, SCALE as u32));
|
||||
}
|
||||
|
||||
pub fn vblank_interrupt(&mut self) -> bool {
|
||||
// Returns whether or not a vblank interrupt should be done
|
||||
// Yes, this is polling, and yes, this sucks.\
|
||||
if self.vblank_interrupt {
|
||||
self.vblank_interrupt = false;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stat_interrupt(&mut self) -> bool {
|
||||
if self.stat_interrupt {
|
||||
self.stat_interrupt = false;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
||||
match addr {
|
||||
0x8000 ... 0x9FFF => {
|
||||
println!("VRAM: Write {:02X} to {:04X}", val, addr);
|
||||
// println!("VRAM: Write {:02X} to {:04X}", val, addr);
|
||||
self.vram[(addr - 0x8000) as usize] = val;
|
||||
}
|
||||
0xFE00 ... 0xFE9F => {
|
||||
println!("OAM: Write {:02X} to {:04X}", val, addr);
|
||||
self.oam[(addr - 0xFE00) as usize] = val;
|
||||
}
|
||||
0xFF40 => self.control = val,
|
||||
0xFF41 => self.status = val,
|
||||
0xFF42 => self.scrolly = val,
|
||||
0xFF43 => self.scrollx = val,
|
||||
0xFF44 => self.curline = 0,
|
||||
0xFF45 => self.lyc = val,
|
||||
0xFF47 => self.background_palette = val,
|
||||
0xFF48 => self.object_palette_0 = val,
|
||||
0xFF49 => self.object_palette_1 = val,
|
||||
@ -114,11 +156,13 @@ impl Display {
|
||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||
match addr {
|
||||
0x8000 ... 0x9FFF => self.vram[(addr - 0x8000) as usize],
|
||||
0xFE00 ... 0xFE9F => self.oam[(addr - 0xFE00) as usize],
|
||||
0xFF40 => self.control,
|
||||
0xFF41 => self.status,
|
||||
0xFF42 => self.scrolly,
|
||||
0xFF43 => self.scrollx,
|
||||
0xFF44 => self.curline,
|
||||
0xFF45 => self.lyc,
|
||||
0xFF47 => self.background_palette,
|
||||
0xFF48 => self.object_palette_0,
|
||||
0xFF49 => self.object_palette_1,
|
||||
@ -131,9 +175,15 @@ impl Display {
|
||||
// Do we want to have a time delta here?
|
||||
pub fn tick(&mut self, ticks: u16) {
|
||||
self.current_ticks += ticks;
|
||||
if self.curline == self.lyc {
|
||||
self.stat_fired = true;
|
||||
self.stat_interrupt = true;
|
||||
}
|
||||
match self.current_mode {
|
||||
DisplayMode::Scanline => {
|
||||
if self.current_ticks > TICKS_END_SCANLINE {
|
||||
self.vblank_fired = false;
|
||||
self.stat_fired = false;
|
||||
self.current_ticks = 0;
|
||||
self.current_mode = DisplayMode::Readmode;
|
||||
}
|
||||
@ -151,6 +201,8 @@ impl Display {
|
||||
self.curline += 1;
|
||||
if self.curline == 143 {
|
||||
self.current_mode = DisplayMode::VBlank;
|
||||
self.vblank_interrupt = true;
|
||||
self.vblank_fired = true; // We don't need this, do we?
|
||||
} else {
|
||||
self.current_mode = DisplayMode::Scanline;
|
||||
}
|
||||
@ -257,17 +309,50 @@ impl Display {
|
||||
self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB(factor, factor, factor));
|
||||
|
||||
}
|
||||
|
||||
if self.control & CTRL_BG_SPRITE_ENABLE > 0 {
|
||||
// panic!("Sprites not supported");
|
||||
// Let's draw sprites.
|
||||
// TODO: Sprites with smaller X coordinate should
|
||||
// should be in front
|
||||
// TODO: Draw only up to 10 sprites per line
|
||||
for i in 0 .. 39 {
|
||||
let y: u8 = self.oam[i * 4 + 0];
|
||||
let x: u8 = self.oam[i * 4 + 1];
|
||||
let t_num: u8 = self.oam[i * 4 + 2];
|
||||
let flags: u8 = self.oam[i * 4 + 3];
|
||||
|
||||
|
||||
// Is this sprite on the current line?
|
||||
if (y + 8) >= render_y && y <= render_y {
|
||||
let tile_offset_y: usize = render_y as usize - y as usize;
|
||||
let tile_base_addr: usize = 0x1000 + t_num as usize * 16;
|
||||
|
||||
let tile_line_1 = self.vram[tile_base_addr + tile_offset_y * 2];
|
||||
let tile_line_2 = self.vram[tile_base_addr + tile_offset_y * 2 + 1];
|
||||
|
||||
// We need to draw this.
|
||||
for x_o in 0 .. 8 {
|
||||
let b1: bool = (tile_line_1 & 1 << (7 - x_o)) > 0;
|
||||
let b2: bool = (tile_line_2 & 1 << (7 - x_o)) > 0;
|
||||
|
||||
let mut factor = 0;
|
||||
if b1 {
|
||||
factor += 64;
|
||||
}
|
||||
if b2 {
|
||||
factor += 128;
|
||||
}
|
||||
|
||||
// Draw stuff. We're currently only in monochrome mode
|
||||
self.set_pixel(x + x_o, render_y, sdl2::pixels::Color::RGB(factor, factor, factor));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vblank_interrupt(&mut self) {
|
||||
self.curline += 1;
|
||||
if self.curline > 153 {
|
||||
self.curline = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn controller_interrupt(&self) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,88 +1,77 @@
|
||||
const RAM_SIZE: usize = 0x2000;
|
||||
const HIRAM_SIZE: usize = (0xFFFE - 0xFF80) + 1;
|
||||
|
||||
const INTERRUPT_DISPLAY: u8 = 1 << 1;
|
||||
const INTERRUPT_DISPLAY_VBLANK: u8 = 1 << 0;
|
||||
pub const INTERRUPT_DISPLAY_STAT: u8 = 1 << 1;
|
||||
pub const INTERRUPT_DISPLAY_VBLANK: u8 = 1 << 0;
|
||||
|
||||
use super::display;
|
||||
use super::sound;
|
||||
use super::timer;
|
||||
use super::serial;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MemoryBankControllerType {
|
||||
None,
|
||||
MBC1,
|
||||
MBC2,
|
||||
MBC3,
|
||||
HuC1,
|
||||
}
|
||||
use super::cartridge;
|
||||
|
||||
pub struct Interconnect {
|
||||
bios: Box<[u8]>,
|
||||
rom: Box<[u8]>,
|
||||
cartridge: cartridge::Cartridge,
|
||||
ram: Box<[u8]>,
|
||||
hiram: Box<[u8]>,
|
||||
sound: sound::Sound,
|
||||
display: display::Display,
|
||||
interrupt: u8,
|
||||
iflags: u8,
|
||||
interrupt_request_flags: u8,
|
||||
disable_bootrom: u8,
|
||||
infrared_com_port: u8,
|
||||
serial: serial::Serial,
|
||||
timer: timer::Timer,
|
||||
|
||||
bank_no: u16,
|
||||
mbc_type: MemoryBankControllerType,
|
||||
}
|
||||
|
||||
impl Interconnect {
|
||||
pub fn new(bios: Box<[u8]>, rom: Box<[u8]>) -> Interconnect {
|
||||
let mbc: MemoryBankControllerType = match rom[0x0147] {
|
||||
0x00 | 0x08 ... 0x09 => MemoryBankControllerType::None,
|
||||
// 0x01 ... 0x03 => MemoryBankControllerType::MBC1,
|
||||
// 0x05 ... 0x06 => MemoryBankControllerType::MBC2,
|
||||
// 0x0F ... 0x13 => MemoryBankControllerType::MBC3,
|
||||
// 0xFF => MemoryBankControllerType::HuC1,
|
||||
_ => panic!("Unsupported MBC type"),
|
||||
};
|
||||
|
||||
println!("MBC Type: {:?}", &mbc);
|
||||
|
||||
Interconnect {
|
||||
bios: bios,
|
||||
rom: rom,
|
||||
cartridge: cartridge::Cartridge::new(rom),
|
||||
ram: vec![0; RAM_SIZE].into_boxed_slice(),
|
||||
hiram: vec![0; HIRAM_SIZE].into_boxed_slice(),
|
||||
sound: sound::Sound::new(),
|
||||
display: display::Display::new(),
|
||||
// Refactor those
|
||||
iflags: 0,
|
||||
interrupt_request_flags: 0,
|
||||
interrupt: 0,
|
||||
infrared_com_port: 0,
|
||||
disable_bootrom: 0,
|
||||
timer: timer::Timer::new(),
|
||||
serial: serial::Serial::new(),
|
||||
|
||||
bank_no: 0,
|
||||
mbc_type: mbc,
|
||||
}
|
||||
}
|
||||
|
||||
// Somehow we need different timers for this.
|
||||
pub fn tick(&mut self, cycles: u8) {
|
||||
self.display.tick(cycles as u16 * 4);
|
||||
}
|
||||
|
||||
pub fn display_blank_interrupt(&mut self) {
|
||||
if self.interrupt & INTERRUPT_DISPLAY_VBLANK >= 0 {
|
||||
self.display.vblank_interrupt();
|
||||
if self.display.vblank_interrupt() {
|
||||
self.interrupt_request_flags |= INTERRUPT_DISPLAY_VBLANK;
|
||||
}
|
||||
|
||||
if self.display.stat_interrupt() {
|
||||
self.interrupt_request_flags |= INTERRUPT_DISPLAY_STAT;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_interrupt(&mut self) {
|
||||
if self.interrupt & INTERRUPT_DISPLAY > 0 {
|
||||
self.display.controller_interrupt();
|
||||
pub fn is_boot_rom(&self) -> bool {
|
||||
self.disable_bootrom == 0
|
||||
}
|
||||
|
||||
|
||||
pub fn vblank_interrupt(&mut self) -> bool {
|
||||
// This should set 0xFF0F accordingly and the CPU should
|
||||
// handle the interrupt generation. So this is WRONG!
|
||||
// the interrupt WAITS
|
||||
if self.display.vblank_interrupt() && self.interrupt & INTERRUPT_DISPLAY_VBLANK > 0 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,23 +80,16 @@ impl Interconnect {
|
||||
// TODO: if some flag set, use bios, otherwise only use rom
|
||||
// For now, just use bios
|
||||
match addr {
|
||||
0x0000 ... 0x3FFF => {
|
||||
// TODO: Check if bios or cartridge (additional condition: isEnabled)
|
||||
if addr < 0x100 && self.disable_bootrom == 0 {
|
||||
0x0000 ... 0x100 => {
|
||||
if self.disable_bootrom == 0 {
|
||||
self.bios[addr as usize]
|
||||
} else {
|
||||
let val: u8 = self.rom[addr as usize];
|
||||
println!("Access {:04X} = {:02X}", addr, val);
|
||||
val
|
||||
self.cartridge.read_byte(addr)
|
||||
}
|
||||
},
|
||||
0x4000 ... 0x7FFF => {
|
||||
let abs_addr: usize = addr as usize + self.bank_no as usize * 0x4000;
|
||||
let val: u8 = self.rom[abs_addr];
|
||||
println!("Access {:04X}{:04X} = {:02X}", self.bank_no, addr, val);
|
||||
val
|
||||
},
|
||||
0x100 ... 0x7FFF => self.cartridge.read_byte(addr),
|
||||
0x8000 ... 0x9FFF => self.display.read_byte(addr),
|
||||
0xA000 ... 0xBFFF => self.cartridge.read_byte(addr),
|
||||
0xC000 ... 0xDFFF => {
|
||||
self.ram[(addr - 0xC000) as usize]
|
||||
},
|
||||
@ -115,7 +97,7 @@ impl Interconnect {
|
||||
0xFF01 ... 0xFF02 => self.serial.read_byte(addr),
|
||||
0xFF04 ... 0xFF07 => self.timer.read_byte(addr),
|
||||
0xFF0F => {
|
||||
self.iflags
|
||||
self.interrupt_request_flags
|
||||
},
|
||||
0xFF10 ... 0xFF26 => {
|
||||
self.sound.read_byte(addr)
|
||||
@ -157,14 +139,17 @@ impl Interconnect {
|
||||
*/
|
||||
|
||||
match addr {
|
||||
0x0000 ... 0x7FFF => self.cartridge.write_byte(addr, val),
|
||||
0x8000 ... 0x9FFF => self.display.write_byte(addr, val),
|
||||
0xA000 ... 0xBFFF => self.cartridge.write_byte(addr, val),
|
||||
0xC000 ... 0xDFFF => {
|
||||
self.ram[(addr - 0xC000) as usize] = val;
|
||||
},
|
||||
0xFE00 ... 0xFE9F => self.display.write_byte(addr, val), // OAM
|
||||
0xFF01 ... 0xFF02 => self.serial.write_byte(addr, val),
|
||||
0xFF04 ... 0xFF07 => self.timer.write_byte(addr, val),
|
||||
0xFF0F => {
|
||||
self.iflags = val;
|
||||
self.interrupt_request_flags = val;
|
||||
}
|
||||
0xFF10 ... 0xFF26 => {
|
||||
self.sound.write_byte(addr, val);
|
||||
@ -183,6 +168,7 @@ impl Interconnect {
|
||||
self.hiram[(addr - 0xFF80) as usize] = val;
|
||||
},
|
||||
0xFFFF => {
|
||||
println!("Setting interrupt mask value {:02X}", val);
|
||||
self.interrupt = val;
|
||||
},
|
||||
_ => {
|
||||
|
||||
@ -6,6 +6,7 @@ use std::io::Read;
|
||||
use std::fs;
|
||||
use std::env;
|
||||
|
||||
mod cartridge;
|
||||
mod cpu;
|
||||
mod display;
|
||||
mod interconnect;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user