Tidy the gameboy core a little. Mainly by moving the execution loops into the bus class.

Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
Adrian Conlon 2017-10-24 00:04:13 +01:00
parent 774b181008
commit a22e59546b
8 changed files with 155 additions and 154 deletions

View File

@ -2,10 +2,12 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include <string>
#include <Rom.h> #include <Rom.h>
#include <Ram.h> #include <Ram.h>
#include <Bus.h> #include <Bus.h>
#include <Register.h>
#include "LR35902.h" #include "LR35902.h"
#include "IoRegisters.h" #include "IoRegisters.h"
@ -49,6 +51,9 @@ namespace EightBit {
void loadBootRom(const std::string& path); void loadBootRom(const std::string& path);
void loadGameRom(const std::string& path); void loadGameRom(const std::string& path);
int runRasterLines();
int runVerticalBlankLines();
protected: protected:
virtual uint8_t& reference(uint16_t address, bool& rom); virtual uint8_t& reference(uint16_t address, bool& rom);
@ -64,6 +69,8 @@ namespace EightBit {
IoRegisters m_ioPorts; // 0xff00 - 0xff7f IoRegisters m_ioPorts; // 0xff00 - 0xff7f
Ram m_highInternalRam; // 0xff80 - 0xffff Ram m_highInternalRam; // 0xff80 - 0xffff
bool m_enabledLCD;
bool m_disableGameRom; bool m_disableGameRom;
bool m_rom; bool m_rom;
@ -80,6 +87,10 @@ namespace EightBit {
void validateCartridgeType(); void validateCartridgeType();
void Bus_WrittenByte(uint16_t address); void Bus_WrittenByte(uint16_t address);
int runRasterLines(int lines);
int runVerticalBlankLines(int lines);
int runRasterLine(int limit);
}; };
} }
} }

View File

@ -40,22 +40,14 @@ namespace EightBit {
virtual void reset() override; virtual void reset() override;
int runRasterLines();
int runVerticalBlankLines();
int singleStep(); int singleStep();
protected: protected:
int runRasterLines(int limit, int lines);
int runVerticalBlankLines(int limit, int lines);
int runRasterLine(int limit);
virtual int execute(uint8_t opcode); virtual int execute(uint8_t opcode);
int step(); int step();
private: private:
Bus& m_bus; Bus& m_bus;
bool m_enabledLCD;
register16_t af; register16_t af;
register16_t bc; register16_t bc;

View File

@ -17,19 +17,19 @@ std::array<int, 8> EightBit::GameBoy::CharacterDefinition::get(int row) const {
std::array<int, 8> returned; std::array<int, 8> returned;
auto planeAddress = m_address + row * 2; const auto planeAddress = m_address + row * 2;
auto planeLow = m_ram->peek(planeAddress); const auto planeLow = m_ram->peek(planeAddress);
auto planeHigh = m_ram->peek(planeAddress + 1); const auto planeHigh = m_ram->peek(planeAddress + 1);
for (int bit = 0; bit < 8; ++bit) { for (int bit = 0; bit < 8; ++bit) {
auto mask = 1 << bit; const auto mask = 1 << bit;
auto bitLow = planeLow & mask ? 1 : 0; const auto bitLow = planeLow & mask ? 1 : 0;
auto bitHigh = planeHigh & mask ? 0b10 : 0; const auto bitHigh = planeHigh & mask ? 0b10 : 0;
auto colour = bitHigh | bitLow; const auto colour = bitHigh | bitLow;
returned[7 - bit] = colour; returned[7 - bit] = colour;
} }

View File

@ -1,5 +1,6 @@
#include "stdafx.h" #include "stdafx.h"
#include "GameBoyBus.h" #include "GameBoyBus.h"
#include "Display.h"
EightBit::GameBoy::Bus::Bus() EightBit::GameBoy::Bus::Bus()
: m_cpu(*this), : m_cpu(*this),
@ -10,6 +11,7 @@ EightBit::GameBoy::Bus::Bus()
m_lowInternalRam(0x2000), m_lowInternalRam(0x2000),
m_oamRam(0xa0), m_oamRam(0xa0),
m_ioPorts(*this), m_ioPorts(*this),
m_enabledLCD(false),
m_highInternalRam(0x80), m_highInternalRam(0x80),
m_disableGameRom(false), m_disableGameRom(false),
m_rom(false), m_rom(false),
@ -24,8 +26,8 @@ EightBit::GameBoy::Bus::Bus()
} }
void EightBit::GameBoy::Bus::reset() { void EightBit::GameBoy::Bus::reset() {
m_ioPorts.reset(); IO().reset();
m_cpu.initialise(); CPU().initialise();
} }
void EightBit::GameBoy::Bus::loadBootRom(const std::string& path) { void EightBit::GameBoy::Bus::loadBootRom(const std::string& path) {
@ -113,7 +115,7 @@ void EightBit::GameBoy::Bus::validateCartridgeType() {
// ROM size // ROM size
{ {
size_t gameRomBanks = 0; size_t gameRomBanks = 0;
int romSizeSpecification = m_gameRomBanks[0].peek(0x148); const int romSizeSpecification = m_gameRomBanks[0].peek(0x148);
switch (romSizeSpecification) { switch (romSizeSpecification) {
case 0x52: case 0x52:
gameRomBanks = 72; gameRomBanks = 72;
@ -166,7 +168,7 @@ void EightBit::GameBoy::Bus::validateCartridgeType() {
uint8_t& EightBit::GameBoy::Bus::reference(uint16_t address, bool& rom) { uint8_t& EightBit::GameBoy::Bus::reference(uint16_t address, bool& rom) {
rom = true; rom = true;
if ((address < 0x100) && m_ioPorts.bootRomEnabled()) if ((address < 0x100) && IO().bootRomEnabled())
return m_bootRom.reference(address); return m_bootRom.reference(address);
if ((address < 0x4000) && gameRomEnabled()) if ((address < 0x4000) && gameRomEnabled())
return m_gameRomBanks[0].reference(address); return m_gameRomBanks[0].reference(address);
@ -187,6 +189,111 @@ uint8_t& EightBit::GameBoy::Bus::reference(uint16_t address, bool& rom) {
if (address < IoRegisters::BASE) if (address < IoRegisters::BASE)
return rom = true, placeDATA(0xff); return rom = true, placeDATA(0xff);
if (address < 0xff80) if (address < 0xff80)
return m_ioPorts.reference(address - IoRegisters::BASE); return IO().reference(address - IoRegisters::BASE);
return m_highInternalRam.reference(address - 0xff80); return m_highInternalRam.reference(address - 0xff80);
} }
int EightBit::GameBoy::Bus::runRasterLines() {
m_enabledLCD = !!(IO().peek(IoRegisters::LCDC) & IoRegisters::LcdEnable);
IO().resetLY();
return runRasterLines(Display::RasterHeight);
}
int EightBit::GameBoy::Bus::runRasterLines(int lines) {
int count = 0;
int allowed = CyclesPerLine;
for (int line = 0; line < lines; ++line) {
auto executed = runRasterLine(allowed);
count += executed;
allowed = CyclesPerLine - (executed - CyclesPerLine);
}
return count;
}
int EightBit::GameBoy::Bus::runRasterLine(int limit) {
/*
A scanline normally takes 456 clocks (912 clocks in double speed
mode) to complete. A scanline starts in mode 2, then goes to
mode 3 and, when the LCD controller has finished drawing the
line (the timings depend on lots of things) it goes to mode 0.
During lines 144-153 the LCD controller is in mode 1.
Line 153 takes only a few clocks to complete (the exact
timings are below). The rest of the clocks of line 153 are
spent in line 0 in mode 1!
During mode 0 and mode 1 the CPU can access both VRAM and OAM.
During mode 2 the CPU can only access VRAM, not OAM.
During mode 3 OAM and VRAM can't be accessed.
In GBC mode the CPU can't access Palette RAM(FF69h and FF6Bh)
during mode 3.
A scanline normally takes 456 clocks(912 clocks in double speed mode) to complete.
A scanline starts in mode 2, then goes to mode 3 and , when the LCD controller has
finished drawing the line(the timings depend on lots of things) it goes to mode 0.
During lines 144 - 153 the LCD controller is in mode 1.
Line 153 takes only a few clocks to complete(the exact timings are below).
The rest of the clocks of line 153 are spent in line 0 in mode 1!
*/
int count = 0;
if (m_enabledLCD) {
if ((IO().peek(IoRegisters::STAT) & Processor::Bit6) && (IO().peek(IoRegisters::LYC) == IO().peek(IoRegisters::LY)))
IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus);
// Mode 2, OAM unavailable
IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::SearchingOamRam);
if (IO().peek(IoRegisters::STAT) & Processor::Bit5)
IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus);
count += CPU().run(80); // ~19us
// Mode 3, OAM/VRAM unavailable
IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::TransferringDataToLcd);
count += CPU().run(170); // ~41us
// Mode 0
IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::HBlank);
if (IO().peek(IoRegisters::STAT) & Processor::Bit3)
IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus);
count += CPU().run(limit - count); // ~48.6us
IO().incrementLY();
} else {
count += CPU().run(CyclesPerLine);
}
return count;
}
int EightBit::GameBoy::Bus::runVerticalBlankLines() {
const auto lines = TotalLineCount - Display::RasterHeight;
return runVerticalBlankLines(lines);
}
int EightBit::GameBoy::Bus::runVerticalBlankLines(int lines) {
/*
Vertical Blank interrupt is triggered when the LCD
controller enters the VBL screen mode (mode 1, LY=144).
This happens once per frame, so this interrupt is
triggered 59.7 times per second. During this period the
VRAM and OAM can be accessed freely, so it's the best
time to update graphics (for example, use the OAM DMA to
update sprites for next frame, or update tiles to make
animations).
This period lasts 4560 clocks in normal speed mode and
9120 clocks in double speed mode. That's exactly the
time needed to draw 10 scanlines.
The VBL interrupt isn't triggered when the LCD is
powered off or on, even when it was on VBL mode.
It's only triggered when the VBL period starts.
*/
if (m_enabledLCD) {
IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::VBlank);
if (IO().peek(IoRegisters::STAT) & Processor::Bit4)
IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus);
IO().triggerInterrupt(IoRegisters::Interrupts::VerticalBlank);
}
return runRasterLines(lines);
}

View File

@ -1,14 +1,12 @@
#include "stdafx.h" #include "stdafx.h"
#include "LR35902.h" #include "LR35902.h"
#include "GameBoyBus.h" #include "GameBoyBus.h"
#include "Display.h"
// based on http://www.z80.info/decoding.htm // based on http://www.z80.info/decoding.htm
EightBit::GameBoy::LR35902::LR35902(Bus& memory) EightBit::GameBoy::LR35902::LR35902(Bus& memory)
: IntelProcessor(memory), : IntelProcessor(memory),
m_bus(memory), m_bus(memory),
m_enabledLCD(false),
m_ime(false), m_ime(false),
m_stopped(false), m_stopped(false),
m_prefixCB(false) { m_prefixCB(false) {
@ -187,14 +185,14 @@ void EightBit::GameBoy::LR35902::compare(uint8_t& f, uint8_t check, uint8_t valu
uint8_t EightBit::GameBoy::LR35902::rlc(uint8_t& f, uint8_t operand) { uint8_t EightBit::GameBoy::LR35902::rlc(uint8_t& f, uint8_t operand) {
clearFlag(f, NF | HC | ZF); clearFlag(f, NF | HC | ZF);
auto carry = operand & Bit7; const auto carry = operand & Bit7;
setFlag(f, CF, carry); setFlag(f, CF, carry);
return (operand << 1) | (carry >> 7); return (operand << 1) | (carry >> 7);
} }
uint8_t EightBit::GameBoy::LR35902::rrc(uint8_t& f, uint8_t operand) { uint8_t EightBit::GameBoy::LR35902::rrc(uint8_t& f, uint8_t operand) {
clearFlag(f, NF | HC | ZF); clearFlag(f, NF | HC | ZF);
auto carry = operand & Bit0; const auto carry = operand & Bit0;
setFlag(f, CF, carry); setFlag(f, CF, carry);
return (operand >> 1) | (carry << 7); return (operand >> 1) | (carry << 7);
} }
@ -237,7 +235,7 @@ uint8_t EightBit::GameBoy::LR35902::srl(uint8_t& f, uint8_t operand) {
} }
uint8_t EightBit::GameBoy::LR35902::bit(uint8_t& f, int n, uint8_t operand) { uint8_t EightBit::GameBoy::LR35902::bit(uint8_t& f, int n, uint8_t operand) {
auto carry = f & CF; const auto carry = f & CF;
uint8_t discarded = operand; uint8_t discarded = operand;
andr(f, discarded, 1 << n); andr(f, discarded, 1 << n);
setFlag(f, CF, carry); setFlag(f, CF, carry);
@ -290,119 +288,13 @@ void EightBit::GameBoy::LR35902::ccf(uint8_t& a, uint8_t& f) {
clearFlag(f, CF, f & CF); clearFlag(f, CF, f & CF);
} }
int EightBit::GameBoy::LR35902::runRasterLines() {
m_enabledLCD = !!(m_bus.IO().peek(IoRegisters::LCDC) & IoRegisters::LcdEnable);
m_bus.IO().resetLY();
return runRasterLines(Display::RasterHeight * Bus::CyclesPerLine, Display::RasterHeight);
}
int EightBit::GameBoy::LR35902::runRasterLines(int limit, int lines) {
int count = 0;
int allowed = Bus::CyclesPerLine;
for (int line = 0; line < lines; ++line) {
auto executed = runRasterLine(allowed);
count += executed;
allowed = Bus::CyclesPerLine - (executed - Bus::CyclesPerLine);
}
return count;
}
int EightBit::GameBoy::LR35902::runRasterLine(int limit) {
/*
A scanline normally takes 456 clocks (912 clocks in double speed
mode) to complete. A scanline starts in mode 2, then goes to
mode 3 and, when the LCD controller has finished drawing the
line (the timings depend on lots of things) it goes to mode 0.
During lines 144-153 the LCD controller is in mode 1.
Line 153 takes only a few clocks to complete (the exact
timings are below). The rest of the clocks of line 153 are
spent in line 0 in mode 1!
During mode 0 and mode 1 the CPU can access both VRAM and OAM.
During mode 2 the CPU can only access VRAM, not OAM.
During mode 3 OAM and VRAM can't be accessed.
In GBC mode the CPU can't access Palette RAM(FF69h and FF6Bh)
during mode 3.
A scanline normally takes 456 clocks(912 clocks in double speed mode) to complete.
A scanline starts in mode 2, then goes to mode 3 and , when the LCD controller has
finished drawing the line(the timings depend on lots of things) it goes to mode 0.
During lines 144 - 153 the LCD controller is in mode 1.
Line 153 takes only a few clocks to complete(the exact timings are below).
The rest of the clocks of line 153 are spent in line 0 in mode 1!
*/
int count = 0;
if (m_enabledLCD) {
if ((m_bus.IO().peek(IoRegisters::STAT) & Bit6) && (m_bus.IO().peek(IoRegisters::LYC) == m_bus.IO().peek(IoRegisters::LY)))
m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus);
// Mode 2, OAM unavailable
m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::SearchingOamRam);
if (m_bus.IO().peek(IoRegisters::STAT) & Bit5)
m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus);
count += run(80); // ~19us
// Mode 3, OAM/VRAM unavailable
m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::TransferringDataToLcd);
count += run(170); // ~41us
// Mode 0
m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::HBlank);
if (m_bus.IO().peek(IoRegisters::STAT) & Bit3)
m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus);
count += run(limit - count); // ~48.6us
m_bus.IO().incrementLY();
} else {
count += run(Bus::CyclesPerLine);
}
return count;
}
int EightBit::GameBoy::LR35902::runVerticalBlankLines() {
auto lines = Bus::TotalLineCount - Display::RasterHeight;
return runVerticalBlankLines(lines * Bus::CyclesPerLine, lines);
}
int EightBit::GameBoy::LR35902::runVerticalBlankLines(int limit, int lines) {
/*
Vertical Blank interrupt is triggered when the LCD
controller enters the VBL screen mode (mode 1, LY=144).
This happens once per frame, so this interrupt is
triggered 59.7 times per second. During this period the
VRAM and OAM can be accessed freely, so it's the best
time to update graphics (for example, use the OAM DMA to
update sprites for next frame, or update tiles to make
animations).
This period lasts 4560 clocks in normal speed mode and
9120 clocks in double speed mode. That's exactly the
time needed to draw 10 scanlines.
The VBL interrupt isn't triggered when the LCD is
powered off or on, even when it was on VBL mode.
It's only triggered when the VBL period starts.
*/
if (m_enabledLCD) {
m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::VBlank);
if (m_bus.IO().peek(IoRegisters::STAT) & Bit4)
m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus);
m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::VerticalBlank);
}
return runRasterLines(limit, lines);
}
int EightBit::GameBoy::LR35902::singleStep() { int EightBit::GameBoy::LR35902::singleStep() {
int current = 0; int current = 0;
auto interruptEnable = m_bus.peek(IoRegisters::BASE + IoRegisters::IE); const auto interruptEnable = m_bus.peek(IoRegisters::BASE + IoRegisters::IE);
auto interruptFlags = m_bus.IO().peek(IoRegisters::IF); const auto interruptFlags = m_bus.IO().peek(IoRegisters::IF);
auto ime = IME(); const auto ime = IME();
auto masked = interruptEnable & interruptFlags; auto masked = interruptEnable & interruptFlags;
if (masked) { if (masked) {
@ -438,7 +330,7 @@ int EightBit::GameBoy::LR35902::step() {
ExecutingInstruction.fire(*this); ExecutingInstruction.fire(*this);
m_prefixCB = false; m_prefixCB = false;
cycles = 0; cycles = 0;
auto ran = fetchExecute(); const auto ran = fetchExecute();
ExecutedInstruction.fire(*this); ExecutedInstruction.fire(*this);
return ran; return ran;
} }
@ -447,12 +339,12 @@ int EightBit::GameBoy::LR35902::execute(uint8_t opcode) {
const auto& decoded = getDecodedOpcode(opcode); const auto& decoded = getDecodedOpcode(opcode);
auto x = decoded.x; const auto x = decoded.x;
auto y = decoded.y; const auto y = decoded.y;
auto z = decoded.z; const auto z = decoded.z;
auto p = decoded.p; const auto p = decoded.p;
auto q = decoded.q; const auto q = decoded.q;
if (m_prefixCB) if (m_prefixCB)
executeCB(x, y, z, p, q); executeCB(x, y, z, p, q);
@ -756,11 +648,11 @@ void EightBit::GameBoy::LR35902::executeOther(int x, int y, int z, int p, int q)
cycles += 3; cycles += 3;
break; break;
case 5: { // GB: ADD SP,dd case 5: { // GB: ADD SP,dd
auto before = SP().word; const auto before = SP().word;
int8_t value = fetchByte(); const int8_t value = fetchByte();
auto result = before + value; const auto result = before + value;
SP().word = result; SP().word = result;
auto carried = before ^ value ^ (result & Mask16); const auto carried = before ^ value ^ (result & Mask16);
clearFlag(f, ZF | NF); clearFlag(f, ZF | NF);
setFlag(f, CF, carried & Bit8); setFlag(f, CF, carried & Bit8);
setFlag(f, HC, carried & Bit4); setFlag(f, HC, carried & Bit4);
@ -772,11 +664,11 @@ void EightBit::GameBoy::LR35902::executeOther(int x, int y, int z, int p, int q)
cycles += 3; cycles += 3;
break; break;
case 7: { // GB: LD HL,SP + dd case 7: { // GB: LD HL,SP + dd
auto before = SP().word; const auto before = SP().word;
int8_t value = fetchByte(); const int8_t value = fetchByte();
auto result = before + value; const auto result = before + value;
HL().word = result; HL().word = result;
auto carried = before ^ value ^ (result & Mask16); const auto carried = before ^ value ^ (result & Mask16);
clearFlag(f, ZF | NF); clearFlag(f, ZF | NF);
setFlag(f, CF, carried & Bit8); setFlag(f, CF, carried & Bit8);
setFlag(f, HC, carried & Bit4); setFlag(f, HC, carried & Bit4);

View File

@ -49,8 +49,7 @@ namespace EightBit {
uint8_t read() { uint8_t read() {
ReadingByte.fire(ADDRESS().word); ReadingByte.fire(ADDRESS().word);
auto content = reference(); return reference();
return content;
} }
uint8_t read(uint16_t offset) { uint8_t read(uint16_t offset) {

View File

@ -107,13 +107,13 @@ namespace EightBit {
static bool calculateHalfCarryAdd(uint8_t before, uint8_t value, int calculation) { static bool calculateHalfCarryAdd(uint8_t before, uint8_t value, int calculation) {
static std::array<bool, 8> m_halfCarryTableAdd = { { false, false, true, false, true, false, true, true } }; static std::array<bool, 8> m_halfCarryTableAdd = { { false, false, true, false, true, false, true, true } };
auto index = buildHalfCarryIndex(before, value, calculation); const auto index = buildHalfCarryIndex(before, value, calculation);
return m_halfCarryTableAdd[index & Mask3]; return m_halfCarryTableAdd[index & Mask3];
} }
static bool calculateHalfCarrySub(uint8_t before, uint8_t value, int calculation) { static bool calculateHalfCarrySub(uint8_t before, uint8_t value, int calculation) {
std::array<bool, 8> m_halfCarryTableSub = { { false, true, true, true, false, false, false, true } }; std::array<bool, 8> m_halfCarryTableSub = { { false, true, true, true, false, false, false, true } };
auto index = buildHalfCarryIndex(before, value, calculation); const auto index = buildHalfCarryIndex(before, value, calculation);
return m_halfCarryTableSub[index & Mask3]; return m_halfCarryTableSub[index & Mask3];
} }
@ -180,7 +180,7 @@ namespace EightBit {
} }
bool jrConditional(int conditional) { bool jrConditional(int conditional) {
auto offset = fetchByte(); const auto offset = fetchByte();
if (conditional) if (conditional)
jr(offset); jr(offset);
return conditional != 0; return conditional != 0;

View File

@ -3,7 +3,7 @@
uint8_t EightBit::InputOutput::readInputPort(uint8_t port) { uint8_t EightBit::InputOutput::readInputPort(uint8_t port) {
OnReadingPort(port); OnReadingPort(port);
auto value = input[port]; const auto value = input[port];
OnReadPort(port); OnReadPort(port);
return value; return value;
} }