Compare commits

..
189 Commits
Author SHA1 Message Date
Riyyi 301c9343da Emulator: Separate pixel FIFO states into different functions 2022-10-18 21:24:06 +02:00
Riyyi 91884d65fb Emulator: Remove naive old tile draw code 2022-10-18 20:08:07 +02:00
Riyyi e566adad50 Emulator: Add support for both tile index mapping variants 2022-10-18 13:53:39 +02:00
Riyyi 1c14228bf7 Emulator: Fix pixel FIFO fetcher half step speed 2022-10-17 15:58:12 +02:00
Riyyi 00ceacaf52 Emulator: Fix pixel FIFO x-coordinate offset 2022-10-17 15:51:16 +02:00
Riyyi adb290a7c7 Emulator: Move LCDC inside PPU class 2022-10-17 13:33:26 +02:00
Riyyi 67539b6d43 Emulator: Change PPU::State values 2022-10-17 13:33:26 +02:00
Riyyi 7ed5c74faf Emulator: Switch to pixel FIFO 2022-10-17 13:33:26 +02:00
Riyyi f2a68e6294 Emulator: Add initial pixel FIFO to PPU 2022-10-17 13:33:26 +02:00
Riyyi 5e6972e4da Emulator: Fix PPU clocks and LCD offsets 2022-10-16 21:40:58 +02:00
Riyyi c0c592c438 Emulator: Make palette colors more accurate 2022-10-16 13:06:11 +02:00
Riyyi 900dbde54b Emulator: Set camera zoom-level and screen flip in scene JSON 2022-10-16 12:47:21 +02:00
Riyyi 16db21fcab Emulator: Fix predicate jump call opcodes 2022-10-16 12:46:10 +02:00
Riyyi d33de14694 Emulator: Fix swap opcodes 2022-10-16 12:46:10 +02:00
Riyyi 80c68035c5 Emulator: Fix non-prefix rotate opcodes 2022-10-16 12:46:10 +02:00
Riyyi 8f016b1095 Emulator: Add function to fetch pixel color from palette 2022-10-16 12:46:10 +02:00
Riyyi 869438e0e0 Emulator: Add state machine to PPU 2022-10-16 12:46:10 +02:00
Riyyi 6943cfd684 Emulator: Add naive background tile rendering to PPU 2022-10-16 12:46:10 +02:00
Riyyi fcfd27468d Emulator: Change main to become an Inferno application 2022-10-16 12:46:10 +02:00
Riyyi ce46f5033a Emulator+Test: Store ProcessingUnits as std::shared_ptr 2022-10-16 12:46:10 +02:00
Riyyi 5919f70537 CMake+Meta: Add inferno dependency, which includes ruc indirectly 2022-10-16 12:46:10 +02:00
Riyyi 136250355e Meta: Add assets for settings and scene 2022-10-16 11:32:52 +02:00
Riyyi 66c10e3c3e Doc: Update git submodule instructions in readme 2022-09-27 12:42:28 +02:00
Riyyi 7153b0e9b6 Emulator: Optimize interrupt request checking 2022-09-02 23:15:06 +02:00
Riyyi cd2673909c Emulator: Implement DI/EI opcodes, finish RETI opcode 2022-09-02 22:44:24 +02:00
Riyyi 5b4dbdc9a4 Emulator: Add interrupt handling 2022-09-02 22:44:24 +02:00
Riyyi 5b08c18127 Emulator: Fix DAA carry flag, opcode: 0x27 2022-09-02 16:35:43 +02:00
Riyyi 857865dfb9 Emulator: You can only have copyright on stuff you have worked on 2022-09-02 16:08:31 +02:00
Riyyi a29f01389b Emulator: Fix RET zero flag condition, opcode: 0xc0, 0xc8 2022-09-01 21:49:29 +02:00
Riyyi a806f173ce Emulator: Add getters for CPU registers 2022-09-01 18:13:32 +02:00
Riyyi 3b132a6820 Test: Add way to run opcodes in test cases, add test for ADC, POP, PUSH 2022-09-01 18:05:33 +02:00
Riyyi b63f0d3b52 Test: Update CPU test for new carry logic 2022-09-01 17:34:48 +02:00
Riyyi b48f308bfc Emulator: Simplify isCarry(Subtraction) function 2022-09-01 17:23:07 +02:00
Riyyi 059441c30a Emulator: Fix ADC/CP/DEC/SBC/SUB opcode: 0x8e, 0xbe, 0x35, 0x93, 0x96
Most of these were broken on the carry and half-carry calculation.
2022-09-01 16:59:27 +02:00
Riyyi a4743fa77b Emulator: Fix LD A,(HL-) opcode: 0x3a 2022-09-01 14:26:58 +02:00
Riyyi 70778840c7 Emulator: Fix SLA/SRA/SRL opcodes: 0x26, 0x2e, 0x3e 2022-09-01 14:26:58 +02:00
Riyyi c396e7fcf6 Emulator: Fix LD L,(HL) opcode 0x6e 2022-09-01 14:26:58 +02:00
Riyyi 92c0236f2a Emulator: Rename ldffi8 => ldff8 and move two opcodes to it 2022-09-01 14:26:58 +02:00
Riyyi f56984b555 Emulator: Fix LD A,(0xff00 + C) and its reverse, opcodes: 0xe2, 0xf2 2022-09-01 14:26:58 +02:00
Riyyi 1dd9a2c16e Doc: Add definition for lsb/msb, (least|most) significant byte 2022-09-01 14:26:58 +02:00
Riyyi 1c0809b611 Emulator: Fix LD a16,SP opcode: 0x08 2022-09-01 14:26:58 +02:00
Riyyi c8ad40c128 Emulator: Fix LD A,a16 opcode: 0xfa 2022-09-01 14:26:58 +02:00
Riyyi cc46a45a73 Emulator: Print blargg's test ROM output 2022-09-01 14:26:58 +02:00
Riyyi ed045a72b2 Emulator: Prepare for running blargg's test ROMs 2022-09-01 14:20:57 +02:00
Riyyi 38b740856e Emulator: Fix 2 opcodes ending up in the wrong location 2022-09-01 00:51:35 +02:00
Riyyi e1eed33410 Emulator: Fake screen scrolling to pass the bootrom 2022-09-01 00:51:35 +02:00
Riyyi 2b7d4dee3d Emulator: Add LCDC register to PPU 2022-08-31 21:43:56 +02:00
Riyyi 5831a23b02 Meta: Update ruc library 2022-08-31 21:33:09 +02:00
Riyyi c7903c0a50 Emulator: Fix address pushed onto stack in CALL opcode 2022-08-31 20:30:23 +02:00
Riyyi 5d054c90f8 Emulator: Update comment wording 2022-08-31 20:21:44 +02:00
Riyyi 5a690a12d2 Meta: Add script to check for missing opcodes 2022-08-31 10:16:11 +02:00
Riyyi 461fc28c94 Emulator: Add support for negative numbers to DAA opcode 2022-08-31 01:22:36 +02:00
Riyyi 6c2e5d32d4 Emulator: Implement DAA opcode 2022-08-30 18:10:47 +02:00
Riyyi 525391144a Emulator: Fix increment HL opcode 2022-08-30 16:41:20 +02:00
Riyyi cfded56dcd Emulator: Fix offset bug in readMemory() function 2022-08-30 16:24:51 +02:00
Riyyi 024e6aecc3 Emulator: Add comments to cycle waiting amount 2022-08-30 15:20:54 +02:00
Riyyi baa679904c Emulator: Implement RET opcodes, minus one 2022-08-29 01:54:31 +02:00
Riyyi 0c4d7b2112 Emulator: Implement JP opcodes 2022-08-29 01:31:02 +02:00
Riyyi 3346b5329a Emulator: Implement XOR/OR opcodes 2022-08-29 01:12:59 +02:00
Riyyi 275739bd5b Emulator: Implement SUB/SBC opcodes 2022-08-29 00:26:40 +02:00
Riyyi d47dddfe69 Emulator: Implement ADC opcode 2022-08-28 22:47:30 +02:00
Riyyi bf8bd66389 Emulator: Implement 8-bit ADD opcodes 2022-08-28 22:47:12 +02:00
Riyyi 3bac216698 Emulator: Add some missing LD opcodes 2022-08-28 22:16:01 +02:00
Riyyi 7dc569b1a1 Emulator: Add SCF/CCF opcodes 2022-08-28 22:16:01 +02:00
Riyyi 5015b9e59d Emulator: Implement 16-bit INC opcode 2022-08-28 18:12:19 +02:00
Riyyi a7169addd8 Emulator: Rename inc() => inc8() 2022-08-28 17:49:49 +02:00
Riyyi 4dffe1a60a Emulator: Implement push/pop opcodes 2022-08-28 17:44:59 +02:00
Riyyi 69760e349e Emulator: Fix compiler warnings in cpu-prefix.cpp 2022-08-28 17:44:59 +02:00
Riyyi 200cf12a8f Emulator: Fix switch case numbers for prefix opcodes 2022-08-28 16:43:01 +02:00
Riyyi c4ccbecaf7 Emulator: Add warning for illegal opcodes 2022-08-28 16:25:39 +02:00
Riyyi ec0f8bf02e Emulator: Implement SLA/SRA/SRL opcodes 2022-08-27 13:43:15 +02:00
Riyyi ac03d70d83 fixup! Emulator: Implement SWAP opcodes 2022-08-27 13:38:04 +02:00
Riyyi 99c768d400 Emulator: Rename reg => register_ 2022-08-27 13:36:07 +02:00
Riyyi fb6ba43ff1 Emulator: Implement RL/RR opcodes 2022-08-27 13:36:07 +02:00
Riyyi 275cab4da4 Emulator: Implement RLC/RRC opcodes 2022-08-27 13:08:21 +02:00
Riyyi d53f4759e3 Emulator: Implement SWAP opcodes 2022-08-27 12:43:01 +02:00
Riyyi 67698528e1 Emulator: Implement SET opcodes 2022-08-27 02:17:37 +02:00
Riyyi e35aa06471 Emulator: Implement RES opcodes 2022-08-27 02:15:18 +02:00
Riyyi 6ada7261e7 Emulator: Add .cpp file for prefix $cb opcodes, implement BIT opcodes 2022-08-27 02:15:18 +02:00
Riyyi ff2f94fc05 Doc: Added mnemonics for two opcode constants 2022-08-27 00:03:34 +02:00
Riyyi 81fe126068 Emulator: Add cartridge ROM bank loading 2022-08-26 07:22:44 +02:00
Riyyi 27abccf863 Emulator: Implement nop/rst opcodes 2022-08-26 07:14:32 +02:00
Riyyi e7e1b58d01 Emulator: Add error message when reading/writing unmapped memory space 2022-08-26 06:11:57 +02:00
Riyyi d33837f118 Emulator: Implement call opcodes 2022-08-26 05:39:09 +02:00
Riyyi 219520965c Emulator: Implement Accumulator rotate opcodes 2022-08-25 19:53:40 +02:00
Riyyi 462fa09a00 Emulator: Implement AND opcodes 2022-08-25 15:11:03 +02:00
Riyyi be3ee22086 Emulator: Add error message when reading unloaded cartridge header 2022-08-25 14:56:33 +02:00
Riyyi c9e2bf9e94 Emulator: Implement 16-bit add opcodes 2022-08-25 14:56:33 +02:00
Riyyi c81db1d3b5 Emulator: Rename decr16 => dec16 as there is only one variant 2022-08-25 14:55:58 +02:00
Riyyi 69dc545bf0 Emulator: Implement 8-bit decrement opcodes 2022-08-25 14:55:58 +02:00
Riyyi 802995b7d9 Emulator: Implement JR opcodes 2022-08-25 14:55:58 +02:00
Riyyi eb0f4b6a5f Doc: Add explanation of flag conditions 2022-08-25 11:42:46 +02:00
Riyyi 67f8bcfca9 Emulator: Implement INC and CP arithmetic opcodes 2022-08-25 02:02:57 +02:00
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
Riyyi 009938b126 Meta: Update ruc library 2022-08-24 22:10:19 +02:00
Riyyi 50e1672524 Emulator: Fix opcode logic in the ldi16 function 2022-08-24 21:21:12 +02:00
Riyyi 1b713f8ab0 Emulator: Make ldi8 function more compact 2022-08-24 21:14:59 +02:00
Riyyi 3fcc6823e8 Emulator: Add last LD opcode 2022-08-24 21:14:59 +02:00
Riyyi efe798084f Emulator: Add last 8-bit LD opcode variants 2022-08-24 21:14:59 +02:00
Riyyi c630a7aab6 Emulator: Add more LD opcodes 2022-08-24 20:52:47 +02:00
Riyyi 8cc8f711e9 Emulator: Reorganize 16-bit and more 8-bit load opcode functions 2022-08-24 20:07:10 +02:00
Riyyi 4220c4fcd2 Emulator: Put opcode comment on the case label line 2022-08-24 19:44:16 +02:00
Riyyi 9f12eaa5ff Emulator: Reorganize 8-bit load opcode functions 2022-08-24 19:44:09 +02:00
Riyyi 4c24ade250 Test: Add unit test for carry calculation 2022-08-24 18:57:00 +02:00
Riyyi 1cd6c428e3 Emulator: Update opcode commentary to decided terminology 2022-08-24 18:56:57 +02:00
Riyyi 002c992e0d Emulator: Add FIXME to verify shared register necessity 2022-08-24 18:55:59 +02:00
Riyyi cc120891ca Emulator: Add isCarry() function to CPU 2022-08-24 18:55:47 +02:00
Riyyi 1f25f875e6 Doc: Make the half-carry flag explanation more clear 2022-08-24 15:27:08 +02:00
Riyyi ab550f6d3e Emulator: Fix offset bug in writeMemory() function 2022-08-23 23:08:54 +02:00
Riyyi fe020c9374 Doc: Decide on opcode mnemonics 2022-08-23 23:08:21 +02:00
Riyyi e2ca4359f2 Emulator: Add opcode $20/$0d/$2f 2022-08-23 20:49:46 +02:00
Riyyi 446a777e40 Emulator: Add Formatter for CPU class 2022-08-23 17:03:52 +02:00
Riyyi 1acb5af23e Emulator: Add function to retrieve AF/PC/SP register 2022-08-23 16:55:35 +02:00
Riyyi a20d756e7b Meta: Update ruc library 2022-08-23 16:55:31 +02:00
Riyyi 80e15531f2 Emulator: Add bitmask for additional safety in read/write functions 2022-08-23 13:47:02 +02:00
Riyyi 16235b4663 Emulator: Add opcode $0a/$0e/$1a/$1e/$2a/$2e/$3a 2022-08-23 13:38:08 +02:00
Riyyi af07357812 Emulator: Add opcode $a8/$af 2022-08-23 01:28:16 +02:00
Riyyi 7e54c733a8 Emulator: Add opcode $cd 2022-08-22 23:24:36 +02:00
Riyyi 5d2ee7cbc9 Emulator: Add opcodes $6/$16/$26/$36 2022-08-22 22:53:52 +02:00
Riyyi 2dc4da71e4 Emulator: Add mnemonic comments to opcodes 2022-08-22 22:53:02 +02:00
Riyyi 6bbc705651 Emulator: Make read/write function more generic 2022-08-22 22:47:32 +02:00
Riyyi 5035b26f8a Emulator: Add opcodes $2/$12/$22/$32 2022-08-22 21:49:24 +02:00
Riyyi afedc8a30b Emulator: Some cleanup in the CPU class 2022-08-22 21:49:24 +02:00
Riyyi c83d322d0c Emulator: Make read/write function more generic 2022-08-22 21:49:24 +02:00
Riyyi 2c870e89c6 Emulator: Make memory spaces more accurate 2022-08-22 13:29:54 +02:00
Riyyi e2db1598af Emulator: Change opcode lookup table to regular switch case 2022-08-22 13:25:59 +02:00
Riyyi 2a234fabfd Emulator: Add opcode LDH or LD 0xff00 2022-08-22 13:25:59 +02:00
Riyyi 07345d0552 Emulator: Add some more opcodes 2022-08-22 13:10:30 +02:00
Riyyi 7689efeb44 Meta: Allow single line short case labels in .clang-format 2022-08-22 12:49:44 +02:00
Riyyi 6a44e48244 Doc: Add reference section to terminology.org, add links to mnemonics 2022-08-22 11:47:55 +02:00
Riyyi 4cc21a1c3a Doc: Add document that describes the GameBoy terminology 2022-08-22 11:33:09 +02:00
Riyyi ab5d9cbe69 Emulator: Fix loading 16-bit immediate value
The 8 lower bits are read first. The 8 higher bits come afterwards!
2022-08-21 23:41:26 +02:00
Riyyi 077ba70a16 Emulator: Read bootrom into memory space 2022-08-21 23:40:40 +02:00
Riyyi 5d89883d15 Emulator: Fix Emu memory writing 2022-08-21 22:17:58 +02:00
Riyyi 8768daabfe Emulator: Remove bootrom as separate Emu member variable 2022-08-21 21:13:16 +02:00
Riyyi e38e35ba81 Emulator: Add checks for undefined memory spaces 2022-08-21 20:59:14 +02:00
Riyyi dab457b31f Emulator: Be more const correct 2022-08-21 20:07:43 +02:00
Riyyi 46f3009124 Emulator: Add writing of ECHO RAM 2022-08-21 20:05:10 +02:00
Riyyi 65c3797030 Emulator: Fix memory end address for bootrom segment 1 2022-08-21 19:57:29 +02:00
Riyyi b6e075108e Emulator: Update calls for adding memory spaces 2022-08-21 19:55:18 +02:00
Riyyi 42e54f1344 Emulator: Add support for memory banks to memory spaces 2022-08-21 19:54:02 +02:00
Riyyi 687990edca Emulator: Add more opcodes, convenience functions 2022-08-20 20:24:59 +02:00
Riyyi aa15d9cd3f Doc: Reorder readme reference sections 2022-08-20 12:31:43 +02:00
Riyyi 379ff3babf Doc: Add bootrom section to readme 2022-08-20 12:27:43 +02:00
Riyyi cee8ffabb3 Emulator: Make memory spaces more accurate 2022-08-20 12:27:05 +02:00
Riyyi f6478da541 Emulator: Add copyright header to main, Emu 2022-08-20 11:06:51 +02:00
Riyyi 895b54e927 Emulator: Add opcode function to lookup table 2022-08-20 03:56:31 +02:00
Riyyi d511ec8397 Emulator: Add opcode lookup table 2022-08-20 03:50:04 +02:00
Riyyi a68171002c Emulator: Fix out of scope destruction of string_view 2022-08-20 03:43:00 +02:00
Riyyi 85d54c9bcb Emulator: Prefer string_view over const char* 2022-08-20 02:28:56 +02:00
Riyyi 3163d0cb95 Emulator: Read bootrom in Emu class 2022-08-20 02:26:10 +02:00
Riyyi 7f8f8fd493 Meta: Update ruc library 2022-08-20 02:25:31 +02:00
Riyyi b82ec9eb11 Emulator: Use constructor initialization list 2022-08-20 01:38:56 +02:00
Riyyi e3c3875195 Emulator: Store registers separately 2022-08-19 21:49:23 +02:00
Riyyi dcc517c28d Emulator: Set registers for the power-up sequence 2022-08-19 21:09:22 +02:00
Riyyi 52cd604eb1 Emulator: Do not store overflown bits 2022-08-19 14:30:40 +02:00
Riyyi 0e67e7e2cc Emulator: Add register sharing to processing units 2022-08-19 02:56:46 +02:00
Riyyi 68f78a0299 Emulator: Change register type to uint32_t 2022-08-19 02:52:22 +02:00
Riyyi 21da21a760 Meta: Update ruc library 2022-08-19 02:49:38 +02:00
Frank 2ee1fa147d Merge branch 'master' of https://github.com/Riyyi/garbage 2022-08-19 00:22:46 +02:00
Frank fa066cbd04 Emu: Add cycle accurate timing 2022-08-19 00:22:39 +02:00
Riyyi c691c6287d Meta: Update ruc library 2022-08-19 00:21:23 +02:00
Riyyi 5f44f71e1a CMake: Fix unit test target 2022-08-19 00:10:11 +02:00
Riyyi ccbabdd5b3 Meta: Update ruc library 2022-08-18 23:31:39 +02:00
Riyyi f1aa4f33e2 Meta: Update ruc library 2022-08-18 22:24:29 +02:00
Riyyi 0db89cf109 Doc: Add contributing section to readme 2022-08-18 20:37:27 +02:00
Riyyi 22f3ed8b84 Meta: Update ruc library 2022-08-18 20:24:20 +02:00
Riyyi c159e09ce3 Emulator: Use formatting library print function 2022-08-18 14:36:24 +02:00
Riyyi 2c3109e795 Emulator: Add cycles to wait after executing opcode 2022-08-18 14:35:24 +02:00
Riyyi e95e7452fe Emulator: Change register af 16-bit => a 8-bit 2022-08-18 14:31:39 +02:00
Riyyi 1bfcfcc1f1 Doc: Present references as a list 2022-08-18 02:35:38 +02:00
Riyyi 2eeb255a27 Doc: Add references to readme 2022-08-18 02:33:02 +02:00
Riyyi 6da0184713 Emulator: Add opcode example 2022-08-18 02:29:08 +02:00
Riyyi de2237fac2 Emulator: Dont take a signed number as size 2022-08-18 02:06:02 +02:00
Riyyi 83d3a33e14 Emulator: Prefer uint32_t over unsigned int 2022-08-18 02:05:31 +02:00
Riyyi cef2ebd926 Emulator: Initialize primitive members 2022-08-18 02:03:29 +02:00
Riyyi 57576aaf9b Emulator: Use formatting library print function 2022-08-18 02:03:29 +02:00
Riyyi 4df59970da Emulator: Run clang-format 2022-08-18 01:57:39 +02:00
Riyyi 915db7dc85 Emulator: Fix vector memory reservation 2022-08-18 01:52:10 +02:00
Frank 083cf2f732 Emu:: Add broken function call 2022-08-18 01:28:38 +02:00
Frank 31dc57733f Merge branch 'master' of garbage 2022-08-18 00:45:24 +02:00
Frank 265bf09845 Emu: Add clock divider support for PUs 2022-08-18 00:43:41 +02:00
Riyyi 351f4a9c8d Merge branch 'master' of github.com:Riyyi/garbage 2022-08-18 00:27:20 +02:00
Frank 175227cca5 Merge branch 'master' of https://github.com/Riyyi/garbage 2022-08-17 23:41:52 +02:00
Frank f15c560b5d Emu: Add functionality to add Memory and PUs 2022-08-17 23:41:46 +02:00
29 changed files with 3764 additions and 118 deletions
+1
View File
@@ -14,6 +14,7 @@ AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: true AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortLambdasOnASingleLine: All AllowShortLambdasOnASingleLine: All
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes
+3 -3
View File
@@ -1,3 +1,3 @@
[submodule "vendor/ruc"] [submodule "vendor/inferno"]
path = vendor/ruc path = vendor/inferno
url = https://github.com/riyyi/ruc url = https://github.com/riyyi/inferno
+15 -10
View File
@@ -52,21 +52,28 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# ------------------------------------------ # ------------------------------------------
# Library # Library
set(RUC_BUILD_TESTS OFF) set(INFERNO_BUILD_EXAMPLES OFF)
add_subdirectory("vendor/ruc") add_subdirectory("vendor/inferno")
# ------------------------------------------ # ------------------------------------------
# Application target # Application target
# Define source files # Define source files
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp") file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
set(PROJECT_SOURCES ${PROJECT_SOURCES})
add_executable(${PROJECT} ${PROJECT_SOURCES}) add_executable(${PROJECT} ${PROJECT_SOURCES})
target_include_directories(${PROJECT} PRIVATE target_include_directories(${PROJECT} PRIVATE
"src" "src")
"vendor/ruc/src") target_link_libraries(${PROJECT} inferno)
target_link_libraries(${PROJECT} ruc)
# ------------------------------------------
# Assets target
add_custom_target(${PROJECT}-assets
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/copy-assets.cmake
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_dependencies(${PROJECT} ${PROJECT}-assets)
add_dependencies(${PROJECT}-assets inferno-assets) # Copy project assets *AFTER* engine assets!
# ------------------------------------------ # ------------------------------------------
# Execute target # Execute target
@@ -87,8 +94,6 @@ if (GARBAGE_BUILD_TESTS)
add_executable(${PROJECT}-unit-test ${TEST_SOURCES}) add_executable(${PROJECT}-unit-test ${TEST_SOURCES})
target_include_directories(${PROJECT}-unit-test PRIVATE target_include_directories(${PROJECT}-unit-test PRIVATE
"src" "src"
"test" "test")
"vendor/ruc/src" target_link_libraries(${PROJECT}-unit-test inferno)
"vendor/ruc/test")
target_link_libraries(${PROJECT}-unit-test ruc-test)
endif() endif()
+39 -14
View File
@@ -9,32 +9,57 @@ Accurate GameBoy emulator.
*** Clone *** Clone
#+BEGIN_SRC sh #+BEGIN_SRC shell-script
$ git clone https://gitlab.com/riyyi/garbage $ git clone https://github.com/riyyi/garbage
$ cd garbage $ cd garbage
$ git submodule init $ git submodule update --init --recursive
$ git submodule update
#+END_SRC #+END_SRC
Or Or
#+BEGIN_SRC sh #+BEGIN_SRC shell-script
$ git clone --recursive https://gitlab.com/riyyi/garbage $ git clone --recursive https://github.com/riyyi/garbage
#+END_SRC #+END_SRC
*** Update *** Update
#+BEGIN_SRC sh #+BEGIN_SRC shell-script
$ git pull $ git pull
$ git submodule update --recursive $ git submodule update --recursive
#+END_SRC #+END_SRC
** Build instructions ** Build instructions
#+BEGIN_SRC sh #+BEGIN_SRC shell-script
$ mkdir build $ mkdir build
$ cd build $ cd build
$ cmake .. && make $ cmake .. && make
#+END_SRC
** Contributing
Enable 'commit-hooks' to lint your changes before committing them.
#+BEGIN_SRC shell-script
$ ./vendor/ruc/script/pre-commit.sh install
#+END_SRC #+END_SRC
** Libraries ** Libraries
- [[https://github.com/riyyi/ruc][ruc]] - [[https://github.com/riyyi/ruc][ruc]]
** References
*** Opcodes
- https://gbdev.io/gb-opcodes/optables/
- https://gb.insertcoin.dev/
- https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7/
- https://gbdev.io/pandocs/CPU_Instruction_Set.html
*** Bootstrap ROMs
- https://github.com/ISSOtm/gb-bootroms
*** Test ROMs
- https://github.com/gbdev/awesome-gbdev#testing
- https://tasvideos.org/EmulatorResources/GBAccuracyTests
+16
View File
@@ -0,0 +1,16 @@
{
"camera": {
"name": "Camera",
"translate": [ 0.0, 0.0, -1.0 ],
"rotate": [ 0.0, 0.0, 0.0 ],
"scale": [ 1.0, 1.0, 1.0 ],
"type": "orthographic",
"zoom-level": 0.45
},
"quad" : [
{
"name": "Screen",
"scale": [ 1.0, -0.9, 1.0 ]
}
]
}
+9
View File
@@ -0,0 +1,9 @@
{
"window": {
"fullscreen": "windowed",
"height": 432,
"title": "GarbAGE",
"vsync": true,
"width": 480
}
}
+1
View File
@@ -0,0 +1 @@
file(COPY ${CMAKE_CURRENT_LIST_DIR}/../assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+85
View File
@@ -0,0 +1,85 @@
#+TITLE: Terminology
#+AUTHOR: Riyyi
#+LANGUAGE: en
#+OPTIONS: toc:nil
GameBoy terminology.
** Terminology
*** Hardware [[#references][(1)]][[#references][(2)]]
| Name | Modern equivalent | Description |
|-----------------+-------------------+-------------------------------------------|
| APU | Sound card | Audio Processing Unit |
| PPU | GPU | Pixel Processing Unit |
| Bootrom | BIOS | |
| HRAM (High RAM) | CPU cache | Embedded memory inside the CPU, 127 bytes |
| WRAM (Work RAM) | RAM | |
*** Flags
The half-carry flag ~H~ is set when an arithmetic operation on two numbers
causes a carry from bit #3 to bit #4 for 8-bit operations or a carry from bit
#11 to bit #12 for 16-bit operations. For 16-bit operation this indicates the
lower 4 bits of the *second* byte[[#references][(3)]].
We start counting from the right as bit *#0*!
*** Opcode
| Name | Bits | Sign | Mnemonics | [[https://gbdev.io/gb-opcodes/optables/][1]] | [[https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7/][2]] | [[https://gekkio.fi/files/gb-docs/gbctr.pdf][3]]/[[http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf][3]] | [[https://gbdev.io/pandocs/CPU_Instruction_Set.html][4]] | Description | Note |
|-----------+------+----------+-----------+------+-----+------+-----+------------------------------+------------------------|
| | | | | <r> | <r> | <r> | <r> | | |
| immediate | 8 | unsigned | i8 | d8 | n8 | n | | Next byte in memory | |
| immediate | 16 | unsigned | i16 | d16 | n16 | nn | | Next byte in memory | little-endian |
| 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 or () | a16 | n16 | (nn) | nn | Address at value | little-endian |
| immediate | 8 | signed | s8 | r8 | e8 | e | dd | Next byte in memory | used as offset |
| immediate | 8 | unsigned | io8 | (a8) | n16 | (n) | n | 0xff00 + next byte in memory | write to I/O-port |
| condition | | | <ZNHC> | | cc | | f | Execute if condition met | |
| constant | 8 | unsigned | b | | u3 | b | n | Single bit at position 'x' | |
| constant | 8 | unsigned | vec | | vec | | n | One of RST vectors | absolute fixed address |
d = data \\
n = constant
**** Flag conditions
Flag conditions, sometimes referred to as ~cc~, are the state the flags need to
be in to execute the opcode. There are four possible checks[[#references][(4)]][[#references][(5)]]:
| Code | Meaning | State |
|------+--------------------------------+-------|
| Z | Execute if Zero flag is set | 1 |
| NZ | Execute if Zero flag is unset | 0 |
| C | Execute if Carry flag is set | 1 |
| NC | Execute if Carry flag is unset | 0 |
**** RST vectors
Absolute fixed address, possible values are[[#references][(4)]]: ~0x00~, ~0x08~, ~0x10~, ~0x18~,
~0x20~, ~0x28~, ~0x30~, and ~0x38~.
*** Variables
| Name | Meaning |
|--------+------------------------|
| length | amount of elements |
| size | amount of bytes |
|--------+------------------------|
| lhs | left hand side |
| rhs | right hand side |
|--------+------------------------|
| lsb | least significant byte |
| msb | most significant byte |
** References
1. [[https://gbdev.io/pandocs/Memory_Map.html]["Memory Map"]]. rgbdev.io. Retrieved 22 Aug 2022.
2. [[https://gbdev.io/pandocs/Specifications.html]["Specifications"]]. rgbdev.io. Retrieved 22 Aug 2022.
3. [[https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/]["A Visual Guide to the Gameboy's Half-Carry Flag"]]. robdor.com. Retrieved 24 Aug 2022.
4. [[https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7#LEGEND]["gbz80(7) — CPU opcode reference"]]. rgbds.gbdev.io. Retrieved 26 Aug 2022.
5. [[http://www.devrs.com/gb/files/opcodes.html]["GameBoy Opcode Summary"]]. devrs.com. Retrieved 25 Aug 2022.
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
# Get the full path to this script while handling spaces and symlinks correctly
scriptPath="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
cd "$scriptPath/.." || exit 1
opcode_check() {
path="$1"
file="$(basename "$path")"
max=255
for i in $(seq 1 "$max")
do
opcode=$(printf "0x%.2x\n" "$i")
# Pattern: case 0x??:
if ! grep -q "case $opcode:" "$path"; then
echo "missing opcode: $opcode in $file"
fi
done
}
opcode_check "src/cpu.cpp"
opcode_check "src/cpu-prefix.cpp"
+2 -1
View File
@@ -7,7 +7,8 @@
#include "apu.h" #include "apu.h"
APU::APU() APU::APU(unsigned int frequency)
: ProcessingUnit(frequency)
{ {
} }
+1 -1
View File
@@ -11,6 +11,6 @@
class APU : public ProcessingUnit { class APU : public ProcessingUnit {
public: public:
APU(); APU(unsigned int frequency);
virtual ~APU(); virtual ~APU();
}; };
+704
View File
@@ -0,0 +1,704 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstdint> // uint8_t, uint32_t
#include "cpu.h"
#include "ruc/format/print.h"
#include "ruc/meta/assert.h"
void CPU::prefix()
{
// Note: All these opcodes are considered 2 bytes, as the prefix is included
// Skip 0xcb prefix opcode
m_pc = (m_pc + 1) & 0xffff;
// Read next opcode
uint8_t opcode = read(m_pc);
// print("running opcode: {:#04x} @ ({:#06x})\n", opcode, m_pc);
if (opcode <= 0x07) {
rlc();
}
else if (opcode >= 0x08 && opcode <= 0x0f) {
rrc();
}
else if (opcode >= 0x10 && opcode <= 0x17) {
rl();
}
else if (opcode >= 0x18 && opcode <= 0x1f) {
rr();
}
else if (opcode >= 0x20 && opcode <= 0x27) {
sla();
}
else if (opcode >= 0x28 && opcode <= 0x2f) {
sra();
}
else if (opcode >= 0x30 && opcode <= 0x37) {
swap();
}
else if (opcode >= 0x38 && opcode <= 0x3f) {
srl();
}
else if (opcode >= 0x40 && opcode <= 0x7f) {
bit();
}
else if (opcode >= 0x80 && opcode <= 0xbf) {
res();
}
else if (opcode >= 0xc0) {
set();
}
else {
print("opcode {:#04x} not implemented\n", opcode);
print("immediate: {:#04x}\n", pcRead());
VERIFY_NOT_REACHED();
}
}
void CPU::bit()
{
auto test_bit = [this](uint32_t bit, uint32_t byte) -> void {
// BIT b,r8, flags: Z 0 1 -
m_wait_cycles += 8;
// Set flags
// Set zero flag if bit at position 'x' of register r8 is 0
m_zf = (byte & bit) == 0x0;
m_nf = 0;
m_hf = 1;
};
auto test_bit_hl = [this](uint32_t bit) -> void {
// BIT b,(HL), flags: Z 0 1 -
m_wait_cycles += 12;
// Set flags
// Set zero flag if bit at position 'x' of byte pointed by HL is 0
m_zf = (read(hl()) & bit) == 0x0;
m_nf = 0;
m_hf = 1;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x40: /* BIT 0,B */ test_bit(0x01, m_b); break;
case 0x41: /* BIT 0,C */ test_bit(0x01, m_c); break;
case 0x42: /* BIT 0,D */ test_bit(0x01, m_d); break;
case 0x43: /* BIT 0,E */ test_bit(0x01, m_e); break;
case 0x44: /* BIT 0,H */ test_bit(0x01, m_h); break;
case 0x45: /* BIT 0,L */ test_bit(0x01, m_l); break;
case 0x46: /* BIT 0,(HL) */ test_bit_hl(0x01); break;
case 0x47: /* BIT 0,A */ test_bit(0x01, m_a); break;
case 0x48: /* BIT 1,B */ test_bit(0x02, m_b); break;
case 0x49: /* BIT 1,C */ test_bit(0x02, m_c); break;
case 0x4a: /* BIT 1,D */ test_bit(0x02, m_d); break;
case 0x4b: /* BIT 1,E */ test_bit(0x02, m_e); break;
case 0x4c: /* BIT 1,H */ test_bit(0x02, m_h); break;
case 0x4d: /* BIT 1,L */ test_bit(0x02, m_l); break;
case 0x4e: /* BIT 1,(HL) */ test_bit_hl(0x02); break;
case 0x4f: /* BIT 1,A */ test_bit(0x02, m_a); break;
case 0x50: /* BIT 2,B */ test_bit(0x04, m_b); break;
case 0x51: /* BIT 2,C */ test_bit(0x04, m_c); break;
case 0x52: /* BIT 2,D */ test_bit(0x04, m_d); break;
case 0x53: /* BIT 2,E */ test_bit(0x04, m_e); break;
case 0x54: /* BIT 2,H */ test_bit(0x04, m_h); break;
case 0x55: /* BIT 2,L */ test_bit(0x04, m_l); break;
case 0x56: /* BIT 2,(HL) */ test_bit_hl(0x04); break;
case 0x57: /* BIT 2,A */ test_bit(0x04, m_a); break;
case 0x58: /* BIT 3,B */ test_bit(0x08, m_b); break;
case 0x59: /* BIT 3,C */ test_bit(0x08, m_c); break;
case 0x5a: /* BIT 3,D */ test_bit(0x08, m_d); break;
case 0x5b: /* BIT 3,E */ test_bit(0x08, m_e); break;
case 0x5c: /* BIT 3,H */ test_bit(0x08, m_h); break;
case 0x5d: /* BIT 3,L */ test_bit(0x08, m_l); break;
case 0x5e: /* BIT 3,(HL) */ test_bit_hl(0x08); break;
case 0x5f: /* BIT 3,A */ test_bit(0x08, m_a); break;
case 0x60: /* BIT 4,B */ test_bit(0x10, m_b); break;
case 0x61: /* BIT 4,C */ test_bit(0x10, m_c); break;
case 0x62: /* BIT 4,D */ test_bit(0x10, m_d); break;
case 0x63: /* BIT 4,E */ test_bit(0x10, m_e); break;
case 0x64: /* BIT 4,H */ test_bit(0x10, m_h); break;
case 0x65: /* BIT 4,L */ test_bit(0x10, m_l); break;
case 0x66: /* BIT 4,(HL) */ test_bit_hl(0x10); break;
case 0x67: /* BIT 4,A */ test_bit(0x10, m_a); break;
case 0x68: /* BIT 5,B */ test_bit(0x20, m_b); break;
case 0x69: /* BIT 5,C */ test_bit(0x20, m_c); break;
case 0x6a: /* BIT 5,D */ test_bit(0x20, m_d); break;
case 0x6b: /* BIT 5,E */ test_bit(0x20, m_e); break;
case 0x6c: /* BIT 5,H */ test_bit(0x20, m_h); break;
case 0x6d: /* BIT 5,L */ test_bit(0x20, m_l); break;
case 0x6e: /* BIT 5,(HL) */ test_bit_hl(0x20); break;
case 0x6f: /* BIT 5,A */ test_bit(0x20, m_a); break;
case 0x70: /* BIT 6,B */ test_bit(0x40, m_b); break;
case 0x71: /* BIT 6,C */ test_bit(0x40, m_c); break;
case 0x72: /* BIT 6,D */ test_bit(0x40, m_d); break;
case 0x73: /* BIT 6,E */ test_bit(0x40, m_e); break;
case 0x74: /* BIT 6,H */ test_bit(0x40, m_h); break;
case 0x75: /* BIT 6,L */ test_bit(0x40, m_l); break;
case 0x76: /* BIT 6,(HL) */ test_bit_hl(0x40); break;
case 0x77: /* BIT 6,A */ test_bit(0x40, m_a); break;
case 0x78: /* BIT 7,B */ test_bit(0x80, m_b); break;
case 0x79: /* BIT 7,C */ test_bit(0x80, m_c); break;
case 0x7a: /* BIT 7,D */ test_bit(0x80, m_d); break;
case 0x7b: /* BIT 7,E */ test_bit(0x80, m_e); break;
case 0x7c: /* BIT 7,H */ test_bit(0x80, m_h); break;
case 0x7d: /* BIT 7,L */ test_bit(0x80, m_l); break;
case 0x7e: /* BIT 7,(HL) */ test_bit_hl(0x80); break;
case 0x7f: /* BIT 7,A */ test_bit(0x80, m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::res()
{
auto reset_bit = [this](uint32_t bit, uint32_t& register_) -> void {
// RES b,r8
m_wait_cycles += 8;
// Reset bit at position 'x' in register r8
register_ = register_ & (~bit);
};
auto reset_bit_hl = [this](uint32_t bit) -> void {
// RES b,(HL)
m_wait_cycles += 16;
// Set bit at postition 'x' in the byte pointed by HL to 0
uint32_t data = read(hl());
data = data & (~bit);
write(hl(), data);
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x80: /* RES 0,B */ reset_bit(0x01, m_b); break;
case 0x81: /* RES 0,C */ reset_bit(0x01, m_c); break;
case 0x82: /* RES 0,D */ reset_bit(0x01, m_d); break;
case 0x83: /* RES 0,E */ reset_bit(0x01, m_e); break;
case 0x84: /* RES 0,H */ reset_bit(0x01, m_h); break;
case 0x85: /* RES 0,L */ reset_bit(0x01, m_l); break;
case 0x86: /* RES 0,(HL) */ reset_bit_hl(0x01); break;
case 0x87: /* RES 0,A */ reset_bit(0x01, m_a); break;
case 0x88: /* RES 1,B */ reset_bit(0x02, m_b); break;
case 0x89: /* RES 1,C */ reset_bit(0x02, m_c); break;
case 0x8a: /* RES 1,D */ reset_bit(0x02, m_d); break;
case 0x8b: /* RES 1,E */ reset_bit(0x02, m_e); break;
case 0x8c: /* RES 1,H */ reset_bit(0x02, m_h); break;
case 0x8d: /* RES 1,L */ reset_bit(0x02, m_l); break;
case 0x8e: /* RES 1,(HL) */ reset_bit_hl(0x02); break;
case 0x8f: /* RES 1,A */ reset_bit(0x02, m_a); break;
case 0x90: /* RES 2,B */ reset_bit(0x04, m_b); break;
case 0x91: /* RES 2,C */ reset_bit(0x04, m_c); break;
case 0x92: /* RES 2,D */ reset_bit(0x04, m_d); break;
case 0x93: /* RES 2,E */ reset_bit(0x04, m_e); break;
case 0x94: /* RES 2,H */ reset_bit(0x04, m_h); break;
case 0x95: /* RES 2,L */ reset_bit(0x04, m_l); break;
case 0x96: /* RES 2,(HL) */ reset_bit_hl(0x04); break;
case 0x97: /* RES 2,A */ reset_bit(0x04, m_a); break;
case 0x98: /* RES 3,B */ reset_bit(0x08, m_b); break;
case 0x99: /* RES 3,C */ reset_bit(0x08, m_c); break;
case 0x9a: /* RES 3,D */ reset_bit(0x08, m_d); break;
case 0x9b: /* RES 3,E */ reset_bit(0x08, m_e); break;
case 0x9c: /* RES 3,H */ reset_bit(0x08, m_h); break;
case 0x9d: /* RES 3,L */ reset_bit(0x08, m_l); break;
case 0x9e: /* RES 3,(HL) */ reset_bit_hl(0x08); break;
case 0x9f: /* RES 3,A */ reset_bit(0x08, m_a); break;
case 0xa0: /* RES 4,B */ reset_bit(0x10, m_b); break;
case 0xa1: /* RES 4,C */ reset_bit(0x10, m_c); break;
case 0xa2: /* RES 4,D */ reset_bit(0x10, m_d); break;
case 0xa3: /* RES 4,E */ reset_bit(0x10, m_e); break;
case 0xa4: /* RES 4,H */ reset_bit(0x10, m_h); break;
case 0xa5: /* RES 4,L */ reset_bit(0x10, m_l); break;
case 0xa6: /* RES 4,(HL) */ reset_bit_hl(0x10); break;
case 0xa7: /* RES 4,A */ reset_bit(0x10, m_a); break;
case 0xa8: /* RES 5,B */ reset_bit(0x20, m_b); break;
case 0xa9: /* RES 5,C */ reset_bit(0x20, m_c); break;
case 0xaa: /* RES 5,D */ reset_bit(0x20, m_d); break;
case 0xab: /* RES 5,E */ reset_bit(0x20, m_e); break;
case 0xac: /* RES 5,H */ reset_bit(0x20, m_h); break;
case 0xad: /* RES 5,L */ reset_bit(0x20, m_l); break;
case 0xae: /* RES 5,(HL) */ reset_bit_hl(0x20); break;
case 0xaf: /* RES 5,A */ reset_bit(0x20, m_a); break;
case 0xb0: /* RES 6,B */ reset_bit(0x40, m_b); break;
case 0xb1: /* RES 6,C */ reset_bit(0x40, m_c); break;
case 0xb2: /* RES 6,D */ reset_bit(0x40, m_d); break;
case 0xb3: /* RES 6,E */ reset_bit(0x40, m_e); break;
case 0xb4: /* RES 6,H */ reset_bit(0x40, m_h); break;
case 0xb5: /* RES 6,L */ reset_bit(0x40, m_l); break;
case 0xb6: /* RES 6,(HL) */ reset_bit_hl(0x40); break;
case 0xb7: /* RES 6,A */ reset_bit(0x40, m_a); break;
case 0xb8: /* RES 7,B */ reset_bit(0x80, m_b); break;
case 0xb9: /* RES 7,C */ reset_bit(0x80, m_c); break;
case 0xba: /* RES 7,D */ reset_bit(0x80, m_d); break;
case 0xbb: /* RES 7,E */ reset_bit(0x80, m_e); break;
case 0xbc: /* RES 7,H */ reset_bit(0x80, m_h); break;
case 0xbd: /* RES 7,L */ reset_bit(0x80, m_l); break;
case 0xbe: /* RES 7,(HL) */ reset_bit_hl(0x80); break;
case 0xbf: /* RES 7,A */ reset_bit(0x80, m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::set()
{
auto set_bit = [this](uint32_t bit, uint32_t& register_) -> void {
// RES b,r8
m_wait_cycles += 8;
// Set bit at position 'x' in register r8
register_ = register_ | bit;
};
auto set_bit_hl = [this](uint32_t bit) -> void {
// RES b,(HL)
m_wait_cycles += 16;
// Set bit at postition 'x' in the byte pointed by HL to 0
uint32_t data = read(hl());
data = data | bit;
write(hl(), data);
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0xc0: /* SET 0,B */ set_bit(0x01, m_b); break;
case 0xc1: /* SET 0,C */ set_bit(0x01, m_c); break;
case 0xc2: /* SET 0,D */ set_bit(0x01, m_d); break;
case 0xc3: /* SET 0,E */ set_bit(0x01, m_e); break;
case 0xc4: /* SET 0,H */ set_bit(0x01, m_h); break;
case 0xc5: /* SET 0,L */ set_bit(0x01, m_l); break;
case 0xc6: /* SET 0,(HL) */ set_bit_hl(0x01); break;
case 0xc7: /* SET 0,A */ set_bit(0x01, m_a); break;
case 0xc8: /* SET 1,B */ set_bit(0x02, m_b); break;
case 0xc9: /* SET 1,C */ set_bit(0x02, m_c); break;
case 0xca: /* SET 1,D */ set_bit(0x02, m_d); break;
case 0xcb: /* SET 1,E */ set_bit(0x02, m_e); break;
case 0xcc: /* SET 1,H */ set_bit(0x02, m_h); break;
case 0xcd: /* SET 1,L */ set_bit(0x02, m_l); break;
case 0xce: /* SET 1,(HL) */ set_bit_hl(0x02); break;
case 0xcf: /* SET 1,A */ set_bit(0x02, m_a); break;
case 0xd0: /* SET 2,B */ set_bit(0x04, m_b); break;
case 0xd1: /* SET 2,C */ set_bit(0x04, m_c); break;
case 0xd2: /* SET 2,D */ set_bit(0x04, m_d); break;
case 0xd3: /* SET 2,E */ set_bit(0x04, m_e); break;
case 0xd4: /* SET 2,H */ set_bit(0x04, m_h); break;
case 0xd5: /* SET 2,L */ set_bit(0x04, m_l); break;
case 0xd6: /* SET 2,(HL) */ set_bit_hl(0x04); break;
case 0xd7: /* SET 2,A */ set_bit(0x04, m_a); break;
case 0xd8: /* SET 3,B */ set_bit(0x08, m_b); break;
case 0xd9: /* SET 3,C */ set_bit(0x08, m_c); break;
case 0xda: /* SET 3,D */ set_bit(0x08, m_d); break;
case 0xdb: /* SET 3,E */ set_bit(0x08, m_e); break;
case 0xdc: /* SET 3,H */ set_bit(0x08, m_h); break;
case 0xdd: /* SET 3,L */ set_bit(0x08, m_l); break;
case 0xde: /* SET 3,(HL) */ set_bit_hl(0x08); break;
case 0xdf: /* SET 3,A */ set_bit(0x08, m_a); break;
case 0xe0: /* SET 4,B */ set_bit(0x10, m_b); break;
case 0xe1: /* SET 4,C */ set_bit(0x10, m_c); break;
case 0xe2: /* SET 4,D */ set_bit(0x10, m_d); break;
case 0xe3: /* SET 4,E */ set_bit(0x10, m_e); break;
case 0xe4: /* SET 4,H */ set_bit(0x10, m_h); break;
case 0xe5: /* SET 4,L */ set_bit(0x10, m_l); break;
case 0xe6: /* SET 4,(HL) */ set_bit_hl(0x10); break;
case 0xe7: /* SET 4,A */ set_bit(0x10, m_a); break;
case 0xe8: /* SET 5,B */ set_bit(0x20, m_b); break;
case 0xe9: /* SET 5,C */ set_bit(0x20, m_c); break;
case 0xea: /* SET 5,D */ set_bit(0x20, m_d); break;
case 0xeb: /* SET 5,E */ set_bit(0x20, m_e); break;
case 0xec: /* SET 5,H */ set_bit(0x20, m_h); break;
case 0xed: /* SET 5,L */ set_bit(0x20, m_l); break;
case 0xee: /* SET 5,(HL) */ set_bit_hl(0x20); break;
case 0xef: /* SET 5,A */ set_bit(0x20, m_a); break;
case 0xf0: /* SET 6,B */ set_bit(0x40, m_b); break;
case 0xf1: /* SET 6,C */ set_bit(0x40, m_c); break;
case 0xf2: /* SET 6,D */ set_bit(0x40, m_d); break;
case 0xf3: /* SET 6,E */ set_bit(0x40, m_e); break;
case 0xf4: /* SET 6,H */ set_bit(0x40, m_h); break;
case 0xf5: /* SET 6,L */ set_bit(0x40, m_l); break;
case 0xf6: /* SET 6,(HL) */ set_bit_hl(0x40); break;
case 0xf7: /* SET 6,A */ set_bit(0x40, m_a); break;
case 0xf8: /* SET 7,B */ set_bit(0x80, m_b); break;
case 0xf9: /* SET 7,C */ set_bit(0x80, m_c); break;
case 0xfa: /* SET 7,D */ set_bit(0x80, m_d); break;
case 0xfb: /* SET 7,E */ set_bit(0x80, m_e); break;
case 0xfc: /* SET 7,H */ set_bit(0x80, m_h); break;
case 0xfd: /* SET 7,L */ set_bit(0x80, m_l); break;
case 0xfe: /* SET 7,(HL) */ set_bit_hl(0x80); break;
case 0xff: /* SET 7,A */ set_bit(0x80, m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::swap()
{
auto swap_bits = [this](uint32_t& register_) -> void {
// SWAP r8, flags: Z 0 0 0
m_wait_cycles += 8;
// Swap upper 4 bits in register r8 with lower 4 bits
register_ = ((register_ >> 4) | (register_ << 4)) & 0xff;
// Set flags
m_zf = (register_ == 0);
m_nf = 0;
m_hf = 0;
m_cf = 0;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x30: /* SWAP B */ swap_bits(m_b); break;
case 0x31: /* SWAP C */ swap_bits(m_c); break;
case 0x32: /* SWAP D */ swap_bits(m_d); break;
case 0x33: /* SWAP E */ swap_bits(m_e); break;
case 0x34: /* SWAP H */ swap_bits(m_h); break;
case 0x35: /* SWAP L */ swap_bits(m_l); break;
case 0x36: /* SWAP (HL) */ {
m_wait_cycles += 8; // + 8 = 16 total
// Swap upper 4 bits in the byte pointed by HL with lower 4 bits
uint32_t data = read(hl());
swap_bits(data);
write(hl(), data);
break;
}
case 0x37: /* SWAP A */ swap_bits(m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::rl()
{
auto rotate_left_carry = [this](uint32_t& register_) -> void {
// RL r8, flags: Z 0 0 C
m_wait_cycles += 8;
// Copy bit 7 into carry flag
uint32_t old_carry = m_cf != 0;
m_cf = (register_ & 0x80) == 0x80;
// Rotate register r8 left through carry
register_ = (old_carry | (register_ << 1)) & 0xff;
// Set other flags
m_zf = (register_ == 0);
m_nf = 0;
m_hf = 0;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x10: /* RL B */ rotate_left_carry(m_b); break;
case 0x11: /* RL C */ rotate_left_carry(m_c); break;
case 0x12: /* RL D */ rotate_left_carry(m_d); break;
case 0x13: /* RL E */ rotate_left_carry(m_e); break;
case 0x14: /* RL H */ rotate_left_carry(m_h); break;
case 0x15: /* RL L */ rotate_left_carry(m_l); break;
case 0x16: /* RL (HL) */ {
m_wait_cycles += 8; // + 8 = 16 total
uint32_t data = read(hl());
rotate_left_carry(data);
write(hl(), data);
break;
}
case 0x17: /* RL A */ rotate_left_carry(m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::rlc()
{
auto rotate_left = [this](uint32_t& register_) -> void {
// RLC r8, flags: Z 0 0 C
m_wait_cycles += 8;
// Copy bit 7 into carry flag
m_cf = (register_ & 0x80) == 0x80;
// Rotate register r8 left
register_ = ((register_ >> 7) | (register_ << 1)) & 0xff;
// Set other flags
m_zf = (register_ == 0);
m_nf = 0;
m_hf = 0;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x00: /* RLC B */ rotate_left(m_b); break;
case 0x01: /* RLC C */ rotate_left(m_c); break;
case 0x02: /* RLC D */ rotate_left(m_d); break;
case 0x03: /* RLC E */ rotate_left(m_e); break;
case 0x04: /* RLC H */ rotate_left(m_h); break;
case 0x05: /* RLC L */ rotate_left(m_l); break;
case 0x06: /* RLC (HL) */ {
m_wait_cycles += 8; // + 8 = 16 total
// Rotate the byte pointed to by HL left
uint32_t data = read(hl());
rotate_left(data);
write(hl(), data);
break;
}
case 0x07: /* RLC A */ rotate_left(m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::rr()
{
auto rotate_right_carry = [this](uint32_t& register_) -> void {
// RR r8, flags: Z 0 0 C
m_wait_cycles += 8;
// Copy bit 0 into carry flag
uint32_t old_carry = m_cf != 0;
m_cf = (register_ & 0x1) == 0x1;
// Rotate register r8 right through carry
register_ = ((register_ >> 1) | (old_carry << 7)) & 0xff;
// Set other flags
m_zf = (register_ == 0);
m_nf = 0;
m_hf = 0;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x18: /* RR B */ rotate_right_carry(m_b); break;
case 0x19: /* RR C */ rotate_right_carry(m_c); break;
case 0x1a: /* RR D */ rotate_right_carry(m_d); break;
case 0x1b: /* RR E */ rotate_right_carry(m_e); break;
case 0x1c: /* RR H */ rotate_right_carry(m_h); break;
case 0x1d: /* RR L */ rotate_right_carry(m_l); break;
case 0x1e: /* RR (HL) */ {
m_wait_cycles += 8; // + 8 = 16 total
uint32_t data = read(hl());
rotate_right_carry(data);
write(hl(), data);
break;
}
case 0x1f: /* RR A */ rotate_right_carry(m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::rrc()
{
auto rotate_right = [this](uint32_t& register_) -> void {
// RRC r8, flags: Z 0 0 C
m_wait_cycles += 8;
// Copy bit 0 into carry flag
m_cf = (register_ & 0x1) == 0x1;
// Rotate register r8 right
register_ = ((register_ >> 1) | (register_ << 7)) & 0xff;
// Set other flags
m_zf = (register_ == 0);
m_nf = 0;
m_hf = 0;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x08: /* RRC B */ rotate_right(m_b); break;
case 0x09: /* RRC C */ rotate_right(m_c); break;
case 0x0a: /* RRC D */ rotate_right(m_d); break;
case 0x0b: /* RRC E */ rotate_right(m_e); break;
case 0x0c: /* RRC H */ rotate_right(m_h); break;
case 0x0d: /* RRC L */ rotate_right(m_l); break;
case 0x0e: /* RRC (HL) */ {
m_wait_cycles += 8; // + 8 = 16 total
// Rotate the byte pointed to by HL right
uint32_t data = read(hl());
rotate_right(data);
write(hl(), data);
break;
}
case 0x0f: /* RRC A */ rotate_right(m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::sla()
{
auto shift_left_arithmatically = [this](uint32_t& register_) -> void {
// SLA r8, flags: Z 0 0 C
m_wait_cycles += 8;
// Shift Left Arithmetically register r8
// ┌─────────┐
// C <─│7 <── 0│<─ 0
// └─────────┘
// r8
// Copy bit 7 into carry flag
m_cf = (register_ & 0x80) == 0x80;
// Shift Left Arithmetically register r8
register_ = (register_ << 1) & 0xff;
// Set other flags
m_zf = (register_ == 0);
m_nf = 0;
m_hf = 0;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x20: /* SLA B */ shift_left_arithmatically(m_b); break;
case 0x21: /* SLA C */ shift_left_arithmatically(m_c); break;
case 0x22: /* SLA D */ shift_left_arithmatically(m_d); break;
case 0x23: /* SLA E */ shift_left_arithmatically(m_e); break;
case 0x24: /* SLA H */ shift_left_arithmatically(m_h); break;
case 0x25: /* SLA L */ shift_left_arithmatically(m_l); break;
case 0x26: /* SLA (HL) */ {
m_wait_cycles += 8; // + 8 = 16 total
// Rotate the byte pointed to by HL right
uint32_t data = read(hl());
shift_left_arithmatically(data);
write(hl(), data);
break;
}
case 0x27: /* SLA A */ shift_left_arithmatically(m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::sra()
{
auto shift_right_arithmatically = [this](uint32_t& register_) -> void {
// SRL r8, flags: Z 0 0 C
m_wait_cycles += 8;
// Shift Right Arithmatically register r8
// ┌─────────┐
// ┌─│7 ──> 0│─> C
// │ └─────────┘
// │ ^ r8
// └──┘
// Copy bit 0 into carry flag
m_cf = (register_ & 0x1) == 0x1;
// Shift Right Arithmatically register r8
register_ = (register_ >> 1) | (register_ & 0x80); // Note: bit 7 remains
// Set other flags
m_zf = (register_ == 0);
m_nf = 0;
m_hf = 0;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x28: /* SRA B */ shift_right_arithmatically(m_b); break;
case 0x29: /* SRA C */ shift_right_arithmatically(m_c); break;
case 0x2a: /* SRA D */ shift_right_arithmatically(m_d); break;
case 0x2b: /* SRA E */ shift_right_arithmatically(m_e); break;
case 0x2c: /* SRA H */ shift_right_arithmatically(m_h); break;
case 0x2d: /* SRA L */ shift_right_arithmatically(m_l); break;
case 0x2e: /* SRA (HL) */ {
m_wait_cycles += 8; // + 8 = 16 total
// Rotate the byte pointed to by HL right
uint32_t data = read(hl());
shift_right_arithmatically(data);
write(hl(), data);
break;
}
case 0x2f: /* SRA A */ shift_right_arithmatically(m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
void CPU::srl()
{
auto shift_right_logically = [this](uint32_t& register_) -> void {
// SRL r8, flags: Z 0 0 C
m_wait_cycles += 8;
// Shift Right Locically register r8
// ┌─────────┐
// 0 ─>│7 ──> 0│─> C
// └─────────┘
// r8
// Copy bit 0 into carry flag
m_cf = (register_ & 0x1) == 0x1;
// Shift Right Locically register r8
register_ = (register_ >> 1) & 0x7f; // Note: bit 7 is set to 0
// Set other flags
m_zf = (register_ == 0);
m_nf = 0;
m_hf = 0;
};
uint8_t opcode = pcRead();
switch (opcode) {
case 0x38: /* SRL B */ shift_right_logically(m_b); break;
case 0x39: /* SRL C */ shift_right_logically(m_c); break;
case 0x3a: /* SRL D */ shift_right_logically(m_d); break;
case 0x3b: /* SRL E */ shift_right_logically(m_e); break;
case 0x3c: /* SRL H */ shift_right_logically(m_h); break;
case 0x3d: /* SRL L */ shift_right_logically(m_l); break;
case 0x3e: /* SRL (HL) */ {
m_wait_cycles += 8; // + 8 = 16 total
// Shift right logically the byte pointed to by HL
uint32_t data = read(hl());
shift_right_logically(data);
write(hl(), data);
break;
}
case 0x3f: /* SRL A */ shift_right_logically(m_a); break;
default:
VERIFY_NOT_REACHED();
}
}
+1768 -2
View File
File diff suppressed because it is too large Load Diff
+133 -17
View File
@@ -7,43 +7,159 @@
#pragma once #pragma once
#include <cstdint> // uint8_t, uint16_t #include <cstdint> // int8_t, uint8_t, uint32_t
#include <functional> // std::function
#include <unordered_map>
#include "processing-unit.h" #include "processing-unit.h"
#include "ruc/format/formatter.h"
class CPU final : public ProcessingUnit { class CPU final : public ProcessingUnit {
private:
friend struct CPUTest;
public: public:
CPU(); explicit CPU(uint32_t frequency);
virtual ~CPU(); virtual ~CPU();
// 8-bit Arithmetic and Logic Instructions void handleInterrupt(uint32_t interrupt_flag, uint8_t interrupt_source, uint8_t address);
void update() override;
// 16-bit Arithmetic Instructions // -------------------------------------
// Arithmetic and Logic Instructions
// 8-bit
void adc8();
void add8();
void and8();
void cp();
void daa();
void dec8();
void inc8();
void or8();
void sbc8();
void sub8();
void xor8();
// 16-bit
void addr16();
void adds8();
void dec16();
void inc16();
// -------------------------------------
// Bit Operations Instructions // Bit Operations Instructions
void bit();
void res();
void set();
void swap();
// -------------------------------------
// Bit Shift Instructions // Bit Shift Instructions
void ra();
void rl();
void rlc();
void rr();
void rrc();
void sla();
void sra();
void srl();
// -------------------------------------
// Load Instructions // Load Instructions
// 8-bit
void lda8();
void ldff8();
void ldi8();
void ldr8();
// 16-bit
void ldi16();
void ldr16();
void pop();
void push();
// -------------------------------------
// Jumps and Subroutines // Jumps and Subroutines
void call();
void jp16();
void jrs8();
void ret();
void rst();
// -------------------------------------
// Stack Operations Instructions // Stack Operations Instructions
// -------------------------------------
// Miscellaneous Instructions // Miscellaneous Instructions
private: void misc();
// 16-bit registers void nop();
uint16_t m_af { 0 }; // Accumulator & Flags void prefix();
uint16_t m_bc { 0 }; // BC
uint16_t m_de { 0 }; // DE
uint16_t m_hl { 0 }; // HL
uint16_t m_sp { 0 }; // Stack Pointer
uint16_t m_pc { 0 }; // Program Counter
// 8-bit registers (flags) // -------------------------------------
uint8_t m_z { 0 }; // Zero flag
uint8_t m_n { 0 }; // Subtraction flag (BCD) uint32_t a() const { return m_a; }
uint8_t m_h { 0 }; // Half Carry flag (BCD) uint32_t b() const { return m_b; }
uint8_t m_c { 0 }; // Carry flag uint32_t c() const { return m_c; }
uint32_t af() const { return (m_cf << 4 | m_hf << 5 | m_nf << 6 | m_zf << 7) | m_a << 8; }
uint32_t bc() const { return m_c | m_b << 8; }
uint32_t de() const { return m_e | m_d << 8; }
uint32_t hl() const { return m_l | m_h << 8; }
uint32_t pc() const { return m_pc; }
uint32_t sp() const { return m_sp; }
uint32_t zf() const { return m_zf; }
uint32_t nf() const { return m_nf; }
uint32_t hf() const { return m_hf; }
uint32_t cf() const { return m_cf; }
void setBC(uint32_t value);
void setDE(uint32_t value);
void setHL(uint32_t value);
private:
uint32_t pcRead();
uint32_t pcRead16() { return pcRead() | (pcRead() << 8); }
void write(uint32_t address, uint32_t value);
uint32_t read(uint32_t address);
void ffWrite(uint32_t address, uint32_t value);
uint32_t ffRead(uint32_t address);
bool isCarry(uint32_t limit_bit, uint32_t first, uint32_t second, uint32_t third = 0x0);
bool isCarrySubtraction(uint32_t limit_bit, uint32_t lhs, uint32_t rhs, uint32_t rhs_extra = 0x0);
// Registers
uint32_t m_a { 0 }; // Accumulator
uint32_t m_b { 0 }; // B
uint32_t m_c { 0 }; // C
uint32_t m_d { 0 }; // D
uint32_t m_e { 0 }; // E
uint32_t m_h { 0 }; // H
uint32_t m_l { 0 }; // L
uint32_t m_pc { 0 }; // Program Counter
uint32_t m_sp { 0 }; // Stack Pointer
// Flags
uint32_t m_zf { 0 }; // Zero flag
uint32_t m_nf { 0 }; // Subtraction flag (BCD)
uint32_t m_hf { 0 }; // Half Carry flag (BCD)
uint32_t m_cf { 0 }; // Carry flag
uint32_t m_ime { 0 }; // Interrupt Master Enable flag
bool m_should_enable_ime { 0 };
int8_t m_wait_cycles { 0 };
};
template<>
struct ruc::format::Formatter<CPU> : Formatter<uint32_t> {
void parse(Parser& parser);
void format(Builder& builder, const CPU& value) const;
}; };
-1
View File
@@ -1,6 +1,5 @@
/* /*
* Copyright (C) 2022 Riyyi * Copyright (C) 2022 Riyyi
* Copyright (C) 2022 Th3FrankXD
* *
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
-1
View File
@@ -1,6 +1,5 @@
/* /*
* Copyright (C) 2022 Riyyi * Copyright (C) 2022 Riyyi
* Copyright (C) 2022 Th3FrankXD
* *
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
+126 -12
View File
@@ -1,19 +1,133 @@
#include "emu.h" /*
* Copyright (C) 2022 Riyyi
* Copyright (C) 2022 Th3FrankXD
*
* SPDX-License-Identifier: MIT
*/
void Emu::update() { #include <cstdint> // uint32_t
for (auto unit : m_processing_units) { #include <string_view>
if (m_cycle % int(m_frequency / unit.frequency()) == 0) { #include <utility> // std::move
unit.update(); #include <vector>
#include "cpu.h"
#include "emu.h"
#include "loader.h"
#include "ruc/format/log.h"
#include "ruc/format/print.h"
#include "ruc/meta/assert.h"
void Emu::init(uint32_t frequency)
{
m_frequency = frequency;
m_timestep = 1.0 / m_frequency * 1000000;
}
void Emu::update()
{
double time = m_timer.elapsedNanoseconds() / 1000.0;
m_cycle_time += (time - m_previous_time);
m_previous_time = time;
if (m_cycle_time > m_timestep) {
for (auto unit : m_processing_units) {
if (m_cycle % (m_frequency / unit.second->frequency()) == 0) {
unit.second->update();
}
} }
m_cycle_time -= m_timestep;
m_cycle++;
} }
} }
void Emu::addProcessingUnit(ProcessingUnit processing_unit) { void Emu::addProcessingUnit(std::string_view name, std::shared_ptr<ProcessingUnit> processing_unit)
m_processing_units.push_back(processing_unit); {
m_processing_units.emplace(name, processing_unit);
} }
void Emu::addMemorySpace(const char* name, int size) { void Emu::addMemorySpace(std::string_view name, uint32_t start_address, uint32_t end_adress, uint32_t amount_of_banks)
std::vector<uint8_t> memory; {
memory.reserve(size); uint32_t bank_length = 1 + end_adress - start_address;
m_memory_spaces.emplace(name, memory); MemorySpace memory_space {
} .memory = BankedMemory(amount_of_banks, std::vector<uint32_t>(bank_length)),
.active_bank = 0,
.start_address = start_address,
.end_address = end_adress,
};
m_memory_spaces.emplace(name, std::move(memory_space));
}
void Emu::removeMemorySpace(std::string_view name)
{
m_memory_spaces.erase(name);
}
void Emu::writeMemory(uint32_t address, uint32_t value)
{
// Bail if the CPU tries to write to a read-only address
switch (address) {
case 0xff44:
ruc::error("writing to read-only address: {:#06x}", address);
VERIFY_NOT_REACHED();
break;
default:
break;
}
for (auto& memory_space : m_memory_spaces) {
auto& memory = memory_space.second;
if (address >= memory.start_address && address <= memory.end_address) {
// Note: ECHO RAM hack
if (address >= 0xc000 && address <= 0xddff) {
writeMemory(address + (0xe000 - 0xc000), value);
}
memory.memory[memory.active_bank][address - memory.start_address] = value;
if (address == 0xff50) {
print("DISABLING BOOTROM\n");
Loader::the().disableBootrom();
}
// Write serial data from linkport I/O, used for blargg's test ROMs
if (address == 0xff02 && value == 0x81) {
uint32_t data = readMemory(0xff01);
print("{:c}", (data >= 58 && data <= 64) ? data + 7 : data);
}
return;
}
}
ruc::error("writing into address '{:#06x}' which is not in a memory space!", address);
VERIFY_NOT_REACHED();
}
uint32_t Emu::readMemory(uint32_t address) const
{
switch (address) {
case 0xff44:
return *m_processing_units.at("PPU")->sharedRegister("LY");
default:
break;
};
for (const auto& memory_space : m_memory_spaces) {
const auto& memory = memory_space.second;
if (address >= memory.start_address && address <= memory.end_address) {
return memory.memory[memory.active_bank][address - memory.start_address];
}
}
// When trying to access the cartridge header
if (address >= 0x100 && address <= 0x14f) {
ruc::error("no cartridge loaded!");
}
else {
ruc::error("reading from address '{:#06x}' which is not in a memory space!", address);
}
VERIFY_NOT_REACHED();
return 0;
}
+48 -14
View File
@@ -1,33 +1,67 @@
/*
* Copyright (C) 2022 Riyyi
* Copyright (C) 2022 Th3FrankXD
*
* SPDX-License-Identifier: MIT
*/
#pragma once #pragma once
#include <cstdint> #include <cstdint> // uint32_t
#include <vector> #include <memory> // std::shared_ptr
#include <string_view>
#include <unordered_map> #include <unordered_map>
#include <vector>
#include "processing-unit.h" #include "processing-unit.h"
#include "ruc/singleton.h" #include "ruc/singleton.h"
#include "ruc/timer.h"
using BankedMemory = std::vector<std::vector<uint32_t>>;
struct MemorySpace {
BankedMemory memory;
uint32_t active_bank { 0 };
uint32_t start_address { 0 };
uint32_t end_address { 0 };
};
class Emu final : public ruc::Singleton<Emu> { class Emu final : public ruc::Singleton<Emu> {
public: public:
Emu(s) {} Emu(s) {}
enum Mode : uint8_t {
DMG, // Game Boy
CGB, // Game Boy Color
};
void init(uint32_t frequency);
void update(); void update();
void addProcessingUnit(ProcessingUnit processing_unit); void addProcessingUnit(std::string_view name, std::shared_ptr<ProcessingUnit> processing_unit);
void addMemorySpace(const char* name, int size); void addMemorySpace(std::string_view name, uint32_t start_address, uint32_t end_address, uint32_t amount_of_banks = 1);
void removeMemorySpace(std::string_view name);
void writeRAM(const char* memory_space, int location); void writeMemory(uint32_t address, uint32_t value);
void writeVRAM(const char* memory_space, int location); uint32_t readMemory(uint32_t address) const;
void writeROM(const char* memory_space, int location);
uint8_t readRAM(const char* memory_space, int location); // -------------------------------------
uint8_t readVRAM(const char* memory_space, int location);
uint8_t readROM(const char* memory_space, int location); Mode mode() const { return m_mode; }
std::shared_ptr<ProcessingUnit> processingUnit(std::string_view name) const { return m_processing_units.at(name); }
MemorySpace memorySpace(std::string_view name) { return m_memory_spaces[name]; }
private: private:
float m_frequency; Mode m_mode { Mode::DMG };
int m_cycle = 0; uint32_t m_frequency { 0 };
double m_timestep { 0 };
uint32_t m_cycle { 0 };
double m_cycle_time { 0 };
double m_previous_time { 0 };
std::vector<ProcessingUnit> m_processing_units; ruc::Timer m_timer;
std::unordered_map<const char*, std::vector<uint8_t>> m_memory_spaces;
std::unordered_map<std::string_view, std::shared_ptr<ProcessingUnit>> m_processing_units;
std::unordered_map<std::string_view, MemorySpace> m_memory_spaces;
}; };
+120
View File
@@ -0,0 +1,120 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstddef> // size_t
#include <cstdint> // uint32_t
#include <memory> // std::make_shared
#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::disableBootrom()
{
Emu::the().removeMemorySpace("BOOTROM1");
Emu::the().removeMemorySpace("CARTHEADER");
Emu::the().removeMemorySpace("BOOTROM2");
// Load cartridge bank 0
Emu::the().addMemorySpace("CARTROM1", 0x0000, 0x3fff); // 16KiB
for (size_t i = 0x0000; i <= 0x3fff; ++i) {
Emu::the().writeMemory(i, m_rom_data[i]);
}
}
// -----------------------------------------
void Loader::init()
{
destroy();
Emu::the().init(4000000);
auto cpu = std::make_shared<CPU>(4000000);
auto ppu = std::make_shared<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
loadCartridgeBanks();
Emu::the().addMemorySpace("VRAM", 0x8000, 0x9fff, 2); // 8KiB * 2 banks
Emu::the().addMemorySpace("CARTRAM", 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]);
}
}
void Loader::destroy()
{
Emu::destroy();
}
void Loader::loadCartridgeHeader()
{
if (m_rom_data.empty()) {
return;
}
Emu::the().addMemorySpace("CARTHEADER", 0x100, 0x14f); // 80B
for (size_t i = 0x0100; i <= 0x014f; ++i) {
Emu::the().writeMemory(i, m_rom_data[i]);
}
}
void Loader::loadCartridgeBanks()
{
if (m_rom_data.empty()) {
return;
}
uint32_t rom_size = 32 * 1024 * (1 << m_rom_data[0x0148]);
uint32_t rom_banks = rom_size / (16 * 1024) - 1;
Emu::the().addMemorySpace("CARTROM2", 0x4000, 0x7fff, rom_banks); // 16KiB * banks
// Load cartridge bank 1~NN
auto rom_memory_spaces = Emu::the().memorySpace("CARTROM2");
for (size_t i = 0; i < rom_banks; ++i) {
for (size_t i = 0x4000; i <= 0x7fff; ++i) {
Emu::the().writeMemory(i, m_rom_data[i]);
}
rom_memory_spaces.active_bank += 1;
}
rom_memory_spaces.active_bank = 0;
}
+32
View File
@@ -0,0 +1,32 @@
/*
* 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 disableBootrom();
void setBootromPath(std::string_view bootrom_path) { m_bootrom_path = bootrom_path; }
private:
void init();
void destroy();
void loadCartridgeHeader();
void loadCartridgeBanks();
std::string_view m_bootrom_path;
std::string m_rom_data;
};
+55 -10
View File
@@ -1,15 +1,60 @@
#include <cstdio> /*
* Copyright (C) 2022 Riyyi
* Copyright (C) 2022 Th3FrankXD
*
* SPDX-License-Identifier: MIT
*/
#include "emu.h" #include <string_view>
#include "inferno.h"
#include "inferno/entrypoint.h"
#include "ppu.h"
#include "ruc/argparser.h"
#include "ruc/format/print.h"
#include "ruc/timer.h" #include "ruc/timer.h"
int main(int argc, char* argv[]) #include "emu.h"
#include "loader.h"
class GarbAGE final : public Inferno::Application {
public:
GarbAGE(int argc, char* argv[])
: Application()
{
std::string_view bootrom_path = "gbc_bios.bin";
std::string_view rom_path;
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);
Loader::the().setBootromPath(bootrom_path);
Loader::the().loadRom(rom_path);
}
~GarbAGE()
{
}
void update() override
{
// 154 scanlines * 456 cycles = 70224 cycles per frame
for (int i = 0; i < (144 * 456 + 10 * 456); ++i) {
Emu::the().update();
}
}
void render() override
{
auto* ppu = static_cast<PPU*>(Emu::the().processingUnit("PPU").get());
ppu->render();
}
};
Inferno::Application* Inferno::createApplication(int argc, char* argv[])
{ {
ruc::Timer t; GarbAGE* gameboy = new GarbAGE(argc, argv);
return gameboy;
printf("%fms\n", t.elapsedNanoseconds() / 1000000.0);
Emu::the().ReadRAM(0, 0);
return 0;
} }
+288 -2
View File
@@ -5,12 +5,298 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <cstdint> // uint8_t, uint16_t, uint32_t
#include <memory> // std::make_shared
#include "glm/ext/vector_float3.hpp" // glm::vec3
#include "glm/ext/vector_float4.hpp" // glm::vec4
#include "inferno/application.h"
#include "inferno/component/spritecomponent.h"
#include "inferno/component/transformcomponent.h"
#include "inferno/scene/scene.h"
#include "ruc/format/print.h"
#include "emu.h"
#include "ppu.h" #include "ppu.h"
#include "ruc/meta/assert.h"
PPU ::PPU() PPU::PPU(uint32_t frequency)
: ProcessingUnit(frequency)
{
m_shared_registers.emplace("LY", &m_lcd_y_coordinate);
auto& scene = Inferno::Application::the().scene();
m_entity = scene.findEntity("Screen");
}
PPU::~PPU()
{ {
} }
PPU ::~PPU() void PPU::update()
{ {
LCDC lcd_control = static_cast<LCDC>(Emu::the().readMemory(0xff40));
if (!(lcd_control & LCDC::LCDandPPUEnable)) {
return;
}
// print("PPU update\n");
m_clocks_into_frame++;
switch (m_state) {
case State::OAMSearch:
// OAM logic goes here..
if (m_clocks_into_frame % 80 == 0) {
// Reset FIFO
m_pixel_fifo = {};
m_state = State::PixelTransfer;
}
break;
case State::PixelTransfer:
updatePixelFifo();
if (m_lcd_x_coordinate == 160) {
m_lcd_x_coordinate = 0;
m_state = State::HBlank;
}
break;
case State::HBlank:
// H-Blank logic goes here..
if (m_clocks_into_frame % (80 + 172 + 204) == 0) {
m_lcd_y_coordinate++;
if (m_lcd_y_coordinate == 144) {
m_state = State::VBlank;
}
else {
m_state = State::OAMSearch;
}
}
break;
case State::VBlank:
// V-Blank logic goes here..
if (m_clocks_into_frame % (80 + 172 + 204) == 0) {
m_lcd_y_coordinate++;
if (m_lcd_y_coordinate == 154) {
resetFrame();
}
}
break;
default:
VERIFY_NOT_REACHED();
};
}
void PPU::render()
{
LCDC lcd_control = static_cast<LCDC>(Emu::the().readMemory(0xff40));
if (!(lcd_control & LCDC::BGandWindowEnable)) {
// When Bit 0 is cleared, both background and window become blank (white)
auto pixel = getPixelColor(0, Palette::BGP);
for (size_t i = 0; i < m_screen.size(); i += 3) {
m_screen[i + 0] = pixel[0];
m_screen[i + 1] = pixel[1];
m_screen[i + 2] = pixel[2];
}
}
auto& scene = Inferno::Application::the().scene();
auto texture = std::make_shared<Inferno::Texture>(m_screen.data(), SCREEN_WIDTH, SCREEN_HEIGHT, FORMAT_SIZE);
scene.removeComponent<Inferno::SpriteComponent>(m_entity);
scene.addComponent<Inferno::SpriteComponent>(m_entity, glm::vec4 { 1.0f }, texture);
}
void PPU::resetFrame()
{
m_state = State::OAMSearch;
m_clocks_into_frame = 0;
m_lcd_x_coordinate = 0;
m_lcd_y_coordinate = 0;
}
// -----------------------------------------
uint32_t PPU::getBgTileDataAddress(uint8_t tile_index)
{
// https://gbdev.io/pandocs/Tile_Data.html
switch (m_pixel_fifo.tile_data_address) {
case 0x8000:
// 0x8000-0x8fff: index 0 => 255
return m_pixel_fifo.tile_data_address + (tile_index * TILE_SIZE); // Each tile is 16 bytes
case 0x8800:
// 0x8800-0x8fff: index 128 => 255 (or -128 => -1)
// 0x9000-0x97ff: index 0 => 127
if (tile_index <= 127) {
return m_pixel_fifo.tile_data_address + 0x800 + (tile_index * TILE_SIZE); // Each tile is 16 bytes
}
else {
return m_pixel_fifo.tile_data_address + ((tile_index - 128) * TILE_SIZE); // Each tile is 16 bytes
}
default:
VERIFY_NOT_REACHED();
return 0;
};
};
std::array<uint8_t, 3> PPU::getPixelColor(uint8_t color_index, Palette palette)
{
VERIFY(color_index < 4, "trying to fetch invalid color index '{}'", color_index);
switch (Emu::the().mode()) {
case Emu::Mode::DMG: {
uint8_t palette_data = Emu::the().readMemory(palette) & 0xff;
uint8_t palette_value = palette_data >> (color_index * 2) & 0x3;
switch (palette_value) {
case 0:
return { 200, 199, 168 };
case 1:
return { 160, 160, 136 };
case 2:
return { 104, 104, 80 };
case 3:
return { 39, 40, 24 };
default:
VERIFY_NOT_REACHED();
};
}
case Emu::Mode::CGB:
VERIFY_NOT_REACHED();
default:
VERIFY_NOT_REACHED();
}
return {};
}
void PPU::updatePixelFifo()
{
switch (m_pixel_fifo.state) {
case PixelFifo::State::TileIndex:
tileIndex();
break;
case PixelFifo::State::TileDataLow:
tileDataLow();
break;
case PixelFifo::State::TileDataHigh:
tileDataHigh();
break;
case PixelFifo::State::Sleep:
sleep();
break;
case PixelFifo::State::Push:
pushFifo();
break;
default:
VERIFY_NOT_REACHED();
};
pushPixel();
}
void PPU::tileIndex()
{
if (!m_pixel_fifo.step) {
m_pixel_fifo.step = true;
}
else {
m_pixel_fifo.step = false;
m_pixel_fifo.state = PixelFifo::State::TileDataLow;
LCDC lcd_control = static_cast<LCDC>(Emu::the().readMemory(0xff40));
// Tile map
uint32_t bg_tile_map_address = (lcd_control & LCDC::BGTileMapArea) ? 0x9c00 : 0x9800;
// uint32_t window_tile_map_address = (lcd_control & LCDC::WindowTileMapArea) ? 0x9c00 : 0x9800;
// Tile data
m_pixel_fifo.tile_data_address = (lcd_control & LCDC::BGandWindowTileDataArea) ? 0x8000 : 0x8800;
// Viewport
// https://gbdev.io/pandocs/Scrolling.html#mid-frame-behavior
m_pixel_fifo.viewport_x = Emu::the().readMemory(0xff43); // TODO: only read lower 3-bits at beginning of scanline
m_pixel_fifo.viewport_y = Emu::the().readMemory(0xff42);
// Read the tile map index
uint16_t offset = (((m_pixel_fifo.viewport_y + m_lcd_y_coordinate) / TILE_HEIGHT) * 32)
+ ((m_pixel_fifo.viewport_x + m_pixel_fifo.x_coordinate) / TILE_WIDTH);
m_pixel_fifo.x_coordinate += 8;
m_pixel_fifo.tile_index = Emu::the().readMemory(bg_tile_map_address + offset);
// Set the tile line we're currently on
m_pixel_fifo.tile_line = (m_pixel_fifo.viewport_y + m_lcd_y_coordinate) % TILE_HEIGHT;
}
}
void PPU::tileDataLow()
{
if (!m_pixel_fifo.step) {
m_pixel_fifo.step = true;
}
else {
m_pixel_fifo.step = false;
m_pixel_fifo.state = PixelFifo::State::TileDataHigh;
// Read tile data
m_pixel_fifo.pixels_lsb = Emu::the().readMemory(
getBgTileDataAddress(m_pixel_fifo.tile_index)
+ m_pixel_fifo.tile_line * 2); // Each tile line is 2 bytes
}
}
void PPU::tileDataHigh()
{
if (!m_pixel_fifo.step) {
m_pixel_fifo.step = true;
}
else {
m_pixel_fifo.step = false;
m_pixel_fifo.state = PixelFifo::State::Sleep;
// Read tile data
m_pixel_fifo.pixels_msb = Emu::the().readMemory(
getBgTileDataAddress(m_pixel_fifo.tile_index)
+ m_pixel_fifo.tile_line * 2 // Each tile line is 2 bytes
+ 1);
}
}
void PPU::sleep()
{
if (m_pixel_fifo.background.size() <= TILE_WIDTH + 1) {
m_pixel_fifo.state = PixelFifo::State::Push;
}
}
void PPU::pushFifo()
{
m_pixel_fifo.state = PixelFifo::State::TileIndex;
for (uint8_t i = 0; i < TILE_WIDTH; ++i) {
uint8_t color_index = (m_pixel_fifo.pixels_lsb >> (7 - i)
| ((m_pixel_fifo.pixels_msb >> (7 - i)) << 1))
& 0x3;
m_pixel_fifo.background.push({ color_index, Palette::BGP });
}
}
void PPU::pushPixel()
{
// The pixel FIFO needs to contain more than 8 pixels to shift one out
if (m_pixel_fifo.background.size() > 8) {
auto pixel = m_pixel_fifo.background.front();
m_pixel_fifo.background.pop();
uint32_t index = (m_lcd_y_coordinate * SCREEN_WIDTH + m_lcd_x_coordinate) * FORMAT_SIZE;
auto color = getPixelColor(pixel.first, pixel.second);
m_screen[index + 0] = color[0];
m_screen[index + 1] = color[1];
m_screen[index + 2] = color[2];
m_lcd_x_coordinate++;
}
} }
+95 -2
View File
@@ -7,10 +7,103 @@
#pragma once #pragma once
#include <array>
#include <cstdint> // uint8_t, uint16_t, uint32_t
#include <queue>
#include <utility> // std::pair
#include "ruc/meta/core.h"
#include "processing-unit.h" #include "processing-unit.h"
#define SCREEN_WIDTH 160
#define SCREEN_HEIGHT 144
#define FORMAT_SIZE 3
#define TILE_WIDTH 8
#define TILE_HEIGHT 8
#define TILE_SIZE 16
class PPU final : public ProcessingUnit { class PPU final : public ProcessingUnit {
public: public:
PPU(); PPU(uint32_t frequency);
virtual ~PPU(); ~PPU();
enum LCDC : uint8_t {
None = 0,
BGandWindowEnable = BIT(0),
OBJEnable = BIT(1),
OBJSize = BIT(2), // 0 = 8x8, 1 = 8x16
BGTileMapArea = BIT(3), // 0 = 0x9800-9bff, 1 = 0x9c00-9fff
BGandWindowTileDataArea = BIT(4), // 0 = 0x8800-97ff, 1 = 0x8000-8fff
WindowEnable = BIT(5), //
WindowTileMapArea = BIT(6), // 0 = 0x9800-9bff, 1 = 0x9c00-9fff
LCDandPPUEnable = BIT(7),
};
enum State : uint8_t {
HBlank = 0,
VBlank = 1,
OAMSearch = 2,
PixelTransfer = 3,
};
enum Palette : uint16_t {
BGP = 0xff47, // BG and Window palette
OBP0 = 0xff48, // OBJ palette 0
OBP1 = 0xff49, // OBJ palette 1
};
struct PixelFifo {
enum State : uint8_t {
TileIndex,
TileDataLow,
TileDataHigh,
Sleep,
Push,
};
State state { State::TileIndex };
bool step { false };
uint32_t tile_data_address { 0 };
uint8_t viewport_x { 0 };
uint8_t viewport_y { 0 };
uint8_t x_coordinate { 0 };
uint8_t tile_index { 0 };
uint8_t tile_line { 0 };
uint8_t pixels_lsb { 0 };
uint8_t pixels_msb { 0 };
using Fifo = std::queue<std::pair<uint8_t, Palette>>; // colorID, source
Fifo background;
Fifo oam;
};
void update() override;
void render();
void resetFrame();
private:
uint32_t getBgTileDataAddress(uint8_t tile_index);
std::array<uint8_t, 3> getPixelColor(uint8_t color_index, Palette palette);
void updatePixelFifo();
void tileIndex();
void tileDataLow();
void tileDataHigh();
void sleep();
void pushFifo();
void pushPixel();
// -------------------------------------
State m_state { State::OAMSearch };
uint32_t m_clocks_into_frame { 0 };
uint32_t m_lcd_x_coordinate { 0 };
uint32_t m_lcd_y_coordinate { 0 }; // Note: includes V-Blank
PixelFifo m_pixel_fifo;
uint32_t m_entity { 0 };
std::array<uint8_t, SCREEN_WIDTH * SCREEN_HEIGHT * FORMAT_SIZE> m_screen;
}; };
+4 -5
View File
@@ -5,16 +5,15 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <cstdint> // uint32_t
#include "processing-unit.h" #include "processing-unit.h"
ProcessingUnit::ProcessingUnit(float frequency) : m_frequency(frequency) ProcessingUnit::ProcessingUnit(uint32_t frequency)
: m_frequency(frequency)
{ {
} }
ProcessingUnit::~ProcessingUnit() ProcessingUnit::~ProcessingUnit()
{ {
} }
float ProcessingUnit::frequency() {
return m_frequency;
}
+13 -5
View File
@@ -7,15 +7,23 @@
#pragma once #pragma once
#include <cstdint> // uint32_t
#include <string_view>
#include <unordered_map>
class ProcessingUnit { class ProcessingUnit {
public: public:
ProcessingUnit(float frequency); ProcessingUnit(uint32_t frequency);
virtual ~ProcessingUnit(); virtual ~ProcessingUnit();
virtual void update(); virtual void update() = 0;
float frequency(); // -------------------------------------
private: uint32_t frequency() const { return m_frequency; };
float m_frequency; uint32_t* sharedRegister(std::string_view shared_register) const { return m_shared_registers.at(shared_register); }
protected:
uint32_t m_frequency { 0 };
std::unordered_map<std::string_view, uint32_t*> m_shared_registers;
}; };
+182
View File
@@ -0,0 +1,182 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <memory> // std::make_shared, std::shared_ptr
#include <vector>
#include "cpu.h"
#include "emu.h"
#include "macro.h"
#include "testcase.h"
#include "testsuite.h"
struct CPUTest {
CPU cpu { 0 };
bool isCarry(uint32_t limit_bit, uint32_t first, uint32_t second, uint32_t third = 0x0)
{
return cpu.isCarry(limit_bit, first, second, third);
}
bool isCarrySubtraction(uint32_t limit_bit, uint32_t lhs, uint32_t rhs, uint32_t rhs_extra = 0x0)
{
return cpu.isCarrySubtraction(limit_bit, lhs, rhs, rhs_extra);
}
};
std::shared_ptr<CPU> runCPUTest(std::vector<uint8_t> test)
{
auto cpu = std::make_shared<CPU>(4000000);
Emu::the().destroy();
Emu::the().init(8000000);
Emu::the().addProcessingUnit("cpu", cpu);
Emu::the().addMemorySpace("FULL", 0x0000, 0xffff);
// Load the test
for (size_t i = 0; i < test.size(); ++i) {
Emu::the().writeMemory(i, test[i]);
}
// Run the test
while (cpu->pc() < test.size()) {
Emu::the().update();
}
return cpu;
}
// -----------------------------------------
TEST_CASE(CPUIsCarry)
{
CPUTest test;
// 8-bit Half-carry false
EXPECT(test.isCarry(0xf, 56, 17) == false);
EXPECT(test.isCarrySubtraction(0xf, 136, 1) == false);
EXPECT(test.isCarrySubtraction(0xf, 219, 20) == false);
EXPECT(test.isCarrySubtraction(0xf, 250, 20) == false);
// 8-bit Half-carry true
EXPECT(test.isCarry(0xf, 46, 34) == true);
EXPECT(test.isCarry(0xf, 62, 34) == true);
EXPECT(test.isCarrySubtraction(0xf, 16, 1) == true);
EXPECT(test.isCarrySubtraction(0xf, 32, 1) == true);
EXPECT(test.isCarrySubtraction(0xf, 144, 1) == true);
EXPECT(test.isCarrySubtraction(0xf, 211, 20) == true);
// 8-bit Full carry false
EXPECT(test.isCarry(0xff, 254, 1) == false);
// 8-bit Full carry true
EXPECT(test.isCarry(0xff, 254, 2) == true);
}
TEST_CASE(CPUAddPlusCarry)
{
std::shared_ptr<CPU> cpu(0);
// ADC A,E
std::vector<uint8_t> adc_r8 = {
// clang-format off
0x3e, 0xe1, // LD A,i8
0x1e, 0x0f, // LD E,i8
0x37, // SCF
0x8b, // ADC A,E
// clang-format on
};
cpu = runCPUTest(adc_r8);
EXPECT_EQ(cpu->a(), 0xf1);
EXPECT_EQ(cpu->zf(), 0x0);
EXPECT_EQ(cpu->nf(), 0x0);
EXPECT_EQ(cpu->hf(), 0x1);
EXPECT_EQ(cpu->cf(), 0x0);
// ADC A,i8
std::vector<uint8_t> adc_i8 = {
// clang-format off
0x3e, 0xe1, // LD A,i8
0x37, // SCF
0xce, 0x3b, // ADC A,i8
// clang-format on
};
cpu = runCPUTest(adc_i8);
EXPECT_EQ(cpu->a(), 0x1d);
EXPECT_EQ(cpu->zf(), 0x0);
EXPECT_EQ(cpu->nf(), 0x0);
EXPECT_EQ(cpu->hf(), 0x0);
EXPECT_EQ(cpu->cf(), 0x1);
// ADC A,(HL)
std::vector<uint8_t> adc_hl = {
// clang-format off
0x3e, 0xe1, // LD A,i8
0x36, 0x1e, // LD (HL),i8
0x37, // SCF
0x8e, // ADC A,(HL)
// clang-format on
};
cpu = runCPUTest(adc_hl);
EXPECT_EQ(cpu->a(), 0x0);
EXPECT_EQ(cpu->zf(), 0x1);
EXPECT_EQ(cpu->nf(), 0x0);
EXPECT_EQ(cpu->hf(), 0x1);
EXPECT_EQ(cpu->cf(), 0x1);
}
TEST_CASE(CPUSetStackPointer)
{
std::vector<uint8_t> test = { 0x31, 0xfe, 0xff }; // LD SP,i16
auto cpu = runCPUTest(test);
EXPECT_EQ(cpu->sp(), 0xfffe);
}
TEST_CASE(CPUPushToStack)
{
// PUSH BC
std::vector<uint8_t> push_bc = {
// clang-format off
0x31, 0xfe, 0xff, // LD SP,i16
0x01, 0xfc, 0xff, // LD BC,i16
0xc5, // PUSH BC
// clang-format on
};
auto cpu = runCPUTest(push_bc);
EXPECT_EQ(cpu->bc(), 0xfffc);
EXPECT_EQ(cpu->sp(), 0xfffc);
EXPECT_EQ(Emu::the().readMemory(0xfffd), 0xff);
EXPECT_EQ(Emu::the().readMemory(0xfffc), 0xfc);
}
TEST_CASE(CPUPopFromStack)
{
// POP BC
std::vector<uint8_t> pop_bc = {
// clang-format off
0x31, 0xfe, 0xff, // LD SP,i16
0x11, 0x5f, 0x3c, // LD DE,i16
0xd5, // PUSH DE
0xc1, // POP BC
// clang-format on
};
auto cpu = runCPUTest(pop_bc);
EXPECT_EQ(cpu->bc(), 0x3c5f);
EXPECT_EQ(cpu->sp(), 0xfffe);
EXPECT_EQ(Emu::the().readMemory(0xfffd), 0x3c);
EXPECT_EQ(Emu::the().readMemory(0xfffc), 0x5f);
}
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include "macro.h"
#include "testcase.h"
#include "testsuite.h"
// -----------------------------------------
TEST_CASE(Thing)
{
int a = 5;
EXPECT_EQ(a, 5);
}
Vendored Submodule
+1
Submodule vendor/inferno added at f47babe5cf
Vendored
-1
Submodule vendor/ruc deleted from be73ba335e