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:
parent
3a1d5c10ce
commit
c78a111ad4
264
src/core/dma.rs
264
src/core/dma.rs
|
@ -1,74 +1,210 @@
|
|||
// use super::arm7tdmi::{Addr, Bus};
|
||||
// use super::ioregs::consts::*;
|
||||
// use super::sysbus::SysBus;
|
||||
// use super::{EmuIoDev, Interrupt};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
// #[allow(non_camel_case_types)]
|
||||
// #[derive(Debug)]
|
||||
// pub struct DmaChannel {
|
||||
// src_ioreg: Addr, /* Source Address register */
|
||||
// dst_ioreg: Addr, /* Destination Address register */
|
||||
// wc_ioreg: Addr, /* Word Count 14bit */
|
||||
// }
|
||||
use super::arm7tdmi::{Addr, Bus};
|
||||
use super::sysbus::SysBus;
|
||||
use super::{Interrupt, IrqBitmask, SyncedIoDevice};
|
||||
|
||||
// #[derive(Debug, Primitive)]
|
||||
// enum DmaAddrControl {
|
||||
// Increment = 0,
|
||||
// Decrement = 1,
|
||||
// Fixed = 2,
|
||||
// IncrementReloadProhibited = 3,
|
||||
// }
|
||||
use num::FromPrimitive;
|
||||
|
||||
// #[derive(Debug)]
|
||||
// enum DmaTransferType {
|
||||
// Xfer16bit,
|
||||
// Xfer32bit,
|
||||
// }
|
||||
#[derive(Debug)]
|
||||
enum DmaTransferType {
|
||||
Xfer16bit,
|
||||
Xfer32bit,
|
||||
}
|
||||
|
||||
// #[derive(Debug, Primitive)]
|
||||
// enum DmaStartTiming {
|
||||
// Immediately = 0,
|
||||
// VBlank = 1,
|
||||
// HBlank = 2,
|
||||
// Special = 3,
|
||||
// }
|
||||
#[derive(Debug)]
|
||||
pub struct DmaChannel {
|
||||
id: usize,
|
||||
|
||||
// #[derive(Debug)]
|
||||
// struct DmaControl {
|
||||
// dst_addr_ctl: DmaAddrControl,
|
||||
// src_addr_ctl: DmaAddrControl,
|
||||
// repeat: bool,
|
||||
// xfer: DmaTransferType,
|
||||
// start_timing: DmaStartTiming,
|
||||
// irq_upon_end_of_wc: bool,
|
||||
// enable: bool,
|
||||
// }
|
||||
pub src: u32,
|
||||
pub dst: u32,
|
||||
pub wc: u32,
|
||||
pub ctrl: DmaChannelCtrl,
|
||||
|
||||
// impl DmaChannel {
|
||||
// pub fn new(src_ioreg: Addr, dst_ioreg: Addr, wc_ioreg: Addr) -> DmaChannel {
|
||||
// DmaChannel {
|
||||
// src_ioreg,
|
||||
// dst_ioreg,
|
||||
// wc_ioreg,
|
||||
// }
|
||||
// }
|
||||
running: bool,
|
||||
cycles: usize,
|
||||
start_cycles: usize,
|
||||
irq: Interrupt,
|
||||
}
|
||||
|
||||
// // fn src_addr(&self, sysbus: &SysBus) -> Addr {
|
||||
// // sysbus.ioregs.read_32(self.src_ioreg - IO_BASE) as Addr
|
||||
// // }
|
||||
impl DmaChannel {
|
||||
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 {
|
||||
// // sysbus.ioregs.read_32(self.dst_ioreg - IO_BASE) as Addr
|
||||
// // }
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.running
|
||||
}
|
||||
|
||||
// // fn word_count(&self, sysbus: &SysBus) -> usize {
|
||||
// // sysbus.ioregs.read_reg(self.wc_ioreg) as usize
|
||||
// // }
|
||||
// }
|
||||
pub fn write_src_low(&mut self, low: u16) {
|
||||
let src = self.src;
|
||||
self.src = (src & 0xffff0000) | (low as u32);
|
||||
}
|
||||
|
||||
// impl EmuIoDev for DmaChannel {
|
||||
// fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) {
|
||||
// // TODO
|
||||
// (0, None)
|
||||
// }
|
||||
// }
|
||||
pub fn write_src_high(&mut self, high: u16) {
|
||||
let src = self.src;
|
||||
let high = high as u32;
|
||||
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;
|
||||
}
|
||||
|
|
103
src/core/gba.rs
103
src/core/gba.rs
|
@ -1,42 +1,19 @@
|
|||
/// Struct containing everything
|
||||
///
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::arm7tdmi::{exception::Exception, Core, DecodedInstruction};
|
||||
use super::cartridge::Cartridge;
|
||||
use super::gpu::*;
|
||||
use super::interrupt::*;
|
||||
use super::ioregs::IoRegs;
|
||||
use super::iodev::*;
|
||||
use super::sysbus::SysBus;
|
||||
use super::timer::Timers;
|
||||
|
||||
use super::GBAResult;
|
||||
use super::SyncedIoDevice;
|
||||
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 {
|
||||
backend: Box<EmulatorBackend>,
|
||||
backend: Box<dyn EmulatorBackend>,
|
||||
pub cpu: Core,
|
||||
pub sysbus: SysBus,
|
||||
|
||||
pub io: Rc<RefCell<IoDevices>>,
|
||||
pub sysbus: Box<SysBus>,
|
||||
}
|
||||
|
||||
impl GameBoyAdvance {
|
||||
|
@ -44,51 +21,63 @@ impl GameBoyAdvance {
|
|||
cpu: Core,
|
||||
bios_rom: Vec<u8>,
|
||||
gamepak: Cartridge,
|
||||
backend: Box<EmulatorBackend>,
|
||||
backend: Box<dyn EmulatorBackend>,
|
||||
) -> GameBoyAdvance {
|
||||
let io = Rc::new(RefCell::new(IoDevices::new()));
|
||||
|
||||
let ioregs = IoRegs::new(io.clone());
|
||||
let sysbus = SysBus::new(io.clone(), bios_rom, gamepak, ioregs);
|
||||
|
||||
let io = IoDevices::new();
|
||||
GameBoyAdvance {
|
||||
backend: backend,
|
||||
cpu: cpu,
|
||||
sysbus: sysbus,
|
||||
|
||||
io: io.clone(),
|
||||
sysbus: Box::new(SysBus::new(io, bios_rom, gamepak)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame(&mut self) {
|
||||
self.update_key_state();
|
||||
while self.io.borrow().gpu.state != GpuState::VBlank {
|
||||
let cycles = self.emulate_cpu();
|
||||
self.emulate_peripherals(cycles);
|
||||
while self.sysbus.io.gpu.state != GpuState::VBlank {
|
||||
self.step_new();
|
||||
}
|
||||
self.backend.render(self.io.borrow().gpu.get_framebuffer());
|
||||
while self.io.borrow().gpu.state == GpuState::VBlank {
|
||||
let cycles = self.emulate_cpu();
|
||||
self.emulate_peripherals(cycles);
|
||||
self.backend.render(self.sysbus.io.gpu.get_framebuffer());
|
||||
while self.sysbus.io.gpu.state == GpuState::VBlank {
|
||||
self.step_new();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
self.cpu.step(&mut self.sysbus).unwrap();
|
||||
self.cpu.cycles - previous_cycles
|
||||
let executed_insn = self.cpu.step_one(&mut self.sysbus)?;
|
||||
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 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.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() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -361,7 +361,7 @@ impl Gpu {
|
|||
}
|
||||
|
||||
fn scanline_aff_bg(&mut self, bg: usize, sb: &mut SysBus) {
|
||||
// TODO
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn scanline_mode3(&mut self, bg: usize, sb: &mut SysBus) {
|
||||
|
@ -444,10 +444,14 @@ impl Gpu {
|
|||
pub fn get_framebuffer(&self) -> &[u32] {
|
||||
&self.frame_buffer.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncedIoDevice for Gpu {
|
||||
fn step(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
||||
// Returns the new gpu state
|
||||
pub fn step(
|
||||
&mut self,
|
||||
cycles: usize,
|
||||
sb: &mut SysBus,
|
||||
irqs: &mut IrqBitmask,
|
||||
) -> Option<GpuState> {
|
||||
self.cycles += cycles;
|
||||
|
||||
if self.dispstat.vcount_setting() != 0 {
|
||||
|
@ -472,12 +476,14 @@ impl SyncedIoDevice for Gpu {
|
|||
irqs.set_LCD_HBlank(true);
|
||||
};
|
||||
self.state = HBlank;
|
||||
return Some(HBlank);
|
||||
} else {
|
||||
self.dispstat.set_vblank(true);
|
||||
if self.dispstat.vblank_irq_enable() {
|
||||
irqs.set_LCD_VBlank(true);
|
||||
};
|
||||
self.state = VBlank;
|
||||
return Some(VBlank);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -487,6 +493,7 @@ impl SyncedIoDevice for Gpu {
|
|||
self.state = HDraw;
|
||||
self.dispstat.set_hblank(false);
|
||||
self.dispstat.set_vblank(false);
|
||||
return Some(HDraw);
|
||||
}
|
||||
}
|
||||
VBlank => {
|
||||
|
@ -497,9 +504,12 @@ impl SyncedIoDevice for Gpu {
|
|||
self.dispstat.set_vblank(false);
|
||||
self.current_scanline = 0;
|
||||
self.render_scanline(sb);
|
||||
return Some(HDraw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +1,35 @@
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::arm7tdmi::{Addr, Bus};
|
||||
use super::gba::IoDevices;
|
||||
use super::dma::DmaController;
|
||||
use super::gpu::regs::WindowFlags;
|
||||
use super::gpu::*;
|
||||
use super::interrupt::InterruptController;
|
||||
use super::keypad;
|
||||
use super::sysbus::BoxedMemory;
|
||||
use super::timer::Timers;
|
||||
|
||||
use consts::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IoRegs {
|
||||
mem: BoxedMemory,
|
||||
pub io: Rc<RefCell<IoDevices>>,
|
||||
pub struct IoDevices {
|
||||
pub intc: InterruptController,
|
||||
pub gpu: Gpu,
|
||||
pub timers: Timers,
|
||||
pub dmac: DmaController,
|
||||
pub keyinput: u16,
|
||||
pub post_boot_flag: bool,
|
||||
pub waitcnt: WaitControl, // TODO also implement 4000800
|
||||
|
||||
mem: BoxedMemory,
|
||||
}
|
||||
|
||||
impl IoRegs {
|
||||
pub fn new(io: Rc<RefCell<IoDevices>>) -> IoRegs {
|
||||
IoRegs {
|
||||
impl IoDevices {
|
||||
pub fn new() -> IoDevices {
|
||||
IoDevices {
|
||||
gpu: Gpu::new(),
|
||||
timers: Timers::new(),
|
||||
dmac: DmaController::new(),
|
||||
intc: InterruptController::new(),
|
||||
mem: BoxedMemory::new(vec![0; 0x800].into_boxed_slice()),
|
||||
io: io,
|
||||
post_boot_flag: false,
|
||||
keyinput: keypad::KEYINPUT_ALL_RELEASED,
|
||||
waitcnt: WaitControl(0),
|
||||
|
@ -30,14 +37,15 @@ impl IoRegs {
|
|||
}
|
||||
}
|
||||
|
||||
impl Bus for IoRegs {
|
||||
impl Bus for IoDevices {
|
||||
fn read_32(&self, addr: Addr) -> u32 {
|
||||
(self.read_16(addr + 2) as u32) << 16 | (self.read_16(addr) as u32)
|
||||
}
|
||||
|
||||
fn read_16(&self, addr: Addr) -> u16 {
|
||||
let io = self.io.borrow();
|
||||
match addr + IO_BASE {
|
||||
let io = self;
|
||||
let io_addr = addr + IO_BASE;
|
||||
match io_addr {
|
||||
REG_DISPCNT => io.gpu.dispcnt.0,
|
||||
REG_DISPSTAT => io.gpu.dispstat.0,
|
||||
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_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_KEYINPUT => self.keyinput as u16,
|
||||
_ => self.mem.read_16(addr),
|
||||
REG_KEYINPUT => io.keyinput as u16,
|
||||
_ => {
|
||||
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) {
|
||||
let mut io = self.io.borrow_mut();
|
||||
match addr + IO_BASE {
|
||||
let mut io = self;
|
||||
io.mem.write_16(addr, value);
|
||||
let io_addr = addr + IO_BASE;
|
||||
match io_addr {
|
||||
REG_DISPCNT => io.gpu.dispcnt.0 = value,
|
||||
REG_DISPSTAT => io.gpu.dispstat.0 |= value & !3,
|
||||
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_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 => {}
|
||||
_ => {
|
||||
let ioreg_addr = IO_BASE + addr;
|
||||
println!(
|
||||
"Unimplemented write to {:x} {}",
|
||||
ioreg_addr,
|
||||
io_reg_string(ioreg_addr)
|
||||
io_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_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 DMA_BASE: Addr = REG_DMA0SAD;
|
||||
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_DMA0CNT_L: Addr = 0x0400_00B8; // 2 W DMA 0 Word Count
|
|
@ -4,7 +4,7 @@ pub mod gpu;
|
|||
pub mod sysbus;
|
||||
pub use sysbus::SysBus;
|
||||
pub mod interrupt;
|
||||
pub mod ioregs;
|
||||
pub mod iodev;
|
||||
pub use interrupt::Interrupt;
|
||||
pub use interrupt::IrqBitmask;
|
||||
pub mod gba;
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::ops::Add;
|
||||
use std::rc::Rc;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use super::arm7tdmi::bus::Bus;
|
||||
use super::arm7tdmi::Addr;
|
||||
use super::gba::IoDevices;
|
||||
use super::cartridge::Cartridge;
|
||||
use super::gpu::GpuState;
|
||||
use super::{cartridge::Cartridge, ioregs::IoRegs};
|
||||
use super::iodev::IoDevices;
|
||||
|
||||
const VIDEO_RAM_SIZE: usize = 128 * 1024;
|
||||
const WORK_RAM_SIZE: usize = 256 * 1024;
|
||||
|
@ -141,13 +139,11 @@ impl Bus for DummyBus {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct SysBus {
|
||||
pub io: Rc<RefCell<IoDevices>>,
|
||||
pub io: IoDevices,
|
||||
|
||||
bios: BoxedMemory,
|
||||
onboard_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 vram: BoxedMemory,
|
||||
pub oam: BoxedMemory,
|
||||
|
@ -156,19 +152,13 @@ pub struct SysBus {
|
|||
}
|
||||
|
||||
impl SysBus {
|
||||
pub fn new(
|
||||
io: Rc<RefCell<IoDevices>>,
|
||||
bios_rom: Vec<u8>,
|
||||
gamepak: Cartridge,
|
||||
ioregs: IoRegs,
|
||||
) -> SysBus {
|
||||
pub fn new(io: IoDevices, bios_rom: Vec<u8>, gamepak: Cartridge) -> SysBus {
|
||||
SysBus {
|
||||
io: io,
|
||||
|
||||
bios: BoxedMemory::new(bios_rom.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()),
|
||||
ioregs: ioregs,
|
||||
palette_ram: BoxedMemory::new(vec![0; PALETTE_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()),
|
||||
|
@ -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;
|
||||
match addr & 0xff000000 {
|
||||
BIOS_ADDR => (&self.bios, ofs),
|
||||
EWRAM_ADDR => (&self.onboard_work_ram, ofs & 0x3_ffff),
|
||||
IWRAM_ADDR => (&self.internal_work_ram, ofs & 0x7fff),
|
||||
IOMEM_ADDR => (&self.ioregs, {
|
||||
IOMEM_ADDR => (&self.io, {
|
||||
if ofs & 0xffff == 0x8000 {
|
||||
0x800
|
||||
} else {
|
||||
|
@ -205,13 +195,13 @@ impl SysBus {
|
|||
}
|
||||
|
||||
/// 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;
|
||||
match addr & 0xff000000 {
|
||||
BIOS_ADDR => (&mut self.bios, ofs),
|
||||
EWRAM_ADDR => (&mut self.onboard_work_ram, ofs & 0x3_ffff),
|
||||
IWRAM_ADDR => (&mut self.internal_work_ram, ofs & 0x7fff),
|
||||
IOMEM_ADDR => (&mut self.ioregs, {
|
||||
IOMEM_ADDR => (&mut self.io, {
|
||||
if ofs & 0xffff == 0x8000 {
|
||||
0x800
|
||||
} else {
|
||||
|
@ -249,24 +239,24 @@ impl SysBus {
|
|||
MemoryAccessWidth::MemoryAccess32 => cycles += 2,
|
||||
_ => cycles += 1,
|
||||
}
|
||||
if self.io.borrow().gpu.state == GpuState::HDraw {
|
||||
if self.io.gpu.state == GpuState::HDraw {
|
||||
cycles += 1;
|
||||
}
|
||||
}
|
||||
GAMEPAK_WS0_ADDR => match access.0 {
|
||||
MemoryAccessType::NonSeq => match access.1 {
|
||||
MemoryAccessWidth::MemoryAccess32 => {
|
||||
cycles += nonseq_cycles[self.ioregs.waitcnt.ws0_first_access() as usize];
|
||||
cycles += seq_cycles[self.ioregs.waitcnt.ws0_second_access() as usize];
|
||||
cycles += nonseq_cycles[self.io.waitcnt.ws0_first_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 => {
|
||||
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 {
|
||||
cycles += seq_cycles[self.ioregs.waitcnt.ws0_second_access() as usize];
|
||||
cycles += seq_cycles[self.io.waitcnt.ws0_second_access() as usize];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::core::GBAError;
|
|||
use crate::disass::Disassembler;
|
||||
|
||||
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 ansi_term::Colour;
|
||||
|
@ -32,7 +32,7 @@ pub enum Command {
|
|||
AddBreakpoint(Addr),
|
||||
DelBreakpoint(Addr),
|
||||
PaletteView,
|
||||
TileView(u32),
|
||||
// TileView(u32),
|
||||
ClearBreakpoints,
|
||||
ListBreakpoints,
|
||||
Reset,
|
||||
|
@ -44,7 +44,7 @@ impl Command {
|
|||
use Command::*;
|
||||
match *self {
|
||||
Info => println!("{}", debugger.gba.cpu),
|
||||
DisplayInfo => println!("GPU: {:#?}", debugger.gba.io.borrow().gpu),
|
||||
DisplayInfo => { /*println!("GPU: {:#?}", debugger.gba.sysbus.io.gpu)*/ }
|
||||
Step(count) => {
|
||||
for _ in 0..count {
|
||||
match debugger.gba.step() {
|
||||
|
@ -154,7 +154,7 @@ impl Command {
|
|||
}
|
||||
}
|
||||
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 => {
|
||||
println!("resetting cpu...");
|
||||
debugger.gba.cpu.reset(&mut debugger.gba.sysbus);
|
||||
|
@ -297,13 +297,13 @@ impl Debugger {
|
|||
))),
|
||||
},
|
||||
"palette-view" => Ok(Command::PaletteView),
|
||||
"tiles" => {
|
||||
if args.len() != 1 {
|
||||
return Err(DebuggerError::InvalidCommandFormat("tile <bg>".to_string()));
|
||||
}
|
||||
let bg = self.val_number(&args[0])?;
|
||||
Ok(Command::TileView(bg))
|
||||
}
|
||||
// "tiles" => {
|
||||
// if args.len() != 1 {
|
||||
// return Err(DebuggerError::InvalidCommandFormat("tile <bg>".to_string()));
|
||||
// }
|
||||
// let bg = self.val_number(&args[0])?;
|
||||
// Ok(Command::TileView(bg))
|
||||
// }
|
||||
"bl" => Ok(Command::ListBreakpoints),
|
||||
"q" | "quit" => Ok(Command::Quit),
|
||||
"r" | "reset" => Ok(Command::Reset),
|
||||
|
|
|
@ -1,107 +1,107 @@
|
|||
use std::time::Duration;
|
||||
// use std::time::Duration;
|
||||
|
||||
use sdl2::event::Event;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::Canvas;
|
||||
// use sdl2::event::Event;
|
||||
// use sdl2::pixels::Color;
|
||||
// use sdl2::rect::{Point, Rect};
|
||||
// use sdl2::render::Canvas;
|
||||
|
||||
use crate::core::gba::GameBoyAdvance;
|
||||
use crate::core::gpu::PixelFormat;
|
||||
// use crate::core::gba::GameBoyAdvance;
|
||||
// use crate::core::gpu::PixelFormat;
|
||||
|
||||
fn draw_tile(
|
||||
gba: &GameBoyAdvance,
|
||||
tile_addr: u32,
|
||||
pixel_format: PixelFormat,
|
||||
p: Point,
|
||||
canvas: &mut Canvas<sdl2::video::Window>,
|
||||
) {
|
||||
let io = gba.io.borrow();
|
||||
for y in 0..8 {
|
||||
for x in 0..8 {
|
||||
let index = io
|
||||
.gpu
|
||||
.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);
|
||||
canvas.set_draw_color(Color::RGB(
|
||||
(color.r() as u8) << 3,
|
||||
(color.g() as u8) << 3,
|
||||
(color.b() as u8) << 3,
|
||||
));
|
||||
canvas.draw_point(p.offset(x as i32, y as i32)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
// fn draw_tile(
|
||||
// gba: &GameBoyAdvance,
|
||||
// tile_addr: u32,
|
||||
// pixel_format: PixelFormat,
|
||||
// p: Point,
|
||||
// canvas: &mut Canvas<sdl2::video::Window>,
|
||||
// ) {
|
||||
// let io = &mut gba.sysbus.io;
|
||||
// for y in 0..8 {
|
||||
// for x in 0..8 {
|
||||
// let index = io
|
||||
// .gpu
|
||||
// .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);
|
||||
// canvas.set_draw_color(Color::RGB(
|
||||
// (color.r() as u8) << 3,
|
||||
// (color.g() as u8) << 3,
|
||||
// (color.b() as u8) << 3,
|
||||
// ));
|
||||
// canvas.draw_point(p.offset(x as i32, y as i32)).unwrap();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const TILESET_INITIAL_X: i32 = 0x20;
|
||||
const TILESET_INITIAL_Y: i32 = 0x20;
|
||||
// const TILESET_INITIAL_X: i32 = 0x20;
|
||||
// const TILESET_INITIAL_Y: i32 = 0x20;
|
||||
|
||||
pub fn create_tile_view(bg: u32, gba: &GameBoyAdvance) {
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
// pub fn create_tile_view(bg: u32, gba: &GameBoyAdvance) {
|
||||
// let sdl_context = sdl2::init().unwrap();
|
||||
// let video_subsystem = sdl_context.video().unwrap();
|
||||
|
||||
let window = video_subsystem
|
||||
.window("PaletteView", 512, 512)
|
||||
.position_centered()
|
||||
.build()
|
||||
.unwrap();
|
||||
// let window = video_subsystem
|
||||
// .window("PaletteView", 512, 512)
|
||||
// .position_centered()
|
||||
// .build()
|
||||
// .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 tileset_addr = bgcnt.char_block();
|
||||
let tilemap_addr = bgcnt.screen_block();
|
||||
let tiles_per_row = 32;
|
||||
let num_tiles = 0x4000 / tile_size;
|
||||
println!("tileset: {:#x}, tilemap: {:#x}", tileset_addr, tilemap_addr);
|
||||
// let (tile_size, pixel_format) = bgcnt.tile_format();
|
||||
// let tileset_addr = bgcnt.char_block();
|
||||
// let tilemap_addr = bgcnt.screen_block();
|
||||
// let tiles_per_row = 32;
|
||||
// let num_tiles = 0x4000 / tile_size;
|
||||
// println!("tileset: {:#x}, tilemap: {:#x}", tileset_addr, tilemap_addr);
|
||||
|
||||
let mut event_pump = sdl_context.event_pump().unwrap();
|
||||
'running: loop {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. } => break 'running,
|
||||
Event::MouseButtonDown { x, y, .. } => {
|
||||
let click_point = Point::new(x, y);
|
||||
let mut tile_x = TILESET_INITIAL_X;
|
||||
let mut tile_y = TILESET_INITIAL_Y;
|
||||
for t in 0..num_tiles {
|
||||
let tile_addr = tileset_addr + t * tile_size;
|
||||
if t != 0 && t % tiles_per_row == 0 {
|
||||
tile_y += 10;
|
||||
tile_x = TILESET_INITIAL_Y;
|
||||
}
|
||||
tile_x += 10;
|
||||
if Rect::new(tile_x, tile_y, 8, 8).contains_point(click_point) {
|
||||
println!("tile #{:#x}, addr={:#x}", t, tile_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// let mut event_pump = sdl_context.event_pump().unwrap();
|
||||
// 'running: loop {
|
||||
// for event in event_pump.poll_iter() {
|
||||
// match event {
|
||||
// Event::Quit { .. } => break 'running,
|
||||
// Event::MouseButtonDown { x, y, .. } => {
|
||||
// let click_point = Point::new(x, y);
|
||||
// let mut tile_x = TILESET_INITIAL_X;
|
||||
// let mut tile_y = TILESET_INITIAL_Y;
|
||||
// for t in 0..num_tiles {
|
||||
// let tile_addr = tileset_addr + t * tile_size;
|
||||
// if t != 0 && t % tiles_per_row == 0 {
|
||||
// tile_y += 10;
|
||||
// tile_x = TILESET_INITIAL_Y;
|
||||
// }
|
||||
// tile_x += 10;
|
||||
// if Rect::new(tile_x, tile_y, 8, 8).contains_point(click_point) {
|
||||
// println!("tile #{:#x}, addr={:#x}", t, tile_addr);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
|
||||
canvas.set_draw_color(Color::RGB(00, 00, 00));
|
||||
canvas.clear();
|
||||
// canvas.set_draw_color(Color::RGB(00, 00, 00));
|
||||
// canvas.clear();
|
||||
|
||||
let mut tile_x = TILESET_INITIAL_X;
|
||||
let mut tile_y = TILESET_INITIAL_Y;
|
||||
for t in 0..num_tiles {
|
||||
let tile_addr = tileset_addr + t * tile_size;
|
||||
if t != 0 && t % tiles_per_row == 0 {
|
||||
tile_y += 10;
|
||||
tile_x = TILESET_INITIAL_Y;
|
||||
}
|
||||
tile_x += 10;
|
||||
draw_tile(
|
||||
gba,
|
||||
tile_addr,
|
||||
pixel_format,
|
||||
Point::from((tile_x, tile_y)),
|
||||
&mut canvas,
|
||||
);
|
||||
}
|
||||
canvas.present();
|
||||
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
||||
}
|
||||
}
|
||||
// let mut tile_x = TILESET_INITIAL_X;
|
||||
// let mut tile_y = TILESET_INITIAL_Y;
|
||||
// for t in 0..num_tiles {
|
||||
// let tile_addr = tileset_addr + t * tile_size;
|
||||
// if t != 0 && t % tiles_per_row == 0 {
|
||||
// tile_y += 10;
|
||||
// tile_x = TILESET_INITIAL_Y;
|
||||
// }
|
||||
// tile_x += 10;
|
||||
// draw_tile(
|
||||
// gba,
|
||||
// tile_addr,
|
||||
// pixel_format,
|
||||
// Point::from((tile_x, tile_y)),
|
||||
// &mut canvas,
|
||||
// );
|
||||
// }
|
||||
// canvas.present();
|
||||
// ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
||||
// }
|
||||
// }
|
||||
|
|
Reference in a new issue