GBHawk: C++ memory map
This commit is contained in:
parent
37e8b29056
commit
31c41f6279
|
@ -7,6 +7,8 @@
|
|||
#include "GBAudio.h"
|
||||
#include "PPU_Base.h"
|
||||
#include "Memory.h"
|
||||
#include "Timer.h"
|
||||
#include "SerialPort.h"
|
||||
|
||||
namespace GBHawk
|
||||
{
|
||||
|
@ -18,6 +20,8 @@ namespace GBHawk
|
|||
MemMap.cpu_pntr = &cpu;
|
||||
MemMap.ppu_pntr = &ppu;
|
||||
MemMap.psg_pntr = &psg;
|
||||
MemMap.timer_pntr = &timer;
|
||||
MemMap.serialport_pntr = &serialport;
|
||||
cpu.mem_ctrl = &MemMap;
|
||||
|
||||
ppu.FlagI = &cpu.FlagI;
|
||||
|
@ -35,6 +39,19 @@ namespace GBHawk
|
|||
ppu.HDMA_transfer = &MemMap.HDMA_transfer;
|
||||
ppu.GBC_compat = &MemMap.GBC_compat;
|
||||
|
||||
timer.FlagI = &cpu.FlagI;
|
||||
timer.REG_FFFF = &MemMap.REG_FFFF;
|
||||
timer.REG_FF0F = &MemMap.REG_FF0F;
|
||||
|
||||
serialport.GBC_compat = &MemMap.GBC_compat;
|
||||
serialport.FlagI = &cpu.FlagI;
|
||||
serialport.REG_FFFF = &MemMap.REG_FFFF;
|
||||
serialport.REG_FF0F = &MemMap.REG_FF0F;
|
||||
|
||||
psg.is_GBC = &MemMap.is_GBC;
|
||||
psg.double_speed = &MemMap.double_speed;
|
||||
psg.timer_div_reg = &timer.divider_reg;
|
||||
|
||||
sl_case = 0;
|
||||
};
|
||||
|
||||
|
@ -42,6 +59,8 @@ namespace GBHawk
|
|||
LR35902 cpu;
|
||||
GBAudio psg;
|
||||
MemoryManager MemMap;
|
||||
Timer timer;
|
||||
SerialPort serialport;
|
||||
|
||||
uint8_t sl_case = 0;
|
||||
|
||||
|
|
|
@ -13,15 +13,15 @@ namespace GBHawk
|
|||
|
||||
#pragma region GBAudio
|
||||
|
||||
// Core variables
|
||||
bool* is_GBC = nullptr;
|
||||
bool* double_speed = nullptr;
|
||||
uint32_t* timer_div_reg = nullptr;
|
||||
|
||||
uint32_t num_samples_L, num_samples_R;
|
||||
int32_t samples_L[9000] = {};
|
||||
int32_t samples_R[9000] = {};
|
||||
|
||||
//GBHawk Core{ get; set; }
|
||||
// Core variables
|
||||
bool is_GBC, double_speed;
|
||||
uint32_t timer_div_reg;
|
||||
|
||||
bool DUTY_CYCLES[32] = {false, false, false, false, false, false, false, true,
|
||||
true, false, false, false, false, false, false, true,
|
||||
true, false, false, false, false, true, true, true,
|
||||
|
@ -172,7 +172,7 @@ namespace GBHawk
|
|||
case 0xFF3F:
|
||||
if (WAVE_enable)
|
||||
{
|
||||
if (WAVE_can_get || is_GBC) { ret = Wave_RAM[WAVE_wave_cntr >> 1]; }
|
||||
if (WAVE_can_get || is_GBC[0]) { ret = Wave_RAM[WAVE_wave_cntr >> 1]; }
|
||||
else { ret = 0xFF; }
|
||||
}
|
||||
else { ret = Wave_RAM[addr & 0x0F]; }
|
||||
|
@ -391,7 +391,7 @@ namespace GBHawk
|
|||
if (WAVE_trigger)
|
||||
{
|
||||
// some corruption occurs if triggering while reading
|
||||
if (WAVE_enable && (WAVE_intl_cntr == 2) && !is_GBC)
|
||||
if (WAVE_enable && (WAVE_intl_cntr == 2) && !is_GBC[0])
|
||||
{
|
||||
// we want to use the previous wave cntr value since it was just incremented
|
||||
int t_wave_cntr = (WAVE_wave_cntr + 1) & 31;
|
||||
|
@ -531,7 +531,7 @@ namespace GBHawk
|
|||
case 0xFF3F:
|
||||
if (WAVE_enable)
|
||||
{
|
||||
if (WAVE_can_get || is_GBC) { Wave_RAM[WAVE_wave_cntr >> 1] = value; }
|
||||
if (WAVE_can_get || is_GBC[0]) { Wave_RAM[WAVE_wave_cntr >> 1] = value; }
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -548,28 +548,28 @@ namespace GBHawk
|
|||
switch (addr)
|
||||
{
|
||||
case 0xFF11: // NR11 (sound length / wave pattern duty %)
|
||||
if (!is_GBC)
|
||||
if (!is_GBC[0])
|
||||
{
|
||||
SQ1_length = (uint32_t)(64 - (value & 0x3F));
|
||||
SQ1_len_cntr = SQ1_length;
|
||||
}
|
||||
break;
|
||||
case 0xFF16: // NR21 (sound length / wave pattern duty %)
|
||||
if (!is_GBC)
|
||||
if (!is_GBC[0])
|
||||
{
|
||||
SQ2_length = (uint32_t)(64 - (value & 0x3F));
|
||||
SQ2_len_cntr = SQ2_length;
|
||||
}
|
||||
break;
|
||||
case 0xFF1B: // NR31 (length)
|
||||
if (!is_GBC)
|
||||
if (!is_GBC[0])
|
||||
{
|
||||
WAVE_length = (uint32_t)(256 - value);
|
||||
WAVE_len_cntr = WAVE_length;
|
||||
}
|
||||
break;
|
||||
case 0xFF20: // NR41 (length)
|
||||
if (!is_GBC)
|
||||
if (!is_GBC[0])
|
||||
{
|
||||
NOISE_length = (uint32_t)(64 - (value & 0x3F));
|
||||
NOISE_len_cntr = NOISE_length;
|
||||
|
@ -751,7 +751,7 @@ namespace GBHawk
|
|||
// frame sequencer ticks at a rate of 512 hz (or every time a 13 bit counter rolls over)
|
||||
// the sequencer is actually the timer DIV register
|
||||
// so if it's constantly written to, these values won't update
|
||||
bool check = double_speed ? ((timer_div_reg & 0x2000) > 0) : ((timer_div_reg & 0x1000) > 0);
|
||||
bool check = double_speed[0] ? ((timer_div_reg[0] & 0x2000) > 0) : ((timer_div_reg[0] & 0x1000) > 0);
|
||||
|
||||
if (check && !timer_bit_old)
|
||||
{
|
||||
|
@ -906,7 +906,7 @@ namespace GBHawk
|
|||
}
|
||||
}
|
||||
}
|
||||
timer_bit_old = double_speed ? ((timer_div_reg & 0x2000) > 0) : ((timer_div_reg & 0x1000) > 0);
|
||||
timer_bit_old = double_speed[0] ? ((timer_div_reg[0] & 0x2000) > 0) : ((timer_div_reg[0] & 0x1000) > 0);
|
||||
}
|
||||
|
||||
void power_off()
|
||||
|
@ -929,7 +929,7 @@ namespace GBHawk
|
|||
SQ1_output = SQ2_output = WAVE_output = NOISE_output = 0;
|
||||
|
||||
// on GBC, lengths are also reset
|
||||
if (is_GBC)
|
||||
if (is_GBC[0])
|
||||
{
|
||||
SQ1_length = SQ2_length = WAVE_length = NOISE_length = 0;
|
||||
SQ1_len_cntr = SQ2_len_cntr = WAVE_len_cntr = NOISE_len_cntr = 0;
|
||||
|
@ -942,7 +942,7 @@ namespace GBHawk
|
|||
|
||||
void Reset()
|
||||
{
|
||||
if (is_GBC)
|
||||
if (is_GBC[0])
|
||||
{
|
||||
Wave_RAM[0] = 0; Wave_RAM[2] = 0; Wave_RAM[4] = 0; Wave_RAM[6] = 0;
|
||||
Wave_RAM[8] = 0; Wave_RAM[10] = 0; Wave_RAM[12] = 0; Wave_RAM[14] = 0;
|
||||
|
@ -1200,10 +1200,6 @@ namespace GBHawk
|
|||
saver = int_saver(num_samples_L, saver);
|
||||
saver = int_saver(num_samples_R, saver);
|
||||
|
||||
saver = bool_saver(is_GBC, saver);
|
||||
saver = bool_saver(double_speed, saver);
|
||||
saver = int_saver(timer_div_reg, saver);
|
||||
|
||||
saver = bool_saver(AUD_CTRL_vin_L_en, saver);
|
||||
saver = bool_saver(AUD_CTRL_vin_R_en, saver);
|
||||
saver = bool_saver(AUD_CTRL_sq1_L_en, saver);
|
||||
|
@ -1322,10 +1318,6 @@ namespace GBHawk
|
|||
loader = int_loader(&num_samples_L, loader);
|
||||
loader = int_loader(&num_samples_R, loader);
|
||||
|
||||
loader = bool_loader(&is_GBC, loader);
|
||||
loader = bool_loader(&double_speed, loader);
|
||||
loader = int_loader(&timer_div_reg, loader);
|
||||
|
||||
loader = bool_loader(&AUD_CTRL_vin_L_en, loader);
|
||||
loader = bool_loader(&AUD_CTRL_vin_R_en, loader);
|
||||
loader = bool_loader(&AUD_CTRL_sq1_L_en, loader);
|
||||
|
|
|
@ -157,6 +157,8 @@
|
|||
<ClInclude Include="GBC_PPU.h" />
|
||||
<ClInclude Include="GBHawk.h" />
|
||||
<ClInclude Include="GB_PPU.h" />
|
||||
<ClInclude Include="SerialPort.h" />
|
||||
<ClInclude Include="Timer.h" />
|
||||
<ClInclude Include="Memory.h" />
|
||||
<ClInclude Include="PPU_Base.h" />
|
||||
<ClInclude Include="LR35902.h" />
|
||||
|
|
|
@ -289,7 +289,7 @@ namespace GBHawk
|
|||
// some of gekkio's tests require these to be accessible during DMA
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
if (ppu.DMA_addr < 0x80)
|
||||
if (ppu_pntr->DMA_addr < 0x80)
|
||||
{
|
||||
return 0xFF;
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@ using namespace std;
|
|||
namespace GBHawk
|
||||
{
|
||||
class LR35902;
|
||||
class Timer;
|
||||
class PPU;
|
||||
class GBAudio;
|
||||
class SerialPort;
|
||||
|
||||
class MemoryManager
|
||||
{
|
||||
|
@ -29,6 +31,8 @@ namespace GBHawk
|
|||
PPU* ppu_pntr = nullptr;
|
||||
GBAudio* psg_pntr = nullptr;
|
||||
LR35902* cpu_pntr = nullptr;
|
||||
Timer* timer_pntr = nullptr;
|
||||
SerialPort* serialport_pntr = nullptr;
|
||||
uint8_t* rom_1 = nullptr;
|
||||
uint8_t* rom_2 = nullptr;
|
||||
uint8_t* bios_rom = nullptr;
|
||||
|
@ -53,12 +57,21 @@ namespace GBHawk
|
|||
bool in_vblank;
|
||||
bool GB_bios_register;
|
||||
bool HDMA_transfer;
|
||||
bool _islag;
|
||||
uint8_t addr_access;
|
||||
uint8_t REG_FFFF, REG_FF0F;
|
||||
uint8_t REG_FFFF, REG_FF0F, REG_FF0F_OLD;
|
||||
uint8_t _scanlineCallbackLine;
|
||||
uint8_t input_register;
|
||||
uint32_t RAM_Bank;
|
||||
uint32_t VRAM_Bank;
|
||||
|
||||
uint8_t IR_reg, IR_mask, IR_signal, IR_receive, IR_self;
|
||||
uint32_t IR_write;
|
||||
|
||||
// several undocumented GBC Registers
|
||||
uint8_t undoc_6C, undoc_72, undoc_73, undoc_74, undoc_75, undoc_76, undoc_77;
|
||||
uint8_t controller_state;
|
||||
|
||||
uint8_t ZP_RAM[0x80] = {};
|
||||
uint8_t RAM[0x8000] = {};
|
||||
uint8_t VRAM[0x10000] = {};
|
||||
|
@ -73,6 +86,7 @@ namespace GBHawk
|
|||
#pragma endregion
|
||||
|
||||
#pragma region Functions
|
||||
|
||||
// NOTE: only called when checks pass that the files are correct
|
||||
void Load_BIOS(uint8_t* bios, bool GBC_console)
|
||||
{
|
||||
|
@ -124,7 +138,526 @@ namespace GBHawk
|
|||
return 0;
|
||||
}
|
||||
|
||||
uint8_t Read_Registers(uint32_t addr)
|
||||
{
|
||||
uint8_t ret = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
// Read Input
|
||||
case 0xFF00:
|
||||
_islag = false;
|
||||
ret = input_register;
|
||||
break;
|
||||
|
||||
// Serial data port
|
||||
case 0xFF01:
|
||||
ret = serialport_pntr->ReadReg(addr);
|
||||
break;
|
||||
|
||||
// Serial port control
|
||||
case 0xFF02:
|
||||
ret = serialport_pntr->ReadReg(addr);
|
||||
break;
|
||||
|
||||
// Timer Registers
|
||||
case 0xFF04:
|
||||
case 0xFF05:
|
||||
case 0xFF06:
|
||||
case 0xFF07:
|
||||
ret = timer_pntr->ReadReg(addr);
|
||||
break;
|
||||
|
||||
// Interrupt flags
|
||||
case 0xFF0F:
|
||||
ret = REG_FF0F_OLD;
|
||||
break;
|
||||
|
||||
// audio regs
|
||||
case 0xFF10:
|
||||
case 0xFF11:
|
||||
case 0xFF12:
|
||||
case 0xFF13:
|
||||
case 0xFF14:
|
||||
case 0xFF16:
|
||||
case 0xFF17:
|
||||
case 0xFF18:
|
||||
case 0xFF19:
|
||||
case 0xFF1A:
|
||||
case 0xFF1B:
|
||||
case 0xFF1C:
|
||||
case 0xFF1D:
|
||||
case 0xFF1E:
|
||||
case 0xFF20:
|
||||
case 0xFF21:
|
||||
case 0xFF22:
|
||||
case 0xFF23:
|
||||
case 0xFF24:
|
||||
case 0xFF25:
|
||||
case 0xFF26:
|
||||
case 0xFF30:
|
||||
case 0xFF31:
|
||||
case 0xFF32:
|
||||
case 0xFF33:
|
||||
case 0xFF34:
|
||||
case 0xFF35:
|
||||
case 0xFF36:
|
||||
case 0xFF37:
|
||||
case 0xFF38:
|
||||
case 0xFF39:
|
||||
case 0xFF3A:
|
||||
case 0xFF3B:
|
||||
case 0xFF3C:
|
||||
case 0xFF3D:
|
||||
case 0xFF3E:
|
||||
case 0xFF3F:
|
||||
ret = psg_pntr->ReadReg(addr);
|
||||
break;
|
||||
|
||||
// PPU Regs
|
||||
case 0xFF40:
|
||||
case 0xFF41:
|
||||
case 0xFF42:
|
||||
case 0xFF43:
|
||||
case 0xFF44:
|
||||
case 0xFF45:
|
||||
case 0xFF46:
|
||||
case 0xFF47:
|
||||
case 0xFF48:
|
||||
case 0xFF49:
|
||||
case 0xFF4A:
|
||||
case 0xFF4B:
|
||||
ret = ppu_pntr->ReadReg(addr);
|
||||
break;
|
||||
|
||||
// Speed Control for GBC
|
||||
case 0xFF4D:
|
||||
if (GBC_compat)
|
||||
{
|
||||
ret = (uint8_t)(((double_speed ? 1 : 0) << 7) + ((speed_switch ? 1 : 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0xFF;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF4F: // VBK
|
||||
if (GBC_compat)
|
||||
{
|
||||
ret = (uint8_t)(0xFE | VRAM_Bank);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0xFF;
|
||||
}
|
||||
break;
|
||||
|
||||
// Bios control register. Not sure if it is readable
|
||||
case 0xFF50:
|
||||
ret = 0xFF;
|
||||
break;
|
||||
|
||||
// PPU Regs for GBC
|
||||
case 0xFF51:
|
||||
case 0xFF52:
|
||||
case 0xFF53:
|
||||
case 0xFF54:
|
||||
case 0xFF55:
|
||||
if (GBC_compat)
|
||||
{
|
||||
ret = ppu_pntr->ReadReg(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0xFF;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF56:
|
||||
if (GBC_compat)
|
||||
{
|
||||
// can receive data
|
||||
if ((IR_reg & 0xC0) == 0xC0)
|
||||
{
|
||||
ret = IR_reg;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = (uint8_t)(IR_reg | 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0xFF;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF68:
|
||||
case 0xFF69:
|
||||
case 0xFF6A:
|
||||
case 0xFF6B:
|
||||
if (GBC_compat)
|
||||
{
|
||||
ret = ppu_pntr->ReadReg(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0xFF;
|
||||
}
|
||||
break;
|
||||
|
||||
// Speed Control for GBC
|
||||
case 0xFF70:
|
||||
if (GBC_compat)
|
||||
{
|
||||
ret = (uint8_t)RAM_Bank;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0xFF;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF6C:
|
||||
if (GBC_compat) { ret = undoc_6C; }
|
||||
else { ret = 0xFF; }
|
||||
break;
|
||||
|
||||
case 0xFF72:
|
||||
if (is_GBC) { ret = undoc_72; }
|
||||
else { ret = 0xFF; }
|
||||
break;
|
||||
|
||||
case 0xFF73:
|
||||
if (is_GBC) { ret = undoc_73; }
|
||||
else { ret = 0xFF; }
|
||||
break;
|
||||
|
||||
case 0xFF74:
|
||||
if (GBC_compat) { ret = undoc_74; }
|
||||
else { ret = 0xFF; }
|
||||
break;
|
||||
|
||||
case 0xFF75:
|
||||
if (is_GBC) { ret = undoc_75; }
|
||||
else { ret = 0xFF; }
|
||||
break;
|
||||
|
||||
case 0xFF76:
|
||||
if (is_GBC) { ret = undoc_76; }
|
||||
else { ret = 0xFF; }
|
||||
break;
|
||||
|
||||
case 0xFF77:
|
||||
if (is_GBC) { ret = undoc_77; }
|
||||
else { ret = 0xFF; }
|
||||
break;
|
||||
|
||||
// interrupt control register
|
||||
case 0xFFFF:
|
||||
ret = REG_FFFF;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = 0xFF;
|
||||
break;
|
||||
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Write_Registers(int addr, uint8_t value)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
// select input
|
||||
case 0xFF00:
|
||||
input_register &= 0xCF;
|
||||
input_register |= (uint8_t)(value & 0x30); // top 2 bits always 1
|
||||
|
||||
// check for high to low transitions that trigger IRQs
|
||||
uint8_t contr_prev = input_register;
|
||||
|
||||
input_register &= 0xF0;
|
||||
if ((input_register & 0x30) == 0x20)
|
||||
{
|
||||
input_register |= (uint8_t)(controller_state & 0xF);
|
||||
}
|
||||
else if ((input_register & 0x30) == 0x10)
|
||||
{
|
||||
input_register |= (uint8_t)((controller_state & 0xF0) >> 4);
|
||||
}
|
||||
else if ((input_register & 0x30) == 0x00)
|
||||
{
|
||||
// if both polls are set, then a bit is zero if either or both pins are zero
|
||||
uint8_t temp = (uint8_t)((controller_state & 0xF) & ((controller_state & 0xF0) >> 4));
|
||||
input_register |= temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
input_register |= 0xF;
|
||||
}
|
||||
|
||||
// check for interrupts
|
||||
if (((contr_prev & 8) > 0) && ((input_register & 8) == 0) ||
|
||||
((contr_prev & 4) > 0) && ((input_register & 4) == 0) ||
|
||||
((contr_prev & 2) > 0) && ((input_register & 2) == 0) ||
|
||||
((contr_prev & 1) > 0) && ((input_register & 1) == 0))
|
||||
{
|
||||
if (((REG_FFFF & 0x10) > 0)) { cpu_pntr->FlagI = true; }
|
||||
REG_FF0F |= 0x10;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Serial data port
|
||||
case 0xFF01:
|
||||
serialport_pntr->WriteReg(addr, value);
|
||||
break;
|
||||
|
||||
// Serial port control
|
||||
case 0xFF02:
|
||||
serialport_pntr->WriteReg(addr, value);
|
||||
break;
|
||||
|
||||
// Timer Registers
|
||||
case 0xFF04:
|
||||
case 0xFF05:
|
||||
case 0xFF06:
|
||||
case 0xFF07:
|
||||
timer_pntr->WriteReg(addr, value);
|
||||
break;
|
||||
|
||||
// Interrupt flags
|
||||
case 0xFF0F:
|
||||
REG_FF0F = (uint8_t)(0xE0 | value);
|
||||
|
||||
// check if enabling any of the bits triggered an IRQ
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (((REG_FFFF & (1 <<i)) > 0) && ((REG_FF0F & (1 << i)) > 0))
|
||||
{
|
||||
cpu_pntr->FlagI = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no bits are in common between flags and enables, de-assert the IRQ
|
||||
if (((REG_FF0F & 0x1F) & REG_FFFF) == 0) { cpu_pntr->FlagI = false; }
|
||||
break;
|
||||
|
||||
// audio regs
|
||||
case 0xFF10:
|
||||
case 0xFF11:
|
||||
case 0xFF12:
|
||||
case 0xFF13:
|
||||
case 0xFF14:
|
||||
case 0xFF16:
|
||||
case 0xFF17:
|
||||
case 0xFF18:
|
||||
case 0xFF19:
|
||||
case 0xFF1A:
|
||||
case 0xFF1B:
|
||||
case 0xFF1C:
|
||||
case 0xFF1D:
|
||||
case 0xFF1E:
|
||||
case 0xFF20:
|
||||
case 0xFF21:
|
||||
case 0xFF22:
|
||||
case 0xFF23:
|
||||
case 0xFF24:
|
||||
case 0xFF25:
|
||||
case 0xFF26:
|
||||
case 0xFF30:
|
||||
case 0xFF31:
|
||||
case 0xFF32:
|
||||
case 0xFF33:
|
||||
case 0xFF34:
|
||||
case 0xFF35:
|
||||
case 0xFF36:
|
||||
case 0xFF37:
|
||||
case 0xFF38:
|
||||
case 0xFF39:
|
||||
case 0xFF3A:
|
||||
case 0xFF3B:
|
||||
case 0xFF3C:
|
||||
case 0xFF3D:
|
||||
case 0xFF3E:
|
||||
case 0xFF3F:
|
||||
psg_pntr->WriteReg(addr, value);
|
||||
break;
|
||||
|
||||
// PPU Regs
|
||||
case 0xFF40:
|
||||
case 0xFF41:
|
||||
case 0xFF42:
|
||||
case 0xFF43:
|
||||
case 0xFF44:
|
||||
case 0xFF45:
|
||||
case 0xFF46:
|
||||
case 0xFF47:
|
||||
case 0xFF48:
|
||||
case 0xFF49:
|
||||
case 0xFF4A:
|
||||
case 0xFF4B:
|
||||
ppu_pntr->WriteReg(addr, value);
|
||||
break;
|
||||
|
||||
// GBC compatibility register (I think)
|
||||
case 0xFF4C:
|
||||
if ((value != 0xC0) && (value != 0x80))// && (value != 0xFF) && (value != 0x04))
|
||||
{
|
||||
GBC_compat = false;
|
||||
|
||||
// cpu operation is a function of hardware only
|
||||
//cpu.is_GBC = GBC_compat;
|
||||
}
|
||||
break;
|
||||
|
||||
// Speed Control for GBC
|
||||
case 0xFF4D:
|
||||
if (GBC_compat)
|
||||
{
|
||||
speed_switch = (value & 1) > 0;
|
||||
}
|
||||
break;
|
||||
|
||||
// VBK
|
||||
case 0xFF4F:
|
||||
if (GBC_compat && !ppu_pntr->HDMA_active)
|
||||
{
|
||||
VRAM_Bank = (uint8_t)(value & 1);
|
||||
}
|
||||
break;
|
||||
|
||||
// Bios control register. Writing 1 permanently disables BIOS until a power cycle occurs
|
||||
case 0xFF50:
|
||||
// Console.WriteLine(value);
|
||||
if (GB_bios_register == 0)
|
||||
{
|
||||
GB_bios_register = value;
|
||||
}
|
||||
break;
|
||||
|
||||
// PPU Regs for GBC
|
||||
case 0xFF51:
|
||||
case 0xFF52:
|
||||
case 0xFF53:
|
||||
case 0xFF54:
|
||||
case 0xFF55:
|
||||
if (GBC_compat)
|
||||
{
|
||||
ppu_pntr->WriteReg(addr, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF56:
|
||||
if (is_GBC)
|
||||
{
|
||||
IR_reg = (uint8_t)((value & 0xC1) | (IR_reg & 0x3E));
|
||||
|
||||
// send IR signal out
|
||||
if ((IR_reg & 0x1) == 0x1) { IR_signal = (uint8_t)(0 | IR_mask); }
|
||||
else { IR_signal = 2; }
|
||||
|
||||
// receive own signal if IR on and receive on
|
||||
if ((IR_reg & 0xC1) == 0xC1) { IR_self = (uint8_t)(0 | IR_mask); }
|
||||
else { IR_self = 2; }
|
||||
|
||||
IR_write = 8;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF68:
|
||||
case 0xFF69:
|
||||
case 0xFF6A:
|
||||
case 0xFF6B:
|
||||
//if (GBC_compat)
|
||||
//{
|
||||
ppu_pntr->WriteReg(addr, value);
|
||||
//}
|
||||
break;
|
||||
|
||||
// RAM Bank in GBC mode
|
||||
case 0xFF70:
|
||||
//Console.WriteLine(value);
|
||||
if (GBC_compat)
|
||||
{
|
||||
RAM_Bank = value & 7;
|
||||
if (RAM_Bank == 0) { RAM_Bank = 1; }
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF6C:
|
||||
if (GBC_compat) { undoc_6C |= (uint8_t)(value & 1); }
|
||||
break;
|
||||
|
||||
case 0xFF72:
|
||||
if (is_GBC) { undoc_72 = value; }
|
||||
break;
|
||||
|
||||
case 0xFF73:
|
||||
if (is_GBC) { undoc_73 = value; }
|
||||
break;
|
||||
|
||||
case 0xFF74:
|
||||
if (GBC_compat) { undoc_74 = value; }
|
||||
break;
|
||||
|
||||
case 0xFF75:
|
||||
if (is_GBC) { undoc_75 |= (uint8_t)(value & 0x70); }
|
||||
break;
|
||||
|
||||
case 0xFF76:
|
||||
// read only
|
||||
break;
|
||||
|
||||
case 0xFF77:
|
||||
// read only
|
||||
break;
|
||||
|
||||
// interrupt control register
|
||||
case 0xFFFF:
|
||||
REG_FFFF = value;
|
||||
|
||||
// check if enabling any of the bits triggered an IRQ
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (((REG_FFFF & (1 << i)) > 0) && ((REG_FF0F & (1 << i)) > 0))
|
||||
{
|
||||
cpu_pntr->FlagI = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no bits are in common between flags and enables, de-assert the IRQ
|
||||
if (((REG_FF0F & 0x1F) & REG_FFFF) == 0) { cpu_pntr->FlagI = false; }
|
||||
break;
|
||||
|
||||
default:
|
||||
//Console.Write(addr);
|
||||
//Console.Write(" ");
|
||||
//Console.WriteLine(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Register_Reset()
|
||||
{
|
||||
input_register = 0xCF; // not reading any input
|
||||
|
||||
REG_FFFF = 0;
|
||||
REG_FF0F = 0xE0;
|
||||
REG_FF0F_OLD = 0xE0;
|
||||
|
||||
//undocumented registers
|
||||
undoc_6C = 0xFE;
|
||||
undoc_72 = 0;
|
||||
undoc_73 = 0;
|
||||
undoc_74 = 0;
|
||||
undoc_75 = 0x8F;
|
||||
undoc_76 = 0;
|
||||
undoc_77 = 0;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace GBHawk
|
||||
{
|
||||
class SerialPort
|
||||
{
|
||||
public:
|
||||
|
||||
SerialPort()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
bool* GBC_compat = nullptr;
|
||||
bool* FlagI = nullptr;
|
||||
uint8_t* REG_FFFF = nullptr;
|
||||
uint8_t* REG_FF0F = nullptr;
|
||||
|
||||
bool serial_start;
|
||||
bool can_pulse;
|
||||
uint8_t serial_control;
|
||||
uint8_t serial_data;
|
||||
uint8_t going_out;
|
||||
uint8_t coming_in;
|
||||
uint32_t serial_clock;
|
||||
uint32_t serial_bits;
|
||||
uint32_t clk_rate;
|
||||
|
||||
uint8_t ReadReg(int addr)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF01:
|
||||
return serial_data;
|
||||
case 0xFF02:
|
||||
return serial_control;
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void WriteReg(int addr, uint8_t value)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF01:
|
||||
serial_data = value;
|
||||
break;
|
||||
|
||||
case 0xFF02:
|
||||
if (((value & 0x80) > 0) && !serial_start)
|
||||
{
|
||||
serial_start = true;
|
||||
serial_bits = 8;
|
||||
if ((value & 1) > 0)
|
||||
{
|
||||
if (((value & 2) > 0) && GBC_compat[0])
|
||||
{
|
||||
clk_rate = 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
clk_rate = 512;
|
||||
}
|
||||
serial_clock = clk_rate;
|
||||
can_pulse = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
clk_rate = -1;
|
||||
serial_clock = clk_rate;
|
||||
can_pulse = false;
|
||||
}
|
||||
}
|
||||
else if (serial_start)
|
||||
{
|
||||
if ((value & 1) > 0)
|
||||
{
|
||||
if (((value & 2) > 0) && GBC_compat[0])
|
||||
{
|
||||
clk_rate = 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
clk_rate = 512;
|
||||
}
|
||||
serial_clock = clk_rate;
|
||||
can_pulse = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
clk_rate = -1;
|
||||
serial_clock = clk_rate;
|
||||
can_pulse = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (GBC_compat[0])
|
||||
{
|
||||
serial_control = (uint8_t)(0x7C | (value & 0x83)); // extra CGB bit
|
||||
}
|
||||
else
|
||||
{
|
||||
serial_control = (uint8_t)(0x7E | (value & 0x81)); // middle six bits always 1
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void serial_transfer_tick()
|
||||
{
|
||||
if (serial_start)
|
||||
{
|
||||
if (serial_clock > 0) { serial_clock--; }
|
||||
|
||||
if (serial_clock == 0)
|
||||
{
|
||||
if (serial_bits > 0)
|
||||
{
|
||||
serial_data = (uint8_t)((serial_data << 1) | coming_in);
|
||||
|
||||
serial_bits--;
|
||||
|
||||
if (serial_bits == 0)
|
||||
{
|
||||
serial_control &= 0x7F;
|
||||
serial_start = false;
|
||||
|
||||
if ((REG_FFFF[0] & 0x8) > 0) { FlagI[0] = true; }
|
||||
REG_FF0F[0] |= 0x08;
|
||||
}
|
||||
else
|
||||
{
|
||||
serial_clock = clk_rate;
|
||||
if (clk_rate > 0) { can_pulse = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
serial_control = 0x7E;
|
||||
serial_data = 0x00;
|
||||
serial_start = false;
|
||||
serial_clock = 0;
|
||||
serial_bits = 0;
|
||||
clk_rate = 16;
|
||||
going_out = 0;
|
||||
coming_in = 1;
|
||||
can_pulse = false;
|
||||
}
|
||||
|
||||
#pragma region State Save / Load
|
||||
|
||||
uint8_t* SaveState(uint8_t* saver)
|
||||
{
|
||||
*saver = (uint8_t)(serial_start ? 1 : 0); saver++;
|
||||
*saver = (uint8_t)(can_pulse ? 1 : 0); saver++;
|
||||
|
||||
*saver = serial_control; saver++;
|
||||
*saver = serial_data; saver++;
|
||||
*saver = going_out; saver++;
|
||||
*saver = coming_in; saver++;
|
||||
|
||||
*saver = (uint8_t)(serial_clock & 0xFF); saver++; *saver = (uint8_t)((serial_clock >> 8) & 0xFF); saver++;
|
||||
*saver = (uint8_t)((serial_clock >> 16) & 0xFF); saver++; *saver = (uint8_t)((serial_clock >> 24) & 0xFF); saver++;
|
||||
|
||||
*saver = (uint8_t)(serial_bits & 0xFF); saver++; *saver = (uint8_t)((serial_bits >> 8) & 0xFF); saver++;
|
||||
*saver = (uint8_t)((serial_bits >> 16) & 0xFF); saver++; *saver = (uint8_t)((serial_bits >> 24) & 0xFF); saver++;
|
||||
|
||||
*saver = (uint8_t)(clk_rate & 0xFF); saver++; *saver = (uint8_t)((clk_rate >> 8) & 0xFF); saver++;
|
||||
*saver = (uint8_t)((clk_rate >> 16) & 0xFF); saver++; *saver = (uint8_t)((clk_rate >> 24) & 0xFF); saver++;
|
||||
|
||||
return saver;
|
||||
}
|
||||
|
||||
uint8_t* LoadState(uint8_t* loader)
|
||||
{
|
||||
serial_start = *loader == 1; loader++;
|
||||
can_pulse = *loader == 1; loader++;
|
||||
|
||||
serial_control = *loader; loader++;
|
||||
serial_data = *loader; loader++;
|
||||
going_out = *loader; loader++;
|
||||
coming_in = *loader; loader++;
|
||||
|
||||
serial_clock = *loader; loader++; serial_clock |= (*loader << 8); loader++;
|
||||
serial_clock |= (*loader << 16); loader++; serial_clock |= (*loader << 24); loader++;
|
||||
|
||||
serial_bits = *loader; loader++; serial_bits |= (*loader << 8); loader++;
|
||||
serial_bits |= (*loader << 16); loader++; serial_bits |= (*loader << 24); loader++;
|
||||
|
||||
clk_rate = *loader; loader++; clk_rate |= (*loader << 8); loader++;
|
||||
clk_rate |= (*loader << 16); loader++; clk_rate |= (*loader << 24); loader++;
|
||||
|
||||
return loader;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
};
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace GBHawk
|
||||
{
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
|
||||
Timer()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
bool* FlagI = nullptr;
|
||||
uint8_t* REG_FFFF = nullptr;
|
||||
uint8_t* REG_FF0F = nullptr;
|
||||
|
||||
bool old_state;
|
||||
bool state;
|
||||
bool reload_block;
|
||||
bool TMA_coincidence;
|
||||
|
||||
uint8_t timer_reload;
|
||||
uint8_t timer;
|
||||
uint8_t timer_old;
|
||||
uint8_t timer_control;
|
||||
uint8_t pending_reload;
|
||||
uint8_t write_ignore;
|
||||
|
||||
uint32_t divider_reg;
|
||||
|
||||
uint8_t ReadReg(uint32_t addr)
|
||||
{
|
||||
uint8_t ret = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF04: ret = (uint8_t)(divider_reg >> 8); break; // DIV register
|
||||
case 0xFF05: ret = timer; break; // TIMA (Timer Counter)
|
||||
case 0xFF06: ret = timer_reload; break; // TMA (Timer Modulo)
|
||||
case 0xFF07: ret = timer_control; break; // TAC (Timer Control)
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WriteReg(int addr, uint8_t value)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
// DIV register
|
||||
case 0xFF04:
|
||||
divider_reg = 0;
|
||||
break;
|
||||
|
||||
// TIMA (Timer Counter)
|
||||
case 0xFF05:
|
||||
if (write_ignore == 0)
|
||||
{
|
||||
timer_old = timer;
|
||||
timer = value;
|
||||
reload_block = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// TMA (Timer Modulo)
|
||||
case 0xFF06:
|
||||
timer_reload = value;
|
||||
if (TMA_coincidence)
|
||||
{
|
||||
timer = timer_reload;
|
||||
timer_old = timer;
|
||||
}
|
||||
break;
|
||||
|
||||
// TAC (Timer Control)
|
||||
case 0xFF07:
|
||||
timer_control = (uint8_t)((timer_control & 0xf8) | (value & 0x7)); // only bottom 3 bits function
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void tick_1()
|
||||
{
|
||||
if (write_ignore > 0)
|
||||
{
|
||||
write_ignore--;
|
||||
if (write_ignore == 0)
|
||||
{
|
||||
TMA_coincidence = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pending_reload > 0)
|
||||
{
|
||||
pending_reload--;
|
||||
if (pending_reload == 0 && !reload_block)
|
||||
{
|
||||
timer = timer_reload;
|
||||
timer_old = timer;
|
||||
write_ignore = 4;
|
||||
TMA_coincidence = true;
|
||||
|
||||
// set interrupts
|
||||
if ((REG_FFFF[0] & 0x4) > 0) { FlagI[0] = true; }
|
||||
REG_FF0F[0] |= 0x04;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tick_2()
|
||||
{
|
||||
divider_reg++;
|
||||
|
||||
// pick a bit to test based on the current value of timer control
|
||||
switch (timer_control & 3)
|
||||
{
|
||||
case 0:
|
||||
state = (divider_reg & 0x200) > 0;
|
||||
break;
|
||||
case 1:
|
||||
state = (divider_reg & 0x8) > 0;
|
||||
break;
|
||||
case 2:
|
||||
state = (divider_reg & 0x20) > 0;
|
||||
break;
|
||||
case 3:
|
||||
state = (divider_reg & 0x80) > 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// And it with the state of the timer on/off bit
|
||||
state &= (timer_control & 4) > 0;
|
||||
|
||||
// this procedure allows several glitchy timer ticks, since it only measures falling edge of the state
|
||||
// so things like turning the timer off and resetting the divider will tick the timer
|
||||
if (old_state && !state)
|
||||
{
|
||||
timer_old = timer;
|
||||
timer++;
|
||||
|
||||
// if overflow happens, set the interrupt flag and reload the timer (if applicable)
|
||||
if (timer < timer_old)
|
||||
{
|
||||
if ((timer_control & 4) > 0)
|
||||
{
|
||||
pending_reload = 4;
|
||||
reload_block = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: Check if timer still gets reloaded if TAC diabled causes overflow
|
||||
if ((REG_FFFF[0] & 0x4) > 0) { FlagI[0] = true; }
|
||||
REG_FF0F[0] |= 0x04;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
old_state = state;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
divider_reg = 8; // probably always 8 but not confirmed for GB as far as I know
|
||||
timer_reload = 0;
|
||||
timer = 0;
|
||||
timer_old = 0;
|
||||
timer_control = 0xF8;
|
||||
pending_reload = 0;
|
||||
write_ignore = 0;
|
||||
old_state = false;
|
||||
state = false;
|
||||
reload_block = false;
|
||||
TMA_coincidence = false;
|
||||
}
|
||||
|
||||
#pragma region State Save / Load
|
||||
|
||||
uint8_t* SaveState(uint8_t* saver)
|
||||
{
|
||||
|
||||
*saver = (uint8_t)(old_state ? 1 : 0); saver++;
|
||||
*saver = (uint8_t)(state ? 1 : 0); saver++;
|
||||
*saver = (uint8_t)(reload_block ? 1 : 0); saver++;
|
||||
*saver = (uint8_t)(TMA_coincidence ? 1 : 0); saver++;
|
||||
|
||||
*saver = timer_reload; saver++;
|
||||
*saver = timer; saver++;
|
||||
*saver = timer_old; saver++;
|
||||
*saver = timer_control; saver++;
|
||||
*saver = pending_reload; saver++;
|
||||
*saver = write_ignore; saver++;
|
||||
|
||||
*saver = (uint8_t)(divider_reg & 0xFF); saver++; *saver = (uint8_t)((divider_reg >> 8) & 0xFF); saver++;
|
||||
*saver = (uint8_t)((divider_reg >> 16) & 0xFF); saver++; *saver = (uint8_t)((divider_reg >> 24) & 0xFF); saver++;
|
||||
|
||||
return saver;
|
||||
}
|
||||
|
||||
uint8_t* LoadState(uint8_t* loader)
|
||||
{
|
||||
old_state = *loader == 1; loader++;
|
||||
state = *loader == 1; loader++;
|
||||
reload_block = *loader == 1; loader++;
|
||||
TMA_coincidence = *loader == 1; loader++;
|
||||
|
||||
timer_reload = *loader; loader++;
|
||||
timer = *loader; loader++;
|
||||
timer_old = *loader; loader++;
|
||||
timer_control = *loader; loader++;
|
||||
pending_reload = *loader; loader++;
|
||||
write_ignore = *loader; loader++;
|
||||
|
||||
divider_reg = *loader; loader++; divider_reg |= (*loader << 8); loader++;
|
||||
divider_reg |= (*loader << 16); loader++; divider_reg |= (*loader << 24); loader++;
|
||||
|
||||
return loader;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue