diff --git a/Makefile b/Makefile index 4961d50d..fb280324 100755 --- a/Makefile +++ b/Makefile @@ -60,14 +60,14 @@ endif install: ifeq ($(platform),x) - install -D -m 755 ../bsnes $(DESTDIR)$(prefix)/bin/bsnes - install -D -m 644 data/bsnes.png $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png - install -D -m 644 data/bsnes.desktop $(DESTDIR)$(prefix)/share/applications/bsnes.desktop + install -D -m 755 out/$(snes) $(DESTDIR)$(prefix)/bin/$(snes) + install -D -m 644 qt/data/bsnes.png $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png + install -D -m 644 qt/data/bsnes.desktop $(DESTDIR)$(prefix)/share/applications/bsnes.desktop endif uninstall: ifeq ($(platform),x) - rm $(DESTDIR)$(prefix)/bin/bsnes + rm $(DESTDIR)$(prefix)/bin/$(snes) rm $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png rm $(DESTDIR)$(prefix)/share/applications/bsnes.desktop endif diff --git a/asnes/Makefile b/asnes/Makefile index d3517300..31eff193 100755 --- a/asnes/Makefile +++ b/asnes/Makefile @@ -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/) diff --git a/asnes/cpu/dma/dma.cpp b/asnes/cpu/dma/dma.cpp index 9aad43f2..1e141ba8 100755 --- a/asnes/cpu/dma/dma.cpp +++ b/asnes/cpu/dma/dma.cpp @@ -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; diff --git a/asnes/cpu/dma/dma.hpp b/asnes/cpu/dma/dma.hpp index f82d44ec..1eb07d59 100755 --- a/asnes/cpu/dma/dma.hpp +++ b/asnes/cpu/dma/dma.hpp @@ -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; diff --git a/asnes/cpu/mmio/mmio.cpp b/asnes/cpu/mmio/mmio.cpp index 10df666f..9448c288 100755 --- a/asnes/cpu/mmio/mmio.cpp +++ b/asnes/cpu/mmio/mmio.cpp @@ -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; } //??? diff --git a/asnes/cpu/serialization.cpp b/asnes/cpu/serialization.cpp index 5f739ea4..ac3ceb6d 100755 --- a/asnes/cpu/serialization.cpp +++ b/asnes/cpu/serialization.cpp @@ -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); diff --git a/bsnes/cpu/cpu.cpp b/bsnes/cpu/cpu.cpp index 378e0a34..481b5dca 100755 --- a/bsnes/cpu/cpu.cpp +++ b/bsnes/cpu/cpu.cpp @@ -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 }; } diff --git a/bsnes/cpu/cpu.hpp b/bsnes/cpu/cpu.hpp index 60ef1af8..9a995cc0 100755 --- a/bsnes/cpu/cpu.hpp +++ b/bsnes/cpu/cpu.hpp @@ -1,3 +1,5 @@ +#include + class CPU : public Processor, public CPUcore, public PPUcounter, public MMIO { public: array 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 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; diff --git a/bsnes/cpu/debugger/debugger.cpp b/bsnes/cpu/debugger/debugger.cpp deleted file mode 100755 index bb014df7..00000000 --- a/bsnes/cpu/debugger/debugger.cpp +++ /dev/null @@ -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 diff --git a/bsnes/cpu/debugger/debugger.hpp b/bsnes/cpu/debugger/debugger.hpp deleted file mode 100755 index 19d060d7..00000000 --- a/bsnes/cpu/debugger/debugger.hpp +++ /dev/null @@ -1,96 +0,0 @@ -class CPUDebugger : public CPU, public ChipDebugger { -public: - bool property(unsigned id, string &name, string &value); - - function 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); -}; diff --git a/bsnes/cpu/dma.cpp b/bsnes/cpu/dma.cpp new file mode 100755 index 00000000..404f880b --- /dev/null +++ b/bsnes/cpu/dma.cpp @@ -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 diff --git a/bsnes/cpu/dma/dma.cpp b/bsnes/cpu/dma/dma.cpp deleted file mode 100755 index 9aad43f2..00000000 --- a/bsnes/cpu/dma/dma.cpp +++ /dev/null @@ -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 diff --git a/bsnes/cpu/dma/dma.hpp b/bsnes/cpu/dma/dma.hpp deleted file mode 100755 index f82d44ec..00000000 --- a/bsnes/cpu/dma/dma.hpp +++ /dev/null @@ -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(); diff --git a/bsnes/cpu/memory.cpp b/bsnes/cpu/memory.cpp new file mode 100755 index 00000000..fc399624 --- /dev/null +++ b/bsnes/cpu/memory.cpp @@ -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 diff --git a/bsnes/cpu/memory/memory.cpp b/bsnes/cpu/memory/memory.cpp deleted file mode 100755 index bf48dee5..00000000 --- a/bsnes/cpu/memory/memory.cpp +++ /dev/null @@ -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 diff --git a/bsnes/cpu/memory/memory.hpp b/bsnes/cpu/memory/memory.hpp deleted file mode 100755 index 2ee2b360..00000000 --- a/bsnes/cpu/memory/memory.hpp +++ /dev/null @@ -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; diff --git a/bsnes/cpu/mmio.cpp b/bsnes/cpu/mmio.cpp new file mode 100755 index 00000000..49598fe2 --- /dev/null +++ b/bsnes/cpu/mmio.cpp @@ -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 diff --git a/bsnes/cpu/mmio/mmio.cpp b/bsnes/cpu/mmio/mmio.cpp deleted file mode 100755 index 10df666f..00000000 --- a/bsnes/cpu/mmio/mmio.cpp +++ /dev/null @@ -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 diff --git a/bsnes/cpu/mmio/mmio.hpp b/bsnes/cpu/mmio/mmio.hpp deleted file mode 100755 index 06351e4f..00000000 --- a/bsnes/cpu/mmio/mmio.hpp +++ /dev/null @@ -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); diff --git a/bsnes/cpu/serialization.cpp b/bsnes/cpu/serialization.cpp deleted file mode 100755 index 5f739ea4..00000000 --- a/bsnes/cpu/serialization.cpp +++ /dev/null @@ -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 diff --git a/bsnes/cpu/timing.cpp b/bsnes/cpu/timing.cpp new file mode 100755 index 00000000..95ed6d8e --- /dev/null +++ b/bsnes/cpu/timing.cpp @@ -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 diff --git a/bsnes/cpu/timing/irq.cpp b/bsnes/cpu/timing/irq.cpp deleted file mode 100755 index 506a435e..00000000 --- a/bsnes/cpu/timing/irq.cpp +++ /dev/null @@ -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 diff --git a/bsnes/cpu/timing/joypad.cpp b/bsnes/cpu/timing/joypad.cpp deleted file mode 100755 index d00cdccb..00000000 --- a/bsnes/cpu/timing/joypad.cpp +++ /dev/null @@ -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 diff --git a/bsnes/cpu/timing/timing.cpp b/bsnes/cpu/timing/timing.cpp deleted file mode 100755 index 39dd3a3a..00000000 --- a/bsnes/cpu/timing/timing.cpp +++ /dev/null @@ -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 diff --git a/bsnes/cpu/timing/timing.hpp b/bsnes/cpu/timing/timing.hpp deleted file mode 100755 index c185ea4c..00000000 --- a/bsnes/cpu/timing/timing.hpp +++ /dev/null @@ -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(); diff --git a/bsnes/info.hpp b/bsnes/info.hpp index 242097a5..bed8783b 100755 --- a/bsnes/info.hpp +++ b/bsnes/info.hpp @@ -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; } } diff --git a/bsnes/ppu/ppu.cpp b/bsnes/ppu/ppu.cpp index 8f8632a2..adacaf5a 100755 --- a/bsnes/ppu/ppu.cpp +++ b/bsnes/ppu/ppu.cpp @@ -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 + } } diff --git a/bsnes/ppu/ppu.hpp b/bsnes/ppu/ppu.hpp index 76ad7939..ed73e868 100755 --- a/bsnes/ppu/ppu.hpp +++ b/bsnes/ppu/ppu.hpp @@ -55,7 +55,6 @@ public: void update_oam_status(); //required functions - void run(); void scanline(); void render_scanline(); void frame(); diff --git a/bsnes/smp/debugger/debugger.cpp b/bsnes/smp/debugger/debugger.cpp deleted file mode 100755 index 0d700634..00000000 --- a/bsnes/smp/debugger/debugger.cpp +++ /dev/null @@ -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 diff --git a/bsnes/smp/debugger/debugger.hpp b/bsnes/smp/debugger/debugger.hpp deleted file mode 100755 index 70211d89..00000000 --- a/bsnes/smp/debugger/debugger.hpp +++ /dev/null @@ -1,38 +0,0 @@ -class SMPDebugger : public SMP, public ChipDebugger { -public: - bool property(unsigned id, string &name, string &value); - - function 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(); -}; diff --git a/bsnes/smp/memory/memory.cpp b/bsnes/smp/memory/memory.cpp deleted file mode 100755 index 2f5081ce..00000000 --- a/bsnes/smp/memory/memory.cpp +++ /dev/null @@ -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 diff --git a/bsnes/smp/memory/memory.hpp b/bsnes/smp/memory/memory.hpp deleted file mode 100755 index 1a07445d..00000000 --- a/bsnes/smp/memory/memory.hpp +++ /dev/null @@ -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); diff --git a/bsnes/smp/serialization.cpp b/bsnes/smp/serialization.cpp index 334d15ba..be2b992a 100755 --- a/bsnes/smp/serialization.cpp +++ b/bsnes/smp/serialization.cpp @@ -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 diff --git a/bsnes/smp/smp.cpp b/bsnes/smp/smp.cpp index 5027dea0..85f57584 100755 --- a/bsnes/smp/smp.cpp +++ b/bsnes/smp/smp.cpp @@ -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() { diff --git a/bsnes/smp/smp.hpp b/bsnes/smp/smp.hpp index b8d0bc6b..29d66e2f 100755 --- a/bsnes/smp/smp.hpp +++ b/bsnes/smp/smp.hpp @@ -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; diff --git a/bsnes/smp/snes_spc/Snes_Spc.cpp b/bsnes/smp/snes_spc/Snes_Spc.cpp new file mode 100755 index 00000000..50c8d1a4 --- /dev/null +++ b/bsnes/smp/snes_spc/Snes_Spc.cpp @@ -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 ); +} diff --git a/bsnes/smp/snes_spc/Snes_Spc.h b/bsnes/smp/snes_spc/Snes_Spc.h new file mode 100755 index 00000000..fb19c417 --- /dev/null +++ b/bsnes/smp/snes_spc/Snes_Spc.h @@ -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 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 diff --git a/bsnes/smp/snes_spc/Spc_Core.cpp b/bsnes/smp/snes_spc/Spc_Core.cpp new file mode 100755 index 00000000..268203b9 --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Core.cpp @@ -0,0 +1,256 @@ +// snes_spc 0.9.5. http://www.slack.net/~ant/ + +#include "Spc_Core.h" + +/* Copyright (C) 2004-2010 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Spc_Core::set_tempo( int t ) +{ + #if !SPC_DISABLE_TEMPO + tempo = t; + + if ( !t ) + t = 1; + + int const timer2_rate = 1 << timer2_shift; + int rate = (timer2_rate * tempo_unit + (t >> 1)) / t; + if ( rate < timer2_rate / 4 ) + rate = timer2_rate / 4; // max 4x tempo + + prescaler_base = rate; + #endif +} + +blargg_err_t Spc_Core::init() +{ + BLARGG_CLEAR( ®s ); + BLARGG_CLEAR( ®s_in ); + + // Most SPC music doesn't need ROM, and almost all the rest only rely + // on these two bytes + BLARGG_CLEAR( &rom_ ); + rom_ [0x3E] = 0xFF; + rom_ [0x3F] = 0xC0; + + set_tempo( tempo_unit ); + dsp_.init( ram() ); + reset(); + + return blargg_ok; +} + +void Spc_Core::init_rom( byte const in [rom_size] ) +{ + memcpy( rom_, in, sizeof rom_ ); +} + +void Spc_Core::enable_rom( bool enable ) +{ + if ( rom_enabled != enable ) + { + rom_enabled = enable; + if ( enable ) + memcpy( hi_ram, &ram() [rom_addr], sizeof hi_ram ); + + memcpy( &ram() [rom_addr], (enable ? rom_ : hi_ram), rom_size ); + // TODO: ROM can still get overwritten when DSP writes to echo buffer + } +} + +void Spc_Core::ram_loaded() +{ + // ROM isn't swapped in anymore, since its area was overwritten with + // contents of RAM there + rom_enabled = false; + + // Put STOP instruction around memory to catch PC underflow/overflow + memset( padded_ram, padding_fill, ram_padding ); + memset( padded_ram + ram_padding + ram_size, padding_fill, ram_padding ); +} + +void Spc_Core::load_regs( byte const new_regs [reg_count], byte const ports [port_count] ) +{ + memcpy( regs, new_regs, reg_count ); + memcpy( regs_in, regs, reg_count ); + + // These always read back as 0 or are handled specially + // Commented registers are already copied from regs. + regs_in [r_test ] = 0; + regs_in [r_control ] = 0; + // r_dspaddr + regs_in [r_dspdata ] = 0xFF; // in case attempt is made to execute value + // r_cpuio0 + // r_cpuio1 + // r_cpuio2 + // r_cpuio3 + // 8 + // 9 + regs_in [r_t0target] = 0; + regs_in [r_t1target] = 0; + regs_in [r_t2target] = 0; + regs_in [r_t0out ] &= 0x0F; + regs_in [r_t1out ] &= 0x0F; + regs_in [r_t2out ] &= 0x0F; + + // RAM should always hold copy of regs_in + memcpy( ram() + regs_addr, regs_in, reg_count ); + + enable_rom( regs [r_control] & 0x80 ); + + for ( int i = 0; i < port_count; i++ ) + regs [r_cpuio0 + i] = ports [i]; +} + +void Spc_Core::reset_common( int timer_counter_init ) +{ + memset( &cpu_regs, 0, sizeof cpu_regs ); + cpu_regs.pc = rom_addr; + + cpu_error = NULL; + echo_accessed = 0; + spc_time = 0; + dsp_time = 0; + skipped_kon = 0; + skipped_koff = 0; + + #if SPC_LESS_ACCURATE + dsp_time = clocks_per_sample + 1; + #endif + + int i; + for ( i = 0; i < timer_count; i++ ) + { + timers [i].time = 1; + timers [i].divider = 0; + } + + byte new_regs [reg_count] = { }; + + new_regs [r_test ] = 0x0A; + new_regs [r_control] = 0x80; // ROM enabled + + for ( i = 0; i < timer_count; i++ ) + new_regs [r_t0out + i] = timer_counter_init; + + static byte const new_ports [port_count] = { }; + + load_regs( new_regs, new_ports ); +} + +void Spc_Core::soft_reset() +{ + reset_common( 0 ); + + dsp_.soft_reset(); +} + +void Spc_Core::reset() +{ + memset( ram(), 0xFF, 0x10000 ); + ram_loaded(); + + reset_common( 0x0F ); + + dsp_.reset(); +} + +const char* Spc_Core::error() +{ + const char* e = cpu_error; + cpu_error = NULL; + return e; +} + +bool Spc_Core::check_echo_access( int addr ) +{ + if ( !(dsp_.read( dsp_.r_flg ) & 0x20) ) + { + int start = 0x100 * dsp_.read( dsp_.r_esa ); + int size = 0x800 * (dsp_.read( dsp_.r_edl ) & 0x0F); + int end = start + (size ? size : 4); + if ( start <= addr && addr < end ) + { + if ( !echo_accessed ) + { + echo_accessed = true; + return true; + } + } + } + return false; +} + +// (n ? n : 256) & 0xFF +inline int if_0_then_256( int n ) +{ + return byte (n - 1) + 1; +} + +void Spc_Core::run_timer_( rel_time_t time, int index ) +{ + Timer& t = timers [index]; + check( time >= t.time ); + + int elapsed; // number of ticks, guaranteed at least 1 + int const timer01_shift = 3; + + #if SPC_DISABLE_TEMPO + { + int shift = timer2_shift; + if ( index < 2 ) + shift += timer01_shift; + + elapsed = ((time - t.time) >> shift) + 1; + t.time += elapsed << shift; + } + #else + { + int prescaler = prescaler_base; + if ( index < 2 ) + prescaler <<= timer01_shift; + + elapsed = (time - t.time) / prescaler + 1; + t.time += elapsed * prescaler; + } + #endif + + if ( regs [r_control] >> index & 1 ) + { + int period = if_0_then_256( regs [r_t0target + index] ); + + // Ticks until divider will output tick. If divider currently matches + // period, it will be 256 ticks until output tick, not 0. + int remain = if_0_then_256( period - t.divider ); + + // value divider will take, assuming no tick + int divider = t.divider + elapsed; + + // time relative to when tick will occur + int over = elapsed - remain; + if ( over >= 0 ) + { + // ticks elapsed in second stage + int elapsed2 = over / period; + + regs_in [r_t0out + index] = + (regs_in [r_t0out + index] + 1 + elapsed2) & 0x0F; + + // remaining count on divider + divider = over - elapsed2 * period; + } + + // Must mask for cases when divider has gone past target + t.divider = divider & 0xFF; + } +} diff --git a/bsnes/smp/snes_spc/Spc_Core.h b/bsnes/smp/snes_spc/Spc_Core.h new file mode 100755 index 00000000..c3b5b27e --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Core.h @@ -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 diff --git a/bsnes/smp/snes_spc/Spc_Core_impl.cpp b/bsnes/smp/snes_spc/Spc_Core_impl.cpp new file mode 100755 index 00000000..05dd4a04 --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Core_impl.cpp @@ -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 ); +} diff --git a/bsnes/smp/snes_spc/Spc_Cpu_run.h b/bsnes/smp/snes_spc/Spc_Cpu_run.h new file mode 100755 index 00000000..f7fa8230 --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Cpu_run.h @@ -0,0 +1,1119 @@ +// S-SMP CPU instruction emulation + +// snes_spc 0.9.5. http://www.slack.net/~ant/ + +// Truncation +#undef BYTE +#define BYTE( n ) ((BOOST::uint8_t ) (n)) /* (unsigned) n & 0xFF */ + +//// Memory access + +#if SPC_MORE_ACCURACY + #define SUSPICIOUS_OPCODE( name ) ((void) 0) +#else + #define SUSPICIOUS_OPCODE( name ) dprintf( "SPC: suspicious opcode: %s\n", name ) +#endif + +#define TIME( delay ) (rel_time + (delay)) + +#define DP_ADDR( addr ) (dp + (addr)) + +#define READ_DP( delay, addr ) READ ( delay, DP_ADDR( addr ) ) +#define WRITE_DP( delay, addr, data ) WRITE( delay, DP_ADDR( addr ), data ) + +#define READ_PROG16( addr ) GET_LE16( ram + (addr) ) + +#define SET_PC( n ) (pc = ram + (n)) +#define GET_PC() (pc - ram) +#define READ_PC( pc ) (*(pc)) +#define READ_PC16( pc ) GET_LE16( pc ) + +#define SET_SP( v ) (sp = ram + 0x101 + (v)) +#define GET_SP() (sp - 0x101 - ram) + +#define PUSH16( data )\ +{\ + int addr = (sp -= 2) - ram;\ + if ( addr > 0x100 )\ + {\ + SET_LE16( sp, data );\ + }\ + else\ + {\ + ram [BYTE( addr ) + 0x100] = BYTE( data );\ + sp [1] = BYTE( data >> 8 );\ + sp += 0x100;\ + }\ +} + +#define PUSH( data )\ +{\ + *--sp = BYTE( data );\ + if ( sp - ram == 0x100 )\ + sp += 0x100;\ +} + +#define POP( out )\ +{\ + out = *sp++;\ + if ( sp - ram == 0x201 )\ + {\ + out = sp [-0x101];\ + sp -= 0x100;\ + }\ +} + +#define MEM_BIT( rel ) cpu_mem_bit( rel_time + rel, pc ) + +unsigned Spc_Core::cpu_mem_bit( rel_time_t rel_time, byte const* pc ) +{ + unsigned addr = READ_PC16( pc ); + unsigned t = READ( 0, addr & 0x1FFF ) >> (addr >> 13); + return t << 8 & 0x100; +} + +//// Status flag handling + +// Flags with hex value for clarity when used as mask. +// Stored in indicated variable during emulation. +int const n80 = 0x80; // nz +int const v40 = 0x40; // psw +int const p20 = 0x20; // dp +int const b10 = 0x10; // psw +int const h08 = 0x08; // psw +int const i04 = 0x04; // psw +int const z02 = 0x02; // nz +int const c01 = 0x01; // c + +int const nz_neg_mask = 0x880; // either bit set indicates N flag set + +#define GET_PSW( out )\ +{\ + out = psw & ~(n80 | p20 | z02 | c01);\ + out |= c >> 8 & c01;\ + out |= dp >> 3 & p20;\ + out |= ((nz >> 4) | nz) & n80;\ + if ( !BYTE( nz ) ) out |= z02;\ +} + +#define SET_PSW( in )\ +{\ + psw = in;\ + c = in << 8;\ + dp = in << 3 & 0x100;\ + nz = (in << 4 & 0x800) | (~in & z02);\ +} + +SPC_CPU_RUN_FUNC +{ + byte* const ram = this->ram(); + int a = BYTE( cpu_regs.a ); + int x = BYTE( cpu_regs.x ); + int y = BYTE( cpu_regs.y ); + byte const* pc; + byte* sp; + int psw; + int c; + int nz; + int dp; + + SET_PC( cpu_regs.pc & 0xFFFF ); + SET_SP( BYTE( cpu_regs.sp ) ); + SET_PSW( cpu_regs.psw ); + + goto next_instr; + + + // Main loop + +cbranch_taken_loop: + pc += *(BOOST::int8_t const*) pc; +inc_pc_loop: + pc++; +next_instr: +{ + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + static byte const cycle_table [256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 2,8,4,7,3,4,3,6,2,6,5,4,5,4,6,8,// 0 + 4,8,4,7,4,5,5,6,5,5,6,5,2,2,4,6,// 1 + 2,8,4,7,3,4,3,6,2,6,5,4,5,4,7,4,// 2 + 4,8,4,7,4,5,5,6,5,5,6,5,2,2,3,8,// 3 + 2,8,4,7,3,4,3,6,2,6,4,4,5,4,6,6,// 4 + 4,8,4,7,4,5,5,6,5,5,4,5,2,2,4,3,// 5 + 2,8,4,7,3,4,3,6,2,6,4,4,5,4,7,5,// 6 + 4,8,4,7,4,5,5,6,5,5,5,5,2,2,3,6,// 7 + 2,8,4,7,3,4,3,6,2,6,5,4,5,2,4,5,// 8 + 4,8,4,7,4,5,5,6,5,5,5,5,2,2,12,5,//9 + 3,8,4,7,3,4,3,6,2,6,4,4,5,2,4,4,// A + 4,8,4,7,4,5,5,6,5,5,5,5,2,2,3,4,// B + 3,8,4,7,4,5,4,7,2,5,6,4,5,2,4,9,// C + 4,8,4,7,5,6,6,7,4,5,5,5,2,2,8,3,// D + 2,8,4,7,3,4,3,6,2,4,5,3,4,3,4,0,// E + 4,8,4,7,4,5,5,6,3,4,5,4,2,2,6,0// F + }; + + unsigned opcode = *pc; + if ( rel_time >= 0 ) + goto stop; + + #ifdef CPU_INSTR_HOOK + CPU_INSTR_HOOK( GET_PC(), pc, a, x, y, GET_SP(), rel_time ); + #endif + + rel_time += cycle_table [opcode]; + + // TODO: if PC is at end of memory, this will get wrong operand (very obscure) + unsigned data = *++pc; + switch ( opcode ) + { + +// Common instructions + +#define BRANCH( cond )\ +{\ + pc++;\ + pc += (BOOST::int8_t) data;\ + if ( cond )\ + goto next_instr;\ + pc -= (BOOST::int8_t) data;\ + rel_time -= 2;\ + goto next_instr;\ +} + + case 0xF0: // BEQ + BRANCH( !BYTE( nz ) ) // 89% taken + + case 0xD0: // BNE + BRANCH( BYTE( nz ) ) + + case 0x3F:{// CALL + int old_addr = GET_PC() + 2; + SET_PC( READ_PC16( pc ) ); + PUSH16( old_addr ); + goto next_instr; + } + + case 0x6F:{// RET + int addr = sp - ram; + SET_PC( GET_LE16( sp ) ); + sp += 2; + if ( addr < 0x1FF ) + goto next_instr; + + SET_PC( sp [-0x101] * 0x100 + ram [BYTE( addr ) + 0x100] ); + sp -= 0x100; + goto next_instr; + } + + case 0xE4: // MOV a,dp + ++pc; + READ_TIMER( 0, DP_ADDR( data ), a = nz ); // 80% from timer + goto next_instr; + + case 0xFA:{// MOV dp,dp + int temp; + READ_TIMER( -2, DP_ADDR( data ), temp ); + data = temp + no_read_before_write ; + } + // fall through + case 0x8F:{// MOV dp,#imm + int temp = READ_PC( pc + 1 ); + pc += 2; + WRITE_DP_FAST( temp, data ); // 76% $F0-$FF + goto next_instr; + } + + case 0xC4: // MOV dp,a + ++pc; + WRITE_DP_FAST( data, a ); // 39% $F0-$FF + goto next_instr; + +#define CASE( n ) case n: + +// Define common address modes based on opcode for immediate mode. Execution +// ends with data set to the address of the operand. +#define ADDR_MODES_( op )\ + CASE( op - 0x02 ) /* (X) */\ + data = x + dp;\ + pc--;\ + goto end_##op;\ + CASE( op + 0x0F ) /* (dp)+Y */\ + data = READ_PROG16( data + dp ) + y;\ + goto end_##op;\ + CASE( op - 0x01 ) /* (dp+X) */\ + data = READ_PROG16( (BYTE( data + x ) ) + dp );\ + goto end_##op;\ + CASE( op + 0x0E ) /* abs+Y */\ + data += y;\ + goto abs_##op;\ + CASE( op + 0x0D ) /* abs+X */\ + data += x;\ + CASE( op - 0x03 ) /* abs */\ + abs_##op:\ + data += 0x100 * READ_PC( ++pc );\ + goto end_##op;\ + CASE( op + 0x0C ) /* dp+X */\ + data = BYTE( data + x ); + +#define ADDR_MODES_NO_DP( op )\ + ADDR_MODES_( op )\ + data += dp;\ + end_##op: + +#define ADDR_MODES( op )\ + ADDR_MODES_( op )\ + CASE( op - 0x04 ) /* dp */\ + data += dp;\ + end_##op: + +// 1. 8-bit Data Transmission Commands. Group I + + ADDR_MODES_NO_DP( 0xE8 ) // MOV A,addr + a = nz = READ( 0, data ); + goto inc_pc_loop; + + case 0xBF:{// MOV A,(X)+ + int temp = x + dp; + x = BYTE( x + 1 ); + a = nz = READ( -1, temp ); + goto next_instr; + } + + case 0xE8: // MOV A,imm + a = data; + nz = data; + goto inc_pc_loop; + + case 0xF9: // MOV X,dp+Y + data = BYTE( data + y ); + case 0xF8: // MOV X,dp + READ_TIMER( 0, DP_ADDR( data ), x = nz ); + goto inc_pc_loop; + + case 0xE9: // MOV X,abs + data = READ_PC16( pc ); + ++pc; + data = READ( 0, data ); + case 0xCD: // MOV X,imm + x = data; + nz = data; + goto inc_pc_loop; + + case 0xFB: // MOV Y,dp+X + data = BYTE( data + x ); + case 0xEB: // MOV Y,dp + pc++; + READ_TIMER( 0, DP_ADDR( data ), y = nz ); // 70% from timer + goto next_instr; + + case 0xEC:{// MOV Y,abs + int temp = READ_PC16( pc ); + pc += 2; + READ_TIMER( 0, temp, y = nz ); + goto next_instr; + } + + case 0x8D: // MOV Y,imm + y = data; + nz = data; + goto inc_pc_loop; + +// 2. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 2 + + ADDR_MODES_NO_DP( 0xC8 ) // MOV addr,A + WRITE( 0, data, a ); + goto inc_pc_loop; + + { + int temp; + case 0xCC: // MOV abs,Y + temp = y; + goto mov_abs_temp; + case 0xC9: // MOV abs,X + temp = x; + mov_abs_temp: + WRITE( 0, READ_PC16( pc ), temp ); + pc += 2; + goto next_instr; + } + + case 0xD9: // MOV dp+Y,X + data = BYTE( data + y ); + case 0xD8: // MOV dp,X + WRITE( 0, data + dp, x ); + goto inc_pc_loop; + + case 0xDB: // MOV dp+X,Y + data = BYTE( data + x ); + case 0xCB: // MOV dp,Y + WRITE( 0, data + dp, y ); + goto inc_pc_loop; + +// 3. 8-BIT DATA TRANSMISSIN COMMANDS, GROUP 3. + + case 0x7D: // MOV A,X + a = x; + nz = x; + goto next_instr; + + case 0xDD: // MOV A,Y + a = y; + nz = y; + goto next_instr; + + case 0x5D: // MOV X,A + x = a; + nz = a; + goto next_instr; + + case 0xFD: // MOV Y,A + y = a; + nz = a; + goto next_instr; + + case 0x9D: // MOV X,SP + x = nz = GET_SP(); + goto next_instr; + + case 0xBD: // MOV SP,X + SET_SP( x ); + goto next_instr; + + //case 0xC6: // MOV (X),A (handled by MOV addr,A in group 2) + + case 0xAF: // MOV (X)+,A + WRITE_DP( 0, x, a + no_read_before_write ); + x++; + goto next_instr; + +// 5. 8-BIT LOGIC OPERATION COMMANDS + +#define LOGICAL_OP( op, func )\ + ADDR_MODES( op ) /* addr */\ + data = READ( 0, data );\ + case op: /* imm */\ + nz = a func##= data;\ + goto inc_pc_loop;\ + { unsigned addr;\ + case op + 0x11: /* X,Y */\ + data = READ_DP( -2, y );\ + addr = x + dp;\ + goto addr_##op;\ + case op + 0x01: /* dp,dp */\ + data = READ_DP( -3, data );\ + case op + 0x10:{/*dp,imm*/\ + byte const* addr2 = pc + 1;\ + pc += 2;\ + addr = READ_PC( addr2 ) + dp;\ + }\ + addr_##op:\ + nz = data func READ( -1, addr );\ + WRITE( 0, addr, nz );\ + goto next_instr;\ + } + + LOGICAL_OP( 0x28, & ); // AND + + LOGICAL_OP( 0x08, | ); // OR + + LOGICAL_OP( 0x48, ^ ); // EOR + +// 4. 8-BIT ARITHMETIC OPERATION COMMANDS + + ADDR_MODES( 0x68 ) // CMP addr + data = READ( 0, data ); + case 0x68: // CMP imm + nz = a - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x79: // CMP (X),(Y) + data = READ_DP( -2, y ); + nz = READ_DP( -1, x ) - data; + c = ~nz; + nz &= 0xFF; + goto next_instr; + + case 0x69: // CMP dp,dp + data = READ_DP( -3, data ); + case 0x78: // CMP dp,imm + nz = READ_DP( -1, READ_PC( ++pc ) ) - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x3E: // CMP X,dp + data += dp; + goto cmp_x_addr; + case 0x1E: // CMP X,abs + data = READ_PC16( pc ); + pc++; + cmp_x_addr: + data = READ( 0, data ); + case 0xC8: // CMP X,imm + nz = x - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x7E: // CMP Y,dp + data += dp; + goto cmp_y_addr; + case 0x5E: // CMP Y,abs + data = READ_PC16( pc ); + pc++; + cmp_y_addr: + data = READ( 0, data ); + case 0xAD: // CMP Y,imm + nz = y - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + { + int addr; + case 0xB9: // SBC (x),(y) + case 0x99: // ADC (x),(y) + pc--; // compensate for inc later + data = READ_DP( -2, y ); + addr = x + dp; + goto adc_addr; + case 0xA9: // SBC dp,dp + case 0x89: // ADC dp,dp + data = READ_DP( -3, data ); + case 0xB8: // SBC dp,imm + case 0x98: // ADC dp,imm + addr = READ_PC( ++pc ) + dp; + adc_addr: + nz = READ( -1, addr ); + goto adc_data; + +// catch ADC and SBC together, then decode later based on operand +#undef CASE +#define CASE( n ) case n: case (n) + 0x20: + ADDR_MODES( 0x88 ) // ADC/SBC addr + data = READ( 0, data ); + case 0xA8: // SBC imm + case 0x88: // ADC imm + addr = -1; // A + nz = a; + adc_data: { + int flags; + if ( opcode >= 0xA0 ) // SBC + data ^= 0xFF; + + flags = data ^ nz; + nz += data + (c >> 8 & 1); + flags ^= nz; + + psw = (psw & ~(v40 | h08)) | + (flags >> 1 & h08) | + ((flags + 0x80) >> 2 & v40); + c = nz; + if ( addr < 0 ) + { + a = BYTE( nz ); + goto inc_pc_loop; + } + WRITE( 0, addr, /*(byte)*/ nz ); + goto inc_pc_loop; + } + + } + +// 6. ADDITION & SUBTRACTION COMMANDS + +#define INC_DEC_REG( reg, op )\ + nz = reg op;\ + reg = BYTE( nz );\ + goto next_instr; + + case 0xBC: INC_DEC_REG( a, + 1 ) // INC A + case 0x3D: INC_DEC_REG( x, + 1 ) // INC X + case 0xFC: INC_DEC_REG( y, + 1 ) // INC Y + + case 0x9C: INC_DEC_REG( a, - 1 ) // DEC A + case 0x1D: INC_DEC_REG( x, - 1 ) // DEC X + case 0xDC: INC_DEC_REG( y, - 1 ) // DEC Y + + case 0x9B: // DEC dp+X + case 0xBB: // INC dp+X + data = BYTE( data + x ); + case 0x8B: // DEC dp + case 0xAB: // INC dp + data += dp; + goto inc_abs; + case 0x8C: // DEC abs + case 0xAC: // INC abs + data = READ_PC16( pc ); + pc++; + inc_abs: + nz = (opcode >> 4 & 2) - 1; + nz += READ( -1, data ); + WRITE( 0, data, /*(byte)*/ nz ); + goto inc_pc_loop; + +// 7. SHIFT, ROTATION COMMANDS + + case 0x5C: // LSR A + c = 0; + case 0x7C:{// ROR A + nz = (c >> 1 & 0x80) | (a >> 1); + c = a << 8; + a = nz; + goto next_instr; + } + + case 0x1C: // ASL A + c = 0; + case 0x3C:{// ROL A + int temp = c >> 8 & 1; + c = a << 1; + nz = c | temp; + a = BYTE( nz ); + goto next_instr; + } + + case 0x0B: // ASL dp + c = 0; + data += dp; + goto rol_mem; + case 0x1B: // ASL dp+X + c = 0; + case 0x3B: // ROL dp+X + data = BYTE( data + x ); + case 0x2B: // ROL dp + data += dp; + goto rol_mem; + case 0x0C: // ASL abs + c = 0; + case 0x2C: // ROL abs + data = READ_PC16( pc ); + pc++; + rol_mem: + nz = c >> 8 & 1; + nz |= (c = READ( -1, data ) << 1); + WRITE( 0, data, /*(byte)*/ nz ); + goto inc_pc_loop; + + case 0x4B: // LSR dp + c = 0; + data += dp; + goto ror_mem; + case 0x5B: // LSR dp+X + c = 0; + case 0x7B: // ROR dp+X + data = BYTE( data + x ); + case 0x6B: // ROR dp + data += dp; + goto ror_mem; + case 0x4C: // LSR abs + c = 0; + case 0x6C: // ROR abs + data = READ_PC16( pc ); + pc++; + ror_mem: { + int temp = READ( -1, data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + WRITE( 0, data, nz ); + goto inc_pc_loop; + } + + case 0x9F: // XCN + nz = a = (a >> 4) | BYTE( a << 4 ); + goto next_instr; + +// 8. 16-BIT TRANSMISION COMMANDS + + case 0xBA: // MOVW YA,dp + a = READ_DP( -2, data ); + nz = (a & 0x7F) | (a >> 1); + y = READ_DP( 0, BYTE( data + 1 ) ); + nz |= y; + goto inc_pc_loop; + + case 0xDA: // MOVW dp,YA + WRITE_DP( -1, data, a ); + WRITE_DP( 0, BYTE( data + 1 ), y + no_read_before_write ); + goto inc_pc_loop; + +// 9. 16-BIT OPERATION COMMANDS + + case 0x3A: // INCW dp + case 0x1A:{// DECW dp + int temp; + // low byte + data += dp; + temp = READ( -3, data ); + temp += (opcode >> 4 & 2) - 1; // +1 for INCW, -1 for DECW + nz = ((temp >> 1) | temp) & 0x7F; + WRITE( -2, data, /*(byte)*/ temp ); + + // high byte + data = BYTE( data + 1 ) + dp; + temp = BYTE( (temp >> 8) + READ( -1, data ) ); + nz |= temp; + WRITE( 0, data, temp ); + + goto inc_pc_loop; + } + + case 0x7A: // ADDW YA,dp + case 0x9A:{// SUBW YA,dp + int lo = READ_DP( -2, data ); + int hi = READ_DP( 0, BYTE( data + 1 ) ); + int result; + int flags; + + if ( opcode == 0x9A ) // SUBW + { + lo = (lo ^ 0xFF) + 1; + hi ^= 0xFF; + } + + lo += a; + result = y + hi + (lo >> 8); + flags = hi ^ y ^ result; + + psw = (psw & ~(v40 | h08)) | + (flags >> 1 & h08) | + ((flags + 0x80) >> 2 & v40); + c = result; + a = BYTE( lo ); + result = BYTE( result ); + y = result; + nz = (((lo >> 1) | lo) & 0x7F) | result; + + goto inc_pc_loop; + } + + case 0x5A: { // CMPW YA,dp + int temp = a - READ_DP( -1, data ); + nz = ((temp >> 1) | temp) & 0x7F; + temp = y + (temp >> 8); + temp -= READ_DP( 0, BYTE( data + 1 ) ); + nz |= temp; + c = ~temp; + nz &= 0xFF; + goto inc_pc_loop; + } + +// 10. MULTIPLICATION & DIVISON COMMANDS + + case 0xCF: { // MUL YA + unsigned temp = y * a; + a = BYTE( temp ); + nz = ((temp >> 1) | temp) & 0x7F; + y = temp >> 8; + nz |= y; + goto next_instr; + } + + case 0x9E: // DIV YA,X + { + unsigned ya = y * 0x100 + a; + + psw &= ~(h08 | v40); + + if ( y >= x ) + psw |= v40; + + if ( (y & 15) >= (x & 15) ) + psw |= h08; + + if ( y < x * 2 ) + { + a = ya / x; + y = ya - a * x; + } + else + { + a = 255 - (ya - x * 0x200) / (256 - x); + y = x + (ya - x * 0x200) % (256 - x); + } + + nz = BYTE( a ); + a = BYTE( a ); + + goto next_instr; + } + +// 11. DECIMAL COMPENSATION COMMANDS + + case 0xDF: // DAA + SUSPICIOUS_OPCODE( "DAA" ); + if ( a > 0x99 || c & 0x100 ) + { + a += 0x60; + c = 0x100; + } + + if ( (a & 0x0F) > 9 || psw & h08 ) + a += 0x06; + + nz = a; + a = BYTE( a ); + goto next_instr; + + case 0xBE: // DAS + SUSPICIOUS_OPCODE( "DAS" ); + if ( a > 0x99 || !(c & 0x100) ) + { + a -= 0x60; + c = 0; + } + + if ( (a & 0x0F) > 9 || !(psw & h08) ) + a -= 0x06; + + nz = a; + a = BYTE( a ); + goto next_instr; + +// 12. BRANCHING COMMANDS + + case 0x2F: // BRA rel + pc += (BOOST::int8_t) data; + goto inc_pc_loop; + + case 0x30: // BMI + BRANCH( (nz & nz_neg_mask) ) + + case 0x10: // BPL + BRANCH( !(nz & nz_neg_mask) ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x70: // BVS + BRANCH( psw & v40 ) + + case 0x50: // BVC + BRANCH( !(psw & v40) ) + + #define CBRANCH( cond )\ + {\ + pc++;\ + if ( cond )\ + goto cbranch_taken_loop;\ + rel_time -= 2;\ + goto inc_pc_loop;\ + } + + case 0x03: // BBS dp.bit,rel + case 0x23: + case 0x43: + case 0x63: + case 0x83: + case 0xA3: + case 0xC3: + case 0xE3: + CBRANCH( READ_DP( -4, data ) >> (opcode >> 5) & 1 ) + + case 0x13: // BBC dp.bit,rel + case 0x33: + case 0x53: + case 0x73: + case 0x93: + case 0xB3: + case 0xD3: + case 0xF3: + CBRANCH( !(READ_DP( -4, data ) >> (opcode >> 5) & 1) ) + + case 0xDE: // CBNE dp+X,rel + data = BYTE( data + x ); + // fall through + case 0x2E:{// CBNE dp,rel + int temp; + READ_TIMER( -4, DP_ADDR( data ), temp ); // 61% from timer + CBRANCH( temp != a ) + } + + case 0x6E: { // DBNZ dp,rel + unsigned temp = READ_DP( -4, data ) - 1; + WRITE_DP( -3, BYTE( data ), /*(byte)*/ temp + no_read_before_write ); + CBRANCH( temp ) + } + + case 0xFE: // DBNZ Y,rel + y = BYTE( y - 1 ); + BRANCH( y ) + + case 0x1F: // JMP [abs+X] + SET_PC( READ_PC16( pc ) + x ); + // fall through + case 0x5F: // JMP abs + SET_PC( READ_PC16( pc ) ); + goto next_instr; + +// 13. SUB-ROUTINE CALL RETURN COMMANDS + + case 0x0F:{// BRK + int temp; + int ret_addr = GET_PC(); + SUSPICIOUS_OPCODE( "BRK" ); + SET_PC( READ_PROG16( 0xFFDE ) ); // vector address verified + PUSH16( ret_addr ); + GET_PSW( temp ); + psw = (psw | b10) & ~i04; + PUSH( temp ); + goto next_instr; + } + + case 0x4F:{// PCALL offset + int ret_addr = GET_PC() + 1; + SET_PC( 0xFF00 | data ); + PUSH16( ret_addr ); + goto next_instr; + } + + case 0x01: // TCALL n + case 0x11: + case 0x21: + case 0x31: + case 0x41: + case 0x51: + case 0x61: + case 0x71: + case 0x81: + case 0x91: + case 0xA1: + case 0xB1: + case 0xC1: + case 0xD1: + case 0xE1: + case 0xF1: { + int ret_addr = GET_PC(); + SET_PC( READ_PROG16( 0xFFDE - (opcode >> 3) ) ); + PUSH16( ret_addr ); + goto next_instr; + } + +// 14. STACK OPERATION COMMANDS + + { + int temp; + case 0x7F: // RET1 + temp = *sp; + SET_PC( GET_LE16( sp + 1 ) ); + sp += 3; + goto set_psw; + case 0x8E: // POP PSW + POP( temp ); + set_psw: + SET_PSW( temp ); + goto next_instr; + } + + case 0x0D: { // PUSH PSW + int temp; + GET_PSW( temp ); + PUSH( temp ); + goto next_instr; + } + + case 0x2D: // PUSH A + PUSH( a ); + goto next_instr; + + case 0x4D: // PUSH X + PUSH( x ); + goto next_instr; + + case 0x6D: // PUSH Y + PUSH( y ); + goto next_instr; + + case 0xAE: // POP A + POP( a ); + goto next_instr; + + case 0xCE: // POP X + POP( x ); + goto next_instr; + + case 0xEE: // POP Y + POP( y ); + goto next_instr; + +// 15. BIT OPERATION COMMANDS + + case 0x02: // SET1 + case 0x22: + case 0x42: + case 0x62: + case 0x82: + case 0xA2: + case 0xC2: + case 0xE2: + case 0x12: // CLR1 + case 0x32: + case 0x52: + case 0x72: + case 0x92: + case 0xB2: + case 0xD2: + case 0xF2: { + int bit = 1 << (opcode >> 5); + int mask = ~bit; + if ( opcode & 0x10 ) + bit = 0; + data += dp; + WRITE( 0, data, (READ( -1, data ) & mask) | bit ); + goto inc_pc_loop; + } + + case 0x0E: // TSET1 abs + case 0x4E: // TCLR1 abs + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -2, data ); + nz = BYTE( a - temp ); + temp &= ~a; + if ( opcode == 0x0E ) + temp |= a; + WRITE( 0, data, temp ); + } + goto next_instr; + + case 0x4A: // AND1 C,mem.bit + c &= MEM_BIT( 0 ); + pc += 2; + goto next_instr; + + case 0x6A: // AND1 C,/mem.bit + c &= ~MEM_BIT( 0 ); + pc += 2; + goto next_instr; + + case 0x0A: // OR1 C,mem.bit + c |= MEM_BIT( -1 ); + pc += 2; + goto next_instr; + + case 0x2A: // OR1 C,/mem.bit + c |= ~MEM_BIT( -1 ); + pc += 2; + goto next_instr; + + case 0x8A: // EOR1 C,mem.bit + c ^= MEM_BIT( -1 ); + pc += 2; + goto next_instr; + + case 0xEA: // NOT1 mem.bit + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -1, data & 0x1FFF ); + temp ^= 1 << (data >> 13); + WRITE( 0, data & 0x1FFF, temp ); + } + goto next_instr; + + case 0xCA: // MOV1 mem.bit,C + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -2, data & 0x1FFF ); + unsigned bit = data >> 13; + temp = (temp & ~(1 << bit)) | ((c >> 8 & 1) << bit); + WRITE( 0, data & 0x1FFF, temp + no_read_before_write ); + } + goto next_instr; + + case 0xAA: // MOV1 C,mem.bit + c = MEM_BIT( 0 ); + pc += 2; + goto next_instr; + +// 16. PROGRAM PSW FLAG OPERATION COMMANDS + + case 0x60: // CLRC + c = 0; + goto next_instr; + + case 0x80: // SETC + c = ~0; + goto next_instr; + + case 0xED: // NOTC + c ^= 0x100; + goto next_instr; + + case 0xE0: // CLRV + psw &= ~(v40 | h08); + goto next_instr; + + case 0x20: // CLRP + dp = 0; + goto next_instr; + + case 0x40: // SETP + dp = 0x100; + goto next_instr; + + case 0xA0: // EI + SUSPICIOUS_OPCODE( "EI" ); + psw |= i04; + goto next_instr; + + case 0xC0: // DI + SUSPICIOUS_OPCODE( "DI" ); + psw &= ~i04; + goto next_instr; + +// 17. OTHER COMMANDS + + case 0x00: // NOP + goto next_instr; + + case 0xFF:{// STOP + // handle PC wrap-around + unsigned addr = GET_PC() - 1; + if ( addr >= 0x10000 ) + { + addr &= 0xFFFF; + SET_PC( addr ); + if ( !SPC_MORE_ACCURACY ) + dprintf( "SPC: PC wrapped around\n" ); + goto next_instr; + } + } + // fall through + case 0xEF: // SLEEP + SUSPICIOUS_OPCODE( "STOP/SLEEP" ); + --pc; + + // consume rest of available time + rel_time = 0; + + cpu_error = "SPC emulation error"; + goto stop; + } // switch + + assert( 0 ); // catch any unhandled instructions +} +stop: + + // Uncache registers + if ( GET_PC() >= 0x10000 && !SPC_MORE_ACCURACY ) + dprintf( "SPC: PC wrapped around\n" ); + + cpu_regs.pc = ((unsigned) GET_PC()) & 0xFFFF; + cpu_regs.sp = BYTE( GET_SP() ); + cpu_regs.a = BYTE( a ); + cpu_regs.x = BYTE( x ); + cpu_regs.y = BYTE( y ); + GET_PSW( cpu_regs.psw ); +} diff --git a/bsnes/smp/snes_spc/Spc_Dsp.cpp b/bsnes/smp/snes_spc/Spc_Dsp.cpp new file mode 100755 index 00000000..46412254 --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Dsp.cpp @@ -0,0 +1,897 @@ +// snes_spc 0.9.5. http://www.slack.net/~ant/ + +#include "Spc_Dsp.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2007-2010 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +// New SNES DSP behaves slightly differently (not all differences handled yet) +bool const new_snes = false; + +// if ( io < -32768 ) io = -32768; +// if ( io > 32767 ) io = 32767; +#define CLAMP16( io )\ +{\ + if ( (int16_t) io != io )\ + io = (io >> 31) ^ 0x7FFF;\ +} + +// Access global DSP register +#define REG(n) regs [r_##n] + +// Access voice DSP register +#define VREG(r,n) r [v_##n] + +#ifndef SPC_DSP_OUT_HOOK + #define SPC_DSP_OUT_HOOK( l, r ) \ + write_sample( l, r ) +#endif + +inline void Spc_Dsp::set_null_output() +{ + output_ptr_ = dummy_buf; + output_end = dummy_buf + 2; +} + +void Spc_Dsp::set_output( sample_t* begin, sample_t* end ) +{ + output_begin = begin; + + if ( begin == NULL ) + { + user_output_end = NULL; + set_null_output(); + } + else + { + // Size must be even + assert( (end - begin) % 2 == 0 ); + + output_ptr_ = begin; + output_end = end; + user_output_end = end; + } +} + +inline void Spc_Dsp::write_sample( int l, int r ) +{ + sample_t* out = output_ptr_; + out [0] = l; + out [1] = r; + out += 2; + output_ptr_ = out; + + if ( out >= output_end ) + { + set_null_output(); + if ( out != dummy_buf + 2 ) + { + if ( set_output_callback.f ) + set_output_callback.f( set_output_callback.data ); + else + dprintf( "DSP buffer overflowed\n" ); + } + } +} + +// Volume registers and efb are signed! Easy to forget int8_t cast. +// Prefixes are to avoid accidental use of locals with same names. + +// Gaussian interpolation + +static short const gauss [512] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, + 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, + 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, + 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, + 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, + 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, + 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, + 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, + 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, + 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, + 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, + 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, + 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, + 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, + 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, + 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, + 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, + 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, +1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, +1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, +1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, +1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, +1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, +1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, +1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, +}; + +inline int Spc_Dsp::interpolate( voice_t const* v ) +{ + // Make pointers into gaussian based on fractional position between samples + int offset = v->interp_pos >> 4 & 0xFF; + short const* fwd = gauss + 255 - offset; + short const* rev = gauss + offset; // mirror left half of gaussian + + int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; + int out; + out = (fwd [ 0] * in [0]) >> 11; + out += (fwd [256] * in [1]) >> 11; + out += (rev [256] * in [2]) >> 11; + out = (int16_t) out; + out += (rev [ 0] * in [3]) >> 11; + + CLAMP16( out ); + out &= ~1; + return out; +} + + +//// Counters + +int const simple_counter_range = 2048 * 5 * 3; // 30720 + +static unsigned const counter_rates [32] = +{ + simple_counter_range + 1, // never fires + 2048, 1536, + 1280, 1024, 768, + 640, 512, 384, + 320, 256, 192, + 160, 128, 96, + 80, 64, 48, + 40, 32, 24, + 20, 16, 12, + 10, 8, 6, + 5, 4, 3, + 2, + 1 +}; +static unsigned const counter_offsets [32] = +{ + 1, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 0, + 0 +}; + +inline void Spc_Dsp::init_counter() +{ + m.counter = 0; +} + +inline void Spc_Dsp::run_counters() +{ + if ( --m.counter < 0 ) + m.counter = simple_counter_range - 1; +} + +inline unsigned Spc_Dsp::read_counter( int rate ) +{ + return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; +} + + +//// Envelope + +inline void Spc_Dsp::run_envelope( voice_t* const v ) +{ + int env = v->env; + if ( v->env_mode == env_release ) // 60% + { + if ( (env -= 0x8) < 0 ) + env = 0; + v->env = env; + } + else + { + int rate; + int env_data = VREG(v->regs,adsr1); + if ( m.t_adsr0 & 0x80 ) // 99% ADSR + { + if ( v->env_mode >= env_decay ) // 99% + { + env--; + env -= env >> 8; + rate = env_data & 0x1F; + if ( v->env_mode == env_decay ) // 1% + rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10; + } + else // env_attack + { + rate = (m.t_adsr0 & 0x0F) * 2 + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } + else // GAIN + { + int mode; + env_data = VREG(v->regs,gain); + mode = env_data >> 5; + if ( mode < 4 ) // direct + { + env = env_data * 0x10; + rate = 31; + } + else + { + rate = env_data & 0x1F; + if ( mode == 4 ) // 4: linear decrease + { + env -= 0x20; + } + else if ( mode < 6 ) // 5: exponential decrease + { + env--; + env -= env >> 8; + } + else // 6,7: linear increase + { + env += 0x20; + if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) + env += 0x8 - 0x20; // 7: two-slope linear increase + } + } + } + + // Sustain level + if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) + v->env_mode = env_sustain; + + v->hidden_env = env; + + // unsigned cast because linear decrease going negative also triggers this + if ( (unsigned) env > 0x7FF ) + { + env = (env < 0 ? 0 : 0x7FF); + if ( v->env_mode == env_attack ) + v->env_mode = env_decay; + } + + if ( !read_counter( rate ) ) + v->env = env; // nothing else is controlled by the counter + } +} + + +//// BRR Decoding + +inline void Spc_Dsp::decode_brr( voice_t* v ) +{ + // Arrange the four input nybbles in 0xABCD order for easy decoding + int nybbles = m.t_brr_byte * 0x100 + ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; + + int const header = m.t_brr_header; + + // Write to next four samples in circular buffer + int* pos = &v->buf [v->buf_pos]; + if ( (v->buf_pos += 4) >= brr_buf_size ) + v->buf_pos = 0; + + // Decode four samples + for ( int* end = pos + 4; pos < end; pos++ ) + { + // Extract nybble and sign-extend + int s = (int16_t) nybbles >> 12; + nybbles <<= 4; + + // Shift sample based on header + int const shift = header >> 4; + s = (s << shift) >> 1; + if ( shift >= 0xD ) // handle invalid range + s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) + + // Apply (unstable) IIR filter (8 is the most commonly used) + int const filter = header & 0x0C; + int const p1 = pos [brr_buf_size - 1]; + int const p2 = pos [brr_buf_size - 2] >> 1; + if ( filter >= 8 ) // most common one + { + s += p1; + s -= p2; + if ( filter == 8 ) // pos[0] = s*2 + pos[-1] * 1.09625 - pos[-2] * 0.9375 + { + s += p2 >> 4; + s += (p1 * -3) >> 6; + } + else // pos[0] = s*2 + pos[-1] * 1.796875 - pos[-2] * 0.8125 + { + s += (p1 * -13) >> 7; + s += (p2 * 3) >> 4; + } + } + else if ( filter ) // pos[0] = s*2 + pos[-1] * 0.9375 + { + s += p1 >> 1; + s += (-p1) >> 5; + } + + // Adjust and write sample + CLAMP16( s ); + s = (int16_t) (s * 2); + pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around + } +} + + +//// Misc + +#define MISC_CLOCK( n ) inline void Spc_Dsp::misc_##n() + +MISC_CLOCK( 27 ) +{ + m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON +} +MISC_CLOCK( 28 ) +{ + m.t_non = REG(non); + m.t_eon = REG(eon); + m.t_dir = REG(dir); +} +MISC_CLOCK( 29 ) +{ + if ( (m.every_other_sample ^= 1) != 0 ) + m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read +} +MISC_CLOCK( 30 ) +{ + if ( m.every_other_sample ) + { + m.kon = m.new_kon; + m.t_koff = REG(koff) | mute_mask; + } + + run_counters(); + + // Noise + if ( !read_counter( REG(flg) & 0x1F ) ) + { + int feedback = (m.noise << 13) ^ (m.noise << 14); + m.noise = (feedback & 0x4000) ^ (m.noise >> 1); + } +} + + +//// Voices + +#define VOICE_CLOCK( n ) void Spc_Dsp::voice_##n( voice_t* const v ) + +inline VOICE_CLOCK( V1 ) +{ + m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4; + m.t_srcn = VREG(v->regs,srcn); +} +inline VOICE_CLOCK( V2 ) +{ + // Read sample pointer (ignored if not needed) + byte const* entry = &ram [m.t_dir_addr]; + if ( !v->kon_delay ) + entry += 2; + m.t_brr_next_addr = GET_LE16A( entry ); + + m.t_adsr0 = VREG(v->regs,adsr0); + + // Read pitch, spread over two clocks + m.t_pitch = VREG(v->regs,pitchl); +} +inline VOICE_CLOCK( V3a ) +{ + m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; +} +inline VOICE_CLOCK( V3b ) +{ + // Read BRR header and byte + m.t_brr_byte = ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; + m.t_brr_header = ram [v->brr_addr]; // brr_addr doesn't need masking +} +VOICE_CLOCK( V3c ) +{ + // Pitch modulation using previous voice's output + if ( m.t_pmon & v->vbit ) + m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; + + if ( v->kon_delay ) + { + // Get ready to start BRR decoding on next sample + if ( v->kon_delay == 5 ) + { + v->brr_addr = m.t_brr_next_addr; + v->brr_offset = 1; + v->buf_pos = 0; + m.t_brr_header = 0; // header is ignored on this sample + kon_check = true; + } + + // Envelope is never run during KON + v->env = 0; + v->hidden_env = 0; + + // Disable BRR decoding until last three samples + v->interp_pos = 0; + if ( --v->kon_delay & 3 ) + v->interp_pos = 0x4000; + + // Pitch is never added during KON + m.t_pitch = 0; + } + + // Gaussian interpolation + { + int output = interpolate( v ); + + // Noise + if ( m.t_non & v->vbit ) + output = (int16_t) (m.noise * 2); + + // Apply envelope + m.t_output = (output * v->env) >> 11 & ~1; + v->t_envx_out = (byte) (v->env >> 4); + } + + // Immediate silence due to end of sample or soft reset + if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) + { + v->env_mode = env_release; + v->env = 0; + } + + if ( m.every_other_sample ) + { + // KOFF + if ( m.t_koff & v->vbit && (!new_snes || v->kon_delay < 3) ) + v->env_mode = env_release; + + // KON + if ( m.kon & v->vbit ) + { + v->kon_delay = 5; + v->env_mode = env_attack; + } + } + + // Run envelope for next sample + if ( !v->kon_delay ) + run_envelope( v ); +} +inline void Spc_Dsp::voice_output( voice_t const* v, int ch ) +{ + // Apply left/right volume + int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; + + // Avoid negative volume if surround is disabled + // (emulator feature; not part of actual DSP) + if ( (int8_t) VREG(v->regs,voll + ch) < surround_threshold ) + amp = -amp; + + // Add to output total + m.t_main_out [ch] += amp; + CLAMP16( m.t_main_out [ch] ); + + // Optionally add to echo total + if ( m.t_eon & v->vbit ) + { + m.t_echo_out [ch] += amp; + CLAMP16( m.t_echo_out [ch] ); + } +} +VOICE_CLOCK( V4 ) +{ + // Decode BRR + m.t_looped = 0; + if ( v->interp_pos >= 0x4000 ) + { + decode_brr( v ); + + if ( (v->brr_offset += 2) >= brr_block_size ) + { + // Start decoding next BRR block + assert( v->brr_offset == brr_block_size ); + v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; + if ( m.t_brr_header & 1 ) + { + v->brr_addr = m.t_brr_next_addr; + m.t_looped = v->vbit; + } + v->brr_offset = 1; + } + } + + // Apply pitch + v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; + + // Keep from getting too far ahead (when using pitch modulation) + if ( v->interp_pos > 0x7FFF ) + v->interp_pos = 0x7FFF; + + // Output left + voice_output( v, 0 ); +} +inline VOICE_CLOCK( V5 ) +{ + // Output right + voice_output( v, 1 ); + + // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier + int endx_buf = REG(endx) | m.t_looped; + + // Clear bit in ENDX if KON just began + if ( v->kon_delay == 5 ) + endx_buf &= ~v->vbit; + m.endx_buf = (byte) endx_buf; +} +inline VOICE_CLOCK( V6 ) +{ + (void) v; // avoid compiler warning about unused v + m.outx_buf = (byte) (m.t_output >> 8); +} +inline VOICE_CLOCK( V7 ) +{ + // Update ENDX + REG(endx) = m.endx_buf; + + m.envx_buf = v->t_envx_out; +} +inline VOICE_CLOCK( V8 ) +{ + // Update OUTX + VREG(v->regs,outx) = m.outx_buf; +} +inline VOICE_CLOCK( V9 ) +{ + // Update ENVX + VREG(v->regs,envx) = m.envx_buf; +} + +// Most voices do all these in one clock, so make a handy composite +inline VOICE_CLOCK( V3 ) +{ + voice_V3a( v ); + voice_V3b( v ); + voice_V3c( v ); +} + +// Common combinations of voice steps on different voices. This greatly reduces +// code size and allows everything to be inlined in these functions. +VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } +VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } +VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } + + +//// Echo + +// Current echo buffer pointer for left/right channel +#define ECHO_PTR( ch ) (&ram [m.t_echo_ptr + ch * 2]) + +// Sample in echo history buffer, where 0 is the oldest +#define ECHO_FIR( i ) (m.echo_hist_pos [i]) + +// Calculate FIR point for left/right channel +#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6) + +#define ECHO_CLOCK( n ) inline void Spc_Dsp::echo_##n() + +inline void Spc_Dsp::echo_read( int ch ) +{ + int s = GET_LE16SA( ECHO_PTR( ch ) ); + // second copy simplifies wrap-around handling + ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; +} + +ECHO_CLOCK( 22 ) +{ + // History + if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) + m.echo_hist_pos = m.echo_hist; + + m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; + echo_read( 0 ); + + // FIR (using l and r temporaries below helps compiler optimize) + int l = CALC_FIR( 0, 0 ); + int r = CALC_FIR( 0, 1 ); + + m.t_echo_in [0] = l; + m.t_echo_in [1] = r; +} +ECHO_CLOCK( 23 ) +{ + int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); + int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; + + echo_read( 1 ); +} +ECHO_CLOCK( 24 ) +{ + int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); + int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; +} +ECHO_CLOCK( 25 ) +{ + int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); + int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); + + l = (int16_t) l; + r = (int16_t) r; + + l += (int16_t) CALC_FIR( 7, 0 ); + r += (int16_t) CALC_FIR( 7, 1 ); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_in [0] = l & ~1; + m.t_echo_in [1] = r & ~1; +} +inline int Spc_Dsp::echo_output( int ch ) +{ + int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + + (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); + CLAMP16( out ); + return out; +} +ECHO_CLOCK( 26 ) +{ + // Surround disabler (emulator feature; not part of actual DSP) + if ( (int8_t) REG(mvoll) * (int8_t) REG(mvolr) < surround_threshold ) + m.t_main_out [0] = -m.t_main_out [0]; // eliminate surround + + // Left output volumes + // (save sample for next clock so we can output both together) + m.t_main_out [0] = echo_output( 0 ); + + // Echo feedback + int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); + int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_out [0] = l & ~1; + m.t_echo_out [1] = r & ~1; +} +ECHO_CLOCK( 27 ) +{ + // Output + int l = m.t_main_out [0]; + int r = echo_output( 1 ); + m.t_main_out [0] = 0; + m.t_main_out [1] = 0; + + // TODO: global muting isn't this simple (turns DAC on and off + // or something, causing small ~37-sample pulse when first muted) + if ( REG(flg) & 0x40 ) + { + l = 0; + r = 0; + } + + // Output sample to DAC + SPC_DSP_OUT_HOOK( l, r ); +} +ECHO_CLOCK( 28 ) +{ + m.t_echo_enabled = REG(flg); +} +inline void Spc_Dsp::echo_write( int ch ) +{ + if ( !(m.t_echo_enabled & 0x20) ) + { + #ifdef SPC_DSP_ECHO_DEBUG + SPC_DSP_ECHO_DEBUG + #endif + SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); + } + m.t_echo_out [ch] = 0; +} +ECHO_CLOCK( 29 ) +{ + m.t_esa = REG(esa); + + if ( !m.echo_offset ) + m.echo_length = (REG(edl) & 0x0F) * 0x800; + + m.echo_offset += 4; + if ( m.echo_offset >= m.echo_length ) + m.echo_offset = 0; + + // Write left echo + echo_write( 0 ); + + m.t_echo_enabled = REG(flg); +} +ECHO_CLOCK( 30 ) +{ + // Write right echo + echo_write( 1 ); +} + + +//// Timing + +// Execute clock for a particular voice +#define V( clock, voice ) voice_##clock( &m.voices [voice] ); + +/* The most common sequence of clocks uses composite operations +for efficiency. For example, the following are equivalent to the +individual steps on the right: + +V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) +V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) +V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ + +// Voice 0 1 2 3 4 5 6 7 +#define GEN_DSP_TIMING \ +PHASE( 0) V(V5,0)V(V2,1)\ +PHASE( 1) V(V6,0)V(V3,1)\ +PHASE( 2) V(V7_V4_V1,0)\ +PHASE( 3) V(V8_V5_V2,0)\ +PHASE( 4) V(V9_V6_V3,0)\ +PHASE( 5) V(V7_V4_V1,1)\ +PHASE( 6) V(V8_V5_V2,1)\ +PHASE( 7) V(V9_V6_V3,1)\ +PHASE( 8) V(V7_V4_V1,2)\ +PHASE( 9) V(V8_V5_V2,2)\ +PHASE(10) V(V9_V6_V3,2)\ +PHASE(11) V(V7_V4_V1,3)\ +PHASE(12) V(V8_V5_V2,3)\ +PHASE(13) V(V9_V6_V3,3)\ +PHASE(14) V(V7_V4_V1,4)\ +PHASE(15) V(V8_V5_V2,4)\ +PHASE(16) V(V9_V6_V3,4)\ +PHASE(17) V(V1,0) V(V7,5)V(V4,6)\ +PHASE(18) V(V8_V5_V2,5)\ +PHASE(19) V(V9_V6_V3,5)\ +PHASE(20) V(V1,1) V(V7,6)V(V4,7)\ +PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\ +PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\ +PHASE(23) V(V7,7) echo_23();\ +PHASE(24) V(V8,7) echo_24();\ +PHASE(25) V(V3b,0) V(V9,7) echo_25();\ +PHASE(26) echo_26();\ +PHASE(27) misc_27(); echo_27();\ +PHASE(28) misc_28(); echo_28();\ +PHASE(29) misc_29(); echo_29();\ +PHASE(30) misc_30();V(V3c,0) echo_30();\ +PHASE(31) V(V4,0) V(V1,2)\ + +#if !SPC_DSP_CUSTOM_RUN + +void Spc_Dsp::run( int clocks_remain ) +{ + require( clocks_remain > 0 ); + + int const phase = m.phase; + m.phase = (phase + clocks_remain) & 31; + switch ( phase ) + { + loop: + + #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: + GEN_DSP_TIMING + #undef PHASE + + if ( --clocks_remain ) + goto loop; + } +} + +#endif + + +//// Setup + +void Spc_Dsp::init( void* ram_64k ) +{ + ram = (byte*) ram_64k; + disable_surround( false ); + mute_voices( 0 ); + set_output( NULL, 0 ); + reset(); + + #ifndef NDEBUG + // be sure this sign-extends + assert( (int16_t) 0x8000 == -0x8000 ); + + // be sure right shift preserves sign + assert( (-1 >> 1) == -1 ); + + // check clamp macro + int i; + i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); + i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); + + blargg_verify_byte_order(); + #endif +} + +void Spc_Dsp::soft_reset() +{ + require( ram ); // init() must have been called already + + REG(flg) = 0xE0; + + m.noise = 0x2000; + m.echo_hist_pos = m.echo_hist; + m.every_other_sample = 1; + m.echo_offset = 0; + m.phase = 0; + + init_counter(); + + kon_check = false; +} + +void Spc_Dsp::load( byte const new_regs [register_count] ) +{ + memcpy( regs, new_regs, register_count ); + BLARGG_CLEAR( &m ); + + for ( int i = voice_count; --i >= 0; ) + { + voice_t* v = &m.voices [i]; + v->brr_offset = 1; + v->vbit = 1 << i; + v->regs = ®s [i * 0x10]; + } + m.new_kon = REG(kon); + m.t_dir = REG(dir); + m.t_esa = REG(esa); + + soft_reset(); + REG(flg) = new_regs [r_flg]; // soft_reset() overwrites this +} + +void Spc_Dsp::reset() +{ + static byte const initial_regs [register_count] = { + 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, + 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, + 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, + 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, + 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, + 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF, + 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, + 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF + }; + + load( initial_regs ); +} diff --git a/bsnes/smp/snes_spc/Spc_Dsp.h b/bsnes/smp/snes_spc/Spc_Dsp.h new file mode 100755 index 00000000..95f136d7 --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Dsp.h @@ -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 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 + +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 diff --git a/bsnes/smp/snes_spc/Spc_Dsp_State.cpp b/bsnes/smp/snes_spc/Spc_Dsp_State.cpp new file mode 100755 index 00000000..b92d453b --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Dsp_State.cpp @@ -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 diff --git a/bsnes/smp/snes_spc/Spc_Filter.cpp b/bsnes/smp/snes_spc/Spc_Filter.cpp new file mode 100755 index 00000000..424e6198 --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Filter.cpp @@ -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; + } + } +} diff --git a/bsnes/smp/snes_spc/Spc_Filter.h b/bsnes/smp/snes_spc/Spc_Filter.h new file mode 100755 index 00000000..111784d9 --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_Filter.h @@ -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 diff --git a/bsnes/smp/snes_spc/Spc_State.cpp b/bsnes/smp/snes_spc/Spc_State.cpp new file mode 100755 index 00000000..90bceabf --- /dev/null +++ b/bsnes/smp/snes_spc/Spc_State.cpp @@ -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 + +/* Copyright (C) 2004-2010 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Snes_Spc::init_header( void* spc_out ) +{ + spc_file_t* const spc = (spc_file_t*) spc_out; + + spc->has_id666 = 26; // has none + spc->version = 30; + memcpy( spc, signature, sizeof spc->signature ); + memset( spc->text, 0, sizeof spc->text ); +} + +void Spc_Core::save_regs( byte out [reg_count], byte ports [port_count] ) +{ + memcpy( out, regs, reg_count ); + + int i; + for ( i = 0; i < port_count; i++ ) + out [r_cpuio0 + i] = regs_in [r_cpuio0 + i]; + + for ( i = 0; i < timer_count; i++ ) + out [r_t0out + i] = regs_in [r_t0out + i]; + + memcpy( ports, ®s [r_cpuio0], port_count ); +} + +void Spc_Core::save_ram( byte out [65536] ) +{ + memcpy( out, ram(), 65536 ); + + if ( rom_enabled ) + memcpy( out + rom_addr, hi_ram, sizeof hi_ram ); +} + +void Snes_Spc::save_spc( void* spc_out ) +{ + spc_file_t* const spc = (spc_file_t*) spc_out; + + // CPU + spc->pcl = (byte) (cpu().pc >> 0); + spc->pch = (byte) (cpu().pc >> 8); + spc->a = cpu().a; + spc->x = cpu().x; + spc->y = cpu().y; + spc->psw = cpu().psw; + spc->sp = cpu().sp; + + // RAM, ROM + save_ram( spc->ram ); + memset( spc->unused, 0, sizeof spc->unused ); + memcpy( spc->ipl_rom, rom(), sizeof spc->ipl_rom ); + + // SMP registers + byte out_ports [port_count]; // ignored + save_regs( &spc->ram [regs_addr], out_ports ); + + // DSP registers + for ( int i = 0; i < Spc_Dsp::register_count; i++ ) + spc->dsp [i] = dsp().read( i ); +} + +void Spc_Core::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // Make state data more readable by putting 64K RAM, 16 SMP registers, + // then DSP (with its 128 registers) first + + // RAM + + // Disable ROM so that entire RAM will be in ram(). If ROM was enabled, + // will get re-enabled by load_regs() below. + enable_rom( false ); + copier.copy( ram(), 0x10000 ); + + // SMP registers + { + byte new_regs [reg_count]; + byte out_ports [port_count]; + + save_regs( new_regs, out_ports ); + copier.copy( new_regs, sizeof new_regs ); + copier.copy( out_ports, sizeof out_ports ); + load_regs( new_regs, out_ports ); + } + + // CPU registers + SPC_COPY( uint16_t, cpu().pc ); + SPC_COPY( uint8_t, cpu().a ); + SPC_COPY( uint8_t, cpu().x ); + SPC_COPY( uint8_t, cpu().y ); + SPC_COPY( uint8_t, cpu().psw ); + SPC_COPY( uint8_t, cpu().sp ); + copier.extra(); + + SPC_COPY( int16_t, spc_time ); + SPC_COPY( int16_t, dsp_time ); + + // DSP + dsp().copy_state( io, copy ); + + // Timers + for ( int i = 0; i < timer_count; i++ ) + { + Timer* t = &timers [i]; + SPC_COPY( int16_t, t->time ); + SPC_COPY( uint8_t, t->divider ); + copier.extra(); + } + copier.extra(); +} +#endif diff --git a/bsnes/smp/snes_spc/blargg_common.cpp b/bsnes/smp/snes_spc/blargg_common.cpp new file mode 100755 index 00000000..73141343 --- /dev/null +++ b/bsnes/smp/snes_spc/blargg_common.cpp @@ -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 diff --git a/bsnes/smp/snes_spc/blargg_common.h b/bsnes/smp/snes_spc/blargg_common.h new file mode 100755 index 00000000..394ca726 --- /dev/null +++ b/bsnes/smp/snes_spc/blargg_common.h @@ -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 +#include +#include + +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 (expr) +#define CONST_CAST( T,expr) const_cast (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 + #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 + #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 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 (begin_); } + const T* begin() const { return static_cast (begin_); } + + T* end() { return static_cast (begin_) + size_; } + const T* end() const { return static_cast (begin_) + size_; } + + T& operator [] ( size_t n ) + { + assert( n < size_ ); + return static_cast (begin_) [n]; + } + + const T& operator [] ( size_t n ) const + { + assert( n < size_ ); + return static_cast (begin_) [n]; + } +}; + +// Callback function with user data. +// blargg_callback 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 +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 diff --git a/bsnes/smp/snes_spc/blargg_config.h b/bsnes/smp/snes_spc/blargg_config.h new file mode 100755 index 00000000..2ede514b --- /dev/null +++ b/bsnes/smp/snes_spc/blargg_config.h @@ -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 diff --git a/bsnes/smp/snes_spc/blargg_endian.h b/bsnes/smp/snes_spc/blargg_endian.h new file mode 100755 index 00000000..e0c04421 --- /dev/null +++ b/bsnes/smp/snes_spc/blargg_endian.h @@ -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 + #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 diff --git a/bsnes/smp/snes_spc/blargg_errors.cpp b/bsnes/smp/snes_spc/blargg_errors.cpp new file mode 100755 index 00000000..046a69d2 --- /dev/null +++ b/bsnes/smp/snes_spc/blargg_errors.cpp @@ -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 diff --git a/bsnes/smp/snes_spc/blargg_errors.h b/bsnes/smp/snes_spc/blargg_errors.h new file mode 100755 index 00000000..04269e29 --- /dev/null +++ b/bsnes/smp/snes_spc/blargg_errors.h @@ -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 diff --git a/bsnes/smp/snes_spc/blargg_source.h b/bsnes/smp/snes_spc/blargg_source.h new file mode 100755 index 00000000..a7d3e84a --- /dev/null +++ b/bsnes/smp/snes_spc/blargg_source.h @@ -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 /* memcpy(), memset(), memmove() */ +#include /* 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 + +/* 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 T min( T x, T y ) { return x < y ? x : y; } +template 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 diff --git a/bsnes/smp/snes_spc/spc.cpp b/bsnes/smp/snes_spc/spc.cpp new file mode 100755 index 00000000..b0e77ad5 --- /dev/null +++ b/bsnes/smp/snes_spc/spc.cpp @@ -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 ); } diff --git a/bsnes/smp/snes_spc/spc.h b/bsnes/smp/snes_spc/spc.h new file mode 100755 index 00000000..4ef123b3 --- /dev/null +++ b/bsnes/smp/snes_spc/spc.h @@ -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 + +#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 diff --git a/bsnes/smp/timing/timing.cpp b/bsnes/smp/timing/timing.cpp deleted file mode 100755 index e575ac88..00000000 --- a/bsnes/smp/timing/timing.cpp +++ /dev/null @@ -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 -void SMP::sSMPTimer::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 -void SMP::sSMPTimer::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 diff --git a/bsnes/smp/timing/timing.hpp b/bsnes/smp/timing/timing.hpp deleted file mode 100755 index e76119a5..00000000 --- a/bsnes/smp/timing/timing.hpp +++ /dev/null @@ -1,21 +0,0 @@ -template -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(); diff --git a/bsnes/sync.sh b/bsnes/sync.sh index 9df84231..64964bc8 100755 --- a/bsnes/sync.sh +++ b/bsnes/sync.sh @@ -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" diff --git a/qt/interface.cpp b/qt/interface.cpp index ec7aba34..0cc4998f 100755 --- a/qt/interface.cpp +++ b/qt/interface.cpp @@ -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)