From 31c41f6279eca6cffded8fa69384d47dc0abbb91 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Thu, 26 Mar 2020 08:58:46 -0400 Subject: [PATCH] GBHawk: C++ memory map --- libHawk/GBHawk/GBHawk/Core.h | 19 + libHawk/GBHawk/GBHawk/GBAudio.h | 40 +- libHawk/GBHawk/GBHawk/GBHawk.vcxproj | 2 + libHawk/GBHawk/GBHawk/Memory.cpp | 2 +- libHawk/GBHawk/GBHawk/Memory.h | 535 ++++++++++++++++++++++++++- libHawk/GBHawk/GBHawk/SerialPort.h | 210 +++++++++++ libHawk/GBHawk/GBHawk/Timer.h | 227 ++++++++++++ 7 files changed, 1009 insertions(+), 26 deletions(-) create mode 100644 libHawk/GBHawk/GBHawk/SerialPort.h create mode 100644 libHawk/GBHawk/GBHawk/Timer.h diff --git a/libHawk/GBHawk/GBHawk/Core.h b/libHawk/GBHawk/GBHawk/Core.h index c6a9892ead..c03d4118ef 100644 --- a/libHawk/GBHawk/GBHawk/Core.h +++ b/libHawk/GBHawk/GBHawk/Core.h @@ -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; diff --git a/libHawk/GBHawk/GBHawk/GBAudio.h b/libHawk/GBHawk/GBHawk/GBAudio.h index cc875fe001..e7e2765fd7 100644 --- a/libHawk/GBHawk/GBHawk/GBAudio.h +++ b/libHawk/GBHawk/GBHawk/GBAudio.h @@ -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); diff --git a/libHawk/GBHawk/GBHawk/GBHawk.vcxproj b/libHawk/GBHawk/GBHawk/GBHawk.vcxproj index 84b7adc44e..e0a6fa1e09 100644 --- a/libHawk/GBHawk/GBHawk/GBHawk.vcxproj +++ b/libHawk/GBHawk/GBHawk/GBHawk.vcxproj @@ -157,6 +157,8 @@ + + diff --git a/libHawk/GBHawk/GBHawk/Memory.cpp b/libHawk/GBHawk/GBHawk/Memory.cpp index b822c3cbb8..3bf7fd7c92 100644 --- a/libHawk/GBHawk/GBHawk/Memory.cpp +++ b/libHawk/GBHawk/GBHawk/Memory.cpp @@ -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; } diff --git a/libHawk/GBHawk/GBHawk/Memory.h b/libHawk/GBHawk/GBHawk/Memory.h index eec52b9eb3..bcd0477b7f 100644 --- a/libHawk/GBHawk/GBHawk/Memory.h +++ b/libHawk/GBHawk/GBHawk/Memory.h @@ -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 < 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 diff --git a/libHawk/GBHawk/GBHawk/SerialPort.h b/libHawk/GBHawk/GBHawk/SerialPort.h new file mode 100644 index 0000000000..fcd2c88746 --- /dev/null +++ b/libHawk/GBHawk/GBHawk/SerialPort.h @@ -0,0 +1,210 @@ +#include +#include +#include +#include + +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 + }; +} diff --git a/libHawk/GBHawk/GBHawk/Timer.h b/libHawk/GBHawk/GBHawk/Timer.h new file mode 100644 index 0000000000..713ef03db1 --- /dev/null +++ b/libHawk/GBHawk/GBHawk/Timer.h @@ -0,0 +1,227 @@ +#include +#include +#include +#include + +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 + }; +} \ No newline at end of file