/* * Copyright (C) 2022 Riyyi * Copyright (C) 2022 Th3FrankXD * * SPDX-License-Identifier: MIT */ #include // uint8_t, uint32_t #include "cpu.h" #include "emu.h" #include "ruc/format/color.h" #include "ruc/format/print.h" #include "ruc/meta/assert.h" CPU::CPU(uint32_t frequency) : ProcessingUnit(frequency) // https://gbdev.io/pandocs/Power_Up_Sequence.html#cpu-registers // CGB registers , m_a(0x11) , m_b(0x0) , m_c(0x0) , m_d(0xff) , m_e(0x56) , m_h(0x0) , m_l(0x0d) , m_pc(0x0) , m_sp(0xfffe) // Flags , m_zf(0x1) , m_nf(0x0) , 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); m_shared_registers.emplace("d", &m_d); m_shared_registers.emplace("e", &m_e); m_shared_registers.emplace("h", &m_h); m_shared_registers.emplace("l", &m_l); m_shared_registers.emplace("sp", &m_sp); m_shared_registers.emplace("pc", &m_pc); m_shared_registers.emplace("zf", &m_zf); m_shared_registers.emplace("nf", &m_nf); m_shared_registers.emplace("hf", &m_hf); m_shared_registers.emplace("cf", &m_cf); } CPU::~CPU() { } // ----------------------------------------- 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 0x01: ldi16(); break; case 0x02: ldr8(); break; case 0x04: inc(); break; case 0x05: dec8(); break; case 0x06: ldi8(); break; case 0x08: ldr16(); break; case 0x09: addr16(); break; case 0x0a: ldr8(); break; case 0x0b: dec16(); break; case 0x0c: inc(); break; case 0x0d: dec8(); break; case 0x0e: ldi8(); break; case 0x11: ldi16(); break; case 0x12: ldr8(); break; case 0x14: inc(); break; case 0x15: dec8(); break; case 0x16: ldi8(); break; case 0x18: jrs8(); break; case 0x19: addr16(); break; case 0x1a: ldr8(); break; case 0x1b: dec16(); break; case 0x1c: inc(); break; case 0x1d: dec8(); break; case 0x1e: ldi8(); break; case 0x20: jrs8(); break; case 0x21: ldi16(); break; case 0x22: ldr8(); break; case 0x24: inc(); break; case 0x25: dec8(); break; case 0x26: ldi8(); break; case 0x28: jrs8(); break; case 0x29: addr16(); break; case 0x2a: lda8(); break; case 0x2b: dec16(); break; case 0x2c: inc(); break; case 0x2d: dec8(); break; case 0x2e: ldi8(); break; case 0x2f: misc(); break; case 0x30: jrs8(); break; case 0x31: ldi16(); break; case 0x32: ldr8(); break; case 0x34: inc(); break; case 0x35: dec8(); break; case 0x36: ldi8(); break; case 0x38: jrs8(); break; case 0x39: addr16(); break; case 0x3a: ldr8(); break; case 0x3b: dec16(); break; case 0x3c: inc(); break; case 0x3d: dec8(); 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 0xb8: cp(); break; case 0xb9: cp(); break; case 0xba: cp(); break; case 0xbb: cp(); break; case 0xbc: cp(); break; case 0xbd: cp(); break; case 0xbe: cp(); break; case 0xbf: cp(); break; case 0xc3: jp16(); break; case 0xc6: addi8(); break; case 0xcd: call(); break; case 0xe0: ldffi8(); break; case 0xe2: ldr8(); break; case 0xe8: adds8(); 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; case 0xfe: cp(); break; default: print("opcode {:#x} not implemented\n", opcode); print("pc: {:#x}, immediate: {:#x}\n", m_pc, pcRead()); VERIFY_NOT_REACHED(); } } } // ------------------------------------- void CPU::addi8() { uint8_t opcode = pcRead(); uint8_t immediate = pcRead(); switch (opcode) { case 0xc6: // ADD A,i8, flags: Z 0 H C m_wait_cycles += 8; // Set flags m_nf = 0; m_hf = isCarry(m_a, immediate, 0x10); m_cf = isCarry(m_a, immediate, 0x100); // A = A + r m_a = (m_a + immediate) & 0xff; // Zero flag m_zf = m_a == 0; 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; // Set flags m_nf = 1; m_hf = isCarry(reg, -1, 0x10); // Decrement value in register r8 by 1 reg = (reg - 1) & 0xff; // Zero flag 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; } case 0x3d: /* DEC A */ decrement(m_a); break; default: VERIFY_NOT_REACHED(); } } void CPU::xor8() { uint8_t opcode = pcRead(); switch (opcode) { 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 // A ^ A will always be 0 m_a = m_nf = m_hf = m_cf = 0; m_zf = 1; break; default: VERIFY_NOT_REACHED(); } } void CPU::lda8() { uint8_t opcode = pcRead(); switch (opcode) { case 0x2a: { // LD A,(HL+) == LD A,(HLI) == LDI A,(HL) m_wait_cycles += 8; // Put value at address in HL into A uint32_t address = hl(); m_a = read(address); // Increment HL address = (address + 1) & 0xffff; m_l = address & 0xff; m_h = address >> 8; 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 & 0xff; m_h = address >> 8; break; } 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::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(); switch (opcode) { case 0x0b: { // DEC BC uint32_t data = (bc() - 1) & 0xffff; m_c = data & 0xff; m_b = data >> 8; break; } case 0x1b: { // DEC DE uint32_t data = (de() - 1) & 0xffff; m_e = data & 0xff; m_d = data >> 8; break; } case 0x2b: { // DEC HL uint32_t data = (hl() - 1) & 0xffff; m_l = data & 0xff; m_h = data >> 8; break; } case 0x3b: /* DEC SP */ m_sp = (m_sp - 1) & 0xffff; break; default: VERIFY_NOT_REACHED(); } m_wait_cycles += 8; } // ------------------------------------- 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(); write(address, m_a); // Decrement HL address = (address - 1) & 0xffff; m_l = address & 0xff; m_h = address >> 8; break; } 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 0x71: // LD (HL),C m_wait_cycles += 4; write(hl(), m_c); break; 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::cp() { uint8_t opcode = pcRead(); uint8_t value = 0; switch (opcode) { case 0xb8: /* CP B */ value = m_b; break; case 0xb9: /* CP C */ value = m_c; break; case 0xba: /* CP D */ value = m_d; break; case 0xbb: /* CP E */ value = m_e; break; case 0xbc: /* CP H */ value = m_h; break; case 0xbd: /* CP L */ value = m_l; break; case 0xbe: // CP,(HL) m_wait_cycles += 4; value = read(hl()); break; case 0xbf: // CP A, flags: 1 1 0 0 m_wait_cycles += 4; // A - A m_zf = m_nf = 1; m_hf = m_cf = 0; return; // Early return as we already set the flags case 0xfe: // CP i8 m_wait_cycles += 4; value = pcRead(); break; default: VERIFY_NOT_REACHED(); } // CP r8, flags: Z 1 H C m_wait_cycles += 4; // Set flags m_zf = ((m_a - value) & 0xff) == 0; m_nf = 1; m_hf = isCarry(m_a, value, 0x10); m_cf = isCarry(m_a, value, 0x100); } void CPU::inc() { auto increment = [this](uint32_t& reg) -> void { // INC r8, flags: Z 0 H - m_wait_cycles += 4; // Set flags m_nf = 0; m_hf = isCarry(reg, 1, 0x10); // Increment value in register r8 by 1 reg = (reg + 1) & 0xff; // Zero flag m_zf = reg == 0; }; uint8_t opcode = pcRead(); switch (opcode) { case 0x04: /* INC B */ increment(m_b); break; case 0x0c: /* INC C */ increment(m_c); break; case 0x14: /* INC D */ increment(m_d); break; case 0x1c: /* INC E */ increment(m_e); break; case 0x24: /* INC H */ increment(m_h); break; case 0x2c: /* INC L */ increment(m_l); break; case 0x34: /* INC (HL) */ { m_wait_cycles += 8; // + 4 = 12 total // Increment the byte pointed to by HL by 1 uint32_t data = read(hl()); increment(data); write(hl(), data); break; } case 0x3c: /* INC A */ increment(m_a); break; default: VERIFY_NOT_REACHED(); } } void CPU::ldffi8() { uint8_t opcode = pcRead(); switch (opcode) { 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 + i8) == LDH A,(io8) m_wait_cycles += 12; // Put value at address (0xff00 + next byte in memory) into A m_a = ffRead(pcRead()); break; default: VERIFY_NOT_REACHED(); } } void CPU::ldi16() { uint8_t opcode = pcRead(); switch (opcode) { 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(); } 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 write(pcRead16(), m_sp); break; } 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 uint32_t signed_data = (pcRead() ^ 0x80) - 0x80; uint32_t sum = m_sp + signed_data; m_h = sum >> 8; m_l = sum & 0xff; // 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 m_wait_cycles += 8; m_sp = hl(); break; } default: VERIFY_NOT_REACHED(); } } void CPU::call() { uint8_t opcode = pcRead(); switch (opcode) { case 0xcd: { // CALL a16 m_wait_cycles += 24; uint32_t data = pcRead16(); // Push address of next 2 bytes in memory onto stack m_sp = (m_sp - 1) & 0xffff; write(m_sp, data >> 8); m_sp = (m_sp - 1) & 0xffff; write(m_sp, data & 0xff); // Jump to this address m_pc = data; break; } default: VERIFY_NOT_REACHED(); } } void CPU::jp16() { uint8_t opcode = pcRead(); switch (opcode) { case 0xc3: // JP a16 m_wait_cycles += 16; m_pc = pcRead16(); break; default: VERIFY_NOT_REACHED(); } } void CPU::jrs8() { 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(); switch (opcode) { case 0x18: /* JR s8 */ jump_relative(true); break; case 0x20: /* JR NZ,s8 */ jump_relative(!m_zf); break; case 0x28: /* JR Z,s8 */ jump_relative(m_zf); break; case 0x30: /* JR NC,s8 */ jump_relative(!m_cf); break; case 0x38: /* JR C,s8 */ jump_relative(m_cf); break; default: VERIFY_NOT_REACHED(); } } void CPU::misc() { uint8_t opcode = pcRead(); switch (opcode) { case 0x2f: // CPL, flags: - 1 1 - m_wait_cycles += 4; // Complement register A (flip all bits) m_a ^= 0xff; // equivalent to: m_a = ~m_a & 0xff // Set flags m_nf = m_hf = 1; break; default: VERIFY_NOT_REACHED(); } } // ----------------------------------------- uint32_t CPU::pcRead() { 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 & 0xff); } uint32_t CPU::read(uint32_t address) { return Emu::the().readMemory(address) & 0xff; } void CPU::ffWrite(uint32_t address, uint32_t value) { Emu::the().writeMemory(address | (0xff << 8), value & 0xff); } uint32_t CPU::ffRead(uint32_t address) { 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; } // ----------------------------------------- void Formatter::parse(Parser& parser) { parser.parseSpecifier(specifier, Parser::ParameterType::UserDefined); } void Formatter::format(Builder& builder, const CPU& value) const { if (!specifier.alternativeForm) { builder.putString( ::format("| AF {:#06x} | BC {:#06x} | DE {:#06x} | HL {:#06x} | PC {:#06x} | SP {:#06x} |", value.af(), value.bc(), value.de(), value.hl(), value.pc(), value.sp())); return; } Formatter formatter { .specifier = specifier }; builder.putString("AF: "); formatter.format(builder, value.af()); builder.putString("\nBC: "); formatter.format(builder, value.bc()); builder.putString("\nDE: "); formatter.format(builder, value.de()); builder.putString("\nHL: "); formatter.format(builder, value.hl()); builder.putString("\nPC: "); formatter.format(builder, value.pc()); builder.putString("\nSP: "); formatter.format(builder, value.sp()); }