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:
Tim Allen 2010-08-11 10:40:59 +10:00
parent 973ef89d4a
commit 9000bb4084
60 changed files with 6197 additions and 2670 deletions

View File

@ -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

View File

@ -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/)

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
//???

View File

@ -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);

View File

@ -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 };
}

View File

@ -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;

View File

@ -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

View File

@ -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);
};

184
bsnes/cpu/dma.cpp Executable file
View File

@ -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

View File

@ -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

View File

@ -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();

48
bsnes/cpu/memory.cpp Executable file
View File

@ -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

View File

@ -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

View File

@ -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;

304
bsnes/cpu/mmio.cpp Executable file
View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

122
bsnes/cpu/timing.cpp Executable file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -55,7 +55,6 @@ public:
void update_oam_status();
//required functions
void run();
void scanline();
void render_scanline();
void frame();

View File

@ -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

View File

@ -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();
};

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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() {

View File

@ -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;

240
bsnes/smp/snes_spc/Snes_Spc.cpp Executable file
View File

@ -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 );
}

141
bsnes/smp/snes_spc/Snes_Spc.h Executable file
View File

@ -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

256
bsnes/smp/snes_spc/Spc_Core.cpp Executable file
View File

@ -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( &regs );
BLARGG_CLEAR( &regs_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;
}
}

239
bsnes/smp/snes_spc/Spc_Core.h Executable file
View File

@ -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

View File

@ -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 );
}

1119
bsnes/smp/snes_spc/Spc_Cpu_run.h Executable file

File diff suppressed because it is too large Load Diff

897
bsnes/smp/snes_spc/Spc_Dsp.cpp Executable file
View File

@ -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 = &regs [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 );
}

319
bsnes/smp/snes_spc/Spc_Dsp.h Executable file
View File

@ -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

View File

@ -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

View File

@ -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;
}
}
}

51
bsnes/smp/snes_spc/Spc_Filter.h Executable file
View File

@ -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

134
bsnes/smp/snes_spc/Spc_State.cpp Executable file
View File

@ -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, &regs [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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

79
bsnes/smp/snes_spc/spc.cpp Executable file
View File

@ -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 ); }

196
bsnes/smp/snes_spc/spc.h Executable file
View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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"

View File

@ -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)