GBHawk: C++ memory map

This commit is contained in:
alyosha-tas 2020-03-26 08:58:46 -04:00
parent 37e8b29056
commit 31c41f6279
7 changed files with 1009 additions and 26 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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" />

View File

@ -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;
}

View File

@ -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

View File

@ -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
};
}

View File

@ -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
};
}