diff --git a/gameboy/Makefile b/gameboy/Makefile index d368bc29..ecb87ae3 100755 --- a/gameboy/Makefile +++ b/gameboy/Makefile @@ -1,10 +1,13 @@ gameboy_objects := libco -gameboy_objects += gameboy-system gameboy-cartridge gameboy-memory gameboy-cpu +gameboy_objects += gameboy-system gameboy-scheduler +gameboy_objects += gameboy-cartridge gameboy-memory +gameboy_objects += gameboy-cpu objects += $(gameboy_objects) obj/libco.o: libco/libco.c libco/* obj/gameboy-system.o: $(gameboy)/system/system.cpp $(call rwildcard,$(gameboy)/system/) +obj/gameboy-scheduler.o: $(gameboy)/scheduler/scheduler.cpp $(call rwildcard,$(gameboy)/scheduler/) obj/gameboy-cartridge.o: $(gameboy)/cartridge/cartridge.cpp $(call rwildcard,$(gameboy)/cartridge/) obj/gameboy-memory.o: $(gameboy)/memory/memory.cpp $(call rwildcard,$(gameboy)/memory/) obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/) diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp index 4b7bbfdf..b900cbbc 100755 --- a/gameboy/cartridge/cartridge.cpp +++ b/gameboy/cartridge/cartridge.cpp @@ -41,7 +41,6 @@ void Cartridge::load(uint8_t *data, unsigned size) { } loaded = true; - system.interface->message({ "Loaded:\n", info.name }); } void Cartridge::unload() { diff --git a/gameboy/cpu/core/core.cpp b/gameboy/cpu/core/core.cpp new file mode 100755 index 00000000..d81d9f71 --- /dev/null +++ b/gameboy/cpu/core/core.cpp @@ -0,0 +1,91 @@ +#ifdef CPU_CPP + +#include "disassembler.cpp" + +unsigned opcode_counter = 0; + +void CPU::op_unknown() { + uint8 opcode = bus.read(--r[PC]); + print( + "CPU: unknown opcode [", hex<2>(opcode), "]\n", + "af:", hex<4>(r[AF]), " bc:", hex<4>(r[BC]), " de:", hex<4>(r[DE]), " hl:", hex<4>(r[HL]), " ", + "sp:", hex<4>(r[SP]), " pc:", hex<4>(r[PC]), "\n", + "ly:", decimal<3, ' '>(status.lycounter), " exec:", opcode_counter, "\n" + ); + while(true) scheduler.exit(); +} + +//8-bit load commands + +template void CPU::op_ld_r_n() { + r[x] = op_read(r[PC]++); +} + +void CPU::op_ldd_hl_a() { + op_write(r[HL], r[A]); + r[HL]--; +} + +//16-bit load commands + +template void CPU::op_ld_rr_nn() { + r[y] = op_read(r[PC]++); + r[x] = op_read(r[PC]++); +} + +//8-bit arithmetic commands + +template void CPU::op_xor_r() { + r[A] ^= r[x]; + r.f.z = r[A] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = 0; +} + +template void CPU::op_dec_r() { + r[x]--; + r.f.z = r[x] == 0; + r.f.n = 0; //??? + r.f.h = 0; //??? +} + +//control commands + +void CPU::op_nop() { +} + +//jump commands + +void CPU::op_jp_nn() { + uint8 lo = op_read(r[PC]++); + uint8 hi = op_read(r[PC]++); + r[PC] = (hi << 8) | (lo << 0); + op_io(); +} + +template void CPU::op_jr_f_n() { + int8 n = op_read(r[PC]++); + if(r.f[x] == y) { + r[PC] += n; + op_io(); + } +} + +void CPU::initialize_opcode_table() { + for(unsigned n = 0; n < 256; n++) opcode_table[n] = &CPU::op_unknown; + + opcode_table[0x00] = &CPU::op_nop; + opcode_table[0x05] = &CPU::op_dec_r; + opcode_table[0x06] = &CPU::op_ld_r_n; + opcode_table[0x0d] = &CPU::op_dec_r; + opcode_table[0x0e] = &CPU::op_ld_r_n; + opcode_table[0x20] = &CPU::op_jr_f_n; + opcode_table[0x21] = &CPU::op_ld_rr_nn; + opcode_table[0x32] = &CPU::op_ldd_hl_a; + opcode_table[0x3e] = &CPU::op_ld_r_n; + opcode_table[0xaf] = &CPU::op_xor_r; + opcode_table[0xc3] = &CPU::op_jp_nn; +} + +#endif diff --git a/gameboy/cpu/core/core.hpp b/gameboy/cpu/core/core.hpp new file mode 100755 index 00000000..86c9ee04 --- /dev/null +++ b/gameboy/cpu/core/core.hpp @@ -0,0 +1,27 @@ +#include "registers.hpp" +void (CPU::*opcode_table[256])(); +void initialize_opcode_table(); + +void op_unknown(); + +//8-bit load commands +template void op_ld_r_n(); +void op_ldd_hl_a(); + +//16-bit load commands +template void op_ld_rr_nn(); + +//8-bit arithmetic commands +template void op_xor_r(); +template void op_dec_r(); + +//control commands +void op_nop(); + +//jump commands +void op_jp_nn(); +template void op_jr_f_n(); + +//disassembler.cpp +string disassemble(uint16 pc); +string disassemble_opcode(uint16 pc); diff --git a/gameboy/cpu/core/disassembler.cpp b/gameboy/cpu/core/disassembler.cpp new file mode 100755 index 00000000..85e8e551 --- /dev/null +++ b/gameboy/cpu/core/disassembler.cpp @@ -0,0 +1,30 @@ +#ifdef CPU_CPP + +string CPU::disassemble(uint16 pc) { + return { hex<4>(pc), " ", disassemble_opcode(pc) }; +} + +string CPU::disassemble_opcode(uint16 pc) { + uint8 opcode = bus.read(pc); + uint8 p0 = bus.read(pc + 1); + uint8 p1 = bus.read(pc + 2); + uint8 p2 = bus.read(pc + 3); + + switch(opcode) { + case 0x00: return { "nop" }; + case 0x05: return { "dec b" }; + case 0x06: return { "ld b,$", hex<2>(p0) }; + case 0x0d: return { "dec c" }; + case 0x0e: return { "ld c,$", hex<2>(p0) }; + case 0x20: return { "jp nz,$", hex<2>(p0) }; + case 0x21: return { "ld hl,$", hex<2>(p1), hex<2>(p0) }; + case 0x32: return { "ldd (hl),a" }; + case 0x3e: return { "ld a,$", hex<2>(p0) }; + case 0xaf: return { "xor a" }; + case 0xc3: return { "jp $", hex<2>(p1), hex<2>(p0) }; + } + + return { "???? [", hex<2>(opcode), ",", hex<2>(p1), ",", hex<2>(p0), "]" }; +} + +#endif diff --git a/gameboy/cpu/registers.hpp b/gameboy/cpu/core/registers.hpp similarity index 88% rename from gameboy/cpu/registers.hpp rename to gameboy/cpu/core/registers.hpp index 45c8cbf7..db4cb602 100755 --- a/gameboy/cpu/registers.hpp +++ b/gameboy/cpu/core/registers.hpp @@ -1,3 +1,15 @@ +enum { + A, F, AF, + B, C, BC, + D, E, DE, + H, L, HL, + SP, PC, +}; + +enum { + ZF, NF, HF, CF, +}; + //register base class //the idea here is to have all registers derive from a single base class. //this allows construction of opcodes that can take any register as input or output, @@ -36,6 +48,10 @@ struct RegisterF : Register { bool z, n, h, c; operator unsigned() const { return (z << 7) | (n << 6) | (h << 5) | (c << 4); } unsigned operator=(unsigned x) { z = x & 0x80; n = x & 0x40; h = x & 0x20; c = x & 0x10; return *this; } + bool& operator[](unsigned r) { + static bool* table[] = { &z, &n, &h, &c }; + return *table[r]; + } }; struct Register16 : Register { @@ -75,5 +91,10 @@ struct Registers { Register16 sp; Register16 pc; + Register& operator[](unsigned r) { + static Register* table[] = { &a, &f, &af, &b, &c, &bc, &d, &e, &de, &h, &l, &hl, &sp, &pc }; + return *table[r]; + } + Registers() : af(a, f), bc(b, c), de(d, e), hl(h, l) {} } r; diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index 2a986aec..b0646f32 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -1,18 +1,24 @@ -//Sharp LR35902 @ 4.19MHz - #include #define CPU_CPP namespace GameBoy { +#include "core/core.cpp" +#include "timing/timing.cpp" CPU cpu; +void CPU::Main() { + cpu.main(); +} + void CPU::main() { while(true) { - uint8 opcode = bus.read(r.pc++); - switch(opcode) { - case 0x00: break; //NOP - } + print(disassemble(r[PC]), "\n"); + + uint8 opcode = op_read(r[PC]++); + (this->*opcode_table[opcode])(); + + opcode_counter++; } } @@ -21,7 +27,20 @@ void CPU::power() { } void CPU::reset() { - r.pc = 0x0100; + create(Main, 4 * 1024 * 1024); + + r[PC] = 0x0100; + r[SP] = 0xfffe; + r[AF] = 0x0000; + r[BC] = 0x0000; + r[DE] = 0x0000; + r[HL] = 0x0000; + + status.lycounter = 0; +} + +CPU::CPU() { + initialize_opcode_table(); } } diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index 073d12c9..fa28f0c3 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -1,10 +1,16 @@ -class CPU { -public: - #include "registers.hpp" +struct CPU : Processor { + #include "core/core.hpp" + #include "timing/timing.hpp" + struct Status { + unsigned lycounter; + } status; + + static void Main(); void main(); void power(); void reset(); + CPU(); }; extern CPU cpu; diff --git a/gameboy/cpu/timing/opcode.cpp b/gameboy/cpu/timing/opcode.cpp new file mode 100755 index 00000000..b952e3d6 --- /dev/null +++ b/gameboy/cpu/timing/opcode.cpp @@ -0,0 +1,18 @@ +#ifdef CPU_CPP + +void CPU::op_io() { + add_clocks(4); +} + +uint8 CPU::op_read(uint16 addr) { + uint8 r = bus.read(addr); + add_clocks(4); + return r; +} + +void CPU::op_write(uint16 addr, uint8 data) { + bus.write(addr, data); + add_clocks(4); +} + +#endif diff --git a/gameboy/cpu/timing/timing.cpp b/gameboy/cpu/timing/timing.cpp new file mode 100755 index 00000000..becf6feb --- /dev/null +++ b/gameboy/cpu/timing/timing.cpp @@ -0,0 +1,28 @@ +//4194304hz (4 * 1024 * 1024) +//70224 clocks/frame +// 456 clocks/scanline +// 154 scanlines/frame + +#ifdef CPU_CPP + +#include "opcode.cpp" + +void CPU::add_clocks(unsigned clocks) { + clock += clocks; + + if(clock >= 456) scanline(); +} + +void CPU::scanline() { + clock -= 456; + + status.lycounter++; + if(status.lycounter >= 154) frame(); +} + +void CPU::frame() { + status.lycounter -= 154; + scheduler.exit(); +} + +#endif diff --git a/gameboy/cpu/timing/timing.hpp b/gameboy/cpu/timing/timing.hpp new file mode 100755 index 00000000..89dbf7d8 --- /dev/null +++ b/gameboy/cpu/timing/timing.hpp @@ -0,0 +1,8 @@ +void add_clocks(unsigned clocks); +void scanline(); +void frame(); + +//opcode.cpp +void op_io(); +uint8 op_read(uint16 addr); +void op_write(uint16 addr, uint8 data); diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index 917ea17c..c9c6948b 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000"; + static const char Version[] = "000.01"; } } @@ -28,7 +28,23 @@ namespace GameBoy { typedef uint32_t uint32; typedef uint64_t uint64; + struct Processor { + cothread_t thread; + unsigned frequency; + int64 clock; + + inline void create(void (*entrypoint_)(), unsigned frequency_) { + if(thread) co_delete(thread); + thread = co_create(65536 * sizeof(void*), entrypoint_); + frequency = frequency_; + clock = 0; + } + + inline Processor() : thread(0) {} + }; + #include + #include #include #include #include diff --git a/gameboy/scheduler/scheduler.cpp b/gameboy/scheduler/scheduler.cpp new file mode 100755 index 00000000..e8a98068 --- /dev/null +++ b/gameboy/scheduler/scheduler.cpp @@ -0,0 +1,33 @@ +#include + +#define SCHEDULER_CPP +namespace GameBoy { + +Scheduler scheduler; + +void Scheduler::enter() { + host_thread = co_active(); + co_switch(active_thread); +} + +void Scheduler::exit() { + active_thread = co_active(); + co_switch(host_thread); +} + +void Scheduler::swapto(Processor &p) { + active_thread = p.thread; + co_switch(active_thread); +} + +void Scheduler::init() { + host_thread = co_active(); + active_thread = cpu.thread; +} + +Scheduler::Scheduler() { + host_thread = 0; + active_thread = 0; +} + +} diff --git a/gameboy/scheduler/scheduler.hpp b/gameboy/scheduler/scheduler.hpp new file mode 100755 index 00000000..69337acf --- /dev/null +++ b/gameboy/scheduler/scheduler.hpp @@ -0,0 +1,13 @@ +struct Scheduler { + cothread_t host_thread; + cothread_t active_thread; + + void enter(); + void exit(); + void swapto(Processor&); + + void init(); + Scheduler(); +}; + +extern Scheduler scheduler; diff --git a/gameboy/system/system.cpp b/gameboy/system/system.cpp index bebae611..2a8b0aa1 100755 --- a/gameboy/system/system.cpp +++ b/gameboy/system/system.cpp @@ -11,10 +11,16 @@ void System::init(Interface *interface_) { void System::power() { cpu.power(); + scheduler.init(); } void System::reset() { cpu.reset(); + scheduler.init(); +} + +void System::run() { + scheduler.enter(); } } diff --git a/gameboy/system/system.hpp b/gameboy/system/system.hpp index ccc9b9c0..ccc6c889 100755 --- a/gameboy/system/system.hpp +++ b/gameboy/system/system.hpp @@ -5,6 +5,7 @@ public: void init(Interface*); void power(); void reset(); + void run(); //private: Interface *interface; diff --git a/ui/main.cpp b/ui/main.cpp index 04a697c8..8aace5a4 100755 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -16,6 +16,10 @@ void Application::main(int argc, char **argv) { while(quit == false) { OS::run(); + + if(GameBoy::cartridge.loaded()) { + GameBoy::system.run(); + } } }