Serial and Timer class, cycle counting

Also a little bit of refactoring.
Move VRAM to the display object
This commit is contained in:
Kevin Hamacher 2016-05-26 15:47:08 +02:00
parent 94e73b2eda
commit 396e87304e
6 changed files with 359 additions and 32 deletions

View File

@ -24,6 +24,8 @@ pub struct CPU {
sp: u16,
interconnect: interconnect::Interconnect,
interrupts_enabled: bool,
}
fn to_u16(bytes: Box<[u8]>) -> u16 {
@ -39,7 +41,8 @@ impl CPU {
regs: [0, 0, 0, 0, 0, 0, 0],
ip: 0,
sp: 0xFFFE,
interconnect: interconnect
interconnect: interconnect,
interrupts_enabled: false, // Is this correct?
}
}
@ -234,56 +237,80 @@ impl CPU {
print!("C={} ", self.flags & FLAG_C != 0);
self.ip += 1;
let mut cycles: u16;
match instruction {
0x00 => {
println!("NOP");
cycles = 4;
},
0x01 => {
println!("LD (BC), A");
let addr: u16 = self.get_pair_value(REG_B, REG_C);
let val: u8 = self.regs[REG_A];
self.interconnect.write_byte(addr, val);
cycles = 12;
}
0x04 => {
println!("INC B");
self.reg_inc(REG_B);
cycles = 4;
},
0x05 => {
println!("DEC B");
self.reg_dec(REG_B);
cycles = 4;
},
0x06 => {
let args = self.load_args(1);
println!("LD B, {:02x}", args[0]);
self.regs[REG_B] = args[0];
cycles = 8;
},
0x0C => {
println!("INC C");
self.reg_inc(REG_C);
cycles = 4;
},
0x0D => {
println!("DEC C");
self.reg_dec(REG_C);
cycles = 4;
},
0x0E => {
let args = self.load_args(1);
println!("LD C, {:02x}", args[0]);
self.regs[REG_C] = args[0];
cycles = 8;
},
0x11 => {
let args = self.load_args(2);
let val = to_u16(args);
println!("LD DE, {:04x}", val);
self.set_pair_value(REG_D, REG_E, val);
cycles = 12;
},
0x13 => {
println!("INC DE");
let old_value = self.get_pair_value(REG_D, REG_E);
self.set_pair_value(REG_D, REG_E, old_value + 1);
cycles = 8;
},
0x14 => {
println!("INC D");
self.reg_inc(REG_D);
cycles = 4;
},
0x15 => {
println!("DEC D");
self.reg_dec(REG_D);
cycles = 4;
},
0x16 => {
let args = self.load_args(1);
println!("LD D, {:02x}", args[0]);
self.regs[REG_D] = args[0];
cycles = 8;
},
0x17 => {
println!("RLA");
@ -300,6 +327,7 @@ impl CPU {
}
self.regs[REG_A] = self.regs[REG_A] << 1 | 1;
}
cycles = 4;
},
0x18 => {
let args = self.load_args(1);
@ -310,23 +338,28 @@ impl CPU {
} else {
self.ip += off as u16;
}
cycles = 12;
},
0x1A => {
println!("LD A, (DE)");
self.regs[REG_A] = self.interconnect.read_byte(self.get_pair_value(REG_D, REG_E));
cycles = 8;
},
0x1C => {
println!("INC E");
self.reg_inc(REG_E);
cycles = 4;
},
0x1D => {
println!("DEC E");
self.reg_dec(REG_E);
cycles = 4;
},
0x1E => {
let args = self.load_args(1);
println!("LD E, {:02x}", args[0]);
self.regs[REG_E] = args[0];
cycles = 8;
},
0x20 => {
let args = self.load_args(1);
@ -338,36 +371,45 @@ impl CPU {
} else {
self.ip += offset as u16;
}
cycles = 12;
} else {
cycles = 8;
}
}
0x21 => {
let value = to_u16(self.load_args(2));
println!("LD HL, {:04x}", value);
self.set_pair_value(REG_H, REG_L, value);
cycles = 12;
},
0x22 => {
println!("LD (HL+), A");
let addr: u16 = self.get_pair_value(REG_H, REG_L);
self.interconnect.write_byte(addr, self.regs[REG_A]);
self.set_pair_value(REG_H, REG_L, addr + 1);
cycles = 8;
},
0x23 => {
println!("INC HL");
let old_value = self.get_pair_value(REG_H, REG_L);
self.set_pair_value(REG_H, REG_L, old_value + 1);
cycles = 8;
},
0x24 => {
println!("INC H");
self.reg_inc(REG_H);
cycles = 4;
},
0x25 => {
println!("DEC H");
self.reg_dec(REG_H);
cycles = 4;
},
0x26 => {
let args = self.load_args(1);
println!("LD H, {:02x}", args[0]);
self.regs[REG_H] = args[0];
cycles = 8;
},
0x28 => {
let args = self.load_args(1);
@ -377,26 +419,35 @@ impl CPU {
} else {
target = self.ip + args[0] as u16;
}
println!("LR Z, {:04X}", target);
self.ip = target;
println!("JR Z, {:04X}", target);
if self.flags & FLAG_Z > 0 {
self.ip = target;
cycles = 12;
} else {
cycles = 8;
}
},
0x2C => {
println!("INC L");
self.reg_inc(REG_L);
cycles = 4;
},
0x2D => {
println!("DEC L");
self.reg_dec(REG_L);
cycles = 4;
},
0x2E => {
let args = self.load_args(1);
println!("LD L, {:02x}", args[0]);
self.regs[REG_L] = args[0];
cycles = 8;
},
0x31 => {
let args = self.load_args(2);
self.sp = to_u16(args);
println!("LD SP, {:02x}", self.sp);
cycles = 12;
},
0x32 => {
println!("LD (HL-), A");
@ -405,25 +456,30 @@ impl CPU {
addr -= 1;
self.set_pair_value(REG_H, REG_L, addr);
cycles = 8;
},
0x36 => {
let args = self.load_args(1);
println!("LD (HL), {:02x}", args[0]);
let addr = self.get_pair_value(REG_H, REG_L);
self.interconnect.write_byte(addr, args[0]);
cycles = 12;
},
0x3C => {
println!("INC A");
self.reg_inc(REG_A);
cycles = 4;
}
0x3D => {
println!("DEC A");
self.reg_dec(REG_A);
cycles = 4;
}
0x3E => {
let args = self.load_args(1);
println!("LD A, {:02x}", args[0]);
self.regs[REG_A] = args[0];
cycles = 8;
},
// LDs
@ -431,44 +487,52 @@ impl CPU {
let reg_id = (instruction - 0x40) as usize;
println!("LD B, {}", REG_NAMES[reg_id]);
self.regs[REG_B] = self.get_8bit_reg(reg_id);
cycles = 4;
},
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);
cycles = 4;
},
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);
cycles = 4;
},
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);
cycles = 4;
},
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);
cycles = 4;
},
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);
cycles = 4;
},
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.get_pair_value(REG_H, REG_L);
cycles = 8;
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);
cycles = 4;
},
// ADD
@ -477,6 +541,7 @@ impl CPU {
println!("ADD {}", REG_NAMES[reg_id]);
self.regs[REG_A] = self.regs[REG_A].wrapping_add(self.get_8bit_reg(reg_id));
self.flags &= !FLAG_N;
cycles = 4;
}
// ADC
@ -488,6 +553,7 @@ impl CPU {
self.regs[REG_A] = self.regs[REG_A].wrapping_add(1);
}
self.flags &= !FLAG_N;
cycles = 4;
}
// SUBs
@ -502,6 +568,7 @@ impl CPU {
self.flags &= !FLAG_Z;
}
// TODO: H, C
cycles = 4;
}
// SBC
@ -513,6 +580,7 @@ impl CPU {
self.regs[REG_A] = self.regs[REG_A].wrapping_sub(1);
}
self.flags |= FLAG_N;
cycles = 4;
}
// XOR
@ -528,6 +596,7 @@ impl CPU {
self.flags &= !FLAG_C;
self.flags &= !FLAG_N;
self.flags &= !FLAG_H;
cycles = 4;
},
// CP
@ -545,66 +614,127 @@ impl CPU {
self.flags &= !FLAG_Z;
self.flags &= !FLAG_C;
}
cycles = 4;
},
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);
cycles = 12;
},
0xC2 => {
let addr: u16 = to_u16(self.load_args(2));
println!("JP NZ {:04X}", addr);
if self.flags & FLAG_Z == 0 {
self.ip = addr;
cycles = 16;
} else {
cycles = 12;
}
},
0xC3 => {
let addr: u16 = to_u16(self.load_args(2));
println!("JP {:04X}", addr);
self.ip = addr;
cycles = 16;
}
0xC4 => {
let target = to_u16(self.load_args(2));
println!("CALL NZ {: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;
cycles = 24;
} else {
cycles = 12;
}
},
0xC5 => {
println!("PUSH BC");
let val: u16 = self.get_pair_value(REG_B, REG_C);
self.interconnect.write_word(self.sp - 2, val);
self.sp -= 2;
cycles = 16;
},
0xC9 => {
println!("RET");
self.dump_stack();
self.ip = self.interconnect.read_word(self.sp+1);
self.sp += 2;
cycles = 16;
},
0xCB => {
// Prefix CB. This is annoying.
self.run_prefix_instruction();
cycles = 12; // TODO: Verify that this is the case for all prefix instructions.
},
0xCC => {
let target = to_u16(self.load_args(2));
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;
cycles = 24;
} else {
cycles = 12;
}
},
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.dump_stack();
self.ip = target;
cycles = 24;
},
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]);
cycles = 12;
},
0xE2 => {
println!("LD (C), A");
println!("[{:04X}] = {:02X}", 0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]);
self.interconnect.write_byte(0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A])
self.interconnect.write_byte(0xFF00 + self.regs[REG_C] as u16, self.regs[REG_A]);
cycles = 8;
},
0xEA => {
let addr = to_u16(self.load_args(2));
println!("LD ({:04X}), A", addr);
self.interconnect.write_byte(addr, self.regs[REG_A]);
cycles = 16;
}
0xF0 => {
let args = self.load_args(1);
println!("LDH A, {:02X}", args[0]);
self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + args[0] as u16);
cycles = 12;
},
0xF2 => {
println!("LD A, (C)");
self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + self.regs[REG_C] as u16);
cycles = 8;
},
0xF3 => {
println!("DI");
self.interrupts_enabled = false;
cycles = 4;
}
0xFB => {
// Enable interrupts - TODO
println!("EI");
self.interconnect.write_byte(0xFFFF, 0x01);
panic!("Uh uh");
self.interrupts_enabled = true;
// self.interconnect.write_byte(0xFFFF, 0x01);
// panic!("Uh uh");
cycles = 4;
panic!("ENABLING INTERRUPTS - TODO");
},
0xFE => {
let args = self.load_args(1);
@ -622,10 +752,11 @@ impl CPU {
}
// TODO H
cycles = 8;
}
_ => panic!("Unknown instruction: {:02x}", instruction)
}
// self.dump_stack();
self.interconnect.tick();
self.interconnect.tick(cycles);
}
}

View File

@ -1,43 +1,159 @@
const VRAM_SIZE: usize = 0x2000;
const TICKS_END_SCANLINE: u16 = 80;
const TICKS_END_READMODE: u16 = 172;
const TICKS_END_HBLANK: u16 = 204;
const TICKS_END_VBLANK: u16 = 456;
#[derive(Debug)]
enum DisplayMode {
Scanline,
Readmode,
HBlank,
VBlank,
}
impl Default for DisplayMode {
fn default() -> DisplayMode {
DisplayMode::Scanline
}
}
#[derive(Debug, Default)]
pub struct Display {
control: u8,
status: u8,
background_palette: u8,
object_palette_0: u8,
object_palette_1: u8,
scrollx: u8,
scrolly: u8,
windowx: u8,
windowy: u8,
curline: u8,
vram: Box<[u8]>,
current_ticks: u16,
current_mode: DisplayMode,
// TODO
}
impl Display {
pub fn new() -> Display {
Display::default()
Display {
control: 0,
status: 0,
background_palette: 0,
object_palette_0: 0,
object_palette_1: 0,
scrollx: 0,
scrolly: 0,
windowx: 0,
windowy: 0,
curline: 0,
current_ticks: 0,
current_mode: DisplayMode::default(),
vram: vec![0; VRAM_SIZE].into_boxed_slice(),
}
}
pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0x8000 ... 0x9FFF => self.vram[(addr - 0x8000) as usize] = val,
0xFF40 => self.control = val,
0xFF41 => self.status = val,
0xFF42 => self.scrolly = val,
0xFF43 => self.scrollx = val,
0xFF44 => self.curline = 0,
0xFF47 => self.background_palette = val,
0xFF48 => self.object_palette_0 = val,
0xFF49 => self.object_palette_1 = val,
0xFF4A => self.windowy = val,
0xFF4B => self.windowx = val,
_ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr),
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
match addr {
0x8000 ... 0x9FFF => self.vram[(addr - 0x8000) as usize],
0xFF40 => self.control,
0xFF41 => self.status,
0xFF42 => self.scrolly,
0xFF43 => self.scrollx,
0xFF44 => self.curline,
0xFF47 => self.background_palette,
0xFF48 => self.object_palette_0,
0xFF49 => self.object_palette_1,
0xFF4A => self.windowy,
0xFF4B => self.windowx,
_ => panic!("Display: Read from {:04X} unsupported", addr),
}
}
// Do we want to have a time delta here?
pub fn tick(&mut self, ticks: u16) {
self.current_ticks += ticks;
match self.current_mode {
DisplayMode::Scanline => {
if self.current_ticks > TICKS_END_SCANLINE {
self.current_ticks = 0;
self.current_mode = DisplayMode::Readmode;
}
},
DisplayMode::Readmode => {
if self.current_ticks > TICKS_END_READMODE {
self.current_ticks = 0;
self.current_mode = DisplayMode::HBlank;
}
},
DisplayMode::HBlank => {
if self.current_ticks > TICKS_END_HBLANK {
self.current_ticks = 0;
self.curline += 1;
if self.curline == 143 {
self.current_mode = DisplayMode::VBlank;
} else {
self.current_mode = DisplayMode::Scanline;
}
}
},
DisplayMode::VBlank => {
if self.current_ticks > TICKS_END_VBLANK {
self.current_ticks = 0;
self.curline += 1;
if self.curline > 153 {
self.current_mode = DisplayMode::Scanline;
self.curline = 0;
}
}
}
}
}
fn renderscan(&mut self) {
// TODO: Allow switching of tile map
let tilemap: u16 = 0x1800;
let map_offset_y: u8 = self.curline.wrapping_add(self.scrolly);
let map_offset_x: u8 = self.scrollx;
let tile_index_y: u8 = map_offset_y / 8;
// Render line
for render_x in 0 .. 159 {
let tile_index_x: u8 = render_x / 8;
let tile_offset_x: u8 = render_x % 8;
// TODO: Draw bit
// let tile_base_addr = tilemap + tile_id*128
// pixel(render_x, map_offset_y) := *tile_base_addr + 2* *(tile_base_addr+1)
//[tile_index_x][tile_index_y]
}
}
pub fn vblank_interrupt(&mut self) {
self.curline += 1;
if self.curline > 153 {

View File

@ -1,5 +1,4 @@
const RAM_SIZE: usize = 0x2000;
const VRAM_SIZE: usize = 0x2000;
const HIRAM_SIZE: usize = (0xFFFE - 0xFF80) + 1;
const INTERRUPT_DISPLAY: u8 = 1 << 1;
@ -7,18 +6,23 @@ const INTERRUPT_DISPLAY_VBLANK: u8 = 1 << 0;
use super::display;
use super::sound;
use super::timer;
use super::serial;
pub struct Interconnect {
bios: Box<[u8]>,
rom: Box<[u8]>,
ram: Box<[u8]>,
vram: Box<[u8]>,
hiram: Box<[u8]>,
sound: sound::Sound,
display: display::Display,
interrupt: u8,
timer: u8,
iflags: u8,
itimer: u8,
disable_bootrom: u8,
infrared_com_port: u8,
serial: serial::Serial,
timer: timer::Timer,
}
impl Interconnect {
@ -27,23 +31,30 @@ impl 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(),
sound: sound::Sound::new(),
display: display::Display::new(),
// Refactor those
iflags: 0,
interrupt: 0,
timer: 0,
infrared_com_port: 0,
itimer: 0,
disable_bootrom: 0,
timer: timer::Timer::new(),
serial: serial::Serial::new(),
}
}
// Somehow we need different timers for this.
pub fn tick(&mut self) {
self.timer += 1;
if self.timer == 5 {
pub fn tick(&mut self, cycles: u16) {
/*
self.itimer += 1;
if self.itimer == 5 {
self.display_blank_interrupt();
self.timer = 0;
self.itimer = 0;
}
*/
self.display.tick(cycles * 4);
}
pub fn display_blank_interrupt(&mut self) {
@ -71,12 +82,15 @@ impl Interconnect {
self.rom[addr as usize]
}
}
0x8000 ... 0x9FFF => {
self.vram[(addr - 0x8000) as usize]
},
0x8000 ... 0x9FFF => self.display.read_byte(addr),
0xC000 ... 0xDFFF => {
self.ram[(addr - 0xC000) as usize]
},
0xFF01 ... 0xFF02 => self.serial.read_byte(addr),
0xFF04 ... 0xFF07 => self.timer.read_byte(addr),
0xFF0F => {
self.iflags
},
0xFF10 ... 0xFF26 => {
self.sound.read_byte(addr)
},
@ -86,6 +100,9 @@ impl Interconnect {
0xFF50 => {
self.disable_bootrom
},
0xFF56 => {
self.infrared_com_port
}
0xFF80 ... 0xFFFE => {
self.hiram[(addr - 0xFF80) as usize]
},
@ -100,7 +117,6 @@ impl Interconnect {
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
@ -109,18 +125,21 @@ impl Interconnect {
E000 FCFF Copy of the work RAM
FE00 FE9F OAM (Sprite Attribute Table)
FEA0 FEFF Unused
FF00 FF7F Hardware IO
FF00 FF7F Hardware IO
FF80 FFFE High RAM
FFFF FFFF Interrupt switch
*/
match addr {
0x8000 ... 0x9FFF => {
self.vram[(addr - 0x8000) as usize] = val;
},
0x8000 ... 0x9FFF => self.display.write_byte(addr, val),
0xC000 ... 0xDFFF => {
self.ram[(addr - 0xC000) as usize] = val;
},
0xFF01 ... 0xFF02 => self.serial.write_byte(addr, val),
0xFF04 ... 0xFF07 => self.timer.write_byte(addr, val),
0xFF0F => {
self.iflags = val;
}
0xFF10 ... 0xFF26 => {
self.sound.write_byte(addr, val);
},
@ -129,7 +148,10 @@ impl Interconnect {
},
0xFF50 => {
self.disable_bootrom = val;
}
},
0xFF56 => {
self.infrared_com_port = val;
},
0xFF80 ... 0xFFFE => {
self.hiram[(addr - 0xFF80) as usize] = val;
},

View File

@ -10,7 +10,8 @@ mod cpu;
mod display;
mod interconnect;
mod sound;
mod timer;
mod serial;
fn main() {
let bios_path = env::args().nth(1).unwrap();

27
src/serial.rs Normal file
View File

@ -0,0 +1,27 @@
#[derive(Default, Debug)]
pub struct Serial {
data: u8,
control: u8,
}
impl Serial {
pub fn new() -> Serial {
Serial::default()
}
pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0xFF01 => self.data = val,
0xFF02 => self.control = val,
_ => panic!("Serial: Write {:02X} to {:04X} unsupported", val, addr),
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
match addr {
0xFF01 => self.data,
0xFF02 => self.control,
_ => panic!("Serial: Read from {:04X} unsupported", addr),
}
}
}

30
src/timer.rs Normal file
View File

@ -0,0 +1,30 @@
#[derive(Default, Debug)]
pub struct Timer {
tima: u8,
tma: u8,
tac: u8
}
impl Timer {
pub fn new() -> Timer {
Timer::default()
}
pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0xFF05 => self.tima = val,
0xFF06 => self.tma = val,
0xFF07 => self.tac = val,
_ => panic!("Timer: Write {:02X} to {:04X} unsupported", val, addr),
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
match addr {
0xFF05 => self.tima,
0xFF06 => self.tma,
0xFF07 => self.tac,
_ => panic!("Timer: Read from {:04X} unsupported", addr),
}
}
}