241 lines
8.2 KiB
Rust
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)
|
|
}
|
|
}
|