mirror of
https://github.com/ares-emulator/ares.git
synced 2026-11-08 01:08:24 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ebdb69516 | ||
|
|
add0f74387 | ||
|
|
b20f70f333 | ||
|
|
9aebf7bc6b | ||
|
|
a7bf219d5d | ||
|
|
04118be59a | ||
|
|
f24d17859f | ||
|
|
35fd80bde7 | ||
|
|
ccf1c00b58 | ||
|
|
f4520d41ec | ||
|
|
e308cf4275 | ||
|
|
192e53bb87 | ||
|
|
0ed9edfcdb | ||
|
|
764fe1974a | ||
|
|
a55d640459 | ||
|
|
6010bffe5d | ||
|
|
e492268025 | ||
|
|
a36c26c997 | ||
|
|
a3945e5772 | ||
|
|
6b6233b3af | ||
|
|
9f63cb1b99 | ||
|
|
49c39e0e4d | ||
|
|
7dec0b2a3c | ||
|
|
f288280ceb | ||
|
|
c6c5f4669c | ||
|
|
397b9c4505 | ||
|
|
7e2cfb6d40 | ||
|
|
970dcea0ac | ||
|
|
402c146a53 | ||
|
|
a471c150c9 | ||
|
|
ea38ea2537 | ||
|
|
09b326ae86 | ||
|
|
a60f667b25 | ||
|
|
6f7cf1bd31 | ||
|
|
8751bc433f | ||
|
|
afbee40bc0 | ||
|
|
81e9be64d2 | ||
|
|
0cd4972d11 | ||
|
|
938609818f | ||
|
|
487491e6f2 | ||
|
|
a6ccc7e5d2 | ||
|
|
39dd39cc09 | ||
|
|
1a017ff697 | ||
|
|
591adfcb25 |
No files matched your search
@@ -1,2 +0,0 @@
|
||||
higan/profile/WonderSwan.sys/internal.ram
|
||||
higan/profile/WonderSwan Color.sys/internal.ram
|
||||
@@ -1,65 +0,0 @@
|
||||
include ../nall/GNUmakefile
|
||||
|
||||
target := tomoko
|
||||
# console := true
|
||||
|
||||
flags += -I. -I.. -O3
|
||||
objects := libco audio video resource
|
||||
|
||||
# profile-guided optimization mode
|
||||
# pgo := instrument
|
||||
# pgo := optimize
|
||||
|
||||
ifeq ($(pgo),instrument)
|
||||
flags += -fprofile-generate
|
||||
link += -lgcov
|
||||
else ifeq ($(pgo),optimize)
|
||||
flags += -fprofile-use
|
||||
endif
|
||||
|
||||
# platform
|
||||
ifeq ($(platform),windows)
|
||||
ifeq ($(console),true)
|
||||
link += -mconsole
|
||||
else
|
||||
link += -mwindows
|
||||
endif
|
||||
link += -mthreads -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
link += -Wl,-enable-auto-import
|
||||
link += -Wl,-enable-runtime-pseudo-reloc
|
||||
else ifeq ($(platform),macosx)
|
||||
flags += -march=native
|
||||
else ifneq ($(filter $(platform),linux bsd),)
|
||||
flags += -march=native -fopenmp
|
||||
link += -fopenmp
|
||||
link += -Wl,-export-dynamic
|
||||
link += -lX11 -lXext
|
||||
else
|
||||
$(error "unsupported platform")
|
||||
endif
|
||||
|
||||
compile = \
|
||||
$(strip \
|
||||
$(if $(filter %.c,$<), \
|
||||
$(compiler) $(cflags) $(flags) $1 -c $< -o $@, \
|
||||
$(if $(filter %.cpp,$<), \
|
||||
$(compiler) $(cppflags) $(flags) $1 -c $< -o $@ \
|
||||
) \
|
||||
) \
|
||||
)
|
||||
|
||||
%.o: $<; $(call compile)
|
||||
|
||||
all: build;
|
||||
|
||||
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco/)
|
||||
obj/audio.o: audio/audio.cpp $(call rwildcard,audio/)
|
||||
obj/video.o: video/video.cpp $(call rwildcard,video/)
|
||||
obj/resource.o: resource/resource.cpp $(call rwildcard,resource/)
|
||||
|
||||
ui := target-$(target)
|
||||
include $(ui)/GNUmakefile
|
||||
|
||||
clean:
|
||||
-@$(call delete,out/*)
|
||||
-@$(call delete,obj/*)
|
||||
@@ -1,88 +0,0 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
#include "stream.cpp"
|
||||
Audio audio;
|
||||
|
||||
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
|
||||
if(channels_) channels = channels_();
|
||||
if(frequency_) frequency = frequency_();
|
||||
|
||||
streams.reset();
|
||||
reverb.reset();
|
||||
|
||||
reverb.resize(channels);
|
||||
for(auto c : range(channels)) {
|
||||
reverb[c].resize(7);
|
||||
reverb[c][0].resize(1229);
|
||||
reverb[c][1].resize(1559);
|
||||
reverb[c][2].resize(1907);
|
||||
reverb[c][3].resize(4057);
|
||||
reverb[c][4].resize(8117);
|
||||
reverb[c][5].resize(8311);
|
||||
reverb[c][6].resize(9931);
|
||||
}
|
||||
}
|
||||
|
||||
auto Audio::setInterface(Interface* interface) -> void {
|
||||
this->interface = interface;
|
||||
}
|
||||
|
||||
auto Audio::setVolume(double volume) -> void {
|
||||
this->volume = volume;
|
||||
}
|
||||
|
||||
auto Audio::setBalance(double balance) -> void {
|
||||
this->balance = balance;
|
||||
}
|
||||
|
||||
auto Audio::setReverb(bool enabled) -> void {
|
||||
this->reverbEnable = enabled;
|
||||
}
|
||||
|
||||
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
|
||||
shared_pointer<Stream> stream = new Stream;
|
||||
stream->reset(channels, frequency, this->frequency);
|
||||
streams.append(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
auto Audio::process() -> void {
|
||||
while(true) {
|
||||
for(auto& stream : streams) {
|
||||
if(!stream->pending()) return;
|
||||
}
|
||||
|
||||
double samples[channels] = {0.0};
|
||||
for(auto& stream : streams) {
|
||||
double buffer[16];
|
||||
uint length = stream->read(buffer), offset = 0;
|
||||
|
||||
for(auto c : range(channels)) {
|
||||
samples[c] += buffer[offset];
|
||||
if(++offset >= length) offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto c : range(channels)) {
|
||||
samples[c] = max(-1.0, min(+1.0, samples[c] * volume));
|
||||
|
||||
if(reverbEnable) {
|
||||
samples[c] *= 0.125;
|
||||
for(auto n : range(7)) samples[c] += 0.125 * reverb[c][n].last();
|
||||
for(auto n : range(7)) reverb[c][n].write(samples[c]);
|
||||
samples[c] *= 8.000;
|
||||
}
|
||||
}
|
||||
|
||||
if(channels == 2) {
|
||||
if(balance < 0.0) samples[1] *= 1.0 + balance;
|
||||
if(balance > 0.0) samples[0] *= 1.0 - balance;
|
||||
}
|
||||
|
||||
interface->audioSample(samples, channels);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/dsp/iir/biquad.hpp>
|
||||
#include <nall/dsp/resampler/cubic.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Interface;
|
||||
struct Audio;
|
||||
struct Stream;
|
||||
|
||||
struct Audio {
|
||||
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
|
||||
auto setInterface(Interface*) -> void;
|
||||
|
||||
auto setVolume(double volume) -> void;
|
||||
auto setBalance(double balance) -> void;
|
||||
auto setReverb(bool enabled) -> void;
|
||||
|
||||
auto createStream(uint channels, double frequency) -> shared_pointer<Stream>;
|
||||
|
||||
private:
|
||||
auto process() -> void;
|
||||
|
||||
Interface* interface = nullptr;
|
||||
vector<shared_pointer<Stream>> streams;
|
||||
|
||||
uint channels = 0;
|
||||
double frequency = 0.0;
|
||||
|
||||
double volume = 1.0;
|
||||
double balance = 0.0;
|
||||
|
||||
bool reverbEnable = false;
|
||||
vector<vector<queue<double>>> reverb;
|
||||
|
||||
friend class Stream;
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
|
||||
|
||||
auto pending() const -> bool;
|
||||
auto read(double* samples) -> uint;
|
||||
auto write(const double* samples) -> void;
|
||||
|
||||
template<typename... P> auto sample(P&&... p) -> void {
|
||||
double samples[sizeof...(P)] = {forward<P>(p)...};
|
||||
write(samples);
|
||||
}
|
||||
|
||||
private:
|
||||
const uint order = 6; //Nth-order filter (must be an even number)
|
||||
struct Channel {
|
||||
vector<DSP::IIR::Biquad> iir;
|
||||
DSP::Resampler::Cubic resampler;
|
||||
};
|
||||
vector<Channel> channels;
|
||||
|
||||
friend class Audio;
|
||||
};
|
||||
|
||||
extern Audio audio;
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void {
|
||||
channels.reset();
|
||||
channels.resize(channels_);
|
||||
|
||||
for(auto& channel : channels) {
|
||||
if(outputFrequency / inputFrequency <= 0.5) {
|
||||
channel.iir.resize(order / 2);
|
||||
for(auto phase : range(order / 2)) {
|
||||
double q = DSP::IIR::Biquad::butterworth(order, phase);
|
||||
channel.iir[phase].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0 / inputFrequency, q);
|
||||
}
|
||||
}
|
||||
|
||||
channel.resampler.reset(inputFrequency, outputFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::pending() const -> bool {
|
||||
return channels && channels[0].resampler.pending();
|
||||
}
|
||||
|
||||
auto Stream::read(double* samples) -> uint {
|
||||
for(auto c : range(channels)) samples[c] = channels[c].resampler.read();
|
||||
return channels.size();
|
||||
}
|
||||
|
||||
auto Stream::write(const double* samples) -> void {
|
||||
for(auto c : range(channels)) {
|
||||
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
|
||||
for(auto& iir : channels[c].iir) sample = iir.process(sample);
|
||||
channels[c].resampler.write(sample);
|
||||
}
|
||||
|
||||
audio.process();
|
||||
}
|
||||
-87497
File diff suppressed because it is too large.
Load diff
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="higan" version="1.0.0.0" processorArchitecture="*"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
@@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=higan
|
||||
Comment=Emulator
|
||||
Exec=higan
|
||||
Icon=higan
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Game;Emulator;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.byuu.higan</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>higan</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>higan</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>higan.icns</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<false/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,2 +0,0 @@
|
||||
1 24 "higan.Manifest"
|
||||
2 ICON DISCARDABLE "higan.ico"
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#define debug(id, ...) if(debugger.id) debugger.id(__VA_ARGS__)
|
||||
#define privileged public
|
||||
#else
|
||||
#define debug(id, ...)
|
||||
#define privileged private
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/vfs.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <libco/libco.h>
|
||||
#include <audio/audio.hpp>
|
||||
#include <video/video.hpp>
|
||||
#include <resource/resource.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "100.01";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
||||
//incremented only when serialization format changes
|
||||
static const string SerializerVersion = "100";
|
||||
}
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "debugger.hpp"
|
||||
@@ -1,121 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Interface {
|
||||
struct Information {
|
||||
string manufacturer;
|
||||
string name;
|
||||
uint width;
|
||||
uint height;
|
||||
bool overscan;
|
||||
double aspectRatio;
|
||||
bool resettable;
|
||||
struct Capability {
|
||||
bool states;
|
||||
bool cheats;
|
||||
} capability;
|
||||
} information;
|
||||
|
||||
struct Medium {
|
||||
uint id;
|
||||
string name;
|
||||
string type; //extension
|
||||
};
|
||||
vector<Medium> media;
|
||||
|
||||
struct Device {
|
||||
uint id;
|
||||
string name;
|
||||
struct Input {
|
||||
uint type; //0 = digital, 1 = analog (relative), 2 = rumble
|
||||
string name;
|
||||
};
|
||||
vector<Input> inputs;
|
||||
};
|
||||
|
||||
struct Port {
|
||||
uint id;
|
||||
string name;
|
||||
vector<Device> devices;
|
||||
};
|
||||
vector<Port> ports;
|
||||
|
||||
struct Bind {
|
||||
virtual auto path(uint) -> string { return ""; }
|
||||
virtual auto open(uint, string, vfs::file::mode, bool) -> vfs::shared::file { return {}; }
|
||||
virtual auto load(uint, string, string) -> maybe<uint> { return nothing; }
|
||||
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
|
||||
virtual auto audioSample(const double*, uint) -> void {}
|
||||
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
|
||||
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
|
||||
virtual auto dipSettings(Markup::Node) -> uint { return 0; }
|
||||
virtual auto notify(string text) -> void { print(text, "\n"); }
|
||||
};
|
||||
Bind* bind = nullptr;
|
||||
|
||||
//callback bindings (provided by user interface)
|
||||
auto path(uint id) -> string { return bind->path(id); }
|
||||
auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return bind->open(id, name, mode, required); }
|
||||
auto load(uint id, string name, string type) -> maybe<uint> { return bind->load(id, name, type); }
|
||||
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
|
||||
auto audioSample(const double* samples, uint channels) -> void { return bind->audioSample(samples, channels); }
|
||||
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
|
||||
auto inputRumble(uint port, uint device, uint input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); }
|
||||
auto dipSettings(Markup::Node node) -> uint { return bind->dipSettings(node); }
|
||||
template<typename... P> auto notify(P&&... p) -> void { return bind->notify({forward<P>(p)...}); }
|
||||
|
||||
//information
|
||||
virtual auto manifest() -> string = 0;
|
||||
virtual auto title() -> string = 0;
|
||||
|
||||
//video information
|
||||
virtual auto videoFrequency() -> double = 0;
|
||||
virtual auto videoColors() -> uint32 = 0;
|
||||
virtual auto videoColor(uint32 color) -> uint64 = 0;
|
||||
|
||||
//audio information
|
||||
virtual auto audioFrequency() -> double = 0;
|
||||
|
||||
//media interface
|
||||
virtual auto loaded() -> bool { return false; }
|
||||
virtual auto sha256() -> string { return ""; }
|
||||
virtual auto load(uint id) -> bool { return false; }
|
||||
virtual auto save() -> void {}
|
||||
virtual auto unload() -> void {}
|
||||
|
||||
//system interface
|
||||
virtual auto connect(uint port, uint device) -> void {}
|
||||
virtual auto power() -> void {}
|
||||
virtual auto reset() -> void {}
|
||||
virtual auto run() -> void {}
|
||||
|
||||
//time functions
|
||||
virtual auto rtc() -> bool { return false; }
|
||||
virtual auto rtcsync() -> void {}
|
||||
|
||||
//state functions
|
||||
virtual auto serialize() -> serializer = 0;
|
||||
virtual auto unserialize(serializer&) -> bool = 0;
|
||||
|
||||
//cheat functions
|
||||
virtual auto cheatSet(const string_vector& = {}) -> void {}
|
||||
|
||||
//settings
|
||||
virtual auto cap(const string& name) -> bool { return false; }
|
||||
virtual auto get(const string& name) -> any { return {}; }
|
||||
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
||||
|
||||
//shared functions
|
||||
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
|
||||
};
|
||||
|
||||
//nall/vfs shorthand constants for open(), load()
|
||||
struct File {
|
||||
static const auto Read = vfs::file::mode::read;
|
||||
static const auto Write = vfs::file::mode::write;
|
||||
static const auto Optional = false;
|
||||
static const auto Required = true;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
processors += r6502
|
||||
|
||||
objects += fc-interface fc-system fc-scheduler fc-controller
|
||||
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
|
||||
objects += fc-cheat
|
||||
|
||||
obj/fc-interface.o: fc/interface/interface.cpp $(call rwildcard,fc/interface/)
|
||||
obj/fc-system.o: fc/system/system.cpp $(call rwildcard,fc/system/)
|
||||
obj/fc-scheduler.o: fc/scheduler/scheduler.cpp $(call rwildcard,fc/scheduler/)
|
||||
obj/fc-controller.o: fc/controller/controller.cpp $(call rwildcard,fc/controller/)
|
||||
obj/fc-memory.o: fc/memory/memory.cpp $(call rwildcard,fc/memory/)
|
||||
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp $(call rwildcard,fc/cartridge/)
|
||||
obj/fc-cpu.o: fc/cpu/cpu.cpp $(call rwildcard,fc/cpu/)
|
||||
obj/fc-apu.o: fc/apu/apu.cpp $(call rwildcard,fc/apu/)
|
||||
obj/fc-ppu.o: fc/ppu/ppu.cpp $(call rwildcard,fc/ppu/)
|
||||
obj/fc-cheat.o: fc/cheat/cheat.cpp $(call rwildcard,fc/cheat/)
|
||||
@@ -1,346 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "envelope.cpp"
|
||||
#include "sweep.cpp"
|
||||
#include "pulse.cpp"
|
||||
#include "triangle.cpp"
|
||||
#include "noise.cpp"
|
||||
#include "dmc.cpp"
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
APU::APU() {
|
||||
for(uint amp : range(32)) {
|
||||
if(amp == 0) {
|
||||
pulseDAC[amp] = 0;
|
||||
} else {
|
||||
pulseDAC[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
for(uint dmc_amp : range(128)) {
|
||||
for(uint triangle_amp : range(16)) {
|
||||
for(uint noise_amp : range(16)) {
|
||||
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
|
||||
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp] = 0;
|
||||
} else {
|
||||
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp]
|
||||
= 16384.0 * 159.79 / (100.0 + 1.0 / (triangle_amp / 8227.0 + noise_amp / 12241.0 + dmc_amp / 22638.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
uint pulse_output, triangle_output, noise_output, dmc_output;
|
||||
|
||||
pulse_output = pulse[0].clock();
|
||||
pulse_output += pulse[1].clock();
|
||||
triangle_output = triangle.clock();
|
||||
noise_output = noise.clock();
|
||||
dmc_output = dmc.clock();
|
||||
|
||||
clockFrameCounterDivider();
|
||||
|
||||
int output = pulseDAC[pulse_output] + dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
|
||||
|
||||
output = filter.runHipassStrong(output);
|
||||
output += cartridgeSample;
|
||||
output = filter.runHipassWeak(output);
|
||||
//output = filter.runLopass(output);
|
||||
output = sclamp<16>(output);
|
||||
|
||||
stream->sample(output / 32768.0);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto APU::tick() -> void {
|
||||
clock += 12;
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto APU::setIRQ() -> void {
|
||||
cpu.apuLine(frame.irqPending || dmc.irqPending);
|
||||
}
|
||||
|
||||
auto APU::setSample(int16 sample) -> void {
|
||||
cartridgeSample = sample;
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
filter.hipassStrong = 0;
|
||||
filter.hipassWeak = 0;
|
||||
filter.lopass = 0;
|
||||
|
||||
pulse[0].power();
|
||||
pulse[1].power();
|
||||
triangle.power();
|
||||
noise.power();
|
||||
dmc.power();
|
||||
}
|
||||
|
||||
auto APU::reset() -> void {
|
||||
create(APU::Enter, 21'477'272);
|
||||
stream = Emulator::audio.createStream(1, 21'477'272.0 / 12.0);
|
||||
|
||||
pulse[0].reset();
|
||||
pulse[1].reset();
|
||||
triangle.reset();
|
||||
noise.reset();
|
||||
dmc.reset();
|
||||
|
||||
frame.irqPending = 0;
|
||||
|
||||
frame.mode = 0;
|
||||
frame.counter = 0;
|
||||
frame.divider = 1;
|
||||
|
||||
enabledChannels = 0;
|
||||
cartridgeSample = 0;
|
||||
|
||||
setIRQ();
|
||||
}
|
||||
|
||||
auto APU::readIO(uint16 addr) -> uint8 {
|
||||
switch(addr) {
|
||||
|
||||
case 0x4015: {
|
||||
uint8 result = 0x00;
|
||||
result |= pulse[0].lengthCounter ? 0x01 : 0;
|
||||
result |= pulse[1].lengthCounter ? 0x02 : 0;
|
||||
result |= triangle.lengthCounter ? 0x04 : 0;
|
||||
result |= noise.lengthCounter ? 0x08 : 0;
|
||||
result |= dmc.lengthCounter ? 0x10 : 0;
|
||||
result |= frame.irqPending ? 0x40 : 0;
|
||||
result |= dmc.irqPending ? 0x80 : 0;
|
||||
|
||||
frame.irqPending = false;
|
||||
setIRQ();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto APU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
const uint n = (addr >> 2) & 1; //pulse#
|
||||
|
||||
switch(addr) {
|
||||
|
||||
case 0x4000: case 0x4004: {
|
||||
pulse[n].duty = data >> 6;
|
||||
pulse[n].envelope.loopMode = data & 0x20;
|
||||
pulse[n].envelope.useSpeedAsVolume = data & 0x10;
|
||||
pulse[n].envelope.speed = data & 0x0f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4001: case 0x4005: {
|
||||
pulse[n].sweep.enable = data & 0x80;
|
||||
pulse[n].sweep.period = (data & 0x70) >> 4;
|
||||
pulse[n].sweep.decrement = data & 0x08;
|
||||
pulse[n].sweep.shift = data & 0x07;
|
||||
pulse[n].sweep.reload = true;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4002: case 0x4006: {
|
||||
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
|
||||
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x0700) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4003: case 0x4007: {
|
||||
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
|
||||
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x00ff) | (data << 8);
|
||||
|
||||
pulse[n].dutyCounter = 7;
|
||||
pulse[n].envelope.reloadDecay = true;
|
||||
|
||||
if(enabledChannels & (1 << n)) {
|
||||
pulse[n].lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4008: {
|
||||
triangle.haltLengthCounter = data & 0x80;
|
||||
triangle.linearLength = data & 0x7f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400a: {
|
||||
triangle.period = (triangle.period & 0x0700) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400b: {
|
||||
triangle.period = (triangle.period & 0x00ff) | (data << 8);
|
||||
|
||||
triangle.reloadLinear = true;
|
||||
|
||||
if(enabledChannels & (1 << 2)) {
|
||||
triangle.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400c: {
|
||||
noise.envelope.loopMode = data & 0x20;
|
||||
noise.envelope.useSpeedAsVolume = data & 0x10;
|
||||
noise.envelope.speed = data & 0x0f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400e: {
|
||||
noise.shortMode = data & 0x80;
|
||||
noise.period = data & 0x0f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400f: {
|
||||
noise.envelope.reloadDecay = true;
|
||||
|
||||
if(enabledChannels & (1 << 3)) {
|
||||
noise.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4010: {
|
||||
dmc.irqEnable = data & 0x80;
|
||||
dmc.loopMode = data & 0x40;
|
||||
dmc.period = data & 0x0f;
|
||||
|
||||
dmc.irqPending = dmc.irqPending && dmc.irqEnable && !dmc.loopMode;
|
||||
setIRQ();
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4011: {
|
||||
dmc.dacLatch = data & 0x7f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4012: {
|
||||
dmc.addrLatch = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4013: {
|
||||
dmc.lengthLatch = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4015: {
|
||||
if((data & 0x01) == 0) pulse[0].lengthCounter = 0;
|
||||
if((data & 0x02) == 0) pulse[1].lengthCounter = 0;
|
||||
if((data & 0x04) == 0) triangle.lengthCounter = 0;
|
||||
if((data & 0x08) == 0) noise.lengthCounter = 0;
|
||||
|
||||
(data & 0x10) ? dmc.start() : dmc.stop();
|
||||
dmc.irqPending = false;
|
||||
|
||||
setIRQ();
|
||||
enabledChannels = data & 0x1f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4017: {
|
||||
frame.mode = data >> 6;
|
||||
|
||||
frame.counter = 0;
|
||||
if(frame.mode & 2) clockFrameCounter();
|
||||
if(frame.mode & 1) {
|
||||
frame.irqPending = false;
|
||||
setIRQ();
|
||||
}
|
||||
frame.divider = FrameCounter::NtscPeriod;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Filter::runHipassStrong(int sample) -> int {
|
||||
hipassStrong += ((((int64)sample << 16) - (hipassStrong >> 16)) * HiPassStrong) >> 16;
|
||||
return sample - (hipassStrong >> 32);
|
||||
}
|
||||
|
||||
auto APU::Filter::runHipassWeak(int sample) -> int {
|
||||
hipassWeak += ((((int64)sample << 16) - (hipassWeak >> 16)) * HiPassWeak) >> 16;
|
||||
return sample - (hipassWeak >> 32);
|
||||
}
|
||||
|
||||
auto APU::Filter::runLopass(int sample) -> int {
|
||||
lopass += ((((int64)sample << 16) - (lopass >> 16)) * LoPass) >> 16;
|
||||
return (lopass >> 32);
|
||||
}
|
||||
|
||||
auto APU::clockFrameCounter() -> void {
|
||||
frame.counter++;
|
||||
|
||||
if(frame.counter & 1) {
|
||||
pulse[0].clockLength();
|
||||
pulse[0].sweep.clock(0);
|
||||
pulse[1].clockLength();
|
||||
pulse[1].sweep.clock(1);
|
||||
triangle.clockLength();
|
||||
noise.clockLength();
|
||||
}
|
||||
|
||||
pulse[0].envelope.clock();
|
||||
pulse[1].envelope.clock();
|
||||
triangle.clockLinearLength();
|
||||
noise.envelope.clock();
|
||||
|
||||
if(frame.counter == 0) {
|
||||
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
|
||||
if(frame.mode == 0) {
|
||||
frame.irqPending = true;
|
||||
setIRQ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::clockFrameCounterDivider() -> void {
|
||||
frame.divider -= 2;
|
||||
if(frame.divider <= 0) {
|
||||
clockFrameCounter();
|
||||
frame.divider += FrameCounter::NtscPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8 APU::lengthCounterTable[32] = {
|
||||
0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e,
|
||||
0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e,
|
||||
};
|
||||
|
||||
const uint16 APU::noisePeriodTableNTSC[16] = {
|
||||
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
|
||||
};
|
||||
|
||||
const uint16 APU::noisePeriodTablePAL[16] = {
|
||||
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
|
||||
};
|
||||
|
||||
const uint16 APU::dmcPeriodTableNTSC[16] = {
|
||||
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
|
||||
};
|
||||
|
||||
const uint16 APU::dmcPeriodTablePAL[16] = {
|
||||
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
struct APU : Thread {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
APU();
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
auto setIRQ() -> void;
|
||||
auto setSample(int16 sample) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Filter {
|
||||
auto runHipassStrong(int sample) -> int;
|
||||
auto runHipassWeak(int sample) -> int;
|
||||
auto runLopass(int sample) -> int;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
enum : int { HiPassStrong = 225574, HiPassWeak = 57593, LoPass = 86322413 };
|
||||
|
||||
int64 hipassStrong;
|
||||
int64 hipassWeak;
|
||||
int64 lopass;
|
||||
};
|
||||
|
||||
struct Envelope {
|
||||
auto volume() const -> uint;
|
||||
auto clock() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint4 speed;
|
||||
bool useSpeedAsVolume;
|
||||
bool loopMode;
|
||||
|
||||
bool reloadDecay;
|
||||
uint8 decayCounter;
|
||||
uint4 decayVolume;
|
||||
};
|
||||
|
||||
struct Sweep {
|
||||
auto checkPeriod() -> bool;
|
||||
auto clock(uint channel) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint8 shift;
|
||||
bool decrement;
|
||||
uint3 period;
|
||||
uint8 counter;
|
||||
bool enable;
|
||||
bool reload;
|
||||
uint11 pulsePeriod;
|
||||
};
|
||||
|
||||
struct Pulse {
|
||||
auto clockLength() -> void;
|
||||
auto checkPeriod() -> bool;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint lengthCounter;
|
||||
|
||||
Envelope envelope;
|
||||
Sweep sweep;
|
||||
|
||||
uint2 duty;
|
||||
uint3 dutyCounter;
|
||||
|
||||
uint11 period;
|
||||
uint periodCounter;
|
||||
} pulse[2];
|
||||
|
||||
struct Triangle {
|
||||
auto clockLength() -> void;
|
||||
auto clockLinearLength() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint lengthCounter;
|
||||
|
||||
uint8 linearLength;
|
||||
bool haltLengthCounter;
|
||||
|
||||
uint11 period;
|
||||
uint periodCounter;
|
||||
|
||||
uint5 stepCounter;
|
||||
uint8 linearLengthCounter;
|
||||
bool reloadLinear;
|
||||
} triangle;
|
||||
|
||||
struct Noise {
|
||||
auto clockLength() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint lengthCounter;
|
||||
|
||||
Envelope envelope;
|
||||
|
||||
uint4 period;
|
||||
uint periodCounter;
|
||||
|
||||
bool shortMode;
|
||||
uint15 lfsr;
|
||||
} noise;
|
||||
|
||||
struct DMC {
|
||||
auto start() -> void;
|
||||
auto stop() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint lengthCounter;
|
||||
bool irqPending;
|
||||
|
||||
uint4 period;
|
||||
uint periodCounter;
|
||||
|
||||
bool irqEnable;
|
||||
bool loopMode;
|
||||
|
||||
uint8 dacLatch;
|
||||
uint8 addrLatch;
|
||||
uint8 lengthLatch;
|
||||
|
||||
uint15 readAddr;
|
||||
uint dmaDelayCounter;
|
||||
|
||||
uint3 bitCounter;
|
||||
bool dmaBufferValid;
|
||||
uint8 dmaBuffer;
|
||||
|
||||
bool sampleValid;
|
||||
uint8 sample;
|
||||
} dmc;
|
||||
|
||||
struct FrameCounter {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
enum : uint { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
|
||||
|
||||
bool irqPending;
|
||||
|
||||
uint2 mode;
|
||||
uint2 counter;
|
||||
int divider;
|
||||
};
|
||||
|
||||
auto clockFrameCounter() -> void;
|
||||
auto clockFrameCounterDivider() -> void;
|
||||
|
||||
Filter filter;
|
||||
FrameCounter frame;
|
||||
|
||||
uint8 enabledChannels;
|
||||
int16 cartridgeSample;
|
||||
|
||||
int16 pulseDAC[32];
|
||||
int16 dmcTriangleNoiseDAC[128][16][16];
|
||||
|
||||
static const uint8 lengthCounterTable[32];
|
||||
static const uint16 dmcPeriodTableNTSC[16];
|
||||
static const uint16 dmcPeriodTablePAL[16];
|
||||
static const uint16 noisePeriodTableNTSC[16];
|
||||
static const uint16 noisePeriodTablePAL[16];
|
||||
};
|
||||
|
||||
extern APU apu;
|
||||
@@ -1,92 +0,0 @@
|
||||
auto APU::DMC::start() -> void {
|
||||
if(lengthCounter == 0) {
|
||||
readAddr = 0x4000 + (addrLatch << 6);
|
||||
lengthCounter = (lengthLatch << 4) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::DMC::stop() -> void {
|
||||
lengthCounter = 0;
|
||||
dmaDelayCounter = 0;
|
||||
cpu.rdyLine(1);
|
||||
cpu.rdyAddr(false);
|
||||
}
|
||||
|
||||
auto APU::DMC::clock() -> uint8 {
|
||||
uint8 result = dacLatch;
|
||||
|
||||
if(dmaDelayCounter > 0) {
|
||||
dmaDelayCounter--;
|
||||
|
||||
if(dmaDelayCounter == 1) {
|
||||
cpu.rdyAddr(true, 0x8000 | readAddr);
|
||||
} else if(dmaDelayCounter == 0) {
|
||||
cpu.rdyLine(1);
|
||||
cpu.rdyAddr(false);
|
||||
|
||||
dmaBuffer = cpu.mdr();
|
||||
dmaBufferValid = true;
|
||||
lengthCounter--;
|
||||
readAddr++;
|
||||
|
||||
if(lengthCounter == 0) {
|
||||
if(loopMode) {
|
||||
start();
|
||||
} else if(irqEnable) {
|
||||
irqPending = true;
|
||||
apu.setIRQ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
if(sampleValid) {
|
||||
int delta = (((sample >> bitCounter) & 1) << 2) - 2;
|
||||
uint data = dacLatch + delta;
|
||||
if((data & 0x80) == 0) dacLatch = data;
|
||||
}
|
||||
|
||||
if(++bitCounter == 0) {
|
||||
if(dmaBufferValid) {
|
||||
sampleValid = true;
|
||||
sample = dmaBuffer;
|
||||
dmaBufferValid = false;
|
||||
} else {
|
||||
sampleValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
periodCounter = dmcPeriodTableNTSC[period];
|
||||
}
|
||||
|
||||
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
|
||||
cpu.rdyLine(0);
|
||||
dmaDelayCounter = 4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::DMC::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::DMC::reset() -> void {
|
||||
lengthCounter = 0;
|
||||
irqPending = 0;
|
||||
|
||||
period = 0;
|
||||
periodCounter = dmcPeriodTableNTSC[0];
|
||||
irqEnable = 0;
|
||||
loopMode = 0;
|
||||
dacLatch = 0;
|
||||
addrLatch = 0;
|
||||
lengthLatch = 0;
|
||||
readAddr = 0;
|
||||
dmaDelayCounter = 0;
|
||||
bitCounter = 0;
|
||||
dmaBufferValid = 0;
|
||||
dmaBuffer = 0;
|
||||
sampleValid = 0;
|
||||
sample = 0;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
auto APU::Envelope::volume() const -> uint {
|
||||
return useSpeedAsVolume ? speed : decayVolume;
|
||||
}
|
||||
|
||||
auto APU::Envelope::clock() -> void {
|
||||
if(reloadDecay) {
|
||||
reloadDecay = false;
|
||||
decayVolume = 0x0f;
|
||||
decayCounter = speed + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if(--decayCounter == 0) {
|
||||
decayCounter = speed + 1;
|
||||
if(decayVolume || loopMode) decayVolume--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Envelope::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::Envelope::reset() -> void {
|
||||
speed = 0;
|
||||
useSpeedAsVolume = 0;
|
||||
loopMode = 0;
|
||||
reloadDecay = 0;
|
||||
decayCounter = 0;
|
||||
decayVolume = 0;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
auto APU::Noise::clockLength() -> void {
|
||||
if(envelope.loopMode == 0) {
|
||||
if(lengthCounter > 0) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::clock() -> uint8 {
|
||||
if(lengthCounter == 0) return 0;
|
||||
|
||||
uint8 result = (lfsr & 1) ? envelope.volume() : 0;
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
uint feedback;
|
||||
|
||||
if(shortMode) {
|
||||
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1);
|
||||
} else {
|
||||
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1);
|
||||
}
|
||||
|
||||
lfsr = (lfsr >> 1) | (feedback << 14);
|
||||
periodCounter = apu.noisePeriodTableNTSC[period];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Noise::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::Noise::reset() -> void {
|
||||
lengthCounter = 0;
|
||||
|
||||
envelope.speed = 0;
|
||||
envelope.useSpeedAsVolume = 0;
|
||||
envelope.loopMode = 0;
|
||||
envelope.reloadDecay = 0;
|
||||
envelope.decayCounter = 0;
|
||||
envelope.decayVolume = 0;
|
||||
|
||||
period = 0;
|
||||
periodCounter = 1;
|
||||
shortMode = 0;
|
||||
lfsr = 1;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
auto APU::Pulse::clockLength() -> void {
|
||||
if(envelope.loopMode == 0) {
|
||||
if(lengthCounter) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Pulse::clock() -> uint8 {
|
||||
if(!sweep.checkPeriod()) return 0;
|
||||
if(lengthCounter == 0) return 0;
|
||||
|
||||
static const uint dutyTable[] = {1, 2, 4, 6};
|
||||
uint8 result = (dutyCounter < dutyTable[duty]) ? envelope.volume() : 0;
|
||||
if(sweep.pulsePeriod < 0x008) result = 0;
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
periodCounter = (sweep.pulsePeriod + 1) * 2;
|
||||
dutyCounter++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Pulse::power() -> void {
|
||||
envelope.power();
|
||||
sweep.power();
|
||||
}
|
||||
|
||||
auto APU::Pulse::reset() -> void {
|
||||
envelope.reset();
|
||||
sweep.reset();
|
||||
|
||||
lengthCounter = 0;
|
||||
|
||||
duty = 0;
|
||||
dutyCounter = 0;
|
||||
period = 0;
|
||||
periodCounter = 1;
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
auto APU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
filter.serialize(s);
|
||||
|
||||
pulse[0].serialize(s);
|
||||
pulse[1].serialize(s);
|
||||
triangle.serialize(s);
|
||||
dmc.serialize(s);
|
||||
frame.serialize(s);
|
||||
|
||||
s.integer(enabledChannels);
|
||||
s.integer(cartridgeSample);
|
||||
}
|
||||
|
||||
auto APU::Filter::serialize(serializer& s) -> void {
|
||||
s.integer(hipassStrong);
|
||||
s.integer(hipassWeak);
|
||||
s.integer(lopass);
|
||||
}
|
||||
|
||||
auto APU::Envelope::serialize(serializer& s) -> void {
|
||||
s.integer(speed);
|
||||
s.integer(useSpeedAsVolume);
|
||||
s.integer(loopMode);
|
||||
|
||||
s.integer(reloadDecay);
|
||||
s.integer(decayCounter);
|
||||
s.integer(decayVolume);
|
||||
}
|
||||
|
||||
auto APU::Sweep::serialize(serializer& s) -> void {
|
||||
s.integer(shift);
|
||||
s.integer(decrement);
|
||||
s.integer(period);
|
||||
s.integer(counter);
|
||||
s.integer(enable);
|
||||
s.integer(reload);
|
||||
s.integer(pulsePeriod);
|
||||
}
|
||||
|
||||
auto APU::Pulse::serialize(serializer& s) -> void {
|
||||
s.integer(lengthCounter);
|
||||
|
||||
envelope.serialize(s);
|
||||
sweep.serialize(s);
|
||||
|
||||
s.integer(duty);
|
||||
s.integer(dutyCounter);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(periodCounter);
|
||||
}
|
||||
|
||||
auto APU::Triangle::serialize(serializer& s) -> void {
|
||||
s.integer(lengthCounter);
|
||||
|
||||
s.integer(linearLength);
|
||||
s.integer(haltLengthCounter);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(periodCounter);
|
||||
|
||||
s.integer(stepCounter);
|
||||
s.integer(linearLengthCounter);
|
||||
s.integer(reloadLinear);
|
||||
}
|
||||
|
||||
auto APU::Noise::serialize(serializer& s) -> void {
|
||||
s.integer(lengthCounter);
|
||||
|
||||
envelope.serialize(s);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(periodCounter);
|
||||
|
||||
s.integer(shortMode);
|
||||
s.integer(lfsr);
|
||||
}
|
||||
|
||||
auto APU::DMC::serialize(serializer& s) -> void {
|
||||
s.integer(lengthCounter);
|
||||
s.integer(irqPending);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(periodCounter);
|
||||
|
||||
s.integer(irqEnable);
|
||||
s.integer(loopMode);
|
||||
|
||||
s.integer(dacLatch);
|
||||
s.integer(addrLatch);
|
||||
s.integer(lengthLatch);
|
||||
|
||||
s.integer(readAddr);
|
||||
s.integer(dmaDelayCounter);
|
||||
|
||||
s.integer(bitCounter);
|
||||
s.integer(dmaBufferValid);
|
||||
s.integer(dmaBuffer);
|
||||
|
||||
s.integer(sampleValid);
|
||||
s.integer(sample);
|
||||
}
|
||||
|
||||
auto APU::FrameCounter::serialize(serializer& s) -> void {
|
||||
s.integer(irqPending);
|
||||
|
||||
s.integer(mode);
|
||||
s.integer(counter);
|
||||
s.integer(divider);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
auto APU::Sweep::checkPeriod() -> bool {
|
||||
if(pulsePeriod > 0x7ff) return false;
|
||||
|
||||
if(decrement == 0) {
|
||||
if((pulsePeriod + (pulsePeriod >> shift)) & 0x800) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto APU::Sweep::clock(uint channel) -> void {
|
||||
if(--counter == 0) {
|
||||
counter = period + 1;
|
||||
if(enable && shift && pulsePeriod > 8) {
|
||||
int delta = pulsePeriod >> shift;
|
||||
|
||||
if(decrement) {
|
||||
pulsePeriod -= delta;
|
||||
if(channel == 0) pulsePeriod--;
|
||||
} else if((pulsePeriod + delta) < 0x800) {
|
||||
pulsePeriod += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(reload) {
|
||||
reload = false;
|
||||
counter = period + 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Sweep::power() -> void {
|
||||
shift = 0;
|
||||
decrement = 0;
|
||||
period = 0;
|
||||
counter = 1;
|
||||
enable = 0;
|
||||
reload = 0;
|
||||
pulsePeriod = 0;
|
||||
}
|
||||
|
||||
auto APU::Sweep::reset() -> void {
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
auto APU::Triangle::clockLength() -> void {
|
||||
if(haltLengthCounter == 0) {
|
||||
if(lengthCounter > 0) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Triangle::clockLinearLength() -> void {
|
||||
if(reloadLinear) {
|
||||
linearLengthCounter = linearLength;
|
||||
} else if(linearLengthCounter) {
|
||||
linearLengthCounter--;
|
||||
}
|
||||
|
||||
if(haltLengthCounter == 0) reloadLinear = false;
|
||||
}
|
||||
|
||||
auto APU::Triangle::clock() -> uint8 {
|
||||
uint8 result = stepCounter & 0x0f;
|
||||
if((stepCounter & 0x10) == 0) result ^= 0x0f;
|
||||
if(lengthCounter == 0 || linearLengthCounter == 0) return result;
|
||||
|
||||
if(--periodCounter == 0) {
|
||||
stepCounter++;
|
||||
periodCounter = period + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Triangle::power() -> void {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto APU::Triangle::reset() -> void {
|
||||
lengthCounter = 0;
|
||||
|
||||
linearLength = 0;
|
||||
haltLengthCounter = 0;
|
||||
period = 0;
|
||||
periodCounter = 1;
|
||||
stepCounter = 0;
|
||||
linearLengthCounter = 0;
|
||||
reloadLinear = 0;
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
//BANDAI-FCG
|
||||
|
||||
struct BandaiFCG : Board {
|
||||
BandaiFCG(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqCounterEnable) {
|
||||
if(--irqCounter == 0xffff) {
|
||||
cpu.irqLine(1);
|
||||
irqCounterEnable = false;
|
||||
}
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
case 2: return 0x0000 | (addr & 0x03ff);
|
||||
case 3: return 0x0400 | (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (region == 0 ? prgBank : (uint8)0x0f);
|
||||
return prgrom.read((bank << 14) | (addr & 0x3fff));
|
||||
}
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr >= 0x6000) {
|
||||
switch(addr & 15) {
|
||||
case 0x00: case 0x01: case 0x02: case 0x03:
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
chrBank[addr & 7] = data;
|
||||
break;
|
||||
case 0x08:
|
||||
prgBank = data & 0x0f;
|
||||
break;
|
||||
case 0x09:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
case 0x0a:
|
||||
cpu.irqLine(0);
|
||||
irqCounterEnable = data & 0x01;
|
||||
irqCounter = irqLatch;
|
||||
break;
|
||||
case 0x0b:
|
||||
irqLatch = (irqLatch & 0xff00) | (data << 0);
|
||||
break;
|
||||
case 0x0c:
|
||||
irqLatch = (irqLatch & 0x00ff) | (data << 8);
|
||||
break;
|
||||
case 0x0d:
|
||||
//todo: serial EEPROM support
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
addr = (chrBank[addr >> 10] << 10) | (addr & 0x03ff);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
addr = (chrBank[addr >> 10] << 10) | (addr & 0x03ff);
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : chrBank) n = 0;
|
||||
prgBank = 0;
|
||||
mirror = 0;
|
||||
irqCounterEnable = 0;
|
||||
irqCounter = 0;
|
||||
irqLatch = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.array(chrBank);
|
||||
s.integer(prgBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irqCounterEnable);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqLatch);
|
||||
}
|
||||
|
||||
uint8 chrBank[8];
|
||||
uint8 prgBank;
|
||||
uint2 mirror;
|
||||
bool irqCounterEnable;
|
||||
uint16 irqCounter;
|
||||
uint16 irqLatch;
|
||||
};
|
||||
@@ -1,232 +0,0 @@
|
||||
#include "bandai-fcg.cpp"
|
||||
#include "konami-vrc1.cpp"
|
||||
#include "konami-vrc2.cpp"
|
||||
#include "konami-vrc3.cpp"
|
||||
#include "konami-vrc4.cpp"
|
||||
#include "konami-vrc6.cpp"
|
||||
#include "konami-vrc7.cpp"
|
||||
#include "nes-axrom.cpp"
|
||||
#include "nes-bnrom.cpp"
|
||||
#include "nes-cnrom.cpp"
|
||||
#include "nes-exrom.cpp"
|
||||
#include "nes-fxrom.cpp"
|
||||
#include "nes-gxrom.cpp"
|
||||
#include "nes-hkrom.cpp"
|
||||
#include "nes-nrom.cpp"
|
||||
#include "nes-pxrom.cpp"
|
||||
#include "nes-sxrom.cpp"
|
||||
#include "nes-txrom.cpp"
|
||||
#include "nes-uxrom.cpp"
|
||||
#include "sunsoft-5b.cpp"
|
||||
|
||||
Board::Board(Markup::Node& document) {
|
||||
cartridge.board = this;
|
||||
auto board = document["board"];
|
||||
|
||||
information.type = board["id"].text();
|
||||
information.battery = (bool)board["prg/ram/name"];
|
||||
|
||||
auto prom = board["prg/rom"];
|
||||
auto pram = board["prg/ram"];
|
||||
auto crom = board["chr/rom"];
|
||||
auto cram = board["chr/ram"];
|
||||
|
||||
prgrom.size = prom["size"].natural();
|
||||
prgram.size = pram["size"].natural();
|
||||
chrrom.size = crom["size"].natural();
|
||||
chrram.size = cram["size"].natural();
|
||||
|
||||
if(prgrom.size) prgrom.data = new uint8_t[prgrom.size]();
|
||||
if(prgram.size) prgram.data = new uint8_t[prgram.size]();
|
||||
if(chrrom.size) chrrom.data = new uint8_t[chrrom.size]();
|
||||
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
|
||||
|
||||
if(prgrom.name = prom["name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), prgrom.name, File::Read, File::Required)) {
|
||||
fp->read(prgrom.data, min(prgrom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(prgram.name = pram["name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), prgram.name, File::Read)) {
|
||||
fp->read(prgram.data, min(prgram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(chrrom.name = crom["name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), chrrom.name, File::Read, File::Required)) {
|
||||
fp->read(chrrom.data, min(chrrom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(chrram.name = cram["name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), chrram.name, File::Read)) {
|
||||
fp->read(chrram.data, min(chrram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
|
||||
prgram.writable = true;
|
||||
chrram.writable = true;
|
||||
}
|
||||
|
||||
auto Board::save() -> void {
|
||||
auto document = BML::unserialize(cartridge.manifest());
|
||||
|
||||
if(auto name = document["board/prg/ram/name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), name, File::Write)) {
|
||||
fp->write(prgram.data, prgram.size);
|
||||
}
|
||||
}
|
||||
|
||||
if(auto name = document["board/chr/ram/name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), name, File::Write)) {
|
||||
fp->write(chrram.data, chrram.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Board::Memory::read(uint addr) const -> uint8 {
|
||||
return data[mirror(addr, size)];
|
||||
}
|
||||
|
||||
auto Board::Memory::write(uint addr, uint8 byte) -> void {
|
||||
if(writable) data[mirror(addr, size)] = byte;
|
||||
}
|
||||
|
||||
auto Board::mirror(uint addr, uint size) -> uint {
|
||||
uint base = 0;
|
||||
if(size) {
|
||||
uint 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;
|
||||
}
|
||||
|
||||
auto Board::main() -> void {
|
||||
cartridge.clock += 12 * 4095;
|
||||
tick();
|
||||
}
|
||||
|
||||
auto Board::tick() -> void {
|
||||
cartridge.clock += 12;
|
||||
if(cartridge.clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto Board::readCHR(uint addr) -> uint8 {
|
||||
if(chrram.size) return chrram.data[mirror(addr, chrram.size)];
|
||||
if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)];
|
||||
return 0u;
|
||||
}
|
||||
|
||||
auto Board::writeCHR(uint addr, uint8 data) -> void {
|
||||
if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data;
|
||||
}
|
||||
|
||||
auto Board::power() -> void {
|
||||
}
|
||||
|
||||
auto Board::reset() -> void {
|
||||
}
|
||||
|
||||
auto Board::serialize(serializer& s) -> void {
|
||||
if(prgram.size) s.array(prgram.data, prgram.size);
|
||||
if(chrram.size) s.array(chrram.data, chrram.size);
|
||||
}
|
||||
|
||||
auto Board::load(string manifest) -> Board* {
|
||||
auto document = BML::unserialize(manifest);
|
||||
cartridge.information.title = document["information/title"].text();
|
||||
|
||||
string type = document["board/id"].text();
|
||||
|
||||
if(type == "BANDAI-FCG" ) return new BandaiFCG(document);
|
||||
|
||||
if(type == "KONAMI-VRC-1") return new KonamiVRC1(document);
|
||||
if(type == "KONAMI-VRC-2") return new KonamiVRC2(document);
|
||||
if(type == "KONAMI-VRC-3") return new KonamiVRC3(document);
|
||||
if(type == "KONAMI-VRC-4") return new KonamiVRC4(document);
|
||||
if(type == "KONAMI-VRC-6") return new KonamiVRC6(document);
|
||||
if(type == "KONAMI-VRC-7") return new KonamiVRC7(document);
|
||||
|
||||
if(type == "NES-AMROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-ANROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-AN1ROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-AOROM" ) return new NES_AxROM(document);
|
||||
|
||||
if(type == "NES-BNROM" ) return new NES_BNROM(document);
|
||||
|
||||
if(type == "NES-CNROM" ) return new NES_CNROM(document);
|
||||
|
||||
if(type == "NES-EKROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-ELROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-ETROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-EWROM" ) return new NES_ExROM(document);
|
||||
|
||||
if(type == "NES-FJROM" ) return new NES_FxROM(document);
|
||||
if(type == "NES-FKROM" ) return new NES_FxROM(document);
|
||||
|
||||
if(type == "NES-GNROM" ) return new NES_GxROM(document);
|
||||
if(type == "NES-MHROM" ) return new NES_GxROM(document);
|
||||
|
||||
if(type == "NES-HKROM" ) return new NES_HKROM(document);
|
||||
|
||||
if(type == "NES-NROM" ) return new NES_NROM(document);
|
||||
if(type == "NES-NROM-128") return new NES_NROM(document);
|
||||
if(type == "NES-NROM-256") return new NES_NROM(document);
|
||||
|
||||
if(type == "NES-PEEOROM" ) return new NES_PxROM(document);
|
||||
if(type == "NES-PNROM" ) return new NES_PxROM(document);
|
||||
|
||||
if(type == "NES-SAROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SBROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SCROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SC1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SEROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SFROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SFEXPROM") return new NES_SxROM(document);
|
||||
if(type == "NES-SGROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SHROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SH1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SIROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SJROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SKROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SLROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL2ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL3ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SLRROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SMROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SNROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SOROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SUROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SXROM" ) return new NES_SxROM(document);
|
||||
|
||||
if(type == "NES-TBROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TEROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TFROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TGROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TKROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TKSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TLROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TL1ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TL2ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TLSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TNROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TQROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TR1ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TVROM" ) return new NES_TxROM(document);
|
||||
|
||||
if(type == "NES-UNROM" ) return new NES_UxROM(document);
|
||||
if(type == "NES-UOROM" ) return new NES_UxROM(document);
|
||||
|
||||
if(type == "SUNSOFT-5B" ) return new Sunsoft5B(document);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
struct Board {
|
||||
struct Memory {
|
||||
inline Memory(uint8_t* data, uint size) : data(data), size(size) {}
|
||||
inline Memory() : data(nullptr), size(0u), writable(false) {}
|
||||
inline ~Memory() { if(data) delete[] data; }
|
||||
|
||||
inline auto read(uint addr) const -> uint8;
|
||||
inline auto write(uint addr, uint8 data) -> void;
|
||||
|
||||
string name;
|
||||
uint8_t* data = nullptr;
|
||||
uint size = 0;
|
||||
bool writable = false;
|
||||
};
|
||||
|
||||
virtual ~Board() = default;
|
||||
|
||||
static auto mirror(uint addr, uint size) -> uint;
|
||||
|
||||
Board(Markup::Node& document);
|
||||
auto save() -> void;
|
||||
|
||||
virtual auto main() -> void;
|
||||
virtual auto tick() -> void;
|
||||
|
||||
virtual auto readPRG(uint addr) -> uint8 = 0;
|
||||
virtual auto writePRG(uint addr, uint8 data) -> void = 0;
|
||||
|
||||
virtual auto readCHR(uint addr) -> uint8;
|
||||
virtual auto writeCHR(uint addr, uint8 data) -> void;
|
||||
|
||||
virtual inline auto scanline(uint y) -> void {}
|
||||
|
||||
virtual auto power() -> void;
|
||||
virtual auto reset() -> void;
|
||||
|
||||
virtual auto serialize(serializer&) -> void;
|
||||
|
||||
static auto load(string manifest) -> Board*;
|
||||
|
||||
struct Information {
|
||||
string type;
|
||||
bool battery;
|
||||
} information;
|
||||
|
||||
Memory prgrom;
|
||||
Memory prgram;
|
||||
Memory chrrom;
|
||||
Memory chrram;
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
struct KonamiVRC1 : Board {
|
||||
KonamiVRC1(Markup::Node& document) : Board(document), vrc1(*this) {
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(vrc1.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) return vrc1.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc1.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc1.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc1.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(vrc1.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc1.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc1.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc1.serialize(s);
|
||||
}
|
||||
|
||||
VRC1 vrc1;
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
struct KonamiVRC2 : Board {
|
||||
KonamiVRC2(Markup::Node& document) : Board(document), vrc2(*this) {
|
||||
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
|
||||
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return vrc2.readRAM(addr);
|
||||
return prgrom.read(vrc2.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return vrc2.writeRAM(addr, data);
|
||||
|
||||
bool a0 = (addr & settings.pinout.a0);
|
||||
bool a1 = (addr & settings.pinout.a1);
|
||||
addr &= 0xfff0;
|
||||
addr |= (a0 << 0) | (a1 << 1);
|
||||
return vrc2.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc2.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc2.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc2.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(vrc2.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc2.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc2.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc2.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
struct Pinout {
|
||||
uint a0;
|
||||
uint a1;
|
||||
} pinout;
|
||||
} settings;
|
||||
|
||||
VRC2 vrc2;
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
struct KonamiVRC3 : Board {
|
||||
KonamiVRC3(Markup::Node& document) : Board(document), vrc3(*this) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
vrc3.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) return prgram.read(addr & 0x1fff);
|
||||
if(addr & 0x8000) return prgrom.read(vrc3.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return prgram.write(addr & 0x1fff, data);
|
||||
if(addr & 0x8000) return vrc3.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
return chrram.read(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
return chrram.write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc3.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc3.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc3.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
VRC3 vrc3;
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
struct KonamiVRC4 : Board {
|
||||
KonamiVRC4(Markup::Node& document) : Board(document), vrc4(*this) {
|
||||
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
|
||||
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return vrc4.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
return prgrom.read(vrc4.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
bool a0 = (addr & settings.pinout.a0);
|
||||
bool a1 = (addr & settings.pinout.a1);
|
||||
addr &= 0xfff0;
|
||||
addr |= (a1 << 1) | (a0 << 0);
|
||||
return vrc4.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc4.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc4.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc4.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(vrc4.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc4.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc4.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc4.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
struct Pinout {
|
||||
uint a0;
|
||||
uint a1;
|
||||
} pinout;
|
||||
} settings;
|
||||
|
||||
VRC4 vrc4;
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
struct KonamiVRC6 : Board {
|
||||
KonamiVRC6(Markup::Node& document) : Board(document), vrc6(*this) {
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8{
|
||||
if((addr & 0xe000) == 0x6000) return vrc6.readRAM(addr);
|
||||
if(addr & 0x8000) return prgrom.read(vrc6.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return vrc6.writeRAM(addr, data);
|
||||
if(addr & 0x8000) {
|
||||
addr = (addr & 0xf003);
|
||||
if(prgram.size) addr = (addr & ~3) | ((addr & 2) >> 1) | ((addr & 1) << 1);
|
||||
return vrc6.writeIO(addr, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc6.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc6.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc6.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(vrc6.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc6.serialize(s);
|
||||
}
|
||||
|
||||
auto main() -> void { vrc6.main(); }
|
||||
auto power() -> void { vrc6.power(); }
|
||||
auto reset() -> void { vrc6.reset(); }
|
||||
|
||||
VRC6 vrc6;
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
struct KonamiVRC7 : Board {
|
||||
KonamiVRC7(Markup::Node& document) : Board(document), vrc7(*this) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return vrc7.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
return prgrom.read(vrc7.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
return vrc7.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc7.addrCIRAM(addr));
|
||||
return chrram.read(vrc7.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(vrc7.addrCIRAM(addr), data);
|
||||
return chrram.write(vrc7.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc7.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc7.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc7.serialize(s);
|
||||
}
|
||||
|
||||
VRC7 vrc7;
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
//NES-AMROM
|
||||
//NES-ANROM
|
||||
//NES-AN1ROM
|
||||
//NES-AOROM
|
||||
|
||||
struct NES_AxROM : Board {
|
||||
NES_AxROM(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) {
|
||||
prgBank = data & 0x0f;
|
||||
mirrorSelect = data & 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM((mirrorSelect << 10) | (addr & 0x03ff));
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM((mirrorSelect << 10) | (addr & 0x03ff), data);
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgBank = 0x0f;
|
||||
mirrorSelect = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prgBank);
|
||||
s.integer(mirrorSelect);
|
||||
}
|
||||
|
||||
uint4 prgBank;
|
||||
bool mirrorSelect;
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
//NES-BN-ROM-01
|
||||
|
||||
struct NES_BNROM : Board {
|
||||
NES_BNROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prgBank = data & 0x03;
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr);
|
||||
}
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr, data);
|
||||
}
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 prgBank;
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
//NES-CNROM
|
||||
|
||||
struct NES_CNROM : Board {
|
||||
NES_CNROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(addr & 0x7fff);
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) chrBank = data & 0x03;
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chrBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(chrBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 chrBank;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
struct NES_ExROM : Board {
|
||||
NES_ExROM(Markup::Node& document) : Board(document), mmc5(*this) {
|
||||
revision = Revision::ELROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc5.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
return mmc5.readPRG(addr);
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
mmc5.writePRG(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
return mmc5.readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
mmc5.writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
mmc5.scanline(y);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc5.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmc5.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc5.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
EKROM,
|
||||
ELROM,
|
||||
ETROM,
|
||||
EWROM,
|
||||
} revision;
|
||||
|
||||
MMC5 mmc5;
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
//MMC4
|
||||
|
||||
struct NES_FxROM : Board {
|
||||
NES_FxROM(Markup::Node& document) : Board(document) {
|
||||
revision = Revision::FKROM;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
uint bank = addr < 0xc000 ? prgBank : (uint4)0x0f;
|
||||
return prgrom.read((bank * 0x4000) | (addr & 0x3fff));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
switch(addr & 0xf000) {
|
||||
case 0xa000: prgBank = data & 0x0f; break;
|
||||
case 0xb000: chrBank[0][0] = data & 0x1f; break;
|
||||
case 0xc000: chrBank[0][1] = data & 0x1f; break;
|
||||
case 0xd000: chrBank[1][0] = data & 0x1f; break;
|
||||
case 0xe000: chrBank[1][1] = data & 0x1f; break;
|
||||
case 0xf000: mirror = data & 0x01; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgBank = 0;
|
||||
chrBank[0][0] = 0;
|
||||
chrBank[0][1] = 0;
|
||||
chrBank[1][0] = 0;
|
||||
chrBank[1][1] = 0;
|
||||
mirror = 0;
|
||||
latch[0] = 0;
|
||||
latch[1] = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prgBank);
|
||||
s.integer(chrBank[0][0]);
|
||||
s.integer(chrBank[0][1]);
|
||||
s.integer(chrBank[1][0]);
|
||||
s.integer(chrBank[1][1]);
|
||||
s.integer(mirror);
|
||||
s.array(latch);
|
||||
}
|
||||
|
||||
enum Revision : uint {
|
||||
FJROM,
|
||||
FKROM,
|
||||
} revision;
|
||||
|
||||
uint4 prgBank;
|
||||
uint5 chrBank[2][2];
|
||||
bool mirror;
|
||||
bool latch[2];
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
//NES-GNROM
|
||||
//NES-MHROM
|
||||
|
||||
struct NES_GxROM : Board {
|
||||
NES_GxROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) {
|
||||
prgBank = (data & 0x30) >> 4;
|
||||
chrBank = (data & 0x03) >> 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgBank = 0;
|
||||
chrBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(prgBank);
|
||||
s.integer(chrBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 prgBank;
|
||||
uint2 chrBank;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
struct NES_HKROM : Board {
|
||||
NES_HKROM(Markup::Node& document) : Board(document), mmc6(*this) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc6.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xf000) == 0x7000) return mmc6.readRAM(addr);
|
||||
if(addr & 0x8000) return prgrom.read(mmc6.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xf000) == 0x7000) return mmc6.writeRAM(addr, data);
|
||||
if(addr & 0x8000) return mmc6.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
mmc6.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc6.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc6.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
mmc6.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(mmc6.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(mmc6.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc6.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmc6.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc6.serialize(s);
|
||||
}
|
||||
|
||||
MMC6 mmc6;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
//NES-NROM-128
|
||||
//NES-NROM-256
|
||||
|
||||
struct NES_NROM : Board {
|
||||
NES_NROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(addr);
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
if(chrram.size) return chrram.read(addr);
|
||||
return chrrom.read(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
if(chrram.size) return chrram.write(addr, data);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
//MMC2
|
||||
|
||||
struct NES_PxROM : Board {
|
||||
NES_PxROM(Markup::Node& document) : Board(document) {
|
||||
revision = Revision::PNROM;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
uint bank = 0;
|
||||
switch((addr / 0x2000) & 3) {
|
||||
case 0: bank = prgBank; break;
|
||||
case 1: bank = 0x0d; break;
|
||||
case 2: bank = 0x0e; break;
|
||||
case 3: bank = 0x0f; break;
|
||||
}
|
||||
return prgrom.read((bank * 0x2000) | (addr & 0x1fff));
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
switch(addr & 0xf000) {
|
||||
case 0xa000: prgBank = data & 0x0f; break;
|
||||
case 0xb000: chrBank[0][0] = data & 0x1f; break;
|
||||
case 0xc000: chrBank[0][1] = data & 0x1f; break;
|
||||
case 0xd000: chrBank[1][0] = data & 0x1f; break;
|
||||
case 0xe000: chrBank[1][1] = data & 0x1f; break;
|
||||
case 0xf000: mirror = data & 0x01; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgBank = 0;
|
||||
chrBank[0][0] = 0;
|
||||
chrBank[0][1] = 0;
|
||||
chrBank[1][0] = 0;
|
||||
chrBank[1][1] = 0;
|
||||
mirror = 0;
|
||||
latch[0] = 0;
|
||||
latch[1] = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prgBank);
|
||||
s.integer(chrBank[0][0]);
|
||||
s.integer(chrBank[0][1]);
|
||||
s.integer(chrBank[1][0]);
|
||||
s.integer(chrBank[1][1]);
|
||||
s.integer(mirror);
|
||||
s.array(latch);
|
||||
}
|
||||
|
||||
enum Revision : uint {
|
||||
PEEOROM,
|
||||
PNROM,
|
||||
} revision;
|
||||
|
||||
uint4 prgBank;
|
||||
uint5 chrBank[2][2];
|
||||
bool mirror;
|
||||
bool latch[2];
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
struct NES_SxROM : Board {
|
||||
NES_SxROM(Markup::Node& document) : Board(document), mmc1(*this) {
|
||||
revision = Revision::SXROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return mmc1.main();
|
||||
}
|
||||
|
||||
auto addrRAM(uint addr) -> uint {
|
||||
uint bank = 0;
|
||||
if(revision == Revision::SOROM) bank = (mmc1.chrBank[0] & 0x08) >> 3;
|
||||
if(revision == Revision::SUROM) bank = (mmc1.chrBank[0] & 0x0c) >> 2;
|
||||
if(revision == Revision::SXROM) bank = (mmc1.chrBank[0] & 0x0c) >> 2;
|
||||
return (bank << 13) | (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
if(revision == Revision::SNROM) {
|
||||
if(mmc1.chrBank[0] & 0x10) return cpu.mdr();
|
||||
}
|
||||
if(mmc1.ramDisable) return 0x00;
|
||||
return prgram.read(addrRAM(addr));
|
||||
}
|
||||
|
||||
if(addr & 0x8000) {
|
||||
addr = mmc1.addrPRG(addr);
|
||||
if(revision == Revision::SXROM) {
|
||||
addr |= ((mmc1.chrBank[0] & 0x10) >> 4) << 18;
|
||||
}
|
||||
return prgrom.read(addr);
|
||||
}
|
||||
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
if(revision == Revision::SNROM) {
|
||||
if(mmc1.chrBank[0] & 0x10) return;
|
||||
}
|
||||
if(mmc1.ramDisable) return;
|
||||
return prgram.write(addrRAM(addr), data);
|
||||
}
|
||||
|
||||
if(addr & 0x8000) return mmc1.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc1.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc1.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(mmc1.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(mmc1.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc1.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmc1.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc1.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
SAROM,
|
||||
SBROM,
|
||||
SCROM,
|
||||
SC1ROM,
|
||||
SEROM,
|
||||
SFROM,
|
||||
SGROM,
|
||||
SHROM,
|
||||
SH1ROM,
|
||||
SIROM,
|
||||
SJROM,
|
||||
SKROM,
|
||||
SLROM,
|
||||
SL1ROM,
|
||||
SL2ROM,
|
||||
SL3ROM,
|
||||
SLRROM,
|
||||
SMROM,
|
||||
SNROM,
|
||||
SOROM,
|
||||
SUROM,
|
||||
SXROM,
|
||||
} revision;
|
||||
|
||||
MMC1 mmc1;
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
struct NES_TxROM : Board {
|
||||
NES_TxROM(Markup::Node& document) : Board(document), mmc3(*this) {
|
||||
revision = Revision::TLROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc3.main();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) return mmc3.readRAM(addr);
|
||||
if(addr & 0x8000) return prgrom.read(mmc3.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return mmc3.writeRAM(addr, data);
|
||||
if(addr & 0x8000) return mmc3.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
mmc3.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc3.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc3.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
mmc3.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(mmc3.addrCIRAM(addr), data);
|
||||
return Board::writeCHR(mmc3.addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc3.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmc3.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc3.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
TBROM,
|
||||
TEROM,
|
||||
TFROM,
|
||||
TGROM,
|
||||
TKROM,
|
||||
TKSROM,
|
||||
TLROM,
|
||||
TL1ROM,
|
||||
TL2ROM,
|
||||
TLSROM,
|
||||
TNROM,
|
||||
TQROM,
|
||||
TR1ROM,
|
||||
TSROM,
|
||||
TVROM,
|
||||
} revision;
|
||||
|
||||
MMC3 mmc3;
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
//NES-UNROM
|
||||
//NES-UOROM
|
||||
|
||||
struct NES_UxROM : Board {
|
||||
NES_UxROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xc000) == 0x8000) return prgrom.read((prgBank << 14) | (addr & 0x3fff));
|
||||
if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prgBank = data & 0x0f;
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.readCIRAM(addr);
|
||||
}
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.writeCIRAM(addr, data);
|
||||
}
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint4 prgBank;
|
||||
};
|
||||
@@ -1,218 +0,0 @@
|
||||
//SUNSOFT-5B
|
||||
|
||||
struct Sunsoft5B : Board {
|
||||
Sunsoft5B(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
struct Pulse {
|
||||
auto clock() -> void {
|
||||
if(--counter == 0) {
|
||||
counter = frequency << 4;
|
||||
duty ^= 1;
|
||||
}
|
||||
output = duty ? volume : (uint4)0;
|
||||
if(disable) output = 0;
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
disable = 1;
|
||||
frequency = 1;
|
||||
volume = 0;
|
||||
|
||||
counter = 0;
|
||||
duty = 0;
|
||||
output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(disable);
|
||||
s.integer(frequency);
|
||||
s.integer(volume);
|
||||
|
||||
s.integer(counter);
|
||||
s.integer(duty);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
bool disable;
|
||||
uint12 frequency;
|
||||
uint4 volume;
|
||||
|
||||
uint16 counter; //12-bit countdown + 4-bit phase
|
||||
uint1 duty;
|
||||
uint4 output;
|
||||
} pulse[3];
|
||||
|
||||
auto main() -> void {
|
||||
if(irqCounterEnable) {
|
||||
if(--irqCounter == 0xffff) {
|
||||
cpu.irqLine(irqEnable);
|
||||
}
|
||||
}
|
||||
|
||||
pulse[0].clock();
|
||||
pulse[1].clock();
|
||||
pulse[2].clock();
|
||||
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
||||
apu.setSample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
|
||||
uint8 bank = 0x3f; //((addr & 0xe000) == 0xe000
|
||||
if((addr & 0xe000) == 0x6000) bank = prgBank[0];
|
||||
if((addr & 0xe000) == 0x8000) bank = prgBank[1];
|
||||
if((addr & 0xe000) == 0xa000) bank = prgBank[2];
|
||||
if((addr & 0xe000) == 0xc000) bank = prgBank[3];
|
||||
|
||||
bool ramEnable = bank & 0x80;
|
||||
bool ramSelect = bank & 0x40;
|
||||
bank &= 0x3f;
|
||||
|
||||
if(ramSelect) {
|
||||
if(!ramEnable) return cpu.mdr();
|
||||
return prgram.data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
addr = (bank << 13) | (addr & 0x1fff);
|
||||
return prgrom.read(addr);
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
if(addr == 0x8000) {
|
||||
mmuPort = data & 0x0f;
|
||||
}
|
||||
|
||||
if(addr == 0xa000) {
|
||||
switch(mmuPort) {
|
||||
case 0: chrBank[0] = data; break;
|
||||
case 1: chrBank[1] = data; break;
|
||||
case 2: chrBank[2] = data; break;
|
||||
case 3: chrBank[3] = data; break;
|
||||
case 4: chrBank[4] = data; break;
|
||||
case 5: chrBank[5] = data; break;
|
||||
case 6: chrBank[6] = data; break;
|
||||
case 7: chrBank[7] = data; break;
|
||||
case 8: prgBank[0] = data; break;
|
||||
case 9: prgBank[1] = data; break;
|
||||
case 10: prgBank[2] = data; break;
|
||||
case 11: prgBank[3] = data; break;
|
||||
case 12: mirror = data & 3; break;
|
||||
case 13:
|
||||
irqEnable = data & 0x80;
|
||||
irqCounterEnable = data & 0x01;
|
||||
if(irqEnable == 0) cpu.irqLine(0);
|
||||
break;
|
||||
case 14: irqCounter = (irqCounter & 0xff00) | (data << 0); break;
|
||||
case 15: irqCounter = (irqCounter & 0x00ff) | (data << 8); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(addr == 0xc000) {
|
||||
apuPort = data & 0x0f;
|
||||
}
|
||||
|
||||
if(addr == 0xe000) {
|
||||
switch(apuPort) {
|
||||
case 0: pulse[0].frequency = (pulse[0].frequency & 0xff00) | (data << 0); break;
|
||||
case 1: pulse[0].frequency = (pulse[0].frequency & 0x00ff) | (data << 8); break;
|
||||
case 2: pulse[1].frequency = (pulse[1].frequency & 0xff00) | (data << 0); break;
|
||||
case 3: pulse[1].frequency = (pulse[1].frequency & 0x00ff) | (data << 8); break;
|
||||
case 4: pulse[2].frequency = (pulse[2].frequency & 0xff00) | (data << 0); break;
|
||||
case 5: pulse[2].frequency = (pulse[2].frequency & 0x00ff) | (data << 8); break;
|
||||
case 7:
|
||||
pulse[0].disable = data & 0x01;
|
||||
pulse[1].disable = data & 0x02;
|
||||
pulse[2].disable = data & 0x04;
|
||||
break;
|
||||
case 8: pulse[0].volume = data & 0x0f; break;
|
||||
case 9: pulse[1].volume = data & 0x0f; break;
|
||||
case 10: pulse[2].volume = data & 0x0f; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) -> uint {
|
||||
uint8 bank = (addr >> 10) & 7;
|
||||
return (chrBank[bank] << 10) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //first
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //second
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
return Board::readCHR(addrCHR(addr));
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
return Board::writeCHR(addrCHR(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
for(signed n : range(16)) {
|
||||
double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n));
|
||||
dac[n] = volume * 8192.0;
|
||||
}
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmuPort = 0;
|
||||
apuPort = 0;
|
||||
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
irqEnable = 0;
|
||||
irqCounterEnable = 0;
|
||||
irqCounter = 0;
|
||||
|
||||
pulse[0].reset();
|
||||
pulse[1].reset();
|
||||
pulse[2].reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(mmuPort);
|
||||
s.integer(apuPort);
|
||||
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqCounterEnable);
|
||||
s.integer(irqCounter);
|
||||
|
||||
pulse[0].serialize(s);
|
||||
pulse[1].serialize(s);
|
||||
pulse[2].serialize(s);
|
||||
}
|
||||
|
||||
uint4 mmuPort;
|
||||
uint4 apuPort;
|
||||
|
||||
uint8 prgBank[4];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
bool irqEnable;
|
||||
bool irqCounterEnable;
|
||||
uint16 irqCounter;
|
||||
|
||||
int16 dac[16];
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "chip/chip.cpp"
|
||||
#include "board/board.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cartridge.main();
|
||||
}
|
||||
|
||||
auto Cartridge::main() -> void {
|
||||
board->main();
|
||||
}
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
if(auto pathID = interface->load(ID::Famicom, "Famicom", "fc")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
|
||||
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
Board::load(information.manifest); //this call will set Cartridge::board if successful
|
||||
if(!board) return false;
|
||||
|
||||
Hash::SHA256 sha;
|
||||
sha.data(board->prgrom.data, board->prgrom.size);
|
||||
sha.data(board->chrrom.data, board->chrrom.size);
|
||||
information.sha256 = sha.digest();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
board->save();
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
delete board;
|
||||
board = nullptr;
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
board->power();
|
||||
}
|
||||
|
||||
auto Cartridge::reset() -> void {
|
||||
create(Cartridge::Enter, 21'477'272);
|
||||
board->reset();
|
||||
}
|
||||
|
||||
auto Cartridge::readPRG(uint addr) -> uint8 {
|
||||
return board->readPRG(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::writePRG(uint addr, uint8 data) -> void {
|
||||
return board->writePRG(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::readCHR(uint addr) -> uint8 {
|
||||
return board->readCHR(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::writeCHR(uint addr, uint8 data) -> void {
|
||||
return board->writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::scanline(uint y) -> void {
|
||||
return board->scanline(y);
|
||||
}
|
||||
|
||||
auto Cartridge::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
return board->serialize(s);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
#include "chip/chip.hpp"
|
||||
#include "board/board.hpp"
|
||||
|
||||
struct Cartridge : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto sha256() const -> string { return information.sha256; }
|
||||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Information {
|
||||
uint pathID = 0;
|
||||
string sha256;
|
||||
string manifest;
|
||||
string title;
|
||||
} information;
|
||||
|
||||
//privileged:
|
||||
Board* board = nullptr;
|
||||
|
||||
auto readPRG(uint addr) -> uint8;
|
||||
auto writePRG(uint addr, uint8 data) -> void;
|
||||
|
||||
auto readCHR(uint addr) -> uint8;
|
||||
auto writeCHR(uint addr, uint8 data) -> void;
|
||||
|
||||
//scanline() is for debugging purposes only:
|
||||
//boards must detect scanline edges on their own
|
||||
auto scanline(uint y) -> void;
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "mmc1.cpp"
|
||||
#include "mmc3.cpp"
|
||||
#include "mmc5.cpp"
|
||||
#include "mmc6.cpp"
|
||||
#include "vrc1.cpp"
|
||||
#include "vrc2.cpp"
|
||||
#include "vrc3.cpp"
|
||||
#include "vrc4.cpp"
|
||||
#include "vrc6.cpp"
|
||||
#include "vrc7.cpp"
|
||||
|
||||
Chip::Chip(Board& board) : board(board) {
|
||||
}
|
||||
|
||||
auto Chip::tick() -> void {
|
||||
board.tick();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
struct Board;
|
||||
|
||||
struct Chip {
|
||||
Chip(Board& board);
|
||||
auto tick() -> void;
|
||||
|
||||
Board& board;
|
||||
};
|
||||
@@ -1,128 +0,0 @@
|
||||
struct MMC1 : Chip {
|
||||
MMC1(Board& board) : Chip(board) {
|
||||
revision = Revision::MMC1B2;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(writedelay) writedelay--;
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) -> uint {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (prgBank & ~1) + region;
|
||||
|
||||
if(prgSize) {
|
||||
bank = (region == 0 ? 0x0 : 0xf);
|
||||
if(region != prgMode) bank = prgBank;
|
||||
}
|
||||
|
||||
return (bank << 14) | (addr & 0x3fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) -> uint {
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chrBank[region];
|
||||
if(chrMode == 0) bank = (chrBank[0] & ~1) | region;
|
||||
return (bank << 12) | (addr & 0x0fff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return 0x0000 | (addr & 0x03ff);
|
||||
case 1: return 0x0400 | (addr & 0x03ff);
|
||||
case 2: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
case 3: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
if(writedelay) return;
|
||||
writedelay = 2;
|
||||
|
||||
if(data & 0x80) {
|
||||
shiftaddr = 0;
|
||||
prgSize = 1;
|
||||
prgMode = 1;
|
||||
} else {
|
||||
shiftdata = ((data & 1) << 4) | (shiftdata >> 1);
|
||||
if(++shiftaddr == 5) {
|
||||
shiftaddr = 0;
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
chrMode = (shiftdata & 0x10);
|
||||
prgSize = (shiftdata & 0x08);
|
||||
prgMode = (shiftdata & 0x04);
|
||||
mirror = (shiftdata & 0x03);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
chrBank[0] = (shiftdata & 0x1f);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
chrBank[1] = (shiftdata & 0x1f);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
ramDisable = (shiftdata & 0x10);
|
||||
prgBank = (shiftdata & 0x0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
writedelay = 0;
|
||||
shiftaddr = 0;
|
||||
shiftdata = 0;
|
||||
|
||||
chrMode = 0;
|
||||
prgSize = 1;
|
||||
prgMode = 1;
|
||||
mirror = 0;
|
||||
chrBank[0] = 0;
|
||||
chrBank[1] = 1;
|
||||
ramDisable = 0;
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(writedelay);
|
||||
s.integer(shiftaddr);
|
||||
s.integer(shiftdata);
|
||||
|
||||
s.integer(chrMode);
|
||||
s.integer(prgSize);
|
||||
s.integer(prgMode);
|
||||
s.integer(mirror);
|
||||
s.array(chrBank);
|
||||
s.integer(ramDisable);
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
MMC1,
|
||||
MMC1A,
|
||||
MMC1B1,
|
||||
MMC1B2,
|
||||
MMC1B3,
|
||||
MMC1C,
|
||||
} revision;
|
||||
|
||||
uint writedelay;
|
||||
uint shiftaddr;
|
||||
uint shiftdata;
|
||||
|
||||
bool chrMode;
|
||||
bool prgSize; //0 = 32K, 1 = 16K
|
||||
bool prgMode;
|
||||
uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal
|
||||
uint5 chrBank[2];
|
||||
bool ramDisable;
|
||||
uint4 prgBank;
|
||||
};
|
||||
@@ -1,181 +0,0 @@
|
||||
struct MMC3 : Chip {
|
||||
MMC3(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqDelay) irqDelay--;
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irqTest(uint addr) -> void {
|
||||
if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
|
||||
if(irqDelay == 0) {
|
||||
if(irqCounter == 0) {
|
||||
irqCounter = irqLatch;
|
||||
} else if(--irqCounter == 0) {
|
||||
if(irqEnable) irqLine = 1;
|
||||
}
|
||||
}
|
||||
irqDelay = 6;
|
||||
}
|
||||
chrAbus = addr;
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 1:
|
||||
return (prgBank[1] << 13) | (addr & 0x1fff);
|
||||
case 2:
|
||||
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
if(chrMode == 0) {
|
||||
if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
|
||||
} else {
|
||||
if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
if(ramEnable) return board.prgram.data[addr & 0x1fff];
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(ramEnable && !ramWriteProtect) board.prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xe001) {
|
||||
case 0x8000:
|
||||
chrMode = data & 0x80;
|
||||
prgMode = data & 0x40;
|
||||
bankSelect = data & 0x07;
|
||||
break;
|
||||
|
||||
case 0x8001:
|
||||
switch(bankSelect) {
|
||||
case 0: chrBank[0] = data & ~1; break;
|
||||
case 1: chrBank[1] = data & ~1; break;
|
||||
case 2: chrBank[2] = data; break;
|
||||
case 3: chrBank[3] = data; break;
|
||||
case 4: chrBank[4] = data; break;
|
||||
case 5: chrBank[5] = data; break;
|
||||
case 6: prgBank[0] = data & 0x3f; break;
|
||||
case 7: prgBank[1] = data & 0x3f; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
ramEnable = data & 0x80;
|
||||
ramWriteProtect = data & 0x40;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irqCounter = 0;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
irqEnable = false;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xe001:
|
||||
irqEnable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chrMode = 0;
|
||||
prgMode = 0;
|
||||
bankSelect = 0;
|
||||
prgBank[0] = 0;
|
||||
prgBank[1] = 0;
|
||||
chrBank[0] = 0;
|
||||
chrBank[1] = 0;
|
||||
chrBank[2] = 0;
|
||||
chrBank[3] = 0;
|
||||
chrBank[4] = 0;
|
||||
chrBank[5] = 0;
|
||||
mirror = 0;
|
||||
ramEnable = 1;
|
||||
ramWriteProtect = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter = 0;
|
||||
irqEnable = false;
|
||||
irqDelay = 0;
|
||||
irqLine = 0;
|
||||
|
||||
chrAbus = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(chrMode);
|
||||
s.integer(prgMode);
|
||||
s.integer(bankSelect);
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(ramEnable);
|
||||
s.integer(ramWriteProtect);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqDelay);
|
||||
s.integer(irqLine);
|
||||
|
||||
s.integer(chrAbus);
|
||||
}
|
||||
|
||||
bool chrMode;
|
||||
bool prgMode;
|
||||
uint3 bankSelect;
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[6];
|
||||
bool mirror;
|
||||
bool ramEnable;
|
||||
bool ramWriteProtect;
|
||||
uint8 irqLatch;
|
||||
uint8 irqCounter;
|
||||
bool irqEnable;
|
||||
uint irqDelay;
|
||||
bool irqLine;
|
||||
|
||||
uint16 chrAbus;
|
||||
};
|
||||
@@ -1,489 +0,0 @@
|
||||
struct MMC5 : Chip {
|
||||
MMC5(Board& board) : Chip(board) {
|
||||
revision = Revision::MMC5;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
//scanline() resets this; if no scanlines detected, enter video blanking period
|
||||
if(++cpuCycleCounter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
||||
|
||||
cpu.irqLine(irqEnable && irqPending);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
//used for testing only, to verify MMC5 scanline detection is accurate:
|
||||
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
|
||||
}
|
||||
|
||||
auto accessPRG(bool write, uint addr, uint8 data = 0x00) -> uint8 {
|
||||
uint bank;
|
||||
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
bank = (ramSelect << 2) | ramBank;
|
||||
addr &= 0x1fff;
|
||||
} else if(prgMode == 0) {
|
||||
bank = prgBank[3] & ~3;
|
||||
addr &= 0x7fff;
|
||||
} else if(prgMode == 1) {
|
||||
if((addr & 0xc000) == 0x8000) bank = (prgBank[1] & ~1);
|
||||
if((addr & 0xe000) == 0xc000) bank = (prgBank[3] & ~1);
|
||||
addr &= 0x3fff;
|
||||
} else if(prgMode == 2) {
|
||||
if((addr & 0xe000) == 0x8000) bank = (prgBank[1] & ~1) | 0;
|
||||
if((addr & 0xe000) == 0xa000) bank = (prgBank[1] & ~1) | 1;
|
||||
if((addr & 0xe000) == 0xc000) bank = (prgBank[2]);
|
||||
if((addr & 0xe000) == 0xe000) bank = (prgBank[3]);
|
||||
addr &= 0x1fff;
|
||||
} else if(prgMode == 3) {
|
||||
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
|
||||
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
|
||||
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
|
||||
if((addr & 0xe000) == 0xe000) bank = prgBank[3];
|
||||
addr &= 0x1fff;
|
||||
}
|
||||
|
||||
bool rom = bank & 0x80;
|
||||
bank &= 0x7f;
|
||||
|
||||
if(write == false) {
|
||||
if(rom) {
|
||||
return board.prgrom.read((bank << 13) | addr);
|
||||
} else {
|
||||
return board.prgram.read((bank << 13) | addr);
|
||||
}
|
||||
} else {
|
||||
if(rom) {
|
||||
board.prgrom.write((bank << 13) | addr, data);
|
||||
} else {
|
||||
if(prgramWriteProtect[0] == 2 && prgramWriteProtect[1] == 1) {
|
||||
board.prgram.write((bank << 13) | addr, data);
|
||||
}
|
||||
}
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
if(exramMode >= 2) return exram[addr & 0x03ff];
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
if(addr >= 0x6000) {
|
||||
return accessPRG(0, addr);
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x5204: {
|
||||
uint8 result = (irqPending << 7) | (inFrame << 6);
|
||||
irqPending = false;
|
||||
return result;
|
||||
}
|
||||
case 0x5205: return (multiplier * multiplicand) >> 0;
|
||||
case 0x5206: return (multiplier * multiplicand) >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
//writes 0x00 *during* Vblank (not during screen rendering ...)
|
||||
if(exramMode == 0 || exramMode == 1) exram[addr & 0x03ff] = inFrame ? data : (uint8)0x00;
|
||||
if(exramMode == 2) exram[addr & 0x03ff] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr >= 0x6000) {
|
||||
accessPRG(1, addr, data);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x2000:
|
||||
sprite8x16 = data & 0x20;
|
||||
break;
|
||||
|
||||
case 0x2001:
|
||||
//if BG+sprites are disabled; enter video blanking period
|
||||
if((data & 0x18) == 0) blank();
|
||||
break;
|
||||
|
||||
case 0x5100: prgMode = data & 3; break;
|
||||
case 0x5101: chrMode = data & 3; break;
|
||||
|
||||
case 0x5102: prgramWriteProtect[0] = data & 3; break;
|
||||
case 0x5103: prgramWriteProtect[1] = data & 3; break;
|
||||
|
||||
case 0x5104:
|
||||
exramMode = data & 3;
|
||||
break;
|
||||
|
||||
case 0x5105:
|
||||
nametableMode[0] = (data & 0x03) >> 0;
|
||||
nametableMode[1] = (data & 0x0c) >> 2;
|
||||
nametableMode[2] = (data & 0x30) >> 4;
|
||||
nametableMode[3] = (data & 0xc0) >> 6;
|
||||
break;
|
||||
|
||||
case 0x5106:
|
||||
fillmodeTile = data;
|
||||
break;
|
||||
|
||||
case 0x5107:
|
||||
fillmodeColor = data & 3;
|
||||
fillmodeColor |= fillmodeColor << 2;
|
||||
fillmodeColor |= fillmodeColor << 4;
|
||||
break;
|
||||
|
||||
case 0x5113:
|
||||
ramSelect = data & 0x04;
|
||||
ramBank = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0x5114: prgBank[0] = data; break;
|
||||
case 0x5115: prgBank[1] = data; break;
|
||||
case 0x5116: prgBank[2] = data; break;
|
||||
case 0x5117: prgBank[3] = data | 0x80; break;
|
||||
|
||||
case 0x5120: chrSpriteBank[0] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5121: chrSpriteBank[1] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5122: chrSpriteBank[2] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5123: chrSpriteBank[3] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5124: chrSpriteBank[4] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5125: chrSpriteBank[5] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5126: chrSpriteBank[6] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
case 0x5127: chrSpriteBank[7] = (chrBankHi << 8) | data; chrActive = 0; break;
|
||||
|
||||
case 0x5128: chrBGBank[0] = (chrBankHi << 8) | data; chrActive = 1; break;
|
||||
case 0x5129: chrBGBank[1] = (chrBankHi << 8) | data; chrActive = 1; break;
|
||||
case 0x512a: chrBGBank[2] = (chrBankHi << 8) | data; chrActive = 1; break;
|
||||
case 0x512b: chrBGBank[3] = (chrBankHi << 8) | data; chrActive = 1; break;
|
||||
|
||||
case 0x5130:
|
||||
chrBankHi = data & 3;
|
||||
break;
|
||||
|
||||
case 0x5200:
|
||||
vsEnable = data & 0x80;
|
||||
vsSide = data & 0x40;
|
||||
vsTile = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x5201:
|
||||
vsScroll = data;
|
||||
break;
|
||||
|
||||
case 0x5202:
|
||||
vsBank = data;
|
||||
break;
|
||||
|
||||
case 0x5203:
|
||||
irqLine = data;
|
||||
break;
|
||||
|
||||
case 0x5204:
|
||||
irqEnable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0x5205:
|
||||
multiplicand = data;
|
||||
break;
|
||||
|
||||
case 0x5206:
|
||||
multiplier = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto chrSpriteAddr(uint addr) -> uint {
|
||||
if(chrMode == 0) {
|
||||
auto bank = chrSpriteBank[7];
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
if(chrMode == 1) {
|
||||
auto bank = chrSpriteBank[(addr / 0x1000) * 4 + 3];
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chrMode == 2) {
|
||||
auto bank = chrSpriteBank[(addr / 0x0800) * 2 + 1];
|
||||
return (bank * 0x0800) + (addr & 0x07ff);
|
||||
}
|
||||
|
||||
if(chrMode == 3) {
|
||||
auto bank = chrSpriteBank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chrBGAddr(uint addr) -> uint {
|
||||
addr &= 0x0fff;
|
||||
|
||||
if(chrMode == 0) {
|
||||
auto bank = chrBGBank[3];
|
||||
return (bank * 0x2000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chrMode == 1) {
|
||||
auto bank = chrBGBank[3];
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chrMode == 2) {
|
||||
auto bank = chrBGBank[(addr / 0x0800) * 2 + 1];
|
||||
return (bank * 0x0800) + (addr & 0x07ff);
|
||||
}
|
||||
|
||||
if(chrMode == 3) {
|
||||
auto bank = chrBGBank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chrVSAddr(uint addr) -> uint {
|
||||
return (vsBank * 0x1000) + (addr & 0x0ff8) + (vsVpos & 7);
|
||||
}
|
||||
|
||||
auto blank() -> void {
|
||||
inFrame = false;
|
||||
}
|
||||
|
||||
auto scanline() -> void {
|
||||
hcounter = 0;
|
||||
|
||||
if(inFrame == false) {
|
||||
inFrame = true;
|
||||
irqPending = false;
|
||||
vcounter = 0;
|
||||
} else {
|
||||
if(vcounter == irqLine) irqPending = true;
|
||||
vcounter++;
|
||||
}
|
||||
|
||||
cpuCycleCounter = 0;
|
||||
}
|
||||
|
||||
auto readCIRAM(uint addr) -> uint8 {
|
||||
if(vsFetch && (hcounter & 2) == 0) return exram[vsVpos / 8 * 32 + vsHpos / 8];
|
||||
if(vsFetch && (hcounter & 2) != 0) return exram[vsVpos / 32 * 8 + vsHpos / 32 + 0x03c0];
|
||||
|
||||
switch(nametableMode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.readCIRAM(0x0000 | (addr & 0x03ff));
|
||||
case 1: return ppu.readCIRAM(0x0400 | (addr & 0x03ff));
|
||||
case 2: return exramMode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
|
||||
case 3: return (hcounter & 2) == 0 ? fillmodeTile : fillmodeColor;
|
||||
}
|
||||
}
|
||||
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
chrAccess[0] = chrAccess[1];
|
||||
chrAccess[1] = chrAccess[2];
|
||||
chrAccess[2] = chrAccess[3];
|
||||
chrAccess[3] = addr;
|
||||
|
||||
//detect two unused nametable fetches at end of each scanline
|
||||
if((chrAccess[0] & 0x2000) == 0
|
||||
&& (chrAccess[1] & 0x2000)
|
||||
&& (chrAccess[2] & 0x2000)
|
||||
&& (chrAccess[3] & 0x2000)) scanline();
|
||||
|
||||
if(inFrame == false) {
|
||||
vsFetch = false;
|
||||
if(addr & 0x2000) return readCIRAM(addr);
|
||||
return board.chrrom.read(chrActive ? chrBGAddr(addr) : chrSpriteAddr(addr));
|
||||
}
|
||||
|
||||
bool bgFetch = (hcounter < 256 || hcounter >= 320);
|
||||
uint8 result = 0x00;
|
||||
|
||||
if((hcounter & 7) == 0) {
|
||||
vsHpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
|
||||
vsVpos = vcounter + vsScroll;
|
||||
vsFetch = vsEnable && bgFetch && exramMode < 2
|
||||
&& (vsSide ? vsHpos / 8 >= vsTile : vsHpos / 8 < vsTile);
|
||||
if(vsVpos >= 240) vsVpos -= 240;
|
||||
|
||||
result = readCIRAM(addr);
|
||||
|
||||
exbank = (chrBankHi << 6) | (exram[addr & 0x03ff] & 0x3f);
|
||||
exattr = exram[addr & 0x03ff] >> 6;
|
||||
exattr |= exattr << 2;
|
||||
exattr |= exattr << 4;
|
||||
} else if((hcounter & 7) == 2) {
|
||||
result = readCIRAM(addr);
|
||||
if(bgFetch && exramMode == 1) result = exattr;
|
||||
} else {
|
||||
if(vsFetch) result = board.chrrom.read(chrVSAddr(addr));
|
||||
else if(sprite8x16 ? bgFetch : chrActive) result = board.chrrom.read(chrBGAddr(addr));
|
||||
else result = board.chrrom.read(chrSpriteAddr(addr));
|
||||
if(bgFetch && exramMode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
|
||||
}
|
||||
|
||||
hcounter += 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
switch(nametableMode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.writeCIRAM(0x0000 | (addr & 0x03ff), data);
|
||||
case 1: return ppu.writeCIRAM(0x0400 | (addr & 0x03ff), data);
|
||||
case 2: exram[addr & 0x03ff] = data; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : exram) n = 0xff;
|
||||
|
||||
prgMode = 3;
|
||||
chrMode = 0;
|
||||
for(auto& n : prgramWriteProtect) n = 0;
|
||||
exramMode = 0;
|
||||
for(auto& n : nametableMode) n = 0;
|
||||
fillmodeTile = 0;
|
||||
fillmodeColor = 0;
|
||||
ramSelect = 0;
|
||||
ramBank = 0;
|
||||
prgBank[0] = 0x00;
|
||||
prgBank[1] = 0x00;
|
||||
prgBank[2] = 0x00;
|
||||
prgBank[3] = 0xff;
|
||||
for(auto& n : chrSpriteBank) n = 0;
|
||||
for(auto& n : chrBGBank) n = 0;
|
||||
chrBankHi = 0;
|
||||
vsEnable = 0;
|
||||
vsSide = 0;
|
||||
vsTile = 0;
|
||||
vsScroll = 0;
|
||||
vsBank = 0;
|
||||
irqLine = 0;
|
||||
irqEnable = 0;
|
||||
multiplicand = 0;
|
||||
multiplier = 0;
|
||||
|
||||
cpuCycleCounter = 0;
|
||||
irqCounter = 0;
|
||||
irqPending = 0;
|
||||
inFrame = 0;
|
||||
vcounter = 0;
|
||||
hcounter = 0;
|
||||
for(auto& n : chrAccess) n = 0;
|
||||
chrActive = 0;
|
||||
sprite8x16 = 0;
|
||||
|
||||
exbank = 0;
|
||||
exattr = 0;
|
||||
|
||||
vsFetch = 0;
|
||||
vsVpos = 0;
|
||||
vsHpos = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(exram);
|
||||
|
||||
s.integer(prgMode);
|
||||
s.integer(chrMode);
|
||||
for(auto& n : prgramWriteProtect) s.integer(n);
|
||||
s.integer(exramMode);
|
||||
for(auto& n : nametableMode) s.integer(n);
|
||||
s.integer(fillmodeTile);
|
||||
s.integer(fillmodeColor);
|
||||
s.integer(ramSelect);
|
||||
s.integer(ramBank);
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrSpriteBank) s.integer(n);
|
||||
for(auto& n : chrBGBank) s.integer(n);
|
||||
s.integer(chrBankHi);
|
||||
s.integer(vsEnable);
|
||||
s.integer(vsSide);
|
||||
s.integer(vsTile);
|
||||
s.integer(vsScroll);
|
||||
s.integer(vsBank);
|
||||
s.integer(irqLine);
|
||||
s.integer(irqEnable);
|
||||
s.integer(multiplicand);
|
||||
s.integer(multiplier);
|
||||
|
||||
s.integer(cpuCycleCounter);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqPending);
|
||||
s.integer(inFrame);
|
||||
|
||||
s.integer(vcounter);
|
||||
s.integer(hcounter);
|
||||
for(auto& n : chrAccess) s.integer(n);
|
||||
s.integer(chrActive);
|
||||
s.integer(sprite8x16);
|
||||
|
||||
s.integer(exbank);
|
||||
s.integer(exattr);
|
||||
|
||||
s.integer(vsFetch);
|
||||
s.integer(vsVpos);
|
||||
s.integer(vsHpos);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
MMC5,
|
||||
MMC5B,
|
||||
} revision;
|
||||
|
||||
uint8 exram[1024];
|
||||
|
||||
//programmable registers
|
||||
|
||||
uint2 prgMode; //$5100
|
||||
uint2 chrMode; //$5101
|
||||
|
||||
uint2 prgramWriteProtect[2]; //$5102,$5103
|
||||
|
||||
uint2 exramMode; //$5104
|
||||
uint2 nametableMode[4]; //$5105
|
||||
uint8 fillmodeTile; //$5106
|
||||
uint8 fillmodeColor; //$5107
|
||||
|
||||
bool ramSelect; //$5113
|
||||
uint2 ramBank; //$5113
|
||||
uint8 prgBank[4]; //$5114-5117
|
||||
uint10 chrSpriteBank[8]; //$5120-5127
|
||||
uint10 chrBGBank[4]; //$5128-512b
|
||||
uint2 chrBankHi; //$5130
|
||||
|
||||
bool vsEnable; //$5200
|
||||
bool vsSide; //$5200
|
||||
uint5 vsTile; //$5200
|
||||
uint8 vsScroll; //$5201
|
||||
uint8 vsBank; //$5202
|
||||
|
||||
uint8 irqLine; //$5203
|
||||
bool irqEnable; //$5204
|
||||
|
||||
uint8 multiplicand; //$5205
|
||||
uint8 multiplier; //$5206
|
||||
|
||||
//status registers
|
||||
|
||||
uint cpuCycleCounter;
|
||||
uint irqCounter;
|
||||
bool irqPending;
|
||||
bool inFrame;
|
||||
|
||||
uint vcounter;
|
||||
uint hcounter;
|
||||
uint16 chrAccess[4];
|
||||
bool chrActive;
|
||||
bool sprite8x16;
|
||||
|
||||
uint8 exbank;
|
||||
uint8 exattr;
|
||||
|
||||
bool vsFetch;
|
||||
uint8 vsVpos;
|
||||
uint8 vsHpos;
|
||||
};
|
||||
@@ -1,192 +0,0 @@
|
||||
struct MMC6 : Chip {
|
||||
MMC6(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqDelay) irqDelay--;
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irqTest(uint addr) -> void {
|
||||
if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
|
||||
if(irqDelay == 0) {
|
||||
if(irqCounter == 0) {
|
||||
irqCounter = irqLatch;
|
||||
} else if(--irqCounter == 0) {
|
||||
if(irqEnable) irqLine = 1;
|
||||
}
|
||||
}
|
||||
irqDelay = 6;
|
||||
}
|
||||
chrAbus = addr;
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 1:
|
||||
return (prgBank[1] << 13) | (addr & 0x1fff);
|
||||
case 2:
|
||||
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
if(chrMode == 0) {
|
||||
if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
|
||||
} else {
|
||||
if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
if(ramEnable == false) return cpu.mdr();
|
||||
if(ramReadable[0] == false && ramReadable[1] == false) return cpu.mdr();
|
||||
bool region = addr & 0x0200;
|
||||
if(ramReadable[region] == false) return 0x00;
|
||||
return board.prgram.read((region * 0x0200) + (addr & 0x01ff));
|
||||
}
|
||||
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(ramEnable == false) return;
|
||||
bool region = addr & 0x0200;
|
||||
if(ramWritable[region] == false) return;
|
||||
return board.prgram.write((region * 0x0200) + (addr & 0x01ff), data);
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xe001) {
|
||||
case 0x8000:
|
||||
chrMode = data & 0x80;
|
||||
prgMode = data & 0x40;
|
||||
ramEnable = data & 0x20;
|
||||
bankSelect = data & 0x07;
|
||||
if(ramEnable == false) {
|
||||
for(auto &n : ramReadable) n = false;
|
||||
for(auto &n : ramWritable) n = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x8001:
|
||||
switch(bankSelect) {
|
||||
case 0: chrBank[0] = data & ~1; break;
|
||||
case 1: chrBank[1] = data & ~1; break;
|
||||
case 2: chrBank[2] = data; break;
|
||||
case 3: chrBank[3] = data; break;
|
||||
case 4: chrBank[4] = data; break;
|
||||
case 5: chrBank[5] = data; break;
|
||||
case 6: prgBank[0] = data & 0x3f; break;
|
||||
case 7: prgBank[1] = data & 0x3f; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
if(ramEnable == false) break;
|
||||
ramReadable[1] = data & 0x80;
|
||||
ramWritable[1] = data & 0x40;
|
||||
ramReadable[0] = data & 0x20;
|
||||
ramWritable[0] = data & 0x10;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irqCounter = 0;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
irqEnable = false;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xe001:
|
||||
irqEnable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chrMode = 0;
|
||||
prgMode = 0;
|
||||
ramEnable = 0;
|
||||
bankSelect = 0;
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
for(auto& n : ramReadable) n = 0;
|
||||
for(auto& n : ramWritable) n = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter = 0;
|
||||
irqEnable = 0;
|
||||
irqDelay = 0;
|
||||
irqLine = 0;
|
||||
|
||||
chrAbus = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(chrMode);
|
||||
s.integer(prgMode);
|
||||
s.integer(ramEnable);
|
||||
s.integer(bankSelect);
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrBank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
for(auto& n : ramReadable) s.integer(n);
|
||||
for(auto& n : ramWritable) s.integer(n);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqDelay);
|
||||
s.integer(irqLine);
|
||||
|
||||
s.integer(chrAbus);
|
||||
}
|
||||
|
||||
bool chrMode;
|
||||
bool prgMode;
|
||||
bool ramEnable;
|
||||
uint3 bankSelect;
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[6];
|
||||
bool mirror;
|
||||
bool ramReadable[2];
|
||||
bool ramWritable[2];
|
||||
uint8 irqLatch;
|
||||
uint8 irqCounter;
|
||||
bool irqEnable;
|
||||
uint irqDelay;
|
||||
bool irqLine;
|
||||
|
||||
uint16 chrAbus;
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
struct VRC1 : Chip {
|
||||
VRC1(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0x0f;
|
||||
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
|
||||
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
|
||||
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBankLo[(bool)(addr & 0x1000)];
|
||||
bank |= chrBankHi[(bool)(addr & 0x1000)] << 4;
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xf000) {
|
||||
case 0x8000:
|
||||
prgBank[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
chrBankHi[1] = data & 0x04;
|
||||
chrBankHi[0] = data & 0x02;
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
prgBank[1] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
prgBank[2] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
chrBankLo[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
chrBankLo[1] = data & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBankLo) n = 0;
|
||||
for(auto& n : chrBankHi) n = 0;
|
||||
mirror = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrBankLo) s.integer(n);
|
||||
for(auto& n : chrBankHi) s.integer(n);
|
||||
s.integer(mirror);
|
||||
}
|
||||
|
||||
uint4 prgBank[3];
|
||||
uint4 chrBankLo[2];
|
||||
bool chrBankHi[2];
|
||||
bool mirror;
|
||||
};
|
||||
@@ -1,108 +0,0 @@
|
||||
struct VRC2 : Chip {
|
||||
VRC2(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prgBank[0]; break;
|
||||
case 0xa000: bank = prgBank[1]; break;
|
||||
case 0xc000: bank = 0x1e; break;
|
||||
case 0xe000: bank = 0x1f; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
if(board.prgram.size == 0) {
|
||||
if((addr & 0xf000) == 0x6000) return cpu.mdr() | latch;
|
||||
return cpu.mdr();
|
||||
}
|
||||
return board.prgram.read(addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(board.prgram.size == 0) {
|
||||
if((addr & 0xf000) == 0x6000) latch = data & 0x01;
|
||||
return;
|
||||
}
|
||||
return board.prgram.write(addr & 0x1fff, data);
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prgBank[0] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x9000: case 0x9001: case 0x9002: case 0x9003:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
|
||||
prgBank[1] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0xb000: chrBank[0] = (chrBank[0] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb001: chrBank[0] = (chrBank[0] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xb002: chrBank[1] = (chrBank[1] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb003: chrBank[1] = (chrBank[1] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc000: chrBank[2] = (chrBank[2] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc001: chrBank[2] = (chrBank[2] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc002: chrBank[3] = (chrBank[3] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc003: chrBank[3] = (chrBank[3] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd000: chrBank[4] = (chrBank[4] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd001: chrBank[4] = (chrBank[4] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd002: chrBank[5] = (chrBank[5] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd003: chrBank[5] = (chrBank[5] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe000: chrBank[6] = (chrBank[6] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe001: chrBank[6] = (chrBank[6] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe002: chrBank[7] = (chrBank[7] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe003: chrBank[7] = (chrBank[7] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
latch = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrBank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
s.integer(latch);
|
||||
}
|
||||
|
||||
uint5 prgBank[2];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
bool latch;
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
struct VRC3 : Chip {
|
||||
VRC3(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) { //16-bit
|
||||
if(++irqCounter.w == 0) {
|
||||
irqLine = 1;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqCounter.w = irqLatch;
|
||||
}
|
||||
}
|
||||
if(irqMode == 1) { //8-bit
|
||||
if(++irqCounter.l == 0) {
|
||||
irqLine = 1;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqCounter.l = irqLatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = (addr < 0xc000 ? (uint)prgBank : 0x0f);
|
||||
return (bank * 0x4000) + (addr & 0x3fff);
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xf000) {
|
||||
case 0x8000: irqLatch = (irqLatch & 0xfff0) | ((data & 0x0f) << 0); break;
|
||||
case 0x9000: irqLatch = (irqLatch & 0xff0f) | ((data & 0x0f) << 4); break;
|
||||
case 0xa000: irqLatch = (irqLatch & 0xf0ff) | ((data & 0x0f) << 8); break;
|
||||
case 0xb000: irqLatch = (irqLatch & 0x0fff) | ((data & 0x0f) << 12); break;
|
||||
|
||||
case 0xc000:
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) irqCounter.w = irqLatch;
|
||||
break;
|
||||
|
||||
case 0xd000:
|
||||
irqLine = 0;
|
||||
irqEnable = irqAcknowledge;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
prgBank = data & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgBank = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter.w = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(prgBank);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqCounter.w);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint4 prgBank;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
uint16 irqLatch;
|
||||
struct {
|
||||
union {
|
||||
uint16_t w;
|
||||
struct { uint8_t order_lsb2(l, h); };
|
||||
};
|
||||
} irqCounter;
|
||||
bool irqLine;
|
||||
};
|
||||
@@ -1,176 +0,0 @@
|
||||
struct VRC4 : Chip {
|
||||
VRC4(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0, banks = board.prgrom.size / 0x2000;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prgMode == 0 ? (uint)prgBank[0] : banks - 2; break;
|
||||
case 0xa000: bank = prgBank[1]; break;
|
||||
case 0xc000: bank = prgMode == 0 ? banks - 2 : (uint)prgBank[0]; break;
|
||||
case 0xe000: bank = banks - 1; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prgBank[0] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x9000: case 0x9001:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0x9002: case 0x9003:
|
||||
prgMode = data & 0x02;
|
||||
break;
|
||||
|
||||
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
|
||||
prgBank[1] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0xb000: chrBank[0] = (chrBank[0] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb001: chrBank[0] = (chrBank[0] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xb002: chrBank[1] = (chrBank[1] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb003: chrBank[1] = (chrBank[1] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc000: chrBank[2] = (chrBank[2] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc001: chrBank[2] = (chrBank[2] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc002: chrBank[3] = (chrBank[3] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc003: chrBank[3] = (chrBank[3] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd000: chrBank[4] = (chrBank[4] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd001: chrBank[4] = (chrBank[4] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd002: chrBank[5] = (chrBank[5] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd003: chrBank[5] = (chrBank[5] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe000: chrBank[6] = (chrBank[6] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe001: chrBank[6] = (chrBank[6] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe002: chrBank[7] = (chrBank[7] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe003: chrBank[7] = (chrBank[7] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xf000:
|
||||
irqLatch = (irqLatch & 0xf0) | ((data & 0x0f) << 0);
|
||||
break;
|
||||
|
||||
case 0xf001:
|
||||
irqLatch = (irqLatch & 0x0f) | ((data & 0x0f) << 4);
|
||||
break;
|
||||
|
||||
case 0xf002:
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf003:
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgMode = 0;
|
||||
for(auto& n : prgBank) n = 0;
|
||||
mirror = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(prgMode);
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
for(auto& n : chrBank) s.integer(n);
|
||||
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
bool prgMode;
|
||||
uint5 prgBank[2];
|
||||
uint2 mirror;
|
||||
uint8 chrBank[8];
|
||||
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irqCounter;
|
||||
int irqScalar;
|
||||
bool irqLine;
|
||||
};
|
||||
@@ -1,313 +0,0 @@
|
||||
struct VRC6 : Chip {
|
||||
VRC6(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
struct Pulse {
|
||||
auto clock() -> void {
|
||||
if(--divider == 0) {
|
||||
divider = frequency + 1;
|
||||
cycle++;
|
||||
output = (mode == 1 || cycle > duty) ? volume : (uint4)0;
|
||||
}
|
||||
|
||||
if(enable == false) output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(mode);
|
||||
s.integer(duty);
|
||||
s.integer(volume);
|
||||
s.integer(enable);
|
||||
s.integer(frequency);
|
||||
|
||||
s.integer(divider);
|
||||
s.integer(cycle);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
bool mode;
|
||||
uint3 duty;
|
||||
uint4 volume;
|
||||
bool enable;
|
||||
uint12 frequency;
|
||||
|
||||
uint12 divider;
|
||||
uint4 cycle;
|
||||
uint4 output;
|
||||
} pulse1, pulse2;
|
||||
|
||||
struct Sawtooth {
|
||||
auto clock() -> void {
|
||||
if(--divider == 0) {
|
||||
divider = frequency + 1;
|
||||
if(++phase == 0) {
|
||||
accumulator += rate;
|
||||
if(++stage == 7) {
|
||||
stage = 0;
|
||||
accumulator = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output = accumulator >> 3;
|
||||
if(enable == false) output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(rate);
|
||||
s.integer(enable);
|
||||
s.integer(frequency);
|
||||
|
||||
s.integer(divider);
|
||||
s.integer(phase);
|
||||
s.integer(stage);
|
||||
s.integer(accumulator);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
uint6 rate;
|
||||
bool enable;
|
||||
uint12 frequency;
|
||||
|
||||
uint12 divider;
|
||||
uint1 phase;
|
||||
uint3 stage;
|
||||
uint8 accumulator;
|
||||
uint5 output;
|
||||
} sawtooth;
|
||||
|
||||
auto main() -> void {
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.irqLine(irqLine);
|
||||
|
||||
pulse1.clock();
|
||||
pulse2.clock();
|
||||
sawtooth.clock();
|
||||
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
|
||||
apu.setSample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
if((addr & 0xc000) == 0x8000) return (prgBank[0] << 14) | (addr & 0x3fff);
|
||||
if((addr & 0xe000) == 0xc000) return (prgBank[1] << 13) | (addr & 0x1fff);
|
||||
if((addr & 0xe000) == 0xe000) return ( 0xff << 13) | (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[(addr >> 10) & 7];
|
||||
return (bank << 10) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
}
|
||||
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
return board.prgram.data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
board.prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prgBank[0] = data;
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
pulse1.mode = data & 0x80;
|
||||
pulse1.duty = (data & 0x70) >> 4;
|
||||
pulse1.volume = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x9001:
|
||||
pulse1.frequency = (pulse1.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0x9002:
|
||||
pulse1.frequency = (pulse1.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
pulse1.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
pulse2.mode = data & 0x80;
|
||||
pulse2.duty = (data & 0x70) >> 4;
|
||||
pulse2.volume = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
pulse2.frequency = (pulse2.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0xa002:
|
||||
pulse2.frequency = (pulse2.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
pulse2.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xb000:
|
||||
sawtooth.rate = data & 0x3f;
|
||||
break;
|
||||
|
||||
case 0xb001:
|
||||
sawtooth.frequency = (sawtooth.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0xb002:
|
||||
sawtooth.frequency = (sawtooth.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
sawtooth.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xb003:
|
||||
mirror = (data >> 2) & 3;
|
||||
break;
|
||||
|
||||
case 0xc000: case 0xc001: case 0xc002: case 0xc003:
|
||||
prgBank[1] = data;
|
||||
break;
|
||||
|
||||
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
|
||||
chrBank[0 + (addr & 3)] = data;
|
||||
break;
|
||||
|
||||
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
|
||||
chrBank[4 + (addr & 3)] = data;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xf001:
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf002:
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prgBank[0] = 0;
|
||||
prgBank[1] = 0;
|
||||
chrBank[0] = 0;
|
||||
chrBank[1] = 0;
|
||||
chrBank[2] = 0;
|
||||
chrBank[3] = 0;
|
||||
chrBank[4] = 0;
|
||||
chrBank[5] = 0;
|
||||
chrBank[6] = 0;
|
||||
chrBank[7] = 0;
|
||||
mirror = 0;
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
|
||||
pulse1.mode = 0;
|
||||
pulse1.duty = 0;
|
||||
pulse1.volume = 0;
|
||||
pulse1.enable = 0;
|
||||
pulse1.frequency = 0;
|
||||
|
||||
pulse1.divider = 1;
|
||||
pulse1.cycle = 0;
|
||||
pulse1.output = 0;
|
||||
|
||||
pulse2.mode = 0;
|
||||
pulse2.duty = 0;
|
||||
pulse2.volume = 0;
|
||||
pulse2.enable = 0;
|
||||
pulse2.frequency = 0;
|
||||
|
||||
pulse2.divider = 1;
|
||||
pulse2.cycle = 0;
|
||||
pulse2.output = 0;
|
||||
|
||||
sawtooth.rate = 0;
|
||||
sawtooth.enable = 0;
|
||||
sawtooth.frequency = 0;
|
||||
|
||||
sawtooth.divider = 1;
|
||||
sawtooth.phase = 0;
|
||||
sawtooth.stage = 0;
|
||||
sawtooth.accumulator = 0;
|
||||
sawtooth.output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
pulse1.serialize(s);
|
||||
pulse2.serialize(s);
|
||||
sawtooth.serialize(s);
|
||||
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irqCounter;
|
||||
int irqScalar;
|
||||
bool irqLine;
|
||||
};
|
||||
@@ -1,146 +0,0 @@
|
||||
//Konami VRC7
|
||||
//Yamaha YM2413 OPLL audio - not emulated
|
||||
|
||||
struct VRC7 : Chip {
|
||||
VRC7(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.irqLine(irqLine);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: prgBank[0] = data; break;
|
||||
case 0x8010: prgBank[1] = data; break;
|
||||
case 0x9000: prgBank[2] = data; break;
|
||||
case 0x9010: break; //APU addr port
|
||||
case 0x9030: break; //APU data port
|
||||
case 0xa000: chrBank[0] = data; break;
|
||||
case 0xa010: chrBank[1] = data; break;
|
||||
case 0xb000: chrBank[2] = data; break;
|
||||
case 0xb010: chrBank[3] = data; break;
|
||||
case 0xc000: chrBank[4] = data; break;
|
||||
case 0xc010: chrBank[5] = data; break;
|
||||
case 0xd000: chrBank[6] = data; break;
|
||||
case 0xd010: chrBank[7] = data; break;
|
||||
case 0xe000: mirror = data & 0x03; break;
|
||||
|
||||
case 0xe010:
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf010:
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prgBank[0]; break;
|
||||
case 0xa000: bank = prgBank[1]; break;
|
||||
case 0xc000: bank = prgBank[2]; break;
|
||||
case 0xe000: bank = 0xff; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint8 prgBank[3];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irqCounter;
|
||||
int irqScalar;
|
||||
bool irqLine;
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
Cheat cheat;
|
||||
|
||||
auto Cheat::reset() -> void {
|
||||
codes.reset();
|
||||
}
|
||||
|
||||
auto Cheat::append(uint addr, uint data) -> void {
|
||||
codes.append({addr, Unused, data});
|
||||
}
|
||||
|
||||
auto Cheat::append(uint addr, uint comp, uint data) -> void {
|
||||
codes.append({addr, comp, data});
|
||||
}
|
||||
|
||||
auto Cheat::find(uint addr, uint comp) -> maybe<uint> {
|
||||
for(auto& code : codes) {
|
||||
if(code.addr == addr && (code.comp == Unused || code.comp == comp)) {
|
||||
return code.data;
|
||||
}
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
struct Cheat {
|
||||
struct Code {
|
||||
uint addr;
|
||||
uint comp;
|
||||
uint data;
|
||||
};
|
||||
vector<Code> codes;
|
||||
enum : uint { Unused = ~0u };
|
||||
|
||||
alwaysinline auto enable() const -> bool { return codes.size() > 0; }
|
||||
auto reset() -> void;
|
||||
auto append(uint addr, uint data) -> void;
|
||||
auto append(uint addr, uint comp, uint data) -> void;
|
||||
auto find(uint addr, uint comp) -> maybe<uint>;
|
||||
};
|
||||
|
||||
extern Cheat cheat;
|
||||
@@ -1,26 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "gamepad/gamepad.cpp"
|
||||
|
||||
Controller::Controller(bool port) : port(port) {
|
||||
if(!thread) create(Controller::Enter, 1);
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
}
|
||||
|
||||
auto Controller::Enter() -> void {
|
||||
while(true) {
|
||||
scheduler.synchronize();
|
||||
if(co_active() == peripherals.controllerPort1->thread) peripherals.controllerPort1->main();
|
||||
if(co_active() == peripherals.controllerPort2->thread) peripherals.controllerPort2->main();
|
||||
}
|
||||
}
|
||||
|
||||
auto Controller::main() -> void {
|
||||
step(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//Famicom controller port pinout:
|
||||
// ____
|
||||
// | \
|
||||
// |(7) \
|
||||
// |(2)(1)|
|
||||
// |(3)(5)|
|
||||
// |(4)(6)|
|
||||
// |______|
|
||||
//
|
||||
// pin name port1 port2
|
||||
// 1: +5v
|
||||
// 2: clock $4016 read $4016.d0 write
|
||||
// 3: latch $4016.d0 write $4016.d0 write
|
||||
// 4: data0 $4016.d0 read $4017.d0 read
|
||||
// 5: data3 $4016.d3 read $4017.d3 read
|
||||
// 6: data4 $4016.d4 read $4017.d4 read
|
||||
// 7: gnd
|
||||
|
||||
struct Controller : Cothread {
|
||||
enum : bool { Port1 = 0, Port2 = 1 };
|
||||
|
||||
Controller(bool port);
|
||||
virtual ~Controller();
|
||||
static auto Enter() -> void;
|
||||
|
||||
virtual auto main() -> void;
|
||||
virtual auto data() -> uint3 { return 0; }
|
||||
virtual auto latch(bool data) -> void {}
|
||||
|
||||
const bool port;
|
||||
};
|
||||
|
||||
#include "gamepad/gamepad.hpp"
|
||||
@@ -1,36 +0,0 @@
|
||||
Gamepad::Gamepad(bool port) : Controller(port) {
|
||||
}
|
||||
|
||||
auto Gamepad::data() -> uint3 {
|
||||
if(counter >= 8) return 1;
|
||||
if(latched == 1) return interface->inputPoll(port, ID::Device::Gamepad, A);
|
||||
|
||||
switch(counter++) {
|
||||
case 0: return a;
|
||||
case 1: return b;
|
||||
case 2: return select;
|
||||
case 3: return start;
|
||||
case 4: return up && !down;
|
||||
case 5: return down && !up;
|
||||
case 6: return left && !right;
|
||||
case 7: return right && !left;
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto Gamepad::latch(bool data) -> void {
|
||||
if(latched == data) return;
|
||||
latched = data;
|
||||
counter = 0;
|
||||
|
||||
if(latched == 0) {
|
||||
a = interface->inputPoll(port, ID::Device::Gamepad, A);
|
||||
b = interface->inputPoll(port, ID::Device::Gamepad, B);
|
||||
select = interface->inputPoll(port, ID::Device::Gamepad, Select);
|
||||
start = interface->inputPoll(port, ID::Device::Gamepad, Start);
|
||||
up = interface->inputPoll(port, ID::Device::Gamepad, Up);
|
||||
down = interface->inputPoll(port, ID::Device::Gamepad, Down);
|
||||
left = interface->inputPoll(port, ID::Device::Gamepad, Left);
|
||||
right = interface->inputPoll(port, ID::Device::Gamepad, Right);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
struct Gamepad : Controller {
|
||||
enum : uint {
|
||||
Up, Down, Left, Right, B, A, Select, Start,
|
||||
};
|
||||
|
||||
Gamepad(bool port);
|
||||
auto data() -> uint3;
|
||||
auto latch(bool data) -> void;
|
||||
|
||||
private:
|
||||
bool latched = 0;
|
||||
uint counter = 0;
|
||||
|
||||
bool a = 0;
|
||||
bool b = 0;
|
||||
bool select = 0;
|
||||
bool start = 0;
|
||||
bool up = 0;
|
||||
bool down = 0;
|
||||
bool left = 0;
|
||||
bool right = 0;
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "memory.cpp"
|
||||
#include "timing.cpp"
|
||||
#include "serialization.cpp"
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(io.interruptPending) return interrupt();
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::step(uint clocks) -> void {
|
||||
apu.clock -= clocks;
|
||||
if(apu.clock < 0) co_switch(apu.thread);
|
||||
|
||||
ppu.clock -= clocks;
|
||||
if(ppu.clock < 0) co_switch(ppu.thread);
|
||||
|
||||
cartridge.clock -= clocks;
|
||||
if(cartridge.clock < 0) co_switch(cartridge.thread);
|
||||
|
||||
for(auto peripheral : peripherals) {
|
||||
peripheral->clock -= clocks * (uint64)peripheral->frequency;
|
||||
if(peripheral->clock < 0) co_switch(peripheral->thread);
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
R6502::power();
|
||||
|
||||
for(auto addr : range(0x0800)) ram[addr] = 0xff;
|
||||
ram[0x0008] = 0xf7;
|
||||
ram[0x0009] = 0xef;
|
||||
ram[0x000a] = 0xdf;
|
||||
ram[0x000f] = 0xbf;
|
||||
}
|
||||
|
||||
auto CPU::reset() -> void {
|
||||
R6502::reset();
|
||||
create(CPU::Enter, 21'477'272);
|
||||
|
||||
regs.pc = bus.read(0xfffc) << 0;
|
||||
regs.pc |= bus.read(0xfffd) << 8;
|
||||
|
||||
memory::fill(&io, sizeof(IO));
|
||||
io.rdyLine = 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
struct CPU : Processor::R6502, Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto readRAM(uint11 addr) -> uint8;
|
||||
auto writeRAM(uint11 addr, uint8 data) -> void;
|
||||
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto readDebugger(uint16 addr) -> uint8 override;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//timing.cpp
|
||||
auto read(uint16 addr) -> uint8 override;
|
||||
auto write(uint16 addr, uint8 data) -> void override;
|
||||
auto lastCycle() -> void override;
|
||||
auto nmi(uint16& vector) -> void override;
|
||||
|
||||
auto oamdma() -> void;
|
||||
|
||||
auto nmiLine(bool) -> void;
|
||||
auto irqLine(bool) -> void;
|
||||
auto apuLine(bool) -> void;
|
||||
|
||||
auto rdyLine(bool) -> void;
|
||||
auto rdyAddr(bool valid, uint16 value = 0) -> void;
|
||||
|
||||
//protected:
|
||||
vector<Thread*> peripherals;
|
||||
|
||||
uint8 ram[0x0800];
|
||||
|
||||
struct IO {
|
||||
bool interruptPending;
|
||||
bool nmiPending;
|
||||
bool nmiLine;
|
||||
bool irqLine;
|
||||
bool apuLine;
|
||||
|
||||
bool rdyLine;
|
||||
bool rdyAddrValid;
|
||||
uint16 rdyAddrValue;
|
||||
|
||||
bool oamdmaPending;
|
||||
uint8 oamdmaPage;
|
||||
} io;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
@@ -1,49 +0,0 @@
|
||||
auto CPU::readRAM(uint11 addr) -> uint8 {
|
||||
return ram[addr];
|
||||
}
|
||||
|
||||
auto CPU::writeRAM(uint11 addr, uint8 data) -> void {
|
||||
ram[addr] = data;
|
||||
}
|
||||
|
||||
auto CPU::readIO(uint16 addr) -> uint8 {
|
||||
switch(addr) {
|
||||
|
||||
case 0x4016: {
|
||||
auto data = Famicom::peripherals.controllerPort1->data();
|
||||
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
|
||||
}
|
||||
|
||||
case 0x4017: {
|
||||
auto data = Famicom::peripherals.controllerPort2->data();
|
||||
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return apu.readIO(addr);
|
||||
}
|
||||
|
||||
auto CPU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
|
||||
case 0x4014: {
|
||||
io.oamdmaPage = data;
|
||||
io.oamdmaPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4016: {
|
||||
Famicom::peripherals.controllerPort1->latch(data.bit(0));
|
||||
Famicom::peripherals.controllerPort2->latch(data.bit(0));
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return apu.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto CPU::readDebugger(uint16 addr) -> uint8 {
|
||||
return bus.read(addr);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
auto CPU::serialize(serializer& s) -> void {
|
||||
R6502::serialize(s);
|
||||
Thread::serialize(s);
|
||||
|
||||
s.array(ram);
|
||||
|
||||
s.integer(io.interruptPending);
|
||||
s.integer(io.nmiPending);
|
||||
s.integer(io.nmiLine);
|
||||
s.integer(io.irqLine);
|
||||
s.integer(io.apuLine);
|
||||
|
||||
s.integer(io.rdyLine);
|
||||
s.integer(io.rdyAddrValid);
|
||||
s.integer(io.rdyAddrValue);
|
||||
|
||||
s.integer(io.oamdmaPending);
|
||||
s.integer(io.oamdmaPage);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
auto CPU::read(uint16 addr) -> uint8 {
|
||||
if(io.oamdmaPending) {
|
||||
io.oamdmaPending = false;
|
||||
read(addr);
|
||||
oamdma();
|
||||
}
|
||||
|
||||
while(io.rdyLine == 0) {
|
||||
regs.mdr = bus.read(io.rdyAddrValid ? io.rdyAddrValue : addr);
|
||||
step(12);
|
||||
}
|
||||
|
||||
regs.mdr = bus.read(addr);
|
||||
step(12);
|
||||
return regs.mdr;
|
||||
}
|
||||
|
||||
auto CPU::write(uint16 addr, uint8 data) -> void {
|
||||
bus.write(addr, regs.mdr = data);
|
||||
step(12);
|
||||
}
|
||||
|
||||
auto CPU::lastCycle() -> void {
|
||||
io.interruptPending = ((io.irqLine | io.apuLine) & ~regs.p.i) | io.nmiPending;
|
||||
}
|
||||
|
||||
auto CPU::nmi(uint16& vector) -> void {
|
||||
if(io.nmiPending) {
|
||||
io.nmiPending = false;
|
||||
vector = 0xfffa;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::oamdma() -> void {
|
||||
for(uint n : range(256)) {
|
||||
uint8 data = read(io.oamdmaPage << 8 | n);
|
||||
write(0x2004, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::nmiLine(bool line) -> void {
|
||||
//edge-sensitive (0->1)
|
||||
if(!io.nmiLine && line) io.nmiPending = true;
|
||||
io.nmiLine = line;
|
||||
}
|
||||
|
||||
auto CPU::irqLine(bool line) -> void {
|
||||
//level-sensitive
|
||||
io.irqLine = line;
|
||||
}
|
||||
|
||||
auto CPU::apuLine(bool line) -> void {
|
||||
//level-sensitive
|
||||
io.apuLine = line;
|
||||
}
|
||||
|
||||
auto CPU::rdyLine(bool line) -> void {
|
||||
io.rdyLine = line;
|
||||
}
|
||||
|
||||
auto CPU::rdyAddr(bool valid, uint16 value) -> void {
|
||||
io.rdyAddrValid = valid;
|
||||
io.rdyAddrValue = value;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
//license: GPLv3
|
||||
//started: 2011-09-05
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <processor/r6502/r6502.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
using File = Emulator::File;
|
||||
|
||||
struct Thread {
|
||||
~Thread() {
|
||||
if(thread) co_delete(thread);
|
||||
}
|
||||
|
||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||
if(thread) co_delete(thread);
|
||||
thread = co_create(65'536 * sizeof(void*), entrypoint);
|
||||
this->frequency = frequency;
|
||||
clock = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(frequency);
|
||||
s.integer(clock);
|
||||
}
|
||||
|
||||
cothread_t thread = nullptr;
|
||||
uint frequency = 0;
|
||||
int64 clock = 0;
|
||||
};
|
||||
|
||||
struct Cothread : Thread {
|
||||
auto step(uint clocks) -> void;
|
||||
auto synchronizeCPU() -> void;
|
||||
};
|
||||
|
||||
#include <fc/scheduler/scheduler.hpp>
|
||||
#include <fc/controller/controller.hpp>
|
||||
#include <fc/system/system.hpp>
|
||||
#include <fc/memory/memory.hpp>
|
||||
#include <fc/cartridge/cartridge.hpp>
|
||||
#include <fc/cpu/cpu.hpp>
|
||||
#include <fc/apu/apu.hpp>
|
||||
#include <fc/ppu/ppu.hpp>
|
||||
#include <fc/cheat/cheat.hpp>
|
||||
|
||||
inline auto Cothread::step(uint clocks) -> void {
|
||||
clock += clocks * (uint64)cpu.frequency;
|
||||
synchronizeCPU();
|
||||
}
|
||||
|
||||
inline auto Cothread::synchronizeCPU() -> void {
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
}
|
||||
|
||||
#include <fc/interface/interface.hpp>
|
||||
@@ -1,200 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
Settings settings;
|
||||
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Famicom";
|
||||
information.width = 256;
|
||||
information.height = 240;
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 8.0 / 7.0;
|
||||
information.resettable = true;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
media.append({ID::Famicom, "Famicom", "fc"});
|
||||
|
||||
Port controllerPort1{ID::Port::Controller1, "Controller Port 1"};
|
||||
Port controllerPort2{ID::Port::Controller2, "Controller Port 2"};
|
||||
|
||||
{ Device device{ID::Device::None, "None"};
|
||||
controllerPort1.devices.append(device);
|
||||
controllerPort2.devices.append(device);
|
||||
}
|
||||
|
||||
{ Device device{ID::Device::Gamepad, "Gamepad"};
|
||||
device.inputs.append({0, "Up" });
|
||||
device.inputs.append({0, "Down" });
|
||||
device.inputs.append({0, "Left" });
|
||||
device.inputs.append({0, "Right" });
|
||||
device.inputs.append({0, "B" });
|
||||
device.inputs.append({0, "A" });
|
||||
device.inputs.append({0, "Select"});
|
||||
device.inputs.append({0, "Start" });
|
||||
controllerPort1.devices.append(device);
|
||||
controllerPort2.devices.append(device);
|
||||
}
|
||||
|
||||
ports.append(move(controllerPort1));
|
||||
ports.append(move(controllerPort2));
|
||||
}
|
||||
|
||||
auto Interface::manifest() -> string {
|
||||
return cartridge.manifest();
|
||||
}
|
||||
|
||||
auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoFrequency() -> double {
|
||||
return 21477272.0 / (262.0 * 1364.0 - 4.0);
|
||||
}
|
||||
|
||||
auto Interface::videoColors() -> uint32 {
|
||||
return 1 << 9;
|
||||
}
|
||||
|
||||
auto Interface::videoColor(uint32 n) -> uint64 {
|
||||
double saturation = 2.0;
|
||||
double hue = 0.0;
|
||||
double contrast = 1.0;
|
||||
double brightness = 1.0;
|
||||
double gamma = settings.colorEmulation ? 1.8 : 2.2;
|
||||
|
||||
int color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
|
||||
|
||||
static const double black = 0.518, white = 1.962, attenuation = 0.746;
|
||||
static const double levels[8] = {
|
||||
0.350, 0.518, 0.962, 1.550,
|
||||
1.094, 1.506, 1.962, 1.962,
|
||||
};
|
||||
|
||||
double lo_and_hi[2] = {
|
||||
levels[level + 4 * (color == 0x0)],
|
||||
levels[level + 4 * (color < 0xd)],
|
||||
};
|
||||
|
||||
double y = 0.0, i = 0.0, q = 0.0;
|
||||
auto wave = [](int p, int color) { return (color + p + 8) % 12 < 6; };
|
||||
for(int p : range(12)) {
|
||||
double spot = lo_and_hi[wave(p, color)];
|
||||
|
||||
if(((n & 0x040) && wave(p, 12))
|
||||
|| ((n & 0x080) && wave(p, 4))
|
||||
|| ((n & 0x100) && wave(p, 8))
|
||||
) spot *= attenuation;
|
||||
|
||||
double v = (spot - black) / (white - black);
|
||||
|
||||
v = (v - 0.5) * contrast + 0.5;
|
||||
v *= brightness / 12.0;
|
||||
|
||||
y += v;
|
||||
i += v * cos((Math::Pi / 6.0) * (p + hue));
|
||||
q += v * sin((Math::Pi / 6.0) * (p + hue));
|
||||
}
|
||||
|
||||
i *= saturation;
|
||||
q *= saturation;
|
||||
|
||||
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : pow(f, 2.2 / gamma); };
|
||||
uint64 r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
|
||||
uint64 g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
|
||||
uint64 b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q));
|
||||
|
||||
return r << 32 | g << 16 | b << 0;
|
||||
}
|
||||
|
||||
auto Interface::audioFrequency() -> double {
|
||||
return 21477272.0 / 12.0;
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::sha256() -> string {
|
||||
return cartridge.sha256();
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> bool {
|
||||
return system.load();
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
system.save();
|
||||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::connect(uint port, uint device) -> void {
|
||||
Famicom::peripherals.connect(port, device);
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
system.power();
|
||||
}
|
||||
|
||||
auto Interface::reset() -> void {
|
||||
system.reset();
|
||||
}
|
||||
|
||||
auto Interface::run() -> void {
|
||||
system.run();
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
system.runToSave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
auto Interface::unserialize(serializer& s) -> bool {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
auto Interface::cheatSet(const string_vector& list) -> void {
|
||||
cheat.reset();
|
||||
for(auto& codeset : list) {
|
||||
auto codes = codeset.split("+");
|
||||
for(auto& code : codes) {
|
||||
auto part = code.split("/");
|
||||
if(part.size() == 2) cheat.append(part[0].hex(), part[1].hex());
|
||||
if(part.size() == 3) cheat.append(part[0].hex(), part[1].hex(), part[2].hex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::cap(const string& name) -> bool {
|
||||
if(name == "Color Emulation") return true;
|
||||
if(name == "Scanline Emulation") return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Interface::get(const string& name) -> any {
|
||||
if(name == "Color Emulation") return settings.colorEmulation;
|
||||
if(name == "Scanline Emulation") return settings.scanlineEmulation;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
namespace Famicom {
|
||||
|
||||
struct ID {
|
||||
enum : uint {
|
||||
System,
|
||||
Famicom,
|
||||
};
|
||||
|
||||
struct Port { enum : uint {
|
||||
Controller1,
|
||||
Controller2,
|
||||
Expansion,
|
||||
};};
|
||||
|
||||
struct Device { enum : uint {
|
||||
None,
|
||||
Gamepad,
|
||||
};};
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
Interface();
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
auto videoFrequency() -> double override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto sha256() -> string override;
|
||||
auto load(uint id) -> bool override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
auto connect(uint port, uint device) -> void override;
|
||||
auto power() -> void override;
|
||||
auto reset() -> void override;
|
||||
auto run() -> void override;
|
||||
|
||||
auto serialize() -> serializer override;
|
||||
auto unserialize(serializer&) -> bool override;
|
||||
|
||||
auto cheatSet(const string_vector&) -> void override;
|
||||
|
||||
auto cap(const string& name) -> bool override;
|
||||
auto get(const string& name) -> any override;
|
||||
auto set(const string& name, const any& value) -> bool override;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
bool colorEmulation = true;
|
||||
bool scanlineEmulation = true;
|
||||
|
||||
uint controllerPort1 = 0;
|
||||
uint controllerPort2 = 0;
|
||||
uint expansionPort = 0;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
Bus bus;
|
||||
|
||||
//$0000-07ff = RAM (2KB)
|
||||
//$0800-1fff = RAM (mirror)
|
||||
//$2000-2007 = PPU
|
||||
//$2008-3fff = PPU (mirror)
|
||||
//$4000-4017 = APU + I/O
|
||||
//$4018-ffff = Cartridge
|
||||
|
||||
auto Bus::read(uint16 addr) -> uint8 {
|
||||
uint8 data = cartridge.readPRG(addr);
|
||||
if(addr <= 0x1fff) data = cpu.readRAM(addr);
|
||||
else if(addr <= 0x3fff) data = ppu.readIO(addr);
|
||||
else if(addr <= 0x4017) data = cpu.readIO(addr);
|
||||
|
||||
if(cheat.enable()) {
|
||||
if(auto result = cheat.find(addr, data)) return result();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
auto Bus::write(uint16 addr, uint8 data) -> void {
|
||||
cartridge.writePRG(addr, data);
|
||||
if(addr <= 0x1fff) return cpu.writeRAM(addr, data);
|
||||
if(addr <= 0x3fff) return ppu.writeIO(addr, data);
|
||||
if(addr <= 0x4017) return cpu.writeIO(addr, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
struct Bus {
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
};
|
||||
|
||||
extern Bus bus;
|
||||
@@ -1,144 +0,0 @@
|
||||
auto PPU::readCIRAM(uint11 addr) -> uint8 {
|
||||
return ciram[addr];
|
||||
}
|
||||
|
||||
auto PPU::writeCIRAM(uint11 addr, uint8 data) -> void {
|
||||
ciram[addr] = data;
|
||||
}
|
||||
|
||||
auto PPU::readCGRAM(uint5 addr) -> uint8 {
|
||||
if((addr & 0x13) == 0x10) addr &= ~0x10;
|
||||
uint8 data = cgram[addr];
|
||||
if(io.grayscale) data &= 0x30;
|
||||
return data;
|
||||
}
|
||||
|
||||
auto PPU::writeCGRAM(uint5 addr, uint8 data) -> void {
|
||||
if((addr & 0x13) == 0x10) addr &= ~0x10;
|
||||
cgram[addr] = data;
|
||||
}
|
||||
|
||||
auto PPU::readIO(uint16 addr) -> uint8 {
|
||||
uint8 result = 0x00;
|
||||
|
||||
switch(addr.bits(0,2)) {
|
||||
|
||||
//PPUSTATUS
|
||||
case 2:
|
||||
result |= io.mdr.bits(0,4);
|
||||
result |= io.spriteOverflow << 5;
|
||||
result |= io.spriteZeroHit << 6;
|
||||
result |= io.nmiFlag << 7;
|
||||
io.v.latch = 0;
|
||||
io.nmiHold = 0;
|
||||
cpu.nmiLine(io.nmiFlag = 0);
|
||||
break;
|
||||
|
||||
//OAMDATA
|
||||
case 4:
|
||||
result = oam[io.oamAddress];
|
||||
break;
|
||||
|
||||
//PPUDATA
|
||||
case 7:
|
||||
if(enable() && (io.ly <= 240 || io.ly == 261)) return 0x00;
|
||||
|
||||
addr = (uint14)io.v.address;
|
||||
if(addr <= 0x1fff) {
|
||||
result = io.busData;
|
||||
io.busData = cartridge.readCHR(addr);
|
||||
} else if(addr <= 0x3eff) {
|
||||
result = io.busData;
|
||||
io.busData = cartridge.readCHR(addr);
|
||||
} else if(addr <= 0x3fff) {
|
||||
result = readCGRAM(addr);
|
||||
io.busData = cartridge.readCHR(addr);
|
||||
}
|
||||
io.v.address += io.vramIncrement;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto PPU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
io.mdr = data;
|
||||
|
||||
switch(addr.bits(0,2)) {
|
||||
|
||||
//PPUCTRL
|
||||
case 0:
|
||||
io.t.nametable = data.bits(0,1);
|
||||
io.vramIncrement = data.bit (2) ? 32 : 1;
|
||||
io.spriteAddress = data.bit (3) ? 0x1000 : 0x0000;
|
||||
io.bgAddress = data.bit (4) ? 0x1000 : 0x0000;
|
||||
io.spriteHeight = data.bit (5) ? 16 : 8;
|
||||
io.masterSelect = data.bit (6);
|
||||
io.nmiEnable = data.bit (7);
|
||||
cpu.nmiLine(io.nmiEnable && io.nmiHold && io.nmiFlag);
|
||||
break;
|
||||
|
||||
//PPUMASK
|
||||
case 1:
|
||||
io.grayscale = data.bit (0);
|
||||
io.bgEdgeEnable = data.bit (1);
|
||||
io.spriteEdgeEnable = data.bit (2);
|
||||
io.bgEnable = data.bit (3);
|
||||
io.spriteEnable = data.bit (4);
|
||||
io.emphasis = data.bits(5,7);
|
||||
break;
|
||||
|
||||
//PPUSTATUS
|
||||
case 2:
|
||||
break;
|
||||
|
||||
//OAMADDR
|
||||
case 3:
|
||||
io.oamAddress = data;
|
||||
break;
|
||||
|
||||
//OAMDATA
|
||||
case 4:
|
||||
if(io.oamAddress.bits(0,1) == 2) data.bits(2,4) = 0; //clear non-existent bits (always read back as 0)
|
||||
oam[io.oamAddress++] = data;
|
||||
break;
|
||||
|
||||
//PPUSCROLL
|
||||
case 5:
|
||||
if(io.v.latch++ == 0) {
|
||||
io.v.fineX = data.bits(0,2);
|
||||
io.t.tileX = data.bits(3,7);
|
||||
} else {
|
||||
io.t.fineY = data.bits(0,2);
|
||||
io.t.tileY = data.bits(3,7);
|
||||
}
|
||||
break;
|
||||
|
||||
//PPUADDR
|
||||
case 6:
|
||||
if(io.v.latch++ == 0) {
|
||||
io.t.addressHi = data.bits(0,5);
|
||||
} else {
|
||||
io.t.addressLo = data.bits(0,7);
|
||||
io.v.address = io.t.address;
|
||||
}
|
||||
break;
|
||||
|
||||
//PPUDATA
|
||||
case 7:
|
||||
if(enable() && (io.ly <= 240 || io.ly == 261)) return;
|
||||
|
||||
addr = (uint14)io.v.address;
|
||||
if(addr <= 0x1fff) {
|
||||
cartridge.writeCHR(addr, data);
|
||||
} else if(addr <= 0x3eff) {
|
||||
cartridge.writeCHR(addr, data);
|
||||
} else if(addr <= 0x3fff) {
|
||||
writeCGRAM(addr, data);
|
||||
}
|
||||
io.v.address += io.vramIncrement;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
PPU ppu;
|
||||
#include "memory.cpp"
|
||||
#include "render.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
renderScanline();
|
||||
}
|
||||
|
||||
auto PPU::step(uint clocks) -> void {
|
||||
while(clocks--) {
|
||||
if(io.ly == 240 && io.lx == 340) io.nmiHold = 1;
|
||||
if(io.ly == 241 && io.lx == 0) io.nmiFlag = io.nmiHold;
|
||||
if(io.ly == 241 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
|
||||
|
||||
if(io.ly == 260 && io.lx == 340) io.spriteZeroHit = 0, io.spriteOverflow = 0;
|
||||
|
||||
if(io.ly == 260 && io.lx == 340) io.nmiHold = 0;
|
||||
if(io.ly == 261 && io.lx == 0) io.nmiFlag = io.nmiHold;
|
||||
if(io.ly == 261 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
|
||||
|
||||
clock += 4;
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
|
||||
io.lx++;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::scanline() -> void {
|
||||
io.lx = 0;
|
||||
if(++io.ly == 262) {
|
||||
io.ly = 0;
|
||||
frame();
|
||||
}
|
||||
cartridge.scanline(io.ly);
|
||||
}
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
io.field++;
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
auto PPU::refresh() -> void {
|
||||
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 240);
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
}
|
||||
|
||||
auto PPU::reset() -> void {
|
||||
create(PPU::Enter, 21'477'272);
|
||||
|
||||
memory::fill(&io, sizeof(IO));
|
||||
memory::fill(&latch, sizeof(Latches));
|
||||
io.vramIncrement = 1;
|
||||
|
||||
for(auto& n : ciram ) n = 0;
|
||||
for(auto& n : cgram ) n = 0;
|
||||
for(auto& n : oam ) n = 0;
|
||||
|
||||
for(auto& n : buffer) n = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
struct PPU : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto scanline() -> void;
|
||||
auto frame() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto readCIRAM(uint11 addr) -> uint8;
|
||||
auto writeCIRAM(uint11 addr, uint8 data) -> void;
|
||||
|
||||
auto readCGRAM(uint5 addr) -> uint8;
|
||||
auto writeCGRAM(uint5 addr, uint8 data) -> void;
|
||||
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
//render.cpp
|
||||
auto enable() const -> bool;
|
||||
auto loadCHR(uint16 addr) -> uint8;
|
||||
|
||||
auto renderPixel() -> void;
|
||||
auto renderSprite() -> void;
|
||||
auto renderScanline() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct IO {
|
||||
//internal
|
||||
uint8 mdr;
|
||||
|
||||
uint1 field;
|
||||
uint lx;
|
||||
uint ly;
|
||||
|
||||
uint8 busData;
|
||||
|
||||
union {
|
||||
uint value;
|
||||
NaturalBitField<uint, 0, 4> tileX;
|
||||
NaturalBitField<uint, 5, 9> tileY;
|
||||
NaturalBitField<uint,10,11> nametable;
|
||||
NaturalBitField<uint,10,10> nametableX;
|
||||
NaturalBitField<uint,11,11> nametableY;
|
||||
NaturalBitField<uint,12,14> fineY;
|
||||
NaturalBitField<uint, 0,14> address;
|
||||
NaturalBitField<uint, 0, 7> addressLo;
|
||||
NaturalBitField<uint, 8,14> addressHi;
|
||||
NaturalBitField<uint,15,15> latch;
|
||||
NaturalBitField<uint,16,18> fineX;
|
||||
} v, t;
|
||||
|
||||
bool nmiHold;
|
||||
bool nmiFlag;
|
||||
|
||||
//$2000
|
||||
uint vramIncrement;
|
||||
uint spriteAddress;
|
||||
uint bgAddress;
|
||||
uint spriteHeight;
|
||||
bool masterSelect;
|
||||
bool nmiEnable;
|
||||
|
||||
//$2001
|
||||
bool grayscale;
|
||||
bool bgEdgeEnable;
|
||||
bool spriteEdgeEnable;
|
||||
bool bgEnable;
|
||||
bool spriteEnable;
|
||||
uint3 emphasis;
|
||||
|
||||
//$2002
|
||||
bool spriteOverflow;
|
||||
bool spriteZeroHit;
|
||||
|
||||
//$2003
|
||||
uint8 oamAddress;
|
||||
} io;
|
||||
|
||||
struct OAM {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint8 id = 64;
|
||||
uint8 y = 0xff;
|
||||
uint8 tile = 0xff;
|
||||
uint8 attr = 0xff;
|
||||
uint8 x = 0xff;
|
||||
|
||||
uint8 tiledataLo = 0;
|
||||
uint8 tiledataHi = 0;
|
||||
};
|
||||
|
||||
struct Latches {
|
||||
uint16 nametable;
|
||||
uint16 attribute;
|
||||
uint16 tiledataLo;
|
||||
uint16 tiledataHi;
|
||||
|
||||
uint oamIterator;
|
||||
uint oamCounter;
|
||||
|
||||
OAM oam[8]; //primary
|
||||
OAM soam[8]; //secondary
|
||||
} latch;
|
||||
|
||||
uint8 ciram[2048];
|
||||
uint8 cgram[32];
|
||||
uint8 oam[256];
|
||||
|
||||
uint32 buffer[256 * 262];
|
||||
};
|
||||
|
||||
extern PPU ppu;
|
||||
@@ -1,211 +0,0 @@
|
||||
auto PPU::enable() const -> bool {
|
||||
return io.bgEnable || io.spriteEnable;
|
||||
}
|
||||
|
||||
auto PPU::loadCHR(uint16 addr) -> uint8 {
|
||||
return enable() ? cartridge.readCHR(addr) : (uint8)0x00;
|
||||
}
|
||||
|
||||
auto PPU::renderPixel() -> void {
|
||||
uint32* output = buffer + io.ly * 256;
|
||||
|
||||
uint x = io.lx - 1;
|
||||
uint mask = 0x8000 >> (io.v.fineX + (x & 7));
|
||||
uint palette = 0;
|
||||
uint objectPalette = 0;
|
||||
bool objectPriority = 0;
|
||||
|
||||
palette |= latch.tiledataLo & mask ? 1 : 0;
|
||||
palette |= latch.tiledataHi & mask ? 2 : 0;
|
||||
if(palette) {
|
||||
uint attr = latch.attribute;
|
||||
if(mask >= 256) attr >>= 2;
|
||||
palette |= (attr & 3) << 2;
|
||||
}
|
||||
|
||||
if(!io.bgEnable) palette = 0;
|
||||
if(!io.bgEdgeEnable && x < 8) palette = 0;
|
||||
|
||||
if(io.spriteEnable)
|
||||
for(int sprite = 7; sprite >= 0; sprite--) {
|
||||
if(!io.spriteEdgeEnable && x < 8) continue;
|
||||
if(latch.oam[sprite].id == 64) continue;
|
||||
|
||||
uint spriteX = x - latch.oam[sprite].x;
|
||||
if(spriteX >= 8) continue;
|
||||
|
||||
if(latch.oam[sprite].attr & 0x40) spriteX ^= 7;
|
||||
uint mask = 0x80 >> spriteX;
|
||||
uint spritePalette = 0;
|
||||
spritePalette |= latch.oam[sprite].tiledataLo & mask ? 1 : 0;
|
||||
spritePalette |= latch.oam[sprite].tiledataHi & mask ? 2 : 0;
|
||||
if(spritePalette == 0) continue;
|
||||
|
||||
if(latch.oam[sprite].id == 0 && palette && x != 255) io.spriteZeroHit = 1;
|
||||
spritePalette |= (latch.oam[sprite].attr & 3) << 2;
|
||||
|
||||
objectPriority = latch.oam[sprite].attr & 0x20;
|
||||
objectPalette = 16 + spritePalette;
|
||||
}
|
||||
|
||||
if(objectPalette) {
|
||||
if(palette == 0 || objectPriority == 0) palette = objectPalette;
|
||||
}
|
||||
|
||||
if(!enable()) palette = 0;
|
||||
output[x] = io.emphasis << 6 | readCGRAM(palette);
|
||||
}
|
||||
|
||||
auto PPU::renderSprite() -> void {
|
||||
if(!enable()) return;
|
||||
|
||||
uint n = latch.oamIterator++;
|
||||
int ly = io.ly == 261 ? -1 : io.ly;
|
||||
uint y = ly - oam[n * 4 + 0];
|
||||
|
||||
if(y >= io.spriteHeight) return;
|
||||
if(latch.oamCounter == 8) {
|
||||
io.spriteOverflow = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
auto& o = latch.soam[latch.oamCounter++];
|
||||
o.id = n;
|
||||
o.y = oam[n * 4 + 0];
|
||||
o.tile = oam[n * 4 + 1];
|
||||
o.attr = oam[n * 4 + 2];
|
||||
o.x = oam[n * 4 + 3];
|
||||
}
|
||||
|
||||
auto PPU::renderScanline() -> void {
|
||||
//Vblank
|
||||
if(io.ly >= 240 && io.ly <= 260) return step(341), scanline();
|
||||
|
||||
latch.oamIterator = 0;
|
||||
latch.oamCounter = 0;
|
||||
|
||||
for(auto n : range(8)) latch.soam[n] = {};
|
||||
|
||||
// 0
|
||||
step(1);
|
||||
|
||||
// 1-256
|
||||
for(uint tile : range(32)) {
|
||||
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
|
||||
uint tileaddr = io.bgAddress | nametable << 4 | io.v.fineY;
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
|
||||
if(io.v.tileY & 2) attribute >>= 4;
|
||||
if(io.v.tileX & 2) attribute >>= 2;
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
if(enable() && ++io.v.tileX == 0) io.v.nametableX++;
|
||||
if(enable() && tile == 31 && ++io.v.fineY == 0 && ++io.v.tileY == 30) io.v.nametableY++, io.v.tileY = 0;
|
||||
renderPixel();
|
||||
renderSprite();
|
||||
step(1);
|
||||
|
||||
uint tiledataLo = loadCHR(tileaddr + 0);
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
uint tiledataHi = loadCHR(tileaddr + 8);
|
||||
renderPixel();
|
||||
step(1);
|
||||
|
||||
renderPixel();
|
||||
renderSprite();
|
||||
step(1);
|
||||
|
||||
latch.nametable = latch.nametable << 8 | nametable;
|
||||
latch.attribute = latch.attribute << 2 | (attribute & 3);
|
||||
latch.tiledataLo = latch.tiledataLo << 8 | tiledataLo;
|
||||
latch.tiledataHi = latch.tiledataHi << 8 | tiledataHi;
|
||||
}
|
||||
|
||||
for(auto n : range(8)) latch.oam[n] = latch.soam[n];
|
||||
|
||||
//257-320
|
||||
for(uint sprite : range(8)) {
|
||||
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
|
||||
step(1);
|
||||
|
||||
if(enable() && sprite == 0) {
|
||||
//258
|
||||
io.v.nametableX = io.t.nametableX;
|
||||
io.v.tileX = io.t.tileX;
|
||||
}
|
||||
step(1);
|
||||
|
||||
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
|
||||
uint tileaddr = io.spriteHeight == 8
|
||||
? io.spriteAddress + latch.oam[sprite].tile * 16
|
||||
: (latch.oam[sprite].tile & ~1) * 16 + (latch.oam[sprite].tile & 1) * 0x1000;
|
||||
step(2);
|
||||
|
||||
uint spriteY = (io.ly - latch.oam[sprite].y) & (io.spriteHeight - 1);
|
||||
if(latch.oam[sprite].attr & 0x80) spriteY ^= io.spriteHeight - 1;
|
||||
tileaddr += spriteY + (spriteY & 8);
|
||||
|
||||
latch.oam[sprite].tiledataLo = loadCHR(tileaddr + 0);
|
||||
step(2);
|
||||
|
||||
latch.oam[sprite].tiledataHi = loadCHR(tileaddr + 8);
|
||||
step(2);
|
||||
|
||||
if(enable() && sprite == 6 && io.ly == 261) {
|
||||
//305
|
||||
io.v.address = io.t.address;
|
||||
}
|
||||
}
|
||||
|
||||
//321-336
|
||||
for(uint tile : range(2)) {
|
||||
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
|
||||
uint tileaddr = io.bgAddress | nametable << 4 | io.v.fineY;
|
||||
step(2);
|
||||
|
||||
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
|
||||
if(io.v.tileY & 2) attribute >>= 4;
|
||||
if(io.v.tileX & 2) attribute >>= 2;
|
||||
step(1);
|
||||
|
||||
if(enable() && ++io.v.tileX == 0) io.v.nametableX++;
|
||||
step(1);
|
||||
|
||||
uint tiledataLo = loadCHR(tileaddr + 0);
|
||||
step(2);
|
||||
|
||||
uint tiledataHi = loadCHR(tileaddr + 8);
|
||||
step(2);
|
||||
|
||||
latch.nametable = latch.nametable << 8 | nametable;
|
||||
latch.attribute = latch.attribute << 2 | (attribute & 3);
|
||||
latch.tiledataLo = latch.tiledataLo << 8 | tiledataLo;
|
||||
latch.tiledataHi = latch.tiledataHi << 8 | tiledataHi;
|
||||
}
|
||||
|
||||
//337-338
|
||||
loadCHR(0x2000 | (uint12)io.v.address);
|
||||
step(1);
|
||||
bool skip = enable() && io.field == 1 && io.ly == 261;
|
||||
step(1);
|
||||
|
||||
//339
|
||||
loadCHR(0x2000 | (uint12)io.v.address);
|
||||
step(1);
|
||||
|
||||
//340
|
||||
if(!skip) step(1);
|
||||
|
||||
return scanline();
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
auto PPU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
s.integer(io.mdr);
|
||||
|
||||
s.integer(io.field);
|
||||
s.integer(io.lx);
|
||||
s.integer(io.ly);
|
||||
|
||||
s.integer(io.busData);
|
||||
|
||||
s.integer(io.v.value);
|
||||
s.integer(io.t.value);
|
||||
|
||||
s.integer(io.nmiHold);
|
||||
s.integer(io.nmiFlag);
|
||||
|
||||
s.integer(io.vramIncrement);
|
||||
s.integer(io.spriteAddress);
|
||||
s.integer(io.bgAddress);
|
||||
s.integer(io.spriteHeight);
|
||||
s.integer(io.masterSelect);
|
||||
s.integer(io.nmiEnable);
|
||||
|
||||
s.integer(io.grayscale);
|
||||
s.integer(io.bgEdgeEnable);
|
||||
s.integer(io.spriteEdgeEnable);
|
||||
s.integer(io.bgEnable);
|
||||
s.integer(io.spriteEnable);
|
||||
s.integer(io.emphasis);
|
||||
|
||||
s.integer(io.spriteOverflow);
|
||||
s.integer(io.spriteZeroHit);
|
||||
|
||||
s.integer(io.oamAddress);
|
||||
|
||||
s.integer(latch.nametable);
|
||||
s.integer(latch.attribute);
|
||||
s.integer(latch.tiledataLo);
|
||||
s.integer(latch.tiledataHi);
|
||||
|
||||
s.integer(latch.oamIterator);
|
||||
s.integer(latch.oamCounter);
|
||||
|
||||
for(auto& o : latch.oam) o.serialize(s);
|
||||
for(auto& o : latch.soam) o.serialize(s);
|
||||
|
||||
s.array(ciram);
|
||||
s.array(cgram);
|
||||
s.array(oam);
|
||||
|
||||
s.array(buffer);
|
||||
}
|
||||
|
||||
auto PPU::OAM::serialize(serializer& s) -> void {
|
||||
s.integer(id);
|
||||
s.integer(y);
|
||||
s.integer(tile);
|
||||
s.integer(attr);
|
||||
s.integer(x);
|
||||
|
||||
s.integer(tiledataLo);
|
||||
s.integer(tiledataHi);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
auto Scheduler::reset() -> void {
|
||||
host = co_active();
|
||||
resume = cpu.thread;
|
||||
}
|
||||
|
||||
auto Scheduler::enter(Mode mode_) -> Event {
|
||||
mode = mode_;
|
||||
host = co_active();
|
||||
co_switch(resume);
|
||||
if(event == Event::Frame) ppu.refresh();
|
||||
return event;
|
||||
}
|
||||
|
||||
auto Scheduler::exit(Event event_) -> void {
|
||||
event = event_;
|
||||
resume = co_active();
|
||||
co_switch(host);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize(cothread_t thread) -> void {
|
||||
if(thread == cpu.thread) {
|
||||
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
|
||||
} else {
|
||||
resume = thread;
|
||||
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize() -> void {
|
||||
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
|
||||
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronizing() const -> bool {
|
||||
return mode == Mode::SynchronizeAll;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
struct Scheduler {
|
||||
enum class Mode : uint {
|
||||
Run,
|
||||
SynchronizeCPU,
|
||||
SynchronizeAll,
|
||||
};
|
||||
|
||||
enum class Event : uint {
|
||||
Unknown,
|
||||
Frame,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
auto reset() -> void;
|
||||
auto enter(Mode = Mode::Run) -> Event;
|
||||
auto exit(Event) -> void;
|
||||
auto synchronize(cothread_t) -> void;
|
||||
auto synchronize() -> void;
|
||||
auto synchronizing() const -> bool;
|
||||
|
||||
private:
|
||||
cothread_t host = nullptr;
|
||||
cothread_t resume = nullptr;
|
||||
Mode mode = Mode::Run;
|
||||
Event event = Event::Unknown;
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
||||
@@ -1,46 +0,0 @@
|
||||
Peripherals peripherals;
|
||||
|
||||
auto Peripherals::unload() -> void {
|
||||
delete controllerPort1;
|
||||
delete controllerPort2;
|
||||
controllerPort1 = nullptr;
|
||||
controllerPort2 = nullptr;
|
||||
}
|
||||
|
||||
auto Peripherals::reset() -> void {
|
||||
connect(ID::Port::Controller1, settings.controllerPort1);
|
||||
connect(ID::Port::Controller2, settings.controllerPort2);
|
||||
}
|
||||
|
||||
auto Peripherals::connect(uint port, uint device) -> void {
|
||||
if(port == ID::Port::Controller1) {
|
||||
settings.controllerPort1 = device;
|
||||
if(!system.loaded()) return;
|
||||
|
||||
delete controllerPort1;
|
||||
switch(device) { default:
|
||||
case ID::Device::None: controllerPort1 = new Controller(0); break;
|
||||
case ID::Device::Gamepad: controllerPort1 = new Gamepad(0); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(port == ID::Port::Controller2) {
|
||||
settings.controllerPort2 = device;
|
||||
if(!system.loaded()) return;
|
||||
|
||||
delete controllerPort2;
|
||||
switch(device) { default:
|
||||
case ID::Device::None: controllerPort2 = new Controller(1); break;
|
||||
case ID::Device::Gamepad: controllerPort2 = new Gamepad(1); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(port == ID::Port::Expansion) {
|
||||
settings.expansionPort = device;
|
||||
if(!system.loaded()) return;
|
||||
}
|
||||
|
||||
cpu.peripherals.reset();
|
||||
cpu.peripherals.append(controllerPort1);
|
||||
cpu.peripherals.append(controllerPort2);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(_serializeSize);
|
||||
|
||||
uint signature = 0x31545342;
|
||||
char version[16] = {0};
|
||||
char hash[64] = {0};
|
||||
char description[512] = {0};
|
||||
memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size());
|
||||
memory::copy(&hash, (const char*)cartridge.sha256(), 64);
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serializeAll(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
auto System::unserialize(serializer& s) -> bool {
|
||||
uint signature;
|
||||
char version[16];
|
||||
char hash[64];
|
||||
char description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
if(signature != 0x31545342) return false;
|
||||
if(string{version} != Emulator::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serializeAll(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s) -> void {
|
||||
}
|
||||
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
system.serialize(s);
|
||||
cartridge.serialize(s);
|
||||
cpu.serialize(s);
|
||||
apu.serialize(s);
|
||||
ppu.serialize(s);
|
||||
}
|
||||
|
||||
auto System::serializeInit() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0;
|
||||
char version[16];
|
||||
char hash[64];
|
||||
char description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serializeAll(s);
|
||||
_serializeSize = s.size();
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "peripherals.cpp"
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
auto System::run() -> void {
|
||||
scheduler.enter();
|
||||
}
|
||||
|
||||
auto System::runToSave() -> void {
|
||||
scheduler.synchronize(cpu.thread);
|
||||
scheduler.synchronize(apu.thread);
|
||||
scheduler.synchronize(ppu.thread);
|
||||
scheduler.synchronize(cartridge.thread);
|
||||
for(auto peripheral : cpu.peripherals) {
|
||||
scheduler.synchronize(peripheral->thread);
|
||||
}
|
||||
}
|
||||
|
||||
auto System::load() -> bool {
|
||||
information = Information();
|
||||
if(auto fp = interface->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
if(!cartridge.load()) return false;
|
||||
serializeInit();
|
||||
return information.loaded = true;
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
peripherals.unload();
|
||||
cartridge.unload();
|
||||
information.loaded = false;
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
cartridge.power();
|
||||
cpu.power();
|
||||
apu.power();
|
||||
ppu.power();
|
||||
reset();
|
||||
}
|
||||
|
||||
auto System::reset() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
|
||||
Emulator::audio.reset();
|
||||
Emulator::audio.setInterface(interface);
|
||||
|
||||
cartridge.reset();
|
||||
cpu.reset();
|
||||
apu.reset();
|
||||
ppu.reset();
|
||||
scheduler.reset();
|
||||
peripherals.reset();
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
assert(interface != nullptr);
|
||||
}
|
||||
|
||||
auto System::term() -> void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
struct System {
|
||||
auto loaded() const -> bool { return information.loaded; }
|
||||
|
||||
auto run() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto term() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
struct Information {
|
||||
bool loaded = false;
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
private:
|
||||
uint _serializeSize = 0;
|
||||
};
|
||||
|
||||
struct Peripherals {
|
||||
auto unload() -> void;
|
||||
auto reset() -> void;
|
||||
auto connect(uint port, uint device) -> void;
|
||||
|
||||
Controller* controllerPort1 = nullptr;
|
||||
Controller* controllerPort2 = nullptr;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
extern Peripherals peripherals;
|
||||
@@ -1,6 +0,0 @@
|
||||
auto System::configureVideoPalette() -> void {
|
||||
Emulator::video.setPalette();
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
processors += lr35902
|
||||
|
||||
objects += gb-interface gb-system gb-scheduler
|
||||
objects += gb-memory gb-cartridge
|
||||
objects += gb-cpu gb-ppu gb-apu
|
||||
objects += gb-cheat
|
||||
|
||||
obj/gb-interface.o: gb/interface/interface.cpp $(call rwildcard,gb/interface/)
|
||||
obj/gb-system.o: gb/system/system.cpp $(call rwildcard,gb/system/)
|
||||
obj/gb-scheduler.o: gb/scheduler/scheduler.cpp $(call rwildcard,gb/scheduler/)
|
||||
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp $(call rwildcard,gb/cartridge/)
|
||||
obj/gb-memory.o: gb/memory/memory.cpp $(call rwildcard,gb/memory/)
|
||||
obj/gb-cpu.o: gb/cpu/cpu.cpp $(call rwildcard,gb/cpu/)
|
||||
obj/gb-ppu.o: gb/ppu/ppu.cpp $(call rwildcard,gb/ppu/)
|
||||
obj/gb-apu.o: gb/apu/apu.cpp $(call rwildcard,gb/apu/)
|
||||
obj/gb-cheat.o: gb/cheat/cheat.cpp $(call rwildcard,gb/cheat/)
|
||||
@@ -1,112 +0,0 @@
|
||||
#include <gb/gb.hpp>
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
#include "sequencer.cpp"
|
||||
#include "square1.cpp"
|
||||
#include "square2.cpp"
|
||||
#include "wave.cpp"
|
||||
#include "noise.cpp"
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
square1.run();
|
||||
square2.run();
|
||||
wave.run();
|
||||
noise.run();
|
||||
sequencer.run();
|
||||
|
||||
hipass(sequencer.center, sequencer.centerBias);
|
||||
hipass(sequencer.left, sequencer.leftBias);
|
||||
hipass(sequencer.right, sequencer.rightBias);
|
||||
|
||||
if(!system.sgb()) {
|
||||
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
|
||||
} else {
|
||||
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
|
||||
interface->audioSample(samples, 2);
|
||||
}
|
||||
|
||||
if(cycle == 0) { //512hz
|
||||
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
|
||||
square1.clockLength();
|
||||
square2.clockLength();
|
||||
wave.clockLength();
|
||||
noise.clockLength();
|
||||
}
|
||||
if(phase == 2 || phase == 6) { //128hz
|
||||
square1.clockSweep();
|
||||
}
|
||||
if(phase == 7) { //64hz
|
||||
square1.clockEnvelope();
|
||||
square2.clockEnvelope();
|
||||
noise.clockEnvelope();
|
||||
}
|
||||
phase++;
|
||||
}
|
||||
cycle++;
|
||||
|
||||
clock += cpu.frequency;
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
//filter to remove DC bias
|
||||
auto APU::hipass(int16& sample, int64& bias) -> void {
|
||||
bias += ((((int64)sample << 16) - (bias >> 16)) * 57593) >> 16;
|
||||
sample = sclamp<16>(sample - (bias >> 32));
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
create(Enter, 2 * 1024 * 1024);
|
||||
if(!system.sgb()) stream = Emulator::audio.createStream(2, 2 * 1024 * 1024);
|
||||
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
||||
|
||||
square1.power();
|
||||
square2.power();
|
||||
wave.power();
|
||||
noise.power();
|
||||
sequencer.power();
|
||||
phase = 0;
|
||||
cycle = 0;
|
||||
|
||||
LinearFeedbackShiftRegisterGenerator r;
|
||||
for(auto& n : wave.pattern) n = r();
|
||||
}
|
||||
|
||||
auto APU::readIO(uint16 addr) -> uint8 {
|
||||
if(addr >= 0xff10 && addr <= 0xff14) return square1.read(addr);
|
||||
if(addr >= 0xff15 && addr <= 0xff19) return square2.read(addr);
|
||||
if(addr >= 0xff1a && addr <= 0xff1e) return wave.read(addr);
|
||||
if(addr >= 0xff1f && addr <= 0xff23) return noise.read(addr);
|
||||
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.read(addr);
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) return wave.read(addr);
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
if(!sequencer.enable) {
|
||||
bool valid = addr == 0xff26; //NR52
|
||||
if(!system.cgb()) {
|
||||
//NRx1 length is writable only on DMG,SGB; not on CGB
|
||||
if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0)
|
||||
if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)
|
||||
if(addr == 0xff1b) valid = true; //NR31
|
||||
if(addr == 0xff20) valid = true; //NR41
|
||||
}
|
||||
if(!valid) return;
|
||||
}
|
||||
|
||||
if(addr >= 0xff10 && addr <= 0xff14) return square1.write(addr, data);
|
||||
if(addr >= 0xff15 && addr <= 0xff19) return square2.write(addr, data);
|
||||
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write(addr, data);
|
||||
if(addr >= 0xff1f && addr <= 0xff23) return noise.write(addr, data);
|
||||
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.write(addr, data);
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) return wave.write(addr, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
struct APU : Thread, MMIO {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto hipass(int16& sample, int64& bias) -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//square1.cpp
|
||||
struct Square1 {
|
||||
auto dacEnable() const -> bool;
|
||||
|
||||
auto run() -> void;
|
||||
auto sweep(bool update) -> void;
|
||||
auto clockLength() -> void;
|
||||
auto clockSweep() -> void;
|
||||
auto clockEnvelope() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power(bool initializeLength = true) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool enable;
|
||||
|
||||
uint3 sweepFrequency;
|
||||
bool sweepDirection;
|
||||
uint3 sweepShift;
|
||||
bool sweepNegate;
|
||||
uint2 duty;
|
||||
uint length;
|
||||
uint4 envelopeVolume;
|
||||
bool envelopeDirection;
|
||||
uint3 envelopeFrequency;
|
||||
uint11 frequency;
|
||||
bool counter;
|
||||
|
||||
int16 output;
|
||||
bool dutyOutput;
|
||||
uint3 phase;
|
||||
uint period;
|
||||
uint3 envelopePeriod;
|
||||
uint3 sweepPeriod;
|
||||
int frequencyShadow;
|
||||
bool sweepEnable;
|
||||
uint4 volume;
|
||||
} square1;
|
||||
|
||||
//square2.cpp
|
||||
struct Square2 {
|
||||
auto dacEnable() const -> bool;
|
||||
|
||||
auto run() -> void;
|
||||
auto clockLength() -> void;
|
||||
auto clockEnvelope() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power(bool initializeLength = true) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool enable;
|
||||
|
||||
uint2 duty;
|
||||
uint length;
|
||||
uint4 envelopeVolume;
|
||||
bool envelopeDirection;
|
||||
uint3 envelopeFrequency;
|
||||
uint11 frequency;
|
||||
bool counter;
|
||||
|
||||
int16 output;
|
||||
bool dutyOutput;
|
||||
uint3 phase;
|
||||
uint period;
|
||||
uint3 envelopePeriod;
|
||||
uint4 volume;
|
||||
} square2;
|
||||
|
||||
struct Wave {
|
||||
auto getPattern(uint5 offset) const -> uint4;
|
||||
|
||||
auto run() -> void;
|
||||
auto clockLength() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power(bool initializeLength = true) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool enable;
|
||||
|
||||
bool dacEnable;
|
||||
uint2 volume;
|
||||
uint11 frequency;
|
||||
bool counter;
|
||||
uint8 pattern[16];
|
||||
|
||||
int16 output;
|
||||
uint length;
|
||||
uint period;
|
||||
uint5 patternOffset;
|
||||
uint4 patternSample;
|
||||
uint patternHold;
|
||||
} wave;
|
||||
|
||||
struct Noise {
|
||||
auto dacEnable() const -> bool;
|
||||
auto getPeriod() const -> uint;
|
||||
|
||||
auto run() -> void;
|
||||
auto clockLength() -> void;
|
||||
auto clockEnvelope() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power(bool initializeLength = true) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool enable;
|
||||
|
||||
uint4 envelopeVolume;
|
||||
bool envelopeDirection;
|
||||
uint3 envelopeFrequency;
|
||||
uint4 frequency;
|
||||
bool narrow;
|
||||
uint3 divisor;
|
||||
bool counter;
|
||||
|
||||
int16 output;
|
||||
uint length;
|
||||
uint3 envelopePeriod;
|
||||
uint4 volume;
|
||||
uint period;
|
||||
uint15 lfsr;
|
||||
} noise;
|
||||
|
||||
struct Sequencer {
|
||||
auto run() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool leftEnable;
|
||||
uint3 leftVolume;
|
||||
bool rightEnable;
|
||||
uint3 rightVolume;
|
||||
|
||||
struct Channel {
|
||||
bool leftEnable;
|
||||
bool rightEnable;
|
||||
} square1, square2, wave, noise;
|
||||
|
||||
bool enable;
|
||||
|
||||
int16 center;
|
||||
int16 left;
|
||||
int16 right;
|
||||
|
||||
int64 centerBias;
|
||||
int64 leftBias;
|
||||
int64 rightBias;
|
||||
} sequencer;
|
||||
|
||||
uint3 phase; //high 3-bits of clock counter
|
||||
uint12 cycle; //low 12-bits of clock counter
|
||||
};
|
||||
|
||||
extern APU apu;
|
||||
@@ -1,140 +0,0 @@
|
||||
auto APU::Noise::dacEnable() const -> bool {
|
||||
return (envelopeVolume || envelopeDirection);
|
||||
}
|
||||
|
||||
auto APU::Noise::getPeriod() const -> uint {
|
||||
static const uint table[] = {4, 8, 16, 24, 32, 40, 48, 56};
|
||||
return table[divisor] << frequency;
|
||||
}
|
||||
|
||||
auto APU::Noise::run() -> void {
|
||||
if(period && --period == 0) {
|
||||
period = getPeriod();
|
||||
if(frequency < 14) {
|
||||
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
|
||||
lfsr = (lfsr >> 1) ^ (bit << (narrow ? 6 : 14));
|
||||
}
|
||||
}
|
||||
|
||||
uint4 sample = lfsr & 1 ? 0 : (uint)volume;
|
||||
if(!enable) sample = 0;
|
||||
|
||||
output = sample;
|
||||
}
|
||||
|
||||
auto APU::Noise::clockLength() -> void {
|
||||
if(counter) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::clockEnvelope() -> void {
|
||||
if(enable && envelopeFrequency && --envelopePeriod == 0) {
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
if(envelopeDirection == 0 && volume > 0) volume--;
|
||||
if(envelopeDirection == 1 && volume < 15) volume++;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff1f) { //NR40
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff20) { //NR41
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff21) { //NR42
|
||||
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
|
||||
}
|
||||
|
||||
if(addr == 0xff22) { //NR43
|
||||
return frequency << 4 | narrow << 3 | divisor;
|
||||
}
|
||||
|
||||
if(addr == 0xff23) { //NR44
|
||||
return 0x80 | counter << 6 | 0x3f;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff20) { //NR41
|
||||
length = 64 - (data & 0x3f);
|
||||
}
|
||||
|
||||
if(addr == 0xff21) { //NR42
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff22) { //NR43
|
||||
frequency = data.bits(7,4);
|
||||
narrow = data.bit (3);
|
||||
divisor = data.bits(2,0);
|
||||
period = getPeriod();
|
||||
}
|
||||
|
||||
if(addr == 0xff23) { //NR44
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
counter = data.bit(6);
|
||||
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
lfsr = -1;
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
volume = envelopeVolume;
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::power(bool initializeLength) -> void {
|
||||
enable = 0;
|
||||
|
||||
envelopeVolume = 0;
|
||||
envelopeDirection = 0;
|
||||
envelopeFrequency = 0;
|
||||
frequency = 0;
|
||||
narrow = 0;
|
||||
divisor = 0;
|
||||
counter = 0;
|
||||
|
||||
output = 0;
|
||||
envelopePeriod = 0;
|
||||
volume = 0;
|
||||
period = 0;
|
||||
lfsr = 0;
|
||||
|
||||
if(initializeLength) length = 64;
|
||||
}
|
||||
|
||||
auto APU::Noise::serialize(serializer& s) -> void {
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(envelopeVolume);
|
||||
s.integer(envelopeDirection);
|
||||
s.integer(envelopeFrequency);
|
||||
s.integer(frequency);
|
||||
s.integer(narrow);
|
||||
s.integer(divisor);
|
||||
s.integer(counter);
|
||||
|
||||
s.integer(output);
|
||||
s.integer(length);
|
||||
s.integer(envelopePeriod);
|
||||
s.integer(volume);
|
||||
s.integer(period);
|
||||
s.integer(lfsr);
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
auto APU::Sequencer::run() -> void {
|
||||
if(enable == false) {
|
||||
center = 0;
|
||||
left = 0;
|
||||
right = 0;
|
||||
|
||||
centerBias = leftBias = rightBias = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int sample = 0;
|
||||
sample += apu.square1.output;
|
||||
sample += apu.square2.output;
|
||||
sample += apu.wave.output;
|
||||
sample += apu.noise.output;
|
||||
center = (sample * 512) - 16384;
|
||||
|
||||
sample = 0;
|
||||
if(square1.leftEnable) sample += apu.square1.output;
|
||||
if(square2.leftEnable) sample += apu.square2.output;
|
||||
if( wave.leftEnable) sample += apu.wave.output;
|
||||
if( noise.leftEnable) sample += apu.noise.output;
|
||||
sample = (sample * 512) - 16384;
|
||||
sample = (sample * (leftVolume + 1)) / 8;
|
||||
left = sample;
|
||||
|
||||
sample = 0;
|
||||
if(square1.rightEnable) sample += apu.square1.output;
|
||||
if(square2.rightEnable) sample += apu.square2.output;
|
||||
if( wave.rightEnable) sample += apu.wave.output;
|
||||
if( noise.rightEnable) sample += apu.noise.output;
|
||||
sample = (sample * 512) - 16384;
|
||||
sample = (sample * (rightVolume + 1)) / 8;
|
||||
right = sample;
|
||||
|
||||
//reduce audio volume
|
||||
center >>= 1;
|
||||
left >>= 1;
|
||||
right >>= 1;
|
||||
}
|
||||
|
||||
auto APU::Sequencer::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff24) { //NR50
|
||||
return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
|
||||
}
|
||||
|
||||
if(addr == 0xff25) { //NR51
|
||||
return noise.leftEnable << 7
|
||||
| wave.leftEnable << 6
|
||||
| square2.leftEnable << 5
|
||||
| square1.leftEnable << 4
|
||||
| noise.rightEnable << 3
|
||||
| wave.rightEnable << 2
|
||||
| square2.rightEnable << 1
|
||||
| square1.rightEnable << 0;
|
||||
}
|
||||
|
||||
if(addr == 0xff26) { //NR52
|
||||
return enable << 7 | 0x70
|
||||
| apu.noise.enable << 3
|
||||
| apu.wave.enable << 2
|
||||
| apu.square2.enable << 1
|
||||
| apu.square1.enable << 0;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff24) { //NR50
|
||||
leftEnable = data.bit (7);
|
||||
leftVolume = data.bits(6,4);
|
||||
rightEnable = data.bit (3);
|
||||
rightVolume = data.bits(2,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff25) { //NR51
|
||||
noise.leftEnable = data.bit(7);
|
||||
wave.leftEnable = data.bit(6);
|
||||
square2.leftEnable = data.bit(5);
|
||||
square1.leftEnable = data.bit(4);
|
||||
noise.rightEnable = data.bit(3);
|
||||
wave.rightEnable = data.bit(2);
|
||||
square2.rightEnable = data.bit(1);
|
||||
square1.rightEnable = data.bit(0);
|
||||
}
|
||||
|
||||
if(addr == 0xff26) { //NR52
|
||||
if(enable != data.bit(7)) {
|
||||
enable = data.bit(7);
|
||||
|
||||
if(!enable) {
|
||||
//power(bool) resets length counters when true (eg for CGB only)
|
||||
apu.square1.power(system.cgb());
|
||||
apu.square2.power(system.cgb());
|
||||
apu.wave.power(system.cgb());
|
||||
apu.noise.power(system.cgb());
|
||||
power();
|
||||
} else {
|
||||
apu.phase = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Sequencer::power() -> void {
|
||||
leftEnable = 0;
|
||||
leftVolume = 0;
|
||||
rightEnable = 0;
|
||||
rightVolume = 0;
|
||||
noise.leftEnable = 0;
|
||||
wave.leftEnable = 0;
|
||||
square2.leftEnable = 0;
|
||||
square1.leftEnable = 0;
|
||||
noise.rightEnable = 0;
|
||||
wave.rightEnable = 0;
|
||||
square2.rightEnable = 0;
|
||||
square1.rightEnable = 0;
|
||||
enable = 0;
|
||||
|
||||
center = 0;
|
||||
left = 0;
|
||||
right = 0;
|
||||
|
||||
centerBias = 0;
|
||||
leftBias = 0;
|
||||
rightBias = 0;
|
||||
}
|
||||
|
||||
auto APU::Sequencer::serialize(serializer& s) -> void {
|
||||
s.integer(leftEnable);
|
||||
s.integer(leftVolume);
|
||||
s.integer(rightEnable);
|
||||
s.integer(rightVolume);
|
||||
s.integer(noise.leftEnable);
|
||||
s.integer(wave.leftEnable);
|
||||
s.integer(square2.leftEnable);
|
||||
s.integer(square1.leftEnable);
|
||||
s.integer(noise.rightEnable);
|
||||
s.integer(wave.rightEnable);
|
||||
s.integer(square2.rightEnable);
|
||||
s.integer(square1.rightEnable);
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(center);
|
||||
s.integer(left);
|
||||
s.integer(right);
|
||||
|
||||
s.integer(centerBias);
|
||||
s.integer(leftBias);
|
||||
s.integer(rightBias);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
auto APU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
square1.serialize(s);
|
||||
square2.serialize(s);
|
||||
wave.serialize(s);
|
||||
noise.serialize(s);
|
||||
sequencer.serialize(s);
|
||||
|
||||
s.integer(phase);
|
||||
s.integer(cycle);
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
auto APU::Square1::dacEnable() const -> bool {
|
||||
return (envelopeVolume || envelopeDirection);
|
||||
}
|
||||
|
||||
auto APU::Square1::run() -> void {
|
||||
if(period && --period == 0) {
|
||||
period = 2 * (2048 - frequency);
|
||||
phase++;
|
||||
switch(duty) {
|
||||
case 0: dutyOutput = (phase == 6); break; //______-_
|
||||
case 1: dutyOutput = (phase >= 6); break; //______--
|
||||
case 2: dutyOutput = (phase >= 4); break; //____----
|
||||
case 3: dutyOutput = (phase <= 5); break; //------__
|
||||
}
|
||||
}
|
||||
|
||||
uint4 sample = dutyOutput ? (uint)volume : 0;
|
||||
if(!enable) sample = 0;
|
||||
|
||||
output = sample;
|
||||
}
|
||||
|
||||
auto APU::Square1::sweep(bool update) -> void {
|
||||
if(!sweepEnable) return;
|
||||
|
||||
sweepNegate = sweepDirection;
|
||||
uint delta = frequencyShadow >> sweepShift;
|
||||
int freq = frequencyShadow + (sweepNegate ? -delta : delta);
|
||||
|
||||
if(freq > 2047) {
|
||||
enable = false;
|
||||
} else if(sweepShift && update) {
|
||||
frequencyShadow = freq;
|
||||
frequency = freq & 2047;
|
||||
period = 2 * (2048 - frequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::clockLength() -> void {
|
||||
if(counter) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::clockSweep() -> void {
|
||||
if(--sweepPeriod == 0) {
|
||||
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
|
||||
if(sweepEnable && sweepFrequency) {
|
||||
sweep(1);
|
||||
sweep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::clockEnvelope() -> void {
|
||||
if(enable && envelopeFrequency && --envelopePeriod == 0) {
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
if(envelopeDirection == 0 && volume > 0) volume--;
|
||||
if(envelopeDirection == 1 && volume < 15) volume++;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff10) { //NR10
|
||||
return 0x80 | sweepFrequency << 4 | sweepDirection << 3 | sweepShift;
|
||||
}
|
||||
|
||||
if(addr == 0xff11) { //NR11
|
||||
return duty << 6 | 0x3f;
|
||||
}
|
||||
|
||||
if(addr == 0xff12) { //NR12
|
||||
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
|
||||
}
|
||||
|
||||
if(addr == 0xff13) { //NR13
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff14) { //NR14
|
||||
return 0x80 | counter << 6 | 0x3f;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff10) { //NR10
|
||||
if(sweepEnable && sweepNegate && !data.bit(3)) enable = false;
|
||||
sweepFrequency = data.bits(6,4);
|
||||
sweepDirection = data.bit (3);
|
||||
sweepShift = data.bits(2,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff11) { //NR11
|
||||
duty = data.bits(7,6);
|
||||
length = 64 - data.bits(5,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff12) { //NR12
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff13) { //NR13
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff14) { //NR14
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
period = 2 * (2048 - frequency);
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
volume = envelopeVolume;
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
|
||||
frequencyShadow = frequency;
|
||||
sweepNegate = false;
|
||||
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
|
||||
sweepEnable = sweepPeriod || sweepShift;
|
||||
if(sweepShift) sweep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::power(bool initializeLength) -> void {
|
||||
enable = 0;
|
||||
|
||||
sweepFrequency = 0;
|
||||
sweepDirection = 0;
|
||||
sweepShift = 0;
|
||||
sweepNegate = 0;
|
||||
duty = 0;
|
||||
envelopeVolume = 0;
|
||||
envelopeDirection = 0;
|
||||
envelopeFrequency = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
|
||||
output = 0;
|
||||
dutyOutput = 0;
|
||||
phase = 0;
|
||||
period = 0;
|
||||
envelopePeriod = 0;
|
||||
sweepPeriod = 0;
|
||||
frequencyShadow = 0;
|
||||
sweepEnable = 0;
|
||||
volume = 0;
|
||||
|
||||
if(initializeLength) length = 64;
|
||||
}
|
||||
|
||||
auto APU::Square1::serialize(serializer& s) -> void {
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(sweepFrequency);
|
||||
s.integer(sweepDirection);
|
||||
s.integer(sweepShift);
|
||||
s.integer(sweepNegate);
|
||||
s.integer(duty);
|
||||
s.integer(length);
|
||||
s.integer(envelopeVolume);
|
||||
s.integer(envelopeDirection);
|
||||
s.integer(envelopeFrequency);
|
||||
s.integer(frequency);
|
||||
s.integer(counter);
|
||||
|
||||
s.integer(output);
|
||||
s.integer(dutyOutput);
|
||||
s.integer(phase);
|
||||
s.integer(period);
|
||||
s.integer(envelopePeriod);
|
||||
s.integer(sweepPeriod);
|
||||
s.integer(frequencyShadow);
|
||||
s.integer(sweepEnable);
|
||||
s.integer(volume);
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
auto APU::Square2::dacEnable() const -> bool {
|
||||
return (envelopeVolume || envelopeDirection);
|
||||
}
|
||||
|
||||
auto APU::Square2::run() -> void {
|
||||
if(period && --period == 0) {
|
||||
period = 2 * (2048 - frequency);
|
||||
phase++;
|
||||
switch(duty) {
|
||||
case 0: dutyOutput = (phase == 6); break; //______-_
|
||||
case 1: dutyOutput = (phase >= 6); break; //______--
|
||||
case 2: dutyOutput = (phase >= 4); break; //____----
|
||||
case 3: dutyOutput = (phase <= 5); break; //------__
|
||||
}
|
||||
}
|
||||
|
||||
uint4 sample = dutyOutput ? (uint)volume : 0;
|
||||
if(!enable) sample = 0;
|
||||
|
||||
output = sample;
|
||||
}
|
||||
|
||||
auto APU::Square2::clockLength() -> void {
|
||||
if(counter) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square2::clockEnvelope() -> void {
|
||||
if(enable && envelopeFrequency && --envelopePeriod == 0) {
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
if(envelopeDirection == 0 && volume > 0) volume--;
|
||||
if(envelopeDirection == 1 && volume < 15) volume++;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square2::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff15) { //NR20
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff16) { //NR21
|
||||
return duty << 6 | 0x3f;
|
||||
}
|
||||
|
||||
if(addr == 0xff17) { //NR22
|
||||
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
|
||||
}
|
||||
|
||||
if(addr == 0xff18) { //NR23
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff19) { //NR24
|
||||
return 0x80 | counter << 6 | 0x3f;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff16) { //NR21
|
||||
duty = data.bits(7,6);
|
||||
length = 64 - data.bits(5,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff17) { //NR22
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff18) { //NR23
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff19) { //NR24
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
period = 2 * (2048 - frequency);
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
volume = envelopeVolume;
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square2::power(bool initializeLength) -> void {
|
||||
enable = 0;
|
||||
|
||||
duty = 0;
|
||||
envelopeVolume = 0;
|
||||
envelopeDirection = 0;
|
||||
envelopeFrequency = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
|
||||
output = 0;
|
||||
dutyOutput = 0;
|
||||
phase = 0;
|
||||
period = 0;
|
||||
envelopePeriod = 0;
|
||||
volume = 0;
|
||||
|
||||
if(initializeLength) length = 64;
|
||||
}
|
||||
|
||||
auto APU::Square2::serialize(serializer& s) -> void {
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(duty);
|
||||
s.integer(length);
|
||||
s.integer(envelopeVolume);
|
||||
s.integer(envelopeDirection);
|
||||
s.integer(envelopeFrequency);
|
||||
s.integer(frequency);
|
||||
s.integer(counter);
|
||||
|
||||
s.integer(output);
|
||||
s.integer(dutyOutput);
|
||||
s.integer(phase);
|
||||
s.integer(period);
|
||||
s.integer(envelopePeriod);
|
||||
s.integer(volume);
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
auto APU::Wave::getPattern(uint5 offset) const -> uint4 {
|
||||
return pattern[offset >> 1] >> (offset & 1 ? 0 : 4);
|
||||
}
|
||||
|
||||
auto APU::Wave::run() -> void {
|
||||
if(patternHold) patternHold--;
|
||||
|
||||
if(period && --period == 0) {
|
||||
period = 1 * (2048 - frequency);
|
||||
patternSample = getPattern(++patternOffset);
|
||||
patternHold = 1;
|
||||
}
|
||||
|
||||
static const uint shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
|
||||
uint4 sample = patternSample >> shift[volume];
|
||||
if(!enable) sample = 0;
|
||||
|
||||
output = sample;
|
||||
}
|
||||
|
||||
auto APU::Wave::clockLength() -> void {
|
||||
if(counter) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Wave::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff1a) { //NR30
|
||||
return dacEnable << 7 | 0x7f;
|
||||
}
|
||||
|
||||
if(addr == 0xff1b) { //NR31
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff1c) { //NR32
|
||||
return 0x80 | volume << 5 | 0x1f;
|
||||
}
|
||||
|
||||
if(addr == 0xff1d) { //NR33
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff1e) { //NR34
|
||||
return 0x80 | counter << 6 | 0x3f;
|
||||
}
|
||||
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) {
|
||||
if(enable) {
|
||||
if(!system.cgb() && !patternHold) return 0xff;
|
||||
return pattern[patternOffset >> 1];
|
||||
} else {
|
||||
return pattern[addr & 15];
|
||||
}
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff1a) { //NR30
|
||||
dacEnable = data.bit(7);
|
||||
if(!dacEnable) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff1b) { //NR31
|
||||
length = 256 - data;
|
||||
}
|
||||
|
||||
if(addr == 0xff1c) { //NR32
|
||||
volume = data.bits(6,5);
|
||||
}
|
||||
|
||||
if(addr == 0xff1d) { //NR33
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff1e) { //NR34
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(data.bit(7)) {
|
||||
if(!system.cgb() && patternHold) {
|
||||
//DMG,SGB trigger while channel is being read corrupts wave RAM
|
||||
if((patternOffset >> 1) <= 3) {
|
||||
//if current pattern is with 0-3; only byte 0 is corrupted
|
||||
pattern[0] = pattern[patternOffset >> 1];
|
||||
} else {
|
||||
//if current pattern is within 4-15; pattern&~3 is copied to pattern[0-3]
|
||||
pattern[0] = pattern[((patternOffset >> 1) & ~3) + 0];
|
||||
pattern[1] = pattern[((patternOffset >> 1) & ~3) + 1];
|
||||
pattern[2] = pattern[((patternOffset >> 1) & ~3) + 2];
|
||||
pattern[3] = pattern[((patternOffset >> 1) & ~3) + 3];
|
||||
}
|
||||
}
|
||||
|
||||
enable = dacEnable;
|
||||
period = 1 * (2048 - frequency);
|
||||
patternOffset = 0;
|
||||
patternSample = 0;
|
||||
patternHold = 0;
|
||||
|
||||
if(!length) {
|
||||
length = 256;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) {
|
||||
if(enable) {
|
||||
if(!system.cgb() && !patternHold) return;
|
||||
pattern[patternOffset >> 1] = data;
|
||||
} else {
|
||||
pattern[addr & 15] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Wave::power(bool initializeLength) -> void {
|
||||
enable = 0;
|
||||
|
||||
dacEnable = 0;
|
||||
volume = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
|
||||
output = 0;
|
||||
period = 0;
|
||||
patternOffset = 0;
|
||||
patternSample = 0;
|
||||
patternHold = 0;
|
||||
|
||||
if(initializeLength) length = 256;
|
||||
}
|
||||
|
||||
auto APU::Wave::serialize(serializer& s) -> void {
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(dacEnable);
|
||||
s.integer(volume);
|
||||
s.integer(frequency);
|
||||
s.integer(counter);
|
||||
s.array(pattern);
|
||||
|
||||
s.integer(output);
|
||||
s.integer(length);
|
||||
s.integer(period);
|
||||
s.integer(patternOffset);
|
||||
s.integer(patternSample);
|
||||
s.integer(patternHold);
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
#include <gb/gb.hpp>
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
#include "mbc0/mbc0.cpp"
|
||||
#include "mbc1/mbc1.cpp"
|
||||
#include "mbc1m/mbc1m.cpp"
|
||||
#include "mbc2/mbc2.cpp"
|
||||
#include "mbc3/mbc3.cpp"
|
||||
#include "mbc5/mbc5.cpp"
|
||||
#include "mmm01/mmm01.cpp"
|
||||
#include "huc1/huc1.cpp"
|
||||
#include "huc3/huc3.cpp"
|
||||
#include "serialization.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::load(System::Revision revision) -> bool {
|
||||
information = Information();
|
||||
|
||||
switch(revision) {
|
||||
case System::Revision::GameBoy:
|
||||
if(auto pathID = interface->load(ID::GameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
case System::Revision::SuperGameBoy:
|
||||
if(auto pathID = interface->load(ID::SuperGameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
case System::Revision::GameBoyColor:
|
||||
if(auto pathID = interface->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
auto board = document["board"];
|
||||
information.title = document["information/title"].text();
|
||||
|
||||
auto mapperid = document["board/mapper"].text();
|
||||
if(mapperid == "none" ) information.mapper = Mapper::MBC0;
|
||||
if(mapperid == "MBC1" ) information.mapper = Mapper::MBC1;
|
||||
if(mapperid == "MBC1M") information.mapper = Mapper::MBC1M;
|
||||
if(mapperid == "MBC2" ) information.mapper = Mapper::MBC2;
|
||||
if(mapperid == "MBC3" ) information.mapper = Mapper::MBC3;
|
||||
if(mapperid == "MBC5" ) information.mapper = Mapper::MBC5;
|
||||
if(mapperid == "MMM01") information.mapper = Mapper::MMM01;
|
||||
if(mapperid == "HuC1" ) information.mapper = Mapper::HuC1;
|
||||
if(mapperid == "HuC3" ) information.mapper = Mapper::HuC3;
|
||||
|
||||
information.rtc = false;
|
||||
information.rumble = false;
|
||||
|
||||
rom.size = max(32768u, board["rom/size"].natural());
|
||||
rom.data = (uint8*)memory::allocate(rom.size, 0xff);
|
||||
|
||||
ram.size = board["ram/size"].natural();
|
||||
ram.data = (uint8*)memory::allocate(ram.size, 0xff);
|
||||
|
||||
if(auto name = board["rom/name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Read, File::Required)) {
|
||||
fp->read(rom.data, min(rom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(auto name = board["ram/name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Read, File::Optional)) {
|
||||
fp->read(ram.data, min(ram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
|
||||
information.battery = (bool)board["ram/name"];
|
||||
|
||||
switch(information.mapper) { default:
|
||||
case Mapper::MBC0: mapper = &mbc0; break;
|
||||
case Mapper::MBC1: mapper = &mbc1; break;
|
||||
case Mapper::MBC1M: mapper = &mbc1m; break;
|
||||
case Mapper::MBC2: mapper = &mbc2; break;
|
||||
case Mapper::MBC3: mapper = &mbc3; break;
|
||||
case Mapper::MBC5: mapper = &mbc5; break;
|
||||
case Mapper::MMM01: mapper = &mmm01; break;
|
||||
case Mapper::HuC1: mapper = &huc1; break;
|
||||
case Mapper::HuC3: mapper = &huc3; break;
|
||||
}
|
||||
|
||||
information.sha256 = Hash::SHA256(rom.data, rom.size).digest();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(auto name = document["board/ram/name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Write)) {
|
||||
fp->write(ram.data, ram.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
delete[] rom.data;
|
||||
delete[] ram.data;
|
||||
rom = {};
|
||||
ram = {};
|
||||
}
|
||||
|
||||
auto Cartridge::readROM(uint addr) -> uint8 {
|
||||
if(addr >= rom.size) addr %= rom.size;
|
||||
return rom.data[addr];
|
||||
}
|
||||
|
||||
auto Cartridge::writeROM(uint addr, uint8 data) -> void {
|
||||
if(addr >= rom.size) addr %= rom.size;
|
||||
rom.data[addr] = data;
|
||||
}
|
||||
|
||||
auto Cartridge::readRAM(uint addr) -> uint8 {
|
||||
if(ram.size == 0) return 0xff;
|
||||
if(addr >= ram.size) addr %= ram.size;
|
||||
return ram.data[addr];
|
||||
}
|
||||
|
||||
auto Cartridge::writeRAM(uint addr, uint8 data) -> void {
|
||||
if(ram.size == 0) return;
|
||||
if(addr >= ram.size) addr %= ram.size;
|
||||
ram.data[addr] = data;
|
||||
}
|
||||
|
||||
auto Cartridge::readIO(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff50) return 0xff;
|
||||
|
||||
if(bootromEnable) {
|
||||
const uint8* data = nullptr;
|
||||
switch(system.revision()) { default:
|
||||
case System::Revision::GameBoy: data = system.bootROM.dmg; break;
|
||||
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break;
|
||||
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
|
||||
}
|
||||
if(addr >= 0x0000 && addr <= 0x00ff) return data[addr];
|
||||
if(addr >= 0x0200 && addr <= 0x08ff && system.cgb()) return data[addr - 256];
|
||||
}
|
||||
|
||||
return mapper->readIO(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::writeIO(uint16 addr, uint8 data) -> void {
|
||||
if(bootromEnable && addr == 0xff50) {
|
||||
bootromEnable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mapper->writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
bootromEnable = true;
|
||||
|
||||
mbc0.power();
|
||||
mbc1.power();
|
||||
mbc1m.power();
|
||||
mbc2.power();
|
||||
mbc3.power();
|
||||
mbc5.power();
|
||||
mmm01.power();
|
||||
huc1.power();
|
||||
huc3.power();
|
||||
|
||||
for(uint n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this;
|
||||
for(uint n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this;
|
||||
bus.mmio[0xff50] = this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
struct Cartridge : MMIO {
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto sha256() const -> string { return information.sha256; }
|
||||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
auto load(System::Revision revision) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto readROM(uint addr) -> uint8;
|
||||
auto writeROM(uint addr, uint8 data) -> void;
|
||||
|
||||
auto readRAM(uint addr) -> uint8;
|
||||
auto writeRAM(uint addr, uint8 data) -> void;
|
||||
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
#include "mbc0/mbc0.hpp"
|
||||
#include "mbc1/mbc1.hpp"
|
||||
#include "mbc1m/mbc1m.hpp"
|
||||
#include "mbc2/mbc2.hpp"
|
||||
#include "mbc3/mbc3.hpp"
|
||||
#include "mbc5/mbc5.hpp"
|
||||
#include "mmm01/mmm01.hpp"
|
||||
#include "huc1/huc1.hpp"
|
||||
#include "huc3/huc3.hpp"
|
||||
|
||||
enum Mapper : uint {
|
||||
MBC0,
|
||||
MBC1,
|
||||
MBC1M,
|
||||
MBC2,
|
||||
MBC3,
|
||||
MBC5,
|
||||
MMM01,
|
||||
HuC1,
|
||||
HuC3,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct Information {
|
||||
uint pathID = 0;
|
||||
string sha256;
|
||||
string manifest;
|
||||
string title;
|
||||
|
||||
Mapper mapper = Mapper::Unknown;
|
||||
boolean ram;
|
||||
boolean battery;
|
||||
boolean rtc;
|
||||
boolean rumble;
|
||||
} information;
|
||||
|
||||
struct Memory {
|
||||
uint8* data = nullptr;
|
||||
uint size = 0;
|
||||
} rom, ram;
|
||||
|
||||
MMIO* mapper = nullptr;
|
||||
bool bootromEnable = true;
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
||||
Loaded 100 of 1843 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user