Compare commits
12
Commits
67f8bcfca9
...
81fe126068
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81fe126068 | ||
|
|
27abccf863 | ||
|
|
e7e1b58d01 | ||
|
|
d33837f118 | ||
|
|
219520965c | ||
|
|
462fa09a00 | ||
|
|
be3ee22086 | ||
|
|
c9e2bf9e94 | ||
|
|
c81db1d3b5 | ||
|
|
69dc545bf0 | ||
|
|
802995b7d9 | ||
|
|
eb0f4b6a5f |
@@ -44,6 +44,18 @@ We start counting from the right as bit *#0*!
|
|||||||
d = data \\
|
d = data \\
|
||||||
n = constant
|
n = constant
|
||||||
|
|
||||||
|
**** Flag conditions
|
||||||
|
|
||||||
|
Flag conditions, sometimes referred to as ~cc~, are the state the flags need to
|
||||||
|
be in to execute the opcode. There are four possible checks[[#references][(4)]]:
|
||||||
|
|
||||||
|
| Code | Meaning | State |
|
||||||
|
|------+---------------------+-------|
|
||||||
|
| C | Carry flag is set | 1 |
|
||||||
|
| NC | Carry flag is unset | 0 |
|
||||||
|
| Z | Zero flag is set | 1 |
|
||||||
|
| NZ | Zero flag is unset | 0 |
|
||||||
|
|
||||||
*** Variables
|
*** Variables
|
||||||
|
|
||||||
| Name | Meaning |
|
| Name | Meaning |
|
||||||
@@ -59,3 +71,4 @@ n = constant
|
|||||||
1. https://gbdev.io/pandocs/Memory_Map.html
|
1. https://gbdev.io/pandocs/Memory_Map.html
|
||||||
2. https://gbdev.io/pandocs/Specifications.html
|
2. https://gbdev.io/pandocs/Specifications.html
|
||||||
3. [[https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/]["A Visual Guide to the Gameboy's Half-Carry Flag"]]. robdor.com. Retrieved 24 Aug 2022.
|
3. [[https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/]["A Visual Guide to the Gameboy's Half-Carry Flag"]]. robdor.com. Retrieved 24 Aug 2022.
|
||||||
|
4. [[http://www.devrs.com/gb/files/opcodes.html]["GameBoy Opcode Summary"]]. devrs.com. Retrieved 25 Aug 2022.
|
||||||
|
|||||||
+341
-42
@@ -67,41 +67,61 @@ void CPU::update()
|
|||||||
print("running opcode: {:#x}\n", opcode);
|
print("running opcode: {:#x}\n", opcode);
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
|
||||||
|
case 0x00: nop(); break;
|
||||||
case 0x01: ldi16(); break;
|
case 0x01: ldi16(); break;
|
||||||
case 0x02: ldr8(); break;
|
case 0x02: ldr8(); break;
|
||||||
case 0x04: inc(); break;
|
case 0x04: inc(); break;
|
||||||
|
case 0x05: dec8(); break;
|
||||||
case 0x06: ldi8(); break;
|
case 0x06: ldi8(); break;
|
||||||
|
case 0x07: ra(); break;
|
||||||
case 0x08: ldr16(); break;
|
case 0x08: ldr16(); break;
|
||||||
|
case 0x09: addr16(); break;
|
||||||
case 0x0a: ldr8(); break;
|
case 0x0a: ldr8(); break;
|
||||||
case 0x0b: decr16(); break;
|
case 0x0b: dec16(); break;
|
||||||
case 0x0c: inc(); break;
|
case 0x0c: inc(); break;
|
||||||
case 0x0d: dec8(); break;
|
case 0x0d: dec8(); break;
|
||||||
case 0x0e: ldi8(); break;
|
case 0x0e: ldi8(); break;
|
||||||
|
case 0x0f: ra(); break;
|
||||||
case 0x11: ldi16(); break;
|
case 0x11: ldi16(); break;
|
||||||
case 0x12: ldr8(); break;
|
case 0x12: ldr8(); break;
|
||||||
case 0x14: inc(); break;
|
case 0x14: inc(); break;
|
||||||
|
case 0x15: dec8(); break;
|
||||||
case 0x16: ldi8(); break;
|
case 0x16: ldi8(); break;
|
||||||
|
case 0x17: ra(); break;
|
||||||
|
case 0x18: jrs8(); break;
|
||||||
|
case 0x19: addr16(); break;
|
||||||
case 0x1a: ldr8(); break;
|
case 0x1a: ldr8(); break;
|
||||||
case 0x1b: decr16(); break;
|
case 0x1b: dec16(); break;
|
||||||
case 0x1c: inc(); break;
|
case 0x1c: inc(); break;
|
||||||
|
case 0x1d: dec8(); break;
|
||||||
case 0x1e: ldi8(); break;
|
case 0x1e: ldi8(); break;
|
||||||
case 0x20: jr8(); break;
|
case 0x1f: ra(); break;
|
||||||
|
case 0x20: jrs8(); break;
|
||||||
case 0x21: ldi16(); break;
|
case 0x21: ldi16(); break;
|
||||||
case 0x22: ldr8(); break;
|
case 0x22: ldr8(); break;
|
||||||
case 0x24: inc(); break;
|
case 0x24: inc(); break;
|
||||||
|
case 0x25: dec8(); break;
|
||||||
case 0x26: ldi8(); break;
|
case 0x26: ldi8(); break;
|
||||||
|
case 0x28: jrs8(); break;
|
||||||
|
case 0x29: addr16(); break;
|
||||||
case 0x2a: lda8(); break;
|
case 0x2a: lda8(); break;
|
||||||
case 0x2b: decr16(); break;
|
case 0x2b: dec16(); break;
|
||||||
case 0x2c: inc(); break;
|
case 0x2c: inc(); break;
|
||||||
|
case 0x2d: dec8(); break;
|
||||||
case 0x2e: ldi8(); break;
|
case 0x2e: ldi8(); break;
|
||||||
case 0x2f: misc(); break;
|
case 0x2f: misc(); break;
|
||||||
|
case 0x30: jrs8(); break;
|
||||||
case 0x31: ldi16(); break;
|
case 0x31: ldi16(); break;
|
||||||
case 0x32: ldr8(); break;
|
case 0x32: ldr8(); break;
|
||||||
case 0x34: inc(); break;
|
case 0x34: inc(); break;
|
||||||
|
case 0x35: dec8(); break;
|
||||||
case 0x36: ldi8(); break;
|
case 0x36: ldi8(); break;
|
||||||
|
case 0x38: jrs8(); break;
|
||||||
|
case 0x39: addr16(); break;
|
||||||
case 0x3a: ldr8(); break;
|
case 0x3a: ldr8(); break;
|
||||||
case 0x3b: decr16(); break;
|
case 0x3b: dec16(); break;
|
||||||
case 0x3c: inc(); break;
|
case 0x3c: inc(); break;
|
||||||
|
case 0x3d: dec8(); break;
|
||||||
case 0x3e: ldi8(); break;
|
case 0x3e: ldi8(); break;
|
||||||
case 0x40: ldr8(); break;
|
case 0x40: ldr8(); break;
|
||||||
case 0x41: ldr8(); break;
|
case 0x41: ldr8(); break;
|
||||||
@@ -160,6 +180,14 @@ void CPU::update()
|
|||||||
case 0x7d: ldr8(); break;
|
case 0x7d: ldr8(); break;
|
||||||
case 0x7e: ldr8(); break;
|
case 0x7e: ldr8(); break;
|
||||||
case 0x7f: ldr8(); break;
|
case 0x7f: ldr8(); break;
|
||||||
|
case 0xa0: and8(); break;
|
||||||
|
case 0xa1: and8(); break;
|
||||||
|
case 0xa2: and8(); break;
|
||||||
|
case 0xa3: and8(); break;
|
||||||
|
case 0xa4: and8(); break;
|
||||||
|
case 0xa5: and8(); break;
|
||||||
|
case 0xa6: and8(); break;
|
||||||
|
case 0xa7: and8(); break;
|
||||||
case 0xa8: xor8(); break;
|
case 0xa8: xor8(); break;
|
||||||
case 0xaf: xor8(); break;
|
case 0xaf: xor8(); break;
|
||||||
case 0xb8: cp(); break;
|
case 0xb8: cp(); break;
|
||||||
@@ -171,17 +199,31 @@ void CPU::update()
|
|||||||
case 0xbe: cp(); break;
|
case 0xbe: cp(); break;
|
||||||
case 0xbf: cp(); break;
|
case 0xbf: cp(); break;
|
||||||
case 0xc3: jp16(); break;
|
case 0xc3: jp16(); break;
|
||||||
case 0xc6: add(); break;
|
case 0xc4: call(); break;
|
||||||
|
case 0xc6: addi8(); break;
|
||||||
|
case 0xc7: rst(); break;
|
||||||
|
case 0xcc: call(); break;
|
||||||
case 0xcd: call(); break;
|
case 0xcd: call(); break;
|
||||||
|
case 0xcf: rst(); break;
|
||||||
|
case 0xd4: call(); break;
|
||||||
|
case 0xd7: rst(); break;
|
||||||
|
case 0xdc: call(); break;
|
||||||
|
case 0xdf: rst(); break;
|
||||||
case 0xe0: ldffi8(); break;
|
case 0xe0: ldffi8(); break;
|
||||||
case 0xe2: ldr8(); break;
|
case 0xe2: ldr8(); break;
|
||||||
|
case 0xe6: and8(); break;
|
||||||
|
case 0xe7: rst(); break;
|
||||||
|
case 0xe8: adds8(); break;
|
||||||
case 0xea: ldr8(); break;
|
case 0xea: ldr8(); break;
|
||||||
|
case 0xef: rst(); break;
|
||||||
case 0xf0: ldffi8(); break;
|
case 0xf0: ldffi8(); break;
|
||||||
case 0xf2: lda8(); break;
|
case 0xf2: lda8(); break;
|
||||||
|
case 0xf7: rst(); break;
|
||||||
case 0xf8: ldr16(); break;
|
case 0xf8: ldr16(); break;
|
||||||
case 0xf9: ldr16(); break;
|
case 0xf9: ldr16(); break;
|
||||||
case 0xfa: lda8(); break;
|
case 0xfa: lda8(); break;
|
||||||
case 0xfe: cp(); break;
|
case 0xfe: cp(); break;
|
||||||
|
case 0xff: rst(); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
print("opcode {:#x} not implemented\n", opcode);
|
print("opcode {:#x} not implemented\n", opcode);
|
||||||
@@ -193,7 +235,7 @@ void CPU::update()
|
|||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
void CPU::add()
|
void CPU::addi8()
|
||||||
{
|
{
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
uint8_t immediate = pcRead();
|
uint8_t immediate = pcRead();
|
||||||
@@ -217,24 +259,87 @@ void CPU::add()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::dec8()
|
void CPU::and8()
|
||||||
{
|
{
|
||||||
|
auto bitwise_and = [this](uint32_t byte) {
|
||||||
|
// AND r8, flags: Z 0 1 0
|
||||||
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
// Set flags
|
||||||
|
m_nf = 0;
|
||||||
|
m_hf = 1;
|
||||||
|
m_cf = 0;
|
||||||
|
|
||||||
|
// Bitwise AND between the value in r8 and A
|
||||||
|
m_a = m_a & byte;
|
||||||
|
|
||||||
|
// Zero flag
|
||||||
|
m_zf = m_a == 0;
|
||||||
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0x0d: { // DEC C, flags: Z 1 H -
|
case 0xa0: /* AND B */ bitwise_and(m_b); break;
|
||||||
|
case 0xa1: /* AND C */ bitwise_and(m_c); break;
|
||||||
|
case 0xa2: /* AND D */ bitwise_and(m_d); break;
|
||||||
|
case 0xa3: /* AND E */ bitwise_and(m_e); break;
|
||||||
|
case 0xa4: /* AND H */ bitwise_and(m_h); break;
|
||||||
|
case 0xa5: /* AND L */ bitwise_and(m_l); break;
|
||||||
|
case 0xa6: /* AND (HL) */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
|
|
||||||
|
// Bitwise AND between the byte pointed to by HL and A
|
||||||
|
bitwise_and(read(hl()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xa7: /* AND A */ bitwise_and(m_a); break;
|
||||||
|
case 0xe6: /* AND i8 */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
|
|
||||||
|
// Bitwise AND between the value in i8 and A
|
||||||
|
bitwise_and(pcRead());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::dec8()
|
||||||
|
{
|
||||||
|
auto decrement = [this](uint32_t& reg) -> void {
|
||||||
|
// DEC r8, flags: Z 1 H -
|
||||||
m_wait_cycles += 4;
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_nf = 1;
|
m_nf = 1;
|
||||||
m_hf = isCarry(m_c, -1, 0x10);
|
m_hf = isCarry(reg, -1, 0x10);
|
||||||
|
|
||||||
// C = C - 1
|
// Decrement value in register r8 by 1
|
||||||
m_c = (m_c - 1) & 0xff;
|
reg = (reg - 1) & 0xff;
|
||||||
|
|
||||||
// Zero flag
|
// Zero flag
|
||||||
m_zf = m_c == 0;
|
m_zf = reg == 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0x05: /* DEC B */ decrement(m_b); break;
|
||||||
|
case 0x0d: /* DEC C */ decrement(m_c); break;
|
||||||
|
case 0x15: /* DEC D */ decrement(m_d); break;
|
||||||
|
case 0x1d: /* DEC E */ decrement(m_e); break;
|
||||||
|
case 0x25: /* DEC H */ decrement(m_h); break;
|
||||||
|
case 0x2d: /* DEC L */ decrement(m_l); break;
|
||||||
|
case 0x35: /* DEC (HL) */ {
|
||||||
|
m_wait_cycles += 8; // + 4 = 12 total
|
||||||
|
|
||||||
|
// Decrement the byte pointed to by HL by 1
|
||||||
|
uint32_t data = read(hl());
|
||||||
|
decrement(data);
|
||||||
|
write(hl(), data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 0x3d: /* DEC A */ decrement(m_a); break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -302,7 +407,59 @@ void CPU::lda8()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::decr16()
|
void CPU::addr16()
|
||||||
|
{
|
||||||
|
auto add = [this](uint32_t reg) -> void {
|
||||||
|
// ADD HL,r16, flags: - 0 H C
|
||||||
|
m_wait_cycles += 8;
|
||||||
|
|
||||||
|
// Set flags
|
||||||
|
m_nf = 0;
|
||||||
|
m_hf = isCarry(hl(), reg, 0x1000);
|
||||||
|
m_cf = isCarry(hl(), reg, 0x10000);
|
||||||
|
|
||||||
|
// Add the value in r16 to HL
|
||||||
|
uint32_t data = (hl() + reg) & 0xffff;
|
||||||
|
m_l = data & 0xff;
|
||||||
|
m_h = data >> 8;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0x09: /* ADD HL,BC */ add(bc()); break;
|
||||||
|
case 0x19: /* ADD HL,DE */ add(de()); break;
|
||||||
|
case 0x29: /* ADD HL,HL */ add(hl()); break;
|
||||||
|
case 0x39: /* ADD HL,SP */ add(sp()); break;
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::adds8()
|
||||||
|
{
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0xe8: { // ADD SP,s8, flags: 0 0 H C
|
||||||
|
m_wait_cycles += 16;
|
||||||
|
|
||||||
|
uint32_t signed_data = (pcRead() ^ 0x80) - 0x80;
|
||||||
|
|
||||||
|
// Set flags
|
||||||
|
m_zf = 0;
|
||||||
|
m_nf = 0;
|
||||||
|
m_hf = isCarry(m_sp, signed_data, 0x10);
|
||||||
|
m_cf = isCarry(m_sp, signed_data, 0x100);
|
||||||
|
|
||||||
|
// Add the signed value s8 to SP
|
||||||
|
m_sp = m_sp + signed_data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::dec16()
|
||||||
{
|
{
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
@@ -498,6 +655,93 @@ void CPU::ldr8()
|
|||||||
m_wait_cycles += 4;
|
m_wait_cycles += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotate accumulator
|
||||||
|
void CPU::ra()
|
||||||
|
{
|
||||||
|
// Make sure we only look at the bottom 8 bits
|
||||||
|
m_a = m_a & 0xff;
|
||||||
|
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0x07: // RLCA
|
||||||
|
|
||||||
|
// Rotates A to the left with bit 7 being moved to bit 0 and also stored
|
||||||
|
// into the carry
|
||||||
|
// ┌──────────────┐
|
||||||
|
// │ ┌─────────┐ │
|
||||||
|
// C <─┴─│7 <── 0│<─┘
|
||||||
|
// └─────────┘
|
||||||
|
// A
|
||||||
|
|
||||||
|
// Copy bit 7 into carry flag
|
||||||
|
m_cf = (m_a & 0x80) == 0x80;
|
||||||
|
|
||||||
|
// Rotate register A left
|
||||||
|
m_a = (m_a << 1) | (m_a >> 7);
|
||||||
|
break;
|
||||||
|
case 0x0f: // RRCA
|
||||||
|
|
||||||
|
// Rotates A to the right with bit 0 being moved to bit 7 and also
|
||||||
|
// stored into the carry
|
||||||
|
// ┌──────────────┐
|
||||||
|
// │ ┌─────────┐ │
|
||||||
|
// └─>│7 ──> 0│─┴─> C
|
||||||
|
// └─────────┘
|
||||||
|
// A
|
||||||
|
|
||||||
|
// Copy bit 0 into carry flag
|
||||||
|
m_cf = (m_a & 0x1) == 0x1;
|
||||||
|
|
||||||
|
// Rotate register A right
|
||||||
|
m_a = (m_a << 7) | (m_a >> 1);
|
||||||
|
break;
|
||||||
|
case 0x17: { // RLA
|
||||||
|
|
||||||
|
// Rotates A to the left with the carry's value put into bit 0 and bit 7
|
||||||
|
// is put into the carry
|
||||||
|
// ┌────────────────────┐
|
||||||
|
// │ ┌─────────┐ │
|
||||||
|
// └─ C <──│7 <── 0│<─┘
|
||||||
|
// └─────────┘
|
||||||
|
// A
|
||||||
|
|
||||||
|
uint32_t old_carry = m_cf != 0;
|
||||||
|
m_cf = (m_a & 0x80) == 0x80; // Copy bit 7 into carry flag
|
||||||
|
|
||||||
|
// Rotate register A left through carry
|
||||||
|
m_a = (m_a << 1) | old_carry;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x1f: { // RRA
|
||||||
|
|
||||||
|
// Rotates A to the right with the carry's value put into bit 7 and bit 0
|
||||||
|
// is put into the carry
|
||||||
|
// ┌────────────────────┐
|
||||||
|
// │ ┌─────────┐ │
|
||||||
|
// └─>│7 ──> 0│──> C ─┘
|
||||||
|
// └─────────┘
|
||||||
|
// A
|
||||||
|
|
||||||
|
uint32_t old_carry = m_cf != 0;
|
||||||
|
m_cf = (m_a & 0x1) == 0x1; // Copy bit 0 into carry flag
|
||||||
|
|
||||||
|
// Rotate register A right through carry
|
||||||
|
m_a = (old_carry << 7) | (m_a >> 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// RLCA/RRCA/RLA/RRA, Flags: 0 0 0 C
|
||||||
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
// Set flags
|
||||||
|
m_zf = 0;
|
||||||
|
m_nf = 0;
|
||||||
|
m_hf = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void CPU::cp()
|
void CPU::cp()
|
||||||
{
|
{
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
@@ -562,21 +806,13 @@ void CPU::inc()
|
|||||||
case 0x1c: /* INC E */ increment(m_e); break;
|
case 0x1c: /* INC E */ increment(m_e); break;
|
||||||
case 0x24: /* INC H */ increment(m_h); break;
|
case 0x24: /* INC H */ increment(m_h); break;
|
||||||
case 0x2c: /* INC L */ increment(m_l); break;
|
case 0x2c: /* INC L */ increment(m_l); break;
|
||||||
case 0x34: { /* INC (HL) */
|
case 0x34: /* INC (HL) */ {
|
||||||
m_wait_cycles += 12;
|
m_wait_cycles += 8; // + 4 = 12 total
|
||||||
|
|
||||||
uint32_t data = read(hl());
|
|
||||||
|
|
||||||
// Set flags
|
|
||||||
m_nf = 0;
|
|
||||||
m_hf = isCarry(data, 1, 0x10);
|
|
||||||
|
|
||||||
// Increment the byte pointed to by HL by 1
|
// Increment the byte pointed to by HL by 1
|
||||||
data = (data + 1) & 0xff;
|
uint32_t data = read(hl());
|
||||||
|
increment(data);
|
||||||
write(hl(), data);
|
write(hl(), data);
|
||||||
|
|
||||||
// Zero flag
|
|
||||||
m_zf = data == 0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x3c: /* INC A */ increment(m_a); break;
|
case 0x3c: /* INC A */ increment(m_a); break;
|
||||||
@@ -671,13 +907,17 @@ void CPU::ldr16()
|
|||||||
|
|
||||||
void CPU::call()
|
void CPU::call()
|
||||||
{
|
{
|
||||||
uint8_t opcode = pcRead();
|
auto function_call = [this](bool should_call) -> void {
|
||||||
switch (opcode) {
|
// CALL cc,i16
|
||||||
case 0xcd: { // CALL a16
|
|
||||||
m_wait_cycles += 24;
|
|
||||||
|
|
||||||
|
// Note: the operand is read even when the condition is false
|
||||||
uint32_t data = pcRead16();
|
uint32_t data = pcRead16();
|
||||||
|
|
||||||
|
if (!should_call) {
|
||||||
|
m_wait_cycles += 12;
|
||||||
|
}
|
||||||
|
m_wait_cycles += 24;
|
||||||
|
|
||||||
// Push address of next 2 bytes in memory onto stack
|
// Push address of next 2 bytes in memory onto stack
|
||||||
m_sp = (m_sp - 1) & 0xffff;
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
write(m_sp, data >> 8);
|
write(m_sp, data >> 8);
|
||||||
@@ -686,8 +926,15 @@ void CPU::call()
|
|||||||
|
|
||||||
// Jump to this address
|
// Jump to this address
|
||||||
m_pc = data;
|
m_pc = data;
|
||||||
break;
|
};
|
||||||
}
|
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0xc4: /* CALL NZ,i16 */ function_call(!m_nf); break;
|
||||||
|
case 0xcc: /* CALL Z,i16 */ function_call(m_nf); break;
|
||||||
|
case 0xcd: /* CALL i16 */ function_call(true); break;
|
||||||
|
case 0xd4: /* CALL NC,i16 */ function_call(!m_cf); break;
|
||||||
|
case 0xdc: /* CALL C,i16 */ function_call(m_cf); break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -706,20 +953,62 @@ void CPU::jp16()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jump relative
|
void CPU::jrs8()
|
||||||
void CPU::jr8()
|
|
||||||
{
|
{
|
||||||
|
auto jump_relative = [this](bool should_jump) -> void {
|
||||||
|
// JR cc,s8
|
||||||
|
|
||||||
|
// Note: the operand is read even when the condition is false
|
||||||
|
uint32_t signed_data = (pcRead() ^ 0x80) - 0x80;
|
||||||
|
|
||||||
|
if (!should_jump) {
|
||||||
|
m_wait_cycles += 8;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_wait_cycles += 12;
|
||||||
|
|
||||||
|
// Relative jump by adding s8 to the address of the instruction following the JR
|
||||||
|
m_pc = m_pc + signed_data;
|
||||||
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0x20: { // JR NZ,s8
|
case 0x18: /* JR s8 */ jump_relative(true); break;
|
||||||
m_wait_cycles += 8;
|
case 0x20: /* JR NZ,s8 */ jump_relative(!m_zf); break;
|
||||||
|
case 0x28: /* JR Z,s8 */ jump_relative(m_zf); break;
|
||||||
if (!m_zf) {
|
case 0x30: /* JR NC,s8 */ jump_relative(!m_cf); break;
|
||||||
m_wait_cycles += 4;
|
case 0x38: /* JR C,s8 */ jump_relative(m_cf); break;
|
||||||
// TODO
|
default:
|
||||||
}
|
VERIFY_NOT_REACHED();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::rst()
|
||||||
|
{
|
||||||
|
auto function_call = [this](uint32_t fixed_address) -> void {
|
||||||
|
// RST vec
|
||||||
|
m_wait_cycles += 16;
|
||||||
|
|
||||||
|
// Push present address onto stack
|
||||||
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
|
write(m_sp, m_pc >> 8);
|
||||||
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
|
write(m_sp, m_pc & 0xff);
|
||||||
|
|
||||||
|
// Jump to this address
|
||||||
|
m_pc = fixed_address;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0xc7: /* RST 0x00 */ function_call(0x00); break;
|
||||||
|
case 0xcf: /* RST 0x08 */ function_call(0x08); break;
|
||||||
|
case 0xd7: /* RST 0x10 */ function_call(0x10); break;
|
||||||
|
case 0xdf: /* RST 0x18 */ function_call(0x18); break;
|
||||||
|
case 0xe7: /* RST 0x20 */ function_call(0x20); break;
|
||||||
|
case 0xef: /* RST 0x28 */ function_call(0x28); break;
|
||||||
|
case 0xf7: /* RST 0x30 */ function_call(0x30); break;
|
||||||
|
case 0xff: /* RST 0x38 */ function_call(0x38); break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -743,6 +1032,16 @@ void CPU::misc()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPU::nop()
|
||||||
|
{
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0x0: /* NOP */ m_wait_cycles += 4; break;
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
uint32_t CPU::pcRead()
|
uint32_t CPU::pcRead()
|
||||||
|
|||||||
@@ -28,12 +28,15 @@ public:
|
|||||||
// Arithmetic and Logic Instructions
|
// Arithmetic and Logic Instructions
|
||||||
|
|
||||||
// 8-bit
|
// 8-bit
|
||||||
void add();
|
void addi8();
|
||||||
|
void and8();
|
||||||
void dec8();
|
void dec8();
|
||||||
void xor8();
|
void xor8();
|
||||||
|
|
||||||
// 16-bit
|
// 16-bit
|
||||||
void decr16();
|
void addr16();
|
||||||
|
void adds8();
|
||||||
|
void dec16();
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Bit Operations Instructions
|
// Bit Operations Instructions
|
||||||
@@ -41,6 +44,8 @@ public:
|
|||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Bit Shift Instructions
|
// Bit Shift Instructions
|
||||||
|
|
||||||
|
void ra();
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Load Instructions
|
// Load Instructions
|
||||||
|
|
||||||
@@ -61,7 +66,8 @@ public:
|
|||||||
|
|
||||||
void call();
|
void call();
|
||||||
void jp16();
|
void jp16();
|
||||||
void jr8();
|
void jrs8();
|
||||||
|
void rst();
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
// Stack Operations Instructions
|
// Stack Operations Instructions
|
||||||
@@ -70,6 +76,7 @@ public:
|
|||||||
// Miscellaneous Instructions
|
// Miscellaneous Instructions
|
||||||
|
|
||||||
void misc();
|
void misc();
|
||||||
|
void nop();
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
|
|||||||
+21
-1
@@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
#include "cpu.h"
|
#include "cpu.h"
|
||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "ruc/format/log.h"
|
||||||
#include "ruc/format/print.h"
|
#include "ruc/format/print.h"
|
||||||
#include "ruc/meta/assert.h"
|
#include "ruc/meta/assert.h"
|
||||||
|
|
||||||
@@ -55,11 +57,19 @@ void Emu::addMemorySpace(std::string_view name, uint32_t start_address, uint32_t
|
|||||||
m_memory_spaces.emplace(name, std::move(memory_space));
|
m_memory_spaces.emplace(name, std::move(memory_space));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Emu::removeMemorySpace(std::string_view name)
|
||||||
|
{
|
||||||
|
m_memory_spaces.erase(name);
|
||||||
|
}
|
||||||
|
|
||||||
void Emu::writeMemory(uint32_t address, uint32_t value)
|
void Emu::writeMemory(uint32_t address, uint32_t value)
|
||||||
{
|
{
|
||||||
for (auto& memory_space : m_memory_spaces) {
|
for (auto& memory_space : m_memory_spaces) {
|
||||||
auto& memory = memory_space.second;
|
auto& memory = memory_space.second;
|
||||||
if (address >= memory.start_address && address <= memory.end_address) {
|
if (address == 0xff50) {
|
||||||
|
Loader::the().disableBootrom();
|
||||||
|
}
|
||||||
|
else if (address >= memory.start_address && address <= memory.end_address) {
|
||||||
// Note: ECHO RAM hack
|
// Note: ECHO RAM hack
|
||||||
if (address >= 0xc000 && address <= 0xddff) {
|
if (address >= 0xc000 && address <= 0xddff) {
|
||||||
writeMemory(address + (0xe000 - 0xc000), value);
|
writeMemory(address + (0xe000 - 0xc000), value);
|
||||||
@@ -70,6 +80,8 @@ void Emu::writeMemory(uint32_t address, uint32_t value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruc::error("writing into address '{:#06x}' which is not in a memory space!", address);
|
||||||
|
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +94,14 @@ uint32_t Emu::readMemory(uint32_t address) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When trying to access the cartridge header
|
||||||
|
if (address >= 0x100 && address <= 0x14f) {
|
||||||
|
ruc::error("no cartridge loaded!");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ruc::error("reading from address '{:#06x}' which is not in a memory space!", address);
|
||||||
|
}
|
||||||
|
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public:
|
|||||||
|
|
||||||
void addProcessingUnit(std::string_view name, ProcessingUnit* processing_unit);
|
void addProcessingUnit(std::string_view name, ProcessingUnit* processing_unit);
|
||||||
void addMemorySpace(std::string_view name, uint32_t start_address, uint32_t end_address, uint32_t amount_of_banks = 1);
|
void addMemorySpace(std::string_view name, uint32_t start_address, uint32_t end_address, uint32_t amount_of_banks = 1);
|
||||||
|
void removeMemorySpace(std::string_view name);
|
||||||
|
|
||||||
void writeMemory(uint32_t address, uint32_t value);
|
void writeMemory(uint32_t address, uint32_t value);
|
||||||
uint32_t readMemory(uint32_t address) const;
|
uint32_t readMemory(uint32_t address) const;
|
||||||
@@ -43,6 +44,7 @@ public:
|
|||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
ProcessingUnit* processingUnit(std::string_view name) const { return m_processing_units.at(name); }
|
ProcessingUnit* processingUnit(std::string_view name) const { return m_processing_units.at(name); }
|
||||||
|
MemorySpace memorySpace(std::string_view name) { return m_memory_spaces[name]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_frequency { 0 };
|
uint32_t m_frequency { 0 };
|
||||||
|
|||||||
+40
-4
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint32_t
|
||||||
|
|
||||||
#include "cpu.h"
|
#include "cpu.h"
|
||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
@@ -22,6 +23,19 @@ void Loader::loadRom(std::string_view rom_path)
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Loader::disableBootrom()
|
||||||
|
{
|
||||||
|
Emu::the().removeMemorySpace("BOOTROM1");
|
||||||
|
Emu::the().removeMemorySpace("CARTHEADER");
|
||||||
|
Emu::the().removeMemorySpace("BOOTROM2");
|
||||||
|
|
||||||
|
// Load cartridge bank 0
|
||||||
|
Emu::the().addMemorySpace("CARTROM1", 0x0000, 0x3fff); // 16KiB
|
||||||
|
for (size_t i = 0x0000; i <= 0x3fff; ++i) {
|
||||||
|
Emu::the().writeMemory(i, m_rom_data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
void Loader::init()
|
void Loader::init()
|
||||||
@@ -40,9 +54,10 @@ void Loader::init()
|
|||||||
// https://gbdev.io/pandocs/Power_Up_Sequence.html
|
// https://gbdev.io/pandocs/Power_Up_Sequence.html
|
||||||
Emu::the().addMemorySpace("BOOTROM1", 0x0000, 0x00ff); // 256B
|
Emu::the().addMemorySpace("BOOTROM1", 0x0000, 0x00ff); // 256B
|
||||||
loadCartridgeHeader();
|
loadCartridgeHeader();
|
||||||
Emu::the().addMemorySpace("BOOTROM2", 0x0200, 0x08ff); // 1792B
|
Emu::the().addMemorySpace("BOOTROM2", 0x0200, 0x08ff); // 1792B
|
||||||
|
loadCartridgeBanks();
|
||||||
Emu::the().addMemorySpace("VRAM", 0x8000, 0x9fff, 2); // 8KiB * 2 banks
|
Emu::the().addMemorySpace("VRAM", 0x8000, 0x9fff, 2); // 8KiB * 2 banks
|
||||||
Emu::the().addMemorySpace("CARDRAM", 0xa000, 0xbfff, 1); // 8KiB * ? banks, if any
|
Emu::the().addMemorySpace("CARTRAM", 0xa000, 0xbfff, 1); // 8KiB * ? banks, if any
|
||||||
Emu::the().addMemorySpace("WRAM1", 0xc000, 0xcfff); // 4 KiB, Work RAM
|
Emu::the().addMemorySpace("WRAM1", 0xc000, 0xcfff); // 4 KiB, Work RAM
|
||||||
Emu::the().addMemorySpace("WRAM2", 0xd000, 0xdfff, 7); // 4 KiB * 7 banks, Work RAM
|
Emu::the().addMemorySpace("WRAM2", 0xd000, 0xdfff, 7); // 4 KiB * 7 banks, Work RAM
|
||||||
Emu::the().addMemorySpace("ECHORAM", 0xe000, 0xfdff); // 7680B, Mirror of 0xc000~0xddff
|
Emu::the().addMemorySpace("ECHORAM", 0xe000, 0xfdff); // 7680B, Mirror of 0xc000~0xddff
|
||||||
@@ -84,9 +99,30 @@ void Loader::loadCartridgeHeader()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Emu::the().addMemorySpace("CARTHEADER", 0x100, 0x14f);
|
Emu::the().addMemorySpace("CARTHEADER", 0x100, 0x14f); // 80B
|
||||||
|
|
||||||
for (size_t i = 0x100; i <= 0x14f; ++i) {
|
for (size_t i = 0x0100; i <= 0x014f; ++i) {
|
||||||
Emu::the().writeMemory(i, m_rom_data[i]);
|
Emu::the().writeMemory(i, m_rom_data[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Loader::loadCartridgeBanks()
|
||||||
|
{
|
||||||
|
if (m_rom_data.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rom_size = 32 * 1024 * (1 << m_rom_data[0x0148]);
|
||||||
|
uint32_t rom_banks = rom_size / (16 * 1024) - 1;
|
||||||
|
Emu::the().addMemorySpace("CARTROM2", 0x4000, 0x7fff, rom_banks); // 16KiB * banks
|
||||||
|
|
||||||
|
// Load cartridge bank 1~NN
|
||||||
|
auto rom_memory_spaces = Emu::the().memorySpace("CARTROM2");
|
||||||
|
for (size_t i = 0; i < rom_banks; ++i) {
|
||||||
|
for (size_t i = 0x4000; i <= 0x7fff; ++i) {
|
||||||
|
Emu::the().writeMemory(i, m_rom_data[i]);
|
||||||
|
}
|
||||||
|
rom_memory_spaces.active_bank += 1;
|
||||||
|
}
|
||||||
|
rom_memory_spaces.active_bank = 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public:
|
|||||||
Loader(s) {}
|
Loader(s) {}
|
||||||
|
|
||||||
void loadRom(std::string_view rom_path);
|
void loadRom(std::string_view rom_path);
|
||||||
|
void disableBootrom();
|
||||||
|
|
||||||
void setBootromPath(std::string_view bootrom_path) { m_bootrom_path = bootrom_path; }
|
void setBootromPath(std::string_view bootrom_path) { m_bootrom_path = bootrom_path; }
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ private:
|
|||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
void loadCartridgeHeader();
|
void loadCartridgeHeader();
|
||||||
|
void loadCartridgeBanks();
|
||||||
|
|
||||||
std::string_view m_bootrom_path;
|
std::string_view m_bootrom_path;
|
||||||
std::string m_rom_data;
|
std::string m_rom_data;
|
||||||
|
|||||||
Reference in New Issue
Block a user