Refactor IRQ signaling
Use Rc<Cell> instead of passing `&mut irqs` all around. The runtime impact is two additional size_t's per each holder of the shared pointer. Former-commit-id: afd3188c31608ebcf062256a7ad51575dbc90d8b Former-commit-id: 22e0e01953968cee592b5408677e557059669c31
This commit is contained in:
parent
63cbf9bccf
commit
441f5ad906
|
@ -1,7 +1,8 @@
|
||||||
use super::cartridge::BackupMedia;
|
use super::cartridge::BackupMedia;
|
||||||
|
use super::interrupt::{self, Interrupt, SharedInterruptFlags};
|
||||||
use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
|
use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
use super::{Bus, Interrupt, IrqBitmask};
|
use super::Bus;
|
||||||
|
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -23,6 +24,7 @@ pub struct DmaChannel {
|
||||||
start_cycles: usize,
|
start_cycles: usize,
|
||||||
fifo_mode: bool,
|
fifo_mode: bool,
|
||||||
irq: Interrupt,
|
irq: Interrupt,
|
||||||
|
interrupt_flags: SharedInterruptFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
@ -33,7 +35,7 @@ struct DmaInternalRegs {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DmaChannel {
|
impl DmaChannel {
|
||||||
pub fn new(id: usize) -> DmaChannel {
|
pub fn new(id: usize, interrupt_flags: SharedInterruptFlags) -> DmaChannel {
|
||||||
if id > 3 {
|
if id > 3 {
|
||||||
panic!("invalid dma id {}", id);
|
panic!("invalid dma id {}", id);
|
||||||
}
|
}
|
||||||
|
@ -49,6 +51,7 @@ impl DmaChannel {
|
||||||
start_cycles: 0,
|
start_cycles: 0,
|
||||||
fifo_mode: false,
|
fifo_mode: false,
|
||||||
internal: Default::default(),
|
internal: Default::default(),
|
||||||
|
interrupt_flags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +132,7 @@ impl DmaChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
fn xfer(&mut self, sb: &mut SysBus) {
|
||||||
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
|
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
|
||||||
let count = match self.internal.count {
|
let count = match self.internal.count {
|
||||||
0 => match self.id {
|
0 => match self.id {
|
||||||
|
@ -171,7 +174,7 @@ impl DmaChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.ctrl.is_triggering_irq() {
|
if self.ctrl.is_triggering_irq() {
|
||||||
irqs.add_irq(self.irq);
|
interrupt::signal_irq(&self.interrupt_flags, self.irq);
|
||||||
}
|
}
|
||||||
if self.ctrl.repeat() {
|
if self.ctrl.repeat() {
|
||||||
self.start_cycles = self.cycles;
|
self.start_cycles = self.cycles;
|
||||||
|
@ -194,13 +197,13 @@ pub struct DmaController {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DmaController {
|
impl DmaController {
|
||||||
pub fn new() -> DmaController {
|
pub fn new(interrupt_flags: SharedInterruptFlags) -> DmaController {
|
||||||
DmaController {
|
DmaController {
|
||||||
channels: [
|
channels: [
|
||||||
DmaChannel::new(0),
|
DmaChannel::new(0, interrupt_flags.clone()),
|
||||||
DmaChannel::new(1),
|
DmaChannel::new(1, interrupt_flags.clone()),
|
||||||
DmaChannel::new(2),
|
DmaChannel::new(2, interrupt_flags.clone()),
|
||||||
DmaChannel::new(3),
|
DmaChannel::new(3, interrupt_flags.clone()),
|
||||||
],
|
],
|
||||||
pending_set: 0,
|
pending_set: 0,
|
||||||
cycles: 0,
|
cycles: 0,
|
||||||
|
@ -211,10 +214,10 @@ impl DmaController {
|
||||||
self.pending_set != 0
|
self.pending_set != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_work(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
pub fn perform_work(&mut self, sb: &mut SysBus) {
|
||||||
for id in 0..4 {
|
for id in 0..4 {
|
||||||
if self.pending_set & (1 << id) != 0 {
|
if self.pending_set & (1 << id) != 0 {
|
||||||
self.channels[id].xfer(sb, irqs);
|
self.channels[id].xfer(sb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.pending_set = 0;
|
self.pending_set = 0;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/// Struct containing everything
|
/// Struct containing everything
|
||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use bincode;
|
use bincode;
|
||||||
|
@ -7,11 +7,13 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::arm7tdmi;
|
use super::arm7tdmi;
|
||||||
use super::cartridge::Cartridge;
|
use super::cartridge::Cartridge;
|
||||||
|
use super::dma::DmaController;
|
||||||
use super::gpu::*;
|
use super::gpu::*;
|
||||||
use super::interrupt::*;
|
use super::interrupt::*;
|
||||||
use super::iodev::*;
|
use super::iodev::*;
|
||||||
use super::sound::SoundController;
|
use super::sound::SoundController;
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
|
use super::timer::Timers;
|
||||||
|
|
||||||
use super::{AudioInterface, InputInterface, VideoInterface};
|
use super::{AudioInterface, InputInterface, VideoInterface};
|
||||||
|
|
||||||
|
@ -60,12 +62,17 @@ impl GameBoyAdvance {
|
||||||
true => info!("Verified bios rom"),
|
true => info!("Verified bios rom"),
|
||||||
false => warn!("This is not the real bios rom, some games may not be compatible"),
|
false => warn!("This is not the real bios rom, some games may not be compatible"),
|
||||||
};
|
};
|
||||||
let gpu = Box::new(Gpu::new());
|
|
||||||
|
let interrupt_flags = Rc::new(Cell::new(IrqBitmask(0)));
|
||||||
|
|
||||||
|
let intc = InterruptController::new(interrupt_flags.clone());
|
||||||
|
let gpu = Box::new(Gpu::new(interrupt_flags.clone()));
|
||||||
|
let dmac = DmaController::new(interrupt_flags.clone());
|
||||||
|
let timers = Timers::new(interrupt_flags.clone());
|
||||||
let sound_controller = Box::new(SoundController::new(
|
let sound_controller = Box::new(SoundController::new(
|
||||||
audio_device.borrow().get_sample_rate() as f32,
|
audio_device.borrow().get_sample_rate() as f32,
|
||||||
));
|
));
|
||||||
|
let io = IoDevices::new(intc, gpu, dmac, timers, sound_controller);
|
||||||
let io = IoDevices::new(gpu, sound_controller);
|
|
||||||
let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak));
|
let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak));
|
||||||
|
|
||||||
let cpu = arm7tdmi::Core::new();
|
let cpu = arm7tdmi::Core::new();
|
||||||
|
@ -204,14 +211,11 @@ impl GameBoyAdvance {
|
||||||
&mut (*ptr).io as &mut IoDevices
|
&mut (*ptr).io as &mut IoDevices
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut irqs = IrqBitmask(0);
|
|
||||||
|
|
||||||
let mut cycles_left = self.cycles_to_next_event;
|
let mut cycles_left = self.cycles_to_next_event;
|
||||||
let mut cycles_to_next_event = std::usize::MAX;
|
let mut cycles_to_next_event = std::usize::MAX;
|
||||||
let mut cycles = 0;
|
let mut cycles = 0;
|
||||||
|
|
||||||
while cycles_left > 0 {
|
while cycles_left > 0 {
|
||||||
let mut irqs = IrqBitmask(0);
|
|
||||||
let _cycles = if !io.dmac.is_active() {
|
let _cycles = if !io.dmac.is_active() {
|
||||||
if HaltState::Running == io.haltcnt {
|
if HaltState::Running == io.haltcnt {
|
||||||
self.step_cpu(io)
|
self.step_cpu(io)
|
||||||
|
@ -220,8 +224,7 @@ impl GameBoyAdvance {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
io.dmac.perform_work(&mut self.sysbus, &mut irqs);
|
io.dmac.perform_work(&mut self.sysbus);
|
||||||
io.intc.request_irqs(irqs);
|
|
||||||
return cycles;
|
return cycles;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -233,10 +236,9 @@ impl GameBoyAdvance {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update gpu & sound
|
// update gpu & sound
|
||||||
io.timers.update(cycles, &mut self.sysbus, &mut irqs);
|
io.timers.update(cycles, &mut self.sysbus);
|
||||||
io.gpu.update(
|
io.gpu.update(
|
||||||
cycles,
|
cycles,
|
||||||
&mut irqs,
|
|
||||||
&mut cycles_to_next_event,
|
&mut cycles_to_next_event,
|
||||||
self.sysbus.as_mut(),
|
self.sysbus.as_mut(),
|
||||||
&self.video_device,
|
&self.video_device,
|
||||||
|
@ -244,7 +246,6 @@ impl GameBoyAdvance {
|
||||||
io.sound
|
io.sound
|
||||||
.update(cycles, &mut cycles_to_next_event, &self.audio_device);
|
.update(cycles, &mut cycles_to_next_event, &self.audio_device);
|
||||||
self.cycles_to_next_event = cycles_to_next_event;
|
self.cycles_to_next_event = cycles_to_next_event;
|
||||||
io.intc.request_irqs(irqs);
|
|
||||||
|
|
||||||
cycles
|
cycles
|
||||||
}
|
}
|
||||||
|
@ -260,28 +261,23 @@ impl GameBoyAdvance {
|
||||||
};
|
};
|
||||||
|
|
||||||
// clear any pending DMAs
|
// clear any pending DMAs
|
||||||
let mut irqs = IrqBitmask(0);
|
|
||||||
while io.dmac.is_active() {
|
while io.dmac.is_active() {
|
||||||
io.dmac.perform_work(&mut self.sysbus, &mut irqs);
|
io.dmac.perform_work(&mut self.sysbus);
|
||||||
}
|
}
|
||||||
io.intc.request_irqs(irqs);
|
|
||||||
|
|
||||||
let cycles = self.step_cpu(io);
|
let cycles = self.step_cpu(io);
|
||||||
let breakpoint = self.check_breakpoint();
|
let breakpoint = self.check_breakpoint();
|
||||||
|
|
||||||
irqs = IrqBitmask(0);
|
|
||||||
let mut _ignored = 0;
|
let mut _ignored = 0;
|
||||||
// update gpu & sound
|
// update gpu & sound
|
||||||
io.timers.update(cycles, &mut self.sysbus, &mut irqs);
|
io.timers.update(cycles, &mut self.sysbus);
|
||||||
io.gpu.update(
|
io.gpu.update(
|
||||||
cycles,
|
cycles,
|
||||||
&mut irqs,
|
|
||||||
&mut _ignored,
|
&mut _ignored,
|
||||||
self.sysbus.as_mut(),
|
self.sysbus.as_mut(),
|
||||||
&self.video_device,
|
&self.video_device,
|
||||||
);
|
);
|
||||||
io.sound.update(cycles, &mut _ignored, &self.audio_device);
|
io.sound.update(cycles, &mut _ignored, &self.audio_device);
|
||||||
io.intc.request_irqs(irqs);
|
|
||||||
|
|
||||||
breakpoint
|
breakpoint
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::rc::Rc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
|
use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
|
||||||
use super::interrupt::IrqBitmask;
|
use super::interrupt::{self, Interrupt, SharedInterruptFlags};
|
||||||
pub use super::sysbus::consts::*;
|
pub use super::sysbus::consts::*;
|
||||||
use super::sysbus::BoxedMemory;
|
use super::sysbus::BoxedMemory;
|
||||||
use super::VideoInterface;
|
use super::VideoInterface;
|
||||||
|
@ -174,6 +174,7 @@ type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
|
||||||
#[derive(Serialize, Deserialize, Clone, DebugStub)]
|
#[derive(Serialize, Deserialize, Clone, DebugStub)]
|
||||||
pub struct Gpu {
|
pub struct Gpu {
|
||||||
pub state: GpuState,
|
pub state: GpuState,
|
||||||
|
interrupt_flags: SharedInterruptFlags,
|
||||||
|
|
||||||
/// how many cycles left until next gpu state ?
|
/// how many cycles left until next gpu state ?
|
||||||
cycles_left_for_current_state: usize,
|
cycles_left_for_current_state: usize,
|
||||||
|
@ -270,8 +271,9 @@ impl Bus for Gpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gpu {
|
impl Gpu {
|
||||||
pub fn new() -> Gpu {
|
pub fn new(interrupt_flags: SharedInterruptFlags) -> Gpu {
|
||||||
Gpu {
|
Gpu {
|
||||||
|
interrupt_flags,
|
||||||
dispcnt: DisplayControl(0x80),
|
dispcnt: DisplayControl(0x80),
|
||||||
dispstat: DisplayStatus(0),
|
dispstat: DisplayStatus(0),
|
||||||
backgrounds: [
|
backgrounds: [
|
||||||
|
@ -415,17 +417,6 @@ impl Gpu {
|
||||||
// self.mosaic_sfx();
|
// self.mosaic_sfx();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_vcount(&mut self, value: usize, irqs: &mut IrqBitmask) {
|
|
||||||
self.vcount = value;
|
|
||||||
let vcount_setting = self.dispstat.vcount_setting();
|
|
||||||
self.dispstat
|
|
||||||
.set_vcount_flag(vcount_setting == self.vcount as u16);
|
|
||||||
|
|
||||||
if self.dispstat.vcount_irq_enable() && self.dispstat.get_vcount_flag() {
|
|
||||||
irqs.set_LCD_VCounterMatch(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the gpu obj buffer
|
/// Clears the gpu obj buffer
|
||||||
pub fn obj_buffer_reset(&mut self) {
|
pub fn obj_buffer_reset(&mut self) {
|
||||||
for x in self.obj_buffer.iter_mut() {
|
for x in self.obj_buffer.iter_mut() {
|
||||||
|
@ -440,12 +431,24 @@ impl Gpu {
|
||||||
pub fn on_state_completed<D>(
|
pub fn on_state_completed<D>(
|
||||||
&mut self,
|
&mut self,
|
||||||
completed: GpuState,
|
completed: GpuState,
|
||||||
irqs: &mut IrqBitmask,
|
|
||||||
dma_notifier: &mut D,
|
dma_notifier: &mut D,
|
||||||
video_device: &VideoDeviceRcRefCell,
|
video_device: &VideoDeviceRcRefCell,
|
||||||
) where
|
) where
|
||||||
D: DmaNotifer,
|
D: DmaNotifer,
|
||||||
{
|
{
|
||||||
|
macro_rules! update_vcount {
|
||||||
|
($value:expr) => {
|
||||||
|
self.vcount = $value;
|
||||||
|
let vcount_setting = self.dispstat.vcount_setting();
|
||||||
|
self.dispstat
|
||||||
|
.set_vcount_flag(vcount_setting == self.vcount as u16);
|
||||||
|
|
||||||
|
if self.dispstat.vcount_irq_enable() && self.dispstat.get_vcount_flag() {
|
||||||
|
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VCounterMatch);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
match completed {
|
match completed {
|
||||||
HDraw => {
|
HDraw => {
|
||||||
// Transition to HBlank
|
// Transition to HBlank
|
||||||
|
@ -454,12 +457,12 @@ impl Gpu {
|
||||||
self.dispstat.set_hblank_flag(true);
|
self.dispstat.set_hblank_flag(true);
|
||||||
|
|
||||||
if self.dispstat.hblank_irq_enable() {
|
if self.dispstat.hblank_irq_enable() {
|
||||||
irqs.set_LCD_HBlank(true);
|
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
|
||||||
};
|
};
|
||||||
dma_notifier.notify(TIMING_HBLANK);
|
dma_notifier.notify(TIMING_HBLANK);
|
||||||
}
|
}
|
||||||
HBlank => {
|
HBlank => {
|
||||||
self.update_vcount(self.vcount + 1, irqs);
|
update_vcount!(self.vcount + 1);
|
||||||
|
|
||||||
if self.vcount < DISPLAY_HEIGHT {
|
if self.vcount < DISPLAY_HEIGHT {
|
||||||
self.state = HDraw;
|
self.state = HDraw;
|
||||||
|
@ -481,7 +484,7 @@ impl Gpu {
|
||||||
self.dispstat.set_vblank_flag(true);
|
self.dispstat.set_vblank_flag(true);
|
||||||
self.dispstat.set_hblank_flag(false);
|
self.dispstat.set_hblank_flag(false);
|
||||||
if self.dispstat.vblank_irq_enable() {
|
if self.dispstat.vblank_irq_enable() {
|
||||||
irqs.set_LCD_VBlank(true);
|
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VBlank);
|
||||||
};
|
};
|
||||||
|
|
||||||
dma_notifier.notify(TIMING_VBLANK);
|
dma_notifier.notify(TIMING_VBLANK);
|
||||||
|
@ -497,17 +500,17 @@ impl Gpu {
|
||||||
|
|
||||||
self.dispstat.set_hblank_flag(true);
|
self.dispstat.set_hblank_flag(true);
|
||||||
if self.dispstat.hblank_irq_enable() {
|
if self.dispstat.hblank_irq_enable() {
|
||||||
irqs.set_LCD_HBlank(true);
|
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
VBlankHBlank => {
|
VBlankHBlank => {
|
||||||
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
|
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
|
||||||
self.update_vcount(self.vcount + 1, irqs);
|
update_vcount!(self.vcount + 1);
|
||||||
self.dispstat.set_hblank_flag(false);
|
self.dispstat.set_hblank_flag(false);
|
||||||
self.cycles_left_for_current_state = CYCLES_HDRAW;
|
self.cycles_left_for_current_state = CYCLES_HDRAW;
|
||||||
self.state = VBlankHDraw;
|
self.state = VBlankHDraw;
|
||||||
} else {
|
} else {
|
||||||
self.update_vcount(0, irqs);
|
update_vcount!(0);
|
||||||
self.dispstat.set_vblank_flag(false);
|
self.dispstat.set_vblank_flag(false);
|
||||||
self.dispstat.set_hblank_flag(false);
|
self.dispstat.set_hblank_flag(false);
|
||||||
self.render_scanline();
|
self.render_scanline();
|
||||||
|
@ -521,7 +524,6 @@ impl Gpu {
|
||||||
pub fn update<D>(
|
pub fn update<D>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut cycles: usize,
|
mut cycles: usize,
|
||||||
irqs: &mut IrqBitmask,
|
|
||||||
cycles_to_next_event: &mut usize,
|
cycles_to_next_event: &mut usize,
|
||||||
dma_notifier: &mut D,
|
dma_notifier: &mut D,
|
||||||
video_device: &VideoDeviceRcRefCell,
|
video_device: &VideoDeviceRcRefCell,
|
||||||
|
@ -531,7 +533,7 @@ impl Gpu {
|
||||||
loop {
|
loop {
|
||||||
if self.cycles_left_for_current_state <= cycles {
|
if self.cycles_left_for_current_state <= cycles {
|
||||||
cycles -= self.cycles_left_for_current_state;
|
cycles -= self.cycles_left_for_current_state;
|
||||||
self.on_state_completed(self.state, irqs, dma_notifier, video_device);
|
self.on_state_completed(self.state, dma_notifier, video_device);
|
||||||
} else {
|
} else {
|
||||||
self.cycles_left_for_current_state -= cycles;
|
self.cycles_left_for_current_state -= cycles;
|
||||||
break;
|
break;
|
||||||
|
@ -547,6 +549,8 @@ impl Gpu {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
struct NopDmaNotifer;
|
struct NopDmaNotifer;
|
||||||
impl DmaNotifer for NopDmaNotifer {
|
impl DmaNotifer for NopDmaNotifer {
|
||||||
|
@ -566,10 +570,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gpu_state_machine() {
|
fn test_gpu_state_machine() {
|
||||||
let mut gpu = Gpu::new();
|
let mut gpu = Gpu::new(Rc::new(Cell::new(Default::default())));
|
||||||
let video = Rc::new(RefCell::new(TestVideoInterface::default()));
|
let video = Rc::new(RefCell::new(TestVideoInterface::default()));
|
||||||
let video_clone: VideoDeviceRcRefCell = video.clone();
|
let video_clone: VideoDeviceRcRefCell = video.clone();
|
||||||
let mut irqs = IrqBitmask(0);
|
|
||||||
let mut dma_notifier = NopDmaNotifer;
|
let mut dma_notifier = NopDmaNotifer;
|
||||||
let mut cycles_to_next_event = CYCLES_FULL_REFRESH;
|
let mut cycles_to_next_event = CYCLES_FULL_REFRESH;
|
||||||
|
|
||||||
|
@ -582,7 +585,6 @@ mod tests {
|
||||||
($cycles:expr) => {
|
($cycles:expr) => {
|
||||||
gpu.update(
|
gpu.update(
|
||||||
$cycles,
|
$cycles,
|
||||||
&mut irqs,
|
|
||||||
&mut cycles_to_next_event,
|
&mut cycles_to_next_event,
|
||||||
&mut dma_notifier,
|
&mut dma_notifier,
|
||||||
&video_clone,
|
&video_clone,
|
||||||
|
@ -607,7 +609,7 @@ mod tests {
|
||||||
|
|
||||||
update!(CYCLES_HBLANK);
|
update!(CYCLES_HBLANK);
|
||||||
|
|
||||||
assert_eq!(irqs.LCD_VCounterMatch(), false);
|
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(video.borrow().frame_counter, 1);
|
assert_eq!(video.borrow().frame_counter, 1);
|
||||||
|
@ -623,7 +625,7 @@ mod tests {
|
||||||
assert_eq!(gpu.dispstat.get_hblank_flag(), true);
|
assert_eq!(gpu.dispstat.get_hblank_flag(), true);
|
||||||
assert_eq!(gpu.dispstat.get_vblank_flag(), true);
|
assert_eq!(gpu.dispstat.get_vblank_flag(), true);
|
||||||
assert_eq!(gpu.state, GpuState::VBlankHBlank);
|
assert_eq!(gpu.state, GpuState::VBlankHBlank);
|
||||||
assert_eq!(irqs.LCD_VCounterMatch(), false);
|
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), false);
|
||||||
|
|
||||||
update!(CYCLES_HBLANK);
|
update!(CYCLES_HBLANK);
|
||||||
}
|
}
|
||||||
|
@ -631,7 +633,7 @@ mod tests {
|
||||||
assert_eq!(video.borrow().frame_counter, 1);
|
assert_eq!(video.borrow().frame_counter, 1);
|
||||||
assert_eq!(total_cycles, CYCLES_FULL_REFRESH);
|
assert_eq!(total_cycles, CYCLES_FULL_REFRESH);
|
||||||
|
|
||||||
assert_eq!(irqs.LCD_VCounterMatch(), true);
|
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), true);
|
||||||
assert_eq!(gpu.cycles_left_for_current_state, CYCLES_HDRAW);
|
assert_eq!(gpu.cycles_left_for_current_state, CYCLES_HDRAW);
|
||||||
assert_eq!(gpu.state, GpuState::HDraw);
|
assert_eq!(gpu.state, GpuState::HDraw);
|
||||||
assert_eq!(gpu.vcount, 0);
|
assert_eq!(gpu.vcount, 0);
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Primitive, Copy, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Primitive, Copy, Clone, PartialEq)]
|
||||||
|
@ -23,29 +26,42 @@ pub enum Interrupt {
|
||||||
pub struct InterruptController {
|
pub struct InterruptController {
|
||||||
pub interrupt_master_enable: bool,
|
pub interrupt_master_enable: bool,
|
||||||
pub interrupt_enable: IrqBitmask,
|
pub interrupt_enable: IrqBitmask,
|
||||||
pub interrupt_flags: IrqBitmask,
|
pub interrupt_flags: SharedInterruptFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InterruptController {
|
impl InterruptController {
|
||||||
pub fn new() -> InterruptController {
|
pub fn new(interrupt_flags: SharedInterruptFlags) -> InterruptController {
|
||||||
InterruptController {
|
InterruptController {
|
||||||
|
interrupt_flags,
|
||||||
interrupt_master_enable: false,
|
interrupt_master_enable: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_irqs(&mut self, flags: IrqBitmask) {
|
#[inline]
|
||||||
self.interrupt_flags.0 |= flags.0;
|
pub fn irq_pending(&self) -> bool {
|
||||||
|
self.interrupt_master_enable
|
||||||
|
& ((self.interrupt_flags.get().value() & self.interrupt_enable.0) != 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn irq_pending(&self) -> bool {
|
#[inline]
|
||||||
self.interrupt_master_enable & ((self.interrupt_flags.0 & self.interrupt_enable.0) != 0)
|
pub fn clear(&mut self, value: u16) {
|
||||||
|
let _if = self.interrupt_flags.get();
|
||||||
|
let new_if = _if.0 & !value;
|
||||||
|
self.interrupt_flags.set(IrqBitmask(new_if));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn signal_irq(interrupt_flags: &SharedInterruptFlags, i: Interrupt) {
|
||||||
|
let _if = interrupt_flags.get();
|
||||||
|
let new_if = _if.0 | 1 << (i as usize);
|
||||||
|
interrupt_flags.set(IrqBitmask(new_if));
|
||||||
|
}
|
||||||
|
|
||||||
impl IrqBitmask {
|
impl IrqBitmask {
|
||||||
pub fn add_irq(&mut self, i: Interrupt) {
|
pub fn value(&self) -> u16 {
|
||||||
self.0 |= 1 << (i as usize);
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,3 +99,5 @@ bitfield! {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub GamePak, set_GamePak: 13;
|
pub GamePak, set_GamePak: 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type SharedInterruptFlags = Rc<Cell<IrqBitmask>>;
|
||||||
|
|
|
@ -41,13 +41,19 @@ pub struct IoDevices {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IoDevices {
|
impl IoDevices {
|
||||||
pub fn new(gpu: Box<Gpu>, sound_controller: Box<SoundController>) -> IoDevices {
|
pub fn new(
|
||||||
|
intc: InterruptController,
|
||||||
|
gpu: Box<Gpu>,
|
||||||
|
dmac: DmaController,
|
||||||
|
timers: Timers,
|
||||||
|
sound_controller: Box<SoundController>,
|
||||||
|
) -> IoDevices {
|
||||||
IoDevices {
|
IoDevices {
|
||||||
gpu: gpu,
|
intc,
|
||||||
|
gpu,
|
||||||
|
timers,
|
||||||
|
dmac,
|
||||||
sound: sound_controller,
|
sound: sound_controller,
|
||||||
timers: Timers::new(),
|
|
||||||
dmac: DmaController::new(),
|
|
||||||
intc: InterruptController::new(),
|
|
||||||
post_boot_flag: false,
|
post_boot_flag: false,
|
||||||
haltcnt: HaltState::Running,
|
haltcnt: HaltState::Running,
|
||||||
keyinput: keypad::KEYINPUT_ALL_RELEASED,
|
keyinput: keypad::KEYINPUT_ALL_RELEASED,
|
||||||
|
@ -92,7 +98,7 @@ impl Bus for IoDevices {
|
||||||
|
|
||||||
REG_IME => io.intc.interrupt_master_enable as u16,
|
REG_IME => io.intc.interrupt_master_enable as u16,
|
||||||
REG_IE => io.intc.interrupt_enable.0 as u16,
|
REG_IE => io.intc.interrupt_enable.0 as u16,
|
||||||
REG_IF => io.intc.interrupt_flags.0 as u16,
|
REG_IF => io.intc.interrupt_flags.get().value() as u16,
|
||||||
|
|
||||||
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_read(io_addr),
|
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_read(io_addr),
|
||||||
|
|
||||||
|
@ -222,7 +228,7 @@ impl Bus for IoDevices {
|
||||||
|
|
||||||
REG_IME => io.intc.interrupt_master_enable = value != 0,
|
REG_IME => io.intc.interrupt_master_enable = value != 0,
|
||||||
REG_IE => io.intc.interrupt_enable.0 = value,
|
REG_IE => io.intc.interrupt_enable.0 = value,
|
||||||
REG_IF => io.intc.interrupt_flags.0 &= !value,
|
REG_IF => io.intc.clear(value),
|
||||||
|
|
||||||
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_write(io_addr, value),
|
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_write(io_addr, value),
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub use sysbus::SysBus;
|
||||||
pub mod interrupt;
|
pub mod interrupt;
|
||||||
pub mod iodev;
|
pub mod iodev;
|
||||||
pub use interrupt::Interrupt;
|
pub use interrupt::Interrupt;
|
||||||
pub use interrupt::IrqBitmask;
|
pub use interrupt::SharedInterruptFlags;
|
||||||
pub mod gba;
|
pub mod gba;
|
||||||
pub use gba::GameBoyAdvance;
|
pub use gba::GameBoyAdvance;
|
||||||
pub mod bus;
|
pub mod bus;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::interrupt::{Interrupt, IrqBitmask};
|
use super::interrupt::{self, Interrupt, SharedInterruptFlags};
|
||||||
use super::iodev::consts::*;
|
use super::iodev::consts::*;
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
|
|
||||||
|
@ -15,19 +15,21 @@ pub struct Timer {
|
||||||
pub initial_data: u16,
|
pub initial_data: u16,
|
||||||
|
|
||||||
irq: Interrupt,
|
irq: Interrupt,
|
||||||
|
interrupt_flags: SharedInterruptFlags,
|
||||||
timer_id: usize,
|
timer_id: usize,
|
||||||
cycles: usize,
|
cycles: usize,
|
||||||
prescalar_shift: usize,
|
prescalar_shift: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timer {
|
impl Timer {
|
||||||
pub fn new(timer_id: usize) -> Timer {
|
pub fn new(timer_id: usize, interrupt_flags: SharedInterruptFlags) -> Timer {
|
||||||
if timer_id > 3 {
|
if timer_id > 3 {
|
||||||
panic!("invalid timer id {}", timer_id);
|
panic!("invalid timer id {}", timer_id);
|
||||||
}
|
}
|
||||||
Timer {
|
Timer {
|
||||||
timer_id: timer_id,
|
timer_id: timer_id,
|
||||||
irq: Interrupt::from_usize(timer_id + 3).unwrap(),
|
irq: Interrupt::from_usize(timer_id + 3).unwrap(),
|
||||||
|
interrupt_flags,
|
||||||
data: 0,
|
data: 0,
|
||||||
ctl: TimerCtl(0),
|
ctl: TimerCtl(0),
|
||||||
initial_data: 0,
|
initial_data: 0,
|
||||||
|
@ -43,7 +45,7 @@ impl Timer {
|
||||||
|
|
||||||
/// increments the timer with an amount of ticks
|
/// increments the timer with an amount of ticks
|
||||||
/// returns the number of times it overflowed
|
/// returns the number of times it overflowed
|
||||||
fn update(&mut self, ticks: usize, irqs: &mut IrqBitmask) -> usize {
|
fn update(&mut self, ticks: usize) -> usize {
|
||||||
let mut ticks = ticks as u32;
|
let mut ticks = ticks as u32;
|
||||||
let mut num_overflows = 0;
|
let mut num_overflows = 0;
|
||||||
|
|
||||||
|
@ -59,7 +61,7 @@ impl Timer {
|
||||||
ticks = ticks % ticks_remaining;
|
ticks = ticks % ticks_remaining;
|
||||||
|
|
||||||
if self.ctl.irq_enabled() {
|
if self.ctl.irq_enabled() {
|
||||||
irqs.add_irq(self.irq);
|
interrupt::signal_irq(&self.interrupt_flags, self.irq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,9 +92,14 @@ impl std::ops::IndexMut<usize> for Timers {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timers {
|
impl Timers {
|
||||||
pub fn new() -> Timers {
|
pub fn new(interrupt_flags: SharedInterruptFlags) -> Timers {
|
||||||
Timers {
|
Timers {
|
||||||
timers: [Timer::new(0), Timer::new(1), Timer::new(2), Timer::new(3)],
|
timers: [
|
||||||
|
Timer::new(0, interrupt_flags.clone()),
|
||||||
|
Timer::new(1, interrupt_flags.clone()),
|
||||||
|
Timer::new(2, interrupt_flags.clone()),
|
||||||
|
Timer::new(3, interrupt_flags.clone()),
|
||||||
|
],
|
||||||
running_timers: 0,
|
running_timers: 0,
|
||||||
trace: false,
|
trace: false,
|
||||||
}
|
}
|
||||||
|
@ -163,7 +170,7 @@ impl Timers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
pub fn update(&mut self, cycles: usize, sb: &mut SysBus) {
|
||||||
for id in 0..4 {
|
for id in 0..4 {
|
||||||
if self.running_timers & (1 << id) == 0 {
|
if self.running_timers & (1 << id) == 0 {
|
||||||
continue;
|
continue;
|
||||||
|
@ -174,14 +181,14 @@ impl Timers {
|
||||||
|
|
||||||
let cycles = timer.cycles + cycles;
|
let cycles = timer.cycles + cycles;
|
||||||
let inc = cycles >> timer.prescalar_shift;
|
let inc = cycles >> timer.prescalar_shift;
|
||||||
let num_overflows = timer.update(inc, irqs);
|
let num_overflows = timer.update(inc);
|
||||||
timer.cycles = cycles & ((1 << timer.prescalar_shift) - 1);
|
timer.cycles = cycles & ((1 << timer.prescalar_shift) - 1);
|
||||||
|
|
||||||
if num_overflows > 0 {
|
if num_overflows > 0 {
|
||||||
if id != 3 {
|
if id != 3 {
|
||||||
let next_timer = &mut self.timers[id + 1];
|
let next_timer = &mut self.timers[id + 1];
|
||||||
if next_timer.ctl.cascade() {
|
if next_timer.ctl.cascade() {
|
||||||
next_timer.update(num_overflows, irqs);
|
next_timer.update(num_overflows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if id == 0 || id == 1 {
|
if id == 0 || id == 1 {
|
||||||
|
|
Reference in a new issue