diff --git a/src/Makefile b/Makefile similarity index 88% rename from src/Makefile rename to Makefile index 6f98a8fd..c6ce4d07 100644 --- a/src/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ include nall/Makefile -ui := ui_qt +snes := asnes +ui := qt # compiler c := $(compiler) -std=gnu99 cpp := $(subst cc,++,$(compiler)) -std=gnu++0x -flags := -O3 -fomit-frame-pointer -I. +flags := -O3 -fomit-frame-pointer -I. -I$(snes) link := objects := @@ -43,7 +44,7 @@ compile = \ all: build; -include snes/Makefile +include $(snes)/Makefile include $(ui)/Makefile objects := $(patsubst %,obj/%.o,$(objects)) @@ -54,7 +55,7 @@ ifeq ($(platform),osx) test -d ../bsnes.app || mkdir -p ../bsnes.app/Contents/MacOS $(strip $(cpp) -o ../bsnes.app/Contents/MacOS/bsnes $(objects) $(link)) else - $(strip $(cpp) -o ../bsnes $(objects) $(link)) + $(strip $(cpp) -o out/$(snes) $(objects) $(link)) endif install: @@ -84,4 +85,7 @@ clean: ui_clean -@$(call delete,*.pdb) -@$(call delete,*.manifest) +archive-all: + tar -cjf snes-`date +%Y%m%d`.tar.bz2 asnes bsnes libco nall obj out qt ruby Makefile sync.sh + help:; diff --git a/asnes/Makefile b/asnes/Makefile new file mode 100644 index 00000000..b45ed35b --- /dev/null +++ b/asnes/Makefile @@ -0,0 +1,77 @@ +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-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 +snes_objects += snes-obc1 snes-st0010 snes-st0011 snes-st0018 +snes_objects += snes-msu1 snes-serial +objects += $(snes_objects) + +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-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-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/) +obj/snes-sa1.o : $(snes)/chip/sa1/sa1.cpp $(call rwildcard,$(snes)/chip/sa1/) +obj/snes-bsx.o : $(snes)/chip/bsx/bsx.cpp $(snes)/chip/bsx/* +obj/snes-srtc.o : $(snes)/chip/srtc/srtc.cpp $(snes)/chip/srtc/* +obj/snes-sdd1.o : $(snes)/chip/sdd1/sdd1.cpp $(snes)/chip/sdd1/* +obj/snes-spc7110.o : $(snes)/chip/spc7110/spc7110.cpp $(snes)/chip/spc7110/* +obj/snes-cx4.o : $(snes)/chip/cx4/cx4.cpp $(snes)/chip/cx4/* +obj/snes-dsp1.o : $(snes)/chip/dsp1/dsp1.cpp $(snes)/chip/dsp1/* +obj/snes-dsp2.o : $(snes)/chip/dsp2/dsp2.cpp $(snes)/chip/dsp2/* +obj/snes-dsp3.o : $(snes)/chip/dsp3/dsp3.cpp $(snes)/chip/dsp3/* +obj/snes-dsp4.o : $(snes)/chip/dsp4/dsp4.cpp $(snes)/chip/dsp4/* +obj/snes-obc1.o : $(snes)/chip/obc1/obc1.cpp $(snes)/chip/obc1/* +obj/snes-st0010.o : $(snes)/chip/st0010/st0010.cpp $(snes)/chip/st0010/* +obj/snes-st0011.o : $(snes)/chip/st0011/st0011.cpp $(snes)/chip/st0011/* +obj/snes-st0018.o : $(snes)/chip/st0018/st0018.cpp $(snes)/chip/st0018/* +obj/snes-msu1.o : $(snes)/chip/msu1/msu1.cpp $(snes)/chip/msu1/* +obj/snes-serial.o : $(snes)/chip/serial/serial.cpp $(snes)/chip/serial/* + +########### +# library # +########### + +snes_objects := $(patsubst %,obj/%.o,$(snes_objects)) + +library: $(snes_objects) obj/libsnes.o +ifeq ($(platform),x) + ar rcs obj/libsnes.a $(snes_objects) obj/libsnes.o + $(cpp) -o obj/libsnes.so -shared -Wl,-soname,libsnes.so.1 $(snes_objects) obj/libsnes.o +else ifeq ($(platform),osx) + ar rcs obj/libsnes.a $(snes_objects) obj/libsnes.o + $(cpp) -o obj/libsnes.dylib -install_name @executable_path/../Libraries/libsnes.dylib -shared -dynamiclib $(snes_objects) obj/libsnes.o +else ifeq ($(platform),win) + $(cpp) -o obj/snes.dll -shared -Wl,--out-implib,libsnes.a $(snes_objects) obj/libsnes.o +endif + +library-install: +ifeq ($(platform),x) + install -D -m 755 obj/libsnes.a $(DESTDIR)$(prefix)/lib/libsnes.a + install -D -m 755 obj/libsnes.so $(DESTDIR)$(prefix)/lib/libsnes.so + ldconfig -n $(DESTDIR)$(prefix)/lib +else ifeq ($(platform),osx) + cp obj/libsnes.dylib /usr/local/lib/libsnes.dylib +endif + +library-uninstall: +ifeq ($(platform),x) + rm $(DESTDIR)$(prefix)/lib/libsnes.a + rm $(DESTDIR)$(prefix)/lib/libsnes.so +else ifeq ($(platform),osx) + rm /usr/local/lib/libsnes.dylib +endif diff --git a/src/snes/audio/audio.cpp b/asnes/audio/audio.cpp similarity index 100% rename from src/snes/audio/audio.cpp rename to asnes/audio/audio.cpp diff --git a/src/snes/audio/audio.hpp b/asnes/audio/audio.hpp similarity index 100% rename from src/snes/audio/audio.hpp rename to asnes/audio/audio.hpp diff --git a/src/snes/cartridge/cartridge.cpp b/asnes/cartridge/cartridge.cpp similarity index 99% rename from src/snes/cartridge/cartridge.cpp rename to asnes/cartridge/cartridge.cpp index 857314ea..c1a41e4f 100644 --- a/src/snes/cartridge/cartridge.cpp +++ b/asnes/cartridge/cartridge.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/snes/cartridge/cartridge.hpp b/asnes/cartridge/cartridge.hpp similarity index 100% rename from src/snes/cartridge/cartridge.hpp rename to asnes/cartridge/cartridge.hpp diff --git a/src/snes/cartridge/serialization.cpp b/asnes/cartridge/serialization.cpp similarity index 100% rename from src/snes/cartridge/serialization.cpp rename to asnes/cartridge/serialization.cpp diff --git a/src/snes/cartridge/xml.cpp b/asnes/cartridge/xml.cpp similarity index 100% rename from src/snes/cartridge/xml.cpp rename to asnes/cartridge/xml.cpp diff --git a/src/snes/cheat/cheat-inline.hpp b/asnes/cheat/cheat-inline.hpp similarity index 100% rename from src/snes/cheat/cheat-inline.hpp rename to asnes/cheat/cheat-inline.hpp diff --git a/src/snes/cheat/cheat.cpp b/asnes/cheat/cheat.cpp similarity index 99% rename from src/snes/cheat/cheat.cpp rename to asnes/cheat/cheat.cpp index 6f954b95..c97e9a96 100644 --- a/src/snes/cheat/cheat.cpp +++ b/asnes/cheat/cheat.cpp @@ -1,4 +1,4 @@ -#include +#include #define CHEAT_CPP namespace SNES { diff --git a/src/snes/cheat/cheat.hpp b/asnes/cheat/cheat.hpp similarity index 100% rename from src/snes/cheat/cheat.hpp rename to asnes/cheat/cheat.hpp diff --git a/asnes/chip/bsx/bsx.cpp b/asnes/chip/bsx/bsx.cpp new file mode 100644 index 00000000..8a083103 --- /dev/null +++ b/asnes/chip/bsx/bsx.cpp @@ -0,0 +1,8 @@ +#include + +#define BSX_CPP +namespace SNES { + #include "bsx_base.cpp" + #include "bsx_cart.cpp" + #include "bsx_flash.cpp" +} diff --git a/src/snes/chip/bsx/bsx.hpp b/asnes/chip/bsx/bsx.hpp similarity index 100% rename from src/snes/chip/bsx/bsx.hpp rename to asnes/chip/bsx/bsx.hpp diff --git a/src/snes/chip/bsx/bsx_base.cpp b/asnes/chip/bsx/bsx_base.cpp similarity index 100% rename from src/snes/chip/bsx/bsx_base.cpp rename to asnes/chip/bsx/bsx_base.cpp diff --git a/src/snes/chip/bsx/bsx_cart.cpp b/asnes/chip/bsx/bsx_cart.cpp similarity index 100% rename from src/snes/chip/bsx/bsx_cart.cpp rename to asnes/chip/bsx/bsx_cart.cpp diff --git a/src/snes/chip/bsx/bsx_flash.cpp b/asnes/chip/bsx/bsx_flash.cpp similarity index 100% rename from src/snes/chip/bsx/bsx_flash.cpp rename to asnes/chip/bsx/bsx_flash.cpp diff --git a/asnes/chip/chip.hpp b/asnes/chip/chip.hpp new file mode 100644 index 00000000..6893952f --- /dev/null +++ b/asnes/chip/chip.hpp @@ -0,0 +1,31 @@ +struct Coprocessor : Processor { + alwaysinline void step(unsigned clocks); + alwaysinline void synchronize_cpu(); +}; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void Coprocessor::step(unsigned clocks) { + clock += clocks * (uint64)cpu.frequency; +} + +void Coprocessor::synchronize_cpu() { + if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread); +} diff --git a/src/snes/chip/cx4/cx4.cpp b/asnes/chip/cx4/cx4.cpp similarity index 99% rename from src/snes/chip/cx4/cx4.cpp rename to asnes/chip/cx4/cx4.cpp index 787378ba..f6362e7c 100644 --- a/src/snes/chip/cx4/cx4.cpp +++ b/asnes/chip/cx4/cx4.cpp @@ -4,7 +4,7 @@ //Used in Rockman X2/X3 (Megaman X2/X3) //Portions (c) anomie, Overload, zsKnight, Nach, byuu -#include +#include #define CX4_CPP namespace SNES { diff --git a/src/snes/chip/cx4/cx4.hpp b/asnes/chip/cx4/cx4.hpp similarity index 100% rename from src/snes/chip/cx4/cx4.hpp rename to asnes/chip/cx4/cx4.hpp diff --git a/src/snes/chip/cx4/data.cpp b/asnes/chip/cx4/data.cpp similarity index 100% rename from src/snes/chip/cx4/data.cpp rename to asnes/chip/cx4/data.cpp diff --git a/src/snes/chip/cx4/functions.cpp b/asnes/chip/cx4/functions.cpp similarity index 100% rename from src/snes/chip/cx4/functions.cpp rename to asnes/chip/cx4/functions.cpp diff --git a/src/snes/chip/cx4/oam.cpp b/asnes/chip/cx4/oam.cpp similarity index 100% rename from src/snes/chip/cx4/oam.cpp rename to asnes/chip/cx4/oam.cpp diff --git a/src/snes/chip/cx4/opcodes.cpp b/asnes/chip/cx4/opcodes.cpp similarity index 100% rename from src/snes/chip/cx4/opcodes.cpp rename to asnes/chip/cx4/opcodes.cpp diff --git a/src/snes/chip/cx4/serialization.cpp b/asnes/chip/cx4/serialization.cpp similarity index 100% rename from src/snes/chip/cx4/serialization.cpp rename to asnes/chip/cx4/serialization.cpp diff --git a/src/snes/chip/dsp1/dsp1.cpp b/asnes/chip/dsp1/dsp1.cpp similarity index 95% rename from src/snes/chip/dsp1/dsp1.cpp rename to asnes/chip/dsp1/dsp1.cpp index 3e8679cc..ae71d3bf 100644 --- a/src/snes/chip/dsp1/dsp1.cpp +++ b/asnes/chip/dsp1/dsp1.cpp @@ -1,4 +1,4 @@ -#include +#include #define DSP1_CPP namespace SNES { diff --git a/src/snes/chip/dsp1/dsp1.hpp b/asnes/chip/dsp1/dsp1.hpp similarity index 100% rename from src/snes/chip/dsp1/dsp1.hpp rename to asnes/chip/dsp1/dsp1.hpp diff --git a/src/snes/chip/dsp1/dsp1emu.cpp b/asnes/chip/dsp1/dsp1emu.cpp similarity index 100% rename from src/snes/chip/dsp1/dsp1emu.cpp rename to asnes/chip/dsp1/dsp1emu.cpp diff --git a/src/snes/chip/dsp1/dsp1emu.hpp b/asnes/chip/dsp1/dsp1emu.hpp similarity index 100% rename from src/snes/chip/dsp1/dsp1emu.hpp rename to asnes/chip/dsp1/dsp1emu.hpp diff --git a/src/snes/chip/dsp1/serialization.cpp b/asnes/chip/dsp1/serialization.cpp similarity index 100% rename from src/snes/chip/dsp1/serialization.cpp rename to asnes/chip/dsp1/serialization.cpp diff --git a/src/snes/chip/dsp2/dsp2.cpp b/asnes/chip/dsp2/dsp2.cpp similarity index 99% rename from src/snes/chip/dsp2/dsp2.cpp rename to asnes/chip/dsp2/dsp2.cpp index 2a8ba14d..bb33ba35 100644 --- a/src/snes/chip/dsp2/dsp2.cpp +++ b/asnes/chip/dsp2/dsp2.cpp @@ -1,4 +1,4 @@ -#include +#include #define DSP2_CPP namespace SNES { diff --git a/src/snes/chip/dsp2/dsp2.hpp b/asnes/chip/dsp2/dsp2.hpp similarity index 100% rename from src/snes/chip/dsp2/dsp2.hpp rename to asnes/chip/dsp2/dsp2.hpp diff --git a/src/snes/chip/dsp2/opcodes.cpp b/asnes/chip/dsp2/opcodes.cpp similarity index 100% rename from src/snes/chip/dsp2/opcodes.cpp rename to asnes/chip/dsp2/opcodes.cpp diff --git a/src/snes/chip/dsp2/serialization.cpp b/asnes/chip/dsp2/serialization.cpp similarity index 100% rename from src/snes/chip/dsp2/serialization.cpp rename to asnes/chip/dsp2/serialization.cpp diff --git a/src/snes/chip/dsp3/dsp3.cpp b/asnes/chip/dsp3/dsp3.cpp similarity index 95% rename from src/snes/chip/dsp3/dsp3.cpp rename to asnes/chip/dsp3/dsp3.cpp index 0ec3c32c..bf60c578 100644 --- a/src/snes/chip/dsp3/dsp3.cpp +++ b/asnes/chip/dsp3/dsp3.cpp @@ -1,4 +1,4 @@ -#include +#include #define DSP3_CPP namespace SNES { diff --git a/src/snes/chip/dsp3/dsp3.hpp b/asnes/chip/dsp3/dsp3.hpp similarity index 100% rename from src/snes/chip/dsp3/dsp3.hpp rename to asnes/chip/dsp3/dsp3.hpp diff --git a/src/snes/chip/dsp3/dsp3emu.c b/asnes/chip/dsp3/dsp3emu.c similarity index 100% rename from src/snes/chip/dsp3/dsp3emu.c rename to asnes/chip/dsp3/dsp3emu.c diff --git a/src/snes/chip/dsp4/dsp4.cpp b/asnes/chip/dsp4/dsp4.cpp similarity index 97% rename from src/snes/chip/dsp4/dsp4.cpp rename to asnes/chip/dsp4/dsp4.cpp index 84635eca..c568c728 100644 --- a/src/snes/chip/dsp4/dsp4.cpp +++ b/asnes/chip/dsp4/dsp4.cpp @@ -1,4 +1,4 @@ -#include +#include #define DSP4_CPP namespace SNES { diff --git a/src/snes/chip/dsp4/dsp4.hpp b/asnes/chip/dsp4/dsp4.hpp similarity index 100% rename from src/snes/chip/dsp4/dsp4.hpp rename to asnes/chip/dsp4/dsp4.hpp diff --git a/src/snes/chip/dsp4/dsp4emu.c b/asnes/chip/dsp4/dsp4emu.c similarity index 100% rename from src/snes/chip/dsp4/dsp4emu.c rename to asnes/chip/dsp4/dsp4emu.c diff --git a/src/snes/chip/dsp4/dsp4emu.h b/asnes/chip/dsp4/dsp4emu.h similarity index 100% rename from src/snes/chip/dsp4/dsp4emu.h rename to asnes/chip/dsp4/dsp4emu.h diff --git a/src/snes/chip/msu1/msu1.cpp b/asnes/chip/msu1/msu1.cpp similarity index 99% rename from src/snes/chip/msu1/msu1.cpp rename to asnes/chip/msu1/msu1.cpp index e88f11fc..76ac88d6 100644 --- a/src/snes/chip/msu1/msu1.cpp +++ b/asnes/chip/msu1/msu1.cpp @@ -1,4 +1,4 @@ -#include +#include #define MSU1_CPP namespace SNES { diff --git a/src/snes/chip/msu1/msu1.hpp b/asnes/chip/msu1/msu1.hpp similarity index 100% rename from src/snes/chip/msu1/msu1.hpp rename to asnes/chip/msu1/msu1.hpp diff --git a/src/snes/chip/msu1/serialization.cpp b/asnes/chip/msu1/serialization.cpp similarity index 100% rename from src/snes/chip/msu1/serialization.cpp rename to asnes/chip/msu1/serialization.cpp diff --git a/src/snes/chip/obc1/obc1.cpp b/asnes/chip/obc1/obc1.cpp similarity index 98% rename from src/snes/chip/obc1/obc1.cpp rename to asnes/chip/obc1/obc1.cpp index 52770941..4b79a4c2 100644 --- a/src/snes/chip/obc1/obc1.cpp +++ b/asnes/chip/obc1/obc1.cpp @@ -1,4 +1,4 @@ -#include +#include #define OBC1_CPP namespace SNES { diff --git a/src/snes/chip/obc1/obc1.hpp b/asnes/chip/obc1/obc1.hpp similarity index 100% rename from src/snes/chip/obc1/obc1.hpp rename to asnes/chip/obc1/obc1.hpp diff --git a/src/snes/chip/obc1/serialization.cpp b/asnes/chip/obc1/serialization.cpp similarity index 100% rename from src/snes/chip/obc1/serialization.cpp rename to asnes/chip/obc1/serialization.cpp diff --git a/src/snes/chip/sa1/bus/bus.cpp b/asnes/chip/sa1/bus/bus.cpp similarity index 100% rename from src/snes/chip/sa1/bus/bus.cpp rename to asnes/chip/sa1/bus/bus.cpp diff --git a/src/snes/chip/sa1/bus/bus.hpp b/asnes/chip/sa1/bus/bus.hpp similarity index 100% rename from src/snes/chip/sa1/bus/bus.hpp rename to asnes/chip/sa1/bus/bus.hpp diff --git a/src/snes/chip/sa1/dma/dma.cpp b/asnes/chip/sa1/dma/dma.cpp similarity index 100% rename from src/snes/chip/sa1/dma/dma.cpp rename to asnes/chip/sa1/dma/dma.cpp diff --git a/src/snes/chip/sa1/dma/dma.hpp b/asnes/chip/sa1/dma/dma.hpp similarity index 100% rename from src/snes/chip/sa1/dma/dma.hpp rename to asnes/chip/sa1/dma/dma.hpp diff --git a/src/snes/chip/sa1/memory/memory.cpp b/asnes/chip/sa1/memory/memory.cpp similarity index 100% rename from src/snes/chip/sa1/memory/memory.cpp rename to asnes/chip/sa1/memory/memory.cpp diff --git a/src/snes/chip/sa1/memory/memory.hpp b/asnes/chip/sa1/memory/memory.hpp similarity index 100% rename from src/snes/chip/sa1/memory/memory.hpp rename to asnes/chip/sa1/memory/memory.hpp diff --git a/src/snes/chip/sa1/mmio/mmio.cpp b/asnes/chip/sa1/mmio/mmio.cpp similarity index 100% rename from src/snes/chip/sa1/mmio/mmio.cpp rename to asnes/chip/sa1/mmio/mmio.cpp diff --git a/src/snes/chip/sa1/mmio/mmio.hpp b/asnes/chip/sa1/mmio/mmio.hpp similarity index 100% rename from src/snes/chip/sa1/mmio/mmio.hpp rename to asnes/chip/sa1/mmio/mmio.hpp diff --git a/src/snes/chip/sa1/sa1.cpp b/asnes/chip/sa1/sa1.cpp similarity index 99% rename from src/snes/chip/sa1/sa1.cpp rename to asnes/chip/sa1/sa1.cpp index 7daeb173..5c24a1f7 100644 --- a/src/snes/chip/sa1/sa1.cpp +++ b/asnes/chip/sa1/sa1.cpp @@ -1,4 +1,4 @@ -#include +#include #define SA1_CPP namespace SNES { diff --git a/src/snes/chip/sa1/sa1.hpp b/asnes/chip/sa1/sa1.hpp similarity index 100% rename from src/snes/chip/sa1/sa1.hpp rename to asnes/chip/sa1/sa1.hpp diff --git a/src/snes/chip/sa1/serialization.cpp b/asnes/chip/sa1/serialization.cpp similarity index 100% rename from src/snes/chip/sa1/serialization.cpp rename to asnes/chip/sa1/serialization.cpp diff --git a/src/snes/chip/sdd1/sdd1.cpp b/asnes/chip/sdd1/sdd1.cpp similarity index 99% rename from src/snes/chip/sdd1/sdd1.cpp rename to asnes/chip/sdd1/sdd1.cpp index 6a798547..4a2eabfe 100644 --- a/src/snes/chip/sdd1/sdd1.cpp +++ b/asnes/chip/sdd1/sdd1.cpp @@ -1,4 +1,4 @@ -#include +#include #define SDD1_CPP namespace SNES { diff --git a/src/snes/chip/sdd1/sdd1.hpp b/asnes/chip/sdd1/sdd1.hpp similarity index 100% rename from src/snes/chip/sdd1/sdd1.hpp rename to asnes/chip/sdd1/sdd1.hpp diff --git a/src/snes/chip/sdd1/sdd1emu.cpp b/asnes/chip/sdd1/sdd1emu.cpp similarity index 100% rename from src/snes/chip/sdd1/sdd1emu.cpp rename to asnes/chip/sdd1/sdd1emu.cpp diff --git a/src/snes/chip/sdd1/sdd1emu.hpp b/asnes/chip/sdd1/sdd1emu.hpp similarity index 100% rename from src/snes/chip/sdd1/sdd1emu.hpp rename to asnes/chip/sdd1/sdd1emu.hpp diff --git a/src/snes/chip/sdd1/serialization.cpp b/asnes/chip/sdd1/serialization.cpp similarity index 100% rename from src/snes/chip/sdd1/serialization.cpp rename to asnes/chip/sdd1/serialization.cpp diff --git a/src/snes/chip/serial/serial.cpp b/asnes/chip/serial/serial.cpp similarity index 98% rename from src/snes/chip/serial/serial.cpp rename to asnes/chip/serial/serial.cpp index c257dfe3..b9404adb 100644 --- a/src/snes/chip/serial/serial.cpp +++ b/asnes/chip/serial/serial.cpp @@ -1,4 +1,4 @@ -#include +#include #define SERIAL_CPP namespace SNES { diff --git a/src/snes/chip/serial/serial.hpp b/asnes/chip/serial/serial.hpp similarity index 100% rename from src/snes/chip/serial/serial.hpp rename to asnes/chip/serial/serial.hpp diff --git a/src/snes/chip/serial/serialization.cpp b/asnes/chip/serial/serialization.cpp similarity index 100% rename from src/snes/chip/serial/serialization.cpp rename to asnes/chip/serial/serialization.cpp diff --git a/src/snes/chip/spc7110/decomp.cpp b/asnes/chip/spc7110/decomp.cpp similarity index 100% rename from src/snes/chip/spc7110/decomp.cpp rename to asnes/chip/spc7110/decomp.cpp diff --git a/src/snes/chip/spc7110/decomp.hpp b/asnes/chip/spc7110/decomp.hpp similarity index 100% rename from src/snes/chip/spc7110/decomp.hpp rename to asnes/chip/spc7110/decomp.hpp diff --git a/src/snes/chip/spc7110/serialization.cpp b/asnes/chip/spc7110/serialization.cpp similarity index 100% rename from src/snes/chip/spc7110/serialization.cpp rename to asnes/chip/spc7110/serialization.cpp diff --git a/src/snes/chip/spc7110/spc7110.cpp b/asnes/chip/spc7110/spc7110.cpp similarity index 99% rename from src/snes/chip/spc7110/spc7110.cpp rename to asnes/chip/spc7110/spc7110.cpp index 172731e2..3559a631 100644 --- a/src/snes/chip/spc7110/spc7110.cpp +++ b/asnes/chip/spc7110/spc7110.cpp @@ -1,4 +1,4 @@ -#include +#include #define SPC7110_CPP namespace SNES { diff --git a/src/snes/chip/spc7110/spc7110.hpp b/asnes/chip/spc7110/spc7110.hpp similarity index 100% rename from src/snes/chip/spc7110/spc7110.hpp rename to asnes/chip/spc7110/spc7110.hpp diff --git a/src/snes/chip/srtc/serialization.cpp b/asnes/chip/srtc/serialization.cpp similarity index 100% rename from src/snes/chip/srtc/serialization.cpp rename to asnes/chip/srtc/serialization.cpp diff --git a/src/snes/chip/srtc/srtc.cpp b/asnes/chip/srtc/srtc.cpp similarity index 99% rename from src/snes/chip/srtc/srtc.cpp rename to asnes/chip/srtc/srtc.cpp index 8c8e08e1..965a0ef0 100644 --- a/src/snes/chip/srtc/srtc.cpp +++ b/asnes/chip/srtc/srtc.cpp @@ -1,4 +1,4 @@ -#include +#include #define SRTC_CPP namespace SNES { diff --git a/src/snes/chip/srtc/srtc.hpp b/asnes/chip/srtc/srtc.hpp similarity index 100% rename from src/snes/chip/srtc/srtc.hpp rename to asnes/chip/srtc/srtc.hpp diff --git a/src/snes/chip/st0010/data.hpp b/asnes/chip/st0010/data.hpp similarity index 100% rename from src/snes/chip/st0010/data.hpp rename to asnes/chip/st0010/data.hpp diff --git a/src/snes/chip/st0010/opcodes.cpp b/asnes/chip/st0010/opcodes.cpp similarity index 100% rename from src/snes/chip/st0010/opcodes.cpp rename to asnes/chip/st0010/opcodes.cpp diff --git a/src/snes/chip/st0010/serialization.cpp b/asnes/chip/st0010/serialization.cpp similarity index 100% rename from src/snes/chip/st0010/serialization.cpp rename to asnes/chip/st0010/serialization.cpp diff --git a/src/snes/chip/st0010/st0010.cpp b/asnes/chip/st0010/st0010.cpp similarity index 98% rename from src/snes/chip/st0010/st0010.cpp rename to asnes/chip/st0010/st0010.cpp index 5331af65..295d0f5a 100644 --- a/src/snes/chip/st0010/st0010.cpp +++ b/asnes/chip/st0010/st0010.cpp @@ -1,4 +1,4 @@ -#include +#include #define ST0010_CPP namespace SNES { diff --git a/src/snes/chip/st0010/st0010.hpp b/asnes/chip/st0010/st0010.hpp similarity index 100% rename from src/snes/chip/st0010/st0010.hpp rename to asnes/chip/st0010/st0010.hpp diff --git a/src/snes/chip/st0011/st0011.cpp b/asnes/chip/st0011/st0011.cpp similarity index 86% rename from src/snes/chip/st0011/st0011.cpp rename to asnes/chip/st0011/st0011.cpp index 646e3424..fe8e9c23 100644 --- a/src/snes/chip/st0011/st0011.cpp +++ b/asnes/chip/st0011/st0011.cpp @@ -1,4 +1,4 @@ -#include +#include #define ST0011_CPP namespace SNES { diff --git a/src/snes/chip/st0011/st0011.hpp b/asnes/chip/st0011/st0011.hpp similarity index 100% rename from src/snes/chip/st0011/st0011.hpp rename to asnes/chip/st0011/st0011.hpp diff --git a/src/snes/chip/st0018/st0018.cpp b/asnes/chip/st0018/st0018.cpp similarity index 99% rename from src/snes/chip/st0018/st0018.cpp rename to asnes/chip/st0018/st0018.cpp index 367f09ab..03ef4d0f 100644 --- a/src/snes/chip/st0018/st0018.cpp +++ b/asnes/chip/st0018/st0018.cpp @@ -1,4 +1,4 @@ -#include +#include #define ST0018_CPP namespace SNES { diff --git a/src/snes/chip/st0018/st0018.hpp b/asnes/chip/st0018/st0018.hpp similarity index 100% rename from src/snes/chip/st0018/st0018.hpp rename to asnes/chip/st0018/st0018.hpp diff --git a/src/snes/chip/superfx/bus/bus.cpp b/asnes/chip/superfx/bus/bus.cpp similarity index 100% rename from src/snes/chip/superfx/bus/bus.cpp rename to asnes/chip/superfx/bus/bus.cpp diff --git a/src/snes/chip/superfx/bus/bus.hpp b/asnes/chip/superfx/bus/bus.hpp similarity index 100% rename from src/snes/chip/superfx/bus/bus.hpp rename to asnes/chip/superfx/bus/bus.hpp diff --git a/src/snes/chip/superfx/core/core.cpp b/asnes/chip/superfx/core/core.cpp similarity index 100% rename from src/snes/chip/superfx/core/core.cpp rename to asnes/chip/superfx/core/core.cpp diff --git a/src/snes/chip/superfx/core/core.hpp b/asnes/chip/superfx/core/core.hpp similarity index 100% rename from src/snes/chip/superfx/core/core.hpp rename to asnes/chip/superfx/core/core.hpp diff --git a/src/snes/chip/superfx/core/opcode_table.cpp b/asnes/chip/superfx/core/opcode_table.cpp similarity index 100% rename from src/snes/chip/superfx/core/opcode_table.cpp rename to asnes/chip/superfx/core/opcode_table.cpp diff --git a/src/snes/chip/superfx/core/opcodes.cpp b/asnes/chip/superfx/core/opcodes.cpp similarity index 100% rename from src/snes/chip/superfx/core/opcodes.cpp rename to asnes/chip/superfx/core/opcodes.cpp diff --git a/src/snes/chip/superfx/core/registers.hpp b/asnes/chip/superfx/core/registers.hpp similarity index 100% rename from src/snes/chip/superfx/core/registers.hpp rename to asnes/chip/superfx/core/registers.hpp diff --git a/src/snes/chip/superfx/disasm/disasm.cpp b/asnes/chip/superfx/disasm/disasm.cpp similarity index 100% rename from src/snes/chip/superfx/disasm/disasm.cpp rename to asnes/chip/superfx/disasm/disasm.cpp diff --git a/src/snes/chip/superfx/disasm/disasm.hpp b/asnes/chip/superfx/disasm/disasm.hpp similarity index 100% rename from src/snes/chip/superfx/disasm/disasm.hpp rename to asnes/chip/superfx/disasm/disasm.hpp diff --git a/src/snes/chip/superfx/memory/memory.cpp b/asnes/chip/superfx/memory/memory.cpp similarity index 100% rename from src/snes/chip/superfx/memory/memory.cpp rename to asnes/chip/superfx/memory/memory.cpp diff --git a/src/snes/chip/superfx/memory/memory.hpp b/asnes/chip/superfx/memory/memory.hpp similarity index 100% rename from src/snes/chip/superfx/memory/memory.hpp rename to asnes/chip/superfx/memory/memory.hpp diff --git a/src/snes/chip/superfx/mmio/mmio.cpp b/asnes/chip/superfx/mmio/mmio.cpp similarity index 100% rename from src/snes/chip/superfx/mmio/mmio.cpp rename to asnes/chip/superfx/mmio/mmio.cpp diff --git a/src/snes/chip/superfx/mmio/mmio.hpp b/asnes/chip/superfx/mmio/mmio.hpp similarity index 100% rename from src/snes/chip/superfx/mmio/mmio.hpp rename to asnes/chip/superfx/mmio/mmio.hpp diff --git a/src/snes/chip/superfx/serialization.cpp b/asnes/chip/superfx/serialization.cpp similarity index 100% rename from src/snes/chip/superfx/serialization.cpp rename to asnes/chip/superfx/serialization.cpp diff --git a/src/snes/chip/superfx/superfx.cpp b/asnes/chip/superfx/superfx.cpp similarity index 98% rename from src/snes/chip/superfx/superfx.cpp rename to asnes/chip/superfx/superfx.cpp index 011b80bb..89eaf18b 100644 --- a/src/snes/chip/superfx/superfx.cpp +++ b/asnes/chip/superfx/superfx.cpp @@ -1,4 +1,4 @@ -#include +#include #define SUPERFX_CPP namespace SNES { diff --git a/src/snes/chip/superfx/superfx.hpp b/asnes/chip/superfx/superfx.hpp similarity index 100% rename from src/snes/chip/superfx/superfx.hpp rename to asnes/chip/superfx/superfx.hpp diff --git a/src/snes/chip/superfx/timing/timing.cpp b/asnes/chip/superfx/timing/timing.cpp similarity index 100% rename from src/snes/chip/superfx/timing/timing.cpp rename to asnes/chip/superfx/timing/timing.cpp diff --git a/src/snes/chip/superfx/timing/timing.hpp b/asnes/chip/superfx/timing/timing.hpp similarity index 100% rename from src/snes/chip/superfx/timing/timing.hpp rename to asnes/chip/superfx/timing/timing.hpp diff --git a/src/snes/chip/supergameboy/serialization.cpp b/asnes/chip/supergameboy/serialization.cpp similarity index 100% rename from src/snes/chip/supergameboy/serialization.cpp rename to asnes/chip/supergameboy/serialization.cpp diff --git a/src/snes/chip/supergameboy/supergameboy.cpp b/asnes/chip/supergameboy/supergameboy.cpp similarity index 99% rename from src/snes/chip/supergameboy/supergameboy.cpp rename to asnes/chip/supergameboy/supergameboy.cpp index 719582f7..eee2c8c9 100644 --- a/src/snes/chip/supergameboy/supergameboy.cpp +++ b/asnes/chip/supergameboy/supergameboy.cpp @@ -1,4 +1,4 @@ -#include +#include #define SUPERGAMEBOY_CPP namespace SNES { diff --git a/src/snes/chip/supergameboy/supergameboy.hpp b/asnes/chip/supergameboy/supergameboy.hpp similarity index 100% rename from src/snes/chip/supergameboy/supergameboy.hpp rename to asnes/chip/supergameboy/supergameboy.hpp diff --git a/src/snes/config/config.cpp b/asnes/config/config.cpp similarity index 100% rename from src/snes/config/config.cpp rename to asnes/config/config.cpp diff --git a/src/snes/config/config.hpp b/asnes/config/config.hpp similarity index 100% rename from src/snes/config/config.hpp rename to asnes/config/config.hpp diff --git a/src/snes/cpu/core/algorithms.cpp b/asnes/cpu/core/algorithms.cpp similarity index 100% rename from src/snes/cpu/core/algorithms.cpp rename to asnes/cpu/core/algorithms.cpp diff --git a/src/snes/cpu/core/core.cpp b/asnes/cpu/core/core.cpp similarity index 98% rename from src/snes/cpu/core/core.cpp rename to asnes/cpu/core/core.cpp index bce1ca29..aae4ba67 100644 --- a/src/snes/cpu/core/core.cpp +++ b/asnes/cpu/core/core.cpp @@ -1,4 +1,4 @@ -#include +#include #define CPUCORE_CPP namespace SNES { diff --git a/src/snes/cpu/core/core.hpp b/asnes/cpu/core/core.hpp similarity index 100% rename from src/snes/cpu/core/core.hpp rename to asnes/cpu/core/core.hpp diff --git a/src/snes/cpu/core/disassembler/disassembler.cpp b/asnes/cpu/core/disassembler/disassembler.cpp similarity index 100% rename from src/snes/cpu/core/disassembler/disassembler.cpp rename to asnes/cpu/core/disassembler/disassembler.cpp diff --git a/src/snes/cpu/core/disassembler/disassembler.hpp b/asnes/cpu/core/disassembler/disassembler.hpp similarity index 100% rename from src/snes/cpu/core/disassembler/disassembler.hpp rename to asnes/cpu/core/disassembler/disassembler.hpp diff --git a/src/snes/cpu/core/memory.hpp b/asnes/cpu/core/memory.hpp similarity index 100% rename from src/snes/cpu/core/memory.hpp rename to asnes/cpu/core/memory.hpp diff --git a/src/snes/cpu/core/opcode_misc.cpp b/asnes/cpu/core/opcode_misc.cpp similarity index 100% rename from src/snes/cpu/core/opcode_misc.cpp rename to asnes/cpu/core/opcode_misc.cpp diff --git a/src/snes/cpu/core/opcode_pc.cpp b/asnes/cpu/core/opcode_pc.cpp similarity index 100% rename from src/snes/cpu/core/opcode_pc.cpp rename to asnes/cpu/core/opcode_pc.cpp diff --git a/src/snes/cpu/core/opcode_read.cpp b/asnes/cpu/core/opcode_read.cpp similarity index 100% rename from src/snes/cpu/core/opcode_read.cpp rename to asnes/cpu/core/opcode_read.cpp diff --git a/src/snes/cpu/core/opcode_rmw.cpp b/asnes/cpu/core/opcode_rmw.cpp similarity index 100% rename from src/snes/cpu/core/opcode_rmw.cpp rename to asnes/cpu/core/opcode_rmw.cpp diff --git a/src/snes/cpu/core/opcode_write.cpp b/asnes/cpu/core/opcode_write.cpp similarity index 100% rename from src/snes/cpu/core/opcode_write.cpp rename to asnes/cpu/core/opcode_write.cpp diff --git a/src/snes/cpu/core/registers.hpp b/asnes/cpu/core/registers.hpp similarity index 100% rename from src/snes/cpu/core/registers.hpp rename to asnes/cpu/core/registers.hpp diff --git a/src/snes/cpu/core/serialization.cpp b/asnes/cpu/core/serialization.cpp similarity index 100% rename from src/snes/cpu/core/serialization.cpp rename to asnes/cpu/core/serialization.cpp diff --git a/src/snes/cpu/core/table.cpp b/asnes/cpu/core/table.cpp similarity index 100% rename from src/snes/cpu/core/table.cpp rename to asnes/cpu/core/table.cpp diff --git a/asnes/cpu/cpu.cpp b/asnes/cpu/cpu.cpp new file mode 100644 index 00000000..32bfd41d --- /dev/null +++ b/asnes/cpu/cpu.cpp @@ -0,0 +1,141 @@ +#include + +#define CPU_CPP +namespace SNES { + +#if defined(DEBUGGER) + #include "debugger/debugger.cpp" + CPUDebugger cpu; +#else + CPU cpu; +#endif + +#include "serialization.cpp" +#include "dma/dma.cpp" +#include "memory/memory.cpp" +#include "mmio/mmio.cpp" +#include "timing/timing.cpp" + +void CPU::step(unsigned clocks) { + smp.clock -= clocks * (uint64)smp.frequency; + ppu.clock -= clocks; + for(unsigned i = 0; i < coprocessors.size(); i++) { + Processor &chip = *coprocessors[i]; + chip.clock -= clocks * (uint64)chip.frequency; + } +} + +void CPU::synchronize_smp() { + if(smp.clock < 0) co_switch(smp.thread); +} + +void CPU::synchronize_ppu() { + if(ppu.clock < 0) co_switch(ppu.thread); +} + +void CPU::synchronize_coprocessor() { + for(unsigned i = 0; i < coprocessors.size(); i++) { + Processor &chip = *coprocessors[i]; + if(chip.clock < 0) co_switch(chip.thread); + } +} + +void CPU::Enter() { cpu.enter(); } + +void CPU::enter() { + while(true) { + if(scheduler.sync == Scheduler::SynchronizeMode::CPU) { + scheduler.sync = Scheduler::SynchronizeMode::All; + 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); + } + } + + op_step(); + } +} + +void CPU::op_step() { + (this->*opcode_table[op_readpc()])(); +} + +void CPU::op_irq() { + 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); + regs.pc.b = 0x00; + regs.p.i = 1; + regs.p.d = 0; + rd.h = op_read(status.interrupt_vector + 1); + regs.pc.w = rd.w; +} + +void CPU::power() { + cpu_version = config.cpu.version; + + regs.a = regs.x = regs.y = 0x0000; + regs.s = 0x01ff; + + mmio_power(); + dma_power(); + timing_power(); + + reset(); +} + +void CPU::reset() { + create(Enter, system.cpu_frequency()); + 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; + update_table(); + + mmio_reset(); + dma_reset(); + timing_reset(); + + apu_port[0] = 0x00; + apu_port[1] = 0x00; + apu_port[2] = 0x00; + apu_port[3] = 0x00; +} + +CPU::CPU() { + PPUCounter::scanline = { &CPU::scanline, this }; +} + +CPU::~CPU() { +} + +} diff --git a/asnes/cpu/cpu.hpp b/asnes/cpu/cpu.hpp new file mode 100644 index 00000000..4ad7c83b --- /dev/null +++ b/asnes/cpu/cpu.hpp @@ -0,0 +1,136 @@ +class CPU : public Processor, public PPUCounter, public MMIO, public CPUcore { +public: + //synchronization + array coprocessors; + alwaysinline void step(unsigned clocks); + alwaysinline void synchronize_smp(); + void synchronize_ppu(); + void synchronize_coprocessor(); + + static void Enter(); + void enter(); + debugvirtual void op_step(); + void op_irq(); + bool interrupt_pending() { return status.interrupt_pending; } + + uint8 cpu_version; + + #include "dma/dma.hpp" + #include "memory/memory.hpp" + #include "mmio/mmio.hpp" + #include "timing/timing.hpp" + + 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; + + //$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; + + //$4201 + uint8 pio; + + //$4202-$4203 + uint8 wrmpya; + uint8 wrmpyb; + + //$4204-$4206 + uint16 wrdiva; + uint8 wrdivb; + + //$4207-$420a + uint16 hirq_pos, virq_pos; + + //$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; + + void power(); + void reset(); + + void serialize(serializer&); + CPU(); + ~CPU(); + + friend class CPUDebugger; +}; + +#if defined(DEBUGGER) + #include "debugger/debugger.hpp" + extern CPUDebugger cpu; +#else + extern CPU cpu; +#endif diff --git a/src/snes/cpu/cpu-debugger.cpp b/asnes/cpu/debugger/cpu-debugger.cpp similarity index 100% rename from src/snes/cpu/cpu-debugger.cpp rename to asnes/cpu/debugger/cpu-debugger.cpp diff --git a/src/snes/cpu/cpu-debugger.hpp b/asnes/cpu/debugger/cpu-debugger.hpp similarity index 100% rename from src/snes/cpu/cpu-debugger.hpp rename to asnes/cpu/debugger/cpu-debugger.hpp diff --git a/asnes/cpu/debugger/debugger.cpp b/asnes/cpu/debugger/debugger.cpp new file mode 100644 index 00000000..e22625ea --- /dev/null +++ b/asnes/cpu/debugger/debugger.cpp @@ -0,0 +1,134 @@ +#ifdef CPU_CPP + +void sCPUDebugger::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(); + sCPU::op_step(); + synchronize_smp(); +} + +uint8 sCPUDebugger::op_read(uint32 addr) { + uint8 data = sCPU::op_read(addr); + usage[addr] |= UsageRead; + debugger.breakpoint_test(Debugger::Breakpoint::Source::CPUBus, Debugger::Breakpoint::Mode::Read, addr, data); + return data; +} + +void sCPUDebugger::op_write(uint32 addr, uint8 data) { + sCPU::op_write(addr, data); + usage[addr] |= UsageWrite; + usage[addr] &= ~UsageExec; + debugger.breakpoint_test(Debugger::Breakpoint::Source::CPUBus, Debugger::Breakpoint::Mode::Write, addr, data); +} + +sCPUDebugger::sCPUDebugger() { + usage = new uint8[1 << 24](); + opcode_pc = 0x8000; +} + +sCPUDebugger::~sCPUDebugger() { + delete[] usage; +} + +//=========== +//CPUDebugger +//=========== + +//internal +unsigned sCPUDebugger::mdr() { return regs.mdr; } + +//$2181-$2183 +unsigned sCPUDebugger::wram_address() { return status.wram_addr; } + +//$4016 +bool sCPUDebugger::joypad_strobe_latch() { return status.joypad_strobe_latch; } + +//$4200 +bool sCPUDebugger::nmi_enable() { return status.nmi_enabled; } +bool sCPUDebugger::hirq_enable() { return status.hirq_enabled; } +bool sCPUDebugger::virq_enable() { return status.virq_enabled; } +bool sCPUDebugger::auto_joypad_poll() { return status.auto_joypad_poll; } + +//$4201 +unsigned sCPUDebugger::pio_bits() { return status.pio; } + +//$4202 +unsigned sCPUDebugger::multiplicand() { return status.wrmpya; } + +//$4203 +unsigned sCPUDebugger::multiplier() { return status.wrmpyb; } + +//$4204-$4205 +unsigned sCPUDebugger::dividend() { return status.wrdiva; } + +//$4206 +unsigned sCPUDebugger::divisor() { return status.wrdivb; } + +//$4207-$4208 +unsigned sCPUDebugger::htime() { return status.hirq_pos; } + +//$4209-$420a +unsigned sCPUDebugger::vtime() { return status.virq_pos; } + +//$420b +unsigned sCPUDebugger::dma_enable() { + unsigned result = 0; + for(unsigned n = 0; n < 8; n++) { + result |= channel[n].dma_enabled << n; + } + return result; +} + +//$420c +unsigned sCPUDebugger::hdma_enable() { + unsigned result = 0; + for(unsigned n = 0; n < 8; n++) { + result |= channel[n].hdma_enabled << n; + } + return result; +} + +//$420d +bool sCPUDebugger::fastrom_enable() { return status.rom_speed; } + +//$43x0 +bool sCPUDebugger::dma_direction(unsigned n) { return channel[n].direction; } +bool sCPUDebugger::dma_indirect(unsigned n) { return channel[n].hdma_indirect; } +bool sCPUDebugger::dma_reverse_transfer(unsigned n) { return channel[n].reversexfer; } +bool sCPUDebugger::dma_fixed_transfer(unsigned n) { return channel[n].fixedxfer; } +unsigned sCPUDebugger::dma_transfer_mode(unsigned n) { return channel[n].xfermode; } + +//$43x1 +unsigned sCPUDebugger::dma_bbus_address(unsigned n) { return 0x2100 + channel[n].destaddr; } + +//$43x2-$43x3 +unsigned sCPUDebugger::dma_abus_address(unsigned n) { return channel[n].srcaddr; } + +//$43x4 +unsigned sCPUDebugger::dma_abus_bank(unsigned n) { return channel[n].srcbank; } + +//$43x5-$43x6 +unsigned sCPUDebugger::dma_transfer_size(unsigned n) { return channel[n].xfersize; } + +//$43x7 +unsigned sCPUDebugger::dma_indirect_bank(unsigned n) { return channel[n].hdma_ibank; } + +//$43x8-$43x9 +unsigned sCPUDebugger::dma_table_address(unsigned n) { return channel[n].hdma_addr; } + +//$43xa +unsigned sCPUDebugger::dma_line_counter(unsigned n) { return channel[n].hdma_line_counter; } + +#endif diff --git a/src/snes/cpu/scpu/debugger/debugger.hpp b/asnes/cpu/debugger/debugger.hpp similarity index 100% rename from src/snes/cpu/scpu/debugger/debugger.hpp rename to asnes/cpu/debugger/debugger.hpp diff --git a/asnes/cpu/dma/dma.cpp b/asnes/cpu/dma/dma.cpp new file mode 100644 index 00000000..9aad43f2 --- /dev/null +++ b/asnes/cpu/dma/dma.cpp @@ -0,0 +1,292 @@ +#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/src/snes/cpu/scpu/dma/dma.hpp b/asnes/cpu/dma/dma.hpp similarity index 100% rename from src/snes/cpu/scpu/dma/dma.hpp rename to asnes/cpu/dma/dma.hpp diff --git a/asnes/cpu/memory/memory.cpp b/asnes/cpu/memory/memory.cpp new file mode 100644 index 00000000..bf48dee5 --- /dev/null +++ b/asnes/cpu/memory/memory.cpp @@ -0,0 +1,38 @@ +#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/src/snes/cpu/scpu/memory/memory.hpp b/asnes/cpu/memory/memory.hpp similarity index 100% rename from src/snes/cpu/scpu/memory/memory.hpp rename to asnes/cpu/memory/memory.hpp diff --git a/asnes/cpu/mmio/mmio.cpp b/asnes/cpu/mmio/mmio.cpp new file mode 100644 index 00000000..10df666f --- /dev/null +++ b/asnes/cpu/mmio/mmio.cpp @@ -0,0 +1,541 @@ +#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/src/snes/cpu/scpu/mmio/mmio.hpp b/asnes/cpu/mmio/mmio.hpp similarity index 100% rename from src/snes/cpu/scpu/mmio/mmio.hpp rename to asnes/cpu/mmio/mmio.hpp diff --git a/asnes/cpu/serialization.cpp b/asnes/cpu/serialization.cpp new file mode 100644 index 00000000..cbf31efb --- /dev/null +++ b/asnes/cpu/serialization.cpp @@ -0,0 +1,118 @@ +#ifdef CPU_CPP + +void CPU::serialize(serializer &s) { + CPUcore::core_serialize(s); + Processor::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/asnes/cpu/timing/irq.cpp b/asnes/cpu/timing/irq.cpp new file mode 100644 index 00000000..506a435e --- /dev/null +++ b/asnes/cpu/timing/irq.cpp @@ -0,0 +1,106 @@ +#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/asnes/cpu/timing/joypad.cpp b/asnes/cpu/timing/joypad.cpp new file mode 100644 index 00000000..d00cdccb --- /dev/null +++ b/asnes/cpu/timing/joypad.cpp @@ -0,0 +1,28 @@ +#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/asnes/cpu/timing/timing.cpp b/asnes/cpu/timing/timing.cpp new file mode 100644 index 00000000..39dd3a3a --- /dev/null +++ b/asnes/cpu/timing/timing.cpp @@ -0,0 +1,195 @@ +#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/src/snes/cpu/scpu/timing/timing.hpp b/asnes/cpu/timing/timing.hpp similarity index 100% rename from src/snes/cpu/scpu/timing/timing.hpp rename to asnes/cpu/timing/timing.hpp diff --git a/src/snes/debugger/debugger.cpp b/asnes/debugger/debugger.cpp similarity index 100% rename from src/snes/debugger/debugger.cpp rename to asnes/debugger/debugger.cpp diff --git a/src/snes/debugger/debugger.hpp b/asnes/debugger/debugger.hpp similarity index 100% rename from src/snes/debugger/debugger.hpp rename to asnes/debugger/debugger.hpp diff --git a/asnes/dsp/brr.cpp b/asnes/dsp/brr.cpp new file mode 100644 index 00000000..abdf2a0d --- /dev/null +++ b/asnes/dsp/brr.cpp @@ -0,0 +1,62 @@ +#ifdef DSP_CPP + +void DSP::brr_decode(voice_t &v) { + //state.t_brr_byte = ram[v.brr_addr + v.brr_offset] cached from previous clock cycle + int nybbles = (state.t_brr_byte << 8) + memory::apuram[(uint16)(v.brr_addr + v.brr_offset + 1)]; + + const int filter = (state.t_brr_header >> 2) & 3; + const int scale = (state.t_brr_header >> 4); + + //decode four samples + for(unsigned i = 0; i < 4; i++) { + //bits 12-15 = current nybble; sign extend, then shift right to 4-bit precision + //result: s = 4-bit sign-extended sample value + int s = (int16)nybbles >> 12; + nybbles <<= 4; //slide nybble so that on next loop iteration, bits 12-15 = current nybble + + if(scale <= 12) { + s <<= scale; + s >>= 1; + } else { + s &= ~0x7ff; + } + + //apply IIR filter (2 is the most commonly used) + const int p1 = v.buffer[v.buf_pos - 1]; + const int p2 = v.buffer[v.buf_pos - 2] >> 1; + + switch(filter) { + case 0: break; //no filter + + case 1: { + //s += p1 * 0.46875 + s += p1 >> 1; + s += (-p1) >> 5; + } break; + + case 2: { + //s += p1 * 0.953125 - p2 * 0.46875 + s += p1; + s -= p2; + s += p2 >> 4; + s += (p1 * -3) >> 6; + } break; + + case 3: { + //s += p1 * 0.8984375 - p2 * 0.40625 + s += p1; + s -= p2; + s += (p1 * -13) >> 7; + s += (p2 * 3) >> 4; + } break; + } + + //adjust and write sample + s = sclamp<16>(s); + s = (int16)(s << 1); + v.buffer.write(v.buf_pos++, s); + if(v.buf_pos >= brr_buf_size) v.buf_pos = 0; + } +} + +#endif diff --git a/asnes/dsp/counter.cpp b/asnes/dsp/counter.cpp new file mode 100644 index 00000000..f65fdd26 --- /dev/null +++ b/asnes/dsp/counter.cpp @@ -0,0 +1,52 @@ +#ifdef DSP_CPP + +//counter_rate = number of samples per counter event +//all rates are evenly divisible by counter_range (0x7800, 30720, or 2048 * 5 * 3) +//note that rate[0] is a special case, which never triggers + +const uint16 DSP::counter_rate[32] = { + 0, 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, +}; + +//counter_offset = counter offset from zero +//counters do not appear to be aligned at zero for all rates + +const uint16 DSP::counter_offset[32] = { + 0, 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 DSP::counter_tick() { + state.counter--; + if(state.counter < 0) state.counter = counter_range - 1; +} + +//return true if counter event should trigger + +inline bool DSP::counter_poll(unsigned rate) { + if(rate == 0) return false; + return (((unsigned)state.counter + counter_offset[rate]) % counter_rate[rate]) == 0; +} + +#endif diff --git a/asnes/dsp/debugger/debugger.cpp b/asnes/dsp/debugger/debugger.cpp new file mode 100644 index 00000000..838a08d8 --- /dev/null +++ b/asnes/dsp/debugger/debugger.cpp @@ -0,0 +1,36 @@ +#ifdef DSP_CPP + +//=========== +//DSPDebugger +//=========== + +unsigned sDSPDebugger::main_volume_left() { return state.regs[0x0c]; } +unsigned sDSPDebugger::main_volume_right() { return state.regs[0x1c]; } +unsigned sDSPDebugger::echo_volume_left() { return state.regs[0x2c]; } +unsigned sDSPDebugger::echo_volume_right() { return state.regs[0x3c]; } +unsigned sDSPDebugger::key_on() { return state.regs[0x4c]; } +unsigned sDSPDebugger::key_off() { return state.regs[0x5c]; } +bool sDSPDebugger::flag_reset() { return state.regs[0x6c] & 0x80; } +bool sDSPDebugger::flag_mute() { return state.regs[0x6c] & 0x40; } +bool sDSPDebugger::flag_echo_disable() { return state.regs[0x6c] & 0x20; } +unsigned sDSPDebugger::flag_noise_clock() { return state.regs[0x6c] & 0x1f; } +unsigned sDSPDebugger::source_end_block() { return state.regs[0x7c]; } +unsigned sDSPDebugger::echo_feedback() { return state.regs[0x0d]; } +unsigned sDSPDebugger::pitch_modulation_enable() { return state.regs[0x2d]; } +unsigned sDSPDebugger::noise_enable() { return state.regs[0x3d]; } +unsigned sDSPDebugger::echo_enable() { return state.regs[0x4d]; } +unsigned sDSPDebugger::source_directory() { return state.regs[0x5d]; } +unsigned sDSPDebugger::echo_start_address() { return state.regs[0x6d]; } +unsigned sDSPDebugger::echo_directory() { return state.regs[0x7d]; } +unsigned sDSPDebugger::echo_filter_coefficient(unsigned n) { return state.regs[(n << 4) + 0x0f]; } +unsigned sDSPDebugger::voice_volume_left(unsigned n) { return state.regs[(n << 4) + 0x00]; } +unsigned sDSPDebugger::voice_volume_right(unsigned n) { return state.regs[(n << 4) + 0x01]; } +unsigned sDSPDebugger::voice_pitch_height(unsigned n) { return state.regs[(n << 4) + 0x02] + (state.regs[(n << 4) + 0x03] << 8); } +unsigned sDSPDebugger::voice_source_number(unsigned n) { return state.regs[(n << 4) + 0x04]; } +unsigned sDSPDebugger::voice_adsr1(unsigned n) { return state.regs[(n << 4) + 0x05]; } +unsigned sDSPDebugger::voice_adsr2(unsigned n) { return state.regs[(n << 4) + 0x06]; } +unsigned sDSPDebugger::voice_gain(unsigned n) { return state.regs[(n << 4) + 0x07]; } +unsigned sDSPDebugger::voice_envx(unsigned n) { return state.regs[(n << 4) + 0x08]; } +unsigned sDSPDebugger::voice_outx(unsigned n) { return state.regs[(n << 4) + 0x09]; } + +#endif diff --git a/src/snes/dsp/sdsp/debugger/debugger.hpp b/asnes/dsp/debugger/debugger.hpp similarity index 100% rename from src/snes/dsp/sdsp/debugger/debugger.hpp rename to asnes/dsp/debugger/debugger.hpp diff --git a/src/snes/dsp/dsp-debugger.cpp b/asnes/dsp/debugger/dsp-debugger.cpp similarity index 100% rename from src/snes/dsp/dsp-debugger.cpp rename to asnes/dsp/debugger/dsp-debugger.cpp diff --git a/src/snes/dsp/dsp-debugger.hpp b/asnes/dsp/debugger/dsp-debugger.hpp similarity index 100% rename from src/snes/dsp/dsp-debugger.hpp rename to asnes/dsp/debugger/dsp-debugger.hpp diff --git a/asnes/dsp/dsp.cpp b/asnes/dsp/dsp.cpp new file mode 100644 index 00000000..52edd1cf --- /dev/null +++ b/asnes/dsp/dsp.cpp @@ -0,0 +1,304 @@ +#include + +#define DSP_CPP +namespace SNES { + +#if defined(DEBUGGER) + #include "debugger/debugger.cpp" + DSPDebugger dsp; +#else + DSP dsp; +#endif + +#include "serialization.cpp" + +#define REG(n) state.regs[r_##n] +#define VREG(n) state.regs[v.vidx + v_##n] + +#include "gaussian.cpp" +#include "counter.cpp" +#include "envelope.cpp" +#include "brr.cpp" +#include "misc.cpp" +#include "voice.cpp" +#include "echo.cpp" + +/* timing */ + +void DSP::step(unsigned clocks) { + clock += clocks; +} + +void DSP::synchronize_smp() { + if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(smp.thread); +} + +void DSP::Enter() { dsp.enter(); } + +void DSP::enter() { + while(true) { + if(scheduler.sync == Scheduler::SynchronizeMode::All) { + scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); + } + + voice_5(voice[0]); + voice_2(voice[1]); + tick(); + + voice_6(voice[0]); + voice_3(voice[1]); + tick(); + + voice_7(voice[0]); + voice_4(voice[1]); + voice_1(voice[3]); + tick(); + + voice_8(voice[0]); + voice_5(voice[1]); + voice_2(voice[2]); + tick(); + + voice_9(voice[0]); + voice_6(voice[1]); + voice_3(voice[2]); + tick(); + + voice_7(voice[1]); + voice_4(voice[2]); + voice_1(voice[4]); + tick(); + + voice_8(voice[1]); + voice_5(voice[2]); + voice_2(voice[3]); + tick(); + + voice_9(voice[1]); + voice_6(voice[2]); + voice_3(voice[3]); + tick(); + + voice_7(voice[2]); + voice_4(voice[3]); + voice_1(voice[5]); + tick(); + + voice_8(voice[2]); + voice_5(voice[3]); + voice_2(voice[4]); + tick(); + + voice_9(voice[2]); + voice_6(voice[3]); + voice_3(voice[4]); + tick(); + + voice_7(voice[3]); + voice_4(voice[4]); + voice_1(voice[6]); + tick(); + + voice_8(voice[3]); + voice_5(voice[4]); + voice_2(voice[5]); + tick(); + + voice_9(voice[3]); + voice_6(voice[4]); + voice_3(voice[5]); + tick(); + + voice_7(voice[4]); + voice_4(voice[5]); + voice_1(voice[7]); + tick(); + + voice_8(voice[4]); + voice_5(voice[5]); + voice_2(voice[6]); + tick(); + + voice_9(voice[4]); + voice_6(voice[5]); + voice_3(voice[6]); + tick(); + + voice_1(voice[0]); + voice_7(voice[5]); + voice_4(voice[6]); + tick(); + + voice_8(voice[5]); + voice_5(voice[6]); + voice_2(voice[7]); + tick(); + + voice_9(voice[5]); + voice_6(voice[6]); + voice_3(voice[7]); + tick(); + + voice_1(voice[1]); + voice_7(voice[6]); + voice_4(voice[7]); + tick(); + + voice_8(voice[6]); + voice_5(voice[7]); + voice_2(voice[0]); + tick(); + + voice_3a(voice[0]); + voice_9(voice[6]); + voice_6(voice[7]); + echo_22(); + tick(); + + voice_7(voice[7]); + echo_23(); + tick(); + + voice_8(voice[7]); + echo_24(); + tick(); + + voice_3b(voice[0]); + voice_9(voice[7]); + echo_25(); + tick(); + + echo_26(); + tick(); + + misc_27(); + echo_27(); + tick(); + + misc_28(); + echo_28(); + tick(); + + misc_29(); + echo_29(); + tick(); + + misc_30(); + voice_3c(voice[0]); + echo_30(); + tick(); + + voice_4(voice[0]); + voice_1(voice[2]); + tick(); + } +} + +void DSP::tick() { + step(3 * 8); + synchronize_smp(); +} + +/* register interface for S-SMP $00f2,$00f3 */ + +uint8 DSP::read(uint8 addr) { + return state.regs[addr]; +} + +void DSP::write(uint8 addr, uint8 data) { + state.regs[addr] = data; + + if((addr & 0x0f) == v_envx) { + state.envx_buf = data; + } else if((addr & 0x0f) == v_outx) { + state.outx_buf = data; + } else if(addr == r_kon) { + state.new_kon = data; + } else if(addr == r_endx) { + //always cleared, regardless of data written + state.endx_buf = 0; + state.regs[r_endx] = 0; + } +} + +/* initialization */ + +void DSP::power() { + memset(&state.regs, 0, sizeof state.regs); + state.echo_hist_pos = 0; + state.every_other_sample = false; + state.kon = 0; + state.noise = 0; + state.counter = 0; + state.echo_offset = 0; + state.echo_length = 0; + state.new_kon = 0; + state.endx_buf = 0; + state.envx_buf = 0; + state.outx_buf = 0; + state.t_pmon = 0; + state.t_non = 0; + state.t_eon = 0; + state.t_dir = 0; + state.t_koff = 0; + state.t_brr_next_addr = 0; + state.t_adsr0 = 0; + state.t_brr_header = 0; + state.t_brr_byte = 0; + state.t_srcn = 0; + state.t_esa = 0; + state.t_echo_disabled = 0; + state.t_dir_addr = 0; + state.t_pitch = 0; + state.t_output = 0; + state.t_looped = 0; + state.t_echo_ptr = 0; + state.t_main_out[0] = state.t_main_out[1] = 0; + state.t_echo_out[0] = state.t_echo_out[1] = 0; + state.t_echo_in[0] = state.t_echo_in[1] = 0; + + for(unsigned i = 0; i < 8; i++) { + voice[i].buf_pos = 0; + voice[i].interp_pos = 0; + voice[i].brr_addr = 0; + voice[i].brr_offset = 1; + voice[i].vbit = 1 << i; + voice[i].vidx = i * 0x10; + voice[i].kon_delay = 0; + voice[i].env_mode = env_release; + voice[i].env = 0; + voice[i].t_envx_out = 0; + voice[i].hidden_env = 0; + } + + reset(); +} + +void DSP::reset() { + create(Enter, system.apu_frequency()); + + REG(flg) = 0xe0; + + state.noise = 0x4000; + state.echo_hist_pos = 0; + state.every_other_sample = 1; + state.echo_offset = 0; + state.counter = 0; +} + +DSP::DSP() { + static_assert(sizeof(int) >= 32 / 8, "int >= 32-bits"); + static_assert((int8)0x80 == -0x80, "8-bit sign extension"); + static_assert((int16)0x8000 == -0x8000, "16-bit sign extension"); + static_assert((uint16)0xffff0000 == 0, "16-bit unsigned clip"); + static_assert((-1 >> 1) == -1, "arithmetic shift right"); + + //-0x8000 <= n <= +0x7fff + assert(sclamp<16>(+0x8000) == +0x7fff); + assert(sclamp<16>(-0x8001) == -0x8000); +} + +DSP::~DSP() { +} + +} diff --git a/asnes/dsp/dsp.hpp b/asnes/dsp/dsp.hpp new file mode 100644 index 00000000..eb7885b8 --- /dev/null +++ b/asnes/dsp/dsp.hpp @@ -0,0 +1,179 @@ +class DSP : public Processor { +public: + //synchronization + alwaysinline void step(unsigned clocks); + alwaysinline void synchronize_smp(); + + static void Enter(); + void enter(); + void tick(); + + uint8 read(uint8 addr); + void write(uint8 addr, uint8 data); + + void power(); + void reset(); + + void serialize(serializer&); + DSP(); + ~DSP(); + +protected: + //global registers + enum global_reg_t { + 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 voice_reg_t { + 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, + }; + + //internal envelope modes + enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; + + //internal constants + enum { echo_hist_size = 8 }; + enum { brr_buf_size = 12 }; + enum { brr_block_size = 9 }; + + //global state + struct state_t { + uint8 regs[128]; + + modulo_array echo_hist[2]; //echo history keeps most recent 8 samples + int echo_hist_pos; + + bool 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 + + //hidden registers also written to when main register is written to + int new_kon; + int endx_buf; + int envx_buf; + int 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 before 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_disabled; + + //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]; + } state; + + //voice state + struct voice_t { + modulo_array buffer; //decoded samples + 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 + int vbit; //bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc + int vidx; //voice channel register index: 0x00 for voice 0, 0x10 for voice 1, etc + int kon_delay; //KON delay/current setup phase + int env_mode; + int env; //current envelope level + int t_envx_out; + int hidden_env; //used by GAIN mode 7, very obscure quirk + } voice[8]; + + //gaussian + static const int16 gaussian_table[512]; + int gaussian_interpolate(const voice_t &v); + + //counter + enum { counter_range = 2048 * 5 * 3 }; //30720 (0x7800) + static const uint16 counter_rate[32]; + static const uint16 counter_offset[32]; + void counter_tick(); + bool counter_poll(unsigned rate); + + //envelope + void envelope_run(voice_t &v); + + //brr + void brr_decode(voice_t &v); + + //misc + void misc_27(); + void misc_28(); + void misc_29(); + void misc_30(); + + //voice + void voice_output(voice_t &v, bool channel); + void voice_1 (voice_t &v); + void voice_2 (voice_t &v); + void voice_3 (voice_t &v); + void voice_3a(voice_t &v); + void voice_3b(voice_t &v); + void voice_3c(voice_t &v); + void voice_4 (voice_t &v); + void voice_5 (voice_t &v); + void voice_6 (voice_t &v); + void voice_7 (voice_t &v); + void voice_8 (voice_t &v); + void voice_9 (voice_t &v); + + //echo + int calc_fir(int i, bool channel); + int echo_output(bool channel); + void echo_read(bool channel); + void echo_write(bool channel); + 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(); + + friend class DSPDebugger; +}; + +#if defined(DEBUGGER) + #include "debugger/debugger.hpp" + extern DSPDebugger dsp; +#else + extern DSP dsp; +#endif diff --git a/asnes/dsp/echo.cpp b/asnes/dsp/echo.cpp new file mode 100644 index 00000000..c0cf5f37 --- /dev/null +++ b/asnes/dsp/echo.cpp @@ -0,0 +1,135 @@ +#ifdef DSP_CPP + +int DSP::calc_fir(int i, bool channel) { + int s = state.echo_hist[channel][state.echo_hist_pos + i + 1]; + return (s * (int8)REG(fir + i * 0x10)) >> 6; +} + +int DSP::echo_output(bool channel) { + int output = (int16)((state.t_main_out[channel] * (int8)REG(mvoll + channel * 0x10)) >> 7) + + (int16)((state.t_echo_in [channel] * (int8)REG(evoll + channel * 0x10)) >> 7); + return sclamp<16>(output); +} + +void DSP::echo_read(bool channel) { + unsigned addr = state.t_echo_ptr + channel * 2; + uint8 lo = memory::apuram[(uint16)(addr + 0)]; + uint8 hi = memory::apuram[(uint16)(addr + 1)]; + int s = (int16)((hi << 8) + lo); + state.echo_hist[channel].write(state.echo_hist_pos, s >> 1); +} + +void DSP::echo_write(bool channel) { + if(!(state.t_echo_disabled & 0x20)) { + unsigned addr = state.t_echo_ptr + channel * 2; + int s = state.t_echo_out[channel]; + memory::apuram[(uint16)(addr + 0)] = s; + memory::apuram[(uint16)(addr + 1)] = s >> 8; + } + + state.t_echo_out[channel] = 0; +} + +void DSP::echo_22() { + //history + state.echo_hist_pos++; + if(state.echo_hist_pos >= echo_hist_size) state.echo_hist_pos = 0; + + state.t_echo_ptr = (uint16)((state.t_esa << 8) + state.echo_offset); + echo_read(0); + + //FIR + int l = calc_fir(0, 0); + int r = calc_fir(0, 1); + + state.t_echo_in[0] = l; + state.t_echo_in[1] = r; +} + +void DSP::echo_23() { + int l = calc_fir(1, 0) + calc_fir(2, 0); + int r = calc_fir(1, 1) + calc_fir(2, 1); + + state.t_echo_in[0] += l; + state.t_echo_in[1] += r; + + echo_read(1); +} + +void DSP::echo_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); + + state.t_echo_in[0] += l; + state.t_echo_in[1] += r; +} + +void DSP::echo_25() { + int l = state.t_echo_in[0] + calc_fir(6, 0); + int r = state.t_echo_in[1] + calc_fir(6, 1); + + l = (int16)l; + r = (int16)r; + + l += (int16)calc_fir(7, 0); + r += (int16)calc_fir(7, 1); + + state.t_echo_in[0] = sclamp<16>(l) & ~1; + state.t_echo_in[1] = sclamp<16>(r) & ~1; +} + +void DSP::echo_26() { + //left output volumes + //(save sample for next clock so we can output both together) + state.t_main_out[0] = echo_output(0); + + //echo feedback + int l = state.t_echo_out[0] + (int16)((state.t_echo_in[0] * (int8)REG(efb)) >> 7); + int r = state.t_echo_out[1] + (int16)((state.t_echo_in[1] * (int8)REG(efb)) >> 7); + + state.t_echo_out[0] = sclamp<16>(l) & ~1; + state.t_echo_out[1] = sclamp<16>(r) & ~1; +} + +void DSP::echo_27() { + //output + int outl = state.t_main_out[0]; + int outr = echo_output(1); + state.t_main_out[0] = 0; + state.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) { + outl = 0; + outr = 0; + } + + //output sample to DAC + audio.sample(outl, outr); +} + +void DSP::echo_28() { + state.t_echo_disabled = REG(flg); +} + +void DSP::echo_29() { + state.t_esa = REG(esa); + + if(!state.echo_offset) state.echo_length = (REG(edl) & 0x0f) << 11; + + state.echo_offset += 4; + if(state.echo_offset >= state.echo_length) state.echo_offset = 0; + + //write left echo + echo_write(0); + + state.t_echo_disabled = REG(flg); +} + +void DSP::echo_30() { + //write right echo + echo_write(1); +} + +#endif diff --git a/asnes/dsp/envelope.cpp b/asnes/dsp/envelope.cpp new file mode 100644 index 00000000..9ae0d3e0 --- /dev/null +++ b/asnes/dsp/envelope.cpp @@ -0,0 +1,62 @@ +#ifdef DSP_CPP + +void DSP::envelope_run(voice_t &v) { + int env = v.env; + + if(v.env_mode == env_release) { //60% + env -= 0x8; + if(env < 0) env = 0; + v.env = env; + return; + } + + int rate; + int env_data = VREG(adsr1); + if(state.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 = ((state.t_adsr0 >> 3) & 0x0e) + 0x10; + } + } else { //env_attack + rate = ((state.t_adsr0 & 0x0f) << 1) + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } else { //GAIN + env_data = VREG(gain); + int mode = env_data >> 5; + if(mode < 4) { //direct + env = env_data << 4; + 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 underflowing also triggers this + if((unsigned)env > 0x7ff) { + env = (env < 0 ? 0 : 0x7ff); + if(v.env_mode == env_attack) v.env_mode = env_decay; + } + + if(counter_poll(rate) == true) v.env = env; +} + +#endif diff --git a/asnes/dsp/gaussian.cpp b/asnes/dsp/gaussian.cpp new file mode 100644 index 00000000..80aed8ad --- /dev/null +++ b/asnes/dsp/gaussian.cpp @@ -0,0 +1,54 @@ +#ifdef DSP_CPP + +const int16 DSP::gaussian_table[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, +}; + +int DSP::gaussian_interpolate(const voice_t &v) { + //make pointers into gaussian table based on fractional position between samples + int offset = (v.interp_pos >> 4) & 0xff; + const int16 *fwd = gaussian_table + 255 - offset; + const int16 *rev = gaussian_table + offset; //mirror left half of gaussian table + + offset = v.buf_pos + (v.interp_pos >> 12); + int output; + output = (fwd[ 0] * v.buffer[offset + 0]) >> 11; + output += (fwd[256] * v.buffer[offset + 1]) >> 11; + output += (rev[256] * v.buffer[offset + 2]) >> 11; + output = (int16)output; + output += (rev[ 0] * v.buffer[offset + 3]) >> 11; + return sclamp<16>(output) & ~1; +} + +#endif diff --git a/asnes/dsp/misc.cpp b/asnes/dsp/misc.cpp new file mode 100644 index 00000000..244fc51f --- /dev/null +++ b/asnes/dsp/misc.cpp @@ -0,0 +1,35 @@ +#ifdef DSP_CPP + +void DSP::misc_27() { + state.t_pmon = REG(pmon) & ~1; //voice 0 doesn't support PMON +} + +void DSP::misc_28() { + state.t_non = REG(non); + state.t_eon = REG(eon); + state.t_dir = REG(dir); +} + +void DSP::misc_29() { + state.every_other_sample ^= 1; + if(state.every_other_sample) { + state.new_kon &= ~state.kon; //clears KON 63 clocks after it was last read + } +} + +void DSP::misc_30() { + if(state.every_other_sample) { + state.kon = state.new_kon; + state.t_koff = REG(koff); + } + + counter_tick(); + + //noise + if(counter_poll(REG(flg) & 0x1f) == true) { + int feedback = (state.noise << 13) ^ (state.noise << 14); + state.noise = (feedback & 0x4000) ^ (state.noise >> 1); + } +} + +#endif diff --git a/asnes/dsp/serialization.cpp b/asnes/dsp/serialization.cpp new file mode 100644 index 00000000..0595c11e --- /dev/null +++ b/asnes/dsp/serialization.cpp @@ -0,0 +1,66 @@ +#ifdef DSP_CPP + +void DSP::serialize(serializer &s) { + Processor::serialize(s); + + s.array(state.regs, 128); + state.echo_hist[0].serialize(s); + state.echo_hist[1].serialize(s); + s.integer(state.echo_hist_pos); + + s.integer(state.every_other_sample); + s.integer(state.kon); + s.integer(state.noise); + s.integer(state.counter); + s.integer(state.echo_offset); + s.integer(state.echo_length); + + s.integer(state.new_kon); + s.integer(state.endx_buf); + s.integer(state.envx_buf); + s.integer(state.outx_buf); + + s.integer(state.t_pmon); + s.integer(state.t_non); + s.integer(state.t_eon); + s.integer(state.t_dir); + s.integer(state.t_koff); + + s.integer(state.t_brr_next_addr); + s.integer(state.t_adsr0); + s.integer(state.t_brr_header); + s.integer(state.t_brr_byte); + s.integer(state.t_srcn); + s.integer(state.t_esa); + s.integer(state.t_echo_disabled); + + s.integer(state.t_dir_addr); + s.integer(state.t_pitch); + s.integer(state.t_output); + s.integer(state.t_looped); + s.integer(state.t_echo_ptr); + + s.integer(state.t_main_out[0]); + s.integer(state.t_main_out[1]); + s.integer(state.t_echo_out[0]); + s.integer(state.t_echo_out[1]); + s.integer(state.t_echo_in [0]); + s.integer(state.t_echo_in [1]); + + for(unsigned n = 0; n < 8; n++) { + voice[n].buffer.serialize(s); + s.integer(voice[n].buf_pos); + s.integer(voice[n].interp_pos); + s.integer(voice[n].brr_addr); + s.integer(voice[n].brr_offset); + s.integer(voice[n].vbit); + s.integer(voice[n].vidx); + s.integer(voice[n].kon_delay); + s.integer(voice[n].env_mode); + s.integer(voice[n].env); + s.integer(voice[n].t_envx_out); + s.integer(voice[n].hidden_env); + } +} + +#endif diff --git a/asnes/dsp/voice.cpp b/asnes/dsp/voice.cpp new file mode 100644 index 00000000..2e882f21 --- /dev/null +++ b/asnes/dsp/voice.cpp @@ -0,0 +1,174 @@ +#ifdef DSP_CPP + +inline void DSP::voice_output(voice_t &v, bool channel) { + //apply left/right volume + int amp = (state.t_output * (int8)VREG(voll + channel)) >> 7; + + //add to output total + state.t_main_out[channel] += amp; + state.t_main_out[channel] = sclamp<16>(state.t_main_out[channel]); + + //optionally add to echo total + if(state.t_eon & v.vbit) { + state.t_echo_out[channel] += amp; + state.t_echo_out[channel] = sclamp<16>(state.t_echo_out[channel]); + } +} + +void DSP::voice_1(voice_t &v) { + state.t_dir_addr = (state.t_dir << 8) + (state.t_srcn << 2); + state.t_srcn = VREG(srcn); +} + +void DSP::voice_2(voice_t &v) { + //read sample pointer (ignored if not needed) + uint16 addr = state.t_dir_addr; + if(!v.kon_delay) addr += 2; + uint8 lo = memory::apuram[(uint16)(addr + 0)]; + uint8 hi = memory::apuram[(uint16)(addr + 1)]; + state.t_brr_next_addr = ((hi << 8) + lo); + + state.t_adsr0 = VREG(adsr0); + + //read pitch, spread over two clocks + state.t_pitch = VREG(pitchl); +} + +void DSP::voice_3(voice_t &v) { + voice_3a(v); + voice_3b(v); + voice_3c(v); +} + +void DSP::voice_3a(voice_t &v) { + state.t_pitch += (VREG(pitchh) & 0x3f) << 8; +} + +void DSP::voice_3b(voice_t &v) { + state.t_brr_byte = memory::apuram[(uint16)(v.brr_addr + v.brr_offset)]; + state.t_brr_header = memory::apuram[(uint16)(v.brr_addr)]; +} + +void DSP::voice_3c(voice_t &v) { + //pitch modulation using previous voice's output + + if(state.t_pmon & v.vbit) { + state.t_pitch += ((state.t_output >> 5) * state.t_pitch) >> 10; + } + + if(v.kon_delay) { + //get ready to start BRR decoding on next sample + if(v.kon_delay == 5) { + v.brr_addr = state.t_brr_next_addr; + v.brr_offset = 1; + v.buf_pos = 0; + state.t_brr_header = 0; //header is ignored on this sample + } + + //envelope is never run during KON + v.env = 0; + v.hidden_env = 0; + + //disable BRR decoding until last three samples + v.interp_pos = 0; + v.kon_delay--; + if(v.kon_delay & 3) v.interp_pos = 0x4000; + + //pitch is never added during KON + state.t_pitch = 0; + } + + //gaussian interpolation + int output = gaussian_interpolate(v); + + //noise + if(state.t_non & v.vbit) { + output = (int16)(state.noise << 1); + } + + //apply envelope + state.t_output = ((output * v.env) >> 11) & ~1; + v.t_envx_out = v.env >> 4; + + //immediate silence due to end of sample or soft reset + if(REG(flg) & 0x80 || (state.t_brr_header & 3) == 1) { + v.env_mode = env_release; + v.env = 0; + } + + if(state.every_other_sample) { + //KOFF + if(state.t_koff & v.vbit) { + v.env_mode = env_release; + } + + //KON + if(state.kon & v.vbit) { + v.kon_delay = 5; + v.env_mode = env_attack; + } + } + + //run envelope for next sample + if(!v.kon_delay) envelope_run(v); +} + +void DSP::voice_4(voice_t &v) { + //decode BRR + state.t_looped = 0; + if(v.interp_pos >= 0x4000) { + brr_decode(v); + v.brr_offset += 2; + if(v.brr_offset >= 9) { + //start decoding next BRR block + v.brr_addr = (uint16)(v.brr_addr + 9); + if(state.t_brr_header & 1) { + v.brr_addr = state.t_brr_next_addr; + state.t_looped = v.vbit; + } + v.brr_offset = 1; + } + } + + //apply pitch + v.interp_pos = (v.interp_pos & 0x3fff) + state.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); +} + +void DSP::voice_5(voice_t &v) { + //output right + voice_output(v, 1); + + //ENDX, OUTX and ENVX won't update if you wrote to them 1-2 clocks earlier + state.endx_buf = REG(endx) | state.t_looped; + + //clear bit in ENDX if KON just began + if(v.kon_delay == 5) state.endx_buf &= ~v.vbit; +} + +void DSP::voice_6(voice_t &v) { + state.outx_buf = state.t_output >> 8; +} + +void DSP::voice_7(voice_t &v) { + //update ENDX + REG(endx) = (uint8)state.endx_buf; + state.envx_buf = v.t_envx_out; +} + +void DSP::voice_8(voice_t &v) { + //update OUTX + VREG(outx) = (uint8)state.outx_buf; +} + +void DSP::voice_9(voice_t &v) { + //update ENVX + VREG(envx) = (uint8)state.envx_buf; +} + +#endif diff --git a/src/snes/input/input.cpp b/asnes/input/input.cpp similarity index 100% rename from src/snes/input/input.cpp rename to asnes/input/input.cpp diff --git a/src/snes/input/input.hpp b/asnes/input/input.hpp similarity index 100% rename from src/snes/input/input.hpp rename to asnes/input/input.hpp diff --git a/src/snes/interface/interface.hpp b/asnes/interface/interface.hpp similarity index 100% rename from src/snes/interface/interface.hpp rename to asnes/interface/interface.hpp diff --git a/src/snes/libsnes/libsnes.cpp b/asnes/libsnes/libsnes.cpp similarity index 100% rename from src/snes/libsnes/libsnes.cpp rename to asnes/libsnes/libsnes.cpp diff --git a/src/snes/libsnes/libsnes.hpp b/asnes/libsnes/libsnes.hpp similarity index 100% rename from src/snes/libsnes/libsnes.hpp rename to asnes/libsnes/libsnes.hpp diff --git a/asnes/memory/memory-inline.hpp b/asnes/memory/memory-inline.hpp new file mode 100644 index 00000000..d49f8d48 --- /dev/null +++ b/asnes/memory/memory-inline.hpp @@ -0,0 +1,69 @@ +//Memory + +unsigned Memory::size() const { return 0; } + +//StaticRAM + +uint8* StaticRAM::data() { return data_; } +unsigned StaticRAM::size() const { return size_; } + +uint8 StaticRAM::read(unsigned addr) { return data_[addr]; } +void StaticRAM::write(unsigned addr, uint8 n) { data_[addr] = n; } +uint8& StaticRAM::operator[](unsigned addr) { return data_[addr]; } +const uint8& StaticRAM::operator[](unsigned addr) const { return data_[addr]; } + +StaticRAM::StaticRAM(unsigned n) : size_(n) { data_ = new uint8[size_]; } +StaticRAM::~StaticRAM() { delete[] data_; } + +//MappedRAM + +void MappedRAM::reset() { + if(data_) { + delete[] data_; + data_ = 0; + } + size_ = -1U; + write_protect_ = false; +} + +void MappedRAM::map(uint8 *source, unsigned length) { + reset(); + data_ = source; + size_ = data_ && length > 0 ? length : -1U; +} + +void MappedRAM::copy(const uint8 *data, unsigned size) { + if(!data_) { + size_ = (size & ~255) + ((bool)(size & 255) << 8); + data_ = new uint8[size_](); + } + memcpy(data_, data, min(size_, size)); +} + +void MappedRAM::write_protect(bool status) { write_protect_ = status; } +uint8* MappedRAM::data() { return data_; } +unsigned MappedRAM::size() const { return size_; } + +uint8 MappedRAM::read(unsigned addr) { return data_[addr]; } +void MappedRAM::write(unsigned addr, uint8 n) { if(!write_protect_) data_[addr] = n; } +const uint8& MappedRAM::operator[](unsigned addr) const { return data_[addr]; } +MappedRAM::MappedRAM() : data_(0), size_(-1U), write_protect_(false) {} + +//Bus + +uint8 Bus::read(unsigned addr) { + #if defined(CHEAT_SYSTEM) + if(cheat.active() && cheat.exists(addr)) { + uint8 r; + if(cheat.read(addr, r)) return r; + } + #endif + + Page &p = page[addr >> 8]; + return p.access->read(p.offset + addr); +} + +void Bus::write(unsigned addr, uint8 data) { + Page &p = page[addr >> 8]; + return p.access->write(p.offset + addr, data); +} diff --git a/asnes/memory/memory.cpp b/asnes/memory/memory.cpp new file mode 100644 index 00000000..affd4f2e --- /dev/null +++ b/asnes/memory/memory.cpp @@ -0,0 +1,158 @@ +#include + +#define MEMORY_CPP +namespace SNES { + +Bus bus; + +#include "serialization.cpp" + +namespace memory { + MMIOAccess mmio; + StaticRAM wram(128 * 1024); + StaticRAM apuram(64 * 1024); + StaticRAM vram(64 * 1024); + StaticRAM oam(544); + StaticRAM cgram(512); + + UnmappedMemory memory_unmapped; + UnmappedMMIO mmio_unmapped; +}; + +unsigned UnmappedMemory::size() const { return 16 * 1024 * 1024; } +uint8 UnmappedMemory::read(unsigned) { return cpu.regs.mdr; } +void UnmappedMemory::write(unsigned, uint8) {} + +uint8 UnmappedMMIO::mmio_read(unsigned) { return cpu.regs.mdr; } +void UnmappedMMIO::mmio_write(unsigned, uint8) {} + +void MMIOAccess::map(unsigned addr, MMIO &access) { + //MMIO: $[00-3f]:[2000-5fff] + mmio[(addr - 0x2000) & 0x3fff] = &access; +} + +uint8 MMIOAccess::read(unsigned addr) { + return mmio[(addr - 0x2000) & 0x3fff]->mmio_read(addr); +} + +void MMIOAccess::write(unsigned addr, uint8 data) { + mmio[(addr - 0x2000) & 0x3fff]->mmio_write(addr, data); +} + +unsigned Bus::mirror(unsigned addr, unsigned size) { + unsigned base = 0; + if(size) { + unsigned mask = 1 << 23; + while(addr >= size) { + while(!(addr & mask)) mask >>= 1; + addr -= mask; + if(size > mask) { + size -= mask; + base += mask; + } + mask >>= 1; + } + base += addr; + } + return base; +} + +void Bus::map(unsigned addr, Memory &access, unsigned offset) { + page[addr >> 8].access = &access; + page[addr >> 8].offset = offset - addr; +} + +void Bus::map( + MapMode mode, + uint8 bank_lo, uint8 bank_hi, + uint16 addr_lo, uint16 addr_hi, + Memory &access, unsigned offset, unsigned size +) { + assert(bank_lo <= bank_hi); + assert(addr_lo <= addr_hi); + if(access.size() == -1U) return; + + uint8 page_lo = addr_lo >> 8; + uint8 page_hi = addr_hi >> 8; + unsigned index = 0; + + switch(mode) { + case MapMode::Direct: { + for(unsigned bank = bank_lo; bank <= bank_hi; bank++) { + for(unsigned page = page_lo; page <= page_hi; page++) { + map((bank << 16) + (page << 8), access, (bank << 16) + (page << 8)); + } + } + } break; + + case MapMode::Linear: { + for(unsigned bank = bank_lo; bank <= bank_hi; bank++) { + for(unsigned page = page_lo; page <= page_hi; page++) { + map((bank << 16) + (page << 8), access, mirror(offset + index, access.size())); + index += 256; + if(size) index %= size; + } + } + } break; + + case MapMode::Shadow: { + for(unsigned bank = bank_lo; bank <= bank_hi; bank++) { + index += page_lo * 256; + if(size) index %= size; + + for(unsigned page = page_lo; page <= page_hi; page++) { + map((bank << 16) + (page << 8), access, mirror(offset + index, access.size())); + index += 256; + if(size) index %= size; + } + + index += (255 - page_hi) * 256; + if(size) index %= size; + } + } break; + } +} + +bool Bus::load_cart() { + if(cartridge.loaded() == true) return false; + + map_reset(); + map_xml(); + map_system(); + return true; +} + +void Bus::unload_cart() { +} + +void Bus::map_reset() { + map(MapMode::Direct, 0x00, 0xff, 0x0000, 0xffff, memory::memory_unmapped); + map(MapMode::Direct, 0x00, 0x3f, 0x2000, 0x5fff, memory::mmio); + map(MapMode::Direct, 0x80, 0xbf, 0x2000, 0x5fff, memory::mmio); + for(unsigned i = 0x2000; i <= 0x5fff; i++) memory::mmio.map(i, memory::mmio_unmapped); +} + +void Bus::map_xml() { + foreach(m, cartridge.mapping) { + if(m.memory) { + map(m.mode, m.banklo, m.bankhi, m.addrlo, m.addrhi, *m.memory, m.offset, m.size); + } else if(m.mmio) { + for(unsigned i = m.addrlo; i <= m.addrhi; i++) memory::mmio.map(i, *m.mmio); + } + } +} + +void Bus::map_system() { + map(MapMode::Linear, 0x00, 0x3f, 0x0000, 0x1fff, memory::wram, 0x000000, 0x002000); + map(MapMode::Linear, 0x80, 0xbf, 0x0000, 0x1fff, memory::wram, 0x000000, 0x002000); + map(MapMode::Linear, 0x7e, 0x7f, 0x0000, 0xffff, memory::wram); +} + +void Bus::power() { + foreach(n, memory::wram) n = config.cpu.wram_init_value; +} + +void Bus::reset() { +} + +} diff --git a/asnes/memory/memory.hpp b/asnes/memory/memory.hpp new file mode 100644 index 00000000..1492d7d8 --- /dev/null +++ b/asnes/memory/memory.hpp @@ -0,0 +1,111 @@ +struct Memory { + virtual inline unsigned size() const; + virtual uint8 read(unsigned addr) = 0; + virtual void write(unsigned addr, uint8 data) = 0; +}; + +struct MMIO { + virtual uint8 mmio_read(unsigned addr) = 0; + virtual void mmio_write(unsigned addr, uint8 data) = 0; +}; + +struct UnmappedMemory : Memory { + unsigned size() const; + uint8 read(unsigned); + void write(unsigned, uint8); +}; + +struct UnmappedMMIO : MMIO { + uint8 mmio_read(unsigned); + void mmio_write(unsigned, uint8); +}; + +struct StaticRAM : Memory { + inline uint8* data(); + inline unsigned size() const; + + inline uint8 read(unsigned addr); + inline void write(unsigned addr, uint8 n); + inline uint8& operator[](unsigned addr); + inline const uint8& operator[](unsigned addr) const; + + inline StaticRAM(unsigned size); + inline ~StaticRAM(); + +private: + uint8 *data_; + unsigned size_; +}; + +struct MappedRAM : Memory { + inline void reset(); + inline void map(uint8*, unsigned); + inline void copy(const uint8*, unsigned); + + inline void write_protect(bool status); + inline uint8* data(); + inline unsigned size() const; + + inline uint8 read(unsigned addr); + inline void write(unsigned addr, uint8 n); + inline const uint8& operator[](unsigned addr) const; + inline MappedRAM(); + +private: + uint8 *data_; + unsigned size_; + bool write_protect_; +}; + +struct MMIOAccess : Memory { + void map(unsigned addr, MMIO &access); + uint8 read(unsigned addr); + void write(unsigned addr, uint8 data); + + MMIO *mmio[0x4000]; +}; + +struct Bus { + unsigned mirror(unsigned addr, unsigned size); + void map(unsigned addr, Memory &access, unsigned offset); + enum class MapMode : unsigned { Direct, Linear, Shadow }; + void map(MapMode mode, + uint8 bank_lo, uint8 bank_hi, + uint16 addr_lo, uint16 addr_hi, + Memory &access, unsigned offset = 0, unsigned size = 0); + + alwaysinline uint8 read(unsigned addr); + alwaysinline void write(unsigned addr, uint8 data); + + bool load_cart(); + void unload_cart(); + + void power(); + void reset(); + + struct Page { + Memory *access; + unsigned offset; + } page[65536]; + + void serialize(serializer&); + +private: + void map_reset(); + void map_xml(); + void map_system(); +}; + +namespace memory { + extern MMIOAccess mmio; //S-CPU, S-PPU + extern StaticRAM wram; //S-CPU + extern StaticRAM apuram; //S-SMP, S-DSP + extern StaticRAM vram; //S-PPU + extern StaticRAM oam; //S-PPU + extern StaticRAM cgram; //S-PPU + + extern UnmappedMemory memory_unmapped; + extern UnmappedMMIO mmio_unmapped; +}; + +extern Bus bus; diff --git a/asnes/memory/serialization.cpp b/asnes/memory/serialization.cpp new file mode 100644 index 00000000..334a380a --- /dev/null +++ b/asnes/memory/serialization.cpp @@ -0,0 +1,11 @@ +#ifdef MEMORY_CPP + +void Bus::serialize(serializer &s) { + s.array(memory::wram.data(), memory::wram.size()); + s.array(memory::apuram.data(), memory::apuram.size()); + s.array(memory::vram.data(), memory::vram.size()); + s.array(memory::oam.data(), memory::oam.size()); + s.array(memory::cgram.data(), memory::cgram.size()); +} + +#endif diff --git a/asnes/ppu/background/background.cpp b/asnes/ppu/background/background.cpp new file mode 100644 index 00000000..7010068d --- /dev/null +++ b/asnes/ppu/background/background.cpp @@ -0,0 +1,249 @@ +#ifdef PPU_CPP + +#include "mode7.cpp" + +void PPU::Background::scanline() { + if(self.vcounter() == 1) { + t.mosaic_y = 1; + t.mosaic_countdown = 0; + } else { + if(!regs.mosaic || !t.mosaic_countdown) t.mosaic_y = self.vcounter(); + if(!t.mosaic_countdown) t.mosaic_countdown = regs.mosaic + 1; + t.mosaic_countdown--; + } + + t.x = 0; +} + +void PPU::Background::run() { + bool hires = (self.regs.bgmode == 5 || self.regs.bgmode == 6); + + if((self.hcounter() & 2) == 0) { + output.main.priority = 0; + output.sub.priority = 0; + } else if(hires == false) { + return; + } + + if(regs.mode == Mode::Inactive) return; + if(regs.main_enabled == false && regs.sub_enabled == false) return; + + unsigned x = t.x++; + unsigned y = t.mosaic_y; + if(regs.mode == Mode::Mode7) return run_mode7(x, y); + + unsigned color_depth = (regs.mode == Mode::BPP2 ? 0 : regs.mode == Mode::BPP4 ? 1 : 2); + unsigned palette_offset = (self.regs.bgmode == 0 ? (id << 5) : 0); + unsigned palette_size = 2 << color_depth; + unsigned tile_mask = 0x0fff >> color_depth; + unsigned tiledata_index = regs.tiledata_addr >> (4 + color_depth); + + unsigned tile_height = (regs.tile_size == TileSize::Size8x8 ? 3 : 4); + unsigned tile_width = (!hires ? tile_height : 4); + + unsigned width = (!hires ? 256 : 512); + unsigned mask_x = (tile_height == 3 ? width : (width << 1)); + unsigned mask_y = mask_x; + if(regs.screen_size & 1) mask_x <<= 1; + if(regs.screen_size & 2) mask_y <<= 1; + mask_x--; + mask_y--; + + unsigned hscroll = regs.hoffset; + unsigned vscroll = regs.voffset; + if(hires) { + hscroll <<= 1; + if(self.regs.interlace) y = (y << 1) + self.field(); + } + + unsigned hoffset = hscroll + mosaic_table[regs.mosaic][x]; + unsigned voffset = vscroll + y; + + if(self.regs.bgmode == 2 || self.regs.bgmode == 4 || self.regs.bgmode == 6) { + uint16 opt_x = (x + (hscroll & 7)); + + if(opt_x >= 8) { + unsigned hval = self.bg3.get_tile((opt_x - 8) + (self.bg3.regs.hoffset & ~7), self.bg3.regs.voffset + 0); + unsigned vval = self.bg3.get_tile((opt_x - 8) + (self.bg3.regs.hoffset & ~7), self.bg3.regs.voffset + 8); + unsigned opt_valid_bit = (id == ID::BG1 ? 0x2000 : 0x4000); + + if(self.regs.bgmode == 4) { + if(hval & opt_valid_bit) { + if(!(hval & 0x8000)) { + hoffset = opt_x + (hval & ~7); + } else { + voffset = y + hval; + } + } + } else { + if(hval & opt_valid_bit) hoffset = opt_x + (hval & ~7); + if(vval & opt_valid_bit) voffset = y + vval; + } + } + } + + hoffset &= mask_x; + voffset &= mask_y; + + unsigned tile_number = get_tile(hoffset, voffset); + bool mirror_y = tile_number & 0x8000; + bool mirror_x = tile_number & 0x4000; + unsigned priority = (tile_number & 0x2000 ? regs.priority1 : regs.priority0); + unsigned palette_number = (tile_number >> 10) & 7; + unsigned palette_index = palette_offset + (palette_number << palette_size); + + if(tile_width == 4 && (bool)(hoffset & 8) != mirror_x) tile_number += 1; + if(tile_height == 4 && (bool)(voffset & 8) != mirror_y) tile_number += 16; + tile_number &= 0x03ff; + tile_number += tiledata_index; + tile_number &= tile_mask; + + if(mirror_x) hoffset ^= 7; + if(mirror_y) voffset ^= 7; + + uint8 color = get_color(hoffset, voffset, tile_number); + if(color == 0) return; + + color += palette_index; + + if(hires == false) { + if(regs.main_enabled) { + output.main.priority = priority; + output.main.palette = color; + output.main.tile = tile_number; + } + + if(regs.sub_enabled) { + output.sub.priority = priority; + output.sub.palette = color; + output.sub.tile = tile_number; + } + } else { + if(x & 1) { + if(regs.main_enabled) { + output.main.priority = priority; + output.main.palette = color; + output.main.tile = tile_number; + } + } else { + if(regs.sub_enabled) { + output.sub.priority = priority; + output.sub.palette = color; + output.sub.tile = tile_number; + } + } + } +} + +unsigned PPU::Background::get_tile(unsigned x, unsigned y) { + bool hires = (self.regs.bgmode == 5 || self.regs.bgmode == 6); + unsigned tile_height = (regs.tile_size == TileSize::Size8x8 ? 3 : 4); + unsigned tile_width = (!hires ? tile_height : 4); + unsigned width = (!hires ? 256 : 512); + unsigned mask_x = (tile_height == 3 ? width : (width << 1)); + unsigned mask_y = mask_x; + if(regs.screen_size & 1) mask_x <<= 1; + if(regs.screen_size & 2) mask_y <<= 1; + mask_x--; + mask_y--; + + unsigned screen_x = (regs.screen_size & 1 ? (32 << 5) : 0); + unsigned screen_y = (regs.screen_size & 2 ? (32 << 5) : 0); + if(regs.screen_size == 3) screen_y <<= 1; + + x = (x & mask_x) >> tile_width; + y = (y & mask_y) >> tile_height; + + uint16 pos = ((y & 0x1f) << 5) + (x & 0x1f); + if(x & 0x20) pos += screen_x; + if(y & 0x20) pos += screen_y; + + uint16 addr = regs.screen_addr + (pos << 1); + return memory::vram[addr + 0] + (memory::vram[addr + 1] << 8); +} + +unsigned PPU::Background::get_color(unsigned x, unsigned y, uint16 offset) { + unsigned mask = 0x80 >> (x & 7); + + switch(regs.mode) { + case Background::Mode::BPP2: { + offset = (offset * 16) + ((y & 7) * 2); + + unsigned d0 = memory::vram[offset + 0]; + unsigned d1 = memory::vram[offset + 1]; + + return (((bool)(d0 & mask)) << 0) + + (((bool)(d1 & mask)) << 1); + } + + case Background::Mode::BPP4: { + offset = (offset * 32) + ((y & 7) * 2); + + unsigned d0 = memory::vram[offset + 0]; + unsigned d1 = memory::vram[offset + 1]; + unsigned d2 = memory::vram[offset + 16]; + unsigned d3 = memory::vram[offset + 17]; + + return (((bool)(d0 & mask)) << 0) + + (((bool)(d1 & mask)) << 1) + + (((bool)(d2 & mask)) << 2) + + (((bool)(d3 & mask)) << 3); + } + + case Background::Mode::BPP8: { + offset = (offset * 64) + ((y & 7) * 2); + + unsigned d0 = memory::vram[offset + 0]; + unsigned d1 = memory::vram[offset + 1]; + unsigned d2 = memory::vram[offset + 16]; + unsigned d3 = memory::vram[offset + 17]; + unsigned d4 = memory::vram[offset + 32]; + unsigned d5 = memory::vram[offset + 33]; + unsigned d6 = memory::vram[offset + 48]; + unsigned d7 = memory::vram[offset + 49]; + + return (((bool)(d0 & mask)) << 0) + + (((bool)(d1 & mask)) << 1) + + (((bool)(d2 & mask)) << 2) + + (((bool)(d3 & mask)) << 3) + + (((bool)(d4 & mask)) << 4) + + (((bool)(d5 & mask)) << 5) + + (((bool)(d6 & mask)) << 6) + + (((bool)(d7 & mask)) << 7); + } + }; +} + +void PPU::Background::reset() { + t.x = 0; + t.mosaic_y = 0; + t.mosaic_countdown = 0; + regs.tiledata_addr = 0; + regs.screen_addr = 0; + regs.screen_size = 0; + regs.mosaic = 0; + regs.tile_size = 0; + regs.mode = 0; + regs.priority0 = 0; + regs.priority1 = 0; + regs.main_enabled = 0; + regs.sub_enabled = 0; + regs.hoffset = 0; + regs.voffset = 0; + output.main.palette = 0; + output.main.priority = 0; + output.sub.palette = 0; + output.sub.priority = 0; +} + +PPU::Background::Background(PPU &self, unsigned id) : self(self), id(id) { + for(unsigned m = 0; m < 16; m++) { + for(unsigned x = 0; x < 4096; x++) { + mosaic_table[m][x] = (x / (m + 1)) * (m + 1); + } + } +} + +uint16 PPU::Background::mosaic_table[16][4096]; + +#endif diff --git a/asnes/ppu/background/background.hpp b/asnes/ppu/background/background.hpp new file mode 100644 index 00000000..0849022e --- /dev/null +++ b/asnes/ppu/background/background.hpp @@ -0,0 +1,58 @@ +class Background { +public: + PPU &self; + struct ID { enum { BG1, BG2, BG3, BG4 }; }; + unsigned id; + + struct Mode { enum { BPP2, BPP4, BPP8, Mode7, Inactive }; }; + struct ScreenSize { enum { Size32x32, Size32x64, Size64x32, Size64x64 }; }; + struct TileSize { enum { Size8x8, Size16x16 }; }; + + struct { + unsigned x; + unsigned mosaic_y; + unsigned mosaic_countdown; + } t; + + struct { + unsigned tiledata_addr; + unsigned screen_addr; + unsigned screen_size; + unsigned mosaic; + bool tile_size; + + unsigned mode; + unsigned priority0; + unsigned priority1; + + bool main_enabled; + bool sub_enabled; + + unsigned hoffset; + unsigned voffset; + } regs; + + struct { + struct { + unsigned priority; //0 = none (transparent) + unsigned palette; + unsigned tile; + } main, sub; + } output; + + void scanline(); + void run(); + unsigned get_tile(unsigned x, unsigned y); + unsigned get_color(unsigned x, unsigned y, uint16 offset); + void reset(); + + void serialize(serializer&); + Background(PPU &self, unsigned id); + +private: + static uint16 mosaic_table[16][4096]; + + //mode7.cpp + signed clip(signed n); + void run_mode7(unsigned x, unsigned y); +}; diff --git a/asnes/ppu/background/mode7.cpp b/asnes/ppu/background/mode7.cpp new file mode 100644 index 00000000..c370fd67 --- /dev/null +++ b/asnes/ppu/background/mode7.cpp @@ -0,0 +1,100 @@ +#ifdef PPU_CPP + +signed PPU::Background::clip(signed n) { + //13-bit sign extend: --s---nnnnnnnnnn -> ssssssnnnnnnnnnn + return n & 0x2000 ? (n | ~1023) : (n & 1023); +} + +void PPU::Background::run_mode7(unsigned x, unsigned y) { + signed a = sclip<16>(self.regs.m7a); + signed b = sclip<16>(self.regs.m7b); + signed c = sclip<16>(self.regs.m7c); + signed d = sclip<16>(self.regs.m7d); + + signed cx = sclip<13>(self.regs.m7x); + signed cy = sclip<13>(self.regs.m7y); + signed hoffset = sclip<13>(self.regs.mode7_hoffset); + signed voffset = sclip<13>(self.regs.mode7_voffset); + + if(self.regs.mode7_hflip) x = 255 - x; + if(self.regs.mode7_vflip) y = 255 - y; + + unsigned mosaic_x; + unsigned mosaic_y; + if(id == ID::BG1) { + mosaic_x = mosaic_table[self.bg1.regs.mosaic][x]; + mosaic_y = mosaic_table[self.bg1.regs.mosaic][y]; + } else if(id == ID::BG2) { + mosaic_x = mosaic_table[self.bg2.regs.mosaic][x]; + mosaic_y = mosaic_table[self.bg1.regs.mosaic][y]; //BG2 vertical mosaic uses BG1 mosaic size + } + + signed psx = ((a * clip(hoffset - cx)) & ~63) + ((b * clip(voffset - cy)) & ~63) + ((b * mosaic_y) & ~63) + (cx << 8); + signed psy = ((c * clip(hoffset - cx)) & ~63) + ((d * clip(voffset - cy)) & ~63) + ((d * mosaic_y) & ~63) + (cy << 8); + + signed px = psx + (a * mosaic_x); + signed py = psy + (c * mosaic_x); + + //mask pseudo-FP bits + px >>= 8; + py >>= 8; + + unsigned tile; + unsigned palette; + switch(self.regs.mode7_repeat) { + //screen repetition outside of screen area + case 0: + case 1: { + px &= 1023; + py &= 1023; + tile = memory::vram[((py >> 3) * 128 + (px >> 3)) << 1]; + palette = memory::vram[(((tile << 6) + ((py & 7) << 3) + (px & 7)) << 1) + 1]; + } break; + + //palette color 0 outside of screen area + case 2: { + if(px < 0 || px > 1023 || py < 0 || py > 1023) { + palette = 0; + } else { + px &= 1023; + py &= 1023; + tile = memory::vram[((py >> 3) * 128 + (px >> 3)) << 1]; + palette = memory::vram[(((tile << 6) + ((py & 7) << 3) + (px & 7)) << 1) + 1]; + } + } break; + + //character 0 repetition outside of screen area + case 3: { + if(px < 0 || px > 1023 || py < 0 || py > 1023) { + tile = 0; + } else { + px &= 1023; + py &= 1023; + tile = memory::vram[((py >> 3) * 128 + (px >> 3)) << 1]; + } + palette = memory::vram[(((tile << 6) + ((py & 7) << 3) + (px & 7)) << 1) + 1]; + } break; + } + + unsigned priority; + if(id == ID::BG1) { + priority = regs.priority0; + } else if(id == ID::BG2) { + priority = (palette & 0x80 ? regs.priority1 : regs.priority0); + palette &= 0x7f; + } + + if(palette == 0) return; + + if(regs.main_enabled) { + output.main.palette = palette; + output.main.priority = priority; + } + + if(regs.sub_enabled) { + output.sub.palette = palette; + output.sub.priority = priority; + } +} + +#endif diff --git a/src/snes/ppu/ppu-inline.hpp b/asnes/ppu/counter/counter-inline.hpp similarity index 100% rename from src/snes/ppu/ppu-inline.hpp rename to asnes/ppu/counter/counter-inline.hpp diff --git a/asnes/ppu/counter/counter.hpp b/asnes/ppu/counter/counter.hpp new file mode 100644 index 00000000..f244156a --- /dev/null +++ b/asnes/ppu/counter/counter.hpp @@ -0,0 +1,49 @@ +//PPUCounter emulates the H/V latch counters of the S-PPU2. +// +//real hardware has the S-CPU maintain its own copy of these counters that are +//updated based on the state of the S-PPU Vblank and Hblank pins. emulating this +//would require full lock-step synchronization for every clock tick. +//to bypass this and allow the two to run out-of-order, both the CPU and PPU +//classes inherit PPUcounter and keep their own counters. +//the timers are kept in sync, as the only differences occur on V=240 and V=261, +//based on interlace. thus, we need only synchronize and fetch interlace at any +//point before this in the frame, which is handled internally by this class at +//V=128. + +class PPUCounter { +public: + alwaysinline void tick(); + alwaysinline void tick(unsigned clocks); + + alwaysinline bool field () const; + alwaysinline uint16 vcounter() const; + alwaysinline uint16 hcounter() const; + inline uint16 hdot() const; + inline uint16 lineclocks() const; + + alwaysinline bool field (unsigned offset) const; + alwaysinline uint16 vcounter(unsigned offset) const; + alwaysinline uint16 hcounter(unsigned offset) const; + + inline void reset(); + function scanline; + void serialize(serializer&); + +private: + inline void vcounter_tick(); + + struct { + bool interlace; + bool field; + uint16 vcounter; + uint16 hcounter; + } status; + + struct { + bool field[2048]; + uint16 vcounter[2048]; + uint16 hcounter[2048]; + + int32 index; + } history; +}; diff --git a/asnes/ppu/debugger/debugger.cpp b/asnes/ppu/debugger/debugger.cpp new file mode 100644 index 00000000..1191514f --- /dev/null +++ b/asnes/ppu/debugger/debugger.cpp @@ -0,0 +1,3 @@ +#ifdef PPU_CPP + +#endif diff --git a/src/snes/ppu/sppu/debugger/debugger.hpp b/asnes/ppu/debugger/debugger.hpp similarity index 100% rename from src/snes/ppu/sppu/debugger/debugger.hpp rename to asnes/ppu/debugger/debugger.hpp diff --git a/src/snes/ppu/ppu-debugger.cpp b/asnes/ppu/debugger/ppu-debugger.cpp similarity index 100% rename from src/snes/ppu/ppu-debugger.cpp rename to asnes/ppu/debugger/ppu-debugger.cpp diff --git a/src/snes/ppu/ppu-debugger.hpp b/asnes/ppu/debugger/ppu-debugger.hpp similarity index 100% rename from src/snes/ppu/ppu-debugger.hpp rename to asnes/ppu/debugger/ppu-debugger.hpp diff --git a/asnes/ppu/mmio/mmio.cpp b/asnes/ppu/mmio/mmio.cpp new file mode 100644 index 00000000..959a6d71 --- /dev/null +++ b/asnes/ppu/mmio/mmio.cpp @@ -0,0 +1,871 @@ +#ifdef PPU_CPP + +void PPU::latch_counters() { + cpu.synchronize_ppu(); + regs.hcounter = hdot(); + regs.vcounter = vcounter(); + regs.counters_latched = true; +} + +uint16 PPU::get_vram_address() { + uint16 addr = regs.vram_addr; + switch(regs.vram_mapping) { + case 0: break; //direct mapping + case 1: addr = (addr & 0xff00) | ((addr & 0x001f) << 3) | ((addr >> 5) & 7); break; + case 2: addr = (addr & 0xfe00) | ((addr & 0x003f) << 3) | ((addr >> 6) & 7); break; + case 3: addr = (addr & 0xfc00) | ((addr & 0x007f) << 3) | ((addr >> 7) & 7); break; + } + return (addr << 1); +} + +uint8 PPU::vram_read(unsigned addr) { + if(regs.display_disabled || vcounter() >= (!regs.overscan ? 225 : 240)) { + return memory::vram[addr]; + } + return 0x00; +} + +void PPU::vram_write(unsigned addr, uint8 data) { + if(regs.display_disabled || vcounter() >= (!regs.overscan ? 225 : 240)) { + memory::vram[addr] = data; + } +} + +uint8 PPU::oam_read(unsigned addr) { + if(!regs.display_disabled && vcounter() < (!regs.overscan ? 225 : 240)) addr = regs.ioamaddr; + if(addr & 0x0200) addr &= 0x021f; + return memory::oam[addr]; +} + +void PPU::oam_write(unsigned addr, uint8 data) { + if(!regs.display_disabled && vcounter() < (!regs.overscan ? 225 : 240)) addr = regs.ioamaddr; + if(addr & 0x0200) addr &= 0x021f; + memory::oam[addr] = data; + oam.update(addr, data); +} + +uint8 PPU::cgram_read(unsigned addr) { + return memory::cgram[addr]; +} + +void PPU::cgram_write(unsigned addr, uint8 data) { + memory::cgram[addr] = data; +} + +bool PPU::interlace() const { + return display.interlace; +} + +bool PPU::overscan() const { + return display.overscan; +} + +bool PPU::hires() const { + return true; +} + +void PPU::mmio_update_video_mode() { + switch(regs.bgmode) { + case 0: { + bg1.regs.mode = Background::Mode::BPP2; bg1.regs.priority0 = 8; bg1.regs.priority1 = 11; + bg2.regs.mode = Background::Mode::BPP2; bg2.regs.priority0 = 7; bg2.regs.priority1 = 10; + bg3.regs.mode = Background::Mode::BPP2; bg3.regs.priority0 = 2; bg3.regs.priority1 = 5; + bg4.regs.mode = Background::Mode::BPP2; bg4.regs.priority0 = 1; bg4.regs.priority1 = 4; + oam.regs.priority0 = 3; oam.regs.priority1 = 6; oam.regs.priority2 = 9; oam.regs.priority3 = 12; + } break; + + case 1: { + bg1.regs.mode = Background::Mode::BPP4; + bg2.regs.mode = Background::Mode::BPP4; + bg3.regs.mode = Background::Mode::BPP2; + bg4.regs.mode = Background::Mode::Inactive; + if(regs.bg3_priority) { + bg1.regs.priority0 = 5; bg1.regs.priority1 = 8; + bg2.regs.priority0 = 4; bg2.regs.priority1 = 7; + bg3.regs.priority0 = 1; bg3.regs.priority1 = 10; + oam.regs.priority0 = 2; oam.regs.priority1 = 3; oam.regs.priority2 = 6; oam.regs.priority3 = 9; + } else { + bg1.regs.priority0 = 6; bg1.regs.priority1 = 9; + bg2.regs.priority0 = 5; bg2.regs.priority1 = 8; + bg3.regs.priority0 = 1; bg3.regs.priority1 = 3; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 7; oam.regs.priority3 = 10; + } + } break; + + case 2: { + bg1.regs.mode = Background::Mode::BPP4; + bg2.regs.mode = Background::Mode::BPP4; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 3; bg1.regs.priority1 = 7; + bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 8; + } break; + + case 3: { + bg1.regs.mode = Background::Mode::BPP8; + bg2.regs.mode = Background::Mode::BPP4; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 3; bg1.regs.priority1 = 7; + bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 8; + } break; + + case 4: { + bg1.regs.mode = Background::Mode::BPP8; + bg2.regs.mode = Background::Mode::BPP2; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 3; bg1.regs.priority1 = 7; + bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 8; + } break; + + case 5: { + bg1.regs.mode = Background::Mode::BPP4; + bg2.regs.mode = Background::Mode::BPP2; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 3; bg1.regs.priority1 = 7; + bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 8; + } break; + + case 6: { + bg1.regs.mode = Background::Mode::BPP4; + bg2.regs.mode = Background::Mode::Inactive; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 2; bg1.regs.priority1 = 5; + oam.regs.priority0 = 1; oam.regs.priority1 = 3; oam.regs.priority2 = 4; oam.regs.priority3 = 6; + } break; + + case 7: { + if(regs.mode7_extbg == false) { + bg1.regs.mode = Background::Mode::Mode7; + bg2.regs.mode = Background::Mode::Inactive; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 2; bg1.regs.priority1 = 2; + oam.regs.priority0 = 1; oam.regs.priority1 = 3; oam.regs.priority2 = 4; oam.regs.priority3 = 5; + } else { + bg1.regs.mode = Background::Mode::Mode7; + bg2.regs.mode = Background::Mode::Mode7; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 3; bg1.regs.priority1 = 3; + bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 7; + } + } break; + } +} + +//INIDISP +void PPU::mmio_w2100(uint8 data) { + if(regs.display_disabled && vcounter() == (!regs.overscan ? 225 : 240)) oam.address_reset(); + regs.display_disabled = data & 0x80; + regs.display_brightness = data & 0x0f; +} + +//OBSEL +void PPU::mmio_w2101(uint8 data) { + oam.regs.base_size = (data >> 5) & 7; + oam.regs.nameselect = (data >> 3) & 3; + oam.regs.tiledata_addr = (data & 3) << 14; +} + +//OAMADDL +void PPU::mmio_w2102(uint8 data) { + regs.oam_baseaddr &= 0x0100; + regs.oam_baseaddr |= (data << 0); + oam.address_reset(); +} + +//OAMADDH +void PPU::mmio_w2103(uint8 data) { + regs.oam_priority = data & 0x80; + regs.oam_baseaddr &= 0x00ff; + regs.oam_baseaddr |= (data & 1) << 8; + oam.address_reset(); +} + +//OAMDATA +void PPU::mmio_w2104(uint8 data) { + if(regs.oam_addr & 0x0200) { + oam_write(regs.oam_addr, data); + } else if((regs.oam_addr & 1) == 0) { + regs.oam_latchdata = data; + } else { + oam_write((regs.oam_addr & ~1) + 0, regs.oam_latchdata); + oam_write((regs.oam_addr & ~1) + 1, data); + } + + regs.oam_addr = (regs.oam_addr + 1) & 0x03ff; + oam.regs.first_sprite = (regs.oam_priority == false ? 0 : (regs.oam_addr >> 2) & 127); +} + +//BGMODE +void PPU::mmio_w2105(uint8 data) { + bg4.regs.tile_size = (data & 0x80); + bg3.regs.tile_size = (data & 0x40); + bg2.regs.tile_size = (data & 0x20); + bg1.regs.tile_size = (data & 0x10); + regs.bg3_priority = (data & 0x08); + regs.bgmode = (data & 0x07); + mmio_update_video_mode(); +} + +//MOSAIC +void PPU::mmio_w2106(uint8 data) { + unsigned mosaic_size = (data >> 4) & 15; + bg4.regs.mosaic = (data & 0x08 ? mosaic_size : 0); + bg3.regs.mosaic = (data & 0x04 ? mosaic_size : 0); + bg2.regs.mosaic = (data & 0x02 ? mosaic_size : 0); + bg1.regs.mosaic = (data & 0x01 ? mosaic_size : 0); +} + +//BG1SC +void PPU::mmio_w2107(uint8 data) { + bg1.regs.screen_addr = (data & 0x7c) << 9; + bg1.regs.screen_size = data & 3; +} + +//BG2SC +void PPU::mmio_w2108(uint8 data) { + bg2.regs.screen_addr = (data & 0x7c) << 9; + bg2.regs.screen_size = data & 3; +} + +//BG3SC +void PPU::mmio_w2109(uint8 data) { + bg3.regs.screen_addr = (data & 0x7c) << 9; + bg3.regs.screen_size = data & 3; +} + +//BG4SC +void PPU::mmio_w210a(uint8 data) { + bg4.regs.screen_addr = (data & 0x7c) << 9; + bg4.regs.screen_size = data & 3; +} + +//BG12NBA +void PPU::mmio_w210b(uint8 data) { + bg1.regs.tiledata_addr = (data & 0x07) << 13; + bg2.regs.tiledata_addr = (data & 0x70) << 9; +} + +//BG34NBA +void PPU::mmio_w210c(uint8 data) { + bg3.regs.tiledata_addr = (data & 0x07) << 13; + bg4.regs.tiledata_addr = (data & 0x70) << 9; +} + +//BG1HOFS +void PPU::mmio_w210d(uint8 data) { + regs.mode7_hoffset = (data << 8) | regs.mode7_latchdata; + regs.mode7_latchdata = data; + + bg1.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg1.regs.hoffset >> 8) & 7); + regs.bgofs_latchdata = data; +} + +//BG1VOFS +void PPU::mmio_w210e(uint8 data) { + regs.mode7_voffset = (data << 8) | regs.mode7_latchdata; + regs.mode7_latchdata = data; + + bg1.regs.voffset = (data << 8) | regs.bgofs_latchdata; + regs.bgofs_latchdata = data; +} + +//BG2HOFS +void PPU::mmio_w210f(uint8 data) { + bg2.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg2.regs.hoffset >> 8) & 7); + regs.bgofs_latchdata = data; +} + +//BG2VOFS +void PPU::mmio_w2110(uint8 data) { + bg2.regs.voffset = (data << 8) | regs.bgofs_latchdata; + regs.bgofs_latchdata = data; +} + +//BG3HOFS +void PPU::mmio_w2111(uint8 data) { + bg3.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg3.regs.hoffset >> 8) & 7); + regs.bgofs_latchdata = data; +} + +//BG3VOFS +void PPU::mmio_w2112(uint8 data) { + bg3.regs.voffset = (data << 8) | regs.bgofs_latchdata; + regs.bgofs_latchdata = data; +} + +//BG4HOFS +void PPU::mmio_w2113(uint8 data) { + bg4.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg4.regs.hoffset >> 8) & 7); + regs.bgofs_latchdata = data; +} + +//BG4VOFS +void PPU::mmio_w2114(uint8 data) { + bg4.regs.voffset = (data << 8) | regs.bgofs_latchdata; + regs.bgofs_latchdata = data; +} + +//VMAIN +void PPU::mmio_w2115(uint8 data) { + regs.vram_incmode = data & 0x80; + regs.vram_mapping = (data >> 2) & 3; + switch(data & 3) { + case 0: regs.vram_incsize = 1; break; + case 1: regs.vram_incsize = 32; break; + case 2: regs.vram_incsize = 128; break; + case 3: regs.vram_incsize = 128; break; + } +} + +//VMADDL +void PPU::mmio_w2116(uint8 data) { + regs.vram_addr &= 0xff00; + regs.vram_addr |= (data << 0); + uint16 addr = get_vram_address(); + regs.vram_readbuffer = vram_read(addr + 0) << 0; + regs.vram_readbuffer |= vram_read(addr + 1) << 8; +} + +//VMADDH +void PPU::mmio_w2117(uint8 data) { + regs.vram_addr &= 0x00ff; + regs.vram_addr |= (data << 8); + uint16 addr = get_vram_address(); + regs.vram_readbuffer = vram_read(addr + 0) << 0; + regs.vram_readbuffer |= vram_read(addr + 1) << 8; +} + +//VMDATAL +void PPU::mmio_w2118(uint8 data) { + uint16 addr = get_vram_address() + 0; + vram_write(addr, data); + if(regs.vram_incmode == 0) regs.vram_addr += regs.vram_incsize; +} + +//VMDATAH +void PPU::mmio_w2119(uint8 data) { + uint16 addr = get_vram_address() + 1; + vram_write(addr, data); + if(regs.vram_incmode == 1) regs.vram_addr += regs.vram_incsize; +} + +//M7SEL +void PPU::mmio_w211a(uint8 data) { + regs.mode7_repeat = (data >> 6) & 3; + regs.mode7_vflip = data & 0x02; + regs.mode7_hflip = data & 0x01; +} + +//M7A +void PPU::mmio_w211b(uint8 data) { + regs.m7a = (data << 8) | regs.mode7_latchdata; + regs.mode7_latchdata = data; +} + +//M7B +void PPU::mmio_w211c(uint8 data) { + regs.m7b = (data << 8) | regs.mode7_latchdata; + regs.mode7_latchdata = data; +} + +//M7C +void PPU::mmio_w211d(uint8 data) { + regs.m7c = (data << 8) | regs.mode7_latchdata; + regs.mode7_latchdata = data; +} + +//M7D +void PPU::mmio_w211e(uint8 data) { + regs.m7d = (data << 8) | regs.mode7_latchdata; + regs.mode7_latchdata = data; +} + +//M7X +void PPU::mmio_w211f(uint8 data) { + regs.m7x = (data << 8) | regs.mode7_latchdata; + regs.mode7_latchdata = data; +} + +//M7Y +void PPU::mmio_w2120(uint8 data) { + regs.m7y = (data << 8) | regs.mode7_latchdata; + regs.mode7_latchdata = data; +} + +//CGADD +void PPU::mmio_w2121(uint8 data) { + regs.cgram_addr = data << 1; +} + +//CGDATA +void PPU::mmio_w2122(uint8 data) { + if((regs.cgram_addr & 1) == 0) { + regs.cgram_latchdata = data; + } else { + cgram_write((regs.cgram_addr & ~1) + 0, regs.cgram_latchdata); + cgram_write((regs.cgram_addr & ~1) + 1, data & 0x7f); + } + regs.cgram_addr = (regs.cgram_addr + 1) & 0x01ff; +} + +//W12SEL +void PPU::mmio_w2123(uint8 data) { + window.regs.bg2_two_enable = data & 0x80; + window.regs.bg2_two_invert = data & 0x40; + window.regs.bg2_one_enable = data & 0x20; + window.regs.bg2_one_invert = data & 0x10; + window.regs.bg1_two_enable = data & 0x08; + window.regs.bg1_two_invert = data & 0x04; + window.regs.bg1_one_enable = data & 0x02; + window.regs.bg1_one_invert = data & 0x01; +} + +//W34SEL +void PPU::mmio_w2124(uint8 data) { + window.regs.bg4_two_enable = data & 0x80; + window.regs.bg4_two_invert = data & 0x40; + window.regs.bg4_one_enable = data & 0x20; + window.regs.bg4_one_invert = data & 0x10; + window.regs.bg3_two_enable = data & 0x08; + window.regs.bg3_two_invert = data & 0x04; + window.regs.bg3_one_enable = data & 0x02; + window.regs.bg3_one_invert = data & 0x01; +} + +//WOBJSEL +void PPU::mmio_w2125(uint8 data) { + window.regs.col_two_enable = data & 0x80; + window.regs.col_two_invert = data & 0x40; + window.regs.col_one_enable = data & 0x20; + window.regs.col_one_invert = data & 0x10; + window.regs.oam_two_enable = data & 0x08; + window.regs.oam_two_invert = data & 0x04; + window.regs.oam_one_enable = data & 0x02; + window.regs.oam_one_invert = data & 0x01; +} + +//WH0 +void PPU::mmio_w2126(uint8 data) { + window.regs.one_left = data; +} + +//WH1 +void PPU::mmio_w2127(uint8 data) { + window.regs.one_right = data; +} + +//WH2 +void PPU::mmio_w2128(uint8 data) { + window.regs.two_left = data; +} + +//WH3 +void PPU::mmio_w2129(uint8 data) { + window.regs.two_right = data; +} + +//WBGLOG +void PPU::mmio_w212a(uint8 data) { + window.regs.bg4_mask = (data >> 6) & 3; + window.regs.bg3_mask = (data >> 4) & 3; + window.regs.bg2_mask = (data >> 2) & 3; + window.regs.bg1_mask = (data >> 0) & 3; +} + +//WOBJLOG +void PPU::mmio_w212b(uint8 data) { + window.regs.col_mask = (data >> 2) & 3; + window.regs.oam_mask = (data >> 0) & 3; +} + +//TM +void PPU::mmio_w212c(uint8 data) { + oam.regs.main_enabled = data & 0x10; + bg4.regs.main_enabled = data & 0x08; + bg3.regs.main_enabled = data & 0x04; + bg2.regs.main_enabled = data & 0x02; + bg1.regs.main_enabled = data & 0x01; +} + +//TS +void PPU::mmio_w212d(uint8 data) { + oam.regs.sub_enabled = data & 0x10; + bg4.regs.sub_enabled = data & 0x08; + bg3.regs.sub_enabled = data & 0x04; + bg2.regs.sub_enabled = data & 0x02; + bg1.regs.sub_enabled = data & 0x01; +} + +//TMW +void PPU::mmio_w212e(uint8 data) { + window.regs.oam_main_enable = data & 0x10; + window.regs.bg4_main_enable = data & 0x08; + window.regs.bg3_main_enable = data & 0x04; + window.regs.bg2_main_enable = data & 0x02; + window.regs.bg1_main_enable = data & 0x01; +} + +//TSW +void PPU::mmio_w212f(uint8 data) { + window.regs.oam_sub_enable = data & 0x10; + window.regs.bg4_sub_enable = data & 0x08; + window.regs.bg3_sub_enable = data & 0x04; + window.regs.bg2_sub_enable = data & 0x02; + window.regs.bg1_sub_enable = data & 0x01; +} + +//CGWSEL +void PPU::mmio_w2130(uint8 data) { + window.regs.col_main_mask = (data >> 6) & 3; + window.regs.col_sub_mask = (data >> 4) & 3; + screen.regs.addsub_mode = data & 0x02; + screen.regs.direct_color = data & 0x01; +} + +//CGADDSUB +void PPU::mmio_w2131(uint8 data) { + screen.regs.color_mode = data & 0x80; + screen.regs.color_halve = data & 0x40; + screen.regs.back_color_enable = data & 0x20; + screen.regs.oam_color_enable = data & 0x10; + screen.regs.bg4_color_enable = data & 0x08; + screen.regs.bg3_color_enable = data & 0x04; + screen.regs.bg2_color_enable = data & 0x02; + screen.regs.bg1_color_enable = data & 0x01; +} + +//COLDATA +void PPU::mmio_w2132(uint8 data) { + if(data & 0x80) screen.regs.color_b = data & 0x1f; + if(data & 0x40) screen.regs.color_g = data & 0x1f; + if(data & 0x20) screen.regs.color_r = data & 0x1f; +} + +//SETINI +void PPU::mmio_w2133(uint8 data) { + regs.mode7_extbg = data & 0x40; + regs.pseudo_hires = data & 0x08; + regs.overscan = data & 0x04; + oam.regs.interlace = data & 0x02; + regs.interlace = data & 0x01; + mmio_update_video_mode(); +} + +//MPYL +uint8 PPU::mmio_r2134() { + unsigned result = ((int16)regs.m7a * (int8)(regs.m7b >> 8)); + regs.ppu1_mdr = (result >> 0); + return regs.ppu1_mdr; +} + +//MPYM +uint8 PPU::mmio_r2135() { + unsigned result = ((int16)regs.m7a * (int8)(regs.m7b >> 8)); + regs.ppu1_mdr = (result >> 8); + return regs.ppu1_mdr; +} + +//MPYH +uint8 PPU::mmio_r2136() { + unsigned result = ((int16)regs.m7a * (int8)(regs.m7b >> 8)); + regs.ppu1_mdr = (result >> 16); + return regs.ppu1_mdr; +} + +//SLHV +uint8 PPU::mmio_r2137() { + if(cpu.pio() & 0x80) latch_counters(); + return cpu.regs.mdr; +} + +//OAMDATAREAD +uint8 PPU::mmio_r2138() { + regs.ppu1_mdr = oam_read(regs.oam_addr); + regs.oam_addr = (regs.oam_addr + 1) & 0x03ff; + oam.regs.first_sprite = (regs.oam_priority == false ? 0 : (regs.oam_addr >> 2) & 127); + return regs.ppu1_mdr; +} + +//VMDATALREAD +uint8 PPU::mmio_r2139() { + uint16 addr = get_vram_address() + 0; + regs.ppu1_mdr = regs.vram_readbuffer >> 0; + if(regs.vram_incmode == 0) { + addr &= ~1; + regs.vram_readbuffer = vram_read(addr + 0) << 0; + regs.vram_readbuffer |= vram_read(addr + 1) << 8; + regs.vram_addr += regs.vram_incsize; + } + return regs.ppu1_mdr; +} + +//VMDATAHREAD +uint8 PPU::mmio_r213a() { + uint16 addr = get_vram_address() + 1; + regs.ppu1_mdr = regs.vram_readbuffer >> 8; + if(regs.vram_incmode == 1) { + addr &= ~1; + regs.vram_readbuffer = vram_read(addr + 0) << 0; + regs.vram_readbuffer |= vram_read(addr + 1) << 8; + regs.vram_addr += regs.vram_incsize; + } + return regs.ppu1_mdr; +} + +//CGDATAREAD +uint8 PPU::mmio_r213b() { + if((regs.cgram_addr & 1) == 0) { + regs.ppu2_mdr = cgram_read(regs.cgram_addr) & 0xff; + } else { + regs.ppu2_mdr &= 0x80; + regs.ppu2_mdr |= cgram_read(regs.cgram_addr) & 0x7f; + } + regs.cgram_addr = (regs.cgram_addr + 1) & 0x01ff; + return regs.ppu2_mdr; +} + +//OPHCT +uint8 PPU::mmio_r213c() { + if(regs.latch_hcounter == 0) { + regs.ppu2_mdr = regs.hcounter & 0xff; + } else { + regs.ppu2_mdr &= 0xfe; + regs.ppu2_mdr |= (regs.hcounter >> 8) & 1; + } + regs.latch_hcounter ^= 1; + return regs.ppu2_mdr; +} + +//OPVCT +uint8 PPU::mmio_r213d() { + if(regs.latch_vcounter == 0) { + regs.ppu2_mdr = regs.vcounter & 0xff; + } else { + regs.ppu2_mdr &= 0xfe; + regs.ppu2_mdr |= (regs.vcounter >> 8) & 1; + } + regs.latch_vcounter ^= 1; + return regs.ppu2_mdr; +} + +//STAT77 +uint8 PPU::mmio_r213e() { + regs.ppu1_mdr &= 0x10; + regs.ppu1_mdr |= oam.regs.time_over << 7; + regs.ppu1_mdr |= oam.regs.range_over << 6; + regs.ppu1_mdr |= ppu1_version & 0x0f; + return regs.ppu1_mdr; +} + +//STAT78 +uint8 PPU::mmio_r213f() { + regs.latch_hcounter = 0; + regs.latch_vcounter = 0; + + regs.ppu2_mdr &= 0x20; + regs.ppu2_mdr |= field() << 7; + if((cpu.pio() & 0x80) == 0) { + regs.ppu2_mdr |= 0x40; + } else if(regs.counters_latched) { + regs.ppu2_mdr |= 0x40; + regs.counters_latched = false; + } + regs.ppu2_mdr |= (system.region() == System::Region::NTSC ? 0 : 1) << 4; + regs.ppu2_mdr |= ppu2_version & 0x0f; + return regs.ppu2_mdr; +} + +void PPU::mmio_reset() { + regs.ppu1_mdr = 0xff; + regs.ppu2_mdr = 0xff; + + regs.vram_readbuffer = 0x0000; + regs.oam_latchdata = 0x00; + regs.cgram_latchdata = 0x00; + regs.bgofs_latchdata = 0x00; + regs.mode7_latchdata = 0x00; + regs.counters_latched = false; + regs.latch_hcounter = 0; + regs.latch_vcounter = 0; + + regs.ioamaddr = 0; + regs.icgramaddr = 0; + + //$2100 INIDISP + regs.display_disabled = true; + regs.display_brightness = 0; + + //$2102 OAMADDL + //$2103 OAMADDH + regs.oam_baseaddr = 0x0000; + regs.oam_addr = 0x0000; + regs.oam_priority = false; + + //$2105 BGMODE + regs.bg3_priority = false; + regs.bgmode = 0; + + //$210d BG1HOFS + regs.mode7_hoffset = 0x0000; + + //$210e BG1VOFS + regs.mode7_voffset = 0x0000; + + //$2115 VMAIN + regs.vram_incmode = 1; + regs.vram_mapping = 0; + regs.vram_incsize = 1; + + //$2116 VMADDL + //$2117 VMADDH + regs.vram_addr = 0x0000; + + //$211a M7SEL + regs.mode7_repeat = 0; + regs.mode7_vflip = false; + regs.mode7_hflip = false; + + //$211b M7A + regs.m7a = 0x0000; + + //$211c M7B + regs.m7b = 0x0000; + + //$211d M7C + regs.m7c = 0x0000; + + //$211e M7D + regs.m7d = 0x0000; + + //$211f M7X + regs.m7x = 0x0000; + + //$2120 M7Y + regs.m7y = 0x0000; + + //$2121 CGADD + regs.cgram_addr = 0x0000; + + //$2133 SETINI + regs.mode7_extbg = false; + regs.pseudo_hires = false; + regs.overscan = false; + regs.interlace = false; + + //$213c OPHCT + regs.hcounter = 0; + + //$213d OPVCT + regs.vcounter = 0; +} + +uint8 PPU::mmio_read(unsigned addr) { + cpu.synchronize_ppu(); + + switch(addr & 0xffff) { + case 0x2104: + case 0x2105: + case 0x2106: + case 0x2108: + case 0x2109: + case 0x210a: + case 0x2114: + case 0x2115: + case 0x2116: + case 0x2118: + case 0x2119: + case 0x211a: + case 0x2124: + case 0x2125: + case 0x2126: + case 0x2128: + case 0x2129: + case 0x212a: return regs.ppu1_mdr; + case 0x2134: return mmio_r2134(); //MPYL + case 0x2135: return mmio_r2135(); //MPYM + case 0x2136: return mmio_r2136(); //MYPH + case 0x2137: return mmio_r2137(); //SLHV + case 0x2138: return mmio_r2138(); //OAMDATAREAD + case 0x2139: return mmio_r2139(); //VMDATALREAD + case 0x213a: return mmio_r213a(); //VMDATAHREAD + case 0x213b: return mmio_r213b(); //CGDATAREAD + case 0x213c: return mmio_r213c(); //OPHCT + case 0x213d: return mmio_r213d(); //OPVCT + case 0x213e: return mmio_r213e(); //STAT77 + case 0x213f: return mmio_r213f(); //STAT78 + } + + return cpu.regs.mdr; +} + +void PPU::mmio_write(unsigned addr, uint8 data) { + cpu.synchronize_ppu(); + + switch(addr & 0xffff) { + case 0x2100: return mmio_w2100(data); //INIDISP + case 0x2101: return mmio_w2101(data); //OBSEL + case 0x2102: return mmio_w2102(data); //OAMADDL + case 0x2103: return mmio_w2103(data); //OAMADDH + case 0x2104: return mmio_w2104(data); //OAMDATA + case 0x2105: return mmio_w2105(data); //BGMODE + case 0x2106: return mmio_w2106(data); //MOSAIC + case 0x2107: return mmio_w2107(data); //BG1SC + case 0x2108: return mmio_w2108(data); //BG2SC + case 0x2109: return mmio_w2109(data); //BG3SC + case 0x210a: return mmio_w210a(data); //BG4SC + case 0x210b: return mmio_w210b(data); //BG12NBA + case 0x210c: return mmio_w210c(data); //BG34NBA + case 0x210d: return mmio_w210d(data); //BG1HOFS + case 0x210e: return mmio_w210e(data); //BG1VOFS + case 0x210f: return mmio_w210f(data); //BG2HOFS + case 0x2110: return mmio_w2110(data); //BG2VOFS + case 0x2111: return mmio_w2111(data); //BG3HOFS + case 0x2112: return mmio_w2112(data); //BG3VOFS + case 0x2113: return mmio_w2113(data); //BG4HOFS + case 0x2114: return mmio_w2114(data); //BG4VOFS + case 0x2115: return mmio_w2115(data); //VMAIN + case 0x2116: return mmio_w2116(data); //VMADDL + case 0x2117: return mmio_w2117(data); //VMADDH + case 0x2118: return mmio_w2118(data); //VMDATAL + case 0x2119: return mmio_w2119(data); //VMDATAH + case 0x211a: return mmio_w211a(data); //M7SEL + case 0x211b: return mmio_w211b(data); //M7A + case 0x211c: return mmio_w211c(data); //M7B + case 0x211d: return mmio_w211d(data); //M7C + case 0x211e: return mmio_w211e(data); //M7D + case 0x211f: return mmio_w211f(data); //M7X + case 0x2120: return mmio_w2120(data); //M7Y + case 0x2121: return mmio_w2121(data); //CGADD + case 0x2122: return mmio_w2122(data); //CGDATA + case 0x2123: return mmio_w2123(data); //W12SEL + case 0x2124: return mmio_w2124(data); //W34SEL + case 0x2125: return mmio_w2125(data); //WOBJSEL + case 0x2126: return mmio_w2126(data); //WH0 + case 0x2127: return mmio_w2127(data); //WH1 + case 0x2128: return mmio_w2128(data); //WH2 + case 0x2129: return mmio_w2129(data); //WH3 + case 0x212a: return mmio_w212a(data); //WBGLOG + case 0x212b: return mmio_w212b(data); //WOBJLOG + case 0x212c: return mmio_w212c(data); //TM + case 0x212d: return mmio_w212d(data); //TS + case 0x212e: return mmio_w212e(data); //TMW + case 0x212f: return mmio_w212f(data); //TSW + case 0x2130: return mmio_w2130(data); //CGWSEL + case 0x2131: return mmio_w2131(data); //CGADDSUB + case 0x2132: return mmio_w2132(data); //COLDATA + case 0x2133: return mmio_w2133(data); //SETINI + } +} + +#endif diff --git a/src/snes/ppu/sppu/mmio/mmio.hpp b/asnes/ppu/mmio/mmio.hpp similarity index 100% rename from src/snes/ppu/sppu/mmio/mmio.hpp rename to asnes/ppu/mmio/mmio.hpp diff --git a/asnes/ppu/ppu.cpp b/asnes/ppu/ppu.cpp new file mode 100644 index 00000000..edfdebd0 --- /dev/null +++ b/asnes/ppu/ppu.cpp @@ -0,0 +1,155 @@ +#include + +#define PPU_CPP +namespace SNES { + +#if defined(DEBUGGER) + #include "debugger/debugger.cpp" + PPUDebugger ppu; +#else + PPU ppu; +#endif + +#include "background/background.cpp" +#include "mmio/mmio.cpp" +#include "screen/screen.cpp" +#include "sprite/sprite.cpp" +#include "window/window.cpp" +#include "serialization.cpp" + +void PPU::step(unsigned clocks) { + clock += clocks; +} + +void PPU::synchronize_cpu() { + if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread); +} + +void PPU::Enter() { ppu.enter(); } + +void PPU::enter() { + while(true) { + if(scheduler.sync == Scheduler::SynchronizeMode::All) { + scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); + } + + scanline(); + add_clocks(88); + + if(vcounter() <= (!regs.overscan ? 224 : 239)) { + for(unsigned n = 0; n < 256; n++) { + bg1.run(); + bg2.run(); + bg3.run(); + bg4.run(); + add_clocks(2); + + bg1.run(); + bg2.run(); + bg3.run(); + bg4.run(); + oam.run(); + window.run(); + screen.run(); + add_clocks(2); + } + + add_clocks(22); + oam.tilefetch(); + } else { + add_clocks(1024 + 22 + 136); + } + + add_clocks(lineclocks() - 88 - 1024 - 22 - 136); + } +} + +void PPU::add_clocks(unsigned clocks) { + clocks >>= 1; + while(clocks--) { + tick(2); + step(2); + synchronize_cpu(); + } +} + +void PPU::power() { + ppu1_version = config.ppu1.version; + ppu2_version = config.ppu2.version; + + memset(memory::vram.data(), 0x00, memory::vram.size()); + memset(memory::oam.data(), 0x00, memory::oam.size()); + memset(memory::cgram.data(), 0x00, memory::cgram.size()); + + reset(); +} + +void PPU::reset() { + create(Enter, system.cpu_frequency()); + PPUCounter::reset(); + memset(surface, 0, 512 * 512 * sizeof(uint16)); + + mmio_reset(); + bg1.reset(); + bg2.reset(); + bg3.reset(); + bg4.reset(); + oam.reset(); + window.reset(); + screen.reset(); + + frame(); +} + +void PPU::scanline() { + if(vcounter() == 0) frame(); + bg1.scanline(); + bg2.scanline(); + bg3.scanline(); + bg4.scanline(); + oam.scanline(); + window.scanline(); + screen.scanline(); +} + +void PPU::frame() { + system.frame(); + oam.frame(); + + display.interlace = regs.interlace; + display.overscan = regs.overscan; + + //frame counter + static signed framecount = 0; + static time_t prev, curr; + framecount++; + + time(&curr); + if(curr != prev) { + status.frames_updated = true; + status.frames_executed = framecount; + framecount = 0; + prev = curr; + } +} + +PPU::PPU() : +bg1(*this, Background::ID::BG1), +bg2(*this, Background::ID::BG2), +bg3(*this, Background::ID::BG3), +bg4(*this, Background::ID::BG4), +oam(*this), +window(*this), +screen(*this) { + surface = new uint16[512 * 512]; + output = surface + 16 * 512; + + status.frames_updated = false; + status.frames_executed = 0; +} + +PPU::~PPU() { + delete[] surface; +} + +} diff --git a/asnes/ppu/ppu.hpp b/asnes/ppu/ppu.hpp new file mode 100644 index 00000000..c174446f --- /dev/null +++ b/asnes/ppu/ppu.hpp @@ -0,0 +1,59 @@ +#include "counter/counter.hpp" + +class PPU : public Processor, public PPUCounter, public MMIO { +public: + #include "background/background.hpp" + #include "mmio/mmio.hpp" + #include "screen/screen.hpp" + #include "sprite/sprite.hpp" + #include "window/window.hpp" + + Background bg1; + Background bg2; + Background bg3; + Background bg4; + Sprite oam; + Window window; + Screen screen; + + uint16 *surface; + uint16 *output; + + struct { + bool frames_updated; + unsigned frames_executed; + } status; + + uint8 ppu1_version; + uint8 ppu2_version; + + struct { + bool interlace; + bool overscan; + } display; + + //synchronization + alwaysinline void step(unsigned clocks); + alwaysinline void synchronize_cpu(); + + static void Enter(); + void enter(); + void add_clocks(unsigned); + + void power(); + void reset(); + + void scanline(); + void frame(); + + void serialize(serializer&); + PPU(); + ~PPU(); +}; + +#if defined(DEBUGGER) + #include "debugger/debugger.hpp" + extern PPUDebugger ppu; +#else + extern PPU ppu; +#endif diff --git a/asnes/ppu/screen/screen.cpp b/asnes/ppu/screen/screen.cpp new file mode 100644 index 00000000..0e8fb921 --- /dev/null +++ b/asnes/ppu/screen/screen.cpp @@ -0,0 +1,219 @@ +#ifdef PPU_CPP + +void PPU::Screen::scanline() { + output = self.output + self.vcounter() * 1024; + if(self.display.interlace && self.field()) output += 512; +} + +void PPU::Screen::run() { + uint16 color; + if(self.regs.pseudo_hires == false && self.regs.bgmode != 5 && self.regs.bgmode != 6) { + color = get_pixel(false); + *output++ = color; + *output++ = color; + } else { + color = get_pixel(true); + *output++ = color; + color = get_pixel(false); + *output++ = color; + } +} + +uint16 PPU::Screen::get_pixel(bool swap) { + enum source_t { BG1, BG2, BG3, BG4, OAM, BACK }; + bool color_enable[] = { regs.bg1_color_enable, regs.bg2_color_enable, regs.bg3_color_enable, regs.bg4_color_enable, regs.oam_color_enable, regs.back_color_enable }; + + //=========== + //main screen + //=========== + + unsigned priority_main = 0; + unsigned color_main; + unsigned source_main; + + if(self.bg1.output.main.priority) { + priority_main = self.bg1.output.main.priority; + if(regs.direct_color && (self.regs.bgmode == 3 || self.regs.bgmode == 4 || self.regs.bgmode == 7)) { + color_main = get_direct_color(self.bg1.output.main.palette, self.bg1.output.main.tile); + } else { + color_main = get_color(self.bg1.output.main.palette); + } + source_main = BG1; + } + if(self.bg2.output.main.priority > priority_main) { + priority_main = self.bg2.output.main.priority; + color_main = get_color(self.bg2.output.main.palette); + source_main = BG2; + } + if(self.bg3.output.main.priority > priority_main) { + priority_main = self.bg3.output.main.priority; + color_main = get_color(self.bg3.output.main.palette); + source_main = BG3; + } + if(self.bg4.output.main.priority > priority_main) { + priority_main = self.bg4.output.main.priority; + color_main = get_color(self.bg4.output.main.palette); + source_main = BG4; + } + if(self.oam.output.main.priority > priority_main) { + priority_main = self.oam.output.main.priority; + color_main = get_color(self.oam.output.main.palette); + source_main = OAM; + } + if(priority_main == 0) { + color_main = get_color(0); + source_main = BACK; + } + + //========== + //sub screen + //========== + + unsigned priority_sub = 0; + unsigned color_sub; + unsigned source_sub; + + if(self.bg1.output.sub.priority) { + priority_sub = self.bg1.output.sub.priority; + if(regs.direct_color && (self.regs.bgmode == 3 || self.regs.bgmode == 4 || self.regs.bgmode == 7)) { + color_sub = get_direct_color(self.bg1.output.sub.palette, self.bg1.output.sub.tile); + } else { + color_sub = get_color(self.bg1.output.sub.palette); + } + source_sub = BG1; + } + if(self.bg2.output.sub.priority > priority_sub) { + priority_sub = self.bg2.output.sub.priority; + color_sub = get_color(self.bg2.output.sub.palette); + source_sub = BG2; + } + if(self.bg3.output.sub.priority > priority_sub) { + priority_sub = self.bg3.output.sub.priority; + color_sub = get_color(self.bg3.output.sub.palette); + source_sub = BG3; + } + if(self.bg4.output.sub.priority > priority_sub) { + priority_sub = self.bg4.output.sub.priority; + color_sub = get_color(self.bg4.output.sub.palette); + source_sub = BG4; + } + if(self.oam.output.sub.priority > priority_sub) { + priority_sub = self.oam.output.sub.priority; + color_sub = get_color(self.oam.output.sub.palette); + source_sub = OAM; + } + if(priority_sub == 0) { + if(self.regs.pseudo_hires == true || self.regs.bgmode == 5 || self.regs.bgmode == 6) { + color_sub = get_color(0); + } else { + color_sub = (regs.color_b << 10) + (regs.color_g << 5) + (regs.color_r << 0); + } + source_sub = BACK; + } + + if(swap == true) { + nall::swap(priority_main, priority_sub); + nall::swap(color_main, color_sub); + nall::swap(source_main, source_sub); + } + + uint16 output; + if(!regs.addsub_mode) { + source_sub = BACK; + color_sub = (regs.color_b << 10) + (regs.color_g << 5) + (regs.color_r << 0); + } + + if(self.window.output.main.color_enable == false) { + if(self.window.output.sub.color_enable == false) { + return 0x0000; + } + color_main = 0x0000; + } + + bool color_exempt = (source_main == OAM && self.oam.output.main.palette < 192); + if(!color_exempt && color_enable[source_main] && self.window.output.sub.color_enable) { + bool halve = false; + if(regs.color_halve && self.window.output.main.color_enable) { + if(!regs.addsub_mode || source_sub != BACK) halve = true; + } + output = addsub(color_main, color_sub, halve); + } else { + output = color_main; + } + + //======== + //lighting + //======== + + output = light_table[self.regs.display_brightness][output]; + if(self.regs.display_disabled) output = 0x0000; + return output; +} + +uint16 PPU::Screen::addsub(unsigned x, unsigned y, bool halve) { + if(!regs.color_mode) { + if(!halve) { + unsigned sum = x + y; + unsigned carry = (sum - ((x ^ y) & 0x0421)) & 0x8420; + return (sum - carry) | (carry - (carry >> 5)); + } else { + return (x + y - ((x ^ y) & 0x0421)) >> 1; + } + } else { + unsigned diff = x - y + 0x8420; + unsigned borrow = (diff - ((x ^ y) & 0x8420)) & 0x8420; + if(!halve) { + return (diff - borrow) & (borrow - (borrow >> 5)); + } else { + return (((diff - borrow) & (borrow - (borrow >> 5))) & 0x7bde) >> 1; + } + } +} + +uint16 PPU::Screen::get_color(unsigned palette) { + palette <<= 1; + return memory::cgram[palette + 0] + (memory::cgram[palette + 1] << 8); +} + +uint16 PPU::Screen::get_direct_color(unsigned palette, unsigned tile) { + //palette = -------- BBGGGRRR + //tile = ---bgr-- -------- + //output = 0BBb00GG Gg0RRRr0 + return ((palette << 7) & 0x6000) + ((tile >> 0) & 0x1000) + + ((palette << 4) & 0x0380) + ((tile >> 5) & 0x0040) + + ((palette << 2) & 0x001c) + ((tile >> 9) & 0x0002); +} + +void PPU::Screen::reset() { + regs.addsub_mode = 0; + regs.direct_color = 0; + regs.color_mode = 0; + regs.color_halve = 0; + regs.bg1_color_enable = 0; + regs.bg2_color_enable = 0; + regs.bg3_color_enable = 0; + regs.bg4_color_enable = 0; + regs.oam_color_enable = 0; + regs.back_color_enable = 0; + regs.color_r = 0; + regs.color_g = 0; + regs.color_b = 0; +} + +PPU::Screen::Screen(PPU &self) : self(self) { + for(unsigned l = 0; l < 16; l++) { + for(unsigned r = 0; r < 32; r++) { + for(unsigned g = 0; g < 32; g++) { + for(unsigned b = 0; b < 32; b++) { + double luma = (double)l / 15.0; + unsigned ar = (luma * r + 0.5); + unsigned ag = (luma * g + 0.5); + unsigned ab = (luma * b + 0.5); + light_table[l][(r << 10) + (g << 5) + b] = (ab << 10) + (ag << 5) + ar; + } + } + } + } +} + +#endif diff --git a/asnes/ppu/screen/screen.hpp b/asnes/ppu/screen/screen.hpp new file mode 100644 index 00000000..3472dec0 --- /dev/null +++ b/asnes/ppu/screen/screen.hpp @@ -0,0 +1,37 @@ +class Screen { +public: + PPU &self; + uint16 *output; + + struct { + bool addsub_mode; + bool direct_color; + + bool color_mode; + bool color_halve; + bool bg1_color_enable; + bool bg2_color_enable; + bool bg3_color_enable; + bool bg4_color_enable; + bool oam_color_enable; + bool back_color_enable; + + uint8 color_b; + uint8 color_g; + uint8 color_r; + } regs; + + void scanline(); + void run(); + void reset(); + + void serialize(serializer&); + Screen(PPU &self); + +private: + uint16 light_table[16][32768]; + uint16 get_pixel(bool swap); + uint16 addsub(unsigned x, unsigned y, bool halve); + uint16 get_color(unsigned palette); + uint16 get_direct_color(unsigned palette, unsigned tile); +}; diff --git a/asnes/ppu/serialization.cpp b/asnes/ppu/serialization.cpp new file mode 100644 index 00000000..aa12be90 --- /dev/null +++ b/asnes/ppu/serialization.cpp @@ -0,0 +1,264 @@ +#ifdef PPU_CPP + +void PPUCounter::serialize(serializer &s) { + s.integer(status.interlace); + s.integer(status.field); + s.integer(status.vcounter); + s.integer(status.hcounter); + + s.array(history.field); + s.array(history.vcounter); + s.array(history.hcounter); + s.integer(history.index); +} + +void PPU::serialize(serializer &s) { + Processor::serialize(s); + PPUCounter::serialize(s); + + s.integer(status.frames_updated); + s.integer(status.frames_executed); + + s.integer(ppu1_version); + s.integer(ppu2_version); + + s.integer(display.interlace); + s.integer(display.overscan); + + s.integer(regs.ppu1_mdr); + s.integer(regs.ppu2_mdr); + + s.integer(regs.vram_readbuffer); + s.integer(regs.oam_latchdata); + s.integer(regs.cgram_latchdata); + s.integer(regs.bgofs_latchdata); + s.integer(regs.mode7_latchdata); + s.integer(regs.counters_latched); + s.integer(regs.latch_hcounter); + s.integer(regs.latch_vcounter); + + s.integer(regs.ioamaddr); + s.integer(regs.icgramaddr); + + s.integer(regs.display_disabled); + s.integer(regs.display_brightness); + + s.integer(regs.oam_baseaddr); + s.integer(regs.oam_addr); + s.integer(regs.oam_priority); + + s.integer(regs.bg3_priority); + s.integer(regs.bgmode); + + s.integer(regs.mode7_hoffset); + s.integer(regs.mode7_voffset); + + s.integer(regs.vram_incmode); + s.integer(regs.vram_mapping); + s.integer(regs.vram_incsize); + + s.integer(regs.vram_addr); + + s.integer(regs.mode7_repeat); + s.integer(regs.mode7_vflip); + s.integer(regs.mode7_hflip); + + s.integer(regs.m7a); + s.integer(regs.m7b); + s.integer(regs.m7c); + s.integer(regs.m7d); + s.integer(regs.m7x); + s.integer(regs.m7y); + + s.integer(regs.cgram_addr); + + s.integer(regs.mode7_extbg); + s.integer(regs.pseudo_hires); + s.integer(regs.overscan); + s.integer(regs.interlace); + + s.integer(regs.hcounter); + s.integer(regs.vcounter); + + bg1.serialize(s); + bg2.serialize(s); + bg3.serialize(s); + bg4.serialize(s); + oam.serialize(s); + window.serialize(s); + screen.serialize(s); +} + +void PPU::Background::serialize(serializer &s) { + s.integer(id); + + s.integer(t.x); + s.integer(t.mosaic_y); + s.integer(t.mosaic_countdown); + + s.integer(regs.tiledata_addr); + s.integer(regs.screen_addr); + s.integer(regs.screen_size); + s.integer(regs.mosaic); + s.integer(regs.tile_size); + + s.integer(regs.mode); + s.integer(regs.priority0); + s.integer(regs.priority1); + + s.integer(regs.main_enabled); + s.integer(regs.sub_enabled); + + s.integer(regs.hoffset); + s.integer(regs.voffset); + + s.integer(output.main.priority); + s.integer(output.main.palette); + s.integer(output.main.tile); + + s.integer(output.sub.priority); + s.integer(output.sub.palette); + s.integer(output.sub.tile); +} + +void PPU::Sprite::serialize(serializer &s) { + for(unsigned i = 0; i < 128; i++) { + s.integer(list[i].x); + s.integer(list[i].y); + s.integer(list[i].character); + s.integer(list[i].nameselect); + s.integer(list[i].vflip); + s.integer(list[i].hflip); + s.integer(list[i].priority); + s.integer(list[i].palette); + s.integer(list[i].size); + } + + s.integer(t.x); + s.integer(t.y); + + s.integer(t.item_count); + s.integer(t.tile_count); + + s.integer(t.active); + for(unsigned n = 0; n < 2; n++) { + s.array(t.item[n]); + for(unsigned i = 0; i < 34; i++) { + s.integer(t.tile[n][i].x); + s.integer(t.tile[n][i].priority); + s.integer(t.tile[n][i].palette); + s.integer(t.tile[n][i].hflip); + s.integer(t.tile[n][i].d0); + s.integer(t.tile[n][i].d1); + s.integer(t.tile[n][i].d2); + s.integer(t.tile[n][i].d3); + } + } + + s.integer(regs.main_enabled); + s.integer(regs.sub_enabled); + s.integer(regs.interlace); + + s.integer(regs.base_size); + s.integer(regs.nameselect); + s.integer(regs.tiledata_addr); + s.integer(regs.first_sprite); + + s.integer(regs.priority0); + s.integer(regs.priority1); + s.integer(regs.priority2); + s.integer(regs.priority3); + + s.integer(regs.time_over); + s.integer(regs.range_over); + + s.integer(output.main.priority); + s.integer(output.main.palette); + + s.integer(output.sub.priority); + s.integer(output.sub.palette); +} + +void PPU::Window::serialize(serializer &s) { + s.integer(t.x); + + s.integer(regs.bg1_one_enable); + s.integer(regs.bg1_one_invert); + s.integer(regs.bg1_two_enable); + s.integer(regs.bg1_two_invert); + + s.integer(regs.bg2_one_enable); + s.integer(regs.bg2_one_invert); + s.integer(regs.bg2_two_enable); + s.integer(regs.bg2_two_invert); + + s.integer(regs.bg3_one_enable); + s.integer(regs.bg3_one_invert); + s.integer(regs.bg3_two_enable); + s.integer(regs.bg3_two_invert); + + s.integer(regs.bg4_one_enable); + s.integer(regs.bg4_one_invert); + s.integer(regs.bg4_two_enable); + s.integer(regs.bg4_two_invert); + + s.integer(regs.oam_one_enable); + s.integer(regs.oam_one_invert); + s.integer(regs.oam_two_enable); + s.integer(regs.oam_two_invert); + + s.integer(regs.col_one_enable); + s.integer(regs.col_one_invert); + s.integer(regs.col_two_enable); + s.integer(regs.col_two_invert); + + s.integer(regs.one_left); + s.integer(regs.one_right); + s.integer(regs.two_left); + s.integer(regs.two_right); + + s.integer(regs.bg1_mask); + s.integer(regs.bg2_mask); + s.integer(regs.bg3_mask); + s.integer(regs.bg4_mask); + s.integer(regs.oam_mask); + s.integer(regs.col_mask); + + s.integer(regs.bg1_main_enable); + s.integer(regs.bg1_sub_enable); + s.integer(regs.bg2_main_enable); + s.integer(regs.bg2_sub_enable); + s.integer(regs.bg3_main_enable); + s.integer(regs.bg3_sub_enable); + s.integer(regs.bg4_main_enable); + s.integer(regs.bg4_sub_enable); + s.integer(regs.oam_main_enable); + s.integer(regs.oam_sub_enable); + + s.integer(regs.col_main_mask); + s.integer(regs.col_sub_mask); + + s.integer(output.main.color_enable); + s.integer(output.sub.color_enable); + +} + +void PPU::Screen::serialize(serializer &s) { + s.integer(regs.addsub_mode); + s.integer(regs.direct_color); + + s.integer(regs.color_mode); + s.integer(regs.color_halve); + s.integer(regs.bg1_color_enable); + s.integer(regs.bg2_color_enable); + s.integer(regs.bg3_color_enable); + s.integer(regs.bg4_color_enable); + s.integer(regs.oam_color_enable); + s.integer(regs.back_color_enable); + + s.integer(regs.color_b); + s.integer(regs.color_g); + s.integer(regs.color_r); +} + +#endif diff --git a/asnes/ppu/sprite/list.cpp b/asnes/ppu/sprite/list.cpp new file mode 100644 index 00000000..ea0db2e0 --- /dev/null +++ b/asnes/ppu/sprite/list.cpp @@ -0,0 +1,54 @@ +#ifdef PPU_CPP + +void PPU::Sprite::update(unsigned addr, uint8 data) { + if(addr < 0x0200) { + unsigned n = addr >> 2; + addr &= 3; + if(addr == 0) { + list[n].x = (list[n].x & 0x100) | data; + } else if(addr == 1) { + list[n].y = data; + } else if(addr == 2) { + list[n].character = data; + } else { //(addr == 3) + list[n].vflip = data & 0x80; + list[n].hflip = data & 0x40; + list[n].priority = (data >> 4) & 3; + list[n].palette = (data >> 1) & 7; + list[n].nameselect = data & 1; + } + } else { + unsigned n = (addr & 0x1f) << 2; + list[n + 0].x = ((data & 0x01) << 8) | (list[n + 0].x & 0xff); + list[n + 0].size = data & 0x02; + list[n + 1].x = ((data & 0x04) << 6) | (list[n + 1].x & 0xff); + list[n + 1].size = data & 0x08; + list[n + 2].x = ((data & 0x10) << 4) | (list[n + 2].x & 0xff); + list[n + 2].size = data & 0x20; + list[n + 3].x = ((data & 0x40) << 2) | (list[n + 3].x & 0xff); + list[n + 3].size = data & 0x80; + } +} + +unsigned PPU::Sprite::SpriteItem::width() const { + if(size == 0) { + static unsigned width[] = { 8, 8, 8, 16, 16, 32, 16, 16 }; + return width[ppu.oam.regs.base_size]; + } else { + static unsigned width[] = { 16, 32, 64, 32, 64, 64, 32, 32 }; + return width[ppu.oam.regs.base_size]; + } +} + +unsigned PPU::Sprite::SpriteItem::height() const { + if(size == 0) { + if(ppu.oam.regs.interlace && ppu.oam.regs.base_size >= 6) return 16; + static unsigned height[] = { 8, 8, 8, 16, 16, 32, 32, 32 }; + return height[ppu.oam.regs.base_size]; + } else { + static unsigned height[] = { 16, 32, 64, 32, 64, 64, 64, 32 }; + return height[ppu.oam.regs.base_size]; + } +} + +#endif diff --git a/asnes/ppu/sprite/sprite.cpp b/asnes/ppu/sprite/sprite.cpp new file mode 100644 index 00000000..8501eee0 --- /dev/null +++ b/asnes/ppu/sprite/sprite.cpp @@ -0,0 +1,218 @@ +#ifdef PPU_CPP + +#include "list.cpp" + +void PPU::Sprite::address_reset() { + self.regs.oam_addr = self.regs.oam_baseaddr << 1; + regs.first_sprite = (self.regs.oam_priority == false ? 0 : (self.regs.oam_addr >> 2) & 127); +} + +void PPU::Sprite::frame() { + regs.time_over = false; + regs.range_over = false; +} + +void PPU::Sprite::scanline() { + t.x = 0; + t.y = self.vcounter(); + + t.item_count = 0; + t.tile_count = 0; + + t.active = !t.active; + auto oam_item = t.item[t.active]; + auto oam_tile = t.tile[t.active]; + + if(t.y == (!self.regs.overscan ? 225 : 240) && self.regs.display_disabled == false) address_reset(); + if(t.y >= (!self.regs.overscan ? 224 : 239)) return; + + memset(oam_item, 0xff, 32); //default to invalid + for(unsigned i = 0; i < 34; i++) oam_tile[i].x = 0xffff; //default to invalid + + for(unsigned i = 0; i < 128; i++) { + unsigned sprite = (regs.first_sprite + i) & 127; + if(on_scanline(list[sprite]) == false) continue; + if(t.item_count++ >= 32) break; + oam_item[t.item_count - 1] = sprite; + } + + if(t.item_count > 0 && oam_item[t.item_count - 1] != 0xff) { + ppu.regs.ioamaddr = 0x0200 + (oam_item[t.item_count - 1] >> 2); + } +} + +bool PPU::Sprite::on_scanline(SpriteItem &sprite) { + if(sprite.x > 256 && (sprite.x + sprite.width() - 1) < 512) return false; + signed height = (regs.interlace == false ? sprite.height() : (sprite.height() >> 1)); + if(t.y >= sprite.y && t.y < (sprite.y + height)) return true; + if((sprite.y + height) >= 256 && t.y < ((sprite.y + height) & 255)) return true; + return false; +} + +void PPU::Sprite::run() { + output.main.priority = 0; + output.sub.priority = 0; + + auto oam_tile = t.tile[!t.active]; + unsigned priority_table[] = { regs.priority0, regs.priority1, regs.priority2, regs.priority3 }; + unsigned x = t.x++; + + for(unsigned n = 0; n < 34; n++) { + auto tile = oam_tile[n]; + if(tile.x == 0xffff) break; + + int px = x - sclip<9>(tile.x); + if(px & ~7) continue; + + unsigned mask = 0x80 >> (tile.hflip == false ? px : 7 - px); + unsigned color; + color = ((bool)(tile.d0 & mask)) << 0; + color |= ((bool)(tile.d1 & mask)) << 1; + color |= ((bool)(tile.d2 & mask)) << 2; + color |= ((bool)(tile.d3 & mask)) << 3; + + if(color) { + if(regs.main_enabled) { + output.main.palette = tile.palette + color; + output.main.priority = priority_table[tile.priority]; + } + + if(regs.sub_enabled) { + output.sub.palette = tile.palette + color; + output.sub.priority = priority_table[tile.priority]; + } + } + } +} + +void PPU::Sprite::tilefetch() { + auto oam_item = t.item[t.active]; + auto oam_tile = t.tile[t.active]; + + for(signed i = 31; i >= 0; i--) { + if(oam_item[i] == 0xff) continue; + auto sprite = list[oam_item[i]]; + + unsigned tile_width = sprite.width() >> 3; + signed x = sprite.x; + signed y = (t.y - sprite.y) & 0xff; + if(regs.interlace) y <<= 1; + + if(sprite.vflip) { + if(sprite.width() == sprite.height()) { + y = (sprite.height() - 1) - y; + } else if(y < sprite.width()) { + y = (sprite.width() - 1) - y; + } else { + y = sprite.width() + ((sprite.width() - 1) - (y - sprite.width())); + } + } + + if(regs.interlace) { + y = (sprite.vflip == false ? y + self.field() : y - self.field()); + } + + x &= 511; + y &= 255; + + uint16 tiledata_addr = regs.tiledata_addr; + uint16 chrx = (sprite.character >> 0) & 15; + uint16 chry = (sprite.character >> 4) & 15; + if(sprite.nameselect) { + tiledata_addr += (256 * 32) + (regs.nameselect << 13); + } + chry += (y >> 3); + chry &= 15; + chry <<= 4; + + for(unsigned tx = 0; tx < tile_width; tx++) { + unsigned sx = (x + (tx << 3)) & 511; + if(x != 256 && sx >= 256 && (sx + 7) < 512) continue; + if(t.tile_count++ >= 34) break; + + unsigned n = t.tile_count - 1; + oam_tile[n].x = sx; + oam_tile[n].priority = sprite.priority; + oam_tile[n].palette = 128 + (sprite.palette << 4); + oam_tile[n].hflip = sprite.hflip; + + unsigned mx = (sprite.hflip == false) ? tx : ((tile_width - 1) - tx); + unsigned pos = tiledata_addr + ((chry + ((chrx + mx) & 15)) << 5); + uint16 addr = (pos & 0xffe0) + ((y & 7) * 2); + + oam_tile[n].d0 = memory::vram[addr + 0]; + oam_tile[n].d1 = memory::vram[addr + 1]; + self.add_clocks(2); + + oam_tile[n].d2 = memory::vram[addr + 16]; + oam_tile[n].d3 = memory::vram[addr + 17]; + self.add_clocks(2); + } + } + + if(t.tile_count < 34) self.add_clocks((34 - t.tile_count) * 4); + regs.time_over |= (t.tile_count > 34); + regs.range_over |= (t.item_count > 32); +} + +void PPU::Sprite::reset() { + for(unsigned i = 0; i < 128; i++) { + list[i].x = 0; + list[i].y = 0; + list[i].character = 0; + list[i].nameselect = 0; + list[i].vflip = 0; + list[i].hflip = 0; + list[i].priority = 0; + list[i].palette = 0; + list[i].size = 0; + } + + t.x = 0; + t.y = 0; + + t.item_count = 0; + t.tile_count = 0; + + t.active = 0; + for(unsigned n = 0; n < 2; n++) { + memset(t.item[n], 0, 32); + for(unsigned i = 0; i < 34; i++) { + t.tile[n][i].x = 0; + t.tile[n][i].priority = 0; + t.tile[n][i].palette = 0; + t.tile[n][i].hflip = 0; + t.tile[n][i].d0 = 0; + t.tile[n][i].d1 = 0; + t.tile[n][i].d2 = 0; + t.tile[n][i].d3 = 0; + } + } + + regs.main_enabled = 0; + regs.sub_enabled = 0; + regs.interlace = 0; + + regs.base_size = 0; + regs.nameselect = 0; + regs.tiledata_addr = 0; + regs.first_sprite = 0; + + regs.priority0 = 0; + regs.priority1 = 0; + regs.priority2 = 0; + regs.priority3 = 0; + + regs.time_over = 0; + regs.range_over = 0; + + output.main.palette = 0; + output.main.priority = 0; + output.sub.palette = 0; + output.sub.priority = 0; +} + +PPU::Sprite::Sprite(PPU &self) : self(self) { +} + +#endif diff --git a/asnes/ppu/sprite/sprite.hpp b/asnes/ppu/sprite/sprite.hpp new file mode 100644 index 00000000..d861e724 --- /dev/null +++ b/asnes/ppu/sprite/sprite.hpp @@ -0,0 +1,81 @@ +class Sprite { +public: + PPU &self; + + struct SpriteItem { + uint16 x; + uint16 y; + uint8 character; + bool nameselect; + bool vflip; + bool hflip; + uint8 priority; + uint8 palette; + bool size; + unsigned width() const; + unsigned height() const; + } list[128]; + + struct TileItem { + uint16 x; + uint16 priority; + uint16 palette; + bool hflip; + uint8 d0, d1, d2, d3; + }; + + struct State { + unsigned x; + unsigned y; + + unsigned item_count; + unsigned tile_count; + + bool active; + uint8 item[2][32]; + TileItem tile[2][34]; + } t; + + struct { + bool main_enabled; + bool sub_enabled; + bool interlace; + + uint8 base_size; + uint8 nameselect; + uint16 tiledata_addr; + uint8 first_sprite; + + unsigned priority0; + unsigned priority1; + unsigned priority2; + unsigned priority3; + + bool time_over; + bool range_over; + } regs; + + struct { + struct { + unsigned priority; //0 = none (transparent) + unsigned palette; + } main, sub; + } output; + + //list.cpp + void update(unsigned addr, uint8 data); + + //sprite.cpp + void address_reset(); + void frame(); + void scanline(); + void run(); + void tilefetch(); + void reset(); + + void serialize(serializer&); + Sprite(PPU &self); + +private: + bool on_scanline(SpriteItem&); +}; diff --git a/asnes/ppu/window/window.cpp b/asnes/ppu/window/window.cpp new file mode 100644 index 00000000..34966e3e --- /dev/null +++ b/asnes/ppu/window/window.cpp @@ -0,0 +1,167 @@ +#ifdef PPU_CPP + +void PPU::Window::scanline() { + t.x = 0; +} + +void PPU::Window::run() { + bool main, sub; + + test( + main, sub, + regs.bg1_one_enable, regs.bg1_one_invert, + regs.bg1_two_enable, regs.bg1_two_invert, + regs.bg1_mask, regs.bg1_main_enable, regs.bg1_sub_enable + ); + if(main) self.bg1.output.main.priority = 0; + if(sub) self.bg1.output.sub.priority = 0; + + test( + main, sub, + regs.bg2_one_enable, regs.bg2_one_invert, + regs.bg2_two_enable, regs.bg2_two_invert, + regs.bg2_mask, regs.bg2_main_enable, regs.bg2_sub_enable + ); + if(main) self.bg2.output.main.priority = 0; + if(sub) self.bg2.output.sub.priority = 0; + + test( + main, sub, + regs.bg3_one_enable, regs.bg3_one_invert, + regs.bg3_two_enable, regs.bg3_two_invert, + regs.bg3_mask, regs.bg3_main_enable, regs.bg3_sub_enable + ); + if(main) self.bg3.output.main.priority = 0; + if(sub) self.bg3.output.sub.priority = 0; + + test( + main, sub, + regs.bg4_one_enable, regs.bg4_one_invert, + regs.bg4_two_enable, regs.bg4_two_invert, + regs.bg4_mask, regs.bg4_main_enable, regs.bg4_sub_enable + ); + if(main) self.bg4.output.main.priority = 0; + if(sub) self.bg4.output.sub.priority = 0; + + test( + main, sub, + regs.oam_one_enable, regs.oam_one_invert, + regs.oam_two_enable, regs.oam_two_invert, + regs.oam_mask, regs.oam_main_enable, regs.oam_sub_enable + ); + if(main) self.oam.output.main.priority = 0; + if(sub) self.oam.output.sub.priority = 0; + + test( + main, sub, + regs.col_one_enable, regs.col_one_invert, + regs.col_two_enable, regs.col_two_invert, + regs.col_mask, true, true + ); + + switch(regs.col_main_mask) { + case 0: main = true; break; + case 1: break; + case 2: main = !main; break; + case 3: main = false; break; + } + + switch(regs.col_sub_mask) { + case 0: sub = true; break; + case 1: break; + case 2: sub = !sub; break; + case 3: sub = false; break; + } + + output.main.color_enable = main; + output.sub.color_enable = sub; + + t.x++; +} + +void PPU::Window::test( + bool &main, bool &sub, + bool one_enable, bool one_invert, + bool two_enable, bool two_invert, + uint8 mask, bool main_enable, bool sub_enable +) { + unsigned x = t.x; + bool output; + + if(one_enable == false && two_enable == false) { + output = false; + } else if(one_enable == true && two_enable == false) { + output = (x >= regs.one_left && x <= regs.one_right) ^ one_invert; + } else if(one_enable == false && two_enable == true) { + output = (x >= regs.two_left && x <= regs.two_right) ^ two_invert; + } else { + bool one = (x >= regs.one_left && x <= regs.one_right) ^ one_invert; + bool two = (x >= regs.two_left && x <= regs.two_right) ^ two_invert; + switch(mask) { + case 0: output = (one | two) == 1; break; + case 1: output = (one & two) == 1; break; + case 2: output = (one ^ two) == 1; break; + case 3: output = (one ^ two) == 0; break; + } + } + + main = main_enable ? output : false; + sub = sub_enable ? output : false; +} + +void PPU::Window::reset() { + t.x = 0; + regs.bg1_one_enable = false; + regs.bg1_one_invert = false; + regs.bg1_two_enable = false; + regs.bg1_two_invert = false; + regs.bg2_one_enable = false; + regs.bg2_one_invert = false; + regs.bg2_two_enable = false; + regs.bg2_two_invert = false; + regs.bg3_one_enable = false; + regs.bg3_one_invert = false; + regs.bg3_two_enable = false; + regs.bg3_two_invert = false; + regs.bg4_one_enable = false; + regs.bg4_one_invert = false; + regs.bg4_two_enable = false; + regs.bg4_two_invert = false; + regs.oam_one_enable = false; + regs.oam_one_invert = false; + regs.oam_two_enable = false; + regs.oam_two_invert = false; + regs.col_one_enable = false; + regs.col_one_invert = false; + regs.col_two_enable = false; + regs.col_two_invert = false; + regs.one_left = 0; + regs.one_right = 0; + regs.two_left = 0; + regs.two_right = 0; + regs.bg1_mask = 0; + regs.bg2_mask = 0; + regs.bg3_mask = 0; + regs.bg4_mask = 0; + regs.oam_mask = 0; + regs.col_mask = 0; + regs.bg1_main_enable = 0; + regs.bg1_sub_enable = 0; + regs.bg2_main_enable = 0; + regs.bg2_sub_enable = 0; + regs.bg3_main_enable = 0; + regs.bg3_sub_enable = 0; + regs.bg4_main_enable = 0; + regs.bg4_sub_enable = 0; + regs.oam_main_enable = 0; + regs.oam_sub_enable = 0; + regs.col_main_mask = 0; + regs.col_sub_mask = 0; + output.main.color_enable = 0; + output.sub.color_enable = 0; +} + +PPU::Window::Window(PPU &self) : self(self) { +} + +#endif diff --git a/asnes/ppu/window/window.hpp b/asnes/ppu/window/window.hpp new file mode 100644 index 00000000..8092da21 --- /dev/null +++ b/asnes/ppu/window/window.hpp @@ -0,0 +1,87 @@ +class Window { +public: + PPU &self; + + struct { + unsigned x; + } t; + + struct { + bool bg1_one_enable; + bool bg1_one_invert; + bool bg1_two_enable; + bool bg1_two_invert; + + bool bg2_one_enable; + bool bg2_one_invert; + bool bg2_two_enable; + bool bg2_two_invert; + + bool bg3_one_enable; + bool bg3_one_invert; + bool bg3_two_enable; + bool bg3_two_invert; + + bool bg4_one_enable; + bool bg4_one_invert; + bool bg4_two_enable; + bool bg4_two_invert; + + bool oam_one_enable; + bool oam_one_invert; + bool oam_two_enable; + bool oam_two_invert; + + bool col_one_enable; + bool col_one_invert; + bool col_two_enable; + bool col_two_invert; + + uint8 one_left; + uint8 one_right; + uint8 two_left; + uint8 two_right; + + uint8 bg1_mask; + uint8 bg2_mask; + uint8 bg3_mask; + uint8 bg4_mask; + uint8 oam_mask; + uint8 col_mask; + + bool bg1_main_enable; + bool bg1_sub_enable; + bool bg2_main_enable; + bool bg2_sub_enable; + bool bg3_main_enable; + bool bg3_sub_enable; + bool bg4_main_enable; + bool bg4_sub_enable; + bool oam_main_enable; + bool oam_sub_enable; + + uint8 col_main_mask; + uint8 col_sub_mask; + } regs; + + struct { + struct { + bool color_enable; + } main, sub; + } output; + + void scanline(); + void run(); + void reset(); + + void serialize(serializer&); + Window(PPU &self); + +private: + void test( + bool &main, bool &sub, + bool one_enable, bool one_invert, + bool two_enable, bool two_invert, + uint8 mask, bool main_enable, bool sub_enable + ); +}; diff --git a/src/snes/scheduler/scheduler.cpp b/asnes/scheduler/scheduler.cpp similarity index 100% rename from src/snes/scheduler/scheduler.cpp rename to asnes/scheduler/scheduler.cpp diff --git a/src/snes/scheduler/scheduler.hpp b/asnes/scheduler/scheduler.hpp similarity index 100% rename from src/snes/scheduler/scheduler.hpp rename to asnes/scheduler/scheduler.hpp diff --git a/src/snes/smp/core/algorithms.cpp b/asnes/smp/core/algorithms.cpp similarity index 100% rename from src/snes/smp/core/algorithms.cpp rename to asnes/smp/core/algorithms.cpp diff --git a/src/snes/smp/core/core.cpp b/asnes/smp/core/core.cpp similarity index 94% rename from src/snes/smp/core/core.cpp rename to asnes/smp/core/core.cpp index 51fa453e..88243dad 100644 --- a/src/snes/smp/core/core.cpp +++ b/asnes/smp/core/core.cpp @@ -1,4 +1,4 @@ -#include +#include #define SMPCORE_CPP namespace SNES { diff --git a/src/snes/smp/core/core.hpp b/asnes/smp/core/core.hpp similarity index 100% rename from src/snes/smp/core/core.hpp rename to asnes/smp/core/core.hpp diff --git a/src/snes/smp/core/disassembler/disassembler.cpp b/asnes/smp/core/disassembler/disassembler.cpp similarity index 100% rename from src/snes/smp/core/disassembler/disassembler.cpp rename to asnes/smp/core/disassembler/disassembler.cpp diff --git a/src/snes/smp/core/disassembler/disassembler.hpp b/asnes/smp/core/disassembler/disassembler.hpp similarity index 100% rename from src/snes/smp/core/disassembler/disassembler.hpp rename to asnes/smp/core/disassembler/disassembler.hpp diff --git a/src/snes/smp/core/memory.hpp b/asnes/smp/core/memory.hpp similarity index 100% rename from src/snes/smp/core/memory.hpp rename to asnes/smp/core/memory.hpp diff --git a/src/snes/smp/core/opcode_misc.cpp b/asnes/smp/core/opcode_misc.cpp similarity index 100% rename from src/snes/smp/core/opcode_misc.cpp rename to asnes/smp/core/opcode_misc.cpp diff --git a/src/snes/smp/core/opcode_mov.cpp b/asnes/smp/core/opcode_mov.cpp similarity index 100% rename from src/snes/smp/core/opcode_mov.cpp rename to asnes/smp/core/opcode_mov.cpp diff --git a/src/snes/smp/core/opcode_pc.cpp b/asnes/smp/core/opcode_pc.cpp similarity index 100% rename from src/snes/smp/core/opcode_pc.cpp rename to asnes/smp/core/opcode_pc.cpp diff --git a/src/snes/smp/core/opcode_read.cpp b/asnes/smp/core/opcode_read.cpp similarity index 100% rename from src/snes/smp/core/opcode_read.cpp rename to asnes/smp/core/opcode_read.cpp diff --git a/src/snes/smp/core/opcode_rmw.cpp b/asnes/smp/core/opcode_rmw.cpp similarity index 100% rename from src/snes/smp/core/opcode_rmw.cpp rename to asnes/smp/core/opcode_rmw.cpp diff --git a/src/snes/smp/core/registers.hpp b/asnes/smp/core/registers.hpp similarity index 100% rename from src/snes/smp/core/registers.hpp rename to asnes/smp/core/registers.hpp diff --git a/src/snes/smp/core/serialization.cpp b/asnes/smp/core/serialization.cpp similarity index 100% rename from src/snes/smp/core/serialization.cpp rename to asnes/smp/core/serialization.cpp diff --git a/src/snes/smp/core/table.cpp b/asnes/smp/core/table.cpp similarity index 100% rename from src/snes/smp/core/table.cpp rename to asnes/smp/core/table.cpp diff --git a/asnes/smp/debugger/debugger.cpp b/asnes/smp/debugger/debugger.cpp new file mode 100644 index 00000000..8ca6695a --- /dev/null +++ b/asnes/smp/debugger/debugger.cpp @@ -0,0 +1,61 @@ +#ifdef SMP_CPP + +void sSMPDebugger::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(); + sSMP::op_step(); + synchronize_cpu(); +} + +uint8 sSMPDebugger::op_read(uint16 addr) { + uint8 data = sSMP::op_read(addr); + usage[addr] |= UsageRead; + debugger.breakpoint_test(Debugger::Breakpoint::Source::APURAM, Debugger::Breakpoint::Mode::Read, addr, data); + return data; +} + +void sSMPDebugger::op_write(uint16 addr, uint8 data) { + sSMP::op_write(addr, data); + usage[addr] |= UsageWrite; + usage[addr] &= ~UsageExec; + debugger.breakpoint_test(Debugger::Breakpoint::Source::APURAM, Debugger::Breakpoint::Mode::Write, addr, data); +} + +sSMPDebugger::sSMPDebugger() { + usage = new uint8[1 << 16](); + opcode_pc = 0xffc0; +} + +sSMPDebugger::~sSMPDebugger() { + delete[] usage; +} + +//=========== +//SMPDebugger +//=========== + +//$00f0 +unsigned sSMPDebugger::clock_speed() { return status.clock_speed; } +bool sSMPDebugger::timers_enable() { return status.timers_enabled; } +bool sSMPDebugger::ram_disable() { return status.ram_disabled; } +bool sSMPDebugger::ram_writable() { return status.ram_writable; } +bool sSMPDebugger::timers_disable() { return status.timers_disabled; } + +//$00f1 +bool sSMPDebugger::iplrom_enable() { return status.iplrom_enabled; } + +//$00f2 +unsigned sSMPDebugger::dsp_address() { return status.dsp_addr; } + +#endif diff --git a/src/snes/smp/ssmp/debugger/debugger.hpp b/asnes/smp/debugger/debugger.hpp similarity index 100% rename from src/snes/smp/ssmp/debugger/debugger.hpp rename to asnes/smp/debugger/debugger.hpp diff --git a/src/snes/smp/smp-debugger.cpp b/asnes/smp/debugger/smp-debugger.cpp similarity index 100% rename from src/snes/smp/smp-debugger.cpp rename to asnes/smp/debugger/smp-debugger.cpp diff --git a/src/snes/smp/smp-debugger.hpp b/asnes/smp/debugger/smp-debugger.hpp similarity index 100% rename from src/snes/smp/smp-debugger.hpp rename to asnes/smp/debugger/smp-debugger.hpp diff --git a/asnes/smp/iplrom.cpp b/asnes/smp/iplrom.cpp new file mode 100644 index 00000000..a2ade89d --- /dev/null +++ b/asnes/smp/iplrom.cpp @@ -0,0 +1,44 @@ +#ifdef SMP_CPP + +//this is the IPLROM for the S-SMP coprocessor. +//the S-SMP does not allow writing to the IPLROM. +//all writes are instead mapped to the extended +//RAM region, accessible when $f1.d7 is clear. + +const uint8 SMP::iplrom[64] = { +/*ffc0*/ 0xcd, 0xef, //mov x,#$ef +/*ffc2*/ 0xbd, //mov sp,x +/*ffc3*/ 0xe8, 0x00, //mov a,#$00 +/*ffc5*/ 0xc6, //mov (x),a +/*ffc6*/ 0x1d, //dec x +/*ffc7*/ 0xd0, 0xfc, //bne $ffc5 +/*ffc9*/ 0x8f, 0xaa, 0xf4, //mov $f4,#$aa +/*ffcc*/ 0x8f, 0xbb, 0xf5, //mov $f5,#$bb +/*ffcf*/ 0x78, 0xcc, 0xf4, //cmp $f4,#$cc +/*ffd2*/ 0xd0, 0xfb, //bne $ffcf +/*ffd4*/ 0x2f, 0x19, //bra $ffef +/*ffd6*/ 0xeb, 0xf4, //mov y,$f4 +/*ffd8*/ 0xd0, 0xfc, //bne $ffd6 +/*ffda*/ 0x7e, 0xf4, //cmp y,$f4 +/*ffdc*/ 0xd0, 0x0b, //bne $ffe9 +/*ffde*/ 0xe4, 0xf5, //mov a,$f5 +/*ffe0*/ 0xcb, 0xf4, //mov $f4,y +/*ffe2*/ 0xd7, 0x00, //mov ($00)+y,a +/*ffe4*/ 0xfc, //inc y +/*ffe5*/ 0xd0, 0xf3, //bne $ffda +/*ffe7*/ 0xab, 0x01, //inc $01 +/*ffe9*/ 0x10, 0xef, //bpl $ffda +/*ffeb*/ 0x7e, 0xf4, //cmp y,$f4 +/*ffed*/ 0x10, 0xeb, //bpl $ffda +/*ffef*/ 0xba, 0xf6, //movw ya,$f6 +/*fff1*/ 0xda, 0x00, //movw $00,ya +/*fff3*/ 0xba, 0xf4, //movw ya,$f4 +/*fff5*/ 0xc4, 0xf4, //mov $f4,a +/*fff7*/ 0xdd, //mov a,y +/*fff8*/ 0x5d, //mov x,a +/*fff9*/ 0xd0, 0xdb, //bne $ffd6 +/*fffb*/ 0x1f, 0x00, 0x00, //jmp ($0000+x) +/*fffe*/ 0xc0, 0xff //reset vector location ($ffc0) +}; + +#endif diff --git a/asnes/smp/memory/memory.cpp b/asnes/smp/memory/memory.cpp new file mode 100644 index 00000000..2f5081ce --- /dev/null +++ b/asnes/smp/memory/memory.cpp @@ -0,0 +1,213 @@ +#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/src/snes/smp/ssmp/memory/memory.hpp b/asnes/smp/memory/memory.hpp similarity index 100% rename from src/snes/smp/ssmp/memory/memory.hpp rename to asnes/smp/memory/memory.hpp diff --git a/asnes/smp/serialization.cpp b/asnes/smp/serialization.cpp new file mode 100644 index 00000000..7a6e6ac1 --- /dev/null +++ b/asnes/smp/serialization.cpp @@ -0,0 +1,52 @@ +#ifdef SMP_CPP + +void SMP::serialize(serializer &s) { + Processor::serialize(s); + SMPcore::core_serialize(s); + + s.integer(status.opcode); + + 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/asnes/smp/smp.cpp b/asnes/smp/smp.cpp new file mode 100644 index 00000000..5027dea0 --- /dev/null +++ b/asnes/smp/smp.cpp @@ -0,0 +1,123 @@ +#include + +#define SMP_CPP +namespace SNES { + +#if defined(DEBUGGER) + #include "debugger/debugger.cpp" + SMPDebugger smp; +#else + SMP smp; +#endif + +#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(); } + +void SMP::enter() { + while(true) { + if(scheduler.sync == Scheduler::SynchronizeMode::All) { + scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); + } + + op_step(); + } +} + +void SMP::op_step() { + (this->*opcode_table[op_readpc()])(); +} + +void SMP::power() { + //targets not initialized/changed upon reset + t0.target = 0; + t1.target = 0; + t2.target = 0; + + reset(); +} + +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; +} + +SMP::SMP() { +} + +SMP::~SMP() { +} + +} diff --git a/asnes/smp/smp.hpp b/asnes/smp/smp.hpp new file mode 100644 index 00000000..034d9819 --- /dev/null +++ b/asnes/smp/smp.hpp @@ -0,0 +1,59 @@ +class SMP : public Processor, public SMPcore { +public: + static const uint8 iplrom[64]; + + //synchronization + alwaysinline void step(unsigned clocks); + alwaysinline void synchronize_cpu(); + alwaysinline void synchronize_dsp(); + + static void Enter(); + void enter(); + debugvirtual void op_step(); + + #include "memory/memory.hpp" + #include "timing/timing.hpp" + + struct { + uint8 opcode; + + //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; + + //ssmp.cpp + void power(); + void reset(); + + void serialize(serializer&); + SMP(); + ~SMP(); + + friend class SMPDebugger; +}; + +#if defined(DEBUGGER) + #include "debugger/debugger.hpp" + extern SMPDebugger smp; +#else + extern SMP smp; +#endif diff --git a/asnes/smp/timing/timing.cpp b/asnes/smp/timing/timing.cpp new file mode 100644 index 00000000..24cff287 --- /dev/null +++ b/asnes/smp/timing/timing.cpp @@ -0,0 +1,60 @@ +#ifdef SMP_CPP + +void SMP::add_clocks(unsigned clocks) { + step(clocks); + synchronize_dsp(); + + //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/src/snes/smp/ssmp/timing/timing.hpp b/asnes/smp/timing/timing.hpp similarity index 100% rename from src/snes/smp/ssmp/timing/timing.hpp rename to asnes/smp/timing/timing.hpp diff --git a/asnes/snes.hpp b/asnes/snes.hpp new file mode 100644 index 00000000..98cbcf43 --- /dev/null +++ b/asnes/snes.hpp @@ -0,0 +1,94 @@ +namespace SNES { + namespace Info { + static const char Name[] = "asnes"; + static const char Version[] = "000"; + static const unsigned SerializerVersion = 12; + } +} + +//#define DEBUGGER + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace nall; + +#ifdef DEBUGGER + #define debugvirtual virtual +#else + #define debugvirtual +#endif + +namespace SNES { + typedef int8_t int8; + typedef int16_t int16; + typedef int32_t int32; + typedef int64_t int64; + typedef uint8_t uint8; + typedef uint16_t uint16; + typedef uint32_t uint32; + typedef uint64_t uint64; + + struct Processor { + cothread_t thread; + unsigned frequency; + int64 clock; + + inline void create(void (*entrypoint_)(), unsigned frequency_) { + if(thread) co_delete(thread); + thread = co_create(65536 * sizeof(void*), entrypoint_); + frequency = frequency_; + clock = 0; + } + + inline void serialize(serializer &s) { + s.integer(frequency); + s.integer(clock); + } + + inline Processor() : thread(0) {} + }; + + struct ChipDebugger { + virtual bool property(unsigned id, string &name, string &value) = 0; + }; + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include +} + +namespace nall { + template<> struct has_size { enum { value = true }; }; + template<> struct has_size { enum { value = true }; }; +} + +#undef debugvirtual diff --git a/src/snes/system/serialization.cpp b/asnes/system/serialization.cpp similarity index 93% rename from src/snes/system/serialization.cpp rename to asnes/system/serialization.cpp index 7d1a8af0..da72b4bb 100644 --- a/src/snes/system/serialization.cpp +++ b/asnes/system/serialization.cpp @@ -3,7 +3,7 @@ serializer System::serialize() { serializer s(serialize_size); - unsigned signature = 0x31545342, version = bsnesSerializerVersion, crc32 = cartridge.crc32(); + unsigned signature = 0x31545342, version = Info::SerializerVersion, crc32 = cartridge.crc32(); char description[512]; memset(&description, 0, sizeof description); @@ -26,7 +26,7 @@ bool System::unserialize(serializer &s) { s.array(description); if(signature != 0x31545342) return false; - if(version != bsnesSerializerVersion) return false; + if(version != Info::SerializerVersion) return false; //if(crc32 != cartridge.crc32()) return false; reset(); diff --git a/src/snes/system/system.cpp b/asnes/system/system.cpp similarity index 96% rename from src/snes/system/system.cpp rename to asnes/system/system.cpp index c9b65e5f..a2c8efc8 100644 --- a/src/snes/system/system.cpp +++ b/asnes/system/system.cpp @@ -1,17 +1,17 @@ -#include +#include #define SYSTEM_CPP namespace SNES { System system; -#include -#include -#include +#include +#include +#include -#include -#include -#include +#include