Skip to content

Instantly share code, notes, and snippets.

@flysand7
Created December 14, 2019 09:17
Show Gist options
  • Save flysand7/4bab76962d41c4b579b4b6d7a9c25f86 to your computer and use it in GitHub Desktop.
Save flysand7/4bab76962d41c4b579b4b6d7a9c25f86 to your computer and use it in GitHub Desktop.
////
////
//// 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