mirror of
https://github.com/SonoSooS/PicoGB.git
synced 2025-06-18 17:05:34 -04:00
1318 lines
44 KiB
C
1318 lines
44 KiB
C
#include <stdio.h>
|
|
|
|
#include "microcode.h"
|
|
#include "microcode_dispatch.h"
|
|
#include "dbg.h"
|
|
|
|
|
|
#define self mb_state* __restrict
|
|
|
|
#if GBA
|
|
#define IR_F_COL (IR & 7)
|
|
#define IR_F_ROW ((IR >> 3) & 7)
|
|
#else
|
|
#define IR_F_COL IR_column
|
|
#define IR_F_ROW IR_row
|
|
#endif
|
|
|
|
#define MB_AF_R ((mb->reg.A << 8) + mb->reg.F)
|
|
#define MB_AF_W(v) {mb->reg.A = ((v) >> 8) & 0xFF; mb->reg.F = (v) & MB_FLAG_BITS;}
|
|
#define MB_CC_CHECK (mbh_cc_check(IR, mb->reg.F))
|
|
|
|
|
|
#pragma region Microcode I/O
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Flag mode control
|
|
|
|
PGB_FUNC static inline void mbh_fr_set_r8_add(self mb, word left, word right)
|
|
{
|
|
mb->FR1 = left;
|
|
mb->FR2 = right;
|
|
mb->FMC_MODE = MB_FMC_MODE_ADD_r8;
|
|
}
|
|
|
|
PGB_FUNC static inline void mbh_fr_set_r8_adc(self mb, word left, word right)
|
|
{
|
|
mb->FR1 = left;
|
|
mb->FR2 = right;
|
|
mb->FMC_MODE = MB_FMC_MODE_ADC_r8;
|
|
}
|
|
|
|
PGB_FUNC static inline void mbh_fr_set_r8_sub(self mb, word left, word right)
|
|
{
|
|
mb->FR1 = left;
|
|
mb->FR2 = right;
|
|
mb->FMC_MODE = MB_FMC_MODE_SUB_r8;
|
|
}
|
|
|
|
PGB_FUNC static inline void mbh_fr_set_r8_sbc(self mb, word left, word right)
|
|
{
|
|
mb->FR1 = left;
|
|
mb->FR2 = right;
|
|
mb->FMC_MODE = MB_FMC_MODE_SBC_r8;
|
|
}
|
|
|
|
PGB_FUNC static inline void mbh_fr_set_r16_add(self mb, word left, word right)
|
|
{
|
|
mb->FR1 = left;
|
|
mb->FR2 = right;
|
|
mb->FMC_MODE = MB_FMC_MODE_ADD_r16;
|
|
}
|
|
|
|
PGB_FUNC static inline void mbh_fr_set_r16_add_r8(self mb, word left, word right)
|
|
{
|
|
mb->FR1 = left;
|
|
mb->FR2 = right;
|
|
mb->FMC_MODE = MB_FMC_MODE_ADD_r16_r8;
|
|
}
|
|
|
|
PGB_FUNC word mbh_fr_get(self mb, word Fin)
|
|
{
|
|
if(mb->FMC_MODE == MB_FMC_MODE_NONE)
|
|
return Fin;
|
|
|
|
var n1 = mb->FR1;
|
|
var n2 = mb->FR2;
|
|
|
|
Fin &= ~MB_FLAG_H; //TODO: why clear HC here? explain.
|
|
|
|
switch(mb->FMC_MODE & 0xF)
|
|
{
|
|
default:
|
|
return Fin;
|
|
|
|
case MB_FMC_MODE_ADD_r16_r8:
|
|
{
|
|
//if(((n1 & 0xF00) + (n2 & 0xF00)) > 0xFFF)
|
|
if(((n1 & 0xF) + (n2 & 0xF)) > 0xF) // ???
|
|
Fin |= MB_FLAG_H;
|
|
|
|
break;
|
|
}
|
|
|
|
case MB_FMC_MODE_ADD_r16:
|
|
{
|
|
if(((n1 & 0xFFF) + (n2 & 0xFFF)) > 0xFFF)
|
|
Fin |= MB_FLAG_H;
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case MB_FMC_MODE_ADD_r8:
|
|
{
|
|
if(((n1 & 0xF) + (n2 & 0xF)) > 0xF)
|
|
Fin |= MB_FLAG_H;
|
|
|
|
break;
|
|
}
|
|
|
|
case MB_FMC_MODE_ADC_r8:
|
|
{
|
|
if(((n1 & 0xF) + (n2 & 0xF) + 1) > 0xF)
|
|
Fin |= MB_FLAG_H;
|
|
|
|
break;
|
|
}
|
|
|
|
case MB_FMC_MODE_SUB_r8:
|
|
{
|
|
if((n1 & 0xF) < (n2 & 0xF))
|
|
Fin |= MB_FLAG_H;
|
|
|
|
break;
|
|
}
|
|
|
|
case MB_FMC_MODE_SBC_r8:
|
|
{
|
|
if((n1 & 0xF) < ((n2 & 0xF) + 1))
|
|
Fin |= MB_FLAG_H;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
return Fin;
|
|
}
|
|
|
|
PGB_FUNC static inline wbool mbh_cc_check_0(word F)
|
|
{
|
|
return ~F & MB_FLAG_Z; // NZ
|
|
}
|
|
|
|
PGB_FUNC static inline wbool mbh_cc_check_1(word F)
|
|
{
|
|
return F & MB_FLAG_Z; // Z
|
|
}
|
|
|
|
PGB_FUNC static inline wbool mbh_cc_check_2(word F)
|
|
{
|
|
return ~F & MB_FLAG_C; // NC
|
|
}
|
|
|
|
PGB_FUNC static inline wbool mbh_cc_check_3(word F)
|
|
{
|
|
return F & MB_FLAG_C; // C
|
|
}
|
|
|
|
PGB_FUNC static wbool mbh_cc_check(word IR, word F)
|
|
{
|
|
register word IR_r = (IR >> 3) & 3;
|
|
|
|
if(IR_r == MB_CC_NZ)
|
|
return mbh_cc_check_0(F); // NZ
|
|
else if(IR_r == MB_CC_Z)
|
|
return mbh_cc_check_1(F); // Z
|
|
else if(IR_r == MB_CC_NC)
|
|
return mbh_cc_check_2(F); // NC
|
|
else if(IR_r == MB_CC_C)
|
|
return mbh_cc_check_3(F); // C
|
|
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region disasm (unfinished)
|
|
#if CONFIG_DBG
|
|
|
|
static const char* str_r8[8] = {"B", "C", "D", "E", "H", "L", "[HL]", "A"};
|
|
static const char* str_aluop[8] = {"ADD", "ADC", "SUB", "SBC", "AND", "XOR", "ORR", "CMP"};
|
|
|
|
void mb_disasm(const struct mb_state* __restrict mb)
|
|
{
|
|
var IR = mb->IR.low;
|
|
|
|
switch(IR >> 6)
|
|
{
|
|
case 1:
|
|
printf("LD %s, %s", str_r8[(IR >> 3) & 7], str_r8[IR & 7]);
|
|
break;
|
|
|
|
case 2:
|
|
printf("%s A, %s", str_aluop[(IR >> 3) & 7], str_r8[IR & 7]);
|
|
break;
|
|
}
|
|
|
|
puts("");
|
|
}
|
|
|
|
static const char* str_cbop[4] = {0, "BIT", "SET", "RES"};
|
|
static const char* str_cbop0[8] = {"ROL", "ROR", "RCL", "RCR", "LSL", "ASR", "SWAP", "LSR"};
|
|
|
|
void mb_disasm_CB(const struct mb_state* __restrict mb, word CBIR)
|
|
{
|
|
var IR = CBIR;
|
|
|
|
if(IR >> 6)
|
|
{
|
|
printf("%s %s.%u", str_cbop[(IR >> 6) & 3], str_r8[IR & 7], (IR >> 3) & 7);
|
|
}
|
|
else
|
|
{
|
|
printf("%s %s", str_cbop0[(IR >> 3) & 7], str_r8[IR & 7]);
|
|
}
|
|
|
|
puts("");
|
|
}
|
|
|
|
#endif
|
|
#pragma endregion
|
|
|
|
PGB_FUNC ATTR_HOT word mb_exec(self mb)
|
|
{
|
|
register var IR = mb->IR.low;
|
|
r16* __restrict p_reg16_ptr;
|
|
|
|
// Instruction column left to right
|
|
var IR_column = IR & 7;
|
|
// Instruction row top to bottom
|
|
var IR_row = (IR >> 3) & 7;
|
|
|
|
// Cycle count
|
|
var ncycles = 0;
|
|
|
|
// Index of source or read-only register
|
|
var i_src;
|
|
// Index of destination or read-modify-write register index
|
|
var i_dst;
|
|
|
|
// Data for 8bit registers
|
|
var data_reg;
|
|
// Data for 16bit registers
|
|
var data_wide;
|
|
// Contains flags where necessary
|
|
var data_flags;
|
|
// Contains result data to be written back eventually
|
|
var data_result;
|
|
|
|
if(mb->IME) // Interrupts are enabled
|
|
{
|
|
var F = mbh_irq_get_pending(mb);
|
|
if(F) // Handle IREQ if there is any
|
|
{
|
|
++ncycles; // IDU decrement PC penalty cycle
|
|
|
|
data_wide = (mb->PC - 1) & 0xFFFF;
|
|
|
|
var i = 0;
|
|
for(;;)
|
|
{
|
|
if(F & (1 << i))
|
|
{
|
|
mb->PC = 0x40 + (i << 3);
|
|
break;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
mb->IME = 0;
|
|
mb->IME_ASK = 0;
|
|
|
|
mb->IF &= ~(1 << i);
|
|
|
|
#if CONFIG_DBG
|
|
DBGF("IRQ #%u\n", i);
|
|
#endif
|
|
|
|
goto generic_push;
|
|
}
|
|
}
|
|
else if(mb->IME_ASK) // IME was asked to be turned on
|
|
{
|
|
mb->IME = 1;
|
|
mb->IME_ASK = 0;
|
|
}
|
|
|
|
#if CONFIG_DBG
|
|
if(_IS_DBG)
|
|
{
|
|
DBGF("Instruction %02X (%01o:%01o:%01o) ", IR, IR >> 6, IR & 7, (IR >> 3) & 7);
|
|
mb_disasm(mb);
|
|
}
|
|
#endif
|
|
|
|
#if 1
|
|
if(IR >= 0xC0)
|
|
{
|
|
if(IR == 0xF0)
|
|
{
|
|
data_wide = mch_memory_fetch_PC_op_1(mb);
|
|
goto instr_360;
|
|
}
|
|
else if(IR == 0xFA)
|
|
{
|
|
data_wide = mch_memory_fetch_PC_op_2(mb);
|
|
goto instr_372;
|
|
}
|
|
else
|
|
goto IR_case_3;
|
|
}
|
|
else if(IR >= 0x80)
|
|
goto IR_case_2;
|
|
else if(IR < 0x40)
|
|
{
|
|
if(IR_F_COL == 0)
|
|
goto instr_0x0;
|
|
else
|
|
goto IR_case_0;
|
|
}
|
|
else
|
|
goto IR_case_1;
|
|
#endif
|
|
|
|
switch((IR >> 6) & 3)
|
|
{
|
|
IR_case_0:
|
|
case 0: // Top bullshit
|
|
switch(IR_column)
|
|
{
|
|
instr_0x0:
|
|
case 0: // whatever
|
|
switch(IR_row)
|
|
{
|
|
case 0: // NOP
|
|
goto generic_fetch;
|
|
case 2: // STOP
|
|
goto generic_fetch_stop; // STOP is just bugged NOP, lol
|
|
|
|
case 1: // LD a16, SP
|
|
{
|
|
data_wide = mch_memory_fetch_PC_op_2(mb);
|
|
|
|
var SP = mb->SP;
|
|
|
|
mch_memory_dispatch_write(mb, data_wide + 0, SP & 0xFF);
|
|
mch_memory_dispatch_write(mb, (data_wide + 1) & 0xFFFF, SP >> 8);
|
|
|
|
ncycles += 2 + 2; // a16 op + n16 write
|
|
goto generic_fetch;
|
|
}
|
|
|
|
case 3: // JR e8
|
|
generic_jr:
|
|
{
|
|
var PC = mb->PC;
|
|
data_wide = mch_memory_fetch_PC_op_1(mb);
|
|
|
|
// Wedge if unbreakable spinloop is detected
|
|
// TODO: unfuck this statement
|
|
if(data_wide != 0xFE)
|
|
{
|
|
if(data_wide >= 0x80)
|
|
data_wide += 0xFF00;
|
|
|
|
mb->PC = (data_wide + PC + 1) & 0xFFFF;
|
|
}
|
|
else
|
|
{
|
|
if((!mb->IME && !mb->IME_ASK) || (!mb->IE && !mb->IF))
|
|
return 0; // wedge until NMI
|
|
|
|
mb->PC = PC - 1;
|
|
}
|
|
|
|
ncycles += 2; // op fetch + ALU IDU Cy magic
|
|
goto generic_fetch;
|
|
}
|
|
|
|
case 4: if(mbh_cc_check_0(mb->reg.F)) goto generic_jr; else goto instr_JNR_cc_e8_fail;
|
|
case 5: if(mbh_cc_check_1(mb->reg.F)) goto generic_jr; else goto instr_JNR_cc_e8_fail;
|
|
case 6: if(mbh_cc_check_2(mb->reg.F)) goto generic_jr; else goto instr_JNR_cc_e8_fail;
|
|
case 7: if(mbh_cc_check_3(mb->reg.F)) goto generic_jr; else goto instr_JNR_cc_e8_fail;
|
|
|
|
{
|
|
instr_JNR_cc_e8_fail:
|
|
mb->PC = (mb->PC + 1) & 0xFFFF;
|
|
ncycles += 1; // op fetch only
|
|
goto generic_fetch;
|
|
}
|
|
|
|
default:
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
case 1: // random r16 bullshit
|
|
{
|
|
i_dst = (IR >> 4) & 3;
|
|
|
|
if(i_dst != 3)
|
|
p_reg16_ptr = &mb->reg.raw16[i_dst];
|
|
else
|
|
p_reg16_ptr = &mb->SP;
|
|
|
|
if(!(IR & 8)) // LD r16, n16
|
|
{
|
|
data_wide = mch_memory_fetch_PC_op_2(mb);
|
|
|
|
*p_reg16_ptr = data_wide;
|
|
|
|
ncycles += 2; // n16 op fetch
|
|
goto generic_fetch;
|
|
}
|
|
else // ADD HL, r16
|
|
{
|
|
data_reg = *p_reg16_ptr;
|
|
data_flags = mb->reg.F & MB_FLAG_Z;
|
|
|
|
data_result = mb->reg.HL;
|
|
word mres = data_result + data_reg;
|
|
if(mres >> 16)
|
|
data_flags += MB_FLAG_C;
|
|
|
|
mb->reg.HL = mres;
|
|
mb->reg.F = data_flags;
|
|
|
|
mbh_fr_set_r16_add(mb, data_result, data_reg);
|
|
|
|
ncycles += 2 - 1; // 2nd ALU cycle parallel with fetch
|
|
goto generic_fetch;
|
|
}
|
|
}
|
|
|
|
case 2: // LD r16 ptr
|
|
{
|
|
i_dst = (IR >> 4) & 3;
|
|
|
|
if(i_dst < 2)
|
|
p_reg16_ptr = &mb->reg.raw16[i_dst];
|
|
else
|
|
p_reg16_ptr = &mb->reg.HL;
|
|
|
|
if(IR & 8) // load ptr
|
|
{
|
|
mb->reg.A = mch_memory_dispatch_read(mb, *p_reg16_ptr);
|
|
}
|
|
else // store ptr
|
|
{
|
|
mch_memory_dispatch_write(mb, *p_reg16_ptr, mb->reg.A);
|
|
}
|
|
|
|
if(i_dst < 2)
|
|
;
|
|
else
|
|
{
|
|
if(!(i_dst & 1))
|
|
++*p_reg16_ptr;
|
|
else
|
|
--*p_reg16_ptr;
|
|
}
|
|
|
|
ncycles += 1; // memory op
|
|
goto generic_fetch;
|
|
}
|
|
|
|
case 3: // INCDEC r16
|
|
{
|
|
i_dst = (IR >> 4) & 3;
|
|
|
|
if(i_dst != 3)
|
|
p_reg16_ptr = &mb->reg.raw16[i_dst];
|
|
else
|
|
p_reg16_ptr = &mb->SP;
|
|
|
|
if(!(IR & 8)) // INC r16
|
|
{
|
|
++*p_reg16_ptr;
|
|
}
|
|
else // DEC r16
|
|
{
|
|
--*p_reg16_ptr;
|
|
}
|
|
|
|
ncycles += 1; // IDU post-incdec
|
|
goto generic_fetch;
|
|
}
|
|
|
|
case 4: // INC r8
|
|
case 5: // DEC r8
|
|
{
|
|
// Z1H-
|
|
|
|
i_dst = IR_F_ROW ^ 1;
|
|
|
|
data_flags = mb->reg.F & MB_FLAG_C;
|
|
|
|
if(i_dst != 7)
|
|
data_reg = mb->reg.raw8[i_dst];
|
|
else
|
|
{
|
|
++ncycles; // memory access
|
|
data_reg = mch_memory_dispatch_read(mb, mb->reg.HL);
|
|
}
|
|
|
|
if(IR & 1) // DEC
|
|
{
|
|
data_flags += MB_FLAG_N;
|
|
|
|
if((data_reg & 0xF) == 0)
|
|
data_flags += MB_FLAG_H;
|
|
|
|
data_reg = (data_reg - 1) & 0xFF;
|
|
}
|
|
else // INC
|
|
{
|
|
data_reg = (data_reg + 1) & 0xFF;
|
|
|
|
if((data_reg & 0xF) == 0)
|
|
data_flags += MB_FLAG_H;
|
|
}
|
|
|
|
if(!data_reg)
|
|
data_flags += MB_FLAG_Z;
|
|
|
|
mb->reg.F = data_flags;
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE; // we calculate flags in-place
|
|
|
|
if(i_dst != 7)
|
|
mb->reg.raw8[i_dst] = data_reg;
|
|
else
|
|
{
|
|
++ncycles; // memory access
|
|
mch_memory_dispatch_write(mb, mb->reg.HL, data_reg);
|
|
}
|
|
|
|
goto generic_fetch;
|
|
}
|
|
|
|
case 6: // LD r8, n8
|
|
{
|
|
data_reg = mch_memory_fetch_PC_op_1(mb);
|
|
i_dst = IR_F_ROW ^ 1;
|
|
ncycles += 1; // n8 operand fetch
|
|
goto generic_r8_write;
|
|
}
|
|
|
|
case 7: // generic bullshit
|
|
// just reimpl $CB func here, too much hassle to use goto
|
|
|
|
switch(IR_row)
|
|
{
|
|
case 0: // RLCA
|
|
data_reg = mb->reg.A;
|
|
data_flags = mb->reg.F;
|
|
data_flags &= MB_FLAG_C;
|
|
data_reg = (data_reg << 1) | (data_reg >> 7);
|
|
data_flags = (data_reg >> 4) & 0x10; // Cy flag
|
|
mb->reg.A = data_reg & 0xFF;
|
|
mb->reg.F = data_flags;
|
|
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
goto generic_fetch;
|
|
|
|
case 1: // RRCA
|
|
data_reg = mb->reg.A;
|
|
data_flags = mb->reg.F;
|
|
data_flags &= MB_FLAG_C;
|
|
data_reg = (data_reg << 7) | (data_reg >> 1);
|
|
data_flags = (data_reg >> 3) & 0x10; // Cy flag
|
|
mb->reg.A = data_reg & 0xFF;
|
|
mb->reg.F = data_flags;
|
|
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
goto generic_fetch;
|
|
|
|
case 2: // RLA
|
|
data_reg = mb->reg.A;
|
|
data_flags = mb->reg.F;
|
|
data_flags &= MB_FLAG_C;
|
|
data_reg = (data_reg << 1) | ((data_flags >> 4) & 1);
|
|
data_flags = (data_reg >> 4) & 0x10; // Cy flag
|
|
mb->reg.A = data_reg & 0xFF;
|
|
mb->reg.F = data_flags;
|
|
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
goto generic_fetch;
|
|
|
|
case 3: // RRA
|
|
data_reg = mb->reg.A;
|
|
data_flags = mb->reg.F;
|
|
data_flags &= MB_FLAG_C;
|
|
data_reg = (data_reg << 8) | (data_reg >> 1) | ((data_flags & 0x10) << 3);
|
|
data_flags = (data_reg >> 4) & 0x10; // Cy flag
|
|
mb->reg.A = data_reg & 0xFF;
|
|
mb->reg.F = data_flags;
|
|
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
goto generic_fetch;
|
|
|
|
case 4: // fuck DAA
|
|
data_reg = mb->reg.A;
|
|
data_flags = mb->reg.F;
|
|
data_flags &= ~MB_FLAG_Z;
|
|
|
|
data_flags = mbh_fr_get(mb, data_flags);
|
|
|
|
if(!(data_flags & MB_FLAG_N))
|
|
{
|
|
if((data_flags & MB_FLAG_H) || ((data_reg & 0xF) > 9))
|
|
{
|
|
data_reg += 6;
|
|
data_flags |= MB_FLAG_H;
|
|
}
|
|
|
|
if((data_flags & MB_FLAG_C) || (((data_reg >> 4) & 0x1F) > 9))
|
|
{
|
|
data_reg += 6 << 4;
|
|
data_flags |= MB_FLAG_C;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// why the assymmetry???
|
|
|
|
if((data_flags & MB_FLAG_H))// || ((wdat & 0xF) > 9))
|
|
{
|
|
data_reg -= 6;
|
|
data_flags |= MB_FLAG_H;
|
|
}
|
|
|
|
if((data_flags & MB_FLAG_C))// || (((wdat >> 4) & 0x1F) > 9))
|
|
{
|
|
data_reg -= 6 << 4;
|
|
data_flags |= MB_FLAG_C;
|
|
}
|
|
}
|
|
|
|
data_flags &= (MB_FLAG_C | MB_FLAG_N);
|
|
|
|
data_reg &= 0xFF;
|
|
if(!data_reg)
|
|
data_flags |= MB_FLAG_Z;
|
|
mb->reg.A = data_reg;
|
|
mb->reg.F = data_flags;
|
|
|
|
goto generic_fetch;
|
|
|
|
case 5: // CPL A
|
|
mb->reg.F |= MB_FLAG_N | MB_FLAG_H;
|
|
mb->reg.A = ~mb->reg.A;
|
|
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
goto generic_fetch;
|
|
|
|
case 6: // SET Cy
|
|
mb->reg.F = (mb->reg.F & MB_FLAG_Z) + MB_FLAG_C;
|
|
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
goto generic_fetch;
|
|
|
|
case 7: // CPL Cy
|
|
mb->reg.F = (mb->reg.F ^ MB_FLAG_C) & (MB_FLAG_C | MB_FLAG_Z);
|
|
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
goto generic_fetch;
|
|
|
|
default:
|
|
__builtin_unreachable();
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
IR_case_1:
|
|
case 1: // MOV
|
|
if(COMPILER_LIKELY(IR != 0x76))
|
|
{
|
|
{
|
|
#if GBA
|
|
var vIR = IR ^ 9;
|
|
isrc = vIR & 7;
|
|
idst = (vIR >> 3) & 7;
|
|
#else
|
|
i_src = IR_column ^ 1;
|
|
i_dst = IR_row ^ 1;
|
|
#endif
|
|
}
|
|
|
|
if(i_src != 7)
|
|
{
|
|
data_reg = mb->reg.raw8[i_src];
|
|
}
|
|
else
|
|
{
|
|
++ncycles; // memory access
|
|
data_reg = mch_memory_dispatch_read(mb, mb->reg.HL);
|
|
}
|
|
|
|
generic_r8_write:
|
|
if(i_dst != 7)
|
|
{
|
|
mb->reg.raw8[i_dst] = data_reg;
|
|
}
|
|
else
|
|
{
|
|
++ncycles; // memory access
|
|
mch_memory_dispatch_write(mb, mb->reg.HL, data_reg);
|
|
}
|
|
|
|
goto generic_fetch;
|
|
}
|
|
else
|
|
{
|
|
goto generic_fetch_halt;
|
|
}
|
|
|
|
IR_case_2:
|
|
case 2: // ALU r8
|
|
{
|
|
// A is always the src and dst, thank fuck
|
|
|
|
i_src = IR_F_COL ^ 1;
|
|
|
|
if(i_src != 7)
|
|
{
|
|
data_reg = mb->reg.raw8[i_src];
|
|
}
|
|
else
|
|
{
|
|
++ncycles; // memory access
|
|
data_reg = mch_memory_dispatch_read(mb, mb->reg.HL);
|
|
}
|
|
|
|
alu_op_begin:
|
|
data_result = mb->reg.A;
|
|
|
|
switch(IR_F_ROW)
|
|
{
|
|
instr_ALU_0:
|
|
case 0: // ADD Z0HC
|
|
mbh_fr_set_r8_add(mb, data_result, data_reg);
|
|
|
|
instr_ALU_0_cont:
|
|
data_result = data_result + data_reg;
|
|
if(data_result >> 8)
|
|
data_flags = MB_FLAG_C;
|
|
else
|
|
data_flags = 0;
|
|
|
|
|
|
break;
|
|
|
|
case 1: // ADC Z0HC
|
|
if(mb->reg.F & 0x10)
|
|
{
|
|
mbh_fr_set_r8_adc(mb, data_result, data_reg);
|
|
data_reg += 1;
|
|
goto instr_ALU_0_cont;
|
|
}
|
|
else
|
|
{
|
|
goto instr_ALU_0;
|
|
}
|
|
|
|
instr_ALU_2:
|
|
case 2: // SUB Z1HC
|
|
mbh_fr_set_r8_sub(mb, data_result, data_reg);
|
|
|
|
instr_ALU_2_cont:
|
|
data_result = data_result - data_reg;
|
|
if(data_result >> 8)
|
|
data_flags = MB_FLAG_N | MB_FLAG_C;
|
|
else
|
|
data_flags = MB_FLAG_N;
|
|
|
|
break;
|
|
|
|
case 3: // SBC Z1HC
|
|
if(mb->reg.F & MB_FLAG_C)
|
|
{
|
|
mbh_fr_set_r8_sbc(mb, data_result, data_reg);
|
|
data_reg += 1;
|
|
goto instr_ALU_2_cont;
|
|
}
|
|
else
|
|
{
|
|
goto instr_ALU_2;
|
|
}
|
|
|
|
case 7: // CMP Z1HC
|
|
mbh_fr_set_r8_sub(mb, data_result, data_reg);
|
|
|
|
data_result = data_result - data_reg;
|
|
if(data_result >> 8)
|
|
data_flags = MB_FLAG_N | MB_FLAG_C;
|
|
else
|
|
data_flags = MB_FLAG_N;
|
|
|
|
if(!(data_result & 0xFF))
|
|
data_flags += MB_FLAG_Z;
|
|
|
|
mb->reg.F = data_flags;
|
|
|
|
goto generic_fetch;
|
|
|
|
case 4: // AND Z010
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
|
|
data_flags = MB_FLAG_H;
|
|
data_result &= data_reg;
|
|
break;
|
|
|
|
case 5: // XOR Z000
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
|
|
data_flags = 0;
|
|
data_result ^= data_reg;
|
|
break;
|
|
|
|
case 6: // ORR Z000
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE;
|
|
|
|
data_flags = 0;
|
|
data_result |= data_reg;
|
|
break;
|
|
|
|
default:
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
if(!(data_result & 0xFF))
|
|
data_flags += MB_FLAG_Z;
|
|
|
|
{
|
|
hilow16_t sta;
|
|
sta.low = data_result;
|
|
sta.high = data_flags;
|
|
mb->reg.hilo16[3] = sta; // REG_FA (yes, not AF)
|
|
}
|
|
|
|
goto generic_fetch;
|
|
}
|
|
|
|
IR_case_3:
|
|
case 3: // Bottom bullshit
|
|
switch(IR_column)
|
|
{
|
|
case 0: // misc junk and RET cc
|
|
{
|
|
if(IR & 0x20) // misc junk (bottom 4)
|
|
{
|
|
if(!(IR & 8)) // LDH a8
|
|
{
|
|
data_wide = mch_memory_fetch_PC_op_1(mb);
|
|
|
|
if(IR & 0x10)
|
|
{
|
|
instr_360:
|
|
DBGF("- /HR %04X -> ", data_wide + 0xFF00);
|
|
mb->reg.A = mch_memory_dispatch_read_Haddr(mb, data_wide);
|
|
DBGF("%02X\n", mb->reg.A);
|
|
}
|
|
else
|
|
{
|
|
DBGF("- /HW %04X <- %02X\n", data_wide + 0xFF00, mb->reg.A);
|
|
mch_memory_dispatch_write_Haddr(mb, data_wide, mb->reg.A);
|
|
}
|
|
|
|
ncycles += 2; // a8 op + memory access
|
|
goto generic_fetch;
|
|
}
|
|
else
|
|
{
|
|
data_wide = mch_memory_fetch_PC_op_1(mb);
|
|
if(data_wide >= 0x80)
|
|
data_wide += 0xFF00;
|
|
|
|
data_reg = mb->SP;
|
|
//mbh_fr_set_r16_add_r8(mb, data_reg, data_wide);
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE; // fuck this, the call rate is so low that it's cheaper to do this in-place
|
|
|
|
data_flags = 0;
|
|
|
|
if(((data_wide & 0xFF) + (data_reg & 0xFF)) >> 8)
|
|
data_flags += MB_FLAG_C;
|
|
|
|
if(((data_wide & 0xF) + (data_reg & 0xF)) >> 4)
|
|
data_flags += MB_FLAG_H;
|
|
|
|
data_wide = (data_wide + data_reg) & 0xFFFF;
|
|
|
|
mb->reg.F = data_flags;
|
|
|
|
if(IR & 0x10) // HL, SP+e8
|
|
{
|
|
mb->reg.HL = data_wide;
|
|
ncycles += 2; // operand + magic
|
|
}
|
|
else // SP, SP+e8
|
|
{
|
|
mb->SP = data_wide;
|
|
ncycles += 3; // operand + 2x ALU
|
|
}
|
|
|
|
goto generic_fetch;
|
|
}
|
|
}
|
|
else // RET cc
|
|
{
|
|
ncycles += 1; // cc_check penalty cycle
|
|
|
|
if(!MB_CC_CHECK)
|
|
{
|
|
goto generic_fetch;
|
|
}
|
|
else
|
|
{
|
|
goto generic_ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
case 1: // POP r16 and junk
|
|
{
|
|
if(IR & 8) // junk
|
|
{
|
|
switch((IR >> 4) & 3)
|
|
{
|
|
case 0: // RET
|
|
generic_ret:
|
|
{
|
|
var SP = mb->SP;
|
|
data_wide = mch_memory_dispatch_read(mb, SP++);
|
|
data_wide += mch_memory_dispatch_read(mb, (SP++) & 0xFFFF) << 8;
|
|
mb->SP = SP;
|
|
|
|
mb->PC = data_wide;
|
|
|
|
ncycles += 2 + 1; // 2x LD Imm.(L|H), [SP+] + MOV PC, Imm
|
|
goto generic_fetch;
|
|
}
|
|
|
|
case 1: // IRET
|
|
mb->IME = 1;
|
|
mb->IME_ASK = 1;
|
|
goto generic_ret;
|
|
|
|
case 2: // JP HL
|
|
mb->PC = mb->reg.HL;
|
|
goto generic_fetch; // no cycle penalty, IDU magic
|
|
|
|
case 3: // MOV SP, HL
|
|
mb->SP = mb->reg.HL;
|
|
ncycles += 1; // cross-IDU wide bus move penalty
|
|
goto generic_fetch;
|
|
|
|
default:
|
|
__builtin_unreachable();
|
|
}
|
|
}
|
|
else // POP r16
|
|
{
|
|
var SP = mb->SP;
|
|
data_wide = mch_memory_dispatch_read(mb, SP++);
|
|
data_wide += mch_memory_dispatch_read(mb, (SP++) & 0xFFFF) << 8;
|
|
mb->SP = SP;
|
|
|
|
i_src = (IR >> 4) & 3;
|
|
if(i_src != 3)
|
|
{
|
|
mb->reg.raw16[i_src] = data_wide;
|
|
}
|
|
else
|
|
{
|
|
MB_AF_W(data_wide);
|
|
mb->FMC_MODE = MB_FMC_MODE_NONE; // overwritten manually from stack
|
|
}
|
|
|
|
ncycles += 2; // 2x LD rD.(L|H), [SP+]
|
|
goto generic_fetch;
|
|
}
|
|
}
|
|
|
|
case 2: // LD mem or JP cc, a16
|
|
{
|
|
if(IR & 0x20) // LD mem
|
|
{
|
|
if(IR & 8) // LD a16
|
|
{
|
|
data_wide = mch_memory_fetch_PC_op_2(mb);
|
|
|
|
if(IR & 0x10)
|
|
{
|
|
instr_372:
|
|
mb->reg.A = mch_memory_dispatch_read(mb, data_wide);
|
|
}
|
|
else
|
|
{
|
|
mch_memory_dispatch_write(mb, data_wide, mb->reg.A);
|
|
}
|
|
|
|
ncycles += 2 + 1; // a16 operand + memory access
|
|
}
|
|
else // LDH C
|
|
{
|
|
data_wide = mb->reg.C;
|
|
|
|
if(!(IR & 0x10))
|
|
{
|
|
DBGF("- /HW %04X <- %02X\n", data_wide + 0xFF00, mb->reg.A);
|
|
mch_memory_dispatch_write_Haddr(mb, data_wide, mb->reg.A);
|
|
}
|
|
else
|
|
{
|
|
DBGF("- /HR %04X -> ", data_wide + 0xFF00);
|
|
mb->reg.A = mch_memory_dispatch_read_Haddr(mb, data_wide);
|
|
DBGF("%02X\n", mb->reg.A);
|
|
}
|
|
|
|
ncycles += 1; // memory access
|
|
}
|
|
|
|
goto generic_fetch;
|
|
}
|
|
else // JP cc, a16
|
|
{
|
|
//TODO: optimize this if possible
|
|
// small optimization, no imm fetch if no match
|
|
|
|
if(MB_CC_CHECK)
|
|
goto generic_jp_abs;
|
|
else
|
|
{
|
|
mb->PC = (mb->PC + 2) & 0xFFFF;
|
|
ncycles += 2; // a16 fetch/"skip"
|
|
goto generic_fetch;
|
|
}
|
|
}
|
|
}
|
|
|
|
case 3: // junk
|
|
{
|
|
switch(IR_F_ROW)
|
|
{
|
|
case 0: // JP a16
|
|
generic_jp_abs:
|
|
{
|
|
data_wide = mch_memory_fetch_PC_op_2(mb);
|
|
mb->PC = data_wide;
|
|
ncycles += 2 + 1; // a16 fetch + MOV PC, Imm through IDU
|
|
goto generic_fetch;
|
|
}
|
|
|
|
case 1: // $CB
|
|
// Do not increment ncycles here,
|
|
// it's handled in the $CB opcode handler
|
|
goto handle_cb;
|
|
|
|
case 6: // DI
|
|
mb->IME = 0;
|
|
mb->IME_ASK = 0;
|
|
goto generic_fetch;
|
|
|
|
case 7: // EI
|
|
mb->IME_ASK = 1;
|
|
goto generic_fetch;
|
|
|
|
default:
|
|
return 0; //ILL
|
|
}
|
|
}
|
|
|
|
case 4: // CALL cc, a16
|
|
{
|
|
// small optimization, do not fetch imm unless match
|
|
|
|
if(!(IR & 32))
|
|
{
|
|
//TODO: optimize
|
|
|
|
if(MB_CC_CHECK)
|
|
goto generic_call;
|
|
else
|
|
{
|
|
mb->PC = (mb->PC + 2) & 0xFFFF;
|
|
ncycles += 2; // a16 fetch/"skip"
|
|
goto generic_fetch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0; //ILL
|
|
}
|
|
}
|
|
|
|
case 5: // PUSH r16 / CALL a16
|
|
{
|
|
if(!(IR & 8)) // PUSH r16
|
|
{
|
|
i_src = (IR >> 4) & 3;
|
|
if(i_src != 3)
|
|
{
|
|
data_wide = mb->reg.raw16[i_src];
|
|
}
|
|
else
|
|
{
|
|
mb->reg.F = mbh_fr_get(mb, mb->reg.F);
|
|
|
|
data_wide = MB_AF_R;
|
|
}
|
|
|
|
generic_push:
|
|
if(1)
|
|
{
|
|
var SP = mb->SP;
|
|
mch_memory_dispatch_write(mb, (--SP) & 0xFFFF, data_wide >> 8);
|
|
mch_memory_dispatch_write(mb, (--SP) & 0xFFFF, data_wide & 0xFF);
|
|
mb->SP = SP;
|
|
ncycles += 2 + 1; // SP- + [SP-] = rS.H + [SP] = rS.L
|
|
}
|
|
|
|
goto generic_fetch;
|
|
}
|
|
else
|
|
{
|
|
if(IR_row == 1) // CALL a16
|
|
{
|
|
generic_call:
|
|
{
|
|
var PC = mb->PC;
|
|
data_wide = mch_memory_fetch_PC_op_2(mb);
|
|
mb->PC = data_wide;
|
|
data_wide = (PC + 2) & 0xFFFF;
|
|
ncycles += 2; // a16 fetch
|
|
|
|
goto generic_push;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0; // ILL
|
|
}
|
|
}
|
|
}
|
|
|
|
case 6: // ALU n8
|
|
{
|
|
ncycles += 1; // n8 operand fetch
|
|
data_reg = mch_memory_fetch_PC_op_1(mb);
|
|
goto alu_op_begin;
|
|
}
|
|
|
|
case 7: // RST
|
|
{
|
|
data_wide = mb->PC;
|
|
mb->PC = IR & (7 << 3);
|
|
goto generic_push; // ncycle penalty applied at label
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//goto generic_fetch;
|
|
return 0; // WTF
|
|
|
|
generic_fetch:
|
|
mb->IR.raw = mch_memory_fetch_PC(mb);
|
|
return ncycles + 1;
|
|
|
|
generic_fetch_halt:
|
|
{
|
|
mb->IR.raw = mch_memory_fetch_PC(mb);
|
|
|
|
mb->IMM.high = 0x76;
|
|
|
|
mb->HALTING = !(mb->IE & mb->IF);
|
|
if(mb->HALTING)
|
|
{
|
|
//mb->IR.raw = 0; // NOP
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
generic_fetch_stop:
|
|
{
|
|
mb->IR.raw = mch_memory_fetch_PC(mb);
|
|
|
|
mb->IMM.high = 0x10;
|
|
|
|
mb->HALTING = 1;
|
|
if(mb->HALTING)
|
|
{
|
|
mb->IR.raw = 0; // NOP
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
handle_cb:
|
|
if(1)
|
|
{
|
|
ncycles += 1; // $CB opcode fetch
|
|
var CBIR = mch_memory_fetch_PC_op_1(mb);
|
|
|
|
#if CONFIG_DBG
|
|
if(_IS_DBG)
|
|
{
|
|
printf(" (%01o:%01o:%01o) ", CBIR >> 6, CBIR & 7, (CBIR >> 3) & 7);
|
|
mb_disasm_CB(mb, CBIR);
|
|
}
|
|
#endif
|
|
|
|
i_src = (CBIR >> 3) & 7;
|
|
i_dst = (CBIR & 7) ^ 1;
|
|
|
|
if(i_dst != 7)
|
|
data_reg = mb->reg.raw8[i_dst];
|
|
else
|
|
{
|
|
++ncycles; // memory op fetch src
|
|
data_reg = mch_memory_dispatch_read(mb, mb->reg.HL);
|
|
}
|
|
|
|
switch(CBIR >> 6)
|
|
{
|
|
case 0: // CB OP
|
|
data_flags = mb->reg.F;
|
|
data_flags &= MB_FLAG_C;
|
|
|
|
switch(i_src)
|
|
{
|
|
case 0: // RLC
|
|
data_reg = (data_reg << 1) | (data_reg >> 7);
|
|
data_flags = (data_reg >> 4) & 0x10; // Cy flag
|
|
break;
|
|
|
|
case 1: // RRC
|
|
data_reg = (data_reg << 7) | (data_reg >> 1);
|
|
data_flags = (data_reg >> 3) & 0x10; // Cy flag
|
|
break;
|
|
|
|
case 2: // RL
|
|
data_reg = (data_reg << 1) | ((data_flags >> 4) & 1);
|
|
data_flags = (data_reg >> 4) & 0x10; // Cy flag
|
|
break;
|
|
|
|
case 3: // RR
|
|
data_reg = (data_reg << 8) | (data_reg >> 1) | ((data_flags & 0x10) << 3);
|
|
data_flags = (data_reg >> 4) & 0x10; // Cy flag
|
|
break;
|
|
|
|
case 4: // LSL
|
|
data_reg = (data_reg << 1);
|
|
data_flags = (data_reg >> 4) & 0x10; // Cy flag
|
|
break;
|
|
|
|
case 5: // ASR
|
|
data_flags = (data_reg & 1) << 4; // Cy flag
|
|
data_reg = (data_reg >> 1) | (data_reg & 0x80);
|
|
break;
|
|
|
|
case 6: // SWAP
|
|
data_flags = 0;
|
|
data_reg = (data_reg >> 4) | (data_reg << 4);
|
|
break;
|
|
|
|
case 7: // LSR
|
|
data_flags = (data_reg & 1) << 4; // Cy flag
|
|
data_reg = (data_reg >> 1);
|
|
break;
|
|
}
|
|
|
|
data_reg &= 0xFF;
|
|
if(!data_reg)
|
|
data_flags += MB_FLAG_Z;
|
|
|
|
mb->reg.F = data_flags;
|
|
goto cb_writeback;
|
|
|
|
case 1: // BTT
|
|
data_flags = mb->reg.F;
|
|
|
|
if(data_reg & (1 << i_src))
|
|
data_flags = (data_flags & MB_FLAG_C) + (MB_FLAG_H);
|
|
else
|
|
data_flags = (data_flags & MB_FLAG_C) + (MB_FLAG_H | MB_FLAG_Z);
|
|
|
|
mb->reg.F = data_flags;
|
|
|
|
goto generic_fetch;
|
|
|
|
case 2: // RES
|
|
data_reg &= ~(1 << i_src);
|
|
goto cb_writeback;
|
|
|
|
case 3: // SET
|
|
data_reg |= (1 << i_src);
|
|
goto cb_writeback;
|
|
}
|
|
|
|
return 0; // WTF
|
|
|
|
cb_writeback:
|
|
|
|
if(i_dst != 7)
|
|
mb->reg.raw8[i_dst] = data_reg;
|
|
else
|
|
{
|
|
++ncycles; // memory op src writeback
|
|
mch_memory_dispatch_write(mb, mb->reg.HL, data_reg);
|
|
}
|
|
|
|
goto generic_fetch;
|
|
}
|
|
return 0; // WTF
|
|
}
|