mirror of https://github.com/bsnes-emu/bsnes.git
Update to 20100810-2 release.
byuu says: I wrote a new CPU core from scratch. It has range-based IRQs, and is good enough even to run F-1 Grand Prix and Sink or Swim. It also uses a binary min-heap array for the timing priority queue. This resulted in a ~40% speedup. I also added in blargg's snes_spc library, which is an S-SMP + S-DSP emulator. I am still using his accurate DSP core, and not the fast one. This gives an additional ~10% speedup. THIS IS NOT PERFECT, THERE WILL BE BUGS! I already know that Tales of Phantasia and Star Ocean are hitting some edge cases. Now that it's fast enough, hopefully blargg can take a look at it. Something he couldn't test before because you can't rip SPCs of these games, so it's probably something simple. My CPU core also doesn't nail every last possible edge case. So things like Wild Guns and the two or three games that rely on NMI/IRQ hold aren't going to work ... yet. Be patient. The SuperFX and SA-1 cores are still cycle-accurate. It wouldn't hurt compatibility to reduce their precision a bit. End result is that you can now get well over 60fps in normal games even n a first-generation Intel Atom.
This commit is contained in:
parent
973ef89d4a
commit
9000bb4084
8
Makefile
8
Makefile
|
@ -60,14 +60,14 @@ endif
|
|||
|
||||
install:
|
||||
ifeq ($(platform),x)
|
||||
install -D -m 755 ../bsnes $(DESTDIR)$(prefix)/bin/bsnes
|
||||
install -D -m 644 data/bsnes.png $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png
|
||||
install -D -m 644 data/bsnes.desktop $(DESTDIR)$(prefix)/share/applications/bsnes.desktop
|
||||
install -D -m 755 out/$(snes) $(DESTDIR)$(prefix)/bin/$(snes)
|
||||
install -D -m 644 qt/data/bsnes.png $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png
|
||||
install -D -m 644 qt/data/bsnes.desktop $(DESTDIR)$(prefix)/share/applications/bsnes.desktop
|
||||
endif
|
||||
|
||||
uninstall:
|
||||
ifeq ($(platform),x)
|
||||
rm $(DESTDIR)$(prefix)/bin/bsnes
|
||||
rm $(DESTDIR)$(prefix)/bin/$(snes)
|
||||
rm $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png
|
||||
rm $(DESTDIR)$(prefix)/share/applications/bsnes.desktop
|
||||
endif
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
snes_objects := libco
|
||||
snes_objects += snes-system
|
||||
snes_objects += snes-cartridge snes-cheat
|
||||
snes_objects += snes-memory snes-cpucore snes-cpu snes-smpcore snes-smp snes-dsp snes-ppu
|
||||
snes_objects += snes-memory snes-cpucore snes-smpcore
|
||||
snes_objects += snes-cpu snes-smp snes-dsp snes-ppu
|
||||
snes_objects += snes-supergameboy snes-superfx snes-sa1
|
||||
snes_objects += snes-bsx snes-srtc snes-sdd1 snes-spc7110
|
||||
snes_objects += snes-cx4 snes-dsp1 snes-dsp2 snes-dsp3 snes-dsp4
|
||||
|
@ -13,15 +14,15 @@ obj/libco.o : libco/libco.c libco/*
|
|||
obj/libsnes.o: $(snes)/libsnes/libsnes.cpp $(snes)/libsnes/*
|
||||
|
||||
obj/snes-system.o : $(snes)/system/system.cpp $(call rwildcard,$(snes)/system/) $(call rwildcard,$(snes)/video/)
|
||||
obj/snes-memory.o : $(snes)/memory/memory.cpp $(snes)/memory/*
|
||||
obj/snes-memory.o : $(snes)/memory/memory.cpp $(call rwildcard,$(snes)/memory/)
|
||||
obj/snes-cpucore.o : $(snes)/cpu/core/core.cpp $(call rwildcard,$(snes)/cpu/core/)
|
||||
obj/snes-cpu.o : $(snes)/cpu/cpu.cpp $(snes)/cpu/*
|
||||
obj/snes-smpcore.o : $(snes)/smp/core/core.cpp $(call rwildcard,$(snes)/smp/core/)
|
||||
obj/snes-smp.o : $(snes)/smp/smp.cpp $(snes)/smp/*
|
||||
obj/snes-dsp.o : $(snes)/dsp/dsp.cpp $(snes)/dsp/*
|
||||
obj/snes-ppu.o : $(snes)/ppu/ppu.cpp $(snes)/ppu/*
|
||||
obj/snes-cartridge.o: $(snes)/cartridge/cartridge.cpp $(snes)/cartridge/*
|
||||
obj/snes-cheat.o : $(snes)/cheat/cheat.cpp $(snes)/cheat/*
|
||||
obj/snes-cpu.o : $(snes)/cpu/cpu.cpp $(call rwildcard,$(snes)/cpu/)
|
||||
obj/snes-smp.o : $(snes)/smp/smp.cpp $(call rwildcard,$(snes)/smp/)
|
||||
obj/snes-dsp.o : $(snes)/dsp/dsp.cpp $(call rwildcard,$(snes)/dsp/)
|
||||
obj/snes-ppu.o : $(snes)/ppu/ppu.cpp $(call rwildcard,$(snes)/ppu/)
|
||||
obj/snes-cartridge.o: $(snes)/cartridge/cartridge.cpp $(call rwilddcard,$(snes)/cartridge/)
|
||||
obj/snes-cheat.o : $(snes)/cheat/cheat.cpp $(call rwildcard,$(snes)/cheat/)
|
||||
|
||||
obj/snes-supergameboy.o: $(snes)/chip/supergameboy/supergameboy.cpp $(call rwildcard,$(snes)/chip/supergameboy/)
|
||||
obj/snes-superfx.o : $(snes)/chip/superfx/superfx.cpp $(call rwildcard,$(snes)/chip/superfx/)
|
||||
|
|
|
@ -62,26 +62,26 @@ void CPU::dma_transfer(bool direction, uint8 bbus, uint32 abus) {
|
|||
//===================
|
||||
|
||||
uint8 CPU::dma_bbus(unsigned i, unsigned index) {
|
||||
switch(channel[i].xfermode) { default:
|
||||
case 0: return (channel[i].destaddr); //0
|
||||
case 1: return (channel[i].destaddr + (index & 1)); //0,1
|
||||
case 2: return (channel[i].destaddr); //0,0
|
||||
case 3: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1
|
||||
case 4: return (channel[i].destaddr + (index & 3)); //0,1,2,3
|
||||
case 5: return (channel[i].destaddr + (index & 1)); //0,1,0,1
|
||||
case 6: return (channel[i].destaddr); //0,0 [2]
|
||||
case 7: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1 [3]
|
||||
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].srcbank << 16) | (channel[i].srcaddr);
|
||||
uint32 r = (channel[i].source_bank << 16) | (channel[i].source_addr);
|
||||
|
||||
if(channel[i].fixedxfer == false) {
|
||||
if(channel[i].reversexfer == false) {
|
||||
channel[i].srcaddr++;
|
||||
if(channel[i].fixed_transfer == false) {
|
||||
if(channel[i].reverse_transfer == false) {
|
||||
channel[i].source_addr++;
|
||||
} else {
|
||||
channel[i].srcaddr--;
|
||||
channel[i].source_addr--;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,11 +89,11 @@ inline uint32 CPU::dma_addr(unsigned i) {
|
|||
}
|
||||
|
||||
inline uint32 CPU::hdma_addr(unsigned i) {
|
||||
return (channel[i].srcbank << 16) | (channel[i].hdma_addr++);
|
||||
return (channel[i].source_bank << 16) | (channel[i].hdma_addr++);
|
||||
}
|
||||
|
||||
inline uint32 CPU::hdma_iaddr(unsigned i) {
|
||||
return (channel[i].hdma_ibank << 16) | (channel[i].hdma_iaddr++);
|
||||
return (channel[i].indirect_bank << 16) | (channel[i].indirect_addr++);
|
||||
}
|
||||
|
||||
//==============
|
||||
|
@ -151,7 +151,7 @@ void CPU::dma_run() {
|
|||
do {
|
||||
dma_transfer(channel[i].direction, dma_bbus(i, index++), dma_addr(i));
|
||||
dma_edge();
|
||||
} while(channel[i].dma_enabled && --channel[i].xfersize);
|
||||
} while(channel[i].dma_enabled && --channel[i].transfer_size);
|
||||
|
||||
dma_add_clocks(8);
|
||||
dma_write(false);
|
||||
|
@ -165,29 +165,29 @@ void CPU::dma_run() {
|
|||
|
||||
void CPU::hdma_update(unsigned i) {
|
||||
dma_add_clocks(4);
|
||||
regs.mdr = dma_read((channel[i].srcbank << 16) | channel[i].hdma_addr);
|
||||
regs.mdr = dma_read((channel[i].source_bank << 16) | channel[i].hdma_addr);
|
||||
dma_add_clocks(4);
|
||||
dma_write(false);
|
||||
|
||||
if((channel[i].hdma_line_counter & 0x7f) == 0) {
|
||||
channel[i].hdma_line_counter = regs.mdr;
|
||||
if((channel[i].line_counter & 0x7f) == 0) {
|
||||
channel[i].line_counter = regs.mdr;
|
||||
channel[i].hdma_addr++;
|
||||
|
||||
channel[i].hdma_completed = (channel[i].hdma_line_counter == 0);
|
||||
channel[i].hdma_completed = (channel[i].line_counter == 0);
|
||||
channel[i].hdma_do_transfer = !channel[i].hdma_completed;
|
||||
|
||||
if(channel[i].hdma_indirect) {
|
||||
if(channel[i].indirect) {
|
||||
dma_add_clocks(4);
|
||||
regs.mdr = dma_read(hdma_addr(i));
|
||||
channel[i].hdma_iaddr = regs.mdr << 8;
|
||||
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].hdma_iaddr >>= 8;
|
||||
channel[i].hdma_iaddr |= regs.mdr << 8;
|
||||
channel[i].indirect_addr >>= 8;
|
||||
channel[i].indirect_addr |= regs.mdr << 8;
|
||||
dma_add_clocks(4);
|
||||
dma_write(false);
|
||||
}
|
||||
|
@ -205,9 +205,9 @@ void CPU::hdma_run() {
|
|||
|
||||
if(channel[i].hdma_do_transfer) {
|
||||
static const unsigned transfer_length[8] = { 1, 2, 2, 4, 4, 4, 2, 4 };
|
||||
unsigned length = transfer_length[channel[i].xfermode];
|
||||
unsigned length = transfer_length[channel[i].transfer_mode];
|
||||
for(unsigned index = 0; index < length; index++) {
|
||||
unsigned addr = !channel[i].hdma_indirect ? hdma_addr(i) : hdma_iaddr(i);
|
||||
unsigned addr = channel[i].indirect == false ? hdma_addr(i) : hdma_iaddr(i);
|
||||
dma_transfer(channel[i].direction, dma_bbus(i, index), addr);
|
||||
}
|
||||
}
|
||||
|
@ -216,8 +216,8 @@ void CPU::hdma_run() {
|
|||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(hdma_active(i) == false) continue;
|
||||
|
||||
channel[i].hdma_line_counter--;
|
||||
channel[i].hdma_do_transfer = channel[i].hdma_line_counter & 0x80;
|
||||
channel[i].line_counter--;
|
||||
channel[i].hdma_do_transfer = channel[i].line_counter & 0x80;
|
||||
hdma_update(i);
|
||||
}
|
||||
|
||||
|
@ -226,7 +226,7 @@ void CPU::hdma_run() {
|
|||
|
||||
void CPU::hdma_init_reset() {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].hdma_completed = false;
|
||||
channel[i].hdma_completed = false;
|
||||
channel[i].hdma_do_transfer = false;
|
||||
}
|
||||
}
|
||||
|
@ -239,8 +239,8 @@ void CPU::hdma_init() {
|
|||
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].srcaddr;
|
||||
channel[i].hdma_line_counter = 0;
|
||||
channel[i].hdma_addr = channel[i].source_addr;
|
||||
channel[i].line_counter = 0;
|
||||
hdma_update(i);
|
||||
}
|
||||
|
||||
|
@ -253,35 +253,34 @@ void CPU::hdma_init() {
|
|||
|
||||
void CPU::dma_power() {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].dmap = 0xff;
|
||||
channel[i].direction = 1;
|
||||
channel[i].hdma_indirect = true;
|
||||
channel[i].reversexfer = true;
|
||||
channel[i].fixedxfer = true;
|
||||
channel[i].xfermode = 7;
|
||||
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].destaddr = 0xff;
|
||||
channel[i].dest_addr = 0xff;
|
||||
|
||||
channel[i].srcaddr = 0xffff;
|
||||
channel[i].srcbank = 0xff;
|
||||
channel[i].source_addr = 0xffff;
|
||||
channel[i].source_bank = 0xff;
|
||||
|
||||
//channel[i]::union { xfersize, hdma_iaddr };
|
||||
channel[i].xfersize = 0xffff;
|
||||
channel[i].hdma_ibank = 0xff;
|
||||
channel[i].transfer_size = 0xffff;
|
||||
channel[i].indirect_bank = 0xff;
|
||||
|
||||
channel[i].hdma_addr = 0xffff;
|
||||
channel[i].hdma_line_counter = 0xff;
|
||||
channel[i].unknown = 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].dma_enabled = false;
|
||||
channel[i].hdma_enabled = false;
|
||||
|
||||
channel[i].hdma_completed = false;
|
||||
channel[i].hdma_do_transfer = false;
|
||||
channel[i].hdma_completed = false;
|
||||
channel[i].hdma_do_transfer = false;
|
||||
}
|
||||
|
||||
pipe.valid = false;
|
||||
|
|
|
@ -6,36 +6,36 @@ struct {
|
|||
bool hdma_enabled;
|
||||
|
||||
//$43x0
|
||||
uint8 dmap;
|
||||
bool direction;
|
||||
bool hdma_indirect;
|
||||
bool reversexfer;
|
||||
bool fixedxfer;
|
||||
uint8 xfermode;
|
||||
bool indirect;
|
||||
bool unused;
|
||||
bool reverse_transfer;
|
||||
bool fixed_transfer;
|
||||
uint8 transfer_mode;
|
||||
|
||||
//$43x1
|
||||
uint8 destaddr;
|
||||
uint8 dest_addr;
|
||||
|
||||
//$43x2-$43x3
|
||||
uint16 srcaddr;
|
||||
uint16 source_addr;
|
||||
|
||||
//$43x4
|
||||
uint8 srcbank;
|
||||
uint8 source_bank;
|
||||
|
||||
//$43x5-$43x6
|
||||
union {
|
||||
uint16 xfersize;
|
||||
uint16 hdma_iaddr;
|
||||
uint16 transfer_size;
|
||||
uint16 indirect_addr;
|
||||
};
|
||||
|
||||
//$43x7
|
||||
uint8 hdma_ibank;
|
||||
uint8 indirect_bank;
|
||||
|
||||
//$43x8-$43x9
|
||||
uint16 hdma_addr;
|
||||
|
||||
//$43xa
|
||||
uint8 hdma_line_counter;
|
||||
uint8 line_counter;
|
||||
|
||||
//$43xb/$43xf
|
||||
uint8 unknown;
|
||||
|
|
|
@ -241,44 +241,49 @@ uint8 CPU::mmio_r421f() { return status.joy4h; } //JOY4H
|
|||
|
||||
//DMAPx
|
||||
uint8 CPU::mmio_r43x0(uint8 i) {
|
||||
return channel[i].dmap;
|
||||
return (channel[i].direction << 7)
|
||||
| (channel[i].indirect << 6)
|
||||
| (channel[i].unused << 5)
|
||||
| (channel[i].reverse_transfer << 4)
|
||||
| (channel[i].fixed_transfer << 3)
|
||||
| (channel[i].transfer_mode << 0);
|
||||
}
|
||||
|
||||
//BBADx
|
||||
uint8 CPU::mmio_r43x1(uint8 i) {
|
||||
return channel[i].destaddr;
|
||||
return channel[i].dest_addr;
|
||||
}
|
||||
|
||||
//A1TxL
|
||||
uint8 CPU::mmio_r43x2(uint8 i) {
|
||||
return channel[i].srcaddr;
|
||||
return channel[i].source_addr;
|
||||
}
|
||||
|
||||
//A1TxH
|
||||
uint8 CPU::mmio_r43x3(uint8 i) {
|
||||
return channel[i].srcaddr >> 8;
|
||||
return channel[i].source_addr >> 8;
|
||||
}
|
||||
|
||||
//A1Bx
|
||||
uint8 CPU::mmio_r43x4(uint8 i) {
|
||||
return channel[i].srcbank;
|
||||
return channel[i].source_bank;
|
||||
}
|
||||
|
||||
//DASxL
|
||||
//union { uint16 xfersize; uint16 hdma_iaddr; };
|
||||
//union { uint16 transfer_size; uint16 indirect_addr; };
|
||||
uint8 CPU::mmio_r43x5(uint8 i) {
|
||||
return channel[i].xfersize;
|
||||
return channel[i].transfer_size;
|
||||
}
|
||||
|
||||
//DASxH
|
||||
//union { uint16 xfersize; uint16 hdma_iaddr; };
|
||||
//union { uint16 transfer_size; uint16 indirect_addr; };
|
||||
uint8 CPU::mmio_r43x6(uint8 i) {
|
||||
return channel[i].xfersize >> 8;
|
||||
return channel[i].transfer_size >> 8;
|
||||
}
|
||||
|
||||
//DASBx
|
||||
uint8 CPU::mmio_r43x7(uint8 i) {
|
||||
return channel[i].hdma_ibank;
|
||||
return channel[i].indirect_bank;
|
||||
}
|
||||
|
||||
//A2AxL
|
||||
|
@ -293,7 +298,7 @@ uint8 CPU::mmio_r43x9(uint8 i) {
|
|||
|
||||
//NTRLx
|
||||
uint8 CPU::mmio_r43xa(uint8 i) {
|
||||
return channel[i].hdma_line_counter;
|
||||
return channel[i].line_counter;
|
||||
}
|
||||
|
||||
//???
|
||||
|
@ -303,49 +308,49 @@ uint8 CPU::mmio_r43xb(uint8 i) {
|
|||
|
||||
//DMAPx
|
||||
void CPU::mmio_w43x0(uint8 i, uint8 data) {
|
||||
channel[i].dmap = data;
|
||||
channel[i].direction = data & 0x80;
|
||||
channel[i].hdma_indirect = data & 0x40;
|
||||
channel[i].reversexfer = data & 0x10;
|
||||
channel[i].fixedxfer = data & 0x08;
|
||||
channel[i].xfermode = data & 7;
|
||||
channel[i].direction = data & 0x80;
|
||||
channel[i].indirect = data & 0x40;
|
||||
channel[i].unused = data & 0x20;
|
||||
channel[i].reverse_transfer = data & 0x10;
|
||||
channel[i].fixed_transfer = data & 0x08;
|
||||
channel[i].transfer_mode = data & 0x07;
|
||||
}
|
||||
|
||||
//DDBADx
|
||||
void CPU::mmio_w43x1(uint8 i, uint8 data) {
|
||||
channel[i].destaddr = data;
|
||||
channel[i].dest_addr = data;
|
||||
}
|
||||
|
||||
//A1TxL
|
||||
void CPU::mmio_w43x2(uint8 i, uint8 data) {
|
||||
channel[i].srcaddr = (channel[i].srcaddr & 0xff00) | (data);
|
||||
channel[i].source_addr = (channel[i].source_addr & 0xff00) | (data);
|
||||
}
|
||||
|
||||
//A1TxH
|
||||
void CPU::mmio_w43x3(uint8 i, uint8 data) {
|
||||
channel[i].srcaddr = (channel[i].srcaddr & 0x00ff) | (data << 8);
|
||||
channel[i].source_addr = (channel[i].source_addr & 0x00ff) | (data << 8);
|
||||
}
|
||||
|
||||
//A1Bx
|
||||
void CPU::mmio_w43x4(uint8 i, uint8 data) {
|
||||
channel[i].srcbank = data;
|
||||
channel[i].source_bank = data;
|
||||
}
|
||||
|
||||
//DASxL
|
||||
//union { uint16 xfersize; uint16 hdma_iaddr; };
|
||||
//union { uint16 transfer_size; uint16 indirect_addr; };
|
||||
void CPU::mmio_w43x5(uint8 i, uint8 data) {
|
||||
channel[i].xfersize = (channel[i].xfersize & 0xff00) | (data);
|
||||
channel[i].transfer_size = (channel[i].transfer_size & 0xff00) | (data);
|
||||
}
|
||||
|
||||
//DASxH
|
||||
//union { uint16 xfersize; uint16 hdma_iaddr; };
|
||||
//union { uint16 transfer_size; uint16 indirect_addr; };
|
||||
void CPU::mmio_w43x6(uint8 i, uint8 data) {
|
||||
channel[i].xfersize = (channel[i].xfersize & 0x00ff) | (data << 8);
|
||||
channel[i].transfer_size = (channel[i].transfer_size & 0x00ff) | (data << 8);
|
||||
}
|
||||
|
||||
//DASBx
|
||||
void CPU::mmio_w43x7(uint8 i, uint8 data) {
|
||||
channel[i].hdma_ibank = data;
|
||||
channel[i].indirect_bank = data;
|
||||
}
|
||||
|
||||
//A2AxL
|
||||
|
@ -360,7 +365,7 @@ void CPU::mmio_w43x9(uint8 i, uint8 data) {
|
|||
|
||||
//NTRLx
|
||||
void CPU::mmio_w43xa(uint8 i, uint8 data) {
|
||||
channel[i].hdma_line_counter = data;
|
||||
channel[i].line_counter = data;
|
||||
}
|
||||
|
||||
//???
|
||||
|
|
|
@ -87,19 +87,19 @@ void CPU::serialize(serializer &s) {
|
|||
for(unsigned i = 0; i < 8; i++) {
|
||||
s.integer(channel[i].dma_enabled);
|
||||
s.integer(channel[i].hdma_enabled);
|
||||
s.integer(channel[i].dmap);
|
||||
s.integer(channel[i].direction);
|
||||
s.integer(channel[i].hdma_indirect);
|
||||
s.integer(channel[i].reversexfer);
|
||||
s.integer(channel[i].fixedxfer);
|
||||
s.integer(channel[i].xfermode);
|
||||
s.integer(channel[i].destaddr);
|
||||
s.integer(channel[i].srcaddr);
|
||||
s.integer(channel[i].srcbank);
|
||||
s.integer(channel[i].xfersize);
|
||||
s.integer(channel[i].hdma_ibank);
|
||||
s.integer(channel[i].indirect);
|
||||
s.integer(channel[i].unused);
|
||||
s.integer(channel[i].reverse_transfer);
|
||||
s.integer(channel[i].fixed_transfer);
|
||||
s.integer(channel[i].transfer_mode);
|
||||
s.integer(channel[i].dest_addr);
|
||||
s.integer(channel[i].source_addr);
|
||||
s.integer(channel[i].source_bank);
|
||||
s.integer(channel[i].transfer_size);
|
||||
s.integer(channel[i].indirect_bank);
|
||||
s.integer(channel[i].hdma_addr);
|
||||
s.integer(channel[i].hdma_line_counter);
|
||||
s.integer(channel[i].line_counter);
|
||||
s.integer(channel[i].unknown);
|
||||
s.integer(channel[i].hdma_completed);
|
||||
s.integer(channel[i].hdma_do_transfer);
|
||||
|
|
|
@ -3,18 +3,12 @@
|
|||
#define CPU_CPP
|
||||
namespace SNES {
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#include "debugger/debugger.cpp"
|
||||
CPUDebugger cpu;
|
||||
#else
|
||||
CPU cpu;
|
||||
#endif
|
||||
CPU cpu;
|
||||
|
||||
#include "serialization.cpp"
|
||||
#include "dma/dma.cpp"
|
||||
#include "memory/memory.cpp"
|
||||
#include "mmio/mmio.cpp"
|
||||
#include "timing/timing.cpp"
|
||||
#include "dma.cpp"
|
||||
#include "memory.cpp"
|
||||
#include "mmio.cpp"
|
||||
#include "timing.cpp"
|
||||
|
||||
void CPU::step(unsigned clocks) {
|
||||
smp.clock -= clocks * (uint64)smp.frequency;
|
||||
|
@ -26,7 +20,7 @@ void CPU::step(unsigned clocks) {
|
|||
}
|
||||
|
||||
void CPU::synchronize_smp() {
|
||||
if(smp.clock < 0) co_switch(smp.thread);
|
||||
while(smp.clock < 0) smp.run();
|
||||
}
|
||||
|
||||
void CPU::synchronize_ppu() {
|
||||
|
@ -49,57 +43,41 @@ void CPU::enter() {
|
|||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(status.interrupt_pending) {
|
||||
status.interrupt_pending = false;
|
||||
if(status.nmi_pending) {
|
||||
status.nmi_pending = false;
|
||||
status.interrupt_vector = (regs.e == false ? 0xffea : 0xfffa);
|
||||
op_irq();
|
||||
} else if(status.irq_pending) {
|
||||
status.irq_pending = false;
|
||||
status.interrupt_vector = (regs.e == false ? 0xffee : 0xfffe);
|
||||
op_irq();
|
||||
} else if(status.reset_pending) {
|
||||
status.reset_pending = false;
|
||||
add_clocks(186);
|
||||
regs.pc.l = bus.read(0xfffc);
|
||||
regs.pc.h = bus.read(0xfffd);
|
||||
}
|
||||
if(status.nmi_pending) {
|
||||
status.nmi_pending = false;
|
||||
op_irq(regs.e == false ? 0xffea : 0xfffa);
|
||||
}
|
||||
|
||||
op_step();
|
||||
if(status.irq_pending) {
|
||||
status.irq_pending = false;
|
||||
op_irq(regs.e == false ? 0xffee : 0xfffe);
|
||||
}
|
||||
|
||||
(this->*opcode_table[op_readpc()])();
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::op_step() {
|
||||
(this->*opcode_table[op_readpc()])();
|
||||
}
|
||||
|
||||
void CPU::op_irq() {
|
||||
void CPU::op_irq(uint16 vector) {
|
||||
op_read(regs.pc.d);
|
||||
op_io();
|
||||
if(!regs.e) op_writestack(regs.pc.b);
|
||||
op_writestack(regs.pc.h);
|
||||
op_writestack(regs.pc.l);
|
||||
op_writestack(regs.e ? (regs.p & ~0x10) : regs.p);
|
||||
rd.l = op_read(status.interrupt_vector + 0);
|
||||
rd.l = op_read(vector + 0);
|
||||
regs.pc.b = 0x00;
|
||||
regs.p.i = 1;
|
||||
regs.p.d = 0;
|
||||
rd.h = op_read(status.interrupt_vector + 1);
|
||||
regs.p.i = 1;
|
||||
regs.p.d = 0;
|
||||
rd.h = op_read(vector + 1);
|
||||
regs.pc.w = rd.w;
|
||||
}
|
||||
|
||||
void CPU::power() {
|
||||
cpu_version = config.cpu.version;
|
||||
|
||||
regs.a = regs.x = regs.y = 0x0000;
|
||||
regs.a = 0x0000;
|
||||
regs.x = 0x0000;
|
||||
regs.y = 0x0000;
|
||||
regs.s = 0x01ff;
|
||||
|
||||
mmio_power();
|
||||
dma_power();
|
||||
timing_power();
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
|
@ -108,30 +86,62 @@ void CPU::reset() {
|
|||
coprocessors.reset();
|
||||
PPUcounter::reset();
|
||||
|
||||
//note: some registers are not fully reset by SNES
|
||||
regs.pc = 0x000000;
|
||||
regs.x.h = 0x00;
|
||||
regs.y.h = 0x00;
|
||||
regs.s.h = 0x01;
|
||||
regs.d = 0x0000;
|
||||
regs.db = 0x00;
|
||||
regs.p = 0x34;
|
||||
regs.e = 1;
|
||||
regs.mdr = 0x00;
|
||||
regs.wai = false;
|
||||
regs.pc = 0x000000;
|
||||
regs.x.h = 0x00;
|
||||
regs.y.h = 0x00;
|
||||
regs.s.h = 0x01;
|
||||
regs.d = 0x0000;
|
||||
regs.db = 0x00;
|
||||
regs.p = 0x34;
|
||||
regs.e = 1;
|
||||
regs.mdr = 0x00;
|
||||
regs.wai = false;
|
||||
update_table();
|
||||
|
||||
mmio_reset();
|
||||
dma_reset();
|
||||
timing_reset();
|
||||
regs.pc.l = bus.read(0xfffc);
|
||||
regs.pc.h = bus.read(0xfffd);
|
||||
regs.pc.b = 0x00;
|
||||
|
||||
apu_port[0] = 0x00;
|
||||
apu_port[1] = 0x00;
|
||||
apu_port[2] = 0x00;
|
||||
apu_port[3] = 0x00;
|
||||
status.nmi_valid = false;
|
||||
status.nmi_line = false;
|
||||
status.nmi_transition = false;
|
||||
status.nmi_pending = false;
|
||||
|
||||
status.irq_valid = false;
|
||||
status.irq_line = false;
|
||||
status.irq_transition = false;
|
||||
status.irq_pending = false;
|
||||
|
||||
status.hdma_pending = false;
|
||||
|
||||
status.wram_addr = 0x000000;
|
||||
|
||||
status.joypad_strobe_latch = 0;
|
||||
|
||||
status.nmi_enabled = false;
|
||||
status.virq_enabled = false;
|
||||
status.hirq_enabled = false;
|
||||
status.auto_joypad_poll_enabled = false;
|
||||
|
||||
status.pio = 0xff;
|
||||
|
||||
status.htime = 0x0000;
|
||||
status.vtime = 0x0000;
|
||||
|
||||
status.rom_speed = 8;
|
||||
|
||||
status.joy1l = status.joy1h = 0x00;
|
||||
status.joy2l = status.joy2h = 0x00;
|
||||
status.joy3l = status.joy3h = 0x00;
|
||||
status.joy4l = status.joy4h = 0x00;
|
||||
|
||||
dma_reset();
|
||||
}
|
||||
|
||||
CPU::CPU() {
|
||||
void CPU::serialize(serializer &s) {
|
||||
}
|
||||
|
||||
CPU::CPU() : queue(512, { &CPU::queue_event, this }) {
|
||||
PPUcounter::scanline = { &CPU::scanline, this };
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include <nall/priorityqueue.hpp>
|
||||
|
||||
class CPU : public Processor, public CPUcore, public PPUcounter, public MMIO {
|
||||
public:
|
||||
array<Processor*> coprocessors;
|
||||
|
@ -8,9 +10,15 @@ public:
|
|||
|
||||
uint8 pio();
|
||||
bool joylatch();
|
||||
alwaysinline bool interrupt_pending() { return status.interrupt_pending; }
|
||||
alwaysinline uint8 port_read(uint8 port) { return apu_port[port & 3]; }
|
||||
alwaysinline void port_write(uint8 port, uint8 data) { apu_port[port & 3] = data; }
|
||||
bool interrupt_pending();
|
||||
uint8 port_read(uint8 port);
|
||||
void port_write(uint8 port, uint8 data);
|
||||
uint8 mmio_read(unsigned addr);
|
||||
void mmio_write(unsigned addr, uint8 data);
|
||||
|
||||
void op_io();
|
||||
uint8 op_read(unsigned addr);
|
||||
void op_write(unsigned addr, uint8 data);
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
|
@ -20,113 +28,113 @@ public:
|
|||
~CPU();
|
||||
|
||||
private:
|
||||
#include "dma/dma.hpp"
|
||||
#include "memory/memory.hpp"
|
||||
#include "mmio/mmio.hpp"
|
||||
#include "timing/timing.hpp"
|
||||
//cpu
|
||||
static void Enter();
|
||||
void enter();
|
||||
void op_irq(uint16 vector);
|
||||
|
||||
uint8 cpu_version;
|
||||
//timing
|
||||
nall::priority_queue<unsigned> queue;
|
||||
void queue_event(unsigned id);
|
||||
void last_cycle();
|
||||
void add_clocks(unsigned clocks);
|
||||
void add_time(unsigned clocks);
|
||||
void scanline();
|
||||
void run_auto_joypad_poll();
|
||||
|
||||
//memory
|
||||
unsigned speed(unsigned addr) const;
|
||||
|
||||
//dma
|
||||
bool dma_transfer_valid(uint8 bbus, unsigned abus);
|
||||
bool dma_addr_valid(unsigned abus);
|
||||
uint8 dma_read(unsigned abus);
|
||||
void dma_write(bool valid, unsigned addr, uint8 data);
|
||||
void dma_transfer(bool direction, uint8 bbus, unsigned abus);
|
||||
uint8 dma_bbus(unsigned i, unsigned index);
|
||||
unsigned dma_addr(unsigned i);
|
||||
unsigned hdma_addr(unsigned i);
|
||||
unsigned hdma_iaddr(unsigned i);
|
||||
void dma_run();
|
||||
void hdma_update(unsigned i);
|
||||
void hdma_run();
|
||||
void hdma_init();
|
||||
void dma_reset();
|
||||
|
||||
//registers
|
||||
uint8 port_data[4];
|
||||
|
||||
struct Channel {
|
||||
bool dma_enabled;
|
||||
bool hdma_enabled;
|
||||
|
||||
bool direction;
|
||||
bool indirect;
|
||||
bool unused;
|
||||
bool reverse_transfer;
|
||||
bool fixed_transfer;
|
||||
uint8 transfer_mode;
|
||||
|
||||
uint8 dest_addr;
|
||||
uint16 source_addr;
|
||||
uint8 source_bank;
|
||||
|
||||
union {
|
||||
uint16 transfer_size;
|
||||
uint16 indirect_addr;
|
||||
};
|
||||
|
||||
uint8 indirect_bank;
|
||||
uint16 hdma_addr;
|
||||
uint8 line_counter;
|
||||
uint8 unknown;
|
||||
|
||||
bool hdma_completed;
|
||||
bool hdma_do_transfer;
|
||||
} channel[8];
|
||||
|
||||
struct Status {
|
||||
bool interrupt_pending;
|
||||
uint16 interrupt_vector;
|
||||
|
||||
unsigned clock_count;
|
||||
unsigned line_clocks;
|
||||
|
||||
//timing
|
||||
bool irq_lock;
|
||||
|
||||
unsigned dram_refresh_position;
|
||||
bool dram_refreshed;
|
||||
|
||||
unsigned hdma_init_position;
|
||||
bool hdma_init_triggered;
|
||||
|
||||
unsigned hdma_position;
|
||||
bool hdma_triggered;
|
||||
|
||||
bool nmi_valid;
|
||||
bool nmi_line;
|
||||
bool nmi_transition;
|
||||
bool nmi_pending;
|
||||
bool nmi_hold;
|
||||
|
||||
bool irq_valid;
|
||||
bool irq_line;
|
||||
bool irq_transition;
|
||||
bool irq_pending;
|
||||
bool irq_hold;
|
||||
|
||||
bool reset_pending;
|
||||
|
||||
//DMA
|
||||
bool dma_active;
|
||||
unsigned dma_counter;
|
||||
unsigned dma_clocks;
|
||||
bool dma_pending;
|
||||
bool hdma_pending;
|
||||
bool hdma_mode; //0 = init, 1 = run
|
||||
|
||||
//MMIO
|
||||
//$2181-$2183
|
||||
uint32 wram_addr;
|
||||
unsigned wram_addr;
|
||||
|
||||
//$4016-$4017
|
||||
bool joypad_strobe_latch;
|
||||
uint32 joypad1_bits;
|
||||
uint32 joypad2_bits;
|
||||
|
||||
//$4200
|
||||
bool nmi_enabled;
|
||||
bool hirq_enabled, virq_enabled;
|
||||
bool auto_joypad_poll;
|
||||
bool virq_enabled;
|
||||
bool hirq_enabled;
|
||||
bool auto_joypad_poll_enabled;
|
||||
|
||||
//$4201
|
||||
uint8 pio;
|
||||
|
||||
//$4202-$4203
|
||||
uint8 wrmpya;
|
||||
uint8 wrmpyb;
|
||||
|
||||
//$4204-$4206
|
||||
uint16 wrdiva;
|
||||
uint8 wrdivb;
|
||||
uint8 wrdivb;
|
||||
|
||||
//$4207-$420a
|
||||
uint16 hirq_pos, virq_pos;
|
||||
uint16 htime;
|
||||
uint16 vtime;
|
||||
|
||||
//$420d
|
||||
unsigned rom_speed;
|
||||
|
||||
//$4214-$4217
|
||||
uint16 rddiv;
|
||||
uint16 rdmpy;
|
||||
|
||||
//$4218-$421f
|
||||
uint8 joy1l, joy1h;
|
||||
uint8 joy2l, joy2h;
|
||||
uint8 joy3l, joy3h;
|
||||
uint8 joy4l, joy4h;
|
||||
} status;
|
||||
|
||||
struct ALU {
|
||||
unsigned mpyctr;
|
||||
unsigned divctr;
|
||||
unsigned shift;
|
||||
} alu;
|
||||
|
||||
static void Enter();
|
||||
void enter();
|
||||
void op_irq();
|
||||
debugvirtual void op_step();
|
||||
|
||||
friend class CPUDebugger;
|
||||
};
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#include "debugger/debugger.hpp"
|
||||
extern CPUDebugger cpu;
|
||||
#else
|
||||
extern CPU cpu;
|
||||
#endif
|
||||
extern CPU cpu;
|
||||
|
|
|
@ -1,226 +0,0 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
bool CPUDebugger::property(unsigned id, string &name, string &value) {
|
||||
unsigned n = 0;
|
||||
|
||||
//internal
|
||||
if(id == n++) { name = "S-CPU MDR"; value = string("0x", strhex<2>(mdr())); return true; }
|
||||
|
||||
//$2181-2183
|
||||
if(id == n++) { name = "$2181-$2183"; value = ""; return true; }
|
||||
if(id == n++) { name = "WRAM Address"; value = string("0x", strhex<6>(wram_address())); return true; }
|
||||
|
||||
//$4016
|
||||
if(id == n++) { name = "$4016"; value = ""; return true; }
|
||||
if(id == n++) { name = "Joypad Strobe Latch"; value = joypad_strobe_latch(); return true; }
|
||||
|
||||
//$4200
|
||||
if(id == n++) { name = "$4200"; value = ""; return true; }
|
||||
if(id == n++) { name = "NMI Enable"; value = nmi_enable(); return true; }
|
||||
if(id == n++) { name = "H-IRQ Enable"; value = hirq_enable(); return true; }
|
||||
if(id == n++) { name = "V-IRQ Enable"; value = virq_enable(); return true; }
|
||||
if(id == n++) { name = "Auto Joypad Poll"; value = auto_joypad_poll(); return true; }
|
||||
|
||||
//$4201
|
||||
if(id == n++) { name = "$4201"; value = ""; return true; }
|
||||
if(id == n++) { name = "PIO"; value = string("0x", strhex<2>(pio_bits())); return true; }
|
||||
|
||||
//$4202
|
||||
if(id == n++) { name = "$4202"; value = ""; return true; }
|
||||
if(id == n++) { name = "Multiplicand"; value = string("0x", strhex<2>(multiplicand())); return true; }
|
||||
|
||||
//$4203
|
||||
if(id == n++) { name = "$4203"; value = ""; return true; }
|
||||
if(id == n++) { name = "Multiplier"; value = string("0x", strhex<2>(multiplier())); return true; }
|
||||
|
||||
//$4204-$4205
|
||||
if(id == n++) { name = "$4204-$4205"; value = ""; return true; }
|
||||
if(id == n++) { name = "Dividend"; value = string("0x", strhex<4>(dividend())); return true; }
|
||||
|
||||
//$4206
|
||||
if(id == n++) { name = "$4206"; value = ""; return true; }
|
||||
if(id == n++) { name = "Divisor"; value = string("0x", strhex<2>(divisor())); return true; }
|
||||
|
||||
//$4207-$4208
|
||||
if(id == n++) { name = "$4207-$4208"; value = ""; return true; }
|
||||
if(id == n++) { name = "H-Time"; value = string("0x", strhex<4>(htime())); return true; }
|
||||
|
||||
//$4209-$420a
|
||||
if(id == n++) { name = "$4209-$420a"; value = ""; return true; }
|
||||
if(id == n++) { name = "V-Time"; value = string("0x", strhex<4>(vtime())); return true; }
|
||||
|
||||
//$420b
|
||||
if(id == n++) { name = "$420b"; value = ""; return true; }
|
||||
if(id == n++) { name = "DMA Enable"; value = string("0x", strhex<2>(dma_enable())); return true; }
|
||||
|
||||
//$420c
|
||||
if(id == n++) { name = "$420c"; value = ""; return true; }
|
||||
if(id == n++) { name = "HDMA Enable"; value = string("0x", strhex<2>(hdma_enable())); return true; }
|
||||
|
||||
//$420d
|
||||
if(id == n++) { name = "$420d"; value = ""; return true; }
|
||||
if(id == n++) { name = "FastROM Enable"; value = fastrom_enable(); return true; }
|
||||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(id == n++) { name = string() << "DMA Channel " << i; return true; }
|
||||
|
||||
//$43x0
|
||||
if(id == n++) { name = "Direction"; value = dma_direction(i); return true; }
|
||||
if(id == n++) { name = "Indirect"; value = dma_indirect(i); return true; }
|
||||
if(id == n++) { name = "Reverse Transfer"; value = dma_reverse_transfer(i); return true; }
|
||||
if(id == n++) { name = "Fixed Transfer"; value = dma_fixed_transfer(i); return true; }
|
||||
if(id == n++) { name = "Transfer Mode"; value = dma_transfer_mode(i); return true; }
|
||||
|
||||
//$43x1
|
||||
if(id == n++) { name = "B-Bus Address"; value = string("0x", strhex<4>(dma_bbus_address(i))); return true; }
|
||||
|
||||
//$43x2-$43x3
|
||||
if(id == n++) { name = "A-Bus Address"; value = string("0x", strhex<4>(dma_abus_address(i))); return true; }
|
||||
|
||||
//$43x4
|
||||
if(id == n++) { name = "A-Bus Bank"; value = string("0x", strhex<2>(dma_abus_bank(i))); return true; }
|
||||
|
||||
//$43x5-$43x6
|
||||
if(id == n++) { name = "Transfer Size / Indirect Address"; value = string("0x", strhex<4>(dma_transfer_size(i))); return true; }
|
||||
|
||||
//$43x7
|
||||
if(id == n++) { name = "Indirect Bank"; value = string("0x", strhex<2>(dma_indirect_bank(i))); return true; }
|
||||
|
||||
//$43x8-$43x9
|
||||
if(id == n++) { name = "Table Address"; value = string("0x", strhex<4>(dma_table_address(i))); return true; }
|
||||
|
||||
//$43xa
|
||||
if(id == n++) { name = "Line Counter"; value = string("0x", strhex<2>(dma_line_counter(i))); return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CPUDebugger::op_step() {
|
||||
bool break_event = false;
|
||||
|
||||
usage[regs.pc] &= ~(UsageFlagM | UsageFlagX);
|
||||
usage[regs.pc] |= UsageExec | (regs.p.m << 1) | (regs.p.x << 0);
|
||||
opcode_pc = regs.pc;
|
||||
|
||||
if(debugger.step_cpu) {
|
||||
debugger.break_event = Debugger::BreakEvent::CPUStep;
|
||||
scheduler.exit(Scheduler::ExitReason::DebuggerEvent);
|
||||
} else {
|
||||
debugger.breakpoint_test(Debugger::Breakpoint::Source::CPUBus, Debugger::Breakpoint::Mode::Exec, regs.pc, 0x00);
|
||||
}
|
||||
|
||||
if(step_event) step_event();
|
||||
CPU::op_step();
|
||||
synchronize_smp();
|
||||
}
|
||||
|
||||
uint8 CPUDebugger::op_read(uint32 addr) {
|
||||
uint8 data = CPU::op_read(addr);
|
||||
usage[addr] |= UsageRead;
|
||||
debugger.breakpoint_test(Debugger::Breakpoint::Source::CPUBus, Debugger::Breakpoint::Mode::Read, addr, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
void CPUDebugger::op_write(uint32 addr, uint8 data) {
|
||||
CPU::op_write(addr, data);
|
||||
usage[addr] |= UsageWrite;
|
||||
usage[addr] &= ~UsageExec;
|
||||
debugger.breakpoint_test(Debugger::Breakpoint::Source::CPUBus, Debugger::Breakpoint::Mode::Write, addr, data);
|
||||
}
|
||||
|
||||
CPUDebugger::CPUDebugger() {
|
||||
usage = new uint8[1 << 24]();
|
||||
opcode_pc = 0x8000;
|
||||
}
|
||||
|
||||
CPUDebugger::~CPUDebugger() {
|
||||
delete[] usage;
|
||||
}
|
||||
|
||||
//internal
|
||||
unsigned CPUDebugger::mdr() { return regs.mdr; }
|
||||
|
||||
//$2181-$2183
|
||||
unsigned CPUDebugger::wram_address() { return status.wram_addr; }
|
||||
|
||||
//$4016
|
||||
bool CPUDebugger::joypad_strobe_latch() { return status.joypad_strobe_latch; }
|
||||
|
||||
//$4200
|
||||
bool CPUDebugger::nmi_enable() { return status.nmi_enabled; }
|
||||
bool CPUDebugger::hirq_enable() { return status.hirq_enabled; }
|
||||
bool CPUDebugger::virq_enable() { return status.virq_enabled; }
|
||||
bool CPUDebugger::auto_joypad_poll() { return status.auto_joypad_poll; }
|
||||
|
||||
//$4201
|
||||
unsigned CPUDebugger::pio_bits() { return status.pio; }
|
||||
|
||||
//$4202
|
||||
unsigned CPUDebugger::multiplicand() { return status.wrmpya; }
|
||||
|
||||
//$4203
|
||||
unsigned CPUDebugger::multiplier() { return status.wrmpyb; }
|
||||
|
||||
//$4204-$4205
|
||||
unsigned CPUDebugger::dividend() { return status.wrdiva; }
|
||||
|
||||
//$4206
|
||||
unsigned CPUDebugger::divisor() { return status.wrdivb; }
|
||||
|
||||
//$4207-$4208
|
||||
unsigned CPUDebugger::htime() { return status.hirq_pos; }
|
||||
|
||||
//$4209-$420a
|
||||
unsigned CPUDebugger::vtime() { return status.virq_pos; }
|
||||
|
||||
//$420b
|
||||
unsigned CPUDebugger::dma_enable() {
|
||||
unsigned result = 0;
|
||||
for(unsigned n = 0; n < 8; n++) {
|
||||
result |= channel[n].dma_enabled << n;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//$420c
|
||||
unsigned CPUDebugger::hdma_enable() {
|
||||
unsigned result = 0;
|
||||
for(unsigned n = 0; n < 8; n++) {
|
||||
result |= channel[n].hdma_enabled << n;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//$420d
|
||||
bool CPUDebugger::fastrom_enable() { return status.rom_speed; }
|
||||
|
||||
//$43x0
|
||||
bool CPUDebugger::dma_direction(unsigned n) { return channel[n].direction; }
|
||||
bool CPUDebugger::dma_indirect(unsigned n) { return channel[n].hdma_indirect; }
|
||||
bool CPUDebugger::dma_reverse_transfer(unsigned n) { return channel[n].reversexfer; }
|
||||
bool CPUDebugger::dma_fixed_transfer(unsigned n) { return channel[n].fixedxfer; }
|
||||
unsigned CPUDebugger::dma_transfer_mode(unsigned n) { return channel[n].xfermode; }
|
||||
|
||||
//$43x1
|
||||
unsigned CPUDebugger::dma_bbus_address(unsigned n) { return 0x2100 + channel[n].destaddr; }
|
||||
|
||||
//$43x2-$43x3
|
||||
unsigned CPUDebugger::dma_abus_address(unsigned n) { return channel[n].srcaddr; }
|
||||
|
||||
//$43x4
|
||||
unsigned CPUDebugger::dma_abus_bank(unsigned n) { return channel[n].srcbank; }
|
||||
|
||||
//$43x5-$43x6
|
||||
unsigned CPUDebugger::dma_transfer_size(unsigned n) { return channel[n].xfersize; }
|
||||
|
||||
//$43x7
|
||||
unsigned CPUDebugger::dma_indirect_bank(unsigned n) { return channel[n].hdma_ibank; }
|
||||
|
||||
//$43x8-$43x9
|
||||
unsigned CPUDebugger::dma_table_address(unsigned n) { return channel[n].hdma_addr; }
|
||||
|
||||
//$43xa
|
||||
unsigned CPUDebugger::dma_line_counter(unsigned n) { return channel[n].hdma_line_counter; }
|
||||
|
||||
#endif
|
|
@ -1,96 +0,0 @@
|
|||
class CPUDebugger : public CPU, public ChipDebugger {
|
||||
public:
|
||||
bool property(unsigned id, string &name, string &value);
|
||||
|
||||
function<void ()> step_event;
|
||||
|
||||
enum Usage {
|
||||
UsageRead = 0x80,
|
||||
UsageWrite = 0x40,
|
||||
UsageExec = 0x20,
|
||||
UsageFlagM = 0x02,
|
||||
UsageFlagX = 0x01,
|
||||
};
|
||||
uint8 *usage;
|
||||
uint32 opcode_pc; //points to the current opcode, used to backtrace on read/write breakpoints
|
||||
|
||||
void op_step();
|
||||
uint8 op_read(uint32 addr);
|
||||
void op_write(uint32 addr, uint8 data);
|
||||
|
||||
CPUDebugger();
|
||||
~CPUDebugger();
|
||||
|
||||
//internal
|
||||
unsigned mdr();
|
||||
|
||||
//$2181-$2183
|
||||
unsigned wram_address();
|
||||
|
||||
//$4016
|
||||
bool joypad_strobe_latch();
|
||||
|
||||
//$4200
|
||||
bool nmi_enable();
|
||||
bool hirq_enable();
|
||||
bool virq_enable();
|
||||
bool auto_joypad_poll();
|
||||
|
||||
//$4201
|
||||
unsigned pio_bits();
|
||||
|
||||
//$4202
|
||||
unsigned multiplicand();
|
||||
|
||||
//$4203
|
||||
unsigned multiplier();
|
||||
|
||||
//$4204-$4205
|
||||
unsigned dividend();
|
||||
|
||||
//$4206
|
||||
unsigned divisor();
|
||||
|
||||
//$4207-$4208
|
||||
unsigned htime();
|
||||
|
||||
//$4209-$420a
|
||||
unsigned vtime();
|
||||
|
||||
//$420b
|
||||
unsigned dma_enable();
|
||||
|
||||
//$420c
|
||||
unsigned hdma_enable();
|
||||
|
||||
//$420d
|
||||
bool fastrom_enable();
|
||||
|
||||
//$43x0
|
||||
bool dma_direction(unsigned);
|
||||
bool dma_indirect(unsigned);
|
||||
bool dma_reverse_transfer(unsigned);
|
||||
bool dma_fixed_transfer(unsigned);
|
||||
unsigned dma_transfer_mode(unsigned);
|
||||
|
||||
//$43x1
|
||||
unsigned dma_bbus_address(unsigned);
|
||||
|
||||
//$43x2-$43x3
|
||||
unsigned dma_abus_address(unsigned);
|
||||
|
||||
//$43x4
|
||||
unsigned dma_abus_bank(unsigned);
|
||||
|
||||
//$43x5-$43x6
|
||||
unsigned dma_transfer_size(unsigned);
|
||||
|
||||
//$43x7
|
||||
unsigned dma_indirect_bank(unsigned);
|
||||
|
||||
//$43x8-$43x9
|
||||
unsigned dma_table_address(unsigned);
|
||||
|
||||
//$43xa
|
||||
unsigned dma_line_counter(unsigned);
|
||||
};
|
|
@ -0,0 +1,184 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
bool CPU::dma_transfer_valid(uint8 bbus, unsigned 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(unsigned 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(unsigned abus) {
|
||||
if(dma_addr_valid(abus) == false) return 0x00;
|
||||
return bus.read(abus);
|
||||
}
|
||||
|
||||
void CPU::dma_write(bool valid, unsigned addr, uint8 data) {
|
||||
if(valid) bus.write(addr, data);
|
||||
}
|
||||
|
||||
void CPU::dma_transfer(bool direction, uint8 bbus, unsigned abus) {
|
||||
if(direction == 0) {
|
||||
uint8 data = dma_read(abus);
|
||||
add_clocks(8);
|
||||
dma_write(dma_transfer_valid(bbus, abus), 0x2100 | bbus, data);
|
||||
} else {
|
||||
uint8 data = dma_transfer_valid(bbus, abus) ? bus.read(0x2100 | bbus) : 0x00;
|
||||
add_clocks(8);
|
||||
dma_write(dma_addr_valid(abus), abus, data);
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
unsigned CPU::dma_addr(unsigned i) {
|
||||
unsigned result = (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 result;
|
||||
}
|
||||
|
||||
unsigned CPU::hdma_addr(unsigned i) {
|
||||
return (channel[i].source_bank << 16) | (channel[i].hdma_addr++);
|
||||
}
|
||||
|
||||
unsigned CPU::hdma_iaddr(unsigned i) {
|
||||
return (channel[i].indirect_bank << 16) | (channel[i].indirect_addr++);
|
||||
}
|
||||
|
||||
void CPU::dma_run() {
|
||||
add_clocks(16);
|
||||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(channel[i].dma_enabled == false) continue;
|
||||
add_clocks(8);
|
||||
|
||||
unsigned index = 0;
|
||||
do {
|
||||
dma_transfer(channel[i].direction, dma_bbus(i, index++), dma_addr(i));
|
||||
} while(channel[i].dma_enabled && --channel[i].transfer_size);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::hdma_update(unsigned i) {
|
||||
if((channel[i].line_counter & 0x7f) == 0) {
|
||||
channel[i].line_counter = dma_read(hdma_addr(i));
|
||||
channel[i].hdma_completed = (channel[i].line_counter == 0);
|
||||
channel[i].hdma_do_transfer = !channel[i].hdma_completed;
|
||||
add_clocks(8);
|
||||
|
||||
if(channel[i].indirect) {
|
||||
channel[i].indirect_addr = dma_read(hdma_addr(i)) << 0;
|
||||
add_clocks(8);
|
||||
channel[i].indirect_addr |= dma_read(hdma_addr(i)) << 8;
|
||||
add_clocks(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::hdma_run() {
|
||||
unsigned channels = 0;
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(channel[i].hdma_enabled) channels++;
|
||||
}
|
||||
if(channels == 0) return;
|
||||
|
||||
add_clocks(16);
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(channel[i].hdma_enabled == false || channel[i].hdma_completed == true) continue;
|
||||
channel[i].dma_enabled = false;
|
||||
|
||||
if(channel[i].hdma_do_transfer) {
|
||||
static const unsigned transfer_length[] = { 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(channel[i].hdma_enabled == false || channel[i].hdma_completed == true) continue;
|
||||
|
||||
channel[i].line_counter--;
|
||||
channel[i].hdma_do_transfer = channel[i].line_counter & 0x80;
|
||||
hdma_update(i);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::hdma_init() {
|
||||
unsigned channels = 0;
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].hdma_completed = false;
|
||||
channel[i].hdma_do_transfer = false;
|
||||
if(channel[i].hdma_enabled) channels++;
|
||||
}
|
||||
if(channels == 0) return;
|
||||
|
||||
add_clocks(16);
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(!channel[i].hdma_enabled) continue;
|
||||
channel[i].dma_enabled = false;
|
||||
|
||||
channel[i].hdma_addr = channel[i].source_addr;
|
||||
channel[i].line_counter = 0;
|
||||
hdma_update(i);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::dma_reset() {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].dma_enabled = false;
|
||||
channel[i].hdma_enabled = false;
|
||||
|
||||
channel[i].direction = 0;
|
||||
channel[i].indirect = false;
|
||||
channel[i].unused = false;
|
||||
channel[i].reverse_transfer = false;
|
||||
channel[i].fixed_transfer = false;
|
||||
channel[i].transfer_mode = 0x00;
|
||||
|
||||
channel[i].dest_addr = 0x0000;
|
||||
channel[i].source_addr = 0x0000;
|
||||
channel[i].source_bank = 0x00;
|
||||
|
||||
channel[i].transfer_size = 0x0000;
|
||||
channel[i].indirect_addr = 0x0000;
|
||||
|
||||
channel[i].indirect_bank = 0x00;
|
||||
channel[i].hdma_addr = 0x00;
|
||||
channel[i].line_counter = 0x00;
|
||||
channel[i].unknown = 0x00;
|
||||
|
||||
channel[i].hdma_completed = false;
|
||||
channel[i].hdma_do_transfer = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,292 +0,0 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
void CPU::dma_add_clocks(unsigned clocks) {
|
||||
status.dma_clocks += clocks;
|
||||
add_clocks(clocks);
|
||||
synchronize_ppu();
|
||||
synchronize_coprocessor();
|
||||
}
|
||||
|
||||
//=============
|
||||
//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);
|
||||
}
|
||||
|
||||
//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) : 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].xfermode) { default:
|
||||
case 0: return (channel[i].destaddr); //0
|
||||
case 1: return (channel[i].destaddr + (index & 1)); //0,1
|
||||
case 2: return (channel[i].destaddr); //0,0
|
||||
case 3: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1
|
||||
case 4: return (channel[i].destaddr + (index & 3)); //0,1,2,3
|
||||
case 5: return (channel[i].destaddr + (index & 1)); //0,1,0,1
|
||||
case 6: return (channel[i].destaddr); //0,0 [2]
|
||||
case 7: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1 [3]
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32 CPU::dma_addr(unsigned i) {
|
||||
uint32 r = (channel[i].srcbank << 16) | (channel[i].srcaddr);
|
||||
|
||||
if(channel[i].fixedxfer == false) {
|
||||
if(channel[i].reversexfer == false) {
|
||||
channel[i].srcaddr++;
|
||||
} else {
|
||||
channel[i].srcaddr--;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
inline uint32 CPU::hdma_addr(unsigned i) {
|
||||
return (channel[i].srcbank << 16) | (channel[i].hdma_addr++);
|
||||
}
|
||||
|
||||
inline uint32 CPU::hdma_iaddr(unsigned i) {
|
||||
return (channel[i].hdma_ibank << 16) | (channel[i].hdma_iaddr++);
|
||||
}
|
||||
|
||||
//==============
|
||||
//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;
|
||||
|
||||
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].xfersize);
|
||||
|
||||
dma_add_clocks(8);
|
||||
dma_write(false);
|
||||
dma_edge();
|
||||
|
||||
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].srcbank << 16) | channel[i].hdma_addr);
|
||||
dma_add_clocks(4);
|
||||
dma_write(false);
|
||||
|
||||
if((channel[i].hdma_line_counter & 0x7f) == 0) {
|
||||
channel[i].hdma_line_counter = regs.mdr;
|
||||
channel[i].hdma_addr++;
|
||||
|
||||
channel[i].hdma_completed = (channel[i].hdma_line_counter == 0);
|
||||
channel[i].hdma_do_transfer = !channel[i].hdma_completed;
|
||||
|
||||
if(channel[i].hdma_indirect) {
|
||||
dma_add_clocks(4);
|
||||
regs.mdr = dma_read(hdma_addr(i));
|
||||
channel[i].hdma_iaddr = 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].hdma_iaddr >>= 8;
|
||||
channel[i].hdma_iaddr |= 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) {
|
||||
static const unsigned transfer_length[8] = { 1, 2, 2, 4, 4, 4, 2, 4 };
|
||||
unsigned length = transfer_length[channel[i].xfermode];
|
||||
for(unsigned index = 0; index < length; index++) {
|
||||
unsigned addr = !channel[i].hdma_indirect ? 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].hdma_line_counter--;
|
||||
channel[i].hdma_do_transfer = channel[i].hdma_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].srcaddr;
|
||||
channel[i].hdma_line_counter = 0;
|
||||
hdma_update(i);
|
||||
}
|
||||
|
||||
status.irq_lock = true;
|
||||
}
|
||||
|
||||
//==============
|
||||
//initialization
|
||||
//==============
|
||||
|
||||
void CPU::dma_power() {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].dmap = 0xff;
|
||||
channel[i].direction = 1;
|
||||
channel[i].hdma_indirect = true;
|
||||
channel[i].reversexfer = true;
|
||||
channel[i].fixedxfer = true;
|
||||
channel[i].xfermode = 7;
|
||||
|
||||
channel[i].destaddr = 0xff;
|
||||
|
||||
channel[i].srcaddr = 0xffff;
|
||||
channel[i].srcbank = 0xff;
|
||||
|
||||
//channel[i]::union { xfersize, hdma_iaddr };
|
||||
channel[i].xfersize = 0xffff;
|
||||
channel[i].hdma_ibank = 0xff;
|
||||
|
||||
channel[i].hdma_addr = 0xffff;
|
||||
channel[i].hdma_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;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,79 +0,0 @@
|
|||
struct {
|
||||
//$420b
|
||||
bool dma_enabled;
|
||||
|
||||
//$420c
|
||||
bool hdma_enabled;
|
||||
|
||||
//$43x0
|
||||
uint8 dmap;
|
||||
bool direction;
|
||||
bool hdma_indirect;
|
||||
bool reversexfer;
|
||||
bool fixedxfer;
|
||||
uint8 xfermode;
|
||||
|
||||
//$43x1
|
||||
uint8 destaddr;
|
||||
|
||||
//$43x2-$43x3
|
||||
uint16 srcaddr;
|
||||
|
||||
//$43x4
|
||||
uint8 srcbank;
|
||||
|
||||
//$43x5-$43x6
|
||||
union {
|
||||
uint16 xfersize;
|
||||
uint16 hdma_iaddr;
|
||||
};
|
||||
|
||||
//$43x7
|
||||
uint8 hdma_ibank;
|
||||
|
||||
//$43x8-$43x9
|
||||
uint16 hdma_addr;
|
||||
|
||||
//$43xa
|
||||
uint8 hdma_line_counter;
|
||||
|
||||
//$43xb/$43xf
|
||||
uint8 unknown;
|
||||
|
||||
//internal state
|
||||
bool hdma_completed;
|
||||
bool hdma_do_transfer;
|
||||
} channel[8];
|
||||
|
||||
struct {
|
||||
bool valid;
|
||||
unsigned addr;
|
||||
uint8 data;
|
||||
} pipe;
|
||||
|
||||
void dma_add_clocks(unsigned clocks);
|
||||
bool dma_transfer_valid(uint8 bbus, uint32 abus);
|
||||
bool dma_addr_valid(uint32 abus);
|
||||
uint8 dma_read(uint32 abus);
|
||||
void dma_write(bool valid, unsigned addr = 0, uint8 data = 0);
|
||||
void dma_transfer(bool direction, uint8 bbus, uint32 abus);
|
||||
|
||||
uint8 dma_bbus(unsigned i, unsigned channel);
|
||||
uint32 dma_addr(unsigned i);
|
||||
uint32 hdma_addr(unsigned i);
|
||||
uint32 hdma_iaddr(unsigned i);
|
||||
|
||||
uint8 dma_enabled_channels();
|
||||
bool hdma_active(unsigned i);
|
||||
bool hdma_active_after(unsigned i);
|
||||
uint8 hdma_enabled_channels();
|
||||
uint8 hdma_active_channels();
|
||||
|
||||
void dma_run();
|
||||
void hdma_update(unsigned i);
|
||||
void hdma_run();
|
||||
void hdma_init_reset();
|
||||
void hdma_init();
|
||||
|
||||
void dma_power();
|
||||
void dma_reset();
|
|
@ -0,0 +1,48 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
uint8 CPU::pio() {
|
||||
return status.pio;
|
||||
}
|
||||
|
||||
bool CPU::joylatch() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CPU::interrupt_pending() {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8 CPU::port_read(uint8 port) {
|
||||
return port_data[port & 3];
|
||||
}
|
||||
|
||||
void CPU::port_write(uint8 port, uint8 data) {
|
||||
port_data[port & 3] = data;
|
||||
}
|
||||
|
||||
void CPU::op_io() {
|
||||
add_clocks(6);
|
||||
}
|
||||
|
||||
uint8 CPU::op_read(unsigned addr) {
|
||||
regs.mdr = bus.read(addr);
|
||||
add_clocks(speed(addr));
|
||||
return regs.mdr;
|
||||
}
|
||||
|
||||
void CPU::op_write(unsigned addr, uint8 data) {
|
||||
add_clocks(speed(addr));
|
||||
bus.write(addr, regs.mdr = data);
|
||||
}
|
||||
|
||||
unsigned CPU::speed(unsigned addr) const {
|
||||
if(addr & 0x408000) {
|
||||
if(addr & 0x800000) return status.rom_speed;
|
||||
return 8;
|
||||
}
|
||||
if((addr + 0x6000) & 0x4000) return 8;
|
||||
if((addr - 0x4000) & 0x7e00) return 6;
|
||||
return 12;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,38 +0,0 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
void CPU::op_io() {
|
||||
status.clock_count = 6;
|
||||
dma_edge();
|
||||
add_clocks(6);
|
||||
alu_edge();
|
||||
}
|
||||
|
||||
uint8 CPU::op_read(uint32 addr) {
|
||||
status.clock_count = speed(addr);
|
||||
dma_edge();
|
||||
add_clocks(status.clock_count - 4);
|
||||
regs.mdr = bus.read(addr);
|
||||
add_clocks(4);
|
||||
alu_edge();
|
||||
return regs.mdr;
|
||||
}
|
||||
|
||||
void CPU::op_write(uint32 addr, uint8 data) {
|
||||
alu_edge();
|
||||
status.clock_count = speed(addr);
|
||||
dma_edge();
|
||||
add_clocks(status.clock_count);
|
||||
bus.write(addr, regs.mdr = data);
|
||||
}
|
||||
|
||||
unsigned CPU::speed(unsigned addr) const {
|
||||
if(addr & 0x408000) {
|
||||
if(addr & 0x800000) return status.rom_speed;
|
||||
return 8;
|
||||
}
|
||||
if((addr + 0x6000) & 0x4000) return 8;
|
||||
if((addr - 0x4000) & 0x7e00) return 6;
|
||||
return 12;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,6 +0,0 @@
|
|||
uint8 apu_port[4];
|
||||
|
||||
void op_io();
|
||||
debugvirtual uint8 op_read(uint32 addr);
|
||||
debugvirtual void op_write(uint32 addr, uint8 data);
|
||||
alwaysinline unsigned speed(unsigned addr) const;
|
|
@ -0,0 +1,304 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
uint8 CPU::mmio_read(unsigned addr) {
|
||||
addr &= 0xffff;
|
||||
|
||||
if((addr & 0xffc0) == 0x2140) {
|
||||
synchronize_smp();
|
||||
return smp.port_read(addr & 3);
|
||||
}
|
||||
|
||||
if(addr == 0x2180) {
|
||||
uint8 result = bus.read(0x7e0000 | status.wram_addr);
|
||||
status.wram_addr = (status.wram_addr + 1) & 0x01ffff;
|
||||
return result;
|
||||
}
|
||||
|
||||
if(addr == 0x4016) {
|
||||
uint8 result = regs.mdr & 0xfc;
|
||||
result |= input.port_read(0) & 3;
|
||||
return result;
|
||||
}
|
||||
|
||||
if(addr == 0x4017) {
|
||||
uint8 result = (regs.mdr & 0xe0) | 0x1c;
|
||||
result |= input.port_read(1) & 3;
|
||||
return result;
|
||||
}
|
||||
|
||||
if(addr == 0x4210) {
|
||||
uint8 result = (regs.mdr & 0x70);
|
||||
result |= status.nmi_line << 7;
|
||||
result |= 0x02; //CPU revision
|
||||
status.nmi_line = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
if(addr == 0x4211) {
|
||||
uint8 result = (regs.mdr & 0x7f);
|
||||
result |= status.irq_line << 7;
|
||||
status.irq_line = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
if(addr == 0x4212) {
|
||||
uint8 result = (regs.mdr & 0x3e);
|
||||
unsigned vbstart = ppu.overscan() == false ? 225 : 240;
|
||||
|
||||
if(vcounter() >= vbstart && vcounter() <= vbstart + 2) result |= 0x01;
|
||||
if(hcounter() <= 2 || hcounter() >= 1096) result |= 0x40;
|
||||
if(vcounter() >= vbstart) result |= 0x80;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if(addr == 0x4213) return status.pio;
|
||||
|
||||
if(addr == 0x4214) return status.rddiv >> 0;
|
||||
if(addr == 0x4215) return status.rddiv >> 8;
|
||||
if(addr == 0x4216) return status.rdmpy >> 0;
|
||||
if(addr == 0x4217) return status.rdmpy >> 8;
|
||||
|
||||
if(addr == 0x4218) return status.joy1l;
|
||||
if(addr == 0x4219) return status.joy1h;
|
||||
if(addr == 0x421a) return status.joy2l;
|
||||
if(addr == 0x421b) return status.joy2h;
|
||||
if(addr == 0x421c) return status.joy3l;
|
||||
if(addr == 0x421d) return status.joy3h;
|
||||
if(addr == 0x421e) return status.joy4l;
|
||||
if(addr == 0x421f) return status.joy4h;
|
||||
|
||||
if((addr & 0xff80) == 0x4300) {
|
||||
unsigned i = (addr >> 4) & 7;
|
||||
addr &= 0xff8f;
|
||||
|
||||
if(addr == 0x4300) {
|
||||
return (channel[i].direction << 7)
|
||||
| (channel[i].indirect << 6)
|
||||
| (channel[i].unused << 5)
|
||||
| (channel[i].reverse_transfer << 4)
|
||||
| (channel[i].fixed_transfer << 3)
|
||||
| (channel[i].transfer_mode << 0);
|
||||
}
|
||||
|
||||
if(addr == 0x4301) return channel[i].dest_addr;
|
||||
if(addr == 0x4302) return channel[i].source_addr >> 0;
|
||||
if(addr == 0x4303) return channel[i].source_addr >> 8;
|
||||
if(addr == 0x4304) return channel[i].source_bank;
|
||||
if(addr == 0x4305) return channel[i].transfer_size >> 0;
|
||||
if(addr == 0x4306) return channel[i].transfer_size >> 8;
|
||||
if(addr == 0x4307) return channel[i].indirect_bank;
|
||||
if(addr == 0x4308) return channel[i].hdma_addr >> 0;
|
||||
if(addr == 0x4309) return channel[i].hdma_addr >> 8;
|
||||
if(addr == 0x430a) return channel[i].line_counter;
|
||||
if(addr == 0x430b || addr == 0x430f) return channel[i].unknown;
|
||||
}
|
||||
|
||||
return regs.mdr;
|
||||
}
|
||||
|
||||
void CPU::mmio_write(unsigned addr, uint8 data) {
|
||||
addr &= 0xffff;
|
||||
|
||||
if((addr & 0xffc0) == 0x2140) {
|
||||
synchronize_smp();
|
||||
smp.port_write(addr & 3, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x2180) {
|
||||
bus.write(0x7e0000 | status.wram_addr, data);
|
||||
status.wram_addr = (status.wram_addr + 1) & 0x01ffff;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x2181) {
|
||||
status.wram_addr = (status.wram_addr & 0x01ff00) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x2182) {
|
||||
status.wram_addr = (status.wram_addr & 0x0100ff) | (data << 8);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x2183) {
|
||||
status.wram_addr = (status.wram_addr & 0x00ffff) | ((data & 1) << 16);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4016) {
|
||||
bool old_latch = status.joypad_strobe_latch;
|
||||
bool new_latch = data & 1;
|
||||
status.joypad_strobe_latch = new_latch;
|
||||
if(old_latch != new_latch) input.poll();
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4200) {
|
||||
bool nmi_enabled = status.nmi_enabled;
|
||||
bool virq_enabled = status.virq_enabled;
|
||||
bool hirq_enabled = status.hirq_enabled;
|
||||
|
||||
status.nmi_enabled = data & 0x80;
|
||||
status.virq_enabled = data & 0x20;
|
||||
status.hirq_enabled = data & 0x10;
|
||||
status.auto_joypad_poll_enabled = data & 0x01;
|
||||
|
||||
if(!nmi_enabled && status.nmi_enabled && status.nmi_line) {
|
||||
status.nmi_transition = true;
|
||||
}
|
||||
|
||||
if(status.virq_enabled && !status.hirq_enabled && status.irq_line) {
|
||||
status.irq_transition = true;
|
||||
}
|
||||
|
||||
if(!status.virq_enabled && !status.hirq_enabled) {
|
||||
status.irq_line = false;
|
||||
status.irq_transition = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4201) {
|
||||
if((status.pio & 0x80) && !(data & 0x80)) ppu.latch_counters();
|
||||
status.pio = data;
|
||||
}
|
||||
|
||||
if(addr == 0x4202) {
|
||||
status.wrmpya = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4203) {
|
||||
status.wrmpyb = data;
|
||||
status.rdmpy = status.wrmpya * status.wrmpyb;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4204) {
|
||||
status.wrdiva = (status.wrdiva & 0xff00) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4205) {
|
||||
status.wrdiva = (data << 8) | (status.wrdiva & 0x00ff);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4206) {
|
||||
status.wrdivb = data;
|
||||
status.rddiv = status.wrdivb ? status.wrdiva / status.wrdivb : 0xffff;
|
||||
status.rdmpy = status.wrdivb ? status.wrdiva % status.wrdivb : status.wrdiva;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4207) {
|
||||
status.htime = (status.htime & 0x0100) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4208) {
|
||||
status.htime = ((data & 1) << 8) | (status.htime & 0x00ff);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4209) {
|
||||
status.vtime = (status.vtime & 0x0100) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x420a) {
|
||||
status.vtime = ((data & 1) << 8) | (status.vtime & 0x00ff);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x420b) {
|
||||
for(unsigned i = 0; i < 8; i++) channel[i].dma_enabled = data & (1 << i);
|
||||
if(data) dma_run();
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x420c) {
|
||||
for(unsigned i = 0; i < 8; i++) channel[i].hdma_enabled = data & (1 << i);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x420d) {
|
||||
status.rom_speed = data & 1 ? 6 : 8;
|
||||
return;
|
||||
}
|
||||
|
||||
if((addr & 0xff80) == 0x4300) {
|
||||
unsigned i = (addr >> 4) & 7;
|
||||
addr &= 0xff8f;
|
||||
|
||||
if(addr == 0x4300) {
|
||||
channel[i].direction = data & 0x80;
|
||||
channel[i].indirect = data & 0x40;
|
||||
channel[i].unused = data & 0x20;
|
||||
channel[i].reverse_transfer = data & 0x10;
|
||||
channel[i].fixed_transfer = data & 0x08;
|
||||
channel[i].transfer_mode = data & 0x07;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4301) {
|
||||
channel[i].dest_addr = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4302) {
|
||||
channel[i].source_addr = (channel[i].source_addr & 0xff00) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4303) {
|
||||
channel[i].source_addr = (data << 8) | (channel[i].source_addr & 0x00ff);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4304) {
|
||||
channel[i].source_bank = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4305) {
|
||||
channel[i].transfer_size = (channel[i].transfer_size & 0xff00) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4306) {
|
||||
channel[i].transfer_size = (data << 8) | (channel[i].transfer_size & 0x00ff);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4307) {
|
||||
channel[i].indirect_bank = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4308) {
|
||||
channel[i].hdma_addr = (channel[i].hdma_addr & 0xff00) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x4309) {
|
||||
channel[i].hdma_addr = (data << 8) | (channel[i].hdma_addr & 0x00ff);
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x430a) {
|
||||
channel[i].line_counter = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr == 0x430b || addr == 0x430f) {
|
||||
channel[i].unknown = data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,541 +0,0 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
uint8 CPU::pio() { return status.pio; }
|
||||
bool CPU::joylatch() { return status.joypad_strobe_latch; }
|
||||
|
||||
//WMDATA
|
||||
uint8 CPU::mmio_r2180() {
|
||||
uint8 r = bus.read(0x7e0000 | status.wram_addr);
|
||||
status.wram_addr = (status.wram_addr + 1) & 0x01ffff;
|
||||
return r;
|
||||
}
|
||||
|
||||
//WMDATA
|
||||
void CPU::mmio_w2180(uint8 data) {
|
||||
bus.write(0x7e0000 | status.wram_addr, data);
|
||||
status.wram_addr = (status.wram_addr + 1) & 0x01ffff;
|
||||
}
|
||||
|
||||
//WMADDL
|
||||
void CPU::mmio_w2181(uint8 data) {
|
||||
status.wram_addr = (status.wram_addr & 0xffff00) | (data);
|
||||
status.wram_addr &= 0x01ffff;
|
||||
}
|
||||
|
||||
//WMADDM
|
||||
void CPU::mmio_w2182(uint8 data) {
|
||||
status.wram_addr = (status.wram_addr & 0xff00ff) | (data << 8);
|
||||
status.wram_addr &= 0x01ffff;
|
||||
}
|
||||
|
||||
//WMADDH
|
||||
void CPU::mmio_w2183(uint8 data) {
|
||||
status.wram_addr = (status.wram_addr & 0x00ffff) | (data << 16);
|
||||
status.wram_addr &= 0x01ffff;
|
||||
}
|
||||
|
||||
//JOYSER0
|
||||
//bit 0 is shared between JOYSER0 and JOYSER1, therefore
|
||||
//strobing $4016.d0 affects both controller port latches.
|
||||
//$4017 bit 0 writes are ignored.
|
||||
void CPU::mmio_w4016(uint8 data) {
|
||||
bool old_latch = status.joypad_strobe_latch;
|
||||
bool new_latch = data & 1;
|
||||
status.joypad_strobe_latch = new_latch;
|
||||
|
||||
if(old_latch != new_latch) {
|
||||
input.poll();
|
||||
}
|
||||
}
|
||||
|
||||
//JOYSER0
|
||||
//7-2 = MDR
|
||||
//1-0 = Joypad serial data
|
||||
uint8 CPU::mmio_r4016() {
|
||||
uint8 r = regs.mdr & 0xfc;
|
||||
r |= input.port_read(0) & 3;
|
||||
return r;
|
||||
}
|
||||
|
||||
//JOYSER1
|
||||
//7-5 = MDR
|
||||
//4-2 = Always 1 (pins are connected to GND)
|
||||
//1-0 = Joypad serial data
|
||||
uint8 CPU::mmio_r4017() {
|
||||
uint8 r = (regs.mdr & 0xe0) | 0x1c;
|
||||
r |= input.port_read(1) & 3;
|
||||
return r;
|
||||
}
|
||||
|
||||
//NMITIMEN
|
||||
void CPU::mmio_w4200(uint8 data) {
|
||||
status.auto_joypad_poll = !!(data & 0x01);
|
||||
nmitimen_update(data);
|
||||
}
|
||||
|
||||
//WRIO
|
||||
void CPU::mmio_w4201(uint8 data) {
|
||||
if((status.pio & 0x80) && !(data & 0x80)) {
|
||||
ppu.latch_counters();
|
||||
}
|
||||
status.pio = data;
|
||||
}
|
||||
|
||||
//WRMPYA
|
||||
void CPU::mmio_w4202(uint8 data) {
|
||||
status.wrmpya = data;
|
||||
}
|
||||
|
||||
//WRMPYB
|
||||
void CPU::mmio_w4203(uint8 data) {
|
||||
status.rdmpy = 0;
|
||||
if(alu.mpyctr || alu.divctr) return;
|
||||
|
||||
status.wrmpyb = data;
|
||||
status.rddiv = (status.wrmpyb << 8) | status.wrmpya;
|
||||
|
||||
alu.mpyctr = 8; //perform multiplication over the next eight cycles
|
||||
alu.shift = status.wrmpyb;
|
||||
}
|
||||
|
||||
//WRDIVL
|
||||
void CPU::mmio_w4204(uint8 data) {
|
||||
status.wrdiva = (status.wrdiva & 0xff00) | (data);
|
||||
}
|
||||
|
||||
//WRDIVH
|
||||
void CPU::mmio_w4205(uint8 data) {
|
||||
status.wrdiva = (status.wrdiva & 0x00ff) | (data << 8);
|
||||
}
|
||||
|
||||
//WRDIVB
|
||||
void CPU::mmio_w4206(uint8 data) {
|
||||
status.rdmpy = status.wrdiva;
|
||||
if(alu.mpyctr || alu.divctr) return;
|
||||
|
||||
status.wrdivb = data;
|
||||
|
||||
alu.divctr = 16; //perform division over the next sixteen cycles
|
||||
alu.shift = status.wrdivb << 16;
|
||||
}
|
||||
|
||||
//HTIMEL
|
||||
void CPU::mmio_w4207(uint8 data) {
|
||||
status.hirq_pos = (status.hirq_pos & ~0xff) | (data);
|
||||
status.hirq_pos &= 0x01ff;
|
||||
}
|
||||
|
||||
//HTIMEH
|
||||
void CPU::mmio_w4208(uint8 data) {
|
||||
status.hirq_pos = (status.hirq_pos & 0xff) | (data << 8);
|
||||
status.hirq_pos &= 0x01ff;
|
||||
}
|
||||
|
||||
//VTIMEL
|
||||
void CPU::mmio_w4209(uint8 data) {
|
||||
status.virq_pos = (status.virq_pos & ~0xff) | (data);
|
||||
status.virq_pos &= 0x01ff;
|
||||
}
|
||||
|
||||
//VTIMEH
|
||||
void CPU::mmio_w420a(uint8 data) {
|
||||
status.virq_pos = (status.virq_pos & 0xff) | (data << 8);
|
||||
status.virq_pos &= 0x01ff;
|
||||
}
|
||||
|
||||
//DMAEN
|
||||
void CPU::mmio_w420b(uint8 data) {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].dma_enabled = data & (1 << i);
|
||||
}
|
||||
if(data) status.dma_pending = true;
|
||||
}
|
||||
|
||||
//HDMAEN
|
||||
void CPU::mmio_w420c(uint8 data) {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
channel[i].hdma_enabled = data & (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
//MEMSEL
|
||||
void CPU::mmio_w420d(uint8 data) {
|
||||
status.rom_speed = (data & 1 ? 6 : 8);
|
||||
}
|
||||
|
||||
//RDNMI
|
||||
//7 = NMI acknowledge
|
||||
//6-4 = MDR
|
||||
//3-0 = CPU (5a22) version
|
||||
uint8 CPU::mmio_r4210() {
|
||||
uint8 r = (regs.mdr & 0x70);
|
||||
r |= (uint8)(rdnmi()) << 7;
|
||||
r |= (cpu_version & 0x0f);
|
||||
return r;
|
||||
}
|
||||
|
||||
//TIMEUP
|
||||
//7 = IRQ acknowledge
|
||||
//6-0 = MDR
|
||||
uint8 CPU::mmio_r4211() {
|
||||
uint8 r = (regs.mdr & 0x7f);
|
||||
r |= (uint8)(timeup()) << 7;
|
||||
return r;
|
||||
}
|
||||
|
||||
//HVBJOY
|
||||
//7 = VBLANK acknowledge
|
||||
//6 = HBLANK acknowledge
|
||||
//5-1 = MDR
|
||||
//0 = JOYPAD acknowledge
|
||||
uint8 CPU::mmio_r4212() {
|
||||
uint8 r = (regs.mdr & 0x3e);
|
||||
uint16 vs = ppu.overscan() == false ? 225 : 240;
|
||||
|
||||
//auto joypad polling
|
||||
if(vcounter() >= vs && vcounter() <= (vs + 2))r |= 0x01;
|
||||
|
||||
//hblank
|
||||
if(hcounter() <= 2 || hcounter() >= 1096)r |= 0x40;
|
||||
|
||||
//vblank
|
||||
if(vcounter() >= vs)r |= 0x80;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
//RDIO
|
||||
uint8 CPU::mmio_r4213() {
|
||||
return status.pio;
|
||||
}
|
||||
|
||||
//RDDIVL
|
||||
uint8 CPU::mmio_r4214() {
|
||||
return status.rddiv;
|
||||
}
|
||||
|
||||
//RDDIVH
|
||||
uint8 CPU::mmio_r4215() {
|
||||
return status.rddiv >> 8;
|
||||
}
|
||||
|
||||
//RDMPYL
|
||||
uint8 CPU::mmio_r4216() {
|
||||
return status.rdmpy;
|
||||
}
|
||||
|
||||
//RDMPYH
|
||||
uint8 CPU::mmio_r4217() {
|
||||
return status.rdmpy >> 8;
|
||||
}
|
||||
|
||||
//TODO: handle reads during joypad polling (v=225-227)
|
||||
uint8 CPU::mmio_r4218() { return status.joy1l; } //JOY1L
|
||||
uint8 CPU::mmio_r4219() { return status.joy1h; } //JOY1H
|
||||
uint8 CPU::mmio_r421a() { return status.joy2l; } //JOY2L
|
||||
uint8 CPU::mmio_r421b() { return status.joy2h; } //JOY2H
|
||||
uint8 CPU::mmio_r421c() { return status.joy3l; } //JOY3L
|
||||
uint8 CPU::mmio_r421d() { return status.joy3h; } //JOY3H
|
||||
uint8 CPU::mmio_r421e() { return status.joy4l; } //JOY4L
|
||||
uint8 CPU::mmio_r421f() { return status.joy4h; } //JOY4H
|
||||
|
||||
//DMAPx
|
||||
uint8 CPU::mmio_r43x0(uint8 i) {
|
||||
return channel[i].dmap;
|
||||
}
|
||||
|
||||
//BBADx
|
||||
uint8 CPU::mmio_r43x1(uint8 i) {
|
||||
return channel[i].destaddr;
|
||||
}
|
||||
|
||||
//A1TxL
|
||||
uint8 CPU::mmio_r43x2(uint8 i) {
|
||||
return channel[i].srcaddr;
|
||||
}
|
||||
|
||||
//A1TxH
|
||||
uint8 CPU::mmio_r43x3(uint8 i) {
|
||||
return channel[i].srcaddr >> 8;
|
||||
}
|
||||
|
||||
//A1Bx
|
||||
uint8 CPU::mmio_r43x4(uint8 i) {
|
||||
return channel[i].srcbank;
|
||||
}
|
||||
|
||||
//DASxL
|
||||
//union { uint16 xfersize; uint16 hdma_iaddr; };
|
||||
uint8 CPU::mmio_r43x5(uint8 i) {
|
||||
return channel[i].xfersize;
|
||||
}
|
||||
|
||||
//DASxH
|
||||
//union { uint16 xfersize; uint16 hdma_iaddr; };
|
||||
uint8 CPU::mmio_r43x6(uint8 i) {
|
||||
return channel[i].xfersize >> 8;
|
||||
}
|
||||
|
||||
//DASBx
|
||||
uint8 CPU::mmio_r43x7(uint8 i) {
|
||||
return channel[i].hdma_ibank;
|
||||
}
|
||||
|
||||
//A2AxL
|
||||
uint8 CPU::mmio_r43x8(uint8 i) {
|
||||
return channel[i].hdma_addr;
|
||||
}
|
||||
|
||||
//A2AxH
|
||||
uint8 CPU::mmio_r43x9(uint8 i) {
|
||||
return channel[i].hdma_addr >> 8;
|
||||
}
|
||||
|
||||
//NTRLx
|
||||
uint8 CPU::mmio_r43xa(uint8 i) {
|
||||
return channel[i].hdma_line_counter;
|
||||
}
|
||||
|
||||
//???
|
||||
uint8 CPU::mmio_r43xb(uint8 i) {
|
||||
return channel[i].unknown;
|
||||
}
|
||||
|
||||
//DMAPx
|
||||
void CPU::mmio_w43x0(uint8 i, uint8 data) {
|
||||
channel[i].dmap = data;
|
||||
channel[i].direction = data & 0x80;
|
||||
channel[i].hdma_indirect = data & 0x40;
|
||||
channel[i].reversexfer = data & 0x10;
|
||||
channel[i].fixedxfer = data & 0x08;
|
||||
channel[i].xfermode = data & 7;
|
||||
}
|
||||
|
||||
//DDBADx
|
||||
void CPU::mmio_w43x1(uint8 i, uint8 data) {
|
||||
channel[i].destaddr = data;
|
||||
}
|
||||
|
||||
//A1TxL
|
||||
void CPU::mmio_w43x2(uint8 i, uint8 data) {
|
||||
channel[i].srcaddr = (channel[i].srcaddr & 0xff00) | (data);
|
||||
}
|
||||
|
||||
//A1TxH
|
||||
void CPU::mmio_w43x3(uint8 i, uint8 data) {
|
||||
channel[i].srcaddr = (channel[i].srcaddr & 0x00ff) | (data << 8);
|
||||
}
|
||||
|
||||
//A1Bx
|
||||
void CPU::mmio_w43x4(uint8 i, uint8 data) {
|
||||
channel[i].srcbank = data;
|
||||
}
|
||||
|
||||
//DASxL
|
||||
//union { uint16 xfersize; uint16 hdma_iaddr; };
|
||||
void CPU::mmio_w43x5(uint8 i, uint8 data) {
|
||||
channel[i].xfersize = (channel[i].xfersize & 0xff00) | (data);
|
||||
}
|
||||
|
||||
//DASxH
|
||||
//union { uint16 xfersize; uint16 hdma_iaddr; };
|
||||
void CPU::mmio_w43x6(uint8 i, uint8 data) {
|
||||
channel[i].xfersize = (channel[i].xfersize & 0x00ff) | (data << 8);
|
||||
}
|
||||
|
||||
//DASBx
|
||||
void CPU::mmio_w43x7(uint8 i, uint8 data) {
|
||||
channel[i].hdma_ibank = data;
|
||||
}
|
||||
|
||||
//A2AxL
|
||||
void CPU::mmio_w43x8(uint8 i, uint8 data) {
|
||||
channel[i].hdma_addr = (channel[i].hdma_addr & 0xff00) | (data);
|
||||
}
|
||||
|
||||
//A2AxH
|
||||
void CPU::mmio_w43x9(uint8 i, uint8 data) {
|
||||
channel[i].hdma_addr = (channel[i].hdma_addr & 0x00ff) | (data << 8);
|
||||
}
|
||||
|
||||
//NTRLx
|
||||
void CPU::mmio_w43xa(uint8 i, uint8 data) {
|
||||
channel[i].hdma_line_counter = data;
|
||||
}
|
||||
|
||||
//???
|
||||
void CPU::mmio_w43xb(uint8 i, uint8 data) {
|
||||
channel[i].unknown = data;
|
||||
}
|
||||
|
||||
void CPU::mmio_power() {
|
||||
}
|
||||
|
||||
void CPU::mmio_reset() {
|
||||
//$2181-$2183
|
||||
status.wram_addr = 0x000000;
|
||||
|
||||
//$4016-$4017
|
||||
status.joypad_strobe_latch = 0;
|
||||
status.joypad1_bits = ~0;
|
||||
status.joypad2_bits = ~0;
|
||||
|
||||
//$4200
|
||||
status.nmi_enabled = false;
|
||||
status.hirq_enabled = false;
|
||||
status.virq_enabled = false;
|
||||
status.auto_joypad_poll = false;
|
||||
|
||||
//$4201
|
||||
status.pio = 0xff;
|
||||
|
||||
//$4202-$4203
|
||||
status.wrmpya = 0xff;
|
||||
status.wrmpyb = 0xff;
|
||||
|
||||
//$4204-$4206
|
||||
status.wrdiva = 0xffff;
|
||||
status.wrdivb = 0xff;
|
||||
|
||||
//$4207-$420a
|
||||
status.hirq_pos = 0x01ff;
|
||||
status.virq_pos = 0x01ff;
|
||||
|
||||
//$420d
|
||||
status.rom_speed = 8;
|
||||
|
||||
//$4214-$4217
|
||||
status.rddiv = 0x0000;
|
||||
status.rdmpy = 0x0000;
|
||||
|
||||
//$4218-$421f
|
||||
status.joy1l = 0x00;
|
||||
status.joy1h = 0x00;
|
||||
status.joy2l = 0x00;
|
||||
status.joy2h = 0x00;
|
||||
status.joy3l = 0x00;
|
||||
status.joy3h = 0x00;
|
||||
status.joy4l = 0x00;
|
||||
status.joy4h = 0x00;
|
||||
|
||||
//ALU
|
||||
alu.mpyctr = 0;
|
||||
alu.divctr = 0;
|
||||
alu.shift = 0;
|
||||
}
|
||||
|
||||
uint8 CPU::mmio_read(unsigned addr) {
|
||||
addr &= 0xffff;
|
||||
|
||||
//APU
|
||||
if((addr & 0xffc0) == 0x2140) { //$2140-$217f
|
||||
synchronize_smp();
|
||||
return smp.port_read(addr & 3);
|
||||
}
|
||||
|
||||
//DMA
|
||||
if((addr & 0xff80) == 0x4300) { //$4300-$437f
|
||||
unsigned i = (addr >> 4) & 7;
|
||||
switch(addr & 0xf) {
|
||||
case 0x0: return mmio_r43x0(i);
|
||||
case 0x1: return mmio_r43x1(i);
|
||||
case 0x2: return mmio_r43x2(i);
|
||||
case 0x3: return mmio_r43x3(i);
|
||||
case 0x4: return mmio_r43x4(i);
|
||||
case 0x5: return mmio_r43x5(i);
|
||||
case 0x6: return mmio_r43x6(i);
|
||||
case 0x7: return mmio_r43x7(i);
|
||||
case 0x8: return mmio_r43x8(i);
|
||||
case 0x9: return mmio_r43x9(i);
|
||||
case 0xa: return mmio_r43xa(i);
|
||||
case 0xb: return mmio_r43xb(i);
|
||||
case 0xc: return regs.mdr; //unmapped
|
||||
case 0xd: return regs.mdr; //unmapped
|
||||
case 0xe: return regs.mdr; //unmapped
|
||||
case 0xf: return mmio_r43xb(i); //mirror of $43xb
|
||||
}
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x2180: return mmio_r2180();
|
||||
case 0x4016: return mmio_r4016();
|
||||
case 0x4017: return mmio_r4017();
|
||||
case 0x4210: return mmio_r4210();
|
||||
case 0x4211: return mmio_r4211();
|
||||
case 0x4212: return mmio_r4212();
|
||||
case 0x4213: return mmio_r4213();
|
||||
case 0x4214: return mmio_r4214();
|
||||
case 0x4215: return mmio_r4215();
|
||||
case 0x4216: return mmio_r4216();
|
||||
case 0x4217: return mmio_r4217();
|
||||
case 0x4218: return mmio_r4218();
|
||||
case 0x4219: return mmio_r4219();
|
||||
case 0x421a: return mmio_r421a();
|
||||
case 0x421b: return mmio_r421b();
|
||||
case 0x421c: return mmio_r421c();
|
||||
case 0x421d: return mmio_r421d();
|
||||
case 0x421e: return mmio_r421e();
|
||||
case 0x421f: return mmio_r421f();
|
||||
}
|
||||
|
||||
return regs.mdr;
|
||||
}
|
||||
|
||||
void CPU::mmio_write(unsigned addr, uint8 data) {
|
||||
addr &= 0xffff;
|
||||
|
||||
//APU
|
||||
if((addr & 0xffc0) == 0x2140) { //$2140-$217f
|
||||
synchronize_smp();
|
||||
port_write(addr & 3, data);
|
||||
return;
|
||||
}
|
||||
|
||||
//DMA
|
||||
if((addr & 0xff80) == 0x4300) { //$4300-$437f
|
||||
unsigned i = (addr >> 4) & 7;
|
||||
switch(addr & 0xf) {
|
||||
case 0x0: mmio_w43x0(i, data); return;
|
||||
case 0x1: mmio_w43x1(i, data); return;
|
||||
case 0x2: mmio_w43x2(i, data); return;
|
||||
case 0x3: mmio_w43x3(i, data); return;
|
||||
case 0x4: mmio_w43x4(i, data); return;
|
||||
case 0x5: mmio_w43x5(i, data); return;
|
||||
case 0x6: mmio_w43x6(i, data); return;
|
||||
case 0x7: mmio_w43x7(i, data); return;
|
||||
case 0x8: mmio_w43x8(i, data); return;
|
||||
case 0x9: mmio_w43x9(i, data); return;
|
||||
case 0xa: mmio_w43xa(i, data); return;
|
||||
case 0xb: mmio_w43xb(i, data); return;
|
||||
case 0xc: return; //unmapped
|
||||
case 0xd: return; //unmapped
|
||||
case 0xe: return; //unmapped
|
||||
case 0xf: mmio_w43xb(i, data); return; //mirror of $43xb
|
||||
}
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x2180: mmio_w2180(data); return;
|
||||
case 0x2181: mmio_w2181(data); return;
|
||||
case 0x2182: mmio_w2182(data); return;
|
||||
case 0x2183: mmio_w2183(data); return;
|
||||
case 0x4016: mmio_w4016(data); return;
|
||||
case 0x4017: return; //unmapped
|
||||
case 0x4200: mmio_w4200(data); return;
|
||||
case 0x4201: mmio_w4201(data); return;
|
||||
case 0x4202: mmio_w4202(data); return;
|
||||
case 0x4203: mmio_w4203(data); return;
|
||||
case 0x4204: mmio_w4204(data); return;
|
||||
case 0x4205: mmio_w4205(data); return;
|
||||
case 0x4206: mmio_w4206(data); return;
|
||||
case 0x4207: mmio_w4207(data); return;
|
||||
case 0x4208: mmio_w4208(data); return;
|
||||
case 0x4209: mmio_w4209(data); return;
|
||||
case 0x420a: mmio_w420a(data); return;
|
||||
case 0x420b: mmio_w420b(data); return;
|
||||
case 0x420c: mmio_w420c(data); return;
|
||||
case 0x420d: mmio_w420d(data); return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,68 +0,0 @@
|
|||
void mmio_power();
|
||||
void mmio_reset();
|
||||
uint8 mmio_read(unsigned addr);
|
||||
void mmio_write(unsigned addr, uint8 data);
|
||||
|
||||
uint8 mmio_r2180();
|
||||
uint8 mmio_r4016();
|
||||
uint8 mmio_r4017();
|
||||
uint8 mmio_r4210();
|
||||
uint8 mmio_r4211();
|
||||
uint8 mmio_r4212();
|
||||
uint8 mmio_r4213();
|
||||
uint8 mmio_r4214();
|
||||
uint8 mmio_r4215();
|
||||
uint8 mmio_r4216();
|
||||
uint8 mmio_r4217();
|
||||
uint8 mmio_r4218();
|
||||
uint8 mmio_r4219();
|
||||
uint8 mmio_r421a();
|
||||
uint8 mmio_r421b();
|
||||
uint8 mmio_r421c();
|
||||
uint8 mmio_r421d();
|
||||
uint8 mmio_r421e();
|
||||
uint8 mmio_r421f();
|
||||
uint8 mmio_r43x0(uint8 i);
|
||||
uint8 mmio_r43x1(uint8 i);
|
||||
uint8 mmio_r43x2(uint8 i);
|
||||
uint8 mmio_r43x3(uint8 i);
|
||||
uint8 mmio_r43x4(uint8 i);
|
||||
uint8 mmio_r43x5(uint8 i);
|
||||
uint8 mmio_r43x6(uint8 i);
|
||||
uint8 mmio_r43x7(uint8 i);
|
||||
uint8 mmio_r43x8(uint8 i);
|
||||
uint8 mmio_r43x9(uint8 i);
|
||||
uint8 mmio_r43xa(uint8 i);
|
||||
uint8 mmio_r43xb(uint8 i);
|
||||
|
||||
void mmio_w2180(uint8 data);
|
||||
void mmio_w2181(uint8 data);
|
||||
void mmio_w2182(uint8 data);
|
||||
void mmio_w2183(uint8 data);
|
||||
void mmio_w4016(uint8 data);
|
||||
void mmio_w4200(uint8 data);
|
||||
void mmio_w4201(uint8 data);
|
||||
void mmio_w4202(uint8 data);
|
||||
void mmio_w4203(uint8 data);
|
||||
void mmio_w4204(uint8 data);
|
||||
void mmio_w4205(uint8 data);
|
||||
void mmio_w4206(uint8 data);
|
||||
void mmio_w4207(uint8 data);
|
||||
void mmio_w4208(uint8 data);
|
||||
void mmio_w4209(uint8 data);
|
||||
void mmio_w420a(uint8 data);
|
||||
void mmio_w420b(uint8 data);
|
||||
void mmio_w420c(uint8 data);
|
||||
void mmio_w420d(uint8 data);
|
||||
void mmio_w43x0(uint8 i, uint8 data);
|
||||
void mmio_w43x1(uint8 i, uint8 data);
|
||||
void mmio_w43x2(uint8 i, uint8 data);
|
||||
void mmio_w43x3(uint8 i, uint8 data);
|
||||
void mmio_w43x4(uint8 i, uint8 data);
|
||||
void mmio_w43x5(uint8 i, uint8 data);
|
||||
void mmio_w43x6(uint8 i, uint8 data);
|
||||
void mmio_w43x7(uint8 i, uint8 data);
|
||||
void mmio_w43x8(uint8 i, uint8 data);
|
||||
void mmio_w43x9(uint8 i, uint8 data);
|
||||
void mmio_w43xa(uint8 i, uint8 data);
|
||||
void mmio_w43xb(uint8 i, uint8 data);
|
|
@ -1,118 +0,0 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
void CPU::serialize(serializer &s) {
|
||||
Processor::serialize(s);
|
||||
CPUcore::core_serialize(s);
|
||||
PPUcounter::serialize(s);
|
||||
s.integer(cpu_version);
|
||||
|
||||
s.integer(status.interrupt_pending);
|
||||
s.integer(status.interrupt_vector);
|
||||
|
||||
s.integer(status.clock_count);
|
||||
s.integer(status.line_clocks);
|
||||
|
||||
s.integer(status.irq_lock);
|
||||
|
||||
s.integer(status.dram_refresh_position);
|
||||
s.integer(status.dram_refreshed);
|
||||
|
||||
s.integer(status.hdma_init_position);
|
||||
s.integer(status.hdma_init_triggered);
|
||||
|
||||
s.integer(status.hdma_position);
|
||||
s.integer(status.hdma_triggered);
|
||||
|
||||
s.integer(status.nmi_valid);
|
||||
s.integer(status.nmi_line);
|
||||
s.integer(status.nmi_transition);
|
||||
s.integer(status.nmi_pending);
|
||||
s.integer(status.nmi_hold);
|
||||
|
||||
s.integer(status.irq_valid);
|
||||
s.integer(status.irq_line);
|
||||
s.integer(status.irq_transition);
|
||||
s.integer(status.irq_pending);
|
||||
s.integer(status.irq_hold);
|
||||
|
||||
s.integer(status.reset_pending);
|
||||
|
||||
s.integer(status.dma_active);
|
||||
s.integer(status.dma_counter);
|
||||
s.integer(status.dma_clocks);
|
||||
s.integer(status.dma_pending);
|
||||
s.integer(status.hdma_pending);
|
||||
s.integer(status.hdma_mode);
|
||||
|
||||
s.integer(status.wram_addr);
|
||||
|
||||
s.integer(status.joypad_strobe_latch);
|
||||
s.integer(status.joypad1_bits);
|
||||
s.integer(status.joypad2_bits);
|
||||
|
||||
s.integer(status.nmi_enabled);
|
||||
s.integer(status.hirq_enabled);
|
||||
s.integer(status.virq_enabled);
|
||||
s.integer(status.auto_joypad_poll);
|
||||
|
||||
s.integer(status.pio);
|
||||
|
||||
s.integer(status.wrmpya);
|
||||
s.integer(status.wrmpyb);
|
||||
|
||||
s.integer(status.wrdiva);
|
||||
s.integer(status.wrdivb);
|
||||
|
||||
s.integer(status.hirq_pos);
|
||||
s.integer(status.virq_pos);
|
||||
|
||||
s.integer(status.rom_speed);
|
||||
|
||||
s.integer(status.rddiv);
|
||||
s.integer(status.rdmpy);
|
||||
|
||||
s.integer(status.joy1l);
|
||||
s.integer(status.joy1h);
|
||||
s.integer(status.joy2l);
|
||||
s.integer(status.joy2h);
|
||||
s.integer(status.joy3l);
|
||||
s.integer(status.joy3h);
|
||||
s.integer(status.joy4l);
|
||||
s.integer(status.joy4h);
|
||||
|
||||
s.integer(alu.mpyctr);
|
||||
s.integer(alu.divctr);
|
||||
s.integer(alu.shift);
|
||||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
s.integer(channel[i].dma_enabled);
|
||||
s.integer(channel[i].hdma_enabled);
|
||||
s.integer(channel[i].dmap);
|
||||
s.integer(channel[i].direction);
|
||||
s.integer(channel[i].hdma_indirect);
|
||||
s.integer(channel[i].reversexfer);
|
||||
s.integer(channel[i].fixedxfer);
|
||||
s.integer(channel[i].xfermode);
|
||||
s.integer(channel[i].destaddr);
|
||||
s.integer(channel[i].srcaddr);
|
||||
s.integer(channel[i].srcbank);
|
||||
s.integer(channel[i].xfersize);
|
||||
s.integer(channel[i].hdma_ibank);
|
||||
s.integer(channel[i].hdma_addr);
|
||||
s.integer(channel[i].hdma_line_counter);
|
||||
s.integer(channel[i].unknown);
|
||||
s.integer(channel[i].hdma_completed);
|
||||
s.integer(channel[i].hdma_do_transfer);
|
||||
}
|
||||
|
||||
s.integer(pipe.valid);
|
||||
s.integer(pipe.addr);
|
||||
s.integer(pipe.data);
|
||||
|
||||
s.integer(apu_port[0]);
|
||||
s.integer(apu_port[1]);
|
||||
s.integer(apu_port[2]);
|
||||
s.integer(apu_port[3]);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,122 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
struct QueueEvent {
|
||||
enum : unsigned {
|
||||
DramRefresh,
|
||||
HdmaRun,
|
||||
};
|
||||
};
|
||||
|
||||
void CPU::queue_event(unsigned id) {
|
||||
switch(id) {
|
||||
case QueueEvent::DramRefresh: return add_clocks(40);
|
||||
case QueueEvent::HdmaRun: return hdma_run();
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::last_cycle() {
|
||||
if(status.nmi_transition) {
|
||||
regs.wai = false;
|
||||
status.nmi_transition = false;
|
||||
status.nmi_pending = true;
|
||||
}
|
||||
|
||||
if(status.irq_transition) {
|
||||
regs.wai = false;
|
||||
status.irq_transition = false;
|
||||
status.irq_pending = !regs.p.i;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::add_clocks(unsigned clocks) {
|
||||
step(clocks);
|
||||
queue.tick(clocks);
|
||||
unsigned clocksleft = lineclocks() - hcounter();
|
||||
if(clocks > clocksleft) {
|
||||
add_time(clocksleft);
|
||||
add_time(clocks - clocksleft);
|
||||
} else {
|
||||
add_time(clocks);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::add_time(unsigned clocks) {
|
||||
if(status.irq_line && (status.virq_enabled || status.hirq_enabled)) {
|
||||
status.irq_transition = true;
|
||||
}
|
||||
|
||||
if(status.virq_enabled && !status.hirq_enabled) {
|
||||
bool irq_valid = status.irq_valid;
|
||||
status.irq_valid = vcounter() == status.vtime;
|
||||
if(!irq_valid && status.irq_valid) {
|
||||
status.irq_line = true;
|
||||
status.irq_transition = true;
|
||||
}
|
||||
} else if(status.hirq_enabled) {
|
||||
bool irq_valid = status.irq_valid;
|
||||
status.irq_valid = hcounter() <= status.htime * 4 && hcounter() + clocks > status.htime * 4;
|
||||
if(status.virq_enabled && vcounter() != status.vtime) status.irq_valid = false;
|
||||
if(!irq_valid && status.irq_valid) {
|
||||
status.irq_line = true;
|
||||
status.irq_transition = true;
|
||||
}
|
||||
}
|
||||
|
||||
tick(clocks);
|
||||
}
|
||||
|
||||
void CPU::scanline() {
|
||||
synchronize_smp();
|
||||
synchronize_ppu();
|
||||
synchronize_coprocessor();
|
||||
system.scanline();
|
||||
|
||||
if(vcounter() == 0) hdma_init();
|
||||
|
||||
queue.enqueue(534, QueueEvent::DramRefresh);
|
||||
|
||||
if(vcounter() <= (ppu.overscan() == false ? 224 : 239)) {
|
||||
queue.enqueue(1104, QueueEvent::HdmaRun);
|
||||
}
|
||||
|
||||
bool nmi_valid = status.nmi_valid;
|
||||
status.nmi_valid = vcounter() >= (ppu.overscan() == false ? 225 : 240);
|
||||
if(!nmi_valid && status.nmi_valid) {
|
||||
status.nmi_line = true;
|
||||
if(status.nmi_enabled) status.nmi_transition = true;
|
||||
} else if(nmi_valid && !status.nmi_valid) {
|
||||
status.nmi_line = false;
|
||||
}
|
||||
|
||||
if(status.auto_joypad_poll_enabled && vcounter() == (ppu.overscan() == false ? 227 : 242)) {
|
||||
input.poll();
|
||||
run_auto_joypad_poll();
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::run_auto_joypad_poll() {
|
||||
uint16 joy1 = 0, joy2 = 0, joy3 = 0, joy4 = 0;
|
||||
for(unsigned i = 0; i < 16; i++) {
|
||||
uint8 port0 = input.port_read(0);
|
||||
uint8 port1 = input.port_read(1);
|
||||
|
||||
joy1 |= (port0 & 1) ? (0x8000 >> i) : 0;
|
||||
joy2 |= (port1 & 1) ? (0x8000 >> i) : 0;
|
||||
joy3 |= (port0 & 2) ? (0x8000 >> i) : 0;
|
||||
joy4 |= (port1 & 2) ? (0x8000 >> i) : 0;
|
||||
}
|
||||
|
||||
status.joy1l = joy1;
|
||||
status.joy1h = joy1 >> 8;
|
||||
|
||||
status.joy2l = joy2;
|
||||
status.joy2h = joy2 >> 8;
|
||||
|
||||
status.joy3l = joy3;
|
||||
status.joy3h = joy3 >> 8;
|
||||
|
||||
status.joy4l = joy4;
|
||||
status.joy4h = joy4 >> 8;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,106 +0,0 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
//called once every four clock cycles;
|
||||
//as NMI steps by scanlines (divisible by 4) and IRQ by PPU 4-cycle dots.
|
||||
//
|
||||
//ppu.(vh)counter(n) returns the value of said counters n-clocks before current time;
|
||||
//it is used to emulate hardware communication delay between opcode and interrupt units.
|
||||
void CPU::poll_interrupts() {
|
||||
//NMI hold
|
||||
if(status.nmi_hold) {
|
||||
status.nmi_hold = false;
|
||||
if(status.nmi_enabled) status.nmi_transition = true;
|
||||
}
|
||||
|
||||
//NMI test
|
||||
bool nmi_valid = (vcounter(2) >= (!ppu.overscan() ? 225 : 240));
|
||||
if(!status.nmi_valid && nmi_valid) {
|
||||
//0->1 edge sensitive transition
|
||||
status.nmi_line = true;
|
||||
status.nmi_hold = true; //hold /NMI for four cycles
|
||||
} else if(status.nmi_valid && !nmi_valid) {
|
||||
//1->0 edge sensitive transition
|
||||
status.nmi_line = false;
|
||||
}
|
||||
status.nmi_valid = nmi_valid;
|
||||
|
||||
//IRQ hold
|
||||
status.irq_hold = false;
|
||||
if(status.irq_line) {
|
||||
if(status.virq_enabled || status.hirq_enabled) status.irq_transition = true;
|
||||
}
|
||||
|
||||
//IRQ test
|
||||
bool irq_valid = (status.virq_enabled || status.hirq_enabled);
|
||||
if(irq_valid) {
|
||||
if((status.virq_enabled && vcounter(10) != (status.virq_pos))
|
||||
|| (status.hirq_enabled && hcounter(10) != (status.hirq_pos + 1) * 4)
|
||||
|| (status.virq_pos && vcounter(6) == 0) //IRQs cannot trigger on last dot of field
|
||||
) irq_valid = false;
|
||||
}
|
||||
if(!status.irq_valid && irq_valid) {
|
||||
//0->1 edge sensitive transition
|
||||
status.irq_line = true;
|
||||
status.irq_hold = true; //hold /IRQ for four cycles
|
||||
}
|
||||
status.irq_valid = irq_valid;
|
||||
}
|
||||
|
||||
void CPU::nmitimen_update(uint8 data) {
|
||||
bool nmi_enabled = status.nmi_enabled;
|
||||
bool virq_enabled = status.virq_enabled;
|
||||
bool hirq_enabled = status.hirq_enabled;
|
||||
status.nmi_enabled = data & 0x80;
|
||||
status.virq_enabled = data & 0x20;
|
||||
status.hirq_enabled = data & 0x10;
|
||||
|
||||
//0->1 edge sensitive transition
|
||||
if(!nmi_enabled && status.nmi_enabled && status.nmi_line) {
|
||||
status.nmi_transition = true;
|
||||
}
|
||||
|
||||
//?->1 level sensitive transition
|
||||
if(status.virq_enabled && !status.hirq_enabled && status.irq_line) {
|
||||
status.irq_transition = true;
|
||||
}
|
||||
|
||||
if(!status.virq_enabled && !status.hirq_enabled) {
|
||||
status.irq_line = false;
|
||||
status.irq_transition = false;
|
||||
}
|
||||
|
||||
status.irq_lock = true;
|
||||
}
|
||||
|
||||
bool CPU::rdnmi() {
|
||||
bool result = status.nmi_line;
|
||||
if(!status.nmi_hold) {
|
||||
status.nmi_line = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CPU::timeup() {
|
||||
bool result = status.irq_line;
|
||||
if(!status.irq_hold) {
|
||||
status.irq_line = false;
|
||||
status.irq_transition = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CPU::nmi_test() {
|
||||
if(!status.nmi_transition) return false;
|
||||
status.nmi_transition = false;
|
||||
regs.wai = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CPU::irq_test() {
|
||||
if(!status.irq_transition && !regs.irq) return false;
|
||||
status.irq_transition = false;
|
||||
regs.wai = false;
|
||||
return !regs.p.i;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,28 +0,0 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
void CPU::run_auto_joypad_poll() {
|
||||
uint16 joy1 = 0, joy2 = 0, joy3 = 0, joy4 = 0;
|
||||
for(unsigned i = 0; i < 16; i++) {
|
||||
uint8 port0 = input.port_read(0);
|
||||
uint8 port1 = input.port_read(1);
|
||||
|
||||
joy1 |= (port0 & 1) ? (0x8000 >> i) : 0;
|
||||
joy2 |= (port1 & 1) ? (0x8000 >> i) : 0;
|
||||
joy3 |= (port0 & 2) ? (0x8000 >> i) : 0;
|
||||
joy4 |= (port1 & 2) ? (0x8000 >> i) : 0;
|
||||
}
|
||||
|
||||
status.joy1l = joy1;
|
||||
status.joy1h = joy1 >> 8;
|
||||
|
||||
status.joy2l = joy2;
|
||||
status.joy2h = joy2 >> 8;
|
||||
|
||||
status.joy3l = joy3;
|
||||
status.joy3h = joy3 >> 8;
|
||||
|
||||
status.joy4l = joy4;
|
||||
status.joy4h = joy4 >> 8;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,195 +0,0 @@
|
|||
#ifdef CPU_CPP
|
||||
|
||||
#include "irq.cpp"
|
||||
#include "joypad.cpp"
|
||||
|
||||
unsigned CPU::dma_counter() {
|
||||
return (status.dma_counter + hcounter()) & 7;
|
||||
}
|
||||
|
||||
void CPU::add_clocks(unsigned clocks) {
|
||||
status.irq_lock = false;
|
||||
unsigned ticks = clocks >> 1;
|
||||
while(ticks--) {
|
||||
tick();
|
||||
if(hcounter() & 2) {
|
||||
input.tick();
|
||||
poll_interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
step(clocks);
|
||||
|
||||
if(status.dram_refreshed == false && hcounter() >= status.dram_refresh_position) {
|
||||
status.dram_refreshed = true;
|
||||
add_clocks(40);
|
||||
}
|
||||
}
|
||||
|
||||
//called by ppu.tick() when Hcounter=0
|
||||
void CPU::scanline() {
|
||||
status.dma_counter = (status.dma_counter + status.line_clocks) & 7;
|
||||
status.line_clocks = lineclocks();
|
||||
|
||||
//forcefully sync S-CPU to other processors, in case chips are not communicating
|
||||
synchronize_ppu();
|
||||
synchronize_smp();
|
||||
synchronize_coprocessor();
|
||||
system.scanline();
|
||||
|
||||
if(vcounter() == 0) {
|
||||
//HDMA init triggers once every frame
|
||||
status.hdma_init_position = (cpu_version == 1 ? 12 + 8 - dma_counter() : 12 + dma_counter());
|
||||
status.hdma_init_triggered = false;
|
||||
}
|
||||
|
||||
//DRAM refresh occurs once every scanline
|
||||
if(cpu_version == 2) status.dram_refresh_position = 530 + 8 - dma_counter();
|
||||
status.dram_refreshed = false;
|
||||
|
||||
//HDMA triggers once every visible scanline
|
||||
if(vcounter() <= (ppu.overscan() == false ? 224 : 239)) {
|
||||
status.hdma_position = 1104;
|
||||
status.hdma_triggered = false;
|
||||
}
|
||||
|
||||
if(status.auto_joypad_poll == true && vcounter() == (ppu.overscan() == false ? 227 : 242)) {
|
||||
input.poll();
|
||||
run_auto_joypad_poll();
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::alu_edge() {
|
||||
if(alu.mpyctr) {
|
||||
alu.mpyctr--;
|
||||
if(status.rddiv & 1) status.rdmpy += alu.shift;
|
||||
status.rddiv >>= 1;
|
||||
alu.shift <<= 1;
|
||||
}
|
||||
|
||||
if(alu.divctr) {
|
||||
alu.divctr--;
|
||||
status.rddiv <<= 1;
|
||||
alu.shift >>= 1;
|
||||
if(status.rdmpy >= alu.shift) {
|
||||
status.rdmpy -= alu.shift;
|
||||
status.rddiv |= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::dma_edge() {
|
||||
//H/DMA pending && DMA inactive?
|
||||
//.. Run one full CPU cycle
|
||||
//.. HDMA pending && HDMA enabled ? DMA sync + HDMA run
|
||||
//.. DMA pending && DMA enabled ? DMA sync + DMA run
|
||||
//.... HDMA during DMA && HDMA enabled ? DMA sync + HDMA run
|
||||
//.. Run one bus CPU cycle
|
||||
//.. CPU sync
|
||||
|
||||
if(status.dma_active == true) {
|
||||
if(status.hdma_pending) {
|
||||
status.hdma_pending = false;
|
||||
if(hdma_enabled_channels()) {
|
||||
if(!dma_enabled_channels()) {
|
||||
dma_add_clocks(8 - dma_counter());
|
||||
}
|
||||
status.hdma_mode == 0 ? hdma_init() : hdma_run();
|
||||
if(!dma_enabled_channels()) {
|
||||
add_clocks(status.clock_count - (status.dma_clocks % status.clock_count));
|
||||
status.dma_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(status.dma_pending) {
|
||||
status.dma_pending = false;
|
||||
if(dma_enabled_channels()) {
|
||||
dma_add_clocks(8 - dma_counter());
|
||||
dma_run();
|
||||
add_clocks(status.clock_count - (status.dma_clocks % status.clock_count));
|
||||
status.dma_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(status.hdma_init_triggered == false && hcounter() >= status.hdma_init_position) {
|
||||
status.hdma_init_triggered = true;
|
||||
hdma_init_reset();
|
||||
if(hdma_enabled_channels()) {
|
||||
status.hdma_pending = true;
|
||||
status.hdma_mode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(status.hdma_triggered == false && hcounter() >= status.hdma_position) {
|
||||
status.hdma_triggered = true;
|
||||
if(hdma_active_channels()) {
|
||||
status.hdma_pending = true;
|
||||
status.hdma_mode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(status.dma_active == false) {
|
||||
if(status.dma_pending || status.hdma_pending) {
|
||||
status.dma_clocks = 0;
|
||||
status.dma_active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//used to test for NMI/IRQ, which can trigger on the edge of every opcode.
|
||||
//test one cycle early to simulate two-stage pipeline of x816 CPU.
|
||||
//
|
||||
//status.irq_lock is used to simulate hardware delay before interrupts can
|
||||
//trigger during certain events (immediately after DMA, writes to $4200, etc)
|
||||
void CPU::last_cycle() {
|
||||
if(status.irq_lock == false) {
|
||||
status.nmi_pending |= nmi_test();
|
||||
status.irq_pending |= irq_test();
|
||||
status.interrupt_pending = (status.nmi_pending || status.irq_pending);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::timing_power() {
|
||||
}
|
||||
|
||||
void CPU::timing_reset() {
|
||||
status.clock_count = 0;
|
||||
status.line_clocks = lineclocks();
|
||||
|
||||
status.irq_lock = false;
|
||||
status.dram_refresh_position = (cpu_version == 1 ? 530 : 538);
|
||||
status.dram_refreshed = false;
|
||||
|
||||
status.hdma_init_position = (cpu_version == 1 ? 12 + 8 - dma_counter() : 12 + dma_counter());
|
||||
status.hdma_init_triggered = false;
|
||||
|
||||
status.hdma_position = 1104;
|
||||
status.hdma_triggered = false;
|
||||
|
||||
status.nmi_valid = false;
|
||||
status.nmi_line = false;
|
||||
status.nmi_transition = false;
|
||||
status.nmi_pending = false;
|
||||
status.nmi_hold = false;
|
||||
|
||||
status.irq_valid = false;
|
||||
status.irq_line = false;
|
||||
status.irq_transition = false;
|
||||
status.irq_pending = false;
|
||||
status.irq_hold = false;
|
||||
|
||||
status.reset_pending = true;
|
||||
status.interrupt_pending = true;
|
||||
status.interrupt_vector = 0xfffc; //reset vector address
|
||||
|
||||
status.dma_active = false;
|
||||
status.dma_counter = 0;
|
||||
status.dma_clocks = 0;
|
||||
status.dma_pending = false;
|
||||
status.hdma_pending = false;
|
||||
status.hdma_mode = 0;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,24 +0,0 @@
|
|||
//timing.cpp
|
||||
unsigned dma_counter();
|
||||
|
||||
void add_clocks(unsigned clocks);
|
||||
void scanline();
|
||||
|
||||
alwaysinline void alu_edge();
|
||||
alwaysinline void dma_edge();
|
||||
alwaysinline void last_cycle();
|
||||
|
||||
void timing_power();
|
||||
void timing_reset();
|
||||
|
||||
//irq.cpp
|
||||
alwaysinline void poll_interrupts();
|
||||
void nmitimen_update(uint8 data);
|
||||
bool rdnmi();
|
||||
bool timeup();
|
||||
|
||||
alwaysinline bool nmi_test();
|
||||
alwaysinline bool irq_test();
|
||||
|
||||
//joypad.cpp
|
||||
void run_auto_joypad_poll();
|
|
@ -1,7 +1,7 @@
|
|||
namespace SNES {
|
||||
namespace Info {
|
||||
static const char Name[] = "bsnes";
|
||||
static const char Version[] = "067.08";
|
||||
static const char Version[] = "067.10";
|
||||
static const unsigned SerializerVersion = 12;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ void PPU::enter() {
|
|||
cache.oam_nameselect = regs.oam_nameselect;
|
||||
cache.oam_tdaddr = regs.oam_tdaddr;
|
||||
add_clocks(lineclocks() - 1152); //seek to start of next scanline
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,6 @@ public:
|
|||
|
||||
void update_oam_status();
|
||||
//required functions
|
||||
void run();
|
||||
void scanline();
|
||||
void render_scanline();
|
||||
void frame();
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
#ifdef SMP_CPP
|
||||
|
||||
bool SMPDebugger::property(unsigned id, string &name, string &value) {
|
||||
unsigned n = 0;
|
||||
|
||||
//$00f0
|
||||
if(id == n++) { name = "$00f0"; value = ""; return true; }
|
||||
if(id == n++) { name = "Clock Speed"; value = clock_speed(); return true; }
|
||||
if(id == n++) { name = "Timers Enable"; value = timers_enable(); return true; }
|
||||
if(id == n++) { name = "RAM Disable"; value = ram_disable(); return true; }
|
||||
if(id == n++) { name = "RAM Writable"; value = ram_writable(); return true; }
|
||||
if(id == n++) { name = "Timers Disable"; value = timers_disable(); return true; }
|
||||
|
||||
//$00f1
|
||||
if(id == n++) { name = "$00f1"; value = ""; return true; }
|
||||
if(id == n++) { name = "IPLROM Enable"; value = iplrom_enable(); return true; }
|
||||
|
||||
//$00f2
|
||||
if(id == n++) { name = "$00f2"; value = ""; return true; }
|
||||
if(id == n++) { name = "DSP Address"; value = string("0x", strhex<2>(dsp_address())); return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SMPDebugger::op_step() {
|
||||
bool break_event = false;
|
||||
|
||||
usage[regs.pc] |= UsageExec;
|
||||
opcode_pc = regs.pc;
|
||||
|
||||
if(debugger.step_smp) {
|
||||
debugger.break_event = Debugger::BreakEvent::SMPStep;
|
||||
scheduler.exit(Scheduler::ExitReason::DebuggerEvent);
|
||||
} else {
|
||||
debugger.breakpoint_test(Debugger::Breakpoint::Source::APURAM, Debugger::Breakpoint::Mode::Exec, regs.pc, 0x00);
|
||||
}
|
||||
|
||||
if(step_event) step_event();
|
||||
SMP::op_step();
|
||||
synchronize_cpu();
|
||||
}
|
||||
|
||||
uint8 SMPDebugger::op_read(uint16 addr) {
|
||||
uint8 data = SMP::op_read(addr);
|
||||
usage[addr] |= UsageRead;
|
||||
debugger.breakpoint_test(Debugger::Breakpoint::Source::APURAM, Debugger::Breakpoint::Mode::Read, addr, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
void SMPDebugger::op_write(uint16 addr, uint8 data) {
|
||||
SMP::op_write(addr, data);
|
||||
usage[addr] |= UsageWrite;
|
||||
usage[addr] &= ~UsageExec;
|
||||
debugger.breakpoint_test(Debugger::Breakpoint::Source::APURAM, Debugger::Breakpoint::Mode::Write, addr, data);
|
||||
}
|
||||
|
||||
SMPDebugger::SMPDebugger() {
|
||||
usage = new uint8[1 << 16]();
|
||||
opcode_pc = 0xffc0;
|
||||
}
|
||||
|
||||
SMPDebugger::~SMPDebugger() {
|
||||
delete[] usage;
|
||||
}
|
||||
|
||||
//===========
|
||||
//SMPDebugger
|
||||
//===========
|
||||
|
||||
//$00f0
|
||||
unsigned SMPDebugger::clock_speed() { return status.clock_speed; }
|
||||
bool SMPDebugger::timers_enable() { return status.timers_enabled; }
|
||||
bool SMPDebugger::ram_disable() { return status.ram_disabled; }
|
||||
bool SMPDebugger::ram_writable() { return status.ram_writable; }
|
||||
bool SMPDebugger::timers_disable() { return status.timers_disabled; }
|
||||
|
||||
//$00f1
|
||||
bool SMPDebugger::iplrom_enable() { return status.iplrom_enabled; }
|
||||
|
||||
//$00f2
|
||||
unsigned SMPDebugger::dsp_address() { return status.dsp_addr; }
|
||||
|
||||
#endif
|
|
@ -1,38 +0,0 @@
|
|||
class SMPDebugger : public SMP, public ChipDebugger {
|
||||
public:
|
||||
bool property(unsigned id, string &name, string &value);
|
||||
|
||||
function<void ()> step_event;
|
||||
|
||||
enum Usage {
|
||||
UsageRead = 0x80,
|
||||
UsageWrite = 0x40,
|
||||
UsageExec = 0x20,
|
||||
};
|
||||
uint8 *usage;
|
||||
uint16 opcode_pc;
|
||||
|
||||
void op_step();
|
||||
uint8 op_read(uint16 addr);
|
||||
void op_write(uint16 addr, uint8 data);
|
||||
|
||||
SMPDebugger();
|
||||
~SMPDebugger();
|
||||
|
||||
//===========
|
||||
//SMPDebugger
|
||||
//===========
|
||||
|
||||
//$00f0
|
||||
unsigned clock_speed();
|
||||
bool timers_enable();
|
||||
bool ram_disable();
|
||||
bool ram_writable();
|
||||
bool timers_disable();
|
||||
|
||||
//$00f1
|
||||
bool iplrom_enable();
|
||||
|
||||
//$00f2
|
||||
unsigned dsp_address();
|
||||
};
|
|
@ -1,213 +0,0 @@
|
|||
#ifdef SMP_CPP
|
||||
|
||||
alwaysinline uint8 SMP::ram_read(uint16 addr) {
|
||||
if(addr >= 0xffc0 && status.iplrom_enabled) return iplrom[addr & 0x3f];
|
||||
if(status.ram_disabled) return 0x5a; //0xff on mini-SNES
|
||||
return memory::apuram[addr];
|
||||
}
|
||||
|
||||
alwaysinline void SMP::ram_write(uint16 addr, uint8 data) {
|
||||
//writes to $ffc0-$ffff always go to apuram, even if the iplrom is enabled
|
||||
if(status.ram_writable && !status.ram_disabled) memory::apuram[addr] = data;
|
||||
}
|
||||
|
||||
uint8 SMP::port_read(uint8 port) {
|
||||
return memory::apuram[0xf4 + (port & 3)];
|
||||
}
|
||||
|
||||
void SMP::port_write(uint8 port, uint8 data) {
|
||||
memory::apuram[0xf4 + (port & 3)] = data;
|
||||
}
|
||||
|
||||
alwaysinline uint8 SMP::op_busread(uint16 addr) {
|
||||
uint8 r;
|
||||
if((addr & 0xfff0) == 0x00f0) { //00f0-00ff
|
||||
switch(addr) {
|
||||
case 0xf0: { //TEST -- write-only register
|
||||
r = 0x00;
|
||||
} break;
|
||||
|
||||
case 0xf1: { //CONTROL -- write-only register
|
||||
r = 0x00;
|
||||
} break;
|
||||
|
||||
case 0xf2: { //DSPADDR
|
||||
r = status.dsp_addr;
|
||||
} break;
|
||||
|
||||
case 0xf3: { //DSPDATA
|
||||
//0x80-0xff are read-only mirrors of 0x00-0x7f
|
||||
r = dsp.read(status.dsp_addr & 0x7f);
|
||||
} break;
|
||||
|
||||
case 0xf4: //CPUIO0
|
||||
case 0xf5: //CPUIO1
|
||||
case 0xf6: //CPUIO2
|
||||
case 0xf7: { //CPUIO3
|
||||
synchronize_cpu();
|
||||
r = cpu.port_read(addr & 3);
|
||||
} break;
|
||||
|
||||
case 0xf8: { //RAM0
|
||||
r = status.smp_f8;
|
||||
} break;
|
||||
|
||||
case 0xf9: { //RAM1
|
||||
r = status.smp_f9;
|
||||
} break;
|
||||
|
||||
case 0xfa: //T0TARGET
|
||||
case 0xfb: //T1TARGET
|
||||
case 0xfc: { //T2TARGET -- write-only registers
|
||||
r = 0x00;
|
||||
} break;
|
||||
|
||||
case 0xfd: { //T0OUT -- 4-bit counter value
|
||||
r = t0.stage3_ticks & 15;
|
||||
t0.stage3_ticks = 0;
|
||||
} break;
|
||||
|
||||
case 0xfe: { //T1OUT -- 4-bit counter value
|
||||
r = t1.stage3_ticks & 15;
|
||||
t1.stage3_ticks = 0;
|
||||
} break;
|
||||
|
||||
case 0xff: { //T2OUT -- 4-bit counter value
|
||||
r = t2.stage3_ticks & 15;
|
||||
t2.stage3_ticks = 0;
|
||||
} break;
|
||||
}
|
||||
} else {
|
||||
r = ram_read(addr);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
alwaysinline void SMP::op_buswrite(uint16 addr, uint8 data) {
|
||||
if((addr & 0xfff0) == 0x00f0) { //$00f0-00ff
|
||||
switch(addr) {
|
||||
case 0xf0: { //TEST
|
||||
if(regs.p.p) break; //writes only valid when P flag is clear
|
||||
|
||||
status.clock_speed = (data >> 6) & 3;
|
||||
status.timer_speed = (data >> 4) & 3;
|
||||
status.timers_enabled = data & 0x08;
|
||||
status.ram_disabled = data & 0x04;
|
||||
status.ram_writable = data & 0x02;
|
||||
status.timers_disabled = data & 0x01;
|
||||
|
||||
status.timer_step = (1 << status.clock_speed) + (2 << status.timer_speed);
|
||||
|
||||
t0.sync_stage1();
|
||||
t1.sync_stage1();
|
||||
t2.sync_stage1();
|
||||
} break;
|
||||
|
||||
case 0xf1: { //CONTROL
|
||||
status.iplrom_enabled = data & 0x80;
|
||||
|
||||
if(data & 0x30) {
|
||||
//one-time clearing of APU port read registers,
|
||||
//emulated by simulating CPU writes of 0x00
|
||||
synchronize_cpu();
|
||||
if(data & 0x20) {
|
||||
cpu.port_write(2, 0x00);
|
||||
cpu.port_write(3, 0x00);
|
||||
}
|
||||
if(data & 0x10) {
|
||||
cpu.port_write(0, 0x00);
|
||||
cpu.port_write(1, 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
//0->1 transistion resets timers
|
||||
if(t2.enabled == false && (data & 0x04)) {
|
||||
t2.stage2_ticks = 0;
|
||||
t2.stage3_ticks = 0;
|
||||
}
|
||||
t2.enabled = data & 0x04;
|
||||
|
||||
if(t1.enabled == false && (data & 0x02)) {
|
||||
t1.stage2_ticks = 0;
|
||||
t1.stage3_ticks = 0;
|
||||
}
|
||||
t1.enabled = data & 0x02;
|
||||
|
||||
if(t0.enabled == false && (data & 0x01)) {
|
||||
t0.stage2_ticks = 0;
|
||||
t0.stage3_ticks = 0;
|
||||
}
|
||||
t0.enabled = data & 0x01;
|
||||
} break;
|
||||
|
||||
case 0xf2: { //DSPADDR
|
||||
status.dsp_addr = data;
|
||||
} break;
|
||||
|
||||
case 0xf3: { //DSPDATA
|
||||
//0x80-0xff is a read-only mirror of 0x00-0x7f
|
||||
if(!(status.dsp_addr & 0x80)) {
|
||||
dsp.write(status.dsp_addr & 0x7f, data);
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0xf4: //CPUIO0
|
||||
case 0xf5: //CPUIO1
|
||||
case 0xf6: //CPUIO2
|
||||
case 0xf7: { //CPUIO3
|
||||
synchronize_cpu();
|
||||
port_write(addr & 3, data);
|
||||
} break;
|
||||
|
||||
case 0xf8: { //RAM0
|
||||
status.smp_f8 = data;
|
||||
} break;
|
||||
|
||||
case 0xf9: { //RAM1
|
||||
status.smp_f9 = data;
|
||||
} break;
|
||||
|
||||
case 0xfa: { //T0TARGET
|
||||
t0.target = data;
|
||||
} break;
|
||||
|
||||
case 0xfb: { //T1TARGET
|
||||
t1.target = data;
|
||||
} break;
|
||||
|
||||
case 0xfc: { //T2TARGET
|
||||
t2.target = data;
|
||||
} break;
|
||||
|
||||
case 0xfd: //T0OUT
|
||||
case 0xfe: //T1OUT
|
||||
case 0xff: { //T2OUT -- read-only registers
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
//all writes, even to MMIO registers, appear on bus
|
||||
ram_write(addr, data);
|
||||
}
|
||||
|
||||
void SMP::op_io() {
|
||||
add_clocks(24);
|
||||
cycle_edge();
|
||||
}
|
||||
|
||||
uint8 SMP::op_read(uint16 addr) {
|
||||
add_clocks(12);
|
||||
uint8 r = op_busread(addr);
|
||||
add_clocks(12);
|
||||
cycle_edge();
|
||||
return r;
|
||||
}
|
||||
|
||||
void SMP::op_write(uint16 addr, uint8 data) {
|
||||
add_clocks(24);
|
||||
op_buswrite(addr, data);
|
||||
cycle_edge();
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,9 +0,0 @@
|
|||
uint8 ram_read(uint16 addr);
|
||||
void ram_write(uint16 addr, uint8 data);
|
||||
|
||||
uint8 op_busread(uint16 addr);
|
||||
void op_buswrite(uint16 addr, uint8 data);
|
||||
|
||||
void op_io();
|
||||
debugvirtual uint8 op_read(uint16 addr);
|
||||
debugvirtual void op_write(uint16 addr, uint8 data);
|
|
@ -1,50 +1,6 @@
|
|||
#ifdef SMP_CPP
|
||||
|
||||
void SMP::serialize(serializer &s) {
|
||||
Processor::serialize(s);
|
||||
SMPcore::core_serialize(s);
|
||||
|
||||
s.integer(status.clock_counter);
|
||||
s.integer(status.dsp_counter);
|
||||
s.integer(status.timer_step);
|
||||
|
||||
s.integer(status.clock_speed);
|
||||
s.integer(status.timer_speed);
|
||||
s.integer(status.timers_enabled);
|
||||
s.integer(status.ram_disabled);
|
||||
s.integer(status.ram_writable);
|
||||
s.integer(status.timers_disabled);
|
||||
|
||||
s.integer(status.iplrom_enabled);
|
||||
|
||||
s.integer(status.dsp_addr);
|
||||
|
||||
s.integer(status.smp_f8);
|
||||
s.integer(status.smp_f9);
|
||||
|
||||
s.integer(t0.stage0_ticks);
|
||||
s.integer(t0.stage1_ticks);
|
||||
s.integer(t0.stage2_ticks);
|
||||
s.integer(t0.stage3_ticks);
|
||||
s.integer(t0.current_line);
|
||||
s.integer(t0.enabled);
|
||||
s.integer(t0.target);
|
||||
|
||||
s.integer(t1.stage0_ticks);
|
||||
s.integer(t1.stage1_ticks);
|
||||
s.integer(t1.stage2_ticks);
|
||||
s.integer(t1.stage3_ticks);
|
||||
s.integer(t1.current_line);
|
||||
s.integer(t1.enabled);
|
||||
s.integer(t1.target);
|
||||
|
||||
s.integer(t2.stage0_ticks);
|
||||
s.integer(t2.stage1_ticks);
|
||||
s.integer(t2.stage2_ticks);
|
||||
s.integer(t2.stage3_ticks);
|
||||
s.integer(t2.current_line);
|
||||
s.integer(t2.enabled);
|
||||
s.integer(t2.target);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,115 +3,65 @@
|
|||
#define SMP_CPP
|
||||
namespace SNES {
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#include "debugger/debugger.cpp"
|
||||
SMPDebugger smp;
|
||||
#else
|
||||
SMP smp;
|
||||
#endif
|
||||
SMP smp;
|
||||
|
||||
#include "snes_spc/Snes_Spc.cpp"
|
||||
#include "snes_spc/Spc_Core.cpp"
|
||||
#include "snes_spc/Spc_Core_impl.cpp"
|
||||
#include "snes_spc/Spc_Dsp.cpp"
|
||||
#include "snes_spc/Spc_Dsp_State.cpp"
|
||||
#include "snes_spc/Spc_Filter.cpp"
|
||||
#include "snes_spc/Spc_State.cpp"
|
||||
#include "snes_spc/spc.cpp"
|
||||
#include "snes_spc/blargg_common.cpp"
|
||||
#include "snes_spc/blargg_errors.cpp"
|
||||
|
||||
#include "serialization.cpp"
|
||||
#include "iplrom.cpp"
|
||||
#include "memory/memory.cpp"
|
||||
#include "timing/timing.cpp"
|
||||
|
||||
void SMP::step(unsigned clocks) {
|
||||
clock += clocks * (uint64)cpu.frequency;
|
||||
dsp.clock -= clocks;
|
||||
}
|
||||
|
||||
void SMP::synchronize_cpu() {
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
void SMP::synchronize_dsp() {
|
||||
if(dsp.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(dsp.thread);
|
||||
}
|
||||
|
||||
void SMP::Enter() { smp.enter(); }
|
||||
uint8 SMP::port_read(uint8 port) {
|
||||
return snes_spc.read_port(snes_spc_time, port & 3);
|
||||
}
|
||||
|
||||
void SMP::enter() {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
void SMP::port_write(uint8 port, uint8 data) {
|
||||
snes_spc.write_port(snes_spc_time, port & 3, data);
|
||||
}
|
||||
|
||||
void SMP::run() {
|
||||
step(24);
|
||||
if(++snes_spc_time >= snes_spc.clock_rate / 60) {
|
||||
snes_spc.end_frame(snes_spc_time);
|
||||
snes_spc_time = 0;
|
||||
static int16 buffer[8192];
|
||||
while(signed count = snes_spc.read_samples(buffer, 8192)) {
|
||||
for(unsigned n = 0; n < count; n += 2) audio.sample(buffer[n + 0], buffer[n + 1]);
|
||||
}
|
||||
|
||||
op_step();
|
||||
}
|
||||
}
|
||||
|
||||
void SMP::op_step() {
|
||||
(this->*opcode_table[op_readpc()])();
|
||||
}
|
||||
static void Enter() {}
|
||||
|
||||
void SMP::power() {
|
||||
//targets not initialized/changed upon reset
|
||||
t0.target = 0;
|
||||
t1.target = 0;
|
||||
t2.target = 0;
|
||||
|
||||
reset();
|
||||
create(Enter, 24576000); //system.apu_frequency()
|
||||
snes_spc.init();
|
||||
snes_spc.init_rom(iplrom);
|
||||
snes_spc.reset();
|
||||
snes_spc_time = 0;
|
||||
}
|
||||
|
||||
void SMP::reset() {
|
||||
create(Enter, system.apu_frequency());
|
||||
|
||||
regs.pc = 0xffc0;
|
||||
regs.a = 0x00;
|
||||
regs.x = 0x00;
|
||||
regs.y = 0x00;
|
||||
regs.sp = 0xef;
|
||||
regs.p = 0x02;
|
||||
|
||||
for(unsigned i = 0; i < memory::apuram.size(); i++) {
|
||||
memory::apuram.write(i, 0x00);
|
||||
}
|
||||
|
||||
status.clock_counter = 0;
|
||||
status.dsp_counter = 0;
|
||||
status.timer_step = 3;
|
||||
|
||||
//$00f0
|
||||
status.clock_speed = 0;
|
||||
status.timer_speed = 0;
|
||||
status.timers_enabled = true;
|
||||
status.ram_disabled = false;
|
||||
status.ram_writable = true;
|
||||
status.timers_disabled = false;
|
||||
|
||||
//$00f1
|
||||
status.iplrom_enabled = true;
|
||||
|
||||
//$00f2
|
||||
status.dsp_addr = 0x00;
|
||||
|
||||
//$00f8,$00f9
|
||||
status.smp_f8 = 0x00;
|
||||
status.smp_f9 = 0x00;
|
||||
|
||||
t0.stage0_ticks = 0;
|
||||
t1.stage0_ticks = 0;
|
||||
t2.stage0_ticks = 0;
|
||||
|
||||
t0.stage1_ticks = 0;
|
||||
t1.stage1_ticks = 0;
|
||||
t2.stage1_ticks = 0;
|
||||
|
||||
t0.stage2_ticks = 0;
|
||||
t1.stage2_ticks = 0;
|
||||
t2.stage2_ticks = 0;
|
||||
|
||||
t0.stage3_ticks = 0;
|
||||
t1.stage3_ticks = 0;
|
||||
t2.stage3_ticks = 0;
|
||||
|
||||
t0.current_line = 0;
|
||||
t1.current_line = 0;
|
||||
t2.current_line = 0;
|
||||
|
||||
t0.enabled = false;
|
||||
t1.enabled = false;
|
||||
t2.enabled = false;
|
||||
snes_spc.soft_reset();
|
||||
snes_spc_time = 0;
|
||||
}
|
||||
|
||||
SMP::SMP() {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class SMP : public Processor, public SMPcore {
|
||||
#include "snes_spc/Snes_Spc.h"
|
||||
|
||||
class SMP : public Processor {
|
||||
public:
|
||||
alwaysinline void step(unsigned clocks);
|
||||
alwaysinline void synchronize_cpu();
|
||||
|
@ -7,6 +9,7 @@ public:
|
|||
uint8 port_read(uint8 port);
|
||||
void port_write(uint8 port, uint8 data);
|
||||
|
||||
void run();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
|
@ -15,46 +18,11 @@ public:
|
|||
~SMP();
|
||||
|
||||
private:
|
||||
#include "memory/memory.hpp"
|
||||
#include "timing/timing.hpp"
|
||||
|
||||
Snes_Spc snes_spc;
|
||||
unsigned snes_spc_time;
|
||||
static const uint8 iplrom[64];
|
||||
|
||||
struct {
|
||||
//timing
|
||||
unsigned clock_counter;
|
||||
unsigned dsp_counter;
|
||||
unsigned timer_step;
|
||||
|
||||
//$00f0
|
||||
uint8 clock_speed;
|
||||
uint8 timer_speed;
|
||||
bool timers_enabled;
|
||||
bool ram_disabled;
|
||||
bool ram_writable;
|
||||
bool timers_disabled;
|
||||
|
||||
//$00f1
|
||||
bool iplrom_enabled;
|
||||
|
||||
//$00f2
|
||||
uint8 dsp_addr;
|
||||
|
||||
//$00f8,$00f9
|
||||
uint8 smp_f8, smp_f9;
|
||||
} status;
|
||||
|
||||
static void Enter();
|
||||
void enter();
|
||||
debugvirtual void op_step();
|
||||
|
||||
friend class SMPcore;
|
||||
friend class SMPDebugger;
|
||||
};
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#include "debugger/debugger.hpp"
|
||||
extern SMPDebugger smp;
|
||||
#else
|
||||
extern SMP smp;
|
||||
#endif
|
||||
extern SMP smp;
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
// SPC emulation support: init, sample buffering, reset, SPC loading
|
||||
|
||||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "Snes_Spc.h"
|
||||
|
||||
/* Copyright (C) 2004-2010 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// Extra silence added at beginning, to handle cases where fast DSP falls
|
||||
// behind and thus doesn't generate all the samples needed.
|
||||
int const extra_samples = 2;
|
||||
|
||||
int const stereo = 2; // for clarity
|
||||
|
||||
char const Snes_Spc::signature [signature_size + 1] =
|
||||
"SNES-SPC700 Sound File Data v0.30\x1A\x1A";
|
||||
|
||||
blargg_err_t Snes_Spc::init()
|
||||
{
|
||||
RETURN_ERR( Spc_Core::init() );
|
||||
RETURN_ERR( buf.resize( sample_rate / 30 * stereo ) );
|
||||
reset_samples();
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Snes_Spc::reset_samples()
|
||||
{
|
||||
user_out = NULL;
|
||||
user_pos = 0;
|
||||
user_size = 0;
|
||||
|
||||
clocks_avail = 0;
|
||||
memset( buf.begin(), 0, extra_samples * sizeof buf [0] );
|
||||
dsp().set_output( buf.begin() + extra_samples, buf.end() );
|
||||
}
|
||||
|
||||
void Snes_Spc::reset()
|
||||
{
|
||||
reset_samples();
|
||||
Spc_Core::reset();
|
||||
}
|
||||
|
||||
void Snes_Spc::soft_reset()
|
||||
{
|
||||
reset_samples();
|
||||
Spc_Core::soft_reset();
|
||||
}
|
||||
|
||||
inline int Snes_Spc::raw_samples_avail() const
|
||||
{
|
||||
return dsp().output_ptr() - buf.begin();
|
||||
}
|
||||
|
||||
int Snes_Spc::samples_avail() const
|
||||
{
|
||||
return (unsigned) clocks_avail / clocks_per_sample * stereo;
|
||||
}
|
||||
|
||||
int Snes_Spc::remove_samples( int count )
|
||||
{
|
||||
count = min( count, samples_avail() );
|
||||
int remain = raw_samples_avail() - count;
|
||||
if ( remain < 0 )
|
||||
{
|
||||
check( false );
|
||||
count += remain;
|
||||
remain = 0;
|
||||
}
|
||||
|
||||
clocks_avail -= clocks_per_sample / stereo * count;
|
||||
memmove( buf.begin(), buf.begin() + count, remain * sizeof buf [0] );
|
||||
dsp().set_output( buf.begin() + remain, buf.end() );
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int Snes_Spc::read_samples( sample_t out [], int count )
|
||||
{
|
||||
count = min( count, samples_avail() );
|
||||
|
||||
memcpy( out, buf.begin(), count * sizeof buf [0] );
|
||||
|
||||
remove_samples( count );
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Keep track of number of samples we should have generated so far, since
|
||||
// fast DSP might be slightly ahead or behind.
|
||||
void Snes_Spc::end_frame( time_t end )
|
||||
{
|
||||
clocks_avail += end;
|
||||
Spc_Core::end_frame( end );
|
||||
|
||||
int excess = samples_avail() - raw_samples_avail();
|
||||
|
||||
// Worst-case, DSP can be 6+36 clocks ahead, and clocks_avail%32==31,
|
||||
// generating an extra 4 samples.
|
||||
check( excess >= -4 - extra_samples );
|
||||
|
||||
if ( excess > 0 )
|
||||
{
|
||||
// DSP should never get behind what's needed, given that it
|
||||
// starts with extra_samples in buffer.
|
||||
clocks_avail -= clocks_per_sample / stereo * excess;
|
||||
check( false );
|
||||
}
|
||||
}
|
||||
|
||||
#if BLARGG_LEGACY
|
||||
|
||||
void Snes_Spc::set_output( sample_t* out, int out_size )
|
||||
{
|
||||
user_out = out;
|
||||
user_pos = 0;
|
||||
user_size = out_size;
|
||||
}
|
||||
|
||||
int Snes_Spc::sample_count() const
|
||||
{
|
||||
int more = read_samples( user_out + user_pos, user_size - user_pos );
|
||||
CONST_CAST(int&,user_pos) += more;
|
||||
return user_pos;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
blargg_err_t Snes_Spc::load_spc( void const* data, long size )
|
||||
{
|
||||
spc_file_t const* const spc = (spc_file_t const*) data;
|
||||
|
||||
// be sure compiler didn't insert any padding into spc_file_t
|
||||
assert( sizeof (spc_file_t) == spc_min_file_size + 0x80 );
|
||||
|
||||
// Check signature and file size
|
||||
if ( size < signature_size || memcmp( spc, signature, 27 ) )
|
||||
return "Not an SPC file";
|
||||
|
||||
if ( size < spc_min_file_size )
|
||||
return "Corrupt SPC file";
|
||||
|
||||
reset();
|
||||
|
||||
// CPU registers
|
||||
cpu().pc = spc->pch * 0x100 + spc->pcl;
|
||||
cpu().a = spc->a;
|
||||
cpu().x = spc->x;
|
||||
cpu().y = spc->y;
|
||||
cpu().psw = spc->psw;
|
||||
cpu().sp = spc->sp;
|
||||
|
||||
// RAM and registers
|
||||
memcpy( ram(), spc->ram, 0x10000 );
|
||||
ram_loaded();
|
||||
static byte const new_ports [port_count] = { };
|
||||
load_regs( spc->ram + 0xF0, new_ports );
|
||||
|
||||
// DSP registers
|
||||
dsp().load( spc->dsp );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Snes_Spc::clear_echo()
|
||||
{
|
||||
if ( !(dsp().read( Spc_Dsp::r_flg ) & 0x20) )
|
||||
{
|
||||
int addr = 0x100 * dsp().read( Spc_Dsp::r_esa );
|
||||
int end = addr + 0x800 * (dsp().read( Spc_Dsp::r_edl ) & 0x0F);
|
||||
|
||||
addr = max( addr, 0x200 );
|
||||
end = min( end, 0x10000 ); // if it wraps around, stop at end of RAM
|
||||
|
||||
// 0xFF = STOP, so if this overwrites any code, SPC won't play,
|
||||
// rather than behave strangely
|
||||
memset( &ram() [addr], 0xFF, end - addr );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Sample output
|
||||
|
||||
blargg_err_t Snes_Spc::play( int count, sample_t out [] )
|
||||
{
|
||||
assert( count % 2 == 0 ); // must be even
|
||||
|
||||
// Refill and empty buffer until satisfied
|
||||
while ( count > 0 )
|
||||
{
|
||||
if ( samples_avail() == 0 )
|
||||
end_frame( clocks_per_sample / stereo * (buf.size() - 16) );
|
||||
|
||||
int n;
|
||||
if ( out )
|
||||
{
|
||||
n = read_samples( out, count );
|
||||
out += n;
|
||||
}
|
||||
else
|
||||
{
|
||||
n = min( samples_avail(), count );
|
||||
remove_samples( n );
|
||||
}
|
||||
|
||||
count -= n;
|
||||
}
|
||||
|
||||
return error();
|
||||
}
|
||||
|
||||
blargg_err_t Snes_Spc::skip( int count )
|
||||
{
|
||||
assert( count % 2 == 0 );
|
||||
|
||||
count -= remove_samples( count );
|
||||
|
||||
int const final_skip = sample_rate * stereo;
|
||||
|
||||
if ( count >= 2 * final_skip )
|
||||
{
|
||||
end_frame_skip( clocks_per_sample / stereo * (count - final_skip) );
|
||||
clear_echo();
|
||||
|
||||
count = final_skip;
|
||||
}
|
||||
|
||||
return play( count, NULL );
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
// SNES SPC-700 APU emulator/SPC music file player
|
||||
|
||||
// snes_spc 0.9.5
|
||||
#ifndef BLARGG_SNES_SPC_H
|
||||
#define BLARGG_SNES_SPC_H
|
||||
|
||||
#include "spc.h"
|
||||
#include "Spc_Core.h"
|
||||
#include "blargg_endian.h"
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
struct spc_t : public Spc_Core {
|
||||
public:
|
||||
// Must be called once before using. OK to call more than once.
|
||||
blargg_err_t init();
|
||||
|
||||
// Sample pairs generated per second
|
||||
enum { sample_rate = 32000 };
|
||||
|
||||
// Number of samples in internal buffer. Increased by 2 (stereo) every 32
|
||||
// clocks of emulation.
|
||||
int samples_avail() const;
|
||||
|
||||
// Reads at most count samples from internal buffer and returns number
|
||||
// actually read, less if samples_avail() < count
|
||||
typedef short sample_t;
|
||||
int read_samples( sample_t out [], int count );
|
||||
|
||||
// Removes samples without reading. Returns number actually removed.
|
||||
int remove_samples( int count );
|
||||
|
||||
// Sound control
|
||||
|
||||
// Mutes voices corresponding to non-zero bits in mask.
|
||||
// Reduces emulation accuracy.
|
||||
enum { voice_count = 8 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// If true, prevents channels and global volumes from being phase-negated.
|
||||
// Only supported by fast DSP.
|
||||
void disable_surround( bool disable = true );
|
||||
|
||||
// SPC music files
|
||||
|
||||
// Loads SPC data into emulator
|
||||
enum { spc_min_file_size = 0x10180 };
|
||||
enum { spc_file_size = 0x10200 };
|
||||
blargg_err_t load_spc( void const* in, long size );
|
||||
|
||||
// Clears echo region if appropriate. Useful after loading an SPC, as many have
|
||||
// garbage in echo.
|
||||
void clear_echo();
|
||||
|
||||
// Plays for count samples and write samples to out. Discards samples if out
|
||||
// is NULL. Count must be a multiple of 2 since output is stereo.
|
||||
blargg_err_t play( int count, sample_t out [] );
|
||||
|
||||
// Skips count samples. When using fast DSP, this is several times faster than
|
||||
// simply calling play() and discarding samples.
|
||||
blargg_err_t skip( int count );
|
||||
|
||||
// only available when using accurate DSP
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
|
||||
// Writes minimal header to spc_out
|
||||
static void init_header( void* spc_out );
|
||||
|
||||
// Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out.
|
||||
// Does not set up SPC header; use init_header() for that.
|
||||
void save_spc( void* spc_out );
|
||||
|
||||
// Returns true if new key-on events occurred since last check. Useful for
|
||||
// trimming silence while saving an SPC.
|
||||
bool check_kon();
|
||||
#endif
|
||||
|
||||
enum { signature_size = 35 };
|
||||
|
||||
public:
|
||||
// "Overrides"
|
||||
void reset();
|
||||
void soft_reset();
|
||||
void end_frame( time_t );
|
||||
|
||||
// Deprecated
|
||||
BLARGG_DEPRECATED( void set_output( sample_t* out, int out_size ); )
|
||||
BLARGG_DEPRECATED( int sample_count() const; )
|
||||
|
||||
private:
|
||||
struct spc_file_t
|
||||
{
|
||||
char signature [signature_size];
|
||||
byte has_id666;
|
||||
byte version;
|
||||
byte pcl, pch;
|
||||
byte a;
|
||||
byte x;
|
||||
byte y;
|
||||
byte psw;
|
||||
byte sp;
|
||||
char text [212];
|
||||
byte ram [0x10000];
|
||||
byte dsp [128];
|
||||
byte unused [0x40];
|
||||
byte ipl_rom [0x40];
|
||||
};
|
||||
|
||||
static char const signature [signature_size + 1];
|
||||
|
||||
int clocks_avail;
|
||||
blargg_vector<sample_t> buf;
|
||||
|
||||
sample_t* user_out;
|
||||
BLARGG_MUTABLE int user_pos;
|
||||
int user_size;
|
||||
|
||||
int raw_samples_avail() const;
|
||||
void reset_samples();
|
||||
};
|
||||
|
||||
inline void Snes_Spc::mute_voices( int mask )
|
||||
{
|
||||
dsp().mute_voices( mask );
|
||||
}
|
||||
|
||||
inline void Snes_Spc::disable_surround( bool disable )
|
||||
{
|
||||
dsp().disable_surround( disable );
|
||||
}
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
inline bool Snes_Spc::check_kon()
|
||||
{
|
||||
return dsp().check_kon();
|
||||
}
|
||||
#endif
|
||||
|
||||
BLARGG_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,256 @@
|
|||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "Spc_Core.h"
|
||||
|
||||
/* Copyright (C) 2004-2010 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void Spc_Core::set_tempo( int t )
|
||||
{
|
||||
#if !SPC_DISABLE_TEMPO
|
||||
tempo = t;
|
||||
|
||||
if ( !t )
|
||||
t = 1;
|
||||
|
||||
int const timer2_rate = 1 << timer2_shift;
|
||||
int rate = (timer2_rate * tempo_unit + (t >> 1)) / t;
|
||||
if ( rate < timer2_rate / 4 )
|
||||
rate = timer2_rate / 4; // max 4x tempo
|
||||
|
||||
prescaler_base = rate;
|
||||
#endif
|
||||
}
|
||||
|
||||
blargg_err_t Spc_Core::init()
|
||||
{
|
||||
BLARGG_CLEAR( ®s );
|
||||
BLARGG_CLEAR( ®s_in );
|
||||
|
||||
// Most SPC music doesn't need ROM, and almost all the rest only rely
|
||||
// on these two bytes
|
||||
BLARGG_CLEAR( &rom_ );
|
||||
rom_ [0x3E] = 0xFF;
|
||||
rom_ [0x3F] = 0xC0;
|
||||
|
||||
set_tempo( tempo_unit );
|
||||
dsp_.init( ram() );
|
||||
reset();
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Spc_Core::init_rom( byte const in [rom_size] )
|
||||
{
|
||||
memcpy( rom_, in, sizeof rom_ );
|
||||
}
|
||||
|
||||
void Spc_Core::enable_rom( bool enable )
|
||||
{
|
||||
if ( rom_enabled != enable )
|
||||
{
|
||||
rom_enabled = enable;
|
||||
if ( enable )
|
||||
memcpy( hi_ram, &ram() [rom_addr], sizeof hi_ram );
|
||||
|
||||
memcpy( &ram() [rom_addr], (enable ? rom_ : hi_ram), rom_size );
|
||||
// TODO: ROM can still get overwritten when DSP writes to echo buffer
|
||||
}
|
||||
}
|
||||
|
||||
void Spc_Core::ram_loaded()
|
||||
{
|
||||
// ROM isn't swapped in anymore, since its area was overwritten with
|
||||
// contents of RAM there
|
||||
rom_enabled = false;
|
||||
|
||||
// Put STOP instruction around memory to catch PC underflow/overflow
|
||||
memset( padded_ram, padding_fill, ram_padding );
|
||||
memset( padded_ram + ram_padding + ram_size, padding_fill, ram_padding );
|
||||
}
|
||||
|
||||
void Spc_Core::load_regs( byte const new_regs [reg_count], byte const ports [port_count] )
|
||||
{
|
||||
memcpy( regs, new_regs, reg_count );
|
||||
memcpy( regs_in, regs, reg_count );
|
||||
|
||||
// These always read back as 0 or are handled specially
|
||||
// Commented registers are already copied from regs.
|
||||
regs_in [r_test ] = 0;
|
||||
regs_in [r_control ] = 0;
|
||||
// r_dspaddr
|
||||
regs_in [r_dspdata ] = 0xFF; // in case attempt is made to execute value
|
||||
// r_cpuio0
|
||||
// r_cpuio1
|
||||
// r_cpuio2
|
||||
// r_cpuio3
|
||||
// 8
|
||||
// 9
|
||||
regs_in [r_t0target] = 0;
|
||||
regs_in [r_t1target] = 0;
|
||||
regs_in [r_t2target] = 0;
|
||||
regs_in [r_t0out ] &= 0x0F;
|
||||
regs_in [r_t1out ] &= 0x0F;
|
||||
regs_in [r_t2out ] &= 0x0F;
|
||||
|
||||
// RAM should always hold copy of regs_in
|
||||
memcpy( ram() + regs_addr, regs_in, reg_count );
|
||||
|
||||
enable_rom( regs [r_control] & 0x80 );
|
||||
|
||||
for ( int i = 0; i < port_count; i++ )
|
||||
regs [r_cpuio0 + i] = ports [i];
|
||||
}
|
||||
|
||||
void Spc_Core::reset_common( int timer_counter_init )
|
||||
{
|
||||
memset( &cpu_regs, 0, sizeof cpu_regs );
|
||||
cpu_regs.pc = rom_addr;
|
||||
|
||||
cpu_error = NULL;
|
||||
echo_accessed = 0;
|
||||
spc_time = 0;
|
||||
dsp_time = 0;
|
||||
skipped_kon = 0;
|
||||
skipped_koff = 0;
|
||||
|
||||
#if SPC_LESS_ACCURATE
|
||||
dsp_time = clocks_per_sample + 1;
|
||||
#endif
|
||||
|
||||
int i;
|
||||
for ( i = 0; i < timer_count; i++ )
|
||||
{
|
||||
timers [i].time = 1;
|
||||
timers [i].divider = 0;
|
||||
}
|
||||
|
||||
byte new_regs [reg_count] = { };
|
||||
|
||||
new_regs [r_test ] = 0x0A;
|
||||
new_regs [r_control] = 0x80; // ROM enabled
|
||||
|
||||
for ( i = 0; i < timer_count; i++ )
|
||||
new_regs [r_t0out + i] = timer_counter_init;
|
||||
|
||||
static byte const new_ports [port_count] = { };
|
||||
|
||||
load_regs( new_regs, new_ports );
|
||||
}
|
||||
|
||||
void Spc_Core::soft_reset()
|
||||
{
|
||||
reset_common( 0 );
|
||||
|
||||
dsp_.soft_reset();
|
||||
}
|
||||
|
||||
void Spc_Core::reset()
|
||||
{
|
||||
memset( ram(), 0xFF, 0x10000 );
|
||||
ram_loaded();
|
||||
|
||||
reset_common( 0x0F );
|
||||
|
||||
dsp_.reset();
|
||||
}
|
||||
|
||||
const char* Spc_Core::error()
|
||||
{
|
||||
const char* e = cpu_error;
|
||||
cpu_error = NULL;
|
||||
return e;
|
||||
}
|
||||
|
||||
bool Spc_Core::check_echo_access( int addr )
|
||||
{
|
||||
if ( !(dsp_.read( dsp_.r_flg ) & 0x20) )
|
||||
{
|
||||
int start = 0x100 * dsp_.read( dsp_.r_esa );
|
||||
int size = 0x800 * (dsp_.read( dsp_.r_edl ) & 0x0F);
|
||||
int end = start + (size ? size : 4);
|
||||
if ( start <= addr && addr < end )
|
||||
{
|
||||
if ( !echo_accessed )
|
||||
{
|
||||
echo_accessed = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// (n ? n : 256) & 0xFF
|
||||
inline int if_0_then_256( int n )
|
||||
{
|
||||
return byte (n - 1) + 1;
|
||||
}
|
||||
|
||||
void Spc_Core::run_timer_( rel_time_t time, int index )
|
||||
{
|
||||
Timer& t = timers [index];
|
||||
check( time >= t.time );
|
||||
|
||||
int elapsed; // number of ticks, guaranteed at least 1
|
||||
int const timer01_shift = 3;
|
||||
|
||||
#if SPC_DISABLE_TEMPO
|
||||
{
|
||||
int shift = timer2_shift;
|
||||
if ( index < 2 )
|
||||
shift += timer01_shift;
|
||||
|
||||
elapsed = ((time - t.time) >> shift) + 1;
|
||||
t.time += elapsed << shift;
|
||||
}
|
||||
#else
|
||||
{
|
||||
int prescaler = prescaler_base;
|
||||
if ( index < 2 )
|
||||
prescaler <<= timer01_shift;
|
||||
|
||||
elapsed = (time - t.time) / prescaler + 1;
|
||||
t.time += elapsed * prescaler;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( regs [r_control] >> index & 1 )
|
||||
{
|
||||
int period = if_0_then_256( regs [r_t0target + index] );
|
||||
|
||||
// Ticks until divider will output tick. If divider currently matches
|
||||
// period, it will be 256 ticks until output tick, not 0.
|
||||
int remain = if_0_then_256( period - t.divider );
|
||||
|
||||
// value divider will take, assuming no tick
|
||||
int divider = t.divider + elapsed;
|
||||
|
||||
// time relative to when tick will occur
|
||||
int over = elapsed - remain;
|
||||
if ( over >= 0 )
|
||||
{
|
||||
// ticks elapsed in second stage
|
||||
int elapsed2 = over / period;
|
||||
|
||||
regs_in [r_t0out + index] =
|
||||
(regs_in [r_t0out + index] + 1 + elapsed2) & 0x0F;
|
||||
|
||||
// remaining count on divider
|
||||
divider = over - elapsed2 * period;
|
||||
}
|
||||
|
||||
// Must mask for cases when divider has gone past target
|
||||
t.divider = divider & 0xFF;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
#ifndef BLARGG_SPC_CORE_H
|
||||
#define BLARGG_SPC_CORE_H
|
||||
|
||||
#include "Spc_Dsp.h"
|
||||
#include "blargg_endian.h"
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
class Spc_Core {
|
||||
public:
|
||||
typedef BOOST::uint8_t byte;
|
||||
|
||||
// Must be called once before using. OK to call more than once.
|
||||
blargg_err_t init();
|
||||
|
||||
// Sets IPL ROM data. Default IPL ROM is zero-filled. Most SPC music files
|
||||
// don't need ROM, but a full emulator MUST provide this.
|
||||
enum { rom_size = 0x40 };
|
||||
void init_rom( byte const rom [rom_size] );
|
||||
|
||||
// Reset
|
||||
|
||||
// Use functions in order listed here
|
||||
|
||||
// Resets to power-on state
|
||||
void reset();
|
||||
|
||||
// Emulates pressing reset on SNES
|
||||
void soft_reset();
|
||||
|
||||
// Pointer to 64K RAM
|
||||
byte* ram();
|
||||
|
||||
// S-SMP CPU registers
|
||||
struct regs_t
|
||||
{
|
||||
int pc;
|
||||
int a;
|
||||
int x;
|
||||
int y;
|
||||
int psw;
|
||||
int sp;
|
||||
};
|
||||
regs_t& cpu() { return cpu_regs; }
|
||||
|
||||
// S-DSP
|
||||
Spc_Dsp& dsp() { return dsp_; }
|
||||
const Spc_Dsp& dsp() const { return dsp_; }
|
||||
|
||||
// Emulation
|
||||
|
||||
// 1024000 SPC clocks per second, sample pair every 32 clocks
|
||||
typedef int time_t;
|
||||
enum { clock_rate = 1024000 };
|
||||
enum { clocks_per_sample = 32 };
|
||||
|
||||
// Emulates port read/write at specified time
|
||||
enum { port_count = 4 };
|
||||
int read_port ( time_t, int port );
|
||||
void write_port( time_t, int port, int data );
|
||||
|
||||
// Runs SPC to end_time and starts a new time frame at 0
|
||||
void end_frame( time_t end_time );
|
||||
|
||||
// Sound control
|
||||
|
||||
// Sets tempo, where tempo_unit = normal, tempo_unit/2 = half speed,
|
||||
// tempo_unit*2 = double speed, etc.
|
||||
enum { tempo_unit = 0x100 };
|
||||
void set_tempo( int );
|
||||
|
||||
// State save/load (only available with accurate DSP)
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
// Saves/loads state
|
||||
enum { state_size = 67 * 1024 }; // maximum space needed when saving
|
||||
typedef Spc_Dsp::copy_func_t copy_func_t;
|
||||
void copy_state( unsigned char** io, copy_func_t );
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
// Pointer to string describing CPU emulation error since last call, or NULL
|
||||
// if none. Clears error after returning.
|
||||
const char* error();
|
||||
|
||||
enum { regs_addr = 0xF0 };
|
||||
enum { reg_count = 0x10 };
|
||||
|
||||
// Saves registers in unified 16-byte format, and output ports
|
||||
// separately
|
||||
void save_regs( byte out [reg_count], byte out_ports [port_count] );
|
||||
|
||||
// Loads registers from unified 16-byte format. Most registers are
|
||||
// last values S-SMP wrote. CPUIOx are last values S-CPU wrote to
|
||||
// APUIOx. TxOUT are values to load into timer counters. DSPDATA is
|
||||
// ignored. CONTROL is restored without disturbing CPUIOx and TxOUT.
|
||||
void load_regs( byte const in [reg_count], byte const ports [port_count] );
|
||||
|
||||
void save_ram( byte out [65536] );
|
||||
|
||||
// Call after loading new data into entire RAM
|
||||
void ram_loaded();
|
||||
|
||||
byte const* rom() const { return rom_; }
|
||||
|
||||
void end_frame_skip( time_t );
|
||||
|
||||
public:
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
enum { skipping_time = 127 };
|
||||
private:
|
||||
// Time relative to m_spc_time. Speeds up code a bit by eliminating need to
|
||||
// constantly add m_spc_time to time from CPU. CPU uses time that ends at
|
||||
// 0 to eliminate reloading end time every instruction. It pays off.
|
||||
typedef int rel_time_t;
|
||||
|
||||
// Main goal for emulator state is to have as little redundancy as possible.
|
||||
// That means the last values written to each SMP register, and any internal
|
||||
// state needed (e.g. timer prescaler and divider, but not counters, which
|
||||
// are stored in regs_in).
|
||||
|
||||
// Registers
|
||||
enum {
|
||||
r_test = 0x0, r_control = 0x1,
|
||||
r_dspaddr = 0x2, r_dspdata = 0x3,
|
||||
r_cpuio0 = 0x4, r_cpuio1 = 0x5,
|
||||
r_cpuio2 = 0x6, r_cpuio3 = 0x7,
|
||||
r_f8 = 0x8, r_f9 = 0x9,
|
||||
r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC,
|
||||
r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF
|
||||
};
|
||||
byte regs [reg_count]; // last values SMP wrote
|
||||
byte regs_in [reg_count]; // values SMP should read, except timers
|
||||
|
||||
const char* cpu_error;
|
||||
bool echo_accessed;
|
||||
time_t spc_time;
|
||||
rel_time_t dsp_time;
|
||||
int tempo;
|
||||
int skipped_kon;
|
||||
int skipped_koff;
|
||||
regs_t cpu_regs;
|
||||
|
||||
// IPL ROM
|
||||
enum { rom_addr = 0xFFC0 };
|
||||
bool rom_enabled;
|
||||
byte rom_ [rom_size];
|
||||
byte hi_ram [rom_size];
|
||||
void enable_rom( bool enable );
|
||||
|
||||
// Timers
|
||||
enum { timer2_shift = 4 };
|
||||
enum { timer_count = 3 };
|
||||
struct Timer
|
||||
{
|
||||
rel_time_t time; // time of next stage 0 tick
|
||||
int divider;
|
||||
};
|
||||
Timer timers [timer_count];
|
||||
int prescaler_base;
|
||||
void adjust_timers( int offset );
|
||||
void run_timer_ ( rel_time_t, int index );
|
||||
void run_timer ( rel_time_t, int index );
|
||||
void enable_timers( rel_time_t, int mask );
|
||||
int read_timer ( rel_time_t, int index );
|
||||
|
||||
Spc_Dsp dsp_;
|
||||
|
||||
// 64K RAM with padding for CPU emulator to hit if it goes outside
|
||||
enum { padding_fill = 0xFF };
|
||||
enum { ram_padding = 0x100 };
|
||||
enum { ram_size = 0x10000 };
|
||||
byte padded_ram [ram_padding + ram_size + ram_padding];
|
||||
|
||||
void cpu_write_smp_reg_( rel_time_t, int data, int reg );
|
||||
void cpu_write_smp_reg ( rel_time_t, int data, int reg );
|
||||
void cpu_write_high ( rel_time_t, int data, int offset );
|
||||
void cpu_write ( rel_time_t, int data, int addr );
|
||||
|
||||
void dsp_write ( rel_time_t, int data );
|
||||
int dsp_read ( rel_time_t );
|
||||
|
||||
int cpu_read_smp_reg ( rel_time_t, int reg );
|
||||
int cpu_read ( rel_time_t, int addr );
|
||||
|
||||
unsigned cpu_mem_bit ( rel_time_t, byte const* pc );
|
||||
|
||||
void write_apuio( int port, int data );
|
||||
void reset_common( int timer_counter_init );
|
||||
bool check_echo_access ( int addr );
|
||||
void run_until_( time_t end_time );
|
||||
};
|
||||
|
||||
inline Spc_Core::byte* Spc_Core::ram()
|
||||
{
|
||||
return &padded_ram [ram_padding];
|
||||
}
|
||||
|
||||
inline int Spc_Core::read_port( time_t t, int port )
|
||||
{
|
||||
assert( (unsigned) port < port_count );
|
||||
run_until_( t );
|
||||
return regs [r_cpuio0 + port];
|
||||
}
|
||||
|
||||
inline void Spc_Core::write_apuio( int port, int data )
|
||||
{
|
||||
regs_in [ r_cpuio0 + port] = data;
|
||||
ram() [regs_addr + r_cpuio0 + port] = data;
|
||||
}
|
||||
|
||||
inline void Spc_Core::write_port( time_t t, int port, int data )
|
||||
{
|
||||
assert( (unsigned) port < port_count );
|
||||
run_until_( t );
|
||||
write_apuio( port, data );
|
||||
}
|
||||
|
||||
inline void Spc_Core::end_frame_skip( rel_time_t end )
|
||||
{
|
||||
assert( end % clocks_per_sample == 0 );
|
||||
|
||||
skipped_kon = 0;
|
||||
skipped_koff = 0;
|
||||
|
||||
// Bump DSP by count, so it won't have to run at all
|
||||
dsp_time += end;
|
||||
end_frame( end );
|
||||
|
||||
// Write most recent KOFF/KONs
|
||||
dsp_.write( Spc_Dsp::r_koff, skipped_koff & ~skipped_kon );
|
||||
dsp_.write( Spc_Dsp::r_kon , skipped_kon );
|
||||
}
|
||||
|
||||
BLARGG_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,455 @@
|
|||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "Spc_Core.h"
|
||||
|
||||
//#include "spc_cpu_log.h"
|
||||
|
||||
/* Copyright (C) 2004-2010 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// Note: SPC_MORE_ACCURACY exists mainly so I can run my validation tests, which
|
||||
// do crazy echo buffer accesses.
|
||||
#ifndef SPC_MORE_ACCURACY
|
||||
#define SPC_MORE_ACCURACY 0
|
||||
#endif
|
||||
|
||||
|
||||
//// Timers
|
||||
|
||||
inline void Spc_Core::adjust_timers( int offset )
|
||||
{
|
||||
timers [0].time += offset;
|
||||
timers [1].time += offset;
|
||||
timers [2].time += offset;
|
||||
}
|
||||
|
||||
inline void Spc_Core::run_timer( rel_time_t time, int i )
|
||||
{
|
||||
if ( time >= timers [i].time )
|
||||
run_timer_( time, i );
|
||||
}
|
||||
|
||||
inline void Spc_Core::enable_timers( rel_time_t time, int new_control )
|
||||
{
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
{
|
||||
int const bit = 1 << i;
|
||||
|
||||
int changed = new_control ^ regs [r_control];
|
||||
if ( changed & bit )
|
||||
{
|
||||
run_timer( time, i );
|
||||
|
||||
if ( new_control & bit )
|
||||
{
|
||||
// Timer just enabled
|
||||
regs_in [r_t0out + i] = 0;
|
||||
timers [i].divider = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline int Spc_Core::read_timer( rel_time_t time, int i )
|
||||
{
|
||||
run_timer( time, i );
|
||||
|
||||
int result = regs_in [r_t0out + i];
|
||||
regs_in [r_t0out + i] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//// DSP
|
||||
|
||||
#if SPC_LESS_ACCURATE
|
||||
int const max_reg_time = 29;
|
||||
|
||||
/* Fast DSP only runs every 32nd clock. By adjusting the end time based
|
||||
on which register is being accessed, in most cases the register access
|
||||
is emulated at the precise time. */
|
||||
static signed char const reg_times [256] =
|
||||
{
|
||||
-1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22,
|
||||
2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23,
|
||||
5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23,
|
||||
8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24,
|
||||
11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24,
|
||||
14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24,
|
||||
17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25,
|
||||
20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25,
|
||||
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
};
|
||||
|
||||
// not surrounded in block so else can be added
|
||||
#define RUN_DSP( time, offset ) \
|
||||
int count = (time) - (offset) - dsp_time;\
|
||||
if ( count >= 0 )\
|
||||
{\
|
||||
int clock_count = (count & ~(clocks_per_sample - 1)) + clocks_per_sample;\
|
||||
dsp_time += clock_count;\
|
||||
dsp_.run( clock_count );\
|
||||
}
|
||||
#else
|
||||
#define RUN_DSP( time, ignored ) \
|
||||
{\
|
||||
int count = (time) - dsp_time;\
|
||||
if ( !SPC_MORE_ACCURACY || count )\
|
||||
{\
|
||||
assert( count > 0 );\
|
||||
dsp_time = (time);\
|
||||
dsp_.run( count );\
|
||||
}\
|
||||
}
|
||||
#endif
|
||||
|
||||
int Spc_Core::dsp_read( rel_time_t time )
|
||||
{
|
||||
RUN_DSP( time, reg_times [regs [r_dspaddr] & 0x7F] );
|
||||
|
||||
return dsp_.read( regs [r_dspaddr] & 0x7F );
|
||||
}
|
||||
|
||||
inline void Spc_Core::dsp_write( rel_time_t time, int data )
|
||||
{
|
||||
RUN_DSP( time, reg_times [regs [r_dspaddr]] )
|
||||
#if SPC_LESS_ACCURATE
|
||||
else if ( dsp_time == skipping_time )
|
||||
{
|
||||
int r = regs [r_dspaddr];
|
||||
if ( r == Spc_Dsp::r_kon )
|
||||
skipped_kon |= data & ~dsp_.read( Spc_Dsp::r_koff );
|
||||
|
||||
if ( r == Spc_Dsp::r_koff )
|
||||
{
|
||||
skipped_koff |= data;
|
||||
skipped_kon &= ~data;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( regs [r_dspaddr] <= 0x7F )
|
||||
dsp_.write( regs [r_dspaddr], data );
|
||||
else if ( !SPC_MORE_ACCURACY )
|
||||
dprintf( "SPC wrote to DSP register > $7F\n" );
|
||||
}
|
||||
|
||||
|
||||
//// Memory access
|
||||
|
||||
#if SPC_MORE_ACCURACY
|
||||
#define MEM_ACCESS( time, addr ) \
|
||||
if ( time >= dsp_time )\
|
||||
{ RUN_DSP( time, max_reg_time ); }\
|
||||
|
||||
#elif !defined (NDEBUG)
|
||||
// Debug-only check for read/write within echo buffer, since this might result in
|
||||
// inaccurate emulation due to the DSP not being caught up to the present.
|
||||
#define MEM_ACCESS( time, addr ) check( !check_echo_access( (BOOST::uint16_t) addr ) );
|
||||
|
||||
#else
|
||||
#define MEM_ACCESS( time, addr )
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// Read/write handlers are divided into multiple functions to keep rarely-used
|
||||
// functionality separate so often-used functionality can be optimized better
|
||||
// by compiler.
|
||||
|
||||
// If write isn't preceded by read, data has this added to it
|
||||
int const no_read_before_write = 0x2000;
|
||||
|
||||
void Spc_Core::cpu_write_smp_reg_( rel_time_t time, int data, int reg )
|
||||
{
|
||||
// Undo write to RAM, since most writes shouldn't affect value read back
|
||||
ram() [regs_addr + reg] = regs_in [reg];
|
||||
|
||||
switch ( reg )
|
||||
{
|
||||
case r_test:
|
||||
if ( (byte) data != 0x0A )
|
||||
dprintf( "SPC wrote to TEST register\n" );
|
||||
break;
|
||||
|
||||
case r_control:
|
||||
if ( data & 0x10 )
|
||||
{
|
||||
write_apuio( 0, 0 );
|
||||
write_apuio( 1, 0 );
|
||||
}
|
||||
|
||||
if ( data & 0x20 )
|
||||
{
|
||||
write_apuio( 2, 0 );
|
||||
write_apuio( 3, 0 );
|
||||
}
|
||||
|
||||
enable_timers( time, data );
|
||||
|
||||
enable_rom( data & 0x80 );
|
||||
break;
|
||||
|
||||
// Registers that act like RAM
|
||||
case r_dspaddr:
|
||||
case 0x8:
|
||||
case 0x9:
|
||||
ram() [regs_addr + reg] = data;
|
||||
regs_in [ reg] = data;
|
||||
break;
|
||||
|
||||
case r_t0target:
|
||||
case r_t1target:
|
||||
case r_t2target:
|
||||
run_timer( time, reg - r_t0target );
|
||||
break;
|
||||
|
||||
case r_t0out:
|
||||
case r_t1out:
|
||||
case r_t2out:
|
||||
if ( !SPC_MORE_ACCURACY )
|
||||
dprintf( "SPC wrote to counter %d\n", (int) reg - r_t0out );
|
||||
|
||||
if ( data < no_read_before_write / 2 )
|
||||
{
|
||||
// This was a write which also reads one clock before. That read
|
||||
// causes the counter to be cleared.
|
||||
read_timer( time - 1, reg - r_t0out );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
regs [reg] = data;
|
||||
}
|
||||
|
||||
void Spc_Core::cpu_write_high( rel_time_t time, int data, int i )
|
||||
{
|
||||
// i = addr - rom_addr
|
||||
|
||||
// $FFC0-$FFFF
|
||||
if ( i < rom_size )
|
||||
{
|
||||
hi_ram [i] = (byte) data;
|
||||
if ( rom_enabled )
|
||||
ram() [i + rom_addr] = rom_ [i]; // restore overwritten ROM
|
||||
}
|
||||
// Wrapped around to beginning of RAM
|
||||
else
|
||||
{
|
||||
assert( ram() [i + rom_addr] == (byte) data );
|
||||
ram() [i + rom_addr] = padding_fill; // restore overwritten padding
|
||||
cpu_write( time, data, i + rom_addr - 0x10000 );
|
||||
}
|
||||
}
|
||||
|
||||
inline void Spc_Core::cpu_write_smp_reg( rel_time_t time, int data, int reg )
|
||||
{
|
||||
if ( reg == r_dspaddr ) // 64%
|
||||
regs [r_dspaddr] = data;
|
||||
|
||||
else if ( reg == r_dspdata ) // 35%
|
||||
{
|
||||
ram() [regs_addr + r_dspdata] = 0xFF; // needed for copy_state test to pass
|
||||
dsp_write( time, data );
|
||||
}
|
||||
|
||||
else // 1%
|
||||
cpu_write_smp_reg_( time, data, reg );
|
||||
}
|
||||
|
||||
void Spc_Core::cpu_write( rel_time_t time, int data, int addr )
|
||||
{
|
||||
// CPU can generate addresses beyond $FFFF, which need to wrap around
|
||||
|
||||
MEM_ACCESS( time, addr )
|
||||
|
||||
// RAM
|
||||
ram() [addr] = (byte) data;
|
||||
int reg = addr - regs_addr;
|
||||
if ( reg >= 0 ) // 64%
|
||||
{
|
||||
// $F0+
|
||||
|
||||
if ( reg < reg_count ) // 87%
|
||||
{
|
||||
// $F0-$FF
|
||||
cpu_write_smp_reg( time, data, reg );
|
||||
return;
|
||||
}
|
||||
|
||||
reg -= rom_addr - regs_addr;
|
||||
if ( reg >= 0 ) // 1%
|
||||
{
|
||||
// $FFC0+
|
||||
cpu_write_high( time, data, reg );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// CPU read
|
||||
|
||||
inline int Spc_Core::cpu_read_smp_reg( rel_time_t time, int reg )
|
||||
{
|
||||
int result = regs_in [reg];
|
||||
|
||||
reg -= r_dspaddr;
|
||||
if ( (unsigned) reg <= 1 ) // 4%
|
||||
{
|
||||
// dspaddr or dspdata
|
||||
result = regs [r_dspaddr];
|
||||
|
||||
if ( (unsigned) reg == 1 ) // dspdata
|
||||
result = dsp_read( time );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int Spc_Core::cpu_read( rel_time_t time, int addr )
|
||||
{
|
||||
// CPU can generate addresses beyond $FFFF, which need to wrap around
|
||||
|
||||
MEM_ACCESS( time, addr )
|
||||
|
||||
// RAM
|
||||
int result = ram() [addr];
|
||||
int reg = addr - regs_addr;
|
||||
if ( reg >= 0 ) // 40%
|
||||
{
|
||||
// $F0+
|
||||
|
||||
reg -= 0x10;
|
||||
if ( (unsigned) reg >= 0xFF00 ) // 21%
|
||||
{
|
||||
// $F0-$FF or $10000+
|
||||
|
||||
reg += 0x10 - r_t0out;
|
||||
|
||||
// Timers
|
||||
if ( (unsigned) reg < timer_count ) // 90%
|
||||
{
|
||||
// $FD-$FF
|
||||
result = read_timer( time, reg );
|
||||
}
|
||||
// Other registers
|
||||
else if ( reg < 0 ) // 10%
|
||||
{
|
||||
// $F0-$FC
|
||||
result = cpu_read_smp_reg( time, reg + r_t0out );
|
||||
}
|
||||
else // 1%
|
||||
{
|
||||
// $10000+
|
||||
assert( reg + (r_t0out + regs_addr - 0x10000) < 0x100 );
|
||||
result = cpu_read( time, reg + (r_t0out + regs_addr - 0x10000) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#define READ( delay, addr ) cpu_read ( TIME( delay ), addr )
|
||||
#define WRITE( delay, addr, data ) cpu_write( TIME( delay ), data, addr )
|
||||
|
||||
#if SPC_MORE_ACCURACY
|
||||
#define READ_TIMER( delay, addr, out )\
|
||||
{ out = READ( delay, addr ); }
|
||||
|
||||
#define WRITE_DP_FAST( offset, data ) \
|
||||
WRITE_DP( 0, offset, data )
|
||||
#else
|
||||
// timers are by far the most common thing read from dp
|
||||
#define READ_TIMER( delay, addr_, out )\
|
||||
{\
|
||||
rel_time_t time = TIME( delay );\
|
||||
int dp_addr = addr_;\
|
||||
int ti = dp_addr - (r_t0out + regs_addr);\
|
||||
if ( (unsigned) ti < timer_count )\
|
||||
{\
|
||||
out = read_timer( time, ti );\
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
out = ram [dp_addr];\
|
||||
int i = dp_addr - regs_addr;\
|
||||
if ( (unsigned) i < 0x10 )\
|
||||
out = cpu_read_smp_reg( time, i );\
|
||||
}\
|
||||
}
|
||||
|
||||
#define WRITE_DP_FAST( offset, data ) \
|
||||
{\
|
||||
int i = dp + offset;\
|
||||
ram [i] = (byte) data;\
|
||||
i -= regs_addr;\
|
||||
if ( (unsigned) i < 0x10 )\
|
||||
cpu_write_smp_reg( TIME( 0 ), data, i );\
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//// Run
|
||||
|
||||
void Spc_Core::end_frame( time_t end_time )
|
||||
{
|
||||
// Catch CPU up to as close to end as possible. If final instruction
|
||||
// would exceed end, does NOT execute it and leaves spc_time < end.
|
||||
if ( end_time > spc_time )
|
||||
run_until_( end_time );
|
||||
|
||||
spc_time -= end_time;
|
||||
|
||||
// Should have run at least to end_time, and at most 11 clocks over
|
||||
// (for DIV YA,X)
|
||||
assert( 0 <= spc_time && spc_time < 12 );
|
||||
|
||||
// Catch timers and DSP up to end_time
|
||||
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
run_timer( 0, i );
|
||||
|
||||
// This may still leave dsp_time < 0, in the case of the fast DSP, but
|
||||
// that's fine
|
||||
if ( dsp_time < 0 )
|
||||
{ RUN_DSP( 0, max_reg_time ); }
|
||||
}
|
||||
|
||||
// Macro to put prefix into same file as body of function, so that debugger
|
||||
// can properly step through
|
||||
#define SPC_CPU_RUN_FUNC \
|
||||
void Spc_Core::run_until_( time_t end_time )\
|
||||
{\
|
||||
rel_time_t rel_time = spc_time - end_time;\
|
||||
check( rel_time <= 0 );\
|
||||
\
|
||||
spc_time -= rel_time;\
|
||||
dsp_time += rel_time;\
|
||||
adjust_timers( rel_time );
|
||||
|
||||
#include "Spc_Cpu_run.h"
|
||||
|
||||
spc_time += rel_time;
|
||||
dsp_time -= rel_time;
|
||||
adjust_timers( -rel_time );
|
||||
check( spc_time >= end_time );
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,897 @@
|
|||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "Spc_Dsp.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2007-2010 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
// New SNES DSP behaves slightly differently (not all differences handled yet)
|
||||
bool const new_snes = false;
|
||||
|
||||
// if ( io < -32768 ) io = -32768;
|
||||
// if ( io > 32767 ) io = 32767;
|
||||
#define CLAMP16( io )\
|
||||
{\
|
||||
if ( (int16_t) io != io )\
|
||||
io = (io >> 31) ^ 0x7FFF;\
|
||||
}
|
||||
|
||||
// Access global DSP register
|
||||
#define REG(n) regs [r_##n]
|
||||
|
||||
// Access voice DSP register
|
||||
#define VREG(r,n) r [v_##n]
|
||||
|
||||
#ifndef SPC_DSP_OUT_HOOK
|
||||
#define SPC_DSP_OUT_HOOK( l, r ) \
|
||||
write_sample( l, r )
|
||||
#endif
|
||||
|
||||
inline void Spc_Dsp::set_null_output()
|
||||
{
|
||||
output_ptr_ = dummy_buf;
|
||||
output_end = dummy_buf + 2;
|
||||
}
|
||||
|
||||
void Spc_Dsp::set_output( sample_t* begin, sample_t* end )
|
||||
{
|
||||
output_begin = begin;
|
||||
|
||||
if ( begin == NULL )
|
||||
{
|
||||
user_output_end = NULL;
|
||||
set_null_output();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Size must be even
|
||||
assert( (end - begin) % 2 == 0 );
|
||||
|
||||
output_ptr_ = begin;
|
||||
output_end = end;
|
||||
user_output_end = end;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Spc_Dsp::write_sample( int l, int r )
|
||||
{
|
||||
sample_t* out = output_ptr_;
|
||||
out [0] = l;
|
||||
out [1] = r;
|
||||
out += 2;
|
||||
output_ptr_ = out;
|
||||
|
||||
if ( out >= output_end )
|
||||
{
|
||||
set_null_output();
|
||||
if ( out != dummy_buf + 2 )
|
||||
{
|
||||
if ( set_output_callback.f )
|
||||
set_output_callback.f( set_output_callback.data );
|
||||
else
|
||||
dprintf( "DSP buffer overflowed\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volume registers and efb are signed! Easy to forget int8_t cast.
|
||||
// Prefixes are to avoid accidental use of locals with same names.
|
||||
|
||||
// Gaussian interpolation
|
||||
|
||||
static short const gauss [512] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
|
||||
2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5,
|
||||
6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
|
||||
11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17,
|
||||
18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27,
|
||||
28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||
58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77,
|
||||
78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102,
|
||||
104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132,
|
||||
134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168,
|
||||
171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210,
|
||||
212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257,
|
||||
260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311,
|
||||
314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370,
|
||||
374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434,
|
||||
439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504,
|
||||
508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577,
|
||||
582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654,
|
||||
659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732,
|
||||
737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811,
|
||||
816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889,
|
||||
894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965,
|
||||
969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036,
|
||||
1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102,
|
||||
1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160,
|
||||
1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210,
|
||||
1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251,
|
||||
1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280,
|
||||
1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298,
|
||||
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
|
||||
};
|
||||
|
||||
inline int Spc_Dsp::interpolate( voice_t const* v )
|
||||
{
|
||||
// Make pointers into gaussian based on fractional position between samples
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = (fwd [ 0] * in [0]) >> 11;
|
||||
out += (fwd [256] * in [1]) >> 11;
|
||||
out += (rev [256] * in [2]) >> 11;
|
||||
out = (int16_t) out;
|
||||
out += (rev [ 0] * in [3]) >> 11;
|
||||
|
||||
CLAMP16( out );
|
||||
out &= ~1;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
//// Counters
|
||||
|
||||
int const simple_counter_range = 2048 * 5 * 3; // 30720
|
||||
|
||||
static unsigned const counter_rates [32] =
|
||||
{
|
||||
simple_counter_range + 1, // never fires
|
||||
2048, 1536,
|
||||
1280, 1024, 768,
|
||||
640, 512, 384,
|
||||
320, 256, 192,
|
||||
160, 128, 96,
|
||||
80, 64, 48,
|
||||
40, 32, 24,
|
||||
20, 16, 12,
|
||||
10, 8, 6,
|
||||
5, 4, 3,
|
||||
2,
|
||||
1
|
||||
};
|
||||
static unsigned const counter_offsets [32] =
|
||||
{
|
||||
1, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
inline void Spc_Dsp::init_counter()
|
||||
{
|
||||
m.counter = 0;
|
||||
}
|
||||
|
||||
inline void Spc_Dsp::run_counters()
|
||||
{
|
||||
if ( --m.counter < 0 )
|
||||
m.counter = simple_counter_range - 1;
|
||||
}
|
||||
|
||||
inline unsigned Spc_Dsp::read_counter( int rate )
|
||||
{
|
||||
return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate];
|
||||
}
|
||||
|
||||
|
||||
//// Envelope
|
||||
|
||||
inline void Spc_Dsp::run_envelope( voice_t* const v )
|
||||
{
|
||||
int env = v->env;
|
||||
if ( v->env_mode == env_release ) // 60%
|
||||
{
|
||||
if ( (env -= 0x8) < 0 )
|
||||
env = 0;
|
||||
v->env = env;
|
||||
}
|
||||
else
|
||||
{
|
||||
int rate;
|
||||
int env_data = VREG(v->regs,adsr1);
|
||||
if ( m.t_adsr0 & 0x80 ) // 99% ADSR
|
||||
{
|
||||
if ( v->env_mode >= env_decay ) // 99%
|
||||
{
|
||||
env--;
|
||||
env -= env >> 8;
|
||||
rate = env_data & 0x1F;
|
||||
if ( v->env_mode == env_decay ) // 1%
|
||||
rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10;
|
||||
}
|
||||
else // env_attack
|
||||
{
|
||||
rate = (m.t_adsr0 & 0x0F) * 2 + 1;
|
||||
env += rate < 31 ? 0x20 : 0x400;
|
||||
}
|
||||
}
|
||||
else // GAIN
|
||||
{
|
||||
int mode;
|
||||
env_data = VREG(v->regs,gain);
|
||||
mode = env_data >> 5;
|
||||
if ( mode < 4 ) // direct
|
||||
{
|
||||
env = env_data * 0x10;
|
||||
rate = 31;
|
||||
}
|
||||
else
|
||||
{
|
||||
rate = env_data & 0x1F;
|
||||
if ( mode == 4 ) // 4: linear decrease
|
||||
{
|
||||
env -= 0x20;
|
||||
}
|
||||
else if ( mode < 6 ) // 5: exponential decrease
|
||||
{
|
||||
env--;
|
||||
env -= env >> 8;
|
||||
}
|
||||
else // 6,7: linear increase
|
||||
{
|
||||
env += 0x20;
|
||||
if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )
|
||||
env += 0x8 - 0x20; // 7: two-slope linear increase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sustain level
|
||||
if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )
|
||||
v->env_mode = env_sustain;
|
||||
|
||||
v->hidden_env = env;
|
||||
|
||||
// unsigned cast because linear decrease going negative also triggers this
|
||||
if ( (unsigned) env > 0x7FF )
|
||||
{
|
||||
env = (env < 0 ? 0 : 0x7FF);
|
||||
if ( v->env_mode == env_attack )
|
||||
v->env_mode = env_decay;
|
||||
}
|
||||
|
||||
if ( !read_counter( rate ) )
|
||||
v->env = env; // nothing else is controlled by the counter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// BRR Decoding
|
||||
|
||||
inline void Spc_Dsp::decode_brr( voice_t* v )
|
||||
{
|
||||
// Arrange the four input nybbles in 0xABCD order for easy decoding
|
||||
int nybbles = m.t_brr_byte * 0x100 + ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];
|
||||
|
||||
int const header = m.t_brr_header;
|
||||
|
||||
// Write to next four samples in circular buffer
|
||||
int* pos = &v->buf [v->buf_pos];
|
||||
if ( (v->buf_pos += 4) >= brr_buf_size )
|
||||
v->buf_pos = 0;
|
||||
|
||||
// Decode four samples
|
||||
for ( int* end = pos + 4; pos < end; pos++ )
|
||||
{
|
||||
// Extract nybble and sign-extend
|
||||
int s = (int16_t) nybbles >> 12;
|
||||
nybbles <<= 4;
|
||||
|
||||
// Shift sample based on header
|
||||
int const shift = header >> 4;
|
||||
s = (s << shift) >> 1;
|
||||
if ( shift >= 0xD ) // handle invalid range
|
||||
s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0)
|
||||
|
||||
// Apply (unstable) IIR filter (8 is the most commonly used)
|
||||
int const filter = header & 0x0C;
|
||||
int const p1 = pos [brr_buf_size - 1];
|
||||
int const p2 = pos [brr_buf_size - 2] >> 1;
|
||||
if ( filter >= 8 ) // most common one
|
||||
{
|
||||
s += p1;
|
||||
s -= p2;
|
||||
if ( filter == 8 ) // pos[0] = s*2 + pos[-1] * 1.09625 - pos[-2] * 0.9375
|
||||
{
|
||||
s += p2 >> 4;
|
||||
s += (p1 * -3) >> 6;
|
||||
}
|
||||
else // pos[0] = s*2 + pos[-1] * 1.796875 - pos[-2] * 0.8125
|
||||
{
|
||||
s += (p1 * -13) >> 7;
|
||||
s += (p2 * 3) >> 4;
|
||||
}
|
||||
}
|
||||
else if ( filter ) // pos[0] = s*2 + pos[-1] * 0.9375
|
||||
{
|
||||
s += p1 >> 1;
|
||||
s += (-p1) >> 5;
|
||||
}
|
||||
|
||||
// Adjust and write sample
|
||||
CLAMP16( s );
|
||||
s = (int16_t) (s * 2);
|
||||
pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Misc
|
||||
|
||||
#define MISC_CLOCK( n ) inline void Spc_Dsp::misc_##n()
|
||||
|
||||
MISC_CLOCK( 27 )
|
||||
{
|
||||
m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON
|
||||
}
|
||||
MISC_CLOCK( 28 )
|
||||
{
|
||||
m.t_non = REG(non);
|
||||
m.t_eon = REG(eon);
|
||||
m.t_dir = REG(dir);
|
||||
}
|
||||
MISC_CLOCK( 29 )
|
||||
{
|
||||
if ( (m.every_other_sample ^= 1) != 0 )
|
||||
m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read
|
||||
}
|
||||
MISC_CLOCK( 30 )
|
||||
{
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
m.kon = m.new_kon;
|
||||
m.t_koff = REG(koff) | mute_mask;
|
||||
}
|
||||
|
||||
run_counters();
|
||||
|
||||
// Noise
|
||||
if ( !read_counter( REG(flg) & 0x1F ) )
|
||||
{
|
||||
int feedback = (m.noise << 13) ^ (m.noise << 14);
|
||||
m.noise = (feedback & 0x4000) ^ (m.noise >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Voices
|
||||
|
||||
#define VOICE_CLOCK( n ) void Spc_Dsp::voice_##n( voice_t* const v )
|
||||
|
||||
inline VOICE_CLOCK( V1 )
|
||||
{
|
||||
m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4;
|
||||
m.t_srcn = VREG(v->regs,srcn);
|
||||
}
|
||||
inline VOICE_CLOCK( V2 )
|
||||
{
|
||||
// Read sample pointer (ignored if not needed)
|
||||
byte const* entry = &ram [m.t_dir_addr];
|
||||
if ( !v->kon_delay )
|
||||
entry += 2;
|
||||
m.t_brr_next_addr = GET_LE16A( entry );
|
||||
|
||||
m.t_adsr0 = VREG(v->regs,adsr0);
|
||||
|
||||
// Read pitch, spread over two clocks
|
||||
m.t_pitch = VREG(v->regs,pitchl);
|
||||
}
|
||||
inline VOICE_CLOCK( V3a )
|
||||
{
|
||||
m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8;
|
||||
}
|
||||
inline VOICE_CLOCK( V3b )
|
||||
{
|
||||
// Read BRR header and byte
|
||||
m.t_brr_byte = ram [(v->brr_addr + v->brr_offset) & 0xFFFF];
|
||||
m.t_brr_header = ram [v->brr_addr]; // brr_addr doesn't need masking
|
||||
}
|
||||
VOICE_CLOCK( V3c )
|
||||
{
|
||||
// Pitch modulation using previous voice's output
|
||||
if ( m.t_pmon & v->vbit )
|
||||
m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10;
|
||||
|
||||
if ( v->kon_delay )
|
||||
{
|
||||
// Get ready to start BRR decoding on next sample
|
||||
if ( v->kon_delay == 5 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
v->brr_offset = 1;
|
||||
v->buf_pos = 0;
|
||||
m.t_brr_header = 0; // header is ignored on this sample
|
||||
kon_check = true;
|
||||
}
|
||||
|
||||
// Envelope is never run during KON
|
||||
v->env = 0;
|
||||
v->hidden_env = 0;
|
||||
|
||||
// Disable BRR decoding until last three samples
|
||||
v->interp_pos = 0;
|
||||
if ( --v->kon_delay & 3 )
|
||||
v->interp_pos = 0x4000;
|
||||
|
||||
// Pitch is never added during KON
|
||||
m.t_pitch = 0;
|
||||
}
|
||||
|
||||
// Gaussian interpolation
|
||||
{
|
||||
int output = interpolate( v );
|
||||
|
||||
// Noise
|
||||
if ( m.t_non & v->vbit )
|
||||
output = (int16_t) (m.noise * 2);
|
||||
|
||||
// Apply envelope
|
||||
m.t_output = (output * v->env) >> 11 & ~1;
|
||||
v->t_envx_out = (byte) (v->env >> 4);
|
||||
}
|
||||
|
||||
// Immediate silence due to end of sample or soft reset
|
||||
if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 )
|
||||
{
|
||||
v->env_mode = env_release;
|
||||
v->env = 0;
|
||||
}
|
||||
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
// KOFF
|
||||
if ( m.t_koff & v->vbit && (!new_snes || v->kon_delay < 3) )
|
||||
v->env_mode = env_release;
|
||||
|
||||
// KON
|
||||
if ( m.kon & v->vbit )
|
||||
{
|
||||
v->kon_delay = 5;
|
||||
v->env_mode = env_attack;
|
||||
}
|
||||
}
|
||||
|
||||
// Run envelope for next sample
|
||||
if ( !v->kon_delay )
|
||||
run_envelope( v );
|
||||
}
|
||||
inline void Spc_Dsp::voice_output( voice_t const* v, int ch )
|
||||
{
|
||||
// Apply left/right volume
|
||||
int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7;
|
||||
|
||||
// Avoid negative volume if surround is disabled
|
||||
// (emulator feature; not part of actual DSP)
|
||||
if ( (int8_t) VREG(v->regs,voll + ch) < surround_threshold )
|
||||
amp = -amp;
|
||||
|
||||
// Add to output total
|
||||
m.t_main_out [ch] += amp;
|
||||
CLAMP16( m.t_main_out [ch] );
|
||||
|
||||
// Optionally add to echo total
|
||||
if ( m.t_eon & v->vbit )
|
||||
{
|
||||
m.t_echo_out [ch] += amp;
|
||||
CLAMP16( m.t_echo_out [ch] );
|
||||
}
|
||||
}
|
||||
VOICE_CLOCK( V4 )
|
||||
{
|
||||
// Decode BRR
|
||||
m.t_looped = 0;
|
||||
if ( v->interp_pos >= 0x4000 )
|
||||
{
|
||||
decode_brr( v );
|
||||
|
||||
if ( (v->brr_offset += 2) >= brr_block_size )
|
||||
{
|
||||
// Start decoding next BRR block
|
||||
assert( v->brr_offset == brr_block_size );
|
||||
v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;
|
||||
if ( m.t_brr_header & 1 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
m.t_looped = v->vbit;
|
||||
}
|
||||
v->brr_offset = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pitch
|
||||
v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch;
|
||||
|
||||
// Keep from getting too far ahead (when using pitch modulation)
|
||||
if ( v->interp_pos > 0x7FFF )
|
||||
v->interp_pos = 0x7FFF;
|
||||
|
||||
// Output left
|
||||
voice_output( v, 0 );
|
||||
}
|
||||
inline VOICE_CLOCK( V5 )
|
||||
{
|
||||
// Output right
|
||||
voice_output( v, 1 );
|
||||
|
||||
// ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier
|
||||
int endx_buf = REG(endx) | m.t_looped;
|
||||
|
||||
// Clear bit in ENDX if KON just began
|
||||
if ( v->kon_delay == 5 )
|
||||
endx_buf &= ~v->vbit;
|
||||
m.endx_buf = (byte) endx_buf;
|
||||
}
|
||||
inline VOICE_CLOCK( V6 )
|
||||
{
|
||||
(void) v; // avoid compiler warning about unused v
|
||||
m.outx_buf = (byte) (m.t_output >> 8);
|
||||
}
|
||||
inline VOICE_CLOCK( V7 )
|
||||
{
|
||||
// Update ENDX
|
||||
REG(endx) = m.endx_buf;
|
||||
|
||||
m.envx_buf = v->t_envx_out;
|
||||
}
|
||||
inline VOICE_CLOCK( V8 )
|
||||
{
|
||||
// Update OUTX
|
||||
VREG(v->regs,outx) = m.outx_buf;
|
||||
}
|
||||
inline VOICE_CLOCK( V9 )
|
||||
{
|
||||
// Update ENVX
|
||||
VREG(v->regs,envx) = m.envx_buf;
|
||||
}
|
||||
|
||||
// Most voices do all these in one clock, so make a handy composite
|
||||
inline VOICE_CLOCK( V3 )
|
||||
{
|
||||
voice_V3a( v );
|
||||
voice_V3b( v );
|
||||
voice_V3c( v );
|
||||
}
|
||||
|
||||
// Common combinations of voice steps on different voices. This greatly reduces
|
||||
// code size and allows everything to be inlined in these functions.
|
||||
VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); }
|
||||
VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); }
|
||||
VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }
|
||||
|
||||
|
||||
//// Echo
|
||||
|
||||
// Current echo buffer pointer for left/right channel
|
||||
#define ECHO_PTR( ch ) (&ram [m.t_echo_ptr + ch * 2])
|
||||
|
||||
// Sample in echo history buffer, where 0 is the oldest
|
||||
#define ECHO_FIR( i ) (m.echo_hist_pos [i])
|
||||
|
||||
// Calculate FIR point for left/right channel
|
||||
#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6)
|
||||
|
||||
#define ECHO_CLOCK( n ) inline void Spc_Dsp::echo_##n()
|
||||
|
||||
inline void Spc_Dsp::echo_read( int ch )
|
||||
{
|
||||
int s = GET_LE16SA( ECHO_PTR( ch ) );
|
||||
// second copy simplifies wrap-around handling
|
||||
ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1;
|
||||
}
|
||||
|
||||
ECHO_CLOCK( 22 )
|
||||
{
|
||||
// History
|
||||
if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] )
|
||||
m.echo_hist_pos = m.echo_hist;
|
||||
|
||||
m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF;
|
||||
echo_read( 0 );
|
||||
|
||||
// FIR (using l and r temporaries below helps compiler optimize)
|
||||
int l = CALC_FIR( 0, 0 );
|
||||
int r = CALC_FIR( 0, 1 );
|
||||
|
||||
m.t_echo_in [0] = l;
|
||||
m.t_echo_in [1] = r;
|
||||
}
|
||||
ECHO_CLOCK( 23 )
|
||||
{
|
||||
int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 );
|
||||
int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 );
|
||||
|
||||
m.t_echo_in [0] += l;
|
||||
m.t_echo_in [1] += r;
|
||||
|
||||
echo_read( 1 );
|
||||
}
|
||||
ECHO_CLOCK( 24 )
|
||||
{
|
||||
int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 );
|
||||
int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 );
|
||||
|
||||
m.t_echo_in [0] += l;
|
||||
m.t_echo_in [1] += r;
|
||||
}
|
||||
ECHO_CLOCK( 25 )
|
||||
{
|
||||
int l = m.t_echo_in [0] + CALC_FIR( 6, 0 );
|
||||
int r = m.t_echo_in [1] + CALC_FIR( 6, 1 );
|
||||
|
||||
l = (int16_t) l;
|
||||
r = (int16_t) r;
|
||||
|
||||
l += (int16_t) CALC_FIR( 7, 0 );
|
||||
r += (int16_t) CALC_FIR( 7, 1 );
|
||||
|
||||
CLAMP16( l );
|
||||
CLAMP16( r );
|
||||
|
||||
m.t_echo_in [0] = l & ~1;
|
||||
m.t_echo_in [1] = r & ~1;
|
||||
}
|
||||
inline int Spc_Dsp::echo_output( int ch )
|
||||
{
|
||||
int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) +
|
||||
(int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7);
|
||||
CLAMP16( out );
|
||||
return out;
|
||||
}
|
||||
ECHO_CLOCK( 26 )
|
||||
{
|
||||
// Surround disabler (emulator feature; not part of actual DSP)
|
||||
if ( (int8_t) REG(mvoll) * (int8_t) REG(mvolr) < surround_threshold )
|
||||
m.t_main_out [0] = -m.t_main_out [0]; // eliminate surround
|
||||
|
||||
// Left output volumes
|
||||
// (save sample for next clock so we can output both together)
|
||||
m.t_main_out [0] = echo_output( 0 );
|
||||
|
||||
// Echo feedback
|
||||
int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7);
|
||||
int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7);
|
||||
|
||||
CLAMP16( l );
|
||||
CLAMP16( r );
|
||||
|
||||
m.t_echo_out [0] = l & ~1;
|
||||
m.t_echo_out [1] = r & ~1;
|
||||
}
|
||||
ECHO_CLOCK( 27 )
|
||||
{
|
||||
// Output
|
||||
int l = m.t_main_out [0];
|
||||
int r = echo_output( 1 );
|
||||
m.t_main_out [0] = 0;
|
||||
m.t_main_out [1] = 0;
|
||||
|
||||
// TODO: global muting isn't this simple (turns DAC on and off
|
||||
// or something, causing small ~37-sample pulse when first muted)
|
||||
if ( REG(flg) & 0x40 )
|
||||
{
|
||||
l = 0;
|
||||
r = 0;
|
||||
}
|
||||
|
||||
// Output sample to DAC
|
||||
SPC_DSP_OUT_HOOK( l, r );
|
||||
}
|
||||
ECHO_CLOCK( 28 )
|
||||
{
|
||||
m.t_echo_enabled = REG(flg);
|
||||
}
|
||||
inline void Spc_Dsp::echo_write( int ch )
|
||||
{
|
||||
if ( !(m.t_echo_enabled & 0x20) )
|
||||
{
|
||||
#ifdef SPC_DSP_ECHO_DEBUG
|
||||
SPC_DSP_ECHO_DEBUG
|
||||
#endif
|
||||
SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] );
|
||||
}
|
||||
m.t_echo_out [ch] = 0;
|
||||
}
|
||||
ECHO_CLOCK( 29 )
|
||||
{
|
||||
m.t_esa = REG(esa);
|
||||
|
||||
if ( !m.echo_offset )
|
||||
m.echo_length = (REG(edl) & 0x0F) * 0x800;
|
||||
|
||||
m.echo_offset += 4;
|
||||
if ( m.echo_offset >= m.echo_length )
|
||||
m.echo_offset = 0;
|
||||
|
||||
// Write left echo
|
||||
echo_write( 0 );
|
||||
|
||||
m.t_echo_enabled = REG(flg);
|
||||
}
|
||||
ECHO_CLOCK( 30 )
|
||||
{
|
||||
// Write right echo
|
||||
echo_write( 1 );
|
||||
}
|
||||
|
||||
|
||||
//// Timing
|
||||
|
||||
// Execute clock for a particular voice
|
||||
#define V( clock, voice ) voice_##clock( &m.voices [voice] );
|
||||
|
||||
/* The most common sequence of clocks uses composite operations
|
||||
for efficiency. For example, the following are equivalent to the
|
||||
individual steps on the right:
|
||||
|
||||
V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5)
|
||||
V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4)
|
||||
V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */
|
||||
|
||||
// Voice 0 1 2 3 4 5 6 7
|
||||
#define GEN_DSP_TIMING \
|
||||
PHASE( 0) V(V5,0)V(V2,1)\
|
||||
PHASE( 1) V(V6,0)V(V3,1)\
|
||||
PHASE( 2) V(V7_V4_V1,0)\
|
||||
PHASE( 3) V(V8_V5_V2,0)\
|
||||
PHASE( 4) V(V9_V6_V3,0)\
|
||||
PHASE( 5) V(V7_V4_V1,1)\
|
||||
PHASE( 6) V(V8_V5_V2,1)\
|
||||
PHASE( 7) V(V9_V6_V3,1)\
|
||||
PHASE( 8) V(V7_V4_V1,2)\
|
||||
PHASE( 9) V(V8_V5_V2,2)\
|
||||
PHASE(10) V(V9_V6_V3,2)\
|
||||
PHASE(11) V(V7_V4_V1,3)\
|
||||
PHASE(12) V(V8_V5_V2,3)\
|
||||
PHASE(13) V(V9_V6_V3,3)\
|
||||
PHASE(14) V(V7_V4_V1,4)\
|
||||
PHASE(15) V(V8_V5_V2,4)\
|
||||
PHASE(16) V(V9_V6_V3,4)\
|
||||
PHASE(17) V(V1,0) V(V7,5)V(V4,6)\
|
||||
PHASE(18) V(V8_V5_V2,5)\
|
||||
PHASE(19) V(V9_V6_V3,5)\
|
||||
PHASE(20) V(V1,1) V(V7,6)V(V4,7)\
|
||||
PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\
|
||||
PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\
|
||||
PHASE(23) V(V7,7) echo_23();\
|
||||
PHASE(24) V(V8,7) echo_24();\
|
||||
PHASE(25) V(V3b,0) V(V9,7) echo_25();\
|
||||
PHASE(26) echo_26();\
|
||||
PHASE(27) misc_27(); echo_27();\
|
||||
PHASE(28) misc_28(); echo_28();\
|
||||
PHASE(29) misc_29(); echo_29();\
|
||||
PHASE(30) misc_30();V(V3c,0) echo_30();\
|
||||
PHASE(31) V(V4,0) V(V1,2)\
|
||||
|
||||
#if !SPC_DSP_CUSTOM_RUN
|
||||
|
||||
void Spc_Dsp::run( int clocks_remain )
|
||||
{
|
||||
require( clocks_remain > 0 );
|
||||
|
||||
int const phase = m.phase;
|
||||
m.phase = (phase + clocks_remain) & 31;
|
||||
switch ( phase )
|
||||
{
|
||||
loop:
|
||||
|
||||
#define PHASE( n ) if ( n && !--clocks_remain ) break; case n:
|
||||
GEN_DSP_TIMING
|
||||
#undef PHASE
|
||||
|
||||
if ( --clocks_remain )
|
||||
goto loop;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
//// Setup
|
||||
|
||||
void Spc_Dsp::init( void* ram_64k )
|
||||
{
|
||||
ram = (byte*) ram_64k;
|
||||
disable_surround( false );
|
||||
mute_voices( 0 );
|
||||
set_output( NULL, 0 );
|
||||
reset();
|
||||
|
||||
#ifndef NDEBUG
|
||||
// be sure this sign-extends
|
||||
assert( (int16_t) 0x8000 == -0x8000 );
|
||||
|
||||
// be sure right shift preserves sign
|
||||
assert( (-1 >> 1) == -1 );
|
||||
|
||||
// check clamp macro
|
||||
int i;
|
||||
i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF );
|
||||
i = -0x8001; CLAMP16( i ); assert( i == -0x8000 );
|
||||
|
||||
blargg_verify_byte_order();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Spc_Dsp::soft_reset()
|
||||
{
|
||||
require( ram ); // init() must have been called already
|
||||
|
||||
REG(flg) = 0xE0;
|
||||
|
||||
m.noise = 0x2000;
|
||||
m.echo_hist_pos = m.echo_hist;
|
||||
m.every_other_sample = 1;
|
||||
m.echo_offset = 0;
|
||||
m.phase = 0;
|
||||
|
||||
init_counter();
|
||||
|
||||
kon_check = false;
|
||||
}
|
||||
|
||||
void Spc_Dsp::load( byte const new_regs [register_count] )
|
||||
{
|
||||
memcpy( regs, new_regs, register_count );
|
||||
BLARGG_CLEAR( &m );
|
||||
|
||||
for ( int i = voice_count; --i >= 0; )
|
||||
{
|
||||
voice_t* v = &m.voices [i];
|
||||
v->brr_offset = 1;
|
||||
v->vbit = 1 << i;
|
||||
v->regs = ®s [i * 0x10];
|
||||
}
|
||||
m.new_kon = REG(kon);
|
||||
m.t_dir = REG(dir);
|
||||
m.t_esa = REG(esa);
|
||||
|
||||
soft_reset();
|
||||
REG(flg) = new_regs [r_flg]; // soft_reset() overwrites this
|
||||
}
|
||||
|
||||
void Spc_Dsp::reset()
|
||||
{
|
||||
static byte const initial_regs [register_count] = {
|
||||
0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80,
|
||||
0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF,
|
||||
0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A,
|
||||
0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF,
|
||||
0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67,
|
||||
0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF,
|
||||
0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F,
|
||||
0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF
|
||||
};
|
||||
|
||||
load( initial_regs );
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
// Highly accurate SNES SPC-700 DSP emulator
|
||||
|
||||
// snes_spc 0.9.5
|
||||
#ifndef BLARGG_SPC_DSP_H
|
||||
#define BLARGG_SPC_DSP_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); }
|
||||
|
||||
struct Spc_Dsp {
|
||||
public:
|
||||
typedef BOOST::uint8_t byte;
|
||||
|
||||
// Setup
|
||||
|
||||
// Initializes DSP and has it use the 64K RAM provided
|
||||
void init( void* ram_64k );
|
||||
|
||||
// Sets destination for output samples. If begin is NULL, doesn't generate any.
|
||||
typedef short sample_t;
|
||||
void set_output( sample_t* begin, sample_t* end );
|
||||
|
||||
// Current position in output buffer, or NULL if no buffer set
|
||||
sample_t* output_ptr() const;
|
||||
|
||||
// Sets function that is called when output buffer is filled, or NULL for none
|
||||
blargg_callback<void (*)( void* user_data )> set_output_callback;
|
||||
//void set_output_callback( void (*func)( void* user_data ), void* user_data );
|
||||
|
||||
// Emulation
|
||||
|
||||
// Resets DSP to power-on state
|
||||
void reset();
|
||||
|
||||
// Emulates pressing reset switch on SNES
|
||||
void soft_reset();
|
||||
|
||||
// Reads/writes DSP registers. For accuracy, you must first call run()
|
||||
// to catch the DSP up to present.
|
||||
int read ( int addr ) const;
|
||||
void write( int addr, int data );
|
||||
|
||||
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
|
||||
// a pair of samples is be generated.
|
||||
void run( int clock_count );
|
||||
|
||||
// Sound control
|
||||
|
||||
// Using these reduces emulation accuracy.
|
||||
|
||||
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
|
||||
enum { voice_count = 8 };
|
||||
void mute_voices( int mask ) { mute_mask = mask; }
|
||||
|
||||
// If true, prevents channels and global volumes from being phase-negated
|
||||
void disable_surround( bool disable = true );
|
||||
|
||||
// State
|
||||
|
||||
// Resets DSP and uses supplied values to initialize registers
|
||||
enum { register_count = 128 };
|
||||
void load( byte const regs [register_count] );
|
||||
|
||||
// Saves/loads exact emulator state
|
||||
enum { state_size = 640 }; // maximum space needed when saving
|
||||
typedef dsp_copy_func_t copy_func_t;
|
||||
void copy_state( unsigned char** io, copy_func_t );
|
||||
|
||||
// Returns non-zero if new key-on events occurred since last call
|
||||
bool check_kon();
|
||||
|
||||
// DSP register addresses
|
||||
|
||||
// Global registers
|
||||
enum {
|
||||
r_mvoll = 0x0C, r_mvolr = 0x1C,
|
||||
r_evoll = 0x2C, r_evolr = 0x3C,
|
||||
r_kon = 0x4C, r_koff = 0x5C,
|
||||
r_flg = 0x6C, r_endx = 0x7C,
|
||||
r_efb = 0x0D, r_pmon = 0x2D,
|
||||
r_non = 0x3D, r_eon = 0x4D,
|
||||
r_dir = 0x5D, r_esa = 0x6D,
|
||||
r_edl = 0x7D,
|
||||
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
|
||||
};
|
||||
|
||||
// Voice registers
|
||||
enum {
|
||||
v_voll = 0x00, v_volr = 0x01,
|
||||
v_pitchl = 0x02, v_pitchh = 0x03,
|
||||
v_srcn = 0x04, v_adsr0 = 0x05,
|
||||
v_adsr1 = 0x06, v_gain = 0x07,
|
||||
v_envx = 0x08, v_outx = 0x09
|
||||
};
|
||||
|
||||
public:
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
typedef BOOST::int8_t int8_t;
|
||||
typedef BOOST::int16_t int16_t;
|
||||
|
||||
enum { echo_hist_size = 8 };
|
||||
|
||||
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
|
||||
enum { brr_buf_size = 12 };
|
||||
struct voice_t
|
||||
{
|
||||
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
|
||||
int buf_pos; // place in buffer where next samples will be decoded
|
||||
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
|
||||
int brr_addr; // address of current BRR block
|
||||
int brr_offset; // current decoding offset in BRR block
|
||||
byte* regs; // pointer to voice's DSP registers
|
||||
int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc.
|
||||
int kon_delay; // KON delay/current setup phase
|
||||
env_mode_t env_mode;
|
||||
int env; // current envelope level
|
||||
int hidden_env; // used by GAIN mode 7, very obscure quirk
|
||||
byte t_envx_out;
|
||||
};
|
||||
private:
|
||||
enum { brr_block_size = 9 };
|
||||
|
||||
// non-emulation state
|
||||
byte* ram; // 64K shared RAM between DSP and SMP
|
||||
int mute_mask;
|
||||
int surround_threshold;
|
||||
sample_t* output_begin;
|
||||
sample_t* output_ptr_;
|
||||
sample_t* output_end;
|
||||
sample_t* user_output_end;
|
||||
sample_t dummy_buf [2];
|
||||
bool kon_check; // set when a new KON occurs
|
||||
|
||||
struct state_t
|
||||
{
|
||||
int every_other_sample; // toggles every sample
|
||||
int kon; // KON value when last checked
|
||||
int noise;
|
||||
int counter;
|
||||
int echo_offset; // offset from ESA in echo buffer
|
||||
int echo_length; // number of bytes that echo_offset will stop at
|
||||
int phase; // next clock cycle to run (0-31)
|
||||
|
||||
// Hidden registers also written to when main register is written to
|
||||
int new_kon;
|
||||
byte endx_buf;
|
||||
byte envx_buf;
|
||||
byte outx_buf;
|
||||
|
||||
// Temporary state between clocks
|
||||
|
||||
// read once per sample
|
||||
int t_pmon;
|
||||
int t_non;
|
||||
int t_eon;
|
||||
int t_dir;
|
||||
int t_koff;
|
||||
|
||||
// read a few clocks ahead then used
|
||||
int t_brr_next_addr;
|
||||
int t_adsr0;
|
||||
int t_brr_header;
|
||||
int t_brr_byte;
|
||||
int t_srcn;
|
||||
int t_esa;
|
||||
int t_echo_enabled;
|
||||
|
||||
// internal state that is recalculated every sample
|
||||
int t_dir_addr;
|
||||
int t_pitch;
|
||||
int t_output;
|
||||
int t_looped;
|
||||
int t_echo_ptr;
|
||||
|
||||
// left/right sums
|
||||
int t_main_out [2];
|
||||
int t_echo_out [2];
|
||||
int t_echo_in [2];
|
||||
|
||||
voice_t voices [voice_count];
|
||||
|
||||
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
|
||||
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
|
||||
int echo_hist [echo_hist_size * 2] [2];
|
||||
};
|
||||
state_t m;
|
||||
|
||||
byte regs [register_count];
|
||||
|
||||
void init_counter();
|
||||
void run_counters();
|
||||
unsigned read_counter( int rate );
|
||||
|
||||
int interpolate( voice_t const* v );
|
||||
void run_envelope( voice_t* const v );
|
||||
void decode_brr( voice_t* v );
|
||||
|
||||
void misc_27();
|
||||
void misc_28();
|
||||
void misc_29();
|
||||
void misc_30();
|
||||
|
||||
void voice_output( voice_t const* v, int ch );
|
||||
void voice_V1( voice_t* const );
|
||||
void voice_V2( voice_t* const );
|
||||
void voice_V3( voice_t* const );
|
||||
void voice_V3a( voice_t* const );
|
||||
void voice_V3b( voice_t* const );
|
||||
void voice_V3c( voice_t* const );
|
||||
void voice_V4( voice_t* const );
|
||||
void voice_V5( voice_t* const );
|
||||
void voice_V6( voice_t* const );
|
||||
void voice_V7( voice_t* const );
|
||||
void voice_V8( voice_t* const );
|
||||
void voice_V9( voice_t* const );
|
||||
void voice_V7_V4_V1( voice_t* const );
|
||||
void voice_V8_V5_V2( voice_t* const );
|
||||
void voice_V9_V6_V3( voice_t* const );
|
||||
|
||||
void echo_read( int ch );
|
||||
int echo_output( int ch );
|
||||
void echo_write( int ch );
|
||||
void echo_22();
|
||||
void echo_23();
|
||||
void echo_24();
|
||||
void echo_25();
|
||||
void echo_26();
|
||||
void echo_27();
|
||||
void echo_28();
|
||||
void echo_29();
|
||||
void echo_30();
|
||||
|
||||
void set_null_output();
|
||||
void write_sample( int l, int r );
|
||||
};
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
inline int Spc_Dsp::read( int addr ) const
|
||||
{
|
||||
assert( (unsigned) addr < register_count );
|
||||
|
||||
return regs [addr];
|
||||
}
|
||||
|
||||
inline void Spc_Dsp::write( int addr, int data )
|
||||
{
|
||||
assert( (unsigned) addr < register_count );
|
||||
|
||||
regs [addr] = (byte) data;
|
||||
switch ( addr & 0x0F )
|
||||
{
|
||||
case v_envx:
|
||||
m.envx_buf = (byte) data;
|
||||
break;
|
||||
|
||||
case v_outx:
|
||||
m.outx_buf = (byte) data;
|
||||
break;
|
||||
|
||||
case 0x0C:
|
||||
if ( addr == r_kon )
|
||||
m.new_kon = (byte) data;
|
||||
|
||||
if ( addr == r_endx ) // always cleared, regardless of data written
|
||||
{
|
||||
m.endx_buf = 0;
|
||||
regs [r_endx] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Spc_Dsp::disable_surround( bool disable )
|
||||
{
|
||||
surround_threshold = disable ? 0 : -0x4000;
|
||||
}
|
||||
|
||||
inline bool Spc_Dsp::check_kon()
|
||||
{
|
||||
bool old = kon_check;
|
||||
kon_check = 0;
|
||||
return old;
|
||||
}
|
||||
|
||||
inline Spc_Dsp::sample_t* Spc_Dsp::output_ptr() const
|
||||
{
|
||||
// Don't return pointer into dummy_buf
|
||||
return (output_ptr_ != dummy_buf ? output_ptr_ : user_output_end);
|
||||
}
|
||||
|
||||
class SPC_State_Copier {
|
||||
Spc_Dsp::copy_func_t func;
|
||||
unsigned char** buf;
|
||||
public:
|
||||
SPC_State_Copier( unsigned char** p, Spc_Dsp::copy_func_t f ) { func = f; buf = p; }
|
||||
void copy( void* state, size_t size );
|
||||
int copy_int( int state, int size );
|
||||
void skip( int count );
|
||||
|
||||
// Reads uint8_t and then skips that many bytes. If writing, writes
|
||||
// uint8_t of 0. This allows future expansion at this point, by writing
|
||||
// non-zero and additional data.
|
||||
void extra();
|
||||
};
|
||||
|
||||
#define SPC_COPY( type, state )\
|
||||
{\
|
||||
state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\
|
||||
check( (BOOST::type) state == state );\
|
||||
}
|
||||
|
||||
BLARGG_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,159 @@
|
|||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "Spc_Dsp.h"
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2007-2010 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void SPC_State_Copier::copy( void* state, size_t size )
|
||||
{
|
||||
func( buf, state, size );
|
||||
}
|
||||
|
||||
int SPC_State_Copier::copy_int( int state, int size )
|
||||
{
|
||||
byte s [2];
|
||||
SET_LE16( s, state );
|
||||
func( buf, &s, size );
|
||||
return GET_LE16( s );
|
||||
}
|
||||
|
||||
void SPC_State_Copier::skip( int count )
|
||||
{
|
||||
if ( count > 0 )
|
||||
{
|
||||
char temp [64];
|
||||
memset( temp, 0, sizeof temp );
|
||||
do
|
||||
{
|
||||
int n = sizeof temp;
|
||||
if ( n > count )
|
||||
n = count;
|
||||
count -= n;
|
||||
func( buf, temp, n );
|
||||
}
|
||||
while ( count );
|
||||
}
|
||||
}
|
||||
|
||||
void SPC_State_Copier::extra()
|
||||
{
|
||||
int n = 0;
|
||||
SPC_State_Copier& copier = *this;
|
||||
SPC_COPY( uint8_t, n );
|
||||
skip( n );
|
||||
}
|
||||
|
||||
void Spc_Dsp::copy_state( unsigned char** io, copy_func_t copy )
|
||||
{
|
||||
SPC_State_Copier copier( io, copy );
|
||||
|
||||
// DSP registers
|
||||
copier.copy( regs, register_count );
|
||||
|
||||
// Internal state
|
||||
|
||||
// Voices
|
||||
int i;
|
||||
for ( i = 0; i < voice_count; i++ )
|
||||
{
|
||||
voice_t* v = &m.voices [i];
|
||||
|
||||
// BRR buffer
|
||||
for ( int bi = 0; bi < brr_buf_size; bi++ )
|
||||
{
|
||||
int s = v->buf [bi];
|
||||
SPC_COPY( int16_t, s );
|
||||
v->buf [bi] = v->buf [bi + brr_buf_size] = s;
|
||||
}
|
||||
|
||||
SPC_COPY( uint16_t, v->interp_pos );
|
||||
SPC_COPY( uint16_t, v->brr_addr );
|
||||
SPC_COPY( uint16_t, v->env );
|
||||
SPC_COPY( int16_t, v->hidden_env );
|
||||
SPC_COPY( uint8_t, v->buf_pos );
|
||||
SPC_COPY( uint8_t, v->brr_offset );
|
||||
SPC_COPY( uint8_t, v->kon_delay );
|
||||
{
|
||||
int mode = v->env_mode;
|
||||
SPC_COPY( uint8_t, mode );
|
||||
v->env_mode = (enum env_mode_t) mode;
|
||||
}
|
||||
SPC_COPY( uint8_t, v->t_envx_out );
|
||||
|
||||
copier.extra();
|
||||
}
|
||||
|
||||
// Echo history
|
||||
for ( i = 0; i < echo_hist_size; i++ )
|
||||
{
|
||||
int j;
|
||||
for ( j = 0; j < 2; j++ )
|
||||
{
|
||||
int s = m.echo_hist_pos [i] [j];
|
||||
SPC_COPY( int16_t, s );
|
||||
m.echo_hist [i] [j] = s; // write back at offset 0
|
||||
}
|
||||
}
|
||||
m.echo_hist_pos = m.echo_hist;
|
||||
memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] );
|
||||
|
||||
// Misc
|
||||
SPC_COPY( uint8_t, m.every_other_sample );
|
||||
SPC_COPY( uint8_t, m.kon );
|
||||
|
||||
SPC_COPY( uint16_t, m.noise );
|
||||
SPC_COPY( uint16_t, m.counter );
|
||||
SPC_COPY( uint16_t, m.echo_offset );
|
||||
SPC_COPY( uint16_t, m.echo_length );
|
||||
SPC_COPY( uint8_t, m.phase );
|
||||
|
||||
SPC_COPY( uint8_t, m.new_kon );
|
||||
SPC_COPY( uint8_t, m.endx_buf );
|
||||
SPC_COPY( uint8_t, m.envx_buf );
|
||||
SPC_COPY( uint8_t, m.outx_buf );
|
||||
|
||||
SPC_COPY( uint8_t, m.t_pmon );
|
||||
SPC_COPY( uint8_t, m.t_non );
|
||||
SPC_COPY( uint8_t, m.t_eon );
|
||||
SPC_COPY( uint8_t, m.t_dir );
|
||||
SPC_COPY( uint8_t, m.t_koff );
|
||||
|
||||
SPC_COPY( uint16_t, m.t_brr_next_addr );
|
||||
SPC_COPY( uint8_t, m.t_adsr0 );
|
||||
SPC_COPY( uint8_t, m.t_brr_header );
|
||||
SPC_COPY( uint8_t, m.t_brr_byte );
|
||||
SPC_COPY( uint8_t, m.t_srcn );
|
||||
SPC_COPY( uint8_t, m.t_esa );
|
||||
SPC_COPY( uint8_t, m.t_echo_enabled );
|
||||
|
||||
SPC_COPY( int16_t, m.t_main_out [0] );
|
||||
SPC_COPY( int16_t, m.t_main_out [1] );
|
||||
SPC_COPY( int16_t, m.t_echo_out [0] );
|
||||
SPC_COPY( int16_t, m.t_echo_out [1] );
|
||||
SPC_COPY( int16_t, m.t_echo_in [0] );
|
||||
SPC_COPY( int16_t, m.t_echo_in [1] );
|
||||
|
||||
SPC_COPY( uint16_t, m.t_dir_addr );
|
||||
SPC_COPY( uint16_t, m.t_pitch );
|
||||
SPC_COPY( int16_t, m.t_output );
|
||||
SPC_COPY( uint16_t, m.t_echo_ptr );
|
||||
SPC_COPY( uint8_t, m.t_looped );
|
||||
|
||||
copier.extra();
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,88 @@
|
|||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "Spc_Filter.h"
|
||||
|
||||
/* Copyright (C) 2007-2010 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void Spc_Filter::clear()
|
||||
{
|
||||
BLARGG_CLEAR( &ch );
|
||||
}
|
||||
|
||||
Spc_Filter::Spc_Filter()
|
||||
{
|
||||
enabled = true;
|
||||
gain = gain_unit;
|
||||
bass = bass_norm;
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
void Spc_Filter::run( sample_t io [], int count )
|
||||
{
|
||||
require( (count & 1) == 0 ); // must be even
|
||||
|
||||
int const gain = this->gain;
|
||||
if ( enabled )
|
||||
{
|
||||
int const bass = this->bass;
|
||||
chan_t* c = &ch [2];
|
||||
do
|
||||
{
|
||||
// cache in registers
|
||||
int sum = (--c)->sum;
|
||||
int pp1 = c->pp1;
|
||||
int p1 = c->p1;
|
||||
|
||||
for ( int i = 0; i < count; i += 2 )
|
||||
{
|
||||
// Low-pass filter (two point FIR with coeffs 0.25, 0.75)
|
||||
int f = io [i] + p1;
|
||||
p1 = io [i] * 3;
|
||||
|
||||
// High-pass filter ("leaky integrator")
|
||||
int delta = f - pp1;
|
||||
pp1 = f;
|
||||
int s = sum >> (gain_bits + 2);
|
||||
sum += (delta * gain) - (sum >> bass);
|
||||
|
||||
// Clamp to 16 bits
|
||||
if ( (sample_t) s != s )
|
||||
s = (s >> 31) ^ 0x7FFF;
|
||||
|
||||
io [i] = (sample_t) s;
|
||||
}
|
||||
|
||||
c->p1 = p1;
|
||||
c->pp1 = pp1;
|
||||
c->sum = sum;
|
||||
++io;
|
||||
}
|
||||
while ( c != ch );
|
||||
}
|
||||
else if ( gain != gain_unit )
|
||||
{
|
||||
sample_t* const end = io + count;
|
||||
while ( io < end )
|
||||
{
|
||||
int s = (*io * gain) >> gain_bits;
|
||||
|
||||
// Clamp to 16 bits
|
||||
if ( (sample_t) s != s )
|
||||
s = (s >> 31) ^ 0x7FFF;
|
||||
|
||||
*io++ = (sample_t) s;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// Simple low-pass and high-pass filter to better match sound output of a SNES
|
||||
|
||||
// snes_spc 0.9.5
|
||||
#ifndef BLARGG_SPC_FILTER_H
|
||||
#define BLARGG_SPC_FILTER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
struct Spc_Filter {
|
||||
public:
|
||||
|
||||
// Filters count samples of stereo sound in place. Count must be a multiple of 2.
|
||||
typedef short sample_t;
|
||||
void run( sample_t io [], int count );
|
||||
|
||||
// Optional features
|
||||
|
||||
// Clears filter to silence
|
||||
void clear();
|
||||
|
||||
// Sets gain (volume), where gain_unit is normal. Gains greater than gain_unit
|
||||
// are fine, since output is clamped to 16-bit sample range.
|
||||
enum { gain_unit = 0x100 };
|
||||
void set_gain( int g ) { gain = g; }
|
||||
|
||||
// Enables/disables filtering (when disabled, gain is still applied)
|
||||
void enable( bool b ) { enabled = b; }
|
||||
|
||||
// Sets amount of bass (logarithmic scale)
|
||||
enum { bass_none = 0 };
|
||||
enum { bass_norm = 8 }; // normal amount
|
||||
enum { bass_max = 31 };
|
||||
void set_bass( int b ) { bass = b; }
|
||||
|
||||
public:
|
||||
Spc_Filter();
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
private:
|
||||
enum { gain_bits = 8 };
|
||||
int gain;
|
||||
int bass;
|
||||
bool enabled;
|
||||
struct chan_t { int p1, pp1, sum; };
|
||||
chan_t ch [2];
|
||||
};
|
||||
|
||||
BLARGG_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,134 @@
|
|||
// SPC emulation state save/load: copy_state(), save_spc()
|
||||
// Separate file to avoid linking in unless needed
|
||||
|
||||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "Snes_Spc.h"
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2010 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void Snes_Spc::init_header( void* spc_out )
|
||||
{
|
||||
spc_file_t* const spc = (spc_file_t*) spc_out;
|
||||
|
||||
spc->has_id666 = 26; // has none
|
||||
spc->version = 30;
|
||||
memcpy( spc, signature, sizeof spc->signature );
|
||||
memset( spc->text, 0, sizeof spc->text );
|
||||
}
|
||||
|
||||
void Spc_Core::save_regs( byte out [reg_count], byte ports [port_count] )
|
||||
{
|
||||
memcpy( out, regs, reg_count );
|
||||
|
||||
int i;
|
||||
for ( i = 0; i < port_count; i++ )
|
||||
out [r_cpuio0 + i] = regs_in [r_cpuio0 + i];
|
||||
|
||||
for ( i = 0; i < timer_count; i++ )
|
||||
out [r_t0out + i] = regs_in [r_t0out + i];
|
||||
|
||||
memcpy( ports, ®s [r_cpuio0], port_count );
|
||||
}
|
||||
|
||||
void Spc_Core::save_ram( byte out [65536] )
|
||||
{
|
||||
memcpy( out, ram(), 65536 );
|
||||
|
||||
if ( rom_enabled )
|
||||
memcpy( out + rom_addr, hi_ram, sizeof hi_ram );
|
||||
}
|
||||
|
||||
void Snes_Spc::save_spc( void* spc_out )
|
||||
{
|
||||
spc_file_t* const spc = (spc_file_t*) spc_out;
|
||||
|
||||
// CPU
|
||||
spc->pcl = (byte) (cpu().pc >> 0);
|
||||
spc->pch = (byte) (cpu().pc >> 8);
|
||||
spc->a = cpu().a;
|
||||
spc->x = cpu().x;
|
||||
spc->y = cpu().y;
|
||||
spc->psw = cpu().psw;
|
||||
spc->sp = cpu().sp;
|
||||
|
||||
// RAM, ROM
|
||||
save_ram( spc->ram );
|
||||
memset( spc->unused, 0, sizeof spc->unused );
|
||||
memcpy( spc->ipl_rom, rom(), sizeof spc->ipl_rom );
|
||||
|
||||
// SMP registers
|
||||
byte out_ports [port_count]; // ignored
|
||||
save_regs( &spc->ram [regs_addr], out_ports );
|
||||
|
||||
// DSP registers
|
||||
for ( int i = 0; i < Spc_Dsp::register_count; i++ )
|
||||
spc->dsp [i] = dsp().read( i );
|
||||
}
|
||||
|
||||
void Spc_Core::copy_state( unsigned char** io, copy_func_t copy )
|
||||
{
|
||||
SPC_State_Copier copier( io, copy );
|
||||
|
||||
// Make state data more readable by putting 64K RAM, 16 SMP registers,
|
||||
// then DSP (with its 128 registers) first
|
||||
|
||||
// RAM
|
||||
|
||||
// Disable ROM so that entire RAM will be in ram(). If ROM was enabled,
|
||||
// will get re-enabled by load_regs() below.
|
||||
enable_rom( false );
|
||||
copier.copy( ram(), 0x10000 );
|
||||
|
||||
// SMP registers
|
||||
{
|
||||
byte new_regs [reg_count];
|
||||
byte out_ports [port_count];
|
||||
|
||||
save_regs( new_regs, out_ports );
|
||||
copier.copy( new_regs, sizeof new_regs );
|
||||
copier.copy( out_ports, sizeof out_ports );
|
||||
load_regs( new_regs, out_ports );
|
||||
}
|
||||
|
||||
// CPU registers
|
||||
SPC_COPY( uint16_t, cpu().pc );
|
||||
SPC_COPY( uint8_t, cpu().a );
|
||||
SPC_COPY( uint8_t, cpu().x );
|
||||
SPC_COPY( uint8_t, cpu().y );
|
||||
SPC_COPY( uint8_t, cpu().psw );
|
||||
SPC_COPY( uint8_t, cpu().sp );
|
||||
copier.extra();
|
||||
|
||||
SPC_COPY( int16_t, spc_time );
|
||||
SPC_COPY( int16_t, dsp_time );
|
||||
|
||||
// DSP
|
||||
dsp().copy_state( io, copy );
|
||||
|
||||
// Timers
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &timers [i];
|
||||
SPC_COPY( int16_t, t->time );
|
||||
SPC_COPY( uint8_t, t->divider );
|
||||
copier.extra();
|
||||
}
|
||||
copier.extra();
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,58 @@
|
|||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
/* Copyright (C) 2008-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
// defined here to avoid need for blargg_errors.cpp in simple programs
|
||||
blargg_err_def_t blargg_err_memory = BLARGG_ERR_MEMORY;
|
||||
|
||||
void blargg_vector_::init()
|
||||
{
|
||||
begin_ = NULL;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
void blargg_vector_::clear()
|
||||
{
|
||||
void* p = begin_;
|
||||
begin_ = NULL;
|
||||
size_ = 0;
|
||||
free( p );
|
||||
}
|
||||
|
||||
blargg_err_t blargg_vector_::resize_( size_t n, size_t elem_size )
|
||||
{
|
||||
if ( n != size_ )
|
||||
{
|
||||
if ( n == 0 )
|
||||
{
|
||||
// Simpler to handle explicitly. Realloc will handle a size of 0,
|
||||
// but then we have to avoid raising an error for a NULL return.
|
||||
clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
void* p = realloc( begin_, n * elem_size );
|
||||
CHECK_ALLOC( p );
|
||||
begin_ = p;
|
||||
size_ = n;
|
||||
}
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
BLARGG_NAMESPACE_END
|
|
@ -0,0 +1,222 @@
|
|||
// Sets up common environment for Shay Green's libraries.
|
||||
// To change configuration options, modify blargg_config.h, not this file.
|
||||
|
||||
// snes_spc 0.9.5
|
||||
#ifndef BLARGG_COMMON_H
|
||||
#define BLARGG_COMMON_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
typedef const char* blargg_err_t; // 0 on success, otherwise error string
|
||||
|
||||
// Success; no error
|
||||
int const blargg_ok = 0;
|
||||
|
||||
// BLARGG_RESTRICT: equivalent to C99's restrict, where supported
|
||||
#if __GNUC__ >= 3 || _MSC_VER >= 1100
|
||||
#define BLARGG_RESTRICT __restrict
|
||||
#else
|
||||
#define BLARGG_RESTRICT
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 199711
|
||||
#define BLARGG_MUTABLE mutable
|
||||
#else
|
||||
#define BLARGG_MUTABLE
|
||||
#endif
|
||||
|
||||
/* BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant).
|
||||
I don't just use 'abcd' because that's implementation-dependent. */
|
||||
#define BLARGG_4CHAR( a, b, c, d ) \
|
||||
((a&0xFF)*0x1000000 + (b&0xFF)*0x10000 + (c&0xFF)*0x100 + (d&0xFF))
|
||||
|
||||
/* BLARGG_STATIC_ASSERT( expr ): Generates compile error if expr is 0.
|
||||
Can be used at file, function, or class scope. */
|
||||
#ifdef _MSC_VER
|
||||
// MSVC6 (_MSC_VER < 1300) __LINE__ fails when /Zl is specified
|
||||
#define BLARGG_STATIC_ASSERT( expr ) \
|
||||
void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] )
|
||||
#else
|
||||
// Others fail when declaring same function multiple times in class,
|
||||
// so differentiate them by line
|
||||
#define BLARGG_STATIC_ASSERT( expr ) \
|
||||
void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] )
|
||||
#endif
|
||||
|
||||
/* Pure virtual functions cause a vtable entry to a "called pure virtual"
|
||||
error handler, requiring linkage to the C++ runtime library. This macro is
|
||||
used in place of the "= 0", and simply expands to its argument. During
|
||||
development, it expands to "= 0", allowing detection of missing overrides. */
|
||||
#define BLARGG_PURE( def ) def
|
||||
|
||||
/* My code depends on ASCII anywhere a character or string constant is
|
||||
compared with data read from a file, and anywhere file data is read and
|
||||
treated as a string. */
|
||||
#if '\n'!=0x0A || ' '!=0x20 || '0'!=0x30 || 'A'!=0x41 || 'a'!=0x61
|
||||
#error "ASCII character set required"
|
||||
#endif
|
||||
|
||||
/* My code depends on int being at least 32 bits. Almost everything these days
|
||||
uses at least 32-bit ints, so it's hard to even find a system with 16-bit ints
|
||||
to test with. The issue can't be gotten around by using a suitable blargg_int
|
||||
everywhere either, because int is often converted to implicitly when doing
|
||||
arithmetic on smaller types. */
|
||||
#if UINT_MAX < 0xFFFFFFFF
|
||||
#error "int must be at least 32 bits"
|
||||
#endif
|
||||
|
||||
// In case compiler doesn't support these properly. Used rarely.
|
||||
#define STATIC_CAST(T,expr) static_cast<T> (expr)
|
||||
#define CONST_CAST( T,expr) const_cast<T> (expr)
|
||||
|
||||
// User configuration can override the above macros if necessary
|
||||
#include "blargg_config.h"
|
||||
|
||||
#ifdef BLARGG_NAMESPACE
|
||||
#define BLARGG_NAMESPACE_BEGIN namespace BLARGG_NAMESPACE {
|
||||
#define BLARGG_NAMESPACE_END }
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
BLARGG_NAMESPACE_END
|
||||
using namespace BLARGG_NAMESPACE;
|
||||
#else
|
||||
#define BLARGG_NAMESPACE_BEGIN
|
||||
#define BLARGG_NAMESPACE_END
|
||||
#endif
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
/* BLARGG_DEPRECATED [_TEXT] for any declarations/text to be removed in a
|
||||
future version. In GCC, we can let the compiler warn. In other compilers,
|
||||
we strip it out unless BLARGG_LEGACY is true. */
|
||||
#if BLARGG_LEGACY
|
||||
// Allow old client code to work without warnings
|
||||
#define BLARGG_DEPRECATED_TEXT( text ) text
|
||||
#define BLARGG_DEPRECATED( text ) text
|
||||
#elif __GNUC__ >= 4
|
||||
// In GCC, we can mark declarations and let the compiler warn
|
||||
#define BLARGG_DEPRECATED_TEXT( text ) text
|
||||
#define BLARGG_DEPRECATED( text ) __attribute__ ((deprecated)) text
|
||||
#else
|
||||
// By default, deprecated items are removed, to avoid use in new code
|
||||
#define BLARGG_DEPRECATED_TEXT( text )
|
||||
#define BLARGG_DEPRECATED( text )
|
||||
#endif
|
||||
|
||||
/* BOOST::int8_t, BOOST::int32_t, etc.
|
||||
I used BOOST since I originally was going to allow use of the boost library
|
||||
for prividing the definitions. If I'm defining them, they must be scoped or
|
||||
else they could conflict with the standard ones at global scope. Even if
|
||||
HAVE_STDINT_H isn't defined, I can't assume the typedefs won't exist at
|
||||
global scope already. */
|
||||
#if defined (HAVE_STDINT_H) || \
|
||||
UCHAR_MAX != 0xFF || USHRT_MAX != 0xFFFF || UINT_MAX != 0xFFFFFFFF
|
||||
#include <stdint.h>
|
||||
#define BOOST
|
||||
#else
|
||||
struct BOOST
|
||||
{
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef short int16_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef int int32_t;
|
||||
typedef unsigned int uint32_t;
|
||||
};
|
||||
#endif
|
||||
|
||||
/* My code is not written with exceptions in mind, so either uses new (nothrow)
|
||||
OR overrides operator new in my classes. The former is best since clients
|
||||
creating objects will get standard exceptions on failure, but that causes it
|
||||
to require the standard C++ library. So, when the client is using the C
|
||||
interface, I override operator new to use malloc. */
|
||||
|
||||
// BLARGG_DISABLE_NOTHROW is put inside classes
|
||||
#ifndef BLARGG_DISABLE_NOTHROW
|
||||
// throw spec mandatory in ISO C++ if NULL can be returned
|
||||
#if __cplusplus >= 199711 || __GNUC__ >= 3 || _MSC_VER >= 1300
|
||||
#define BLARGG_THROWS_NOTHING throw ()
|
||||
#else
|
||||
#define BLARGG_THROWS_NOTHING
|
||||
#endif
|
||||
|
||||
#define BLARGG_DISABLE_NOTHROW \
|
||||
void* operator new ( size_t s ) BLARGG_THROWS_NOTHING { return malloc( s ); }\
|
||||
void operator delete( void* p ) BLARGG_THROWS_NOTHING { free( p ); }
|
||||
|
||||
#define BLARGG_NEW new
|
||||
#else
|
||||
// BLARGG_NEW is used in place of new in library code
|
||||
#include <new>
|
||||
#define BLARGG_NEW new (std::nothrow)
|
||||
#endif
|
||||
|
||||
class blargg_vector_ {
|
||||
protected:
|
||||
void* begin_;
|
||||
size_t size_;
|
||||
void init();
|
||||
blargg_err_t resize_( size_t n, size_t elem_size );
|
||||
public:
|
||||
size_t size() const { return size_; }
|
||||
void clear();
|
||||
};
|
||||
|
||||
// Very lightweight vector for POD types (no constructor/destructor)
|
||||
template<class T>
|
||||
class blargg_vector : public blargg_vector_ {
|
||||
union T_must_be_pod { T t; }; // fails if T is not POD
|
||||
public:
|
||||
blargg_vector() { init(); }
|
||||
~blargg_vector() { clear(); }
|
||||
|
||||
blargg_err_t resize( size_t n ) { return resize_( n, sizeof (T) ); }
|
||||
|
||||
T* begin() { return static_cast<T*> (begin_); }
|
||||
const T* begin() const { return static_cast<T*> (begin_); }
|
||||
|
||||
T* end() { return static_cast<T*> (begin_) + size_; }
|
||||
const T* end() const { return static_cast<T*> (begin_) + size_; }
|
||||
|
||||
T& operator [] ( size_t n )
|
||||
{
|
||||
assert( n < size_ );
|
||||
return static_cast<T*> (begin_) [n];
|
||||
}
|
||||
|
||||
const T& operator [] ( size_t n ) const
|
||||
{
|
||||
assert( n < size_ );
|
||||
return static_cast<T*> (begin_) [n];
|
||||
}
|
||||
};
|
||||
|
||||
// Callback function with user data.
|
||||
// blargg_callback<T> set_callback; // for user, this acts like...
|
||||
// void set_callback( T func, void* user_data = NULL ); // ...this
|
||||
// To call function, do set_callback.f( .. set_callback.data ... );
|
||||
template<class T>
|
||||
struct blargg_callback
|
||||
{
|
||||
T f;
|
||||
void* data;
|
||||
blargg_callback() { f = NULL; }
|
||||
void operator () ( T callback, void* user_data = NULL ) { f = callback; data = user_data; }
|
||||
};
|
||||
|
||||
#ifndef _WIN32
|
||||
// Not supported on any other platforms
|
||||
#undef BLARGG_UTF8_PATHS
|
||||
#endif
|
||||
|
||||
BLARGG_DEPRECATED( typedef signed int blargg_long; )
|
||||
BLARGG_DEPRECATED( typedef unsigned int blargg_ulong; )
|
||||
#if BLARGG_LEGACY
|
||||
#define BOOST_STATIC_ASSERT BLARGG_STATIC_ASSERT
|
||||
#endif
|
||||
|
||||
BLARGG_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,26 @@
|
|||
// snes_spc 0.9.5 user configuration file. Don't replace when updating library.
|
||||
|
||||
#ifndef BLARGG_CONFIG_H
|
||||
#define BLARGG_CONFIG_H
|
||||
|
||||
// Uncomment to use zlib for transparent decompression of gzipped files
|
||||
//#define HAVE_ZLIB_H
|
||||
|
||||
// Uncomment to enable platform-specific (and possibly non-portable) optimizations.
|
||||
//#define BLARGG_NONPORTABLE 1
|
||||
|
||||
// Uncomment if automatic byte-order determination doesn't work
|
||||
//#define BLARGG_BIG_ENDIAN 1
|
||||
|
||||
// Uncomment to enable the normal behavior of getting an out-of-memory exception
|
||||
// when new fails while creating an object of a class from this library. Note
|
||||
// that this does not enable exceptions when calling library functions, which
|
||||
// ALWAYS report errors by blargg_err_t.
|
||||
//#define BLARGG_DISABLE_NOTHROW
|
||||
|
||||
// Use standard config.h if present
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,201 @@
|
|||
// CPU Byte Order Utilities
|
||||
|
||||
// snes_spc 0.9.5
|
||||
#ifndef BLARGG_ENDIAN_H
|
||||
#define BLARGG_ENDIAN_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16)
|
||||
#if defined (__i386__) || defined (__x86_64__) || defined (_M_IX86) || defined (_M_X64)
|
||||
#define BLARGG_CPU_X86 1
|
||||
#define BLARGG_CPU_CISC 1
|
||||
#endif
|
||||
|
||||
#if defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) || \
|
||||
defined (__POWERPC__) || defined (__powerc)
|
||||
#define BLARGG_CPU_POWERPC 1
|
||||
#define BLARGG_CPU_RISC 1
|
||||
#endif
|
||||
|
||||
// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only
|
||||
// one may be #defined to 1. Only needed if something actually depends on byte order.
|
||||
#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN)
|
||||
#ifdef __GLIBC__
|
||||
// GCC handles this for us
|
||||
#include <endian.h>
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
#define BLARGG_BIG_ENDIAN 1
|
||||
#endif
|
||||
#else
|
||||
|
||||
#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \
|
||||
(defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234)
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#endif
|
||||
|
||||
#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \
|
||||
defined (__sparc__) || BLARGG_CPU_POWERPC || \
|
||||
(defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321)
|
||||
#define BLARGG_BIG_ENDIAN 1
|
||||
#elif !defined (__mips__)
|
||||
// No endian specified; assume little-endian, since it's most common
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN
|
||||
#undef BLARGG_LITTLE_ENDIAN
|
||||
#undef BLARGG_BIG_ENDIAN
|
||||
#endif
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
inline void blargg_verify_byte_order()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
#if BLARGG_LITTLE_ENDIAN || BLARGG_BIG_ENDIAN
|
||||
union {
|
||||
volatile int i;
|
||||
volatile char c;
|
||||
};
|
||||
i = 1;
|
||||
#if BLARGG_LITTLE_ENDIAN
|
||||
assert( c != 0 );
|
||||
#else
|
||||
assert( c == 0 );
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
inline unsigned get_le16( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [1] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [0];
|
||||
}
|
||||
|
||||
inline unsigned get_be16( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [0] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [1];
|
||||
}
|
||||
|
||||
inline unsigned get_le32( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [3] << 24 |
|
||||
(unsigned) ((unsigned char const*) p) [2] << 16 |
|
||||
(unsigned) ((unsigned char const*) p) [1] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [0];
|
||||
}
|
||||
|
||||
inline unsigned get_be32( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [0] << 24 |
|
||||
(unsigned) ((unsigned char const*) p) [1] << 16 |
|
||||
(unsigned) ((unsigned char const*) p) [2] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [3];
|
||||
}
|
||||
|
||||
inline void set_le16( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [0] = (unsigned char) n;
|
||||
}
|
||||
|
||||
inline void set_be16( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [0] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [1] = (unsigned char) n;
|
||||
}
|
||||
|
||||
inline void set_le32( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [0] = (unsigned char) n;
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [2] = (unsigned char) (n >> 16);
|
||||
((unsigned char*) p) [3] = (unsigned char) (n >> 24);
|
||||
}
|
||||
|
||||
inline void set_be32( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [3] = (unsigned char) n;
|
||||
((unsigned char*) p) [2] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 16);
|
||||
((unsigned char*) p) [0] = (unsigned char) (n >> 24);
|
||||
}
|
||||
|
||||
#if BLARGG_NONPORTABLE
|
||||
// Optimized implementation if byte order is known
|
||||
#if BLARGG_LITTLE_ENDIAN
|
||||
#define GET_LE16_FAST GET_LE16
|
||||
#define GET_LE16( addr ) (*(BOOST::uint16_t const*) (addr))
|
||||
#define GET_LE32( addr ) (*(BOOST::uint32_t const*) (addr))
|
||||
#define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
|
||||
#define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
|
||||
#elif BLARGG_BIG_ENDIAN
|
||||
#define GET_BE16( addr ) (*(BOOST::uint16_t const*) (addr))
|
||||
#define GET_BE32( addr ) (*(BOOST::uint32_t const*) (addr))
|
||||
#define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
|
||||
#define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
|
||||
|
||||
#if BLARGG_CPU_POWERPC
|
||||
// PowerPC has special byte-reversed instructions
|
||||
#define GET_LE16_FAST GET_LE16
|
||||
#if defined (__MWERKS__)
|
||||
#define GET_LE16( addr ) (__lhbrx( addr, 0 ))
|
||||
#define GET_LE32( addr ) (__lwbrx( addr, 0 ))
|
||||
#define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 ))
|
||||
#define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 ))
|
||||
#elif defined (__GNUC__)
|
||||
#define GET_LE16( addr ) ({unsigned short ppc_lhbrx_; __asm__ volatile( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr) : "memory" ); ppc_lhbrx_;})
|
||||
#define GET_LE32( addr ) ({unsigned short ppc_lwbrx_; __asm__ volatile( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr) : "memory" ); ppc_lwbrx_;})
|
||||
#define SET_LE16( addr, in ) ({__asm__ volatile( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) : "memory" );})
|
||||
#define SET_LE32( addr, in ) ({__asm__ volatile( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) : "memory" );})
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef GET_LE16
|
||||
#define GET_LE16( addr ) get_le16( addr )
|
||||
#define SET_LE16( addr, data ) set_le16( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_LE32
|
||||
#define GET_LE32( addr ) get_le32( addr )
|
||||
#define SET_LE32( addr, data ) set_le32( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_BE16
|
||||
#define GET_BE16( addr ) get_be16( addr )
|
||||
#define SET_BE16( addr, data ) set_be16( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_BE32
|
||||
#define GET_BE32( addr ) get_be32( addr )
|
||||
#define SET_BE32( addr, data ) set_be32( addr, data )
|
||||
#endif
|
||||
|
||||
// Address must be aligned
|
||||
#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr ))
|
||||
#define GET_LE16A( addr ) GET_LE16( addr )
|
||||
#define SET_LE16A( addr, data ) SET_LE16( addr, data )
|
||||
|
||||
// auto-selecting versions
|
||||
|
||||
inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); }
|
||||
inline void set_le( BOOST::uint32_t* p, unsigned n ) { SET_LE32( p, n ); }
|
||||
inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); }
|
||||
inline void set_be( BOOST::uint32_t* p, unsigned n ) { SET_BE32( p, n ); }
|
||||
inline unsigned get_le( BOOST::uint16_t const* p ) { return GET_LE16( p ); }
|
||||
inline unsigned get_le( BOOST::uint32_t const* p ) { return GET_LE32( p ); }
|
||||
inline unsigned get_be( BOOST::uint16_t const* p ) { return GET_BE16( p ); }
|
||||
inline unsigned get_be( BOOST::uint32_t const* p ) { return GET_BE32( p ); }
|
||||
|
||||
BLARGG_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,117 @@
|
|||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "blargg_errors.h"
|
||||
|
||||
/* Copyright (C) 2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
blargg_err_def_t blargg_err_generic = BLARGG_ERR_GENERIC;
|
||||
// blargg_err_memory is defined in blargg_common.cpp
|
||||
blargg_err_def_t blargg_err_caller = BLARGG_ERR_CALLER;
|
||||
blargg_err_def_t blargg_err_internal = BLARGG_ERR_INTERNAL;
|
||||
blargg_err_def_t blargg_err_limitation = BLARGG_ERR_LIMITATION;
|
||||
|
||||
blargg_err_def_t blargg_err_file_missing = BLARGG_ERR_FILE_MISSING;
|
||||
blargg_err_def_t blargg_err_file_read = BLARGG_ERR_FILE_READ;
|
||||
blargg_err_def_t blargg_err_file_write = BLARGG_ERR_FILE_WRITE;
|
||||
blargg_err_def_t blargg_err_file_io = BLARGG_ERR_FILE_IO;
|
||||
blargg_err_def_t blargg_err_file_full = BLARGG_ERR_FILE_FULL;
|
||||
blargg_err_def_t blargg_err_file_eof = BLARGG_ERR_FILE_EOF;
|
||||
|
||||
blargg_err_def_t blargg_err_file_type = BLARGG_ERR_FILE_TYPE;
|
||||
blargg_err_def_t blargg_err_file_feature = BLARGG_ERR_FILE_FEATURE;
|
||||
blargg_err_def_t blargg_err_file_corrupt = BLARGG_ERR_FILE_CORRUPT;
|
||||
|
||||
const char* blargg_err_str( blargg_err_t err )
|
||||
{
|
||||
if ( !err )
|
||||
return "";
|
||||
|
||||
if ( *err == BLARGG_ERR_TYPE("")[0] )
|
||||
return err + 1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool blargg_is_err_type( blargg_err_t err, const char type [] )
|
||||
{
|
||||
if ( err )
|
||||
{
|
||||
// True if first strlen(type) characters of err match type
|
||||
char const* p = err;
|
||||
while ( *type && *type == *p )
|
||||
{
|
||||
type++;
|
||||
p++;
|
||||
}
|
||||
|
||||
if ( !*type )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* blargg_err_details( blargg_err_t err )
|
||||
{
|
||||
const char* p = err;
|
||||
if ( !p )
|
||||
{
|
||||
p = "";
|
||||
}
|
||||
else if ( *p == BLARGG_ERR_TYPE("")[0] )
|
||||
{
|
||||
while ( *p && *p != ';' )
|
||||
p++;
|
||||
|
||||
// Skip ; and space after it
|
||||
if ( *p )
|
||||
{
|
||||
p++;
|
||||
|
||||
check( *p == ' ' );
|
||||
if ( *p )
|
||||
p++;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const codes [] )
|
||||
{
|
||||
if ( !err )
|
||||
return 0;
|
||||
|
||||
while ( codes->str && !blargg_is_err_type( err, codes->str ) )
|
||||
codes++;
|
||||
|
||||
return codes->code;
|
||||
}
|
||||
|
||||
blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const codes [] )
|
||||
{
|
||||
if ( !code )
|
||||
return blargg_ok;
|
||||
|
||||
while ( codes->str && codes->code != code )
|
||||
codes++;
|
||||
|
||||
if ( !codes->str )
|
||||
return blargg_err_generic;
|
||||
|
||||
return codes->str;
|
||||
}
|
||||
|
||||
BLARGG_NAMESPACE_END
|
|
@ -0,0 +1,84 @@
|
|||
// Error strings and conversion functions
|
||||
|
||||
// snes_spc 0.9.5
|
||||
#ifndef BLARGG_ERRORS_H
|
||||
#define BLARGG_ERRORS_H
|
||||
|
||||
#ifndef BLARGG_COMMON_H
|
||||
#include "blargg_common.h"
|
||||
#endif
|
||||
|
||||
BLARGG_NAMESPACE_BEGIN
|
||||
|
||||
typedef const char blargg_err_def_t [];
|
||||
|
||||
// Basic errors
|
||||
extern blargg_err_def_t blargg_err_generic;
|
||||
extern blargg_err_def_t blargg_err_memory;
|
||||
extern blargg_err_def_t blargg_err_caller;
|
||||
extern blargg_err_def_t blargg_err_internal;
|
||||
extern blargg_err_def_t blargg_err_limitation;
|
||||
|
||||
// File low-level
|
||||
extern blargg_err_def_t blargg_err_file_missing; // not found
|
||||
extern blargg_err_def_t blargg_err_file_read;
|
||||
extern blargg_err_def_t blargg_err_file_write;
|
||||
extern blargg_err_def_t blargg_err_file_io;
|
||||
extern blargg_err_def_t blargg_err_file_full;
|
||||
extern blargg_err_def_t blargg_err_file_eof;
|
||||
|
||||
// File high-level
|
||||
extern blargg_err_def_t blargg_err_file_type; // wrong file type
|
||||
extern blargg_err_def_t blargg_err_file_feature;
|
||||
extern blargg_err_def_t blargg_err_file_corrupt;
|
||||
|
||||
// C string describing error, or "" if err == NULL
|
||||
const char* blargg_err_str( blargg_err_t err );
|
||||
|
||||
// True iff error is of given type, or false if err == NULL
|
||||
bool blargg_is_err_type( blargg_err_t, const char type [] );
|
||||
|
||||
// Details of error without describing main cause, or "" if err == NULL
|
||||
const char* blargg_err_details( blargg_err_t err );
|
||||
|
||||
// Converts error string to integer code using mapping table. Calls blargg_is_err_type()
|
||||
// for each str and returns code on first match. Returns 0 if err == NULL.
|
||||
struct blargg_err_to_code_t {
|
||||
const char* str;
|
||||
int code;
|
||||
};
|
||||
int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const [] );
|
||||
|
||||
// Converts error code back to string. If code == 0, returns NULL. If not in table,
|
||||
// returns blargg_err_generic.
|
||||
blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const [] );
|
||||
|
||||
// Generates error string literal with details of cause
|
||||
#define BLARGG_ERR( type, str ) (type "; " str)
|
||||
|
||||
// Extra space to make it clear when blargg_err_str() isn't called to get
|
||||
// printable version of error. At some point, I might prefix error strings
|
||||
// with a code, to speed conversion to a code.
|
||||
#define BLARGG_ERR_TYPE( str ) " " str
|
||||
|
||||
// Error types to pass to BLARGG_ERR macro
|
||||
#define BLARGG_ERR_GENERIC BLARGG_ERR_TYPE( "operation failed" )
|
||||
#define BLARGG_ERR_MEMORY BLARGG_ERR_TYPE( "out of memory" )
|
||||
#define BLARGG_ERR_CALLER BLARGG_ERR_TYPE( "internal usage bug" )
|
||||
#define BLARGG_ERR_INTERNAL BLARGG_ERR_TYPE( "internal bug" )
|
||||
#define BLARGG_ERR_LIMITATION BLARGG_ERR_TYPE( "exceeded limitation" )
|
||||
|
||||
#define BLARGG_ERR_FILE_MISSING BLARGG_ERR_TYPE( "file not found" )
|
||||
#define BLARGG_ERR_FILE_READ BLARGG_ERR_TYPE( "couldn't open file" )
|
||||
#define BLARGG_ERR_FILE_WRITE BLARGG_ERR_TYPE( "couldn't modify file" )
|
||||
#define BLARGG_ERR_FILE_IO BLARGG_ERR_TYPE( "read/write error" )
|
||||
#define BLARGG_ERR_FILE_FULL BLARGG_ERR_TYPE( "disk full" )
|
||||
#define BLARGG_ERR_FILE_EOF BLARGG_ERR_TYPE( "truncated file" )
|
||||
|
||||
#define BLARGG_ERR_FILE_TYPE BLARGG_ERR_TYPE( "wrong file type" )
|
||||
#define BLARGG_ERR_FILE_FEATURE BLARGG_ERR_TYPE( "unsupported file feature" )
|
||||
#define BLARGG_ERR_FILE_CORRUPT BLARGG_ERR_TYPE( "corrupt file" )
|
||||
|
||||
BLARGG_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,136 @@
|
|||
/* Included at the beginning of library source files, AFTER all other #include
|
||||
lines. Sets up helpful macros and services used in my source code. Since this
|
||||
is only "active" in my source code, I don't have to worry about polluting the
|
||||
global namespace with unprefixed names. */
|
||||
|
||||
// snes_spc 0.9.5
|
||||
#ifndef BLARGG_SOURCE_H
|
||||
#define BLARGG_SOURCE_H
|
||||
|
||||
#ifndef BLARGG_COMMON_H // optimization only
|
||||
#include "blargg_common.h"
|
||||
#endif
|
||||
#include "blargg_errors.h"
|
||||
|
||||
#include <string.h> /* memcpy(), memset(), memmove() */
|
||||
#include <stddef.h> /* offsetof() */
|
||||
|
||||
/* The following four macros are for debugging only. Some or all might be
|
||||
defined to do nothing, depending on the circumstances. Described is what
|
||||
happens when a particular macro is defined to do something. When defined to
|
||||
do nothing, the macros do NOT evaluate their argument(s). */
|
||||
|
||||
/* If expr is false, prints file and line number, then aborts program. Meant
|
||||
for checking internal state and consistency. A failed assertion indicates a bug
|
||||
in MY code.
|
||||
|
||||
void assert( bool expr ); */
|
||||
#include <assert.h>
|
||||
|
||||
/* If expr is false, prints file and line number, then aborts program. Meant
|
||||
for checking caller-supplied parameters and operations that are outside the
|
||||
control of the module. A failed requirement probably indicates a bug in YOUR
|
||||
code.
|
||||
|
||||
void require( bool expr ); */
|
||||
#undef require
|
||||
#define require( expr ) assert( expr )
|
||||
|
||||
/* Like printf() except output goes to debugging console/file.
|
||||
|
||||
void dprintf( const char format [], ... ); */
|
||||
static inline void blargg_dprintf_( const char [], ... ) { }
|
||||
#undef dprintf
|
||||
#define dprintf (1) ? (void) 0 : blargg_dprintf_
|
||||
|
||||
/* If expr is false, prints file and line number to debug console/log, then
|
||||
continues execution normally. Meant for flagging potential problems or things
|
||||
that should be looked into, but that aren't serious problems.
|
||||
|
||||
void check( bool expr ); */
|
||||
#undef check
|
||||
#define check( expr ) ((void) 0)
|
||||
|
||||
/* If expr yields non-NULL error string, returns it from current function,
|
||||
otherwise continues normally. */
|
||||
#undef RETURN_ERR
|
||||
#define RETURN_ERR( expr ) \
|
||||
do {\
|
||||
blargg_err_t blargg_return_err_ = (expr);\
|
||||
if ( blargg_return_err_ )\
|
||||
return blargg_return_err_;\
|
||||
} while ( 0 )
|
||||
|
||||
/* If ptr is NULL, returns out-of-memory error, otherwise continues normally. */
|
||||
#undef CHECK_ALLOC
|
||||
#define CHECK_ALLOC( ptr ) \
|
||||
do {\
|
||||
if ( !(ptr) )\
|
||||
return blargg_err_memory;\
|
||||
} while ( 0 )
|
||||
|
||||
/* The usual min/max functions for built-in types.
|
||||
|
||||
template<typename T> T min( T x, T y ) { return x < y ? x : y; }
|
||||
template<typename T> T max( T x, T y ) { return x > y ? x : y; } */
|
||||
#define BLARGG_DEF_MIN_MAX( type ) \
|
||||
static inline type blargg_min( type x, type y ) { if ( y < x ) x = y; return x; }\
|
||||
static inline type blargg_max( type x, type y ) { if ( x < y ) x = y; return x; }
|
||||
|
||||
BLARGG_DEF_MIN_MAX( int )
|
||||
BLARGG_DEF_MIN_MAX( unsigned )
|
||||
BLARGG_DEF_MIN_MAX( long )
|
||||
BLARGG_DEF_MIN_MAX( unsigned long )
|
||||
BLARGG_DEF_MIN_MAX( float )
|
||||
BLARGG_DEF_MIN_MAX( double )
|
||||
|
||||
#undef min
|
||||
#define min blargg_min
|
||||
|
||||
#undef max
|
||||
#define max blargg_max
|
||||
|
||||
// typedef unsigned char byte;
|
||||
typedef unsigned char blargg_byte;
|
||||
#undef byte
|
||||
#define byte blargg_byte
|
||||
|
||||
// Inform optimizer that if is unlikely to be taken
|
||||
// if UNLIKELY( condition )
|
||||
#ifdef __GNUC__
|
||||
#define UNLIKELY( b ) (__builtin_expect( b, 0 ))
|
||||
#else
|
||||
#define UNLIKELY( b ) (b)
|
||||
#endif
|
||||
|
||||
#define BLARGG_CLEAR( p ) \
|
||||
memset( (p), 0, sizeof *(p) )
|
||||
|
||||
#ifndef BLARGG_EXPORT
|
||||
#if defined (_WIN32) && BLARGG_BUILD_DLL
|
||||
#define BLARGG_EXPORT __declspec(dllexport)
|
||||
#elif defined (__GNUC__)
|
||||
// can always set visibility, even when not building DLL
|
||||
#define BLARGG_EXPORT __attribute__ ((visibility ("default")))
|
||||
#else
|
||||
#define BLARGG_EXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLARGG_LEGACY
|
||||
#define BLARGG_CHECK_ALLOC CHECK_ALLOC
|
||||
#define BLARGG_RETURN_ERR RETURN_ERR
|
||||
#endif
|
||||
|
||||
// Called after failed operation when overall operation may still complete OK.
|
||||
// Only used by unit testing framework.
|
||||
#undef ACK_FAILURE
|
||||
#define ACK_FAILURE() ((void)0)
|
||||
|
||||
/* BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf etc.
|
||||
and check */
|
||||
#ifdef BLARGG_SOURCE_BEGIN
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,79 @@
|
|||
// snes_spc 0.9.5. http://www.slack.net/~ant/
|
||||
|
||||
#include "spc.h"
|
||||
|
||||
#include "Snes_Spc.h"
|
||||
#include "Spc_Filter.h"
|
||||
|
||||
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
spc_t* spc_new( void )
|
||||
{
|
||||
// be sure constants match
|
||||
assert( spc_sample_rate == (int) spc_t::sample_rate );
|
||||
assert( spc_rom_size == (int) spc_t::rom_size );
|
||||
assert( spc_clock_rate == (int) spc_t::clock_rate );
|
||||
assert( spc_clocks_per_sample == (int) spc_t::clocks_per_sample );
|
||||
assert( spc_port_count == (int) spc_t::port_count );
|
||||
assert( spc_voice_count == (int) spc_t::voice_count );
|
||||
assert( spc_tempo_unit == (int) spc_t::tempo_unit );
|
||||
assert( spc_file_size == (int) spc_t::spc_file_size );
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
assert( spc_state_size == (int) spc_t::state_size );
|
||||
#endif
|
||||
|
||||
spc_t* s = new spc_t;
|
||||
if ( s && s->init() )
|
||||
{
|
||||
delete s;
|
||||
s = 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void spc_delete ( spc_t* s ) { delete s; }
|
||||
void spc_init_rom ( spc_t* s, unsigned char const r [64] ) { s->init_rom( r ); }
|
||||
int spc_read_samples ( spc_t* s, spc_sample_t out [], int n ) { return s->read_samples( out, n ); }
|
||||
int spc_samples_avail ( spc_t const* s ) { return s->samples_avail(); }
|
||||
void spc_reset ( spc_t* s ) { s->reset(); }
|
||||
void spc_soft_reset ( spc_t* s ) { s->soft_reset(); }
|
||||
int spc_read_port ( spc_t* s, spc_time_t t, int p ) { return s->read_port( t, p ); }
|
||||
void spc_write_port ( spc_t* s, spc_time_t t, int p, int d ) { s->write_port( t, p, d ); }
|
||||
void spc_end_frame ( spc_t* s, spc_time_t t ) { s->end_frame( t ); }
|
||||
void spc_mute_voices ( spc_t* s, int mask ) { s->mute_voices( mask ); }
|
||||
void spc_disable_surround( spc_t* s, int disable ) { s->disable_surround( disable ); }
|
||||
void spc_set_tempo ( spc_t* s, int tempo ) { s->set_tempo( tempo ); }
|
||||
spc_err_t spc_load_spc ( spc_t* s, void const* p, long n ) { return s->load_spc( p, n ); }
|
||||
void spc_clear_echo ( spc_t* s ) { s->clear_echo(); }
|
||||
spc_err_t spc_play ( spc_t* s, int count, short* out ) { return s->play( count, out ); }
|
||||
spc_err_t spc_skip ( spc_t* s, int count ) { return s->skip( count ); }
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
void spc_copy_state ( spc_t* s, unsigned char** p, spc_copy_func_t f ) { s->copy_state( p, f ); }
|
||||
void spc_init_header ( void* spc_out ) { spc_t::init_header( spc_out ); }
|
||||
void spc_save_spc ( spc_t* s, void* spc_out ) { s->save_spc( spc_out ); }
|
||||
int spc_check_kon ( spc_t* s ) { return s->check_kon(); }
|
||||
#endif
|
||||
|
||||
#if BLARGG_LEGACY
|
||||
void spc_set_output ( spc_t* s, spc_sample_t* p, int n ) { s->set_output( p, n ); }
|
||||
int spc_sample_count ( spc_t const* s ) { return s->sample_count(); }
|
||||
#endif
|
||||
|
||||
SPC_Filter* spc_filter_new( void ) { return new SPC_Filter; }
|
||||
void spc_filter_delete( SPC_Filter* f ) { delete f; }
|
||||
void spc_filter_run( SPC_Filter* f, spc_sample_t* p, int s ) { f->run( p, s ); }
|
||||
void spc_filter_clear( SPC_Filter* f ) { f->clear(); }
|
||||
void spc_filter_set_gain( SPC_Filter* f, int gain ) { f->set_gain( gain ); }
|
||||
void spc_filter_set_bass( SPC_Filter* f, int bass ) { f->set_bass( bass ); }
|
|
@ -0,0 +1,196 @@
|
|||
/** SNES SPC-700 APU emulator and SPC music file player \file */
|
||||
|
||||
/* snes_spc 0.9.5 */
|
||||
#ifndef SPC_H
|
||||
#define SPC_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/** First parameter of most functions is spc_t*, or const spc_t* if nothing is
|
||||
changed. Once one of these functions returns an error, the object should not
|
||||
be used any further, other than to delete it. */
|
||||
typedef struct spc_t spc_t;
|
||||
|
||||
/** Pointer to error, or NULL if function was successful. See error functions
|
||||
below. */
|
||||
#ifndef spc_err_t /* (#ifndef allows better testing of library) */
|
||||
typedef const char* spc_err_t;
|
||||
#endif
|
||||
|
||||
enum { /** Sample pairs generated per second */
|
||||
spc_sample_rate = 32000 };
|
||||
|
||||
|
||||
/**** Creation/deletion ****/
|
||||
|
||||
/** Creates new SPC emulator. NULL if out of memory. */
|
||||
spc_t* spc_new( void );
|
||||
|
||||
/** Frees SPC emulator. OK to pass NULL. */
|
||||
void spc_delete( spc_t* );
|
||||
|
||||
|
||||
/**** SPC music file playback *****/
|
||||
|
||||
/** Loads SPC data into emulator */
|
||||
spc_err_t spc_load_spc( spc_t*, void const* spc_in, long size );
|
||||
|
||||
/** Clears echo region. Useful after loading an SPC as many have garbage in echo. */
|
||||
void spc_clear_echo( spc_t* );
|
||||
|
||||
/** Plays for count samples and writes samples to out. Discards samples if out
|
||||
is NULL. Count must be a multiple of 2 since output is stereo. */
|
||||
spc_err_t spc_play( spc_t*, int count, short* out );
|
||||
|
||||
/** Skips count samples. Several times faster than spc_play(). */
|
||||
spc_err_t spc_skip( spc_t*, int count );
|
||||
|
||||
|
||||
/**** Sound options ****/
|
||||
|
||||
/** Mutes voices corresponding to non-zero bits in mask. */
|
||||
void spc_mute_voices( spc_t*, int mask );
|
||||
enum { spc_voice_count = 8 };
|
||||
|
||||
/** If true, prevents channels and global volumes from being phase-negated.
|
||||
Some SPC music does this to achieve a pseudo-surround-sound effect that is
|
||||
unpleasant to listen to through headphones. */
|
||||
void spc_disable_surround( spc_t*, int disable );
|
||||
|
||||
/** Sets music tempo, where spc_tempo_unit = normal,
|
||||
spc_tempo_unit / 2 = half speed, etc. */
|
||||
void spc_set_tempo( spc_t*, int );
|
||||
enum { spc_tempo_unit = 0x100 };
|
||||
|
||||
|
||||
/**** Emulator use ****/
|
||||
|
||||
/** Clock count relative to current time frame */
|
||||
typedef int spc_time_t;
|
||||
|
||||
enum { /** Number of clocks per second */
|
||||
spc_clock_rate = 1024000 };
|
||||
|
||||
enum { /** One sample pair is generated after this many clocks */
|
||||
spc_clocks_per_sample = 32 };
|
||||
|
||||
enum { spc_rom_size = 0x40 };
|
||||
|
||||
/** Sets IPL ROM data. Library does not include ROM data. Most SPC music files
|
||||
don't need ROM, but a full emulator must provide this. */
|
||||
void spc_init_rom( spc_t*, const unsigned char rom [spc_rom_size] );
|
||||
|
||||
/** Resets SPC-700 to power-on state. */
|
||||
void spc_reset( spc_t* );
|
||||
|
||||
/** Emulates pressing reset switch on SNES. */
|
||||
void spc_soft_reset( spc_t* );
|
||||
|
||||
enum { spc_port_count = 4 /**< Number of ports, indexed from 0 */ };
|
||||
|
||||
/** Reads from port at specified time */
|
||||
int spc_read_port ( spc_t*, spc_time_t, int port );
|
||||
|
||||
/** Writes to port at specified time */
|
||||
void spc_write_port( spc_t*, spc_time_t, int port, int data );
|
||||
|
||||
/** Emulates to end_time, starts a new time frame at 0, and makes all resulting
|
||||
samples available. */
|
||||
void spc_end_frame( spc_t*, spc_time_t end_time );
|
||||
|
||||
/** Number of samples available for reading. Always even. */
|
||||
int spc_samples_avail( const spc_t* );
|
||||
|
||||
/** 16-bit signed sample */
|
||||
typedef short spc_sample_t;
|
||||
|
||||
/** Writes at most count samples to out, removes them from internal buffer,
|
||||
and returns number of samples actually written. Output is in stereo, so
|
||||
count must be even. */
|
||||
int spc_read_samples( spc_t*, spc_sample_t out [], int count );
|
||||
|
||||
|
||||
/**** State save/load ****/
|
||||
|
||||
/** Not available when using fast DSP */
|
||||
|
||||
/** Callback used for state save/load. Called with io value passed to
|
||||
spc_copy_state(). State points to size bytes of data to save or load,
|
||||
as desired. */
|
||||
typedef void (*spc_copy_func_t)( unsigned char** io, void* state, size_t size );
|
||||
|
||||
/** Saves/loads exact emulator state, using callback to save/load data.
|
||||
The callback determines which action occurs. */
|
||||
void spc_copy_state( spc_t*, unsigned char** io, spc_copy_func_t );
|
||||
enum { spc_state_size = 67 * 1024 /**< maximum space needed when saving */ };
|
||||
|
||||
/** Writes minimal SPC file header to spc_out */
|
||||
void spc_init_header( void* spc_out );
|
||||
|
||||
/** Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out.
|
||||
Does not set up SPC header; use spc_init_header() for that. */
|
||||
void spc_save_spc( spc_t*, void* spc_out );
|
||||
enum { spc_file_size = 0x10200 /**> spc_out must have this many bytes allocated */ };
|
||||
|
||||
/** Returns non-zero if new key-on events occurred since last check. Useful for
|
||||
trimming silence while saving an SPC. */
|
||||
int spc_check_kon( spc_t* );
|
||||
|
||||
|
||||
/**** Sound filter ****/
|
||||
|
||||
/** Simple filter to more closely match sound output of SNES. Applies slight
|
||||
high-pass and low-pass filtering. */
|
||||
|
||||
/** Filter functions take a pointer to this */
|
||||
typedef struct Spc_Filter Spc_Filter;
|
||||
|
||||
/** Creates new filter. NULL if out of memory. */
|
||||
Spc_Filter* spc_filter_new( void );
|
||||
|
||||
/** Frees filter. OK to pass NULL. */
|
||||
void spc_filter_delete( Spc_Filter* );
|
||||
|
||||
/** Filters count samples of stereo sound in place. Count must be a multiple of 2. */
|
||||
void spc_filter_run( Spc_Filter*, spc_sample_t io [], int count );
|
||||
|
||||
/** Clears filter to silence */
|
||||
void spc_filter_clear( Spc_Filter* );
|
||||
|
||||
/** Sets gain (volume), where spc_filter_gain_unit is normal. Gains greater than
|
||||
spc_filter_gain_unit are fine, since output is clamped to 16-bit sample range. */
|
||||
void spc_filter_set_gain( Spc_Filter*, int gain );
|
||||
enum { spc_filter_gain_unit = 0x100 };
|
||||
|
||||
/** Sets amount of bass (logarithmic scale) */
|
||||
void spc_filter_set_bass( Spc_Filter*, int bass );
|
||||
enum {
|
||||
spc_filter_bass_none = 0,
|
||||
spc_filter_bass_norm = 8, /**< normal amount of bass */
|
||||
spc_filter_bass_max = 31
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**** Deprecated ****/
|
||||
|
||||
/* Provided for compatibility with old code. May be removed from future release. */
|
||||
|
||||
typedef spc_t Snes_Spc;
|
||||
typedef struct Spc_Filter SPC_Filter;
|
||||
|
||||
void spc_set_output( spc_t*, spc_sample_t* out, int out_size );
|
||||
|
||||
int spc_sample_count( const spc_t* );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,64 +0,0 @@
|
|||
#ifdef SMP_CPP
|
||||
|
||||
void SMP::add_clocks(unsigned clocks) {
|
||||
step(clocks);
|
||||
#if !defined(DSP_STATE_MACHINE)
|
||||
synchronize_dsp();
|
||||
#else
|
||||
while(dsp.clock < 0) dsp.enter();
|
||||
#endif
|
||||
|
||||
//forcefully sync S-SMP to S-CPU in case chips are not communicating
|
||||
//sync if S-SMP is more than 24 samples ahead of S-CPU
|
||||
if(clock > +(768 * 24 * (int64)24000000)) synchronize_cpu();
|
||||
}
|
||||
|
||||
void SMP::cycle_edge() {
|
||||
t0.tick();
|
||||
t1.tick();
|
||||
t2.tick();
|
||||
|
||||
//TEST register S-SMP speed control
|
||||
//24 clocks have already been added for this cycle at this point
|
||||
switch(status.clock_speed) {
|
||||
case 0: break; //100% speed
|
||||
case 1: add_clocks(24); break; // 50% speed
|
||||
case 2: while(true) add_clocks(24); // 0% speed -- locks S-SMP
|
||||
case 3: add_clocks(24 * 9); break; // 10% speed
|
||||
}
|
||||
}
|
||||
|
||||
template<unsigned timer_frequency>
|
||||
void SMP::sSMPTimer<timer_frequency>::tick() {
|
||||
//stage 0 increment
|
||||
stage0_ticks += smp.status.timer_step;
|
||||
if(stage0_ticks < timer_frequency) return;
|
||||
stage0_ticks -= timer_frequency;
|
||||
|
||||
//stage 1 increment
|
||||
stage1_ticks ^= 1;
|
||||
sync_stage1();
|
||||
}
|
||||
|
||||
template<unsigned frequency>
|
||||
void SMP::sSMPTimer<frequency>::sync_stage1() {
|
||||
bool new_line = stage1_ticks;
|
||||
if(smp.status.timers_enabled == false) new_line = false;
|
||||
if(smp.status.timers_disabled == true) new_line = false;
|
||||
|
||||
bool old_line = current_line;
|
||||
current_line = new_line;
|
||||
if(old_line != 1 || new_line != 0) return; //only pulse on 1->0 transition
|
||||
|
||||
//stage 2 increment
|
||||
if(enabled == false) return;
|
||||
stage2_ticks++;
|
||||
if(stage2_ticks != target) return;
|
||||
|
||||
//stage 3 increment
|
||||
stage2_ticks = 0;
|
||||
stage3_ticks++;
|
||||
stage3_ticks &= 15;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,21 +0,0 @@
|
|||
template<unsigned timer_frequency>
|
||||
class sSMPTimer {
|
||||
public:
|
||||
uint8 stage0_ticks;
|
||||
uint8 stage1_ticks;
|
||||
uint8 stage2_ticks;
|
||||
uint8 stage3_ticks;
|
||||
bool current_line;
|
||||
bool enabled;
|
||||
uint8 target;
|
||||
|
||||
void tick();
|
||||
void sync_stage1();
|
||||
};
|
||||
|
||||
sSMPTimer<192> t0;
|
||||
sSMPTimer<192> t1;
|
||||
sSMPTimer< 24> t2;
|
||||
|
||||
alwaysinline void add_clocks(unsigned clocks);
|
||||
alwaysinline void cycle_edge();
|
|
@ -10,7 +10,7 @@ synchronize "cartridge"
|
|||
synchronize "cheat"
|
||||
synchronize "chip"
|
||||
synchronize "config"
|
||||
synchronize "cpu"
|
||||
synchronize "cpu/core"
|
||||
synchronize "debugger"
|
||||
synchronize "input"
|
||||
synchronize "interface"
|
||||
|
@ -18,7 +18,7 @@ synchronize "libsnes"
|
|||
synchronize "memory"
|
||||
synchronize "ppu/counter"
|
||||
synchronize "scheduler"
|
||||
synchronize "smp"
|
||||
synchronize "smp/core"
|
||||
synchronize "system"
|
||||
synchronize "video"
|
||||
synchronize "snes.hpp"
|
||||
|
|
|
@ -10,12 +10,12 @@ void Interface::video_refresh(const uint16_t *data, unsigned width, unsigned hei
|
|||
//NTSC
|
||||
height = 224;
|
||||
if(interlace) height <<= 1;
|
||||
if(overscan) data += 8 * 1024;
|
||||
if(overscan) data += 7 * 1024;
|
||||
} else {
|
||||
//PAL
|
||||
height = 239;
|
||||
if(interlace) height <<= 1;
|
||||
if(!overscan) data -= 8 * 1024;
|
||||
if(!overscan) data -= 7 * 1024;
|
||||
}
|
||||
|
||||
//scale display.crop* values from percentage-based (0-100%) to exact pixel sizes (width, height)
|
||||
|
|
Loading…
Reference in New Issue