Compare commits

...
5 Commits
Author SHA1 Message Date
Riyyi 8e4dacca8a Emulator: Add cli argument to specify bootrom path 2022-08-25 00:16:32 +02:00
Riyyi dd2920b2a4 Emulator: Load cartridge header 2022-08-25 00:10:57 +02:00
Riyyi 2aa50d3532 Emulator: Put startup into a Loader class 2022-08-24 23:47:36 +02:00
Riyyi 589faceb9b Doc: Clarify opcode mnemonic for 16-bit address registers
When you see a 16-bit register wrapped by parentheses, its referencing
the value at the address the register is pointing to rather than the
address itself.
2022-08-24 22:54:40 +02:00
Riyyi e9b47d7d68 Emulator: Implement 16-bit DEC opcode 2022-08-24 22:53:35 +02:00
6 changed files with 176 additions and 53 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ We start counting from the right as bit *#0*!
| register | 8 | unsigned | r8 | | r8 | r | | Any of the registers | |
| register | 16 | unsigned | r16 | | r16 | rr | | Any of the registers | little-endian |
| address | 8 | unsigned | a8 | | | (n) | | Address at value | |
| address | 16 | unsigned | a16 | a16 | n16 | (nn) | nn | Address at value | little-endian |
| address | 16 | unsigned | a16 or () | a16 | n16 | (nn) | nn | Address at value | little-endian |
| immediate | 8 | signed | s8 | r8 | e8 | e | dd | Next byte in memory | |
| immediate | 8 | unsigned | io8 | (a8) | n16 | (n) | n | 0xff00 + next byte in memory | write to I/O-port |
| condition | | | <ZNHC> | NZ | cc | | f | Execute if condition met | |
+39 -1
View File
@@ -72,24 +72,28 @@ void CPU::update()
case 0x06: ldi8(); break;
case 0x08: ldr16(); break;
case 0x0a: ldr8(); break;
case 0x0b: decr16(); break;
case 0x0d: dec8(); break;
case 0x0e: ldi8(); break;
case 0x11: ldi16(); break;
case 0x12: ldr8(); break;
case 0x16: ldi8(); break;
case 0x1a: ldr8(); break;
case 0x1b: decr16(); break;
case 0x1e: ldi8(); break;
case 0x20: jr8(); break;
case 0x21: ldi16(); break;
case 0x22: ldr8(); break;
case 0x26: ldi8(); break;
case 0x2a: ldr8(); break;
case 0x2a: lda8(); break;
case 0x2b: decr16(); break;
case 0x2e: ldi8(); break;
case 0x2f: misc(); break;
case 0x31: ldi16(); break;
case 0x32: ldr8(); break;
case 0x36: ldi8(); break;
case 0x3a: ldr8(); break;
case 0x3b: decr16(); break;
case 0x3e: ldi8(); break;
case 0x40: ldr8(); break;
case 0x41: ldr8(); break;
@@ -170,6 +174,8 @@ void CPU::update()
}
}
// -------------------------------------
void CPU::add()
{
uint8_t opcode = pcRead();
@@ -279,6 +285,38 @@ void CPU::lda8()
}
}
void CPU::decr16()
{
uint8_t opcode = pcRead();
switch (opcode) {
case 0x0b: { // DEC BC
uint32_t data = (bc() - 1) & 0xffff;
m_c = data & 0xff;
m_b = data >> 8;
break;
}
case 0x1b: { // DEC DE
uint32_t data = (de() - 1) & 0xffff;
m_e = data & 0xff;
m_d = data >> 8;
break;
}
case 0x2b: { // DEC HL
uint32_t data = (hl() - 1) & 0xffff;
m_l = data & 0xff;
m_h = data >> 8;
break;
}
case 0x3b: /* DEC SP */ m_sp = (m_sp - 1) & 0xffff; break;
default:
VERIFY_NOT_REACHED();
}
m_wait_cycles += 8;
}
// -------------------------------------
void CPU::ldi8()
{
uint8_t opcode = pcRead();
+1
View File
@@ -33,6 +33,7 @@ public:
void xor8();
// 16-bit
void decr16();
// -------------------------------------
// Bit Operations Instructions
+92
View File
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstddef> // size_t
#include "cpu.h"
#include "emu.h"
#include "loader.h"
#include "ppu.h"
#include "ruc/file.h"
#include "ruc/format/print.h"
void Loader::loadRom(std::string_view rom_path)
{
if (!rom_path.empty()) {
m_rom_data = ruc::File(rom_path).data();
}
init();
}
// -----------------------------------------
void Loader::init()
{
destroy();
Emu::the().init(8000000);
CPU cpu(8000000);
PPU ppu(4000000);
Emu::the().addProcessingUnit("cpu", &cpu);
Emu::the().addProcessingUnit("ppu", &ppu);
// https://gbdev.io/pandocs/Memory_Map.html
// https://gbdev.io/pandocs/Power_Up_Sequence.html
Emu::the().addMemorySpace("BOOTROM1", 0x0000, 0x00ff); // 256B
loadCartridgeHeader();
Emu::the().addMemorySpace("BOOTROM2", 0x0200, 0x08ff); // 1792B
Emu::the().addMemorySpace("VRAM", 0x8000, 0x9fff, 2); // 8KiB * 2 banks
Emu::the().addMemorySpace("CARDRAM", 0xa000, 0xbfff, 1); // 8KiB * ? banks, if any
Emu::the().addMemorySpace("WRAM1", 0xc000, 0xcfff); // 4 KiB, Work RAM
Emu::the().addMemorySpace("WRAM2", 0xd000, 0xdfff, 7); // 4 KiB * 7 banks, Work RAM
Emu::the().addMemorySpace("ECHORAM", 0xe000, 0xfdff); // 7680B, Mirror of 0xc000~0xddff
Emu::the().addMemorySpace("OAM", 0xfe00, 0xfe9f); // 160B, Object Attribute Memory (VRAM Sprite Attribute Table)
Emu::the().addMemorySpace("Not Usable", 0xfea0, 0xfeff); // 96B, Nintendo probibits this area
Emu::the().addMemorySpace("IO", 0xff00, 0xff7f); // 128B, I/O Registers
Emu::the().addMemorySpace("HRAM", 0xff80, 0xfffe); // 127B, High RAM (CPU cache)
Emu::the().addMemorySpace("IE", 0xffff, 0xffff); // 1B, Interrupt Enable register
// Load bootrom
auto bootrom = ruc::File(m_bootrom_path).data();
for (size_t i = 0; i < bootrom.length(); ++i) {
// Skip cartridge header memory range
if (i >= 0x0100 && i <= 0x01ff) {
continue;
}
Emu::the().writeMemory(i, bootrom[i]);
}
update();
}
void Loader::update()
{
while (true) {
Emu::the().update();
}
}
void Loader::destroy()
{
Emu::destroy();
}
void Loader::loadCartridgeHeader()
{
if (m_rom_data.empty()) {
return;
}
Emu::the().addMemorySpace("CARTHEADER", 0x100, 0x14f);
for (size_t i = 0x100; i <= 0x14f; ++i) {
Emu::the().writeMemory(i, m_rom_data[i]);
}
}
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <string>
#include <string_view>
#include "ruc/singleton.h"
class Loader final : public ruc::Singleton<Loader> {
public:
Loader(s) {}
void loadRom(std::string_view rom_path);
void setBootromPath(std::string_view bootrom_path) { m_bootrom_path = bootrom_path; }
private:
void init();
void update();
void destroy();
void loadCartridgeHeader();
std::string_view m_bootrom_path;
std::string m_rom_data;
};
+12 -51
View File
@@ -5,63 +5,24 @@
* SPDX-License-Identifier: MIT
*/
#include "cpu.h"
#include "emu.h"
#include "ppu.h"
#include "ruc/file.h"
#include "loader.h"
#include "ruc/argparser.h"
#include "ruc/format/print.h"
#include "ruc/timer.h"
#include <string_view>
int main(int argc, char* argv[])
int main(int argc, const char* argv[])
{
(void)argc;
(void)argv;
std::string_view bootrom_path = "gbc_bios.bin";
std::string_view rom_path;
ruc::Timer t;
ruc::ArgParser argParser;
argParser.addOption(bootrom_path, 'b', "bootrom", nullptr, nullptr, "", ruc::ArgParser::Required::Yes);
argParser.addOption(rom_path, 'r', "rom", nullptr, nullptr, "", ruc::ArgParser::Required::Yes);
argParser.parse(argc, argv);
print("{}ms\n", t.elapsedNanoseconds() / 1000000.0);
Emu::the().init(8000000);
CPU cpu(8000000);
PPU ppu(4000000);
Emu::the().addProcessingUnit("cpu", &cpu);
Emu::the().addProcessingUnit("ppu", &ppu);
// https://gbdev.io/pandocs/Memory_Map.html
// https://gbdev.io/pandocs/Power_Up_Sequence.html
Emu::the().addMemorySpace("BOOTROM1", 0x0000, 0x00ff); // 256B
Emu::the().addMemorySpace("BOOTROM2", 0x0200, 0x08ff); // 1792B
Emu::the().addMemorySpace("VRAM", 0x8000, 0x9fff, 2); // 8KiB * 2 banks
Emu::the().addMemorySpace("CARDRAM", 0xa000, 0xbfff, 1); // 8KiB * ? banks, if any
Emu::the().addMemorySpace("WRAM1", 0xc000, 0xcfff); // 4 KiB, Work RAM
Emu::the().addMemorySpace("WRAM2", 0xd000, 0xdfff, 7); // 4 KiB * 7 banks, Work RAM
Emu::the().addMemorySpace("ECHORAM", 0xe000, 0xfdff); // 7680B, Mirror of 0xc000~0xddff
Emu::the().addMemorySpace("OAM", 0xfe00, 0xfe9f); // 160B, Object Attribute Memory (VRAM Sprite Attribute Table)
Emu::the().addMemorySpace("Not Usable", 0xfea0, 0xfeff); // 96B, Nintendo probibits this area
Emu::the().addMemorySpace("IO", 0xff00, 0xff7f); // 128B, I/O Registers
Emu::the().addMemorySpace("HRAM", 0xff80, 0xfffe); // 127B, High RAM (CPU cache)
Emu::the().addMemorySpace("IE", 0xffff, 0xffff); // 1B, Interrupt Enable register
// Load bootrom
auto bootrom = ruc::File("gbc_bios.bin").data();
for (size_t i = 0; i < bootrom.length(); ++i) {
// Skip cartridge header memory range
if (i >= 0x0100 && i <= 0x01ff) {
continue;
}
Emu::the().writeMemory(i, bootrom[i]);
}
// Get shared register
print("{}\n", *Emu::the().processingUnit("cpu")->sharedRegister("a"));
print("{}\n", *Emu::the().processingUnit("cpu")->sharedRegister("b"));
while (true) {
Emu::the().update();
}
Loader::the().setBootromPath(bootrom_path);
Loader::the().loadRom(rom_path);
return 0;
}