Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
009938b126 | ||
|
|
50e1672524 | ||
|
|
1b713f8ab0 | ||
|
|
3fcc6823e8 | ||
|
|
efe798084f | ||
|
|
c630a7aab6 | ||
|
|
8cc8f711e9 | ||
|
|
4220c4fcd2 | ||
|
|
9f12eaa5ff | ||
|
|
4c24ade250 | ||
|
|
1cd6c428e3 | ||
|
|
002c992e0d | ||
|
|
cc120891ca | ||
|
|
1f25f875e6 |
+9
-6
@@ -19,10 +19,12 @@ GameBoy terminology.
|
||||
|
||||
*** Flags
|
||||
|
||||
The Half Cary (H) flag differs between 8-bit or 16-bit operations. The 8-bit
|
||||
variant indicates carry for the lower 4 bits of the result, or overflow from bit
|
||||
4 to bit 5. The 16-bit variant indicates carry for the lower 4 bits of the
|
||||
*second* byte of the result, or overflow from bit 12 to bit 13.
|
||||
The half-carry flag ~H~ is set when an arithmetic operation on two numbers
|
||||
causes a carry from bit #3 to bit #4 for 8-bit operations or a carry from bit
|
||||
#11 to bit #12 for 16-bit operations. For 16-bit operation this indicates the
|
||||
lower 4 bits of the *second* byte[[#references][(3)]].
|
||||
|
||||
We start counting from the right as bit *#0*!
|
||||
|
||||
*** Opcode
|
||||
|
||||
@@ -54,5 +56,6 @@ n = constant
|
||||
|
||||
** References
|
||||
|
||||
- https://gbdev.io/pandocs/Memory_Map.html
|
||||
- https://gbdev.io/pandocs/Specifications.html
|
||||
1. https://gbdev.io/pandocs/Memory_Map.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.
|
||||
|
||||
+363
-184
@@ -5,7 +5,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdint> // int32_t, uint8_t, uint32_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
|
||||
#include "cpu.h"
|
||||
#include "emu.h"
|
||||
@@ -32,6 +32,8 @@ CPU::CPU(uint32_t frequency)
|
||||
, m_hf(0x0)
|
||||
, m_cf(0x0)
|
||||
{
|
||||
// FIXME: Figure out if other ProcessingUnits require access to these registers,
|
||||
// delete this functionality if they dont
|
||||
m_shared_registers.emplace("a", &m_a);
|
||||
m_shared_registers.emplace("b", &m_b);
|
||||
m_shared_registers.emplace("c", &m_c);
|
||||
@@ -57,47 +59,112 @@ void CPU::update()
|
||||
{
|
||||
m_wait_cycles--;
|
||||
if (m_wait_cycles <= 0) {
|
||||
|
||||
// print(ruc::format::Emphasis::Underline | ruc::format::Emphasis::Bold | fg(ruc::format::TerminalColor::Blue), "{:#06x}\n", *this);
|
||||
|
||||
// Read next opcode
|
||||
uint8_t opcode = read(m_pc);
|
||||
print("running opcode: {:#x}\n", opcode);
|
||||
switch (opcode) {
|
||||
|
||||
case 0x20: jr8(); break;
|
||||
case 0x01: ld16(); break;
|
||||
case 0x02: ld8(); break;
|
||||
case 0x06: ld8(); break;
|
||||
case 0x08: ld16(); break;
|
||||
case 0x0a: ld8(); break;
|
||||
case 0x01: ldi16(); break;
|
||||
case 0x02: ldr8(); break;
|
||||
case 0x06: ldi8(); break;
|
||||
case 0x08: ldr16(); break;
|
||||
case 0x0a: ldr8(); break;
|
||||
case 0x0d: dec8(); break;
|
||||
case 0x0e: ld8(); break;
|
||||
case 0x11: ld16(); break;
|
||||
case 0x12: ld8(); break;
|
||||
case 0x16: ld8(); break;
|
||||
case 0x1a: ld8(); break;
|
||||
case 0x1e: ld8(); break;
|
||||
case 0x21: ld16(); break;
|
||||
case 0x22: ld8(); break;
|
||||
case 0x26: ld8(); break;
|
||||
case 0x2a: ld8(); break;
|
||||
case 0x2e: ld8(); break;
|
||||
case 0x0e: ldi8(); break;
|
||||
case 0x11: ldi16(); break;
|
||||
case 0x12: ldr8(); break;
|
||||
case 0x16: ldi8(); break;
|
||||
case 0x1a: ldr8(); break;
|
||||
case 0x1e: ldi8(); break;
|
||||
case 0x20: jr8(); break;
|
||||
case 0x21: ldi16(); break;
|
||||
case 0x22: ldr8(); break;
|
||||
case 0x26: ldi8(); break;
|
||||
case 0x2a: ldr8(); break;
|
||||
case 0x2e: ldi8(); break;
|
||||
case 0x2f: misc(); break;
|
||||
case 0x31: ld16(); break;
|
||||
case 0x32: ld8(); break;
|
||||
case 0x36: ld8(); break;
|
||||
case 0x3a: ld8(); break;
|
||||
case 0x3e: ld8(); break;
|
||||
case 0x31: ldi16(); break;
|
||||
case 0x32: ldr8(); break;
|
||||
case 0x36: ldi8(); break;
|
||||
case 0x3a: ldr8(); break;
|
||||
case 0x3e: ldi8(); break;
|
||||
case 0x40: ldr8(); break;
|
||||
case 0x41: ldr8(); break;
|
||||
case 0x42: ldr8(); break;
|
||||
case 0x43: ldr8(); break;
|
||||
case 0x44: ldr8(); break;
|
||||
case 0x45: ldr8(); break;
|
||||
case 0x47: ldr8(); break;
|
||||
case 0x48: ldr8(); break;
|
||||
case 0x49: ldr8(); break;
|
||||
case 0x4a: ldr8(); break;
|
||||
case 0x4b: ldr8(); break;
|
||||
case 0x4c: ldr8(); break;
|
||||
case 0x4d: ldr8(); break;
|
||||
case 0x4f: ldr8(); break;
|
||||
case 0x50: ldr8(); break;
|
||||
case 0x51: ldr8(); break;
|
||||
case 0x52: ldr8(); break;
|
||||
case 0x53: ldr8(); break;
|
||||
case 0x54: ldr8(); break;
|
||||
case 0x55: ldr8(); break;
|
||||
case 0x57: ldr8(); break;
|
||||
case 0x58: ldr8(); break;
|
||||
case 0x59: ldr8(); break;
|
||||
case 0x5a: ldr8(); break;
|
||||
case 0x5b: ldr8(); break;
|
||||
case 0x5c: ldr8(); break;
|
||||
case 0x5d: ldr8(); break;
|
||||
case 0x5f: ldr8(); break;
|
||||
case 0x60: ldr8(); break;
|
||||
case 0x61: ldr8(); break;
|
||||
case 0x62: ldr8(); break;
|
||||
case 0x63: ldr8(); break;
|
||||
case 0x64: ldr8(); break;
|
||||
case 0x65: ldr8(); break;
|
||||
case 0x67: ldr8(); break;
|
||||
case 0x68: ldr8(); break;
|
||||
case 0x69: ldr8(); break;
|
||||
case 0x6a: ldr8(); break;
|
||||
case 0x6b: ldr8(); break;
|
||||
case 0x6c: ldr8(); break;
|
||||
case 0x6d: ldr8(); break;
|
||||
case 0x6f: ldr8(); break;
|
||||
case 0x70: ldr8(); break;
|
||||
case 0x71: ldr8(); break;
|
||||
case 0x72: ldr8(); break;
|
||||
case 0x73: ldr8(); break;
|
||||
case 0x74: ldr8(); break;
|
||||
case 0x75: ldr8(); break;
|
||||
case 0x77: ldr8(); break;
|
||||
case 0x78: ldr8(); break;
|
||||
case 0x79: ldr8(); break;
|
||||
case 0x7a: ldr8(); break;
|
||||
case 0x7b: ldr8(); break;
|
||||
case 0x7c: ldr8(); break;
|
||||
case 0x7d: ldr8(); break;
|
||||
case 0x7e: ldr8(); break;
|
||||
case 0x7f: ldr8(); break;
|
||||
case 0xa8: xor8(); break;
|
||||
case 0xaf: xor8(); break;
|
||||
case 0xc3: jp16(); break;
|
||||
case 0xc6: add(); break;
|
||||
case 0xcd: call(); break;
|
||||
case 0xe0: ldh8(); break;
|
||||
case 0xf0: ldh8(); break;
|
||||
case 0xf8: ld16(); break;
|
||||
case 0xf9: ld16(); break;
|
||||
case 0xe0: ldffi8(); break;
|
||||
case 0xe2: ldr8(); break;
|
||||
case 0xea: ldr8(); break;
|
||||
case 0xf0: ldffi8(); break;
|
||||
case 0xf2: lda8(); break;
|
||||
case 0xf8: ldr16(); break;
|
||||
case 0xf9: ldr16(); break;
|
||||
case 0xfa: lda8(); break;
|
||||
|
||||
default:
|
||||
print("opcode {:#x} not implemented\n", opcode);
|
||||
print("pc: {:#x}, immediate: {:#x}\n", m_pc, pcRead());
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
@@ -108,18 +175,19 @@ void CPU::add()
|
||||
uint8_t opcode = pcRead();
|
||||
uint8_t immediate = pcRead();
|
||||
switch (opcode) {
|
||||
case 0xc6:
|
||||
// ADD A,d8, flags: Z 0 H C
|
||||
case 0xc6: // ADD A,i8, flags: Z 0 H C
|
||||
m_wait_cycles += 8;
|
||||
|
||||
// Set flags
|
||||
m_zf = m_a + immediate == 0;
|
||||
m_nf = 0;
|
||||
m_hf = m_a + immediate > 16;
|
||||
m_cf = m_a + immediate > 255;
|
||||
m_hf = isCarry(m_a, immediate, 0x10);
|
||||
m_cf = isCarry(m_a, immediate, 0x100);
|
||||
|
||||
// A = A + r
|
||||
m_a = (m_a + immediate) & 0x00ff;
|
||||
m_a = (m_a + immediate) & 0xff;
|
||||
|
||||
// Zero flag
|
||||
m_zf = m_a == 0;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
@@ -130,17 +198,18 @@ void CPU::dec8()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0x0d: {
|
||||
// DEC C, flags: Z 1 H -
|
||||
case 0x0d: { // DEC C, flags: Z 1 H -
|
||||
m_wait_cycles += 4;
|
||||
|
||||
m_c = (m_c - 1) & 0x00ff;
|
||||
|
||||
// Set flags
|
||||
// TODO
|
||||
// m_zf = ?
|
||||
m_nf = 1;
|
||||
// m_hf = ?
|
||||
m_hf = isCarry(m_c, -1, 0x10);
|
||||
|
||||
// C = C - 1
|
||||
m_c = (m_c - 1) & 0xff;
|
||||
|
||||
// Zero flag
|
||||
m_zf = m_c == 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -152,14 +221,12 @@ void CPU::xor8()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0xa8:
|
||||
// XOR B, flags: Z 0 0 0
|
||||
case 0xa8: // XOR B, flags: Z 0 0 0
|
||||
m_nf = m_hf = m_cf = 0;
|
||||
m_a ^= m_b;
|
||||
m_zf = m_a == 0;
|
||||
break;
|
||||
case 0xaf:
|
||||
// XOR A, flags: 1 0 0 0
|
||||
case 0xaf: // XOR A, flags: 1 0 0 0
|
||||
// A ^ A will always be 0
|
||||
m_a = m_nf = m_hf = m_cf = 0;
|
||||
m_zf = 1;
|
||||
@@ -169,71 +236,11 @@ void CPU::xor8()
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::ld8()
|
||||
void CPU::lda8()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0x02:
|
||||
// LD (BC),A
|
||||
m_wait_cycles += 8;
|
||||
write(bc(), m_a);
|
||||
break;
|
||||
case 0x06:
|
||||
// LD B,n
|
||||
m_wait_cycles += 8;
|
||||
m_b = pcRead();
|
||||
break;
|
||||
case 0x0a:
|
||||
// LD A,(BC)
|
||||
m_wait_cycles += 8;
|
||||
m_a = read(bc());
|
||||
break;
|
||||
case 0x0e:
|
||||
// LD C,n
|
||||
m_wait_cycles += 8;
|
||||
m_c = pcRead();
|
||||
break;
|
||||
case 0x12:
|
||||
// LD (DE),A
|
||||
m_wait_cycles += 8;
|
||||
write(de(), m_a);
|
||||
break;
|
||||
case 0x16:
|
||||
// LD D,n
|
||||
m_wait_cycles += 8;
|
||||
m_d = pcRead();
|
||||
break;
|
||||
case 0x1a:
|
||||
// LD A,(DE)
|
||||
m_wait_cycles += 8;
|
||||
m_a = read(de());
|
||||
break;
|
||||
case 0x1e:
|
||||
// LD E,n
|
||||
m_wait_cycles += 8;
|
||||
m_e = pcRead();
|
||||
break;
|
||||
case 0x22: {
|
||||
// LD (HL+),A == LD (HLI),A == LDI (HL),A
|
||||
m_wait_cycles += 8;
|
||||
|
||||
// Put A into memory address in HL
|
||||
uint32_t address = hl();
|
||||
write(address, m_a);
|
||||
|
||||
// Increment HL
|
||||
address = (address + 1) & 0xffff;
|
||||
m_l = address & 0x00ff;
|
||||
m_h = address >> 8;
|
||||
break;
|
||||
}
|
||||
case 0x26:
|
||||
// LD H,n
|
||||
m_wait_cycles += 8;
|
||||
m_h = pcRead();
|
||||
break;
|
||||
case 0x2a: {
|
||||
// LD A,(HL+) == LD A,(HLI) == LDI A,(HL)
|
||||
case 0x2a: { // LD A,(HL+) == LD A,(HLI) == LDI A,(HL)
|
||||
m_wait_cycles += 8;
|
||||
|
||||
// Put value at address in HL into A
|
||||
@@ -242,18 +249,93 @@ void CPU::ld8()
|
||||
|
||||
// Increment HL
|
||||
address = (address + 1) & 0xffff;
|
||||
m_l = address & 0x00ff;
|
||||
m_l = address & 0xff;
|
||||
m_h = address >> 8;
|
||||
break;
|
||||
}
|
||||
case 0x2e:
|
||||
// LD L,n
|
||||
case 0x3a: { // LD A,(HL-) == LD A,(HLD) == LDD A,(HL)
|
||||
m_wait_cycles += 8;
|
||||
m_l = pcRead();
|
||||
|
||||
// Put value at address in HL into A
|
||||
uint32_t address = hl();
|
||||
m_a = read(address);
|
||||
|
||||
// Decrement HL
|
||||
address = (address + 1) & 0xffff;
|
||||
m_l = address & 0xff;
|
||||
m_h = address >> 8;
|
||||
break;
|
||||
case 0x32: {
|
||||
// LD (HL-),A == LD (HLD),A == LDD (HL),A
|
||||
}
|
||||
case 0xf2: // LD A,(C)
|
||||
m_wait_cycles += 8;
|
||||
m_a = read(m_c);
|
||||
break;
|
||||
case 0xfa: // LD A,a16
|
||||
m_wait_cycles += 16;
|
||||
m_a = pcRead16();
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::ldi8()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0x06: /* LD B,i8 */ m_b = pcRead(); break;
|
||||
case 0x0e: /* LD C,i8 */ m_c = pcRead(); break;
|
||||
case 0x16: /* LD D,i8 */ m_d = pcRead(); break;
|
||||
case 0x1e: /* LD E,i8 */ m_e = pcRead(); break;
|
||||
case 0x26: /* LD H,i8 */ m_h = pcRead(); break;
|
||||
case 0x2e: /* LD L,i8 */ m_l = pcRead(); break;
|
||||
case 0x36: // LD (HL),i8
|
||||
m_wait_cycles += 4;
|
||||
write(hl(), pcRead());
|
||||
break;
|
||||
case 0x3e: /* LD A,i8 */ m_a = pcRead(); break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
m_wait_cycles += 8;
|
||||
}
|
||||
|
||||
void CPU::ldr8()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0x02: // LD (BC),A
|
||||
m_wait_cycles += 4;
|
||||
write(bc(), m_a);
|
||||
break;
|
||||
case 0x0a: // LD A,(BC)
|
||||
m_wait_cycles += 4;
|
||||
m_a = read(bc());
|
||||
break;
|
||||
case 0x12: // LD (DE),A
|
||||
m_wait_cycles += 4;
|
||||
write(de(), m_a);
|
||||
break;
|
||||
case 0x1a: // LD A,(DE)
|
||||
m_wait_cycles += 4;
|
||||
m_a = read(de());
|
||||
break;
|
||||
case 0x22: { // LD (HL+),A == LD (HLI),A == LDI (HL),A
|
||||
m_wait_cycles += 4;
|
||||
|
||||
// Put A into memory address in HL
|
||||
uint32_t address = hl();
|
||||
write(address, m_a);
|
||||
|
||||
// Increment HL
|
||||
address = (address + 1) & 0xffff;
|
||||
m_l = address & 0xff;
|
||||
m_h = address >> 8;
|
||||
break;
|
||||
}
|
||||
case 0x32: { // LD (HL-),A == LD (HLD),A == LDD (HL),A
|
||||
m_wait_cycles += 4;
|
||||
|
||||
// Put A into memory address in HL
|
||||
uint32_t address = hl();
|
||||
@@ -261,52 +343,117 @@ void CPU::ld8()
|
||||
|
||||
// Decrement HL
|
||||
address = (address - 1) & 0xffff;
|
||||
m_l = address & 0x00ff;
|
||||
m_l = address & 0xff;
|
||||
m_h = address >> 8;
|
||||
break;
|
||||
}
|
||||
case 0x36:
|
||||
// LD (HL),n
|
||||
m_wait_cycles += 12;
|
||||
write(hl(), pcRead());
|
||||
case 0x40: /* LD B,B m_b = m_b; */ break;
|
||||
case 0x41: /* LD B,C */ m_b = m_c; break;
|
||||
case 0x42: /* LD B,D */ m_b = m_d; break;
|
||||
case 0x43: /* LD B,E */ m_b = m_e; break;
|
||||
case 0x44: /* LD B,H */ m_b = m_h; break;
|
||||
case 0x45: /* LD B,L */ m_b = m_l; break;
|
||||
case 0x47: /* LD B,A */ m_b = m_a; break;
|
||||
case 0x48: /* LD C,B */ m_c = m_b; break;
|
||||
case 0x49: /* LD C,C m_c = m_c; */ break;
|
||||
case 0x4a: /* LD C,D */ m_c = m_d; break;
|
||||
case 0x4b: /* LD C,E */ m_c = m_e; break;
|
||||
case 0x4c: /* LD C,H */ m_c = m_h; break;
|
||||
case 0x4d: /* LD C,L */ m_c = m_l; break;
|
||||
case 0x4f: /* LD C,A */ m_c = m_a; break;
|
||||
case 0x50: /* LD D,B */ m_d = m_b; break;
|
||||
case 0x51: /* LD D,C */ m_d = m_c; break;
|
||||
case 0x52: /* LD D,D m_d = m_d; */ break;
|
||||
case 0x53: /* LD D,E */ m_d = m_e; break;
|
||||
case 0x54: /* LD D,H */ m_d = m_h; break;
|
||||
case 0x55: /* LD D,L */ m_d = m_l; break;
|
||||
case 0x57: /* LD D,A */ m_d = m_a; break;
|
||||
case 0x58: /* LD E,B */ m_e = m_b; break;
|
||||
case 0x59: /* LD E,C */ m_e = m_c; break;
|
||||
case 0x5a: /* LD E,D */ m_e = m_d; break;
|
||||
case 0x5b: /* LD E,E m_e = m_e; */ break;
|
||||
case 0x5c: /* LD E,H */ m_e = m_h; break;
|
||||
case 0x5d: /* LD E,L */ m_e = m_l; break;
|
||||
case 0x5f: /* LD E,A */ m_e = m_a; break;
|
||||
case 0x60: /* LD H,B */ m_h = m_b; break;
|
||||
case 0x61: /* LD H,C */ m_h = m_c; break;
|
||||
case 0x62: /* LD H,D */ m_h = m_d; break;
|
||||
case 0x63: /* LD H,E */ m_h = m_e; break;
|
||||
case 0x64: /* LD H,H m_h = m_h; */ break;
|
||||
case 0x65: /* LD H,L */ m_h = m_l; break;
|
||||
case 0x67: /* LD H,A */ m_h = m_a; break;
|
||||
case 0x68: /* LD L,B */ m_l = m_b; break;
|
||||
case 0x69: /* LD L,C */ m_l = m_c; break;
|
||||
case 0x6a: /* LD L,D */ m_l = m_d; break;
|
||||
case 0x6b: /* LD L,E */ m_l = m_e; break;
|
||||
case 0x6c: /* LD L,H */ m_l = m_h; break;
|
||||
case 0x6d: /* LD L,L m_l = m_l; */ break;
|
||||
case 0x6f: /* LD L,A */ m_l = m_a; break;
|
||||
case 0x70: // LD (HL),B
|
||||
m_wait_cycles += 4;
|
||||
write(hl(), m_b);
|
||||
break;
|
||||
case 0x3a: {
|
||||
// LD A,(HL-) == LD A,(HLD) == LDD A,(HL)
|
||||
m_wait_cycles += 8;
|
||||
|
||||
// Put value at address in HL into A
|
||||
uint32_t address = hl();
|
||||
m_a = read(address);
|
||||
|
||||
// Decrement HL
|
||||
address = (address + 1) & 0xffff;
|
||||
m_l = address & 0x00ff;
|
||||
m_h = address >> 8;
|
||||
case 0x71: // LD (HL),C
|
||||
m_wait_cycles += 4;
|
||||
write(hl(), m_c);
|
||||
break;
|
||||
}
|
||||
case 0x3e:
|
||||
// LD A,n
|
||||
m_wait_cycles += 8;
|
||||
m_a = pcRead();
|
||||
case 0x72: // LD (HL),D
|
||||
m_wait_cycles += 4;
|
||||
write(hl(), m_d);
|
||||
break;
|
||||
case 0x73: // LD (HL),E
|
||||
m_wait_cycles += 4;
|
||||
write(hl(), m_e);
|
||||
break;
|
||||
case 0x74: // LD (HL),H
|
||||
m_wait_cycles += 4;
|
||||
write(hl(), m_h);
|
||||
break;
|
||||
case 0x75: // LD (HL),L
|
||||
m_wait_cycles += 4;
|
||||
write(hl(), m_l);
|
||||
break;
|
||||
case 0x77: // LD (HL),A
|
||||
m_wait_cycles += 4;
|
||||
write(hl(), m_a);
|
||||
break;
|
||||
case 0x78: /* LD A,B */ m_a = m_b; break;
|
||||
case 0x79: /* LD A,B */ m_a = m_c; break;
|
||||
case 0x7a: /* LD A,D */ m_a = m_d; break;
|
||||
case 0x7b: /* LD A,E */ m_a = m_e; break;
|
||||
case 0x7c: /* LD A,H */ m_a = m_h; break;
|
||||
case 0x7d: /* LD A,L */ m_a = m_l; break;
|
||||
case 0x7e: // LD A,(HL)
|
||||
m_wait_cycles += 4;
|
||||
m_a = read(hl());
|
||||
break;
|
||||
case 0x7f: /* LD A,A m_a = m_a; */ break;
|
||||
case 0xe2: // LD (C),A
|
||||
m_wait_cycles += 4;
|
||||
write(m_c, m_a);
|
||||
break;
|
||||
case 0xea: // LD a16,A
|
||||
m_wait_cycles += 16;
|
||||
write(pcRead16(), m_a);
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
m_wait_cycles += 4;
|
||||
}
|
||||
|
||||
void CPU::ldh8()
|
||||
void CPU::ldffi8()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0xe0:
|
||||
// LD ($ff00 + n),A == LDH (n),A
|
||||
case 0xe0: // LD ($ff00 + i8),A == LDH (io8),A
|
||||
m_wait_cycles += 12;
|
||||
|
||||
// Put value in A into address (0xff00 + next byte in memory)
|
||||
ffWrite(pcRead(), m_a);
|
||||
break;
|
||||
case 0xf0:
|
||||
// LD A,($ff00 + n) == LDH A,(n)
|
||||
case 0xf0: // LD A,($ff00 + i8) == LDH A,(io8)
|
||||
m_wait_cycles += 12;
|
||||
|
||||
// Put value at address (0xff00 + next byte in memory) into A
|
||||
@@ -317,52 +464,60 @@ void CPU::ldh8()
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::ld16()
|
||||
void CPU::ldi16()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0x01: {
|
||||
m_wait_cycles += 12;
|
||||
write(bc(), pcRead16());
|
||||
case 0x01: /* LD BC,i16 */
|
||||
m_c = pcRead();
|
||||
m_b = pcRead();
|
||||
break;
|
||||
case 0x11: /* LD DE,i16 */
|
||||
m_e = pcRead();
|
||||
m_d = pcRead();
|
||||
break;
|
||||
case 0x21: /* LD HL,i16 */
|
||||
m_l = pcRead();
|
||||
m_h = pcRead();
|
||||
break;
|
||||
case 0x31: /* LD SP,i16 */
|
||||
m_sp = pcRead16();
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
case 0x08: {
|
||||
// LD (nn),SP
|
||||
|
||||
m_wait_cycles += 12;
|
||||
}
|
||||
|
||||
void CPU::ldr16()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0x08: { // LD a16,SP
|
||||
m_wait_cycles += 20;
|
||||
|
||||
// Put value of SP into address given by next 2 bytes in memory
|
||||
// TODO
|
||||
write(pcRead16(), m_sp);
|
||||
break;
|
||||
}
|
||||
case 0x11:
|
||||
// LD DE,nn
|
||||
m_wait_cycles += 12;
|
||||
write(de(), pcRead16());
|
||||
break;
|
||||
case 0x21:
|
||||
// LD HL,nn
|
||||
m_wait_cycles += 12;
|
||||
write(hl(), pcRead16());
|
||||
break;
|
||||
case 0x31: {
|
||||
// LD SP,nn
|
||||
m_wait_cycles += 12;
|
||||
m_sp = pcRead16();
|
||||
break;
|
||||
}
|
||||
case 0xf8: {
|
||||
// LD HL,SP + e8 == LDHL SP,e8
|
||||
case 0xf8: { // LD HL,SP + s8 == LDHL SP,s8, flags: 0 0 H C
|
||||
m_wait_cycles += 12;
|
||||
|
||||
// Put SP + next (signed) byte in memory into HL
|
||||
// TODO
|
||||
uint32_t signed_data = (pcRead() ^ 0x80) - 0x80;
|
||||
uint32_t sum = m_sp + signed_data;
|
||||
m_h = sum >> 8;
|
||||
m_l = sum & 0xff;
|
||||
|
||||
// Unsets ZF and NF, may enable HF and CF
|
||||
// TODO flags
|
||||
// Set flags
|
||||
m_zf = 0;
|
||||
m_nf = 0;
|
||||
m_hf = isCarry(m_sp, signed_data, 0x10);
|
||||
m_cf = isCarry(m_sp, signed_data, 0x100);
|
||||
break;
|
||||
}
|
||||
case 0xf9: {
|
||||
// LD SP,HL
|
||||
case 0xf9: { // LD SP,HL
|
||||
m_wait_cycles += 8;
|
||||
m_sp = hl();
|
||||
break;
|
||||
@@ -376,8 +531,7 @@ void CPU::call()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0xcd: {
|
||||
// CALL nn
|
||||
case 0xcd: { // CALL a16
|
||||
m_wait_cycles += 24;
|
||||
|
||||
uint32_t data = pcRead16();
|
||||
@@ -386,7 +540,7 @@ void CPU::call()
|
||||
m_sp = (m_sp - 1) & 0xffff;
|
||||
write(m_sp, data >> 8);
|
||||
m_sp = (m_sp - 1) & 0xffff;
|
||||
write(m_sp, data & 0x00ff);
|
||||
write(m_sp, data & 0xff);
|
||||
|
||||
// Jump to this address
|
||||
m_pc = data;
|
||||
@@ -401,8 +555,7 @@ void CPU::jp16()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0xc3:
|
||||
// JP nn
|
||||
case 0xc3: // JP a16
|
||||
m_wait_cycles += 16;
|
||||
m_pc = pcRead16();
|
||||
break;
|
||||
@@ -416,8 +569,7 @@ void CPU::jr8()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0x20: {
|
||||
// JR NZ,e8
|
||||
case 0x20: { // JR NZ,s8
|
||||
m_wait_cycles += 8;
|
||||
|
||||
if (!m_zf) {
|
||||
@@ -435,12 +587,11 @@ void CPU::misc()
|
||||
{
|
||||
uint8_t opcode = pcRead();
|
||||
switch (opcode) {
|
||||
case 0x2f:
|
||||
// CPL, flags: - 1 1 -
|
||||
case 0x2f: // CPL, flags: - 1 1 -
|
||||
m_wait_cycles += 4;
|
||||
|
||||
// Complement register A (flip all bits)
|
||||
m_a ^= 0x00ff; // equivalent to: m_a = ~m_a & 0x00ff
|
||||
m_a ^= 0xff; // equivalent to: m_a = ~m_a & 0xff
|
||||
|
||||
// Set flags
|
||||
m_nf = m_hf = 1;
|
||||
@@ -454,29 +605,57 @@ void CPU::misc()
|
||||
|
||||
uint32_t CPU::pcRead()
|
||||
{
|
||||
uint32_t data = Emu::the().readMemory(m_pc) & 0x00ff;
|
||||
uint32_t data = Emu::the().readMemory(m_pc) & 0xff;
|
||||
m_pc = (m_pc + 1) & 0xffff;
|
||||
return data;
|
||||
}
|
||||
|
||||
void CPU::write(uint32_t address, uint32_t value)
|
||||
{
|
||||
Emu::the().writeMemory(address, value & 0x00ff);
|
||||
Emu::the().writeMemory(address, value & 0xff);
|
||||
}
|
||||
|
||||
uint32_t CPU::read(uint32_t address)
|
||||
{
|
||||
return Emu::the().readMemory(address) & 0x00ff;
|
||||
return Emu::the().readMemory(address) & 0xff;
|
||||
}
|
||||
|
||||
void CPU::ffWrite(uint32_t address, uint32_t value)
|
||||
{
|
||||
Emu::the().writeMemory(address | (0xff << 8), value & 0x00ff);
|
||||
Emu::the().writeMemory(address | (0xff << 8), value & 0xff);
|
||||
}
|
||||
|
||||
uint32_t CPU::ffRead(uint32_t address)
|
||||
{
|
||||
return Emu::the().readMemory(address | (0xff << 8)) & 0x00ff;
|
||||
return Emu::the().readMemory(address | (0xff << 8)) & 0xff;
|
||||
}
|
||||
|
||||
bool CPU::isCarry(uint32_t lhs, uint32_t rhs, uint32_t limit_bit)
|
||||
{
|
||||
// limit_bit values:
|
||||
// 8-bit half-carry = 0x10 or 16
|
||||
// 8-bit carry = 0x100 or 256
|
||||
// 16-bit half-carry = 0x1000 or 4096
|
||||
// 16-bit carry = 0x10000 or 65536
|
||||
|
||||
// Example for 8-bit half-carry:
|
||||
// 0b00111110 62 | 0b00111000 56
|
||||
// 0b00100010 34 + | 0b00010001 17 +
|
||||
// --------------- | ---------------
|
||||
// 0b01100000 96 | 0b01001001 73
|
||||
// |
|
||||
// 0b00111110 62 | 0b00111000 56
|
||||
// 0b00100010 34 ^ | 0b00010001 17 ^
|
||||
// --------------- | ---------------
|
||||
// 0b00011100 | 0b00101001
|
||||
// 0b01100000 96 ^ | 0b01001001 73 ^
|
||||
// --------------- | ---------------
|
||||
// 0b01111100 | 0b01100000
|
||||
// 0b00010000 16 & | 0b00010000 16 &
|
||||
// --------------- | ---------------
|
||||
// 0b00010000 = true! | 0b00000000 = false!
|
||||
|
||||
return (lhs ^ rhs ^ (lhs + rhs)) & limit_bit;
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // int8_t, int32_t, uint8_t, uint32_t
|
||||
#include <cstdint> // int8_t, uint8_t, uint32_t
|
||||
#include <functional> // std::function
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#include "ruc/format/formatter.h"
|
||||
|
||||
class CPU final : public ProcessingUnit {
|
||||
private:
|
||||
friend struct CPUTest;
|
||||
|
||||
public:
|
||||
explicit CPU(uint32_t frequency);
|
||||
virtual ~CPU();
|
||||
@@ -41,11 +44,14 @@ public:
|
||||
// Load Instructions
|
||||
|
||||
// 8-bit
|
||||
void ld8();
|
||||
void ldh8();
|
||||
void ldffi8();
|
||||
void lda8();
|
||||
void ldi8();
|
||||
void ldr8();
|
||||
|
||||
// 16-bit
|
||||
void ld16();
|
||||
void ldi16();
|
||||
void ldr16();
|
||||
|
||||
// -------------------------------------
|
||||
// Jumps and Subroutines
|
||||
@@ -81,6 +87,8 @@ private:
|
||||
void ffWrite(uint32_t address, uint32_t value);
|
||||
uint32_t ffRead(uint32_t address);
|
||||
|
||||
bool isCarry(uint32_t lhs, uint32_t rhs, uint32_t limit_bit);
|
||||
|
||||
// Registers
|
||||
uint32_t m_a { 0 }; // Accumulator
|
||||
uint32_t m_b { 0 }; // B
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "cpu.h"
|
||||
#include "macro.h"
|
||||
#include "testcase.h"
|
||||
#include "testsuite.h"
|
||||
|
||||
struct CPUTest {
|
||||
CPU cpu { 0 };
|
||||
|
||||
bool isCarry(uint32_t lhs, uint32_t rhs, uint32_t limit_bit) { return cpu.isCarry(lhs, rhs, limit_bit); }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
TEST_CASE(CPUIsCarry)
|
||||
{
|
||||
CPUTest test;
|
||||
|
||||
// 8-bit Half-carry false
|
||||
|
||||
EXPECT_EQ(test.isCarry(56, 17, 0x10), false);
|
||||
|
||||
EXPECT_EQ(test.isCarry(16, -1, 0x10), false);
|
||||
EXPECT_EQ(test.isCarry(32, -1, 0x10), false);
|
||||
EXPECT_EQ(test.isCarry(144, -1, 0x10), false);
|
||||
EXPECT_EQ(test.isCarry(211, -20, 0x10), false);
|
||||
|
||||
// 8-bit Half-carry true
|
||||
|
||||
EXPECT_EQ(test.isCarry(46, 34, 0x10), true);
|
||||
EXPECT_EQ(test.isCarry(62, 34, 0x10), true);
|
||||
|
||||
EXPECT_EQ(test.isCarry(136, -1, 0x10), true);
|
||||
EXPECT_EQ(test.isCarry(219, -20, 0x10), true);
|
||||
EXPECT_EQ(test.isCarry(250, -20, 0x10), true);
|
||||
|
||||
// 8-bit Full carry false
|
||||
|
||||
EXPECT_EQ(test.isCarry(254, 1, 0x100), false);
|
||||
|
||||
// 8-bit Full carry true
|
||||
|
||||
EXPECT_EQ(test.isCarry(254, 2, 0x100), true);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "macro.h"
|
||||
#include "testcase.h"
|
||||
#include "testsuite.h"
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
TEST_CASE(Thing)
|
||||
{
|
||||
int a = 5;
|
||||
EXPECT_EQ(a, 5);
|
||||
}
|
||||
Vendored
+1
-1
Submodule vendor/ruc updated: 27489c6cb4...f31f7feb5e
Reference in New Issue
Block a user