2019-08-07 07:50:33 +01:00
|
|
|
use std::fmt;
|
2019-07-15 05:30:52 +01:00
|
|
|
|
2020-01-16 18:06:22 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
2019-11-08 23:43:43 +00:00
|
|
|
use super::cartridge::Cartridge;
|
2020-05-20 19:22:30 +01:00
|
|
|
use super::dma::DmaNotifer;
|
2020-04-04 11:50:50 +01:00
|
|
|
use super::iodev::{IoDevices, WaitControl};
|
2019-12-29 21:03:57 +00:00
|
|
|
use super::{Addr, Bus};
|
2020-05-29 15:39:35 +01:00
|
|
|
use super::util::WeakPointer;
|
2019-06-25 00:10:09 +01:00
|
|
|
|
2020-01-28 08:25:52 +00:00
|
|
|
pub mod consts {
|
|
|
|
pub const WORK_RAM_SIZE: usize = 256 * 1024;
|
|
|
|
pub const INTERNAL_RAM_SIZE: usize = 32 * 1024;
|
|
|
|
|
|
|
|
pub const BIOS_ADDR: u32 = 0x0000_0000;
|
|
|
|
pub const EWRAM_ADDR: u32 = 0x0200_0000;
|
|
|
|
pub const IWRAM_ADDR: u32 = 0x0300_0000;
|
|
|
|
pub const IOMEM_ADDR: u32 = 0x0400_0000;
|
|
|
|
pub const PALRAM_ADDR: u32 = 0x0500_0000;
|
|
|
|
pub const VRAM_ADDR: u32 = 0x0600_0000;
|
|
|
|
pub const OAM_ADDR: u32 = 0x0700_0000;
|
|
|
|
pub const GAMEPAK_WS0_LO: u32 = 0x0800_0000;
|
|
|
|
pub const GAMEPAK_WS0_HI: u32 = 0x0900_0000;
|
|
|
|
pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000;
|
|
|
|
pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000;
|
|
|
|
pub const GAMEPAK_WS2_LO: u32 = 0x0C00_0000;
|
|
|
|
pub const GAMEPAK_WS2_HI: u32 = 0x0D00_0000;
|
|
|
|
pub const SRAM_LO: u32 = 0x0E00_0000;
|
|
|
|
pub const SRAM_HI: u32 = 0x0F00_0000;
|
2020-04-04 11:50:50 +01:00
|
|
|
|
|
|
|
pub const PAGE_BIOS: usize = (BIOS_ADDR >> 24) as usize;
|
|
|
|
pub const PAGE_EWRAM: usize = (EWRAM_ADDR >> 24) as usize;
|
|
|
|
pub const PAGE_IWRAM: usize = (IWRAM_ADDR >> 24) as usize;
|
|
|
|
pub const PAGE_IOMEM: usize = (IOMEM_ADDR >> 24) as usize;
|
|
|
|
pub const PAGE_PALRAM: usize = (PALRAM_ADDR >> 24) as usize;
|
|
|
|
pub const PAGE_VRAM: usize = (VRAM_ADDR >> 24) as usize;
|
|
|
|
pub const PAGE_OAM: usize = (OAM_ADDR >> 24) as usize;
|
|
|
|
pub const PAGE_GAMEPAK_WS0: usize = (GAMEPAK_WS0_LO >> 24) as usize;
|
|
|
|
pub const PAGE_GAMEPAK_WS1: usize = (GAMEPAK_WS1_LO >> 24) as usize;
|
|
|
|
pub const PAGE_GAMEPAK_WS2: usize = (GAMEPAK_WS2_LO >> 24) as usize;
|
|
|
|
pub const PAGE_SRAM_LO: usize = (SRAM_LO >> 24) as usize;
|
|
|
|
pub const PAGE_SRAM_HI: usize = (SRAM_HI >> 24) as usize;
|
2020-01-28 08:25:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
use consts::*;
|
2019-08-07 07:50:33 +01:00
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
pub enum MemoryAccessType {
|
|
|
|
NonSeq,
|
|
|
|
Seq,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for MemoryAccessType {
|
2020-02-14 12:01:48 +00:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2019-08-07 07:50:33 +01:00
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"{}",
|
|
|
|
match self {
|
|
|
|
MemoryAccessType::NonSeq => "N",
|
|
|
|
MemoryAccessType::Seq => "S",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
|
|
|
pub enum MemoryAccessWidth {
|
|
|
|
MemoryAccess8,
|
|
|
|
MemoryAccess16,
|
|
|
|
MemoryAccess32,
|
|
|
|
}
|
|
|
|
|
2020-01-16 18:06:22 +00:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
2020-02-24 22:09:19 +00:00
|
|
|
#[repr(transparent)]
|
2019-07-28 23:28:22 +01:00
|
|
|
pub struct BoxedMemory {
|
2019-08-02 15:58:56 +01:00
|
|
|
pub mem: Box<[u8]>,
|
2019-07-28 23:28:22 +01:00
|
|
|
}
|
2019-07-02 23:26:48 +01:00
|
|
|
|
|
|
|
impl BoxedMemory {
|
2019-11-08 22:55:09 +00:00
|
|
|
pub fn new(boxed_slice: Box<[u8]>) -> BoxedMemory {
|
|
|
|
BoxedMemory { mem: boxed_slice }
|
2019-07-02 23:26:48 +01:00
|
|
|
}
|
|
|
|
}
|
2019-06-25 00:10:09 +01:00
|
|
|
|
2019-07-01 15:45:29 +01:00
|
|
|
impl Bus for BoxedMemory {
|
2019-07-15 05:30:52 +01:00
|
|
|
fn read_8(&self, addr: Addr) -> u8 {
|
2020-01-16 18:06:22 +00:00
|
|
|
unsafe { *self.mem.get_unchecked(addr as usize) }
|
2019-07-15 05:30:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn write_8(&mut self, addr: Addr, value: u8) {
|
2020-01-16 18:06:22 +00:00
|
|
|
unsafe {
|
|
|
|
*self.mem.get_unchecked_mut(addr as usize) = value;
|
|
|
|
}
|
2019-07-15 05:30:52 +01:00
|
|
|
}
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
2020-04-04 11:50:50 +01:00
|
|
|
const CYCLE_LUT_SIZE: usize = 0x10;
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone)]
|
|
|
|
struct CycleLookupTables {
|
|
|
|
n_cycles32: [usize; CYCLE_LUT_SIZE],
|
|
|
|
s_cycles32: [usize; CYCLE_LUT_SIZE],
|
|
|
|
n_cycles16: [usize; CYCLE_LUT_SIZE],
|
|
|
|
s_cycles16: [usize; CYCLE_LUT_SIZE],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for CycleLookupTables {
|
|
|
|
fn default() -> CycleLookupTables {
|
|
|
|
CycleLookupTables {
|
|
|
|
n_cycles32: [1; CYCLE_LUT_SIZE],
|
|
|
|
s_cycles32: [1; CYCLE_LUT_SIZE],
|
|
|
|
n_cycles16: [1; CYCLE_LUT_SIZE],
|
|
|
|
s_cycles16: [1; CYCLE_LUT_SIZE],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CycleLookupTables {
|
|
|
|
pub fn init(&mut self) {
|
|
|
|
self.n_cycles32[PAGE_EWRAM] = 6;
|
|
|
|
self.s_cycles32[PAGE_EWRAM] = 6;
|
|
|
|
self.n_cycles16[PAGE_EWRAM] = 3;
|
|
|
|
self.s_cycles16[PAGE_EWRAM] = 3;
|
|
|
|
|
|
|
|
self.n_cycles32[PAGE_OAM] = 2;
|
|
|
|
self.s_cycles32[PAGE_OAM] = 2;
|
|
|
|
self.n_cycles16[PAGE_OAM] = 1;
|
|
|
|
self.s_cycles16[PAGE_OAM] = 1;
|
|
|
|
|
|
|
|
self.n_cycles32[PAGE_VRAM] = 2;
|
|
|
|
self.s_cycles32[PAGE_VRAM] = 2;
|
|
|
|
self.n_cycles16[PAGE_VRAM] = 1;
|
|
|
|
self.s_cycles16[PAGE_VRAM] = 1;
|
|
|
|
|
|
|
|
self.n_cycles32[PAGE_PALRAM] = 2;
|
|
|
|
self.s_cycles32[PAGE_PALRAM] = 2;
|
|
|
|
self.n_cycles16[PAGE_PALRAM] = 1;
|
|
|
|
self.s_cycles16[PAGE_PALRAM] = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update_gamepak_waitstates(&mut self, waitcnt: WaitControl) {
|
|
|
|
static S_GAMEPAK_NSEQ_CYCLES: [usize; 4] = [4, 3, 2, 8];
|
|
|
|
static S_GAMEPAK_WS0_SEQ_CYCLES: [usize; 2] = [2, 1];
|
|
|
|
static S_GAMEPAK_WS1_SEQ_CYCLES: [usize; 2] = [4, 1];
|
|
|
|
static S_GAMEPAK_WS2_SEQ_CYCLES: [usize; 2] = [8, 1];
|
|
|
|
|
|
|
|
let ws0_first_access = waitcnt.ws0_first_access() as usize;
|
|
|
|
let ws1_first_access = waitcnt.ws1_first_access() as usize;
|
|
|
|
let ws2_first_access = waitcnt.ws2_first_access() as usize;
|
|
|
|
let ws0_second_access = waitcnt.ws0_second_access() as usize;
|
|
|
|
let ws1_second_access = waitcnt.ws1_second_access() as usize;
|
|
|
|
let ws2_second_access = waitcnt.ws2_second_access() as usize;
|
|
|
|
|
|
|
|
// update SRAM access
|
|
|
|
let sram_wait_cycles = 1 + S_GAMEPAK_NSEQ_CYCLES[waitcnt.sram_wait_control() as usize];
|
|
|
|
self.n_cycles32[PAGE_SRAM_LO] = sram_wait_cycles;
|
|
|
|
self.n_cycles32[PAGE_SRAM_LO] = sram_wait_cycles;
|
|
|
|
self.n_cycles16[PAGE_SRAM_HI] = sram_wait_cycles;
|
|
|
|
self.n_cycles16[PAGE_SRAM_HI] = sram_wait_cycles;
|
|
|
|
self.s_cycles32[PAGE_SRAM_LO] = sram_wait_cycles;
|
|
|
|
self.s_cycles32[PAGE_SRAM_LO] = sram_wait_cycles;
|
|
|
|
self.s_cycles16[PAGE_SRAM_HI] = sram_wait_cycles;
|
|
|
|
self.s_cycles16[PAGE_SRAM_HI] = sram_wait_cycles;
|
|
|
|
|
|
|
|
// update both pages of each waitstate
|
|
|
|
for i in 0..2 {
|
|
|
|
self.n_cycles16[PAGE_GAMEPAK_WS0 + i] = 1 + S_GAMEPAK_NSEQ_CYCLES[ws0_first_access];
|
|
|
|
self.s_cycles16[PAGE_GAMEPAK_WS0 + i] = 1 + S_GAMEPAK_WS0_SEQ_CYCLES[ws0_second_access];
|
|
|
|
|
|
|
|
self.n_cycles16[PAGE_GAMEPAK_WS1 + i] = 1 + S_GAMEPAK_NSEQ_CYCLES[ws1_first_access];
|
|
|
|
self.s_cycles16[PAGE_GAMEPAK_WS1 + i] = 1 + S_GAMEPAK_WS1_SEQ_CYCLES[ws1_second_access];
|
|
|
|
|
|
|
|
self.n_cycles16[PAGE_GAMEPAK_WS2 + i] = 1 + S_GAMEPAK_NSEQ_CYCLES[ws2_first_access];
|
|
|
|
self.s_cycles16[PAGE_GAMEPAK_WS2 + i] = 1 + S_GAMEPAK_WS2_SEQ_CYCLES[ws2_second_access];
|
|
|
|
|
|
|
|
// ROM 32bit accesses are split into two 16bit accesses 1N+1S
|
|
|
|
self.n_cycles32[PAGE_GAMEPAK_WS0 + i] =
|
|
|
|
self.n_cycles16[PAGE_GAMEPAK_WS0 + i] + self.s_cycles16[PAGE_GAMEPAK_WS0 + i];
|
|
|
|
self.n_cycles32[PAGE_GAMEPAK_WS1 + i] =
|
|
|
|
self.n_cycles16[PAGE_GAMEPAK_WS1 + i] + self.s_cycles16[PAGE_GAMEPAK_WS1 + i];
|
|
|
|
self.n_cycles32[PAGE_GAMEPAK_WS2 + i] =
|
|
|
|
self.n_cycles16[PAGE_GAMEPAK_WS2 + i] + self.s_cycles16[PAGE_GAMEPAK_WS2 + i];
|
|
|
|
|
|
|
|
self.s_cycles32[PAGE_GAMEPAK_WS0 + i] = 2 * self.s_cycles16[PAGE_GAMEPAK_WS0 + i];
|
|
|
|
self.s_cycles32[PAGE_GAMEPAK_WS1 + i] = 2 * self.s_cycles16[PAGE_GAMEPAK_WS1 + i];
|
|
|
|
self.s_cycles32[PAGE_GAMEPAK_WS2 + i] = 2 * self.s_cycles16[PAGE_GAMEPAK_WS2 + i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-16 18:06:22 +00:00
|
|
|
#[derive(Serialize, Deserialize, Clone)]
|
2019-06-30 14:59:19 +01:00
|
|
|
pub struct SysBus {
|
2019-11-08 23:43:43 +00:00
|
|
|
pub io: IoDevices,
|
2019-08-05 07:44:27 +01:00
|
|
|
|
2019-07-01 15:45:29 +01:00
|
|
|
bios: BoxedMemory,
|
|
|
|
onboard_work_ram: BoxedMemory,
|
|
|
|
internal_work_ram: BoxedMemory,
|
2020-01-31 10:41:13 +00:00
|
|
|
pub cartridge: Cartridge,
|
2019-11-16 16:09:37 +00:00
|
|
|
|
2020-04-04 11:50:50 +01:00
|
|
|
cycle_luts: CycleLookupTables,
|
|
|
|
|
2019-11-16 16:09:37 +00:00
|
|
|
pub trace_access: bool,
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
2020-05-29 15:39:35 +01:00
|
|
|
pub type SysBusPtr = WeakPointer<SysBus>;
|
2020-04-04 11:50:50 +01:00
|
|
|
|
2020-04-10 19:32:31 +01:00
|
|
|
macro_rules! memory_map {
|
|
|
|
(read($sb:ident, $read_fn:ident, $addr:expr)) => {
|
|
|
|
match $addr & 0xff000000 {
|
|
|
|
BIOS_ADDR => {
|
|
|
|
if $addr >= 0x4000 {
|
|
|
|
0
|
|
|
|
} else {
|
|
|
|
$sb.bios.$read_fn($addr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EWRAM_ADDR => $sb.onboard_work_ram.$read_fn($addr & 0x3_ffff),
|
|
|
|
IWRAM_ADDR => $sb.internal_work_ram.$read_fn($addr & 0x7fff),
|
|
|
|
IOMEM_ADDR => {
|
|
|
|
let addr = if $addr & 0xffff == 0x8000 {
|
|
|
|
0x800
|
|
|
|
} else {
|
|
|
|
$addr & 0x7ff
|
|
|
|
};
|
|
|
|
$sb.io.$read_fn(addr)
|
|
|
|
}
|
2020-05-18 19:11:49 +01:00
|
|
|
PALRAM_ADDR | VRAM_ADDR | OAM_ADDR => $sb.io.gpu.$read_fn($addr),
|
2020-04-10 19:32:31 +01:00
|
|
|
GAMEPAK_WS0_LO | GAMEPAK_WS0_HI | GAMEPAK_WS1_LO | GAMEPAK_WS1_HI | GAMEPAK_WS2_LO => {
|
|
|
|
$sb.cartridge.$read_fn($addr)
|
|
|
|
}
|
|
|
|
GAMEPAK_WS2_HI => $sb.cartridge.$read_fn($addr),
|
|
|
|
SRAM_LO | SRAM_HI => $sb.cartridge.$read_fn($addr),
|
|
|
|
_ => {
|
|
|
|
// warn!("trying to read invalid address {:#x}", $addr);
|
|
|
|
// TODO open bus
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
(write($sb:ident, $write_fn:ident, $addr:expr, $value:expr)) => {
|
|
|
|
match $addr & 0xff000000 {
|
|
|
|
BIOS_ADDR => {}
|
|
|
|
EWRAM_ADDR => $sb.onboard_work_ram.$write_fn($addr & 0x3_ffff, $value),
|
|
|
|
IWRAM_ADDR => $sb.internal_work_ram.$write_fn($addr & 0x7fff, $value),
|
|
|
|
IOMEM_ADDR => {
|
|
|
|
let addr = if $addr & 0xffff == 0x8000 {
|
|
|
|
0x800
|
|
|
|
} else {
|
|
|
|
$addr & 0x7ff
|
|
|
|
};
|
|
|
|
$sb.io.$write_fn(addr, $value)
|
|
|
|
}
|
2020-05-18 19:11:49 +01:00
|
|
|
PALRAM_ADDR | VRAM_ADDR | OAM_ADDR => $sb.io.gpu.$write_fn($addr, $value),
|
2020-05-17 18:12:31 +01:00
|
|
|
GAMEPAK_WS0_LO => $sb.cartridge.$write_fn($addr, $value),
|
2020-04-10 19:32:31 +01:00
|
|
|
GAMEPAK_WS2_HI => $sb.cartridge.$write_fn($addr, $value),
|
|
|
|
SRAM_LO | SRAM_HI => $sb.cartridge.$write_fn($addr, $value),
|
|
|
|
_ => {
|
|
|
|
// warn!("trying to write invalid address {:#x}", $addr);
|
|
|
|
// TODO open bus
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-06-30 14:59:19 +01:00
|
|
|
impl SysBus {
|
2020-02-24 22:11:10 +00:00
|
|
|
pub fn new(io: IoDevices, bios_rom: Box<[u8]>, cartridge: Cartridge) -> SysBus {
|
2020-04-04 11:50:50 +01:00
|
|
|
let mut luts = CycleLookupTables::default();
|
|
|
|
luts.init();
|
|
|
|
luts.update_gamepak_waitstates(io.waitcnt);
|
|
|
|
|
2019-07-01 15:45:29 +01:00
|
|
|
SysBus {
|
2019-08-05 07:44:27 +01:00
|
|
|
io: io,
|
|
|
|
|
2020-02-24 22:11:10 +00:00
|
|
|
bios: BoxedMemory::new(bios_rom),
|
2019-11-08 22:55:09 +00:00
|
|
|
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()),
|
2020-01-26 00:06:44 +00:00
|
|
|
cartridge: cartridge,
|
2019-11-16 16:09:37 +00:00
|
|
|
|
2020-04-04 11:50:50 +01:00
|
|
|
cycle_luts: luts,
|
|
|
|
|
2019-11-16 16:09:37 +00:00
|
|
|
trace_access: false,
|
2019-07-01 15:45:29 +01:00
|
|
|
}
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
2020-04-04 11:50:50 +01:00
|
|
|
/// must be called whenever this object is instanciated
|
|
|
|
pub fn created(&mut self) {
|
|
|
|
let ptr = SysBusPtr::new(self as *mut SysBus);
|
|
|
|
// HACK
|
|
|
|
self.io.set_sysbus_ptr(ptr.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn on_waitcnt_written(&mut self, waitcnt: WaitControl) {
|
|
|
|
self.cycle_luts.update_gamepak_waitstates(waitcnt);
|
|
|
|
}
|
|
|
|
|
2020-03-28 12:45:16 +00:00
|
|
|
#[inline(always)]
|
2020-04-04 11:50:50 +01:00
|
|
|
pub fn get_cycles(
|
|
|
|
&self,
|
|
|
|
addr: Addr,
|
|
|
|
access: MemoryAccessType,
|
|
|
|
width: MemoryAccessWidth,
|
|
|
|
) -> usize {
|
|
|
|
use MemoryAccessType::*;
|
|
|
|
use MemoryAccessWidth::*;
|
|
|
|
let page = (addr >> 24) as usize;
|
|
|
|
|
|
|
|
// TODO optimize out by making the LUTs have 0x100 entries for each possible page ?
|
|
|
|
if page > 0xF {
|
|
|
|
// open bus
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
match width {
|
|
|
|
MemoryAccess8 | MemoryAccess16 => match access {
|
|
|
|
NonSeq => self.cycle_luts.n_cycles16[page],
|
|
|
|
Seq => self.cycle_luts.s_cycles16[page],
|
2020-01-26 00:06:44 +00:00
|
|
|
},
|
2020-04-04 11:50:50 +01:00
|
|
|
MemoryAccess32 => match access {
|
|
|
|
NonSeq => self.cycle_luts.n_cycles32[page],
|
|
|
|
Seq => self.cycle_luts.s_cycles32[page],
|
2020-01-26 00:06:44 +00:00
|
|
|
},
|
2019-08-07 07:50:33 +01:00
|
|
|
}
|
|
|
|
}
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Bus for SysBus {
|
|
|
|
fn read_32(&self, addr: Addr) -> u32 {
|
2020-04-10 19:32:31 +01:00
|
|
|
memory_map!(read(self, read_32, addr & !3))
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn read_16(&self, addr: Addr) -> u16 {
|
2020-04-10 19:32:31 +01:00
|
|
|
memory_map!(read(self, read_16, addr & !1))
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn read_8(&self, addr: Addr) -> u8 {
|
2020-04-10 19:32:31 +01:00
|
|
|
memory_map!(read(self, read_8, addr))
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
2019-07-15 05:30:52 +01:00
|
|
|
fn write_32(&mut self, addr: Addr, value: u32) {
|
2020-04-10 19:32:31 +01:00
|
|
|
memory_map!(write(self, write_32, addr & !3, value));
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
2019-07-15 05:30:52 +01:00
|
|
|
fn write_16(&mut self, addr: Addr, value: u16) {
|
2020-04-10 19:32:31 +01:00
|
|
|
memory_map!(write(self, write_16, addr & !1, value));
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
|
|
|
|
2019-07-15 05:30:52 +01:00
|
|
|
fn write_8(&mut self, addr: Addr, value: u8) {
|
2020-04-10 19:32:31 +01:00
|
|
|
memory_map!(write(self, write_8, addr, value));
|
2019-06-30 14:59:19 +01:00
|
|
|
}
|
2019-06-25 00:10:09 +01:00
|
|
|
}
|
2020-05-20 19:22:30 +01:00
|
|
|
|
|
|
|
impl DmaNotifer for SysBus {
|
|
|
|
fn notify(&mut self, timing: u16) {
|
|
|
|
self.io.dmac.notify_from_gpu(timing);
|
|
|
|
}
|
|
|
|
}
|