From 210a768646456d94dd9d5906dc7a671a096c5937 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 5 Nov 2022 15:50:08 -0500 Subject: [PATCH 1/9] adding gameboycore support and allowing build without NES support. testing spi_lcd transaction queuing instead of blocking. --- CMakeLists.txt | 4 +- components/box-emu-hal/src/spi_lcd.cpp | 34 +++++++++------ main/gameboy.hpp | 58 +++++++++++++++++--------- main/nes.hpp | 13 +++++- 4 files changed, 72 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 77e06ec2..234aae24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,10 @@ set(EXTRA_COMPONENT_DIRS ) ### NES ### -set(NES_COMPONENTS "nofrendo nofrendo-esp32") +# set(NES_COMPONENTS "nofrendo nofrendo-esp32") ### GBC ### -# set(GBC_COMPONENTS "gameboycore") +set(GBC_COMPONENTS "gameboycore") # set(GBC_COMPONENTS "gnuboy") ### SMS ### diff --git a/components/box-emu-hal/src/spi_lcd.cpp b/components/box-emu-hal/src/spi_lcd.cpp index a686e85f..4e1221a4 100644 --- a/components/box-emu-hal/src/spi_lcd.cpp +++ b/components/box-emu-hal/src/spi_lcd.cpp @@ -13,6 +13,7 @@ static constexpr size_t display_height = 240; static constexpr size_t pixel_buffer_size = display_width*NUM_ROWS_IN_FRAME_BUFFER; std::shared_ptr display; +#if USE_GAMEBOY_GNUBOY // for gnuboy uint16_t* displayBuffer[2]; struct fb @@ -50,7 +51,7 @@ struct lcd }; static struct lcd lcd; int frame = 0; - +#endif //This function is called (in irq context!) just before a transmission starts. It will //set the D/C line to the value indicated in the user field. @@ -74,25 +75,30 @@ void lcd_spi_post_transfer_callback(spi_transaction_t *t) } -// TODO: see if IRAM_ATTR improves the display refresh frequency // create the lcd_write function -extern "C" void lcd_write(const uint8_t *data, size_t length, uint16_t user_data) { +static const int spi_queue_size = 10; +static spi_transaction_t ts_[spi_queue_size]; +static size_t ts_index = 0; +extern "C" void IRAM_ATTR lcd_write(const uint8_t *data, size_t length, uint16_t user_data) { if (length == 0) { // oddly the esp-idf-cxx spi driver asserts if we try to send 0 data... return; } esp_err_t ret; - spi_transaction_t t; // declared static so spi driver can still access it - memset(&t, 0, sizeof(t)); //Zero out the transaction - t.length=length*8; //Length is in bytes, transaction length is in bits. - t.tx_buffer=data; //Data - t.user=(void*)user_data; //whether or not to flush - ret=spi_device_polling_transmit(spi, &t); //Transmit! - // ret=spi_device_queue_trans(spi, &t, portMAX_DELAY); //Transmit! + //spi_transaction_t t; // declared static so spi driver can still access it + spi_transaction_t *t = &ts_[ts_index]; + memset(t, 0, sizeof(*t)); //Zero out the transaction + t->length=length*8; //Length is in bytes, transaction length is in bits. + t->tx_buffer=data; //Data + t->user=(void*)user_data; //whether or not to flush + // ret=spi_device_polling_transmit(spi, &t); //Transmit! + ret=spi_device_queue_trans(spi, t, portMAX_DELAY); //Transmit! // assert(ret==ESP_OK); //Should have had no issues. if (ret != ESP_OK) { fmt::print("Could not write to lcd: {} '{}'\n", ret, esp_err_to_name(ret)); } + ts_index++; + if (ts_index >= spi_queue_size) ts_index = 0; } #define U16x2toU32(m,l) ((((uint32_t)(l>>8|(l&0xFF)<<8))<<16)|(m>>8|(m&0xFF)<<8)) @@ -120,11 +126,11 @@ extern "C" void set_pixel(const uint16_t x, const uint16_t y, const uint16_t col } extern "C" uint16_t* get_vram0() { - return displayBuffer[0]; + return display->vram0(); } extern "C" uint16_t* get_vram1() { - return displayBuffer[1]; + return display->vram1(); } extern "C" void delay_us(size_t num_us) { @@ -182,7 +188,7 @@ extern "C" void lcd_init() { .mode=0, //SPI mode 0 .clock_speed_hz=60*1000*1000, //Clock out at 60 MHz .spics_io_num=GPIO_NUM_5, //CS pin - .queue_size=7, //We want to be able to queue 7 transactions at a time + .queue_size=spi_queue_size, //We want to be able to queue 7 transactions at a time .pre_cb=lcd_spi_pre_transfer_callback, .post_cb=lcd_spi_post_transfer_callback, }; @@ -218,6 +224,7 @@ extern "C" void lcd_init() { .software_rotation_enabled = true, }); initialized = true; +#if USE_GAMEBOY_GNUBOY // for gnuboy displayBuffer[0] = display->vram0(); displayBuffer[1] = display->vram1(); @@ -231,4 +238,5 @@ extern "C" void lcd_init() { fb.ptr = (uint8_t*)displayBuffer[0]; fb.enabled = 1; fb.dirty = 0; +#endif } diff --git a/main/gameboy.hpp b/main/gameboy.hpp index 5e684489..44b097be 100644 --- a/main/gameboy.hpp +++ b/main/gameboy.hpp @@ -3,6 +3,7 @@ #include #include "format.hpp" #include "spi_lcd.h" +#include "i2s_audio.h" #include "st7789.hpp" #ifdef USE_GAMEBOY_GAMEBOYCORE @@ -17,32 +18,48 @@ extern "C" { } #endif +static const size_t gameboy_screen_width = 160; + +void render_scanline(const gb::GPU::Scanline& scanline, int line) { + // fmt::print("Line {}\n", line); + // scanline is just std::array, where pixel is uint8_t r,g,b + // make array of lv_color_t + static const size_t num_lines_to_flush = 48; + static size_t num_lines = 0; + static auto color_data = get_vram0(); + size_t index = num_lines * gameboy_screen_width; + for (auto &pixel : scanline) { + color_data[index++] = make_color(pixel.r, pixel.g, pixel.b); + } + num_lines++; + if (num_lines == num_lines_to_flush) { + lcd_write_frame(0, line - num_lines, gameboy_screen_width, num_lines, (const uint8_t*)&color_data[0]); + num_lines = 0; + } +} + +void vblank_callback() { + // fmt::print("VBLANK\n"); +} + +void play_audio_sample(int16_t l, int16_t r) { + // fmt::print("L: {} R: {}\n", l, r); +} + void init_gameboy(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size) { - // WIDTH = 160, so 320-WIDTH is 160 - espp::St7789::set_offset((320-160) / 2, 0); + // WIDTH = gameboy_screen_width, so 320-WIDTH is gameboy_screen_width + espp::St7789::set_offset((320-gameboy_screen_width) / 2, (240-144) / 2); #ifdef USE_GAMEBOY_GAMEBOYCORE - fmt::print("GAMEBOY enabled: GAMEBOYCORE\n"); + fmt::print("GAMEBOY enabled: GAMEBOYCORE, loading {} ({} bytes)\n", rom_filename, rom_data_size); // Create an instance of the gameboy emulator core core = std::make_shared(); - // Set callbacks for video and audio - core->setScanlineCallback([](const gb::GPU::Scanline& scanline, int line){ - fmt::print("Line {}\n", line); - // scanline is just std::array, where pixel is uint8_t r,g,b - // make array of lv_color_t - static std::array color_data; - size_t index = 0; - for (auto &pixel : scanline) { - color_data[index++] = make_color(pixel.r, pixel.g, pixel.b); - } - lcd_write_frame(0, line, 160, 1, (const uint8_t*)&color_data[0]); - }); - core->setAudioSampleCallback([](int16_t l, int16_t r){ - fmt::print("L: {} R: {}\n", l, r); - }); - + core->setScanlineCallback(render_scanline); + core->setVBlankCallback(vblank_callback); + core->setAudioSampleCallback(play_audio_sample); + // now load the rom fmt::print("Opening rom {}!\n", rom_filename); core->loadROM(romdata, rom_data_size); #endif @@ -55,7 +72,8 @@ void init_gameboy(const std::string& rom_filename, uint8_t *romdata, size_t rom_ void run_gameboy_rom() { #ifdef USE_GAMEBOY_GAMEBOYCORE - fmt::print("gameboycore: emulating frame\n"); + // static size_t frame = 0; + // fmt::print("gameboycore: emulating frame {}\n", frame++); core->emulateFrame(); #endif #ifdef USE_GAMEBOY_GNUBOY diff --git a/main/nes.hpp b/main/nes.hpp index ad76e20f..109d9725 100644 --- a/main/nes.hpp +++ b/main/nes.hpp @@ -1,20 +1,24 @@ #pragma once +#ifdef USE_NES_NOFRENDO extern "C" { #include "event.h" #include "gui.h" #include } +nes_t* console_nes; + +#endif + #include #include "format.hpp" #include "spi_lcd.h" #include "st7789.hpp" -nes_t* console_nes; - void init_nes(const std::string& rom_filename, uint16_t* vram_ptr, uint8_t *romdata, size_t rom_data_size) { +#ifdef USE_NES_NOFRENDO espp::St7789::set_offset((320-NES_SCREEN_WIDTH) / 2, 0); static bool initialized = false; @@ -34,13 +38,18 @@ void init_nes(const std::string& rom_filename, uint16_t* vram_ptr, uint8_t *romd nes_insertcart(rom_filename.c_str(), console_nes); vid_setmode(NES_SCREEN_WIDTH, NES_VISIBLE_HEIGHT); +#endif } void run_nes_rom() { +#ifdef USE_NES_NOFRENDO nes_emulate(); +#endif } void deinit_nes() { +#ifdef USE_NES_NOFRENDO nes_poweroff(); nes_destroy(&console_nes); +#endif } From 6949e4f2a71c1aedf39a80557018483b701c6df6 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 5 Nov 2022 16:32:58 -0500 Subject: [PATCH 2/9] revert gameboycore back to default (working) state which uses a _lot_ of RAM (mbc). --- .../gameboycore/include/gameboycore/mbc.h | 4 +--- components/gameboycore/src/mbc.cpp | 20 ++++++++----------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/components/gameboycore/include/gameboycore/mbc.h b/components/gameboycore/include/gameboycore/mbc.h index 93128aac..86e48dc7 100644 --- a/components/gameboycore/include/gameboycore/mbc.h +++ b/components/gameboycore/include/gameboycore/mbc.h @@ -123,9 +123,7 @@ namespace gb virtual void control(uint8_t value, uint16_t addr) = 0; //! virtual memory - // std::vector memory_; - uint8_t *memory_; - size_t memory_size_; + std::vector memory_; //! Flag inidicating if external ram is enabled bool xram_enable_; //! ROM bank number diff --git a/components/gameboycore/src/mbc.cpp b/components/gameboycore/src/mbc.cpp index 6ab77229..d16b2b1c 100644 --- a/components/gameboycore/src/mbc.cpp +++ b/components/gameboycore/src/mbc.cpp @@ -85,15 +85,13 @@ namespace gb { auto start_idx = getIndex(start, rom_bank_, ram_bank_); auto end_idx = getIndex(end, rom_bank_, ram_bank_); - // return std::vector(memory_.begin() + start_idx, memory_.begin() + end_idx); - return std::vector(&memory_[0] + start_idx, &memory_[0] + end_idx); + return std::vector(memory_.begin() + start_idx, memory_.begin() + end_idx); } void MBC::setMemory(uint16_t start, const std::vector& mem) { // TODO: error checks - // std::copy(mem.begin(), mem.end(), memory_.begin() + getIndex(start, rom_bank_, ram_bank_)); - std::copy(mem.begin(), mem.end(), &memory_[0] + getIndex(start, rom_bank_, ram_bank_)); + std::copy(mem.begin(), mem.end(), memory_.begin() + getIndex(start, rom_bank_, ram_bank_)); } std::vector MBC::getXram() const @@ -103,8 +101,7 @@ namespace gb auto end = getIndex(memorymap::EXTERNAL_RAM_END, rom_bank_, num_cartridge_ram_banks_ - 1); // Copy external RAM range. Add 1 so range [START, END] is inclusive - // return std::vector(memory_.begin() + start, memory_.begin() + end + 1); - return std::vector(&memory_[0] + start, &memory_[0] + end + 1); + return std::vector(memory_.begin() + start, memory_.begin() + end + 1); } int MBC::getRomBank() const @@ -216,10 +213,10 @@ namespace gb const auto high_ram = kilo(8); // $E000 - $FFFF const auto memory_size = rom_bank0_fixed + rom_switchable + vram + ram_cartridge_switchable + ram_bank0_fixed + ram_internal_switchable + high_ram; - memory_size_ = memory_size; - // memory_.resize(memory_size); - // std::memcpy((char*)&memory_[0], rom, size); + memory_.resize(memory_size); + + std::memcpy((char*)&memory_[0], rom, size); } unsigned int MBC::kilo(unsigned int n) const @@ -229,12 +226,11 @@ namespace gb std::size_t MBC::getVirtualMemorySize() const { - // return memory_.size(); - return memory_size_; + return memory_.size(); } MBC::~MBC() { } } -} +} \ No newline at end of file From a696ef1b86aecb80628b126e6d8513042a03f03d Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sun, 6 Nov 2022 20:54:55 -0600 Subject: [PATCH 3/9] go back to blocking writes; update dependencies to add touch and type headers. --- components/box-emu-hal/CMakeLists.txt | 2 +- components/box-emu-hal/include/spi_lcd.h | 1 + components/box-emu-hal/src/spi_lcd.cpp | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/box-emu-hal/CMakeLists.txt b/components/box-emu-hal/CMakeLists.txt index c9a5dc0e..d4bdd2ec 100644 --- a/components/box-emu-hal/CMakeLists.txt +++ b/components/box-emu-hal/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( INCLUDE_DIRS "include" SRC_DIRS "src" - REQUIRES "driver" "esp_lcd" "spi_flash" "nvs_flash" "codec" "display" "display_drivers" "controller" "ads1x15" "qwiicnes" "input_drivers" "ft5x06" "tt21100" + REQUIRES "driver" "heap" "esp_lcd" "esp_psram" "spi_flash" "nvs_flash" "codec" "display" "display_drivers" "controller" "ads1x15" "qwiicnes" "input_drivers" "ft5x06" "tt21100" ) diff --git a/components/box-emu-hal/include/spi_lcd.h b/components/box-emu-hal/include/spi_lcd.h index 628a1540..1d14c426 100644 --- a/components/box-emu-hal/include/spi_lcd.h +++ b/components/box-emu-hal/include/spi_lcd.h @@ -14,6 +14,7 @@ #pragma once #include +#include //***************************************************************************** // diff --git a/components/box-emu-hal/src/spi_lcd.cpp b/components/box-emu-hal/src/spi_lcd.cpp index 4e1221a4..f6a1226c 100644 --- a/components/box-emu-hal/src/spi_lcd.cpp +++ b/components/box-emu-hal/src/spi_lcd.cpp @@ -91,9 +91,8 @@ extern "C" void IRAM_ATTR lcd_write(const uint8_t *data, size_t length, uint16_t t->length=length*8; //Length is in bytes, transaction length is in bits. t->tx_buffer=data; //Data t->user=(void*)user_data; //whether or not to flush - // ret=spi_device_polling_transmit(spi, &t); //Transmit! - ret=spi_device_queue_trans(spi, t, portMAX_DELAY); //Transmit! - // assert(ret==ESP_OK); //Should have had no issues. + ret=spi_device_polling_transmit(spi, t); //Transmit! + // ret=spi_device_queue_trans(spi, t, portMAX_DELAY); //Transmit! if (ret != ESP_OK) { fmt::print("Could not write to lcd: {} '{}'\n", ret, esp_err_to_name(ret)); } From 120017167f85ec22b391db402ddadacb89d3c177 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sun, 6 Nov 2022 20:55:20 -0600 Subject: [PATCH 4/9] deinit gameboy --- main/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/main/main.cpp b/main/main.cpp index f95da459..8bf20aff 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -107,6 +107,7 @@ extern "C" void app_main(void) { while (!user_quit()) { run_gameboy_rom(); } + deinit_gameboy(); break; case Emulator::NES: init_nes(rom_filename, display->vram0(), romdata, rom_size_bytes); From 353e85877ac6d7da1ea2a697a3e9e9373cb272d0 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sun, 6 Nov 2022 22:10:41 -0600 Subject: [PATCH 5/9] testing libgbc library which is about 2x as fast as gameboycore. --- CMakeLists.txt | 4 +- components/libgbc/CMakeLists.txt | 9 + components/libgbc/include/apu.hpp | 46 ++ components/libgbc/include/common.hpp | 51 ++ components/libgbc/include/cpu.hpp | 114 ++++ components/libgbc/include/generators.hpp | 21 + components/libgbc/include/gpu.hpp | 184 +++++ components/libgbc/include/instruction.hpp | 27 + components/libgbc/include/interrupt.hpp | 22 + components/libgbc/include/io.hpp | 170 +++++ components/libgbc/include/machine.hpp | 82 +++ components/libgbc/include/mbc.hpp | 76 +++ components/libgbc/include/mbc1m.hpp | 36 + components/libgbc/include/mbc3.hpp | 29 + components/libgbc/include/mbc5.hpp | 31 + components/libgbc/include/memory.hpp | 109 +++ components/libgbc/include/printers.hpp | 58 ++ components/libgbc/include/registers.hpp | 199 ++++++ components/libgbc/include/sprite.hpp | 69 ++ components/libgbc/include/tiledata.hpp | 65 ++ components/libgbc/include/tracing.hpp | 14 + components/libgbc/src/apu.cpp | 54 ++ components/libgbc/src/cpu.cpp | 621 +++++++++++++++++ components/libgbc/src/debug.cpp | 319 +++++++++ components/libgbc/src/gpu.cpp | 428 ++++++++++++ components/libgbc/src/instructions.cpp | 778 ++++++++++++++++++++++ components/libgbc/src/io.cpp | 234 +++++++ components/libgbc/src/io_regs.cpp | 235 +++++++ components/libgbc/src/machine.cpp | 84 +++ components/libgbc/src/mbc.cpp | 279 ++++++++ components/libgbc/src/memory.cpp | 225 +++++++ main/gameboy.hpp | 116 +++- 32 files changed, 4770 insertions(+), 19 deletions(-) create mode 100644 components/libgbc/CMakeLists.txt create mode 100644 components/libgbc/include/apu.hpp create mode 100644 components/libgbc/include/common.hpp create mode 100644 components/libgbc/include/cpu.hpp create mode 100644 components/libgbc/include/generators.hpp create mode 100644 components/libgbc/include/gpu.hpp create mode 100644 components/libgbc/include/instruction.hpp create mode 100644 components/libgbc/include/interrupt.hpp create mode 100644 components/libgbc/include/io.hpp create mode 100644 components/libgbc/include/machine.hpp create mode 100644 components/libgbc/include/mbc.hpp create mode 100644 components/libgbc/include/mbc1m.hpp create mode 100644 components/libgbc/include/mbc3.hpp create mode 100644 components/libgbc/include/mbc5.hpp create mode 100644 components/libgbc/include/memory.hpp create mode 100644 components/libgbc/include/printers.hpp create mode 100644 components/libgbc/include/registers.hpp create mode 100644 components/libgbc/include/sprite.hpp create mode 100644 components/libgbc/include/tiledata.hpp create mode 100644 components/libgbc/include/tracing.hpp create mode 100644 components/libgbc/src/apu.cpp create mode 100644 components/libgbc/src/cpu.cpp create mode 100644 components/libgbc/src/debug.cpp create mode 100644 components/libgbc/src/gpu.cpp create mode 100644 components/libgbc/src/instructions.cpp create mode 100644 components/libgbc/src/io.cpp create mode 100644 components/libgbc/src/io_regs.cpp create mode 100644 components/libgbc/src/machine.cpp create mode 100644 components/libgbc/src/mbc.cpp create mode 100644 components/libgbc/src/memory.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 234aae24..22e2f67f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,9 @@ set(EXTRA_COMPONENT_DIRS # set(NES_COMPONENTS "nofrendo nofrendo-esp32") ### GBC ### -set(GBC_COMPONENTS "gameboycore") +set(GBC_COMPONENTS "libgbc") +# set(GBC_COMPONENTS "gameboy") +# set(GBC_COMPONENTS "gameboycore") # set(GBC_COMPONENTS "gnuboy") ### SMS ### diff --git a/components/libgbc/CMakeLists.txt b/components/libgbc/CMakeLists.txt new file mode 100644 index 00000000..0d7e604b --- /dev/null +++ b/components/libgbc/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register( + INCLUDE_DIRS "include" + SRC_DIRS "src" + REQUIRES box-emu-hal + ) + +target_compile_options(${COMPONENT_LIB} PRIVATE -Wall -Wextra -O2 -Ofast) +# target_compile_definitions(${COMPONENT_LIB} PRIVATE GAMEBRO_INDEXED_FRAME) +target_compile_definitions(${COMPONENT_LIB} PUBLIC USE_GAMEBOY_LIBGBC) diff --git a/components/libgbc/include/apu.hpp b/components/libgbc/include/apu.hpp new file mode 100644 index 00000000..dd3fdef6 --- /dev/null +++ b/components/libgbc/include/apu.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "common.hpp" +#include +#include +#include + +namespace gbc +{ +class APU +{ +public: + APU(Machine& mach); + using audio_stream_t = std::function; + + void on_audio_out(audio_stream_t); + void simulate(); + + uint8_t read(uint16_t, uint8_t& reg); + void write(uint16_t, uint8_t, uint8_t& reg); + + // serialization + int restore_state(const std::vector&, int); + void serialize_state(std::vector&) const; + + Machine& machine() noexcept { return m_machine; } + +private: + struct generator_t + { + }; + struct channel_t + { + bool generators_enabled[4] = {false}; + bool enabled = true; + }; + + struct state_t + { + bool nothing = false; + + } m_state; + + Machine& m_machine; + audio_stream_t m_audio_out; +}; +} // namespace gbc diff --git a/components/libgbc/include/common.hpp b/components/libgbc/include/common.hpp new file mode 100644 index 00000000..2cfaeeb1 --- /dev/null +++ b/components/libgbc/include/common.hpp @@ -0,0 +1,51 @@ +#pragma once +#include +#include + +#ifndef LIKELY +#define LIKELY(x) __builtin_expect((x), 1) +#endif +#ifndef UNLIKELY +#define UNLIKELY(x) __builtin_expect((x), 0) +#endif + +namespace gbc +{ +class CPU; +class Machine; +class Memory; +class IO; +constexpr bool ENABLE_GBC = true; + +inline void setflag(bool expr, uint8_t& flg, uint8_t mask) +{ + if (expr) + flg |= mask; + else + flg &= ~mask; +} +extern void assert_failed(const int expr, const char* strexpr, + const char* filename, const int line); + +class MachineException : public std::exception +{ +public: + MachineException(const char* m) + : message{m} {} + + const char* what() const noexcept override + { + return message; + } +private: + const char* const message; +}; + +} // namespace gbc + + +#define GBC_ASSERT(expr) \ + { if (!(expr)) { \ + gbc::assert_failed(expr, #expr, __FILE__, __LINE__); \ + __builtin_unreachable(); \ + } } diff --git a/components/libgbc/include/cpu.hpp b/components/libgbc/include/cpu.hpp new file mode 100644 index 00000000..4b643300 --- /dev/null +++ b/components/libgbc/include/cpu.hpp @@ -0,0 +1,114 @@ +#pragma once +#include "instruction.hpp" +#include "interrupt.hpp" +#include "registers.hpp" +#include "tracing.hpp" +#include +#include +#include +#include + +namespace gbc +{ +class Machine; +class Memory; + +class CPU +{ +public: + CPU(Machine&) noexcept; + void reset() noexcept; + void simulate(); + uint64_t gettime() const noexcept { return m_state.cycles_total; } + + void execute(); + // read and increment PC, and cycle counters, then tick hardware + uint8_t readop8(); + uint16_t readop16(); + // peek at operands past PC without doing anything else + uint8_t peekop8(int disp); + uint16_t peekop16(int disp); + // ticking memory reads & writes + uint8_t mtread8(uint16_t addr); + void mtwrite8(uint16_t addr, uint8_t value); + uint16_t mtread16(uint16_t addr); + void mtwrite16(uint16_t addr, uint16_t value); + // perform one hardware tick + void hardware_tick(); + void incr_cycles(int count); + void push_value(uint16_t addr); + void push_and_jump(uint16_t addr); + void jump(uint16_t dest); + void stop(); + void wait(); // wait for interrupts + void buggy_halt(); + instruction_t& decode(uint8_t opcode); + + regs_t& registers() noexcept { return m_state.registers; } + // helpers for reading and writing (HL) + uint8_t read_hl(); + void write_hl(uint8_t); + + Memory& memory() noexcept { return m_memory; } + Machine& machine() noexcept { return m_machine; } + + void enable_interrupts() noexcept; + void disable_interrupts() noexcept; + bool ime() const noexcept { return m_state.ime; } + + bool is_stopping() const noexcept { return m_state.stopped; } + bool is_halting() const noexcept { return m_state.asleep; } + + // serialization + int restore_state(const std::vector&, int); + void serialize_state(std::vector&) const; + + // debugging + void breakpoint(uint16_t address, breakpoint_t func); + auto& breakpoints() { return this->m_breakpoints; } + void default_pausepoint(uint16_t address); + void break_on_steps(int steps); + void break_now() { this->m_break = true; } + void break_checks(); + bool is_breaking() const noexcept { return this->m_break; } + static void print_and_pause(CPU&, const uint8_t opcode); + + std::string to_string() const; + +private: + void handle_interrupts(); + void handle_speed_switch(); + void execute_interrupts(const uint8_t); + bool break_time() const; + void interrupt(interrupt_t&); + + Machine& m_machine; + Memory& m_memory; + struct state_t + { + regs_t registers; + uint64_t cycles_total = 0; + uint8_t last_flags = 0xff; + int8_t intr_pending = 0; + bool ime = false; + bool stopped = false; + bool asleep = false; + bool haltbug = false; + uint8_t switch_cycles = 0; + } m_state; + // debugging + bool m_break = false; + mutable int16_t m_break_steps = 0; + mutable int16_t m_break_steps_cnt = 0; + std::map m_breakpoints; +}; + +inline void CPU::breakpoint(uint16_t addr, breakpoint_t func) { this->m_breakpoints[addr] = func; } + +inline void CPU::default_pausepoint(const uint16_t addr) +{ + this->breakpoint(addr, breakpoint_t{[](gbc::CPU& cpu, const uint8_t opcode) { + print_and_pause(cpu, opcode); + }}); +} +} // namespace gbc diff --git a/components/libgbc/include/generators.hpp b/components/libgbc/include/generators.hpp new file mode 100644 index 00000000..f104ce32 --- /dev/null +++ b/components/libgbc/include/generators.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace gbc +{ +struct sample_t +{ + uint16_t left; + uint16_t right; +}; +struct Generator +{ + virtual void tick(Machine&) = 0; + virtual sample_t sample(Machine&) = 0; +}; + +struct WhiteNoise : public Generator +{ + void tick(Machine& machine) override {} + sample_t sample(Machine& machine) override { return {0, 0}; } +}; +} // namespace gbc diff --git a/components/libgbc/include/gpu.hpp b/components/libgbc/include/gpu.hpp new file mode 100644 index 00000000..d52f98b8 --- /dev/null +++ b/components/libgbc/include/gpu.hpp @@ -0,0 +1,184 @@ +#pragma once +#include "common.hpp" +#include "sprite.hpp" +#include "tiledata.hpp" +#include +#include + +#include "spi_lcd.h" + +namespace gbc +{ +enum dmg_variant_t +{ + LIGHTER_GREEN = 0, + DARKER_GREEN, + GRAYSCALE +}; +class GPU +{ +public: + static const int SCREEN_W = 160; + static const int SCREEN_H = 144; + static const int NUM_PALETTES = 64; + // this palette idx is used when the screen is off + static const int WHITE_IDX = 32; +#ifdef GAMEBRO_INDEXED_FRAME + using PixelType = uint8_t; +#else + using PixelType = uint16_t; +#endif + + GPU(Machine&) noexcept; + void reset() noexcept; + void simulate(); + // the vector is resized to exactly fit the screen + const auto& pixels() const noexcept { return m_pixels; } + // trap on palette changes + using palchange_func_t = std::function; + void on_palchange(palchange_func_t func) { m_on_palchange = func; } + // get default GB palette + static std::array dmg_colors(dmg_variant_t = GRAYSCALE); + // set GB palette used in RGBA mode + void set_dmg_variant(dmg_variant_t); + // get the 32-bit RGB colors (with alpha=0) + uint32_t expand_cgb_color(uint8_t idx) const noexcept; + uint32_t expand_dmg_color(uint8_t idx) const noexcept; + static uint32_t color15_to_rgba32(uint16_t color15); + // enable / disable scanline rendering + void scanline_rendering(bool en) noexcept { this->m_render = en; } + // render whole frame now (NOTE: changes are often made mid-frame!) + void render_frame(); + + bool is_vblank() const noexcept; + bool is_hblank() const noexcept; + int current_scanline() const noexcept { return m_state.current_scanline; } + uint64_t frame_count() const noexcept { return m_state.frame_count; } + + void set_mode(uint8_t mode); + uint8_t get_mode() const noexcept; + + uint16_t video_offset() const noexcept { return m_state.video_offset; } + void set_video_bank(uint8_t bank); + void lcd_power_changed(bool state); + + bool lcd_enabled() const noexcept; + bool window_enabled() const noexcept; + std::pair window_size(); + bool window_visible(); + int window_x(); + int window_y(); + + // CGB palette registers + uint8_t& getpal(uint16_t index) noexcept { return m_state.cgb_palette[index]; } + void setpal(uint16_t index, uint8_t value); + uint8_t getpal(uint16_t index) const { return m_state.cgb_palette.at(index); } + + // serialization + int restore_state(const std::vector&, int); + void serialize_state(std::vector&) const; + + Machine& machine() noexcept { return m_memory.machine(); } + Memory& memory() noexcept { return m_memory; } + IO& io() noexcept { return m_io; } + const Memory& memory() const noexcept { return m_memory; } + std::vector dump_background(); + std::vector dump_window(); + std::vector dump_tiles(int bank); + // OAM sprite inspection + const Sprite* sprites_begin() const noexcept; + const Sprite* sprites_end() const noexcept; + +private: + uint64_t scanline_cycles() const noexcept; + uint64_t oam_cycles() const noexcept; + uint64_t vram_cycles() const noexcept; + uint64_t hblank_cycles() const noexcept; + void render_scanline(int y); + void do_ly_comparison(); + TileData create_tiledata(uint16_t tiles, uint16_t patt); + tileconf_t tile_config(); + sprite_config_t sprite_config(); + std::vector find_sprites(const sprite_config_t&) const; + uint16_t colorize_tile(const tileconf_t&, uint8_t attr, uint8_t idx); + uint16_t colorize_sprite(const Sprite*, sprite_config_t&, uint8_t); + // addresses + uint16_t bg_tiles() const noexcept; + uint16_t window_tiles() const noexcept; + uint16_t tile_data() const noexcept; + + Memory& m_memory; + IO& m_io; + uint8_t& m_reg_lcdc; + uint8_t& m_reg_stat; + uint8_t& m_reg_ly; + std::vector m_pixels; + palchange_func_t m_on_palchange = nullptr; + dmg_variant_t m_variant = LIGHTER_GREEN; + bool m_render = true; + + struct state_t + { + uint64_t period = 0; + uint64_t frame_count = 0; + int current_scanline = 0; + uint16_t video_offset = 0x0; + bool white_frame = false; + // 0-63: tiles 64-127: sprites + std::array cgb_palette; + } m_state; +}; + +inline std::array GPU::dmg_colors(dmg_variant_t variant) +{ +#define mRGB(r, g, b) (make_color(r,g,b)) + switch (variant) + { + case LIGHTER_GREEN: + return std::array{ + mRGB(224, 248, 208), // least green + mRGB(136, 192, 112), // less green + mRGB(52, 104, 86), // very green + mRGB(8, 24, 32) // dark green + }; + case DARKER_GREEN: + return std::array{ + mRGB(175, 203, 70), // least green + mRGB(121, 170, 109), // less green + mRGB(34, 111, 95), // very green + mRGB(8, 41, 85) // dark green + }; + case GRAYSCALE: + default: + return std::array{ + mRGB(232, 232, 232), // least gray + mRGB(160, 160, 160), // less gray + mRGB(88, 88, 88), // very gray + mRGB(16, 16, 16) // dark gray + }; + } +#undef mRGB +} + +// convert palette to grayscale colors +inline uint32_t GPU::expand_dmg_color(const uint8_t index) const noexcept +{ + return dmg_colors(m_variant).at(index); +} +// convert 15-bit color to 32-bit RGBA +inline uint32_t GPU::expand_cgb_color(const uint8_t index) const noexcept +{ + const uint16_t color15 = getpal(index * 2) | (getpal(index * 2 + 1) << 8); + const uint16_t r = ((color15 >> 0) & 0x1f) << 3; + const uint16_t g = ((color15 >> 5) & 0x1f) << 3; + const uint16_t b = ((color15 >> 10) & 0x1f) << 3; + return r | (g << 8) | (b << 16); +} +inline uint32_t GPU::color15_to_rgba32(const uint16_t color15) +{ + const uint16_t r = ((color15 >> 0) & 0x1f) << 3; + const uint16_t g = ((color15 >> 5) & 0x1f) << 3; + const uint16_t b = ((color15 >> 10) & 0x1f) << 3; + return (r << 0u) | (g << 8u) | (b << 16u) | (255ul << 24u); +} +} // namespace gbc diff --git a/components/libgbc/include/instruction.hpp b/components/libgbc/include/instruction.hpp new file mode 100644 index 00000000..d296cd63 --- /dev/null +++ b/components/libgbc/include/instruction.hpp @@ -0,0 +1,27 @@ +#pragma once +#include + +namespace gbc +{ +class CPU; +using handler_t = void (*)(CPU&, uint8_t); +using printer_t = int (*)(char*, size_t, CPU&, uint8_t); + +enum alu_t : uint8_t +{ + ADD = 0, + ADC, + SUB, + SBC, + AND, + XOR, + OR, + CP +}; + +struct instruction_t +{ + const handler_t handler; + const printer_t printer; +}; +} // namespace gbc diff --git a/components/libgbc/include/interrupt.hpp b/components/libgbc/include/interrupt.hpp new file mode 100644 index 00000000..5279e51c --- /dev/null +++ b/components/libgbc/include/interrupt.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +namespace gbc +{ +class Machine; +struct interrupt_t; +using interrupt_handler = std::function; + +struct interrupt_t +{ + const uint8_t mask; + const uint16_t fixed_address; + const char* const name = ""; + interrupt_handler callback = nullptr; + + interrupt_t(uint8_t msk, uint16_t addr, const char* n) : mask(msk), fixed_address(addr), name(n) + {} +}; + +} // namespace gbc diff --git a/components/libgbc/include/io.hpp b/components/libgbc/include/io.hpp new file mode 100644 index 00000000..d0dd5be6 --- /dev/null +++ b/components/libgbc/include/io.hpp @@ -0,0 +1,170 @@ +#pragma once +#include "common.hpp" +#include "interrupt.hpp" +#include +#include + +namespace gbc +{ +class IO +{ +public: + static const uint16_t SND_START = 0xff10; + static const uint16_t SND_END = 0xff40; + enum regnames_t + { + REG_P1 = 0xff00, + // TIMER + REG_DIV = 0xff04, + REG_TIMA = 0xff05, + REG_TMA = 0xff06, + REG_TAC = 0xff07, + // SOUND + REG_NR10 = 0xff10, + REG_NR11 = 0xff11, + REG_NR12 = 0xff12, + REG_NR13 = 0xff13, + REG_NR14 = 0xff14, + REG_NR21 = 0xff16, + REG_NR22 = 0xff17, + REG_NR23 = 0xff18, + REG_NR24 = 0xff19, + REG_NR30 = 0xff1a, + REG_NR31 = 0xff1b, + REG_NR32 = 0xff1c, + REG_NR33 = 0xff1d, + REG_NR34 = 0xff1e, + REG_NR41 = 0xff20, + REG_NR42 = 0xff21, + REG_NR43 = 0xff22, + REG_NR44 = 0xff23, + REG_NR50 = 0xff24, + REG_NR51 = 0xff25, + REG_NR52 = 0xff26, + REG_WAV0 = 0xff30, + REG_WAVF = 0xff3f, + // LCD + REG_LCDC = 0xff40, + REG_STAT = 0xff41, + REG_SCY = 0xff42, + REG_SCX = 0xff43, + REG_LY = 0xff44, + REG_LYC = 0xff45, + REG_DMA = 0xff46, + // PALETTE + REG_BGP = 0xff47, + REG_OBP0 = 0xff48, + REG_OBP1 = 0xff49, + REG_WY = 0xff4a, + REG_WX = 0xff4b, + + // CBG I/O regs + REG_KEY1 = 0xff4d, + REG_VBK = 0xff4f, + REG_BOOT = 0xff50, + + REG_HDMA1 = 0xff51, + REG_HDMA2 = 0xff52, + REG_HDMA3 = 0xff53, + REG_HDMA4 = 0xff54, + REG_HDMA5 = 0xff55, + + REG_BGPI = 0xff68, + REG_BGPD = 0xff69, + REG_OBPI = 0xff6a, + REG_OBPD = 0xff6b, + + REG_SVBK = 0xff70, + + // INTERRUPTS + REG_IF = 0xff0f, + REG_IE = 0xffff, + }; + + IO(Machine&); + void write_io(const uint16_t, uint8_t); + uint8_t read_io(const uint16_t); + + void trigger_keys(uint8_t); + bool joypad_is_disabled() const noexcept; + void trigger(interrupt_t&); + uint8_t interrupt_mask() const; + void start_dma(uint16_t src); + void start_hdma(uint16_t src, uint16_t dst, uint16_t bytes); + bool dma_active() const noexcept { return oam_dma().bytes_left > 0; } + bool hdma_active() const noexcept { return hdma().bytes_left > 0; } + + void perform_stop(); + void deactivate_stop(); + void reset_divider(); + + Machine& machine() noexcept { return m_machine; } + + void reset(); + void simulate(); + + inline uint8_t& reg(const uint16_t addr) { return m_state.ioregs[addr & 0x7f]; } + inline const uint8_t& reg(const uint16_t addr) const { return m_state.ioregs[addr & 0x7f]; } + + struct joypad_t + { + uint8_t ioswitch = 0; + uint8_t keypad = 0xFF; + uint8_t buttons = 0xFF; + uint8_t last_mask = 0; + }; + inline joypad_t& joypad() { return m_state.joypad; } + + using joypad_read_handler_t = std::function; + void on_joypad_read(joypad_read_handler_t h) { m_jp_handler = h; } + void trigger_joypad_read() + { + if (m_jp_handler) m_jp_handler(machine(), joypad().ioswitch); + } + + interrupt_t vblank; + interrupt_t lcd_stat; + interrupt_t timerint; + interrupt_t serialint; + interrupt_t joypadint; + interrupt_t debugint; + + // serialization + int restore_state(const std::vector&, int); + void serialize_state(std::vector&) const; + +private: + struct dma_t + { + uint64_t cur_line; + int8_t slow_start = 0; + uint16_t src; + uint16_t dst; + int32_t bytes_left = 0; + }; + const dma_t& oam_dma() const noexcept { return m_state.dma; } + dma_t& oam_dma() noexcept { return m_state.dma; } + const dma_t& hdma() const noexcept { return m_state.hdma; } + dma_t& hdma() noexcept { return m_state.hdma; } + + Machine& m_machine; + struct state_t + { + std::array ioregs = {}; + joypad_t joypad; + uint16_t divider = 0; + uint16_t timabug = 0; + // LCD on/off during STOP? + bool lcd_powered = false; + uint8_t reg_ie = 0x0; + + dma_t dma; + dma_t hdma; + } m_state; + + joypad_read_handler_t m_jp_handler = nullptr; +}; + +inline void IO::trigger(interrupt_t& intr) { this->reg(REG_IF) |= intr.mask; } +inline uint8_t IO::interrupt_mask() const { return this->m_state.reg_ie & this->reg(REG_IF); } +} // namespace gbc diff --git a/components/libgbc/include/machine.hpp b/components/libgbc/include/machine.hpp new file mode 100644 index 00000000..c12d14f1 --- /dev/null +++ b/components/libgbc/include/machine.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "apu.hpp" +#include "cpu.hpp" +#include "gpu.hpp" +#include "interrupt.hpp" +#include "io.hpp" +#include "memory.hpp" + +namespace gbc +{ +enum keys_t +{ + DPAD_RIGHT = 0x1, + DPAD_LEFT = 0x2, + DPAD_UP = 0x4, + DPAD_DOWN = 0x8, + BUTTON_A = 0x10, + BUTTON_B = 0x20, + BUTTON_SELECT = 0x40, + BUTTON_START = 0x80 +}; + +class Machine +{ +public: + Machine(const uint8_t *rom, size_t rom_data_size, bool init = true) + : Machine(std::string_view{(const char *)rom, rom_data_size}, init) {} + Machine(const std::string_view rom, bool init = true); + Machine(const std::vector& rom, bool init = true) + : Machine(std::string_view{(const char *)rom.data(), rom.size()}, init) {} + + CPU cpu; + Memory memory; + IO io; + GPU gpu; + APU apu; + + void simulate(); + void simulate_one_frame(); + void reset(); + uint64_t now() noexcept; + bool is_running() const noexcept { return this->m_running; } + bool is_cgb() const noexcept { return this->m_cgb_mode; } + + // set delegates to be notified on interrupts + enum interrupt + { + VBLANK, + TIMER, + JOYPAD, + DEBUG, + }; + void set_handler(interrupt, interrupt_handler); + + // use keys_t to form an 8-bit mask + void set_inputs(uint8_t mask); + + // serialization (state-keeping) + size_t restore_state(const std::vector&); + void serialize_state(std::vector&) const; + + /// debugging aids /// + bool verbose_instructions = false; + bool verbose_interrupts = false; + bool verbose_banking = false; + // make the machine stop when an undefined OP happens + bool stop_when_undefined = false; + bool break_on_interrupts = false; + bool break_on_io = false; + void break_now(); + bool is_breaking() const noexcept; + void undefined(); + void stop() noexcept; + +private: + bool m_running = true; + bool m_cgb_mode = false; +}; + +inline void Machine::simulate() { cpu.simulate(); } +} // namespace gbc diff --git a/components/libgbc/include/mbc.hpp b/components/libgbc/include/mbc.hpp new file mode 100644 index 00000000..4b123dac --- /dev/null +++ b/components/libgbc/include/mbc.hpp @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace gbc +{ +class Memory; + +class MBC +{ +public: + using range_t = std::pair; + static constexpr range_t ROMbank0{0x0000, 0x4000}; + static constexpr range_t ROMbankX{0x4000, 0x8000}; + static constexpr range_t RAMbankX{0xA000, 0xC000}; + static constexpr range_t WRAM_0{0xC000, 0xD000}; + static constexpr range_t WRAM_bX{0xD000, 0xE000}; + static constexpr range_t EchoRAM{0xE000, 0xFE00}; + + MBC(Memory&, const std::string_view rom); + + const auto& rom() const noexcept { return m_rom; } + uint32_t rombank_offset() const noexcept { return m_state.rom_bank_offset; } + + bool ram_enabled() const noexcept { return m_state.ram_enabled; } + size_t rombank_size() const noexcept { return 0x4000; } + size_t rambank_size() const noexcept { return 0x2000; } + size_t wrambank_size() const noexcept { return 0x1000; } + + uint8_t read(uint16_t addr); + void write(uint16_t addr, uint8_t value); + + void set_rombank(int offset); + void set_rambank(int offset); + void set_wrambank(int offset); + void set_mode(int mode); + + // serialization + int restore_state(const std::vector&, int); + void serialize_state(std::vector&) const; + +private: + void write_MBC1M(uint16_t, uint8_t); + void write_MBC3(uint16_t, uint8_t); + void write_MBC5(uint16_t, uint8_t); + bool verbose_banking() const noexcept; + + Memory& m_memory; + const std::string_view m_rom; + struct state_t + { + uint32_t rom_bank_offset = 0x4000; + uint16_t ram_banks = 0; + uint16_t ram_bank_offset = 0x0; + uint32_t ram_bank_size = 0x0; + uint16_t wram_offset = 0x1000; + uint16_t wram_size = 0x2000; + bool ram_enabled = false; + bool rtc_enabled = false; + bool rumble = false; + uint16_t rom_bank_reg = 0x1; + uint8_t mode_select = 0; + uint8_t version = 1; + std::array wram; + } m_state; + // RAM is so big we want to deal with it dynamically + std::array m_ram; + + friend class Memory; + void init(); +}; +} // namespace gbc diff --git a/components/libgbc/include/mbc1m.hpp b/components/libgbc/include/mbc1m.hpp new file mode 100644 index 00000000..6c4b68ad --- /dev/null +++ b/components/libgbc/include/mbc1m.hpp @@ -0,0 +1,36 @@ +#include "mbc.hpp" + +#include "machine.hpp" +#include "memory.hpp" + +namespace gbc +{ +inline void MBC::write_MBC1M(uint16_t addr, uint8_t value) +{ + switch (addr & 0xF000) + { + case 0x2000: + case 0x3000: + // ROM bank number + this->m_state.rom_bank_reg &= 0x60; + this->m_state.rom_bank_reg |= value & 0x1F; + // lower 5 bits cant be 0 + if ((value & 0x1F) == 0) { this->m_state.rom_bank_reg++; } + this->set_rombank(this->m_state.rom_bank_reg); + return; + case 0x4000: + case 0x5000: + // ROM / RAM bank select + if (this->m_state.mode_select == 1) { this->set_rambank(value & 0x3); } + // always changed ROM bank value + this->m_state.rom_bank_reg &= 0x1F; + this->m_state.rom_bank_reg |= (value & 0x3) << 5; + this->set_rombank(this->m_state.rom_bank_reg); + return; + case 0x6000: + case 0x7000: + // RAM / ROM mode select + this->set_mode(value & 0x1); + } +} +} // namespace gbc diff --git a/components/libgbc/include/mbc3.hpp b/components/libgbc/include/mbc3.hpp new file mode 100644 index 00000000..cd184170 --- /dev/null +++ b/components/libgbc/include/mbc3.hpp @@ -0,0 +1,29 @@ +#include "mbc.hpp" + +#include "machine.hpp" +#include "memory.hpp" + +namespace gbc +{ +inline void MBC::write_MBC3(uint16_t addr, uint8_t value) +{ + switch (addr & 0xF000) + { + case 0x2000: + case 0x3000: + this->m_state.rom_bank_reg = value & 0x7F; + if (m_state.rom_bank_reg == 0) m_state.rom_bank_reg = 1; + this->set_rombank(this->m_state.rom_bank_reg); + return; + case 0x4000: + case 0x5000: + this->set_rambank(value & 0x7); + this->m_state.rtc_enabled = (value & 0x80); + return; + case 0x6000: + case 0x7000: + // TODO: RTC latch values + return; + } +} +} // namespace gbc diff --git a/components/libgbc/include/mbc5.hpp b/components/libgbc/include/mbc5.hpp new file mode 100644 index 00000000..c3ff3934 --- /dev/null +++ b/components/libgbc/include/mbc5.hpp @@ -0,0 +1,31 @@ +#include "mbc.hpp" + +#include "machine.hpp" +#include "memory.hpp" + +namespace gbc +{ +inline void MBC::write_MBC5(uint16_t addr, uint8_t value) +{ + switch (addr & 0xF000) + { + case 0x2000: + // ROM bank select (lower) + this->m_state.rom_bank_reg &= 0x100; + this->m_state.rom_bank_reg |= value & 0xFF; + this->set_rombank(this->m_state.rom_bank_reg); + return; + case 0x3000: + // ROM bank select (upper) + this->m_state.rom_bank_reg &= 0xFF; + this->m_state.rom_bank_reg |= value & 0x100; + this->set_rombank(this->m_state.rom_bank_reg); + return; + case 0x4000: + case 0x5000: + // RAM bank select + this->set_rambank(value & 0xF); + return; + } +} +} // namespace gbc diff --git a/components/libgbc/include/memory.hpp b/components/libgbc/include/memory.hpp new file mode 100644 index 00000000..4b50a0f0 --- /dev/null +++ b/components/libgbc/include/memory.hpp @@ -0,0 +1,109 @@ +#pragma once +#include "common.hpp" +#include "mbc.hpp" +#include +#include +#include +#include +#include + +namespace gbc +{ +class Memory +{ +public: + using range_t = std::pair; + static constexpr range_t ProgramArea{0x0000, 0x7FFF}; + static constexpr range_t VideoRAM{0x8000, 0x9FFF}; + + static constexpr range_t BankRAM{0xA000, 0xBFFF}; + static constexpr range_t WorkRAM{0xC000, 0xDFFF}; + static constexpr range_t EchoRAM{0xE000, 0xFDFF}; // echo of work RAM + static constexpr range_t OAM_RAM{0xFE00, 0xFEFF}; + + static constexpr range_t IO_Ports{0xFF00, 0xFF7F}; + static constexpr range_t ZRAM{0xFF80, 0xFFFE}; + static constexpr uint16_t InterruptEn = 0xFFFF; + + Memory(Machine&, const std::string_view rom); + void reset(); + void set_wram_bank(uint8_t bank); + + uint8_t read8(uint16_t address); + void write8(uint16_t address, uint8_t value); + + uint16_t read16(uint16_t address); + void write16(uint16_t address, uint16_t value); + + uint8_t* oam_ram_ptr() noexcept { return m_state.oam_ram.data(); } + const uint8_t* oam_ram_ptr() const noexcept { return m_state.oam_ram.data(); } + uint8_t* video_ram_ptr() noexcept { return m_state.video_ram.data(); } + const uint8_t* video_ram_ptr() const noexcept { return m_state.video_ram.data(); } + + static constexpr uint16_t range_size(range_t range) { return range.second - range.first; } + + Machine& machine() const noexcept { return m_machine; } + Machine& machine() noexcept { return m_machine; } + bool rom_valid() const noexcept; + bool bootrom_enabled() const noexcept { return false; } + void disable_bootrom(); + + bool double_speed() const noexcept { return m_state.speed_factor != 1; } + int speed_factor() const noexcept { return m_state.speed_factor; } + void do_switch_speed(); + + // serialization + int restore_state(const std::vector&, int); + void serialize_state(std::vector&) const; + + // debugging + std::string explain(uint16_t address) const; + enum amode_t + { + READ, + WRITE + }; + using access_t = std::function; + void breakpoint(amode_t, access_t); + + inline static bool is_within(uint16_t addr, const range_t& range) + { + return addr >= range.first && addr <= range.second; + } + +private: + Machine& m_machine; + const std::string_view m_rom; + MBC m_mbc; + struct state_t + { + std::array video_ram = {}; + std::array oam_ram = {}; + std::array zram = {}; // high-speed RAM + bool bootrom_enabled = true; + int8_t speed_factor = 1; + } m_state; + bool m_is_busy = false; + std::vector m_read_breakpoints; + std::vector m_write_breakpoints; +}; + +inline void Memory::breakpoint(amode_t mode, access_t func) +{ + if (mode == READ) + m_read_breakpoints.push_back(func); + else if (mode == WRITE) + m_write_breakpoints.push_back(func); +} + +inline uint16_t Memory::read16(uint16_t address) +{ + return read8(address) | read8(address + 1) << 8; +} +inline void Memory::write16(uint16_t address, uint16_t value) +{ + write8(address + 0, value & 0xff); + write8(address + 1, value >> 8); +} + +} // namespace gbc diff --git a/components/libgbc/include/printers.hpp b/components/libgbc/include/printers.hpp new file mode 100644 index 00000000..d68d2076 --- /dev/null +++ b/components/libgbc/include/printers.hpp @@ -0,0 +1,58 @@ +#pragma once +#include + +namespace gbc +{ +static const char* cstr_reg(const uint8_t bf, bool sp) +{ + static const char* notsp[] = {"BC", "DE", "HL", "AF"}; + static const char* notaf[] = {"BC", "DE", "HL", "SP"}; + if (sp) return notaf[(bf >> 4) & 0x3]; + return notsp[(bf >> 4) & 0x3]; +} +static const char* cstr_dest(const uint8_t bf) +{ + static const char* dest[] = {"B", "C", "D", "E", "H", "L", "(HL)", "A"}; + return dest[bf & 0x7]; +} +static const char* cstr_flags(char buff[5], const uint8_t flags) +{ + buff[0] = (flags & MASK_ZERO) ? 'Z' : '_'; + buff[1] = (flags & MASK_NEGATIVE) ? 'N' : '_'; + buff[2] = (flags & MASK_HALFCARRY) ? 'H' : '_'; + buff[3] = (flags & MASK_CARRY) ? 'C' : '_'; + buff[4] = 0; + return buff; +} +static const char* cstr_cond(const uint8_t bf) +{ + static const char* s[] = {"not zero", "zero", "not carry", "carry"}; + return s[(bf >> 3) & 0x3]; +} +static const char* cstr_cond_en(const uint8_t bf, const uint8_t flags) +{ + const uint8_t f = (bf >> 3) & 0x3; + switch (f) + { + case 0: + return (flags & 0x80) == 0 ? "YES" : "NO"; + case 1: + return (flags & 0x80) ? "YES" : "NO"; + case 2: + return (flags & 0x10) == 0 ? "YES" : "NO"; + case 3: + return (flags & 0x10) ? "YES" : "NO"; + } + __builtin_unreachable(); +} +static void fill_flag_buffer(char* buffer, size_t len, const uint8_t opcode, const uint8_t flags) +{ + snprintf(buffer, len, "%s: %s", cstr_cond(opcode), cstr_cond_en(opcode, flags)); +} + +static const char* cstr_alu(const uint8_t bf) +{ + static const char* dest[] = {"ADD", "ADC", "SUB", "SBC", "AND", "XOR", "OR", "CP"}; + return dest[bf & 0x7]; +} +} // namespace gbc diff --git a/components/libgbc/include/registers.hpp b/components/libgbc/include/registers.hpp new file mode 100644 index 00000000..20927b8a --- /dev/null +++ b/components/libgbc/include/registers.hpp @@ -0,0 +1,199 @@ +#pragma once +#include "common.hpp" +#include +#include + +namespace gbc +{ +enum flags_t +{ + MASK_ZERO = 0x80, + MASK_NEGATIVE = 0x40, + MASK_HALFCARRY = 0x20, + MASK_CARRY = 0x10, +}; + +struct regs_t +{ + union + { + struct + { + uint8_t flags; + uint8_t accum; + }; + uint16_t af; + }; + union + { + struct + { + uint8_t c; + uint8_t b; + }; + uint16_t bc; + }; + union + { + struct + { + uint8_t e; + uint8_t d; + }; + uint16_t de; + }; + union + { + struct + { + uint8_t l; + uint8_t h; + }; + uint16_t hl; + }; + + uint16_t sp; + uint16_t pc; + + inline uint16_t& getreg(const uint8_t bf, const bool use_sp) + { + switch (bf & 0x3) + { + case 0: + return bc; + case 1: + return de; + case 2: + return hl; + case 3: + return (use_sp) ? sp : af; + } + __builtin_unreachable(); + } + uint16_t& getreg_sp(const uint8_t opcode) { return getreg((opcode >> 4) & 0x3, true); } + uint16_t& getreg_af(const uint8_t opcode) { return getreg((opcode >> 4) & 0x3, false); } + + uint8_t& getdest(const uint8_t bf) + { + switch (bf & 0x7) + { + case 0: + return b; + case 1: + return c; + case 2: + return d; + case 3: + return e; + case 4: + return h; + case 5: + return l; + case 6: + throw MachineException("getdest: (HL) not accessible here"); + case 7: + return accum; + } + __builtin_unreachable(); + } + + bool compare_flags(const uint8_t opcode) noexcept + { + const uint8_t idx = (opcode >> 3) & 0x3; + if (idx == 0) return (flags & MASK_ZERO) == 0; // not zero + if (idx == 1) return (flags & MASK_ZERO); // zero + if (idx == 2) return (flags & MASK_CARRY) == 0; // not carry + if (idx == 3) return (flags & MASK_CARRY); // carry + __builtin_unreachable(); + } + + inline static bool half_carry(const uint8_t reg, const uint8_t val) + { + return ((reg & 0xf) + (val & 0xf)) & (0x10); + } + inline static bool half_borrow(const uint8_t reg, const uint8_t val) + { + return (reg & 0xf) < (val & 0xf); + } + + void alu(uint8_t op, uint8_t value) noexcept + { + auto& reg = this->accum; + switch (op & 0x7) + { + case 0x0: + { // ADD + const uint16_t calc = reg + value; + setflag(false, flags, MASK_NEGATIVE); + setflag(half_carry(reg, value), flags, MASK_HALFCARRY); + setflag(calc & 0x100, flags, MASK_CARRY); + reg += value; + setflag(reg == 0, flags, MASK_ZERO); + } + return; + case 0x1: + { // ADC + const int carry = (flags & MASK_CARRY) ? 1 : 0; + setflag(false, flags, MASK_NEGATIVE); + setflag((reg & 0xf) + (value & 0xf) + carry > 0xf, flags, MASK_HALFCARRY); // annoying! + setflag(((int) reg + value + carry) > 0xFF, flags, MASK_CARRY); + reg += value + carry; + setflag(reg == 0, flags, MASK_ZERO); + } + return; + case 0x2: // SUB + setflag(true, flags, MASK_NEGATIVE); + setflag(half_borrow(reg, value), flags, MASK_HALFCARRY); + setflag(reg < value, flags, MASK_CARRY); + setflag(reg == value, flags, MASK_ZERO); + reg -= value; + return; + case 0x3: + { // SBC + const int carry = (flags & MASK_CARRY) ? 1 : 0; + flags = MASK_NEGATIVE; + setflag(((reg & 0xf) - (value & 0xf) - carry) < 0, flags, MASK_HALFCARRY); + setflag(reg < value + carry, flags, MASK_CARRY); + reg -= value + carry; + setflag(reg == 0, flags, MASK_ZERO); + } + return; + case 0x4: // AND + reg &= value; + flags = MASK_HALFCARRY; + setflag(reg == 0, flags, MASK_ZERO); + return; + case 0x5: // XOR + reg ^= value; + flags = 0; + setflag(reg == 0, flags, MASK_ZERO); + return; + case 0x6: // OR + reg |= value; + flags = 0; + setflag(reg == 0, flags, MASK_ZERO); + return; + case 0x7: // CP + const uint8_t tmp = reg - value; + flags |= MASK_NEGATIVE; + setflag(tmp == 0, flags, MASK_ZERO); + setflag(reg < value, flags, MASK_CARRY); + setflag(half_borrow(reg, value), flags, MASK_HALFCARRY); + // printf("CP %02x vs %02x (F => %02x)\n", reg, value, flags); + return; + } + } // alu() + + std::string to_string() const + { + char buffer[512]; + int len = snprintf(buffer, sizeof(buffer), + "\tAF = %04X BC = %04X DE = %04X\n" + "\tHL = %04X SP = %04X PC = %04X\n", + af, bc, de, hl, sp, pc); + return std::string(buffer, len); + } +}; + +inline flags_t to_flag(const uint8_t opcode) { return (flags_t)(1 >> (4 + ((opcode >> 3) & 0x3))); } +} // namespace gbc diff --git a/components/libgbc/include/sprite.hpp b/components/libgbc/include/sprite.hpp new file mode 100644 index 00000000..0d79ff30 --- /dev/null +++ b/components/libgbc/include/sprite.hpp @@ -0,0 +1,69 @@ +#pragma once +#include "memory.hpp" + +namespace gbc +{ +struct sprite_config_t +{ + const uint8_t* patterns; + uint8_t palette[2]; + int scan_x; + int scan_y; + int height; + bool is_cgb; + + void set_height(bool mode8x16) { height = mode8x16 ? 16 : 8; } +}; + +class Sprite +{ +public: + static const int SPRITE_W = 8; + + Sprite() = delete; + + bool hidden() const noexcept { return ypos == 0 || ypos >= 160 || xpos == 0 || xpos >= 168; } + uint8_t pattern_idx() const noexcept { return pattern; } + bool behind() const noexcept { return attr & 0x80; } + bool flipx() const noexcept { return attr & 0x20; } + bool flipy() const noexcept { return attr & 0x40; } + int pal() const noexcept { return (attr & 0x10) >> 4; } + int cgb_bank() const noexcept { return (attr & 0x8) >> 3; } + int cgb_pal() const noexcept { return attr & 0x7; } + + uint8_t pixel(const sprite_config_t&) const; + + int start_x() const noexcept { return xpos - 8; } + int start_y() const noexcept { return ypos - 16; } + + bool is_within_scanline(const sprite_config_t& config) const noexcept + { + return config.scan_y >= start_y() && config.scan_y < start_y() + config.height; + } + +private: + uint8_t ypos; + uint8_t xpos; + uint8_t pattern; + uint8_t attr; +}; + +inline uint8_t Sprite::pixel(const sprite_config_t& config) const +{ + int tx = config.scan_x - start_x(); + int ty = config.scan_y - start_y(); + if (tx < 0 || tx >= SPRITE_W) return 0; + if (this->flipx()) tx = SPRITE_W - 1 - tx; + if (this->flipy()) ty = config.height - 1 - ty; + + int offset = this->pattern * 16 + ty * 2; + if (config.is_cgb) offset += cgb_bank() * 0x2000; + uint8_t c0 = config.patterns[offset]; + uint8_t c1 = config.patterns[offset + 1]; + // return combined 4-bits, right to left + const int bit = 7 - tx; + const int v0 = (c0 >> bit) & 0x1; + const int v1 = (c1 >> bit) & 0x1; + return v0 | (v1 << 1); +} +} // namespace gbc diff --git a/components/libgbc/include/tiledata.hpp b/components/libgbc/include/tiledata.hpp new file mode 100644 index 00000000..ddb1f9f5 --- /dev/null +++ b/components/libgbc/include/tiledata.hpp @@ -0,0 +1,65 @@ +#pragma once +#include "memory.hpp" + +namespace gbc +{ +struct tileconf_t +{ + const bool is_cgb; + const uint8_t dmg_pal; +}; + +class TileData +{ +public: + static const int TILE_W = 8; + static const int TILE_H = 8; + + TileData(const uint8_t* tile, const uint8_t* pattern, const uint8_t* attr, bool sign) + : m_tile_base(tile), m_patt_base(pattern), m_attr_base(attr), m_signed(sign) + {} + + int tile_attr(int tx, int ty); + int tile_id(int tx, int ty); + int pattern(int t, int tattr, int dx, int dy) const; + int pattern(const uint8_t* base, int tattr, int t, int dx, int dy) const; + void set_tilebase(const uint8_t* new_base) { m_tile_base = new_base; } + +private: + const uint8_t* m_tile_base; + const uint8_t* m_patt_base; + const uint8_t* m_attr_base; + const bool m_signed; +}; + +inline int TileData::tile_id(int x, int y) +{ + if (this->m_signed) return 128 + (int8_t) m_tile_base[y * 32 + x]; + return m_tile_base[y * 32 + x]; +} +inline int TileData::tile_attr(int x, int y) +{ + if (m_attr_base == nullptr) return 0; + return m_attr_base[y * 32 + x]; +} + +inline int TileData::pattern(const uint8_t* base, int tid, int tattr, int tx, int ty) const +{ + if (tattr & 0x20) tx = 7 - tx; + if (tattr & 0x40) ty = 7 - ty; + if (tattr & 0x08) base += 0x2000; + const int offset = 16 * tid + ty * 2; + // get 16-bit c0, c1 + uint8_t c0 = base[offset]; + uint8_t c1 = base[offset + 1]; + // return combined 4-bits, right to left + const int bit = 7 - tx; + const int v0 = (c0 >> bit) & 0x1; + const int v1 = (c1 >> bit) & 0x1; + return v0 | (v1 << 1); +} // pattern(...) +inline int TileData::pattern(int tid, int tattr, int tx, int ty) const +{ + return pattern(m_patt_base, tid, tattr, tx, ty); +} +} // namespace gbc diff --git a/components/libgbc/include/tracing.hpp b/components/libgbc/include/tracing.hpp new file mode 100644 index 00000000..90d065ef --- /dev/null +++ b/components/libgbc/include/tracing.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +namespace gbc +{ +class CPU; + +struct breakpoint_t +{ + std::function callback; +}; + +} // namespace gbc diff --git a/components/libgbc/src/apu.cpp b/components/libgbc/src/apu.cpp new file mode 100644 index 00000000..4fff6e45 --- /dev/null +++ b/components/libgbc/src/apu.cpp @@ -0,0 +1,54 @@ +#include "apu.hpp" +#include "generators.hpp" +#include "io.hpp" +#include "machine.hpp" + +namespace gbc +{ +APU::APU(Machine& mach) : m_machine{mach} {} + +void APU::simulate() +{ + // if sound is off, don't do anything + if ((machine().io.reg(IO::REG_NR52) & 0x80) == 0) return; + + // TODO: writeme +} + +uint8_t APU::read(const uint16_t addr, uint8_t& reg) +{ + switch (addr) + { + case IO::REG_NR52: + return reg; + } + printf("ERROR: Unhandled APU read at %04X (reg %02X)\n", addr, reg); + GBC_ASSERT(0 && "Unhandled APU read"); +} +void APU::write(const uint16_t addr, const uint8_t value, uint8_t& reg) +{ + switch (addr) + { + case IO::REG_NR52: + // TODO: writing bit7 should clear all sound registers + // printf("NR52 Sound ON/OFF 0x%04x write 0x%02x\n", addr, value); + reg &= 0xF; + reg |= value & 0x80; + // GBC_ASSERT(0 && "NR52 Sound ON/OFF register write"); + return; + } + printf("ERROR: Unhandled APU write at %04X val=%02X (reg %02X)\n", addr, value, reg); + GBC_ASSERT(0 && "Unhandled APU write"); +} + +// serialization +int APU::restore_state(const std::vector& data, int off) +{ + this->m_state = *(state_t*) &data.at(off); + return sizeof(m_state); +} +void APU::serialize_state(std::vector& res) const +{ + res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); +} +} // namespace gbc diff --git a/components/libgbc/src/cpu.cpp b/components/libgbc/src/cpu.cpp new file mode 100644 index 00000000..a73d839c --- /dev/null +++ b/components/libgbc/src/cpu.cpp @@ -0,0 +1,621 @@ +#include "cpu.hpp" + +#include "instructions.cpp" +#include "machine.hpp" +#include + +namespace gbc +{ +CPU::CPU(Machine& mach) noexcept : m_machine(mach), m_memory(mach.memory) {} + +void CPU::reset() noexcept +{ + if (!machine().is_cgb()) + { + // gameboy DMG initial register values + registers().af = 0x01b0; + registers().bc = 0x0013; + registers().de = 0x00d8; + registers().hl = 0x014d; + } + else + { + // gameboy color initial register values + registers().af = 0x1180; + registers().bc = 0x0000; + registers().de = 0xff56; + registers().hl = 0x000d; + } + registers().sp = 0xfffe; + registers().pc = memory().bootrom_enabled() ? 0x0 : 0x100; + this->m_state.cycles_total = 0; +} + +void CPU::simulate() +{ + // breakpoint handling + if (UNLIKELY(this->break_time() || !this->m_breakpoints.empty())) + { + this->break_checks(); + // user can quit during break + if (!machine().is_running()) return; + } + // handle interrupts + this->handle_interrupts(); + + if (!this->is_halting() && !this->is_stopping()) { this->execute(); } + else + { + // make sure time passes when not executing instructions + this->hardware_tick(); + // speed switch + this->handle_speed_switch(); + } +} + +void CPU::execute() +{ + // 1. read instruction from memory + const uint8_t opcode = this->peekop8(0); + // 2. decode into executable instruction + auto& instr = decode(opcode); + + // 2a. print the instruction (when enabled) + if (UNLIKELY(machine().verbose_instructions)) + { + char prn[128]; + instr.printer(prn, sizeof(prn), *this, opcode); + printf("%9llu: [pc %04X] opcode %02X: %s\n", gettime(), registers().pc, opcode, prn); + } + + // 3. increment PC, hardware tick + registers().pc++; + this->hardware_tick(); + + // 4. run instruction handler + instr.handler(*this, opcode); + + if (UNLIKELY(machine().verbose_instructions)) + { + // print out the resulting flags reg + if (m_state.last_flags != registers().flags) + { + m_state.last_flags = registers().flags; + char fbuf[5]; + printf("* Flags changed: [%s]\n", cstr_flags(fbuf, registers().flags)); + } + } + if (UNLIKELY(memory().is_within(registers().pc, Memory::VideoRAM))) + { + fprintf(stderr, "ERROR: PC is in the Video RAM area: %04X\n", registers().pc); + this->break_now(); + } + if (UNLIKELY(memory().is_within(registers().pc, Memory::EchoRAM))) + { + fprintf(stderr, "WARN: PC is in the Echo RAM area: %04X\n", registers().pc); + this->break_now(); + } + if (UNLIKELY(memory().is_within(registers().pc, Memory::OAM_RAM))) + { + fprintf(stderr, "ERROR: PC is in the OAM RAM area: %04X\n", registers().pc); + this->break_now(); + } + if (UNLIKELY(memory().is_within(registers().pc, Memory::IO_Ports))) + { + fprintf(stderr, "ERROR: PC is in the I/O port area: %04X\n", registers().pc); + this->break_now(); + } +} + +void CPU::hardware_tick() +{ + this->incr_cycles(4); + machine().gpu.simulate(); + machine().io.simulate(); + machine().apu.simulate(); +} + +// it takes 2 instruction-cycles to toggle interrupts +void CPU::enable_interrupts() noexcept +{ + if (this->m_state.intr_pending <= 0) { this->m_state.intr_pending = 2; } +} +void CPU::disable_interrupts() noexcept { this->m_state.intr_pending = -2; } + +void CPU::handle_interrupts() +{ + // enable/disable interrupts over cycles + if (UNLIKELY(m_state.intr_pending != 0)) + { + if (m_state.intr_pending > 0) + { + m_state.intr_pending--; + if (!m_state.intr_pending) this->m_state.ime = true; + } + else if (m_state.intr_pending < 0) + { + m_state.intr_pending++; + if (!m_state.intr_pending) this->m_state.ime = false; + } + } + // check if interrupts are enabled and pending + const uint8_t imask = machine().io.interrupt_mask(); + if (UNLIKELY(this->ime() && imask != 0x0)) + { + // disable interrupts immediately + this->m_state.ime = false; + this->m_state.asleep = false; + // execute pending interrupts (sorted by priority) + auto& io = machine().io; + if (imask & 0x1) + this->interrupt(io.vblank); + else if (imask & 0x2) + this->interrupt(io.lcd_stat); + else if (imask & 0x4) + this->interrupt(io.timerint); + else if (imask & 0x8) + this->interrupt(io.serialint); + else if (imask & 0x10) + this->interrupt(io.joypadint); + } + else if (UNLIKELY(this->m_state.haltbug && imask != 0)) + { + // do *NOT* call interrupt handler when buggy HALTing + this->m_state.asleep = false; + this->m_state.haltbug = false; + } +} +void CPU::interrupt(interrupt_t& intr) +{ + if (UNLIKELY(machine().verbose_interrupts)) + { printf("%9llu: Executing interrupt %s (%#x)\n", this->gettime(), intr.name, intr.mask); } + // disable interrupt request + machine().io.reg(IO::REG_IF) &= ~intr.mask; + // set interrupt bit + // this->m_state.reg_ie |= intr.mask; + this->hardware_tick(); + this->hardware_tick(); + // push PC and jump to INTR addr + this->push_and_jump(intr.fixed_address); + // sometimes we want to break on interrupts + if (UNLIKELY(machine().break_on_interrupts && !machine().is_breaking())) + { machine().break_now(); } + if (intr.callback) intr.callback(machine(), intr); +} + +instruction_t& CPU::decode(const uint8_t opcode) +{ + switch (opcode) + { + case 0x00: // NOP + return instr_NOP; + case 0x08: // LD SP, imm16 + return instr_LD_N_SP; + case 0x10: // STOP + return instr_STOP; + case 0x76: // HALT + return instr_HALT; + // LD D, imm8 + case 0x06: + case 0x16: + case 0x26: + case 0x36: + case 0x0E: + case 0x1E: + case 0x2E: + case 0x3E: + return instr_LD_D_N; + // LD B, D + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + // LD C, D + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + // LD D, D + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + // LD E, D + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + // LD H, D + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + // LD L, D + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + // LD (HL), D + // NOTE: 0x76: is HALT and *not* LD (HL), (HL) + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x77: + // LD A, D + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return instr_LD_D_D; + // LD A, R + case 0x02: + case 0x12: + case 0x0A: + case 0x1A: + return instr_LD_R_A_R; + case 0xEA: // LD (imm16), A + case 0xFA: // LD A, (imm16) + return instr_LD_N_A_N; + case 0x22: // LDI (HL), A + case 0x32: // LDD (HL), A + case 0x2A: // LDI A, (HL) + case 0x3A: // LDD A, (HL) + return instr_LDID_HL_A; + case 0xE2: // LD (FF00+C), A + case 0xF2: // LD A, (FF00+C) + case 0xE0: // LD (FF00+imm8), A + case 0xF0: // LD A, (FF00+imm8) + return instr_LD_FF00_A; + // LD R, imm16 + case 0x01: + case 0x11: + case 0x21: + case 0x31: + return instr_LD_R_N; + case 0xE8: + return instr_ADD_SP_N; + case 0xF8: // LD HL, SP+imm8 + case 0xF9: // LD SP, HL + return instr_LD_HL_SP; + // POP R + case 0xC1: + case 0xD1: + case 0xE1: + case 0xF1: + // PUSH R + case 0xC5: + case 0xD5: + case 0xE5: + case 0xF5: + return instr_PUSH_POP; + // ALU operations + // ADD A, D + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + return instr_ALU_A_D; + // ADC A, D + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + return instr_ALU_A_D; + // SUB A, D + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + return instr_ALU_A_D; + // SBC A, D + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + return instr_ALU_A_D; + // AND A, D + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + return instr_ALU_A_D; + // XOR A, D + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + return instr_ALU_A_D; + // OR A, D + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + return instr_ALU_A_D; + // CP A, D + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + return instr_ALU_A_D; + // ALU OP A, im8 + case 0xC6: + case 0xCE: + case 0xD6: + case 0xDE: + case 0xE6: + case 0xEE: + case 0xF6: + case 0xFE: + return instr_ALU_A_N; + // INC R, DEC R + case 0x03: + case 0x0B: + case 0x13: + case 0x1B: + case 0x23: + case 0x2B: + case 0x33: + case 0x3B: + return instr_INC_DEC_R; + // INC D, DEC D + case 0x04: + case 0x05: + case 0x0C: + case 0x0D: + case 0x14: + case 0x15: + case 0x1C: + case 0x1D: + case 0x24: + case 0x25: + case 0x2C: + case 0x2D: + case 0x34: + case 0x35: + case 0x3C: + case 0x3D: + return instr_INC_DEC_D; + // ADD HL, R + case 0x09: + case 0x19: + case 0x29: + case 0x39: + return instr_ADD_HL_R; + case 0x27: // DA A + return instr_DAA; + case 0x2F: // CPL A + return instr_CPL_A; + case 0x37: // SCF + case 0x3F: // CCF + return instr_SCF_CCF; + case 0x07: // RLC A + case 0x17: // RL A + case 0x0F: // RRC A + case 0x1F: // RR A + return instr_RLC_RRC; + case 0xC3: // JP imm16 + case 0xC2: // JP nz, imm16 + case 0xCA: // JP z, imm16 + case 0xD2: // JP nc, imm16 + case 0xDA: // JP c, imm16 + return instr_JP; + case 0xE9: // JP HL + return instr_JP_HL; + case 0x18: // JR imm8 + case 0x20: // JR nz, imm8 + case 0x28: // JR z, imm8 + case 0x30: // JR nc, imm8 + case 0x38: // JR c, imm8 + return instr_JR_N; + case 0xCD: // CALL imm16 + case 0xC4: // CALL nz, imm16 + case 0xCC: // CALL z, imm16 + case 0xD4: // CALL nc, imm16 + case 0xDC: // CALL c, imm16 + return instr_CALL; + case 0xC9: // RET + case 0xC0: // RET nz + case 0xC8: // RET z + case 0xD0: // RET nc + case 0xD8: // RET c + return instr_RET; + case 0xD9: // RETI + return instr_RETI; + // RST 0x0, 0x08, 0x10, 0x18 + case 0xC7: + case 0xCF: + case 0xD7: + case 0xDF: + // RST 0x20, 0x028, 0x30, 0x38 + case 0xE7: + case 0xEF: + case 0xF7: + case 0xFF: + return instr_RST; + case 0xF3: // DI + case 0xFB: // EI + return instr_DI_EI; + case 0xCB: + return instr_CB_EXT; + } + return instr_MISSING; +} + +uint8_t CPU::peekop8(int disp) { return memory().read8(registers().pc + disp); } +uint16_t CPU::peekop16(int disp) { return memory().read16(registers().pc + disp); } +uint8_t CPU::readop8() +{ + const uint8_t operand = peekop8(0); + registers().pc++; + hardware_tick(); + return operand; +} +uint16_t CPU::readop16() +{ + const uint16_t operand = peekop16(0); + registers().pc += 2; + hardware_tick(); + hardware_tick(); + return operand; +} + +uint8_t CPU::read_hl() { return this->mtread8(registers().hl); } +void CPU::write_hl(const uint8_t value) { this->mtwrite8(registers().hl, value); } +uint8_t CPU::mtread8(uint16_t addr) +{ + const uint8_t value = memory().read8(addr); + this->hardware_tick(); + return value; +} +void CPU::mtwrite8(uint16_t addr, uint8_t value) +{ + memory().write8(addr, value); + this->hardware_tick(); +} +uint16_t CPU::mtread16(uint16_t addr) +{ + const uint16_t value = memory().read16(addr); + this->hardware_tick(); + this->hardware_tick(); + return value; +} +void CPU::mtwrite16(uint16_t addr, uint16_t value) +{ + memory().write16(addr, value); + this->hardware_tick(); + this->hardware_tick(); +} + +void CPU::incr_cycles(int count) +{ + assert(count >= 0); + this->m_state.cycles_total += count; +} + +void CPU::stop() +{ + this->m_state.stopped = true; + // preparing a speed switch? + if (machine().io.reg(IO::REG_KEY1) & 0x1) { this->m_state.switch_cycles = 4; } + // disable screen etc. + machine().io.perform_stop(); +} +void CPU::handle_speed_switch() +{ + if (UNLIKELY(this->m_state.switch_cycles > 0)) + { + this->m_state.switch_cycles--; + if (this->m_state.switch_cycles == 0) + { + // stop the stopping + this->m_state.stopped = false; + // change speed + memory().do_switch_speed(); + // this can turn the LCD back on + machine().io.deactivate_stop(); + } + } +} + +void CPU::wait() +{ + this->m_state.asleep = true; + this->m_state.haltbug = false; +} +void CPU::buggy_halt() +{ + this->m_state.asleep = true; + this->m_state.haltbug = true; +} + +void CPU::jump(const uint16_t dest) +{ + if (UNLIKELY(machine().verbose_instructions)) + { printf("* Jumped to %04X (from %04X)\n", dest, registers().pc); } + this->registers().pc = dest; +} +void CPU::push_value(uint16_t address) +{ + this->hardware_tick(); + registers().sp -= 2; + this->mtwrite16(registers().sp, address); +} +void CPU::push_and_jump(uint16_t address) +{ + this->push_value(registers().pc); + this->jump(address); +} + +int CPU::restore_state(const std::vector& data, int off) +{ + this->m_state = *(state_t*) &data.at(off); + return sizeof(m_state); +} +void CPU::serialize_state(std::vector& res) const +{ + res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); +} +} // namespace gbc diff --git a/components/libgbc/src/debug.cpp b/components/libgbc/src/debug.cpp new file mode 100644 index 00000000..aeb68f82 --- /dev/null +++ b/components/libgbc/src/debug.cpp @@ -0,0 +1,319 @@ +#include "cpu.hpp" +#include "machine.hpp" +#include +#include + +namespace gbc +{ +static inline std::vector split(const std::string& txt, char ch) +{ + size_t pos = txt.find(ch); + size_t initialPos = 0; + std::vector strs; + + while (pos != std::string::npos) + { + strs.push_back(txt.substr(initialPos, pos - initialPos)); + initialPos = pos + 1; + + pos = txt.find(ch, initialPos); + } + + // Add the last one + strs.push_back(txt.substr(initialPos, std::min(pos, txt.size()) - initialPos + 1)); + return strs; +} + +static void print_help() +{ + const char* help_text = R"V0G0N( + usage: command [options] + commands: + ?, help Show this informational text + c, continue Continue execution, disable stepping + s, step [steps=1] Run [steps] instructions, then break + v, verbose Toggle verbose instruction execution + b, break [addr] Breakpoint on executing [addr] + rb [addr] Breakpoint on reading from [addr] + wb [addr] Breakpoint on writing to [addr] + clear Clear all breakpoints + reset Reset the machine + read [addr] (len=1) Read from [addr] (len) bytes and print + write [addr] [value] Write [value] to memory location [addr] + readv0 [addr] Print byte from VRAM0:[addr] + readv1 [addr] Print byte from VRAM1:[addr] + debug Trigger the debug interrupt handler + vblank Render current screen and call vblank + frame Show frame number and extra frame info +)V0G0N"; + printf("%s\n", help_text); +} + +static bool execute_commands(CPU& cpu) +{ + printf("Enter = cont, help, quit: "); + std::string text; + while (true) + { + const int c = getchar(); // press any key + if (c == '\n' || c < 0) + break; + else + text.append(1, (char) c); + } + if (text.empty()) return false; + std::vector params = split(text, ' '); + const auto& cmd = params[0]; + + // continue + if (cmd == "c" || cmd == "continue") + { + cpu.break_on_steps(0); + return false; + } + // stepping + if (cmd == "") { return false; } + else if (cmd == "s" || cmd == "step") + { + cpu.machine().verbose_instructions = true; // ??? + int steps = 1; + if (params.size() > 1) steps = std::stoi(params[1]); + printf("Pressing Enter will now execute %d steps\n", steps); + cpu.break_on_steps(steps); + return false; + } + // breaking + else if (cmd == "b" || cmd == "break") + { + if (params.size() < 2) + { + printf(">>> Not enough parameters: break [addr]\n"); + return true; + } + unsigned long hex = std::strtoul(params[1].c_str(), 0, 16); + cpu.default_pausepoint(hex & 0xFFFF); + return true; + } + else if (cmd == "clear") + { + cpu.breakpoints().clear(); + return true; + } + else if (cmd == "rb" || cmd == "wb") + { + const auto mode = (cmd == "rb") ? Memory::READ : Memory::WRITE; + if (params.size() < 2) + { + printf(">>> Not enough parameters: rb/wb [addr]\n"); + return true; + } + uint16_t traploc = std::strtoul(params[1].c_str(), 0, 16) & 0xFFFF; + printf("Breaking after any %s %04X (%s)\n", (mode) ? "write to" : "read from", traploc, + cpu.memory().explain(traploc).c_str()); + cpu.memory().breakpoint(mode, [traploc, mode](Memory& mem, uint16_t addr, uint8_t value) { + if (addr == traploc) + { + if (mode == Memory::READ) + { + printf("Breaking after read from %04X (%s) with value %02X\n", addr, + mem.explain(addr).c_str(), mem.read8(addr)); + } + else + { // WRITE + printf("Breaking after write to %04X (%s) with value %02X (old: %02X)\n", addr, + mem.explain(addr).c_str(), value, mem.read8(addr)); + } + mem.machine().break_now(); + } + }); + return true; + } + // verbose instructions + else if (cmd == "v" || cmd == "verbose") + { + bool& v = cpu.machine().verbose_instructions; + v = !v; + printf("Verbose instructions are now %s\n", v ? "ON" : "OFF"); + return true; + } + else if (cmd == "r" || cmd == "run") + { + cpu.machine().verbose_instructions = false; + cpu.break_on_steps(0); + return false; + } + else if (cmd == "q" || cmd == "quit" || cmd == "exit") + { + cpu.machine().stop(); + return false; + } + else if (cmd == "reset") + { + cpu.machine().reset(); + cpu.break_now(); + return false; + } + // read 0xAddr size + else if (cmd == "ld" || cmd == "read") + { + if (params.size() < 2) + { + printf(">>> Not enough parameters: read [addr] (length=1)\n"); + return true; + } + unsigned long hex = std::strtoul(params[1].c_str(), 0, 16); + int bytes = 1; + if (params.size() > 2) bytes = std::stoi(params[2]); + int col = 0; + for (int i = 0; i < bytes; i++) + { + if (col == 0) printf("0x%04lx: ", hex + i); + printf("0x%02x ", cpu.memory().read8(hex + i)); + if (++col == 4) + { + printf("\n"); + col = 0; + } + } + if (col) printf("\n"); + return true; + } + // write 0xAddr value + else if (cmd == "write") + { + if (params.size() < 3) + { + printf(">>> Not enough parameters: write [addr] [value]\n"); + return true; + } + unsigned long hex = std::strtoul(params[1].c_str(), 0, 16); + int value = std::stoi(params[2]) & 0xff; + printf("0x%04lx -> 0x%02x\n", hex, value); + cpu.memory().write8(hex, value); + return true; + } + // read from VRAM bank 1 + else if (cmd == "readv0" || cmd == "readv1") + { + const uint16_t off = (cmd == "readv0") ? 0x0 : 0x2000; + if (params.size() < 2) + { + printf(">>> Not enough parameters: readv1 [addr]\n"); + return true; + } + unsigned long hex = std::strtoul(params[1].c_str(), 0, 16); + hex &= 0x1FFF; + printf("VRAM1:%04lX -> %02X\n", hex, cpu.memory().video_ram_ptr()[off + hex]); + return true; + } + else if (cmd == "vblank" || cmd == "vbl") + { + cpu.machine().gpu.render_frame(); + // call vblank handler directly + auto& vblank = cpu.machine().io.vblank; + if (vblank.callback) + vblank.callback(cpu.machine(), vblank); + else + printf(">>> V-blank callback was not set, and could not be called\n"); + return true; + } + else if (cmd == "frame" || cmd == "fr") + { + printf("Frame: %llu\n", cpu.machine().gpu.frame_count()); + return true; + } + else if (cmd == "debug") + { + auto& io = cpu.machine().io; + io.debugint.callback(cpu.machine(), io.debugint); + return true; + } + else if (cmd == "help" || cmd == "?") + { + print_help(); + return true; + } + else + { + printf(">>> Unknown command: '%s'\n", cmd.c_str()); + print_help(); + return true; + } + return false; +} + +void CPU::print_and_pause(CPU& cpu, const uint8_t opcode) +{ + char buffer[512]; + cpu.decode(opcode).printer(buffer, sizeof(buffer), cpu, opcode); + printf("\n"); + printf(">>> Breakpoint at [pc %04X] opcode %02X: %s\n", cpu.registers().pc, opcode, buffer); + // CPU registers + printf("%s", cpu.registers().to_string().c_str()); + // I/O interrupt registers + auto& io = cpu.machine().io; + printf("\tIF %02X IE %02X IME %X LY %u LCDC 0x%X\n", io.read_io(IO::REG_IF), + io.read_io(IO::REG_IE), cpu.ime(), io.read_io(IO::REG_LY), io.read_io(IO::REG_LCDC)); + try + { + auto& mem = cpu.memory(); + printf("\t(HL) = %02X (SP) = %04X\n", mem.read16(cpu.registers().hl), + mem.read16(cpu.registers().sp)); + } catch (...) + { + printf("\tUnable to read from (HL) or (SP)\n"); + } + while (execute_commands(cpu)) + ; +} // print_and_pause(...) + +bool CPU::break_time() const +{ + if (UNLIKELY(this->m_break)) return true; + if (UNLIKELY(m_break_steps_cnt != 0)) + { + m_break_steps--; + if (m_break_steps <= 0) + { + m_break_steps = m_break_steps_cnt; + return true; + } + } + return false; +} + +void CPU::break_on_steps(int steps) +{ + assert(steps >= 0); + this->m_break_steps_cnt = steps; + this->m_break_steps = steps; +} + +void CPU::break_checks() +{ + if (this->break_time()) + { + this->m_break = false; + // pause for each instruction + this->print_and_pause(*this, this->peekop8(0)); + } + if (!m_breakpoints.empty()) + { + // look for breakpoints + auto it = m_breakpoints.find(registers().pc); + if (it != m_breakpoints.end()) + { + auto& bp = it->second; + bp.callback(*this, this->peekop8(0)); + } + } +} + +void assert_failed(const int expr, const char* strexpr, + const char* filename, const int line) +{ + fprintf(stderr, "Assertion failed in %s:%d: %s\n", + filename, line, strexpr); + abort(); +} +} // namespace gbc diff --git a/components/libgbc/src/gpu.cpp b/components/libgbc/src/gpu.cpp new file mode 100644 index 00000000..23a2dc52 --- /dev/null +++ b/components/libgbc/src/gpu.cpp @@ -0,0 +1,428 @@ +#include "gpu.hpp" + +#include "machine.hpp" +#include "sprite.hpp" +#include "tiledata.hpp" +#include +#include + +namespace gbc +{ +const int GPU::WHITE_IDX; +GPU::GPU(Machine& mach) noexcept + : m_memory(mach.memory) + , m_io(mach.io) + , m_reg_lcdc{io().reg(IO::REG_LCDC)} + , m_reg_stat{io().reg(IO::REG_STAT)} + , m_reg_ly{io().reg(IO::REG_LY)} +{ + this->reset(); +} + +void GPU::reset() noexcept +{ + m_pixels.resize(SCREEN_W * SCREEN_H); + this->m_state.video_offset = 0; + // set_mode((m_reg_ly >= 144) ? 1 : 2); +} +uint64_t GPU::scanline_cycles() const noexcept +{ + return oam_cycles() + vram_cycles() + hblank_cycles(); +} +uint64_t GPU::oam_cycles() const noexcept +{ + return 83 * memory().speed_factor(); + // return memory().speed_factor() * 80; +} +uint64_t GPU::vram_cycles() const noexcept +{ + return 175 * memory().speed_factor(); + // return memory().speed_factor() * 172; +} +uint64_t GPU::hblank_cycles() const noexcept +{ + return 207 * memory().speed_factor(); + // return memory().speed_factor() * 204; +} +void GPU::simulate() +{ + // nothing to do with LCD being off + if (!this->lcd_enabled()) { return; } + + auto& vblank = io().vblank; + auto& lcd_stat = io().lcd_stat; + + this->m_state.period += 4; + const uint64_t period = this->m_state.period; + // assert(period == 4); + const bool new_scanline = period >= scanline_cycles(); + + // scanline logic when screen on + if (UNLIKELY(new_scanline)) + { + this->m_state.period = 0; // start over each scanline + // scanline LY increment logic + static const int MAX_LINES = 154; + m_state.current_scanline = (m_state.current_scanline + 1) % MAX_LINES; + m_reg_ly = m_state.current_scanline; + + if (UNLIKELY(m_reg_ly == 144)) + { + if (this->m_state.white_frame) + { + this->m_state.white_frame = false; + // create white palette value at color 32 + if (this->m_on_palchange) { this->m_on_palchange(WHITE_IDX, 0xFFFF); } + if (LIKELY(this->m_render)) + { + // clear pixelbuffer with white + std::fill_n(m_pixels.begin(), m_pixels.size(), WHITE_IDX); + } + } + // enable MODE 1: V-blank + set_mode(1); + // MODE 1: vblank interrupt + io().trigger(vblank); + // modify stat + this->set_mode(1); + // if STAT vblank interrupt is enabled + if (m_reg_stat & 0x10) io().trigger(lcd_stat); + } + else if (m_reg_ly == 1 && get_mode() == 1) + { + assert(this->is_vblank()); + // start over in regular mode + this->m_state.current_scanline = 0; + this->m_reg_ly = 0; + set_mode(0); + // new frame + m_state.frame_count++; + } + // LY == LYC comparison on each line + this->do_ly_comparison(); + } + // STAT mode & scanline period modulation + if (!this->is_vblank()) + { + if (get_mode() == 0 && new_scanline) + { + // enable MODE 2: OAM search + set_mode(2); + // check if OAM interrupt enabled + if (m_reg_stat & 0x20) io().trigger(lcd_stat); + } + else if (get_mode() == 2 && period >= oam_cycles()) + { + // enable MODE 3: Scanline VRAM + set_mode(3); + + // render a scanline (if rendering enabled) + if (LIKELY(!this->m_state.white_frame && this->m_render)) + { this->render_scanline(m_state.current_scanline); } + // TODO: perform HDMA transfers here! + } + else if (get_mode() == 3 && period >= oam_cycles() + vram_cycles()) + { + // enable MODE 0: H-blank + if (m_reg_stat & 0x8) io().trigger(lcd_stat); + set_mode(0); + } + // printf("Current mode: %u -> %u period %lu\n", + // current_mode(), m_reg_stat & 0x3, period); + } +} + +bool GPU::is_vblank() const noexcept { return get_mode() == 1; } +bool GPU::is_hblank() const noexcept { return get_mode() == 0; } +uint8_t GPU::get_mode() const noexcept { return m_reg_stat & 0x3; } +void GPU::set_mode(uint8_t mode) +{ + this->m_reg_stat &= 0xfc; + this->m_reg_stat |= mode & 0x3; +} + +void GPU::do_ly_comparison() +{ + const bool equal = m_reg_ly == io().reg(IO::REG_LYC); + // STAT coincidence bit + setflag(equal, m_reg_stat, 0x4); + // STAT interrupt (if enabled) when LY == LYC + if (equal && (m_reg_stat & 0x40)) io().trigger(io().lcd_stat); +} + +void GPU::render_frame() +{ + if (!m_state.white_frame && lcd_enabled()) + { + // render each scanline + for (int y = 0; y < SCREEN_H; y++) { this->render_scanline(y); } + } + else + { + // clear pixelbuffer with white + std::fill_n(m_pixels.begin(), m_pixels.size(), WHITE_IDX); + } +} + +void GPU::render_scanline(int scan_y) +{ + const uint8_t scroll_y = memory().read8(IO::REG_SCY); + const uint8_t scroll_x = memory().read8(IO::REG_SCX); + const int sy = (scan_y + scroll_y) % 256; + + // create tiledata object from LCDC register + auto td = this->create_tiledata(bg_tiles(), tile_data()); + // window visibility + const bool window = this->window_visible() && scan_y >= window_y(); + auto wtd = this->create_tiledata(window_tiles(), tile_data()); + + // create sprite configuration structure + auto sprconf = this->sprite_config(); + sprconf.scan_y = scan_y; + // create list of sprites that are on this scanline + auto sprites = this->find_sprites(sprconf); + + // tile configuration + tileconf_t tileconf = this->tile_config(); + + // render whole scanline + for (int scan_x = 0; scan_x < SCREEN_W; scan_x++) + { + const int sx = (scan_x + scroll_x) % 256; + // get the tile id and attribute + const int tid = td.tile_id(sx / 8, sy / 8); + const int tattr = td.tile_attr(sx / 8, sy / 8); + // copy the 16-byte tile into buffer + const int tile_color = td.pattern(tid, tattr, sx & 7, sy & 7); + uint16_t color15 = this->colorize_tile(tileconf, tattr, tile_color); + + if ((tattr & 0x80) == 0 || !machine().is_cgb()) + { + // window on can be under sprites + if (window && scan_x >= window_x() - 7) + { + const int wpx = scan_x - window_x() + 7; + const int wpy = scan_y - window_y(); + // draw window pixel + const int wtile = wtd.tile_id(wpx / 8, wpy / 8); + const int wattr = wtd.tile_attr(wpx / 8, wpy / 8); + const int widx = wtd.pattern(wtile, wattr, wpx & 7, wpy & 7); + color15 = this->colorize_tile(tileconf, wattr, widx); + } + + // render sprites within this x + sprconf.scan_x = scan_x; + for (const auto* sprite : sprites) + { + const uint8_t idx = sprite->pixel(sprconf); + if (idx != 0) + { + if (!sprite->behind() || tile_color == 0) { + color15 = this->colorize_sprite(sprite, sprconf, idx); + } + } + } + } // BG priority +#ifndef GAMEBRO_INDEXED_FRAME + // Convert to 15-bit RGB + color15 = getpal(color15 * 2) | (getpal(color15 * 2 + 1) << 8); + uint8_t r = ((color15) >> 0 & 0x1f) << 3; + uint8_t g = ((color15) >> 5 & 0x1f) << 3; + uint8_t b = ((color15) >> 10 & 0x1f) << 3; + color15 = make_color(r,g,b); +#endif + m_pixels.at(scan_y * SCREEN_W + scan_x) = color15; + } // x +} // render_to(...) + +uint16_t GPU::colorize_tile(const tileconf_t& conf, const uint8_t attr, const uint8_t idx) +{ + uint16_t index = 0; + if (conf.is_cgb) + { + const uint8_t pal = attr & 0x7; + index = 4 * pal + idx; + } + else + { + const uint8_t pal = conf.dmg_pal; + index = (pal >> (idx * 2)) & 0x3; + } + // no conversion + return index; +} +uint16_t GPU::colorize_sprite(const Sprite* sprite, sprite_config_t& sprconf, const uint8_t idx) +{ + uint16_t index = 0; + if (machine().is_cgb()) { + index = 32 + 4 * sprite->cgb_pal() + idx; + } + else { + const uint8_t pal = sprconf.palette[sprite->pal()]; + index = (pal >> (idx * 2)) & 0x3; + } + // no conversion + return index; +} + +bool GPU::lcd_enabled() const noexcept { return m_reg_lcdc & 0x80; } +bool GPU::window_enabled() const noexcept { return m_reg_lcdc & 0x20; } +bool GPU::window_visible() { return window_enabled() && window_x() < 166 && window_y() < 143; } +int GPU::window_x() { return io().reg(IO::REG_WX); } +int GPU::window_y() { return io().reg(IO::REG_WY); } + +uint16_t GPU::bg_tiles() const noexcept { return (m_reg_lcdc & 0x08) ? 0x9C00 : 0x9800; } +uint16_t GPU::window_tiles() const noexcept { return (m_reg_lcdc & 0x40) ? 0x9C00 : 0x9800; } +uint16_t GPU::tile_data() const noexcept { return (m_reg_lcdc & 0x10) ? 0x8000 : 0x8800; } + +TileData GPU::create_tiledata(uint16_t tiles, uint16_t patterns) +{ + const bool is_signed = (m_reg_lcdc & 0x10) == 0; + const auto* vram = memory().video_ram_ptr(); + // printf("Background tiles: 0x%04x Tile data: 0x%04x\n", + // bg_tiles(), tile_data()); + const auto* tile_base = &vram[tiles - 0x8000]; + const auto* patt_base = &vram[patterns - 0x8000]; + const uint8_t* attr_base = nullptr; + if (machine().is_cgb()) + { + // attributes are always in VRAM bank 1 (which is off=0x2000) + attr_base = &vram[tiles - 0x8000 + 0x2000]; + } + return TileData{tile_base, patt_base, attr_base, is_signed}; +} +tileconf_t GPU::tile_config() +{ + return tileconf_t{ + .is_cgb = machine().is_cgb(), + .dmg_pal = memory().read8(IO::REG_BGP), + }; +} +sprite_config_t GPU::sprite_config() +{ + sprite_config_t config; + config.patterns = memory().video_ram_ptr(); + config.palette[0] = memory().read8(IO::REG_OBP0); + config.palette[1] = memory().read8(IO::REG_OBP1); + config.scan_x = 0; + config.scan_y = 0; + config.set_height(m_reg_lcdc & 0x4); + config.is_cgb = machine().is_cgb(); + return config; +} + +std::vector GPU::find_sprites(const sprite_config_t& config) const +{ + const Sprite* sprite_begin = this->sprites_begin(); + const Sprite* sprite_back = this->sprites_end() - 1; + std::vector results; + // draw sprites from right to left + for (const Sprite* sprite = sprite_back; sprite >= sprite_begin; sprite--) + { + if (sprite->hidden() == false) + if (sprite->is_within_scanline(config)) + { + results.push_back(sprite); + // GB/GBC supports 10 sprites max per scanline + if (results.size() == 10) break; + } + } + return results; +} +const Sprite* GPU::sprites_begin() const noexcept { return &((Sprite*) memory().oam_ram_ptr())[0]; } +const Sprite* GPU::sprites_end() const noexcept { return &((Sprite*) memory().oam_ram_ptr())[40]; } + +std::vector GPU::dump_background() +{ + std::vector data(256 * 256); + // create tiledata object from LCDC register + auto td = this->create_tiledata(bg_tiles(), tile_data()); + auto tconf = this->tile_config(); + + for (int y = 0; y < 256; y++) + for (int x = 0; x < 256; x++) + { + // get the tile id + const int tid = td.tile_id(x >> 3, y >> 3); + const int tattr = td.tile_attr(x >> 3, y >> 3); + // copy the 16-byte tile into buffer + const int idx = td.pattern(tid, tattr, x & 7, y & 7); + data.at(y * 256 + x) = this->colorize_tile(tconf, tattr, idx); + } + return data; +} +std::vector GPU::dump_tiles(int bank) +{ + std::vector data(16 * 24 * 8 * 8); + // tiles start at the beginning of video RAM + auto td = this->create_tiledata(0x8000, 0x8000); + auto tconf = this->tile_config(); + const uint8_t attr = (bank == 0) ? 0x00 : 0x08; + + for (int y = 0; y < 24 * 8; y++) + for (int x = 0; x < 16 * 8; x++) + { + int tile = (y / 8) * 16 + (x / 8); + // copy the 16-byte tile into buffer + const int idx = td.pattern(tile, attr, x & 7, y & 7); + data.at(y * 128 + x) = this->colorize_tile(tconf, attr, idx); + } + return data; +} + +void GPU::set_video_bank(const uint8_t bank) +{ + assert(bank < 2); + this->m_state.video_offset = bank * 0x2000; +} +void GPU::lcd_power_changed(const bool online) +{ + // printf("Screen turned %s\n", online ? "ON" : "OFF"); + if (online) + { + // at the start of a new frame + this->m_state.period = this->scanline_cycles(); + this->m_state.current_scanline = 153; + this->m_reg_ly = this->m_state.current_scanline; + } + else + { + // LCD off, just reset to LY 0 + this->m_state.period = 0; + this->m_state.current_scanline = 0; + this->m_reg_ly = 0; + // modify stat to V-blank? + this->set_mode(1); + // theres a full white frame when turning on again + this->m_state.white_frame = true; + } +} + +void GPU::setpal(uint16_t index, uint8_t value) +{ + this->getpal(index) = value; + // sprite palette index 0 is unused + if (index >= 64 && (index & 7) < 2) return; + // + if (this->m_on_palchange) + { + const uint8_t base = index / 2; + const uint16_t c16 = getpal(base * 2) | (getpal(base * 2 + 1) << 8); + // linearize palette memory + this->m_on_palchange(base, c16); + } +} // setpal(...) + +void GPU::set_dmg_variant(dmg_variant_t variant) { this->m_variant = variant; } + +// serialization +int GPU::restore_state(const std::vector& data, int off) +{ + this->m_state = *(state_t*) &data.at(off); + return sizeof(m_state); +} +void GPU::serialize_state(std::vector& res) const +{ + res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); +} +} // namespace gbc diff --git a/components/libgbc/src/instructions.cpp b/components/libgbc/src/instructions.cpp new file mode 100644 index 00000000..54d4a5ee --- /dev/null +++ b/components/libgbc/src/instructions.cpp @@ -0,0 +1,778 @@ +// only include this file once! +#include "machine.hpp" +#include "printers.hpp" +#define DEF_INSTR(x) \ + static instruction_t instr_##x { handler_##x, printer_##x } +#define INSTRUCTION(x) static void handler_##x +#define PRINTER(x) static int printer_##x +union imm8_t +{ + uint8_t u8; + int8_t s8; +}; + +namespace gbc +{ +INSTRUCTION(NOP)(CPU&, const uint8_t) +{ + // NOP takes 4 T-states (instruction decoding) +} +PRINTER(NOP)(char* buffer, size_t len, CPU&, const uint8_t) { return snprintf(buffer, len, "NOP"); } + +INSTRUCTION(LD_N_SP)(CPU& cpu, const uint8_t) { cpu.mtwrite16(cpu.readop16(), cpu.registers().sp); } +PRINTER(LD_N_SP)(char* buffer, size_t len, CPU& cpu, const uint8_t) +{ + return snprintf(buffer, len, "LD (%04X), SP", cpu.peekop16(1)); +} + +INSTRUCTION(LD_R_N)(CPU& cpu, const uint8_t opcode) +{ + cpu.registers().getreg_sp(opcode) = cpu.readop16(); +} +PRINTER(LD_R_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + return snprintf(buffer, len, "LD %s, %04x", cstr_reg(opcode, true), cpu.peekop16(1)); +} + +INSTRUCTION(ADD_HL_R)(CPU& cpu, const uint8_t opcode) +{ + auto& reg = cpu.registers().getreg_sp(opcode); + auto& hl = cpu.registers().hl; + auto& flags = cpu.registers().flags; + setflag(false, flags, MASK_NEGATIVE); + setflag(((hl & 0x0fff) + (reg & 0x0fff)) & 0x1000, flags, MASK_HALFCARRY); + setflag(((hl & 0x0ffff) + (reg & 0x0ffff)) & 0x10000, flags, MASK_CARRY); + hl += reg; + cpu.hardware_tick(); +} +PRINTER(ADD_HL_R)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + return snprintf(buffer, len, "ADD HL, %s", cstr_reg(opcode, true)); +} + +INSTRUCTION(LD_R_A_R)(CPU& cpu, const uint8_t opcode) +{ + if (opcode & 0x8) { cpu.registers().accum = cpu.mtread8(cpu.registers().getreg_sp(opcode)); } + else + { + cpu.mtwrite8(cpu.registers().getreg_sp(opcode), cpu.registers().accum); + } +} +PRINTER(LD_R_A_R)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + if (opcode & 0x8) { return snprintf(buffer, len, "LD A, (%s)", cstr_reg(opcode, true)); } + return snprintf(buffer, len, "LD (%s), A", cstr_reg(opcode, true)); +} + +INSTRUCTION(INC_DEC_R)(CPU& cpu, const uint8_t opcode) +{ + auto& reg = cpu.registers().getreg_sp(opcode); + if ((opcode & 0x8) == 0) { reg++; } + else + { + reg--; + } + cpu.hardware_tick(); +} +PRINTER(INC_DEC_R)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + if ((opcode & 0x8) == 0) { return snprintf(buffer, len, "INC %s", cstr_reg(opcode, true)); } + return snprintf(buffer, len, "DEC %s", cstr_reg(opcode, true)); +} + +INSTRUCTION(INC_DEC_D)(CPU& cpu, const uint8_t opcode) +{ + const uint8_t dst = opcode >> 3; + uint8_t value; + if (dst != 0x6) + { + if ((opcode & 0x1) == 0) { cpu.registers().getdest(dst)++; } + else + { + cpu.registers().getdest(dst)--; + } + value = cpu.registers().getdest(dst); + } + else + { + value = cpu.read_hl(); + if ((opcode & 0x1) == 0) { value++; } + else + { + value--; + } + cpu.write_hl(value); + } + auto& flags = cpu.registers().flags; + setflag(opcode & 0x1, flags, MASK_NEGATIVE); + setflag(value == 0, flags, MASK_ZERO); // set zero + if ((opcode & 0x1) == 0) + setflag((value & 0xF) == 0x0, flags, MASK_HALFCARRY); + else + setflag((value & 0xF) == 0xF, flags, MASK_HALFCARRY); +} +PRINTER(INC_DEC_D)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + const char* mnemonic = (opcode & 0x1) ? "DEC" : "INC"; + return snprintf(buffer, len, "%s %s", mnemonic, cstr_dest(opcode >> 3)); +} + +INSTRUCTION(LD_D_N)(CPU& cpu, const uint8_t opcode) +{ + const uint8_t imm8 = cpu.readop8(); + if (((opcode >> 3) & 0x7) != 0x6) { cpu.registers().getdest(opcode >> 3) = imm8; } + else + { + cpu.write_hl(imm8); + } +} +PRINTER(LD_D_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + if (((opcode >> 3) & 0x7) != 0x6) + return snprintf(buffer, len, "LD %s, %02X", cstr_dest(opcode >> 3), cpu.peekop8(1)); + else + return snprintf(buffer, len, "LD (HL=%04X), %02X", cpu.registers().hl, cpu.peekop8(1)); +} + +INSTRUCTION(RLC_RRC)(CPU& cpu, const uint8_t opcode) +{ + auto& accum = cpu.registers().accum; + auto& flags = cpu.registers().flags; + switch (opcode) + { + case 0x07: + { + // RLCA, rotate A left + const uint8_t bit7 = accum & 0x80; + accum = (accum << 1) | (bit7 >> 7); + flags = 0; + setflag(bit7, flags, MASK_CARRY); // old bit7 to CF + } + break; + case 0x0F: + { + // RRCA, rotate A right + const uint8_t bit0 = accum & 0x1; + accum = (accum >> 1) | (bit0 << 7); + flags = 0; + setflag(bit0, flags, MASK_CARRY); // old bit0 to CF + } + break; + case 0x17: + { + // RLA, rotate A left, old CF to bit 0 + const uint8_t bit7 = accum & 0x80; + accum = (accum << 1) | ((flags & MASK_CARRY) >> 4); + flags = 0; + setflag(bit7, flags, MASK_CARRY); // old bit7 to CF + } + break; + case 0x1F: + { + // RRA, rotate A right, old CF to bit 7 + const uint8_t bit0 = accum & 0x1; + accum = (accum >> 1) | ((flags & MASK_CARRY) << 3); + flags = 0; + setflag(bit0, flags, MASK_CARRY); // old bit0 to CF + } + break; + default: + GBC_ASSERT(0 && "Unknown opcode in RLC/RRC handler"); + } +} +PRINTER(RLC_RRC)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + const char* mnemonic[4] = {"RLC", "RRC", "RL", "RR"}; + return snprintf(buffer, len, "%s A (A = %02X)", mnemonic[opcode >> 3], cpu.registers().accum); +} + +INSTRUCTION(LD_D_D)(CPU& cpu, const uint8_t opcode) +{ + const bool HL = (opcode & 0x7) == 0x6; + uint8_t reg; + if (!HL) + reg = cpu.registers().getdest(opcode); + else + reg = cpu.read_hl(); + + if (((opcode >> 3) & 0x7) != 0x6) { cpu.registers().getdest(opcode >> 3) = reg; } + else + { + cpu.write_hl(reg); + } +} +PRINTER(LD_D_D)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + return snprintf(buffer, len, "LD %s, %s", cstr_dest(opcode >> 3), cstr_dest(opcode >> 0)); +} + +INSTRUCTION(LD_N_A_N)(CPU& cpu, const uint8_t opcode) +{ + const uint16_t addr = cpu.readop16(); + if (opcode == 0xEA) + { + // load into (N) from A + cpu.mtwrite8(addr, cpu.registers().accum); + } + else + { + // load into A from (N) + cpu.registers().accum = cpu.mtread8(addr); + } + cpu.hardware_tick(); // 16 T-states +} +PRINTER(LD_N_A_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + if (opcode == 0xEA) + return snprintf(buffer, len, "LD (%04X), A (A = %02X)", cpu.peekop16(1), + cpu.registers().accum); + else + return snprintf(buffer, len, "LD A, (%04X)", cpu.peekop16(1)); +} + +INSTRUCTION(LDID_HL_A)(CPU& cpu, const uint8_t opcode) +{ + if ((opcode & 0x8) == 0) + { + // load from A into (HL) + cpu.write_hl(cpu.registers().accum); + } + else + { + // load from (HL) into A + cpu.registers().accum = cpu.read_hl(); + } + if ((opcode & 0x10) == 0) { cpu.registers().hl++; } + else + { + cpu.registers().hl--; + } +} +PRINTER(LDID_HL_A)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + const char* mnemonic = (opcode & 0x10) ? "LDD" : "LDI"; + if ((opcode & 0x8) == 0) + return snprintf(buffer, len, "%s (HL=%04X), A", mnemonic, cpu.registers().hl); + else + return snprintf(buffer, len, "%s A, (HL=%04X)", mnemonic, cpu.registers().hl); +} + +INSTRUCTION(DAA)(CPU& cpu, const uint8_t) +{ + auto& regs = cpu.registers(); + if (regs.flags & MASK_NEGATIVE) + { + if (regs.flags & MASK_CARRY) regs.accum -= 0x60; + if (regs.flags & MASK_HALFCARRY) regs.accum -= 0x06; + } + else + { + if ((regs.flags & MASK_CARRY) || regs.accum > 0x99) + { + regs.accum += 0x60; + setflag(true, regs.flags, MASK_CARRY); + } + if ((regs.flags & MASK_HALFCARRY) || (regs.accum & 0xF) > 0x9) { regs.accum += 0x06; } + } + setflag(regs.accum == 0, regs.flags, MASK_ZERO); + setflag(false, regs.flags, MASK_HALFCARRY); +} +PRINTER(DAA)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "DAA"); } + +INSTRUCTION(CPL_A)(CPU& cpu, const uint8_t) +{ + cpu.registers().accum = ~cpu.registers().accum; + auto& regs = cpu.registers(); + setflag(true, regs.flags, MASK_NEGATIVE); + setflag(true, regs.flags, MASK_HALFCARRY); +} +PRINTER(CPL_A)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "CPL A"); } + +INSTRUCTION(SCF_CCF)(CPU& cpu, const uint8_t opcode) +{ + auto& flags = cpu.registers().flags; + if ((opcode & 0x8) == 0) + { + // Set CF + flags |= MASK_CARRY; + } + else + { + // Complement CF + setflag(not(flags & MASK_CARRY), flags, MASK_CARRY); + } + setflag(false, flags, MASK_NEGATIVE); + setflag(false, flags, MASK_HALFCARRY); +} +PRINTER(SCF_CCF)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + return snprintf(buffer, len, (opcode & 0x8) ? "CCF (CY=0)" : "SCF (CY=1)"); +} + +// ALU A, D / A, N +INSTRUCTION(ALU_A_D)(CPU& cpu, const uint8_t opcode) +{ + const uint8_t alu_op = (opcode >> 3) & 0x7; + // A, D + if ((opcode & 0x7) != 0x6) { cpu.registers().alu(alu_op, cpu.registers().getdest(opcode)); } + else + { + cpu.registers().alu(alu_op, cpu.read_hl()); + } +} +PRINTER(ALU_A_D)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + return snprintf(buffer, len, "%s A, %s", cstr_alu(opcode >> 3), cstr_dest(opcode)); +} + +INSTRUCTION(ALU_A_N)(CPU& cpu, const uint8_t opcode) +{ + const uint8_t alu_op = (opcode >> 3) & 0x7; + // A, N + const uint8_t imm8 = cpu.readop8(); + cpu.registers().alu(alu_op, imm8); +} +PRINTER(ALU_A_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + return snprintf(buffer, len, "%s A, 0x%02x", cstr_alu(opcode >> 3), cpu.peekop8(1)); +} + +INSTRUCTION(JP)(CPU& cpu, const uint8_t opcode) +{ + const uint16_t dest = cpu.readop16(); + if ((opcode & 1) || (cpu.registers().compare_flags(opcode))) + { + cpu.jump(dest); + cpu.hardware_tick(); + } +} +PRINTER(JP)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + if (opcode & 1) { return snprintf(buffer, len, "JP 0x%04x", cpu.peekop16(1)); } + char temp[128]; + fill_flag_buffer(temp, sizeof(temp), opcode, cpu.registers().flags); + return snprintf(buffer, len, "JP 0x%04x (%s)", cpu.peekop16(1), temp); +} + +INSTRUCTION(PUSH_POP)(CPU& cpu, const uint8_t opcode) +{ + if (opcode & 4) + { + // PUSH R + cpu.push_value(cpu.registers().getreg_af(opcode)); + } + else + { + // POP R + cpu.registers().getreg_af(opcode) = cpu.mtread16(cpu.registers().sp); + cpu.registers().sp += 2; + if (((opcode >> 4) & 0x3) == 0x3) + { + // NOTE: POP AF requires clearing flag bits 0-3 + cpu.registers().flags &= 0xF0; + } + } +} +PRINTER(PUSH_POP)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + if (opcode & 4) + { + return snprintf(buffer, len, "PUSH %s (0x%04x)", cstr_reg(opcode, false), + cpu.registers().getreg_af(opcode)); + } + return snprintf(buffer, len, "POP %s (0x%04x)", cstr_reg(opcode, false), + cpu.memory().read16(cpu.registers().sp)); +} + +INSTRUCTION(RET)(CPU& cpu, const uint8_t opcode) +{ + if ((opcode & 0xef) == 0xc9 || cpu.registers().compare_flags(opcode)) + { + cpu.registers().pc = cpu.mtread16(cpu.registers().sp); + cpu.registers().sp += 2; + if (UNLIKELY(cpu.machine().verbose_instructions)) + { printf("* Returned to 0x%04x\n", cpu.registers().pc); } + if (opcode != 0xc9) + { + cpu.hardware_tick(); // RET nzc needs one more tick + } + } + cpu.hardware_tick(); +} +PRINTER(RET)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + if (opcode == 0xc9) { return snprintf(buffer, len, "RET"); } + char temp[128]; + fill_flag_buffer(temp, sizeof(temp), opcode, cpu.registers().flags); + return snprintf(buffer, len, "RET %s", temp); +} + +INSTRUCTION(RETI)(CPU& cpu, const uint8_t) +{ + cpu.registers().pc = cpu.mtread16(cpu.registers().sp); + cpu.registers().sp += 2; + if (UNLIKELY(cpu.machine().verbose_instructions)) + { printf("* Returned (w/interrupts) to 0x%04x\n", cpu.registers().pc); } + cpu.hardware_tick(); + cpu.enable_interrupts(); +} +PRINTER(RETI)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "RETI"); } + +INSTRUCTION(RST)(CPU& cpu, const uint8_t opcode) +{ + const uint16_t dst = opcode & 0x38; + if (UNLIKELY(cpu.registers().pc == dst + 1)) + { + printf(">>> RST loop detected at vector 0x%04x\n", dst); + cpu.break_now(); + return; + } + // jump to vector area + cpu.push_and_jump(dst); +} +PRINTER(RST)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + return snprintf(buffer, len, "RST 0x%02x", opcode & 0x38); +} + +INSTRUCTION(STOP)(CPU& cpu, const uint8_t) +{ + // STOP is a weirdo two-byte instruction + cpu.registers().pc++; + if (cpu.machine().io.joypad_is_disabled()) + { + printf("The machine has stopped with joypad disabled\n"); + cpu.break_now(); + } + // enter stopped state + cpu.stop(); +} +PRINTER(STOP)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "STOP"); } + +INSTRUCTION(JR_N)(CPU& cpu, const uint8_t opcode) +{ + const imm8_t disp{.u8 = cpu.readop8()}; + cpu.hardware_tick(); + if (opcode == 0x18 || (cpu.registers().compare_flags(opcode))) + { cpu.jump(cpu.registers().pc + disp.s8); } +} +PRINTER(JR_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + const imm8_t disp{.u8 = cpu.peekop8(1)}; + const uint16_t dest = cpu.registers().pc + 2 + disp.s8; + if (opcode & 0x20) + { + char temp[128]; + fill_flag_buffer(temp, sizeof(temp), opcode, cpu.registers().flags); + return snprintf(buffer, len, "JR %+hhd (%s) => %04X", disp.s8, temp, dest); + } + return snprintf(buffer, len, "JR %+hhd => %04X", disp.s8, dest); +} + +INSTRUCTION(HALT)(CPU& cpu, const uint8_t) +{ + if (cpu.ime()) { cpu.wait(); } + else + { + cpu.buggy_halt(); + } +} +PRINTER(HALT)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "HALT"); } + +INSTRUCTION(CALL)(CPU& cpu, const uint8_t opcode) +{ + const uint16_t dest = cpu.readop16(); + if ((opcode & 1) || cpu.registers().compare_flags(opcode)) + { + // push address of **next** instr + cpu.push_and_jump(dest); + } +} +PRINTER(CALL)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + if (opcode & 1) { return snprintf(buffer, len, "CALL %04X", cpu.peekop16(1)); } + char temp[128]; + fill_flag_buffer(temp, sizeof(temp), opcode, cpu.registers().flags); + return snprintf(buffer, len, "CALL %04X (%s)", cpu.peekop16(1), temp); +} + +INSTRUCTION(ADD_SP_N)(CPU& cpu, const uint8_t) +{ + const imm8_t imm{.u8 = cpu.readop8()}; + auto& regs = cpu.registers(); + const int calc = (regs.sp + imm.s8) & 0xFFFF; + regs.flags = 0; + setflag(((regs.sp ^ imm.s8 ^ calc) & 0x100) == 0x100, regs.flags, MASK_CARRY); + setflag(((regs.sp ^ imm.s8 ^ calc) & 0x10) == 0x10, regs.flags, MASK_HALFCARRY); + cpu.registers().sp = calc; + cpu.hardware_tick(); + cpu.hardware_tick(); +} +PRINTER(ADD_SP_N)(char* buffer, size_t len, CPU& cpu, uint8_t) +{ + return snprintf(buffer, len, "ADD SP, 0x%02x", cpu.peekop8(1)); +} + +INSTRUCTION(LD_FF00_A)(CPU& cpu, const uint8_t opcode) +{ + switch (opcode) + { + case 0xE2: + cpu.mtwrite8(0xFF00 + cpu.registers().c, cpu.registers().accum); + return; + case 0xF2: + cpu.registers().accum = cpu.mtread8(0xFF00 + cpu.registers().c); + return; + case 0xE0: + cpu.mtwrite8(0xFF00 + cpu.readop8(), cpu.registers().accum); + return; + case 0xF0: + cpu.registers().accum = cpu.mtread8(0xFF00 + cpu.readop8()); + return; + } + GBC_ASSERT(0); +} +PRINTER(LD_FF00_A)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + switch (opcode) + { + case 0xE2: + return snprintf(buffer, len, "LD (FF00+C=%02X), A", cpu.registers().c); + case 0xF2: + return snprintf(buffer, len, "LD A, (FF00+C=%02X)", cpu.registers().c); + case 0xE0: + return snprintf(buffer, len, "LD (FF00+%02X), A", cpu.peekop8(1)); + case 0xF0: + return snprintf(buffer, len, "LD A, (FF00+%02X)", cpu.peekop8(1)); + } + GBC_ASSERT(0); +} + +INSTRUCTION(LD_HL_SP)(CPU& cpu, const uint8_t opcode) +{ + if (opcode == 0xF8) + { + // the ADD operation is signed + const imm8_t imm{.u8 = cpu.readop8()}; + cpu.registers().flags = 0; + setflag(((cpu.registers().sp & 0xf) + (imm.u8 & 0x0f)) & 0x10, cpu.registers().flags, + MASK_HALFCARRY); + setflag(((cpu.registers().sp & 0xff) + (imm.u8 & 0xff)) & 0x100, cpu.registers().flags, + MASK_CARRY); + cpu.registers().hl = cpu.registers().sp + imm.s8; + } + else + { + cpu.registers().sp = cpu.registers().hl; + } + cpu.hardware_tick(); +} +PRINTER(LD_HL_SP)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) +{ + if (opcode == 0xF8) { return snprintf(buffer, len, "LD HL, SP + %02X", cpu.peekop8(1)); } + return snprintf(buffer, len, "LD SP, HL (HL=%04X)", cpu.registers().hl); +} + +INSTRUCTION(JP_HL)(CPU& cpu, const uint8_t) { cpu.jump(cpu.registers().hl); } +PRINTER(JP_HL)(char* buffer, size_t len, CPU& cpu, uint8_t) +{ + return snprintf(buffer, len, "JP HL (HL=%04X)", cpu.registers().hl); +} + +INSTRUCTION(DI_EI)(CPU& cpu, const uint8_t opcode) +{ + if (opcode & 0x08) { cpu.enable_interrupts(); } + else + { + cpu.disable_interrupts(); + } +} +PRINTER(DI_EI)(char* buffer, size_t len, CPU&, uint8_t opcode) +{ + const char* mnemonic = (opcode & 0x08) ? "EI" : "DI"; + return snprintf(buffer, len, "%s", mnemonic); +} + +INSTRUCTION(CB_EXT)(CPU& cpu, const uint8_t) +{ + const uint8_t opcode = cpu.readop8(); + const bool HL = (opcode & 0x7) == 0x6; + uint8_t reg; + if (!HL) + reg = cpu.registers().getdest(opcode); + else + reg = cpu.read_hl(); + + // BIT, RESET, SET + if (opcode >> 6) + { + const uint8_t bit = (opcode >> 3) & 7; + switch (opcode >> 6) + { + case 0x1: + { // BIT + const int set = reg & (1 << bit); + // set flags + cpu.registers().flags &= ~MASK_NEGATIVE; + cpu.registers().flags |= MASK_HALFCARRY; + setflag(set == 0, cpu.registers().flags, MASK_ZERO); + // BIT only takes 8/12 T-cycles + return; + } + case 0x2: // RESET + reg &= ~(1 << bit); + break; + case 0x3: // SET + reg |= 1 << bit; + break; + } + } + else if ((opcode & 0xF0) == 0x00) + { + auto& flags = cpu.registers().flags; + flags = 0; + if (opcode & 0x8) + { + // RRC D, rotate D right, keep old bit0 + setflag(reg & 0x1, flags, MASK_CARRY); // old bit0 to CF + reg = (reg >> 1) | (reg << 7); + } + else + { + // RLC D, rotate D left, keep old bit7 + setflag(reg & 0x80, flags, MASK_CARRY); // old bit7 to CF + reg = (reg << 1) | (reg >> 7); + } + setflag(reg == 0, cpu.registers().flags, MASK_ZERO); + } + else if ((opcode & 0xF0) == 0x10) + { + auto& flags = cpu.registers().flags; + // NOTE: dont reset flags here + if (opcode & 0x8) + { + // RR D, rotate D right through carry, old CF to bit 7 + const uint8_t bit0 = reg & 0x1; + reg = (reg >> 1) | ((flags & MASK_CARRY) << 3); + flags = 0; + setflag(bit0, flags, MASK_CARRY); // old bit0 to CF + } + else + { + // RL D, rotate D left through carry, old CF to bit 0 + const uint8_t bit7 = reg & 0x80; + reg = (reg << 1) | ((flags & MASK_CARRY) >> 4); + flags = 0; + setflag(bit7, flags, MASK_CARRY); // old bit7 to CF + } + setflag(reg == 0, flags, MASK_ZERO); + } + else if ((opcode & 0xF0) == 0x20) + { + auto& flags = cpu.registers().flags; + flags = 0; + if (opcode & 0x8) + { + // SRA D + setflag(reg & 0x1, flags, MASK_CARRY); + reg >>= 1; + reg |= (reg & 0x40) << 1; + } + else + { + // SLA D + setflag(reg & 0x80, flags, MASK_CARRY); + reg <<= 1; + } + setflag(reg == 0, cpu.registers().flags, MASK_ZERO); + } + else if ((opcode & 0xf0) == 0x30) + { + if ((opcode & 0x8) == 0x0) + { + // SWAP D + reg = (reg >> 4) | (reg << 4); + cpu.registers().flags = 0; + setflag(reg == 0, cpu.registers().flags, MASK_ZERO); + } + else + { + // SRL D (logical) + cpu.registers().flags = 0; + setflag(reg & 0x1, cpu.registers().flags, MASK_CARRY); + reg >>= 1; + setflag(reg == 0, cpu.registers().flags, MASK_ZERO); + } + } + else + { + fprintf(stderr, "Missing instruction: %#x\n", opcode); + GBC_ASSERT(0 && "Unimplemented extended instruction"); + } + // all instructions on this opcode go into the same dest + if (!HL) + cpu.registers().getdest(opcode) = reg; + else + cpu.write_hl(reg); +} +PRINTER(CB_EXT)(char* buffer, size_t len, CPU& cpu, uint8_t) +{ + const uint8_t opcode = cpu.peekop8(1); + if (opcode >> 6) + { + const char* mnemonic[] = {"IMPLEMENT ME", "BIT", "RES", "SET"}; + return snprintf(buffer, len, "%s %u, %s", mnemonic[opcode >> 6], (opcode >> 3) & 0x7, + cstr_dest(opcode)); + } + else + { + const char* mnemonic[] = {"RLC", "RRC", "RL", "RR", "SLA", "SRA", "SWAP", "SRL"}; + return snprintf(buffer, len, "%s %s", mnemonic[opcode >> 3], cstr_dest(opcode)); + } +} + +INSTRUCTION(MISSING)(CPU& cpu, const uint8_t opcode) +{ + fprintf(stderr, "Missing instruction: %#x\n", opcode); + // pause for each instruction + cpu.print_and_pause(cpu, opcode); +} +PRINTER(MISSING)(char* buffer, size_t len, CPU&, const uint8_t opcode) +{ + return snprintf(buffer, len, "MISSING opcode 0x%02x", opcode); +} + +DEF_INSTR(NOP); +DEF_INSTR(LD_N_SP); +DEF_INSTR(LD_R_N); +DEF_INSTR(ADD_HL_R); +DEF_INSTR(LD_R_A_R); +DEF_INSTR(INC_DEC_R); +DEF_INSTR(INC_DEC_D); +DEF_INSTR(LD_D_N); +DEF_INSTR(LD_D_D); +DEF_INSTR(LD_N_A_N); +DEF_INSTR(JR_N); +DEF_INSTR(RLC_RRC); +DEF_INSTR(LDID_HL_A); +DEF_INSTR(DAA); +DEF_INSTR(CPL_A); +DEF_INSTR(SCF_CCF); +DEF_INSTR(HALT); +DEF_INSTR(ALU_A_D); +DEF_INSTR(ALU_A_N); +DEF_INSTR(PUSH_POP); +DEF_INSTR(RST); +DEF_INSTR(RET); +DEF_INSTR(RETI); +DEF_INSTR(STOP); +DEF_INSTR(JP); +DEF_INSTR(CALL); +DEF_INSTR(ADD_SP_N); +DEF_INSTR(LD_FF00_A); +DEF_INSTR(LD_HL_SP); +DEF_INSTR(JP_HL); +DEF_INSTR(DI_EI); +DEF_INSTR(CB_EXT); +DEF_INSTR(MISSING); +} // namespace gbc diff --git a/components/libgbc/src/io.cpp b/components/libgbc/src/io.cpp new file mode 100644 index 00000000..b9ec82d7 --- /dev/null +++ b/components/libgbc/src/io.cpp @@ -0,0 +1,234 @@ +#include "io.hpp" +#include "io_regs.cpp" +#include "machine.hpp" +#include + +namespace gbc +{ +IO::IO(Machine& mach) + : vblank{0x1, 0x40, "V-blank"} + , lcd_stat{0x2, 0x48, "LCD Status"} + , timerint{0x4, 0x50, "Timer"} + , serialint{0x8, 0x58, "Serial"} + , joypadint{0x10, 0x60, "Joypad"} + , debugint{0x0, 0x0, "Debug"} + , m_machine(mach) +{ + this->reset(); +} + +void IO::reset() +{ + // register defaults + reg(REG_P1) = 0xcf; + reg(REG_TIMA) = 0x00; + reg(REG_TMA) = 0x00; + reg(REG_TAC) = 0xf8; + // sound defaults + reg(REG_NR10) = 0x80; + reg(REG_NR11) = 0xbf; + reg(REG_NR52) = 0xf1; + // LCD defaults + reg(REG_LCDC) = 0x91; + reg(REG_STAT) = 0x81; + reg(REG_LY) = 0x90; // 144 + reg(REG_LYC) = 0x0; + reg(REG_DMA) = 0x00; + // Palette + reg(REG_BGP) = 0xfc; + reg(REG_OBP0) = 0xff; + reg(REG_OBP1) = 0xff; + // boot rom enabled at boot + reg(REG_BOOT) = 0x00; + reg(REG_HDMA5) = 0xFF; + + this->m_state.reg_ie = 0x00; +} + +void IO::simulate() +{ + // 1. DIV timer + this->m_state.divider += 256 / 64; + this->reg(REG_DIV) = this->m_state.divider >> 8; + + // 2. TIMA timer + if (this->reg(REG_TAC) & 0x4) + { + const std::array TIMA_CYCLES = {1024, 16, 64, 256}; + const int speed = this->reg(REG_TAC) & 0x3; + // TIMA counter timer + if (m_state.divider % (TIMA_CYCLES[speed]) == 0) + { + this->reg(REG_TIMA)++; + // if (reg(REG_TIMA) > 16) machine().break_now(); + // timer interrupt when overflowing to 0 + if (this->reg(REG_TIMA) == 0) + { + this->trigger(this->timerint); + // BUG: TIMA does not get reset before after 4 cycles + this->m_state.timabug = 4; + } + } + else if (UNLIKELY(this->m_state.timabug > 0)) + { + this->m_state.timabug--; + if (this->m_state.timabug == 0) + { + // restart at modulo + this->reg(REG_TIMA) = this->reg(REG_TMA); + } + } + } + + // 3. OAM DMA operation + if (this->m_state.dma.bytes_left > 0) + { + if (this->m_state.dma.slow_start > 0) { this->m_state.dma.slow_start--; } + else + { + // calculate number of bytes to copy + const int btw = 1; + // do the copying + auto& memory = machine().memory; + for (int i = 0; i < btw; i++) + { memory.write8(m_state.dma.dst++, memory.read8(m_state.dma.src++)); } + assert(m_state.dma.bytes_left >= btw); + m_state.dma.bytes_left -= btw; + } + } + + // 4. HDMA operation + if (this->hdma().bytes_left > 0) + { + // during H-blank, once for each line + if (machine().gpu.is_hblank() && hdma().cur_line != reg(REG_LY)) + { + hdma().cur_line = reg(REG_LY); + auto& memory = machine().memory; + int btw = std::min(16, (int)(hdma().bytes_left)); + // do the copying + for (int i = 0; i < btw; i++) + { memory.write8(hdma().dst++, memory.read8(hdma().src++)); } + hdma().dst &= 0x9FFF; // make sure it wraps around VRAM + assert(hdma().bytes_left >= btw); + hdma().bytes_left -= btw; + if (hdma().bytes_left == 0) + { + // transfer complete + this->reg(REG_HDMA5) = 0xFF; + } + } + } +} + +uint8_t IO::read_io(const uint16_t addr) +{ + // default: just return the register value + if (addr >= 0xff00 && addr < 0xff80) + { + if (UNLIKELY(machine().break_on_io && !machine().is_breaking())) + { + printf("[io] * I/O read 0x%04x => 0x%02x\n", addr, reg(addr)); + machine().break_now(); + } + auto& handler = iologic.at(addr - 0xff00); + if (handler.on_read != nullptr) { return handler.on_read(*this, addr); } + return reg(addr); + } + if (addr == REG_IE) { return this->m_state.reg_ie; } + printf("[io] * Unknown read 0x%04x\n", addr); + machine().undefined(); + return 0xff; +} +void IO::write_io(const uint16_t addr, uint8_t value) +{ + // default: just write to register + if (addr >= 0xff00 && addr < 0xff80) + { + if (UNLIKELY(machine().break_on_io && !machine().is_breaking())) + { + printf("[io] * I/O write 0x%04x value 0x%02x\n", addr, value); + machine().break_now(); + } + auto& handler = iologic.at(addr - 0xff00); + if (handler.on_write != nullptr) + { + handler.on_write(*this, addr, value); + return; + } + // default: just write... + reg(addr) = value; + return; + } + if (addr == REG_IE) + { + this->m_state.reg_ie = value; + return; + } + printf("[io] * Unknown write 0x%04x value 0x%02x\n", addr, value); + machine().undefined(); +} + +void IO::trigger_keys(uint8_t mask) +{ + joypad().keypad = ~(mask & 0xF); + joypad().buttons = ~(mask >> 4); + // trigger joypad interrupt on every change + if (joypad().last_mask != mask) + { + joypad().last_mask = mask; + this->trigger(joypadint); + } +} +bool IO::joypad_is_disabled() const noexcept { return (reg(REG_P1) & 0x30) == 0x30; } + +void IO::start_dma(uint16_t src) +{ + oam_dma().slow_start = 2; + oam_dma().src = src; + oam_dma().dst = 0xfe00; + oam_dma().bytes_left = 160; // 160 bytes total +} + +void IO::start_hdma(uint16_t src, uint16_t dst, uint16_t bytes) +{ + hdma().src = src; + hdma().dst = dst; + hdma().bytes_left = bytes; + hdma().cur_line = 0xff; +} + +void IO::perform_stop() +{ + // bit 1 = stopped, bit 8 = LCD on/off + this->reg(REG_KEY1) = 0x1; + // remember previous LCD on/off value + this->m_state.lcd_powered = reg(REG_LCDC) & 0x80; + // disable LCD + reg(REG_LCDC) &= ~0x80; + // enable joypad interrupts + this->m_state.reg_ie |= joypadint.mask; +} +void IO::deactivate_stop() +{ + // turn screen back on, if it was turned off + if (this->m_state.lcd_powered) reg(REG_LCDC) |= 0x80; + reg(REG_KEY1) = machine().memory.double_speed() ? 0x80 : 0x0; +} + +void IO::reset_divider() +{ + this->m_state.divider = 0; + this->reg(REG_DIV) = 0; +} + +int IO::restore_state(const std::vector& data, int off) +{ + this->m_state = *(state_t*) &data.at(off); + return sizeof(m_state); +} +void IO::serialize_state(std::vector& res) const +{ + res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); +} +} // namespace gbc diff --git a/components/libgbc/src/io_regs.cpp b/components/libgbc/src/io_regs.cpp new file mode 100644 index 00000000..158608fe --- /dev/null +++ b/components/libgbc/src/io_regs.cpp @@ -0,0 +1,235 @@ +#include "machine.hpp" +// should only be included once +#define IOHANDLER(off, x) new (&iologic.at(off - 0xff00)) iowrite_t{iowrite_##x, ioread_##x}; + +namespace gbc +{ +struct iowrite_t +{ + using write_handler_t = void (*)(IO&, uint16_t, uint8_t); + using read_handler_t = uint8_t (*)(IO&, uint16_t); + const write_handler_t on_write = nullptr; + const read_handler_t on_read = nullptr; +}; +static std::array iologic = {}; + +void iowrite_JOYP(IO& io, uint16_t, uint8_t value) +{ + if (value & 0x10) + io.joypad().ioswitch = 0; + else if (value & 0x20) + io.joypad().ioswitch = 1; +} +uint8_t ioread_JOYP(IO& io, uint16_t) +{ + io.trigger_joypad_read(); + switch (io.joypad().ioswitch) + { + case 0: + return 0xD0 | io.joypad().buttons; + case 1: + return 0xE0 | io.joypad().keypad; + } + GBC_ASSERT(0 && "Invalid joypad GPIO value"); +} + +void iowrite_DIV(IO& io, uint16_t, uint8_t) +{ + // writing to DIV resets it to 0 + io.reset_divider(); +} +uint8_t ioread_DIV(IO& io, uint16_t) { return io.reg(IO::REG_DIV); } + +void iowrite_LCDC(IO& io, uint16_t addr, uint8_t value) +{ + const bool was_enabled = io.reg(addr) & 0x80; + io.reg(addr) = value; + const bool is_enabled = io.reg(addr) & 0x80; + // check if LCD just turned on + if (!was_enabled && is_enabled) { io.machine().gpu.lcd_power_changed(true); } + // check if LCD was just turned off + else if (was_enabled && !is_enabled) + { + io.machine().gpu.lcd_power_changed(false); + } +} +uint8_t ioread_LCDC(IO& io, uint16_t addr) { return io.reg(addr); } + +void iowrite_STAT(IO& io, uint16_t addr, uint8_t value) +{ + // can only write to the upper bits 3-6 + io.reg(addr) &= 0x87; + io.reg(addr) |= value & 0x78; +} +uint8_t ioread_STAT(IO& io, uint16_t addr) { return io.reg(addr) | 0x80; } + +void iowrite_DMA(IO& io, uint16_t, uint8_t value) +{ + const uint16_t src = value << 8; + // printf("DMA copy start from 0x%04x to 0x%04x\n", src, dst); + io.start_dma(src); +} +uint8_t ioread_DMA(IO& io, uint16_t addr) { return io.reg(addr); } + +void iowrite_HDMA(IO& io, uint16_t addr, uint8_t value) +{ + if (io.machine().is_cgb() == false) return; + switch (addr) + { + case IO::REG_HDMA1: + case IO::REG_HDMA3: + io.reg(addr) = value; + return; + case IO::REG_HDMA2: + case IO::REG_HDMA4: + io.reg(addr) = value & 0xF0; + return; + } + // HDMA 5: start DMA operation + uint16_t src = (io.reg(IO::REG_HDMA1) << 8) | io.reg(IO::REG_HDMA2); + src &= 0xFFF0; + uint16_t dst = (io.reg(IO::REG_HDMA3) << 8) | io.reg(IO::REG_HDMA4); + dst &= 0x9FF0; + dst |= 0x8000; // VRAM only! + // length is measured blocks of 16-bytes, minimum 16 + const uint16_t num_bytes = (1 + (value & 0x7F)) * 16; + + if ((value & 0x80) == 0) + { + if (io.hdma_active()) + { + // disable currently running HDMA + io.start_hdma(0, 0, 0); + io.reg(IO::REG_HDMA5) = value; + } + else + { + // do the transfer immediately + // printf("HDMA transfer 0x%04x to 0x%04x (%u bytes)\n", src, dst, end - src); + const uint16_t end = src + num_bytes; + auto& mem = io.machine().memory; + while (src < end) mem.write8(dst++, mem.read8(src++)); + // transfer complete + io.reg(IO::REG_HDMA5) = 0xFF; + } + } + else + { + // H-blank DMA + io.start_hdma(src, dst, num_bytes); + io.reg(IO::REG_HDMA5) = value; + } +} +uint8_t ioread_HDMA(IO& io, uint16_t addr) +{ + switch (addr) + { + case IO::REG_HDMA1: + case IO::REG_HDMA3: + case IO::REG_HDMA2: + case IO::REG_HDMA4: + return 0xFF; // apparently always? + case IO::REG_HDMA5: + return io.reg(addr); + } + return 0xFF; +} + +void iowrite_AUDIO(IO& io, uint16_t addr, uint8_t value) +{ + io.machine().apu.write(addr, value, io.reg(addr)); +} +uint8_t ioread_AUDIO(IO& io, uint16_t addr) { return io.machine().apu.read(addr, io.reg(addr)); } + +void iowrite_KEY1(IO& io, uint16_t addr, uint8_t value) +{ + // printf("KEY1 0x%04x write 0x%02x\n", addr, value); + io.reg(addr) &= 0x80; + io.reg(addr) |= value & 1; +} +uint8_t ioread_KEY1(IO& io, uint16_t addr) +{ + if (!io.machine().is_cgb()) return 0xff; + return (io.reg(addr) & 0x81) | 0x7E; +} + +void iowrite_VBK(IO& io, uint16_t addr, uint8_t value) +{ + io.reg(addr) = value & 1; + io.machine().gpu.set_video_bank(value & 1); +} +uint8_t ioread_VBK(IO& io, uint16_t addr) { return io.reg(addr) | 0xfe; } + +void iowrite_BOOT(IO& io, uint16_t addr, uint8_t value) +{ + if (value) { io.machine().memory.disable_bootrom(); } + io.reg(addr) |= value; +} +uint8_t ioread_BOOT(IO& io, uint16_t addr) { return io.reg(addr); } + +void iowrite_SVBK(IO& io, uint16_t addr, uint8_t value) +{ + // printf("SVBK 0x%04x write 0x%02x\n", addr, value); + value &= 0x7; + if (value == 0) value = 1; + io.reg(addr) = value; + io.machine().memory.set_wram_bank(value); +} +uint8_t ioread_SVBK(IO& io, uint16_t addr) { return io.reg(addr); } + +inline void auto_increment(uint8_t& idx, const uint8_t mask) +{ + const uint8_t v = idx & mask; + idx &= ~mask; + idx |= (v + 1) & mask; +} + +void iowrite_BGPD(IO& io, uint16_t, uint8_t value) +{ + uint8_t& idx = io.reg(IO::REG_BGPI); // palette index + io.machine().gpu.setpal(0 + (idx & 63), value); + // when bit7 is set, auto-increment the index register + if (idx & 0x80) auto_increment(idx, 63); +} +uint8_t ioread_BGPD(IO& io, uint16_t) +{ + uint8_t idx = io.reg(IO::REG_BGPI) & 63; // palette index + return io.machine().gpu.getpal(0 + idx); +} + +void iowrite_OBPD(IO& io, uint16_t, uint8_t value) +{ + uint8_t& idx = io.reg(IO::REG_OBPI); // palette index + io.machine().gpu.setpal(64 + (idx & 63), value); + // when bit7 is set, auto-increment the index register + if (idx & 0x80) auto_increment(idx, 63); +} +uint8_t ioread_OBPD(IO& io, uint16_t) +{ + uint8_t idx = io.reg(IO::REG_OBPI) & 63; // palette index + return io.machine().gpu.getpal(64 + idx); +} + +__attribute__((constructor)) static void set_io_handlers() +{ + IOHANDLER(IO::REG_P1, JOYP); + IOHANDLER(IO::REG_DIV, DIV); + IOHANDLER(IO::REG_LCDC, LCDC); + IOHANDLER(IO::REG_STAT, STAT); + IOHANDLER(IO::REG_DMA, DMA); + IOHANDLER(IO::REG_NR52, AUDIO); + // CGB registers + IOHANDLER(IO::REG_KEY1, KEY1); + IOHANDLER(IO::REG_VBK, VBK); + IOHANDLER(IO::REG_SVBK, SVBK); + IOHANDLER(IO::REG_BOOT, BOOT); + IOHANDLER(IO::REG_HDMA1, HDMA); + IOHANDLER(IO::REG_HDMA2, HDMA); + IOHANDLER(IO::REG_HDMA3, HDMA); + IOHANDLER(IO::REG_HDMA4, HDMA); + IOHANDLER(IO::REG_HDMA5, HDMA); + // CGB palettes + IOHANDLER(IO::REG_BGPD, BGPD); + IOHANDLER(IO::REG_OBPD, OBPD); +} +} // namespace gbc diff --git a/components/libgbc/src/machine.cpp b/components/libgbc/src/machine.cpp new file mode 100644 index 00000000..959d2304 --- /dev/null +++ b/components/libgbc/src/machine.cpp @@ -0,0 +1,84 @@ +#include "machine.hpp" + +namespace gbc +{ +Machine::Machine(const std::string_view rom, bool init) + : cpu(*this), memory(*this, rom), io(*this), gpu(*this), apu(*this) +{ + // set CGB mode when ROM supports it + const uint8_t cgb = memory.read8(0x143); + this->m_cgb_mode = (cgb & 0x80) && ENABLE_GBC; + // reset CPU now that we know the machine type + if (init) this->cpu.reset(); +} + +void Machine::reset() +{ + cpu.reset(); + memory.reset(); + io.reset(); + gpu.reset(); +} +void Machine::stop() noexcept { this->m_running = false; } + +void Machine::simulate_one_frame() +{ + while (gpu.current_scanline() != 0) { cpu.simulate(); } + while (gpu.current_scanline() != 144) { cpu.simulate(); } + assert(gpu.is_vblank()); +} + +uint64_t Machine::now() noexcept { return cpu.gettime(); } + +void Machine::set_handler(interrupt i, interrupt_handler handler) +{ + switch (i) + { + case VBLANK: + io.vblank.callback = handler; + return; + case TIMER: + io.timerint.callback = handler; + return; + case JOYPAD: + io.joypadint.callback = handler; + return; + case DEBUG: + io.debugint.callback = handler; + return; + } +} + +void Machine::set_inputs(uint8_t mask) { io.trigger_keys(mask); } + +size_t Machine::restore_state(const std::vector& data) +{ + int offset = 0; + offset += cpu.restore_state(data, offset); + offset += memory.restore_state(data, offset); + offset += io.restore_state(data, offset); + offset += gpu.restore_state(data, offset); + offset += apu.restore_state(data, offset); + return offset; +} +void Machine::serialize_state(std::vector& result) const +{ + cpu.serialize_state(result); + memory.serialize_state(result); + io.serialize_state(result); + gpu.serialize_state(result); + apu.serialize_state(result); +} + +void Machine::break_now() { cpu.break_now(); } +bool Machine::is_breaking() const noexcept { return cpu.is_breaking(); } + +void Machine::undefined() +{ + if (this->stop_when_undefined) + { + printf("*** An undefined operation happened\n"); + cpu.break_now(); + } +} +} // namespace gbc diff --git a/components/libgbc/src/mbc.cpp b/components/libgbc/src/mbc.cpp new file mode 100644 index 00000000..a2db73c8 --- /dev/null +++ b/components/libgbc/src/mbc.cpp @@ -0,0 +1,279 @@ +#include "mbc.hpp" + +#include "machine.hpp" +#include "memory.hpp" + +#include "mbc1m.hpp" +#include "mbc3.hpp" +#include "mbc5.hpp" + +namespace gbc +{ +MBC::MBC(Memory& m, const std::string_view rom) + : m_memory(m), m_rom(rom) {} + +void MBC::init() +{ + this->m_ram.at(0x100) = 0x1; + this->m_ram.at(0x101) = 0x3; + this->m_ram.at(0x102) = 0x5; + this->m_ram.at(0x103) = 0x7; + this->m_ram.at(0x104) = 0x9; + // test ROMs are just instruction arrays + if (m_rom.size() < 0x150) return; + // parse ROM header + switch (m_memory.read8(0x147)) + { + case 0x0: + case 0x1: // MBC 1 + case 0x2: + case 0x3: + this->m_state.version = 1; + break; + case 0x5: + case 0x6: + this->m_state.version = 2; + assert(0 && "MBC2 is a weirdo!"); + break; + case 0x0F: + case 0x10: // MBC 3 + case 0x12: + case 0x13: + this->m_state.version = 3; + break; + case 0x19: + case 0x1A: // MBC 5 + case 0x1B: + case 0x1C: + this->m_state.version = 5; + this->m_state.rumble = false; + break; + case 0x1D: + case 0x1E: + this->m_state.version = 5; + this->m_state.rumble = true; + break; + default: + assert(0 && "Unknown cartridge type"); + } + // printf("MBC version %u Rumble: %d\n", this->m_state.version, this->m_state.rumble); + switch (m_memory.read8(0x149)) + { + case 0x0: + m_state.ram_banks = 0; + m_state.ram_bank_size = 0; + break; + case 0x1: // 2kb + m_state.ram_banks = 1; + m_state.ram_bank_size = 2048; + break; + case 0x2: // 8kb + m_state.ram_banks = 1; + m_state.ram_bank_size = 8192; + break; + case 0x3: // 32kb + m_state.ram_banks = 4; + m_state.ram_bank_size = 32768; + break; + case 0x4: // 128kb + m_state.ram_banks = 16; + m_state.ram_bank_size = 0x20000; + break; + case 0x5: // 64kb + m_state.ram_banks = 8; + m_state.ram_bank_size = 0x10000; + break; + } + // printf("RAM bank size: 0x%05x\n", m_state.ram_bank_size); + this->m_state.wram_size = 0x8000; + // printf("Work RAM bank size: 0x%04x\n", m_state.wram_size); +} + +uint8_t MBC::read(uint16_t addr) +{ + switch (addr & 0xF000) + { + case 0xA000: + case 0xB000: + if (this->ram_enabled()) + { + if (this->m_state.rtc_enabled == false) + { + addr -= RAMbankX.first; + addr |= this->m_state.ram_bank_offset; + if (addr < this->m_state.ram_bank_size) return this->m_ram.at(addr); + return 0xff; // small 2kb RAM banks + } + else + { + return 0xff; // TODO: Read from RTC register + } + } + else + { + return 0xff; + } + case 0xC000: + return this->m_state.wram.at(addr - WRAM_0.first); + case 0xD000: + return m_state.wram.at(m_state.wram_offset + addr - WRAM_bX.first); + case 0xE000: // echo RAM + case 0xF000: + return this->read(addr - 0x2000); + } + printf("* Invalid MBC read: 0x%04x\n", addr); + return 0xff; +} + +void MBC::write(uint16_t addr, uint8_t value) +{ + switch (addr & 0xF000) + { + case 0x0000: + case 0x1000: + // RAM enable + if (m_state.version == 2) + this->m_state.ram_enabled = value != 0; + else + this->m_state.ram_enabled = ((value & 0xF) == 0xA); + if (UNLIKELY(verbose_banking())) { + printf("* External RAM enabled: %d\n", this->m_state.ram_enabled); + } + return; + case 0x2000: + case 0x3000: + case 0x4000: + case 0x5000: + case 0x6000: + case 0x7000: + // MBC control ranges + switch (this->m_state.version) + { + case 5: + this->write_MBC5(addr, value); + break; + case 3: + this->write_MBC3(addr, value); + break; + case 1: + this->write_MBC1M(addr, value); + break; + case 0: + break; // no MBC + default: + assert(0 && "Unimplemented MBC version"); + } + return; + case 0xA000: + case 0xB000: + if (this->ram_enabled()) + { + if (this->m_state.rtc_enabled == false) + { + addr -= RAMbankX.first; + addr |= this->m_state.ram_bank_offset; + if (addr < this->m_state.ram_bank_size) { this->m_ram.at(addr) = value; } + } + else + { + // TODO: Write to RTC register + } + } + return; + case 0xC000: // WRAM bank 0 + this->m_state.wram.at(addr - WRAM_0.first) = value; + return; + case 0xD000: // WRAM bank X + this->m_state.wram.at(m_state.wram_offset + addr - WRAM_bX.first) = value; + return; + case 0xE000: // Echo RAM + case 0xF000: + this->write(addr - 0x2000, value); + return; + } + printf("* Invalid MBC write: 0x%04x => 0x%02x\n", addr, value); + assert(0); +} + +void MBC::set_rombank(int reg) +{ + const int rom_banks = m_rom.size() / rombank_size(); + reg &= (rom_banks - 1); + + // cant select bank 0 + const int offset = reg * rombank_size(); + if (UNLIKELY(verbose_banking())) { + printf("Selecting ROM bank 0x%02x offset %#x max %#zx\n", reg, offset, m_rom.size()); + } + if (UNLIKELY((offset + rombank_size()) > m_rom.size())) + { + printf("Invalid ROM bank 0x%02x offset %#x max %#zx\n", reg, offset, m_rom.size()); + this->m_memory.machine().break_now(); + return; + } + this->m_state.rom_bank_offset = offset; +} +void MBC::set_rambank(int reg) +{ + if (this->m_state.ram_banks >= 0) + { + // NOTE: we have to remove bits here + reg &= (this->m_state.ram_banks - 1); + } + const int offset = reg * rambank_size(); + if (UNLIKELY(verbose_banking())) { + printf("Selecting RAM bank 0x%02x offset %#x max %#lx\n", reg, offset, + m_state.ram_bank_size); + } + this->m_state.ram_bank_offset = offset; +} +void MBC::set_wrambank(int reg) +{ + const int offset = reg * wrambank_size(); + if (UNLIKELY(verbose_banking())) { + printf("Selecting WRAM bank 0x%02x offset %#x max %#x\n", reg, offset, m_state.wram_size); + } + if (UNLIKELY((offset + wrambank_size()) > m_state.wram_size)) + { + printf("Invalid Work RAM bank 0x%02x offset %#x\n", reg, offset); + this->m_memory.machine().break_now(); + return; + } + this->m_state.wram_offset = offset; +} +void MBC::set_mode(int mode) +{ + if (UNLIKELY(verbose_banking())) { + printf("Mode select: 0x%02x\n", this->m_state.mode_select); + } + this->m_state.mode_select = mode & 0x1; + // for MBC we have to reset the upper bits when going into RAM mode + if (this->m_state.version == 1 && this->m_state.mode_select == 1) + { + // reset ROM bank upper bits when going into RAM mode + if (this->m_state.rom_bank_reg & 0x60) + { + this->m_state.rom_bank_reg &= 0x1F; + this->set_rombank(this->m_state.rom_bank_reg); + } + } +} + +bool MBC::verbose_banking() const noexcept { return m_memory.machine().verbose_banking; } + +// serialization +int MBC::restore_state(const std::vector& data, int off) +{ + // copy state first + this->m_state = *(state_t*) &data.at(off); + off += sizeof(state_t); + // then copy RAM by size + std::copy(&data.at(off), &data.at(off) + m_state.ram_bank_size, m_ram.begin()); + return sizeof(state_t) + m_state.ram_bank_size; +} +void MBC::serialize_state(std::vector& res) const +{ + res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); + res.insert(res.end(), m_ram.begin(), m_ram.begin() + m_state.ram_bank_size); +} +} // namespace gbc diff --git a/components/libgbc/src/memory.cpp b/components/libgbc/src/memory.cpp new file mode 100644 index 00000000..bc3965aa --- /dev/null +++ b/components/libgbc/src/memory.cpp @@ -0,0 +1,225 @@ +#include "memory.hpp" +#include "machine.hpp" + +namespace gbc +{ +Memory::Memory(Machine& mach, const std::string_view rom) + : m_machine(mach), m_rom(rom), m_mbc{*this, rom} +{ + assert(this->rom_valid()); + this->disable_bootrom(); + m_mbc.init(); +} +void Memory::reset() +{ + // this->disable_bootrom(); + // m_mbc.reset(); +} +bool Memory::rom_valid() const noexcept +{ + // TODO: implement me + return true; +} +void Memory::disable_bootrom() { m_state.bootrom_enabled = false; } + +void Memory::set_wram_bank(uint8_t bank) { this->m_mbc.set_wrambank(bank); } + +uint8_t Memory::read8(uint16_t address) +{ + if (UNLIKELY(!m_read_breakpoints.empty() && !m_is_busy)) + { + this->m_is_busy = true; + for (auto& func : m_read_breakpoints) { func(*this, address, 0x0); } + this->m_is_busy = false; + } + switch (address & 0xF000) + { + case 0x0000: + case 0x1000: + case 0x2000: + case 0x3000: + return m_rom[address]; + case 0x4000: + case 0x5000: + case 0x6000: + case 0x7000: + address -= 0x4000; + return m_rom[m_mbc.rombank_offset() | address]; + case 0x8000: + case 0x9000: + // cant read from Video RAM when working on scanline + if (UNLIKELY(machine().gpu.get_mode() != 3)) + { + const uint16_t offset = machine().gpu.video_offset(); + return m_state.video_ram.at(offset + address - VideoRAM.first); + } + return 0xff; + case 0xA000: + case 0xB000: + return m_mbc.read(address); + case 0xC000: + case 0xD000: + return m_mbc.read(address); + case 0xE000: // echo RAM + return m_mbc.read(address); + case 0xF000: + if (this->is_within(address, EchoRAM)) { return m_mbc.read(address); } + else if (this->is_within(address, OAM_RAM)) + { + // TODO: return 0xff when rendering? + if (!machine().io.dma_active()) return m_state.oam_ram.at(address - OAM_RAM.first); + return 0xff; + } + else if (this->is_within(address, IO_Ports)) + { + return machine().io.read_io(address); + } + else if (this->is_within(address, ZRAM)) + { + return m_state.zram.at(address - ZRAM.first); + } + else if (address == InterruptEn) + { + return machine().io.read_io(address); + } + } + printf(">>> Invalid memory read at 0x%04x\n", address); + return 0xff; +} + +void Memory::write8(uint16_t address, uint8_t value) +{ + if (UNLIKELY(!m_write_breakpoints.empty() && !m_is_busy)) + { + this->m_is_busy = true; + for (auto& func : m_write_breakpoints) { func(*this, address, value); } + this->m_is_busy = false; + } + switch (address & 0xF000) + { + case 0x0000: + case 0x1000: + case 0x2000: + case 0x3000: + case 0x4000: + case 0x5000: + case 0x6000: + case 0x7000: + m_mbc.write(address, value); + return; + case 0x8000: + case 0x9000: + if (machine().gpu.get_mode() != 3) + { + const uint16_t offset = machine().gpu.video_offset(); + m_state.video_ram.at(offset + address - VideoRAM.first) = value; + } + return; + case 0xA000: + case 0xB000: + m_mbc.write(address, value); + return; + case 0xC000: + case 0xD000: + m_mbc.write(address, value); + return; + case 0xE000: // echo RAM + m_mbc.write(address, value); + return; + case 0xF000: + if (this->is_within(address, EchoRAM)) + { + m_mbc.write(address, value); + return; + } + else if (this->is_within(address, OAM_RAM)) + { + this->m_state.oam_ram.at(address - OAM_RAM.first) = value; + return; + } + else if (this->is_within(address, IO_Ports)) + { + machine().io.write_io(address, value); + return; + } + else if (this->is_within(address, ZRAM)) + { + this->m_state.zram.at(address - ZRAM.first) = value; + return; + } + else if (address == InterruptEn) + { + machine().io.write_io(address, value); + return; + } + } + printf(">>> Invalid memory write at 0x%04x, value 0x%x\n", address, value); +} + +void Memory::do_switch_speed() +{ + auto& reg = machine().io.reg(IO::REG_KEY1); + if (this->double_speed()) + { + this->m_state.speed_factor = 1; + reg = 0x0; + } + else + { + this->m_state.speed_factor = 2; + reg = 0x80; + } +} + +std::string Memory::explain(const uint16_t addr) const +{ + switch (addr & 0xF000) + { + case 0x0000: + case 0x1000: + case 0x2000: + case 0x3000: + return "Program 0"; + case 0x4000: + case 0x5000: + case 0x6000: + case 0x7000: + return "Program " + std::to_string(m_mbc.rombank_offset() / 0x4000); + case 0x8000: + case 0x9000: + { + const uint16_t offset = machine().gpu.video_offset(); + return "VideoRAM " + std::to_string(offset / 0x2000); + } + case 0xA000: + case 0xB000: + return "ExtRAM"; + case 0xC000: + case 0xD000: + return "WorkRAM"; + case 0xE000: + return "EchoRAM"; + case 0xF000: + if (this->is_within(addr, EchoRAM)) return "EchoRAM"; + if (this->is_within(addr, OAM_RAM)) return "OAM RAM"; + if (this->is_within(addr, ZRAM)) return "ZRAM"; + return "I/O port"; + } + return "Unknown"; +} + +// serialization +int Memory::restore_state(const std::vector& data, int off) +{ + this->m_state = *(state_t*) &data.at(off); + off += sizeof(state_t); + // also restore MBC + return sizeof(state_t) + this->m_mbc.restore_state(data, off); +} +void Memory::serialize_state(std::vector& res) const +{ + res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); + // also serialize MBC + this->m_mbc.serialize_state(res); +} +} // namespace gbc diff --git a/main/gameboy.hpp b/main/gameboy.hpp index 44b097be..217d53ea 100644 --- a/main/gameboy.hpp +++ b/main/gameboy.hpp @@ -4,22 +4,32 @@ #include "format.hpp" #include "spi_lcd.h" #include "i2s_audio.h" +#include "input.h" #include "st7789.hpp" -#ifdef USE_GAMEBOY_GAMEBOYCORE -#include "gameboycore/gameboycore.h" +static const size_t gameboy_screen_width = 160; -static std::shared_ptr core; -#endif -#ifdef USE_GAMEBOY_GNUBOY -extern "C" { -#include "gnuboy.h" -#include "loader.h" +void vblank_callback() { + // fmt::print("VBLANK\n"); } -#endif -static const size_t gameboy_screen_width = 160; +static const int audio_frame_size = 2*256; +static int16_t audio_frame[audio_frame_size]; +static int audio_frame_index = 0; +void play_audio_sample(int16_t l, int16_t r) { + // fmt::print("L: {} R: {}\n", l, r); + audio_frame[audio_frame_index++] = l; + audio_frame[audio_frame_index++] = r; + if (audio_frame_index == audio_frame_size) { + // audio_play_frame((uint8_t*)audio_frame, audio_frame_size); + audio_frame_index = 0; + } +} +#ifdef USE_GAMEBOY_GAMEBOYCORE +#include "gameboycore/gameboycore.h" + +static std::shared_ptr core; void render_scanline(const gb::GPU::Scanline& scanline, int line) { // fmt::print("Line {}\n", line); // scanline is just std::array, where pixel is uint8_t r,g,b @@ -37,14 +47,28 @@ void render_scanline(const gb::GPU::Scanline& scanline, int line) { num_lines = 0; } } - -void vblank_callback() { - // fmt::print("VBLANK\n"); +#endif +#ifdef USE_GAMEBOY_GNUBOY +extern "C" { +#include "gnuboy.h" +#include "loader.h" } - -void play_audio_sample(int16_t l, int16_t r) { - // fmt::print("L: {} R: {}\n", l, r); +#endif +#ifdef USE_GAMEBOY_GAMEBOY +extern "C" { +namespace gbc{ +#include "gameboy/timer.h" +#include "gameboy/rom.h" +#include "gameboy/mem.h" +#include "gameboy/cpu.h" +#include "gameboy/lcd.h" + } } +#endif +#ifdef USE_GAMEBOY_LIBGBC +#include "machine.hpp" +std::shared_ptr gbc_machine; +#endif void init_gameboy(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size) { // WIDTH = gameboy_screen_width, so 320-WIDTH is gameboy_screen_width @@ -68,23 +92,79 @@ void init_gameboy(const std::string& rom_filename, uint8_t *romdata, size_t rom_ loader_init_raw(romdata, rom_data_size); emu_reset(); #endif +#ifdef USE_GAMEBOY_GAMEBOY + // gbc::rom_load(rom_filename.c_str()); + gbc::rom_init(romdata, rom_data_size); + gbc::gb_lcd_init(); + gbc::mem_init(); + gbc::cpu_init(); +#endif +#ifdef USE_GAMEBOY_LIBGBC + gbc_machine = std::make_shared(romdata, rom_data_size); + + // trap on V-blank interrupts + gbc_machine->set_handler(gbc::Machine::VBLANK, + [] (gbc::Machine& machine, gbc::interrupt_t&) + { + const auto& pixels = machine.gpu.pixels(); + const int num_lines = 24; + const int frame_offset = num_lines * gbc::GPU::SCREEN_W; + for (int l=0; l(end - start).count(); + fmt::print("gameboy: FPS {}\n", (float) frame / elapsed); + } + #ifdef USE_GAMEBOY_GAMEBOYCORE - // static size_t frame = 0; - // fmt::print("gameboycore: emulating frame {}\n", frame++); core->emulateFrame(); #endif #ifdef USE_GAMEBOY_GNUBOY emu_run(); #endif +#ifdef USE_GAMEBOY_GAMEBOY + static int r = 0; + int now; + if(!gbc::cpu_cycle()) + return; + now = gbc::cpu_get_cycles(); + while(now != r) { + for(int i = 0; i < 4; i++) { + if(!gbc::lcd_cycle()) + return; + } + r++; + } + gbc::timer_cycle(); + r = now; +#endif +#ifdef USE_GAMEBOY_LIBGBC + gbc_machine->simulate_one_frame(); +#endif } void deinit_gameboy() { + fmt::print("quitting gameboy emulation!\n"); #ifdef USE_GAMEBOY_GAMEBOYCORE core.reset(); #endif #ifdef USE_GAMEBOY_GNUBOY #endif +#ifdef USE_GAMEBOY_LIBGBC + gbc_machine.reset(); +#endif } From 389609975a56371bc56cd05b7602612858396b96 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Mon, 7 Nov 2022 08:58:30 -0600 Subject: [PATCH 6/9] cleaned up gnuboy folder. --- components/gnuboy/CMakeLists.txt | 4 ++-- components/gnuboy/{ => include}/cpu.h | 0 components/gnuboy/{ => include}/cpucore.h | 0 components/gnuboy/{ => include}/cpuregs.h | 0 components/gnuboy/{ => include}/defs.h | 0 components/gnuboy/{ => include}/fastmem.h | 0 components/gnuboy/{ => include}/fb.h | 0 components/gnuboy/{ => include}/gnuboy.h | 0 components/gnuboy/{ => include}/hw.h | 0 components/gnuboy/{ => include}/input.h | 0 components/gnuboy/{ => include}/lcd.h | 2 +- components/gnuboy/{ => include}/loader.h | 0 components/gnuboy/{ => include}/mem.h | 0 components/gnuboy/{ => include}/noise.h | 0 components/gnuboy/{ => include}/pcm.h | 0 components/gnuboy/{ => include}/rc.h | 0 components/gnuboy/{ => include}/regs.h | 0 components/gnuboy/{ => include}/rtc.h | 0 components/gnuboy/{ => include}/sound.h | 0 components/gnuboy/{ => src}/cpu.c | 0 components/gnuboy/{ => src}/debug.c | 0 components/gnuboy/{ => src}/emu.c | 11 ++++----- components/gnuboy/{ => src}/events.c | 0 components/gnuboy/{ => src}/exports.c | 0 components/gnuboy/{ => src}/fastmem.c | 0 components/gnuboy/{ => src}/hw.c | 0 components/gnuboy/{ => src}/inflate.c | 0 components/gnuboy/{ => src}/keytable.c | 0 components/gnuboy/{ => src}/lcd.c | 27 ++++++++++++++++------- components/gnuboy/{ => src}/lcdc.c | 4 ++-- components/gnuboy/{ => src}/loader.c | 0 components/gnuboy/{ => src}/mem.c | 0 components/gnuboy/{ => src}/newsound.c | 0 components/gnuboy/{ => src}/palette.c | 0 components/gnuboy/{ => src}/path.c | 0 components/gnuboy/{ => src}/rccmds.c | 0 components/gnuboy/{ => src}/rcfile.c | 0 components/gnuboy/{ => src}/rckeys.c | 0 components/gnuboy/{ => src}/rcvars.c | 0 components/gnuboy/{ => src}/refresh.c | 0 components/gnuboy/{ => src}/rtc.c | 0 components/gnuboy/{ => src}/save.c | 0 components/gnuboy/{ => src}/sound.c | 0 components/gnuboy/{ => src}/split.c | 0 44 files changed, 30 insertions(+), 18 deletions(-) rename components/gnuboy/{ => include}/cpu.h (100%) rename components/gnuboy/{ => include}/cpucore.h (100%) rename components/gnuboy/{ => include}/cpuregs.h (100%) rename components/gnuboy/{ => include}/defs.h (100%) rename components/gnuboy/{ => include}/fastmem.h (100%) rename components/gnuboy/{ => include}/fb.h (100%) rename components/gnuboy/{ => include}/gnuboy.h (100%) rename components/gnuboy/{ => include}/hw.h (100%) rename components/gnuboy/{ => include}/input.h (100%) rename components/gnuboy/{ => include}/lcd.h (97%) rename components/gnuboy/{ => include}/loader.h (100%) rename components/gnuboy/{ => include}/mem.h (100%) rename components/gnuboy/{ => include}/noise.h (100%) rename components/gnuboy/{ => include}/pcm.h (100%) rename components/gnuboy/{ => include}/rc.h (100%) rename components/gnuboy/{ => include}/regs.h (100%) rename components/gnuboy/{ => include}/rtc.h (100%) rename components/gnuboy/{ => include}/sound.h (100%) rename components/gnuboy/{ => src}/cpu.c (100%) rename components/gnuboy/{ => src}/debug.c (100%) rename components/gnuboy/{ => src}/emu.c (94%) rename components/gnuboy/{ => src}/events.c (100%) rename components/gnuboy/{ => src}/exports.c (100%) rename components/gnuboy/{ => src}/fastmem.c (100%) rename components/gnuboy/{ => src}/hw.c (100%) rename components/gnuboy/{ => src}/inflate.c (100%) rename components/gnuboy/{ => src}/keytable.c (100%) rename components/gnuboy/{ => src}/lcd.c (95%) rename components/gnuboy/{ => src}/lcdc.c (99%) rename components/gnuboy/{ => src}/loader.c (100%) rename components/gnuboy/{ => src}/mem.c (100%) rename components/gnuboy/{ => src}/newsound.c (100%) rename components/gnuboy/{ => src}/palette.c (100%) rename components/gnuboy/{ => src}/path.c (100%) rename components/gnuboy/{ => src}/rccmds.c (100%) rename components/gnuboy/{ => src}/rcfile.c (100%) rename components/gnuboy/{ => src}/rckeys.c (100%) rename components/gnuboy/{ => src}/rcvars.c (100%) rename components/gnuboy/{ => src}/refresh.c (100%) rename components/gnuboy/{ => src}/rtc.c (100%) rename components/gnuboy/{ => src}/save.c (100%) rename components/gnuboy/{ => src}/sound.c (100%) rename components/gnuboy/{ => src}/split.c (100%) diff --git a/components/gnuboy/CMakeLists.txt b/components/gnuboy/CMakeLists.txt index 4a76a564..eeca8068 100644 --- a/components/gnuboy/CMakeLists.txt +++ b/components/gnuboy/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( - INCLUDE_DIRS "." - SRC_DIRS "." + INCLUDE_DIRS "include" + SRC_DIRS "src" REQUIRES box-emu-hal ) diff --git a/components/gnuboy/cpu.h b/components/gnuboy/include/cpu.h similarity index 100% rename from components/gnuboy/cpu.h rename to components/gnuboy/include/cpu.h diff --git a/components/gnuboy/cpucore.h b/components/gnuboy/include/cpucore.h similarity index 100% rename from components/gnuboy/cpucore.h rename to components/gnuboy/include/cpucore.h diff --git a/components/gnuboy/cpuregs.h b/components/gnuboy/include/cpuregs.h similarity index 100% rename from components/gnuboy/cpuregs.h rename to components/gnuboy/include/cpuregs.h diff --git a/components/gnuboy/defs.h b/components/gnuboy/include/defs.h similarity index 100% rename from components/gnuboy/defs.h rename to components/gnuboy/include/defs.h diff --git a/components/gnuboy/fastmem.h b/components/gnuboy/include/fastmem.h similarity index 100% rename from components/gnuboy/fastmem.h rename to components/gnuboy/include/fastmem.h diff --git a/components/gnuboy/fb.h b/components/gnuboy/include/fb.h similarity index 100% rename from components/gnuboy/fb.h rename to components/gnuboy/include/fb.h diff --git a/components/gnuboy/gnuboy.h b/components/gnuboy/include/gnuboy.h similarity index 100% rename from components/gnuboy/gnuboy.h rename to components/gnuboy/include/gnuboy.h diff --git a/components/gnuboy/hw.h b/components/gnuboy/include/hw.h similarity index 100% rename from components/gnuboy/hw.h rename to components/gnuboy/include/hw.h diff --git a/components/gnuboy/input.h b/components/gnuboy/include/input.h similarity index 100% rename from components/gnuboy/input.h rename to components/gnuboy/include/input.h diff --git a/components/gnuboy/lcd.h b/components/gnuboy/include/lcd.h similarity index 97% rename from components/gnuboy/lcd.h rename to components/gnuboy/include/lcd.h index decc4e38..a1728492 100644 --- a/components/gnuboy/lcd.h +++ b/components/gnuboy/include/lcd.h @@ -47,7 +47,7 @@ extern struct lcd lcd; extern struct scan scan; -void lcd_begin(); +void gb_lcd_begin(); void lcd_refreshline(); void pal_write(int i, byte b); void pal_write_dmg(int i, int mapnum, byte d); diff --git a/components/gnuboy/loader.h b/components/gnuboy/include/loader.h similarity index 100% rename from components/gnuboy/loader.h rename to components/gnuboy/include/loader.h diff --git a/components/gnuboy/mem.h b/components/gnuboy/include/mem.h similarity index 100% rename from components/gnuboy/mem.h rename to components/gnuboy/include/mem.h diff --git a/components/gnuboy/noise.h b/components/gnuboy/include/noise.h similarity index 100% rename from components/gnuboy/noise.h rename to components/gnuboy/include/noise.h diff --git a/components/gnuboy/pcm.h b/components/gnuboy/include/pcm.h similarity index 100% rename from components/gnuboy/pcm.h rename to components/gnuboy/include/pcm.h diff --git a/components/gnuboy/rc.h b/components/gnuboy/include/rc.h similarity index 100% rename from components/gnuboy/rc.h rename to components/gnuboy/include/rc.h diff --git a/components/gnuboy/regs.h b/components/gnuboy/include/regs.h similarity index 100% rename from components/gnuboy/regs.h rename to components/gnuboy/include/regs.h diff --git a/components/gnuboy/rtc.h b/components/gnuboy/include/rtc.h similarity index 100% rename from components/gnuboy/rtc.h rename to components/gnuboy/include/rtc.h diff --git a/components/gnuboy/sound.h b/components/gnuboy/include/sound.h similarity index 100% rename from components/gnuboy/sound.h rename to components/gnuboy/include/sound.h diff --git a/components/gnuboy/cpu.c b/components/gnuboy/src/cpu.c similarity index 100% rename from components/gnuboy/cpu.c rename to components/gnuboy/src/cpu.c diff --git a/components/gnuboy/debug.c b/components/gnuboy/src/debug.c similarity index 100% rename from components/gnuboy/debug.c rename to components/gnuboy/src/debug.c diff --git a/components/gnuboy/emu.c b/components/gnuboy/src/emu.c similarity index 94% rename from components/gnuboy/emu.c rename to components/gnuboy/src/emu.c index 200eca2e..a05705e6 100644 --- a/components/gnuboy/emu.c +++ b/components/gnuboy/src/emu.c @@ -75,8 +75,7 @@ void emu_run() // FIXME: what does vid do? // vid_begin(); - lcd_begin(); - for (;;) + // for (;;) { /* FRAME BEGIN */ @@ -85,6 +84,8 @@ void emu_run() end of the loop. */ cpu_emulate(2280); + gb_lcd_begin(); + /* FIXME: R_LY >= 0; comparsion to zero can also be removed altogether, R_LY is always 0 at this point */ while (R_LY > 0 && R_LY < 144) @@ -92,9 +93,9 @@ void emu_run() /* Step through visible line scanning phase */ emu_step(); } - static size_t frame_num=0; - printf("frame: %d\n", frame_num++); - lcd_write_frame(0, 0, 160, 144, fb.ptr); + // static size_t frame_num=0; + // printf("frame: %d\n", frame_num++); + // lcd_write_frame(0, 0, 160, 144, fb.ptr); /* VBLANK BEGIN */ // FIXME: what does this do? diff --git a/components/gnuboy/events.c b/components/gnuboy/src/events.c similarity index 100% rename from components/gnuboy/events.c rename to components/gnuboy/src/events.c diff --git a/components/gnuboy/exports.c b/components/gnuboy/src/exports.c similarity index 100% rename from components/gnuboy/exports.c rename to components/gnuboy/src/exports.c diff --git a/components/gnuboy/fastmem.c b/components/gnuboy/src/fastmem.c similarity index 100% rename from components/gnuboy/fastmem.c rename to components/gnuboy/src/fastmem.c diff --git a/components/gnuboy/hw.c b/components/gnuboy/src/hw.c similarity index 100% rename from components/gnuboy/hw.c rename to components/gnuboy/src/hw.c diff --git a/components/gnuboy/inflate.c b/components/gnuboy/src/inflate.c similarity index 100% rename from components/gnuboy/inflate.c rename to components/gnuboy/src/inflate.c diff --git a/components/gnuboy/keytable.c b/components/gnuboy/src/keytable.c similarity index 100% rename from components/gnuboy/keytable.c rename to components/gnuboy/src/keytable.c diff --git a/components/gnuboy/lcd.c b/components/gnuboy/src/lcd.c similarity index 95% rename from components/gnuboy/lcd.c rename to components/gnuboy/src/lcd.c index 7f1b186f..9ff67acf 100755 --- a/components/gnuboy/lcd.c +++ b/components/gnuboy/src/lcd.c @@ -2,6 +2,8 @@ #include +#include "spi_lcd.h" + #include "gnuboy.h" #include "defs.h" #include "regs.h" @@ -50,7 +52,7 @@ static int sprsort = 1; static int sprdebug = 0; // BGR -#if 0 +#if 1 // Testing/Debug palette static int dmg_pal[4][4] = {{0xffffff, 0x808080, 0x404040, 0x000000}, {0xff0000, 0x800000, 0x400000, 0x000000}, @@ -634,16 +636,19 @@ static void IRAM_ATTR spr_scan() if (sprdebug) for (i = 0; i < NS; i++) BUF[i<<1] = 36; } - -inline void lcd_begin() +static int current_line = 0; +static int num_lines_written = 0; +inline void gb_lcd_begin() { vdest = fb.ptr; WY = R_WY; + current_line = 0; + num_lines_written = 0; } extern int frame; -extern uint16_t* displayBuffer[2]; +// extern uint16_t* displayBuffer[2]; int lastLcdDisabled = 0; void IRAM_ATTR lcd_refreshline() @@ -673,8 +678,8 @@ void IRAM_ATTR lcd_refreshline() { if (!lastLcdDisabled) { - memset(displayBuffer[0], 0xff, 144 * 160 * 2); - memset(displayBuffer[1], 0xff, 144 * 160 * 2); + // memset(displayBuffer[0], 0xff, 144 * 160 * 2); + // memset(displayBuffer[1], 0xff, 144 * 160 * 2); lastLcdDisabled = 1; } @@ -715,7 +720,13 @@ void IRAM_ATTR lcd_refreshline() while (cnt--) *(dst++) = PAL2[*(src++)]; } + current_line++; vdest += fb.pitch; + if ((current_line % 48) == 0) { + // printf("writing line: %d\n", current_line); + lcd_write_frame(0, current_line-48, 160, 48, fb.ptr); + vdest = fb.ptr; + } } inline static void updatepalette(int i) @@ -737,7 +748,7 @@ inline static void updatepalette(int i) // bit 10-14 blue b = (c >> 10) & 0x1f; - PAL2[i] = (r << 11) | (g << (5 + 1)) | (b); + PAL2[i] = make_color(r,g,b); // (r << 11) | (g << (5 + 1)) | (b); } inline void pal_write(int i, byte b) @@ -810,7 +821,7 @@ void lcd_reset() { memset(&lcd, 0, sizeof lcd); - lcd_begin(); + gb_lcd_begin(); vram_dirty(); pal_dirty(); } diff --git a/components/gnuboy/lcdc.c b/components/gnuboy/src/lcdc.c similarity index 99% rename from components/gnuboy/lcdc.c rename to components/gnuboy/src/lcdc.c index 5c4121f0..59b97193 100644 --- a/components/gnuboy/lcdc.c +++ b/components/gnuboy/src/lcdc.c @@ -81,7 +81,7 @@ void IRAM_ATTR lcdc_change(byte b) R_LY = 0; stat_change(2); C = 40; - lcd_begin(); + gb_lcd_begin(); } } @@ -192,7 +192,7 @@ void IRAM_ATTR lcdc_trans() } if (R_LY == 0) { - lcd_begin(); + gb_lcd_begin(); stat_change(2); /* -> search */ C += 40; break; diff --git a/components/gnuboy/loader.c b/components/gnuboy/src/loader.c similarity index 100% rename from components/gnuboy/loader.c rename to components/gnuboy/src/loader.c diff --git a/components/gnuboy/mem.c b/components/gnuboy/src/mem.c similarity index 100% rename from components/gnuboy/mem.c rename to components/gnuboy/src/mem.c diff --git a/components/gnuboy/newsound.c b/components/gnuboy/src/newsound.c similarity index 100% rename from components/gnuboy/newsound.c rename to components/gnuboy/src/newsound.c diff --git a/components/gnuboy/palette.c b/components/gnuboy/src/palette.c similarity index 100% rename from components/gnuboy/palette.c rename to components/gnuboy/src/palette.c diff --git a/components/gnuboy/path.c b/components/gnuboy/src/path.c similarity index 100% rename from components/gnuboy/path.c rename to components/gnuboy/src/path.c diff --git a/components/gnuboy/rccmds.c b/components/gnuboy/src/rccmds.c similarity index 100% rename from components/gnuboy/rccmds.c rename to components/gnuboy/src/rccmds.c diff --git a/components/gnuboy/rcfile.c b/components/gnuboy/src/rcfile.c similarity index 100% rename from components/gnuboy/rcfile.c rename to components/gnuboy/src/rcfile.c diff --git a/components/gnuboy/rckeys.c b/components/gnuboy/src/rckeys.c similarity index 100% rename from components/gnuboy/rckeys.c rename to components/gnuboy/src/rckeys.c diff --git a/components/gnuboy/rcvars.c b/components/gnuboy/src/rcvars.c similarity index 100% rename from components/gnuboy/rcvars.c rename to components/gnuboy/src/rcvars.c diff --git a/components/gnuboy/refresh.c b/components/gnuboy/src/refresh.c similarity index 100% rename from components/gnuboy/refresh.c rename to components/gnuboy/src/refresh.c diff --git a/components/gnuboy/rtc.c b/components/gnuboy/src/rtc.c similarity index 100% rename from components/gnuboy/rtc.c rename to components/gnuboy/src/rtc.c diff --git a/components/gnuboy/save.c b/components/gnuboy/src/save.c similarity index 100% rename from components/gnuboy/save.c rename to components/gnuboy/src/save.c diff --git a/components/gnuboy/sound.c b/components/gnuboy/src/sound.c similarity index 100% rename from components/gnuboy/sound.c rename to components/gnuboy/src/sound.c diff --git a/components/gnuboy/split.c b/components/gnuboy/src/split.c similarity index 100% rename from components/gnuboy/split.c rename to components/gnuboy/src/split.c From f094e1a6d90834a7e774c44fafc7e9b992a531c9 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Mon, 7 Nov 2022 10:46:44 -0600 Subject: [PATCH 7/9] updated gnuboy implementation to have working video + audio and to be the only implementation we support currently. --- components/box-emu-hal/src/spi_lcd.cpp | 55 - components/gameboycore/CMakeLists.txt | 12 - .../gameboycore/include/gameboycore/alu.h | 82 - .../gameboycore/include/gameboycore/apu.h | 83 - .../include/gameboycore/cartinfo.h | 38 - .../gameboycore/include/gameboycore/channel.h | 118 - .../gameboycore/include/gameboycore/cpu.h | 128 - .../gameboycore/detail/audio/noise_channel.h | 206 -- .../detail/audio/square_wave_channel.h | 319 --- .../gameboycore/detail/audio/wave_channel.h | 194 -- .../include/gameboycore/detail/hash.h | 39 - .../include/gameboycore/detail/rtc/rtc.h | 109 - .../include/gameboycore/gameboycore.h | 174 -- .../include/gameboycore/gameboycore_api.h | 25 - .../gameboycore/include/gameboycore/gpu.h | 99 - .../include/gameboycore/instruction.h | 46 - .../include/gameboycore/interrupt_provider.h | 53 - .../gameboycore/include/gameboycore/joypad.h | 63 - .../gameboycore/include/gameboycore/link.h | 66 - .../include/gameboycore/link_cable.h | 45 - .../gameboycore/include/gameboycore/mbc.h | 177 -- .../gameboycore/include/gameboycore/mbc1.h | 51 - .../gameboycore/include/gameboycore/mbc2.h | 40 - .../gameboycore/include/gameboycore/mbc3.h | 44 - .../gameboycore/include/gameboycore/mbc5.h | 28 - .../include/gameboycore/memorymap.h | 195 -- .../gameboycore/include/gameboycore/mmu.h | 131 - .../gameboycore/include/gameboycore/oam.h | 29 - .../include/gameboycore/opcode_cycles.h | 70 - .../include/gameboycore/opcodeinfo.h | 48 - .../gameboycore/include/gameboycore/palette.h | 59 - .../gameboycore/include/gameboycore/pixel.h | 42 - .../gameboycore/include/gameboycore/sprite.h | 77 - .../gameboycore/include/gameboycore/tile.h | 14 - .../gameboycore/include/gameboycore/tilemap.h | 70 - .../gameboycore/include/gameboycore/tileram.h | 63 - .../gameboycore/include/gameboycore/time.h | 32 - .../gameboycore/include/gameboycore/timer.h | 46 - components/gameboycore/src/alu.cpp | 150 - components/gameboycore/src/apu.cpp | 507 ---- components/gameboycore/src/bitutil.h | 158 -- components/gameboycore/src/cartinfo.cpp | 30 - components/gameboycore/src/cpu.cpp | 2476 ----------------- components/gameboycore/src/gameboycore.cpp | 326 --- components/gameboycore/src/gpu.cpp | 490 ---- components/gameboycore/src/instruction.cpp | 35 - components/gameboycore/src/joypad.cpp | 88 - components/gameboycore/src/link.cpp | 204 -- components/gameboycore/src/link_cable.cpp | 144 - components/gameboycore/src/mbc.cpp | 236 -- components/gameboycore/src/mbc1.cpp | 85 - components/gameboycore/src/mbc2.cpp | 45 - components/gameboycore/src/mbc3.cpp | 78 - components/gameboycore/src/mbc5.cpp | 51 - components/gameboycore/src/mmu.cpp | 334 --- components/gameboycore/src/oam.cpp | 57 - components/gameboycore/src/opcodeinfo.cpp | 585 ---- components/gameboycore/src/shiftrotate.cpp | 205 -- components/gameboycore/src/shiftrotate.h | 55 - components/gameboycore/src/tilemap.cpp | 284 -- components/gameboycore/src/tileram.cpp | 163 -- components/gameboycore/src/timer.cpp | 77 - components/gnuboy/include/{ => gnuboy}/cpu.h | 3 +- .../gnuboy/include/{ => gnuboy}/cpucore.h | 4 +- .../gnuboy/include/{ => gnuboy}/cpuregs.h | 4 +- components/gnuboy/include/{ => gnuboy}/defs.h | 0 .../gnuboy/include/{ => gnuboy}/fastmem.h | 4 +- components/gnuboy/include/{ => gnuboy}/fb.h | 4 +- .../gnuboy/include/{ => gnuboy}/gnuboy.h | 2 +- components/gnuboy/include/{ => gnuboy}/hw.h | 2 +- .../gnuboy/include/{ => gnuboy}/input.h | 0 components/gnuboy/include/{ => gnuboy}/lcd.h | 4 +- .../gnuboy/include/{ => gnuboy}/loader.h | 6 +- components/gnuboy/include/{ => gnuboy}/mem.h | 4 +- .../gnuboy/include/{ => gnuboy}/noise.h | 2 +- components/gnuboy/include/{ => gnuboy}/pcm.h | 2 +- components/gnuboy/include/{ => gnuboy}/rc.h | 0 components/gnuboy/include/{ => gnuboy}/regs.h | 2 +- components/gnuboy/include/{ => gnuboy}/rtc.h | 2 +- .../gnuboy/include/{ => gnuboy}/sound.h | 0 components/gnuboy/main.c | 318 --- components/gnuboy/src/cpu.c | 22 +- components/gnuboy/src/debug.c | 16 +- components/gnuboy/src/emu.c | 52 +- components/gnuboy/src/events.c | 2 +- components/gnuboy/src/exports.c | 4 +- components/gnuboy/src/fastmem.c | 2 +- components/gnuboy/src/hw.c | 16 +- components/gnuboy/src/inflate.c | 2 +- components/gnuboy/src/keytable.c | 4 +- components/gnuboy/src/lcd.c | 48 +- components/gnuboy/src/lcdc.c | 16 +- components/gnuboy/src/loader.c | 286 +- components/gnuboy/src/mem.c | 374 +-- components/gnuboy/src/palette.c | 6 +- components/gnuboy/src/path.c | 2 +- components/gnuboy/src/rccmds.c | 10 +- components/gnuboy/src/rcfile.c | 8 +- components/gnuboy/src/rckeys.c | 8 +- components/gnuboy/src/rcvars.c | 6 +- components/gnuboy/src/refresh.c | 8 +- components/gnuboy/src/rtc.c | 8 +- components/gnuboy/src/save.c | 20 +- components/gnuboy/src/sound.c | 20 +- components/libgbc/CMakeLists.txt | 9 - components/libgbc/include/apu.hpp | 46 - components/libgbc/include/common.hpp | 51 - components/libgbc/include/cpu.hpp | 114 - components/libgbc/include/generators.hpp | 21 - components/libgbc/include/gpu.hpp | 184 -- components/libgbc/include/instruction.hpp | 27 - components/libgbc/include/interrupt.hpp | 22 - components/libgbc/include/io.hpp | 170 -- components/libgbc/include/machine.hpp | 82 - components/libgbc/include/mbc.hpp | 76 - components/libgbc/include/mbc1m.hpp | 36 - components/libgbc/include/mbc3.hpp | 29 - components/libgbc/include/mbc5.hpp | 31 - components/libgbc/include/memory.hpp | 109 - components/libgbc/include/printers.hpp | 58 - components/libgbc/include/registers.hpp | 199 -- components/libgbc/include/sprite.hpp | 69 - components/libgbc/include/tiledata.hpp | 65 - components/libgbc/include/tracing.hpp | 14 - components/libgbc/src/apu.cpp | 54 - components/libgbc/src/cpu.cpp | 621 ----- components/libgbc/src/debug.cpp | 319 --- components/libgbc/src/gpu.cpp | 428 --- components/libgbc/src/instructions.cpp | 778 ------ components/libgbc/src/io.cpp | 234 -- components/libgbc/src/io_regs.cpp | 235 -- components/libgbc/src/machine.cpp | 84 - components/libgbc/src/mbc.cpp | 279 -- components/libgbc/src/memory.cpp | 225 -- main/gameboy.hpp | 249 +- 135 files changed, 495 insertions(+), 15759 deletions(-) delete mode 100644 components/gameboycore/CMakeLists.txt delete mode 100644 components/gameboycore/include/gameboycore/alu.h delete mode 100644 components/gameboycore/include/gameboycore/apu.h delete mode 100644 components/gameboycore/include/gameboycore/cartinfo.h delete mode 100644 components/gameboycore/include/gameboycore/channel.h delete mode 100644 components/gameboycore/include/gameboycore/cpu.h delete mode 100644 components/gameboycore/include/gameboycore/detail/audio/noise_channel.h delete mode 100644 components/gameboycore/include/gameboycore/detail/audio/square_wave_channel.h delete mode 100644 components/gameboycore/include/gameboycore/detail/audio/wave_channel.h delete mode 100644 components/gameboycore/include/gameboycore/detail/hash.h delete mode 100644 components/gameboycore/include/gameboycore/detail/rtc/rtc.h delete mode 100644 components/gameboycore/include/gameboycore/gameboycore.h delete mode 100644 components/gameboycore/include/gameboycore/gameboycore_api.h delete mode 100644 components/gameboycore/include/gameboycore/gpu.h delete mode 100644 components/gameboycore/include/gameboycore/instruction.h delete mode 100644 components/gameboycore/include/gameboycore/interrupt_provider.h delete mode 100644 components/gameboycore/include/gameboycore/joypad.h delete mode 100644 components/gameboycore/include/gameboycore/link.h delete mode 100644 components/gameboycore/include/gameboycore/link_cable.h delete mode 100644 components/gameboycore/include/gameboycore/mbc.h delete mode 100644 components/gameboycore/include/gameboycore/mbc1.h delete mode 100644 components/gameboycore/include/gameboycore/mbc2.h delete mode 100644 components/gameboycore/include/gameboycore/mbc3.h delete mode 100644 components/gameboycore/include/gameboycore/mbc5.h delete mode 100644 components/gameboycore/include/gameboycore/memorymap.h delete mode 100644 components/gameboycore/include/gameboycore/mmu.h delete mode 100644 components/gameboycore/include/gameboycore/oam.h delete mode 100644 components/gameboycore/include/gameboycore/opcode_cycles.h delete mode 100644 components/gameboycore/include/gameboycore/opcodeinfo.h delete mode 100644 components/gameboycore/include/gameboycore/palette.h delete mode 100644 components/gameboycore/include/gameboycore/pixel.h delete mode 100644 components/gameboycore/include/gameboycore/sprite.h delete mode 100644 components/gameboycore/include/gameboycore/tile.h delete mode 100644 components/gameboycore/include/gameboycore/tilemap.h delete mode 100644 components/gameboycore/include/gameboycore/tileram.h delete mode 100644 components/gameboycore/include/gameboycore/time.h delete mode 100644 components/gameboycore/include/gameboycore/timer.h delete mode 100644 components/gameboycore/src/alu.cpp delete mode 100644 components/gameboycore/src/apu.cpp delete mode 100644 components/gameboycore/src/bitutil.h delete mode 100644 components/gameboycore/src/cartinfo.cpp delete mode 100644 components/gameboycore/src/cpu.cpp delete mode 100644 components/gameboycore/src/gameboycore.cpp delete mode 100644 components/gameboycore/src/gpu.cpp delete mode 100644 components/gameboycore/src/instruction.cpp delete mode 100644 components/gameboycore/src/joypad.cpp delete mode 100644 components/gameboycore/src/link.cpp delete mode 100644 components/gameboycore/src/link_cable.cpp delete mode 100644 components/gameboycore/src/mbc.cpp delete mode 100644 components/gameboycore/src/mbc1.cpp delete mode 100644 components/gameboycore/src/mbc2.cpp delete mode 100644 components/gameboycore/src/mbc3.cpp delete mode 100644 components/gameboycore/src/mbc5.cpp delete mode 100644 components/gameboycore/src/mmu.cpp delete mode 100644 components/gameboycore/src/oam.cpp delete mode 100644 components/gameboycore/src/opcodeinfo.cpp delete mode 100644 components/gameboycore/src/shiftrotate.cpp delete mode 100644 components/gameboycore/src/shiftrotate.h delete mode 100644 components/gameboycore/src/tilemap.cpp delete mode 100644 components/gameboycore/src/tileram.cpp delete mode 100644 components/gameboycore/src/timer.cpp rename components/gnuboy/include/{ => gnuboy}/cpu.h (91%) rename components/gnuboy/include/{ => gnuboy}/cpucore.h (99%) rename components/gnuboy/include/{ => gnuboy}/cpuregs.h (94%) rename components/gnuboy/include/{ => gnuboy}/defs.h (100%) rename components/gnuboy/include/{ => gnuboy}/fastmem.h (95%) rename components/gnuboy/include/{ => gnuboy}/fb.h (89%) rename components/gnuboy/include/{ => gnuboy}/gnuboy.h (98%) rename components/gnuboy/include/{ => gnuboy}/hw.h (95%) rename components/gnuboy/include/{ => gnuboy}/input.h (100%) rename components/gnuboy/include/{ => gnuboy}/lcd.h (95%) rename components/gnuboy/include/{ => gnuboy}/loader.h (64%) rename components/gnuboy/include/{ => gnuboy}/mem.h (95%) rename components/gnuboy/include/{ => gnuboy}/noise.h (99%) rename components/gnuboy/include/{ => gnuboy}/pcm.h (86%) rename components/gnuboy/include/{ => gnuboy}/rc.h (100%) rename components/gnuboy/include/{ => gnuboy}/regs.h (99%) rename components/gnuboy/include/{ => gnuboy}/rtc.h (92%) rename components/gnuboy/include/{ => gnuboy}/sound.h (100%) delete mode 100644 components/gnuboy/main.c delete mode 100644 components/libgbc/CMakeLists.txt delete mode 100644 components/libgbc/include/apu.hpp delete mode 100644 components/libgbc/include/common.hpp delete mode 100644 components/libgbc/include/cpu.hpp delete mode 100644 components/libgbc/include/generators.hpp delete mode 100644 components/libgbc/include/gpu.hpp delete mode 100644 components/libgbc/include/instruction.hpp delete mode 100644 components/libgbc/include/interrupt.hpp delete mode 100644 components/libgbc/include/io.hpp delete mode 100644 components/libgbc/include/machine.hpp delete mode 100644 components/libgbc/include/mbc.hpp delete mode 100644 components/libgbc/include/mbc1m.hpp delete mode 100644 components/libgbc/include/mbc3.hpp delete mode 100644 components/libgbc/include/mbc5.hpp delete mode 100644 components/libgbc/include/memory.hpp delete mode 100644 components/libgbc/include/printers.hpp delete mode 100644 components/libgbc/include/registers.hpp delete mode 100644 components/libgbc/include/sprite.hpp delete mode 100644 components/libgbc/include/tiledata.hpp delete mode 100644 components/libgbc/include/tracing.hpp delete mode 100644 components/libgbc/src/apu.cpp delete mode 100644 components/libgbc/src/cpu.cpp delete mode 100644 components/libgbc/src/debug.cpp delete mode 100644 components/libgbc/src/gpu.cpp delete mode 100644 components/libgbc/src/instructions.cpp delete mode 100644 components/libgbc/src/io.cpp delete mode 100644 components/libgbc/src/io_regs.cpp delete mode 100644 components/libgbc/src/machine.cpp delete mode 100644 components/libgbc/src/mbc.cpp delete mode 100644 components/libgbc/src/memory.cpp diff --git a/components/box-emu-hal/src/spi_lcd.cpp b/components/box-emu-hal/src/spi_lcd.cpp index f6a1226c..18ef0e79 100644 --- a/components/box-emu-hal/src/spi_lcd.cpp +++ b/components/box-emu-hal/src/spi_lcd.cpp @@ -13,46 +13,6 @@ static constexpr size_t display_height = 240; static constexpr size_t pixel_buffer_size = display_width*NUM_ROWS_IN_FRAME_BUFFER; std::shared_ptr display; -#if USE_GAMEBOY_GNUBOY -// for gnuboy -uint16_t* displayBuffer[2]; -struct fb -{ - uint8_t *ptr; - int w, h; - int pelsize; - int pitch; - int indexed; - struct - { - int l, r; - } cc[4]; - int yuv; - int enabled; - int dirty; -}; -struct fb fb; -struct obj -{ - uint8_t y; - uint8_t x; - uint8_t pat; - uint8_t flags; -}; -struct lcd -{ - uint8_t vbank[2][8192]; - union - { - uint8_t mem[256]; - struct obj obj[40]; - } oam; - uint8_t pal[128]; -}; -static struct lcd lcd; -int frame = 0; -#endif - //This function is called (in irq context!) just before a transmission starts. It will //set the D/C line to the value indicated in the user field. void lcd_spi_pre_transfer_callback(spi_transaction_t *t) @@ -223,19 +183,4 @@ extern "C" void lcd_init() { .software_rotation_enabled = true, }); initialized = true; -#if USE_GAMEBOY_GNUBOY - // for gnuboy - displayBuffer[0] = display->vram0(); - displayBuffer[1] = display->vram1(); - memset(&fb, 0, sizeof(fb)); - // got these from https://github.com/OtherCrashOverride/go-play/blob/master/gnuboy-go/main/main.c - fb.w = 160; - fb.h = 144; - fb.pelsize = 2; - fb.pitch = fb.w * fb.pelsize; - fb.indexed = 0; - fb.ptr = (uint8_t*)displayBuffer[0]; - fb.enabled = 1; - fb.dirty = 0; -#endif } diff --git a/components/gameboycore/CMakeLists.txt b/components/gameboycore/CMakeLists.txt deleted file mode 100644 index 2f676a2a..00000000 --- a/components/gameboycore/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -set(ENDIAN "__LITTLEENDIAN__") - -idf_component_register( - INCLUDE_DIRS "include" - SRC_DIRS "src" - REQUIRES box-emu-hal - ) - -# target_compile_features(gameboycore PUBLIC cxx_std_11) -target_compile_options(${COMPONENT_LIB} PRIVATE -Wall -Wextra) -target_compile_definitions(${COMPONENT_LIB} PRIVATE GAMEBOYCORE_STATIC=1 ${ENDIAN}=1 _CRT_SECURE_NO_WARNINGS=1) -target_compile_definitions(${COMPONENT_LIB} PUBLIC USE_GAMEBOY_GAMEBOYCORE) diff --git a/components/gameboycore/include/gameboycore/alu.h b/components/gameboycore/include/gameboycore/alu.h deleted file mode 100644 index 10a24e19..00000000 --- a/components/gameboycore/include/gameboycore/alu.h +++ /dev/null @@ -1,82 +0,0 @@ -/** - \file alu.h - \author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_ALU_H -#define GAMEBOYCORE_ALU_H - -#include - -namespace gb -{ - /*! - \class ALU - \brief Arithmetic and logic unit - */ - class ALU - { - public: - enum Flags - { - Z = 1 << 7, - N = 1 << 6, - H = 1 << 5, - C = 1 << 4 - }; - - public: - ALU(uint8_t& flags); - ~ALU(); - - /** - ADD - */ - void add(uint8_t& a, uint8_t n); - void add(uint16_t& hl, uint16_t n); - void addr(uint16_t& sp, int8_t n); - - /** - ADC - */ - void addc(uint8_t& a, uint8_t n); - - /** - SUB - */ - void sub(uint8_t& a, uint8_t n); - - /** - SUBC - */ - void subc(uint8_t& a, uint8_t n); - - /** - AND - */ - void anda(uint8_t& a, uint8_t n); - - /** - OR - */ - void ora(uint8_t& a, uint8_t n); - - /** - XOR - */ - void xora(uint8_t& a, uint8_t n); - - /** - Compare - */ - void compare(uint8_t& a, uint8_t n); - - private: - void setFlag(uint8_t mask, bool set); - - private: - uint8_t& flags_; - }; - -} -#endif // GAMEBOYCORE_ALU_H diff --git a/components/gameboycore/include/gameboycore/apu.h b/components/gameboycore/include/gameboycore/apu.h deleted file mode 100644 index 123bffd1..00000000 --- a/components/gameboycore/include/gameboycore/apu.h +++ /dev/null @@ -1,83 +0,0 @@ -/** - \file apu.h - \brief Audio Emulation - \author Natesh Narain - \date Nov 3 2016 - \defgroup Audio -*/ - - -#ifndef GAMEBOYCORE_APU_H -#define GAMEBOYCORE_APU_H - -#include "gameboycore/gameboycore_api.h" -#include "gameboycore/mmu.h" - -#include -#include - -namespace gb -{ - /** - \class APU - \brief Emulate Gameboy sound functions - \ingroup API - \ingroup Audio - */ - class GAMEBOYCORE_API APU - { - public: - enum - { - CHANNEL_COUNT = 2, ///< Number of audio channels the APU provides (Stereo sound: left, right) - SAMPLE_RATE = 44100 ///< Audio sample rate - }; - public: - //! Smart pointer type - using Ptr = std::unique_ptr; - //! Callback used to provide audio to the host system - using AudioSampleCallback = std::function; - - public: - - APU(MMU::Ptr& mmu); - APU(const APU&) = delete; - ~APU(); - - /** - Update APU with the elasped cycles. For use by the CPU - */ - void update(uint8_t cycles); - - /** - Get Sound 1 output - */ - uint8_t getSound1Volume(); - - /** - Get Sound 2 output - */ - uint8_t getSound2Volume(); - - /** - Get Sound 3 output - */ - uint8_t getSound3Volume(); - - /** - Get Sound 4 output - */ - uint8_t getSound4Volume(); - - /** - Set the host callback - */ - void setAudioSampleCallback(AudioSampleCallback callback); - - private: - class Impl; - Impl* impl_; - }; -} - -#endif diff --git a/components/gameboycore/include/gameboycore/cartinfo.h b/components/gameboycore/include/gameboycore/cartinfo.h deleted file mode 100644 index f5ff2938..00000000 --- a/components/gameboycore/include/gameboycore/cartinfo.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - @file cartinfo.h - @author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_CARTINFO_H -#define GAMEBOYCORE_CARTINFO_H - -#include "gameboycore/memorymap.h" -#include - -namespace gb -{ - /** - Structure containing cartridge information, contained in header - */ - struct CartInfo - { - uint8_t type; - uint8_t rom_size; - uint8_t ram_size; - char game_title[(memorymap::GAME_TITLE_END - memorymap::GAME_TITLE_START) + 1]; - bool cgb_enabled; - }; - - /** - \brief Used to parse rom image for information contained in header - */ - class RomParser - { - public: - RomParser(); - - static CartInfo parse(const uint8_t* image); - }; -} - -#endif diff --git a/components/gameboycore/include/gameboycore/channel.h b/components/gameboycore/include/gameboycore/channel.h deleted file mode 100644 index cbb8a6ad..00000000 --- a/components/gameboycore/include/gameboycore/channel.h +++ /dev/null @@ -1,118 +0,0 @@ - -/** - \file channel.h - \brief Base class for Sound channel - \author Natesh Narain - \date Nov 5 2016 -*/ - -#ifndef GAMEBOYCORE_CHANNEL_H -#define GAMEBOYCORE_CHANNEL_H - -#include - -namespace gb -{ - namespace detail - { - /** - \class Channel - \brief Base class for Sound channel - \ingroup Audio - */ - class Channel - { - enum{ - //! Mask covers DAC bits - DAC_MASK = 0xF8 - }; - - public: - Channel(uint8_t& nrx1, uint8_t& nrx2, uint8_t& nrx3, uint8_t& nrx4) : - nrx1_(nrx1), - nrx2_(nrx2), - nrx3_(nrx3), - nrx4_(nrx4), - length_counter_(0), - enabled_(false) - { - } - - ~Channel() - { - } - - virtual void trigger() - { - // only enable channel if the DAC is on - if(isDacEnabled()) - enabled_ = true; - - if (length_counter_ == 0) - length_counter_ = 64; - } - - bool isEnabled() const - { - return enabled_; - } - - uint8_t getLength() const - { - return length_counter_; - } - - void setLength(int length) - { - length_counter_ = length; - } - - void setDacPower(uint8_t p) - { - if (p == 0) - enabled_ = false; - } - - virtual bool isDacEnabled() const - { - return (nrx2_ & DAC_MASK) != 0; - } - - void clockLength() - { - if (!isCounterMode()) return; - - if (length_counter_ > 0) - { - length_counter_--; - } - - if (length_counter_ == 0) - enabled_ = false; - } - - private: - bool isCounterMode() - { - return (nrx4_ & 0x40) != 0; - } - - protected: - //! NRx1 APU Register - uint8_t& nrx1_; - //! NRx2 APU Register - uint8_t& nrx2_; - //! NRx3 APU Register - uint8_t& nrx3_; - //! NRx4 APU Register - uint8_t& nrx4_; - - //! Length Counter - int length_counter_; - //! Channel Enabled flag - bool enabled_; - }; - } -} - -#endif diff --git a/components/gameboycore/include/gameboycore/cpu.h b/components/gameboycore/include/gameboycore/cpu.h deleted file mode 100644 index f29d7787..00000000 --- a/components/gameboycore/include/gameboycore/cpu.h +++ /dev/null @@ -1,128 +0,0 @@ -/** - \file cpu.h - \brief Gameboy CPU - \author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_CPU_H -#define GAMEBOYCORE_CPU_H - -#include "gameboycore/mmu.h" -#include "gameboycore/gpu.h" -#include "gameboycore/apu.h" -#include "gameboycore/link.h" -#include "gameboycore/instruction.h" - -#include -#include -#include -#include - -namespace gb -{ - /*! - \class CPU - \brief Emulates Gameboy CPU instructions - \ingroup API - */ - class GAMEBOYCORE_API CPU - { - public: - //! CPU state - struct Status - { - uint16_t af; - uint8_t a; - uint8_t f; - - uint16_t bc; - uint8_t b; - uint8_t c; - - uint16_t de; - uint8_t d; - uint8_t e; - - uint16_t hl; - uint8_t h; - uint8_t l; - - uint16_t pc; - uint16_t sp; - - bool halt; - bool stopped; - bool ime; - uint8_t enabled_interrupts; - - bool flag_z; - bool flag_n; - bool flag_h; - bool flag_c; - }; - - //! Flags set by the most recent instruction - enum Flags - { - Z = 1 << 7, - N = 1 << 6, - H = 1 << 5, - C = 1 << 4 - }; - - //! Smart pointer type - using Ptr = std::unique_ptr; - - public: - CPU(MMU::Ptr& mmu, GPU::Ptr& gpu, APU::Ptr& apu, Link::Ptr& link); - CPU(const CPU&) = delete; - ~CPU(); - - /** - Run one CPU fetch, decode, execute cycle - */ - void step(); - - /** - Reset the CPU state - */ - void reset(); - - /** - \return true if the CPU is halted - */ - bool isHalted() const noexcept; - - /** - Set CPU debug mode - */ - void setDebugMode(bool debug_mode) noexcept; - - /** - Set a callback for every CPU instruction. - */ - void setInstructionCallback(std::function) noexcept; - - /** - Serialize the CPU state - */ - std::array serialize() const noexcept; - - /** - Deserialize the CPU state - */ - void deserialize(const std::array& data) noexcept; - - /** - Get the current status of the CPU - */ - Status getStatus() const; - - private: - // Private implementation class - class Impl; - Impl* impl_; - }; -} - -#endif // GAMEBOYCORE_CPU_H diff --git a/components/gameboycore/include/gameboycore/detail/audio/noise_channel.h b/components/gameboycore/include/gameboycore/detail/audio/noise_channel.h deleted file mode 100644 index cc70f89e..00000000 --- a/components/gameboycore/include/gameboycore/detail/audio/noise_channel.h +++ /dev/null @@ -1,206 +0,0 @@ -/** - \author Natesh Narain - \date Nov 3 2016 -*/ - -#ifndef GAMEBOYCORE_NOISE_H -#define GAMEBOYCORE_NOISE_H - -#include -#include - -namespace gb -{ - namespace detail - { - /** - \class NoiseChannel - \brief Generate white noise - \ingroup Audio - */ - class NoiseChannel - { - public: - NoiseChannel() : - length_(0), - length_envelope_(0), - envelope_add_mode_(false), - envelope_default_(0), - division_ratio_(0), - width_mode_(false), - shift_clock_frequency_(0), - length_enabled_(false), - trigger_(false), - volume_(0), - output_volume_(0), - length_counter_(0), - timer_load_(0), - timer_(0), - divisor_table_{{8, 16, 32, 48, 64, 80, 96, 112}}, - lfsr_(0), - dac_enabled_(false), - is_enabled_(false) - { - } - - void step() - { - // count down timer - if (--timer_ <= 0) - { - // reload timer - timer_ = timer_load_; - - // It has a 15 - bit shift register with feedback.When clocked by the frequency timer, the low two bits(0 and 1) are XORed, - // all bits are shifted right by one, and the result of the XOR is put into the now - empty high bit. If width mode is 1 (NR43), - // the XOR result is ALSO put into bit 6 AFTER the shift, resulting in a 7 - bit LFSR. - // The waveform output is bit 0 of the LFSR, INVERTED. - uint8_t xored = (lfsr_ & 0x01) ^ ((lfsr_ >> 1) & 0x01); - lfsr_ >>= 1; - lfsr_ |= (xored << 14); - - if (width_mode_) - { - lfsr_ = (lfsr_ & 0x04) | (xored << 6); - } - - if (is_enabled_ && dac_enabled_ && (lfsr_ & 0x01) == 0) - { - output_volume_ = volume_; - } - else - { - output_volume_ = 0; - } - } - } - - void clockLength() - { - if (length_enabled_ && length_counter_ > 0) - { - if (--length_counter_ <= 0) - { - is_enabled_ = false; - } - } - } - - void clockVolume() - { - // count down envelope timer - if (envelope_timer_-- <= 0) - { - // reload envelope timer - envelope_timer_ = length_envelope_; - - if (envelope_add_mode_ && volume_ < 15) - { - volume_++; - } - else if (!envelope_add_mode_ && volume_ > 0) - { - volume_--; - } - } - } - - uint8_t read(uint16_t register_number) - { - switch (register_number) - { - case 0: - return length_ & 0x3F; - case 1: - return (envelope_default_ << 4) | (envelope_add_mode_ << 3) | (length_envelope_ & 0x07); - case 2: - return (shift_clock_frequency_ << 4) | (width_mode_ << 3) | (division_ratio_ & 0x07); - case 3: - return (trigger_ << 7) | (length_enabled_ << 6); - default: - return 0; - } - } - - void write(uint8_t value, uint16_t register_number) - { - switch (register_number) - { - case 0: - length_ = value & 0x3F; - break; - case 1: - length_envelope_ = value & 0x07; - envelope_add_mode_ = (value & 0x08) != 0; - envelope_default_ = (value & 0xF0) >> 4; - break; - case 2: - division_ratio_ = value & 0x07; - width_mode_ = (value & 0x08) != 0; - shift_clock_frequency_ = (value & 0xF0) >> 4; - break; - case 3: - length_enabled_ = (value & 0x40) != 0; - trigger_ = (value & 0x80) != 0; - - if (trigger_) - trigger(); - break; - default: - break; - } - } - - uint8_t getVolume() const - { - return output_volume_; - } - - bool isEnabled() const - { - return is_enabled_; - } - - void disable() - { - is_enabled_ = false; - } - - private: - void trigger() - { - length_counter_ = 64 - length_; - envelope_timer_ = length_envelope_; - volume_ = envelope_default_; - - timer_load_ = divisor_table_[division_ratio_] << shift_clock_frequency_; - timer_ = timer_load_; - } - - uint8_t length_; - uint8_t length_envelope_; - bool envelope_add_mode_; - uint8_t envelope_default_; - uint8_t division_ratio_; - bool width_mode_; - uint8_t shift_clock_frequency_; - bool length_enabled_; - bool trigger_; - - uint8_t volume_; - uint8_t output_volume_; - uint8_t length_counter_; - uint8_t envelope_timer_; - int timer_load_; - int timer_; - - std::array divisor_table_; - uint16_t lfsr_; - - bool dac_enabled_; - bool is_enabled_; - }; - } -} - -#endif // GAMEBOY_NOISE_H diff --git a/components/gameboycore/include/gameboycore/detail/audio/square_wave_channel.h b/components/gameboycore/include/gameboycore/detail/audio/square_wave_channel.h deleted file mode 100644 index 26b2e63e..00000000 --- a/components/gameboycore/include/gameboycore/detail/audio/square_wave_channel.h +++ /dev/null @@ -1,319 +0,0 @@ - -/** - \file square.h - \brief Square wave generator - \author Natesh Narain - \date Nov 21 2016 -*/ - -#ifndef GAMEBOYCORE_SQUARE_WAVE_CHANNEL_H -#define GAMEBOYCORE_SQUARE_WAVE_CHANNEL_H - -#include -#include - -namespace gb -{ - namespace detail - { - /** - \class Square - \brief Square wave generator channels - \ingroup Audio - */ - class SquareWaveChannel - { - public: - static constexpr int LENGTH_MASK = 0x3F; - static constexpr int DAC_MASK = 0xF8; - - public: - - SquareWaveChannel(bool sweep = true) - : sweep_period_{ 0 } - , sweep_negate_{ false } - , sweep_shift_{ 0 } - , sweep_timer_{ 0 } - , frequency_shadow_{ 0 } - , sweep_enabled_{ sweep } - , duty_{ 0 } - , length_{ 0 } - , length_counter_{ 0 } - , volume_{ 0 } - , envelope_add_mode_{ false } - , envelop_period_{ 0 } - , dac_enabled_{ false } - , volume_counter_{ 0 } - , envelop_timer_{ 0 } - , frequency_{ 0 } - , trigger_{ false } - , length_enabled_{ false } - , is_enabled_{ false } - , waveform_idx_{ 0 } - , waveform_timer_{0} - , waveform_timer_load_{ 0 } - , output_volume_{0} - { - // waveforms with different duty cycles - waveform_[0] = { 0, 0, 0, 0, 0, 0, 0, 1 }; - waveform_[1] = { 1, 0, 0, 0, 0, 0, 0, 1 }; - waveform_[2] = { 1, 0, 0, 0, 0, 1, 1, 1 }; - waveform_[3] = { 0, 1, 1, 1, 1, 1, 1, 0 }; - } - - ~SquareWaveChannel() - { - } - - void step() - { - // clock the waveform timer - if (waveform_timer_-- <= 0) - { - // reset timer - waveform_timer_ = waveform_timer_load_; - - // next waveform value - waveform_idx_ = (waveform_idx_ + 1) % waveform_[0].size(); - } - - // compute the output volume - // volume is zero if not enabled - if (isEnabled() && dac_enabled_) - { - output_volume_ = volume_counter_; - } - else - { - output_volume_ = 0; - } - - // output is low if the waveform is low - if (waveform_[duty_][waveform_idx_] == 0) - { - output_volume_ = 0; - } - } - - void clockLength() - { - if (length_enabled_ && length_counter_ > 0) - { - // decrement the length counter and disable the channel when it reaches zero - if (length_counter_-- == 0) - { - is_enabled_ = false; - } - } - } - - void clockVolume() - { - // clock the envelop timer - if (envelop_timer_-- <= 0) - { - // reset envelop timer - envelop_timer_ = envelop_period_; - - if (envelop_period_ != 0) - { - if (envelope_add_mode_ && volume_counter_ < 15) - { - volume_counter_++; - } - else if (!envelope_add_mode_ && volume_counter_ > 0) - { - volume_counter_--; - } - } - } - } - - void clockSweep() - { - // clock sweep timer - if (sweep_timer_-- <= 0) - { - // reload sweep timer - sweep_timer_ = sweep_period_; - - if (sweep_enabled_ && sweep_period_ > 0) - { - auto newFreq = sweepCalculation(); - - if (newFreq <= 2047 && sweep_shift_ > 0) - { - frequency_shadow_ = (uint16_t)newFreq; - waveform_timer_load_ = newFreq; - sweepCalculation(); - } - - sweepCalculation(); - } - } - } - - uint8_t read(uint16_t register_number) - { - // deconstruct byte value into variables - - switch (register_number) - { - case 0: - return ((sweep_period_ & 0x07) << 4) | (sweep_negate_ << 3) | (sweep_shift_ & 0x07); - case 1: - return ((duty_ & 0x03) << 6) | (length_ & 0x3F); - case 2: - return ((volume_ & 0x0F) << 4) | (envelope_add_mode_ << 3) | (envelop_period_ & 0x07); - case 3: - return frequency_ & 0x00FF; - case 4: - return ((frequency_ & 0x0700) >> 8) | (trigger_ << 7) | (length_enabled_ << 6); - } - - return 0; - } - - void write(uint8_t value, uint16_t register_number) - { - // construct byte values from variables - - switch (register_number) - { - case 0: - sweep_period_ = (value & 0x70) >> 4; - sweep_negate_ = (value & 0x08) != 0; - sweep_shift_ = value & 0x07; - - sweep_timer_ = sweep_period_; - break; - case 1: - duty_ = (value >> 6); - length_ = (value & 0x3F); - length_counter_ = 64 - length_; - break; - case 2: - volume_ = (value >> 4); - envelope_add_mode_ = (value & 0x08) != 0; - envelop_period_ = (value & 0x07); - - dac_enabled_ = (value & DAC_MASK) != 0; - volume_counter_ = volume_; - envelop_timer_ = envelop_period_; - - break; - case 3: - frequency_ = (frequency_ & 0xFF00) | value; - break; - case 4: - frequency_ = (frequency_ & 0x00FF) | ((value & 0x07) << 8); - length_enabled_ = (value & 0x40) != 0; - trigger_ = (value & 0x80) != 0; - - if (trigger_) - trigger(); - - break; - } - } - - void trigger() - { - is_enabled_ = true; - - waveform_timer_load_ = calculateWaveformTimer(); - waveform_timer_ = waveform_timer_load_; - - // sweep - frequency_shadow_ = frequency_; - sweep_timer_ = sweep_period_; - - sweep_enabled_ = (sweep_period_ > 0) || (sweep_shift_ > 0); - } - - int calculateWaveformTimer() - { - return (2048 - frequency_) * 4; - } - - int sweepCalculation() - { - int f = 0; - - f = frequency_shadow_ >> sweep_shift_; - - if (sweep_negate_) - { - f = frequency_shadow_ - f; - } - else - { - f = frequency_shadow_ + f; - } - - sweep_enabled_ = f < 2047; - - return f; - } - - uint8_t getVolume() const - { - return output_volume_; - } - - bool isEnabled() const - { - return is_enabled_; - } - - void disable() - { - is_enabled_ = false; - } - - private: - // NR10 FF10: -PPP NSSS Sweep period, negate, shift - uint8_t sweep_period_; - bool sweep_negate_; - uint8_t sweep_shift_; - - int sweep_timer_; - uint16_t frequency_shadow_; - bool sweep_enabled_; - - // NR11 FF11: DDLL LLLL Duty, Length load (64-L) - uint8_t duty_; - uint8_t length_; - int length_counter_; - - // NR12 FF12: VVVV APPP Starting volume, Envelope add mode, period - uint8_t volume_; - bool envelope_add_mode_; - uint8_t envelop_period_; - - bool dac_enabled_; - uint8_t volume_counter_; - int envelop_timer_; - - // NR13 FF13: FFFF FFFF Frequency LSB - uint16_t frequency_; - - // NR14 FF14 TL-- -FFF Trigger, Length enable, Frequency MSB - bool trigger_; - bool length_enabled_; - - // - bool is_enabled_; - - std::array, 4> waveform_; - int waveform_idx_; - int waveform_timer_; - int waveform_timer_load_; - - uint8_t output_volume_; - - }; - } -} - -#endif // GAMEBOYCORE_SOUND_H diff --git a/components/gameboycore/include/gameboycore/detail/audio/wave_channel.h b/components/gameboycore/include/gameboycore/detail/audio/wave_channel.h deleted file mode 100644 index 98826c40..00000000 --- a/components/gameboycore/include/gameboycore/detail/audio/wave_channel.h +++ /dev/null @@ -1,194 +0,0 @@ - -/** - \author Natesh Narain -*/ - -#ifndef GAMEBOY_WAVE_CHANNEL_H -#define GAMEBOY_WAVE_CHANNEL_H - -#include - -namespace gb -{ - namespace detail - { - /** - \class Wave - \brief Wave register - \ingroup Audio - */ - class WaveChannel - { - public: - static const int LENGTH_MASK = 0xFF; - - public: - WaveChannel() : - dac_enabled_(false), - length_load_(0), - volume_code_(0), - frequency_(0), - length_enabled_(false), - trigger_(false), - wave_ram_{0}, - timer_load_(0), - timer_(0), - sample_index_(0), - length_counter_(0), - volume_(0), - shift_table_{{4, 0, 1, 2}}, - is_enabled_(false) - { - } - - ~WaveChannel() - { - } - - void clockLength() - { - // wave channel length counter will disable channel when it gets to zero - if (length_enabled_ && length_counter_ > 0) - { - if (length_counter_-- <= 0) - { - is_enabled_ = false; - } - } - } - - void step() - { - // wave channel internal timer - if (timer_-- <= 0) - { - timer_ = timer_load_; - - // next sample index - sample_index_ = (sample_index_ + 1) % wave_ram_.size(); - - // get the next volume - volume_ = wave_ram_[sample_index_]; - volume_ >>= shift_table_[volume_code_]; - } - - if (!dac_enabled_ || !is_enabled_) - volume_ = 0; - } - - uint8_t read(uint16_t register_number) - { - switch (register_number) - { - case 0: - return (dac_enabled_ << 7); - case 1: - return length_load_; - case 2: - return volume_code_ << 5; - case 3: - return frequency_ & 0x00FF; - case 4: - return (trigger_ << 7) | (length_enabled_ << 6) | ((frequency_ & 0x0700) >> 8); - default: - return 0; - } - } - - void write(uint8_t value, uint16_t register_number) - { - switch (register_number) - { - case 0: - dac_enabled_ = (value & 0x80) != 0; - break; - - case 1: - length_load_ = value; - break; - - case 2: - volume_code_ = (value & 0x60) >> 5; - break; - - case 3: - frequency_ = (frequency_ & 0xFF00) | value; - break; - - case 4: - frequency_ = (frequency_ & 0x00FF) | ((value & 0x0007) << 8); - length_enabled_ = (value & 0x40) != 0; - trigger_ = (value & 0x80) != 0; - - if (trigger_) - trigger(); - - break; - } - } - - uint8_t readWaveRam(uint16_t addr) - { - auto idx = (addr & 0x000F) * 2; - return ((wave_ram_[idx]) << 4) | wave_ram_[idx+1]; - } - - void writeWaveRam(uint8_t value, uint16_t addr) - { - auto idx = (addr & 0x0F) * 2; - - wave_ram_[idx] = (value & 0xF0) >> 4; - wave_ram_[idx+1] = value & 0x0F; - } - - bool isEnabled() const - { - return is_enabled_; - } - - uint8_t getVolume() const - { - return volume_; - } - - void disable() - { - is_enabled_ = false; - } - - private: - - void trigger() - { - is_enabled_ = true; - - timer_load_ = (2048 - frequency_) * 2; - timer_ = timer_load_; - - length_counter_ = length_load_; - } - - bool dac_enabled_; - uint8_t length_load_; - uint8_t volume_code_; - uint16_t frequency_; - bool length_enabled_; - bool trigger_; - - std::array wave_ram_; - - - int timer_load_; - int timer_; - int sample_index_; - int length_counter_; - uint8_t volume_; - - std::array shift_table_; - - bool is_enabled_; - }; - } -} - -#endif // GAMEBOYCORE_WAVE_H diff --git a/components/gameboycore/include/gameboycore/detail/hash.h b/components/gameboycore/include/gameboycore/detail/hash.h deleted file mode 100644 index 23f8a5a3..00000000 --- a/components/gameboycore/include/gameboycore/detail/hash.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef GAMEBOYCORE_DETAIL_HASH -#define GAMEBOYCORE_DETAIL_HASH - -#include -#include - -namespace gb -{ - namespace detail - { - // boost::hash_combine - template - inline void hash_combine(std::size_t& seed, T const& v) - { - seed ^= std::hash{}(v) + 0x9E3779B9 + (seed << 6) + (seed >> 2); - } - - /** - Vector Hash - */ - template - struct ContainerHash - { - using argument_type = T; - using result_type = std::size_t; - - result_type operator()(argument_type const& in) const - { - std::size_t seed = 0; - for (const auto& i : in) - hash_combine(seed, i); - - return seed; - } - }; - } -} - -#endif diff --git a/components/gameboycore/include/gameboycore/detail/rtc/rtc.h b/components/gameboycore/include/gameboycore/detail/rtc/rtc.h deleted file mode 100644 index ce2d3afa..00000000 --- a/components/gameboycore/include/gameboycore/detail/rtc/rtc.h +++ /dev/null @@ -1,109 +0,0 @@ -/** - \file rtc.h - \brief Real Time Clock Emulation - \author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_RTC_H -#define GAMEBOYCORE_RTC_H - -#include - -#include -#include -#include - -namespace gb -{ - namespace detail - { - /** - \class RTC - \brief Real Time Clock - \ingroup MBC - */ - class RTC - { - private: - static constexpr uint8_t REGISTER_BASE = 0x08; - - enum Registers - { - SECONDS_REGISTER = 0, - MINUTES_REGISTER, - HOURS_REGISTER, - DAY_LSB_REGISTER, - DAY_MSB_REGISTER - }; - - public: - RTC() - : enabled_{false} - , selected_{0} - , time_data_{0, 0, 0} - { - time_provider_ = RTC::getTime; - } - - ~RTC() - { - } - - uint8_t get() const - { - return time_data_[selected_]; - } - - void setEnable(bool enable) - { - enabled_ = enable; - } - - void latch() - { - const auto time = time_provider_(); - time_data_[0] = time.seconds; - time_data_[1] = time.minutes; - time_data_[2] = time.hours; - time_data_[3] = time.days & 0xFF; - time_data_[4] = (time.days & 0x100) >> 8; - } - - void select(uint8_t reg) - { - selected_ = reg - REGISTER_BASE; - } - - bool isEnabled() const - { - return enabled_; - } - - void setTimeProvider(TimeProvider fn) - { - time_provider_ = fn; - } - - static const Time getTime() - { - const auto time = std::time(0); - const auto now = std::localtime(&time); - - const auto seconds = static_cast(now->tm_sec); - const auto minutes = static_cast(now->tm_min); - const auto hours = static_cast(now->tm_hour); - const auto days = static_cast(now->tm_yday); - - return Time{ seconds, minutes, hours, days }; - } - - private: - bool enabled_; - uint8_t selected_; - std::array time_data_; - TimeProvider time_provider_; - }; - } -} - -#endif // GAMEBOYCORE_RTC_H diff --git a/components/gameboycore/include/gameboycore/gameboycore.h b/components/gameboycore/include/gameboycore/gameboycore.h deleted file mode 100644 index 1c854d1e..00000000 --- a/components/gameboycore/include/gameboycore/gameboycore.h +++ /dev/null @@ -1,174 +0,0 @@ -/** - \file gameboycore.h - \brief Encapsulate Gameboy hardware - \author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_H -#define GAMEBOYCORE_H - -#include "gameboycore/gameboycore_api.h" - -#include "gameboycore/cpu.h" -#include "gameboycore/mmu.h" -#include "gameboycore/gpu.h" -#include "gameboycore/apu.h" -#include "gameboycore/joypad.h" -#include "gameboycore/link.h" - -#include -#include -#include - -//! GameboyCore namespace -namespace gb -{ - /** - \brief Encapsulation for Gameboy emulation - \class GameboyCore - \ingroup API - */ - class GAMEBOYCORE_API GameboyCore - { - public: - enum class ColorTheme - { - DEFAULT, - GOLD, - GREEN - }; - - GameboyCore(); - GameboyCore(const GameboyCore&) = delete; - ~GameboyCore(); - - /** - runs `steps` number of steps on the gameboycore - */ - void update(int steps = 1); - - /** - Run emulation for a single frame - */ - void emulateFrame(); - - /** - Load a ROM file - */ - void open(const std::string& filename); - - /** - Load byte buffer into virtual memroy - */ - void loadROM(const std::vector& buffer); - - /** - Load byte buffer into virtual memory - */ - void loadROM(const uint8_t* rom, uint32_t size); - - /** - Reset the GameboyCore state - */ - void reset(); - - /** - Enable debug output - */ - void setDebugMode(bool debug); - - /** - Set Color theme - */ - void setColorTheme(ColorTheme theme); - - /** - * Read memory - */ - uint8_t readMemory(uint16_t addr); - - /** - * Write memory - */ - void writeMemory(uint16_t addr, uint8_t value); - - /** - Set scanline callback - */ - void setScanlineCallback(GPU::RenderScanlineCallback callback); - - /** - Set VBlank callback - */ - void setVBlankCallback(GPU::VBlankCallback callback); - - /** - Set audio sample callback - */ - void setAudioSampleCallback(APU::AudioSampleCallback callback); - - /** - Joypad key input event - */ - void input(Joy::Key key, bool pressed); - - /** - Get battery RAM - - This copies the battery backed RAM from the emulator and returns it to the user - */ - std::vector getBatteryRam() const; - - /** - Set battery RAM - */ - void setBatteryRam(const std::vector& ram); - - /** - Write a byte to the serial port - */ - void linkWrite(uint8_t byte); - - /** - Set Link ready callback - - Set a callback that fires when the core is ready to transfer a byte to the serial port - */ - void setLinkReadyCallback(Link::ReadyCallback callback); - - /** - Serialize GameboyCore state - */ - std::vector serialize() const; - - /** - Deserialize GameboyCore state - */ - void deserialize(const std::vector& data); - - /** - Set the time to be read from the RTC register (MBC3) - */ - void setTimeProvider(const TimeProvider provider); - - /** - Set instruction callback - */ - void setInstructionCallback(std::function instr); - - CPU::Ptr& getCPU(); - MMU::Ptr& getMMU(); - GPU::Ptr& getGPU(); - APU::Ptr& getAPU(); - Joy::Ptr& getJoypad(); - Link::Ptr& getLink(); - - bool isDone() const; - - private: - class Impl; - Impl* impl_; - }; -} - -#endif // GAMEBOYCORE_H diff --git a/components/gameboycore/include/gameboycore/gameboycore_api.h b/components/gameboycore/include/gameboycore/gameboycore_api.h deleted file mode 100644 index d9e5d66d..00000000 --- a/components/gameboycore/include/gameboycore/gameboycore_api.h +++ /dev/null @@ -1,25 +0,0 @@ - -/** - \file gameboycore_api.h - \brief Define export macros - \author Natesh Narain - \date Nov 12 2016 - \defgroup API -*/ - -#ifndef GAMEBOYCORE_API_H -#define GAMEBOYCORE_API_H - -/* DLL Export/Import */ - -#if defined(_MSC_VER) && !defined(GAMEBOYCORE_STATIC) -# if defined(GAMEBOYCORE_EXPORT) -# define GAMEBOYCORE_API __declspec(dllexport) -# else -# define GAMEBOYCORE_API __declspec(dllimport) -# endif -#else -# define GAMEBOYCORE_API -#endif - -#endif diff --git a/components/gameboycore/include/gameboycore/gpu.h b/components/gameboycore/include/gameboycore/gpu.h deleted file mode 100644 index 24ca325d..00000000 --- a/components/gameboycore/include/gameboycore/gpu.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - \file gpu.h - \brief GPU emulation - \author Natesh Narain - \date Oct 23 2016 - - \defgroup Graphics -*/ - -#ifndef GAMEBOYCORE_GPU_H -#define GAMEBOYCORE_GPU_H - -#include "gameboycore_api.h" -#include "gameboycore/mmu.h" -#include "gameboycore/pixel.h" -#include "gameboycore/sprite.h" - -#include -#include -#include -#include -#include - -namespace gb -{ - /** - \class GPU - \brief Handle LCD state, compute scanlines and send to an external renderer - \ingroup API - \ingroup Graphics - */ - class GAMEBOYCORE_API GPU - { - public: - //! Smart pointer type - using Ptr = std::unique_ptr; - - //! Array on Pixel objects representing a single scan line produced by the GPU - using Scanline = std::array; - - /** - Callback function called by the GPU when it has produced a new scan line - Provides the Scanline and the line number - */ - using RenderScanlineCallback = std::function; - - /** - Callback function call by the GPU when VBlank is reached - */ - using VBlankCallback = std::function; - - public: - explicit GPU(MMU::Ptr& mmu); - GPU(const GPU&) = delete; - ~GPU(); - - /** - Update the GPU with elasped cycles. Used by the CPU - */ - void update(uint8_t cycles, bool ime); - - /** - Set the host system callback - */ - void setRenderCallback(RenderScanlineCallback callback); - - /** - Set the host system vblank callback - */ - void setVBlankCallback(VBlankCallback callback); - - /** - Set Default Palette Color - */ - void setPaletteColor(uint8_t r, uint8_t g, uint8_t b, int idx); - - /** - \return Background tilemap data - */ - std::vector getBackgroundTileMap(); - - /** - \return currently cached tile data - */ - std::array getSpriteCache() const; - - /** - \return Hashed background map - */ - std::size_t getBackgroundHash(); - - private: - //! Private Implementation class - class Impl; - Impl* impl_; - }; -} - -#endif diff --git a/components/gameboycore/include/gameboycore/instruction.h b/components/gameboycore/include/gameboycore/instruction.h deleted file mode 100644 index 1adae1d1..00000000 --- a/components/gameboycore/include/gameboycore/instruction.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * \file instruction.h - * \author Natesh Narain - * \date April 21 2019 -*/ - -#ifndef GAMEBOYCORE_INSTRUCTION_H -#define GAMEBOYCORE_INSTRUCTION_H - -#include -#include -#include - -namespace gb -{ - struct Instruction - { - enum class OpcodePage - { - PAGE1, - PAGE2 - }; - - //! Instruction Opcode - uint8_t opcode; - //! Opcode Page - OpcodePage page; - //! Opcode Data - std::array operand_data; - - Instruction(uint8_t opcode, OpcodePage page, std::array data) - : opcode{opcode} - , page{page} - , operand_data{data} - { - } - }; - - /** - Stringify a Instruction struct - */ - std::string disassemble(const Instruction& instruction); - -} - -#endif // GAMEBOYCORE_INSTRUCTION_H diff --git a/components/gameboycore/include/gameboycore/interrupt_provider.h b/components/gameboycore/include/gameboycore/interrupt_provider.h deleted file mode 100644 index c8a8032a..00000000 --- a/components/gameboycore/include/gameboycore/interrupt_provider.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef GAMEBOYCORE_INTERRUPT_PROVIDER_H -#define GAMEBOYCORE_INTERRUPT_PROVIDER_H - -#include "gameboycore/mmu.h" -#include "gameboycore/memorymap.h" - -#include - -namespace gb -{ - /** - \class InterruptProvider - \brief Used to set interrupt flag register for a single interrupt - */ - class InterruptProvider - { - public: - enum class Interrupt - { - VBLANK = (1 << 0), - LCDSTAT = (1 << 1), - TIMER = (1 << 2), - SERIAL = (1 << 3), - JOYPAD = (1 << 4) - }; - - public: - InterruptProvider(MMU& mmu, Interrupt interrupt) : - flags_(mmu.get(memorymap::INTERRUPT_FLAG)), - interrupt_(interrupt) - { - } - - /** - Set the interrupt - */ - void set() - { - flags_ |= static_cast(interrupt_); - } - - ~InterruptProvider() - { - } - - private: - uint8_t& flags_; - Interrupt interrupt_; - }; - -} - -#endif diff --git a/components/gameboycore/include/gameboycore/joypad.h b/components/gameboycore/include/gameboycore/joypad.h deleted file mode 100644 index 01757813..00000000 --- a/components/gameboycore/include/gameboycore/joypad.h +++ /dev/null @@ -1,63 +0,0 @@ - -/** - \file joypad.h - \brief Emulate Gameboy user input - \author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_JOYPAD_H -#define GAMEBOYCORE_JOYPAD_H - -#include "gameboycore/gameboycore_api.h" -#include "gameboycore/mmu.h" - -#include -#include - -namespace gb -{ - /** - \class Joy - \brief Emulate Gameboy Joypad - \ingroup API - */ - class GAMEBOYCORE_API Joy - { - public: - //! Keys on the Gameboy - enum class Key - { - RIGHT = 0, - LEFT = 1, - UP = 2, - DOWN = 3, - A = 4, - B = 5, - SELECT = 6, - START = 7 - }; - - //! Smart pointer type - using Ptr = std::unique_ptr; - - explicit Joy(MMU& mmu); - Joy(const Joy&); - ~Joy(); - - /** - Press Key on the Gameboy - */ - void press(Key key); - /** - Release Key on the Gameboy - */ - void release(Key key); - - private: - //! Private Implementation - class Impl; - Impl* impl_; - }; -} - -#endif // GAMEBOY_JOYPAD_H diff --git a/components/gameboycore/include/gameboycore/link.h b/components/gameboycore/include/gameboycore/link.h deleted file mode 100644 index e7301771..00000000 --- a/components/gameboycore/include/gameboycore/link.h +++ /dev/null @@ -1,66 +0,0 @@ -/** - \file link.h - \brief Link port emulation - \author Natesh Narain - \date Nov 30 2016 -*/ - -#ifndef GAMEBOYCORE_LINK_H -#define GAMEBOYCORE_LINK_H - -#include "gameboycore/gameboycore_api.h" -#include "gameboycore/mmu.h" - -#include -#include -#include - -namespace gb -{ - /** - \class Link - \brief Emulate link port - \ingroup API - */ - class GAMEBOYCORE_API Link - { - public: - enum class Mode - { - INTERNAL, ///< Internal Clock Mode - EXTERNAL ///< External Clock Mode - }; - - //! Smart pointer type - using Ptr = std::unique_ptr; - - //! Callback to signal transfer ready status - using ReadyCallback = std::function; - - public: - explicit Link(MMU::Ptr& mmu); - Link(const Link&) = delete; - ~Link(); - - /** - Update with CPU cycles elapsed - */ - void update(uint8_t cycles); - - /** - Write a byte buffer into the core - */ - void recieve(uint8_t byte); - - /** - Signal that this core is ready to transfer a byte - */ - void setReadyCallback(const ReadyCallback& callback); - - private: - class Impl; - Impl* impl_; - }; -} - -#endif diff --git a/components/gameboycore/include/gameboycore/link_cable.h b/components/gameboycore/include/gameboycore/link_cable.h deleted file mode 100644 index 3af65a3d..00000000 --- a/components/gameboycore/include/gameboycore/link_cable.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - \file link_cable.h - \brief Gameboy Link Cable Emulation - \author Natesh Narain - \date Dec 10 2016 -*/ - -#ifndef GAMEBOYCORE_LINK_CABLE_H -#define GAMEBOYCORE_LINK_CABLE_H - -#include "gameboycore/gameboycore_api.h" -#include "gameboycore/link.h" - -#include -#include - -namespace gb -{ - /** - \class Link Cable - \brief Contains Gameboy link cable logic - \ingroup API - */ - class GAMEBOYCORE_API LinkCable - { - public: - //! Callback from Link Cable - using RecieveCallback = std::function; - - LinkCable(); - ~LinkCable(); - - void link1ReadyCallback(uint8_t byte, Link::Mode mode); - void link2ReadyCallback(uint8_t byte, Link::Mode mode); - - void setLink1RecieveCallback(const RecieveCallback& callback); - void setLink2RecieveCallback(const RecieveCallback& callback); - - private: - class Impl; - Impl* impl_; - }; -} - -#endif // GAMEBOYCORE_LINK_CABLE_H diff --git a/components/gameboycore/include/gameboycore/mbc.h b/components/gameboycore/include/gameboycore/mbc.h deleted file mode 100644 index 86e48dc7..00000000 --- a/components/gameboycore/include/gameboycore/mbc.h +++ /dev/null @@ -1,177 +0,0 @@ - -/** - \file mbc.h - \brief Interface memory bank controllers - \author Natesh Narain - \date Oct 11 2016 - - \defgroup MBC Memory Bank Controllers -*/ - -#ifndef GAMEBOYCORE_MBC_H -#define GAMEBOYCORE_MBC_H - -#include -#include -#include - -namespace gb -{ - namespace detail - { - enum { - KILO_BYTE = 1024, - BANK_SIZE = (16 * KILO_BYTE) - }; - - /** - \class MBC - \brief Memory Bank Controller Interface - \ingroup MBC - */ - class MBC - { - public: - enum class Type { - ROM_ONLY = 0x00, - MBC1 = 0x01, - MBC1_RAM = 0x02, - MBC1_RAM_BAT = 0x03, - MBC2 = 0x05, - MBC2_BAT = 0x06, - ROM_RAM = 0x08, - ROM_RAM_BAT = 0x09, - MMM01 = 0x0B, - MMM01_RAM = 0x0C, - MMM01_RAM_BAT = 0x0D, - MBC3_TIME_BAT = 0x0F, - MBC3_TIME_RAM_BAT = 0x10, - MBC3 = 0x11, - MBC3_RAM = 0x12, - MBC3_RAM_BAT = 0x13, - MBC4 = 0x15, - MBC4_RAM = 0x16, - MBC4_RAM_BAT = 0x17, - MBC5 = 0x19, - MBC5_RAM = 0x1A, - MBC5_RAM_BAT = 0x1B, - MBC5_RUMBLE = 0x1C, - MBC5_RUMBLE_RAM = 0x1D, - MBC5_RUMBLE_RAM_BAT = 0x1E - }; - - //! ROM types specified in cartridge header - enum class ROM - { - KB32 = 0x00, ///< 32 kB - KB64 = 0x01, ///< 64 kB - KB128 = 0x02, ///< 128 kB - KB256 = 0x03, ///< 256 kB - KB512 = 0x04, ///< 512 kB - MB1 = 0x05, ///< 1 MB - MB2 = 0x06, ///< 2 MB - MB4 = 0x07, ///< 4 MB - MB1_1 = 0x52, ///< 1.1 MB - MB1_2 = 0x53, ///< 1.2 MB - MB1_5 = 0x54 ///< 1.5 MB - }; - - //! External RAM types specified in the cartridge header - enum class XRAM - { - NONE = 0x00, ///< No External RAM - KB2 = 0x01, ///< 2 kB of External RAM - KB8 = 0x02, ///< 8 kB of External RAM - KB32 = 0x03 ///< (4 x 8 kB) of External RAM - }; - - public: - using Ptr = std::unique_ptr; - - public: - MBC(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enable = false); - virtual ~MBC(); - - virtual void write(uint8_t value, uint16_t addr); - virtual uint8_t read(uint16_t addr) const; - - uint8_t readVram(uint16_t addr, uint8_t bank); - - uint8_t& get(uint16_t addr); - uint8_t* getptr(uint16_t addr); - - /** - Get the virtual memory location from the logical address - */ - int resolveAddress(const uint16_t& addr) const; - - std::vector getRange(uint16_t start, uint16_t end) const; - void setMemory(uint16_t start, const std::vector& mem); - - std::vector getXram() const; - - int getRomBank() const; - int getRamBank() const; - bool isXramEnabled() const; - - std::size_t getVirtualMemorySize() const; - - protected: - /** - Called when a write to ROM occurs - */ - virtual void control(uint8_t value, uint16_t addr) = 0; - - //! virtual memory - std::vector memory_; - //! Flag inidicating if external ram is enabled - bool xram_enable_; - //! ROM bank number - int rom_bank_; - //! RAM bank number - int ram_bank_; - - private: - /** - \return index of address into virtual memory - */ - int getIndex(uint16_t addr, int rom_bank, int ram_bank) const; - - /** - */ - int getIoIndex(uint16_t addr) const; - - /** - Get the VRAM offset given the current state of the VBK register - */ - int getVramOffset() const; - - /** - Get the internal ram bank offset given the current state of the SVBK register - */ - int getInternalRamOffset() const; - - /** - */ - unsigned int kilo(unsigned int n) const; - - /** - Load memory - */ - void loadMemory(const uint8_t* rom, std::size_t size, uint8_t rom_size, uint8_t ram_size); - - //! number of switchable rom banks - int num_rom_banks_; - //! number of cartridge ram banks - int num_cartridge_ram_banks_; - //! CGB enabled - bool cgb_enabled_; - //! CGB mode has 2 vram banks for character and map data - int vram_banks_; - //! number internal ram banks - int num_internal_ram_banks_; - }; - } -} - -#endif // GAMEBOYCORE_MBC_H diff --git a/components/gameboycore/include/gameboycore/mbc1.h b/components/gameboycore/include/gameboycore/mbc1.h deleted file mode 100644 index e9f6f9ac..00000000 --- a/components/gameboycore/include/gameboycore/mbc1.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * \file mbc1.h - * \author Natesh Narain - * \brief Memory Back Controller 1 - * \date Oct 11 2016 -*/ -#ifndef GAMEBOYCORE_MBC1_H -#define GAMEBOYCORE_MBC1_H - -#include "gameboycore/mbc.h" - -#include - -namespace gb -{ - namespace detail - { - /** - \class MBC1 - \brief Memory Bank Controller 1 - \ingroup MBC - */ - class MBC1 : public MBC - { - private: - - //! RAM or ROM bank switching mode - enum class MemoryMode - { - ROM = 0, RAM = 1 ///< determines how address range $4000 - $5000 is used - }; - - public: - MBC1(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enabled); - - protected: - virtual void control(uint8_t value, uint16_t addr); - - private: - void selectRomBank(uint8_t lo, uint8_t hi); - void selectRamBank(uint8_t ram_bank_number); - - uint8_t rom_bank_lower_bits_; // bit 0 - 4 - uint8_t rom_bank_upper_bits_; // bit 5 and 6 - - MemoryMode mode_; - }; - } -} - -#endif diff --git a/components/gameboycore/include/gameboycore/mbc2.h b/components/gameboycore/include/gameboycore/mbc2.h deleted file mode 100644 index 481274e5..00000000 --- a/components/gameboycore/include/gameboycore/mbc2.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - \file mbc2.h - \brief Memory Bank Controller 2 - \author Natesh Narain - \date Nov 20 2016 -*/ - -#ifndef GAMEBOYCORE_MBC2_H -#define GAMEBOYCORE_MBC2_H - -#include "gameboycore/mbc.h" - -namespace gb -{ - namespace detail - { - /** - \class MBC2 - \brief Memory Bank Controller 2 - \ingroup MBC - */ - class MBC2 : public MBC - { - public: - - MBC2(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enable); - ~MBC2(); - - virtual void write(uint8_t value, uint16_t addr); - - protected: - virtual void control(uint8_t value, uint16_t addr); - - private: - }; - } -} - -#endif // !GAMEBOYCORE_MBC2_H - diff --git a/components/gameboycore/include/gameboycore/mbc3.h b/components/gameboycore/include/gameboycore/mbc3.h deleted file mode 100644 index dc528148..00000000 --- a/components/gameboycore/include/gameboycore/mbc3.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - \file mbc2.h - \brief Memory Bank Controller 3 - \author Natesh Narain - \date Nov 20 2016 -*/ - -#ifndef GAMEBOYCORE_MBC3_H -#define GAMEBOYCORE_MBC3_H - -#include "gameboycore/mbc.h" -#include "gameboycore/detail/rtc/rtc.h" -#include - -namespace gb -{ - namespace detail - { - /** - \class MBC3 - \brief Memory Bank Controller 3 - \ingroup MBC - */ - class MBC3 : public MBC - { - public: - MBC3(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enable); - ~MBC3(); - - virtual uint8_t read(uint16_t addr) const; - - void setTimeProvider(TimeProvider provider); - - protected: - virtual void control(uint8_t value, uint16_t addr); - - private: - RTC rtc_; - uint8_t latch_ctl_; - }; - } -} - -#endif \ No newline at end of file diff --git a/components/gameboycore/include/gameboycore/mbc5.h b/components/gameboycore/include/gameboycore/mbc5.h deleted file mode 100644 index 2fbcb056..00000000 --- a/components/gameboycore/include/gameboycore/mbc5.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef GAMEBOYCORE_MBC5_H -#define GAMEBOYCORE_MBC5_H - -#include "gameboycore/mbc.h" - -namespace gb -{ - namespace detail - { - class MBC5 : public MBC - { - public: - - MBC5(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enable); - ~MBC5(); - - void control(uint8_t value, uint16_t addr); - - private: - void selectRomBank(uint8_t lo, uint8_t hi); - - uint8_t rom_bank_lower_bits_; - uint8_t rom_bank_upper_bit_; - }; - } -} - -#endif // GAMEBOYCORE_MBC5_H diff --git a/components/gameboycore/include/gameboycore/memorymap.h b/components/gameboycore/include/gameboycore/memorymap.h deleted file mode 100644 index 7fecb2f8..00000000 --- a/components/gameboycore/include/gameboycore/memorymap.h +++ /dev/null @@ -1,195 +0,0 @@ -/** - \file memorymap.h - \brief Constants that define the CPU memory map - \author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_MEMORYMAP_H -#define GAMEBOYCORE_MEMORYMAP_H - -namespace gb{ - //! Defines values for specific locations in the Gameboy memory map - namespace memorymap{ - enum Locations { - PERMANENT_ROM_BANK_START = 0x0000, ///< - - INTERRUPT_HANDLER_VBLANK = 0x0040, - INTERRUPT_HANDLER_LCDSTAT = 0x0048, - INTERRUPT_HANDLER_TIMER = 0x0050, - INTERRUPT_HANDLER_SERIAL = 0x0058, - INTERRUPT_HANDLER_JOYPAD = 0x0060, - - PROGRAM_START = 0x0100, - - NINTENDO_LOGO_START = 0x0104, - NINTENDO_LOGO_END = 0x0133, - - GAME_TITLE_START = 0x0134, - GAME_TITLE_END = 0x013E, - - GAME_DESTINATION_START = 0x013F, - GAME_DESTINATION_END = 0x0142, - - COLOR_COMPATABILITY = 0x0143, - - NEW_LICENSE_START = 0x0144, - NEW_LICENSE_END = 0x0145, - - SGB_COMPATABILITY = 0x0146, - - CART_TYPE = 0x0147, - - CART_ROM_SIZE = 0x0148, - CART_RAM_SIZE = 0x0149, - - DETINATION_CODE = 0x014A, - - OLD_LICENSE = 0x014B, - - MASK_ROM_VERSION = 0x014C, - - COMPLEMENT_CHECKSUM = 0x014D, - - CHECKSUM_START = 0x014E, - CHECKSUM_END = 0x014F, - - PERMANENT_ROM_BANK_END = 0x3FFF, - - SWITCHABLE_ROM_BANK_START = 0x4000, - SWITCHABLE_ROM_BANK_END = 0x7FFF, - - CHARACTER_RAM_START = 0x8000, - CHARACTER_RAM_END = 0x97FF, - - BG_MAP_DATA_1_START = 0x9800, - BG_MAP_DATA_1_END = 0x9BFF, - - BG_MAP_DATA_2_START = 0x9C00, - BG_MAP_DATA_2_END = 0x9FFF, - - EXTERNAL_RAM_START = 0xA000, - EXTERNAL_RAM_END = 0xBFFF, - - WORK_RAM_BANK_0_START = 0xC000, - WORK_RAM_BANK_0_END = 0xCFFF, - - WORK_RAM_BANK_1_START = 0xD000, - WORK_RAM_BANK_1_END = 0xDFFF, - - OAM_START = 0xFE00, - OAM_END = 0xFE9F, - - JOYPAD_REGISTER = 0xFF00, - - SB_REGISTER = 0xFF01, - SC_REGISTER = 0xFF02, - - DIVIDER_LO_REGISTER = 0xFF03, - DIVIDER_REGISER = 0xFF04, - - TIMER_COUNTER_REGISTER = 0xFF05, - TIMER_MODULO_REGISTER = 0xFF06, - TIMER_CONTROLLER_REGISTER = 0xFF07, - - NR10_REGISTER = 0xFF10, // Channel 1 Sweep - NR11_REGISTER = 0xFF11, // Channel 1 Sound length/Wave Pattern - NR12_REGISTER = 0xFF12, // Channel 1 Volume Envelop - NR13_REGISTER = 0xFF13, // Channel 1 Frequency LOW - NR14_REGISTER = 0xFF14, // Channel 1 Frequency HIGH - - NR20_REGISTER = 0xFF15, // UNUSED - NR21_REGISTER = 0xFF16, // Channel 2 Sound length/Wave Pattern - NR22_REGISTER = 0xFF17, // Channel 2 Volume Envelop - NR23_REGISTER = 0xFF18, // Channel 2 Frequency LOW - NR24_REGISTER = 0xFF19, // Channel 2 Frequency HIGH - - NR30_REGISTER = 0xFF1A, // Channel 3 ON/OFF - NR31_REGISTER = 0xFF1B, // Channel 3 Sound length - NR32_REGISTER = 0xFF1C, // Channel 3 Select Output Level - NR33_REGISTER = 0xFF1D, // Channel 3 Frequency LOW - NR34_REGISTER = 0xFF1E, // Channel 3 Frequency HIGH - - NR41_REGISTER = 0xFF20, // Channel 4 Sound length - NR42_REGISTER = 0xFF21, // Channel 4 Volume Envelope - NR43_REGISTER = 0xFF22, // Channel 4 Polynomial Counter - NR44_REGISTER = 0xFF23, // Channel 4 Counter/consecutive selection - - NR50_REGISTER = 0xFF24, // Sound Control Register ON/OFF / Volume Control - NR51_REGISTER = 0xFF25, // Output terminal selection - NR52_REGISTER = 0xFF26, // Sound On/Off - - WAVE_PATTERN_RAM_START = 0xFF30, - WAVE_PATTERN_RAM_END = 0xFF3F, - - LCDC_REGISTER = 0xFF40, - LCD_STAT_REGISTER = 0xFF41, - SCY_REGISTER = 0xFF42, - SCX_REGISTER = 0xFF43, - LY_REGISTER = 0xFF44, - LYC_REGISTER = 0xFF45, - - BGP_REGISTER = 0xFF47, - OBP0_REGISTER = 0xFF48, - OBP1_REGISTER = 0xFF49, - - WY_REGISTER = 0xFF4A, - WX_REGISTER = 0xFF4B, - - KEY1_REGISER = 0xFF4D, - - DMA_REGISTER = 0xFF46, - - VBK_REGISTER = 0xFF4F, ///< VRAM Bank Selection - - HDMA1 = 0xFF51, ///< New D -*/ - -#ifndef GAMEBOYCORE_MMU_H -#define GAMEBOYCORE_MMU_H - -#include "gameboycore/gameboycore_api.h" -#include "gameboycore/time.h" - -#include -#include -#include -#include - -namespace gb -{ - /** - \class MMU - \brief Memory interface - \ingroup API - */ - class GAMEBOYCORE_API MMU - { - public: - //! Smart pointer type - using Ptr = std::unique_ptr; - - using MemoryWriteHandler = std::function; - using MemoryReadHandler = std::function; - - public: - MMU(const uint8_t* rom, uint32_t size); - MMU(const MMU&) = delete; - ~MMU(); - - /** - @return the value of memory at the psecified location - */ - uint8_t read(uint16_t) const; - uint8_t read(uint16_t); - /** - Write a byte to the specified location - */ - void write(uint8_t value, uint16_t addr); - /** - Write a word to the specified location - */ - void write(uint16_t value, uint16_t addr); - - /** - */ - uint8_t readVram(uint16_t addr, uint8_t bank); - - /** - tranfer `n` bytes from `src` to `dest` - */ - void dma(uint16_t dest, uint16_t src, uint16_t n); - - /** - Add a IO write handler - - \param addr IO address - \handler the handler - */ - void addWriteHandler(uint16_t addr, MemoryWriteHandler handler); - - /** - Add an IO read handler - - \param addr IO address - \param handler the handler - */ - void addReadHandler(uint16_t addr, MemoryReadHandler handler); - - /** - \return Battery RAM - */ - std::vector getBatteryRam() const; - - /** - Set battery RAM - - \param buffer containing battery RAM - */ - void setBatteryRam(const std::vector& battery_ram); - - /** - Set the time to be read from the RTC register (MBC3) - */ - void setTimeProvider(const TimeProvider provider); - - /** - Check if OAM transfer has occured - */ - bool getOamTransferStatus() const; - - /** - @return true if rom is CGB compatible - */ - bool cgbEnabled() const; - - /** - \return a reference to a memory location - */ - uint8_t& get(uint16_t); - - /** - \return point to memory location - */ - uint8_t* getptr(uint16_t); - - /** - Get the virtual memory location from the logical address - */ - int resolveAddress(const uint16_t& addr) const; - - /** - Get size of virtual memory - */ - std::size_t getVirtualMemorySize() const; - - private: - class Impl; - Impl* impl_; - }; -} - -#endif // GAMEBOY_MMU_H diff --git a/components/gameboycore/include/gameboycore/oam.h b/components/gameboycore/include/gameboycore/oam.h deleted file mode 100644 index 84767ef9..00000000 --- a/components/gameboycore/include/gameboycore/oam.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef GAMEBOYCORE_OAM_H -#define GAMEBOYCORE_OAM_H - -#include "gameboycore/mmu.h" -#include "gameboycore/memorymap.h" -#include "gameboycore/sprite.h" - -#include - -namespace gb -{ - /** - \brief Access Gameboy Object Attribute Memory - */ - class OAM - { - public: - OAM(MMU& mmu); - ~OAM(); - - Sprite getSprite(std::size_t idx) const; - std::array getSprites() const; - - private: - MMU& mmu_; - }; -} - -#endif // GAMEBOYCORE_OAM_H diff --git a/components/gameboycore/include/gameboycore/opcode_cycles.h b/components/gameboycore/include/gameboycore/opcode_cycles.h deleted file mode 100644 index 10647822..00000000 --- a/components/gameboycore/include/gameboycore/opcode_cycles.h +++ /dev/null @@ -1,70 +0,0 @@ - -/** - \brief Contains all opcode cycle information - \author Natesh Narain - \date Oct 30, 2016 -*/ - -#ifndef GAMEBOYCORE_OPCODE_CYCLES_H -#define GAMEBOYCORE_OPCODE_CYCLES_H - -#include - -static constexpr uint8_t opcode_page1[256] = { - 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1, - 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1, - 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1, - 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4, - 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4, - 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4, - 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4 -}; - -static constexpr uint8_t opcode_page1_branch[256] = { - 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1, - 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1, - 3,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1, - 3,3,2,2,3,3,3,1,3,2,2,2,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, - 5,3,4,4,6,4,2,4,5,4,4,0,6,6,2,4, - 5,3,4,0,6,4,2,4,5,4,4,0,6,0,2,4, - 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4, - 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4, -}; - -static constexpr uint8_t opcode_page2[256] = { - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, - 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, - 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, - 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, - 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 -}; - -#endif // GAMEBOY_OPCODE_CYCLES_H diff --git a/components/gameboycore/include/gameboycore/opcodeinfo.h b/components/gameboycore/include/gameboycore/opcodeinfo.h deleted file mode 100644 index 7e76812e..00000000 --- a/components/gameboycore/include/gameboycore/opcodeinfo.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - * \file opcodeinfo.h - * \brief Define opcode metadata - * \author Natesh Narain -*/ -#ifndef GAMEBOYCORE_OPCODEINFO_H -#define GAMEBOYCORE_OPCODEINFO_H - -#include - -namespace gb -{ - /** - Which page of instructions - */ - enum class OpcodePage - { - PAGE1, PAGE2 - }; - - enum class OperandType - { - NONE, // No operands - IMM8, // 8 bit immediate data - IMM16 // 16 bit immediate data - }; - - /** - Struct containing metadata - */ - struct OpcodeInfo - { - uint8_t cycles; - const char *disassembly; - OperandType userdata; - - OpcodeInfo(uint8_t cycles, const char* disassembly, OperandType userdata = OperandType::NONE) - : cycles{cycles} - , disassembly{disassembly} - , userdata{userdata} - { - } - }; - - OpcodeInfo getOpcodeInfo(uint8_t opcode, OpcodePage page); -} - -#endif // GAMEBOY_OPCODEINFO_H diff --git a/components/gameboycore/include/gameboycore/palette.h b/components/gameboycore/include/gameboycore/palette.h deleted file mode 100644 index 6dfd7012..00000000 --- a/components/gameboycore/include/gameboycore/palette.h +++ /dev/null @@ -1,59 +0,0 @@ -/** - * \file palette.h - * \author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_PALETTE_H -#define GAMEBOYCORE_PALETTE_H - -#include "gameboycore/pixel.h" - -#include -#include - -namespace gb -{ - class Palette - { - public: - Palette() - { - reset(); - } - - std::array get(uint8_t reg) - { - std::array palette; - - auto color3 = (reg & 0xC0) >> 6; - auto color2 = (reg & 0x30) >> 4; - auto color1 = (reg & 0x0C) >> 2; - auto color0 = (reg & 0x03); - - palette[3] = colors_[color3]; - palette[2] = colors_[color2]; - palette[1] = colors_[color1]; - palette[0] = colors_[color0]; - - return palette; - } - - void reset() - { - colors_[0] = Pixel(255); - colors_[1] = Pixel(192); - colors_[2] = Pixel(96); - colors_[3] = Pixel(0); - } - - void set(uint8_t r, uint8_t g, uint8_t b, int idx) - { - colors_[idx] = Pixel(r, g, b); - } - - private: - std::array colors_; - }; -} - -#endif // GAMEBOYCORE_PALETTE_H diff --git a/components/gameboycore/include/gameboycore/pixel.h b/components/gameboycore/include/gameboycore/pixel.h deleted file mode 100644 index 88b0c65c..00000000 --- a/components/gameboycore/include/gameboycore/pixel.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * \file pixel.h - * \author Natesh Narain - * \date Oct 23 2016 -*/ - -#ifndef GAMEBOYCORE_PIXEL_H -#define GAMEBOYCORE_PIXEL_H - -#include - -namespace gb -{ - //! Pixel type - struct Pixel - { - Pixel() : - r(0), g(0), b(0) - { - } - - Pixel(uint8_t r, uint8_t g, uint8_t b) : - r(r), g(g), b(b) - { - } - - explicit Pixel(uint8_t v) : r(v), g(v), b(v) - { - } - - uint8_t r; ///< Red - uint8_t g; ///< Green - uint8_t b; ///< Blue - - bool operator==(const Pixel& rhs) - { - return this->r == rhs.r && this->g == rhs.g && this->b == rhs.b; - } - }; -} - -#endif // GAMEBOYCORE_PIXEL_H diff --git a/components/gameboycore/include/gameboycore/sprite.h b/components/gameboycore/include/gameboycore/sprite.h deleted file mode 100644 index 7917bc02..00000000 --- a/components/gameboycore/include/gameboycore/sprite.h +++ /dev/null @@ -1,77 +0,0 @@ - -/** - @author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_SPRITE_H -#define GAMEBOYCORE_SPRITE_H - -#include - -namespace gb -{ - /** - Sprite object that are stored in OAM - */ - class Sprite - { - public: - uint8_t y; ///< y pixel coordinate - uint8_t x; ///< x pixel coordinate - uint8_t tile; ///< tile number - uint8_t attr; ///< attribute data - - uint8_t height; ///< sprite height in pixels - - Sprite() - : y{0} - , x{0} - , tile{0} - , attr{0} - , height{0} - { - } - - bool isHorizontallyFlipped() const - { - return (attr & (1 << 5)) != 0; - } - - bool isVerticallyFlipped() const - { - return (attr & (1 << 6)) != 0; - } - - bool hasPriority() const - { - return (attr & (1 << 7)) == 0; - } - - uint8_t paletteOBP0() const - { - return !!(attr & (1 << 4)); - } - - uint8_t getCgbPalette() const - { - return (attr & 0x07); - } - - uint8_t getCharacterBank() const - { - return !!(attr & (1 << 3)); - } - - bool operator==(const Sprite& rhs) - { - return - this->y == rhs.y && - this->x == rhs.x && - this->tile == rhs.tile && - this->attr == rhs.attr && - this->height == rhs.height; - } - }; -} - -#endif // GAMEBOYCORE_SPRITE_H diff --git a/components/gameboycore/include/gameboycore/tile.h b/components/gameboycore/include/gameboycore/tile.h deleted file mode 100644 index 3083c2d9..00000000 --- a/components/gameboycore/include/gameboycore/tile.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef GAMEBOYCORE_TILE_H -#define GAMEBOYCORE_TILE_H - -#include - -namespace gb -{ - struct Tile - { - uint8_t color[64]; - }; -} - -#endif // GAMEBOYCORE_TILE_H diff --git a/components/gameboycore/include/gameboycore/tilemap.h b/components/gameboycore/include/gameboycore/tilemap.h deleted file mode 100644 index cb6ae4fa..00000000 --- a/components/gameboycore/include/gameboycore/tilemap.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - \file tilemap.h - \author Natesh Narain - \date Sept 15 2016 -*/ - -#ifndef GAMEBOYCORE_TILEMAP_H -#define GAMEBOYCORE_TILEMAP_H - -#include "gameboycore/tileram.h" -#include "gameboycore/pixel.h" -#include "gameboycore/sprite.h" -#include "gameboycore/palette.h" -#include "gameboycore/memorymap.h" - -#include -#include -#include - -namespace gb -{ - namespace detail - { - /** - \brief Class that knows how to render background map data - */ - class TileMap - { - public: - /* Map types */ - enum class Map - { - BACKGROUND = (1 << 3), - WINDOW_OVERLAY = (1 << 6) - }; - - using Line = std::array; - - public: - - TileMap(MMU& mmu, Palette& palette); - ~TileMap(); - - Line getBackground(int line, bool cgb_enable); - Line getWindowOverlay(int line); - - void drawSprites(std::array& scanline, Line& color_line, int line, bool cgb_enable, std::array,8>& cgb_palette); - - std::array getSpriteCache() const; - std::vector getBackgroundTileMap(); - - std::size_t hashBackground(); - - private: - uint16_t getAddress(Map map) const; - void forEachBackgroundTile(std::function fn); - - private: - detail::TileRAM tileram_; - MMU& mmu_; - uint8_t& scx_; - uint8_t& scy_; - Palette& palette_; - - std::array sprite_cache_; - }; - } -} - -#endif // GAMEBOYCORE_TILEMAP_H diff --git a/components/gameboycore/include/gameboycore/tileram.h b/components/gameboycore/include/gameboycore/tileram.h deleted file mode 100644 index b5a0d7e2..00000000 --- a/components/gameboycore/include/gameboycore/tileram.h +++ /dev/null @@ -1,63 +0,0 @@ - -/** - @author Natesh Narain -*/ - - -#ifndef GAMEBOYCORE_DISPLAY_H -#define GAMEBOYCORE_DISPLAY_H - -#include "gameboycore/mmu.h" -#include "gameboycore/tile.h" -#include "gameboycore/sprite.h" - -#include -#include -#include - -namespace gb -{ - namespace detail - { - /** - \brief Class that knows how to read Tile RAM in the Gameboy memory map - */ - class TileRAM - { - public: - static const unsigned int NUM_TILES = 192; - static const unsigned int TILE_SIZE = 16; - - using TileRow = std::array; - using TileLine = std::array; - - public: - // TODO: remove this constructor - TileRAM(MMU& mmu); - ~TileRAM(); - - Tile getSpriteTile(const Sprite& sprite) const; - std::vector getTiles(); - - TileRow getRow(int row, uint8_t tilenum, bool umode, uint8_t character_bank = 0); - - template - uint16_t getTileAddress(int32_t base_addr, uint8_t tilenum) const - { - return (uint16_t)(base_addr + ((T)tilenum * TILE_SIZE)); - } - - private: - void setRow(Tile& tile, uint8_t msb, uint8_t lsb, int row) const; - - Tile flipV(const Tile& old) const; - Tile flipH(const Tile& old) const; - - private: - uint8_t* tile_ram_; - MMU& mmu_; - }; - } -} - -#endif // GAMEBOYCORE_DISPLAY_H diff --git a/components/gameboycore/include/gameboycore/time.h b/components/gameboycore/include/gameboycore/time.h deleted file mode 100644 index 64bee6c3..00000000 --- a/components/gameboycore/include/gameboycore/time.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef GAMEBOYCORE_TIME_H -#define GAMEBOYCORE_TIME_H - -#include -#include - -namespace gb -{ - struct Time - { - Time(uint8_t s, uint8_t m, uint8_t h, uint16_t d) - : seconds{ s } - , minutes{ m } - , hours{ h } - , days{ d } - { - } - - Time() : Time{0,0,0,0} - { - } - - uint8_t seconds; - uint8_t minutes; - uint8_t hours; - uint16_t days; - }; - - using TimeProvider = std::function; -} - -#endif // GAMEBOYCORE_TIME_H diff --git a/components/gameboycore/include/gameboycore/timer.h b/components/gameboycore/include/gameboycore/timer.h deleted file mode 100644 index 32be8b57..00000000 --- a/components/gameboycore/include/gameboycore/timer.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * \file timer.h - * \author Natesh Narain - * \date Oct 15 2016 -*/ - -#ifndef GAMEBOY_TIMER_H -#define GAMEBOY_TIMER_H - -#include "gameboycore/mmu.h" -#include "gameboycore/memorymap.h" -#include "gameboycore/interrupt_provider.h" - -#include -#include - -namespace gb -{ - /** - \brief Opcode accurate timer - */ - class Timer - { - public: - Timer(MMU& mmu); - ~Timer(); - - void update(const uint8_t cycles); - - private: - void tick(); - - uint8_t& controller_; // TAC - uint8_t& counter_; // TIMA - uint8_t& modulo_; // TMA - uint8_t& divider_; // DIV - - int t_clock_; - int base_clock_; - int div_clock_; - - InterruptProvider timer_interrupt_; - }; -} - -#endif // GAMEBOYCORE_TIMER_H diff --git a/components/gameboycore/src/alu.cpp b/components/gameboycore/src/alu.cpp deleted file mode 100644 index 191cc0c0..00000000 --- a/components/gameboycore/src/alu.cpp +++ /dev/null @@ -1,150 +0,0 @@ - -#include "gameboycore/alu.h" - -#include "bitutil.h" - -namespace gb -{ - ALU::ALU(uint8_t& flags) : - flags_(flags) - { - } - - void ALU::add(uint8_t& a, uint8_t n) - { - bool is_half_carry = isHalfCarry(a, n); - bool is_full_carry = isFullCarry(a, n); - - a += n; - - setFlag(ALU::Flags::H, is_half_carry); - setFlag(ALU::Flags::C, is_full_carry); - setFlag(ALU::Flags::Z, (a == 0)); - setFlag(ALU::Flags::N, false); - } - - void ALU::add(uint16_t& hl, uint16_t n) - { - bool is_half_carry = isHalfCarry16(hl, n); - bool is_full_carry = isFullCarry16(hl, n); - - hl += n; - - setFlag(ALU::Flags::H, is_half_carry); - setFlag(ALU::Flags::C, is_full_carry); - setFlag(ALU::Flags::N, false); - } - - void ALU::addr(uint16_t& sp, int8_t n) - { - int result = sp + n; - - setFlag(Flags::C, ((sp ^ n ^ (result & 0xFFFF)) & 0x100) == 0x100); - setFlag(Flags::H, ((sp ^ n ^ (result & 0xFFFF)) & 0x10) == 0x10); - - sp = (uint16_t)result; - - setFlag(ALU::Flags::Z, false); - setFlag(ALU::Flags::N, false); - } - - void ALU::addc(uint8_t& a, uint8_t n) - { - int carry = (isSet(flags_, ALU::Flags::C)) ? 1 : 0; - - int result = (int)a + (int)n + carry; - - setFlag(ALU::Flags::H, ((a & 0x0F) + (n & 0x0F) + carry) > 0x0F); - setFlag(ALU::Flags::C, result > 0xFF); - setFlag(ALU::Flags::Z, ((uint8_t)result == 0)); - setFlag(ALU::Flags::N, false); - - a = (uint8_t)result; - } - - void ALU::sub(uint8_t& a, uint8_t n) - { - bool is_half_borrow = isHalfBorrow(a, n); - bool is_full_borrow = isFullBorrow(a, n); - - a -= n; - - setFlag(ALU::Flags::H, is_half_borrow); - setFlag(ALU::Flags::C, is_full_borrow); - setFlag(ALU::Flags::Z, (a == 0)); - setFlag(ALU::Flags::N, true); - } - - void ALU::subc(uint8_t& a, uint8_t n) - { - int carry = (isSet(flags_, ALU::Flags::C)) ? 1 : 0; - int result = (int)a - n - carry; - - setFlag(ALU::Flags::C, result < 0); - setFlag(ALU::Flags::H, ((a & 0x0F) - (n & 0x0F) - carry) < 0); - - a = (uint8_t)result; - - setFlag(ALU::Flags::Z, (a == 0)); - setFlag(ALU::Flags::N, true); - } - - void ALU::anda(uint8_t& a, uint8_t n) - { - a &= n; - - setFlag(ALU::Flags::N, false); - setFlag(ALU::Flags::H, true); - setFlag(ALU::Flags::C, false); - setFlag(ALU::Flags::Z, (a == 0)); - } - - void ALU::ora(uint8_t& a, uint8_t n) - { - a |= n; - - setFlag(ALU::Flags::N, false); - setFlag(ALU::Flags::Z, (a == 0)); - setFlag(ALU::Flags::H, false); - setFlag(ALU::Flags::C, false); - } - - void ALU::xora(uint8_t& a, uint8_t n) - { - a ^= n; - - setFlag(ALU::Flags::Z, (a == 0)); - setFlag(ALU::Flags::N, false); - setFlag(ALU::Flags::H, false); - setFlag(ALU::Flags::C, false); - } - - void ALU::compare(uint8_t& a, uint8_t n) - { - bool is_half_borrow = isHalfBorrow(a, n); - bool is_full_borrow = isFullBorrow(a, n); - - uint8_t r = a - n; - - setFlag(ALU::Flags::H, is_half_borrow); - setFlag(ALU::Flags::C, is_full_borrow); - setFlag(ALU::Flags::Z, (r == 0)); - setFlag(ALU::Flags::N, true); - } - - void ALU::setFlag(uint8_t mask, bool set) - { - if (set) - { - setMask(flags_, mask); - } - else - { - clearMask(flags_, mask); - } - } - - ALU::~ALU() - { - } -} diff --git a/components/gameboycore/src/apu.cpp b/components/gameboycore/src/apu.cpp deleted file mode 100644 index 69467a47..00000000 --- a/components/gameboycore/src/apu.cpp +++ /dev/null @@ -1,507 +0,0 @@ - -/** - \author Natesh Narain -*/ - -#include "gameboycore/apu.h" -#include "gameboycore/memorymap.h" -#include "gameboycore/detail/audio/square_wave_channel.h" -#include "gameboycore/detail/audio/wave_channel.h" -#include "gameboycore/detail/audio/noise_channel.h" - -#include "bitutil.h" - -#include -#include -#include - -namespace gb -{ - /* Private Interface */ - - class APU::Impl - { - //! Cycles for 512 Hz with ~4.2 MHz clock - static constexpr unsigned int CYCLES_512HZ = 8192; - //! APU down sampling rate (CPU clock / sample rate of host system) - static constexpr unsigned int DOWNSAMPLE_RATE = 4200000 / 44100; - //! Starting address of the APU registers - static constexpr uint16_t APU_REG_BASE = memorymap::NR10_REGISTER; - - public: - explicit Impl(MMU::Ptr& mmu) : - mmu_(mmu), - square1_(true), - square2_(false), - frame_sequencer_counter_(CYCLES_512HZ), - frame_sequencer_(0), - down_sample_counter_(0) - { - // intercept all read/write attempts here - for (uint16_t i = memorymap::NR10_REGISTER; i <= memorymap::WAVE_PATTERN_RAM_END; ++i) - { - mmu->addReadHandler(i, std::bind(&Impl::read, this, std::placeholders::_1)); - mmu->addWriteHandler(i, std::bind(&Impl::write, this, std::placeholders::_1, std::placeholders::_2)); - } - - // init register memory - std::fill(apu_registers.begin(), apu_registers.end(), (uint8_t)0); - - // set extra read bits - initExtraBits(); - } - - /** - update with cycles - */ - void update(uint8_t cycles) - { - // ignore if apu is disabled - if (!isEnabled()) return; - - while (cycles--) - { - // frame sequencer clock - if (frame_sequencer_counter_-- <= 0) - { - frame_sequencer_counter_ = CYCLES_512HZ; - - clockFrameSequencer(); - } - - // run channel logic - square1_.step(); - square2_.step(); - wave_.step(); - noise_.step(); - - // down sampling is required since the APU can generate audio at a rate faster than the host system will play - if (--down_sample_counter_ == 0) - { - down_sample_counter_ = DOWNSAMPLE_RATE; - - // generate left and right audio samples - mixVolumes(); - } - } - } - - uint8_t getSound1Volume() const noexcept - { - return square1_.getVolume(); - } - - uint8_t getSound2Volume() const noexcept - { - return square2_.getVolume(); - } - - uint8_t getSound3Volume() const noexcept - { - return wave_.getVolume(); - } - - uint8_t getSound4Volume() const noexcept - { - return noise_.getVolume(); - } - - void setAudioSampleCallback(AudioSampleCallback callback) - { - send_audio_sample_ = callback; - } - - private: - - void clockFrameSequencer() - { - switch (frame_sequencer_) - { - case 0: - case 2: - clockLength(); - square1_.clockSweep(); - break; - case 4: - clockLength(); - break; - case 6: - clockLength(); - square1_.clockSweep(); - break; - case 7: - clockVolume(); - break; - } - - frame_sequencer_++; - - if (frame_sequencer_ >= 8) - { - frame_sequencer_ = 0; - } - } - - void mixVolumes() - { - static constexpr float AMPLITUDE = 30000; - - // convert sound output between [0, 1] - const auto sound1 = (float)square1_.getVolume() / 15.f; - const auto sound2 = (float)square2_.getVolume() / 15.f; - const auto sound3 = (float)wave_.getVolume() / 15.f; - const auto sound4 = (float)noise_.getVolume() / 15.f; - - float left_sample = 0; - float right_sample = 0; - - // add left channel contributions - if (channel_left_enabled_[0]) - left_sample += sound1; - if (channel_left_enabled_[1]) - left_sample += sound2; - if (channel_left_enabled_[2]) - left_sample += sound3; - if (channel_left_enabled_[3]) - left_sample += sound4; - - // add right channel contributions - if (channel_right_enabled_[0]) - right_sample += sound1; - if (channel_right_enabled_[1]) - right_sample += sound2; - if (channel_right_enabled_[2]) - right_sample += sound3; - if (channel_right_enabled_[3]) - right_sample += sound4; - - // average the totals - left_sample /= 4.0f; - right_sample /= 4.0f; - - // volume per channel between [0, 1] - const auto right_volume = ((float)right_volume_) / 7.f; - const auto left_volume = ((float)left_volume_) / 7.f; - - // generate a sample - const auto left = (int16_t)(left_sample * left_volume * AMPLITUDE); - const auto right = (int16_t)(right_sample * right_volume * AMPLITUDE); - - // send the samples to the host system - if (send_audio_sample_) - send_audio_sample_(left, right); - } - - void clockLength() noexcept - { - square1_.clockLength(); - square2_.clockLength(); - wave_.clockLength(); - noise_.clockLength(); - } - - void clockVolume() noexcept - { - square1_.clockVolume(); - square2_.clockVolume(); - noise_.clockVolume(); - } - - bool isEnabled() const noexcept - { - return isBitSet(apuRead(memorymap::NR52_REGISTER), 7) != 0; - } - - uint8_t read(uint16_t addr) - { - uint8_t value = 0; - - const auto& extras = extra_bits_[addr - APU_REG_BASE]; - - if (addr == memorymap::NR52_REGISTER) - { - value = apuRead(addr) & 0xF0; - - value |= square1_.isEnabled() << 0; - value |= square2_.isEnabled() << 1; - value |= wave_.isEnabled() << 2; - value |= noise_.isEnabled() << 3; - } - else - { - if (addr >= memorymap::NR10_REGISTER && addr <= memorymap::NR14_REGISTER) - { - value = square1_.read(addr - memorymap::NR10_REGISTER); - } - else if (addr >= memorymap::NR20_REGISTER && addr <= memorymap::NR24_REGISTER) - { - value = square2_.read(addr - memorymap::NR20_REGISTER); - } - else if (addr >= memorymap::NR30_REGISTER && addr <= memorymap::NR34_REGISTER) - { - value = wave_.read(addr - memorymap::NR30_REGISTER); - } - else if (addr >= memorymap::WAVE_PATTERN_RAM_START && addr <= memorymap::WAVE_PATTERN_RAM_END) - { - value = wave_.readWaveRam(addr); - } - else if (addr >= memorymap::NR41_REGISTER && addr <= memorymap::NR44_REGISTER) - { - value = noise_.read(addr - memorymap::NR41_REGISTER); - } - else if (addr >= memorymap::NR50_REGISTER && addr <= memorymap::NR52_REGISTER) - { - value = apuRead(addr); - } - } - - return value | extras; - } - - void write(uint8_t value, uint16_t addr) - { - if (addr == memorymap::NR52_REGISTER) - { - // check if APU is being disabled - if (isClear(value, 0x80)) - { - clearRegisters(); - - square1_.disable(); - square2_.disable(); - wave_.disable(); - noise_.disable(); - - frame_sequencer_ = 0; - } - - // check is being enabled - if (!isEnabled() && isSet(value, 0x80)) - { - frame_sequencer_counter_ = CYCLES_512HZ; - } - - apuWrite(value, addr); - } - else if (addr == memorymap::NR50_REGISTER && isEnabled()) - { - right_volume_ = value & 0x07; - right_enabled_ = (value & 0x08) != 0; - - left_volume_ = (value & 0x70) >> 4; - left_enabled_ = (value & 0x80) != 0; - - apuWrite(value, addr); - } - else if (addr == memorymap::NR51_REGISTER && isEnabled()) - { - channel_right_enabled_[0] = (value & 0x01) != 0; - channel_right_enabled_[1] = (value & 0x02) != 0; - channel_right_enabled_[2] = (value & 0x04) != 0; - channel_right_enabled_[3] = (value & 0x08) != 0; - channel_left_enabled_[0] = (value & 0x10) != 0; - channel_left_enabled_[1] = (value & 0x20) != 0; - channel_left_enabled_[2] = (value & 0x40) != 0; - channel_left_enabled_[3] = (value & 0x80) != 0; - - apuWrite(value, addr); - } - else - { - if (isEnabled()) - { - if (addr >= memorymap::NR10_REGISTER && addr <= memorymap::NR14_REGISTER) - { - square1_.write(value, addr - memorymap::NR10_REGISTER); - } - else if (addr >= memorymap::NR20_REGISTER && addr <= memorymap::NR24_REGISTER) - { - square2_.write(value, addr - memorymap::NR20_REGISTER); - } - else if (addr >= memorymap::NR30_REGISTER && addr <= memorymap::NR34_REGISTER) - { - wave_.write(value, addr - memorymap::NR30_REGISTER); - } - else if (addr >= memorymap::WAVE_PATTERN_RAM_START && addr <= memorymap::WAVE_PATTERN_RAM_END) - { - wave_.writeWaveRam(value, addr); - } - else if (addr >= memorymap::NR41_REGISTER && addr <= memorymap::NR44_REGISTER) - { - noise_.write(value, addr - memorymap::NR41_REGISTER); - } - } - } - } - - uint8_t apuRead(uint16_t addr) const noexcept - { - return apu_registers[addr - APU_REG_BASE]; - } - - void apuWrite(uint8_t value, uint16_t addr) noexcept - { - apu_registers[addr - APU_REG_BASE] = value; - } - - void clearRegisters() - { - for (auto addr = APU_REG_BASE; addr < memorymap::WAVE_PATTERN_RAM_START; ++addr) - { - if (addr == memorymap::NR52_REGISTER) continue; - mmu_->write((uint8_t)0, addr); - } - } - - void initExtraBits() noexcept - { - // NR10 - NR14 - extra_bits_[0x00] = 0x80; - extra_bits_[0x01] = 0x3F; - extra_bits_[0x02] = 0x00; - extra_bits_[0x03] = 0xFF; - extra_bits_[0x04] = 0xBF; - - // NR20 - NR24 - extra_bits_[0x05] = 0xFF; - extra_bits_[0x06] = 0x3F; - extra_bits_[0x07] = 0x00; - extra_bits_[0x08] = 0xFF; - extra_bits_[0x09] = 0xBF; - - // NR30 - NR34 - extra_bits_[0x0A] = 0x7F; - extra_bits_[0x0B] = 0xFF; - extra_bits_[0x0C] = 0x9F; - extra_bits_[0x0D] = 0xFF; - extra_bits_[0x0E] = 0xBF; - - // NR40 - NR44 - extra_bits_[0x0F] = 0xFF; - extra_bits_[0x10] = 0xFF; - extra_bits_[0x11] = 0x00; - extra_bits_[0x12] = 0x00; - extra_bits_[0x13] = 0xBF; - - // NR50 - NR52 - extra_bits_[0x14] = 0x00; - extra_bits_[0x15] = 0x00; - extra_bits_[0x16] = 0x70; - - // - extra_bits_[0x17] = 0xFF; - extra_bits_[0x18] = 0xFF; - extra_bits_[0x19] = 0xFF; - extra_bits_[0x1A] = 0xFF; - extra_bits_[0x1B] = 0xFF; - extra_bits_[0x1C] = 0xFF; - extra_bits_[0x1D] = 0xFF; - extra_bits_[0x1E] = 0xFF; - extra_bits_[0x1F] = 0xFF; - - // wave ram - extra_bits_[0x20] = 0x00; - extra_bits_[0x21] = 0x00; - extra_bits_[0x22] = 0x00; - extra_bits_[0x23] = 0x00; - extra_bits_[0x24] = 0x00; - extra_bits_[0x25] = 0x00; - extra_bits_[0x26] = 0x00; - extra_bits_[0x27] = 0x00; - extra_bits_[0x28] = 0x00; - extra_bits_[0x29] = 0x00; - extra_bits_[0x2A] = 0x00; - extra_bits_[0x2B] = 0x00; - extra_bits_[0x2C] = 0x00; - extra_bits_[0x2D] = 0x00; - extra_bits_[0x2E] = 0x00; - extra_bits_[0x2F] = 0x00; - } - - private: - MMU::Ptr& mmu_; - - //! Sound 1 - Square wave with Sweep - detail::SquareWaveChannel square1_; - //! Sound 2 - Square wave - detail::SquareWaveChannel square2_; - //! Sound 3 - Wave from wave ram - detail::WaveChannel wave_; - //! Sound 4 - Noise Channel - detail::NoiseChannel noise_; - - //! callback to host when an audio sample is computed - AudioSampleCallback send_audio_sample_; - - //! APU cycle counter - int frame_sequencer_counter_; - - //! APU internal timer - int frame_sequencer_; - - //! APU registers - std::array apu_registers; - - //! left volume - uint8_t left_volume_; - //! left enabled - bool left_enabled_; - //! right volume - uint8_t right_volume_; - //! right enabled - bool right_enabled_; - - //! left channel enables - bool channel_left_enabled_[4]; - //! right channel enables - bool channel_right_enabled_[4]; - - //! - uint8_t down_sample_counter_; - - //! bits that are ORed into the value when read - std::array extra_bits_; - }; - - /* Public Interface */ - - APU::APU(MMU::Ptr& mmu) : - impl_(new Impl(mmu)) - { - } - - void APU::update(uint8_t cycles) - { - impl_->update(cycles); - } - - uint8_t APU::getSound1Volume() - { - return impl_->getSound1Volume(); - } - - uint8_t APU::getSound2Volume() - { - return impl_->getSound2Volume(); - } - - uint8_t APU::getSound3Volume() - { - return impl_->getSound3Volume(); - } - - uint8_t APU::getSound4Volume() - { - return impl_->getSound4Volume(); - } - - void APU::setAudioSampleCallback(AudioSampleCallback callback) - { - impl_->setAudioSampleCallback(callback); - } - - APU::~APU() - { - delete impl_; - } -} \ No newline at end of file diff --git a/components/gameboycore/src/bitutil.h b/components/gameboycore/src/bitutil.h deleted file mode 100644 index c1c44666..00000000 --- a/components/gameboycore/src/bitutil.h +++ /dev/null @@ -1,158 +0,0 @@ - -/** - Bit operation macros - - @author Natesh Narain -*/ - -#ifndef BITUTIL_H -#define BITUTIL_H - -//! Bit value -template -inline T bv(T b) -{ - return 1 << b; -} - -//! set mask y in x -template -inline void setMask(Tx& x, Ty y) noexcept -{ - x |= (Tx)y; -} - -//! clear mask y in x -template -inline void clearMask(Tx& x, Ty y) noexcept -{ - x &= ~((Tx)y); -} -//! toggle mask y in x -template -inline void toggleMask(Tx& x, Ty y) noexcept -{ - x ^= (Tx)y; -} - -//! set bit y in x -template -inline void setBit(Tx& x, Ty y) noexcept -{ - setMask(x, bv(y)); -} -//! clear bit y in x -template -inline void clearBit(Tx& x, Ty y) noexcept -{ - clearMask(x, bv(y)); -} -//! toggle bit y in x -template -inline void toggleBit(Tx& x, Ty y) noexcept -{ - toggleMask(x, bv(y)); -} - -//! -template -inline Tx lownybble(const Tx& x) noexcept -{ - return x & 0x0F; -} - -//! -template -inline T highnybble(const T& t) noexcept -{ - return (t >> 4); -} - -//! Create a WORD -template -inline uint16_t word(const Tx& hi, const Ty& lo) noexcept -{ - return ((hi & 0xFFFF) << 8) | (lo & 0xFFFF); -} - -//! Get bit -template -inline Tx getBit(const Tx& x, const Ty& n) noexcept -{ - return !!(x & bv(n)); -} - -//! check if mask y is set in x -template -inline bool isSet(const Tx& x, const Ty& y) noexcept -{ - return (x & y) != 0; -} - -//! check if mask y is clear in x -template -inline bool isClear(const Tx& x, const Ty& y) noexcept -{ - return !(x & y); -} -//! check if bit y is set in x -//#define isBitSet(x,y) ( isSet(x, bv(y)) ) -template -inline bool isBitSet(const Tx& x, const Ty& y) noexcept -{ - return isSet(x, bv(y)); -} -//! check if bit y is clear in x -template -inline bool isBitClear(const Tx& x, const Ty& y) noexcept -{ - return isClear(x, bv(y)); -} - -/* Full and Half Carry */ - -template -inline bool isHalfCarry(const Tx& x, const Ty& y) noexcept -{ - return (((x & 0x0F) + (y & 0x0F)) & 0x10) != 0; -} - -template -inline bool isFullCarry(const Tx& x, const Ty& y) noexcept -{ - return (((x & 0x0FF) + (y & 0x0FF)) & 0x100) != 0; -} - -template -inline bool isHalfCarry16(const Tx& x, const Ty& y) noexcept -{ - return ((((x & 0x0FFF) + (y & 0x0FFF)) & 0x1000) != 0); -} - -template -inline bool isFullCarry16(const Tx& x, const Ty& y) noexcept -{ - return ((((x & 0x0FFFF) + ((y & 0x0FFFF))) & 0x10000) != 0); -} - -template -inline bool isHalfBorrow(const Tx& x, const Ty& y) noexcept -{ - return ((x & 0x0F) < (y & 0x0F)); -} -template -inline bool isFullBorrow(const Tx& x, const Ty& y) noexcept -{ - return ((x & 0xFF) < (y & 0xFF)); -} - -#endif // BITUTIL_H - - - - - - - - - diff --git a/components/gameboycore/src/cartinfo.cpp b/components/gameboycore/src/cartinfo.cpp deleted file mode 100644 index d99d587a..00000000 --- a/components/gameboycore/src/cartinfo.cpp +++ /dev/null @@ -1,30 +0,0 @@ - -#include "gameboycore/cartinfo.h" -#include "gameboycore/mbc.h" -#include - -namespace gb -{ - RomParser::RomParser() - { - } - - CartInfo RomParser::parse(const uint8_t* image) - { - CartInfo info; - info.type = image[memorymap::CART_TYPE]; - info.rom_size = image[memorymap::CART_ROM_SIZE]; - info.ram_size = image[memorymap::CART_RAM_SIZE]; - std::memcpy( - info.game_title, - &image[memorymap::GAME_TITLE_START], - memorymap::GAME_TITLE_END - memorymap::GAME_TITLE_START - ); - info.game_title[(memorymap::GAME_TITLE_END - memorymap::GAME_TITLE_START)] = '\0'; - - auto cgb_flag = image[memorymap::COLOR_COMPATABILITY]; - info.cgb_enabled = (cgb_flag == 0x80) || (cgb_flag == 0xC0); - - return info; - } -} diff --git a/components/gameboycore/src/cpu.cpp b/components/gameboycore/src/cpu.cpp deleted file mode 100644 index 65082010..00000000 --- a/components/gameboycore/src/cpu.cpp +++ /dev/null @@ -1,2476 +0,0 @@ - -#include "gameboycore/cpu.h" -#include "gameboycore/alu.h" -#include "gameboycore/timer.h" -#include "gameboycore/opcodeinfo.h" -#include "gameboycore/opcode_cycles.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "bitutil.h" -#include "shiftrotate.h" - -// check endianness -#if !defined(__BIGENDIAN__) && !defined(__LITTLEENDIAN__) -# error "Either __BIGENDIAN__ or __LITTLEENDIAN__ must be defined" -#endif - -namespace gb -{ - /* Private Interface */ - - class CPU::Impl - { - public: - union Register - { - struct { -#ifdef __LITTLEENDIAN__ - uint8_t lo; - uint8_t hi; -#else - uint8_t hi; - uint8_t lo; -#endif - }; - uint16_t val; - }; - - enum InterruptMask - { - VBLANK = 1 << 0, - LCDC_STAT = 1 << 1, - TIME_OVERFLOW = 1 << 2, - SERIAL_TRANSFER_COMPLETE = 1 << 3, - JOYPAD = 1 << 4 - }; - - enum class InterruptVector - { - VBLANK = 0x0040, - LCDC_STAT = 0x0048, - TIME_OVERFLOW = 0x0050, - SERIAL_TRANSFER_COMPLETE = 0x0058, - JOYPAD = 0x0060 - }; - - Impl(MMU::Ptr& mmu, GPU::Ptr& gpu, APU::Ptr& apu, Link::Ptr& link) - : af_{0} - , bc_{0} - , de_{0} - , hl_{0} - , sp_{0} - , pc_{0} - , mmu_{ mmu } - , gpu_{ gpu } - , apu_{ apu } - , link_{ link } - , alu_{ af_.lo } - , timer_{ *mmu.get() } - , halted_{ false } - , stopped_{ false } - , interrupt_master_enable_{ false } - , interrupt_master_enable_pending_{ -1 } - , interrupt_master_disable_pending_{ -1 } - , debug_mode_{ false } - , current_pc_{0} - , cycle_count_{ 0 } - , interrupt_flags_{ mmu_->get(memorymap::INTERRUPT_FLAG) } - , interrupt_enable_{ mmu_->get(memorymap::INTERRUPT_ENABLE) } - , cgb_enabled_{ mmu->cgbEnabled() } - { - } - - void step() - { - // set current PC for debugging - current_pc_ = pc_.val; - - cycle_count_ = 0; - - if (!halted_) - { - // fetch next opcode - uint8_t opcode = mmu_->read(pc_.val++); - - // $CB means decode from the second page of instructions - if (opcode != 0xCB) - { - // decode from first page - cycle_count_ += decode1(opcode); - } - else - { - // read the second page opcode - opcode = mmu_->read(pc_.val++); - // decode from second page - cycle_count_ += decode2(opcode); - } - } - else - { - cycle_count_ += 1; - } - - checkPowerMode(); - checkInterrupts(); - - auto cpu_cycles = cycle_count_ * 4; - auto instr_cycles = cycle_count_; - - if (!stopped_) - { - gpu_->update((uint8_t)cpu_cycles, interrupt_master_enable_); - apu_->update((uint8_t)cpu_cycles); - link_->update((uint8_t)cpu_cycles); - timer_.update((uint8_t)instr_cycles); - } - } - - uint8_t decode1(uint8_t opcode) - { - int cycles = -1; - - switch (opcode) - { - // NOP - case 0x00: - break; - // STOP - case 0x10: - stop(); - break; - - /* Load Instructions */ - - // 8 bit loads immediate - case 0x3E: // LD A,d8 - af_.hi = load8Imm(); - break; - case 0x06: // LD B,d8 - bc_.hi = load8Imm(); - break; - case 0x0E: // LD C,d8 - bc_.lo = load8Imm(); - break; - case 0x16: // LD D,d8 - de_.hi = load8Imm(); - break; - case 0x1E: // LD E,d8 - de_.lo = load8Imm(); - break; - case 0x26: // LD H,d8 - hl_.hi = load8Imm(); - break; - case 0x2E: // LD L,d8 - hl_.lo = load8Imm(); - break; - case 0x36: // LD (HL),d8 - mmu_->write(load8Imm(), hl_.val); - break; - - // load 16 bit immediate - case 0x01: // LD BC,d16 - bc_.val = load16Imm(); - break; - case 0x11: // LD DE,d16 - de_.val = load16Imm(); - break; - case 0x21: // LD HL,d16 - hl_.val = load16Imm(); - break; - case 0x31: // LD SP,d16 - sp_.val = load16Imm(); - break; - - // load A into memory - case 0x02: - mmu_->write(af_.hi, bc_.val); - break; - case 0x12: - mmu_->write(af_.hi, de_.val); - break; - - // load A from memory - case 0x0A: // LD A,(BC) - af_.hi = mmu_->read(bc_.val); - break; - case 0x1A: // LD A,(DE) - af_.hi = mmu_->read(de_.val); - break; - - // transfer (Register to register, memory to register) - case 0x40: // LD B,B - bc_.hi = bc_.hi; - break; - case 0x41: // LD B,C - bc_.hi = bc_.lo; - break; - case 0x42: // LD B,D - bc_.hi = de_.hi; - break; - case 0x43: // LD B,E - bc_.hi = de_.lo; - break; - case 0x44: // LD B,H - bc_.hi = hl_.hi; - break; - case 0x45: // LD B,L - bc_.hi = hl_.lo; - break; - case 0x46: // LD B,(HL) - bc_.hi = mmu_->read(hl_.val); - break; - case 0x47: // LD B,A - bc_.hi = af_.hi; - break; - case 0x48: // LD C,B - bc_.lo = bc_.hi; - break; - case 0x49: // LD C,C - bc_.lo = bc_.lo; - break; - case 0x4A: // LD C,D - bc_.lo = de_.hi; - break; - case 0x4B: // LD C,E - bc_.lo = de_.lo; - break; - case 0x4C: // LD C,H - bc_.lo = hl_.hi; - break; - case 0x4D: // LD C,L - bc_.lo = hl_.lo; - break; - case 0x4E: // LD C,(HL) - bc_.lo = mmu_->read(hl_.val); - break; - case 0x4F: // LD C,A - bc_.lo = af_.hi; - break; - - case 0x50: // LD D,B - de_.hi = bc_.hi; - break; - case 0x51: // LD D,C - de_.hi = bc_.lo; - break; - case 0x52: // LD D,D - de_.hi = de_.hi; - break; - case 0x53: // LD D,E - de_.hi = de_.lo; - break; - case 0x54: // LD D,H - de_.hi = hl_.hi; - break; - case 0x55: // LD D,L - de_.hi = hl_.lo; - break; - case 0x56: // LD D,(HL) - de_.hi = mmu_->read(hl_.val); - break; - case 0x57: // LD D,A - de_.hi = af_.hi; - break; - case 0x58: // LD E,B - de_.lo = bc_.hi; - break; - case 0x59: // LD E,C - de_.lo = bc_.lo; - break; - case 0x5A: // LD E,D - de_.lo = de_.hi; - break; - case 0x5B: // LD E,E - de_.lo = de_.lo; - break; - case 0x5C: // LD E,H - de_.lo = hl_.hi; - break; - case 0x5D: // LD E,L - de_.lo = hl_.lo; - break; - case 0x5E: // LD E,(HL) - de_.lo = mmu_->read(hl_.val); - break; - case 0x5F: // LD E,A - de_.lo = af_.hi; - break; - - case 0x60: // LD H,B - hl_.hi = bc_.hi; - break; - case 0x61: // LD H,C - hl_.hi = bc_.lo; - break; - case 0x62: // LD H,D - hl_.hi = de_.hi; - break; - case 0x63: // LD H,E - hl_.hi = de_.lo; - break; - case 0x64: // LD H,H - hl_.hi = hl_.hi; - break; - case 0x65: // LD H,L - hl_.hi = hl_.lo; - break; - case 0x66: // LD H,(HL) - hl_.hi = mmu_->read(hl_.val); - break; - case 0x67: // LD H,A - hl_.hi = af_.hi; - break; - case 0x68: // LD L,B - hl_.lo = bc_.hi; - break; - case 0x69: // LD L,C - hl_.lo = bc_.lo; - break; - case 0x6A: // LD L,D - hl_.lo = de_.hi; - break; - case 0x6B: // LD L,E - hl_.lo = de_.lo; - break; - case 0x6C: // LD L,H - hl_.lo = hl_.hi; - break; - case 0x6D: // LD L,L - hl_.lo = hl_.lo; - break; - case 0x6E: // LD L,(HL) - hl_.lo = mmu_->read(hl_.val); - break; - case 0x6F: // LD L,A - hl_.lo = af_.hi; - break; - - case 0x78: // LD A,B - af_.hi = bc_.hi; - break; - case 0x79: // LD A,C - af_.hi = bc_.lo; - break; - case 0x7A: // LD A,D - af_.hi = de_.hi; - break; - case 0x7B: // LD A,E - af_.hi = de_.lo; - break; - case 0x7C: // LD A,H - af_.hi = hl_.hi; - break; - case 0x7D: // LD A,L - af_.hi = hl_.lo; - break; - case 0x7E: // LD A,(HL) - af_.hi = mmu_->read(hl_.val); - break; - case 0x7F: // LD A,A - af_.hi = af_.hi; - break; - - // register to memory - case 0x70: // LD (HL),B - mmu_->write(bc_.hi, hl_.val); - break; - case 0x71: // LD (HL),C - mmu_->write(bc_.lo, hl_.val); - break; - case 0x72: // LD (HL),D - mmu_->write(de_.hi, hl_.val); - break; - case 0x73: // LD (HL),E - mmu_->write(de_.lo, hl_.val); - break; - case 0x74: // LD (HL),H - mmu_->write(hl_.hi, hl_.val); - break; - case 0x75: // LD (HL),L - mmu_->write(hl_.lo, hl_.val); - break; - case 0x77: // LD (HL),A - mmu_->write(af_.hi, hl_.val); - break; - - // Load Increment/Decrement - // (HL+/-) <- A & A <- (HL+/-) - case 0x22: // LD (HL+),A - mmu_->write(af_.hi, hl_.val++); - break; - case 0x32: // LD (HL-),A - mmu_->write(af_.hi, hl_.val--); - break; - case 0x2A: // LD A,(HL+) - af_.hi = mmu_->read(hl_.val++); - break; - case 0x3A: // LD A,(HL-) - af_.hi = mmu_->read(hl_.val--); - break; - - // IN/OUT Instructions. Load and Store to IO Registers (immediate or using C register). IO Offset is $FF00 - case 0xE0: // LDH (a8),A - out(0xFF00 + load8Imm()); - break; - case 0xF0: // LDH A,(a8) - in(0xFF00 + load8Imm()); - break; - case 0xE2: // LD (C),A - out(0xFF00 + bc_.lo); - break; - case 0xF2: // LD A,(C) - in(0xFF00 + bc_.lo); - break; - case 0xEA: // LD (a16),A - out(load16Imm()); - break; - case 0xFA: // LD A,(a16) - in(load16Imm()); - break; - - /* Increment Instruction */ - - // 16 bit increment - case 0x03: // INC BC - inc(bc_.val); - break; - case 0x13: // INC DE - inc(de_.val); - break; - case 0x23: // INC HL - inc(hl_.val); - break; - case 0x33: // INC SP - inc(sp_.val); - break; - - // 16 bit decrement - case 0x0B: // DEC BC - dec(bc_.val); - break; - case 0x1B: // DEC DE - dec(de_.val); - break; - case 0x2B: // DEC HL - dec(hl_.val); - break; - case 0x3B: // DEC SP - dec(sp_.val); - break; - - // 8 bit increment - case 0x04: // INC B - inc(bc_.hi); - break; - case 0x0C: // INC C - inc(bc_.lo); - break; - case 0x14: // INC D - inc(de_.hi); - break; - case 0x1C: // INC E - inc(de_.lo); - break; - case 0x24: // INC H - inc(hl_.hi); - break; - case 0x2C: // INC L - inc(hl_.lo); - break; - case 0x34: // INC (HL) - inca(hl_.val); - break; - case 0x3C: // INC A - inc(af_.hi); - break; - - // 8 bit decrement - case 0x05: // DEC B - dec(bc_.hi); - break; - case 0x0D: // DEC C - dec(bc_.lo); - break; - case 0x15: // DEC D - dec(de_.hi); - break; - case 0x1D: // DEC C - dec(de_.lo); - break; - case 0x25: // DEC H - dec(hl_.hi); - break; - case 0x2D: // DEC L - dec(hl_.lo); - break; - case 0x35: // DEC (HL) - deca(hl_.val); - break; - case 0x3D: // DEC A - dec(af_.hi); - break; - - /* Stack Instructions */ - - // Push - case 0xC5: // PUSH BC - push(bc_.val); - break; - case 0xD5: // PUSH DE - push(de_.val); - break; - case 0xE5: // PUSH HL - push(hl_.val); - break; - case 0xF5: // PUSH AF - push(af_.val); - break; - - // Pop - case 0xC1: // POP BC - bc_.val = pop(); - break; - case 0xD1: // POP DE - de_.val = pop(); - break; - case 0xE1: // POP HL - hl_.val = pop(); - break; - case 0xF1: // POP AF - af_.val = pop(); - af_.lo &= 0xF0; // explicitly clear lower 4 bits - break; - - // Load - - case 0x08: // LD (a16),SP - mmu_->write(sp_.val, load16Imm()); - break; - case 0xF8: // LD HL,SP+r8 - //hl_.val = (uint16_t)((int16_t)sp_.val + (int8_t)load8Imm()); - hl_.val = ldHLSPe(); - break; - case 0xF9: // LD SP,HL - sp_.val = hl_.val; - break; - - case 0x76: - halted_ = true; - break; - - /* Jumps */ - case 0xC3: // JP a16 - jp(load16Imm()); - break; - - case 0xE9: // JP (HL) - jp(hl_.val); - break; - - // conditional jumps - case 0xC2: // JP NZ,nn - if (isClear(af_.lo, Flags::Z)) { - jp(load16Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val += 2; - cycles = opcode_page1[opcode]; - } - break; - case 0xCA: // JP Z,nn - if (isSet(af_.lo, Flags::Z)) { - jp(load16Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val += 2; - cycles = opcode_page1[opcode]; - } - break; - case 0xD2: // JP NC,nn - if (isClear(af_.lo, Flags::C)) { - jp(load16Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val += 2; - cycles = opcode_page1[opcode]; - } - break; - case 0xDA: // JP C,nn - if (isSet(af_.lo, Flags::C)) { - jp(load16Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val += 2; - cycles = opcode_page1[opcode]; - } - break; - - // relative jumps - case 0x18: // JR r8 - jr((int8_t)load8Imm()); - break; - - // relative conditional jumps - case 0x20: // JR NZ,n - if (isClear(af_.lo, Flags::Z)) { - jr((int8_t)load8Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val++; // skip next byte - } - break; - case 0x28: // JR Z,n - if (isSet(af_.lo, Flags::Z)) { - jr((int8_t)load8Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val++; // skip next byte - } - break; - case 0x30: // JR NC,n - if (isClear(af_.lo, Flags::C)) { - jr((int8_t)load8Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val++; // skip next byte - } - break; - case 0x38: // JR C,n - if (isSet(af_.lo, Flags::C)) { - jr((int8_t)load8Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val++; // skip next byte - } - break; - - /* Call */ - case 0xCD: // CALL nn - call(load16Imm()); - break; - - // call condition - case 0xC4: // CALL NZ,nn - if (isClear(af_.lo, Flags::Z)) { - call(load16Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val += 2; - } - break; - case 0xCC: // CALL Z,nn - if (isSet(af_.lo, Flags::Z)) { - call(load16Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val += 2; - } - break; - case 0xD4: // CALL NC,nn - if (isClear(af_.lo, Flags::C)) { - call(load16Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val += 2; - } - break; - case 0xDC: // CALL C,nn - if (isSet(af_.lo, Flags::C)) { - call(load16Imm()); - cycles = opcode_page1_branch[opcode]; - } - else { - pc_.val += 2; - } - break; - - /* Returns */ - case 0xC9: // RET - ret(); - break; - - // conditional returns - case 0xC0: // RET NZ - if (isClear(af_.lo, Flags::Z)) { - ret(); - cycles = opcode_page1_branch[opcode]; - } - break; - case 0xC8: // RET Z - if (isSet(af_.lo, Flags::Z)) { - ret(); - cycles = opcode_page1_branch[opcode]; - } - break; - case 0xD0: // RET NC - if (isClear(af_.lo, Flags::C)) { - ret(); - cycles = opcode_page1_branch[opcode]; - } - break; - case 0xD8: // RET C - if (isSet(af_.lo, Flags::C)) { - ret(); - cycles = opcode_page1_branch[opcode]; - } - break; - - // return from interrupt - case 0xD9: // RETI - reti(); - break; - - /* Reset Instructions */ - case 0xC7: // RST $00 - call(0x00); - break; - case 0xCF: // RST $08 - call(0x08); - break; - case 0xD7: // RST $10 - call(0x10); - break; - case 0xDF: // RST $18 - call(0x18); - break; - case 0xE7: // RST $20 - call(0x20); - break; - case 0xEF: // RST $28 - call(0x28); - break; - case 0xF7: // RST $30 - call(0x30); - break; - case 0xFF: // RST $38 - call(0x38); - break; - - /* Decimal Adjust */ - case 0x27: - daa(); - break; - - /* Complement */ - - // Register A - case 0x2F: // CPL - toggleMask(af_.hi, 0xFF); - setMask(af_.lo, CPU::Flags::N); - setMask(af_.lo, CPU::Flags::H); - break; - // Carry Flag - case 0x3F: // CCF - toggleMask(af_.lo, CPU::Flags::C); - clearMask(af_.lo, CPU::Flags::N); - clearMask(af_.lo, CPU::Flags::H); - break; - - /* Set Carry Flag */ - case 0x37: // SCF - setMask(af_.lo, CPU::Flags::C); - clearMask(af_.lo, CPU::Flags::N); - clearMask(af_.lo, CPU::Flags::H); - break; - - /* Disable and Enable Interrupt */ - case 0xF3: // DI - interrupt_master_disable_pending_ = 0; - break; - case 0xFB: // EI - interrupt_master_enable_pending_ = 0; - break; - - /* Arithmetic Operations */ - // add 8 bit - case 0x87: // ADD A,A - alu_.add(af_.hi, af_.hi); - break; - case 0x80: // ADD A,B - alu_.add(af_.hi, bc_.hi); - break; - case 0x81: // ADD A,C - alu_.add(af_.hi, bc_.lo); - break; - case 0x82: // ADD A,D - alu_.add(af_.hi, de_.hi); - break; - case 0x83: // ADD A,E - alu_.add(af_.hi, de_.lo); - break; - case 0x84: // ADD A,H - alu_.add(af_.hi, hl_.hi); - break; - case 0x85: // ADD A,L - alu_.add(af_.hi, hl_.lo); - break; - case 0x86: // ADD A,(HL) - alu_.add(af_.hi, mmu_->read(hl_.val)); - break; - case 0xC6: // ADD A,n - alu_.add(af_.hi, load8Imm()); - break; - - // add with carry - case 0x8F: // ADC A,A - alu_.addc(af_.hi, af_.hi); - break; - case 0x88: // ADC A,B - alu_.addc(af_.hi, bc_.hi); - break; - case 0x89: // ADC A,C - alu_.addc(af_.hi, bc_.lo); - break; - case 0x8A: // ADC A,D - alu_.addc(af_.hi, de_.hi); - break; - case 0x8B: // ADC A,E - alu_.addc(af_.hi, de_.lo); - break; - case 0x8C: // ADC A,H - alu_.addc(af_.hi, hl_.hi); - break; - case 0x8D: // ADC A,L - alu_.addc(af_.hi, hl_.lo); - break; - case 0x8E: // ADC A,(HL) - alu_.addc(af_.hi, mmu_->read(hl_.val)); - break; - case 0xCE: // ADC A,n - alu_.addc(af_.hi, load8Imm()); - break; - - // 16 bit addition - case 0x09: // ADD HL,BC - alu_.add(hl_.val, bc_.val); - break; - case 0x19: // ADD HL,DE - alu_.add(hl_.val, de_.val); - break; - case 0x29: // ADD HL,HL - alu_.add(hl_.val, hl_.val); - break; - case 0x39: // ADD HL,SP - alu_.add(hl_.val, sp_.val); - break; - - case 0xE8: // ADD SP,n - alu_.addr(sp_.val, (int8_t)load8Imm()); - break; - - // subtract - case 0x97: // SUB A,A - alu_.sub(af_.hi, af_.hi); - break; - case 0x90: // SUB A,B - alu_.sub(af_.hi, bc_.hi); - break; - case 0x91: // SUB A,C - alu_.sub(af_.hi, bc_.lo); - break; - case 0x92: // SUB A,D - alu_.sub(af_.hi, de_.hi); - break; - case 0x93: // SUB A,E - alu_.sub(af_.hi, de_.lo); - break; - case 0x94: // SUB A,H - alu_.sub(af_.hi, hl_.hi); - break; - case 0x95: // SUB A,L - alu_.sub(af_.hi, hl_.lo); - break; - case 0x96: // SUB A,(HL) - alu_.sub(af_.hi, mmu_->read(hl_.val)); - break; - case 0xD6: // SUB A,n - alu_.sub(af_.hi, load8Imm()); - break; - - // substract with carry - case 0x9F: // SBC A,A - alu_.subc(af_.hi, af_.hi); - break; - case 0x98: // SBC A,B - alu_.subc(af_.hi, bc_.hi); - break; - case 0x99: // SBC A,C - alu_.subc(af_.hi, bc_.lo); - break; - case 0x9A: // SBC A,D - alu_.subc(af_.hi, de_.hi); - break; - case 0x9B: // SBC A,E - alu_.subc(af_.hi, de_.lo); - break; - case 0x9C: // SBC A,H - alu_.subc(af_.hi, hl_.hi); - break; - case 0x9D: // SBC A,L - alu_.subc(af_.hi, hl_.lo); - break; - case 0x9E: // SBC A,(HL) - alu_.subc(af_.hi, mmu_->read(hl_.val)); - break; - case 0xDE: // SBC A,n - alu_.subc(af_.hi, load8Imm()); - break; - - /* Logical Operations */ - case 0xA7: // AND A,A - alu_.anda(af_.hi, af_.hi); - break; - case 0xA0: // AND A,B - alu_.anda(af_.hi, bc_.hi); - break; - case 0xA1: // AND A,C - alu_.anda(af_.hi, bc_.lo); - break; - case 0xA2: // AND A,D - alu_.anda(af_.hi, de_.hi); - break; - case 0xA3: // AND A,E - alu_.anda(af_.hi, de_.lo); - break; - case 0xA4: // AND A,H - alu_.anda(af_.hi, hl_.hi); - break; - case 0xA5: // AND A,L - alu_.anda(af_.hi, hl_.lo); - break; - case 0xA6: // AND A,(HL) - alu_.anda(af_.hi, mmu_->read(hl_.val)); - break; - case 0xE6: // AND A,n - alu_.anda(af_.hi, load8Imm()); - break; - - case 0xB7: // OR A,A - alu_.ora(af_.hi, af_.hi); - break; - case 0xB0: // OR A,B - alu_.ora(af_.hi, bc_.hi); - break; - case 0xB1: // OR A,C - alu_.ora(af_.hi, bc_.lo); - break; - case 0xB2: // OR A,D - alu_.ora(af_.hi, de_.hi); - break; - case 0xB3: // OR A,E - alu_.ora(af_.hi, de_.lo); - break; - case 0xB4: // OR A,H - alu_.ora(af_.hi, hl_.hi); - break; - case 0xB5: // OR A,L - alu_.ora(af_.hi, hl_.lo); - break; - case 0xB6: // OR A,(HL) - alu_.ora(af_.hi, mmu_->read(hl_.val)); - break; - case 0xF6: // OR A,n - alu_.ora(af_.hi, load8Imm()); - break; - - case 0xAF: // XOR A,A - alu_.xora(af_.hi, af_.hi); - break; - case 0xA8: // XOR A,B - alu_.xora(af_.hi, bc_.hi); - break; - case 0xA9: // XOR A,C - alu_.xora(af_.hi, bc_.lo); - break; - case 0xAA: // XOR A,D - alu_.xora(af_.hi, de_.hi); - break; - case 0xAB: // XOR A,E - alu_.xora(af_.hi, de_.lo); - break; - case 0xAC: // XOR A,H - alu_.xora(af_.hi, hl_.hi); - break; - case 0xAD: // XOR A,L - alu_.xora(af_.hi, hl_.lo); - break; - case 0xAE: // XOR A,(HL) - alu_.xora(af_.hi, mmu_->read(hl_.val)); - break; - case 0xEE: // OR A,n - alu_.xora(af_.hi, load8Imm()); - break; - - /* Comparison */ - case 0xBF: // CP A,A - alu_.compare(af_.hi, af_.hi); - break; - case 0xB8: // CP A,B - alu_.compare(af_.hi, bc_.hi); - break; - case 0xB9: // CP A,C - alu_.compare(af_.hi, bc_.lo); - break; - case 0xBA: // CP A,D - alu_.compare(af_.hi, de_.hi); - break; - case 0xBB: // CP A,E - alu_.compare(af_.hi, de_.lo); - break; - case 0xBC: // CP A,H - alu_.compare(af_.hi, hl_.hi); - break; - case 0xBD: // CP A,L - alu_.compare(af_.hi, hl_.lo); - break; - case 0xBE: // CP A,(HL) - alu_.compare(af_.hi, mmu_->read(hl_.val)); - break; - case 0xFE: // CP A,n - alu_.compare(af_.hi, load8Imm()); - break; - - /* Rotate A*/ - - case 0x07: // RLCA - af_.hi = rlca(af_.hi, af_.lo); - break; - case 0x17: // RLA - af_.hi = rla(af_.hi, af_.lo); - break; - case 0x0F: // RRCA - af_.hi = rrca(af_.hi, af_.lo); - break; - case 0x1F: // RRA - af_.hi = rra(af_.hi, af_.lo); - break; - - default: - throw std::runtime_error("Unimplemented Instruction"); - break; - } - - if (debug_mode_) - { - sendInstructionData(opcode, current_pc_, OpcodePage::PAGE1); - } - - if (cycles == -1) - { - cycles = opcode_page1[opcode]; - } - - return (uint8_t)cycles; - } - - uint8_t decode2(uint8_t opcode) - { - uint8_t tmp; - - switch (opcode) - { - /* SWAP */ - case 0x37: // SWAP A - af_.hi = swap(af_.hi); - break; - case 0x30: // SWAP B - bc_.hi = swap(bc_.hi); - break; - case 0x31: // SWAP C - bc_.lo = swap(bc_.lo); - break; - case 0x32: // SWAP D - de_.hi = swap(de_.hi); - break; - case 0x33: // SWAP E - de_.lo = swap(de_.lo); - break; - case 0x34: // SWAP H - hl_.hi = swap(hl_.hi); - break; - case 0x35: // SWAP L - hl_.lo = swap(hl_.lo); - break; - case 0x36: // SWAP (HL) - mmu_->write(swap(mmu_->read(hl_.val)), hl_.val); - break; - - /* Rotate */ - - case 0x00: // RLC B - bc_.hi = rotateLeft(bc_.hi, 1, af_.lo); - break; - case 0x01: // RLC C - bc_.lo = rotateLeft(bc_.lo, 1, af_.lo); - break; - case 0x02: // RLC D - de_.hi = rotateLeft(de_.hi, 1, af_.lo); - break; - case 0x03: // RLC E - de_.lo = rotateLeft(de_.lo, 1, af_.lo); - break; - case 0x04: // RLC H - hl_.hi = rotateLeft(hl_.hi, 1, af_.lo); - break; - case 0x05: // RLC L - hl_.lo = rotateLeft(hl_.lo, 1, af_.lo); - break; - case 0x06: // RLC (HL) - mmu_->write(rotateLeft(mmu_->read(hl_.val), 1, af_.lo), hl_.val); - break; - case 0x07: // RLC A - af_.hi = rotateLeft(af_.hi, 1, af_.lo); - break; - - case 0x08: // RRC B - bc_.hi = rotateRight(bc_.hi, 1, af_.lo); - break; - case 0x09: // RRC C - bc_.lo = rotateRight(bc_.lo, 1, af_.lo); - break; - case 0x0A: // RRC D - de_.hi = rotateRight(de_.hi, 1, af_.lo); - break; - case 0x0B: // RRC E - de_.lo = rotateRight(de_.lo, 1, af_.lo); - break; - case 0x0C: // RRC H - hl_.hi = rotateRight(hl_.hi, 1, af_.lo); - break; - case 0x0D: // RRC L - hl_.lo = rotateRight(hl_.lo, 1, af_.lo); - break; - case 0x0E: // RRC (HL) - mmu_->write(rotateRight(mmu_->read(hl_.val), 1, af_.lo), hl_.val); - break; - case 0x0F: // RRC A - af_.hi = rotateRight(af_.hi, 1, af_.lo); - break; - - //......................... - - case 0x10: // RL B - bc_.hi = rotateLeftCarry(bc_.hi, 1, af_.lo); - break; - case 0x11: // RL C - bc_.lo = rotateLeftCarry(bc_.lo, 1, af_.lo); - break; - case 0x12: // RL D - de_.hi = rotateLeftCarry(de_.hi, 1, af_.lo); - break; - case 0x13: // RL E - de_.lo = rotateLeftCarry(de_.lo, 1, af_.lo); - break; - case 0x14: // RL H - hl_.hi = rotateLeftCarry(hl_.hi, 1, af_.lo); - break; - case 0x15: // RL L - hl_.lo = rotateLeftCarry(hl_.lo, 1, af_.lo); - break; - case 0x16: // RL (HL) - mmu_->write(rotateLeftCarry(mmu_->read(hl_.val), 1, af_.lo), hl_.val); - break; - case 0x17: // RL A - af_.hi = rotateLeftCarry(af_.hi, 1, af_.lo); - break; - - case 0x18: // RR B - bc_.hi = rotateRightCarry(bc_.hi, 1, af_.lo); - break; - case 0x19: // RR C - bc_.lo = rotateRightCarry(bc_.lo, 1, af_.lo); - break; - case 0x1A: // RR D - de_.hi = rotateRightCarry(de_.hi, 1, af_.lo); - break; - case 0x1B: // RR E - de_.lo = rotateRightCarry(de_.lo, 1, af_.lo); - break; - case 0x1C: // RR H - hl_.hi = rotateRightCarry(hl_.hi, 1, af_.lo); - break; - case 0x1D: // RR L - hl_.lo = rotateRightCarry(hl_.lo, 1, af_.lo); - break; - case 0x1E: // RR (HL) - mmu_->write(rotateRightCarry(mmu_->read(hl_.val), 1, af_.lo), hl_.val); - break; - case 0x1F: // RR A - af_.hi = rotateRightCarry(af_.hi, 1, af_.lo); - break; - - /* Shift */ - - case 0x20: // SLA B - bc_.hi = shiftLeft(bc_.hi, 1, af_.lo); - break; - case 0x21: // SLA C - bc_.lo = shiftLeft(bc_.lo, 1, af_.lo); - break; - case 0x22: // SLA D - de_.hi = shiftLeft(de_.hi, 1, af_.lo); - break; - case 0x23: // SLA E - de_.lo = shiftLeft(de_.lo, 1, af_.lo); - break; - case 0x24: // SLA H - hl_.hi = shiftLeft(hl_.hi, 1, af_.lo); - break; - case 0x25: // SLA L - hl_.lo = shiftLeft(hl_.lo, 1, af_.lo); - break; - case 0x26: // SLA (HL) - mmu_->write(shiftLeft(mmu_->read(hl_.val), 1, af_.lo), hl_.val); - break; - case 0x27: // SLA A - af_.hi = shiftLeft(af_.hi, 1, af_.lo); - break; - - case 0x28: // SRA B - bc_.hi = shiftRightA(bc_.hi, 1, af_.lo); - break; - case 0x29: // SRA C - bc_.lo = shiftRightA(bc_.lo, 1, af_.lo); - break; - case 0x2A: // SRA D - de_.hi = shiftRightA(de_.hi, 1, af_.lo); - break; - case 0x2B: // SRA E - de_.lo = shiftRightA(de_.lo, 1, af_.lo); - break; - case 0x2C: // SRA H - hl_.hi = shiftRightA(hl_.hi, 1, af_.lo); - break; - case 0x2D: // SRA L - hl_.lo = shiftRightA(hl_.lo, 1, af_.lo); - break; - case 0x2E: // SRA (HL) - mmu_->write(shiftRightA(mmu_->read(hl_.val), 1, af_.lo), hl_.val); - break; - case 0x2F: // SRA A - af_.hi = shiftRightA(af_.hi, 1, af_.lo); - break; - - case 0x38: // SRL B - bc_.hi = shiftRightL(bc_.hi, 1, af_.lo); - break; - case 0x39: // SRL C - bc_.lo = shiftRightL(bc_.lo, 1, af_.lo); - break; - case 0x3A: // SRL D - de_.hi = shiftRightL(de_.hi, 1, af_.lo); - break; - case 0x3B: // SRL E - de_.lo = shiftRightL(de_.lo, 1, af_.lo); - break; - case 0x3C: // SRL H - hl_.hi = shiftRightL(hl_.hi, 1, af_.lo); - break; - case 0x3D: // SRL L - hl_.lo = shiftRightL(hl_.lo, 1, af_.lo); - break; - case 0x3E: // SRL (HL) - mmu_->write(shiftRightL(mmu_->read(hl_.val), 1, af_.lo), hl_.val); - break; - case 0x3F: // SRL A - af_.hi = shiftRightL(af_.hi, 1, af_.lo); - break; - - /* Bit */ - - // bit 0 - case 0x40: // BIT 0,B - bit(bc_.hi, 0); - break; - case 0x41: // BIT 0,C - bit(bc_.lo, 0); - break; - case 0x42: // BIT 0,D - bit(de_.hi, 0); - break; - case 0x43: // BIT 0,E - bit(de_.lo, 0); - break; - case 0x44: // BIT 0,H - bit(hl_.hi, 0); - break; - case 0x45: // BIT 0,L - bit(hl_.lo, 0); - break; - case 0x46: // BIT 0,(HL) - bit(mmu_->read(hl_.val), 0); - break; - case 0x47: // BIT 0,A - bit(af_.hi, 0); - break; - // bit 1 - case 0x48: // BIT 1,B - bit(bc_.hi, 1); - break; - case 0x49: // BIT 1,C - bit(bc_.lo, 1); - break; - case 0x4A: // BIT 1,D - bit(de_.hi, 1); - break; - case 0x4B: // BIT 1,E - bit(de_.lo, 1); - break; - case 0x4C: // BIT 1,H - bit(hl_.hi, 1); - break; - case 0x4D: // BIT 1,L - bit(hl_.lo, 1); - break; - case 0x4E: // BIT 1,(HL) - bit(mmu_->read(hl_.val), 1); - break; - case 0x4F: // BIT 1,A - bit(af_.hi, 1); - break; - - // bit 2 - case 0x50: // BIT 2,B - bit(bc_.hi, 2); - break; - case 0x51: // BIT 2,C - bit(bc_.lo, 2); - break; - case 0x52: // BIT 2,D - bit(de_.hi, 2); - break; - case 0x53: // BIT 2,E - bit(de_.lo, 2); - break; - case 0x54: // BIT 2,H - bit(hl_.hi, 2); - break; - case 0x55: // BIT 2,L - bit(hl_.lo, 2); - break; - case 0x56: // BIT 2,(HL) - bit(mmu_->read(hl_.val), 2); - break; - case 0x57: // BIT 2,A - bit(af_.hi, 2); - break; - - // bit 3 - case 0x58: // BIT 3,B - bit(bc_.hi, 3); - break; - case 0x59: // BIT 3,C - bit(bc_.lo, 3); - break; - case 0x5A: // BIT 3,D - bit(de_.hi, 3); - break; - case 0x5B: // BIT 3,E - bit(de_.lo, 3); - break; - case 0x5C: // BIT 3,H - bit(hl_.hi, 3); - break; - case 0x5D: // BIT 3,L - bit(hl_.lo, 3); - break; - case 0x5E: // BIT 3,(HL) - bit(mmu_->read(hl_.val), 3); - break; - case 0x5F: // BIT 3,A - bit(af_.hi, 3); - break; - - // bit 4 - case 0x60: // BIT 4,B - bit(bc_.hi, 4); - break; - case 0x61: // BIT 4,C - bit(bc_.lo, 4); - break; - case 0x62: // BIT 4,D - bit(de_.hi, 4); - break; - case 0x63: // BIT 4,E - bit(de_.lo, 4); - break; - case 0x64: // BIT 4,H - bit(hl_.hi, 4); - break; - case 0x65: // BIT 4,L - bit(hl_.lo, 4); - break; - case 0x66: // BIT 4,(HL) - bit(mmu_->read(hl_.val), 4); - break; - case 0x67: // BIT 4,A - bit(af_.hi, 4); - break; - - // bit 5 - case 0x68: // BIT 5,B - bit(bc_.hi, 5); - break; - case 0x69: // BIT 5,C - bit(bc_.lo, 5); - break; - case 0x6A: // BIT 5,D - bit(de_.hi, 5); - break; - case 0x6B: // BIT 5,E - bit(de_.lo, 5); - break; - case 0x6C: // BIT 5,H - bit(hl_.hi, 5); - break; - case 0x6D: // BIT 5,L - bit(hl_.lo, 5); - break; - case 0x6E: // BIT 5,(HL) - bit(mmu_->read(hl_.val), 5); - break; - case 0x6F: // BIT 5,A - bit(af_.hi, 5); - break; - - // bit 6 - case 0x70: // BIT 6,B - bit(bc_.hi, 6); - break; - case 0x71: // BIT 6,C - bit(bc_.lo, 6); - break; - case 0x72: // BIT 6,D - bit(de_.hi, 6); - break; - case 0x73: // BIT 6,E - bit(de_.lo, 6); - break; - case 0x74: // BIT 6,H - bit(hl_.hi, 6); - break; - case 0x75: // BIT 6,L - bit(hl_.lo, 6); - break; - case 0x76: // BIT 6,(HL) - bit(mmu_->read(hl_.val), 6); - break; - case 0x77: // BIT 6,A - bit(af_.hi, 6); - break; - // bit 7 - case 0x78: // BIT 7,B - bit(bc_.hi, 7); - break; - case 0x79: // BIT 7,C - bit(bc_.lo, 7); - break; - case 0x7A: // BIT 7,D - bit(de_.hi, 7); - break; - case 0x7B: // BIT 7,E - bit(de_.lo, 7); - break; - case 0x7C: // BIT 7,H - bit(hl_.hi, 7); - break; - case 0x7D: // BIT 7,L - bit(hl_.lo, 7); - break; - case 0x7E: // BIT 7,(HL) - bit(mmu_->read(hl_.val), 7); - break; - case 0x7F: // BIT 7,A - bit(af_.hi, 7); - break; - - /* Reset */ - case 0x80: // RES 0,B - clearBit(bc_.hi, 0); - break; - case 0x81: // RES 0,C - clearBit(bc_.lo, 0); - break; - case 0x82: // RES 0,D - clearBit(de_.hi, 0); - break; - case 0x83: // RES 0,E - clearBit(de_.lo, 0); - break; - case 0x84: // RES 0,H - clearBit(hl_.hi, 0); - break; - case 0x85: // RES 0,L - clearBit(hl_.lo, 0); - break; - case 0x86: // RES 0,(HL) - tmp = mmu_->read(hl_.val); - clearBit(tmp, 0); - mmu_->write(tmp, hl_.val); - break; - case 0x87: // RES 0,A - clearBit(af_.hi, 0); - break; - case 0x88: // RES 1,B - clearBit(bc_.hi, 1); - break; - case 0x89: // RES 1,C - clearBit(bc_.lo, 1); - break; - case 0x8A: // RES 1,D - clearBit(de_.hi, 1); - break; - case 0x8B: // RES 1,E - clearBit(de_.lo, 1); - break; - case 0x8C: // RES 1,H - clearBit(hl_.hi, 1); - break; - case 0x8D: // RES 1,L - clearBit(hl_.lo, 1); - break; - case 0x8E: // RES 1,(HL) - tmp = mmu_->read(hl_.val); - clearBit(tmp, 1); - mmu_->write(tmp, hl_.val); - break; - case 0x8F: // RES 1,A - clearBit(af_.hi, 1); - break; - - case 0x90: // RES 2,B - clearBit(bc_.hi, 2); - break; - case 0x91: // RES 2,C - clearBit(bc_.lo, 2); - break; - case 0x92: // RES 2,D - clearBit(de_.hi, 2); - break; - case 0x93: // RES 2,E - clearBit(de_.lo, 2); - break; - case 0x94: // RES 2,H - clearBit(hl_.hi, 2); - break; - case 0x95: // RES 2,L - clearBit(hl_.lo, 2); - break; - case 0x96: // RES 2,(HL) - tmp = mmu_->read(hl_.val); - clearBit(tmp, 2); - mmu_->write(tmp, hl_.val); - break; - case 0x97: // RES 2,A - clearBit(af_.hi, 2); - break; - case 0x98: // RES 3,B - clearBit(bc_.hi, 3); - break; - case 0x99: // RES 3,C - clearBit(bc_.lo, 3); - break; - case 0x9A: // RES 3,D - clearBit(de_.hi, 3); - break; - case 0x9B: // RES 3,E - clearBit(de_.lo, 3); - break; - case 0x9C: // RES 3,H - clearBit(hl_.hi, 3); - break; - case 0x9D: // RES 3,L - clearBit(hl_.lo, 3); - break; - case 0x9E: // RES 3,(HL) - tmp = mmu_->read(hl_.val); - clearBit(tmp, 3); - mmu_->write(tmp, hl_.val); - break; - case 0x9F: // RES 3,A - clearBit(af_.hi, 3); - break; - - case 0xA0: // RES 4,B - clearBit(bc_.hi, 4); - break; - case 0xA1: // RES 4,C - clearBit(bc_.lo, 4); - break; - case 0xA2: // RES 4,D - clearBit(de_.hi, 4); - break; - case 0xA3: // RES 4,E - clearBit(de_.lo, 4); - break; - case 0xA4: // RES 4,H - clearBit(hl_.hi, 4); - break; - case 0xA5: // RES 4,L - clearBit(hl_.lo, 4); - break; - case 0xA6: // RES 4,(HL) - tmp = mmu_->read(hl_.val); - clearBit(tmp, 4); - mmu_->write(tmp, hl_.val); - break; - case 0xA7: // RES 4,A - clearBit(af_.hi, 4); - break; - case 0xA8: // RES 5,B - clearBit(bc_.hi, 5); - break; - case 0xA9: // RES 5,C - clearBit(bc_.lo, 5); - break; - case 0xAA: // RES 5,D - clearBit(de_.hi, 5); - break; - case 0xAB: // RES 5,E - clearBit(de_.lo, 5); - break; - case 0xAC: // RES 5,H - clearBit(hl_.hi, 5); - break; - case 0xAD: // RES 5,L - clearBit(hl_.lo, 5); - break; - case 0xAE: // RES 5,(HL) - tmp = mmu_->read(hl_.val); - clearBit(tmp, 5); - mmu_->write(tmp, hl_.val); - break; - case 0xAF: // RES 5,A - clearBit(af_.hi, 5); - break; - - case 0xB0: // RES 6,B - clearBit(bc_.hi, 6); - break; - case 0xB1: // RES 6,C - clearBit(bc_.lo, 6); - break; - case 0xB2: // RES 6,D - clearBit(de_.hi, 6); - break; - case 0xB3: // RES 6,E - clearBit(de_.lo, 6); - break; - case 0xB4: // RES 6,H - clearBit(hl_.hi, 6); - break; - case 0xB5: // RES 6,L - clearBit(hl_.lo, 6); - break; - case 0xB6: // RES 6,(HL) - tmp = mmu_->read(hl_.val); - clearBit(tmp, 6); - mmu_->write(tmp, hl_.val); - break; - case 0xB7: // RES 6,A - clearBit(af_.hi, 6); - break; - case 0xB8: // RES 7,B - clearBit(bc_.hi, 7); - break; - case 0xB9: // RES 7,C - clearBit(bc_.lo, 7); - break; - case 0xBA: // RES 7,D - clearBit(de_.hi, 7); - break; - case 0xBB: // RES 7,E - clearBit(de_.lo, 7); - break; - case 0xBC: // RES 7,H - clearBit(hl_.hi, 7); - break; - case 0xBD: // RES 7,L - clearBit(hl_.lo, 7); - break; - case 0xBE: // RES 7,(HL) - tmp = mmu_->read(hl_.val); - clearBit(tmp, 7); - mmu_->write(tmp, hl_.val); - break; - case 0xBF: // RES 7,A - clearBit(af_.hi, 7); - break; - - /* Set */ - - case 0xC0: // SET 0,B - setBit(bc_.hi, 0); - break; - case 0xC1: // SET 0,C - setBit(bc_.lo, 0); - break; - case 0xC2: // SET 0,D - setBit(de_.hi, 0); - break; - case 0xC3: // SET 0,E - setBit(de_.lo, 0); - break; - case 0xC4: // SET 0,H - setBit(hl_.hi, 0); - break; - case 0xC5: // SET 0,L - setBit(hl_.lo, 0); - break; - case 0xC6: // SET 0,(HL) - tmp = mmu_->read(hl_.val); - setBit(tmp, 0); - mmu_->write(tmp, hl_.val); - break; - case 0xC7: // SET 0,A - setBit(af_.hi, 0); - break; - case 0xC8: // SET 1,B - setBit(bc_.hi, 1); - break; - case 0xC9: // SET 1,C - setBit(bc_.lo, 1); - break; - case 0xCA: // SET 1,D - setBit(de_.hi, 1); - break; - case 0xCB: // SET 1,E - setBit(de_.lo, 1); - break; - case 0xCC: // SET 1,H - setBit(hl_.hi, 1); - break; - case 0xCD: // SET 1,L - setBit(hl_.lo, 1); - break; - case 0xCE: // SET 1,(HL) - tmp = mmu_->read(hl_.val); - setBit(tmp, 1); - mmu_->write(tmp, hl_.val); - break; - case 0xCF: // SET 1,A - setBit(af_.hi, 1); - break; - - case 0xD0: // SET 2,B - setBit(bc_.hi, 2); - break; - case 0xD1: // SET 2,C - setBit(bc_.lo, 2); - break; - case 0xD2: // SET 2,D - setBit(de_.hi, 2); - break; - case 0xD3: // SET 2,E - setBit(de_.lo, 2); - break; - case 0xD4: // SET 2,H - setBit(hl_.hi, 2); - break; - case 0xD5: // SET 2,L - setBit(hl_.lo, 2); - break; - case 0xD6: // SET 2,(HL) - tmp = mmu_->read(hl_.val); - setBit(tmp, 2); - mmu_->write(tmp, hl_.val); - break; - case 0xD7: // SET 2,A - setBit(af_.hi, 2); - break; - case 0xD8: // SET 3,B - setBit(bc_.hi, 3); - break; - case 0xD9: // SET 3,C - setBit(bc_.lo, 3); - break; - case 0xDA: // SET 3,D - setBit(de_.hi, 3); - break; - case 0xDB: // SET 3,E - setBit(de_.lo, 3); - break; - case 0xDC: // SET 3,H - setBit(hl_.hi, 3); - break; - case 0xDD: // SET 3,L - setBit(hl_.lo, 3); - break; - case 0xDE: // SET 3,(HL) - tmp = mmu_->read(hl_.val); - setBit(tmp, 3); - mmu_->write(tmp, hl_.val); - break; - case 0xDF: // SET 3,A - setBit(af_.hi, 3); - break; - - case 0xE0: // SET 4,B - setBit(bc_.hi, 4); - break; - case 0xE1: // SET 4,C - setBit(bc_.lo, 4); - break; - case 0xE2: // SET 4,D - setBit(de_.hi, 4); - break; - case 0xE3: // SET 4,E - setBit(de_.lo, 4); - break; - case 0xE4: // SET 4,H - setBit(hl_.hi, 4); - break; - case 0xE5: // SET 4,L - setBit(hl_.lo, 4); - break; - case 0xE6: // SET 4,(HL) - tmp = mmu_->read(hl_.val); - setBit(tmp, 4); - mmu_->write(tmp, hl_.val); - break; - case 0xE7: // SET 4,A - setBit(af_.hi, 4); - break; - case 0xE8: // SET 5,B - setBit(bc_.hi, 5); - break; - case 0xE9: // SET 5,C - setBit(bc_.lo, 5); - break; - case 0xEA: // SET 5,D - setBit(de_.hi, 5); - break; - case 0xEB: // SET 5,E - setBit(de_.lo, 5); - break; - case 0xEC: // SET 5,H - setBit(hl_.hi, 5); - break; - case 0xED: // SET 5,L - setBit(hl_.lo, 5); - break; - case 0xEE: // SET 5,(HL) - tmp = mmu_->read(hl_.val); - setBit(tmp, 5); - mmu_->write(tmp, hl_.val); - break; - case 0xEF: // SET 5,A - setBit(af_.hi, 5); - break; - - case 0xF0: // SET 6,B - setBit(bc_.hi, 6); - break; - case 0xF1: // SET 6,C - setBit(bc_.lo, 6); - break; - case 0xF2: // SET 6,D - setBit(de_.hi, 6); - break; - case 0xF3: // SET 6,E - setBit(de_.lo, 6); - break; - case 0xF4: // SET 6,H - setBit(hl_.hi, 6); - break; - case 0xF5: // SET 6,L - setBit(hl_.lo, 6); - break; - case 0xF6: // SET 6,(HL) - tmp = mmu_->read(hl_.val); - setBit(tmp, 6); - mmu_->write(tmp, hl_.val); - break; - case 0xF7: // SET 6,A - setBit(af_.hi, 6); - break; - case 0xF8: // SET 7,B - setBit(bc_.hi, 7); - break; - case 0xF9: // SET 7,C - setBit(bc_.lo, 7); - break; - case 0xFA: // SET 7,D - setBit(de_.hi, 7); - break; - case 0xFB: // SET 7,E - setBit(de_.lo, 7); - break; - case 0xFC: // SET 7,H - setBit(hl_.hi, 7); - break; - case 0xFD: // SET 7,L - setBit(hl_.lo, 7); - break; - case 0xFE: // SET 7,(HL) - tmp = mmu_->read(hl_.val); - setBit(tmp, 7); - mmu_->write(tmp, hl_.val); - break; - case 0xFF: // SET 7,A - setBit(af_.hi, 7); - break; - - default: - throw std::runtime_error("Unimplemented Instruction"); - break; - } - - if (debug_mode_) - { - sendInstructionData(opcode, current_pc_, OpcodePage::PAGE2); - } - - return opcode_page2[opcode]; - } - - void checkInterrupts() - { - // when EI or DI is used to change the IME the change takes effect after the next instruction is executed - - if (interrupt_master_disable_pending_ >= 0) - { - interrupt_master_disable_pending_++; - if (interrupt_master_disable_pending_ == 2) - { - interrupt_master_enable_ = false; - interrupt_master_disable_pending_ = -1; - } - } - - if (interrupt_master_enable_pending_ >= 0) - { - interrupt_master_enable_pending_++; - if (interrupt_master_enable_pending_ == 2) - { - interrupt_master_enable_ = true; - interrupt_master_enable_pending_ = -1; - } - } - - if (interrupt_master_enable_) - { - // mask off disabled interrupts - uint8_t pending_interrupts = interrupt_flags_ & interrupt_enable_; - - if (isSet(pending_interrupts, InterruptMask::JOYPAD)) - interrupt(InterruptVector::JOYPAD, InterruptMask::JOYPAD); - - if (isSet(pending_interrupts, InterruptMask::SERIAL_TRANSFER_COMPLETE)) - interrupt(InterruptVector::SERIAL_TRANSFER_COMPLETE, InterruptMask::SERIAL_TRANSFER_COMPLETE); - - if (isSet(pending_interrupts, InterruptMask::TIME_OVERFLOW)) - interrupt(InterruptVector::TIME_OVERFLOW, InterruptMask::TIME_OVERFLOW); - - if (isSet(pending_interrupts, InterruptMask::LCDC_STAT)) - interrupt(InterruptVector::LCDC_STAT, InterruptMask::LCDC_STAT); - - if (isSet(pending_interrupts, InterruptMask::VBLANK)) - interrupt(InterruptVector::VBLANK, InterruptMask::VBLANK); - } - } - - void interrupt(InterruptVector vector, InterruptMask mask) - { - interrupt_master_enable_ = false; - - push(pc_.val); - pc_.val = static_cast(vector); - - clearMask(interrupt_flags_, mask); - - cycle_count_ += 5; - } - - void checkPowerMode() - { - uint8_t pending_interrupts = interrupt_enable_ & interrupt_flags_; - - if (!stopped_) - { - if (pending_interrupts != 0) - { - halted_ = false; - } - } - else - { - if (isSet(pending_interrupts, InterruptMask::JOYPAD)) - { - stopped_ = false; - halted_ = false; - } - } - } - - void sendInstructionData(uint8_t opcode, uint16_t addr, OpcodePage page) - { - if (instruction_callback_) - { - const auto instr = fetchInstructionData(opcode, addr, page); - instruction_callback_(instr, addr); - } - } - - Instruction fetchInstructionData(uint8_t opcode, uint16_t opcode_addr, OpcodePage page) - { - OpcodeInfo opcodeinfo = getOpcodeInfo(opcode, page); - - if (opcodeinfo.userdata == OperandType::NONE) - { - return Instruction{ opcode, static_cast(page), {0, 0} }; - } - else - { - if (opcodeinfo.userdata == OperandType::IMM8) - { - const auto data = mmu_->read(opcode_addr + 1); - return Instruction{ opcode, static_cast(page), {data, 0} }; - } - else // OperandType::IMM16 - { - const auto lo = mmu_->read(opcode_addr + 1); - const auto hi = mmu_->read(opcode_addr + 2); - - return Instruction{ opcode, static_cast(page), {lo, hi} }; - } - } - } - - uint8_t load8Imm() - { - return mmu_->read(pc_.val++); - } - - uint16_t load16Imm() - { - uint8_t lo = load8Imm(); - uint8_t hi = load8Imm(); - - return word(hi, lo); - } - - void in(uint16_t addr) - { - // read from offset into IO registers - af_.hi = mmu_->read(addr); - } - - void out(uint16_t addr) - { - // write out to the IO registers given the offset - mmu_->write(af_.hi, addr); - } - - void inc(uint16_t& i) - { - i++; - } - - void dec(uint8_t& d) - { - bool half_carry = isHalfBorrow(d, 1); - - d--; - - setFlag(CPU::Flags::Z, d == 0); - setFlag(CPU::Flags::N, true); - setFlag(CPU::Flags::H, half_carry); - } - - void inca(uint16_t addr) - { - uint8_t b = mmu_->read(addr); - inc(b); - mmu_->write(b, addr); - } - - void deca(uint16_t addr) - { - uint8_t b = mmu_->read(addr); - dec(b); - mmu_->write(b, addr); - } - - void push(uint16_t value) - { - uint8_t hi = (value & 0xFF00) >> 8; - uint8_t lo = (value & 0x00FF); - - mmu_->write(hi, sp_.val - 1); - mmu_->write(lo, sp_.val - 2); - - sp_.val -= 2; - } - - uint16_t pop() - { - uint8_t lo = mmu_->read(sp_.val); - uint8_t hi = mmu_->read(sp_.val + 1); - - sp_.val += 2; - - return word(hi, lo); - } - - void jp(uint16_t addr) - { - pc_.val = addr; - } - - void jr(int8_t r) - { - pc_.val += r; - } - - void inc(uint8_t& i) - { - bool half_carry = isHalfCarry(i, 1); - - i++; - - setFlag(CPU::Flags::Z, i == 0); - setFlag(CPU::Flags::N, false); - setFlag(CPU::Flags::H, half_carry); - } - - void dec(uint16_t& d) - { - d--; - } - - void call(uint16_t addr) - { - push(pc_.val); - pc_.val = addr; - } - - void ret() - { - pc_.val = pop(); - } - - void reti() - { - ret(); - interrupt_master_enable_ = true; - } - - uint8_t swap(uint8_t byte) - { - uint8_t hi = (byte & 0xF0) >> 4; - uint8_t lo = byte & 0x0F; - - uint8_t newByte = (lo << 4) | hi; - - setFlag(CPU::Flags::Z, newByte == 0); - setFlag(CPU::Flags::N, false); - setFlag(CPU::Flags::H, false); - setFlag(CPU::Flags::C, false); - - return newByte; - } - - void daa() - { - bool n = isSet(af_.lo, CPU::Flags::N) != 0; - bool h = isSet(af_.lo, CPU::Flags::H) != 0; - bool c = isSet(af_.lo, CPU::Flags::C) != 0; - - int a = (int)af_.hi; - - if (!n) - { - if (c || (a > 0x99)) { - a = (a + 0x60) & 0xFF; - setFlag(Flags::C, true); - } - if (h || ((a & 0x0F) > 9)) { - a = (a + 0x06) & 0xFF; - setFlag(Flags::H, false); - } - } - else if (c && h) - { - a = (a + 0x9A) & 0xFF; - setFlag(Flags::H, false); - } - else if (c) - { - a = (a + 0xA0) & 0xFF; - } - else if (h) - { - a = (a + 0xFA) & 0xFF; - setFlag(Flags::H, false); - } - - setFlag(Flags::Z, a == 0); - - af_.hi = (uint8_t)a; - } - - uint16_t ldHLSPe() - { - int8_t e = (int8_t)load8Imm(); - int result = sp_.val + e; - - setFlag(Flags::C, ((sp_.val ^ e ^ (result & 0xFFFF)) & 0x100) == 0x100); - setFlag(Flags::H, ((sp_.val ^ e ^ (result & 0xFFFF)) & 0x10) == 0x10); - - setFlag(ALU::Flags::Z, false); - setFlag(ALU::Flags::N, false); - - return (uint16_t)result; - } - - void stop() - { - if (cgb_enabled_) - { - // TODO: CGB support - } - else - { - // TODO: Remove the KEY1 check - auto key1_reg = mmu_->read(memorymap::KEY1_REGISER); - - // check for preparing speed switch - if (key1_reg & 0x01) return; - - stopped_ = true; - halted_ = true; - pc_.val++; - } - } - - void setFlag(uint8_t mask, bool set) - { - if (set) - { - setMask(af_.lo, mask); - } - else - { - clearMask(af_.lo, mask); - } - } - - void reset() - { - af_.hi = (cgb_enabled_) ? 0x11 : 0x00; - af_.lo = 0; - - bc_.val = 0; - de_.val = 0; - hl_.val = 0; - sp_.val = memorymap::HIGH_RAM_END; - pc_.val = memorymap::PROGRAM_START; - - cycle_count_ = 0; - halted_ = false; - stopped_ = false; - interrupt_master_enable_ = false; - interrupt_master_enable_pending_ = -1; - interrupt_master_disable_pending_ = -1; - - // set normal speed mode of CGB - mmu_->write((uint8_t)0x00, memorymap::KEY1_REGISER); - } - - void setDebugMode(bool debug_mode) noexcept - { - debug_mode_ = debug_mode; - } - - void setInstructionCallback(const std::function& callback) noexcept - { - instruction_callback_ = callback; - } - - bool isHalted() const noexcept - { - return halted_; - } - - void bit(uint8_t val, uint8_t n) - { - uint8_t b = (val & bv(n)) >> n; - - setFlag(CPU::Flags::Z, b == 0); - setFlag(CPU::Flags::H, true); - setFlag(CPU::Flags::N, false); - } - - std::array serialize() const noexcept - { - return { - af_.hi, - af_.lo, - bc_.hi, - bc_.lo, - de_.hi, - de_.lo, - hl_.hi, - hl_.lo, - sp_.hi, - sp_.lo, - pc_.hi, - pc_.lo - }; - } - - void deserialize(const std::array& data) noexcept - { - af_.hi = data[0]; - af_.lo = data[1]; - bc_.hi = data[2]; - bc_.lo = data[3]; - de_.hi = data[4]; - de_.lo = data[5]; - hl_.hi = data[6]; - hl_.lo = data[7]; - sp_.hi = data[8]; - sp_.lo = data[9]; - pc_.hi = data[10]; - pc_.lo = data[11]; - } - - CPU::Status getStatus() const - { - Status status; - status.af = af_.val; - status.a = af_.hi; - status.f = af_.lo; - status.bc = bc_.val; - status.b = bc_.hi; - status.c = bc_.lo; - status.de = de_.val; - status.d = de_.hi; - status.e = de_.lo; - status.hl = hl_.val; - status.h = hl_.hi; - status.l = hl_.lo; - status.sp = sp_.val; - status.pc = pc_.val; - status.halt = halted_; - status.stopped = stopped_; - status.ime = interrupt_master_enable_; - status.enabled_interrupts = interrupt_enable_; - - status.flag_z = !!(af_.lo & Flags::Z); - status.flag_n = !!(af_.lo & Flags::N); - status.flag_h = !!(af_.lo & Flags::H); - status.flag_c = !!(af_.lo & Flags::C); - - return status; - } - - private: - Register af_; - Register bc_; - Register de_; - Register hl_; - Register sp_; - Register pc_; - - MMU::Ptr& mmu_; - GPU::Ptr& gpu_; - APU::Ptr& apu_; - Link::Ptr& link_; - - ALU alu_; - - Timer timer_; - - bool halted_; - bool stopped_; - - bool interrupt_master_enable_; - int interrupt_master_enable_pending_; - int interrupt_master_disable_pending_; - - bool debug_mode_; - uint16_t current_pc_; - std::function instruction_callback_; - - int cycle_count_; - - uint8_t& interrupt_flags_; - uint8_t& interrupt_enable_; - - bool cgb_enabled_; - }; - - /* Public Interface */ - - CPU::CPU(MMU::Ptr& mmu, GPU::Ptr& gpu, APU::Ptr& apu, Link::Ptr& link) : - impl_(new Impl(mmu, gpu, apu, link)) - { - reset(); - } - - CPU::~CPU() - { - delete impl_; - } - - void CPU::step() - { - impl_->step(); - } - - void CPU::reset() - { - impl_->reset(); - } - - bool CPU::isHalted() const noexcept - { - return impl_->isHalted(); - } - - void CPU::setDebugMode(bool debug_mode) noexcept - { - impl_->setDebugMode(debug_mode); - } - - void CPU::setInstructionCallback(std::function callback) noexcept - { - impl_->setInstructionCallback(callback); - } - - std::array CPU::serialize() const noexcept - { - return impl_->serialize(); - } - - void CPU::deserialize(const std::array& data) noexcept - { - impl_->deserialize(data); - } - - CPU::Status CPU::getStatus() const - { - return impl_->getStatus(); - } -} diff --git a/components/gameboycore/src/gameboycore.cpp b/components/gameboycore/src/gameboycore.cpp deleted file mode 100644 index 85eade84..00000000 --- a/components/gameboycore/src/gameboycore.cpp +++ /dev/null @@ -1,326 +0,0 @@ - -#include "gameboycore/gameboycore.h" -#include "gameboycore/cartinfo.h" -#include "bitutil.h" - -#include -#include -#include - -namespace gb -{ - /* Private Interface */ - - class GameboyCore::Impl - { - public: - CPU::Ptr cpu; - MMU::Ptr mmu; - GPU::Ptr gpu; - APU::Ptr apu; - Joy::Ptr joy; - Link::Ptr link; - - GPU::RenderScanlineCallback scanline_callback_; - GPU::VBlankCallback vblank_callback_; - APU::AudioSampleCallback audio_sample_callback_; - - void setScanlineCallback(GPU::RenderScanlineCallback fn) - { - scanline_callback_ = fn; - - if (gpu) - gpu->setRenderCallback(scanline_callback_); - } - - void setVBlankCallback(GPU::VBlankCallback fn) - { - vblank_callback_ = fn; - - if (gpu) - gpu->setVBlankCallback(vblank_callback_); - } - - void setAudioSampleCallback(APU::AudioSampleCallback fn) - { - audio_sample_callback_ = fn; - - if (apu) - apu->setAudioSampleCallback(audio_sample_callback_); - } - - void loadROM(const uint8_t* rom, uint32_t size) - { - mmu.reset(new MMU(rom, size)); - - gpu.reset(new GPU(mmu)); - apu.reset(new APU(mmu)); - link.reset(new Link(mmu)); - - cpu.reset(new CPU(mmu, gpu, apu, link)); - - joy.reset(new Joy(*mmu)); - - // setup callback - gpu->setRenderCallback(scanline_callback_); - gpu->setVBlankCallback(vblank_callback_); - apu->setAudioSampleCallback(audio_sample_callback_); - - setColorTheme(ColorTheme::GOLD); - } - - std::vector serialize() const - { - const auto cpu_data = cpu->serialize(); - - const auto ram_start = mmu->getptr(memorymap::SWITCHABLE_ROM_BANK_END + 1); - const auto ram_end = mmu->getptr(memorymap::INTERRUPT_ENABLE); - - const auto serialize_size = cpu_data.size() + static_cast(ram_end - ram_start); - - std::vector data; - data.reserve(serialize_size); - - std::copy(cpu_data.begin(), cpu_data.end(), std::back_inserter(data)); - std::copy(ram_start, ram_end, std::back_inserter(data)); - - return data; - } - - void deserialize(const std::vector& data) - { - std::array cpu_state; - std::copy(data.begin(), data.begin() + cpu_state.size(), cpu_state.begin()); - - const auto ram_start = mmu->getptr(memorymap::SWITCHABLE_ROM_BANK_END + 1); - std::copy(data.begin() + cpu_state.size(), data.end(), ram_start); - } - - void setTimeProvider(const TimeProvider provider) - { - mmu->setTimeProvider(provider); - } - - void setInstructionCallback(std::function fn) - { - cpu->setInstructionCallback(fn); - } - - void setColorTheme(ColorTheme theme) - { - switch (theme) - { - case gb::GameboyCore::ColorTheme::DEFAULT: - gpu->setPaletteColor(255, 255, 255, 0); - gpu->setPaletteColor(196, 196, 196, 1); - gpu->setPaletteColor( 96, 96, 96, 2); - gpu->setPaletteColor( 0, 0, 0, 3); - break; - case gb::GameboyCore::ColorTheme::GOLD: - gpu->setPaletteColor(252, 232, 140, 0); - gpu->setPaletteColor(220, 180, 92, 1); - gpu->setPaletteColor(152, 124, 60, 2); - gpu->setPaletteColor(76, 60, 28, 3); - break; - case gb::GameboyCore::ColorTheme::GREEN: - gpu->setPaletteColor(155, 188, 15, 0); - gpu->setPaletteColor(139, 172, 15, 1); - gpu->setPaletteColor( 48, 98, 48, 2); - gpu->setPaletteColor( 15, 56, 15, 3); - break; - default: - break; - } - } - }; - - /* Public Interface */ - - GameboyCore::GameboyCore() : - impl_(new Impl) - { - } - - GameboyCore::~GameboyCore() - { - delete impl_; - } - - void GameboyCore::update(int steps) - { - while(steps--) - { - impl_->cpu->step(); - } - } - - void GameboyCore::emulateFrame() - { - auto& cpu = impl_->cpu; - auto& ly = impl_->mmu->get(memorymap::LY_REGISTER); - - // Run to starting point of line=0 - while (ly != 0) - { - cpu->step(); - } - - // run for a frames worth of scanlines line=144 - while (ly <= 143) - { - cpu->step(); - } - } - - void GameboyCore::open(const std::string& filename) - { - std::ifstream file{ filename, std::ios::ate | std::ios::binary }; - if (!file.is_open()) - { - throw std::runtime_error(std::string("Could not load ROM file: " + filename)); - } - - auto size = (std::size_t)file.tellg(); - file.seekg(0, std::ios::beg); - - std::vector buffer; - buffer.resize(size); - - file.read((char*)buffer.data(), buffer.size()); - - loadROM(buffer); - } - - void GameboyCore::loadROM(const std::vector& buffer) - { - loadROM(buffer.data(), buffer.size()); - } - - void GameboyCore::loadROM(const uint8_t* rom, uint32_t size) - { - impl_->loadROM(rom, size); - } - - void GameboyCore::reset() - { - impl_->cpu->reset(); - } - - void GameboyCore::setDebugMode(bool debug) - { - impl_->cpu->setDebugMode(debug); - } - - void GameboyCore::setColorTheme(ColorTheme theme) - { - impl_->setColorTheme(theme); - } - - uint8_t GameboyCore::readMemory(uint16_t addr) - { - return impl_->mmu->read(addr); - } - - void GameboyCore::writeMemory(uint16_t addr, uint8_t value) - { - impl_->mmu->write(value, addr); - } - - void GameboyCore::setScanlineCallback(GPU::RenderScanlineCallback callback) - { - impl_->setScanlineCallback(callback); - } - - void GameboyCore::setVBlankCallback(GPU::VBlankCallback callback) - { - impl_->setVBlankCallback(callback); - } - - void GameboyCore::setAudioSampleCallback(APU::AudioSampleCallback callback) - { - impl_->setAudioSampleCallback(callback); - } - - void GameboyCore::input(Joy::Key key, bool pressed) - { - if (pressed) - impl_->joy->press(key); - else - impl_->joy->release(key); - } - - std::vector GameboyCore::getBatteryRam() const - { - return impl_->mmu->getBatteryRam(); - } - - void GameboyCore::setBatteryRam(const std::vector& ram) - { - impl_->mmu->setBatteryRam(ram); - } - - void GameboyCore::linkWrite(uint8_t byte) - { - impl_->link->recieve(byte); - } - - void GameboyCore::setLinkReadyCallback(Link::ReadyCallback callback) - { - impl_->link->setReadyCallback(callback); - } - - std::vector GameboyCore::serialize() const - { - return impl_->serialize(); - } - - void GameboyCore::deserialize(const std::vector& data) - { - impl_->deserialize(data); - } - - void GameboyCore::setTimeProvider(const TimeProvider provider) - { - impl_->setTimeProvider(provider); - } - - void GameboyCore::setInstructionCallback(std::function fn) - { - impl_->setInstructionCallback(fn); - } - - CPU::Ptr& GameboyCore::getCPU() - { - return impl_->cpu; - } - - MMU::Ptr& GameboyCore::getMMU() - { - return impl_->mmu; - } - - GPU::Ptr& GameboyCore::getGPU() - { - return impl_->gpu; - } - - APU::Ptr& GameboyCore::getAPU() - { - return impl_->apu; - } - - Joy::Ptr& GameboyCore::getJoypad() - { - return impl_->joy; - } - - Link::Ptr& GameboyCore::getLink() - { - return impl_->link; - } - - bool GameboyCore::isDone() const - { - return impl_->cpu->isHalted(); - } -} diff --git a/components/gameboycore/src/gpu.cpp b/components/gameboycore/src/gpu.cpp deleted file mode 100644 index 1e1330d7..00000000 --- a/components/gameboycore/src/gpu.cpp +++ /dev/null @@ -1,490 +0,0 @@ -#include "gameboycore/gpu.h" -#include "gameboycore/palette.h" -#include "gameboycore/memorymap.h" -#include "gameboycore/pixel.h" -#include "gameboycore/interrupt_provider.h" -#include "gameboycore/tilemap.h" - -#include "gameboycore/detail/hash.h" - -#include "bitutil.h" - -namespace gb -{ - static constexpr auto HBLANK_CYCLES = 207; - static constexpr auto OAM_ACCESS_CYCLES = 83; - static constexpr auto LCD_TRANSFER_CYCLES = 175; - - static constexpr auto LINE_CYCLES = 456; - static constexpr auto VBLANK_LINE = 144; - static constexpr auto MAX_LINES = 153; - - /* Private Implementation */ - - class GPU::Impl - { - public: - enum class Mode - { - HBLANK, - VBLANK, - OAM, - LCD - }; - - /** - Data needed for HDMA transfer - */ - struct Hdma - { - Hdma() : - transfer_active(false), - source(0), - destination(0), - length(0) - { - } - - bool transfer_active; - uint16_t source; - uint16_t destination; - uint16_t length; - }; - - using CgbPalette = std::array, 8>; - - explicit Impl(MMU::Ptr& mmu) : - mmu_(mmu), - mode_(Mode::OAM), - cycle_count_(0), - line_(0), - lcdc_(mmu->get(memorymap::LCDC_REGISTER)), - stat_(mmu->get(memorymap::LCD_STAT_REGISTER)), - hdma5_(mmu->get(memorymap::HDMA5)), - vblank_provider_(*mmu.get(), InterruptProvider::Interrupt::VBLANK), - stat_provider_(*mmu.get(), InterruptProvider::Interrupt::LCDSTAT), - tilemap_(*mmu.get(), palette_), - cgb_enabled_(mmu->cgbEnabled()) - { - mmu->addWriteHandler(memorymap::LCDC_REGISTER, std::bind(&Impl::lcdcWriteHandler, this, std::placeholders::_1, std::placeholders::_2)); - mmu->addWriteHandler(memorymap::BGPD, std::bind(&Impl::paletteWriteHandler, this, std::placeholders::_1, std::placeholders::_2)); - mmu->addWriteHandler(memorymap::OBPD, std::bind(&Impl::paletteWriteHandler, this, std::placeholders::_1, std::placeholders::_2)); - mmu->addWriteHandler(memorymap::HDMA5, std::bind(&Impl::hdma5WriteHandler, this, std::placeholders::_1, std::placeholders::_2)); - } - - void update(uint8_t cycles, bool ime) - { - if (isClear(lcdc_, memorymap::LCDC::ENABLE)) return; - - cycle_count_ += cycles; - - switch (mode_) - { - case Mode::HBLANK: - // check if the HBLANK period is over - if (hasElapsed(HBLANK_CYCLES)) - { - // update the scan line - updateLY(); - // check if LY matches LYC - compareLyToLyc(ime); - - // check if in VBlank mode - if (line_ == VBLANK_LINE) - { - mode_ = Mode::VBLANK; - vblank_provider_.set(); - - if (vblank_callback_) - vblank_callback_(); - } - else - { - mode_ = Mode::OAM; - } - - checkStatInterrupts(ime); - } - break; - case Mode::OAM: - if (hasElapsed(OAM_ACCESS_CYCLES)) - { - mode_ = Mode::LCD; - } - break; - case Mode::LCD: - if (hasElapsed(LCD_TRANSFER_CYCLES)) - { - // render the current scan line - renderScanline(); - - mode_ = Mode::HBLANK; - // perform an hdma transfer - doHdma(); - checkStatInterrupts(ime); - } - break; - case Mode::VBLANK: - if (hasElapsed(LINE_CYCLES)) - { - updateLY(); - compareLyToLyc(ime); - - if (line_ == 0) - { - mode_ = Mode::OAM; - checkStatInterrupts(ime); - } - } - break; - } - - // update LCDC stat mode - stat_ = (stat_ & 0xFC) | (static_cast(mode_)); - } - - void setRenderCallback(RenderScanlineCallback callback) - { - render_scanline_ = callback; - } - - void setVBlankCallback(VBlankCallback callback) - { - vblank_callback_ = callback; - } - - void setDefaultPaletteColor(uint8_t r, uint8_t g, uint8_t b, int idx) - { - palette_.set(r, g, b, idx); - } - - std::vector getBackgroundTileMap() - { - return tilemap_.getBackgroundTileMap(); - } - - std::array getSpriteCache() const - { - return tilemap_.getSpriteCache(); - } - - std::size_t getBackgroundHash() - { - return tilemap_.hashBackground(); - } - - private: - - void renderScanline() - { - Scanline scanline; - std::array color_line; - - auto background_palette = palette_.get(mmu_->read(memorymap::BGP_REGISTER)); - - // get lcd config - - const auto background_enabled = isSet(lcdc_, memorymap::LCDC::BG_DISPLAY_ON) != 0; - const auto window_enabled = isSet(lcdc_, memorymap::LCDC::WINDOW_ON) != 0; - const auto sprites_enabled = isSet(lcdc_, memorymap::LCDC::OBJ_ON) != 0; - - // get background tile line - const auto background = tilemap_.getBackground(line_, cgb_enabled_); - - // get window overlay tile line - const auto window = tilemap_.getWindowOverlay(line_); - const auto wx = mmu_->read(memorymap::WX_REGISTER); - const auto wy = mmu_->read(memorymap::WY_REGISTER); - - // compute a scan line - for (auto pixel_idx = 0u; pixel_idx < scanline.size(); ++pixel_idx) - { - auto tileinfo = 0u; - - if (window_enabled && line_ >= (int)wy && (int)pixel_idx >= (wx - 7)) - tileinfo = window[pixel_idx]; - else if (background_enabled || cgb_enabled_) - tileinfo = background[pixel_idx]; - else - tileinfo = 0; - - auto color_number = tileinfo & 0x03; - auto color_palette = (tileinfo >> 2) & 0x07; - auto priority = (tileinfo >> 5); - - color_line[pixel_idx] = (uint8_t)(color_number | (priority << 2)); - - if (cgb_enabled_) - { - scanline[pixel_idx] = cgb_background_palettes_[color_palette][color_number]; - } - else - { - scanline[pixel_idx] = background_palette[color_number]; - } - } - - if (sprites_enabled) - tilemap_.drawSprites(scanline, color_line, line_, cgb_enabled_, cgb_sprite_palette_); - - // send scan line to the renderer - if (render_scanline_ && line_ < VBLANK_LINE) - render_scanline_(scanline, line_); - } - - bool hasElapsed(int mode_cycles) - { - if (cycle_count_ >= mode_cycles) - { - cycle_count_ -= mode_cycles; - return true; - } - return false; - } - - void updateLY() - { - line_ = (line_ + 1) % MAX_LINES; - mmu_->write((uint8_t)line_, memorymap::LY_REGISTER); - } - - void lcdcWriteHandler(uint8_t value, uint16_t) - { - bool enable = (value & memorymap::LCDC::ENABLE) != 0; - - if (enable && isClear(lcdc_, memorymap::LCDC::ENABLE)) - { - line_ = 0; - cycle_count_ = 0; - } - - lcdc_ = value; - } - - void paletteWriteHandler(uint8_t value, uint16_t addr) - { - if (addr == memorymap::BGPD) - { - setPalette(cgb_background_palettes_, value, memorymap::BGPI); - } - else if(addr == memorymap::OBPD) - { - setPalette(cgb_sprite_palette_, value, memorymap::OBPI); - } - } - - void setPalette(CgbPalette& palettes, uint8_t value, uint16_t index_reg) - { - // get the background palette index - const auto index = mmu_->read(index_reg); - - // extract high byte, color index and palette index info from background palette index - auto hi = index & 0x01; - auto color_idx = (index >> 1) & 0x03; - auto palette_idx = (index >> 3) & 0x07; - - // RGB value break down - // MSB: | xBBBBBGG | - // LBS: | GGGRRRRR | - - auto& palette_color = palettes[palette_idx][color_idx]; - - if (hi) - { - palette_color.b = (value >> 2) & 0x1F; - palette_color.g |= ((value & 0x03) << 3); - - palette_color = translateRGB(palette_color); - } - else - { - palette_color.g = ((value & 0xE0) >> 5); - palette_color.r = (value & 0x1F); - } - - // auto increment index if increment flag is set - if (isBitSet(index, 7)) - { - mmu_->write((uint8_t)(index + 1), index_reg); - } - } - - Pixel translateRGB(const Pixel& pixel) - { - Pixel out; - out.r = scale(pixel.r); - out.g = scale(pixel.g); - out.b = scale(pixel.b); - - return out; - } - - uint8_t scale(uint8_t v) - { - auto old_range = (0x1F - 0x00); - auto new_range = (0xFF - 0x00); - - return (uint8_t)((v * new_range) / old_range); - } - - void hdma5WriteHandler(uint8_t value, uint16_t) - { - uint16_t src = word(mmu_->read(memorymap::HDMA1), mmu_->read(memorymap::HDMA2)) & 0xFFF0; - uint16_t dest = word(((mmu_->read(memorymap::HDMA3) & 0x1F) | 0x80), mmu_->read(memorymap::HDMA4)) & 0xFFF0; - uint16_t length = ((value & 0x7F) + 1) * 0x10; - - if (isBitClear(value, 7) && !hdma_.transfer_active) - { - // perform a general purpose DMA - mmu_->dma(dest, src, length); - } - else if (isBitClear(value, 7) && hdma_.transfer_active) - { - // disable an active hdma transfer - hdma_.transfer_active = false; - } - else - { - // initialize an HDMA transfer - hdma_.source = src; - hdma_.destination = dest; - hdma_.length = length; - hdma_.transfer_active = true; - } - - hdma5_ = value; - } - - void doHdma() - { - if (hdma_.transfer_active) - { - // hdma only works between this range - if (line_ >= 0 && line_ <= 143) - { - // transfer $10 bytes - mmu_->dma(hdma_.destination, hdma_.source, 0x10); - // advance source $10 bytes - hdma_.source += 0x10; - // advance destination $10 bytes - hdma_.destination += 0x10; - // count down the length - hdma_.length -= 0x10; - - if (hdma_.length == 0) - { - hdma_.transfer_active = false; - } - } - } - } - - void compareLyToLyc(bool ime) - { - auto lyc = mmu_->read(memorymap::LYC_REGISTER); - - if ((uint8_t)line_ == lyc) - { - //stat_ |= memorymap::Stat::LYCLY; - setMask(stat_, memorymap::Stat::LYCLY); - } - else - { - //stat_ &= ~(memorymap::Stat::LYCLY); - clearMask(stat_, memorymap::Stat::LYCLY); - } - - // check the ly=lyc flag - if (stat_ & memorymap::Stat::LYCLY) - { - if (ime) - stat_provider_.set(); - } - } - - void checkStatInterrupts(bool ime) - { - // check mode selection interrupts - uint8_t mask = (1 << (3 + static_cast(mode_))); - - if (stat_ & mask) - { - if (ime) - stat_provider_.set(); - } - } - - private: - MMU::Ptr& mmu_; - - Mode mode_; - int cycle_count_; - int line_; - uint8_t& lcdc_; - uint8_t& stat_; - uint8_t& hdma5_; - Hdma hdma_; - - InterruptProvider vblank_provider_; - InterruptProvider stat_provider_; - - detail::TileMap tilemap_; - Palette palette_; - - RenderScanlineCallback render_scanline_; - VBlankCallback vblank_callback_; - - bool cgb_enabled_; - - CgbPalette cgb_background_palettes_; - CgbPalette cgb_sprite_palette_; - }; - - /* Public Implementation */ - - GPU::GPU(MMU::Ptr& mmu) : - impl_(new Impl(mmu)) - { - } - - GPU::~GPU() - { - delete impl_; - } - - void GPU::update(uint8_t cycles, bool ime) - { - impl_->update(cycles, ime); - } - - void GPU::setRenderCallback(RenderScanlineCallback callback) - { - impl_->setRenderCallback(callback); - } - - void GPU::setVBlankCallback(VBlankCallback callback) - { - impl_->setVBlankCallback(callback); - } - - void GPU::setPaletteColor(uint8_t r, uint8_t g, uint8_t b, int idx) - { - impl_->setDefaultPaletteColor(r, g, b, idx); - } - - std::vector GPU::getBackgroundTileMap() - { - return impl_->getBackgroundTileMap(); - } - - std::array GPU::getSpriteCache() const - { - return impl_->getSpriteCache(); - } - - std::size_t GPU::getBackgroundHash() - { - return impl_->getBackgroundHash(); - } - -} // namespace gb diff --git a/components/gameboycore/src/instruction.cpp b/components/gameboycore/src/instruction.cpp deleted file mode 100644 index 691b7bfb..00000000 --- a/components/gameboycore/src/instruction.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "gameboycore/instruction.h" -#include "gameboycore/opcodeinfo.h" - -#include - -namespace gb -{ - std::string disassemble(const Instruction& instruction) - { - const auto opcode = instruction.opcode; - const auto page = static_cast(instruction.page); - - const auto info = getOpcodeInfo(opcode, page); - - //char buf[32]; - std::array buf; - - switch (info.userdata) - { - case OperandType::NONE: - std::sprintf(&buf[0], "%s", info.disassembly); - break; - case OperandType::IMM8: - std::sprintf(&buf[0], info.disassembly, instruction.operand_data[0]); - break; - case OperandType::IMM16: - std::sprintf(&buf[0], info.disassembly, (instruction.operand_data[0] | instruction.operand_data[1] << 8)); - break; - default: - break; - } - - return std::string(&buf[0]); - } -} \ No newline at end of file diff --git a/components/gameboycore/src/joypad.cpp b/components/gameboycore/src/joypad.cpp deleted file mode 100644 index c720f878..00000000 --- a/components/gameboycore/src/joypad.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/** - \author Natesh Narain - \date Oct 8 2016 -*/ - -#include "gameboycore/joypad.h" -#include "gameboycore/interrupt_provider.h" - -namespace gb -{ - /* Private Implementation */ - - class Joy::Impl - { - public: - explicit Impl(MMU& mmu) : - mmu_(mmu), - reg_(mmu.get(memorymap::JOYPAD_REGISTER)), - keys_(0xFF), - interrupt_provider_(mmu, InterruptProvider::Interrupt::JOYPAD) - { - // add handlers - mmu_.addReadHandler(memorymap::JOYPAD_REGISTER, std::bind(&Impl::readJoypad, this, std::placeholders::_1)); - } - - void press(Key key) - { - keys_ &= ~(1 << static_cast(key)); - interrupt_provider_.set(); - } - - void release(Key key) - { - keys_ |= (1 << static_cast(key)); - } - - private: - uint8_t readJoypad(uint16_t) const - { - uint8_t hi = (reg_ & 0xF0); - - if ((hi & 0x30) == 0x10 || (hi & 0x30) == 0x20) - { - // first 2 bits of high nybble is group selection - uint8_t group = ((~(reg_ >> 4)) & 0x03) - 1; - - uint8_t selection = (keys_ >> (group * 4)) & 0x0F; - - return (reg_ & 0xF0) | selection; - } - else - { - return reg_ | 0x0F; - } - } - - private: - MMU& mmu_; - uint8_t& reg_; - uint8_t keys_; - - InterruptProvider interrupt_provider_; - }; - - - /* Public Implementation */ - - Joy::Joy(MMU& mmu) : - impl_(new Impl(mmu)) - { - - } - - Joy::~Joy() - { - delete impl_; - } - - void Joy::press(Key key) - { - impl_->press(key); - } - - void Joy::release(Key key) - { - impl_->release(key); - } -} \ No newline at end of file diff --git a/components/gameboycore/src/link.cpp b/components/gameboycore/src/link.cpp deleted file mode 100644 index 8b876c43..00000000 --- a/components/gameboycore/src/link.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include "gameboycore/link.h" -#include "gameboycore/memorymap.h" -#include "gameboycore/interrupt_provider.h" - -#include "bitutil.h" - -#include -#include // TODO: remove - -namespace gb -{ - /* Private Interface */ - - class Link::Impl - { - public: - - explicit Impl(MMU::Ptr& mmu) : - control_(mmu->get(memorymap::SC_REGISTER)), - byte_to_transfer_(0), - byte_to_recieve_(0), - serial_interrupt_{ *mmu.get(), InterruptProvider::Interrupt::SERIAL }, - shift_clock_(0), - shift_counter_(0), - shift_clock_rate_(0), - pending_recieve_(false) - { - // serial byte handlers - mmu->addReadHandler(memorymap::SB_REGISTER, std::bind(&Impl::recieveHandler, this, std::placeholders::_1)); - mmu->addWriteHandler(memorymap::SB_REGISTER, std::bind(&Impl::sendHandler, this, std::placeholders::_1, std::placeholders::_2)); - - // control callback - mmu->addWriteHandler(memorymap::SC_REGISTER, std::bind(&Impl::control, this, std::placeholders::_1, std::placeholders::_2)); - } - - ~Impl() - { - } - - void update(uint8_t cycles) - { - if (!isTransferring() || pending_recieve_) return; - - // if using internal shift clock, run clocking logic - if (getLinkMode() == Link::Mode::INTERNAL) - { - internalClock(cycles); - } - else - { - // transferring in external clock mode, signal transfer ready - - signalReady(); - } - } - - void internalClock(uint8_t cycles) - { - // increment shift clock - shift_clock_ += cycles; - - if (shift_clock_ >= shift_clock_rate_) - { - shift_clock_ -= shift_clock_rate_; - - shift_counter_++; - - if (shift_counter_ == 8) - { - // signal to the host system that this core is ready to do the transfer - signalReady(); - - shift_counter_ = 0; - } - } - } - - void control(uint8_t value, uint16_t) noexcept - { - control_ = (control_ & 0x80) | 0x02 | value; - - shift_clock_rate_ = getTransferRate(value); - - pending_recieve_ = false; - } - - void sendHandler(uint8_t value, uint16_t) noexcept - { - byte_to_transfer_ = value; - } - - uint8_t recieveHandler(uint16_t) const noexcept - { - return byte_to_recieve_; - } - - /** - Data into the core - */ - void recieve(uint8_t byte) - { - // recieve the byte - byte_to_recieve_ = byte; - // set serial interrupt - serial_interrupt_.set(); - // clear transfer flag - clearMask(control_, memorymap::SC::TRANSFER); - - pending_recieve_ = false; - } - - void setReadyCallback(const ReadyCallback& callback) - { - ready_callback_ = callback; - } - - private: - - bool isTransferring() const noexcept - { - return isSet(control_, memorymap::SC::TRANSFER) != 0; - } - - int getTransferRate(uint8_t sc) - { - // TODO: CGB speed modes - (void)sc; - return 4194304 / 8192; - } - - void signalReady() - { - if (ready_callback_) - { - ready_callback_(byte_to_transfer_, getLinkMode()); - pending_recieve_ = true; - } - } - - Mode getLinkMode() const noexcept - { - if (isSet(control_, memorymap::SC::CLOCK_MODE)) - { - return Mode::INTERNAL; - } - else - { - return Mode::EXTERNAL; - } - } - - private: - //! Serial Control Register - uint8_t& control_; - - //! Byte to be transfered to the opponent gameboy - uint8_t byte_to_transfer_; - //! Byte recieved by the opponent gameboy - uint8_t byte_to_recieve_; - - //! ready callback - ReadyCallback ready_callback_; - - //! Serial interrupt provider - InterruptProvider serial_interrupt_; - - //! Internal Timer - int shift_clock_; - //! Count the shift clock overflows - int shift_counter_; - //! Transfer rate - int shift_clock_rate_; - - //! Flag indicating that the link port is waiting to recieve a byte - bool pending_recieve_; - }; - - /* Public Interface */ - - Link::Link(MMU::Ptr& mmu) : - impl_(new Impl(mmu)) - { - } - - void Link::update(uint8_t cycles) - { - impl_->update(cycles); - } - - void Link::recieve(uint8_t byte) - { - impl_->recieve(byte); - } - - void Link::setReadyCallback(const ReadyCallback& callback) - { - impl_->setReadyCallback(callback); - } - - Link::~Link() - { - delete impl_; - } -} \ No newline at end of file diff --git a/components/gameboycore/src/link_cable.cpp b/components/gameboycore/src/link_cable.cpp deleted file mode 100644 index 00b52abd..00000000 --- a/components/gameboycore/src/link_cable.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "gameboycore/link_cable.h" - -namespace gb -{ - /* Private Implementation */ - - class LinkCable::Impl - { - public: - //! Contains link ready status, byte to transfer and clocking mode - struct LinkData - { - LinkData() : - mode(Link::Mode::EXTERNAL), - byte(0), - ready(false) - { - } - - Link::Mode mode; - uint8_t byte; - bool ready; - }; - - void link1ReadyCallback(uint8_t byte, Link::Mode mode) - { - link_data1_.byte = byte; - link_data1_.mode = mode; - link_data1_.ready = true; - - update(); - } - - void link2ReadyCallback(uint8_t byte, Link::Mode mode) - { - link_data2_.byte = byte; - link_data2_.mode = mode; - link_data2_.ready = true; - - update(); - } - - void setLink1RecieveCallback(const RecieveCallback& callback) - { - recieve1_ = callback; - } - - void setLink2RecieveCallback(const RecieveCallback& callback) - { - recieve2_ = callback; - } - - private: - void update() - { - if (link_data1_.ready && link_data2_.ready) - { - // both links indicate they are ready to transfer - - if (link_data1_.mode != link_data2_.mode) - { - transfer(); - } - } - else - { - // only one link is ready to tranfer, or neither are - - - if (link_data1_.ready) - { - if (link_data1_.mode == Link::Mode::INTERNAL) - { - // if this link is the master device, supply it with a $FF - link_data2_.byte = 0xFF; - transfer(); - } - } - - if (link_data2_.ready) - { - if (link_data2_.mode == Link::Mode::INTERNAL) - { - link_data1_.byte = 0xFF; - transfer(); - } - } - - } - } - - void transfer() - { - if (recieve1_) - recieve1_(link_data2_.byte); - - if (recieve2_) - recieve2_(link_data1_.byte); - - link_data1_.ready = false; - link_data2_.ready = false; - } - - private: - RecieveCallback recieve1_; - RecieveCallback recieve2_; - - LinkData link_data1_; - LinkData link_data2_; - }; - - /* Public Implementation */ - - LinkCable::LinkCable() : - impl_(new Impl) - { - } - - LinkCable::~LinkCable() - { - delete impl_; - } - - void LinkCable::link1ReadyCallback(uint8_t byte, Link::Mode mode) - { - impl_->link1ReadyCallback(byte, mode); - } - - void LinkCable::link2ReadyCallback(uint8_t byte, Link::Mode mode) - { - impl_->link2ReadyCallback(byte, mode); - } - - void LinkCable::setLink1RecieveCallback(const RecieveCallback& callback) - { - impl_->setLink1RecieveCallback(callback); - } - - void LinkCable::setLink2RecieveCallback(const RecieveCallback& callback) - { - impl_->setLink2RecieveCallback(callback); - } - -} \ No newline at end of file diff --git a/components/gameboycore/src/mbc.cpp b/components/gameboycore/src/mbc.cpp deleted file mode 100644 index d16b2b1c..00000000 --- a/components/gameboycore/src/mbc.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include "gameboycore/mbc.h" -#include "gameboycore/memorymap.h" - -#include -#include -#include - -namespace gb -{ - namespace detail - { - MBC::MBC(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enable) : - xram_enable_(false), - rom_bank_(0), - ram_bank_(0), - num_rom_banks_(0), - num_cartridge_ram_banks_(0), - cgb_enabled_(cgb_enable), - vram_banks_(0), - num_internal_ram_banks_(0) - { - loadMemory(rom, size, rom_size, ram_size); - } - - void MBC::write(uint8_t value, uint16_t addr) - { - // check for write to ROM - if (addr <= 0x7FFF) - { - control(value, addr); - } - else - { - // get the memory index from the addr - auto idx = getIndex(addr, rom_bank_, ram_bank_); - - // check if writing to external RAM - if (addr >= 0xA000 && addr <= 0xBFFF) - { - // write to external ram if it is enabled - if (xram_enable_) - memory_[idx] = value; - } - else - { - memory_[idx] = value; - } - } - } - - uint8_t MBC::read(uint16_t addr) const - { - // return FF is read from external ram and it is not enabled - if (addr >= 0xA000 && addr <= 0xBFFF && !xram_enable_) - { - return 0xFF; - } - - auto idx = getIndex(addr, rom_bank_, ram_bank_); - return memory_[idx]; - } - - uint8_t MBC::readVram(uint16_t addr, uint8_t bank) - { - auto index = (addr) + (16 * KILO_BYTE * (num_rom_banks_ - 1)) + ((8 * KILO_BYTE) * bank); - return memory_[index]; - } - - uint8_t& MBC::get(uint16_t addr) - { - return memory_[getIndex(addr, rom_bank_, ram_bank_)]; - } - - uint8_t* MBC::getptr(uint16_t addr) - { - return &memory_[getIndex(addr, rom_bank_, ram_bank_)]; - } - - int MBC::resolveAddress(const uint16_t& addr) const - { - return getIndex(addr, rom_bank_, ram_bank_); - } - - std::vector MBC::getRange(uint16_t start, uint16_t end) const - { - auto start_idx = getIndex(start, rom_bank_, ram_bank_); - auto end_idx = getIndex(end, rom_bank_, ram_bank_); - return std::vector(memory_.begin() + start_idx, memory_.begin() + end_idx); - } - - void MBC::setMemory(uint16_t start, const std::vector& mem) - { - // TODO: error checks - std::copy(mem.begin(), mem.end(), memory_.begin() + getIndex(start, rom_bank_, ram_bank_)); - } - - std::vector MBC::getXram() const - { - // index the points around external RAM to capture all bank - auto start = getIndex(memorymap::EXTERNAL_RAM_START, rom_bank_, 0); - auto end = getIndex(memorymap::EXTERNAL_RAM_END, rom_bank_, num_cartridge_ram_banks_ - 1); - - // Copy external RAM range. Add 1 so range [START, END] is inclusive - return std::vector(memory_.begin() + start, memory_.begin() + end + 1); - } - - int MBC::getRomBank() const - { - return rom_bank_; - } - - int MBC::getRamBank() const - { - return ram_bank_; - } - - bool MBC::isXramEnabled() const - { - return xram_enable_; - } - - int MBC::getIndex(uint16_t addr, int rom_bank, int ram_bank) const - { - switch (addr & 0xF000) - { - case 0x0000: - case 0x1000: - case 0x2000: - case 0x3000: - return addr; - case 0x4000: - case 0x5000: - case 0x6000: - case 0x7000: - return (addr) + (kilo(16) * rom_bank); - case 0x8000: - case 0x9000: - return (addr) + (kilo(16) * (num_rom_banks_-1)) + getVramOffset(); - case 0xA000: - case 0xB000: - return (addr) + (kilo(16) * (num_rom_banks_ - 1)) + (kilo(8) * (vram_banks_ - 1)) + (kilo(8) * ram_bank); - case 0xC000: - return (addr) + (kilo(16) * (num_rom_banks_ - 1)) + (kilo(8) * (vram_banks_ - 1)) + (kilo(8) * (num_cartridge_ram_banks_-1)); - case 0xD000: - return (addr)+(kilo(16) * (num_rom_banks_ - 1)) + (kilo(8) * (vram_banks_ - 1)) + (kilo(8) * (num_cartridge_ram_banks_ - 1)) + getInternalRamOffset(); - case 0xE000: - case 0xF000: - return (addr)+(kilo(16) * (num_rom_banks_ - 1)) + (kilo(8) * (vram_banks_ - 1)) + (kilo(8) * (num_cartridge_ram_banks_ - 1)) + (kilo(4) * (num_internal_ram_banks_-1)); - } - - return 0; - } - - int MBC::getIoIndex(uint16_t addr) const - { - return (addr)+ - (kilo(16) * (num_rom_banks_ - 1)) + - (kilo(8) * (vram_banks_ - 1)) + - (kilo(8) * (num_cartridge_ram_banks_ - 1)) + - (kilo(4)* (num_internal_ram_banks_-1)); - } - - int MBC::getVramOffset() const - { - return kilo(8) * (memory_[getIoIndex(memorymap::VBK_REGISTER)] & 0x01); - } - - int MBC::getInternalRamOffset() const - { - auto bank_number = memory_[getIoIndex(memorymap::SVBK_REGISTER)] & 0x07; - if (bank_number < 2) bank_number = 0; - - return kilo(4) * (bank_number); - } - - void MBC::loadMemory(const uint8_t* rom, std::size_t size, uint8_t rom_size, uint8_t ram_size) - { - // lookup tables for number of ROM banks a cartridge has - static const unsigned int rom_banks1[] = { - 2, 4, 8, 16, 32, 64, 128, 256 - }; - static const unsigned int rom_banks2[] = { - 72, 80, 96 - }; - - if (rom_size <= static_cast(MBC::ROM::MB4)) - { - // look up the total number of banks this cartridge has - auto cartridge_rom_banks = rom_banks1[rom_size]; - // the number of switchable ROM banks is 2 less than the total - // since there are always 2 available in the $0000 - $3FFF range - num_rom_banks_ = cartridge_rom_banks - 1; - } - else - { - // the number of switchable ROM banks is 2 less than the total - // since there are always 2 available in the $0000 - $3FFF range - num_rom_banks_ = rom_banks2[rom_size] - 1; - } - - num_cartridge_ram_banks_ = (static_cast(ram_size) == MBC::XRAM::KB32) ? 4 : 1; - - num_internal_ram_banks_ = (cgb_enabled_) ? 7 : 1; - vram_banks_ = (cgb_enabled_) ? 2 : 1; - - // memory sizes - const auto rom_bank0_fixed = kilo(16); // $0000 - $3FFF - const auto rom_switchable = kilo(16) * num_rom_banks_; // $4000 - $7FFF - const auto vram = kilo(8) * vram_banks_; // $8000 - $9FFF - const auto ram_cartridge_switchable = kilo(8) * num_cartridge_ram_banks_; // $A000 - $B000 - const auto ram_bank0_fixed = kilo(4); // $C000 - $CFFF - const auto ram_internal_switchable = kilo(4) * num_internal_ram_banks_; // $D000 - $DFFF - const auto high_ram = kilo(8); // $E000 - $FFFF - - const auto memory_size = rom_bank0_fixed + rom_switchable + vram + ram_cartridge_switchable + ram_bank0_fixed + ram_internal_switchable + high_ram; - - memory_.resize(memory_size); - - std::memcpy((char*)&memory_[0], rom, size); - } - - unsigned int MBC::kilo(unsigned int n) const - { - return KILO_BYTE * n; - } - - std::size_t MBC::getVirtualMemorySize() const - { - return memory_.size(); - } - - MBC::~MBC() - { - } - } -} \ No newline at end of file diff --git a/components/gameboycore/src/mbc1.cpp b/components/gameboycore/src/mbc1.cpp deleted file mode 100644 index 5fd87ca3..00000000 --- a/components/gameboycore/src/mbc1.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "gameboycore/mbc1.h" - -namespace gb -{ - namespace detail - { - MBC1::MBC1(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enabled) : - MBC(rom, size, rom_size, ram_size, cgb_enabled), - rom_bank_lower_bits_(0), - rom_bank_upper_bits_(0), - mode_(MemoryMode::ROM) - { - } - - void MBC1::control(uint8_t value, uint16_t addr) - { - if (addr <= 0x1FFF) - { - xram_enable_ = ((value & 0x0F) == 0x0A); - } - else if (addr >= 0x2000 && addr <= 0x3FFF) - { - rom_bank_lower_bits_ = value & 0x1F; - - if (mode_ == MemoryMode::ROM) - { - selectRomBank(rom_bank_lower_bits_, rom_bank_upper_bits_); - } - else - { - rom_bank_upper_bits_ = 0; - selectRomBank(rom_bank_lower_bits_, rom_bank_upper_bits_); - } - } - else if (addr >= 0x4000 && addr <= 0x5FFF) - { - if (mode_ == MemoryMode::ROM) - { - rom_bank_upper_bits_ = value & 0x03; - selectRomBank(rom_bank_lower_bits_, rom_bank_upper_bits_); - } - else - { - selectRamBank(value & 0x3); - } - } - else if (addr >= 0x6000 && addr <= 0x7FFF) - { - mode_ = static_cast(value & 0x01); - - if (mode_ == MemoryMode::RAM) - { - rom_bank_upper_bits_ = 0; - selectRomBank(rom_bank_lower_bits_, rom_bank_upper_bits_); - } - } - } - - void MBC1::selectRomBank(uint8_t lo, uint8_t hi) - { - auto bank_number = ((hi << 5) | lo); - - // potentially remap the rom bank number - switch (bank_number) - { - case 0x00: - case 0x20: - case 0x40: - case 0x60: - bank_number++; - break; - default: - // ... - break; - } - - rom_bank_ = bank_number - 1; - } - - void MBC1::selectRamBank(uint8_t ram_bank_number) - { - ram_bank_ = ram_bank_number; - } - } -} \ No newline at end of file diff --git a/components/gameboycore/src/mbc2.cpp b/components/gameboycore/src/mbc2.cpp deleted file mode 100644 index ead09bda..00000000 --- a/components/gameboycore/src/mbc2.cpp +++ /dev/null @@ -1,45 +0,0 @@ - -#include "gameboycore/mbc2.h" - -#include "bitutil.h" - -namespace gb -{ - namespace detail - { - MBC2::MBC2(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enable) : - MBC(rom, size, rom_size, ram_size, cgb_enable) - { - } - - void MBC2::write(uint8_t value, uint16_t addr) - { - // MBC2 only uses the lower 4 bits - MBC::write(value & 0x0F, addr); - } - - void MBC2::control(uint8_t value, uint16_t addr) - { - if (addr <= 0x1FFF) - { - // least significant bit of upper byte in address must be zero - if (isClear(addr, 0x0100)) - { - xram_enable_ = ((value & 0x0F) == 0x0A); - } - } - else if (addr >= 0x2000 && addr <= 0x3FFF) - { - // least significant bit of upper byte in address must be one - if (isSet(addr, 0x0100)) - { - rom_bank_ = (value & 0x0F) - 1; - } - } - } - - MBC2::~MBC2() - { - } - } -} diff --git a/components/gameboycore/src/mbc3.cpp b/components/gameboycore/src/mbc3.cpp deleted file mode 100644 index 8a8caf73..00000000 --- a/components/gameboycore/src/mbc3.cpp +++ /dev/null @@ -1,78 +0,0 @@ - -#include "gameboycore/mbc3.h" - -namespace gb -{ - namespace detail - { - MBC3::MBC3(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enable) - : MBC(rom, size, rom_size, ram_size, cgb_enable) - , latch_ctl_{0} - { - } - - uint8_t MBC3::read(uint16_t addr) const - { - if (addr >= 0xA000 && addr <= 0xBFFF && rtc_.isEnabled()) - { - return rtc_.get(); - } - else - { - return MBC::read(addr); - } - } - - void MBC3::control(uint8_t value, uint16_t addr) - { - if (addr <= 0x1FFF) - { - xram_enable_ = ((value & 0x0F) == 0x0A); - } - else if (addr >= 0x2000 && addr <= 0x3FFF) - { - auto bank_select = (value == 0) ? 1 : value; - rom_bank_ = bank_select - 1; - } - else if (addr >= 0x4000 && addr <= 0x5FFF) - { - // is a RAM bank number - if (value <= 0x03) - { - ram_bank_ = value & 0x0F; - rtc_.setEnable(false); - } - else if (value >= 0x08 && value <= 0x0C) - { - rtc_.setEnable(true); - rtc_.select(value); - } - } - else if(addr >= 0x6000 && addr <= 0x7FFF) - { - if (value == 0x00) - { - latch_ctl_++; - } - else if (latch_ctl_ == 1 && value == 0x01) - { - rtc_.latch(); - latch_ctl_ = 0; - } - else - { - latch_ctl_ = 0; - } - } - } - - void MBC3::setTimeProvider(TimeProvider provider) - { - rtc_.setTimeProvider(provider); - } - - MBC3::~MBC3() - { - } - } -} diff --git a/components/gameboycore/src/mbc5.cpp b/components/gameboycore/src/mbc5.cpp deleted file mode 100644 index 06aa5f46..00000000 --- a/components/gameboycore/src/mbc5.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "gameboycore/mbc5.h" - -namespace gb -{ - namespace detail - { - MBC5::MBC5(const uint8_t* rom, uint32_t size, uint8_t rom_size, uint8_t ram_size, bool cgb_enable) : - MBC(rom, size, rom_size, ram_size, cgb_enable), - rom_bank_lower_bits_(0), - rom_bank_upper_bit_(0) - { - } - - void MBC5::control(uint8_t value, uint16_t addr) - { - if (addr <= 0x1FFF) - { - // enable / disable external ram - xram_enable_ = ((value & 0x0F) == 0x0A); - } - else if (addr >= 0x2000 && addr <= 0x2FFF) - { - // lower 8 bits of rom bank number - rom_bank_lower_bits_ = value; - selectRomBank(rom_bank_lower_bits_, rom_bank_upper_bit_); - } - else if (addr >= 0x3000 && addr <= 0x3FFF) - { - // 9th bit of rom bank number - rom_bank_upper_bit_ = value; - selectRomBank(rom_bank_lower_bits_, rom_bank_upper_bit_); - } - else if (addr >= 0x4000 && addr <= 0x5FFF) - { - // ram bank number - ram_bank_ = value & 0x0F; - } - } - - void MBC5::selectRomBank(uint8_t lo, uint8_t hi) - { - rom_bank_ = ((hi & 0x0001) << 8) | (lo & 0xFFFF); - - if (rom_bank_ > 0) rom_bank_ -= 1; - } - - MBC5::~MBC5() - { - } - } -} \ No newline at end of file diff --git a/components/gameboycore/src/mmu.cpp b/components/gameboycore/src/mmu.cpp deleted file mode 100644 index 888509fa..00000000 --- a/components/gameboycore/src/mmu.cpp +++ /dev/null @@ -1,334 +0,0 @@ - -/** - \author Natesh Narain -*/ - -#include "gameboycore/mmu.h" -#include "gameboycore/mbc.h" -#include "gameboycore/mbc1.h" -#include "gameboycore/mbc2.h" -#include "gameboycore/mbc3.h" -#include "gameboycore/mbc5.h" -#include "gameboycore/cartinfo.h" -#include "gameboycore/memorymap.h" - -#include "bitutil.h" - -#include -#include -#include - -namespace gb -{ - /* Private Interface */ - - class MMU::Impl - { - public: - Impl(const uint8_t* rom, uint32_t size) - : oam_updated_{false} - , cgb_enabled_{false} - { - load(rom, size); - } - - ~Impl() - { - } - - void load(const uint8_t* rom, uint32_t size) - { - CartInfo header = RomParser::parse(rom); - - cgb_enabled_ = header.cgb_enabled; - - switch (static_cast(header.type)) - { - // Supports MBC1 - // Note: MBC1 handles the ROM only case - case detail::MBC::Type::ROM_ONLY: - case detail::MBC::Type::MBC1: - case detail::MBC::Type::MBC1_RAM: - case detail::MBC::Type::MBC1_RAM_BAT: - mbc_.reset(new detail::MBC1(rom, size, header.rom_size, header.ram_size, cgb_enabled_)); - break; - - case detail::MBC::Type::MBC2: - case detail::MBC::Type::MBC2_BAT: - mbc_.reset(new detail::MBC2(rom, size, header.rom_size, header.ram_size, cgb_enabled_)); - break; - - case detail::MBC::Type::MBC3: - case detail::MBC::Type::MBC3_RAM: - case detail::MBC::Type::MBC3_RAM_BAT: - case detail::MBC::Type::MBC3_TIME_BAT: - case detail::MBC::Type::MBC3_TIME_RAM_BAT: - mbc_.reset(new detail::MBC3(rom, size, header.rom_size, header.ram_size, cgb_enabled_)); - break; - - // TODO: MBC4 - case detail::MBC::Type::MBC5: - case detail::MBC::Type::MBC5_RAM: - case detail::MBC::Type::MBC5_RAM_BAT: - case detail::MBC::Type::MBC5_RUMBLE: - case detail::MBC::Type::MBC5_RUMBLE_RAM: - case detail::MBC::Type::MBC5_RUMBLE_RAM_BAT: - mbc_.reset(new detail::MBC5(rom, size, header.rom_size, header.ram_size, cgb_enabled_)); - break; - - default: - throw std::runtime_error("Unsupported cartridge type :("); - break; - } - - // initialize joypad keys to not pressed - mbc_->write(0x0F, memorymap::JOYPAD_REGISTER); - - loadResetValues(); - } - - uint8_t read(uint16_t addr) - { - if (addr >= 0xFF00 && addr <= 0xFF7F && read_handlers_[addr - 0xFF00]) - { - return read_handlers_[addr - 0xFF00](addr); - } - else - { - return mbc_->read(addr); - } - } - - void write(uint8_t value, uint16_t addr) - { - if (addr == memorymap::DMA_REGISTER) - { - oamTransfer(value); - } - else if (addr == memorymap::JOYPAD_REGISTER) - { - mbc_->write(value | 0x0F, addr); - } - else if (addr == memorymap::DIVIDER_REGISER) - { - mbc_->write(0, addr); - } - else - { - if (addr >= 0xFF00 && addr <= 0xFF7F && write_handlers_[addr - 0xFF00]) - { - write_handlers_[addr - 0xFF00](value, addr); - } - else - { - mbc_->write(value, addr); - } - } - } - - void write(uint16_t value, uint16_t addr) - { - uint8_t hi = (value & 0xFF00) >> 8; - uint8_t lo = (value & 0x00FF); - - write(lo, addr + 0); - write(hi, addr + 1); - } - - uint8_t readVram(uint16_t addr, uint8_t bank) - { - return (cgb_enabled_ || bank == 0) ? mbc_->readVram(addr, bank) : 0; - } - - void dma(uint16_t dest, uint16_t src, uint16_t n) - { - while (n--) - { - write(read(src++), dest++); - } - } - - void oamTransfer(uint8_t base) - { - // increments of $100 bytes - uint16_t addr = ((base & 0x00FF) << 8); - // copy to OAM - std::memcpy(mbc_->getptr(memorymap::OAM_START), mbc_->getptr(addr), memorymap::OAM_END - memorymap::OAM_START); - - // set flag indicating oam transfer has taken place - oam_updated_ = true; - } - - std::vector getBatteryRam() const - { - return mbc_->getXram(); - } - - void setBatteryRam(const std::vector& battery_ram) - { - mbc_->setMemory(memorymap::EXTERNAL_RAM_START, battery_ram); - } - - void setTimeProvider(const TimeProvider provider) - { - // The time provider is only valid for MBC3 - if (auto mbc_ptr = static_cast(mbc_.get())) - { - mbc_ptr->setTimeProvider(provider); - } - } - - int resolveAddress(const uint16_t& addr) const - { - return mbc_->resolveAddress(addr); - } - - void loadResetValues() - { - // load reset values into registers - mbc_->write(0x00, memorymap::TIMER_COUNTER_REGISTER); - mbc_->write(0x00, memorymap::TIMER_MODULO_REGISTER); - mbc_->write(0x00, memorymap::TIMER_CONTROLLER_REGISTER); - - mbc_->write(0x80, memorymap::NR10_REGISTER); - mbc_->write(0xBF, memorymap::NR11_REGISTER); - mbc_->write(0xF3, memorymap::NR12_REGISTER); - mbc_->write(0xBF, memorymap::NR14_REGISTER); - mbc_->write(0x3F, memorymap::NR21_REGISTER); - mbc_->write(0x00, memorymap::NR22_REGISTER); - mbc_->write(0xBF, memorymap::NR24_REGISTER); - mbc_->write(0x7F, memorymap::NR30_REGISTER); - mbc_->write(0xFF, memorymap::NR31_REGISTER); - mbc_->write(0x9F, memorymap::NR32_REGISTER); - mbc_->write(0xBF, memorymap::NR33_REGISTER); - mbc_->write(0xFF, memorymap::NR41_REGISTER); - mbc_->write(0x00, memorymap::NR42_REGISTER); - mbc_->write(0x00, memorymap::NR43_REGISTER); - mbc_->write(0xBF, memorymap::NR44_REGISTER); - mbc_->write(0x77, memorymap::NR50_REGISTER); - mbc_->write(0xF3, memorymap::NR51_REGISTER); - mbc_->write(0xF1, memorymap::NR52_REGISTER); // TODO: super gameboy mode - - mbc_->write(0x91, memorymap::LCDC_REGISTER); - mbc_->write(0x00, memorymap::SCY_REGISTER); - mbc_->write(0x00, memorymap::SCX_REGISTER); - mbc_->write(0x00, memorymap::LYC_REGISTER); - mbc_->write(0xFC, memorymap::BGP_REGISTER); - mbc_->write(0xFF, memorymap::OBP0_REGISTER); - mbc_->write(0xFF, memorymap::OBP1_REGISTER); - mbc_->write(0x00, memorymap::WX_REGISTER); - mbc_->write(0x00, memorymap::WY_REGISTER); - mbc_->write(0x00, memorymap::INTERRUPT_ENABLE); - } - - public: - detail::MBC::Ptr mbc_; - - std::array write_handlers_; - std::array read_handlers_; - - bool oam_updated_; - bool cgb_enabled_; - }; - - - /* Public Interface */ - - MMU::MMU(const uint8_t* rom, uint32_t size) : - impl_(new Impl(rom, size)) - { - } - - MMU::~MMU() - { - delete impl_; - } - - uint8_t MMU::read(uint16_t addr) const - { - return impl_->mbc_->read(addr); - } - - uint8_t MMU::read(uint16_t addr) - { - return impl_->read(addr); - } - - void MMU::write(uint8_t value, uint16_t addr) - { - impl_->write(value, addr); - } - - void MMU::write(uint16_t value, uint16_t addr) - { - impl_->write(value, addr); - } - - uint8_t MMU::readVram(uint16_t addr, uint8_t bank) - { - return impl_->readVram(addr, bank); - } - - void MMU::dma(uint16_t dest, uint16_t src, uint16_t n) - { - impl_->dma(dest, src, n); - } - - void MMU::addWriteHandler(uint16_t addr, MemoryWriteHandler handler) - { - impl_->write_handlers_[addr - 0xFF00] = handler; - } - - void MMU::addReadHandler(uint16_t addr, MemoryReadHandler handler) - { - impl_->read_handlers_[addr - 0xFF00] = handler; - } - - std::vector MMU::getBatteryRam() const - { - return impl_->getBatteryRam(); - } - - void MMU::setBatteryRam(const std::vector& battery_ram) - { - impl_->setBatteryRam(battery_ram); - } - - void MMU::setTimeProvider(const TimeProvider provider) - { - impl_->setTimeProvider(provider); - } - - bool MMU::getOamTransferStatus() const - { - bool ret = impl_->oam_updated_; - impl_->oam_updated_ = false; - - return ret; - } - - bool MMU::cgbEnabled() const - { - return impl_->cgb_enabled_; - } - - uint8_t& MMU::get(uint16_t addr) - { - return impl_->mbc_->get(addr); - } - - uint8_t* MMU::getptr(uint16_t addr) - { - return impl_->mbc_->getptr(addr); - } - - int MMU::resolveAddress(const uint16_t& addr) const - { - return impl_->resolveAddress(addr); - } - - std::size_t MMU::getVirtualMemorySize() const - { - return impl_->mbc_->getVirtualMemorySize(); - } -} diff --git a/components/gameboycore/src/oam.cpp b/components/gameboycore/src/oam.cpp deleted file mode 100644 index bc74cf13..00000000 --- a/components/gameboycore/src/oam.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "gameboycore/oam.h" -#include "bitutil.h" - -namespace gb -{ - OAM::OAM(MMU& mmu) : - mmu_(mmu) - { - } - - Sprite OAM::getSprite(std::size_t idx) const - { - // get location of sprite in memory - auto sprite_base = uint16_t(memorymap::OAM_START + (idx * 4)); - - auto ptr = mmu_.getptr(sprite_base); - - // read OAM attributes from OAM table - Sprite sprite; - sprite.y = ptr[0]; - sprite.x = ptr[1]; - sprite.tile = ptr[2]; - sprite.attr = ptr[3]; - - return sprite; - } - - std::array OAM::getSprites() const - { - // check if sprites are 8x16 or 8x8 - auto lcdc = mmu_.read(memorymap::LCDC_REGISTER); - const bool mode_8x16 = isSet(lcdc, memorymap::LCDC::OBJ_8x16) != 0; - - std::array sprites; - - for (auto i = 0u; i < sprites.size(); ++i) - { - auto& sprite = sprites[i]; - sprite = getSprite(i); - - if (mode_8x16) - { - sprite.height = 16; - } - else - { - sprite.height = 8; - } - } - - return sprites; - } - - OAM::~OAM() - { - } -} \ No newline at end of file diff --git a/components/gameboycore/src/opcodeinfo.cpp b/components/gameboycore/src/opcodeinfo.cpp deleted file mode 100644 index 44aac093..00000000 --- a/components/gameboycore/src/opcodeinfo.cpp +++ /dev/null @@ -1,585 +0,0 @@ - -#include "gameboycore/opcodeinfo.h" - -namespace gb -{ - static const OpcodeInfo opcodeinfo1[] = { - { 4, "nop" }, // 0x00 - { 12, "ld BC,%04X", OperandType::IMM16 }, // 0x01 - { 8, "ld (BC),A" }, // 0x02 - { 8, "inc BC" }, // 0x03 - { 4, "inc B" }, // 0x04 - { 4, "dec B" }, // 0x05 - { 8, "ld B,%02X", OperandType::IMM8 }, // 0x06 - { 4, "rlca", }, // 0x07 - { 20, "ld (%04X)SP", OperandType::IMM16 }, // 0x08 - { 8, "add HL,BC", }, // 0x09 - { 8, "ld A,(BC)", }, // 0x0A - { 4, "dec BC" }, // 0x0B - { 4, "inc C" }, // 0x0C - { 4, "dec C" }, // 0x0D - { 8, "ld C,%02X", OperandType::IMM8 }, // 0x0E - { 4, "rrca", }, // 0x0F - - { 4, "stop %02X", OperandType::IMM8 }, // 0x10 - { 12, "ld DE,%04X", OperandType::IMM16 }, // 0x11 - { 8, "ld (DE),A", }, // 0x12 - { 8, "inc DE", }, // 0x13 - { 4, "inc D", }, // 0x14 - { 4, "dec D", }, // 0x15 - { 8, "ld D,%02X", OperandType::IMM8 }, // 0x16 - { 4, "rla", }, // 0x17 - { 12, "jr %02X", OperandType::IMM8 }, // 0x18 - { 8, "add HL,DE", }, // 0x19 - { 8, "ld A,(DE)", }, // 0x1A - { 8, "dec DE", }, // 0x1B - { 4, "inc E", }, // 0x1C - { 4, "dec E", }, // 0x1D - { 8, "ld E,%02X", OperandType::IMM8 }, // 0x1E - { 4, "rra", }, // 0x1F - - { 8, "jr NZ,%02X", OperandType::IMM8 }, // 0x20 - { 12, "ld HL,%04X", OperandType::IMM16 }, // 0x21 - { 8, "ld (HL+),A", }, // 0x22 - { 8, "inc HL", }, // 0x23 - { 4, "inc H", }, // 0x24 - { 4, "dec H", }, // 0x25 - { 8, "ld H,%02X", OperandType::IMM8 }, // 0x26 - { 4, "daa", }, // 0x27 - { 8, "jr Z,%02X", OperandType::IMM8 }, // 0x28 - { 8, "add HL,HL", }, // 0x29 - { 8, "ld A,(HL+)", }, // 0x2A - { 8, "dec HL", }, // 0x2B - { 4, "inc L", }, // 0x2C - { 4, "dec L", }, // 0x2D - { 8, "ld L,%02X", OperandType::IMM8 }, // 0x2E - { 4, "cpl", }, // 0x2F - - { 8, "jr NC,%02X", OperandType::IMM8 }, // 0x30 - { 12, "ld SP,%04X", OperandType::IMM16 }, // 0x31 - { 8, "ld (HL-),A", }, // 0x32 - { 8, "inc SP", }, // 0x33 - { 12, "inc (HL)", }, // 0x34 - { 12, "dec (HL)", }, // 0x35 - { 12, "ld (HL),%02X", OperandType::IMM8 }, // 0x36 - { 4, "scf", }, // 0x37 - { 8, "jr C,%02X", OperandType::IMM8 }, // 0x38 - { 8, "add HL,SP", }, // 0x39 - { 8, "ld A,(HL-)", }, // 0x3A - { 8, "dec SP", }, // 0x3B - { 4, "inc A", }, // 0x3C - { 4, "dec A", }, // 0x3D - { 8, "ld A,%02X", OperandType::IMM8 }, // 0x3E - { 4, "ccf", }, // 0x3F - - { 4, "ld B,B", }, // 0x40 - { 4, "ld B,C", }, // 0x41 - { 4, "ld B,D", }, // 0x42 - { 4, "ld B,E", }, // 0x43 - { 4, "ld B,H", }, // 0x44 - { 4, "ld B,L", }, // 0x45 - { 8, "ld B,(HL)", }, // 0x46 - { 4, "ld B,A", }, // 0x47 - { 4, "ld C,B", }, // 0x48 - { 4, "ld C,C", }, // 0x49 - { 4, "ld C,D", }, // 0x4A - { 4, "ld C,E", }, // 0x4B - { 4, "ld C,H", }, // 0x4C - { 4, "ld C,L", }, // 0x4D - { 8, "ld C,(HL)", }, // 0x4E - { 4, "ld C,A", }, // 0x4F - - { 4, "ld D,B", }, // 0x50 - { 4, "ld D,C", }, // 0x51 - { 4, "ld D,D", }, // 0x52 - { 4, "ld D,E", }, // 0x53 - { 4, "ld D,H", }, // 0x54 - { 4, "ld D,L", }, // 0x55 - { 8, "ld D,(HL)", }, // 0x56 - { 4, "ld D,A", }, // 0x57 - { 4, "ld E,B", }, // 0x58 - { 4, "ld E,C", }, // 0x59 - { 4, "ld E,D", }, // 0x5A - { 4, "ld E,E", }, // 0x5B - { 4, "ld E,H", }, // 0x5C - { 4, "ld E,L", }, // 0x5D - { 8, "ld E,(HL)", }, // 0x5E - { 4, "ld E,A", }, // 0x5F - - { 4, "ld H,B", }, // 0x60 - { 4, "ld H,C", }, // 0x61 - { 4, "ld H,D", }, // 0x62 - { 4, "ld H,E", }, // 0x63 - { 4, "ld H,H", }, // 0x64 - { 4, "ld H,L", }, // 0x65 - { 8, "ld H,(HL)", }, // 0x66 - { 4, "ld H,A", }, // 0x67 - { 4, "ld L,B", }, // 0x68 - { 4, "ld L,C", }, // 0x69 - { 4, "ld L,D", }, // 0x6A - { 4, "ld L,E", }, // 0x6B - { 4, "ld L,H", }, // 0x6C - { 4, "ld L,L", }, // 0x6D - { 8, "ld L,(HL)", }, // 0x6E - { 4, "ld L,A", }, // 0x6F - - { 8, "ld (HL),B", }, // 0x70 - { 8, "ld (HL),C", }, // 0x71 - { 8, "ld (HL),D", }, // 0x72 - { 8, "ld (HL),E", }, // 0x73 - { 8, "ld (HL),H", }, // 0x74 - { 8, "ld (HL),L", }, // 0x75 - { 4, "halt", }, // 0x76 - { 8, "ld (HL),A", }, // 0x77 - { 4, "ld A,B", }, // 0x78 - { 4, "ld A,C", }, // 0x79 - { 4, "ld A,D", }, // 0x7A - { 4, "ld A,E", }, // 0x7B - { 4, "ld A,H", }, // 0x7C - { 4, "ld A,L", }, // 0x7D - { 4, "ld A,(HL)", }, // 0x7E - { 4, "ld A,A", }, // 0x7F - - { 4, "add A,B", }, // 0x80 - { 4, "add A,C", }, // 0x81 - { 4, "add A,D", }, // 0x82 - { 4, "add A,E", }, // 0x83 - { 4, "add A,H", }, // 0x84 - { 4, "add A,L", }, // 0x85 - { 8, "add A,(HL)", }, // 0x86 - { 4, "add A,A", }, // 0x87 - { 4, "adc A,B", }, // 0x88 - { 4, "adc A,C", }, // 0x89 - { 4, "adc A,D", }, // 0x8A - { 4, "adc A,E", }, // 0x8B - { 4, "adc A,H", }, // 0x8C - { 4, "adc A,L", }, // 0x8D - { 8, "adc A,(HL)", }, // 0x8E - { 4, "adc A,A", }, // 0x8F - - { 4, "sub B", }, // 0x90 - { 4, "sub C", }, // 0x91 - { 4, "sub D", }, // 0x92 - { 4, "sub E", }, // 0x93 - { 4, "sub H", }, // 0x94 - { 4, "sub L", }, // 0x95 - { 8, "sub (HL)", }, // 0x96 - { 4, "sub A", }, // 0x97 - { 4, "sbc A,B", }, // 0x98 - { 4, "sbc A,C", }, // 0x99 - { 4, "sbc A,D", }, // 0x9A - { 4, "sbc A,E", }, // 0x9B - { 4, "sbc A,H", }, // 0x9C - { 4, "sbc A,L", }, // 0x9D - { 8, "sbc A,(HL)",}, // 0x9E - { 4, "sbc A,A", }, // 0x9F - - { 4, "and B", }, // 0xA0 - { 4, "and C", }, // 0xA1 - { 4, "and D", }, // 0xA2 - { 4, "and E", }, // 0xA3 - { 4, "and H", }, // 0xA4 - { 4, "and L", }, // 0xA5 - { 8, "and (HL)", }, // 0xA6 - { 4, "and A", }, // 0xA7 - { 4, "xor B", }, // 0xA8 - { 4, "xor C", }, // 0xA9 - { 4, "xor D", }, // 0xAA - { 4, "xor E", }, // 0xAB - { 4, "xor H", }, // 0xAC - { 4, "xor L", }, // 0xAD - { 8, "xor (HL)", }, // 0xAE - { 4, "xor A", }, // 0xAF - - { 4, "or B", }, // 0xB0 - { 4, "or C", }, // 0xB1 - { 4, "or D", }, // 0xB2 - { 4, "or E", }, // 0xB3 - { 4, "or H", }, // 0xB4 - { 4, "or L", }, // 0xB5 - { 8, "or (HL)", }, // 0xB6 - { 4, "or A", }, // 0xB7 - { 4, "cp B", }, // 0xB8 - { 4, "cp C", }, // 0xB9 - { 4, "cp D", }, // 0xBA - { 4, "cp E", }, // 0xBB - { 4, "cp H", }, // 0xBC - { 4, "cp L", }, // 0xBD - { 4, "cp (HL)", }, // 0xBE - { 4, "cp A", }, // 0xBF - - { 8, "ret NZ", }, // 0xC0 - { 12, "pop BC", }, // 0xC1 - { 12, "jp NZ,%04X", OperandType::IMM16 }, // 0xC2 - { 16, "jp %04X", OperandType::IMM16 }, // 0xC3 - { 12, "call NZ,%04X", OperandType::IMM16 }, // 0xC4 - { 16, "push BC", }, // 0xC5 - { 8, "add A,%02X", OperandType::IMM8 }, // 0xC6 - { 16, "rst 00", }, // 0xC7 - { 8, "ret Z", }, // 0xC8 - { 8, "ret", }, // 0xC9 - { 12, "jp Z,%04X", OperandType::IMM16 }, // 0xCA - { 0, "prefix" }, // 0xCB - { 12, "call Z,%04X", OperandType::IMM16 }, // 0xCC - { 24, "call %04X", OperandType::IMM16 }, // 0xCD - { 8, "adc A,%02X", OperandType::IMM8 }, // 0xCE - { 16, "rst 08h", }, // 0xCF - - { 8, "ret NC", }, // 0xD0 - { 12, "pop DE", }, // 0xD1 - { 12, "jp NC,%04X", OperandType::IMM16 }, // 0xD2 - { 0, "invalid" }, // 0xD3 - { 12, "call NC,%04X", OperandType::IMM16 }, // 0xD4 - { 16, "push DE", }, // 0xD5 - { 8, "sub %02X", OperandType::IMM8 }, // 0xD6 - { 16, "rst 10", }, // 0xD7 - { 8, "ret C", }, // 0xD8 - { 16, "reti", }, // 0xD9 - { 12, "jp C,%04X", OperandType::IMM16 }, // 0xDA - { 0, "invalid" }, // 0xDB - { 12, "call C,%04X", OperandType::IMM16 }, // 0xDC - { 0, "invalid" }, // 0xDD - { 8, "sbc A,%02X", OperandType::IMM8 }, // 0xDE - { 16, "rst 18", }, // 0xDF - - { 12, "ldh (%02X),A", OperandType:: IMM8}, // 0xE0 - { 12, "pop HL", }, // 0xE1 - { 8, "ld (C),A", }, // 0xE2 - { 0, "invalid" }, // 0xE3 - { 0, "invalid" }, // 0xE4 - { 16, "push HL", }, // 0xE5 - { 8, "and %02X", OperandType::IMM8 }, // 0xE6 - { 16, "rst 20", }, // 0xE7 - { 8, "add SP,%02X", OperandType::IMM8 }, // 0xE8 - { 4, "jp (HL)", }, // 0xE9 - { 16, "ld (%04X),A", OperandType:: IMM16}, // 0xEA - { 0, "invalid" }, // 0xEB - { 0, "invalid" }, // 0xEC - { 0, "invalid" }, // 0xED - { 8, "xor %02X", OperandType::IMM8 }, // 0xEE - { 16, "rst 28", }, // 0xEF - - { 12, "ldh A,(%02X)", OperandType::IMM8 }, // 0xF0 - { 12, "pop AF", }, // 0xF1 - { 8, "ld (A),C", }, // 0xF2 - { 4, "di", }, // 0xF3 - { 0, "invalid" }, // 0xF4 - { 16, "push AF", }, // 0xF5 - { 8, "or %02X", OperandType::IMM8 }, // 0xF6 - { 16, "rst 30", }, // 0xF7 - { 12, "ld HL,SP+r8", }, // 0xF8 - { 8, "ld SP,HL", }, // 0xF9 - { 16, "ld A,(%04X)", OperandType::IMM16 }, // 0xFA - { 4, "ei", }, // 0xFB - { 0, "invalid" }, // 0xFC - { 0, "invalid" }, // 0xFD - { 8, "cp %02X", OperandType::IMM8 }, // 0xFE - { 16, "rst 38" } // 0xFF - }; - - static const OpcodeInfo opcodeinfo2[] = { - { 8, "RLC B" }, // 00 - { 8, "RLC C" }, // 01 - { 8, "RLC D" }, // 02 - { 8, "RLC E" }, // 03 - { 8, "RLC H" }, // 04 - { 8, "RLC L" }, // 05 - { 16, "RLC (HL)" }, // 06 - { 8, "RLC A" }, // 07 - - { 8, "RRC B" }, // 08 - { 8, "RRC C" }, // 09 - { 8, "RRC D" }, // 0A - { 8, "RRC E" }, // 0B - { 8, "RRC H" }, // 0C - { 8, "RRC L" }, // 0D - { 16, "RRC (HL)" }, // 0E - { 8, "RRC A" }, // 0F - - { 8, "RL B" }, // 10 - { 8, "RL C" }, // 11 - { 8, "RL D" }, // 12 - { 8, "RL E" }, - { 8, "RL H" }, - { 8, "RL L" }, - { 16, "RL (HL)" }, - { 8, "RL A" }, - - { 8, "RR B" }, - { 8, "RR C" }, - { 8, "RR D" }, - { 8, "RR E" }, - { 8, "RR H" }, - { 8, "RR L" }, - { 16, "RR (HL)" }, - { 8, "RR A" }, - - { 8, "SLA B" }, - { 8, "SLA C" }, - { 8, "SLA D" }, - { 8, "SLA E" }, - { 8, "SLA H" }, - { 8, "SLA L" }, - { 16, "SLA (HL)" }, - { 8, "SLA A" }, - - { 8, "SRA B" }, - { 8, "SRA C" }, - { 8, "SRA D" }, - { 8, "SRA E" }, - { 8, "SRA H" }, - { 8, "SRA L" }, - { 16, "SRA (HL)" }, - { 8, "SRA A" }, - - { 8, "SWAP B" }, - { 8, "SWAP C" }, - { 8, "SWAP D" }, - { 8, "SWAP E" }, - { 8, "SWAP H" }, - { 8, "SWAP L" }, - { 16, "SWAP (HL)" }, - { 8, "SWAP A" }, - - { 8, "SRL B" }, - { 8, "SRL C" }, - { 8, "SRL D" }, - { 8, "SRL E" }, - { 8, "SRL H" }, - { 8, "SRL L" }, - { 16, "SRL (HL)" }, - { 8, "SRL A" }, - - { 8, "BIT 0,B" }, - { 8, "BIT 0,C" }, - { 8, "BIT 0,D" }, - { 8, "BIT 0,E" }, - { 8, "BIT 0,H" }, - { 8, "BIT 0,L" }, - { 16, "BIT 0,(HL)" }, - { 8, "BIT 0,A" }, - - { 8, "BIT 1,B" }, - { 8, "BIT 1,C" }, - { 8, "BIT 1,D" }, - { 8, "BIT 1,E" }, - { 8, "BIT 1,H" }, - { 8, "BIT 1,L" }, - { 16, "BIT 1,(HL)" }, - { 8, "BIT 1,A" }, - - { 8, "BIT 2,B" }, - { 8, "BIT 2,C" }, - { 8, "BIT 2,D" }, - { 8, "BIT 2,E" }, - { 8, "BIT 2,H" }, - { 8, "BIT 2,L" }, - { 16, "BIT 2,(HL)" }, - { 8, "BIT 2,A" }, - - { 8, "BIT 3,B" }, - { 8, "BIT 3,C" }, - { 8, "BIT 3,D" }, - { 8, "BIT 3,E" }, - { 8, "BIT 3,H" }, - { 8, "BIT 3,L" }, - { 16, "BIT 3,(HL)" }, - { 8, "BIT 3,A" }, - - { 8, "BIT 4,B" }, - { 8, "BIT 4,C" }, - { 8, "BIT 4,D" }, - { 8, "BIT 4,E" }, - { 8, "BIT 4,H" }, - { 8, "BIT 4,L" }, - { 16, "BIT 4,(HL)" }, - { 8, "BIT 4,A" }, - - { 8, "BIT 5,B" }, - { 8, "BIT 5,C" }, - { 8, "BIT 5,D" }, - { 8, "BIT 5,E" }, - { 8, "BIT 5,H" }, - { 8, "BIT 5,L" }, - { 16, "BIT 5,(HL)" }, - { 8, "BIT 5,A" }, - - { 8, "BIT 6,B" }, - { 8, "BIT 6,C" }, - { 8, "BIT 6,D" }, - { 8, "BIT 6,E" }, - { 8, "BIT 6,H" }, - { 8, "BIT 6,L" }, - { 16, "BIT 6,(HL)" }, - { 8, "BIT 6,A" }, - - { 8, "BIT 7,B" }, - { 8, "BIT 7,C" }, - { 8, "BIT 7,D" }, - { 8, "BIT 7,E" }, - { 8, "BIT 7,H" }, - { 8, "BIT 7,L" }, - { 16, "BIT 7,(HL)" }, - { 8, "BIT 7,A" }, - - // - - { 8, "RES 0,B" }, - { 8, "RES 0,C" }, - { 8, "RES 0,D" }, - { 8, "RES 0,E" }, - { 8, "RES 0,H" }, - { 8, "RES 0,L" }, - { 16, "RES 0,(HL)" }, - { 8, "RES 0,A" }, - - { 8, "RES 1,B" }, - { 8, "RES 1,C" }, - { 8, "RES 1,D" }, - { 8, "RES 1,E" }, - { 8, "RES 1,H" }, - { 8, "RES 1,L" }, - { 16, "RES 1,(HL)" }, - { 8, "RES 1,A" }, - - { 8, "RES 2,B" }, - { 8, "RES 2,C" }, - { 8, "RES 2,D" }, - { 8, "RES 2,E" }, - { 8, "RES 2,H" }, - { 8, "RES 2,L" }, - { 16, "RES 2,(HL)" }, - { 8, "RES 2,A" }, - - { 8, "RES 3,B" }, - { 8, "RES 3,C" }, - { 8, "RES 3,D" }, - { 8, "RES 3,E" }, - { 8, "RES 3,H" }, - { 8, "RES 3,L" }, - { 16, "RES 3,(HL)" }, - { 8, "RES 3,A" }, - - { 8, "RES 4,B" }, - { 8, "RES 4,C" }, - { 8, "RES 4,D" }, - { 8, "RES 4,E" }, - { 8, "RES 4,H" }, - { 8, "RES 4,L" }, - { 16, "RES 4,(HL)" }, - { 8, "RES 4,A" }, - - { 8, "RES 5,B" }, - { 8, "RES 5,C" }, - { 8, "RES 5,D" }, - { 8, "RES 5,E" }, - { 8, "RES 5,H" }, - { 8, "RES 5,L" }, - { 16, "RES 5,(HL)" }, - { 8, "RES 5,A" }, - - { 8, "RES 6,B" }, - { 8, "RES 6,C" }, - { 8, "RES 6,D" }, - { 8, "RES 6,E" }, - { 8, "RES 6,H" }, - { 8, "RES 6,L" }, - { 16, "RES 6,(HL)" }, - { 8, "RES 6,A" }, - - { 8, "RES 7,B" }, - { 8, "RES 7,C" }, - { 8, "RES 7,D" }, - { 8, "RES 7,E" }, - { 8, "RES 7,H" }, - { 8, "RES 7,L" }, - { 16, "RES 7,(HL)" }, - { 8, "RES 7,A" }, - // - - { 8, "SET 0,B" }, - { 8, "SET 0,C" }, - { 8, "SET 0,D" }, - { 8, "SET 0,E" }, - { 8, "SET 0,H" }, - { 8, "SET 0,L" }, - { 16, "SET 0,(HL)" }, - { 8, "SET 0,A" }, - - { 8, "SET 1,B" }, - { 8, "SET 1,C" }, - { 8, "SET 1,D" }, - { 8, "SET 1,E" }, - { 8, "SET 1,H" }, - { 8, "SET 1,L" }, - { 16, "SET 1,(HL)" }, - { 8, "SET 1,A" }, - - { 8, "SET 2,B" }, - { 8, "SET 2,C" }, - { 8, "SET 2,D" }, - { 8, "SET 2,E" }, - { 8, "SET 2,H" }, - { 8, "SET 2,L" }, - { 16, "SET 2,(HL)" }, - { 8, "SET 2,A" }, - - { 8, "SET 3,B" }, - { 8, "SET 3,C" }, - { 8, "SET 3,D" }, - { 8, "SET 3,E" }, - { 8, "SET 3,H" }, - { 8, "SET 3,L" }, - { 16, "SET 3,(HL)" }, - { 8, "SET 3,A" }, - - { 8, "SET 4,B" }, - { 8, "SET 4,C" }, - { 8, "SET 4,D" }, - { 8, "SET 4,E" }, - { 8, "SET 4,H" }, - { 8, "SET 4,L" }, - { 16, "SET 4,(HL)" }, - { 8, "SET 4,A" }, - - { 8, "SET 5,B" }, - { 8, "SET 5,C" }, - { 8, "SET 5,D" }, - { 8, "SET 5,E" }, - { 8, "SET 5,H" }, - { 8, "SET 5,L" }, - { 16, "SET 5,(HL)" }, - { 8, "SET 5,A" }, - - { 8, "SET 6,B" }, - { 8, "SET 6,C" }, - { 8, "SET 6,D" }, - { 8, "SET 6,E" }, - { 8, "SET 6,H" }, - { 8, "SET 6,L" }, - { 16, "SET 6,(HL)" }, - { 8, "SET 6,A" }, - - { 8, "SET 7,B" }, - { 8, "SET 7,C" }, - { 8, "SET 7,D" }, - { 8, "SET 7,E" }, - { 8, "SET 7,H" }, - { 8, "SET 7,L" }, - { 16, "SET 7,(HL)" }, - { 8, "SET 7,A" }, - - }; - - OpcodeInfo getOpcodeInfo(uint8_t opcode, OpcodePage page) - { - if (page == OpcodePage::PAGE1) - { - return opcodeinfo1[opcode]; - } - else // page == OpcodePage::PAGE2 - { - return opcodeinfo2[opcode]; - } - } -} diff --git a/components/gameboycore/src/shiftrotate.cpp b/components/gameboycore/src/shiftrotate.cpp deleted file mode 100644 index 1af8d4e9..00000000 --- a/components/gameboycore/src/shiftrotate.cpp +++ /dev/null @@ -1,205 +0,0 @@ - -#include "shiftrotate.h" -#include "bitutil.h" - -#define C_BIT 4 -#define C_MASK (1 << C_BIT) - -#define Z_BIT 7 -#define Z_MASK (1 << Z_BIT) - -namespace gb -{ - static void setFlag(uint8_t& flags, uint8_t mask, bool set) - { - if (set) - { - setMask(flags, mask); - } - else - { - clearMask(flags, mask); - } - } - - uint8_t rlca(uint8_t val, uint8_t& flags) - { - uint8_t r = 0; - - uint8_t bit7 = (isBitSet(val, 7)) ? 1 : 0; - - r = (val << 1) | bit7; - - flags = 0; // clear N, Z and H - // flags |= (bit7 << C_BIT); - setFlag(flags, C_MASK, bit7 == 1); - - return r; - } - - uint8_t rla(uint8_t val, uint8_t& flags) - { - uint8_t r = 0; - uint8_t c = (isBitSet(flags, C_BIT)) ? 1 : 0; - - uint8_t bit7 = (isBitSet(val, 7)) ? 1 : 0; - - r = (val << 1) | c; - - flags = 0; - flags |= (bit7 << C_BIT); - - return r; - } - - - uint8_t rrca(uint8_t val, uint8_t& flags) - { - uint8_t r = 0; - - uint8_t bit0 = (isBitSet(val, 0)) ? 1 : 0; - - r = (val >> 1) | (bit0 << 7); - - flags = 0; // clear N, Z and H - flags |= (bit0 << C_BIT); - - return r; - } - - uint8_t rra(uint8_t val, uint8_t& flags) - { - uint8_t r = 0; - uint8_t c = (isBitSet(flags, C_BIT)) ? 1 : 0; - - uint8_t bit0 = (isBitSet(val, 0)) ? 1 : 0; - - r = (val >> 1) | (c << 7); - - flags = 0; - flags |= (bit0 << C_BIT); - - return r; - } - - uint8_t rotateLeft(uint8_t val, uint8_t n, uint8_t& flags) - { - uint8_t r = 0; - - uint8_t bit7 = (isBitSet(val, 7)) ? 1 : 0; - - r = (val << n) | bit7; - - flags = 0; // clear N, Z and H - //flags |= (bit7 << C_BIT); - - setFlag(flags, C_MASK, bit7 == 1); - setFlag(flags, Z_MASK, r == 0); - - return r; - } - - uint8_t rotateLeftCarry(uint8_t val, uint8_t n, uint8_t& flags) - { - uint8_t r = 0; - uint8_t c = (isBitSet(flags, C_BIT)) ? 1 : 0; - - uint8_t bit7 = (isBitSet(val, 7)) ? 1 : 0; - - r = (val << n) | c; - - flags = 0; - //flags |= (bit7 << C_BIT); - - setFlag(flags, C_MASK, bit7 == 1); - setFlag(flags, Z_MASK, r == 0); - - return r; - } - - uint8_t rotateRight(uint8_t val, uint8_t n, uint8_t& flags) - { - uint8_t r = 0; - - uint8_t bit0 = (isBitSet(val, 0)) ? 1 : 0; - - r = (val >> n) | (bit0 << 7); - - flags = 0; // clear N, Z and H - //flags |= (bit0 << C_BIT); - - setFlag(flags, C_MASK, bit0 == 1); - setFlag(flags, Z_MASK, r == 0); - - return r; - } - - uint8_t rotateRightCarry(uint8_t val, uint8_t n, uint8_t& flags) - { - uint8_t r = 0; - uint8_t c = (isBitSet(flags, C_BIT)) ? 1 : 0; - - uint8_t bit0 = (isBitSet(val, 0)) ? 1 : 0; - - r = (val >> n) | (c << 7); - - flags = 0; - //flags |= (bit0 << C_BIT); - - setFlag(flags, C_MASK, bit0 == 1); - setFlag(flags, Z_MASK, r == 0); - - return r; - } - - uint8_t shiftLeft(uint8_t val, uint8_t n, uint8_t& flags) - { - uint8_t r = 0; - uint8_t bit7 = (isBitSet(val, 7)) ? 1 : 0; - - r = (val << n); - - flags = 0; - setFlag(flags, C_MASK, bit7 == 1); - setFlag(flags, Z_MASK, r == 0); - - return r; - } - - uint8_t shiftRightA(uint8_t val, uint8_t n, uint8_t& flags) - { - uint8_t r = 0; - uint8_t bit0 = (isBitSet(val, 0)) ? 1 : 0; - - r = (val >> n) | (val & 0x80); - - flags = 0; - // flags |= (bit0 << C_BIT); - - // if (r == 0) flags |= Z_MASK; - - setFlag(flags, C_MASK, bit0 == 1); - setFlag(flags, Z_MASK, r == 0); - - return r; - } - - uint8_t shiftRightL(uint8_t val, uint8_t n, uint8_t& flags) - { - uint8_t r = 0; - uint8_t bit0 = (isBitSet(val, 0)) ? 1 : 0; - - r = (val >> n); - - flags = 0; - // flags |= (bit0 << C_BIT); - - // if (r == 0) flags |= Z_MASK; - - setFlag(flags, C_MASK, bit0 == 1); - setFlag(flags, Z_MASK, r == 0); - - return r; - } - -} diff --git a/components/gameboycore/src/shiftrotate.h b/components/gameboycore/src/shiftrotate.h deleted file mode 100644 index e7fe34b0..00000000 --- a/components/gameboycore/src/shiftrotate.h +++ /dev/null @@ -1,55 +0,0 @@ - -/** - @author Natesh Narain -*/ - -#ifndef GAMEBOYCORE_SHIFT_ROTATE_H -#define GAMEBOYCORE_SHIFT_ROTATE_H - -#include - -namespace gb -{ - uint8_t rlca(uint8_t val, uint8_t& flags); - - uint8_t rla(uint8_t val, uint8_t& flags); - - - uint8_t rrca(uint8_t val, uint8_t& flags); - - uint8_t rra(uint8_t val, uint8_t& flags); - - /** - Rotate bits left and set carry flags - */ - uint8_t rotateLeft(uint8_t val, uint8_t n, uint8_t& flags); - - /** - Rotate bits left through the carry flag - */ - uint8_t rotateLeftCarry(uint8_t val, uint8_t n, uint8_t& flags); - - /** - Rotate bits right adn set carry flag - */ - uint8_t rotateRight(uint8_t val, uint8_t n, uint8_t& flags); - - /** - Rotate bits right through carry flag - */ - uint8_t rotateRightCarry(uint8_t val, uint8_t n, uint8_t& flags); - - /** - Shift bits left - */ - uint8_t shiftLeft(uint8_t val, uint8_t n, uint8_t& flags); - - /** - Shift bits right. Keep sign bit - */ - uint8_t shiftRightA(uint8_t val, uint8_t n, uint8_t& flags); - - uint8_t shiftRightL(uint8_t val, uint8_t n, uint8_t& flags); -} - -#endif \ No newline at end of file diff --git a/components/gameboycore/src/tilemap.cpp b/components/gameboycore/src/tilemap.cpp deleted file mode 100644 index b35c690b..00000000 --- a/components/gameboycore/src/tilemap.cpp +++ /dev/null @@ -1,284 +0,0 @@ - -#include "gameboycore/tilemap.h" -#include "gameboycore/oam.h" -#include "gameboycore/palette.h" -#include "gameboycore/detail/hash.h" - -#include "bitutil.h" - -#include - -namespace gb -{ - namespace detail - { - TileMap::TileMap(MMU& mmu, Palette& palette) : - tileram_(mmu), - mmu_(mmu), - scx_(mmu.get(memorymap::SCX_REGISTER)), - scy_(mmu.get(memorymap::SCY_REGISTER)), - palette_(palette) - { - } - - TileMap::Line TileMap::getBackground(int line, bool cgb_enable) - { - static constexpr auto tiles_per_row = 32; - static constexpr auto tiles_per_col = 32; - static constexpr auto tile_width = 8; - static constexpr auto tile_height = 8; - - const auto start = getAddress(Map::BACKGROUND); - const auto umode = (mmu_.read(memorymap::LCDC_REGISTER) & memorymap::LCDC::CHARACTER_DATA) != 0; - - TileMap::Line tileline{}; - - // scroll x - const auto scx = mmu_.read(memorymap::SCX_REGISTER); - // scroll y - const auto scy = mmu_.read(memorymap::SCY_REGISTER); - - // starting row given the scroll - const auto tile_row = ((scy + line) / tile_height); - // starting column given the scroll - const auto start_tile_col = scx / tile_width; - auto pixel_row = (scy + line) % tile_height; - - auto idx = 0; - for (auto tile_col = start_tile_col; tile_col < start_tile_col + 21; ++tile_col) - { - // calculate tile address - const auto tile_offset = (uint16_t)(start + (tiles_per_row * (tile_row % tiles_per_row)) + (tile_col % tiles_per_col)); - - // read tile character code from map - const auto tilenum = mmu_.readVram(tile_offset, 0); - // read tile attributes - const auto tileattr = mmu_.readVram(tile_offset, 1); - - // extract tile attributes - const auto palette_number = (cgb_enable) ? (tileattr & 0x07) : 0; - const auto character_bank = (cgb_enable) ? ((tileattr >> 3) & 0x01) : 0; - const auto flip_horizontal = (cgb_enable && (tileattr & 0x20) != 0); - const auto flip_vertical = (cgb_enable && (tileattr & 0x40) != 0); - const auto backgroud_priority = (cgb_enable && (tileattr & 0x80) != 0); - - if (flip_vertical) - pixel_row = tile_height - pixel_row - 1; - - // get the row of the tile the current scan line is on. - auto row = tileram_.getRow(pixel_row, tilenum, umode, (uint8_t)character_bank); - - // horizontally flip the row if the flag is set - if (flip_horizontal) - std::reverse(row.begin(), row.end()); - - // calculate pixel column number - auto pixel_col = tile_col * tile_width; - - // - for (auto i = 0u; i < row.size(); ++i) - { - if (pixel_col >= scx && pixel_col <= scx + 160 && idx < 160) - tileline[idx++] = (uint8_t)(row[i] | (palette_number << 2) | (backgroud_priority << 5)); - - pixel_col++; - } - } - - return tileline; - } - - TileMap::Line TileMap::getWindowOverlay(int line) - { - static constexpr auto tiles_per_row = 32; - static constexpr auto tile_height = 8; - - TileMap::Line tileline{}; - - auto wy = mmu_.read(memorymap::WY_REGISTER); - auto umode = (mmu_.read(memorymap::LCDC_REGISTER) & memorymap::LCDC::CHARACTER_DATA) != 0; - - auto window_row = line - wy; - auto tile_row = window_row / tile_height; - auto idx = 0; - - auto start = getAddress(Map::WINDOW_OVERLAY); - - for (auto tile_col = 0; tile_col < 20; ++tile_col) - { - auto tile_offset = start + ((tiles_per_row * tile_row) + tile_col); - auto tilenum = mmu_.read((uint16_t)tile_offset); - - const auto pixel_row = tileram_.getRow(line % tile_height, tilenum, umode); - - for (const auto pixel : pixel_row) - { - tileline[idx++] = pixel; - } - } - - return tileline; - } - - void TileMap::drawSprites( - std::array& scanline, - std::array& info, - int line, - bool cgb_enable, - std::array, 8>& cgb_palette) - { - OAM oam{ mmu_ }; - - auto palette0 = palette_.get(mmu_.read(memorymap::OBP0_REGISTER)); - auto palette1 = palette_.get(mmu_.read(memorymap::OBP1_REGISTER)); - - if (mmu_.getOamTransferStatus()) - { - sprite_cache_ = oam.getSprites(); - } - - auto count = 0; - - for (const auto& sprite : sprite_cache_) - { - if (count > 10) break; - - - // check for out of bounds coordinates - if (sprite.x == 0 || sprite.x >= 168) continue; - if (sprite.y == 0 || sprite.y >= 160) continue; - - auto x = sprite.x - 8; - auto y = sprite.y - 16; - - // check if the sprite contains the line - if (line >= y && line < y + sprite.height) - { - // get the pixel row in tile - auto row = line - y; - - if (sprite.isVerticallyFlipped()) - row = sprite.height - row - 1; - - auto sprite_line = tileram_.getRow(row, sprite.tile, true, sprite.getCharacterBank()); - - if (sprite.isHorizontallyFlipped()) - std::reverse(sprite_line.begin(), sprite_line.end()); - - // get color palette for this sprite - - std::array palette; - - if (cgb_enable) - { - palette = cgb_palette[sprite.getCgbPalette()]; - } - else - { - palette = (sprite.paletteOBP0() == 0) ? palette0 : palette1; - } - - for (auto i = 0; i < 8; ++i) - { - // skip this pixel if outside the window - if ((x + i) < 0 || (x + i) >= 160) continue; - - auto color = info[x + i] & 0x03; - auto background_priority = (bool)(info[x + i] >> 2); - - if (sprite.hasPriority()) - { - if (sprite_line[i] != 0 && !background_priority) - scanline[x + i] = palette[sprite_line[i]]; - } - else - { - // if priority is to the background the sprite is behind colors 1-3 - if (color == 0 && sprite_line[i] != 0) - scanline[x + i] = palette[sprite_line[i]]; - } - - } - - count++; - } - } - } - - uint16_t TileMap::getAddress(Map map) const - { - auto lcdc = mmu_.read(memorymap::LCDC_REGISTER); - - if (map == Map::BACKGROUND) - { - return (isSet(lcdc, memorymap::LCDC::BG_CODE_AREA)) ? 0x9C00 : 0x9800; - } - else - { - return (isSet(lcdc, memorymap::LCDC::WINDOW_CODE_AREA)) ? 0x9C00 : 0x9800; - } - } - - std::array TileMap::getSpriteCache() const - { - return sprite_cache_; - } - - std::vector TileMap::getBackgroundTileMap() - { - // make std::array? - std::vector tiles; - - forEachBackgroundTile([&](uint8_t tile){ - tiles.push_back(tile); - }); - - return tiles; - } - - std::size_t TileMap::hashBackground() - { - std::size_t seed = 0; - - forEachBackgroundTile([&](uint8_t tilenum){ - hash_combine(seed, tilenum); - }); - - return seed; - } - - void TileMap::forEachBackgroundTile(std::function fn) - { - static constexpr auto tiles_per_row = 32; - static constexpr auto tiles_per_col = 32; - static constexpr auto tile_width = 8; - static constexpr auto tile_height = 8; - - const auto start = getAddress(Map::BACKGROUND); - - const auto scx = mmu_.read(memorymap::SCX_REGISTER); - const auto scy = mmu_.read(memorymap::SCY_REGISTER); - - const auto start_tile_col = scx / tile_width; - - for (auto line = 0; line < 144; line += 8) - { - const auto tile_row = ((scy + line) / tile_height); - - for (auto tile_col = start_tile_col; tile_col < start_tile_col + 20; ++tile_col) - { - // calculate tile address - const auto tile_offset = start + (tiles_per_row * (tile_row % tiles_per_row)) + (tile_col % tiles_per_col); - // read tile character code from map - const auto tilenum = mmu_.readVram((uint16_t)tile_offset, 0); - - fn(tilenum); - } - } - } - - TileMap::~TileMap() - { - } - } -} diff --git a/components/gameboycore/src/tileram.cpp b/components/gameboycore/src/tileram.cpp deleted file mode 100644 index e8f9d3b4..00000000 --- a/components/gameboycore/src/tileram.cpp +++ /dev/null @@ -1,163 +0,0 @@ - -#include "gameboycore/tileram.h" -#include "gameboycore/memorymap.h" - -#include - -namespace gb -{ - namespace detail - { - TileRAM::TileRAM(MMU& mmu) : - tile_ram_(mmu.getptr(0x8000)), - mmu_(mmu) - { - } - - TileRAM::TileRow TileRAM::getRow(int row, uint8_t tilenum, bool umode, uint8_t character_bank) - { - TileRow tile_row; - - // get the tile address depending on whether current using unsigned mode or signed mode - uint16_t addr = (umode) - ? getTileAddress(0x8000, tilenum) - : getTileAddress(0x9000, tilenum); - - auto row_offset = uint16_t(row * 2); - auto lsb = mmu_.readVram(addr + row_offset, character_bank); - auto msb = mmu_.readVram(addr + row_offset + 1, character_bank); - - auto idx = 0; - for (auto bit = (int)(tile_row.size() - 1); bit >= 0; --bit) - { - uint8_t mask = (1 << bit); - uint8_t color = (((msb & mask) >> bit) << 1) | ((lsb & mask) >> bit); - - tile_row[idx++] = color; - } - - return tile_row; - } - - Tile TileRAM::getSpriteTile(const Sprite& sprite) const - { - auto tile_ptr = mmu_.getptr(getTileAddress(0x8000, sprite.tile)); - - Tile tile; - auto row = 0; - - for (auto i = 0u; i < TILE_SIZE; i += 2) - { - uint8_t lsb = tile_ptr[i + 0]; - uint8_t msb = tile_ptr[i + 1]; - - setRow(tile, msb, lsb, row); - row++; - } - - // apply sprite attributes - if (sprite.isVerticallyFlipped()) - tile = flipV(tile); - if (sprite.isHorizontallyFlipped()) - tile = flipH(tile); - - return tile; - } - - Tile TileRAM::flipV(const Tile& old) const - { - static const auto NUM_ROWS = 8; - static const auto PIXELS_PER_ROW = 8; - - Tile tile; - - for (auto row = 0; row < NUM_ROWS; ++row) - { - auto target_row = NUM_ROWS - row - 1; - - auto old_idx = (row * PIXELS_PER_ROW); - auto target_idx = (target_row * PIXELS_PER_ROW); - - tile.color[target_idx + 0] = old.color[old_idx + 0]; - tile.color[target_idx + 1] = old.color[old_idx + 1]; - tile.color[target_idx + 2] = old.color[old_idx + 2]; - tile.color[target_idx + 3] = old.color[old_idx + 3]; - tile.color[target_idx + 4] = old.color[old_idx + 4]; - tile.color[target_idx + 5] = old.color[old_idx + 5]; - tile.color[target_idx + 6] = old.color[old_idx + 6]; - tile.color[target_idx + 7] = old.color[old_idx + 7]; - } - - return tile; - } - - Tile TileRAM::flipH(const Tile& old) const - { - static const auto NUM_COLS = 8; - static const auto NUM_ROWS = 8; - static const auto PIXELS_PER_ROW = 8; - - Tile tile; - - for (auto col = 0; col < NUM_COLS; ++col) - { - auto old_idx = col; - auto target_idx = NUM_COLS - col; - - for (auto row = 0; row < NUM_ROWS; ++row) - { - auto row_offset = (row * PIXELS_PER_ROW) - 1; - tile.color[target_idx + row_offset] = old.color[old_idx + row_offset]; - } - } - - return tile; - } - - std::vector TileRAM::getTiles() - { - std::vector tiles; - - for (auto i = 0u; i < NUM_TILES; i++) - { - unsigned int offset = i * TILE_SIZE; - uint8_t* current_tile_ptr = tile_ram_ + offset; - - Tile tile; - int row = 0; - - for (auto j = 0u; j < TILE_SIZE; j += 2) - { - uint8_t lsb = current_tile_ptr[j + 0]; - uint8_t msb = current_tile_ptr[j + 1]; - - setRow(tile, msb, lsb, row); - row++; - } - - tiles.push_back(tile); - } - - return tiles; - } - - void TileRAM::setRow(Tile& tile, uint8_t msb, uint8_t lsb, int row) const - { - int offset = row * 8; - - int pixel = 0; - for (int i = 7; i >= 0; i--) - { - uint8_t mask = (1 << i); - uint8_t color = (((msb & mask) >> i) << 1) | ((lsb & mask) >> i); - - tile.color[offset + pixel] = color; - pixel++; - } - } - - TileRAM::~TileRAM() - { - } - } -} diff --git a/components/gameboycore/src/timer.cpp b/components/gameboycore/src/timer.cpp deleted file mode 100644 index 362263d7..00000000 --- a/components/gameboycore/src/timer.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "gameboycore/timer.h" - -namespace gb -{ - Timer::Timer(MMU& mmu) : - controller_(mmu.get(memorymap::TIMER_CONTROLLER_REGISTER)), - counter_(mmu.get(memorymap::TIMER_COUNTER_REGISTER)), - modulo_(mmu.get(memorymap::TIMER_MODULO_REGISTER)), - divider_(mmu.get(memorymap::DIVIDER_REGISER)), - t_clock_(0), - base_clock_(0), - div_clock_(0), - timer_interrupt_(mmu, InterruptProvider::Interrupt::TIMER) - { - } - - void Timer::update(const uint8_t machine_cycles) - { - // M clock increments at 1/4 the T clock rate - t_clock_ += machine_cycles * 4; - - // timer ticks occur at 1/16 the CPU cycles - while (t_clock_ >= 16) - { - t_clock_ -= 16; - tick(); - } - } - - void Timer::tick() - { - // base clock dividers - static constexpr int freqs[] = { - 64, // 4 KHz - 1, // 262 KHz (base) - 4, // 65 KHz - 16 // 16 KHz - }; - - base_clock_++; - div_clock_++; - - // do divider clock - if (div_clock_ == 16) - { - divider_++; - div_clock_ = 0; - } - - // only if timer is enabled - if (controller_ & 0x04) - { - // get frequency - auto freq = freqs[controller_ & 0x03]; - - // increment counter - while (base_clock_ >= freq) - { - base_clock_ -= freq; - - if (counter_ == 0xFF) - { - counter_ = modulo_; - timer_interrupt_.set(); - } - else - { - counter_++; - } - } - } - } - - Timer::~Timer() - { - } -} \ No newline at end of file diff --git a/components/gnuboy/include/cpu.h b/components/gnuboy/include/gnuboy/cpu.h similarity index 91% rename from components/gnuboy/include/cpu.h rename to components/gnuboy/include/gnuboy/cpu.h index da24fcfa..9257ec13 100644 --- a/components/gnuboy/include/cpu.h +++ b/components/gnuboy/include/gnuboy/cpu.h @@ -3,7 +3,8 @@ -#include "defs.h" +#include "gnuboy/defs.h" +#include union reg diff --git a/components/gnuboy/include/cpucore.h b/components/gnuboy/include/gnuboy/cpucore.h similarity index 99% rename from components/gnuboy/include/cpucore.h rename to components/gnuboy/include/gnuboy/cpucore.h index 16441e2c..88723822 100644 --- a/components/gnuboy/include/cpucore.h +++ b/components/gnuboy/include/gnuboy/cpucore.h @@ -3,8 +3,8 @@ ** The variable defs of this header are candidates for moving into cpu.c */ -#include "defs.h" -#include +#include "gnuboy/defs.h" +// #include static const byte cycles_table[256] = { diff --git a/components/gnuboy/include/cpuregs.h b/components/gnuboy/include/gnuboy/cpuregs.h similarity index 94% rename from components/gnuboy/include/cpuregs.h rename to components/gnuboy/include/gnuboy/cpuregs.h index 9049a44f..0c463e1d 100644 --- a/components/gnuboy/include/cpuregs.h +++ b/components/gnuboy/include/gnuboy/cpuregs.h @@ -6,8 +6,8 @@ -#include "defs.h" -#include "cpu.h" +#include "gnuboy/defs.h" +#include "gnuboy/cpu.h" #define LB(r) ((r).b[LO][LO]) #define HB(r) ((r).b[LO][HI]) diff --git a/components/gnuboy/include/defs.h b/components/gnuboy/include/gnuboy/defs.h similarity index 100% rename from components/gnuboy/include/defs.h rename to components/gnuboy/include/gnuboy/defs.h diff --git a/components/gnuboy/include/fastmem.h b/components/gnuboy/include/gnuboy/fastmem.h similarity index 95% rename from components/gnuboy/include/fastmem.h rename to components/gnuboy/include/gnuboy/fastmem.h index 87554505..803fcf08 100644 --- a/components/gnuboy/include/fastmem.h +++ b/components/gnuboy/include/gnuboy/fastmem.h @@ -3,8 +3,8 @@ #define __FASTMEM_H__ -#include "defs.h" -#include "mem.h" +#include "gnuboy/defs.h" +#include "gnuboy/mem.h" inline static byte readb(int a) diff --git a/components/gnuboy/include/fb.h b/components/gnuboy/include/gnuboy/fb.h similarity index 89% rename from components/gnuboy/include/fb.h rename to components/gnuboy/include/gnuboy/fb.h index 97d16ae1..a552c6e0 100644 --- a/components/gnuboy/include/fb.h +++ b/components/gnuboy/include/gnuboy/fb.h @@ -4,9 +4,7 @@ #define __FB_H__ -#include "defs.h" - - +#include "gnuboy/defs.h" struct fb { diff --git a/components/gnuboy/include/gnuboy.h b/components/gnuboy/include/gnuboy/gnuboy.h similarity index 98% rename from components/gnuboy/include/gnuboy.h rename to components/gnuboy/include/gnuboy/gnuboy.h index e8c778d8..641ddb6c 100644 --- a/components/gnuboy/include/gnuboy.h +++ b/components/gnuboy/include/gnuboy/gnuboy.h @@ -64,7 +64,7 @@ void init_exports(); void show_exports(); /* hw.c */ -#include "defs.h" /* need byte for below */ +#include "gnuboy/defs.h" /* need byte for below */ void hw_interrupt(byte i, byte mask); /* palette.c */ diff --git a/components/gnuboy/include/hw.h b/components/gnuboy/include/gnuboy/hw.h similarity index 95% rename from components/gnuboy/include/hw.h rename to components/gnuboy/include/gnuboy/hw.h index 83a4c0fb..c1cdfb67 100644 --- a/components/gnuboy/include/hw.h +++ b/components/gnuboy/include/gnuboy/hw.h @@ -2,7 +2,7 @@ #define __HW_H__ -#include "defs.h" +#include "gnuboy/defs.h" #define PAD_RIGHT 0x01 diff --git a/components/gnuboy/include/input.h b/components/gnuboy/include/gnuboy/input.h similarity index 100% rename from components/gnuboy/include/input.h rename to components/gnuboy/include/gnuboy/input.h diff --git a/components/gnuboy/include/lcd.h b/components/gnuboy/include/gnuboy/lcd.h similarity index 95% rename from components/gnuboy/include/lcd.h rename to components/gnuboy/include/gnuboy/lcd.h index a1728492..7826b4fe 100644 --- a/components/gnuboy/include/lcd.h +++ b/components/gnuboy/include/gnuboy/lcd.h @@ -1,7 +1,7 @@ #ifndef __LCD_H__ #define __LCD_H__ -#include "defs.h" +#include "gnuboy/defs.h" struct vissprite { @@ -47,7 +47,7 @@ extern struct lcd lcd; extern struct scan scan; -void gb_lcd_begin(); +void lcd_begin(); void lcd_refreshline(); void pal_write(int i, byte b); void pal_write_dmg(int i, int mapnum, byte d); diff --git a/components/gnuboy/include/loader.h b/components/gnuboy/include/gnuboy/loader.h similarity index 64% rename from components/gnuboy/include/loader.h rename to components/gnuboy/include/gnuboy/loader.h index 202e39b8..74a50631 100644 --- a/components/gnuboy/include/loader.h +++ b/components/gnuboy/include/gnuboy/loader.h @@ -14,11 +14,9 @@ typedef struct loader_s extern loader_t loader; -void loader_init(char *s); -void loader_init_raw(uint8_t *romdata, size_t rom_data_size); +void loader_init(unsigned char *romptr, unsigned int rom_size); void loader_unload(); -int rom_load(); -int rom_load_raw(uint8_t *romdata, size_t rom_data_size); +int rom_load(unsigned char *romptr, unsigned int rom_size); int sram_load(); int sram_save(); void state_load(int n); diff --git a/components/gnuboy/include/mem.h b/components/gnuboy/include/gnuboy/mem.h similarity index 95% rename from components/gnuboy/include/mem.h rename to components/gnuboy/include/gnuboy/mem.h index 247f1087..303061b4 100644 --- a/components/gnuboy/include/mem.h +++ b/components/gnuboy/include/gnuboy/mem.h @@ -2,7 +2,7 @@ #define __MEM_H__ -#include "defs.h" +#include "gnuboy/defs.h" @@ -30,7 +30,7 @@ struct mbc struct rom { - byte* bank[512]; + byte (* bank)[16384]; char name[20]; int length; }; diff --git a/components/gnuboy/include/noise.h b/components/gnuboy/include/gnuboy/noise.h similarity index 99% rename from components/gnuboy/include/noise.h rename to components/gnuboy/include/gnuboy/noise.h index a0d16c6d..d29602b1 100644 --- a/components/gnuboy/include/noise.h +++ b/components/gnuboy/include/gnuboy/noise.h @@ -3,7 +3,7 @@ #define __NOISE_H__ -#include "defs.h" +#include "gnuboy/defs.h" #include /*DRAM_ATTR*/ static const byte noise7[] = diff --git a/components/gnuboy/include/pcm.h b/components/gnuboy/include/gnuboy/pcm.h similarity index 86% rename from components/gnuboy/include/pcm.h rename to components/gnuboy/include/gnuboy/pcm.h index 884cb28d..e9c0004d 100644 --- a/components/gnuboy/include/pcm.h +++ b/components/gnuboy/include/gnuboy/pcm.h @@ -3,7 +3,7 @@ #define __PCM_H__ -#include "defs.h" +#include "gnuboy/defs.h" #include struct pcm diff --git a/components/gnuboy/include/rc.h b/components/gnuboy/include/gnuboy/rc.h similarity index 100% rename from components/gnuboy/include/rc.h rename to components/gnuboy/include/gnuboy/rc.h diff --git a/components/gnuboy/include/regs.h b/components/gnuboy/include/gnuboy/regs.h similarity index 99% rename from components/gnuboy/include/regs.h rename to components/gnuboy/include/gnuboy/regs.h index 4457fd90..1b16679e 100644 --- a/components/gnuboy/include/regs.h +++ b/components/gnuboy/include/gnuboy/regs.h @@ -3,7 +3,7 @@ #define __REGS_H__ -#include "mem.h" +#include "gnuboy/mem.h" /* General internal/io stuff */ diff --git a/components/gnuboy/include/rtc.h b/components/gnuboy/include/gnuboy/rtc.h similarity index 92% rename from components/gnuboy/include/rtc.h rename to components/gnuboy/include/gnuboy/rtc.h index da7f21a9..04d60c1b 100644 --- a/components/gnuboy/include/rtc.h +++ b/components/gnuboy/include/gnuboy/rtc.h @@ -3,7 +3,7 @@ #include -#include "defs.h" +#include "gnuboy/defs.h" struct rtc { diff --git a/components/gnuboy/include/sound.h b/components/gnuboy/include/gnuboy/sound.h similarity index 100% rename from components/gnuboy/include/sound.h rename to components/gnuboy/include/gnuboy/sound.h diff --git a/components/gnuboy/main.c b/components/gnuboy/main.c deleted file mode 100644 index 71949bb9..00000000 --- a/components/gnuboy/main.c +++ /dev/null @@ -1,318 +0,0 @@ -#include -#include -#include -#include -#include - -#include "gnuboy.h" -#include "input.h" -#include "rc.h" -#include "loader.h" - - -void die(char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - abort(); -} - - -#if 0 -#include "Version" - - -static char *defaultconfig[] = -{ - "bind esc quit", - "bind up +up", - "bind down +down", - "bind left +left", - "bind right +right", - "bind d +a", - "bind s +b", - "bind enter +start", - "bind space +select", - "bind tab +select", - "bind joyup +up", - "bind joydown +down", - "bind joyleft +left", - "bind joyright +right", - "bind joy0 +b", - "bind joy1 +a", - "bind joy2 +select", - "bind joy3 +start", - "bind 1 \"set saveslot 1\"", - "bind 2 \"set saveslot 2\"", - "bind 3 \"set saveslot 3\"", - "bind 4 \"set saveslot 4\"", - "bind 5 \"set saveslot 5\"", - "bind 6 \"set saveslot 6\"", - "bind 7 \"set saveslot 7\"", - "bind 8 \"set saveslot 8\"", - "bind 9 \"set saveslot 9\"", - "bind 0 \"set saveslot 0\"", - "bind ins savestate", - "bind del loadstate", - "source gnuboy.rc", - NULL -}; - - -static void banner() -{ - printf("\ngnuboy " VERSION "\n"); -} - -static void copyright() -{ - banner(); - printf( -"Copyright (C) 2000-2001 Laguna and Gilgamesh\n" -"Portions contributed by other authors; see CREDITS for details.\n" -"\n" -"This program is free software; you can redistribute it and/or modify\n" -"it under the terms of the GNU General Public License as published by\n" -"the Free Software Foundation; either version 2 of the License, or\n" -"(at your option) any later version.\n" -"\n" -"This program is distributed in the hope that it will be useful,\n" -"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" -"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" -"GNU General Public License for more details.\n" -"\n" -"You should have received a copy of the GNU General Public License\n" -"along with this program; if not, write to the Free Software\n" -"Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n" -"\n"); -} - -static void usage(char *name) -{ - copyright(); - printf("Type %s --help for detailed help.\n\n", name); - exit(1); -} - -static void copying() -{ - copyright(); - exit(0); -} - -static void help(char *name) -{ - banner(); - printf("Usage: %s [options] romfile\n", name); - printf("\n" -" --source FILE read rc commands from FILE\n" -" --bind KEY COMMAND bind KEY to perform COMMAND\n" -" --VAR=VALUE set rc variable VAR to VALUE\n" -" --VAR set VAR to 1 (turn on boolean options)\n" -" --no-VAR set VAR to 0 (turn off boolean options)\n" -" --showvars list all available rc variables\n" -" --help display this help and exit\n" -" --version output version information and exit\n" -" --copying show copying permissions\n" -""); - exit(0); -} - - -static void version(char *name) -{ - printf("%s-" VERSION "\n", name); - exit(0); -} - - -void doevents() -{ - event_t ev; - int st; - - ev_poll(); - while (ev_getevent(&ev)) - { - if (ev.type != EV_PRESS && ev.type != EV_RELEASE) - continue; - st = (ev.type != EV_RELEASE); - rc_dokey(ev.code, st); - } -} - - - - -static void shutdown() -{ - vid_close(); - pcm_close(); -} - -void die(char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - exit(1); -} - -static int bad_signals[] = -{ - /* These are all standard, so no need to #ifdef them... */ - SIGINT, SIGSEGV, SIGTERM, SIGFPE, SIGABRT, SIGILL, -#ifdef SIGQUIT - SIGQUIT, -#endif -#ifdef SIGPIPE - SIGPIPE, -#endif - 0 -}; - -static void fatalsignal(int s) -{ - die("Signal %d\n", s); -} - -static void catch_signals() -{ - int i; - for (i = 0; bad_signals[i]; i++) - signal(bad_signals[i], fatalsignal); -} - - - -static char *base(char *s) -{ - char *p; - p = strrchr(s, DIRSEP_CHAR); - if (p) return p+1; - return s; -} - - -int main(int argc, char *argv[]) -{ - int i; - char *opt, *arg, *cmd, *s, *rom = 0; - - /* Avoid initializing video if we don't have to */ - for (i = 1; i < argc; i++) - { - if (!strcmp(argv[i], "--help")) - help(base(argv[0])); - else if (!strcmp(argv[i], "--version")) - version(base(argv[0])); - else if (!strcmp(argv[i], "--copying")) - copying(); - else if (!strcmp(argv[i], "--bind")) i += 2; - else if (!strcmp(argv[i], "--source")) i++; - else if (!strcmp(argv[i], "--showvars")) - { - show_exports(); - exit(0); - } - else if (argv[i][0] == '-' && argv[i][1] == '-'); - else if (argv[i][0] == '-' && argv[i][1]); - else rom = argv[i]; - } - - if (!rom) usage(base(argv[0])); - - /* If we have special perms, drop them ASAP! */ - vid_preinit(); - - init_exports(); - - s = strdup(argv[0]); - sys_sanitize(s); - sys_initpath(s); - - for (i = 0; defaultconfig[i]; i++) - rc_command(defaultconfig[i]); - - cmd = malloc(strlen(rom) + 11); - sprintf(cmd, "source %s", rom); - s = strchr(cmd, '.'); - if (s) *s = 0; - strcat(cmd, ".rc"); - rc_command(cmd); - - for (i = 1; i < argc; i++) - { - if (!strcmp(argv[i], "--bind")) - { - if (i + 2 >= argc) die("missing arguments to bind\n"); - cmd = malloc(strlen(argv[i+1]) + strlen(argv[i+2]) + 9); - sprintf(cmd, "bind %s \"%s\"", argv[i+1], argv[i+2]); - rc_command(cmd); - free(cmd); - i += 2; - } - else if (!strcmp(argv[i], "--source")) - { - if (i + 1 >= argc) die("missing argument to source\n"); - cmd = malloc(strlen(argv[i+1]) + 6); - sprintf(cmd, "source %s", argv[++i]); - rc_command(cmd); - free(cmd); - } - else if (!strncmp(argv[i], "--no-", 5)) - { - opt = strdup(argv[i]+5); - while ((s = strchr(opt, '-'))) *s = '_'; - cmd = malloc(strlen(opt) + 7); - sprintf(cmd, "set %s 0", opt); - rc_command(cmd); - free(cmd); - free(opt); - } - else if (argv[i][0] == '-' && argv[i][1] == '-') - { - opt = strdup(argv[i]+2); - if ((s = strchr(opt, '='))) - { - *s = 0; - arg = s+1; - } - else arg = "1"; - while ((s = strchr(opt, '-'))) *s = '_'; - while ((s = strchr(arg, ','))) *s = ' '; - - cmd = malloc(strlen(opt) + strlen(arg) + 6); - sprintf(cmd, "set %s %s", opt, arg); - - rc_command(cmd); - free(cmd); - free(opt); - } - /* short options not yet implemented */ - else if (argv[i][0] == '-' && argv[i][1]); - } - - /* FIXME - make interface modules responsible for atexit() */ - atexit(shutdown); - catch_signals(); - vid_init(); - pcm_init(); - - rom = strdup(rom); - sys_sanitize(rom); - - loader_init(rom); - - emu_reset(); - emu_run(); - - /* never reached */ - return 0; -} -#endif diff --git a/components/gnuboy/src/cpu.c b/components/gnuboy/src/cpu.c index 8d99af04..c74fddba 100644 --- a/components/gnuboy/src/cpu.c +++ b/components/gnuboy/src/cpu.c @@ -1,18 +1,18 @@ #pragma GCC optimize ("O3") -#include "gnuboy.h" -#include "defs.h" -#include "regs.h" -#include "hw.h" -#include "lcd.h" -#include "cpu.h" -#include "mem.h" -#include "fastmem.h" -#include "cpuregs.h" -#include "cpucore.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/regs.h" +#include "gnuboy/hw.h" +#include "gnuboy/lcd.h" +#include "gnuboy/cpu.h" +#include "gnuboy/mem.h" +#include "gnuboy/fastmem.h" +#include "gnuboy/cpuregs.h" +#include "gnuboy/cpucore.h" #ifdef USE_ASM -#include "asm.h" +#include "gnuboy/asm.h" #endif diff --git a/components/gnuboy/src/debug.c b/components/gnuboy/src/debug.c index 25e783f1..6519c400 100644 --- a/components/gnuboy/src/debug.c +++ b/components/gnuboy/src/debug.c @@ -1,16 +1,16 @@ #include #include -#include "gnuboy.h" -#include "defs.h" -#include "cpu.h" -#include "mem.h" -#include "regs.h" -#include "rc.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/cpu.h" +#include "gnuboy/mem.h" +#include "gnuboy/regs.h" +#include "gnuboy/rc.h" -#include "cpuregs.h" +#include "gnuboy/cpuregs.h" #ifndef GNUBOY_DISABLE_DEBUG_DISASSEMBLE -#include "fastmem.h" +#include "gnuboy/fastmem.h" static char *mnemonic_table[256] = { diff --git a/components/gnuboy/src/emu.c b/components/gnuboy/src/emu.c index a05705e6..76500b97 100644 --- a/components/gnuboy/src/emu.c +++ b/components/gnuboy/src/emu.c @@ -1,16 +1,14 @@ -#include "gnuboy.h" -#include "defs.h" -#include "regs.h" -#include "hw.h" -#include "cpu.h" -#include "sound.h" -#include "mem.h" -#include "lcd.h" -#include "rtc.h" -#include "rc.h" -#include "fb.h" - -#include "spi_lcd.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/regs.h" +#include "gnuboy/hw.h" +#include "gnuboy/cpu.h" +#include "gnuboy/sound.h" +#include "gnuboy/mem.h" +#include "gnuboy/lcd.h" +#include "gnuboy/rtc.h" +#include "gnuboy/rc.h" + static int framelen = 16743; static int framecount; @@ -69,13 +67,12 @@ void emu_step() */ void emu_run() { - // FIXME: how to handle timing? - // void *timer = sys_timer(); + void *timer = sys_timer(); int delay; - // FIXME: what does vid do? - // vid_begin(); - // for (;;) + vid_begin(); + lcd_begin(); + for (;;) { /* FRAME BEGIN */ @@ -84,8 +81,6 @@ void emu_run() end of the loop. */ cpu_emulate(2280); - gb_lcd_begin(); - /* FIXME: R_LY >= 0; comparsion to zero can also be removed altogether, R_LY is always 0 at this point */ while (R_LY > 0 && R_LY < 144) @@ -93,29 +88,22 @@ void emu_run() /* Step through visible line scanning phase */ emu_step(); } - // static size_t frame_num=0; - // printf("frame: %d\n", frame_num++); - // lcd_write_frame(0, 0, 160, 144, fb.ptr); + /* VBLANK BEGIN */ - // FIXME: what does this do? - // vid_end(); + vid_end(); rtc_tick(); sound_mix(); /* pcm_submit() introduces delay, if it fails we use sys_sleep() instead */ - // FIXME: what does this do... - // if (!pcm_submit()) + if (!pcm_submit()) { - /* FIXME: need to replace this with waits? delay = framelen - sys_elapsed(timer); sys_sleep(delay); sys_elapsed(timer); - */ } - // FIXME: what does this function do? - // doevents(); - // vid_begin(); + doevents(); + vid_begin(); if (framecount) { if (!--framecount) die("finished\n"); } if (!(R_LCDC & 0x80)) { diff --git a/components/gnuboy/src/events.c b/components/gnuboy/src/events.c index ca05eed2..f24cd621 100644 --- a/components/gnuboy/src/events.c +++ b/components/gnuboy/src/events.c @@ -6,7 +6,7 @@ */ -#include "input.h" +#include "gnuboy/input.h" char keystates[MAX_KEYS]; diff --git a/components/gnuboy/src/exports.c b/components/gnuboy/src/exports.c index 54050a04..be1cd4a7 100644 --- a/components/gnuboy/src/exports.c +++ b/components/gnuboy/src/exports.c @@ -1,8 +1,8 @@ #include #include -#include "gnuboy.h" -#include "rc.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/rc.h" extern rcvar_t rcfile_exports[], emu_exports[], loader_exports[], lcd_exports[], rtc_exports[], debug_exports[], sound_exports[], diff --git a/components/gnuboy/src/fastmem.c b/components/gnuboy/src/fastmem.c index 767fb8c8..cc442ef5 100644 --- a/components/gnuboy/src/fastmem.c +++ b/components/gnuboy/src/fastmem.c @@ -1,6 +1,6 @@ -#include "fastmem.h" +#include "gnuboy/fastmem.h" #include diff --git a/components/gnuboy/src/hw.c b/components/gnuboy/src/hw.c index ad821074..ecdae040 100644 --- a/components/gnuboy/src/hw.c +++ b/components/gnuboy/src/hw.c @@ -2,14 +2,14 @@ #include -#include "gnuboy.h" -#include "defs.h" -#include "cpu.h" -#include "hw.h" -#include "regs.h" -#include "lcd.h" -#include "mem.h" -#include "fastmem.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/cpu.h" +#include "gnuboy/hw.h" +#include "gnuboy/regs.h" +#include "gnuboy/lcd.h" +#include "gnuboy/mem.h" +#include "gnuboy/fastmem.h" #include "esp_attr.h" diff --git a/components/gnuboy/src/inflate.c b/components/gnuboy/src/inflate.c index 24729cc8..67b66a4c 100644 --- a/components/gnuboy/src/inflate.c +++ b/components/gnuboy/src/inflate.c @@ -1,4 +1,4 @@ -#include "gnuboy.h" +#include "gnuboy/gnuboy.h" /* Slightly modified from its original form so as not to exit the * program on errors. The resulting file remains in the public diff --git a/components/gnuboy/src/keytable.c b/components/gnuboy/src/keytable.c index 8e637e10..5f8c8186 100644 --- a/components/gnuboy/src/keytable.c +++ b/components/gnuboy/src/keytable.c @@ -10,8 +10,8 @@ #include #include -#include "gnuboy.h" -#include "input.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/input.h" /* keytable - Mapping of key names to codes, and back. A single code can have more than one name, in which case the first will be used diff --git a/components/gnuboy/src/lcd.c b/components/gnuboy/src/lcd.c index 9ff67acf..7d9e95e0 100755 --- a/components/gnuboy/src/lcd.c +++ b/components/gnuboy/src/lcd.c @@ -2,24 +2,24 @@ #include -#include "spi_lcd.h" - -#include "gnuboy.h" -#include "defs.h" -#include "regs.h" -#include "hw.h" -#include "mem.h" -#include "lcd.h" -#include "rc.h" -#include "fb.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/regs.h" +#include "gnuboy/hw.h" +#include "gnuboy/mem.h" +#include "gnuboy/lcd.h" +#include "gnuboy/rc.h" +#include "gnuboy/fb.h" #ifdef USE_ASM -#include "asm.h" +#include "gnuboy/asm.h" #endif #include #include #include +#include "spi_lcd.h" + struct lcd lcd; struct scan scan; @@ -52,7 +52,7 @@ static int sprsort = 1; static int sprdebug = 0; // BGR -#if 1 +#if 0 // Testing/Debug palette static int dmg_pal[4][4] = {{0xffffff, 0x808080, 0x404040, 0x000000}, {0xff0000, 0x800000, 0x400000, 0x000000}, @@ -636,19 +636,16 @@ static void IRAM_ATTR spr_scan() if (sprdebug) for (i = 0; i < NS; i++) BUF[i<<1] = 36; } -static int current_line = 0; -static int num_lines_written = 0; -inline void gb_lcd_begin() + +inline void lcd_begin() { vdest = fb.ptr; WY = R_WY; - current_line = 0; - num_lines_written = 0; } extern int frame; -// extern uint16_t* displayBuffer[2]; +extern uint16_t* displayBuffer[2]; int lastLcdDisabled = 0; void IRAM_ATTR lcd_refreshline() @@ -678,8 +675,8 @@ void IRAM_ATTR lcd_refreshline() { if (!lastLcdDisabled) { - // memset(displayBuffer[0], 0xff, 144 * 160 * 2); - // memset(displayBuffer[1], 0xff, 144 * 160 * 2); + memset(displayBuffer[0], 0xff, 144 * 160 * 2); + memset(displayBuffer[1], 0xff, 144 * 160 * 2); lastLcdDisabled = 1; } @@ -720,13 +717,7 @@ void IRAM_ATTR lcd_refreshline() while (cnt--) *(dst++) = PAL2[*(src++)]; } - current_line++; vdest += fb.pitch; - if ((current_line % 48) == 0) { - // printf("writing line: %d\n", current_line); - lcd_write_frame(0, current_line-48, 160, 48, fb.ptr); - vdest = fb.ptr; - } } inline static void updatepalette(int i) @@ -748,7 +739,8 @@ inline static void updatepalette(int i) // bit 10-14 blue b = (c >> 10) & 0x1f; - PAL2[i] = make_color(r,g,b); // (r << 11) | (g << (5 + 1)) | (b); + // PAL2[i] = (r << 11) | (g << (5 + 1)) | (b); + PAL2[i] = make_color(r << 3 , g << 3 , b << 3); } inline void pal_write(int i, byte b) @@ -821,7 +813,7 @@ void lcd_reset() { memset(&lcd, 0, sizeof lcd); - gb_lcd_begin(); + lcd_begin(); vram_dirty(); pal_dirty(); } diff --git a/components/gnuboy/src/lcdc.c b/components/gnuboy/src/lcdc.c index 59b97193..d4b17afa 100644 --- a/components/gnuboy/src/lcdc.c +++ b/components/gnuboy/src/lcdc.c @@ -2,12 +2,12 @@ #include -#include "gnuboy.h" -#include "defs.h" -#include "hw.h" -#include "cpu.h" -#include "regs.h" -#include "lcd.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/hw.h" +#include "gnuboy/cpu.h" +#include "gnuboy/regs.h" +#include "gnuboy/lcd.h" #include @@ -81,7 +81,7 @@ void IRAM_ATTR lcdc_change(byte b) R_LY = 0; stat_change(2); C = 40; - gb_lcd_begin(); + lcd_begin(); } } @@ -192,7 +192,7 @@ void IRAM_ATTR lcdc_trans() } if (R_LY == 0) { - gb_lcd_begin(); + lcd_begin(); stat_change(2); /* -> search */ C += 40; break; diff --git a/components/gnuboy/src/loader.c b/components/gnuboy/src/loader.c index e62e50db..04695bb0 100644 --- a/components/gnuboy/src/loader.c +++ b/components/gnuboy/src/loader.c @@ -4,38 +4,26 @@ #include #include +#include "nvs_flash.h" #include "esp_partition.h" #include "esp_system.h" #include "esp_heap_caps.h" -#ifndef GNUBOY_NO_MINIZIP -/* -** use http://www.winimage.com/zLibDll/minizip.html v1.1 -** which needs zlib -*/ -#include -#endif /* GNUBOY_USE_MINIZIP */ - - -#include "gnuboy.h" -#include "defs.h" -#include "regs.h" -#include "mem.h" -#include "hw.h" -#include "lcd.h" -#include "rtc.h" -#include "rc.h" -#include "sound.h" - +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/regs.h" +#include "gnuboy/mem.h" +#include "gnuboy/hw.h" +#include "gnuboy/lcd.h" +#include "gnuboy/rtc.h" +#include "gnuboy/rc.h" +#include "gnuboy/sound.h" +// #include "settings.h" + +void* FlashAddress = 0; FILE* RomFile = NULL; uint8_t BankCache[512 / 8]; - -#ifndef GNUBOY_NO_MINIZIP -static int check_zip(char *filename); -static byte *loadzipfile(char *archive, int *filesize); -#endif /* GNUBOY_USE_MINIZIP */ - static int mbc_table[256] = { 0, 1, 1, 1, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, @@ -111,196 +99,76 @@ static int forcedmg=0, gbamode=0; static int memfill = 0, memrand = -1; -extern const char* SD_BASE_PATH; - - static void initmem(void *mem, int size) { char *p = mem; - memset(p, 0xff /*memfill*/, size); + //memset(p, 0xff /*memfill*/, size); + if (memfill >= 0) + memset(p, memfill, size); } -static byte *loadfile(FILE *f, int *len) -{ - int l = 0, c = 0; - byte *d = NULL; -#ifdef GNUBOY_ENABLE_ORIGINAL_SLOW_INCREMENTAL_LOADER - int p = 0; - byte buf[512]; +static byte *inf_buf; +static int inf_pos, inf_len; +static byte *_data_ptr = NULL; - for(;;) - { - c = fread(buf, 1, sizeof buf, f); - if (c <= 0) break; - l += c; - d = realloc(d, l); - if (!d) return 0; - memcpy(d+p, buf, c); - p += c; - } -#else /* fast and no space check */ - /* alloc and read once - NOTE no sanity check on filesize */ - fseek(f, 0, SEEK_END); - l = ftell(f); - fseek(f, 0, SEEK_SET); - d = (byte*) malloc(l); - if (d != NULL) - { - c = fread((void *) d, (size_t) l, 1, f); - if (c != 1) - { - l = 0; - /* NOTE if this fails caller doesn't catch it (ditto the slow and "safe" version) */ - } - } -#endif /* GNUBOY_ENABLE_ORIGINAL_SLOW_INCREMENTAL_LOADER */ - *len = l; - return d; -} - - -// TODO: update this to take in a pointer to the right location... -int rom_load() +int rom_load(uint8_t *rom_data, size_t rom_data_size) { - byte c, *data, *header; + /*byte c, *data, *header; int len = 0, rlen; - // TODO: figure out what this address is... - data = (void*)0x3f800000; + const esp_partition_t* part; + spi_flash_mmap_handle_t hrom; + esp_err_t err; - printf("loader: Reading from flash.\n"); + nvs_flash_init(); - // copy from flash - spi_flash_mmap_handle_t hrom; + part=esp_partition_find_first(0x40, 1, NULL); - const esp_partition_t* part = esp_partition_find_first(0x40, 0, NULL); if (part == 0) { - printf("esp_partition_find_first failed.\n"); - abort(); + printf("Couldn't find rom part!\n"); } - for (size_t offset = 0; offset < 0x400000; offset += 0x100000) { - esp_err_t err = esp_partition_read(part, offset, (void *)(data + offset), 0x100000); - if (err != ESP_OK) { - printf("esp_partition_read failed. size = %lx, offset = %x (%d)\n", part->size, offset, err); - abort(); - } + err = esp_partition_mmap(part, 0, 3*1024*1024, SPI_FLASH_MMAP_DATA, (const void**)&data, &hrom); + if (err != ESP_OK) { + printf("Couldn't map rom part!\n"); } - printf("Initialized. ROM@%p\n", data); - header = data; - - memcpy(rom.name, header+0x0134, 16); - //if (rom.name[14] & 0x80) rom.name[14] = 0; - //if (rom.name[15] & 0x80) rom.name[15] = 0; - rom.name[16] = 0; - printf("loader: rom.name='%s'\n", rom.name); - - int tmp = *((int*)(header + 0x0144)); - c = (tmp >> 24) & 0xff; - mbc.type = mbc_table[c]; - mbc.batt = (batt_table[c] && !nobatt) || forcebatt; - rtc.batt = rtc_table[c]; - - tmp = *((int*)(header + 0x0148)); - mbc.romsize = romsize_table[(tmp & 0xff)]; - mbc.ramsize = ramsize_table[((tmp >> 8) & 0xff)]; - - if (!mbc.romsize) die("unknown ROM size %02X\n", header[0x0148]); - if (!mbc.ramsize) die("unknown SRAM size %02X\n", header[0x0149]); - - const char* mbcName; - switch (mbc.type) + BankCache[0] = 1; + */ + byte c, *data, *header; + int len = 0, rlen; + data = heap_caps_malloc(0x400000, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + _data_ptr = data; + /* + // nvs_flash_init(); + data = (void*)0x3f800000; { - case MBC_NONE: - mbcName = "MBC_NONE"; - break; - - case MBC_MBC1: - mbcName = "MBC_MBC1"; - break; - - case MBC_MBC2: - mbcName = "MBC_MBC2"; - break; - - case MBC_MBC3: - mbcName = "MBC_MBC3"; - break; - - case MBC_MBC5: - mbcName = "MBC_MBC5"; - break; - - case MBC_RUMBLE: - mbcName = "MBC_RUMBLE"; - break; + printf("loader: Reading from flash.\n"); - case MBC_HUC1: - mbcName = "MBC_HUC1"; - break; + // copy from flash + spi_flash_mmap_handle_t hrom; - case MBC_HUC3: - mbcName = "MBC_HUC3"; - break; - - default: - mbcName = "(unknown)"; - break; - } - - rlen = 16384 * mbc.romsize; - int sram_length = 8192 * mbc.ramsize; - printf("loader: mbc.type=%s, mbc.romsize=%d (%dK), mbc.ramsize=%d (%dK)\n", mbcName, mbc.romsize, rlen / 1024, mbc.ramsize, sram_length / 1024); - - // ROM - rom.bank[0] = data; - rom.length = rlen; - - // SRAM - ram.sram_dirty = 1; - ram.sbank = malloc(sram_length); - if (!ram.sbank) - { - // not enough free RAM, - // check if PSRAM has free space - if (rlen <= (0x100000 * 3) && - sram_length <= 0x100000) + const esp_partition_t* part = esp_partition_find_first(0x40, 0, NULL); + if (part == 0) { - ram.sbank = data + (0x100000 * 3); - printf("SRAM using PSRAM.\n"); + printf("esp_partition_find_first failed.\n"); + abort(); } - else + + for (size_t offset = 0; offset < 0x400000; offset += 0x100000) { - printf("No free spece for SRAM.\n"); - abort(); + esp_err_t err = esp_partition_read(part, offset, (void *)(data + offset), 0x100000); + if (err != ESP_OK) + { + printf("esp_partition_read failed. size = %x, offset = %x (%d)\n", part->size, offset, err); + abort(); + } } } - - - initmem(ram.sbank, 8192 * mbc.ramsize); - initmem(ram.ibank, 4096 * 8); - - mbc.rombank = 1; - mbc.rambank = 0; - - tmp = *((int*)(header + 0x0140)); - c = tmp >> 24; - hw.cgb = ((c == 0x80) || (c == 0xc0)) && !forcedmg; - hw.gba = (hw.cgb && gbamode); - - return 0; -} - -int rom_load_raw(uint8_t *romdata, size_t rom_data_size) -{ - byte c, *data, *header; - int len = 0, rlen; - - data = romdata; - rlen = rom_data_size; - - printf("loader: initialized. ROM@%p\n", data); + */ + memcpy(data, rom_data, rom_data_size); + // data = rom_data; + printf("Initialized. ROM@%p\n", data); header = data; memcpy(rom.name, header+0x0134, 16); @@ -367,7 +235,8 @@ int rom_load_raw(uint8_t *romdata, size_t rom_data_size) printf("loader: mbc.type=%s, mbc.romsize=%d (%dK), mbc.ramsize=%d (%dK)\n", mbcName, mbc.romsize, rlen / 1024, mbc.ramsize, sram_length / 1024); // ROM - rom.bank[0] = data; + //rom.bank[0] = data; + rom.bank = data; rom.length = rlen; // SRAM @@ -412,7 +281,7 @@ int sram_load() /* Consider sram loaded at this point, even if file doesn't exist */ ram.loaded = 1; - + /* const esp_partition_t* part; spi_flash_mmap_handle_t hrom; esp_err_t err; @@ -435,7 +304,7 @@ int sram_load() printf("sram_load: sram load OK.\n"); ram.sram_dirty = 0; } - } + }*/ return 0; } @@ -447,7 +316,7 @@ int sram_save() if (!mbc.batt || !ram.loaded || !mbc.ramsize) return -1; - const esp_partition_t* part; + /*const esp_partition_t* part; spi_flash_mmap_handle_t hrom; esp_err_t err; @@ -475,7 +344,7 @@ int sram_save() { printf("sram_load: sram save OK.\n"); } - } + }*/ return 0; } @@ -483,6 +352,7 @@ int sram_save() void state_save(int n) { + /* FILE *f; char *name; @@ -497,11 +367,13 @@ void state_save(int n) fclose(f); } free(name); + */ } void state_load(int n) { + /* FILE *f; char *name; @@ -520,6 +392,7 @@ void state_load(int n) mem_updatemap(); } free(name); + */ } void rtc_save() @@ -543,8 +416,11 @@ void rtc_load() void loader_unload() { - // TODO: unmap flash - + printf("freeing data\n"); + if (_data_ptr != NULL) + free(_data_ptr); + printf("data freed!\n"); + /* sram_save(); if (romfile) free(romfile); if (sramfile) free(sramfile); @@ -552,9 +428,10 @@ void loader_unload() if (rom.bank) free(rom.bank); if (ram.sbank) free(ram.sbank); romfile = sramfile = saveprefix = 0; - rom.bank[0] = 0; + rom.bank = 0; ram.sbank = 0; mbc.type = mbc.romsize = mbc.ramsize = mbc.batt = 0; + */ } /* basename/dirname like function */ @@ -583,18 +460,9 @@ static void cleanup() /* IDEA - if error, write emergency savestate..? */ } -void loader_init(char *s) +void loader_init(uint8_t *romptr, size_t rom_size) { - char *name, *p; - - rom_load(); - rtc_load(); - - //atexit(cleanup); -} - -void loader_init_raw(uint8_t *romdata, size_t rom_data_size) { - rom_load_raw(romdata, rom_data_size); + rom_load(romptr, rom_size); rtc_load(); } diff --git a/components/gnuboy/src/mem.c b/components/gnuboy/src/mem.c index 4a99c058..864a523e 100644 --- a/components/gnuboy/src/mem.c +++ b/components/gnuboy/src/mem.c @@ -2,14 +2,14 @@ #include -#include "gnuboy.h" -#include "defs.h" -#include "hw.h" -#include "regs.h" -#include "mem.h" -#include "rtc.h" -#include "lcd.h" -#include "sound.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/hw.h" +#include "gnuboy/regs.h" +#include "gnuboy/mem.h" +#include "gnuboy/rtc.h" +#include "gnuboy/lcd.h" +#include "gnuboy/sound.h" #include "esp_partition.h" #include "esp_attr.h" @@ -18,87 +18,6 @@ struct mbc mbc; struct rom rom; struct ram ram; -extern FILE* RomFile; -extern uint8_t BankCache[512 / 8]; - -static inline byte* GetRomPtr(short bank) -{ - // GBC pages are 16k. - const size_t BANK_SIZE = 0x4000; - byte* const PSRAM = (byte*)0x3f800000; - const size_t OFFSET = bank * BANK_SIZE; - - if (RomFile) - { - short slot = bank >> 3; - uint8_t bit = 1 << (bank & 0x7); - - if (!(BankCache[slot] & bit)) - { - //printf("GetRomPtr: Loading bank=%d, slot=%d, bit=%d.\n", bank, slot, bit); - - // Stop the SPI bus - // FIXME: do i need to do this? - // odroid_display_lock_gb_display(); - - //odroid_display_drain_spi(); - - // Load the 16K page - if (fseek(RomFile, OFFSET, SEEK_SET)) - { - printf("GetRomPtr: fseek failed. OFFSET=%d\n", OFFSET); - - // FIXME: do i need to do this - // odroid_audio_terminate(); - - // FIXME: how should i show errors on the display? - // odroid_display_show_sderr(ODROID_SD_ERR_BADFILE); - abort(); - } - - #if 0 - const size_t BLOCK_SIZE = 512; - for (size_t offset = 0; offset < BANK_SIZE; offset += BLOCK_SIZE) - { - size_t count = fread((uint8_t*)PSRAM + (bank * BANK_SIZE) + offset, 1, BLOCK_SIZE, RomFile); - __asm__("nop"); - __asm__("nop"); - __asm__("nop"); - __asm__("nop"); - __asm__("memw"); - - if (count < BLOCK_SIZE) break; - } - #else - size_t count = fread((uint8_t*)PSRAM + OFFSET, 1, BANK_SIZE, RomFile); - if (count < BANK_SIZE) - { - printf("GetRomPtr: fread failed. bank=%d, count=%d\n", bank, count); - - // FIXME: do i need to do this - // odroid_audio_terminate(); - - // FIXME: how should I show errors on the display? - // odroid_display_show_sderr(ODROID_SD_ERR_BADFILE); - abort(); - } - #endif - - BankCache[slot] |= bit; - - //printf("%s: bank=%d, result=%p\n", __func__, bank, (void*)PSRAM + OFFSET); - - // FIXME: do i need to do this? - // odroid_display_unlock_gb_display(); - } - } - - byte* result = PSRAM + OFFSET; - //printf("%s: bank=%d, result=%p\n", __func__, bank, result); - - return result; -} - /* * In order to make reads and writes efficient, we keep tables * (indexed by the high nibble of the address) specifying which @@ -116,12 +35,6 @@ void IRAM_ATTR mem_updatemap() int n; byte **map; - mbc.rombank &= (mbc.romsize - 1); - - rom.bank[mbc.rombank] = GetRomPtr(mbc.rombank); - - mbc.rambank &= (mbc.ramsize - 1); - map = mbc.rmap; map[0x0] = rom.bank[0]; map[0x1] = rom.bank[0]; @@ -142,8 +55,8 @@ void IRAM_ATTR mem_updatemap() //if (0 && (R_STAT & 0x03) == 0x03) //{ - map[0x8] = NULL; - map[0x9] = NULL; + map[0x8] = NULL; + map[0x9] = NULL; //} //else //{ @@ -158,7 +71,7 @@ void IRAM_ATTR mem_updatemap() // } // else // { - map[0xA] = map[0xB] = NULL; + map[0xA] = map[0xB] = NULL; //} #if 1 @@ -211,58 +124,58 @@ void IRAM_ATTR ioreg_write(byte r, byte b) { switch (r) { - case RI_VBK: - case RI_BCPS: - case RI_OCPS: - case RI_BCPD: - case RI_OCPD: - case RI_SVBK: - case RI_KEY1: - case RI_HDMA1: - case RI_HDMA2: - case RI_HDMA3: - case RI_HDMA4: - case RI_HDMA5: + case RI_VBK: + case RI_BCPS: + case RI_OCPS: + case RI_BCPD: + case RI_OCPD: + case RI_SVBK: + case RI_KEY1: + case RI_HDMA1: + case RI_HDMA2: + case RI_HDMA3: + case RI_HDMA4: + case RI_HDMA5: return; } } switch(r) { - case RI_TIMA: - case RI_TMA: - case RI_TAC: - case RI_SCY: - case RI_SCX: - case RI_WY: - case RI_WX: + case RI_TIMA: + case RI_TMA: + case RI_TAC: + case RI_SCY: + case RI_SCX: + case RI_WY: + case RI_WX: REG(r) = b; break; - case RI_BGP: + case RI_BGP: if (R_BGP == b) break; pal_write_dmg(0, 0, b); pal_write_dmg(8, 1, b); R_BGP = b; break; - case RI_OBP0: + case RI_OBP0: if (R_OBP0 == b) break; pal_write_dmg(64, 2, b); R_OBP0 = b; break; - case RI_OBP1: + case RI_OBP1: if (R_OBP1 == b) break; pal_write_dmg(72, 3, b); R_OBP1 = b; break; - case RI_IF: - case RI_IE: + case RI_IF: + case RI_IE: REG(r) = b & 0x1F; break; - case RI_P1: + case RI_P1: REG(r) = b; pad_refresh(); break; - case RI_SC: + case RI_SC: /* FIXME - this is a hack for stupid roms that probe serial */ if ((b & 0x81) == 0x81) { @@ -272,81 +185,81 @@ void IRAM_ATTR ioreg_write(byte r, byte b) } R_SC = b; /* & 0x7f; */ break; - case RI_SB: + case RI_SB: REG(r) = b; break; - case RI_DIV: + case RI_DIV: REG(r) = 0; break; - case RI_LCDC: + case RI_LCDC: lcdc_change(b); break; - case RI_STAT: + case RI_STAT: stat_write(b); break; - case RI_LYC: + case RI_LYC: REG(r) = b; stat_trigger(); break; - case RI_VBK: + case RI_VBK: REG(r) = b | 0xFE; mem_updatemap(); break; - case RI_BCPS: + case RI_BCPS: R_BCPS = b & 0xBF; R_BCPD = lcd.pal[b & 0x3F]; break; - case RI_OCPS: + case RI_OCPS: R_OCPS = b & 0xBF; R_OCPD = lcd.pal[64 + (b & 0x3F)]; break; - case RI_BCPD: + case RI_BCPD: R_BCPD = b; pal_write(R_BCPS & 0x3F, b); if (R_BCPS & 0x80) R_BCPS = (R_BCPS+1) & 0xBF; break; - case RI_OCPD: + case RI_OCPD: R_OCPD = b; pal_write(64 + (R_OCPS & 0x3F), b); if (R_OCPS & 0x80) R_OCPS = (R_OCPS+1) & 0xBF; break; - case RI_SVBK: + case RI_SVBK: REG(r) = b & 0x07; mem_updatemap(); break; - case RI_DMA: + case RI_DMA: hw_dma(b); break; - case RI_KEY1: + case RI_KEY1: REG(r) = (REG(r) & 0x80) | (b & 0x01); break; - case RI_HDMA1: + case RI_HDMA1: REG(r) = b; break; - case RI_HDMA2: + case RI_HDMA2: REG(r) = b; //& 0xF0; break; - case RI_HDMA3: + case RI_HDMA3: REG(r) = b; //& 0x1F; break; - case RI_HDMA4: + case RI_HDMA4: REG(r) = b; //& 0xF0; break; - case RI_HDMA5: + case RI_HDMA5: hw_hdma_cmd(b); break; } switch (r) { - case RI_BGP: - case RI_OBP0: - case RI_OBP1: + case RI_BGP: + case RI_OBP0: + case RI_OBP1: /* printf("palette reg %02X write %02X at LY=%02X\n", r, b, R_LY); */ - case RI_HDMA1: - case RI_HDMA2: - case RI_HDMA3: - case RI_HDMA4: - case RI_HDMA5: + case RI_HDMA1: + case RI_HDMA2: + case RI_HDMA3: + case RI_HDMA4: + case RI_HDMA5: /* printf("HDMA %d: %02X\n", r - RI_HDMA1 + 1, b); */ break; } @@ -358,44 +271,44 @@ byte IRAM_ATTR ioreg_read(byte r) { switch(r) { - case RI_SC: + case RI_SC: r = R_SC; R_SC &= 0x7f; return r; - case RI_P1: - case RI_SB: - case RI_DIV: - case RI_TIMA: - case RI_TMA: - case RI_TAC: - case RI_LCDC: - case RI_STAT: - case RI_SCY: - case RI_SCX: - case RI_LY: - case RI_LYC: - case RI_BGP: - case RI_OBP0: - case RI_OBP1: - case RI_WY: - case RI_WX: - case RI_IE: - case RI_IF: + case RI_P1: + case RI_SB: + case RI_DIV: + case RI_TIMA: + case RI_TMA: + case RI_TAC: + case RI_LCDC: + case RI_STAT: + case RI_SCY: + case RI_SCX: + case RI_LY: + case RI_LYC: + case RI_BGP: + case RI_OBP0: + case RI_OBP1: + case RI_WY: + case RI_WX: + case RI_IE: + case RI_IF: return REG(r); - case RI_VBK: - case RI_BCPS: - case RI_OCPS: - case RI_BCPD: - case RI_OCPD: - case RI_SVBK: - case RI_KEY1: - case RI_HDMA1: - case RI_HDMA2: - case RI_HDMA3: - case RI_HDMA4: - case RI_HDMA5: + case RI_VBK: + case RI_BCPS: + case RI_OCPS: + case RI_BCPD: + case RI_OCPD: + case RI_SVBK: + case RI_KEY1: + case RI_HDMA1: + case RI_HDMA2: + case RI_HDMA3: + case RI_HDMA4: + case RI_HDMA5: if (hw.cgb) return REG(r); - default: + default: return 0xff; } } @@ -418,17 +331,17 @@ void IRAM_ATTR mbc_write(int a, byte b) /* printf("mbc %d: rom bank %02X -[%04X:%02X]-> ", mbc.type, mbc.rombank, a, b); */ switch (mbc.type) { - case MBC_MBC1: + case MBC_MBC1: switch (ha & 0xE) { - case 0x0: + case 0x0: mbc.enableram = ((b & 0x0F) == 0x0A); break; - case 0x2: + case 0x2: if ((b & 0x1F) == 0) b = 0x01; mbc.rombank = (mbc.rombank & 0x60) | (b & 0x1F); break; - case 0x4: + case 0x4: if (mbc.model) { mbc.rambank = b & 0x03; @@ -436,7 +349,7 @@ void IRAM_ATTR mbc_write(int a, byte b) } mbc.rombank = (mbc.rombank & 0x1F) | ((int)(b&3)<<5); break; - case 0x6: + case 0x6: mbc.model = b & 0x1; break; } @@ -455,59 +368,58 @@ void IRAM_ATTR mbc_write(int a, byte b) } break; - case MBC_MBC3: + case MBC_MBC3: switch (ha & 0xE) { - case 0x0: + case 0x0: mbc.enableram = ((b & 0x0F) == 0x0A); break; - case 0x2: + case 0x2: if ((b & 0x7F) == 0) b = 0x01; mbc.rombank = b & 0x7F; break; - case 0x4: + case 0x4: rtc.sel = b & 0x0f; mbc.rambank = b & 0x03; break; - case 0x6: + case 0x6: rtc_latch(b); break; } break; - case MBC_RUMBLE: + case MBC_RUMBLE: switch (ha & 0xF) { - case 0x4: - case 0x5: + case 0x4: + case 0x5: /* FIXME - save high bit as rumble state */ /* mask off high bit */ b &= 0x7; break; } /* fall thru */ - case MBC_MBC5: + case MBC_MBC5: switch (ha & 0xF) { - case 0x0: - case 0x1: + case 0x0: + case 0x1: mbc.enableram = ((b & 0x0F) == 0x0A); break; - case 0x2: + case 0x2: //if ((b & 0xFF) == 0) b = 0x01; mbc.rombank = (mbc.rombank & 0x100) | (b); break; - case 0x3: + case 0x3: mbc.rombank = (mbc.rombank & 0x0FF) | ((int)(b&1)<<8); break; - case 0x4: - case 0x5: + case 0x4: + case 0x5: mbc.rambank = b & 0x0f; //printf("MBC5: Mapped rambank=%d\n", mbc.rambank); break; - default: - // FIXME: what does this do and why do we get here? - // printf("MBC_MBC5: invalid write to 0x%x (0x%x)\n", a, b); + default: + printf("MBC_MBC5: invalid write to 0x%x (0x%x)\n", a, b); break; } break; @@ -515,14 +427,14 @@ void IRAM_ATTR mbc_write(int a, byte b) case MBC_HUC1: /* FIXME - this is all guesswork -- is it right??? */ switch (ha & 0xE) { - case 0x0: + case 0x0: mbc.enableram = ((b & 0x0F) == 0x0A); break; - case 0x2: + case 0x2: if ((b & 0x1F) == 0) b = 0x01; mbc.rombank = (mbc.rombank & 0x60) | (b & 0x1F); break; - case 0x4: + case 0x4: if (mbc.model) { mbc.rambank = b & 0x03; @@ -530,27 +442,27 @@ void IRAM_ATTR mbc_write(int a, byte b) } mbc.rombank = (mbc.rombank & 0x1F) | ((int)(b&3)<<5); break; - case 0x6: + case 0x6: mbc.model = b & 0x1; break; } break; - case MBC_HUC3: + case MBC_HUC3: switch (ha & 0xE) { - case 0x0: + case 0x0: mbc.enableram = ((b & 0x0F) == 0x0A); break; - case 0x2: + case 0x2: b &= 0x7F; mbc.rombank = b ? b : 1; break; - case 0x4: + case 0x4: rtc.sel = b & 0x0f; mbc.rambank = b & 0x03; break; - case 0x6: + case 0x6: rtc_latch(b); break; } @@ -576,17 +488,17 @@ void IRAM_ATTR mem_write(int a, byte b) /* printf("write to 0x%04X: 0x%02X\n", a, b); */ switch (ha) { - case 0x0: - case 0x2: - case 0x4: - case 0x6: + case 0x0: + case 0x2: + case 0x4: + case 0x6: mbc_write(a, b); break; - case 0x8: + case 0x8: /* if ((R_STAT & 0x03) == 0x03) break; */ vram_write(a & 0x1FFF, b); break; - case 0xA: + case 0xA: if (!mbc.enableram) break; if (rtc.sel&8) { @@ -610,7 +522,7 @@ void IRAM_ATTR mem_write(int a, byte b) //printf("mem_write: bank=%d, sram %p=0x%d\n", mbc.rambank, (void*)(a & 0x1fff), b); //printf("mem_write: check - write=0x%x, read=0x%x\n", b, ram.sbank[mbc.rambank][a & 0x1FFF]); break; - case 0xC: + case 0xC: if ((a & 0xF000) == 0xC000) { ram.ibank[0][a & 0x0FFF] = b; @@ -619,7 +531,7 @@ void IRAM_ATTR mem_write(int a, byte b) n = R_SVBK & 0x07; ram.ibank[n?n:1][a & 0x0FFF] = b; break; - case 0xE: + case 0xE: if (a < 0xFE00) { mem_write(a & 0xDFFF, b); @@ -665,17 +577,17 @@ byte IRAM_ATTR mem_read(int a) switch (ha) { - case 0x0: - case 0x2: + case 0x0: + case 0x2: //if (a >= 16384) return 0xff; return rom.bank[0][a & 0x3fff]; - case 0x4: - case 0x6: + case 0x4: + case 0x6: return rom.bank[mbc.rombank][a & 0x3FFF]; - case 0x8: + case 0x8: /* if ((R_STAT & 0x03) == 0x03) return 0xFF; */ return lcd.vbank[R_VBK&1][a & 0x1FFF]; - case 0xA: + case 0xA: if (!mbc.enableram && mbc.type == MBC_HUC3) return 0x01; if (!mbc.enableram) @@ -690,12 +602,12 @@ byte IRAM_ATTR mem_read(int a) __asm__("memw"); //printf("mem_read: bank=%d, sram %p=0x%d\n", mbc.rambank, (void*)(a & 0x1fff), ram.sbank[mbc.rambank][a & 0x1FFF]); return ram.sbank[mbc.rambank][a & 0x1FFF]; - case 0xC: + case 0xC: if ((a & 0xF000) == 0xC000) return ram.ibank[0][a & 0x0FFF]; n = R_SVBK & 0x07; return ram.ibank[n?n:1][a & 0x0FFF]; - case 0xE: + case 0xE: if (a < 0xFE00) return mem_read(a & 0xDFFF); if ((a & 0xFF00) == 0xFE00) { diff --git a/components/gnuboy/src/palette.c b/components/gnuboy/src/palette.c index b0c93d23..2ce7f590 100644 --- a/components/gnuboy/src/palette.c +++ b/components/gnuboy/src/palette.c @@ -1,9 +1,9 @@ #if 0 #include -#include "gnuboy.h" -#include "defs.h" -#include "fb.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/fb.h" static byte palmap[32768]; diff --git a/components/gnuboy/src/path.c b/components/gnuboy/src/path.c index 09471cc9..8f9b7945 100644 --- a/components/gnuboy/src/path.c +++ b/components/gnuboy/src/path.c @@ -2,7 +2,7 @@ #include #include -#include "gnuboy.h" +#include "gnuboy/gnuboy.h" #ifdef ALT_PATH_SEP #define SEP ';' diff --git a/components/gnuboy/src/rccmds.c b/components/gnuboy/src/rccmds.c index 5efa0eed..c7dbdca0 100644 --- a/components/gnuboy/src/rccmds.c +++ b/components/gnuboy/src/rccmds.c @@ -1,11 +1,11 @@ #include #include -#include "gnuboy.h" -#include "defs.h" -#include "rc.h" -#include "hw.h" -#include "loader.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/rc.h" +#include "gnuboy/hw.h" +#include "gnuboy/loader.h" diff --git a/components/gnuboy/src/rcfile.c b/components/gnuboy/src/rcfile.c index 71697b51..2d1055f2 100644 --- a/components/gnuboy/src/rcfile.c +++ b/components/gnuboy/src/rcfile.c @@ -3,10 +3,10 @@ #include #include -#include "gnuboy.h" -#include "defs.h" -#include "rc.h" -#include "hw.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/rc.h" +#include "gnuboy/hw.h" char *rcpath; diff --git a/components/gnuboy/src/rckeys.c b/components/gnuboy/src/rckeys.c index 22157311..3aa8d8de 100644 --- a/components/gnuboy/src/rckeys.c +++ b/components/gnuboy/src/rckeys.c @@ -2,10 +2,10 @@ #include #include -#include "gnuboy.h" -#include "defs.h" -#include "rc.h" -#include "input.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/rc.h" +#include "gnuboy/input.h" char *keybind[MAX_KEYS]; diff --git a/components/gnuboy/src/rcvars.c b/components/gnuboy/src/rcvars.c index bd57946d..c4c6aee3 100644 --- a/components/gnuboy/src/rcvars.c +++ b/components/gnuboy/src/rcvars.c @@ -2,9 +2,9 @@ #include #include -#include "gnuboy.h" -#include "defs.h" -#include "rc.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/rc.h" static rcvar_t *rcvars; diff --git a/components/gnuboy/src/refresh.c b/components/gnuboy/src/refresh.c index 67adb4dc..ebc3c198 100644 --- a/components/gnuboy/src/refresh.c +++ b/components/gnuboy/src/refresh.c @@ -1,11 +1,11 @@ -#include "gnuboy.h" -#include "defs.h" -#include "lcd.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/lcd.h" #define BUF (scan.buf) #ifdef USE_ASM -#include "asm.h" +#include "gnuboy/asm.h" #endif diff --git a/components/gnuboy/src/rtc.c b/components/gnuboy/src/rtc.c index 4e29f080..6b7aebde 100644 --- a/components/gnuboy/src/rtc.c +++ b/components/gnuboy/src/rtc.c @@ -4,10 +4,10 @@ #include #include -#include "defs.h" -#include "mem.h" -#include "rtc.h" -#include "rc.h" +#include "gnuboy/defs.h" +#include "gnuboy/mem.h" +#include "gnuboy/rtc.h" +#include "gnuboy/rc.h" struct rtc rtc; diff --git a/components/gnuboy/src/save.c b/components/gnuboy/src/save.c index 01ee3550..e616e326 100644 --- a/components/gnuboy/src/save.c +++ b/components/gnuboy/src/save.c @@ -3,16 +3,16 @@ #include #include -#include "gnuboy.h" -#include "defs.h" -#include "cpu.h" -#include "cpuregs.h" -#include "hw.h" -#include "regs.h" -#include "lcd.h" -#include "rtc.h" -#include "mem.h" -#include "sound.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/cpu.h" +#include "gnuboy/cpuregs.h" +#include "gnuboy/hw.h" +#include "gnuboy/regs.h" +#include "gnuboy/lcd.h" +#include "gnuboy/rtc.h" +#include "gnuboy/mem.h" +#include "gnuboy/sound.h" diff --git a/components/gnuboy/src/sound.c b/components/gnuboy/src/sound.c index 8555b601..7833d4c5 100644 --- a/components/gnuboy/src/sound.c +++ b/components/gnuboy/src/sound.c @@ -2,21 +2,19 @@ #include -#include "gnuboy.h" -#include "defs.h" -#include "pcm.h" -#include "sound.h" -#include "cpu.h" -#include "hw.h" -#include "regs.h" -#include "rc.h" -#include "noise.h" +#include "gnuboy/gnuboy.h" +#include "gnuboy/defs.h" +#include "gnuboy/pcm.h" +#include "gnuboy/sound.h" +#include "gnuboy/cpu.h" +#include "gnuboy/hw.h" +#include "gnuboy/regs.h" +#include "gnuboy/rc.h" +#include "gnuboy/noise.h" #include #include "freertos/FreeRTOS.h" -struct pcm pcm; - static const byte DRAM_ATTR dmgwave[16] = { 0xac, 0xdd, 0xda, 0x48, diff --git a/components/libgbc/CMakeLists.txt b/components/libgbc/CMakeLists.txt deleted file mode 100644 index 0d7e604b..00000000 --- a/components/libgbc/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -idf_component_register( - INCLUDE_DIRS "include" - SRC_DIRS "src" - REQUIRES box-emu-hal - ) - -target_compile_options(${COMPONENT_LIB} PRIVATE -Wall -Wextra -O2 -Ofast) -# target_compile_definitions(${COMPONENT_LIB} PRIVATE GAMEBRO_INDEXED_FRAME) -target_compile_definitions(${COMPONENT_LIB} PUBLIC USE_GAMEBOY_LIBGBC) diff --git a/components/libgbc/include/apu.hpp b/components/libgbc/include/apu.hpp deleted file mode 100644 index dd3fdef6..00000000 --- a/components/libgbc/include/apu.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#include "common.hpp" -#include -#include -#include - -namespace gbc -{ -class APU -{ -public: - APU(Machine& mach); - using audio_stream_t = std::function; - - void on_audio_out(audio_stream_t); - void simulate(); - - uint8_t read(uint16_t, uint8_t& reg); - void write(uint16_t, uint8_t, uint8_t& reg); - - // serialization - int restore_state(const std::vector&, int); - void serialize_state(std::vector&) const; - - Machine& machine() noexcept { return m_machine; } - -private: - struct generator_t - { - }; - struct channel_t - { - bool generators_enabled[4] = {false}; - bool enabled = true; - }; - - struct state_t - { - bool nothing = false; - - } m_state; - - Machine& m_machine; - audio_stream_t m_audio_out; -}; -} // namespace gbc diff --git a/components/libgbc/include/common.hpp b/components/libgbc/include/common.hpp deleted file mode 100644 index 2cfaeeb1..00000000 --- a/components/libgbc/include/common.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -#include -#include - -#ifndef LIKELY -#define LIKELY(x) __builtin_expect((x), 1) -#endif -#ifndef UNLIKELY -#define UNLIKELY(x) __builtin_expect((x), 0) -#endif - -namespace gbc -{ -class CPU; -class Machine; -class Memory; -class IO; -constexpr bool ENABLE_GBC = true; - -inline void setflag(bool expr, uint8_t& flg, uint8_t mask) -{ - if (expr) - flg |= mask; - else - flg &= ~mask; -} -extern void assert_failed(const int expr, const char* strexpr, - const char* filename, const int line); - -class MachineException : public std::exception -{ -public: - MachineException(const char* m) - : message{m} {} - - const char* what() const noexcept override - { - return message; - } -private: - const char* const message; -}; - -} // namespace gbc - - -#define GBC_ASSERT(expr) \ - { if (!(expr)) { \ - gbc::assert_failed(expr, #expr, __FILE__, __LINE__); \ - __builtin_unreachable(); \ - } } diff --git a/components/libgbc/include/cpu.hpp b/components/libgbc/include/cpu.hpp deleted file mode 100644 index 4b643300..00000000 --- a/components/libgbc/include/cpu.hpp +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once -#include "instruction.hpp" -#include "interrupt.hpp" -#include "registers.hpp" -#include "tracing.hpp" -#include -#include -#include -#include - -namespace gbc -{ -class Machine; -class Memory; - -class CPU -{ -public: - CPU(Machine&) noexcept; - void reset() noexcept; - void simulate(); - uint64_t gettime() const noexcept { return m_state.cycles_total; } - - void execute(); - // read and increment PC, and cycle counters, then tick hardware - uint8_t readop8(); - uint16_t readop16(); - // peek at operands past PC without doing anything else - uint8_t peekop8(int disp); - uint16_t peekop16(int disp); - // ticking memory reads & writes - uint8_t mtread8(uint16_t addr); - void mtwrite8(uint16_t addr, uint8_t value); - uint16_t mtread16(uint16_t addr); - void mtwrite16(uint16_t addr, uint16_t value); - // perform one hardware tick - void hardware_tick(); - void incr_cycles(int count); - void push_value(uint16_t addr); - void push_and_jump(uint16_t addr); - void jump(uint16_t dest); - void stop(); - void wait(); // wait for interrupts - void buggy_halt(); - instruction_t& decode(uint8_t opcode); - - regs_t& registers() noexcept { return m_state.registers; } - // helpers for reading and writing (HL) - uint8_t read_hl(); - void write_hl(uint8_t); - - Memory& memory() noexcept { return m_memory; } - Machine& machine() noexcept { return m_machine; } - - void enable_interrupts() noexcept; - void disable_interrupts() noexcept; - bool ime() const noexcept { return m_state.ime; } - - bool is_stopping() const noexcept { return m_state.stopped; } - bool is_halting() const noexcept { return m_state.asleep; } - - // serialization - int restore_state(const std::vector&, int); - void serialize_state(std::vector&) const; - - // debugging - void breakpoint(uint16_t address, breakpoint_t func); - auto& breakpoints() { return this->m_breakpoints; } - void default_pausepoint(uint16_t address); - void break_on_steps(int steps); - void break_now() { this->m_break = true; } - void break_checks(); - bool is_breaking() const noexcept { return this->m_break; } - static void print_and_pause(CPU&, const uint8_t opcode); - - std::string to_string() const; - -private: - void handle_interrupts(); - void handle_speed_switch(); - void execute_interrupts(const uint8_t); - bool break_time() const; - void interrupt(interrupt_t&); - - Machine& m_machine; - Memory& m_memory; - struct state_t - { - regs_t registers; - uint64_t cycles_total = 0; - uint8_t last_flags = 0xff; - int8_t intr_pending = 0; - bool ime = false; - bool stopped = false; - bool asleep = false; - bool haltbug = false; - uint8_t switch_cycles = 0; - } m_state; - // debugging - bool m_break = false; - mutable int16_t m_break_steps = 0; - mutable int16_t m_break_steps_cnt = 0; - std::map m_breakpoints; -}; - -inline void CPU::breakpoint(uint16_t addr, breakpoint_t func) { this->m_breakpoints[addr] = func; } - -inline void CPU::default_pausepoint(const uint16_t addr) -{ - this->breakpoint(addr, breakpoint_t{[](gbc::CPU& cpu, const uint8_t opcode) { - print_and_pause(cpu, opcode); - }}); -} -} // namespace gbc diff --git a/components/libgbc/include/generators.hpp b/components/libgbc/include/generators.hpp deleted file mode 100644 index f104ce32..00000000 --- a/components/libgbc/include/generators.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -namespace gbc -{ -struct sample_t -{ - uint16_t left; - uint16_t right; -}; -struct Generator -{ - virtual void tick(Machine&) = 0; - virtual sample_t sample(Machine&) = 0; -}; - -struct WhiteNoise : public Generator -{ - void tick(Machine& machine) override {} - sample_t sample(Machine& machine) override { return {0, 0}; } -}; -} // namespace gbc diff --git a/components/libgbc/include/gpu.hpp b/components/libgbc/include/gpu.hpp deleted file mode 100644 index d52f98b8..00000000 --- a/components/libgbc/include/gpu.hpp +++ /dev/null @@ -1,184 +0,0 @@ -#pragma once -#include "common.hpp" -#include "sprite.hpp" -#include "tiledata.hpp" -#include -#include - -#include "spi_lcd.h" - -namespace gbc -{ -enum dmg_variant_t -{ - LIGHTER_GREEN = 0, - DARKER_GREEN, - GRAYSCALE -}; -class GPU -{ -public: - static const int SCREEN_W = 160; - static const int SCREEN_H = 144; - static const int NUM_PALETTES = 64; - // this palette idx is used when the screen is off - static const int WHITE_IDX = 32; -#ifdef GAMEBRO_INDEXED_FRAME - using PixelType = uint8_t; -#else - using PixelType = uint16_t; -#endif - - GPU(Machine&) noexcept; - void reset() noexcept; - void simulate(); - // the vector is resized to exactly fit the screen - const auto& pixels() const noexcept { return m_pixels; } - // trap on palette changes - using palchange_func_t = std::function; - void on_palchange(palchange_func_t func) { m_on_palchange = func; } - // get default GB palette - static std::array dmg_colors(dmg_variant_t = GRAYSCALE); - // set GB palette used in RGBA mode - void set_dmg_variant(dmg_variant_t); - // get the 32-bit RGB colors (with alpha=0) - uint32_t expand_cgb_color(uint8_t idx) const noexcept; - uint32_t expand_dmg_color(uint8_t idx) const noexcept; - static uint32_t color15_to_rgba32(uint16_t color15); - // enable / disable scanline rendering - void scanline_rendering(bool en) noexcept { this->m_render = en; } - // render whole frame now (NOTE: changes are often made mid-frame!) - void render_frame(); - - bool is_vblank() const noexcept; - bool is_hblank() const noexcept; - int current_scanline() const noexcept { return m_state.current_scanline; } - uint64_t frame_count() const noexcept { return m_state.frame_count; } - - void set_mode(uint8_t mode); - uint8_t get_mode() const noexcept; - - uint16_t video_offset() const noexcept { return m_state.video_offset; } - void set_video_bank(uint8_t bank); - void lcd_power_changed(bool state); - - bool lcd_enabled() const noexcept; - bool window_enabled() const noexcept; - std::pair window_size(); - bool window_visible(); - int window_x(); - int window_y(); - - // CGB palette registers - uint8_t& getpal(uint16_t index) noexcept { return m_state.cgb_palette[index]; } - void setpal(uint16_t index, uint8_t value); - uint8_t getpal(uint16_t index) const { return m_state.cgb_palette.at(index); } - - // serialization - int restore_state(const std::vector&, int); - void serialize_state(std::vector&) const; - - Machine& machine() noexcept { return m_memory.machine(); } - Memory& memory() noexcept { return m_memory; } - IO& io() noexcept { return m_io; } - const Memory& memory() const noexcept { return m_memory; } - std::vector dump_background(); - std::vector dump_window(); - std::vector dump_tiles(int bank); - // OAM sprite inspection - const Sprite* sprites_begin() const noexcept; - const Sprite* sprites_end() const noexcept; - -private: - uint64_t scanline_cycles() const noexcept; - uint64_t oam_cycles() const noexcept; - uint64_t vram_cycles() const noexcept; - uint64_t hblank_cycles() const noexcept; - void render_scanline(int y); - void do_ly_comparison(); - TileData create_tiledata(uint16_t tiles, uint16_t patt); - tileconf_t tile_config(); - sprite_config_t sprite_config(); - std::vector find_sprites(const sprite_config_t&) const; - uint16_t colorize_tile(const tileconf_t&, uint8_t attr, uint8_t idx); - uint16_t colorize_sprite(const Sprite*, sprite_config_t&, uint8_t); - // addresses - uint16_t bg_tiles() const noexcept; - uint16_t window_tiles() const noexcept; - uint16_t tile_data() const noexcept; - - Memory& m_memory; - IO& m_io; - uint8_t& m_reg_lcdc; - uint8_t& m_reg_stat; - uint8_t& m_reg_ly; - std::vector m_pixels; - palchange_func_t m_on_palchange = nullptr; - dmg_variant_t m_variant = LIGHTER_GREEN; - bool m_render = true; - - struct state_t - { - uint64_t period = 0; - uint64_t frame_count = 0; - int current_scanline = 0; - uint16_t video_offset = 0x0; - bool white_frame = false; - // 0-63: tiles 64-127: sprites - std::array cgb_palette; - } m_state; -}; - -inline std::array GPU::dmg_colors(dmg_variant_t variant) -{ -#define mRGB(r, g, b) (make_color(r,g,b)) - switch (variant) - { - case LIGHTER_GREEN: - return std::array{ - mRGB(224, 248, 208), // least green - mRGB(136, 192, 112), // less green - mRGB(52, 104, 86), // very green - mRGB(8, 24, 32) // dark green - }; - case DARKER_GREEN: - return std::array{ - mRGB(175, 203, 70), // least green - mRGB(121, 170, 109), // less green - mRGB(34, 111, 95), // very green - mRGB(8, 41, 85) // dark green - }; - case GRAYSCALE: - default: - return std::array{ - mRGB(232, 232, 232), // least gray - mRGB(160, 160, 160), // less gray - mRGB(88, 88, 88), // very gray - mRGB(16, 16, 16) // dark gray - }; - } -#undef mRGB -} - -// convert palette to grayscale colors -inline uint32_t GPU::expand_dmg_color(const uint8_t index) const noexcept -{ - return dmg_colors(m_variant).at(index); -} -// convert 15-bit color to 32-bit RGBA -inline uint32_t GPU::expand_cgb_color(const uint8_t index) const noexcept -{ - const uint16_t color15 = getpal(index * 2) | (getpal(index * 2 + 1) << 8); - const uint16_t r = ((color15 >> 0) & 0x1f) << 3; - const uint16_t g = ((color15 >> 5) & 0x1f) << 3; - const uint16_t b = ((color15 >> 10) & 0x1f) << 3; - return r | (g << 8) | (b << 16); -} -inline uint32_t GPU::color15_to_rgba32(const uint16_t color15) -{ - const uint16_t r = ((color15 >> 0) & 0x1f) << 3; - const uint16_t g = ((color15 >> 5) & 0x1f) << 3; - const uint16_t b = ((color15 >> 10) & 0x1f) << 3; - return (r << 0u) | (g << 8u) | (b << 16u) | (255ul << 24u); -} -} // namespace gbc diff --git a/components/libgbc/include/instruction.hpp b/components/libgbc/include/instruction.hpp deleted file mode 100644 index d296cd63..00000000 --- a/components/libgbc/include/instruction.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include - -namespace gbc -{ -class CPU; -using handler_t = void (*)(CPU&, uint8_t); -using printer_t = int (*)(char*, size_t, CPU&, uint8_t); - -enum alu_t : uint8_t -{ - ADD = 0, - ADC, - SUB, - SBC, - AND, - XOR, - OR, - CP -}; - -struct instruction_t -{ - const handler_t handler; - const printer_t printer; -}; -} // namespace gbc diff --git a/components/libgbc/include/interrupt.hpp b/components/libgbc/include/interrupt.hpp deleted file mode 100644 index 5279e51c..00000000 --- a/components/libgbc/include/interrupt.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include -#include - -namespace gbc -{ -class Machine; -struct interrupt_t; -using interrupt_handler = std::function; - -struct interrupt_t -{ - const uint8_t mask; - const uint16_t fixed_address; - const char* const name = ""; - interrupt_handler callback = nullptr; - - interrupt_t(uint8_t msk, uint16_t addr, const char* n) : mask(msk), fixed_address(addr), name(n) - {} -}; - -} // namespace gbc diff --git a/components/libgbc/include/io.hpp b/components/libgbc/include/io.hpp deleted file mode 100644 index d0dd5be6..00000000 --- a/components/libgbc/include/io.hpp +++ /dev/null @@ -1,170 +0,0 @@ -#pragma once -#include "common.hpp" -#include "interrupt.hpp" -#include -#include - -namespace gbc -{ -class IO -{ -public: - static const uint16_t SND_START = 0xff10; - static const uint16_t SND_END = 0xff40; - enum regnames_t - { - REG_P1 = 0xff00, - // TIMER - REG_DIV = 0xff04, - REG_TIMA = 0xff05, - REG_TMA = 0xff06, - REG_TAC = 0xff07, - // SOUND - REG_NR10 = 0xff10, - REG_NR11 = 0xff11, - REG_NR12 = 0xff12, - REG_NR13 = 0xff13, - REG_NR14 = 0xff14, - REG_NR21 = 0xff16, - REG_NR22 = 0xff17, - REG_NR23 = 0xff18, - REG_NR24 = 0xff19, - REG_NR30 = 0xff1a, - REG_NR31 = 0xff1b, - REG_NR32 = 0xff1c, - REG_NR33 = 0xff1d, - REG_NR34 = 0xff1e, - REG_NR41 = 0xff20, - REG_NR42 = 0xff21, - REG_NR43 = 0xff22, - REG_NR44 = 0xff23, - REG_NR50 = 0xff24, - REG_NR51 = 0xff25, - REG_NR52 = 0xff26, - REG_WAV0 = 0xff30, - REG_WAVF = 0xff3f, - // LCD - REG_LCDC = 0xff40, - REG_STAT = 0xff41, - REG_SCY = 0xff42, - REG_SCX = 0xff43, - REG_LY = 0xff44, - REG_LYC = 0xff45, - REG_DMA = 0xff46, - // PALETTE - REG_BGP = 0xff47, - REG_OBP0 = 0xff48, - REG_OBP1 = 0xff49, - REG_WY = 0xff4a, - REG_WX = 0xff4b, - - // CBG I/O regs - REG_KEY1 = 0xff4d, - REG_VBK = 0xff4f, - REG_BOOT = 0xff50, - - REG_HDMA1 = 0xff51, - REG_HDMA2 = 0xff52, - REG_HDMA3 = 0xff53, - REG_HDMA4 = 0xff54, - REG_HDMA5 = 0xff55, - - REG_BGPI = 0xff68, - REG_BGPD = 0xff69, - REG_OBPI = 0xff6a, - REG_OBPD = 0xff6b, - - REG_SVBK = 0xff70, - - // INTERRUPTS - REG_IF = 0xff0f, - REG_IE = 0xffff, - }; - - IO(Machine&); - void write_io(const uint16_t, uint8_t); - uint8_t read_io(const uint16_t); - - void trigger_keys(uint8_t); - bool joypad_is_disabled() const noexcept; - void trigger(interrupt_t&); - uint8_t interrupt_mask() const; - void start_dma(uint16_t src); - void start_hdma(uint16_t src, uint16_t dst, uint16_t bytes); - bool dma_active() const noexcept { return oam_dma().bytes_left > 0; } - bool hdma_active() const noexcept { return hdma().bytes_left > 0; } - - void perform_stop(); - void deactivate_stop(); - void reset_divider(); - - Machine& machine() noexcept { return m_machine; } - - void reset(); - void simulate(); - - inline uint8_t& reg(const uint16_t addr) { return m_state.ioregs[addr & 0x7f]; } - inline const uint8_t& reg(const uint16_t addr) const { return m_state.ioregs[addr & 0x7f]; } - - struct joypad_t - { - uint8_t ioswitch = 0; - uint8_t keypad = 0xFF; - uint8_t buttons = 0xFF; - uint8_t last_mask = 0; - }; - inline joypad_t& joypad() { return m_state.joypad; } - - using joypad_read_handler_t = std::function; - void on_joypad_read(joypad_read_handler_t h) { m_jp_handler = h; } - void trigger_joypad_read() - { - if (m_jp_handler) m_jp_handler(machine(), joypad().ioswitch); - } - - interrupt_t vblank; - interrupt_t lcd_stat; - interrupt_t timerint; - interrupt_t serialint; - interrupt_t joypadint; - interrupt_t debugint; - - // serialization - int restore_state(const std::vector&, int); - void serialize_state(std::vector&) const; - -private: - struct dma_t - { - uint64_t cur_line; - int8_t slow_start = 0; - uint16_t src; - uint16_t dst; - int32_t bytes_left = 0; - }; - const dma_t& oam_dma() const noexcept { return m_state.dma; } - dma_t& oam_dma() noexcept { return m_state.dma; } - const dma_t& hdma() const noexcept { return m_state.hdma; } - dma_t& hdma() noexcept { return m_state.hdma; } - - Machine& m_machine; - struct state_t - { - std::array ioregs = {}; - joypad_t joypad; - uint16_t divider = 0; - uint16_t timabug = 0; - // LCD on/off during STOP? - bool lcd_powered = false; - uint8_t reg_ie = 0x0; - - dma_t dma; - dma_t hdma; - } m_state; - - joypad_read_handler_t m_jp_handler = nullptr; -}; - -inline void IO::trigger(interrupt_t& intr) { this->reg(REG_IF) |= intr.mask; } -inline uint8_t IO::interrupt_mask() const { return this->m_state.reg_ie & this->reg(REG_IF); } -} // namespace gbc diff --git a/components/libgbc/include/machine.hpp b/components/libgbc/include/machine.hpp deleted file mode 100644 index c12d14f1..00000000 --- a/components/libgbc/include/machine.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include "apu.hpp" -#include "cpu.hpp" -#include "gpu.hpp" -#include "interrupt.hpp" -#include "io.hpp" -#include "memory.hpp" - -namespace gbc -{ -enum keys_t -{ - DPAD_RIGHT = 0x1, - DPAD_LEFT = 0x2, - DPAD_UP = 0x4, - DPAD_DOWN = 0x8, - BUTTON_A = 0x10, - BUTTON_B = 0x20, - BUTTON_SELECT = 0x40, - BUTTON_START = 0x80 -}; - -class Machine -{ -public: - Machine(const uint8_t *rom, size_t rom_data_size, bool init = true) - : Machine(std::string_view{(const char *)rom, rom_data_size}, init) {} - Machine(const std::string_view rom, bool init = true); - Machine(const std::vector& rom, bool init = true) - : Machine(std::string_view{(const char *)rom.data(), rom.size()}, init) {} - - CPU cpu; - Memory memory; - IO io; - GPU gpu; - APU apu; - - void simulate(); - void simulate_one_frame(); - void reset(); - uint64_t now() noexcept; - bool is_running() const noexcept { return this->m_running; } - bool is_cgb() const noexcept { return this->m_cgb_mode; } - - // set delegates to be notified on interrupts - enum interrupt - { - VBLANK, - TIMER, - JOYPAD, - DEBUG, - }; - void set_handler(interrupt, interrupt_handler); - - // use keys_t to form an 8-bit mask - void set_inputs(uint8_t mask); - - // serialization (state-keeping) - size_t restore_state(const std::vector&); - void serialize_state(std::vector&) const; - - /// debugging aids /// - bool verbose_instructions = false; - bool verbose_interrupts = false; - bool verbose_banking = false; - // make the machine stop when an undefined OP happens - bool stop_when_undefined = false; - bool break_on_interrupts = false; - bool break_on_io = false; - void break_now(); - bool is_breaking() const noexcept; - void undefined(); - void stop() noexcept; - -private: - bool m_running = true; - bool m_cgb_mode = false; -}; - -inline void Machine::simulate() { cpu.simulate(); } -} // namespace gbc diff --git a/components/libgbc/include/mbc.hpp b/components/libgbc/include/mbc.hpp deleted file mode 100644 index 4b123dac..00000000 --- a/components/libgbc/include/mbc.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include - -namespace gbc -{ -class Memory; - -class MBC -{ -public: - using range_t = std::pair; - static constexpr range_t ROMbank0{0x0000, 0x4000}; - static constexpr range_t ROMbankX{0x4000, 0x8000}; - static constexpr range_t RAMbankX{0xA000, 0xC000}; - static constexpr range_t WRAM_0{0xC000, 0xD000}; - static constexpr range_t WRAM_bX{0xD000, 0xE000}; - static constexpr range_t EchoRAM{0xE000, 0xFE00}; - - MBC(Memory&, const std::string_view rom); - - const auto& rom() const noexcept { return m_rom; } - uint32_t rombank_offset() const noexcept { return m_state.rom_bank_offset; } - - bool ram_enabled() const noexcept { return m_state.ram_enabled; } - size_t rombank_size() const noexcept { return 0x4000; } - size_t rambank_size() const noexcept { return 0x2000; } - size_t wrambank_size() const noexcept { return 0x1000; } - - uint8_t read(uint16_t addr); - void write(uint16_t addr, uint8_t value); - - void set_rombank(int offset); - void set_rambank(int offset); - void set_wrambank(int offset); - void set_mode(int mode); - - // serialization - int restore_state(const std::vector&, int); - void serialize_state(std::vector&) const; - -private: - void write_MBC1M(uint16_t, uint8_t); - void write_MBC3(uint16_t, uint8_t); - void write_MBC5(uint16_t, uint8_t); - bool verbose_banking() const noexcept; - - Memory& m_memory; - const std::string_view m_rom; - struct state_t - { - uint32_t rom_bank_offset = 0x4000; - uint16_t ram_banks = 0; - uint16_t ram_bank_offset = 0x0; - uint32_t ram_bank_size = 0x0; - uint16_t wram_offset = 0x1000; - uint16_t wram_size = 0x2000; - bool ram_enabled = false; - bool rtc_enabled = false; - bool rumble = false; - uint16_t rom_bank_reg = 0x1; - uint8_t mode_select = 0; - uint8_t version = 1; - std::array wram; - } m_state; - // RAM is so big we want to deal with it dynamically - std::array m_ram; - - friend class Memory; - void init(); -}; -} // namespace gbc diff --git a/components/libgbc/include/mbc1m.hpp b/components/libgbc/include/mbc1m.hpp deleted file mode 100644 index 6c4b68ad..00000000 --- a/components/libgbc/include/mbc1m.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "mbc.hpp" - -#include "machine.hpp" -#include "memory.hpp" - -namespace gbc -{ -inline void MBC::write_MBC1M(uint16_t addr, uint8_t value) -{ - switch (addr & 0xF000) - { - case 0x2000: - case 0x3000: - // ROM bank number - this->m_state.rom_bank_reg &= 0x60; - this->m_state.rom_bank_reg |= value & 0x1F; - // lower 5 bits cant be 0 - if ((value & 0x1F) == 0) { this->m_state.rom_bank_reg++; } - this->set_rombank(this->m_state.rom_bank_reg); - return; - case 0x4000: - case 0x5000: - // ROM / RAM bank select - if (this->m_state.mode_select == 1) { this->set_rambank(value & 0x3); } - // always changed ROM bank value - this->m_state.rom_bank_reg &= 0x1F; - this->m_state.rom_bank_reg |= (value & 0x3) << 5; - this->set_rombank(this->m_state.rom_bank_reg); - return; - case 0x6000: - case 0x7000: - // RAM / ROM mode select - this->set_mode(value & 0x1); - } -} -} // namespace gbc diff --git a/components/libgbc/include/mbc3.hpp b/components/libgbc/include/mbc3.hpp deleted file mode 100644 index cd184170..00000000 --- a/components/libgbc/include/mbc3.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "mbc.hpp" - -#include "machine.hpp" -#include "memory.hpp" - -namespace gbc -{ -inline void MBC::write_MBC3(uint16_t addr, uint8_t value) -{ - switch (addr & 0xF000) - { - case 0x2000: - case 0x3000: - this->m_state.rom_bank_reg = value & 0x7F; - if (m_state.rom_bank_reg == 0) m_state.rom_bank_reg = 1; - this->set_rombank(this->m_state.rom_bank_reg); - return; - case 0x4000: - case 0x5000: - this->set_rambank(value & 0x7); - this->m_state.rtc_enabled = (value & 0x80); - return; - case 0x6000: - case 0x7000: - // TODO: RTC latch values - return; - } -} -} // namespace gbc diff --git a/components/libgbc/include/mbc5.hpp b/components/libgbc/include/mbc5.hpp deleted file mode 100644 index c3ff3934..00000000 --- a/components/libgbc/include/mbc5.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "mbc.hpp" - -#include "machine.hpp" -#include "memory.hpp" - -namespace gbc -{ -inline void MBC::write_MBC5(uint16_t addr, uint8_t value) -{ - switch (addr & 0xF000) - { - case 0x2000: - // ROM bank select (lower) - this->m_state.rom_bank_reg &= 0x100; - this->m_state.rom_bank_reg |= value & 0xFF; - this->set_rombank(this->m_state.rom_bank_reg); - return; - case 0x3000: - // ROM bank select (upper) - this->m_state.rom_bank_reg &= 0xFF; - this->m_state.rom_bank_reg |= value & 0x100; - this->set_rombank(this->m_state.rom_bank_reg); - return; - case 0x4000: - case 0x5000: - // RAM bank select - this->set_rambank(value & 0xF); - return; - } -} -} // namespace gbc diff --git a/components/libgbc/include/memory.hpp b/components/libgbc/include/memory.hpp deleted file mode 100644 index 4b50a0f0..00000000 --- a/components/libgbc/include/memory.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once -#include "common.hpp" -#include "mbc.hpp" -#include -#include -#include -#include -#include - -namespace gbc -{ -class Memory -{ -public: - using range_t = std::pair; - static constexpr range_t ProgramArea{0x0000, 0x7FFF}; - static constexpr range_t VideoRAM{0x8000, 0x9FFF}; - - static constexpr range_t BankRAM{0xA000, 0xBFFF}; - static constexpr range_t WorkRAM{0xC000, 0xDFFF}; - static constexpr range_t EchoRAM{0xE000, 0xFDFF}; // echo of work RAM - static constexpr range_t OAM_RAM{0xFE00, 0xFEFF}; - - static constexpr range_t IO_Ports{0xFF00, 0xFF7F}; - static constexpr range_t ZRAM{0xFF80, 0xFFFE}; - static constexpr uint16_t InterruptEn = 0xFFFF; - - Memory(Machine&, const std::string_view rom); - void reset(); - void set_wram_bank(uint8_t bank); - - uint8_t read8(uint16_t address); - void write8(uint16_t address, uint8_t value); - - uint16_t read16(uint16_t address); - void write16(uint16_t address, uint16_t value); - - uint8_t* oam_ram_ptr() noexcept { return m_state.oam_ram.data(); } - const uint8_t* oam_ram_ptr() const noexcept { return m_state.oam_ram.data(); } - uint8_t* video_ram_ptr() noexcept { return m_state.video_ram.data(); } - const uint8_t* video_ram_ptr() const noexcept { return m_state.video_ram.data(); } - - static constexpr uint16_t range_size(range_t range) { return range.second - range.first; } - - Machine& machine() const noexcept { return m_machine; } - Machine& machine() noexcept { return m_machine; } - bool rom_valid() const noexcept; - bool bootrom_enabled() const noexcept { return false; } - void disable_bootrom(); - - bool double_speed() const noexcept { return m_state.speed_factor != 1; } - int speed_factor() const noexcept { return m_state.speed_factor; } - void do_switch_speed(); - - // serialization - int restore_state(const std::vector&, int); - void serialize_state(std::vector&) const; - - // debugging - std::string explain(uint16_t address) const; - enum amode_t - { - READ, - WRITE - }; - using access_t = std::function; - void breakpoint(amode_t, access_t); - - inline static bool is_within(uint16_t addr, const range_t& range) - { - return addr >= range.first && addr <= range.second; - } - -private: - Machine& m_machine; - const std::string_view m_rom; - MBC m_mbc; - struct state_t - { - std::array video_ram = {}; - std::array oam_ram = {}; - std::array zram = {}; // high-speed RAM - bool bootrom_enabled = true; - int8_t speed_factor = 1; - } m_state; - bool m_is_busy = false; - std::vector m_read_breakpoints; - std::vector m_write_breakpoints; -}; - -inline void Memory::breakpoint(amode_t mode, access_t func) -{ - if (mode == READ) - m_read_breakpoints.push_back(func); - else if (mode == WRITE) - m_write_breakpoints.push_back(func); -} - -inline uint16_t Memory::read16(uint16_t address) -{ - return read8(address) | read8(address + 1) << 8; -} -inline void Memory::write16(uint16_t address, uint16_t value) -{ - write8(address + 0, value & 0xff); - write8(address + 1, value >> 8); -} - -} // namespace gbc diff --git a/components/libgbc/include/printers.hpp b/components/libgbc/include/printers.hpp deleted file mode 100644 index d68d2076..00000000 --- a/components/libgbc/include/printers.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once -#include - -namespace gbc -{ -static const char* cstr_reg(const uint8_t bf, bool sp) -{ - static const char* notsp[] = {"BC", "DE", "HL", "AF"}; - static const char* notaf[] = {"BC", "DE", "HL", "SP"}; - if (sp) return notaf[(bf >> 4) & 0x3]; - return notsp[(bf >> 4) & 0x3]; -} -static const char* cstr_dest(const uint8_t bf) -{ - static const char* dest[] = {"B", "C", "D", "E", "H", "L", "(HL)", "A"}; - return dest[bf & 0x7]; -} -static const char* cstr_flags(char buff[5], const uint8_t flags) -{ - buff[0] = (flags & MASK_ZERO) ? 'Z' : '_'; - buff[1] = (flags & MASK_NEGATIVE) ? 'N' : '_'; - buff[2] = (flags & MASK_HALFCARRY) ? 'H' : '_'; - buff[3] = (flags & MASK_CARRY) ? 'C' : '_'; - buff[4] = 0; - return buff; -} -static const char* cstr_cond(const uint8_t bf) -{ - static const char* s[] = {"not zero", "zero", "not carry", "carry"}; - return s[(bf >> 3) & 0x3]; -} -static const char* cstr_cond_en(const uint8_t bf, const uint8_t flags) -{ - const uint8_t f = (bf >> 3) & 0x3; - switch (f) - { - case 0: - return (flags & 0x80) == 0 ? "YES" : "NO"; - case 1: - return (flags & 0x80) ? "YES" : "NO"; - case 2: - return (flags & 0x10) == 0 ? "YES" : "NO"; - case 3: - return (flags & 0x10) ? "YES" : "NO"; - } - __builtin_unreachable(); -} -static void fill_flag_buffer(char* buffer, size_t len, const uint8_t opcode, const uint8_t flags) -{ - snprintf(buffer, len, "%s: %s", cstr_cond(opcode), cstr_cond_en(opcode, flags)); -} - -static const char* cstr_alu(const uint8_t bf) -{ - static const char* dest[] = {"ADD", "ADC", "SUB", "SBC", "AND", "XOR", "OR", "CP"}; - return dest[bf & 0x7]; -} -} // namespace gbc diff --git a/components/libgbc/include/registers.hpp b/components/libgbc/include/registers.hpp deleted file mode 100644 index 20927b8a..00000000 --- a/components/libgbc/include/registers.hpp +++ /dev/null @@ -1,199 +0,0 @@ -#pragma once -#include "common.hpp" -#include -#include - -namespace gbc -{ -enum flags_t -{ - MASK_ZERO = 0x80, - MASK_NEGATIVE = 0x40, - MASK_HALFCARRY = 0x20, - MASK_CARRY = 0x10, -}; - -struct regs_t -{ - union - { - struct - { - uint8_t flags; - uint8_t accum; - }; - uint16_t af; - }; - union - { - struct - { - uint8_t c; - uint8_t b; - }; - uint16_t bc; - }; - union - { - struct - { - uint8_t e; - uint8_t d; - }; - uint16_t de; - }; - union - { - struct - { - uint8_t l; - uint8_t h; - }; - uint16_t hl; - }; - - uint16_t sp; - uint16_t pc; - - inline uint16_t& getreg(const uint8_t bf, const bool use_sp) - { - switch (bf & 0x3) - { - case 0: - return bc; - case 1: - return de; - case 2: - return hl; - case 3: - return (use_sp) ? sp : af; - } - __builtin_unreachable(); - } - uint16_t& getreg_sp(const uint8_t opcode) { return getreg((opcode >> 4) & 0x3, true); } - uint16_t& getreg_af(const uint8_t opcode) { return getreg((opcode >> 4) & 0x3, false); } - - uint8_t& getdest(const uint8_t bf) - { - switch (bf & 0x7) - { - case 0: - return b; - case 1: - return c; - case 2: - return d; - case 3: - return e; - case 4: - return h; - case 5: - return l; - case 6: - throw MachineException("getdest: (HL) not accessible here"); - case 7: - return accum; - } - __builtin_unreachable(); - } - - bool compare_flags(const uint8_t opcode) noexcept - { - const uint8_t idx = (opcode >> 3) & 0x3; - if (idx == 0) return (flags & MASK_ZERO) == 0; // not zero - if (idx == 1) return (flags & MASK_ZERO); // zero - if (idx == 2) return (flags & MASK_CARRY) == 0; // not carry - if (idx == 3) return (flags & MASK_CARRY); // carry - __builtin_unreachable(); - } - - inline static bool half_carry(const uint8_t reg, const uint8_t val) - { - return ((reg & 0xf) + (val & 0xf)) & (0x10); - } - inline static bool half_borrow(const uint8_t reg, const uint8_t val) - { - return (reg & 0xf) < (val & 0xf); - } - - void alu(uint8_t op, uint8_t value) noexcept - { - auto& reg = this->accum; - switch (op & 0x7) - { - case 0x0: - { // ADD - const uint16_t calc = reg + value; - setflag(false, flags, MASK_NEGATIVE); - setflag(half_carry(reg, value), flags, MASK_HALFCARRY); - setflag(calc & 0x100, flags, MASK_CARRY); - reg += value; - setflag(reg == 0, flags, MASK_ZERO); - } - return; - case 0x1: - { // ADC - const int carry = (flags & MASK_CARRY) ? 1 : 0; - setflag(false, flags, MASK_NEGATIVE); - setflag((reg & 0xf) + (value & 0xf) + carry > 0xf, flags, MASK_HALFCARRY); // annoying! - setflag(((int) reg + value + carry) > 0xFF, flags, MASK_CARRY); - reg += value + carry; - setflag(reg == 0, flags, MASK_ZERO); - } - return; - case 0x2: // SUB - setflag(true, flags, MASK_NEGATIVE); - setflag(half_borrow(reg, value), flags, MASK_HALFCARRY); - setflag(reg < value, flags, MASK_CARRY); - setflag(reg == value, flags, MASK_ZERO); - reg -= value; - return; - case 0x3: - { // SBC - const int carry = (flags & MASK_CARRY) ? 1 : 0; - flags = MASK_NEGATIVE; - setflag(((reg & 0xf) - (value & 0xf) - carry) < 0, flags, MASK_HALFCARRY); - setflag(reg < value + carry, flags, MASK_CARRY); - reg -= value + carry; - setflag(reg == 0, flags, MASK_ZERO); - } - return; - case 0x4: // AND - reg &= value; - flags = MASK_HALFCARRY; - setflag(reg == 0, flags, MASK_ZERO); - return; - case 0x5: // XOR - reg ^= value; - flags = 0; - setflag(reg == 0, flags, MASK_ZERO); - return; - case 0x6: // OR - reg |= value; - flags = 0; - setflag(reg == 0, flags, MASK_ZERO); - return; - case 0x7: // CP - const uint8_t tmp = reg - value; - flags |= MASK_NEGATIVE; - setflag(tmp == 0, flags, MASK_ZERO); - setflag(reg < value, flags, MASK_CARRY); - setflag(half_borrow(reg, value), flags, MASK_HALFCARRY); - // printf("CP %02x vs %02x (F => %02x)\n", reg, value, flags); - return; - } - } // alu() - - std::string to_string() const - { - char buffer[512]; - int len = snprintf(buffer, sizeof(buffer), - "\tAF = %04X BC = %04X DE = %04X\n" - "\tHL = %04X SP = %04X PC = %04X\n", - af, bc, de, hl, sp, pc); - return std::string(buffer, len); - } -}; - -inline flags_t to_flag(const uint8_t opcode) { return (flags_t)(1 >> (4 + ((opcode >> 3) & 0x3))); } -} // namespace gbc diff --git a/components/libgbc/include/sprite.hpp b/components/libgbc/include/sprite.hpp deleted file mode 100644 index 0d79ff30..00000000 --- a/components/libgbc/include/sprite.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once -#include "memory.hpp" - -namespace gbc -{ -struct sprite_config_t -{ - const uint8_t* patterns; - uint8_t palette[2]; - int scan_x; - int scan_y; - int height; - bool is_cgb; - - void set_height(bool mode8x16) { height = mode8x16 ? 16 : 8; } -}; - -class Sprite -{ -public: - static const int SPRITE_W = 8; - - Sprite() = delete; - - bool hidden() const noexcept { return ypos == 0 || ypos >= 160 || xpos == 0 || xpos >= 168; } - uint8_t pattern_idx() const noexcept { return pattern; } - bool behind() const noexcept { return attr & 0x80; } - bool flipx() const noexcept { return attr & 0x20; } - bool flipy() const noexcept { return attr & 0x40; } - int pal() const noexcept { return (attr & 0x10) >> 4; } - int cgb_bank() const noexcept { return (attr & 0x8) >> 3; } - int cgb_pal() const noexcept { return attr & 0x7; } - - uint8_t pixel(const sprite_config_t&) const; - - int start_x() const noexcept { return xpos - 8; } - int start_y() const noexcept { return ypos - 16; } - - bool is_within_scanline(const sprite_config_t& config) const noexcept - { - return config.scan_y >= start_y() && config.scan_y < start_y() + config.height; - } - -private: - uint8_t ypos; - uint8_t xpos; - uint8_t pattern; - uint8_t attr; -}; - -inline uint8_t Sprite::pixel(const sprite_config_t& config) const -{ - int tx = config.scan_x - start_x(); - int ty = config.scan_y - start_y(); - if (tx < 0 || tx >= SPRITE_W) return 0; - if (this->flipx()) tx = SPRITE_W - 1 - tx; - if (this->flipy()) ty = config.height - 1 - ty; - - int offset = this->pattern * 16 + ty * 2; - if (config.is_cgb) offset += cgb_bank() * 0x2000; - uint8_t c0 = config.patterns[offset]; - uint8_t c1 = config.patterns[offset + 1]; - // return combined 4-bits, right to left - const int bit = 7 - tx; - const int v0 = (c0 >> bit) & 0x1; - const int v1 = (c1 >> bit) & 0x1; - return v0 | (v1 << 1); -} -} // namespace gbc diff --git a/components/libgbc/include/tiledata.hpp b/components/libgbc/include/tiledata.hpp deleted file mode 100644 index ddb1f9f5..00000000 --- a/components/libgbc/include/tiledata.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once -#include "memory.hpp" - -namespace gbc -{ -struct tileconf_t -{ - const bool is_cgb; - const uint8_t dmg_pal; -}; - -class TileData -{ -public: - static const int TILE_W = 8; - static const int TILE_H = 8; - - TileData(const uint8_t* tile, const uint8_t* pattern, const uint8_t* attr, bool sign) - : m_tile_base(tile), m_patt_base(pattern), m_attr_base(attr), m_signed(sign) - {} - - int tile_attr(int tx, int ty); - int tile_id(int tx, int ty); - int pattern(int t, int tattr, int dx, int dy) const; - int pattern(const uint8_t* base, int tattr, int t, int dx, int dy) const; - void set_tilebase(const uint8_t* new_base) { m_tile_base = new_base; } - -private: - const uint8_t* m_tile_base; - const uint8_t* m_patt_base; - const uint8_t* m_attr_base; - const bool m_signed; -}; - -inline int TileData::tile_id(int x, int y) -{ - if (this->m_signed) return 128 + (int8_t) m_tile_base[y * 32 + x]; - return m_tile_base[y * 32 + x]; -} -inline int TileData::tile_attr(int x, int y) -{ - if (m_attr_base == nullptr) return 0; - return m_attr_base[y * 32 + x]; -} - -inline int TileData::pattern(const uint8_t* base, int tid, int tattr, int tx, int ty) const -{ - if (tattr & 0x20) tx = 7 - tx; - if (tattr & 0x40) ty = 7 - ty; - if (tattr & 0x08) base += 0x2000; - const int offset = 16 * tid + ty * 2; - // get 16-bit c0, c1 - uint8_t c0 = base[offset]; - uint8_t c1 = base[offset + 1]; - // return combined 4-bits, right to left - const int bit = 7 - tx; - const int v0 = (c0 >> bit) & 0x1; - const int v1 = (c1 >> bit) & 0x1; - return v0 | (v1 << 1); -} // pattern(...) -inline int TileData::pattern(int tid, int tattr, int tx, int ty) const -{ - return pattern(m_patt_base, tid, tattr, tx, ty); -} -} // namespace gbc diff --git a/components/libgbc/include/tracing.hpp b/components/libgbc/include/tracing.hpp deleted file mode 100644 index 90d065ef..00000000 --- a/components/libgbc/include/tracing.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include -#include - -namespace gbc -{ -class CPU; - -struct breakpoint_t -{ - std::function callback; -}; - -} // namespace gbc diff --git a/components/libgbc/src/apu.cpp b/components/libgbc/src/apu.cpp deleted file mode 100644 index 4fff6e45..00000000 --- a/components/libgbc/src/apu.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "apu.hpp" -#include "generators.hpp" -#include "io.hpp" -#include "machine.hpp" - -namespace gbc -{ -APU::APU(Machine& mach) : m_machine{mach} {} - -void APU::simulate() -{ - // if sound is off, don't do anything - if ((machine().io.reg(IO::REG_NR52) & 0x80) == 0) return; - - // TODO: writeme -} - -uint8_t APU::read(const uint16_t addr, uint8_t& reg) -{ - switch (addr) - { - case IO::REG_NR52: - return reg; - } - printf("ERROR: Unhandled APU read at %04X (reg %02X)\n", addr, reg); - GBC_ASSERT(0 && "Unhandled APU read"); -} -void APU::write(const uint16_t addr, const uint8_t value, uint8_t& reg) -{ - switch (addr) - { - case IO::REG_NR52: - // TODO: writing bit7 should clear all sound registers - // printf("NR52 Sound ON/OFF 0x%04x write 0x%02x\n", addr, value); - reg &= 0xF; - reg |= value & 0x80; - // GBC_ASSERT(0 && "NR52 Sound ON/OFF register write"); - return; - } - printf("ERROR: Unhandled APU write at %04X val=%02X (reg %02X)\n", addr, value, reg); - GBC_ASSERT(0 && "Unhandled APU write"); -} - -// serialization -int APU::restore_state(const std::vector& data, int off) -{ - this->m_state = *(state_t*) &data.at(off); - return sizeof(m_state); -} -void APU::serialize_state(std::vector& res) const -{ - res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); -} -} // namespace gbc diff --git a/components/libgbc/src/cpu.cpp b/components/libgbc/src/cpu.cpp deleted file mode 100644 index a73d839c..00000000 --- a/components/libgbc/src/cpu.cpp +++ /dev/null @@ -1,621 +0,0 @@ -#include "cpu.hpp" - -#include "instructions.cpp" -#include "machine.hpp" -#include - -namespace gbc -{ -CPU::CPU(Machine& mach) noexcept : m_machine(mach), m_memory(mach.memory) {} - -void CPU::reset() noexcept -{ - if (!machine().is_cgb()) - { - // gameboy DMG initial register values - registers().af = 0x01b0; - registers().bc = 0x0013; - registers().de = 0x00d8; - registers().hl = 0x014d; - } - else - { - // gameboy color initial register values - registers().af = 0x1180; - registers().bc = 0x0000; - registers().de = 0xff56; - registers().hl = 0x000d; - } - registers().sp = 0xfffe; - registers().pc = memory().bootrom_enabled() ? 0x0 : 0x100; - this->m_state.cycles_total = 0; -} - -void CPU::simulate() -{ - // breakpoint handling - if (UNLIKELY(this->break_time() || !this->m_breakpoints.empty())) - { - this->break_checks(); - // user can quit during break - if (!machine().is_running()) return; - } - // handle interrupts - this->handle_interrupts(); - - if (!this->is_halting() && !this->is_stopping()) { this->execute(); } - else - { - // make sure time passes when not executing instructions - this->hardware_tick(); - // speed switch - this->handle_speed_switch(); - } -} - -void CPU::execute() -{ - // 1. read instruction from memory - const uint8_t opcode = this->peekop8(0); - // 2. decode into executable instruction - auto& instr = decode(opcode); - - // 2a. print the instruction (when enabled) - if (UNLIKELY(machine().verbose_instructions)) - { - char prn[128]; - instr.printer(prn, sizeof(prn), *this, opcode); - printf("%9llu: [pc %04X] opcode %02X: %s\n", gettime(), registers().pc, opcode, prn); - } - - // 3. increment PC, hardware tick - registers().pc++; - this->hardware_tick(); - - // 4. run instruction handler - instr.handler(*this, opcode); - - if (UNLIKELY(machine().verbose_instructions)) - { - // print out the resulting flags reg - if (m_state.last_flags != registers().flags) - { - m_state.last_flags = registers().flags; - char fbuf[5]; - printf("* Flags changed: [%s]\n", cstr_flags(fbuf, registers().flags)); - } - } - if (UNLIKELY(memory().is_within(registers().pc, Memory::VideoRAM))) - { - fprintf(stderr, "ERROR: PC is in the Video RAM area: %04X\n", registers().pc); - this->break_now(); - } - if (UNLIKELY(memory().is_within(registers().pc, Memory::EchoRAM))) - { - fprintf(stderr, "WARN: PC is in the Echo RAM area: %04X\n", registers().pc); - this->break_now(); - } - if (UNLIKELY(memory().is_within(registers().pc, Memory::OAM_RAM))) - { - fprintf(stderr, "ERROR: PC is in the OAM RAM area: %04X\n", registers().pc); - this->break_now(); - } - if (UNLIKELY(memory().is_within(registers().pc, Memory::IO_Ports))) - { - fprintf(stderr, "ERROR: PC is in the I/O port area: %04X\n", registers().pc); - this->break_now(); - } -} - -void CPU::hardware_tick() -{ - this->incr_cycles(4); - machine().gpu.simulate(); - machine().io.simulate(); - machine().apu.simulate(); -} - -// it takes 2 instruction-cycles to toggle interrupts -void CPU::enable_interrupts() noexcept -{ - if (this->m_state.intr_pending <= 0) { this->m_state.intr_pending = 2; } -} -void CPU::disable_interrupts() noexcept { this->m_state.intr_pending = -2; } - -void CPU::handle_interrupts() -{ - // enable/disable interrupts over cycles - if (UNLIKELY(m_state.intr_pending != 0)) - { - if (m_state.intr_pending > 0) - { - m_state.intr_pending--; - if (!m_state.intr_pending) this->m_state.ime = true; - } - else if (m_state.intr_pending < 0) - { - m_state.intr_pending++; - if (!m_state.intr_pending) this->m_state.ime = false; - } - } - // check if interrupts are enabled and pending - const uint8_t imask = machine().io.interrupt_mask(); - if (UNLIKELY(this->ime() && imask != 0x0)) - { - // disable interrupts immediately - this->m_state.ime = false; - this->m_state.asleep = false; - // execute pending interrupts (sorted by priority) - auto& io = machine().io; - if (imask & 0x1) - this->interrupt(io.vblank); - else if (imask & 0x2) - this->interrupt(io.lcd_stat); - else if (imask & 0x4) - this->interrupt(io.timerint); - else if (imask & 0x8) - this->interrupt(io.serialint); - else if (imask & 0x10) - this->interrupt(io.joypadint); - } - else if (UNLIKELY(this->m_state.haltbug && imask != 0)) - { - // do *NOT* call interrupt handler when buggy HALTing - this->m_state.asleep = false; - this->m_state.haltbug = false; - } -} -void CPU::interrupt(interrupt_t& intr) -{ - if (UNLIKELY(machine().verbose_interrupts)) - { printf("%9llu: Executing interrupt %s (%#x)\n", this->gettime(), intr.name, intr.mask); } - // disable interrupt request - machine().io.reg(IO::REG_IF) &= ~intr.mask; - // set interrupt bit - // this->m_state.reg_ie |= intr.mask; - this->hardware_tick(); - this->hardware_tick(); - // push PC and jump to INTR addr - this->push_and_jump(intr.fixed_address); - // sometimes we want to break on interrupts - if (UNLIKELY(machine().break_on_interrupts && !machine().is_breaking())) - { machine().break_now(); } - if (intr.callback) intr.callback(machine(), intr); -} - -instruction_t& CPU::decode(const uint8_t opcode) -{ - switch (opcode) - { - case 0x00: // NOP - return instr_NOP; - case 0x08: // LD SP, imm16 - return instr_LD_N_SP; - case 0x10: // STOP - return instr_STOP; - case 0x76: // HALT - return instr_HALT; - // LD D, imm8 - case 0x06: - case 0x16: - case 0x26: - case 0x36: - case 0x0E: - case 0x1E: - case 0x2E: - case 0x3E: - return instr_LD_D_N; - // LD B, D - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - // LD C, D - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - // LD D, D - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - // LD E, D - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5C: - case 0x5D: - case 0x5E: - case 0x5F: - // LD H, D - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - // LD L, D - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - // LD (HL), D - // NOTE: 0x76: is HALT and *not* LD (HL), (HL) - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x77: - // LD A, D - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: - return instr_LD_D_D; - // LD A, R - case 0x02: - case 0x12: - case 0x0A: - case 0x1A: - return instr_LD_R_A_R; - case 0xEA: // LD (imm16), A - case 0xFA: // LD A, (imm16) - return instr_LD_N_A_N; - case 0x22: // LDI (HL), A - case 0x32: // LDD (HL), A - case 0x2A: // LDI A, (HL) - case 0x3A: // LDD A, (HL) - return instr_LDID_HL_A; - case 0xE2: // LD (FF00+C), A - case 0xF2: // LD A, (FF00+C) - case 0xE0: // LD (FF00+imm8), A - case 0xF0: // LD A, (FF00+imm8) - return instr_LD_FF00_A; - // LD R, imm16 - case 0x01: - case 0x11: - case 0x21: - case 0x31: - return instr_LD_R_N; - case 0xE8: - return instr_ADD_SP_N; - case 0xF8: // LD HL, SP+imm8 - case 0xF9: // LD SP, HL - return instr_LD_HL_SP; - // POP R - case 0xC1: - case 0xD1: - case 0xE1: - case 0xF1: - // PUSH R - case 0xC5: - case 0xD5: - case 0xE5: - case 0xF5: - return instr_PUSH_POP; - // ALU operations - // ADD A, D - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - return instr_ALU_A_D; - // ADC A, D - case 0x88: - case 0x89: - case 0x8A: - case 0x8B: - case 0x8C: - case 0x8D: - case 0x8E: - case 0x8F: - return instr_ALU_A_D; - // SUB A, D - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: - return instr_ALU_A_D; - // SBC A, D - case 0x98: - case 0x99: - case 0x9A: - case 0x9B: - case 0x9C: - case 0x9D: - case 0x9E: - case 0x9F: - return instr_ALU_A_D; - // AND A, D - case 0xA0: - case 0xA1: - case 0xA2: - case 0xA3: - case 0xA4: - case 0xA5: - case 0xA6: - case 0xA7: - return instr_ALU_A_D; - // XOR A, D - case 0xA8: - case 0xA9: - case 0xAA: - case 0xAB: - case 0xAC: - case 0xAD: - case 0xAE: - case 0xAF: - return instr_ALU_A_D; - // OR A, D - case 0xB0: - case 0xB1: - case 0xB2: - case 0xB3: - case 0xB4: - case 0xB5: - case 0xB6: - case 0xB7: - return instr_ALU_A_D; - // CP A, D - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBC: - case 0xBD: - case 0xBE: - case 0xBF: - return instr_ALU_A_D; - // ALU OP A, im8 - case 0xC6: - case 0xCE: - case 0xD6: - case 0xDE: - case 0xE6: - case 0xEE: - case 0xF6: - case 0xFE: - return instr_ALU_A_N; - // INC R, DEC R - case 0x03: - case 0x0B: - case 0x13: - case 0x1B: - case 0x23: - case 0x2B: - case 0x33: - case 0x3B: - return instr_INC_DEC_R; - // INC D, DEC D - case 0x04: - case 0x05: - case 0x0C: - case 0x0D: - case 0x14: - case 0x15: - case 0x1C: - case 0x1D: - case 0x24: - case 0x25: - case 0x2C: - case 0x2D: - case 0x34: - case 0x35: - case 0x3C: - case 0x3D: - return instr_INC_DEC_D; - // ADD HL, R - case 0x09: - case 0x19: - case 0x29: - case 0x39: - return instr_ADD_HL_R; - case 0x27: // DA A - return instr_DAA; - case 0x2F: // CPL A - return instr_CPL_A; - case 0x37: // SCF - case 0x3F: // CCF - return instr_SCF_CCF; - case 0x07: // RLC A - case 0x17: // RL A - case 0x0F: // RRC A - case 0x1F: // RR A - return instr_RLC_RRC; - case 0xC3: // JP imm16 - case 0xC2: // JP nz, imm16 - case 0xCA: // JP z, imm16 - case 0xD2: // JP nc, imm16 - case 0xDA: // JP c, imm16 - return instr_JP; - case 0xE9: // JP HL - return instr_JP_HL; - case 0x18: // JR imm8 - case 0x20: // JR nz, imm8 - case 0x28: // JR z, imm8 - case 0x30: // JR nc, imm8 - case 0x38: // JR c, imm8 - return instr_JR_N; - case 0xCD: // CALL imm16 - case 0xC4: // CALL nz, imm16 - case 0xCC: // CALL z, imm16 - case 0xD4: // CALL nc, imm16 - case 0xDC: // CALL c, imm16 - return instr_CALL; - case 0xC9: // RET - case 0xC0: // RET nz - case 0xC8: // RET z - case 0xD0: // RET nc - case 0xD8: // RET c - return instr_RET; - case 0xD9: // RETI - return instr_RETI; - // RST 0x0, 0x08, 0x10, 0x18 - case 0xC7: - case 0xCF: - case 0xD7: - case 0xDF: - // RST 0x20, 0x028, 0x30, 0x38 - case 0xE7: - case 0xEF: - case 0xF7: - case 0xFF: - return instr_RST; - case 0xF3: // DI - case 0xFB: // EI - return instr_DI_EI; - case 0xCB: - return instr_CB_EXT; - } - return instr_MISSING; -} - -uint8_t CPU::peekop8(int disp) { return memory().read8(registers().pc + disp); } -uint16_t CPU::peekop16(int disp) { return memory().read16(registers().pc + disp); } -uint8_t CPU::readop8() -{ - const uint8_t operand = peekop8(0); - registers().pc++; - hardware_tick(); - return operand; -} -uint16_t CPU::readop16() -{ - const uint16_t operand = peekop16(0); - registers().pc += 2; - hardware_tick(); - hardware_tick(); - return operand; -} - -uint8_t CPU::read_hl() { return this->mtread8(registers().hl); } -void CPU::write_hl(const uint8_t value) { this->mtwrite8(registers().hl, value); } -uint8_t CPU::mtread8(uint16_t addr) -{ - const uint8_t value = memory().read8(addr); - this->hardware_tick(); - return value; -} -void CPU::mtwrite8(uint16_t addr, uint8_t value) -{ - memory().write8(addr, value); - this->hardware_tick(); -} -uint16_t CPU::mtread16(uint16_t addr) -{ - const uint16_t value = memory().read16(addr); - this->hardware_tick(); - this->hardware_tick(); - return value; -} -void CPU::mtwrite16(uint16_t addr, uint16_t value) -{ - memory().write16(addr, value); - this->hardware_tick(); - this->hardware_tick(); -} - -void CPU::incr_cycles(int count) -{ - assert(count >= 0); - this->m_state.cycles_total += count; -} - -void CPU::stop() -{ - this->m_state.stopped = true; - // preparing a speed switch? - if (machine().io.reg(IO::REG_KEY1) & 0x1) { this->m_state.switch_cycles = 4; } - // disable screen etc. - machine().io.perform_stop(); -} -void CPU::handle_speed_switch() -{ - if (UNLIKELY(this->m_state.switch_cycles > 0)) - { - this->m_state.switch_cycles--; - if (this->m_state.switch_cycles == 0) - { - // stop the stopping - this->m_state.stopped = false; - // change speed - memory().do_switch_speed(); - // this can turn the LCD back on - machine().io.deactivate_stop(); - } - } -} - -void CPU::wait() -{ - this->m_state.asleep = true; - this->m_state.haltbug = false; -} -void CPU::buggy_halt() -{ - this->m_state.asleep = true; - this->m_state.haltbug = true; -} - -void CPU::jump(const uint16_t dest) -{ - if (UNLIKELY(machine().verbose_instructions)) - { printf("* Jumped to %04X (from %04X)\n", dest, registers().pc); } - this->registers().pc = dest; -} -void CPU::push_value(uint16_t address) -{ - this->hardware_tick(); - registers().sp -= 2; - this->mtwrite16(registers().sp, address); -} -void CPU::push_and_jump(uint16_t address) -{ - this->push_value(registers().pc); - this->jump(address); -} - -int CPU::restore_state(const std::vector& data, int off) -{ - this->m_state = *(state_t*) &data.at(off); - return sizeof(m_state); -} -void CPU::serialize_state(std::vector& res) const -{ - res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); -} -} // namespace gbc diff --git a/components/libgbc/src/debug.cpp b/components/libgbc/src/debug.cpp deleted file mode 100644 index aeb68f82..00000000 --- a/components/libgbc/src/debug.cpp +++ /dev/null @@ -1,319 +0,0 @@ -#include "cpu.hpp" -#include "machine.hpp" -#include -#include - -namespace gbc -{ -static inline std::vector split(const std::string& txt, char ch) -{ - size_t pos = txt.find(ch); - size_t initialPos = 0; - std::vector strs; - - while (pos != std::string::npos) - { - strs.push_back(txt.substr(initialPos, pos - initialPos)); - initialPos = pos + 1; - - pos = txt.find(ch, initialPos); - } - - // Add the last one - strs.push_back(txt.substr(initialPos, std::min(pos, txt.size()) - initialPos + 1)); - return strs; -} - -static void print_help() -{ - const char* help_text = R"V0G0N( - usage: command [options] - commands: - ?, help Show this informational text - c, continue Continue execution, disable stepping - s, step [steps=1] Run [steps] instructions, then break - v, verbose Toggle verbose instruction execution - b, break [addr] Breakpoint on executing [addr] - rb [addr] Breakpoint on reading from [addr] - wb [addr] Breakpoint on writing to [addr] - clear Clear all breakpoints - reset Reset the machine - read [addr] (len=1) Read from [addr] (len) bytes and print - write [addr] [value] Write [value] to memory location [addr] - readv0 [addr] Print byte from VRAM0:[addr] - readv1 [addr] Print byte from VRAM1:[addr] - debug Trigger the debug interrupt handler - vblank Render current screen and call vblank - frame Show frame number and extra frame info -)V0G0N"; - printf("%s\n", help_text); -} - -static bool execute_commands(CPU& cpu) -{ - printf("Enter = cont, help, quit: "); - std::string text; - while (true) - { - const int c = getchar(); // press any key - if (c == '\n' || c < 0) - break; - else - text.append(1, (char) c); - } - if (text.empty()) return false; - std::vector params = split(text, ' '); - const auto& cmd = params[0]; - - // continue - if (cmd == "c" || cmd == "continue") - { - cpu.break_on_steps(0); - return false; - } - // stepping - if (cmd == "") { return false; } - else if (cmd == "s" || cmd == "step") - { - cpu.machine().verbose_instructions = true; // ??? - int steps = 1; - if (params.size() > 1) steps = std::stoi(params[1]); - printf("Pressing Enter will now execute %d steps\n", steps); - cpu.break_on_steps(steps); - return false; - } - // breaking - else if (cmd == "b" || cmd == "break") - { - if (params.size() < 2) - { - printf(">>> Not enough parameters: break [addr]\n"); - return true; - } - unsigned long hex = std::strtoul(params[1].c_str(), 0, 16); - cpu.default_pausepoint(hex & 0xFFFF); - return true; - } - else if (cmd == "clear") - { - cpu.breakpoints().clear(); - return true; - } - else if (cmd == "rb" || cmd == "wb") - { - const auto mode = (cmd == "rb") ? Memory::READ : Memory::WRITE; - if (params.size() < 2) - { - printf(">>> Not enough parameters: rb/wb [addr]\n"); - return true; - } - uint16_t traploc = std::strtoul(params[1].c_str(), 0, 16) & 0xFFFF; - printf("Breaking after any %s %04X (%s)\n", (mode) ? "write to" : "read from", traploc, - cpu.memory().explain(traploc).c_str()); - cpu.memory().breakpoint(mode, [traploc, mode](Memory& mem, uint16_t addr, uint8_t value) { - if (addr == traploc) - { - if (mode == Memory::READ) - { - printf("Breaking after read from %04X (%s) with value %02X\n", addr, - mem.explain(addr).c_str(), mem.read8(addr)); - } - else - { // WRITE - printf("Breaking after write to %04X (%s) with value %02X (old: %02X)\n", addr, - mem.explain(addr).c_str(), value, mem.read8(addr)); - } - mem.machine().break_now(); - } - }); - return true; - } - // verbose instructions - else if (cmd == "v" || cmd == "verbose") - { - bool& v = cpu.machine().verbose_instructions; - v = !v; - printf("Verbose instructions are now %s\n", v ? "ON" : "OFF"); - return true; - } - else if (cmd == "r" || cmd == "run") - { - cpu.machine().verbose_instructions = false; - cpu.break_on_steps(0); - return false; - } - else if (cmd == "q" || cmd == "quit" || cmd == "exit") - { - cpu.machine().stop(); - return false; - } - else if (cmd == "reset") - { - cpu.machine().reset(); - cpu.break_now(); - return false; - } - // read 0xAddr size - else if (cmd == "ld" || cmd == "read") - { - if (params.size() < 2) - { - printf(">>> Not enough parameters: read [addr] (length=1)\n"); - return true; - } - unsigned long hex = std::strtoul(params[1].c_str(), 0, 16); - int bytes = 1; - if (params.size() > 2) bytes = std::stoi(params[2]); - int col = 0; - for (int i = 0; i < bytes; i++) - { - if (col == 0) printf("0x%04lx: ", hex + i); - printf("0x%02x ", cpu.memory().read8(hex + i)); - if (++col == 4) - { - printf("\n"); - col = 0; - } - } - if (col) printf("\n"); - return true; - } - // write 0xAddr value - else if (cmd == "write") - { - if (params.size() < 3) - { - printf(">>> Not enough parameters: write [addr] [value]\n"); - return true; - } - unsigned long hex = std::strtoul(params[1].c_str(), 0, 16); - int value = std::stoi(params[2]) & 0xff; - printf("0x%04lx -> 0x%02x\n", hex, value); - cpu.memory().write8(hex, value); - return true; - } - // read from VRAM bank 1 - else if (cmd == "readv0" || cmd == "readv1") - { - const uint16_t off = (cmd == "readv0") ? 0x0 : 0x2000; - if (params.size() < 2) - { - printf(">>> Not enough parameters: readv1 [addr]\n"); - return true; - } - unsigned long hex = std::strtoul(params[1].c_str(), 0, 16); - hex &= 0x1FFF; - printf("VRAM1:%04lX -> %02X\n", hex, cpu.memory().video_ram_ptr()[off + hex]); - return true; - } - else if (cmd == "vblank" || cmd == "vbl") - { - cpu.machine().gpu.render_frame(); - // call vblank handler directly - auto& vblank = cpu.machine().io.vblank; - if (vblank.callback) - vblank.callback(cpu.machine(), vblank); - else - printf(">>> V-blank callback was not set, and could not be called\n"); - return true; - } - else if (cmd == "frame" || cmd == "fr") - { - printf("Frame: %llu\n", cpu.machine().gpu.frame_count()); - return true; - } - else if (cmd == "debug") - { - auto& io = cpu.machine().io; - io.debugint.callback(cpu.machine(), io.debugint); - return true; - } - else if (cmd == "help" || cmd == "?") - { - print_help(); - return true; - } - else - { - printf(">>> Unknown command: '%s'\n", cmd.c_str()); - print_help(); - return true; - } - return false; -} - -void CPU::print_and_pause(CPU& cpu, const uint8_t opcode) -{ - char buffer[512]; - cpu.decode(opcode).printer(buffer, sizeof(buffer), cpu, opcode); - printf("\n"); - printf(">>> Breakpoint at [pc %04X] opcode %02X: %s\n", cpu.registers().pc, opcode, buffer); - // CPU registers - printf("%s", cpu.registers().to_string().c_str()); - // I/O interrupt registers - auto& io = cpu.machine().io; - printf("\tIF %02X IE %02X IME %X LY %u LCDC 0x%X\n", io.read_io(IO::REG_IF), - io.read_io(IO::REG_IE), cpu.ime(), io.read_io(IO::REG_LY), io.read_io(IO::REG_LCDC)); - try - { - auto& mem = cpu.memory(); - printf("\t(HL) = %02X (SP) = %04X\n", mem.read16(cpu.registers().hl), - mem.read16(cpu.registers().sp)); - } catch (...) - { - printf("\tUnable to read from (HL) or (SP)\n"); - } - while (execute_commands(cpu)) - ; -} // print_and_pause(...) - -bool CPU::break_time() const -{ - if (UNLIKELY(this->m_break)) return true; - if (UNLIKELY(m_break_steps_cnt != 0)) - { - m_break_steps--; - if (m_break_steps <= 0) - { - m_break_steps = m_break_steps_cnt; - return true; - } - } - return false; -} - -void CPU::break_on_steps(int steps) -{ - assert(steps >= 0); - this->m_break_steps_cnt = steps; - this->m_break_steps = steps; -} - -void CPU::break_checks() -{ - if (this->break_time()) - { - this->m_break = false; - // pause for each instruction - this->print_and_pause(*this, this->peekop8(0)); - } - if (!m_breakpoints.empty()) - { - // look for breakpoints - auto it = m_breakpoints.find(registers().pc); - if (it != m_breakpoints.end()) - { - auto& bp = it->second; - bp.callback(*this, this->peekop8(0)); - } - } -} - -void assert_failed(const int expr, const char* strexpr, - const char* filename, const int line) -{ - fprintf(stderr, "Assertion failed in %s:%d: %s\n", - filename, line, strexpr); - abort(); -} -} // namespace gbc diff --git a/components/libgbc/src/gpu.cpp b/components/libgbc/src/gpu.cpp deleted file mode 100644 index 23a2dc52..00000000 --- a/components/libgbc/src/gpu.cpp +++ /dev/null @@ -1,428 +0,0 @@ -#include "gpu.hpp" - -#include "machine.hpp" -#include "sprite.hpp" -#include "tiledata.hpp" -#include -#include - -namespace gbc -{ -const int GPU::WHITE_IDX; -GPU::GPU(Machine& mach) noexcept - : m_memory(mach.memory) - , m_io(mach.io) - , m_reg_lcdc{io().reg(IO::REG_LCDC)} - , m_reg_stat{io().reg(IO::REG_STAT)} - , m_reg_ly{io().reg(IO::REG_LY)} -{ - this->reset(); -} - -void GPU::reset() noexcept -{ - m_pixels.resize(SCREEN_W * SCREEN_H); - this->m_state.video_offset = 0; - // set_mode((m_reg_ly >= 144) ? 1 : 2); -} -uint64_t GPU::scanline_cycles() const noexcept -{ - return oam_cycles() + vram_cycles() + hblank_cycles(); -} -uint64_t GPU::oam_cycles() const noexcept -{ - return 83 * memory().speed_factor(); - // return memory().speed_factor() * 80; -} -uint64_t GPU::vram_cycles() const noexcept -{ - return 175 * memory().speed_factor(); - // return memory().speed_factor() * 172; -} -uint64_t GPU::hblank_cycles() const noexcept -{ - return 207 * memory().speed_factor(); - // return memory().speed_factor() * 204; -} -void GPU::simulate() -{ - // nothing to do with LCD being off - if (!this->lcd_enabled()) { return; } - - auto& vblank = io().vblank; - auto& lcd_stat = io().lcd_stat; - - this->m_state.period += 4; - const uint64_t period = this->m_state.period; - // assert(period == 4); - const bool new_scanline = period >= scanline_cycles(); - - // scanline logic when screen on - if (UNLIKELY(new_scanline)) - { - this->m_state.period = 0; // start over each scanline - // scanline LY increment logic - static const int MAX_LINES = 154; - m_state.current_scanline = (m_state.current_scanline + 1) % MAX_LINES; - m_reg_ly = m_state.current_scanline; - - if (UNLIKELY(m_reg_ly == 144)) - { - if (this->m_state.white_frame) - { - this->m_state.white_frame = false; - // create white palette value at color 32 - if (this->m_on_palchange) { this->m_on_palchange(WHITE_IDX, 0xFFFF); } - if (LIKELY(this->m_render)) - { - // clear pixelbuffer with white - std::fill_n(m_pixels.begin(), m_pixels.size(), WHITE_IDX); - } - } - // enable MODE 1: V-blank - set_mode(1); - // MODE 1: vblank interrupt - io().trigger(vblank); - // modify stat - this->set_mode(1); - // if STAT vblank interrupt is enabled - if (m_reg_stat & 0x10) io().trigger(lcd_stat); - } - else if (m_reg_ly == 1 && get_mode() == 1) - { - assert(this->is_vblank()); - // start over in regular mode - this->m_state.current_scanline = 0; - this->m_reg_ly = 0; - set_mode(0); - // new frame - m_state.frame_count++; - } - // LY == LYC comparison on each line - this->do_ly_comparison(); - } - // STAT mode & scanline period modulation - if (!this->is_vblank()) - { - if (get_mode() == 0 && new_scanline) - { - // enable MODE 2: OAM search - set_mode(2); - // check if OAM interrupt enabled - if (m_reg_stat & 0x20) io().trigger(lcd_stat); - } - else if (get_mode() == 2 && period >= oam_cycles()) - { - // enable MODE 3: Scanline VRAM - set_mode(3); - - // render a scanline (if rendering enabled) - if (LIKELY(!this->m_state.white_frame && this->m_render)) - { this->render_scanline(m_state.current_scanline); } - // TODO: perform HDMA transfers here! - } - else if (get_mode() == 3 && period >= oam_cycles() + vram_cycles()) - { - // enable MODE 0: H-blank - if (m_reg_stat & 0x8) io().trigger(lcd_stat); - set_mode(0); - } - // printf("Current mode: %u -> %u period %lu\n", - // current_mode(), m_reg_stat & 0x3, period); - } -} - -bool GPU::is_vblank() const noexcept { return get_mode() == 1; } -bool GPU::is_hblank() const noexcept { return get_mode() == 0; } -uint8_t GPU::get_mode() const noexcept { return m_reg_stat & 0x3; } -void GPU::set_mode(uint8_t mode) -{ - this->m_reg_stat &= 0xfc; - this->m_reg_stat |= mode & 0x3; -} - -void GPU::do_ly_comparison() -{ - const bool equal = m_reg_ly == io().reg(IO::REG_LYC); - // STAT coincidence bit - setflag(equal, m_reg_stat, 0x4); - // STAT interrupt (if enabled) when LY == LYC - if (equal && (m_reg_stat & 0x40)) io().trigger(io().lcd_stat); -} - -void GPU::render_frame() -{ - if (!m_state.white_frame && lcd_enabled()) - { - // render each scanline - for (int y = 0; y < SCREEN_H; y++) { this->render_scanline(y); } - } - else - { - // clear pixelbuffer with white - std::fill_n(m_pixels.begin(), m_pixels.size(), WHITE_IDX); - } -} - -void GPU::render_scanline(int scan_y) -{ - const uint8_t scroll_y = memory().read8(IO::REG_SCY); - const uint8_t scroll_x = memory().read8(IO::REG_SCX); - const int sy = (scan_y + scroll_y) % 256; - - // create tiledata object from LCDC register - auto td = this->create_tiledata(bg_tiles(), tile_data()); - // window visibility - const bool window = this->window_visible() && scan_y >= window_y(); - auto wtd = this->create_tiledata(window_tiles(), tile_data()); - - // create sprite configuration structure - auto sprconf = this->sprite_config(); - sprconf.scan_y = scan_y; - // create list of sprites that are on this scanline - auto sprites = this->find_sprites(sprconf); - - // tile configuration - tileconf_t tileconf = this->tile_config(); - - // render whole scanline - for (int scan_x = 0; scan_x < SCREEN_W; scan_x++) - { - const int sx = (scan_x + scroll_x) % 256; - // get the tile id and attribute - const int tid = td.tile_id(sx / 8, sy / 8); - const int tattr = td.tile_attr(sx / 8, sy / 8); - // copy the 16-byte tile into buffer - const int tile_color = td.pattern(tid, tattr, sx & 7, sy & 7); - uint16_t color15 = this->colorize_tile(tileconf, tattr, tile_color); - - if ((tattr & 0x80) == 0 || !machine().is_cgb()) - { - // window on can be under sprites - if (window && scan_x >= window_x() - 7) - { - const int wpx = scan_x - window_x() + 7; - const int wpy = scan_y - window_y(); - // draw window pixel - const int wtile = wtd.tile_id(wpx / 8, wpy / 8); - const int wattr = wtd.tile_attr(wpx / 8, wpy / 8); - const int widx = wtd.pattern(wtile, wattr, wpx & 7, wpy & 7); - color15 = this->colorize_tile(tileconf, wattr, widx); - } - - // render sprites within this x - sprconf.scan_x = scan_x; - for (const auto* sprite : sprites) - { - const uint8_t idx = sprite->pixel(sprconf); - if (idx != 0) - { - if (!sprite->behind() || tile_color == 0) { - color15 = this->colorize_sprite(sprite, sprconf, idx); - } - } - } - } // BG priority -#ifndef GAMEBRO_INDEXED_FRAME - // Convert to 15-bit RGB - color15 = getpal(color15 * 2) | (getpal(color15 * 2 + 1) << 8); - uint8_t r = ((color15) >> 0 & 0x1f) << 3; - uint8_t g = ((color15) >> 5 & 0x1f) << 3; - uint8_t b = ((color15) >> 10 & 0x1f) << 3; - color15 = make_color(r,g,b); -#endif - m_pixels.at(scan_y * SCREEN_W + scan_x) = color15; - } // x -} // render_to(...) - -uint16_t GPU::colorize_tile(const tileconf_t& conf, const uint8_t attr, const uint8_t idx) -{ - uint16_t index = 0; - if (conf.is_cgb) - { - const uint8_t pal = attr & 0x7; - index = 4 * pal + idx; - } - else - { - const uint8_t pal = conf.dmg_pal; - index = (pal >> (idx * 2)) & 0x3; - } - // no conversion - return index; -} -uint16_t GPU::colorize_sprite(const Sprite* sprite, sprite_config_t& sprconf, const uint8_t idx) -{ - uint16_t index = 0; - if (machine().is_cgb()) { - index = 32 + 4 * sprite->cgb_pal() + idx; - } - else { - const uint8_t pal = sprconf.palette[sprite->pal()]; - index = (pal >> (idx * 2)) & 0x3; - } - // no conversion - return index; -} - -bool GPU::lcd_enabled() const noexcept { return m_reg_lcdc & 0x80; } -bool GPU::window_enabled() const noexcept { return m_reg_lcdc & 0x20; } -bool GPU::window_visible() { return window_enabled() && window_x() < 166 && window_y() < 143; } -int GPU::window_x() { return io().reg(IO::REG_WX); } -int GPU::window_y() { return io().reg(IO::REG_WY); } - -uint16_t GPU::bg_tiles() const noexcept { return (m_reg_lcdc & 0x08) ? 0x9C00 : 0x9800; } -uint16_t GPU::window_tiles() const noexcept { return (m_reg_lcdc & 0x40) ? 0x9C00 : 0x9800; } -uint16_t GPU::tile_data() const noexcept { return (m_reg_lcdc & 0x10) ? 0x8000 : 0x8800; } - -TileData GPU::create_tiledata(uint16_t tiles, uint16_t patterns) -{ - const bool is_signed = (m_reg_lcdc & 0x10) == 0; - const auto* vram = memory().video_ram_ptr(); - // printf("Background tiles: 0x%04x Tile data: 0x%04x\n", - // bg_tiles(), tile_data()); - const auto* tile_base = &vram[tiles - 0x8000]; - const auto* patt_base = &vram[patterns - 0x8000]; - const uint8_t* attr_base = nullptr; - if (machine().is_cgb()) - { - // attributes are always in VRAM bank 1 (which is off=0x2000) - attr_base = &vram[tiles - 0x8000 + 0x2000]; - } - return TileData{tile_base, patt_base, attr_base, is_signed}; -} -tileconf_t GPU::tile_config() -{ - return tileconf_t{ - .is_cgb = machine().is_cgb(), - .dmg_pal = memory().read8(IO::REG_BGP), - }; -} -sprite_config_t GPU::sprite_config() -{ - sprite_config_t config; - config.patterns = memory().video_ram_ptr(); - config.palette[0] = memory().read8(IO::REG_OBP0); - config.palette[1] = memory().read8(IO::REG_OBP1); - config.scan_x = 0; - config.scan_y = 0; - config.set_height(m_reg_lcdc & 0x4); - config.is_cgb = machine().is_cgb(); - return config; -} - -std::vector GPU::find_sprites(const sprite_config_t& config) const -{ - const Sprite* sprite_begin = this->sprites_begin(); - const Sprite* sprite_back = this->sprites_end() - 1; - std::vector results; - // draw sprites from right to left - for (const Sprite* sprite = sprite_back; sprite >= sprite_begin; sprite--) - { - if (sprite->hidden() == false) - if (sprite->is_within_scanline(config)) - { - results.push_back(sprite); - // GB/GBC supports 10 sprites max per scanline - if (results.size() == 10) break; - } - } - return results; -} -const Sprite* GPU::sprites_begin() const noexcept { return &((Sprite*) memory().oam_ram_ptr())[0]; } -const Sprite* GPU::sprites_end() const noexcept { return &((Sprite*) memory().oam_ram_ptr())[40]; } - -std::vector GPU::dump_background() -{ - std::vector data(256 * 256); - // create tiledata object from LCDC register - auto td = this->create_tiledata(bg_tiles(), tile_data()); - auto tconf = this->tile_config(); - - for (int y = 0; y < 256; y++) - for (int x = 0; x < 256; x++) - { - // get the tile id - const int tid = td.tile_id(x >> 3, y >> 3); - const int tattr = td.tile_attr(x >> 3, y >> 3); - // copy the 16-byte tile into buffer - const int idx = td.pattern(tid, tattr, x & 7, y & 7); - data.at(y * 256 + x) = this->colorize_tile(tconf, tattr, idx); - } - return data; -} -std::vector GPU::dump_tiles(int bank) -{ - std::vector data(16 * 24 * 8 * 8); - // tiles start at the beginning of video RAM - auto td = this->create_tiledata(0x8000, 0x8000); - auto tconf = this->tile_config(); - const uint8_t attr = (bank == 0) ? 0x00 : 0x08; - - for (int y = 0; y < 24 * 8; y++) - for (int x = 0; x < 16 * 8; x++) - { - int tile = (y / 8) * 16 + (x / 8); - // copy the 16-byte tile into buffer - const int idx = td.pattern(tile, attr, x & 7, y & 7); - data.at(y * 128 + x) = this->colorize_tile(tconf, attr, idx); - } - return data; -} - -void GPU::set_video_bank(const uint8_t bank) -{ - assert(bank < 2); - this->m_state.video_offset = bank * 0x2000; -} -void GPU::lcd_power_changed(const bool online) -{ - // printf("Screen turned %s\n", online ? "ON" : "OFF"); - if (online) - { - // at the start of a new frame - this->m_state.period = this->scanline_cycles(); - this->m_state.current_scanline = 153; - this->m_reg_ly = this->m_state.current_scanline; - } - else - { - // LCD off, just reset to LY 0 - this->m_state.period = 0; - this->m_state.current_scanline = 0; - this->m_reg_ly = 0; - // modify stat to V-blank? - this->set_mode(1); - // theres a full white frame when turning on again - this->m_state.white_frame = true; - } -} - -void GPU::setpal(uint16_t index, uint8_t value) -{ - this->getpal(index) = value; - // sprite palette index 0 is unused - if (index >= 64 && (index & 7) < 2) return; - // - if (this->m_on_palchange) - { - const uint8_t base = index / 2; - const uint16_t c16 = getpal(base * 2) | (getpal(base * 2 + 1) << 8); - // linearize palette memory - this->m_on_palchange(base, c16); - } -} // setpal(...) - -void GPU::set_dmg_variant(dmg_variant_t variant) { this->m_variant = variant; } - -// serialization -int GPU::restore_state(const std::vector& data, int off) -{ - this->m_state = *(state_t*) &data.at(off); - return sizeof(m_state); -} -void GPU::serialize_state(std::vector& res) const -{ - res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); -} -} // namespace gbc diff --git a/components/libgbc/src/instructions.cpp b/components/libgbc/src/instructions.cpp deleted file mode 100644 index 54d4a5ee..00000000 --- a/components/libgbc/src/instructions.cpp +++ /dev/null @@ -1,778 +0,0 @@ -// only include this file once! -#include "machine.hpp" -#include "printers.hpp" -#define DEF_INSTR(x) \ - static instruction_t instr_##x { handler_##x, printer_##x } -#define INSTRUCTION(x) static void handler_##x -#define PRINTER(x) static int printer_##x -union imm8_t -{ - uint8_t u8; - int8_t s8; -}; - -namespace gbc -{ -INSTRUCTION(NOP)(CPU&, const uint8_t) -{ - // NOP takes 4 T-states (instruction decoding) -} -PRINTER(NOP)(char* buffer, size_t len, CPU&, const uint8_t) { return snprintf(buffer, len, "NOP"); } - -INSTRUCTION(LD_N_SP)(CPU& cpu, const uint8_t) { cpu.mtwrite16(cpu.readop16(), cpu.registers().sp); } -PRINTER(LD_N_SP)(char* buffer, size_t len, CPU& cpu, const uint8_t) -{ - return snprintf(buffer, len, "LD (%04X), SP", cpu.peekop16(1)); -} - -INSTRUCTION(LD_R_N)(CPU& cpu, const uint8_t opcode) -{ - cpu.registers().getreg_sp(opcode) = cpu.readop16(); -} -PRINTER(LD_R_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - return snprintf(buffer, len, "LD %s, %04x", cstr_reg(opcode, true), cpu.peekop16(1)); -} - -INSTRUCTION(ADD_HL_R)(CPU& cpu, const uint8_t opcode) -{ - auto& reg = cpu.registers().getreg_sp(opcode); - auto& hl = cpu.registers().hl; - auto& flags = cpu.registers().flags; - setflag(false, flags, MASK_NEGATIVE); - setflag(((hl & 0x0fff) + (reg & 0x0fff)) & 0x1000, flags, MASK_HALFCARRY); - setflag(((hl & 0x0ffff) + (reg & 0x0ffff)) & 0x10000, flags, MASK_CARRY); - hl += reg; - cpu.hardware_tick(); -} -PRINTER(ADD_HL_R)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - return snprintf(buffer, len, "ADD HL, %s", cstr_reg(opcode, true)); -} - -INSTRUCTION(LD_R_A_R)(CPU& cpu, const uint8_t opcode) -{ - if (opcode & 0x8) { cpu.registers().accum = cpu.mtread8(cpu.registers().getreg_sp(opcode)); } - else - { - cpu.mtwrite8(cpu.registers().getreg_sp(opcode), cpu.registers().accum); - } -} -PRINTER(LD_R_A_R)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - if (opcode & 0x8) { return snprintf(buffer, len, "LD A, (%s)", cstr_reg(opcode, true)); } - return snprintf(buffer, len, "LD (%s), A", cstr_reg(opcode, true)); -} - -INSTRUCTION(INC_DEC_R)(CPU& cpu, const uint8_t opcode) -{ - auto& reg = cpu.registers().getreg_sp(opcode); - if ((opcode & 0x8) == 0) { reg++; } - else - { - reg--; - } - cpu.hardware_tick(); -} -PRINTER(INC_DEC_R)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - if ((opcode & 0x8) == 0) { return snprintf(buffer, len, "INC %s", cstr_reg(opcode, true)); } - return snprintf(buffer, len, "DEC %s", cstr_reg(opcode, true)); -} - -INSTRUCTION(INC_DEC_D)(CPU& cpu, const uint8_t opcode) -{ - const uint8_t dst = opcode >> 3; - uint8_t value; - if (dst != 0x6) - { - if ((opcode & 0x1) == 0) { cpu.registers().getdest(dst)++; } - else - { - cpu.registers().getdest(dst)--; - } - value = cpu.registers().getdest(dst); - } - else - { - value = cpu.read_hl(); - if ((opcode & 0x1) == 0) { value++; } - else - { - value--; - } - cpu.write_hl(value); - } - auto& flags = cpu.registers().flags; - setflag(opcode & 0x1, flags, MASK_NEGATIVE); - setflag(value == 0, flags, MASK_ZERO); // set zero - if ((opcode & 0x1) == 0) - setflag((value & 0xF) == 0x0, flags, MASK_HALFCARRY); - else - setflag((value & 0xF) == 0xF, flags, MASK_HALFCARRY); -} -PRINTER(INC_DEC_D)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - const char* mnemonic = (opcode & 0x1) ? "DEC" : "INC"; - return snprintf(buffer, len, "%s %s", mnemonic, cstr_dest(opcode >> 3)); -} - -INSTRUCTION(LD_D_N)(CPU& cpu, const uint8_t opcode) -{ - const uint8_t imm8 = cpu.readop8(); - if (((opcode >> 3) & 0x7) != 0x6) { cpu.registers().getdest(opcode >> 3) = imm8; } - else - { - cpu.write_hl(imm8); - } -} -PRINTER(LD_D_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - if (((opcode >> 3) & 0x7) != 0x6) - return snprintf(buffer, len, "LD %s, %02X", cstr_dest(opcode >> 3), cpu.peekop8(1)); - else - return snprintf(buffer, len, "LD (HL=%04X), %02X", cpu.registers().hl, cpu.peekop8(1)); -} - -INSTRUCTION(RLC_RRC)(CPU& cpu, const uint8_t opcode) -{ - auto& accum = cpu.registers().accum; - auto& flags = cpu.registers().flags; - switch (opcode) - { - case 0x07: - { - // RLCA, rotate A left - const uint8_t bit7 = accum & 0x80; - accum = (accum << 1) | (bit7 >> 7); - flags = 0; - setflag(bit7, flags, MASK_CARRY); // old bit7 to CF - } - break; - case 0x0F: - { - // RRCA, rotate A right - const uint8_t bit0 = accum & 0x1; - accum = (accum >> 1) | (bit0 << 7); - flags = 0; - setflag(bit0, flags, MASK_CARRY); // old bit0 to CF - } - break; - case 0x17: - { - // RLA, rotate A left, old CF to bit 0 - const uint8_t bit7 = accum & 0x80; - accum = (accum << 1) | ((flags & MASK_CARRY) >> 4); - flags = 0; - setflag(bit7, flags, MASK_CARRY); // old bit7 to CF - } - break; - case 0x1F: - { - // RRA, rotate A right, old CF to bit 7 - const uint8_t bit0 = accum & 0x1; - accum = (accum >> 1) | ((flags & MASK_CARRY) << 3); - flags = 0; - setflag(bit0, flags, MASK_CARRY); // old bit0 to CF - } - break; - default: - GBC_ASSERT(0 && "Unknown opcode in RLC/RRC handler"); - } -} -PRINTER(RLC_RRC)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - const char* mnemonic[4] = {"RLC", "RRC", "RL", "RR"}; - return snprintf(buffer, len, "%s A (A = %02X)", mnemonic[opcode >> 3], cpu.registers().accum); -} - -INSTRUCTION(LD_D_D)(CPU& cpu, const uint8_t opcode) -{ - const bool HL = (opcode & 0x7) == 0x6; - uint8_t reg; - if (!HL) - reg = cpu.registers().getdest(opcode); - else - reg = cpu.read_hl(); - - if (((opcode >> 3) & 0x7) != 0x6) { cpu.registers().getdest(opcode >> 3) = reg; } - else - { - cpu.write_hl(reg); - } -} -PRINTER(LD_D_D)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - return snprintf(buffer, len, "LD %s, %s", cstr_dest(opcode >> 3), cstr_dest(opcode >> 0)); -} - -INSTRUCTION(LD_N_A_N)(CPU& cpu, const uint8_t opcode) -{ - const uint16_t addr = cpu.readop16(); - if (opcode == 0xEA) - { - // load into (N) from A - cpu.mtwrite8(addr, cpu.registers().accum); - } - else - { - // load into A from (N) - cpu.registers().accum = cpu.mtread8(addr); - } - cpu.hardware_tick(); // 16 T-states -} -PRINTER(LD_N_A_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - if (opcode == 0xEA) - return snprintf(buffer, len, "LD (%04X), A (A = %02X)", cpu.peekop16(1), - cpu.registers().accum); - else - return snprintf(buffer, len, "LD A, (%04X)", cpu.peekop16(1)); -} - -INSTRUCTION(LDID_HL_A)(CPU& cpu, const uint8_t opcode) -{ - if ((opcode & 0x8) == 0) - { - // load from A into (HL) - cpu.write_hl(cpu.registers().accum); - } - else - { - // load from (HL) into A - cpu.registers().accum = cpu.read_hl(); - } - if ((opcode & 0x10) == 0) { cpu.registers().hl++; } - else - { - cpu.registers().hl--; - } -} -PRINTER(LDID_HL_A)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - const char* mnemonic = (opcode & 0x10) ? "LDD" : "LDI"; - if ((opcode & 0x8) == 0) - return snprintf(buffer, len, "%s (HL=%04X), A", mnemonic, cpu.registers().hl); - else - return snprintf(buffer, len, "%s A, (HL=%04X)", mnemonic, cpu.registers().hl); -} - -INSTRUCTION(DAA)(CPU& cpu, const uint8_t) -{ - auto& regs = cpu.registers(); - if (regs.flags & MASK_NEGATIVE) - { - if (regs.flags & MASK_CARRY) regs.accum -= 0x60; - if (regs.flags & MASK_HALFCARRY) regs.accum -= 0x06; - } - else - { - if ((regs.flags & MASK_CARRY) || regs.accum > 0x99) - { - regs.accum += 0x60; - setflag(true, regs.flags, MASK_CARRY); - } - if ((regs.flags & MASK_HALFCARRY) || (regs.accum & 0xF) > 0x9) { regs.accum += 0x06; } - } - setflag(regs.accum == 0, regs.flags, MASK_ZERO); - setflag(false, regs.flags, MASK_HALFCARRY); -} -PRINTER(DAA)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "DAA"); } - -INSTRUCTION(CPL_A)(CPU& cpu, const uint8_t) -{ - cpu.registers().accum = ~cpu.registers().accum; - auto& regs = cpu.registers(); - setflag(true, regs.flags, MASK_NEGATIVE); - setflag(true, regs.flags, MASK_HALFCARRY); -} -PRINTER(CPL_A)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "CPL A"); } - -INSTRUCTION(SCF_CCF)(CPU& cpu, const uint8_t opcode) -{ - auto& flags = cpu.registers().flags; - if ((opcode & 0x8) == 0) - { - // Set CF - flags |= MASK_CARRY; - } - else - { - // Complement CF - setflag(not(flags & MASK_CARRY), flags, MASK_CARRY); - } - setflag(false, flags, MASK_NEGATIVE); - setflag(false, flags, MASK_HALFCARRY); -} -PRINTER(SCF_CCF)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - return snprintf(buffer, len, (opcode & 0x8) ? "CCF (CY=0)" : "SCF (CY=1)"); -} - -// ALU A, D / A, N -INSTRUCTION(ALU_A_D)(CPU& cpu, const uint8_t opcode) -{ - const uint8_t alu_op = (opcode >> 3) & 0x7; - // A, D - if ((opcode & 0x7) != 0x6) { cpu.registers().alu(alu_op, cpu.registers().getdest(opcode)); } - else - { - cpu.registers().alu(alu_op, cpu.read_hl()); - } -} -PRINTER(ALU_A_D)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - return snprintf(buffer, len, "%s A, %s", cstr_alu(opcode >> 3), cstr_dest(opcode)); -} - -INSTRUCTION(ALU_A_N)(CPU& cpu, const uint8_t opcode) -{ - const uint8_t alu_op = (opcode >> 3) & 0x7; - // A, N - const uint8_t imm8 = cpu.readop8(); - cpu.registers().alu(alu_op, imm8); -} -PRINTER(ALU_A_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - return snprintf(buffer, len, "%s A, 0x%02x", cstr_alu(opcode >> 3), cpu.peekop8(1)); -} - -INSTRUCTION(JP)(CPU& cpu, const uint8_t opcode) -{ - const uint16_t dest = cpu.readop16(); - if ((opcode & 1) || (cpu.registers().compare_flags(opcode))) - { - cpu.jump(dest); - cpu.hardware_tick(); - } -} -PRINTER(JP)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - if (opcode & 1) { return snprintf(buffer, len, "JP 0x%04x", cpu.peekop16(1)); } - char temp[128]; - fill_flag_buffer(temp, sizeof(temp), opcode, cpu.registers().flags); - return snprintf(buffer, len, "JP 0x%04x (%s)", cpu.peekop16(1), temp); -} - -INSTRUCTION(PUSH_POP)(CPU& cpu, const uint8_t opcode) -{ - if (opcode & 4) - { - // PUSH R - cpu.push_value(cpu.registers().getreg_af(opcode)); - } - else - { - // POP R - cpu.registers().getreg_af(opcode) = cpu.mtread16(cpu.registers().sp); - cpu.registers().sp += 2; - if (((opcode >> 4) & 0x3) == 0x3) - { - // NOTE: POP AF requires clearing flag bits 0-3 - cpu.registers().flags &= 0xF0; - } - } -} -PRINTER(PUSH_POP)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - if (opcode & 4) - { - return snprintf(buffer, len, "PUSH %s (0x%04x)", cstr_reg(opcode, false), - cpu.registers().getreg_af(opcode)); - } - return snprintf(buffer, len, "POP %s (0x%04x)", cstr_reg(opcode, false), - cpu.memory().read16(cpu.registers().sp)); -} - -INSTRUCTION(RET)(CPU& cpu, const uint8_t opcode) -{ - if ((opcode & 0xef) == 0xc9 || cpu.registers().compare_flags(opcode)) - { - cpu.registers().pc = cpu.mtread16(cpu.registers().sp); - cpu.registers().sp += 2; - if (UNLIKELY(cpu.machine().verbose_instructions)) - { printf("* Returned to 0x%04x\n", cpu.registers().pc); } - if (opcode != 0xc9) - { - cpu.hardware_tick(); // RET nzc needs one more tick - } - } - cpu.hardware_tick(); -} -PRINTER(RET)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - if (opcode == 0xc9) { return snprintf(buffer, len, "RET"); } - char temp[128]; - fill_flag_buffer(temp, sizeof(temp), opcode, cpu.registers().flags); - return snprintf(buffer, len, "RET %s", temp); -} - -INSTRUCTION(RETI)(CPU& cpu, const uint8_t) -{ - cpu.registers().pc = cpu.mtread16(cpu.registers().sp); - cpu.registers().sp += 2; - if (UNLIKELY(cpu.machine().verbose_instructions)) - { printf("* Returned (w/interrupts) to 0x%04x\n", cpu.registers().pc); } - cpu.hardware_tick(); - cpu.enable_interrupts(); -} -PRINTER(RETI)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "RETI"); } - -INSTRUCTION(RST)(CPU& cpu, const uint8_t opcode) -{ - const uint16_t dst = opcode & 0x38; - if (UNLIKELY(cpu.registers().pc == dst + 1)) - { - printf(">>> RST loop detected at vector 0x%04x\n", dst); - cpu.break_now(); - return; - } - // jump to vector area - cpu.push_and_jump(dst); -} -PRINTER(RST)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - return snprintf(buffer, len, "RST 0x%02x", opcode & 0x38); -} - -INSTRUCTION(STOP)(CPU& cpu, const uint8_t) -{ - // STOP is a weirdo two-byte instruction - cpu.registers().pc++; - if (cpu.machine().io.joypad_is_disabled()) - { - printf("The machine has stopped with joypad disabled\n"); - cpu.break_now(); - } - // enter stopped state - cpu.stop(); -} -PRINTER(STOP)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "STOP"); } - -INSTRUCTION(JR_N)(CPU& cpu, const uint8_t opcode) -{ - const imm8_t disp{.u8 = cpu.readop8()}; - cpu.hardware_tick(); - if (opcode == 0x18 || (cpu.registers().compare_flags(opcode))) - { cpu.jump(cpu.registers().pc + disp.s8); } -} -PRINTER(JR_N)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - const imm8_t disp{.u8 = cpu.peekop8(1)}; - const uint16_t dest = cpu.registers().pc + 2 + disp.s8; - if (opcode & 0x20) - { - char temp[128]; - fill_flag_buffer(temp, sizeof(temp), opcode, cpu.registers().flags); - return snprintf(buffer, len, "JR %+hhd (%s) => %04X", disp.s8, temp, dest); - } - return snprintf(buffer, len, "JR %+hhd => %04X", disp.s8, dest); -} - -INSTRUCTION(HALT)(CPU& cpu, const uint8_t) -{ - if (cpu.ime()) { cpu.wait(); } - else - { - cpu.buggy_halt(); - } -} -PRINTER(HALT)(char* buffer, size_t len, CPU&, uint8_t) { return snprintf(buffer, len, "HALT"); } - -INSTRUCTION(CALL)(CPU& cpu, const uint8_t opcode) -{ - const uint16_t dest = cpu.readop16(); - if ((opcode & 1) || cpu.registers().compare_flags(opcode)) - { - // push address of **next** instr - cpu.push_and_jump(dest); - } -} -PRINTER(CALL)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - if (opcode & 1) { return snprintf(buffer, len, "CALL %04X", cpu.peekop16(1)); } - char temp[128]; - fill_flag_buffer(temp, sizeof(temp), opcode, cpu.registers().flags); - return snprintf(buffer, len, "CALL %04X (%s)", cpu.peekop16(1), temp); -} - -INSTRUCTION(ADD_SP_N)(CPU& cpu, const uint8_t) -{ - const imm8_t imm{.u8 = cpu.readop8()}; - auto& regs = cpu.registers(); - const int calc = (regs.sp + imm.s8) & 0xFFFF; - regs.flags = 0; - setflag(((regs.sp ^ imm.s8 ^ calc) & 0x100) == 0x100, regs.flags, MASK_CARRY); - setflag(((regs.sp ^ imm.s8 ^ calc) & 0x10) == 0x10, regs.flags, MASK_HALFCARRY); - cpu.registers().sp = calc; - cpu.hardware_tick(); - cpu.hardware_tick(); -} -PRINTER(ADD_SP_N)(char* buffer, size_t len, CPU& cpu, uint8_t) -{ - return snprintf(buffer, len, "ADD SP, 0x%02x", cpu.peekop8(1)); -} - -INSTRUCTION(LD_FF00_A)(CPU& cpu, const uint8_t opcode) -{ - switch (opcode) - { - case 0xE2: - cpu.mtwrite8(0xFF00 + cpu.registers().c, cpu.registers().accum); - return; - case 0xF2: - cpu.registers().accum = cpu.mtread8(0xFF00 + cpu.registers().c); - return; - case 0xE0: - cpu.mtwrite8(0xFF00 + cpu.readop8(), cpu.registers().accum); - return; - case 0xF0: - cpu.registers().accum = cpu.mtread8(0xFF00 + cpu.readop8()); - return; - } - GBC_ASSERT(0); -} -PRINTER(LD_FF00_A)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - switch (opcode) - { - case 0xE2: - return snprintf(buffer, len, "LD (FF00+C=%02X), A", cpu.registers().c); - case 0xF2: - return snprintf(buffer, len, "LD A, (FF00+C=%02X)", cpu.registers().c); - case 0xE0: - return snprintf(buffer, len, "LD (FF00+%02X), A", cpu.peekop8(1)); - case 0xF0: - return snprintf(buffer, len, "LD A, (FF00+%02X)", cpu.peekop8(1)); - } - GBC_ASSERT(0); -} - -INSTRUCTION(LD_HL_SP)(CPU& cpu, const uint8_t opcode) -{ - if (opcode == 0xF8) - { - // the ADD operation is signed - const imm8_t imm{.u8 = cpu.readop8()}; - cpu.registers().flags = 0; - setflag(((cpu.registers().sp & 0xf) + (imm.u8 & 0x0f)) & 0x10, cpu.registers().flags, - MASK_HALFCARRY); - setflag(((cpu.registers().sp & 0xff) + (imm.u8 & 0xff)) & 0x100, cpu.registers().flags, - MASK_CARRY); - cpu.registers().hl = cpu.registers().sp + imm.s8; - } - else - { - cpu.registers().sp = cpu.registers().hl; - } - cpu.hardware_tick(); -} -PRINTER(LD_HL_SP)(char* buffer, size_t len, CPU& cpu, uint8_t opcode) -{ - if (opcode == 0xF8) { return snprintf(buffer, len, "LD HL, SP + %02X", cpu.peekop8(1)); } - return snprintf(buffer, len, "LD SP, HL (HL=%04X)", cpu.registers().hl); -} - -INSTRUCTION(JP_HL)(CPU& cpu, const uint8_t) { cpu.jump(cpu.registers().hl); } -PRINTER(JP_HL)(char* buffer, size_t len, CPU& cpu, uint8_t) -{ - return snprintf(buffer, len, "JP HL (HL=%04X)", cpu.registers().hl); -} - -INSTRUCTION(DI_EI)(CPU& cpu, const uint8_t opcode) -{ - if (opcode & 0x08) { cpu.enable_interrupts(); } - else - { - cpu.disable_interrupts(); - } -} -PRINTER(DI_EI)(char* buffer, size_t len, CPU&, uint8_t opcode) -{ - const char* mnemonic = (opcode & 0x08) ? "EI" : "DI"; - return snprintf(buffer, len, "%s", mnemonic); -} - -INSTRUCTION(CB_EXT)(CPU& cpu, const uint8_t) -{ - const uint8_t opcode = cpu.readop8(); - const bool HL = (opcode & 0x7) == 0x6; - uint8_t reg; - if (!HL) - reg = cpu.registers().getdest(opcode); - else - reg = cpu.read_hl(); - - // BIT, RESET, SET - if (opcode >> 6) - { - const uint8_t bit = (opcode >> 3) & 7; - switch (opcode >> 6) - { - case 0x1: - { // BIT - const int set = reg & (1 << bit); - // set flags - cpu.registers().flags &= ~MASK_NEGATIVE; - cpu.registers().flags |= MASK_HALFCARRY; - setflag(set == 0, cpu.registers().flags, MASK_ZERO); - // BIT only takes 8/12 T-cycles - return; - } - case 0x2: // RESET - reg &= ~(1 << bit); - break; - case 0x3: // SET - reg |= 1 << bit; - break; - } - } - else if ((opcode & 0xF0) == 0x00) - { - auto& flags = cpu.registers().flags; - flags = 0; - if (opcode & 0x8) - { - // RRC D, rotate D right, keep old bit0 - setflag(reg & 0x1, flags, MASK_CARRY); // old bit0 to CF - reg = (reg >> 1) | (reg << 7); - } - else - { - // RLC D, rotate D left, keep old bit7 - setflag(reg & 0x80, flags, MASK_CARRY); // old bit7 to CF - reg = (reg << 1) | (reg >> 7); - } - setflag(reg == 0, cpu.registers().flags, MASK_ZERO); - } - else if ((opcode & 0xF0) == 0x10) - { - auto& flags = cpu.registers().flags; - // NOTE: dont reset flags here - if (opcode & 0x8) - { - // RR D, rotate D right through carry, old CF to bit 7 - const uint8_t bit0 = reg & 0x1; - reg = (reg >> 1) | ((flags & MASK_CARRY) << 3); - flags = 0; - setflag(bit0, flags, MASK_CARRY); // old bit0 to CF - } - else - { - // RL D, rotate D left through carry, old CF to bit 0 - const uint8_t bit7 = reg & 0x80; - reg = (reg << 1) | ((flags & MASK_CARRY) >> 4); - flags = 0; - setflag(bit7, flags, MASK_CARRY); // old bit7 to CF - } - setflag(reg == 0, flags, MASK_ZERO); - } - else if ((opcode & 0xF0) == 0x20) - { - auto& flags = cpu.registers().flags; - flags = 0; - if (opcode & 0x8) - { - // SRA D - setflag(reg & 0x1, flags, MASK_CARRY); - reg >>= 1; - reg |= (reg & 0x40) << 1; - } - else - { - // SLA D - setflag(reg & 0x80, flags, MASK_CARRY); - reg <<= 1; - } - setflag(reg == 0, cpu.registers().flags, MASK_ZERO); - } - else if ((opcode & 0xf0) == 0x30) - { - if ((opcode & 0x8) == 0x0) - { - // SWAP D - reg = (reg >> 4) | (reg << 4); - cpu.registers().flags = 0; - setflag(reg == 0, cpu.registers().flags, MASK_ZERO); - } - else - { - // SRL D (logical) - cpu.registers().flags = 0; - setflag(reg & 0x1, cpu.registers().flags, MASK_CARRY); - reg >>= 1; - setflag(reg == 0, cpu.registers().flags, MASK_ZERO); - } - } - else - { - fprintf(stderr, "Missing instruction: %#x\n", opcode); - GBC_ASSERT(0 && "Unimplemented extended instruction"); - } - // all instructions on this opcode go into the same dest - if (!HL) - cpu.registers().getdest(opcode) = reg; - else - cpu.write_hl(reg); -} -PRINTER(CB_EXT)(char* buffer, size_t len, CPU& cpu, uint8_t) -{ - const uint8_t opcode = cpu.peekop8(1); - if (opcode >> 6) - { - const char* mnemonic[] = {"IMPLEMENT ME", "BIT", "RES", "SET"}; - return snprintf(buffer, len, "%s %u, %s", mnemonic[opcode >> 6], (opcode >> 3) & 0x7, - cstr_dest(opcode)); - } - else - { - const char* mnemonic[] = {"RLC", "RRC", "RL", "RR", "SLA", "SRA", "SWAP", "SRL"}; - return snprintf(buffer, len, "%s %s", mnemonic[opcode >> 3], cstr_dest(opcode)); - } -} - -INSTRUCTION(MISSING)(CPU& cpu, const uint8_t opcode) -{ - fprintf(stderr, "Missing instruction: %#x\n", opcode); - // pause for each instruction - cpu.print_and_pause(cpu, opcode); -} -PRINTER(MISSING)(char* buffer, size_t len, CPU&, const uint8_t opcode) -{ - return snprintf(buffer, len, "MISSING opcode 0x%02x", opcode); -} - -DEF_INSTR(NOP); -DEF_INSTR(LD_N_SP); -DEF_INSTR(LD_R_N); -DEF_INSTR(ADD_HL_R); -DEF_INSTR(LD_R_A_R); -DEF_INSTR(INC_DEC_R); -DEF_INSTR(INC_DEC_D); -DEF_INSTR(LD_D_N); -DEF_INSTR(LD_D_D); -DEF_INSTR(LD_N_A_N); -DEF_INSTR(JR_N); -DEF_INSTR(RLC_RRC); -DEF_INSTR(LDID_HL_A); -DEF_INSTR(DAA); -DEF_INSTR(CPL_A); -DEF_INSTR(SCF_CCF); -DEF_INSTR(HALT); -DEF_INSTR(ALU_A_D); -DEF_INSTR(ALU_A_N); -DEF_INSTR(PUSH_POP); -DEF_INSTR(RST); -DEF_INSTR(RET); -DEF_INSTR(RETI); -DEF_INSTR(STOP); -DEF_INSTR(JP); -DEF_INSTR(CALL); -DEF_INSTR(ADD_SP_N); -DEF_INSTR(LD_FF00_A); -DEF_INSTR(LD_HL_SP); -DEF_INSTR(JP_HL); -DEF_INSTR(DI_EI); -DEF_INSTR(CB_EXT); -DEF_INSTR(MISSING); -} // namespace gbc diff --git a/components/libgbc/src/io.cpp b/components/libgbc/src/io.cpp deleted file mode 100644 index b9ec82d7..00000000 --- a/components/libgbc/src/io.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#include "io.hpp" -#include "io_regs.cpp" -#include "machine.hpp" -#include - -namespace gbc -{ -IO::IO(Machine& mach) - : vblank{0x1, 0x40, "V-blank"} - , lcd_stat{0x2, 0x48, "LCD Status"} - , timerint{0x4, 0x50, "Timer"} - , serialint{0x8, 0x58, "Serial"} - , joypadint{0x10, 0x60, "Joypad"} - , debugint{0x0, 0x0, "Debug"} - , m_machine(mach) -{ - this->reset(); -} - -void IO::reset() -{ - // register defaults - reg(REG_P1) = 0xcf; - reg(REG_TIMA) = 0x00; - reg(REG_TMA) = 0x00; - reg(REG_TAC) = 0xf8; - // sound defaults - reg(REG_NR10) = 0x80; - reg(REG_NR11) = 0xbf; - reg(REG_NR52) = 0xf1; - // LCD defaults - reg(REG_LCDC) = 0x91; - reg(REG_STAT) = 0x81; - reg(REG_LY) = 0x90; // 144 - reg(REG_LYC) = 0x0; - reg(REG_DMA) = 0x00; - // Palette - reg(REG_BGP) = 0xfc; - reg(REG_OBP0) = 0xff; - reg(REG_OBP1) = 0xff; - // boot rom enabled at boot - reg(REG_BOOT) = 0x00; - reg(REG_HDMA5) = 0xFF; - - this->m_state.reg_ie = 0x00; -} - -void IO::simulate() -{ - // 1. DIV timer - this->m_state.divider += 256 / 64; - this->reg(REG_DIV) = this->m_state.divider >> 8; - - // 2. TIMA timer - if (this->reg(REG_TAC) & 0x4) - { - const std::array TIMA_CYCLES = {1024, 16, 64, 256}; - const int speed = this->reg(REG_TAC) & 0x3; - // TIMA counter timer - if (m_state.divider % (TIMA_CYCLES[speed]) == 0) - { - this->reg(REG_TIMA)++; - // if (reg(REG_TIMA) > 16) machine().break_now(); - // timer interrupt when overflowing to 0 - if (this->reg(REG_TIMA) == 0) - { - this->trigger(this->timerint); - // BUG: TIMA does not get reset before after 4 cycles - this->m_state.timabug = 4; - } - } - else if (UNLIKELY(this->m_state.timabug > 0)) - { - this->m_state.timabug--; - if (this->m_state.timabug == 0) - { - // restart at modulo - this->reg(REG_TIMA) = this->reg(REG_TMA); - } - } - } - - // 3. OAM DMA operation - if (this->m_state.dma.bytes_left > 0) - { - if (this->m_state.dma.slow_start > 0) { this->m_state.dma.slow_start--; } - else - { - // calculate number of bytes to copy - const int btw = 1; - // do the copying - auto& memory = machine().memory; - for (int i = 0; i < btw; i++) - { memory.write8(m_state.dma.dst++, memory.read8(m_state.dma.src++)); } - assert(m_state.dma.bytes_left >= btw); - m_state.dma.bytes_left -= btw; - } - } - - // 4. HDMA operation - if (this->hdma().bytes_left > 0) - { - // during H-blank, once for each line - if (machine().gpu.is_hblank() && hdma().cur_line != reg(REG_LY)) - { - hdma().cur_line = reg(REG_LY); - auto& memory = machine().memory; - int btw = std::min(16, (int)(hdma().bytes_left)); - // do the copying - for (int i = 0; i < btw; i++) - { memory.write8(hdma().dst++, memory.read8(hdma().src++)); } - hdma().dst &= 0x9FFF; // make sure it wraps around VRAM - assert(hdma().bytes_left >= btw); - hdma().bytes_left -= btw; - if (hdma().bytes_left == 0) - { - // transfer complete - this->reg(REG_HDMA5) = 0xFF; - } - } - } -} - -uint8_t IO::read_io(const uint16_t addr) -{ - // default: just return the register value - if (addr >= 0xff00 && addr < 0xff80) - { - if (UNLIKELY(machine().break_on_io && !machine().is_breaking())) - { - printf("[io] * I/O read 0x%04x => 0x%02x\n", addr, reg(addr)); - machine().break_now(); - } - auto& handler = iologic.at(addr - 0xff00); - if (handler.on_read != nullptr) { return handler.on_read(*this, addr); } - return reg(addr); - } - if (addr == REG_IE) { return this->m_state.reg_ie; } - printf("[io] * Unknown read 0x%04x\n", addr); - machine().undefined(); - return 0xff; -} -void IO::write_io(const uint16_t addr, uint8_t value) -{ - // default: just write to register - if (addr >= 0xff00 && addr < 0xff80) - { - if (UNLIKELY(machine().break_on_io && !machine().is_breaking())) - { - printf("[io] * I/O write 0x%04x value 0x%02x\n", addr, value); - machine().break_now(); - } - auto& handler = iologic.at(addr - 0xff00); - if (handler.on_write != nullptr) - { - handler.on_write(*this, addr, value); - return; - } - // default: just write... - reg(addr) = value; - return; - } - if (addr == REG_IE) - { - this->m_state.reg_ie = value; - return; - } - printf("[io] * Unknown write 0x%04x value 0x%02x\n", addr, value); - machine().undefined(); -} - -void IO::trigger_keys(uint8_t mask) -{ - joypad().keypad = ~(mask & 0xF); - joypad().buttons = ~(mask >> 4); - // trigger joypad interrupt on every change - if (joypad().last_mask != mask) - { - joypad().last_mask = mask; - this->trigger(joypadint); - } -} -bool IO::joypad_is_disabled() const noexcept { return (reg(REG_P1) & 0x30) == 0x30; } - -void IO::start_dma(uint16_t src) -{ - oam_dma().slow_start = 2; - oam_dma().src = src; - oam_dma().dst = 0xfe00; - oam_dma().bytes_left = 160; // 160 bytes total -} - -void IO::start_hdma(uint16_t src, uint16_t dst, uint16_t bytes) -{ - hdma().src = src; - hdma().dst = dst; - hdma().bytes_left = bytes; - hdma().cur_line = 0xff; -} - -void IO::perform_stop() -{ - // bit 1 = stopped, bit 8 = LCD on/off - this->reg(REG_KEY1) = 0x1; - // remember previous LCD on/off value - this->m_state.lcd_powered = reg(REG_LCDC) & 0x80; - // disable LCD - reg(REG_LCDC) &= ~0x80; - // enable joypad interrupts - this->m_state.reg_ie |= joypadint.mask; -} -void IO::deactivate_stop() -{ - // turn screen back on, if it was turned off - if (this->m_state.lcd_powered) reg(REG_LCDC) |= 0x80; - reg(REG_KEY1) = machine().memory.double_speed() ? 0x80 : 0x0; -} - -void IO::reset_divider() -{ - this->m_state.divider = 0; - this->reg(REG_DIV) = 0; -} - -int IO::restore_state(const std::vector& data, int off) -{ - this->m_state = *(state_t*) &data.at(off); - return sizeof(m_state); -} -void IO::serialize_state(std::vector& res) const -{ - res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); -} -} // namespace gbc diff --git a/components/libgbc/src/io_regs.cpp b/components/libgbc/src/io_regs.cpp deleted file mode 100644 index 158608fe..00000000 --- a/components/libgbc/src/io_regs.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "machine.hpp" -// should only be included once -#define IOHANDLER(off, x) new (&iologic.at(off - 0xff00)) iowrite_t{iowrite_##x, ioread_##x}; - -namespace gbc -{ -struct iowrite_t -{ - using write_handler_t = void (*)(IO&, uint16_t, uint8_t); - using read_handler_t = uint8_t (*)(IO&, uint16_t); - const write_handler_t on_write = nullptr; - const read_handler_t on_read = nullptr; -}; -static std::array iologic = {}; - -void iowrite_JOYP(IO& io, uint16_t, uint8_t value) -{ - if (value & 0x10) - io.joypad().ioswitch = 0; - else if (value & 0x20) - io.joypad().ioswitch = 1; -} -uint8_t ioread_JOYP(IO& io, uint16_t) -{ - io.trigger_joypad_read(); - switch (io.joypad().ioswitch) - { - case 0: - return 0xD0 | io.joypad().buttons; - case 1: - return 0xE0 | io.joypad().keypad; - } - GBC_ASSERT(0 && "Invalid joypad GPIO value"); -} - -void iowrite_DIV(IO& io, uint16_t, uint8_t) -{ - // writing to DIV resets it to 0 - io.reset_divider(); -} -uint8_t ioread_DIV(IO& io, uint16_t) { return io.reg(IO::REG_DIV); } - -void iowrite_LCDC(IO& io, uint16_t addr, uint8_t value) -{ - const bool was_enabled = io.reg(addr) & 0x80; - io.reg(addr) = value; - const bool is_enabled = io.reg(addr) & 0x80; - // check if LCD just turned on - if (!was_enabled && is_enabled) { io.machine().gpu.lcd_power_changed(true); } - // check if LCD was just turned off - else if (was_enabled && !is_enabled) - { - io.machine().gpu.lcd_power_changed(false); - } -} -uint8_t ioread_LCDC(IO& io, uint16_t addr) { return io.reg(addr); } - -void iowrite_STAT(IO& io, uint16_t addr, uint8_t value) -{ - // can only write to the upper bits 3-6 - io.reg(addr) &= 0x87; - io.reg(addr) |= value & 0x78; -} -uint8_t ioread_STAT(IO& io, uint16_t addr) { return io.reg(addr) | 0x80; } - -void iowrite_DMA(IO& io, uint16_t, uint8_t value) -{ - const uint16_t src = value << 8; - // printf("DMA copy start from 0x%04x to 0x%04x\n", src, dst); - io.start_dma(src); -} -uint8_t ioread_DMA(IO& io, uint16_t addr) { return io.reg(addr); } - -void iowrite_HDMA(IO& io, uint16_t addr, uint8_t value) -{ - if (io.machine().is_cgb() == false) return; - switch (addr) - { - case IO::REG_HDMA1: - case IO::REG_HDMA3: - io.reg(addr) = value; - return; - case IO::REG_HDMA2: - case IO::REG_HDMA4: - io.reg(addr) = value & 0xF0; - return; - } - // HDMA 5: start DMA operation - uint16_t src = (io.reg(IO::REG_HDMA1) << 8) | io.reg(IO::REG_HDMA2); - src &= 0xFFF0; - uint16_t dst = (io.reg(IO::REG_HDMA3) << 8) | io.reg(IO::REG_HDMA4); - dst &= 0x9FF0; - dst |= 0x8000; // VRAM only! - // length is measured blocks of 16-bytes, minimum 16 - const uint16_t num_bytes = (1 + (value & 0x7F)) * 16; - - if ((value & 0x80) == 0) - { - if (io.hdma_active()) - { - // disable currently running HDMA - io.start_hdma(0, 0, 0); - io.reg(IO::REG_HDMA5) = value; - } - else - { - // do the transfer immediately - // printf("HDMA transfer 0x%04x to 0x%04x (%u bytes)\n", src, dst, end - src); - const uint16_t end = src + num_bytes; - auto& mem = io.machine().memory; - while (src < end) mem.write8(dst++, mem.read8(src++)); - // transfer complete - io.reg(IO::REG_HDMA5) = 0xFF; - } - } - else - { - // H-blank DMA - io.start_hdma(src, dst, num_bytes); - io.reg(IO::REG_HDMA5) = value; - } -} -uint8_t ioread_HDMA(IO& io, uint16_t addr) -{ - switch (addr) - { - case IO::REG_HDMA1: - case IO::REG_HDMA3: - case IO::REG_HDMA2: - case IO::REG_HDMA4: - return 0xFF; // apparently always? - case IO::REG_HDMA5: - return io.reg(addr); - } - return 0xFF; -} - -void iowrite_AUDIO(IO& io, uint16_t addr, uint8_t value) -{ - io.machine().apu.write(addr, value, io.reg(addr)); -} -uint8_t ioread_AUDIO(IO& io, uint16_t addr) { return io.machine().apu.read(addr, io.reg(addr)); } - -void iowrite_KEY1(IO& io, uint16_t addr, uint8_t value) -{ - // printf("KEY1 0x%04x write 0x%02x\n", addr, value); - io.reg(addr) &= 0x80; - io.reg(addr) |= value & 1; -} -uint8_t ioread_KEY1(IO& io, uint16_t addr) -{ - if (!io.machine().is_cgb()) return 0xff; - return (io.reg(addr) & 0x81) | 0x7E; -} - -void iowrite_VBK(IO& io, uint16_t addr, uint8_t value) -{ - io.reg(addr) = value & 1; - io.machine().gpu.set_video_bank(value & 1); -} -uint8_t ioread_VBK(IO& io, uint16_t addr) { return io.reg(addr) | 0xfe; } - -void iowrite_BOOT(IO& io, uint16_t addr, uint8_t value) -{ - if (value) { io.machine().memory.disable_bootrom(); } - io.reg(addr) |= value; -} -uint8_t ioread_BOOT(IO& io, uint16_t addr) { return io.reg(addr); } - -void iowrite_SVBK(IO& io, uint16_t addr, uint8_t value) -{ - // printf("SVBK 0x%04x write 0x%02x\n", addr, value); - value &= 0x7; - if (value == 0) value = 1; - io.reg(addr) = value; - io.machine().memory.set_wram_bank(value); -} -uint8_t ioread_SVBK(IO& io, uint16_t addr) { return io.reg(addr); } - -inline void auto_increment(uint8_t& idx, const uint8_t mask) -{ - const uint8_t v = idx & mask; - idx &= ~mask; - idx |= (v + 1) & mask; -} - -void iowrite_BGPD(IO& io, uint16_t, uint8_t value) -{ - uint8_t& idx = io.reg(IO::REG_BGPI); // palette index - io.machine().gpu.setpal(0 + (idx & 63), value); - // when bit7 is set, auto-increment the index register - if (idx & 0x80) auto_increment(idx, 63); -} -uint8_t ioread_BGPD(IO& io, uint16_t) -{ - uint8_t idx = io.reg(IO::REG_BGPI) & 63; // palette index - return io.machine().gpu.getpal(0 + idx); -} - -void iowrite_OBPD(IO& io, uint16_t, uint8_t value) -{ - uint8_t& idx = io.reg(IO::REG_OBPI); // palette index - io.machine().gpu.setpal(64 + (idx & 63), value); - // when bit7 is set, auto-increment the index register - if (idx & 0x80) auto_increment(idx, 63); -} -uint8_t ioread_OBPD(IO& io, uint16_t) -{ - uint8_t idx = io.reg(IO::REG_OBPI) & 63; // palette index - return io.machine().gpu.getpal(64 + idx); -} - -__attribute__((constructor)) static void set_io_handlers() -{ - IOHANDLER(IO::REG_P1, JOYP); - IOHANDLER(IO::REG_DIV, DIV); - IOHANDLER(IO::REG_LCDC, LCDC); - IOHANDLER(IO::REG_STAT, STAT); - IOHANDLER(IO::REG_DMA, DMA); - IOHANDLER(IO::REG_NR52, AUDIO); - // CGB registers - IOHANDLER(IO::REG_KEY1, KEY1); - IOHANDLER(IO::REG_VBK, VBK); - IOHANDLER(IO::REG_SVBK, SVBK); - IOHANDLER(IO::REG_BOOT, BOOT); - IOHANDLER(IO::REG_HDMA1, HDMA); - IOHANDLER(IO::REG_HDMA2, HDMA); - IOHANDLER(IO::REG_HDMA3, HDMA); - IOHANDLER(IO::REG_HDMA4, HDMA); - IOHANDLER(IO::REG_HDMA5, HDMA); - // CGB palettes - IOHANDLER(IO::REG_BGPD, BGPD); - IOHANDLER(IO::REG_OBPD, OBPD); -} -} // namespace gbc diff --git a/components/libgbc/src/machine.cpp b/components/libgbc/src/machine.cpp deleted file mode 100644 index 959d2304..00000000 --- a/components/libgbc/src/machine.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "machine.hpp" - -namespace gbc -{ -Machine::Machine(const std::string_view rom, bool init) - : cpu(*this), memory(*this, rom), io(*this), gpu(*this), apu(*this) -{ - // set CGB mode when ROM supports it - const uint8_t cgb = memory.read8(0x143); - this->m_cgb_mode = (cgb & 0x80) && ENABLE_GBC; - // reset CPU now that we know the machine type - if (init) this->cpu.reset(); -} - -void Machine::reset() -{ - cpu.reset(); - memory.reset(); - io.reset(); - gpu.reset(); -} -void Machine::stop() noexcept { this->m_running = false; } - -void Machine::simulate_one_frame() -{ - while (gpu.current_scanline() != 0) { cpu.simulate(); } - while (gpu.current_scanline() != 144) { cpu.simulate(); } - assert(gpu.is_vblank()); -} - -uint64_t Machine::now() noexcept { return cpu.gettime(); } - -void Machine::set_handler(interrupt i, interrupt_handler handler) -{ - switch (i) - { - case VBLANK: - io.vblank.callback = handler; - return; - case TIMER: - io.timerint.callback = handler; - return; - case JOYPAD: - io.joypadint.callback = handler; - return; - case DEBUG: - io.debugint.callback = handler; - return; - } -} - -void Machine::set_inputs(uint8_t mask) { io.trigger_keys(mask); } - -size_t Machine::restore_state(const std::vector& data) -{ - int offset = 0; - offset += cpu.restore_state(data, offset); - offset += memory.restore_state(data, offset); - offset += io.restore_state(data, offset); - offset += gpu.restore_state(data, offset); - offset += apu.restore_state(data, offset); - return offset; -} -void Machine::serialize_state(std::vector& result) const -{ - cpu.serialize_state(result); - memory.serialize_state(result); - io.serialize_state(result); - gpu.serialize_state(result); - apu.serialize_state(result); -} - -void Machine::break_now() { cpu.break_now(); } -bool Machine::is_breaking() const noexcept { return cpu.is_breaking(); } - -void Machine::undefined() -{ - if (this->stop_when_undefined) - { - printf("*** An undefined operation happened\n"); - cpu.break_now(); - } -} -} // namespace gbc diff --git a/components/libgbc/src/mbc.cpp b/components/libgbc/src/mbc.cpp deleted file mode 100644 index a2db73c8..00000000 --- a/components/libgbc/src/mbc.cpp +++ /dev/null @@ -1,279 +0,0 @@ -#include "mbc.hpp" - -#include "machine.hpp" -#include "memory.hpp" - -#include "mbc1m.hpp" -#include "mbc3.hpp" -#include "mbc5.hpp" - -namespace gbc -{ -MBC::MBC(Memory& m, const std::string_view rom) - : m_memory(m), m_rom(rom) {} - -void MBC::init() -{ - this->m_ram.at(0x100) = 0x1; - this->m_ram.at(0x101) = 0x3; - this->m_ram.at(0x102) = 0x5; - this->m_ram.at(0x103) = 0x7; - this->m_ram.at(0x104) = 0x9; - // test ROMs are just instruction arrays - if (m_rom.size() < 0x150) return; - // parse ROM header - switch (m_memory.read8(0x147)) - { - case 0x0: - case 0x1: // MBC 1 - case 0x2: - case 0x3: - this->m_state.version = 1; - break; - case 0x5: - case 0x6: - this->m_state.version = 2; - assert(0 && "MBC2 is a weirdo!"); - break; - case 0x0F: - case 0x10: // MBC 3 - case 0x12: - case 0x13: - this->m_state.version = 3; - break; - case 0x19: - case 0x1A: // MBC 5 - case 0x1B: - case 0x1C: - this->m_state.version = 5; - this->m_state.rumble = false; - break; - case 0x1D: - case 0x1E: - this->m_state.version = 5; - this->m_state.rumble = true; - break; - default: - assert(0 && "Unknown cartridge type"); - } - // printf("MBC version %u Rumble: %d\n", this->m_state.version, this->m_state.rumble); - switch (m_memory.read8(0x149)) - { - case 0x0: - m_state.ram_banks = 0; - m_state.ram_bank_size = 0; - break; - case 0x1: // 2kb - m_state.ram_banks = 1; - m_state.ram_bank_size = 2048; - break; - case 0x2: // 8kb - m_state.ram_banks = 1; - m_state.ram_bank_size = 8192; - break; - case 0x3: // 32kb - m_state.ram_banks = 4; - m_state.ram_bank_size = 32768; - break; - case 0x4: // 128kb - m_state.ram_banks = 16; - m_state.ram_bank_size = 0x20000; - break; - case 0x5: // 64kb - m_state.ram_banks = 8; - m_state.ram_bank_size = 0x10000; - break; - } - // printf("RAM bank size: 0x%05x\n", m_state.ram_bank_size); - this->m_state.wram_size = 0x8000; - // printf("Work RAM bank size: 0x%04x\n", m_state.wram_size); -} - -uint8_t MBC::read(uint16_t addr) -{ - switch (addr & 0xF000) - { - case 0xA000: - case 0xB000: - if (this->ram_enabled()) - { - if (this->m_state.rtc_enabled == false) - { - addr -= RAMbankX.first; - addr |= this->m_state.ram_bank_offset; - if (addr < this->m_state.ram_bank_size) return this->m_ram.at(addr); - return 0xff; // small 2kb RAM banks - } - else - { - return 0xff; // TODO: Read from RTC register - } - } - else - { - return 0xff; - } - case 0xC000: - return this->m_state.wram.at(addr - WRAM_0.first); - case 0xD000: - return m_state.wram.at(m_state.wram_offset + addr - WRAM_bX.first); - case 0xE000: // echo RAM - case 0xF000: - return this->read(addr - 0x2000); - } - printf("* Invalid MBC read: 0x%04x\n", addr); - return 0xff; -} - -void MBC::write(uint16_t addr, uint8_t value) -{ - switch (addr & 0xF000) - { - case 0x0000: - case 0x1000: - // RAM enable - if (m_state.version == 2) - this->m_state.ram_enabled = value != 0; - else - this->m_state.ram_enabled = ((value & 0xF) == 0xA); - if (UNLIKELY(verbose_banking())) { - printf("* External RAM enabled: %d\n", this->m_state.ram_enabled); - } - return; - case 0x2000: - case 0x3000: - case 0x4000: - case 0x5000: - case 0x6000: - case 0x7000: - // MBC control ranges - switch (this->m_state.version) - { - case 5: - this->write_MBC5(addr, value); - break; - case 3: - this->write_MBC3(addr, value); - break; - case 1: - this->write_MBC1M(addr, value); - break; - case 0: - break; // no MBC - default: - assert(0 && "Unimplemented MBC version"); - } - return; - case 0xA000: - case 0xB000: - if (this->ram_enabled()) - { - if (this->m_state.rtc_enabled == false) - { - addr -= RAMbankX.first; - addr |= this->m_state.ram_bank_offset; - if (addr < this->m_state.ram_bank_size) { this->m_ram.at(addr) = value; } - } - else - { - // TODO: Write to RTC register - } - } - return; - case 0xC000: // WRAM bank 0 - this->m_state.wram.at(addr - WRAM_0.first) = value; - return; - case 0xD000: // WRAM bank X - this->m_state.wram.at(m_state.wram_offset + addr - WRAM_bX.first) = value; - return; - case 0xE000: // Echo RAM - case 0xF000: - this->write(addr - 0x2000, value); - return; - } - printf("* Invalid MBC write: 0x%04x => 0x%02x\n", addr, value); - assert(0); -} - -void MBC::set_rombank(int reg) -{ - const int rom_banks = m_rom.size() / rombank_size(); - reg &= (rom_banks - 1); - - // cant select bank 0 - const int offset = reg * rombank_size(); - if (UNLIKELY(verbose_banking())) { - printf("Selecting ROM bank 0x%02x offset %#x max %#zx\n", reg, offset, m_rom.size()); - } - if (UNLIKELY((offset + rombank_size()) > m_rom.size())) - { - printf("Invalid ROM bank 0x%02x offset %#x max %#zx\n", reg, offset, m_rom.size()); - this->m_memory.machine().break_now(); - return; - } - this->m_state.rom_bank_offset = offset; -} -void MBC::set_rambank(int reg) -{ - if (this->m_state.ram_banks >= 0) - { - // NOTE: we have to remove bits here - reg &= (this->m_state.ram_banks - 1); - } - const int offset = reg * rambank_size(); - if (UNLIKELY(verbose_banking())) { - printf("Selecting RAM bank 0x%02x offset %#x max %#lx\n", reg, offset, - m_state.ram_bank_size); - } - this->m_state.ram_bank_offset = offset; -} -void MBC::set_wrambank(int reg) -{ - const int offset = reg * wrambank_size(); - if (UNLIKELY(verbose_banking())) { - printf("Selecting WRAM bank 0x%02x offset %#x max %#x\n", reg, offset, m_state.wram_size); - } - if (UNLIKELY((offset + wrambank_size()) > m_state.wram_size)) - { - printf("Invalid Work RAM bank 0x%02x offset %#x\n", reg, offset); - this->m_memory.machine().break_now(); - return; - } - this->m_state.wram_offset = offset; -} -void MBC::set_mode(int mode) -{ - if (UNLIKELY(verbose_banking())) { - printf("Mode select: 0x%02x\n", this->m_state.mode_select); - } - this->m_state.mode_select = mode & 0x1; - // for MBC we have to reset the upper bits when going into RAM mode - if (this->m_state.version == 1 && this->m_state.mode_select == 1) - { - // reset ROM bank upper bits when going into RAM mode - if (this->m_state.rom_bank_reg & 0x60) - { - this->m_state.rom_bank_reg &= 0x1F; - this->set_rombank(this->m_state.rom_bank_reg); - } - } -} - -bool MBC::verbose_banking() const noexcept { return m_memory.machine().verbose_banking; } - -// serialization -int MBC::restore_state(const std::vector& data, int off) -{ - // copy state first - this->m_state = *(state_t*) &data.at(off); - off += sizeof(state_t); - // then copy RAM by size - std::copy(&data.at(off), &data.at(off) + m_state.ram_bank_size, m_ram.begin()); - return sizeof(state_t) + m_state.ram_bank_size; -} -void MBC::serialize_state(std::vector& res) const -{ - res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); - res.insert(res.end(), m_ram.begin(), m_ram.begin() + m_state.ram_bank_size); -} -} // namespace gbc diff --git a/components/libgbc/src/memory.cpp b/components/libgbc/src/memory.cpp deleted file mode 100644 index bc3965aa..00000000 --- a/components/libgbc/src/memory.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include "memory.hpp" -#include "machine.hpp" - -namespace gbc -{ -Memory::Memory(Machine& mach, const std::string_view rom) - : m_machine(mach), m_rom(rom), m_mbc{*this, rom} -{ - assert(this->rom_valid()); - this->disable_bootrom(); - m_mbc.init(); -} -void Memory::reset() -{ - // this->disable_bootrom(); - // m_mbc.reset(); -} -bool Memory::rom_valid() const noexcept -{ - // TODO: implement me - return true; -} -void Memory::disable_bootrom() { m_state.bootrom_enabled = false; } - -void Memory::set_wram_bank(uint8_t bank) { this->m_mbc.set_wrambank(bank); } - -uint8_t Memory::read8(uint16_t address) -{ - if (UNLIKELY(!m_read_breakpoints.empty() && !m_is_busy)) - { - this->m_is_busy = true; - for (auto& func : m_read_breakpoints) { func(*this, address, 0x0); } - this->m_is_busy = false; - } - switch (address & 0xF000) - { - case 0x0000: - case 0x1000: - case 0x2000: - case 0x3000: - return m_rom[address]; - case 0x4000: - case 0x5000: - case 0x6000: - case 0x7000: - address -= 0x4000; - return m_rom[m_mbc.rombank_offset() | address]; - case 0x8000: - case 0x9000: - // cant read from Video RAM when working on scanline - if (UNLIKELY(machine().gpu.get_mode() != 3)) - { - const uint16_t offset = machine().gpu.video_offset(); - return m_state.video_ram.at(offset + address - VideoRAM.first); - } - return 0xff; - case 0xA000: - case 0xB000: - return m_mbc.read(address); - case 0xC000: - case 0xD000: - return m_mbc.read(address); - case 0xE000: // echo RAM - return m_mbc.read(address); - case 0xF000: - if (this->is_within(address, EchoRAM)) { return m_mbc.read(address); } - else if (this->is_within(address, OAM_RAM)) - { - // TODO: return 0xff when rendering? - if (!machine().io.dma_active()) return m_state.oam_ram.at(address - OAM_RAM.first); - return 0xff; - } - else if (this->is_within(address, IO_Ports)) - { - return machine().io.read_io(address); - } - else if (this->is_within(address, ZRAM)) - { - return m_state.zram.at(address - ZRAM.first); - } - else if (address == InterruptEn) - { - return machine().io.read_io(address); - } - } - printf(">>> Invalid memory read at 0x%04x\n", address); - return 0xff; -} - -void Memory::write8(uint16_t address, uint8_t value) -{ - if (UNLIKELY(!m_write_breakpoints.empty() && !m_is_busy)) - { - this->m_is_busy = true; - for (auto& func : m_write_breakpoints) { func(*this, address, value); } - this->m_is_busy = false; - } - switch (address & 0xF000) - { - case 0x0000: - case 0x1000: - case 0x2000: - case 0x3000: - case 0x4000: - case 0x5000: - case 0x6000: - case 0x7000: - m_mbc.write(address, value); - return; - case 0x8000: - case 0x9000: - if (machine().gpu.get_mode() != 3) - { - const uint16_t offset = machine().gpu.video_offset(); - m_state.video_ram.at(offset + address - VideoRAM.first) = value; - } - return; - case 0xA000: - case 0xB000: - m_mbc.write(address, value); - return; - case 0xC000: - case 0xD000: - m_mbc.write(address, value); - return; - case 0xE000: // echo RAM - m_mbc.write(address, value); - return; - case 0xF000: - if (this->is_within(address, EchoRAM)) - { - m_mbc.write(address, value); - return; - } - else if (this->is_within(address, OAM_RAM)) - { - this->m_state.oam_ram.at(address - OAM_RAM.first) = value; - return; - } - else if (this->is_within(address, IO_Ports)) - { - machine().io.write_io(address, value); - return; - } - else if (this->is_within(address, ZRAM)) - { - this->m_state.zram.at(address - ZRAM.first) = value; - return; - } - else if (address == InterruptEn) - { - machine().io.write_io(address, value); - return; - } - } - printf(">>> Invalid memory write at 0x%04x, value 0x%x\n", address, value); -} - -void Memory::do_switch_speed() -{ - auto& reg = machine().io.reg(IO::REG_KEY1); - if (this->double_speed()) - { - this->m_state.speed_factor = 1; - reg = 0x0; - } - else - { - this->m_state.speed_factor = 2; - reg = 0x80; - } -} - -std::string Memory::explain(const uint16_t addr) const -{ - switch (addr & 0xF000) - { - case 0x0000: - case 0x1000: - case 0x2000: - case 0x3000: - return "Program 0"; - case 0x4000: - case 0x5000: - case 0x6000: - case 0x7000: - return "Program " + std::to_string(m_mbc.rombank_offset() / 0x4000); - case 0x8000: - case 0x9000: - { - const uint16_t offset = machine().gpu.video_offset(); - return "VideoRAM " + std::to_string(offset / 0x2000); - } - case 0xA000: - case 0xB000: - return "ExtRAM"; - case 0xC000: - case 0xD000: - return "WorkRAM"; - case 0xE000: - return "EchoRAM"; - case 0xF000: - if (this->is_within(addr, EchoRAM)) return "EchoRAM"; - if (this->is_within(addr, OAM_RAM)) return "OAM RAM"; - if (this->is_within(addr, ZRAM)) return "ZRAM"; - return "I/O port"; - } - return "Unknown"; -} - -// serialization -int Memory::restore_state(const std::vector& data, int off) -{ - this->m_state = *(state_t*) &data.at(off); - off += sizeof(state_t); - // also restore MBC - return sizeof(state_t) + this->m_mbc.restore_state(data, off); -} -void Memory::serialize_state(std::vector& res) const -{ - res.insert(res.end(), (uint8_t*) &m_state, (uint8_t*) &m_state + sizeof(m_state)); - // also serialize MBC - this->m_mbc.serialize_state(res); -} -} // namespace gbc diff --git a/main/gameboy.hpp b/main/gameboy.hpp index 217d53ea..b5a288ab 100644 --- a/main/gameboy.hpp +++ b/main/gameboy.hpp @@ -7,114 +7,146 @@ #include "input.h" #include "st7789.hpp" +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + static const size_t gameboy_screen_width = 160; -void vblank_callback() { - // fmt::print("VBLANK\n"); +uint16_t* displayBuffer[2]; +struct fb fb; +struct pcm pcm; +uint8_t currentBuffer; +uint16_t* framebuffer; +int frame = 0; +uint elapsedTime = 0; + +#define AUDIO_SAMPLE_RATE (16000) + +int32_t* audioBuffer[2]; +volatile uint8_t currentAudioBuffer = 0; +volatile uint16_t currentAudioSampleCount; +volatile int32_t* currentAudioBufferPtr; + +extern "C" void die(char *fmt, ...) { + fmt::print("DIE!\n"); } -static const int audio_frame_size = 2*256; -static int16_t audio_frame[audio_frame_size]; -static int audio_frame_index = 0; -void play_audio_sample(int16_t l, int16_t r) { - // fmt::print("L: {} R: {}\n", l, r); - audio_frame[audio_frame_index++] = l; - audio_frame[audio_frame_index++] = r; - if (audio_frame_index == audio_frame_size) { - // audio_play_frame((uint8_t*)audio_frame, audio_frame_size); - audio_frame_index = 0; +void run_to_vblank() +{ + /* FRAME BEGIN */ + + /* FIXME: djudging by the time specified this was intended + to emulate through vblank phase which is handled at the + end of the loop. */ + cpu_emulate(2280); + + /* FIXME: R_LY >= 0; comparsion to zero can also be removed + altogether, R_LY is always 0 at this point */ + while (R_LY > 0 && R_LY < 144) + { + emu_step(); } -} -#ifdef USE_GAMEBOY_GAMEBOYCORE -#include "gameboycore/gameboycore.h" - -static std::shared_ptr core; -void render_scanline(const gb::GPU::Scanline& scanline, int line) { - // fmt::print("Line {}\n", line); - // scanline is just std::array, where pixel is uint8_t r,g,b - // make array of lv_color_t - static const size_t num_lines_to_flush = 48; - static size_t num_lines = 0; - static auto color_data = get_vram0(); - size_t index = num_lines * gameboy_screen_width; - for (auto &pixel : scanline) { - color_data[index++] = make_color(pixel.r, pixel.g, pixel.b); + /* VBLANK BEGIN */ + + //vid_end(); + if ((frame % 2) == 0) { + auto _frame = displayBuffer[currentBuffer]; + for (int y=0; y<144; y+=48) { + lcd_write_frame(0, y, 160, 48, (uint8_t*)&_frame[y*160]); + } + + // swap buffers + currentBuffer = currentBuffer ? 0 : 1; + framebuffer = displayBuffer[currentBuffer]; + + fb.ptr = (uint8_t*)framebuffer; } - num_lines++; - if (num_lines == num_lines_to_flush) { - lcd_write_frame(0, line - num_lines, gameboy_screen_width, num_lines, (const uint8_t*)&color_data[0]); - num_lines = 0; + + rtc_tick(); + + sound_mix(); + + if (pcm.pos > 100) { + currentAudioBufferPtr = audioBuffer[currentAudioBuffer]; + currentAudioSampleCount = pcm.pos; + + // void* tempPtr = 0x1234; + audio_play_frame((uint8_t*)currentAudioBufferPtr, currentAudioSampleCount * 2); + + // Swap buffers + currentAudioBuffer = currentAudioBuffer ? 0 : 1; + pcm.buf = (int16_t*)audioBuffer[currentAudioBuffer]; + pcm.pos = 0; } -} -#endif -#ifdef USE_GAMEBOY_GNUBOY -extern "C" { -#include "gnuboy.h" -#include "loader.h" -} -#endif -#ifdef USE_GAMEBOY_GAMEBOY -extern "C" { -namespace gbc{ -#include "gameboy/timer.h" -#include "gameboy/rom.h" -#include "gameboy/mem.h" -#include "gameboy/cpu.h" -#include "gameboy/lcd.h" + + if (!(R_LCDC & 0x80)) { + /* LCDC operation stopped */ + /* FIXME: djudging by the time specified, this is + intended to emulate through visible line scanning + phase, even though we are already at vblank here */ + cpu_emulate(32832); + } + + while (R_LY > 0) { + /* Step through vblank phase */ + emu_step(); } + ++frame; } -#endif -#ifdef USE_GAMEBOY_LIBGBC -#include "machine.hpp" -std::shared_ptr gbc_machine; -#endif void init_gameboy(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size) { // WIDTH = gameboy_screen_width, so 320-WIDTH is gameboy_screen_width espp::St7789::set_offset((320-gameboy_screen_width) / 2, (240-144) / 2); + static bool initialized = false; + + // Note: Magic number obtained by adjusting until audio buffer overflows stop. + const int audioBufferLength = AUDIO_SAMPLE_RATE / 5 + 1; // / 10 + //printf("CHECKPOINT AUDIO: HEAP:0x%x - allocating 0x%x\n", esp_get_free_heap_size(), audioBufferLength * sizeof(int16_t) * 2 * 2); + const int AUDIO_BUFFER_SIZE = audioBufferLength * sizeof(int16_t) * 2; + + if (!initialized) { + displayBuffer[0] = (uint16_t*)heap_caps_malloc(160*144*2, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + displayBuffer[1] = (uint16_t*)heap_caps_malloc(160*144*2, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + audioBuffer[0] = (int32_t*)heap_caps_malloc(AUDIO_BUFFER_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + audioBuffer[1] = (int32_t*)heap_caps_malloc(AUDIO_BUFFER_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + } + memset(&fb, 0, sizeof(fb)); + fb.w = 160; + fb.h = 144; + fb.pelsize = 2; + fb.pitch = fb.w * fb.pelsize; + fb.indexed = 0; + fb.ptr = (uint8_t*)displayBuffer[0]; + fb.enabled = 1; + fb.dirty = 0; + + + // pcm.len = count of 16bit samples (x2 for stereo) + memset(&pcm, 0, sizeof(pcm)); + pcm.hz = AUDIO_SAMPLE_RATE; + pcm.stereo = 1; + pcm.len = /*pcm.hz / 2*/ audioBufferLength; + pcm.buf = (int16_t*)audioBuffer[0]; + pcm.pos = 0; + + sound_reset(); -#ifdef USE_GAMEBOY_GAMEBOYCORE - fmt::print("GAMEBOY enabled: GAMEBOYCORE, loading {} ({} bytes)\n", rom_filename, rom_data_size); - - // Create an instance of the gameboy emulator core - core = std::make_shared(); - // Set callbacks for video and audio - core->setScanlineCallback(render_scanline); - core->setVBlankCallback(vblank_callback); - core->setAudioSampleCallback(play_audio_sample); - // now load the rom - fmt::print("Opening rom {}!\n", rom_filename); - core->loadROM(romdata, rom_data_size); -#endif -#ifdef USE_GAMEBOY_GNUBOY fmt::print("GAMEBOY enabled: GNUBOY\n"); - loader_init_raw(romdata, rom_data_size); + loader_init(romdata, rom_data_size); emu_reset(); -#endif -#ifdef USE_GAMEBOY_GAMEBOY - // gbc::rom_load(rom_filename.c_str()); - gbc::rom_init(romdata, rom_data_size); - gbc::gb_lcd_init(); - gbc::mem_init(); - gbc::cpu_init(); -#endif -#ifdef USE_GAMEBOY_LIBGBC - gbc_machine = std::make_shared(romdata, rom_data_size); - - // trap on V-blank interrupts - gbc_machine->set_handler(gbc::Machine::VBLANK, - [] (gbc::Machine& machine, gbc::interrupt_t&) - { - const auto& pixels = machine.gpu.pixels(); - const int num_lines = 24; - const int frame_offset = num_lines * gbc::GPU::SCREEN_W; - for (int l=0; lemulateFrame(); -#endif -#ifdef USE_GAMEBOY_GNUBOY - emu_run(); -#endif -#ifdef USE_GAMEBOY_GAMEBOY - static int r = 0; - int now; - if(!gbc::cpu_cycle()) - return; - now = gbc::cpu_get_cycles(); - while(now != r) { - for(int i = 0; i < 4; i++) { - if(!gbc::lcd_cycle()) - return; - } - r++; - } - gbc::timer_cycle(); - r = now; -#endif -#ifdef USE_GAMEBOY_LIBGBC - gbc_machine->simulate_one_frame(); -#endif + run_to_vblank(); } void deinit_gameboy() { fmt::print("quitting gameboy emulation!\n"); -#ifdef USE_GAMEBOY_GAMEBOYCORE - core.reset(); -#endif -#ifdef USE_GAMEBOY_GNUBOY -#endif -#ifdef USE_GAMEBOY_LIBGBC - gbc_machine.reset(); -#endif + loader_unload(); } From 64f3abf664e1847d59a815ee85adf47fdf69d0bb Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Mon, 7 Nov 2022 10:46:55 -0600 Subject: [PATCH 8/9] update cmake lists. --- CMakeLists.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22e2f67f..af74bc0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,10 +13,7 @@ set(EXTRA_COMPONENT_DIRS # set(NES_COMPONENTS "nofrendo nofrendo-esp32") ### GBC ### -set(GBC_COMPONENTS "libgbc") -# set(GBC_COMPONENTS "gameboy") -# set(GBC_COMPONENTS "gameboycore") -# set(GBC_COMPONENTS "gnuboy") +set(GBC_COMPONENTS "gnuboy") ### SMS ### # set(SMS_COMPONENTS "smsplus") From 88fd58a4d179cb7a811057e72eae7270c57f439f Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Mon, 7 Nov 2022 15:21:48 -0600 Subject: [PATCH 9/9] updated so that nes and gameboy emulators can coexist together; they can now also share the same framebuffer memory (which is nice) and that framebuffer is allocated in SPIRAM. Fixed the nes cpu to not conflict with gb cpu declaration. fixed NES input to not invert the logic of the button presses incorrectly. updated so that all emulators can use the same audio buffers. added api for setting the audio output volume. removed vid_init (unused) from gnuboy since it conflicts with nofrendo. Moved gameboy.hpp and nes.hpp implementations into gameboy.cpp and nes.cpp files for cleanlieness. Moved touch input out of nofrendo and into nes.cpp. --- CMakeLists.txt | 2 +- components/box-emu-hal/include/i2s_audio.h | 2 + components/box-emu-hal/include/spi_lcd.h | 1 + components/box-emu-hal/src/i2s_audio.cpp | 14 ++ components/box-emu-hal/src/spi_lcd.cpp | 11 +- components/gnuboy/include/gnuboy/gnuboy.h | 2 +- components/nofrendo-esp32/video_audio.c | 65 ++----- components/nofrendo/bitmap.c | 22 ++- components/nofrendo/cpu/nes6502.c | 106 +++++------ components/nofrendo/nes/nes.c | 69 +++++--- components/nofrendo/nes/nes.h | 2 + components/nofrendo/nes/nes_rom.c | 2 +- components/nofrendo/nes/nes_rom.h | 2 +- main/gameboy.cpp | 193 +++++++++++++++++++++ main/gameboy.hpp | 172 +----------------- main/main.cpp | 15 +- main/nes.cpp | 60 +++++++ main/nes.hpp | 54 +----- 18 files changed, 420 insertions(+), 374 deletions(-) create mode 100644 main/gameboy.cpp create mode 100644 main/nes.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index af74bc0c..45c3379b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set(EXTRA_COMPONENT_DIRS ) ### NES ### -# set(NES_COMPONENTS "nofrendo nofrendo-esp32") +set(NES_COMPONENTS "nofrendo nofrendo-esp32") ### GBC ### set(GBC_COMPONENTS "gnuboy") diff --git a/components/box-emu-hal/include/i2s_audio.h b/components/box-emu-hal/include/i2s_audio.h index a48ad1e2..ee074fb3 100644 --- a/components/box-emu-hal/include/i2s_audio.h +++ b/components/box-emu-hal/include/i2s_audio.h @@ -7,7 +7,9 @@ extern "C" { #endif void audio_init(); +int16_t* get_audio_buffer(); void audio_play_frame(uint8_t *data, uint32_t num_bytes); +void set_audio_volume(int percent); #ifdef __cplusplus } diff --git a/components/box-emu-hal/include/spi_lcd.h b/components/box-emu-hal/include/spi_lcd.h index 1d14c426..7050ab40 100644 --- a/components/box-emu-hal/include/spi_lcd.h +++ b/components/box-emu-hal/include/spi_lcd.h @@ -33,6 +33,7 @@ uint16_t make_color(uint8_t r, uint8_t g, uint8_t b); void set_pixel(const uint16_t x, const uint16_t y, const uint16_t color); uint16_t* get_vram0(); uint16_t* get_vram1(); +uint8_t* get_frame_buffer(); void delay_us(size_t num_us); uint16_t reorder_color(uint16_t color); void lcd_set_drawing_frame(const uint16_t xs, const uint16_t ys, const uint16_t width, const uint16_t height); diff --git a/components/box-emu-hal/src/i2s_audio.cpp b/components/box-emu-hal/src/i2s_audio.cpp index a8fcafe9..d43d7918 100644 --- a/components/box-emu-hal/src/i2s_audio.cpp +++ b/components/box-emu-hal/src/i2s_audio.cpp @@ -46,6 +46,17 @@ static i2s_chan_handle_t tx_handle = NULL; static i2s_chan_handle_t rx_handle = NULL; +static const int AUDIO_BUFFER_SIZE = EXAMPLE_SAMPLE_RATE / 3 + 1; +static int16_t *audio_buffer; + +int16_t *get_audio_buffer() { + return audio_buffer; +} + +void set_audio_volume(int percent) { + es8311_codec_set_voice_volume(percent); +} + static esp_err_t i2s_driver_init(void) { printf("initializing i2s driver...\n"); @@ -178,6 +189,9 @@ void audio_init() { i2c_driver_init(); es7210_init_default(); es8311_init_default(); + + audio_buffer = (int16_t*)heap_caps_malloc(AUDIO_BUFFER_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + initialized = true; } diff --git a/components/box-emu-hal/src/spi_lcd.cpp b/components/box-emu-hal/src/spi_lcd.cpp index 18ef0e79..f9ab8e5b 100644 --- a/components/box-emu-hal/src/spi_lcd.cpp +++ b/components/box-emu-hal/src/spi_lcd.cpp @@ -13,6 +13,9 @@ static constexpr size_t display_height = 240; static constexpr size_t pixel_buffer_size = display_width*NUM_ROWS_IN_FRAME_BUFFER; std::shared_ptr display; +static constexpr size_t frame_buffer_size = (256 * 240 * 2); +static uint8_t *frame_buffer; + //This function is called (in irq context!) just before a transmission starts. It will //set the D/C line to the value indicated in the user field. void lcd_spi_pre_transfer_callback(spi_transaction_t *t) @@ -92,6 +95,10 @@ extern "C" uint16_t* get_vram1() { return display->vram1(); } +extern "C" uint8_t* get_frame_buffer() { + return frame_buffer; +} + extern "C" void delay_us(size_t num_us) { using namespace std::chrono_literals; std::this_thread::sleep_for(1us * num_us); @@ -147,7 +154,7 @@ extern "C" void lcd_init() { .mode=0, //SPI mode 0 .clock_speed_hz=60*1000*1000, //Clock out at 60 MHz .spics_io_num=GPIO_NUM_5, //CS pin - .queue_size=spi_queue_size, //We want to be able to queue 7 transactions at a time + .queue_size=spi_queue_size, //We want to be able to queue 7 transactions at a time .pre_cb=lcd_spi_pre_transfer_callback, .post_cb=lcd_spi_post_transfer_callback, }; @@ -182,5 +189,7 @@ extern "C" void lcd_init() { .rotation = espp::Display::Rotation::LANDSCAPE, .software_rotation_enabled = true, }); + + frame_buffer = (uint8_t*)heap_caps_malloc(frame_buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); initialized = true; } diff --git a/components/gnuboy/include/gnuboy/gnuboy.h b/components/gnuboy/include/gnuboy/gnuboy.h index 641ddb6c..aed9a8f6 100644 --- a/components/gnuboy/include/gnuboy/gnuboy.h +++ b/components/gnuboy/include/gnuboy/gnuboy.h @@ -16,7 +16,7 @@ void ev_poll(); void vid_close(); void vid_preinit(); -void vid_init(); +// void vid_init(); // NOT NEEDED void vid_begin(); void vid_end(); void vid_setpal(int i, int r, int g, int b); diff --git a/components/nofrendo-esp32/video_audio.c b/components/nofrendo-esp32/video_audio.c index 6a663691..eefd0663 100644 --- a/components/nofrendo-esp32/video_audio.c +++ b/components/nofrendo-esp32/video_audio.c @@ -16,7 +16,6 @@ #include #include #include -// #include "driver/rtc_io.h" //Nes stuff wants to define this as well... #undef false @@ -95,13 +94,9 @@ static void osd_stopsound(void) static int osd_init_sound(void) { - audio_frame=malloc(4*DEFAULT_FRAGSIZE); - - // odroid_audio_init(odroid_settings_AudioSink_get(), DEFAULT_SAMPLERATE); + audio_frame=get_audio_buffer(); audio_init(); - audio_callback = NULL; - return 0; } @@ -123,7 +118,6 @@ static void clear(uint8 color); static bitmap_t *lock_write(void); static void free_write(int num_dirties, rect_t *dirty_rects); static void custom_blit(bitmap_t *bmp, int num_dirties, rect_t *dirty_rects); -static char fb[1]; //dummy QueueHandle_t vidQueue; @@ -141,21 +135,6 @@ viddriver_t sdlDriver = false /* invalidate flag */ }; - // GB -#define GAMEBOY_WIDTH (160) -#define GAMEBOY_HEIGHT (144) - - -// SMS -#define SMS_WIDTH (256) -#define SMS_HEIGHT (192) - -#define GAMEGEAR_WIDTH (160) -#define GAMEGEAR_HEIGHT (144) - -#define PIXEL_MASK (0x1F) - - // NES #define NES_GAME_WIDTH (256) #define NES_GAME_HEIGHT (224) /* NES_VISIBLE_HEIGHT */ @@ -173,6 +152,7 @@ void ili9341_write_frame_nes(const uint8_t* buffer, uint16_t* myPalette, uint8_t // width/height are just the NES_GAME_WIDTH/HEIGHT lcd_set_drawing_frame(0, 0, NES_GAME_WIDTH-1, NES_GAME_HEIGHT-1); + // uint16_t* line_buffer = get_frame_buffer(); uint16_t* line_buffer = get_vram0(); for (y = 0; y < NES_GAME_HEIGHT; y += LINE_COUNT) { int linesWritten = 0; @@ -254,7 +234,7 @@ static void clear(uint8 color) static bitmap_t *lock_write(void) { // SDL_LockSurface(mySurface); - myBitmap = bmp_createhw((uint8*)fb, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_WIDTH*2); + myBitmap = bmp_createhw((uint8*)get_frame_buffer(), DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_WIDTH*2); return myBitmap; } @@ -264,8 +244,9 @@ static void free_write(int num_dirties, rect_t *dirty_rects) bmp_destroy(&myBitmap); } -static uint8_t lcdfb[256 * 224]; +// static uint8_t lcdfb[256 * 224]; static void custom_blit(bitmap_t *bmp, int num_dirties, rect_t *dirty_rects) { + uint8_t *lcdfb = get_frame_buffer(); if (bmp->line[0] != NULL) { memcpy(lcdfb, bmp->line[0], 256 * 224); @@ -349,36 +330,21 @@ static int ConvertJoystickInput() static struct InputState state; get_input_state(&state); - // A - if (state.a) + if (!state.a) result |= (1<<13); - - // B - if (state.b) + if (!state.b) result |= (1 << 14); - - // select - if (state.select) + if (!state.select) result |= (1 << 0); - - // start - if (state.start) + if (!state.start) result |= (1 << 3); - - // left - if (state.left) + if (!state.right) result |= (1 << 5); - - // right - if (state.right) + if (!state.left) result |= (1 << 7); - - // down - if (state.down) + if (!state.up) result |= (1 << 4); - - // up - if (state.up) + if (!state.down) result |= (1 << 6); return result; @@ -395,10 +361,6 @@ void osd_getinput(void) 0,0,0,0,event_soft_reset,event_joypad1_a,event_joypad1_b,event_hard_reset }; static int oldb=0xffff; - // we have to call touchpad_read to determine if the user needs to quit... - uint8_t _num_touches, _btn_state; - uint16_t _x,_y; - touchpad_read(&_num_touches, &_x, &_y, &_btn_state); if (user_quit()) { nes_poweroff(); } @@ -407,7 +369,6 @@ void osd_getinput(void) int x; oldb=b; event_t evh; -// printf("Input: %x\n", b); for (x=0; x<16; x++) { if (chg&1) { evh=event_get(ev[x]); diff --git a/components/nofrendo/bitmap.c b/components/nofrendo/bitmap.c index 4b064a24..a2ad385c 100644 --- a/components/nofrendo/bitmap.c +++ b/components/nofrendo/bitmap.c @@ -29,6 +29,9 @@ #include #include +#include "spi_lcd.h" +#include "esp_heap_caps.h" + void bmp_clear(const bitmap_t *bitmap, uint8 color) { memset(bitmap->data, color, bitmap->pitch * bitmap->height); @@ -77,25 +80,20 @@ static bitmap_t *_make_bitmap(uint8 *data_addr, bool hw, int width, /* Allocate and initialize a bitmap structure */ #define FRAME_BUFFER_LENGTH ((256 + (2 * 8)) * 240) -uint8 frameBuffer[FRAME_BUFFER_LENGTH]; +static uint8 *frameBuffer = NULL; // [FRAME_BUFFER_LENGTH]; bitmap_t *bmp_create(int width, int height, int overdraw) { - printf("bmp_create: width=%d, height=%d, overdraw=%d\n", width, height, overdraw); + printf("bmp_create: width=%d, height=%d, overdraw=%d\n", width, height, overdraw); uint8 *addr; int pitch; pitch = width + (overdraw * 2); /* left and right */ - //addr = _my_malloc((pitch * height) + 3); /* add max 32-bit aligned adjustment */ - //if (NULL == addr) - // return NULL; - - if (pitch * height > FRAME_BUFFER_LENGTH) - { - abort(); - } - - addr = frameBuffer; + // addr = get_frame_buffer(); + if (frameBuffer == NULL) { + frameBuffer = (uint8*)heap_caps_malloc(FRAME_BUFFER_LENGTH, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + } + addr = frameBuffer; return _make_bitmap(addr, false, width, height, width, overdraw); } diff --git a/components/nofrendo/cpu/nes6502.c b/components/nofrendo/cpu/nes6502.c index 7887dad3..5a7db41d 100644 --- a/components/nofrendo/cpu/nes6502.c +++ b/components/nofrendo/cpu/nes6502.c @@ -40,7 +40,7 @@ #define ADD_CYCLES(x) \ { \ remaining_cycles -= (x); \ - cpu.total_cycles += (x); \ + nes_cpu.total_cycles += (x); \ } /* @@ -554,9 +554,9 @@ { \ i_flag = 0; \ ADD_CYCLES(2); \ - if (cpu.int_pending && remaining_cycles > 0) \ + if (nes_cpu.int_pending && remaining_cycles > 0) \ { \ - cpu.int_pending = 0; \ + nes_cpu.int_pending = 0; \ IRQ_PROC(); \ ADD_CYCLES(INT_CYCLES); \ } \ @@ -686,8 +686,8 @@ #define JAM() \ { \ PC--; \ - cpu.jammed = true; \ - cpu.int_pending = 0; \ + nes_cpu.jammed = true; \ + nes_cpu.int_pending = 0; \ ADD_CYCLES(2); \ } #endif /* !NES6502_TESTOPS */ @@ -896,9 +896,9 @@ PC = PULL(); \ PC |= PULL() << 8; \ ADD_CYCLES(6); \ - if (0 == i_flag && cpu.int_pending && remaining_cycles > 0) \ + if (0 == i_flag && nes_cpu.int_pending && remaining_cycles > 0) \ { \ - cpu.int_pending = 0; \ + nes_cpu.int_pending = 0; \ IRQ_PROC(); \ ADD_CYCLES(INT_CYCLES); \ } \ @@ -1133,7 +1133,7 @@ /* internal CPU context */ -nes6502_context cpu; +nes6502_context nes_cpu; static int remaining_cycles = 0; /* so we can release timeslice */ /* memory region pointers */ static uint8 *ram = NULL, *stack = NULL; @@ -1164,7 +1164,7 @@ INLINE uint32 bank_readword(register uint32 address) ** be fetching a word across page boundaries, which only would ** make sense if the banks were physically consecutive. */ - return (uint32) (*(uint16 *)(cpu.mem_page[address >> NES6502_BANKSHIFT] + (address & NES6502_BANKMASK))); + return (uint32) (*(uint16 *)(nes_cpu.mem_page[address >> NES6502_BANKSHIFT] + (address & NES6502_BANKMASK))); } #else /* !HOST_LITTLE_ENDIAN */ @@ -1182,9 +1182,9 @@ INLINE uint32 zp_readword(register uint8 address) INLINE uint32 bank_readword(register uint32 address) { #ifdef TARGET_CPU_PPC - return __lhbrx(cpu.mem_page[address >> NES6502_BANKSHIFT], address & NES6502_BANKMASK); + return __lhbrx(nes_cpu.mem_page[address >> NES6502_BANKSHIFT], address & NES6502_BANKMASK); #else /* !TARGET_CPU_PPC */ - uint32 x = (uint32) *(uint16 *)(cpu.mem_page[address >> NES6502_BANKSHIFT] + (address & NES6502_BANKMASK)); + uint32 x = (uint32) *(uint16 *)(nes_cpu.mem_page[address >> NES6502_BANKSHIFT] + (address & NES6502_BANKMASK)); return (x << 8) | (x >> 8); #endif /* !TARGET_CPU_PPC */ } @@ -1193,12 +1193,12 @@ INLINE uint32 bank_readword(register uint32 address) INLINE uint8 bank_readbyte(register uint32 address) { - return cpu.mem_page[address >> NES6502_BANKSHIFT][address & NES6502_BANKMASK]; + return nes_cpu.mem_page[address >> NES6502_BANKSHIFT][address & NES6502_BANKMASK]; } INLINE void bank_writebyte(register uint32 address, register uint8 value) { - cpu.mem_page[address >> NES6502_BANKSHIFT][address & NES6502_BANKMASK] = value; + nes_cpu.mem_page[address >> NES6502_BANKSHIFT][address & NES6502_BANKMASK] = value; } /* read a byte of 6502 memory */ @@ -1220,7 +1220,7 @@ static uint8 mem_readbyte(uint32 address) /* check memory range handlers */ else { - for (mr = cpu.read_handler; mr->min_range != 0xFFFFFFFF; mr++) + for (mr = nes_cpu.read_handler; mr->min_range != 0xFFFFFFFF; mr++) { if (address >= mr->min_range && address <= mr->max_range) return mr->read_func(address); @@ -1245,7 +1245,7 @@ static void mem_writebyte(uint32 address, uint8 value) /* check memory range handlers */ else { - for (mw = cpu.write_handler; mw->min_range != 0xFFFFFFFF; mw++) + for (mw = nes_cpu.write_handler; mw->min_range != 0xFFFFFFFF; mw++) { if (address >= mw->min_range && address <= mw->max_range) { @@ -1266,16 +1266,16 @@ void nes6502_setcontext(nes6502_context *context) ASSERT(context); - cpu = *context; + nes_cpu = *context; /* set dead page for all pages not pointed at anything */ for (loop = 0; loop < NES6502_NUMBANKS; loop++) { - if (NULL == cpu.mem_page[loop]) - cpu.mem_page[loop] = null_page; + if (NULL == nes_cpu.mem_page[loop]) + nes_cpu.mem_page[loop] = null_page; } - ram = cpu.mem_page[0]; /* quick zero-page/RAM references */ + ram = nes_cpu.mem_page[0]; /* quick zero-page/RAM references */ stack = ram + STACK_OFFSET; } @@ -1286,7 +1286,7 @@ void nes6502_getcontext(nes6502_context *context) ASSERT(context); - *context = cpu; + *context = nes_cpu; /* reset dead pages to null */ for (loop = 0; loop < NES6502_NUMBANKS; loop++) @@ -1305,32 +1305,32 @@ uint8 nes6502_getbyte(uint32 address) /* get number of elapsed cycles */ uint32 nes6502_getcycles(bool reset_flag) { - uint32 cycles = cpu.total_cycles; + uint32 cycles = nes_cpu.total_cycles; if (reset_flag) - cpu.total_cycles = 0; + nes_cpu.total_cycles = 0; return cycles; } #define GET_GLOBAL_REGS() \ { \ - PC = cpu.pc_reg; \ - A = cpu.a_reg; \ - X = cpu.x_reg; \ - Y = cpu.y_reg; \ - SCATTER_FLAGS(cpu.p_reg); \ - S = cpu.s_reg; \ + PC = nes_cpu.pc_reg; \ + A = nes_cpu.a_reg; \ + X = nes_cpu.x_reg; \ + Y = nes_cpu.y_reg; \ + SCATTER_FLAGS(nes_cpu.p_reg); \ + S = nes_cpu.s_reg; \ } #define STORE_LOCAL_REGS() \ { \ - cpu.pc_reg = PC; \ - cpu.a_reg = A; \ - cpu.x_reg = X; \ - cpu.y_reg = Y; \ - cpu.p_reg = COMBINE_FLAGS(); \ - cpu.s_reg = S; \ + nes_cpu.pc_reg = PC; \ + nes_cpu.a_reg = A; \ + nes_cpu.x_reg = X; \ + nes_cpu.y_reg = Y; \ + nes_cpu.p_reg = COMBINE_FLAGS(); \ + nes_cpu.s_reg = S; \ } #define MIN(a,b) (((a) < (b)) ? (a) : (b)) @@ -1368,7 +1368,7 @@ uint32 nes6502_getcycles(bool reset_flag) */ int nes6502_execute(int timeslice_cycles) { - int old_cycles = cpu.total_cycles; + int old_cycles = nes_cpu.total_cycles; uint32 temp, addr; /* for macros */ uint8 btemp, baddr; /* for macros */ @@ -1427,18 +1427,18 @@ int nes6502_execute(int timeslice_cycles) GET_GLOBAL_REGS(); /* check for DMA cycle burning */ - if (cpu.burn_cycles && remaining_cycles > 0) + if (nes_cpu.burn_cycles && remaining_cycles > 0) { int burn_for; - burn_for = MIN(remaining_cycles, cpu.burn_cycles); + burn_for = MIN(remaining_cycles, nes_cpu.burn_cycles); ADD_CYCLES(burn_for); - cpu.burn_cycles -= burn_for; + nes_cpu.burn_cycles -= burn_for; } - if (0 == i_flag && cpu.int_pending && remaining_cycles > 0) + if (0 == i_flag && nes_cpu.int_pending && remaining_cycles > 0) { - cpu.int_pending = 0; + nes_cpu.int_pending = 0; IRQ_PROC(); ADD_CYCLES(INT_CYCLES); } @@ -2400,18 +2400,18 @@ int nes6502_execute(int timeslice_cycles) STORE_LOCAL_REGS(); /* Return our actual amount of executed cycles */ - return (cpu.total_cycles - old_cycles); + return (nes_cpu.total_cycles - old_cycles); } /* Issue a CPU Reset */ void nes6502_reset(void) { - cpu.p_reg = Z_FLAG | R_FLAG | I_FLAG; /* Reserved bit always 1 */ - cpu.int_pending = 0; /* No pending interrupts */ - cpu.int_latency = 0; /* No latent interrupts */ - cpu.pc_reg = bank_readword(RESET_VECTOR); /* Fetch reset vector */ - cpu.burn_cycles = RESET_CYCLES; - cpu.jammed = false; + nes_cpu.p_reg = Z_FLAG | R_FLAG | I_FLAG; /* Reserved bit always 1 */ + nes_cpu.int_pending = 0; /* No pending interrupts */ + nes_cpu.int_latency = 0; /* No latent interrupts */ + nes_cpu.pc_reg = bank_readword(RESET_VECTOR); /* Fetch reset vector */ + nes_cpu.burn_cycles = RESET_CYCLES; + nes_cpu.jammed = false; } /* following macro is used for below 2 functions */ @@ -2426,11 +2426,11 @@ void nes6502_nmi(void) { DECLARE_LOCAL_REGS - if (false == cpu.jammed) + if (false == nes_cpu.jammed) { GET_GLOBAL_REGS(); NMI_PROC(); - cpu.burn_cycles += INT_CYCLES; + nes_cpu.burn_cycles += INT_CYCLES; STORE_LOCAL_REGS(); } } @@ -2440,17 +2440,17 @@ void nes6502_irq(void) { DECLARE_LOCAL_REGS - if (false == cpu.jammed) + if (false == nes_cpu.jammed) { GET_GLOBAL_REGS(); if (0 == i_flag) { IRQ_PROC(); - cpu.burn_cycles += INT_CYCLES; + nes_cpu.burn_cycles += INT_CYCLES; } else { - cpu.int_pending = 1; + nes_cpu.int_pending = 1; } STORE_LOCAL_REGS(); } @@ -2459,7 +2459,7 @@ void nes6502_irq(void) /* Set dead cycle period */ void nes6502_burn(int cycles) { - cpu.burn_cycles += cycles; + nes_cpu.burn_cycles += cycles; } /* Release our timeslice */ diff --git a/components/nofrendo/nes/nes.c b/components/nofrendo/nes/nes.c index c50f5252..59f92344 100644 --- a/components/nofrendo/nes/nes.c +++ b/components/nofrendo/nes/nes.c @@ -364,18 +364,19 @@ extern bool forceConsoleReset; /* main emulation loop */ void nes_emulate(void) { + nes_prep_emulation(); + while (false == nes.poweroff) + { + nes_emulateframe(0); + } +} + +void nes_prep_emulation() { osd_setsound(nes.apu->process); nes.scanline_cycles = 0; nes.fiq_cycles = (int) NES_FIQ_PERIOD; - float totalElapsedTime = 0; - int frame = 0; - int skipFrame = 0; - - struct timeval tv_start; - struct timeval tv_stop; - for (int i = 0; i < 4; ++i) { nes_renderframe(1); @@ -388,36 +389,48 @@ void nes_emulate(void) { nes_reset(SOFT_RESET); } + nes_emulateframe(1); +} - while (false == nes.poweroff) - { - gettimeofday(&tv_start, NULL); +void nes_emulateframe(unsigned char reset) { + static float totalElapsedTime = 0; + static int frame = 0; + static int skipFrame = 0; + if (reset) { + frame = skipFrame = 0; + totalElapsedTime = 0; + } + + struct timeval tv_start; + struct timeval tv_stop; + + gettimeofday(&tv_start, NULL); - bool renderFrame = ((skipFrame % 2) == 0); + // if skipframe is 0 or 2 we don't render + bool renderFrame = ((skipFrame % 2) == 0); - nes_renderframe(renderFrame); - system_video(renderFrame); + nes_renderframe(renderFrame); + system_video(renderFrame); - if (skipFrame % 7 == 0) ++skipFrame; - ++skipFrame; + // if skipframe is 7 or 0, we increment + if (skipFrame % 7 == 0) ++skipFrame; + ++skipFrame; - do_audio_frame(); + do_audio_frame(); - gettimeofday(&tv_stop, NULL); + gettimeofday(&tv_stop, NULL); - float time_sec = tv_stop.tv_sec - tv_start.tv_sec + 1e-6f * (tv_stop.tv_usec - tv_start.tv_usec); - totalElapsedTime += time_sec; - ++frame; + float time_sec = tv_stop.tv_sec - tv_start.tv_sec + 1e-6f * (tv_stop.tv_usec - tv_start.tv_usec); + totalElapsedTime += time_sec; + ++frame; - if (frame == 60) - { - float fps = frame / totalElapsedTime; + if (frame == 60) { + float fps = frame / totalElapsedTime; - printf("HEAP:0x%lx, FPS:%f\n", esp_get_free_heap_size(), fps); + printf("HEAP:0x%lx, FPS:%f\n", esp_get_free_heap_size(), fps); - frame = 0; - totalElapsedTime = 0; - } + frame = 0; + totalElapsedTime = 0; } } @@ -487,7 +500,7 @@ int nes_insertcart(const char *filename, nes_t *machine) nes6502_setcontext(machine->cpu); /* rom file */ - machine->rominfo = rom_load(filename); + machine->rominfo = nes_rom_load(filename); if (NULL == machine->rominfo) goto _fail; diff --git a/components/nofrendo/nes/nes.h b/components/nofrendo/nes/nes.h index 39b28af3..c614bddc 100644 --- a/components/nofrendo/nes/nes.h +++ b/components/nofrendo/nes/nes.h @@ -106,6 +106,8 @@ extern void nes_setfiq(uint8 state); extern void nes_nmi(void); extern void nes_irq(void); extern void nes_emulate(void); +extern void nes_prep_emulation(void); +extern void nes_emulateframe(unsigned char reset); extern void nes_reset(int reset_type); diff --git a/components/nofrendo/nes/nes_rom.c b/components/nofrendo/nes/nes_rom.c index 80d151a8..b414e0bd 100644 --- a/components/nofrendo/nes/nes_rom.c +++ b/components/nofrendo/nes/nes_rom.c @@ -437,7 +437,7 @@ char *rom_getinfo(rominfo_t *rominfo) } /* Load a ROM image into memory */ -rominfo_t *rom_load(const char *filename) +rominfo_t *nes_rom_load(const char *filename) { unsigned char *rom=(unsigned char*)osd_getromdata(); rominfo_t *rominfo; diff --git a/components/nofrendo/nes/nes_rom.h b/components/nofrendo/nes/nes_rom.h index ba86d2f6..ae126e5b 100644 --- a/components/nofrendo/nes/nes_rom.h +++ b/components/nofrendo/nes/nes_rom.h @@ -62,7 +62,7 @@ typedef struct rominfo_s extern int rom_checkmagic(const char *filename); -extern rominfo_t *rom_load(const char *filename); +extern rominfo_t *nes_rom_load(const char *filename); extern void rom_free(rominfo_t **rominfo); extern char *rom_getinfo(rominfo_t *rominfo); diff --git a/main/gameboy.cpp b/main/gameboy.cpp new file mode 100644 index 00000000..4bd6302a --- /dev/null +++ b/main/gameboy.cpp @@ -0,0 +1,193 @@ +#include "gameboy.hpp" + +#include +#include "format.hpp" +#include "spi_lcd.h" +#include "i2s_audio.h" +#include "input.h" +#include "st7789.hpp" +#include "task.hpp" + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" + +static const size_t gameboy_screen_width = 160; + +#if USE_GAMEBOY_GNUBOY +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + +uint16_t* displayBuffer[2]; +struct fb fb; +struct pcm pcm; +uint8_t currentBuffer; +uint16_t* framebuffer; +int frame = 0; +uint elapsedTime = 0; + +#define AUDIO_SAMPLE_RATE (16000) + +int32_t* audioBuffer[2]; +volatile uint8_t currentAudioBuffer = 0; +volatile uint16_t currentAudioSampleCount; +volatile int32_t* currentAudioBufferPtr; + +extern "C" void die(char *fmt, ...) { + fmt::print("DIE!\n"); +} + +static std::shared_ptr gbc_task; + +void run_to_vblank(std::mutex &m, std::condition_variable& cv) +{ + /* FRAME BEGIN */ + static auto start = std::chrono::high_resolution_clock::now(); + + /* FIXME: djudging by the time specified this was intended + to emulate through vblank phase which is handled at the + end of the loop. */ + cpu_emulate(2280); + + /* FIXME: R_LY >= 0; comparsion to zero can also be removed + altogether, R_LY is always 0 at this point */ + while (R_LY > 0 && R_LY < 144) + { + emu_step(); + } + + /* VBLANK BEGIN */ + + //vid_end(); + if ((frame % 2) == 0) { + // xQueueSend(video_q, &framebuffer, portMAX_DELAY); + auto _frame = displayBuffer[currentBuffer]; + for (int y=0; y<144; y+=48) { + lcd_write_frame(0, y, 160, 48, (uint8_t*)&_frame[y*160]); + } + // swap buffers + // currentBuffer = currentBuffer ? 0 : 1; + // framebuffer = displayBuffer[currentBuffer]; + // fb.ptr = (uint8_t*)framebuffer; + } + + rtc_tick(); + + sound_mix(); + + if (pcm.pos > 100) { + currentAudioBufferPtr = audioBuffer[currentAudioBuffer]; + currentAudioSampleCount = pcm.pos; + + // void* tempPtr = (void*)0x1234; + // xQueueSend(audio_q, &tempPtr, portMAX_DELAY); + audio_play_frame((uint8_t*)currentAudioBufferPtr, currentAudioSampleCount * 2); + + // Swap buffers + // currentAudioBuffer = currentAudioBuffer ? 0 : 1; + // pcm.buf = (int16_t*)audioBuffer[currentAudioBuffer]; + pcm.pos = 0; + } + + if (!(R_LCDC & 0x80)) { + /* LCDC operation stopped */ + /* FIXME: djudging by the time specified, this is + intended to emulate through visible line scanning + phase, even though we are already at vblank here */ + cpu_emulate(32832); + } + + while (R_LY > 0) { + /* Step through vblank phase */ + emu_step(); + } + ++frame; + if ((frame % 60) == 0) { + auto end = std::chrono::high_resolution_clock::now(); + float elapsed = std::chrono::duration(end - start).count(); + fmt::print("gameboy: FPS {}\n", (float) frame / elapsed); + } +} +#endif + +void init_gameboy(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size) { + // WIDTH = gameboy_screen_width, so 320-WIDTH is gameboy_screen_width + espp::St7789::set_offset((320-gameboy_screen_width) / 2, (240-144) / 2); + static bool initialized = false; + +#if USE_GAMEBOY_GNUBOY + // Note: Magic number obtained by adjusting until audio buffer overflows stop. + const int audioBufferLength = AUDIO_SAMPLE_RATE / 3 + 1; // / 10 + //printf("CHECKPOINT AUDIO: HEAP:0x%x - allocating 0x%x\n", esp_get_free_heap_size(), audioBufferLength * sizeof(int16_t) * 2 * 2); + const int AUDIO_BUFFER_SIZE = audioBufferLength * sizeof(int16_t) * 2; + + if (!initialized) { + // displayBuffer[0] = (uint16_t*)heap_caps_malloc(160*144*2, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + // displayBuffer[1] = (uint16_t*)heap_caps_malloc(160*144*2, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + // audioBuffer[0] = (int32_t*)heap_caps_malloc(AUDIO_BUFFER_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + // audioBuffer[1] = (int32_t*)heap_caps_malloc(AUDIO_BUFFER_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + } + displayBuffer[0] = (uint16_t*)get_frame_buffer(); + displayBuffer[1] = (uint16_t*)get_frame_buffer(); + audioBuffer[0] = (int32_t*)get_audio_buffer(); + audioBuffer[1] = (int32_t*)get_audio_buffer(); + + memset(&fb, 0, sizeof(fb)); + fb.w = 160; + fb.h = 144; + fb.pelsize = 2; + fb.pitch = fb.w * fb.pelsize; + fb.indexed = 0; + fb.ptr = (uint8_t*)displayBuffer[0]; + fb.enabled = 1; + fb.dirty = 0; + framebuffer = displayBuffer[0]; + + // pcm.len = count of 16bit samples (x2 for stereo) + memset(&pcm, 0, sizeof(pcm)); + pcm.hz = AUDIO_SAMPLE_RATE; + pcm.stereo = 1; + pcm.len = /*pcm.hz / 2*/ audioBufferLength; + pcm.buf = (int16_t*)audioBuffer[0]; + pcm.pos = 0; + + sound_reset(); + + fmt::print("GAMEBOY enabled: GNUBOY\n"); + loader_init(romdata, rom_data_size); + emu_reset(); + initialized = true; + gbc_task = std::make_shared(espp::Task::Config{ + .name = "gbc task", + .callback = run_to_vblank, + .stack_size_bytes = 10*1024, + .priority = 20, + .core_id = 1 + }); + gbc_task->start(); +#endif +} + +void run_gameboy_rom() { + uint8_t _num_touches, _btn_state; + uint16_t _x,_y; + touchpad_read(&_num_touches, &_x, &_y, &_btn_state); +} + +void deinit_gameboy() { + fmt::print("quitting gameboy emulation!\n"); +#if USE_GAMEBOY_GNUBOY + loader_unload(); + gbc_task.reset(); +#endif +} diff --git a/main/gameboy.hpp b/main/gameboy.hpp index b5a288ab..506d8269 100644 --- a/main/gameboy.hpp +++ b/main/gameboy.hpp @@ -1,171 +1,7 @@ #pragma once -#include -#include "format.hpp" -#include "spi_lcd.h" -#include "i2s_audio.h" -#include "input.h" -#include "st7789.hpp" +#include -extern "C" { -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -} - -static const size_t gameboy_screen_width = 160; - -uint16_t* displayBuffer[2]; -struct fb fb; -struct pcm pcm; -uint8_t currentBuffer; -uint16_t* framebuffer; -int frame = 0; -uint elapsedTime = 0; - -#define AUDIO_SAMPLE_RATE (16000) - -int32_t* audioBuffer[2]; -volatile uint8_t currentAudioBuffer = 0; -volatile uint16_t currentAudioSampleCount; -volatile int32_t* currentAudioBufferPtr; - -extern "C" void die(char *fmt, ...) { - fmt::print("DIE!\n"); -} - -void run_to_vblank() -{ - /* FRAME BEGIN */ - - /* FIXME: djudging by the time specified this was intended - to emulate through vblank phase which is handled at the - end of the loop. */ - cpu_emulate(2280); - - /* FIXME: R_LY >= 0; comparsion to zero can also be removed - altogether, R_LY is always 0 at this point */ - while (R_LY > 0 && R_LY < 144) - { - emu_step(); - } - - /* VBLANK BEGIN */ - - //vid_end(); - if ((frame % 2) == 0) { - auto _frame = displayBuffer[currentBuffer]; - for (int y=0; y<144; y+=48) { - lcd_write_frame(0, y, 160, 48, (uint8_t*)&_frame[y*160]); - } - - // swap buffers - currentBuffer = currentBuffer ? 0 : 1; - framebuffer = displayBuffer[currentBuffer]; - - fb.ptr = (uint8_t*)framebuffer; - } - - rtc_tick(); - - sound_mix(); - - if (pcm.pos > 100) { - currentAudioBufferPtr = audioBuffer[currentAudioBuffer]; - currentAudioSampleCount = pcm.pos; - - // void* tempPtr = 0x1234; - audio_play_frame((uint8_t*)currentAudioBufferPtr, currentAudioSampleCount * 2); - - // Swap buffers - currentAudioBuffer = currentAudioBuffer ? 0 : 1; - pcm.buf = (int16_t*)audioBuffer[currentAudioBuffer]; - pcm.pos = 0; - } - - if (!(R_LCDC & 0x80)) { - /* LCDC operation stopped */ - /* FIXME: djudging by the time specified, this is - intended to emulate through visible line scanning - phase, even though we are already at vblank here */ - cpu_emulate(32832); - } - - while (R_LY > 0) { - /* Step through vblank phase */ - emu_step(); - } - ++frame; -} - -void init_gameboy(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size) { - // WIDTH = gameboy_screen_width, so 320-WIDTH is gameboy_screen_width - espp::St7789::set_offset((320-gameboy_screen_width) / 2, (240-144) / 2); - static bool initialized = false; - - // Note: Magic number obtained by adjusting until audio buffer overflows stop. - const int audioBufferLength = AUDIO_SAMPLE_RATE / 5 + 1; // / 10 - //printf("CHECKPOINT AUDIO: HEAP:0x%x - allocating 0x%x\n", esp_get_free_heap_size(), audioBufferLength * sizeof(int16_t) * 2 * 2); - const int AUDIO_BUFFER_SIZE = audioBufferLength * sizeof(int16_t) * 2; - - if (!initialized) { - displayBuffer[0] = (uint16_t*)heap_caps_malloc(160*144*2, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); - displayBuffer[1] = (uint16_t*)heap_caps_malloc(160*144*2, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); - audioBuffer[0] = (int32_t*)heap_caps_malloc(AUDIO_BUFFER_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); - audioBuffer[1] = (int32_t*)heap_caps_malloc(AUDIO_BUFFER_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); - } - memset(&fb, 0, sizeof(fb)); - fb.w = 160; - fb.h = 144; - fb.pelsize = 2; - fb.pitch = fb.w * fb.pelsize; - fb.indexed = 0; - fb.ptr = (uint8_t*)displayBuffer[0]; - fb.enabled = 1; - fb.dirty = 0; - - - // pcm.len = count of 16bit samples (x2 for stereo) - memset(&pcm, 0, sizeof(pcm)); - pcm.hz = AUDIO_SAMPLE_RATE; - pcm.stereo = 1; - pcm.len = /*pcm.hz / 2*/ audioBufferLength; - pcm.buf = (int16_t*)audioBuffer[0]; - pcm.pos = 0; - - sound_reset(); - - fmt::print("GAMEBOY enabled: GNUBOY\n"); - loader_init(romdata, rom_data_size); - emu_reset(); - initialized = true; -} - -void run_gameboy_rom() { - uint8_t _num_touches, _btn_state; - uint16_t _x,_y; - touchpad_read(&_num_touches, &_x, &_y, &_btn_state); - static size_t frame = 0; - static auto start = std::chrono::high_resolution_clock::now(); - frame++; - if ((frame % 60) == 0) { - auto end = std::chrono::high_resolution_clock::now(); - float elapsed = std::chrono::duration(end - start).count(); - fmt::print("gameboy: FPS {}\n", (float) frame / elapsed); - } - - run_to_vblank(); -} - -void deinit_gameboy() { - fmt::print("quitting gameboy emulation!\n"); - loader_unload(); -} +void init_gameboy(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size); +void run_gameboy_rom(); +void deinit_gameboy(); diff --git a/main/main.cpp b/main/main.cpp index 8bf20aff..59efb3f1 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -29,10 +29,15 @@ extern std::shared_ptr display; using namespace std::chrono_literals; -// NOTE: to see the indev configuration for the esp32 s3 box, look at -// bsp/src/boards/esp32_s3_box.c:56. Regarding whether it uses the FT5x06 or the -// TT21100, it uses the tp_prob function which checks to see if the devie -// addresses for those chips exist on the i2c bus (indev_tp.c:37) +// GB +#define GAMEBOY_WIDTH (160) +#define GAMEBOY_HEIGHT (144) +// SMS +#define SMS_WIDTH (256) +#define SMS_HEIGHT (192) +// GG +#define GAMEGEAR_WIDTH (160) +#define GAMEGEAR_HEIGHT (144) extern "C" void app_main(void) { fmt::print("Starting esp-box-emu...\n"); @@ -110,7 +115,7 @@ extern "C" void app_main(void) { deinit_gameboy(); break; case Emulator::NES: - init_nes(rom_filename, display->vram0(), romdata, rom_size_bytes); + init_nes(rom_filename, romdata, rom_size_bytes); while (!user_quit()) { run_nes_rom(); } diff --git a/main/nes.cpp b/main/nes.cpp new file mode 100644 index 00000000..ca8cde23 --- /dev/null +++ b/main/nes.cpp @@ -0,0 +1,60 @@ +#include "nes.hpp" + +#ifdef USE_NES_NOFRENDO +extern "C" { +#include "event.h" +#include "gui.h" +#include +} + +static nes_t* console_nes; +#endif + +#include + +#include "format.hpp" +#include "spi_lcd.h" +#include "input.h" +#include "st7789.hpp" + +void init_nes(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size) { +#ifdef USE_NES_NOFRENDO + espp::St7789::set_offset((320-NES_SCREEN_WIDTH) / 2, 0); + + static bool initialized = false; + if (!initialized) { + event_init(); + osd_init(); + gui_init(); + vidinfo_t video; + osd_getvideoinfo(&video); + vid_init(video.default_width, video.default_height, video.driver); + console_nes = nes_create(); + event_set_system(system_nes); + } else { + nes_reset(HARD_RESET); + } + initialized = true; + + nes_insertcart(rom_filename.c_str(), console_nes); + vid_setmode(NES_SCREEN_WIDTH, NES_VISIBLE_HEIGHT); + nes_prep_emulation(); +#endif +} + +void run_nes_rom() { + // we have to call touchpad_read to determine if the user needs to quit... + uint8_t _num_touches, _btn_state; + uint16_t _x,_y; + touchpad_read(&_num_touches, &_x, &_y, &_btn_state); +#ifdef USE_NES_NOFRENDO + nes_emulateframe(0); +#endif +} + +void deinit_nes() { +#ifdef USE_NES_NOFRENDO + nes_poweroff(); + nes_destroy(&console_nes); +#endif +} diff --git a/main/nes.hpp b/main/nes.hpp index 109d9725..a30e5bd3 100644 --- a/main/nes.hpp +++ b/main/nes.hpp @@ -1,55 +1,7 @@ #pragma once -#ifdef USE_NES_NOFRENDO -extern "C" { -#include "event.h" -#include "gui.h" -#include -} - -nes_t* console_nes; - -#endif - #include -#include "format.hpp" -#include "spi_lcd.h" -#include "st7789.hpp" - -void init_nes(const std::string& rom_filename, uint16_t* vram_ptr, uint8_t *romdata, size_t rom_data_size) { -#ifdef USE_NES_NOFRENDO - espp::St7789::set_offset((320-NES_SCREEN_WIDTH) / 2, 0); - - static bool initialized = false; - if (!initialized) { - event_init(); - osd_init(); - gui_init(); - vidinfo_t video; - osd_getvideoinfo(&video); - vid_init(video.default_width, video.default_height, video.driver); - console_nes = nes_create(); - event_set_system(system_nes); - } else { - nes_reset(HARD_RESET); - } - initialized = true; - - nes_insertcart(rom_filename.c_str(), console_nes); - vid_setmode(NES_SCREEN_WIDTH, NES_VISIBLE_HEIGHT); -#endif -} - -void run_nes_rom() { -#ifdef USE_NES_NOFRENDO - nes_emulate(); -#endif -} - -void deinit_nes() { -#ifdef USE_NES_NOFRENDO - nes_poweroff(); - nes_destroy(&console_nes); -#endif -} +void init_nes(const std::string& rom_filename, uint8_t *romdata, size_t rom_data_size); +void run_nes_rom(); +void deinit_nes();