Update to v087r16 release.

byuu says:

Fixed the r15 mask per Cydrak.
Added DMA support (immediate + Vblank + Hblank + HDMA) with IRQ support.
Basically only missing FIFO reload mode for the APU on channel 2.
Added background linear renderer (tilemap mode.)
Added really inefficient pixel priority selector, so that all BGs+OBJ
could be visible onscreen at the same time.

As a result of the above:
* Mr. Driller is our first fully playable game
* Bakunetsu Dodge Ball Fighters is also fully playable
* Pinobee no Daibouken is also fully playable

Most games (15 of 16 tested) are now showing *something*, many things
look really really good in fact.

Absolutely essential missing components:
- APU
- CPU timers and their interrupts
- DMA FIFO mode
- OBJ affine mode
- BG affine mode
- BG bitmap mode
- PPU windows (BG and OBJ)
- PPU mosaic
- PPU blending modes
- SRAM / EEPROM (going to rely on a database, not heuristics. Homebrew
  will require a manifest file.)
This commit is contained in:
Tim Allen 2012-04-04 09:50:40 +10:00
parent 79a47d133a
commit b8d0ec29b2
18 changed files with 200 additions and 46 deletions

View File

@ -1,7 +1,7 @@
#ifndef BASE_HPP
#define BASE_HPP
static const char Version[] = "087.15";
static const char Version[] = "087.16";
#include <nall/platform.hpp>
#include <nall/algorithm.hpp>

View File

@ -4,6 +4,7 @@ namespace GBA {
#include "registers.cpp"
#include "mmio.cpp"
#include "dma.cpp"
CPU cpu;
void CPU::Enter() { cpu.enter(); }
@ -28,6 +29,7 @@ void CPU::enter() {
regs.mode = Registers::Mode::Normal;
}
dma_run();
exec();
}
}
@ -72,6 +74,10 @@ void CPU::power() {
regs.mode = Registers::Mode::Normal;
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 = 0x130; n <= 0x133; n++) bus.mmio[n] = this; //Keypad

View File

@ -2,6 +2,7 @@ struct CPU : Processor::ARM, Thread, MMIO {
StaticMemory iwram;
StaticMemory ewram;
#include "registers.hpp"
#include "state.hpp"
static void Enter();
void enter();
@ -14,6 +15,9 @@ struct CPU : Processor::ARM, Thread, MMIO {
uint8 read(uint32 addr);
void write(uint32 addr, uint8 byte);
void dma_run();
void dma_transfer(uint2 channel);
CPU();
};

45
bsnes/gba/cpu/dma.cpp Executable file
View File

@ -0,0 +1,45 @@
void CPU::dma_run() {
for(unsigned n = 0; n < 4; n++) {
if(regs.dma[n].control.enable == false) continue;
switch(regs.dma[n].control.timingmode) {
case 0: break;
case 1: if(pending.dma.vblank == false) continue; break;
case 2: if(pending.dma.hblank == false) continue; break;
case 3: if(pending.dma.hdma == false || n != 3) continue; break;
}
dma_transfer(n);
}
pending.dma.vblank = false;
pending.dma.hblank = false;
pending.dma.hdma = false;
}
void CPU::dma_transfer(uint2 n) {
auto &channel = regs.dma[n];
unsigned size = channel.control.size ? Word : Half;
unsigned seek = channel.control.size ? 4 : 2;
uint16 length = channel.length;
channel.basetarget = channel.target;
do {
uint32 word = bus.read(channel.source, size);
bus.write(channel.target, size, word);
switch(channel.control.sourcemode) {
case 0: channel.source += seek; break;
case 1: channel.source -= seek; break;
}
switch(channel.control.targetmode) {
case 0: channel.target += seek; break;
case 1: channel.target -= seek; break;
case 3: channel.target += seek; break;
}
} while(--length);
if(channel.control.targetmode == 3) channel.target = channel.basetarget;
channel.control.enable = false;
if(channel.control.irq) regs.irq.flag.dma[n] = 1;
}

View File

@ -5,7 +5,7 @@ CPU::Registers::DMA::Control::operator uint16() const {
| (repeat << 9)
| (size << 10)
| (drq << 11)
| (timing << 12)
| (timingmode << 12)
| (irq << 14)
| (enable << 15)
);
@ -17,7 +17,7 @@ uint16 CPU::Registers::DMA::Control::operator=(uint16 source) {
repeat = source >> 9;
size = source >> 10;
drq = source >> 11;
timing = source >> 12;
timingmode = source >> 12;
irq = source >> 14;
enable = source >> 15;
return operator uint16();
@ -78,15 +78,15 @@ CPU::Registers::Interrupt::operator uint16() const {
(vblank << 0)
| (hblank << 1)
| (vcoincidence << 2)
| (timer0 << 3)
| (timer1 << 4)
| (timer2 << 5)
| (timer3 << 6)
| (timer[0] << 3)
| (timer[1] << 4)
| (timer[2] << 5)
| (timer[3] << 6)
| (serial << 7)
| (dma0 << 8)
| (dma1 << 9)
| (dma2 << 10)
| (dma3 << 11)
| (dma[0] << 8)
| (dma[1] << 9)
| (dma[2] << 10)
| (dma[3] << 11)
| (keypad << 12)
| (cartridge << 13)
);
@ -96,15 +96,15 @@ uint16 CPU::Registers::Interrupt::operator=(uint16 source) {
vblank = source & (1 << 0);
hblank = source & (1 << 1);
vcoincidence = source & (1 << 2);
timer0 = source & (1 << 3);
timer1 = source & (1 << 4);
timer2 = source & (1 << 5);
timer3 = source & (1 << 6);
timer[0] = source & (1 << 3);
timer[1] = source & (1 << 4);
timer[2] = source & (1 << 5);
timer[3] = source & (1 << 6);
serial = source & (1 << 7);
dma0 = source & (1 << 8);
dma1 = source & (1 << 9);
dma2 = source & (1 << 10);
dma3 = source & (1 << 11);
dma[0] = source & (1 << 8);
dma[1] = source & (1 << 9);
dma[2] = source & (1 << 10);
dma[3] = source & (1 << 11);
keypad = source & (1 << 12);
cartridge = source & (1 << 13);
return operator uint16();

View File

@ -9,7 +9,7 @@ struct Registers {
uint1 repeat;
uint1 size;
uint1 drq;
uint2 timing;
uint2 timingmode;
uint1 irq;
uint1 enable;
@ -17,6 +17,9 @@ struct Registers {
uint16 operator=(uint16 source);
DMA& operator=(const DMA&) = delete;
} control;
//internal
uint32 basetarget;
} dma[4];
struct TimerControl {
@ -64,15 +67,9 @@ struct Registers {
bool vblank;
bool hblank;
bool vcoincidence;
bool timer0;
bool timer1;
bool timer2;
bool timer3;
bool timer[4];
bool serial;
bool dma0;
bool dma1;
bool dma2;
bool dma3;
bool dma[4];
bool keypad;
bool cartridge;

7
bsnes/gba/cpu/state.hpp Executable file
View File

@ -0,0 +1,7 @@
struct Pending {
struct DMA {
bool vblank;
bool hblank;
bool hdma;
} dma;
} pending;

View File

@ -13,7 +13,7 @@ namespace GBA {
/*
bgba - Game Boy Advance emulator
author: byuu
authors: byuu, Cydrak
license: GPLv3
project started: 2012-03-19
*/

72
bsnes/gba/ppu/background.cpp Executable file
View File

@ -0,0 +1,72 @@
void PPU::render_backgrounds() {
if(regs.control.bgmode == 0) {
render_background_linear(0);
render_background_linear(1);
render_background_linear(2);
render_background_linear(3);
}
if(regs.control.bgmode == 1) {
render_background_linear(0);
render_background_linear(1);
//render_background_affine(2);
}
if(regs.control.bgmode == 2) {
//render_background_affine(2);
//render_background_affine(3);
}
}
void PPU::render_background_linear(unsigned bgnumber) {
for(unsigned n = 0; n < 240; n++) pixel[bgnumber][n].exists = false;
if(regs.control.enablebg[bgnumber] == false) return;
auto &bg = regs.bg[bgnumber];
uint9 voffset = regs.vcounter + 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 == 0) continue; //transparent
if(bg.control.colormode == 0) pixel[bgnumber][x] = { true, palette(tile.palette * 16 + color), bg.control.priority };
if(bg.control.colormode == 1) pixel[bgnumber][x] = { true, palette(color), bg.control.priority };
}
}

View File

@ -1,5 +1,6 @@
void PPU::render_objects() {
for(unsigned n = 0; n < 240; n++) pixel[n].exists = false;
for(unsigned n = 0; n < 240; n++) pixel[4][n].exists = false;
if(regs.control.enableobj == false) return;
for(unsigned n = 0; n < 128; n++) {
auto &obj = object[n];
@ -72,8 +73,8 @@ void PPU::render_object_linear(Object &obj) {
if(obj.colors == 0) color = (px & 1) ? color >> 4 : color & 15;
if(color == 0) continue; //transparent
if(obj.colors == 0) pixel[sx] = { true, palette(256 + obj.palette * 16 + color), obj.priority };
if(obj.colors == 1) pixel[sx] = { true, palette(256 + color), obj.priority };
if(obj.colors == 0) pixel[4][sx] = { true, palette(256 + obj.palette * 16 + color), obj.priority };
if(obj.colors == 1) pixel[4][sx] = { true, palette(256 + color), obj.priority };
}
}

View File

@ -13,6 +13,7 @@
namespace GBA {
#include "registers.cpp"
#include "background.cpp"
#include "object.cpp"
#include "screen.cpp"
#include "mmio.cpp"
@ -85,6 +86,7 @@ void PPU::scanline() {
if(regs.vcounter == 160) {
if(regs.status.irqvblank) cpu.regs.irq.flag.vblank = 1;
cpu.pending.dma.vblank = true;
}
if(regs.status.irqvcoincidence) {
@ -92,17 +94,21 @@ void PPU::scanline() {
}
if(regs.vcounter < 160) {
render_backgrounds();
render_objects();
render_screen();
}
step(256 * 4);
step(1024);
regs.status.hblank = 1;
if(regs.status.irqhblank) cpu.regs.irq.flag.hblank = 1;
cpu.pending.dma.hblank = true;
step( 52 * 4);
step(200);
regs.status.hblank = 0;
cpu.pending.dma.hdma = true;
step(8);
if(++regs.vcounter == 228) regs.vcounter = 0;
}

View File

@ -17,6 +17,9 @@ struct PPU : Thread, MMIO {
uint8 read(uint32 addr);
void write(uint32 addr, uint8 byte);
void render_backgrounds();
void render_background_linear(unsigned bgnumber);
void render_objects();
void render_object_linear(Object&);
void render_object_affine(Object&);

View File

@ -9,7 +9,14 @@ void PPU::render_screen() {
uint16 *line = output + regs.vcounter * 240;
for(unsigned x = 0; x < 240; x++) {
if(pixel[x].exists) line[x] = pixel[x].color;
else line[x] = palette(0) & 0x7fff;
uint15 color = palette(0) & 0x7fff;
for(signed p = 3; p >= 0; p--) {
if(pixel[3][x].exists && pixel[3][x].priority == p) color = pixel[3][x].color;
if(pixel[2][x].exists && pixel[2][x].priority == p) color = pixel[2][x].color;
if(pixel[1][x].exists && pixel[1][x].priority == p) color = pixel[1][x].color;
if(pixel[0][x].exists && pixel[0][x].priority == p) color = pixel[0][x].color;
if(pixel[4][x].exists && pixel[4][x].priority == p) color = pixel[4][x].color;
}
line[x] = color;
}
}

View File

@ -2,7 +2,7 @@ struct Pixel {
bool exists;
uint15 color;
uint2 priority;
} pixel[256];
} pixel[5][256];
struct Object {
uint8 y;
@ -27,3 +27,10 @@ struct Object {
unsigned width;
unsigned height;
} object[128];
struct Tile {
uint10 character;
uint1 hflip;
uint1 vflip;
uint4 palette;
};

View File

@ -16,7 +16,6 @@ void ARM::power() {
crash = false;
r(15).modify = [&] {
pipeline.reload = true;
r(15).data &= cpsr().t ? ~1 : ~3;
};
trace = false;

View File

@ -5,8 +5,8 @@ void ARM::arm_step() {
pipeline.reload = false;
r(15).data &= ~3;
pipeline.fetch.address = r(15);
pipeline.fetch.instruction = read(r(15), Word);
pipeline.fetch.address = r(15) & ~3;
pipeline.fetch.instruction = read(pipeline.fetch.address, Word);
pipeline_step();
step(2);

View File

@ -5,8 +5,8 @@ void ARM::thumb_step() {
pipeline.reload = false;
r(15).data &= ~1;
pipeline.fetch.address = r(15);
pipeline.fetch.instruction = read(r(15), Half);
pipeline.fetch.address = r(15) & ~1;
pipeline.fetch.instruction = read(pipeline.fetch.address, Half);
pipeline_step();
step(1);

View File

@ -66,12 +66,12 @@ void ARM::pipeline_step() {
if(cpsr().t == 0) {
r(15).data += 4;
pipeline.fetch.address = r(15);
pipeline.fetch.instruction = read(r(15), Word);
pipeline.fetch.address = r(15) & ~3;
pipeline.fetch.instruction = read(pipeline.fetch.address, Word);
} else {
r(15).data += 2;
pipeline.fetch.address = r(15);
pipeline.fetch.instruction = read(r(15), Half);
pipeline.fetch.address = r(15) & ~1;
pipeline.fetch.instruction = read(pipeline.fetch.address, Half);
}
}