mirror of
https://github.com/ares-emulator/ares.git
synced 2026-11-09 01:09:46 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fd379613a | ||
|
|
aabf52d678 | ||
|
|
c9ca01fe20 | ||
|
|
becf122aaa | ||
|
|
1e130d7872 | ||
|
|
c57c733d7d | ||
|
|
e41aa25887 | ||
|
|
435f7d4371 | ||
|
|
a1980fab09 | ||
|
|
ebb234ba5f | ||
|
|
2cc7fe30b4 | ||
|
|
5c3c872b78 | ||
|
|
36bf915244 | ||
|
|
045a0f7e79 | ||
|
|
a209e6ffbe | ||
|
|
3bf672dd97 | ||
|
|
157ddf3e8f | ||
|
|
ea23bf53ae | ||
|
|
d4598e1d01 | ||
|
|
f9a8564af0 | ||
|
|
6d66b1136d | ||
|
|
b01f18c34c | ||
|
|
1ebdb69516 | ||
|
|
add0f74387 | ||
|
|
b20f70f333 | ||
|
|
9aebf7bc6b | ||
|
|
a7bf219d5d | ||
|
|
04118be59a | ||
|
|
f24d17859f | ||
|
|
35fd80bde7 | ||
|
|
ccf1c00b58 | ||
|
|
f4520d41ec | ||
|
|
e308cf4275 | ||
|
|
192e53bb87 | ||
|
|
0ed9edfcdb | ||
|
|
764fe1974a | ||
|
|
a55d640459 | ||
|
|
6010bffe5d | ||
|
|
e492268025 | ||
|
|
a36c26c997 | ||
|
|
a3945e5772 | ||
|
|
6b6233b3af | ||
|
|
9f63cb1b99 | ||
|
|
49c39e0e4d | ||
|
|
7dec0b2a3c | ||
|
|
f288280ceb | ||
|
|
c6c5f4669c | ||
|
|
397b9c4505 | ||
|
|
7e2cfb6d40 | ||
|
|
970dcea0ac | ||
|
|
402c146a53 | ||
|
|
a471c150c9 | ||
|
|
ea38ea2537 | ||
|
|
09b326ae86 | ||
|
|
a60f667b25 | ||
|
|
6f7cf1bd31 | ||
|
|
8751bc433f | ||
|
|
afbee40bc0 | ||
|
|
81e9be64d2 | ||
|
|
0cd4972d11 | ||
|
|
938609818f | ||
|
|
487491e6f2 | ||
|
|
a6ccc7e5d2 | ||
|
|
39dd39cc09 | ||
|
|
1a017ff697 | ||
|
|
591adfcb25 |
No files matched your search
@@ -1,66 +0,0 @@
|
||||
include ../nall/GNUmakefile
|
||||
|
||||
fc := fc
|
||||
sfc := sfc
|
||||
gb := gb
|
||||
gba := gba
|
||||
|
||||
profile := accuracy
|
||||
target := tomoko
|
||||
# console := true
|
||||
|
||||
flags += -I. -I.. -O3
|
||||
objects := libco
|
||||
|
||||
# profile-guided optimization mode
|
||||
# pgo := instrument
|
||||
# pgo := optimize
|
||||
|
||||
ifeq ($(pgo),instrument)
|
||||
flags += -fprofile-generate
|
||||
link += -lgcov
|
||||
else ifeq ($(pgo),optimize)
|
||||
flags += -fprofile-use
|
||||
endif
|
||||
|
||||
# platform
|
||||
ifeq ($(platform),windows)
|
||||
ifeq ($(console),true)
|
||||
link += -mconsole
|
||||
else
|
||||
link += -mwindows
|
||||
endif
|
||||
link += -mthreads -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
link += -Wl,-enable-auto-import
|
||||
link += -Wl,-enable-runtime-pseudo-reloc
|
||||
else ifeq ($(platform),macosx)
|
||||
flags += -march=native
|
||||
else ifneq ($(filter $(platform),linux bsd),)
|
||||
flags += -march=native -fopenmp
|
||||
link += -fopenmp
|
||||
link += -Wl,-export-dynamic
|
||||
link += -lX11 -lXext
|
||||
else
|
||||
$(error "unsupported platform")
|
||||
endif
|
||||
|
||||
compile = \
|
||||
$(strip \
|
||||
$(if $(filter %.c,$<), \
|
||||
$(compiler) $(cflags) $(flags) $1 -c $< -o $@, \
|
||||
$(if $(filter %.cpp,$<), \
|
||||
$(compiler) $(cppflags) $(flags) $1 -c $< -o $@ \
|
||||
) \
|
||||
) \
|
||||
)
|
||||
|
||||
%.o: $<; $(call compile)
|
||||
|
||||
all: build;
|
||||
|
||||
ui := target-$(target)
|
||||
include $(ui)/GNUmakefile
|
||||
|
||||
clean:
|
||||
-@$(call delete,out/*)
|
||||
-@$(call delete,obj/*)
|
||||
-87497
File diff suppressed because it is too large.
Load diff
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="higan" version="1.0.0.0" processorArchitecture="*"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
@@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=higan
|
||||
Comment=Nintendo emulator
|
||||
Exec=higan
|
||||
Icon=higan
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Game;Emulator;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.byuu.higan</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>higan</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>higan</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>higan.icns</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,2 +0,0 @@
|
||||
1 24 "higan.Manifest"
|
||||
2 ICON DISCARDABLE "higan.ico"
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/dsp.hpp>
|
||||
using namespace nall;
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "097";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
||||
#if defined(PROFILE_ACCURACY)
|
||||
static const string Profile = "Accuracy";
|
||||
#elif defined(PROFILE_BALANCED)
|
||||
static const string Profile = "Balanced";
|
||||
#elif defined(PROFILE_PERFORMANCE)
|
||||
static const string Profile = "Performance";
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "interface.hpp"
|
||||
|
||||
//debugging function hook:
|
||||
//no overhead (and no debugger invocation) if not compiled with -DDEBUGGER
|
||||
//wraps testing of function to allow invocation without a defined callback
|
||||
template<typename T> struct hook;
|
||||
template<typename R, typename... P> struct hook<auto (P...) -> R> {
|
||||
function<auto (P...) -> R> callback;
|
||||
|
||||
auto operator()(P... p) const -> R {
|
||||
#if defined(DEBUGGER)
|
||||
if(callback) return callback(forward<P>(p)...);
|
||||
#endif
|
||||
return R();
|
||||
}
|
||||
|
||||
hook() {}
|
||||
hook(const hook& hook) { callback = hook.callback; }
|
||||
hook(void* function) { callback = function; }
|
||||
hook(auto (*function)(P...) -> R) { callback = function; }
|
||||
template<typename C> hook(auto (C::*function)(P...) -> R, C* object) { callback = {function, object}; }
|
||||
template<typename C> hook(auto (C::*function)(P...) const -> R, C* object) { callback = {function, object}; }
|
||||
template<typename L> hook(const L& function) { callback = function; }
|
||||
|
||||
auto operator=(const hook& source) -> hook& { callback = source.callback; return *this; }
|
||||
};
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#define privileged public
|
||||
#else
|
||||
#define privileged private
|
||||
#endif
|
||||
|
||||
using varuint = varuint_t<uint>;
|
||||
@@ -1,113 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Interface {
|
||||
struct Information {
|
||||
string name;
|
||||
uint width;
|
||||
uint height;
|
||||
bool overscan;
|
||||
double aspectRatio;
|
||||
bool resettable;
|
||||
struct Capability {
|
||||
bool states;
|
||||
bool cheats;
|
||||
} capability;
|
||||
} information;
|
||||
|
||||
struct Media {
|
||||
uint id;
|
||||
string name;
|
||||
string type;
|
||||
bool bootable; //false for cartridge slots (eg Sufami Turbo cartridges)
|
||||
};
|
||||
vector<Media> media;
|
||||
|
||||
struct Device {
|
||||
uint id;
|
||||
uint portmask;
|
||||
string name;
|
||||
struct Input {
|
||||
uint id;
|
||||
uint type; //0 = digital, 1 = analog (relative), 2 = rumble
|
||||
string name;
|
||||
uintptr guid; //user data field
|
||||
};
|
||||
vector<Input> input;
|
||||
vector<uint> order;
|
||||
};
|
||||
|
||||
struct Port {
|
||||
uint id;
|
||||
string name;
|
||||
vector<Device> device;
|
||||
};
|
||||
vector<Port> port;
|
||||
|
||||
struct Bind {
|
||||
virtual auto loadRequest(uint, string, string, bool) -> void {}
|
||||
virtual auto loadRequest(uint, string, bool) -> void {}
|
||||
virtual auto saveRequest(uint, string) -> void {}
|
||||
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
|
||||
virtual auto audioSample(int16, int16) -> void {}
|
||||
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
|
||||
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
|
||||
virtual auto dipSettings(const Markup::Node&) -> uint { return 0; }
|
||||
virtual auto path(uint) -> string { return ""; }
|
||||
virtual auto notify(string text) -> void { print(text, "\n"); }
|
||||
};
|
||||
Bind* bind = nullptr;
|
||||
|
||||
//callback bindings (provided by user interface)
|
||||
auto loadRequest(uint id, string name, string type, bool required) -> void { return bind->loadRequest(id, name, type, required); }
|
||||
auto loadRequest(uint id, string path, bool required) -> void { return bind->loadRequest(id, path, required); }
|
||||
auto saveRequest(uint id, string path) -> void { return bind->saveRequest(id, path); }
|
||||
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
|
||||
auto audioSample(int16 lsample, int16 rsample) -> void { return bind->audioSample(lsample, rsample); }
|
||||
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
|
||||
auto inputRumble(uint port, uint device, uint input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); }
|
||||
auto dipSettings(const Markup::Node& node) -> uint { return bind->dipSettings(node); }
|
||||
auto path(uint group) -> string { return bind->path(group); }
|
||||
template<typename... P> auto notify(P&&... p) -> void { return bind->notify({forward<P>(p)...}); }
|
||||
|
||||
//information
|
||||
virtual auto manifest() -> string = 0;
|
||||
virtual auto title() -> string = 0;
|
||||
virtual auto videoFrequency() -> double = 0;
|
||||
virtual auto audioFrequency() -> double = 0;
|
||||
|
||||
//media interface
|
||||
virtual auto loaded() -> bool { return false; }
|
||||
virtual auto sha256() -> string { return ""; }
|
||||
virtual auto group(uint id) -> uint = 0;
|
||||
virtual auto load(uint id) -> void {}
|
||||
virtual auto save() -> void {}
|
||||
virtual auto load(uint id, const stream& memory) -> void {}
|
||||
virtual auto save(uint id, const stream& memory) -> void {}
|
||||
virtual auto unload() -> void {}
|
||||
|
||||
//system interface
|
||||
virtual auto connect(uint port, uint device) -> void {}
|
||||
virtual auto power() -> void {}
|
||||
virtual auto reset() -> void {}
|
||||
virtual auto run() -> void {}
|
||||
|
||||
//time functions
|
||||
virtual auto rtc() -> bool { return false; }
|
||||
virtual auto rtcsync() -> void {}
|
||||
|
||||
//state functions
|
||||
virtual auto serialize() -> serializer = 0;
|
||||
virtual auto unserialize(serializer&) -> bool = 0;
|
||||
|
||||
//cheat functions
|
||||
virtual auto cheatSet(const lstring& = lstring{}) -> void {}
|
||||
|
||||
//settings
|
||||
virtual auto cap(const string& name) -> bool { return false; }
|
||||
virtual auto get(const string& name) -> any { return {}; }
|
||||
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
fc_objects := fc-interface fc-system fc-scheduler fc-input
|
||||
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/)
|
||||
obj/fc-system.o: $(fc)/system/system.cpp $(call rwildcard,$(fc)/system/)
|
||||
obj/fc-scheduler.o: $(fc)/scheduler/scheduler.cpp $(call rwildcard,$(fc)/scheduler/)
|
||||
obj/fc-input.o: $(fc)/input/input.cpp $(call rwildcard,$(fc)/input/)
|
||||
obj/fc-memory.o: $(fc)/memory/memory.cpp $(call rwildcard,$(fc)/memory/)
|
||||
obj/fc-cartridge.o: $(fc)/cartridge/cartridge.cpp $(call rwildcard,$(fc)/cartridge/)
|
||||
obj/fc-cpu.o: $(fc)/cpu/cpu.cpp $(call rwildcard,$(fc)/cpu/)
|
||||
obj/fc-apu.o: $(fc)/apu/apu.cpp $(call rwildcard,$(fc)/apu/)
|
||||
obj/fc-ppu.o: $(fc)/ppu/ppu.cpp $(call rwildcard,$(fc)/ppu/)
|
||||
obj/fc-cheat.o: $(fc)/cheat/cheat.cpp $(call rwildcard,$(fc)/cheat/)
|
||||
obj/fc-video.o: $(fc)/video/video.cpp $(call rwildcard,$(fc)/video/)
|
||||
@@ -1,329 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "envelope.cpp"
|
||||
#include "sweep.cpp"
|
||||
#include "pulse.cpp"
|
||||
#include "triangle.cpp"
|
||||
#include "noise.cpp"
|
||||
#include "dmc.cpp"
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
APU::APU() {
|
||||
for(uint amp : range(32)) {
|
||||
if(amp == 0) {
|
||||
pulse_dac[amp] = 0;
|
||||
} else {
|
||||
pulse_dac[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
for(uint dmc_amp : range(128)) {
|
||||
for(uint triangle_amp : range(16)) {
|
||||
for(uint noise_amp : range(16)) {
|
||||
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
|
||||
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp] = 0;
|
||||
} else {
|
||||
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp]
|
||||
= 16384.0 * 159.79 / (100.0 + 1.0 / (triangle_amp / 8227.0 + noise_amp / 12241.0 + dmc_amp / 22638.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Main() -> void {
|
||||
apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
while(true) {
|
||||
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();
|
||||
|
||||
clock_frame_counter_divider();
|
||||
|
||||
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);
|
||||
|
||||
interface->audioSample(output, output);
|
||||
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::tick() -> void {
|
||||
clock += 12;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto APU::set_irq_line() -> void {
|
||||
cpu.set_irq_apu_line(frame.irq_pending || dmc.irq_pending);
|
||||
}
|
||||
|
||||
auto APU::set_sample(int16 sample) -> void {
|
||||
cartridge_sample = sample;
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
filter.hipass_strong = 0;
|
||||
filter.hipass_weak = 0;
|
||||
filter.lopass = 0;
|
||||
|
||||
pulse[0].power();
|
||||
pulse[1].power();
|
||||
triangle.power();
|
||||
noise.power();
|
||||
dmc.power();
|
||||
}
|
||||
|
||||
auto APU::reset() -> void {
|
||||
create(APU::Main, 21477272);
|
||||
|
||||
pulse[0].reset();
|
||||
pulse[1].reset();
|
||||
triangle.reset();
|
||||
noise.reset();
|
||||
dmc.reset();
|
||||
|
||||
frame.irq_pending = 0;
|
||||
|
||||
frame.mode = 0;
|
||||
frame.counter = 0;
|
||||
frame.divider = 1;
|
||||
|
||||
enabled_channels = 0;
|
||||
cartridge_sample = 0;
|
||||
|
||||
set_irq_line();
|
||||
}
|
||||
|
||||
auto APU::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0x4015) {
|
||||
uint8 result = 0x00;
|
||||
result |= pulse[0].length_counter ? 0x01 : 0;
|
||||
result |= pulse[1].length_counter ? 0x02 : 0;
|
||||
result |= triangle.length_counter ? 0x04 : 0;
|
||||
result |= noise.length_counter ? 0x08 : 0;
|
||||
result |= dmc.length_counter ? 0x10 : 0;
|
||||
result |= frame.irq_pending ? 0x40 : 0;
|
||||
result |= dmc.irq_pending ? 0x80 : 0;
|
||||
|
||||
frame.irq_pending = false;
|
||||
set_irq_line();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto APU::write(uint16 addr, uint8 data) -> void {
|
||||
const uint n = (addr >> 2) & 1; //pulse#
|
||||
|
||||
switch(addr) {
|
||||
case 0x4000: case 0x4004:
|
||||
pulse[n].duty = data >> 6;
|
||||
pulse[n].envelope.loop_mode = data & 0x20;
|
||||
pulse[n].envelope.use_speed_as_volume = data & 0x10;
|
||||
pulse[n].envelope.speed = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x4001: case 0x4005:
|
||||
pulse[n].sweep.enable = data & 0x80;
|
||||
pulse[n].sweep.period = (data & 0x70) >> 4;
|
||||
pulse[n].sweep.decrement = data & 0x08;
|
||||
pulse[n].sweep.shift = data & 0x07;
|
||||
pulse[n].sweep.reload = true;
|
||||
break;
|
||||
|
||||
case 0x4002: case 0x4006:
|
||||
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
|
||||
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x0700) | (data << 0);
|
||||
break;
|
||||
|
||||
case 0x4003: case 0x4007:
|
||||
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
|
||||
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x00ff) | (data << 8);
|
||||
|
||||
pulse[n].duty_counter = 7;
|
||||
pulse[n].envelope.reload_decay = true;
|
||||
|
||||
if(enabled_channels & (1 << n)) {
|
||||
pulse[n].length_counter = length_counter_table[(data >> 3) & 0x1f];
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x4008:
|
||||
triangle.halt_length_counter = data & 0x80;
|
||||
triangle.linear_length = data & 0x7f;
|
||||
break;
|
||||
|
||||
case 0x400a:
|
||||
triangle.period = (triangle.period & 0x0700) | (data << 0);
|
||||
break;
|
||||
|
||||
case 0x400b:
|
||||
triangle.period = (triangle.period & 0x00ff) | (data << 8);
|
||||
|
||||
triangle.reload_linear = true;
|
||||
|
||||
if(enabled_channels & (1 << 2)) {
|
||||
triangle.length_counter = length_counter_table[(data >> 3) & 0x1f];
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x400c:
|
||||
noise.envelope.loop_mode = data & 0x20;
|
||||
noise.envelope.use_speed_as_volume = data & 0x10;
|
||||
noise.envelope.speed = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x400e:
|
||||
noise.short_mode = data & 0x80;
|
||||
noise.period = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x400f:
|
||||
noise.envelope.reload_decay = true;
|
||||
|
||||
if(enabled_channels & (1 << 3)) {
|
||||
noise.length_counter = length_counter_table[(data >> 3) & 0x1f];
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x4010:
|
||||
dmc.irq_enable = data & 0x80;
|
||||
dmc.loop_mode = data & 0x40;
|
||||
dmc.period = data & 0x0f;
|
||||
|
||||
dmc.irq_pending = dmc.irq_pending && dmc.irq_enable && !dmc.loop_mode;
|
||||
set_irq_line();
|
||||
break;
|
||||
|
||||
case 0x4011:
|
||||
dmc.dac_latch = data & 0x7f;
|
||||
break;
|
||||
|
||||
case 0x4012:
|
||||
dmc.addr_latch = data;
|
||||
break;
|
||||
|
||||
case 0x4013:
|
||||
dmc.length_latch = data;
|
||||
break;
|
||||
|
||||
case 0x4015:
|
||||
if((data & 0x01) == 0) pulse[0].length_counter = 0;
|
||||
if((data & 0x02) == 0) pulse[1].length_counter = 0;
|
||||
if((data & 0x04) == 0) triangle.length_counter = 0;
|
||||
if((data & 0x08) == 0) noise.length_counter = 0;
|
||||
|
||||
(data & 0x10) ? dmc.start() : dmc.stop();
|
||||
dmc.irq_pending = false;
|
||||
|
||||
set_irq_line();
|
||||
enabled_channels = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x4017:
|
||||
frame.mode = data >> 6;
|
||||
|
||||
frame.counter = 0;
|
||||
if(frame.mode & 2) clock_frame_counter();
|
||||
if(frame.mode & 1) {
|
||||
frame.irq_pending = false;
|
||||
set_irq_line();
|
||||
}
|
||||
frame.divider = FrameCounter::NtscPeriod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Filter::run_hipass_strong(int sample) -> int {
|
||||
hipass_strong += ((((int64)sample << 16) - (hipass_strong >> 16)) * HiPassStrong) >> 16;
|
||||
return sample - (hipass_strong >> 32);
|
||||
}
|
||||
|
||||
auto APU::Filter::run_hipass_weak(int sample) -> int {
|
||||
hipass_weak += ((((int64)sample << 16) - (hipass_weak >> 16)) * HiPassWeak) >> 16;
|
||||
return sample - (hipass_weak >> 32);
|
||||
}
|
||||
|
||||
auto APU::Filter::run_lopass(int sample) -> int {
|
||||
lopass += ((((int64)sample << 16) - (lopass >> 16)) * LoPass) >> 16;
|
||||
return (lopass >> 32);
|
||||
}
|
||||
|
||||
auto APU::clock_frame_counter() -> void {
|
||||
frame.counter++;
|
||||
|
||||
if(frame.counter & 1) {
|
||||
pulse[0].clock_length();
|
||||
pulse[0].sweep.clock(0);
|
||||
pulse[1].clock_length();
|
||||
pulse[1].sweep.clock(1);
|
||||
triangle.clock_length();
|
||||
noise.clock_length();
|
||||
}
|
||||
|
||||
pulse[0].envelope.clock();
|
||||
pulse[1].envelope.clock();
|
||||
triangle.clock_linear_length();
|
||||
noise.envelope.clock();
|
||||
|
||||
if(frame.counter == 0) {
|
||||
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
|
||||
if(frame.mode == 0) {
|
||||
frame.irq_pending = true;
|
||||
set_irq_line();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::clock_frame_counter_divider() -> void {
|
||||
frame.divider -= 2;
|
||||
if(frame.divider <= 0) {
|
||||
clock_frame_counter();
|
||||
frame.divider += FrameCounter::NtscPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8 APU::length_counter_table[32] = {
|
||||
0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e,
|
||||
0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e,
|
||||
};
|
||||
|
||||
const uint16 APU::ntsc_noise_period_table[16] = {
|
||||
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
|
||||
};
|
||||
|
||||
const uint16 APU::pal_noise_period_table[16] = {
|
||||
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
|
||||
};
|
||||
|
||||
const uint16 APU::ntsc_dmc_period_table[16] = {
|
||||
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
|
||||
};
|
||||
|
||||
const uint16 APU::pal_dmc_period_table[16] = {
|
||||
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
struct APU : Thread {
|
||||
APU();
|
||||
|
||||
static auto Main() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
auto set_irq_line() -> void;
|
||||
auto set_sample(int16 sample) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Filter {
|
||||
auto run_hipass_strong(int sample) -> int;
|
||||
auto run_hipass_weak(int sample) -> int;
|
||||
auto run_lopass(int sample) -> int;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
enum : int { HiPassStrong = 225574, HiPassWeak = 57593, LoPass = 86322413 };
|
||||
|
||||
int64 hipass_strong;
|
||||
int64 hipass_weak;
|
||||
int64 lopass;
|
||||
};
|
||||
|
||||
#include "envelope.hpp"
|
||||
#include "sweep.hpp"
|
||||
#include "pulse.hpp"
|
||||
#include "triangle.hpp"
|
||||
#include "noise.hpp"
|
||||
#include "dmc.hpp"
|
||||
|
||||
struct FrameCounter {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
enum : uint { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
|
||||
|
||||
bool irq_pending;
|
||||
|
||||
uint2 mode;
|
||||
uint2 counter;
|
||||
int divider;
|
||||
};
|
||||
|
||||
auto clock_frame_counter() -> void;
|
||||
auto clock_frame_counter_divider() -> void;
|
||||
|
||||
Filter filter;
|
||||
FrameCounter frame;
|
||||
|
||||
uint8 enabled_channels;
|
||||
int16 cartridge_sample;
|
||||
|
||||
int16 pulse_dac[32];
|
||||
int16 dmc_triangle_noise_dac[128][16][16];
|
||||
|
||||
static const uint8 length_counter_table[32];
|
||||
static const uint16 ntsc_dmc_period_table[16];
|
||||
static const uint16 pal_dmc_period_table[16];
|
||||
static const uint16 ntsc_noise_period_table[16];
|
||||
static const uint16 pal_noise_period_table[16];
|
||||
};
|
||||
|
||||
extern APU apu;
|
||||
@@ -1,117 +0,0 @@
|
||||
auto APU::DMC::start() -> void {
|
||||
if(length_counter == 0) {
|
||||
read_addr = 0x4000 + (addr_latch << 6);
|
||||
length_counter = (length_latch << 4) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::DMC::stop() -> void {
|
||||
length_counter = 0;
|
||||
dma_delay_counter = 0;
|
||||
cpu.set_rdy_line(1);
|
||||
cpu.set_rdy_addr(false);
|
||||
}
|
||||
|
||||
auto APU::DMC::clock() -> uint8 {
|
||||
uint8 result = dac_latch;
|
||||
|
||||
if(dma_delay_counter > 0) {
|
||||
dma_delay_counter--;
|
||||
|
||||
if(dma_delay_counter == 1) {
|
||||
cpu.set_rdy_addr(true, 0x8000 | read_addr);
|
||||
} else if(dma_delay_counter == 0) {
|
||||
cpu.set_rdy_line(1);
|
||||
cpu.set_rdy_addr(false);
|
||||
|
||||
dma_buffer = cpu.mdr();
|
||||
have_dma_buffer = true;
|
||||
length_counter--;
|
||||
read_addr++;
|
||||
|
||||
if(length_counter == 0) {
|
||||
if(loop_mode) {
|
||||
start();
|
||||
} else if(irq_enable) {
|
||||
irq_pending = true;
|
||||
apu.set_irq_line();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(--period_counter == 0) {
|
||||
if(have_sample) {
|
||||
int delta = (((sample >> bit_counter) & 1) << 2) - 2;
|
||||
uint data = dac_latch + delta;
|
||||
if((data & 0x80) == 0) dac_latch = data;
|
||||
}
|
||||
|
||||
if(++bit_counter == 0) {
|
||||
if(have_dma_buffer) {
|
||||
have_sample = true;
|
||||
sample = dma_buffer;
|
||||
have_dma_buffer = false;
|
||||
} else {
|
||||
have_sample = false;
|
||||
}
|
||||
}
|
||||
|
||||
period_counter = ntsc_dmc_period_table[period];
|
||||
}
|
||||
|
||||
if(length_counter > 0 && have_dma_buffer == false && dma_delay_counter == 0) {
|
||||
cpu.set_rdy_line(0);
|
||||
dma_delay_counter = 4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::DMC::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::DMC::reset() -> void {
|
||||
length_counter = 0;
|
||||
irq_pending = 0;
|
||||
|
||||
period = 0;
|
||||
period_counter = ntsc_dmc_period_table[0];
|
||||
irq_enable = 0;
|
||||
loop_mode = 0;
|
||||
dac_latch = 0;
|
||||
addr_latch = 0;
|
||||
length_latch = 0;
|
||||
read_addr = 0;
|
||||
dma_delay_counter = 0;
|
||||
bit_counter = 0;
|
||||
have_dma_buffer = 0;
|
||||
dma_buffer = 0;
|
||||
have_sample = 0;
|
||||
sample = 0;
|
||||
}
|
||||
|
||||
auto APU::DMC::serialize(serializer& s) -> void {
|
||||
s.integer(length_counter);
|
||||
s.integer(irq_pending);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(period_counter);
|
||||
|
||||
s.integer(irq_enable);
|
||||
s.integer(loop_mode);
|
||||
|
||||
s.integer(dac_latch);
|
||||
s.integer(addr_latch);
|
||||
s.integer(length_latch);
|
||||
|
||||
s.integer(read_addr);
|
||||
s.integer(dma_delay_counter);
|
||||
|
||||
s.integer(bit_counter);
|
||||
s.integer(have_dma_buffer);
|
||||
s.integer(dma_buffer);
|
||||
|
||||
s.integer(have_sample);
|
||||
s.integer(sample);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
struct DMC {
|
||||
auto start() -> void;
|
||||
auto stop() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint length_counter;
|
||||
bool irq_pending;
|
||||
|
||||
uint4 period;
|
||||
uint period_counter;
|
||||
|
||||
bool irq_enable;
|
||||
bool loop_mode;
|
||||
|
||||
uint8 dac_latch;
|
||||
uint8 addr_latch;
|
||||
uint8 length_latch;
|
||||
|
||||
uint15 read_addr;
|
||||
uint dma_delay_counter;
|
||||
|
||||
uint3 bit_counter;
|
||||
bool have_dma_buffer;
|
||||
uint8 dma_buffer;
|
||||
|
||||
bool have_sample;
|
||||
uint8 sample;
|
||||
} dmc;
|
||||
@@ -1,39 +0,0 @@
|
||||
auto APU::Envelope::volume() const -> uint {
|
||||
return use_speed_as_volume ? speed : decay_volume;
|
||||
}
|
||||
|
||||
auto APU::Envelope::clock() -> void {
|
||||
if(reload_decay) {
|
||||
reload_decay = false;
|
||||
decay_volume = 0x0f;
|
||||
decay_counter = speed + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if(--decay_counter == 0) {
|
||||
decay_counter = speed + 1;
|
||||
if(decay_volume || loop_mode) decay_volume--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Envelope::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::Envelope::reset() -> void {
|
||||
speed = 0;
|
||||
use_speed_as_volume = 0;
|
||||
loop_mode = 0;
|
||||
reload_decay = 0;
|
||||
decay_counter = 0;
|
||||
decay_volume = 0;
|
||||
}
|
||||
|
||||
auto APU::Envelope::serialize(serializer& s) -> void {
|
||||
s.integer(speed);
|
||||
s.integer(use_speed_as_volume);
|
||||
s.integer(loop_mode);
|
||||
|
||||
s.integer(reload_decay);
|
||||
s.integer(decay_counter);
|
||||
s.integer(decay_volume);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
struct Envelope {
|
||||
auto volume() const -> uint;
|
||||
auto clock() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint4 speed;
|
||||
bool use_speed_as_volume;
|
||||
bool loop_mode;
|
||||
|
||||
bool reload_decay;
|
||||
uint8 decay_counter;
|
||||
uint4 decay_volume;
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
auto APU::Noise::clock_length() -> void {
|
||||
if(envelope.loop_mode == 0) {
|
||||
if(length_counter > 0) length_counter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::clock() -> uint8 {
|
||||
if(length_counter == 0) return 0;
|
||||
|
||||
uint8 result = (lfsr & 1) ? envelope.volume() : 0;
|
||||
|
||||
if(--period_counter == 0) {
|
||||
uint feedback;
|
||||
|
||||
if(short_mode) {
|
||||
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1);
|
||||
} else {
|
||||
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1);
|
||||
}
|
||||
|
||||
lfsr = (lfsr >> 1) | (feedback << 14);
|
||||
period_counter = apu.ntsc_noise_period_table[period];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Noise::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::Noise::reset() -> void {
|
||||
length_counter = 0;
|
||||
|
||||
envelope.speed = 0;
|
||||
envelope.use_speed_as_volume = 0;
|
||||
envelope.loop_mode = 0;
|
||||
envelope.reload_decay = 0;
|
||||
envelope.decay_counter = 0;
|
||||
envelope.decay_volume = 0;
|
||||
|
||||
period = 0;
|
||||
period_counter = 1;
|
||||
short_mode = 0;
|
||||
lfsr = 1;
|
||||
}
|
||||
|
||||
auto APU::Noise::serialize(serializer& s) -> void {
|
||||
s.integer(length_counter);
|
||||
|
||||
envelope.serialize(s);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(period_counter);
|
||||
|
||||
s.integer(short_mode);
|
||||
s.integer(lfsr);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
struct Noise {
|
||||
auto clock_length() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint length_counter;
|
||||
|
||||
Envelope envelope;
|
||||
|
||||
uint4 period;
|
||||
uint period_counter;
|
||||
|
||||
bool short_mode;
|
||||
uint15 lfsr;
|
||||
} noise;
|
||||
@@ -1,51 +0,0 @@
|
||||
auto APU::Pulse::clock_length() -> void {
|
||||
if(envelope.loop_mode == 0) {
|
||||
if(length_counter) length_counter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Pulse::clock() -> uint8 {
|
||||
if(sweep.check_period() == false) return 0;
|
||||
if(length_counter == 0) return 0;
|
||||
|
||||
static const uint duty_table[] = {1, 2, 4, 6};
|
||||
uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0;
|
||||
if(sweep.pulse_period < 0x008) result = 0;
|
||||
|
||||
if(--period_counter == 0) {
|
||||
period_counter = (sweep.pulse_period + 1) * 2;
|
||||
duty_counter++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Pulse::power() -> void {
|
||||
envelope.power();
|
||||
sweep.power();
|
||||
}
|
||||
|
||||
auto APU::Pulse::reset() -> void {
|
||||
envelope.reset();
|
||||
sweep.reset();
|
||||
|
||||
length_counter = 0;
|
||||
|
||||
duty = 0;
|
||||
duty_counter = 0;
|
||||
period = 0;
|
||||
period_counter = 1;
|
||||
}
|
||||
|
||||
auto APU::Pulse::serialize(serializer& s) -> void {
|
||||
s.integer(length_counter);
|
||||
|
||||
envelope.serialize(s);
|
||||
sweep.serialize(s);
|
||||
|
||||
s.integer(duty);
|
||||
s.integer(duty_counter);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(period_counter);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
struct Pulse {
|
||||
auto clock_length() -> void;
|
||||
auto check_period() -> bool;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint length_counter;
|
||||
|
||||
Envelope envelope;
|
||||
Sweep sweep;
|
||||
|
||||
uint2 duty;
|
||||
uint3 duty_counter;
|
||||
|
||||
uint11 period;
|
||||
uint period_counter;
|
||||
} pulse[2];
|
||||
@@ -1,28 +0,0 @@
|
||||
auto APU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
filter.serialize(s);
|
||||
|
||||
pulse[0].serialize(s);
|
||||
pulse[1].serialize(s);
|
||||
triangle.serialize(s);
|
||||
dmc.serialize(s);
|
||||
frame.serialize(s);
|
||||
|
||||
s.integer(enabled_channels);
|
||||
s.integer(cartridge_sample);
|
||||
}
|
||||
|
||||
auto APU::Filter::serialize(serializer& s) -> void {
|
||||
s.integer(hipass_strong);
|
||||
s.integer(hipass_weak);
|
||||
s.integer(lopass);
|
||||
}
|
||||
|
||||
auto APU::FrameCounter::serialize(serializer& s) -> void {
|
||||
s.integer(irq_pending);
|
||||
|
||||
s.integer(mode);
|
||||
s.integer(counter);
|
||||
s.integer(divider);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
auto APU::Sweep::check_period() -> bool {
|
||||
if(pulse_period > 0x7ff) return false;
|
||||
|
||||
if(decrement == 0) {
|
||||
if((pulse_period + (pulse_period >> shift)) & 0x800) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto APU::Sweep::clock(uint channel) -> void {
|
||||
if(--counter == 0) {
|
||||
counter = period + 1;
|
||||
if(enable && shift && pulse_period > 8) {
|
||||
int delta = pulse_period >> shift;
|
||||
|
||||
if(decrement) {
|
||||
pulse_period -= delta;
|
||||
if(channel == 0) pulse_period--;
|
||||
} else if((pulse_period + delta) < 0x800) {
|
||||
pulse_period += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(reload) {
|
||||
reload = false;
|
||||
counter = period + 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Sweep::power() -> void {
|
||||
shift = 0;
|
||||
decrement = 0;
|
||||
period = 0;
|
||||
counter = 1;
|
||||
enable = 0;
|
||||
reload = 0;
|
||||
pulse_period = 0;
|
||||
}
|
||||
|
||||
auto APU::Sweep::reset() -> void {
|
||||
}
|
||||
|
||||
auto APU::Sweep::serialize(serializer& s) -> void {
|
||||
s.integer(shift);
|
||||
s.integer(decrement);
|
||||
s.integer(period);
|
||||
s.integer(counter);
|
||||
s.integer(enable);
|
||||
s.integer(reload);
|
||||
s.integer(pulse_period);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
struct Sweep {
|
||||
auto check_period() -> bool;
|
||||
auto clock(uint channel) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint8 shift;
|
||||
bool decrement;
|
||||
uint3 period;
|
||||
uint8 counter;
|
||||
bool enable;
|
||||
bool reload;
|
||||
uint11 pulse_period;
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
auto APU::Triangle::clock_length() -> void {
|
||||
if(halt_length_counter == 0) {
|
||||
if(length_counter > 0) length_counter--;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Triangle::clock_linear_length() -> void {
|
||||
if(reload_linear) {
|
||||
linear_length_counter = linear_length;
|
||||
} else if(linear_length_counter) {
|
||||
linear_length_counter--;
|
||||
}
|
||||
|
||||
if(halt_length_counter == 0) reload_linear = false;
|
||||
}
|
||||
|
||||
auto APU::Triangle::clock() -> uint8 {
|
||||
uint8 result = step_counter & 0x0f;
|
||||
if((step_counter & 0x10) == 0) result ^= 0x0f;
|
||||
if(length_counter == 0 || linear_length_counter == 0) return result;
|
||||
|
||||
if(--period_counter == 0) {
|
||||
step_counter++;
|
||||
period_counter = period + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto APU::Triangle::power() -> void {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto APU::Triangle::reset() -> void {
|
||||
length_counter = 0;
|
||||
|
||||
linear_length = 0;
|
||||
halt_length_counter = 0;
|
||||
period = 0;
|
||||
period_counter = 1;
|
||||
step_counter = 0;
|
||||
linear_length_counter = 0;
|
||||
reload_linear = 0;
|
||||
}
|
||||
|
||||
auto APU::Triangle::serialize(serializer& s) -> void {
|
||||
s.integer(length_counter);
|
||||
|
||||
s.integer(linear_length);
|
||||
s.integer(halt_length_counter);
|
||||
|
||||
s.integer(period);
|
||||
s.integer(period_counter);
|
||||
|
||||
s.integer(step_counter);
|
||||
s.integer(linear_length_counter);
|
||||
s.integer(reload_linear);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
struct Triangle {
|
||||
auto clock_length() -> void;
|
||||
auto clock_linear_length() -> void;
|
||||
auto clock() -> uint8;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint length_counter;
|
||||
|
||||
uint8 linear_length;
|
||||
bool halt_length_counter;
|
||||
|
||||
uint11 period;
|
||||
uint period_counter;
|
||||
|
||||
uint5 step_counter;
|
||||
uint8 linear_length_counter;
|
||||
bool reload_linear;
|
||||
} triangle;
|
||||
@@ -1,115 +0,0 @@
|
||||
//BANDAI-FCG
|
||||
|
||||
struct BandaiFCG : Board {
|
||||
BandaiFCG(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(1);
|
||||
irq_counter_enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
case 2: return 0x0000 | (addr & 0x03ff);
|
||||
case 3: return 0x0400 | (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (region == 0 ? prg_bank : 0x0f);
|
||||
return prgrom.read((bank << 14) | (addr & 0x3fff));
|
||||
}
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr >= 0x6000) {
|
||||
switch(addr & 15) {
|
||||
case 0x00: case 0x01: case 0x02: case 0x03:
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
chr_bank[addr & 7] = data;
|
||||
break;
|
||||
case 0x08:
|
||||
prg_bank = data & 0x0f;
|
||||
break;
|
||||
case 0x09:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
case 0x0a:
|
||||
cpu.set_irq_line(0);
|
||||
irq_counter_enable = data & 0x01;
|
||||
irq_counter = irq_latch;
|
||||
break;
|
||||
case 0x0b:
|
||||
irq_latch = (irq_latch & 0xff00) | (data << 0);
|
||||
break;
|
||||
case 0x0c:
|
||||
irq_latch = (irq_latch & 0x00ff) | (data << 8);
|
||||
break;
|
||||
case 0x0d:
|
||||
//TODO: serial EEPROM support
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
|
||||
addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
|
||||
return Board::chr_read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
|
||||
addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
|
||||
return Board::chr_write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto &n : chr_bank) n = 0;
|
||||
prg_bank = 0;
|
||||
mirror = 0;
|
||||
irq_counter_enable = 0;
|
||||
irq_counter = 0;
|
||||
irq_latch = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.array(chr_bank);
|
||||
s.integer(prg_bank);
|
||||
s.integer(mirror);
|
||||
s.integer(irq_counter_enable);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_latch);
|
||||
}
|
||||
|
||||
uint8 chr_bank[8];
|
||||
uint8 prg_bank;
|
||||
uint2 mirror;
|
||||
bool irq_counter_enable;
|
||||
uint16 irq_counter;
|
||||
uint16 irq_latch;
|
||||
};
|
||||
@@ -1,209 +0,0 @@
|
||||
#include "bandai-fcg.cpp"
|
||||
#include "konami-vrc1.cpp"
|
||||
#include "konami-vrc2.cpp"
|
||||
#include "konami-vrc3.cpp"
|
||||
#include "konami-vrc4.cpp"
|
||||
#include "konami-vrc6.cpp"
|
||||
#include "konami-vrc7.cpp"
|
||||
#include "nes-axrom.cpp"
|
||||
#include "nes-bnrom.cpp"
|
||||
#include "nes-cnrom.cpp"
|
||||
#include "nes-exrom.cpp"
|
||||
#include "nes-fxrom.cpp"
|
||||
#include "nes-gxrom.cpp"
|
||||
#include "nes-hkrom.cpp"
|
||||
#include "nes-nrom.cpp"
|
||||
#include "nes-pxrom.cpp"
|
||||
#include "nes-sxrom.cpp"
|
||||
#include "nes-txrom.cpp"
|
||||
#include "nes-uxrom.cpp"
|
||||
#include "sunsoft-5b.cpp"
|
||||
|
||||
Board::Board(Markup::Node& document) {
|
||||
cartridge.board = this;
|
||||
auto board = document["board"];
|
||||
|
||||
information.type = board["id"].text();
|
||||
information.battery = (bool)board["prg/ram/name"];
|
||||
|
||||
auto prom = board["prg/rom"];
|
||||
auto pram = board["prg/ram"];
|
||||
auto crom = board["chr/rom"];
|
||||
auto cram = board["chr/ram"];
|
||||
|
||||
prgrom.size = prom["size"].natural();
|
||||
prgram.size = pram["size"].natural();
|
||||
chrrom.size = crom["size"].natural();
|
||||
chrram.size = cram["size"].natural();
|
||||
|
||||
if(prgrom.size) prgrom.data = new uint8[prgrom.size]();
|
||||
if(prgram.size) prgram.data = new uint8[prgram.size]();
|
||||
if(chrrom.size) chrrom.data = new uint8[chrrom.size]();
|
||||
if(chrram.size) chrram.data = new uint8[chrram.size]();
|
||||
|
||||
if(auto name = prom["name"].text()) interface->loadRequest(ID::ProgramROM, name, true);
|
||||
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name, false);
|
||||
if(auto name = crom["name"].text()) interface->loadRequest(ID::CharacterROM, name, true);
|
||||
if(auto name = cram["name"].text()) interface->loadRequest(ID::CharacterRAM, name, false);
|
||||
|
||||
if(auto name = pram["name"].text()) Famicom::cartridge.memory.append({ID::ProgramRAM, name});
|
||||
if(auto name = cram["name"].text()) Famicom::cartridge.memory.append({ID::CharacterRAM, name});
|
||||
|
||||
prgram.writable = true;
|
||||
chrram.writable = true;
|
||||
}
|
||||
|
||||
auto Board::Memory::read(uint addr) const -> uint8 {
|
||||
return data[mirror(addr, size)];
|
||||
}
|
||||
|
||||
auto Board::Memory::write(uint addr, uint8 byte) -> void {
|
||||
if(writable) data[mirror(addr, size)] = byte;
|
||||
}
|
||||
|
||||
auto Board::mirror(uint addr, uint size) -> uint {
|
||||
uint base = 0;
|
||||
if(size) {
|
||||
uint mask = 1 << 23;
|
||||
while(addr >= size) {
|
||||
while(!(addr & mask)) mask >>= 1;
|
||||
addr -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
base += addr;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
auto Board::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cartridge.clock += 12 * 4095;
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
auto Board::tick() -> void {
|
||||
cartridge.clock += 12;
|
||||
if(cartridge.clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto Board::chr_read(uint addr) -> uint8 {
|
||||
if(chrram.size) return chrram.data[mirror(addr, chrram.size)];
|
||||
if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)];
|
||||
return 0u;
|
||||
}
|
||||
|
||||
auto Board::chr_write(uint addr, uint8 data) -> void {
|
||||
if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data;
|
||||
}
|
||||
|
||||
auto Board::power() -> void {
|
||||
}
|
||||
|
||||
auto Board::reset() -> void {
|
||||
}
|
||||
|
||||
auto Board::serialize(serializer& s) -> void {
|
||||
if(prgram.size) s.array(prgram.data, prgram.size);
|
||||
if(chrram.size) s.array(chrram.data, chrram.size);
|
||||
}
|
||||
|
||||
auto Board::load(string manifest) -> Board* {
|
||||
auto document = BML::unserialize(manifest);
|
||||
cartridge.information.title = document["information/title"].text();
|
||||
|
||||
string type = document["board/id"].text();
|
||||
|
||||
if(type == "BANDAI-FCG" ) return new BandaiFCG(document);
|
||||
|
||||
if(type == "KONAMI-VRC-1") return new KonamiVRC1(document);
|
||||
if(type == "KONAMI-VRC-2") return new KonamiVRC2(document);
|
||||
if(type == "KONAMI-VRC-3") return new KonamiVRC3(document);
|
||||
if(type == "KONAMI-VRC-4") return new KonamiVRC4(document);
|
||||
if(type == "KONAMI-VRC-6") return new KonamiVRC6(document);
|
||||
if(type == "KONAMI-VRC-7") return new KonamiVRC7(document);
|
||||
|
||||
if(type == "NES-AMROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-ANROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-AN1ROM" ) return new NES_AxROM(document);
|
||||
if(type == "NES-AOROM" ) return new NES_AxROM(document);
|
||||
|
||||
if(type == "NES-BNROM" ) return new NES_BNROM(document);
|
||||
|
||||
if(type == "NES-CNROM" ) return new NES_CNROM(document);
|
||||
|
||||
if(type == "NES-EKROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-ELROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-ETROM" ) return new NES_ExROM(document);
|
||||
if(type == "NES-EWROM" ) return new NES_ExROM(document);
|
||||
|
||||
if(type == "NES-FJROM" ) return new NES_FxROM(document);
|
||||
if(type == "NES-FKROM" ) return new NES_FxROM(document);
|
||||
|
||||
if(type == "NES-GNROM" ) return new NES_GxROM(document);
|
||||
if(type == "NES-MHROM" ) return new NES_GxROM(document);
|
||||
|
||||
if(type == "NES-HKROM" ) return new NES_HKROM(document);
|
||||
|
||||
if(type == "NES-NROM" ) return new NES_NROM(document);
|
||||
if(type == "NES-NROM-128") return new NES_NROM(document);
|
||||
if(type == "NES-NROM-256") return new NES_NROM(document);
|
||||
|
||||
if(type == "NES-PEEOROM" ) return new NES_PxROM(document);
|
||||
if(type == "NES-PNROM" ) return new NES_PxROM(document);
|
||||
|
||||
if(type == "NES-SAROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SBROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SCROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SC1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SEROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SFROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SFEXPROM") return new NES_SxROM(document);
|
||||
if(type == "NES-SGROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SHROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SH1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SIROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SJROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SKROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SLROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL1ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL2ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SL3ROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SLRROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SMROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SNROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SOROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SUROM" ) return new NES_SxROM(document);
|
||||
if(type == "NES-SXROM" ) return new NES_SxROM(document);
|
||||
|
||||
if(type == "NES-TBROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TEROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TFROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TGROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TKROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TKSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TLROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TL1ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TL2ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TLSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TNROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TQROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TR1ROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TSROM" ) return new NES_TxROM(document);
|
||||
if(type == "NES-TVROM" ) return new NES_TxROM(document);
|
||||
|
||||
if(type == "NES-UNROM" ) return new NES_UxROM(document);
|
||||
if(type == "NES-UOROM" ) return new NES_UxROM(document);
|
||||
|
||||
if(type == "SUNSOFT-5B" ) return new Sunsoft5B(document);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
struct Board {
|
||||
struct Memory {
|
||||
inline Memory(uint8_t* data, uint size) : data(data), size(size) {}
|
||||
inline Memory() : data(nullptr), size(0u), writable(false) {}
|
||||
inline ~Memory() { if(data) delete[] data; }
|
||||
|
||||
inline auto read(uint addr) const -> uint8;
|
||||
inline auto write(uint addr, uint8 data) -> void;
|
||||
|
||||
uint8_t* data;
|
||||
uint size;
|
||||
bool writable;
|
||||
};
|
||||
|
||||
Board(Markup::Node& document);
|
||||
virtual ~Board() = default;
|
||||
|
||||
static auto mirror(uint addr, uint size) -> uint;
|
||||
|
||||
virtual auto main() -> void;
|
||||
virtual auto tick() -> void;
|
||||
|
||||
virtual auto prg_read(uint addr) -> uint8 = 0;
|
||||
virtual auto prg_write(uint addr, uint8 data) -> void = 0;
|
||||
|
||||
virtual auto chr_read(uint addr) -> uint8;
|
||||
virtual auto chr_write(uint addr, uint8 data) -> void;
|
||||
|
||||
virtual inline auto scanline(uint y) -> void {}
|
||||
|
||||
virtual auto power() -> void;
|
||||
virtual auto reset() -> void;
|
||||
|
||||
virtual auto serialize(serializer&) -> void;
|
||||
|
||||
static auto load(string manifest) -> Board*;
|
||||
|
||||
struct Information {
|
||||
string type;
|
||||
bool battery;
|
||||
} information;
|
||||
|
||||
Memory prgrom;
|
||||
Memory prgram;
|
||||
Memory chrrom;
|
||||
Memory chrram;
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
struct KonamiVRC1 : Board {
|
||||
KonamiVRC1(Markup::Node& document) : Board(document), vrc1(*this) {
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(vrc1.prg_addr(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) return vrc1.reg_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc1.ciram_addr(addr));
|
||||
return Board::chr_read(vrc1.chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc1.ciram_addr(addr), data);
|
||||
return Board::chr_write(vrc1.chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc1.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc1.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc1.serialize(s);
|
||||
}
|
||||
|
||||
VRC1 vrc1;
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
struct KonamiVRC2 : Board {
|
||||
KonamiVRC2(Markup::Node& document) : Board(document), vrc2(*this) {
|
||||
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
|
||||
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return vrc2.ram_read(addr);
|
||||
return prgrom.read(vrc2.prg_addr(addr));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return vrc2.ram_write(addr, data);
|
||||
|
||||
bool a0 = (addr & settings.pinout.a0);
|
||||
bool a1 = (addr & settings.pinout.a1);
|
||||
addr &= 0xfff0;
|
||||
addr |= (a0 << 0) | (a1 << 1);
|
||||
return vrc2.reg_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc2.ciram_addr(addr));
|
||||
return Board::chr_read(vrc2.chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc2.ciram_addr(addr), data);
|
||||
return Board::chr_write(vrc2.chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc2.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc2.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc2.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
struct Pinout {
|
||||
uint a0;
|
||||
uint a1;
|
||||
} pinout;
|
||||
} settings;
|
||||
|
||||
VRC2 vrc2;
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
struct KonamiVRC3 : Board {
|
||||
KonamiVRC3(Markup::Node& document) : Board(document), vrc3(*this) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
vrc3.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) return prgram.read(addr & 0x1fff);
|
||||
if(addr & 0x8000) return prgrom.read(vrc3.prg_addr(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return prgram.write(addr & 0x1fff, data);
|
||||
if(addr & 0x8000) return vrc3.reg_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr & 0x07ff);
|
||||
}
|
||||
return chrram.read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr & 0x07ff, data);
|
||||
}
|
||||
return chrram.write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc3.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc3.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc3.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
VRC3 vrc3;
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
struct KonamiVRC4 : Board {
|
||||
KonamiVRC4(Markup::Node& document) : Board(document), vrc4(*this) {
|
||||
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
|
||||
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return vrc4.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
return prgrom.read(vrc4.prg_addr(addr));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
bool a0 = (addr & settings.pinout.a0);
|
||||
bool a1 = (addr & settings.pinout.a1);
|
||||
addr &= 0xfff0;
|
||||
addr |= (a1 << 1) | (a0 << 0);
|
||||
return vrc4.reg_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc4.ciram_addr(addr));
|
||||
return Board::chr_read(vrc4.chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc4.ciram_addr(addr), data);
|
||||
return Board::chr_write(vrc4.chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc4.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc4.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc4.serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
struct Pinout {
|
||||
uint a0;
|
||||
uint a1;
|
||||
} pinout;
|
||||
} settings;
|
||||
|
||||
VRC4 vrc4;
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
struct KonamiVRC6 : Board {
|
||||
KonamiVRC6(Markup::Node& document) : Board(document), vrc6(*this) {
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8{
|
||||
if((addr & 0xe000) == 0x6000) return vrc6.ram_read(addr);
|
||||
if(addr & 0x8000) return prgrom.read(vrc6.prg_addr(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return vrc6.ram_write(addr, data);
|
||||
if(addr & 0x8000) {
|
||||
addr = (addr & 0xf003);
|
||||
if(prgram.size) addr = (addr & ~3) | ((addr & 2) >> 1) | ((addr & 1) << 1);
|
||||
return vrc6.reg_write(addr, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc6.ciram_addr(addr));
|
||||
return Board::chr_read(vrc6.chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc6.ciram_addr(addr), data);
|
||||
return Board::chr_write(vrc6.chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc6.serialize(s);
|
||||
}
|
||||
|
||||
auto main() -> void { vrc6.main(); }
|
||||
auto power() -> void { vrc6.power(); }
|
||||
auto reset() -> void { vrc6.reset(); }
|
||||
|
||||
VRC6 vrc6;
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
struct KonamiVRC7 : Board {
|
||||
KonamiVRC7(Markup::Node& document) : Board(document), vrc7(*this) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return vrc7.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
return prgrom.read(vrc7.prg_addr(addr));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
return vrc7.reg_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(vrc7.ciram_addr(addr));
|
||||
return chrram.read(vrc7.chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(vrc7.ciram_addr(addr), data);
|
||||
return chrram.write(vrc7.chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
vrc7.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
vrc7.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
vrc7.serialize(s);
|
||||
}
|
||||
|
||||
VRC7 vrc7;
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
//NES-AMROM
|
||||
//NES-ANROM
|
||||
//NES-AN1ROM
|
||||
//NES-AOROM
|
||||
|
||||
struct NES_AxROM : Board {
|
||||
NES_AxROM(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) {
|
||||
prg_bank = data & 0x0f;
|
||||
mirror_select = data & 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read((mirror_select << 10) | (addr & 0x03ff));
|
||||
return Board::chr_read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write((mirror_select << 10) | (addr & 0x03ff), data);
|
||||
return Board::chr_write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0x0f;
|
||||
mirror_select = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prg_bank);
|
||||
s.integer(mirror_select);
|
||||
}
|
||||
|
||||
uint4 prg_bank;
|
||||
bool mirror_select;
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
//NES-BN-ROM-01
|
||||
|
||||
struct NES_BNROM : Board {
|
||||
NES_BNROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prg_bank = data & 0x03;
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr);
|
||||
}
|
||||
return Board::chr_read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr, data);
|
||||
}
|
||||
return Board::chr_write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(prg_bank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 prg_bank;
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
//NES-CNROM
|
||||
|
||||
struct NES_CNROM : Board {
|
||||
NES_CNROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(addr & 0x7fff);
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) chr_bank = data & 0x03;
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr & 0x07ff);
|
||||
}
|
||||
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::chr_read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr & 0x07ff, data);
|
||||
}
|
||||
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
|
||||
Board::chr_write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chr_bank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(chr_bank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 chr_bank;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
struct NES_ExROM : Board {
|
||||
NES_ExROM(Markup::Node& document) : Board(document), mmc5(*this) {
|
||||
revision = Revision::ELROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc5.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
return mmc5.prg_read(addr);
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
mmc5.prg_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
return mmc5.chr_read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
mmc5.chr_write(addr, data);
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
mmc5.scanline(y);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc5.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmc5.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc5.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
EKROM,
|
||||
ELROM,
|
||||
ETROM,
|
||||
EWROM,
|
||||
} revision;
|
||||
|
||||
MMC5 mmc5;
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
//MMC4
|
||||
|
||||
struct NES_FxROM : Board {
|
||||
NES_FxROM(Markup::Node& document) : Board(document) {
|
||||
revision = Revision::FKROM;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
uint bank = addr < 0xc000 ? prg_bank : (uint4)0x0f;
|
||||
return prgrom.read((bank * 0x4000) | (addr & 0x3fff));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
switch(addr & 0xf000) {
|
||||
case 0xa000: prg_bank = data & 0x0f; break;
|
||||
case 0xb000: chr_bank[0][0] = data & 0x1f; break;
|
||||
case 0xc000: chr_bank[0][1] = data & 0x1f; break;
|
||||
case 0xd000: chr_bank[1][0] = data & 0x1f; break;
|
||||
case 0xe000: chr_bank[1][1] = data & 0x1f; break;
|
||||
case 0xf000: mirror = data & 0x01; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::chr_read((bank * 0x1000) | (addr & 0x0fff));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
chr_bank[0][0] = 0;
|
||||
chr_bank[0][1] = 0;
|
||||
chr_bank[1][0] = 0;
|
||||
chr_bank[1][1] = 0;
|
||||
mirror = 0;
|
||||
latch[0] = 0;
|
||||
latch[1] = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prg_bank);
|
||||
s.integer(chr_bank[0][0]);
|
||||
s.integer(chr_bank[0][1]);
|
||||
s.integer(chr_bank[1][0]);
|
||||
s.integer(chr_bank[1][1]);
|
||||
s.integer(mirror);
|
||||
s.array(latch);
|
||||
}
|
||||
|
||||
enum Revision : uint {
|
||||
FJROM,
|
||||
FKROM,
|
||||
} revision;
|
||||
|
||||
uint4 prg_bank;
|
||||
uint5 chr_bank[2][2];
|
||||
bool mirror;
|
||||
bool latch[2];
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
//NES-GNROM
|
||||
//NES-MHROM
|
||||
|
||||
struct NES_GxROM : Board {
|
||||
NES_GxROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read((prg_bank << 15) | (addr & 0x7fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) {
|
||||
prg_bank = (data & 0x30) >> 4;
|
||||
chr_bank = (data & 0x03) >> 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr & 0x07ff);
|
||||
}
|
||||
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
|
||||
return Board::chr_read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr & 0x07ff, data);
|
||||
}
|
||||
addr = (chr_bank * 0x2000) + (addr & 0x1fff);
|
||||
Board::chr_write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
chr_bank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
s.integer(prg_bank);
|
||||
s.integer(chr_bank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint2 prg_bank;
|
||||
uint2 chr_bank;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
struct NES_HKROM : Board {
|
||||
NES_HKROM(Markup::Node& document) : Board(document), mmc6(*this) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc6.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xf000) == 0x7000) return mmc6.ram_read(addr);
|
||||
if(addr & 0x8000) return prgrom.read(mmc6.prg_addr(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xf000) == 0x7000) return mmc6.ram_write(addr, data);
|
||||
if(addr & 0x8000) return mmc6.reg_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
mmc6.irq_test(addr);
|
||||
if(addr & 0x2000) return ppu.ciram_read(mmc6.ciram_addr(addr));
|
||||
return Board::chr_read(mmc6.chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
mmc6.irq_test(addr);
|
||||
if(addr & 0x2000) return ppu.ciram_write(mmc6.ciram_addr(addr), data);
|
||||
return Board::chr_write(mmc6.chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc6.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmc6.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc6.serialize(s);
|
||||
}
|
||||
|
||||
MMC6 mmc6;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
//NES-NROM-128
|
||||
//NES-NROM-256
|
||||
|
||||
struct NES_NROM : Board {
|
||||
NES_NROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) return prgrom.read(addr);
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr & 0x07ff);
|
||||
}
|
||||
if(chrram.size) return chrram.read(addr);
|
||||
return chrrom.read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr & 0x07ff, data);
|
||||
}
|
||||
if(chrram.size) return chrram.write(addr, data);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
//MMC2
|
||||
|
||||
struct NES_PxROM : Board {
|
||||
NES_PxROM(Markup::Node& document) : Board(document) {
|
||||
revision = Revision::PNROM;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
if(addr < 0x8000) return prgram.read(addr);
|
||||
uint bank = 0;
|
||||
switch((addr / 0x2000) & 3) {
|
||||
case 0: bank = prg_bank; break;
|
||||
case 1: bank = 0x0d; break;
|
||||
case 2: bank = 0x0e; break;
|
||||
case 3: bank = 0x0f; break;
|
||||
}
|
||||
return prgrom.read((bank * 0x2000) | (addr & 0x1fff));
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr < 0x6000) return;
|
||||
if(addr < 0x8000) return prgram.write(addr, data);
|
||||
|
||||
switch(addr & 0xf000) {
|
||||
case 0xa000: prg_bank = data & 0x0f; break;
|
||||
case 0xb000: chr_bank[0][0] = data & 0x1f; break;
|
||||
case 0xc000: chr_bank[0][1] = data & 0x1f; break;
|
||||
case 0xd000: chr_bank[1][0] = data & 0x1f; break;
|
||||
case 0xe000: chr_bank[1][1] = data & 0x1f; break;
|
||||
case 0xf000: mirror = data & 0x01; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::chr_read((bank * 0x1000) | (addr & 0x0fff));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region][latch[region]];
|
||||
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
|
||||
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
|
||||
return Board::chr_write((bank * 0x1000) | (addr & 0x0fff), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
chr_bank[0][0] = 0;
|
||||
chr_bank[0][1] = 0;
|
||||
chr_bank[1][0] = 0;
|
||||
chr_bank[1][1] = 0;
|
||||
mirror = 0;
|
||||
latch[0] = 0;
|
||||
latch[1] = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prg_bank);
|
||||
s.integer(chr_bank[0][0]);
|
||||
s.integer(chr_bank[0][1]);
|
||||
s.integer(chr_bank[1][0]);
|
||||
s.integer(chr_bank[1][1]);
|
||||
s.integer(mirror);
|
||||
s.array(latch);
|
||||
}
|
||||
|
||||
enum Revision : uint {
|
||||
PEEOROM,
|
||||
PNROM,
|
||||
} revision;
|
||||
|
||||
uint4 prg_bank;
|
||||
uint5 chr_bank[2][2];
|
||||
bool mirror;
|
||||
bool latch[2];
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
struct NES_SxROM : Board {
|
||||
NES_SxROM(Markup::Node& document) : Board(document), mmc1(*this) {
|
||||
revision = Revision::SXROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
return mmc1.main();
|
||||
}
|
||||
|
||||
auto ram_addr(uint addr) -> uint {
|
||||
uint bank = 0;
|
||||
if(revision == Revision::SOROM) bank = (mmc1.chr_bank[0] & 0x08) >> 3;
|
||||
if(revision == Revision::SUROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2;
|
||||
if(revision == Revision::SXROM) bank = (mmc1.chr_bank[0] & 0x0c) >> 2;
|
||||
return (bank << 13) | (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
if(revision == Revision::SNROM) {
|
||||
if(mmc1.chr_bank[0] & 0x10) return cpu.mdr();
|
||||
}
|
||||
if(mmc1.ram_disable) return 0x00;
|
||||
return prgram.read(ram_addr(addr));
|
||||
}
|
||||
|
||||
if(addr & 0x8000) {
|
||||
addr = mmc1.prg_addr(addr);
|
||||
if(revision == Revision::SXROM) {
|
||||
addr |= ((mmc1.chr_bank[0] & 0x10) >> 4) << 18;
|
||||
}
|
||||
return prgrom.read(addr);
|
||||
}
|
||||
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
if(revision == Revision::SNROM) {
|
||||
if(mmc1.chr_bank[0] & 0x10) return;
|
||||
}
|
||||
if(mmc1.ram_disable) return;
|
||||
return prgram.write(ram_addr(addr), data);
|
||||
}
|
||||
|
||||
if(addr & 0x8000) return mmc1.mmio_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(mmc1.ciram_addr(addr));
|
||||
return Board::chr_read(mmc1.chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(mmc1.ciram_addr(addr), data);
|
||||
return Board::chr_write(mmc1.chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc1.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmc1.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc1.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
SAROM,
|
||||
SBROM,
|
||||
SCROM,
|
||||
SC1ROM,
|
||||
SEROM,
|
||||
SFROM,
|
||||
SGROM,
|
||||
SHROM,
|
||||
SH1ROM,
|
||||
SIROM,
|
||||
SJROM,
|
||||
SKROM,
|
||||
SLROM,
|
||||
SL1ROM,
|
||||
SL2ROM,
|
||||
SL3ROM,
|
||||
SLRROM,
|
||||
SMROM,
|
||||
SNROM,
|
||||
SOROM,
|
||||
SUROM,
|
||||
SXROM,
|
||||
} revision;
|
||||
|
||||
MMC1 mmc1;
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
struct NES_TxROM : Board {
|
||||
NES_TxROM(Markup::Node& document) : Board(document), mmc3(*this) {
|
||||
revision = Revision::TLROM;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
mmc3.main();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xe000) == 0x6000) return mmc3.ram_read(addr);
|
||||
if(addr & 0x8000) return prgrom.read(mmc3.prg_addr(addr));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) return mmc3.ram_write(addr, data);
|
||||
if(addr & 0x8000) return mmc3.reg_write(addr, data);
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
mmc3.irq_test(addr);
|
||||
if(addr & 0x2000) return ppu.ciram_read(mmc3.ciram_addr(addr));
|
||||
return Board::chr_read(mmc3.chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
mmc3.irq_test(addr);
|
||||
if(addr & 0x2000) return ppu.ciram_write(mmc3.ciram_addr(addr), data);
|
||||
return Board::chr_write(mmc3.chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
mmc3.power();
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmc3.reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
mmc3.serialize(s);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
TBROM,
|
||||
TEROM,
|
||||
TFROM,
|
||||
TGROM,
|
||||
TKROM,
|
||||
TKSROM,
|
||||
TLROM,
|
||||
TL1ROM,
|
||||
TL2ROM,
|
||||
TLSROM,
|
||||
TNROM,
|
||||
TQROM,
|
||||
TR1ROM,
|
||||
TSROM,
|
||||
TVROM,
|
||||
} revision;
|
||||
|
||||
MMC3 mmc3;
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
//NES-UNROM
|
||||
//NES-UOROM
|
||||
|
||||
struct NES_UxROM : Board {
|
||||
NES_UxROM(Markup::Node& document) : Board(document) {
|
||||
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xc000) == 0x8000) return prgrom.read((prg_bank << 14) | (addr & 0x3fff));
|
||||
if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff));
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x8000) prg_bank = data & 0x0f;
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_read(addr);
|
||||
}
|
||||
return Board::chr_read(addr);
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
return ppu.ciram_write(addr, data);
|
||||
}
|
||||
return Board::chr_write(addr, data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(prg_bank);
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
bool mirror; //0 = horizontal, 1 = vertical
|
||||
} settings;
|
||||
|
||||
uint4 prg_bank;
|
||||
};
|
||||
@@ -1,224 +0,0 @@
|
||||
//SUNSOFT-5B
|
||||
|
||||
struct Sunsoft5B : Board {
|
||||
Sunsoft5B(Markup::Node& document) : Board(document) {
|
||||
}
|
||||
|
||||
struct Pulse {
|
||||
auto clock() -> void {
|
||||
if(--counter == 0) {
|
||||
counter = frequency << 4;
|
||||
duty ^= 1;
|
||||
}
|
||||
output = duty ? volume : (uint4)0;
|
||||
if(disable) output = 0;
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
disable = 1;
|
||||
frequency = 1;
|
||||
volume = 0;
|
||||
|
||||
counter = 0;
|
||||
duty = 0;
|
||||
output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(disable);
|
||||
s.integer(frequency);
|
||||
s.integer(volume);
|
||||
|
||||
s.integer(counter);
|
||||
s.integer(duty);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
bool disable;
|
||||
uint12 frequency;
|
||||
uint4 volume;
|
||||
|
||||
uint16 counter; //12-bit countdown + 4-bit phase
|
||||
uint1 duty;
|
||||
uint4 output;
|
||||
} pulse[3];
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr < 0x6000) return cpu.mdr();
|
||||
|
||||
uint8 bank = 0x3f; //((addr & 0xe000) == 0xe000
|
||||
if((addr & 0xe000) == 0x6000) bank = prg_bank[0];
|
||||
if((addr & 0xe000) == 0x8000) bank = prg_bank[1];
|
||||
if((addr & 0xe000) == 0xa000) bank = prg_bank[2];
|
||||
if((addr & 0xe000) == 0xc000) bank = prg_bank[3];
|
||||
|
||||
bool ram_enable = bank & 0x80;
|
||||
bool ram_select = bank & 0x40;
|
||||
bank &= 0x3f;
|
||||
|
||||
if(ram_select) {
|
||||
if(ram_enable == false) return cpu.mdr();
|
||||
return prgram.data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
addr = (bank << 13) | (addr & 0x1fff);
|
||||
return prgrom.read(addr);
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
if(addr == 0x8000) {
|
||||
mmu_port = data & 0x0f;
|
||||
}
|
||||
|
||||
if(addr == 0xa000) {
|
||||
switch(mmu_port) {
|
||||
case 0: chr_bank[0] = data; break;
|
||||
case 1: chr_bank[1] = data; break;
|
||||
case 2: chr_bank[2] = data; break;
|
||||
case 3: chr_bank[3] = data; break;
|
||||
case 4: chr_bank[4] = data; break;
|
||||
case 5: chr_bank[5] = data; break;
|
||||
case 6: chr_bank[6] = data; break;
|
||||
case 7: chr_bank[7] = data; break;
|
||||
case 8: prg_bank[0] = data; break;
|
||||
case 9: prg_bank[1] = data; break;
|
||||
case 10: prg_bank[2] = data; break;
|
||||
case 11: prg_bank[3] = data; break;
|
||||
case 12: mirror = data & 3; break;
|
||||
case 13:
|
||||
irq_enable = data & 0x80;
|
||||
irq_counter_enable = data & 0x01;
|
||||
if(irq_enable == 0) cpu.set_irq_line(0);
|
||||
break;
|
||||
case 14: irq_counter = (irq_counter & 0xff00) | (data << 0); break;
|
||||
case 15: irq_counter = (irq_counter & 0x00ff) | (data << 8); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(addr == 0xc000) {
|
||||
apu_port = data & 0x0f;
|
||||
}
|
||||
|
||||
if(addr == 0xe000) {
|
||||
switch(apu_port) {
|
||||
case 0: pulse[0].frequency = (pulse[0].frequency & 0xff00) | (data << 0); break;
|
||||
case 1: pulse[0].frequency = (pulse[0].frequency & 0x00ff) | (data << 8); break;
|
||||
case 2: pulse[1].frequency = (pulse[1].frequency & 0xff00) | (data << 0); break;
|
||||
case 3: pulse[1].frequency = (pulse[1].frequency & 0x00ff) | (data << 8); break;
|
||||
case 4: pulse[2].frequency = (pulse[2].frequency & 0xff00) | (data << 0); break;
|
||||
case 5: pulse[2].frequency = (pulse[2].frequency & 0x00ff) | (data << 8); break;
|
||||
case 7:
|
||||
pulse[0].disable = data & 0x01;
|
||||
pulse[1].disable = data & 0x02;
|
||||
pulse[2].disable = data & 0x04;
|
||||
break;
|
||||
case 8: pulse[0].volume = data & 0x0f; break;
|
||||
case 9: pulse[1].volume = data & 0x0f; break;
|
||||
case 10: pulse[2].volume = data & 0x0f; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) -> uint {
|
||||
uint8 bank = (addr >> 10) & 7;
|
||||
return (chr_bank[bank] << 10) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //first
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //second
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
if(addr & 0x2000) return ppu.ciram_read(ciram_addr(addr));
|
||||
return Board::chr_read(chr_addr(addr));
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) return ppu.ciram_write(ciram_addr(addr), data);
|
||||
return Board::chr_write(chr_addr(addr), data);
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
for(signed n : range(16)) {
|
||||
double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n));
|
||||
dac[n] = volume * 8192.0;
|
||||
}
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
mmu_port = 0;
|
||||
apu_port = 0;
|
||||
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
mirror = 0;
|
||||
irq_enable = 0;
|
||||
irq_counter_enable = 0;
|
||||
irq_counter = 0;
|
||||
|
||||
pulse[0].reset();
|
||||
pulse[1].reset();
|
||||
pulse[2].reset();
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
Board::serialize(s);
|
||||
|
||||
s.integer(mmu_port);
|
||||
s.integer(apu_port);
|
||||
|
||||
s.array(prg_bank);
|
||||
s.array(chr_bank);
|
||||
s.integer(mirror);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_counter_enable);
|
||||
s.integer(irq_counter);
|
||||
|
||||
pulse[0].serialize(s);
|
||||
pulse[1].serialize(s);
|
||||
pulse[2].serialize(s);
|
||||
}
|
||||
|
||||
uint4 mmu_port;
|
||||
uint4 apu_port;
|
||||
|
||||
uint8 prg_bank[4];
|
||||
uint8 chr_bank[8];
|
||||
uint2 mirror;
|
||||
bool irq_enable;
|
||||
bool irq_counter_enable;
|
||||
uint16 irq_counter;
|
||||
|
||||
int16 dac[16];
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "chip/chip.cpp"
|
||||
#include "board/board.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::loaded() const -> bool {
|
||||
return _loaded;
|
||||
}
|
||||
|
||||
auto Cartridge::sha256() const -> string {
|
||||
return _sha256;
|
||||
}
|
||||
|
||||
auto Cartridge::manifest() const -> string {
|
||||
return information.markup;
|
||||
}
|
||||
|
||||
auto Cartridge::title() const -> string {
|
||||
return information.title;
|
||||
}
|
||||
|
||||
auto Cartridge::Main() -> void {
|
||||
cartridge.main();
|
||||
}
|
||||
|
||||
auto Cartridge::main() -> void {
|
||||
board->main();
|
||||
}
|
||||
|
||||
auto Cartridge::load() -> void {
|
||||
interface->loadRequest(ID::Manifest, "manifest.bml", true);
|
||||
|
||||
Board::load(information.markup); //this call will set Cartridge::board if successful
|
||||
if(!board) return;
|
||||
|
||||
Hash::SHA256 sha;
|
||||
sha.data(board->prgrom.data, board->prgrom.size);
|
||||
sha.data(board->chrrom.data, board->chrrom.size);
|
||||
_sha256 = sha.digest();
|
||||
|
||||
system.load();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
_loaded = false;
|
||||
memory.reset();
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
board->power();
|
||||
}
|
||||
|
||||
auto Cartridge::reset() -> void {
|
||||
create(Cartridge::Main, 21477272);
|
||||
board->reset();
|
||||
}
|
||||
|
||||
auto Cartridge::prg_read(uint addr) -> uint8 {
|
||||
return board->prg_read(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::prg_write(uint addr, uint8 data) -> void {
|
||||
return board->prg_write(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::chr_read(uint addr) -> uint8 {
|
||||
return board->chr_read(addr);
|
||||
}
|
||||
|
||||
auto Cartridge::chr_write(uint addr, uint8 data) -> void {
|
||||
return board->chr_write(addr, data);
|
||||
}
|
||||
|
||||
auto Cartridge::scanline(uint y) -> void {
|
||||
return board->scanline(y);
|
||||
}
|
||||
|
||||
auto Cartridge::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
return board->serialize(s);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "chip/chip.hpp"
|
||||
#include "board/board.hpp"
|
||||
|
||||
struct Cartridge : Thread {
|
||||
static auto Main() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
auto loaded() const -> bool;
|
||||
auto sha256() const -> string;
|
||||
auto manifest() const -> string;
|
||||
auto title() const -> string;
|
||||
|
||||
auto load() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Information {
|
||||
string markup;
|
||||
string title;
|
||||
} information;
|
||||
|
||||
struct Memory {
|
||||
unsigned id;
|
||||
string name;
|
||||
};
|
||||
vector<Memory> memory;
|
||||
|
||||
//privileged:
|
||||
Board* board = nullptr;
|
||||
bool _loaded = false;
|
||||
string _sha256;
|
||||
|
||||
auto prg_read(uint addr) -> uint8;
|
||||
auto prg_write(uint addr, uint8 data) -> void;
|
||||
|
||||
auto chr_read(uint addr) -> uint8;
|
||||
auto chr_write(uint addr, uint8 data) -> void;
|
||||
|
||||
//scanline() is for debugging purposes only:
|
||||
//boards must detect scanline edges on their own
|
||||
auto scanline(uint y) -> void;
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "mmc1.cpp"
|
||||
#include "mmc3.cpp"
|
||||
#include "mmc5.cpp"
|
||||
#include "mmc6.cpp"
|
||||
#include "vrc1.cpp"
|
||||
#include "vrc2.cpp"
|
||||
#include "vrc3.cpp"
|
||||
#include "vrc4.cpp"
|
||||
#include "vrc6.cpp"
|
||||
#include "vrc7.cpp"
|
||||
|
||||
Chip::Chip(Board& board) : board(board) {
|
||||
}
|
||||
|
||||
auto Chip::tick() -> void {
|
||||
board.tick();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
struct Board;
|
||||
|
||||
struct Chip {
|
||||
Chip(Board& board);
|
||||
auto tick() -> void;
|
||||
|
||||
Board& board;
|
||||
};
|
||||
@@ -1,134 +0,0 @@
|
||||
struct MMC1 : Chip {
|
||||
MMC1(Board& board) : Chip(board) {
|
||||
revision = Revision::MMC1B2;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(writedelay) writedelay--;
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) -> uint {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (prg_bank & ~1) + region;
|
||||
|
||||
if(prg_size) {
|
||||
bank = (region == 0 ? 0x0 : 0xf);
|
||||
if(region != prg_mode) bank = prg_bank;
|
||||
}
|
||||
|
||||
return (bank << 14) | (addr & 0x3fff);
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) -> uint {
|
||||
bool region = addr & 0x1000;
|
||||
uint bank = chr_bank[region];
|
||||
if(chr_mode == 0) bank = (chr_bank[0] & ~1) | region;
|
||||
return (bank << 12) | (addr & 0x0fff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return 0x0000 | (addr & 0x03ff);
|
||||
case 1: return 0x0400 | (addr & 0x03ff);
|
||||
case 2: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
case 3: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto mmio_write(uint addr, uint8 data) -> void {
|
||||
if(writedelay) return;
|
||||
writedelay = 2;
|
||||
|
||||
if(data & 0x80) {
|
||||
shiftaddr = 0;
|
||||
prg_size = 1;
|
||||
prg_mode = 1;
|
||||
} else {
|
||||
shiftdata = ((data & 1) << 4) | (shiftdata >> 1);
|
||||
if(++shiftaddr == 5) {
|
||||
shiftaddr = 0;
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
chr_mode = (shiftdata & 0x10);
|
||||
prg_size = (shiftdata & 0x08);
|
||||
prg_mode = (shiftdata & 0x04);
|
||||
mirror = (shiftdata & 0x03);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
chr_bank[0] = (shiftdata & 0x1f);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
chr_bank[1] = (shiftdata & 0x1f);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
ram_disable = (shiftdata & 0x10);
|
||||
prg_bank = (shiftdata & 0x0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
writedelay = 0;
|
||||
shiftaddr = 0;
|
||||
shiftdata = 0;
|
||||
|
||||
chr_mode = 0;
|
||||
prg_size = 1;
|
||||
prg_mode = 1;
|
||||
mirror = 0;
|
||||
chr_bank[0] = 0;
|
||||
chr_bank[1] = 1;
|
||||
ram_disable = 0;
|
||||
prg_bank = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(writedelay);
|
||||
s.integer(shiftaddr);
|
||||
s.integer(shiftdata);
|
||||
|
||||
s.integer(chr_mode);
|
||||
s.integer(prg_size);
|
||||
s.integer(prg_mode);
|
||||
s.integer(mirror);
|
||||
s.array(chr_bank);
|
||||
s.integer(ram_disable);
|
||||
s.integer(prg_bank);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
MMC1,
|
||||
MMC1A,
|
||||
MMC1B1,
|
||||
MMC1B2,
|
||||
MMC1B3,
|
||||
MMC1C,
|
||||
} revision;
|
||||
|
||||
uint writedelay;
|
||||
uint shiftaddr;
|
||||
uint shiftdata;
|
||||
|
||||
bool chr_mode;
|
||||
bool prg_size; //0 = 32K, 1 = 16K
|
||||
bool prg_mode;
|
||||
uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal
|
||||
uint5 chr_bank[2];
|
||||
bool ram_disable;
|
||||
uint4 prg_bank;
|
||||
};
|
||||
@@ -1,187 +0,0 @@
|
||||
struct MMC3 : Chip {
|
||||
MMC3(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
auto irq_test(uint addr) -> void {
|
||||
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
|
||||
if(irq_delay == 0) {
|
||||
if(irq_counter == 0) {
|
||||
irq_counter = irq_latch;
|
||||
} else if(--irq_counter == 0) {
|
||||
if(irq_enable) irq_line = 1;
|
||||
}
|
||||
}
|
||||
irq_delay = 6;
|
||||
}
|
||||
chr_abus = addr;
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
if(prg_mode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prg_bank[0] << 13) | (addr & 0x1fff);
|
||||
case 1:
|
||||
return (prg_bank[1] << 13) | (addr & 0x1fff);
|
||||
case 2:
|
||||
if(prg_mode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prg_bank[0] << 13) | (addr & 0x1fff);
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
if(chr_mode == 0) {
|
||||
if(addr <= 0x07ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x0fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x13ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
|
||||
} else {
|
||||
if(addr <= 0x03ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x07ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ram_read(uint addr) -> uint8 {
|
||||
if(ram_enable) return board.prgram.data[addr & 0x1fff];
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto ram_write(uint addr, uint8 data) -> void {
|
||||
if(ram_enable && !ram_write_protect) board.prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xe001) {
|
||||
case 0x8000:
|
||||
chr_mode = data & 0x80;
|
||||
prg_mode = data & 0x40;
|
||||
bank_select = data & 0x07;
|
||||
break;
|
||||
|
||||
case 0x8001:
|
||||
switch(bank_select) {
|
||||
case 0: chr_bank[0] = data & ~1; break;
|
||||
case 1: chr_bank[1] = data & ~1; break;
|
||||
case 2: chr_bank[2] = data; break;
|
||||
case 3: chr_bank[3] = data; break;
|
||||
case 4: chr_bank[4] = data; break;
|
||||
case 5: chr_bank[5] = data; break;
|
||||
case 6: prg_bank[0] = data & 0x3f; break;
|
||||
case 7: prg_bank[1] = data & 0x3f; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
ram_enable = data & 0x80;
|
||||
ram_write_protect = data & 0x40;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
irq_latch = data;
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irq_counter = 0;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
irq_enable = false;
|
||||
irq_line = 0;
|
||||
break;
|
||||
|
||||
case 0xe001:
|
||||
irq_enable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chr_mode = 0;
|
||||
prg_mode = 0;
|
||||
bank_select = 0;
|
||||
prg_bank[0] = 0;
|
||||
prg_bank[1] = 0;
|
||||
chr_bank[0] = 0;
|
||||
chr_bank[1] = 0;
|
||||
chr_bank[2] = 0;
|
||||
chr_bank[3] = 0;
|
||||
chr_bank[4] = 0;
|
||||
chr_bank[5] = 0;
|
||||
mirror = 0;
|
||||
ram_enable = 1;
|
||||
ram_write_protect = 0;
|
||||
irq_latch = 0;
|
||||
irq_counter = 0;
|
||||
irq_enable = false;
|
||||
irq_delay = 0;
|
||||
irq_line = 0;
|
||||
|
||||
chr_abus = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(chr_mode);
|
||||
s.integer(prg_mode);
|
||||
s.integer(bank_select);
|
||||
s.array(prg_bank);
|
||||
s.array(chr_bank);
|
||||
s.integer(mirror);
|
||||
s.integer(ram_enable);
|
||||
s.integer(ram_write_protect);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_delay);
|
||||
s.integer(irq_line);
|
||||
|
||||
s.integer(chr_abus);
|
||||
}
|
||||
|
||||
bool chr_mode;
|
||||
bool prg_mode;
|
||||
uint3 bank_select;
|
||||
uint8 prg_bank[2];
|
||||
uint8 chr_bank[6];
|
||||
bool mirror;
|
||||
bool ram_enable;
|
||||
bool ram_write_protect;
|
||||
uint8 irq_latch;
|
||||
uint8 irq_counter;
|
||||
bool irq_enable;
|
||||
uint irq_delay;
|
||||
bool irq_line;
|
||||
|
||||
uint16 chr_abus;
|
||||
};
|
||||
@@ -1,495 +0,0 @@
|
||||
struct MMC5 : Chip {
|
||||
MMC5(Board& board) : Chip(board) {
|
||||
revision = Revision::MMC5;
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
//scanline() resets this; if no scanlines detected, enter video blanking period
|
||||
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
||||
|
||||
cpu.set_irq_line(irq_enable && irq_pending);
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
//used for testing only, to verify MMC5 scanline detection is accurate:
|
||||
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
|
||||
}
|
||||
|
||||
auto prg_access(bool write, uint addr, uint8 data = 0x00) -> uint8 {
|
||||
uint bank;
|
||||
|
||||
if((addr & 0xe000) == 0x6000) {
|
||||
bank = (ram_select << 2) | ram_bank;
|
||||
addr &= 0x1fff;
|
||||
} else if(prg_mode == 0) {
|
||||
bank = prg_bank[3] & ~3;
|
||||
addr &= 0x7fff;
|
||||
} else if(prg_mode == 1) {
|
||||
if((addr & 0xc000) == 0x8000) bank = (prg_bank[1] & ~1);
|
||||
if((addr & 0xe000) == 0xc000) bank = (prg_bank[3] & ~1);
|
||||
addr &= 0x3fff;
|
||||
} else if(prg_mode == 2) {
|
||||
if((addr & 0xe000) == 0x8000) bank = (prg_bank[1] & ~1) | 0;
|
||||
if((addr & 0xe000) == 0xa000) bank = (prg_bank[1] & ~1) | 1;
|
||||
if((addr & 0xe000) == 0xc000) bank = (prg_bank[2]);
|
||||
if((addr & 0xe000) == 0xe000) bank = (prg_bank[3]);
|
||||
addr &= 0x1fff;
|
||||
} else if(prg_mode == 3) {
|
||||
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
|
||||
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
|
||||
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
|
||||
if((addr & 0xe000) == 0xe000) bank = prg_bank[3];
|
||||
addr &= 0x1fff;
|
||||
}
|
||||
|
||||
bool rom = bank & 0x80;
|
||||
bank &= 0x7f;
|
||||
|
||||
if(write == false) {
|
||||
if(rom) {
|
||||
return board.prgrom.read((bank << 13) | addr);
|
||||
} else {
|
||||
return board.prgram.read((bank << 13) | addr);
|
||||
}
|
||||
} else {
|
||||
if(rom) {
|
||||
board.prgrom.write((bank << 13) | addr, data);
|
||||
} else {
|
||||
if(prgram_write_protect[0] == 2 && prgram_write_protect[1] == 1) {
|
||||
board.prgram.write((bank << 13) | addr, data);
|
||||
}
|
||||
}
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
if(exram_mode >= 2) return exram[addr & 0x03ff];
|
||||
return cpu.mdr();
|
||||
}
|
||||
|
||||
if(addr >= 0x6000) {
|
||||
return prg_access(0, addr);
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x5204: {
|
||||
uint8 result = (irq_pending << 7) | (in_frame << 6);
|
||||
irq_pending = false;
|
||||
return result;
|
||||
}
|
||||
case 0x5205: return (multiplier * multiplicand) >> 0;
|
||||
case 0x5206: return (multiplier * multiplicand) >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
//writes 0x00 *during* Vblank (not during screen rendering ...)
|
||||
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : 0x00;
|
||||
if(exram_mode == 2) exram[addr & 0x03ff] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr >= 0x6000) {
|
||||
prg_access(1, addr, data);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x2000:
|
||||
sprite_8x16 = data & 0x20;
|
||||
break;
|
||||
|
||||
case 0x2001:
|
||||
//if BG+sprites are disabled; enter video blanking period
|
||||
if((data & 0x18) == 0) blank();
|
||||
break;
|
||||
|
||||
case 0x5100: prg_mode = data & 3; break;
|
||||
case 0x5101: chr_mode = data & 3; break;
|
||||
|
||||
case 0x5102: prgram_write_protect[0] = data & 3; break;
|
||||
case 0x5103: prgram_write_protect[1] = data & 3; break;
|
||||
|
||||
case 0x5104:
|
||||
exram_mode = data & 3;
|
||||
break;
|
||||
|
||||
case 0x5105:
|
||||
nametable_mode[0] = (data & 0x03) >> 0;
|
||||
nametable_mode[1] = (data & 0x0c) >> 2;
|
||||
nametable_mode[2] = (data & 0x30) >> 4;
|
||||
nametable_mode[3] = (data & 0xc0) >> 6;
|
||||
break;
|
||||
|
||||
case 0x5106:
|
||||
fillmode_tile = data;
|
||||
break;
|
||||
|
||||
case 0x5107:
|
||||
fillmode_color = data & 3;
|
||||
fillmode_color |= fillmode_color << 2;
|
||||
fillmode_color |= fillmode_color << 4;
|
||||
break;
|
||||
|
||||
case 0x5113:
|
||||
ram_select = data & 0x04;
|
||||
ram_bank = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0x5114: prg_bank[0] = data; break;
|
||||
case 0x5115: prg_bank[1] = data; break;
|
||||
case 0x5116: prg_bank[2] = data; break;
|
||||
case 0x5117: prg_bank[3] = data | 0x80; break;
|
||||
|
||||
case 0x5120: chr_sprite_bank[0] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5121: chr_sprite_bank[1] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5122: chr_sprite_bank[2] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5123: chr_sprite_bank[3] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5124: chr_sprite_bank[4] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5125: chr_sprite_bank[5] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5126: chr_sprite_bank[6] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
case 0x5127: chr_sprite_bank[7] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
||||
|
||||
case 0x5128: chr_bg_bank[0] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
||||
case 0x5129: chr_bg_bank[1] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
||||
case 0x512a: chr_bg_bank[2] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
||||
case 0x512b: chr_bg_bank[3] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
||||
|
||||
case 0x5130:
|
||||
chr_bank_hi = data & 3;
|
||||
break;
|
||||
|
||||
case 0x5200:
|
||||
vs_enable = data & 0x80;
|
||||
vs_side = data & 0x40;
|
||||
vs_tile = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x5201:
|
||||
vs_scroll = data;
|
||||
break;
|
||||
|
||||
case 0x5202:
|
||||
vs_bank = data;
|
||||
break;
|
||||
|
||||
case 0x5203:
|
||||
irq_line = data;
|
||||
break;
|
||||
|
||||
case 0x5204:
|
||||
irq_enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0x5205:
|
||||
multiplicand = data;
|
||||
break;
|
||||
|
||||
case 0x5206:
|
||||
multiplier = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_sprite_addr(uint addr) -> uint {
|
||||
if(chr_mode == 0) {
|
||||
auto bank = chr_sprite_bank[7];
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
if(chr_mode == 1) {
|
||||
auto bank = chr_sprite_bank[(addr / 0x1000) * 4 + 3];
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chr_mode == 2) {
|
||||
auto bank = chr_sprite_bank[(addr / 0x0800) * 2 + 1];
|
||||
return (bank * 0x0800) + (addr & 0x07ff);
|
||||
}
|
||||
|
||||
if(chr_mode == 3) {
|
||||
auto bank = chr_sprite_bank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_bg_addr(uint addr) -> uint {
|
||||
addr &= 0x0fff;
|
||||
|
||||
if(chr_mode == 0) {
|
||||
auto bank = chr_bg_bank[3];
|
||||
return (bank * 0x2000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chr_mode == 1) {
|
||||
auto bank = chr_bg_bank[3];
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
if(chr_mode == 2) {
|
||||
auto bank = chr_bg_bank[(addr / 0x0800) * 2 + 1];
|
||||
return (bank * 0x0800) + (addr & 0x07ff);
|
||||
}
|
||||
|
||||
if(chr_mode == 3) {
|
||||
auto bank = chr_bg_bank[(addr / 0x0400)];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_vs_addr(uint addr) -> uint {
|
||||
return (vs_bank * 0x1000) + (addr & 0x0ff8) + (vs_vpos & 7);
|
||||
}
|
||||
|
||||
auto blank() -> void {
|
||||
in_frame = false;
|
||||
}
|
||||
|
||||
auto scanline() -> void {
|
||||
hcounter = 0;
|
||||
|
||||
if(in_frame == false) {
|
||||
in_frame = true;
|
||||
irq_pending = false;
|
||||
vcounter = 0;
|
||||
} else {
|
||||
if(vcounter == irq_line) irq_pending = true;
|
||||
vcounter++;
|
||||
}
|
||||
|
||||
cpu_cycle_counter = 0;
|
||||
}
|
||||
|
||||
auto ciram_read(uint addr) -> uint8 {
|
||||
if(vs_fetch && (hcounter & 2) == 0) return exram[vs_vpos / 8 * 32 + vs_hpos / 8];
|
||||
if(vs_fetch && (hcounter & 2) != 0) return exram[vs_vpos / 32 * 8 + vs_hpos / 32 + 0x03c0];
|
||||
|
||||
switch(nametable_mode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
|
||||
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
|
||||
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : 0x00;
|
||||
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_read(uint addr) -> uint8 {
|
||||
chr_access[0] = chr_access[1];
|
||||
chr_access[1] = chr_access[2];
|
||||
chr_access[2] = chr_access[3];
|
||||
chr_access[3] = addr;
|
||||
|
||||
//detect two unused nametable fetches at end of each scanline
|
||||
if((chr_access[0] & 0x2000) == 0
|
||||
&& (chr_access[1] & 0x2000)
|
||||
&& (chr_access[2] & 0x2000)
|
||||
&& (chr_access[3] & 0x2000)) scanline();
|
||||
|
||||
if(in_frame == false) {
|
||||
vs_fetch = false;
|
||||
if(addr & 0x2000) return ciram_read(addr);
|
||||
return board.chrrom.read(chr_active ? chr_bg_addr(addr) : chr_sprite_addr(addr));
|
||||
}
|
||||
|
||||
bool bg_fetch = (hcounter < 256 || hcounter >= 320);
|
||||
uint8 result = 0x00;
|
||||
|
||||
if((hcounter & 7) == 0) {
|
||||
vs_hpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
|
||||
vs_vpos = vcounter + vs_scroll;
|
||||
vs_fetch = vs_enable && bg_fetch && exram_mode < 2
|
||||
&& (vs_side ? vs_hpos / 8 >= vs_tile : vs_hpos / 8 < vs_tile);
|
||||
if(vs_vpos >= 240) vs_vpos -= 240;
|
||||
|
||||
result = ciram_read(addr);
|
||||
|
||||
exbank = (chr_bank_hi << 6) | (exram[addr & 0x03ff] & 0x3f);
|
||||
exattr = exram[addr & 0x03ff] >> 6;
|
||||
exattr |= exattr << 2;
|
||||
exattr |= exattr << 4;
|
||||
} else if((hcounter & 7) == 2) {
|
||||
result = ciram_read(addr);
|
||||
if(bg_fetch && exram_mode == 1) result = exattr;
|
||||
} else {
|
||||
if(vs_fetch) result = board.chrrom.read(chr_vs_addr(addr));
|
||||
else if(sprite_8x16 ? bg_fetch : chr_active) result = board.chrrom.read(chr_bg_addr(addr));
|
||||
else result = board.chrrom.read(chr_sprite_addr(addr));
|
||||
if(bg_fetch && exram_mode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
|
||||
}
|
||||
|
||||
hcounter += 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto chr_write(uint addr, uint8 data) -> void {
|
||||
if(addr & 0x2000) {
|
||||
switch(nametable_mode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.ciram_write(0x0000 | (addr & 0x03ff), data);
|
||||
case 1: return ppu.ciram_write(0x0400 | (addr & 0x03ff), data);
|
||||
case 2: exram[addr & 0x03ff] = data; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : exram) n = 0xff;
|
||||
|
||||
prg_mode = 3;
|
||||
chr_mode = 0;
|
||||
for(auto& n : prgram_write_protect) n = 0;
|
||||
exram_mode = 0;
|
||||
for(auto& n : nametable_mode) n = 0;
|
||||
fillmode_tile = 0;
|
||||
fillmode_color = 0;
|
||||
ram_select = 0;
|
||||
ram_bank = 0;
|
||||
prg_bank[0] = 0x00;
|
||||
prg_bank[1] = 0x00;
|
||||
prg_bank[2] = 0x00;
|
||||
prg_bank[3] = 0xff;
|
||||
for(auto& n : chr_sprite_bank) n = 0;
|
||||
for(auto& n : chr_bg_bank) n = 0;
|
||||
chr_bank_hi = 0;
|
||||
vs_enable = 0;
|
||||
vs_side = 0;
|
||||
vs_tile = 0;
|
||||
vs_scroll = 0;
|
||||
vs_bank = 0;
|
||||
irq_line = 0;
|
||||
irq_enable = 0;
|
||||
multiplicand = 0;
|
||||
multiplier = 0;
|
||||
|
||||
cpu_cycle_counter = 0;
|
||||
irq_counter = 0;
|
||||
irq_pending = 0;
|
||||
in_frame = 0;
|
||||
vcounter = 0;
|
||||
hcounter = 0;
|
||||
for(auto& n : chr_access) n = 0;
|
||||
chr_active = 0;
|
||||
sprite_8x16 = 0;
|
||||
|
||||
exbank = 0;
|
||||
exattr = 0;
|
||||
|
||||
vs_fetch = 0;
|
||||
vs_vpos = 0;
|
||||
vs_hpos = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(exram);
|
||||
|
||||
s.integer(prg_mode);
|
||||
s.integer(chr_mode);
|
||||
for(auto& n : prgram_write_protect) s.integer(n);
|
||||
s.integer(exram_mode);
|
||||
for(auto& n : nametable_mode) s.integer(n);
|
||||
s.integer(fillmode_tile);
|
||||
s.integer(fillmode_color);
|
||||
s.integer(ram_select);
|
||||
s.integer(ram_bank);
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
for(auto& n : chr_sprite_bank) s.integer(n);
|
||||
for(auto& n : chr_bg_bank) s.integer(n);
|
||||
s.integer(chr_bank_hi);
|
||||
s.integer(vs_enable);
|
||||
s.integer(vs_side);
|
||||
s.integer(vs_tile);
|
||||
s.integer(vs_scroll);
|
||||
s.integer(vs_bank);
|
||||
s.integer(irq_line);
|
||||
s.integer(irq_enable);
|
||||
s.integer(multiplicand);
|
||||
s.integer(multiplier);
|
||||
|
||||
s.integer(cpu_cycle_counter);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_pending);
|
||||
s.integer(in_frame);
|
||||
|
||||
s.integer(vcounter);
|
||||
s.integer(hcounter);
|
||||
for(auto& n : chr_access) s.integer(n);
|
||||
s.integer(chr_active);
|
||||
s.integer(sprite_8x16);
|
||||
|
||||
s.integer(exbank);
|
||||
s.integer(exattr);
|
||||
|
||||
s.integer(vs_fetch);
|
||||
s.integer(vs_vpos);
|
||||
s.integer(vs_hpos);
|
||||
}
|
||||
|
||||
enum class Revision : uint {
|
||||
MMC5,
|
||||
MMC5B,
|
||||
} revision;
|
||||
|
||||
uint8 exram[1024];
|
||||
|
||||
//programmable registers
|
||||
|
||||
uint2 prg_mode; //$5100
|
||||
uint2 chr_mode; //$5101
|
||||
|
||||
uint2 prgram_write_protect[2]; //$5102,$5103
|
||||
|
||||
uint2 exram_mode; //$5104
|
||||
uint2 nametable_mode[4]; //$5105
|
||||
uint8 fillmode_tile; //$5106
|
||||
uint8 fillmode_color; //$5107
|
||||
|
||||
bool ram_select; //$5113
|
||||
uint2 ram_bank; //$5113
|
||||
uint8 prg_bank[4]; //$5114-5117
|
||||
uint10 chr_sprite_bank[8]; //$5120-5127
|
||||
uint10 chr_bg_bank[4]; //$5128-512b
|
||||
uint2 chr_bank_hi; //$5130
|
||||
|
||||
bool vs_enable; //$5200
|
||||
bool vs_side; //$5200
|
||||
uint5 vs_tile; //$5200
|
||||
uint8 vs_scroll; //$5201
|
||||
uint8 vs_bank; //$5202
|
||||
|
||||
uint8 irq_line; //$5203
|
||||
bool irq_enable; //$5204
|
||||
|
||||
uint8 multiplicand; //$5205
|
||||
uint8 multiplier; //$5206
|
||||
|
||||
//status registers
|
||||
|
||||
uint cpu_cycle_counter;
|
||||
uint irq_counter;
|
||||
bool irq_pending;
|
||||
bool in_frame;
|
||||
|
||||
uint vcounter;
|
||||
uint hcounter;
|
||||
uint16 chr_access[4];
|
||||
bool chr_active;
|
||||
bool sprite_8x16;
|
||||
|
||||
uint8 exbank;
|
||||
uint8 exattr;
|
||||
|
||||
bool vs_fetch;
|
||||
uint8 vs_vpos;
|
||||
uint8 vs_hpos;
|
||||
};
|
||||
@@ -1,198 +0,0 @@
|
||||
struct MMC6 : Chip {
|
||||
MMC6(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
auto irq_test(uint addr) -> void {
|
||||
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
|
||||
if(irq_delay == 0) {
|
||||
if(irq_counter == 0) {
|
||||
irq_counter = irq_latch;
|
||||
} else if(--irq_counter == 0) {
|
||||
if(irq_enable) irq_line = 1;
|
||||
}
|
||||
}
|
||||
irq_delay = 6;
|
||||
}
|
||||
chr_abus = addr;
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
switch((addr >> 13) & 3) {
|
||||
case 0:
|
||||
if(prg_mode == 1) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prg_bank[0] << 13) | (addr & 0x1fff);
|
||||
case 1:
|
||||
return (prg_bank[1] << 13) | (addr & 0x1fff);
|
||||
case 2:
|
||||
if(prg_mode == 0) return (0x3e << 13) | (addr & 0x1fff);
|
||||
return (prg_bank[0] << 13) | (addr & 0x1fff);
|
||||
case 3:
|
||||
return (0x3f << 13) | (addr & 0x1fff);
|
||||
}
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
if(chr_mode == 0) {
|
||||
if(addr <= 0x07ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x0fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x13ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x1fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
|
||||
} else {
|
||||
if(addr <= 0x03ff) return (chr_bank[2] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x07ff) return (chr_bank[3] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0bff) return (chr_bank[4] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x0fff) return (chr_bank[5] << 10) | (addr & 0x03ff);
|
||||
if(addr <= 0x17ff) return (chr_bank[0] << 10) | (addr & 0x07ff);
|
||||
if(addr <= 0x1fff) return (chr_bank[1] << 10) | (addr & 0x07ff);
|
||||
}
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
|
||||
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ram_read(uint addr) -> uint8 {
|
||||
if(ram_enable == false) return cpu.mdr();
|
||||
if(ram_readable[0] == false && ram_readable[1] == false) return cpu.mdr();
|
||||
bool region = addr & 0x0200;
|
||||
if(ram_readable[region] == false) return 0x00;
|
||||
return board.prgram.read((region * 0x0200) + (addr & 0x01ff));
|
||||
}
|
||||
|
||||
auto ram_write(uint addr, uint8 data) -> void {
|
||||
if(ram_enable == false) return;
|
||||
bool region = addr & 0x0200;
|
||||
if(ram_writable[region] == false) return;
|
||||
return board.prgram.write((region * 0x0200) + (addr & 0x01ff), data);
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xe001) {
|
||||
case 0x8000:
|
||||
chr_mode = data & 0x80;
|
||||
prg_mode = data & 0x40;
|
||||
ram_enable = data & 0x20;
|
||||
bank_select = data & 0x07;
|
||||
if(ram_enable == false) {
|
||||
for(auto &n : ram_readable) n = false;
|
||||
for(auto &n : ram_writable) n = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x8001:
|
||||
switch(bank_select) {
|
||||
case 0: chr_bank[0] = data & ~1; break;
|
||||
case 1: chr_bank[1] = data & ~1; break;
|
||||
case 2: chr_bank[2] = data; break;
|
||||
case 3: chr_bank[3] = data; break;
|
||||
case 4: chr_bank[4] = data; break;
|
||||
case 5: chr_bank[5] = data; break;
|
||||
case 6: prg_bank[0] = data & 0x3f; break;
|
||||
case 7: prg_bank[1] = data & 0x3f; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
if(ram_enable == false) break;
|
||||
ram_readable[1] = data & 0x80;
|
||||
ram_writable[1] = data & 0x40;
|
||||
ram_readable[0] = data & 0x20;
|
||||
ram_writable[0] = data & 0x10;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
irq_latch = data;
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irq_counter = 0;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
irq_enable = false;
|
||||
irq_line = 0;
|
||||
break;
|
||||
|
||||
case 0xe001:
|
||||
irq_enable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
chr_mode = 0;
|
||||
prg_mode = 0;
|
||||
ram_enable = 0;
|
||||
bank_select = 0;
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
mirror = 0;
|
||||
for(auto& n : ram_readable) n = 0;
|
||||
for(auto& n : ram_writable) n = 0;
|
||||
irq_latch = 0;
|
||||
irq_counter = 0;
|
||||
irq_enable = 0;
|
||||
irq_delay = 0;
|
||||
irq_line = 0;
|
||||
|
||||
chr_abus = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(chr_mode);
|
||||
s.integer(prg_mode);
|
||||
s.integer(ram_enable);
|
||||
s.integer(bank_select);
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
for(auto& n : chr_bank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
for(auto& n : ram_readable) s.integer(n);
|
||||
for(auto& n : ram_writable) s.integer(n);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_delay);
|
||||
s.integer(irq_line);
|
||||
|
||||
s.integer(chr_abus);
|
||||
}
|
||||
|
||||
bool chr_mode;
|
||||
bool prg_mode;
|
||||
bool ram_enable;
|
||||
uint3 bank_select;
|
||||
uint8 prg_bank[2];
|
||||
uint8 chr_bank[6];
|
||||
bool mirror;
|
||||
bool ram_readable[2];
|
||||
bool ram_writable[2];
|
||||
uint8 irq_latch;
|
||||
uint8 irq_counter;
|
||||
bool irq_enable;
|
||||
uint irq_delay;
|
||||
bool irq_line;
|
||||
|
||||
uint16 chr_abus;
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
struct VRC1 : Chip {
|
||||
VRC1(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
uint bank = 0x0f;
|
||||
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
|
||||
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
|
||||
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_banklo[(bool)(addr & 0x1000)];
|
||||
bank |= chr_bankhi[(bool)(addr & 0x1000)] << 4;
|
||||
return (bank * 0x1000) + (addr & 0x0fff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xf000) {
|
||||
case 0x8000:
|
||||
prg_bank[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
chr_bankhi[1] = data & 0x04;
|
||||
chr_bankhi[0] = data & 0x02;
|
||||
mirror = data & 0x01;
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
prg_bank[1] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
prg_bank[2] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xe000:
|
||||
chr_banklo[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
chr_banklo[1] = data & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_banklo) n = 0;
|
||||
for(auto& n : chr_bankhi) n = 0;
|
||||
mirror = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
for(auto& n : chr_banklo) s.integer(n);
|
||||
for(auto& n : chr_bankhi) s.integer(n);
|
||||
s.integer(mirror);
|
||||
}
|
||||
|
||||
uint4 prg_bank[3];
|
||||
uint4 chr_banklo[2];
|
||||
bool chr_bankhi[2];
|
||||
bool mirror;
|
||||
};
|
||||
@@ -1,108 +0,0 @@
|
||||
struct VRC2 : Chip {
|
||||
VRC2(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
uint bank;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prg_bank[0]; break;
|
||||
case 0xa000: bank = prg_bank[1]; break;
|
||||
case 0xc000: bank = 0x1e; break;
|
||||
case 0xe000: bank = 0x1f; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_bank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto ram_read(uint addr) -> uint8 {
|
||||
if(board.prgram.size == 0) {
|
||||
if((addr & 0xf000) == 0x6000) return cpu.mdr() | latch;
|
||||
return cpu.mdr();
|
||||
}
|
||||
return board.prgram.read(addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto ram_write(uint addr, uint8 data) -> void {
|
||||
if(board.prgram.size == 0) {
|
||||
if((addr & 0xf000) == 0x6000) latch = data & 0x01;
|
||||
return;
|
||||
}
|
||||
return board.prgram.write(addr & 0x1fff, data);
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prg_bank[0] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x9000: case 0x9001: case 0x9002: case 0x9003:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
|
||||
prg_bank[1] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xb002: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb003: chr_bank[1] = (chr_bank[1] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc000: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc001: chr_bank[2] = (chr_bank[2] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc002: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc003: chr_bank[3] = (chr_bank[3] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd000: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd001: chr_bank[4] = (chr_bank[4] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd002: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd003: chr_bank[5] = (chr_bank[5] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe000: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe001: chr_bank[6] = (chr_bank[6] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe002: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe003: chr_bank[7] = (chr_bank[7] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
mirror = 0;
|
||||
latch = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
for(auto& n : chr_bank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
s.integer(latch);
|
||||
}
|
||||
|
||||
uint5 prg_bank[2];
|
||||
uint8 chr_bank[8];
|
||||
uint2 mirror;
|
||||
bool latch;
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
struct VRC3 : Chip {
|
||||
VRC3(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) { //16-bit
|
||||
if(++irq_counter.w == 0) {
|
||||
irq_line = 1;
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_counter.w = irq_latch;
|
||||
}
|
||||
}
|
||||
if(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();
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
uint bank = (addr < 0xc000 ? (uint)prg_bank : 0x0f);
|
||||
return (bank * 0x4000) + (addr & 0x3fff);
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
switch(addr & 0xf000) {
|
||||
case 0x8000: irq_latch = (irq_latch & 0xfff0) | ((data & 0x0f) << 0); break;
|
||||
case 0x9000: irq_latch = (irq_latch & 0xff0f) | ((data & 0x0f) << 4); break;
|
||||
case 0xa000: irq_latch = (irq_latch & 0xf0ff) | ((data & 0x0f) << 8); break;
|
||||
case 0xb000: irq_latch = (irq_latch & 0x0fff) | ((data & 0x0f) << 12); break;
|
||||
|
||||
case 0xc000:
|
||||
irq_mode = data & 0x04;
|
||||
irq_enable = data & 0x02;
|
||||
irq_acknowledge = data & 0x01;
|
||||
if(irq_enable) irq_counter.w = irq_latch;
|
||||
break;
|
||||
|
||||
case 0xd000:
|
||||
irq_line = 0;
|
||||
irq_enable = irq_acknowledge;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
prg_bank = data & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank = 0;
|
||||
irq_mode = 0;
|
||||
irq_enable = 0;
|
||||
irq_acknowledge = 0;
|
||||
irq_latch = 0;
|
||||
irq_counter.w = 0;
|
||||
irq_line = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(prg_bank);
|
||||
s.integer(irq_mode);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_acknowledge);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_counter.w);
|
||||
s.integer(irq_line);
|
||||
}
|
||||
|
||||
uint4 prg_bank;
|
||||
bool irq_mode;
|
||||
bool irq_enable;
|
||||
bool irq_acknowledge;
|
||||
uint16 irq_latch;
|
||||
struct {
|
||||
union {
|
||||
uint16 w;
|
||||
struct { uint8 order_lsb2(l, h); };
|
||||
};
|
||||
} irq_counter;
|
||||
bool irq_line;
|
||||
};
|
||||
@@ -1,182 +0,0 @@
|
||||
struct VRC4 : Chip {
|
||||
VRC4(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
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) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
uint bank = 0, banks = board.prgrom.size / 0x2000;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prg_mode == 0 ? (unsigned)prg_bank[0] : banks - 2; break;
|
||||
case 0xa000: bank = prg_bank[1]; break;
|
||||
case 0xc000: bank = prg_mode == 0 ? banks - 2 : (unsigned)prg_bank[0]; break;
|
||||
case 0xe000: bank = banks - 1; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_bank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prg_bank[0] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0x9000: case 0x9001:
|
||||
mirror = data & 0x03;
|
||||
break;
|
||||
|
||||
case 0x9002: case 0x9003:
|
||||
prg_mode = data & 0x02;
|
||||
break;
|
||||
|
||||
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
|
||||
prg_bank[1] = data & 0x1f;
|
||||
break;
|
||||
|
||||
case 0xb000: chr_bank[0] = (chr_bank[0] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb001: chr_bank[0] = (chr_bank[0] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xb002: chr_bank[1] = (chr_bank[1] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xb003: chr_bank[1] = (chr_bank[1] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc000: chr_bank[2] = (chr_bank[2] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc001: chr_bank[2] = (chr_bank[2] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xc002: chr_bank[3] = (chr_bank[3] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xc003: chr_bank[3] = (chr_bank[3] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd000: chr_bank[4] = (chr_bank[4] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd001: chr_bank[4] = (chr_bank[4] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xd002: chr_bank[5] = (chr_bank[5] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xd003: chr_bank[5] = (chr_bank[5] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe000: chr_bank[6] = (chr_bank[6] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe001: chr_bank[6] = (chr_bank[6] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xe002: chr_bank[7] = (chr_bank[7] & 0xf0) | ((data & 0x0f) << 0); break;
|
||||
case 0xe003: chr_bank[7] = (chr_bank[7] & 0x0f) | ((data & 0x0f) << 4); break;
|
||||
|
||||
case 0xf000:
|
||||
irq_latch = (irq_latch & 0xf0) | ((data & 0x0f) << 0);
|
||||
break;
|
||||
|
||||
case 0xf001:
|
||||
irq_latch = (irq_latch & 0x0f) | ((data & 0x0f) << 4);
|
||||
break;
|
||||
|
||||
case 0xf002:
|
||||
irq_mode = data & 0x04;
|
||||
irq_enable = data & 0x02;
|
||||
irq_acknowledge = data & 0x01;
|
||||
if(irq_enable) {
|
||||
irq_counter = irq_latch;
|
||||
irq_scalar = 341;
|
||||
}
|
||||
irq_line = 0;
|
||||
break;
|
||||
|
||||
case 0xf003:
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_line = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_mode = 0;
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
mirror = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
|
||||
irq_latch = 0;
|
||||
irq_mode = 0;
|
||||
irq_enable = 0;
|
||||
irq_acknowledge = 0;
|
||||
|
||||
irq_counter = 0;
|
||||
irq_scalar = 0;
|
||||
irq_line = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(prg_mode);
|
||||
for(auto& n : prg_bank) s.integer(n);
|
||||
s.integer(mirror);
|
||||
for(auto& n : chr_bank) s.integer(n);
|
||||
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_mode);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_acknowledge);
|
||||
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_scalar);
|
||||
s.integer(irq_line);
|
||||
}
|
||||
|
||||
bool prg_mode;
|
||||
uint5 prg_bank[2];
|
||||
uint2 mirror;
|
||||
uint8 chr_bank[8];
|
||||
|
||||
uint8 irq_latch;
|
||||
bool irq_mode;
|
||||
bool irq_enable;
|
||||
bool irq_acknowledge;
|
||||
|
||||
uint8 irq_counter;
|
||||
int irq_scalar;
|
||||
bool irq_line;
|
||||
};
|
||||
@@ -1,319 +0,0 @@
|
||||
struct VRC6 : Chip {
|
||||
VRC6(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
struct Pulse {
|
||||
auto clock() -> void {
|
||||
if(--divider == 0) {
|
||||
divider = frequency + 1;
|
||||
cycle++;
|
||||
output = (mode == 1 || cycle > duty) ? volume : (uint4)0;
|
||||
}
|
||||
|
||||
if(enable == false) output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(mode);
|
||||
s.integer(duty);
|
||||
s.integer(volume);
|
||||
s.integer(enable);
|
||||
s.integer(frequency);
|
||||
|
||||
s.integer(divider);
|
||||
s.integer(cycle);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
bool mode;
|
||||
uint3 duty;
|
||||
uint4 volume;
|
||||
bool enable;
|
||||
uint12 frequency;
|
||||
|
||||
uint12 divider;
|
||||
uint4 cycle;
|
||||
uint4 output;
|
||||
} pulse1, pulse2;
|
||||
|
||||
struct Sawtooth {
|
||||
auto clock() -> void {
|
||||
if(--divider == 0) {
|
||||
divider = frequency + 1;
|
||||
if(++phase == 0) {
|
||||
accumulator += rate;
|
||||
if(++stage == 7) {
|
||||
stage = 0;
|
||||
accumulator = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output = accumulator >> 3;
|
||||
if(enable == false) output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(rate);
|
||||
s.integer(enable);
|
||||
s.integer(frequency);
|
||||
|
||||
s.integer(divider);
|
||||
s.integer(phase);
|
||||
s.integer(stage);
|
||||
s.integer(accumulator);
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
uint6 rate;
|
||||
bool enable;
|
||||
uint12 frequency;
|
||||
|
||||
uint12 divider;
|
||||
uint1 phase;
|
||||
uint3 stage;
|
||||
uint8 accumulator;
|
||||
uint5 output;
|
||||
} sawtooth;
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
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) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if((addr & 0xc000) == 0x8000) return (prg_bank[0] << 14) | (addr & 0x3fff);
|
||||
if((addr & 0xe000) == 0xc000) return (prg_bank[1] << 13) | (addr & 0x1fff);
|
||||
if((addr & 0xe000) == 0xe000) return ( 0xff << 13) | (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_bank[(addr >> 10) & 7];
|
||||
return (bank << 10) | (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
}
|
||||
|
||||
auto ram_read(uint addr) -> uint8 {
|
||||
return board.prgram.data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
auto ram_write(uint addr, uint8 data) -> void {
|
||||
board.prgram.data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
||||
prg_bank[0] = data;
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
pulse1.mode = data & 0x80;
|
||||
pulse1.duty = (data & 0x70) >> 4;
|
||||
pulse1.volume = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x9001:
|
||||
pulse1.frequency = (pulse1.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0x9002:
|
||||
pulse1.frequency = (pulse1.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
pulse1.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
pulse2.mode = data & 0x80;
|
||||
pulse2.duty = (data & 0x70) >> 4;
|
||||
pulse2.volume = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0xa001:
|
||||
pulse2.frequency = (pulse2.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0xa002:
|
||||
pulse2.frequency = (pulse2.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
pulse2.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xb000:
|
||||
sawtooth.rate = data & 0x3f;
|
||||
break;
|
||||
|
||||
case 0xb001:
|
||||
sawtooth.frequency = (sawtooth.frequency & 0x0f00) | ((data & 0xff) << 0);
|
||||
break;
|
||||
|
||||
case 0xb002:
|
||||
sawtooth.frequency = (sawtooth.frequency & 0x00ff) | ((data & 0x0f) << 8);
|
||||
sawtooth.enable = data & 0x80;
|
||||
break;
|
||||
|
||||
case 0xb003:
|
||||
mirror = (data >> 2) & 3;
|
||||
break;
|
||||
|
||||
case 0xc000: case 0xc001: case 0xc002: case 0xc003:
|
||||
prg_bank[1] = data;
|
||||
break;
|
||||
|
||||
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
|
||||
chr_bank[0 + (addr & 3)] = data;
|
||||
break;
|
||||
|
||||
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
|
||||
chr_bank[4 + (addr & 3)] = data;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
irq_latch = data;
|
||||
break;
|
||||
|
||||
case 0xf001:
|
||||
irq_mode = data & 0x04;
|
||||
irq_enable = data & 0x02;
|
||||
irq_acknowledge = data & 0x01;
|
||||
if(irq_enable) {
|
||||
irq_counter = irq_latch;
|
||||
irq_scalar = 341;
|
||||
}
|
||||
irq_line = 0;
|
||||
break;
|
||||
|
||||
case 0xf002:
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_line = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
prg_bank[0] = 0;
|
||||
prg_bank[1] = 0;
|
||||
chr_bank[0] = 0;
|
||||
chr_bank[1] = 0;
|
||||
chr_bank[2] = 0;
|
||||
chr_bank[3] = 0;
|
||||
chr_bank[4] = 0;
|
||||
chr_bank[5] = 0;
|
||||
chr_bank[6] = 0;
|
||||
chr_bank[7] = 0;
|
||||
mirror = 0;
|
||||
irq_latch = 0;
|
||||
irq_mode = 0;
|
||||
irq_enable = 0;
|
||||
irq_acknowledge = 0;
|
||||
|
||||
irq_counter = 0;
|
||||
irq_scalar = 0;
|
||||
irq_line = 0;
|
||||
|
||||
pulse1.mode = 0;
|
||||
pulse1.duty = 0;
|
||||
pulse1.volume = 0;
|
||||
pulse1.enable = 0;
|
||||
pulse1.frequency = 0;
|
||||
|
||||
pulse1.divider = 1;
|
||||
pulse1.cycle = 0;
|
||||
pulse1.output = 0;
|
||||
|
||||
pulse2.mode = 0;
|
||||
pulse2.duty = 0;
|
||||
pulse2.volume = 0;
|
||||
pulse2.enable = 0;
|
||||
pulse2.frequency = 0;
|
||||
|
||||
pulse2.divider = 1;
|
||||
pulse2.cycle = 0;
|
||||
pulse2.output = 0;
|
||||
|
||||
sawtooth.rate = 0;
|
||||
sawtooth.enable = 0;
|
||||
sawtooth.frequency = 0;
|
||||
|
||||
sawtooth.divider = 1;
|
||||
sawtooth.phase = 0;
|
||||
sawtooth.stage = 0;
|
||||
sawtooth.accumulator = 0;
|
||||
sawtooth.output = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
pulse1.serialize(s);
|
||||
pulse2.serialize(s);
|
||||
sawtooth.serialize(s);
|
||||
|
||||
s.array(prg_bank);
|
||||
s.array(chr_bank);
|
||||
s.integer(mirror);
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_mode);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_acknowledge);
|
||||
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_scalar);
|
||||
s.integer(irq_line);
|
||||
}
|
||||
|
||||
uint8 prg_bank[2];
|
||||
uint8 chr_bank[8];
|
||||
uint2 mirror;
|
||||
uint8 irq_latch;
|
||||
bool irq_mode;
|
||||
bool irq_enable;
|
||||
bool irq_acknowledge;
|
||||
|
||||
uint8 irq_counter;
|
||||
int irq_scalar;
|
||||
bool irq_line;
|
||||
};
|
||||
@@ -1,152 +0,0 @@
|
||||
//Konami VRC7
|
||||
//Yamaha YM2413 OPLL audio - not emulated
|
||||
|
||||
struct VRC7 : Chip {
|
||||
VRC7(Board& board) : Chip(board) {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
switch(addr) {
|
||||
case 0x8000: prg_bank[0] = data; break;
|
||||
case 0x8010: prg_bank[1] = data; break;
|
||||
case 0x9000: prg_bank[2] = data; break;
|
||||
case 0x9010: break; //APU addr port
|
||||
case 0x9030: break; //APU data port
|
||||
case 0xa000: chr_bank[0] = data; break;
|
||||
case 0xa010: chr_bank[1] = data; break;
|
||||
case 0xb000: chr_bank[2] = data; break;
|
||||
case 0xb010: chr_bank[3] = data; break;
|
||||
case 0xc000: chr_bank[4] = data; break;
|
||||
case 0xc010: chr_bank[5] = data; break;
|
||||
case 0xd000: chr_bank[6] = data; break;
|
||||
case 0xd010: chr_bank[7] = data; break;
|
||||
case 0xe000: mirror = data & 0x03; break;
|
||||
|
||||
case 0xe010:
|
||||
irq_latch = data;
|
||||
break;
|
||||
|
||||
case 0xf000:
|
||||
irq_mode = data & 0x04;
|
||||
irq_enable = data & 0x02;
|
||||
irq_acknowledge = data & 0x01;
|
||||
if(irq_enable) {
|
||||
irq_counter = irq_latch;
|
||||
irq_scalar = 341;
|
||||
}
|
||||
irq_line = 0;
|
||||
break;
|
||||
|
||||
case 0xf010:
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_line = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
uint bank = 0;
|
||||
switch(addr & 0xe000) {
|
||||
case 0x8000: bank = prg_bank[0]; break;
|
||||
case 0xa000: bank = prg_bank[1]; break;
|
||||
case 0xc000: bank = prg_bank[2]; break;
|
||||
case 0xe000: bank = 0xff; break;
|
||||
}
|
||||
return (bank * 0x2000) + (addr & 0x1fff);
|
||||
}
|
||||
|
||||
auto chr_addr(uint addr) const -> uint {
|
||||
uint bank = chr_bank[addr / 0x0400];
|
||||
return (bank * 0x0400) + (addr & 0x03ff);
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
switch(mirror) {
|
||||
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
|
||||
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
|
||||
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
|
||||
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void {
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& n : prg_bank) n = 0;
|
||||
for(auto& n : chr_bank) n = 0;
|
||||
mirror = 0;
|
||||
|
||||
irq_latch = 0;
|
||||
irq_mode = 0;
|
||||
irq_enable = 0;
|
||||
irq_acknowledge = 0;
|
||||
|
||||
irq_counter = 0;
|
||||
irq_scalar = 0;
|
||||
irq_line = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(prg_bank);
|
||||
s.array(chr_bank);
|
||||
s.integer(mirror);
|
||||
|
||||
s.integer(irq_latch);
|
||||
s.integer(irq_mode);
|
||||
s.integer(irq_enable);
|
||||
s.integer(irq_acknowledge);
|
||||
|
||||
s.integer(irq_counter);
|
||||
s.integer(irq_scalar);
|
||||
s.integer(irq_line);
|
||||
}
|
||||
|
||||
uint8 prg_bank[3];
|
||||
uint8 chr_bank[8];
|
||||
uint2 mirror;
|
||||
|
||||
uint8 irq_latch;
|
||||
bool irq_mode;
|
||||
bool irq_enable;
|
||||
bool irq_acknowledge;
|
||||
|
||||
uint8 irq_counter;
|
||||
int irq_scalar;
|
||||
bool irq_line;
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
Cheat cheat;
|
||||
|
||||
auto Cheat::reset() -> void {
|
||||
codes.reset();
|
||||
}
|
||||
|
||||
auto Cheat::append(uint addr, uint data) -> void {
|
||||
codes.append({addr, Unused, data});
|
||||
}
|
||||
|
||||
auto Cheat::append(uint addr, uint comp, uint data) -> void {
|
||||
codes.append({addr, comp, data});
|
||||
}
|
||||
|
||||
auto Cheat::find(uint addr, uint comp) -> maybe<uint> {
|
||||
for(auto& code : codes) {
|
||||
if(code.addr == addr && (code.comp == Unused || code.comp == comp)) {
|
||||
return code.data;
|
||||
}
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
struct Cheat {
|
||||
struct Code {
|
||||
uint addr;
|
||||
uint comp;
|
||||
uint data;
|
||||
};
|
||||
vector<Code> codes;
|
||||
enum : uint { Unused = ~0u };
|
||||
|
||||
alwaysinline auto enable() const -> bool { return codes.size() > 0; }
|
||||
auto reset() -> void;
|
||||
auto append(uint addr, uint data) -> void;
|
||||
auto append(uint addr, uint comp, uint data) -> void;
|
||||
auto find(uint addr, uint comp) -> maybe<uint>;
|
||||
};
|
||||
|
||||
extern Cheat cheat;
|
||||
@@ -1,111 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "timing.cpp"
|
||||
#include "serialization.cpp"
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cpu.main();
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(status.interrupt_pending) {
|
||||
interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
exec();
|
||||
}
|
||||
|
||||
auto CPU::add_clocks(uint clocks) -> void {
|
||||
apu.clock -= clocks;
|
||||
if(apu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(apu.thread);
|
||||
|
||||
ppu.clock -= clocks;
|
||||
if(ppu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(ppu.thread);
|
||||
|
||||
cartridge.clock -= clocks;
|
||||
if(cartridge.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cartridge.thread);
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
R6502::power();
|
||||
|
||||
for(auto addr : range(0x0800)) ram[addr] = 0xff;
|
||||
ram[0x0008] = 0xf7;
|
||||
ram[0x0009] = 0xef;
|
||||
ram[0x000a] = 0xdf;
|
||||
ram[0x000f] = 0xbf;
|
||||
}
|
||||
|
||||
auto CPU::reset() -> void {
|
||||
R6502::reset();
|
||||
create(CPU::Enter, 21477272);
|
||||
|
||||
regs.pc = bus.read(0xfffc) << 0;
|
||||
regs.pc |= bus.read(0xfffd) << 8;
|
||||
|
||||
status.interrupt_pending = false;
|
||||
status.nmi_pending = false;
|
||||
status.nmi_line = 0;
|
||||
status.irq_line = 0;
|
||||
status.irq_apu_line = 0;
|
||||
|
||||
status.rdy_line = 1;
|
||||
status.rdy_addr_valid = false;
|
||||
status.rdy_addr_value = 0x0000;
|
||||
|
||||
status.oam_dma_pending = false;
|
||||
status.oam_dma_page = 0x00;
|
||||
|
||||
status.controller_latch = false;
|
||||
status.controller_port0 = 0;
|
||||
status.controller_port1 = 0;
|
||||
}
|
||||
|
||||
auto CPU::debugger_read(uint16 addr) -> uint8 {
|
||||
return bus.read(addr);
|
||||
}
|
||||
|
||||
auto CPU::ram_read(uint16 addr) -> uint8 {
|
||||
return ram[addr & 0x07ff];
|
||||
}
|
||||
|
||||
auto CPU::ram_write(uint16 addr, uint8 data) -> void {
|
||||
ram[addr & 0x07ff] = data;
|
||||
}
|
||||
|
||||
auto CPU::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0x4016) {
|
||||
return (mdr() & 0xc0) | input.data(0);
|
||||
}
|
||||
|
||||
if(addr == 0x4017) {
|
||||
return (mdr() & 0xc0) | input.data(1);
|
||||
}
|
||||
|
||||
return apu.read(addr);
|
||||
}
|
||||
|
||||
auto CPU::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0x4014) {
|
||||
status.oam_dma_page = data;
|
||||
status.oam_dma_pending = true;
|
||||
}
|
||||
|
||||
if(addr == 0x4016) {
|
||||
input.latch(data & 0x01);
|
||||
}
|
||||
|
||||
return apu.write(addr, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
struct CPU : Processor::R6502, Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto add_clocks(uint clocks) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto debugger_read(uint16 addr) -> uint8;
|
||||
|
||||
auto ram_read(uint16 addr) -> uint8;
|
||||
auto ram_write(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//timing.cpp
|
||||
auto op_read(uint16 addr) -> uint8;
|
||||
auto op_write(uint16 addr, uint8 data) -> void;
|
||||
auto last_cycle() -> void;
|
||||
auto nmi(uint16& vector) -> void;
|
||||
|
||||
auto oam_dma() -> void;
|
||||
|
||||
auto set_nmi_line(bool) -> void;
|
||||
auto set_irq_line(bool) -> void;
|
||||
auto set_irq_apu_line(bool) -> void;
|
||||
|
||||
auto set_rdy_line(bool) -> void;
|
||||
auto set_rdy_addr(bool valid, uint16 value = 0) -> void;
|
||||
|
||||
//protected:
|
||||
uint8 ram[0x0800];
|
||||
|
||||
struct Status {
|
||||
bool interrupt_pending;
|
||||
bool nmi_pending;
|
||||
bool nmi_line;
|
||||
bool irq_line;
|
||||
bool irq_apu_line;
|
||||
|
||||
bool rdy_line;
|
||||
bool rdy_addr_valid;
|
||||
uint16 rdy_addr_value;
|
||||
|
||||
bool oam_dma_pending;
|
||||
uint8 oam_dma_page;
|
||||
|
||||
bool controller_latch;
|
||||
uint controller_port0;
|
||||
uint controller_port1;
|
||||
} status;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
@@ -1,23 +0,0 @@
|
||||
auto CPU::serialize(serializer& s) -> void {
|
||||
R6502::serialize(s);
|
||||
Thread::serialize(s);
|
||||
|
||||
s.array(ram);
|
||||
|
||||
s.integer(status.interrupt_pending);
|
||||
s.integer(status.nmi_pending);
|
||||
s.integer(status.nmi_line);
|
||||
s.integer(status.irq_line);
|
||||
s.integer(status.irq_apu_line);
|
||||
|
||||
s.integer(status.rdy_line);
|
||||
s.integer(status.rdy_addr_valid);
|
||||
s.integer(status.rdy_addr_value);
|
||||
|
||||
s.integer(status.oam_dma_pending);
|
||||
s.integer(status.oam_dma_page);
|
||||
|
||||
s.integer(status.controller_latch);
|
||||
s.integer(status.controller_port0);
|
||||
s.integer(status.controller_port1);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
auto CPU::op_read(uint16 addr) -> uint8 {
|
||||
if(status.oam_dma_pending) {
|
||||
status.oam_dma_pending = false;
|
||||
op_read(addr);
|
||||
oam_dma();
|
||||
}
|
||||
|
||||
while(status.rdy_line == 0) {
|
||||
regs.mdr = bus.read(status.rdy_addr_valid ? status.rdy_addr_value : addr);
|
||||
add_clocks(12);
|
||||
}
|
||||
|
||||
regs.mdr = bus.read(addr);
|
||||
add_clocks(12);
|
||||
return regs.mdr;
|
||||
}
|
||||
|
||||
auto CPU::op_write(uint16 addr, uint8 data) -> void {
|
||||
bus.write(addr, regs.mdr = data);
|
||||
add_clocks(12);
|
||||
}
|
||||
|
||||
auto CPU::last_cycle() -> void {
|
||||
status.interrupt_pending = ((status.irq_line | status.irq_apu_line) & ~regs.p.i) | status.nmi_pending;
|
||||
}
|
||||
|
||||
auto CPU::nmi(uint16& vector) -> void {
|
||||
if(status.nmi_pending) {
|
||||
status.nmi_pending = false;
|
||||
vector = 0xfffa;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::oam_dma() -> void {
|
||||
for(uint n : range(256)) {
|
||||
uint8 data = op_read((status.oam_dma_page << 8) + n);
|
||||
op_write(0x2004, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::set_nmi_line(bool line) -> void {
|
||||
//edge-sensitive (0->1)
|
||||
if(!status.nmi_line && line) status.nmi_pending = true;
|
||||
status.nmi_line = line;
|
||||
}
|
||||
|
||||
auto CPU::set_irq_line(bool line) -> void {
|
||||
//level-sensitive
|
||||
status.irq_line = line;
|
||||
}
|
||||
|
||||
auto CPU::set_irq_apu_line(bool line) -> void {
|
||||
//level-sensitive
|
||||
status.irq_apu_line = line;
|
||||
}
|
||||
|
||||
auto CPU::set_rdy_line(bool line) -> void {
|
||||
status.rdy_line = line;
|
||||
}
|
||||
|
||||
auto CPU::set_rdy_addr(bool valid, uint16 value) -> void {
|
||||
status.rdy_addr_valid = valid;
|
||||
status.rdy_addr_value = value;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <processor/r6502/r6502.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
namespace Info {
|
||||
static const string Name = "bnes";
|
||||
static const uint SerializerVersion = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bnes - Famicom emulator
|
||||
authors: byuu, Ryphecha
|
||||
license: GPLv3
|
||||
project started: 2011-09-05
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
namespace Famicom {
|
||||
struct Thread {
|
||||
~Thread() {
|
||||
if(thread) co_delete(thread);
|
||||
}
|
||||
|
||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||
if(thread) co_delete(thread);
|
||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
||||
this->frequency = frequency;
|
||||
clock = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.integer(frequency);
|
||||
s.integer(clock);
|
||||
}
|
||||
|
||||
cothread_t thread = nullptr;
|
||||
uint frequency = 0;
|
||||
int64 clock = 0;
|
||||
};
|
||||
|
||||
#include <fc/system/system.hpp>
|
||||
#include <fc/scheduler/scheduler.hpp>
|
||||
#include <fc/input/input.hpp>
|
||||
#include <fc/memory/memory.hpp>
|
||||
#include <fc/cartridge/cartridge.hpp>
|
||||
#include <fc/cpu/cpu.hpp>
|
||||
#include <fc/apu/apu.hpp>
|
||||
#include <fc/ppu/ppu.hpp>
|
||||
#include <fc/cheat/cheat.hpp>
|
||||
#include <fc/video/video.hpp>
|
||||
}
|
||||
|
||||
#include <fc/interface/interface.hpp>
|
||||
@@ -1,53 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "serialization.cpp"
|
||||
Input input;
|
||||
|
||||
auto Input::latch(bool data) -> void {
|
||||
latchdata = data;
|
||||
|
||||
if(latchdata == 1) {
|
||||
counter1 = 0;
|
||||
counter2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto Input::data(bool port) -> bool {
|
||||
bool result = 0;
|
||||
|
||||
if(port == 0) {
|
||||
if(port1 == Device::Joypad) {
|
||||
if(counter1 >= 8) return 1;
|
||||
result = interface->inputPoll(0, 0u, counter1);
|
||||
if(latchdata == 0) counter1++;
|
||||
}
|
||||
}
|
||||
|
||||
if(port == 1) {
|
||||
if(port2 == Device::Joypad) {
|
||||
if(counter2 >= 8) return 1;
|
||||
result = interface->inputPoll(1, 0u, counter2);
|
||||
if(latchdata == 0) counter2++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto Input::connect(bool port, Device device) -> void {
|
||||
if(port == 0) port1 = device, counter1 = 0;
|
||||
if(port == 1) port2 = device, counter2 = 0;
|
||||
}
|
||||
|
||||
auto Input::power() -> void {
|
||||
}
|
||||
|
||||
auto Input::reset() -> void {
|
||||
latchdata = 0;
|
||||
counter1 = 0;
|
||||
counter2 = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
struct Input {
|
||||
enum class Device : uint {
|
||||
Joypad,
|
||||
None,
|
||||
};
|
||||
|
||||
auto latch(bool data) -> void;
|
||||
auto data(bool port) -> bool;
|
||||
auto connect(bool port, Device device) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
private:
|
||||
Device port1;
|
||||
Device port2;
|
||||
|
||||
bool latchdata;
|
||||
uint counter1;
|
||||
uint counter2;
|
||||
};
|
||||
|
||||
extern Input input;
|
||||
@@ -1,8 +0,0 @@
|
||||
auto Input::serialize(serializer& s) -> void {
|
||||
s.integer((uint&)port1);
|
||||
s.integer((uint&)port2);
|
||||
|
||||
s.integer(latchdata);
|
||||
s.integer(counter1);
|
||||
s.integer(counter2);
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
Settings settings;
|
||||
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.name = "Famicom";
|
||||
information.width = 256;
|
||||
information.height = 240;
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 8.0 / 7.0;
|
||||
information.resettable = true;
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
media.append({ID::Famicom, "Famicom", "fc", true});
|
||||
|
||||
{ Device device{0, ID::Port1 | ID::Port2, "Controller"};
|
||||
device.input.append({0, 0, "A" });
|
||||
device.input.append({1, 0, "B" });
|
||||
device.input.append({2, 0, "Select"});
|
||||
device.input.append({3, 0, "Start" });
|
||||
device.input.append({4, 0, "Up" });
|
||||
device.input.append({5, 0, "Down" });
|
||||
device.input.append({6, 0, "Left" });
|
||||
device.input.append({7, 0, "Right" });
|
||||
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
port.append({0, "Port 1"});
|
||||
port.append({1, "Port 2"});
|
||||
|
||||
for(auto& device : this->device) {
|
||||
for(auto& port : this->port) {
|
||||
if(device.portmask & (1 << port.id)) {
|
||||
port.device.append(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::manifest() -> string {
|
||||
return cartridge.manifest();
|
||||
}
|
||||
|
||||
auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoFrequency() -> double {
|
||||
return 21477272.0 / (262.0 * 1364.0 - 4.0);
|
||||
}
|
||||
|
||||
auto Interface::audioFrequency() -> double {
|
||||
return 21477272.0 / 12.0;
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return cartridge.loaded();
|
||||
}
|
||||
|
||||
auto Interface::sha256() -> string {
|
||||
return cartridge.sha256();
|
||||
}
|
||||
|
||||
auto Interface::group(uint id) -> uint {
|
||||
switch(id) {
|
||||
case ID::SystemManifest:
|
||||
return 0;
|
||||
case ID::Manifest:
|
||||
case ID::ProgramROM:
|
||||
case ID::ProgramRAM:
|
||||
case ID::CharacterROM:
|
||||
case ID::CharacterRAM:
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> void {
|
||||
cartridge.load();
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
for(auto& memory : cartridge.memory) {
|
||||
saveRequest(memory.id, memory.name);
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::load(uint id, const stream& stream) -> void {
|
||||
if(id == ID::SystemManifest) {
|
||||
system.information.manifest = stream.text();
|
||||
}
|
||||
|
||||
if(id == ID::Manifest) {
|
||||
cartridge.information.markup = stream.text();
|
||||
}
|
||||
|
||||
if(id == ID::ProgramROM) {
|
||||
stream.read(cartridge.board->prgrom.data, min(cartridge.board->prgrom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::ProgramRAM) {
|
||||
stream.read(cartridge.board->prgram.data, min(cartridge.board->prgram.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::CharacterROM) {
|
||||
stream.read(cartridge.board->chrrom.data, min(cartridge.board->chrrom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::CharacterRAM) {
|
||||
stream.read(cartridge.board->chrram.data, min(cartridge.board->chrram.size, stream.size()));
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::save(uint id, const stream& stream) -> void {
|
||||
if(id == ID::ProgramRAM) {
|
||||
stream.write(cartridge.board->prgram.data, cartridge.board->prgram.size);
|
||||
}
|
||||
|
||||
if(id == ID::CharacterRAM) {
|
||||
stream.write(cartridge.board->chrram.data, cartridge.board->chrram.size);
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
cartridge.unload();
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
system.power();
|
||||
}
|
||||
|
||||
auto Interface::reset() -> void {
|
||||
system.reset();
|
||||
}
|
||||
|
||||
auto Interface::run() -> void {
|
||||
system.run();
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
system.runtosave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
auto Interface::unserialize(serializer& s) -> bool {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
auto Interface::cheatSet(const lstring& list) -> void {
|
||||
cheat.reset();
|
||||
for(auto& codeset : list) {
|
||||
lstring codes = codeset.split("+");
|
||||
for(auto& code : codes) {
|
||||
lstring part = code.split("/");
|
||||
if(part.size() == 2) cheat.append(hex(part[0]), hex(part[1]));
|
||||
if(part.size() == 3) cheat.append(hex(part[0]), hex(part[1]), hex(part[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::cap(const string& name) -> bool {
|
||||
if(name == "Color Emulation") return true;
|
||||
if(name == "Scanline Emulation") return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Interface::get(const string& name) -> any {
|
||||
if(name == "Color Emulation") return settings.colorEmulation;
|
||||
if(name == "Scanline Emulation") return settings.scanlineEmulation;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
||||
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
namespace Famicom {
|
||||
|
||||
struct ID {
|
||||
enum : uint {
|
||||
System,
|
||||
Famicom,
|
||||
};
|
||||
|
||||
enum : uint {
|
||||
SystemManifest,
|
||||
|
||||
Manifest,
|
||||
ProgramROM,
|
||||
ProgramRAM,
|
||||
CharacterROM,
|
||||
CharacterRAM,
|
||||
};
|
||||
|
||||
enum : uint {
|
||||
Port1 = 1,
|
||||
Port2 = 2,
|
||||
};
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
Interface();
|
||||
|
||||
auto manifest() -> string;
|
||||
auto title() -> string;
|
||||
auto videoFrequency() -> double;
|
||||
auto audioFrequency() -> double;
|
||||
|
||||
auto loaded() -> bool;
|
||||
auto sha256() -> string;
|
||||
auto group(uint id) -> uint;
|
||||
auto load(uint id) -> void;
|
||||
auto save() -> void;
|
||||
auto load(uint id, const stream& stream) -> void;
|
||||
auto save(uint id, const stream& stream) -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
auto run() -> void;
|
||||
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
auto cheatSet(const lstring&) -> void;
|
||||
|
||||
auto cap(const string& name) -> bool override;
|
||||
auto get(const string& name) -> any override;
|
||||
auto set(const string& name, const any& value) -> bool override;
|
||||
|
||||
private:
|
||||
vector<Device> device;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
bool colorEmulation = true;
|
||||
bool scanlineEmulation = true;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
Bus bus;
|
||||
|
||||
//$0000-07ff = RAM (2KB)
|
||||
//$0800-1fff = RAM (mirror)
|
||||
//$2000-2007 = PPU
|
||||
//$2008-3fff = PPU (mirror)
|
||||
//$4000-4017 = APU + I/O
|
||||
//$4018-ffff = Cartridge
|
||||
|
||||
auto Bus::read(uint16 addr) -> uint8 {
|
||||
uint8 data = cartridge.prg_read(addr);
|
||||
if(addr <= 0x1fff) data = cpu.ram_read(addr);
|
||||
else if(addr <= 0x3fff) data = ppu.read(addr);
|
||||
else if(addr <= 0x4017) data = cpu.read(addr);
|
||||
|
||||
if(cheat.enable()) {
|
||||
if(auto result = cheat.find(addr, data)) return result();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
auto Bus::write(uint16 addr, uint8 data) -> void {
|
||||
cartridge.prg_write(addr, data);
|
||||
if(addr <= 0x1fff) return cpu.ram_write(addr, data);
|
||||
if(addr <= 0x3fff) return ppu.write(addr, data);
|
||||
if(addr <= 0x4017) return cpu.write(addr, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
struct Bus {
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
};
|
||||
|
||||
extern Bus bus;
|
||||
@@ -1,487 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "serialization.cpp"
|
||||
PPU ppu;
|
||||
|
||||
auto PPU::Main() -> void {
|
||||
ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::PPU) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
raster_scanline();
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::tick() -> void {
|
||||
if(status.ly == 240 && status.lx == 340) status.nmi_hold = 1;
|
||||
if(status.ly == 241 && status.lx == 0) status.nmi_flag = status.nmi_hold;
|
||||
if(status.ly == 241 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
|
||||
|
||||
if(status.ly == 260 && status.lx == 340) status.sprite_zero_hit = 0, status.sprite_overflow = 0;
|
||||
|
||||
if(status.ly == 260 && status.lx == 340) status.nmi_hold = 0;
|
||||
if(status.ly == 261 && status.lx == 0) status.nmi_flag = status.nmi_hold;
|
||||
if(status.ly == 261 && status.lx == 2) cpu.set_nmi_line(status.nmi_enable && status.nmi_flag);
|
||||
|
||||
clock += 4;
|
||||
if(clock >= 0) co_switch(cpu.thread);
|
||||
|
||||
status.lx++;
|
||||
}
|
||||
|
||||
auto PPU::scanline() -> void {
|
||||
status.lx = 0;
|
||||
if(++status.ly == 262) {
|
||||
status.ly = 0;
|
||||
frame();
|
||||
}
|
||||
cartridge.scanline(status.ly);
|
||||
}
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
status.field ^= 1;
|
||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
}
|
||||
|
||||
auto PPU::reset() -> void {
|
||||
create(PPU::Main, 21477272);
|
||||
|
||||
status.mdr = 0x00;
|
||||
status.field = 0;
|
||||
status.ly = 0;
|
||||
status.bus_data = 0x00;
|
||||
status.address_latch = 0;
|
||||
|
||||
status.vaddr = 0x0000;
|
||||
status.taddr = 0x0000;
|
||||
status.xaddr = 0x00;
|
||||
|
||||
status.nmi_hold = 0;
|
||||
status.nmi_flag = 0;
|
||||
|
||||
//$2000
|
||||
status.nmi_enable = false;
|
||||
status.master_select = 0;
|
||||
status.sprite_size = 0;
|
||||
status.bg_addr = 0x0000;
|
||||
status.sprite_addr = 0x0000;
|
||||
status.vram_increment = 1;
|
||||
|
||||
//$2001
|
||||
status.emphasis = 0;
|
||||
status.sprite_enable = false;
|
||||
status.bg_enable = false;
|
||||
status.sprite_edge_enable = false;
|
||||
status.bg_edge_enable = false;
|
||||
status.grayscale = false;
|
||||
|
||||
//$2002
|
||||
status.sprite_zero_hit = false;
|
||||
status.sprite_overflow = false;
|
||||
|
||||
//$2003
|
||||
status.oam_addr = 0x00;
|
||||
|
||||
for(auto& n : buffer) n = 0;
|
||||
for(auto& n : ciram ) n = 0;
|
||||
for(auto& n : cgram ) n = 0;
|
||||
for(auto& n : oam ) n = 0;
|
||||
}
|
||||
|
||||
auto PPU::read(uint16 addr) -> uint8 {
|
||||
uint8 result = 0x00;
|
||||
|
||||
switch(addr & 7) {
|
||||
case 2: //PPUSTATUS
|
||||
result |= status.nmi_flag << 7;
|
||||
result |= status.sprite_zero_hit << 6;
|
||||
result |= status.sprite_overflow << 5;
|
||||
result |= status.mdr & 0x1f;
|
||||
status.address_latch = 0;
|
||||
status.nmi_hold = 0;
|
||||
cpu.set_nmi_line(status.nmi_flag = 0);
|
||||
break;
|
||||
case 4: //OAMDATA
|
||||
result = oam[status.oam_addr];
|
||||
if((status.oam_addr & 3) == 3) result &= 0xe3;
|
||||
break;
|
||||
case 7: //PPUDATA
|
||||
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00;
|
||||
|
||||
addr = status.vaddr & 0x3fff;
|
||||
if(addr <= 0x1fff) {
|
||||
result = status.bus_data;
|
||||
status.bus_data = cartridge.chr_read(addr);
|
||||
} else if(addr <= 0x3eff) {
|
||||
result = status.bus_data;
|
||||
status.bus_data = cartridge.chr_read(addr);
|
||||
} else if(addr <= 0x3fff) {
|
||||
result = cgram_read(addr);
|
||||
status.bus_data = cartridge.chr_read(addr);
|
||||
}
|
||||
status.vaddr += status.vram_increment;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto PPU::write(uint16 addr, uint8 data) -> void {
|
||||
status.mdr = data;
|
||||
|
||||
switch(addr & 7) {
|
||||
case 0: //PPUCTRL
|
||||
status.nmi_enable = data & 0x80;
|
||||
status.master_select = data & 0x40;
|
||||
status.sprite_size = data & 0x20;
|
||||
status.bg_addr = (data & 0x10) ? 0x1000 : 0x0000;
|
||||
status.sprite_addr = (data & 0x08) ? 0x1000 : 0x0000;
|
||||
status.vram_increment = (data & 0x04) ? 32 : 1;
|
||||
status.taddr = (status.taddr & 0x73ff) | ((data & 0x03) << 10);
|
||||
cpu.set_nmi_line(status.nmi_enable && status.nmi_hold && status.nmi_flag);
|
||||
return;
|
||||
case 1: //PPUMASK
|
||||
status.emphasis = data >> 5;
|
||||
status.sprite_enable = data & 0x10;
|
||||
status.bg_enable = data & 0x08;
|
||||
status.sprite_edge_enable = data & 0x04;
|
||||
status.bg_edge_enable = data & 0x02;
|
||||
status.grayscale = data & 0x01;
|
||||
return;
|
||||
case 2: //PPUSTATUS
|
||||
return;
|
||||
case 3: //OAMADDR
|
||||
status.oam_addr = data;
|
||||
return;
|
||||
case 4: //OAMDATA
|
||||
oam[status.oam_addr++] = data;
|
||||
return;
|
||||
case 5: //PPUSCROLL
|
||||
if(status.address_latch == 0) {
|
||||
status.xaddr = data & 0x07;
|
||||
status.taddr = (status.taddr & 0x7fe0) | (data >> 3);
|
||||
} else {
|
||||
status.taddr = (status.taddr & 0x0c1f) | ((data & 0x07) << 12) | ((data >> 3) << 5);
|
||||
}
|
||||
status.address_latch ^= 1;
|
||||
return;
|
||||
case 6: //PPUADDR
|
||||
if(status.address_latch == 0) {
|
||||
status.taddr = (status.taddr & 0x00ff) | ((data & 0x3f) << 8);
|
||||
} else {
|
||||
status.taddr = (status.taddr & 0x7f00) | data;
|
||||
status.vaddr = status.taddr;
|
||||
}
|
||||
status.address_latch ^= 1;
|
||||
return;
|
||||
case 7: //PPUDATA
|
||||
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return;
|
||||
|
||||
addr = status.vaddr & 0x3fff;
|
||||
if(addr <= 0x1fff) {
|
||||
cartridge.chr_write(addr, data);
|
||||
} else if(addr <= 0x3eff) {
|
||||
cartridge.chr_write(addr, data);
|
||||
} else if(addr <= 0x3fff) {
|
||||
cgram_write(addr, data);
|
||||
}
|
||||
status.vaddr += status.vram_increment;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::ciram_read(uint16 addr) -> uint8 {
|
||||
return ciram[addr & 0x07ff];
|
||||
}
|
||||
|
||||
auto PPU::ciram_write(uint16 addr, uint8 data) -> void {
|
||||
ciram[addr & 0x07ff] = data;
|
||||
}
|
||||
|
||||
auto PPU::cgram_read(uint16 addr) -> uint8 {
|
||||
if((addr & 0x13) == 0x10) addr &= ~0x10;
|
||||
uint8 data = cgram[addr & 0x1f];
|
||||
if(status.grayscale) data &= 0x30;
|
||||
return data;
|
||||
}
|
||||
|
||||
auto PPU::cgram_write(uint16 addr, uint8 data) -> void {
|
||||
if((addr & 0x13) == 0x10) addr &= ~0x10;
|
||||
cgram[addr & 0x1f] = data;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
//vaddr = 0yyy VHYY YYYX XXXX
|
||||
//yyy = fine Yscroll (y:d0-d2)
|
||||
//V = V nametable (y:d8)
|
||||
//H = H nametable (x:d8)
|
||||
//YYYYY = Y nametable (y:d3-d7)
|
||||
//XXXXX = X nametable (x:d3-d7)
|
||||
|
||||
auto PPU::raster_enable() const -> bool {
|
||||
return (status.bg_enable || status.sprite_enable);
|
||||
}
|
||||
|
||||
auto PPU::nametable_addr() const -> uint {
|
||||
return 0x2000 + (status.vaddr & 0x0c00);
|
||||
}
|
||||
|
||||
auto PPU::scrollx() const -> uint {
|
||||
return ((status.vaddr & 0x1f) << 3) | status.xaddr;
|
||||
}
|
||||
|
||||
auto PPU::scrolly() const -> uint {
|
||||
return (((status.vaddr >> 5) & 0x1f) << 3) | ((status.vaddr >> 12) & 7);
|
||||
}
|
||||
|
||||
auto PPU::sprite_height() const -> uint {
|
||||
return status.sprite_size == 0 ? 8 : 16;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto PPU::chr_load(uint16 addr) -> uint8 {
|
||||
if(raster_enable() == false) return 0x00;
|
||||
return cartridge.chr_read(addr);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto PPU::scrollx_increment() -> void {
|
||||
if(raster_enable() == false) return;
|
||||
status.vaddr = (status.vaddr & 0x7fe0) | ((status.vaddr + 0x0001) & 0x001f);
|
||||
if((status.vaddr & 0x001f) == 0x0000) {
|
||||
status.vaddr ^= 0x0400;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::scrolly_increment() -> void {
|
||||
if(raster_enable() == false) return;
|
||||
status.vaddr = (status.vaddr & 0x0fff) | ((status.vaddr + 0x1000) & 0x7000);
|
||||
if((status.vaddr & 0x7000) == 0x0000) {
|
||||
status.vaddr = (status.vaddr & 0x7c1f) | ((status.vaddr + 0x0020) & 0x03e0);
|
||||
if((status.vaddr & 0x03e0) == 0x03c0) { //0x03c0 == 30 << 5; 30 * 8 = 240
|
||||
status.vaddr &= 0x7c1f;
|
||||
status.vaddr ^= 0x0800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto PPU::raster_pixel() -> void {
|
||||
uint32* output = buffer + status.ly * 256;
|
||||
|
||||
uint mask = 0x8000 >> (status.xaddr + (status.lx & 7));
|
||||
uint palette = 0, object_palette = 0;
|
||||
bool object_priority = 0;
|
||||
palette |= (raster.tiledatalo & mask) ? 1 : 0;
|
||||
palette |= (raster.tiledatahi & mask) ? 2 : 0;
|
||||
if(palette) {
|
||||
uint attr = raster.attribute;
|
||||
if(mask >= 256) attr >>= 2;
|
||||
palette |= (attr & 3) << 2;
|
||||
}
|
||||
|
||||
if(status.bg_enable == false) palette = 0;
|
||||
if(status.bg_edge_enable == false && status.lx < 8) palette = 0;
|
||||
|
||||
if(status.sprite_enable == true)
|
||||
for(int sprite = 7; sprite >= 0; sprite--) {
|
||||
if(status.sprite_edge_enable == false && status.lx < 8) continue;
|
||||
if(raster.oam[sprite].id == 64) continue;
|
||||
|
||||
uint spritex = status.lx - raster.oam[sprite].x;
|
||||
if(spritex >= 8) continue;
|
||||
|
||||
if(raster.oam[sprite].attr & 0x40) spritex ^= 7;
|
||||
uint mask = 0x80 >> spritex;
|
||||
uint sprite_palette = 0;
|
||||
sprite_palette |= (raster.oam[sprite].tiledatalo & mask) ? 1 : 0;
|
||||
sprite_palette |= (raster.oam[sprite].tiledatahi & mask) ? 2 : 0;
|
||||
if(sprite_palette == 0) continue;
|
||||
|
||||
if(raster.oam[sprite].id == 0 && palette && status.lx != 255) status.sprite_zero_hit = 1;
|
||||
sprite_palette |= (raster.oam[sprite].attr & 3) << 2;
|
||||
|
||||
object_priority = raster.oam[sprite].attr & 0x20;
|
||||
object_palette = 16 + sprite_palette;
|
||||
}
|
||||
|
||||
if(object_palette) {
|
||||
if(palette == 0 || object_priority == 0) palette = object_palette;
|
||||
}
|
||||
|
||||
if(raster_enable() == false) palette = 0;
|
||||
output[status.lx] = (status.emphasis << 6) | cgram_read(palette);
|
||||
}
|
||||
|
||||
auto PPU::raster_sprite() -> void {
|
||||
if(raster_enable() == false) return;
|
||||
|
||||
uint n = raster.oam_iterator++;
|
||||
int ly = (status.ly == 261 ? -1 : status.ly);
|
||||
uint y = ly - oam[(n * 4) + 0];
|
||||
|
||||
if(y >= sprite_height()) return;
|
||||
if(raster.oam_counter == 8) {
|
||||
status.sprite_overflow = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
raster.soam[raster.oam_counter].id = n;
|
||||
raster.soam[raster.oam_counter].y = oam[(n * 4) + 0];
|
||||
raster.soam[raster.oam_counter].tile = oam[(n * 4) + 1];
|
||||
raster.soam[raster.oam_counter].attr = oam[(n * 4) + 2];
|
||||
raster.soam[raster.oam_counter].x = oam[(n * 4) + 3];
|
||||
raster.oam_counter++;
|
||||
}
|
||||
|
||||
auto PPU::raster_scanline() -> void {
|
||||
if((status.ly >= 240 && status.ly <= 260)) {
|
||||
for(auto x : range(341)) tick();
|
||||
return scanline();
|
||||
}
|
||||
|
||||
raster.oam_iterator = 0;
|
||||
raster.oam_counter = 0;
|
||||
|
||||
for(auto n : range(8)) {
|
||||
raster.soam[n].id = 64;
|
||||
raster.soam[n].y = 0xff;
|
||||
raster.soam[n].tile = 0xff;
|
||||
raster.soam[n].attr = 0xff;
|
||||
raster.soam[n].x = 0xff;
|
||||
raster.soam[n].tiledatalo = 0;
|
||||
raster.soam[n].tiledatahi = 0;
|
||||
}
|
||||
|
||||
for(uint tile : range(32)) { // 0-255
|
||||
uint nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
uint tileaddr = status.bg_addr + (nametable << 4) + (scrolly() & 7);
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
uint attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
|
||||
if(scrolly() & 16) attribute >>= 4;
|
||||
if(scrollx() & 16) attribute >>= 2;
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
scrollx_increment();
|
||||
if(tile == 31) scrolly_increment();
|
||||
raster_pixel();
|
||||
raster_sprite();
|
||||
tick();
|
||||
|
||||
uint tiledatalo = chr_load(tileaddr + 0);
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
uint tiledatahi = chr_load(tileaddr + 8);
|
||||
raster_pixel();
|
||||
tick();
|
||||
|
||||
raster_pixel();
|
||||
raster_sprite();
|
||||
tick();
|
||||
|
||||
raster.nametable = (raster.nametable << 8) | nametable;
|
||||
raster.attribute = (raster.attribute << 2) | (attribute & 3);
|
||||
raster.tiledatalo = (raster.tiledatalo << 8) | tiledatalo;
|
||||
raster.tiledatahi = (raster.tiledatahi << 8) | tiledatahi;
|
||||
}
|
||||
|
||||
for(auto n : range(8)) raster.oam[n] = raster.soam[n];
|
||||
|
||||
for(uint sprite : range(8)) { //256-319
|
||||
uint nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
tick();
|
||||
|
||||
if(raster_enable() && sprite == 0) status.vaddr = (status.vaddr & 0x7be0) | (status.taddr & 0x041f); //257
|
||||
tick();
|
||||
|
||||
uint attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
|
||||
uint tileaddr = (sprite_height() == 8)
|
||||
? status.sprite_addr + raster.oam[sprite].tile * 16
|
||||
: ((raster.oam[sprite].tile & ~1) * 16) + ((raster.oam[sprite].tile & 1) * 0x1000);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
uint spritey = (status.ly - raster.oam[sprite].y) & (sprite_height() - 1);
|
||||
if(raster.oam[sprite].attr & 0x80) spritey ^= (sprite_height() - 1);
|
||||
tileaddr += spritey + (spritey & 8);
|
||||
|
||||
raster.oam[sprite].tiledatalo = chr_load(tileaddr + 0);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
raster.oam[sprite].tiledatahi = chr_load(tileaddr + 8);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
if(raster_enable() && sprite == 6 && status.ly == 261) status.vaddr = status.taddr; //304
|
||||
}
|
||||
|
||||
for(uint tile : range(2)) { //320-335
|
||||
uint nametable = chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
uint tileaddr = status.bg_addr + (nametable << 4) + (scrolly() & 7);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
uint attribute = chr_load(0x23c0 | (status.vaddr & 0x0fc0) | ((scrolly() >> 5) << 3) | (scrollx() >> 5));
|
||||
if(scrolly() & 16) attribute >>= 4;
|
||||
if(scrollx() & 16) attribute >>= 2;
|
||||
tick();
|
||||
|
||||
scrollx_increment();
|
||||
tick();
|
||||
|
||||
uint tiledatalo = chr_load(tileaddr + 0);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
uint tiledatahi = chr_load(tileaddr + 8);
|
||||
tick();
|
||||
tick();
|
||||
|
||||
raster.nametable = (raster.nametable << 8) | nametable;
|
||||
raster.attribute = (raster.attribute << 2) | (attribute & 3);
|
||||
raster.tiledatalo = (raster.tiledatalo << 8) | tiledatalo;
|
||||
raster.tiledatahi = (raster.tiledatahi << 8) | tiledatahi;
|
||||
}
|
||||
|
||||
//336-339
|
||||
chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
tick();
|
||||
bool skip = (raster_enable() && status.field == 1 && status.ly == 261);
|
||||
tick();
|
||||
|
||||
chr_load(0x2000 | (status.vaddr & 0x0fff));
|
||||
tick();
|
||||
tick();
|
||||
|
||||
//340
|
||||
if(skip == false) tick();
|
||||
|
||||
return scanline();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
struct PPU : Thread {
|
||||
static auto Main() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
|
||||
auto scanline() -> void;
|
||||
auto frame() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto ciram_read(uint16 addr) -> uint8;
|
||||
auto ciram_write(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto cgram_read(uint16 addr) -> uint8;
|
||||
auto cgram_write(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto raster_enable() const -> bool;
|
||||
auto nametable_addr() const -> uint;
|
||||
auto scrollx() const -> uint;
|
||||
auto scrolly() const -> uint;
|
||||
auto sprite_height() const -> uint;
|
||||
|
||||
auto chr_load(uint16 addr) -> uint8;
|
||||
|
||||
auto scrollx_increment() -> void;
|
||||
auto scrolly_increment() -> void;
|
||||
|
||||
auto raster_pixel() -> void;
|
||||
auto raster_sprite() -> void;
|
||||
auto raster_scanline() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Status {
|
||||
uint8 mdr;
|
||||
|
||||
bool field;
|
||||
uint lx;
|
||||
uint ly;
|
||||
|
||||
uint8 bus_data;
|
||||
|
||||
bool address_latch;
|
||||
|
||||
uint15 vaddr;
|
||||
uint15 taddr;
|
||||
uint8 xaddr;
|
||||
|
||||
bool nmi_hold;
|
||||
bool nmi_flag;
|
||||
|
||||
//$2000
|
||||
bool nmi_enable;
|
||||
bool master_select;
|
||||
bool sprite_size;
|
||||
uint bg_addr;
|
||||
uint sprite_addr;
|
||||
uint vram_increment;
|
||||
|
||||
//$2001
|
||||
uint3 emphasis;
|
||||
bool sprite_enable;
|
||||
bool bg_enable;
|
||||
bool sprite_edge_enable;
|
||||
bool bg_edge_enable;
|
||||
bool grayscale;
|
||||
|
||||
//$2002
|
||||
bool sprite_zero_hit;
|
||||
bool sprite_overflow;
|
||||
|
||||
//$2003
|
||||
uint8 oam_addr;
|
||||
} status;
|
||||
|
||||
struct Raster {
|
||||
uint16 nametable;
|
||||
uint16 attribute;
|
||||
uint16 tiledatalo;
|
||||
uint16 tiledatahi;
|
||||
|
||||
uint oam_iterator;
|
||||
uint oam_counter;
|
||||
|
||||
struct OAM {
|
||||
uint8 id;
|
||||
uint8 y;
|
||||
uint8 tile;
|
||||
uint8 attr;
|
||||
uint8 x;
|
||||
|
||||
uint8 tiledatalo;
|
||||
uint8 tiledatahi;
|
||||
} oam[8], soam[8];
|
||||
} raster;
|
||||
|
||||
uint32 buffer[256 * 262];
|
||||
uint8 ciram[2048];
|
||||
uint8 cgram[32];
|
||||
uint8 oam[256];
|
||||
};
|
||||
|
||||
extern PPU ppu;
|
||||
@@ -1,74 +0,0 @@
|
||||
auto PPU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
s.integer(status.mdr);
|
||||
|
||||
s.integer(status.field);
|
||||
s.integer(status.lx);
|
||||
s.integer(status.ly);
|
||||
|
||||
s.integer(status.bus_data);
|
||||
|
||||
s.integer(status.address_latch);
|
||||
|
||||
s.integer(status.vaddr);
|
||||
s.integer(status.taddr);
|
||||
s.integer(status.xaddr);
|
||||
|
||||
s.integer(status.nmi_hold);
|
||||
s.integer(status.nmi_flag);
|
||||
|
||||
s.integer(status.nmi_enable);
|
||||
s.integer(status.master_select);
|
||||
s.integer(status.sprite_size);
|
||||
s.integer(status.bg_addr);
|
||||
s.integer(status.sprite_addr);
|
||||
s.integer(status.vram_increment);
|
||||
|
||||
s.integer(status.emphasis);
|
||||
s.integer(status.sprite_enable);
|
||||
s.integer(status.bg_enable);
|
||||
s.integer(status.sprite_edge_enable);
|
||||
s.integer(status.bg_edge_enable);
|
||||
s.integer(status.grayscale);
|
||||
|
||||
s.integer(status.sprite_zero_hit);
|
||||
s.integer(status.sprite_overflow);
|
||||
|
||||
s.integer(status.oam_addr);
|
||||
|
||||
s.integer(raster.nametable);
|
||||
s.integer(raster.attribute);
|
||||
s.integer(raster.tiledatalo);
|
||||
s.integer(raster.tiledatahi);
|
||||
|
||||
s.integer(raster.oam_iterator);
|
||||
s.integer(raster.oam_counter);
|
||||
|
||||
for(auto n : range(8)) {
|
||||
s.integer(raster.oam[n].id);
|
||||
s.integer(raster.oam[n].y);
|
||||
s.integer(raster.oam[n].tile);
|
||||
s.integer(raster.oam[n].attr);
|
||||
s.integer(raster.oam[n].x);
|
||||
|
||||
s.integer(raster.oam[n].tiledatalo);
|
||||
s.integer(raster.oam[n].tiledatahi);
|
||||
}
|
||||
|
||||
for(auto n : range(8)) {
|
||||
s.integer(raster.soam[n].id);
|
||||
s.integer(raster.soam[n].y);
|
||||
s.integer(raster.soam[n].tile);
|
||||
s.integer(raster.soam[n].attr);
|
||||
s.integer(raster.soam[n].x);
|
||||
|
||||
s.integer(raster.soam[n].tiledatalo);
|
||||
s.integer(raster.soam[n].tiledatahi);
|
||||
}
|
||||
|
||||
s.array(buffer);
|
||||
s.array(ciram);
|
||||
s.array(cgram);
|
||||
s.array(oam);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
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 {
|
||||
host_thread = co_active();
|
||||
thread = cpu.thread;
|
||||
sync = SynchronizeMode::None;
|
||||
exit_reason = ExitReason::UnknownEvent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
struct Scheduler : property<Scheduler> {
|
||||
enum class SynchronizeMode : uint { None, PPU, All } sync;
|
||||
enum class ExitReason : uint { UnknownEvent, FrameEvent, SynchronizeEvent };
|
||||
|
||||
auto enter() -> void;
|
||||
auto exit(ExitReason) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
cothread_t host_thread; //program thread (used to exit emulation)
|
||||
cothread_t thread; //active emulation thread (used to enter emulation)
|
||||
readonly<ExitReason> exit_reason;
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
||||
@@ -1,60 +0,0 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(serialize_size);
|
||||
|
||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||
char hash[64], description[512];
|
||||
memcpy(&hash, (const char*)cartridge.sha256(), 64);
|
||||
memset(&description, 0, sizeof description);
|
||||
|
||||
s.integer(signature);
|
||||
s.integer(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
auto System::unserialize(serializer& s) -> bool {
|
||||
uint signature, version;
|
||||
char hash[64], description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.integer(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
if(signature != 0x31545342) return false;
|
||||
if(version != Info::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serialize_all(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s) -> void {
|
||||
}
|
||||
|
||||
auto System::serialize_all(serializer& s) -> void {
|
||||
system.serialize(s);
|
||||
input.serialize(s);
|
||||
cartridge.serialize(s);
|
||||
cpu.serialize(s);
|
||||
apu.serialize(s);
|
||||
ppu.serialize(s);
|
||||
}
|
||||
|
||||
auto System::serialize_init() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0, version = 0;
|
||||
char hash[64], description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.integer(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serialize_size = s.size();
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
auto System::run() -> void {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
auto System::runtosave() -> void {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::PPU;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
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 {
|
||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
serialize_init();
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
cartridge.power();
|
||||
cpu.power();
|
||||
apu.power();
|
||||
ppu.power();
|
||||
input.reset();
|
||||
scheduler.power();
|
||||
reset();
|
||||
}
|
||||
|
||||
auto System::reset() -> void {
|
||||
cartridge.reset();
|
||||
cpu.reset();
|
||||
apu.reset();
|
||||
ppu.reset();
|
||||
input.reset();
|
||||
scheduler.reset();
|
||||
video.reset();
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
assert(interface != 0);
|
||||
input.connect(0, Input::Device::Joypad);
|
||||
input.connect(1, Input::Device::None);
|
||||
}
|
||||
|
||||
auto System::term() -> void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
struct System {
|
||||
auto run() -> void;
|
||||
auto runtosave() -> void;
|
||||
auto runthreadtosave() -> void;
|
||||
|
||||
auto load() -> void;
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto term() -> void;
|
||||
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
auto serialize_all(serializer&) -> void;
|
||||
auto serialize_init() -> void;
|
||||
|
||||
struct Information {
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
uint serialize_size;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
@@ -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 +0,0 @@
|
||||
gb_objects := gb-interface gb-system gb-scheduler
|
||||
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/)
|
||||
obj/gb-system.o: $(gb)/system/system.cpp $(call rwildcard,$(gb)/system/)
|
||||
obj/gb-scheduler.o: $(gb)/scheduler/scheduler.cpp $(call rwildcard,$(gb)/scheduler/)
|
||||
obj/gb-cartridge.o: $(gb)/cartridge/cartridge.cpp $(call rwildcard,$(gb)/cartridge/)
|
||||
obj/gb-memory.o: $(gb)/memory/memory.cpp $(call rwildcard,$(gb)/memory/)
|
||||
obj/gb-cpu.o: $(gb)/cpu/cpu.cpp $(call rwildcard,$(gb)/cpu/)
|
||||
obj/gb-ppu.o: $(gb)/ppu/ppu.cpp $(call rwildcard,$(gb)/ppu/)
|
||||
obj/gb-apu.o: $(gb)/apu/apu.cpp $(call rwildcard,$(gb)/apu/)
|
||||
obj/gb-cheat.o: $(gb)/cheat/cheat.cpp $(call rwildcard,$(gb)/cheat/)
|
||||
obj/gb-video.o: $(gb)/video/video.cpp $(call rwildcard,$(gb)/video/)
|
||||
@@ -1,114 +0,0 @@
|
||||
#include <gb/gb.hpp>
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
#include "sequencer/sequencer.cpp"
|
||||
#include "square1/square1.cpp"
|
||||
#include "square2/square2.cpp"
|
||||
#include "wave/wave.cpp"
|
||||
#include "noise/noise.cpp"
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
auto APU::Main() -> void {
|
||||
apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
square1.run();
|
||||
square2.run();
|
||||
wave.run();
|
||||
noise.run();
|
||||
sequencer.run();
|
||||
|
||||
hipass(sequencer.center, sequencer.centerBias);
|
||||
hipass(sequencer.left, sequencer.leftBias);
|
||||
hipass(sequencer.right, sequencer.rightBias);
|
||||
|
||||
interface->audioSample(sequencer.left, sequencer.right);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//filter to remove DC bias
|
||||
auto APU::hipass(int16& sample, int64& bias) -> void {
|
||||
bias += ((((int64)sample << 16) - (bias >> 16)) * 57593) >> 16;
|
||||
sample = sclamp<16>(sample - (bias >> 32));
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
create(Main, 2 * 1024 * 1024);
|
||||
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
||||
|
||||
square1.power();
|
||||
square2.power();
|
||||
wave.power();
|
||||
noise.power();
|
||||
sequencer.power();
|
||||
phase = 0;
|
||||
cycle = 0;
|
||||
|
||||
LinearFeedbackShiftRegisterGenerator r;
|
||||
for(auto& n : wave.pattern) n = r();
|
||||
}
|
||||
|
||||
auto APU::mmio_read(uint16 addr) -> uint8 {
|
||||
if(addr >= 0xff10 && addr <= 0xff14) return square1.read(addr);
|
||||
if(addr >= 0xff15 && addr <= 0xff19) return square2.read(addr);
|
||||
if(addr >= 0xff1a && addr <= 0xff1e) return wave.read(addr);
|
||||
if(addr >= 0xff1f && addr <= 0xff23) return noise.read(addr);
|
||||
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.read(addr);
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) return wave.read(addr);
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||
if(!sequencer.enable) {
|
||||
bool valid = addr == 0xff26; //NR52
|
||||
if(!system.cgb()) {
|
||||
//NRx1 length is writable only on DMG,SGB; not on CGB
|
||||
if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0)
|
||||
if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)
|
||||
if(addr == 0xff1b) valid = true; //NR31
|
||||
if(addr == 0xff20) valid = true; //NR41
|
||||
}
|
||||
if(!valid) return;
|
||||
}
|
||||
|
||||
if(addr >= 0xff10 && addr <= 0xff14) return square1.write(addr, data);
|
||||
if(addr >= 0xff15 && addr <= 0xff19) return square2.write(addr, data);
|
||||
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write(addr, data);
|
||||
if(addr >= 0xff1f && addr <= 0xff23) return noise.write(addr, data);
|
||||
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.write(addr, data);
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) return wave.write(addr, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
struct APU : Thread, MMIO {
|
||||
static auto Main() -> void;
|
||||
auto main() -> void;
|
||||
auto hipass(int16& sample, int64& bias) -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto mmio_read(uint16 addr) -> uint8;
|
||||
auto mmio_write(uint16 addr, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
#include "square1/square1.hpp"
|
||||
#include "square2/square2.hpp"
|
||||
#include "wave/wave.hpp"
|
||||
#include "noise/noise.hpp"
|
||||
#include "sequencer/sequencer.hpp"
|
||||
|
||||
Square1 square1;
|
||||
Square2 square2;
|
||||
Wave wave;
|
||||
Noise noise;
|
||||
Sequencer sequencer;
|
||||
|
||||
uint3 phase; //high 3-bits of clock counter
|
||||
uint12 cycle; //low 12-bits of clock counter
|
||||
};
|
||||
|
||||
extern APU apu;
|
||||
@@ -1,141 +0,0 @@
|
||||
auto APU::Noise::dacEnable() const -> bool {
|
||||
return (envelopeVolume || envelopeDirection);
|
||||
}
|
||||
|
||||
auto APU::Noise::getPeriod() const -> uint {
|
||||
static const uint table[] = {4, 8, 16, 24, 32, 40, 48, 56};
|
||||
return table[divisor] << frequency;
|
||||
}
|
||||
|
||||
auto APU::Noise::run() -> void {
|
||||
if(period && --period == 0) {
|
||||
period = getPeriod();
|
||||
if(frequency < 14) {
|
||||
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
|
||||
lfsr = (lfsr >> 1) ^ (bit << (narrow ? 6 : 14));
|
||||
}
|
||||
}
|
||||
|
||||
uint4 sample = lfsr & 1 ? 0 : (uint)volume;
|
||||
if(!enable) sample = 0;
|
||||
|
||||
output = sample;
|
||||
}
|
||||
|
||||
auto APU::Noise::clockLength() -> void {
|
||||
if(counter) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::clockEnvelope() -> void {
|
||||
if(enable && envelopeFrequency && --envelopePeriod == 0) {
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
if(envelopeDirection == 0 && volume > 0) volume--;
|
||||
if(envelopeDirection == 1 && volume < 15) volume++;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff1f) { //NR40
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff20) { //NR41
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff21) { //NR42
|
||||
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
|
||||
}
|
||||
|
||||
if(addr == 0xff22) { //NR43
|
||||
return frequency << 4 | narrow << 3 | divisor;
|
||||
}
|
||||
|
||||
if(addr == 0xff23) { //NR44
|
||||
return 0x80 | counter << 6 | 0x3f;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff20) { //NR41
|
||||
length = 64 - (data & 0x3f);
|
||||
}
|
||||
|
||||
if(addr == 0xff21) { //NR42
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff22) { //NR43
|
||||
frequency = data >> 4;
|
||||
narrow = data & 0x08;
|
||||
divisor = data & 0x07;
|
||||
period = getPeriod();
|
||||
}
|
||||
|
||||
if(addr == 0xff23) { //NR44
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
|
||||
if(initialize) {
|
||||
enable = dacEnable();
|
||||
lfsr = -1;
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
volume = envelopeVolume;
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Noise::power(bool initializeLength) -> void {
|
||||
enable = 0;
|
||||
|
||||
envelopeVolume = 0;
|
||||
envelopeDirection = 0;
|
||||
envelopeFrequency = 0;
|
||||
frequency = 0;
|
||||
narrow = 0;
|
||||
divisor = 0;
|
||||
counter = 0;
|
||||
|
||||
output = 0;
|
||||
envelopePeriod = 0;
|
||||
volume = 0;
|
||||
period = 0;
|
||||
lfsr = 0;
|
||||
|
||||
if(initializeLength) length = 64;
|
||||
}
|
||||
|
||||
auto APU::Noise::serialize(serializer& s) -> void {
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(envelopeVolume);
|
||||
s.integer(envelopeDirection);
|
||||
s.integer(envelopeFrequency);
|
||||
s.integer(frequency);
|
||||
s.integer(narrow);
|
||||
s.integer(divisor);
|
||||
s.integer(counter);
|
||||
|
||||
s.integer(output);
|
||||
s.integer(length);
|
||||
s.integer(envelopePeriod);
|
||||
s.integer(volume);
|
||||
s.integer(period);
|
||||
s.integer(lfsr);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
struct Noise {
|
||||
auto dacEnable() const -> bool;
|
||||
auto getPeriod() const -> uint;
|
||||
|
||||
auto run() -> void;
|
||||
auto clockLength() -> void;
|
||||
auto clockEnvelope() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power(bool initializeLength = true) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool enable;
|
||||
|
||||
uint4 envelopeVolume;
|
||||
bool envelopeDirection;
|
||||
uint3 envelopeFrequency;
|
||||
uint4 frequency;
|
||||
bool narrow;
|
||||
uint3 divisor;
|
||||
bool counter;
|
||||
|
||||
int16 output;
|
||||
uint length;
|
||||
uint3 envelopePeriod;
|
||||
uint4 volume;
|
||||
uint period;
|
||||
uint15 lfsr;
|
||||
};
|
||||
@@ -1,152 +0,0 @@
|
||||
auto APU::Sequencer::run() -> void {
|
||||
if(enable == false) {
|
||||
center = 0;
|
||||
left = 0;
|
||||
right = 0;
|
||||
|
||||
centerBias = leftBias = rightBias = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int sample = 0;
|
||||
sample += apu.square1.output;
|
||||
sample += apu.square2.output;
|
||||
sample += apu.wave.output;
|
||||
sample += apu.noise.output;
|
||||
center = (sample * 512) - 16384;
|
||||
|
||||
sample = 0;
|
||||
if(square1.leftEnable) sample += apu.square1.output;
|
||||
if(square2.leftEnable) sample += apu.square2.output;
|
||||
if( wave.leftEnable) sample += apu.wave.output;
|
||||
if( noise.leftEnable) sample += apu.noise.output;
|
||||
sample = (sample * 512) - 16384;
|
||||
sample = (sample * (leftVolume + 1)) / 8;
|
||||
left = sample;
|
||||
|
||||
sample = 0;
|
||||
if(square1.rightEnable) sample += apu.square1.output;
|
||||
if(square2.rightEnable) sample += apu.square2.output;
|
||||
if( wave.rightEnable) sample += apu.wave.output;
|
||||
if( noise.rightEnable) sample += apu.noise.output;
|
||||
sample = (sample * 512) - 16384;
|
||||
sample = (sample * (rightVolume + 1)) / 8;
|
||||
right = sample;
|
||||
|
||||
//reduce audio volume
|
||||
center >>= 1;
|
||||
left >>= 1;
|
||||
right >>= 1;
|
||||
}
|
||||
|
||||
auto APU::Sequencer::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff24) { //NR50
|
||||
return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
|
||||
}
|
||||
|
||||
if(addr == 0xff25) { //NR51
|
||||
return noise.leftEnable << 7
|
||||
| wave.leftEnable << 6
|
||||
| square2.leftEnable << 5
|
||||
| square1.leftEnable << 4
|
||||
| noise.rightEnable << 3
|
||||
| wave.rightEnable << 2
|
||||
| square2.rightEnable << 1
|
||||
| square1.rightEnable << 0;
|
||||
}
|
||||
|
||||
if(addr == 0xff26) { //NR52
|
||||
return enable << 7 | 0x70
|
||||
| apu.noise.enable << 3
|
||||
| apu.wave.enable << 2
|
||||
| apu.square2.enable << 1
|
||||
| apu.square1.enable << 0;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff24) { //NR50
|
||||
leftEnable = (uint1)(data >> 7);
|
||||
leftVolume = (uint3)(data >> 4);
|
||||
rightEnable = (uint1)(data >> 3);
|
||||
rightVolume = (uint3)(data >> 0);
|
||||
}
|
||||
|
||||
if(addr == 0xff25) { //NR51
|
||||
noise.leftEnable = data & 0x80;
|
||||
wave.leftEnable = data & 0x40;
|
||||
square2.leftEnable = data & 0x20;
|
||||
square1.leftEnable = data & 0x10;
|
||||
noise.rightEnable = data & 0x08;
|
||||
wave.rightEnable = data & 0x04;
|
||||
square2.rightEnable = data & 0x02;
|
||||
square1.rightEnable = data & 0x01;
|
||||
}
|
||||
|
||||
if(addr == 0xff26) { //NR52
|
||||
if(enable != (bool)(data & 0x80)) {
|
||||
enable = data & 0x80;
|
||||
|
||||
if(!enable) {
|
||||
//power(bool) resets length counters when true (eg for CGB only)
|
||||
apu.square1.power(system.cgb());
|
||||
apu.square2.power(system.cgb());
|
||||
apu.wave.power(system.cgb());
|
||||
apu.noise.power(system.cgb());
|
||||
power();
|
||||
} else {
|
||||
apu.phase = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Sequencer::power() -> void {
|
||||
leftEnable = 0;
|
||||
leftVolume = 0;
|
||||
rightEnable = 0;
|
||||
rightVolume = 0;
|
||||
noise.leftEnable = 0;
|
||||
wave.leftEnable = 0;
|
||||
square2.leftEnable = 0;
|
||||
square1.leftEnable = 0;
|
||||
noise.rightEnable = 0;
|
||||
wave.rightEnable = 0;
|
||||
square2.rightEnable = 0;
|
||||
square1.rightEnable = 0;
|
||||
enable = 0;
|
||||
|
||||
center = 0;
|
||||
left = 0;
|
||||
right = 0;
|
||||
|
||||
centerBias = 0;
|
||||
leftBias = 0;
|
||||
rightBias = 0;
|
||||
}
|
||||
|
||||
auto APU::Sequencer::serialize(serializer& s) -> void {
|
||||
s.integer(leftEnable);
|
||||
s.integer(leftVolume);
|
||||
s.integer(rightEnable);
|
||||
s.integer(rightVolume);
|
||||
s.integer(noise.leftEnable);
|
||||
s.integer(wave.leftEnable);
|
||||
s.integer(square2.leftEnable);
|
||||
s.integer(square1.leftEnable);
|
||||
s.integer(noise.rightEnable);
|
||||
s.integer(wave.rightEnable);
|
||||
s.integer(square2.rightEnable);
|
||||
s.integer(square1.rightEnable);
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(center);
|
||||
s.integer(left);
|
||||
s.integer(right);
|
||||
|
||||
s.integer(centerBias);
|
||||
s.integer(leftBias);
|
||||
s.integer(rightBias);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
struct Sequencer {
|
||||
auto run() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool leftEnable;
|
||||
uint3 leftVolume;
|
||||
bool rightEnable;
|
||||
uint3 rightVolume;
|
||||
|
||||
struct Channel {
|
||||
bool leftEnable;
|
||||
bool rightEnable;
|
||||
} square1, square2, wave, noise;
|
||||
|
||||
bool enable;
|
||||
|
||||
int16 center;
|
||||
int16 left;
|
||||
int16 right;
|
||||
|
||||
int64 centerBias;
|
||||
int64 leftBias;
|
||||
int64 rightBias;
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
auto APU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
square1.serialize(s);
|
||||
square2.serialize(s);
|
||||
wave.serialize(s);
|
||||
noise.serialize(s);
|
||||
sequencer.serialize(s);
|
||||
|
||||
s.integer(phase);
|
||||
s.integer(cycle);
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
auto APU::Square1::dacEnable() const -> bool {
|
||||
return (envelopeVolume || envelopeDirection);
|
||||
}
|
||||
|
||||
auto APU::Square1::run() -> void {
|
||||
if(period && --period == 0) {
|
||||
period = 2 * (2048 - frequency);
|
||||
phase++;
|
||||
switch(duty) {
|
||||
case 0: dutyOutput = (phase == 6); break; //______-_
|
||||
case 1: dutyOutput = (phase >= 6); break; //______--
|
||||
case 2: dutyOutput = (phase >= 4); break; //____----
|
||||
case 3: dutyOutput = (phase <= 5); break; //------__
|
||||
}
|
||||
}
|
||||
|
||||
uint4 sample = dutyOutput ? (uint)volume : 0;
|
||||
if(!enable) sample = 0;
|
||||
|
||||
output = sample;
|
||||
}
|
||||
|
||||
auto APU::Square1::sweep(bool update) -> void {
|
||||
if(!sweepEnable) return;
|
||||
|
||||
sweepNegate = sweepDirection;
|
||||
uint delta = frequencyShadow >> sweepShift;
|
||||
int freq = frequencyShadow + (sweepNegate ? -delta : delta);
|
||||
|
||||
if(freq > 2047) {
|
||||
enable = false;
|
||||
} else if(sweepShift && update) {
|
||||
frequencyShadow = freq;
|
||||
frequency = freq & 2047;
|
||||
period = 2 * (2048 - frequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::clockLength() -> void {
|
||||
if(counter) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::clockSweep() -> void {
|
||||
if(--sweepPeriod == 0) {
|
||||
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
|
||||
if(sweepEnable && sweepFrequency) {
|
||||
sweep(1);
|
||||
sweep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::clockEnvelope() -> void {
|
||||
if(enable && envelopeFrequency && --envelopePeriod == 0) {
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
if(envelopeDirection == 0 && volume > 0) volume--;
|
||||
if(envelopeDirection == 1 && volume < 15) volume++;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff10) { //NR10
|
||||
return 0x80 | sweepFrequency << 4 | sweepDirection << 3 | sweepShift;
|
||||
}
|
||||
|
||||
if(addr == 0xff11) { //NR11
|
||||
return duty << 6 | 0x3f;
|
||||
}
|
||||
|
||||
if(addr == 0xff12) { //NR12
|
||||
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
|
||||
}
|
||||
|
||||
if(addr == 0xff13) { //NR13
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff14) { //NR14
|
||||
return 0x80 | counter << 6 | 0x3f;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff10) { //NR10
|
||||
if(sweepEnable && sweepNegate && !(data & 0x08)) enable = false;
|
||||
sweepFrequency = (data >> 4) & 7;
|
||||
sweepDirection = data & 0x08;
|
||||
sweepShift = data & 0x07;
|
||||
}
|
||||
|
||||
if(addr == 0xff11) { //NR11
|
||||
duty = data >> 6;
|
||||
length = 64 - (data & 0x3f);
|
||||
}
|
||||
|
||||
if(addr == 0xff12) { //NR12
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff13) { //NR13
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
}
|
||||
|
||||
if(addr == 0xff14) { //NR14
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
|
||||
if(initialize) {
|
||||
enable = dacEnable();
|
||||
period = 2 * (2048 - frequency);
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
volume = envelopeVolume;
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
}
|
||||
|
||||
frequencyShadow = frequency;
|
||||
sweepNegate = false;
|
||||
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
|
||||
sweepEnable = sweepPeriod || sweepShift;
|
||||
if(sweepShift) sweep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square1::power(bool initializeLength) -> void {
|
||||
enable = 0;
|
||||
|
||||
sweepFrequency = 0;
|
||||
sweepDirection = 0;
|
||||
sweepShift = 0;
|
||||
sweepNegate = 0;
|
||||
duty = 0;
|
||||
envelopeVolume = 0;
|
||||
envelopeDirection = 0;
|
||||
envelopeFrequency = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
|
||||
output = 0;
|
||||
dutyOutput = 0;
|
||||
phase = 0;
|
||||
period = 0;
|
||||
envelopePeriod = 0;
|
||||
sweepPeriod = 0;
|
||||
frequencyShadow = 0;
|
||||
sweepEnable = 0;
|
||||
volume = 0;
|
||||
|
||||
if(initializeLength) length = 64;
|
||||
}
|
||||
|
||||
auto APU::Square1::serialize(serializer& s) -> void {
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(sweepFrequency);
|
||||
s.integer(sweepDirection);
|
||||
s.integer(sweepShift);
|
||||
s.integer(sweepNegate);
|
||||
s.integer(duty);
|
||||
s.integer(length);
|
||||
s.integer(envelopeVolume);
|
||||
s.integer(envelopeDirection);
|
||||
s.integer(envelopeFrequency);
|
||||
s.integer(frequency);
|
||||
s.integer(counter);
|
||||
|
||||
s.integer(output);
|
||||
s.integer(dutyOutput);
|
||||
s.integer(phase);
|
||||
s.integer(period);
|
||||
s.integer(envelopePeriod);
|
||||
s.integer(sweepPeriod);
|
||||
s.integer(frequencyShadow);
|
||||
s.integer(sweepEnable);
|
||||
s.integer(volume);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
struct Square1 {
|
||||
auto dacEnable() const -> bool;
|
||||
|
||||
auto run() -> void;
|
||||
auto sweep(bool update) -> void;
|
||||
auto clockLength() -> void;
|
||||
auto clockSweep() -> void;
|
||||
auto clockEnvelope() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power(bool initializeLength = true) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool enable;
|
||||
|
||||
uint3 sweepFrequency;
|
||||
bool sweepDirection;
|
||||
uint3 sweepShift;
|
||||
bool sweepNegate;
|
||||
uint2 duty;
|
||||
uint length;
|
||||
uint4 envelopeVolume;
|
||||
bool envelopeDirection;
|
||||
uint3 envelopeFrequency;
|
||||
uint11 frequency;
|
||||
bool counter;
|
||||
|
||||
int16 output;
|
||||
bool dutyOutput;
|
||||
uint3 phase;
|
||||
uint period;
|
||||
uint3 envelopePeriod;
|
||||
uint3 sweepPeriod;
|
||||
int frequencyShadow;
|
||||
bool sweepEnable;
|
||||
uint4 volume;
|
||||
};
|
||||
@@ -1,138 +0,0 @@
|
||||
auto APU::Square2::dacEnable() const -> bool {
|
||||
return (envelopeVolume || envelopeDirection);
|
||||
}
|
||||
|
||||
auto APU::Square2::run() -> void {
|
||||
if(period && --period == 0) {
|
||||
period = 2 * (2048 - frequency);
|
||||
phase++;
|
||||
switch(duty) {
|
||||
case 0: dutyOutput = (phase == 6); break; //______-_
|
||||
case 1: dutyOutput = (phase >= 6); break; //______--
|
||||
case 2: dutyOutput = (phase >= 4); break; //____----
|
||||
case 3: dutyOutput = (phase <= 5); break; //------__
|
||||
}
|
||||
}
|
||||
|
||||
uint4 sample = dutyOutput ? (uint)volume : 0;
|
||||
if(!enable) sample = 0;
|
||||
|
||||
output = sample;
|
||||
}
|
||||
|
||||
auto APU::Square2::clockLength() -> void {
|
||||
if(counter) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square2::clockEnvelope() -> void {
|
||||
if(enable && envelopeFrequency && --envelopePeriod == 0) {
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
if(envelopeDirection == 0 && volume > 0) volume--;
|
||||
if(envelopeDirection == 1 && volume < 15) volume++;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square2::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff15) { //NR20
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff16) { //NR21
|
||||
return duty << 6 | 0x3f;
|
||||
}
|
||||
|
||||
if(addr == 0xff17) { //NR22
|
||||
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
|
||||
}
|
||||
|
||||
if(addr == 0xff18) { //NR23
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff19) { //NR24
|
||||
return 0x80 | counter << 6 | 0x3f;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff16) { //NR21
|
||||
duty = data >> 6;
|
||||
length = 64 - (data & 0x3f);
|
||||
}
|
||||
|
||||
if(addr == 0xff17) { //NR22
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff18) { //NR23
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
}
|
||||
|
||||
if(addr == 0xff19) { //NR24
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
|
||||
if(initialize) {
|
||||
enable = dacEnable();
|
||||
period = 2 * (2048 - frequency);
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
volume = envelopeVolume;
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Square2::power(bool initializeLength) -> void {
|
||||
enable = 0;
|
||||
|
||||
duty = 0;
|
||||
envelopeVolume = 0;
|
||||
envelopeDirection = 0;
|
||||
envelopeFrequency = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
|
||||
output = 0;
|
||||
dutyOutput = 0;
|
||||
phase = 0;
|
||||
period = 0;
|
||||
envelopePeriod = 0;
|
||||
volume = 0;
|
||||
|
||||
if(initializeLength) length = 64;
|
||||
}
|
||||
|
||||
auto APU::Square2::serialize(serializer& s) -> void {
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(duty);
|
||||
s.integer(length);
|
||||
s.integer(envelopeVolume);
|
||||
s.integer(envelopeDirection);
|
||||
s.integer(envelopeFrequency);
|
||||
s.integer(frequency);
|
||||
s.integer(counter);
|
||||
|
||||
s.integer(output);
|
||||
s.integer(dutyOutput);
|
||||
s.integer(phase);
|
||||
s.integer(period);
|
||||
s.integer(envelopePeriod);
|
||||
s.integer(volume);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
struct Square2 {
|
||||
auto dacEnable() const -> bool;
|
||||
|
||||
auto run() -> void;
|
||||
auto clockLength() -> void;
|
||||
auto clockEnvelope() -> void;
|
||||
auto read(uint16 addr) -> uint8;
|
||||
auto write(uint16 addr, uint8 data) -> void;
|
||||
auto power(bool initializeLength = true) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
bool enable;
|
||||
|
||||
uint2 duty;
|
||||
uint length;
|
||||
uint4 envelopeVolume;
|
||||
bool envelopeDirection;
|
||||
uint3 envelopeFrequency;
|
||||
uint11 frequency;
|
||||
bool counter;
|
||||
|
||||
int16 output;
|
||||
bool dutyOutput;
|
||||
uint3 phase;
|
||||
uint period;
|
||||
uint3 envelopePeriod;
|
||||
uint4 volume;
|
||||
};
|
||||
@@ -1,157 +0,0 @@
|
||||
auto APU::Wave::getPattern(uint5 offset) const -> uint4 {
|
||||
return pattern[offset >> 1] >> (offset & 1 ? 0 : 4);
|
||||
}
|
||||
|
||||
auto APU::Wave::run() -> void {
|
||||
if(patternHold) patternHold--;
|
||||
|
||||
if(period && --period == 0) {
|
||||
period = 1 * (2048 - frequency);
|
||||
patternSample = getPattern(++patternOffset);
|
||||
patternHold = 1;
|
||||
}
|
||||
|
||||
static const uint shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
|
||||
uint4 sample = patternSample >> shift[volume];
|
||||
if(!enable) sample = 0;
|
||||
|
||||
output = sample;
|
||||
}
|
||||
|
||||
auto APU::Wave::clockLength() -> void {
|
||||
if(counter) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Wave::read(uint16 addr) -> uint8 {
|
||||
if(addr == 0xff1a) { //NR30
|
||||
return dacEnable << 7 | 0x7f;
|
||||
}
|
||||
|
||||
if(addr == 0xff1b) { //NR31
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff1c) { //NR32
|
||||
return 0x80 | volume << 5 | 0x1f;
|
||||
}
|
||||
|
||||
if(addr == 0xff1d) { //NR33
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if(addr == 0xff1e) { //NR34
|
||||
return 0x80 | counter << 6 | 0x3f;
|
||||
}
|
||||
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) {
|
||||
if(enable) {
|
||||
if(!system.cgb() && !patternHold) return 0xff;
|
||||
return pattern[patternOffset >> 1];
|
||||
} else {
|
||||
return pattern[addr & 15];
|
||||
}
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff1a) { //NR30
|
||||
dacEnable = data & 0x80;
|
||||
if(!dacEnable) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff1b) { //NR31
|
||||
length = 256 - data;
|
||||
}
|
||||
|
||||
if(addr == 0xff1c) { //NR32
|
||||
volume = data >> 5;
|
||||
}
|
||||
|
||||
if(addr == 0xff1d) { //NR33
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
}
|
||||
|
||||
if(addr == 0xff1e) { //NR34
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
|
||||
if(initialize) {
|
||||
if(!system.cgb() && patternHold) {
|
||||
//DMG,SGB trigger while channel is being read corrupts wave RAM
|
||||
if((patternOffset >> 1) <= 3) {
|
||||
//if current pattern is with 0-3; only byte 0 is corrupted
|
||||
pattern[0] = pattern[patternOffset >> 1];
|
||||
} else {
|
||||
//if current pattern is within 4-15; pattern&~3 is copied to pattern[0-3]
|
||||
pattern[0] = pattern[((patternOffset >> 1) & ~3) + 0];
|
||||
pattern[1] = pattern[((patternOffset >> 1) & ~3) + 1];
|
||||
pattern[2] = pattern[((patternOffset >> 1) & ~3) + 2];
|
||||
pattern[3] = pattern[((patternOffset >> 1) & ~3) + 3];
|
||||
}
|
||||
}
|
||||
|
||||
enable = dacEnable;
|
||||
period = 1 * (2048 - frequency);
|
||||
patternOffset = 0;
|
||||
patternSample = 0;
|
||||
patternHold = 0;
|
||||
|
||||
if(!length) {
|
||||
length = 256;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) {
|
||||
if(enable) {
|
||||
if(!system.cgb() && !patternHold) return;
|
||||
pattern[patternOffset >> 1] = data;
|
||||
} else {
|
||||
pattern[addr & 15] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Wave::power(bool initializeLength) -> void {
|
||||
enable = 0;
|
||||
|
||||
dacEnable = 0;
|
||||
volume = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
|
||||
output = 0;
|
||||
period = 0;
|
||||
patternOffset = 0;
|
||||
patternSample = 0;
|
||||
patternHold = 0;
|
||||
|
||||
if(initializeLength) length = 256;
|
||||
}
|
||||
|
||||
auto APU::Wave::serialize(serializer& s) -> void {
|
||||
s.integer(enable);
|
||||
|
||||
s.integer(dacEnable);
|
||||
s.integer(volume);
|
||||
s.integer(frequency);
|
||||
s.integer(counter);
|
||||
s.array(pattern);
|
||||
|
||||
s.integer(output);
|
||||
s.integer(length);
|
||||
s.integer(period);
|
||||
s.integer(patternOffset);
|
||||
s.integer(patternSample);
|
||||
s.integer(patternHold);
|
||||
}
|
||||
Loaded 100 of 1898 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user