mirror of
https://github.com/FranLMSP/snes.git
synced 2026-10-03 16:05:18 -04:00
WIP DMA transfer logic
This commit is contained in:
@@ -1,13 +1,17 @@
|
|||||||
use crate::ppu::PPU;
|
use crate::ppu::PPU;
|
||||||
|
use crate::cpu::dma;
|
||||||
use crate::cpu::internal_registers::InternalRegisters;
|
use crate::cpu::internal_registers::InternalRegisters;
|
||||||
use crate::rom::ROM;
|
use crate::rom::ROM;
|
||||||
use crate::rom::lo_rom::LoROM;
|
use crate::rom::lo_rom::LoROM;
|
||||||
|
|
||||||
|
use super::dma::DMATransferProps;
|
||||||
|
|
||||||
pub struct Bus {
|
pub struct Bus {
|
||||||
wram: [u8; 0x10000],
|
wram: [u8; 0x10000],
|
||||||
pub ppu: PPU,
|
pub ppu: PPU,
|
||||||
pub rom: Box<dyn ROM>,
|
pub rom: Box<dyn ROM>,
|
||||||
pub internal_registers: InternalRegisters,
|
pub internal_registers: InternalRegisters,
|
||||||
|
pub is_dma_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
@@ -26,6 +30,7 @@ impl Bus {
|
|||||||
ppu: PPU::new(),
|
ppu: PPU::new(),
|
||||||
rom: Box::new(LoROM::new()),
|
rom: Box::new(LoROM::new()),
|
||||||
internal_registers: InternalRegisters::new(),
|
internal_registers: InternalRegisters::new(),
|
||||||
|
is_dma_active: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +101,29 @@ impl Bus {
|
|||||||
MemoryMap::Cartridge => self.rom.write(address, value),
|
MemoryMap::Cartridge => self.rom.write(address, value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tick_dma(&mut self, cpu_cycles: usize) {
|
||||||
|
if !self.is_dma_active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dma_select = self.internal_registers.read_dma(dma::MDMAEN);
|
||||||
|
let mut active_channels = [false; 8];
|
||||||
|
for i in 0..8 {
|
||||||
|
active_channels[i] = (dma_select & (1 << i)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (channel, is_active) in active_channels.iter().enumerate() {
|
||||||
|
if !is_active {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let mut props = DMATransferProps::new_from_internal_registers(
|
||||||
|
&self.internal_registers,
|
||||||
|
channel as u8,
|
||||||
|
);
|
||||||
|
props.tick(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
use crate::cpu::internal_registers::InternalRegisters;
|
||||||
|
use crate::cpu::bus::Bus;
|
||||||
|
|
||||||
|
pub const MDMAEN: u16 = 0x420B; // Select General Purpose DMA Channel(s) and Start Transfer (W)
|
||||||
|
|
||||||
|
pub const DMAPX: u16 = 0x4300; // DMA/HDMA Parameters (R/W)
|
||||||
|
pub const BBADX: u16 = 0x4301; // DMA/HDMA I/O-Bus Address (PPU-Bus aka B-Bus) (R/W)
|
||||||
|
pub const A1TXL: u16 = 0x4302; // HDMA Table Start Address (low) / DMA Current Addr (low) (R/W)
|
||||||
|
pub const A1TXH: u16 = 0x4303; // HDMA Table Start Address (hi) / DMA Current Addr (hi) (R/W)
|
||||||
|
pub const A1BX: u16 = 0x4304; // HDMA Table Start Address (bank) / DMA Current Addr (bank) (R/W)
|
||||||
|
|
||||||
|
pub const DASXL: u16 = 0x4305; // Indirect HDMA Address (low) / DMA Byte-Counter (low) (R/W)
|
||||||
|
pub const DASXH: u16 = 0x4306; // Indirect HDMA Address (hi) / DMA Byte-Counter (hi) (R/W)
|
||||||
|
pub const DASBX: u16 = 0x4307; // Indirect HDMA Address (bank) (R/W)
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum DMAChannel {
|
||||||
|
Channel0,
|
||||||
|
Channel1,
|
||||||
|
Channel2,
|
||||||
|
Channel3,
|
||||||
|
Channel4,
|
||||||
|
Channel5,
|
||||||
|
Channel6,
|
||||||
|
Channel7,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TransferDirection {
|
||||||
|
AtoB,
|
||||||
|
BtoA,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AutoUpdateA {
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
Neither,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DMAAddressingMode {
|
||||||
|
Direct,
|
||||||
|
Indirect,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TransferFormat {
|
||||||
|
OneByteOneRegister, // Write once
|
||||||
|
TwoBytesTwoRegisters, // Write once
|
||||||
|
TwoBytesOneRegister, // Write twice
|
||||||
|
FourBytesTwoRegisters, // Write twice
|
||||||
|
FourBytesTwoRegistersSequential, // Write twice
|
||||||
|
FourBytesFourRegisters, // Write once
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DMATransferProps {
|
||||||
|
channel: DMAChannel,
|
||||||
|
channel_number: u8,
|
||||||
|
direction: TransferDirection,
|
||||||
|
addressing_mode: DMAAddressingMode,
|
||||||
|
auto_update_a_address: AutoUpdateA,
|
||||||
|
transfer_format: TransferFormat,
|
||||||
|
a_bus_address: u32,
|
||||||
|
b_bus_address: u8,
|
||||||
|
number_of_bytes: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DMATransferProps {
|
||||||
|
fn read_register(internal_registers: &InternalRegisters, register: u16, channel_number: u8) -> u8 {
|
||||||
|
internal_registers.read_dma((register & 0xFF0F) | ((channel_number as u16) << 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_register(internal_registers: &mut InternalRegisters, register: u16, channel_number: u8, val: u8) {
|
||||||
|
internal_registers.write((register & 0xFF0F) | ((channel_number as u16) << 4), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_internal_registers(
|
||||||
|
internal_registers: &InternalRegisters,
|
||||||
|
channel_number: u8,
|
||||||
|
) -> Self {
|
||||||
|
let channel = match channel_number {
|
||||||
|
0 => DMAChannel::Channel0,
|
||||||
|
1 => DMAChannel::Channel1,
|
||||||
|
2 => DMAChannel::Channel2,
|
||||||
|
3 => DMAChannel::Channel3,
|
||||||
|
4 => DMAChannel::Channel4,
|
||||||
|
5 => DMAChannel::Channel5,
|
||||||
|
6 => DMAChannel::Channel6,
|
||||||
|
7 => DMAChannel::Channel7,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let params = DMATransferProps::read_register(internal_registers, DMAPX, channel_number);
|
||||||
|
let direction = match params >> 7 == 1 {
|
||||||
|
false => TransferDirection::AtoB,
|
||||||
|
true => TransferDirection::BtoA,
|
||||||
|
};
|
||||||
|
let addressing_mode= match (params >> 6) & 1 == 1 {
|
||||||
|
false => DMAAddressingMode::Direct,
|
||||||
|
true => DMAAddressingMode::Indirect,
|
||||||
|
};
|
||||||
|
let auto_update_a_address = match (params >> 3) & 0b11 {
|
||||||
|
0b00 => AutoUpdateA::Increment,
|
||||||
|
0b10 => AutoUpdateA::Decrement,
|
||||||
|
0b01 | 0b11 => AutoUpdateA::Neither,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let transfer_format = match params & 0b111 {
|
||||||
|
0 => TransferFormat::OneByteOneRegister,
|
||||||
|
1 => TransferFormat::TwoBytesTwoRegisters,
|
||||||
|
2 => TransferFormat::TwoBytesOneRegister,
|
||||||
|
3 => TransferFormat::FourBytesTwoRegisters,
|
||||||
|
4 => TransferFormat::FourBytesFourRegisters,
|
||||||
|
5 => TransferFormat::FourBytesTwoRegistersSequential,
|
||||||
|
6 => TransferFormat::TwoBytesOneRegister,
|
||||||
|
7 => TransferFormat::FourBytesTwoRegisters,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let a_bus_address =
|
||||||
|
(DMATransferProps::read_register(internal_registers, A1BX, channel_number) as u32) << 16 |
|
||||||
|
(DMATransferProps::read_register(internal_registers, A1TXH, channel_number) as u32) << 8 |
|
||||||
|
DMATransferProps::read_register(internal_registers, A1TXL, channel_number) as u32;
|
||||||
|
let b_bus_address = DMATransferProps::read_register(internal_registers, BBADX, channel_number);
|
||||||
|
let number_of_bytes =
|
||||||
|
(DMATransferProps::read_register(internal_registers, DASXH, channel_number) as u16) << 8 |
|
||||||
|
DMATransferProps::read_register(internal_registers, DASXL, channel_number) as u16;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
channel: channel,
|
||||||
|
channel_number: channel_number,
|
||||||
|
direction: direction,
|
||||||
|
addressing_mode: addressing_mode,
|
||||||
|
auto_update_a_address: auto_update_a_address,
|
||||||
|
transfer_format: transfer_format,
|
||||||
|
a_bus_address: a_bus_address,
|
||||||
|
b_bus_address: b_bus_address,
|
||||||
|
number_of_bytes: number_of_bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_channel_state(&self, internal_registers: &mut InternalRegisters) {
|
||||||
|
// Update B Bus address
|
||||||
|
DMATransferProps::write_register(internal_registers, BBADX, self.channel_number, self.b_bus_address);
|
||||||
|
// Update A Bus address
|
||||||
|
DMATransferProps::write_register(internal_registers, A1BX, self.channel_number, (self.a_bus_address >> 16) as u8);
|
||||||
|
DMATransferProps::write_register(internal_registers, A1TXH, self.channel_number, (self.a_bus_address >> 8) as u8);
|
||||||
|
DMATransferProps::write_register(internal_registers, A1BX, self.channel_number, self.a_bus_address as u8);
|
||||||
|
// Update Byte count
|
||||||
|
DMATransferProps::write_register(internal_registers, DASXH, self.channel_number, (self.number_of_bytes >> 8) as u8);
|
||||||
|
DMATransferProps::write_register(internal_registers, DASXL, self.channel_number, self.number_of_bytes as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self, bus: &mut Bus) {
|
||||||
|
let source_address = match self.direction {
|
||||||
|
TransferDirection::AtoB => self.a_bus_address,
|
||||||
|
TransferDirection::BtoA => self.b_bus_address as u32,
|
||||||
|
};
|
||||||
|
let dest_address = match self.direction {
|
||||||
|
TransferDirection::AtoB => self.a_bus_address,
|
||||||
|
TransferDirection::BtoA => 0x002100 | (self.b_bus_address as u32),
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.transfer_format {
|
||||||
|
TransferFormat::OneByteOneRegister => {
|
||||||
|
// Transfer 1 byte xx ;eg. for WRAM (port 2180h)
|
||||||
|
let source_byte = bus.read(source_address);
|
||||||
|
bus.write(dest_address, source_byte)
|
||||||
|
},
|
||||||
|
TransferFormat::TwoBytesTwoRegisters => {
|
||||||
|
// Transfer 2 bytes xx, xx+1 ;eg. for VRAM (port 2118h/19h)
|
||||||
|
for index in 0..2 {
|
||||||
|
let source_byte = bus.read(source_address.wrapping_add(index));
|
||||||
|
bus.write(dest_address.wrapping_add(index), source_byte);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransferFormat::TwoBytesOneRegister => {
|
||||||
|
// Transfer 2 bytes xx, xx ;eg. for OAM or CGRAM
|
||||||
|
for index in 0..2 {
|
||||||
|
let source_byte = bus.read(source_address.wrapping_add(index));
|
||||||
|
bus.write(dest_address, source_byte);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransferFormat::FourBytesTwoRegisters => {
|
||||||
|
// Transfer 4 bytes xx, xx, xx+1, xx+1 ;eg. for BGnxOFS, M7x
|
||||||
|
for index in 0..4 {
|
||||||
|
let source_byte = bus.read(source_address.wrapping_add(index));
|
||||||
|
bus.write(dest_address.wrapping_add(index / 2), source_byte);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransferFormat::FourBytesFourRegisters => {
|
||||||
|
// Transfer 4 bytes xx, xx+1, xx+2, xx+3 ;eg. for BGnSC, Window, APU..
|
||||||
|
for index in 0..4 {
|
||||||
|
let source_byte = bus.read(source_address.wrapping_add(index));
|
||||||
|
bus.write(dest_address.wrapping_add(index), source_byte);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransferFormat::FourBytesTwoRegistersSequential => {
|
||||||
|
// Transfer 4 bytes xx, xx+1, xx, xx+1 ;whatever purpose, VRAM maybe
|
||||||
|
for index in 0..4 {
|
||||||
|
let source_byte = bus.read(source_address.wrapping_add(index));
|
||||||
|
bus.write(dest_address.wrapping_add(index & 1), source_byte);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.number_of_bytes -= 1;
|
||||||
|
match self.auto_update_a_address {
|
||||||
|
AutoUpdateA::Increment => self.a_bus_address += self.a_bus_address,
|
||||||
|
AutoUpdateA::Decrement => self.a_bus_address -= self.a_bus_address,
|
||||||
|
AutoUpdateA::Neither => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.save_channel_state(&mut bus.internal_registers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,10 @@ impl InternalRegisters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_dma(&self, address: u16) -> u8 {
|
||||||
|
self._read(address)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write(&mut self, address: u16, value: u8) {
|
pub fn write(&mut self, address: u16, value: u8) {
|
||||||
self._write(address, value);
|
self._write(address, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ pub mod cpu;
|
|||||||
pub use cpu::CPU;
|
pub use cpu::CPU;
|
||||||
pub mod bus;
|
pub mod bus;
|
||||||
pub mod registers;
|
pub mod registers;
|
||||||
|
pub mod dma;
|
||||||
pub mod instructions;
|
pub mod instructions;
|
||||||
pub mod vectors;
|
pub mod vectors;
|
||||||
pub mod cycles;
|
pub mod cycles;
|
||||||
|
|||||||
Reference in New Issue
Block a user