rustyboy/src/interconnect.rs

241 lines
8.2 KiB
Rust

// const RAM_SIZE: usize = 0x2000;
const WRAM_SIZE: usize = 0x8000;
const HIRAM_SIZE: usize = (0xFFFE - 0xFF80) + 1;
pub const INTERRUPT_TIMER_OVERFLOW: u8 = 1 << 2;
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;
use super::cartridge;
pub struct Interconnect {
bios: Box<[u8]>,
cartridge: cartridge::Cartridge,
ram: Box<[u8]>,
hiram: Box<[u8]>,
wram_bank: u8,
sound: sound::Sound,
display: display::Display,
interrupt: u8,
interrupt_request_flags: u8,
disable_bootrom: u8,
infrared_com_port: u8,
serial: serial::Serial,
timer: timer::Timer,
vram_dma_source_high: u8,
vram_dma_source_low: u8,
vram_dma_destination_high: u8,
vram_dma_destination_low: u8,
}
impl Interconnect {
pub fn new(bios: Box<[u8]>, rom: Box<[u8]>) -> Interconnect {
Interconnect {
bios: bios,
cartridge: cartridge::Cartridge::new(rom),
ram: vec![0; WRAM_SIZE].into_boxed_slice(),
hiram: vec![0; HIRAM_SIZE].into_boxed_slice(),
wram_bank: 1,
sound: sound::Sound::new(),
display: display::Display::new(),
// Refactor those
interrupt_request_flags: 0,
interrupt: 0,
infrared_com_port: 0,
disable_bootrom: 0,
timer: timer::Timer::new(),
serial: serial::Serial::new(),
vram_dma_source_high: 0,
vram_dma_source_low: 0,
vram_dma_destination_high: 0,
vram_dma_destination_low: 0,
}
}
// Somehow we need different timers for this.
pub fn tick(&mut self, cycles: u8) {
self.display.tick(cycles as u16 * 4);
self.timer.tick(cycles as u16 * 4);
if self.display.vblank_interrupt() {
self.interrupt_request_flags |= INTERRUPT_DISPLAY_VBLANK;
}
if self.display.stat_interrupt() {
self.interrupt_request_flags |= INTERRUPT_DISPLAY_STAT;
}
if self.timer.timer_interrupt() {
self.interrupt_request_flags |= INTERRUPT_TIMER_OVERFLOW;
}
}
pub fn is_boot_rom(&self) -> bool {
self.disable_bootrom == 0
}
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 ... 0x100 => {
if self.disable_bootrom == 0 {
self.bios[addr as usize]
} else {
self.cartridge.read_byte(addr)
}
},
0x100 ... 0x7FFF => self.cartridge.read_byte(addr),
0x8000 ... 0x9FFF => self.display.read_byte(addr),
0xA000 ... 0xBFFF => self.cartridge.read_byte(addr),
0xC000 ... 0xCFFF => {
self.ram[(addr - 0xC000) as usize]
},
0xD000 ... 0xDFFF => {
self.ram[(addr - 0xD000) as usize + self.wram_bank as usize * 0x1000]
}
0xFF00 => 0xFF, // TODO: This is the input stuff
0xFF01 ... 0xFF02 => self.serial.read_byte(addr),
0xFF04 ... 0xFF07 => self.timer.read_byte(addr),
0xFF0F => {
self.interrupt_request_flags
},
0xFF10 ... 0xFF26 => {
self.sound.read_byte(addr)
},
0xFF40 ... 0xFF4B => {
self.display.read_byte(addr)
},
0xFF50 => {
self.disable_bootrom
},
0xFF51 => self.vram_dma_source_high,
0xFF52 => self.vram_dma_source_low,
0xFF53 => self.vram_dma_destination_high,
0xFF54 => self.vram_dma_destination_low,
0xFF55 => {
println!("Read from 0xFF55 (DMA length/mode/start) not fully supported");
0xFF
}
0xFF56 => {
self.infrared_com_port
},
0xFF70 => self.wram_bank,
0xFF80 ... 0xFFFE => {
self.hiram[(addr - 0xFF80) as usize]
},
0xFFFF => {
self.interrupt
}
_ => {
panic!("Read from {:04X} not supported.", addr);
}
}
}
pub fn write_byte(&mut self, addr: u16, val: u8) {
// TODO: Make this more beautful
/*
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
FF80 FFFE High RAM
FFFF FFFF Interrupt switch
*/
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 ... 0xCFFF => {
self.ram[(addr - 0xC000) as usize] = val;
},
0xD000 ... 0xDFFF => {
self.ram[(addr - 0xD000) as usize + self.wram_bank as usize * 0x1000] = 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.interrupt_request_flags = val;
}
0xFF10 ... 0xFF26 => {
self.sound.write_byte(addr, val);
},
// Exclude DMA transfer, we will do this below
0xFF40 ... 0xFF45 | 0xFF47 ... 0xFF4B => {
self.display.write_byte(addr, val);
},
0xFF46 => {
println!("OAM DMA transfer: ");
for x in 0x00 .. 0x9F {
let dma_b = self.read_byte(((val as u16) << 8) | x);
self.write_byte(0xFE00 | x, dma_b);
print!("{:02X} ", val);
}
println!("");
}
0xFF50 => {
println!("Disabling boot rom.");
self.disable_bootrom = val;
},
0xFF51 => self.vram_dma_source_high = val,
0xFF52 => self.vram_dma_source_low = val,
0xFF53 => self.vram_dma_destination_high = val,
0xFF54 => self.vram_dma_destination_low = val,
0xFF55 => {
let src: u16 = ((self.vram_dma_source_high as u16) << 8) | self.vram_dma_source_low as u16;
let dst: u16 = ((self.vram_dma_destination_high as u16) << 8) | self.vram_dma_destination_low as u16;
println!("VRAM DMA transfer from {:04X} to {:04X}; {:02X}", src, dst, val);
let len: u16 = ((val & 0x7F) + 1) as u16 * 0x10 - 1;
for i in 0..len {
let v = self.read_byte(src.wrapping_add(i));
self.write_byte(dst.wrapping_add(i), v);
}
}
0xFF56 => {
self.infrared_com_port = val;
},
0xFF70 => {
println!("Set wram bank to {:02X}", val);
if val > 7 {
panic!("R u sure this is correct?");
}
self.wram_bank = val;
}
0xFF80 ... 0xFFFE => {
self.hiram[(addr - 0xFF80) as usize] = val;
},
0xFFFF => {
println!("Setting interrupt mask value {:02X}", val);
self.interrupt = val;
},
_ => {
println!("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
// (self.read_byte(addr) as u16) << 8 | (self.read_byte(addr + 1) as u16)
}
}