-
-
Save flysand7/4bab76962d41c4b579b4b6d7a9c25f86 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//// | |
//// | |
//// GB.H | |
//// | |
//// | |
// NOTE(bumboni): GameBoy technical data | |
// | |
// CPU: LR25902 | |
// Clock speed: 4.194304MHz | |
// Work RAM: 8Kb | |
// Video RAM: 8Kb | |
// Resolution: 160x144 | |
// Sprites: <40 per screen, <10 per line | |
// Palett: 1x4 - BG, 2x3 - OBJ | |
// Sprite Size: 8x8 or 8x16 | |
// Colors: 4 grayshades | |
// Sound: 4 Ch. / Stereo | |
// | |
int8_t typedef s8; | |
int16_t typedef s16; | |
int32_t typedef s32; | |
int64_t typedef s64; | |
uint8_t typedef u8; | |
uint16_t typedef u16; | |
uint32_t typedef u32; | |
uint64_t typedef u64; | |
int32_t typedef b32; | |
float typedef r32; | |
double typedef r64; | |
unsigned char typedef byte; | |
u16 typedef word; | |
enum | |
{ | |
false, | |
true, | |
} bool; | |
typedef struct | |
{ | |
u32 EntryPoint; | |
byte NintendoLogo[48]; | |
char Title[16]; | |
byte NewLicenseCode[2]; | |
byte SGBFlag[2]; | |
byte CartridgeType; | |
byte ROMSize; | |
byte RAMSize; | |
byte DestCode; | |
byte OldLicenseCode; | |
byte MaskROMVersionNumber; | |
byte HeaderChecksum; | |
byte GlobalChecksum[2]; | |
} gb_cartridge_header; | |
typedef struct | |
{ | |
union | |
{ | |
struct | |
{ | |
union | |
{ | |
u16 AF; | |
struct | |
{ | |
u8 A; | |
u8 _F; | |
}; | |
}; | |
union | |
{ | |
u16 BC; | |
struct | |
{ | |
u8 B; | |
u8 C; | |
}; | |
}; | |
union | |
{ | |
u16 DE; | |
struct | |
{ | |
u8 D; | |
u8 E; | |
}; | |
}; | |
union | |
{ | |
u16 HL; | |
struct | |
{ | |
u8 H; | |
u8 L; | |
}; | |
}; | |
u16 SP; | |
}; | |
u16 Reg16[5]; | |
u8 Reg8[8]; | |
}; | |
byte* ExcecutionPointer; | |
} gb_cpu_t; | |
typedef enum | |
{ | |
REG8_A = 0, | |
REG8_B = 2, | |
REG8_C = 3, | |
REG8_D = 4, | |
REG8_E = 5, | |
REG8_H = 6, | |
REG8_L = 7, | |
} gb_8reg_id; | |
#define MIN(a,b) (a<b?a:b) | |
#define ISSET(value, bit) ((value>>bit) & 1) | |
#define ASSERT(e) {if(!(e)) {*((int*)0) = 0;}} | |
#define CPU_CLOCK_FREQUENCY_HZ (1<<22) | |
//// | |
//// | |
//// GB_CPU.H | |
//// | |
//// | |
typedef enum | |
{ | |
flag_zero = (1<<7), | |
flag_sub = (1<<6), | |
flag_half_carry = (1<<5), | |
flag_carry = (1<<4), | |
} flag_t; | |
// TODO(bumboni): gOD FORGIVE ME FOR USING MULTIPLAE | |
#define FlagValue(mask, obj, value) (obj = ((obj & ~mask) | (mask * value))) | |
#define FlagMask(mask, obj, flags) (obj = ((obj & ~mask) | (flags & mask))) | |
#define FlagGet(flag, value) ((value>>flag)&1) | |
(byte[8]) typedef op_byte; | |
static op_byte | |
ByteToOp(byte Value) | |
{ | |
op_byte Result = {0}; | |
for(int Bit = 0; | |
Bit < 8; | |
++ Bit) | |
{ | |
Result[Bit] = (Result << Bit); | |
} | |
return(Result); | |
} | |
static byte | |
OpToByte(op_byte OpByte) | |
{ | |
byte Result = 0; | |
for(int Bit = 0; | |
Bit < 8; | |
++ Bit) | |
{ | |
Result |= (OpByte[Bit] << Bit); | |
} | |
return(Result); | |
} | |
struct | |
{ | |
byte Byte; | |
byte Flags; | |
} typedef reg8_op_result; | |
static reg8_op_result | |
AddByte2(byte ByteA, byte ByteB) | |
{ | |
reg_op_result Result = {0}; | |
op_byte ResultOp; | |
op_byte A = ByteToOp(ByteA); | |
op_byte B = ByteToOp(ByteB); | |
u4 Carry = 0; | |
FlagValue(flag_sub, ResultFlags, false); | |
for(int Bit = 0; | |
Bit < 7; | |
++ Bit) | |
{ | |
ResultOp[Bit] = A[Bit] ^ B[Bit] ^ Carry; | |
Carry = ((A[Bit] ^ B[Bit]) & Carry) | (A[Bit] & B[Bit]); | |
if(Bit == 3) | |
{ | |
FlagValue(flag_half_carry, Result.Flags, Carry); | |
} | |
if(Bit == 7) | |
{ | |
FlagValue(flag_carry, Result.Flags, Carry); | |
} | |
} | |
byte ResultByte = OpToByte(ResultOp); | |
Result.Byte = ResultByte; | |
FlagValue(flag_zero, Result.Flags, ResultByte == 0); | |
} | |
static reg8_op_result | |
SubByte2(byte ByteA, byte ByteB) | |
{ | |
reg8_op_result Result; | |
op_byte ResultOp = {}; | |
op_byte A = ByteToOp(ByteA); | |
op_byte B = ByteToOp(ByteB); | |
u4 Borrow = 0; | |
FlagValue(flag_sub, Result.Flags, true); | |
for(int Bit = 0; | |
Bit < 8; | |
++ Bit) | |
{ | |
ResultOp[Bit] = A[Bit] ^ B[Bit] ^ Borrow; | |
Borrow = (A[Bit] < (B[Bit] + Borrow)); | |
if(Bit == 3) | |
{ | |
FlagValue(flag_half_carry, Result.Flags, Borrow); | |
} | |
if(Bit == 7) | |
{ | |
FlagValue(flag_carry, Result.Flags, Borrow); | |
} | |
} | |
byte ResultByte = OpToByte(ResultOp); | |
Result.Byte = ResultOp; | |
FlagValue(flag_zero, Result.Flags, ResultByte); | |
return(Result); | |
} | |
(byte[16]) typedef op_word; | |
struct | |
{ | |
word Result; | |
byte Flags; | |
} typedef reg16_op_result; | |
static reg16_op_result | |
WordToOp(word Word) | |
{ | |
reg16_op_result Result = {0}; | |
for(int Bit = 0; | |
Bit < 16; | |
++ Bit) | |
{ | |
Result[Bit] = 1&(Word>>Bit); | |
} | |
return(Result); | |
} | |
static word | |
OpToWord(op_reg16 Op) | |
{ | |
word Result = 0; | |
for(int Bit = 0; | |
Bit < 16; | |
++ Bit) | |
{ | |
Result |= << (Op[Bit] << Bit); | |
} | |
return(Result); | |
} | |
static reg16_op_result | |
Add16Reg2(word WordA, word WordB) | |
{ | |
reg16_op_result Result = {0}; | |
op_word A = WordToOp(WordA); | |
op_word B = WordToOp(WOrdB); | |
op_word ResultOp = {0}; | |
u4 Carry = 0; | |
for(int Bit = 0; | |
Bit < 16; | |
++Bit) | |
{ | |
ResultOp[Bit] = A[Bit] ^ B[Bit] ^ Carry; | |
Carry = (A[Bit] & B[Bit]) | ((A[Bit] ^ B[Bit]) & Carry); | |
if(Bit == 11) | |
{ | |
FlagValue(flag_half_carry, Result.Flags, Carry); | |
} | |
if(Bit == 15) | |
{ | |
FlagValue(flag_carry, Result.Flags, Carry); | |
} | |
} | |
word ResultWord = OpToWord(ResultOp); | |
FlagValue(flag_sub, Result.Flags, false); | |
FlagValue(flag_zero, Result.Flags, ResultWord == 0); | |
Result.Word = ResultWord; | |
} | |
static reg16_op_result | |
Sub16Reg2(word WordA, word WordB) | |
{ | |
reg16_op_result Result = {0}; | |
op_word A = WordToOp(WordA); | |
op_word B = WordToOp(WordB); | |
op_word ResultOp = {0}; | |
u4 Borrow = 0; | |
for(int Bit = 0; | |
Bit < 16; | |
++ Bit) | |
{ | |
ResultOp[Bit] = A[Bit]^B[Bit]^Carry; | |
Borrow = (A[Bit] < (B[Bit] + Carry)); | |
if(Bit == 11) | |
{ | |
FlagValue(flag_half_carry, Result.Flags, Borrow); | |
} | |
if(Bit == 15) | |
{ | |
FlagValue(flag_carry, Result.Flags, Borrow); | |
} | |
} | |
word ResultWord = OpToWord(ResultOp); | |
FlagValue(flag_sub, Result.Flags, true); | |
FlagValue(flag_zero, Result.Flags, ResultWord == 0); | |
Result.Word = ResultWord; | |
} | |
// TODO(bumboni): Directly emulate LR25902 | |
// calling Intel x86 instructions and | |
// retrieving the flags. | |
// NOTE(bumboni): This is what you call to | |
// emulate instructions. Assuming the excecution | |
// pointer is set externally. | |
static int | |
CPUStart(byte* Memory, gb_cpu_state_t* CPU) | |
{ | |
for(;;) | |
{ | |
byte OP = *CPU->ExcecutionPointer++; | |
switch(OP) | |
{ | |
// NOTE(bumboni): NOP (1,4) | |
case 0x00: | |
{ | |
} break; | |
// NOTE(bumboni): LD BC,d16 (3,12) | |
case 0x01: { | |
CPU->BC = *((word*)CPU->ExcecutionPointer)++; | |
} break; | |
// NOTE(bumboni): LD (BC),A (1,8) | |
case 0x02: { | |
*(Memory + *((byte*)CPU->BC)) = CPU->A; | |
} break; | |
// NOTE(bumboni): INC BC (1, 8) | |
case 0x03: { ++CPU->BC; } break; | |
// NOTE(bumboni): INC B (1, 4) (Z 0 H -) | |
case 0x04: { | |
reg8_op_result Result = Add8RegByte(CPU->B, 1); | |
CPU->B = Result.Byte; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): DEC B (1, 4) (Z 1 H -) | |
case 0x05:{ | |
reg8_op_result Result = Sub8RegByte(CPU->B, 1); | |
CPU->B = Result.Byte; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): LD B,d8 (2, 8) | |
case 0x06: { CPU->B = *CPU->ExcecutionPointer++; } break; | |
// NOTE(bumboni): RLCA (1, 4) (- - - C) | |
case 0x07: | |
{ | |
b4 Bit7 = ISSET(CPU->A, 7); | |
CPU->A <<= 1; | |
CPU->A |= FlagGet(flag_carry, CPU->_F); | |
FlagValue(flag_carry, CPU->_F, Bit7); | |
} break; | |
// NOTE(bumboni): LD (a16),SP (3, 20) | |
case 0x08:{ | |
*((word*)(Memory + *((word*)CPU->ExcecutionPointer)++)) = CPU->SP; | |
} break; | |
// NOTE(bumboni): ADD HL,BC (1, 8) (- 0 H C) | |
case 0x09: | |
{ | |
reg16_op_result Result = Add16Reg2(CPU->HL, CPU->BC); | |
CPU->HL = Result.Word; | |
FlagMask(flag_sub|flag_half_carry|flag_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): LD A,(BC) (1, 8) | |
case 0x0a: | |
{ | |
CPU->A = *(Memory + CPU->BC); | |
} break; | |
// NOTE(bumboni): DEC BC (1, 8) | |
case 0x0b: | |
{ | |
--CPU->BC; | |
} break; | |
// NOTE(bumboni): INC C (1, 4) (Z 0 H -) | |
case 0x0c: | |
{ | |
reg8_op_result Result = Add8RegByte(CPU->C, 1); | |
CPU->C = Result.Byte; | |
FlagValue(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): DEC C (1, 4) (Z 1 H -) | |
case 0x0d: | |
{ | |
reg8_op_result Result = Sub8RegByte(CPU->C, 1); | |
CPU->C = Result.Byte; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): LD C,d8 (2, 8) | |
case 0x0e: { CPU->C = *CPU->ExcecutionPointer++; } break; | |
// NOTE(bumboni): RRCA (1, 4) (- - - C) | |
case 0x0f: | |
{ | |
Bit0 = ISSET(CPU->C, 0); | |
CPU->C >>= 1; | |
CPU->C |= (Bit0 << 7); | |
FlagValue(flag_carry, CPU->_F, Bit0); | |
} break; | |
// NOTE(bumboni): STOP 0 (2, 4) | |
// NOTE(bumboni): VERY LOW power state. | |
// TODO(bumboni): wut shud i do here? | |
case 0x10: | |
{ | |
} break; | |
// NOTE(bumboni): LD DE,d16 (3, 12) | |
case 0x11: { CPU->DE = *(word*)CPU->ExcecutionPointer++; } break; | |
// NOTE(bumboni): LD (DE),A (1, 8) | |
case 0x12: { *(word*)CPU->DE = CPU->A; } break; | |
// NOTE(bumboni): INC DE (1,8) | |
case 0x13: { ++CPU->DE; } break; | |
// NOTE(bumboni): INC D (1, 4) (Z 0 H -) | |
case 0x14: { | |
reg8_op_result Result = Add8Reg2(CPU->D, 1); | |
CPU->D = Result.Byte; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): DEC D (1, 4) (Z 1 H -) | |
case 0x15: | |
{ | |
reg8_op_result Result = Sub8Reg2(CPU->D, 1); | |
CPU->D = Result.Byte; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): LD D,d8 (2, 8) | |
case 0x16: { CPU->D = *CPU->ExcecutionPointer++; } break; | |
// NOTE(bumboni): RLA (1, 4) (0 0 0 C) | |
case 0x17: | |
{ | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, 0); | |
byte CarryFlagValue = FlagGet(flag_carry, CPU->_F); | |
FlagValue(flag_carry, ISSET(CPU->A, 7)); | |
CPU->A <<= 1; | |
CPU->A |= CarryFlagValue; | |
} | |
// NOTE(bumboni): JR r8 (2, 12) | |
case 0x18: | |
{ | |
char JumpOffset = *CPU->ExcecutionPointer++; | |
// NOTE(bumboni): So far we have incremented twice. | |
// Once for command parse and once for arg parse. | |
// So these two bytes have to be not considered. | |
*CPU->ExcecutionPointer += (JumpOffset - 2); | |
} break; | |
// NOTE(bumboni): ADD HL, DE (1, 8) (- 0 H C) | |
case 0x19: | |
{ | |
reg16_op_result Result = Add16Reg2(CPU->HL, CPU->DE); | |
CPU->HL = Result.Word; | |
FlagMask(flag_sub|flag_half_carry|flag_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): LD A,(DE) (1, 8) | |
case 0x1a: { CPU->A = *(word*)(Memory + CPU->DE); } break; | |
// NOTE(bumboni): DEC DE (1, 8) | |
case 0x1b: { --CPU->DE; } break; | |
// NOTE(bumboni): INC E (1, 4) (Z 0 H -) | |
case 0x1c: | |
{ | |
reg8_op_result Result = Add8Reg2(CPU->E, 1); | |
CPU->E = Result.Byte; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): DEC E (1, 4) (Z 1 H -) | |
case 0x1d: | |
{ | |
reg8_op_result Result = Sub8Reg2(CPU->E, 1); | |
CPU->E = Result.Byte; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): LD E,d8 (2, 8) | |
case 0x1e: { CPU->E = *CPU->ExcecutionPointer++; } break; | |
// NOTE(bumboni): RRA (1 4) (0 0 0 C) | |
case 0x1f: | |
{ | |
byte CarryValue = GetCarryFlag(); | |
SetCarryFlag(ISSET(CPU->A, 0)); | |
CPU->A >>= 1; | |
CPU->A |= (CarryValue << 7); | |
} break; | |
// NOTE(bumboni): JR NZ,r8 (2 12/8) | |
case 0x20: | |
{ | |
char JumpOffset = *CPU->ExceuctionPointer++ - 2; | |
if(GetZeroFlag() == 0) | |
{ | |
CPU->ExcecutionPointer += JumpOffset; | |
} | |
} break; | |
// NOTE(bumboni): LD HL,d16 (3 12) | |
case 0x21: { CPU->HL = *(word*)CPU->ExcecutionPointer++; } break; | |
// NOTE(bumboni): LD (HL+),A (1 8) | |
case 0x22: { *Address = (word*)CPU->HL++; } break; | |
// NOTE(bumboni): INC HL (1 8) | |
case 0x23: { ++ CPU->HL; } break; | |
// NOTE(bumboni): INC H (1 4) (Z 0 H -) | |
case 0x24: | |
{ | |
reg8_op_result Result = Add8Reg2(CPU->H, 1); | |
CPU->H = Result.Byte; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): DEC H (1 4) (Z 1 H -) | |
case 0x25: | |
{ | |
reg8_op_result Result = Sub8Reg2(CPU->H, 1); | |
CPU->H = Result; | |
FlagMask(flag_zero|flag_sub|flag_half_carry, CPU->_F, Result.Flags); | |
} | |
// NOTE(bumboni): LD H,d8 (2, 8) | |
case 0x26: { CPU->H = *CPU->ExcecutionPointer++; } break; | |
// NOTE(bumboni): DAA (1 4) (Z - 0 C) | |
case 0x27: | |
{ | |
// TODO(bumboni): implement | |
} break; | |
// NOTE(bumboni): JR Z,r8 (2 12/8) | |
case 0x28: | |
{ | |
byte JumpOffset = *CPU->ExcecutionPointer++ - 2; | |
if(GetZeroFlag() == 1) | |
{ | |
CPU->ExcecutionPointer += JumpOffset; | |
} | |
} break; | |
// NOTE(bumboni): ADD HL HL (1 8) (- 0 H C) | |
case 0x29: | |
{ | |
reg16_op_result Result = Add16Reg2(CPU->HL, CPU->HL); | |
CPU->HL = Result.Word; | |
FlagMask(flag_sub|flag_half_carry|flag_carry, CPU->_F, Result.Flags); | |
} break; | |
// NOTE(bumboni): LD A,(HL+) (1, 8) | |
case 0x2a: | |
{ | |
CPU->A = ((byte*)CPU->HL)++; | |
} break; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment