mirror of
https://github.com/rodamaral/lsnes.git
synced 2025-04-02 10:42:15 -04:00
373 lines
10 KiB
C++
Executable file
373 lines
10 KiB
C++
Executable file
#ifdef CPU_CPP
|
|
|
|
void CPU::dma_add_clocks(unsigned clocks) {
|
|
status.dma_clocks += clocks;
|
|
add_clocks(clocks);
|
|
}
|
|
|
|
//=============
|
|
//memory access
|
|
//=============
|
|
|
|
bool CPU::dma_transfer_valid(uint8 bbus, uint32 abus) {
|
|
//transfers from WRAM to WRAM are invalid; chip only has one address bus
|
|
if(bbus == 0x80 && ((abus & 0xfe0000) == 0x7e0000 || (abus & 0x40e000) == 0x0000)) return false;
|
|
return true;
|
|
}
|
|
|
|
bool CPU::dma_addr_valid(uint32 abus) {
|
|
//A-bus access to B-bus or S-CPU registers are invalid
|
|
if((abus & 0x40ff00) == 0x2100) return false; //$[00-3f|80-bf]:[2100-21ff]
|
|
if((abus & 0x40fe00) == 0x4000) return false; //$[00-3f|80-bf]:[4000-41ff]
|
|
if((abus & 0x40ffe0) == 0x4200) return false; //$[00-3f|80-bf]:[4200-421f]
|
|
if((abus & 0x40ff80) == 0x4300) return false; //$[00-3f|80-bf]:[4300-437f]
|
|
return true;
|
|
}
|
|
|
|
uint8 CPU::dma_read(uint32 abus) {
|
|
if(dma_addr_valid(abus) == false) return 0x00;
|
|
return bus.read(abus, false);
|
|
}
|
|
|
|
//simulate two-stage pipeline for DMA transfers; example:
|
|
//cycle 0: read N+0
|
|
//cycle 1: write N+0 & read N+1 (parallel; one on A-bus, one on B-bus)
|
|
//cycle 2: write N+1 & read N+2 (parallel)
|
|
//cycle 3: write N+2
|
|
void CPU::dma_write(bool valid, unsigned addr, uint8 data) {
|
|
if(pipe.valid) bus.write(pipe.addr, pipe.data);
|
|
pipe.valid = valid;
|
|
pipe.addr = addr;
|
|
pipe.data = data;
|
|
}
|
|
|
|
void CPU::dma_transfer(bool direction, uint8 bbus, uint32 abus) {
|
|
if(direction == 0) {
|
|
dma_add_clocks(4);
|
|
regs.mdr = dma_read(abus);
|
|
dma_add_clocks(4);
|
|
dma_write(dma_transfer_valid(bbus, abus), 0x2100 | bbus, regs.mdr);
|
|
} else {
|
|
dma_add_clocks(4);
|
|
regs.mdr = dma_transfer_valid(bbus, abus) ? bus.read(0x2100 | bbus, false) : 0x00;
|
|
dma_add_clocks(4);
|
|
dma_write(dma_addr_valid(abus), abus, regs.mdr);
|
|
}
|
|
}
|
|
|
|
//===================
|
|
//address calculation
|
|
//===================
|
|
|
|
uint8 CPU::dma_bbus(unsigned i, unsigned index) {
|
|
switch(channel[i].transfer_mode) { default:
|
|
case 0: return (channel[i].dest_addr); //0
|
|
case 1: return (channel[i].dest_addr + (index & 1)); //0,1
|
|
case 2: return (channel[i].dest_addr); //0,0
|
|
case 3: return (channel[i].dest_addr + ((index >> 1) & 1)); //0,0,1,1
|
|
case 4: return (channel[i].dest_addr + (index & 3)); //0,1,2,3
|
|
case 5: return (channel[i].dest_addr + (index & 1)); //0,1,0,1
|
|
case 6: return (channel[i].dest_addr); //0,0 [2]
|
|
case 7: return (channel[i].dest_addr + ((index >> 1) & 1)); //0,0,1,1 [3]
|
|
}
|
|
}
|
|
|
|
inline uint32 CPU::dma_addr(unsigned i) {
|
|
uint32 r = (channel[i].source_bank << 16) | (channel[i].source_addr);
|
|
|
|
if(channel[i].fixed_transfer == false) {
|
|
if(channel[i].reverse_transfer == false) {
|
|
channel[i].source_addr++;
|
|
} else {
|
|
channel[i].source_addr--;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
inline uint32 CPU::hdma_addr(unsigned i) {
|
|
return (channel[i].source_bank << 16) | (channel[i].hdma_addr++);
|
|
}
|
|
|
|
inline uint32 CPU::hdma_iaddr(unsigned i) {
|
|
return (channel[i].indirect_bank << 16) | (channel[i].indirect_addr++);
|
|
}
|
|
|
|
//==============
|
|
//channel status
|
|
//==============
|
|
|
|
uint8 CPU::dma_enabled_channels() {
|
|
uint8 r = 0;
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
if(channel[i].dma_enabled) r++;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
inline bool CPU::hdma_active(unsigned i) {
|
|
return (channel[i].hdma_enabled && !channel[i].hdma_completed);
|
|
}
|
|
|
|
inline bool CPU::hdma_active_after(unsigned i) {
|
|
for(unsigned n = i + 1; n < 8; n++) {
|
|
if(hdma_active(n) == true) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline uint8 CPU::hdma_enabled_channels() {
|
|
uint8 r = 0;
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
if(channel[i].hdma_enabled) r++;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
inline uint8 CPU::hdma_active_channels() {
|
|
uint8 r = 0;
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
if(hdma_active(i) == true) r++;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
//==============
|
|
//core functions
|
|
//==============
|
|
|
|
void CPU::dma_run() {
|
|
dma_add_clocks(8);
|
|
dma_write(false);
|
|
dma_edge();
|
|
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
if(channel[i].dma_enabled == false) continue;
|
|
dma_trace_start(i);
|
|
|
|
unsigned index = 0;
|
|
do {
|
|
dma_transfer(channel[i].direction, dma_bbus(i, index++), dma_addr(i));
|
|
dma_edge();
|
|
} while(channel[i].dma_enabled && --channel[i].transfer_size);
|
|
|
|
dma_add_clocks(8);
|
|
dma_write(false);
|
|
dma_edge();
|
|
|
|
dma_trace_end(i);
|
|
channel[i].dma_enabled = false;
|
|
}
|
|
|
|
status.irq_lock = true;
|
|
}
|
|
|
|
void CPU::hdma_update(unsigned i) {
|
|
dma_add_clocks(4);
|
|
regs.mdr = dma_read((channel[i].source_bank << 16) | channel[i].hdma_addr);
|
|
dma_add_clocks(4);
|
|
dma_write(false);
|
|
|
|
if((channel[i].line_counter & 0x7f) == 0) {
|
|
channel[i].line_counter = regs.mdr;
|
|
channel[i].hdma_addr++;
|
|
|
|
channel[i].hdma_completed = (channel[i].line_counter == 0);
|
|
channel[i].hdma_do_transfer = !channel[i].hdma_completed;
|
|
|
|
if(channel[i].indirect) {
|
|
dma_add_clocks(4);
|
|
regs.mdr = dma_read(hdma_addr(i));
|
|
channel[i].indirect_addr = regs.mdr << 8;
|
|
dma_add_clocks(4);
|
|
dma_write(false);
|
|
|
|
if(!channel[i].hdma_completed || hdma_active_after(i)) {
|
|
dma_add_clocks(4);
|
|
regs.mdr = dma_read(hdma_addr(i));
|
|
channel[i].indirect_addr >>= 8;
|
|
channel[i].indirect_addr |= regs.mdr << 8;
|
|
dma_add_clocks(4);
|
|
dma_write(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPU::hdma_run() {
|
|
dma_add_clocks(8);
|
|
dma_write(false);
|
|
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
if(hdma_active(i) == false) continue;
|
|
channel[i].dma_enabled = false; //HDMA run during DMA will stop DMA mid-transfer
|
|
|
|
if(channel[i].hdma_do_transfer) {
|
|
dma_trace_hdma(i);
|
|
static const unsigned transfer_length[8] = { 1, 2, 2, 4, 4, 4, 2, 4 };
|
|
unsigned length = transfer_length[channel[i].transfer_mode];
|
|
for(unsigned index = 0; index < length; index++) {
|
|
unsigned addr = channel[i].indirect == false ? hdma_addr(i) : hdma_iaddr(i);
|
|
dma_transfer(channel[i].direction, dma_bbus(i, index), addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
if(hdma_active(i) == false) continue;
|
|
|
|
channel[i].line_counter--;
|
|
channel[i].hdma_do_transfer = channel[i].line_counter & 0x80;
|
|
hdma_update(i);
|
|
}
|
|
|
|
status.irq_lock = true;
|
|
}
|
|
|
|
void CPU::hdma_init_reset() {
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
channel[i].hdma_completed = false;
|
|
channel[i].hdma_do_transfer = false;
|
|
}
|
|
}
|
|
|
|
void CPU::hdma_init() {
|
|
dma_add_clocks(8);
|
|
dma_write(false);
|
|
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
if(!channel[i].hdma_enabled) continue;
|
|
channel[i].dma_enabled = false; //HDMA init during DMA will stop DMA mid-transfer
|
|
|
|
channel[i].hdma_addr = channel[i].source_addr;
|
|
channel[i].line_counter = 0;
|
|
hdma_update(i);
|
|
}
|
|
|
|
status.irq_lock = true;
|
|
}
|
|
|
|
//==============
|
|
//initialization
|
|
//==============
|
|
|
|
void CPU::dma_power() {
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
channel[i].direction = 1;
|
|
channel[i].indirect = true;
|
|
channel[i].unused = true;
|
|
channel[i].reverse_transfer = true;
|
|
channel[i].fixed_transfer = true;
|
|
channel[i].transfer_mode = 7;
|
|
|
|
channel[i].dest_addr = 0xff;
|
|
|
|
channel[i].source_addr = 0xffff;
|
|
channel[i].source_bank = 0xff;
|
|
|
|
channel[i].transfer_size = 0xffff;
|
|
channel[i].indirect_bank = 0xff;
|
|
|
|
channel[i].hdma_addr = 0xffff;
|
|
channel[i].line_counter = 0xff;
|
|
channel[i].unknown = 0xff;
|
|
}
|
|
}
|
|
|
|
void CPU::dma_reset() {
|
|
for(unsigned i = 0; i < 8; i++) {
|
|
channel[i].dma_enabled = false;
|
|
channel[i].hdma_enabled = false;
|
|
|
|
channel[i].hdma_completed = false;
|
|
channel[i].hdma_do_transfer = false;
|
|
}
|
|
|
|
pipe.valid = false;
|
|
pipe.addr = 0;
|
|
pipe.data = 0;
|
|
}
|
|
|
|
size_t CPU::dma_trace_subaddr(char* buf, uint8 b_addr)
|
|
{
|
|
if(b_addr == 0x04 || b_addr == 0x38) {
|
|
return ppu.get_dma_oam_subaddr(buf);
|
|
}
|
|
if(b_addr == 0x22 || b_addr == 0x3B) {
|
|
return ppu.get_dma_cgram_subaddr(buf);
|
|
}
|
|
if(b_addr == 0x18 || b_addr == 0x19 || b_addr == 0x39 || b_addr == 0x3A) {
|
|
return ppu.get_dma_vram_subaddr(buf);
|
|
}
|
|
if(b_addr == 0x80) {
|
|
return sprintf(buf, "[%06x]", 0x7e0000 | status.wram_addr);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void CPU::dma_trace_start(unsigned i)
|
|
{
|
|
if(!dma_trace_fn) return;
|
|
char buf[512];
|
|
size_t ptr = 0;
|
|
unsigned bytes = channel[i].transfer_size;
|
|
if(!bytes) bytes = 0x10000;
|
|
ptr += sprintf(buf + ptr, "-- DMA%i %d(%x) bytes ", i, bytes, bytes);
|
|
if(channel[i].direction) {
|
|
//B->A
|
|
ptr += sprintf(buf + ptr, "%02x", channel[i].dest_addr);
|
|
ptr += dma_trace_subaddr(buf + ptr, channel[i].dest_addr);
|
|
ptr += sprintf(buf + ptr, "-> %02x%04x", channel[i].source_bank,
|
|
channel[i].source_addr);
|
|
} else {
|
|
//A->B
|
|
ptr += sprintf(buf + ptr, "%02x%04x -> %02x", channel[i].source_bank,
|
|
channel[i].source_addr, channel[i].dest_addr);
|
|
ptr += dma_trace_subaddr(buf + ptr, channel[i].dest_addr);
|
|
}
|
|
if(channel[i].fixed_transfer)
|
|
ptr += sprintf(buf + ptr, " fixed");
|
|
else if(channel[i].reverse_transfer)
|
|
ptr += sprintf(buf + ptr, " decrement");
|
|
else
|
|
ptr += sprintf(buf + ptr, " incrment");
|
|
ptr += sprintf(buf + ptr, " mode%d --", channel[i].transfer_mode);
|
|
dma_trace_fn(buf);
|
|
}
|
|
|
|
void CPU::dma_trace_end(unsigned i)
|
|
{
|
|
if(!dma_trace_fn) return;
|
|
if(!channel[i].transfer_size) return; //No message for complete DMA.
|
|
char buf[512];
|
|
size_t ptr = 0;
|
|
sprintf(buf, "-- DMA%i aborted with %d(0x%x) bytes remaining --", i,
|
|
(int)channel[i].transfer_size, (unsigned)channel[i].transfer_size);
|
|
dma_trace_fn(buf);
|
|
}
|
|
|
|
void CPU::dma_trace_hdma(unsigned i)
|
|
{
|
|
if(!dma_trace_fn) return;
|
|
char buf[512];
|
|
size_t ptr = 0;
|
|
unsigned addr = channel[i].indirect ?
|
|
(channel[i].indirect_bank << 16) | (channel[i].indirect_addr) :
|
|
(channel[i].source_bank << 16) | (channel[i].hdma_addr);
|
|
ptr += sprintf(buf + ptr, "-- HDMA%i %06x -> %02x", i, addr,
|
|
channel[i].dest_addr);
|
|
ptr += dma_trace_subaddr(buf + ptr, channel[i].dest_addr);
|
|
if(channel[i].indirect)
|
|
ptr += sprintf(buf + ptr, " indirect");
|
|
if(channel[i].fixed_transfer)
|
|
ptr += sprintf(buf + ptr, " fixed");
|
|
else if(channel[i].reverse_transfer)
|
|
ptr += sprintf(buf + ptr, " decrement");
|
|
else
|
|
ptr += sprintf(buf + ptr, " incrment");
|
|
ptr += sprintf(buf + ptr, " mode%d --", channel[i].transfer_mode);
|
|
dma_trace_fn(buf);
|
|
}
|
|
|
|
#endif
|