mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-17 14:42:03 +02:00
Compare commits
92 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ae5968cfeb | ||
|
b03563426f | ||
|
f500426158 | ||
|
8499c64756 | ||
|
26bd7590ad | ||
|
21ee597aae | ||
|
bf90bdfcc8 | ||
|
0ad70a30f8 | ||
|
79c83ade70 | ||
|
a3aea95e6b | ||
|
569f5abc28 | ||
|
5bdf55f08f | ||
|
e30780bb72 | ||
|
bab2ac812a | ||
|
1d7b674dd4 | ||
|
c2c957a9da | ||
|
8cf20dabbf | ||
|
2707c5316d | ||
|
f3e67da937 | ||
|
c6fc15f8d2 | ||
|
d6e9d94ec3 | ||
|
2fbbccf985 | ||
|
4c3f58150c | ||
|
d91f3999cc | ||
|
7c96826eb0 | ||
|
5df717ff2a | ||
|
f7ddbfc462 | ||
|
0b70a01b47 | ||
|
4d2e17f9c0 | ||
|
043f6a8b33 | ||
|
ffd150735b | ||
|
427bac3011 | ||
|
ac2d0ba1cf | ||
|
1df2549d18 | ||
|
9b8c3ff8c0 | ||
|
0a57cac70c | ||
|
8bdf8f2a55 | ||
|
e39987a3e3 | ||
|
f5e5bf1772 | ||
|
c50723ef61 | ||
|
ca277cd5e8 | ||
|
306cac2b54 | ||
|
f230d144b5 | ||
|
7ccfbe0206 | ||
|
4b897ba791 | ||
|
be3f6ac0d5 | ||
|
92fe5b0813 | ||
|
059347e575 | ||
|
0d6a09f9f8 | ||
|
b72f35a13e | ||
|
1c0ef793fe | ||
|
76a8ecd32a | ||
|
3dd1aa9c1b | ||
|
88c79e56a0 | ||
|
07995c05a5 | ||
|
13ad9644a2 | ||
|
8d5cc0c35e | ||
|
82293c95ae | ||
|
67457fade4 | ||
|
7a68059f78 | ||
|
3e807946b8 | ||
|
a816998122 | ||
|
3a9c7c6843 | ||
|
f48b332c83 | ||
|
ccd8878d75 | ||
|
875f031182 | ||
|
f04d9d58f5 | ||
|
40abcfc4a5 | ||
|
44a8c5a2b4 | ||
|
f1a80075fa | ||
|
ae5b4c3bb3 | ||
|
c074c6e064 | ||
|
50420e3dd2 | ||
|
b08449215a | ||
|
9b452c9f5f | ||
|
3681961ca5 | ||
|
20ac95ee49 | ||
|
fdc41611cf | ||
|
839813d0f1 | ||
|
7f3cfa17b9 | ||
|
ae5d380d06 | ||
|
3ebc77c148 | ||
|
6ae0abe3d3 | ||
|
0955295475 | ||
|
7cdae5195a | ||
|
e2ee6689a0 | ||
|
55e507d5df | ||
|
a2d3b8ba15 | ||
|
1929ad47d2 | ||
|
7403e69307 | ||
|
19e1d89f00 | ||
|
aff00506c5 |
47
.gitlab-ci.yml
Normal file
47
.gitlab-ci.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
# NOTE: This file is not part of the official higan source, it's been added
|
||||
# to help build WIP binaries with minimal fuss.
|
||||
|
||||
image: debian:stable
|
||||
|
||||
linux-x86_64-binaries:
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential libgtk2.0-dev libpulse-dev mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl1.2-dev libxv-dev libao-dev libopenal-dev libudev-dev
|
||||
- make -C icarus compiler=g++
|
||||
- make -C higan compiler=g++
|
||||
- mkdir higan-nightly
|
||||
- cp -a icarus/out/icarus higan-nightly/icarus
|
||||
- cp -a icarus/Database higan-nightly/
|
||||
- cp -a higan/out/higan higan-nightly/higan
|
||||
- cp -a higan/data/cheats.bml higan-nightly/
|
||||
- cp -a higan/systems/* higan-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- higan-nightly/*
|
||||
cache:
|
||||
paths:
|
||||
- icarus/obj/*.o
|
||||
- higan/obj/*.o
|
||||
|
||||
windows-x86_64-binaries:
|
||||
# This is a normal Windows cross-compile process, except that
|
||||
# nall::chrono tries to use clock_gettime on Windows even
|
||||
# though it's a POSIX function, and for some weird reason mingw has
|
||||
# clock_gettime() in the pthread library.
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential mingw-w64
|
||||
- sed -i -e 's/-lole32/& -static -lpthread/' nall/GNUmakefile
|
||||
- make -C icarus platform=windows compiler="x86_64-w64-mingw32-g++ -static-libgcc -static-libstdc++" windres="x86_64-w64-mingw32-windres"
|
||||
- make -C higan platform=windows compiler="x86_64-w64-mingw32-g++ -static-libgcc -static-libstdc++" windres="x86_64-w64-mingw32-windres"
|
||||
- mkdir higan-nightly
|
||||
- cp -a icarus/out/icarus higan-nightly/icarus.exe
|
||||
- cp -a icarus/Database higan-nightly/
|
||||
- cp -a higan/out/higan higan-nightly/higan.exe
|
||||
- cp -a higan/data/cheats.bml higan-nightly/
|
||||
- cp -a higan/systems/* higan-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- higan-nightly/*
|
||||
cache:
|
||||
paths:
|
||||
- icarus/obj/*.o
|
||||
- higan/obj/*.o
|
@@ -1,12 +1,10 @@
|
||||
include ../nall/GNUmakefile
|
||||
|
||||
target := tomoko
|
||||
# target := loki
|
||||
profile := accuracy
|
||||
# console := true
|
||||
|
||||
flags += -I. -I.. -O3
|
||||
objects := libco
|
||||
objects := libco emulator audio video resource
|
||||
|
||||
# profile-guided optimization mode
|
||||
# pgo := instrument
|
||||
@@ -26,7 +24,7 @@ ifeq ($(platform),windows)
|
||||
else
|
||||
link += -mwindows
|
||||
endif
|
||||
link += -mthreads -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
link += -mthreads -lpthread -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
link += -Wl,-enable-auto-import
|
||||
link += -Wl,-enable-runtime-pseudo-reloc
|
||||
else ifeq ($(platform),macosx)
|
||||
@@ -54,6 +52,12 @@ compile = \
|
||||
|
||||
all: build;
|
||||
|
||||
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco)
|
||||
obj/emulator.o: emulator/emulator.cpp $(call rwildcard,emulator)
|
||||
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
|
||||
|
||||
|
90
higan/audio/audio.cpp
Normal file
90
higan/audio/audio.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
#include "stream.cpp"
|
||||
Audio audio;
|
||||
|
||||
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
|
||||
interface = nullptr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
platform->audioSample(samples, channels);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
65
higan/audio/audio.hpp
Normal file
65
higan/audio/audio.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#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* 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;
|
||||
|
||||
}
|
35
higan/audio/stream.cpp
Normal file
35
higan/audio/stream.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
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();
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
objects += emulator
|
||||
|
||||
obj/emulator.o: emulator/emulator.cpp $(call rwildcard,emulator/)
|
48
higan/emulator/cheat.hpp
Normal file
48
higan/emulator/cheat.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Cheat {
|
||||
struct Code {
|
||||
uint addr;
|
||||
uint data;
|
||||
maybe<uint> comp;
|
||||
};
|
||||
|
||||
explicit operator bool() const {
|
||||
return codes.size() > 0;
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
codes.reset();
|
||||
}
|
||||
|
||||
auto append(uint addr, uint data, maybe<uint> comp = nothing) -> void {
|
||||
codes.append({addr, data, comp});
|
||||
}
|
||||
|
||||
auto assign(const string_vector& list) -> void {
|
||||
reset();
|
||||
for(auto& entry : list) {
|
||||
for(auto code : entry.split("+")) {
|
||||
auto part = code.split("/");
|
||||
if(part.size() == 2) append(part[0].hex(), part[1].hex());
|
||||
if(part.size() == 3) append(part[0].hex(), part[2].hex(), part[1].hex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto find(uint addr, uint comp) -> maybe<uint> {
|
||||
for(auto& code : codes) {
|
||||
if(code.addr == addr && (!code.comp || code.comp() == comp)) {
|
||||
return code.data;
|
||||
}
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private:
|
||||
vector<Code> codes;
|
||||
};
|
||||
|
||||
}
|
@@ -2,33 +2,6 @@
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
auto Interface::videoColor(uint16 r, uint16 g, uint16 b) -> uint32 {
|
||||
double saturation = 1.0;
|
||||
double gamma = 1.0;
|
||||
double luminance = 1.0;
|
||||
|
||||
if(saturation != 1.0) {
|
||||
uint16 grayscale = uclamp<16>((r + g + b) / 3);
|
||||
double inverse = max(0.0, 1.0 - saturation);
|
||||
r = uclamp<16>(r * saturation + grayscale * inverse);
|
||||
g = uclamp<16>(g * saturation + grayscale * inverse);
|
||||
b = uclamp<16>(b * saturation + grayscale * inverse);
|
||||
}
|
||||
|
||||
if(gamma != 1.0) {
|
||||
double reciprocal = 1.0 / 32767.0;
|
||||
r = r > 32767 ? r : uint16(32767 * pow(r * reciprocal, gamma));
|
||||
g = g > 32767 ? g : uint16(32767 * pow(g * reciprocal, gamma));
|
||||
b = b > 32767 ? b : uint16(32767 * pow(b * reciprocal, gamma));
|
||||
}
|
||||
|
||||
if(luminance != 1.0) {
|
||||
r = uclamp<16>(r * luminance);
|
||||
g = uclamp<16>(g * luminance);
|
||||
b = uclamp<16>(b * luminance);
|
||||
}
|
||||
|
||||
return 255 << 24 | (r >> 8) << 16 | (g >> 8) << 8 | (b >> 8) << 0;
|
||||
}
|
||||
Platform* platform = nullptr;
|
||||
|
||||
}
|
||||
|
@@ -1,54 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/dsp.hpp>
|
||||
#include <nall/vfs.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include "types.hpp"
|
||||
#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 = "098";
|
||||
static const string Author = "byuu";
|
||||
static const string Name = "higan";
|
||||
static const string Version = "102";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
||||
#if defined(PROFILE_ACCURACY)
|
||||
static const string Profile = "Accuracy";
|
||||
#elif defined(PROFILE_BALANCED)
|
||||
static const string Profile = "Balanced";
|
||||
#elif defined(PROFILE_PERFORMANCE)
|
||||
static const string Profile = "Performance";
|
||||
#endif
|
||||
//incremented only when serialization format changes
|
||||
static const string SerializerVersion = "101";
|
||||
|
||||
namespace Constants {
|
||||
namespace Colorburst {
|
||||
static constexpr double NTSC = 315.0 / 88.0 * 1'000'000.0;
|
||||
static constexpr double PAL = 283.75 * 15'625.0 + 25.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "interface.hpp"
|
||||
|
||||
//debugging function hook:
|
||||
//no overhead (and no debugger invocation) if not compiled with -DDEBUGGER
|
||||
//wraps testing of function to allow invocation without a defined callback
|
||||
template<typename T> struct hook;
|
||||
template<typename R, typename... P> struct hook<auto (P...) -> R> {
|
||||
function<auto (P...) -> R> callback;
|
||||
|
||||
auto operator()(P... p) const -> R {
|
||||
#if defined(DEBUGGER)
|
||||
if(callback) return callback(forward<P>(p)...);
|
||||
#endif
|
||||
return R();
|
||||
}
|
||||
|
||||
hook() {}
|
||||
hook(const hook& hook) { callback = hook.callback; }
|
||||
hook(void* function) { callback = function; }
|
||||
hook(auto (*function)(P...) -> R) { callback = function; }
|
||||
template<typename C> hook(auto (C::*function)(P...) -> R, C* object) { callback = {function, object}; }
|
||||
template<typename C> hook(auto (C::*function)(P...) const -> R, C* object) { callback = {function, object}; }
|
||||
template<typename L> hook(const L& function) { callback = function; }
|
||||
|
||||
auto operator=(const hook& source) -> hook& { callback = source.callback; return *this; }
|
||||
};
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#define privileged public
|
||||
#else
|
||||
#define privileged private
|
||||
#endif
|
||||
|
@@ -2,14 +2,23 @@
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Platform {
|
||||
virtual auto path(uint id) -> string { return ""; }
|
||||
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; }
|
||||
virtual auto load(uint id, string name, string type) -> maybe<uint> { return nothing; }
|
||||
virtual auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {}
|
||||
virtual auto audioSample(const double* samples, uint channels) -> void {}
|
||||
virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
|
||||
virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}
|
||||
virtual auto dipSettings(Markup::Node node) -> uint { return 0; }
|
||||
virtual auto notify(string text) -> void { print(text, "\n"); }
|
||||
};
|
||||
|
||||
struct Interface {
|
||||
struct Information {
|
||||
string manufacturer;
|
||||
string name;
|
||||
uint width;
|
||||
uint height;
|
||||
bool overscan;
|
||||
double aspectRatio;
|
||||
bool resettable;
|
||||
struct Capability {
|
||||
bool states;
|
||||
@@ -17,75 +26,50 @@ struct Interface {
|
||||
} capability;
|
||||
} information;
|
||||
|
||||
struct Media {
|
||||
struct Medium {
|
||||
uint id;
|
||||
string name;
|
||||
string type;
|
||||
bool bootable; //false for cartridge slots (eg Sufami Turbo cartridges)
|
||||
string type; //extension
|
||||
};
|
||||
vector<Media> media;
|
||||
vector<Medium> media;
|
||||
|
||||
struct Device {
|
||||
uint id;
|
||||
uint portmask;
|
||||
string name;
|
||||
struct Input {
|
||||
uint id;
|
||||
uint type; //0 = digital, 1 = analog (relative), 2 = rumble
|
||||
string name;
|
||||
uintptr guid; //user data field
|
||||
};
|
||||
vector<Input> input;
|
||||
vector<uint> order;
|
||||
vector<Input> inputs;
|
||||
};
|
||||
|
||||
struct Port {
|
||||
uint id;
|
||||
string name;
|
||||
vector<Device> device;
|
||||
vector<Device> devices;
|
||||
};
|
||||
vector<Port> port;
|
||||
|
||||
struct Bind {
|
||||
virtual auto loadRequest(uint, string, string, bool) -> void {}
|
||||
virtual auto loadRequest(uint, string, bool) -> void {}
|
||||
virtual auto saveRequest(uint, string) -> void {}
|
||||
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
|
||||
virtual auto audioSample(int16, int16) -> void {}
|
||||
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
|
||||
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
|
||||
virtual auto dipSettings(const Markup::Node&) -> uint { return 0; }
|
||||
virtual auto path(uint) -> string { return ""; }
|
||||
virtual auto notify(string text) -> void { print(text, "\n"); }
|
||||
};
|
||||
Bind* bind = nullptr;
|
||||
|
||||
//callback bindings (provided by user interface)
|
||||
auto loadRequest(uint id, string name, string type, bool required) -> void { return bind->loadRequest(id, name, type, required); }
|
||||
auto loadRequest(uint id, string path, bool required) -> void { return bind->loadRequest(id, path, required); }
|
||||
auto saveRequest(uint id, string path) -> void { return bind->saveRequest(id, path); }
|
||||
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
|
||||
auto audioSample(int16 lsample, int16 rsample) -> void { return bind->audioSample(lsample, rsample); }
|
||||
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(const Markup::Node& node) -> uint { return bind->dipSettings(node); }
|
||||
auto path(uint group) -> string { return bind->path(group); }
|
||||
template<typename... P> auto notify(P&&... p) -> void { return bind->notify({forward<P>(p)...}); }
|
||||
vector<Port> ports;
|
||||
|
||||
//information
|
||||
virtual auto manifest() -> string = 0;
|
||||
virtual auto title() -> string = 0;
|
||||
|
||||
//video information
|
||||
struct VideoSize { uint width, height; };
|
||||
virtual auto videoSize() -> VideoSize = 0;
|
||||
virtual auto videoSize(uint width, uint height, bool arc) -> VideoSize = 0;
|
||||
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 group(uint id) -> uint = 0;
|
||||
virtual auto load(uint id) -> void {}
|
||||
virtual auto load(uint id) -> bool { return false; }
|
||||
virtual auto save() -> void {}
|
||||
virtual auto load(uint id, const stream& memory) -> void {}
|
||||
virtual auto save(uint id, const stream& memory) -> void {}
|
||||
virtual auto unload() -> void {}
|
||||
|
||||
//system interface
|
||||
@@ -103,7 +87,7 @@ struct Interface {
|
||||
virtual auto unserialize(serializer&) -> bool = 0;
|
||||
|
||||
//cheat functions
|
||||
virtual auto cheatSet(const lstring& = lstring{}) -> void {}
|
||||
virtual auto cheatSet(const string_vector& = {}) -> void {}
|
||||
|
||||
//settings
|
||||
virtual auto cap(const string& name) -> bool { return false; }
|
||||
@@ -114,4 +98,14 @@ struct Interface {
|
||||
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;
|
||||
};
|
||||
|
||||
extern Platform* platform;
|
||||
|
||||
}
|
||||
|
91
higan/emulator/scheduler.hpp
Normal file
91
higan/emulator/scheduler.hpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Scheduler {
|
||||
enum class Mode : uint {
|
||||
Run,
|
||||
SynchronizeMaster,
|
||||
SynchronizeSlave,
|
||||
};
|
||||
|
||||
enum class Event : uint {
|
||||
Step,
|
||||
Frame,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
inline auto synchronizing() const -> bool { return _mode == Mode::SynchronizeSlave; }
|
||||
|
||||
auto reset() -> void {
|
||||
_host = co_active();
|
||||
_threads.reset();
|
||||
}
|
||||
|
||||
auto primary(Thread& thread) -> void {
|
||||
_master = _resume = thread.handle();
|
||||
}
|
||||
|
||||
auto append(Thread& thread) -> bool {
|
||||
if(_threads.find(&thread)) return false;
|
||||
thread._clock += _threads.size(); //this bias prioritizes threads appended earlier first
|
||||
return _threads.append(&thread), true;
|
||||
}
|
||||
|
||||
auto remove(Thread& thread) -> bool {
|
||||
if(auto offset = _threads.find(&thread)) return _threads.remove(*offset), true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto enter(Mode mode = Mode::Run) -> Event {
|
||||
_mode = mode;
|
||||
_host = co_active();
|
||||
co_switch(_resume);
|
||||
return _event;
|
||||
}
|
||||
|
||||
inline auto resume(Thread& thread) -> void {
|
||||
if(_mode != Mode::SynchronizeSlave) co_switch(thread.handle());
|
||||
}
|
||||
|
||||
auto exit(Event event) -> void {
|
||||
uintmax minimum = -1;
|
||||
for(auto thread : _threads) {
|
||||
if(thread->_clock < minimum) minimum = thread->_clock;
|
||||
}
|
||||
for(auto thread : _threads) {
|
||||
thread->_clock -= minimum;
|
||||
}
|
||||
|
||||
_event = event;
|
||||
_resume = co_active();
|
||||
co_switch(_host);
|
||||
}
|
||||
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(thread.handle() == _master) {
|
||||
while(enter(Mode::SynchronizeMaster) != Event::Synchronize);
|
||||
} else {
|
||||
_resume = thread.handle();
|
||||
while(enter(Mode::SynchronizeSlave) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
inline auto synchronize() -> void {
|
||||
if(co_active() == _master) {
|
||||
if(_mode == Mode::SynchronizeMaster) return exit(Event::Synchronize);
|
||||
} else {
|
||||
if(_mode == Mode::SynchronizeSlave) return exit(Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
cothread_t _host = nullptr; //program thread (used to exit scheduler)
|
||||
cothread_t _resume = nullptr; //resume thread (used to enter scheduler)
|
||||
cothread_t _master = nullptr; //primary thread (used to synchronize components)
|
||||
Mode _mode = Mode::Run;
|
||||
Event _event = Event::Step;
|
||||
vector<Thread*> _threads;
|
||||
};
|
||||
|
||||
}
|
57
higan/emulator/thread.hpp
Normal file
57
higan/emulator/thread.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Thread {
|
||||
enum : uintmax { Second = (uintmax)-1 >> 1 };
|
||||
|
||||
virtual ~Thread() {
|
||||
if(_handle) co_delete(_handle);
|
||||
}
|
||||
|
||||
inline auto active() const { return co_active() == _handle; }
|
||||
inline auto handle() const { return _handle; }
|
||||
inline auto frequency() const { return _frequency; }
|
||||
inline auto scalar() const { return _scalar; }
|
||||
inline auto clock() const { return _clock; }
|
||||
|
||||
auto setFrequency(double frequency) -> void {
|
||||
_frequency = frequency + 0.5;
|
||||
_scalar = Second / _frequency;
|
||||
}
|
||||
|
||||
auto setScalar(uintmax scalar) -> void {
|
||||
_scalar = scalar;
|
||||
}
|
||||
|
||||
auto setClock(uintmax clock) -> void {
|
||||
_clock = clock;
|
||||
}
|
||||
|
||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||
if(_handle) co_delete(_handle);
|
||||
_handle = co_create(64 * 1024 * sizeof(void*), entrypoint);
|
||||
setFrequency(frequency);
|
||||
setClock(0);
|
||||
}
|
||||
|
||||
inline auto step(uint clocks) -> void {
|
||||
_clock += _scalar * clocks;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(_frequency);
|
||||
s.integer(_scalar);
|
||||
s.integer(_clock);
|
||||
}
|
||||
|
||||
protected:
|
||||
cothread_t _handle = nullptr;
|
||||
uintmax _frequency = 0;
|
||||
uintmax _scalar = 0;
|
||||
uintmax _clock = 0;
|
||||
|
||||
friend class Scheduler;
|
||||
};
|
||||
|
||||
}
|
129
higan/emulator/types.hpp
Normal file
129
higan/emulator/types.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
using int1 = nall::Integer< 1>;
|
||||
using int2 = nall::Integer< 2>;
|
||||
using int3 = nall::Integer< 3>;
|
||||
using int4 = nall::Integer< 4>;
|
||||
using int5 = nall::Integer< 5>;
|
||||
using int6 = nall::Integer< 6>;
|
||||
using int7 = nall::Integer< 7>;
|
||||
using int8 = nall::Integer< 8>;
|
||||
using int9 = nall::Integer< 9>;
|
||||
using int10 = nall::Integer<10>;
|
||||
using int11 = nall::Integer<11>;
|
||||
using int12 = nall::Integer<12>;
|
||||
using int13 = nall::Integer<13>;
|
||||
using int14 = nall::Integer<14>;
|
||||
using int15 = nall::Integer<15>;
|
||||
using int16 = nall::Integer<16>;
|
||||
using int17 = nall::Integer<17>;
|
||||
using int18 = nall::Integer<18>;
|
||||
using int19 = nall::Integer<19>;
|
||||
using int20 = nall::Integer<20>;
|
||||
using int21 = nall::Integer<21>;
|
||||
using int22 = nall::Integer<22>;
|
||||
using int23 = nall::Integer<23>;
|
||||
using int24 = nall::Integer<24>;
|
||||
using int25 = nall::Integer<25>;
|
||||
using int26 = nall::Integer<26>;
|
||||
using int27 = nall::Integer<27>;
|
||||
using int28 = nall::Integer<28>;
|
||||
using int29 = nall::Integer<29>;
|
||||
using int30 = nall::Integer<30>;
|
||||
using int31 = nall::Integer<31>;
|
||||
using int32 = nall::Integer<32>;
|
||||
using int33 = nall::Integer<33>;
|
||||
using int34 = nall::Integer<34>;
|
||||
using int35 = nall::Integer<35>;
|
||||
using int36 = nall::Integer<36>;
|
||||
using int37 = nall::Integer<37>;
|
||||
using int38 = nall::Integer<38>;
|
||||
using int39 = nall::Integer<39>;
|
||||
using int40 = nall::Integer<40>;
|
||||
using int41 = nall::Integer<41>;
|
||||
using int42 = nall::Integer<42>;
|
||||
using int43 = nall::Integer<43>;
|
||||
using int44 = nall::Integer<44>;
|
||||
using int45 = nall::Integer<45>;
|
||||
using int46 = nall::Integer<46>;
|
||||
using int47 = nall::Integer<47>;
|
||||
using int48 = nall::Integer<48>;
|
||||
using int49 = nall::Integer<49>;
|
||||
using int50 = nall::Integer<50>;
|
||||
using int51 = nall::Integer<51>;
|
||||
using int52 = nall::Integer<52>;
|
||||
using int53 = nall::Integer<53>;
|
||||
using int54 = nall::Integer<54>;
|
||||
using int55 = nall::Integer<55>;
|
||||
using int56 = nall::Integer<56>;
|
||||
using int57 = nall::Integer<57>;
|
||||
using int58 = nall::Integer<58>;
|
||||
using int59 = nall::Integer<59>;
|
||||
using int60 = nall::Integer<60>;
|
||||
using int61 = nall::Integer<61>;
|
||||
using int62 = nall::Integer<62>;
|
||||
using int63 = nall::Integer<63>;
|
||||
using int64 = nall::Integer<64>;
|
||||
|
||||
using uint1 = nall::Natural< 1>;
|
||||
using uint2 = nall::Natural< 2>;
|
||||
using uint3 = nall::Natural< 3>;
|
||||
using uint4 = nall::Natural< 4>;
|
||||
using uint5 = nall::Natural< 5>;
|
||||
using uint6 = nall::Natural< 6>;
|
||||
using uint7 = nall::Natural< 7>;
|
||||
using uint8 = nall::Natural< 8>;
|
||||
using uint9 = nall::Natural< 9>;
|
||||
using uint10 = nall::Natural<10>;
|
||||
using uint11 = nall::Natural<11>;
|
||||
using uint12 = nall::Natural<12>;
|
||||
using uint13 = nall::Natural<13>;
|
||||
using uint14 = nall::Natural<14>;
|
||||
using uint15 = nall::Natural<15>;
|
||||
using uint16 = nall::Natural<16>;
|
||||
using uint17 = nall::Natural<17>;
|
||||
using uint18 = nall::Natural<18>;
|
||||
using uint19 = nall::Natural<19>;
|
||||
using uint20 = nall::Natural<20>;
|
||||
using uint21 = nall::Natural<21>;
|
||||
using uint22 = nall::Natural<22>;
|
||||
using uint23 = nall::Natural<23>;
|
||||
using uint24 = nall::Natural<24>;
|
||||
using uint25 = nall::Natural<25>;
|
||||
using uint26 = nall::Natural<26>;
|
||||
using uint27 = nall::Natural<27>;
|
||||
using uint28 = nall::Natural<28>;
|
||||
using uint29 = nall::Natural<29>;
|
||||
using uint30 = nall::Natural<30>;
|
||||
using uint31 = nall::Natural<31>;
|
||||
using uint32 = nall::Natural<32>;
|
||||
using uint33 = nall::Natural<33>;
|
||||
using uint34 = nall::Natural<34>;
|
||||
using uint35 = nall::Natural<35>;
|
||||
using uint36 = nall::Natural<36>;
|
||||
using uint37 = nall::Natural<37>;
|
||||
using uint38 = nall::Natural<38>;
|
||||
using uint39 = nall::Natural<39>;
|
||||
using uint40 = nall::Natural<40>;
|
||||
using uint41 = nall::Natural<41>;
|
||||
using uint42 = nall::Natural<42>;
|
||||
using uint43 = nall::Natural<43>;
|
||||
using uint44 = nall::Natural<44>;
|
||||
using uint45 = nall::Natural<45>;
|
||||
using uint46 = nall::Natural<46>;
|
||||
using uint47 = nall::Natural<47>;
|
||||
using uint48 = nall::Natural<48>;
|
||||
using uint49 = nall::Natural<49>;
|
||||
using uint50 = nall::Natural<50>;
|
||||
using uint51 = nall::Natural<51>;
|
||||
using uint52 = nall::Natural<52>;
|
||||
using uint53 = nall::Natural<53>;
|
||||
using uint54 = nall::Natural<54>;
|
||||
using uint55 = nall::Natural<55>;
|
||||
using uint56 = nall::Natural<56>;
|
||||
using uint57 = nall::Natural<57>;
|
||||
using uint58 = nall::Natural<58>;
|
||||
using uint59 = nall::Natural<59>;
|
||||
using uint60 = nall::Natural<60>;
|
||||
using uint61 = nall::Natural<61>;
|
||||
using uint62 = nall::Natural<62>;
|
||||
using uint63 = nall::Natural<63>;
|
||||
using uint64 = nall::Natural<64>;
|
@@ -1,16 +1,13 @@
|
||||
processors += r6502
|
||||
|
||||
objects += fc-interface fc-system fc-scheduler fc-input
|
||||
objects += fc-interface fc-system 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-input.o: fc/input/input.cpp $(call rwildcard,fc/input/)
|
||||
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/)
|
||||
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-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/)
|
||||
|
@@ -14,9 +14,9 @@ APU apu;
|
||||
APU::APU() {
|
||||
for(uint amp : range(32)) {
|
||||
if(amp == 0) {
|
||||
pulse_dac[amp] = 0;
|
||||
pulseDAC[amp] = 0;
|
||||
} else {
|
||||
pulse_dac[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
|
||||
pulseDAC[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ APU::APU() {
|
||||
for(uint triangle_amp : range(16)) {
|
||||
for(uint noise_amp : range(16)) {
|
||||
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
|
||||
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp] = 0;
|
||||
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp] = 0;
|
||||
} else {
|
||||
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp]
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -47,37 +47,37 @@ auto APU::main() -> void {
|
||||
noise_output = noise.clock();
|
||||
dmc_output = dmc.clock();
|
||||
|
||||
clock_frame_counter_divider();
|
||||
clockFrameCounterDivider();
|
||||
|
||||
int output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
|
||||
int output = pulseDAC[pulse_output] + dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
|
||||
|
||||
output = filter.run_hipass_strong(output);
|
||||
output += cartridge_sample;
|
||||
output = filter.run_hipass_weak(output);
|
||||
//output = filter.run_lopass(output);
|
||||
output = filter.runHipassStrong(output);
|
||||
output += cartridgeSample;
|
||||
output = filter.runHipassWeak(output);
|
||||
//output = filter.runLopass(output);
|
||||
output = sclamp<16>(output);
|
||||
|
||||
interface->audioSample(output, output);
|
||||
stream->sample(output / 32768.0);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto APU::tick() -> void {
|
||||
clock += 12;
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
Thread::step(12);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto APU::set_irq_line() -> void {
|
||||
cpu.set_irq_apu_line(frame.irq_pending || dmc.irq_pending);
|
||||
auto APU::setIRQ() -> void {
|
||||
cpu.apuLine(frame.irqPending || dmc.irqPending);
|
||||
}
|
||||
|
||||
auto APU::set_sample(int16 sample) -> void {
|
||||
cartridge_sample = sample;
|
||||
auto APU::setSample(int16 sample) -> void {
|
||||
cartridgeSample = sample;
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
filter.hipass_strong = 0;
|
||||
filter.hipass_weak = 0;
|
||||
filter.hipassStrong = 0;
|
||||
filter.hipassWeak = 0;
|
||||
filter.lopass = 0;
|
||||
|
||||
pulse[0].power();
|
||||
@@ -88,7 +88,8 @@ auto APU::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::reset() -> void {
|
||||
create(APU::Enter, 21'477'272);
|
||||
create(APU::Enter, system.colorburst() * 6.0);
|
||||
stream = Emulator::audio.createStream(1, system.colorburst() / 2.0);
|
||||
|
||||
pulse[0].reset();
|
||||
pulse[1].reset();
|
||||
@@ -96,227 +97,249 @@ auto APU::reset() -> void {
|
||||
noise.reset();
|
||||
dmc.reset();
|
||||
|
||||
frame.irq_pending = 0;
|
||||
frame.irqPending = 0;
|
||||
|
||||
frame.mode = 0;
|
||||
frame.counter = 0;
|
||||
frame.divider = 1;
|
||||
|
||||
enabled_channels = 0;
|
||||
cartridge_sample = 0;
|
||||
enabledChannels = 0;
|
||||
cartridgeSample = 0;
|
||||
|
||||
set_irq_line();
|
||||
setIRQ();
|
||||
}
|
||||
|
||||
auto APU::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0x4015) {
|
||||
uint8 result = 0x00;
|
||||
result |= pulse[0].length_counter ? 0x01 : 0;
|
||||
result |= pulse[1].length_counter ? 0x02 : 0;
|
||||
result |= triangle.length_counter ? 0x04 : 0;
|
||||
result |= noise.length_counter ? 0x08 : 0;
|
||||
result |= dmc.length_counter ? 0x10 : 0;
|
||||
result |= frame.irq_pending ? 0x40 : 0;
|
||||
result |= dmc.irq_pending ? 0x80 : 0;
|
||||
auto APU::readIO(uint16 addr) -> uint8 {
|
||||
switch(addr) {
|
||||
|
||||
frame.irq_pending = false;
|
||||
set_irq_line();
|
||||
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::write(uint16 addr, uint8 data) -> void {
|
||||
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.loop_mode = data & 0x20;
|
||||
pulse[n].envelope.use_speed_as_volume = data & 0x10;
|
||||
pulse[n].envelope.speed = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x4001: case 0x4005:
|
||||
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;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4002: case 0x4006:
|
||||
case 0x4002: case 0x4006: {
|
||||
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
|
||||
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x0700) | (data << 0);
|
||||
break;
|
||||
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x0700) | (data << 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4003: case 0x4007:
|
||||
case 0x4003: case 0x4007: {
|
||||
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
|
||||
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x00ff) | (data << 8);
|
||||
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x00ff) | (data << 8);
|
||||
|
||||
pulse[n].duty_counter = 7;
|
||||
pulse[n].envelope.reload_decay = true;
|
||||
pulse[n].dutyCounter = 7;
|
||||
pulse[n].envelope.reloadDecay = true;
|
||||
|
||||
if(enabled_channels & (1 << n)) {
|
||||
pulse[n].length_counter = length_counter_table[(data >> 3) & 0x1f];
|
||||
if(enabledChannels & (1 << n)) {
|
||||
pulse[n].lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4008:
|
||||
triangle.halt_length_counter = data & 0x80;
|
||||
triangle.linear_length = data & 0x7f;
|
||||
break;
|
||||
case 0x4008: {
|
||||
triangle.haltLengthCounter = data & 0x80;
|
||||
triangle.linearLength = data & 0x7f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400a:
|
||||
case 0x400a: {
|
||||
triangle.period = (triangle.period & 0x0700) | (data << 0);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400b:
|
||||
case 0x400b: {
|
||||
triangle.period = (triangle.period & 0x00ff) | (data << 8);
|
||||
|
||||
triangle.reload_linear = true;
|
||||
triangle.reloadLinear = true;
|
||||
|
||||
if(enabled_channels & (1 << 2)) {
|
||||
triangle.length_counter = length_counter_table[(data >> 3) & 0x1f];
|
||||
if(enabledChannels & (1 << 2)) {
|
||||
triangle.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400c:
|
||||
noise.envelope.loop_mode = data & 0x20;
|
||||
noise.envelope.use_speed_as_volume = data & 0x10;
|
||||
case 0x400c: {
|
||||
noise.envelope.loopMode = data & 0x20;
|
||||
noise.envelope.useSpeedAsVolume = data & 0x10;
|
||||
noise.envelope.speed = data & 0x0f;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400e:
|
||||
noise.short_mode = data & 0x80;
|
||||
case 0x400e: {
|
||||
noise.shortMode = data & 0x80;
|
||||
noise.period = data & 0x0f;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400f:
|
||||
noise.envelope.reload_decay = true;
|
||||
case 0x400f: {
|
||||
noise.envelope.reloadDecay = true;
|
||||
|
||||
if(enabled_channels & (1 << 3)) {
|
||||
noise.length_counter = length_counter_table[(data >> 3) & 0x1f];
|
||||
if(enabledChannels & (1 << 3)) {
|
||||
noise.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
|
||||
}
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4010:
|
||||
dmc.irq_enable = data & 0x80;
|
||||
dmc.loop_mode = data & 0x40;
|
||||
case 0x4010: {
|
||||
dmc.irqEnable = data & 0x80;
|
||||
dmc.loopMode = data & 0x40;
|
||||
dmc.period = data & 0x0f;
|
||||
|
||||
dmc.irq_pending = dmc.irq_pending && dmc.irq_enable && !dmc.loop_mode;
|
||||
set_irq_line();
|
||||
break;
|
||||
dmc.irqPending = dmc.irqPending && dmc.irqEnable && !dmc.loopMode;
|
||||
setIRQ();
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4011:
|
||||
dmc.dac_latch = data & 0x7f;
|
||||
break;
|
||||
case 0x4011: {
|
||||
dmc.dacLatch = data & 0x7f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4012:
|
||||
dmc.addr_latch = data;
|
||||
break;
|
||||
case 0x4012: {
|
||||
dmc.addrLatch = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4013:
|
||||
dmc.length_latch = data;
|
||||
break;
|
||||
case 0x4013: {
|
||||
dmc.lengthLatch = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4015:
|
||||
if((data & 0x01) == 0) pulse[0].length_counter = 0;
|
||||
if((data & 0x02) == 0) pulse[1].length_counter = 0;
|
||||
if((data & 0x04) == 0) triangle.length_counter = 0;
|
||||
if((data & 0x08) == 0) noise.length_counter = 0;
|
||||
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.irq_pending = false;
|
||||
dmc.irqPending = false;
|
||||
|
||||
set_irq_line();
|
||||
enabled_channels = data & 0x1f;
|
||||
break;
|
||||
setIRQ();
|
||||
enabledChannels = data & 0x1f;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x4017:
|
||||
case 0x4017: {
|
||||
frame.mode = data >> 6;
|
||||
|
||||
frame.counter = 0;
|
||||
if(frame.mode & 2) clock_frame_counter();
|
||||
if(frame.mode & 2) clockFrameCounter();
|
||||
if(frame.mode & 1) {
|
||||
frame.irq_pending = false;
|
||||
set_irq_line();
|
||||
frame.irqPending = false;
|
||||
setIRQ();
|
||||
}
|
||||
frame.divider = FrameCounter::NtscPeriod;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Filter::run_hipass_strong(int sample) -> int {
|
||||
hipass_strong += ((((int64)sample << 16) - (hipass_strong >> 16)) * HiPassStrong) >> 16;
|
||||
return sample - (hipass_strong >> 32);
|
||||
auto APU::Filter::runHipassStrong(int sample) -> int {
|
||||
hipassStrong += ((((int64)sample << 16) - (hipassStrong >> 16)) * HiPassStrong) >> 16;
|
||||
return sample - (hipassStrong >> 32);
|
||||
}
|
||||
|
||||
auto APU::Filter::run_hipass_weak(int sample) -> int {
|
||||
hipass_weak += ((((int64)sample << 16) - (hipass_weak >> 16)) * HiPassWeak) >> 16;
|
||||
return sample - (hipass_weak >> 32);
|
||||
auto APU::Filter::runHipassWeak(int sample) -> int {
|
||||
hipassWeak += ((((int64)sample << 16) - (hipassWeak >> 16)) * HiPassWeak) >> 16;
|
||||
return sample - (hipassWeak >> 32);
|
||||
}
|
||||
|
||||
auto APU::Filter::run_lopass(int sample) -> int {
|
||||
auto APU::Filter::runLopass(int sample) -> int {
|
||||
lopass += ((((int64)sample << 16) - (lopass >> 16)) * LoPass) >> 16;
|
||||
return (lopass >> 32);
|
||||
}
|
||||
|
||||
auto APU::clock_frame_counter() -> void {
|
||||
auto APU::clockFrameCounter() -> void {
|
||||
frame.counter++;
|
||||
|
||||
if(frame.counter & 1) {
|
||||
pulse[0].clock_length();
|
||||
pulse[0].clockLength();
|
||||
pulse[0].sweep.clock(0);
|
||||
pulse[1].clock_length();
|
||||
pulse[1].clockLength();
|
||||
pulse[1].sweep.clock(1);
|
||||
triangle.clock_length();
|
||||
noise.clock_length();
|
||||
triangle.clockLength();
|
||||
noise.clockLength();
|
||||
}
|
||||
|
||||
pulse[0].envelope.clock();
|
||||
pulse[1].envelope.clock();
|
||||
triangle.clock_linear_length();
|
||||
triangle.clockLinearLength();
|
||||
noise.envelope.clock();
|
||||
|
||||
if(frame.counter == 0) {
|
||||
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
|
||||
if(frame.mode == 0) {
|
||||
frame.irq_pending = true;
|
||||
set_irq_line();
|
||||
frame.irqPending = true;
|
||||
setIRQ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::clock_frame_counter_divider() -> void {
|
||||
auto APU::clockFrameCounterDivider() -> void {
|
||||
frame.divider -= 2;
|
||||
if(frame.divider <= 0) {
|
||||
clock_frame_counter();
|
||||
clockFrameCounter();
|
||||
frame.divider += FrameCounter::NtscPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8 APU::length_counter_table[32] = {
|
||||
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::ntsc_noise_period_table[16] = {
|
||||
const uint16 APU::noisePeriodTableNTSC[16] = {
|
||||
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
|
||||
};
|
||||
|
||||
const uint16 APU::pal_noise_period_table[16] = {
|
||||
const uint16 APU::noisePeriodTablePAL[16] = {
|
||||
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
|
||||
};
|
||||
|
||||
const uint16 APU::ntsc_dmc_period_table[16] = {
|
||||
const uint16 APU::dmcPeriodTableNTSC[16] = {
|
||||
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
|
||||
};
|
||||
|
||||
const uint16 APU::pal_dmc_period_table[16] = {
|
||||
const uint16 APU::dmcPeriodTablePAL[16] = {
|
||||
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
|
||||
};
|
||||
|
||||
|
@@ -1,70 +1,200 @@
|
||||
struct APU : Thread {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
APU();
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
auto set_irq_line() -> void;
|
||||
auto set_sample(int16 sample) -> void;
|
||||
auto setIRQ() -> void;
|
||||
auto setSample(int16 sample) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Filter {
|
||||
auto run_hipass_strong(int sample) -> int;
|
||||
auto run_hipass_weak(int sample) -> int;
|
||||
auto run_lopass(int sample) -> int;
|
||||
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 hipass_strong;
|
||||
int64 hipass_weak;
|
||||
int64 hipassStrong;
|
||||
int64 hipassWeak;
|
||||
int64 lopass;
|
||||
};
|
||||
|
||||
#include "envelope.hpp"
|
||||
#include "sweep.hpp"
|
||||
#include "pulse.hpp"
|
||||
#include "triangle.hpp"
|
||||
#include "noise.hpp"
|
||||
#include "dmc.hpp"
|
||||
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 irq_pending;
|
||||
bool irqPending;
|
||||
|
||||
uint2 mode;
|
||||
uint2 counter;
|
||||
int divider;
|
||||
};
|
||||
|
||||
auto clock_frame_counter() -> void;
|
||||
auto clock_frame_counter_divider() -> void;
|
||||
auto clockFrameCounter() -> void;
|
||||
auto clockFrameCounterDivider() -> void;
|
||||
|
||||
Filter filter;
|
||||
FrameCounter frame;
|
||||
|
||||
uint8 enabled_channels;
|
||||
int16 cartridge_sample;
|
||||
uint8 enabledChannels;
|
||||
int16 cartridgeSample;
|
||||
|
||||
int16 pulse_dac[32];
|
||||
int16 dmc_triangle_noise_dac[128][16][16];
|
||||
int16 pulseDAC[32];
|
||||
int16 dmcTriangleNoiseDAC[128][16][16];
|
||||
|
||||
static const uint8 length_counter_table[32];
|
||||
static const uint16 ntsc_dmc_period_table[16];
|
||||
static const uint16 pal_dmc_period_table[16];
|
||||
static const uint16 ntsc_noise_period_table[16];
|
||||
static const uint16 pal_noise_period_table[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,68 +1,68 @@
|
||||
auto APU::DMC::start() -> void {
|
||||
if(length_counter == 0) {
|
||||
read_addr = 0x4000 + (addr_latch << 6);
|
||||
length_counter = (length_latch << 4) + 1;
|
||||
if(lengthCounter == 0) {
|
||||
readAddr = 0x4000 + (addrLatch << 6);
|
||||
lengthCounter = (lengthLatch << 4) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::DMC::stop() -> void {
|
||||
length_counter = 0;
|
||||
dma_delay_counter = 0;
|
||||
cpu.set_rdy_line(1);
|
||||
cpu.set_rdy_addr(false);
|
||||
lengthCounter = 0;
|
||||
dmaDelayCounter = 0;
|
||||
cpu.rdyLine(1);
|
||||
cpu.rdyAddr(false);
|
||||
}
|
||||
|
||||
auto APU::DMC::clock() -> uint8 {
|
||||
uint8 result = dac_latch;
|
||||
uint8 result = dacLatch;
|
||||
|
||||
if(dma_delay_counter > 0) {
|
||||
dma_delay_counter--;
|
||||
if(dmaDelayCounter > 0) {
|
||||
dmaDelayCounter--;
|
||||
|
||||
if(dma_delay_counter == 1) {
|
||||
cpu.set_rdy_addr(true, 0x8000 | read_addr);
|
||||
} else if(dma_delay_counter == 0) {
|
||||
cpu.set_rdy_line(1);
|
||||
cpu.set_rdy_addr(false);
|
||||
if(dmaDelayCounter == 1) {
|
||||
cpu.rdyAddr(true, 0x8000 | readAddr);
|
||||
} else if(dmaDelayCounter == 0) {
|
||||
cpu.rdyLine(1);
|
||||
cpu.rdyAddr(false);
|
||||
|
||||
dma_buffer = cpu.mdr();
|
||||
have_dma_buffer = true;
|
||||
length_counter--;
|
||||
read_addr++;
|
||||
dmaBuffer = cpu.mdr();
|
||||
dmaBufferValid = true;
|
||||
lengthCounter--;
|
||||
readAddr++;
|
||||
|
||||
if(length_counter == 0) {
|
||||
if(loop_mode) {
|
||||
if(lengthCounter == 0) {
|
||||
if(loopMode) {
|
||||
start();
|
||||
} else if(irq_enable) {
|
||||
irq_pending = true;
|
||||
apu.set_irq_line();
|
||||
} else if(irqEnable) {
|
||||
irqPending = true;
|
||||
apu.setIRQ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(--period_counter == 0) {
|
||||
if(have_sample) {
|
||||
int delta = (((sample >> bit_counter) & 1) << 2) - 2;
|
||||
uint data = dac_latch + delta;
|
||||
if((data & 0x80) == 0) dac_latch = data;
|
||||
if(--periodCounter == 0) {
|
||||
if(sampleValid) {
|
||||
int delta = (((sample >> bitCounter) & 1) << 2) - 2;
|
||||
uint data = dacLatch + delta;
|
||||
if((data & 0x80) == 0) dacLatch = data;
|
||||
}
|
||||
|
||||
if(++bit_counter == 0) {
|
||||
if(have_dma_buffer) {
|
||||
have_sample = true;
|
||||
sample = dma_buffer;
|
||||
have_dma_buffer = false;
|
||||
if(++bitCounter == 0) {
|
||||
if(dmaBufferValid) {
|
||||
sampleValid = true;
|
||||
sample = dmaBuffer;
|
||||
dmaBufferValid = false;
|
||||
} else {
|
||||
have_sample = false;
|
||||
sampleValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
period_counter = ntsc_dmc_period_table[period];
|
||||
periodCounter = dmcPeriodTableNTSC[period];
|
||||
}
|
||||
|
||||
if(length_counter > 0 && have_dma_buffer == false && dma_delay_counter == 0) {
|
||||
cpu.set_rdy_line(0);
|
||||
dma_delay_counter = 4;
|
||||
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
|
||||
cpu.rdyLine(0);
|
||||
dmaDelayCounter = 4;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -72,46 +72,21 @@ auto APU::DMC::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::DMC::reset() -> void {
|
||||
length_counter = 0;
|
||||
irq_pending = 0;
|
||||
lengthCounter = 0;
|
||||
irqPending = 0;
|
||||
|
||||
period = 0;
|
||||
period_counter = ntsc_dmc_period_table[0];
|
||||
irq_enable = 0;
|
||||
loop_mode = 0;
|
||||
dac_latch = 0;
|
||||
addr_latch = 0;
|
||||
length_latch = 0;
|
||||
read_addr = 0;
|
||||
dma_delay_counter = 0;
|
||||
bit_counter = 0;
|
||||
have_dma_buffer = 0;
|
||||
dma_buffer = 0;
|
||||
have_sample = 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;
|
||||
}
|
||||
|
||||
auto APU::DMC::serialize(serializer& s) -> void {
|
||||
s.integer(length_counter);
|
||||
s.integer(irq_pending);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(period_counter);
|
||||
|
||||
s.integer(irq_enable);
|
||||
s.integer(loop_mode);
|
||||
|
||||
s.integer(dac_latch);
|
||||
s.integer(addr_latch);
|
||||
s.integer(length_latch);
|
||||
|
||||
s.integer(read_addr);
|
||||
s.integer(dma_delay_counter);
|
||||
|
||||
s.integer(bit_counter);
|
||||
s.integer(have_dma_buffer);
|
||||
s.integer(dma_buffer);
|
||||
|
||||
s.integer(have_sample);
|
||||
s.integer(sample);
|
||||
}
|
||||
|
@@ -1,33 +0,0 @@
|
||||
struct DMC {
|
||||
auto start() -> void;
|
||||
auto stop() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint length_counter;
|
||||
bool irq_pending;
|
||||
|
||||
uint4 period;
|
||||
uint period_counter;
|
||||
|
||||
bool irq_enable;
|
||||
bool loop_mode;
|
||||
|
||||
uint8 dac_latch;
|
||||
uint8 addr_latch;
|
||||
uint8 length_latch;
|
||||
|
||||
uint15 read_addr;
|
||||
uint dma_delay_counter;
|
||||
|
||||
uint3 bit_counter;
|
||||
bool have_dma_buffer;
|
||||
uint8 dma_buffer;
|
||||
|
||||
bool have_sample;
|
||||
uint8 sample;
|
||||
} dmc;
|
@@ -1,18 +1,18 @@
|
||||
auto APU::Envelope::volume() const -> uint {
|
||||
return use_speed_as_volume ? speed : decay_volume;
|
||||
return useSpeedAsVolume ? speed : decayVolume;
|
||||
}
|
||||
|
||||
auto APU::Envelope::clock() -> void {
|
||||
if(reload_decay) {
|
||||
reload_decay = false;
|
||||
decay_volume = 0x0f;
|
||||
decay_counter = speed + 1;
|
||||
if(reloadDecay) {
|
||||
reloadDecay = false;
|
||||
decayVolume = 0x0f;
|
||||
decayCounter = speed + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if(--decay_counter == 0) {
|
||||
decay_counter = speed + 1;
|
||||
if(decay_volume || loop_mode) decay_volume--;
|
||||
if(--decayCounter == 0) {
|
||||
decayCounter = speed + 1;
|
||||
if(decayVolume || loopMode) decayVolume--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,19 +21,9 @@ auto APU::Envelope::power() -> void {
|
||||
|
||||
auto APU::Envelope::reset() -> void {
|
||||
speed = 0;
|
||||
use_speed_as_volume = 0;
|
||||
loop_mode = 0;
|
||||
reload_decay = 0;
|
||||
decay_counter = 0;
|
||||
decay_volume = 0;
|
||||
}
|
||||
|
||||
auto APU::Envelope::serialize(serializer& s) -> void {
|
||||
s.integer(speed);
|
||||
s.integer(use_speed_as_volume);
|
||||
s.integer(loop_mode);
|
||||
|
||||
s.integer(reload_decay);
|
||||
s.integer(decay_counter);
|
||||
s.integer(decay_volume);
|
||||
useSpeedAsVolume = 0;
|
||||
loopMode = 0;
|
||||
reloadDecay = 0;
|
||||
decayCounter = 0;
|
||||
decayVolume = 0;
|
||||
}
|
||||
|
@@ -1,17 +0,0 @@
|
||||
struct Envelope {
|
||||
auto volume() const -> uint;
|
||||
auto clock() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint4 speed;
|
||||
bool use_speed_as_volume;
|
||||
bool loop_mode;
|
||||
|
||||
bool reload_decay;
|
||||
uint8 decay_counter;
|
||||
uint4 decay_volume;
|
||||
};
|
@@ -1,25 +1,25 @@
|
||||
auto APU::Noise::clock_length() -> void {
|
||||
if(envelope.loop_mode == 0) {
|
||||
if(length_counter > 0) length_counter--;
|
||||
auto APU::Noise::clockLength() -> void {
|
||||
if(envelope.loopMode == 0) {
|
||||
if(lengthCounter > 0) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::clock() -> uint8 {
|
||||
if(length_counter == 0) return 0;
|
||||
if(lengthCounter == 0) return 0;
|
||||
|
||||
uint8 result = (lfsr & 1) ? envelope.volume() : 0;
|
||||
|
||||
if(--period_counter == 0) {
|
||||
if(--periodCounter == 0) {
|
||||
uint feedback;
|
||||
|
||||
if(short_mode) {
|
||||
if(shortMode) {
|
||||
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1);
|
||||
} else {
|
||||
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1);
|
||||
}
|
||||
|
||||
lfsr = (lfsr >> 1) | (feedback << 14);
|
||||
period_counter = apu.ntsc_noise_period_table[period];
|
||||
periodCounter = apu.noisePeriodTableNTSC[period];
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -29,29 +29,17 @@ auto APU::Noise::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::Noise::reset() -> void {
|
||||
length_counter = 0;
|
||||
lengthCounter = 0;
|
||||
|
||||
envelope.speed = 0;
|
||||
envelope.use_speed_as_volume = 0;
|
||||
envelope.loop_mode = 0;
|
||||
envelope.reload_decay = 0;
|
||||
envelope.decay_counter = 0;
|
||||
envelope.decay_volume = 0;
|
||||
envelope.useSpeedAsVolume = 0;
|
||||
envelope.loopMode = 0;
|
||||
envelope.reloadDecay = 0;
|
||||
envelope.decayCounter = 0;
|
||||
envelope.decayVolume = 0;
|
||||
|
||||
period = 0;
|
||||
period_counter = 1;
|
||||
short_mode = 0;
|
||||
periodCounter = 1;
|
||||
shortMode = 0;
|
||||
lfsr = 1;
|
||||
}
|
||||
|
||||
auto APU::Noise::serialize(serializer& s) -> void {
|
||||
s.integer(length_counter);
|
||||
|
||||
envelope.serialize(s);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(period_counter);
|
||||
|
||||
s.integer(short_mode);
|
||||
s.integer(lfsr);
|
||||
}
|
||||
|
@@ -1,19 +0,0 @@
|
||||
struct Noise {
|
||||
auto clock_length() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint length_counter;
|
||||
|
||||
Envelope envelope;
|
||||
|
||||
uint4 period;
|
||||
uint period_counter;
|
||||
|
||||
bool short_mode;
|
||||
uint15 lfsr;
|
||||
} noise;
|
@@ -1,20 +1,20 @@
|
||||
auto APU::Pulse::clock_length() -> void {
|
||||
if(envelope.loop_mode == 0) {
|
||||
if(length_counter) length_counter--;
|
||||
auto APU::Pulse::clockLength() -> void {
|
||||
if(envelope.loopMode == 0) {
|
||||
if(lengthCounter) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Pulse::clock() -> uint8 {
|
||||
if(sweep.check_period() == false) return 0;
|
||||
if(length_counter == 0) return 0;
|
||||
if(!sweep.checkPeriod()) return 0;
|
||||
if(lengthCounter == 0) return 0;
|
||||
|
||||
static const uint duty_table[] = {1, 2, 4, 6};
|
||||
uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0;
|
||||
if(sweep.pulse_period < 0x008) result = 0;
|
||||
static const uint dutyTable[] = {1, 2, 4, 6};
|
||||
uint8 result = (dutyCounter < dutyTable[duty]) ? envelope.volume() : 0;
|
||||
if(sweep.pulsePeriod < 0x008) result = 0;
|
||||
|
||||
if(--period_counter == 0) {
|
||||
period_counter = (sweep.pulse_period + 1) * 2;
|
||||
duty_counter++;
|
||||
if(--periodCounter == 0) {
|
||||
periodCounter = (sweep.pulsePeriod + 1) * 2;
|
||||
dutyCounter++;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -29,23 +29,10 @@ auto APU::Pulse::reset() -> void {
|
||||
envelope.reset();
|
||||
sweep.reset();
|
||||
|
||||
length_counter = 0;
|
||||
lengthCounter = 0;
|
||||
|
||||
duty = 0;
|
||||
duty_counter = 0;
|
||||
dutyCounter = 0;
|
||||
period = 0;
|
||||
period_counter = 1;
|
||||
}
|
||||
|
||||
auto APU::Pulse::serialize(serializer& s) -> void {
|
||||
s.integer(length_counter);
|
||||
|
||||
envelope.serialize(s);
|
||||
sweep.serialize(s);
|
||||
|
||||
s.integer(duty);
|
||||
s.integer(duty_counter);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(period_counter);
|
||||
periodCounter = 1;
|
||||
}
|
||||
|
@@ -1,21 +0,0 @@
|
||||
struct Pulse {
|
||||
auto clock_length() -> void;
|
||||
auto check_period() -> bool;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint length_counter;
|
||||
|
||||
Envelope envelope;
|
||||
Sweep sweep;
|
||||
|
||||
uint2 duty;
|
||||
uint3 duty_counter;
|
||||
|
||||
uint11 period;
|
||||
uint period_counter;
|
||||
} pulse[2];
|
@@ -9,18 +9,102 @@ auto APU::serialize(serializer& s) -> void {
|
||||
dmc.serialize(s);
|
||||
frame.serialize(s);
|
||||
|
||||
s.integer(enabled_channels);
|
||||
s.integer(cartridge_sample);
|
||||
s.integer(enabledChannels);
|
||||
s.integer(cartridgeSample);
|
||||
}
|
||||
|
||||
auto APU::Filter::serialize(serializer& s) -> void {
|
||||
s.integer(hipass_strong);
|
||||
s.integer(hipass_weak);
|
||||
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(irq_pending);
|
||||
s.integer(irqPending);
|
||||
|
||||
s.integer(mode);
|
||||
s.integer(counter);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
auto APU::Sweep::check_period() -> bool {
|
||||
if(pulse_period > 0x7ff) return false;
|
||||
auto APU::Sweep::checkPeriod() -> bool {
|
||||
if(pulsePeriod > 0x7ff) return false;
|
||||
|
||||
if(decrement == 0) {
|
||||
if((pulse_period + (pulse_period >> shift)) & 0x800) return false;
|
||||
if((pulsePeriod + (pulsePeriod >> shift)) & 0x800) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -11,14 +11,14 @@ auto APU::Sweep::check_period() -> bool {
|
||||
auto APU::Sweep::clock(uint channel) -> void {
|
||||
if(--counter == 0) {
|
||||
counter = period + 1;
|
||||
if(enable && shift && pulse_period > 8) {
|
||||
int delta = pulse_period >> shift;
|
||||
if(enable && shift && pulsePeriod > 8) {
|
||||
int delta = pulsePeriod >> shift;
|
||||
|
||||
if(decrement) {
|
||||
pulse_period -= delta;
|
||||
if(channel == 0) pulse_period--;
|
||||
} else if((pulse_period + delta) < 0x800) {
|
||||
pulse_period += delta;
|
||||
pulsePeriod -= delta;
|
||||
if(channel == 0) pulsePeriod--;
|
||||
} else if((pulsePeriod + delta) < 0x800) {
|
||||
pulsePeriod += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,18 +36,8 @@ auto APU::Sweep::power() -> void {
|
||||
counter = 1;
|
||||
enable = 0;
|
||||
reload = 0;
|
||||
pulse_period = 0;
|
||||
pulsePeriod = 0;
|
||||
}
|
||||
|
||||
auto APU::Sweep::reset() -> void {
|
||||
}
|
||||
|
||||
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(pulse_period);
|
||||
}
|
||||
|
@@ -1,17 +0,0 @@
|
||||
struct Sweep {
|
||||
auto check_period() -> 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 pulse_period;
|
||||
};
|
@@ -1,27 +1,27 @@
|
||||
auto APU::Triangle::clock_length() -> void {
|
||||
if(halt_length_counter == 0) {
|
||||
if(length_counter > 0) length_counter--;
|
||||
auto APU::Triangle::clockLength() -> void {
|
||||
if(haltLengthCounter == 0) {
|
||||
if(lengthCounter > 0) lengthCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Triangle::clock_linear_length() -> void {
|
||||
if(reload_linear) {
|
||||
linear_length_counter = linear_length;
|
||||
} else if(linear_length_counter) {
|
||||
linear_length_counter--;
|
||||
auto APU::Triangle::clockLinearLength() -> void {
|
||||
if(reloadLinear) {
|
||||
linearLengthCounter = linearLength;
|
||||
} else if(linearLengthCounter) {
|
||||
linearLengthCounter--;
|
||||
}
|
||||
|
||||
if(halt_length_counter == 0) reload_linear = false;
|
||||
if(haltLengthCounter == 0) reloadLinear = false;
|
||||
}
|
||||
|
||||
auto APU::Triangle::clock() -> uint8 {
|
||||
uint8 result = step_counter & 0x0f;
|
||||
if((step_counter & 0x10) == 0) result ^= 0x0f;
|
||||
if(length_counter == 0 || linear_length_counter == 0) return result;
|
||||
uint8 result = stepCounter & 0x0f;
|
||||
if((stepCounter & 0x10) == 0) result ^= 0x0f;
|
||||
if(lengthCounter == 0 || linearLengthCounter == 0) return result;
|
||||
|
||||
if(--period_counter == 0) {
|
||||
step_counter++;
|
||||
period_counter = period + 1;
|
||||
if(--periodCounter == 0) {
|
||||
stepCounter++;
|
||||
periodCounter = period + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -32,27 +32,13 @@ auto APU::Triangle::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::Triangle::reset() -> void {
|
||||
length_counter = 0;
|
||||
lengthCounter = 0;
|
||||
|
||||
linear_length = 0;
|
||||
halt_length_counter = 0;
|
||||
linearLength = 0;
|
||||
haltLengthCounter = 0;
|
||||
period = 0;
|
||||
period_counter = 1;
|
||||
step_counter = 0;
|
||||
linear_length_counter = 0;
|
||||
reload_linear = 0;
|
||||
}
|
||||
|
||||
auto APU::Triangle::serialize(serializer& s) -> void {
|
||||
s.integer(length_counter);
|
||||
|
||||
s.integer(linear_length);
|
||||
s.integer(halt_length_counter);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(period_counter);
|
||||
|
||||
s.integer(step_counter);
|
||||
s.integer(linear_length_counter);
|
||||
s.integer(reload_linear);
|
||||
periodCounter = 1;
|
||||
stepCounter = 0;
|
||||
linearLengthCounter = 0;
|
||||
reloadLinear = 0;
|
||||
}
|
||||
|
@@ -1,22 +0,0 @@
|
||||
struct Triangle {
|
||||
auto clock_length() -> void;
|
||||
auto clock_linear_length() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint length_counter;
|
||||
|
||||
uint8 linear_length;
|
||||
bool halt_length_counter;
|
||||
|
||||
uint11 period;
|
||||
uint period_counter;
|
||||
|
||||
uint5 step_counter;
|
||||
uint8 linear_length_counter;
|
||||
bool reload_linear;
|
||||
} triangle;
|
@@ -5,17 +5,17 @@ struct BandaiFCG : Board {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(1);
|
||||
irq_counter_enable = false;
|
||||
if(irqCounterEnable) {
|
||||
if(--irqCounter == 0xffff) {
|
||||
cpu.irqLine(1);
|
||||
irqCounterEnable = false;
|
||||
}
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
auto addrCIRAM(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
@@ -24,56 +24,56 @@ struct BandaiFCG : Board {
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (region == 0 ? prg_bank : (uint8)0x0f);
|
||||
uint bank = (region == 0 ? prgBank : (uint8)0x0f);
|
||||
return prgrom.read((bank << 14) | (addr & 0x3fff));
|
||||
}
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
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:
|
||||
chr_bank[addr & 7] = data;
|
||||
chrBank[addr & 7] = data;
|
||||
break;
|
||||
case 0x08:
|
||||
prg_bank = data & 0x0f;
|
||||
prgBank = data & 0x0f;
|
||||
break;
|
||||
case 0x09:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
case 0x0a:
|
||||
cpu.set_irq_line(0);
|
||||
irq_counter_enable = data & 0x01;
|
||||
irq_counter = irq_latch;
|
||||
cpu.irqLine(0);
|
||||
irqCounterEnable = data & 0x01;
|
||||
irqCounter = irqLatch;
|
||||
break;
|
||||
case 0x0b:
|
||||
irq_latch = (irq_latch & 0xff00) | (data << 0);
|
||||
irqLatch = (irqLatch & 0xff00) | (data << 0);
|
||||
break;
|
||||
case 0x0c:
|
||||
irq_latch = (irq_latch & 0x00ff) | (data << 8);
|
||||
irqLatch = (irqLatch & 0x00ff) | (data << 8);
|
||||
break;
|
||||
case 0x0d:
|
||||
//TODO: serial EEPROM support
|
||||
//todo: serial EEPROM support
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
|
||||
addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
|
||||
return Board::chr_read(addr);
|
||||
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 chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
|
||||
addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
|
||||
return Board::chr_write(addr, data);
|
||||
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 {
|
||||
@@ -81,29 +81,29 @@ struct BandaiFCG : Board {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto &n : chr_bank) n = 0;
|
||||
prg_bank = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
prgBank = 0;
|
||||
mirror = 0;
|
||||
irq_counter_enable = 0;
|
||||
irq_counter = 0;
|
||||
irq_latch = 0;
|
||||
irqCounterEnable = 0;
|
||||
irqCounter = 0;
|
||||
irqLatch = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.array(chr_bank);
|
||||
s.integer(prg_bank);
|
||||
s.array(chrBank);
|
||||
s.integer(prgBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irq_counter_enable);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irqCounterEnable);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqLatch);
|
||||
}
|
||||
|
||||
uint8 chr_bank[8];
|
||||
uint8 prg_bank;
|
||||
uint8 chrBank[8];
|
||||
uint8 prgBank;
|
||||
uint2 mirror;
|
||||
bool irq_counter_enable;
|
||||
uint16 irq_counter;
|
||||
uint16 irq_latch;
|
||||
bool irqCounterEnable;
|
||||
uint16 irqCounter;
|
||||
uint16 irqLatch;
|
||||
};
|
||||
|
@@ -41,18 +41,47 @@ Board::Board(Markup::Node& document) {
|
||||
if(chrrom.size) chrrom.data = new uint8_t[chrrom.size]();
|
||||
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
|
||||
|
||||
if(auto name = prom["name"].text()) interface->loadRequest(ID::ProgramROM, name, true);
|
||||
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name, false);
|
||||
if(auto name = crom["name"].text()) interface->loadRequest(ID::CharacterROM, name, true);
|
||||
if(auto name = cram["name"].text()) interface->loadRequest(ID::CharacterRAM, name, false);
|
||||
|
||||
if(auto name = pram["name"].text()) Famicom::cartridge.memory.append({ID::ProgramRAM, name});
|
||||
if(auto name = cram["name"].text()) Famicom::cartridge.memory.append({ID::CharacterRAM, name});
|
||||
if(prgrom.name = prom["name"].text()) {
|
||||
if(auto fp = platform->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 = platform->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 = platform->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 = platform->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 = platform->open(cartridge.pathID(), name, File::Write)) {
|
||||
fp->write(prgram.data, prgram.size);
|
||||
}
|
||||
}
|
||||
|
||||
if(auto name = document["board/chr/ram/name"].text()) {
|
||||
if(auto fp = platform->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)];
|
||||
}
|
||||
@@ -80,22 +109,22 @@ auto Board::mirror(uint addr, uint size) -> uint {
|
||||
}
|
||||
|
||||
auto Board::main() -> void {
|
||||
cartridge.clock += 12 * 4095;
|
||||
cartridge.step(12 * 4095);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto Board::tick() -> void {
|
||||
cartridge.clock += 12;
|
||||
if(cartridge.clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
cartridge.step(12);
|
||||
cartridge.synchronize(cpu);
|
||||
}
|
||||
|
||||
auto Board::chr_read(uint addr) -> uint8 {
|
||||
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::chr_write(uint addr, uint8 data) -> void {
|
||||
auto Board::writeCHR(uint addr, uint8 data) -> void {
|
||||
if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data;
|
||||
}
|
||||
|
||||
|
@@ -7,24 +7,27 @@ struct Board {
|
||||
inline auto read(uint addr) const -> uint8;
|
||||
inline auto write(uint addr, uint8 data) -> void;
|
||||
|
||||
uint8_t* data;
|
||||
uint size;
|
||||
bool writable;
|
||||
string name;
|
||||
uint8_t* data = nullptr;
|
||||
uint size = 0;
|
||||
bool writable = false;
|
||||
};
|
||||
|
||||
Board(Markup::Node& document);
|
||||
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 prg_read(uint addr) -> uint8 = 0;
|
||||
virtual auto prg_write(uint addr, uint8 data) -> void = 0;
|
||||
virtual auto readPRG(uint addr) -> uint8 = 0;
|
||||
virtual auto writePRG(uint addr, uint8 data) -> void = 0;
|
||||
|
||||
virtual auto chr_read(uint addr) -> uint8;
|
||||
virtual auto chr_write(uint addr, uint8 data) -> void;
|
||||
virtual auto readCHR(uint addr) -> uint8;
|
||||
virtual auto writeCHR(uint addr, uint8 data) -> void;
|
||||
|
||||
virtual inline auto scanline(uint y) -> void {}
|
||||
|
||||
|
@@ -2,23 +2,23 @@ struct KonamiVRC1 : Board {
|
||||
KonamiVRC1(Markup::Node& document) : Board(document), vrc1(*this) {
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(vrc1.prg_addr(addr));
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(vrc1.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) return vrc1.reg_write(addr, data);
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) return vrc1.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc1.ciram_addr(addr));
|
||||
return Board::chr_read(vrc1.chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc1.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc1.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc1.ciram_addr(addr), data);
|
||||
return Board::chr_write(vrc1.chr_addr(addr), data);
|
||||
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 {
|
||||
|
@@ -4,31 +4,31 @@ struct KonamiVRC2 : Board {
|
||||
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return vrc2.ram_read(addr);
|
||||
return prgrom.read(vrc2.prg_addr(addr));
|
||||
if(addr < 0x8000) return vrc2.readRAM(addr);
|
||||
return prgrom.read(vrc2.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return vrc2.ram_write(addr, data);
|
||||
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.reg_write(addr, data);
|
||||
return vrc2.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc2.ciram_addr(addr));
|
||||
return Board::chr_read(vrc2.chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc2.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc2.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc2.ciram_addr(addr), data);
|
||||
return Board::chr_write(vrc2.chr_addr(addr), data);
|
||||
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 {
|
||||
|
@@ -7,29 +7,29 @@ struct KonamiVRC3 : Board {
|
||||
vrc3.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) return prgram.read(addr & 0x1fff);
|
||||
if(addr & 0x8000) return prgrom.read(vrc3.prg_addr(addr));
|
||||
if(addr & 0x8000) return prgrom.read(vrc3.addrPRG(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return prgram.write(addr & 0x1fff, data);
|
||||
if(addr & 0x8000) return vrc3.reg_write(addr, data);
|
||||
if(addr & 0x8000) return vrc3.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr & 0x07ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
return chrram.read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr & 0x07ff, data);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
return chrram.write(addr, data);
|
||||
}
|
||||
|
@@ -8,13 +8,13 @@ struct KonamiVRC4 : Board {
|
||||
return vrc4.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
return prgrom.read(vrc4.prg_addr(addr));
|
||||
return prgrom.read(vrc4.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
@@ -22,17 +22,17 @@ struct KonamiVRC4 : Board {
|
||||
bool a1 = (addr & settings.pinout.a1);
|
||||
addr &= 0xfff0;
|
||||
addr |= (a1 << 1) | (a0 << 0);
|
||||
return vrc4.reg_write(addr, data);
|
||||
return vrc4.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc4.ciram_addr(addr));
|
||||
return Board::chr_read(vrc4.chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc4.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc4.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc4.ciram_addr(addr), data);
|
||||
return Board::chr_write(vrc4.chr_addr(addr), data);
|
||||
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 {
|
||||
|
@@ -2,29 +2,29 @@ struct KonamiVRC6 : Board {
|
||||
KonamiVRC6(Markup::Node& document) : Board(document), vrc6(*this) {
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8{
|
||||
if((addr & 0xe000) == 0x6000) return vrc6.ram_read(addr);
|
||||
if(addr & 0x8000) return prgrom.read(vrc6.prg_addr(addr));
|
||||
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 prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return vrc6.ram_write(addr, data);
|
||||
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.reg_write(addr, data);
|
||||
return vrc6.writeIO(addr, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc6.ciram_addr(addr));
|
||||
return Board::chr_read(vrc6.chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc6.addrCIRAM(addr));
|
||||
return Board::readCHR(vrc6.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc6.ciram_addr(addr), data);
|
||||
return Board::chr_write(vrc6.chr_addr(addr), data);
|
||||
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 {
|
||||
|
@@ -6,26 +6,26 @@ struct KonamiVRC7 : Board {
|
||||
return vrc7.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
return prgrom.read(vrc7.prg_addr(addr));
|
||||
return prgrom.read(vrc7.addrPRG(addr));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
return vrc7.reg_write(addr, data);
|
||||
return vrc7.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc7.ciram_addr(addr));
|
||||
return chrram.read(vrc7.chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(vrc7.addrCIRAM(addr));
|
||||
return chrram.read(vrc7.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc7.ciram_addr(addr), data);
|
||||
return chrram.write(vrc7.chr_addr(addr), data);
|
||||
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 {
|
||||
|
@@ -7,43 +7,43 @@ struct NES_AxROM : Board {
|
||||
NES_AxROM(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) {
|
||||
prg_bank = data & 0x0f;
|
||||
mirror_select = data & 0x10;
|
||||
prgBank = data & 0x0f;
|
||||
mirrorSelect = data & 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read((mirror_select << 10) | (addr & 0x03ff));
|
||||
return Board::chr_read(addr);
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM((mirrorSelect << 10) | (addr & 0x03ff));
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write((mirror_select << 10) | (addr & 0x03ff), data);
|
||||
return Board::chr_write(addr, data);
|
||||
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 {
|
||||
prg_bank = 0x0f;
|
||||
mirror_select = 0;
|
||||
prgBank = 0x0f;
|
||||
mirrorSelect = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prg_bank);
|
||||
s.integer(mirror_select);
|
||||
s.integer(prgBank);
|
||||
s.integer(mirrorSelect);
|
||||
}
|
||||
|
||||
uint4 prg_bank;
|
||||
bool mirror_select;
|
||||
uint4 prgBank;
|
||||
bool mirrorSelect;
|
||||
};
|
||||
|
@@ -5,46 +5,46 @@ struct NES_BNROM : Board {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prg_bank = data & 0x03;
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prgBank = data & 0x03;
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr);
|
||||
return ppu.readCIRAM(addr);
|
||||
}
|
||||
return Board::chr_read(addr);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr, data);
|
||||
return ppu.writeCIRAM(addr, data);
|
||||
}
|
||||
return Board::chr_write(addr, data);
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(prg_bank);
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 prg_bank;
|
||||
uint2 prgBank;
|
||||
};
|
||||
|
@@ -5,48 +5,48 @@ struct NES_CNROM : Board {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(addr & 0x7fff);
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) chr_bank = data & 0x03;
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) chrBank = data & 0x03;
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr & 0x07ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::chr_read(addr);
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr & 0x07ff, data);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
|
||||
Board::chr_write(addr, data);
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chr_bank = 0;
|
||||
chrBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(chr_bank);
|
||||
s.integer(chrBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 chr_bank;
|
||||
uint2 chrBank;
|
||||
};
|
||||
|
@@ -7,20 +7,20 @@ struct NES_ExROM : Board {
|
||||
mmc5.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
return mmc5.prg_read(addr);
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
return mmc5.readPRG(addr);
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
mmc5.prg_write(addr, data);
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
mmc5.writePRG(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
return mmc5.chr_read(addr);
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
return mmc5.readCHR(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
mmc5.chr_write(addr, data);
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
mmc5.writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
|
@@ -5,61 +5,61 @@ struct NES_FxROM : Board {
|
||||
revision = Revision::FKROM;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
uint bank = addr < 0xc000 ? prg_bank : (uint4)0x0f;
|
||||
uint bank = addr < 0xc000 ? prgBank : (uint4)0x0f;
|
||||
return prgrom.read((bank * 0x4000) | (addr & 0x3fff));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
switch(addr & 0xf000) {
|
||||
case 0xa000: prg_bank = data & 0x0f; break;
|
||||
case 0xb000: chr_bank[0][0] = data & 0x1f; break;
|
||||
case 0xc000: chr_bank[0][1] = data & 0x1f; break;
|
||||
case 0xd000: chr_bank[1][0] = data & 0x1f; break;
|
||||
case 0xe000: chr_bank[1][1] = data & 0x1f; break;
|
||||
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 ciram_addr(uint addr) const -> uint {
|
||||
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 chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region][latch[region]];
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::chr_read((bank * 0x1000) | (addr & 0x0fff));
|
||||
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region][latch[region]];
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
chr_bank[0][0] = 0;
|
||||
chr_bank[0][1] = 0;
|
||||
chr_bank[1][0] = 0;
|
||||
chr_bank[1][1] = 0;
|
||||
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;
|
||||
@@ -68,11 +68,11 @@ struct NES_FxROM : Board {
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prg_bank);
|
||||
s.integer(chr_bank[0][0]);
|
||||
s.integer(chr_bank[0][1]);
|
||||
s.integer(chr_bank[1][0]);
|
||||
s.integer(chr_bank[1][1]);
|
||||
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);
|
||||
}
|
||||
@@ -82,8 +82,8 @@ struct NES_FxROM : Board {
|
||||
FKROM,
|
||||
} revision;
|
||||
|
||||
uint4 prg_bank;
|
||||
uint5 chr_bank[2][2];
|
||||
uint4 prgBank;
|
||||
uint5 chrBank[2][2];
|
||||
bool mirror;
|
||||
bool latch[2];
|
||||
};
|
||||
|
@@ -6,54 +6,54 @@ struct NES_GxROM : Board {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) {
|
||||
prg_bank = (data & 0x30) >> 4;
|
||||
chr_bank = (data & 0x03) >> 0;
|
||||
prgBank = (data & 0x30) >> 4;
|
||||
chrBank = (data & 0x03) >> 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr & 0x07ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::chr_read(addr);
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr & 0x07ff, data);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
|
||||
Board::chr_write(addr, data);
|
||||
addr = (chrBank * 0x2000) + (addr & 0x1fff);
|
||||
Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
chr_bank = 0;
|
||||
prgBank = 0;
|
||||
chrBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(prg_bank);
|
||||
s.integer(chr_bank);
|
||||
s.integer(prgBank);
|
||||
s.integer(chrBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 prg_bank;
|
||||
uint2 chr_bank;
|
||||
uint2 prgBank;
|
||||
uint2 chrBank;
|
||||
};
|
||||
|
@@ -6,27 +6,27 @@ struct NES_HKROM : Board {
|
||||
mmc6.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xf000) == 0x7000) return mmc6.ram_read(addr);
|
||||
if(addr & 0x8000) return prgrom.read(mmc6.prg_addr(addr));
|
||||
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 prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xf000) == 0x7000) return mmc6.ram_write(addr, data);
|
||||
if(addr & 0x8000) return mmc6.reg_write(addr, data);
|
||||
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 chr_read(uint addr) -> uint8 {
|
||||
mmc6.irq_test(addr);
|
||||
if(addr & 0x2000) return ppu.ciram_read(mmc6.ciram_addr(addr));
|
||||
return Board::chr_read(mmc6.chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
mmc6.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc6.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc6.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
mmc6.irq_test(addr);
|
||||
if(addr & 0x2000) return ppu.ciram_write(mmc6.ciram_addr(addr), data);
|
||||
return Board::chr_write(mmc6.chr_addr(addr), data);
|
||||
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 {
|
||||
|
@@ -6,27 +6,27 @@ struct NES_NROM : Board {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(addr);
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr & 0x07ff);
|
||||
return ppu.readCIRAM(addr & 0x07ff);
|
||||
}
|
||||
if(chrram.size) return chrram.read(addr);
|
||||
return chrrom.read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr & 0x07ff, data);
|
||||
return ppu.writeCIRAM(addr & 0x07ff, data);
|
||||
}
|
||||
if(chrram.size) return chrram.write(addr, data);
|
||||
}
|
||||
|
@@ -5,12 +5,12 @@ struct NES_PxROM : Board {
|
||||
revision = Revision::PNROM;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
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 = prg_bank; break;
|
||||
case 0: bank = prgBank; break;
|
||||
case 1: bank = 0x0d; break;
|
||||
case 2: bank = 0x0e; break;
|
||||
case 3: bank = 0x0f; break;
|
||||
@@ -18,54 +18,54 @@ struct NES_PxROM : Board {
|
||||
return prgrom.read((bank * 0x2000) | (addr & 0x1fff));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
switch(addr & 0xf000) {
|
||||
case 0xa000: prg_bank = data & 0x0f; break;
|
||||
case 0xb000: chr_bank[0][0] = data & 0x1f; break;
|
||||
case 0xc000: chr_bank[0][1] = data & 0x1f; break;
|
||||
case 0xd000: chr_bank[1][0] = data & 0x1f; break;
|
||||
case 0xe000: chr_bank[1][1] = data & 0x1f; break;
|
||||
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 ciram_addr(uint addr) const -> uint {
|
||||
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 chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region][latch[region]];
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::chr_read((bank * 0x1000) | (addr & 0x0fff));
|
||||
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region][latch[region]];
|
||||
uint bank = chrBank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
chr_bank[0][0] = 0;
|
||||
chr_bank[0][1] = 0;
|
||||
chr_bank[1][0] = 0;
|
||||
chr_bank[1][1] = 0;
|
||||
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;
|
||||
@@ -74,11 +74,11 @@ struct NES_PxROM : Board {
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prg_bank);
|
||||
s.integer(chr_bank[0][0]);
|
||||
s.integer(chr_bank[0][1]);
|
||||
s.integer(chr_bank[1][0]);
|
||||
s.integer(chr_bank[1][1]);
|
||||
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);
|
||||
}
|
||||
@@ -88,8 +88,8 @@ struct NES_PxROM : Board {
|
||||
PNROM,
|
||||
} revision;
|
||||
|
||||
uint4 prg_bank;
|
||||
uint5 chr_bank[2][2];
|
||||
uint4 prgBank;
|
||||
uint5 chrBank[2][2];
|
||||
bool mirror;
|
||||
bool latch[2];
|
||||
};
|
||||
|
@@ -7,27 +7,27 @@ struct NES_SxROM : Board {
|
||||
return mmc1.main();
|
||||
}
|
||||
|
||||
auto ram_addr(uint addr) -> uint {
|
||||
auto addrRAM(uint addr) -> uint {
|
||||
uint bank = 0;
|
||||
if(revision == Revision::SOROM) bank = (mmc1.chr_bank[0] & 0x08) >> 3;
|
||||
if(revision == Revision::SUROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2;
|
||||
if(revision == Revision::SXROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2;
|
||||
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 prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
if(revision == Revision::SNROM) {
|
||||
if(mmc1.chr_bank[0] & 0x10) return cpu.mdr();
|
||||
if(mmc1.chrBank[0] & 0x10) return cpu.mdr();
|
||||
}
|
||||
if(mmc1.ram_disable) return 0x00;
|
||||
return prgram.read(ram_addr(addr));
|
||||
if(mmc1.ramDisable) return 0x00;
|
||||
return prgram.read(addrRAM(addr));
|
||||
}
|
||||
|
||||
if(addr & 0x8000) {
|
||||
addr = mmc1.prg_addr(addr);
|
||||
addr = mmc1.addrPRG(addr);
|
||||
if(revision == Revision::SXROM) {
|
||||
addr |= ((mmc1.chr_bank[0] & 0x10) >> 4) << 18;
|
||||
addr |= ((mmc1.chrBank[0] & 0x10) >> 4) << 18;
|
||||
}
|
||||
return prgrom.read(addr);
|
||||
}
|
||||
@@ -35,26 +35,26 @@ struct NES_SxROM : Board {
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
if(revision == Revision::SNROM) {
|
||||
if(mmc1.chr_bank[0] & 0x10) return;
|
||||
if(mmc1.chrBank[0] & 0x10) return;
|
||||
}
|
||||
if(mmc1.ram_disable) return;
|
||||
return prgram.write(ram_addr(addr), data);
|
||||
if(mmc1.ramDisable) return;
|
||||
return prgram.write(addrRAM(addr), data);
|
||||
}
|
||||
|
||||
if(addr & 0x8000) return mmc1.mmio_write(addr, data);
|
||||
if(addr & 0x8000) return mmc1.writeIO(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(mmc1.ciram_addr(addr));
|
||||
return Board::chr_read(mmc1.chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc1.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc1.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(mmc1.ciram_addr(addr), data);
|
||||
return Board::chr_write(mmc1.chr_addr(addr), data);
|
||||
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 {
|
||||
|
@@ -7,27 +7,27 @@ struct NES_TxROM : Board {
|
||||
mmc3.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) return mmc3.ram_read(addr);
|
||||
if(addr & 0x8000) return prgrom.read(mmc3.prg_addr(addr));
|
||||
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 prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return mmc3.ram_write(addr, data);
|
||||
if(addr & 0x8000) return mmc3.reg_write(addr, data);
|
||||
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 chr_read(uint addr) -> uint8 {
|
||||
mmc3.irq_test(addr);
|
||||
if(addr & 0x2000) return ppu.ciram_read(mmc3.ciram_addr(addr));
|
||||
return Board::chr_read(mmc3.chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
mmc3.irqTest(addr);
|
||||
if(addr & 0x2000) return ppu.readCIRAM(mmc3.addrCIRAM(addr));
|
||||
return Board::readCHR(mmc3.addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
mmc3.irq_test(addr);
|
||||
if(addr & 0x2000) return ppu.ciram_write(mmc3.ciram_addr(addr), data);
|
||||
return Board::chr_write(mmc3.chr_addr(addr), data);
|
||||
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 {
|
||||
|
@@ -6,48 +6,48 @@ struct NES_UxROM : Board {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xc000) == 0x8000) return prgrom.read((prg_bank << 14) | (addr & 0x3fff));
|
||||
if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff));
|
||||
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 prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prg_bank = data & 0x0f;
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prgBank = data & 0x0f;
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr);
|
||||
return ppu.readCIRAM(addr);
|
||||
}
|
||||
return Board::chr_read(addr);
|
||||
return Board::readCHR(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr, data);
|
||||
return ppu.writeCIRAM(addr, data);
|
||||
}
|
||||
return Board::chr_write(addr, data);
|
||||
return Board::writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prg_bank);
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint4 prg_bank;
|
||||
uint4 prgBank;
|
||||
};
|
||||
|
@@ -44,9 +44,9 @@ struct Sunsoft5B : Board {
|
||||
} pulse[3];
|
||||
|
||||
auto main() -> void {
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(irq_enable);
|
||||
if(irqCounterEnable) {
|
||||
if(--irqCounter == 0xffff) {
|
||||
cpu.irqLine(irqEnable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,26 +54,26 @@ struct Sunsoft5B : Board {
|
||||
pulse[1].clock();
|
||||
pulse[2].clock();
|
||||
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
||||
apu.set_sample(-output);
|
||||
apu.setSample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
|
||||
uint8 bank = 0x3f; //((addr & 0xe000) == 0xe000
|
||||
if((addr & 0xe000) == 0x6000) bank = prg_bank[0];
|
||||
if((addr & 0xe000) == 0x8000) bank = prg_bank[1];
|
||||
if((addr & 0xe000) == 0xa000) bank = prg_bank[2];
|
||||
if((addr & 0xe000) == 0xc000) bank = prg_bank[3];
|
||||
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 ram_enable = bank & 0x80;
|
||||
bool ram_select = bank & 0x40;
|
||||
bool ramEnable = bank & 0x80;
|
||||
bool ramSelect = bank & 0x40;
|
||||
bank &= 0x3f;
|
||||
|
||||
if(ram_select) {
|
||||
if(ram_enable == false) return cpu.mdr();
|
||||
if(ramSelect) {
|
||||
if(!ramEnable) return cpu.mdr();
|
||||
return prgram.data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
@@ -81,46 +81,46 @@ struct Sunsoft5B : Board {
|
||||
return prgrom.read(addr);
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
if(addr == 0x8000) {
|
||||
mmu_port = data & 0x0f;
|
||||
mmuPort = data & 0x0f;
|
||||
}
|
||||
|
||||
if(addr == 0xa000) {
|
||||
switch(mmu_port) {
|
||||
case 0: chr_bank[0] = data; break;
|
||||
case 1: chr_bank[1] = data; break;
|
||||
case 2: chr_bank[2] = data; break;
|
||||
case 3: chr_bank[3] = data; break;
|
||||
case 4: chr_bank[4] = data; break;
|
||||
case 5: chr_bank[5] = data; break;
|
||||
case 6: chr_bank[6] = data; break;
|
||||
case 7: chr_bank[7] = data; break;
|
||||
case 8: prg_bank[0] = data; break;
|
||||
case 9: prg_bank[1] = data; break;
|
||||
case 10: prg_bank[2] = data; break;
|
||||
case 11: prg_bank[3] = data; break;
|
||||
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:
|
||||
irq_enable = data & 0x80;
|
||||
irq_counter_enable = data & 0x01;
|
||||
if(irq_enable == 0) cpu.set_irq_line(0);
|
||||
irqEnable = data & 0x80;
|
||||
irqCounterEnable = data & 0x01;
|
||||
if(irqEnable == 0) cpu.irqLine(0);
|
||||
break;
|
||||
case 14: irq_counter = (irq_counter & 0xff00) | (data << 0); break;
|
||||
case 15: irq_counter = (irq_counter & 0x00ff) | (data << 8); break;
|
||||
case 14: irqCounter = (irqCounter & 0xff00) | (data << 0); break;
|
||||
case 15: irqCounter = (irqCounter & 0x00ff) | (data << 8); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(addr == 0xc000) {
|
||||
apu_port = data & 0x0f;
|
||||
apuPort = data & 0x0f;
|
||||
}
|
||||
|
||||
if(addr == 0xe000) {
|
||||
switch(apu_port) {
|
||||
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;
|
||||
@@ -139,12 +139,12 @@ struct Sunsoft5B : Board {
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) -> uint {
|
||||
auto addrCHR(uint addr) -> uint {
|
||||
uint8 bank = (addr >> 10) & 7;
|
||||
return (chr_bank[bank] << 10) | (addr & 0x03ff);
|
||||
return (chrBank[bank] << 10) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) -> uint {
|
||||
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
|
||||
@@ -153,14 +153,14 @@ struct Sunsoft5B : Board {
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
|
||||
return Board::chr_read(chr_addr(addr));
|
||||
auto readCHR(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
|
||||
return Board::readCHR(addrCHR(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
|
||||
return Board::chr_write(chr_addr(addr), data);
|
||||
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 {
|
||||
@@ -171,15 +171,15 @@ struct Sunsoft5B : Board {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmu_port = 0;
|
||||
apu_port = 0;
|
||||
mmuPort = 0;
|
||||
apuPort = 0;
|
||||
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
irq_enable = 0;
|
||||
irq_counter_enable = 0;
|
||||
irq_counter = 0;
|
||||
irqEnable = 0;
|
||||
irqCounterEnable = 0;
|
||||
irqCounter = 0;
|
||||
|
||||
pulse[0].reset();
|
||||
pulse[1].reset();
|
||||
@@ -189,30 +189,30 @@ struct Sunsoft5B : Board {
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(mmu_port);
|
||||
s.integer(apu_port);
|
||||
s.integer(mmuPort);
|
||||
s.integer(apuPort);
|
||||
|
||||
s.array(prg_bank);
|
||||
s.array(chr_bank);
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_counter_enable);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqCounterEnable);
|
||||
s.integer(irqCounter);
|
||||
|
||||
pulse[0].serialize(s);
|
||||
pulse[1].serialize(s);
|
||||
pulse[2].serialize(s);
|
||||
}
|
||||
|
||||
uint4 mmu_port;
|
||||
uint4 apu_port;
|
||||
uint4 mmuPort;
|
||||
uint4 apuPort;
|
||||
|
||||
uint8 prg_bank[4];
|
||||
uint8 chr_bank[8];
|
||||
uint8 prgBank[4];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
bool irq_enable;
|
||||
bool irq_counter_enable;
|
||||
uint16 irq_counter;
|
||||
bool irqEnable;
|
||||
bool irqCounterEnable;
|
||||
uint16 irqCounter;
|
||||
|
||||
int16 dac[16];
|
||||
};
|
||||
|
@@ -6,18 +6,6 @@ namespace Famicom {
|
||||
#include "board/board.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::sha256() const -> string {
|
||||
return _sha256;
|
||||
}
|
||||
|
||||
auto Cartridge::manifest() const -> string {
|
||||
return information.markup;
|
||||
}
|
||||
|
||||
auto Cartridge::title() const -> string {
|
||||
return information.title;
|
||||
}
|
||||
|
||||
auto Cartridge::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cartridge.main();
|
||||
}
|
||||
@@ -26,20 +14,34 @@ auto Cartridge::main() -> void {
|
||||
board->main();
|
||||
}
|
||||
|
||||
auto Cartridge::load() -> void {
|
||||
interface->loadRequest(ID::Manifest, "manifest.bml", true);
|
||||
auto Cartridge::load() -> bool {
|
||||
if(auto pathID = platform->load(ID::Famicom, "Famicom", "fc")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
|
||||
Board::load(information.markup); //this call will set Cartridge::board if successful
|
||||
if(!board) return;
|
||||
if(auto fp = platform->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);
|
||||
_sha256 = sha.digest();
|
||||
sha.input(board->prgrom.data, board->prgrom.size);
|
||||
sha.input(board->chrrom.data, board->chrrom.size);
|
||||
information.sha256 = sha.digest();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
board->save();
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
memory.reset();
|
||||
delete board;
|
||||
board = nullptr;
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
@@ -47,24 +49,24 @@ auto Cartridge::power() -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::reset() -> void {
|
||||
create(Cartridge::Enter, 21'477'272);
|
||||
create(Cartridge::Enter, system.colorburst() * 6.0);
|
||||
board->reset();
|
||||
}
|
||||
|
||||
auto Cartridge::prg_read(uint addr) -> uint8 {
|
||||
return board->prg_read(addr);
|
||||
auto Cartridge::readPRG(uint addr) -> uint8 {
|
||||
return board->readPRG(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::prg_write(uint addr, uint8 data) -> void {
|
||||
return board->prg_write(addr, data);
|
||||
auto Cartridge::writePRG(uint addr, uint8 data) -> void {
|
||||
return board->writePRG(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::chr_read(uint addr) -> uint8 {
|
||||
return board->chr_read(addr);
|
||||
auto Cartridge::readCHR(uint addr) -> uint8 {
|
||||
return board->readCHR(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::chr_write(uint addr, uint8 data) -> void {
|
||||
return board->chr_write(addr, data);
|
||||
auto Cartridge::writeCHR(uint addr, uint8 data) -> void {
|
||||
return board->writeCHR(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::scanline(uint y) -> void {
|
||||
|
@@ -5,11 +5,13 @@ struct Cartridge : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
auto sha256() const -> string;
|
||||
auto manifest() const -> string;
|
||||
auto title() const -> string;
|
||||
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() -> void;
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
@@ -18,25 +20,20 @@ struct Cartridge : Thread {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Information {
|
||||
string markup;
|
||||
uint pathID = 0;
|
||||
string sha256;
|
||||
string manifest;
|
||||
string title;
|
||||
} information;
|
||||
|
||||
struct Memory {
|
||||
unsigned id;
|
||||
string name;
|
||||
};
|
||||
vector<Memory> memory;
|
||||
|
||||
//privileged:
|
||||
Board* board = nullptr;
|
||||
string _sha256;
|
||||
|
||||
auto prg_read(uint addr) -> uint8;
|
||||
auto prg_write(uint addr, uint8 data) -> void;
|
||||
auto readPRG(uint addr) -> uint8;
|
||||
auto writePRG(uint addr, uint8 data) -> void;
|
||||
|
||||
auto chr_read(uint addr) -> uint8;
|
||||
auto chr_write(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
|
||||
|
@@ -8,26 +8,26 @@ struct MMC1 : Chip {
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) -> uint {
|
||||
auto addrPRG(uint addr) -> uint {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (prg_bank & ~1) + region;
|
||||
uint bank = (prgBank & ~1) + region;
|
||||
|
||||
if(prg_size) {
|
||||
if(prgSize) {
|
||||
bank = (region == 0 ? 0x0 : 0xf);
|
||||
if(region != prg_mode) bank = prg_bank;
|
||||
if(region != prgMode) bank = prgBank;
|
||||
}
|
||||
|
||||
return (bank << 14) | (addr & 0x3fff);
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) -> uint {
|
||||
auto addrCHR(uint addr) -> uint {
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region];
|
||||
if(chr_mode == 0) bank = (chr_bank[0] & ~1) | region;
|
||||
uint bank = chrBank[region];
|
||||
if(chrMode == 0) bank = (chrBank[0] & ~1) | region;
|
||||
return (bank << 12) | (addr & 0x0fff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) -> uint {
|
||||
auto addrCIRAM(uint addr) -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return 0x0000 | (addr & 0x03ff);
|
||||
case 1: return 0x0400 | (addr & 0x03ff);
|
||||
@@ -36,37 +36,37 @@ struct MMC1 : Chip {
|
||||
}
|
||||
}
|
||||
|
||||
auto mmio_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
if(writedelay) return;
|
||||
writedelay = 2;
|
||||
|
||||
if(data & 0x80) {
|
||||
shiftaddr = 0;
|
||||
prg_size = 1;
|
||||
prg_mode = 1;
|
||||
prgSize = 1;
|
||||
prgMode = 1;
|
||||
} else {
|
||||
shiftdata = ((data & 1) << 4) | (shiftdata >> 1);
|
||||
if(++shiftaddr == 5) {
|
||||
shiftaddr = 0;
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
chr_mode = (shiftdata & 0x10);
|
||||
prg_size = (shiftdata & 0x08);
|
||||
prg_mode = (shiftdata & 0x04);
|
||||
chrMode = (shiftdata & 0x10);
|
||||
prgSize = (shiftdata & 0x08);
|
||||
prgMode = (shiftdata & 0x04);
|
||||
mirror = (shiftdata & 0x03);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
chr_bank[0] = (shiftdata & 0x1f);
|
||||
chrBank[0] = (shiftdata & 0x1f);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
chr_bank[1] = (shiftdata & 0x1f);
|
||||
chrBank[1] = (shiftdata & 0x1f);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
ram_disable = (shiftdata & 0x10);
|
||||
prg_bank = (shiftdata & 0x0f);
|
||||
ramDisable = (shiftdata & 0x10);
|
||||
prgBank = (shiftdata & 0x0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -81,14 +81,14 @@ struct MMC1 : Chip {
|
||||
shiftaddr = 0;
|
||||
shiftdata = 0;
|
||||
|
||||
chr_mode = 0;
|
||||
prg_size = 1;
|
||||
prg_mode = 1;
|
||||
chrMode = 0;
|
||||
prgSize = 1;
|
||||
prgMode = 1;
|
||||
mirror = 0;
|
||||
chr_bank[0] = 0;
|
||||
chr_bank[1] = 1;
|
||||
ram_disable = 0;
|
||||
prg_bank = 0;
|
||||
chrBank[0] = 0;
|
||||
chrBank[1] = 1;
|
||||
ramDisable = 0;
|
||||
prgBank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
@@ -96,13 +96,13 @@ struct MMC1 : Chip {
|
||||
s.integer(shiftaddr);
|
||||
s.integer(shiftdata);
|
||||
|
||||
s.integer(chr_mode);
|
||||
s.integer(prg_size);
|
||||
s.integer(prg_mode);
|
||||
s.integer(chrMode);
|
||||
s.integer(prgSize);
|
||||
s.integer(prgMode);
|
||||
s.integer(mirror);
|
||||
s.array(chr_bank);
|
||||
s.integer(ram_disable);
|
||||
s.integer(prg_bank);
|
||||
s.array(chrBank);
|
||||
s.integer(ramDisable);
|
||||
s.integer(prgBank);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
@@ -118,11 +118,11 @@ struct MMC1 : Chip {
|
||||
uint shiftaddr;
|
||||
uint shiftdata;
|
||||
|
||||
bool chr_mode;
|
||||
bool prg_size; //0 = 32K, 1 = 16K
|
||||
bool prg_mode;
|
||||
bool chrMode;
|
||||
bool prgSize; //0 = 32K, 1 = 16K
|
||||
bool prgMode;
|
||||
uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal
|
||||
uint5 chr_bank[2];
|
||||
bool ram_disable;
|
||||
uint4 prg_bank;
|
||||
uint5 chrBank[2];
|
||||
bool ramDisable;
|
||||
uint4 prgBank;
|
||||
};
|
||||
|
@@ -3,90 +3,90 @@ struct MMC3 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
if(irqDelay) irqDelay--;
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irq_test(uint addr) -> void {
|
||||
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
|
||||
if(irq_delay == 0) {
|
||||
if(irq_counter == 0) {
|
||||
irq_counter = irq_latch;
|
||||
} else if(--irq_counter == 0) {
|
||||
if(irq_enable) irq_line = 1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
irq_delay = 6;
|
||||
irqDelay = 6;
|
||||
}
|
||||
chr_abus = addr;
|
||||
chrAbus = addr;
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
if(prg_mode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prg_bank[0] << 13) | (addr & 0x1fff);
|
||||
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 1:
|
||||
return (prg_bank[1] << 13) | (addr & 0x1fff);
|
||||
return (prgBank[1] << 13) | (addr & 0x1fff);
|
||||
case 2:
|
||||
if(prg_mode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prg_bank[0] << 13) | (addr & 0x1fff);
|
||||
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
if(chr_mode == 0) {
|
||||
if(addr <= 0x07ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x0fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x13ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
|
||||
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 (chr_bank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x07ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
|
||||
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 ciram_addr(uint addr) const -> uint {
|
||||
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 ram_read(uint addr) -> uint8 {
|
||||
if(ram_enable) return board.prgram.data[addr & 0x1fff];
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
if(ramEnable) return board.prgram.data[addr & 0x1fff];
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto ram_write(uint addr, uint8 data) -> void {
|
||||
if(ram_enable && !ram_write_protect) board.prgram.data[addr & 0x1fff] = data;
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(ramEnable && !ramWriteProtect) board.prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xe001) {
|
||||
case 0x8000:
|
||||
chr_mode = data & 0x80;
|
||||
prg_mode = data & 0x40;
|
||||
bank_select = data & 0x07;
|
||||
chrMode = data & 0x80;
|
||||
prgMode = data & 0x40;
|
||||
bankSelect = data & 0x07;
|
||||
break;
|
||||
|
||||
case 0x8001:
|
||||
switch(bank_select) {
|
||||
case 0: chr_bank[0] = data & ~1; break;
|
||||
case 1: chr_bank[1] = data & ~1; break;
|
||||
case 2: chr_bank[2] = data; break;
|
||||
case 3: chr_bank[3] = data; break;
|
||||
case 4: chr_bank[4] = data; break;
|
||||
case 5: chr_bank[5] = data; break;
|
||||
case 6: prg_bank[0] = data & 0x3f; break;
|
||||
case 7: prg_bank[1] = data & 0x3f; break;
|
||||
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;
|
||||
|
||||
@@ -95,25 +95,25 @@ struct MMC3 : Chip {
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
ram_enable = data & 0x80;
|
||||
ram_write_protect = data & 0x40;
|
||||
ramEnable = data & 0x80;
|
||||
ramWriteProtect = data & 0x40;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
irq_latch = data;
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irq_counter = 0;
|
||||
irqCounter = 0;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
irq_enable = false;
|
||||
irq_line = 0;
|
||||
irqEnable = false;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xe001:
|
||||
irq_enable = true;
|
||||
irqEnable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -122,60 +122,60 @@ struct MMC3 : Chip {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chr_mode = 0;
|
||||
prg_mode = 0;
|
||||
bank_select = 0;
|
||||
prg_bank[0] = 0;
|
||||
prg_bank[1] = 0;
|
||||
chr_bank[0] = 0;
|
||||
chr_bank[1] = 0;
|
||||
chr_bank[2] = 0;
|
||||
chr_bank[3] = 0;
|
||||
chr_bank[4] = 0;
|
||||
chr_bank[5] = 0;
|
||||
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;
|
||||
ram_enable = 1;
|
||||
ram_write_protect = 0;
|
||||
irq_latch = 0;
|
||||
irq_counter = 0;
|
||||
irq_enable = false;
|
||||
irq_delay = 0;
|
||||
irq_line = 0;
|
||||
ramEnable = 1;
|
||||
ramWriteProtect = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter = 0;
|
||||
irqEnable = false;
|
||||
irqDelay = 0;
|
||||
irqLine = 0;
|
||||
|
||||
chr_abus = 0;
|
||||
chrAbus = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(chr_mode);
|
||||
s.integer(prg_mode);
|
||||
s.integer(bank_select);
|
||||
s.array(prg_bank);
|
||||
s.array(chr_bank);
|
||||
s.integer(chrMode);
|
||||
s.integer(prgMode);
|
||||
s.integer(bankSelect);
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(ram_enable);
|
||||
s.integer(ram_write_protect);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_delay);
|
||||
s.integer(irq_line);
|
||||
s.integer(ramEnable);
|
||||
s.integer(ramWriteProtect);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqDelay);
|
||||
s.integer(irqLine);
|
||||
|
||||
s.integer(chr_abus);
|
||||
s.integer(chrAbus);
|
||||
}
|
||||
|
||||
bool chr_mode;
|
||||
bool prg_mode;
|
||||
uint3 bank_select;
|
||||
uint8 prg_bank[2];
|
||||
uint8 chr_bank[6];
|
||||
bool chrMode;
|
||||
bool prgMode;
|
||||
uint3 bankSelect;
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[6];
|
||||
bool mirror;
|
||||
bool ram_enable;
|
||||
bool ram_write_protect;
|
||||
uint8 irq_latch;
|
||||
uint8 irq_counter;
|
||||
bool irq_enable;
|
||||
uint irq_delay;
|
||||
bool irq_line;
|
||||
bool ramEnable;
|
||||
bool ramWriteProtect;
|
||||
uint8 irqLatch;
|
||||
uint8 irqCounter;
|
||||
bool irqEnable;
|
||||
uint irqDelay;
|
||||
bool irqLine;
|
||||
|
||||
uint16 chr_abus;
|
||||
uint16 chrAbus;
|
||||
};
|
||||
|
@@ -5,9 +5,9 @@ struct MMC5 : Chip {
|
||||
|
||||
auto main() -> void {
|
||||
//scanline() resets this; if no scanlines detected, enter video blanking period
|
||||
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
||||
if(++cpuCycleCounter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
||||
|
||||
cpu.set_irq_line(irq_enable && irq_pending);
|
||||
cpu.irqLine(irqEnable && irqPending);
|
||||
tick();
|
||||
}
|
||||
|
||||
@@ -16,30 +16,30 @@ struct MMC5 : Chip {
|
||||
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
|
||||
}
|
||||
|
||||
auto prg_access(bool write, uint addr, uint8 data = 0x00) -> uint8 {
|
||||
auto accessPRG(bool write, uint addr, uint8 data = 0x00) -> uint8 {
|
||||
uint bank;
|
||||
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
bank = (ram_select << 2) | ram_bank;
|
||||
bank = (ramSelect << 2) | ramBank;
|
||||
addr &= 0x1fff;
|
||||
} else if(prg_mode == 0) {
|
||||
bank = prg_bank[3] & ~3;
|
||||
} else if(prgMode == 0) {
|
||||
bank = prgBank[3] & ~3;
|
||||
addr &= 0x7fff;
|
||||
} else if(prg_mode == 1) {
|
||||
if((addr & 0xc000) == 0x8000) bank = (prg_bank[1] & ~1);
|
||||
if((addr & 0xe000) == 0xc000) bank = (prg_bank[3] & ~1);
|
||||
} else if(prgMode == 1) {
|
||||
if((addr & 0xc000) == 0x8000) bank = (prgBank[1] & ~1);
|
||||
if((addr & 0xe000) == 0xc000) bank = (prgBank[3] & ~1);
|
||||
addr &= 0x3fff;
|
||||
} else if(prg_mode == 2) {
|
||||
if((addr & 0xe000) == 0x8000) bank = (prg_bank[1] & ~1) | 0;
|
||||
if((addr & 0xe000) == 0xa000) bank = (prg_bank[1] & ~1) | 1;
|
||||
if((addr & 0xe000) == 0xc000) bank = (prg_bank[2]);
|
||||
if((addr & 0xe000) == 0xe000) bank = (prg_bank[3]);
|
||||
} 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(prg_mode == 3) {
|
||||
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
|
||||
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
|
||||
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
|
||||
if((addr & 0xe000) == 0xe000) bank = prg_bank[3];
|
||||
} 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;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ struct MMC5 : Chip {
|
||||
if(rom) {
|
||||
board.prgrom.write((bank << 13) | addr, data);
|
||||
} else {
|
||||
if(prgram_write_protect[0] == 2 && prgram_write_protect[1] == 1) {
|
||||
if(prgramWriteProtect[0] == 2 && prgramWriteProtect[1] == 1) {
|
||||
board.prgram.write((bank << 13) | addr, data);
|
||||
}
|
||||
}
|
||||
@@ -64,20 +64,20 @@ struct MMC5 : Chip {
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
auto readPRG(uint addr) -> uint8 {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
if(exram_mode >= 2) return exram[addr & 0x03ff];
|
||||
if(exramMode >= 2) return exram[addr & 0x03ff];
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
if(addr >= 0x6000) {
|
||||
return prg_access(0, addr);
|
||||
return accessPRG(0, addr);
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x5204: {
|
||||
uint8 result = (irq_pending << 7) | (in_frame << 6);
|
||||
irq_pending = false;
|
||||
uint8 result = (irqPending << 7) | (inFrame << 6);
|
||||
irqPending = false;
|
||||
return result;
|
||||
}
|
||||
case 0x5205: return (multiplier * multiplicand) >> 0;
|
||||
@@ -85,22 +85,22 @@ struct MMC5 : Chip {
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
auto writePRG(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
//writes 0x00 *during* Vblank (not during screen rendering ...)
|
||||
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : (uint8)0x00;
|
||||
if(exram_mode == 2) exram[addr & 0x03ff] = data;
|
||||
if(exramMode == 0 || exramMode == 1) exram[addr & 0x03ff] = inFrame ? data : (uint8)0x00;
|
||||
if(exramMode == 2) exram[addr & 0x03ff] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr >= 0x6000) {
|
||||
prg_access(1, addr, data);
|
||||
accessPRG(1, addr, data);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x2000:
|
||||
sprite_8x16 = data & 0x20;
|
||||
sprite8x16 = data & 0x20;
|
||||
break;
|
||||
|
||||
case 0x2001:
|
||||
@@ -108,81 +108,81 @@ struct MMC5 : Chip {
|
||||
if((data & 0x18) == 0) blank();
|
||||
break;
|
||||
|
||||
case 0x5100: prg_mode = data & 3; break;
|
||||
case 0x5101: chr_mode = data & 3; break;
|
||||
case 0x5100: prgMode = data & 3; break;
|
||||
case 0x5101: chrMode = data & 3; break;
|
||||
|
||||
case 0x5102: prgram_write_protect[0] = data & 3; break;
|
||||
case 0x5103: prgram_write_protect[1] = data & 3; break;
|
||||
case 0x5102: prgramWriteProtect[0] = data & 3; break;
|
||||
case 0x5103: prgramWriteProtect[1] = data & 3; break;
|
||||
|
||||
case 0x5104:
|
||||
exram_mode = data & 3;
|
||||
exramMode = data & 3;
|
||||
break;
|
||||
|
||||
case 0x5105:
|
||||
nametable_mode[0] = (data & 0x03) >> 0;
|
||||
nametable_mode[1] = (data & 0x0c) >> 2;
|
||||
nametable_mode[2] = (data & 0x30) >> 4;
|
||||
nametable_mode[3] = (data & 0xc0) >> 6;
|
||||
nametableMode[0] = (data & 0x03) >> 0;
|
||||
nametableMode[1] = (data & 0x0c) >> 2;
|
||||
nametableMode[2] = (data & 0x30) >> 4;
|
||||
nametableMode[3] = (data & 0xc0) >> 6;
|
||||
break;
|
||||
|
||||
case 0x5106:
|
||||
fillmode_tile = data;
|
||||
fillmodeTile = data;
|
||||
break;
|
||||
|
||||
case 0x5107:
|
||||
fillmode_color = data & 3;
|
||||
fillmode_color |= fillmode_color << 2;
|
||||
fillmode_color |= fillmode_color << 4;
|
||||
fillmodeColor = data & 3;
|
||||
fillmodeColor |= fillmodeColor << 2;
|
||||
fillmodeColor |= fillmodeColor << 4;
|
||||
break;
|
||||
|
||||
case 0x5113:
|
||||
ram_select = data & 0x04;
|
||||
ram_bank = data & 0x03;
|
||||
ramSelect = data & 0x04;
|
||||
ramBank = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0x5114: prg_bank[0] = data; break;
|
||||
case 0x5115: prg_bank[1] = data; break;
|
||||
case 0x5116: prg_bank[2] = data; break;
|
||||
case 0x5117: prg_bank[3] = data | 0x80; 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: chr_sprite_bank[0] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5121: chr_sprite_bank[1] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5122: chr_sprite_bank[2] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5123: chr_sprite_bank[3] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5124: chr_sprite_bank[4] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5125: chr_sprite_bank[5] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5126: chr_sprite_bank[6] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5127: chr_sprite_bank[7] = (chr_bank_hi << 8) | data; chr_active = 0; 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: chr_bg_bank[0] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
||||
case 0x5129: chr_bg_bank[1] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
||||
case 0x512a: chr_bg_bank[2] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
||||
case 0x512b: chr_bg_bank[3] = (chr_bank_hi << 8) | data; chr_active = 1; 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:
|
||||
chr_bank_hi = data & 3;
|
||||
chrBankHi = data & 3;
|
||||
break;
|
||||
|
||||
case 0x5200:
|
||||
vs_enable = data & 0x80;
|
||||
vs_side = data & 0x40;
|
||||
vs_tile = data & 0x1f;
|
||||
vsEnable = data & 0x80;
|
||||
vsSide = data & 0x40;
|
||||
vsTile = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x5201:
|
||||
vs_scroll = data;
|
||||
vsScroll = data;
|
||||
break;
|
||||
|
||||
case 0x5202:
|
||||
vs_bank = data;
|
||||
vsBank = data;
|
||||
break;
|
||||
|
||||
case 0x5203:
|
||||
irq_line = data;
|
||||
irqLine = data;
|
||||
break;
|
||||
|
||||
case 0x5204:
|
||||
irq_enable = data & 0x80;
|
||||
irqEnable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0x5205:
|
||||
@@ -195,140 +195,140 @@ struct MMC5 : Chip {
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_sprite_addr(uint addr) -> uint {
|
||||
if(chr_mode == 0) {
|
||||
auto bank = chr_sprite_bank[7];
|
||||
auto chrSpriteAddr(uint addr) -> uint {
|
||||
if(chrMode == 0) {
|
||||
auto bank = chrSpriteBank[7];
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
if(chr_mode == 1) {
|
||||
auto bank = chr_sprite_bank[(addr / 0x1000) * 4 + 3];
|
||||
if(chrMode == 1) {
|
||||
auto bank = chrSpriteBank[(addr / 0x1000) * 4 + 3];
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chr_mode == 2) {
|
||||
auto bank = chr_sprite_bank[(addr / 0x0800) * 2 + 1];
|
||||
if(chrMode == 2) {
|
||||
auto bank = chrSpriteBank[(addr / 0x0800) * 2 + 1];
|
||||
return (bank * 0x0800) + (addr & 0x07ff);
|
||||
}
|
||||
|
||||
if(chr_mode == 3) {
|
||||
auto bank = chr_sprite_bank[(addr / 0x0400)];
|
||||
if(chrMode == 3) {
|
||||
auto bank = chrSpriteBank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_bg_addr(uint addr) -> uint {
|
||||
auto chrBGAddr(uint addr) -> uint {
|
||||
addr &= 0x0fff;
|
||||
|
||||
if(chr_mode == 0) {
|
||||
auto bank = chr_bg_bank[3];
|
||||
if(chrMode == 0) {
|
||||
auto bank = chrBGBank[3];
|
||||
return (bank * 0x2000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chr_mode == 1) {
|
||||
auto bank = chr_bg_bank[3];
|
||||
if(chrMode == 1) {
|
||||
auto bank = chrBGBank[3];
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chr_mode == 2) {
|
||||
auto bank = chr_bg_bank[(addr / 0x0800) * 2 + 1];
|
||||
if(chrMode == 2) {
|
||||
auto bank = chrBGBank[(addr / 0x0800) * 2 + 1];
|
||||
return (bank * 0x0800) + (addr & 0x07ff);
|
||||
}
|
||||
|
||||
if(chr_mode == 3) {
|
||||
auto bank = chr_bg_bank[(addr / 0x0400)];
|
||||
if(chrMode == 3) {
|
||||
auto bank = chrBGBank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_vs_addr(uint addr) -> uint {
|
||||
return (vs_bank * 0x1000) + (addr & 0x0ff8) + (vs_vpos & 7);
|
||||
auto chrVSAddr(uint addr) -> uint {
|
||||
return (vsBank * 0x1000) + (addr & 0x0ff8) + (vsVpos & 7);
|
||||
}
|
||||
|
||||
auto blank() -> void {
|
||||
in_frame = false;
|
||||
inFrame = false;
|
||||
}
|
||||
|
||||
auto scanline() -> void {
|
||||
hcounter = 0;
|
||||
|
||||
if(in_frame == false) {
|
||||
in_frame = true;
|
||||
irq_pending = false;
|
||||
if(inFrame == false) {
|
||||
inFrame = true;
|
||||
irqPending = false;
|
||||
vcounter = 0;
|
||||
} else {
|
||||
if(vcounter == irq_line) irq_pending = true;
|
||||
if(vcounter == irqLine) irqPending = true;
|
||||
vcounter++;
|
||||
}
|
||||
|
||||
cpu_cycle_counter = 0;
|
||||
cpuCycleCounter = 0;
|
||||
}
|
||||
|
||||
auto ciram_read(uint addr) -> uint8 {
|
||||
if(vs_fetch && (hcounter & 2) == 0) return exram[vs_vpos / 8 * 32 + vs_hpos / 8];
|
||||
if(vs_fetch && (hcounter & 2) != 0) return exram[vs_vpos / 32 * 8 + vs_hpos / 32 + 0x03c0];
|
||||
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(nametable_mode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
|
||||
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
|
||||
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
|
||||
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
|
||||
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 chr_read(uint addr) -> uint8 {
|
||||
chr_access[0] = chr_access[1];
|
||||
chr_access[1] = chr_access[2];
|
||||
chr_access[2] = chr_access[3];
|
||||
chr_access[3] = addr;
|
||||
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((chr_access[0] & 0x2000) == 0
|
||||
&& (chr_access[1] & 0x2000)
|
||||
&& (chr_access[2] & 0x2000)
|
||||
&& (chr_access[3] & 0x2000)) scanline();
|
||||
if((chrAccess[0] & 0x2000) == 0
|
||||
&& (chrAccess[1] & 0x2000)
|
||||
&& (chrAccess[2] & 0x2000)
|
||||
&& (chrAccess[3] & 0x2000)) scanline();
|
||||
|
||||
if(in_frame == false) {
|
||||
vs_fetch = false;
|
||||
if(addr & 0x2000) return ciram_read(addr);
|
||||
return board.chrrom.read(chr_active ? chr_bg_addr(addr) : chr_sprite_addr(addr));
|
||||
if(inFrame == false) {
|
||||
vsFetch = false;
|
||||
if(addr & 0x2000) return readCIRAM(addr);
|
||||
return board.chrrom.read(chrActive ? chrBGAddr(addr) : chrSpriteAddr(addr));
|
||||
}
|
||||
|
||||
bool bg_fetch = (hcounter < 256 || hcounter >= 320);
|
||||
bool bgFetch = (hcounter < 256 || hcounter >= 320);
|
||||
uint8 result = 0x00;
|
||||
|
||||
if((hcounter & 7) == 0) {
|
||||
vs_hpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
|
||||
vs_vpos = vcounter + vs_scroll;
|
||||
vs_fetch = vs_enable && bg_fetch && exram_mode < 2
|
||||
&& (vs_side ? vs_hpos / 8 >= vs_tile : vs_hpos / 8 < vs_tile);
|
||||
if(vs_vpos >= 240) vs_vpos -= 240;
|
||||
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 = ciram_read(addr);
|
||||
result = readCIRAM(addr);
|
||||
|
||||
exbank = (chr_bank_hi << 6) | (exram[addr & 0x03ff] & 0x3f);
|
||||
exbank = (chrBankHi << 6) | (exram[addr & 0x03ff] & 0x3f);
|
||||
exattr = exram[addr & 0x03ff] >> 6;
|
||||
exattr |= exattr << 2;
|
||||
exattr |= exattr << 4;
|
||||
} else if((hcounter & 7) == 2) {
|
||||
result = ciram_read(addr);
|
||||
if(bg_fetch && exram_mode == 1) result = exattr;
|
||||
result = readCIRAM(addr);
|
||||
if(bgFetch && exramMode == 1) result = exattr;
|
||||
} else {
|
||||
if(vs_fetch) result = board.chrrom.read(chr_vs_addr(addr));
|
||||
else if(sprite_8x16 ? bg_fetch : chr_active) result = board.chrrom.read(chr_bg_addr(addr));
|
||||
else result = board.chrrom.read(chr_sprite_addr(addr));
|
||||
if(bg_fetch && exram_mode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
|
||||
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 chr_write(uint addr, uint8 data) -> void {
|
||||
auto writeCHR(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
switch(nametable_mode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.ciram_write(0x0000 | (addr & 0x03ff), data);
|
||||
case 1: return ppu.ciram_write(0x0400 | (addr & 0x03ff), data);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -340,93 +340,93 @@ struct MMC5 : Chip {
|
||||
auto reset() -> void {
|
||||
for(auto& n : exram) n = 0xff;
|
||||
|
||||
prg_mode = 3;
|
||||
chr_mode = 0;
|
||||
for(auto& n : prgram_write_protect) n = 0;
|
||||
exram_mode = 0;
|
||||
for(auto& n : nametable_mode) n = 0;
|
||||
fillmode_tile = 0;
|
||||
fillmode_color = 0;
|
||||
ram_select = 0;
|
||||
ram_bank = 0;
|
||||
prg_bank[0] = 0x00;
|
||||
prg_bank[1] = 0x00;
|
||||
prg_bank[2] = 0x00;
|
||||
prg_bank[3] = 0xff;
|
||||
for(auto& n : chr_sprite_bank) n = 0;
|
||||
for(auto& n : chr_bg_bank) n = 0;
|
||||
chr_bank_hi = 0;
|
||||
vs_enable = 0;
|
||||
vs_side = 0;
|
||||
vs_tile = 0;
|
||||
vs_scroll = 0;
|
||||
vs_bank = 0;
|
||||
irq_line = 0;
|
||||
irq_enable = 0;
|
||||
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;
|
||||
|
||||
cpu_cycle_counter = 0;
|
||||
irq_counter = 0;
|
||||
irq_pending = 0;
|
||||
in_frame = 0;
|
||||
cpuCycleCounter = 0;
|
||||
irqCounter = 0;
|
||||
irqPending = 0;
|
||||
inFrame = 0;
|
||||
vcounter = 0;
|
||||
hcounter = 0;
|
||||
for(auto& n : chr_access) n = 0;
|
||||
chr_active = 0;
|
||||
sprite_8x16 = 0;
|
||||
for(auto& n : chrAccess) n = 0;
|
||||
chrActive = 0;
|
||||
sprite8x16 = 0;
|
||||
|
||||
exbank = 0;
|
||||
exattr = 0;
|
||||
|
||||
vs_fetch = 0;
|
||||
vs_vpos = 0;
|
||||
vs_hpos = 0;
|
||||
vsFetch = 0;
|
||||
vsVpos = 0;
|
||||
vsHpos = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(exram);
|
||||
|
||||
s.integer(prg_mode);
|
||||
s.integer(chr_mode);
|
||||
for(auto& n : prgram_write_protect) s.integer(n);
|
||||
s.integer(exram_mode);
|
||||
for(auto& n : nametable_mode) s.integer(n);
|
||||
s.integer(fillmode_tile);
|
||||
s.integer(fillmode_color);
|
||||
s.integer(ram_select);
|
||||
s.integer(ram_bank);
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
for(auto& n : chr_sprite_bank) s.integer(n);
|
||||
for(auto& n : chr_bg_bank) s.integer(n);
|
||||
s.integer(chr_bank_hi);
|
||||
s.integer(vs_enable);
|
||||
s.integer(vs_side);
|
||||
s.integer(vs_tile);
|
||||
s.integer(vs_scroll);
|
||||
s.integer(vs_bank);
|
||||
s.integer(irq_line);
|
||||
s.integer(irq_enable);
|
||||
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(cpu_cycle_counter);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_pending);
|
||||
s.integer(in_frame);
|
||||
s.integer(cpuCycleCounter);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqPending);
|
||||
s.integer(inFrame);
|
||||
|
||||
s.integer(vcounter);
|
||||
s.integer(hcounter);
|
||||
for(auto& n : chr_access) s.integer(n);
|
||||
s.integer(chr_active);
|
||||
s.integer(sprite_8x16);
|
||||
for(auto& n : chrAccess) s.integer(n);
|
||||
s.integer(chrActive);
|
||||
s.integer(sprite8x16);
|
||||
|
||||
s.integer(exbank);
|
||||
s.integer(exattr);
|
||||
|
||||
s.integer(vs_fetch);
|
||||
s.integer(vs_vpos);
|
||||
s.integer(vs_hpos);
|
||||
s.integer(vsFetch);
|
||||
s.integer(vsVpos);
|
||||
s.integer(vsHpos);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
@@ -438,52 +438,52 @@ struct MMC5 : Chip {
|
||||
|
||||
//programmable registers
|
||||
|
||||
uint2 prg_mode; //$5100
|
||||
uint2 chr_mode; //$5101
|
||||
uint2 prgMode; //$5100
|
||||
uint2 chrMode; //$5101
|
||||
|
||||
uint2 prgram_write_protect[2]; //$5102,$5103
|
||||
uint2 prgramWriteProtect[2]; //$5102,$5103
|
||||
|
||||
uint2 exram_mode; //$5104
|
||||
uint2 nametable_mode[4]; //$5105
|
||||
uint8 fillmode_tile; //$5106
|
||||
uint8 fillmode_color; //$5107
|
||||
uint2 exramMode; //$5104
|
||||
uint2 nametableMode[4]; //$5105
|
||||
uint8 fillmodeTile; //$5106
|
||||
uint8 fillmodeColor; //$5107
|
||||
|
||||
bool ram_select; //$5113
|
||||
uint2 ram_bank; //$5113
|
||||
uint8 prg_bank[4]; //$5114-5117
|
||||
uint10 chr_sprite_bank[8]; //$5120-5127
|
||||
uint10 chr_bg_bank[4]; //$5128-512b
|
||||
uint2 chr_bank_hi; //$5130
|
||||
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 vs_enable; //$5200
|
||||
bool vs_side; //$5200
|
||||
uint5 vs_tile; //$5200
|
||||
uint8 vs_scroll; //$5201
|
||||
uint8 vs_bank; //$5202
|
||||
bool vsEnable; //$5200
|
||||
bool vsSide; //$5200
|
||||
uint5 vsTile; //$5200
|
||||
uint8 vsScroll; //$5201
|
||||
uint8 vsBank; //$5202
|
||||
|
||||
uint8 irq_line; //$5203
|
||||
bool irq_enable; //$5204
|
||||
uint8 irqLine; //$5203
|
||||
bool irqEnable; //$5204
|
||||
|
||||
uint8 multiplicand; //$5205
|
||||
uint8 multiplier; //$5206
|
||||
|
||||
//status registers
|
||||
|
||||
uint cpu_cycle_counter;
|
||||
uint irq_counter;
|
||||
bool irq_pending;
|
||||
bool in_frame;
|
||||
uint cpuCycleCounter;
|
||||
uint irqCounter;
|
||||
bool irqPending;
|
||||
bool inFrame;
|
||||
|
||||
uint vcounter;
|
||||
uint hcounter;
|
||||
uint16 chr_access[4];
|
||||
bool chr_active;
|
||||
bool sprite_8x16;
|
||||
uint16 chrAccess[4];
|
||||
bool chrActive;
|
||||
bool sprite8x16;
|
||||
|
||||
uint8 exbank;
|
||||
uint8 exattr;
|
||||
|
||||
bool vs_fetch;
|
||||
uint8 vs_vpos;
|
||||
uint8 vs_hpos;
|
||||
bool vsFetch;
|
||||
uint8 vsVpos;
|
||||
uint8 vsHpos;
|
||||
};
|
||||
|
@@ -3,101 +3,101 @@ struct MMC6 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
if(irqDelay) irqDelay--;
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irq_test(uint addr) -> void {
|
||||
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
|
||||
if(irq_delay == 0) {
|
||||
if(irq_counter == 0) {
|
||||
irq_counter = irq_latch;
|
||||
} else if(--irq_counter == 0) {
|
||||
if(irq_enable) irq_line = 1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
irq_delay = 6;
|
||||
irqDelay = 6;
|
||||
}
|
||||
chr_abus = addr;
|
||||
chrAbus = addr;
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
if(prg_mode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prg_bank[0] << 13) | (addr & 0x1fff);
|
||||
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 1:
|
||||
return (prg_bank[1] << 13) | (addr & 0x1fff);
|
||||
return (prgBank[1] << 13) | (addr & 0x1fff);
|
||||
case 2:
|
||||
if(prg_mode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prg_bank[0] << 13) | (addr & 0x1fff);
|
||||
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prgBank[0] << 13) | (addr & 0x1fff);
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
if(chr_mode == 0) {
|
||||
if(addr <= 0x07ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x0fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x13ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
|
||||
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 (chr_bank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x07ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
|
||||
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 ciram_addr(uint addr) const -> uint {
|
||||
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 ram_read(uint addr) -> uint8 {
|
||||
if(ram_enable == false) return cpu.mdr();
|
||||
if(ram_readable[0] == false && ram_readable[1] == false) return cpu.mdr();
|
||||
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(ram_readable[region] == false) return 0x00;
|
||||
if(ramReadable[region] == false) return 0x00;
|
||||
return board.prgram.read((region * 0x0200) + (addr & 0x01ff));
|
||||
}
|
||||
|
||||
auto ram_write(uint addr, uint8 data) -> void {
|
||||
if(ram_enable == false) return;
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(ramEnable == false) return;
|
||||
bool region = addr & 0x0200;
|
||||
if(ram_writable[region] == false) return;
|
||||
if(ramWritable[region] == false) return;
|
||||
return board.prgram.write((region * 0x0200) + (addr & 0x01ff), data);
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xe001) {
|
||||
case 0x8000:
|
||||
chr_mode = data & 0x80;
|
||||
prg_mode = data & 0x40;
|
||||
ram_enable = data & 0x20;
|
||||
bank_select = data & 0x07;
|
||||
if(ram_enable == false) {
|
||||
for(auto &n : ram_readable) n = false;
|
||||
for(auto &n : ram_writable) n = false;
|
||||
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(bank_select) {
|
||||
case 0: chr_bank[0] = data & ~1; break;
|
||||
case 1: chr_bank[1] = data & ~1; break;
|
||||
case 2: chr_bank[2] = data; break;
|
||||
case 3: chr_bank[3] = data; break;
|
||||
case 4: chr_bank[4] = data; break;
|
||||
case 5: chr_bank[5] = data; break;
|
||||
case 6: prg_bank[0] = data & 0x3f; break;
|
||||
case 7: prg_bank[1] = data & 0x3f; break;
|
||||
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;
|
||||
|
||||
@@ -106,28 +106,28 @@ struct MMC6 : Chip {
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
if(ram_enable == false) break;
|
||||
ram_readable[1] = data & 0x80;
|
||||
ram_writable[1] = data & 0x40;
|
||||
ram_readable[0] = data & 0x20;
|
||||
ram_writable[0] = data & 0x10;
|
||||
if(ramEnable == false) break;
|
||||
ramReadable[1] = data & 0x80;
|
||||
ramWritable[1] = data & 0x40;
|
||||
ramReadable[0] = data & 0x20;
|
||||
ramWritable[0] = data & 0x10;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
irq_latch = data;
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irq_counter = 0;
|
||||
irqCounter = 0;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
irq_enable = false;
|
||||
irq_line = 0;
|
||||
irqEnable = false;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xe001:
|
||||
irq_enable = true;
|
||||
irqEnable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -136,57 +136,57 @@ struct MMC6 : Chip {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chr_mode = 0;
|
||||
prg_mode = 0;
|
||||
ram_enable = 0;
|
||||
bank_select = 0;
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
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 : ram_readable) n = 0;
|
||||
for(auto& n : ram_writable) n = 0;
|
||||
irq_latch = 0;
|
||||
irq_counter = 0;
|
||||
irq_enable = 0;
|
||||
irq_delay = 0;
|
||||
irq_line = 0;
|
||||
for(auto& n : ramReadable) n = 0;
|
||||
for(auto& n : ramWritable) n = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter = 0;
|
||||
irqEnable = 0;
|
||||
irqDelay = 0;
|
||||
irqLine = 0;
|
||||
|
||||
chr_abus = 0;
|
||||
chrAbus = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(chr_mode);
|
||||
s.integer(prg_mode);
|
||||
s.integer(ram_enable);
|
||||
s.integer(bank_select);
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
for(auto& n : chr_bank) s.integer(n);
|
||||
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 : ram_readable) s.integer(n);
|
||||
for(auto& n : ram_writable) s.integer(n);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_delay);
|
||||
s.integer(irq_line);
|
||||
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(chr_abus);
|
||||
s.integer(chrAbus);
|
||||
}
|
||||
|
||||
bool chr_mode;
|
||||
bool prg_mode;
|
||||
bool ram_enable;
|
||||
uint3 bank_select;
|
||||
uint8 prg_bank[2];
|
||||
uint8 chr_bank[6];
|
||||
bool chrMode;
|
||||
bool prgMode;
|
||||
bool ramEnable;
|
||||
uint3 bankSelect;
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[6];
|
||||
bool mirror;
|
||||
bool ram_readable[2];
|
||||
bool ram_writable[2];
|
||||
uint8 irq_latch;
|
||||
uint8 irq_counter;
|
||||
bool irq_enable;
|
||||
uint irq_delay;
|
||||
bool irq_line;
|
||||
bool ramReadable[2];
|
||||
bool ramWritable[2];
|
||||
uint8 irqLatch;
|
||||
uint8 irqCounter;
|
||||
bool irqEnable;
|
||||
uint irqDelay;
|
||||
bool irqLine;
|
||||
|
||||
uint16 chr_abus;
|
||||
uint16 chrAbus;
|
||||
};
|
||||
|
@@ -2,21 +2,21 @@ struct VRC1 : Chip {
|
||||
VRC1(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0x0f;
|
||||
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
|
||||
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
|
||||
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
|
||||
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 chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_banklo[(bool)(addr & 0x1000)];
|
||||
bank |= chr_bankhi[(bool)(addr & 0x1000)] << 4;
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBankLo[(bool)(addr & 0x1000)];
|
||||
bank |= chrBankHi[(bool)(addr & 0x1000)] << 4;
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
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
|
||||
@@ -24,32 +24,32 @@ struct VRC1 : Chip {
|
||||
throw;
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xf000) {
|
||||
case 0x8000:
|
||||
prg_bank[0] = data & 0x0f;
|
||||
prgBank[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
chr_bankhi[1] = data & 0x04;
|
||||
chr_bankhi[0] = data & 0x02;
|
||||
chrBankHi[1] = data & 0x04;
|
||||
chrBankHi[0] = data & 0x02;
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
prg_bank[1] = data & 0x0f;
|
||||
prgBank[1] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
prg_bank[2] = data & 0x0f;
|
||||
prgBank[2] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
chr_banklo[0] = data & 0x0f;
|
||||
chrBankLo[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
chr_banklo[1] = data & 0x0f;
|
||||
chrBankLo[1] = data & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -58,21 +58,21 @@ struct VRC1 : Chip {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_banklo) n = 0;
|
||||
for(auto& n : chr_bankhi) n = 0;
|
||||
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 : prg_bank) s.integer(n);
|
||||
for(auto& n : chr_banklo) s.integer(n);
|
||||
for(auto& n : chr_bankhi) s.integer(n);
|
||||
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 prg_bank[3];
|
||||
uint4 chr_banklo[2];
|
||||
bool chr_bankhi[2];
|
||||
uint4 prgBank[3];
|
||||
uint4 chrBankLo[2];
|
||||
bool chrBankHi[2];
|
||||
bool mirror;
|
||||
};
|
||||
|
@@ -2,23 +2,23 @@ struct VRC2 : Chip {
|
||||
VRC2(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prg_bank[0]; break;
|
||||
case 0xa000: bank = prg_bank[1]; break;
|
||||
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 chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_bank[addr / 0x0400];
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
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
|
||||
@@ -28,7 +28,7 @@ struct VRC2 : Chip {
|
||||
throw;
|
||||
}
|
||||
|
||||
auto ram_read(uint addr) -> uint8 {
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
if(board.prgram.size == 0) {
|
||||
if((addr & 0xf000) == 0x6000) return cpu.mdr() | latch;
|
||||
return cpu.mdr();
|
||||
@@ -36,7 +36,7 @@ struct VRC2 : Chip {
|
||||
return board.prgram.read(addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto ram_write(uint addr, uint8 data) -> void {
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
if(board.prgram.size == 0) {
|
||||
if((addr & 0xf000) == 0x6000) latch = data & 0x01;
|
||||
return;
|
||||
@@ -44,10 +44,10 @@ struct VRC2 : Chip {
|
||||
return board.prgram.write(addr & 0x1fff, data);
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prg_bank[0] = data & 0x1f;
|
||||
prgBank[0] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x9000: case 0x9001: case 0x9002: case 0x9003:
|
||||
@@ -55,32 +55,32 @@ struct VRC2 : Chip {
|
||||
break;
|
||||
|
||||
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
|
||||
prg_bank[1] = data & 0x1f;
|
||||
prgBank[1] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); 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: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb003: chr_bank[1] = (chr_bank[1] & 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: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc001: chr_bank[2] = (chr_bank[2] & 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: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc003: chr_bank[3] = (chr_bank[3] & 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: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd001: chr_bank[4] = (chr_bank[4] & 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: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd003: chr_bank[5] = (chr_bank[5] & 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: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe001: chr_bank[6] = (chr_bank[6] & 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: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe003: chr_bank[7] = (chr_bank[7] & 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,21 +88,21 @@ struct VRC2 : Chip {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
latch = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
for(auto& n : chr_bank) s.integer(n);
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
for(auto& n : chrBank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
s.integer(latch);
|
||||
}
|
||||
|
||||
uint5 prg_bank[2];
|
||||
uint8 chr_bank[8];
|
||||
uint5 prgBank[2];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
bool latch;
|
||||
};
|
||||
|
@@ -3,53 +3,53 @@ struct VRC3 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) { //16-bit
|
||||
if(++irq_counter.w == 0) {
|
||||
irq_line = 1;
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_counter.w = irq_latch;
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) { //16-bit
|
||||
if(++irqCounter.w == 0) {
|
||||
irqLine = 1;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqCounter.w = irqLatch;
|
||||
}
|
||||
}
|
||||
if(irq_mode == 1) { //8-bit
|
||||
if(++irq_counter.l == 0) {
|
||||
irq_line = 1;
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_counter.l = irq_latch;
|
||||
if(irqMode == 1) { //8-bit
|
||||
if(++irqCounter.l == 0) {
|
||||
irqLine = 1;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqCounter.l = irqLatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
uint bank = (addr < 0xc000 ? (uint)prg_bank : 0x0f);
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = (addr < 0xc000 ? (uint)prgBank : 0x0f);
|
||||
return (bank * 0x4000) + (addr & 0x3fff);
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xf000) {
|
||||
case 0x8000: irq_latch = (irq_latch & 0xfff0) | ((data & 0x0f) << 0); break;
|
||||
case 0x9000: irq_latch = (irq_latch & 0xff0f) | ((data & 0x0f) << 4); break;
|
||||
case 0xa000: irq_latch = (irq_latch & 0xf0ff) | ((data & 0x0f) << 8); break;
|
||||
case 0xb000: irq_latch = (irq_latch & 0x0fff) | ((data & 0x0f) << 12); break;
|
||||
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:
|
||||
irq_mode = data & 0x04;
|
||||
irq_enable = data & 0x02;
|
||||
irq_acknowledge = data & 0x01;
|
||||
if(irq_enable) irq_counter.w = irq_latch;
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) irqCounter.w = irqLatch;
|
||||
break;
|
||||
|
||||
case 0xd000:
|
||||
irq_line = 0;
|
||||
irq_enable = irq_acknowledge;
|
||||
irqLine = 0;
|
||||
irqEnable = irqAcknowledge;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
prg_bank = data & 0x0f;
|
||||
prgBank = data & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -58,35 +58,35 @@ struct VRC3 : Chip {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
irq_mode = 0;
|
||||
irq_enable = 0;
|
||||
irq_acknowledge = 0;
|
||||
irq_latch = 0;
|
||||
irq_counter.w = 0;
|
||||
irq_line = 0;
|
||||
prgBank = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
irqLatch = 0;
|
||||
irqCounter.w = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(prg_bank);
|
||||
s.integer(irq_mode);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_acknowledge);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_counter.w);
|
||||
s.integer(irq_line);
|
||||
s.integer(prgBank);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqCounter.w);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint4 prg_bank;
|
||||
bool irq_mode;
|
||||
bool irq_enable;
|
||||
bool irq_acknowledge;
|
||||
uint16 irq_latch;
|
||||
uint4 prgBank;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
uint16 irqLatch;
|
||||
struct {
|
||||
union {
|
||||
uint16_t w;
|
||||
struct { uint8_t order_lsb2(l, h); };
|
||||
};
|
||||
} irq_counter;
|
||||
bool irq_line;
|
||||
} irqCounter;
|
||||
bool irqLine;
|
||||
};
|
||||
|
@@ -3,51 +3,51 @@ struct VRC4 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irq_mode == 1) {
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0, banks = board.prgrom.size / 0x2000;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prg_mode == 0 ? (unsigned)prg_bank[0] : banks - 2; break;
|
||||
case 0xa000: bank = prg_bank[1]; break;
|
||||
case 0xc000: bank = prg_mode == 0 ? banks - 2 : (unsigned)prg_bank[0]; break;
|
||||
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 chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_bank[addr / 0x0400];
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
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
|
||||
@@ -57,10 +57,10 @@ struct VRC4 : Chip {
|
||||
throw;
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prg_bank[0] = data & 0x1f;
|
||||
prgBank[0] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x9000: case 0x9001:
|
||||
@@ -68,59 +68,59 @@ struct VRC4 : Chip {
|
||||
break;
|
||||
|
||||
case 0x9002: case 0x9003:
|
||||
prg_mode = data & 0x02;
|
||||
prgMode = data & 0x02;
|
||||
break;
|
||||
|
||||
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
|
||||
prg_bank[1] = data & 0x1f;
|
||||
prgBank[1] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); 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: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb003: chr_bank[1] = (chr_bank[1] & 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: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc001: chr_bank[2] = (chr_bank[2] & 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: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc003: chr_bank[3] = (chr_bank[3] & 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: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd001: chr_bank[4] = (chr_bank[4] & 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: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd003: chr_bank[5] = (chr_bank[5] & 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: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe001: chr_bank[6] = (chr_bank[6] & 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: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe003: chr_bank[7] = (chr_bank[7] & 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:
|
||||
irq_latch = (irq_latch & 0xf0) | ((data & 0x0f) << 0);
|
||||
irqLatch = (irqLatch & 0xf0) | ((data & 0x0f) << 0);
|
||||
break;
|
||||
|
||||
case 0xf001:
|
||||
irq_latch = (irq_latch & 0x0f) | ((data & 0x0f) << 4);
|
||||
irqLatch = (irqLatch & 0x0f) | ((data & 0x0f) << 4);
|
||||
break;
|
||||
|
||||
case 0xf002:
|
||||
irq_mode = data & 0x04;
|
||||
irq_enable = data & 0x02;
|
||||
irq_acknowledge = data & 0x01;
|
||||
if(irq_enable) {
|
||||
irq_counter = irq_latch;
|
||||
irq_scalar = 341;
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irq_line = 0;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf003:
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_line = 0;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -129,48 +129,48 @@ struct VRC4 : Chip {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_mode = 0;
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
prgMode = 0;
|
||||
for(auto& n : prgBank) n = 0;
|
||||
mirror = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
|
||||
irq_latch = 0;
|
||||
irq_mode = 0;
|
||||
irq_enable = 0;
|
||||
irq_acknowledge = 0;
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irq_counter = 0;
|
||||
irq_scalar = 0;
|
||||
irq_line = 0;
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(prg_mode);
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
s.integer(prgMode);
|
||||
for(auto& n : prgBank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
for(auto& n : chr_bank) s.integer(n);
|
||||
for(auto& n : chrBank) s.integer(n);
|
||||
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_mode);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_acknowledge);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_scalar);
|
||||
s.integer(irq_line);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
bool prg_mode;
|
||||
uint5 prg_bank[2];
|
||||
bool prgMode;
|
||||
uint5 prgBank[2];
|
||||
uint2 mirror;
|
||||
uint8 chr_bank[8];
|
||||
uint8 chrBank[8];
|
||||
|
||||
uint8 irq_latch;
|
||||
bool irq_mode;
|
||||
bool irq_enable;
|
||||
bool irq_acknowledge;
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irq_counter;
|
||||
int irq_scalar;
|
||||
bool irq_line;
|
||||
uint8 irqCounter;
|
||||
int irqScalar;
|
||||
bool irqLine;
|
||||
};
|
||||
|
@@ -77,52 +77,52 @@ struct VRC6 : Chip {
|
||||
} sawtooth;
|
||||
|
||||
auto main() -> void {
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irq_mode == 1) {
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.set_irq_line(irq_line);
|
||||
cpu.irqLine(irqLine);
|
||||
|
||||
pulse1.clock();
|
||||
pulse2.clock();
|
||||
sawtooth.clock();
|
||||
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
|
||||
apu.set_sample(-output);
|
||||
apu.setSample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
if((addr & 0xc000) == 0x8000) return (prg_bank[0] << 14) | (addr & 0x3fff);
|
||||
if((addr & 0xe000) == 0xc000) return (prg_bank[1] << 13) | (addr & 0x1fff);
|
||||
if((addr & 0xe000) == 0xe000) return ( 0xff << 13) | (addr & 0x1fff);
|
||||
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 chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_bank[(addr >> 10) & 7];
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[(addr >> 10) & 7];
|
||||
return (bank << 10) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
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
|
||||
@@ -131,18 +131,18 @@ struct VRC6 : Chip {
|
||||
}
|
||||
}
|
||||
|
||||
auto ram_read(uint addr) -> uint8 {
|
||||
auto readRAM(uint addr) -> uint8 {
|
||||
return board.prgram.data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
auto ram_write(uint addr, uint8 data) -> void {
|
||||
auto writeRAM(uint addr, uint8 data) -> void {
|
||||
board.prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prg_bank[0] = data;
|
||||
prgBank[0] = data;
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
@@ -193,35 +193,35 @@ struct VRC6 : Chip {
|
||||
break;
|
||||
|
||||
case 0xc000: case 0xc001: case 0xc002: case 0xc003:
|
||||
prg_bank[1] = data;
|
||||
prgBank[1] = data;
|
||||
break;
|
||||
|
||||
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
|
||||
chr_bank[0 + (addr & 3)] = data;
|
||||
chrBank[0 + (addr & 3)] = data;
|
||||
break;
|
||||
|
||||
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
|
||||
chr_bank[4 + (addr & 3)] = data;
|
||||
chrBank[4 + (addr & 3)] = data;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
irq_latch = data;
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xf001:
|
||||
irq_mode = data & 0x04;
|
||||
irq_enable = data & 0x02;
|
||||
irq_acknowledge = data & 0x01;
|
||||
if(irq_enable) {
|
||||
irq_counter = irq_latch;
|
||||
irq_scalar = 341;
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irq_line = 0;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf002:
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_line = 0;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -230,25 +230,25 @@ struct VRC6 : Chip {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank[0] = 0;
|
||||
prg_bank[1] = 0;
|
||||
chr_bank[0] = 0;
|
||||
chr_bank[1] = 0;
|
||||
chr_bank[2] = 0;
|
||||
chr_bank[3] = 0;
|
||||
chr_bank[4] = 0;
|
||||
chr_bank[5] = 0;
|
||||
chr_bank[6] = 0;
|
||||
chr_bank[7] = 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;
|
||||
chrBank[6] = 0;
|
||||
chrBank[7] = 0;
|
||||
mirror = 0;
|
||||
irq_latch = 0;
|
||||
irq_mode = 0;
|
||||
irq_enable = 0;
|
||||
irq_acknowledge = 0;
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irq_counter = 0;
|
||||
irq_scalar = 0;
|
||||
irq_line = 0;
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
|
||||
pulse1.mode = 0;
|
||||
pulse1.duty = 0;
|
||||
@@ -286,28 +286,28 @@ struct VRC6 : Chip {
|
||||
pulse2.serialize(s);
|
||||
sawtooth.serialize(s);
|
||||
|
||||
s.array(prg_bank);
|
||||
s.array(chr_bank);
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_mode);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_acknowledge);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_scalar);
|
||||
s.integer(irq_line);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint8 prg_bank[2];
|
||||
uint8 chr_bank[8];
|
||||
uint8 prgBank[2];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
uint8 irq_latch;
|
||||
bool irq_mode;
|
||||
bool irq_enable;
|
||||
bool irq_acknowledge;
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irq_counter;
|
||||
int irq_scalar;
|
||||
bool irq_line;
|
||||
uint8 irqCounter;
|
||||
int irqScalar;
|
||||
bool irqLine;
|
||||
};
|
||||
|
@@ -6,90 +6,90 @@ struct VRC7 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
if(irqEnable) {
|
||||
if(irqMode == 0) {
|
||||
irqScalar -= 3;
|
||||
if(irqScalar <= 0) {
|
||||
irqScalar += 341;
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irq_mode == 1) {
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
if(irqMode == 1) {
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = irqLatch;
|
||||
irqLine = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
irqCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.set_irq_line(irq_line);
|
||||
cpu.irqLine(irqLine);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
auto writeIO(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: prg_bank[0] = data; break;
|
||||
case 0x8010: prg_bank[1] = data; break;
|
||||
case 0x9000: prg_bank[2] = data; break;
|
||||
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: chr_bank[0] = data; break;
|
||||
case 0xa010: chr_bank[1] = data; break;
|
||||
case 0xb000: chr_bank[2] = data; break;
|
||||
case 0xb010: chr_bank[3] = data; break;
|
||||
case 0xc000: chr_bank[4] = data; break;
|
||||
case 0xc010: chr_bank[5] = data; break;
|
||||
case 0xd000: chr_bank[6] = data; break;
|
||||
case 0xd010: chr_bank[7] = data; break;
|
||||
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:
|
||||
irq_latch = data;
|
||||
irqLatch = data;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
irq_mode = data & 0x04;
|
||||
irq_enable = data & 0x02;
|
||||
irq_acknowledge = data & 0x01;
|
||||
if(irq_enable) {
|
||||
irq_counter = irq_latch;
|
||||
irq_scalar = 341;
|
||||
irqMode = data & 0x04;
|
||||
irqEnable = data & 0x02;
|
||||
irqAcknowledge = data & 0x01;
|
||||
if(irqEnable) {
|
||||
irqCounter = irqLatch;
|
||||
irqScalar = 341;
|
||||
}
|
||||
irq_line = 0;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xf010:
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_line = 0;
|
||||
irqEnable = irqAcknowledge;
|
||||
irqLine = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
auto addrPRG(uint addr) const -> uint {
|
||||
uint bank = 0;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prg_bank[0]; break;
|
||||
case 0xa000: bank = prg_bank[1]; break;
|
||||
case 0xc000: bank = prg_bank[2]; break;
|
||||
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 chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_bank[addr / 0x0400];
|
||||
auto addrCHR(uint addr) const -> uint {
|
||||
uint bank = chrBank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
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
|
||||
@@ -102,45 +102,45 @@ struct VRC7 : Chip {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
for(auto& n : prgBank) n = 0;
|
||||
for(auto& n : chrBank) n = 0;
|
||||
mirror = 0;
|
||||
|
||||
irq_latch = 0;
|
||||
irq_mode = 0;
|
||||
irq_enable = 0;
|
||||
irq_acknowledge = 0;
|
||||
irqLatch = 0;
|
||||
irqMode = 0;
|
||||
irqEnable = 0;
|
||||
irqAcknowledge = 0;
|
||||
|
||||
irq_counter = 0;
|
||||
irq_scalar = 0;
|
||||
irq_line = 0;
|
||||
irqCounter = 0;
|
||||
irqScalar = 0;
|
||||
irqLine = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(prg_bank);
|
||||
s.array(chr_bank);
|
||||
s.array(prgBank);
|
||||
s.array(chrBank);
|
||||
s.integer(mirror);
|
||||
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_mode);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_acknowledge);
|
||||
s.integer(irqLatch);
|
||||
s.integer(irqMode);
|
||||
s.integer(irqEnable);
|
||||
s.integer(irqAcknowledge);
|
||||
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_scalar);
|
||||
s.integer(irq_line);
|
||||
s.integer(irqCounter);
|
||||
s.integer(irqScalar);
|
||||
s.integer(irqLine);
|
||||
}
|
||||
|
||||
uint8 prg_bank[3];
|
||||
uint8 chr_bank[8];
|
||||
uint8 prgBank[3];
|
||||
uint8 chrBank[8];
|
||||
uint2 mirror;
|
||||
|
||||
uint8 irq_latch;
|
||||
bool irq_mode;
|
||||
bool irq_enable;
|
||||
bool irq_acknowledge;
|
||||
uint8 irqLatch;
|
||||
bool irqMode;
|
||||
bool irqEnable;
|
||||
bool irqAcknowledge;
|
||||
|
||||
uint8 irq_counter;
|
||||
int irq_scalar;
|
||||
bool irq_line;
|
||||
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;
|
28
higan/fc/controller/controller.cpp
Normal file
28
higan/fc/controller/controller.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "gamepad/gamepad.cpp"
|
||||
|
||||
Controller::Controller(bool port) : port(port) {
|
||||
if(!handle()) create(Controller::Enter, 1);
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
scheduler.remove(*this);
|
||||
}
|
||||
|
||||
auto Controller::Enter() -> void {
|
||||
while(true) {
|
||||
scheduler.synchronize();
|
||||
if(peripherals.controllerPort1->active()) peripherals.controllerPort1->main();
|
||||
if(peripherals.controllerPort2->active()) peripherals.controllerPort2->main();
|
||||
}
|
||||
}
|
||||
|
||||
auto Controller::main() -> void {
|
||||
step(1);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
}
|
33
higan/fc/controller/controller.hpp
Normal file
33
higan/fc/controller/controller.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
//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 : Thread {
|
||||
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"
|
36
higan/fc/controller/gamepad/gamepad.cpp
Normal file
36
higan/fc/controller/gamepad/gamepad.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
Gamepad::Gamepad(bool port) : Controller(port) {
|
||||
}
|
||||
|
||||
auto Gamepad::data() -> uint3 {
|
||||
if(counter >= 8) return 1;
|
||||
if(latched == 1) return platform->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 = platform->inputPoll(port, ID::Device::Gamepad, A);
|
||||
b = platform->inputPoll(port, ID::Device::Gamepad, B);
|
||||
select = platform->inputPoll(port, ID::Device::Gamepad, Select);
|
||||
start = platform->inputPoll(port, ID::Device::Gamepad, Start);
|
||||
up = platform->inputPoll(port, ID::Device::Gamepad, Up);
|
||||
down = platform->inputPoll(port, ID::Device::Gamepad, Down);
|
||||
left = platform->inputPoll(port, ID::Device::Gamepad, Left);
|
||||
right = platform->inputPoll(port, ID::Device::Gamepad, Right);
|
||||
}
|
||||
}
|
22
higan/fc/controller/gamepad/gamepad.hpp
Normal file
22
higan/fc/controller/gamepad/gamepad.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
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;
|
||||
};
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "memory.cpp"
|
||||
#include "timing.cpp"
|
||||
#include "serialization.cpp"
|
||||
CPU cpu;
|
||||
@@ -11,19 +12,16 @@ auto CPU::Enter() -> void {
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(status.interrupt_pending) return interrupt();
|
||||
exec();
|
||||
if(io.interruptPending) return interrupt();
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::add_clocks(uint clocks) -> void {
|
||||
apu.clock -= clocks;
|
||||
if(apu.clock < 0 && !scheduler.synchronizing()) co_switch(apu.thread);
|
||||
|
||||
ppu.clock -= clocks;
|
||||
if(ppu.clock < 0 && !scheduler.synchronizing()) co_switch(ppu.thread);
|
||||
|
||||
cartridge.clock -= clocks;
|
||||
if(cartridge.clock < 0 && !scheduler.synchronizing()) co_switch(cartridge.thread);
|
||||
auto CPU::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(apu);
|
||||
synchronize(ppu);
|
||||
synchronize(cartridge);
|
||||
for(auto peripheral : peripherals) synchronize(*peripheral);
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
@@ -38,64 +36,13 @@ auto CPU::power() -> void {
|
||||
|
||||
auto CPU::reset() -> void {
|
||||
R6502::reset();
|
||||
create(CPU::Enter, 21'477'272);
|
||||
create(CPU::Enter, system.colorburst() * 6.0);
|
||||
|
||||
regs.pc = bus.read(0xfffc) << 0;
|
||||
regs.pc |= bus.read(0xfffd) << 8;
|
||||
|
||||
status.interrupt_pending = false;
|
||||
status.nmi_pending = false;
|
||||
status.nmi_line = 0;
|
||||
status.irq_line = 0;
|
||||
status.irq_apu_line = 0;
|
||||
|
||||
status.rdy_line = 1;
|
||||
status.rdy_addr_valid = false;
|
||||
status.rdy_addr_value = 0x0000;
|
||||
|
||||
status.oam_dma_pending = false;
|
||||
status.oam_dma_page = 0x00;
|
||||
|
||||
status.controller_latch = false;
|
||||
status.controller_port0 = 0;
|
||||
status.controller_port1 = 0;
|
||||
}
|
||||
|
||||
auto CPU::debugger_read(uint16 addr) -> uint8 {
|
||||
return bus.read(addr);
|
||||
}
|
||||
|
||||
auto CPU::ram_read(uint16 addr) -> uint8 {
|
||||
return ram[addr & 0x07ff];
|
||||
}
|
||||
|
||||
auto CPU::ram_write(uint16 addr, uint8 data) -> void {
|
||||
ram[addr & 0x07ff] = data;
|
||||
}
|
||||
|
||||
auto CPU::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0x4016) {
|
||||
return (mdr() & 0xc0) | input.data(0);
|
||||
}
|
||||
|
||||
if(addr == 0x4017) {
|
||||
return (mdr() & 0xc0) | input.data(1);
|
||||
}
|
||||
|
||||
return apu.read(addr);
|
||||
}
|
||||
|
||||
auto CPU::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0x4014) {
|
||||
status.oam_dma_page = data;
|
||||
status.oam_dma_pending = true;
|
||||
}
|
||||
|
||||
if(addr == 0x4016) {
|
||||
input.latch(data & 0x01);
|
||||
}
|
||||
|
||||
return apu.write(addr, data);
|
||||
memory::fill(&io, sizeof(IO));
|
||||
io.rdyLine = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,57 +1,56 @@
|
||||
struct CPU : Processor::R6502, Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto add_clocks(uint clocks) -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto debugger_read(uint16 addr) -> uint8;
|
||||
//memory.cpp
|
||||
auto readRAM(uint11 addr) -> uint8;
|
||||
auto writeRAM(uint11 addr, uint8 data) -> void;
|
||||
|
||||
auto ram_read(uint16 addr) -> uint8;
|
||||
auto ram_write(uint16 addr, uint8 data) -> void;
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto readDebugger(uint16 addr) -> uint8 override;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//timing.cpp
|
||||
auto op_read(uint16 addr) -> uint8;
|
||||
auto op_write(uint16 addr, uint8 data) -> void;
|
||||
auto last_cycle() -> void;
|
||||
auto nmi(uint16& vector) -> void;
|
||||
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 oam_dma() -> void;
|
||||
auto oamdma() -> void;
|
||||
|
||||
auto set_nmi_line(bool) -> void;
|
||||
auto set_irq_line(bool) -> void;
|
||||
auto set_irq_apu_line(bool) -> void;
|
||||
auto nmiLine(bool) -> void;
|
||||
auto irqLine(bool) -> void;
|
||||
auto apuLine(bool) -> void;
|
||||
|
||||
auto set_rdy_line(bool) -> void;
|
||||
auto set_rdy_addr(bool valid, uint16 value = 0) -> void;
|
||||
auto rdyLine(bool) -> void;
|
||||
auto rdyAddr(bool valid, uint16 value = 0) -> void;
|
||||
|
||||
//protected:
|
||||
vector<Thread*> peripherals;
|
||||
|
||||
uint8 ram[0x0800];
|
||||
|
||||
struct Status {
|
||||
bool interrupt_pending;
|
||||
bool nmi_pending;
|
||||
bool nmi_line;
|
||||
bool irq_line;
|
||||
bool irq_apu_line;
|
||||
struct IO {
|
||||
bool interruptPending;
|
||||
bool nmiPending;
|
||||
bool nmiLine;
|
||||
bool irqLine;
|
||||
bool apuLine;
|
||||
|
||||
bool rdy_line;
|
||||
bool rdy_addr_valid;
|
||||
uint16 rdy_addr_value;
|
||||
bool rdyLine;
|
||||
bool rdyAddrValid;
|
||||
uint16 rdyAddrValue;
|
||||
|
||||
bool oam_dma_pending;
|
||||
uint8 oam_dma_page;
|
||||
|
||||
bool controller_latch;
|
||||
uint controller_port0;
|
||||
uint controller_port1;
|
||||
} status;
|
||||
bool oamdmaPending;
|
||||
uint8 oamdmaPage;
|
||||
} io;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
|
49
higan/fc/cpu/memory.cpp
Normal file
49
higan/fc/cpu/memory.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
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);
|
||||
}
|
@@ -4,20 +4,16 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
|
||||
s.array(ram);
|
||||
|
||||
s.integer(status.interrupt_pending);
|
||||
s.integer(status.nmi_pending);
|
||||
s.integer(status.nmi_line);
|
||||
s.integer(status.irq_line);
|
||||
s.integer(status.irq_apu_line);
|
||||
s.integer(io.interruptPending);
|
||||
s.integer(io.nmiPending);
|
||||
s.integer(io.nmiLine);
|
||||
s.integer(io.irqLine);
|
||||
s.integer(io.apuLine);
|
||||
|
||||
s.integer(status.rdy_line);
|
||||
s.integer(status.rdy_addr_valid);
|
||||
s.integer(status.rdy_addr_value);
|
||||
s.integer(io.rdyLine);
|
||||
s.integer(io.rdyAddrValid);
|
||||
s.integer(io.rdyAddrValue);
|
||||
|
||||
s.integer(status.oam_dma_pending);
|
||||
s.integer(status.oam_dma_page);
|
||||
|
||||
s.integer(status.controller_latch);
|
||||
s.integer(status.controller_port0);
|
||||
s.integer(status.controller_port1);
|
||||
s.integer(io.oamdmaPending);
|
||||
s.integer(io.oamdmaPage);
|
||||
}
|
||||
|
@@ -1,64 +1,64 @@
|
||||
auto CPU::op_read(uint16 addr) -> uint8 {
|
||||
if(status.oam_dma_pending) {
|
||||
status.oam_dma_pending = false;
|
||||
op_read(addr);
|
||||
oam_dma();
|
||||
auto CPU::read(uint16 addr) -> uint8 {
|
||||
if(io.oamdmaPending) {
|
||||
io.oamdmaPending = false;
|
||||
read(addr);
|
||||
oamdma();
|
||||
}
|
||||
|
||||
while(status.rdy_line == 0) {
|
||||
regs.mdr = bus.read(status.rdy_addr_valid ? status.rdy_addr_value : addr);
|
||||
add_clocks(12);
|
||||
while(io.rdyLine == 0) {
|
||||
regs.mdr = bus.read(io.rdyAddrValid ? io.rdyAddrValue : addr);
|
||||
step(12);
|
||||
}
|
||||
|
||||
regs.mdr = bus.read(addr);
|
||||
add_clocks(12);
|
||||
step(12);
|
||||
return regs.mdr;
|
||||
}
|
||||
|
||||
auto CPU::op_write(uint16 addr, uint8 data) -> void {
|
||||
auto CPU::write(uint16 addr, uint8 data) -> void {
|
||||
bus.write(addr, regs.mdr = data);
|
||||
add_clocks(12);
|
||||
step(12);
|
||||
}
|
||||
|
||||
auto CPU::last_cycle() -> void {
|
||||
status.interrupt_pending = ((status.irq_line | status.irq_apu_line) & ~regs.p.i) | status.nmi_pending;
|
||||
auto CPU::lastCycle() -> void {
|
||||
io.interruptPending = ((io.irqLine | io.apuLine) & ~regs.p.i) | io.nmiPending;
|
||||
}
|
||||
|
||||
auto CPU::nmi(uint16& vector) -> void {
|
||||
if(status.nmi_pending) {
|
||||
status.nmi_pending = false;
|
||||
if(io.nmiPending) {
|
||||
io.nmiPending = false;
|
||||
vector = 0xfffa;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::oam_dma() -> void {
|
||||
auto CPU::oamdma() -> void {
|
||||
for(uint n : range(256)) {
|
||||
uint8 data = op_read((status.oam_dma_page << 8) + n);
|
||||
op_write(0x2004, data);
|
||||
uint8 data = read(io.oamdmaPage << 8 | n);
|
||||
write(0x2004, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::set_nmi_line(bool line) -> void {
|
||||
auto CPU::nmiLine(bool line) -> void {
|
||||
//edge-sensitive (0->1)
|
||||
if(!status.nmi_line && line) status.nmi_pending = true;
|
||||
status.nmi_line = line;
|
||||
if(!io.nmiLine && line) io.nmiPending = true;
|
||||
io.nmiLine = line;
|
||||
}
|
||||
|
||||
auto CPU::set_irq_line(bool line) -> void {
|
||||
auto CPU::irqLine(bool line) -> void {
|
||||
//level-sensitive
|
||||
status.irq_line = line;
|
||||
io.irqLine = line;
|
||||
}
|
||||
|
||||
auto CPU::set_irq_apu_line(bool line) -> void {
|
||||
auto CPU::apuLine(bool line) -> void {
|
||||
//level-sensitive
|
||||
status.irq_apu_line = line;
|
||||
io.apuLine = line;
|
||||
}
|
||||
|
||||
auto CPU::set_rdy_line(bool line) -> void {
|
||||
status.rdy_line = line;
|
||||
auto CPU::rdyLine(bool line) -> void {
|
||||
io.rdyLine = line;
|
||||
}
|
||||
|
||||
auto CPU::set_rdy_addr(bool valid, uint16 value) -> void {
|
||||
status.rdy_addr_valid = valid;
|
||||
status.rdy_addr_value = value;
|
||||
auto CPU::rdyAddr(bool valid, uint16 value) -> void {
|
||||
io.rdyAddrValid = valid;
|
||||
io.rdyAddrValue = value;
|
||||
}
|
||||
|
@@ -1,56 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
//license: GPLv3
|
||||
//started: 2011-09-05
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <emulator/thread.hpp>
|
||||
#include <emulator/scheduler.hpp>
|
||||
#include <emulator/cheat.hpp>
|
||||
|
||||
#include <processor/r6502/r6502.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
namespace Info {
|
||||
static const string Name = "bnes";
|
||||
static const uint SerializerVersion = 2;
|
||||
}
|
||||
}
|
||||
#define platform Emulator::platform
|
||||
using File = Emulator::File;
|
||||
using Scheduler = Emulator::Scheduler;
|
||||
using Cheat = Emulator::Cheat;
|
||||
extern Scheduler scheduler;
|
||||
extern Cheat cheat;
|
||||
|
||||
/*
|
||||
bnes - Famicom emulator
|
||||
authors: byuu, Ryphecha
|
||||
license: GPLv3
|
||||
project started: 2011-09-05
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
namespace Famicom {
|
||||
struct Thread {
|
||||
~Thread() {
|
||||
if(thread) co_delete(thread);
|
||||
struct Thread : Emulator::Thread {
|
||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||
Emulator::Thread::create(entrypoint, frequency);
|
||||
scheduler.append(*this);
|
||||
}
|
||||
|
||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||
if(thread) co_delete(thread);
|
||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
||||
this->frequency = frequency;
|
||||
clock = 0;
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(clock() >= thread.clock()) scheduler.resume(thread);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(frequency);
|
||||
s.integer(clock);
|
||||
}
|
||||
|
||||
cothread_t thread = nullptr;
|
||||
uint frequency = 0;
|
||||
int64 clock = 0;
|
||||
};
|
||||
|
||||
#include <fc/controller/controller.hpp>
|
||||
#include <fc/system/system.hpp>
|
||||
#include <fc/scheduler/scheduler.hpp>
|
||||
#include <fc/input/input.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>
|
||||
}
|
||||
|
||||
#include <fc/interface/interface.hpp>
|
||||
|
@@ -1,53 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "serialization.cpp"
|
||||
Input input;
|
||||
|
||||
auto Input::latch(bool data) -> void {
|
||||
latchdata = data;
|
||||
|
||||
if(latchdata == 1) {
|
||||
counter1 = 0;
|
||||
counter2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto Input::data(bool port) -> bool {
|
||||
bool result = 0;
|
||||
|
||||
if(port == 0) {
|
||||
if(port1 == Device::Joypad) {
|
||||
if(counter1 >= 8) return 1;
|
||||
result = interface->inputPoll(0, 0u, counter1);
|
||||
if(latchdata == 0) counter1++;
|
||||
}
|
||||
}
|
||||
|
||||
if(port == 1) {
|
||||
if(port2 == Device::Joypad) {
|
||||
if(counter2 >= 8) return 1;
|
||||
result = interface->inputPoll(1, 0u, counter2);
|
||||
if(latchdata == 0) counter2++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto Input::connect(bool port, Device device) -> void {
|
||||
if(port == 0) port1 = device, counter1 = 0;
|
||||
if(port == 1) port2 = device, counter2 = 0;
|
||||
}
|
||||
|
||||
auto Input::power() -> void {
|
||||
}
|
||||
|
||||
auto Input::reset() -> void {
|
||||
latchdata = 0;
|
||||
counter1 = 0;
|
||||
counter2 = 0;
|
||||
}
|
||||
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
struct Input {
|
||||
enum class Device : uint {
|
||||
Joypad,
|
||||
None,
|
||||
};
|
||||
|
||||
auto latch(bool data) -> void;
|
||||
auto data(bool port) -> bool;
|
||||
auto connect(bool port, Device device) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
private:
|
||||
Device port1;
|
||||
Device port2;
|
||||
|
||||
bool latchdata;
|
||||
uint counter1;
|
||||
uint counter2;
|
||||
};
|
||||
|
||||
extern Input input;
|
@@ -1,8 +0,0 @@
|
||||
auto Input::serialize(serializer& s) -> void {
|
||||
s.integer((uint&)port1);
|
||||
s.integer((uint&)port2);
|
||||
|
||||
s.integer(latchdata);
|
||||
s.integer(counter1);
|
||||
s.integer(counter2);
|
||||
}
|
@@ -2,48 +2,42 @@
|
||||
|
||||
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", true});
|
||||
media.append({ID::Famicom, "Famicom", "fc"});
|
||||
|
||||
{ Device device{0, ID::Port1 | ID::Port2, "Controller"};
|
||||
device.input.append({0, 0, "A" });
|
||||
device.input.append({1, 0, "B" });
|
||||
device.input.append({2, 0, "Select"});
|
||||
device.input.append({3, 0, "Start" });
|
||||
device.input.append({4, 0, "Up" });
|
||||
device.input.append({5, 0, "Down" });
|
||||
device.input.append({6, 0, "Left" });
|
||||
device.input.append({7, 0, "Right" });
|
||||
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
|
||||
this->device.append(device);
|
||||
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);
|
||||
}
|
||||
|
||||
port.append({0, "Port 1"});
|
||||
port.append({1, "Port 2"});
|
||||
|
||||
for(auto& device : this->device) {
|
||||
for(auto& port : this->port) {
|
||||
if(device.portmask & (1 << port.id)) {
|
||||
port.device.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 {
|
||||
@@ -54,10 +48,76 @@ auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoSize() -> VideoSize {
|
||||
return {256, 240};
|
||||
}
|
||||
|
||||
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
|
||||
uint w = 256 * (arc ? 8.0 / 7.0 : 1.0);
|
||||
uint h = 240;
|
||||
uint m = min(width / w, height / h);
|
||||
return {w * m, h * m};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -70,65 +130,12 @@ auto Interface::sha256() -> string {
|
||||
return cartridge.sha256();
|
||||
}
|
||||
|
||||
auto Interface::group(uint id) -> uint {
|
||||
switch(id) {
|
||||
case ID::SystemManifest:
|
||||
return 0;
|
||||
case ID::Manifest:
|
||||
case ID::ProgramROM:
|
||||
case ID::ProgramRAM:
|
||||
case ID::CharacterROM:
|
||||
case ID::CharacterRAM:
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> void {
|
||||
system.load();
|
||||
auto Interface::load(uint id) -> bool {
|
||||
return system.load(this);
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
for(auto& memory : cartridge.memory) {
|
||||
saveRequest(memory.id, memory.name);
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::load(uint id, const stream& stream) -> void {
|
||||
if(id == ID::SystemManifest) {
|
||||
system.information.manifest = stream.text();
|
||||
}
|
||||
|
||||
if(id == ID::Manifest) {
|
||||
cartridge.information.markup = stream.text();
|
||||
}
|
||||
|
||||
if(id == ID::ProgramROM) {
|
||||
stream.read(cartridge.board->prgrom.data, min(cartridge.board->prgrom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::ProgramRAM) {
|
||||
stream.read(cartridge.board->prgram.data, min(cartridge.board->prgram.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::CharacterROM) {
|
||||
stream.read(cartridge.board->chrrom.data, min(cartridge.board->chrrom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::CharacterRAM) {
|
||||
stream.read(cartridge.board->chrram.data, min(cartridge.board->chrram.size, stream.size()));
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::save(uint id, const stream& stream) -> void {
|
||||
if(id == ID::ProgramRAM) {
|
||||
stream.write(cartridge.board->prgram.data, cartridge.board->prgram.size);
|
||||
}
|
||||
|
||||
if(id == ID::CharacterRAM) {
|
||||
stream.write(cartridge.board->chrram.data, cartridge.board->chrram.size);
|
||||
}
|
||||
system.save();
|
||||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
@@ -136,6 +143,10 @@ auto Interface::unload() -> void {
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::connect(uint port, uint device) -> void {
|
||||
peripherals.connect(port, device);
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
system.power();
|
||||
}
|
||||
@@ -157,16 +168,8 @@ auto Interface::unserialize(serializer& s) -> bool {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
auto Interface::cheatSet(const lstring& list) -> void {
|
||||
cheat.reset();
|
||||
for(auto& codeset : list) {
|
||||
lstring codes = codeset.split("+");
|
||||
for(auto& code : codes) {
|
||||
lstring part = code.split("/");
|
||||
if(part.size() == 2) cheat.append(hex(part[0]), hex(part[1]));
|
||||
if(part.size() == 3) cheat.append(hex(part[0]), hex(part[1]), hex(part[2]));
|
||||
}
|
||||
}
|
||||
auto Interface::cheatSet(const string_vector& list) -> void {
|
||||
cheat.assign(list);
|
||||
}
|
||||
|
||||
auto Interface::cap(const string& name) -> bool {
|
||||
@@ -182,7 +185,11 @@ auto Interface::get(const string& name) -> any {
|
||||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
||||
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;
|
||||
}
|
||||
|
@@ -6,62 +6,64 @@ struct ID {
|
||||
Famicom,
|
||||
};
|
||||
|
||||
enum : uint {
|
||||
SystemManifest,
|
||||
struct Port { enum : uint {
|
||||
Controller1,
|
||||
Controller2,
|
||||
Expansion,
|
||||
};};
|
||||
|
||||
Manifest,
|
||||
ProgramROM,
|
||||
ProgramRAM,
|
||||
CharacterROM,
|
||||
CharacterRAM,
|
||||
};
|
||||
|
||||
enum : uint {
|
||||
Port1 = 1,
|
||||
Port2 = 2,
|
||||
};
|
||||
struct Device { enum : uint {
|
||||
None,
|
||||
Gamepad,
|
||||
};};
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
Interface();
|
||||
|
||||
auto manifest() -> string;
|
||||
auto title() -> string;
|
||||
auto videoFrequency() -> double;
|
||||
auto audioFrequency() -> double;
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
||||
auto loaded() -> bool;
|
||||
auto sha256() -> string;
|
||||
auto group(uint id) -> uint;
|
||||
auto load(uint id) -> void;
|
||||
auto save() -> void;
|
||||
auto load(uint id, const stream& stream) -> void;
|
||||
auto save(uint id, const stream& stream) -> void;
|
||||
auto unload() -> void;
|
||||
auto videoSize() -> VideoSize override;
|
||||
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
|
||||
auto videoFrequency() -> double override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
auto run() -> void;
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
auto loaded() -> bool override;
|
||||
auto sha256() -> string override;
|
||||
auto load(uint id) -> bool override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
auto cheatSet(const lstring&) -> void;
|
||||
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;
|
||||
|
||||
private:
|
||||
vector<Device> device;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
bool colorEmulation = true;
|
||||
bool scanlineEmulation = true;
|
||||
|
||||
uint controllerPort1 = 0;
|
||||
uint controllerPort2 = 0;
|
||||
uint expansionPort = 0;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
|
@@ -12,12 +12,12 @@ Bus bus;
|
||||
//$4018-ffff = Cartridge
|
||||
|
||||
auto Bus::read(uint16 addr) -> uint8 {
|
||||
uint8 data = cartridge.prg_read(addr);
|
||||
if(addr <= 0x1fff) data = cpu.ram_read(addr);
|
||||
else if(addr <= 0x3fff) data = ppu.read(addr);
|
||||
else if(addr <= 0x4017) data = cpu.read(addr);
|
||||
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(cheat) {
|
||||
if(auto result = cheat.find(addr, data)) return result();
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ auto Bus::read(uint16 addr) -> uint8 {
|
||||
}
|
||||
|
||||
auto Bus::write(uint16 addr, uint8 data) -> void {
|
||||
cartridge.prg_write(addr, data);
|
||||
if(addr <= 0x1fff) return cpu.ram_write(addr, data);
|
||||
if(addr <= 0x3fff) return ppu.write(addr, data);
|
||||
if(addr <= 0x4017) return cpu.write(addr, data);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
144
higan/fc/ppu/memory.cpp
Normal file
144
higan/fc/ppu/memory.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
@@ -3,8 +3,8 @@
|
||||
namespace Famicom {
|
||||
|
||||
PPU ppu;
|
||||
#include "video.cpp"
|
||||
|
||||
#include "memory.cpp"
|
||||
#include "render.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
@@ -12,473 +12,61 @@ auto PPU::Enter() -> void {
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
raster_scanline();
|
||||
renderScanline();
|
||||
}
|
||||
|
||||
auto PPU::tick() -> void {
|
||||
if(status.ly == 240 && status.lx == 340) status.nmi_hold = 1;
|
||||
if(status.ly == 241 && status.lx == 0) status.nmi_flag = status.nmi_hold;
|
||||
if(status.ly == 241 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
|
||||
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(status.ly == 260 && status.lx == 340) status.sprite_zero_hit = 0, status.sprite_overflow = 0;
|
||||
if(io.ly == 260 && io.lx == 340) io.spriteZeroHit = 0, io.spriteOverflow = 0;
|
||||
|
||||
if(status.ly == 260 && status.lx == 340) status.nmi_hold = 0;
|
||||
if(status.ly == 261 && status.lx == 0) status.nmi_flag = status.nmi_hold;
|
||||
if(status.ly == 261 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
|
||||
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) co_switch(cpu.thread);
|
||||
Thread::step(4);
|
||||
synchronize(cpu);
|
||||
|
||||
status.lx++;
|
||||
io.lx++;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::scanline() -> void {
|
||||
status.lx = 0;
|
||||
if(++status.ly == 262) {
|
||||
status.ly = 0;
|
||||
io.lx = 0;
|
||||
if(++io.ly == 262) {
|
||||
io.ly = 0;
|
||||
frame();
|
||||
}
|
||||
cartridge.scanline(status.ly);
|
||||
cartridge.scanline(io.ly);
|
||||
}
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
status.field ^= 1;
|
||||
video.refresh();
|
||||
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);
|
||||
create(PPU::Enter, system.colorburst() * 6.0);
|
||||
|
||||
status.mdr = 0x00;
|
||||
status.field = 0;
|
||||
status.ly = 0;
|
||||
status.bus_data = 0x00;
|
||||
status.address_latch = 0;
|
||||
memory::fill(&io, sizeof(IO));
|
||||
memory::fill(&latch, sizeof(Latches));
|
||||
io.vramIncrement = 1;
|
||||
|
||||
status.vaddr = 0x0000;
|
||||
status.taddr = 0x0000;
|
||||
status.xaddr = 0x00;
|
||||
|
||||
status.nmi_hold = 0;
|
||||
status.nmi_flag = 0;
|
||||
|
||||
//$2000
|
||||
status.nmi_enable = false;
|
||||
status.master_select = 0;
|
||||
status.sprite_size = 0;
|
||||
status.bg_addr = 0x0000;
|
||||
status.sprite_addr = 0x0000;
|
||||
status.vram_increment = 1;
|
||||
|
||||
//$2001
|
||||
status.emphasis = 0;
|
||||
status.sprite_enable = false;
|
||||
status.bg_enable = false;
|
||||
status.sprite_edge_enable = false;
|
||||
status.bg_edge_enable = false;
|
||||
status.grayscale = false;
|
||||
|
||||
//$2002
|
||||
status.sprite_zero_hit = false;
|
||||
status.sprite_overflow = false;
|
||||
|
||||
//$2003
|
||||
status.oam_addr = 0x00;
|
||||
|
||||
for(auto& n : buffer) n = 0;
|
||||
for(auto& n : ciram ) n = 0;
|
||||
for(auto& n : cgram ) n = 0;
|
||||
for(auto& n : oam ) n = 0;
|
||||
}
|
||||
|
||||
auto PPU::read(uint16 addr) -> uint8 {
|
||||
uint8 result = 0x00;
|
||||
|
||||
switch(addr & 7) {
|
||||
case 2: //PPUSTATUS
|
||||
result |= status.nmi_flag << 7;
|
||||
result |= status.sprite_zero_hit << 6;
|
||||
result |= status.sprite_overflow << 5;
|
||||
result |= status.mdr & 0x1f;
|
||||
status.address_latch = 0;
|
||||
status.nmi_hold = 0;
|
||||
cpu.set_nmi_line(status.nmi_flag = 0);
|
||||
break;
|
||||
case 4: //OAMDATA
|
||||
result = oam[status.oam_addr];
|
||||
break;
|
||||
case 7: //PPUDATA
|
||||
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00;
|
||||
|
||||
addr = status.vaddr & 0x3fff;
|
||||
if(addr <= 0x1fff) {
|
||||
result = status.bus_data;
|
||||
status.bus_data = cartridge.chr_read(addr);
|
||||
} else if(addr <= 0x3eff) {
|
||||
result = status.bus_data;
|
||||
status.bus_data = cartridge.chr_read(addr);
|
||||
} else if(addr <= 0x3fff) {
|
||||
result = cgram_read(addr);
|
||||
status.bus_data = cartridge.chr_read(addr);
|
||||
}
|
||||
status.vaddr += status.vram_increment;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto PPU::write(uint16 addr, uint8 data) -> void {
|
||||
status.mdr = data;
|
||||
|
||||
switch(addr & 7) {
|
||||
case 0: //PPUCTRL
|
||||
status.nmi_enable = data & 0x80;
|
||||
status.master_select = data & 0x40;
|
||||
status.sprite_size = data & 0x20;
|
||||
status.bg_addr = (data & 0x10) ? 0x1000 : 0x0000;
|
||||
status.sprite_addr = (data & 0x08) ? 0x1000 : 0x0000;
|
||||
status.vram_increment = (data & 0x04) ? 32 : 1;
|
||||
status.taddr = (status.taddr & 0x73ff) | ((data & 0x03) << 10);
|
||||
cpu.set_nmi_line(status.nmi_enable && status.nmi_hold && status.nmi_flag);
|
||||
return;
|
||||
case 1: //PPUMASK
|
||||
status.emphasis = data >> 5;
|
||||
status.sprite_enable = data & 0x10;
|
||||
status.bg_enable = data & 0x08;
|
||||
status.sprite_edge_enable = data & 0x04;
|
||||
status.bg_edge_enable = data & 0x02;
|
||||
status.grayscale = data & 0x01;
|
||||
return;
|
||||
case 2: //PPUSTATUS
|
||||
return;
|
||||
case 3: //OAMADDR
|
||||
status.oam_addr = data;
|
||||
return;
|
||||
case 4: //OAMDATA
|
||||
if(status.oam_addr.bits(0,1) == 2) data.bits(2,4) = 0; //clear non-existent bits (always read back as 0)
|
||||
oam[status.oam_addr++] = data;
|
||||
return;
|
||||
case 5: //PPUSCROLL
|
||||
if(status.address_latch == 0) {
|
||||
status.xaddr = data & 0x07;
|
||||
status.taddr = (status.taddr & 0x7fe0) | (data >> 3);
|
||||
} else {
|
||||
status.taddr = (status.taddr & 0x0c1f) | ((data & 0x07) << 12) | ((data >> 3) << 5);
|
||||
}
|
||||
status.address_latch ^= 1;
|
||||
return;
|
||||
case 6: //PPUADDR
|
||||
if(status.address_latch == 0) {
|
||||
status.taddr = (status.taddr & 0x00ff) | ((data & 0x3f) << 8);
|
||||
} else {
|
||||
status.taddr = (status.taddr & 0x7f00) | data;
|
||||
status.vaddr = status.taddr;
|
||||
}
|
||||
status.address_latch ^= 1;
|
||||
return;
|
||||
case 7: //PPUDATA
|
||||
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return;
|
||||
|
||||
addr = status.vaddr & 0x3fff;
|
||||
if(addr <= 0x1fff) {
|
||||
cartridge.chr_write(addr, data);
|
||||
} else if(addr <= 0x3eff) {
|
||||
cartridge.chr_write(addr, data);
|
||||
} else if(addr <= 0x3fff) {
|
||||
cgram_write(addr, data);
|
||||
}
|
||||
status.vaddr += status.vram_increment;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::ciram_read(uint16 addr) -> uint8 {
|
||||
return ciram[addr & 0x07ff];
|
||||
}
|
||||
|
||||
auto PPU::ciram_write(uint16 addr, uint8 data) -> void {
|
||||
ciram[addr & 0x07ff] = data;
|
||||
}
|
||||
|
||||
auto PPU::cgram_read(uint16 addr) -> uint8 {
|
||||
if((addr & 0x13) == 0x10) addr &= ~0x10;
|
||||
uint8 data = cgram[addr & 0x1f];
|
||||
if(status.grayscale) data &= 0x30;
|
||||
return data;
|
||||
}
|
||||
|
||||
auto PPU::cgram_write(uint16 addr, uint8 data) -> void {
|
||||
if((addr & 0x13) == 0x10) addr &= ~0x10;
|
||||
cgram[addr & 0x1f] = data;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
//vaddr = 0yyy VHYY YYYX XXXX
|
||||
//yyy = fine Yscroll (y:d0-d2)
|
||||
//V = V nametable (y:d8)
|
||||
//H = H nametable (x:d8)
|
||||
//YYYYY = Y nametable (y:d3-d7)
|
||||
//XXXXX = X nametable (x:d3-d7)
|
||||
|
||||
auto PPU::raster_enable() const -> bool {
|
||||
return (status.bg_enable || status.sprite_enable);
|
||||
}
|
||||
|
||||
auto PPU::nametable_addr() const -> uint {
|
||||
return 0x2000 + (status.vaddr & 0x0c00);
|
||||
}
|
||||
|
||||
auto PPU::scrollx() const -> uint {
|
||||
return ((status.vaddr & 0x1f) << 3) | status.xaddr;
|
||||
}
|
||||
|
||||
auto PPU::scrolly() const -> uint {
|
||||
return (((status.vaddr >> 5) & 0x1f) << 3) | ((status.vaddr >> 12) & 7);
|
||||
}
|
||||
|
||||
auto PPU::sprite_height() const -> uint {
|
||||
return status.sprite_size == 0 ? 8 : 16;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto PPU::chr_load(uint16 addr) -> uint8 {
|
||||
if(raster_enable() == false) return 0x00;
|
||||
return cartridge.chr_read(addr);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto PPU::scrollx_increment() -> void {
|
||||
if(raster_enable() == false) return;
|
||||
status.vaddr = (status.vaddr & 0x7fe0) | ((status.vaddr + 0x0001) & 0x001f);
|
||||
if((status.vaddr & 0x001f) == 0x0000) {
|
||||
status.vaddr ^= 0x0400;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::scrolly_increment() -> void {
|
||||
if(raster_enable() == false) return;
|
||||
status.vaddr = (status.vaddr & 0x0fff) | ((status.vaddr + 0x1000) & 0x7000);
|
||||
if((status.vaddr & 0x7000) == 0x0000) {
|
||||
status.vaddr = (status.vaddr & 0x7c1f) | ((status.vaddr + 0x0020) & 0x03e0);
|
||||
if((status.vaddr & 0x03e0) == 0x03c0) { //0x03c0 == 30 << 5; 30 * 8 = 240
|
||||
status.vaddr &= 0x7c1f;
|
||||
status.vaddr ^= 0x0800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto PPU::raster_pixel() -> void {
|
||||
uint32* output = buffer + status.ly * 256;
|
||||
|
||||
uint mask = 0x8000 >> (status.xaddr + (status.lx & 7));
|
||||
uint palette = 0, object_palette = 0;
|
||||
bool object_priority = 0;
|
||||
palette |= (raster.tiledatalo & mask) ? 1 : 0;
|
||||
palette |= (raster.tiledatahi & mask) ? 2 : 0;
|
||||
if(palette) {
|
||||
uint attr = raster.attribute;
|
||||
if(mask >= 256) attr >>= 2;
|
||||
palette |= (attr & 3) << 2;
|
||||
}
|
||||
|
||||
if(status.bg_enable == false) palette = 0;
|
||||
if(status.bg_edge_enable == false && status.lx < 8) palette = 0;
|
||||
|
||||
if(status.sprite_enable == true)
|
||||
for(int sprite = 7; sprite >= 0; sprite--) {
|
||||
if(status.sprite_edge_enable == false && status.lx < 8) continue;
|
||||
if(raster.oam[sprite].id == 64) continue;
|
||||
|
||||
uint spritex = status.lx - raster.oam[sprite].x;
|
||||
if(spritex >= 8) continue;
|
||||
|
||||
if(raster.oam[sprite].attr & 0x40) spritex ^= 7;
|
||||
uint mask = 0x80 >> spritex;
|
||||
uint sprite_palette = 0;
|
||||
sprite_palette |= (raster.oam[sprite].tiledatalo & mask) ? 1 : 0;
|
||||
sprite_palette |= (raster.oam[sprite].tiledatahi & mask) ? 2 : 0;
|
||||
if(sprite_palette == 0) continue;
|
||||
|
||||
if(raster.oam[sprite].id == 0 && palette && status.lx != 255) status.sprite_zero_hit = 1;
|
||||
sprite_palette |= (raster.oam[sprite].attr & 3) << 2;
|
||||
|
||||
object_priority = raster.oam[sprite].attr & 0x20;
|
||||
object_palette = 16 + sprite_palette;
|
||||
}
|
||||
|
||||
if(object_palette) {
|
||||
if(palette == 0 || object_priority == 0) palette = object_palette;
|
||||
}
|
||||
|
||||
if(raster_enable() == false) palette = 0;
|
||||
output[status.lx] = (status.emphasis << 6) | cgram_read(palette);
|
||||
}
|
||||
|
||||
auto PPU::raster_sprite() -> void {
|
||||
if(raster_enable() == false) return;
|
||||
|
||||
uint n = raster.oam_iterator++;
|
||||
int ly = (status.ly == 261 ? -1 : status.ly);
|
||||
uint y = ly - oam[(n * 4) + 0];
|
||||
|
||||
if(y >= sprite_height()) return;
|
||||
if(raster.oam_counter == 8) {
|
||||
status.sprite_overflow = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
raster.soam[raster.oam_counter].id = n;
|
||||
raster.soam[raster.oam_counter].y = oam[(n * 4) + 0];
|
||||
raster.soam[raster.oam_counter].tile = oam[(n * 4) + 1];
|
||||
raster.soam[raster.oam_counter].attr = oam[(n * 4) + 2];
|
||||
raster.soam[raster.oam_counter].x = oam[(n * 4) + 3];
|
||||
raster.oam_counter++;
|
||||
}
|
||||
|
||||
auto PPU::raster_scanline() -> void {
|
||||
if((status.ly >= 240 && status.ly <= 260)) {
|
||||
for(auto x : range(341)) tick();
|
||||
return scanline();
|
||||
}
|
||||
|
||||
raster.oam_iterator = 0;
|
||||
raster.oam_counter = 0;
|
||||
|
||||
for(auto n : range(8)) {
|
||||
raster.soam[n].id = 64;
|
||||
raster.soam[n].y = 0xff;
|
||||
raster.soam[n].tile = 0xff;
|
||||
raster.soam[n].attr = 0xff;
|
||||
raster.soam[n].x = 0xff;
|
||||
raster.soam[n].tiledatalo = 0;
|
||||
raster.soam[n].tiledatahi = 0;
|
||||
}
|
||||
|
||||
for(uint tile : range(32)) { // 0-255
|
||||
uint nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
uint tileaddr = status.bg_addr + (nametable << 4) + (scrolly() & 7);
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
uint attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
|
||||
if(scrolly() & 16) attribute >>= 4;
|
||||
if(scrollx() & 16) attribute >>= 2;
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
scrollx_increment();
|
||||
if(tile == 31) scrolly_increment();
|
||||
raster_pixel();
|
||||
raster_sprite();
|
||||
tick();
|
||||
|
||||
uint tiledatalo = chr_load(tileaddr + 0);
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
uint tiledatahi = chr_load(tileaddr + 8);
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
raster_pixel();
|
||||
raster_sprite();
|
||||
tick();
|
||||
|
||||
raster.nametable = (raster.nametable << 8) | nametable;
|
||||
raster.attribute = (raster.attribute << 2) | (attribute & 3);
|
||||
raster.tiledatalo = (raster.tiledatalo << 8) | tiledatalo;
|
||||
raster.tiledatahi = (raster.tiledatahi << 8) | tiledatahi;
|
||||
}
|
||||
|
||||
for(auto n : range(8)) raster.oam[n] = raster.soam[n];
|
||||
|
||||
for(uint sprite : range(8)) { //256-319
|
||||
uint nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
tick();
|
||||
|
||||
if(raster_enable() && sprite == 0) status.vaddr = (status.vaddr & 0x7be0) | (status.taddr & 0x041f); //257
|
||||
tick();
|
||||
|
||||
uint attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
|
||||
uint tileaddr = (sprite_height() == 8)
|
||||
? status.sprite_addr + raster.oam[sprite].tile * 16
|
||||
: ((raster.oam[sprite].tile & ~1) * 16) + ((raster.oam[sprite].tile & 1) * 0x1000);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
uint spritey = (status.ly - raster.oam[sprite].y) & (sprite_height() - 1);
|
||||
if(raster.oam[sprite].attr & 0x80) spritey ^= (sprite_height() - 1);
|
||||
tileaddr += spritey + (spritey & 8);
|
||||
|
||||
raster.oam[sprite].tiledatalo = chr_load(tileaddr + 0);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
raster.oam[sprite].tiledatahi = chr_load(tileaddr + 8);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
if(raster_enable() && sprite == 6 && status.ly == 261) status.vaddr = status.taddr; //304
|
||||
}
|
||||
|
||||
for(uint tile : range(2)) { //320-335
|
||||
uint nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
uint tileaddr = status.bg_addr + (nametable << 4) + (scrolly() & 7);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
uint attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
|
||||
if(scrolly() & 16) attribute >>= 4;
|
||||
if(scrollx() & 16) attribute >>= 2;
|
||||
tick();
|
||||
|
||||
scrollx_increment();
|
||||
tick();
|
||||
|
||||
uint tiledatalo = chr_load(tileaddr + 0);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
uint tiledatahi = chr_load(tileaddr + 8);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
raster.nametable = (raster.nametable << 8) | nametable;
|
||||
raster.attribute = (raster.attribute << 2) | (attribute & 3);
|
||||
raster.tiledatalo = (raster.tiledatalo << 8) | tiledatalo;
|
||||
raster.tiledatahi = (raster.tiledatahi << 8) | tiledatahi;
|
||||
}
|
||||
|
||||
//336-339
|
||||
chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
tick();
|
||||
bool skip = (raster_enable() && status.field == 1 && status.ly == 261);
|
||||
tick();
|
||||
|
||||
chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
tick();
|
||||
tick();
|
||||
|
||||
//340
|
||||
if(skip == false) tick();
|
||||
|
||||
return scanline();
|
||||
for(auto& n : buffer) n = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,109 +1,120 @@
|
||||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto scanline() -> void;
|
||||
auto frame() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
//memory.cpp
|
||||
auto readCIRAM(uint11 addr) -> uint8;
|
||||
auto writeCIRAM(uint11 addr, uint8 data) -> void;
|
||||
|
||||
auto ciram_read(uint16 addr) -> uint8;
|
||||
auto ciram_write(uint16 addr, uint8 data) -> void;
|
||||
auto readCGRAM(uint5 addr) -> uint8;
|
||||
auto writeCGRAM(uint5 addr, uint8 data) -> void;
|
||||
|
||||
auto cgram_read(uint16 addr) -> uint8;
|
||||
auto cgram_write(uint16 addr, uint8 data) -> void;
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto raster_enable() const -> bool;
|
||||
auto nametable_addr() const -> uint;
|
||||
auto scrollx() const -> uint;
|
||||
auto scrolly() const -> uint;
|
||||
auto sprite_height() const -> uint;
|
||||
//render.cpp
|
||||
auto enable() const -> bool;
|
||||
auto loadCHR(uint16 addr) -> uint8;
|
||||
|
||||
auto chr_load(uint16 addr) -> uint8;
|
||||
|
||||
auto scrollx_increment() -> void;
|
||||
auto scrolly_increment() -> void;
|
||||
|
||||
auto raster_pixel() -> void;
|
||||
auto raster_sprite() -> void;
|
||||
auto raster_scanline() -> void;
|
||||
auto renderPixel() -> void;
|
||||
auto renderSprite() -> void;
|
||||
auto renderScanline() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Status {
|
||||
struct IO {
|
||||
//internal
|
||||
uint8 mdr;
|
||||
|
||||
bool field;
|
||||
uint1 field;
|
||||
uint lx;
|
||||
uint ly;
|
||||
|
||||
uint8 bus_data;
|
||||
uint8 busData;
|
||||
|
||||
bool address_latch;
|
||||
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;
|
||||
|
||||
uint15 vaddr;
|
||||
uint15 taddr;
|
||||
uint8 xaddr;
|
||||
|
||||
bool nmi_hold;
|
||||
bool nmi_flag;
|
||||
bool nmiHold;
|
||||
bool nmiFlag;
|
||||
|
||||
//$2000
|
||||
bool nmi_enable;
|
||||
bool master_select;
|
||||
bool sprite_size;
|
||||
uint bg_addr;
|
||||
uint sprite_addr;
|
||||
uint vram_increment;
|
||||
uint vramIncrement;
|
||||
uint spriteAddress;
|
||||
uint bgAddress;
|
||||
uint spriteHeight;
|
||||
bool masterSelect;
|
||||
bool nmiEnable;
|
||||
|
||||
//$2001
|
||||
uint3 emphasis;
|
||||
bool sprite_enable;
|
||||
bool bg_enable;
|
||||
bool sprite_edge_enable;
|
||||
bool bg_edge_enable;
|
||||
bool grayscale;
|
||||
bool bgEdgeEnable;
|
||||
bool spriteEdgeEnable;
|
||||
bool bgEnable;
|
||||
bool spriteEnable;
|
||||
uint3 emphasis;
|
||||
|
||||
//$2002
|
||||
bool sprite_zero_hit;
|
||||
bool sprite_overflow;
|
||||
bool spriteOverflow;
|
||||
bool spriteZeroHit;
|
||||
|
||||
//$2003
|
||||
uint8 oam_addr;
|
||||
} status;
|
||||
uint8 oamAddress;
|
||||
} io;
|
||||
|
||||
struct Raster {
|
||||
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;
|
||||
uint16 tiledataLo;
|
||||
uint16 tiledataHi;
|
||||
|
||||
uint oam_iterator;
|
||||
uint oam_counter;
|
||||
uint oamIterator;
|
||||
uint oamCounter;
|
||||
|
||||
struct OAM {
|
||||
uint8 id;
|
||||
uint8 y;
|
||||
uint8 tile;
|
||||
uint8 attr;
|
||||
uint8 x;
|
||||
OAM oam[8]; //primary
|
||||
OAM soam[8]; //secondary
|
||||
} latch;
|
||||
|
||||
uint8 tiledatalo;
|
||||
uint8 tiledatahi;
|
||||
} oam[8], soam[8];
|
||||
} raster;
|
||||
|
||||
uint32 buffer[256 * 262];
|
||||
uint8 ciram[2048];
|
||||
uint8 cgram[32];
|
||||
uint8 oam[256];
|
||||
|
||||
uint32 buffer[256 * 262];
|
||||
};
|
||||
|
||||
extern PPU ppu;
|
||||
|
211
higan/fc/ppu/render.cpp
Normal file
211
higan/fc/ppu/render.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
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,74 +1,64 @@
|
||||
auto PPU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
s.integer(status.mdr);
|
||||
s.integer(io.mdr);
|
||||
|
||||
s.integer(status.field);
|
||||
s.integer(status.lx);
|
||||
s.integer(status.ly);
|
||||
s.integer(io.field);
|
||||
s.integer(io.lx);
|
||||
s.integer(io.ly);
|
||||
|
||||
s.integer(status.bus_data);
|
||||
s.integer(io.busData);
|
||||
|
||||
s.integer(status.address_latch);
|
||||
s.integer(io.v.value);
|
||||
s.integer(io.t.value);
|
||||
|
||||
s.integer(status.vaddr);
|
||||
s.integer(status.taddr);
|
||||
s.integer(status.xaddr);
|
||||
s.integer(io.nmiHold);
|
||||
s.integer(io.nmiFlag);
|
||||
|
||||
s.integer(status.nmi_hold);
|
||||
s.integer(status.nmi_flag);
|
||||
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(status.nmi_enable);
|
||||
s.integer(status.master_select);
|
||||
s.integer(status.sprite_size);
|
||||
s.integer(status.bg_addr);
|
||||
s.integer(status.sprite_addr);
|
||||
s.integer(status.vram_increment);
|
||||
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(status.emphasis);
|
||||
s.integer(status.sprite_enable);
|
||||
s.integer(status.bg_enable);
|
||||
s.integer(status.sprite_edge_enable);
|
||||
s.integer(status.bg_edge_enable);
|
||||
s.integer(status.grayscale);
|
||||
s.integer(io.spriteOverflow);
|
||||
s.integer(io.spriteZeroHit);
|
||||
|
||||
s.integer(status.sprite_zero_hit);
|
||||
s.integer(status.sprite_overflow);
|
||||
s.integer(io.oamAddress);
|
||||
|
||||
s.integer(status.oam_addr);
|
||||
s.integer(latch.nametable);
|
||||
s.integer(latch.attribute);
|
||||
s.integer(latch.tiledataLo);
|
||||
s.integer(latch.tiledataHi);
|
||||
|
||||
s.integer(raster.nametable);
|
||||
s.integer(raster.attribute);
|
||||
s.integer(raster.tiledatalo);
|
||||
s.integer(raster.tiledatahi);
|
||||
s.integer(latch.oamIterator);
|
||||
s.integer(latch.oamCounter);
|
||||
|
||||
s.integer(raster.oam_iterator);
|
||||
s.integer(raster.oam_counter);
|
||||
for(auto& o : latch.oam) o.serialize(s);
|
||||
for(auto& o : latch.soam) o.serialize(s);
|
||||
|
||||
for(auto n : range(8)) {
|
||||
s.integer(raster.oam[n].id);
|
||||
s.integer(raster.oam[n].y);
|
||||
s.integer(raster.oam[n].tile);
|
||||
s.integer(raster.oam[n].attr);
|
||||
s.integer(raster.oam[n].x);
|
||||
|
||||
s.integer(raster.oam[n].tiledatalo);
|
||||
s.integer(raster.oam[n].tiledatahi);
|
||||
}
|
||||
|
||||
for(auto n : range(8)) {
|
||||
s.integer(raster.soam[n].id);
|
||||
s.integer(raster.soam[n].y);
|
||||
s.integer(raster.soam[n].tile);
|
||||
s.integer(raster.soam[n].attr);
|
||||
s.integer(raster.soam[n].x);
|
||||
|
||||
s.integer(raster.soam[n].tiledatalo);
|
||||
s.integer(raster.soam[n].tiledatahi);
|
||||
}
|
||||
|
||||
s.array(buffer);
|
||||
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,94 +0,0 @@
|
||||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[256 * 480];
|
||||
paletteLiteral = new uint32[1 << 9];
|
||||
paletteStandard = new uint32[1 << 9];
|
||||
paletteEmulation = new uint32[1 << 9];
|
||||
}
|
||||
|
||||
auto Video::reset() -> void {
|
||||
memory::fill(output(), 256 * 480);
|
||||
|
||||
for(auto color : range(1 << 9)) {
|
||||
paletteLiteral[color] = color;
|
||||
paletteStandard[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 2.2);
|
||||
paletteEmulation[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 1.8);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto output = this->output();
|
||||
auto& palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
if(settings.scanlineEmulation) {
|
||||
for(uint y = 0; y < 240; y++) {
|
||||
auto source = ppu.buffer + y * 256;
|
||||
auto targetLo = output + y * 512;
|
||||
auto targetHi = output + y * 512 + 256;
|
||||
for(uint x = 0; x < 256; x++) {
|
||||
auto color = palette[*source++];
|
||||
*targetLo++ = color;
|
||||
*targetHi++ = (255 << 24) | ((color & 0xfefefe) >> 1);
|
||||
}
|
||||
}
|
||||
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 480);
|
||||
} else {
|
||||
for(uint y = 0; y < 240; y++) {
|
||||
auto source = ppu.buffer + y * 256;
|
||||
auto target = output + y * 256;
|
||||
for(uint x = 0; x < 256; x++) {
|
||||
*target++ = palette[*source++];
|
||||
}
|
||||
}
|
||||
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 240);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::generateColor(
|
||||
uint n, double saturation, double hue,
|
||||
double contrast, double brightness, double gamma
|
||||
) -> uint32 {
|
||||
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 * std::cos((3.141592653 / 6.0) * (p + hue));
|
||||
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
|
||||
}
|
||||
|
||||
i *= saturation;
|
||||
q *= saturation;
|
||||
|
||||
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
|
||||
uint r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
|
||||
uint g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
|
||||
uint b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q));
|
||||
|
||||
return interface->videoColor(r, g, b);
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
struct Video {
|
||||
Video();
|
||||
|
||||
auto reset() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
private:
|
||||
auto generateColor(uint, double, double, double, double, double) -> uint32;
|
||||
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
};
|
||||
|
||||
extern Video video;
|
@@ -1,43 +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);
|
||||
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 == ppu.thread) {
|
||||
while(enter(Mode::SynchronizePPU) != Event::Synchronize);
|
||||
} else {
|
||||
resume = thread;
|
||||
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize() -> void {
|
||||
if(co_active() == ppu.thread && mode == Mode::SynchronizePPU) return exit(Event::Synchronize);
|
||||
if(co_active() != ppu.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,
|
||||
SynchronizePPU,
|
||||
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;
|
46
higan/fc/system/peripherals.cpp
Normal file
46
higan/fc/system/peripherals.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
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,13 +1,15 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(_serializeSize);
|
||||
|
||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||
char hash[64], description[512];
|
||||
memcpy(&hash, (const char*)cartridge.sha256(), 64);
|
||||
memset(&description, 0, sizeof description);
|
||||
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.integer(version);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
@@ -16,16 +18,18 @@ auto System::serialize() -> serializer {
|
||||
}
|
||||
|
||||
auto System::unserialize(serializer& s) -> bool {
|
||||
uint signature, version;
|
||||
char hash[64], description[512];
|
||||
uint signature;
|
||||
char version[16];
|
||||
char hash[64];
|
||||
char description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.integer(version);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
if(signature != 0x31545342) return false;
|
||||
if(version != Info::SerializerVersion) return false;
|
||||
if(string{version} != Emulator::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serializeAll(s);
|
||||
@@ -37,7 +41,6 @@ auto System::serialize(serializer& s) -> void {
|
||||
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
system.serialize(s);
|
||||
input.serialize(s);
|
||||
cartridge.serialize(s);
|
||||
cpu.serialize(s);
|
||||
apu.serialize(s);
|
||||
@@ -47,11 +50,13 @@ auto System::serializeAll(serializer& s) -> void {
|
||||
auto System::serializeInit() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0, version = 0;
|
||||
char hash[64], description[512];
|
||||
uint signature = 0;
|
||||
char version[16];
|
||||
char hash[64];
|
||||
char description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.integer(version);
|
||||
s.array(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
|
@@ -2,34 +2,50 @@
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "peripherals.cpp"
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
auto System::loaded() const -> bool { return _loaded; }
|
||||
Scheduler scheduler;
|
||||
Cheat cheat;
|
||||
|
||||
auto System::run() -> void {
|
||||
scheduler.enter();
|
||||
if(scheduler.enter() == Scheduler::Event::Frame) ppu.refresh();
|
||||
}
|
||||
|
||||
auto System::runToSave() -> void {
|
||||
scheduler.synchronize(ppu.thread);
|
||||
scheduler.synchronize(cpu.thread);
|
||||
scheduler.synchronize(apu.thread);
|
||||
scheduler.synchronize(cartridge.thread);
|
||||
scheduler.synchronize(cpu);
|
||||
scheduler.synchronize(apu);
|
||||
scheduler.synchronize(ppu);
|
||||
scheduler.synchronize(cartridge);
|
||||
for(auto peripheral : cpu.peripherals) scheduler.synchronize(*peripheral);
|
||||
}
|
||||
|
||||
auto System::load() -> void {
|
||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||
auto System::load(Emulator::Interface* interface) -> bool {
|
||||
information = Information();
|
||||
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
cartridge.load();
|
||||
if(!cartridge.load()) return false;
|
||||
|
||||
this->interface = interface;
|
||||
information.colorburst = Emulator::Constants::Colorburst::NTSC;
|
||||
serializeInit();
|
||||
_loaded = true;
|
||||
return information.loaded = true;
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
peripherals.unload();
|
||||
cartridge.unload();
|
||||
_loaded = false;
|
||||
information.loaded = false;
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
@@ -37,24 +53,29 @@ auto System::power() -> void {
|
||||
cpu.power();
|
||||
apu.power();
|
||||
ppu.power();
|
||||
input.reset();
|
||||
reset();
|
||||
}
|
||||
|
||||
auto System::reset() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
|
||||
Emulator::audio.reset();
|
||||
Emulator::audio.setInterface(interface);
|
||||
|
||||
scheduler.reset();
|
||||
cartridge.reset();
|
||||
cpu.reset();
|
||||
apu.reset();
|
||||
ppu.reset();
|
||||
input.reset();
|
||||
scheduler.reset();
|
||||
video.reset();
|
||||
scheduler.primary(cpu);
|
||||
peripherals.reset();
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
assert(interface != nullptr);
|
||||
input.connect(0, Input::Device::Joypad);
|
||||
input.connect(1, Input::Device::None);
|
||||
}
|
||||
|
||||
auto System::term() -> void {
|
||||
|
@@ -1,10 +1,12 @@
|
||||
struct System {
|
||||
auto loaded() const -> bool;
|
||||
auto loaded() const -> bool { return information.loaded; }
|
||||
auto colorburst() const -> double { return information.colorburst; }
|
||||
|
||||
auto run() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto load() -> void;
|
||||
auto load(Emulator::Interface*) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
@@ -12,6 +14,11 @@ struct System {
|
||||
auto init() -> void;
|
||||
auto term() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
@@ -19,13 +26,26 @@ struct System {
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
private:
|
||||
Emulator::Interface* interface = nullptr;
|
||||
|
||||
struct Information {
|
||||
bool loaded = false;
|
||||
double colorburst = 0.0;
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
private:
|
||||
bool _loaded = false;
|
||||
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;
|
||||
|
6
higan/fc/system/video.cpp
Normal file
6
higan/fc/system/video.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
auto System::configureVideoPalette() -> void {
|
||||
Emulator::video.setPalette();
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
}
|
@@ -1,16 +1,13 @@
|
||||
processors += lr35902
|
||||
|
||||
objects += gb-interface gb-system gb-scheduler
|
||||
objects += gb-interface gb-system
|
||||
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/)
|
||||
|
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
#include "sequencer/sequencer.cpp"
|
||||
#include "square1/square1.cpp"
|
||||
#include "square2/square2.cpp"
|
||||
#include "wave/wave.cpp"
|
||||
#include "noise/noise.cpp"
|
||||
#include "sequencer.cpp"
|
||||
#include "square1.cpp"
|
||||
#include "square2.cpp"
|
||||
#include "wave.cpp"
|
||||
#include "noise.cpp"
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
@@ -25,7 +25,12 @@ auto APU::main() -> void {
|
||||
hipass(sequencer.left, sequencer.leftBias);
|
||||
hipass(sequencer.right, sequencer.rightBias);
|
||||
|
||||
interface->audioSample(sequencer.left, sequencer.right);
|
||||
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
|
||||
@@ -46,8 +51,8 @@ auto APU::main() -> void {
|
||||
}
|
||||
cycle++;
|
||||
|
||||
clock += cpu.frequency;
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
Thread::step(1);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
//filter to remove DC bias
|
||||
@@ -58,6 +63,7 @@ auto APU::hipass(int16& sample, int64& bias) -> void {
|
||||
|
||||
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();
|
||||
@@ -72,7 +78,7 @@ auto APU::power() -> void {
|
||||
for(auto& n : wave.pattern) n = r();
|
||||
}
|
||||
|
||||
auto APU::mmio_read(uint16 addr) -> uint8 {
|
||||
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);
|
||||
@@ -82,7 +88,7 @@ auto APU::mmio_read(uint16 addr) -> uint8 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||
auto APU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
if(!sequencer.enable) {
|
||||
bool valid = addr == 0xff26; //NR52
|
||||
if(!system.cgb()) {
|
||||
|
@@ -1,25 +1,173 @@
|
||||
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 mmio_read(uint16 addr) -> uint8;
|
||||
auto mmio_write(uint16 addr, uint8 data) -> void;
|
||||
auto readIO(uint16 addr) -> uint8;
|
||||
auto writeIO(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
#include "square1/square1.hpp"
|
||||
#include "square2/square2.hpp"
|
||||
#include "wave/wave.hpp"
|
||||
#include "noise/noise.hpp"
|
||||
#include "sequencer/sequencer.hpp"
|
||||
//square1.cpp
|
||||
struct Square1 {
|
||||
auto dacEnable() const -> bool;
|
||||
|
||||
Square1 square1;
|
||||
Square2 square2;
|
||||
Wave wave;
|
||||
Noise noise;
|
||||
Sequencer sequencer;
|
||||
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
|
||||
|
@@ -1,30 +0,0 @@
|
||||
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;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user