Commit d4876a83 authored by Screwtape's avatar Screwtape

Update to v103r07 release.

byuu says:

Changelog:

  - gba/cpu: massive code cleanup effort
  - gba/cpu: DMA can run in between active instructions¹
  - gba/cpu: added two-cycle startup delay between DMA activation and
    DMA transfers²
  - processor/spc700: BBC, BBC, CBNE cycle 4 is an idle cycle
  - processor/spc700: ADDW, SUBW, MOVW (read) cycle 4 is an idle cycle

¹: unfortunately, this causes yet another performance penalty for the
poor GBA core =( Also, I think I may have missed disabling DMAs while
the CPU is stopped. I'll fix that in the next WIP.

²: I put the waiting counter decrement at the wrong place, so this
doesn't actually work. Needs to be more like
this:

    auto CPU::step(uint clocks) -> void {
      for(auto _ : range(clocks)) {
        for(auto& timer : this->timer) timer.run();
        for(auto& dma : this->dma) if(dma.active && dma.waiting) dma.waiting--;
        context.clock++;
      }
      ...

    auto CPU::DMA::run() -> bool {
      if(cpu.stopped() || !active || waiting) return false;

      transfer();
      if(irq) cpu.irq.flag |= CPU::Interrupt::DMA0 << id;
      if(drq && id == 3) cpu.irq.flag |= CPU::Interrupt::Cartridge;
      return true;
    }

Of course, the real fix will be restructuring how DMA works, so that
it's always running in parallel with the CPU instead of this weird
design where it tries to run all channels in some kind of loop until no
channels are active anymore whenever one channel is activated.

Not really sure how to design that yet, however.
parent 16f73630
......@@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "103.06";
static const string Version = "103.07";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";
......
......@@ -2,6 +2,7 @@
namespace GameBoyAdvance {
APU apu;
#include "io.cpp"
#include "square.cpp"
#include "square1.cpp"
......@@ -11,7 +12,6 @@ namespace GameBoyAdvance {
#include "sequencer.cpp"
#include "fifo.cpp"
#include "serialization.cpp"
APU apu;
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
......@@ -65,7 +65,7 @@ auto APU::main() -> void {
if(regs.bias.amplitude == 2) lsample &= ~3, rsample &= ~3; //7-bit
if(regs.bias.amplitude == 3) lsample &= ~7, rsample &= ~7; //6-bit
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
if(cpu.stopped()) lsample = 0, rsample = 0;
stream->sample((lsample << 5) / 32768.0, (rsample << 5) / 32768.0);
}
......@@ -75,7 +75,7 @@ auto APU::step(uint clocks) -> void {
}
auto APU::power() -> void {
create(APU::Enter, 16'777'216);
create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(2, frequency() / 64.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
......
......@@ -2,12 +2,12 @@
namespace GameBoyAdvance {
Cartridge cartridge;
#include "mrom.cpp"
#include "sram.cpp"
#include "eeprom.cpp"
#include "flash.cpp"
#include "serialization.cpp"
Cartridge cartridge;
Cartridge::Cartridge() {
mrom.data = new uint8[mrom.size = 32 * 1024 * 1024];
......
......@@ -3,24 +3,24 @@ auto CPU::_idle() -> void {
}
auto CPU::_read(uint mode, uint32 addr) -> uint32 {
uint wait = this->wait(mode, addr);
uint clocks = _wait(mode, addr);
uint word = pipeline.fetch.instruction;
if(addr >= 0x1000'0000) {
prefetchStep(wait);
prefetchStep(clocks);
} else if(addr & 0x0800'0000) {
if(mode & Prefetch && regs.wait.control.prefetch) {
if(mode & Prefetch && wait.prefetch) {
prefetchSync(addr);
word = prefetchRead();
if(mode & Word) word |= prefetchRead() << 16;
} else {
if(!active.dma) prefetchWait();
step(wait - 1);
if(!context.dmaActive) prefetchWait();
step(clocks - 1);
word = cartridge.read(mode, addr);
step(1);
}
} else {
prefetchStep(wait - 1);
prefetchStep(clocks - 1);
if(addr < 0x0200'0000) word = bios.read(mode, addr);
else if(addr < 0x0300'0000) word = readEWRAM(mode, addr);
else if(addr < 0x0400'0000) word = readIWRAM(mode, addr);
......@@ -36,16 +36,16 @@ auto CPU::_read(uint mode, uint32 addr) -> uint32 {
}
auto CPU::_write(uint mode, uint32 addr, uint32 word) -> void {
uint wait = this->wait(mode, addr);
uint clocks = _wait(mode, addr);
if(addr >= 0x1000'0000) {
prefetchStep(wait);
prefetchStep(clocks);
} else if(addr & 0x0800'0000) {
if(!active.dma) prefetchWait();
step(wait);
if(!context.dmaActive) prefetchWait();
step(clocks);
cartridge.write(mode, addr, word);
} else {
prefetchStep(wait);
prefetchStep(clocks);
if(addr < 0x0200'0000);
else if(addr < 0x0300'0000) writeEWRAM(mode, addr, word);
else if(addr < 0x0400'0000) writeIWRAM(mode, addr, word);
......@@ -57,17 +57,17 @@ auto CPU::_write(uint mode, uint32 addr, uint32 word) -> void {
}
}
auto CPU::wait(uint mode, uint32 addr) -> uint {
auto CPU::_wait(uint mode, uint32 addr) -> uint {
if(addr >= 0x1000'0000) return 1; //unmapped
if(addr < 0x0200'0000) return 1;
if(addr < 0x0300'0000) return (16 - regs.memory.control.ewramwait) * (mode & Word ? 2 : 1);
if(addr < 0x0300'0000) return (16 - memory.ewramWait) * (mode & Word ? 2 : 1);
if(addr < 0x0500'0000) return 1;
if(addr < 0x0700'0000) return mode & Word ? 2 : 1;
if(addr < 0x0800'0000) return 1;
static uint timings[] = {5, 4, 3, 9};
uint n = timings[regs.wait.control.nwait[addr >> 25 & 3]];
uint s = regs.wait.control.swait[addr >> 25 & 3];
uint n = timings[wait.nwait[addr >> 25 & 3]];
uint s = wait.swait[addr >> 25 & 3];
switch(addr & 0x0e00'0000) {
case 0x0800'0000: s = s ? 2 : 3; break;
......
......@@ -2,172 +2,90 @@
namespace GameBoyAdvance {
CPU cpu;
#include "prefetch.cpp"
#include "bus.cpp"
#include "io.cpp"
#include "memory.cpp"
#include "dma.cpp"
#include "timer.cpp"
#include "keypad.cpp"
#include "serialization.cpp"
CPU cpu;
CPU::CPU() {
iwram = new uint8[ 32 * 1024];
ewram = new uint8[256 * 1024];
regs.dma[0].source.resize(27); regs.dma[0].run.source.resize(27);
regs.dma[0].target.resize(27); regs.dma[0].run.target.resize(27);
regs.dma[0].length.resize(14); regs.dma[0].run.length.resize(14);
regs.dma[1].source.resize(28); regs.dma[1].run.source.resize(28);
regs.dma[1].target.resize(27); regs.dma[1].run.target.resize(27);
regs.dma[1].length.resize(14); regs.dma[1].run.length.resize(14);
regs.dma[2].source.resize(28); regs.dma[2].run.source.resize(28);
regs.dma[2].target.resize(27); regs.dma[2].run.target.resize(27);
regs.dma[2].length.resize(14); regs.dma[2].run.length.resize(14);
regs.dma[3].source.resize(28); regs.dma[3].run.source.resize(28);
regs.dma[3].target.resize(28); regs.dma[3].run.target.resize(28);
regs.dma[3].length.resize(16); regs.dma[3].run.length.resize(16);
}
CPU::~CPU() {
delete[] iwram;
delete[] ewram;
}
auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
#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);
processor.irqline = irq.ime && (irq.enable & irq.flag);
if(regs.mode == Registers::Mode::Stop) {
if(!(regs.irq.enable & regs.irq.flag & Interrupt::Keypad)) {
syncStep(16); //STOP does not advance timers
} else {
regs.mode = Registers::Mode::Normal;
}
return;
if(stopped()) {
if(!(irq.enable & irq.flag & Interrupt::Keypad)) return step(16);
context.stopped = false;
}
dmaRun();
if(regs.mode == Registers::Mode::Halt) {
if(!(regs.irq.enable & regs.irq.flag)) {
step(16);
} else {
regs.mode = Registers::Mode::Normal;
}
return;
if(halted()) {
if(!(irq.enable & irq.flag)) return step(16);
context.halted = false;
}
exec();
}
auto CPU::step(uint clocks) -> void {
timerStep(clocks);
syncStep(clocks);
}
if(!context.dmaActive) {
context.dmaActive = true;
while(true) {
bool transferred = false;
for(auto& dma : this->dma) transferred |= dma.run();
if(!transferred) break;
}
context.dmaActive = false;
}
for(auto _ : range(clocks)) {
for(auto& timer : this->timer) timer.run();
context.clock++;
}
auto CPU::syncStep(uint clocks) -> void {
Thread::step(clocks);
synchronize(ppu);
synchronize(apu);
}
auto CPU::keypadRun() -> void {
//lookup table to convert button indexes to Emulator::Interface indexes
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1, 7, 6};
if(!regs.keypad.control.enable) return;
bool test = regs.keypad.control.condition; //0 = OR, 1 = AND
for(auto n : range(10)) {
if(!regs.keypad.control.flag[n]) continue;
bool input = platform->inputPoll(0, 0, lookup[n]);
if(regs.keypad.control.condition == 0) test |= input;
if(regs.keypad.control.condition == 1) test &= input;
}
if(test) regs.irq.flag |= Interrupt::Keypad;
}
auto CPU::power() -> void {
create(CPU::Enter, 16'777'216);
ARM::power();
for(auto n : range( 32 * 1024)) iwram[n] = 0;
for(auto n : range(256 * 1024)) ewram[n] = 0;
for(auto& dma : regs.dma) {
dma.source = 0;
dma.target = 0;
dma.length = 0;
dma.data = 0;
dma.control.targetmode = 0;
dma.control.sourcemode = 0;
dma.control.repeat = 0;
dma.control.size = 0;
dma.control.drq = 0;
dma.control.timingmode = 0;
dma.control.irq = 0;
dma.control.enable = 0;
dma.pending = 0;
dma.run.target = 0;
dma.run.source = 0;
dma.run.length = 0;
}
for(auto& timer : regs.timer) {
timer.period = 0;
timer.reload = 0;
timer.pending = false;
timer.control.frequency = 0;
timer.control.cascade = 0;
timer.control.irq = 0;
timer.control.enable = 0;
}
regs.serial = {};
for(auto& flag : regs.keypad.control.flag) flag = 0;
regs.keypad.control.enable = 0;
regs.keypad.control.condition = 0;
regs.joybus = {};
regs.ime = 0;
regs.irq.enable = 0;
regs.irq.flag = 0;
for(auto& nwait : regs.wait.control.nwait) nwait = 0;
for(auto& swait : regs.wait.control.swait) swait = 0;
regs.wait.control.phi = 0;
regs.wait.control.prefetch = 0;
regs.wait.control.gametype = 0; //0 = GBA, 1 = GBC
regs.memory.control.disable = 0;
regs.memory.control.unknown1 = 0;
regs.memory.control.ewram = 1;
regs.memory.control.ewramwait = 13;
regs.memory.control.unknown2 = 0;
regs.postboot = 0;
regs.mode = Registers::Mode::Normal;
regs.clock = 0;
create(CPU::Enter, system.frequency());
for(auto& byte : iwram) byte = 0x00;
for(auto& byte : ewram) byte = 0x00;
for(auto n : range(4)) dma[n] = {n};
for(auto n : range(4)) timer[n] = {n};
serial = {};
keypad = {};
joybus = {};
irq = {};
wait = {};
memory = {};
prefetch = {};
prefetch.wait = 1;
context = {};
dma[0].source.resize(27); dma[0].latch.source.resize(27);
dma[0].target.resize(27); dma[0].latch.target.resize(27);
dma[0].length.resize(14); dma[0].latch.length.resize(14);
dma[1].source.resize(28); dma[1].latch.source.resize(28);
dma[1].target.resize(27); dma[1].latch.target.resize(27);
dma[1].length.resize(14); dma[1].latch.length.resize(14);
pending.dma.vblank = 0;
pending.dma.hblank = 0;
pending.dma.hdma = 0;
dma[2].source.resize(28); dma[2].latch.source.resize(28);
dma[2].target.resize(27); dma[2].latch.target.resize(27);
dma[2].length.resize(14); dma[2].latch.length.resize(14);
active.dma = false;
dma[3].source.resize(28); dma[3].latch.source.resize(28);
dma[3].target.resize(28); dma[3].latch.target.resize(28);
dma[3].length.resize(16); dma[3].latch.length.resize(16);
for(uint n = 0x0b0; n <= 0x0df; n++) bus.io[n] = this; //DMA
for(uint n = 0x100; n <= 0x10f; n++) bus.io[n] = this; //Timers
......@@ -176,7 +94,7 @@ auto CPU::power() -> void {
for(uint n = 0x134; n <= 0x159; n++) bus.io[n] = this; //Serial
for(uint n = 0x200; n <= 0x209; n++) bus.io[n] = this; //System
for(uint n = 0x300; n <= 0x301; n++) bus.io[n] = this; //System
//0x080-0x083 mirrored via gba/memory/memory.cpp //System
//0x080-0x083 mirrored via gba/memory/memory.cpp //System
}
}
......@@ -2,47 +2,46 @@ struct CPU : Processor::ARM, Thread, IO {
using ARM::read;
using ARM::write;
struct Interrupt {
enum : uint {
VBlank = 0x0001,
HBlank = 0x0002,
VCoincidence = 0x0004,
Timer0 = 0x0008,
Timer1 = 0x0010,
Timer2 = 0x0020,
Timer3 = 0x0040,
Serial = 0x0080,
DMA0 = 0x0100,
DMA1 = 0x0200,
DMA2 = 0x0400,
DMA3 = 0x0800,
Keypad = 0x1000,
Cartridge = 0x2000,
};
};
#include "registers.hpp"
#include "prefetch.hpp"
#include "state.hpp"
struct Interrupt { enum : uint {
VBlank = 0x0001,
HBlank = 0x0002,
VCoincidence = 0x0004,
Timer0 = 0x0008,
Timer1 = 0x0010,
Timer2 = 0x0020,
Timer3 = 0x0040,
Serial = 0x0080,
DMA0 = 0x0100,
DMA1 = 0x0200,
DMA2 = 0x0400,
DMA3 = 0x0800,
Keypad = 0x1000,
Cartridge = 0x2000,
};};
//cpu.cpp
CPU();
~CPU();
inline auto clock() const -> uint { return context.clock; }
inline auto halted() const -> bool { return context.halted; }
inline auto stopped() const -> bool { return context.stopped; }
//cpu.cpp
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void override;
auto syncStep(uint clocks) -> void;
auto keypadRun() -> void;
auto power() -> void;
//prefetch.cpp
auto prefetchSync(uint32 addr) -> void;
auto prefetchStep(uint clocks) -> void;
auto prefetchWait() -> void;
auto prefetchRead() -> uint16;
//bus.cpp
auto _idle() -> void override;
auto _read(uint mode, uint32 addr) -> uint32 override;
auto _write(uint mode, uint32 addr, uint32 word) -> void override;
auto wait(uint mode, uint32 addr) -> uint;
auto _wait(uint mode, uint32 addr) -> uint;
//io.cpp
auto readIO(uint32 addr) -> uint8;
......@@ -55,22 +54,155 @@ struct CPU : Processor::ARM, Thread, IO {
auto writeEWRAM(uint mode, uint32 addr, uint32 word) -> void;
//dma.cpp
auto dmaRun() -> void;
auto dmaExecute(Registers::DMA& dma) -> void;
auto dmaVblank() -> void;
auto dmaHblank() -> void;
auto dmaHDMA() -> void;
//timer.cpp
auto timerStep(uint clocks) -> void;
auto timerIncrement(uint n) -> void;
auto timerRunFIFO(uint n) -> void;
auto runFIFO(uint n) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
uint8* iwram = nullptr;
uint8* ewram = nullptr;
uint8 iwram[ 32 * 1024];
uint8 ewram[256 * 1024];
//private:
struct DMA {
//dma.cpp
auto run() -> bool;
auto transfer() -> void;
uint2 id;
boolean active;
natural waiting;
uint2 targetMode;
uint2 sourceMode;
uint1 repeat;
uint1 size;
uint1 drq;
uint2 timingMode;
uint1 irq;
uint1 enable;
VariadicNatural source;
VariadicNatural target;
VariadicNatural length;
uint32 data;
struct Latch {
VariadicNatural target;
VariadicNatural source;
VariadicNatural length;
} latch;
} dma[4];
struct Timer {
//timer.cpp
auto run() -> void;
auto step() -> void;
uint2 id;
boolean pending;
uint16 period;
uint16 reload;
uint2 frequency;
uint1 cascade;
uint1 irq;
uint1 enable;
} timer[4];
struct Serial {
uint1 shiftClockSelect;
uint1 shiftClockFrequency;
uint1 transferEnableReceive;
uint1 transferEnableSend;
uint1 startBit;
uint1 transferLength;
uint1 irqEnable;
uint16 data[4];
uint8 data8;
} serial;
struct Keypad {
//auto keypad.cpp
auto run() -> void;
uint1 enable;
uint1 condition;
uint1 flag[10];
} keypad;
struct Joybus {
uint1 sc;
uint1 sd;
uint1 si;
uint1 so;
uint1 scMode;
uint1 sdMode;
uint1 siMode;
uint1 soMode;
uint1 siIRQEnable;
uint2 mode;
uint1 resetSignal;
uint1 receiveComplete;
uint1 sendComplete;
uint1 resetIRQEnable;
uint32 receive;
uint32 transmit;
uint1 receiveFlag;
uint1 sendFlag;
uint2 generalFlag;
} joybus;
struct IRQ {
uint1 ime;
uint16 enable;
uint16 flag;
} irq;
struct Wait {
uint2 nwait[4];
uint1 swait[4];
uint2 phi;
uint1 prefetch;
uint1 gameType;
} wait;
struct Memory {
uint1 disable;
uint3 unknown1;
uint1 ewram = 1;
uint4 ewramWait = 13;
uint4 unknown2;
} memory;
struct {
uint16 slot[8];
uint32 addr; //read location of slot buffer
uint32 load; //write location of slot buffer
integer wait = 1; //number of clocks before next slot load
auto empty() const { return addr == load; }
auto full() const { return load - addr == 16; }
} prefetch;
struct Context {
natural clock;
boolean halted;
boolean stopped;
boolean booted; //set to true by the GBA BIOS
boolean dmaActive;
} context;
};
extern CPU cpu;
auto CPU::dmaRun() -> void {
active.dma = true;
auto CPU::DMA::run() -> bool {
if(!active) return false;
if(waiting && --waiting) return false;
while(true) {
bool transferred = false;
for(auto n : range(4)) {
auto& dma = regs.dma[n];
if(dma.pending) {
dmaExecute(dma);
if(dma.control.irq) regs.irq.flag |= Interrupt::DMA0 << n;
if(dma.control.drq && n == 3) regs.irq.flag |= Interrupt::Cartridge;
transferred = true;
break;
}
}
if(!transferred) break;
}
active.dma = false;
transfer();
if(irq) cpu.irq.flag |= CPU::Interrupt::DMA0 << id;
if(drq && id == 3) cpu.irq.flag |= CPU::Interrupt::Cartridge;
return true;
}
auto CPU::dmaExecute(Registers::DMA& dma) -> void {
uint seek = dma.control.size ? 4 : 2;
uint mode = dma.control.size ? Word : Half;
mode |= dma.run.length == dma.length ? Nonsequential : Sequential;
auto CPU::DMA::transfer() -> void {
uint seek = size ? 4 : 2;
uint mode = size ? Word : Half;
mode |= latch.length == length ? Nonsequential : Sequential;
if(mode & Nonsequential) {
if((dma.source & 0x0800'0000) && (dma.target & 0x0800'0000)) {
if((source & 0x0800'0000) && (target & 0x0800'0000)) {
//ROM -> ROM transfer
} else {
idle();
idle();
cpu.idle();
cpu.idle();
}
}
if(dma.run.source < 0x0200'0000) {
idle(); //cannot access BIOS
if(latch.source < 0x0200'0000) {
cpu.idle(); //cannot access BIOS
} else {
uint32 addr = dma.run.source;
uint32 addr = latch.source;
if(mode & Word) addr &= ~3;
if(mode & Half) addr &= ~1;
dma.data = _read(mode, addr);
data = cpu._read(mode, addr);
}
if(dma.run.target < 0x0200'0000) {
idle(); //cannot access BIOS
if(latch.target < 0x0200'0000) {
cpu.idle(); //cannot access BIOS
} else {
uint32 addr = dma.run.target;
uint32 addr = latch.target;
if(mode & Word) addr &= ~3;
if(mode & Half) addr &= ~1;
_write(mode, addr, dma.data);
cpu._write(mode, addr, data);
}
switch(dma.control.sourcemode) {
case 0: dma.run.source += seek; break;
case 1: dma.run.source -= seek; break;
switch(sourceMode) {
case 0: latch.source += seek; break;
case 1: latch.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;
switch(targetMode) {
case 0: latch.target += seek; break;
case 1: latch.target -= seek; break;
case 3: latch.target += seek; break;
}
if(--dma.run.length == 0) {
dma.pending = false;