void GBMemory::write(int addr, u8 value)
{
if (addr < 0x8000) mbc->write(addr, value);
- else if (addr < 0xA000) VRAM [addr - VRAM_BASE] = value;
+ else if (addr < 0xA000) core->video->write_VRAM(addr, value);
else if (addr < 0xC000) mbc->write(addr, value);
else if (addr < 0xD000) WRAM0[addr - WRAM0_BASE] = value;
else if (addr < 0xE000) WRAM1[addr - WRAM1_BASE] = value;
else if (addr < 0xFDFF) write(addr-0x2000, value);
- else if (addr < 0xFEA0) OAM [addr - OAM_BASE] = value;
+ else if (addr < 0xFEA0) core->video->write_OAM (addr, value);
else if (addr >= 0xFF00 && addr <= 0xFF7F) {
IO.write(addr,value);
}
u8 GBMemory::read(int addr) const
{
if (addr < 0x8000) return mbc->read(addr);
- else if (addr < 0xA000) return VRAM [addr - VRAM_BASE];
+ else if (addr < 0xA000) return core->video->read_VRAM(addr);
else if (addr < 0xC000) return mbc->read(addr);
else if (addr < 0xD000) return WRAM0[addr - WRAM0_BASE];
else if (addr < 0xE000) return WRAM1[addr - WRAM1_BASE];
else if (addr < 0xFDFF) return read(addr-0x2000);
- else if (addr < 0xFEA0) return OAM [addr - OAM_BASE];
+ else if (addr < 0xFEA0) return core->video->read_OAM (addr);
else if (addr >= 0xFF00 && addr <= 0xFF7F)
return IO.read(addr);
else if (addr >= 0xFF80 && addr <= 0xFFFE) return HRAM[addr - HRAM_BASE];
+ else if (addr == 0xFFFF) return IE;
else {
std::ostringstream errmsg;
errmsg << "Invalid read address 0x" <<
MBC *mbc;
// 0000-3FFF: ROM Bank 0 (in cart)
// 4000-7FFF: Switchable ROM Bank (in cart)
- u8 VRAM[8192]; // 8000-9FFF: Video RAM
+ // 8000-9FFF: Video RAM
// A000-BFFF: External RAM (in cart, switchable)
u8 WRAM0[4096]; // C000-CFFF: Work RAM Bank 0
u8 WRAM1[4096]; // D000-DFFF: Work RAM Bank 1 (TODO: In GBC mode switchable bank 1-7)
// E000-FDFF: ECHO: Same as C000-DDFF
- u8 OAM[160]; // FE00-FE9F: Sprite Attribute Table
+ // FE00-FE9F: Sprite Attribute Table
GBIO IO; // FF00-FF7F: IO ports
u8 HRAM[126]; // FF80-FFFE: High RAM
-Woverloaded-virtual
LDFLAGS=-g
-all: gbcore.o MBC.o GBMemory.o Logger.o GBRom.o tests
+all: gbcore.o MBC.o GBMemory.o Logger.o GBRom.o
-tests: tests/test_gbrom
+tests: tests/test_gbrom tests/test_core
Logger.o: Logger.cc Logger.h
g++ $(CXXFLAGS) -c -o $@ $<
tests/test_gbrom: GBRom.cc GBRom.h Logger.o
g++ -DTEST_GBROM -o $@ GBRom.cc Logger.o
+tests/test_core: tests/test_core.cc gbcore.o MBC.o GBMemory.o Logger.o GBRom.o
+ g++ -o $@ $^
+
clean:
rm -f *.o tests/test_gbrom
void GameBoy::run_cycle()
{
+ // Check for interrupts before opcode fetching
+ u8 IE;
+ if (IME && (IE=memory.read(0xFFFF)))
+ {
+ u8 IF = memory.read(0xFF0F);
+ if (IF)
+ {
+ if ((IF & IRQ_VBLANK) && (IE & IRQ_VBLANK))
+ {
+ IME = 0;
+ IF &= (~IRQ_VBLANK);
+ do_call(0x40);
+ }
+ else if ((IF & IRQ_LCD_STAT) && (IE & IRQ_LCD_STAT))
+ {
+ IME = 0;
+ IF &= (~IRQ_LCD_STAT);
+ IF do_call(0x48);
+ }
+ else if ((IF & IRQ_TIMER) && (IE & IRQ_TIMER))
+ {
+ IME = 0;
+ IF &= (~IRQ_TIMER);
+ do_call(0x50);
+ }
+ else if ((IF & IRQ_SERIAL) && (IE & IRQ_SERIAL))
+ {
+ IME = 0;
+ IF &= (~IRQ_SERIAL);
+ do_call(0x58);
+ }
+ else if ((IF & IRQ_JOYPAD) && (IE & IRQ_JOYPAD))
+ {
+ IME = 0;
+ IF &= (~IRQ_JOYPAD);
+ do_call(0x60);
+ }
+ }
+ }
+
+
+
int prefix;
int opcode;
opcode = memory.read(regs.PC++);
for_each_register(0x1F, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, RR_reg)
// RR (HL) (through carry)
- case 0x16: {
+ case 0x1E: {
u8 value = memory.read(regs.HL);
u8 bit0 = value & 1;
value = (value >> 1) | (check_flag(CARRY_FLAG) << 7);
memory.write(regs.HL, value);
- set_flag_if(bit7, CARRY_FLAG);
+ set_flag_if(bit0, CARRY_FLAG);
set_flag_if(value == 0, ZERO_FLAG);
reset_flag(ADD_SUB_FLAG);
reset_flag(HALF_CARRY_FLAG);
// Calls
// CALL nn
case 0xCD: {
- // push, then jump
- u16 retaddr = regs.PC+2;
- memory.write(regs.SP-1, retaddr >> 8); // high
- memory.write(regs.SP-2, retaddr & 0xFF); // low
- regs.SP -= 2;
-
- regs.PC = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ u16 addr = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ regs.PC += 2;
+ do_call(addr);
break;
}
// CALL cc, nn
case 0xC4: { // CALL NZ, nn
if (!check_flag(ZERO_FLAG)) {
- // push, then jump
- u16 retaddr = regs.PC+2;
- memory.write(regs.SP-1, retaddr >> 8); // high
- memory.write(regs.SP-2, retaddr & 0xFF); // low
- regs.SP -= 2;
-
- regs.PC = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ u16 addr = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ regs.PC += 2;
+ do_call(addr);
} else {
regs.PC += 2; // if !cc, skip 2 (nn) bytes
}
case 0xCC: { // CALL Z, nn
if (check_flag(ZERO_FLAG)) {
- // push, then jump
- u16 retaddr = regs.PC+2;
- memory.write(regs.SP-1, retaddr >> 8); // high
- memory.write(regs.SP-2, retaddr & 0xFF); // low
- regs.SP -= 2;
-
- regs.PC = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ u16 addr = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ regs.PC += 2;
+ do_call(addr);
} else {
regs.PC += 2; // if !cc, skip 2 (nn) bytes
}
case 0xD4: { // CALL NC, nn
if (!check_flag(CARRY_FLAG)) {
- // push, then jump
- u16 retaddr = regs.PC+2;
- memory.write(regs.SP-1, retaddr >> 8); // high
- memory.write(regs.SP-2, retaddr & 0xFF); // low
- regs.SP -= 2;
-
- regs.PC = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ u16 addr = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ regs.PC += 2;
+ do_call(addr);
} else {
regs.PC += 2; // if !cc, skip 2 (nn) bytes
}
case 0xDC: { // CALL C, nn
if (check_flag(CARRY_FLAG)) {
- // push, then jump
- u16 retaddr = regs.PC+2;
- memory.write(regs.SP-1, retaddr >> 8); // high
- memory.write(regs.SP-2, retaddr & 0xFF); // low
- regs.SP -= 2;
-
- regs.PC = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ u16 addr = memory.read(regs.PC) | (memory.read(regs.PC+1)<<8);
+ regs.PC += 2;
+ do_call(addr);
} else {
regs.PC += 2; // if !cc, skip 2 (nn) bytes
}
class GameBoy
{
enum GameBoyType { GAMEBOY, GAMEBOYCOLOR, SUPERGAMEBOY } gameboy_type;
+ enum InterruptRequest {
+ IRQ_VBLANK = 0x00,
+ IRQ_LCD_STAT = 0x10,
+ IRQ_TIMER = 0x20,
+ IRQ_SERIAL = 0x40,
+ IRQ_JOYPAD = 0x80
+ };
+
+ enum Flag
+ {
+ ZERO_FLAG = 0x80,
+ ADD_SUB_FLAG = 0x40,
+ HALF_CARRY_FLAG = 0x20,
+ CARRY_FLAG = 0x10,
+ };
+
friend class GBMemory;
GBMemory memory;
GBRom *rom;
- enum flags_enum
- {
- ZERO_FLAG=0x80,
- ADD_SUB_FLAG=0x40,
- HALF_CARRY_FLAG=0x20,
- CARRY_FLAG=0x10,
- };
-
// CPU Registers
// ENDIANNESS WARNING!
struct
u8 IME; // Interrupt master enable flag
u8 HALT; // Is the CPU halted waiting for an interrupt?
+
u32 cycle_count;
+
+ inline void do_call(u16 addr)
+ {
+ memory.write(regs.SP-1, regs.PC >> 8);
+ memory.write(regs.SP-2, regs.PC & 0xFF);
+ regs.SP -= 2;
+ regs.PC = addr;
+ }
- void set_flag(const u8 f) { regs.flags |= f; }
- void reset_flag(const u8 f) { regs.flags &= (~f); }
- bool check_flag(const u8 f) { return ((regs.flags & f) != 0); }
+ void set_flag (Flag f) { regs.flags |= f; }
+ void reset_flag(Flag f) { regs.flags &= (~f); }
+ bool check_flag(Flag f) { return ((regs.flags & f) != 0); }
public:
GameBoy(std::string rom_name, GameBoyType type=GAMEBOY);
+ void irq(InterruptRequest i) { memory.write(0xFFFF, memory.read(0xFFFF) | i); }
void reset();
void run_cycle();
void run();
#define RST(opcode, n) \
case opcode: \
- memory.write(regs.SP-1, regs.PC >> 8); \
- memory.write(regs.SP-2, regs.PC & 0xFF); \
- regs.SP -= 2; \
- regs.PC = n; \
+ do_call(n); \
break;
//set_flag_if(bit7, CARRY_FLAG);
//
#define RLC_reg(opcode, reg) \
- case opcode: {\
+ case opcode: { \
u8 bit7 = regs.reg >> 7; \
regs.reg = (regs.reg << 1) | bit7; \
set_flag_if(regs.reg == 0, ZERO_FLAG); \
reset_flag(ADD_SUB_FLAG); \
reset_flag(HALF_CARRY_FLAG); \
- break;
+ break; \
}
#define RL_reg(opcode, reg) \
- case opcode: {
- u8 bit7 = regs.reg >> 7;
- regs.reg = (regs.reg << 1) | check_flag(CARRY_FLAG);
- set_flag_if(bit7, CARRY_FLAG);
- set_flag_if(value == 0, ZERO_FLAG);
- reset_flag(ADD_SUB_FLAG);
- reset_flag(HALF_CARRY_FLAG);
- break;
+ case opcode: { \
+ u8 bit7 = regs.reg >> 7; \
+ regs.reg = (regs.reg << 1) | check_flag(CARRY_FLAG); \
+ set_flag_if(bit7, CARRY_FLAG); \
+ set_flag_if(regs.reg == 0, ZERO_FLAG); \
+ reset_flag(ADD_SUB_FLAG); \
+ reset_flag(HALF_CARRY_FLAG); \
+ break; \
}
// TODO: Check which of GBCPUman.pdf or
//set_flag_if(bit7, CARRY_FLAG);
//
#define RRC_reg(opcode, reg) \
- case opcode: {\
+ case opcode: { \
u8 bit0 = regs.reg & 1; \
regs.reg = (regs.reg >> 1) | (bit0 << 7); \
set_flag_if(regs.reg == 0, ZERO_FLAG); \
reset_flag(ADD_SUB_FLAG); \
reset_flag(HALF_CARRY_FLAG); \
- break;
+ break; \
}
#define RR_reg(opcode, reg) \
- case opcode: {
- u8 bit0 = regs.reg & 1;
- regs.reg = (regs.reg >> 1) | (check_flag(CARRY_FLAG) << 7);
- set_flag_if(bit7, CARRY_FLAG);
- set_flag_if(value == 0, ZERO_FLAG);
- reset_flag(ADD_SUB_FLAG);
- reset_flag(HALF_CARRY_FLAG);
- break;
+ case opcode: { \
+ u8 bit0 = regs.reg & 1; \
+ regs.reg = (regs.reg >> 1) | (check_flag(CARRY_FLAG) << 7); \
+ set_flag_if(bit0, CARRY_FLAG); \
+ set_flag_if(regs.reg == 0, ZERO_FLAG); \
+ reset_flag(ADD_SUB_FLAG); \
+ reset_flag(HALF_CARRY_FLAG); \
+ break; \
}