Compare commits
59
Commits
275739bd5b
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
301c9343da | ||
|
|
91884d65fb | ||
|
|
e566adad50 | ||
|
|
1c14228bf7 | ||
|
|
00ceacaf52 | ||
|
|
adb290a7c7 | ||
|
|
67539b6d43 | ||
|
|
7ed5c74faf | ||
|
|
f2a68e6294 | ||
|
|
5e6972e4da | ||
|
|
c0c592c438 | ||
|
|
900dbde54b | ||
|
|
16db21fcab | ||
|
|
d33de14694 | ||
|
|
80c68035c5 | ||
|
|
8f016b1095 | ||
|
|
869438e0e0 | ||
|
|
6943cfd684 | ||
|
|
fcfd27468d | ||
|
|
ce46f5033a | ||
|
|
5919f70537 | ||
|
|
136250355e | ||
|
|
66c10e3c3e | ||
|
|
7153b0e9b6 | ||
|
|
cd2673909c | ||
|
|
5b4dbdc9a4 | ||
|
|
5b08c18127 | ||
|
|
857865dfb9 | ||
|
|
a29f01389b | ||
|
|
a806f173ce | ||
|
|
3b132a6820 | ||
|
|
b63f0d3b52 | ||
|
|
b48f308bfc | ||
|
|
059441c30a | ||
|
|
a4743fa77b | ||
|
|
70778840c7 | ||
|
|
c396e7fcf6 | ||
|
|
92c0236f2a | ||
|
|
f56984b555 | ||
|
|
1dd9a2c16e | ||
|
|
1c0809b611 | ||
|
|
c8ad40c128 | ||
|
|
cc46a45a73 | ||
|
|
ed045a72b2 | ||
|
|
38b740856e | ||
|
|
e1eed33410 | ||
|
|
2b7d4dee3d | ||
|
|
5831a23b02 | ||
|
|
c7903c0a50 | ||
|
|
5d054c90f8 | ||
|
|
5a690a12d2 | ||
|
|
461fc28c94 | ||
|
|
6c2e5d32d4 | ||
|
|
525391144a | ||
|
|
cfded56dcd | ||
|
|
024e6aecc3 | ||
|
|
baa679904c | ||
|
|
0c4d7b2112 | ||
|
|
3346b5329a |
+3
-3
@@ -1,3 +1,3 @@
|
|||||||
[submodule "vendor/ruc"]
|
[submodule "vendor/inferno"]
|
||||||
path = vendor/ruc
|
path = vendor/inferno
|
||||||
url = https://github.com/riyyi/ruc
|
url = https://github.com/riyyi/inferno
|
||||||
|
|||||||
+15
-11
@@ -52,21 +52,28 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Library
|
# Library
|
||||||
|
|
||||||
set(RUC_BUILD_TESTS OFF)
|
set(INFERNO_BUILD_EXAMPLES OFF)
|
||||||
add_subdirectory("vendor/ruc")
|
add_subdirectory("vendor/inferno")
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Application target
|
# Application target
|
||||||
|
|
||||||
# Define source files
|
# Define source files
|
||||||
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
|
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
|
||||||
set(PROJECT_SOURCES ${PROJECT_SOURCES})
|
|
||||||
|
|
||||||
add_executable(${PROJECT} ${PROJECT_SOURCES})
|
add_executable(${PROJECT} ${PROJECT_SOURCES})
|
||||||
target_include_directories(${PROJECT} PRIVATE
|
target_include_directories(${PROJECT} PRIVATE
|
||||||
"src"
|
"src")
|
||||||
"vendor/ruc/src")
|
target_link_libraries(${PROJECT} inferno)
|
||||||
target_link_libraries(${PROJECT} ruc)
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Assets target
|
||||||
|
|
||||||
|
add_custom_target(${PROJECT}-assets
|
||||||
|
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/copy-assets.cmake
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
add_dependencies(${PROJECT} ${PROJECT}-assets)
|
||||||
|
add_dependencies(${PROJECT}-assets inferno-assets) # Copy project assets *AFTER* engine assets!
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Execute target
|
# Execute target
|
||||||
@@ -87,9 +94,6 @@ if (GARBAGE_BUILD_TESTS)
|
|||||||
add_executable(${PROJECT}-unit-test ${TEST_SOURCES})
|
add_executable(${PROJECT}-unit-test ${TEST_SOURCES})
|
||||||
target_include_directories(${PROJECT}-unit-test PRIVATE
|
target_include_directories(${PROJECT}-unit-test PRIVATE
|
||||||
"src"
|
"src"
|
||||||
"test"
|
"test")
|
||||||
"vendor/ruc/src"
|
target_link_libraries(${PROJECT}-unit-test inferno)
|
||||||
"vendor/ruc/test")
|
|
||||||
target_link_libraries(${PROJECT}-unit-test ruc)
|
|
||||||
target_link_libraries(${PROJECT}-unit-test ruc-test)
|
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
+3
-4
@@ -10,14 +10,13 @@ Accurate GameBoy emulator.
|
|||||||
*** Clone
|
*** Clone
|
||||||
|
|
||||||
#+BEGIN_SRC shell-script
|
#+BEGIN_SRC shell-script
|
||||||
$ git clone https://gitlab.com/riyyi/garbage
|
$ git clone https://github.com/riyyi/garbage
|
||||||
$ cd garbage
|
$ cd garbage
|
||||||
$ git submodule init
|
$ git submodule update --init --recursive
|
||||||
$ git submodule update
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
Or
|
Or
|
||||||
#+BEGIN_SRC shell-script
|
#+BEGIN_SRC shell-script
|
||||||
$ git clone --recursive https://gitlab.com/riyyi/garbage
|
$ git clone --recursive https://github.com/riyyi/garbage
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
*** Update
|
*** Update
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"camera": {
|
||||||
|
"name": "Camera",
|
||||||
|
"translate": [ 0.0, 0.0, -1.0 ],
|
||||||
|
"rotate": [ 0.0, 0.0, 0.0 ],
|
||||||
|
"scale": [ 1.0, 1.0, 1.0 ],
|
||||||
|
"type": "orthographic",
|
||||||
|
"zoom-level": 0.45
|
||||||
|
},
|
||||||
|
"quad" : [
|
||||||
|
{
|
||||||
|
"name": "Screen",
|
||||||
|
"scale": [ 1.0, -0.9, 1.0 ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"window": {
|
||||||
|
"fullscreen": "windowed",
|
||||||
|
"height": 432,
|
||||||
|
"title": "GarbAGE",
|
||||||
|
"vsync": true,
|
||||||
|
"width": 480
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
file(COPY ${CMAKE_CURRENT_LIST_DIR}/../assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
+5
-2
@@ -66,12 +66,15 @@ Absolute fixed address, possible values are[[#references][(4)]]: ~0x00~, ~0x08~,
|
|||||||
*** Variables
|
*** Variables
|
||||||
|
|
||||||
| Name | Meaning |
|
| Name | Meaning |
|
||||||
|--------+--------------------|
|
|--------+------------------------|
|
||||||
| length | amount of elements |
|
| length | amount of elements |
|
||||||
| size | amount of bytes |
|
| size | amount of bytes |
|
||||||
|--------+--------------------|
|
|--------+------------------------|
|
||||||
| lhs | left hand side |
|
| lhs | left hand side |
|
||||||
| rhs | right hand side |
|
| rhs | right hand side |
|
||||||
|
|--------+------------------------|
|
||||||
|
| lsb | least significant byte |
|
||||||
|
| msb | most significant byte |
|
||||||
|
|
||||||
** References
|
** References
|
||||||
|
|
||||||
|
|||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Get the full path to this script while handling spaces and symlinks correctly
|
||||||
|
scriptPath="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
|
||||||
|
cd "$scriptPath/.." || exit 1
|
||||||
|
|
||||||
|
opcode_check() {
|
||||||
|
path="$1"
|
||||||
|
file="$(basename "$path")"
|
||||||
|
|
||||||
|
max=255
|
||||||
|
for i in $(seq 1 "$max")
|
||||||
|
do
|
||||||
|
opcode=$(printf "0x%.2x\n" "$i")
|
||||||
|
# Pattern: case 0x??:
|
||||||
|
if ! grep -q "case $opcode:" "$path"; then
|
||||||
|
echo "missing opcode: $opcode in $file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
opcode_check "src/cpu.cpp"
|
||||||
|
opcode_check "src/cpu-prefix.cpp"
|
||||||
+15
-16
@@ -19,7 +19,7 @@ void CPU::prefix()
|
|||||||
|
|
||||||
// Read next opcode
|
// Read next opcode
|
||||||
uint8_t opcode = read(m_pc);
|
uint8_t opcode = read(m_pc);
|
||||||
print("running opcode: {:#04x} @ ({:#06x})\n", opcode, m_pc);
|
// print("running opcode: {:#04x} @ ({:#06x})\n", opcode, m_pc);
|
||||||
if (opcode <= 0x07) {
|
if (opcode <= 0x07) {
|
||||||
rlc();
|
rlc();
|
||||||
}
|
}
|
||||||
@@ -365,11 +365,10 @@ void CPU::swap()
|
|||||||
m_wait_cycles += 8;
|
m_wait_cycles += 8;
|
||||||
|
|
||||||
// Swap upper 4 bits in register r8 with lower 4 bits
|
// Swap upper 4 bits in register r8 with lower 4 bits
|
||||||
register_ = register_ & 0xff;
|
register_ = ((register_ >> 4) | (register_ << 4)) & 0xff;
|
||||||
register_ = (register_ >> 4) | (register_ << 4);
|
|
||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
m_cf = 0;
|
m_cf = 0;
|
||||||
@@ -412,7 +411,7 @@ void CPU::rl()
|
|||||||
register_ = (old_carry | (register_ << 1)) & 0xff;
|
register_ = (old_carry | (register_ << 1)) & 0xff;
|
||||||
|
|
||||||
// Set other flags
|
// Set other flags
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
};
|
};
|
||||||
@@ -452,7 +451,7 @@ void CPU::rlc()
|
|||||||
register_ = ((register_ >> 7) | (register_ << 1)) & 0xff;
|
register_ = ((register_ >> 7) | (register_ << 1)) & 0xff;
|
||||||
|
|
||||||
// Set other flags
|
// Set other flags
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
};
|
};
|
||||||
@@ -488,13 +487,13 @@ void CPU::rr()
|
|||||||
|
|
||||||
// Copy bit 0 into carry flag
|
// Copy bit 0 into carry flag
|
||||||
uint32_t old_carry = m_cf != 0;
|
uint32_t old_carry = m_cf != 0;
|
||||||
m_cf = (register_ & 0x01) == 0x01;
|
m_cf = (register_ & 0x1) == 0x1;
|
||||||
|
|
||||||
// Rotate register r8 right through carry
|
// Rotate register r8 right through carry
|
||||||
register_ = ((register_ >> 1) | (old_carry << 7)) & 0xff;
|
register_ = ((register_ >> 1) | (old_carry << 7)) & 0xff;
|
||||||
|
|
||||||
// Set other flags
|
// Set other flags
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
};
|
};
|
||||||
@@ -534,7 +533,7 @@ void CPU::rrc()
|
|||||||
register_ = ((register_ >> 1) | (register_ << 7)) & 0xff;
|
register_ = ((register_ >> 1) | (register_ << 7)) & 0xff;
|
||||||
|
|
||||||
// Set other flags
|
// Set other flags
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
};
|
};
|
||||||
@@ -575,13 +574,13 @@ void CPU::sla()
|
|||||||
// r8
|
// r8
|
||||||
|
|
||||||
// Copy bit 7 into carry flag
|
// Copy bit 7 into carry flag
|
||||||
m_cf = (m_a & 0x80) == 0x80;
|
m_cf = (register_ & 0x80) == 0x80;
|
||||||
|
|
||||||
// Shift Left Arithmetically register r8
|
// Shift Left Arithmetically register r8
|
||||||
register_ = (register_ << 1) & 0xff;
|
register_ = (register_ << 1) & 0xff;
|
||||||
|
|
||||||
// Set other flags
|
// Set other flags
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
};
|
};
|
||||||
@@ -623,13 +622,13 @@ void CPU::sra()
|
|||||||
// └──┘
|
// └──┘
|
||||||
|
|
||||||
// Copy bit 0 into carry flag
|
// Copy bit 0 into carry flag
|
||||||
m_cf = (m_a & 0x01) == 0x01;
|
m_cf = (register_ & 0x1) == 0x1;
|
||||||
|
|
||||||
// Shift Right Arithmatically register r8
|
// Shift Right Arithmatically register r8
|
||||||
register_ = (register_ >> 1) | (register_ & 0x80); // Note: bit 7 remains
|
register_ = (register_ >> 1) | (register_ & 0x80); // Note: bit 7 remains
|
||||||
|
|
||||||
// Set other flags
|
// Set other flags
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
};
|
};
|
||||||
@@ -670,13 +669,13 @@ void CPU::srl()
|
|||||||
// r8
|
// r8
|
||||||
|
|
||||||
// Copy bit 0 into carry flag
|
// Copy bit 0 into carry flag
|
||||||
m_cf = (m_a & 0x01) == 0x01;
|
m_cf = (register_ & 0x1) == 0x1;
|
||||||
|
|
||||||
// Shift Right Locically register r8
|
// Shift Right Locically register r8
|
||||||
register_ = (register_ >> 1) & 0x7f; // Note: bit 7 is set to 0
|
register_ = (register_ >> 1) & 0x7f; // Note: bit 7 is set to 0
|
||||||
|
|
||||||
// Set other flags
|
// Set other flags
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
};
|
};
|
||||||
@@ -692,7 +691,7 @@ void CPU::srl()
|
|||||||
case 0x3e: /* SRL (HL) */ {
|
case 0x3e: /* SRL (HL) */ {
|
||||||
m_wait_cycles += 8; // + 8 = 16 total
|
m_wait_cycles += 8; // + 8 = 16 total
|
||||||
|
|
||||||
// Rotate the byte pointed to by HL right
|
// Shift right logically the byte pointed to by HL
|
||||||
uint32_t data = read(hl());
|
uint32_t data = read(hl());
|
||||||
shift_right_logically(data);
|
shift_right_logically(data);
|
||||||
write(hl(), data);
|
write(hl(), data);
|
||||||
|
|||||||
+399
-117
@@ -12,6 +12,7 @@
|
|||||||
#include "ruc/format/color.h"
|
#include "ruc/format/color.h"
|
||||||
#include "ruc/format/print.h"
|
#include "ruc/format/print.h"
|
||||||
#include "ruc/meta/assert.h"
|
#include "ruc/meta/assert.h"
|
||||||
|
#include "ruc/meta/core.h"
|
||||||
|
|
||||||
CPU::CPU(uint32_t frequency)
|
CPU::CPU(uint32_t frequency)
|
||||||
: ProcessingUnit(frequency)
|
: ProcessingUnit(frequency)
|
||||||
@@ -55,18 +56,70 @@ CPU::~CPU()
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void CPU::handleInterrupt(uint32_t interrupt_flag, uint8_t interrupt_source, uint8_t address)
|
||||||
|
{
|
||||||
|
// Clear interrupt
|
||||||
|
m_ime = 0;
|
||||||
|
Emu::the().writeMemory(0xff0f, interrupt_flag & (~interrupt_source));
|
||||||
|
|
||||||
|
// Call
|
||||||
|
|
||||||
|
m_wait_cycles += 20;
|
||||||
|
|
||||||
|
// Push address of the program counter on the stack, such that RET can pop it later
|
||||||
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
|
write(m_sp, m_pc >> 8); // msb(PC)
|
||||||
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
|
write(m_sp, m_pc & 0xff); // lsb(PC)
|
||||||
|
|
||||||
|
// Jump to address
|
||||||
|
m_pc = address;
|
||||||
|
}
|
||||||
|
|
||||||
void CPU::update()
|
void CPU::update()
|
||||||
{
|
{
|
||||||
|
// -------------------------------------
|
||||||
|
// Interrupt Service Routine
|
||||||
|
|
||||||
|
bool effective_ime = m_ime;
|
||||||
|
|
||||||
|
// IME only becomes active after the instruction following EI
|
||||||
|
if (m_should_enable_ime) {
|
||||||
|
m_ime = 1;
|
||||||
|
m_should_enable_ime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effective_ime) {
|
||||||
|
// Get the 5 lower bits of the IE (interrupt enable) address
|
||||||
|
uint32_t interrupt_enabled = Emu::the().readMemory(0xffff) & 0x1f;
|
||||||
|
// Get the 5 lower bits of the IF (interrupt flag) address
|
||||||
|
uint32_t interrupt_flag = Emu::the().readMemory(0xff0f) & 0x1f;
|
||||||
|
|
||||||
|
uint32_t interrupt = interrupt_enabled & interrupt_flag;
|
||||||
|
for (uint8_t i = 0; i < 5; ++i) {
|
||||||
|
if (interrupt & BIT(i)) {
|
||||||
|
handleInterrupt(interrupt_flag, BIT(i), i * 8 + 0x40);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
// Run opcodes
|
||||||
|
|
||||||
m_wait_cycles--;
|
m_wait_cycles--;
|
||||||
|
// TODO: convert to early-return
|
||||||
if (m_wait_cycles <= 0) {
|
if (m_wait_cycles <= 0) {
|
||||||
|
|
||||||
// print(ruc::format::Emphasis::Underline | ruc::format::Emphasis::Bold | fg(ruc::format::TerminalColor::Blue), "{:#06x}\n", *this);
|
// print(ruc::format::Emphasis::Underline | ruc::format::Emphasis::Bold | fg(ruc::format::TerminalColor::Blue), "{:#06x}\n", *this);
|
||||||
|
|
||||||
// Read next opcode
|
// Read next opcode
|
||||||
uint8_t opcode = read(m_pc);
|
uint8_t opcode = read(m_pc);
|
||||||
print("running opcode: {:#04x} @ ({:#06x})\n", opcode, m_pc);
|
// print("running opcode: {:#04x} @ ({:#06x})\n", opcode, m_pc);
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
|
||||||
|
case 0x10: /* TODO */ m_pc += 2; break;
|
||||||
|
|
||||||
case 0x00: nop(); break;
|
case 0x00: nop(); break;
|
||||||
case 0x01: ldi16(); break;
|
case 0x01: ldi16(); break;
|
||||||
case 0x02: ldr8(); break;
|
case 0x02: ldr8(); break;
|
||||||
@@ -105,6 +158,7 @@ void CPU::update()
|
|||||||
case 0x24: inc8(); break;
|
case 0x24: inc8(); break;
|
||||||
case 0x25: dec8(); break;
|
case 0x25: dec8(); break;
|
||||||
case 0x26: ldi8(); break;
|
case 0x26: ldi8(); break;
|
||||||
|
case 0x27: daa(); break;
|
||||||
case 0x28: jrs8(); break;
|
case 0x28: jrs8(); break;
|
||||||
case 0x29: addr16(); break;
|
case 0x29: addr16(); break;
|
||||||
case 0x2a: lda8(); break;
|
case 0x2a: lda8(); break;
|
||||||
@@ -123,7 +177,7 @@ void CPU::update()
|
|||||||
case 0x37: misc(); break;
|
case 0x37: misc(); break;
|
||||||
case 0x38: jrs8(); break;
|
case 0x38: jrs8(); break;
|
||||||
case 0x39: addr16(); break;
|
case 0x39: addr16(); break;
|
||||||
case 0x3a: ldr8(); break;
|
case 0x3a: lda8(); break;
|
||||||
case 0x3b: dec16(); break;
|
case 0x3b: dec16(); break;
|
||||||
case 0x3c: inc8(); break;
|
case 0x3c: inc8(); break;
|
||||||
case 0x3d: dec8(); break;
|
case 0x3d: dec8(); break;
|
||||||
@@ -233,7 +287,21 @@ void CPU::update()
|
|||||||
case 0xa6: and8(); break;
|
case 0xa6: and8(); break;
|
||||||
case 0xa7: and8(); break;
|
case 0xa7: and8(); break;
|
||||||
case 0xa8: xor8(); break;
|
case 0xa8: xor8(); break;
|
||||||
|
case 0xa9: xor8(); break;
|
||||||
|
case 0xaa: xor8(); break;
|
||||||
|
case 0xab: xor8(); break;
|
||||||
|
case 0xac: xor8(); break;
|
||||||
|
case 0xad: xor8(); break;
|
||||||
|
case 0xae: xor8(); break;
|
||||||
case 0xaf: xor8(); break;
|
case 0xaf: xor8(); break;
|
||||||
|
case 0xb0: or8(); break;
|
||||||
|
case 0xb1: or8(); break;
|
||||||
|
case 0xb2: or8(); break;
|
||||||
|
case 0xb3: or8(); break;
|
||||||
|
case 0xb4: or8(); break;
|
||||||
|
case 0xb5: or8(); break;
|
||||||
|
case 0xb6: or8(); break;
|
||||||
|
case 0xb7: or8(); break;
|
||||||
case 0xb8: cp(); break;
|
case 0xb8: cp(); break;
|
||||||
case 0xb9: cp(); break;
|
case 0xb9: cp(); break;
|
||||||
case 0xba: cp(); break;
|
case 0xba: cp(); break;
|
||||||
@@ -242,42 +310,57 @@ void CPU::update()
|
|||||||
case 0xbd: cp(); break;
|
case 0xbd: cp(); break;
|
||||||
case 0xbe: cp(); break;
|
case 0xbe: cp(); break;
|
||||||
case 0xbf: cp(); break;
|
case 0xbf: cp(); break;
|
||||||
|
case 0xc0: ret(); break;
|
||||||
case 0xc1: pop(); break;
|
case 0xc1: pop(); break;
|
||||||
|
case 0xc2: jp16(); break;
|
||||||
case 0xc3: jp16(); break;
|
case 0xc3: jp16(); break;
|
||||||
case 0xc4: call(); break;
|
case 0xc4: call(); break;
|
||||||
case 0xc5: push(); break;
|
case 0xc5: push(); break;
|
||||||
case 0xc6: add8(); break;
|
case 0xc6: add8(); break;
|
||||||
case 0xc7: rst(); break;
|
case 0xc7: rst(); break;
|
||||||
|
case 0xc8: ret(); break;
|
||||||
|
case 0xc9: ret(); break;
|
||||||
|
case 0xca: jp16(); break;
|
||||||
case 0xcb: prefix(); break;
|
case 0xcb: prefix(); break;
|
||||||
case 0xcc: call(); break;
|
case 0xcc: call(); break;
|
||||||
case 0xcd: call(); break;
|
case 0xcd: call(); break;
|
||||||
case 0xce: adc8(); break;
|
case 0xce: adc8(); break;
|
||||||
case 0xcf: rst(); break;
|
case 0xcf: rst(); break;
|
||||||
|
case 0xd0: ret(); break;
|
||||||
case 0xd1: pop(); break;
|
case 0xd1: pop(); break;
|
||||||
|
case 0xd2: jp16(); break;
|
||||||
case 0xd4: call(); break;
|
case 0xd4: call(); break;
|
||||||
case 0xd5: push(); break;
|
case 0xd5: push(); break;
|
||||||
case 0xd6: sub8(); break;
|
case 0xd6: sub8(); break;
|
||||||
case 0xd7: rst(); break;
|
case 0xd7: rst(); break;
|
||||||
|
case 0xd8: ret(); break;
|
||||||
|
case 0xd9: ret(); break;
|
||||||
|
case 0xda: jp16(); break;
|
||||||
case 0xdc: call(); break;
|
case 0xdc: call(); break;
|
||||||
case 0xde: sbc8(); break;
|
case 0xde: sbc8(); break;
|
||||||
case 0xdf: rst(); break;
|
case 0xdf: rst(); break;
|
||||||
case 0xe0: ldffi8(); break;
|
case 0xe0: ldff8(); break;
|
||||||
case 0xe1: pop(); break;
|
case 0xe1: pop(); break;
|
||||||
case 0xe2: ldr8(); break;
|
case 0xe2: ldff8(); break;
|
||||||
case 0xe5: push(); break;
|
case 0xe5: push(); break;
|
||||||
case 0xe6: and8(); break;
|
case 0xe6: and8(); break;
|
||||||
case 0xe7: rst(); break;
|
case 0xe7: rst(); break;
|
||||||
case 0xe8: adds8(); break;
|
case 0xe8: adds8(); break;
|
||||||
|
case 0xe9: jp16(); break;
|
||||||
case 0xea: ldr8(); break;
|
case 0xea: ldr8(); break;
|
||||||
|
case 0xee: xor8(); break;
|
||||||
case 0xef: rst(); break;
|
case 0xef: rst(); break;
|
||||||
case 0xf0: ldffi8(); break;
|
case 0xf0: ldff8(); break;
|
||||||
case 0xf1: pop(); break;
|
case 0xf1: pop(); break;
|
||||||
case 0xf2: lda8(); break;
|
case 0xf2: ldff8(); break;
|
||||||
|
case 0xf3: misc(); break;
|
||||||
case 0xf5: push(); break;
|
case 0xf5: push(); break;
|
||||||
|
case 0xf6: or8(); break;
|
||||||
case 0xf7: rst(); 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 0xfb: misc(); break;
|
||||||
case 0xfe: cp(); break;
|
case 0xfe: cp(); break;
|
||||||
case 0xff: rst(); break;
|
case 0xff: rst(); break;
|
||||||
|
|
||||||
@@ -310,16 +393,18 @@ void CPU::adc8()
|
|||||||
// ADC A,r8, flags: Z 0 H C
|
// ADC A,r8, flags: Z 0 H C
|
||||||
m_wait_cycles += 4;
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
uint32_t old_carry = m_cf != 0;
|
||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = isCarry(m_a, register_, 0x10);
|
m_hf = isCarry(0xf, m_a, register_, old_carry);
|
||||||
m_cf = isCarry(m_a, register_, 0x100);
|
m_cf = isCarry(0xff, m_a, register_, old_carry);
|
||||||
|
|
||||||
// Add the value in r8 plus the carry flag to A
|
// Add the value in r8 plus the carry flag to A
|
||||||
m_a = (m_a + register_ + (m_cf) ? 0x1 : 0) & 0xff;
|
m_a = (m_a + register_ + old_carry) & 0xff;
|
||||||
|
|
||||||
// Zero flag
|
// Zero flag
|
||||||
m_zf = m_a == 0;
|
m_zf = (m_a == 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
@@ -356,8 +441,8 @@ void CPU::add8()
|
|||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = isCarry(m_a, register_, 0x10);
|
m_hf = isCarry(0xf, m_a, register_);
|
||||||
m_cf = isCarry(m_a, register_, 0x100);
|
m_cf = isCarry(0xff, m_a, register_);
|
||||||
|
|
||||||
// Add the value in r8 to A
|
// Add the value in r8 to A
|
||||||
m_a = (m_a + register_) & 0xff;
|
m_a = (m_a + register_) & 0xff;
|
||||||
@@ -451,9 +536,8 @@ void CPU::cp()
|
|||||||
// Zero flag
|
// Zero flag
|
||||||
m_zf = ((m_a - register_) & 0xff) == 0;
|
m_zf = ((m_a - register_) & 0xff) == 0;
|
||||||
m_nf = 1;
|
m_nf = 1;
|
||||||
// Note: for negation between two positive numbers the carry result is flipped!
|
m_hf = isCarrySubtraction(0xf, m_a, register_);
|
||||||
m_hf = !isCarry(m_a, register_, 0x10);
|
m_cf = isCarrySubtraction(0xff, m_a, register_);
|
||||||
m_cf = !isCarry(m_a, register_, 0x100);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
@@ -486,6 +570,64 @@ void CPU::cp()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPU::daa()
|
||||||
|
{
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0x27: { // DAA, flags: Z - 0 C
|
||||||
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
// Decimal Adjust Accumulator to get a correct Binary Coded Decimal
|
||||||
|
// (BCD) representation after an arithmetic instruction
|
||||||
|
|
||||||
|
// BCD is a way of representing decimal quantities using each nibble (4
|
||||||
|
// bits) of a byte (a byte, since it is 8 bits has two nibbles) to store
|
||||||
|
// a decimal digit.
|
||||||
|
// Example: 64 split on each number gives a 6 and a 4, so 0110 0100 as a BCD
|
||||||
|
|
||||||
|
// Step 1: Check upper nibble of the BCD stored in A.
|
||||||
|
// If it is greater than decimal 9 or the carry flag is set
|
||||||
|
// (meaning that the upper nibble value is > 15),
|
||||||
|
// add decimal 6 to the upper nibble to make it wrap around and set carry flag to 1
|
||||||
|
|
||||||
|
// Step 2: Check lower nibble of the BCD stored in A.
|
||||||
|
// If it is greater than decimal 9 or half carry flag is set
|
||||||
|
// (meaning that the lower nibble value is > 15),
|
||||||
|
// add decimal 6 to the lower nibble to make it wrap around
|
||||||
|
|
||||||
|
if (!m_nf) {
|
||||||
|
if (m_cf || m_a > 0x99) {
|
||||||
|
m_a += 0x60;
|
||||||
|
// Carry flag
|
||||||
|
m_cf = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_hf || (m_a & 0xf) > 0x9) {
|
||||||
|
m_a += 0x6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (m_cf) {
|
||||||
|
m_a -= 0x60;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_hf) {
|
||||||
|
m_a -= 0x6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_a = m_a & 0xff;
|
||||||
|
|
||||||
|
// Set flags
|
||||||
|
m_zf = (m_a == 0);
|
||||||
|
m_hf = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CPU::dec8()
|
void CPU::dec8()
|
||||||
{
|
{
|
||||||
auto decrement = [this](uint32_t& register_) -> void {
|
auto decrement = [this](uint32_t& register_) -> void {
|
||||||
@@ -494,13 +636,13 @@ void CPU::dec8()
|
|||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_nf = 1;
|
m_nf = 1;
|
||||||
m_hf = isCarry(register_, -1, 0x10);
|
m_hf = isCarrySubtraction(0xf, register_, 1);
|
||||||
|
|
||||||
// Decrement value in register r8 by 1
|
// Decrement value in register r8 by 1
|
||||||
register_ = (register_ - 1) & 0xff;
|
register_ = (register_ - 1) & 0xff;
|
||||||
|
|
||||||
// Zero flag
|
// Zero flag
|
||||||
m_zf = register_ == 0;
|
m_zf = (register_ == 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
@@ -534,7 +676,7 @@ void CPU::inc8()
|
|||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = isCarry(register_, 1, 0x10);
|
m_hf = isCarry(0xf, register_, 1);
|
||||||
|
|
||||||
// Increment value in register r8 by 1
|
// Increment value in register r8 by 1
|
||||||
register_ = (register_ + 1) & 0xff;
|
register_ = (register_ + 1) & 0xff;
|
||||||
@@ -566,25 +708,68 @@ void CPU::inc8()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPU::or8()
|
||||||
|
{
|
||||||
|
auto bitwise_or = [this](uint32_t register_) {
|
||||||
|
// OR r8, flags: Z 0 0 0
|
||||||
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
// Set flags
|
||||||
|
m_nf = m_hf = m_cf = 0;
|
||||||
|
|
||||||
|
// Store into A the bitwise OR of the value in r8 and A
|
||||||
|
m_a = m_a | register_;
|
||||||
|
|
||||||
|
// Zero flag
|
||||||
|
m_zf = m_a == 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0xb0: /* OR A,B */ bitwise_or(m_b); break;
|
||||||
|
case 0xb1: /* OR A,C */ bitwise_or(m_c); break;
|
||||||
|
case 0xb2: /* OR A,D */ bitwise_or(m_d); break;
|
||||||
|
case 0xb3: /* OR A,E */ bitwise_or(m_e); break;
|
||||||
|
case 0xb4: /* OR A,H */ bitwise_or(m_h); break;
|
||||||
|
case 0xb5: /* OR A,L */ bitwise_or(m_l); break;
|
||||||
|
case 0xb6: /* OR A,(HL) */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
|
|
||||||
|
// Store into A the bitwise OR of the byte pointed to by HL and A
|
||||||
|
bitwise_or(read(hl()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xb7: /* OR A,A */ bitwise_or(m_a); break;
|
||||||
|
case 0xf6: /* OR A,i8 */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
|
|
||||||
|
// Store into A the bitwise OR of i8 and A
|
||||||
|
bitwise_or(pcRead());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CPU::sbc8()
|
void CPU::sbc8()
|
||||||
{
|
{
|
||||||
auto subtract_carry = [this](uint32_t register_) -> void {
|
auto subtract_carry = [this](uint32_t register_) -> void {
|
||||||
// SBC A,r8, flags: Z 1 H C
|
// SBC A,r8, flags: Z 1 H C
|
||||||
m_wait_cycles += 4;
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
uint32_t old_carry = m_cf;
|
uint32_t old_carry = m_cf != 0;
|
||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_nf = 1;
|
m_nf = 1;
|
||||||
// Note: for negation between two positive numbers the carry result is flipped!
|
m_hf = isCarrySubtraction(0xf, m_a, register_, old_carry);
|
||||||
m_hf = !isCarry(m_a, register_, 0x10);
|
m_cf = isCarrySubtraction(0xff, m_a, register_, old_carry);
|
||||||
m_cf = !isCarry(m_a, (register_ + (old_carry) ? 1 : 0), 0x100);
|
|
||||||
|
|
||||||
// Subtract the value in r8 and the carry flag from A
|
// Subtract the value in r8 and the carry flag from A
|
||||||
m_a = (m_a - register_ - (old_carry) ? 1 : 0) & 0xff;
|
m_a = (m_a - register_ - old_carry) & 0xff;
|
||||||
|
|
||||||
// Zero flag
|
// Zero flag
|
||||||
m_zf = m_a == 0;
|
m_zf = (m_a == 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
@@ -623,9 +808,8 @@ void CPU::sub8()
|
|||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_nf = 1;
|
m_nf = 1;
|
||||||
// Note: for negation between two positive numbers the carry result is flipped!
|
m_hf = isCarrySubtraction(0xf, m_a, register_);
|
||||||
m_hf = !isCarry(m_a, register_, 0x10);
|
m_cf = isCarrySubtraction(0xff, m_a, register_);
|
||||||
m_cf = !isCarry(m_a, register_, 0x100);
|
|
||||||
|
|
||||||
// Subtract the value in r8 from A
|
// Subtract the value in r8 from A
|
||||||
m_a = (m_a - register_) & 0xff;
|
m_a = (m_a - register_) & 0xff;
|
||||||
@@ -664,18 +848,43 @@ void CPU::sub8()
|
|||||||
|
|
||||||
void CPU::xor8()
|
void CPU::xor8()
|
||||||
{
|
{
|
||||||
|
auto bitwise_xor = [this](uint32_t register_) {
|
||||||
|
// XOR r8, flags: Z 0 0 0
|
||||||
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
// Set flags
|
||||||
|
m_nf = m_hf = m_cf = 0;
|
||||||
|
|
||||||
|
// Bitwise XOR between the value in r8 and A
|
||||||
|
m_a = m_a ^ register_;
|
||||||
|
|
||||||
|
// Zero flag
|
||||||
|
m_zf = m_a == 0;
|
||||||
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0xa8: // XOR B, flags: Z 0 0 0
|
case 0xa8: /* XOR A,B */ bitwise_xor(m_b); break;
|
||||||
m_nf = m_hf = m_cf = 0;
|
case 0xa9: /* XOR A,C */ bitwise_xor(m_c); break;
|
||||||
m_a ^= m_b;
|
case 0xaa: /* XOR A,D */ bitwise_xor(m_d); break;
|
||||||
m_zf = m_a == 0;
|
case 0xab: /* XOR A,E */ bitwise_xor(m_e); break;
|
||||||
|
case 0xac: /* XOR A,H */ bitwise_xor(m_h); break;
|
||||||
|
case 0xad: /* XOR A,L */ bitwise_xor(m_l); break;
|
||||||
|
case 0xae: /* XOR A,(HL) */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
|
|
||||||
|
// Bitwise XOR between the byte pointed to by HL and A
|
||||||
|
bitwise_xor(read(hl()));
|
||||||
break;
|
break;
|
||||||
case 0xaf: // XOR A, flags: 1 0 0 0
|
}
|
||||||
// A ^ A will always be 0
|
case 0xaf: /* XOR A,A */ bitwise_xor(m_a); break;
|
||||||
m_a = m_nf = m_hf = m_cf = 0;
|
case 0xee: /* XOR A,i8 */ {
|
||||||
m_zf = 1;
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
|
|
||||||
|
// Bitwise XOR between the value in i8 and A
|
||||||
|
bitwise_xor(pcRead());
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -688,7 +897,7 @@ void CPU::lda8()
|
|||||||
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;
|
m_wait_cycles += 8;
|
||||||
|
|
||||||
// Put value at address in HL into A
|
// Load value into register A from the byte pointed by HL
|
||||||
uint32_t address = hl();
|
uint32_t address = hl();
|
||||||
m_a = read(address);
|
m_a = read(address);
|
||||||
|
|
||||||
@@ -701,23 +910,21 @@ void CPU::lda8()
|
|||||||
case 0x3a: { // LD A,(HL-) == LD A,(HLD) == LDD A,(HL)
|
case 0x3a: { // LD A,(HL-) == LD A,(HLD) == LDD A,(HL)
|
||||||
m_wait_cycles += 8;
|
m_wait_cycles += 8;
|
||||||
|
|
||||||
// Put value at address in HL into A
|
// Load value into register A from the byte pointed by HL
|
||||||
uint32_t address = hl();
|
uint32_t address = hl();
|
||||||
m_a = read(address);
|
m_a = read(address);
|
||||||
|
|
||||||
// Decrement HL
|
// Decrement HL
|
||||||
address = (address + 1) & 0xffff;
|
address = (address - 1) & 0xffff;
|
||||||
m_l = address & 0xff;
|
m_l = address & 0xff;
|
||||||
m_h = address >> 8;
|
m_h = address >> 8;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xf2: // LD A,(C)
|
|
||||||
m_wait_cycles += 8;
|
|
||||||
m_a = read(m_c);
|
|
||||||
break;
|
|
||||||
case 0xfa: // LD A,a16
|
case 0xfa: // LD A,a16
|
||||||
m_wait_cycles += 16;
|
m_wait_cycles += 16;
|
||||||
m_a = pcRead16();
|
|
||||||
|
// Load value into register A from the byte pointed to by register r16
|
||||||
|
m_a = read(pcRead16());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
@@ -732,8 +939,8 @@ void CPU::addr16()
|
|||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = isCarry(hl(), register_, 0x1000);
|
m_hf = isCarry(0xfff, hl(), register_);
|
||||||
m_cf = isCarry(hl(), register_, 0x10000);
|
m_cf = isCarry(0xffff, hl(), register_);
|
||||||
|
|
||||||
// Add the value in r16 to HL
|
// Add the value in r16 to HL
|
||||||
uint32_t data = (hl() + register_) & 0xffff;
|
uint32_t data = (hl() + register_) & 0xffff;
|
||||||
@@ -764,8 +971,8 @@ void CPU::adds8()
|
|||||||
// Set flags
|
// Set flags
|
||||||
m_zf = 0;
|
m_zf = 0;
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = isCarry(m_sp, signed_data, 0x10);
|
m_hf = isCarry(0xf, m_sp, signed_data);
|
||||||
m_cf = isCarry(m_sp, signed_data, 0x100);
|
m_cf = isCarry(0xff, m_sp, signed_data);
|
||||||
|
|
||||||
// Add the signed value s8 to SP
|
// Add the signed value s8 to SP
|
||||||
m_sp = m_sp + signed_data;
|
m_sp = m_sp + signed_data;
|
||||||
@@ -797,8 +1004,8 @@ void CPU::inc16()
|
|||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0x03: /* INC BC */ setBC(bc() + 1); break;
|
case 0x03: /* INC BC */ setBC(bc() + 1); break;
|
||||||
case 0x13: /* INC DE */ setDE(de() + 1); break;
|
case 0x13: /* INC DE */ setDE(de() + 1); break;
|
||||||
case 0x23: /* INC HL */ setHL(de() + 1); break;
|
case 0x23: /* INC HL */ setHL(hl() + 1); break;
|
||||||
case 0x24: /* INC SP */ m_sp = (m_sp + 1) & 0xffff; break;
|
case 0x33: /* INC SP */ m_sp = (m_sp + 1) & 0xffff; break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -834,24 +1041,28 @@ void CPU::ldr8()
|
|||||||
{
|
{
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0x02: // LD (BC),A
|
case 0x02: /* LD (BC),A */ {
|
||||||
m_wait_cycles += 4;
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
write(bc(), m_a);
|
write(bc(), m_a);
|
||||||
break;
|
break;
|
||||||
case 0x0a: // LD A,(BC)
|
}
|
||||||
m_wait_cycles += 4;
|
case 0x0a: /* LD A,(BC) */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
m_a = read(bc());
|
m_a = read(bc());
|
||||||
break;
|
break;
|
||||||
case 0x12: // LD (DE),A
|
}
|
||||||
m_wait_cycles += 4;
|
case 0x12: /* LD (DE),A */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
write(de(), m_a);
|
write(de(), m_a);
|
||||||
break;
|
break;
|
||||||
case 0x1a: // LD A,(DE)
|
}
|
||||||
m_wait_cycles += 4;
|
case 0x1a: /* LD A,(DE) */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
m_a = read(de());
|
m_a = read(de());
|
||||||
break;
|
break;
|
||||||
case 0x22: { // LD (HL+),A == LD (HLI),A == LDI (HL),A
|
}
|
||||||
m_wait_cycles += 4;
|
case 0x22: /* LD (HL+),A == LD (HLI),A == LDI (HL),A */ {
|
||||||
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
|
|
||||||
// Put A into memory address in HL
|
// Put A into memory address in HL
|
||||||
uint32_t address = hl();
|
uint32_t address = hl();
|
||||||
@@ -863,8 +1074,8 @@ void CPU::ldr8()
|
|||||||
m_h = address >> 8;
|
m_h = address >> 8;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x32: { // LD (HL-),A == LD (HLD),A == LDD (HL),A
|
case 0x32: /* LD (HL-),A == LD (HLD),A == LDD (HL),A */ {
|
||||||
m_wait_cycles += 4;
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
|
|
||||||
// Put A into memory address in HL
|
// Put A into memory address in HL
|
||||||
uint32_t address = hl();
|
uint32_t address = hl();
|
||||||
@@ -944,7 +1155,7 @@ void CPU::ldr8()
|
|||||||
case 0x6d: /* LD L,L m_l = m_l; */ break;
|
case 0x6d: /* LD L,L m_l = m_l; */ break;
|
||||||
case 0x6e: /* LD L,(HL) */ {
|
case 0x6e: /* LD L,(HL) */ {
|
||||||
m_wait_cycles += 4; // + 4 = 8 total
|
m_wait_cycles += 4; // + 4 = 8 total
|
||||||
m_e = read(hl());
|
m_l = read(hl());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x6f: /* LD L,A */ m_l = m_a; break;
|
case 0x6f: /* LD L,A */ m_l = m_a; break;
|
||||||
@@ -995,13 +1206,10 @@ void CPU::ldr8()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x7f: /* LD A,A m_a = m_a; */ break;
|
case 0x7f: /* LD A,A m_a = m_a; */ break;
|
||||||
case 0xe2: /* LD (C),A */ {
|
|
||||||
m_wait_cycles += 4; // + 4 = 8 total
|
|
||||||
write(m_c, m_a);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0xea: /* LD a16,A */ {
|
case 0xea: /* LD a16,A */ {
|
||||||
m_wait_cycles += 12; // + 4 = 16 total
|
m_wait_cycles += 12; // + 4 = 16 total
|
||||||
|
|
||||||
|
// Store value in register A into the byte at address a16
|
||||||
write(pcRead16(), m_a);
|
write(pcRead16(), m_a);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1033,7 +1241,7 @@ void CPU::ra()
|
|||||||
m_cf = (m_a & 0x80) == 0x80;
|
m_cf = (m_a & 0x80) == 0x80;
|
||||||
|
|
||||||
// Rotate register A left
|
// Rotate register A left
|
||||||
m_a = (m_a >> 7) | (m_a << 1);
|
m_a = ((m_a >> 7) | (m_a << 1)) & 0xff;
|
||||||
break;
|
break;
|
||||||
case 0x0f: // RRCA
|
case 0x0f: // RRCA
|
||||||
|
|
||||||
@@ -1048,7 +1256,7 @@ void CPU::ra()
|
|||||||
m_cf = (m_a & 0x1) == 0x1;
|
m_cf = (m_a & 0x1) == 0x1;
|
||||||
|
|
||||||
// Rotate register A right
|
// Rotate register A right
|
||||||
m_a = (m_a >> 1) | (m_a << 7);
|
m_a = ((m_a >> 1) | (m_a << 7)) & 0xff;
|
||||||
break;
|
break;
|
||||||
case 0x17: { // RLA
|
case 0x17: { // RLA
|
||||||
|
|
||||||
@@ -1064,7 +1272,7 @@ void CPU::ra()
|
|||||||
m_cf = (m_a & 0x80) == 0x80;
|
m_cf = (m_a & 0x80) == 0x80;
|
||||||
|
|
||||||
// Rotate register A left through carry
|
// Rotate register A left through carry
|
||||||
m_a = old_carry | (m_a << 1);
|
m_a = (old_carry | (m_a << 1)) & 0xff;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x1f: { // RRA
|
case 0x1f: { // RRA
|
||||||
@@ -1081,7 +1289,7 @@ void CPU::ra()
|
|||||||
m_cf = (m_a & 0x1) == 0x1;
|
m_cf = (m_a & 0x1) == 0x1;
|
||||||
|
|
||||||
// Rotate register A right through carry
|
// Rotate register A right through carry
|
||||||
m_a = (m_a >> 1) | (old_carry << 7);
|
m_a = ((m_a >> 1) | (old_carry << 7)) & 0xff;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -1097,22 +1305,34 @@ void CPU::ra()
|
|||||||
m_hf = 0;
|
m_hf = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::ldffi8()
|
void CPU::ldff8()
|
||||||
{
|
{
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0xe0: // LD ($ff00 + i8),A == LDH (io8),A
|
case 0xe0: // LD (0xff00 + s8),A == LDH (io8),A
|
||||||
m_wait_cycles += 12;
|
m_wait_cycles += 12;
|
||||||
|
|
||||||
// Put value in A into address (0xff00 + next byte in memory)
|
// Put value in A into address (0xff00 + next byte in memory)
|
||||||
ffWrite(pcRead(), m_a);
|
ffWrite(pcRead(), m_a);
|
||||||
break;
|
break;
|
||||||
case 0xf0: // LD A,($ff00 + i8) == LDH A,(io8)
|
case 0xe2: // LD (0xff00 + C),A == LDH (C),A
|
||||||
|
m_wait_cycles += 8;
|
||||||
|
|
||||||
|
// Store value in register A into the byte at address 0xff00 + C
|
||||||
|
ffWrite(m_c, m_a);
|
||||||
|
break;
|
||||||
|
case 0xf0: // LD A,(0xff00 + s8) == LDH A,(io8)
|
||||||
m_wait_cycles += 12;
|
m_wait_cycles += 12;
|
||||||
|
|
||||||
// Put value at address (0xff00 + next byte in memory) into A
|
// Put value at address (0xff00 + next byte in memory) into A
|
||||||
m_a = ffRead(pcRead());
|
m_a = ffRead(pcRead());
|
||||||
break;
|
break;
|
||||||
|
case 0xf2: // LD A,(0xff00 + C) == LDH A,(C)
|
||||||
|
m_wait_cycles += 8;
|
||||||
|
|
||||||
|
// Load value into register A from the byte at address 0xff00 + C
|
||||||
|
m_a = ffRead(m_c);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -1152,7 +1372,9 @@ void CPU::ldr16()
|
|||||||
m_wait_cycles += 20;
|
m_wait_cycles += 20;
|
||||||
|
|
||||||
// Put value of SP into address given by next 2 bytes in memory
|
// Put value of SP into address given by next 2 bytes in memory
|
||||||
write(pcRead16(), m_sp);
|
uint32_t address = pcRead16();
|
||||||
|
write(address, m_sp & 0xff); // lsb(SP)
|
||||||
|
write((address + 1) & 0xffff, m_sp >> 8); // msb(SP)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xf8: { // LD HL,SP + s8 == LDHL SP,s8, flags: 0 0 H C
|
case 0xf8: { // LD HL,SP + s8 == LDHL SP,s8, flags: 0 0 H C
|
||||||
@@ -1161,18 +1383,20 @@ void CPU::ldr16()
|
|||||||
// Put SP + next (signed) byte in memory into HL
|
// Put SP + next (signed) byte in memory into HL
|
||||||
uint32_t signed_data = (pcRead() ^ 0x80) - 0x80;
|
uint32_t signed_data = (pcRead() ^ 0x80) - 0x80;
|
||||||
uint32_t sum = m_sp + signed_data;
|
uint32_t sum = m_sp + signed_data;
|
||||||
m_h = sum >> 8;
|
|
||||||
m_l = sum & 0xff;
|
m_l = sum & 0xff;
|
||||||
|
m_h = sum >> 8;
|
||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
m_zf = 0;
|
m_zf = 0;
|
||||||
m_nf = 0;
|
m_nf = 0;
|
||||||
m_hf = isCarry(m_sp, signed_data, 0x10);
|
m_hf = isCarry(0xf, m_sp, signed_data);
|
||||||
m_cf = isCarry(m_sp, signed_data, 0x100);
|
m_cf = isCarry(0xff, m_sp, signed_data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xf9: { // LD SP,HL
|
case 0xf9: { // LD SP,HL
|
||||||
m_wait_cycles += 8;
|
m_wait_cycles += 8;
|
||||||
|
|
||||||
|
// Load register HL into register SP
|
||||||
m_sp = hl();
|
m_sp = hl();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1188,9 +1412,9 @@ void CPU::pop()
|
|||||||
m_wait_cycles += 12;
|
m_wait_cycles += 12;
|
||||||
|
|
||||||
// Pop register r16 from stack
|
// Pop register r16 from stack
|
||||||
register_low = read(m_sp);
|
register_low = read(m_sp); // lsb(r16)
|
||||||
m_sp = (m_sp + 1) & 0xffff;
|
m_sp = (m_sp + 1) & 0xffff;
|
||||||
register_high = read(m_sp);
|
register_high = read(m_sp); // msb(r16)
|
||||||
m_sp = (m_sp + 1) & 0xffff;
|
m_sp = (m_sp + 1) & 0xffff;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1223,9 +1447,9 @@ void CPU::push()
|
|||||||
|
|
||||||
// Push register r16 into stack
|
// Push register r16 into stack
|
||||||
m_sp = (m_sp - 1) & 0xffff;
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
write(m_sp, register_ >> 8);
|
write(m_sp, register_ >> 8); // msb(r16)
|
||||||
m_sp = (m_sp - 1) & 0xffff;
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
write(m_sp, register_ & 0xff);
|
write(m_sp, register_ & 0xff); // lsb(r16)
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
@@ -1249,23 +1473,25 @@ void CPU::call()
|
|||||||
|
|
||||||
if (!should_call) {
|
if (!should_call) {
|
||||||
m_wait_cycles += 12;
|
m_wait_cycles += 12;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
m_wait_cycles += 24;
|
m_wait_cycles += 24;
|
||||||
|
|
||||||
// Push address of next 2 bytes in memory onto stack
|
// Push address of the instruction after the CALL on the stack,
|
||||||
|
// such that RET can pop it later
|
||||||
m_sp = (m_sp - 1) & 0xffff;
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
write(m_sp, data >> 8);
|
write(m_sp, m_pc >> 8); // msb(PC)
|
||||||
m_sp = (m_sp - 1) & 0xffff;
|
m_sp = (m_sp - 1) & 0xffff;
|
||||||
write(m_sp, data & 0xff);
|
write(m_sp, m_pc & 0xff); // lsb(PC)
|
||||||
|
|
||||||
// Jump to this address
|
// Jump to operand address
|
||||||
m_pc = data;
|
m_pc = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0xc4: /* CALL NZ,i16 */ function_call(!m_nf); break;
|
case 0xc4: /* CALL NZ,i16 */ function_call(!m_zf); break;
|
||||||
case 0xcc: /* CALL Z,i16 */ function_call(m_nf); break;
|
case 0xcc: /* CALL Z,i16 */ function_call(m_zf); break;
|
||||||
case 0xcd: /* CALL i16 */ function_call(true); break;
|
case 0xcd: /* CALL i16 */ function_call(true); break;
|
||||||
case 0xd4: /* CALL NC,i16 */ function_call(!m_cf); break;
|
case 0xd4: /* CALL NC,i16 */ function_call(!m_cf); break;
|
||||||
case 0xdc: /* CALL C,i16 */ function_call(m_cf); break;
|
case 0xdc: /* CALL C,i16 */ function_call(m_cf); break;
|
||||||
@@ -1276,11 +1502,34 @@ void CPU::call()
|
|||||||
|
|
||||||
void CPU::jp16()
|
void CPU::jp16()
|
||||||
{
|
{
|
||||||
|
auto jump = [this](bool should_jump) -> void {
|
||||||
|
// JP cc,i16
|
||||||
|
|
||||||
|
// Note: the operand is read even when the condition is false
|
||||||
|
uint32_t data = pcRead16();
|
||||||
|
|
||||||
|
if (!should_jump) {
|
||||||
|
m_wait_cycles += 12;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_wait_cycles += 16;
|
||||||
|
|
||||||
|
// Jump to address i16 if condition is met; effectively, store i16 into PC
|
||||||
|
m_pc = data;
|
||||||
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0xc3: // JP a16
|
case 0xc2: /* JP NZ,a16 */ jump(!m_zf); break;
|
||||||
m_wait_cycles += 16;
|
case 0xc3: /* JP a16 */ jump(true); break;
|
||||||
m_pc = pcRead16();
|
case 0xca: /* JP Z,a16 */ jump(m_zf); break;
|
||||||
|
case 0xd2: /* JP NC,a16 */ jump(!m_cf); break;
|
||||||
|
case 0xda: /* JP C,a16 */ jump(m_cf); break;
|
||||||
|
case 0xe9: // JP HL
|
||||||
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
// Jump to address in HL; effectively, load PC with value in register HL
|
||||||
|
m_pc = hl();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
@@ -1302,7 +1551,7 @@ void CPU::jrs8()
|
|||||||
m_wait_cycles += 12;
|
m_wait_cycles += 12;
|
||||||
|
|
||||||
// Relative jump by adding s8 to the address of the instruction following the JR
|
// Relative jump by adding s8 to the address of the instruction following the JR
|
||||||
m_pc = m_pc + signed_data;
|
m_pc = (m_pc + signed_data) & 0xffff;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t opcode = pcRead();
|
uint8_t opcode = pcRead();
|
||||||
@@ -1317,6 +1566,43 @@ void CPU::jrs8()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPU::ret()
|
||||||
|
{
|
||||||
|
auto function_return = [this](bool should_call) -> void {
|
||||||
|
// RET cc,i16
|
||||||
|
|
||||||
|
if (!should_call) {
|
||||||
|
m_wait_cycles += 8;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_wait_cycles += 20;
|
||||||
|
|
||||||
|
// Return from subroutine if condition cc is met,
|
||||||
|
// this is basically a POP PC (if such an instruction existed)
|
||||||
|
m_pc = read(m_sp); // lsb(SP)
|
||||||
|
m_sp = (m_sp + 1) & 0xffff;
|
||||||
|
m_pc = m_pc | (read(m_sp) << 8); // msb(SP)
|
||||||
|
m_sp = (m_sp + 1) & 0xffff;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t opcode = pcRead();
|
||||||
|
switch (opcode) {
|
||||||
|
case 0xc0: /* RET NZ,i16 */ function_return(!m_zf); break;
|
||||||
|
case 0xc8: /* RET Z,i16 */ function_return(m_zf); break;
|
||||||
|
case 0xc9: /* RET i16 */ function_return(true); break;
|
||||||
|
case 0xd0: /* RET NC,i16 */ function_return(!m_cf); break;
|
||||||
|
case 0xd8: /* RET C,i16 */ function_return(m_cf); break;
|
||||||
|
case 0xd9: /* RETI */ {
|
||||||
|
// Return from subroutine
|
||||||
|
function_return(true);
|
||||||
|
m_ime = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CPU::rst()
|
void CPU::rst()
|
||||||
{
|
{
|
||||||
auto function_call = [this](uint32_t fixed_address) -> void {
|
auto function_call = [this](uint32_t fixed_address) -> void {
|
||||||
@@ -1376,6 +1662,19 @@ void CPU::misc()
|
|||||||
// Invert carry
|
// Invert carry
|
||||||
m_cf = (m_cf) ? 0 : 1;
|
m_cf = (m_cf) ? 0 : 1;
|
||||||
break;
|
break;
|
||||||
|
case 0xf3: // DI
|
||||||
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
// Disable Interrupts by clearing the IME flag
|
||||||
|
m_ime = 0;
|
||||||
|
break;
|
||||||
|
case 0xfb: // EI
|
||||||
|
m_wait_cycles += 4;
|
||||||
|
|
||||||
|
// Enable Interrupts by setting the IME flag.
|
||||||
|
// The flag is only set after the instruction following EI
|
||||||
|
m_should_enable_ime = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
@@ -1427,7 +1726,8 @@ void CPU::write(uint32_t address, uint32_t value)
|
|||||||
|
|
||||||
uint32_t CPU::read(uint32_t address)
|
uint32_t CPU::read(uint32_t address)
|
||||||
{
|
{
|
||||||
return Emu::the().readMemory(address) & 0xff;
|
// FIXME: Figure out where HL gets set to above 0xffff
|
||||||
|
return Emu::the().readMemory(address & 0xffff) & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::ffWrite(uint32_t address, uint32_t value)
|
void CPU::ffWrite(uint32_t address, uint32_t value)
|
||||||
@@ -1440,32 +1740,14 @@ uint32_t CPU::ffRead(uint32_t address)
|
|||||||
return Emu::the().readMemory(address | (0xff << 8)) & 0xff;
|
return Emu::the().readMemory(address | (0xff << 8)) & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CPU::isCarry(uint32_t lhs, uint32_t rhs, uint32_t limit_bit)
|
bool CPU::isCarry(uint32_t limit_bit, uint32_t first, uint32_t second, uint32_t third)
|
||||||
{
|
{
|
||||||
// limit_bit values:
|
return (first & limit_bit) + (second & limit_bit) + (third & limit_bit) > limit_bit;
|
||||||
// 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:
|
bool CPU::isCarrySubtraction(uint32_t limit_bit, uint32_t lhs, uint32_t rhs, uint32_t rhs_extra)
|
||||||
// 0b00111110 62 | 0b00111000 56
|
{
|
||||||
// 0b00100010 34 + | 0b00010001 17 +
|
return (lhs & limit_bit) < (rhs & limit_bit) + (rhs_extra & limit_bit);
|
||||||
// --------------- | ---------------
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public:
|
|||||||
explicit CPU(uint32_t frequency);
|
explicit CPU(uint32_t frequency);
|
||||||
virtual ~CPU();
|
virtual ~CPU();
|
||||||
|
|
||||||
|
void handleInterrupt(uint32_t interrupt_flag, uint8_t interrupt_source, uint8_t address);
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
@@ -32,8 +33,10 @@ public:
|
|||||||
void add8();
|
void add8();
|
||||||
void and8();
|
void and8();
|
||||||
void cp();
|
void cp();
|
||||||
|
void daa();
|
||||||
void dec8();
|
void dec8();
|
||||||
void inc8();
|
void inc8();
|
||||||
|
void or8();
|
||||||
void sbc8();
|
void sbc8();
|
||||||
void sub8();
|
void sub8();
|
||||||
void xor8();
|
void xor8();
|
||||||
@@ -69,7 +72,7 @@ public:
|
|||||||
|
|
||||||
// 8-bit
|
// 8-bit
|
||||||
void lda8();
|
void lda8();
|
||||||
void ldffi8();
|
void ldff8();
|
||||||
void ldi8();
|
void ldi8();
|
||||||
void ldr8();
|
void ldr8();
|
||||||
|
|
||||||
@@ -85,6 +88,7 @@ public:
|
|||||||
void call();
|
void call();
|
||||||
void jp16();
|
void jp16();
|
||||||
void jrs8();
|
void jrs8();
|
||||||
|
void ret();
|
||||||
void rst();
|
void rst();
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
@@ -99,6 +103,10 @@ public:
|
|||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
|
uint32_t a() const { return m_a; }
|
||||||
|
uint32_t b() const { return m_b; }
|
||||||
|
uint32_t c() const { return m_c; }
|
||||||
|
|
||||||
uint32_t af() const { return (m_cf << 4 | m_hf << 5 | m_nf << 6 | m_zf << 7) | m_a << 8; }
|
uint32_t af() const { return (m_cf << 4 | m_hf << 5 | m_nf << 6 | m_zf << 7) | m_a << 8; }
|
||||||
uint32_t bc() const { return m_c | m_b << 8; }
|
uint32_t bc() const { return m_c | m_b << 8; }
|
||||||
uint32_t de() const { return m_e | m_d << 8; }
|
uint32_t de() const { return m_e | m_d << 8; }
|
||||||
@@ -106,6 +114,11 @@ public:
|
|||||||
uint32_t pc() const { return m_pc; }
|
uint32_t pc() const { return m_pc; }
|
||||||
uint32_t sp() const { return m_sp; }
|
uint32_t sp() const { return m_sp; }
|
||||||
|
|
||||||
|
uint32_t zf() const { return m_zf; }
|
||||||
|
uint32_t nf() const { return m_nf; }
|
||||||
|
uint32_t hf() const { return m_hf; }
|
||||||
|
uint32_t cf() const { return m_cf; }
|
||||||
|
|
||||||
void setBC(uint32_t value);
|
void setBC(uint32_t value);
|
||||||
void setDE(uint32_t value);
|
void setDE(uint32_t value);
|
||||||
void setHL(uint32_t value);
|
void setHL(uint32_t value);
|
||||||
@@ -120,7 +133,8 @@ private:
|
|||||||
void ffWrite(uint32_t address, uint32_t value);
|
void ffWrite(uint32_t address, uint32_t value);
|
||||||
uint32_t ffRead(uint32_t address);
|
uint32_t ffRead(uint32_t address);
|
||||||
|
|
||||||
bool isCarry(uint32_t lhs, uint32_t rhs, uint32_t limit_bit);
|
bool isCarry(uint32_t limit_bit, uint32_t first, uint32_t second, uint32_t third = 0x0);
|
||||||
|
bool isCarrySubtraction(uint32_t limit_bit, uint32_t lhs, uint32_t rhs, uint32_t rhs_extra = 0x0);
|
||||||
|
|
||||||
// Registers
|
// Registers
|
||||||
uint32_t m_a { 0 }; // Accumulator
|
uint32_t m_a { 0 }; // Accumulator
|
||||||
@@ -138,7 +152,9 @@ private:
|
|||||||
uint32_t m_nf { 0 }; // Subtraction flag (BCD)
|
uint32_t m_nf { 0 }; // Subtraction flag (BCD)
|
||||||
uint32_t m_hf { 0 }; // Half Carry flag (BCD)
|
uint32_t m_hf { 0 }; // Half Carry flag (BCD)
|
||||||
uint32_t m_cf { 0 }; // Carry flag
|
uint32_t m_cf { 0 }; // Carry flag
|
||||||
|
uint32_t m_ime { 0 }; // Interrupt Master Enable flag
|
||||||
|
|
||||||
|
bool m_should_enable_ime { 0 };
|
||||||
int8_t m_wait_cycles { 0 };
|
int8_t m_wait_cycles { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022 Riyyi
|
||||||
* Copyright (C) 2022 Th3FrankXD
|
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022 Riyyi
|
||||||
* Copyright (C) 2022 Th3FrankXD
|
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|||||||
+32
-6
@@ -39,7 +39,7 @@ void Emu::update()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::addProcessingUnit(std::string_view name, ProcessingUnit* processing_unit)
|
void Emu::addProcessingUnit(std::string_view name, std::shared_ptr<ProcessingUnit> processing_unit)
|
||||||
{
|
{
|
||||||
m_processing_units.emplace(name, processing_unit);
|
m_processing_units.emplace(name, processing_unit);
|
||||||
}
|
}
|
||||||
@@ -64,18 +64,37 @@ void Emu::removeMemorySpace(std::string_view name)
|
|||||||
|
|
||||||
void Emu::writeMemory(uint32_t address, uint32_t value)
|
void Emu::writeMemory(uint32_t address, uint32_t value)
|
||||||
{
|
{
|
||||||
|
// Bail if the CPU tries to write to a read-only address
|
||||||
|
switch (address) {
|
||||||
|
case 0xff44:
|
||||||
|
ruc::error("writing to read-only address: {:#06x}", address);
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
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 == 0xff50) {
|
if (address >= memory.start_address && address <= memory.end_address) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
memory.memory[memory.active_bank][address - memory.start_address] = value;
|
memory.memory[memory.active_bank][address - memory.start_address] = value;
|
||||||
|
|
||||||
|
if (address == 0xff50) {
|
||||||
|
print("DISABLING BOOTROM\n");
|
||||||
|
Loader::the().disableBootrom();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write serial data from linkport I/O, used for blargg's test ROMs
|
||||||
|
if (address == 0xff02 && value == 0x81) {
|
||||||
|
uint32_t data = readMemory(0xff01);
|
||||||
|
print("{:c}", (data >= 58 && data <= 64) ? data + 7 : data);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,10 +106,17 @@ void Emu::writeMemory(uint32_t address, uint32_t value)
|
|||||||
|
|
||||||
uint32_t Emu::readMemory(uint32_t address) const
|
uint32_t Emu::readMemory(uint32_t address) const
|
||||||
{
|
{
|
||||||
|
switch (address) {
|
||||||
|
case 0xff44:
|
||||||
|
return *m_processing_units.at("PPU")->sharedRegister("LY");
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
for (const auto& memory_space : m_memory_spaces) {
|
for (const auto& memory_space : m_memory_spaces) {
|
||||||
const auto& memory = memory_space.second;
|
const auto& memory = memory_space.second;
|
||||||
if (address >= memory.start_address && address <= memory.end_address) {
|
if (address >= memory.start_address && address <= memory.end_address) {
|
||||||
return memory.memory[memory.active_bank][address];
|
return memory.memory[memory.active_bank][address - memory.start_address];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <string>
|
#include <memory> // std::shared_ptr
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -30,11 +30,16 @@ class Emu final : public ruc::Singleton<Emu> {
|
|||||||
public:
|
public:
|
||||||
Emu(s) {}
|
Emu(s) {}
|
||||||
|
|
||||||
|
enum Mode : uint8_t {
|
||||||
|
DMG, // Game Boy
|
||||||
|
CGB, // Game Boy Color
|
||||||
|
};
|
||||||
|
|
||||||
void init(uint32_t frequency);
|
void init(uint32_t frequency);
|
||||||
|
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
void addProcessingUnit(std::string_view name, ProcessingUnit* processing_unit);
|
void addProcessingUnit(std::string_view name, std::shared_ptr<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 removeMemorySpace(std::string_view name);
|
||||||
|
|
||||||
@@ -43,10 +48,12 @@ public:
|
|||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
ProcessingUnit* processingUnit(std::string_view name) const { return m_processing_units.at(name); }
|
Mode mode() const { return m_mode; }
|
||||||
|
std::shared_ptr<ProcessingUnit> processingUnit(std::string_view name) const { return m_processing_units.at(name); }
|
||||||
MemorySpace memorySpace(std::string_view name) { return m_memory_spaces[name]; }
|
MemorySpace memorySpace(std::string_view name) { return m_memory_spaces[name]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Mode m_mode { Mode::DMG };
|
||||||
uint32_t m_frequency { 0 };
|
uint32_t m_frequency { 0 };
|
||||||
double m_timestep { 0 };
|
double m_timestep { 0 };
|
||||||
uint32_t m_cycle { 0 };
|
uint32_t m_cycle { 0 };
|
||||||
@@ -55,6 +62,6 @@ private:
|
|||||||
|
|
||||||
ruc::Timer m_timer;
|
ruc::Timer m_timer;
|
||||||
|
|
||||||
std::unordered_map<std::string_view, ProcessingUnit*> m_processing_units;
|
std::unordered_map<std::string_view, std::shared_ptr<ProcessingUnit>> m_processing_units;
|
||||||
std::unordered_map<std::string_view, MemorySpace> m_memory_spaces;
|
std::unordered_map<std::string_view, MemorySpace> m_memory_spaces;
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-14
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
|
#include <memory> // std::make_shared
|
||||||
|
|
||||||
#include "cpu.h"
|
#include "cpu.h"
|
||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
@@ -42,13 +43,13 @@ void Loader::init()
|
|||||||
{
|
{
|
||||||
destroy();
|
destroy();
|
||||||
|
|
||||||
Emu::the().init(8000000);
|
Emu::the().init(4000000);
|
||||||
|
|
||||||
CPU cpu(8000000);
|
auto cpu = std::make_shared<CPU>(4000000);
|
||||||
PPU ppu(4000000);
|
auto ppu = std::make_shared<PPU>(4000000);
|
||||||
|
|
||||||
Emu::the().addProcessingUnit("cpu", &cpu);
|
Emu::the().addProcessingUnit("CPU", cpu);
|
||||||
Emu::the().addProcessingUnit("ppu", &ppu);
|
Emu::the().addProcessingUnit("PPU", ppu);
|
||||||
|
|
||||||
// https://gbdev.io/pandocs/Memory_Map.html
|
// https://gbdev.io/pandocs/Memory_Map.html
|
||||||
// https://gbdev.io/pandocs/Power_Up_Sequence.html
|
// https://gbdev.io/pandocs/Power_Up_Sequence.html
|
||||||
@@ -77,15 +78,6 @@ void Loader::init()
|
|||||||
|
|
||||||
Emu::the().writeMemory(i, bootrom[i]);
|
Emu::the().writeMemory(i, bootrom[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Loader::update()
|
|
||||||
{
|
|
||||||
while (true) {
|
|
||||||
Emu::the().update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Loader::destroy()
|
void Loader::destroy()
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void init();
|
void init();
|
||||||
void update();
|
|
||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
void loadCartridgeHeader();
|
void loadCartridgeHeader();
|
||||||
|
|||||||
+37
-5
@@ -5,14 +5,23 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "loader.h"
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "inferno.h"
|
||||||
|
#include "inferno/entrypoint.h"
|
||||||
|
#include "ppu.h"
|
||||||
#include "ruc/argparser.h"
|
#include "ruc/argparser.h"
|
||||||
#include "ruc/format/print.h"
|
#include "ruc/format/print.h"
|
||||||
#include "ruc/timer.h"
|
#include "ruc/timer.h"
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
int main(int argc, const char* argv[])
|
#include "emu.h"
|
||||||
{
|
#include "loader.h"
|
||||||
|
|
||||||
|
class GarbAGE final : public Inferno::Application {
|
||||||
|
public:
|
||||||
|
GarbAGE(int argc, char* argv[])
|
||||||
|
: Application()
|
||||||
|
{
|
||||||
std::string_view bootrom_path = "gbc_bios.bin";
|
std::string_view bootrom_path = "gbc_bios.bin";
|
||||||
std::string_view rom_path;
|
std::string_view rom_path;
|
||||||
|
|
||||||
@@ -23,6 +32,29 @@ int main(int argc, const char* argv[])
|
|||||||
|
|
||||||
Loader::the().setBootromPath(bootrom_path);
|
Loader::the().setBootromPath(bootrom_path);
|
||||||
Loader::the().loadRom(rom_path);
|
Loader::the().loadRom(rom_path);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
~GarbAGE()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() override
|
||||||
|
{
|
||||||
|
// 154 scanlines * 456 cycles = 70224 cycles per frame
|
||||||
|
for (int i = 0; i < (144 * 456 + 10 * 456); ++i) {
|
||||||
|
Emu::the().update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render() override
|
||||||
|
{
|
||||||
|
auto* ppu = static_cast<PPU*>(Emu::the().processingUnit("PPU").get());
|
||||||
|
ppu->render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Inferno::Application* Inferno::createApplication(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
GarbAGE* gameboy = new GarbAGE(argc, argv);
|
||||||
|
return gameboy;
|
||||||
}
|
}
|
||||||
|
|||||||
+284
-6
@@ -5,20 +5,298 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ppu.h"
|
#include <cstdint> // uint8_t, uint16_t, uint32_t
|
||||||
#include "ruc/format/print.h"
|
#include <memory> // std::make_shared
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
PPU ::PPU(unsigned int frequency)
|
#include "glm/ext/vector_float3.hpp" // glm::vec3
|
||||||
|
#include "glm/ext/vector_float4.hpp" // glm::vec4
|
||||||
|
#include "inferno/application.h"
|
||||||
|
#include "inferno/component/spritecomponent.h"
|
||||||
|
#include "inferno/component/transformcomponent.h"
|
||||||
|
#include "inferno/scene/scene.h"
|
||||||
|
#include "ruc/format/print.h"
|
||||||
|
|
||||||
|
#include "emu.h"
|
||||||
|
#include "ppu.h"
|
||||||
|
#include "ruc/meta/assert.h"
|
||||||
|
|
||||||
|
PPU::PPU(uint32_t frequency)
|
||||||
: ProcessingUnit(frequency)
|
: ProcessingUnit(frequency)
|
||||||
{
|
{
|
||||||
|
m_shared_registers.emplace("LY", &m_lcd_y_coordinate);
|
||||||
|
|
||||||
|
auto& scene = Inferno::Application::the().scene();
|
||||||
|
m_entity = scene.findEntity("Screen");
|
||||||
}
|
}
|
||||||
|
|
||||||
PPU ::~PPU()
|
PPU::~PPU()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPU::update()
|
void PPU::update()
|
||||||
{
|
{
|
||||||
// print("ppu update\n");
|
LCDC lcd_control = static_cast<LCDC>(Emu::the().readMemory(0xff40));
|
||||||
|
if (!(lcd_control & LCDC::LCDandPPUEnable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// print("PPU update\n");
|
||||||
|
|
||||||
|
m_clocks_into_frame++;
|
||||||
|
|
||||||
|
switch (m_state) {
|
||||||
|
case State::OAMSearch:
|
||||||
|
// OAM logic goes here..
|
||||||
|
|
||||||
|
if (m_clocks_into_frame % 80 == 0) {
|
||||||
|
// Reset FIFO
|
||||||
|
m_pixel_fifo = {};
|
||||||
|
|
||||||
|
m_state = State::PixelTransfer;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::PixelTransfer:
|
||||||
|
updatePixelFifo();
|
||||||
|
|
||||||
|
if (m_lcd_x_coordinate == 160) {
|
||||||
|
m_lcd_x_coordinate = 0;
|
||||||
|
m_state = State::HBlank;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::HBlank:
|
||||||
|
// H-Blank logic goes here..
|
||||||
|
|
||||||
|
if (m_clocks_into_frame % (80 + 172 + 204) == 0) {
|
||||||
|
m_lcd_y_coordinate++;
|
||||||
|
if (m_lcd_y_coordinate == 144) {
|
||||||
|
m_state = State::VBlank;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_state = State::OAMSearch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::VBlank:
|
||||||
|
// V-Blank logic goes here..
|
||||||
|
|
||||||
|
if (m_clocks_into_frame % (80 + 172 + 204) == 0) {
|
||||||
|
m_lcd_y_coordinate++;
|
||||||
|
if (m_lcd_y_coordinate == 154) {
|
||||||
|
resetFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::render()
|
||||||
|
{
|
||||||
|
LCDC lcd_control = static_cast<LCDC>(Emu::the().readMemory(0xff40));
|
||||||
|
|
||||||
|
if (!(lcd_control & LCDC::BGandWindowEnable)) {
|
||||||
|
// When Bit 0 is cleared, both background and window become blank (white)
|
||||||
|
auto pixel = getPixelColor(0, Palette::BGP);
|
||||||
|
for (size_t i = 0; i < m_screen.size(); i += 3) {
|
||||||
|
m_screen[i + 0] = pixel[0];
|
||||||
|
m_screen[i + 1] = pixel[1];
|
||||||
|
m_screen[i + 2] = pixel[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& scene = Inferno::Application::the().scene();
|
||||||
|
auto texture = std::make_shared<Inferno::Texture>(m_screen.data(), SCREEN_WIDTH, SCREEN_HEIGHT, FORMAT_SIZE);
|
||||||
|
scene.removeComponent<Inferno::SpriteComponent>(m_entity);
|
||||||
|
scene.addComponent<Inferno::SpriteComponent>(m_entity, glm::vec4 { 1.0f }, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::resetFrame()
|
||||||
|
{
|
||||||
|
m_state = State::OAMSearch;
|
||||||
|
m_clocks_into_frame = 0;
|
||||||
|
m_lcd_x_coordinate = 0;
|
||||||
|
m_lcd_y_coordinate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
uint32_t PPU::getBgTileDataAddress(uint8_t tile_index)
|
||||||
|
{
|
||||||
|
// https://gbdev.io/pandocs/Tile_Data.html
|
||||||
|
switch (m_pixel_fifo.tile_data_address) {
|
||||||
|
case 0x8000:
|
||||||
|
// 0x8000-0x8fff: index 0 => 255
|
||||||
|
return m_pixel_fifo.tile_data_address + (tile_index * TILE_SIZE); // Each tile is 16 bytes
|
||||||
|
case 0x8800:
|
||||||
|
// 0x8800-0x8fff: index 128 => 255 (or -128 => -1)
|
||||||
|
// 0x9000-0x97ff: index 0 => 127
|
||||||
|
if (tile_index <= 127) {
|
||||||
|
return m_pixel_fifo.tile_data_address + 0x800 + (tile_index * TILE_SIZE); // Each tile is 16 bytes
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return m_pixel_fifo.tile_data_address + ((tile_index - 128) * TILE_SIZE); // Each tile is 16 bytes
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<uint8_t, 3> PPU::getPixelColor(uint8_t color_index, Palette palette)
|
||||||
|
{
|
||||||
|
VERIFY(color_index < 4, "trying to fetch invalid color index '{}'", color_index);
|
||||||
|
|
||||||
|
switch (Emu::the().mode()) {
|
||||||
|
case Emu::Mode::DMG: {
|
||||||
|
uint8_t palette_data = Emu::the().readMemory(palette) & 0xff;
|
||||||
|
uint8_t palette_value = palette_data >> (color_index * 2) & 0x3;
|
||||||
|
switch (palette_value) {
|
||||||
|
case 0:
|
||||||
|
return { 200, 199, 168 };
|
||||||
|
case 1:
|
||||||
|
return { 160, 160, 136 };
|
||||||
|
case 2:
|
||||||
|
return { 104, 104, 80 };
|
||||||
|
case 3:
|
||||||
|
return { 39, 40, 24 };
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case Emu::Mode::CGB:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::updatePixelFifo()
|
||||||
|
{
|
||||||
|
switch (m_pixel_fifo.state) {
|
||||||
|
case PixelFifo::State::TileIndex:
|
||||||
|
tileIndex();
|
||||||
|
break;
|
||||||
|
case PixelFifo::State::TileDataLow:
|
||||||
|
tileDataLow();
|
||||||
|
break;
|
||||||
|
case PixelFifo::State::TileDataHigh:
|
||||||
|
tileDataHigh();
|
||||||
|
break;
|
||||||
|
case PixelFifo::State::Sleep:
|
||||||
|
sleep();
|
||||||
|
break;
|
||||||
|
case PixelFifo::State::Push:
|
||||||
|
pushFifo();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
|
||||||
|
pushPixel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::tileIndex()
|
||||||
|
{
|
||||||
|
if (!m_pixel_fifo.step) {
|
||||||
|
m_pixel_fifo.step = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_pixel_fifo.step = false;
|
||||||
|
m_pixel_fifo.state = PixelFifo::State::TileDataLow;
|
||||||
|
|
||||||
|
LCDC lcd_control = static_cast<LCDC>(Emu::the().readMemory(0xff40));
|
||||||
|
|
||||||
|
// Tile map
|
||||||
|
uint32_t bg_tile_map_address = (lcd_control & LCDC::BGTileMapArea) ? 0x9c00 : 0x9800;
|
||||||
|
// uint32_t window_tile_map_address = (lcd_control & LCDC::WindowTileMapArea) ? 0x9c00 : 0x9800;
|
||||||
|
|
||||||
|
// Tile data
|
||||||
|
m_pixel_fifo.tile_data_address = (lcd_control & LCDC::BGandWindowTileDataArea) ? 0x8000 : 0x8800;
|
||||||
|
|
||||||
|
// Viewport
|
||||||
|
// https://gbdev.io/pandocs/Scrolling.html#mid-frame-behavior
|
||||||
|
m_pixel_fifo.viewport_x = Emu::the().readMemory(0xff43); // TODO: only read lower 3-bits at beginning of scanline
|
||||||
|
m_pixel_fifo.viewport_y = Emu::the().readMemory(0xff42);
|
||||||
|
|
||||||
|
// Read the tile map index
|
||||||
|
uint16_t offset = (((m_pixel_fifo.viewport_y + m_lcd_y_coordinate) / TILE_HEIGHT) * 32)
|
||||||
|
+ ((m_pixel_fifo.viewport_x + m_pixel_fifo.x_coordinate) / TILE_WIDTH);
|
||||||
|
m_pixel_fifo.x_coordinate += 8;
|
||||||
|
m_pixel_fifo.tile_index = Emu::the().readMemory(bg_tile_map_address + offset);
|
||||||
|
|
||||||
|
// Set the tile line we're currently on
|
||||||
|
m_pixel_fifo.tile_line = (m_pixel_fifo.viewport_y + m_lcd_y_coordinate) % TILE_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::tileDataLow()
|
||||||
|
{
|
||||||
|
if (!m_pixel_fifo.step) {
|
||||||
|
m_pixel_fifo.step = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_pixel_fifo.step = false;
|
||||||
|
m_pixel_fifo.state = PixelFifo::State::TileDataHigh;
|
||||||
|
|
||||||
|
// Read tile data
|
||||||
|
m_pixel_fifo.pixels_lsb = Emu::the().readMemory(
|
||||||
|
getBgTileDataAddress(m_pixel_fifo.tile_index)
|
||||||
|
+ m_pixel_fifo.tile_line * 2); // Each tile line is 2 bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::tileDataHigh()
|
||||||
|
{
|
||||||
|
if (!m_pixel_fifo.step) {
|
||||||
|
m_pixel_fifo.step = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_pixel_fifo.step = false;
|
||||||
|
m_pixel_fifo.state = PixelFifo::State::Sleep;
|
||||||
|
|
||||||
|
// Read tile data
|
||||||
|
m_pixel_fifo.pixels_msb = Emu::the().readMemory(
|
||||||
|
getBgTileDataAddress(m_pixel_fifo.tile_index)
|
||||||
|
+ m_pixel_fifo.tile_line * 2 // Each tile line is 2 bytes
|
||||||
|
+ 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::sleep()
|
||||||
|
{
|
||||||
|
if (m_pixel_fifo.background.size() <= TILE_WIDTH + 1) {
|
||||||
|
m_pixel_fifo.state = PixelFifo::State::Push;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::pushFifo()
|
||||||
|
{
|
||||||
|
m_pixel_fifo.state = PixelFifo::State::TileIndex;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < TILE_WIDTH; ++i) {
|
||||||
|
uint8_t color_index = (m_pixel_fifo.pixels_lsb >> (7 - i)
|
||||||
|
| ((m_pixel_fifo.pixels_msb >> (7 - i)) << 1))
|
||||||
|
& 0x3;
|
||||||
|
m_pixel_fifo.background.push({ color_index, Palette::BGP });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::pushPixel()
|
||||||
|
{
|
||||||
|
// The pixel FIFO needs to contain more than 8 pixels to shift one out
|
||||||
|
if (m_pixel_fifo.background.size() > 8) {
|
||||||
|
auto pixel = m_pixel_fifo.background.front();
|
||||||
|
m_pixel_fifo.background.pop();
|
||||||
|
|
||||||
|
uint32_t index = (m_lcd_y_coordinate * SCREEN_WIDTH + m_lcd_x_coordinate) * FORMAT_SIZE;
|
||||||
|
auto color = getPixelColor(pixel.first, pixel.second);
|
||||||
|
m_screen[index + 0] = color[0];
|
||||||
|
m_screen[index + 1] = color[1];
|
||||||
|
m_screen[index + 2] = color[2];
|
||||||
|
m_lcd_x_coordinate++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,103 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint> // uint8_t, uint16_t, uint32_t
|
||||||
|
#include <queue>
|
||||||
|
#include <utility> // std::pair
|
||||||
|
|
||||||
|
#include "ruc/meta/core.h"
|
||||||
|
|
||||||
#include "processing-unit.h"
|
#include "processing-unit.h"
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH 160
|
||||||
|
#define SCREEN_HEIGHT 144
|
||||||
|
#define FORMAT_SIZE 3
|
||||||
|
#define TILE_WIDTH 8
|
||||||
|
#define TILE_HEIGHT 8
|
||||||
|
#define TILE_SIZE 16
|
||||||
|
|
||||||
class PPU final : public ProcessingUnit {
|
class PPU final : public ProcessingUnit {
|
||||||
public:
|
public:
|
||||||
PPU(unsigned int frequency);
|
PPU(uint32_t frequency);
|
||||||
virtual ~PPU();
|
~PPU();
|
||||||
|
|
||||||
virtual void update() override;
|
enum LCDC : uint8_t {
|
||||||
|
None = 0,
|
||||||
|
BGandWindowEnable = BIT(0),
|
||||||
|
OBJEnable = BIT(1),
|
||||||
|
OBJSize = BIT(2), // 0 = 8x8, 1 = 8x16
|
||||||
|
BGTileMapArea = BIT(3), // 0 = 0x9800-9bff, 1 = 0x9c00-9fff
|
||||||
|
BGandWindowTileDataArea = BIT(4), // 0 = 0x8800-97ff, 1 = 0x8000-8fff
|
||||||
|
WindowEnable = BIT(5), //
|
||||||
|
WindowTileMapArea = BIT(6), // 0 = 0x9800-9bff, 1 = 0x9c00-9fff
|
||||||
|
LCDandPPUEnable = BIT(7),
|
||||||
|
};
|
||||||
|
|
||||||
|
enum State : uint8_t {
|
||||||
|
HBlank = 0,
|
||||||
|
VBlank = 1,
|
||||||
|
OAMSearch = 2,
|
||||||
|
PixelTransfer = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Palette : uint16_t {
|
||||||
|
BGP = 0xff47, // BG and Window palette
|
||||||
|
OBP0 = 0xff48, // OBJ palette 0
|
||||||
|
OBP1 = 0xff49, // OBJ palette 1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PixelFifo {
|
||||||
|
enum State : uint8_t {
|
||||||
|
TileIndex,
|
||||||
|
TileDataLow,
|
||||||
|
TileDataHigh,
|
||||||
|
Sleep,
|
||||||
|
Push,
|
||||||
|
};
|
||||||
|
|
||||||
|
State state { State::TileIndex };
|
||||||
|
bool step { false };
|
||||||
|
uint32_t tile_data_address { 0 };
|
||||||
|
uint8_t viewport_x { 0 };
|
||||||
|
uint8_t viewport_y { 0 };
|
||||||
|
uint8_t x_coordinate { 0 };
|
||||||
|
uint8_t tile_index { 0 };
|
||||||
|
uint8_t tile_line { 0 };
|
||||||
|
uint8_t pixels_lsb { 0 };
|
||||||
|
uint8_t pixels_msb { 0 };
|
||||||
|
|
||||||
|
using Fifo = std::queue<std::pair<uint8_t, Palette>>; // colorID, source
|
||||||
|
|
||||||
|
Fifo background;
|
||||||
|
Fifo oam;
|
||||||
|
};
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void render();
|
||||||
|
void resetFrame();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t getBgTileDataAddress(uint8_t tile_index);
|
||||||
|
std::array<uint8_t, 3> getPixelColor(uint8_t color_index, Palette palette);
|
||||||
|
|
||||||
|
void updatePixelFifo();
|
||||||
|
void tileIndex();
|
||||||
|
void tileDataLow();
|
||||||
|
void tileDataHigh();
|
||||||
|
void sleep();
|
||||||
|
void pushFifo();
|
||||||
|
void pushPixel();
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
|
State m_state { State::OAMSearch };
|
||||||
|
uint32_t m_clocks_into_frame { 0 };
|
||||||
|
uint32_t m_lcd_x_coordinate { 0 };
|
||||||
|
uint32_t m_lcd_y_coordinate { 0 }; // Note: includes V-Blank
|
||||||
|
|
||||||
|
PixelFifo m_pixel_fifo;
|
||||||
|
|
||||||
|
uint32_t m_entity { 0 };
|
||||||
|
std::array<uint8_t, SCREEN_WIDTH * SCREEN_HEIGHT * FORMAT_SIZE> m_screen;
|
||||||
};
|
};
|
||||||
|
|||||||
+146
-13
@@ -4,7 +4,11 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <memory> // std::make_shared, std::shared_ptr
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "cpu.h"
|
#include "cpu.h"
|
||||||
|
#include "emu.h"
|
||||||
#include "macro.h"
|
#include "macro.h"
|
||||||
#include "testcase.h"
|
#include "testcase.h"
|
||||||
#include "testsuite.h"
|
#include "testsuite.h"
|
||||||
@@ -12,9 +16,39 @@
|
|||||||
struct CPUTest {
|
struct CPUTest {
|
||||||
CPU cpu { 0 };
|
CPU cpu { 0 };
|
||||||
|
|
||||||
bool isCarry(uint32_t lhs, uint32_t rhs, uint32_t limit_bit) { return cpu.isCarry(lhs, rhs, limit_bit); }
|
bool isCarry(uint32_t limit_bit, uint32_t first, uint32_t second, uint32_t third = 0x0)
|
||||||
|
{
|
||||||
|
return cpu.isCarry(limit_bit, first, second, third);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCarrySubtraction(uint32_t limit_bit, uint32_t lhs, uint32_t rhs, uint32_t rhs_extra = 0x0)
|
||||||
|
{
|
||||||
|
return cpu.isCarrySubtraction(limit_bit, lhs, rhs, rhs_extra);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<CPU> runCPUTest(std::vector<uint8_t> test)
|
||||||
|
{
|
||||||
|
auto cpu = std::make_shared<CPU>(4000000);
|
||||||
|
|
||||||
|
Emu::the().destroy();
|
||||||
|
Emu::the().init(8000000);
|
||||||
|
Emu::the().addProcessingUnit("cpu", cpu);
|
||||||
|
Emu::the().addMemorySpace("FULL", 0x0000, 0xffff);
|
||||||
|
|
||||||
|
// Load the test
|
||||||
|
for (size_t i = 0; i < test.size(); ++i) {
|
||||||
|
Emu::the().writeMemory(i, test[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the test
|
||||||
|
while (cpu->pc() < test.size()) {
|
||||||
|
Emu::the().update();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cpu;
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
TEST_CASE(CPUIsCarry)
|
TEST_CASE(CPUIsCarry)
|
||||||
@@ -23,27 +57,126 @@ TEST_CASE(CPUIsCarry)
|
|||||||
|
|
||||||
// 8-bit Half-carry false
|
// 8-bit Half-carry false
|
||||||
|
|
||||||
EXPECT_EQ(test.isCarry(56, 17, 0x10), false);
|
EXPECT(test.isCarry(0xf, 56, 17) == false);
|
||||||
|
|
||||||
EXPECT_EQ(test.isCarry(16, -1, 0x10), false);
|
EXPECT(test.isCarrySubtraction(0xf, 136, 1) == false);
|
||||||
EXPECT_EQ(test.isCarry(32, -1, 0x10), false);
|
EXPECT(test.isCarrySubtraction(0xf, 219, 20) == false);
|
||||||
EXPECT_EQ(test.isCarry(144, -1, 0x10), false);
|
EXPECT(test.isCarrySubtraction(0xf, 250, 20) == false);
|
||||||
EXPECT_EQ(test.isCarry(211, -20, 0x10), false);
|
|
||||||
|
|
||||||
// 8-bit Half-carry true
|
// 8-bit Half-carry true
|
||||||
|
|
||||||
EXPECT_EQ(test.isCarry(46, 34, 0x10), true);
|
EXPECT(test.isCarry(0xf, 46, 34) == true);
|
||||||
EXPECT_EQ(test.isCarry(62, 34, 0x10), true);
|
EXPECT(test.isCarry(0xf, 62, 34) == true);
|
||||||
|
|
||||||
EXPECT_EQ(test.isCarry(136, -1, 0x10), true);
|
EXPECT(test.isCarrySubtraction(0xf, 16, 1) == true);
|
||||||
EXPECT_EQ(test.isCarry(219, -20, 0x10), true);
|
EXPECT(test.isCarrySubtraction(0xf, 32, 1) == true);
|
||||||
EXPECT_EQ(test.isCarry(250, -20, 0x10), true);
|
EXPECT(test.isCarrySubtraction(0xf, 144, 1) == true);
|
||||||
|
EXPECT(test.isCarrySubtraction(0xf, 211, 20) == true);
|
||||||
|
|
||||||
// 8-bit Full carry false
|
// 8-bit Full carry false
|
||||||
|
|
||||||
EXPECT_EQ(test.isCarry(254, 1, 0x100), false);
|
EXPECT(test.isCarry(0xff, 254, 1) == false);
|
||||||
|
|
||||||
// 8-bit Full carry true
|
// 8-bit Full carry true
|
||||||
|
|
||||||
EXPECT_EQ(test.isCarry(254, 2, 0x100), true);
|
EXPECT(test.isCarry(0xff, 254, 2) == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(CPUAddPlusCarry)
|
||||||
|
{
|
||||||
|
std::shared_ptr<CPU> cpu(0);
|
||||||
|
|
||||||
|
// ADC A,E
|
||||||
|
|
||||||
|
std::vector<uint8_t> adc_r8 = {
|
||||||
|
// clang-format off
|
||||||
|
0x3e, 0xe1, // LD A,i8
|
||||||
|
0x1e, 0x0f, // LD E,i8
|
||||||
|
0x37, // SCF
|
||||||
|
0x8b, // ADC A,E
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
cpu = runCPUTest(adc_r8);
|
||||||
|
EXPECT_EQ(cpu->a(), 0xf1);
|
||||||
|
EXPECT_EQ(cpu->zf(), 0x0);
|
||||||
|
EXPECT_EQ(cpu->nf(), 0x0);
|
||||||
|
EXPECT_EQ(cpu->hf(), 0x1);
|
||||||
|
EXPECT_EQ(cpu->cf(), 0x0);
|
||||||
|
|
||||||
|
// ADC A,i8
|
||||||
|
|
||||||
|
std::vector<uint8_t> adc_i8 = {
|
||||||
|
// clang-format off
|
||||||
|
0x3e, 0xe1, // LD A,i8
|
||||||
|
0x37, // SCF
|
||||||
|
0xce, 0x3b, // ADC A,i8
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
cpu = runCPUTest(adc_i8);
|
||||||
|
EXPECT_EQ(cpu->a(), 0x1d);
|
||||||
|
EXPECT_EQ(cpu->zf(), 0x0);
|
||||||
|
EXPECT_EQ(cpu->nf(), 0x0);
|
||||||
|
EXPECT_EQ(cpu->hf(), 0x0);
|
||||||
|
EXPECT_EQ(cpu->cf(), 0x1);
|
||||||
|
|
||||||
|
// ADC A,(HL)
|
||||||
|
|
||||||
|
std::vector<uint8_t> adc_hl = {
|
||||||
|
// clang-format off
|
||||||
|
0x3e, 0xe1, // LD A,i8
|
||||||
|
0x36, 0x1e, // LD (HL),i8
|
||||||
|
0x37, // SCF
|
||||||
|
0x8e, // ADC A,(HL)
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
cpu = runCPUTest(adc_hl);
|
||||||
|
EXPECT_EQ(cpu->a(), 0x0);
|
||||||
|
EXPECT_EQ(cpu->zf(), 0x1);
|
||||||
|
EXPECT_EQ(cpu->nf(), 0x0);
|
||||||
|
EXPECT_EQ(cpu->hf(), 0x1);
|
||||||
|
EXPECT_EQ(cpu->cf(), 0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(CPUSetStackPointer)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> test = { 0x31, 0xfe, 0xff }; // LD SP,i16
|
||||||
|
auto cpu = runCPUTest(test);
|
||||||
|
EXPECT_EQ(cpu->sp(), 0xfffe);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(CPUPushToStack)
|
||||||
|
{
|
||||||
|
// PUSH BC
|
||||||
|
|
||||||
|
std::vector<uint8_t> push_bc = {
|
||||||
|
// clang-format off
|
||||||
|
0x31, 0xfe, 0xff, // LD SP,i16
|
||||||
|
0x01, 0xfc, 0xff, // LD BC,i16
|
||||||
|
0xc5, // PUSH BC
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
auto cpu = runCPUTest(push_bc);
|
||||||
|
EXPECT_EQ(cpu->bc(), 0xfffc);
|
||||||
|
EXPECT_EQ(cpu->sp(), 0xfffc);
|
||||||
|
EXPECT_EQ(Emu::the().readMemory(0xfffd), 0xff);
|
||||||
|
EXPECT_EQ(Emu::the().readMemory(0xfffc), 0xfc);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(CPUPopFromStack)
|
||||||
|
{
|
||||||
|
// POP BC
|
||||||
|
|
||||||
|
std::vector<uint8_t> pop_bc = {
|
||||||
|
// clang-format off
|
||||||
|
0x31, 0xfe, 0xff, // LD SP,i16
|
||||||
|
0x11, 0x5f, 0x3c, // LD DE,i16
|
||||||
|
0xd5, // PUSH DE
|
||||||
|
0xc1, // POP BC
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
auto cpu = runCPUTest(pop_bc);
|
||||||
|
EXPECT_EQ(cpu->bc(), 0x3c5f);
|
||||||
|
EXPECT_EQ(cpu->sp(), 0xfffe);
|
||||||
|
EXPECT_EQ(Emu::the().readMemory(0xfffd), 0x3c);
|
||||||
|
EXPECT_EQ(Emu::the().readMemory(0xfffc), 0x5f);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
Submodule vendor/inferno added at f47babe5cf
Vendored
-1
Submodule vendor/ruc deleted from f31f7feb5e
Reference in New Issue
Block a user