mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-08-28 22:49:59 +02:00
Update to v093 release.
byuu says: Changelog: - added Cocoa target: higan can now be compiled for OS X Lion [Cydrak, byuu] - SNES/accuracy profile hires color blending improvements - fixes Marvelous text [AWJ] - fixed a slight bug in SNES/SA-1 VBR support caused by a typo - added support for multi-pass shaders that can load external textures (requires OpenGL 3.2+) - added game library path (used by ananke->Import Game) to Settings->Advanced - system profiles, shaders and cheats database can be stored in "all users" shared folders now (eg /usr/share on Linux) - all configuration files are in BML format now, instead of XML (much easier to read and edit this way) - main window supports drag-and-drop of game folders (but not game files / ZIP archives) - audio buffer clears when entering a modal loop on Windows (prevents audio repetition with DirectSound driver) - a substantial amount of code clean-up (probably the biggest refactoring to date) One highly desired target for this release was to default to the optimal drivers instead of the safest drivers, but because AMD drivers don't seem to like my OpenGL 3.2 driver, I've decided to postpone that. AMD has too big a market share. Hopefully with v093 officially released, we can get some public input on what AMD doesn't like.
This commit is contained in:
14
gba/Makefile
Normal file
14
gba/Makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
gba_objects := gba-memory gba-interface gba-scheduler gba-system
|
||||
gba_objects += gba-video gba-cartridge
|
||||
gba_objects += gba-cpu gba-ppu gba-apu
|
||||
objects += $(gba_objects)
|
||||
|
||||
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-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)
|
98
gba/apu/apu.cpp
Normal file
98
gba/apu/apu.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "registers.cpp"
|
||||
#include "mmio.cpp"
|
||||
#include "square.cpp"
|
||||
#include "square1.cpp"
|
||||
#include "square2.cpp"
|
||||
#include "wave.cpp"
|
||||
#include "noise.cpp"
|
||||
#include "sequencer.cpp"
|
||||
#include "fifo.cpp"
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
void APU::Enter() {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
apu.main();
|
||||
}
|
||||
}
|
||||
|
||||
void APU::main() {
|
||||
for(unsigned n = 0; n < 128; n++) {
|
||||
runsequencer();
|
||||
}
|
||||
|
||||
signed lsample = regs.bias.level - 0x0200;
|
||||
signed rsample = regs.bias.level - 0x0200;
|
||||
|
||||
//(4-bit x 4 -> 6-bit) + 3-bit volume = 9-bit output
|
||||
if(sequencer.masterenable) {
|
||||
signed lsequence = 0;
|
||||
if(sequencer.lenable[0]) lsequence += square1.output;
|
||||
if(sequencer.lenable[1]) lsequence += square2.output;
|
||||
if(sequencer.lenable[2]) lsequence += wave.output;
|
||||
if(sequencer.lenable[3]) lsequence += noise.output;
|
||||
|
||||
signed rsequence = 0;
|
||||
if(sequencer.renable[0]) rsequence += square1.output;
|
||||
if(sequencer.renable[1]) rsequence += square2.output;
|
||||
if(sequencer.renable[2]) rsequence += wave.output;
|
||||
if(sequencer.renable[3]) rsequence += noise.output;
|
||||
|
||||
if(sequencer.volume < 3) {
|
||||
lsample += lsequence * (sequencer.lvolume + 1) >> (2 - sequencer.volume);
|
||||
rsample += rsequence * (sequencer.rvolume + 1) >> (2 - sequencer.volume);
|
||||
}
|
||||
}
|
||||
|
||||
//(8-bit x 2 -> 7-bit) + 1-bit volume = 10-bit output
|
||||
signed fifo0 = fifo[0].output + (1 << fifo[0].volume);
|
||||
signed fifo1 = fifo[1].output + (1 << fifo[1].volume);
|
||||
|
||||
if(fifo[0].lenable) lsample += fifo0;
|
||||
if(fifo[1].lenable) lsample += fifo1;
|
||||
|
||||
if(fifo[0].renable) rsample += fifo0;
|
||||
if(fifo[1].renable) rsample += fifo1;
|
||||
|
||||
lsample = sclamp<10>(lsample);
|
||||
rsample = sclamp<10>(rsample);
|
||||
|
||||
if(regs.bias.amplitude == 1) lsample &= ~3, rsample &= ~3;
|
||||
if(regs.bias.amplitude == 2) lsample &= ~7, rsample &= ~7;
|
||||
if(regs.bias.amplitude == 3) lsample &= ~15, rsample &= ~15;
|
||||
|
||||
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
|
||||
interface->audioSample(sclamp<16>(lsample << 7), sclamp<16>(rsample << 7)); //should be <<5, use <<7 for added volume
|
||||
step(512);
|
||||
}
|
||||
|
||||
void APU::step(unsigned clocks) {
|
||||
clock += clocks;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
void APU::power() {
|
||||
create(APU::Enter, 16777216);
|
||||
|
||||
square1.power();
|
||||
square2.power();
|
||||
wave.power();
|
||||
noise.power();
|
||||
sequencer.power();
|
||||
fifo[0].power();
|
||||
fifo[1].power();
|
||||
|
||||
regs.bias = 0x0200;
|
||||
|
||||
for(unsigned n = 0x060; n <= 0x0a7; n++) bus.mmio[n] = this;
|
||||
}
|
||||
|
||||
}
|
17
gba/apu/apu.hpp
Normal file
17
gba/apu/apu.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
struct APU : Thread, MMIO {
|
||||
#include "registers.hpp"
|
||||
|
||||
static void Enter();
|
||||
void main();
|
||||
void step(unsigned clocks);
|
||||
|
||||
uint8 read(uint32 addr);
|
||||
void write(uint32 addr, uint8 byte);
|
||||
void power();
|
||||
|
||||
void runsequencer();
|
||||
|
||||
void serialize(serializer&);
|
||||
};
|
||||
|
||||
extern APU apu;
|
28
gba/apu/fifo.cpp
Normal file
28
gba/apu/fifo.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
void APU::FIFO::read() {
|
||||
if(size == 0) return;
|
||||
size--;
|
||||
output = sample[rdoffset++];
|
||||
}
|
||||
|
||||
void APU::FIFO::write(int8 byte) {
|
||||
if(size == 32) return;
|
||||
size++;
|
||||
sample[wroffset++] = byte;
|
||||
}
|
||||
|
||||
void APU::FIFO::reset() {
|
||||
for(auto& byte : sample) byte = 0;
|
||||
output = 0;
|
||||
|
||||
rdoffset = 0;
|
||||
wroffset = 0;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
void APU::FIFO::power() {
|
||||
reset();
|
||||
|
||||
lenable = 0;
|
||||
renable = 0;
|
||||
timer = 0;
|
||||
}
|
209
gba/apu/mmio.cpp
Normal file
209
gba/apu/mmio.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
uint8 APU::read(uint32 addr) {
|
||||
switch(addr) {
|
||||
|
||||
//NR10
|
||||
case 0x04000060: return square1.read(0);
|
||||
case 0x04000061: return 0u;
|
||||
|
||||
//NR11 + NR12
|
||||
case 0x04000062: return square1.read(1);
|
||||
case 0x04000063: return square1.read(2);
|
||||
|
||||
//NR13 + NR14
|
||||
case 0x04000064: return square1.read(3);
|
||||
case 0x04000065: return square1.read(4);
|
||||
|
||||
//NR21 + NR22
|
||||
case 0x04000068: return square2.read(1);
|
||||
case 0x04000069: return square2.read(2);
|
||||
|
||||
//NR23 + NR24
|
||||
case 0x0400006c: return square2.read(3);
|
||||
case 0x0400006d: return square2.read(4);
|
||||
|
||||
//NR30
|
||||
case 0x04000070: return wave.read(0);
|
||||
case 0x04000071: return 0u;
|
||||
|
||||
//NR31 + NR32
|
||||
case 0x04000072: return wave.read(1);
|
||||
case 0x04000073: return wave.read(2);
|
||||
|
||||
//NR33 + NR34
|
||||
case 0x04000074: return wave.read(3);
|
||||
case 0x04000075: return wave.read(4);
|
||||
|
||||
//NR41 + NR42
|
||||
case 0x04000078: return noise.read(1);
|
||||
case 0x04000079: return noise.read(2);
|
||||
|
||||
//NR43 + NR44
|
||||
case 0x0400007c: return noise.read(3);
|
||||
case 0x0400007d: return noise.read(4);
|
||||
|
||||
//NR50 + NR51
|
||||
case 0x04000080: return sequencer.read(0);
|
||||
case 0x04000081: return sequencer.read(1);
|
||||
|
||||
//NR52
|
||||
case 0x04000084: return sequencer.read(2);
|
||||
case 0x04000085: return 0u;
|
||||
|
||||
//SOUNDBIAS
|
||||
case 0x04000088: return regs.bias >> 0;
|
||||
case 0x04000089: return regs.bias >> 8;
|
||||
|
||||
//WAVE_RAM0_L
|
||||
case 0x04000090: return wave.readram( 0);
|
||||
case 0x04000091: return wave.readram( 1);
|
||||
|
||||
//WAVE_RAM0_H
|
||||
case 0x04000092: return wave.readram( 2);
|
||||
case 0x04000093: return wave.readram( 3);
|
||||
|
||||
//WAVE_RAM1_L
|
||||
case 0x04000094: return wave.readram( 4);
|
||||
case 0x04000095: return wave.readram( 5);
|
||||
|
||||
//WAVE_RAM1_H
|
||||
case 0x04000096: return wave.readram( 6);
|
||||
case 0x04000097: return wave.readram( 7);
|
||||
|
||||
//WAVE_RAM2_L
|
||||
case 0x04000098: return wave.readram( 8);
|
||||
case 0x04000099: return wave.readram( 9);
|
||||
|
||||
//WAVE_RAM2_H
|
||||
case 0x0400009a: return wave.readram(10);
|
||||
case 0x0400009b: return wave.readram(11);
|
||||
|
||||
//WAVE_RAM3_L
|
||||
case 0x0400009c: return wave.readram(12);
|
||||
case 0x0400009d: return wave.readram(13);
|
||||
|
||||
//WAVE_RAM3_H
|
||||
case 0x0400009e: return wave.readram(14);
|
||||
case 0x0400009f: return wave.readram(15);
|
||||
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
|
||||
void APU::write(uint32 addr, uint8 byte) {
|
||||
switch(addr) {
|
||||
|
||||
//NR10
|
||||
case 0x04000060: return square1.write(0, byte);
|
||||
case 0x04000061: return;
|
||||
|
||||
//NR11 + NR12
|
||||
case 0x04000062: return square1.write(1, byte);
|
||||
case 0x04000063: return square1.write(2, byte);
|
||||
|
||||
//NR13 + NR14
|
||||
case 0x04000064: return square1.write(3, byte);
|
||||
case 0x04000065: return square1.write(4, byte);
|
||||
|
||||
//NR21 + NR22
|
||||
case 0x04000068: return square2.write(1, byte);
|
||||
case 0x04000069: return square2.write(2, byte);
|
||||
|
||||
//NR23 + NR24
|
||||
case 0x0400006c: return square2.write(3, byte);
|
||||
case 0x0400006d: return square2.write(4, byte);
|
||||
|
||||
//NR30
|
||||
case 0x04000070: return wave.write(0, byte);
|
||||
case 0x04000071: return;
|
||||
|
||||
//NR31 + NR32
|
||||
case 0x04000072: return wave.write(1, byte);
|
||||
case 0x04000073: return wave.write(2, byte);
|
||||
|
||||
//NR33 + NR34
|
||||
case 0x04000074: return wave.write(3, byte);
|
||||
case 0x04000075: return wave.write(4, byte);
|
||||
|
||||
//NR41 + NR42
|
||||
case 0x04000078: return noise.write(1, byte);
|
||||
case 0x04000079: return noise.write(2, byte);
|
||||
|
||||
//NR43 + NR44
|
||||
case 0x0400007c: return noise.write(3, byte);
|
||||
case 0x0400007d: return noise.write(4, byte);
|
||||
|
||||
//NR50 + NR51
|
||||
case 0x04000080: return sequencer.write(0, byte);
|
||||
case 0x04000081: return sequencer.write(1, byte);
|
||||
|
||||
//SOUND_CNT_H
|
||||
case 0x04000082:
|
||||
sequencer.volume = byte >> 0;
|
||||
fifo[0].volume = byte >> 2;
|
||||
fifo[1].volume = byte >> 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();
|
||||
return;
|
||||
|
||||
//NR52
|
||||
case 0x04000084: return sequencer.write(2, byte);
|
||||
case 0x04000085: return;
|
||||
|
||||
//SOUNDBIAS
|
||||
case 0x04000088: regs.bias = (regs.bias & 0xff00) | (byte << 0); return;
|
||||
case 0x04000089: regs.bias = (regs.bias & 0x00ff) | (byte << 8); return;
|
||||
|
||||
//WAVE_RAM0_L
|
||||
case 0x04000090: return wave.writeram( 0, byte);
|
||||
case 0x04000091: return wave.writeram( 1, byte);
|
||||
|
||||
//WAVE_RAM0_H
|
||||
case 0x04000092: return wave.writeram( 2, byte);
|
||||
case 0x04000093: return wave.writeram( 3, byte);
|
||||
|
||||
//WAVE_RAM1_L
|
||||
case 0x04000094: return wave.writeram( 4, byte);
|
||||
case 0x04000095: return wave.writeram( 5, byte);
|
||||
|
||||
//WAVE_RAM1_H
|
||||
case 0x04000096: return wave.writeram( 6, byte);
|
||||
case 0x04000097: return wave.writeram( 7, byte);
|
||||
|
||||
//WAVE_RAM2_L
|
||||
case 0x04000098: return wave.writeram( 8, byte);
|
||||
case 0x04000099: return wave.writeram( 9, byte);
|
||||
|
||||
//WAVE_RAM2_H
|
||||
case 0x0400009a: return wave.writeram(10, byte);
|
||||
case 0x0400009b: return wave.writeram(11, byte);
|
||||
|
||||
//WAVE_RAM3_L
|
||||
case 0x0400009c: return wave.writeram(12, byte);
|
||||
case 0x0400009d: return wave.writeram(13, byte);
|
||||
|
||||
//WAVE_RAM3_H
|
||||
case 0x0400009e: return wave.writeram(14, byte);
|
||||
case 0x0400009f: return wave.writeram(15, byte);
|
||||
|
||||
//FIFO_A_L
|
||||
//FIFO_A_H
|
||||
case 0x040000a0: case 0x040000a1:
|
||||
case 0x040000a2: case 0x040000a3:
|
||||
return fifo[0].write(byte);
|
||||
|
||||
//FIFO_B_L
|
||||
//FIFO_B_H
|
||||
case 0x040000a4: case 0x040000a5:
|
||||
case 0x040000a6: case 0x040000a7:
|
||||
return fifo[1].write(byte);
|
||||
}
|
||||
}
|
93
gba/apu/noise.cpp
Normal file
93
gba/apu/noise.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
unsigned APU::Noise::divider() const {
|
||||
if(divisor == 0) return 8;
|
||||
return divisor * 16;
|
||||
}
|
||||
|
||||
void APU::Noise::run() {
|
||||
if(period && --period == 0) {
|
||||
period = divider() << frequency;
|
||||
if(frequency < 14) {
|
||||
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
|
||||
lfsr = (lfsr >> 1) ^ (bit << (narrowlfsr ? 6 : 14));
|
||||
}
|
||||
}
|
||||
|
||||
output = volume;
|
||||
if(enable == false || (lfsr & 1)) output = 0;
|
||||
}
|
||||
|
||||
void APU::Noise::clocklength() {
|
||||
if(enable && counter) {
|
||||
if(++length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Noise::clockenvelope() {
|
||||
if(enable && envelope.frequency && --envelope.period == 0) {
|
||||
envelope.period = envelope.frequency;
|
||||
if(envelope.direction == 0 && volume > 0) volume--;
|
||||
if(envelope.direction == 1 && volume < 15) volume++;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 APU::Noise::read(unsigned addr) const {
|
||||
switch(addr) {
|
||||
case 1: return (length << 0);
|
||||
case 2: return (envelope.frequency << 0) | (envelope.direction << 3) | (envelope.volume << 4);
|
||||
case 3: return (divisor << 0) | (narrowlfsr << 3) | (frequency << 4);
|
||||
case 4: return (counter << 6) | (initialize << 7);
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Noise::write(unsigned addr, uint8 byte) {
|
||||
switch(addr) {
|
||||
case 1: //NR41
|
||||
length = byte >> 0;
|
||||
break;
|
||||
|
||||
case 2: //NR42
|
||||
envelope.frequency = byte >> 0;
|
||||
envelope.direction = byte >> 3;
|
||||
envelope.volume = byte >> 4;
|
||||
if(envelope.dacenable() == false) enable = false;
|
||||
break;
|
||||
|
||||
case 3: //NR43
|
||||
divisor = byte >> 0;
|
||||
narrowlfsr = byte >> 3;
|
||||
frequency = byte >> 4;
|
||||
period = divider() << frequency;
|
||||
break;
|
||||
|
||||
case 4: //NR44
|
||||
counter = byte >> 6;
|
||||
initialize = byte >> 7;
|
||||
|
||||
if(initialize) {
|
||||
enable = envelope.dacenable();
|
||||
lfsr = ~0u;
|
||||
envelope.period = envelope.frequency;
|
||||
volume = envelope.volume;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Noise::power() {
|
||||
envelope.frequency = 0;
|
||||
envelope.direction = 0;
|
||||
envelope.volume = 0;
|
||||
envelope.period = 0;
|
||||
length = 0;
|
||||
divisor = 0;
|
||||
narrowlfsr = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
initialize = 0;
|
||||
enable = 0;
|
||||
lfsr = 0;
|
||||
output = 0;
|
||||
period = 0;
|
||||
volume = 0;
|
||||
}
|
12
gba/apu/registers.cpp
Normal file
12
gba/apu/registers.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
APU::Registers::SoundBias::operator uint16() const {
|
||||
return (
|
||||
(level << 0)
|
||||
| (amplitude << 14)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 APU::Registers::SoundBias::operator=(uint16 source) {
|
||||
level = (source >> 0) & 1023;
|
||||
amplitude = (source >> 14) & 3;
|
||||
return operator uint16();
|
||||
}
|
158
gba/apu/registers.hpp
Normal file
158
gba/apu/registers.hpp
Normal file
@@ -0,0 +1,158 @@
|
||||
struct Registers {
|
||||
struct SoundBias {
|
||||
uint10 level;
|
||||
uint2 amplitude;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
SoundBias& operator=(const SoundBias&) = delete;
|
||||
} bias;
|
||||
|
||||
unsigned clock;
|
||||
} regs;
|
||||
|
||||
struct Sweep {
|
||||
uint3 shift;
|
||||
uint1 direction;
|
||||
uint3 frequency;
|
||||
|
||||
uint1 enable;
|
||||
uint1 negate;
|
||||
uint3 period;
|
||||
};
|
||||
|
||||
struct Envelope {
|
||||
uint3 frequency;
|
||||
uint1 direction;
|
||||
uint4 volume;
|
||||
|
||||
uint3 period;
|
||||
|
||||
inline bool dacenable() const { return volume || direction; }
|
||||
};
|
||||
|
||||
struct Square {
|
||||
Envelope envelope;
|
||||
uint1 enable;
|
||||
uint6 length;
|
||||
uint2 duty;
|
||||
uint11 frequency;
|
||||
uint1 counter;
|
||||
uint1 initialize;
|
||||
|
||||
signed shadowfrequency;
|
||||
uint1 signal;
|
||||
uint4 output;
|
||||
unsigned period;
|
||||
uint3 phase;
|
||||
uint4 volume;
|
||||
|
||||
void run();
|
||||
void clocklength();
|
||||
void clockenvelope();
|
||||
};
|
||||
|
||||
struct Square1 : Square {
|
||||
Sweep sweep;
|
||||
|
||||
void runsweep(bool update);
|
||||
void clocksweep();
|
||||
uint8 read(unsigned addr) const;
|
||||
void write(unsigned addr, uint8 byte);
|
||||
void power();
|
||||
} square1;
|
||||
|
||||
struct Square2 : Square {
|
||||
uint8 read(unsigned addr) const;
|
||||
void write(unsigned addr, uint8 byte);
|
||||
void power();
|
||||
} square2;
|
||||
|
||||
struct Wave {
|
||||
uint1 mode;
|
||||
uint1 bank;
|
||||
uint1 dacenable;
|
||||
uint8 length;
|
||||
uint3 volume;
|
||||
uint11 frequency;
|
||||
uint1 counter;
|
||||
uint1 initialize;
|
||||
uint4 pattern[32];
|
||||
|
||||
uint1 enable;
|
||||
uint4 output;
|
||||
uint4 patternaddr;
|
||||
uint1 patternbank;
|
||||
uint4 patternsample;
|
||||
unsigned period;
|
||||
|
||||
void run();
|
||||
void clocklength();
|
||||
uint8 read(unsigned addr) const;
|
||||
void write(unsigned addr, uint8 byte);
|
||||
uint8 readram(unsigned addr) const;
|
||||
void writeram(unsigned addr, uint8 byte);
|
||||
void power();
|
||||
} wave;
|
||||
|
||||
struct Noise {
|
||||
Envelope envelope;
|
||||
uint6 length;
|
||||
uint3 divisor;
|
||||
uint1 narrowlfsr;
|
||||
uint4 frequency;
|
||||
uint1 counter;
|
||||
uint1 initialize;
|
||||
|
||||
uint1 enable;
|
||||
uint15 lfsr;
|
||||
uint4 output;
|
||||
unsigned period;
|
||||
uint4 volume;
|
||||
|
||||
unsigned divider() const;
|
||||
void run();
|
||||
void clocklength();
|
||||
void clockenvelope();
|
||||
uint8 read(unsigned addr) const;
|
||||
void write(unsigned addr, uint8 byte);
|
||||
void power();
|
||||
} noise;
|
||||
|
||||
struct Sequencer {
|
||||
uint2 volume;
|
||||
uint3 lvolume;
|
||||
uint3 rvolume;
|
||||
uint1 lenable[4];
|
||||
uint1 renable[4];
|
||||
uint1 enable[4];
|
||||
uint1 masterenable;
|
||||
|
||||
uint13 base;
|
||||
uint3 step;
|
||||
int16 lsample;
|
||||
int16 rsample;
|
||||
|
||||
uint8 read(unsigned addr) const;
|
||||
void write(unsigned addr, uint8 byte);
|
||||
void power();
|
||||
} sequencer;
|
||||
|
||||
struct FIFO {
|
||||
int8 sample[32];
|
||||
int8 output;
|
||||
|
||||
uint5 rdoffset;
|
||||
uint5 wroffset;
|
||||
uint6 size;
|
||||
|
||||
uint1 volume; //0 = 50%, 1 = 100%
|
||||
uint1 lenable;
|
||||
uint1 renable;
|
||||
uint1 timer;
|
||||
|
||||
void read();
|
||||
void write(int8 byte);
|
||||
void reset();
|
||||
void power();
|
||||
} fifo[2];
|
91
gba/apu/sequencer.cpp
Normal file
91
gba/apu/sequencer.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
void APU::runsequencer() {
|
||||
auto& r = sequencer;
|
||||
|
||||
if(r.base == 0) { //512hz
|
||||
if(r.step == 0 || r.step == 2 || r.step == 4 || r.step == 6) { //256hz
|
||||
square1.clocklength();
|
||||
square2.clocklength();
|
||||
wave.clocklength();
|
||||
noise.clocklength();
|
||||
}
|
||||
if(r.step == 2 || r.step == 6) { //128hz
|
||||
square1.clocksweep();
|
||||
}
|
||||
if(r.step == 7) { //64hz
|
||||
square1.clockenvelope();
|
||||
square2.clockenvelope();
|
||||
noise.clockenvelope();
|
||||
}
|
||||
r.step++;
|
||||
}
|
||||
r.base++;
|
||||
|
||||
if(r.enable[0]) square1.run();
|
||||
if(r.enable[1]) square2.run();
|
||||
if(r.enable[2]) wave.run();
|
||||
if(r.enable[3]) noise.run();
|
||||
}
|
||||
|
||||
uint8 APU::Sequencer::read(unsigned addr) const {
|
||||
switch(addr) {
|
||||
case 0: return (rvolume << 0) | (lvolume << 4);
|
||||
case 1: return (
|
||||
(renable[0] << 0)
|
||||
| (renable[1] << 1)
|
||||
| (renable[2] << 2)
|
||||
| (renable[3] << 3)
|
||||
| (lenable[0] << 4)
|
||||
| (lenable[1] << 5)
|
||||
| (lenable[2] << 6)
|
||||
| (lenable[3] << 7)
|
||||
);
|
||||
case 2: return (
|
||||
(enable[0] << 0)
|
||||
| (enable[1] << 1)
|
||||
| (enable[2] << 2)
|
||||
| (enable[3] << 3)
|
||||
| (masterenable << 7)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Sequencer::write(unsigned addr, uint8 byte) {
|
||||
switch(addr) {
|
||||
case 0: //NR50
|
||||
rvolume = byte >> 0;
|
||||
lvolume = byte >> 4;
|
||||
break;
|
||||
|
||||
case 1: //NR51
|
||||
renable[0] = byte >> 0;
|
||||
renable[1] = byte >> 1;
|
||||
renable[2] = byte >> 2;
|
||||
renable[3] = byte >> 3;
|
||||
lenable[0] = byte >> 4;
|
||||
lenable[1] = byte >> 5;
|
||||
lenable[2] = byte >> 6;
|
||||
lenable[3] = byte >> 7;
|
||||
break;
|
||||
|
||||
case 2: //NR52
|
||||
enable[0] = byte >> 0;
|
||||
enable[1] = byte >> 1;
|
||||
enable[2] = byte >> 2;
|
||||
enable[3] = byte >> 3;
|
||||
masterenable = byte >> 7;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Sequencer::power() {
|
||||
lvolume = 0;
|
||||
rvolume = 0;
|
||||
for(auto& n : lenable) n = 0;
|
||||
for(auto& n : renable) n = 0;
|
||||
for(auto& n : enable) n = 0;
|
||||
masterenable = 0;
|
||||
base = 0;
|
||||
step = 0;
|
||||
lsample = 0;
|
||||
rsample = 0;
|
||||
}
|
107
gba/apu/serialization.cpp
Normal file
107
gba/apu/serialization.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
void APU::serialize(serializer& s) {
|
||||
Thread::serialize(s);
|
||||
|
||||
s.integer(regs.bias.level);
|
||||
s.integer(regs.bias.amplitude);
|
||||
s.integer(regs.clock);
|
||||
|
||||
s.integer(square1.sweep.shift);
|
||||
s.integer(square1.sweep.direction);
|
||||
s.integer(square1.sweep.frequency);
|
||||
s.integer(square1.sweep.enable);
|
||||
s.integer(square1.sweep.negate);
|
||||
s.integer(square1.sweep.period);
|
||||
|
||||
s.integer(square1.envelope.frequency);
|
||||
s.integer(square1.envelope.direction);
|
||||
s.integer(square1.envelope.volume);
|
||||
s.integer(square1.envelope.period);
|
||||
|
||||
s.integer(square1.enable);
|
||||
s.integer(square1.length);
|
||||
s.integer(square1.duty);
|
||||
s.integer(square1.frequency);
|
||||
s.integer(square1.counter);
|
||||
s.integer(square1.initialize);
|
||||
s.integer(square1.shadowfrequency);
|
||||
s.integer(square1.signal);
|
||||
s.integer(square1.output);
|
||||
s.integer(square1.period);
|
||||
s.integer(square1.phase);
|
||||
s.integer(square1.volume);
|
||||
|
||||
s.integer(square2.envelope.frequency);
|
||||
s.integer(square2.envelope.direction);
|
||||
s.integer(square2.envelope.volume);
|
||||
s.integer(square2.envelope.period);
|
||||
|
||||
s.integer(square2.enable);
|
||||
s.integer(square2.length);
|
||||
s.integer(square2.duty);
|
||||
s.integer(square2.frequency);
|
||||
s.integer(square2.counter);
|
||||
s.integer(square2.initialize);
|
||||
s.integer(square2.shadowfrequency);
|
||||
s.integer(square2.signal);
|
||||
s.integer(square2.output);
|
||||
s.integer(square2.period);
|
||||
s.integer(square2.phase);
|
||||
s.integer(square2.volume);
|
||||
|
||||
s.integer(wave.mode);
|
||||
s.integer(wave.bank);
|
||||
s.integer(wave.dacenable);
|
||||
s.integer(wave.length);
|
||||
s.integer(wave.volume);
|
||||
s.integer(wave.frequency);
|
||||
s.integer(wave.counter);
|
||||
s.integer(wave.initialize);
|
||||
for(auto& value : wave.pattern) s.integer(value);
|
||||
s.integer(wave.enable);
|
||||
s.integer(wave.output);
|
||||
s.integer(wave.patternaddr);
|
||||
s.integer(wave.patternbank);
|
||||
s.integer(wave.patternsample);
|
||||
s.integer(wave.period);
|
||||
|
||||
s.integer(noise.envelope.frequency);
|
||||
s.integer(noise.envelope.direction);
|
||||
s.integer(noise.envelope.volume);
|
||||
s.integer(noise.envelope.period);
|
||||
|
||||
s.integer(noise.length);
|
||||
s.integer(noise.divisor);
|
||||
s.integer(noise.narrowlfsr);
|
||||
s.integer(noise.frequency);
|
||||
s.integer(noise.counter);
|
||||
s.integer(noise.initialize);
|
||||
s.integer(noise.enable);
|
||||
s.integer(noise.lfsr);
|
||||
s.integer(noise.output);
|
||||
s.integer(noise.period);
|
||||
s.integer(noise.volume);
|
||||
|
||||
s.integer(sequencer.volume);
|
||||
s.integer(sequencer.lvolume);
|
||||
s.integer(sequencer.rvolume);
|
||||
for(auto& flag : sequencer.lenable) s.integer(flag);
|
||||
for(auto& flag : sequencer.renable) s.integer(flag);
|
||||
for(auto& flag : sequencer.enable) s.integer(flag);
|
||||
s.integer(sequencer.masterenable);
|
||||
s.integer(sequencer.base);
|
||||
s.integer(sequencer.step);
|
||||
s.integer(sequencer.lsample);
|
||||
s.integer(sequencer.rsample);
|
||||
|
||||
for(auto& f : fifo) {
|
||||
for(auto& value : f.sample) s.integer(value);
|
||||
s.integer(f.output);
|
||||
s.integer(f.rdoffset);
|
||||
s.integer(f.wroffset);
|
||||
s.integer(f.size);
|
||||
s.integer(f.volume);
|
||||
s.integer(f.lenable);
|
||||
s.integer(f.renable);
|
||||
s.integer(f.timer);
|
||||
}
|
||||
}
|
30
gba/apu/square.cpp
Normal file
30
gba/apu/square.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
void APU::Square::run() {
|
||||
if(period && --period == 0) {
|
||||
period = 4 * (2048 - frequency);
|
||||
phase++;
|
||||
switch(duty) {
|
||||
case 0: signal = (phase == 6); break; //_____-_
|
||||
case 1: signal = (phase >= 6); break; //______--
|
||||
case 2: signal = (phase >= 4); break; //____----
|
||||
case 3: signal = (phase <= 5); break; //------__
|
||||
}
|
||||
}
|
||||
|
||||
uint4 sample = volume;
|
||||
if(enable == false || signal == false) sample = 0;
|
||||
output = sample;
|
||||
}
|
||||
|
||||
void APU::Square::clocklength() {
|
||||
if(enable && counter) {
|
||||
if(++length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Square::clockenvelope() {
|
||||
if(enable && envelope.frequency && --envelope.period == 0) {
|
||||
envelope.period = envelope.frequency;
|
||||
if(envelope.direction == 0 && volume > 0) volume--;
|
||||
if(envelope.direction == 1 && volume < 15) volume++;
|
||||
}
|
||||
}
|
104
gba/apu/square1.cpp
Normal file
104
gba/apu/square1.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
void APU::Square1::runsweep(bool update) {
|
||||
if(sweep.enable == false) return;
|
||||
|
||||
sweep.negate = sweep.direction;
|
||||
unsigned delta = shadowfrequency >> sweep.shift;
|
||||
signed updatefrequency = shadowfrequency + (sweep.negate ? -delta : delta);
|
||||
|
||||
if(updatefrequency > 2047) {
|
||||
enable = false;
|
||||
} else if(sweep.shift && update) {
|
||||
shadowfrequency = updatefrequency;
|
||||
frequency = updatefrequency;
|
||||
period = 4 * (2048 - frequency);
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Square1::clocksweep() {
|
||||
if(enable && sweep.frequency && --sweep.period == 0) {
|
||||
sweep.period = sweep.frequency;
|
||||
runsweep(1);
|
||||
runsweep(0);
|
||||
}
|
||||
}
|
||||
|
||||
uint8 APU::Square1::read(unsigned addr) const {
|
||||
switch(addr) {
|
||||
case 0: return (sweep.shift << 0) | (sweep.direction << 3) | (sweep.frequency << 4);
|
||||
case 1: return (length << 0) | (duty << 6);
|
||||
case 2: return (envelope.frequency << 0) | (envelope.direction << 3) | (envelope.volume << 4);
|
||||
case 3: return (frequency << 0);
|
||||
case 4: return (frequency >> 8) | (counter << 6) | (initialize << 7);
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Square1::write(unsigned addr, uint8 byte) {
|
||||
switch(addr) {
|
||||
case 0: //NR10
|
||||
if(sweep.negate && sweep.direction && !(byte & 0x08)) enable = false;
|
||||
sweep.shift = byte >> 0;
|
||||
sweep.direction = byte >> 3;
|
||||
sweep.frequency = byte >> 4;
|
||||
break;
|
||||
|
||||
case 1: //NR11
|
||||
length = byte >> 0;
|
||||
duty = byte >> 6;
|
||||
break;
|
||||
|
||||
case 2: //NR12
|
||||
envelope.frequency = byte >> 0;
|
||||
envelope.direction = byte >> 3;
|
||||
envelope.volume = byte >> 4;
|
||||
if(envelope.dacenable() == false) enable = false;
|
||||
break;
|
||||
|
||||
case 3: //NR13
|
||||
frequency = (frequency & 0xff00) | (byte << 0);
|
||||
break;
|
||||
|
||||
case 4: //NR14
|
||||
frequency = (frequency & 0x00ff) | (byte << 8);
|
||||
counter = byte >> 6;
|
||||
initialize = byte >> 7;
|
||||
|
||||
if(initialize) {
|
||||
enable = envelope.dacenable();
|
||||
period = 4 * (2048 - frequency);
|
||||
envelope.period = envelope.frequency;
|
||||
volume = envelope.volume;
|
||||
shadowfrequency = frequency;
|
||||
sweep.period = sweep.frequency;
|
||||
sweep.enable = sweep.period || sweep.shift;
|
||||
sweep.negate = false;
|
||||
if(sweep.shift) runsweep(0);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Square1::power() {
|
||||
envelope.frequency = 0;
|
||||
envelope.direction = 0;
|
||||
envelope.direction = 0;
|
||||
envelope.period = 0;
|
||||
sweep.shift = 0;
|
||||
sweep.direction = 0;
|
||||
sweep.frequency = 0;
|
||||
sweep.enable = 0;
|
||||
sweep.negate = 0;
|
||||
sweep.period = 0;
|
||||
enable = 0;
|
||||
length = 0;
|
||||
duty = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
initialize = 0;
|
||||
shadowfrequency = 0;
|
||||
signal = 0;
|
||||
output = 0;
|
||||
period = 0;
|
||||
phase = 0;
|
||||
volume = 0;
|
||||
}
|
61
gba/apu/square2.cpp
Normal file
61
gba/apu/square2.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
uint8 APU::Square2::read(unsigned addr) const {
|
||||
switch(addr) {
|
||||
case 1: return (length << 0) | (duty << 6);
|
||||
case 2: return (envelope.frequency << 0) | (envelope.direction << 3) | (envelope.volume << 4);
|
||||
case 3: return (frequency >> 0);
|
||||
case 4: return (frequency >> 8) | (counter << 6) | (initialize << 7);
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Square2::write(unsigned addr, uint8 byte) {
|
||||
switch(addr) {
|
||||
case 1: //NR21
|
||||
length = byte >> 0;
|
||||
duty = byte >> 6;
|
||||
break;
|
||||
|
||||
case 2: //NR22
|
||||
envelope.frequency = byte >> 0;
|
||||
envelope.direction = byte >> 3;
|
||||
envelope.volume = byte >> 4;
|
||||
if(envelope.dacenable() == false) enable = false;
|
||||
break;
|
||||
|
||||
case 3: //NR23
|
||||
frequency = (frequency & 0xff00) | (byte << 0);
|
||||
break;
|
||||
|
||||
case 4: //NR24
|
||||
frequency = (frequency & 0x00ff) | (byte << 8);
|
||||
counter = byte >> 6;
|
||||
initialize = byte >> 7;
|
||||
|
||||
if(initialize) {
|
||||
enable = envelope.dacenable();
|
||||
period = 4 * (2048 - frequency);
|
||||
envelope.period = envelope.frequency;
|
||||
volume = envelope.volume;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Square2::power() {
|
||||
envelope.frequency = 0;
|
||||
envelope.direction = 0;
|
||||
envelope.direction = 0;
|
||||
envelope.period = 0;
|
||||
enable = 0;
|
||||
length = 0;
|
||||
duty = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
initialize = 0;
|
||||
shadowfrequency = 0;
|
||||
signal = 0;
|
||||
output = 0;
|
||||
period = 0;
|
||||
phase = 0;
|
||||
volume = 0;
|
||||
}
|
95
gba/apu/wave.cpp
Normal file
95
gba/apu/wave.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
void APU::Wave::run() {
|
||||
if(period && --period == 0) {
|
||||
period = 2 * (2048 - frequency);
|
||||
patternsample = pattern[patternbank * 16 + patternaddr++];
|
||||
if(patternaddr == 0) patternbank ^= mode;
|
||||
}
|
||||
|
||||
output = patternsample;
|
||||
static unsigned multiplier[] = {0, 4, 2, 1, 3, 3, 3, 3};
|
||||
output = (output * multiplier[volume]) / 4;
|
||||
if(enable == false) output = 0;
|
||||
}
|
||||
|
||||
void APU::Wave::clocklength() {
|
||||
if(enable && counter) {
|
||||
if(++length == 0) enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 APU::Wave::read(unsigned addr) const {
|
||||
switch(addr) {
|
||||
case 0: return (mode << 5) | (bank << 6) | (dacenable << 7);
|
||||
case 1: return (length << 0);
|
||||
case 2: return (volume << 5);
|
||||
case 3: return (frequency >> 0);
|
||||
case 4: return (frequency >> 8) | (counter << 6) | (initialize << 7);
|
||||
}
|
||||
}
|
||||
|
||||
void APU::Wave::write(unsigned addr, uint8 byte) {
|
||||
switch(addr) {
|
||||
case 0: //NR30
|
||||
mode = byte >> 5;
|
||||
bank = byte >> 6;
|
||||
dacenable = byte >> 7;
|
||||
if(dacenable == false) enable = false;
|
||||
break;
|
||||
|
||||
case 1: //NR31
|
||||
length = byte >> 0;
|
||||
break;
|
||||
|
||||
case 2: //NR32
|
||||
volume = byte >> 5;
|
||||
break;
|
||||
|
||||
case 3: //NR33
|
||||
frequency = (frequency & 0xff00) | (byte << 0);
|
||||
break;
|
||||
|
||||
case 4: //NR34
|
||||
frequency = (frequency & 0x00ff) | (byte << 8);
|
||||
counter = byte >> 6;
|
||||
initialize = byte >> 7;
|
||||
|
||||
if(initialize) {
|
||||
enable = dacenable;
|
||||
period = 2 * (2048 - frequency);
|
||||
patternaddr = 0;
|
||||
patternbank = mode ? (uint1)0 : bank;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 APU::Wave::readram(unsigned addr) const {
|
||||
uint8 byte = 0;
|
||||
byte |= pattern[addr * 2 + 0] << 0;
|
||||
byte |= pattern[addr * 2 + 1] << 4;
|
||||
return byte;
|
||||
}
|
||||
|
||||
void APU::Wave::writeram(unsigned addr, uint8 byte) {
|
||||
pattern[addr * 2 + 0] = byte >> 0;
|
||||
pattern[addr * 2 + 1] = byte >> 4;
|
||||
}
|
||||
|
||||
void APU::Wave::power() {
|
||||
mode = 0;
|
||||
bank = 0;
|
||||
dacenable = 0;
|
||||
length = 0;
|
||||
volume = 0;
|
||||
frequency = 0;
|
||||
counter = 0;
|
||||
initialize = 0;
|
||||
for(auto& sample : pattern) sample = 0;
|
||||
enable = 0;
|
||||
output = 0;
|
||||
patternaddr = 0;
|
||||
patternbank = 0;
|
||||
patternsample = 0;
|
||||
period = 0;
|
||||
}
|
176
gba/cartridge/cartridge.cpp
Normal file
176
gba/cartridge/cartridge.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "eeprom.cpp"
|
||||
#include "flashrom.cpp"
|
||||
#include "serialization.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
string Cartridge::title() {
|
||||
return information.title;
|
||||
}
|
||||
|
||||
void Cartridge::load() {
|
||||
interface->loadRequest(ID::Manifest, "manifest.bml");
|
||||
|
||||
auto document = Markup::Document(information.markup);
|
||||
information.title = document["information/title"].text();
|
||||
|
||||
unsigned rom_size = 0;
|
||||
if(document["cartridge/rom"].exists()) {
|
||||
auto info = document["cartridge/rom"];
|
||||
interface->loadRequest(ID::ROM, info["name"].data);
|
||||
rom_size = numeral(info["size"].data);
|
||||
for(unsigned addr = rom_size; addr < rom.size; addr++) {
|
||||
rom.data[addr] = rom.data[Bus::mirror(addr, rom_size)];
|
||||
}
|
||||
}
|
||||
|
||||
has_sram = false;
|
||||
has_eeprom = false;
|
||||
has_flashrom = false;
|
||||
|
||||
if(document["cartridge/ram"].exists()) {
|
||||
auto info = document["cartridge/ram"];
|
||||
|
||||
if(info["type"].data == "SRAM" || info["type"].data == "FRAM") {
|
||||
has_sram = true;
|
||||
ram.size = numeral(info["size"].data);
|
||||
ram.mask = ram.size - 1;
|
||||
for(unsigned n = 0; n < ram.size; n++) ram.data[n] = 0xff;
|
||||
|
||||
interface->loadRequest(ID::RAM, info["name"].data);
|
||||
memory.append({ID::RAM, info["name"].data});
|
||||
}
|
||||
|
||||
if(info["type"].data == "EEPROM") {
|
||||
has_eeprom = true;
|
||||
eeprom.size = numeral(info["size"].data);
|
||||
eeprom.bits = eeprom.size <= 512 ? 6 : 14;
|
||||
if(eeprom.size == 0) eeprom.size = 8192, eeprom.bits = 0; //auto-detect size
|
||||
eeprom.mask = rom_size > 16 * 1024 * 1024 ? 0x0fffff00 : 0x0f000000;
|
||||
eeprom.test = rom_size > 16 * 1024 * 1024 ? 0x0dffff00 : 0x0d000000;
|
||||
for(unsigned n = 0; n < eeprom.size; n++) eeprom.data[n] = 0xff;
|
||||
|
||||
interface->loadRequest(ID::EEPROM, info["name"].data);
|
||||
memory.append({ID::EEPROM, info["name"].data});
|
||||
}
|
||||
|
||||
if(info["type"].data == "FlashROM") {
|
||||
has_flashrom = true;
|
||||
flashrom.id = numeral(info["id"].data);
|
||||
flashrom.size = numeral(info["size"].data);
|
||||
for(unsigned n = 0; n < flashrom.size; n++) flashrom.data[n] = 0xff;
|
||||
|
||||
interface->loadRequest(ID::FlashROM, info["name"].data);
|
||||
memory.append({ID::FlashROM, info["name"].data});
|
||||
}
|
||||
}
|
||||
|
||||
sha256 = nall::sha256(rom.data, rom_size);
|
||||
|
||||
system.load();
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
void Cartridge::unload() {
|
||||
if(loaded == false) return;
|
||||
loaded = false;
|
||||
memory.reset();
|
||||
}
|
||||
|
||||
void Cartridge::power() {
|
||||
eeprom.power();
|
||||
flashrom.power();
|
||||
}
|
||||
|
||||
uint8* Cartridge::ram_data() {
|
||||
if(has_sram) return ram.data;
|
||||
if(has_eeprom) return eeprom.data;
|
||||
if(has_flashrom) return flashrom.data;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned Cartridge::ram_size() {
|
||||
if(has_sram) return ram.size;
|
||||
if(has_eeprom) return eeprom.size;
|
||||
if(has_flashrom) return flashrom.size;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
uint32 Cartridge::read(uint8 *data, uint32 addr, uint32 size) {
|
||||
if(size == Word) addr &= ~3;
|
||||
if(size == Half) addr &= ~1;
|
||||
data += addr;
|
||||
if(size == Word) return data[0] << 0 | data[1] << 8 | data[2] << 16 | data[3] << 24;
|
||||
if(size == Half) return data[0] << 0 | data[1] << 8;
|
||||
return data[0];
|
||||
}
|
||||
|
||||
void Cartridge::write(uint8 *data, uint32 addr, uint32 size, uint32 word) {
|
||||
if(size == Word) addr &= ~3;
|
||||
if(size == Half) addr &= ~1;
|
||||
data += addr;
|
||||
switch(size) {
|
||||
case Word: data[3] = word >> 24;
|
||||
data[2] = word >> 16;
|
||||
case Half: data[1] = word >> 8;
|
||||
case Byte: data[0] = word >> 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define RAM_ANALYZE
|
||||
|
||||
uint32 Cartridge::read(uint32 addr, uint32 size) {
|
||||
#ifdef RAM_ANALYZE
|
||||
if((addr & 0x0e000000) == 0x0e000000) {
|
||||
static bool once = true;
|
||||
if(once) once = false, print("* SRAM/FlashROM read detected\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(has_sram && (addr & 0x0e000000 ) == 0x0e000000 ) return read(ram.data, addr & ram.mask, size);
|
||||
if(has_eeprom && (addr & eeprom.mask) == eeprom.test) return eeprom.read();
|
||||
if(has_flashrom && (addr & 0x0e000000 ) == 0x0e000000 ) return flashrom.read(addr);
|
||||
if(addr < 0x0e000000) return read(rom.data, addr & 0x01ffffff, size);
|
||||
return cpu.pipeline.fetch.instruction;
|
||||
}
|
||||
|
||||
void Cartridge::write(uint32 addr, uint32 size, uint32 word) {
|
||||
#ifdef RAM_ANALYZE
|
||||
if((addr & 0x0e000000) == 0x0e000000) {
|
||||
static bool once = true;
|
||||
if(once) once = false, print("* SRAM/FlashROM write detected\n");
|
||||
}
|
||||
if((addr & 0x0f000000) == 0x0d000000) {
|
||||
static bool once = true;
|
||||
if(once) once = false, print("* EEPROM write detected\n");
|
||||
}
|
||||
if((addr & 0x0e00ffff) == 0x0e005555 && (word & 0xff) == 0xaa) {
|
||||
static bool once = true;
|
||||
if(once) once = false, print("* FlashROM write detected\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(has_sram && (addr & 0x0e000000 ) == 0x0e000000 ) return write(ram.data, addr & ram.mask, size, word);
|
||||
if(has_eeprom && (addr & eeprom.mask) == eeprom.test) return eeprom.write(word & 1);
|
||||
if(has_flashrom && (addr & 0x0e000000 ) == 0x0e000000 ) return flashrom.write(addr, word);
|
||||
}
|
||||
|
||||
Cartridge::Cartridge() {
|
||||
loaded = false;
|
||||
rom.data = new uint8[rom.size = 32 * 1024 * 1024];
|
||||
ram.data = new uint8[ram.size = 32 * 1024];
|
||||
eeprom.data = new uint8[eeprom.size = 8 * 1024];
|
||||
flashrom.data = new uint8[flashrom.size = 128 * 1024];
|
||||
}
|
||||
|
||||
Cartridge::~Cartridge() {
|
||||
delete[] rom.data;
|
||||
delete[] ram.data;
|
||||
delete[] eeprom.data;
|
||||
delete[] flashrom.data;
|
||||
}
|
||||
|
||||
}
|
42
gba/cartridge/cartridge.hpp
Normal file
42
gba/cartridge/cartridge.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
struct Cartridge : property<Cartridge> {
|
||||
#include "memory.hpp"
|
||||
|
||||
readonly<bool> loaded;
|
||||
readonly<string> sha256;
|
||||
|
||||
readonly<bool> has_sram;
|
||||
readonly<bool> has_eeprom;
|
||||
readonly<bool> has_flashrom;
|
||||
|
||||
struct Information {
|
||||
string markup;
|
||||
string title;
|
||||
} information;
|
||||
|
||||
string title();
|
||||
|
||||
struct Media {
|
||||
unsigned id;
|
||||
string name;
|
||||
};
|
||||
vector<Media> memory;
|
||||
|
||||
void load();
|
||||
void unload();
|
||||
void power();
|
||||
|
||||
uint8* ram_data();
|
||||
unsigned ram_size();
|
||||
|
||||
uint32 read(uint8* data, uint32 addr, uint32 size);
|
||||
void write(uint8* data, uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
uint32 read(uint32 addr, uint32 size);
|
||||
void write(uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
void serialize(serializer&);
|
||||
Cartridge();
|
||||
~Cartridge();
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
94
gba/cartridge/eeprom.cpp
Normal file
94
gba/cartridge/eeprom.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
bool Cartridge::EEPROM::read(unsigned addr) {
|
||||
return data[addr >> 3] & 0x80 >> (addr & 7);
|
||||
}
|
||||
|
||||
void Cartridge::EEPROM::write(unsigned addr, bool bit) {
|
||||
if(bit == 0) data[addr >> 3] &=~ (0x80 >> (addr & 7));
|
||||
if(bit == 1) data[addr >> 3] |= (0x80 >> (addr & 7));
|
||||
}
|
||||
|
||||
bool Cartridge::EEPROM::read() {
|
||||
bool bit = 1;
|
||||
|
||||
//EEPROM size auto-detection
|
||||
if(bits == 0 && mode == Mode::ReadAddress) {
|
||||
print("EEPROM address bits: ", --addressbits, "\n");
|
||||
bits = addressbits == 6 ? 6 : 14;
|
||||
size = 8192;
|
||||
mode = Mode::ReadData;
|
||||
offset = 0;
|
||||
//fallthrough
|
||||
}
|
||||
|
||||
if(mode == Mode::ReadData) {
|
||||
if(offset >= 4) bit = read(address * 64 + (offset - 4));
|
||||
if(++offset == 68) mode = Mode::Wait;
|
||||
}
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
void Cartridge::EEPROM::write(bool bit) {
|
||||
if(mode == Mode::Wait) {
|
||||
if(bit == 1) mode = Mode::Command;
|
||||
}
|
||||
|
||||
else if(mode == Mode::Command) {
|
||||
if(bit == 0) mode = Mode::WriteAddress;
|
||||
if(bit == 1) mode = Mode::ReadAddress;
|
||||
offset = 0;
|
||||
address = 0;
|
||||
addressbits = 0;
|
||||
}
|
||||
|
||||
else if(mode == Mode::ReadAddress) {
|
||||
address = (address << 1) | bit;
|
||||
addressbits++;
|
||||
if(++offset == bits) {
|
||||
mode = Mode::ReadValidate;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
else if(mode == Mode::ReadValidate) {
|
||||
if(bit == 1); //invalid
|
||||
mode = Mode::ReadData;
|
||||
}
|
||||
|
||||
else if(mode == Mode::WriteAddress) {
|
||||
address = (address << 1) | bit;
|
||||
if(++offset == bits) {
|
||||
mode = Mode::WriteData;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
else if(mode == Mode::WriteData) {
|
||||
write(address * 64 + offset, bit);
|
||||
if(++offset == 64) {
|
||||
mode = Mode::WriteValidate;
|
||||
}
|
||||
}
|
||||
|
||||
else if(mode == Mode::WriteValidate) {
|
||||
if(bit == 1); //invalid
|
||||
mode = Mode::Wait;
|
||||
}
|
||||
}
|
||||
|
||||
void Cartridge::EEPROM::power() {
|
||||
mode = Mode::Wait;
|
||||
offset = 0;
|
||||
address = 0;
|
||||
}
|
||||
|
||||
void Cartridge::EEPROM::serialize(serializer& s) {
|
||||
s.array(data, size);
|
||||
s.integer(size);
|
||||
s.integer(mask);
|
||||
s.integer(test);
|
||||
s.integer(bits);
|
||||
s.integer((unsigned&)mode);
|
||||
s.integer(offset);
|
||||
s.integer(address);
|
||||
}
|
100
gba/cartridge/flashrom.cpp
Normal file
100
gba/cartridge/flashrom.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
//Dev.ID Size Blocks Manufacturer
|
||||
//====== ===== ======== ============
|
||||
//0xd4bf 64KB 16x4096 SST
|
||||
//0x1cc2 64KB 16x4096 Macronix
|
||||
//0x1b32 64KB 16x4096 Panasonic
|
||||
//0x3d1f 64KB 512x 128 Atmel
|
||||
//0x1362 128KB 32x4096 Sanyo
|
||||
//0x09c2 128KB 32x4096 Macronix
|
||||
|
||||
uint8 Cartridge::FlashROM::read(uint16 addr) {
|
||||
if(idmode) {
|
||||
if(addr == 0x0000) return id >> 0;
|
||||
if(addr == 0x0001) return id >> 8;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
return data[bank << 16 | addr];
|
||||
}
|
||||
|
||||
void Cartridge::FlashROM::write(uint16 addr, uint8 byte) {
|
||||
if(bankselect) {
|
||||
bankselect = false;
|
||||
//bank select is only applicable on 128KB chips
|
||||
if(addr == 0x0000) bank = byte & (size > 64 * 1024);
|
||||
return;
|
||||
}
|
||||
|
||||
if(writeselect) {
|
||||
//Atmel writes 128 bytes per command; all others write 1 byte per command
|
||||
if(id != 0x3d1f || (addr & 0x007f) == 0x007f) writeselect = false;
|
||||
data[bank << 16 | addr] = byte;
|
||||
return;
|
||||
}
|
||||
|
||||
if(byte == 0xaa && addr == 0x5555) { unlockhi = true; return; }
|
||||
if(byte == 0x55 && addr == 0x2aaa) { unlocklo = true; return; }
|
||||
|
||||
if(unlockhi && unlocklo) {
|
||||
unlockhi = false;
|
||||
unlocklo = false;
|
||||
|
||||
if(byte == 0x10 && addr == 0x5555) {
|
||||
if(erasemode) {
|
||||
erasemode = false;
|
||||
for(unsigned n = 0; n < size; n++) data[n] = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
if(byte == 0x30 && (addr & 0x0fff) == 0x0000) {
|
||||
//command only valid for non-Atmel chips
|
||||
if(erasemode && id != 0x3d1f) {
|
||||
erasemode = false;
|
||||
unsigned offset = bank << 16 | (addr & ~4095);
|
||||
for(unsigned n = 0; n < 4096; n++) data[offset++] = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
if(byte == 0x80 && addr == 0x5555) {
|
||||
erasemode = true;
|
||||
}
|
||||
|
||||
if(byte == 0x90 && addr == 0x5555) {
|
||||
idmode = true;
|
||||
}
|
||||
|
||||
if(byte == 0xa0 && addr == 0x5555) {
|
||||
writeselect = true;
|
||||
}
|
||||
|
||||
if(byte == 0xb0 && addr == 0x5555) {
|
||||
bankselect = true;
|
||||
}
|
||||
|
||||
if(byte == 0xf0 && addr == 0x5555) {
|
||||
idmode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Cartridge::FlashROM::power() {
|
||||
unlockhi = false;
|
||||
unlocklo = false;
|
||||
idmode = false;
|
||||
bankselect = false;
|
||||
writeselect = false;
|
||||
bank = 0;
|
||||
}
|
||||
|
||||
void Cartridge::FlashROM::serialize(serializer& s) {
|
||||
s.array(data, size);
|
||||
s.integer(size);
|
||||
s.integer(id);
|
||||
s.integer(unlockhi);
|
||||
s.integer(unlocklo);
|
||||
s.integer(idmode);
|
||||
s.integer(erasemode);
|
||||
s.integer(bankselect);
|
||||
s.integer(writeselect);
|
||||
s.integer(bank);
|
||||
}
|
45
gba/cartridge/memory.hpp
Normal file
45
gba/cartridge/memory.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
struct Memory {
|
||||
uint8* data;
|
||||
unsigned size;
|
||||
unsigned mask;
|
||||
} rom, ram;
|
||||
|
||||
struct EEPROM {
|
||||
uint8* data;
|
||||
unsigned size;
|
||||
unsigned mask;
|
||||
unsigned test;
|
||||
unsigned bits;
|
||||
|
||||
enum class Mode : unsigned { Wait, Command, ReadAddress, ReadValidate, ReadData, WriteAddress, WriteData, WriteValidate } mode;
|
||||
unsigned offset;
|
||||
unsigned address;
|
||||
unsigned addressbits;
|
||||
|
||||
bool read(unsigned addr);
|
||||
void write(unsigned addr, bool bit);
|
||||
|
||||
bool read();
|
||||
void write(bool bit);
|
||||
void power();
|
||||
void serialize(serializer&);
|
||||
} eeprom;
|
||||
|
||||
struct FlashROM {
|
||||
uint8* data;
|
||||
unsigned size;
|
||||
uint16 id;
|
||||
|
||||
bool unlockhi;
|
||||
bool unlocklo;
|
||||
bool idmode;
|
||||
bool erasemode;
|
||||
bool bankselect;
|
||||
bool writeselect;
|
||||
bool bank;
|
||||
|
||||
uint8 read(uint16 addr);
|
||||
void write(uint16 addr, uint8 byte);
|
||||
void power();
|
||||
void serialize(serializer&);
|
||||
} flashrom;
|
5
gba/cartridge/serialization.cpp
Normal file
5
gba/cartridge/serialization.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
void Cartridge::serialize(serializer& s) {
|
||||
if(has_sram) s.array(ram.data, ram.size);
|
||||
if(has_eeprom) eeprom.serialize(s);
|
||||
if(has_flashrom) flashrom.serialize(s);
|
||||
}
|
173
gba/cpu/cpu.cpp
Normal file
173
gba/cpu/cpu.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "registers.cpp"
|
||||
#include "mmio.cpp"
|
||||
#include "memory.cpp"
|
||||
#include "dma.cpp"
|
||||
#include "timer.cpp"
|
||||
#include "serialization.cpp"
|
||||
CPU cpu;
|
||||
|
||||
void CPU::Enter() {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cpu.main();
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::main() {
|
||||
#if defined(DEBUG)
|
||||
if(crash) {
|
||||
print(cpsr().t ? disassemble_thumb_instruction(pipeline.execute.address)
|
||||
: disassemble_arm_instruction(pipeline.execute.address), "\n");
|
||||
print(disassemble_registers(), "\n");
|
||||
print("Executed: ", instructions, "\n");
|
||||
while(true) step(frequency);
|
||||
}
|
||||
#endif
|
||||
|
||||
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) {
|
||||
sync_step(16); //STOP does not advance timers
|
||||
} else {
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dma_run();
|
||||
|
||||
if(regs.mode == Registers::Mode::Halt) {
|
||||
if((regs.irq.enable & regs.irq.flag) == 0) {
|
||||
step(16);
|
||||
} else {
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
exec();
|
||||
}
|
||||
|
||||
void CPU::step(unsigned clocks) {
|
||||
timer_step(clocks);
|
||||
sync_step(clocks);
|
||||
}
|
||||
|
||||
void CPU::sync_step(unsigned clocks) {
|
||||
ppu.clock -= clocks;
|
||||
if(ppu.clock < 0) co_switch(ppu.thread);
|
||||
|
||||
apu.clock -= clocks;
|
||||
if(apu.clock < 0) co_switch(apu.thread);
|
||||
}
|
||||
|
||||
void CPU::bus_idle(uint32 addr) {
|
||||
step(1);
|
||||
return bus.idle(addr);
|
||||
}
|
||||
|
||||
uint32 CPU::bus_read(uint32 addr, uint32 size) {
|
||||
step(bus.speed(addr, size));
|
||||
return bus.read(addr, size);
|
||||
}
|
||||
|
||||
void CPU::bus_write(uint32 addr, uint32 size, uint32 word) {
|
||||
step(bus.speed(addr, size));
|
||||
return bus.write(addr, size, word);
|
||||
}
|
||||
|
||||
void CPU::keypad_run() {
|
||||
if(regs.keypad.control.enable == false) return;
|
||||
|
||||
bool test = regs.keypad.control.condition; //0 = OR, 1 = AND
|
||||
for(unsigned n = 0; n < 10; n++) {
|
||||
if(regs.keypad.control.flag[n] == false) continue;
|
||||
bool input = interface->inputPoll(0, 0, n);
|
||||
if(regs.keypad.control.condition == 0) test |= input;
|
||||
if(regs.keypad.control.condition == 1) test &= input;
|
||||
}
|
||||
if(test) regs.irq.flag.keypad = true;
|
||||
}
|
||||
|
||||
void CPU::power() {
|
||||
create(CPU::Enter, 16777216);
|
||||
|
||||
ARM::power();
|
||||
for(unsigned n = 0; n < 32 * 1024; n++) iwram[n] = 0;
|
||||
for(unsigned n = 0; n < 256 * 1024; n++) ewram[n] = 0;
|
||||
|
||||
for(auto& dma : regs.dma) {
|
||||
dma.source = 0;
|
||||
dma.target = 0;
|
||||
dma.length = 0;
|
||||
dma.control = 0;
|
||||
dma.pending = 0;
|
||||
dma.run.target = 0;
|
||||
dma.run.source = 0;
|
||||
dma.run.length = 0;
|
||||
}
|
||||
for(auto& timer : regs.timer) {
|
||||
timer.period = 0;
|
||||
timer.reload = 0;
|
||||
timer.control = 0;
|
||||
}
|
||||
regs.keypad.control = 0;
|
||||
regs.ime = 0;
|
||||
regs.irq.enable = 0;
|
||||
regs.irq.flag = 0;
|
||||
regs.wait.control = 0;
|
||||
regs.postboot = 0;
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
regs.clock = 0;
|
||||
regs.memory.control = 0x0d000020;
|
||||
|
||||
pending.dma.vblank = 0;
|
||||
pending.dma.hblank = 0;
|
||||
pending.dma.hdma = 0;
|
||||
|
||||
for(unsigned n = 0x0b0; n <= 0x0df; n++) bus.mmio[n] = this; //DMA
|
||||
for(unsigned n = 0x100; n <= 0x10f; n++) bus.mmio[n] = this; //Timers
|
||||
for(unsigned n = 0x120; n <= 0x12b; n++) bus.mmio[n] = this; //Serial
|
||||
for(unsigned n = 0x130; n <= 0x133; n++) bus.mmio[n] = this; //Keypad
|
||||
for(unsigned n = 0x134; n <= 0x159; n++) bus.mmio[n] = this; //Serial
|
||||
for(unsigned n = 0x200; n <= 0x209; n++) bus.mmio[n] = this; //System
|
||||
for(unsigned n = 0x300; n <= 0x301; n++) bus.mmio[n] = this; //System
|
||||
//0x080-0x083 mirrored via gba/memory/memory.cpp //System
|
||||
}
|
||||
|
||||
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[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[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[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);
|
||||
}
|
||||
|
||||
CPU::~CPU() {
|
||||
delete[] iwram;
|
||||
delete[] ewram;
|
||||
}
|
||||
|
||||
}
|
43
gba/cpu/cpu.hpp
Normal file
43
gba/cpu/cpu.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
struct CPU : Processor::ARM, Thread, MMIO {
|
||||
uint8* iwram;
|
||||
uint8* ewram;
|
||||
#include "registers.hpp"
|
||||
#include "state.hpp"
|
||||
|
||||
static void Enter();
|
||||
void main();
|
||||
void step(unsigned clocks);
|
||||
void sync_step(unsigned clocks);
|
||||
|
||||
void bus_idle(uint32 addr);
|
||||
uint32 bus_read(uint32 addr, uint32 size);
|
||||
void bus_write(uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
void keypad_run();
|
||||
void power();
|
||||
|
||||
uint8 read(uint32 addr);
|
||||
void write(uint32 addr, uint8 byte);
|
||||
|
||||
uint32 iwram_read(uint32 addr, uint32 size);
|
||||
void iwram_write(uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
uint32 ewram_read(uint32 addr, uint32 size);
|
||||
void ewram_write(uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
void dma_run();
|
||||
void dma_transfer(Registers::DMA &dma);
|
||||
void dma_vblank();
|
||||
void dma_hblank();
|
||||
void dma_hdma();
|
||||
|
||||
void timer_step(unsigned clocks);
|
||||
void timer_increment(unsigned n);
|
||||
void timer_fifo_run(unsigned n);
|
||||
|
||||
void serialize(serializer&);
|
||||
CPU();
|
||||
~CPU();
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
60
gba/cpu/dma.cpp
Normal file
60
gba/cpu/dma.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
void CPU::dma_run() {
|
||||
for(unsigned n = 0; n < 4; n++) {
|
||||
auto& dma = regs.dma[n];
|
||||
if(dma.pending) {
|
||||
dma.pending = false;
|
||||
dma_transfer(dma);
|
||||
if(dma.control.irq) regs.irq.flag.dma[n] = 1;
|
||||
if(dma.control.drq && n == 3) regs.irq.flag.cartridge = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::dma_transfer(Registers::DMA& dma) {
|
||||
unsigned size = dma.control.size ? Word : Half;
|
||||
unsigned seek = dma.control.size ? 4 : 2;
|
||||
|
||||
sequential() = false;
|
||||
do {
|
||||
step(bus.speed(dma.run.source, size));
|
||||
uint32 word = bus.read(dma.run.source, size);
|
||||
|
||||
step(bus.speed(dma.run.target, size));
|
||||
bus.write(dma.run.target, size, word);
|
||||
|
||||
sequential() = true;
|
||||
|
||||
switch(dma.control.sourcemode) {
|
||||
case 0: dma.run.source += seek; break;
|
||||
case 1: dma.run.source -= seek; break;
|
||||
}
|
||||
|
||||
switch(dma.control.targetmode) {
|
||||
case 0: dma.run.target += seek; break;
|
||||
case 1: dma.run.target -= seek; break;
|
||||
case 3: dma.run.target += seek; break;
|
||||
}
|
||||
} while(--dma.run.length);
|
||||
sequential() = false;
|
||||
|
||||
if(dma.control.targetmode == 3) dma.run.target = dma.target;
|
||||
if(dma.control.repeat == 1) dma.run.length = dma.length;
|
||||
if(dma.control.repeat == 0) dma.control.enable = false;
|
||||
}
|
||||
|
||||
void CPU::dma_vblank() {
|
||||
for(auto& dma : regs.dma) {
|
||||
if(dma.control.enable && dma.control.timingmode == 1) dma.pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::dma_hblank() {
|
||||
for(auto& dma : regs.dma) {
|
||||
if(dma.control.enable && dma.control.timingmode == 2) dma.pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::dma_hdma() {
|
||||
auto& dma = regs.dma[3];
|
||||
if(dma.control.enable && dma.control.timingmode == 3) dma.pending = true;
|
||||
}
|
55
gba/cpu/memory.cpp
Normal file
55
gba/cpu/memory.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
uint32 CPU::iwram_read(uint32 addr, uint32 size) {
|
||||
if(regs.memory.control.disable) return cpu.pipeline.fetch.instruction;
|
||||
|
||||
if(size == Word) return iwram_read(addr &~ 2, Half) << 0 | iwram_read(addr | 2, Half) << 16;
|
||||
if(size == Half) return iwram_read(addr &~ 1, Byte) << 0 | iwram_read(addr | 1, Byte) << 8;
|
||||
|
||||
return iwram[addr & 0x7fff];
|
||||
}
|
||||
|
||||
void CPU::iwram_write(uint32 addr, uint32 size, uint32 word) {
|
||||
if(regs.memory.control.disable) return;
|
||||
|
||||
if(size == Word) {
|
||||
iwram_write(addr &~2, Half, word >> 0);
|
||||
iwram_write(addr | 2, Half, word >> 16);
|
||||
return;
|
||||
}
|
||||
|
||||
if(size == Half) {
|
||||
iwram_write(addr &~1, Byte, word >> 0);
|
||||
iwram_write(addr | 1, Byte, word >> 8);
|
||||
return;
|
||||
}
|
||||
|
||||
iwram[addr & 0x7fff] = word;
|
||||
}
|
||||
|
||||
uint32 CPU::ewram_read(uint32 addr, uint32 size) {
|
||||
if(regs.memory.control.disable) return cpu.pipeline.fetch.instruction;
|
||||
if(regs.memory.control.ewram == false) return iwram_read(addr, size);
|
||||
|
||||
if(size == Word) return ewram_read(addr &~ 2, Half) << 0 | ewram_read(addr | 2, Half) << 16;
|
||||
if(size == Half) return ewram_read(addr &~ 1, Byte) << 0 | ewram_read(addr | 1, Byte) << 8;
|
||||
|
||||
return ewram[addr & 0x3ffff];
|
||||
}
|
||||
|
||||
void CPU::ewram_write(uint32 addr, uint32 size, uint32 word) {
|
||||
if(regs.memory.control.disable) return;
|
||||
if(regs.memory.control.ewram == false) return iwram_write(addr, size, word);
|
||||
|
||||
if(size == Word) {
|
||||
ewram_write(addr &~2, Half, word >> 0);
|
||||
ewram_write(addr | 2, Half, word >> 16);
|
||||
return;
|
||||
}
|
||||
|
||||
if(size == Half) {
|
||||
ewram_write(addr &~1, Byte, word >> 0);
|
||||
ewram_write(addr | 1, Byte, word >> 8);
|
||||
return;
|
||||
}
|
||||
|
||||
ewram[addr & 0x3ffff] = word;
|
||||
}
|
316
gba/cpu/mmio.cpp
Normal file
316
gba/cpu/mmio.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
uint8 CPU::read(uint32 addr) {
|
||||
uint8 result = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//SIOMULTI0 (SIODATA32_L)
|
||||
//SIOMULTI1 (SIODATA32_H)
|
||||
//SIOMULTI2
|
||||
//SIOMULTI3
|
||||
case 0x04000120: case 0x04000121:
|
||||
case 0x04000122: case 0x04000123:
|
||||
case 0x04000124: case 0x04000125:
|
||||
case 0x04000126: case 0x04000127: {
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
return data >> shift;
|
||||
}
|
||||
|
||||
//SIOCNT
|
||||
case 0x04000128: return regs.serial.control >> 0;
|
||||
case 0x04000129: return regs.serial.control >> 8;
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
case 0x0400012a: return regs.serial.data8;
|
||||
case 0x0400012b: return 0u;
|
||||
|
||||
//KEYINPUT
|
||||
case 0x04000130:
|
||||
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
|
||||
return result ^ 0xff;
|
||||
case 0x04000131:
|
||||
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;
|
||||
|
||||
//RCNT
|
||||
case 0x04000134: return regs.joybus.settings >> 0;
|
||||
case 0x04000135: return regs.joybus.settings >> 8;
|
||||
|
||||
//JOYCNT
|
||||
case 0x04000140: return regs.joybus.control >> 0;
|
||||
case 0x04000141: return regs.joybus.control >> 8;
|
||||
|
||||
//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_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;
|
||||
|
||||
//JOYSTAT
|
||||
case 0x04000158: return regs.joybus.status >> 0;
|
||||
case 0x04000159: return regs.joybus.status >> 8;
|
||||
|
||||
//IE
|
||||
case 0x04000200: return regs.irq.enable >> 0;
|
||||
case 0x04000201: return regs.irq.enable >> 8;
|
||||
|
||||
//IF
|
||||
case 0x04000202: return regs.irq.flag >> 0;
|
||||
case 0x04000203: return regs.irq.flag >> 8;
|
||||
|
||||
//WAITCNT
|
||||
case 0x04000204: return regs.wait.control >> 0;
|
||||
case 0x04000205: return regs.wait.control >> 8;
|
||||
|
||||
//IME
|
||||
case 0x04000208: return regs.ime;
|
||||
case 0x04000209: return 0u;
|
||||
|
||||
//POSTFLG + HALTCNT
|
||||
case 0x04000300: return regs.postboot;
|
||||
case 0x04000301: return 0u;
|
||||
|
||||
//MEMCNT_L
|
||||
case 0x04000800: return regs.memory.control >> 0;
|
||||
case 0x04000801: return regs.memory.control >> 8;
|
||||
|
||||
//MEMCNT_H
|
||||
case 0x04000802: return regs.memory.control >> 16;
|
||||
case 0x04000803: return regs.memory.control >> 24;
|
||||
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
|
||||
void CPU::write(uint32 addr, uint8 byte) {
|
||||
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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//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_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;
|
||||
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;
|
||||
}
|
||||
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_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.period = timer.reload;
|
||||
}
|
||||
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: {
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
data = (data & ~(255 << shift)) | (byte << shift);
|
||||
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;
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
case 0x0400012a: regs.serial.data8 = byte; return;
|
||||
case 0x0400012b: 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;
|
||||
|
||||
//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;
|
||||
|
||||
//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;
|
||||
|
||||
//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;
|
||||
|
||||
//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;
|
||||
|
||||
//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;
|
||||
|
||||
//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;
|
||||
|
||||
//IF
|
||||
case 0x04000202: regs.irq.flag = regs.irq.flag & ~(byte << 0); return;
|
||||
case 0x04000203: regs.irq.flag = regs.irq.flag & ~(byte << 8); 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;
|
||||
|
||||
//IME
|
||||
case 0x04000208: regs.ime = byte >> 0; return;
|
||||
case 0x04000209: return;
|
||||
|
||||
//POSTFLG, HALTCNT
|
||||
case 0x04000300: regs.postboot |= byte >> 0; return;
|
||||
case 0x04000301: regs.mode = byte & 0x80 ? 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;
|
||||
|
||||
}
|
||||
}
|
244
gba/cpu/registers.cpp
Normal file
244
gba/cpu/registers.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
CPU::Registers::DMAControl::operator uint16() const {
|
||||
return (
|
||||
(targetmode << 5)
|
||||
| (sourcemode << 7)
|
||||
| (repeat << 9)
|
||||
| (size << 10)
|
||||
| (drq << 11)
|
||||
| (timingmode << 12)
|
||||
| (irq << 14)
|
||||
| (enable << 15)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::DMAControl::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::TimerControl::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::SerialControl::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::KeypadControl::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::JoybusSettings::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::JoybusControl::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::JoybusStatus::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::Interrupt::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 CPU::Registers::WaitControl::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint32 CPU::Registers::MemoryControl::operator=(uint32 source) {
|
||||
disable = source >> 0;
|
||||
unknown1 = source >> 1;
|
||||
ewram = source >> 5;
|
||||
ewramwait = source >> 24;
|
||||
unknown2 = source >> 28;
|
||||
return operator uint32();
|
||||
}
|
186
gba/cpu/registers.hpp
Normal file
186
gba/cpu/registers.hpp
Normal file
@@ -0,0 +1,186 @@
|
||||
struct Registers {
|
||||
struct DMAControl {
|
||||
uint2 targetmode;
|
||||
uint2 sourcemode;
|
||||
uint1 repeat;
|
||||
uint1 size;
|
||||
uint1 drq;
|
||||
uint2 timingmode;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
DMAControl& operator=(const DMAControl&) = delete;
|
||||
};
|
||||
|
||||
struct DMA {
|
||||
varuint source;
|
||||
varuint target;
|
||||
varuint length;
|
||||
DMAControl control;
|
||||
|
||||
//internal
|
||||
bool pending;
|
||||
struct Run {
|
||||
varuint target;
|
||||
varuint source;
|
||||
varuint length;
|
||||
} run;
|
||||
} dma[4];
|
||||
|
||||
struct TimerControl {
|
||||
uint2 frequency;
|
||||
uint1 cascade;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
TimerControl& operator=(const TimerControl&) = delete;
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
uint16 period;
|
||||
uint16 reload;
|
||||
TimerControl control;
|
||||
} timer[4];
|
||||
|
||||
struct SerialControl {
|
||||
uint1 shiftclockselect;
|
||||
uint1 shiftclockfrequency;
|
||||
uint1 transferenablereceive;
|
||||
uint1 transferenablesend;
|
||||
uint1 startbit;
|
||||
uint1 transferlength;
|
||||
uint1 irqenable;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
SerialControl& operator=(const SerialControl&) = delete;
|
||||
};
|
||||
|
||||
struct Serial {
|
||||
uint16 data[4];
|
||||
SerialControl control;
|
||||
uint8 data8;
|
||||
} serial;
|
||||
|
||||
struct KeypadControl {
|
||||
uint1 flag[10];
|
||||
uint1 enable;
|
||||
uint1 condition;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
KeypadControl& operator=(const KeypadControl&) = delete;
|
||||
};
|
||||
|
||||
struct Keypad {
|
||||
KeypadControl 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;
|
||||
uint16 operator=(uint16 source);
|
||||
JoybusSettings& operator=(const JoybusSettings&) = delete;
|
||||
};
|
||||
|
||||
struct JoybusControl {
|
||||
uint1 resetsignal;
|
||||
uint1 receivecomplete;
|
||||
uint1 sendcomplete;
|
||||
uint1 irqenable;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
JoybusControl& operator=(const JoybusControl&) = delete;
|
||||
};
|
||||
|
||||
struct JoybusStatus {
|
||||
uint1 receiveflag;
|
||||
uint1 sendflag;
|
||||
uint2 generalflag;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
JoybusStatus& operator=(const JoybusStatus&) = delete;
|
||||
};
|
||||
|
||||
struct Joybus {
|
||||
JoybusSettings settings;
|
||||
JoybusControl control;
|
||||
uint32 receive;
|
||||
uint32 transmit;
|
||||
JoybusStatus 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;
|
||||
uint16 operator=(uint16 source);
|
||||
Interrupt& operator=(const Interrupt&) = delete;
|
||||
};
|
||||
|
||||
struct IRQ {
|
||||
Interrupt enable;
|
||||
Interrupt flag;
|
||||
} irq;
|
||||
|
||||
struct WaitControl {
|
||||
uint2 nwait[4];
|
||||
uint2 swait[4];
|
||||
uint2 phi;
|
||||
uint1 prefetch;
|
||||
uint1 gametype;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
WaitControl& operator=(const WaitControl&) = delete;
|
||||
};
|
||||
|
||||
struct Wait {
|
||||
WaitControl control;
|
||||
} wait;
|
||||
|
||||
struct MemoryControl {
|
||||
uint1 disable;
|
||||
uint3 unknown1;
|
||||
uint1 ewram;
|
||||
uint4 ewramwait;
|
||||
uint4 unknown2;
|
||||
|
||||
operator uint32() const;
|
||||
uint32 operator=(uint32 source);
|
||||
MemoryControl& operator=(const MemoryControl&) = delete;
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
MemoryControl control;
|
||||
} memory;
|
||||
|
||||
uint1 postboot;
|
||||
enum class Mode : unsigned { Normal, Halt, Stop } mode;
|
||||
unsigned clock;
|
||||
} regs;
|
110
gba/cpu/serialization.cpp
Normal file
110
gba/cpu/serialization.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
void CPU::serialize(serializer& s) {
|
||||
ARM::serialize(s);
|
||||
Thread::serialize(s);
|
||||
|
||||
s.array(iwram, 32 * 1024);
|
||||
s.array(ewram, 256 * 1024);
|
||||
|
||||
for(auto& dma : regs.dma) {
|
||||
s.integer(dma.source);
|
||||
s.integer(dma.target);
|
||||
s.integer(dma.length);
|
||||
s.integer(dma.control.targetmode);
|
||||
s.integer(dma.control.sourcemode);
|
||||
s.integer(dma.control.repeat);
|
||||
s.integer(dma.control.size);
|
||||
s.integer(dma.control.drq);
|
||||
s.integer(dma.control.timingmode);
|
||||
s.integer(dma.control.irq);
|
||||
s.integer(dma.control.enable);
|
||||
s.integer(dma.run.target);
|
||||
s.integer(dma.run.source);
|
||||
s.integer(dma.run.length);
|
||||
}
|
||||
|
||||
for(auto& timer : regs.timer) {
|
||||
s.integer(timer.period);
|
||||
s.integer(timer.reload);
|
||||
s.integer(timer.control.frequency);
|
||||
s.integer(timer.control.cascade);
|
||||
s.integer(timer.control.irq);
|
||||
s.integer(timer.control.enable);
|
||||
}
|
||||
|
||||
for(auto& value : regs.serial.data) s.integer(value);
|
||||
s.integer(regs.serial.control.shiftclockselect);
|
||||
s.integer(regs.serial.control.shiftclockfrequency);
|
||||
s.integer(regs.serial.control.transferenablereceive);
|
||||
s.integer(regs.serial.control.transferenablesend);
|
||||
s.integer(regs.serial.control.startbit);
|
||||
s.integer(regs.serial.control.transferlength);
|
||||
s.integer(regs.serial.control.irqenable);
|
||||
s.integer(regs.serial.data8);
|
||||
|
||||
for(auto& flag : regs.keypad.control.flag) s.integer(flag);
|
||||
s.integer(regs.keypad.control.enable);
|
||||
s.integer(regs.keypad.control.condition);
|
||||
|
||||
s.integer(regs.joybus.settings.sc);
|
||||
s.integer(regs.joybus.settings.sd);
|
||||
s.integer(regs.joybus.settings.si);
|
||||
s.integer(regs.joybus.settings.so);
|
||||
s.integer(regs.joybus.settings.scmode);
|
||||
s.integer(regs.joybus.settings.sdmode);
|
||||
s.integer(regs.joybus.settings.simode);
|
||||
s.integer(regs.joybus.settings.somode);
|
||||
s.integer(regs.joybus.settings.irqenable);
|
||||
s.integer(regs.joybus.settings.mode);
|
||||
|
||||
s.integer(regs.joybus.control.resetsignal);
|
||||
s.integer(regs.joybus.control.receivecomplete);
|
||||
s.integer(regs.joybus.control.sendcomplete);
|
||||
s.integer(regs.joybus.control.irqenable);
|
||||
|
||||
s.integer(regs.joybus.receive);
|
||||
s.integer(regs.joybus.transmit);
|
||||
|
||||
s.integer(regs.joybus.status.receiveflag);
|
||||
s.integer(regs.joybus.status.sendflag);
|
||||
s.integer(regs.joybus.status.generalflag);
|
||||
|
||||
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);
|
||||
|
||||
for(auto& flag : regs.wait.control.nwait) s.integer(flag);
|
||||
for(auto& flag : regs.wait.control.swait) s.integer(flag);
|
||||
s.integer(regs.wait.control.phi);
|
||||
s.integer(regs.wait.control.prefetch);
|
||||
s.integer(regs.wait.control.gametype);
|
||||
|
||||
s.integer(regs.memory.control.disable);
|
||||
s.integer(regs.memory.control.unknown1);
|
||||
s.integer(regs.memory.control.ewram);
|
||||
s.integer(regs.memory.control.ewramwait);
|
||||
s.integer(regs.memory.control.unknown2);
|
||||
|
||||
s.integer(regs.postboot);
|
||||
s.integer((unsigned&)regs.mode);
|
||||
s.integer(regs.clock);
|
||||
|
||||
s.integer(pending.dma.vblank);
|
||||
s.integer(pending.dma.hblank);
|
||||
s.integer(pending.dma.hdma);
|
||||
}
|
7
gba/cpu/state.hpp
Normal file
7
gba/cpu/state.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
struct Pending {
|
||||
struct DMA {
|
||||
bool vblank;
|
||||
bool hblank;
|
||||
bool hdma;
|
||||
} dma;
|
||||
} pending;
|
44
gba/cpu/timer.cpp
Normal file
44
gba/cpu/timer.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
void CPU::timer_step(unsigned clocks) {
|
||||
for(unsigned c = 0; c < clocks; c++) {
|
||||
for(unsigned n = 0; n < 4; n++) {
|
||||
auto& timer = regs.timer[n];
|
||||
if(timer.control.enable == false || timer.control.cascade == true) continue;
|
||||
|
||||
static unsigned mask[] = {0, 63, 255, 1023};
|
||||
if((regs.clock & mask[timer.control.frequency]) == 0) {
|
||||
timer_increment(n);
|
||||
}
|
||||
}
|
||||
|
||||
regs.clock++;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::timer_increment(unsigned n) {
|
||||
auto& timer = regs.timer[n];
|
||||
if(++timer.period == 0) {
|
||||
timer.period = timer.reload;
|
||||
|
||||
if(timer.control.irq) regs.irq.flag.timer[n] = 1;
|
||||
|
||||
if(apu.fifo[0].timer == n) timer_fifo_run(0);
|
||||
if(apu.fifo[1].timer == n) timer_fifo_run(1);
|
||||
|
||||
if(n < 3 && regs.timer[n + 1].control.enable && regs.timer[n + 1].control.cascade) {
|
||||
timer_increment(n + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::timer_fifo_run(unsigned n) {
|
||||
apu.fifo[n].read();
|
||||
if(apu.fifo[n].size > 16) return;
|
||||
|
||||
auto& dma = regs.dma[1 + n];
|
||||
if(dma.control.enable && dma.control.timingmode == 3) {
|
||||
dma.pending = true;
|
||||
dma.control.targetmode = 2;
|
||||
dma.control.size = 1;
|
||||
dma.run.length = 4;
|
||||
}
|
||||
}
|
62
gba/gba.hpp
Normal file
62
gba/gba.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef GBA_HPP
|
||||
#define GBA_HPP
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <processor/arm/arm.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
namespace Info {
|
||||
static const char Name[] = "bgba";
|
||||
static const unsigned SerializerVersion = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bgba - Game Boy Advance emulator
|
||||
authors: byuu, Cydrak
|
||||
license: GPLv3
|
||||
project started: 2012-03-19
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
enum : unsigned { Byte = 8, Half = 16, Word = 32 };
|
||||
|
||||
struct Thread {
|
||||
cothread_t thread;
|
||||
unsigned frequency;
|
||||
signed clock;
|
||||
|
||||
inline void create(void (*entrypoint)(), unsigned frequency) {
|
||||
if(thread) co_delete(thread);
|
||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
||||
this->frequency = frequency;
|
||||
clock = 0;
|
||||
}
|
||||
|
||||
inline void serialize(serializer& s) {
|
||||
s.integer(frequency);
|
||||
s.integer(clock);
|
||||
}
|
||||
|
||||
inline Thread() : thread(nullptr) {
|
||||
}
|
||||
|
||||
inline ~Thread() {
|
||||
if(thread) co_delete(thread);
|
||||
}
|
||||
};
|
||||
|
||||
#include <gba/memory/memory.hpp>
|
||||
#include <gba/interface/interface.hpp>
|
||||
#include <gba/scheduler/scheduler.hpp>
|
||||
#include <gba/system/system.hpp>
|
||||
#include <gba/cartridge/cartridge.hpp>
|
||||
#include <gba/cpu/cpu.hpp>
|
||||
#include <gba/ppu/ppu.hpp>
|
||||
#include <gba/apu/apu.hpp>
|
||||
#include <gba/video/video.hpp>
|
||||
}
|
||||
|
||||
#endif
|
149
gba/interface/interface.cpp
Normal file
149
gba/interface/interface.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
|
||||
string Interface::title() {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
double Interface::videoFrequency() {
|
||||
return 16777216.0 / (228.0 * 1232.0);
|
||||
}
|
||||
|
||||
double Interface::audioFrequency() {
|
||||
return 16777216.0 / 512.0;
|
||||
}
|
||||
|
||||
bool Interface::loaded() {
|
||||
return cartridge.loaded();
|
||||
}
|
||||
|
||||
unsigned Interface::group(unsigned id) {
|
||||
switch(id) {
|
||||
case ID::BIOS:
|
||||
return ID::System;
|
||||
case ID::Manifest:
|
||||
case ID::ROM:
|
||||
case ID::RAM:
|
||||
case ID::EEPROM:
|
||||
case ID::FlashROM:
|
||||
return ID::GameBoyAdvance;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
void Interface::load(unsigned id) {
|
||||
cartridge.load();
|
||||
}
|
||||
|
||||
void Interface::save() {
|
||||
for(auto& memory : cartridge.memory) {
|
||||
interface->saveRequest(memory.id, memory.name);
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::load(unsigned id, const stream& stream) {
|
||||
if(id == ID::BIOS) {
|
||||
stream.read(bios.data, min(bios.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::Manifest) cartridge.information.markup = stream.text();
|
||||
|
||||
if(id == ID::ROM) {
|
||||
stream.read(cartridge.rom.data, min(cartridge.rom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::RAM) {
|
||||
stream.read(cartridge.ram.data, min(cartridge.ram.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::EEPROM) {
|
||||
stream.read(cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::FlashROM) {
|
||||
stream.read(cartridge.flashrom.data, min(cartridge.flashrom.size, stream.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::save(unsigned id, const stream& stream) {
|
||||
if(id == ID::RAM) {
|
||||
stream.write(cartridge.ram.data, cartridge.ram.size);
|
||||
}
|
||||
|
||||
if(id == ID::EEPROM) {
|
||||
stream.write(cartridge.eeprom.data, cartridge.eeprom.size);
|
||||
}
|
||||
|
||||
if(id == ID::FlashROM) {
|
||||
stream.write(cartridge.flashrom.data, cartridge.flashrom.size);
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::unload() {
|
||||
save();
|
||||
cartridge.unload();
|
||||
}
|
||||
|
||||
void Interface::power() {
|
||||
system.power();
|
||||
}
|
||||
|
||||
void Interface::reset() {
|
||||
system.power();
|
||||
}
|
||||
|
||||
void Interface::run() {
|
||||
system.run();
|
||||
}
|
||||
|
||||
serializer Interface::serialize() {
|
||||
system.runtosave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
bool Interface::unserialize(serializer& s) {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::paletteUpdate() {
|
||||
video.generate_palette();
|
||||
}
|
||||
|
||||
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.capability.states = true;
|
||||
information.capability.cheats = false;
|
||||
|
||||
media.append({ID::GameBoyAdvance, "Game Boy Advance", "gba", true});
|
||||
|
||||
{
|
||||
Device device{0, ID::Device, "Controller"};
|
||||
device.input.append({0, 0, "A" });
|
||||
device.input.append({1, 0, "B" });
|
||||
device.input.append({2, 0, "Select"});
|
||||
device.input.append({3, 0, "Start" });
|
||||
device.input.append({4, 0, "Right" });
|
||||
device.input.append({5, 0, "Left" });
|
||||
device.input.append({6, 0, "Up" });
|
||||
device.input.append({7, 0, "Down" });
|
||||
device.input.append({8, 0, "R" });
|
||||
device.input.append({9, 0, "L" });
|
||||
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
port.append({0, "Device", {device[0]}});
|
||||
}
|
||||
|
||||
}
|
58
gba/interface/interface.hpp
Normal file
58
gba/interface/interface.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef GBA_HPP
|
||||
namespace GameBoyAdvance {
|
||||
#endif
|
||||
|
||||
struct ID {
|
||||
enum : unsigned {
|
||||
System,
|
||||
GameBoyAdvance,
|
||||
};
|
||||
|
||||
enum : unsigned {
|
||||
BIOS,
|
||||
|
||||
Manifest,
|
||||
ROM,
|
||||
RAM,
|
||||
EEPROM,
|
||||
FlashROM,
|
||||
};
|
||||
|
||||
enum : unsigned {
|
||||
Device = 1,
|
||||
};
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
string title();
|
||||
double videoFrequency();
|
||||
double audioFrequency();
|
||||
|
||||
bool loaded();
|
||||
unsigned group(unsigned id);
|
||||
void load(unsigned id);
|
||||
void save();
|
||||
void load(unsigned id, const stream& stream);
|
||||
void save(unsigned id, const stream& stream);
|
||||
void unload();
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
void run();
|
||||
|
||||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void paletteUpdate();
|
||||
|
||||
Interface();
|
||||
|
||||
private:
|
||||
vector<Device> device;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
|
||||
#ifndef GBA_HPP
|
||||
}
|
||||
#endif
|
116
gba/memory/memory.cpp
Normal file
116
gba/memory/memory.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "mmio.cpp"
|
||||
#include "serialization.cpp"
|
||||
Bus bus;
|
||||
|
||||
struct UnmappedMemory : Memory {
|
||||
uint32 read(uint32 addr, uint32 size) { return 0u; }
|
||||
void write(uint32 addr, uint32 size, uint32 word) {}
|
||||
};
|
||||
|
||||
static UnmappedMemory unmappedMemory;
|
||||
|
||||
uint32 Bus::mirror(uint32 addr, uint32 size) {
|
||||
uint32 base = 0;
|
||||
if(size) {
|
||||
uint32 mask = 1 << 27; //28-bit bus
|
||||
while(addr >= size) {
|
||||
while(!(addr & mask)) mask >>= 1;
|
||||
addr -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
base += addr;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
uint32 Bus::speed(uint32 addr, uint32 size) {
|
||||
if(addr & 0x08000000) {
|
||||
static unsigned timing[] = {5, 4, 3, 9};
|
||||
unsigned n = cpu.regs.wait.control.nwait[addr >> 25 & 3];
|
||||
unsigned s = cpu.regs.wait.control.swait[addr >> 25 & 3];
|
||||
n = timing[n];
|
||||
|
||||
switch(addr >> 25 & 3) {
|
||||
case 0: s = s ? 3 : 2; break;
|
||||
case 1: s = s ? 5 : 2; break;
|
||||
case 2: s = s ? 9 : 2; break;
|
||||
case 3: s = n; break;
|
||||
}
|
||||
|
||||
bool sequential = cpu.sequential();
|
||||
if((addr & 0xffff << 1) == 0) sequential = false; //N cycle on 16-bit ROM crossing page boundary (RAM S==N)
|
||||
if(idleflag) sequential = false; //LDR/LDM interrupts instruction fetches
|
||||
|
||||
if(sequential) return s << (size == Word); //16-bit bus requires two transfers for words
|
||||
if(size == Word) n += s;
|
||||
return n;
|
||||
}
|
||||
|
||||
switch(addr >> 24 & 7) {
|
||||
case 0: return 1;
|
||||
case 1: return 1;
|
||||
case 2: return (1 + 15 - cpu.regs.memory.control.ewramwait) << (size == Word);
|
||||
case 3: return 1;
|
||||
case 4: return 1;
|
||||
case 5: return 1 << (size == Word);
|
||||
case 6: return 1 << (size == Word);
|
||||
case 7: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::idle(uint32 addr) {
|
||||
if(addr & 0x08000000) idleflag = true;
|
||||
}
|
||||
|
||||
uint32 Bus::read(uint32 addr, uint32 size) {
|
||||
idleflag = false;
|
||||
if(addr & 0x08000000) return cartridge.read(addr, size);
|
||||
|
||||
switch(addr >> 24 & 7) {
|
||||
case 0: return bios.read(addr, size);
|
||||
case 1: return bios.read(addr, size);
|
||||
case 2: return cpu.ewram_read(addr, size);
|
||||
case 3: return cpu.iwram_read(addr, size);
|
||||
case 4:
|
||||
if((addr & 0xfffffc00) == 0x04000000) return mmio[addr & 0x3ff]->read(addr, size);
|
||||
if((addr & 0xff00ffff) == 0x04000800) return ((MMIO&)cpu).read(0x04000800 | (addr & 3), size);
|
||||
return cpu.pipeline.fetch.instruction;
|
||||
case 5: return ppu.pram_read(addr, size);
|
||||
case 6: return ppu.vram_read(addr, size);
|
||||
case 7: return ppu.oam_read(addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::write(uint32 addr, uint32 size, uint32 word) {
|
||||
idleflag = false;
|
||||
if(addr & 0x08000000) return cartridge.write(addr, size, word);
|
||||
|
||||
switch(addr >> 24 & 7) {
|
||||
case 0: return;
|
||||
case 1: return;
|
||||
case 2: return cpu.ewram_write(addr, size, word);
|
||||
case 3: return cpu.iwram_write(addr, size, word);
|
||||
case 4:
|
||||
if((addr & 0xfffffc00) == 0x04000000) return mmio[addr & 0x3ff]->write(addr, size, word);
|
||||
if((addr & 0xff00ffff) == 0x04000800) return ((MMIO&)cpu).write(0x04000800 | (addr & 3), size, word);
|
||||
return;
|
||||
case 5: return ppu.pram_write(addr, size, word);
|
||||
case 6: return ppu.vram_write(addr, size, word);
|
||||
case 7: return ppu.oam_write(addr, size, word);
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::power() {
|
||||
for(unsigned n = 0; n < 0x400; n++) mmio[n] = &unmappedMemory;
|
||||
idleflag = false;
|
||||
}
|
||||
|
||||
}
|
27
gba/memory/memory.hpp
Normal file
27
gba/memory/memory.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
struct Memory {
|
||||
virtual uint32 read(uint32 addr, uint32 size) = 0;
|
||||
virtual void write(uint32 addr, uint32 size, uint32 word) = 0;
|
||||
};
|
||||
|
||||
struct MMIO : Memory {
|
||||
virtual uint8 read(uint32 addr) = 0;
|
||||
virtual void write(uint32 addr, uint8 data) = 0;
|
||||
uint32 read(uint32 addr, uint32 size);
|
||||
void write(uint32 addr, uint32 size, uint32 word);
|
||||
};
|
||||
|
||||
struct Bus : Memory {
|
||||
Memory* mmio[0x400];
|
||||
bool idleflag;
|
||||
static uint32 mirror(uint32 addr, uint32 size);
|
||||
|
||||
uint32 speed(uint32 addr, uint32 size);
|
||||
void idle(uint32 addr);
|
||||
uint32 read(uint32 addr, uint32 size);
|
||||
void write(uint32 addr, uint32 size, uint32 word);
|
||||
void power();
|
||||
|
||||
void serialize(serializer&);
|
||||
};
|
||||
|
||||
extern Bus bus;
|
43
gba/memory/mmio.cpp
Normal file
43
gba/memory/mmio.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
uint32 MMIO::read(uint32 addr, uint32 size) {
|
||||
uint32 word = 0;
|
||||
|
||||
switch(size) {
|
||||
case Word:
|
||||
addr &= ~3;
|
||||
word |= read(addr + 0) << 0;
|
||||
word |= read(addr + 1) << 8;
|
||||
word |= read(addr + 2) << 16;
|
||||
word |= read(addr + 3) << 24;
|
||||
break;
|
||||
case Half:
|
||||
addr &= ~1;
|
||||
word |= read(addr + 0) << 0;
|
||||
word |= read(addr + 1) << 8;
|
||||
break;
|
||||
case Byte:
|
||||
word |= read(addr + 0) << 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
void MMIO::write(uint32 addr, uint32 size, uint32 word) {
|
||||
switch(size) {
|
||||
case Word:
|
||||
addr &= ~3;
|
||||
write(addr + 0, word >> 0);
|
||||
write(addr + 1, word >> 8);
|
||||
write(addr + 2, word >> 16);
|
||||
write(addr + 3, word >> 24);
|
||||
break;
|
||||
case Half:
|
||||
addr &= ~1;
|
||||
write(addr + 0, word >> 0);
|
||||
write(addr + 1, word >> 8);
|
||||
break;
|
||||
case Byte:
|
||||
write(addr + 0, word >> 0);
|
||||
break;
|
||||
}
|
||||
}
|
3
gba/memory/serialization.cpp
Normal file
3
gba/memory/serialization.cpp
Normal file
@@ -0,0 +1,3 @@
|
||||
void Bus::serialize(serializer& s) {
|
||||
s.integer(idleflag);
|
||||
}
|
156
gba/ppu/background.cpp
Normal file
156
gba/ppu/background.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
void PPU::render_backgrounds() {
|
||||
switch(regs.control.bgmode) {
|
||||
case 0:
|
||||
render_background_linear(regs.bg[3]);
|
||||
render_background_linear(regs.bg[2]);
|
||||
render_background_linear(regs.bg[1]);
|
||||
render_background_linear(regs.bg[0]);
|
||||
break;
|
||||
case 1:
|
||||
render_background_affine(regs.bg[2]);
|
||||
render_background_linear(regs.bg[1]);
|
||||
render_background_linear(regs.bg[0]);
|
||||
break;
|
||||
case 2:
|
||||
render_background_affine(regs.bg[3]);
|
||||
render_background_affine(regs.bg[2]);
|
||||
break;
|
||||
case 3: case 4: case 5:
|
||||
render_background_bitmap(regs.bg[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::render_background_linear(Registers::Background& bg) {
|
||||
if(regs.control.enable[bg.id] == false) return;
|
||||
auto& output = layer[bg.id];
|
||||
|
||||
if(bg.control.mosaic == false || (regs.vcounter % (1 + regs.mosaic.bgvsize)) == 0) {
|
||||
bg.vmosaic = regs.vcounter;
|
||||
}
|
||||
|
||||
uint9 voffset = bg.vmosaic + bg.voffset;
|
||||
uint9 hoffset = bg.hoffset;
|
||||
|
||||
unsigned basemap = bg.control.screenbaseblock << 11;
|
||||
unsigned basechr = bg.control.characterbaseblock << 14;
|
||||
unsigned px = hoffset & 7, py = voffset & 7;
|
||||
|
||||
Tile tile;
|
||||
uint8 data[8];
|
||||
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
if(x == 0 || px & 8) {
|
||||
px &= 7;
|
||||
|
||||
unsigned tx = hoffset / 8, ty = voffset / 8;
|
||||
unsigned offset = (ty & 31) * 32 + (tx & 31);
|
||||
if(bg.control.screensize & 1) if(tx & 32) offset += 32 * 32;
|
||||
if(bg.control.screensize & 2) if(ty & 32) offset += 32 * 32 * (1 + (bg.control.screensize & 1));
|
||||
offset = basemap + offset * 2;
|
||||
uint16 mapdata = vram_read(offset, Half);
|
||||
|
||||
tile.character = mapdata >> 0;
|
||||
tile.hflip = mapdata >> 10;
|
||||
tile.vflip = mapdata >> 11;
|
||||
tile.palette = mapdata >> 12;
|
||||
|
||||
if(bg.control.colormode == 0) {
|
||||
offset = basechr + tile.character * 32 + (py ^ (tile.vflip ? 7 : 0)) * 4;
|
||||
uint32 word = vram_read(offset, Word);
|
||||
for(unsigned n = 0; n < 8; n++) data[n] = (word >> (n * 4)) & 15;
|
||||
} else {
|
||||
offset = basechr + tile.character * 64 + (py ^ (tile.vflip ? 7 : 0)) * 8;
|
||||
uint32 wordlo = vram_read(offset + 0, Word);
|
||||
uint32 wordhi = vram_read(offset + 4, Word);
|
||||
for(unsigned n = 0; n < 4; n++) data[0 + n] = (wordlo >> (n * 8)) & 255;
|
||||
for(unsigned n = 0; n < 4; n++) data[4 + n] = (wordhi >> (n * 8)) & 255;
|
||||
}
|
||||
}
|
||||
|
||||
hoffset++;
|
||||
uint8 color = data[px++ ^ (tile.hflip ? 7 : 0)];
|
||||
|
||||
if(color) {
|
||||
if(bg.control.colormode == 0) output[x].write(true, bg.control.priority, pram[tile.palette * 16 + color]);
|
||||
if(bg.control.colormode == 1) output[x].write(true, bg.control.priority, pram[color]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::render_background_affine(Registers::Background& bg) {
|
||||
if(regs.control.enable[bg.id] == false) return;
|
||||
auto& output = layer[bg.id];
|
||||
|
||||
unsigned basemap = bg.control.screenbaseblock << 11;
|
||||
unsigned basechr = bg.control.characterbaseblock << 14;
|
||||
unsigned screensize = 16 << bg.control.screensize;
|
||||
unsigned screenwrap = (1 << (bg.control.affinewrap ? 7 + bg.control.screensize : 20)) - 1;
|
||||
|
||||
if(bg.control.mosaic == false || (regs.vcounter % (1 + regs.mosaic.bgvsize)) == 0) {
|
||||
bg.hmosaic = bg.lx;
|
||||
bg.vmosaic = bg.ly;
|
||||
}
|
||||
|
||||
int28 fx = bg.hmosaic;
|
||||
int28 fy = bg.vmosaic;
|
||||
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
unsigned cx = (fx >> 8) & screenwrap, tx = cx / 8, px = cx & 7;
|
||||
unsigned cy = (fy >> 8) & screenwrap, ty = cy / 8, py = cy & 7;
|
||||
|
||||
if(tx < screensize && ty < screensize) {
|
||||
uint8 character = vram[basemap + ty * screensize + tx];
|
||||
uint8 color = vram[basechr + (character * 64) + py * 8 + px];
|
||||
if(color) output[x].write(true, bg.control.priority, pram[color]);
|
||||
}
|
||||
|
||||
fx += bg.pa;
|
||||
fy += bg.pc;
|
||||
}
|
||||
|
||||
bg.lx += bg.pb;
|
||||
bg.ly += bg.pd;
|
||||
}
|
||||
|
||||
void PPU::render_background_bitmap(Registers::Background& bg) {
|
||||
if(regs.control.enable[bg.id] == false) return;
|
||||
auto& output = layer[bg.id];
|
||||
|
||||
uint1 depth = regs.control.bgmode != 4; //0 = 8-bit (Mode 4), 1 = 15-bit (Mode 3, Mode 5)
|
||||
unsigned basemap = regs.control.bgmode == 3 ? 0 : 0xa000 * regs.control.frame;
|
||||
|
||||
unsigned width = regs.control.bgmode == 5 ? 160 : 240;
|
||||
unsigned height = regs.control.bgmode == 5 ? 128 : 160;
|
||||
unsigned size = depth ? Half : Byte;
|
||||
|
||||
if(bg.control.mosaic == false || (regs.vcounter % (1 + regs.mosaic.bgvsize)) == 0) {
|
||||
bg.hmosaic = bg.lx;
|
||||
bg.vmosaic = bg.ly;
|
||||
}
|
||||
|
||||
int28 fx = bg.hmosaic;
|
||||
int28 fy = bg.vmosaic;
|
||||
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
unsigned px = fx >> 8;
|
||||
unsigned py = fy >> 8;
|
||||
|
||||
if(px < width && py < height) {
|
||||
unsigned offset = py * width + px;
|
||||
unsigned color = vram_read(basemap + (offset << depth), size);
|
||||
|
||||
if(depth || color) { //8bpp color 0 is transparent; 15bpp color is always opaque
|
||||
if(depth == 0) color = pram[color];
|
||||
if(depth == 1) color = color & 0x7fff;
|
||||
output[x].write(true, bg.control.priority, color);
|
||||
}
|
||||
}
|
||||
|
||||
fx += bg.pa;
|
||||
fy += bg.pc;
|
||||
}
|
||||
|
||||
bg.lx += bg.pb;
|
||||
bg.ly += bg.pd;
|
||||
}
|
169
gba/ppu/memory.cpp
Normal file
169
gba/ppu/memory.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
uint32 PPU::vram_read(uint32 addr, uint32 size) {
|
||||
addr &= (addr & 0x10000) ? 0x17fff : 0x0ffff;
|
||||
|
||||
switch(size) {
|
||||
case Word:
|
||||
addr &= ~3;
|
||||
return vram[addr + 0] << 0 | vram[addr + 1] << 8 | vram[addr + 2] << 16 | vram[addr + 3] << 24;
|
||||
case Half:
|
||||
addr &= ~1;
|
||||
return vram[addr + 0] << 0 | vram[addr + 1] << 8;
|
||||
case Byte:
|
||||
return vram[addr];
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::vram_write(uint32 addr, uint32 size, uint32 word) {
|
||||
addr &= (addr & 0x10000) ? 0x17fff : 0x0ffff;
|
||||
|
||||
switch(size) {
|
||||
case Word:
|
||||
addr &= ~3;
|
||||
vram[addr + 0] = word >> 0;
|
||||
vram[addr + 1] = word >> 8;
|
||||
vram[addr + 2] = word >> 16;
|
||||
vram[addr + 3] = word >> 24;
|
||||
break;
|
||||
case Half:
|
||||
addr &= ~1;
|
||||
vram[addr + 0] = word >> 0;
|
||||
vram[addr + 1] = word >> 8;
|
||||
break;
|
||||
case Byte:
|
||||
addr &= ~1;
|
||||
vram[addr + 0] = word;
|
||||
vram[addr + 1] = word;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 PPU::pram_read(uint32 addr, uint32 size) {
|
||||
if(size == Word) return pram_read(addr & ~2, Half) << 0 | pram_read(addr | 2, Half) << 16;
|
||||
if(size == Byte) return pram_read(addr, Half) >> ((addr & 1) * 8);
|
||||
return pram[addr >> 1 & 511];
|
||||
}
|
||||
|
||||
void PPU::pram_write(uint32 addr, uint32 size, uint32 word) {
|
||||
if(size == Word) {
|
||||
pram_write(addr & ~2, Half, word >> 0);
|
||||
pram_write(addr | 2, Half, word >> 16);
|
||||
return;
|
||||
}
|
||||
|
||||
if(size == Byte) {
|
||||
return pram_write(addr, Half, word << 8 | word << 0);
|
||||
}
|
||||
|
||||
pram[addr >> 1 & 511] = word & 0x7fff;
|
||||
}
|
||||
|
||||
uint32 PPU::oam_read(uint32 addr, uint32 size) {
|
||||
if(size == Word) return oam_read(addr & ~2, Half) << 0 | oam_read(addr | 2, Half) << 16;
|
||||
if(size == Byte) return oam_read(addr, Half) >> ((addr & 1) * 8);
|
||||
|
||||
auto& obj = object[addr >> 3 & 127];
|
||||
auto& par = objectparam[addr >> 5 & 31];
|
||||
|
||||
switch(addr & 6) {
|
||||
|
||||
case 0: return (
|
||||
(obj.y << 0)
|
||||
| (obj.affine << 8)
|
||||
| (obj.affinesize << 9)
|
||||
| (obj.mode << 10)
|
||||
| (obj.mosaic << 12)
|
||||
| (obj.colors << 13)
|
||||
| (obj.shape << 14)
|
||||
);
|
||||
|
||||
case 2: return (
|
||||
(obj.x << 0)
|
||||
| (obj.affineparam << 9)
|
||||
| (obj.hflip << 12)
|
||||
| (obj.vflip << 13)
|
||||
| (obj.size << 14)
|
||||
);
|
||||
|
||||
case 4: return (
|
||||
(obj.character << 0)
|
||||
| (obj.priority << 10)
|
||||
| (obj.palette << 12)
|
||||
);
|
||||
|
||||
case 6:
|
||||
switch(addr >> 3 & 3) {
|
||||
case 0: return par.pa;
|
||||
case 1: return par.pb;
|
||||
case 2: return par.pc;
|
||||
case 3: return par.pd;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::oam_write(uint32 addr, uint32 size, uint32 word) {
|
||||
if(size == Word) {
|
||||
oam_write(addr & ~2, Half, word >> 0);
|
||||
oam_write(addr | 2, Half, word >> 16);
|
||||
return;
|
||||
}
|
||||
|
||||
if(size == Byte) {
|
||||
return oam_write(addr, Half, word << 8 | word << 0);
|
||||
}
|
||||
|
||||
auto& obj = object[addr >> 3 & 127];
|
||||
auto& par = objectparam[addr >> 5 & 31];
|
||||
switch(addr & 6) {
|
||||
|
||||
case 0:
|
||||
obj.y = word >> 0;
|
||||
obj.affine = word >> 8;
|
||||
obj.affinesize = word >> 9;
|
||||
obj.mode = word >> 10;
|
||||
obj.mosaic = word >> 12;
|
||||
obj.colors = word >> 13;
|
||||
obj.shape = word >> 14;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
obj.x = word >> 0;
|
||||
obj.affineparam = word >> 9;
|
||||
obj.hflip = word >> 12;
|
||||
obj.vflip = word >> 13;
|
||||
obj.size = word >> 14;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
obj.character = word >> 0;
|
||||
obj.priority = word >> 10;
|
||||
obj.palette = word >> 12;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
switch(addr >> 3 & 3) {
|
||||
case 0: par.pa = word; break;
|
||||
case 1: par.pb = word; break;
|
||||
case 2: par.pc = word; break;
|
||||
case 3: par.pd = word; break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static unsigned widths[] = {
|
||||
8, 16, 32, 64,
|
||||
16, 32, 32, 64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 8, 8, //invalid modes
|
||||
};
|
||||
|
||||
static unsigned heights[] = {
|
||||
8, 16, 32, 64,
|
||||
8, 8, 16, 32,
|
||||
16, 32, 32, 64,
|
||||
8, 8, 8, 8, //invalid modes
|
||||
};
|
||||
|
||||
obj.width = widths [obj.shape * 4 + obj.size];
|
||||
obj.height = heights[obj.shape * 4 + obj.size];
|
||||
}
|
200
gba/ppu/mmio.cpp
Normal file
200
gba/ppu/mmio.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
uint8 PPU::read(uint32 addr) {
|
||||
switch(addr) {
|
||||
|
||||
//DISPCNT
|
||||
case 0x04000000: return regs.control >> 0;
|
||||
case 0x04000001: return regs.control >> 8;
|
||||
|
||||
//GRSWP
|
||||
case 0x04000002: return regs.greenswap;
|
||||
case 0x04000003: return 0u;
|
||||
|
||||
//DISPSTAT
|
||||
case 0x04000004: return regs.status >> 0;
|
||||
case 0x04000005: return regs.status >> 8;
|
||||
|
||||
//VCOUNT
|
||||
case 0x04000006: return regs.vcounter >> 0;
|
||||
case 0x04000007: return regs.vcounter >> 8;
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//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];
|
||||
|
||||
//BLTCNT
|
||||
case 0x04000050: return regs.blend.control >> 0;
|
||||
case 0x04000051: return regs.blend.control >> 8;
|
||||
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
|
||||
void PPU::write(uint32 addr, uint8 byte) {
|
||||
switch(addr) {
|
||||
|
||||
//DISPCNT
|
||||
case 0x04000000: regs.control = (regs.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000001: regs.control = (regs.control & 0x00ff) | (byte << 8); return;
|
||||
|
||||
//GRSWP
|
||||
case 0x04000002: regs.greenswap = byte >> 0; return;
|
||||
case 0x04000003: return;
|
||||
|
||||
//DISPSTAT
|
||||
case 0x04000004:
|
||||
regs.status.irqvblank = byte >> 3;
|
||||
regs.status.irqhblank = byte >> 4;
|
||||
regs.status.irqvcoincidence = byte >> 5;
|
||||
return;
|
||||
case 0x04000005:
|
||||
regs.status.vcompare = byte;
|
||||
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;
|
||||
bg.control = (bg.control & ~(255 << shift)) | (byte << shift);
|
||||
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);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//WIN0H
|
||||
case 0x04000040: regs.window[0].x2 = byte; return;
|
||||
case 0x04000041: regs.window[0].x1 = byte; return;
|
||||
|
||||
//WIN1H
|
||||
case 0x04000042: regs.window[1].x2 = byte; return;
|
||||
case 0x04000043: regs.window[1].x1 = byte; return;
|
||||
|
||||
//WIN0V
|
||||
case 0x04000044: regs.window[0].y2 = byte; return;
|
||||
case 0x04000045: regs.window[0].y1 = byte; return;
|
||||
|
||||
//WIN1V
|
||||
case 0x04000046: regs.window[1].y2 = byte; return;
|
||||
case 0x04000047: regs.window[1].y1 = byte; 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;
|
||||
|
||||
//MOSAIC
|
||||
case 0x0400004c:
|
||||
regs.mosaic.bghsize = byte >> 0;
|
||||
regs.mosaic.bgvsize = byte >> 4;
|
||||
return;
|
||||
case 0x0400004d:
|
||||
regs.mosaic.objhsize = byte >> 0;
|
||||
regs.mosaic.objvsize = byte >> 4;
|
||||
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;
|
||||
|
||||
//BLDALPHA
|
||||
case 0x04000052: regs.blend.eva = std::min(16, byte & 0x1f); return;
|
||||
case 0x04000053: regs.blend.evb = std::min(16, byte & 0x1f); return;
|
||||
|
||||
//BLDY
|
||||
case 0x04000054: regs.blend.evy = std::min(16, byte & 0x1f); return;
|
||||
case 0x04000055: return;
|
||||
|
||||
}
|
||||
}
|
33
gba/ppu/mosaic.cpp
Normal file
33
gba/ppu/mosaic.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
void PPU::render_mosaic_background(unsigned id) {
|
||||
if(regs.mosaic.bghsize == 0) return;
|
||||
unsigned width = 1 + regs.mosaic.bghsize;
|
||||
auto& buffer = layer[id];
|
||||
|
||||
for(unsigned x = 0; x < 240;) {
|
||||
for(unsigned m = 1; m < width; m++) {
|
||||
if(x + m >= 240) break;
|
||||
buffer[x + m] = buffer[x];
|
||||
}
|
||||
x += width;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::render_mosaic_object() {
|
||||
if(regs.mosaic.objhsize == 0) return;
|
||||
unsigned width = 1 + regs.mosaic.objhsize;
|
||||
auto& buffer = layer[OBJ];
|
||||
|
||||
Pixel mosaicPixel;
|
||||
mosaicPixel.mosaic = false;
|
||||
unsigned counter = 0;
|
||||
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
if(counter == width || mosaicPixel.mosaic == false) {
|
||||
mosaicPixel = buffer[x];
|
||||
if(counter == width) counter = 0;
|
||||
} else {
|
||||
if(buffer[x].mosaic) buffer[x] = mosaicPixel;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
80
gba/ppu/object.cpp
Normal file
80
gba/ppu/object.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
void PPU::render_objects() {
|
||||
if(regs.control.enable[OBJ] == false) return;
|
||||
for(unsigned n = 0; n < 128; n++) render_object(object[n]);
|
||||
}
|
||||
|
||||
//px,py = pixel coordinates within sprite [0,0 - width,height)
|
||||
//fx,fy = affine pixel coordinates
|
||||
//pa,pb,pc,pd = affine pixel adjustments
|
||||
//x,y = adjusted coordinates within sprite (linear = vflip/hflip, affine = rotation/zoom)
|
||||
void PPU::render_object(Object& obj) {
|
||||
uint8 py = regs.vcounter - obj.y;
|
||||
if(obj.affine == 0 && obj.affinesize == 1) return; //hidden
|
||||
if(py >= obj.height << obj.affinesize) return; //offscreen
|
||||
|
||||
auto& output = layer[OBJ];
|
||||
unsigned rowsize = regs.control.objmapping == 0 ? 32 >> obj.colors : obj.width / 8;
|
||||
unsigned baseaddr = obj.character * 32;
|
||||
|
||||
if(obj.mosaic && regs.mosaic.objvsize) {
|
||||
signed mosaicy = (regs.vcounter / (1 + regs.mosaic.objvsize)) * (1 + regs.mosaic.objvsize);
|
||||
py = obj.y >= 160 || mosaicy - obj.y >= 0 ? mosaicy - obj.y : 0;
|
||||
}
|
||||
|
||||
int16 pa = objectparam[obj.affineparam].pa;
|
||||
int16 pb = objectparam[obj.affineparam].pb;
|
||||
int16 pc = objectparam[obj.affineparam].pc;
|
||||
int16 pd = objectparam[obj.affineparam].pd;
|
||||
|
||||
//center-of-sprite coordinates
|
||||
int16 centerx = obj.width / 2;
|
||||
int16 centery = obj.height / 2;
|
||||
|
||||
//origin coordinates (top-left of sprite)
|
||||
int28 originx = -(centerx << obj.affinesize);
|
||||
int28 originy = -(centery << obj.affinesize) + py;
|
||||
|
||||
//fractional pixel coordinates
|
||||
int28 fx = originx * pa + originy * pb;
|
||||
int28 fy = originx * pc + originy * pd;
|
||||
|
||||
for(unsigned px = 0; px < (obj.width << obj.affinesize); px++) {
|
||||
unsigned x, y;
|
||||
if(obj.affine == 0) {
|
||||
x = px;
|
||||
y = py;
|
||||
if(obj.hflip) x ^= obj.width - 1;
|
||||
if(obj.vflip) y ^= obj.height - 1;
|
||||
} else {
|
||||
x = (fx >> 8) + centerx;
|
||||
y = (fy >> 8) + centery;
|
||||
}
|
||||
|
||||
uint9 ox = obj.x + px;
|
||||
if(ox < 240 && x < obj.width && y < obj.height) {
|
||||
unsigned offset = (y / 8) * rowsize + (x / 8);
|
||||
offset = offset * 64 + (y & 7) * 8 + (x & 7);
|
||||
|
||||
uint8 color = object_vram_read(baseaddr + (offset >> !obj.colors));
|
||||
if(obj.colors == 0) color = (x & 1) ? color >> 4 : color & 15;
|
||||
if(color) {
|
||||
if(obj.mode & 2) {
|
||||
windowmask[Obj][ox] = true;
|
||||
} else if(output[ox].enable == false || obj.priority < output[ox].priority) {
|
||||
if(obj.colors == 0) color = obj.palette * 16 + color;
|
||||
output[ox].write(true, obj.priority, pram[256 + color], obj.mode == 1, obj.mosaic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fx += pa;
|
||||
fy += pc;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 PPU::object_vram_read(unsigned addr) const {
|
||||
if(regs.control.bgmode == 3 || regs.control.bgmode == 4 || regs.control.bgmode == 5) {
|
||||
if(addr <= 0x3fff) return 0u;
|
||||
}
|
||||
return vram[0x10000 + (addr & 0x7fff)];
|
||||
}
|
170
gba/ppu/ppu.cpp
Normal file
170
gba/ppu/ppu.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
//pixel: 4 cycles
|
||||
|
||||
//hdraw: 240 pixels ( 960 cycles)
|
||||
//hblank: 68 pixels ( 272 cycles)
|
||||
//scanline: 308 pixels (1232 cycles)
|
||||
|
||||
//vdraw: 160 scanlines (197120 cycles)
|
||||
//vblank: 68 scanlines ( 83776 cycles)
|
||||
//frame: 228 scanlines (280896 cycles)
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "registers.cpp"
|
||||
#include "background.cpp"
|
||||
#include "object.cpp"
|
||||
#include "mosaic.cpp"
|
||||
#include "screen.cpp"
|
||||
#include "mmio.cpp"
|
||||
#include "memory.cpp"
|
||||
#include "serialization.cpp"
|
||||
PPU ppu;
|
||||
|
||||
void PPU::Enter() {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
ppu.main();
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::main() {
|
||||
scanline();
|
||||
}
|
||||
|
||||
void PPU::step(unsigned clocks) {
|
||||
clock += clocks;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
void PPU::power() {
|
||||
create(PPU::Enter, 16777216);
|
||||
|
||||
for(unsigned n = 0; n < 240 * 160; n++) output[n] = 0, blur[n] = 0;
|
||||
|
||||
for(unsigned n = 0; n < 1024; n += 2) pram_write(n, Half, 0x0000);
|
||||
for(unsigned n = 0; n < 1024; n += 2) oam_write(n, Half, 0x0000);
|
||||
|
||||
regs.control = 0;
|
||||
regs.greenswap = 0;
|
||||
regs.status = 0;
|
||||
regs.vcounter = 0;
|
||||
for(auto& bg : regs.bg) {
|
||||
bg.control = 0;
|
||||
bg.hoffset = 0;
|
||||
bg.voffset = 0;
|
||||
bg.pa = 0;
|
||||
bg.pb = 0;
|
||||
bg.pc = 0;
|
||||
bg.pd = 0;
|
||||
bg.x = 0;
|
||||
bg.y = 0;
|
||||
bg.lx = 0;
|
||||
bg.ly = 0;
|
||||
}
|
||||
for(auto& w : regs.window) {
|
||||
w.x1 = 0;
|
||||
w.x2 = 0;
|
||||
w.y1 = 0;
|
||||
w.y2 = 0;
|
||||
}
|
||||
for(auto& f : regs.windowflags) {
|
||||
f = 0;
|
||||
}
|
||||
regs.mosaic.bghsize = 0;
|
||||
regs.mosaic.bgvsize = 0;
|
||||
regs.mosaic.objhsize = 0;
|
||||
regs.mosaic.objvsize = 0;
|
||||
regs.blend.control = 0;
|
||||
regs.blend.eva = 0;
|
||||
regs.blend.evb = 0;
|
||||
regs.blend.evy = 0;
|
||||
|
||||
for(unsigned n = 0x000; n <= 0x055; n++) bus.mmio[n] = this;
|
||||
}
|
||||
|
||||
void PPU::scanline() {
|
||||
cpu.keypad_run();
|
||||
|
||||
regs.status.vblank = regs.vcounter >= 160 && regs.vcounter <= 226;
|
||||
regs.status.vcoincidence = regs.vcounter == regs.status.vcompare;
|
||||
|
||||
if(regs.vcounter == 0) {
|
||||
frame();
|
||||
|
||||
regs.bg[2].lx = regs.bg[2].x;
|
||||
regs.bg[2].ly = regs.bg[2].y;
|
||||
|
||||
regs.bg[3].lx = regs.bg[3].x;
|
||||
regs.bg[3].ly = regs.bg[3].y;
|
||||
}
|
||||
|
||||
if(regs.vcounter == 160) {
|
||||
if(regs.status.irqvblank) cpu.regs.irq.flag.vblank = 1;
|
||||
cpu.dma_vblank();
|
||||
}
|
||||
|
||||
if(regs.status.irqvcoincidence) {
|
||||
if(regs.status.vcoincidence) cpu.regs.irq.flag.vcoincidence = 1;
|
||||
}
|
||||
|
||||
if(regs.vcounter < 160) {
|
||||
if(regs.control.forceblank || cpu.regs.mode == CPU::Registers::Mode::Stop) {
|
||||
render_forceblank();
|
||||
} else {
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
windowmask[0][x] = false;
|
||||
windowmask[1][x] = false;
|
||||
windowmask[2][x] = false;
|
||||
layer[OBJ][x].write(false);
|
||||
layer[BG0][x].write(false);
|
||||
layer[BG1][x].write(false);
|
||||
layer[BG2][x].write(false);
|
||||
layer[BG3][x].write(false);
|
||||
layer[SFX][x].write(true, 3, pram[0]);
|
||||
}
|
||||
render_window(0);
|
||||
render_window(1);
|
||||
render_objects();
|
||||
render_backgrounds();
|
||||
render_screen();
|
||||
}
|
||||
}
|
||||
|
||||
step(960);
|
||||
regs.status.hblank = 1;
|
||||
if(regs.status.irqhblank) cpu.regs.irq.flag.hblank = 1;
|
||||
if(regs.vcounter < 160) cpu.dma_hblank();
|
||||
|
||||
step(240);
|
||||
regs.status.hblank = 0;
|
||||
if(regs.vcounter < 160) cpu.dma_hdma();
|
||||
|
||||
step(32);
|
||||
if(++regs.vcounter == 228) regs.vcounter = 0;
|
||||
}
|
||||
|
||||
void PPU::frame() {
|
||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||
}
|
||||
|
||||
PPU::PPU() {
|
||||
output = new uint32[240 * 160];
|
||||
blur = new uint16[240 * 160];
|
||||
|
||||
regs.bg[0].id = BG0;
|
||||
regs.bg[1].id = BG1;
|
||||
regs.bg[2].id = BG2;
|
||||
regs.bg[3].id = BG3;
|
||||
}
|
||||
|
||||
PPU::~PPU() {
|
||||
delete[] output;
|
||||
delete[] blur;
|
||||
}
|
||||
|
||||
}
|
51
gba/ppu/ppu.hpp
Normal file
51
gba/ppu/ppu.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
struct PPU : Thread, MMIO {
|
||||
uint8 vram[96 * 1024];
|
||||
uint16 pram[512];
|
||||
#include "registers.hpp"
|
||||
#include "state.hpp"
|
||||
uint32* output;
|
||||
uint16* blur;
|
||||
|
||||
static void Enter();
|
||||
void main();
|
||||
void step(unsigned clocks);
|
||||
|
||||
void power();
|
||||
void scanline();
|
||||
void frame();
|
||||
|
||||
uint8 read(uint32 addr);
|
||||
void write(uint32 addr, uint8 byte);
|
||||
|
||||
uint32 vram_read(uint32 addr, uint32 size);
|
||||
void vram_write(uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
uint32 pram_read(uint32 addr, uint32 size);
|
||||
void pram_write(uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
uint32 oam_read(uint32 addr, uint32 size);
|
||||
void oam_write(uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
void render_backgrounds();
|
||||
void render_background_linear(Registers::Background&);
|
||||
void render_background_affine(Registers::Background&);
|
||||
void render_background_bitmap(Registers::Background&);
|
||||
|
||||
void render_objects();
|
||||
void render_object(Object&);
|
||||
uint8 object_vram_read(unsigned addr) const;
|
||||
|
||||
void render_mosaic_background(unsigned id);
|
||||
void render_mosaic_object();
|
||||
|
||||
void render_forceblank();
|
||||
void render_screen();
|
||||
void render_window(unsigned window);
|
||||
unsigned blend(unsigned above, unsigned eva, unsigned below, unsigned evb);
|
||||
|
||||
void serialize(serializer&);
|
||||
PPU();
|
||||
~PPU();
|
||||
};
|
||||
|
||||
extern PPU ppu;
|
138
gba/ppu/registers.cpp
Normal file
138
gba/ppu/registers.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 PPU::Registers::Control::operator=(uint16 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 PPU::Registers::Status::operator=(uint16 source) {
|
||||
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)
|
||||
| (mosaic << 6)
|
||||
| (colormode << 7)
|
||||
| (screenbaseblock << 8)
|
||||
| (affinewrap << 13)
|
||||
| (screensize << 14)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 PPU::Registers::BackgroundControl::operator=(uint16 source) {
|
||||
priority = source >> 0;
|
||||
characterbaseblock = source >> 2;
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint8 PPU::Registers::WindowFlags::operator=(uint8 source) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
uint16 PPU::Registers::BlendControl::operator=(uint16 source) {
|
||||
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();
|
||||
}
|
106
gba/ppu/registers.hpp
Normal file
106
gba/ppu/registers.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
enum : unsigned { OBJ = 0, BG0 = 1, BG1 = 2, BG2 = 3, BG3 = 4, SFX = 5 };
|
||||
enum : unsigned { In0 = 0, In1 = 1, Obj = 2, Out = 3 };
|
||||
|
||||
struct Registers {
|
||||
struct Control {
|
||||
uint3 bgmode;
|
||||
uint1 cgbmode;
|
||||
uint1 frame;
|
||||
uint1 hblank;
|
||||
uint1 objmapping;
|
||||
uint1 forceblank;
|
||||
uint1 enable[5];
|
||||
uint1 enablewindow[3];
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
Control& operator=(const Control&) = delete;
|
||||
} control;
|
||||
|
||||
uint1 greenswap;
|
||||
|
||||
struct Status {
|
||||
uint1 vblank;
|
||||
uint1 hblank;
|
||||
uint1 vcoincidence;
|
||||
uint1 irqvblank;
|
||||
uint1 irqhblank;
|
||||
uint1 irqvcoincidence;
|
||||
uint8 vcompare;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
Status& operator=(const Status&) = delete;
|
||||
} status;
|
||||
|
||||
uint16 vcounter;
|
||||
|
||||
struct BackgroundControl {
|
||||
uint2 priority;
|
||||
uint2 characterbaseblock;
|
||||
uint1 mosaic;
|
||||
uint1 colormode;
|
||||
uint5 screenbaseblock;
|
||||
uint1 affinewrap; //BG2,3 only
|
||||
uint2 screensize;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
BackgroundControl& operator=(const BackgroundControl&) = delete;
|
||||
};
|
||||
|
||||
struct Background {
|
||||
BackgroundControl control;
|
||||
uint9 hoffset;
|
||||
uint9 voffset;
|
||||
|
||||
//BG2,3 only
|
||||
int16 pa, pb, pc, pd;
|
||||
int28 x, y;
|
||||
|
||||
//internal
|
||||
int28 lx, ly;
|
||||
unsigned vmosaic;
|
||||
unsigned hmosaic;
|
||||
unsigned id;
|
||||
} bg[4];
|
||||
|
||||
struct WindowFlags {
|
||||
uint1 enable[6];
|
||||
|
||||
operator uint8() const;
|
||||
uint8 operator=(uint8 source);
|
||||
WindowFlags& operator=(const WindowFlags&) = delete;
|
||||
};
|
||||
|
||||
struct Window {
|
||||
uint8 x1, x2;
|
||||
uint8 y1, y2;
|
||||
} window[2];
|
||||
|
||||
WindowFlags windowflags[4];
|
||||
|
||||
struct Mosaic {
|
||||
uint4 bghsize;
|
||||
uint4 bgvsize;
|
||||
uint4 objhsize;
|
||||
uint4 objvsize;
|
||||
} mosaic;
|
||||
|
||||
struct BlendControl {
|
||||
uint1 above[6];
|
||||
uint1 below[6];
|
||||
uint2 mode;
|
||||
|
||||
operator uint16() const;
|
||||
uint16 operator=(uint16 source);
|
||||
BlendControl& operator=(const BlendControl&) = delete;
|
||||
};
|
||||
|
||||
struct Blend {
|
||||
BlendControl control;
|
||||
uint5 eva;
|
||||
uint5 evb;
|
||||
uint5 evy;
|
||||
} blend;
|
||||
} regs;
|
92
gba/ppu/screen.cpp
Normal file
92
gba/ppu/screen.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
void PPU::render_forceblank() {
|
||||
uint32* line = output + regs.vcounter * 240;
|
||||
uint16* last = blur + regs.vcounter * 240;
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
line[x] = video.palette[(0x7fff + last[x] - ((0x7fff ^ last[x]) & 0x0421)) >> 1];
|
||||
last[x] = 0x7fff;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::render_screen() {
|
||||
uint32* line = output + regs.vcounter * 240;
|
||||
uint16* last = blur + regs.vcounter * 240;
|
||||
|
||||
if(regs.bg[0].control.mosaic) render_mosaic_background(BG0);
|
||||
if(regs.bg[1].control.mosaic) render_mosaic_background(BG1);
|
||||
if(regs.bg[2].control.mosaic) render_mosaic_background(BG2);
|
||||
if(regs.bg[3].control.mosaic) render_mosaic_background(BG3);
|
||||
render_mosaic_object();
|
||||
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
Registers::WindowFlags flags;
|
||||
flags = ~0; //enable all layers if no windows are enabled
|
||||
|
||||
//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];
|
||||
}
|
||||
|
||||
//priority sorting: find topmost two pixels
|
||||
unsigned a = 5, b = 5;
|
||||
for(signed p = 3; p >= 0; p--) {
|
||||
for(signed l = 5; l >= 0; l--) {
|
||||
if(layer[l][x].enable && layer[l][x].priority == p && flags.enable[l]) {
|
||||
b = a;
|
||||
a = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto& above = layer[a];
|
||||
auto& below = layer[b];
|
||||
bool blendabove = regs.blend.control.above[a];
|
||||
bool blendbelow = regs.blend.control.below[b];
|
||||
unsigned color = above[x].color;
|
||||
|
||||
//perform blending, if needed
|
||||
if(flags.enable[SFX] == false) {
|
||||
} else if(above[x].translucent && blendbelow) {
|
||||
color = blend(above[x].color, regs.blend.eva, below[x].color, regs.blend.evb);
|
||||
} else if(regs.blend.control.mode == 1 && blendabove && blendbelow) {
|
||||
color = blend(above[x].color, regs.blend.eva, below[x].color, regs.blend.evb);
|
||||
} else if(regs.blend.control.mode == 2 && blendabove) {
|
||||
color = blend(above[x].color, 16 - regs.blend.evy, 0x7fff, regs.blend.evy);
|
||||
} else if(regs.blend.control.mode == 3 && blendabove) {
|
||||
color = blend(above[x].color, 16 - regs.blend.evy, 0x0000, regs.blend.evy);
|
||||
}
|
||||
|
||||
//output pixel; blend with previous pixel to simulate GBA LCD blur
|
||||
line[x] = video.palette[(color + last[x] - ((color ^ last[x]) & 0x0421)) >> 1];
|
||||
last[x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::render_window(unsigned w) {
|
||||
unsigned y = regs.vcounter;
|
||||
|
||||
unsigned y1 = regs.window[w].y1, y2 = regs.window[w].y2;
|
||||
unsigned x1 = regs.window[w].x1, x2 = regs.window[w].x2;
|
||||
|
||||
if(y2 < y1 || y2 > 160) y2 = 160;
|
||||
if(x2 < x1 || x2 > 240) x2 = 240;
|
||||
|
||||
if(y >= y1 && y < y2) {
|
||||
for(unsigned x = x1; x < x2; x++) {
|
||||
windowmask[w][x] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned PPU::blend(unsigned above, unsigned eva, unsigned below, unsigned evb) {
|
||||
uint5 ar = above >> 0, ag = above >> 5, ab = above >> 10;
|
||||
uint5 br = below >> 0, bg = below >> 5, bb = below >> 10;
|
||||
|
||||
unsigned r = (ar * eva + br * evb) >> 4;
|
||||
unsigned g = (ag * eva + bg * evb) >> 4;
|
||||
unsigned b = (ab * eva + bb * evb) >> 4;
|
||||
|
||||
return min(31, r) << 0 | min(31, g) << 5 | min(31, b) << 10;
|
||||
}
|
123
gba/ppu/serialization.cpp
Normal file
123
gba/ppu/serialization.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
void PPU::serialize(serializer& s) {
|
||||
Thread::serialize(s);
|
||||
|
||||
s.array(vram, 96 * 1024);
|
||||
s.array(pram, 512);
|
||||
|
||||
s.integer(regs.control.bgmode);
|
||||
s.integer(regs.control.cgbmode);
|
||||
s.integer(regs.control.frame);
|
||||
s.integer(regs.control.hblank);
|
||||
s.integer(regs.control.objmapping);
|
||||
s.integer(regs.control.forceblank);
|
||||
for(auto& flag : regs.control.enable) s.integer(flag);
|
||||
for(auto& flag : regs.control.enablewindow) s.integer(flag);
|
||||
|
||||
s.integer(regs.greenswap);
|
||||
|
||||
s.integer(regs.status.vblank);
|
||||
s.integer(regs.status.hblank);
|
||||
s.integer(regs.status.vcoincidence);
|
||||
s.integer(regs.status.irqvblank);
|
||||
s.integer(regs.status.irqhblank);
|
||||
s.integer(regs.status.irqvcoincidence);
|
||||
s.integer(regs.status.vcompare);
|
||||
|
||||
s.integer(regs.vcounter);
|
||||
|
||||
for(auto& bg : regs.bg) {
|
||||
s.integer(bg.control.priority);
|
||||
s.integer(bg.control.characterbaseblock);
|
||||
s.integer(bg.control.mosaic);
|
||||
s.integer(bg.control.colormode);
|
||||
s.integer(bg.control.screenbaseblock);
|
||||
s.integer(bg.control.affinewrap);
|
||||
s.integer(bg.control.screensize);
|
||||
s.integer(bg.hoffset);
|
||||
s.integer(bg.voffset);
|
||||
s.integer(bg.pa);
|
||||
s.integer(bg.pb);
|
||||
s.integer(bg.pc);
|
||||
s.integer(bg.pd);
|
||||
s.integer(bg.x);
|
||||
s.integer(bg.y);
|
||||
s.integer(bg.lx);
|
||||
s.integer(bg.ly);
|
||||
s.integer(bg.vmosaic);
|
||||
s.integer(bg.hmosaic);
|
||||
s.integer(bg.id);
|
||||
}
|
||||
|
||||
for(auto& window : regs.window) {
|
||||
s.integer(window.x1);
|
||||
s.integer(window.x2);
|
||||
s.integer(window.y1);
|
||||
s.integer(window.y2);
|
||||
}
|
||||
|
||||
for(auto& windowflags : regs.windowflags) {
|
||||
for(auto& flag : windowflags.enable) s.integer(flag);
|
||||
}
|
||||
|
||||
s.integer(regs.mosaic.bghsize);
|
||||
s.integer(regs.mosaic.bgvsize);
|
||||
s.integer(regs.mosaic.objhsize);
|
||||
s.integer(regs.mosaic.objvsize);
|
||||
|
||||
for(auto& flag : regs.blend.control.above) s.integer(flag);
|
||||
for(auto& flag : regs.blend.control.below) s.integer(flag);
|
||||
s.integer(regs.blend.control.mode);
|
||||
s.integer(regs.blend.eva);
|
||||
s.integer(regs.blend.evb);
|
||||
s.integer(regs.blend.evy);
|
||||
|
||||
for(unsigned l = 0; l < 6; l++) {
|
||||
for(unsigned p = 0; p < 240; p++) {
|
||||
auto& pixel = layer[l][p];
|
||||
s.integer(pixel.enable);
|
||||
s.integer(pixel.priority);
|
||||
s.integer(pixel.color);
|
||||
s.integer(pixel.translucent);
|
||||
s.integer(pixel.mosaic);
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned w = 0; w < 3; w++) {
|
||||
for(unsigned p = 0; p < 240; p++) {
|
||||
s.integer(windowmask[w][p]);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& value : vmosaic) s.integer(value);
|
||||
for(auto& value : hmosaic) s.integer(value);
|
||||
|
||||
for(auto& obj : object) {
|
||||
s.integer(obj.y);
|
||||
s.integer(obj.affine);
|
||||
s.integer(obj.affinesize);
|
||||
s.integer(obj.mode);
|
||||
s.integer(obj.mosaic);
|
||||
s.integer(obj.colors);
|
||||
s.integer(obj.shape);
|
||||
|
||||
s.integer(obj.x);
|
||||
s.integer(obj.affineparam);
|
||||
s.integer(obj.hflip);
|
||||
s.integer(obj.vflip);
|
||||
s.integer(obj.size);
|
||||
|
||||
s.integer(obj.character);
|
||||
s.integer(obj.priority);
|
||||
s.integer(obj.palette);
|
||||
|
||||
s.integer(obj.width);
|
||||
s.integer(obj.height);
|
||||
}
|
||||
|
||||
for(auto& par : objectparam) {
|
||||
s.integer(par.pa);
|
||||
s.integer(par.pb);
|
||||
s.integer(par.pc);
|
||||
s.integer(par.pd);
|
||||
}
|
||||
}
|
55
gba/ppu/state.hpp
Normal file
55
gba/ppu/state.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
struct Pixel {
|
||||
bool enable;
|
||||
unsigned priority;
|
||||
unsigned color;
|
||||
|
||||
//objects only
|
||||
bool translucent;
|
||||
bool mosaic;
|
||||
|
||||
alwaysinline void write(bool e) { enable = e; }
|
||||
alwaysinline void write(bool e, unsigned p, unsigned c) { enable = e; priority = p; color = c; }
|
||||
alwaysinline void write(bool e, unsigned p, unsigned c, bool t, bool m) { enable = e; priority = p; color = c; translucent = t; mosaic = m; }
|
||||
} layer[6][240];
|
||||
|
||||
bool windowmask[3][240];
|
||||
unsigned vmosaic[5];
|
||||
unsigned hmosaic[5];
|
||||
|
||||
struct Object {
|
||||
uint8 y;
|
||||
uint1 affine;
|
||||
uint1 affinesize;
|
||||
uint2 mode;
|
||||
uint1 mosaic;
|
||||
uint1 colors; //0 = 16, 1 = 256
|
||||
uint2 shape; //0 = square, 1 = horizontal, 2 = vertical
|
||||
|
||||
uint9 x;
|
||||
uint5 affineparam;
|
||||
uint1 hflip;
|
||||
uint1 vflip;
|
||||
uint2 size;
|
||||
|
||||
uint10 character;
|
||||
uint2 priority;
|
||||
uint4 palette;
|
||||
|
||||
//ancillary data
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
} object[128];
|
||||
|
||||
struct ObjectParam {
|
||||
int16 pa;
|
||||
int16 pb;
|
||||
int16 pc;
|
||||
int16 pd;
|
||||
} objectparam[32];
|
||||
|
||||
struct Tile {
|
||||
uint10 character;
|
||||
uint1 hflip;
|
||||
uint1 vflip;
|
||||
uint4 palette;
|
||||
};
|
30
gba/scheduler/scheduler.cpp
Normal file
30
gba/scheduler/scheduler.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
void Scheduler::enter() {
|
||||
host = co_active();
|
||||
co_switch(active);
|
||||
}
|
||||
|
||||
void Scheduler::exit(ExitReason reason) {
|
||||
exit_reason = reason;
|
||||
active = co_active();
|
||||
co_switch(host);
|
||||
}
|
||||
|
||||
void Scheduler::power() {
|
||||
host = co_active();
|
||||
active = cpu.thread;
|
||||
}
|
||||
|
||||
Scheduler::Scheduler() {
|
||||
sync = SynchronizeMode::None;
|
||||
exit_reason = ExitReason::UnknownEvent;
|
||||
host = nullptr;
|
||||
active = nullptr;
|
||||
}
|
||||
|
||||
}
|
16
gba/scheduler/scheduler.hpp
Normal file
16
gba/scheduler/scheduler.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
struct Scheduler : property<Scheduler> {
|
||||
enum class SynchronizeMode : unsigned { None, CPU, All } sync;
|
||||
enum class ExitReason : unsigned { UnknownEvent, FrameEvent, SynchronizeEvent };
|
||||
readonly<ExitReason> exit_reason;
|
||||
|
||||
cothread_t host;
|
||||
cothread_t active;
|
||||
|
||||
void enter();
|
||||
void exit(ExitReason);
|
||||
|
||||
void power();
|
||||
Scheduler();
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
20
gba/system/bios.cpp
Normal file
20
gba/system/bios.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
uint32 BIOS::read(uint32 addr, uint32 size) {
|
||||
//GBA BIOS is read-protected; only the BIOS itself can read its own memory
|
||||
//when accessed elsewhere; this returns the last value read by the BIOS program
|
||||
if(cpu.r(15) >= 0x02000000) return mdr;
|
||||
|
||||
if(size == Word) return mdr = read(addr &~ 2, Half) << 0 | read(addr | 2, Half) << 16;
|
||||
if(size == Half) return mdr = read(addr &~ 1, Byte) << 0 | read(addr | 1, Byte) << 8;
|
||||
return mdr = data[addr & 0x3fff];
|
||||
}
|
||||
|
||||
void BIOS::write(uint32 addr, uint32 size, uint32 word) {
|
||||
}
|
||||
|
||||
BIOS::BIOS() {
|
||||
data = new uint8[size = 16384]();
|
||||
}
|
||||
|
||||
BIOS::~BIOS() {
|
||||
delete[] data;
|
||||
}
|
62
gba/system/serialization.cpp
Normal file
62
gba/system/serialization.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
serializer System::serialize() {
|
||||
serializer s(serialize_size);
|
||||
|
||||
unsigned signature = 0x31545342, version = Info::SerializerVersion;
|
||||
char hash[64], description[512];
|
||||
memcpy(&hash, (const char*)cartridge.sha256(), 64);
|
||||
memset(&description, 0, sizeof description);
|
||||
|
||||
s.integer(signature);
|
||||
s.integer(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool System::unserialize(serializer& s) {
|
||||
unsigned signature, version;
|
||||
char hash[64], description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.integer(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
if(signature != 0x31545342) return false;
|
||||
if(version != Info::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serialize_all(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
void System::serialize(serializer& s) {
|
||||
s.integer(bios.size);
|
||||
s.integer(bios.mdr);
|
||||
}
|
||||
|
||||
void System::serialize_all(serializer& s) {
|
||||
cartridge.serialize(s);
|
||||
system.serialize(s);
|
||||
cpu.serialize(s);
|
||||
ppu.serialize(s);
|
||||
apu.serialize(s);
|
||||
bus.serialize(s);
|
||||
}
|
||||
|
||||
void System::serialize_init() {
|
||||
serializer s;
|
||||
|
||||
unsigned signature = 0, version = 0;
|
||||
char hash[64], description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.integer(version);
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serialize_size = s.size();
|
||||
}
|
70
gba/system/system.cpp
Normal file
70
gba/system/system.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "bios.cpp"
|
||||
#include "serialization.cpp"
|
||||
BIOS bios;
|
||||
System system;
|
||||
|
||||
void System::init() {
|
||||
}
|
||||
|
||||
void System::term() {
|
||||
}
|
||||
|
||||
void System::power() {
|
||||
bus.power();
|
||||
cpu.power();
|
||||
ppu.power();
|
||||
apu.power();
|
||||
cartridge.power();
|
||||
scheduler.power();
|
||||
}
|
||||
|
||||
void System::load() {
|
||||
string manifest = string::read({interface->path(ID::System), "manifest.bml"});
|
||||
auto document = Markup::Document(manifest);
|
||||
|
||||
interface->loadRequest(ID::BIOS, document["system/cpu/rom/name"].data);
|
||||
if(!file::exists({interface->path(ID::System), document["system/cpu/rom/name"].data})) {
|
||||
interface->notify("Error: required Game Boy Advance firmware bios.rom not found.\n");
|
||||
}
|
||||
|
||||
serialize_init();
|
||||
}
|
||||
|
||||
void System::run() {
|
||||
while(true) {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) break;
|
||||
}
|
||||
interface->videoRefresh(ppu.output, 4 * 240, 240, 160);
|
||||
}
|
||||
|
||||
void System::runtosave() {
|
||||
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;
|
||||
}
|
||||
|
||||
void System::runthreadtosave() {
|
||||
while(true) {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
interface->videoRefresh(ppu.output, 4 * 240, 240, 160);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
37
gba/system/system.hpp
Normal file
37
gba/system/system.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
enum class Input : unsigned {
|
||||
A, B, Select, Start, Right, Left, Up, Down, R, L,
|
||||
};
|
||||
|
||||
struct BIOS : Memory {
|
||||
uint8* data;
|
||||
unsigned size;
|
||||
uint32 mdr;
|
||||
|
||||
uint32 read(uint32 addr, uint32 size);
|
||||
void write(uint32 addr, uint32 size, uint32 word);
|
||||
|
||||
BIOS();
|
||||
~BIOS();
|
||||
};
|
||||
|
||||
struct System {
|
||||
void init();
|
||||
void term();
|
||||
void load();
|
||||
void power();
|
||||
void run();
|
||||
void runtosave();
|
||||
void runthreadtosave();
|
||||
|
||||
unsigned serialize_size;
|
||||
|
||||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void serialize(serializer&);
|
||||
void serialize_all(serializer&);
|
||||
void serialize_init();
|
||||
};
|
||||
|
||||
extern BIOS bios;
|
||||
extern System system;
|
29
gba/video/video.cpp
Normal file
29
gba/video/video.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
Video video;
|
||||
|
||||
void Video::generate_palette() {
|
||||
for(unsigned color = 0; color < (1 << 15); color++) {
|
||||
uint5 b = color >> 10;
|
||||
uint5 g = color >> 5;
|
||||
uint5 r = color >> 0;
|
||||
|
||||
uint16 R = r << 11 | r << 6 | r << 1 | r >> 4;
|
||||
uint16 G = g << 11 | g << 6 | g << 1 | g >> 4;
|
||||
uint16 B = b << 11 | b << 6 | b << 1 | b >> 4;
|
||||
|
||||
palette[color] = interface->videoColor(color, R, G, B);
|
||||
}
|
||||
}
|
||||
|
||||
Video::Video() {
|
||||
palette = new uint32[1 << 15]();
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
delete[] palette;
|
||||
}
|
||||
|
||||
}
|
9
gba/video/video.hpp
Normal file
9
gba/video/video.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
struct Video {
|
||||
unsigned* palette;
|
||||
void generate_palette();
|
||||
|
||||
Video();
|
||||
~Video();
|
||||
};
|
||||
|
||||
extern Video video;
|
Reference in New Issue
Block a user