Implement DMA, WIP

I have fought very hard against the rust ownership model,
In the end for DMA to play nice with my code, I had to resort to use
unsafe code for now..

The DMA implementation itself is not accurate to say the least, but will
have to do for now.

Tonc's dma_demo.gba plays but with a visual glitch.


Former-commit-id: 3b9cdcb2d09c78701290f2c48b77f9f3487e85c9
This commit is contained in:
Michel Heily 2019-11-09 01:43:43 +02:00
parent 3a1d5c10ce
commit c78a111ad4
8 changed files with 427 additions and 289 deletions

View file

@ -1,74 +1,210 @@
// use super::arm7tdmi::{Addr, Bus}; use std::collections::VecDeque;
// use super::ioregs::consts::*;
// use super::sysbus::SysBus;
// use super::{EmuIoDev, Interrupt};
// #[allow(non_camel_case_types)] use super::arm7tdmi::{Addr, Bus};
// #[derive(Debug)] use super::sysbus::SysBus;
// pub struct DmaChannel { use super::{Interrupt, IrqBitmask, SyncedIoDevice};
// src_ioreg: Addr, /* Source Address register */
// dst_ioreg: Addr, /* Destination Address register */
// wc_ioreg: Addr, /* Word Count 14bit */
// }
// #[derive(Debug, Primitive)] use num::FromPrimitive;
// enum DmaAddrControl {
// Increment = 0,
// Decrement = 1,
// Fixed = 2,
// IncrementReloadProhibited = 3,
// }
// #[derive(Debug)] #[derive(Debug)]
// enum DmaTransferType { enum DmaTransferType {
// Xfer16bit, Xfer16bit,
// Xfer32bit, Xfer32bit,
// } }
// #[derive(Debug, Primitive)] #[derive(Debug)]
// enum DmaStartTiming { pub struct DmaChannel {
// Immediately = 0, id: usize,
// VBlank = 1,
// HBlank = 2,
// Special = 3,
// }
// #[derive(Debug)] pub src: u32,
// struct DmaControl { pub dst: u32,
// dst_addr_ctl: DmaAddrControl, pub wc: u32,
// src_addr_ctl: DmaAddrControl, pub ctrl: DmaChannelCtrl,
// repeat: bool,
// xfer: DmaTransferType,
// start_timing: DmaStartTiming,
// irq_upon_end_of_wc: bool,
// enable: bool,
// }
// impl DmaChannel { running: bool,
// pub fn new(src_ioreg: Addr, dst_ioreg: Addr, wc_ioreg: Addr) -> DmaChannel { cycles: usize,
// DmaChannel { start_cycles: usize,
// src_ioreg, irq: Interrupt,
// dst_ioreg, }
// wc_ioreg,
// }
// }
// // fn src_addr(&self, sysbus: &SysBus) -> Addr { impl DmaChannel {
// // sysbus.ioregs.read_32(self.src_ioreg - IO_BASE) as Addr pub fn new(id: usize) -> DmaChannel {
// // } if id > 3 {
panic!("invalid dma id {}", id);
}
DmaChannel {
id: id,
irq: Interrupt::from_usize(id + 8).unwrap(),
running: false,
src: 0,
dst: 0,
wc: 0,
ctrl: DmaChannelCtrl(0),
cycles: 0,
start_cycles: 0,
}
}
// // fn dst_addr(&self, sysbus: &SysBus) -> Addr { pub fn is_running(&self) -> bool {
// // sysbus.ioregs.read_32(self.dst_ioreg - IO_BASE) as Addr self.running
// // } }
// // fn word_count(&self, sysbus: &SysBus) -> usize { pub fn write_src_low(&mut self, low: u16) {
// // sysbus.ioregs.read_reg(self.wc_ioreg) as usize let src = self.src;
// // } self.src = (src & 0xffff0000) | (low as u32);
// } }
// impl EmuIoDev for DmaChannel { pub fn write_src_high(&mut self, high: u16) {
// fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) { let src = self.src;
// // TODO let high = high as u32;
// (0, None) self.src = (src & 0xffff) | (high << 16);
// } }
// }
pub fn write_dst_low(&mut self, low: u16) {
let dst = self.dst;
self.dst = (dst & 0xffff0000) | (low as u32);
}
pub fn write_dst_high(&mut self, high: u16) {
let dst = self.dst;
let high = high as u32;
self.dst = (dst & 0xffff) | (high << 16);
}
pub fn write_word_count(&mut self, value: u16) {
self.wc = value as u32;
}
pub fn write_dma_ctrl(&mut self, value: u16) -> bool {
let ctrl = DmaChannelCtrl(value);
let mut start_immediately = false;
if ctrl.is_enabled() {
self.start_cycles = self.cycles;
self.running = true;
if ctrl.timing() == 0 {
start_immediately = true;
}
}
self.ctrl = ctrl;
return start_immediately;
}
fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
let dst_rld = self.dst;
for word in 0..self.wc {
if word_size == 4 {
let w = sb.read_32(self.src);
sb.write_32(self.dst, w)
} else {
let hw = sb.read_16(self.src);
// println!("src {:x} dst {:x}", self.src, self.dst);
sb.write_16(self.dst, hw)
}
match self.ctrl.src_adj() {
/* Increment */ 0 => self.src += word_size,
/* Decrement */ 1 => self.src -= word_size,
/* Fixed */ 2 => {}
_ => panic!("forbidden DMA source address adjustment"),
}
match self.ctrl.dst_adj() {
/* Increment[+Reload] */ 0 | 3 => self.dst += word_size,
/* Decrement */ 1 => self.dst -= word_size,
/* Fixed */ 2 => {}
_ => panic!("forbidden DMA dest address adjustment"),
}
}
if self.ctrl.is_triggering_irq() {
irqs.add_irq(self.irq);
}
if self.ctrl.repeat() {
self.start_cycles = self.cycles;
/* reload */
if 3 == self.ctrl.dst_adj() {
self.dst = dst_rld;
}
} else {
self.running = false;
self.ctrl.set_enabled(false);
}
}
}
#[derive(Debug)]
pub struct DmaController {
pub channels: [DmaChannel; 4],
xfers_queue: VecDeque<usize>,
cycles: usize,
}
impl DmaController {
pub fn new() -> DmaController {
DmaController {
channels: [
DmaChannel::new(0),
DmaChannel::new(1),
DmaChannel::new(2),
DmaChannel::new(3),
],
xfers_queue: VecDeque::new(),
cycles: 0,
}
}
pub fn perform_work(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) -> bool {
if self.xfers_queue.is_empty() {
false
} else {
while let Some(id) = self.xfers_queue.pop_front() {
self.channels[id].xfer(sb, irqs)
}
true
}
}
pub fn write_16(&mut self, channel_id: usize, ofs: u32, value: u16) {
match ofs {
0 => self.channels[channel_id].write_src_low(value),
2 => self.channels[channel_id].write_src_high(value),
4 => self.channels[channel_id].write_dst_low(value),
6 => self.channels[channel_id].write_dst_high(value),
8 => self.channels[channel_id].write_word_count(value),
10 => {
if self.channels[channel_id].write_dma_ctrl(value) {
self.xfers_queue.push_back(channel_id)
}
}
_ => panic!("Invalid dma offset"),
}
}
pub fn notify_vblank(&mut self) {
for i in 0..4 {
if self.channels[i].ctrl.is_enabled() && self.channels[i].ctrl.timing() == 1 {
self.xfers_queue.push_back(i);
}
}
}
pub fn notify_hblank(&mut self) {
for i in 0..4 {
if self.channels[i].ctrl.is_enabled() && self.channels[i].ctrl.timing() == 2 {
self.xfers_queue.push_back(i);
}
}
}
}
bitfield! {
#[derive(Default)]
pub struct DmaChannelCtrl(u16);
impl Debug;
u16;
dst_adj, _ : 6, 5;
src_adj, _ : 8, 7;
repeat, _ : 9;
is_32bit, _: 10;
timing, _: 13, 12;
is_triggering_irq, _: 14;
is_enabled, set_enabled: 15;
}

View file

@ -1,42 +1,19 @@
/// Struct containing everything /// Struct containing everything
///
use std::cell::RefCell;
use std::rc::Rc;
use super::arm7tdmi::{exception::Exception, Core, DecodedInstruction}; use super::arm7tdmi::{exception::Exception, Core, DecodedInstruction};
use super::cartridge::Cartridge; use super::cartridge::Cartridge;
use super::gpu::*; use super::gpu::*;
use super::interrupt::*; use super::interrupt::*;
use super::ioregs::IoRegs; use super::iodev::*;
use super::sysbus::SysBus; use super::sysbus::SysBus;
use super::timer::Timers;
use super::GBAResult; use super::GBAResult;
use super::SyncedIoDevice; use super::SyncedIoDevice;
use crate::backend::*; use crate::backend::*;
#[derive(Debug)]
pub struct IoDevices {
pub intc: InterruptController,
pub gpu: Gpu,
pub timers: Timers,
}
impl IoDevices {
pub fn new() -> IoDevices {
IoDevices {
intc: InterruptController::new(),
gpu: Gpu::new(),
timers: Timers::new(),
}
}
}
pub struct GameBoyAdvance { pub struct GameBoyAdvance {
backend: Box<EmulatorBackend>, backend: Box<dyn EmulatorBackend>,
pub cpu: Core, pub cpu: Core,
pub sysbus: SysBus, pub sysbus: Box<SysBus>,
pub io: Rc<RefCell<IoDevices>>,
} }
impl GameBoyAdvance { impl GameBoyAdvance {
@ -44,51 +21,63 @@ impl GameBoyAdvance {
cpu: Core, cpu: Core,
bios_rom: Vec<u8>, bios_rom: Vec<u8>,
gamepak: Cartridge, gamepak: Cartridge,
backend: Box<EmulatorBackend>, backend: Box<dyn EmulatorBackend>,
) -> GameBoyAdvance { ) -> GameBoyAdvance {
let io = Rc::new(RefCell::new(IoDevices::new())); let io = IoDevices::new();
let ioregs = IoRegs::new(io.clone());
let sysbus = SysBus::new(io.clone(), bios_rom, gamepak, ioregs);
GameBoyAdvance { GameBoyAdvance {
backend: backend, backend: backend,
cpu: cpu, cpu: cpu,
sysbus: sysbus, sysbus: Box::new(SysBus::new(io, bios_rom, gamepak)),
io: io.clone(),
} }
} }
pub fn frame(&mut self) { pub fn frame(&mut self) {
self.update_key_state(); self.update_key_state();
while self.io.borrow().gpu.state != GpuState::VBlank { while self.sysbus.io.gpu.state != GpuState::VBlank {
let cycles = self.emulate_cpu(); self.step_new();
self.emulate_peripherals(cycles);
} }
self.backend.render(self.io.borrow().gpu.get_framebuffer()); self.backend.render(self.sysbus.io.gpu.get_framebuffer());
while self.io.borrow().gpu.state == GpuState::VBlank { while self.sysbus.io.gpu.state == GpuState::VBlank {
let cycles = self.emulate_cpu(); self.step_new();
self.emulate_peripherals(cycles);
} }
} }
fn update_key_state(&mut self) { fn update_key_state(&mut self) {
self.sysbus.ioregs.keyinput = self.backend.get_key_state(); self.sysbus.io.keyinput = self.backend.get_key_state();
} }
pub fn emulate_cpu(&mut self) -> usize { // TODO deprecate
pub fn step(&mut self) -> GBAResult<DecodedInstruction> {
let previous_cycles = self.cpu.cycles; let previous_cycles = self.cpu.cycles;
self.cpu.step(&mut self.sysbus).unwrap(); let executed_insn = self.cpu.step_one(&mut self.sysbus)?;
self.cpu.cycles - previous_cycles let cycles = self.cpu.cycles - previous_cycles;
Ok(executed_insn)
} }
pub fn emulate_peripherals(&mut self, cycles: usize) { pub fn step_new(&mut self) {
let mut irqs = IrqBitmask(0); let mut irqs = IrqBitmask(0);
let mut io = self.io.borrow_mut(); let previous_cycles = self.cpu.cycles;
// // I hate myself for doing this, but rust left me no choice.
let io = unsafe {
let ptr = &mut *self.sysbus as *mut SysBus;
&mut (*ptr).io as &mut IoDevices
};
if !io.dmac.perform_work(&mut self.sysbus, &mut irqs) {
self.cpu.step(&mut self.sysbus).unwrap();
}
let cycles = self.cpu.cycles - previous_cycles;
io.timers.step(cycles, &mut self.sysbus, &mut irqs); io.timers.step(cycles, &mut self.sysbus, &mut irqs);
io.gpu.step(cycles, &mut self.sysbus, &mut irqs); if let Some(new_gpu_state) = io.gpu.step(cycles, &mut self.sysbus, &mut irqs) {
match new_gpu_state {
GpuState::VBlank => io.dmac.notify_vblank(),
GpuState::HBlank => io.dmac.notify_hblank(),
_ => {}
}
}
if !self.cpu.cpsr.irq_disabled() { if !self.cpu.cpsr.irq_disabled() {
io.intc.request_irqs(irqs); io.intc.request_irqs(irqs);
@ -97,18 +86,4 @@ impl GameBoyAdvance {
} }
} }
} }
pub fn step(&mut self) -> GBAResult<DecodedInstruction> {
let previous_cycles = self.cpu.cycles;
let executed_insn = self.cpu.step_one(&mut self.sysbus)?;
let cycles = self.cpu.cycles - previous_cycles;
self.emulate_peripherals(cycles);
if self.io.borrow().gpu.state == GpuState::VBlank {
self.backend.render(self.io.borrow().gpu.get_framebuffer());
}
Ok(executed_insn)
}
} }

View file

@ -361,7 +361,7 @@ impl Gpu {
} }
fn scanline_aff_bg(&mut self, bg: usize, sb: &mut SysBus) { fn scanline_aff_bg(&mut self, bg: usize, sb: &mut SysBus) {
// TODO // TODO
} }
fn scanline_mode3(&mut self, bg: usize, sb: &mut SysBus) { fn scanline_mode3(&mut self, bg: usize, sb: &mut SysBus) {
@ -444,10 +444,14 @@ impl Gpu {
pub fn get_framebuffer(&self) -> &[u32] { pub fn get_framebuffer(&self) -> &[u32] {
&self.frame_buffer.0 &self.frame_buffer.0
} }
}
impl SyncedIoDevice for Gpu { // Returns the new gpu state
fn step(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) { pub fn step(
&mut self,
cycles: usize,
sb: &mut SysBus,
irqs: &mut IrqBitmask,
) -> Option<GpuState> {
self.cycles += cycles; self.cycles += cycles;
if self.dispstat.vcount_setting() != 0 { if self.dispstat.vcount_setting() != 0 {
@ -472,12 +476,14 @@ impl SyncedIoDevice for Gpu {
irqs.set_LCD_HBlank(true); irqs.set_LCD_HBlank(true);
}; };
self.state = HBlank; self.state = HBlank;
return Some(HBlank);
} else { } else {
self.dispstat.set_vblank(true); self.dispstat.set_vblank(true);
if self.dispstat.vblank_irq_enable() { if self.dispstat.vblank_irq_enable() {
irqs.set_LCD_VBlank(true); irqs.set_LCD_VBlank(true);
}; };
self.state = VBlank; self.state = VBlank;
return Some(VBlank);
}; };
} }
} }
@ -487,6 +493,7 @@ impl SyncedIoDevice for Gpu {
self.state = HDraw; self.state = HDraw;
self.dispstat.set_hblank(false); self.dispstat.set_hblank(false);
self.dispstat.set_vblank(false); self.dispstat.set_vblank(false);
return Some(HDraw);
} }
} }
VBlank => { VBlank => {
@ -497,9 +504,12 @@ impl SyncedIoDevice for Gpu {
self.dispstat.set_vblank(false); self.dispstat.set_vblank(false);
self.current_scanline = 0; self.current_scanline = 0;
self.render_scanline(sb); self.render_scanline(sb);
return Some(HDraw);
} }
} }
} }
return None;
} }
} }

View file

@ -1,28 +1,35 @@
use std::cell::RefCell;
use std::rc::Rc;
use super::arm7tdmi::{Addr, Bus}; use super::arm7tdmi::{Addr, Bus};
use super::gba::IoDevices; use super::dma::DmaController;
use super::gpu::regs::WindowFlags; use super::gpu::regs::WindowFlags;
use super::gpu::*;
use super::interrupt::InterruptController;
use super::keypad; use super::keypad;
use super::sysbus::BoxedMemory; use super::sysbus::BoxedMemory;
use super::timer::Timers;
use consts::*; use consts::*;
#[derive(Debug)] #[derive(Debug)]
pub struct IoRegs { pub struct IoDevices {
mem: BoxedMemory, pub intc: InterruptController,
pub io: Rc<RefCell<IoDevices>>, pub gpu: Gpu,
pub timers: Timers,
pub dmac: DmaController,
pub keyinput: u16, pub keyinput: u16,
pub post_boot_flag: bool, pub post_boot_flag: bool,
pub waitcnt: WaitControl, // TODO also implement 4000800 pub waitcnt: WaitControl, // TODO also implement 4000800
mem: BoxedMemory,
} }
impl IoRegs { impl IoDevices {
pub fn new(io: Rc<RefCell<IoDevices>>) -> IoRegs { pub fn new() -> IoDevices {
IoRegs { IoDevices {
gpu: Gpu::new(),
timers: Timers::new(),
dmac: DmaController::new(),
intc: InterruptController::new(),
mem: BoxedMemory::new(vec![0; 0x800].into_boxed_slice()), mem: BoxedMemory::new(vec![0; 0x800].into_boxed_slice()),
io: io,
post_boot_flag: false, post_boot_flag: false,
keyinput: keypad::KEYINPUT_ALL_RELEASED, keyinput: keypad::KEYINPUT_ALL_RELEASED,
waitcnt: WaitControl(0), waitcnt: WaitControl(0),
@ -30,14 +37,15 @@ impl IoRegs {
} }
} }
impl Bus for IoRegs { impl Bus for IoDevices {
fn read_32(&self, addr: Addr) -> u32 { fn read_32(&self, addr: Addr) -> u32 {
(self.read_16(addr + 2) as u32) << 16 | (self.read_16(addr) as u32) (self.read_16(addr + 2) as u32) << 16 | (self.read_16(addr) as u32)
} }
fn read_16(&self, addr: Addr) -> u16 { fn read_16(&self, addr: Addr) -> u16 {
let io = self.io.borrow(); let io = self;
match addr + IO_BASE { let io_addr = addr + IO_BASE;
match io_addr {
REG_DISPCNT => io.gpu.dispcnt.0, REG_DISPCNT => io.gpu.dispcnt.0,
REG_DISPSTAT => io.gpu.dispstat.0, REG_DISPSTAT => io.gpu.dispstat.0,
REG_VCOUNT => io.gpu.current_scanline as u16, REG_VCOUNT => io.gpu.current_scanline as u16,
@ -71,12 +79,24 @@ impl Bus for IoRegs {
REG_TM3CNT_L => io.timers[3].timer_data, REG_TM3CNT_L => io.timers[3].timer_data,
REG_TM3CNT_H => io.timers[3].timer_ctl.0, REG_TM3CNT_H => io.timers[3].timer_ctl.0,
REG_WAITCNT => self.waitcnt.0, REG_DMA0CNT_H => io.dmac.channels[0].ctrl.0,
REG_DMA1CNT_H => io.dmac.channels[1].ctrl.0,
REG_DMA2CNT_H => io.dmac.channels[2].ctrl.0,
REG_DMA3CNT_H => io.dmac.channels[3].ctrl.0,
REG_POSTFLG => self.post_boot_flag as u16, REG_WAITCNT => io.waitcnt.0,
REG_POSTFLG => io.post_boot_flag as u16,
REG_HALTCNT => 0, REG_HALTCNT => 0,
REG_KEYINPUT => self.keyinput as u16, REG_KEYINPUT => io.keyinput as u16,
_ => self.mem.read_16(addr), _ => {
println!(
"Unimplemented read from {:x} {}",
io_addr,
io_reg_string(io_addr)
);
io.mem.read_16(addr)
}
} }
} }
@ -95,8 +115,10 @@ impl Bus for IoRegs {
} }
fn write_16(&mut self, addr: Addr, value: u16) { fn write_16(&mut self, addr: Addr, value: u16) {
let mut io = self.io.borrow_mut(); let mut io = self;
match addr + IO_BASE { io.mem.write_16(addr, value);
let io_addr = addr + IO_BASE;
match io_addr {
REG_DISPCNT => io.gpu.dispcnt.0 = value, REG_DISPCNT => io.gpu.dispcnt.0 = value,
REG_DISPSTAT => io.gpu.dispstat.0 |= value & !3, REG_DISPSTAT => io.gpu.dispstat.0 |= value & !3,
REG_BG0CNT => io.gpu.bg[0].bgcnt.0 = value, REG_BG0CNT => io.gpu.bg[0].bgcnt.0 = value,
@ -192,18 +214,22 @@ impl Bus for IoRegs {
} }
REG_TM3CNT_H => io.timers[3].timer_ctl.0 = value, REG_TM3CNT_H => io.timers[3].timer_ctl.0 = value,
REG_WAITCNT => self.waitcnt.0 = value, DMA_BASE..=REG_DMA3CNT_H => {
let ofs = io_addr - DMA_BASE;
let channel_id = (ofs / 12) as usize;
io.dmac.write_16(channel_id, ofs % 12, value)
}
REG_POSTFLG => self.post_boot_flag = value != 0, REG_WAITCNT => io.waitcnt.0 = value,
REG_POSTFLG => io.post_boot_flag = value != 0,
REG_HALTCNT => {} REG_HALTCNT => {}
_ => { _ => {
let ioreg_addr = IO_BASE + addr;
println!( println!(
"Unimplemented write to {:x} {}", "Unimplemented write to {:x} {}",
ioreg_addr, io_addr,
io_reg_string(ioreg_addr) io_reg_string(io_addr)
); );
self.mem.write_16(addr, value);
} }
} }
} }
@ -301,6 +327,7 @@ pub mod consts {
pub const REG_WAVE_RAM: Addr = 0x0400_0090; // Channel 3 Wave Pattern RAM (2 banks!!) pub const REG_WAVE_RAM: Addr = 0x0400_0090; // Channel 3 Wave Pattern RAM (2 banks!!)
pub const REG_FIFO_A: Addr = 0x0400_00A0; // 4 W Channel A FIFO, Data 0-3 pub const REG_FIFO_A: Addr = 0x0400_00A0; // 4 W Channel A FIFO, Data 0-3
pub const REG_FIFO_B: Addr = 0x0400_00A4; // 4 W Channel B FIFO, Data 0-3 pub const REG_FIFO_B: Addr = 0x0400_00A4; // 4 W Channel B FIFO, Data 0-3
pub const DMA_BASE: Addr = REG_DMA0SAD;
pub const REG_DMA0SAD: Addr = 0x0400_00B0; // 4 W DMA 0 Source Address pub const REG_DMA0SAD: Addr = 0x0400_00B0; // 4 W DMA 0 Source Address
pub const REG_DMA0DAD: Addr = 0x0400_00B4; // 4 W DMA 0 Destination Address pub const REG_DMA0DAD: Addr = 0x0400_00B4; // 4 W DMA 0 Destination Address
pub const REG_DMA0CNT_L: Addr = 0x0400_00B8; // 2 W DMA 0 Word Count pub const REG_DMA0CNT_L: Addr = 0x0400_00B8; // 2 W DMA 0 Word Count

View file

@ -4,7 +4,7 @@ pub mod gpu;
pub mod sysbus; pub mod sysbus;
pub use sysbus::SysBus; pub use sysbus::SysBus;
pub mod interrupt; pub mod interrupt;
pub mod ioregs; pub mod iodev;
pub use interrupt::Interrupt; pub use interrupt::Interrupt;
pub use interrupt::IrqBitmask; pub use interrupt::IrqBitmask;
pub mod gba; pub mod gba;

View file

@ -1,15 +1,13 @@
use std::cell::RefCell;
use std::fmt; use std::fmt;
use std::ops::Add; use std::ops::Add;
use std::rc::Rc;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use super::arm7tdmi::bus::Bus; use super::arm7tdmi::bus::Bus;
use super::arm7tdmi::Addr; use super::arm7tdmi::Addr;
use super::gba::IoDevices; use super::cartridge::Cartridge;
use super::gpu::GpuState; use super::gpu::GpuState;
use super::{cartridge::Cartridge, ioregs::IoRegs}; use super::iodev::IoDevices;
const VIDEO_RAM_SIZE: usize = 128 * 1024; const VIDEO_RAM_SIZE: usize = 128 * 1024;
const WORK_RAM_SIZE: usize = 256 * 1024; const WORK_RAM_SIZE: usize = 256 * 1024;
@ -141,13 +139,11 @@ impl Bus for DummyBus {
#[derive(Debug)] #[derive(Debug)]
pub struct SysBus { pub struct SysBus {
pub io: Rc<RefCell<IoDevices>>, pub io: IoDevices,
bios: BoxedMemory, bios: BoxedMemory,
onboard_work_ram: BoxedMemory, onboard_work_ram: BoxedMemory,
internal_work_ram: BoxedMemory, internal_work_ram: BoxedMemory,
/// Currently model the IOMem as regular buffer, later make it into something more sophisticated.
pub ioregs: IoRegs,
pub palette_ram: BoxedMemory, pub palette_ram: BoxedMemory,
pub vram: BoxedMemory, pub vram: BoxedMemory,
pub oam: BoxedMemory, pub oam: BoxedMemory,
@ -156,19 +152,13 @@ pub struct SysBus {
} }
impl SysBus { impl SysBus {
pub fn new( pub fn new(io: IoDevices, bios_rom: Vec<u8>, gamepak: Cartridge) -> SysBus {
io: Rc<RefCell<IoDevices>>,
bios_rom: Vec<u8>,
gamepak: Cartridge,
ioregs: IoRegs,
) -> SysBus {
SysBus { SysBus {
io: io, io: io,
bios: BoxedMemory::new(bios_rom.into_boxed_slice()), bios: BoxedMemory::new(bios_rom.into_boxed_slice()),
onboard_work_ram: BoxedMemory::new(vec![0; WORK_RAM_SIZE].into_boxed_slice()), onboard_work_ram: BoxedMemory::new(vec![0; WORK_RAM_SIZE].into_boxed_slice()),
internal_work_ram: BoxedMemory::new(vec![0; INTERNAL_RAM_SIZE].into_boxed_slice()), internal_work_ram: BoxedMemory::new(vec![0; INTERNAL_RAM_SIZE].into_boxed_slice()),
ioregs: ioregs,
palette_ram: BoxedMemory::new(vec![0; PALETTE_RAM_SIZE].into_boxed_slice()), palette_ram: BoxedMemory::new(vec![0; PALETTE_RAM_SIZE].into_boxed_slice()),
vram: BoxedMemory::new(vec![0; VIDEO_RAM_SIZE].into_boxed_slice()), vram: BoxedMemory::new(vec![0; VIDEO_RAM_SIZE].into_boxed_slice()),
oam: BoxedMemory::new(vec![0; OAM_SIZE].into_boxed_slice()), oam: BoxedMemory::new(vec![0; OAM_SIZE].into_boxed_slice()),
@ -177,13 +167,13 @@ impl SysBus {
} }
} }
fn map(&self, addr: Addr) -> (&Bus, Addr) { fn map(&self, addr: Addr) -> (&dyn Bus, Addr) {
let ofs = addr & 0x00ff_ffff; let ofs = addr & 0x00ff_ffff;
match addr & 0xff000000 { match addr & 0xff000000 {
BIOS_ADDR => (&self.bios, ofs), BIOS_ADDR => (&self.bios, ofs),
EWRAM_ADDR => (&self.onboard_work_ram, ofs & 0x3_ffff), EWRAM_ADDR => (&self.onboard_work_ram, ofs & 0x3_ffff),
IWRAM_ADDR => (&self.internal_work_ram, ofs & 0x7fff), IWRAM_ADDR => (&self.internal_work_ram, ofs & 0x7fff),
IOMEM_ADDR => (&self.ioregs, { IOMEM_ADDR => (&self.io, {
if ofs & 0xffff == 0x8000 { if ofs & 0xffff == 0x8000 {
0x800 0x800
} else { } else {
@ -205,13 +195,13 @@ impl SysBus {
} }
/// TODO proc-macro for generating this function /// TODO proc-macro for generating this function
fn map_mut(&mut self, addr: Addr) -> (&mut Bus, Addr) { fn map_mut(&mut self, addr: Addr) -> (&mut dyn Bus, Addr) {
let ofs = addr & 0x00ff_ffff; let ofs = addr & 0x00ff_ffff;
match addr & 0xff000000 { match addr & 0xff000000 {
BIOS_ADDR => (&mut self.bios, ofs), BIOS_ADDR => (&mut self.bios, ofs),
EWRAM_ADDR => (&mut self.onboard_work_ram, ofs & 0x3_ffff), EWRAM_ADDR => (&mut self.onboard_work_ram, ofs & 0x3_ffff),
IWRAM_ADDR => (&mut self.internal_work_ram, ofs & 0x7fff), IWRAM_ADDR => (&mut self.internal_work_ram, ofs & 0x7fff),
IOMEM_ADDR => (&mut self.ioregs, { IOMEM_ADDR => (&mut self.io, {
if ofs & 0xffff == 0x8000 { if ofs & 0xffff == 0x8000 {
0x800 0x800
} else { } else {
@ -249,24 +239,24 @@ impl SysBus {
MemoryAccessWidth::MemoryAccess32 => cycles += 2, MemoryAccessWidth::MemoryAccess32 => cycles += 2,
_ => cycles += 1, _ => cycles += 1,
} }
if self.io.borrow().gpu.state == GpuState::HDraw { if self.io.gpu.state == GpuState::HDraw {
cycles += 1; cycles += 1;
} }
} }
GAMEPAK_WS0_ADDR => match access.0 { GAMEPAK_WS0_ADDR => match access.0 {
MemoryAccessType::NonSeq => match access.1 { MemoryAccessType::NonSeq => match access.1 {
MemoryAccessWidth::MemoryAccess32 => { MemoryAccessWidth::MemoryAccess32 => {
cycles += nonseq_cycles[self.ioregs.waitcnt.ws0_first_access() as usize]; cycles += nonseq_cycles[self.io.waitcnt.ws0_first_access() as usize];
cycles += seq_cycles[self.ioregs.waitcnt.ws0_second_access() as usize]; cycles += seq_cycles[self.io.waitcnt.ws0_second_access() as usize];
} }
_ => { _ => {
cycles += nonseq_cycles[self.ioregs.waitcnt.ws0_first_access() as usize]; cycles += nonseq_cycles[self.io.waitcnt.ws0_first_access() as usize];
} }
}, },
MemoryAccessType::Seq => { MemoryAccessType::Seq => {
cycles += seq_cycles[self.ioregs.waitcnt.ws0_second_access() as usize]; cycles += seq_cycles[self.io.waitcnt.ws0_second_access() as usize];
if access.1 == MemoryAccessWidth::MemoryAccess32 { if access.1 == MemoryAccessWidth::MemoryAccess32 {
cycles += seq_cycles[self.ioregs.waitcnt.ws0_second_access() as usize]; cycles += seq_cycles[self.io.waitcnt.ws0_second_access() as usize];
} }
} }
}, },

View file

@ -6,7 +6,7 @@ use crate::core::GBAError;
use crate::disass::Disassembler; use crate::disass::Disassembler;
use super::palette_view::create_palette_view; use super::palette_view::create_palette_view;
use super::tile_view::create_tile_view; // use super::tile_view::create_tile_view;
use super::{parser::Value, Debugger, DebuggerError, DebuggerResult}; use super::{parser::Value, Debugger, DebuggerError, DebuggerResult};
use ansi_term::Colour; use ansi_term::Colour;
@ -32,7 +32,7 @@ pub enum Command {
AddBreakpoint(Addr), AddBreakpoint(Addr),
DelBreakpoint(Addr), DelBreakpoint(Addr),
PaletteView, PaletteView,
TileView(u32), // TileView(u32),
ClearBreakpoints, ClearBreakpoints,
ListBreakpoints, ListBreakpoints,
Reset, Reset,
@ -44,7 +44,7 @@ impl Command {
use Command::*; use Command::*;
match *self { match *self {
Info => println!("{}", debugger.gba.cpu), Info => println!("{}", debugger.gba.cpu),
DisplayInfo => println!("GPU: {:#?}", debugger.gba.io.borrow().gpu), DisplayInfo => { /*println!("GPU: {:#?}", debugger.gba.sysbus.io.gpu)*/ }
Step(count) => { Step(count) => {
for _ in 0..count { for _ in 0..count {
match debugger.gba.step() { match debugger.gba.step() {
@ -154,7 +154,7 @@ impl Command {
} }
} }
PaletteView => create_palette_view(&debugger.gba.sysbus.palette_ram.mem), PaletteView => create_palette_view(&debugger.gba.sysbus.palette_ram.mem),
TileView(bg) => create_tile_view(bg, &debugger.gba), // TileView(bg) => create_tile_view(bg, &debugger.gba),
Reset => { Reset => {
println!("resetting cpu..."); println!("resetting cpu...");
debugger.gba.cpu.reset(&mut debugger.gba.sysbus); debugger.gba.cpu.reset(&mut debugger.gba.sysbus);
@ -297,13 +297,13 @@ impl Debugger {
))), ))),
}, },
"palette-view" => Ok(Command::PaletteView), "palette-view" => Ok(Command::PaletteView),
"tiles" => { // "tiles" => {
if args.len() != 1 { // if args.len() != 1 {
return Err(DebuggerError::InvalidCommandFormat("tile <bg>".to_string())); // return Err(DebuggerError::InvalidCommandFormat("tile <bg>".to_string()));
} // }
let bg = self.val_number(&args[0])?; // let bg = self.val_number(&args[0])?;
Ok(Command::TileView(bg)) // Ok(Command::TileView(bg))
} // }
"bl" => Ok(Command::ListBreakpoints), "bl" => Ok(Command::ListBreakpoints),
"q" | "quit" => Ok(Command::Quit), "q" | "quit" => Ok(Command::Quit),
"r" | "reset" => Ok(Command::Reset), "r" | "reset" => Ok(Command::Reset),

View file

@ -1,107 +1,107 @@
use std::time::Duration; // use std::time::Duration;
use sdl2::event::Event; // use sdl2::event::Event;
use sdl2::pixels::Color; // use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; // use sdl2::rect::{Point, Rect};
use sdl2::render::Canvas; // use sdl2::render::Canvas;
use crate::core::gba::GameBoyAdvance; // use crate::core::gba::GameBoyAdvance;
use crate::core::gpu::PixelFormat; // use crate::core::gpu::PixelFormat;
fn draw_tile( // fn draw_tile(
gba: &GameBoyAdvance, // gba: &GameBoyAdvance,
tile_addr: u32, // tile_addr: u32,
pixel_format: PixelFormat, // pixel_format: PixelFormat,
p: Point, // p: Point,
canvas: &mut Canvas<sdl2::video::Window>, // canvas: &mut Canvas<sdl2::video::Window>,
) { // ) {
let io = gba.io.borrow(); // let io = &mut gba.sysbus.io;
for y in 0..8 { // for y in 0..8 {
for x in 0..8 { // for x in 0..8 {
let index = io // let index = io
.gpu // .gpu
.read_pixel_index(&gba.sysbus, tile_addr, x, y, pixel_format); // .read_pixel_index(&gba.sysbus, tile_addr, x, y, pixel_format);
let color = io.gpu.get_palette_color(&gba.sysbus, index as u32, 0, 0); // let color = io.gpu.get_palette_color(&gba.sysbus, index as u32, 0, 0);
canvas.set_draw_color(Color::RGB( // canvas.set_draw_color(Color::RGB(
(color.r() as u8) << 3, // (color.r() as u8) << 3,
(color.g() as u8) << 3, // (color.g() as u8) << 3,
(color.b() as u8) << 3, // (color.b() as u8) << 3,
)); // ));
canvas.draw_point(p.offset(x as i32, y as i32)).unwrap(); // canvas.draw_point(p.offset(x as i32, y as i32)).unwrap();
} // }
} // }
} // }
const TILESET_INITIAL_X: i32 = 0x20; // const TILESET_INITIAL_X: i32 = 0x20;
const TILESET_INITIAL_Y: i32 = 0x20; // const TILESET_INITIAL_Y: i32 = 0x20;
pub fn create_tile_view(bg: u32, gba: &GameBoyAdvance) { // pub fn create_tile_view(bg: u32, gba: &GameBoyAdvance) {
let sdl_context = sdl2::init().unwrap(); // let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap(); // let video_subsystem = sdl_context.video().unwrap();
let window = video_subsystem // let window = video_subsystem
.window("PaletteView", 512, 512) // .window("PaletteView", 512, 512)
.position_centered() // .position_centered()
.build() // .build()
.unwrap(); // .unwrap();
let mut canvas = window.into_canvas().build().unwrap(); // let mut canvas = window.into_canvas().build().unwrap();
let bgcnt = gba.io.borrow().gpu.bg[bg as usize].bgcnt.clone(); // let bgcnt = gba.sysbus.io.gpu.bg[bg as usize].bgcnt.clone();
let (tile_size, pixel_format) = bgcnt.tile_format(); // let (tile_size, pixel_format) = bgcnt.tile_format();
let tileset_addr = bgcnt.char_block(); // let tileset_addr = bgcnt.char_block();
let tilemap_addr = bgcnt.screen_block(); // let tilemap_addr = bgcnt.screen_block();
let tiles_per_row = 32; // let tiles_per_row = 32;
let num_tiles = 0x4000 / tile_size; // let num_tiles = 0x4000 / tile_size;
println!("tileset: {:#x}, tilemap: {:#x}", tileset_addr, tilemap_addr); // println!("tileset: {:#x}, tilemap: {:#x}", tileset_addr, tilemap_addr);
let mut event_pump = sdl_context.event_pump().unwrap(); // let mut event_pump = sdl_context.event_pump().unwrap();
'running: loop { // 'running: loop {
for event in event_pump.poll_iter() { // for event in event_pump.poll_iter() {
match event { // match event {
Event::Quit { .. } => break 'running, // Event::Quit { .. } => break 'running,
Event::MouseButtonDown { x, y, .. } => { // Event::MouseButtonDown { x, y, .. } => {
let click_point = Point::new(x, y); // let click_point = Point::new(x, y);
let mut tile_x = TILESET_INITIAL_X; // let mut tile_x = TILESET_INITIAL_X;
let mut tile_y = TILESET_INITIAL_Y; // let mut tile_y = TILESET_INITIAL_Y;
for t in 0..num_tiles { // for t in 0..num_tiles {
let tile_addr = tileset_addr + t * tile_size; // let tile_addr = tileset_addr + t * tile_size;
if t != 0 && t % tiles_per_row == 0 { // if t != 0 && t % tiles_per_row == 0 {
tile_y += 10; // tile_y += 10;
tile_x = TILESET_INITIAL_Y; // tile_x = TILESET_INITIAL_Y;
} // }
tile_x += 10; // tile_x += 10;
if Rect::new(tile_x, tile_y, 8, 8).contains_point(click_point) { // if Rect::new(tile_x, tile_y, 8, 8).contains_point(click_point) {
println!("tile #{:#x}, addr={:#x}", t, tile_addr); // println!("tile #{:#x}, addr={:#x}", t, tile_addr);
} // }
} // }
} // }
_ => {} // _ => {}
} // }
} // }
canvas.set_draw_color(Color::RGB(00, 00, 00)); // canvas.set_draw_color(Color::RGB(00, 00, 00));
canvas.clear(); // canvas.clear();
let mut tile_x = TILESET_INITIAL_X; // let mut tile_x = TILESET_INITIAL_X;
let mut tile_y = TILESET_INITIAL_Y; // let mut tile_y = TILESET_INITIAL_Y;
for t in 0..num_tiles { // for t in 0..num_tiles {
let tile_addr = tileset_addr + t * tile_size; // let tile_addr = tileset_addr + t * tile_size;
if t != 0 && t % tiles_per_row == 0 { // if t != 0 && t % tiles_per_row == 0 {
tile_y += 10; // tile_y += 10;
tile_x = TILESET_INITIAL_Y; // tile_x = TILESET_INITIAL_Y;
} // }
tile_x += 10; // tile_x += 10;
draw_tile( // draw_tile(
gba, // gba,
tile_addr, // tile_addr,
pixel_format, // pixel_format,
Point::from((tile_x, tile_y)), // Point::from((tile_x, tile_y)),
&mut canvas, // &mut canvas,
); // );
} // }
canvas.present(); // canvas.present();
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)); // ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
} // }
} // }