mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-21 05:11:28 +02:00
Compare commits
55 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c074c6e064 | ||
|
50420e3dd2 | ||
|
b08449215a | ||
|
9b452c9f5f | ||
|
3681961ca5 | ||
|
20ac95ee49 | ||
|
fdc41611cf | ||
|
839813d0f1 | ||
|
7f3cfa17b9 | ||
|
ae5d380d06 | ||
|
3ebc77c148 | ||
|
6ae0abe3d3 | ||
|
0955295475 | ||
|
7cdae5195a | ||
|
e2ee6689a0 | ||
|
55e507d5df | ||
|
a2d3b8ba15 | ||
|
1929ad47d2 | ||
|
7403e69307 | ||
|
19e1d89f00 | ||
|
aff00506c5 | ||
|
e846c83d47 | ||
|
06d44b4878 | ||
|
25eaaa82f4 | ||
|
2d83300235 | ||
|
680d16561e | ||
|
379ab6991f | ||
|
d3413db04a | ||
|
a7f7985581 | ||
|
b586471562 | ||
|
c33065fbd1 | ||
|
79e7e6ab9e | ||
|
3d3ac8c1db | ||
|
b0d2f5033e | ||
|
570eb9c5f5 | ||
|
7dc62e3a69 | ||
|
fc7d5991ce | ||
|
29be18ce0c | ||
|
810cbdafb4 | ||
|
4b29f4bad7 | ||
|
ef65bb862a | ||
|
0d0af39b44 | ||
|
6c83329cae | ||
|
32a95a9761 | ||
|
a89a3da77a | ||
|
7a748e093e | ||
|
d158c8f293 | ||
|
71bda4144a | ||
|
ad51f1478e | ||
|
d0ddd87e9c | ||
|
605a8aa3e9 | ||
|
a8323d0d2b | ||
|
d7998b23ef | ||
|
344e63d928 | ||
|
f1ebef2ea8 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
higan/profile/WonderSwan.sys/internal.ram
|
||||||
|
higan/profile/WonderSwan Color.sys/internal.ram
|
@@ -1,16 +1,11 @@
|
|||||||
include ../nall/GNUmakefile
|
include ../nall/GNUmakefile
|
||||||
|
|
||||||
fc := fc
|
|
||||||
sfc := sfc
|
|
||||||
gb := gb
|
|
||||||
gba := gba
|
|
||||||
|
|
||||||
profile := accuracy
|
|
||||||
target := tomoko
|
target := tomoko
|
||||||
|
# target := loki
|
||||||
# console := true
|
# console := true
|
||||||
|
|
||||||
flags += -I. -I.. -O3
|
flags += -I. -I.. -O3
|
||||||
objects := libco
|
objects := libco audio video resource
|
||||||
|
|
||||||
# profile-guided optimization mode
|
# profile-guided optimization mode
|
||||||
# pgo := instrument
|
# pgo := instrument
|
||||||
@@ -58,6 +53,11 @@ compile = \
|
|||||||
|
|
||||||
all: build;
|
all: build;
|
||||||
|
|
||||||
|
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco/)
|
||||||
|
obj/audio.o: audio/audio.cpp $(call rwildcard,audio/)
|
||||||
|
obj/video.o: video/video.cpp $(call rwildcard,video/)
|
||||||
|
obj/resource.o: resource/resource.cpp $(call rwildcard,resource/)
|
||||||
|
|
||||||
ui := target-$(target)
|
ui := target-$(target)
|
||||||
include $(ui)/GNUmakefile
|
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 {
|
||||||
|
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};
|
||||||
|
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] /= streams.size();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
samples[c] *= volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(channels == 2) {
|
||||||
|
if(balance < 0.0) samples[1] *= 1.0 + balance;
|
||||||
|
if(balance > 0.0) samples[0] *= 1.0 - balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface->audioSample(samples, channels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
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*) -> 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,6 +1,6 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=higan
|
Name=higan
|
||||||
Comment=Nintendo emulator
|
Comment=Emulator
|
||||||
Exec=higan
|
Exec=higan
|
||||||
Icon=higan
|
Icon=higan
|
||||||
Terminal=false
|
Terminal=false
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>higan.icns</string>
|
<string>higan.icns</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<false/>
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
@@ -1,23 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <nall/nall.hpp>
|
#include <nall/nall.hpp>
|
||||||
#include <nall/dsp.hpp>
|
|
||||||
using namespace nall;
|
using namespace nall;
|
||||||
|
|
||||||
|
#include <audio/audio.hpp>
|
||||||
|
#include <video/video.hpp>
|
||||||
|
#include <resource/resource.hpp>
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "higan";
|
static const string Name = "higan";
|
||||||
static const string Version = "097";
|
static const string Version = "099";
|
||||||
static const string Author = "byuu";
|
static const string Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "http://byuu.org/";
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "interface.hpp"
|
#include "interface.hpp"
|
||||||
@@ -52,5 +47,3 @@ template<typename R, typename... P> struct hook<auto (P...) -> R> {
|
|||||||
#else
|
#else
|
||||||
#define privileged private
|
#define privileged private
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using varuint = varuint_t<uint>;
|
|
||||||
|
@@ -4,6 +4,7 @@ namespace Emulator {
|
|||||||
|
|
||||||
struct Interface {
|
struct Interface {
|
||||||
struct Information {
|
struct Information {
|
||||||
|
string manufacturer;
|
||||||
string name;
|
string name;
|
||||||
uint width;
|
uint width;
|
||||||
uint height;
|
uint height;
|
||||||
@@ -16,13 +17,13 @@ struct Interface {
|
|||||||
} capability;
|
} capability;
|
||||||
} information;
|
} information;
|
||||||
|
|
||||||
struct Media {
|
struct Medium {
|
||||||
uint id;
|
uint id;
|
||||||
string name;
|
string name;
|
||||||
string type;
|
string type;
|
||||||
bool bootable; //false for cartridge slots (eg Sufami Turbo cartridges)
|
bool bootable; //false for cartridge slots (eg Sufami Turbo cartridges)
|
||||||
};
|
};
|
||||||
vector<Media> media;
|
vector<Medium> media;
|
||||||
|
|
||||||
struct Device {
|
struct Device {
|
||||||
uint id;
|
uint id;
|
||||||
@@ -34,23 +35,22 @@ struct Interface {
|
|||||||
string name;
|
string name;
|
||||||
uintptr guid; //user data field
|
uintptr guid; //user data field
|
||||||
};
|
};
|
||||||
vector<Input> input;
|
vector<Input> inputs;
|
||||||
vector<uint> order;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Port {
|
struct Port {
|
||||||
uint id;
|
uint id;
|
||||||
string name;
|
string name;
|
||||||
vector<Device> device;
|
vector<Device> devices;
|
||||||
};
|
};
|
||||||
vector<Port> port;
|
vector<Port> ports;
|
||||||
|
|
||||||
struct Bind {
|
struct Bind {
|
||||||
virtual auto loadRequest(uint, string, string, bool) -> void {}
|
virtual auto loadRequest(uint, string, string, bool) -> void {}
|
||||||
virtual auto loadRequest(uint, string, bool) -> void {}
|
virtual auto loadRequest(uint, string, bool) -> void {}
|
||||||
virtual auto saveRequest(uint, string) -> void {}
|
virtual auto saveRequest(uint, string) -> void {}
|
||||||
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
|
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
|
||||||
virtual auto audioSample(int16, int16) -> void {}
|
virtual auto audioSample(const double*, uint) -> void {}
|
||||||
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
|
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
|
||||||
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
|
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
|
||||||
virtual auto dipSettings(const Markup::Node&) -> uint { return 0; }
|
virtual auto dipSettings(const Markup::Node&) -> uint { return 0; }
|
||||||
@@ -64,7 +64,7 @@ struct Interface {
|
|||||||
auto loadRequest(uint id, string path, bool required) -> void { return bind->loadRequest(id, path, 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 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 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 audioSample(const double* samples, uint channels) -> void { return bind->audioSample(samples, channels); }
|
||||||
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
|
auto 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 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 dipSettings(const Markup::Node& node) -> uint { return bind->dipSettings(node); }
|
||||||
@@ -74,7 +74,13 @@ struct Interface {
|
|||||||
//information
|
//information
|
||||||
virtual auto manifest() -> string = 0;
|
virtual auto manifest() -> string = 0;
|
||||||
virtual auto title() -> string = 0;
|
virtual auto title() -> string = 0;
|
||||||
|
|
||||||
|
//video information
|
||||||
virtual auto videoFrequency() -> double = 0;
|
virtual auto videoFrequency() -> double = 0;
|
||||||
|
virtual auto videoColors() -> uint32 { return 1 << 19; }
|
||||||
|
virtual auto videoColor(uint32 color) -> uint64 { return 0; }
|
||||||
|
|
||||||
|
//audio information
|
||||||
virtual auto audioFrequency() -> double = 0;
|
virtual auto audioFrequency() -> double = 0;
|
||||||
|
|
||||||
//media interface
|
//media interface
|
||||||
@@ -108,6 +114,9 @@ struct Interface {
|
|||||||
virtual auto cap(const string& name) -> bool { return false; }
|
virtual auto cap(const string& name) -> bool { return false; }
|
||||||
virtual auto get(const string& name) -> any { return {}; }
|
virtual auto get(const string& name) -> any { return {}; }
|
||||||
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
||||||
|
|
||||||
|
//shared functions
|
||||||
|
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
fc_objects := fc-interface fc-system fc-scheduler fc-input
|
processors += r6502
|
||||||
fc_objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
|
|
||||||
fc_objects += fc-cheat fc-video
|
|
||||||
objects += $(fc_objects)
|
|
||||||
|
|
||||||
obj/fc-interface.o: $(fc)/interface/interface.cpp $(call rwildcard,$(fc)/interface/)
|
objects += fc-interface fc-system fc-scheduler fc-input
|
||||||
obj/fc-system.o: $(fc)/system/system.cpp $(call rwildcard,$(fc)/system/)
|
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
|
||||||
obj/fc-scheduler.o: $(fc)/scheduler/scheduler.cpp $(call rwildcard,$(fc)/scheduler/)
|
objects += fc-cheat
|
||||||
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-interface.o: fc/interface/interface.cpp $(call rwildcard,fc/interface/)
|
||||||
obj/fc-cartridge.o: $(fc)/cartridge/cartridge.cpp $(call rwildcard,$(fc)/cartridge/)
|
obj/fc-system.o: fc/system/system.cpp $(call rwildcard,fc/system/)
|
||||||
obj/fc-cpu.o: $(fc)/cpu/cpu.cpp $(call rwildcard,$(fc)/cpu/)
|
obj/fc-scheduler.o: fc/scheduler/scheduler.cpp $(call rwildcard,fc/scheduler/)
|
||||||
obj/fc-apu.o: $(fc)/apu/apu.cpp $(call rwildcard,$(fc)/apu/)
|
obj/fc-input.o: fc/input/input.cpp $(call rwildcard,fc/input/)
|
||||||
obj/fc-ppu.o: $(fc)/ppu/ppu.cpp $(call rwildcard,$(fc)/ppu/)
|
obj/fc-memory.o: fc/memory/memory.cpp $(call rwildcard,fc/memory/)
|
||||||
obj/fc-cheat.o: $(fc)/cheat/cheat.cpp $(call rwildcard,$(fc)/cheat/)
|
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp $(call rwildcard,fc/cartridge/)
|
||||||
obj/fc-video.o: $(fc)/video/video.cpp $(call rwildcard,$(fc)/video/)
|
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/)
|
||||||
|
@@ -34,43 +34,37 @@ APU::APU() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::Main() -> void {
|
auto APU::Enter() -> void {
|
||||||
apu.main();
|
while(true) scheduler.synchronize(), apu.main();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::main() -> void {
|
auto APU::main() -> void {
|
||||||
while(true) {
|
uint pulse_output, triangle_output, noise_output, dmc_output;
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint pulse_output, triangle_output, noise_output, dmc_output;
|
pulse_output = pulse[0].clock();
|
||||||
|
pulse_output += pulse[1].clock();
|
||||||
|
triangle_output = triangle.clock();
|
||||||
|
noise_output = noise.clock();
|
||||||
|
dmc_output = dmc.clock();
|
||||||
|
|
||||||
pulse_output = pulse[0].clock();
|
clock_frame_counter_divider();
|
||||||
pulse_output += pulse[1].clock();
|
|
||||||
triangle_output = triangle.clock();
|
|
||||||
noise_output = noise.clock();
|
|
||||||
dmc_output = dmc.clock();
|
|
||||||
|
|
||||||
clock_frame_counter_divider();
|
int output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
|
||||||
|
|
||||||
int output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[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 = sclamp<16>(output);
|
||||||
|
|
||||||
output = filter.run_hipass_strong(output);
|
stream->sample(output / 32768.0);
|
||||||
output += cartridge_sample;
|
|
||||||
output = filter.run_hipass_weak(output);
|
|
||||||
//output = filter.run_lopass(output);
|
|
||||||
output = sclamp<16>(output);
|
|
||||||
|
|
||||||
interface->audioSample(output, output);
|
tick();
|
||||||
|
|
||||||
tick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::tick() -> void {
|
auto APU::tick() -> void {
|
||||||
clock += 12;
|
clock += 12;
|
||||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::set_irq_line() -> void {
|
auto APU::set_irq_line() -> void {
|
||||||
@@ -94,7 +88,8 @@ auto APU::power() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto APU::reset() -> void {
|
auto APU::reset() -> void {
|
||||||
create(APU::Main, 21477272);
|
create(APU::Enter, 21'477'272);
|
||||||
|
stream = Emulator::audio.createStream(1, 21'477'272.0 / 12.0);
|
||||||
|
|
||||||
pulse[0].reset();
|
pulse[0].reset();
|
||||||
pulse[1].reset();
|
pulse[1].reset();
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
struct APU : Thread {
|
struct APU : Thread {
|
||||||
|
shared_pointer<Emulator::Stream> stream;
|
||||||
|
|
||||||
APU();
|
APU();
|
||||||
|
|
||||||
static auto Main() -> void;
|
static auto Enter() -> void;
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
auto tick() -> void;
|
auto tick() -> void;
|
||||||
auto set_irq_line() -> void;
|
auto set_irq_line() -> void;
|
||||||
|
@@ -5,20 +5,14 @@ struct BandaiFCG : Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(irq_counter_enable) {
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
if(--irq_counter == 0xffff) {
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
cpu.set_irq_line(1);
|
||||||
|
irq_counter_enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(irq_counter_enable) {
|
|
||||||
if(--irq_counter == 0xffff) {
|
|
||||||
cpu.set_irq_line(1);
|
|
||||||
irq_counter_enable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ciram_addr(uint addr) const -> uint {
|
auto ciram_addr(uint addr) const -> uint {
|
||||||
@@ -33,7 +27,7 @@ struct BandaiFCG : Board {
|
|||||||
auto prg_read(uint addr) -> uint8 {
|
auto prg_read(uint addr) -> uint8 {
|
||||||
if(addr & 0x8000) {
|
if(addr & 0x8000) {
|
||||||
bool region = addr & 0x4000;
|
bool region = addr & 0x4000;
|
||||||
uint bank = (region == 0 ? prg_bank : 0x0f);
|
uint bank = (region == 0 ? prg_bank : (uint8)0x0f);
|
||||||
return prgrom.read((bank << 14) | (addr & 0x3fff));
|
return prgrom.read((bank << 14) | (addr & 0x3fff));
|
||||||
}
|
}
|
||||||
return cpu.mdr();
|
return cpu.mdr();
|
||||||
|
@@ -36,10 +36,10 @@ Board::Board(Markup::Node& document) {
|
|||||||
chrrom.size = crom["size"].natural();
|
chrrom.size = crom["size"].natural();
|
||||||
chrram.size = cram["size"].natural();
|
chrram.size = cram["size"].natural();
|
||||||
|
|
||||||
if(prgrom.size) prgrom.data = new uint8[prgrom.size]();
|
if(prgrom.size) prgrom.data = new uint8_t[prgrom.size]();
|
||||||
if(prgram.size) prgram.data = new uint8[prgram.size]();
|
if(prgram.size) prgram.data = new uint8_t[prgram.size]();
|
||||||
if(chrrom.size) chrrom.data = new uint8[chrrom.size]();
|
if(chrrom.size) chrrom.data = new uint8_t[chrrom.size]();
|
||||||
if(chrram.size) chrram.data = new uint8[chrram.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 = prom["name"].text()) interface->loadRequest(ID::ProgramROM, name, true);
|
||||||
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name, false);
|
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name, false);
|
||||||
@@ -80,19 +80,13 @@ auto Board::mirror(uint addr, uint size) -> uint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Board::main() -> void {
|
auto Board::main() -> void {
|
||||||
while(true) {
|
cartridge.clock += 12 * 4095;
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
tick();
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
cartridge.clock += 12 * 4095;
|
|
||||||
tick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Board::tick() -> void {
|
auto Board::tick() -> void {
|
||||||
cartridge.clock += 12;
|
cartridge.clock += 12;
|
||||||
if(cartridge.clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
if(cartridge.clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Board::chr_read(uint addr) -> uint8 {
|
auto Board::chr_read(uint addr) -> uint8 {
|
||||||
|
@@ -44,25 +44,19 @@ struct Sunsoft5B : Board {
|
|||||||
} pulse[3];
|
} pulse[3];
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(irq_counter_enable) {
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
if(--irq_counter == 0xffff) {
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
cpu.set_irq_line(irq_enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(irq_counter_enable) {
|
|
||||||
if(--irq_counter == 0xffff) {
|
|
||||||
cpu.set_irq_line(irq_enable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pulse[0].clock();
|
|
||||||
pulse[1].clock();
|
|
||||||
pulse[2].clock();
|
|
||||||
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
|
||||||
apu.set_sample(-output);
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pulse[0].clock();
|
||||||
|
pulse[1].clock();
|
||||||
|
pulse[2].clock();
|
||||||
|
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
||||||
|
apu.set_sample(-output);
|
||||||
|
|
||||||
|
tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto prg_read(uint addr) -> uint8 {
|
auto prg_read(uint addr) -> uint8 {
|
||||||
|
@@ -6,10 +6,6 @@ namespace Famicom {
|
|||||||
#include "board/board.cpp"
|
#include "board/board.cpp"
|
||||||
Cartridge cartridge;
|
Cartridge cartridge;
|
||||||
|
|
||||||
auto Cartridge::loaded() const -> bool {
|
|
||||||
return _loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Cartridge::sha256() const -> string {
|
auto Cartridge::sha256() const -> string {
|
||||||
return _sha256;
|
return _sha256;
|
||||||
}
|
}
|
||||||
@@ -22,8 +18,8 @@ auto Cartridge::title() const -> string {
|
|||||||
return information.title;
|
return information.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::Main() -> void {
|
auto Cartridge::Enter() -> void {
|
||||||
cartridge.main();
|
while(true) scheduler.synchronize(), cartridge.main();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::main() -> void {
|
auto Cartridge::main() -> void {
|
||||||
@@ -40,14 +36,9 @@ auto Cartridge::load() -> void {
|
|||||||
sha.data(board->prgrom.data, board->prgrom.size);
|
sha.data(board->prgrom.data, board->prgrom.size);
|
||||||
sha.data(board->chrrom.data, board->chrrom.size);
|
sha.data(board->chrrom.data, board->chrrom.size);
|
||||||
_sha256 = sha.digest();
|
_sha256 = sha.digest();
|
||||||
|
|
||||||
system.load();
|
|
||||||
_loaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::unload() -> void {
|
auto Cartridge::unload() -> void {
|
||||||
if(!loaded()) return;
|
|
||||||
_loaded = false;
|
|
||||||
memory.reset();
|
memory.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +47,7 @@ auto Cartridge::power() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::reset() -> void {
|
auto Cartridge::reset() -> void {
|
||||||
create(Cartridge::Main, 21477272);
|
create(Cartridge::Enter, 21'477'272);
|
||||||
board->reset();
|
board->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,10 +2,9 @@
|
|||||||
#include "board/board.hpp"
|
#include "board/board.hpp"
|
||||||
|
|
||||||
struct Cartridge : Thread {
|
struct Cartridge : Thread {
|
||||||
static auto Main() -> void;
|
static auto Enter() -> void;
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
|
|
||||||
auto loaded() const -> bool;
|
|
||||||
auto sha256() const -> string;
|
auto sha256() const -> string;
|
||||||
auto manifest() const -> string;
|
auto manifest() const -> string;
|
||||||
auto title() const -> string;
|
auto title() const -> string;
|
||||||
@@ -31,7 +30,6 @@ struct Cartridge : Thread {
|
|||||||
|
|
||||||
//privileged:
|
//privileged:
|
||||||
Board* board = nullptr;
|
Board* board = nullptr;
|
||||||
bool _loaded = false;
|
|
||||||
string _sha256;
|
string _sha256;
|
||||||
|
|
||||||
auto prg_read(uint addr) -> uint8;
|
auto prg_read(uint addr) -> uint8;
|
||||||
|
@@ -4,14 +4,8 @@ struct MMC1 : Chip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(writedelay) writedelay--;
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
tick();
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(writedelay) writedelay--;
|
|
||||||
tick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto prg_addr(uint addr) -> uint {
|
auto prg_addr(uint addr) -> uint {
|
||||||
|
@@ -3,15 +3,9 @@ struct MMC3 : Chip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(irq_delay) irq_delay--;
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
cpu.set_irq_line(irq_line);
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
tick();
|
||||||
}
|
|
||||||
|
|
||||||
if(irq_delay) irq_delay--;
|
|
||||||
cpu.set_irq_line(irq_line);
|
|
||||||
tick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto irq_test(uint addr) -> void {
|
auto irq_test(uint addr) -> void {
|
||||||
|
@@ -4,17 +4,11 @@ struct MMC5 : Chip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
//scanline() resets this; if no scanlines detected, enter video blanking period
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
//scanline() resets this; if no scanlines detected, enter video blanking period
|
cpu.set_irq_line(irq_enable && irq_pending);
|
||||||
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
tick();
|
||||||
|
|
||||||
cpu.set_irq_line(irq_enable && irq_pending);
|
|
||||||
tick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto scanline(uint y) -> void {
|
auto scanline(uint y) -> void {
|
||||||
@@ -94,7 +88,7 @@ struct MMC5 : Chip {
|
|||||||
auto prg_write(uint addr, uint8 data) -> void {
|
auto prg_write(uint addr, uint8 data) -> void {
|
||||||
if((addr & 0xfc00) == 0x5c00) {
|
if((addr & 0xfc00) == 0x5c00) {
|
||||||
//writes 0x00 *during* Vblank (not during screen rendering ...)
|
//writes 0x00 *during* Vblank (not during screen rendering ...)
|
||||||
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : 0x00;
|
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : (uint8)0x00;
|
||||||
if(exram_mode == 2) exram[addr & 0x03ff] = data;
|
if(exram_mode == 2) exram[addr & 0x03ff] = data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -277,7 +271,7 @@ struct MMC5 : Chip {
|
|||||||
switch(nametable_mode[(addr >> 10) & 3]) {
|
switch(nametable_mode[(addr >> 10) & 3]) {
|
||||||
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
|
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
|
||||||
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
|
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
|
||||||
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : 0x00;
|
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
|
||||||
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
|
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,15 +3,9 @@ struct MMC6 : Chip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(irq_delay) irq_delay--;
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
cpu.set_irq_line(irq_line);
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
tick();
|
||||||
}
|
|
||||||
|
|
||||||
if(irq_delay) irq_delay--;
|
|
||||||
cpu.set_irq_line(irq_line);
|
|
||||||
tick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto irq_test(uint addr) -> void {
|
auto irq_test(uint addr) -> void {
|
||||||
|
@@ -3,31 +3,25 @@ struct VRC3 : Chip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(irq_enable) {
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
if(irq_mode == 0) { //16-bit
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
if(++irq_counter.w == 0) {
|
||||||
}
|
irq_line = 1;
|
||||||
|
irq_enable = irq_acknowledge;
|
||||||
if(irq_enable) {
|
irq_counter.w = irq_latch;
|
||||||
if(irq_mode == 0) { //16-bit
|
}
|
||||||
if(++irq_counter.w == 0) {
|
}
|
||||||
irq_line = 1;
|
if(irq_mode == 1) { //8-bit
|
||||||
irq_enable = irq_acknowledge;
|
if(++irq_counter.l == 0) {
|
||||||
irq_counter.w = irq_latch;
|
irq_line = 1;
|
||||||
}
|
irq_enable = irq_acknowledge;
|
||||||
}
|
irq_counter.l = irq_latch;
|
||||||
if(irq_mode == 1) { //8-bit
|
|
||||||
if(++irq_counter.l == 0) {
|
|
||||||
irq_line = 1;
|
|
||||||
irq_enable = irq_acknowledge;
|
|
||||||
irq_counter.l = irq_latch;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.set_irq_line(irq_line);
|
|
||||||
tick();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cpu.set_irq_line(irq_line);
|
||||||
|
tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto prg_addr(uint addr) const -> uint {
|
auto prg_addr(uint addr) const -> uint {
|
||||||
@@ -90,8 +84,8 @@ struct VRC3 : Chip {
|
|||||||
uint16 irq_latch;
|
uint16 irq_latch;
|
||||||
struct {
|
struct {
|
||||||
union {
|
union {
|
||||||
uint16 w;
|
uint16_t w;
|
||||||
struct { uint8 order_lsb2(l, h); };
|
struct { uint8_t order_lsb2(l, h); };
|
||||||
};
|
};
|
||||||
} irq_counter;
|
} irq_counter;
|
||||||
bool irq_line;
|
bool irq_line;
|
||||||
|
@@ -3,26 +3,11 @@ struct VRC4 : Chip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(irq_enable) {
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
if(irq_mode == 0) {
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
irq_scalar -= 3;
|
||||||
}
|
if(irq_scalar <= 0) {
|
||||||
|
irq_scalar += 341;
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
irq_counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(irq_mode == 1) {
|
|
||||||
if(irq_counter == 0xff) {
|
if(irq_counter == 0xff) {
|
||||||
irq_counter = irq_latch;
|
irq_counter = irq_latch;
|
||||||
irq_line = 1;
|
irq_line = 1;
|
||||||
@@ -32,9 +17,18 @@ struct VRC4 : Chip {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.set_irq_line(irq_line);
|
if(irq_mode == 1) {
|
||||||
tick();
|
if(irq_counter == 0xff) {
|
||||||
|
irq_counter = irq_latch;
|
||||||
|
irq_line = 1;
|
||||||
|
} else {
|
||||||
|
irq_counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cpu.set_irq_line(irq_line);
|
||||||
|
tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto prg_addr(uint addr) const -> uint {
|
auto prg_addr(uint addr) const -> uint {
|
||||||
|
@@ -77,26 +77,11 @@ struct VRC6 : Chip {
|
|||||||
} sawtooth;
|
} sawtooth;
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(irq_enable) {
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
if(irq_mode == 0) {
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
irq_scalar -= 3;
|
||||||
}
|
if(irq_scalar <= 0) {
|
||||||
|
irq_scalar += 341;
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
irq_counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(irq_mode == 1) {
|
|
||||||
if(irq_counter == 0xff) {
|
if(irq_counter == 0xff) {
|
||||||
irq_counter = irq_latch;
|
irq_counter = irq_latch;
|
||||||
irq_line = 1;
|
irq_line = 1;
|
||||||
@@ -105,16 +90,25 @@ struct VRC6 : Chip {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cpu.set_irq_line(irq_line);
|
|
||||||
|
|
||||||
pulse1.clock();
|
if(irq_mode == 1) {
|
||||||
pulse2.clock();
|
if(irq_counter == 0xff) {
|
||||||
sawtooth.clock();
|
irq_counter = irq_latch;
|
||||||
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
|
irq_line = 1;
|
||||||
apu.set_sample(-output);
|
} else {
|
||||||
|
irq_counter++;
|
||||||
tick();
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
cpu.set_irq_line(irq_line);
|
||||||
|
|
||||||
|
pulse1.clock();
|
||||||
|
pulse2.clock();
|
||||||
|
sawtooth.clock();
|
||||||
|
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
|
||||||
|
apu.set_sample(-output);
|
||||||
|
|
||||||
|
tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto prg_addr(uint addr) const -> uint {
|
auto prg_addr(uint addr) const -> uint {
|
||||||
|
@@ -6,26 +6,11 @@ struct VRC7 : Chip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> void {
|
auto main() -> void {
|
||||||
while(true) {
|
if(irq_enable) {
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
if(irq_mode == 0) {
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
irq_scalar -= 3;
|
||||||
}
|
if(irq_scalar <= 0) {
|
||||||
|
irq_scalar += 341;
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
irq_counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(irq_mode == 1) {
|
|
||||||
if(irq_counter == 0xff) {
|
if(irq_counter == 0xff) {
|
||||||
irq_counter = irq_latch;
|
irq_counter = irq_latch;
|
||||||
irq_line = 1;
|
irq_line = 1;
|
||||||
@@ -34,10 +19,19 @@ struct VRC7 : Chip {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cpu.set_irq_line(irq_line);
|
|
||||||
|
|
||||||
tick();
|
if(irq_mode == 1) {
|
||||||
|
if(irq_counter == 0xff) {
|
||||||
|
irq_counter = irq_latch;
|
||||||
|
irq_line = 1;
|
||||||
|
} else {
|
||||||
|
irq_counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
cpu.set_irq_line(irq_line);
|
||||||
|
|
||||||
|
tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto reg_write(uint addr, uint8 data) -> void {
|
auto reg_write(uint addr, uint8 data) -> void {
|
||||||
|
@@ -7,33 +7,23 @@ namespace Famicom {
|
|||||||
CPU cpu;
|
CPU cpu;
|
||||||
|
|
||||||
auto CPU::Enter() -> void {
|
auto CPU::Enter() -> void {
|
||||||
while(true) {
|
while(true) scheduler.synchronize(), cpu.main();
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
cpu.main();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::main() -> void {
|
auto CPU::main() -> void {
|
||||||
if(status.interrupt_pending) {
|
if(status.interrupt_pending) return interrupt();
|
||||||
interrupt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
exec();
|
exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::add_clocks(uint clocks) -> void {
|
auto CPU::add_clocks(uint clocks) -> void {
|
||||||
apu.clock -= clocks;
|
apu.clock -= clocks;
|
||||||
if(apu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(apu.thread);
|
if(apu.clock < 0 && !scheduler.synchronizing()) co_switch(apu.thread);
|
||||||
|
|
||||||
ppu.clock -= clocks;
|
ppu.clock -= clocks;
|
||||||
if(ppu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(ppu.thread);
|
if(ppu.clock < 0 && !scheduler.synchronizing()) co_switch(ppu.thread);
|
||||||
|
|
||||||
cartridge.clock -= clocks;
|
cartridge.clock -= clocks;
|
||||||
if(cartridge.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cartridge.thread);
|
if(cartridge.clock < 0 && !scheduler.synchronizing()) co_switch(cartridge.thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::power() -> void {
|
auto CPU::power() -> void {
|
||||||
@@ -48,7 +38,7 @@ auto CPU::power() -> void {
|
|||||||
|
|
||||||
auto CPU::reset() -> void {
|
auto CPU::reset() -> void {
|
||||||
R6502::reset();
|
R6502::reset();
|
||||||
create(CPU::Enter, 21477272);
|
create(CPU::Enter, 21'477'272);
|
||||||
|
|
||||||
regs.pc = bus.read(0xfffc) << 0;
|
regs.pc = bus.read(0xfffc) << 0;
|
||||||
regs.pc |= bus.read(0xfffd) << 8;
|
regs.pc |= bus.read(0xfffd) << 8;
|
||||||
|
@@ -1,22 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
//license: GPLv3
|
||||||
|
//started: 2011-09-05
|
||||||
|
|
||||||
#include <emulator/emulator.hpp>
|
#include <emulator/emulator.hpp>
|
||||||
#include <processor/r6502/r6502.hpp>
|
#include <processor/r6502/r6502.hpp>
|
||||||
|
|
||||||
namespace Famicom {
|
namespace Famicom {
|
||||||
namespace Info {
|
namespace Info {
|
||||||
static const string Name = "bnes";
|
static const uint SerializerVersion = 3;
|
||||||
static const uint SerializerVersion = 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
bnes - Famicom emulator
|
|
||||||
authors: byuu, Ryphecha
|
|
||||||
license: GPLv3
|
|
||||||
project started: 2011-09-05
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <libco/libco.h>
|
#include <libco/libco.h>
|
||||||
|
|
||||||
namespace Famicom {
|
namespace Famicom {
|
||||||
@@ -27,7 +22,7 @@ namespace Famicom {
|
|||||||
|
|
||||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||||
if(thread) co_delete(thread);
|
if(thread) co_delete(thread);
|
||||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
thread = co_create(65'536 * sizeof(void*), entrypoint);
|
||||||
this->frequency = frequency;
|
this->frequency = frequency;
|
||||||
clock = 0;
|
clock = 0;
|
||||||
}
|
}
|
||||||
@@ -51,7 +46,6 @@ namespace Famicom {
|
|||||||
#include <fc/apu/apu.hpp>
|
#include <fc/apu/apu.hpp>
|
||||||
#include <fc/ppu/ppu.hpp>
|
#include <fc/ppu/ppu.hpp>
|
||||||
#include <fc/cheat/cheat.hpp>
|
#include <fc/cheat/cheat.hpp>
|
||||||
#include <fc/video/video.hpp>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <fc/interface/interface.hpp>
|
#include <fc/interface/interface.hpp>
|
||||||
|
@@ -15,12 +15,15 @@ auto Input::latch(bool data) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Input::data(bool port) -> bool {
|
auto Input::data(bool port) -> bool {
|
||||||
|
//table to convert native button ordering to Emulator::Interface ordering
|
||||||
|
static const uint lookup[] = {5, 4, 6, 7, 0, 1, 2, 3};
|
||||||
|
|
||||||
bool result = 0;
|
bool result = 0;
|
||||||
|
|
||||||
if(port == 0) {
|
if(port == 0) {
|
||||||
if(port1 == Device::Joypad) {
|
if(port1 == Device::Joypad) {
|
||||||
if(counter1 >= 8) return 1;
|
if(counter1 >= 8) return 1;
|
||||||
result = interface->inputPoll(0, 0u, counter1);
|
result = interface->inputPoll(0, 0u, lookup[counter1]);
|
||||||
if(latchdata == 0) counter1++;
|
if(latchdata == 0) counter1++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +31,7 @@ auto Input::data(bool port) -> bool {
|
|||||||
if(port == 1) {
|
if(port == 1) {
|
||||||
if(port2 == Device::Joypad) {
|
if(port2 == Device::Joypad) {
|
||||||
if(counter2 >= 8) return 1;
|
if(counter2 >= 8) return 1;
|
||||||
result = interface->inputPoll(1, 0u, counter2);
|
result = interface->inputPoll(1, 0u, lookup[counter2]);
|
||||||
if(latchdata == 0) counter2++;
|
if(latchdata == 0) counter2++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,37 +8,38 @@ Settings settings;
|
|||||||
Interface::Interface() {
|
Interface::Interface() {
|
||||||
interface = this;
|
interface = this;
|
||||||
|
|
||||||
information.name = "Famicom";
|
information.manufacturer = "Nintendo";
|
||||||
information.width = 256;
|
information.name = "Famicom";
|
||||||
information.height = 240;
|
information.width = 256;
|
||||||
information.overscan = true;
|
information.height = 240;
|
||||||
information.aspectRatio = 8.0 / 7.0;
|
information.overscan = true;
|
||||||
information.resettable = true;
|
information.aspectRatio = 8.0 / 7.0;
|
||||||
|
information.resettable = true;
|
||||||
|
|
||||||
information.capability.states = true;
|
information.capability.states = true;
|
||||||
information.capability.cheats = true;
|
information.capability.cheats = true;
|
||||||
|
|
||||||
media.append({ID::Famicom, "Famicom", "fc", true});
|
media.append({ID::Famicom, "Famicom", "fc", true});
|
||||||
|
|
||||||
{ Device device{0, ID::Port1 | ID::Port2, "Controller"};
|
{ Device device{0, ID::Port1 | ID::Port2, "Controller"};
|
||||||
device.input.append({0, 0, "A" });
|
device.inputs.append({0, 0, "Up" });
|
||||||
device.input.append({1, 0, "B" });
|
device.inputs.append({1, 0, "Down" });
|
||||||
device.input.append({2, 0, "Select"});
|
device.inputs.append({2, 0, "Left" });
|
||||||
device.input.append({3, 0, "Start" });
|
device.inputs.append({3, 0, "Right" });
|
||||||
device.input.append({4, 0, "Up" });
|
device.inputs.append({4, 0, "B" });
|
||||||
device.input.append({5, 0, "Down" });
|
device.inputs.append({5, 0, "A" });
|
||||||
device.input.append({6, 0, "Left" });
|
device.inputs.append({6, 0, "Select"});
|
||||||
device.input.append({7, 0, "Right" });
|
device.inputs.append({7, 0, "Start" });
|
||||||
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
|
devices.append(device);
|
||||||
this->device.append(device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
port.append({0, "Port 1"});
|
ports.append({0, "Port 1"});
|
||||||
port.append({1, "Port 2"});
|
ports.append({1, "Port 2"});
|
||||||
|
|
||||||
for(auto& device : this->device) {
|
for(auto& device : devices) {
|
||||||
for(auto& port : this->port) {
|
for(auto& port : ports) {
|
||||||
if(device.portmask & (1 << port.id)) {
|
if(device.portmask & (1 << port.id)) {
|
||||||
port.device.append(device);
|
port.devices.append(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,12 +57,67 @@ auto Interface::videoFrequency() -> double {
|
|||||||
return 21477272.0 / (262.0 * 1364.0 - 4.0);
|
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 {
|
auto Interface::audioFrequency() -> double {
|
||||||
return 21477272.0 / 12.0;
|
return 21477272.0 / 12.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::loaded() -> bool {
|
auto Interface::loaded() -> bool {
|
||||||
return cartridge.loaded();
|
return system.loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::sha256() -> string {
|
auto Interface::sha256() -> string {
|
||||||
@@ -84,7 +140,7 @@ auto Interface::group(uint id) -> uint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::load(uint id) -> void {
|
auto Interface::load(uint id) -> void {
|
||||||
cartridge.load();
|
system.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::save() -> void {
|
auto Interface::save() -> void {
|
||||||
@@ -131,7 +187,7 @@ auto Interface::save(uint id, const stream& stream) -> void {
|
|||||||
|
|
||||||
auto Interface::unload() -> void {
|
auto Interface::unload() -> void {
|
||||||
save();
|
save();
|
||||||
cartridge.unload();
|
system.unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::power() -> void {
|
auto Interface::power() -> void {
|
||||||
@@ -147,7 +203,7 @@ auto Interface::run() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::serialize() -> serializer {
|
auto Interface::serialize() -> serializer {
|
||||||
system.runtosave();
|
system.runToSave();
|
||||||
return system.serialize();
|
return system.serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +236,11 @@ auto Interface::get(const string& name) -> any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::set(const string& name, const any& value) -> bool {
|
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;
|
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,8 @@ struct Interface : Emulator::Interface {
|
|||||||
auto manifest() -> string;
|
auto manifest() -> string;
|
||||||
auto title() -> string;
|
auto title() -> string;
|
||||||
auto videoFrequency() -> double;
|
auto videoFrequency() -> double;
|
||||||
|
auto videoColors() -> uint32;
|
||||||
|
auto videoColor(uint32 color) -> uint64;
|
||||||
auto audioFrequency() -> double;
|
auto audioFrequency() -> double;
|
||||||
|
|
||||||
auto loaded() -> bool;
|
auto loaded() -> bool;
|
||||||
@@ -53,7 +55,7 @@ struct Interface : Emulator::Interface {
|
|||||||
auto set(const string& name, const any& value) -> bool override;
|
auto set(const string& name, const any& value) -> bool override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
vector<Device> device;
|
vector<Device> devices;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
|
@@ -2,21 +2,16 @@
|
|||||||
|
|
||||||
namespace Famicom {
|
namespace Famicom {
|
||||||
|
|
||||||
#include "serialization.cpp"
|
|
||||||
PPU ppu;
|
PPU ppu;
|
||||||
|
|
||||||
auto PPU::Main() -> void {
|
#include "serialization.cpp"
|
||||||
ppu.main();
|
|
||||||
|
auto PPU::Enter() -> void {
|
||||||
|
while(true) scheduler.synchronize(), ppu.main();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::main() -> void {
|
auto PPU::main() -> void {
|
||||||
while(true) {
|
raster_scanline();
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::PPU) {
|
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
raster_scanline();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::tick() -> void {
|
auto PPU::tick() -> void {
|
||||||
@@ -47,14 +42,18 @@ auto PPU::scanline() -> void {
|
|||||||
|
|
||||||
auto PPU::frame() -> void {
|
auto PPU::frame() -> void {
|
||||||
status.field ^= 1;
|
status.field ^= 1;
|
||||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
scheduler.exit(Scheduler::Event::Frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PPU::refresh() -> void {
|
||||||
|
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 240);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::power() -> void {
|
auto PPU::power() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::reset() -> void {
|
auto PPU::reset() -> void {
|
||||||
create(PPU::Main, 21477272);
|
create(PPU::Enter, 21'477'272);
|
||||||
|
|
||||||
status.mdr = 0x00;
|
status.mdr = 0x00;
|
||||||
status.field = 0;
|
status.field = 0;
|
||||||
@@ -113,7 +112,6 @@ auto PPU::read(uint16 addr) -> uint8 {
|
|||||||
break;
|
break;
|
||||||
case 4: //OAMDATA
|
case 4: //OAMDATA
|
||||||
result = oam[status.oam_addr];
|
result = oam[status.oam_addr];
|
||||||
if((status.oam_addr & 3) == 3) result &= 0xe3;
|
|
||||||
break;
|
break;
|
||||||
case 7: //PPUDATA
|
case 7: //PPUDATA
|
||||||
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00;
|
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00;
|
||||||
@@ -164,6 +162,7 @@ auto PPU::write(uint16 addr, uint8 data) -> void {
|
|||||||
status.oam_addr = data;
|
status.oam_addr = data;
|
||||||
return;
|
return;
|
||||||
case 4: //OAMDATA
|
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;
|
oam[status.oam_addr++] = data;
|
||||||
return;
|
return;
|
||||||
case 5: //PPUSCROLL
|
case 5: //PPUSCROLL
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
struct PPU : Thread {
|
struct PPU : Thread {
|
||||||
static auto Main() -> void;
|
static auto Enter() -> void;
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
auto tick() -> void;
|
auto tick() -> void;
|
||||||
|
|
||||||
auto scanline() -> void;
|
auto scanline() -> void;
|
||||||
auto frame() -> void;
|
auto frame() -> void;
|
||||||
|
auto refresh() -> void;
|
||||||
|
|
||||||
auto power() -> void;
|
auto power() -> void;
|
||||||
auto reset() -> void;
|
auto reset() -> void;
|
||||||
|
@@ -4,25 +4,41 @@ namespace Famicom {
|
|||||||
|
|
||||||
Scheduler scheduler;
|
Scheduler scheduler;
|
||||||
|
|
||||||
auto Scheduler::enter() -> void {
|
|
||||||
host_thread = co_active();
|
|
||||||
co_switch(thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Scheduler::exit(ExitReason reason) -> void {
|
|
||||||
exit_reason = reason;
|
|
||||||
thread = co_active();
|
|
||||||
co_switch(host_thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Scheduler::power() -> void {
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Scheduler::reset() -> void {
|
auto Scheduler::reset() -> void {
|
||||||
host_thread = co_active();
|
host = co_active();
|
||||||
thread = cpu.thread;
|
resume = cpu.thread;
|
||||||
sync = SynchronizeMode::None;
|
}
|
||||||
exit_reason = ExitReason::UnknownEvent;
|
|
||||||
|
auto Scheduler::enter(Mode mode_) -> Event {
|
||||||
|
mode = mode_;
|
||||||
|
host = co_active();
|
||||||
|
co_switch(resume);
|
||||||
|
if(event == Event::Frame) ppu.refresh();
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Scheduler::exit(Event event_) -> void {
|
||||||
|
event = event_;
|
||||||
|
resume = co_active();
|
||||||
|
co_switch(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Scheduler::synchronize(cothread_t thread) -> void {
|
||||||
|
if(thread == 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,16 +1,28 @@
|
|||||||
struct Scheduler : property<Scheduler> {
|
struct Scheduler {
|
||||||
enum class SynchronizeMode : uint { None, PPU, All } sync;
|
enum class Mode : uint {
|
||||||
enum class ExitReason : uint { UnknownEvent, FrameEvent, SynchronizeEvent };
|
Run,
|
||||||
|
SynchronizePPU,
|
||||||
|
SynchronizeAll,
|
||||||
|
};
|
||||||
|
|
||||||
auto enter() -> void;
|
enum class Event : uint {
|
||||||
auto exit(ExitReason) -> void;
|
Unknown,
|
||||||
|
Frame,
|
||||||
|
Synchronize,
|
||||||
|
};
|
||||||
|
|
||||||
auto power() -> void;
|
|
||||||
auto reset() -> void;
|
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;
|
||||||
|
|
||||||
cothread_t host_thread; //program thread (used to exit emulation)
|
private:
|
||||||
cothread_t thread; //active emulation thread (used to enter emulation)
|
cothread_t host = nullptr;
|
||||||
readonly<ExitReason> exit_reason;
|
cothread_t resume = nullptr;
|
||||||
|
Mode mode = Mode::Run;
|
||||||
|
Event event = Event::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Scheduler scheduler;
|
extern Scheduler scheduler;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
auto System::serialize() -> serializer {
|
auto System::serialize() -> serializer {
|
||||||
serializer s(serialize_size);
|
serializer s(_serializeSize);
|
||||||
|
|
||||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||||
char hash[64], description[512];
|
char hash[64], description[512];
|
||||||
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
|
|||||||
s.array(hash);
|
s.array(hash);
|
||||||
s.array(description);
|
s.array(description);
|
||||||
|
|
||||||
serialize_all(s);
|
serializeAll(s);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,14 +28,14 @@ auto System::unserialize(serializer& s) -> bool {
|
|||||||
if(version != Info::SerializerVersion) return false;
|
if(version != Info::SerializerVersion) return false;
|
||||||
|
|
||||||
power();
|
power();
|
||||||
serialize_all(s);
|
serializeAll(s);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::serialize(serializer& s) -> void {
|
auto System::serialize(serializer& s) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::serialize_all(serializer& s) -> void {
|
auto System::serializeAll(serializer& s) -> void {
|
||||||
system.serialize(s);
|
system.serialize(s);
|
||||||
input.serialize(s);
|
input.serialize(s);
|
||||||
cartridge.serialize(s);
|
cartridge.serialize(s);
|
||||||
@@ -44,7 +44,7 @@ auto System::serialize_all(serializer& s) -> void {
|
|||||||
ppu.serialize(s);
|
ppu.serialize(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::serialize_init() -> void {
|
auto System::serializeInit() -> void {
|
||||||
serializer s;
|
serializer s;
|
||||||
|
|
||||||
uint signature = 0, version = 0;
|
uint signature = 0, version = 0;
|
||||||
@@ -55,6 +55,6 @@ auto System::serialize_init() -> void {
|
|||||||
s.array(hash);
|
s.array(hash);
|
||||||
s.array(description);
|
s.array(description);
|
||||||
|
|
||||||
serialize_all(s);
|
serializeAll(s);
|
||||||
serialize_size = s.size();
|
_serializeSize = s.size();
|
||||||
}
|
}
|
||||||
|
@@ -2,50 +2,35 @@
|
|||||||
|
|
||||||
namespace Famicom {
|
namespace Famicom {
|
||||||
|
|
||||||
|
#include "video.cpp"
|
||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
System system;
|
System system;
|
||||||
|
|
||||||
|
auto System::loaded() const -> bool { return _loaded; }
|
||||||
|
|
||||||
auto System::run() -> void {
|
auto System::run() -> void {
|
||||||
scheduler.enter();
|
scheduler.enter();
|
||||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
|
||||||
video.refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::runtosave() -> void {
|
auto System::runToSave() -> void {
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::PPU;
|
scheduler.synchronize(ppu.thread);
|
||||||
runthreadtosave();
|
scheduler.synchronize(cpu.thread);
|
||||||
|
scheduler.synchronize(apu.thread);
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
scheduler.synchronize(cartridge.thread);
|
||||||
scheduler.thread = cpu.thread;
|
|
||||||
runthreadtosave();
|
|
||||||
|
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
|
||||||
scheduler.thread = apu.thread;
|
|
||||||
runthreadtosave();
|
|
||||||
|
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
|
||||||
scheduler.thread = cartridge.thread;
|
|
||||||
runthreadtosave();
|
|
||||||
|
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto System::runthreadtosave() -> void {
|
|
||||||
while(true) {
|
|
||||||
scheduler.enter();
|
|
||||||
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
|
|
||||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
|
||||||
video.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::load() -> void {
|
auto System::load() -> void {
|
||||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||||
auto document = BML::unserialize(information.manifest);
|
auto document = BML::unserialize(information.manifest);
|
||||||
|
cartridge.load();
|
||||||
|
serializeInit();
|
||||||
|
_loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
serialize_init();
|
auto System::unload() -> void {
|
||||||
|
if(!loaded()) return;
|
||||||
|
cartridge.unload();
|
||||||
|
_loaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::power() -> void {
|
auto System::power() -> void {
|
||||||
@@ -54,22 +39,28 @@ auto System::power() -> void {
|
|||||||
apu.power();
|
apu.power();
|
||||||
ppu.power();
|
ppu.power();
|
||||||
input.reset();
|
input.reset();
|
||||||
scheduler.power();
|
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::reset() -> void {
|
auto System::reset() -> void {
|
||||||
|
Emulator::video.reset();
|
||||||
|
Emulator::video.setInterface(interface);
|
||||||
|
configureVideoPalette();
|
||||||
|
configureVideoEffects();
|
||||||
|
|
||||||
|
Emulator::audio.reset();
|
||||||
|
Emulator::audio.setInterface(interface);
|
||||||
|
|
||||||
cartridge.reset();
|
cartridge.reset();
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
apu.reset();
|
apu.reset();
|
||||||
ppu.reset();
|
ppu.reset();
|
||||||
input.reset();
|
input.reset();
|
||||||
scheduler.reset();
|
scheduler.reset();
|
||||||
video.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::init() -> void {
|
auto System::init() -> void {
|
||||||
assert(interface != 0);
|
assert(interface != nullptr);
|
||||||
input.connect(0, Input::Device::Joypad);
|
input.connect(0, Input::Device::Joypad);
|
||||||
input.connect(1, Input::Device::None);
|
input.connect(1, Input::Device::None);
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,36 @@
|
|||||||
struct System {
|
struct System {
|
||||||
|
auto loaded() const -> bool;
|
||||||
|
|
||||||
auto run() -> void;
|
auto run() -> void;
|
||||||
auto runtosave() -> void;
|
auto runToSave() -> void;
|
||||||
auto runthreadtosave() -> void;
|
|
||||||
|
|
||||||
auto load() -> void;
|
auto load() -> void;
|
||||||
|
auto unload() -> void;
|
||||||
auto power() -> void;
|
auto power() -> void;
|
||||||
auto reset() -> void;
|
auto reset() -> void;
|
||||||
|
|
||||||
auto init() -> void;
|
auto init() -> void;
|
||||||
auto term() -> void;
|
auto term() -> void;
|
||||||
|
|
||||||
|
//video.cpp
|
||||||
|
auto configureVideoPalette() -> void;
|
||||||
|
auto configureVideoEffects() -> void;
|
||||||
|
|
||||||
|
//serialization.cpp
|
||||||
auto serialize() -> serializer;
|
auto serialize() -> serializer;
|
||||||
auto unserialize(serializer&) -> bool;
|
auto unserialize(serializer&) -> bool;
|
||||||
|
|
||||||
auto serialize(serializer&) -> void;
|
auto serialize(serializer&) -> void;
|
||||||
auto serialize_all(serializer&) -> void;
|
auto serializeAll(serializer&) -> void;
|
||||||
auto serialize_init() -> void;
|
auto serializeInit() -> void;
|
||||||
|
|
||||||
struct Information {
|
struct Information {
|
||||||
string manifest;
|
string manifest;
|
||||||
} information;
|
} information;
|
||||||
|
|
||||||
uint serialize_size;
|
private:
|
||||||
|
bool _loaded = false;
|
||||||
|
uint _serializeSize = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern System system;
|
extern System system;
|
||||||
|
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,105 +0,0 @@
|
|||||||
#include <fc/fc.hpp>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#define VIDEO_CPP
|
|
||||||
namespace Famicom {
|
|
||||||
|
|
||||||
Video video;
|
|
||||||
|
|
||||||
Video::Video() {
|
|
||||||
output = new uint32[256 * 480];
|
|
||||||
paletteStandard = new uint32[1 << 9];
|
|
||||||
paletteEmulation = new uint32[1 << 9];
|
|
||||||
}
|
|
||||||
|
|
||||||
Video::~Video() {
|
|
||||||
delete[] output;
|
|
||||||
delete[] paletteStandard;
|
|
||||||
delete[] paletteEmulation;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Video::reset() -> void {
|
|
||||||
memory::fill(output, 256 * 480);
|
|
||||||
|
|
||||||
for(auto color : range(1 << 9)) {
|
|
||||||
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 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 (255 << 24) | ((r >> 8) << 16) | ((g >> 8) << 8) | ((b >> 8) << 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
struct Video {
|
|
||||||
Video();
|
|
||||||
~Video();
|
|
||||||
|
|
||||||
auto reset() -> void;
|
|
||||||
auto refresh() -> void;
|
|
||||||
|
|
||||||
uint32* output = nullptr;
|
|
||||||
uint32* paletteStandard = nullptr;
|
|
||||||
uint32* paletteEmulation = nullptr;
|
|
||||||
|
|
||||||
private:
|
|
||||||
auto generateColor(uint, double, double, double, double, double) -> uint32;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Video video;
|
|
@@ -1,16 +1,16 @@
|
|||||||
gb_objects := gb-interface gb-system gb-scheduler
|
processors += lr35902
|
||||||
gb_objects += gb-memory gb-cartridge
|
|
||||||
gb_objects += gb-cpu gb-ppu gb-apu
|
|
||||||
gb_objects += gb-cheat gb-video
|
|
||||||
objects += $(gb_objects)
|
|
||||||
|
|
||||||
obj/gb-interface.o: $(gb)/interface/interface.cpp $(call rwildcard,$(gb)/interface/)
|
objects += gb-interface gb-system gb-scheduler
|
||||||
obj/gb-system.o: $(gb)/system/system.cpp $(call rwildcard,$(gb)/system/)
|
objects += gb-memory gb-cartridge
|
||||||
obj/gb-scheduler.o: $(gb)/scheduler/scheduler.cpp $(call rwildcard,$(gb)/scheduler/)
|
objects += gb-cpu gb-ppu gb-apu
|
||||||
obj/gb-cartridge.o: $(gb)/cartridge/cartridge.cpp $(call rwildcard,$(gb)/cartridge/)
|
objects += gb-cheat
|
||||||
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-interface.o: gb/interface/interface.cpp $(call rwildcard,gb/interface/)
|
||||||
obj/gb-ppu.o: $(gb)/ppu/ppu.cpp $(call rwildcard,$(gb)/ppu/)
|
obj/gb-system.o: gb/system/system.cpp $(call rwildcard,gb/system/)
|
||||||
obj/gb-apu.o: $(gb)/apu/apu.cpp $(call rwildcard,$(gb)/apu/)
|
obj/gb-scheduler.o: gb/scheduler/scheduler.cpp $(call rwildcard,gb/scheduler/)
|
||||||
obj/gb-cheat.o: $(gb)/cheat/cheat.cpp $(call rwildcard,$(gb)/cheat/)
|
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp $(call rwildcard,gb/cartridge/)
|
||||||
obj/gb-video.o: $(gb)/video/video.cpp $(call rwildcard,$(gb)/video/)
|
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/)
|
||||||
|
@@ -10,52 +10,49 @@ namespace GameBoy {
|
|||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
APU apu;
|
APU apu;
|
||||||
|
|
||||||
auto APU::Main() -> void {
|
auto APU::Enter() -> void {
|
||||||
apu.main();
|
while(true) scheduler.synchronize(), apu.main();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::main() -> void {
|
auto APU::main() -> void {
|
||||||
while(true) {
|
square1.run();
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
square2.run();
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
wave.run();
|
||||||
}
|
noise.run();
|
||||||
|
sequencer.run();
|
||||||
|
|
||||||
square1.run();
|
hipass(sequencer.center, sequencer.centerBias);
|
||||||
square2.run();
|
hipass(sequencer.left, sequencer.leftBias);
|
||||||
wave.run();
|
hipass(sequencer.right, sequencer.rightBias);
|
||||||
noise.run();
|
|
||||||
sequencer.run();
|
|
||||||
|
|
||||||
hipass(sequencer.center, sequencer.centerBias);
|
if(!system.sgb()) {
|
||||||
hipass(sequencer.left, sequencer.leftBias);
|
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
|
||||||
hipass(sequencer.right, sequencer.rightBias);
|
} else {
|
||||||
|
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
|
||||||
interface->audioSample(sequencer.left, sequencer.right);
|
interface->audioSample(samples, 2);
|
||||||
|
|
||||||
if(cycle == 0) { //512hz
|
|
||||||
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
|
|
||||||
square1.clockLength();
|
|
||||||
square2.clockLength();
|
|
||||||
wave.clockLength();
|
|
||||||
noise.clockLength();
|
|
||||||
}
|
|
||||||
if(phase == 2 || phase == 6) { //128hz
|
|
||||||
square1.clockSweep();
|
|
||||||
}
|
|
||||||
if(phase == 7) { //64hz
|
|
||||||
square1.clockEnvelope();
|
|
||||||
square2.clockEnvelope();
|
|
||||||
noise.clockEnvelope();
|
|
||||||
}
|
|
||||||
phase++;
|
|
||||||
}
|
|
||||||
cycle++;
|
|
||||||
|
|
||||||
clock += cpu.frequency;
|
|
||||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
|
||||||
co_switch(scheduler.active_thread = cpu.thread);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(cycle == 0) { //512hz
|
||||||
|
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
|
||||||
|
square1.clockLength();
|
||||||
|
square2.clockLength();
|
||||||
|
wave.clockLength();
|
||||||
|
noise.clockLength();
|
||||||
|
}
|
||||||
|
if(phase == 2 || phase == 6) { //128hz
|
||||||
|
square1.clockSweep();
|
||||||
|
}
|
||||||
|
if(phase == 7) { //64hz
|
||||||
|
square1.clockEnvelope();
|
||||||
|
square2.clockEnvelope();
|
||||||
|
noise.clockEnvelope();
|
||||||
|
}
|
||||||
|
phase++;
|
||||||
|
}
|
||||||
|
cycle++;
|
||||||
|
|
||||||
|
clock += cpu.frequency;
|
||||||
|
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
//filter to remove DC bias
|
//filter to remove DC bias
|
||||||
@@ -65,7 +62,8 @@ auto APU::hipass(int16& sample, int64& bias) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto APU::power() -> void {
|
auto APU::power() -> void {
|
||||||
create(Main, 2 * 1024 * 1024);
|
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;
|
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
||||||
|
|
||||||
square1.power();
|
square1.power();
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
struct APU : Thread, MMIO {
|
struct APU : Thread, MMIO {
|
||||||
static auto Main() -> void;
|
shared_pointer<Emulator::Stream> stream;
|
||||||
|
|
||||||
|
static auto Enter() -> void;
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
auto hipass(int16& sample, int64& bias) -> void;
|
auto hipass(int16& sample, int64& bias) -> void;
|
||||||
auto power() -> void;
|
auto power() -> void;
|
||||||
|
@@ -66,16 +66,16 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff21) { //NR42
|
if(addr == 0xff21) { //NR42
|
||||||
envelopeVolume = data >> 4;
|
envelopeVolume = data.bits(7,4);
|
||||||
envelopeDirection = data & 0x08;
|
envelopeDirection = data.bit (3);
|
||||||
envelopeFrequency = data & 0x07;
|
envelopeFrequency = data.bits(2,0);
|
||||||
if(!dacEnable()) enable = false;
|
if(!dacEnable()) enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff22) { //NR43
|
if(addr == 0xff22) { //NR43
|
||||||
frequency = data >> 4;
|
frequency = data.bits(7,4);
|
||||||
narrow = data & 0x08;
|
narrow = data.bit (3);
|
||||||
divisor = data & 0x07;
|
divisor = data.bits(2,0);
|
||||||
period = getPeriod();
|
period = getPeriod();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,10 +84,9 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
|||||||
if(length && --length == 0) enable = false;
|
if(length && --length == 0) enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initialize = data & 0x80;
|
counter = data.bit(6);
|
||||||
counter = data & 0x40;
|
|
||||||
|
|
||||||
if(initialize) {
|
if(data.bit(7)) {
|
||||||
enable = dacEnable();
|
enable = dacEnable();
|
||||||
lfsr = -1;
|
lfsr = -1;
|
||||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||||
@@ -95,7 +94,7 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
|||||||
|
|
||||||
if(!length) {
|
if(!length) {
|
||||||
length = 64;
|
length = 64;
|
||||||
if((apu.phase & 1) && counter) length--;
|
if(apu.phase.bit(0) && counter) length--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -68,26 +68,26 @@ auto APU::Sequencer::read(uint16 addr) -> uint8 {
|
|||||||
|
|
||||||
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
|
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
|
||||||
if(addr == 0xff24) { //NR50
|
if(addr == 0xff24) { //NR50
|
||||||
leftEnable = (uint1)(data >> 7);
|
leftEnable = data.bit (7);
|
||||||
leftVolume = (uint3)(data >> 4);
|
leftVolume = data.bits(6,4);
|
||||||
rightEnable = (uint1)(data >> 3);
|
rightEnable = data.bit (3);
|
||||||
rightVolume = (uint3)(data >> 0);
|
rightVolume = data.bits(2,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff25) { //NR51
|
if(addr == 0xff25) { //NR51
|
||||||
noise.leftEnable = data & 0x80;
|
noise.leftEnable = data.bit(7);
|
||||||
wave.leftEnable = data & 0x40;
|
wave.leftEnable = data.bit(6);
|
||||||
square2.leftEnable = data & 0x20;
|
square2.leftEnable = data.bit(5);
|
||||||
square1.leftEnable = data & 0x10;
|
square1.leftEnable = data.bit(4);
|
||||||
noise.rightEnable = data & 0x08;
|
noise.rightEnable = data.bit(3);
|
||||||
wave.rightEnable = data & 0x04;
|
wave.rightEnable = data.bit(2);
|
||||||
square2.rightEnable = data & 0x02;
|
square2.rightEnable = data.bit(1);
|
||||||
square1.rightEnable = data & 0x01;
|
square1.rightEnable = data.bit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff26) { //NR52
|
if(addr == 0xff26) { //NR52
|
||||||
if(enable != (bool)(data & 0x80)) {
|
if(enable != data.bit(7)) {
|
||||||
enable = data & 0x80;
|
enable = data.bit(7);
|
||||||
|
|
||||||
if(!enable) {
|
if(!enable) {
|
||||||
//power(bool) resets length counters when true (eg for CGB only)
|
//power(bool) resets length counters when true (eg for CGB only)
|
||||||
|
@@ -86,38 +86,37 @@ auto APU::Square1::read(uint16 addr) -> uint8 {
|
|||||||
|
|
||||||
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
||||||
if(addr == 0xff10) { //NR10
|
if(addr == 0xff10) { //NR10
|
||||||
if(sweepEnable && sweepNegate && !(data & 0x08)) enable = false;
|
if(sweepEnable && sweepNegate && !data.bit(3)) enable = false;
|
||||||
sweepFrequency = (data >> 4) & 7;
|
sweepFrequency = data.bits(6,4);
|
||||||
sweepDirection = data & 0x08;
|
sweepDirection = data.bit (3);
|
||||||
sweepShift = data & 0x07;
|
sweepShift = data.bits(2,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff11) { //NR11
|
if(addr == 0xff11) { //NR11
|
||||||
duty = data >> 6;
|
duty = data.bits(7,6);
|
||||||
length = 64 - (data & 0x3f);
|
length = 64 - data.bits(5,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff12) { //NR12
|
if(addr == 0xff12) { //NR12
|
||||||
envelopeVolume = data >> 4;
|
envelopeVolume = data.bits(7,4);
|
||||||
envelopeDirection = data & 0x08;
|
envelopeDirection = data.bit (3);
|
||||||
envelopeFrequency = data & 0x07;
|
envelopeFrequency = data.bits(2,0);
|
||||||
if(!dacEnable()) enable = false;
|
if(!dacEnable()) enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff13) { //NR13
|
if(addr == 0xff13) { //NR13
|
||||||
frequency = (frequency & 0x0700) | data;
|
frequency.bits(7,0) = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff14) { //NR14
|
if(addr == 0xff14) { //NR14
|
||||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||||
if(length && --length == 0) enable = false;
|
if(length && --length == 0) enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initialize = data & 0x80;
|
counter = data.bit(6);
|
||||||
counter = data & 0x40;
|
frequency.bits(10,8) = data.bits(2,0);
|
||||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
|
||||||
|
|
||||||
if(initialize) {
|
if(data.bit(7)) {
|
||||||
enable = dacEnable();
|
enable = dacEnable();
|
||||||
period = 2 * (2048 - frequency);
|
period = 2 * (2048 - frequency);
|
||||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||||
@@ -125,7 +124,7 @@ auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
|||||||
|
|
||||||
if(!length) {
|
if(!length) {
|
||||||
length = 64;
|
length = 64;
|
||||||
if((apu.phase & 1) && counter) length--;
|
if(apu.phase.bit(0) && counter) length--;
|
||||||
}
|
}
|
||||||
|
|
||||||
frequencyShadow = frequency;
|
frequencyShadow = frequency;
|
||||||
|
@@ -60,31 +60,30 @@ auto APU::Square2::read(uint16 addr) -> uint8 {
|
|||||||
|
|
||||||
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
||||||
if(addr == 0xff16) { //NR21
|
if(addr == 0xff16) { //NR21
|
||||||
duty = data >> 6;
|
duty = data.bits(7,6);
|
||||||
length = 64 - (data & 0x3f);
|
length = 64 - data.bits(5,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff17) { //NR22
|
if(addr == 0xff17) { //NR22
|
||||||
envelopeVolume = data >> 4;
|
envelopeVolume = data.bits(7,4);
|
||||||
envelopeDirection = data & 0x08;
|
envelopeDirection = data.bit (3);
|
||||||
envelopeFrequency = data & 0x07;
|
envelopeFrequency = data.bits(2,0);
|
||||||
if(!dacEnable()) enable = false;
|
if(!dacEnable()) enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff18) { //NR23
|
if(addr == 0xff18) { //NR23
|
||||||
frequency = (frequency & 0x0700) | data;
|
frequency.bits(7,0) = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff19) { //NR24
|
if(addr == 0xff19) { //NR24
|
||||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||||
if(length && --length == 0) enable = false;
|
if(length && --length == 0) enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initialize = data & 0x80;
|
counter = data.bit(6);
|
||||||
counter = data & 0x40;
|
frequency.bits(10,8) = data.bits(2,0);
|
||||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
|
||||||
|
|
||||||
if(initialize) {
|
if(data.bit(7)) {
|
||||||
enable = dacEnable();
|
enable = dacEnable();
|
||||||
period = 2 * (2048 - frequency);
|
period = 2 * (2048 - frequency);
|
||||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||||
@@ -92,7 +91,7 @@ auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
|||||||
|
|
||||||
if(!length) {
|
if(!length) {
|
||||||
length = 64;
|
length = 64;
|
||||||
if((apu.phase & 1) && counter) length--;
|
if(apu.phase.bit(0) && counter) length--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -59,7 +59,7 @@ auto APU::Wave::read(uint16 addr) -> uint8 {
|
|||||||
|
|
||||||
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||||
if(addr == 0xff1a) { //NR30
|
if(addr == 0xff1a) { //NR30
|
||||||
dacEnable = data & 0x80;
|
dacEnable = data.bit(7);
|
||||||
if(!dacEnable) enable = false;
|
if(!dacEnable) enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,23 +68,22 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff1c) { //NR32
|
if(addr == 0xff1c) { //NR32
|
||||||
volume = data >> 5;
|
volume = data.bits(6,5);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff1d) { //NR33
|
if(addr == 0xff1d) { //NR33
|
||||||
frequency = (frequency & 0x0700) | data;
|
frequency.bits(7,0) = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff1e) { //NR34
|
if(addr == 0xff1e) { //NR34
|
||||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||||
if(length && --length == 0) enable = false;
|
if(length && --length == 0) enable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initialize = data & 0x80;
|
counter = data.bit(6);
|
||||||
counter = data & 0x40;
|
frequency.bits(10,8) = data.bits(2,0);
|
||||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
|
||||||
|
|
||||||
if(initialize) {
|
if(data.bit(7)) {
|
||||||
if(!system.cgb() && patternHold) {
|
if(!system.cgb() && patternHold) {
|
||||||
//DMG,SGB trigger while channel is being read corrupts wave RAM
|
//DMG,SGB trigger while channel is being read corrupts wave RAM
|
||||||
if((patternOffset >> 1) <= 3) {
|
if((patternOffset >> 1) <= 3) {
|
||||||
@@ -107,7 +106,7 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
|||||||
|
|
||||||
if(!length) {
|
if(!length) {
|
||||||
length = 256;
|
length = 256;
|
||||||
if((apu.phase & 1) && counter) length--;
|
if(apu.phase.bit(0) && counter) length--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ namespace GameBoy {
|
|||||||
|
|
||||||
#include "mbc0/mbc0.cpp"
|
#include "mbc0/mbc0.cpp"
|
||||||
#include "mbc1/mbc1.cpp"
|
#include "mbc1/mbc1.cpp"
|
||||||
|
#include "mbc1m/mbc1m.cpp"
|
||||||
#include "mbc2/mbc2.cpp"
|
#include "mbc2/mbc2.cpp"
|
||||||
#include "mbc3/mbc3.cpp"
|
#include "mbc3/mbc3.cpp"
|
||||||
#include "mbc5/mbc5.cpp"
|
#include "mbc5/mbc5.cpp"
|
||||||
@@ -13,15 +14,6 @@ namespace GameBoy {
|
|||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
Cartridge cartridge;
|
Cartridge cartridge;
|
||||||
|
|
||||||
Cartridge::Cartridge() {
|
|
||||||
loaded = false;
|
|
||||||
sha256 = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Cartridge::~Cartridge() {
|
|
||||||
unload();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Cartridge::manifest() const -> string {
|
auto Cartridge::manifest() const -> string {
|
||||||
return information.markup;
|
return information.markup;
|
||||||
}
|
}
|
||||||
@@ -30,25 +22,9 @@ auto Cartridge::title() const -> string {
|
|||||||
return information.title;
|
return information.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
//intended for use with Super Game Boy for when no Game Boy cartridge is inserted
|
|
||||||
auto Cartridge::load_empty(System::Revision revision) -> void {
|
|
||||||
unload();
|
|
||||||
romsize = 32768;
|
|
||||||
romdata = allocate<uint8>(romsize, 0xff);
|
|
||||||
ramsize = 0;
|
|
||||||
mapper = &mbc0;
|
|
||||||
sha256 = Hash::SHA256(romdata, romsize).digest();
|
|
||||||
loaded = true;
|
|
||||||
system.load(revision);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Cartridge::load(System::Revision revision) -> void {
|
auto Cartridge::load(System::Revision revision) -> void {
|
||||||
unload();
|
information.markup = "";
|
||||||
|
interface->loadRequest(ID::Manifest, "manifest.bml", !system.sgb());
|
||||||
system.revision = revision; //needed for ID::Manifest to return correct group ID
|
|
||||||
if(revision != System::Revision::SuperGameBoy) {
|
|
||||||
interface->loadRequest(ID::Manifest, "manifest.bml", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
information.mapper = Mapper::Unknown;
|
information.mapper = Mapper::Unknown;
|
||||||
information.ram = false;
|
information.ram = false;
|
||||||
@@ -65,6 +41,7 @@ auto Cartridge::load(System::Revision revision) -> void {
|
|||||||
auto mapperid = document["board/mapper"].text();
|
auto mapperid = document["board/mapper"].text();
|
||||||
if(mapperid == "none" ) information.mapper = Mapper::MBC0;
|
if(mapperid == "none" ) information.mapper = Mapper::MBC0;
|
||||||
if(mapperid == "MBC1" ) information.mapper = Mapper::MBC1;
|
if(mapperid == "MBC1" ) information.mapper = Mapper::MBC1;
|
||||||
|
if(mapperid == "MBC1M") information.mapper = Mapper::MBC1M;
|
||||||
if(mapperid == "MBC2" ) information.mapper = Mapper::MBC2;
|
if(mapperid == "MBC2" ) information.mapper = Mapper::MBC2;
|
||||||
if(mapperid == "MBC3" ) information.mapper = Mapper::MBC3;
|
if(mapperid == "MBC3" ) information.mapper = Mapper::MBC3;
|
||||||
if(mapperid == "MBC5" ) information.mapper = Mapper::MBC5;
|
if(mapperid == "MBC5" ) information.mapper = Mapper::MBC5;
|
||||||
@@ -78,26 +55,24 @@ auto Cartridge::load(System::Revision revision) -> void {
|
|||||||
auto rom = document["board/rom"];
|
auto rom = document["board/rom"];
|
||||||
auto ram = document["board/ram"];
|
auto ram = document["board/ram"];
|
||||||
|
|
||||||
romsize = rom["size"].natural();
|
romsize = max(32768u, rom["size"].natural());
|
||||||
romdata = allocate<uint8>(romsize, 0xff);
|
romdata = allocate<uint8>(romsize, 0xff);
|
||||||
|
|
||||||
ramsize = ram["size"].natural();
|
ramsize = ram["size"].natural();
|
||||||
ramdata = allocate<uint8>(ramsize, 0xff);
|
ramdata = allocate<uint8>(ramsize, 0xff);
|
||||||
|
|
||||||
//Super Game Boy core loads memory from Super Famicom core
|
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, !system.sgb());
|
||||||
if(revision != System::Revision::SuperGameBoy) {
|
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name, false);
|
||||||
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, true);
|
if(auto name = ram["name"].text()) memory.append({ID::RAM, name});
|
||||||
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name, false);
|
|
||||||
if(auto name = ram["name"].text()) memory.append({ID::RAM, name});
|
|
||||||
}
|
|
||||||
|
|
||||||
information.romsize = rom["size"].natural();
|
information.romsize = romsize;
|
||||||
information.ramsize = ram["size"].natural();
|
information.ramsize = ramsize;
|
||||||
information.battery = (bool)ram["name"];
|
information.battery = (bool)ram["name"];
|
||||||
|
|
||||||
switch(information.mapper) { default:
|
switch(information.mapper) { default:
|
||||||
case Mapper::MBC0: mapper = &mbc0; break;
|
case Mapper::MBC0: mapper = &mbc0; break;
|
||||||
case Mapper::MBC1: mapper = &mbc1; break;
|
case Mapper::MBC1: mapper = &mbc1; break;
|
||||||
|
case Mapper::MBC1M: mapper = &mbc1m; break;
|
||||||
case Mapper::MBC2: mapper = &mbc2; break;
|
case Mapper::MBC2: mapper = &mbc2; break;
|
||||||
case Mapper::MBC3: mapper = &mbc3; break;
|
case Mapper::MBC3: mapper = &mbc3; break;
|
||||||
case Mapper::MBC5: mapper = &mbc5; break;
|
case Mapper::MBC5: mapper = &mbc5; break;
|
||||||
@@ -107,14 +82,11 @@ auto Cartridge::load(System::Revision revision) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sha256 = Hash::SHA256(romdata, romsize).digest();
|
sha256 = Hash::SHA256(romdata, romsize).digest();
|
||||||
loaded = true;
|
|
||||||
system.load(revision);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::unload() -> void {
|
auto Cartridge::unload() -> void {
|
||||||
if(romdata) { delete[] romdata; romdata = nullptr; romsize = 0; }
|
if(romdata) { delete[] romdata; romdata = nullptr; romsize = 0; }
|
||||||
if(ramdata) { delete[] ramdata; ramdata = nullptr; ramsize = 0; }
|
if(ramdata) { delete[] ramdata; ramdata = nullptr; ramsize = 0; }
|
||||||
loaded = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::rom_read(uint addr) -> uint8 {
|
auto Cartridge::rom_read(uint addr) -> uint8 {
|
||||||
@@ -144,7 +116,7 @@ auto Cartridge::mmio_read(uint16 addr) -> uint8 {
|
|||||||
|
|
||||||
if(bootrom_enable) {
|
if(bootrom_enable) {
|
||||||
const uint8* data = nullptr;
|
const uint8* data = nullptr;
|
||||||
switch(system.revision) { default:
|
switch(system.revision()) { default:
|
||||||
case System::Revision::GameBoy: data = system.bootROM.dmg; break;
|
case System::Revision::GameBoy: data = system.bootROM.dmg; break;
|
||||||
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break;
|
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break;
|
||||||
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
|
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
|
||||||
@@ -170,6 +142,7 @@ auto Cartridge::power() -> void {
|
|||||||
|
|
||||||
mbc0.power();
|
mbc0.power();
|
||||||
mbc1.power();
|
mbc1.power();
|
||||||
|
mbc1m.power();
|
||||||
mbc2.power();
|
mbc2.power();
|
||||||
mbc3.power();
|
mbc3.power();
|
||||||
mbc5.power();
|
mbc5.power();
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
struct Cartridge : MMIO, property<Cartridge> {
|
struct Cartridge : MMIO, property<Cartridge> {
|
||||||
Cartridge();
|
|
||||||
~Cartridge();
|
|
||||||
|
|
||||||
auto load_empty(System::Revision revision) -> void;
|
|
||||||
auto load(System::Revision revision) -> void;
|
auto load(System::Revision revision) -> void;
|
||||||
auto unload() -> void;
|
auto unload() -> void;
|
||||||
|
|
||||||
@@ -20,6 +16,7 @@ struct Cartridge : MMIO, property<Cartridge> {
|
|||||||
|
|
||||||
#include "mbc0/mbc0.hpp"
|
#include "mbc0/mbc0.hpp"
|
||||||
#include "mbc1/mbc1.hpp"
|
#include "mbc1/mbc1.hpp"
|
||||||
|
#include "mbc1m/mbc1m.hpp"
|
||||||
#include "mbc2/mbc2.hpp"
|
#include "mbc2/mbc2.hpp"
|
||||||
#include "mbc3/mbc3.hpp"
|
#include "mbc3/mbc3.hpp"
|
||||||
#include "mbc5/mbc5.hpp"
|
#include "mbc5/mbc5.hpp"
|
||||||
@@ -30,6 +27,7 @@ struct Cartridge : MMIO, property<Cartridge> {
|
|||||||
enum Mapper : uint {
|
enum Mapper : uint {
|
||||||
MBC0,
|
MBC0,
|
||||||
MBC1,
|
MBC1,
|
||||||
|
MBC1M,
|
||||||
MBC2,
|
MBC2,
|
||||||
MBC3,
|
MBC3,
|
||||||
MBC5,
|
MBC5,
|
||||||
@@ -62,7 +60,6 @@ struct Cartridge : MMIO, property<Cartridge> {
|
|||||||
};
|
};
|
||||||
vector<Memory> memory;
|
vector<Memory> memory;
|
||||||
|
|
||||||
readonly<bool> loaded;
|
|
||||||
readonly<string> sha256;
|
readonly<string> sha256;
|
||||||
|
|
||||||
uint8* romdata = nullptr;
|
uint8* romdata = nullptr;
|
||||||
|
@@ -9,7 +9,7 @@ auto Cartridge::HuC3::mmio_read(uint16 addr) -> uint8 {
|
|||||||
|
|
||||||
if((addr & 0xe000) == 0xa000) { //$a000-bfff
|
if((addr & 0xe000) == 0xa000) { //$a000-bfff
|
||||||
if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
|
if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
|
||||||
return 0xff;
|
return 0x01; //does not return open collection
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0xff;
|
return 0xff;
|
||||||
|
40
higan/gb/cartridge/mbc1m/mbc1m.cpp
Normal file
40
higan/gb/cartridge/mbc1m/mbc1m.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
auto Cartridge::MBC1M::mmio_read(uint16 addr) -> uint8 {
|
||||||
|
if((addr & 0xc000) == 0x0000) { //$0000-3fff
|
||||||
|
if(!modeSelect) return cartridge.rom_read(addr & 0x3fff);
|
||||||
|
return cartridge.rom_read((romHi << 4) * 0x4000 + (addr & 0x3fff));
|
||||||
|
}
|
||||||
|
|
||||||
|
if((addr & 0xc000) == 0x4000) { //$4000-7fff
|
||||||
|
return cartridge.rom_read((romHi << 4 | romLo) * 0x4000 + (addr & 0x3fff));
|
||||||
|
}
|
||||||
|
|
||||||
|
if((addr & 0xe000) == 0xa000) { //$a000-bfff
|
||||||
|
return cartridge.ram_read(addr & 0x1fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Cartridge::MBC1M::mmio_write(uint16 addr, uint8 data) -> void {
|
||||||
|
if((addr & 0xe000) == 0x2000) { //$2000-3fff
|
||||||
|
romLo = data.bits(0,3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if((addr & 0xe000) == 0x4000) { //$4000-5fff
|
||||||
|
romHi = data.bits(0,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if((addr & 0xe000) == 0x6000) { //$6000-7fff
|
||||||
|
modeSelect = data.bit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if((addr & 0xe000) == 0xa000) { //$a000-bfff
|
||||||
|
cartridge.ram_write(addr & 0x1fff, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Cartridge::MBC1M::power() -> void {
|
||||||
|
romHi = 0;
|
||||||
|
romLo = 1;
|
||||||
|
modeSelect = 0;
|
||||||
|
}
|
9
higan/gb/cartridge/mbc1m/mbc1m.hpp
Normal file
9
higan/gb/cartridge/mbc1m/mbc1m.hpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
struct MBC1M : MMIO {
|
||||||
|
auto mmio_read(uint16 addr) -> uint8;
|
||||||
|
auto mmio_write(uint16 addr, uint8 data) -> void;
|
||||||
|
auto power() -> void;
|
||||||
|
|
||||||
|
uint4 romLo;
|
||||||
|
uint2 romHi;
|
||||||
|
uint1 modeSelect;
|
||||||
|
} mbc1m;
|
@@ -7,6 +7,10 @@ auto Cartridge::serialize(serializer& s) -> void {
|
|||||||
s.integer(mbc1.ram_select);
|
s.integer(mbc1.ram_select);
|
||||||
s.integer(mbc1.mode_select);
|
s.integer(mbc1.mode_select);
|
||||||
|
|
||||||
|
s.integer(mbc1m.romLo);
|
||||||
|
s.integer(mbc1m.romHi);
|
||||||
|
s.integer(mbc1m.modeSelect);
|
||||||
|
|
||||||
s.integer(mbc2.ram_enable);
|
s.integer(mbc2.ram_enable);
|
||||||
s.integer(mbc2.rom_select);
|
s.integer(mbc2.rom_select);
|
||||||
|
|
||||||
|
@@ -8,23 +8,16 @@ namespace GameBoy {
|
|||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
CPU cpu;
|
CPU cpu;
|
||||||
|
|
||||||
auto CPU::Main() -> void {
|
auto CPU::Enter() -> void {
|
||||||
cpu.main();
|
while(true) scheduler.synchronize(), cpu.main();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::main() -> void {
|
auto CPU::main() -> void {
|
||||||
while(true) {
|
interrupt_test();
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
|
instruction();
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
interrupt_test();
|
|
||||||
exec();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::interrupt_raise(CPU::Interrupt id) -> void {
|
auto CPU::raise(CPU::Interrupt id) -> void {
|
||||||
if(id == Interrupt::Vblank) {
|
if(id == Interrupt::Vblank) {
|
||||||
status.interrupt_request_vblank = 1;
|
status.interrupt_request_vblank = 1;
|
||||||
if(status.interrupt_enable_vblank) r.halt = false;
|
if(status.interrupt_enable_vblank) r.halt = false;
|
||||||
@@ -52,42 +45,32 @@ auto CPU::interrupt_raise(CPU::Interrupt id) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::interrupt_test() -> void {
|
auto CPU::interrupt_test() -> void {
|
||||||
if(r.ime) {
|
if(!r.ime) return;
|
||||||
if(status.interrupt_request_vblank && status.interrupt_enable_vblank) {
|
|
||||||
status.interrupt_request_vblank = 0;
|
|
||||||
return interrupt_exec(0x0040);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(status.interrupt_request_stat && status.interrupt_enable_stat) {
|
if(status.interrupt_request_vblank && status.interrupt_enable_vblank) {
|
||||||
status.interrupt_request_stat = 0;
|
status.interrupt_request_vblank = 0;
|
||||||
return interrupt_exec(0x0048);
|
return interrupt(0x0040);
|
||||||
}
|
|
||||||
|
|
||||||
if(status.interrupt_request_timer && status.interrupt_enable_timer) {
|
|
||||||
status.interrupt_request_timer = 0;
|
|
||||||
return interrupt_exec(0x0050);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(status.interrupt_request_serial && status.interrupt_enable_serial) {
|
|
||||||
status.interrupt_request_serial = 0;
|
|
||||||
return interrupt_exec(0x0058);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(status.interrupt_request_joypad && status.interrupt_enable_joypad) {
|
|
||||||
status.interrupt_request_joypad = 0;
|
|
||||||
return interrupt_exec(0x0060);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::interrupt_exec(uint16 pc) -> void {
|
if(status.interrupt_request_stat && status.interrupt_enable_stat) {
|
||||||
op_io();
|
status.interrupt_request_stat = 0;
|
||||||
op_io();
|
return interrupt(0x0048);
|
||||||
op_io();
|
}
|
||||||
r.ime = 0;
|
|
||||||
op_write(--r[SP], r[PC] >> 8);
|
if(status.interrupt_request_timer && status.interrupt_enable_timer) {
|
||||||
op_write(--r[SP], r[PC] >> 0);
|
status.interrupt_request_timer = 0;
|
||||||
r[PC] = pc;
|
return interrupt(0x0050);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(status.interrupt_request_serial && status.interrupt_enable_serial) {
|
||||||
|
status.interrupt_request_serial = 0;
|
||||||
|
return interrupt(0x0058);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(status.interrupt_request_joypad && status.interrupt_enable_joypad) {
|
||||||
|
status.interrupt_request_joypad = 0;
|
||||||
|
return interrupt(0x0060);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::stop() -> bool {
|
auto CPU::stop() -> bool {
|
||||||
@@ -102,7 +85,7 @@ auto CPU::stop() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::power() -> void {
|
auto CPU::power() -> void {
|
||||||
create(Main, 4 * 1024 * 1024);
|
create(Enter, 4 * 1024 * 1024);
|
||||||
LR35902::power();
|
LR35902::power();
|
||||||
|
|
||||||
for(uint n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM
|
for(uint n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
struct CPU : Processor::LR35902, Thread, MMIO {
|
struct CPU : Processor::LR35902, Thread, MMIO {
|
||||||
enum class Interrupt : uint { Vblank, Stat, Timer, Serial, Joypad };
|
enum class Interrupt : uint { Vblank, Stat, Timer, Serial, Joypad };
|
||||||
|
|
||||||
static auto Main() -> void;
|
static auto Enter() -> void;
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
auto interrupt_raise(Interrupt id) -> void;
|
auto raise(Interrupt id) -> void;
|
||||||
auto interrupt_test() -> void;
|
auto interrupt_test() -> void;
|
||||||
auto interrupt_exec(uint16 pc) -> void;
|
|
||||||
auto stop() -> bool;
|
auto stop() -> bool;
|
||||||
auto power() -> void;
|
auto power() -> void;
|
||||||
|
|
||||||
@@ -18,9 +17,9 @@ struct CPU : Processor::LR35902, Thread, MMIO {
|
|||||||
auto mmio_write(uint16 addr, uint8 data) -> void;
|
auto mmio_write(uint16 addr, uint8 data) -> void;
|
||||||
|
|
||||||
//memory.cpp
|
//memory.cpp
|
||||||
auto op_io() -> void;
|
auto io() -> void override;
|
||||||
auto op_read(uint16 addr) -> uint8;
|
auto read(uint16 addr) -> uint8 override;
|
||||||
auto op_write(uint16 addr, uint8 data) -> void;
|
auto write(uint16 addr, uint8 data) -> void override;
|
||||||
auto cycle_edge() -> void;
|
auto cycle_edge() -> void;
|
||||||
auto dma_read(uint16 addr) -> uint8;
|
auto dma_read(uint16 addr) -> uint8;
|
||||||
auto dma_write(uint16 addr, uint8 data) -> void;
|
auto dma_write(uint16 addr, uint8 data) -> void;
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
auto CPU::op_io() -> void {
|
auto CPU::io() -> void {
|
||||||
cycle_edge();
|
cycle_edge();
|
||||||
add_clocks(4);
|
add_clocks(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::op_read(uint16 addr) -> uint8 {
|
auto CPU::read(uint16 addr) -> uint8 {
|
||||||
cycle_edge();
|
cycle_edge();
|
||||||
add_clocks(4);
|
add_clocks(4);
|
||||||
return bus.read(addr);
|
return bus.read(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::op_write(uint16 addr, uint8 data) -> void {
|
auto CPU::write(uint16 addr, uint8 data) -> void {
|
||||||
cycle_edge();
|
cycle_edge();
|
||||||
add_clocks(4);
|
add_clocks(4);
|
||||||
bus.write(addr, data);
|
bus.write(addr, data);
|
||||||
|
@@ -18,7 +18,7 @@ auto CPU::mmio_joyp_poll() -> void {
|
|||||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Left) << 1;
|
dpad |= interface->inputPoll(0, 0, (uint)Input::Left) << 1;
|
||||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Right) << 0;
|
dpad |= interface->inputPoll(0, 0, (uint)Input::Right) << 0;
|
||||||
|
|
||||||
if(system.revision != System::Revision::SuperGameBoy) {
|
if(system.revision() != System::Revision::SuperGameBoy) {
|
||||||
//D-pad pivot makes it impossible to press opposing directions at the same time
|
//D-pad pivot makes it impossible to press opposing directions at the same time
|
||||||
//however, Super Game Boy BIOS is able to set these bits together
|
//however, Super Game Boy BIOS is able to set these bits together
|
||||||
if(dpad & 4) dpad &= ~8; //disallow up+down
|
if(dpad & 4) dpad &= ~8; //disallow up+down
|
||||||
@@ -29,7 +29,7 @@ auto CPU::mmio_joyp_poll() -> void {
|
|||||||
if(status.p15 == 1 && status.p14 == 1) status.joyp -= status.mlt_req;
|
if(status.p15 == 1 && status.p14 == 1) status.joyp -= status.mlt_req;
|
||||||
if(status.p15 == 0) status.joyp &= button ^ 0x0f;
|
if(status.p15 == 0) status.joyp &= button ^ 0x0f;
|
||||||
if(status.p14 == 0) status.joyp &= dpad ^ 0x0f;
|
if(status.p14 == 0) status.joyp &= dpad ^ 0x0f;
|
||||||
if(status.joyp != 0x0f) interrupt_raise(Interrupt::Joypad);
|
if(status.joyp != 0x0f) raise(Interrupt::Joypad);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::mmio_read(uint16 addr) -> uint8 {
|
auto CPU::mmio_read(uint16 addr) -> uint8 {
|
||||||
@@ -38,17 +38,19 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
|||||||
|
|
||||||
if(addr == 0xff00) { //JOYP
|
if(addr == 0xff00) { //JOYP
|
||||||
mmio_joyp_poll();
|
mmio_joyp_poll();
|
||||||
return (status.p15 << 5)
|
return 0xc0
|
||||||
|
| (status.p15 << 5)
|
||||||
| (status.p14 << 4)
|
| (status.p14 << 4)
|
||||||
| (status.joyp << 0);
|
| (status.joyp << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff01) { //SB
|
if(addr == 0xff01) { //SB
|
||||||
return 0xff;
|
return 0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff02) { //SC
|
if(addr == 0xff02) { //SC
|
||||||
return (status.serial_transfer << 7)
|
return (status.serial_transfer << 7)
|
||||||
|
| 0x7e
|
||||||
| (status.serial_clock << 0);
|
| (status.serial_clock << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,12 +67,14 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff07) { //TAC
|
if(addr == 0xff07) { //TAC
|
||||||
return (status.timer_enable << 2)
|
return 0xf8
|
||||||
|
| (status.timer_enable << 2)
|
||||||
| (status.timer_clock << 0);
|
| (status.timer_clock << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff0f) { //IF
|
if(addr == 0xff0f) { //IF
|
||||||
return (status.interrupt_request_joypad << 4)
|
return 0xe0
|
||||||
|
| (status.interrupt_request_joypad << 4)
|
||||||
| (status.interrupt_request_serial << 3)
|
| (status.interrupt_request_serial << 3)
|
||||||
| (status.interrupt_request_timer << 2)
|
| (status.interrupt_request_timer << 2)
|
||||||
| (status.interrupt_request_stat << 1)
|
| (status.interrupt_request_stat << 1)
|
||||||
@@ -123,7 +127,8 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xffff) { //IE
|
if(addr == 0xffff) { //IE
|
||||||
return (status.interrupt_enable_joypad << 4)
|
return 0xe0
|
||||||
|
| (status.interrupt_enable_joypad << 4)
|
||||||
| (status.interrupt_enable_serial << 3)
|
| (status.interrupt_enable_serial << 3)
|
||||||
| (status.interrupt_enable_timer << 2)
|
| (status.interrupt_enable_timer << 2)
|
||||||
| (status.interrupt_enable_stat << 1)
|
| (status.interrupt_enable_stat << 1)
|
||||||
|
@@ -3,9 +3,7 @@
|
|||||||
// 154 scanlines/frame
|
// 154 scanlines/frame
|
||||||
|
|
||||||
auto CPU::add_clocks(uint clocks) -> void {
|
auto CPU::add_clocks(uint clocks) -> void {
|
||||||
if(system.sgb()) system.clocks_executed += clocks;
|
for(auto n : range(clocks)) {
|
||||||
|
|
||||||
while(clocks--) {
|
|
||||||
if(++status.clock == 0) {
|
if(++status.clock == 0) {
|
||||||
cartridge.mbc3.second();
|
cartridge.mbc3.second();
|
||||||
}
|
}
|
||||||
@@ -19,20 +17,23 @@ auto CPU::add_clocks(uint clocks) -> void {
|
|||||||
if((status.div & 1023) == 0) timer_4096hz();
|
if((status.div & 1023) == 0) timer_4096hz();
|
||||||
|
|
||||||
ppu.clock -= ppu.frequency;
|
ppu.clock -= ppu.frequency;
|
||||||
if(ppu.clock < 0) co_switch(scheduler.active_thread = ppu.thread);
|
if(ppu.clock < 0) co_switch(ppu.thread);
|
||||||
|
|
||||||
apu.clock -= apu.frequency;
|
apu.clock -= apu.frequency;
|
||||||
if(apu.clock < 0) co_switch(scheduler.active_thread = apu.thread);
|
if(apu.clock < 0) co_switch(apu.thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(system.sgb()) scheduler.exit(Scheduler::ExitReason::StepEvent);
|
if(system.sgb()) {
|
||||||
|
system._clocksExecuted += clocks;
|
||||||
|
scheduler.exit(Scheduler::Event::Step);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::timer_262144hz() -> void {
|
auto CPU::timer_262144hz() -> void {
|
||||||
if(status.timer_enable && status.timer_clock == 1) {
|
if(status.timer_enable && status.timer_clock == 1) {
|
||||||
if(++status.tima == 0) {
|
if(++status.tima == 0) {
|
||||||
status.tima = status.tma;
|
status.tima = status.tma;
|
||||||
interrupt_raise(Interrupt::Timer);
|
raise(Interrupt::Timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,7 @@ auto CPU::timer_65536hz() -> void {
|
|||||||
if(status.timer_enable && status.timer_clock == 2) {
|
if(status.timer_enable && status.timer_clock == 2) {
|
||||||
if(++status.tima == 0) {
|
if(++status.tima == 0) {
|
||||||
status.tima = status.tma;
|
status.tima = status.tma;
|
||||||
interrupt_raise(Interrupt::Timer);
|
raise(Interrupt::Timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ auto CPU::timer_16384hz() -> void {
|
|||||||
if(status.timer_enable && status.timer_clock == 3) {
|
if(status.timer_enable && status.timer_clock == 3) {
|
||||||
if(++status.tima == 0) {
|
if(++status.tima == 0) {
|
||||||
status.tima = status.tma;
|
status.tima = status.tma;
|
||||||
interrupt_raise(Interrupt::Timer);
|
raise(Interrupt::Timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +60,7 @@ auto CPU::timer_8192hz() -> void {
|
|||||||
if(status.serial_transfer && status.serial_clock) {
|
if(status.serial_transfer && status.serial_clock) {
|
||||||
if(--status.serial_bits == 0) {
|
if(--status.serial_bits == 0) {
|
||||||
status.serial_transfer = 0;
|
status.serial_transfer = 0;
|
||||||
interrupt_raise(Interrupt::Serial);
|
raise(Interrupt::Serial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +69,7 @@ auto CPU::timer_4096hz() -> void {
|
|||||||
if(status.timer_enable && status.timer_clock == 0) {
|
if(status.timer_enable && status.timer_clock == 0) {
|
||||||
if(++status.tima == 0) {
|
if(++status.tima == 0) {
|
||||||
status.tima = status.tma;
|
status.tima = status.tma;
|
||||||
interrupt_raise(Interrupt::Timer);
|
raise(Interrupt::Timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
//license: GPLv3
|
||||||
|
//started: 2010-12-27
|
||||||
|
|
||||||
#include <emulator/emulator.hpp>
|
#include <emulator/emulator.hpp>
|
||||||
#include <processor/lr35902/lr35902.hpp>
|
#include <processor/lr35902/lr35902.hpp>
|
||||||
|
|
||||||
namespace GameBoy {
|
namespace GameBoy {
|
||||||
namespace Info {
|
namespace Info {
|
||||||
static const string Name = "bgb";
|
static const uint SerializerVersion = 5;
|
||||||
static const uint SerializerVersion = 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
bgb - Game Boy, Super Game Boy, and Game Boy Color emulator
|
|
||||||
author: byuu
|
|
||||||
license: GPLv3
|
|
||||||
project started: 2010-12-27
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <libco/libco.h>
|
#include <libco/libco.h>
|
||||||
|
|
||||||
namespace GameBoy {
|
namespace GameBoy {
|
||||||
@@ -27,7 +22,7 @@ namespace GameBoy {
|
|||||||
|
|
||||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||||
if(thread) co_delete(thread);
|
if(thread) co_delete(thread);
|
||||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
thread = co_create(65'536 * sizeof(void*), entrypoint);
|
||||||
this->frequency = frequency;
|
this->frequency = frequency;
|
||||||
clock = 0;
|
clock = 0;
|
||||||
}
|
}
|
||||||
@@ -50,7 +45,6 @@ namespace GameBoy {
|
|||||||
#include <gb/ppu/ppu.hpp>
|
#include <gb/ppu/ppu.hpp>
|
||||||
#include <gb/apu/apu.hpp>
|
#include <gb/apu/apu.hpp>
|
||||||
#include <gb/cheat/cheat.hpp>
|
#include <gb/cheat/cheat.hpp>
|
||||||
#include <gb/video/video.hpp>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <gb/interface/interface.hpp>
|
#include <gb/interface/interface.hpp>
|
||||||
|
@@ -9,12 +9,14 @@ Interface::Interface() {
|
|||||||
interface = this;
|
interface = this;
|
||||||
hook = nullptr;
|
hook = nullptr;
|
||||||
|
|
||||||
information.name = "Game Boy";
|
information.manufacturer = "Nintendo";
|
||||||
information.width = 160;
|
information.name = "Game Boy";
|
||||||
information.height = 144;
|
information.width = 160;
|
||||||
information.overscan = false;
|
information.height = 144;
|
||||||
information.aspectRatio = 1.0;
|
information.overscan = false;
|
||||||
information.resettable = false;
|
information.aspectRatio = 1.0;
|
||||||
|
information.resettable = false;
|
||||||
|
|
||||||
information.capability.states = true;
|
information.capability.states = true;
|
||||||
information.capability.cheats = true;
|
information.capability.cheats = true;
|
||||||
|
|
||||||
@@ -23,19 +25,18 @@ Interface::Interface() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
Device device{0, ID::Device, "Controller"};
|
Device device{0, ID::Device, "Controller"};
|
||||||
device.input.append({0, 0, "Up" });
|
device.inputs.append({0, 0, "Up" });
|
||||||
device.input.append({1, 0, "Down" });
|
device.inputs.append({1, 0, "Down" });
|
||||||
device.input.append({2, 0, "Left" });
|
device.inputs.append({2, 0, "Left" });
|
||||||
device.input.append({3, 0, "Right" });
|
device.inputs.append({3, 0, "Right" });
|
||||||
device.input.append({4, 0, "B" });
|
device.inputs.append({4, 0, "B" });
|
||||||
device.input.append({5, 0, "A" });
|
device.inputs.append({5, 0, "A" });
|
||||||
device.input.append({6, 0, "Select"});
|
device.inputs.append({6, 0, "Select"});
|
||||||
device.input.append({7, 0, "Start" });
|
device.inputs.append({7, 0, "Start" });
|
||||||
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
|
devices.append(device);
|
||||||
this->device.append(device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
port.append({0, "Device", {device[0]}});
|
ports.append({0, "Device", {devices[0]}});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::manifest() -> string {
|
auto Interface::manifest() -> string {
|
||||||
@@ -50,12 +51,73 @@ auto Interface::videoFrequency() -> double {
|
|||||||
return 4194304.0 / (154.0 * 456.0);
|
return 4194304.0 / (154.0 * 456.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Interface::videoColors() -> uint32 {
|
||||||
|
return !system.cgb() ? 1 << 2 : 1 << 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Interface::videoColor(uint32 color) -> uint64 {
|
||||||
|
if(!system.cgb()) {
|
||||||
|
if(!settings.colorEmulation) {
|
||||||
|
uint64 L = image::normalize(3 - color, 2, 16);
|
||||||
|
return L << 32 | L << 16 | L << 0;
|
||||||
|
} else {
|
||||||
|
#define DMG_PALETTE_GREEN
|
||||||
|
//#define DMG_PALETTE_YELLOW
|
||||||
|
//#define DMG_PALETTE_WHITE
|
||||||
|
|
||||||
|
const uint16 monochrome[4][3] = {
|
||||||
|
#if defined(DMG_PALETTE_GREEN)
|
||||||
|
{0xaeae, 0xd9d9, 0x2727},
|
||||||
|
{0x5858, 0xa0a0, 0x2828},
|
||||||
|
{0x2020, 0x6262, 0x2929},
|
||||||
|
{0x1a1a, 0x4545, 0x2a2a},
|
||||||
|
#elif defined(DMG_PALETTE_YELLOW)
|
||||||
|
{0xffff, 0xf7f7, 0x7b7b},
|
||||||
|
{0xb5b5, 0xaeae, 0x4a4a},
|
||||||
|
{0x6b6b, 0x6969, 0x3131},
|
||||||
|
{0x2121, 0x2020, 0x1010},
|
||||||
|
#elif defined(DMG_PALETTE_WHITE)
|
||||||
|
{0xffff, 0xffff, 0xffff},
|
||||||
|
{0xaaaa, 0xaaaa, 0xaaaa},
|
||||||
|
{0x5555, 0x5555, 0x5555},
|
||||||
|
{0x0000, 0x0000, 0x0000},
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64 R = monochrome[color][0];
|
||||||
|
uint64 G = monochrome[color][1];
|
||||||
|
uint64 B = monochrome[color][2];
|
||||||
|
|
||||||
|
return R << 32 | G << 16 | B << 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uint r = color.bits( 0, 4);
|
||||||
|
uint g = color.bits( 5, 9);
|
||||||
|
uint b = color.bits(10,14);
|
||||||
|
|
||||||
|
uint64_t R = image::normalize(r, 5, 16);
|
||||||
|
uint64_t G = image::normalize(g, 5, 16);
|
||||||
|
uint64_t B = image::normalize(b, 5, 16);
|
||||||
|
|
||||||
|
if(settings.colorEmulation) {
|
||||||
|
R = (r * 26 + g * 4 + b * 2);
|
||||||
|
G = ( g * 24 + b * 8);
|
||||||
|
B = (r * 6 + g * 4 + b * 22);
|
||||||
|
R = image::normalize(min(960, R), 10, 16);
|
||||||
|
G = image::normalize(min(960, G), 10, 16);
|
||||||
|
B = image::normalize(min(960, B), 10, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return R << 32 | G << 16 | B << 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto Interface::audioFrequency() -> double {
|
auto Interface::audioFrequency() -> double {
|
||||||
return 4194304.0 / 2.0;
|
return 4194304.0 / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::loaded() -> bool {
|
auto Interface::loaded() -> bool {
|
||||||
return cartridge.loaded();
|
return system.loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::sha256() -> string {
|
auto Interface::sha256() -> string {
|
||||||
@@ -72,7 +134,7 @@ auto Interface::group(uint id) -> uint {
|
|||||||
case ID::Manifest:
|
case ID::Manifest:
|
||||||
case ID::ROM:
|
case ID::ROM:
|
||||||
case ID::RAM:
|
case ID::RAM:
|
||||||
switch(system.revision) {
|
switch(system.revision()) {
|
||||||
case System::Revision::GameBoy: return ID::GameBoy;
|
case System::Revision::GameBoy: return ID::GameBoy;
|
||||||
case System::Revision::SuperGameBoy: return ID::SuperGameBoy;
|
case System::Revision::SuperGameBoy: return ID::SuperGameBoy;
|
||||||
case System::Revision::GameBoyColor: return ID::GameBoyColor;
|
case System::Revision::GameBoyColor: return ID::GameBoyColor;
|
||||||
@@ -83,9 +145,9 @@ auto Interface::group(uint id) -> uint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::load(uint id) -> void {
|
auto Interface::load(uint id) -> void {
|
||||||
if(id == ID::GameBoy) cartridge.load(System::Revision::GameBoy);
|
if(id == ID::GameBoy) system.load(System::Revision::GameBoy);
|
||||||
if(id == ID::SuperGameBoy) cartridge.load(System::Revision::SuperGameBoy);
|
if(id == ID::SuperGameBoy) system.load(System::Revision::SuperGameBoy);
|
||||||
if(id == ID::GameBoyColor) cartridge.load(System::Revision::GameBoyColor);
|
if(id == ID::GameBoyColor) system.load(System::Revision::GameBoyColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::save() -> void {
|
auto Interface::save() -> void {
|
||||||
@@ -100,15 +162,15 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::GameBoyBootROM) {
|
if(id == ID::GameBoyBootROM) {
|
||||||
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
|
stream.read((uint8_t*)system.bootROM.dmg, min( 256u, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::SuperGameBoyBootROM) {
|
if(id == ID::SuperGameBoyBootROM) {
|
||||||
stream.read(system.bootROM.sgb, min( 256u, stream.size()));
|
stream.read((uint8_t*)system.bootROM.sgb, min( 256u, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::GameBoyColorBootROM) {
|
if(id == ID::GameBoyColorBootROM) {
|
||||||
stream.read(system.bootROM.cgb, min(2048u, stream.size()));
|
stream.read((uint8_t*)system.bootROM.cgb, min(2048u, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::Manifest) {
|
if(id == ID::Manifest) {
|
||||||
@@ -116,23 +178,23 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::ROM) {
|
if(id == ID::ROM) {
|
||||||
stream.read(cartridge.romdata, min(cartridge.romsize, stream.size()));
|
stream.read((uint8_t*)cartridge.romdata, min(cartridge.romsize, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::RAM) {
|
if(id == ID::RAM) {
|
||||||
stream.read(cartridge.ramdata, min(stream.size(), cartridge.ramsize));
|
stream.read((uint8_t*)cartridge.ramdata, min(stream.size(), cartridge.ramsize));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::save(uint id, const stream& stream) -> void {
|
auto Interface::save(uint id, const stream& stream) -> void {
|
||||||
if(id == ID::RAM) {
|
if(id == ID::RAM) {
|
||||||
stream.write(cartridge.ramdata, cartridge.ramsize);
|
stream.write((uint8_t*)cartridge.ramdata, cartridge.ramsize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::unload() -> void {
|
auto Interface::unload() -> void {
|
||||||
save();
|
save();
|
||||||
cartridge.unload();
|
system.unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::power() -> void {
|
auto Interface::power() -> void {
|
||||||
@@ -148,7 +210,7 @@ auto Interface::run() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::serialize() -> serializer {
|
auto Interface::serialize() -> serializer {
|
||||||
system.runtosave();
|
system.runToSave();
|
||||||
return system.serialize();
|
return system.serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +255,18 @@ auto Interface::get(const string& name) -> any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::set(const string& name, const any& value) -> bool {
|
auto Interface::set(const string& name, const any& value) -> bool {
|
||||||
if(name == "Blur Emulation" && value.is<bool>()) return settings.blurEmulation = value.get<bool>(), true;
|
if(name == "Blur Emulation" && value.is<bool>()) {
|
||||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
settings.blurEmulation = value.get<bool>();
|
||||||
|
system.configureVideoEffects();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(name == "Color Emulation" && value.is<bool>()) {
|
||||||
|
settings.colorEmulation = value.get<bool>();
|
||||||
|
system.configureVideoPalette();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,6 +30,8 @@ struct Interface : Emulator::Interface {
|
|||||||
auto manifest() -> string;
|
auto manifest() -> string;
|
||||||
auto title() -> string;
|
auto title() -> string;
|
||||||
auto videoFrequency() -> double;
|
auto videoFrequency() -> double;
|
||||||
|
auto videoColors() -> uint32;
|
||||||
|
auto videoColor(uint32 color) -> uint64;
|
||||||
auto audioFrequency() -> double;
|
auto audioFrequency() -> double;
|
||||||
|
|
||||||
auto loaded() -> bool;
|
auto loaded() -> bool;
|
||||||
@@ -67,7 +69,7 @@ struct Interface : Emulator::Interface {
|
|||||||
auto joypWrite(bool p15, bool p14) -> void;
|
auto joypWrite(bool p15, bool p14) -> void;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
vector<Device> device;
|
vector<Device> devices;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
|
@@ -16,13 +16,13 @@ auto Memory::operator[](uint addr) -> uint8& {
|
|||||||
auto Memory::allocate(uint size_) -> void {
|
auto Memory::allocate(uint size_) -> void {
|
||||||
free();
|
free();
|
||||||
size = size_;
|
size = size_;
|
||||||
data = new uint8_t[size]();
|
data = new uint8[size];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Memory::copy(const uint8_t* data_, unsigned size_) -> void {
|
auto Memory::copy(const uint8* data_, unsigned size_) -> void {
|
||||||
free();
|
free();
|
||||||
size = size_;
|
size = size_;
|
||||||
data = new uint8_t[size];
|
data = new uint8[size];
|
||||||
memcpy(data, data_, size);
|
memcpy(data, data_, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,6 +37,7 @@ auto PPU::cgb_read_tile(bool select, uint x, uint y, uint& attr, uint& data) ->
|
|||||||
|
|
||||||
auto PPU::cgb_scanline() -> void {
|
auto PPU::cgb_scanline() -> void {
|
||||||
px = 0;
|
px = 0;
|
||||||
|
if(!enabled()) return;
|
||||||
|
|
||||||
const uint Height = (status.ob_size == 0 ? 8 : 16);
|
const uint Height = (status.ob_size == 0 ? 8 : 16);
|
||||||
sprites = 0;
|
sprites = 0;
|
||||||
@@ -68,7 +69,7 @@ auto PPU::cgb_run() -> void {
|
|||||||
ob.priority = 0;
|
ob.priority = 0;
|
||||||
|
|
||||||
uint color = 0x7fff;
|
uint color = 0x7fff;
|
||||||
if(status.display_enable) {
|
if(enabled()) {
|
||||||
cgb_run_bg();
|
cgb_run_bg();
|
||||||
if(status.window_display_enable) cgb_run_window();
|
if(status.window_display_enable) cgb_run_window();
|
||||||
if(status.ob_enable) cgb_run_ob();
|
if(status.ob_enable) cgb_run_ob();
|
||||||
|
@@ -19,6 +19,7 @@ auto PPU::dmg_read_tile(bool select, uint x, uint y, uint& data) -> void {
|
|||||||
|
|
||||||
auto PPU::dmg_scanline() -> void {
|
auto PPU::dmg_scanline() -> void {
|
||||||
px = 0;
|
px = 0;
|
||||||
|
if(!enabled()) return;
|
||||||
|
|
||||||
const uint Height = (status.ob_size == 0 ? 8 : 16);
|
const uint Height = (status.ob_size == 0 ? 8 : 16);
|
||||||
sprites = 0;
|
sprites = 0;
|
||||||
@@ -59,7 +60,7 @@ auto PPU::dmg_run() -> void {
|
|||||||
ob.palette = 0;
|
ob.palette = 0;
|
||||||
|
|
||||||
uint color = 0;
|
uint color = 0;
|
||||||
if(status.display_enable) {
|
if(enabled()) {
|
||||||
if(status.bg_enable) dmg_run_bg();
|
if(status.bg_enable) dmg_run_bg();
|
||||||
if(status.window_display_enable) dmg_run_window();
|
if(status.window_display_enable) dmg_run_window();
|
||||||
if(status.ob_enable) dmg_run_ob();
|
if(status.ob_enable) dmg_run_ob();
|
||||||
|
@@ -24,18 +24,12 @@ auto PPU::mmio_read(uint16 addr) -> uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff41) { //STAT
|
if(addr == 0xff41) { //STAT
|
||||||
uint mode;
|
|
||||||
if(status.ly >= 144) mode = 1; //Vblank
|
|
||||||
else if(status.lx < 80) mode = 2; //OAM
|
|
||||||
else if(status.lx < 252) mode = 3; //LCD
|
|
||||||
else mode = 0; //Hblank
|
|
||||||
|
|
||||||
return (status.interrupt_lyc << 6)
|
return (status.interrupt_lyc << 6)
|
||||||
| (status.interrupt_oam << 5)
|
| (status.interrupt_oam << 5)
|
||||||
| (status.interrupt_vblank << 4)
|
| (status.interrupt_vblank << 4)
|
||||||
| (status.interrupt_hblank << 3)
|
| (status.interrupt_hblank << 3)
|
||||||
| ((status.ly == status.lyc) << 2)
|
| ((status.ly == status.lyc) << 2)
|
||||||
| (mode << 0);
|
| (status.mode << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff42) { //SCY
|
if(addr == 0xff42) { //SCY
|
||||||
@@ -125,7 +119,7 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
|
|||||||
|
|
||||||
//restart cothread to begin new frame
|
//restart cothread to begin new frame
|
||||||
auto clock = this->clock;
|
auto clock = this->clock;
|
||||||
create(Main, 4 * 1024 * 1024);
|
create(Enter, 4 * 1024 * 1024);
|
||||||
this->clock = clock;
|
this->clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +139,13 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
|
|||||||
status.interrupt_oam = data & 0x20;
|
status.interrupt_oam = data & 0x20;
|
||||||
status.interrupt_vblank = data & 0x10;
|
status.interrupt_vblank = data & 0x10;
|
||||||
status.interrupt_hblank = data & 0x08;
|
status.interrupt_hblank = data & 0x08;
|
||||||
|
|
||||||
|
//hardware bug: writes to STAT on DMG,SGB during vblank triggers STAT IRQ
|
||||||
|
//note: this behavior isn't entirely correct; more research is needed ...
|
||||||
|
if(!system.cgb() && status.mode == 1) {
|
||||||
|
cpu.raise(CPU::Interrupt::Stat);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,77 +1,82 @@
|
|||||||
#include <gb/gb.hpp>
|
#include <gb/gb.hpp>
|
||||||
|
|
||||||
//LY = 0-153
|
|
||||||
//Raster = 0-143
|
|
||||||
//Vblank = 144-153
|
|
||||||
|
|
||||||
//LX = 0-455
|
|
||||||
|
|
||||||
namespace GameBoy {
|
namespace GameBoy {
|
||||||
|
|
||||||
|
PPU ppu;
|
||||||
#include "mmio.cpp"
|
#include "mmio.cpp"
|
||||||
#include "dmg.cpp"
|
#include "dmg.cpp"
|
||||||
#include "cgb.cpp"
|
#include "cgb.cpp"
|
||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
PPU ppu;
|
|
||||||
|
|
||||||
auto PPU::Main() -> void {
|
auto PPU::enabled() const -> bool { return status.display_enable; }
|
||||||
ppu.main();
|
|
||||||
|
auto PPU::Enter() -> void {
|
||||||
|
while(true) scheduler.synchronize(), ppu.main();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::main() -> void {
|
auto PPU::main() -> void {
|
||||||
while(true) {
|
status.lx = 0;
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
interface->lcdScanline(); //Super Game Boy notification
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
|
if(status.ly <= 143) {
|
||||||
|
mode(2);
|
||||||
|
scanline();
|
||||||
|
wait(92);
|
||||||
|
|
||||||
|
mode(3);
|
||||||
|
for(auto n : range(160)) {
|
||||||
|
run();
|
||||||
|
wait(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
status.lx = 0;
|
mode(0);
|
||||||
interface->lcdScanline(); //Super Game Boy notification
|
if(enabled()) cpu.hblank();
|
||||||
|
wait(204);
|
||||||
|
} else {
|
||||||
|
mode(1);
|
||||||
|
wait(456);
|
||||||
|
}
|
||||||
|
|
||||||
if(status.display_enable) {
|
status.ly++;
|
||||||
//LYC of zero triggers on LY==153
|
|
||||||
if((status.lyc && status.ly == status.lyc) || (!status.lyc && status.ly == 153)) {
|
|
||||||
if(status.interrupt_lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(status.ly <= 143) {
|
if(status.ly == 144) {
|
||||||
scanline();
|
if(enabled()) cpu.raise(CPU::Interrupt::Vblank);
|
||||||
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
scheduler.exit(Scheduler::Event::Frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(status.ly == 144) {
|
if(status.ly == 154) {
|
||||||
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
status.ly = 0;
|
||||||
else if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat); //hardware quirk
|
|
||||||
cpu.interrupt_raise(CPU::Interrupt::Vblank);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add_clocks(92);
|
|
||||||
|
|
||||||
if(status.ly <= 143) {
|
|
||||||
for(auto n : range(160)) {
|
|
||||||
if(status.display_enable) run();
|
|
||||||
add_clocks(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(status.display_enable) {
|
|
||||||
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
|
||||||
cpu.hblank();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
add_clocks(160);
|
|
||||||
}
|
|
||||||
|
|
||||||
add_clocks(204);
|
|
||||||
|
|
||||||
if(++status.ly == 154) {
|
|
||||||
status.ly = 0;
|
|
||||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::add_clocks(uint clocks) -> void {
|
auto PPU::mode(uint mode) -> void {
|
||||||
|
status.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PPU::stat() -> void {
|
||||||
|
bool irq = status.irq;
|
||||||
|
|
||||||
|
status.irq = status.interrupt_hblank && status.mode == 0;
|
||||||
|
status.irq |= status.interrupt_vblank && status.mode == 1;
|
||||||
|
status.irq |= status.interrupt_oam && status.mode == 2;
|
||||||
|
status.irq |= status.interrupt_lyc && coincidence();
|
||||||
|
|
||||||
|
if(!irq && status.irq) cpu.raise(CPU::Interrupt::Stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PPU::coincidence() -> bool {
|
||||||
|
uint ly = status.ly;
|
||||||
|
if(ly == 153 && status.lx >= 92) ly = 0; //LYC=0 triggers early during LY=153
|
||||||
|
return ly == status.lyc;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PPU::refresh() -> void {
|
||||||
|
if(!system.sgb()) Emulator::video.refresh(screen, 160 * sizeof(uint32), 160, 144);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PPU::wait(uint clocks) -> void {
|
||||||
while(clocks--) {
|
while(clocks--) {
|
||||||
|
stat();
|
||||||
if(status.dma_active) {
|
if(status.dma_active) {
|
||||||
uint hi = status.dma_clock++;
|
uint hi = status.dma_clock++;
|
||||||
uint lo = hi & (cpu.status.speed_double ? 1 : 3);
|
uint lo = hi & (cpu.status.speed_double ? 1 : 3);
|
||||||
@@ -90,21 +95,19 @@ auto PPU::add_clocks(uint clocks) -> void {
|
|||||||
|
|
||||||
status.lx++;
|
status.lx++;
|
||||||
clock += cpu.frequency;
|
clock += cpu.frequency;
|
||||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||||
co_switch(scheduler.active_thread = cpu.thread);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::hflip(uint data) const -> uint {
|
auto PPU::hflip(uint data) const -> uint {
|
||||||
return ((data & 0x8080) >> 7) | ((data & 0x4040) >> 5)
|
return (data & 0x8080) >> 7 | (data & 0x4040) >> 5
|
||||||
| ((data & 0x2020) >> 3) | ((data & 0x1010) >> 1)
|
| (data & 0x2020) >> 3 | (data & 0x1010) >> 1
|
||||||
| ((data & 0x0808) << 1) | ((data & 0x0404) << 3)
|
| (data & 0x0808) << 1 | (data & 0x0404) << 3
|
||||||
| ((data & 0x0202) << 5) | ((data & 0x0101) << 7);
|
| (data & 0x0202) << 5 | (data & 0x0101) << 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PPU::power() -> void {
|
auto PPU::power() -> void {
|
||||||
create(Main, 4 * 1024 * 1024);
|
create(Enter, 4 * 1024 * 1024);
|
||||||
|
|
||||||
if(system.cgb()) {
|
if(system.cgb()) {
|
||||||
scanline = {&PPU::cgb_scanline, this};
|
scanline = {&PPU::cgb_scanline, this};
|
||||||
@@ -141,11 +144,12 @@ auto PPU::power() -> void {
|
|||||||
for(auto& n : vram) n = 0x00;
|
for(auto& n : vram) n = 0x00;
|
||||||
for(auto& n : oam) n = 0x00;
|
for(auto& n : oam) n = 0x00;
|
||||||
for(auto& n : bgp) n = 0x00;
|
for(auto& n : bgp) n = 0x00;
|
||||||
for(auto& n : obp[0]) n = 0x00;
|
for(auto& n : obp[0]) n = 3;
|
||||||
for(auto& n : obp[1]) n = 0x00;
|
for(auto& n : obp[1]) n = 3;
|
||||||
for(auto& n : bgpd) n = 0x0000;
|
for(auto& n : bgpd) n = 0x0000;
|
||||||
for(auto& n : obpd) n = 0x0000;
|
for(auto& n : obpd) n = 0x0000;
|
||||||
|
|
||||||
|
status.irq = false;
|
||||||
status.lx = 0;
|
status.lx = 0;
|
||||||
|
|
||||||
status.display_enable = 0;
|
status.display_enable = 0;
|
||||||
@@ -161,6 +165,7 @@ auto PPU::power() -> void {
|
|||||||
status.interrupt_oam = 0;
|
status.interrupt_oam = 0;
|
||||||
status.interrupt_vblank = 0;
|
status.interrupt_vblank = 0;
|
||||||
status.interrupt_hblank = 0;
|
status.interrupt_hblank = 0;
|
||||||
|
status.mode = 0;
|
||||||
|
|
||||||
status.scy = 0;
|
status.scy = 0;
|
||||||
status.scx = 0;
|
status.scx = 0;
|
||||||
|
@@ -1,7 +1,13 @@
|
|||||||
struct PPU : Thread, MMIO {
|
struct PPU : Thread, MMIO {
|
||||||
static auto Main() -> void;
|
auto enabled() const -> bool;
|
||||||
|
|
||||||
|
static auto Enter() -> void;
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
auto add_clocks(uint clocks) -> void;
|
auto mode(uint) -> void;
|
||||||
|
auto stat() -> void;
|
||||||
|
auto coincidence() -> bool;
|
||||||
|
auto refresh() -> void;
|
||||||
|
auto wait(uint clocks) -> void;
|
||||||
|
|
||||||
auto hflip(uint data) const -> uint;
|
auto hflip(uint data) const -> uint;
|
||||||
|
|
||||||
@@ -41,6 +47,7 @@ struct PPU : Thread, MMIO {
|
|||||||
function<auto () -> void> run;
|
function<auto () -> void> run;
|
||||||
|
|
||||||
struct Status {
|
struct Status {
|
||||||
|
bool irq; //STAT IRQ line
|
||||||
uint lx;
|
uint lx;
|
||||||
|
|
||||||
//$ff40 LCDC
|
//$ff40 LCDC
|
||||||
@@ -58,6 +65,7 @@ struct PPU : Thread, MMIO {
|
|||||||
bool interrupt_oam;
|
bool interrupt_oam;
|
||||||
bool interrupt_vblank;
|
bool interrupt_vblank;
|
||||||
bool interrupt_hblank;
|
bool interrupt_hblank;
|
||||||
|
uint2 mode;
|
||||||
|
|
||||||
//$ff42 SCY
|
//$ff42 SCY
|
||||||
uint8 scy;
|
uint8 scy;
|
||||||
|
@@ -9,6 +9,7 @@ auto PPU::serialize(serializer& s) -> void {
|
|||||||
s.array(bgpd);
|
s.array(bgpd);
|
||||||
s.array(obpd);
|
s.array(obpd);
|
||||||
|
|
||||||
|
s.integer(status.irq);
|
||||||
s.integer(status.lx);
|
s.integer(status.lx);
|
||||||
|
|
||||||
s.integer(status.display_enable);
|
s.integer(status.display_enable);
|
||||||
@@ -24,6 +25,7 @@ auto PPU::serialize(serializer& s) -> void {
|
|||||||
s.integer(status.interrupt_oam);
|
s.integer(status.interrupt_oam);
|
||||||
s.integer(status.interrupt_vblank);
|
s.integer(status.interrupt_vblank);
|
||||||
s.integer(status.interrupt_hblank);
|
s.integer(status.interrupt_hblank);
|
||||||
|
s.integer(status.mode);
|
||||||
|
|
||||||
s.integer(status.scy);
|
s.integer(status.scy);
|
||||||
s.integer(status.scx);
|
s.integer(status.scx);
|
||||||
|
@@ -4,20 +4,41 @@ namespace GameBoy {
|
|||||||
|
|
||||||
Scheduler scheduler;
|
Scheduler scheduler;
|
||||||
|
|
||||||
auto Scheduler::init() -> void {
|
auto Scheduler::power() -> void {
|
||||||
host_thread = co_active();
|
host = co_active();
|
||||||
active_thread = cpu.thread;
|
resume = cpu.thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Scheduler::enter() -> void {
|
auto Scheduler::enter(Mode mode_) -> Event {
|
||||||
host_thread = co_active();
|
mode = mode_;
|
||||||
co_switch(active_thread);
|
host = co_active();
|
||||||
|
co_switch(resume);
|
||||||
|
if(event == Event::Frame) ppu.refresh();
|
||||||
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Scheduler::exit(ExitReason reason) -> void {
|
auto Scheduler::exit(Event event_) -> void {
|
||||||
exit_reason = reason;
|
event = event_;
|
||||||
active_thread = co_active();
|
resume = co_active();
|
||||||
co_switch(host_thread);
|
co_switch(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Scheduler::synchronize(cothread_t thread) -> void {
|
||||||
|
if(thread == cpu.thread) {
|
||||||
|
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
|
||||||
|
} else {
|
||||||
|
resume = thread;
|
||||||
|
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Scheduler::synchronize() -> void {
|
||||||
|
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
|
||||||
|
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Scheduler::synchronizing() const -> bool {
|
||||||
|
return mode == Mode::SynchronizeAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,29 @@
|
|||||||
struct Scheduler {
|
struct Scheduler {
|
||||||
enum class SynchronizeMode : uint { None, CPU, All } sync;
|
enum class Mode : uint {
|
||||||
enum class ExitReason : uint { UnknownEvent, StepEvent, FrameEvent, SynchronizeEvent };
|
Run,
|
||||||
|
SynchronizeCPU,
|
||||||
|
SynchronizeAll,
|
||||||
|
};
|
||||||
|
|
||||||
auto init() -> void;
|
enum class Event : uint {
|
||||||
auto enter() -> void;
|
Unknown,
|
||||||
auto exit(ExitReason) -> void;
|
Step,
|
||||||
|
Frame,
|
||||||
|
Synchronize,
|
||||||
|
};
|
||||||
|
|
||||||
cothread_t host_thread = nullptr;
|
auto power() -> void;
|
||||||
cothread_t active_thread = nullptr;
|
auto enter(Mode = Mode::Run) -> Event;
|
||||||
ExitReason exit_reason = ExitReason::UnknownEvent;
|
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;
|
extern Scheduler scheduler;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
auto System::serialize() -> serializer {
|
auto System::serialize() -> serializer {
|
||||||
serializer s(serialize_size);
|
serializer s(_serializeSize);
|
||||||
|
|
||||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||||
char hash[64], description[512];
|
char hash[64], description[512];
|
||||||
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
|
|||||||
s.array(hash);
|
s.array(hash);
|
||||||
s.array(description);
|
s.array(description);
|
||||||
|
|
||||||
serialize_all(s);
|
serializeAll(s);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,15 +28,15 @@ auto System::unserialize(serializer& s) -> bool {
|
|||||||
if(version != Info::SerializerVersion) return false;
|
if(version != Info::SerializerVersion) return false;
|
||||||
|
|
||||||
power();
|
power();
|
||||||
serialize_all(s);
|
serializeAll(s);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::serialize(serializer& s) -> void {
|
auto System::serialize(serializer& s) -> void {
|
||||||
s.integer(clocks_executed);
|
s.integer(_clocksExecuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::serialize_all(serializer& s) -> void {
|
auto System::serializeAll(serializer& s) -> void {
|
||||||
cartridge.serialize(s);
|
cartridge.serialize(s);
|
||||||
system.serialize(s);
|
system.serialize(s);
|
||||||
cpu.serialize(s);
|
cpu.serialize(s);
|
||||||
@@ -44,10 +44,10 @@ auto System::serialize_all(serializer& s) -> void {
|
|||||||
apu.serialize(s);
|
apu.serialize(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::serialize_init() -> void {
|
auto System::serializeInit() -> void {
|
||||||
serializer s;
|
serializer s;
|
||||||
|
|
||||||
uint signature = 0, version = 0, crc32 = 0;
|
uint signature = 0, version = 0;
|
||||||
char hash[64], description[512];
|
char hash[64], description[512];
|
||||||
|
|
||||||
s.integer(signature);
|
s.integer(signature);
|
||||||
@@ -55,6 +55,6 @@ auto System::serialize_init() -> void {
|
|||||||
s.array(hash);
|
s.array(hash);
|
||||||
s.array(description);
|
s.array(description);
|
||||||
|
|
||||||
serialize_all(s);
|
serializeAll(s);
|
||||||
serialize_size = s.size();
|
_serializeSize = s.size();
|
||||||
}
|
}
|
||||||
|
@@ -2,9 +2,14 @@
|
|||||||
|
|
||||||
namespace GameBoy {
|
namespace GameBoy {
|
||||||
|
|
||||||
|
#include "video.cpp"
|
||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
System system;
|
System system;
|
||||||
|
|
||||||
|
auto System::loaded() const -> bool { return _loaded; }
|
||||||
|
auto System::revision() const -> Revision { return _revision; }
|
||||||
|
auto System::clocksExecuted() const -> uint { return _clocksExecuted; }
|
||||||
|
|
||||||
System::System() {
|
System::System() {
|
||||||
for(auto& byte : bootROM.dmg) byte = 0;
|
for(auto& byte : bootROM.dmg) byte = 0;
|
||||||
for(auto& byte : bootROM.sgb) byte = 0;
|
for(auto& byte : bootROM.sgb) byte = 0;
|
||||||
@@ -12,37 +17,13 @@ System::System() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto System::run() -> void {
|
auto System::run() -> void {
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
|
||||||
|
|
||||||
scheduler.enter();
|
scheduler.enter();
|
||||||
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
|
|
||||||
video.refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::runtosave() -> void {
|
auto System::runToSave() -> void {
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::CPU;
|
scheduler.synchronize(cpu.thread);
|
||||||
runthreadtosave();
|
scheduler.synchronize(ppu.thread);
|
||||||
|
scheduler.synchronize(apu.thread);
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
|
||||||
scheduler.active_thread = ppu.thread;
|
|
||||||
runthreadtosave();
|
|
||||||
|
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
|
||||||
scheduler.active_thread = apu.thread;
|
|
||||||
runthreadtosave();
|
|
||||||
|
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto System::runthreadtosave() -> void {
|
|
||||||
while(true) {
|
|
||||||
scheduler.enter();
|
|
||||||
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
|
|
||||||
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
|
|
||||||
video.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::init() -> void {
|
auto System::init() -> void {
|
||||||
@@ -50,31 +31,53 @@ auto System::init() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto System::load(Revision revision) -> void {
|
auto System::load(Revision revision) -> void {
|
||||||
this->revision = revision;
|
_revision = revision;
|
||||||
serialize_init();
|
|
||||||
if(revision == Revision::SuperGameBoy) return; //Super Famicom core loads boot ROM for SGB
|
|
||||||
|
|
||||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||||
auto document = BML::unserialize(information.manifest);
|
auto document = BML::unserialize(information.manifest);
|
||||||
|
string path = "system/cpu/rom/name";
|
||||||
|
if(revision == Revision::SuperGameBoy) path = "board/icd2/rom/name";
|
||||||
|
|
||||||
if(auto bootROM = document["system/cpu/rom/name"].text()) {
|
if(auto bootROM = document[path].text()) {
|
||||||
interface->loadRequest(
|
interface->loadRequest(
|
||||||
revision == Revision::GameBoy ? ID::GameBoyBootROM : ID::GameBoyColorBootROM,
|
revision == Revision::GameBoy ? ID::GameBoyBootROM
|
||||||
|
: revision == Revision::SuperGameBoy ? ID::SuperGameBoyBootROM
|
||||||
|
: revision == Revision::GameBoyColor ? ID::GameBoyColorBootROM
|
||||||
|
: ID::GameBoyBootROM,
|
||||||
bootROM, true
|
bootROM, true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cartridge.load(revision);
|
||||||
|
serializeInit();
|
||||||
|
_loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto System::unload() -> void {
|
||||||
|
if(!loaded()) return;
|
||||||
|
cartridge.unload();
|
||||||
|
_loaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::power() -> void {
|
auto System::power() -> void {
|
||||||
|
if(!system.sgb()) {
|
||||||
|
Emulator::video.reset();
|
||||||
|
Emulator::video.setInterface(interface);
|
||||||
|
configureVideoPalette();
|
||||||
|
configureVideoEffects();
|
||||||
|
|
||||||
|
Emulator::audio.reset();
|
||||||
|
Emulator::audio.setInterface(interface);
|
||||||
|
}
|
||||||
|
|
||||||
bus.power();
|
bus.power();
|
||||||
cartridge.power();
|
cartridge.power();
|
||||||
cpu.power();
|
cpu.power();
|
||||||
ppu.power();
|
ppu.power();
|
||||||
apu.power();
|
apu.power();
|
||||||
video.power();
|
scheduler.power();
|
||||||
scheduler.init();
|
|
||||||
|
|
||||||
clocks_executed = 0;
|
_clocksExecuted = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -9,29 +9,37 @@ struct System {
|
|||||||
GameBoy,
|
GameBoy,
|
||||||
SuperGameBoy,
|
SuperGameBoy,
|
||||||
GameBoyColor,
|
GameBoyColor,
|
||||||
} revision;
|
};
|
||||||
|
|
||||||
System();
|
System();
|
||||||
|
|
||||||
inline auto dmg() const { return revision == Revision::GameBoy; }
|
auto loaded() const -> bool;
|
||||||
inline auto sgb() const { return revision == Revision::SuperGameBoy; }
|
auto revision() const -> Revision;
|
||||||
inline auto cgb() const { return revision == Revision::GameBoyColor; }
|
auto clocksExecuted() const -> uint;
|
||||||
|
|
||||||
|
inline auto dmg() const { return _revision == Revision::GameBoy; }
|
||||||
|
inline auto sgb() const { return _revision == Revision::SuperGameBoy; }
|
||||||
|
inline auto cgb() const { return _revision == Revision::GameBoyColor; }
|
||||||
|
|
||||||
auto run() -> void;
|
auto run() -> void;
|
||||||
auto runtosave() -> void;
|
auto runToSave() -> void;
|
||||||
auto runthreadtosave() -> void;
|
|
||||||
|
|
||||||
auto init() -> void;
|
auto init() -> void;
|
||||||
auto load(Revision) -> void;
|
auto load(Revision) -> void;
|
||||||
|
auto unload() -> void;
|
||||||
auto power() -> void;
|
auto power() -> void;
|
||||||
|
|
||||||
|
//video.cpp
|
||||||
|
auto configureVideoPalette() -> void;
|
||||||
|
auto configureVideoEffects() -> void;
|
||||||
|
|
||||||
//serialization.cpp
|
//serialization.cpp
|
||||||
auto serialize() -> serializer;
|
auto serialize() -> serializer;
|
||||||
auto unserialize(serializer&) -> bool;
|
auto unserialize(serializer&) -> bool;
|
||||||
|
|
||||||
auto serialize(serializer&) -> void;
|
auto serialize(serializer&) -> void;
|
||||||
auto serialize_all(serializer&) -> void;
|
auto serializeAll(serializer&) -> void;
|
||||||
auto serialize_init() -> void;
|
auto serializeInit() -> void;
|
||||||
|
|
||||||
struct BootROM {
|
struct BootROM {
|
||||||
uint8 dmg[ 256];
|
uint8 dmg[ 256];
|
||||||
@@ -43,8 +51,10 @@ struct System {
|
|||||||
string manifest;
|
string manifest;
|
||||||
} information;
|
} information;
|
||||||
|
|
||||||
uint clocks_executed = 0;
|
bool _loaded = false;
|
||||||
uint serialize_size = 0;
|
Revision _revision = Revision::GameBoy;
|
||||||
|
uint _serializeSize = 0;
|
||||||
|
uint _clocksExecuted = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#include <gb/interface/interface.hpp>
|
#include <gb/interface/interface.hpp>
|
||||||
|
9
higan/gb/system/video.cpp
Normal file
9
higan/gb/system/video.cpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
auto System::configureVideoPalette() -> void {
|
||||||
|
if(sgb()) return;
|
||||||
|
Emulator::video.setPalette();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto System::configureVideoEffects() -> void {
|
||||||
|
if(sgb()) return;
|
||||||
|
Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation);
|
||||||
|
}
|
@@ -1,111 +0,0 @@
|
|||||||
#include <gb/gb.hpp>
|
|
||||||
|
|
||||||
namespace GameBoy {
|
|
||||||
|
|
||||||
Video video;
|
|
||||||
|
|
||||||
Video::Video() {
|
|
||||||
output = new uint32[160 * 144];
|
|
||||||
paletteStandard = new uint32[1 << 15];
|
|
||||||
paletteEmulation = new uint32[1 << 15];
|
|
||||||
}
|
|
||||||
|
|
||||||
Video::~Video() {
|
|
||||||
delete[] output;
|
|
||||||
delete[] paletteStandard;
|
|
||||||
delete[] paletteEmulation;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Video::power() -> void {
|
|
||||||
memory::fill(output, 160 * 144 * sizeof(uint32));
|
|
||||||
|
|
||||||
if(system.dmg()) {
|
|
||||||
for(auto color : range(1 << 2)) {
|
|
||||||
uint L = image::normalize(3 - color, 2, 8);
|
|
||||||
uint R = monochrome[color][0] >> 8;
|
|
||||||
uint G = monochrome[color][1] >> 8;
|
|
||||||
uint B = monochrome[color][2] >> 8;
|
|
||||||
paletteStandard[color] = (255 << 24) | (L << 16) | (L << 8) | (L << 0);
|
|
||||||
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(system.sgb()) {
|
|
||||||
for(auto color : range(1 << 2)) {
|
|
||||||
paletteStandard[color] = color;
|
|
||||||
paletteEmulation[color] = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(system.cgb()) {
|
|
||||||
for(auto color : range(1 << 15)) {
|
|
||||||
uint r = (uint5)(color >> 0);
|
|
||||||
uint g = (uint5)(color >> 5);
|
|
||||||
uint b = (uint5)(color >> 10);
|
|
||||||
|
|
||||||
{ uint R = image::normalize(r, 5, 8);
|
|
||||||
uint G = image::normalize(g, 5, 8);
|
|
||||||
uint B = image::normalize(b, 5, 8);
|
|
||||||
paletteStandard[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{ uint R = (r * 26 + g * 4 + b * 2);
|
|
||||||
uint G = ( g * 24 + b * 8);
|
|
||||||
uint B = (r * 6 + g * 4 + b * 22);
|
|
||||||
R = min(960, R) >> 2;
|
|
||||||
G = min(960, G) >> 2;
|
|
||||||
B = min(960, B) >> 2;
|
|
||||||
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Video::refresh() -> void {
|
|
||||||
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
|
||||||
|
|
||||||
for(uint y = 0; y < 144; y++) {
|
|
||||||
auto source = ppu.screen + y * 160;
|
|
||||||
auto target = output + y * 160;
|
|
||||||
|
|
||||||
if(settings.blurEmulation) {
|
|
||||||
for(uint x = 0; x < 160; x++) {
|
|
||||||
auto a = palette[*source++];
|
|
||||||
auto b = *target;
|
|
||||||
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(uint x = 0; x < 160; x++) {
|
|
||||||
auto color = palette[*source++];
|
|
||||||
*target++ = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface->videoRefresh(output, 4 * 160, 160, 144);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define DMG_PALETTE_GREEN
|
|
||||||
//#define DMG_PALETTE_YELLOW
|
|
||||||
//#define DMG_PALETTE_WHITE
|
|
||||||
|
|
||||||
const uint16 Video::monochrome[4][3] = {
|
|
||||||
#if defined(DMG_PALETTE_GREEN)
|
|
||||||
{0xaeae, 0xd9d9, 0x2727},
|
|
||||||
{0x5858, 0xa0a0, 0x2828},
|
|
||||||
{0x2020, 0x6262, 0x2929},
|
|
||||||
{0x1a1a, 0x4545, 0x2a2a},
|
|
||||||
#elif defined(DMG_PALETTE_YELLOW)
|
|
||||||
{0xffff, 0xf7f7, 0x7b7b},
|
|
||||||
{0xb5b5, 0xaeae, 0x4a4a},
|
|
||||||
{0x6b6b, 0x6969, 0x3131},
|
|
||||||
{0x2121, 0x2020, 0x1010},
|
|
||||||
#else //DMG_PALETTE_WHITE
|
|
||||||
{0xffff, 0xffff, 0xffff},
|
|
||||||
{0xaaaa, 0xaaaa, 0xaaaa},
|
|
||||||
{0x5555, 0x5555, 0x5555},
|
|
||||||
{0x0000, 0x0000, 0x0000},
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
struct Video {
|
|
||||||
Video();
|
|
||||||
~Video();
|
|
||||||
|
|
||||||
auto power() -> void;
|
|
||||||
auto refresh() -> void;
|
|
||||||
|
|
||||||
uint32* output = nullptr;
|
|
||||||
uint32* paletteStandard = nullptr;
|
|
||||||
uint32* paletteEmulation = nullptr;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static const uint16 monochrome[4][3];
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Video video;
|
|
@@ -1,15 +1,15 @@
|
|||||||
gba_objects := gba-memory gba-interface gba-scheduler gba-system
|
processors += arm
|
||||||
gba_objects += gba-video gba-cartridge gba-player
|
|
||||||
gba_objects += gba-cpu gba-ppu gba-apu
|
|
||||||
objects += $(gba_objects)
|
|
||||||
|
|
||||||
obj/gba-memory.o: $(gba)/memory/memory.cpp $(call rwildcard,$(gba)/memory)
|
objects += gba-memory gba-interface gba-scheduler gba-system
|
||||||
obj/gba-interface.o: $(gba)/interface/interface.cpp $(call rwildcard,$(gba)/interface)
|
objects += gba-cartridge gba-player
|
||||||
obj/gba-scheduler.o: $(gba)/scheduler/scheduler.cpp $(call rwildcard,$(gba)/scheduler)
|
objects += gba-cpu gba-ppu gba-apu
|
||||||
obj/gba-system.o: $(gba)/system/system.cpp $(call rwildcard,$(gba)/system)
|
|
||||||
obj/gba-video.o: $(gba)/video/video.cpp $(call rwildcard,$(gba)/video)
|
obj/gba-memory.o: gba/memory/memory.cpp $(call rwildcard,gba/memory)
|
||||||
obj/gba-cartridge.o: $(gba)/cartridge/cartridge.cpp $(call rwildcard,$(gba)/cartridge)
|
obj/gba-interface.o: gba/interface/interface.cpp $(call rwildcard,gba/interface)
|
||||||
obj/gba-player.o: $(gba)/player/player.cpp $(call rwildcard,$(gba)/player)
|
obj/gba-scheduler.o: gba/scheduler/scheduler.cpp $(call rwildcard,gba/scheduler)
|
||||||
obj/gba-cpu.o: $(gba)/cpu/cpu.cpp $(call rwildcard,$(gba)/cpu)
|
obj/gba-system.o: gba/system/system.cpp $(call rwildcard,gba/system)
|
||||||
obj/gba-ppu.o: $(gba)/ppu/ppu.cpp $(call rwildcard,$(gba)/ppu)
|
obj/gba-cartridge.o: gba/cartridge/cartridge.cpp $(call rwildcard,gba/cartridge)
|
||||||
obj/gba-apu.o: $(gba)/apu/apu.cpp $(call rwildcard,$(gba)/apu)
|
obj/gba-player.o: gba/player/player.cpp $(call rwildcard,gba/player)
|
||||||
|
obj/gba-cpu.o: gba/cpu/cpu.cpp $(call rwildcard,gba/cpu)
|
||||||
|
obj/gba-ppu.o: gba/ppu/ppu.cpp $(call rwildcard,gba/ppu)
|
||||||
|
obj/gba-apu.o: gba/apu/apu.cpp $(call rwildcard,gba/apu)
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace GameBoyAdvance {
|
namespace GameBoyAdvance {
|
||||||
|
|
||||||
#include "registers.cpp"
|
|
||||||
#include "mmio.cpp"
|
#include "mmio.cpp"
|
||||||
#include "square.cpp"
|
#include "square.cpp"
|
||||||
#include "square1.cpp"
|
#include "square1.cpp"
|
||||||
@@ -15,13 +14,7 @@ namespace GameBoyAdvance {
|
|||||||
APU apu;
|
APU apu;
|
||||||
|
|
||||||
auto APU::Enter() -> void {
|
auto APU::Enter() -> void {
|
||||||
while(true) {
|
while(true) scheduler.synchronize(), apu.main();
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
apu.main();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::main() -> void {
|
auto APU::main() -> void {
|
||||||
@@ -70,17 +63,18 @@ auto APU::main() -> void {
|
|||||||
if(regs.bias.amplitude == 3) lsample &= ~15, rsample &= ~15;
|
if(regs.bias.amplitude == 3) lsample &= ~15, rsample &= ~15;
|
||||||
|
|
||||||
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
|
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
|
||||||
interface->audioSample(sclamp<16>(lsample << 6), sclamp<16>(rsample << 6)); //should be <<5, use <<6 for added volume
|
stream->sample(sclamp<16>(lsample << 6) / 32768.0, sclamp<16>(rsample << 6) / 32768.0); //should be <<5; use <<6 for added volume
|
||||||
step(512);
|
step(512);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::step(uint clocks) -> void {
|
auto APU::step(uint clocks) -> void {
|
||||||
clock += clocks;
|
clock += clocks;
|
||||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::power() -> void {
|
auto APU::power() -> void {
|
||||||
create(APU::Enter, 16777216);
|
create(APU::Enter, 16'777'216);
|
||||||
|
stream = Emulator::audio.createStream(2, 16'777'216.0 / 512.0);
|
||||||
|
|
||||||
square1.power();
|
square1.power();
|
||||||
square2.power();
|
square2.power();
|
||||||
@@ -90,7 +84,8 @@ auto APU::power() -> void {
|
|||||||
fifo[0].power();
|
fifo[0].power();
|
||||||
fifo[1].power();
|
fifo[1].power();
|
||||||
|
|
||||||
regs.bias = 0x0200;
|
regs.bias.amplitude = 0;
|
||||||
|
regs.bias.level = 0x200;
|
||||||
|
|
||||||
for(uint n = 0x060; n <= 0x0a7; n++) bus.mmio[n] = this;
|
for(uint n = 0x060; n <= 0x0a7; n++) bus.mmio[n] = this;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
struct APU : Thread, MMIO {
|
struct APU : Thread, MMIO {
|
||||||
|
shared_pointer<Emulator::Stream> stream;
|
||||||
|
|
||||||
#include "registers.hpp"
|
#include "registers.hpp"
|
||||||
|
|
||||||
static auto Enter() -> void;
|
static auto Enter() -> void;
|
||||||
|
@@ -2,215 +2,234 @@ auto APU::read(uint32 addr) -> uint8 {
|
|||||||
switch(addr) {
|
switch(addr) {
|
||||||
|
|
||||||
//NR10
|
//NR10
|
||||||
case 0x04000060: return square1.read(0);
|
case 0x0400'0060: return square1.read(0);
|
||||||
case 0x04000061: return 0u;
|
case 0x0400'0061: return 0;
|
||||||
|
|
||||||
//NR11 + NR12
|
//NR11, NR12
|
||||||
case 0x04000062: return square1.read(1);
|
case 0x0400'0062: return square1.read(1);
|
||||||
case 0x04000063: return square1.read(2);
|
case 0x0400'0063: return square1.read(2);
|
||||||
|
|
||||||
//NR13 + NR14
|
//NR13, NR14
|
||||||
case 0x04000064: return square1.read(3);
|
case 0x0400'0064: return square1.read(3);
|
||||||
case 0x04000065: return square1.read(4);
|
case 0x0400'0065: return square1.read(4);
|
||||||
|
|
||||||
//NR21 + NR22
|
//NR21, NR22
|
||||||
case 0x04000068: return square2.read(1);
|
case 0x0400'0068: return square2.read(1);
|
||||||
case 0x04000069: return square2.read(2);
|
case 0x0400'0069: return square2.read(2);
|
||||||
|
|
||||||
//NR23 + NR24
|
//NR23, NR24
|
||||||
case 0x0400006c: return square2.read(3);
|
case 0x0400'006c: return square2.read(3);
|
||||||
case 0x0400006d: return square2.read(4);
|
case 0x0400'006d: return square2.read(4);
|
||||||
|
|
||||||
//NR30
|
//NR30
|
||||||
case 0x04000070: return wave.read(0);
|
case 0x0400'0070: return wave.read(0);
|
||||||
case 0x04000071: return 0u;
|
case 0x0400'0071: return 0;
|
||||||
|
|
||||||
//NR31 + NR32
|
//NR31, NR32
|
||||||
case 0x04000072: return wave.read(1);
|
case 0x0400'0072: return wave.read(1);
|
||||||
case 0x04000073: return wave.read(2);
|
case 0x0400'0073: return wave.read(2);
|
||||||
|
|
||||||
//NR33 + NR34
|
//NR33, NR34
|
||||||
case 0x04000074: return wave.read(3);
|
case 0x0400'0074: return wave.read(3);
|
||||||
case 0x04000075: return wave.read(4);
|
case 0x0400'0075: return wave.read(4);
|
||||||
|
|
||||||
//NR41 + NR42
|
//NR41, NR42
|
||||||
case 0x04000078: return noise.read(1);
|
case 0x0400'0078: return noise.read(1);
|
||||||
case 0x04000079: return noise.read(2);
|
case 0x0400'0079: return noise.read(2);
|
||||||
|
|
||||||
//NR43 + NR44
|
//NR43, NR44
|
||||||
case 0x0400007c: return noise.read(3);
|
case 0x0400'007c: return noise.read(3);
|
||||||
case 0x0400007d: return noise.read(4);
|
case 0x0400'007d: return noise.read(4);
|
||||||
|
|
||||||
//NR50 + NR51
|
//NR50, NR51
|
||||||
case 0x04000080: return sequencer.read(0);
|
case 0x0400'0080: return sequencer.read(0);
|
||||||
case 0x04000081: return sequencer.read(1);
|
case 0x0400'0081: return sequencer.read(1);
|
||||||
|
|
||||||
//SOUND_CNT_H
|
//SOUND_CNT_H
|
||||||
case 0x04000082:
|
case 0x0400'0082: return (
|
||||||
return (fifo[1].volume << 3) | (fifo[0].volume << 2) | (sequencer.volume << 0);
|
sequencer.volume << 0
|
||||||
case 0x04000083:
|
| fifo[0].volume << 2
|
||||||
return (fifo[1].timer << 6) | (fifo[1].lenable << 5) | (fifo[1].renable << 4)
|
| fifo[1].volume << 3
|
||||||
| (fifo[0].timer << 2) | (fifo[0].lenable << 1) | (fifo[0].renable << 0);
|
);
|
||||||
|
|
||||||
|
case 0x0400'0083: return (
|
||||||
|
fifo[0].renable << 0
|
||||||
|
| fifo[0].lenable << 1
|
||||||
|
| fifo[0].timer << 2
|
||||||
|
| fifo[1].renable << 4
|
||||||
|
| fifo[1].lenable << 5
|
||||||
|
| fifo[1].timer << 6
|
||||||
|
);
|
||||||
|
|
||||||
//NR52
|
//NR52
|
||||||
case 0x04000084: return sequencer.read(2);
|
case 0x0400'0084: return sequencer.read(2);
|
||||||
case 0x04000085: return 0u;
|
case 0x0400'0085: return 0;
|
||||||
|
|
||||||
//SOUNDBIAS
|
//SOUNDBIAS
|
||||||
case 0x04000088: return regs.bias >> 0;
|
case 0x0400'0088: return (
|
||||||
case 0x04000089: return regs.bias >> 8;
|
regs.bias.level.bits(0,7)
|
||||||
|
);
|
||||||
|
case 0x0400'0089: return (
|
||||||
|
regs.bias.level.bits(8,9) << 0
|
||||||
|
| regs.bias.amplitude << 6
|
||||||
|
);
|
||||||
|
|
||||||
//WAVE_RAM0_L
|
//WAVE_RAM0_L
|
||||||
case 0x04000090: return wave.readram( 0);
|
case 0x0400'0090: return wave.readram( 0);
|
||||||
case 0x04000091: return wave.readram( 1);
|
case 0x0400'0091: return wave.readram( 1);
|
||||||
|
|
||||||
//WAVE_RAM0_H
|
//WAVE_RAM0_H
|
||||||
case 0x04000092: return wave.readram( 2);
|
case 0x0400'0092: return wave.readram( 2);
|
||||||
case 0x04000093: return wave.readram( 3);
|
case 0x0400'0093: return wave.readram( 3);
|
||||||
|
|
||||||
//WAVE_RAM1_L
|
//WAVE_RAM1_L
|
||||||
case 0x04000094: return wave.readram( 4);
|
case 0x0400'0094: return wave.readram( 4);
|
||||||
case 0x04000095: return wave.readram( 5);
|
case 0x0400'0095: return wave.readram( 5);
|
||||||
|
|
||||||
//WAVE_RAM1_H
|
//WAVE_RAM1_H
|
||||||
case 0x04000096: return wave.readram( 6);
|
case 0x0400'0096: return wave.readram( 6);
|
||||||
case 0x04000097: return wave.readram( 7);
|
case 0x0400'0097: return wave.readram( 7);
|
||||||
|
|
||||||
//WAVE_RAM2_L
|
//WAVE_RAM2_L
|
||||||
case 0x04000098: return wave.readram( 8);
|
case 0x0400'0098: return wave.readram( 8);
|
||||||
case 0x04000099: return wave.readram( 9);
|
case 0x0400'0099: return wave.readram( 9);
|
||||||
|
|
||||||
//WAVE_RAM2_H
|
//WAVE_RAM2_H
|
||||||
case 0x0400009a: return wave.readram(10);
|
case 0x0400'009a: return wave.readram(10);
|
||||||
case 0x0400009b: return wave.readram(11);
|
case 0x0400'009b: return wave.readram(11);
|
||||||
|
|
||||||
//WAVE_RAM3_L
|
//WAVE_RAM3_L
|
||||||
case 0x0400009c: return wave.readram(12);
|
case 0x0400'009c: return wave.readram(12);
|
||||||
case 0x0400009d: return wave.readram(13);
|
case 0x0400'009d: return wave.readram(13);
|
||||||
|
|
||||||
//WAVE_RAM3_H
|
//WAVE_RAM3_H
|
||||||
case 0x0400009e: return wave.readram(14);
|
case 0x0400'009e: return wave.readram(14);
|
||||||
case 0x0400009f: return wave.readram(15);
|
case 0x0400'009f: return wave.readram(15);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0u;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::write(uint32 addr, uint8 byte) -> void {
|
auto APU::write(uint32 addr, uint8 data) -> void {
|
||||||
switch(addr) {
|
switch(addr) {
|
||||||
|
|
||||||
//NR10
|
//NR10
|
||||||
case 0x04000060: return square1.write(0, byte);
|
case 0x0400'0060: return square1.write(0, data);
|
||||||
case 0x04000061: return;
|
case 0x0400'0061: return;
|
||||||
|
|
||||||
//NR11 + NR12
|
//NR11, NR12
|
||||||
case 0x04000062: return square1.write(1, byte);
|
case 0x0400'0062: return square1.write(1, data);
|
||||||
case 0x04000063: return square1.write(2, byte);
|
case 0x0400'0063: return square1.write(2, data);
|
||||||
|
|
||||||
//NR13 + NR14
|
//NR13, NR14
|
||||||
case 0x04000064: return square1.write(3, byte);
|
case 0x0400'0064: return square1.write(3, data);
|
||||||
case 0x04000065: return square1.write(4, byte);
|
case 0x0400'0065: return square1.write(4, data);
|
||||||
|
|
||||||
//NR21 + NR22
|
//NR21, NR22
|
||||||
case 0x04000068: return square2.write(1, byte);
|
case 0x0400'0068: return square2.write(1, data);
|
||||||
case 0x04000069: return square2.write(2, byte);
|
case 0x0400'0069: return square2.write(2, data);
|
||||||
|
|
||||||
//NR23 + NR24
|
//NR23, NR24
|
||||||
case 0x0400006c: return square2.write(3, byte);
|
case 0x0400'006c: return square2.write(3, data);
|
||||||
case 0x0400006d: return square2.write(4, byte);
|
case 0x0400'006d: return square2.write(4, data);
|
||||||
|
|
||||||
//NR30
|
//NR30
|
||||||
case 0x04000070: return wave.write(0, byte);
|
case 0x0400'0070: return wave.write(0, data);
|
||||||
case 0x04000071: return;
|
case 0x0400'0071: return;
|
||||||
|
|
||||||
//NR31 + NR32
|
//NR31, NR32
|
||||||
case 0x04000072: return wave.write(1, byte);
|
case 0x0400'0072: return wave.write(1, data);
|
||||||
case 0x04000073: return wave.write(2, byte);
|
case 0x0400'0073: return wave.write(2, data);
|
||||||
|
|
||||||
//NR33 + NR34
|
//NR33, NR34
|
||||||
case 0x04000074: return wave.write(3, byte);
|
case 0x0400'0074: return wave.write(3, data);
|
||||||
case 0x04000075: return wave.write(4, byte);
|
case 0x0400'0075: return wave.write(4, data);
|
||||||
|
|
||||||
//NR41 + NR42
|
//NR41, NR42
|
||||||
case 0x04000078: return noise.write(1, byte);
|
case 0x0400'0078: return noise.write(1, data);
|
||||||
case 0x04000079: return noise.write(2, byte);
|
case 0x0400'0079: return noise.write(2, data);
|
||||||
|
|
||||||
//NR43 + NR44
|
//NR43, NR44
|
||||||
case 0x0400007c: return noise.write(3, byte);
|
case 0x0400'007c: return noise.write(3, data);
|
||||||
case 0x0400007d: return noise.write(4, byte);
|
case 0x0400'007d: return noise.write(4, data);
|
||||||
|
|
||||||
//NR50 + NR51
|
//NR50, NR51
|
||||||
case 0x04000080: return sequencer.write(0, byte);
|
case 0x0400'0080: return sequencer.write(0, data);
|
||||||
case 0x04000081: return sequencer.write(1, byte);
|
case 0x0400'0081: return sequencer.write(1, data);
|
||||||
|
|
||||||
//SOUND_CNT_H
|
//SOUND_CNT_H
|
||||||
case 0x04000082:
|
case 0x0400'0082:
|
||||||
sequencer.volume = byte >> 0;
|
sequencer.volume = data.bits(0,1);
|
||||||
fifo[0].volume = byte >> 2;
|
fifo[0].volume = data.bit (2);
|
||||||
fifo[1].volume = byte >> 3;
|
fifo[1].volume = data.bit (3);
|
||||||
return;
|
return;
|
||||||
case 0x04000083:
|
case 0x0400'0083:
|
||||||
fifo[0].renable = byte >> 0;
|
fifo[0].renable = data.bit(0);
|
||||||
fifo[0].lenable = byte >> 1;
|
fifo[0].lenable = data.bit(1);
|
||||||
fifo[0].timer = byte >> 2;
|
fifo[0].timer = data.bit(2);
|
||||||
if(byte & 1 << 3) fifo[0].reset();
|
if(data.bit(3)) fifo[0].reset();
|
||||||
fifo[1].renable = byte >> 4;
|
fifo[1].renable = data.bit(4);
|
||||||
fifo[1].lenable = byte >> 5;
|
fifo[1].lenable = data.bit(5);
|
||||||
fifo[1].timer = byte >> 6;
|
fifo[1].timer = data.bit(6);
|
||||||
if(byte & 1 << 7) fifo[1].reset();
|
if(data.bit(7)) fifo[1].reset();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//NR52
|
//NR52
|
||||||
case 0x04000084: return sequencer.write(2, byte);
|
case 0x0400'0084: return sequencer.write(2, data);
|
||||||
case 0x04000085: return;
|
case 0x0400'0085: return;
|
||||||
|
|
||||||
//SOUNDBIAS
|
//SOUNDBIAS
|
||||||
case 0x04000088: regs.bias = (regs.bias & 0xff00) | (byte << 0); return;
|
case 0x0400'0088:
|
||||||
case 0x04000089: regs.bias = (regs.bias & 0x00ff) | (byte << 8); return;
|
regs.bias.level.bits(0,7) = data;
|
||||||
|
return;
|
||||||
|
case 0x0400'0089:
|
||||||
|
regs.bias.level.bits(8,9) = data.bits(0,1);
|
||||||
|
regs.bias.amplitude = data.bits(6,7);
|
||||||
|
return;
|
||||||
|
|
||||||
//WAVE_RAM0_L
|
//WAVE_RAM0_L
|
||||||
case 0x04000090: return wave.writeram( 0, byte);
|
case 0x0400'0090: return wave.writeram( 0, data);
|
||||||
case 0x04000091: return wave.writeram( 1, byte);
|
case 0x0400'0091: return wave.writeram( 1, data);
|
||||||
|
|
||||||
//WAVE_RAM0_H
|
//WAVE_RAM0_H
|
||||||
case 0x04000092: return wave.writeram( 2, byte);
|
case 0x0400'0092: return wave.writeram( 2, data);
|
||||||
case 0x04000093: return wave.writeram( 3, byte);
|
case 0x0400'0093: return wave.writeram( 3, data);
|
||||||
|
|
||||||
//WAVE_RAM1_L
|
//WAVE_RAM1_L
|
||||||
case 0x04000094: return wave.writeram( 4, byte);
|
case 0x0400'0094: return wave.writeram( 4, data);
|
||||||
case 0x04000095: return wave.writeram( 5, byte);
|
case 0x0400'0095: return wave.writeram( 5, data);
|
||||||
|
|
||||||
//WAVE_RAM1_H
|
//WAVE_RAM1_H
|
||||||
case 0x04000096: return wave.writeram( 6, byte);
|
case 0x0400'0096: return wave.writeram( 6, data);
|
||||||
case 0x04000097: return wave.writeram( 7, byte);
|
case 0x0400'0097: return wave.writeram( 7, data);
|
||||||
|
|
||||||
//WAVE_RAM2_L
|
//WAVE_RAM2_L
|
||||||
case 0x04000098: return wave.writeram( 8, byte);
|
case 0x0400'0098: return wave.writeram( 8, data);
|
||||||
case 0x04000099: return wave.writeram( 9, byte);
|
case 0x0400'0099: return wave.writeram( 9, data);
|
||||||
|
|
||||||
//WAVE_RAM2_H
|
//WAVE_RAM2_H
|
||||||
case 0x0400009a: return wave.writeram(10, byte);
|
case 0x0400'009a: return wave.writeram(10, data);
|
||||||
case 0x0400009b: return wave.writeram(11, byte);
|
case 0x0400'009b: return wave.writeram(11, data);
|
||||||
|
|
||||||
//WAVE_RAM3_L
|
//WAVE_RAM3_L
|
||||||
case 0x0400009c: return wave.writeram(12, byte);
|
case 0x0400'009c: return wave.writeram(12, data);
|
||||||
case 0x0400009d: return wave.writeram(13, byte);
|
case 0x0400'009d: return wave.writeram(13, data);
|
||||||
|
|
||||||
//WAVE_RAM3_H
|
//WAVE_RAM3_H
|
||||||
case 0x0400009e: return wave.writeram(14, byte);
|
case 0x0400'009e: return wave.writeram(14, data);
|
||||||
case 0x0400009f: return wave.writeram(15, byte);
|
case 0x0400'009f: return wave.writeram(15, data);
|
||||||
|
|
||||||
//FIFO_A_L
|
//FIFO_A_L
|
||||||
//FIFO_A_H
|
//FIFO_A_H
|
||||||
case 0x040000a0: case 0x040000a1:
|
case 0x0400'00a0: case 0x0400'00a1:
|
||||||
case 0x040000a2: case 0x040000a3:
|
case 0x0400'00a2: case 0x0400'00a3:
|
||||||
return fifo[0].write(byte);
|
return fifo[0].write(data);
|
||||||
|
|
||||||
//FIFO_B_L
|
//FIFO_B_L
|
||||||
//FIFO_B_H
|
//FIFO_B_H
|
||||||
case 0x040000a4: case 0x040000a5:
|
case 0x0400'00a4: case 0x0400'00a5:
|
||||||
case 0x040000a6: case 0x040000a7:
|
case 0x0400'00a6: case 0x0400'00a7:
|
||||||
return fifo[1].write(byte);
|
return fifo[1].write(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
APU::Registers::SoundBias::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(level << 0)
|
|
||||||
| (amplitude << 14)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto APU::Registers::SoundBias::operator=(uint16 source) -> uint16 {
|
|
||||||
level = (source >> 0) & 1023;
|
|
||||||
amplitude = (source >> 14) & 3;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
@@ -2,10 +2,6 @@ struct Registers {
|
|||||||
struct SoundBias {
|
struct SoundBias {
|
||||||
uint10 level;
|
uint10 level;
|
||||||
uint2 amplitude;
|
uint2 amplitude;
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const SoundBias&) -> SoundBias& = delete;
|
|
||||||
} bias;
|
} bias;
|
||||||
|
|
||||||
uint clock;
|
uint clock;
|
||||||
|
@@ -23,10 +23,6 @@ Cartridge::~Cartridge() {
|
|||||||
delete[] flash.data;
|
delete[] flash.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::loaded() const -> bool {
|
|
||||||
return isLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Cartridge::sha256() const -> string {
|
auto Cartridge::sha256() const -> string {
|
||||||
return information.sha256;
|
return information.sha256;
|
||||||
}
|
}
|
||||||
@@ -96,16 +92,10 @@ auto Cartridge::load() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
information.sha256 = Hash::SHA256(mrom.data, mrom.size).digest();
|
information.sha256 = Hash::SHA256(mrom.data, mrom.size).digest();
|
||||||
|
|
||||||
system.load();
|
|
||||||
isLoaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::unload() -> void {
|
auto Cartridge::unload() -> void {
|
||||||
if(isLoaded) {
|
memory.reset();
|
||||||
isLoaded = false;
|
|
||||||
memory.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::power() -> void {
|
auto Cartridge::power() -> void {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
struct Cartridge {
|
struct Cartridge {
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
|
|
||||||
auto loaded() const -> bool;
|
|
||||||
auto sha256() const -> string;
|
auto sha256() const -> string;
|
||||||
auto manifest() const -> string;
|
auto manifest() const -> string;
|
||||||
auto title() const -> string;
|
auto title() const -> string;
|
||||||
@@ -31,7 +30,6 @@ struct Cartridge {
|
|||||||
auto serialize(serializer&) -> void;
|
auto serialize(serializer&) -> void;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isLoaded = false;
|
|
||||||
bool hasSRAM = false;
|
bool hasSRAM = false;
|
||||||
bool hasEEPROM = false;
|
bool hasEEPROM = false;
|
||||||
bool hasFLASH = false;
|
bool hasFLASH = false;
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
auto CPU::bus_idle() -> void {
|
auto CPU::busIdle() -> void {
|
||||||
prefetch_step(1);
|
prefetch_step(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::bus_read(unsigned mode, uint32 addr) -> uint32 {
|
auto CPU::busRead(uint mode, uint32 addr) -> uint32 {
|
||||||
unsigned wait = bus_wait(mode, addr);
|
uint wait = busWait(mode, addr);
|
||||||
unsigned word = pipeline.fetch.instruction;
|
uint word = pipeline.fetch.instruction;
|
||||||
|
|
||||||
if(addr >= 0x1000'0000) {
|
if(addr >= 0x1000'0000) {
|
||||||
prefetch_step(wait);
|
prefetch_step(wait);
|
||||||
@@ -35,8 +35,8 @@ auto CPU::bus_read(unsigned mode, uint32 addr) -> uint32 {
|
|||||||
return word;
|
return word;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::bus_write(unsigned mode, uint32 addr, uint32 word) -> void {
|
auto CPU::busWrite(uint mode, uint32 addr, uint32 word) -> void {
|
||||||
unsigned wait = bus_wait(mode, addr);
|
uint wait = busWait(mode, addr);
|
||||||
|
|
||||||
if(addr >= 0x1000'0000) {
|
if(addr >= 0x1000'0000) {
|
||||||
prefetch_step(wait);
|
prefetch_step(wait);
|
||||||
@@ -57,7 +57,7 @@ auto CPU::bus_write(unsigned mode, uint32 addr, uint32 word) -> void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
|
auto CPU::busWait(uint mode, uint32 addr) -> uint {
|
||||||
if(addr >= 0x1000'0000) return 1; //unmapped
|
if(addr >= 0x1000'0000) return 1; //unmapped
|
||||||
if(addr < 0x0200'0000) return 1;
|
if(addr < 0x0200'0000) return 1;
|
||||||
if(addr < 0x0300'0000) return (16 - regs.memory.control.ewramwait) * (mode & Word ? 2 : 1);
|
if(addr < 0x0300'0000) return (16 - regs.memory.control.ewramwait) * (mode & Word ? 2 : 1);
|
||||||
@@ -65,9 +65,9 @@ auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
|
|||||||
if(addr < 0x0700'0000) return mode & Word ? 2 : 1;
|
if(addr < 0x0700'0000) return mode & Word ? 2 : 1;
|
||||||
if(addr < 0x0800'0000) return 1;
|
if(addr < 0x0800'0000) return 1;
|
||||||
|
|
||||||
static unsigned timings[] = {5, 4, 3, 9};
|
static uint timings[] = {5, 4, 3, 9};
|
||||||
unsigned n = timings[regs.wait.control.nwait[addr >> 25 & 3]];
|
uint n = timings[regs.wait.control.nwait[addr >> 25 & 3]];
|
||||||
unsigned s = regs.wait.control.swait[addr >> 25 & 3];
|
uint s = regs.wait.control.swait[addr >> 25 & 3];
|
||||||
|
|
||||||
switch(addr & 0x0e00'0000) {
|
switch(addr & 0x0e00'0000) {
|
||||||
case 0x0800'0000: s = s ? 2 : 3; break;
|
case 0x0800'0000: s = s ? 2 : 3; break;
|
||||||
@@ -79,7 +79,7 @@ auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
|
|||||||
bool sequential = (mode & Sequential);
|
bool sequential = (mode & Sequential);
|
||||||
if((addr & 0x1fffe) == 0) sequential = false; //N cycle on 16-bit ROM crossing 128KB page boundary (RAM S==N)
|
if((addr & 0x1fffe) == 0) sequential = false; //N cycle on 16-bit ROM crossing 128KB page boundary (RAM S==N)
|
||||||
|
|
||||||
unsigned clocks = sequential ? s : n;
|
uint clocks = sequential ? s : n;
|
||||||
if(mode & Word) clocks += s; //16-bit bus requires two transfers for words
|
if(mode & Word) clocks += s; //16-bit bus requires two transfers for words
|
||||||
return clocks;
|
return clocks;
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace GameBoyAdvance {
|
namespace GameBoyAdvance {
|
||||||
|
|
||||||
#include "registers.cpp"
|
|
||||||
#include "prefetch.cpp"
|
#include "prefetch.cpp"
|
||||||
#include "bus.cpp"
|
#include "bus.cpp"
|
||||||
#include "mmio.cpp"
|
#include "mmio.cpp"
|
||||||
@@ -16,21 +15,21 @@ CPU::CPU() {
|
|||||||
iwram = new uint8[ 32 * 1024];
|
iwram = new uint8[ 32 * 1024];
|
||||||
ewram = new uint8[256 * 1024];
|
ewram = new uint8[256 * 1024];
|
||||||
|
|
||||||
regs.dma[0].source.bits(27); regs.dma[0].run.source.bits(27);
|
regs.dma[0].source.resize(27); regs.dma[0].run.source.resize(27);
|
||||||
regs.dma[0].target.bits(27); regs.dma[0].run.target.bits(27);
|
regs.dma[0].target.resize(27); regs.dma[0].run.target.resize(27);
|
||||||
regs.dma[0].length.bits(14); regs.dma[0].run.length.bits(14);
|
regs.dma[0].length.resize(14); regs.dma[0].run.length.resize(14);
|
||||||
|
|
||||||
regs.dma[1].source.bits(28); regs.dma[1].run.source.bits(28);
|
regs.dma[1].source.resize(28); regs.dma[1].run.source.resize(28);
|
||||||
regs.dma[1].target.bits(27); regs.dma[1].run.target.bits(27);
|
regs.dma[1].target.resize(27); regs.dma[1].run.target.resize(27);
|
||||||
regs.dma[1].length.bits(14); regs.dma[1].run.length.bits(14);
|
regs.dma[1].length.resize(14); regs.dma[1].run.length.resize(14);
|
||||||
|
|
||||||
regs.dma[2].source.bits(28); regs.dma[2].run.source.bits(28);
|
regs.dma[2].source.resize(28); regs.dma[2].run.source.resize(28);
|
||||||
regs.dma[2].target.bits(27); regs.dma[2].run.target.bits(27);
|
regs.dma[2].target.resize(27); regs.dma[2].run.target.resize(27);
|
||||||
regs.dma[2].length.bits(14); regs.dma[2].run.length.bits(14);
|
regs.dma[2].length.resize(14); regs.dma[2].run.length.resize(14);
|
||||||
|
|
||||||
regs.dma[3].source.bits(28); regs.dma[3].run.source.bits(28);
|
regs.dma[3].source.resize(28); regs.dma[3].run.source.resize(28);
|
||||||
regs.dma[3].target.bits(28); regs.dma[3].run.target.bits(28);
|
regs.dma[3].target.resize(28); regs.dma[3].run.target.resize(28);
|
||||||
regs.dma[3].length.bits(16); regs.dma[3].run.length.bits(16);
|
regs.dma[3].length.resize(16); regs.dma[3].run.length.resize(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
CPU::~CPU() {
|
CPU::~CPU() {
|
||||||
@@ -39,14 +38,7 @@ CPU::~CPU() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::Enter() -> void {
|
auto CPU::Enter() -> void {
|
||||||
while(true) {
|
while(true) scheduler.synchronize(), cpu.main();
|
||||||
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
|
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
|
||||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
cpu.main();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::main() -> void {
|
auto CPU::main() -> void {
|
||||||
@@ -63,7 +55,7 @@ auto CPU::main() -> void {
|
|||||||
processor.irqline = regs.ime && (regs.irq.enable & regs.irq.flag);
|
processor.irqline = regs.ime && (regs.irq.enable & regs.irq.flag);
|
||||||
|
|
||||||
if(regs.mode == Registers::Mode::Stop) {
|
if(regs.mode == Registers::Mode::Stop) {
|
||||||
if((regs.irq.enable.keypad & regs.irq.flag.keypad) == 0) {
|
if(!(regs.irq.enable & regs.irq.flag & Interrupt::Keypad)) {
|
||||||
sync_step(16); //STOP does not advance timers
|
sync_step(16); //STOP does not advance timers
|
||||||
} else {
|
} else {
|
||||||
regs.mode = Registers::Mode::Normal;
|
regs.mode = Registers::Mode::Normal;
|
||||||
@@ -74,7 +66,7 @@ auto CPU::main() -> void {
|
|||||||
dma_run();
|
dma_run();
|
||||||
|
|
||||||
if(regs.mode == Registers::Mode::Halt) {
|
if(regs.mode == Registers::Mode::Halt) {
|
||||||
if((regs.irq.enable & regs.irq.flag) == 0) {
|
if(!(regs.irq.enable & regs.irq.flag)) {
|
||||||
step(16);
|
step(16);
|
||||||
} else {
|
} else {
|
||||||
regs.mode = Registers::Mode::Normal;
|
regs.mode = Registers::Mode::Normal;
|
||||||
@@ -99,16 +91,19 @@ auto CPU::sync_step(uint clocks) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::keypad_run() -> void {
|
auto CPU::keypad_run() -> void {
|
||||||
if(regs.keypad.control.enable == false) return;
|
//lookup table to convert button indexes to Emulator::Interface indexes
|
||||||
|
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1, 7, 6};
|
||||||
|
|
||||||
|
if(!regs.keypad.control.enable) return;
|
||||||
|
|
||||||
bool test = regs.keypad.control.condition; //0 = OR, 1 = AND
|
bool test = regs.keypad.control.condition; //0 = OR, 1 = AND
|
||||||
for(auto n : range(10)) {
|
for(auto n : range(10)) {
|
||||||
if(regs.keypad.control.flag[n] == false) continue;
|
if(!regs.keypad.control.flag[n]) continue;
|
||||||
bool input = interface->inputPoll(0, 0, n);
|
bool input = interface->inputPoll(0, 0, lookup[n]);
|
||||||
if(regs.keypad.control.condition == 0) test |= input;
|
if(regs.keypad.control.condition == 0) test |= input;
|
||||||
if(regs.keypad.control.condition == 1) test &= input;
|
if(regs.keypad.control.condition == 1) test &= input;
|
||||||
}
|
}
|
||||||
if(test) regs.irq.flag.keypad = true;
|
if(test) regs.irq.flag |= Interrupt::Keypad;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::power() -> void {
|
auto CPU::power() -> void {
|
||||||
@@ -123,7 +118,14 @@ auto CPU::power() -> void {
|
|||||||
dma.target = 0;
|
dma.target = 0;
|
||||||
dma.length = 0;
|
dma.length = 0;
|
||||||
dma.data = 0;
|
dma.data = 0;
|
||||||
dma.control = 0;
|
dma.control.targetmode = 0;
|
||||||
|
dma.control.sourcemode = 0;
|
||||||
|
dma.control.repeat = 0;
|
||||||
|
dma.control.size = 0;
|
||||||
|
dma.control.drq = 0;
|
||||||
|
dma.control.timingmode = 0;
|
||||||
|
dma.control.irq = 0;
|
||||||
|
dma.control.enable = 0;
|
||||||
dma.pending = 0;
|
dma.pending = 0;
|
||||||
dma.run.target = 0;
|
dma.run.target = 0;
|
||||||
dma.run.source = 0;
|
dma.run.source = 0;
|
||||||
@@ -133,17 +135,30 @@ auto CPU::power() -> void {
|
|||||||
timer.period = 0;
|
timer.period = 0;
|
||||||
timer.reload = 0;
|
timer.reload = 0;
|
||||||
timer.pending = false;
|
timer.pending = false;
|
||||||
timer.control = 0;
|
timer.control.frequency = 0;
|
||||||
|
timer.control.cascade = 0;
|
||||||
|
timer.control.irq = 0;
|
||||||
|
timer.control.enable = 0;
|
||||||
}
|
}
|
||||||
regs.keypad.control = 0;
|
for(auto& flag : regs.keypad.control.flag) flag = 0;
|
||||||
|
regs.keypad.control.enable = 0;
|
||||||
|
regs.keypad.control.condition = 0;
|
||||||
regs.ime = 0;
|
regs.ime = 0;
|
||||||
regs.irq.enable = 0;
|
regs.irq.enable = 0;
|
||||||
regs.irq.flag = 0;
|
regs.irq.flag = 0;
|
||||||
regs.wait.control = 0;
|
for(auto& nwait : regs.wait.control.nwait) nwait = 0;
|
||||||
|
for(auto& swait : regs.wait.control.swait) swait = 0;
|
||||||
|
regs.wait.control.phi = 0;
|
||||||
|
regs.wait.control.prefetch = 0;
|
||||||
|
regs.wait.control.gametype = 0;
|
||||||
regs.postboot = 0;
|
regs.postboot = 0;
|
||||||
regs.mode = Registers::Mode::Normal;
|
regs.mode = Registers::Mode::Normal;
|
||||||
regs.clock = 0;
|
regs.clock = 0;
|
||||||
regs.memory.control = 0x0d00'0020;
|
regs.memory.control.disable = 0;
|
||||||
|
regs.memory.control.unknown1 = 0;
|
||||||
|
regs.memory.control.ewram = 1;
|
||||||
|
regs.memory.control.ewramwait = 13;
|
||||||
|
regs.memory.control.unknown2 = 0;
|
||||||
|
|
||||||
pending.dma.vblank = 0;
|
pending.dma.vblank = 0;
|
||||||
pending.dma.hblank = 0;
|
pending.dma.hblank = 0;
|
||||||
|
@@ -2,6 +2,25 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
|||||||
using ARM::read;
|
using ARM::read;
|
||||||
using ARM::write;
|
using ARM::write;
|
||||||
|
|
||||||
|
struct Interrupt {
|
||||||
|
enum : uint {
|
||||||
|
VBlank = 0x0001,
|
||||||
|
HBlank = 0x0002,
|
||||||
|
VCoincidence = 0x0004,
|
||||||
|
Timer0 = 0x0008,
|
||||||
|
Timer1 = 0x0010,
|
||||||
|
Timer2 = 0x0020,
|
||||||
|
Timer3 = 0x0040,
|
||||||
|
Serial = 0x0080,
|
||||||
|
DMA0 = 0x0100,
|
||||||
|
DMA1 = 0x0200,
|
||||||
|
DMA2 = 0x0400,
|
||||||
|
DMA3 = 0x0800,
|
||||||
|
Keypad = 0x1000,
|
||||||
|
Cartridge = 0x2000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
#include "registers.hpp"
|
#include "registers.hpp"
|
||||||
#include "prefetch.hpp"
|
#include "prefetch.hpp"
|
||||||
#include "state.hpp"
|
#include "state.hpp"
|
||||||
@@ -11,7 +30,6 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
|||||||
~CPU();
|
~CPU();
|
||||||
|
|
||||||
static auto Enter() -> void;
|
static auto Enter() -> void;
|
||||||
|
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
|
|
||||||
auto step(uint clocks) -> void override;
|
auto step(uint clocks) -> void override;
|
||||||
@@ -21,10 +39,10 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
|||||||
auto power() -> void;
|
auto power() -> void;
|
||||||
|
|
||||||
//bus.cpp
|
//bus.cpp
|
||||||
auto bus_idle() -> void override;
|
auto busIdle() -> void override;
|
||||||
auto bus_read(uint mode, uint32 addr) -> uint32 override;
|
auto busRead(uint mode, uint32 addr) -> uint32 override;
|
||||||
auto bus_write(uint mode, uint32 addr, uint32 word) -> void override;
|
auto busWrite(uint mode, uint32 addr, uint32 word) -> void override;
|
||||||
auto bus_wait(uint mode, uint32 addr) -> uint;
|
auto busWait(uint mode, uint32 addr) -> uint;
|
||||||
|
|
||||||
//mmio.cpp
|
//mmio.cpp
|
||||||
auto read(uint32 addr) -> uint8;
|
auto read(uint32 addr) -> uint8;
|
||||||
|
@@ -7,8 +7,8 @@ auto CPU::dma_run() -> void {
|
|||||||
auto& dma = regs.dma[n];
|
auto& dma = regs.dma[n];
|
||||||
if(dma.pending) {
|
if(dma.pending) {
|
||||||
dma_exec(dma);
|
dma_exec(dma);
|
||||||
if(dma.control.irq) regs.irq.flag.dma[n] = 1;
|
if(dma.control.irq) regs.irq.flag |= Interrupt::DMA0 << n;
|
||||||
if(dma.control.drq && n == 3) regs.irq.flag.cartridge = 1;
|
if(dma.control.drq && n == 3) regs.irq.flag |= Interrupt::Cartridge;
|
||||||
transferred = true;
|
transferred = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ auto CPU::dma_exec(Registers::DMA& dma) -> void {
|
|||||||
uint32 addr = dma.run.source;
|
uint32 addr = dma.run.source;
|
||||||
if(mode & Word) addr &= ~3;
|
if(mode & Word) addr &= ~3;
|
||||||
if(mode & Half) addr &= ~1;
|
if(mode & Half) addr &= ~1;
|
||||||
dma.data = bus_read(mode, addr);
|
dma.data = busRead(mode, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dma.run.target < 0x0200'0000) {
|
if(dma.run.target < 0x0200'0000) {
|
||||||
@@ -48,7 +48,7 @@ auto CPU::dma_exec(Registers::DMA& dma) -> void {
|
|||||||
uint32 addr = dma.run.target;
|
uint32 addr = dma.run.target;
|
||||||
if(mode & Word) addr &= ~3;
|
if(mode & Word) addr &= ~3;
|
||||||
if(mode & Half) addr &= ~1;
|
if(mode & Half) addr &= ~1;
|
||||||
bus_write(mode, addr, dma.data);
|
busWrite(mode, addr, dma.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(dma.control.sourcemode) {
|
switch(dma.control.sourcemode) {
|
||||||
|
@@ -1,321 +1,416 @@
|
|||||||
auto CPU::read(uint32 addr) -> uint8 {
|
auto CPU::read(uint32 addr) -> uint8 {
|
||||||
uint8 result = 0;
|
auto dma = [&]() -> Registers::DMA& { return regs.dma[addr / 12 & 3]; };
|
||||||
|
auto timer = [&]() -> Registers::Timer& { return regs.timer[addr.bits(2,3)]; };
|
||||||
|
|
||||||
switch(addr) {
|
switch(addr) {
|
||||||
|
|
||||||
//DMA0CNT_H
|
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
|
||||||
//DMA1CNT_H
|
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de: return (
|
||||||
//DMA2CNT_H
|
dma().control.targetmode << 5
|
||||||
//DMA3CNT_H
|
| dma().control.sourcemode.bit(0) << 7
|
||||||
case 0x040000ba: case 0x040000bb:
|
);
|
||||||
case 0x040000c6: case 0x040000c7:
|
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: return (
|
||||||
case 0x040000d2: case 0x040000d3:
|
dma().control.sourcemode.bit(1) << 0
|
||||||
case 0x040000de: case 0x040000df: {
|
| dma().control.repeat << 1
|
||||||
auto& dma = regs.dma[(addr - 0x040000ba) / 12];
|
| dma().control.size << 2
|
||||||
unsigned shift = (addr & 1) * 8;
|
| dma().control.drq << 3
|
||||||
return dma.control >> shift;
|
| dma().control.timingmode << 4
|
||||||
}
|
| dma().control.irq << 6
|
||||||
|
| dma().control.enable << 7
|
||||||
|
);
|
||||||
|
|
||||||
//TM0CNT_L
|
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
|
||||||
//TM1CNT_L
|
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: return timer().period.byte(0);
|
||||||
//TM2CNT_L
|
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: return timer().period.byte(1);
|
||||||
//TM3CNT_L
|
|
||||||
case 0x04000100: case 0x04000101:
|
|
||||||
case 0x04000104: case 0x04000105:
|
|
||||||
case 0x04000108: case 0x04000109:
|
|
||||||
case 0x0400010c: case 0x0400010d: {
|
|
||||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
|
||||||
unsigned shift = (addr & 1) * 8;
|
|
||||||
return timer.period >> shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TIM0CNT_H
|
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
|
||||||
case 0x04000102: case 0x04000103:
|
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: return (
|
||||||
case 0x04000106: case 0x04000107:
|
timer().control.frequency << 0
|
||||||
case 0x0400010a: case 0x0400010b:
|
| timer().control.cascade << 2
|
||||||
case 0x0400010e: case 0x0400010f: {
|
| timer().control.irq << 6
|
||||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
| timer().control.enable << 7
|
||||||
unsigned shift = (addr & 1) * 8;
|
);
|
||||||
return timer.control >> shift;
|
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f: return 0;
|
||||||
}
|
|
||||||
|
|
||||||
//SIOMULTI0 (SIODATA32_L)
|
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
|
||||||
//SIOMULTI1 (SIODATA32_H)
|
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126: {
|
||||||
//SIOMULTI2
|
if(auto data = player.read()) return data().byte(addr.bits(0,1));
|
||||||
//SIOMULTI3
|
return regs.serial.data[addr.bits(1,2)].byte(0);
|
||||||
case 0x04000120: case 0x04000121:
|
}
|
||||||
case 0x04000122: case 0x04000123:
|
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127: {
|
||||||
case 0x04000124: case 0x04000125:
|
if(auto data = player.read()) return data().byte(addr.bits(0,1));
|
||||||
case 0x04000126: case 0x04000127: {
|
return regs.serial.data[addr.bits(1,2)].byte(1);
|
||||||
if(auto data = player.read()) return data() >> ((addr & 3) << 3);
|
|
||||||
unsigned shift = (addr & 1) * 8;
|
|
||||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
|
||||||
return data >> shift;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//SIOCNT
|
//SIOCNT
|
||||||
case 0x04000128: return regs.serial.control >> 0;
|
case 0x0400'0128: return (
|
||||||
case 0x04000129: return regs.serial.control >> 8;
|
regs.serial.control.shiftclockselect << 0
|
||||||
|
| regs.serial.control.shiftclockfrequency << 1
|
||||||
|
| regs.serial.control.transferenablereceive << 2
|
||||||
|
| regs.serial.control.transferenablesend << 3
|
||||||
|
| regs.serial.control.startbit << 7
|
||||||
|
);
|
||||||
|
case 0x0400'0129: return (
|
||||||
|
regs.serial.control.transferlength << 4
|
||||||
|
| regs.serial.control.irqenable << 6
|
||||||
|
);
|
||||||
|
|
||||||
//SIOMLT_SEND (SIODATA8)
|
//SIOMLT_SEND (SIODATA8)
|
||||||
case 0x0400012a: return regs.serial.data8;
|
case 0x0400'012a: return regs.serial.data8;
|
||||||
case 0x0400012b: return 0u;
|
case 0x0400'012b: return 0;
|
||||||
|
|
||||||
//KEYINPUT
|
//KEYINPUT
|
||||||
case 0x04000130:
|
case 0x04000130: {
|
||||||
|
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1};
|
||||||
if(auto result = player.keyinput()) return result() >> 0;
|
if(auto result = player.keyinput()) return result() >> 0;
|
||||||
for(unsigned n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, n) << n;
|
uint8 result = 0;
|
||||||
if((result & 0xc0) == 0xc0) result &= ~0xc0; //up+down cannot be pressed simultaneously
|
for(uint n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, lookup[n]) << n;
|
||||||
if((result & 0x30) == 0x30) result &= ~0x30; //left+right cannot be pressed simultaneously
|
if((result & 0xc0) == 0xc0) result &= (uint8)~0xc0; //up+down cannot be pressed simultaneously
|
||||||
|
if((result & 0x30) == 0x30) result &= (uint8)~0x30; //left+right cannot be pressed simultaneously
|
||||||
return result ^ 0xff;
|
return result ^ 0xff;
|
||||||
case 0x04000131:
|
}
|
||||||
|
case 0x04000131: {
|
||||||
if(auto result = player.keyinput()) return result() >> 8;
|
if(auto result = player.keyinput()) return result() >> 8;
|
||||||
result |= interface->inputPoll(0, 0, 8) << 0;
|
uint8 result = 0;
|
||||||
result |= interface->inputPoll(0, 0, 9) << 1;
|
result |= interface->inputPoll(0, 0, 7) << 0;
|
||||||
|
result |= interface->inputPoll(0, 0, 6) << 1;
|
||||||
return result ^ 0x03;
|
return result ^ 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
//KEYCNT
|
//KEYCNT
|
||||||
case 0x04000132: return regs.keypad.control >> 0;
|
case 0x0400'0132: return (
|
||||||
case 0x04000133: return regs.keypad.control >> 8;
|
regs.keypad.control.flag[0] << 0
|
||||||
|
| regs.keypad.control.flag[1] << 1
|
||||||
|
| regs.keypad.control.flag[2] << 2
|
||||||
|
| regs.keypad.control.flag[3] << 3
|
||||||
|
| regs.keypad.control.flag[4] << 4
|
||||||
|
| regs.keypad.control.flag[5] << 5
|
||||||
|
| regs.keypad.control.flag[6] << 6
|
||||||
|
| regs.keypad.control.flag[7] << 7
|
||||||
|
);
|
||||||
|
case 0x0400'0133: return (
|
||||||
|
regs.keypad.control.flag[8] << 0
|
||||||
|
| regs.keypad.control.flag[9] << 1
|
||||||
|
| regs.keypad.control.enable << 6
|
||||||
|
| regs.keypad.control.condition << 7
|
||||||
|
);
|
||||||
|
|
||||||
//RCNT
|
//RCNT
|
||||||
case 0x04000134: return regs.joybus.settings >> 0;
|
case 0x0400'0134: return (
|
||||||
case 0x04000135: return regs.joybus.settings >> 8;
|
regs.joybus.settings.sc << 0
|
||||||
|
| regs.joybus.settings.sd << 1
|
||||||
|
| regs.joybus.settings.si << 2
|
||||||
|
| regs.joybus.settings.so << 3
|
||||||
|
| regs.joybus.settings.scmode << 4
|
||||||
|
| regs.joybus.settings.sdmode << 5
|
||||||
|
| regs.joybus.settings.simode << 6
|
||||||
|
| regs.joybus.settings.somode << 7
|
||||||
|
);
|
||||||
|
case 0x0400'0135: return (
|
||||||
|
regs.joybus.settings.irqenable << 0
|
||||||
|
| regs.joybus.settings.mode << 6
|
||||||
|
);
|
||||||
|
|
||||||
//JOYCNT
|
//JOYCNT
|
||||||
case 0x04000140: return regs.joybus.control >> 0;
|
case 0x0400'0140: return (
|
||||||
case 0x04000141: return regs.joybus.control >> 8;
|
regs.joybus.control.resetsignal << 0
|
||||||
|
| regs.joybus.control.receivecomplete << 1
|
||||||
|
| regs.joybus.control.sendcomplete << 2
|
||||||
|
| regs.joybus.control.irqenable << 6
|
||||||
|
);
|
||||||
|
case 0x0400'0141: return 0;
|
||||||
|
case 0x0400'0142: return 0;
|
||||||
|
case 0x0400'0143: return 0;
|
||||||
|
|
||||||
//JOY_RECV_L
|
//JOY_RECV_L, JOY_RECV_H
|
||||||
//JOY_RECV_H
|
case 0x0400'0150: return regs.joybus.receive.byte(0);
|
||||||
case 0x04000150: return regs.joybus.receive >> 0;
|
case 0x0400'0151: return regs.joybus.receive.byte(1);
|
||||||
case 0x04000151: return regs.joybus.receive >> 8;
|
case 0x0400'0152: return regs.joybus.receive.byte(2);
|
||||||
case 0x04000152: return regs.joybus.receive >> 16;
|
case 0x0400'0153: return regs.joybus.receive.byte(3);
|
||||||
case 0x04000153: return regs.joybus.receive >> 24;
|
|
||||||
|
|
||||||
//JOY_TRANS_L
|
//JOY_TRANS_L, JOY_TRANS_H
|
||||||
//JOY_TRANS_H
|
case 0x0400'0154: return regs.joybus.transmit.byte(0);
|
||||||
case 0x04000154: return regs.joybus.transmit >> 0;
|
case 0x0400'0155: return regs.joybus.transmit.byte(1);
|
||||||
case 0x04000155: return regs.joybus.transmit >> 8;
|
case 0x0400'0156: return regs.joybus.transmit.byte(2);
|
||||||
case 0x04000156: return regs.joybus.transmit >> 16;
|
case 0x0400'0157: return regs.joybus.transmit.byte(3);
|
||||||
case 0x04000157: return regs.joybus.transmit >> 24;
|
|
||||||
|
|
||||||
//JOYSTAT
|
//JOYSTAT
|
||||||
case 0x04000158: return regs.joybus.status >> 0;
|
case 0x0400'0158: return (
|
||||||
case 0x04000159: return regs.joybus.status >> 8;
|
regs.joybus.status.receiveflag << 1
|
||||||
|
| regs.joybus.status.sendflag << 3
|
||||||
|
| regs.joybus.status.generalflag << 4
|
||||||
|
);
|
||||||
|
case 0x0400'0159: return 0;
|
||||||
|
case 0x0400'015a: return 0;
|
||||||
|
case 0x0400'015b: return 0;
|
||||||
|
|
||||||
//IE
|
//IE
|
||||||
case 0x04000200: return regs.irq.enable >> 0;
|
case 0x0400'0200: return regs.irq.enable.byte(0);
|
||||||
case 0x04000201: return regs.irq.enable >> 8;
|
case 0x0400'0201: return regs.irq.enable.byte(1);
|
||||||
|
|
||||||
//IF
|
//IF
|
||||||
case 0x04000202: return regs.irq.flag >> 0;
|
case 0x0400'0202: return regs.irq.flag.byte(0);
|
||||||
case 0x04000203: return regs.irq.flag >> 8;
|
case 0x0400'0203: return regs.irq.flag.byte(1);
|
||||||
|
|
||||||
//WAITCNT
|
//WAITCNT
|
||||||
case 0x04000204: return regs.wait.control >> 0;
|
case 0x0400'0204: return (
|
||||||
case 0x04000205: return regs.wait.control >> 8;
|
regs.wait.control.nwait[3] << 0
|
||||||
|
| regs.wait.control.nwait[0] << 2
|
||||||
|
| regs.wait.control.swait[0] << 4
|
||||||
|
| regs.wait.control.nwait[1] << 5
|
||||||
|
| regs.wait.control.swait[1] << 7
|
||||||
|
);
|
||||||
|
case 0x0400'0205: return (
|
||||||
|
regs.wait.control.nwait[2] << 0
|
||||||
|
| regs.wait.control.swait[2] << 2
|
||||||
|
| regs.wait.control.phi << 3
|
||||||
|
| regs.wait.control.prefetch << 6
|
||||||
|
| regs.wait.control.gametype << 7
|
||||||
|
);
|
||||||
|
|
||||||
//IME
|
//IME
|
||||||
case 0x04000208: return regs.ime;
|
case 0x0400'0208: return regs.ime;
|
||||||
case 0x04000209: return 0u;
|
case 0x0400'0209: return 0;
|
||||||
|
|
||||||
//POSTFLG + HALTCNT
|
//POSTFLG + HALTCNT
|
||||||
case 0x04000300: return regs.postboot;
|
case 0x0400'0300: return regs.postboot;
|
||||||
case 0x04000301: return 0u;
|
case 0x0400'0301: return 0;
|
||||||
|
|
||||||
//MEMCNT_L
|
//MEMCNT_L
|
||||||
case 0x04000800: return regs.memory.control >> 0;
|
case 0x0400'0800: return (
|
||||||
case 0x04000801: return regs.memory.control >> 8;
|
regs.memory.control.disable << 0
|
||||||
|
| regs.memory.control.unknown1 << 1
|
||||||
|
| regs.memory.control.ewram << 5
|
||||||
|
);
|
||||||
|
case 0x0400'0801: return 0;
|
||||||
|
|
||||||
//MEMCNT_H
|
//MEMCNT_H
|
||||||
case 0x04000802: return regs.memory.control >> 16;
|
case 0x0400'0802: return 0;
|
||||||
case 0x04000803: return regs.memory.control >> 24;
|
case 0x0400'0803: return (
|
||||||
|
regs.memory.control.ewramwait << 0
|
||||||
|
| regs.memory.control.unknown2 << 4
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0u;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::write(uint32 addr, uint8 byte) -> void {
|
auto CPU::write(uint32 addr, uint8 data) -> void {
|
||||||
|
auto dma = [&]() -> Registers::DMA& { return regs.dma[addr / 12 & 3]; };
|
||||||
|
auto timer = [&]() -> Registers::Timer& { return regs.timer[addr.bits(2,3)]; };
|
||||||
|
|
||||||
switch(addr) {
|
switch(addr) {
|
||||||
|
|
||||||
//DMA0SAD
|
//DMA0SAD, DMA1SAD, DMA2SAD, DMA3SAD
|
||||||
//DMA1SAD
|
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: dma().source.byte(0) = data; return;
|
||||||
//DMA2SAD
|
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: dma().source.byte(1) = data; return;
|
||||||
//DMA3SAD
|
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: dma().source.byte(2) = data; return;
|
||||||
case 0x040000b0: case 0x040000b1: case 0x040000b2: case 0x040000b3:
|
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: dma().source.byte(3) = data; return;
|
||||||
case 0x040000bc: case 0x040000bd: case 0x040000be: case 0x040000bf:
|
|
||||||
case 0x040000c8: case 0x040000c9: case 0x040000ca: case 0x040000cb:
|
|
||||||
case 0x040000d4: case 0x040000d5: case 0x040000d6: case 0x040000d7: {
|
|
||||||
auto& dma = regs.dma[(addr - 0x040000b0) / 12];
|
|
||||||
unsigned shift = (addr & 3) * 8;
|
|
||||||
dma.source = (dma.source & ~(255 << shift)) | (byte << shift);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//DMA0DAD
|
//DMA0DAD, DMA1DAD, DMA2DAD, DMA3DAD
|
||||||
//DMA1DAD
|
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: dma().target.byte(0) = data; return;
|
||||||
//DMA2DAD
|
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: dma().target.byte(1) = data; return;
|
||||||
//DMA3DAD
|
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: dma().target.byte(2) = data; return;
|
||||||
case 0x040000b4: case 0x040000b5: case 0x040000b6: case 0x040000b7:
|
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: dma().target.byte(3) = data; return;
|
||||||
case 0x040000c0: case 0x040000c1: case 0x040000c2: case 0x040000c3:
|
|
||||||
case 0x040000cc: case 0x040000cd: case 0x040000ce: case 0x040000cf:
|
|
||||||
case 0x040000d8: case 0x040000d9: case 0x040000da: case 0x040000db: {
|
|
||||||
auto& dma = regs.dma[(addr - 0x040000b4) / 12];
|
|
||||||
unsigned shift = (addr & 3) * 8;
|
|
||||||
dma.target = (dma.target & ~(255 << shift)) | (byte << shift);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//DMA0CNT_L
|
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
|
||||||
//DMA1CNT_L
|
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: dma().length.byte(0) = data; return;
|
||||||
//DMA2CNT_L
|
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: dma().length.byte(1) = data; return;
|
||||||
//DMA3CNT_L
|
|
||||||
case 0x040000b8: case 0x040000b9:
|
|
||||||
case 0x040000c4: case 0x040000c5:
|
|
||||||
case 0x040000d0: case 0x040000d1:
|
|
||||||
case 0x040000dc: case 0x040000dd: {
|
|
||||||
auto& dma = regs.dma[(addr - 0x040000b8) / 12];
|
|
||||||
unsigned shift = (addr & 1) * 8;
|
|
||||||
dma.length = (dma.length & ~(255 << shift)) | (byte << shift);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//DMA0CNT_H
|
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
|
||||||
//DMA1CNT_H
|
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de:
|
||||||
//DMA2CNT_H
|
dma().control.targetmode = data.bits(5,6);
|
||||||
//DMA3CNT_H
|
dma().control.sourcemode.bit(0) = data.bit (7);
|
||||||
case 0x040000ba: case 0x040000bb:
|
return;
|
||||||
case 0x040000c6: case 0x040000c7:
|
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: {
|
||||||
case 0x040000d2: case 0x040000d3:
|
bool enable = dma().control.enable;
|
||||||
case 0x040000de: case 0x040000df: {
|
if(addr != 0x0400'00df) data.bit(3) = 0; //gamepad DRQ valid for DMA3 only
|
||||||
if(addr == 0x040000bb || addr == 0x040000c7 || addr == 0x040000d3) byte &= 0xf7; //gamepak DRQ valid for DMA3 only
|
|
||||||
auto& dma = regs.dma[(addr - 0x040000ba) / 12];
|
dma().control.sourcemode.bit(1) = data.bit (0);
|
||||||
unsigned shift = (addr & 1) * 8;
|
dma().control.repeat = data.bit (1);
|
||||||
bool enable = dma.control.enable;
|
dma().control.size = data.bit (2);
|
||||||
dma.control = (dma.control & ~(255 << shift)) | (byte << shift);
|
dma().control.drq = data.bit (3);
|
||||||
if(enable == 0 && dma.control.enable) {
|
dma().control.timingmode = data.bits(4,5);
|
||||||
if(dma.control.timingmode == 0) dma.pending = true; //immediate transfer mode
|
dma().control.irq = data.bit (6);
|
||||||
dma.run.target = dma.target;
|
dma().control.enable = data.bit (7);
|
||||||
dma.run.source = dma.source;
|
|
||||||
dma.run.length = dma.length;
|
if(!enable && dma().control.enable) { //0->1 transition
|
||||||
} else if(dma.control.enable == 0) {
|
if(dma().control.timingmode == 0) dma().pending = true; //immediate transfer mode
|
||||||
dma.pending = false;
|
dma().run.target = dma().target;
|
||||||
|
dma().run.source = dma().source;
|
||||||
|
dma().run.length = dma().length;
|
||||||
|
} else if(!dma().control.enable) {
|
||||||
|
dma().pending = false;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TM0CNT_L
|
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
|
||||||
//TM1CNT_L
|
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: timer().reload.byte(0) = data; return;
|
||||||
//TM2CNT_L
|
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: timer().reload.byte(1) = data; return;
|
||||||
//TM3CNT_L
|
|
||||||
case 0x04000100: case 0x04000101:
|
|
||||||
case 0x04000104: case 0x04000105:
|
|
||||||
case 0x04000108: case 0x04000109:
|
|
||||||
case 0x0400010c: case 0x0400010d: {
|
|
||||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
|
||||||
unsigned shift = (addr & 1) * 8;
|
|
||||||
timer.reload = (timer.reload & ~(255 << shift)) | (byte << shift);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TM0CNT_H
|
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
|
||||||
//TM1CNT_H
|
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: {
|
||||||
//TM2CNT_H
|
bool enable = timer().control.enable;
|
||||||
//TM3CNT_H
|
|
||||||
case 0x04000102:
|
timer().control.frequency = data.bits(0,1);
|
||||||
case 0x04000106:
|
timer().control.cascade = data.bit (2);
|
||||||
case 0x0400010a:
|
timer().control.irq = data.bit (6);
|
||||||
case 0x0400010e: {
|
timer().control.enable = data.bit (7);
|
||||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
|
||||||
bool enable = timer.control.enable;
|
if(!enable && timer().control.enable) { //0->1 transition
|
||||||
timer.control = byte;
|
timer().pending = true;
|
||||||
if(enable == 0 && timer.control.enable == 1) {
|
|
||||||
timer.pending = true;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f:
|
||||||
//SIOMULTI0 (SIODATA32_L)
|
return;
|
||||||
//SIOMULTI1 (SIODATA32_H)
|
|
||||||
//SIOMULTI2
|
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
|
||||||
//SIOMULTI3
|
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126:
|
||||||
case 0x04000120: case 0x04000121:
|
player.write(addr.bits(0,1), data);
|
||||||
case 0x04000122: case 0x04000123:
|
regs.serial.data[addr.bits(1,2)].byte(0) = data;
|
||||||
case 0x04000124: case 0x04000125:
|
return;
|
||||||
case 0x04000126: case 0x04000127: {
|
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127:
|
||||||
player.write(byte, addr & 3);
|
player.write(addr.bits(0,1), data);
|
||||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
regs.serial.data[addr.bits(1,2)].byte(1) = data;
|
||||||
unsigned shift = (addr & 1) * 8;
|
|
||||||
data = (data & ~(255 << shift)) | (byte << shift);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
//SIOCNT
|
//SIOCNT
|
||||||
case 0x04000128: regs.serial.control = (regs.serial.control & 0xff00) | (byte << 0); return;
|
case 0x0400'0128:
|
||||||
case 0x04000129: regs.serial.control = (regs.serial.control & 0x00ff) | (byte << 8); return;
|
regs.serial.control.shiftclockselect = data.bit(0);
|
||||||
|
regs.serial.control.shiftclockfrequency = data.bit(1);
|
||||||
|
regs.serial.control.transferenablereceive = data.bit(2);
|
||||||
|
regs.serial.control.transferenablesend = data.bit(3);
|
||||||
|
regs.serial.control.startbit = data.bit(7);
|
||||||
|
return;
|
||||||
|
case 0x0400'0129:
|
||||||
|
regs.serial.control.transferlength = data.bit(4);
|
||||||
|
regs.serial.control.irqenable = data.bit(6);
|
||||||
|
return;
|
||||||
|
|
||||||
//SIOMLT_SEND (SIODATA8)
|
//SIOMLT_SEND (SIODATA8)
|
||||||
case 0x0400012a: regs.serial.data8 = byte; return;
|
case 0x0400'012a: regs.serial.data8 = data; return;
|
||||||
case 0x0400012b: return;
|
case 0x0400'012b: return;
|
||||||
|
|
||||||
//KEYCNT
|
//KEYCNT
|
||||||
case 0x04000132: regs.keypad.control = (regs.keypad.control & 0xff00) | (byte << 0); return;
|
case 0x0400'0132:
|
||||||
case 0x04000133: regs.keypad.control = (regs.keypad.control & 0x00ff) | (byte << 8); return;
|
regs.keypad.control.flag[0] = data.bit(0);
|
||||||
|
regs.keypad.control.flag[1] = data.bit(1);
|
||||||
|
regs.keypad.control.flag[2] = data.bit(2);
|
||||||
|
regs.keypad.control.flag[3] = data.bit(3);
|
||||||
|
regs.keypad.control.flag[4] = data.bit(4);
|
||||||
|
regs.keypad.control.flag[5] = data.bit(5);
|
||||||
|
regs.keypad.control.flag[6] = data.bit(6);
|
||||||
|
regs.keypad.control.flag[7] = data.bit(7);
|
||||||
|
return;
|
||||||
|
case 0x0400'0133:
|
||||||
|
regs.keypad.control.flag[8] = data.bit(0);
|
||||||
|
regs.keypad.control.flag[9] = data.bit(1);
|
||||||
|
regs.keypad.control.enable = data.bit(6);
|
||||||
|
regs.keypad.control.condition = data.bit(7);
|
||||||
|
return;
|
||||||
|
|
||||||
//RCNT
|
//RCNT
|
||||||
case 0x04000134: regs.joybus.settings = (regs.joybus.settings & 0xff00) | (byte << 0); return;
|
case 0x0400'0134:
|
||||||
case 0x04000135: regs.joybus.settings = (regs.joybus.settings & 0x00ff) | (byte << 8); return;
|
regs.joybus.settings.sc = data.bit(0);
|
||||||
|
regs.joybus.settings.sd = data.bit(1);
|
||||||
|
regs.joybus.settings.si = data.bit(2);
|
||||||
|
regs.joybus.settings.so = data.bit(3);
|
||||||
|
regs.joybus.settings.scmode = data.bit(4);
|
||||||
|
regs.joybus.settings.sdmode = data.bit(5);
|
||||||
|
regs.joybus.settings.simode = data.bit(6);
|
||||||
|
regs.joybus.settings.somode = data.bit(7);
|
||||||
|
return;
|
||||||
|
case 0x0400'0135:
|
||||||
|
regs.joybus.settings.irqenable = data.bit (0);
|
||||||
|
regs.joybus.settings.mode = data.bits(6,7);
|
||||||
|
return;
|
||||||
|
|
||||||
//JOYCNT
|
//JOYCNT
|
||||||
case 0x04000140: regs.joybus.control = (regs.joybus.control & 0xff00) | (byte << 0); return;
|
case 0x0400'0140:
|
||||||
case 0x04000141: regs.joybus.control = (regs.joybus.control & 0x00ff) | (byte << 8); return;
|
regs.joybus.control.resetsignal = data.bit(0);
|
||||||
|
regs.joybus.control.receivecomplete = data.bit(1);
|
||||||
|
regs.joybus.control.sendcomplete = data.bit(2);
|
||||||
|
regs.joybus.control.irqenable = data.bit(6);
|
||||||
|
return;
|
||||||
|
case 0x0400'0141: return;
|
||||||
|
case 0x0400'0142: return;
|
||||||
|
case 0x0400'0143: return;
|
||||||
|
|
||||||
//JOY_RECV_L
|
//JOY_RECV_L
|
||||||
//JOY_RECV_H
|
//JOY_RECV_H
|
||||||
case 0x04000150: regs.joybus.receive = (regs.joybus.receive & 0xffffff00) | (byte << 0); return;
|
case 0x0400'0150: regs.joybus.receive.byte(0) = data; return;
|
||||||
case 0x04000151: regs.joybus.receive = (regs.joybus.receive & 0xffff00ff) | (byte << 8); return;
|
case 0x0400'0151: regs.joybus.receive.byte(1) = data; return;
|
||||||
case 0x04000152: regs.joybus.receive = (regs.joybus.receive & 0xff00ffff) | (byte << 16); return;
|
case 0x0400'0152: regs.joybus.receive.byte(2) = data; return;
|
||||||
case 0x04000153: regs.joybus.receive = (regs.joybus.receive & 0x00ffffff) | (byte << 24); return;
|
case 0x0400'0153: regs.joybus.receive.byte(3) = data; return;
|
||||||
|
|
||||||
//JOY_TRANS_L
|
//JOY_TRANS_L
|
||||||
//JOY_TRANS_H
|
//JOY_TRANS_H
|
||||||
case 0x04000154: regs.joybus.transmit = (regs.joybus.transmit & 0xffffff00) | (byte << 0); return;
|
case 0x0400'0154: regs.joybus.transmit.byte(0) = data; return;
|
||||||
case 0x04000155: regs.joybus.transmit = (regs.joybus.transmit & 0xffff00ff) | (byte << 8); return;
|
case 0x0400'0155: regs.joybus.transmit.byte(1) = data; return;
|
||||||
case 0x04000156: regs.joybus.transmit = (regs.joybus.transmit & 0xff00ffff) | (byte << 16); return;
|
case 0x0400'0156: regs.joybus.transmit.byte(2) = data; return;
|
||||||
case 0x04000157: regs.joybus.transmit = (regs.joybus.transmit & 0x00ffffff) | (byte << 24); return;
|
case 0x0400'0157: regs.joybus.transmit.byte(3) = data; return;
|
||||||
|
|
||||||
//JOYSTAT
|
//JOYSTAT
|
||||||
case 0x04000158: regs.joybus.status = (regs.joybus.status & 0xff00) | (byte << 0); return;
|
case 0x0400'0158:
|
||||||
case 0x04000159: regs.joybus.status = (regs.joybus.status & 0x00ff) | (byte << 8); return;
|
regs.joybus.status.receiveflag = data.bit (1);
|
||||||
|
regs.joybus.status.sendflag = data.bit (3);
|
||||||
|
regs.joybus.status.generalflag = data.bits(4,5);
|
||||||
|
return;
|
||||||
|
case 0x0400'0159: return;
|
||||||
|
|
||||||
//IE
|
//IE
|
||||||
case 0x04000200: regs.irq.enable = (regs.irq.enable & 0xff00) | (byte << 0); return;
|
case 0x0400'0200: regs.irq.enable.byte(0) = data; return;
|
||||||
case 0x04000201: regs.irq.enable = (regs.irq.enable & 0x00ff) | (byte << 8); return;
|
case 0x0400'0201: regs.irq.enable.byte(1) = data; return;
|
||||||
|
|
||||||
//IF
|
//IF
|
||||||
case 0x04000202: regs.irq.flag = regs.irq.flag & ~(byte << 0); return;
|
case 0x0400'0202: regs.irq.flag.byte(0) = regs.irq.flag.byte(0) & ~data; return;
|
||||||
case 0x04000203: regs.irq.flag = regs.irq.flag & ~(byte << 8); return;
|
case 0x0400'0203: regs.irq.flag.byte(1) = regs.irq.flag.byte(1) & ~data; return;
|
||||||
|
|
||||||
//WAITCNT
|
//WAITCNT
|
||||||
case 0x04000204: regs.wait.control = (regs.wait.control & 0xff00) | ((byte & 0xff) << 0); return;
|
case 0x0400'0204:
|
||||||
case 0x04000205: regs.wait.control = (regs.wait.control & 0x00ff) | ((byte & 0x7f) << 8); return;
|
regs.wait.control.swait[3] = data.bit (0); //todo: is this correct?
|
||||||
|
regs.wait.control.nwait[3] = data.bits(0,1);
|
||||||
|
regs.wait.control.nwait[0] = data.bits(2,3);
|
||||||
|
regs.wait.control.swait[0] = data.bit (4);
|
||||||
|
regs.wait.control.nwait[1] = data.bits(5,6);
|
||||||
|
regs.wait.control.swait[1] = data.bit (7);
|
||||||
|
return;
|
||||||
|
case 0x0400'0205:
|
||||||
|
regs.wait.control.nwait[2] = data.bits(0,1);
|
||||||
|
regs.wait.control.swait[2] = data.bit (2);
|
||||||
|
regs.wait.control.phi = data.bit (3);
|
||||||
|
regs.wait.control.prefetch = data.bit (6);
|
||||||
|
regs.wait.control.gametype = data.bit (7);
|
||||||
|
return;
|
||||||
|
|
||||||
//IME
|
//IME
|
||||||
case 0x04000208: regs.ime = byte >> 0; return;
|
case 0x0400'0208: regs.ime = data.bit(0); return;
|
||||||
case 0x04000209: return;
|
case 0x0400'0209: return;
|
||||||
|
|
||||||
//POSTFLG, HALTCNT
|
//POSTFLG, HALTCNT
|
||||||
case 0x04000300: regs.postboot |= byte >> 0; return;
|
case 0x0400'0300:
|
||||||
case 0x04000301: regs.mode = byte & 0x80 ? Registers::Mode::Stop : Registers::Mode::Halt; return;
|
if(data.bit(0)) regs.postboot = 1;
|
||||||
|
return;
|
||||||
|
case 0x0400'0301:
|
||||||
|
regs.mode = data.bit(7) ? Registers::Mode::Stop : Registers::Mode::Halt;
|
||||||
|
return;
|
||||||
|
|
||||||
//MEMCNT_L
|
//MEMCNT_L
|
||||||
//MEMCNT_H
|
//MEMCNT_H
|
||||||
case 0x04000800: regs.memory.control = (regs.memory.control & 0xffffff00) | (byte << 0); return;
|
case 0x0400'0800:
|
||||||
case 0x04000801: regs.memory.control = (regs.memory.control & 0xffff00ff) | (byte << 8); return;
|
regs.memory.control.disable = data.bit (0);
|
||||||
case 0x04000802: regs.memory.control = (regs.memory.control & 0xff00ffff) | (byte << 16); return;
|
regs.memory.control.unknown1 = data.bits(1,3);
|
||||||
case 0x04000803: regs.memory.control = (regs.memory.control & 0x00ffffff) | (byte << 24); return;
|
regs.memory.control.ewram = data.bit (5);
|
||||||
|
return;
|
||||||
|
case 0x0400'0801: return;
|
||||||
|
case 0x0400'0802: return;
|
||||||
|
case 0x0400'0803:
|
||||||
|
regs.memory.control.ewramwait = data.bits(0,3);
|
||||||
|
regs.memory.control.unknown2 = data.bits(4,7);
|
||||||
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ auto CPU::prefetch_sync(uint32 addr) -> void {
|
|||||||
|
|
||||||
prefetch.addr = addr;
|
prefetch.addr = addr;
|
||||||
prefetch.load = addr;
|
prefetch.load = addr;
|
||||||
prefetch.wait = bus_wait(Half | Nonsequential, prefetch.load);
|
prefetch.wait = busWait(Half | Nonsequential, prefetch.load);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::prefetch_step(uint clocks) -> void {
|
auto CPU::prefetch_step(uint clocks) -> void {
|
||||||
@@ -14,7 +14,7 @@ auto CPU::prefetch_step(uint clocks) -> void {
|
|||||||
if(--prefetch.wait) continue;
|
if(--prefetch.wait) continue;
|
||||||
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.read(Half, prefetch.load);
|
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.read(Half, prefetch.load);
|
||||||
prefetch.load += 2;
|
prefetch.load += 2;
|
||||||
prefetch.wait = bus_wait(Half | Sequential, prefetch.load);
|
prefetch.wait = busWait(Half | Sequential, prefetch.load);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,14 +22,14 @@ auto CPU::prefetch_wait() -> void {
|
|||||||
if(!regs.wait.control.prefetch || active.dma || prefetch.full()) return;
|
if(!regs.wait.control.prefetch || active.dma || prefetch.full()) return;
|
||||||
|
|
||||||
prefetch_step(prefetch.wait);
|
prefetch_step(prefetch.wait);
|
||||||
prefetch.wait = bus_wait(Half | Nonsequential, prefetch.load);
|
prefetch.wait = busWait(Half | Nonsequential, prefetch.load);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::prefetch_read() -> uint16 {
|
auto CPU::prefetch_read() -> uint16 {
|
||||||
if(prefetch.empty()) prefetch_step(prefetch.wait);
|
if(prefetch.empty()) prefetch_step(prefetch.wait);
|
||||||
else prefetch_step(1);
|
else prefetch_step(1);
|
||||||
|
|
||||||
if(prefetch.full()) prefetch.wait = bus_wait(Half | Sequential, prefetch.load);
|
if(prefetch.full()) prefetch.wait = busWait(Half | Sequential, prefetch.load);
|
||||||
|
|
||||||
uint16 half = prefetch.slot[prefetch.addr >> 1 & 7];
|
uint16 half = prefetch.slot[prefetch.addr >> 1 & 7];
|
||||||
prefetch.addr += 2;
|
prefetch.addr += 2;
|
||||||
|
@@ -1,244 +0,0 @@
|
|||||||
CPU::Registers::DMAControl::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(targetmode << 5)
|
|
||||||
| (sourcemode << 7)
|
|
||||||
| (repeat << 9)
|
|
||||||
| (size << 10)
|
|
||||||
| (drq << 11)
|
|
||||||
| (timingmode << 12)
|
|
||||||
| (irq << 14)
|
|
||||||
| (enable << 15)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::DMAControl::operator=(uint16 source) -> uint16 {
|
|
||||||
targetmode = source >> 5;
|
|
||||||
sourcemode = source >> 7;
|
|
||||||
repeat = source >> 9;
|
|
||||||
size = source >> 10;
|
|
||||||
drq = source >> 11;
|
|
||||||
timingmode = source >> 12;
|
|
||||||
irq = source >> 14;
|
|
||||||
enable = source >> 15;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::TimerControl::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(frequency << 0)
|
|
||||||
| (cascade << 2)
|
|
||||||
| (irq << 6)
|
|
||||||
| (enable << 7)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::TimerControl::operator=(uint16 source) -> uint16 {
|
|
||||||
frequency = source >> 0;
|
|
||||||
cascade = source >> 2;
|
|
||||||
irq = source >> 6;
|
|
||||||
enable = source >> 7;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::SerialControl::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(shiftclockselect << 0)
|
|
||||||
| (shiftclockfrequency << 1)
|
|
||||||
| (transferenablereceive << 2)
|
|
||||||
| (transferenablesend << 3)
|
|
||||||
| (startbit << 7)
|
|
||||||
| (transferlength << 12)
|
|
||||||
| (irqenable << 14)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::SerialControl::operator=(uint16 source) -> uint16 {
|
|
||||||
shiftclockselect = source >> 0;
|
|
||||||
shiftclockfrequency = source >> 1;
|
|
||||||
transferenablereceive = source >> 2;
|
|
||||||
transferenablesend = source >> 3;
|
|
||||||
startbit = source >> 7;
|
|
||||||
transferlength = source >> 12;
|
|
||||||
irqenable = source >> 14;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::KeypadControl::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(flag[0] << 0)
|
|
||||||
| (flag[1] << 1)
|
|
||||||
| (flag[2] << 2)
|
|
||||||
| (flag[3] << 3)
|
|
||||||
| (flag[4] << 4)
|
|
||||||
| (flag[5] << 5)
|
|
||||||
| (flag[6] << 6)
|
|
||||||
| (flag[7] << 7)
|
|
||||||
| (flag[8] << 8)
|
|
||||||
| (flag[9] << 9)
|
|
||||||
| (enable << 14)
|
|
||||||
| (condition << 15)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::KeypadControl::operator=(uint16 source) -> uint16 {
|
|
||||||
flag[0] = source >> 0;
|
|
||||||
flag[1] = source >> 1;
|
|
||||||
flag[2] = source >> 2;
|
|
||||||
flag[3] = source >> 3;
|
|
||||||
flag[4] = source >> 4;
|
|
||||||
flag[5] = source >> 5;
|
|
||||||
flag[6] = source >> 6;
|
|
||||||
flag[7] = source >> 7;
|
|
||||||
flag[8] = source >> 8;
|
|
||||||
flag[9] = source >> 9;
|
|
||||||
enable = source >> 14;
|
|
||||||
condition = source >> 15;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::JoybusSettings::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(sc << 0)
|
|
||||||
| (sd << 1)
|
|
||||||
| (si << 2)
|
|
||||||
| (so << 3)
|
|
||||||
| (scmode << 4)
|
|
||||||
| (sdmode << 5)
|
|
||||||
| (simode << 6)
|
|
||||||
| (somode << 7)
|
|
||||||
| (irqenable << 8)
|
|
||||||
| (mode << 14)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::JoybusSettings::operator=(uint16 source) -> uint16 {
|
|
||||||
sc = source >> 0;
|
|
||||||
sd = source >> 1;
|
|
||||||
si = source >> 2;
|
|
||||||
so = source >> 3;
|
|
||||||
scmode = source >> 4;
|
|
||||||
sdmode = source >> 5;
|
|
||||||
simode = source >> 6;
|
|
||||||
somode = source >> 7;
|
|
||||||
irqenable = source >> 8;
|
|
||||||
mode = source >> 14;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::JoybusControl::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(resetsignal << 0)
|
|
||||||
| (receivecomplete << 1)
|
|
||||||
| (sendcomplete << 2)
|
|
||||||
| (irqenable << 6)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::JoybusControl::operator=(uint16 source) -> uint16 {
|
|
||||||
resetsignal = source >> 0;
|
|
||||||
receivecomplete = source >> 1;
|
|
||||||
sendcomplete = source >> 2;
|
|
||||||
irqenable = source >> 6;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::JoybusStatus::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(receiveflag << 1)
|
|
||||||
| (sendflag << 3)
|
|
||||||
| (generalflag << 4)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::JoybusStatus::operator=(uint16 source) -> uint16 {
|
|
||||||
receiveflag = source >> 1;
|
|
||||||
sendflag = source >> 3;
|
|
||||||
generalflag = source >> 4;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::Interrupt::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(vblank << 0)
|
|
||||||
| (hblank << 1)
|
|
||||||
| (vcoincidence << 2)
|
|
||||||
| (timer[0] << 3)
|
|
||||||
| (timer[1] << 4)
|
|
||||||
| (timer[2] << 5)
|
|
||||||
| (timer[3] << 6)
|
|
||||||
| (serial << 7)
|
|
||||||
| (dma[0] << 8)
|
|
||||||
| (dma[1] << 9)
|
|
||||||
| (dma[2] << 10)
|
|
||||||
| (dma[3] << 11)
|
|
||||||
| (keypad << 12)
|
|
||||||
| (cartridge << 13)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::Interrupt::operator=(uint16 source) -> uint16 {
|
|
||||||
vblank = source >> 0;
|
|
||||||
hblank = source >> 1;
|
|
||||||
vcoincidence = source >> 2;
|
|
||||||
timer[0] = source >> 3;
|
|
||||||
timer[1] = source >> 4;
|
|
||||||
timer[2] = source >> 5;
|
|
||||||
timer[3] = source >> 6;
|
|
||||||
serial = source >> 7;
|
|
||||||
dma[0] = source >> 8;
|
|
||||||
dma[1] = source >> 9;
|
|
||||||
dma[2] = source >> 10;
|
|
||||||
dma[3] = source >> 11;
|
|
||||||
keypad = source >> 12;
|
|
||||||
cartridge = source >> 13;
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::WaitControl::operator uint16() const {
|
|
||||||
return (
|
|
||||||
(nwait[3] << 0)
|
|
||||||
| (nwait[0] << 2)
|
|
||||||
| (swait[0] << 4)
|
|
||||||
| (nwait[1] << 5)
|
|
||||||
| (swait[1] << 7)
|
|
||||||
| (nwait[2] << 8)
|
|
||||||
| (swait[2] << 10)
|
|
||||||
| (phi << 11)
|
|
||||||
| (prefetch << 14)
|
|
||||||
| (gametype << 15)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::WaitControl::operator=(uint16 source) -> uint16 {
|
|
||||||
nwait[3] = (source >> 0) & 3;
|
|
||||||
nwait[0] = (source >> 2) & 3;
|
|
||||||
swait[0] = (source >> 4) & 1;
|
|
||||||
nwait[1] = (source >> 5) & 3;
|
|
||||||
swait[1] = (source >> 7) & 1;
|
|
||||||
nwait[2] = (source >> 8) & 3;
|
|
||||||
swait[2] = (source >> 10) & 1;
|
|
||||||
phi = (source >> 11) & 3;
|
|
||||||
prefetch = (source >> 14) & 1;
|
|
||||||
gametype = (source >> 15) & 1;
|
|
||||||
swait[3] = nwait[3];
|
|
||||||
return operator uint16();
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::Registers::MemoryControl::operator uint32() const {
|
|
||||||
return (
|
|
||||||
(disable << 0)
|
|
||||||
| (unknown1 << 1)
|
|
||||||
| (ewram << 5)
|
|
||||||
| (ewramwait << 24)
|
|
||||||
| (unknown2 << 28)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto CPU::Registers::MemoryControl::operator=(uint32 source) -> uint32 {
|
|
||||||
disable = source >> 0;
|
|
||||||
unknown1 = source >> 1;
|
|
||||||
ewram = source >> 5;
|
|
||||||
ewramwait = source >> 24;
|
|
||||||
unknown2 = source >> 28;
|
|
||||||
return operator uint32();
|
|
||||||
}
|
|
@@ -1,185 +1,116 @@
|
|||||||
struct Registers {
|
struct Registers {
|
||||||
struct DMAControl {
|
|
||||||
uint2 targetmode;
|
|
||||||
uint2 sourcemode;
|
|
||||||
uint1 repeat;
|
|
||||||
uint1 size;
|
|
||||||
uint1 drq;
|
|
||||||
uint2 timingmode;
|
|
||||||
uint1 irq;
|
|
||||||
uint1 enable;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const DMAControl&) -> DMAControl& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DMA {
|
struct DMA {
|
||||||
varuint source;
|
VariadicNatural source;
|
||||||
varuint target;
|
VariadicNatural target;
|
||||||
varuint length;
|
VariadicNatural length;
|
||||||
uint32 data;
|
uint32 data;
|
||||||
DMAControl control;
|
struct Control {
|
||||||
|
uint2 targetmode;
|
||||||
|
uint2 sourcemode;
|
||||||
|
uint1 repeat;
|
||||||
|
uint1 size;
|
||||||
|
uint1 drq;
|
||||||
|
uint2 timingmode;
|
||||||
|
uint1 irq;
|
||||||
|
uint1 enable;
|
||||||
|
} control;
|
||||||
|
|
||||||
//internal
|
//internal
|
||||||
bool pending;
|
bool pending;
|
||||||
struct Run {
|
struct Run {
|
||||||
varuint target;
|
VariadicNatural target;
|
||||||
varuint source;
|
VariadicNatural source;
|
||||||
varuint length;
|
VariadicNatural length;
|
||||||
} run;
|
} run;
|
||||||
} dma[4];
|
} dma[4];
|
||||||
|
|
||||||
struct TimerControl {
|
|
||||||
uint2 frequency;
|
|
||||||
uint1 cascade;
|
|
||||||
uint1 irq;
|
|
||||||
uint1 enable;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const TimerControl&) -> TimerControl& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Timer {
|
struct Timer {
|
||||||
uint16 period;
|
uint16 period;
|
||||||
uint16 reload;
|
uint16 reload;
|
||||||
bool pending;
|
bool pending;
|
||||||
TimerControl control;
|
struct Control {
|
||||||
|
uint2 frequency;
|
||||||
|
uint1 cascade;
|
||||||
|
uint1 irq;
|
||||||
|
uint1 enable;
|
||||||
|
} control;
|
||||||
} timer[4];
|
} timer[4];
|
||||||
|
|
||||||
struct SerialControl {
|
|
||||||
uint1 shiftclockselect;
|
|
||||||
uint1 shiftclockfrequency;
|
|
||||||
uint1 transferenablereceive;
|
|
||||||
uint1 transferenablesend;
|
|
||||||
uint1 startbit;
|
|
||||||
uint1 transferlength;
|
|
||||||
uint1 irqenable;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const SerialControl&) -> SerialControl& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Serial {
|
struct Serial {
|
||||||
uint16 data[4];
|
uint16 data[4];
|
||||||
SerialControl control;
|
struct Control {
|
||||||
|
uint1 shiftclockselect;
|
||||||
|
uint1 shiftclockfrequency;
|
||||||
|
uint1 transferenablereceive;
|
||||||
|
uint1 transferenablesend;
|
||||||
|
uint1 startbit;
|
||||||
|
uint1 transferlength;
|
||||||
|
uint1 irqenable;
|
||||||
|
} control;
|
||||||
uint8 data8;
|
uint8 data8;
|
||||||
} serial;
|
} serial;
|
||||||
|
|
||||||
struct KeypadControl {
|
|
||||||
uint1 flag[10];
|
|
||||||
uint1 enable;
|
|
||||||
uint1 condition;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const KeypadControl&) -> KeypadControl& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Keypad {
|
struct Keypad {
|
||||||
KeypadControl control;
|
struct Control {
|
||||||
|
uint1 flag[10];
|
||||||
|
uint1 enable;
|
||||||
|
uint1 condition;
|
||||||
|
} control;
|
||||||
} keypad;
|
} keypad;
|
||||||
|
|
||||||
struct JoybusSettings {
|
|
||||||
uint1 sc;
|
|
||||||
uint1 sd;
|
|
||||||
uint1 si;
|
|
||||||
uint1 so;
|
|
||||||
uint1 scmode;
|
|
||||||
uint1 sdmode;
|
|
||||||
uint1 simode;
|
|
||||||
uint1 somode;
|
|
||||||
uint1 irqenable;
|
|
||||||
uint2 mode;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const JoybusSettings&) -> JoybusSettings& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct JoybusControl {
|
|
||||||
uint1 resetsignal;
|
|
||||||
uint1 receivecomplete;
|
|
||||||
uint1 sendcomplete;
|
|
||||||
uint1 irqenable;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const JoybusControl&) -> JoybusControl& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct JoybusStatus {
|
|
||||||
uint1 receiveflag;
|
|
||||||
uint1 sendflag;
|
|
||||||
uint2 generalflag;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const JoybusStatus&) -> JoybusStatus& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Joybus {
|
struct Joybus {
|
||||||
JoybusSettings settings;
|
struct Settings {
|
||||||
JoybusControl control;
|
uint1 sc;
|
||||||
|
uint1 sd;
|
||||||
|
uint1 si;
|
||||||
|
uint1 so;
|
||||||
|
uint1 scmode;
|
||||||
|
uint1 sdmode;
|
||||||
|
uint1 simode;
|
||||||
|
uint1 somode;
|
||||||
|
uint1 irqenable;
|
||||||
|
uint2 mode;
|
||||||
|
} settings;
|
||||||
|
struct Control {
|
||||||
|
uint1 resetsignal;
|
||||||
|
uint1 receivecomplete;
|
||||||
|
uint1 sendcomplete;
|
||||||
|
uint1 irqenable;
|
||||||
|
} control;
|
||||||
uint32 receive;
|
uint32 receive;
|
||||||
uint32 transmit;
|
uint32 transmit;
|
||||||
JoybusStatus status;
|
struct Status {
|
||||||
|
uint1 receiveflag;
|
||||||
|
uint1 sendflag;
|
||||||
|
uint2 generalflag;
|
||||||
|
} status;
|
||||||
} joybus;
|
} joybus;
|
||||||
|
|
||||||
uint1 ime;
|
uint1 ime;
|
||||||
|
|
||||||
struct Interrupt {
|
|
||||||
uint1 vblank;
|
|
||||||
uint1 hblank;
|
|
||||||
uint1 vcoincidence;
|
|
||||||
uint1 timer[4];
|
|
||||||
uint1 serial;
|
|
||||||
uint1 dma[4];
|
|
||||||
uint1 keypad;
|
|
||||||
uint1 cartridge;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const Interrupt&) -> Interrupt& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IRQ {
|
struct IRQ {
|
||||||
Interrupt enable;
|
uint16 enable;
|
||||||
Interrupt flag;
|
uint16 flag;
|
||||||
} irq;
|
} irq;
|
||||||
|
|
||||||
struct WaitControl {
|
|
||||||
uint2 nwait[4];
|
|
||||||
uint1 swait[4];
|
|
||||||
uint2 phi;
|
|
||||||
uint1 prefetch;
|
|
||||||
uint1 gametype;
|
|
||||||
|
|
||||||
operator uint16() const;
|
|
||||||
auto operator=(uint16 source) -> uint16;
|
|
||||||
auto operator=(const WaitControl&) -> WaitControl& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Wait {
|
struct Wait {
|
||||||
WaitControl control;
|
struct Control {
|
||||||
|
uint2 nwait[4];
|
||||||
|
uint1 swait[4];
|
||||||
|
uint2 phi;
|
||||||
|
uint1 prefetch;
|
||||||
|
uint1 gametype;
|
||||||
|
} control;
|
||||||
} wait;
|
} wait;
|
||||||
|
|
||||||
struct MemoryControl {
|
|
||||||
uint1 disable;
|
|
||||||
uint3 unknown1;
|
|
||||||
uint1 ewram;
|
|
||||||
uint4 ewramwait;
|
|
||||||
uint4 unknown2;
|
|
||||||
|
|
||||||
operator uint32() const;
|
|
||||||
auto operator=(uint32 source) -> uint32;
|
|
||||||
auto operator=(const MemoryControl&) -> MemoryControl& = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Memory {
|
struct Memory {
|
||||||
MemoryControl control;
|
struct Control {
|
||||||
|
uint1 disable;
|
||||||
|
uint3 unknown1;
|
||||||
|
uint1 ewram;
|
||||||
|
uint4 ewramwait;
|
||||||
|
uint4 unknown2;
|
||||||
|
} control;
|
||||||
} memory;
|
} memory;
|
||||||
|
|
||||||
uint1 postboot;
|
uint1 postboot;
|
||||||
|
@@ -72,23 +72,8 @@ auto CPU::serialize(serializer& s) -> void {
|
|||||||
|
|
||||||
s.integer(regs.ime);
|
s.integer(regs.ime);
|
||||||
|
|
||||||
s.integer(regs.irq.enable.vblank);
|
s.integer(regs.irq.enable);
|
||||||
s.integer(regs.irq.enable.hblank);
|
s.integer(regs.irq.flag);
|
||||||
s.integer(regs.irq.enable.vcoincidence);
|
|
||||||
for(auto& flag : regs.irq.enable.timer) s.integer(flag);
|
|
||||||
s.integer(regs.irq.enable.serial);
|
|
||||||
for(auto& flag : regs.irq.enable.dma) s.integer(flag);
|
|
||||||
s.integer(regs.irq.enable.keypad);
|
|
||||||
s.integer(regs.irq.enable.cartridge);
|
|
||||||
|
|
||||||
s.integer(regs.irq.flag.vblank);
|
|
||||||
s.integer(regs.irq.flag.hblank);
|
|
||||||
s.integer(regs.irq.flag.vcoincidence);
|
|
||||||
for(auto& flag : regs.irq.flag.timer) s.integer(flag);
|
|
||||||
s.integer(regs.irq.flag.serial);
|
|
||||||
for(auto& flag : regs.irq.flag.dma) s.integer(flag);
|
|
||||||
s.integer(regs.irq.flag.keypad);
|
|
||||||
s.integer(regs.irq.flag.cartridge);
|
|
||||||
|
|
||||||
for(auto& flag : regs.wait.control.nwait) s.integer(flag);
|
for(auto& flag : regs.wait.control.nwait) s.integer(flag);
|
||||||
for(auto& flag : regs.wait.control.swait) s.integer(flag);
|
for(auto& flag : regs.wait.control.swait) s.integer(flag);
|
||||||
|
@@ -28,7 +28,7 @@ auto CPU::timer_increment(uint n) -> void {
|
|||||||
if(++timer.period == 0) {
|
if(++timer.period == 0) {
|
||||||
timer.period = timer.reload;
|
timer.period = timer.reload;
|
||||||
|
|
||||||
if(timer.control.irq) regs.irq.flag.timer[n] = 1;
|
if(timer.control.irq) regs.irq.flag |= Interrupt::Timer0 << n;
|
||||||
|
|
||||||
if(apu.fifo[0].timer == n) timer_fifo_run(0);
|
if(apu.fifo[0].timer == n) timer_fifo_run(0);
|
||||||
if(apu.fifo[1].timer == n) timer_fifo_run(1);
|
if(apu.fifo[1].timer == n) timer_fifo_run(1);
|
||||||
|
@@ -1,22 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
//license: GPLv3
|
||||||
|
//started: 2012-03-19
|
||||||
|
|
||||||
#include <emulator/emulator.hpp>
|
#include <emulator/emulator.hpp>
|
||||||
#include <processor/arm/arm.hpp>
|
#include <processor/arm/arm.hpp>
|
||||||
|
|
||||||
namespace GameBoyAdvance {
|
namespace GameBoyAdvance {
|
||||||
namespace Info {
|
namespace Info {
|
||||||
static const string Name = "bgba";
|
static const uint SerializerVersion = 4;
|
||||||
static const uint SerializerVersion = 3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
bgba - Game Boy Advance emulator
|
|
||||||
authors: byuu, Cydrak
|
|
||||||
license: GPLv3
|
|
||||||
project started: 2012-03-19
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <libco/libco.h>
|
#include <libco/libco.h>
|
||||||
|
|
||||||
namespace GameBoyAdvance {
|
namespace GameBoyAdvance {
|
||||||
@@ -39,7 +34,7 @@ namespace GameBoyAdvance {
|
|||||||
|
|
||||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||||
if(thread) co_delete(thread);
|
if(thread) co_delete(thread);
|
||||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
thread = co_create(65'536 * sizeof(void*), entrypoint);
|
||||||
this->frequency = frequency;
|
this->frequency = frequency;
|
||||||
clock = 0;
|
clock = 0;
|
||||||
}
|
}
|
||||||
@@ -62,7 +57,6 @@ namespace GameBoyAdvance {
|
|||||||
#include <gba/cpu/cpu.hpp>
|
#include <gba/cpu/cpu.hpp>
|
||||||
#include <gba/ppu/ppu.hpp>
|
#include <gba/ppu/ppu.hpp>
|
||||||
#include <gba/apu/apu.hpp>
|
#include <gba/apu/apu.hpp>
|
||||||
#include <gba/video/video.hpp>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <gba/interface/interface.hpp>
|
#include <gba/interface/interface.hpp>
|
||||||
|
@@ -8,34 +8,35 @@ Settings settings;
|
|||||||
Interface::Interface() {
|
Interface::Interface() {
|
||||||
interface = this;
|
interface = this;
|
||||||
|
|
||||||
information.name = "Game Boy Advance";
|
information.manufacturer = "Nintendo";
|
||||||
information.width = 240;
|
information.name = "Game Boy Advance";
|
||||||
information.height = 160;
|
information.width = 240;
|
||||||
information.overscan = false;
|
information.height = 160;
|
||||||
information.aspectRatio = 1.0;
|
information.overscan = false;
|
||||||
information.resettable = false;
|
information.aspectRatio = 1.0;
|
||||||
|
information.resettable = false;
|
||||||
|
|
||||||
information.capability.states = true;
|
information.capability.states = true;
|
||||||
information.capability.cheats = false;
|
information.capability.cheats = false;
|
||||||
|
|
||||||
media.append({ID::GameBoyAdvance, "Game Boy Advance", "gba", true});
|
media.append({ID::GameBoyAdvance, "Game Boy Advance", "gba", true});
|
||||||
|
|
||||||
{ Device device{0, ID::Device, "Controller"};
|
{ Device device{0, ID::Device, "Controller"};
|
||||||
device.input.append({ 0, 0, "A" });
|
device.inputs.append({ 0, 0, "Up" });
|
||||||
device.input.append({ 1, 0, "B" });
|
device.inputs.append({ 1, 0, "Down" });
|
||||||
device.input.append({ 2, 0, "Select"});
|
device.inputs.append({ 2, 0, "Left" });
|
||||||
device.input.append({ 3, 0, "Start" });
|
device.inputs.append({ 3, 0, "Right" });
|
||||||
device.input.append({ 4, 0, "Right" });
|
device.inputs.append({ 4, 0, "B" });
|
||||||
device.input.append({ 5, 0, "Left" });
|
device.inputs.append({ 5, 0, "A" });
|
||||||
device.input.append({ 6, 0, "Up" });
|
device.inputs.append({ 6, 0, "L" });
|
||||||
device.input.append({ 7, 0, "Down" });
|
device.inputs.append({ 7, 0, "R" });
|
||||||
device.input.append({ 8, 0, "R" });
|
device.inputs.append({ 8, 0, "Select"});
|
||||||
device.input.append({ 9, 0, "L" });
|
device.inputs.append({ 9, 0, "Start" });
|
||||||
device.input.append({10, 2, "Rumble"});
|
device.inputs.append({10, 2, "Rumble"});
|
||||||
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3, 10};
|
devices.append(device);
|
||||||
this->device.append(device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
port.append({0, "Device", {device[0]}});
|
ports.append({0, "Device", {devices[0]}});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::manifest() -> string {
|
auto Interface::manifest() -> string {
|
||||||
@@ -50,12 +51,38 @@ auto Interface::videoFrequency() -> double {
|
|||||||
return 16777216.0 / (228.0 * 1232.0);
|
return 16777216.0 / (228.0 * 1232.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Interface::videoColors() -> uint32 {
|
||||||
|
return 1 << 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Interface::videoColor(uint32 color) -> uint64 {
|
||||||
|
uint R = color.bits( 0, 4);
|
||||||
|
uint G = color.bits( 5, 9);
|
||||||
|
uint B = color.bits(10,14);
|
||||||
|
|
||||||
|
uint64 r = image::normalize(R, 5, 16);
|
||||||
|
uint64 g = image::normalize(G, 5, 16);
|
||||||
|
uint64 b = image::normalize(B, 5, 16);
|
||||||
|
|
||||||
|
if(settings.colorEmulation) {
|
||||||
|
double lcdGamma = 4.0, outGamma = 2.2;
|
||||||
|
double lb = pow(B / 31.0, lcdGamma);
|
||||||
|
double lg = pow(G / 31.0, lcdGamma);
|
||||||
|
double lr = pow(R / 31.0, lcdGamma);
|
||||||
|
r = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||||
|
g = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||||
|
b = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r << 32 | g << 16 | b << 0;
|
||||||
|
}
|
||||||
|
|
||||||
auto Interface::audioFrequency() -> double {
|
auto Interface::audioFrequency() -> double {
|
||||||
return 16777216.0 / 512.0;
|
return 16777216.0 / 512.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::loaded() -> bool {
|
auto Interface::loaded() -> bool {
|
||||||
return cartridge.loaded();
|
return system.loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::group(uint id) -> uint {
|
auto Interface::group(uint id) -> uint {
|
||||||
@@ -75,7 +102,7 @@ auto Interface::group(uint id) -> uint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::load(uint id) -> void {
|
auto Interface::load(uint id) -> void {
|
||||||
cartridge.load();
|
system.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::save() -> void {
|
auto Interface::save() -> void {
|
||||||
@@ -90,7 +117,7 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::BIOS) {
|
if(id == ID::BIOS) {
|
||||||
stream.read(bios.data, min(bios.size, stream.size()));
|
stream.read((uint8_t*)bios.data, min(bios.size, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::Manifest) {
|
if(id == ID::Manifest) {
|
||||||
@@ -98,39 +125,39 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::MROM) {
|
if(id == ID::MROM) {
|
||||||
stream.read(cartridge.mrom.data, min(cartridge.mrom.size, stream.size()));
|
stream.read((uint8_t*)cartridge.mrom.data, min(cartridge.mrom.size, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::SRAM) {
|
if(id == ID::SRAM) {
|
||||||
stream.read(cartridge.sram.data, min(cartridge.sram.size, stream.size()));
|
stream.read((uint8_t*)cartridge.sram.data, min(cartridge.sram.size, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::EEPROM) {
|
if(id == ID::EEPROM) {
|
||||||
stream.read(cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
|
stream.read((uint8_t*)cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::FLASH) {
|
if(id == ID::FLASH) {
|
||||||
stream.read(cartridge.flash.data, min(cartridge.flash.size, stream.size()));
|
stream.read((uint8_t*)cartridge.flash.data, min(cartridge.flash.size, stream.size()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::save(uint id, const stream& stream) -> void {
|
auto Interface::save(uint id, const stream& stream) -> void {
|
||||||
if(id == ID::SRAM) {
|
if(id == ID::SRAM) {
|
||||||
stream.write(cartridge.sram.data, cartridge.sram.size);
|
stream.write((uint8_t*)cartridge.sram.data, cartridge.sram.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::EEPROM) {
|
if(id == ID::EEPROM) {
|
||||||
stream.write(cartridge.eeprom.data, cartridge.eeprom.size);
|
stream.write((uint8_t*)cartridge.eeprom.data, cartridge.eeprom.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id == ID::FLASH) {
|
if(id == ID::FLASH) {
|
||||||
stream.write(cartridge.flash.data, cartridge.flash.size);
|
stream.write((uint8_t*)cartridge.flash.data, cartridge.flash.size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::unload() -> void {
|
auto Interface::unload() -> void {
|
||||||
save();
|
save();
|
||||||
cartridge.unload();
|
system.unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::power() -> void {
|
auto Interface::power() -> void {
|
||||||
@@ -146,7 +173,7 @@ auto Interface::run() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::serialize() -> serializer {
|
auto Interface::serialize() -> serializer {
|
||||||
system.runtosave();
|
system.runToSave();
|
||||||
return system.serialize();
|
return system.serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +194,18 @@ auto Interface::get(const string& name) -> any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::set(const string& name, const any& value) -> bool {
|
auto Interface::set(const string& name, const any& value) -> bool {
|
||||||
if(name == "Blur Emulation" && value.is<bool>()) return settings.blurEmulation = value.get<bool>(), true;
|
if(name == "Blur Emulation" && value.is<bool>()) {
|
||||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
settings.blurEmulation = value.get<bool>();
|
||||||
|
system.configureVideoEffects();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(name == "Color Emulation" && value.is<bool>()) {
|
||||||
|
settings.colorEmulation = value.get<bool>();
|
||||||
|
system.configureVideoPalette();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,6 +28,8 @@ struct Interface : Emulator::Interface {
|
|||||||
auto manifest() -> string;
|
auto manifest() -> string;
|
||||||
auto title() -> string;
|
auto title() -> string;
|
||||||
auto videoFrequency() -> double;
|
auto videoFrequency() -> double;
|
||||||
|
auto videoColors() -> uint32;
|
||||||
|
auto videoColor(uint32 color) -> uint64;
|
||||||
auto audioFrequency() -> double;
|
auto audioFrequency() -> double;
|
||||||
|
|
||||||
auto loaded() -> bool;
|
auto loaded() -> bool;
|
||||||
@@ -50,7 +52,7 @@ struct Interface : Emulator::Interface {
|
|||||||
auto set(const string& name, const any& value) -> bool override;
|
auto set(const string& name, const any& value) -> bool override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
vector<Device> device;
|
vector<Device> devices;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
|
@@ -31,7 +31,26 @@ auto Player::frame() -> void {
|
|||||||
|
|
||||||
if(!status.enable) return;
|
if(!status.enable) return;
|
||||||
|
|
||||||
if(cpu.regs.joybus.settings == 0x0000 && cpu.regs.serial.control == 0x5088) {
|
//todo: verify which settings are actually required
|
||||||
|
//values were taken from observing GBP-compatible games
|
||||||
|
if(!cpu.regs.joybus.settings.sc
|
||||||
|
&& !cpu.regs.joybus.settings.sd
|
||||||
|
&& !cpu.regs.joybus.settings.si
|
||||||
|
&& !cpu.regs.joybus.settings.so
|
||||||
|
&& !cpu.regs.joybus.settings.scmode
|
||||||
|
&& !cpu.regs.joybus.settings.sdmode
|
||||||
|
&& !cpu.regs.joybus.settings.simode
|
||||||
|
&& !cpu.regs.joybus.settings.somode
|
||||||
|
&& !cpu.regs.joybus.settings.irqenable
|
||||||
|
&& !cpu.regs.joybus.settings.mode
|
||||||
|
&& !cpu.regs.serial.control.shiftclockselect
|
||||||
|
&& !cpu.regs.serial.control.shiftclockfrequency
|
||||||
|
&& !cpu.regs.serial.control.transferenablereceive
|
||||||
|
&& cpu.regs.serial.control.transferenablesend
|
||||||
|
&& cpu.regs.serial.control.startbit
|
||||||
|
&& cpu.regs.serial.control.transferlength
|
||||||
|
&& cpu.regs.serial.control.irqenable
|
||||||
|
) {
|
||||||
status.packet = (status.packet + 1) % 17;
|
status.packet = (status.packet + 1) % 17;
|
||||||
switch(status.packet) {
|
switch(status.packet) {
|
||||||
case 0: status.send = 0x0000494e; break;
|
case 0: status.send = 0x0000494e; break;
|
||||||
@@ -52,16 +71,16 @@ auto Player::frame() -> void {
|
|||||||
case 15: status.send = 0x30000003; break;
|
case 15: status.send = 0x30000003; break;
|
||||||
case 16: status.send = 0x30000003; break;
|
case 16: status.send = 0x30000003; break;
|
||||||
}
|
}
|
||||||
cpu.regs.irq.flag.serial = true;
|
cpu.regs.irq.flag |= CPU::Interrupt::Serial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Player::keyinput() -> maybe<uint16> {
|
auto Player::keyinput() -> maybe<uint16> {
|
||||||
if(status.logoDetected) {
|
if(status.logoDetected) {
|
||||||
switch(status.logoCounter) {
|
switch(status.logoCounter) {
|
||||||
case 0: return 0x03ff;
|
case 0: return {0x03ff};
|
||||||
case 1: return 0x03ff;
|
case 1: return {0x03ff};
|
||||||
case 2: return 0x030f;
|
case 2: return {0x030f};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -72,7 +91,7 @@ auto Player::read() -> maybe<uint32> {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Player::write(uint8 byte, uint2 addr) -> void {
|
auto Player::write(uint2 addr, uint8 byte) -> void {
|
||||||
if(!status.enable) return;
|
if(!status.enable) return;
|
||||||
|
|
||||||
uint shift = addr << 3;
|
uint shift = addr << 3;
|
||||||
|
@@ -16,7 +16,7 @@ struct Player {
|
|||||||
|
|
||||||
auto keyinput() -> maybe<uint16>;
|
auto keyinput() -> maybe<uint16>;
|
||||||
auto read() -> maybe<uint32>;
|
auto read() -> maybe<uint32>;
|
||||||
auto write(uint8 byte, uint2 addr) -> void;
|
auto write(uint2 addr, uint8 byte) -> void;
|
||||||
|
|
||||||
auto serialize(serializer& s) -> void;
|
auto serialize(serializer& s) -> void;
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user