mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-17 07:32:11 +02:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
aff00506c5 | ||
|
e846c83d47 | ||
|
06d44b4878 | ||
|
25eaaa82f4 | ||
|
2d83300235 | ||
|
680d16561e | ||
|
379ab6991f | ||
|
d3413db04a | ||
|
a7f7985581 | ||
|
b586471562 | ||
|
c33065fbd1 | ||
|
79e7e6ab9e | ||
|
3d3ac8c1db | ||
|
b0d2f5033e | ||
|
570eb9c5f5 | ||
|
7dc62e3a69 | ||
|
fc7d5991ce | ||
|
29be18ce0c | ||
|
810cbdafb4 | ||
|
4b29f4bad7 | ||
|
ef65bb862a | ||
|
0d0af39b44 | ||
|
6c83329cae | ||
|
32a95a9761 | ||
|
a89a3da77a | ||
|
7a748e093e | ||
|
d158c8f293 | ||
|
71bda4144a | ||
|
ad51f1478e | ||
|
d0ddd87e9c | ||
|
605a8aa3e9 | ||
|
a8323d0d2b | ||
|
d7998b23ef | ||
|
344e63d928 | ||
|
f1ebef2ea8 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
higan/profile/WonderSwan.sys/internal.ram
|
||||
higan/profile/WonderSwan Color.sys/internal.ram
|
@@ -1,12 +1,8 @@
|
||||
include ../nall/GNUmakefile
|
||||
|
||||
fc := fc
|
||||
sfc := sfc
|
||||
gb := gb
|
||||
gba := gba
|
||||
|
||||
profile := accuracy
|
||||
target := tomoko
|
||||
# target := loki
|
||||
profile := accuracy
|
||||
# console := true
|
||||
|
||||
flags += -I. -I.. -O3
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=higan
|
||||
Comment=Nintendo emulator
|
||||
Comment=Emulator
|
||||
Exec=higan
|
||||
Icon=higan
|
||||
Terminal=false
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>higan.icns</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
3
higan/emulator/GNUmakefile
Normal file
3
higan/emulator/GNUmakefile
Normal file
@@ -0,0 +1,3 @@
|
||||
objects += emulator
|
||||
|
||||
obj/emulator.o: emulator/emulator.cpp $(call rwildcard,emulator/)
|
34
higan/emulator/emulator.cpp
Normal file
34
higan/emulator/emulator.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
auto Interface::videoColor(uint16 r, uint16 g, uint16 b) -> uint32 {
|
||||
double saturation = 1.0;
|
||||
double gamma = 1.0;
|
||||
double luminance = 1.0;
|
||||
|
||||
if(saturation != 1.0) {
|
||||
uint16 grayscale = uclamp<16>((r + g + b) / 3);
|
||||
double inverse = max(0.0, 1.0 - saturation);
|
||||
r = uclamp<16>(r * saturation + grayscale * inverse);
|
||||
g = uclamp<16>(g * saturation + grayscale * inverse);
|
||||
b = uclamp<16>(b * saturation + grayscale * inverse);
|
||||
}
|
||||
|
||||
if(gamma != 1.0) {
|
||||
double reciprocal = 1.0 / 32767.0;
|
||||
r = r > 32767 ? r : uint16(32767 * pow(r * reciprocal, gamma));
|
||||
g = g > 32767 ? g : uint16(32767 * pow(g * reciprocal, gamma));
|
||||
b = b > 32767 ? b : uint16(32767 * pow(b * reciprocal, gamma));
|
||||
}
|
||||
|
||||
if(luminance != 1.0) {
|
||||
r = uclamp<16>(r * luminance);
|
||||
g = uclamp<16>(g * luminance);
|
||||
b = uclamp<16>(b * luminance);
|
||||
}
|
||||
|
||||
return 255 << 24 | (r >> 8) << 16 | (g >> 8) << 8 | (b >> 8) << 0;
|
||||
}
|
||||
|
||||
}
|
@@ -6,7 +6,7 @@ using namespace nall;
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "097";
|
||||
static const string Version = "098";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
@@ -52,5 +52,3 @@ template<typename R, typename... P> struct hook<auto (P...) -> R> {
|
||||
#else
|
||||
#define privileged private
|
||||
#endif
|
||||
|
||||
using varuint = varuint_t<uint>;
|
||||
|
@@ -4,6 +4,7 @@ namespace Emulator {
|
||||
|
||||
struct Interface {
|
||||
struct Information {
|
||||
string manufacturer;
|
||||
string name;
|
||||
uint width;
|
||||
uint height;
|
||||
@@ -108,6 +109,9 @@ struct Interface {
|
||||
virtual auto cap(const string& name) -> bool { return false; }
|
||||
virtual auto get(const string& name) -> any { return {}; }
|
||||
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
||||
|
||||
//shared functions
|
||||
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -1,16 +1,16 @@
|
||||
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)
|
||||
processors += r6502
|
||||
|
||||
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/)
|
||||
objects += fc-interface fc-system fc-scheduler fc-input
|
||||
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
|
||||
objects += fc-cheat
|
||||
|
||||
obj/fc-interface.o: fc/interface/interface.cpp $(call rwildcard,fc/interface/)
|
||||
obj/fc-system.o: fc/system/system.cpp $(call rwildcard,fc/system/)
|
||||
obj/fc-scheduler.o: fc/scheduler/scheduler.cpp $(call rwildcard,fc/scheduler/)
|
||||
obj/fc-input.o: fc/input/input.cpp $(call rwildcard,fc/input/)
|
||||
obj/fc-memory.o: fc/memory/memory.cpp $(call rwildcard,fc/memory/)
|
||||
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp $(call rwildcard,fc/cartridge/)
|
||||
obj/fc-cpu.o: fc/cpu/cpu.cpp $(call rwildcard,fc/cpu/)
|
||||
obj/fc-apu.o: fc/apu/apu.cpp $(call rwildcard,fc/apu/)
|
||||
obj/fc-ppu.o: fc/ppu/ppu.cpp $(call rwildcard,fc/ppu/)
|
||||
obj/fc-cheat.o: fc/cheat/cheat.cpp $(call rwildcard,fc/cheat/)
|
||||
|
@@ -34,43 +34,37 @@ APU::APU() {
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Main() -> void {
|
||||
apu.main();
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), 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;
|
||||
|
||||
uint pulse_output, triangle_output, noise_output, dmc_output;
|
||||
pulse_output = pulse[0].clock();
|
||||
pulse_output += pulse[1].clock();
|
||||
triangle_output = triangle.clock();
|
||||
noise_output = noise.clock();
|
||||
dmc_output = dmc.clock();
|
||||
|
||||
pulse_output = pulse[0].clock();
|
||||
pulse_output += pulse[1].clock();
|
||||
triangle_output = triangle.clock();
|
||||
noise_output = noise.clock();
|
||||
dmc_output = dmc.clock();
|
||||
clock_frame_counter_divider();
|
||||
|
||||
clock_frame_counter_divider();
|
||||
int output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
|
||||
|
||||
int output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
|
||||
output = filter.run_hipass_strong(output);
|
||||
output += cartridge_sample;
|
||||
output = filter.run_hipass_weak(output);
|
||||
//output = filter.run_lopass(output);
|
||||
output = sclamp<16>(output);
|
||||
|
||||
output = filter.run_hipass_strong(output);
|
||||
output += cartridge_sample;
|
||||
output = filter.run_hipass_weak(output);
|
||||
//output = filter.run_lopass(output);
|
||||
output = sclamp<16>(output);
|
||||
interface->audioSample(output, output);
|
||||
|
||||
interface->audioSample(output, output);
|
||||
|
||||
tick();
|
||||
}
|
||||
tick();
|
||||
}
|
||||
|
||||
auto APU::tick() -> void {
|
||||
clock += 12;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto APU::set_irq_line() -> void {
|
||||
@@ -94,7 +88,7 @@ auto APU::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::reset() -> void {
|
||||
create(APU::Main, 21477272);
|
||||
create(APU::Enter, 21'477'272);
|
||||
|
||||
pulse[0].reset();
|
||||
pulse[1].reset();
|
||||
|
@@ -1,7 +1,7 @@
|
||||
struct APU : Thread {
|
||||
APU();
|
||||
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
auto set_irq_line() -> void;
|
||||
|
@@ -5,20 +5,14 @@ struct BandaiFCG : Board {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(1);
|
||||
irq_counter_enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
@@ -33,7 +27,7 @@ struct BandaiFCG : Board {
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (region == 0 ? prg_bank : 0x0f);
|
||||
uint bank = (region == 0 ? prg_bank : (uint8)0x0f);
|
||||
return prgrom.read((bank << 14) | (addr & 0x3fff));
|
||||
}
|
||||
return cpu.mdr();
|
||||
|
@@ -36,10 +36,10 @@ Board::Board(Markup::Node& document) {
|
||||
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(prgrom.size) prgrom.data = new uint8_t[prgrom.size]();
|
||||
if(prgram.size) prgram.data = new uint8_t[prgram.size]();
|
||||
if(chrrom.size) chrrom.data = new uint8_t[chrrom.size]();
|
||||
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
|
||||
|
||||
if(auto name = prom["name"].text()) interface->loadRequest(ID::ProgramROM, name, true);
|
||||
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name, false);
|
||||
@@ -80,19 +80,13 @@ auto Board::mirror(uint addr, uint size) -> uint {
|
||||
}
|
||||
|
||||
auto Board::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cartridge.clock += 12 * 4095;
|
||||
tick();
|
||||
}
|
||||
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);
|
||||
if(cartridge.clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto Board::chr_read(uint addr) -> uint8 {
|
||||
|
@@ -44,25 +44,19 @@ struct Sunsoft5B : Board {
|
||||
} 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);
|
||||
}
|
||||
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(irq_enable);
|
||||
}
|
||||
}
|
||||
|
||||
pulse[0].clock();
|
||||
pulse[1].clock();
|
||||
pulse[2].clock();
|
||||
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
||||
apu.set_sample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
pulse[0].clock();
|
||||
pulse[1].clock();
|
||||
pulse[2].clock();
|
||||
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
||||
apu.set_sample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
|
@@ -6,10 +6,6 @@ namespace Famicom {
|
||||
#include "board/board.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::loaded() const -> bool {
|
||||
return _loaded;
|
||||
}
|
||||
|
||||
auto Cartridge::sha256() const -> string {
|
||||
return _sha256;
|
||||
}
|
||||
@@ -22,8 +18,8 @@ auto Cartridge::title() const -> string {
|
||||
return information.title;
|
||||
}
|
||||
|
||||
auto Cartridge::Main() -> void {
|
||||
cartridge.main();
|
||||
auto Cartridge::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cartridge.main();
|
||||
}
|
||||
|
||||
auto Cartridge::main() -> void {
|
||||
@@ -40,14 +36,9 @@ auto Cartridge::load() -> void {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -56,7 +47,7 @@ auto Cartridge::power() -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::reset() -> void {
|
||||
create(Cartridge::Main, 21477272);
|
||||
create(Cartridge::Enter, 21'477'272);
|
||||
board->reset();
|
||||
}
|
||||
|
||||
|
@@ -2,10 +2,9 @@
|
||||
#include "board/board.hpp"
|
||||
|
||||
struct Cartridge : Thread {
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
auto loaded() const -> bool;
|
||||
auto sha256() const -> string;
|
||||
auto manifest() const -> string;
|
||||
auto title() const -> string;
|
||||
@@ -31,7 +30,6 @@ struct Cartridge : Thread {
|
||||
|
||||
//privileged:
|
||||
Board* board = nullptr;
|
||||
bool _loaded = false;
|
||||
string _sha256;
|
||||
|
||||
auto prg_read(uint addr) -> uint8;
|
||||
|
@@ -4,14 +4,8 @@ struct MMC1 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(writedelay) writedelay--;
|
||||
tick();
|
||||
}
|
||||
if(writedelay) writedelay--;
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) -> uint {
|
||||
|
@@ -3,15 +3,9 @@ struct MMC3 : Chip {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irq_test(uint addr) -> void {
|
||||
|
@@ -4,17 +4,11 @@ struct MMC5 : Chip {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
//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();
|
||||
}
|
||||
cpu.set_irq_line(irq_enable && irq_pending);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
@@ -94,7 +88,7 @@ struct MMC5 : Chip {
|
||||
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 == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : (uint8)0x00;
|
||||
if(exram_mode == 2) exram[addr & 0x03ff] = data;
|
||||
return;
|
||||
}
|
||||
@@ -277,7 +271,7 @@ struct MMC5 : Chip {
|
||||
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 2: return exram_mode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
|
||||
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
|
||||
}
|
||||
}
|
||||
|
@@ -3,15 +3,9 @@ struct MMC6 : Chip {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irq_test(uint addr) -> void {
|
||||
|
@@ -3,31 +3,25 @@ struct VRC3 : Chip {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
@@ -90,8 +84,8 @@ struct VRC3 : Chip {
|
||||
uint16 irq_latch;
|
||||
struct {
|
||||
union {
|
||||
uint16 w;
|
||||
struct { uint8 order_lsb2(l, h); };
|
||||
uint16_t w;
|
||||
struct { uint8_t order_lsb2(l, h); };
|
||||
};
|
||||
} irq_counter;
|
||||
bool irq_line;
|
||||
|
@@ -3,26 +3,11 @@ struct VRC4 : Chip {
|
||||
}
|
||||
|
||||
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_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;
|
||||
@@ -32,9 +17,18 @@ struct VRC4 : Chip {
|
||||
}
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
if(irq_mode == 1) {
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
|
@@ -77,26 +77,11 @@ struct VRC6 : Chip {
|
||||
} 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_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;
|
||||
@@ -105,16 +90,25 @@ struct VRC6 : Chip {
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
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 {
|
||||
|
@@ -6,26 +6,11 @@ struct VRC7 : Chip {
|
||||
}
|
||||
|
||||
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_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;
|
||||
@@ -34,10 +19,19 @@ struct VRC7 : Chip {
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.set_irq_line(irq_line);
|
||||
|
||||
tick();
|
||||
if(irq_mode == 1) {
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.set_irq_line(irq_line);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
|
@@ -7,33 +7,23 @@ namespace Famicom {
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cpu.main();
|
||||
}
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(status.interrupt_pending) {
|
||||
interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
if(status.interrupt_pending) return interrupt();
|
||||
exec();
|
||||
}
|
||||
|
||||
auto CPU::add_clocks(uint clocks) -> void {
|
||||
apu.clock -= clocks;
|
||||
if(apu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(apu.thread);
|
||||
if(apu.clock < 0 && !scheduler.synchronizing()) co_switch(apu.thread);
|
||||
|
||||
ppu.clock -= clocks;
|
||||
if(ppu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(ppu.thread);
|
||||
if(ppu.clock < 0 && !scheduler.synchronizing()) co_switch(ppu.thread);
|
||||
|
||||
cartridge.clock -= clocks;
|
||||
if(cartridge.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cartridge.thread);
|
||||
if(cartridge.clock < 0 && !scheduler.synchronizing()) co_switch(cartridge.thread);
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
@@ -48,7 +38,7 @@ auto CPU::power() -> void {
|
||||
|
||||
auto CPU::reset() -> void {
|
||||
R6502::reset();
|
||||
create(CPU::Enter, 21477272);
|
||||
create(CPU::Enter, 21'477'272);
|
||||
|
||||
regs.pc = bus.read(0xfffc) << 0;
|
||||
regs.pc |= bus.read(0xfffd) << 8;
|
||||
|
@@ -51,7 +51,6 @@ namespace Famicom {
|
||||
#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>
|
||||
|
@@ -8,12 +8,14 @@ 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.manufacturer = "Nintendo";
|
||||
information.name = "Famicom";
|
||||
information.width = 256;
|
||||
information.height = 240;
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 8.0 / 7.0;
|
||||
information.resettable = true;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
@@ -61,7 +63,7 @@ auto Interface::audioFrequency() -> double {
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return cartridge.loaded();
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::sha256() -> string {
|
||||
@@ -84,7 +86,7 @@ auto Interface::group(uint id) -> uint {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> void {
|
||||
cartridge.load();
|
||||
system.load();
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
@@ -131,7 +133,7 @@ auto Interface::save(uint id, const stream& stream) -> void {
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
cartridge.unload();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
@@ -147,7 +149,7 @@ auto Interface::run() -> void {
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
system.runtosave();
|
||||
system.runToSave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
|
@@ -2,21 +2,17 @@
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "serialization.cpp"
|
||||
PPU ppu;
|
||||
#include "video.cpp"
|
||||
|
||||
auto PPU::Main() -> void {
|
||||
ppu.main();
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::PPU) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
raster_scanline();
|
||||
}
|
||||
raster_scanline();
|
||||
}
|
||||
|
||||
auto PPU::tick() -> void {
|
||||
@@ -47,14 +43,15 @@ auto PPU::scanline() -> void {
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
status.field ^= 1;
|
||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||
video.refresh();
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
}
|
||||
|
||||
auto PPU::reset() -> void {
|
||||
create(PPU::Main, 21477272);
|
||||
create(PPU::Enter, 21'477'272);
|
||||
|
||||
status.mdr = 0x00;
|
||||
status.field = 0;
|
||||
@@ -113,7 +110,6 @@ auto PPU::read(uint16 addr) -> uint8 {
|
||||
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;
|
||||
@@ -164,6 +160,7 @@ auto PPU::write(uint16 addr, uint8 data) -> void {
|
||||
status.oam_addr = data;
|
||||
return;
|
||||
case 4: //OAMDATA
|
||||
if(status.oam_addr.bits(0,1) == 2) data.bits(2,4) = 0; //clear non-existent bits (always read back as 0)
|
||||
oam[status.oam_addr++] = data;
|
||||
return;
|
||||
case 5: //PPUSCROLL
|
||||
|
@@ -1,5 +1,7 @@
|
||||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread {
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
|
||||
|
@@ -1,34 +1,25 @@
|
||||
#include <fc/fc.hpp>
|
||||
#include <cmath>
|
||||
|
||||
#define VIDEO_CPP
|
||||
namespace Famicom {
|
||||
|
||||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[256 * 480];
|
||||
paletteLiteral = new uint32[1 << 9];
|
||||
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);
|
||||
memory::fill(output(), 256 * 480);
|
||||
|
||||
for(auto color : range(1 << 9)) {
|
||||
paletteLiteral[color] = color;
|
||||
paletteStandard[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 2.2);
|
||||
paletteEmulation[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 1.8);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
auto output = this->output();
|
||||
auto& palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
if(settings.scanlineEmulation) {
|
||||
for(uint y = 0; y < 240; y++) {
|
||||
@@ -99,7 +90,5 @@ auto Video::generateColor(
|
||||
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);
|
||||
}
|
||||
|
||||
return interface->videoColor(r, g, b);
|
||||
}
|
@@ -1,16 +1,16 @@
|
||||
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;
|
||||
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
};
|
||||
|
||||
extern Video video;
|
@@ -4,25 +4,40 @@ 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;
|
||||
host = co_active();
|
||||
resume = cpu.thread;
|
||||
}
|
||||
|
||||
auto Scheduler::enter(Mode mode_) -> Event {
|
||||
mode = mode_;
|
||||
host = co_active();
|
||||
co_switch(resume);
|
||||
return event;
|
||||
}
|
||||
|
||||
auto Scheduler::exit(Event event_) -> void {
|
||||
event = event_;
|
||||
resume = co_active();
|
||||
co_switch(host);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize(cothread_t thread) -> void {
|
||||
if(thread == ppu.thread) {
|
||||
while(enter(Mode::SynchronizePPU) != Event::Synchronize);
|
||||
} else {
|
||||
resume = thread;
|
||||
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize() -> void {
|
||||
if(co_active() == ppu.thread && mode == Mode::SynchronizePPU) return exit(Event::Synchronize);
|
||||
if(co_active() != ppu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronizing() const -> bool {
|
||||
return mode == Mode::SynchronizeAll;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,16 +1,28 @@
|
||||
struct Scheduler : property<Scheduler> {
|
||||
enum class SynchronizeMode : uint { None, PPU, All } sync;
|
||||
enum class ExitReason : uint { UnknownEvent, FrameEvent, SynchronizeEvent };
|
||||
struct Scheduler {
|
||||
enum class Mode : uint {
|
||||
Run,
|
||||
SynchronizePPU,
|
||||
SynchronizeAll,
|
||||
};
|
||||
|
||||
auto enter() -> void;
|
||||
auto exit(ExitReason) -> void;
|
||||
enum class Event : uint {
|
||||
Unknown,
|
||||
Frame,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
auto enter(Mode = Mode::Run) -> Event;
|
||||
auto exit(Event) -> void;
|
||||
auto synchronize(cothread_t) -> void;
|
||||
auto synchronize() -> void;
|
||||
auto synchronizing() const -> bool;
|
||||
|
||||
cothread_t host_thread; //program thread (used to exit emulation)
|
||||
cothread_t thread; //active emulation thread (used to enter emulation)
|
||||
readonly<ExitReason> exit_reason;
|
||||
private:
|
||||
cothread_t host = nullptr;
|
||||
cothread_t resume = nullptr;
|
||||
Mode mode = Mode::Run;
|
||||
Event event = Event::Unknown;
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(serialize_size);
|
||||
serializer s(_serializeSize);
|
||||
|
||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||
char hash[64], description[512];
|
||||
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ auto System::unserialize(serializer& s) -> bool {
|
||||
if(version != Info::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s) -> void {
|
||||
}
|
||||
|
||||
auto System::serialize_all(serializer& s) -> void {
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
system.serialize(s);
|
||||
input.serialize(s);
|
||||
cartridge.serialize(s);
|
||||
@@ -44,7 +44,7 @@ auto System::serialize_all(serializer& s) -> void {
|
||||
ppu.serialize(s);
|
||||
}
|
||||
|
||||
auto System::serialize_init() -> void {
|
||||
auto System::serializeInit() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0, version = 0;
|
||||
@@ -55,6 +55,6 @@ auto System::serialize_init() -> void {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serialize_size = s.size();
|
||||
serializeAll(s);
|
||||
_serializeSize = s.size();
|
||||
}
|
||||
|
@@ -5,47 +5,31 @@ namespace Famicom {
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
auto System::loaded() const -> bool { return _loaded; }
|
||||
|
||||
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::runToSave() -> void {
|
||||
scheduler.synchronize(ppu.thread);
|
||||
scheduler.synchronize(cpu.thread);
|
||||
scheduler.synchronize(apu.thread);
|
||||
scheduler.synchronize(cartridge.thread);
|
||||
}
|
||||
|
||||
auto System::load() -> void {
|
||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
cartridge.load();
|
||||
serializeInit();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
serialize_init();
|
||||
auto System::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
cartridge.unload();
|
||||
_loaded = false;
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
@@ -54,7 +38,6 @@ auto System::power() -> void {
|
||||
apu.power();
|
||||
ppu.power();
|
||||
input.reset();
|
||||
scheduler.power();
|
||||
reset();
|
||||
}
|
||||
|
||||
@@ -69,7 +52,7 @@ auto System::reset() -> void {
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
assert(interface != 0);
|
||||
assert(interface != nullptr);
|
||||
input.connect(0, Input::Device::Joypad);
|
||||
input.connect(1, Input::Device::None);
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
struct System {
|
||||
auto loaded() const -> bool;
|
||||
|
||||
auto run() -> void;
|
||||
auto runtosave() -> void;
|
||||
auto runthreadtosave() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto load() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
@@ -14,14 +16,16 @@ struct System {
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
auto serialize_all(serializer&) -> void;
|
||||
auto serialize_init() -> void;
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
struct Information {
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
uint serialize_size;
|
||||
private:
|
||||
bool _loaded = false;
|
||||
uint _serializeSize = 0;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
|
@@ -1,16 +1,16 @@
|
||||
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)
|
||||
processors += lr35902
|
||||
|
||||
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/)
|
||||
objects += gb-interface gb-system gb-scheduler
|
||||
objects += gb-memory gb-cartridge
|
||||
objects += gb-cpu gb-ppu gb-apu
|
||||
objects += gb-cheat
|
||||
|
||||
obj/gb-interface.o: gb/interface/interface.cpp $(call rwildcard,gb/interface/)
|
||||
obj/gb-system.o: gb/system/system.cpp $(call rwildcard,gb/system/)
|
||||
obj/gb-scheduler.o: gb/scheduler/scheduler.cpp $(call rwildcard,gb/scheduler/)
|
||||
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp $(call rwildcard,gb/cartridge/)
|
||||
obj/gb-memory.o: gb/memory/memory.cpp $(call rwildcard,gb/memory/)
|
||||
obj/gb-cpu.o: gb/cpu/cpu.cpp $(call rwildcard,gb/cpu/)
|
||||
obj/gb-ppu.o: gb/ppu/ppu.cpp $(call rwildcard,gb/ppu/)
|
||||
obj/gb-apu.o: gb/apu/apu.cpp $(call rwildcard,gb/apu/)
|
||||
obj/gb-cheat.o: gb/cheat/cheat.cpp $(call rwildcard,gb/cheat/)
|
||||
|
@@ -10,52 +10,44 @@ namespace GameBoy {
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
auto APU::Main() -> void {
|
||||
apu.main();
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), 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();
|
||||
}
|
||||
|
||||
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++;
|
||||
if(phase == 2 || phase == 6) { //128hz
|
||||
square1.clockSweep();
|
||||
}
|
||||
cycle++;
|
||||
|
||||
clock += cpu.frequency;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||
co_switch(scheduler.active_thread = cpu.thread);
|
||||
if(phase == 7) { //64hz
|
||||
square1.clockEnvelope();
|
||||
square2.clockEnvelope();
|
||||
noise.clockEnvelope();
|
||||
}
|
||||
phase++;
|
||||
}
|
||||
cycle++;
|
||||
|
||||
clock += cpu.frequency;
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
//filter to remove DC bias
|
||||
@@ -65,7 +57,7 @@ auto APU::hipass(int16& sample, int64& bias) -> void {
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
create(Main, 2 * 1024 * 1024);
|
||||
create(Enter, 2 * 1024 * 1024);
|
||||
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
||||
|
||||
square1.power();
|
||||
|
@@ -1,5 +1,5 @@
|
||||
struct APU : Thread, MMIO {
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto hipass(int16& sample, int64& bias) -> void;
|
||||
auto power() -> void;
|
||||
|
@@ -66,16 +66,16 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
if(addr == 0xff21) { //NR42
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff22) { //NR43
|
||||
frequency = data >> 4;
|
||||
narrow = data & 0x08;
|
||||
divisor = data & 0x07;
|
||||
frequency = data.bits(7,4);
|
||||
narrow = data.bit (3);
|
||||
divisor = data.bits(2,0);
|
||||
period = getPeriod();
|
||||
}
|
||||
|
||||
@@ -84,10 +84,9 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
counter = data.bit(6);
|
||||
|
||||
if(initialize) {
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
lfsr = -1;
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
@@ -95,7 +94,7 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -68,26 +68,26 @@ auto APU::Sequencer::read(uint16 addr) -> uint8 {
|
||||
|
||||
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff24) { //NR50
|
||||
leftEnable = (uint1)(data >> 7);
|
||||
leftVolume = (uint3)(data >> 4);
|
||||
rightEnable = (uint1)(data >> 3);
|
||||
rightVolume = (uint3)(data >> 0);
|
||||
leftEnable = data.bit (7);
|
||||
leftVolume = data.bits(6,4);
|
||||
rightEnable = data.bit (3);
|
||||
rightVolume = data.bits(2,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff25) { //NR51
|
||||
noise.leftEnable = data & 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;
|
||||
noise.leftEnable = data.bit(7);
|
||||
wave.leftEnable = data.bit(6);
|
||||
square2.leftEnable = data.bit(5);
|
||||
square1.leftEnable = data.bit(4);
|
||||
noise.rightEnable = data.bit(3);
|
||||
wave.rightEnable = data.bit(2);
|
||||
square2.rightEnable = data.bit(1);
|
||||
square1.rightEnable = data.bit(0);
|
||||
}
|
||||
|
||||
if(addr == 0xff26) { //NR52
|
||||
if(enable != (bool)(data & 0x80)) {
|
||||
enable = data & 0x80;
|
||||
if(enable != data.bit(7)) {
|
||||
enable = data.bit(7);
|
||||
|
||||
if(!enable) {
|
||||
//power(bool) resets length counters when true (eg for CGB only)
|
||||
|
@@ -86,38 +86,37 @@ auto APU::Square1::read(uint16 addr) -> uint8 {
|
||||
|
||||
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff10) { //NR10
|
||||
if(sweepEnable && sweepNegate && !(data & 0x08)) enable = false;
|
||||
sweepFrequency = (data >> 4) & 7;
|
||||
sweepDirection = data & 0x08;
|
||||
sweepShift = data & 0x07;
|
||||
if(sweepEnable && sweepNegate && !data.bit(3)) enable = false;
|
||||
sweepFrequency = data.bits(6,4);
|
||||
sweepDirection = data.bit (3);
|
||||
sweepShift = data.bits(2,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff11) { //NR11
|
||||
duty = data >> 6;
|
||||
length = 64 - (data & 0x3f);
|
||||
duty = data.bits(7,6);
|
||||
length = 64 - data.bits(5,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff12) { //NR12
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff13) { //NR13
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff14) { //NR14
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(initialize) {
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
period = 2 * (2048 - frequency);
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
@@ -125,7 +124,7 @@ auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
|
||||
frequencyShadow = frequency;
|
||||
|
@@ -60,31 +60,30 @@ auto APU::Square2::read(uint16 addr) -> uint8 {
|
||||
|
||||
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff16) { //NR21
|
||||
duty = data >> 6;
|
||||
length = 64 - (data & 0x3f);
|
||||
duty = data.bits(7,6);
|
||||
length = 64 - data.bits(5,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff17) { //NR22
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff18) { //NR23
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff19) { //NR24
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(initialize) {
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
period = 2 * (2048 - frequency);
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
@@ -92,7 +91,7 @@ auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -59,7 +59,7 @@ auto APU::Wave::read(uint16 addr) -> uint8 {
|
||||
|
||||
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff1a) { //NR30
|
||||
dacEnable = data & 0x80;
|
||||
dacEnable = data.bit(7);
|
||||
if(!dacEnable) enable = false;
|
||||
}
|
||||
|
||||
@@ -68,23 +68,22 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
if(addr == 0xff1c) { //NR32
|
||||
volume = data >> 5;
|
||||
volume = data.bits(6,5);
|
||||
}
|
||||
|
||||
if(addr == 0xff1d) { //NR33
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff1e) { //NR34
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(initialize) {
|
||||
if(data.bit(7)) {
|
||||
if(!system.cgb() && patternHold) {
|
||||
//DMG,SGB trigger while channel is being read corrupts wave RAM
|
||||
if((patternOffset >> 1) <= 3) {
|
||||
@@ -107,7 +106,7 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
if(!length) {
|
||||
length = 256;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,15 +13,6 @@ namespace GameBoy {
|
||||
#include "serialization.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
Cartridge::Cartridge() {
|
||||
loaded = false;
|
||||
sha256 = "";
|
||||
}
|
||||
|
||||
Cartridge::~Cartridge() {
|
||||
unload();
|
||||
}
|
||||
|
||||
auto Cartridge::manifest() const -> string {
|
||||
return information.markup;
|
||||
}
|
||||
@@ -30,25 +21,9 @@ auto Cartridge::title() const -> string {
|
||||
return information.title;
|
||||
}
|
||||
|
||||
//intended for use with Super Game Boy for when no Game Boy cartridge is inserted
|
||||
auto Cartridge::load_empty(System::Revision revision) -> void {
|
||||
unload();
|
||||
romsize = 32768;
|
||||
romdata = allocate<uint8>(romsize, 0xff);
|
||||
ramsize = 0;
|
||||
mapper = &mbc0;
|
||||
sha256 = Hash::SHA256(romdata, romsize).digest();
|
||||
loaded = true;
|
||||
system.load(revision);
|
||||
}
|
||||
|
||||
auto Cartridge::load(System::Revision revision) -> void {
|
||||
unload();
|
||||
|
||||
system.revision = revision; //needed for ID::Manifest to return correct group ID
|
||||
if(revision != System::Revision::SuperGameBoy) {
|
||||
interface->loadRequest(ID::Manifest, "manifest.bml", true);
|
||||
}
|
||||
information.markup = "";
|
||||
interface->loadRequest(ID::Manifest, "manifest.bml", !system.sgb());
|
||||
|
||||
information.mapper = Mapper::Unknown;
|
||||
information.ram = false;
|
||||
@@ -78,21 +53,18 @@ auto Cartridge::load(System::Revision revision) -> void {
|
||||
auto rom = document["board/rom"];
|
||||
auto ram = document["board/ram"];
|
||||
|
||||
romsize = rom["size"].natural();
|
||||
romsize = max(32768u, rom["size"].natural());
|
||||
romdata = allocate<uint8>(romsize, 0xff);
|
||||
|
||||
ramsize = ram["size"].natural();
|
||||
ramdata = allocate<uint8>(ramsize, 0xff);
|
||||
|
||||
//Super Game Boy core loads memory from Super Famicom core
|
||||
if(revision != System::Revision::SuperGameBoy) {
|
||||
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, true);
|
||||
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name, false);
|
||||
if(auto name = ram["name"].text()) memory.append({ID::RAM, name});
|
||||
}
|
||||
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, !system.sgb());
|
||||
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name, false);
|
||||
if(auto name = ram["name"].text()) memory.append({ID::RAM, name});
|
||||
|
||||
information.romsize = rom["size"].natural();
|
||||
information.ramsize = ram["size"].natural();
|
||||
information.romsize = romsize;
|
||||
information.ramsize = ramsize;
|
||||
information.battery = (bool)ram["name"];
|
||||
|
||||
switch(information.mapper) { default:
|
||||
@@ -107,14 +79,11 @@ auto Cartridge::load(System::Revision revision) -> void {
|
||||
}
|
||||
|
||||
sha256 = Hash::SHA256(romdata, romsize).digest();
|
||||
loaded = true;
|
||||
system.load(revision);
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
if(romdata) { delete[] romdata; romdata = nullptr; romsize = 0; }
|
||||
if(ramdata) { delete[] ramdata; ramdata = nullptr; ramsize = 0; }
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
auto Cartridge::rom_read(uint addr) -> uint8 {
|
||||
@@ -144,7 +113,7 @@ auto Cartridge::mmio_read(uint16 addr) -> uint8 {
|
||||
|
||||
if(bootrom_enable) {
|
||||
const uint8* data = nullptr;
|
||||
switch(system.revision) { default:
|
||||
switch(system.revision()) { default:
|
||||
case System::Revision::GameBoy: data = system.bootROM.dmg; break;
|
||||
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break;
|
||||
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
|
||||
|
@@ -1,8 +1,4 @@
|
||||
struct Cartridge : MMIO, property<Cartridge> {
|
||||
Cartridge();
|
||||
~Cartridge();
|
||||
|
||||
auto load_empty(System::Revision revision) -> void;
|
||||
auto load(System::Revision revision) -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
@@ -62,7 +58,6 @@ struct Cartridge : MMIO, property<Cartridge> {
|
||||
};
|
||||
vector<Memory> memory;
|
||||
|
||||
readonly<bool> loaded;
|
||||
readonly<string> sha256;
|
||||
|
||||
uint8* romdata = nullptr;
|
||||
|
@@ -8,20 +8,13 @@ namespace GameBoy {
|
||||
#include "serialization.cpp"
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Main() -> void {
|
||||
cpu.main();
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
interrupt_test();
|
||||
exec();
|
||||
}
|
||||
interrupt_test();
|
||||
exec();
|
||||
}
|
||||
|
||||
auto CPU::interrupt_raise(CPU::Interrupt id) -> void {
|
||||
@@ -102,7 +95,7 @@ auto CPU::stop() -> bool {
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
create(Main, 4 * 1024 * 1024);
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
LR35902::power();
|
||||
|
||||
for(uint n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM
|
||||
|
@@ -1,7 +1,7 @@
|
||||
struct CPU : Processor::LR35902, Thread, MMIO {
|
||||
enum class Interrupt : uint { Vblank, Stat, Timer, Serial, Joypad };
|
||||
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto interrupt_raise(Interrupt id) -> void;
|
||||
auto interrupt_test() -> void;
|
||||
|
@@ -18,7 +18,7 @@ auto CPU::mmio_joyp_poll() -> void {
|
||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Left) << 1;
|
||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Right) << 0;
|
||||
|
||||
if(system.revision != System::Revision::SuperGameBoy) {
|
||||
if(system.revision() != System::Revision::SuperGameBoy) {
|
||||
//D-pad pivot makes it impossible to press opposing directions at the same time
|
||||
//however, Super Game Boy BIOS is able to set these bits together
|
||||
if(dpad & 4) dpad &= ~8; //disallow up+down
|
||||
@@ -38,17 +38,19 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
||||
|
||||
if(addr == 0xff00) { //JOYP
|
||||
mmio_joyp_poll();
|
||||
return (status.p15 << 5)
|
||||
return 0xc0
|
||||
| (status.p15 << 5)
|
||||
| (status.p14 << 4)
|
||||
| (status.joyp << 0);
|
||||
}
|
||||
|
||||
if(addr == 0xff01) { //SB
|
||||
return 0xff;
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
if(addr == 0xff02) { //SC
|
||||
return (status.serial_transfer << 7)
|
||||
| 0x7e
|
||||
| (status.serial_clock << 0);
|
||||
}
|
||||
|
||||
@@ -65,12 +67,14 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
||||
}
|
||||
|
||||
if(addr == 0xff07) { //TAC
|
||||
return (status.timer_enable << 2)
|
||||
return 0xf8
|
||||
| (status.timer_enable << 2)
|
||||
| (status.timer_clock << 0);
|
||||
}
|
||||
|
||||
if(addr == 0xff0f) { //IF
|
||||
return (status.interrupt_request_joypad << 4)
|
||||
return 0xe0
|
||||
| (status.interrupt_request_joypad << 4)
|
||||
| (status.interrupt_request_serial << 3)
|
||||
| (status.interrupt_request_timer << 2)
|
||||
| (status.interrupt_request_stat << 1)
|
||||
@@ -123,7 +127,8 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
||||
}
|
||||
|
||||
if(addr == 0xffff) { //IE
|
||||
return (status.interrupt_enable_joypad << 4)
|
||||
return 0xe0
|
||||
| (status.interrupt_enable_joypad << 4)
|
||||
| (status.interrupt_enable_serial << 3)
|
||||
| (status.interrupt_enable_timer << 2)
|
||||
| (status.interrupt_enable_stat << 1)
|
||||
|
@@ -3,7 +3,7 @@
|
||||
// 154 scanlines/frame
|
||||
|
||||
auto CPU::add_clocks(uint clocks) -> void {
|
||||
if(system.sgb()) system.clocks_executed += clocks;
|
||||
if(system.sgb()) system._clocksExecuted += clocks;
|
||||
|
||||
while(clocks--) {
|
||||
if(++status.clock == 0) {
|
||||
@@ -19,13 +19,13 @@ auto CPU::add_clocks(uint clocks) -> void {
|
||||
if((status.div & 1023) == 0) timer_4096hz();
|
||||
|
||||
ppu.clock -= ppu.frequency;
|
||||
if(ppu.clock < 0) co_switch(scheduler.active_thread = ppu.thread);
|
||||
if(ppu.clock < 0) co_switch(ppu.thread);
|
||||
|
||||
apu.clock -= apu.frequency;
|
||||
if(apu.clock < 0) co_switch(scheduler.active_thread = apu.thread);
|
||||
if(apu.clock < 0) co_switch(apu.thread);
|
||||
}
|
||||
|
||||
if(system.sgb()) scheduler.exit(Scheduler::ExitReason::StepEvent);
|
||||
if(system.sgb()) scheduler.exit(Scheduler::Event::Step);
|
||||
}
|
||||
|
||||
auto CPU::timer_262144hz() -> void {
|
||||
|
@@ -50,7 +50,6 @@ namespace GameBoy {
|
||||
#include <gb/ppu/ppu.hpp>
|
||||
#include <gb/apu/apu.hpp>
|
||||
#include <gb/cheat/cheat.hpp>
|
||||
#include <gb/video/video.hpp>
|
||||
}
|
||||
|
||||
#include <gb/interface/interface.hpp>
|
||||
|
@@ -9,12 +9,14 @@ Interface::Interface() {
|
||||
interface = this;
|
||||
hook = nullptr;
|
||||
|
||||
information.name = "Game Boy";
|
||||
information.width = 160;
|
||||
information.height = 144;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Game Boy";
|
||||
information.width = 160;
|
||||
information.height = 144;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
@@ -55,7 +57,7 @@ auto Interface::audioFrequency() -> double {
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return cartridge.loaded();
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::sha256() -> string {
|
||||
@@ -72,7 +74,7 @@ auto Interface::group(uint id) -> uint {
|
||||
case ID::Manifest:
|
||||
case ID::ROM:
|
||||
case ID::RAM:
|
||||
switch(system.revision) {
|
||||
switch(system.revision()) {
|
||||
case System::Revision::GameBoy: return ID::GameBoy;
|
||||
case System::Revision::SuperGameBoy: return ID::SuperGameBoy;
|
||||
case System::Revision::GameBoyColor: return ID::GameBoyColor;
|
||||
@@ -83,9 +85,9 @@ auto Interface::group(uint id) -> uint {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> void {
|
||||
if(id == ID::GameBoy) cartridge.load(System::Revision::GameBoy);
|
||||
if(id == ID::SuperGameBoy) cartridge.load(System::Revision::SuperGameBoy);
|
||||
if(id == ID::GameBoyColor) cartridge.load(System::Revision::GameBoyColor);
|
||||
if(id == ID::GameBoy) system.load(System::Revision::GameBoy);
|
||||
if(id == ID::SuperGameBoy) system.load(System::Revision::SuperGameBoy);
|
||||
if(id == ID::GameBoyColor) system.load(System::Revision::GameBoyColor);
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
@@ -100,15 +102,15 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
||||
}
|
||||
|
||||
if(id == ID::GameBoyBootROM) {
|
||||
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
|
||||
stream.read((uint8_t*)system.bootROM.dmg, min( 256u, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::SuperGameBoyBootROM) {
|
||||
stream.read(system.bootROM.sgb, min( 256u, stream.size()));
|
||||
stream.read((uint8_t*)system.bootROM.sgb, min( 256u, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::GameBoyColorBootROM) {
|
||||
stream.read(system.bootROM.cgb, min(2048u, stream.size()));
|
||||
stream.read((uint8_t*)system.bootROM.cgb, min(2048u, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::Manifest) {
|
||||
@@ -116,23 +118,23 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
||||
}
|
||||
|
||||
if(id == ID::ROM) {
|
||||
stream.read(cartridge.romdata, min(cartridge.romsize, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.romdata, min(cartridge.romsize, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::RAM) {
|
||||
stream.read(cartridge.ramdata, min(stream.size(), cartridge.ramsize));
|
||||
stream.read((uint8_t*)cartridge.ramdata, min(stream.size(), cartridge.ramsize));
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::save(uint id, const stream& stream) -> void {
|
||||
if(id == ID::RAM) {
|
||||
stream.write(cartridge.ramdata, cartridge.ramsize);
|
||||
stream.write((uint8_t*)cartridge.ramdata, cartridge.ramsize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
cartridge.unload();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
@@ -148,7 +150,7 @@ auto Interface::run() -> void {
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
system.runtosave();
|
||||
system.runToSave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
|
@@ -16,13 +16,13 @@ auto Memory::operator[](uint addr) -> uint8& {
|
||||
auto Memory::allocate(uint size_) -> void {
|
||||
free();
|
||||
size = size_;
|
||||
data = new uint8_t[size]();
|
||||
data = new uint8[size];
|
||||
}
|
||||
|
||||
auto Memory::copy(const uint8_t* data_, unsigned size_) -> void {
|
||||
auto Memory::copy(const uint8* data_, unsigned size_) -> void {
|
||||
free();
|
||||
size = size_;
|
||||
data = new uint8_t[size];
|
||||
data = new uint8[size];
|
||||
memcpy(data, data_, size);
|
||||
}
|
||||
|
||||
|
@@ -125,7 +125,7 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
//restart cothread to begin new frame
|
||||
auto clock = this->clock;
|
||||
create(Main, 4 * 1024 * 1024);
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
this->clock = clock;
|
||||
}
|
||||
|
||||
|
@@ -8,66 +8,63 @@
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
PPU ppu;
|
||||
#include "video.cpp"
|
||||
|
||||
#include "mmio.cpp"
|
||||
#include "dmg.cpp"
|
||||
#include "cgb.cpp"
|
||||
#include "serialization.cpp"
|
||||
PPU ppu;
|
||||
|
||||
auto PPU::Main() -> void {
|
||||
ppu.main();
|
||||
auto PPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
status.lx = 0;
|
||||
interface->lcdScanline(); //Super Game Boy notification
|
||||
|
||||
if(status.display_enable) {
|
||||
//LYC of zero triggers on LY==153
|
||||
if((status.lyc && status.ly == status.lyc) || (!status.lyc && status.ly == 153)) {
|
||||
if(status.interrupt_lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
|
||||
status.lx = 0;
|
||||
interface->lcdScanline(); //Super Game Boy notification
|
||||
|
||||
if(status.display_enable) {
|
||||
//LYC of zero triggers on LY==153
|
||||
if((status.lyc && status.ly == status.lyc) || (!status.lyc && status.ly == 153)) {
|
||||
if(status.interrupt_lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
|
||||
if(status.ly <= 143) {
|
||||
scanline();
|
||||
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
|
||||
if(status.ly == 144) {
|
||||
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
else if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat); //hardware quirk
|
||||
cpu.interrupt_raise(CPU::Interrupt::Vblank);
|
||||
}
|
||||
}
|
||||
|
||||
add_clocks(92);
|
||||
|
||||
if(status.ly <= 143) {
|
||||
for(auto n : range(160)) {
|
||||
if(status.display_enable) run();
|
||||
add_clocks(1);
|
||||
}
|
||||
|
||||
if(status.display_enable) {
|
||||
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
cpu.hblank();
|
||||
}
|
||||
} else {
|
||||
add_clocks(160);
|
||||
scanline();
|
||||
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
|
||||
add_clocks(204);
|
||||
|
||||
if(++status.ly == 154) {
|
||||
status.ly = 0;
|
||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||
if(status.ly == 144) {
|
||||
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
else if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat); //hardware quirk
|
||||
cpu.interrupt_raise(CPU::Interrupt::Vblank);
|
||||
}
|
||||
}
|
||||
|
||||
add_clocks(92);
|
||||
|
||||
if(status.ly <= 143) {
|
||||
for(auto n : range(160)) {
|
||||
if(status.display_enable) run();
|
||||
add_clocks(1);
|
||||
}
|
||||
|
||||
if(status.display_enable) {
|
||||
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
cpu.hblank();
|
||||
}
|
||||
} else {
|
||||
add_clocks(160);
|
||||
}
|
||||
|
||||
add_clocks(204);
|
||||
|
||||
if(++status.ly == 154) {
|
||||
status.ly = 0;
|
||||
video.refresh();
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::add_clocks(uint clocks) -> void {
|
||||
@@ -90,9 +87,7 @@ auto PPU::add_clocks(uint clocks) -> void {
|
||||
|
||||
status.lx++;
|
||||
clock += cpu.frequency;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||
co_switch(scheduler.active_thread = cpu.thread);
|
||||
}
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +99,7 @@ auto PPU::hflip(uint data) const -> uint {
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
create(Main, 4 * 1024 * 1024);
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
|
||||
if(system.cgb()) {
|
||||
scanline = {&PPU::cgb_scanline, this};
|
||||
@@ -141,8 +136,8 @@ auto PPU::power() -> void {
|
||||
for(auto& n : vram) n = 0x00;
|
||||
for(auto& n : oam) n = 0x00;
|
||||
for(auto& n : bgp) n = 0x00;
|
||||
for(auto& n : obp[0]) n = 0x00;
|
||||
for(auto& n : obp[1]) n = 0x00;
|
||||
for(auto& n : obp[0]) n = 3;
|
||||
for(auto& n : obp[1]) n = 3;
|
||||
for(auto& n : bgpd) n = 0x0000;
|
||||
for(auto& n : obpd) n = 0x0000;
|
||||
|
||||
|
@@ -1,5 +1,7 @@
|
||||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread, MMIO {
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto add_clocks(uint clocks) -> void;
|
||||
|
||||
|
@@ -1,37 +1,32 @@
|
||||
#include <gb/gb.hpp>
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[160 * 144];
|
||||
paletteLiteral = new uint32[1 << 15];
|
||||
paletteStandard = new uint32[1 << 15];
|
||||
paletteEmulation = new uint32[1 << 15];
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
delete[] output;
|
||||
delete[] paletteStandard;
|
||||
delete[] paletteEmulation;
|
||||
}
|
||||
|
||||
auto Video::power() -> void {
|
||||
memory::fill(output, 160 * 144 * sizeof(uint32));
|
||||
memory::fill(output(), 160 * 144 * sizeof(uint32));
|
||||
|
||||
if(system.dmg()) {
|
||||
for(auto color : range(1 << 2)) {
|
||||
uint L = image::normalize(3 - color, 2, 8);
|
||||
uint R = monochrome[color][0] >> 8;
|
||||
uint G = monochrome[color][1] >> 8;
|
||||
uint B = monochrome[color][2] >> 8;
|
||||
paletteStandard[color] = (255 << 24) | (L << 16) | (L << 8) | (L << 0);
|
||||
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
uint L = image::normalize(3 - color, 2, 16);
|
||||
paletteStandard[color] = interface->videoColor(L, L, L);
|
||||
|
||||
uint R = monochrome[color][0];
|
||||
uint G = monochrome[color][1];
|
||||
uint B = monochrome[color][2];
|
||||
paletteEmulation[color] = interface->videoColor(R, G, B);
|
||||
}
|
||||
}
|
||||
|
||||
if(system.sgb()) {
|
||||
for(auto color : range(1 << 2)) {
|
||||
paletteLiteral[color] = color;
|
||||
paletteStandard[color] = color;
|
||||
paletteEmulation[color] = color;
|
||||
}
|
||||
@@ -39,30 +34,31 @@ auto Video::power() -> void {
|
||||
|
||||
if(system.cgb()) {
|
||||
for(auto color : range(1 << 15)) {
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
uint r = (uint5)(color >> 0);
|
||||
uint g = (uint5)(color >> 5);
|
||||
uint b = (uint5)(color >> 10);
|
||||
|
||||
{ uint R = image::normalize(r, 5, 8);
|
||||
uint G = image::normalize(g, 5, 8);
|
||||
uint B = image::normalize(b, 5, 8);
|
||||
paletteStandard[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
||||
}
|
||||
uint R = image::normalize(r, 5, 16);
|
||||
uint G = image::normalize(g, 5, 16);
|
||||
uint B = image::normalize(b, 5, 16);
|
||||
paletteStandard[color] = interface->videoColor(R, G, B);
|
||||
|
||||
{ uint R = (r * 26 + g * 4 + b * 2);
|
||||
uint G = ( g * 24 + b * 8);
|
||||
uint B = (r * 6 + g * 4 + b * 22);
|
||||
R = min(960, R) >> 2;
|
||||
G = min(960, G) >> 2;
|
||||
B = min(960, B) >> 2;
|
||||
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
||||
}
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(960, R), 10, 16);
|
||||
G = image::normalize(min(960, G), 10, 16);
|
||||
B = image::normalize(min(960, B), 10, 16);
|
||||
paletteEmulation[color] = interface->videoColor(R, G, B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
auto output = this->output();
|
||||
auto& palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
for(uint y = 0; y < 144; y++) {
|
||||
auto source = ppu.screen + y * 160;
|
||||
@@ -107,5 +103,3 @@ const uint16 Video::monochrome[4][3] = {
|
||||
{0x0000, 0x0000, 0x0000},
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
16
higan/gb/ppu/video.hpp
Normal file
16
higan/gb/ppu/video.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
struct Video {
|
||||
Video();
|
||||
|
||||
auto power() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
private:
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
|
||||
static const uint16 monochrome[4][3];
|
||||
};
|
||||
|
||||
extern Video video;
|
@@ -4,20 +4,40 @@ namespace GameBoy {
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
auto Scheduler::init() -> void {
|
||||
host_thread = co_active();
|
||||
active_thread = cpu.thread;
|
||||
auto Scheduler::power() -> void {
|
||||
host = co_active();
|
||||
resume = cpu.thread;
|
||||
}
|
||||
|
||||
auto Scheduler::enter() -> void {
|
||||
host_thread = co_active();
|
||||
co_switch(active_thread);
|
||||
auto Scheduler::enter(Mode mode_) -> Event {
|
||||
mode = mode_;
|
||||
host = co_active();
|
||||
co_switch(resume);
|
||||
return event;
|
||||
}
|
||||
|
||||
auto Scheduler::exit(ExitReason reason) -> void {
|
||||
exit_reason = reason;
|
||||
active_thread = co_active();
|
||||
co_switch(host_thread);
|
||||
auto Scheduler::exit(Event event_) -> void {
|
||||
event = event_;
|
||||
resume = co_active();
|
||||
co_switch(host);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize(cothread_t thread) -> void {
|
||||
if(thread == cpu.thread) {
|
||||
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
|
||||
} else {
|
||||
resume = thread;
|
||||
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize() -> void {
|
||||
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
|
||||
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronizing() const -> bool {
|
||||
return mode == Mode::SynchronizeAll;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,14 +1,29 @@
|
||||
struct Scheduler {
|
||||
enum class SynchronizeMode : uint { None, CPU, All } sync;
|
||||
enum class ExitReason : uint { UnknownEvent, StepEvent, FrameEvent, SynchronizeEvent };
|
||||
enum class Mode : uint {
|
||||
Run,
|
||||
SynchronizeCPU,
|
||||
SynchronizeAll,
|
||||
};
|
||||
|
||||
auto init() -> void;
|
||||
auto enter() -> void;
|
||||
auto exit(ExitReason) -> void;
|
||||
enum class Event : uint {
|
||||
Unknown,
|
||||
Step,
|
||||
Frame,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
cothread_t host_thread = nullptr;
|
||||
cothread_t active_thread = nullptr;
|
||||
ExitReason exit_reason = ExitReason::UnknownEvent;
|
||||
auto power() -> void;
|
||||
auto enter(Mode = Mode::Run) -> Event;
|
||||
auto exit(Event) -> void;
|
||||
auto synchronize(cothread_t) -> void;
|
||||
auto synchronize() -> void;
|
||||
auto synchronizing() const -> bool;
|
||||
|
||||
private:
|
||||
cothread_t host = nullptr;
|
||||
cothread_t resume = nullptr;
|
||||
Mode mode = Mode::Run;
|
||||
Event event = Event::Unknown;
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(serialize_size);
|
||||
serializer s(_serializeSize);
|
||||
|
||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||
char hash[64], description[512];
|
||||
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -28,15 +28,15 @@ auto System::unserialize(serializer& s) -> bool {
|
||||
if(version != Info::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s) -> void {
|
||||
s.integer(clocks_executed);
|
||||
s.integer(_clocksExecuted);
|
||||
}
|
||||
|
||||
auto System::serialize_all(serializer& s) -> void {
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
cartridge.serialize(s);
|
||||
system.serialize(s);
|
||||
cpu.serialize(s);
|
||||
@@ -44,10 +44,10 @@ auto System::serialize_all(serializer& s) -> void {
|
||||
apu.serialize(s);
|
||||
}
|
||||
|
||||
auto System::serialize_init() -> void {
|
||||
auto System::serializeInit() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0, version = 0, crc32 = 0;
|
||||
uint signature = 0, version = 0;
|
||||
char hash[64], description[512];
|
||||
|
||||
s.integer(signature);
|
||||
@@ -55,6 +55,6 @@ auto System::serialize_init() -> void {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serialize_size = s.size();
|
||||
serializeAll(s);
|
||||
_serializeSize = s.size();
|
||||
}
|
||||
|
@@ -5,6 +5,10 @@ namespace GameBoy {
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
auto System::loaded() const -> bool { return _loaded; }
|
||||
auto System::revision() const -> Revision { return _revision; }
|
||||
auto System::clocksExecuted() const -> uint { return _clocksExecuted; }
|
||||
|
||||
System::System() {
|
||||
for(auto& byte : bootROM.dmg) byte = 0;
|
||||
for(auto& byte : bootROM.sgb) byte = 0;
|
||||
@@ -12,37 +16,13 @@ System::System() {
|
||||
}
|
||||
|
||||
auto System::run() -> void {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
||||
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
auto System::runtosave() -> void {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::CPU;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.active_thread = ppu.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.active_thread = apu.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
||||
}
|
||||
|
||||
auto System::runthreadtosave() -> void {
|
||||
while(true) {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
auto System::runToSave() -> void {
|
||||
scheduler.synchronize(cpu.thread);
|
||||
scheduler.synchronize(ppu.thread);
|
||||
scheduler.synchronize(apu.thread);
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
@@ -50,19 +30,32 @@ auto System::init() -> void {
|
||||
}
|
||||
|
||||
auto System::load(Revision revision) -> void {
|
||||
this->revision = revision;
|
||||
serialize_init();
|
||||
if(revision == Revision::SuperGameBoy) return; //Super Famicom core loads boot ROM for SGB
|
||||
_revision = revision;
|
||||
|
||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
string path = "system/cpu/rom/name";
|
||||
if(revision == Revision::SuperGameBoy) path = "board/icd2/rom/name";
|
||||
|
||||
if(auto bootROM = document["system/cpu/rom/name"].text()) {
|
||||
if(auto bootROM = document[path].text()) {
|
||||
interface->loadRequest(
|
||||
revision == Revision::GameBoy ? ID::GameBoyBootROM : ID::GameBoyColorBootROM,
|
||||
revision == Revision::GameBoy ? ID::GameBoyBootROM
|
||||
: revision == Revision::SuperGameBoy ? ID::SuperGameBoyBootROM
|
||||
: revision == Revision::GameBoyColor ? ID::GameBoyColorBootROM
|
||||
: ID::GameBoyBootROM,
|
||||
bootROM, true
|
||||
);
|
||||
}
|
||||
|
||||
cartridge.load(revision);
|
||||
serializeInit();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
cartridge.unload();
|
||||
_loaded = false;
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
@@ -72,9 +65,9 @@ auto System::power() -> void {
|
||||
ppu.power();
|
||||
apu.power();
|
||||
video.power();
|
||||
scheduler.init();
|
||||
scheduler.power();
|
||||
|
||||
clocks_executed = 0;
|
||||
_clocksExecuted = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,20 +9,24 @@ struct System {
|
||||
GameBoy,
|
||||
SuperGameBoy,
|
||||
GameBoyColor,
|
||||
} revision;
|
||||
};
|
||||
|
||||
System();
|
||||
|
||||
inline auto dmg() const { return revision == Revision::GameBoy; }
|
||||
inline auto sgb() const { return revision == Revision::SuperGameBoy; }
|
||||
inline auto cgb() const { return revision == Revision::GameBoyColor; }
|
||||
auto loaded() const -> bool;
|
||||
auto revision() const -> Revision;
|
||||
auto clocksExecuted() const -> uint;
|
||||
|
||||
inline auto dmg() const { return _revision == Revision::GameBoy; }
|
||||
inline auto sgb() const { return _revision == Revision::SuperGameBoy; }
|
||||
inline auto cgb() const { return _revision == Revision::GameBoyColor; }
|
||||
|
||||
auto run() -> void;
|
||||
auto runtosave() -> void;
|
||||
auto runthreadtosave() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto load(Revision) -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
@@ -30,8 +34,8 @@ struct System {
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
auto serialize_all(serializer&) -> void;
|
||||
auto serialize_init() -> void;
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
struct BootROM {
|
||||
uint8 dmg[ 256];
|
||||
@@ -43,8 +47,10 @@ struct System {
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
uint clocks_executed = 0;
|
||||
uint serialize_size = 0;
|
||||
bool _loaded = false;
|
||||
Revision _revision = Revision::GameBoy;
|
||||
uint _serializeSize = 0;
|
||||
uint _clocksExecuted = 0;
|
||||
};
|
||||
|
||||
#include <gb/interface/interface.hpp>
|
||||
|
@@ -1,16 +0,0 @@
|
||||
struct Video {
|
||||
Video();
|
||||
~Video();
|
||||
|
||||
auto power() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
uint32* output = nullptr;
|
||||
uint32* paletteStandard = nullptr;
|
||||
uint32* paletteEmulation = nullptr;
|
||||
|
||||
private:
|
||||
static const uint16 monochrome[4][3];
|
||||
};
|
||||
|
||||
extern Video video;
|
@@ -1,15 +1,15 @@
|
||||
gba_objects := gba-memory gba-interface gba-scheduler gba-system
|
||||
gba_objects += gba-video gba-cartridge gba-player
|
||||
gba_objects += gba-cpu gba-ppu gba-apu
|
||||
objects += $(gba_objects)
|
||||
processors += arm
|
||||
|
||||
obj/gba-memory.o: $(gba)/memory/memory.cpp $(call rwildcard,$(gba)/memory)
|
||||
obj/gba-interface.o: $(gba)/interface/interface.cpp $(call rwildcard,$(gba)/interface)
|
||||
obj/gba-scheduler.o: $(gba)/scheduler/scheduler.cpp $(call rwildcard,$(gba)/scheduler)
|
||||
obj/gba-system.o: $(gba)/system/system.cpp $(call rwildcard,$(gba)/system)
|
||||
obj/gba-video.o: $(gba)/video/video.cpp $(call rwildcard,$(gba)/video)
|
||||
obj/gba-cartridge.o: $(gba)/cartridge/cartridge.cpp $(call rwildcard,$(gba)/cartridge)
|
||||
obj/gba-player.o: $(gba)/player/player.cpp $(call rwildcard,$(gba)/player)
|
||||
obj/gba-cpu.o: $(gba)/cpu/cpu.cpp $(call rwildcard,$(gba)/cpu)
|
||||
obj/gba-ppu.o: $(gba)/ppu/ppu.cpp $(call rwildcard,$(gba)/ppu)
|
||||
obj/gba-apu.o: $(gba)/apu/apu.cpp $(call rwildcard,$(gba)/apu)
|
||||
objects += gba-memory gba-interface gba-scheduler gba-system
|
||||
objects += gba-cartridge gba-player
|
||||
objects += gba-cpu gba-ppu gba-apu
|
||||
|
||||
obj/gba-memory.o: gba/memory/memory.cpp $(call rwildcard,gba/memory)
|
||||
obj/gba-interface.o: gba/interface/interface.cpp $(call rwildcard,gba/interface)
|
||||
obj/gba-scheduler.o: gba/scheduler/scheduler.cpp $(call rwildcard,gba/scheduler)
|
||||
obj/gba-system.o: gba/system/system.cpp $(call rwildcard,gba/system)
|
||||
obj/gba-cartridge.o: gba/cartridge/cartridge.cpp $(call rwildcard,gba/cartridge)
|
||||
obj/gba-player.o: gba/player/player.cpp $(call rwildcard,gba/player)
|
||||
obj/gba-cpu.o: gba/cpu/cpu.cpp $(call rwildcard,gba/cpu)
|
||||
obj/gba-ppu.o: gba/ppu/ppu.cpp $(call rwildcard,gba/ppu)
|
||||
obj/gba-apu.o: gba/apu/apu.cpp $(call rwildcard,gba/apu)
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "registers.cpp"
|
||||
#include "mmio.cpp"
|
||||
#include "square.cpp"
|
||||
#include "square1.cpp"
|
||||
@@ -15,13 +14,7 @@ namespace GameBoyAdvance {
|
||||
APU apu;
|
||||
|
||||
auto APU::Enter() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
apu.main();
|
||||
}
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
@@ -76,11 +69,11 @@ auto APU::main() -> void {
|
||||
|
||||
auto APU::step(uint clocks) -> void {
|
||||
clock += clocks;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
create(APU::Enter, 16777216);
|
||||
create(APU::Enter, 16'777'216);
|
||||
|
||||
square1.power();
|
||||
square2.power();
|
||||
@@ -90,7 +83,8 @@ auto APU::power() -> void {
|
||||
fifo[0].power();
|
||||
fifo[1].power();
|
||||
|
||||
regs.bias = 0x0200;
|
||||
regs.bias.amplitude = 0;
|
||||
regs.bias.level = 0x200;
|
||||
|
||||
for(uint n = 0x060; n <= 0x0a7; n++) bus.mmio[n] = this;
|
||||
}
|
||||
|
@@ -2,215 +2,234 @@ auto APU::read(uint32 addr) -> uint8 {
|
||||
switch(addr) {
|
||||
|
||||
//NR10
|
||||
case 0x04000060: return square1.read(0);
|
||||
case 0x04000061: return 0u;
|
||||
case 0x0400'0060: return square1.read(0);
|
||||
case 0x0400'0061: return 0;
|
||||
|
||||
//NR11 + NR12
|
||||
case 0x04000062: return square1.read(1);
|
||||
case 0x04000063: return square1.read(2);
|
||||
//NR11, NR12
|
||||
case 0x0400'0062: return square1.read(1);
|
||||
case 0x0400'0063: return square1.read(2);
|
||||
|
||||
//NR13 + NR14
|
||||
case 0x04000064: return square1.read(3);
|
||||
case 0x04000065: return square1.read(4);
|
||||
//NR13, NR14
|
||||
case 0x0400'0064: return square1.read(3);
|
||||
case 0x0400'0065: return square1.read(4);
|
||||
|
||||
//NR21 + NR22
|
||||
case 0x04000068: return square2.read(1);
|
||||
case 0x04000069: return square2.read(2);
|
||||
//NR21, NR22
|
||||
case 0x0400'0068: return square2.read(1);
|
||||
case 0x0400'0069: return square2.read(2);
|
||||
|
||||
//NR23 + NR24
|
||||
case 0x0400006c: return square2.read(3);
|
||||
case 0x0400006d: return square2.read(4);
|
||||
//NR23, NR24
|
||||
case 0x0400'006c: return square2.read(3);
|
||||
case 0x0400'006d: return square2.read(4);
|
||||
|
||||
//NR30
|
||||
case 0x04000070: return wave.read(0);
|
||||
case 0x04000071: return 0u;
|
||||
case 0x0400'0070: return wave.read(0);
|
||||
case 0x0400'0071: return 0;
|
||||
|
||||
//NR31 + NR32
|
||||
case 0x04000072: return wave.read(1);
|
||||
case 0x04000073: return wave.read(2);
|
||||
//NR31, NR32
|
||||
case 0x0400'0072: return wave.read(1);
|
||||
case 0x0400'0073: return wave.read(2);
|
||||
|
||||
//NR33 + NR34
|
||||
case 0x04000074: return wave.read(3);
|
||||
case 0x04000075: return wave.read(4);
|
||||
//NR33, NR34
|
||||
case 0x0400'0074: return wave.read(3);
|
||||
case 0x0400'0075: return wave.read(4);
|
||||
|
||||
//NR41 + NR42
|
||||
case 0x04000078: return noise.read(1);
|
||||
case 0x04000079: return noise.read(2);
|
||||
//NR41, NR42
|
||||
case 0x0400'0078: return noise.read(1);
|
||||
case 0x0400'0079: return noise.read(2);
|
||||
|
||||
//NR43 + NR44
|
||||
case 0x0400007c: return noise.read(3);
|
||||
case 0x0400007d: return noise.read(4);
|
||||
//NR43, NR44
|
||||
case 0x0400'007c: return noise.read(3);
|
||||
case 0x0400'007d: return noise.read(4);
|
||||
|
||||
//NR50 + NR51
|
||||
case 0x04000080: return sequencer.read(0);
|
||||
case 0x04000081: return sequencer.read(1);
|
||||
//NR50, NR51
|
||||
case 0x0400'0080: return sequencer.read(0);
|
||||
case 0x0400'0081: return sequencer.read(1);
|
||||
|
||||
//SOUND_CNT_H
|
||||
case 0x04000082:
|
||||
return (fifo[1].volume << 3) | (fifo[0].volume << 2) | (sequencer.volume << 0);
|
||||
case 0x04000083:
|
||||
return (fifo[1].timer << 6) | (fifo[1].lenable << 5) | (fifo[1].renable << 4)
|
||||
| (fifo[0].timer << 2) | (fifo[0].lenable << 1) | (fifo[0].renable << 0);
|
||||
case 0x0400'0082: return (
|
||||
sequencer.volume << 0
|
||||
| fifo[0].volume << 2
|
||||
| fifo[1].volume << 3
|
||||
);
|
||||
|
||||
case 0x0400'0083: return (
|
||||
fifo[0].renable << 0
|
||||
| fifo[0].lenable << 1
|
||||
| fifo[0].timer << 2
|
||||
| fifo[1].renable << 4
|
||||
| fifo[1].lenable << 5
|
||||
| fifo[1].timer << 6
|
||||
);
|
||||
|
||||
//NR52
|
||||
case 0x04000084: return sequencer.read(2);
|
||||
case 0x04000085: return 0u;
|
||||
case 0x0400'0084: return sequencer.read(2);
|
||||
case 0x0400'0085: return 0;
|
||||
|
||||
//SOUNDBIAS
|
||||
case 0x04000088: return regs.bias >> 0;
|
||||
case 0x04000089: return regs.bias >> 8;
|
||||
case 0x0400'0088: return (
|
||||
regs.bias.level.bits(0,7)
|
||||
);
|
||||
case 0x0400'0089: return (
|
||||
regs.bias.level.bits(8,9) << 0
|
||||
| regs.bias.amplitude << 6
|
||||
);
|
||||
|
||||
//WAVE_RAM0_L
|
||||
case 0x04000090: return wave.readram( 0);
|
||||
case 0x04000091: return wave.readram( 1);
|
||||
case 0x0400'0090: return wave.readram( 0);
|
||||
case 0x0400'0091: return wave.readram( 1);
|
||||
|
||||
//WAVE_RAM0_H
|
||||
case 0x04000092: return wave.readram( 2);
|
||||
case 0x04000093: return wave.readram( 3);
|
||||
case 0x0400'0092: return wave.readram( 2);
|
||||
case 0x0400'0093: return wave.readram( 3);
|
||||
|
||||
//WAVE_RAM1_L
|
||||
case 0x04000094: return wave.readram( 4);
|
||||
case 0x04000095: return wave.readram( 5);
|
||||
case 0x0400'0094: return wave.readram( 4);
|
||||
case 0x0400'0095: return wave.readram( 5);
|
||||
|
||||
//WAVE_RAM1_H
|
||||
case 0x04000096: return wave.readram( 6);
|
||||
case 0x04000097: return wave.readram( 7);
|
||||
case 0x0400'0096: return wave.readram( 6);
|
||||
case 0x0400'0097: return wave.readram( 7);
|
||||
|
||||
//WAVE_RAM2_L
|
||||
case 0x04000098: return wave.readram( 8);
|
||||
case 0x04000099: return wave.readram( 9);
|
||||
case 0x0400'0098: return wave.readram( 8);
|
||||
case 0x0400'0099: return wave.readram( 9);
|
||||
|
||||
//WAVE_RAM2_H
|
||||
case 0x0400009a: return wave.readram(10);
|
||||
case 0x0400009b: return wave.readram(11);
|
||||
case 0x0400'009a: return wave.readram(10);
|
||||
case 0x0400'009b: return wave.readram(11);
|
||||
|
||||
//WAVE_RAM3_L
|
||||
case 0x0400009c: return wave.readram(12);
|
||||
case 0x0400009d: return wave.readram(13);
|
||||
case 0x0400'009c: return wave.readram(12);
|
||||
case 0x0400'009d: return wave.readram(13);
|
||||
|
||||
//WAVE_RAM3_H
|
||||
case 0x0400009e: return wave.readram(14);
|
||||
case 0x0400009f: return wave.readram(15);
|
||||
case 0x0400'009e: return wave.readram(14);
|
||||
case 0x0400'009f: return wave.readram(15);
|
||||
|
||||
}
|
||||
|
||||
return 0u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto APU::write(uint32 addr, uint8 byte) -> void {
|
||||
auto APU::write(uint32 addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
|
||||
//NR10
|
||||
case 0x04000060: return square1.write(0, byte);
|
||||
case 0x04000061: return;
|
||||
case 0x0400'0060: return square1.write(0, data);
|
||||
case 0x0400'0061: return;
|
||||
|
||||
//NR11 + NR12
|
||||
case 0x04000062: return square1.write(1, byte);
|
||||
case 0x04000063: return square1.write(2, byte);
|
||||
//NR11, NR12
|
||||
case 0x0400'0062: return square1.write(1, data);
|
||||
case 0x0400'0063: return square1.write(2, data);
|
||||
|
||||
//NR13 + NR14
|
||||
case 0x04000064: return square1.write(3, byte);
|
||||
case 0x04000065: return square1.write(4, byte);
|
||||
//NR13, NR14
|
||||
case 0x0400'0064: return square1.write(3, data);
|
||||
case 0x0400'0065: return square1.write(4, data);
|
||||
|
||||
//NR21 + NR22
|
||||
case 0x04000068: return square2.write(1, byte);
|
||||
case 0x04000069: return square2.write(2, byte);
|
||||
//NR21, NR22
|
||||
case 0x0400'0068: return square2.write(1, data);
|
||||
case 0x0400'0069: return square2.write(2, data);
|
||||
|
||||
//NR23 + NR24
|
||||
case 0x0400006c: return square2.write(3, byte);
|
||||
case 0x0400006d: return square2.write(4, byte);
|
||||
//NR23, NR24
|
||||
case 0x0400'006c: return square2.write(3, data);
|
||||
case 0x0400'006d: return square2.write(4, data);
|
||||
|
||||
//NR30
|
||||
case 0x04000070: return wave.write(0, byte);
|
||||
case 0x04000071: return;
|
||||
case 0x0400'0070: return wave.write(0, data);
|
||||
case 0x0400'0071: return;
|
||||
|
||||
//NR31 + NR32
|
||||
case 0x04000072: return wave.write(1, byte);
|
||||
case 0x04000073: return wave.write(2, byte);
|
||||
//NR31, NR32
|
||||
case 0x0400'0072: return wave.write(1, data);
|
||||
case 0x0400'0073: return wave.write(2, data);
|
||||
|
||||
//NR33 + NR34
|
||||
case 0x04000074: return wave.write(3, byte);
|
||||
case 0x04000075: return wave.write(4, byte);
|
||||
//NR33, NR34
|
||||
case 0x0400'0074: return wave.write(3, data);
|
||||
case 0x0400'0075: return wave.write(4, data);
|
||||
|
||||
//NR41 + NR42
|
||||
case 0x04000078: return noise.write(1, byte);
|
||||
case 0x04000079: return noise.write(2, byte);
|
||||
//NR41, NR42
|
||||
case 0x0400'0078: return noise.write(1, data);
|
||||
case 0x0400'0079: return noise.write(2, data);
|
||||
|
||||
//NR43 + NR44
|
||||
case 0x0400007c: return noise.write(3, byte);
|
||||
case 0x0400007d: return noise.write(4, byte);
|
||||
//NR43, NR44
|
||||
case 0x0400'007c: return noise.write(3, data);
|
||||
case 0x0400'007d: return noise.write(4, data);
|
||||
|
||||
//NR50 + NR51
|
||||
case 0x04000080: return sequencer.write(0, byte);
|
||||
case 0x04000081: return sequencer.write(1, byte);
|
||||
//NR50, NR51
|
||||
case 0x0400'0080: return sequencer.write(0, data);
|
||||
case 0x0400'0081: return sequencer.write(1, data);
|
||||
|
||||
//SOUND_CNT_H
|
||||
case 0x04000082:
|
||||
sequencer.volume = byte >> 0;
|
||||
fifo[0].volume = byte >> 2;
|
||||
fifo[1].volume = byte >> 3;
|
||||
case 0x0400'0082:
|
||||
sequencer.volume = data.bits(0,1);
|
||||
fifo[0].volume = data.bit (2);
|
||||
fifo[1].volume = data.bit (3);
|
||||
return;
|
||||
case 0x04000083:
|
||||
fifo[0].renable = byte >> 0;
|
||||
fifo[0].lenable = byte >> 1;
|
||||
fifo[0].timer = byte >> 2;
|
||||
if(byte & 1 << 3) fifo[0].reset();
|
||||
fifo[1].renable = byte >> 4;
|
||||
fifo[1].lenable = byte >> 5;
|
||||
fifo[1].timer = byte >> 6;
|
||||
if(byte & 1 << 7) fifo[1].reset();
|
||||
case 0x0400'0083:
|
||||
fifo[0].renable = data.bit(0);
|
||||
fifo[0].lenable = data.bit(1);
|
||||
fifo[0].timer = data.bit(2);
|
||||
if(data.bit(3)) fifo[0].reset();
|
||||
fifo[1].renable = data.bit(4);
|
||||
fifo[1].lenable = data.bit(5);
|
||||
fifo[1].timer = data.bit(6);
|
||||
if(data.bit(7)) fifo[1].reset();
|
||||
return;
|
||||
|
||||
//NR52
|
||||
case 0x04000084: return sequencer.write(2, byte);
|
||||
case 0x04000085: return;
|
||||
case 0x0400'0084: return sequencer.write(2, data);
|
||||
case 0x0400'0085: return;
|
||||
|
||||
//SOUNDBIAS
|
||||
case 0x04000088: regs.bias = (regs.bias & 0xff00) | (byte << 0); return;
|
||||
case 0x04000089: regs.bias = (regs.bias & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0088:
|
||||
regs.bias.level.bits(0,7) = data;
|
||||
return;
|
||||
case 0x0400'0089:
|
||||
regs.bias.level.bits(8,9) = data.bits(0,1);
|
||||
regs.bias.amplitude = data.bits(6,7);
|
||||
return;
|
||||
|
||||
//WAVE_RAM0_L
|
||||
case 0x04000090: return wave.writeram( 0, byte);
|
||||
case 0x04000091: return wave.writeram( 1, byte);
|
||||
case 0x0400'0090: return wave.writeram( 0, data);
|
||||
case 0x0400'0091: return wave.writeram( 1, data);
|
||||
|
||||
//WAVE_RAM0_H
|
||||
case 0x04000092: return wave.writeram( 2, byte);
|
||||
case 0x04000093: return wave.writeram( 3, byte);
|
||||
case 0x0400'0092: return wave.writeram( 2, data);
|
||||
case 0x0400'0093: return wave.writeram( 3, data);
|
||||
|
||||
//WAVE_RAM1_L
|
||||
case 0x04000094: return wave.writeram( 4, byte);
|
||||
case 0x04000095: return wave.writeram( 5, byte);
|
||||
case 0x0400'0094: return wave.writeram( 4, data);
|
||||
case 0x0400'0095: return wave.writeram( 5, data);
|
||||
|
||||
//WAVE_RAM1_H
|
||||
case 0x04000096: return wave.writeram( 6, byte);
|
||||
case 0x04000097: return wave.writeram( 7, byte);
|
||||
case 0x0400'0096: return wave.writeram( 6, data);
|
||||
case 0x0400'0097: return wave.writeram( 7, data);
|
||||
|
||||
//WAVE_RAM2_L
|
||||
case 0x04000098: return wave.writeram( 8, byte);
|
||||
case 0x04000099: return wave.writeram( 9, byte);
|
||||
case 0x0400'0098: return wave.writeram( 8, data);
|
||||
case 0x0400'0099: return wave.writeram( 9, data);
|
||||
|
||||
//WAVE_RAM2_H
|
||||
case 0x0400009a: return wave.writeram(10, byte);
|
||||
case 0x0400009b: return wave.writeram(11, byte);
|
||||
case 0x0400'009a: return wave.writeram(10, data);
|
||||
case 0x0400'009b: return wave.writeram(11, data);
|
||||
|
||||
//WAVE_RAM3_L
|
||||
case 0x0400009c: return wave.writeram(12, byte);
|
||||
case 0x0400009d: return wave.writeram(13, byte);
|
||||
case 0x0400'009c: return wave.writeram(12, data);
|
||||
case 0x0400'009d: return wave.writeram(13, data);
|
||||
|
||||
//WAVE_RAM3_H
|
||||
case 0x0400009e: return wave.writeram(14, byte);
|
||||
case 0x0400009f: return wave.writeram(15, byte);
|
||||
case 0x0400'009e: return wave.writeram(14, data);
|
||||
case 0x0400'009f: return wave.writeram(15, data);
|
||||
|
||||
//FIFO_A_L
|
||||
//FIFO_A_H
|
||||
case 0x040000a0: case 0x040000a1:
|
||||
case 0x040000a2: case 0x040000a3:
|
||||
return fifo[0].write(byte);
|
||||
case 0x0400'00a0: case 0x0400'00a1:
|
||||
case 0x0400'00a2: case 0x0400'00a3:
|
||||
return fifo[0].write(data);
|
||||
|
||||
//FIFO_B_L
|
||||
//FIFO_B_H
|
||||
case 0x040000a4: case 0x040000a5:
|
||||
case 0x040000a6: case 0x040000a7:
|
||||
return fifo[1].write(byte);
|
||||
case 0x0400'00a4: case 0x0400'00a5:
|
||||
case 0x0400'00a6: case 0x0400'00a7:
|
||||
return fifo[1].write(data);
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +0,0 @@
|
||||
APU::Registers::SoundBias::operator uint16() const {
|
||||
return (
|
||||
(level << 0)
|
||||
| (amplitude << 14)
|
||||
);
|
||||
}
|
||||
|
||||
auto APU::Registers::SoundBias::operator=(uint16 source) -> uint16 {
|
||||
level = (source >> 0) & 1023;
|
||||
amplitude = (source >> 14) & 3;
|
||||
return operator uint16();
|
||||
}
|
@@ -2,10 +2,6 @@ struct Registers {
|
||||
struct SoundBias {
|
||||
uint10 level;
|
||||
uint2 amplitude;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const SoundBias&) -> SoundBias& = delete;
|
||||
} bias;
|
||||
|
||||
uint clock;
|
||||
|
@@ -23,10 +23,6 @@ Cartridge::~Cartridge() {
|
||||
delete[] flash.data;
|
||||
}
|
||||
|
||||
auto Cartridge::loaded() const -> bool {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
auto Cartridge::sha256() const -> string {
|
||||
return information.sha256;
|
||||
}
|
||||
@@ -96,16 +92,10 @@ auto Cartridge::load() -> void {
|
||||
}
|
||||
|
||||
information.sha256 = Hash::SHA256(mrom.data, mrom.size).digest();
|
||||
|
||||
system.load();
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
if(isLoaded) {
|
||||
isLoaded = false;
|
||||
memory.reset();
|
||||
}
|
||||
memory.reset();
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
|
@@ -1,7 +1,6 @@
|
||||
struct Cartridge {
|
||||
#include "memory.hpp"
|
||||
|
||||
auto loaded() const -> bool;
|
||||
auto sha256() const -> string;
|
||||
auto manifest() const -> string;
|
||||
auto title() const -> string;
|
||||
@@ -31,7 +30,6 @@ struct Cartridge {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
private:
|
||||
bool isLoaded = false;
|
||||
bool hasSRAM = false;
|
||||
bool hasEEPROM = false;
|
||||
bool hasFLASH = false;
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "registers.cpp"
|
||||
#include "prefetch.cpp"
|
||||
#include "bus.cpp"
|
||||
#include "mmio.cpp"
|
||||
@@ -16,21 +15,21 @@ CPU::CPU() {
|
||||
iwram = new uint8[ 32 * 1024];
|
||||
ewram = new uint8[256 * 1024];
|
||||
|
||||
regs.dma[0].source.bits(27); regs.dma[0].run.source.bits(27);
|
||||
regs.dma[0].target.bits(27); regs.dma[0].run.target.bits(27);
|
||||
regs.dma[0].length.bits(14); regs.dma[0].run.length.bits(14);
|
||||
regs.dma[0].source.resize(27); regs.dma[0].run.source.resize(27);
|
||||
regs.dma[0].target.resize(27); regs.dma[0].run.target.resize(27);
|
||||
regs.dma[0].length.resize(14); regs.dma[0].run.length.resize(14);
|
||||
|
||||
regs.dma[1].source.bits(28); regs.dma[1].run.source.bits(28);
|
||||
regs.dma[1].target.bits(27); regs.dma[1].run.target.bits(27);
|
||||
regs.dma[1].length.bits(14); regs.dma[1].run.length.bits(14);
|
||||
regs.dma[1].source.resize(28); regs.dma[1].run.source.resize(28);
|
||||
regs.dma[1].target.resize(27); regs.dma[1].run.target.resize(27);
|
||||
regs.dma[1].length.resize(14); regs.dma[1].run.length.resize(14);
|
||||
|
||||
regs.dma[2].source.bits(28); regs.dma[2].run.source.bits(28);
|
||||
regs.dma[2].target.bits(27); regs.dma[2].run.target.bits(27);
|
||||
regs.dma[2].length.bits(14); regs.dma[2].run.length.bits(14);
|
||||
regs.dma[2].source.resize(28); regs.dma[2].run.source.resize(28);
|
||||
regs.dma[2].target.resize(27); regs.dma[2].run.target.resize(27);
|
||||
regs.dma[2].length.resize(14); regs.dma[2].run.length.resize(14);
|
||||
|
||||
regs.dma[3].source.bits(28); regs.dma[3].run.source.bits(28);
|
||||
regs.dma[3].target.bits(28); regs.dma[3].run.target.bits(28);
|
||||
regs.dma[3].length.bits(16); regs.dma[3].run.length.bits(16);
|
||||
regs.dma[3].source.resize(28); regs.dma[3].run.source.resize(28);
|
||||
regs.dma[3].target.resize(28); regs.dma[3].run.target.resize(28);
|
||||
regs.dma[3].length.resize(16); regs.dma[3].run.length.resize(16);
|
||||
}
|
||||
|
||||
CPU::~CPU() {
|
||||
@@ -39,14 +38,7 @@ CPU::~CPU() {
|
||||
}
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cpu.main();
|
||||
}
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
@@ -63,7 +55,7 @@ auto CPU::main() -> void {
|
||||
processor.irqline = regs.ime && (regs.irq.enable & regs.irq.flag);
|
||||
|
||||
if(regs.mode == Registers::Mode::Stop) {
|
||||
if((regs.irq.enable.keypad & regs.irq.flag.keypad) == 0) {
|
||||
if(!(regs.irq.enable & regs.irq.flag & Interrupt::Keypad)) {
|
||||
sync_step(16); //STOP does not advance timers
|
||||
} else {
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
@@ -74,7 +66,7 @@ auto CPU::main() -> void {
|
||||
dma_run();
|
||||
|
||||
if(regs.mode == Registers::Mode::Halt) {
|
||||
if((regs.irq.enable & regs.irq.flag) == 0) {
|
||||
if(!(regs.irq.enable & regs.irq.flag)) {
|
||||
step(16);
|
||||
} else {
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
@@ -108,7 +100,7 @@ auto CPU::keypad_run() -> void {
|
||||
if(regs.keypad.control.condition == 0) test |= input;
|
||||
if(regs.keypad.control.condition == 1) test &= input;
|
||||
}
|
||||
if(test) regs.irq.flag.keypad = true;
|
||||
if(test) regs.irq.flag |= Interrupt::Keypad;
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
@@ -123,7 +115,14 @@ auto CPU::power() -> void {
|
||||
dma.target = 0;
|
||||
dma.length = 0;
|
||||
dma.data = 0;
|
||||
dma.control = 0;
|
||||
dma.control.targetmode = 0;
|
||||
dma.control.sourcemode = 0;
|
||||
dma.control.repeat = 0;
|
||||
dma.control.size = 0;
|
||||
dma.control.drq = 0;
|
||||
dma.control.timingmode = 0;
|
||||
dma.control.irq = 0;
|
||||
dma.control.enable = 0;
|
||||
dma.pending = 0;
|
||||
dma.run.target = 0;
|
||||
dma.run.source = 0;
|
||||
@@ -133,17 +132,30 @@ auto CPU::power() -> void {
|
||||
timer.period = 0;
|
||||
timer.reload = 0;
|
||||
timer.pending = false;
|
||||
timer.control = 0;
|
||||
timer.control.frequency = 0;
|
||||
timer.control.cascade = 0;
|
||||
timer.control.irq = 0;
|
||||
timer.control.enable = 0;
|
||||
}
|
||||
regs.keypad.control = 0;
|
||||
for(auto& flag : regs.keypad.control.flag) flag = 0;
|
||||
regs.keypad.control.enable = 0;
|
||||
regs.keypad.control.condition = 0;
|
||||
regs.ime = 0;
|
||||
regs.irq.enable = 0;
|
||||
regs.irq.flag = 0;
|
||||
regs.wait.control = 0;
|
||||
for(auto& nwait : regs.wait.control.nwait) nwait = 0;
|
||||
for(auto& swait : regs.wait.control.swait) swait = 0;
|
||||
regs.wait.control.phi = 0;
|
||||
regs.wait.control.prefetch = 0;
|
||||
regs.wait.control.gametype = 0;
|
||||
regs.postboot = 0;
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
regs.clock = 0;
|
||||
regs.memory.control = 0x0d00'0020;
|
||||
regs.memory.control.disable = 0;
|
||||
regs.memory.control.unknown1 = 0;
|
||||
regs.memory.control.ewram = 1;
|
||||
regs.memory.control.ewramwait = 13;
|
||||
regs.memory.control.unknown2 = 0;
|
||||
|
||||
pending.dma.vblank = 0;
|
||||
pending.dma.hblank = 0;
|
||||
|
@@ -2,6 +2,25 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
||||
using ARM::read;
|
||||
using ARM::write;
|
||||
|
||||
struct Interrupt {
|
||||
enum : uint {
|
||||
VBlank = 0x0001,
|
||||
HBlank = 0x0002,
|
||||
VCoincidence = 0x0004,
|
||||
Timer0 = 0x0008,
|
||||
Timer1 = 0x0010,
|
||||
Timer2 = 0x0020,
|
||||
Timer3 = 0x0040,
|
||||
Serial = 0x0080,
|
||||
DMA0 = 0x0100,
|
||||
DMA1 = 0x0200,
|
||||
DMA2 = 0x0400,
|
||||
DMA3 = 0x0800,
|
||||
Keypad = 0x1000,
|
||||
Cartridge = 0x2000,
|
||||
};
|
||||
};
|
||||
|
||||
#include "registers.hpp"
|
||||
#include "prefetch.hpp"
|
||||
#include "state.hpp"
|
||||
@@ -11,7 +30,6 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
||||
~CPU();
|
||||
|
||||
static auto Enter() -> void;
|
||||
|
||||
auto main() -> void;
|
||||
|
||||
auto step(uint clocks) -> void override;
|
||||
|
@@ -7,8 +7,8 @@ auto CPU::dma_run() -> void {
|
||||
auto& dma = regs.dma[n];
|
||||
if(dma.pending) {
|
||||
dma_exec(dma);
|
||||
if(dma.control.irq) regs.irq.flag.dma[n] = 1;
|
||||
if(dma.control.drq && n == 3) regs.irq.flag.cartridge = 1;
|
||||
if(dma.control.irq) regs.irq.flag |= Interrupt::DMA0 << n;
|
||||
if(dma.control.drq && n == 3) regs.irq.flag |= Interrupt::Cartridge;
|
||||
transferred = true;
|
||||
break;
|
||||
}
|
||||
|
@@ -1,321 +1,415 @@
|
||||
auto CPU::read(uint32 addr) -> uint8 {
|
||||
uint8 result = 0;
|
||||
auto dma = [&]() -> Registers::DMA& { return regs.dma[addr / 12 & 3]; };
|
||||
auto timer = [&]() -> Registers::Timer& { return regs.timer[addr.bits(2,3)]; };
|
||||
|
||||
switch(addr) {
|
||||
|
||||
//DMA0CNT_H
|
||||
//DMA1CNT_H
|
||||
//DMA2CNT_H
|
||||
//DMA3CNT_H
|
||||
case 0x040000ba: case 0x040000bb:
|
||||
case 0x040000c6: case 0x040000c7:
|
||||
case 0x040000d2: case 0x040000d3:
|
||||
case 0x040000de: case 0x040000df: {
|
||||
auto& dma = regs.dma[(addr - 0x040000ba) / 12];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
return dma.control >> shift;
|
||||
}
|
||||
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
|
||||
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de: return (
|
||||
dma().control.targetmode << 5
|
||||
| dma().control.sourcemode.bit(0) << 7
|
||||
);
|
||||
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: return (
|
||||
dma().control.sourcemode.bit(1) << 0
|
||||
| dma().control.repeat << 1
|
||||
| dma().control.size << 2
|
||||
| dma().control.drq << 3
|
||||
| dma().control.timingmode << 4
|
||||
| dma().control.irq << 6
|
||||
| dma().control.enable << 7
|
||||
);
|
||||
|
||||
//TM0CNT_L
|
||||
//TM1CNT_L
|
||||
//TM2CNT_L
|
||||
//TM3CNT_L
|
||||
case 0x04000100: case 0x04000101:
|
||||
case 0x04000104: case 0x04000105:
|
||||
case 0x04000108: case 0x04000109:
|
||||
case 0x0400010c: case 0x0400010d: {
|
||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
return timer.period >> shift;
|
||||
}
|
||||
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
|
||||
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: return timer().period.byte(0);
|
||||
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: return timer().period.byte(1);
|
||||
|
||||
//TIM0CNT_H
|
||||
case 0x04000102: case 0x04000103:
|
||||
case 0x04000106: case 0x04000107:
|
||||
case 0x0400010a: case 0x0400010b:
|
||||
case 0x0400010e: case 0x0400010f: {
|
||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
return timer.control >> shift;
|
||||
}
|
||||
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
|
||||
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: return (
|
||||
timer().control.frequency << 0
|
||||
| timer().control.cascade << 2
|
||||
| timer().control.irq << 6
|
||||
| timer().control.enable << 7
|
||||
);
|
||||
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f: return 0;
|
||||
|
||||
//SIOMULTI0 (SIODATA32_L)
|
||||
//SIOMULTI1 (SIODATA32_H)
|
||||
//SIOMULTI2
|
||||
//SIOMULTI3
|
||||
case 0x04000120: case 0x04000121:
|
||||
case 0x04000122: case 0x04000123:
|
||||
case 0x04000124: case 0x04000125:
|
||||
case 0x04000126: case 0x04000127: {
|
||||
if(auto data = player.read()) return data() >> ((addr & 3) << 3);
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
return data >> shift;
|
||||
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
|
||||
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126: {
|
||||
if(auto data = player.read()) return data().byte(addr.bits(0,1));
|
||||
return regs.serial.data[addr.bits(1,2)].byte(0);
|
||||
}
|
||||
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127: {
|
||||
if(auto data = player.read()) return data().byte(addr.bits(0,1));
|
||||
return regs.serial.data[addr.bits(1,2)].byte(1);
|
||||
}
|
||||
|
||||
//SIOCNT
|
||||
case 0x04000128: return regs.serial.control >> 0;
|
||||
case 0x04000129: return regs.serial.control >> 8;
|
||||
case 0x0400'0128: return (
|
||||
regs.serial.control.shiftclockselect << 0
|
||||
| regs.serial.control.shiftclockfrequency << 1
|
||||
| regs.serial.control.transferenablereceive << 2
|
||||
| regs.serial.control.transferenablesend << 3
|
||||
| regs.serial.control.startbit << 7
|
||||
);
|
||||
case 0x0400'0129: return (
|
||||
regs.serial.control.transferlength << 4
|
||||
| regs.serial.control.irqenable << 6
|
||||
);
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
case 0x0400012a: return regs.serial.data8;
|
||||
case 0x0400012b: return 0u;
|
||||
case 0x0400'012a: return regs.serial.data8;
|
||||
case 0x0400'012b: return 0;
|
||||
|
||||
//KEYINPUT
|
||||
case 0x04000130:
|
||||
case 0x04000130: {
|
||||
if(auto result = player.keyinput()) return result() >> 0;
|
||||
for(unsigned n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, n) << n;
|
||||
if((result & 0xc0) == 0xc0) result &= ~0xc0; //up+down cannot be pressed simultaneously
|
||||
if((result & 0x30) == 0x30) result &= ~0x30; //left+right cannot be pressed simultaneously
|
||||
uint8 result = 0;
|
||||
for(uint n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, n) << n;
|
||||
if((result & 0xc0) == 0xc0) result &= (uint8)~0xc0; //up+down cannot be pressed simultaneously
|
||||
if((result & 0x30) == 0x30) result &= (uint8)~0x30; //left+right cannot be pressed simultaneously
|
||||
return result ^ 0xff;
|
||||
case 0x04000131:
|
||||
}
|
||||
case 0x04000131: {
|
||||
if(auto result = player.keyinput()) return result() >> 8;
|
||||
uint8 result = 0;
|
||||
result |= interface->inputPoll(0, 0, 8) << 0;
|
||||
result |= interface->inputPoll(0, 0, 9) << 1;
|
||||
return result ^ 0x03;
|
||||
}
|
||||
|
||||
//KEYCNT
|
||||
case 0x04000132: return regs.keypad.control >> 0;
|
||||
case 0x04000133: return regs.keypad.control >> 8;
|
||||
case 0x0400'0132: return (
|
||||
regs.keypad.control.flag[0] << 0
|
||||
| regs.keypad.control.flag[1] << 1
|
||||
| regs.keypad.control.flag[2] << 2
|
||||
| regs.keypad.control.flag[3] << 3
|
||||
| regs.keypad.control.flag[4] << 4
|
||||
| regs.keypad.control.flag[5] << 5
|
||||
| regs.keypad.control.flag[6] << 6
|
||||
| regs.keypad.control.flag[7] << 7
|
||||
);
|
||||
case 0x0400'0133: return (
|
||||
regs.keypad.control.flag[8] << 0
|
||||
| regs.keypad.control.flag[9] << 1
|
||||
| regs.keypad.control.enable << 6
|
||||
| regs.keypad.control.condition << 7
|
||||
);
|
||||
|
||||
//RCNT
|
||||
case 0x04000134: return regs.joybus.settings >> 0;
|
||||
case 0x04000135: return regs.joybus.settings >> 8;
|
||||
case 0x0400'0134: return (
|
||||
regs.joybus.settings.sc << 0
|
||||
| regs.joybus.settings.sd << 1
|
||||
| regs.joybus.settings.si << 2
|
||||
| regs.joybus.settings.so << 3
|
||||
| regs.joybus.settings.scmode << 4
|
||||
| regs.joybus.settings.sdmode << 5
|
||||
| regs.joybus.settings.simode << 6
|
||||
| regs.joybus.settings.somode << 7
|
||||
);
|
||||
case 0x0400'0135: return (
|
||||
regs.joybus.settings.irqenable << 0
|
||||
| regs.joybus.settings.mode << 6
|
||||
);
|
||||
|
||||
//JOYCNT
|
||||
case 0x04000140: return regs.joybus.control >> 0;
|
||||
case 0x04000141: return regs.joybus.control >> 8;
|
||||
case 0x0400'0140: return (
|
||||
regs.joybus.control.resetsignal << 0
|
||||
| regs.joybus.control.receivecomplete << 1
|
||||
| regs.joybus.control.sendcomplete << 2
|
||||
| regs.joybus.control.irqenable << 6
|
||||
);
|
||||
case 0x0400'0141: return 0;
|
||||
case 0x0400'0142: return 0;
|
||||
case 0x0400'0143: return 0;
|
||||
|
||||
//JOY_RECV_L
|
||||
//JOY_RECV_H
|
||||
case 0x04000150: return regs.joybus.receive >> 0;
|
||||
case 0x04000151: return regs.joybus.receive >> 8;
|
||||
case 0x04000152: return regs.joybus.receive >> 16;
|
||||
case 0x04000153: return regs.joybus.receive >> 24;
|
||||
//JOY_RECV_L, JOY_RECV_H
|
||||
case 0x0400'0150: return regs.joybus.receive.byte(0);
|
||||
case 0x0400'0151: return regs.joybus.receive.byte(1);
|
||||
case 0x0400'0152: return regs.joybus.receive.byte(2);
|
||||
case 0x0400'0153: return regs.joybus.receive.byte(3);
|
||||
|
||||
//JOY_TRANS_L
|
||||
//JOY_TRANS_H
|
||||
case 0x04000154: return regs.joybus.transmit >> 0;
|
||||
case 0x04000155: return regs.joybus.transmit >> 8;
|
||||
case 0x04000156: return regs.joybus.transmit >> 16;
|
||||
case 0x04000157: return regs.joybus.transmit >> 24;
|
||||
//JOY_TRANS_L, JOY_TRANS_H
|
||||
case 0x0400'0154: return regs.joybus.transmit.byte(0);
|
||||
case 0x0400'0155: return regs.joybus.transmit.byte(1);
|
||||
case 0x0400'0156: return regs.joybus.transmit.byte(2);
|
||||
case 0x0400'0157: return regs.joybus.transmit.byte(3);
|
||||
|
||||
//JOYSTAT
|
||||
case 0x04000158: return regs.joybus.status >> 0;
|
||||
case 0x04000159: return regs.joybus.status >> 8;
|
||||
case 0x0400'0158: return (
|
||||
regs.joybus.status.receiveflag << 1
|
||||
| regs.joybus.status.sendflag << 3
|
||||
| regs.joybus.status.generalflag << 4
|
||||
);
|
||||
case 0x0400'0159: return 0;
|
||||
case 0x0400'015a: return 0;
|
||||
case 0x0400'015b: return 0;
|
||||
|
||||
//IE
|
||||
case 0x04000200: return regs.irq.enable >> 0;
|
||||
case 0x04000201: return regs.irq.enable >> 8;
|
||||
case 0x0400'0200: return regs.irq.enable.byte(0);
|
||||
case 0x0400'0201: return regs.irq.enable.byte(1);
|
||||
|
||||
//IF
|
||||
case 0x04000202: return regs.irq.flag >> 0;
|
||||
case 0x04000203: return regs.irq.flag >> 8;
|
||||
case 0x0400'0202: return regs.irq.flag.byte(0);
|
||||
case 0x0400'0203: return regs.irq.flag.byte(1);
|
||||
|
||||
//WAITCNT
|
||||
case 0x04000204: return regs.wait.control >> 0;
|
||||
case 0x04000205: return regs.wait.control >> 8;
|
||||
case 0x0400'0204: return (
|
||||
regs.wait.control.nwait[3] << 0
|
||||
| regs.wait.control.nwait[0] << 2
|
||||
| regs.wait.control.swait[0] << 4
|
||||
| regs.wait.control.nwait[1] << 5
|
||||
| regs.wait.control.swait[1] << 7
|
||||
);
|
||||
case 0x0400'0205: return (
|
||||
regs.wait.control.nwait[2] << 0
|
||||
| regs.wait.control.swait[2] << 2
|
||||
| regs.wait.control.phi << 3
|
||||
| regs.wait.control.prefetch << 6
|
||||
| regs.wait.control.gametype << 7
|
||||
);
|
||||
|
||||
//IME
|
||||
case 0x04000208: return regs.ime;
|
||||
case 0x04000209: return 0u;
|
||||
case 0x0400'0208: return regs.ime;
|
||||
case 0x0400'0209: return 0;
|
||||
|
||||
//POSTFLG + HALTCNT
|
||||
case 0x04000300: return regs.postboot;
|
||||
case 0x04000301: return 0u;
|
||||
case 0x0400'0300: return regs.postboot;
|
||||
case 0x0400'0301: return 0;
|
||||
|
||||
//MEMCNT_L
|
||||
case 0x04000800: return regs.memory.control >> 0;
|
||||
case 0x04000801: return regs.memory.control >> 8;
|
||||
case 0x0400'0800: return (
|
||||
regs.memory.control.disable << 0
|
||||
| regs.memory.control.unknown1 << 1
|
||||
| regs.memory.control.ewram << 5
|
||||
);
|
||||
case 0x0400'0801: return 0;
|
||||
|
||||
//MEMCNT_H
|
||||
case 0x04000802: return regs.memory.control >> 16;
|
||||
case 0x04000803: return regs.memory.control >> 24;
|
||||
case 0x0400'0802: return 0;
|
||||
case 0x0400'0803: return (
|
||||
regs.memory.control.ewramwait << 0
|
||||
| regs.memory.control.unknown2 << 4
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return 0u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto CPU::write(uint32 addr, uint8 byte) -> void {
|
||||
auto CPU::write(uint32 addr, uint8 data) -> void {
|
||||
auto dma = [&]() -> Registers::DMA& { return regs.dma[addr / 12 & 3]; };
|
||||
auto timer = [&]() -> Registers::Timer& { return regs.timer[addr.bits(2,3)]; };
|
||||
|
||||
switch(addr) {
|
||||
|
||||
//DMA0SAD
|
||||
//DMA1SAD
|
||||
//DMA2SAD
|
||||
//DMA3SAD
|
||||
case 0x040000b0: case 0x040000b1: case 0x040000b2: case 0x040000b3:
|
||||
case 0x040000bc: case 0x040000bd: case 0x040000be: case 0x040000bf:
|
||||
case 0x040000c8: case 0x040000c9: case 0x040000ca: case 0x040000cb:
|
||||
case 0x040000d4: case 0x040000d5: case 0x040000d6: case 0x040000d7: {
|
||||
auto& dma = regs.dma[(addr - 0x040000b0) / 12];
|
||||
unsigned shift = (addr & 3) * 8;
|
||||
dma.source = (dma.source & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//DMA0SAD, DMA1SAD, DMA2SAD, DMA3SAD
|
||||
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: dma().source.byte(0) = data; return;
|
||||
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: dma().source.byte(1) = data; return;
|
||||
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: dma().source.byte(2) = data; return;
|
||||
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: dma().source.byte(3) = data; return;
|
||||
|
||||
//DMA0DAD
|
||||
//DMA1DAD
|
||||
//DMA2DAD
|
||||
//DMA3DAD
|
||||
case 0x040000b4: case 0x040000b5: case 0x040000b6: case 0x040000b7:
|
||||
case 0x040000c0: case 0x040000c1: case 0x040000c2: case 0x040000c3:
|
||||
case 0x040000cc: case 0x040000cd: case 0x040000ce: case 0x040000cf:
|
||||
case 0x040000d8: case 0x040000d9: case 0x040000da: case 0x040000db: {
|
||||
auto& dma = regs.dma[(addr - 0x040000b4) / 12];
|
||||
unsigned shift = (addr & 3) * 8;
|
||||
dma.target = (dma.target & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//DMA0DAD, DMA1DAD, DMA2DAD, DMA3DAD
|
||||
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: dma().target.byte(0) = data; return;
|
||||
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: dma().target.byte(1) = data; return;
|
||||
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: dma().target.byte(2) = data; return;
|
||||
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: dma().target.byte(3) = data; return;
|
||||
|
||||
//DMA0CNT_L
|
||||
//DMA1CNT_L
|
||||
//DMA2CNT_L
|
||||
//DMA3CNT_L
|
||||
case 0x040000b8: case 0x040000b9:
|
||||
case 0x040000c4: case 0x040000c5:
|
||||
case 0x040000d0: case 0x040000d1:
|
||||
case 0x040000dc: case 0x040000dd: {
|
||||
auto& dma = regs.dma[(addr - 0x040000b8) / 12];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
dma.length = (dma.length & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
|
||||
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: dma().length.byte(0) = data; return;
|
||||
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: dma().length.byte(1) = data; return;
|
||||
|
||||
//DMA0CNT_H
|
||||
//DMA1CNT_H
|
||||
//DMA2CNT_H
|
||||
//DMA3CNT_H
|
||||
case 0x040000ba: case 0x040000bb:
|
||||
case 0x040000c6: case 0x040000c7:
|
||||
case 0x040000d2: case 0x040000d3:
|
||||
case 0x040000de: case 0x040000df: {
|
||||
if(addr == 0x040000bb || addr == 0x040000c7 || addr == 0x040000d3) byte &= 0xf7; //gamepak DRQ valid for DMA3 only
|
||||
auto& dma = regs.dma[(addr - 0x040000ba) / 12];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
bool enable = dma.control.enable;
|
||||
dma.control = (dma.control & ~(255 << shift)) | (byte << shift);
|
||||
if(enable == 0 && dma.control.enable) {
|
||||
if(dma.control.timingmode == 0) dma.pending = true; //immediate transfer mode
|
||||
dma.run.target = dma.target;
|
||||
dma.run.source = dma.source;
|
||||
dma.run.length = dma.length;
|
||||
} else if(dma.control.enable == 0) {
|
||||
dma.pending = false;
|
||||
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
|
||||
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de:
|
||||
dma().control.targetmode = data.bits(5,6);
|
||||
dma().control.sourcemode.bit(0) = data.bit (7);
|
||||
return;
|
||||
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: {
|
||||
bool enable = dma().control.enable;
|
||||
if(addr != 0x0400'00df) data.bit(3) = 0; //gamepad DRQ valid for DMA3 only
|
||||
|
||||
dma().control.sourcemode.bit(1) = data.bit (0);
|
||||
dma().control.repeat = data.bit (1);
|
||||
dma().control.size = data.bit (2);
|
||||
dma().control.drq = data.bit (3);
|
||||
dma().control.timingmode = data.bits(4,5);
|
||||
dma().control.irq = data.bit (6);
|
||||
dma().control.enable = data.bit (7);
|
||||
|
||||
if(!enable && dma().control.enable) { //0->1 transition
|
||||
if(dma().control.timingmode == 0) dma().pending = true; //immediate transfer mode
|
||||
dma().run.target = dma().target;
|
||||
dma().run.source = dma().source;
|
||||
dma().run.length = dma().length;
|
||||
} else if(!dma().control.enable) {
|
||||
dma().pending = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//TM0CNT_L
|
||||
//TM1CNT_L
|
||||
//TM2CNT_L
|
||||
//TM3CNT_L
|
||||
case 0x04000100: case 0x04000101:
|
||||
case 0x04000104: case 0x04000105:
|
||||
case 0x04000108: case 0x04000109:
|
||||
case 0x0400010c: case 0x0400010d: {
|
||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
timer.reload = (timer.reload & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
|
||||
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: timer().reload.byte(0) = data; return;
|
||||
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: timer().reload.byte(1) = data; return;
|
||||
|
||||
//TM0CNT_H
|
||||
//TM1CNT_H
|
||||
//TM2CNT_H
|
||||
//TM3CNT_H
|
||||
case 0x04000102:
|
||||
case 0x04000106:
|
||||
case 0x0400010a:
|
||||
case 0x0400010e: {
|
||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
||||
bool enable = timer.control.enable;
|
||||
timer.control = byte;
|
||||
if(enable == 0 && timer.control.enable == 1) {
|
||||
timer.pending = true;
|
||||
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
|
||||
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: {
|
||||
bool enable = timer().control.enable;
|
||||
|
||||
timer().control.frequency = data.bits(0,1);
|
||||
timer().control.cascade = data.bit (2);
|
||||
timer().control.irq = data.bit (6);
|
||||
timer().control.enable = data.bit (7);
|
||||
|
||||
if(!enable && timer().control.enable) { //0->1 transition
|
||||
timer().pending = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//SIOMULTI0 (SIODATA32_L)
|
||||
//SIOMULTI1 (SIODATA32_H)
|
||||
//SIOMULTI2
|
||||
//SIOMULTI3
|
||||
case 0x04000120: case 0x04000121:
|
||||
case 0x04000122: case 0x04000123:
|
||||
case 0x04000124: case 0x04000125:
|
||||
case 0x04000126: case 0x04000127: {
|
||||
player.write(byte, addr & 3);
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
data = (data & ~(255 << shift)) | (byte << shift);
|
||||
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f:
|
||||
return;
|
||||
|
||||
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
|
||||
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126:
|
||||
player.write(addr.bits(0,1), data);
|
||||
regs.serial.data[addr.bits(1,2)].byte(0) = data;
|
||||
return;
|
||||
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127:
|
||||
player.write(addr.bits(0,1), data);
|
||||
regs.serial.data[addr.bits(1,2)].byte(1) = data;
|
||||
return;
|
||||
}
|
||||
|
||||
//SIOCNT
|
||||
case 0x04000128: regs.serial.control = (regs.serial.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000129: regs.serial.control = (regs.serial.control & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0128:
|
||||
regs.serial.control.shiftclockselect = data.bit(0);
|
||||
regs.serial.control.shiftclockfrequency = data.bit(1);
|
||||
regs.serial.control.transferenablereceive = data.bit(2);
|
||||
regs.serial.control.transferenablesend = data.bit(3);
|
||||
regs.serial.control.startbit = data.bit(7);
|
||||
return;
|
||||
case 0x0400'0129:
|
||||
regs.serial.control.transferlength = data.bit(4);
|
||||
regs.serial.control.irqenable = data.bit(6);
|
||||
return;
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
case 0x0400012a: regs.serial.data8 = byte; return;
|
||||
case 0x0400012b: return;
|
||||
case 0x0400'012a: regs.serial.data8 = data; return;
|
||||
case 0x0400'012b: return;
|
||||
|
||||
//KEYCNT
|
||||
case 0x04000132: regs.keypad.control = (regs.keypad.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000133: regs.keypad.control = (regs.keypad.control & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0132:
|
||||
regs.keypad.control.flag[0] = data.bit(0);
|
||||
regs.keypad.control.flag[1] = data.bit(1);
|
||||
regs.keypad.control.flag[2] = data.bit(2);
|
||||
regs.keypad.control.flag[3] = data.bit(3);
|
||||
regs.keypad.control.flag[4] = data.bit(4);
|
||||
regs.keypad.control.flag[5] = data.bit(5);
|
||||
regs.keypad.control.flag[6] = data.bit(6);
|
||||
regs.keypad.control.flag[7] = data.bit(7);
|
||||
return;
|
||||
case 0x0400'0133:
|
||||
regs.keypad.control.flag[8] = data.bit(0);
|
||||
regs.keypad.control.flag[9] = data.bit(1);
|
||||
regs.keypad.control.enable = data.bit(6);
|
||||
regs.keypad.control.condition = data.bit(7);
|
||||
return;
|
||||
|
||||
//RCNT
|
||||
case 0x04000134: regs.joybus.settings = (regs.joybus.settings & 0xff00) | (byte << 0); return;
|
||||
case 0x04000135: regs.joybus.settings = (regs.joybus.settings & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0134:
|
||||
regs.joybus.settings.sc = data.bit(0);
|
||||
regs.joybus.settings.sd = data.bit(1);
|
||||
regs.joybus.settings.si = data.bit(2);
|
||||
regs.joybus.settings.so = data.bit(3);
|
||||
regs.joybus.settings.scmode = data.bit(4);
|
||||
regs.joybus.settings.sdmode = data.bit(5);
|
||||
regs.joybus.settings.simode = data.bit(6);
|
||||
regs.joybus.settings.somode = data.bit(7);
|
||||
return;
|
||||
case 0x0400'0135:
|
||||
regs.joybus.settings.irqenable = data.bit (0);
|
||||
regs.joybus.settings.mode = data.bits(6,7);
|
||||
return;
|
||||
|
||||
//JOYCNT
|
||||
case 0x04000140: regs.joybus.control = (regs.joybus.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000141: regs.joybus.control = (regs.joybus.control & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0140:
|
||||
regs.joybus.control.resetsignal = data.bit(0);
|
||||
regs.joybus.control.receivecomplete = data.bit(1);
|
||||
regs.joybus.control.sendcomplete = data.bit(2);
|
||||
regs.joybus.control.irqenable = data.bit(6);
|
||||
return;
|
||||
case 0x0400'0141: return;
|
||||
case 0x0400'0142: return;
|
||||
case 0x0400'0143: return;
|
||||
|
||||
//JOY_RECV_L
|
||||
//JOY_RECV_H
|
||||
case 0x04000150: regs.joybus.receive = (regs.joybus.receive & 0xffffff00) | (byte << 0); return;
|
||||
case 0x04000151: regs.joybus.receive = (regs.joybus.receive & 0xffff00ff) | (byte << 8); return;
|
||||
case 0x04000152: regs.joybus.receive = (regs.joybus.receive & 0xff00ffff) | (byte << 16); return;
|
||||
case 0x04000153: regs.joybus.receive = (regs.joybus.receive & 0x00ffffff) | (byte << 24); return;
|
||||
case 0x0400'0150: regs.joybus.receive.byte(0) = data; return;
|
||||
case 0x0400'0151: regs.joybus.receive.byte(1) = data; return;
|
||||
case 0x0400'0152: regs.joybus.receive.byte(2) = data; return;
|
||||
case 0x0400'0153: regs.joybus.receive.byte(3) = data; return;
|
||||
|
||||
//JOY_TRANS_L
|
||||
//JOY_TRANS_H
|
||||
case 0x04000154: regs.joybus.transmit = (regs.joybus.transmit & 0xffffff00) | (byte << 0); return;
|
||||
case 0x04000155: regs.joybus.transmit = (regs.joybus.transmit & 0xffff00ff) | (byte << 8); return;
|
||||
case 0x04000156: regs.joybus.transmit = (regs.joybus.transmit & 0xff00ffff) | (byte << 16); return;
|
||||
case 0x04000157: regs.joybus.transmit = (regs.joybus.transmit & 0x00ffffff) | (byte << 24); return;
|
||||
case 0x0400'0154: regs.joybus.transmit.byte(0) = data; return;
|
||||
case 0x0400'0155: regs.joybus.transmit.byte(1) = data; return;
|
||||
case 0x0400'0156: regs.joybus.transmit.byte(2) = data; return;
|
||||
case 0x0400'0157: regs.joybus.transmit.byte(3) = data; return;
|
||||
|
||||
//JOYSTAT
|
||||
case 0x04000158: regs.joybus.status = (regs.joybus.status & 0xff00) | (byte << 0); return;
|
||||
case 0x04000159: regs.joybus.status = (regs.joybus.status & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0158:
|
||||
regs.joybus.status.receiveflag = data.bit (1);
|
||||
regs.joybus.status.sendflag = data.bit (3);
|
||||
regs.joybus.status.generalflag = data.bits(4,5);
|
||||
return;
|
||||
case 0x0400'0159: return;
|
||||
|
||||
//IE
|
||||
case 0x04000200: regs.irq.enable = (regs.irq.enable & 0xff00) | (byte << 0); return;
|
||||
case 0x04000201: regs.irq.enable = (regs.irq.enable & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0200: regs.irq.enable.byte(0) = data; return;
|
||||
case 0x0400'0201: regs.irq.enable.byte(1) = data; return;
|
||||
|
||||
//IF
|
||||
case 0x04000202: regs.irq.flag = regs.irq.flag & ~(byte << 0); return;
|
||||
case 0x04000203: regs.irq.flag = regs.irq.flag & ~(byte << 8); return;
|
||||
case 0x0400'0202: regs.irq.flag.byte(0) = regs.irq.flag.byte(0) & ~data; return;
|
||||
case 0x0400'0203: regs.irq.flag.byte(1) = regs.irq.flag.byte(1) & ~data; return;
|
||||
|
||||
//WAITCNT
|
||||
case 0x04000204: regs.wait.control = (regs.wait.control & 0xff00) | ((byte & 0xff) << 0); return;
|
||||
case 0x04000205: regs.wait.control = (regs.wait.control & 0x00ff) | ((byte & 0x7f) << 8); return;
|
||||
case 0x0400'0204:
|
||||
regs.wait.control.swait[3] = data.bit (0); //todo: is this correct?
|
||||
regs.wait.control.nwait[3] = data.bits(0,1);
|
||||
regs.wait.control.nwait[0] = data.bits(2,3);
|
||||
regs.wait.control.swait[0] = data.bit (4);
|
||||
regs.wait.control.nwait[1] = data.bits(5,6);
|
||||
regs.wait.control.swait[1] = data.bit (7);
|
||||
return;
|
||||
case 0x0400'0205:
|
||||
regs.wait.control.nwait[2] = data.bits(0,1);
|
||||
regs.wait.control.swait[2] = data.bit (2);
|
||||
regs.wait.control.phi = data.bit (3);
|
||||
regs.wait.control.prefetch = data.bit (6);
|
||||
regs.wait.control.gametype = data.bit (7);
|
||||
return;
|
||||
|
||||
//IME
|
||||
case 0x04000208: regs.ime = byte >> 0; return;
|
||||
case 0x04000209: return;
|
||||
case 0x0400'0208: regs.ime = data.bit(0); return;
|
||||
case 0x0400'0209: return;
|
||||
|
||||
//POSTFLG, HALTCNT
|
||||
case 0x04000300: regs.postboot |= byte >> 0; return;
|
||||
case 0x04000301: regs.mode = byte & 0x80 ? Registers::Mode::Stop : Registers::Mode::Halt; return;
|
||||
case 0x0400'0300:
|
||||
if(data.bit(0)) regs.postboot = 1;
|
||||
return;
|
||||
case 0x0400'0301:
|
||||
regs.mode = data.bit(7) ? Registers::Mode::Stop : Registers::Mode::Halt;
|
||||
return;
|
||||
|
||||
//MEMCNT_L
|
||||
//MEMCNT_H
|
||||
case 0x04000800: regs.memory.control = (regs.memory.control & 0xffffff00) | (byte << 0); return;
|
||||
case 0x04000801: regs.memory.control = (regs.memory.control & 0xffff00ff) | (byte << 8); return;
|
||||
case 0x04000802: regs.memory.control = (regs.memory.control & 0xff00ffff) | (byte << 16); return;
|
||||
case 0x04000803: regs.memory.control = (regs.memory.control & 0x00ffffff) | (byte << 24); return;
|
||||
case 0x0400'0800:
|
||||
regs.memory.control.disable = data.bit (0);
|
||||
regs.memory.control.unknown1 = data.bits(1,3);
|
||||
regs.memory.control.ewram = data.bit (5);
|
||||
return;
|
||||
case 0x0400'0801: return;
|
||||
case 0x0400'0802: return;
|
||||
case 0x0400'0803:
|
||||
regs.memory.control.ewramwait = data.bits(0,3);
|
||||
regs.memory.control.unknown2 = data.bits(4,7);
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,244 +0,0 @@
|
||||
CPU::Registers::DMAControl::operator uint16() const {
|
||||
return (
|
||||
(targetmode << 5)
|
||||
| (sourcemode << 7)
|
||||
| (repeat << 9)
|
||||
| (size << 10)
|
||||
| (drq << 11)
|
||||
| (timingmode << 12)
|
||||
| (irq << 14)
|
||||
| (enable << 15)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::DMAControl::operator=(uint16 source) -> uint16 {
|
||||
targetmode = source >> 5;
|
||||
sourcemode = source >> 7;
|
||||
repeat = source >> 9;
|
||||
size = source >> 10;
|
||||
drq = source >> 11;
|
||||
timingmode = source >> 12;
|
||||
irq = source >> 14;
|
||||
enable = source >> 15;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::TimerControl::operator uint16() const {
|
||||
return (
|
||||
(frequency << 0)
|
||||
| (cascade << 2)
|
||||
| (irq << 6)
|
||||
| (enable << 7)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::TimerControl::operator=(uint16 source) -> uint16 {
|
||||
frequency = source >> 0;
|
||||
cascade = source >> 2;
|
||||
irq = source >> 6;
|
||||
enable = source >> 7;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::SerialControl::operator uint16() const {
|
||||
return (
|
||||
(shiftclockselect << 0)
|
||||
| (shiftclockfrequency << 1)
|
||||
| (transferenablereceive << 2)
|
||||
| (transferenablesend << 3)
|
||||
| (startbit << 7)
|
||||
| (transferlength << 12)
|
||||
| (irqenable << 14)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::SerialControl::operator=(uint16 source) -> uint16 {
|
||||
shiftclockselect = source >> 0;
|
||||
shiftclockfrequency = source >> 1;
|
||||
transferenablereceive = source >> 2;
|
||||
transferenablesend = source >> 3;
|
||||
startbit = source >> 7;
|
||||
transferlength = source >> 12;
|
||||
irqenable = source >> 14;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::KeypadControl::operator uint16() const {
|
||||
return (
|
||||
(flag[0] << 0)
|
||||
| (flag[1] << 1)
|
||||
| (flag[2] << 2)
|
||||
| (flag[3] << 3)
|
||||
| (flag[4] << 4)
|
||||
| (flag[5] << 5)
|
||||
| (flag[6] << 6)
|
||||
| (flag[7] << 7)
|
||||
| (flag[8] << 8)
|
||||
| (flag[9] << 9)
|
||||
| (enable << 14)
|
||||
| (condition << 15)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::KeypadControl::operator=(uint16 source) -> uint16 {
|
||||
flag[0] = source >> 0;
|
||||
flag[1] = source >> 1;
|
||||
flag[2] = source >> 2;
|
||||
flag[3] = source >> 3;
|
||||
flag[4] = source >> 4;
|
||||
flag[5] = source >> 5;
|
||||
flag[6] = source >> 6;
|
||||
flag[7] = source >> 7;
|
||||
flag[8] = source >> 8;
|
||||
flag[9] = source >> 9;
|
||||
enable = source >> 14;
|
||||
condition = source >> 15;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::JoybusSettings::operator uint16() const {
|
||||
return (
|
||||
(sc << 0)
|
||||
| (sd << 1)
|
||||
| (si << 2)
|
||||
| (so << 3)
|
||||
| (scmode << 4)
|
||||
| (sdmode << 5)
|
||||
| (simode << 6)
|
||||
| (somode << 7)
|
||||
| (irqenable << 8)
|
||||
| (mode << 14)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::JoybusSettings::operator=(uint16 source) -> uint16 {
|
||||
sc = source >> 0;
|
||||
sd = source >> 1;
|
||||
si = source >> 2;
|
||||
so = source >> 3;
|
||||
scmode = source >> 4;
|
||||
sdmode = source >> 5;
|
||||
simode = source >> 6;
|
||||
somode = source >> 7;
|
||||
irqenable = source >> 8;
|
||||
mode = source >> 14;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::JoybusControl::operator uint16() const {
|
||||
return (
|
||||
(resetsignal << 0)
|
||||
| (receivecomplete << 1)
|
||||
| (sendcomplete << 2)
|
||||
| (irqenable << 6)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::JoybusControl::operator=(uint16 source) -> uint16 {
|
||||
resetsignal = source >> 0;
|
||||
receivecomplete = source >> 1;
|
||||
sendcomplete = source >> 2;
|
||||
irqenable = source >> 6;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::JoybusStatus::operator uint16() const {
|
||||
return (
|
||||
(receiveflag << 1)
|
||||
| (sendflag << 3)
|
||||
| (generalflag << 4)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::JoybusStatus::operator=(uint16 source) -> uint16 {
|
||||
receiveflag = source >> 1;
|
||||
sendflag = source >> 3;
|
||||
generalflag = source >> 4;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::Interrupt::operator uint16() const {
|
||||
return (
|
||||
(vblank << 0)
|
||||
| (hblank << 1)
|
||||
| (vcoincidence << 2)
|
||||
| (timer[0] << 3)
|
||||
| (timer[1] << 4)
|
||||
| (timer[2] << 5)
|
||||
| (timer[3] << 6)
|
||||
| (serial << 7)
|
||||
| (dma[0] << 8)
|
||||
| (dma[1] << 9)
|
||||
| (dma[2] << 10)
|
||||
| (dma[3] << 11)
|
||||
| (keypad << 12)
|
||||
| (cartridge << 13)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::Interrupt::operator=(uint16 source) -> uint16 {
|
||||
vblank = source >> 0;
|
||||
hblank = source >> 1;
|
||||
vcoincidence = source >> 2;
|
||||
timer[0] = source >> 3;
|
||||
timer[1] = source >> 4;
|
||||
timer[2] = source >> 5;
|
||||
timer[3] = source >> 6;
|
||||
serial = source >> 7;
|
||||
dma[0] = source >> 8;
|
||||
dma[1] = source >> 9;
|
||||
dma[2] = source >> 10;
|
||||
dma[3] = source >> 11;
|
||||
keypad = source >> 12;
|
||||
cartridge = source >> 13;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::WaitControl::operator uint16() const {
|
||||
return (
|
||||
(nwait[3] << 0)
|
||||
| (nwait[0] << 2)
|
||||
| (swait[0] << 4)
|
||||
| (nwait[1] << 5)
|
||||
| (swait[1] << 7)
|
||||
| (nwait[2] << 8)
|
||||
| (swait[2] << 10)
|
||||
| (phi << 11)
|
||||
| (prefetch << 14)
|
||||
| (gametype << 15)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::WaitControl::operator=(uint16 source) -> uint16 {
|
||||
nwait[3] = (source >> 0) & 3;
|
||||
nwait[0] = (source >> 2) & 3;
|
||||
swait[0] = (source >> 4) & 1;
|
||||
nwait[1] = (source >> 5) & 3;
|
||||
swait[1] = (source >> 7) & 1;
|
||||
nwait[2] = (source >> 8) & 3;
|
||||
swait[2] = (source >> 10) & 1;
|
||||
phi = (source >> 11) & 3;
|
||||
prefetch = (source >> 14) & 1;
|
||||
gametype = (source >> 15) & 1;
|
||||
swait[3] = nwait[3];
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::MemoryControl::operator uint32() const {
|
||||
return (
|
||||
(disable << 0)
|
||||
| (unknown1 << 1)
|
||||
| (ewram << 5)
|
||||
| (ewramwait << 24)
|
||||
| (unknown2 << 28)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::MemoryControl::operator=(uint32 source) -> uint32 {
|
||||
disable = source >> 0;
|
||||
unknown1 = source >> 1;
|
||||
ewram = source >> 5;
|
||||
ewramwait = source >> 24;
|
||||
unknown2 = source >> 28;
|
||||
return operator uint32();
|
||||
}
|
@@ -1,185 +1,116 @@
|
||||
struct Registers {
|
||||
struct DMAControl {
|
||||
uint2 targetmode;
|
||||
uint2 sourcemode;
|
||||
uint1 repeat;
|
||||
uint1 size;
|
||||
uint1 drq;
|
||||
uint2 timingmode;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const DMAControl&) -> DMAControl& = delete;
|
||||
};
|
||||
|
||||
struct DMA {
|
||||
varuint source;
|
||||
varuint target;
|
||||
varuint length;
|
||||
VariadicNatural source;
|
||||
VariadicNatural target;
|
||||
VariadicNatural length;
|
||||
uint32 data;
|
||||
DMAControl control;
|
||||
struct Control {
|
||||
uint2 targetmode;
|
||||
uint2 sourcemode;
|
||||
uint1 repeat;
|
||||
uint1 size;
|
||||
uint1 drq;
|
||||
uint2 timingmode;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
} control;
|
||||
|
||||
//internal
|
||||
bool pending;
|
||||
struct Run {
|
||||
varuint target;
|
||||
varuint source;
|
||||
varuint length;
|
||||
VariadicNatural target;
|
||||
VariadicNatural source;
|
||||
VariadicNatural length;
|
||||
} run;
|
||||
} dma[4];
|
||||
|
||||
struct TimerControl {
|
||||
uint2 frequency;
|
||||
uint1 cascade;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const TimerControl&) -> TimerControl& = delete;
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
uint16 period;
|
||||
uint16 reload;
|
||||
bool pending;
|
||||
TimerControl control;
|
||||
struct Control {
|
||||
uint2 frequency;
|
||||
uint1 cascade;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
} control;
|
||||
} timer[4];
|
||||
|
||||
struct SerialControl {
|
||||
uint1 shiftclockselect;
|
||||
uint1 shiftclockfrequency;
|
||||
uint1 transferenablereceive;
|
||||
uint1 transferenablesend;
|
||||
uint1 startbit;
|
||||
uint1 transferlength;
|
||||
uint1 irqenable;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const SerialControl&) -> SerialControl& = delete;
|
||||
};
|
||||
|
||||
struct Serial {
|
||||
uint16 data[4];
|
||||
SerialControl control;
|
||||
struct Control {
|
||||
uint1 shiftclockselect;
|
||||
uint1 shiftclockfrequency;
|
||||
uint1 transferenablereceive;
|
||||
uint1 transferenablesend;
|
||||
uint1 startbit;
|
||||
uint1 transferlength;
|
||||
uint1 irqenable;
|
||||
} control;
|
||||
uint8 data8;
|
||||
} serial;
|
||||
|
||||
struct KeypadControl {
|
||||
uint1 flag[10];
|
||||
uint1 enable;
|
||||
uint1 condition;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const KeypadControl&) -> KeypadControl& = delete;
|
||||
};
|
||||
|
||||
struct Keypad {
|
||||
KeypadControl control;
|
||||
struct Control {
|
||||
uint1 flag[10];
|
||||
uint1 enable;
|
||||
uint1 condition;
|
||||
} control;
|
||||
} keypad;
|
||||
|
||||
struct JoybusSettings {
|
||||
uint1 sc;
|
||||
uint1 sd;
|
||||
uint1 si;
|
||||
uint1 so;
|
||||
uint1 scmode;
|
||||
uint1 sdmode;
|
||||
uint1 simode;
|
||||
uint1 somode;
|
||||
uint1 irqenable;
|
||||
uint2 mode;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const JoybusSettings&) -> JoybusSettings& = delete;
|
||||
};
|
||||
|
||||
struct JoybusControl {
|
||||
uint1 resetsignal;
|
||||
uint1 receivecomplete;
|
||||
uint1 sendcomplete;
|
||||
uint1 irqenable;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const JoybusControl&) -> JoybusControl& = delete;
|
||||
};
|
||||
|
||||
struct JoybusStatus {
|
||||
uint1 receiveflag;
|
||||
uint1 sendflag;
|
||||
uint2 generalflag;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const JoybusStatus&) -> JoybusStatus& = delete;
|
||||
};
|
||||
|
||||
struct Joybus {
|
||||
JoybusSettings settings;
|
||||
JoybusControl control;
|
||||
struct Settings {
|
||||
uint1 sc;
|
||||
uint1 sd;
|
||||
uint1 si;
|
||||
uint1 so;
|
||||
uint1 scmode;
|
||||
uint1 sdmode;
|
||||
uint1 simode;
|
||||
uint1 somode;
|
||||
uint1 irqenable;
|
||||
uint2 mode;
|
||||
} settings;
|
||||
struct Control {
|
||||
uint1 resetsignal;
|
||||
uint1 receivecomplete;
|
||||
uint1 sendcomplete;
|
||||
uint1 irqenable;
|
||||
} control;
|
||||
uint32 receive;
|
||||
uint32 transmit;
|
||||
JoybusStatus status;
|
||||
struct Status {
|
||||
uint1 receiveflag;
|
||||
uint1 sendflag;
|
||||
uint2 generalflag;
|
||||
} status;
|
||||
} joybus;
|
||||
|
||||
uint1 ime;
|
||||
|
||||
struct Interrupt {
|
||||
uint1 vblank;
|
||||
uint1 hblank;
|
||||
uint1 vcoincidence;
|
||||
uint1 timer[4];
|
||||
uint1 serial;
|
||||
uint1 dma[4];
|
||||
uint1 keypad;
|
||||
uint1 cartridge;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const Interrupt&) -> Interrupt& = delete;
|
||||
};
|
||||
|
||||
struct IRQ {
|
||||
Interrupt enable;
|
||||
Interrupt flag;
|
||||
uint16 enable;
|
||||
uint16 flag;
|
||||
} irq;
|
||||
|
||||
struct WaitControl {
|
||||
uint2 nwait[4];
|
||||
uint1 swait[4];
|
||||
uint2 phi;
|
||||
uint1 prefetch;
|
||||
uint1 gametype;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const WaitControl&) -> WaitControl& = delete;
|
||||
};
|
||||
|
||||
struct Wait {
|
||||
WaitControl control;
|
||||
struct Control {
|
||||
uint2 nwait[4];
|
||||
uint1 swait[4];
|
||||
uint2 phi;
|
||||
uint1 prefetch;
|
||||
uint1 gametype;
|
||||
} control;
|
||||
} wait;
|
||||
|
||||
struct MemoryControl {
|
||||
uint1 disable;
|
||||
uint3 unknown1;
|
||||
uint1 ewram;
|
||||
uint4 ewramwait;
|
||||
uint4 unknown2;
|
||||
|
||||
operator uint32() const;
|
||||
auto operator=(uint32 source) -> uint32;
|
||||
auto operator=(const MemoryControl&) -> MemoryControl& = delete;
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
MemoryControl control;
|
||||
struct Control {
|
||||
uint1 disable;
|
||||
uint3 unknown1;
|
||||
uint1 ewram;
|
||||
uint4 ewramwait;
|
||||
uint4 unknown2;
|
||||
} control;
|
||||
} memory;
|
||||
|
||||
uint1 postboot;
|
||||
|
@@ -72,23 +72,8 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
|
||||
s.integer(regs.ime);
|
||||
|
||||
s.integer(regs.irq.enable.vblank);
|
||||
s.integer(regs.irq.enable.hblank);
|
||||
s.integer(regs.irq.enable.vcoincidence);
|
||||
for(auto& flag : regs.irq.enable.timer) s.integer(flag);
|
||||
s.integer(regs.irq.enable.serial);
|
||||
for(auto& flag : regs.irq.enable.dma) s.integer(flag);
|
||||
s.integer(regs.irq.enable.keypad);
|
||||
s.integer(regs.irq.enable.cartridge);
|
||||
|
||||
s.integer(regs.irq.flag.vblank);
|
||||
s.integer(regs.irq.flag.hblank);
|
||||
s.integer(regs.irq.flag.vcoincidence);
|
||||
for(auto& flag : regs.irq.flag.timer) s.integer(flag);
|
||||
s.integer(regs.irq.flag.serial);
|
||||
for(auto& flag : regs.irq.flag.dma) s.integer(flag);
|
||||
s.integer(regs.irq.flag.keypad);
|
||||
s.integer(regs.irq.flag.cartridge);
|
||||
s.integer(regs.irq.enable);
|
||||
s.integer(regs.irq.flag);
|
||||
|
||||
for(auto& flag : regs.wait.control.nwait) s.integer(flag);
|
||||
for(auto& flag : regs.wait.control.swait) s.integer(flag);
|
||||
|
@@ -28,7 +28,7 @@ auto CPU::timer_increment(uint n) -> void {
|
||||
if(++timer.period == 0) {
|
||||
timer.period = timer.reload;
|
||||
|
||||
if(timer.control.irq) regs.irq.flag.timer[n] = 1;
|
||||
if(timer.control.irq) regs.irq.flag |= Interrupt::Timer0 << n;
|
||||
|
||||
if(apu.fifo[0].timer == n) timer_fifo_run(0);
|
||||
if(apu.fifo[1].timer == n) timer_fifo_run(1);
|
||||
|
@@ -62,7 +62,6 @@ namespace GameBoyAdvance {
|
||||
#include <gba/cpu/cpu.hpp>
|
||||
#include <gba/ppu/ppu.hpp>
|
||||
#include <gba/apu/apu.hpp>
|
||||
#include <gba/video/video.hpp>
|
||||
}
|
||||
|
||||
#include <gba/interface/interface.hpp>
|
||||
|
@@ -8,12 +8,14 @@ Settings settings;
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.name = "Game Boy Advance";
|
||||
information.width = 240;
|
||||
information.height = 160;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Game Boy Advance";
|
||||
information.width = 240;
|
||||
information.height = 160;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = false;
|
||||
|
||||
@@ -55,7 +57,7 @@ auto Interface::audioFrequency() -> double {
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return cartridge.loaded();
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::group(uint id) -> uint {
|
||||
@@ -75,7 +77,7 @@ auto Interface::group(uint id) -> uint {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> void {
|
||||
cartridge.load();
|
||||
system.load();
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
@@ -90,7 +92,7 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
||||
}
|
||||
|
||||
if(id == ID::BIOS) {
|
||||
stream.read(bios.data, min(bios.size, stream.size()));
|
||||
stream.read((uint8_t*)bios.data, min(bios.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::Manifest) {
|
||||
@@ -98,39 +100,39 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
||||
}
|
||||
|
||||
if(id == ID::MROM) {
|
||||
stream.read(cartridge.mrom.data, min(cartridge.mrom.size, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.mrom.data, min(cartridge.mrom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::SRAM) {
|
||||
stream.read(cartridge.sram.data, min(cartridge.sram.size, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.sram.data, min(cartridge.sram.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::EEPROM) {
|
||||
stream.read(cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::FLASH) {
|
||||
stream.read(cartridge.flash.data, min(cartridge.flash.size, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.flash.data, min(cartridge.flash.size, stream.size()));
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::save(uint id, const stream& stream) -> void {
|
||||
if(id == ID::SRAM) {
|
||||
stream.write(cartridge.sram.data, cartridge.sram.size);
|
||||
stream.write((uint8_t*)cartridge.sram.data, cartridge.sram.size);
|
||||
}
|
||||
|
||||
if(id == ID::EEPROM) {
|
||||
stream.write(cartridge.eeprom.data, cartridge.eeprom.size);
|
||||
stream.write((uint8_t*)cartridge.eeprom.data, cartridge.eeprom.size);
|
||||
}
|
||||
|
||||
if(id == ID::FLASH) {
|
||||
stream.write(cartridge.flash.data, cartridge.flash.size);
|
||||
stream.write((uint8_t*)cartridge.flash.data, cartridge.flash.size);
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
cartridge.unload();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
@@ -146,7 +148,7 @@ auto Interface::run() -> void {
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
system.runtosave();
|
||||
system.runToSave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,26 @@ auto Player::frame() -> void {
|
||||
|
||||
if(!status.enable) return;
|
||||
|
||||
if(cpu.regs.joybus.settings == 0x0000 && cpu.regs.serial.control == 0x5088) {
|
||||
//todo: verify which settings are actually required
|
||||
//values were taken from observing GBP-compatible games
|
||||
if(!cpu.regs.joybus.settings.sc
|
||||
&& !cpu.regs.joybus.settings.sd
|
||||
&& !cpu.regs.joybus.settings.si
|
||||
&& !cpu.regs.joybus.settings.so
|
||||
&& !cpu.regs.joybus.settings.scmode
|
||||
&& !cpu.regs.joybus.settings.sdmode
|
||||
&& !cpu.regs.joybus.settings.simode
|
||||
&& !cpu.regs.joybus.settings.somode
|
||||
&& !cpu.regs.joybus.settings.irqenable
|
||||
&& !cpu.regs.joybus.settings.mode
|
||||
&& !cpu.regs.serial.control.shiftclockselect
|
||||
&& !cpu.regs.serial.control.shiftclockfrequency
|
||||
&& !cpu.regs.serial.control.transferenablereceive
|
||||
&& cpu.regs.serial.control.transferenablesend
|
||||
&& cpu.regs.serial.control.startbit
|
||||
&& cpu.regs.serial.control.transferlength
|
||||
&& cpu.regs.serial.control.irqenable
|
||||
) {
|
||||
status.packet = (status.packet + 1) % 17;
|
||||
switch(status.packet) {
|
||||
case 0: status.send = 0x0000494e; break;
|
||||
@@ -52,16 +71,16 @@ auto Player::frame() -> void {
|
||||
case 15: status.send = 0x30000003; break;
|
||||
case 16: status.send = 0x30000003; break;
|
||||
}
|
||||
cpu.regs.irq.flag.serial = true;
|
||||
cpu.regs.irq.flag |= CPU::Interrupt::Serial;
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::keyinput() -> maybe<uint16> {
|
||||
if(status.logoDetected) {
|
||||
switch(status.logoCounter) {
|
||||
case 0: return 0x03ff;
|
||||
case 1: return 0x03ff;
|
||||
case 2: return 0x030f;
|
||||
case 0: return {0x03ff};
|
||||
case 1: return {0x03ff};
|
||||
case 2: return {0x030f};
|
||||
}
|
||||
}
|
||||
return nothing;
|
||||
@@ -72,7 +91,7 @@ auto Player::read() -> maybe<uint32> {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto Player::write(uint8 byte, uint2 addr) -> void {
|
||||
auto Player::write(uint2 addr, uint8 byte) -> void {
|
||||
if(!status.enable) return;
|
||||
|
||||
uint shift = addr << 3;
|
||||
|
@@ -16,7 +16,7 @@ struct Player {
|
||||
|
||||
auto keyinput() -> maybe<uint16>;
|
||||
auto read() -> maybe<uint32>;
|
||||
auto write(uint8 byte, uint2 addr) -> void;
|
||||
auto write(uint2 addr, uint8 byte) -> void;
|
||||
|
||||
auto serialize(serializer& s) -> void;
|
||||
};
|
||||
|
@@ -1,207 +1,275 @@
|
||||
auto PPU::read(uint32 addr) -> uint8 {
|
||||
auto bgcnt = [&]() -> Registers::Background::Control& { return regs.bg[addr.bits(1,2)].control; };
|
||||
auto wf = [&]() -> Registers::WindowFlags& {
|
||||
static uint id[] = {In0, In1, Out, Obj};
|
||||
return regs.windowflags[id[addr.bits(0,1)]];
|
||||
};
|
||||
|
||||
switch(addr) {
|
||||
|
||||
//DISPCNT
|
||||
case 0x04000000: return regs.control >> 0;
|
||||
case 0x04000001: return regs.control >> 8;
|
||||
case 0x0400'0000: return (
|
||||
regs.control.bgmode << 0
|
||||
| regs.control.cgbmode << 3
|
||||
| regs.control.frame << 4
|
||||
| regs.control.hblank << 5
|
||||
| regs.control.objmapping << 6
|
||||
| regs.control.forceblank << 7
|
||||
);
|
||||
case 0x0400'0001: return (
|
||||
regs.control.enable[BG0] << 0
|
||||
| regs.control.enable[BG1] << 1
|
||||
| regs.control.enable[BG2] << 2
|
||||
| regs.control.enable[BG3] << 3
|
||||
| regs.control.enable[OBJ] << 4
|
||||
| regs.control.enablewindow[In0] << 5
|
||||
| regs.control.enablewindow[In1] << 6
|
||||
| regs.control.enablewindow[Obj] << 7
|
||||
);
|
||||
|
||||
//GRSWP
|
||||
case 0x04000002: return regs.greenswap;
|
||||
case 0x04000003: return 0u;
|
||||
case 0x0400'0002: return regs.greenswap;
|
||||
case 0x0400'0003: return 0;
|
||||
|
||||
//DISPSTAT
|
||||
case 0x04000004: return regs.status >> 0;
|
||||
case 0x04000005: return regs.status >> 8;
|
||||
case 0x0400'0004: return (
|
||||
regs.status.vblank << 0
|
||||
| regs.status.hblank << 1
|
||||
| regs.status.vcoincidence << 2
|
||||
| regs.status.irqvblank << 3
|
||||
| regs.status.irqhblank << 4
|
||||
| regs.status.irqvcoincidence << 5
|
||||
);
|
||||
case 0x0400'0005: return (
|
||||
regs.status.vcompare
|
||||
);
|
||||
|
||||
//VCOUNT
|
||||
case 0x04000006: return regs.vcounter >> 0;
|
||||
case 0x04000007: return regs.vcounter >> 8;
|
||||
case 0x0400'0006: return regs.vcounter.byte(0);
|
||||
case 0x0400'0007: return regs.vcounter.byte(1);
|
||||
|
||||
//BG0CNT,BG1CNT,BG2CNT,BG3CNT
|
||||
case 0x04000008: case 0x04000009:
|
||||
case 0x0400000a: case 0x0400000b:
|
||||
case 0x0400000c: case 0x0400000d:
|
||||
case 0x0400000e: case 0x0400000f: {
|
||||
auto& bg = regs.bg[(addr >> 1) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
return bg.control >> shift;
|
||||
}
|
||||
//BG0CNT, BG1CNT, BG2CNT, BG3CNT
|
||||
case 0x0400'0008: case 0x0400'000a: case 0x0400'000c: case 0x0400'000e: return (
|
||||
bgcnt().priority << 0
|
||||
| bgcnt().characterbaseblock << 2
|
||||
| bgcnt().unused << 4
|
||||
| bgcnt().mosaic << 6
|
||||
| bgcnt().colormode << 7
|
||||
);
|
||||
case 0x0400'0009: case 0x0400'000b: case 0x0400'000d: case 0x0400'000f: return (
|
||||
bgcnt().screenbaseblock << 0
|
||||
| bgcnt().affinewrap << 5
|
||||
| bgcnt().screensize << 6
|
||||
);
|
||||
|
||||
//WININ
|
||||
case 0x04000048: return regs.windowflags[In0];
|
||||
case 0x04000049: return regs.windowflags[In1];
|
||||
case 0x0400004a: return regs.windowflags[Out];
|
||||
case 0x0400004b: return regs.windowflags[Obj];
|
||||
//WININ, WINOUT
|
||||
case 0x0400'0048: case 0x0400'0049: case 0x0400'004a: case 0x0400'004b: return (
|
||||
wf().enable[BG0] << 0
|
||||
| wf().enable[BG1] << 1
|
||||
| wf().enable[BG2] << 2
|
||||
| wf().enable[BG3] << 3
|
||||
| wf().enable[OBJ] << 4
|
||||
| wf().enable[SFX] << 4
|
||||
);
|
||||
|
||||
//BLTCNT
|
||||
case 0x04000050: return regs.blend.control >> 0;
|
||||
case 0x04000051: return regs.blend.control >> 8;
|
||||
case 0x0400'0050: return (
|
||||
regs.blend.control.above[BG0] << 0
|
||||
| regs.blend.control.above[BG1] << 1
|
||||
| regs.blend.control.above[BG2] << 2
|
||||
| regs.blend.control.above[BG3] << 3
|
||||
| regs.blend.control.above[OBJ] << 4
|
||||
| regs.blend.control.above[SFX] << 5
|
||||
| regs.blend.control.mode << 6
|
||||
);
|
||||
case 0x0400'0051: return (
|
||||
regs.blend.control.below[BG0] << 0
|
||||
| regs.blend.control.below[BG1] << 1
|
||||
| regs.blend.control.below[BG2] << 2
|
||||
| regs.blend.control.below[BG3] << 3
|
||||
| regs.blend.control.below[OBJ] << 4
|
||||
| regs.blend.control.below[SFX] << 5
|
||||
);
|
||||
|
||||
//BLDALPHA
|
||||
case 0x04000052: return regs.blend.eva;
|
||||
case 0x04000053: return regs.blend.evb;
|
||||
case 0x0400'0052: return regs.blend.eva;
|
||||
case 0x0400'0053: return regs.blend.evb;
|
||||
|
||||
//BLDY is write-only
|
||||
|
||||
}
|
||||
|
||||
return 0u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto PPU::write(uint32 addr, uint8 byte) -> void {
|
||||
auto PPU::write(uint32 addr, uint8 data) -> void {
|
||||
auto bgcnt = [&]() -> Registers::Background::Control& { return regs.bg[addr.bits(1,2)].control; };
|
||||
auto bgofs = [&]() -> Registers::Background& { return regs.bg[addr.bits(2,3)]; };
|
||||
auto bg = [&]() -> Registers::Background& { return regs.bg[addr.bits(4,5)]; };
|
||||
auto wf = [&]() -> Registers::WindowFlags& {
|
||||
static uint id[] = {In0, In1, Out, Obj};
|
||||
return regs.windowflags[id[addr.bits(0,1)]];
|
||||
};
|
||||
|
||||
switch(addr) {
|
||||
|
||||
//DISPCNT
|
||||
case 0x04000000: regs.control = (regs.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000001: regs.control = (regs.control & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0000:
|
||||
regs.control.bgmode = data.bits(0,2);
|
||||
regs.control.cgbmode = data.bit (3);
|
||||
regs.control.frame = data.bit (4);
|
||||
regs.control.hblank = data.bit (5);
|
||||
regs.control.objmapping = data.bit (6);
|
||||
regs.control.forceblank = data.bit (7);
|
||||
return;
|
||||
case 0x0400'0001:
|
||||
regs.control.enable[BG0] = data.bit(0);
|
||||
regs.control.enable[BG1] = data.bit(1);
|
||||
regs.control.enable[BG2] = data.bit(2);
|
||||
regs.control.enable[BG3] = data.bit(3);
|
||||
regs.control.enable[OBJ] = data.bit(4);
|
||||
regs.control.enablewindow[In0] = data.bit(5);
|
||||
regs.control.enablewindow[In1] = data.bit(6);
|
||||
regs.control.enablewindow[Obj] = data.bit(7);
|
||||
return;
|
||||
|
||||
//GRSWP
|
||||
case 0x04000002: regs.greenswap = byte >> 0; return;
|
||||
case 0x04000003: return;
|
||||
case 0x0400'0002:
|
||||
regs.greenswap = data.bit(0);
|
||||
return;
|
||||
case 0x0400'0003: return;
|
||||
|
||||
//DISPSTAT
|
||||
case 0x04000004:
|
||||
regs.status.irqvblank = byte >> 3;
|
||||
regs.status.irqhblank = byte >> 4;
|
||||
regs.status.irqvcoincidence = byte >> 5;
|
||||
case 0x0400'0004:
|
||||
regs.status.irqvblank = data.bit(3);
|
||||
regs.status.irqhblank = data.bit(4);
|
||||
regs.status.irqvcoincidence = data.bit(5);
|
||||
return;
|
||||
case 0x04000005:
|
||||
regs.status.vcompare = byte;
|
||||
case 0x0400'0005:
|
||||
regs.status.vcompare = data;
|
||||
return;
|
||||
|
||||
//BG0CNT,BG1CNT,BG2CNT,BG3CNT
|
||||
case 0x04000008: case 0x04000009:
|
||||
case 0x0400000a: case 0x0400000b:
|
||||
case 0x0400000c: case 0x0400000d:
|
||||
case 0x0400000e: case 0x0400000f: {
|
||||
auto& bg = regs.bg[(addr >> 1) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
if(addr == 0x04000009 || addr == 0x0400000b) byte &= 0xdf; //clear affine wrap for BG0,1
|
||||
bg.control = (bg.control & ~(255 << shift)) | (byte << shift);
|
||||
//BG0CNT, BG1CNT, BG2CNT, BG3CNT
|
||||
case 0x0400'0008: case 0x0400'000a: case 0x0400'000c: case 0x0400'000e:
|
||||
bgcnt().priority = data.bits(0,1);
|
||||
bgcnt().characterbaseblock = data.bits(2,3);
|
||||
bgcnt().unused = data.bits(4,5);
|
||||
bgcnt().mosaic = data.bit (6);
|
||||
bgcnt().colormode = data.bit (7);
|
||||
return;
|
||||
case 0x0400'0009: case 0x0400'000b: case 0x0400'000d: case 0x0400'000f:
|
||||
if(addr.bits(1,2) <= 1) data.bit(5) = 0; //clear affine wrap for BG0, BG1
|
||||
bgcnt().screenbaseblock = data.bits(0,4);
|
||||
bgcnt().affinewrap = data.bit (5);
|
||||
bgcnt().screensize = data.bits(6,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//BG0HOFS,BG1HOFS,BG2BOFS,BG3HOFS
|
||||
case 0x04000010: case 0x04000011:
|
||||
case 0x04000014: case 0x04000015:
|
||||
case 0x04000018: case 0x04000019:
|
||||
case 0x0400001c: case 0x0400001d: {
|
||||
auto& bg = regs.bg[(addr >> 2) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
bg.hoffset = (bg.hoffset & ~(255 << shift)) | (byte << shift);
|
||||
//BG0HOFS, BG1HOFS, BG2BOFS, BG3HOFS
|
||||
case 0x0400'0010: case 0x0400'0014: case 0x0400'0018: case 0x0400'001c:
|
||||
bgofs().hoffset.bits(0,7) = data;
|
||||
return;
|
||||
case 0x0400'0011: case 0x0400'0015: case 0x0400'0019: case 0x0400'001d:
|
||||
bgofs().hoffset.bit(8) = data.bit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
//BG0VOFS,BG1VOFS,BG2VOFS,BG3VOFS
|
||||
case 0x04000012: case 0x04000013:
|
||||
case 0x04000016: case 0x04000017:
|
||||
case 0x0400001a: case 0x0400001b:
|
||||
case 0x0400001e: case 0x0400001f: {
|
||||
auto& bg = regs.bg[(addr >> 2) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
bg.voffset = (bg.voffset & ~(255 << shift)) | (byte << shift);
|
||||
//BG0VOFS, BG1VOFS, BG2VOFS, BG3VOFS
|
||||
case 0x0400'0012: case 0x0400'0016: case 0x0400'001a: case 0x0400'001e:
|
||||
bgofs().voffset.bits(0,7) = data;
|
||||
return;
|
||||
case 0x0400'0013: case 0x0400'0017: case 0x0400'001b: case 0x0400'001f:
|
||||
bgofs().voffset.bit(8) = data.bit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
//BG2PA,BG3PA
|
||||
case 0x04000020: case 0x04000021:
|
||||
case 0x04000030: case 0x04000031: {
|
||||
auto& bg = regs.bg[(addr >> 4) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
bg.pa = (bg.pa & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//BG2PA, BG3PA
|
||||
case 0x0400'0020: case 0x0400'0030: bg().pa.byte(0) = data; return;
|
||||
case 0x0400'0021: case 0x0400'0031: bg().pa.byte(1) = data; return;
|
||||
|
||||
//BG2PB,BG3PB
|
||||
case 0x04000022: case 0x04000023:
|
||||
case 0x04000032: case 0x04000033: {
|
||||
auto& bg = regs.bg[(addr >> 4) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
bg.pb = (bg.pb & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//BG2PB, BG3PB
|
||||
case 0x0400'0022: case 0x0400'0032: bg().pb.byte(0) = data; return;
|
||||
case 0x0400'0023: case 0x0400'0033: bg().pb.byte(1) = data; return;
|
||||
|
||||
//BG2PC,BG3PC
|
||||
case 0x04000024: case 0x04000025:
|
||||
case 0x04000034: case 0x04000035: {
|
||||
auto& bg = regs.bg[(addr >> 4) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
bg.pc = (bg.pc & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//BG2PC, BG3PC
|
||||
case 0x0400'0024: case 0x0400'0034: bg().pc.byte(0) = data; return;
|
||||
case 0x0400'0025: case 0x0400'0035: bg().pc.byte(1) = data; return;
|
||||
|
||||
//BG2PD,BG3PD
|
||||
case 0x04000026: case 0x04000027:
|
||||
case 0x04000036: case 0x04000037: {
|
||||
auto& bg = regs.bg[(addr >> 4) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
bg.pd = (bg.pd & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//BG2PD, BG3PD
|
||||
case 0x0400'0026: case 0x0400'0036: bg().pd.byte(0) = data; return;
|
||||
case 0x0400'0027: case 0x0400'0037: bg().pd.byte(1) = data; return;
|
||||
|
||||
//BG2X_L,BG2X_H,BG3X_L,BG3X_H
|
||||
case 0x04000028: case 0x04000029: case 0x0400002a: case 0x0400002b:
|
||||
case 0x04000038: case 0x04000039: case 0x0400003a: case 0x0400003b: {
|
||||
auto& bg = regs.bg[(addr >> 4) & 3];
|
||||
unsigned shift = (addr & 3) * 8;
|
||||
bg.lx = bg.x = (bg.x & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//BG2X_L, BG2X_H, BG3X_L, BG3X_H
|
||||
case 0x0400'0028: case 0x0400'0038: bg().x.bits( 0, 7) = data.bits(0,7); bg().lx = bg().x; return;
|
||||
case 0x0400'0029: case 0x0400'0039: bg().x.bits( 8,15) = data.bits(0,7); bg().lx = bg().x; return;
|
||||
case 0x0400'002a: case 0x0400'003a: bg().x.bits(16,23) = data.bits(0,7); bg().lx = bg().x; return;
|
||||
case 0x0400'002b: case 0x0400'003b: bg().x.bits(24,27) = data.bits(0,3); bg().lx = bg().x; return;
|
||||
|
||||
//BG2Y_L,BG2Y_H,BG3Y_L,BG3Y_H
|
||||
case 0x0400002c: case 0x0400002d: case 0x0400002e: case 0x0400002f:
|
||||
case 0x0400003c: case 0x0400003d: case 0x0400003e: case 0x0400003f: {
|
||||
auto& bg = regs.bg[(addr >> 4) & 3];
|
||||
unsigned shift = (addr & 3) * 8;
|
||||
bg.ly = bg.y = (bg.y & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//BG2Y_L, BG2Y_H, BG3Y_L, BG3Y_H
|
||||
case 0x0400'002c: case 0x0400'003c: bg().y.bits( 0, 7) = data.bits(0,7); bg().ly = bg().y; return;
|
||||
case 0x0400'002d: case 0x0400'003d: bg().y.bits( 8,15) = data.bits(0,7); bg().ly = bg().y; return;
|
||||
case 0x0400'002e: case 0x0400'003e: bg().y.bits(16,23) = data.bits(0,7); bg().ly = bg().y; return;
|
||||
case 0x0400'002f: case 0x0400'003f: bg().y.bits(24,27) = data.bits(0,3); bg().ly = bg().y; return;
|
||||
|
||||
//WIN0H
|
||||
case 0x04000040: regs.window[0].x2 = byte; return;
|
||||
case 0x04000041: regs.window[0].x1 = byte; return;
|
||||
case 0x0400'0040: regs.window[0].x2 = data; return;
|
||||
case 0x0400'0041: regs.window[0].x1 = data; return;
|
||||
|
||||
//WIN1H
|
||||
case 0x04000042: regs.window[1].x2 = byte; return;
|
||||
case 0x04000043: regs.window[1].x1 = byte; return;
|
||||
case 0x0400'0042: regs.window[1].x2 = data; return;
|
||||
case 0x0400'0043: regs.window[1].x1 = data; return;
|
||||
|
||||
//WIN0V
|
||||
case 0x04000044: regs.window[0].y2 = byte; return;
|
||||
case 0x04000045: regs.window[0].y1 = byte; return;
|
||||
case 0x0400'0044: regs.window[0].y2 = data; return;
|
||||
case 0x0400'0045: regs.window[0].y1 = data; return;
|
||||
|
||||
//WIN1V
|
||||
case 0x04000046: regs.window[1].y2 = byte; return;
|
||||
case 0x04000047: regs.window[1].y1 = byte; return;
|
||||
case 0x0400'0046: regs.window[1].y2 = data; return;
|
||||
case 0x0400'0047: regs.window[1].y1 = data; return;
|
||||
|
||||
//WININ
|
||||
case 0x04000048: regs.windowflags[In0] = byte; return;
|
||||
case 0x04000049: regs.windowflags[In1] = byte; return;
|
||||
|
||||
//WINOUT
|
||||
case 0x0400004a: regs.windowflags[Out] = byte; return;
|
||||
case 0x0400004b: regs.windowflags[Obj] = byte; return;
|
||||
//WININ, WINOUT
|
||||
case 0x0400'0048: case 0x0400'0049: case 0x0400'004a: case 0x0400'004b:
|
||||
wf().enable[BG0] = data.bit(0);
|
||||
wf().enable[BG1] = data.bit(1);
|
||||
wf().enable[BG2] = data.bit(2);
|
||||
wf().enable[BG3] = data.bit(3);
|
||||
wf().enable[OBJ] = data.bit(4);
|
||||
wf().enable[SFX] = data.bit(5);
|
||||
return;
|
||||
|
||||
//MOSAIC
|
||||
case 0x0400004c:
|
||||
regs.mosaic.bghsize = byte >> 0;
|
||||
regs.mosaic.bgvsize = byte >> 4;
|
||||
case 0x0400'004c:
|
||||
regs.mosaic.bghsize = data.bits(0,3);
|
||||
regs.mosaic.bgvsize = data.bits(4,7);
|
||||
return;
|
||||
case 0x0400004d:
|
||||
regs.mosaic.objhsize = byte >> 0;
|
||||
regs.mosaic.objvsize = byte >> 4;
|
||||
case 0x0400'004d:
|
||||
regs.mosaic.objhsize = data.bits(0,3);
|
||||
regs.mosaic.objvsize = data.bits(4,7);
|
||||
return;
|
||||
|
||||
//BLDCNT
|
||||
case 0x04000050: regs.blend.control = (regs.blend.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000051: regs.blend.control = (regs.blend.control & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0050:
|
||||
regs.blend.control.above[BG0] = data.bit (0);
|
||||
regs.blend.control.above[BG1] = data.bit (1);
|
||||
regs.blend.control.above[BG2] = data.bit (2);
|
||||
regs.blend.control.above[BG3] = data.bit (3);
|
||||
regs.blend.control.above[OBJ] = data.bit (4);
|
||||
regs.blend.control.above[SFX] = data.bit (5);
|
||||
regs.blend.control.mode = data.bits(6,7);
|
||||
return;
|
||||
case 0x0400'0051:
|
||||
regs.blend.control.below[BG0] = data.bit(0);
|
||||
regs.blend.control.below[BG1] = data.bit(1);
|
||||
regs.blend.control.below[BG2] = data.bit(2);
|
||||
regs.blend.control.below[BG3] = data.bit(3);
|
||||
regs.blend.control.below[OBJ] = data.bit(4);
|
||||
regs.blend.control.below[SFX] = data.bit(5);
|
||||
return;
|
||||
|
||||
//BLDALPHA
|
||||
case 0x04000052: regs.blend.eva = byte & 0x1f; return;
|
||||
case 0x04000053: regs.blend.evb = byte & 0x1f; return;
|
||||
case 0x0400'0052: regs.blend.eva = data.bits(0,4); return;
|
||||
case 0x0400'0053: regs.blend.evb = data.bits(0,4); return;
|
||||
|
||||
//BLDY
|
||||
case 0x04000054: regs.blend.evy = byte & 0x1f; return;
|
||||
case 0x04000055: return;
|
||||
case 0x0400'0054: regs.blend.evy = data.bits(0,4); return;
|
||||
case 0x0400'0055: return;
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,9 @@
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "registers.cpp"
|
||||
PPU ppu;
|
||||
#include "video.cpp"
|
||||
|
||||
#include "background.cpp"
|
||||
#include "object.cpp"
|
||||
#include "mosaic.cpp"
|
||||
@@ -20,7 +22,6 @@ namespace GameBoyAdvance {
|
||||
#include "mmio.cpp"
|
||||
#include "memory.cpp"
|
||||
#include "serialization.cpp"
|
||||
PPU ppu;
|
||||
|
||||
PPU::PPU() {
|
||||
output = new uint32[240 * 160];
|
||||
@@ -36,13 +37,7 @@ PPU::~PPU() {
|
||||
}
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
ppu.main();
|
||||
}
|
||||
while(true) scheduler.synchronize(), ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
@@ -51,23 +46,43 @@ auto PPU::main() -> void {
|
||||
|
||||
auto PPU::step(uint clocks) -> void {
|
||||
clock += clocks;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
create(PPU::Enter, 16777216);
|
||||
create(PPU::Enter, 16'777'216);
|
||||
|
||||
for(uint n = 0; n < 240 * 160; n++) output[n] = 0;
|
||||
|
||||
for(uint n = 0; n < 1024; n += 2) pram_write(n, Half, 0x0000);
|
||||
for(uint n = 0; n < 1024; n += 2) oam_write(n, Half, 0x0000);
|
||||
|
||||
regs.control = 0;
|
||||
regs.control.bgmode = 0;
|
||||
regs.control.cgbmode = 0;
|
||||
regs.control.frame = 0;
|
||||
regs.control.hblank = 0;
|
||||
regs.control.objmapping = 0;
|
||||
regs.control.forceblank = 0;
|
||||
for(auto& enable : regs.control.enable) enable = 0;
|
||||
for(auto& enablewindow : regs.control.enablewindow) enablewindow = 0;
|
||||
regs.greenswap = 0;
|
||||
regs.status = 0;
|
||||
regs.status.vblank = 0;
|
||||
regs.status.hblank = 0;
|
||||
regs.status.vcoincidence = 0;
|
||||
regs.status.irqvblank = 0;
|
||||
regs.status.irqhblank = 0;
|
||||
regs.status.irqvcoincidence = 0;
|
||||
regs.status.vcompare = 0;
|
||||
regs.vcounter = 0;
|
||||
for(auto& bg : regs.bg) {
|
||||
bg.control = 0;
|
||||
bg.control.priority = 0;
|
||||
bg.control.characterbaseblock = 0;
|
||||
bg.control.unused = 0;
|
||||
bg.control.mosaic = 0;
|
||||
bg.control.colormode = 0;
|
||||
bg.control.screenbaseblock = 0;
|
||||
bg.control.affinewrap = 0;
|
||||
bg.control.screensize = 0;
|
||||
bg.hoffset = 0;
|
||||
bg.voffset = 0;
|
||||
bg.pa = 0;
|
||||
@@ -85,14 +100,16 @@ auto PPU::power() -> void {
|
||||
w.y1 = 0;
|
||||
w.y2 = 0;
|
||||
}
|
||||
for(auto& f : regs.windowflags) {
|
||||
f = 0;
|
||||
for(auto& flags : regs.windowflags) {
|
||||
for(auto& enable : flags.enable) enable = 0;
|
||||
}
|
||||
regs.mosaic.bghsize = 0;
|
||||
regs.mosaic.bgvsize = 0;
|
||||
regs.mosaic.objhsize = 0;
|
||||
regs.mosaic.objvsize = 0;
|
||||
regs.blend.control = 0;
|
||||
for(auto& above : regs.blend.control.above) above = 0;
|
||||
regs.blend.control.mode = 0;
|
||||
for(auto& below : regs.blend.control.below) below = 0;
|
||||
regs.blend.eva = 0;
|
||||
regs.blend.evb = 0;
|
||||
regs.blend.evy = 0;
|
||||
@@ -117,12 +134,12 @@ auto PPU::scanline() -> void {
|
||||
}
|
||||
|
||||
if(regs.vcounter == 160) {
|
||||
if(regs.status.irqvblank) cpu.regs.irq.flag.vblank = 1;
|
||||
if(regs.status.irqvblank) cpu.regs.irq.flag |= CPU::Interrupt::VBlank;
|
||||
cpu.dma_vblank();
|
||||
}
|
||||
|
||||
if(regs.status.irqvcoincidence) {
|
||||
if(regs.status.vcoincidence) cpu.regs.irq.flag.vcoincidence = 1;
|
||||
if(regs.status.vcoincidence) cpu.regs.irq.flag |= CPU::Interrupt::VCoincidence;
|
||||
}
|
||||
|
||||
if(regs.vcounter < 160) {
|
||||
@@ -150,7 +167,7 @@ auto PPU::scanline() -> void {
|
||||
|
||||
step(960);
|
||||
regs.status.hblank = 1;
|
||||
if(regs.status.irqhblank) cpu.regs.irq.flag.hblank = 1;
|
||||
if(regs.status.irqhblank) cpu.regs.irq.flag |= CPU::Interrupt::HBlank;
|
||||
if(regs.vcounter < 160) cpu.dma_hblank();
|
||||
|
||||
step(240);
|
||||
@@ -163,7 +180,8 @@ auto PPU::scanline() -> void {
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
player.frame();
|
||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||
video.refresh();
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread, MMIO {
|
||||
#include "registers.hpp"
|
||||
#include "state.hpp"
|
||||
|
@@ -1,140 +0,0 @@
|
||||
PPU::Registers::Control::operator uint16() const {
|
||||
return (
|
||||
(bgmode << 0)
|
||||
| (cgbmode << 3)
|
||||
| (frame << 4)
|
||||
| (hblank << 5)
|
||||
| (objmapping << 6)
|
||||
| (forceblank << 7)
|
||||
| (enable[BG0] << 8)
|
||||
| (enable[BG1] << 9)
|
||||
| (enable[BG2] << 10)
|
||||
| (enable[BG3] << 11)
|
||||
| (enable[OBJ] << 12)
|
||||
| (enablewindow[In0] << 13)
|
||||
| (enablewindow[In1] << 14)
|
||||
| (enablewindow[Obj] << 15)
|
||||
);
|
||||
}
|
||||
|
||||
auto PPU::Registers::Control::operator=(uint16 source) -> uint16 {
|
||||
bgmode = source >> 0;
|
||||
cgbmode = source >> 3;
|
||||
frame = source >> 4;
|
||||
hblank = source >> 5;
|
||||
objmapping = source >> 6;
|
||||
forceblank = source >> 7;
|
||||
enable[BG0] = source >> 8;
|
||||
enable[BG1] = source >> 9;
|
||||
enable[BG2] = source >> 10;
|
||||
enable[BG3] = source >> 11;
|
||||
enable[OBJ] = source >> 12;
|
||||
enablewindow[In0] = source >> 13;
|
||||
enablewindow[In1] = source >> 14;
|
||||
enablewindow[Obj] = source >> 15;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
PPU::Registers::Status::operator uint16() const {
|
||||
return (
|
||||
(vblank << 0)
|
||||
| (hblank << 1)
|
||||
| (vcoincidence << 2)
|
||||
| (irqvblank << 3)
|
||||
| (irqhblank << 4)
|
||||
| (irqvcoincidence << 5)
|
||||
| (vcompare << 8)
|
||||
);
|
||||
}
|
||||
|
||||
auto PPU::Registers::Status::operator=(uint16 source) -> uint16 {
|
||||
vblank = source >> 0;
|
||||
hblank = source >> 1;
|
||||
vcoincidence = source >> 2;
|
||||
irqvblank = source >> 3;
|
||||
irqhblank = source >> 4;
|
||||
irqvcoincidence = source >> 5;
|
||||
vcompare = source >> 8;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
PPU::Registers::BackgroundControl::operator uint16() const {
|
||||
return (
|
||||
(priority << 0)
|
||||
| (characterbaseblock << 2)
|
||||
| (unused << 4)
|
||||
| (mosaic << 6)
|
||||
| (colormode << 7)
|
||||
| (screenbaseblock << 8)
|
||||
| (affinewrap << 13)
|
||||
| (screensize << 14)
|
||||
);
|
||||
}
|
||||
|
||||
auto PPU::Registers::BackgroundControl::operator=(uint16 source) -> uint16 {
|
||||
priority = source >> 0;
|
||||
characterbaseblock = source >> 2;
|
||||
unused = source >> 4;
|
||||
mosaic = source >> 6;
|
||||
colormode = source >> 7;
|
||||
screenbaseblock = source >> 8;
|
||||
affinewrap = source >> 13;
|
||||
screensize = source >> 14;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
PPU::Registers::WindowFlags::operator uint8() const {
|
||||
return (
|
||||
(enable[BG0] << 0)
|
||||
| (enable[BG1] << 1)
|
||||
| (enable[BG2] << 2)
|
||||
| (enable[BG3] << 3)
|
||||
| (enable[OBJ] << 4)
|
||||
| (enable[SFX] << 5)
|
||||
);
|
||||
}
|
||||
|
||||
auto PPU::Registers::WindowFlags::operator=(uint8 source) -> uint8 {
|
||||
enable[BG0] = source >> 0;
|
||||
enable[BG1] = source >> 1;
|
||||
enable[BG2] = source >> 2;
|
||||
enable[BG3] = source >> 3;
|
||||
enable[OBJ] = source >> 4;
|
||||
enable[SFX] = source >> 5;
|
||||
return operator uint8();
|
||||
}
|
||||
|
||||
PPU::Registers::BlendControl::operator uint16() const {
|
||||
return (
|
||||
(above[BG0] << 0)
|
||||
| (above[BG1] << 1)
|
||||
| (above[BG2] << 2)
|
||||
| (above[BG3] << 3)
|
||||
| (above[OBJ] << 4)
|
||||
| (above[SFX] << 5)
|
||||
| (mode << 6)
|
||||
| (below[BG0] << 8)
|
||||
| (below[BG1] << 9)
|
||||
| (below[BG2] << 10)
|
||||
| (below[BG3] << 11)
|
||||
| (below[OBJ] << 12)
|
||||
| (below[SFX] << 13)
|
||||
);
|
||||
}
|
||||
|
||||
auto PPU::Registers::BlendControl::operator=(uint16 source) -> uint16 {
|
||||
above[BG0] = source >> 0;
|
||||
above[BG1] = source >> 1;
|
||||
above[BG2] = source >> 2;
|
||||
above[BG3] = source >> 3;
|
||||
above[OBJ] = source >> 4;
|
||||
above[SFX] = source >> 5;
|
||||
mode = source >> 6;
|
||||
below[BG0] = source >> 8;
|
||||
below[BG1] = source >> 9;
|
||||
below[BG2] = source >> 10;
|
||||
below[BG3] = source >> 11;
|
||||
below[OBJ] = source >> 12;
|
||||
below[SFX] = source >> 13;
|
||||
return operator uint16();
|
||||
}
|
@@ -11,10 +11,6 @@ struct Registers {
|
||||
uint1 forceblank;
|
||||
uint1 enable[5];
|
||||
uint1 enablewindow[3];
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const Control&) -> Control& = delete;
|
||||
} control;
|
||||
|
||||
uint1 greenswap;
|
||||
@@ -27,31 +23,21 @@ struct Registers {
|
||||
uint1 irqhblank;
|
||||
uint1 irqvcoincidence;
|
||||
uint8 vcompare;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const Status&) -> Status& = delete;
|
||||
} status;
|
||||
|
||||
uint16 vcounter;
|
||||
|
||||
struct BackgroundControl {
|
||||
uint2 priority;
|
||||
uint2 characterbaseblock;
|
||||
uint2 unused;
|
||||
uint1 mosaic;
|
||||
uint1 colormode;
|
||||
uint5 screenbaseblock;
|
||||
uint1 affinewrap; //BG2,3 only
|
||||
uint2 screensize;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const BackgroundControl&) -> BackgroundControl& = delete;
|
||||
};
|
||||
|
||||
struct Background {
|
||||
BackgroundControl control;
|
||||
struct Control {
|
||||
uint2 priority;
|
||||
uint2 characterbaseblock;
|
||||
uint2 unused;
|
||||
uint1 mosaic;
|
||||
uint1 colormode;
|
||||
uint5 screenbaseblock;
|
||||
uint1 affinewrap; //BG2,3 only
|
||||
uint2 screensize;
|
||||
} control;
|
||||
uint9 hoffset;
|
||||
uint9 voffset;
|
||||
|
||||
@@ -66,20 +52,14 @@ struct Registers {
|
||||
uint id;
|
||||
} bg[4];
|
||||
|
||||
struct WindowFlags {
|
||||
uint1 enable[6];
|
||||
|
||||
operator uint8() const;
|
||||
auto operator=(uint8 source) -> uint8;
|
||||
auto operator=(const WindowFlags&) -> WindowFlags& = delete;
|
||||
};
|
||||
|
||||
struct Window {
|
||||
uint8 x1, x2;
|
||||
uint8 y1, y2;
|
||||
} window[2];
|
||||
|
||||
WindowFlags windowflags[4];
|
||||
struct WindowFlags {
|
||||
uint1 enable[6];
|
||||
} windowflags[4];
|
||||
|
||||
struct Mosaic {
|
||||
uint4 bghsize;
|
||||
@@ -88,18 +68,12 @@ struct Registers {
|
||||
uint4 objvsize;
|
||||
} mosaic;
|
||||
|
||||
struct BlendControl {
|
||||
uint1 above[6];
|
||||
uint1 below[6];
|
||||
uint2 mode;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const BlendControl&) -> BlendControl& = delete;
|
||||
};
|
||||
|
||||
struct Blend {
|
||||
BlendControl control;
|
||||
struct Control {
|
||||
uint1 above[6];
|
||||
uint2 mode;
|
||||
uint1 below[6];
|
||||
} control;
|
||||
uint5 eva;
|
||||
uint5 evb;
|
||||
uint5 evy;
|
||||
|
@@ -14,14 +14,19 @@ auto PPU::render_screen() -> void {
|
||||
|
||||
for(auto x : range(240)) {
|
||||
Registers::WindowFlags flags;
|
||||
flags = ~0; //enable all layers if no windows are enabled
|
||||
flags.enable[BG0] = true; //enable all layers if no windows are enabled
|
||||
flags.enable[BG1] = true;
|
||||
flags.enable[BG2] = true;
|
||||
flags.enable[BG3] = true;
|
||||
flags.enable[OBJ] = true;
|
||||
flags.enable[SFX] = true;
|
||||
|
||||
//determine active window
|
||||
if(regs.control.enablewindow[In0] || regs.control.enablewindow[In1] || regs.control.enablewindow[Obj]) {
|
||||
flags = (uint8)regs.windowflags[Out];
|
||||
if(regs.control.enablewindow[Obj] && windowmask[Obj][x]) flags = (uint8)regs.windowflags[Obj];
|
||||
if(regs.control.enablewindow[In1] && windowmask[In1][x]) flags = (uint8)regs.windowflags[In1];
|
||||
if(regs.control.enablewindow[In0] && windowmask[In0][x]) flags = (uint8)regs.windowflags[In0];
|
||||
flags = regs.windowflags[Out];
|
||||
if(regs.control.enablewindow[Obj] && windowmask[Obj][x]) flags = regs.windowflags[Obj];
|
||||
if(regs.control.enablewindow[In1] && windowmask[In1][x]) flags = regs.windowflags[In1];
|
||||
if(regs.control.enablewindow[In0] && windowmask[In0][x]) flags = regs.windowflags[In0];
|
||||
}
|
||||
|
||||
//priority sorting: find topmost two pixels
|
||||
@@ -40,9 +45,9 @@ auto PPU::render_screen() -> void {
|
||||
bool blendabove = regs.blend.control.above[a];
|
||||
bool blendbelow = regs.blend.control.below[b];
|
||||
uint color = above[x].color;
|
||||
auto eva = min(16u, (unsigned)regs.blend.eva);
|
||||
auto evb = min(16u, (unsigned)regs.blend.evb);
|
||||
auto evy = min(16u, (unsigned)regs.blend.evy);
|
||||
auto eva = min(16u, (uint)regs.blend.eva);
|
||||
auto evb = min(16u, (uint)regs.blend.evb);
|
||||
auto evy = min(16u, (uint)regs.blend.evy);
|
||||
|
||||
//perform blending, if needed
|
||||
if(flags.enable[SFX] == false) {
|
||||
|
59
higan/gba/ppu/video.cpp
Normal file
59
higan/gba/ppu/video.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[240 * 160];
|
||||
paletteLiteral = new uint32[1 << 15];
|
||||
paletteStandard = new uint32[1 << 15];
|
||||
paletteEmulation = new uint32[1 << 15];
|
||||
}
|
||||
|
||||
auto Video::power() -> void {
|
||||
memory::fill(output(), 240 * 160 * sizeof(uint32));
|
||||
|
||||
for(auto color : range(1 << 15)) {
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
uint B = (uint5)(color >> 10);
|
||||
uint G = (uint5)(color >> 5);
|
||||
uint R = (uint5)(color >> 0);
|
||||
|
||||
uint b = image::normalize(B, 5, 16);
|
||||
uint g = image::normalize(G, 5, 16);
|
||||
uint r = image::normalize(R, 5, 16);
|
||||
paletteStandard[color] = interface->videoColor(r, g, b);
|
||||
|
||||
double lcdGamma = 4.0, outGamma = 2.2;
|
||||
double lb = pow(B / 31.0, lcdGamma);
|
||||
double lg = pow(G / 31.0, lcdGamma);
|
||||
double lr = pow(R / 31.0, lcdGamma);
|
||||
b = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
g = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
r = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
paletteEmulation[color] = interface->videoColor(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto output = this->output();
|
||||
auto& palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
for(uint y = 0; y < 160; y++) {
|
||||
auto source = ppu.output + y * 240;
|
||||
auto target = output + y * 240;
|
||||
|
||||
if(settings.blurEmulation) {
|
||||
for(uint x = 0; x < 240; x++) {
|
||||
auto a = palette[*source++];
|
||||
auto b = *target;
|
||||
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
}
|
||||
} else {
|
||||
for(uint x = 0; x < 240; x++) {
|
||||
auto color = palette[*source++];
|
||||
*target++ = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface->videoRefresh(output, 240 * sizeof(uint32), 240, 160);
|
||||
}
|
13
higan/gba/ppu/video.hpp
Normal file
13
higan/gba/ppu/video.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
struct Video {
|
||||
Video();
|
||||
|
||||
auto power() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
};
|
||||
|
||||
extern Video video;
|
@@ -4,27 +4,40 @@ namespace GameBoyAdvance {
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
Scheduler::Scheduler() {
|
||||
sync = SynchronizeMode::None;
|
||||
exit_reason = ExitReason::UnknownEvent;
|
||||
host = nullptr;
|
||||
active = nullptr;
|
||||
}
|
||||
|
||||
auto Scheduler::enter() -> void {
|
||||
auto Scheduler::power() -> void {
|
||||
host = co_active();
|
||||
co_switch(active);
|
||||
resume = cpu.thread;
|
||||
}
|
||||
|
||||
auto Scheduler::exit(ExitReason reason) -> void {
|
||||
exit_reason = reason;
|
||||
active = co_active();
|
||||
auto Scheduler::enter(Mode mode_) -> Event {
|
||||
mode = mode_;
|
||||
host = co_active();
|
||||
co_switch(resume);
|
||||
return event;
|
||||
}
|
||||
|
||||
auto Scheduler::exit(Event event_) -> void {
|
||||
event = event_;
|
||||
resume = co_active();
|
||||
co_switch(host);
|
||||
}
|
||||
|
||||
auto Scheduler::power() -> void {
|
||||
host = co_active();
|
||||
active = cpu.thread;
|
||||
auto Scheduler::synchronize(cothread_t thread) -> void {
|
||||
if(thread == cpu.thread) {
|
||||
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
|
||||
} else {
|
||||
resume = thread;
|
||||
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize() -> void {
|
||||
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
|
||||
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronizing() const -> bool {
|
||||
return mode == Mode::SynchronizeAll;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,15 +1,28 @@
|
||||
struct Scheduler : property<Scheduler> {
|
||||
enum class SynchronizeMode : uint { None, CPU, All } sync;
|
||||
enum class ExitReason : uint { UnknownEvent, FrameEvent, SynchronizeEvent };
|
||||
readonly<ExitReason> exit_reason;
|
||||
struct Scheduler {
|
||||
enum class Mode : uint {
|
||||
Run,
|
||||
SynchronizeCPU,
|
||||
SynchronizeAll,
|
||||
};
|
||||
|
||||
cothread_t host;
|
||||
cothread_t active;
|
||||
enum class Event : uint {
|
||||
Unknown,
|
||||
Frame,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
Scheduler();
|
||||
auto enter() -> void;
|
||||
auto exit(ExitReason) -> void;
|
||||
auto power() -> void;
|
||||
auto enter(Mode = Mode::Run) -> Event;
|
||||
auto exit(Event) -> void;
|
||||
auto synchronize(cothread_t) -> void;
|
||||
auto synchronize() -> void;
|
||||
auto synchronizing() const -> bool;
|
||||
|
||||
private:
|
||||
cothread_t host = nullptr;
|
||||
cothread_t resume = nullptr;
|
||||
Mode mode = Mode::Run;
|
||||
Event event = Event::Unknown;
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(serialize_size);
|
||||
serializer s(_serializeSize);
|
||||
|
||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||
char hash[64], description[512];
|
||||
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ auto System::unserialize(serializer& s) -> bool {
|
||||
if(version != Info::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ auto System::serialize(serializer& s) -> void {
|
||||
s.integer(bios.mdr);
|
||||
}
|
||||
|
||||
auto System::serialize_all(serializer& s) -> void {
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
cartridge.serialize(s);
|
||||
system.serialize(s);
|
||||
cpu.serialize(s);
|
||||
@@ -46,7 +46,7 @@ auto System::serialize_all(serializer& s) -> void {
|
||||
player.serialize(s);
|
||||
}
|
||||
|
||||
auto System::serialize_init() -> void {
|
||||
auto System::serializeInit() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0, version = 0;
|
||||
@@ -57,6 +57,6 @@ auto System::serialize_init() -> void {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serialize_size = s.size();
|
||||
serializeAll(s);
|
||||
_serializeSize = s.size();
|
||||
}
|
||||
|
@@ -7,6 +7,8 @@ namespace GameBoyAdvance {
|
||||
BIOS bios;
|
||||
System system;
|
||||
|
||||
auto System::loaded() const -> bool { return _loaded; }
|
||||
|
||||
auto System::init() -> void {
|
||||
}
|
||||
|
||||
@@ -32,40 +34,25 @@ auto System::load() -> void {
|
||||
interface->loadRequest(ID::BIOS, bios, true);
|
||||
}
|
||||
|
||||
serialize_init();
|
||||
cartridge.load();
|
||||
serializeInit();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
cartridge.unload();
|
||||
_loaded = false;
|
||||
}
|
||||
|
||||
auto System::run() -> void {
|
||||
while(true) {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) break;
|
||||
}
|
||||
video.refresh();
|
||||
while(scheduler.enter() != Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
auto System::runtosave() -> void {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::CPU;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.active = ppu.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.active = apu.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
||||
}
|
||||
|
||||
auto System::runthreadtosave() -> void {
|
||||
while(true) {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
auto System::runToSave() -> void {
|
||||
scheduler.synchronize(cpu.thread);
|
||||
scheduler.synchronize(ppu.thread);
|
||||
scheduler.synchronize(apu.thread);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -15,26 +15,29 @@ struct BIOS : Memory {
|
||||
};
|
||||
|
||||
struct System {
|
||||
auto loaded() const -> bool;
|
||||
|
||||
auto init() -> void;
|
||||
auto term() -> void;
|
||||
auto load() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
auto run() -> void;
|
||||
auto runtosave() -> void;
|
||||
auto runthreadtosave() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
auto serialize_all(serializer&) -> void;
|
||||
auto serialize_init() -> void;
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
struct Information {
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
uint serialize_size;
|
||||
bool _loaded = false;
|
||||
uint _serializeSize = 0;
|
||||
};
|
||||
|
||||
extern BIOS bios;
|
||||
|
@@ -1,69 +0,0 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[240 * 160];
|
||||
paletteStandard = new uint32[1 << 15];
|
||||
paletteEmulation = new uint32[1 << 15];
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
delete[] output;
|
||||
delete[] paletteStandard;
|
||||
delete[] paletteEmulation;
|
||||
}
|
||||
|
||||
auto Video::power() -> void {
|
||||
memory::fill(output, 240 * 160 * sizeof(uint32));
|
||||
|
||||
for(auto color : range(1 << 15)) {
|
||||
uint B = (uint5)(color >> 10);
|
||||
uint G = (uint5)(color >> 5);
|
||||
uint R = (uint5)(color >> 0);
|
||||
|
||||
{ uint b = image::normalize(B, 5, 8);
|
||||
uint g = image::normalize(G, 5, 8);
|
||||
uint r = image::normalize(R, 5, 8);
|
||||
paletteStandard[color] = (255 << 24) | (r << 16) | (g << 8) | (b << 0);
|
||||
}
|
||||
|
||||
{ double lcdGamma = 4.0, outGamma = 2.2;
|
||||
double lb = pow(B / 31.0, lcdGamma);
|
||||
double lg = pow(G / 31.0, lcdGamma);
|
||||
double lr = pow(R / 31.0, lcdGamma);
|
||||
uint b = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
uint g = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
uint r = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
paletteEmulation[color] = (255 << 24) | ((r >> 8) << 16) | ((g >> 8) << 8) | ((b >> 8) << 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
for(uint y = 0; y < 160; y++) {
|
||||
auto source = ppu.output + y * 240;
|
||||
auto target = output + y * 240;
|
||||
|
||||
if(settings.blurEmulation) {
|
||||
for(uint x = 0; x < 240; x++) {
|
||||
auto a = palette[*source++];
|
||||
auto b = *target;
|
||||
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
}
|
||||
} else {
|
||||
for(uint x = 0; x < 240; x++) {
|
||||
auto color = palette[*source++];
|
||||
*target++ = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface->videoRefresh(output, 240 * sizeof(uint32), 240, 160);
|
||||
}
|
||||
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
struct Video {
|
||||
Video();
|
||||
~Video();
|
||||
|
||||
auto power() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
uint32* output = nullptr;
|
||||
uint32* paletteStandard = nullptr;
|
||||
uint32* paletteEmulation = nullptr;
|
||||
};
|
||||
|
||||
extern Video video;
|
@@ -1,20 +1,21 @@
|
||||
processor_objects :=
|
||||
processor_objects += $(if $(findstring arm,$(processors)),processor-arm)
|
||||
processor_objects += $(if $(findstring gsu,$(processors)),processor-gsu)
|
||||
processor_objects += $(if $(findstring hg51b,$(processors)),processor-hg51b)
|
||||
processor_objects += $(if $(findstring lr35902,$(processors)),processor-lr35902)
|
||||
processor_objects += $(if $(findstring r6502,$(processors)),processor-r6502)
|
||||
processor_objects += $(if $(findstring r65816,$(processors)),processor-r65816)
|
||||
processor_objects += $(if $(findstring spc700,$(processors)),processor-spc700)
|
||||
processor_objects += $(if $(findstring upd96050,$(processors)),processor-upd96050)
|
||||
objects += $(processor_objects)
|
||||
processors := $(call unique,$(processors))
|
||||
|
||||
processor := processor
|
||||
obj/processor-arm.o: $(processor)/arm/arm.cpp $(call rwildcard,$(processor)/arm)
|
||||
obj/processor-gsu.o: $(processor)/gsu/gsu.cpp $(call rwildcard,$(processor)/gsu)
|
||||
obj/processor-hg51b.o: $(processor)/hg51b/hg51b.cpp $(call rwildcard,$(processor)/hg51b)
|
||||
obj/processor-lr35902.o: $(processor)/lr35902/lr35902.cpp $(call rwildcard,$(processor)/lr35902)
|
||||
obj/processor-r6502.o: $(processor)/r6502/r6502.cpp $(call rwildcard,$(processor)/r6502)
|
||||
obj/processor-r65816.o: $(processor)/r65816/r65816.cpp $(call rwildcard,$(processor)/r65816)
|
||||
obj/processor-spc700.o: $(processor)/spc700/spc700.cpp $(call rwildcard,$(processor)/spc700)
|
||||
obj/processor-upd96050.o: $(processor)/upd96050/upd96050.cpp $(call rwildcard,$(processor)/upd96050)
|
||||
objects += $(if $(findstring arm,$(processors)),processor-arm)
|
||||
objects += $(if $(findstring gsu,$(processors)),processor-gsu)
|
||||
objects += $(if $(findstring hg51b,$(processors)),processor-hg51b)
|
||||
objects += $(if $(findstring lr35902,$(processors)),processor-lr35902)
|
||||
objects += $(if $(findstring r6502,$(processors)),processor-r6502)
|
||||
objects += $(if $(findstring r65816,$(processors)),processor-r65816)
|
||||
objects += $(if $(findstring spc700,$(processors)),processor-spc700)
|
||||
objects += $(if $(findstring upd96050,$(processors)),processor-upd96050)
|
||||
objects += $(if $(findstring v30mz,$(processors)),processor-v30mz)
|
||||
|
||||
obj/processor-arm.o: processor/arm/arm.cpp $(call rwildcard,processor/arm)
|
||||
obj/processor-gsu.o: processor/gsu/gsu.cpp $(call rwildcard,processor/gsu)
|
||||
obj/processor-hg51b.o: processor/hg51b/hg51b.cpp $(call rwildcard,processor/hg51b)
|
||||
obj/processor-lr35902.o: processor/lr35902/lr35902.cpp $(call rwildcard,processor/lr35902)
|
||||
obj/processor-r6502.o: processor/r6502/r6502.cpp $(call rwildcard,processor/r6502)
|
||||
obj/processor-r65816.o: processor/r65816/r65816.cpp $(call rwildcard,processor/r65816)
|
||||
obj/processor-spc700.o: processor/spc700/spc700.cpp $(call rwildcard,processor/spc700)
|
||||
obj/processor-upd96050.o: processor/upd96050/upd96050.cpp $(call rwildcard,processor/upd96050)
|
||||
obj/processor-v30mz.o: processor/v30mz/v30mz.cpp $(call rwildcard,processor/v30mz)
|
||||
|
@@ -44,12 +44,12 @@ auto ARM::load(unsigned mode, uint32 addr) -> uint32 {
|
||||
|
||||
if(mode & Half) {
|
||||
addr &= 1;
|
||||
word = mode & Signed ? (int16)word : (uint16)word;
|
||||
word = mode & Signed ? (int16_t)word : (uint16_t)word;
|
||||
}
|
||||
|
||||
if(mode & Byte) {
|
||||
addr &= 0;
|
||||
word = mode & Signed ? (int8)word : (uint8)word;
|
||||
word = mode & Signed ? (int8_t)word : (uint8_t)word;
|
||||
}
|
||||
|
||||
if(mode & Signed) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user