├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── README.md ├── format.sh ├── src ├── cartridge.c ├── cpu.c ├── include │ ├── bitmask.h │ ├── cartridge.h │ ├── cpu.h │ ├── log.h │ ├── mappers │ │ └── mapper0.h │ ├── memory.h │ ├── nes.h │ ├── ppu.h │ └── types.h ├── main.c ├── mappers │ └── mapper0.c ├── memory.c ├── nes.c ├── ppu.c └── test.c └── test ├── nestest.nes └── nestest.txt /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | AlignAfterOpenBracket: AlwaysBreak 4 | AlignConsecutiveAssignments: 'false' 5 | AlignConsecutiveDeclarations: 'false' 6 | AlignEscapedNewlines: Right 7 | AlignOperands: 'true' 8 | AlignTrailingComments: 'true' 9 | AllowAllParametersOfDeclarationOnNextLine: 'true' 10 | AllowShortBlocksOnASingleLine: 'false' 11 | AllowShortCaseLabelsOnASingleLine: 'false' 12 | AllowShortFunctionsOnASingleLine: 'Empty' 13 | AllowShortIfStatementsOnASingleLine: 'true' 14 | AllowShortLoopsOnASingleLine: 'false' 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: 'true' 17 | AlwaysBreakTemplateDeclarations: MultiLine 18 | BinPackArguments: 'false' 19 | BinPackParameters: 'false' 20 | BreakAfterJavaFieldAnnotations: 'true' 21 | BreakBeforeBinaryOperators: None 22 | BreakBeforeBraces: Attach 23 | BreakBeforeTernaryOperators: 'true' 24 | BreakConstructorInitializers: AfterColon 25 | BreakInheritanceList: AfterColon 26 | BreakStringLiterals: 'true' 27 | ColumnLimit: '100' 28 | CompactNamespaces: 'false' 29 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' 30 | ContinuationIndentWidth: '2' 31 | Cpp11BracedListStyle: 'false' 32 | FixNamespaceComments: 'true' 33 | IncludeBlocks: Regroup 34 | IndentCaseLabels: 'true' 35 | IndentPPDirectives: None 36 | IndentWidth: '4' 37 | IndentWrappedFunctionNames: 'true' 38 | Language: Cpp 39 | MaxEmptyLinesToKeep: '1' 40 | NamespaceIndentation: All 41 | PointerAlignment: Left 42 | ReflowComments: 'true' 43 | SortIncludes: 'true' 44 | SortUsingDeclarations: 'true' 45 | SpaceAfterCStyleCast: 'false' 46 | SpaceAfterTemplateKeyword: 'false' 47 | SpaceBeforeAssignmentOperators: 'true' 48 | SpaceBeforeCpp11BracedList: 'true' 49 | SpaceBeforeCtorInitializerColon: 'true' 50 | SpaceBeforeInheritanceColon: 'true' 51 | SpaceBeforeParens: ControlStatements 52 | SpaceBeforeRangeBasedForLoopColon: 'true' 53 | SpaceInEmptyParentheses: 'false' 54 | SpacesBeforeTrailingComments: '1' 55 | SpacesInAngles: 'false' 56 | SpacesInCStyleCastParentheses: 'false' 57 | SpacesInContainerLiterals: 'false' 58 | SpacesInParentheses: 'false' 59 | SpacesInSquareBrackets: 'false' 60 | TabWidth: '4' 61 | UseTab: Never 62 | 63 | ... 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # VS Code 55 | .vscode/ 56 | 57 | # ROMS 58 | roms/ 59 | 60 | # Build folder 61 | build/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | project(stm32nes C) 4 | 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_C_STANDARD_REQUIRED ON) 7 | set(CMAKE_C_EXTENSIONS OFF) 8 | 9 | if(MSVC) 10 | add_compile_options(/W4 /WX) 11 | add_compile_options($<$:/Zi) 12 | else() 13 | add_compile_options(-Wall -Wextra -Wpedantic -Werror -Wfatal-errors) 14 | add_compile_options($<$:-g3>) 15 | endif() 16 | 17 | add_executable(nes src/mappers/mapper0.c src/cartridge.c src/cpu.c src/memory.c 18 | src/nes.c src/main.c) 19 | target_include_directories(nes PRIVATE src/include) 20 | target_compile_definitions(nes PRIVATE PRINTF_SUPPORTED=1) 21 | 22 | add_executable(cpu_test src/mappers/mapper0.c src/cartridge.c src/cpu.c 23 | src/nes.c src/memory.c src/test.c) 24 | target_include_directories(cpu_test PRIVATE src/include) 25 | target_compile_definitions(cpu_test PRIVATE PRINTF_SUPPORTED=1) 26 | 27 | enable_testing() 28 | 29 | add_test(NAME cpu_test COMMAND $) 30 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 21, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "debug", 11 | "displayName": "Debug", 12 | "description": "Debug build preset", 13 | "generator": "Unix Makefiles", 14 | "binaryDir": "${sourceDir}/build", 15 | "cacheVariables": { 16 | "CMAKE_BUILD_TYPE": "Debug", 17 | "CMAKE_EXPORT_COMPILE_COMMANDS": "1" 18 | } 19 | }, 20 | { 21 | "name": "debug-windows", 22 | "inherits": "debug", 23 | "displayName": "Debug (Windows)", 24 | "description": "Windows debug build preset", 25 | "generator": "MinGW Makefiles", 26 | "condition": { 27 | "type": "equals", 28 | "lhs": "${hostSystemName}", 29 | "rhs": "Windows" 30 | } 31 | }, 32 | { 33 | "name": "release", 34 | "inherits": "debug", 35 | "displayName": "Release", 36 | "description": "Release build preset", 37 | "cacheVariables": { 38 | "CMAKE_BUILD_TYPE": "Release" 39 | } 40 | }, 41 | { 42 | "name": "release-windows", 43 | "inherits": "debug-windows", 44 | "displayName": "Release (Windows)", 45 | "description": "Windows release build preset", 46 | "cacheVariables": { 47 | "CMAKE_BUILD_TYPE": "Release" 48 | } 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STM32NES 2 | C based NES emulator to eventually be ported to STM32 3 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | find . \( -name *.c -o -name *.h \) -not -path "*/build/*" -exec clang-format -style=file -i {} \; 5 | find . \( -name CMakeLists.txt -o -name *.cmake \) -not -path "*/build/*" -exec cmake-format -i {} \; 6 | -------------------------------------------------------------------------------- /src/cartridge.c: -------------------------------------------------------------------------------- 1 | #include "cartridge.h" 2 | 3 | #include "bitmask.h" 4 | #include "log.h" 5 | #include "mappers/mapper0.h" 6 | #include "nes.h" 7 | #include "ppu.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | cartridge_result_t cartridge_init(nes_t* nes, char const* filename) { 14 | // Load ROM into memory 15 | FILE* rom_file = fopen(filename, "rb"); 16 | if (!rom_file) { 17 | return CARTRIDGE_NOT_FOUND; 18 | } 19 | fseek(rom_file, 0L, SEEK_END); 20 | size_t rom_size = ftell(rom_file); 21 | nes->cartridge.rom = malloc(rom_size * sizeof(u8)); 22 | rewind(rom_file); 23 | fread(nes->cartridge.rom, 1, rom_size, rom_file); 24 | fclose(rom_file); 25 | 26 | /* Header - 16 bytes */ 27 | // 4 byte magic number 28 | if (memcmp(nes->cartridge.rom, "NES\x1a", 4)) { 29 | return CARTRIDGE_UNSUPPORTED; 30 | } 31 | // PRG-ROM size in 16 kb blocks 32 | nes->cartridge.config.prg_size = nes->cartridge.rom[4]; 33 | if (!nes->cartridge.config.prg_size) { 34 | return CARTRIDGE_INVALID; 35 | } 36 | // CHR-ROM in 8 kb blocks 37 | if (nes->cartridge.rom[5]) { 38 | nes->cartridge.config.chr_size = nes->cartridge.rom[5]; 39 | } else { 40 | nes->cartridge.config.chr_size = 1; 41 | nes->cartridge.config.has_chr_ram = true; 42 | } 43 | // Flags 6 44 | // PPU nametable mirroring style 45 | // TODO 46 | // nes->cartridge.config.mirroring = NTH_BIT(nes->cartridge.rom[6], 0); 47 | // ppu_set_mirror(nes->cartridge.config.mirroring); 48 | // Presence of PRG RAM 49 | nes->cartridge.config.has_prg_ram = NTH_BIT(nes->cartridge.rom[6], 1); 50 | // 512 byte trainer before PRG data 51 | if (NTH_BIT(nes->cartridge.rom[6], 2)) { 52 | return CARTRIDGE_UNSUPPORTED; 53 | } 54 | // Ignore nametable mirroring, provide 4-screen VRAM 55 | nes->cartridge.config.has_vram = NTH_BIT(nes->cartridge.rom[6], 3); 56 | // Flags 7 57 | // Mapper lower nybble from flags 6, mapper upper nybble from flags 7 58 | nes->cartridge.config.mapper = (nes->cartridge.rom[6] >> 4) | (nes->cartridge.rom[7] & 0xF0); 59 | // Flags 8 60 | // PRG RAM size 61 | nes->cartridge.config.prg_ram_size = (nes->cartridge.rom[8] != 0) ? nes->cartridge.rom[8] : 1; 62 | // Flags 9 63 | // NTSC or PAL 64 | if (nes->cartridge.rom[9] != 0) { 65 | return CARTRIDGE_UNSUPPORTED; 66 | } 67 | // Flags 10-15 unused 68 | 69 | // Load PRG data 70 | nes->cartridge.prg = nes->cartridge.rom + NES_HEADER_SIZE; 71 | 72 | // Load CHR data 73 | nes->cartridge.chr = nes->cartridge.rom + NES_HEADER_SIZE + 74 | nes->cartridge.config.prg_size * NES_PRG_DATA_UNIT_SIZE; 75 | // Allocate PRG RAM 76 | if (nes->cartridge.config.has_prg_ram) 77 | nes->cartridge.prg_ram = 78 | malloc(nes->cartridge.config.prg_ram_size * NES_PRG_RAM_UNIT_SIZE * sizeof(u8)); 79 | 80 | switch (nes->cartridge.config.mapper) { 81 | case 0: 82 | mapper0_init( 83 | nes->cartridge.prg_map, nes->cartridge.chr_map, nes->cartridge.config.prg_size); 84 | break; 85 | default: 86 | LOG("Mapper %d not supported.\n", nes->cartridge.config.mapper); 87 | return CARTRIDGE_UNSUPPORTED; 88 | } 89 | return CARTRIDGE_SUCCESS; 90 | } 91 | 92 | u8 cartridge_prg_rd(nes_t* nes, u16 addr) { 93 | if (addr >= NES_PRG_DATA_OFFSET) { 94 | int slot = (addr - NES_PRG_DATA_OFFSET) / NES_PRG_SLOT_SIZE; 95 | int offset = (addr - NES_PRG_DATA_OFFSET) % NES_PRG_SLOT_SIZE; 96 | return nes->cartridge.prg[nes->cartridge.prg_map[slot] + offset]; 97 | } else { 98 | return nes->cartridge.config.has_prg_ram ? nes->cartridge.prg_ram[addr - NES_PRG_RAM_OFFSET] 99 | : 0; 100 | } 101 | } 102 | 103 | u8 cartridge_chr_rd(nes_t* nes, u16 addr) { 104 | int slot = addr / NES_CHR_SLOT_SIZE; 105 | int offset = addr % NES_CHR_SLOT_SIZE; 106 | return nes->cartridge.chr[nes->cartridge.chr_map[slot] + offset]; 107 | } 108 | 109 | void cartridge_prg_wr(nes_t* nes, u16 addr, u8 data) { 110 | // TODO: Use mapper's write implementation 111 | (void)nes; 112 | (void)addr; 113 | (void)data; 114 | } 115 | 116 | void cartridge_chr_wr(nes_t* nes, u16 addr, u8 data) { 117 | if (nes->cartridge.config.has_chr_ram) { 118 | nes->cartridge.chr[addr] = data; 119 | } 120 | } 121 | 122 | void reset(nes_t* nes) { 123 | free(nes->cartridge.rom); 124 | if (nes->cartridge.config.has_prg_ram) { 125 | free(nes->cartridge.prg_ram); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/cpu.c: -------------------------------------------------------------------------------- 1 | #include "cpu.h" 2 | 3 | #include "bitmask.h" 4 | #include "log.h" 5 | #include "memory.h" 6 | #include "nes.h" 7 | 8 | // For passing addressing modes as instruction arguments 9 | typedef u16 (*mode)(); 10 | 11 | // Processor status flag definitions 12 | enum { 13 | STATUS_CARRY, // [0] C: Carry flag 14 | STATUS_ZERO, // [1] Z: Zero flag 15 | STATUS_INT_DISABLE, // [2] I: Interrupt disable 16 | STATUS_DECIMAL, // [3] D: Decimal mode, can be set/cleared but not used 17 | STATUS_BREAK, // [4] B: Break command 18 | STATUS_UNUSED, // [5] -: Not used, wired to 1 19 | STATUS_OVERFLOW, // [6] V: Overflow flag 20 | STATUS_NEGATIVE, // [7] N: Negative flag 21 | }; 22 | 23 | static void tick(nes_t* state) { 24 | // TODO: 25 | // ppu_tick(state); 26 | // ppu_tick(state); 27 | // ppu_tick(state); 28 | state->cpu.cycle++; 29 | } 30 | 31 | /* Stack operations */ 32 | 33 | static void push(nes_t* state, u8 data) { 34 | memory_write(state, NES_STACK_OFFSET | state->cpu.s--, data); 35 | } 36 | 37 | static u8 pull(nes_t* state) { 38 | return memory_read(state, NES_STACK_OFFSET | ++state->cpu.s); 39 | } 40 | 41 | /* Flag adjustment */ 42 | 43 | static inline void update_c(nes_t* state, u16 r) { 44 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, r > 0xFF); 45 | } 46 | 47 | static inline void update_z(nes_t* state, u8 d) { 48 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_ZERO, d == 0); 49 | } 50 | 51 | static inline void update_v(nes_t* state, u8 d1, u8 d2, u16 r) { 52 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_OVERFLOW, (0xFF ^ d1 ^ d2) & (d1 ^ r) & 0x80); 53 | } 54 | 55 | static inline void update_n(nes_t* state, u8 d) { 56 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_NEGATIVE, NTH_BIT(d, 7)); 57 | } 58 | 59 | /* Interrupts */ 60 | 61 | static void interrupt_nmi(nes_t* state) { 62 | // Throw away fetched instruction 63 | tick(state); 64 | // Suppress PC increment 65 | tick(state); 66 | push(state, state->cpu.pc >> 8); 67 | tick(state); 68 | push(state, state->cpu.pc & 0xFF); 69 | tick(state); 70 | push(state, state->cpu.p | (1 << STATUS_UNUSED)); 71 | tick(state); 72 | SET_NTH_BIT(state->cpu.p, STATUS_INT_DISABLE); 73 | u8 addrl = memory_read(state, NES_NMI_HANDLE_OFFSET); 74 | tick(state); 75 | u8 addrh = memory_read(state, NES_NMI_HANDLE_OFFSET + 1); 76 | state->cpu.pc = addrl | (addrh << 8); 77 | // CPU clears NMI after handling 78 | cpu_set_nmi(state, 0); 79 | tick(state); 80 | } 81 | 82 | static void interrupt_reset(nes_t* state) { 83 | // Throw away fetched instruction 84 | tick(state); 85 | // Suppress PC increment 86 | tick(state); 87 | // Suppress the 3 writes to the stack 88 | state->cpu.s -= 3; 89 | tick(state); 90 | tick(state); 91 | tick(state); 92 | SET_NTH_BIT(state->cpu.p, STATUS_INT_DISABLE); 93 | u8 addrl = memory_read(state, NES_RESET_HANDLE_OFFSET); 94 | tick(state); 95 | u8 addrh = memory_read(state, NES_RESET_HANDLE_OFFSET + 1); 96 | state->cpu.pc = addrl | (addrh << 8); 97 | tick(state); 98 | } 99 | 100 | static void interrupt_irq(nes_t* state) { 101 | // Throw away fetched instruction 102 | tick(state); 103 | // Suppress PC increment 104 | tick(state); 105 | push(state, state->cpu.pc >> 8); 106 | tick(state); 107 | push(state, state->cpu.pc & 0xFF); 108 | tick(state); 109 | push(state, state->cpu.p | (1 << STATUS_UNUSED)); 110 | tick(state); 111 | SET_NTH_BIT(state->cpu.p, STATUS_INT_DISABLE); 112 | u8 addrl = memory_read(state, NES_IRQ_BRK_HANDLE_OFFSET); 113 | tick(state); 114 | u8 addrh = memory_read(state, NES_IRQ_BRK_HANDLE_OFFSET + 1); 115 | state->cpu.pc = addrl | (addrh << 8); 116 | tick(state); 117 | } 118 | 119 | static void BRK(nes_t* state) { 120 | state->cpu.pc++; 121 | tick(state); 122 | push(state, state->cpu.pc >> 8); 123 | tick(state); 124 | push(state, state->cpu.pc & 0xFF); 125 | push(state, state->cpu.p | (1 << STATUS_BREAK) | (1 << STATUS_UNUSED)); 126 | tick(state); 127 | SET_NTH_BIT(state->cpu.p, STATUS_INT_DISABLE); 128 | u8 addrl = memory_read(state, NES_IRQ_BRK_HANDLE_OFFSET); 129 | tick(state); 130 | u8 addrh = memory_read(state, NES_IRQ_BRK_HANDLE_OFFSET + 1); 131 | state->cpu.pc = addrl | (addrh << 8); 132 | tick(state); 133 | } 134 | 135 | /* Addressing modes */ 136 | 137 | // Immediate: 138 | // - Return current PC and increment PC (immediate stored here) 139 | static u16 addr_imm(nes_t* state) { 140 | return state->cpu.pc++; 141 | } 142 | 143 | // ZP: 144 | // - Read the immediate, increment PC 145 | // - Return the immediate 146 | static u16 addr_zp(nes_t* state) { 147 | u16 addr = memory_read(state, addr_imm(state)); 148 | tick(state); 149 | return addr; 150 | } 151 | 152 | // ZP, X: 153 | // - Read the immediate, increment PC 154 | // - Calculate imm + X, include wraparound 155 | // - Return the new address 156 | static u16 addr_zpx(nes_t* state) { 157 | u16 addr = (addr_zp(state) + state->cpu.x) % 0x100; 158 | tick(state); 159 | return addr; 160 | } 161 | 162 | // ZP, Y: 163 | // - Read the immediate, increment PC 164 | // - Calculate imm + Y, include wraparound 165 | // - Return the new address 166 | static u16 addr_zpy(nes_t* state) { 167 | u16 addr = (addr_zp(state) + state->cpu.y) % 0x100; 168 | tick(state); 169 | return addr; 170 | } 171 | 172 | // Absolute: 173 | // - Read the immediate, increment PC 174 | // - Merge new immediate with old immediate, increment PC 175 | // - Return the merged address 176 | static u16 addr_absl(nes_t* state) { 177 | u8 addrl = (u8)addr_zp(state); 178 | u8 addrh = (u8)addr_zp(state); 179 | return addrl | (addrh << 8); 180 | } 181 | 182 | // Absolute, X: 183 | // - Read the immediate, increment PC 184 | // - Read the new immediate, add the old immediate with X, increment PC 185 | // - If the sum of old imm and X overflows, reread the address next tick 186 | // - Merge old imm + X with new imm, return the merged address 187 | static u16 addr_absx_rd(nes_t* state) { 188 | u16 addrl = addr_zp(state); 189 | u8 addrh = memory_read(state, addr_imm(state)); 190 | addrl += state->cpu.x; 191 | tick(state); 192 | if ((addrl & 0xFF00) != 0) { 193 | addrl %= 0x100; 194 | addrh++; 195 | tick(state); 196 | } 197 | return addrl | (addrh << 8); 198 | } 199 | 200 | // Must incur a tick regardless of page boundary cross 201 | static u16 addr_absx_wr(nes_t* state) { 202 | u16 addrl = addr_zp(state); 203 | u8 addrh = memory_read(state, addr_imm(state)); 204 | addrl += state->cpu.x; 205 | tick(state); 206 | if ((addrl & 0xFF00) != 0) { 207 | addrl %= 0x100; 208 | addrh++; 209 | } 210 | tick(state); 211 | return addrl | (addrh << 8); 212 | } 213 | 214 | // Absolute, Y: 215 | // - Read the immediate, increment PC 216 | // - Read the new immediate, add the old immediate with Y, increment PC 217 | // - If the sum of old imm and Y overflows, reread the address next tick 218 | // - Merge old imm + Y with new imm, return the merged address 219 | static u16 addr_absy_rd(nes_t* state) { 220 | u16 addrl = addr_zp(state); 221 | u8 addrh = memory_read(state, addr_imm(state)); 222 | addrl += state->cpu.y; 223 | tick(state); 224 | if ((addrl & 0xFF00) != 0) { 225 | addrl %= 0x100; 226 | addrh++; 227 | tick(state); 228 | } 229 | return addrl | (addrh << 8); 230 | } 231 | 232 | // Must incur a tick regardless of page boundary cross 233 | static u16 addr_absy_wr(nes_t* state) { 234 | u16 addrl = addr_zp(state); 235 | u8 addrh = memory_read(state, addr_imm(state)); 236 | addrl += state->cpu.y; 237 | tick(state); 238 | if ((addrl & 0xFF00) != 0) { 239 | addrl %= 0x100; 240 | addrh++; 241 | } 242 | tick(state); 243 | return addrl | (addrh << 8); 244 | } 245 | 246 | // Absolute Indirect (JMP only): 247 | // - Read imm (pointer low), increment PC 248 | // - Read imm (pointer high), increment PC 249 | // - Read low byte from pointer 250 | // - Read high byte from pointer (wrap around) and return the merged address 251 | static u16 addr_ind(nes_t* state) { 252 | u8 ptrl = (u8)addr_zp(state); 253 | u8 ptrh = (u8)addr_zp(state); 254 | u16 ptr = ptrl | (ptrh << 8); 255 | u8 addrl = memory_read(state, ptr); 256 | tick(state); 257 | u8 addrh = memory_read(state, (ptr & 0xFF00) | ((ptr + 1) % 0x100)); 258 | tick(state); 259 | return addrl | (addrh << 8); 260 | } 261 | 262 | // X, Indirect (Indexed Indirect): 263 | // - Read imm (pointer), increment PC 264 | // - Read address at imm + X on zero page 265 | // - Read low byte from pointer 266 | // - Read high byte from pointer and return the merged address 267 | static u16 addr_xind(nes_t* state) { 268 | u8 ptr = (u8)addr_zpx(state); 269 | u8 addrl = memory_read(state, ptr); 270 | tick(state); 271 | u8 addrh = memory_read(state, (ptr + 1) % 0x100); 272 | tick(state); 273 | return addrl | (addrh << 8); 274 | } 275 | 276 | // Indirect, Y (Indirect Indexed): 277 | // - Read imm (pointer), increment PC 278 | // - Read low byte from pointer on zero page 279 | // - Read high byte from pointer on zero page, add Y to low byte 280 | // - If the sum of low byte and X overflows, reread the address next tick 281 | // - Return the merged address 282 | static u16 addr_indy_rd(nes_t* state) { 283 | u8 ptr = (u8)addr_zp(state); 284 | u16 addrl = memory_read(state, ptr); 285 | tick(state); 286 | u8 addrh = memory_read(state, (ptr + 1) % 0x100); 287 | addrl += state->cpu.y; 288 | tick(state); 289 | if ((addrl & 0xFF00) != 0) { 290 | addrl %= 0x100; 291 | addrh = (addrh + 1); 292 | tick(state); 293 | } 294 | return addrl | (addrh << 8); 295 | } 296 | 297 | // Must incur a tick regardless of page boundary cross 298 | static u16 addr_indy_wr(nes_t* state) { 299 | u8 ptr = (u8)addr_zp(state); 300 | u16 addrl = memory_read(state, ptr); 301 | tick(state); 302 | u8 addrh = memory_read(state, (ptr + 1) % 0x100); 303 | addrl += state->cpu.y; 304 | tick(state); 305 | if ((addrl & 0xFF00) != 0) { 306 | addrl %= 0x100; 307 | addrh = (addrh + 1); 308 | } 309 | tick(state); 310 | return addrl | (addrh << 8); 311 | } 312 | 313 | // Relative (Assuming branch taken): 314 | // - Read imm (offset), increment PC 315 | // - Add offset to PC 316 | // - If adding the offset overflowed the low byte of PC, add a cycle 317 | static u16 addr_rel(nes_t* state) { 318 | s8 imm = (s8)addr_zp(state); 319 | u16 addr = state->cpu.pc + imm; 320 | tick(state); 321 | if ((addr & 0x100) != (state->cpu.pc & 0x100)) tick(state); 322 | return addr; 323 | } 324 | 325 | /* Instructions */ 326 | 327 | // Load / Store operations 328 | static void instr_lda(nes_t* state, mode m) { 329 | u8 d = memory_read(state, m(state)); 330 | update_z(state, d); 331 | update_n(state, d); 332 | state->cpu.a = d; 333 | tick(state); 334 | } 335 | 336 | static void instr_ldx(nes_t* state, mode m) { 337 | u8 d = memory_read(state, m(state)); 338 | update_z(state, d); 339 | update_n(state, d); 340 | state->cpu.x = d; 341 | tick(state); 342 | } 343 | 344 | static void instr_ldy(nes_t* state, mode m) { 345 | u8 d = memory_read(state, m(state)); 346 | update_z(state, d); 347 | update_n(state, d); 348 | state->cpu.y = d; 349 | tick(state); 350 | } 351 | 352 | static void instr_sta(nes_t* state, mode m) { 353 | memory_write(state, m(state), state->cpu.a); 354 | tick(state); 355 | } 356 | 357 | static void instr_stx(nes_t* state, mode m) { 358 | memory_write(state, m(state), state->cpu.x); 359 | tick(state); 360 | } 361 | 362 | static void instr_sty(nes_t* state, mode m) { 363 | memory_write(state, m(state), state->cpu.y); 364 | tick(state); 365 | } 366 | 367 | static void instr_txa(nes_t* state) { 368 | update_z(state, state->cpu.x); 369 | update_n(state, state->cpu.x); 370 | state->cpu.a = state->cpu.x; 371 | tick(state); 372 | } 373 | 374 | static void instr_txs(nes_t* state) { 375 | state->cpu.s = state->cpu.x; 376 | tick(state); 377 | } 378 | 379 | static void instr_tya(nes_t* state) { 380 | update_z(state, state->cpu.y); 381 | update_n(state, state->cpu.y); 382 | state->cpu.a = state->cpu.y; 383 | tick(state); 384 | } 385 | 386 | static void instr_tax(nes_t* state) { 387 | update_z(state, state->cpu.a); 388 | update_n(state, state->cpu.a); 389 | state->cpu.x = state->cpu.a; 390 | tick(state); 391 | } 392 | 393 | static void instr_tay(nes_t* state) { 394 | update_z(state, state->cpu.a); 395 | update_n(state, state->cpu.a); 396 | state->cpu.y = state->cpu.a; 397 | tick(state); 398 | } 399 | 400 | static void instr_tsx(nes_t* state) { 401 | update_z(state, state->cpu.s); 402 | update_n(state, state->cpu.s); 403 | state->cpu.x = state->cpu.s; 404 | tick(state); 405 | } 406 | 407 | // Stack operations 408 | static void instr_php(nes_t* state) { 409 | // Throw away next byte 410 | tick(state); 411 | push(state, state->cpu.p | (1 << STATUS_BREAK) | (1 << STATUS_UNUSED)); 412 | tick(state); 413 | } 414 | 415 | static void instr_plp(nes_t* state) { 416 | // Throw away next byte 417 | tick(state); 418 | // S increment 419 | tick(state); 420 | state->cpu.p = (pull(state) & ~(1 << STATUS_BREAK)) | (1 << STATUS_UNUSED); 421 | tick(state); 422 | } 423 | 424 | static void instr_pha(nes_t* state) { 425 | // Throw away next byte 426 | tick(state); 427 | push(state, state->cpu.a); 428 | tick(state); 429 | } 430 | 431 | static void instr_pla(nes_t* state) { 432 | // Throw away next byte 433 | tick(state); 434 | // S increment 435 | tick(state); 436 | state->cpu.a = pull(state); 437 | update_z(state, state->cpu.a); 438 | update_n(state, state->cpu.a); 439 | tick(state); 440 | } 441 | 442 | // Arithmetic / Logical operations 443 | static void instr_adc(nes_t* state, mode m) { 444 | u8 d = memory_read(state, m(state)); 445 | u16 s = state->cpu.a + d + NTH_BIT(state->cpu.p, STATUS_CARRY); 446 | update_c(state, s); 447 | update_z(state, (u8)s); 448 | update_v(state, state->cpu.a, d, s); 449 | update_n(state, (u8)s); 450 | state->cpu.a = (u8)s; 451 | tick(state); 452 | } 453 | 454 | static void instr_sbc(nes_t* state, mode m) { 455 | u8 d = memory_read(state, m(state)); 456 | u16 s = state->cpu.a + (d ^ 0xFF) + NTH_BIT(state->cpu.p, STATUS_CARRY); 457 | update_c(state, s); 458 | update_z(state, (u8)s); 459 | update_v(state, state->cpu.a, (d ^ 0xFF), s); 460 | update_n(state, (u8)s); 461 | state->cpu.a = (u8)s; 462 | tick(state); 463 | } 464 | 465 | static void instr_and(nes_t* state, mode m) { 466 | u8 d = memory_read(state, m(state)); 467 | state->cpu.a &= d; 468 | update_z(state, state->cpu.a); 469 | update_n(state, state->cpu.a); 470 | tick(state); 471 | } 472 | 473 | static void instr_eor(nes_t* state, mode m) { 474 | u8 d = memory_read(state, m(state)); 475 | state->cpu.a ^= d; 476 | update_z(state, state->cpu.a); 477 | update_n(state, state->cpu.a); 478 | tick(state); 479 | } 480 | 481 | static void instr_ora(nes_t* state, mode m) { 482 | u8 d = memory_read(state, m(state)); 483 | state->cpu.a |= d; 484 | update_z(state, state->cpu.a); 485 | update_n(state, state->cpu.a); 486 | tick(state); 487 | } 488 | 489 | static void instr_bit(nes_t* state, mode m) { 490 | u8 d = memory_read(state, m(state)); 491 | update_z(state, state->cpu.a & d); 492 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_NEGATIVE, NTH_BIT(d, 7)); 493 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_OVERFLOW, NTH_BIT(d, 6)); 494 | tick(state); 495 | } 496 | 497 | // Compares 498 | static void instr_cmp(nes_t* state, mode m) { 499 | u8 d = memory_read(state, m(state)); 500 | u16 s = state->cpu.a + (d ^ 0xFF) + 1; 501 | update_c(state, s); 502 | update_z(state, (u8)s); 503 | update_n(state, (u8)s); 504 | tick(state); 505 | } 506 | 507 | static void instr_cpx(nes_t* state, mode m) { 508 | u8 d = memory_read(state, m(state)); 509 | u16 s = state->cpu.x + (d ^ 0xFF) + 1; 510 | update_c(state, s); 511 | update_z(state, (u8)s); 512 | update_n(state, (u8)s); 513 | tick(state); 514 | } 515 | 516 | static void instr_cpy(nes_t* state, mode m) { 517 | u8 d = memory_read(state, m(state)); 518 | u16 s = state->cpu.y + (d ^ 0xFF) + 1; 519 | update_c(state, s); 520 | update_z(state, (u8)s); 521 | update_n(state, (u8)s); 522 | tick(state); 523 | } 524 | 525 | // Increments / Decrements 526 | static void instr_inc(nes_t* state, mode m) { 527 | u16 addr = m(state); 528 | u8 d = memory_read(state, addr); 529 | tick(state); 530 | d++; 531 | update_z(state, d); 532 | update_n(state, d); 533 | tick(state); 534 | memory_write(state, addr, d); 535 | tick(state); 536 | } 537 | 538 | static void instr_inx(nes_t* state) { 539 | state->cpu.x++; 540 | update_z(state, state->cpu.x); 541 | update_n(state, state->cpu.x); 542 | tick(state); 543 | } 544 | 545 | static void instr_iny(nes_t* state) { 546 | state->cpu.y++; 547 | update_z(state, state->cpu.y); 548 | update_n(state, state->cpu.y); 549 | tick(state); 550 | } 551 | 552 | static void instr_dec(nes_t* state, mode m) { 553 | u16 addr = m(state); 554 | u8 d = memory_read(state, addr); 555 | tick(state); 556 | d--; 557 | update_z(state, d); 558 | update_n(state, d); 559 | tick(state); 560 | memory_write(state, addr, d); 561 | tick(state); 562 | } 563 | 564 | static void instr_dex(nes_t* state) { 565 | state->cpu.x--; 566 | update_z(state, state->cpu.x); 567 | update_n(state, state->cpu.x); 568 | tick(state); 569 | } 570 | 571 | static void instr_dey(nes_t* state) { 572 | state->cpu.y--; 573 | update_z(state, state->cpu.y); 574 | update_n(state, state->cpu.y); 575 | tick(state); 576 | } 577 | 578 | // Shifts 579 | static void instr_asl(nes_t* state, mode m) { 580 | u16 addr = m(state); 581 | u8 d = memory_read(state, addr); 582 | tick(state); 583 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(d, 7)); 584 | d <<= 1; 585 | update_z(state, d); 586 | update_n(state, d); 587 | tick(state); 588 | memory_write(state, addr, d); 589 | tick(state); 590 | } 591 | 592 | static void instr_asl_a(nes_t* state) { 593 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(state->cpu.a, 7)); 594 | state->cpu.a <<= 1; 595 | update_z(state, state->cpu.a); 596 | update_n(state, state->cpu.a); 597 | tick(state); 598 | } 599 | 600 | static void instr_lsr(nes_t* state, mode m) { 601 | u16 addr = m(state); 602 | u8 d = memory_read(state, addr); 603 | tick(state); 604 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(d, 0)); 605 | d >>= 1; 606 | update_z(state, d); 607 | update_n(state, d); 608 | tick(state); 609 | memory_write(state, addr, d); 610 | tick(state); 611 | } 612 | 613 | static void instr_lsr_a(nes_t* state) { 614 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(state->cpu.a, 0)); 615 | state->cpu.a >>= 1; 616 | update_z(state, state->cpu.a); 617 | update_n(state, state->cpu.a); 618 | tick(state); 619 | } 620 | 621 | static void instr_rol(nes_t* state, mode m) { 622 | u16 addr = m(state); 623 | u8 d = memory_read(state, addr); 624 | tick(state); 625 | bool c = NTH_BIT(state->cpu.p, STATUS_CARRY); 626 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(d, 7)); 627 | d = (d << 1) | c; 628 | update_z(state, d); 629 | update_n(state, d); 630 | tick(state); 631 | memory_write(state, addr, d); 632 | tick(state); 633 | } 634 | 635 | static void instr_rol_a(nes_t* state) { 636 | bool c = NTH_BIT(state->cpu.p, STATUS_CARRY); 637 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(state->cpu.a, 7)); 638 | state->cpu.a = (state->cpu.a << 1) | c; 639 | update_z(state, state->cpu.a); 640 | update_n(state, state->cpu.a); 641 | tick(state); 642 | } 643 | 644 | static void instr_ror(nes_t* state, mode m) { 645 | u16 addr = m(state); 646 | u8 d = memory_read(state, addr); 647 | tick(state); 648 | bool c = NTH_BIT(state->cpu.p, STATUS_CARRY); 649 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(d, 0)); 650 | d = (d >> 1) | (c << 7); 651 | update_z(state, d); 652 | update_n(state, d); 653 | tick(state); 654 | memory_write(state, addr, d); 655 | tick(state); 656 | } 657 | 658 | static void instr_ror_a(nes_t* state) { 659 | bool c = NTH_BIT(state->cpu.p, STATUS_CARRY); 660 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(state->cpu.a, 0)); 661 | state->cpu.a = (state->cpu.a >> 1) | (c << 7); 662 | update_z(state, state->cpu.a); 663 | update_n(state, state->cpu.a); 664 | tick(state); 665 | } 666 | 667 | // Jumps / calls 668 | static void instr_jmp(nes_t* state, mode m) { 669 | state->cpu.pc = m(state); 670 | } 671 | 672 | static void instr_jsr(nes_t* state) { 673 | u8 addrl = memory_read(state, state->cpu.pc); 674 | state->cpu.pc += 1; 675 | tick(state); 676 | tick(state); 677 | push(state, state->cpu.pc >> 8); 678 | tick(state); 679 | push(state, state->cpu.pc & 0xFF); 680 | tick(state); 681 | u8 addrh = memory_read(state, state->cpu.pc); 682 | state->cpu.pc = addrl | (addrh << 8); 683 | tick(state); 684 | } 685 | 686 | static void instr_rts(nes_t* state) { 687 | // Throw away next byte 688 | tick(state); 689 | // S increment 690 | tick(state); 691 | u8 addrl = pull(state); 692 | tick(state); 693 | u8 addrh = pull(state); 694 | state->cpu.pc = addrl | (addrh << 8); 695 | tick(state); 696 | state->cpu.pc += 1; 697 | tick(state); 698 | } 699 | 700 | static void instr_rti(nes_t* state) { 701 | // Throw away next byte 702 | tick(state); 703 | // S increment 704 | tick(state); 705 | state->cpu.p = (pull(state) & ~(1 << STATUS_BREAK)) | (1 << STATUS_UNUSED); 706 | tick(state); 707 | u8 addrl = pull(state); 708 | tick(state); 709 | u8 addrh = pull(state); 710 | state->cpu.pc = addrl | (addrh << 8); 711 | tick(state); 712 | } 713 | 714 | // Branches 715 | static void instr_bpl(nes_t* state, mode m) { 716 | if (!NTH_BIT(state->cpu.p, STATUS_NEGATIVE)) { 717 | state->cpu.pc = m(state); 718 | } else { 719 | state->cpu.pc++; 720 | tick(state); 721 | } 722 | } 723 | 724 | static void instr_bmi(nes_t* state, mode m) { 725 | if (NTH_BIT(state->cpu.p, STATUS_NEGATIVE)) { 726 | state->cpu.pc = m(state); 727 | } else { 728 | state->cpu.pc++; 729 | tick(state); 730 | } 731 | } 732 | 733 | static void instr_bvc(nes_t* state, mode m) { 734 | if (!NTH_BIT(state->cpu.p, STATUS_OVERFLOW)) { 735 | state->cpu.pc = m(state); 736 | } else { 737 | state->cpu.pc++; 738 | tick(state); 739 | } 740 | } 741 | 742 | static void instr_bvs(nes_t* state, mode m) { 743 | if (NTH_BIT(state->cpu.p, STATUS_OVERFLOW)) { 744 | state->cpu.pc = m(state); 745 | } else { 746 | state->cpu.pc++; 747 | tick(state); 748 | } 749 | } 750 | 751 | static void instr_bcc(nes_t* state, mode m) { 752 | if (!NTH_BIT(state->cpu.p, STATUS_CARRY)) { 753 | state->cpu.pc = m(state); 754 | } else { 755 | state->cpu.pc++; 756 | tick(state); 757 | } 758 | } 759 | 760 | static void instr_bcs(nes_t* state, mode m) { 761 | if (NTH_BIT(state->cpu.p, STATUS_CARRY)) { 762 | state->cpu.pc = m(state); 763 | } else { 764 | state->cpu.pc++; 765 | tick(state); 766 | } 767 | } 768 | 769 | static void instr_bne(nes_t* state, mode m) { 770 | if (!NTH_BIT(state->cpu.p, STATUS_ZERO)) { 771 | state->cpu.pc = m(state); 772 | } else { 773 | state->cpu.pc++; 774 | tick(state); 775 | } 776 | } 777 | 778 | static void instr_beq(nes_t* state, mode m) { 779 | if (NTH_BIT(state->cpu.p, STATUS_ZERO)) { 780 | state->cpu.pc = m(state); 781 | } else { 782 | state->cpu.pc++; 783 | tick(state); 784 | } 785 | } 786 | 787 | // Status register operations 788 | static void instr_clc(nes_t* state) { 789 | CLEAR_NTH_BIT(state->cpu.p, STATUS_CARRY); 790 | tick(state); 791 | } 792 | 793 | static void instr_cli(nes_t* state) { 794 | CLEAR_NTH_BIT(state->cpu.p, STATUS_INT_DISABLE); 795 | tick(state); 796 | } 797 | 798 | static void instr_clv(nes_t* state) { 799 | CLEAR_NTH_BIT(state->cpu.p, STATUS_OVERFLOW); 800 | tick(state); 801 | } 802 | 803 | static void instr_cld(nes_t* state) { 804 | CLEAR_NTH_BIT(state->cpu.p, STATUS_DECIMAL); 805 | tick(state); 806 | } 807 | 808 | static void instr_sec(nes_t* state) { 809 | SET_NTH_BIT(state->cpu.p, STATUS_CARRY); 810 | tick(state); 811 | } 812 | 813 | static void instr_sei(nes_t* state) { 814 | SET_NTH_BIT(state->cpu.p, STATUS_INT_DISABLE); 815 | tick(state); 816 | } 817 | 818 | static void instr_sed(nes_t* state) { 819 | SET_NTH_BIT(state->cpu.p, STATUS_DECIMAL); 820 | tick(state); 821 | } 822 | 823 | // System functions 824 | static void instr_nop(nes_t* state) { 825 | tick(state); 826 | } 827 | 828 | // Illegal opcodes 829 | static void instr_skb(nes_t* state, mode m) { 830 | m(state); 831 | tick(state); 832 | } 833 | 834 | static void instr_lax(nes_t* state, mode m) { 835 | u8 d = memory_read(state, m(state)); 836 | update_z(state, d); 837 | update_n(state, d); 838 | state->cpu.a = d; 839 | state->cpu.x = d; 840 | tick(state); 841 | } 842 | 843 | static void instr_sax(nes_t* state, mode m) { 844 | u16 addr = m(state); 845 | memory_write(state, addr, state->cpu.a & state->cpu.x); 846 | tick(state); 847 | } 848 | 849 | static void instr_axs(nes_t* state, mode m) { 850 | u8 d = memory_read(state, m(state)); 851 | u16 s = (state->cpu.a & state->cpu.x) + (d ^ 0xFF) + 1; 852 | update_c(state, s); 853 | update_z(state, (u8)s); 854 | update_n(state, (u8)s); 855 | state->cpu.x = (u8)s; 856 | tick(state); 857 | } 858 | 859 | static void instr_dcp(nes_t* state, mode m) { 860 | u16 addr = m(state); 861 | u8 d = memory_read(state, addr); 862 | tick(state); 863 | d--; 864 | u16 s = state->cpu.a + (d ^ 0xFF) + 1; 865 | update_c(state, s); 866 | update_z(state, (u8)s); 867 | update_n(state, (u8)s); 868 | tick(state); 869 | memory_write(state, addr, d); 870 | tick(state); 871 | } 872 | 873 | static void instr_isc(nes_t* state, mode m) { 874 | u16 addr = m(state); 875 | u8 d = memory_read(state, addr); 876 | tick(state); 877 | d++; 878 | u16 s = state->cpu.a + (d ^ 0xFF) + NTH_BIT(state->cpu.p, STATUS_CARRY); 879 | update_c(state, s); 880 | update_z(state, (u8)s); 881 | update_v(state, state->cpu.a, (d ^ 0xFF), s); 882 | update_n(state, (u8)s); 883 | state->cpu.a = (u8)s; 884 | tick(state); 885 | memory_write(state, addr, d); 886 | tick(state); 887 | } 888 | 889 | static void instr_slo(nes_t* state, mode m) { 890 | u16 addr = m(state); 891 | u8 d = memory_read(state, addr); 892 | tick(state); 893 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(d, 7)); 894 | d <<= 1; 895 | state->cpu.a |= d; 896 | update_z(state, state->cpu.a); 897 | update_n(state, state->cpu.a); 898 | tick(state); 899 | memory_write(state, addr, d); 900 | tick(state); 901 | } 902 | 903 | static void instr_rla(nes_t* state, mode m) { 904 | u16 addr = m(state); 905 | u8 d = memory_read(state, addr); 906 | tick(state); 907 | bool c = NTH_BIT(state->cpu.p, STATUS_CARRY); 908 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(d, 7)); 909 | d = (d << 1) | c; 910 | state->cpu.a &= d; 911 | update_z(state, state->cpu.a); 912 | update_n(state, state->cpu.a); 913 | tick(state); 914 | memory_write(state, addr, d); 915 | tick(state); 916 | } 917 | 918 | static void instr_sre(nes_t* state, mode m) { 919 | u16 addr = m(state); 920 | u8 d = memory_read(state, addr); 921 | tick(state); 922 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(d, 0)); 923 | d >>= 1; 924 | state->cpu.a ^= d; 925 | update_z(state, state->cpu.a); 926 | update_n(state, state->cpu.a); 927 | tick(state); 928 | memory_write(state, addr, d); 929 | tick(state); 930 | } 931 | 932 | static void instr_rra(nes_t* state, mode m) { 933 | u16 addr = m(state); 934 | u8 d = memory_read(state, addr); 935 | tick(state); 936 | bool c = NTH_BIT(state->cpu.p, STATUS_CARRY); 937 | ASSIGN_NTH_BIT(state->cpu.p, STATUS_CARRY, NTH_BIT(d, 0)); 938 | d = (d >> 1) | (c << 7); 939 | u16 s = state->cpu.a + d + NTH_BIT(state->cpu.p, STATUS_CARRY); 940 | update_c(state, s); 941 | update_z(state, (u8)s); 942 | update_v(state, state->cpu.a, d, s); 943 | update_n(state, (u8)s); 944 | state->cpu.a = (u8)s; 945 | tick(state); 946 | memory_write(state, addr, d); 947 | tick(state); 948 | } 949 | 950 | /* CPU Execution */ 951 | 952 | static void execute_instruction(nes_t* state) { 953 | // Fetch 954 | u8 op = memory_read(state, state->cpu.pc++); 955 | tick(state); 956 | 957 | #define INSTRUCTION_CASE_DEFAULT(op, fn) \ 958 | case op: { \ 959 | fn(state); \ 960 | break; \ 961 | } 962 | 963 | #define INSTRUCTION_CASE_VARIANT(op, fn, variant) \ 964 | case op: { \ 965 | fn(state, variant); \ 966 | break; \ 967 | } 968 | 969 | // Decode/Execute 970 | switch (op) { 971 | INSTRUCTION_CASE_DEFAULT(0x00, BRK) 972 | INSTRUCTION_CASE_VARIANT(0x01, instr_ora, addr_xind) 973 | INSTRUCTION_CASE_VARIANT(0x05, instr_ora, addr_zp) 974 | INSTRUCTION_CASE_VARIANT(0x06, instr_asl, addr_zp) 975 | INSTRUCTION_CASE_DEFAULT(0x08, instr_php) 976 | INSTRUCTION_CASE_VARIANT(0x09, instr_ora, addr_imm) 977 | INSTRUCTION_CASE_DEFAULT(0x0A, instr_asl_a) 978 | INSTRUCTION_CASE_VARIANT(0x0D, instr_ora, addr_absl) 979 | INSTRUCTION_CASE_VARIANT(0x0E, instr_asl, addr_absl) 980 | INSTRUCTION_CASE_VARIANT(0x10, instr_bpl, addr_rel) 981 | INSTRUCTION_CASE_VARIANT(0x11, instr_ora, addr_indy_rd) 982 | INSTRUCTION_CASE_VARIANT(0x15, instr_ora, addr_zpx) 983 | INSTRUCTION_CASE_VARIANT(0x16, instr_asl, addr_zpx) 984 | INSTRUCTION_CASE_DEFAULT(0x18, instr_clc) 985 | INSTRUCTION_CASE_VARIANT(0x19, instr_ora, addr_absy_rd) 986 | INSTRUCTION_CASE_VARIANT(0x1D, instr_ora, addr_absx_rd) 987 | INSTRUCTION_CASE_VARIANT(0x1E, instr_asl, addr_absx_wr) 988 | INSTRUCTION_CASE_DEFAULT(0x20, instr_jsr) 989 | INSTRUCTION_CASE_VARIANT(0x21, instr_and, addr_xind) 990 | INSTRUCTION_CASE_VARIANT(0x24, instr_bit, addr_zp) 991 | INSTRUCTION_CASE_VARIANT(0x25, instr_and, addr_zp) 992 | INSTRUCTION_CASE_VARIANT(0x26, instr_rol, addr_zp) 993 | INSTRUCTION_CASE_DEFAULT(0x28, instr_plp) 994 | INSTRUCTION_CASE_VARIANT(0x29, instr_and, addr_imm) 995 | INSTRUCTION_CASE_DEFAULT(0x2A, instr_rol_a) 996 | INSTRUCTION_CASE_VARIANT(0x2C, instr_bit, addr_absl) 997 | INSTRUCTION_CASE_VARIANT(0x2D, instr_and, addr_absl) 998 | INSTRUCTION_CASE_VARIANT(0x2E, instr_rol, addr_absl) 999 | INSTRUCTION_CASE_VARIANT(0x30, instr_bmi, addr_rel) 1000 | INSTRUCTION_CASE_VARIANT(0x31, instr_and, addr_indy_rd) 1001 | INSTRUCTION_CASE_VARIANT(0x35, instr_and, addr_zpx) 1002 | INSTRUCTION_CASE_VARIANT(0x36, instr_rol, addr_zpx) 1003 | INSTRUCTION_CASE_DEFAULT(0x38, instr_sec) 1004 | INSTRUCTION_CASE_VARIANT(0x39, instr_and, addr_absy_rd) 1005 | INSTRUCTION_CASE_VARIANT(0x3D, instr_and, addr_absx_rd) 1006 | INSTRUCTION_CASE_VARIANT(0x3E, instr_rol, addr_absx_wr) 1007 | INSTRUCTION_CASE_DEFAULT(0x40, instr_rti) 1008 | INSTRUCTION_CASE_VARIANT(0x41, instr_eor, addr_xind) 1009 | INSTRUCTION_CASE_VARIANT(0x45, instr_eor, addr_zp) 1010 | INSTRUCTION_CASE_VARIANT(0x46, instr_lsr, addr_zp) 1011 | INSTRUCTION_CASE_DEFAULT(0x48, instr_pha) 1012 | INSTRUCTION_CASE_VARIANT(0x49, instr_eor, addr_imm) 1013 | INSTRUCTION_CASE_DEFAULT(0x4A, instr_lsr_a) 1014 | INSTRUCTION_CASE_VARIANT(0x4C, instr_jmp, addr_absl) 1015 | INSTRUCTION_CASE_VARIANT(0x4D, instr_eor, addr_absl) 1016 | INSTRUCTION_CASE_VARIANT(0x4E, instr_lsr, addr_absl) 1017 | INSTRUCTION_CASE_VARIANT(0x50, instr_bvc, addr_rel) 1018 | INSTRUCTION_CASE_VARIANT(0x51, instr_eor, addr_indy_rd) 1019 | INSTRUCTION_CASE_VARIANT(0x55, instr_eor, addr_zpx) 1020 | INSTRUCTION_CASE_VARIANT(0x56, instr_lsr, addr_zpx) 1021 | INSTRUCTION_CASE_DEFAULT(0x58, instr_cli) 1022 | INSTRUCTION_CASE_VARIANT(0x59, instr_eor, addr_absy_rd) 1023 | INSTRUCTION_CASE_VARIANT(0x5D, instr_eor, addr_absx_rd) 1024 | INSTRUCTION_CASE_VARIANT(0x5E, instr_lsr, addr_absx_wr) 1025 | INSTRUCTION_CASE_DEFAULT(0x60, instr_rts) 1026 | INSTRUCTION_CASE_VARIANT(0x61, instr_adc, addr_xind) 1027 | INSTRUCTION_CASE_VARIANT(0x65, instr_adc, addr_zp) 1028 | INSTRUCTION_CASE_VARIANT(0x66, instr_ror, addr_zp) 1029 | INSTRUCTION_CASE_DEFAULT(0x68, instr_pla) 1030 | INSTRUCTION_CASE_VARIANT(0x69, instr_adc, addr_imm) 1031 | INSTRUCTION_CASE_DEFAULT(0x6A, instr_ror_a) 1032 | INSTRUCTION_CASE_VARIANT(0x6C, instr_jmp, addr_ind) 1033 | INSTRUCTION_CASE_VARIANT(0x6D, instr_adc, addr_absl) 1034 | INSTRUCTION_CASE_VARIANT(0x6E, instr_ror, addr_absl) 1035 | INSTRUCTION_CASE_VARIANT(0x70, instr_bvs, addr_rel) 1036 | INSTRUCTION_CASE_VARIANT(0x71, instr_adc, addr_indy_rd) 1037 | INSTRUCTION_CASE_VARIANT(0x75, instr_adc, addr_zpx) 1038 | INSTRUCTION_CASE_VARIANT(0x76, instr_ror, addr_zpx) 1039 | INSTRUCTION_CASE_DEFAULT(0x78, instr_sei) 1040 | INSTRUCTION_CASE_VARIANT(0x79, instr_adc, addr_absy_rd) 1041 | INSTRUCTION_CASE_VARIANT(0x7D, instr_adc, addr_absx_rd) 1042 | INSTRUCTION_CASE_VARIANT(0x7E, instr_ror, addr_absx_wr) 1043 | INSTRUCTION_CASE_VARIANT(0x81, instr_sta, addr_xind) 1044 | INSTRUCTION_CASE_VARIANT(0x84, instr_sty, addr_zp) 1045 | INSTRUCTION_CASE_VARIANT(0x85, instr_sta, addr_zp) 1046 | INSTRUCTION_CASE_VARIANT(0x86, instr_stx, addr_zp) 1047 | INSTRUCTION_CASE_DEFAULT(0x88, instr_dey) 1048 | INSTRUCTION_CASE_DEFAULT(0x8A, instr_txa) 1049 | INSTRUCTION_CASE_VARIANT(0x8C, instr_sty, addr_absl) 1050 | INSTRUCTION_CASE_VARIANT(0x8D, instr_sta, addr_absl) 1051 | INSTRUCTION_CASE_VARIANT(0x8E, instr_stx, addr_absl) 1052 | INSTRUCTION_CASE_VARIANT(0x90, instr_bcc, addr_rel) 1053 | INSTRUCTION_CASE_VARIANT(0x91, instr_sta, addr_indy_wr) 1054 | INSTRUCTION_CASE_VARIANT(0x94, instr_sty, addr_zpx) 1055 | INSTRUCTION_CASE_VARIANT(0x95, instr_sta, addr_zpx) 1056 | INSTRUCTION_CASE_VARIANT(0x96, instr_stx, addr_zpy) 1057 | INSTRUCTION_CASE_DEFAULT(0x98, instr_tya) 1058 | INSTRUCTION_CASE_VARIANT(0x99, instr_sta, addr_absy_wr) 1059 | INSTRUCTION_CASE_DEFAULT(0x9A, instr_txs) 1060 | INSTRUCTION_CASE_VARIANT(0x9D, instr_sta, addr_absx_wr) 1061 | INSTRUCTION_CASE_VARIANT(0xA0, instr_ldy, addr_imm) 1062 | INSTRUCTION_CASE_VARIANT(0xA1, instr_lda, addr_xind) 1063 | INSTRUCTION_CASE_VARIANT(0xA2, instr_ldx, addr_imm) 1064 | INSTRUCTION_CASE_VARIANT(0xA4, instr_ldy, addr_zp) 1065 | INSTRUCTION_CASE_VARIANT(0xA5, instr_lda, addr_zp) 1066 | INSTRUCTION_CASE_VARIANT(0xA6, instr_ldx, addr_zp) 1067 | INSTRUCTION_CASE_DEFAULT(0xA8, instr_tay) 1068 | INSTRUCTION_CASE_VARIANT(0xA9, instr_lda, addr_imm) 1069 | INSTRUCTION_CASE_DEFAULT(0xAA, instr_tax) 1070 | INSTRUCTION_CASE_VARIANT(0xAC, instr_ldy, addr_absl) 1071 | INSTRUCTION_CASE_VARIANT(0xAD, instr_lda, addr_absl) 1072 | INSTRUCTION_CASE_VARIANT(0xAE, instr_ldx, addr_absl) 1073 | INSTRUCTION_CASE_VARIANT(0xB0, instr_bcs, addr_rel) 1074 | INSTRUCTION_CASE_VARIANT(0xB1, instr_lda, addr_indy_rd) 1075 | INSTRUCTION_CASE_VARIANT(0xB4, instr_ldy, addr_zpx) 1076 | INSTRUCTION_CASE_VARIANT(0xB5, instr_lda, addr_zpx) 1077 | INSTRUCTION_CASE_VARIANT(0xB6, instr_ldx, addr_zpy) 1078 | INSTRUCTION_CASE_DEFAULT(0xB8, instr_clv) 1079 | INSTRUCTION_CASE_VARIANT(0xB9, instr_lda, addr_absy_rd) 1080 | INSTRUCTION_CASE_DEFAULT(0xBA, instr_tsx) 1081 | INSTRUCTION_CASE_VARIANT(0xBC, instr_ldy, addr_absx_rd) 1082 | INSTRUCTION_CASE_VARIANT(0xBD, instr_lda, addr_absx_rd) 1083 | INSTRUCTION_CASE_VARIANT(0xBE, instr_ldx, addr_absy_rd) 1084 | INSTRUCTION_CASE_VARIANT(0xC0, instr_cpy, addr_imm) 1085 | INSTRUCTION_CASE_VARIANT(0xC1, instr_cmp, addr_xind) 1086 | INSTRUCTION_CASE_VARIANT(0xC4, instr_cpy, addr_zp) 1087 | INSTRUCTION_CASE_VARIANT(0xC5, instr_cmp, addr_zp) 1088 | INSTRUCTION_CASE_VARIANT(0xC6, instr_dec, addr_zp) 1089 | INSTRUCTION_CASE_DEFAULT(0xC8, instr_iny) 1090 | INSTRUCTION_CASE_VARIANT(0xC9, instr_cmp, addr_imm) 1091 | INSTRUCTION_CASE_DEFAULT(0xCA, instr_dex) 1092 | INSTRUCTION_CASE_VARIANT(0xCC, instr_cpy, addr_absl) 1093 | INSTRUCTION_CASE_VARIANT(0xCD, instr_cmp, addr_absl) 1094 | INSTRUCTION_CASE_VARIANT(0xCE, instr_dec, addr_absl) 1095 | INSTRUCTION_CASE_VARIANT(0xD0, instr_bne, addr_rel) 1096 | INSTRUCTION_CASE_VARIANT(0xD1, instr_cmp, addr_indy_rd) 1097 | INSTRUCTION_CASE_VARIANT(0xD5, instr_cmp, addr_zpx) 1098 | INSTRUCTION_CASE_VARIANT(0xD6, instr_dec, addr_zpx) 1099 | INSTRUCTION_CASE_DEFAULT(0xD8, instr_cld) 1100 | INSTRUCTION_CASE_VARIANT(0xD9, instr_cmp, addr_absy_rd) 1101 | INSTRUCTION_CASE_VARIANT(0xDD, instr_cmp, addr_absx_rd) 1102 | INSTRUCTION_CASE_VARIANT(0xDE, instr_dec, addr_absx_wr) 1103 | INSTRUCTION_CASE_VARIANT(0xE0, instr_cpx, addr_imm) 1104 | INSTRUCTION_CASE_VARIANT(0xE1, instr_sbc, addr_xind) 1105 | INSTRUCTION_CASE_VARIANT(0xE4, instr_cpx, addr_zp) 1106 | INSTRUCTION_CASE_VARIANT(0xE5, instr_sbc, addr_zp) 1107 | INSTRUCTION_CASE_VARIANT(0xE6, instr_inc, addr_zp) 1108 | INSTRUCTION_CASE_DEFAULT(0xE8, instr_inx) 1109 | INSTRUCTION_CASE_VARIANT(0xE9, instr_sbc, addr_imm) 1110 | INSTRUCTION_CASE_DEFAULT(0xEA, instr_nop) 1111 | INSTRUCTION_CASE_VARIANT(0xEC, instr_cpx, addr_absl) 1112 | INSTRUCTION_CASE_VARIANT(0xED, instr_sbc, addr_absl) 1113 | INSTRUCTION_CASE_VARIANT(0xEE, instr_inc, addr_absl) 1114 | INSTRUCTION_CASE_VARIANT(0xF0, instr_beq, addr_rel) 1115 | INSTRUCTION_CASE_VARIANT(0xF1, instr_sbc, addr_indy_rd) 1116 | INSTRUCTION_CASE_VARIANT(0xF5, instr_sbc, addr_zpx) 1117 | INSTRUCTION_CASE_VARIANT(0xF6, instr_inc, addr_zpx) 1118 | INSTRUCTION_CASE_DEFAULT(0xF8, instr_sed) 1119 | INSTRUCTION_CASE_VARIANT(0xF9, instr_sbc, addr_absy_rd) 1120 | INSTRUCTION_CASE_VARIANT(0xFD, instr_sbc, addr_absx_rd) 1121 | INSTRUCTION_CASE_VARIANT(0xFE, instr_inc, addr_absx_wr) 1122 | // Illegal opcodes 1123 | INSTRUCTION_CASE_VARIANT(0x03, instr_slo, addr_xind) 1124 | INSTRUCTION_CASE_VARIANT(0x07, instr_slo, addr_zp) 1125 | INSTRUCTION_CASE_VARIANT(0x0F, instr_slo, addr_absl) 1126 | INSTRUCTION_CASE_VARIANT(0x13, instr_slo, addr_indy_rd) 1127 | INSTRUCTION_CASE_VARIANT(0x17, instr_slo, addr_zpx) 1128 | INSTRUCTION_CASE_VARIANT(0x1B, instr_slo, addr_absy_rd) 1129 | INSTRUCTION_CASE_VARIANT(0x1F, instr_slo, addr_absx_rd) 1130 | INSTRUCTION_CASE_VARIANT(0x23, instr_rla, addr_xind) 1131 | INSTRUCTION_CASE_VARIANT(0x27, instr_rla, addr_zp) 1132 | INSTRUCTION_CASE_VARIANT(0x2F, instr_rla, addr_absl) 1133 | INSTRUCTION_CASE_VARIANT(0x33, instr_rla, addr_indy_rd) 1134 | INSTRUCTION_CASE_VARIANT(0x37, instr_rla, addr_zpx) 1135 | INSTRUCTION_CASE_VARIANT(0x3B, instr_rla, addr_absy_rd) 1136 | INSTRUCTION_CASE_VARIANT(0x3F, instr_rla, addr_absx_rd) 1137 | INSTRUCTION_CASE_VARIANT(0x43, instr_sre, addr_xind) 1138 | INSTRUCTION_CASE_VARIANT(0x47, instr_sre, addr_zp) 1139 | INSTRUCTION_CASE_VARIANT(0x4F, instr_sre, addr_absl) 1140 | INSTRUCTION_CASE_VARIANT(0x53, instr_sre, addr_indy_rd) 1141 | INSTRUCTION_CASE_VARIANT(0x57, instr_sre, addr_zpx) 1142 | INSTRUCTION_CASE_VARIANT(0x5B, instr_sre, addr_absy_rd) 1143 | INSTRUCTION_CASE_VARIANT(0x5F, instr_sre, addr_absx_rd) 1144 | INSTRUCTION_CASE_VARIANT(0x63, instr_rra, addr_xind) 1145 | INSTRUCTION_CASE_VARIANT(0x67, instr_rra, addr_zp) 1146 | INSTRUCTION_CASE_VARIANT(0x6F, instr_rra, addr_absl) 1147 | INSTRUCTION_CASE_VARIANT(0x73, instr_rra, addr_indy_rd) 1148 | INSTRUCTION_CASE_VARIANT(0x77, instr_rra, addr_zpx) 1149 | INSTRUCTION_CASE_VARIANT(0x7B, instr_rra, addr_absy_rd) 1150 | INSTRUCTION_CASE_VARIANT(0x7F, instr_rra, addr_absx_rd) 1151 | INSTRUCTION_CASE_VARIANT(0x83, instr_sax, addr_xind) 1152 | INSTRUCTION_CASE_VARIANT(0x87, instr_sax, addr_zp) 1153 | INSTRUCTION_CASE_VARIANT(0x8F, instr_sax, addr_absl) 1154 | INSTRUCTION_CASE_VARIANT(0x97, instr_sax, addr_zpy) 1155 | INSTRUCTION_CASE_VARIANT(0xA3, instr_lax, addr_xind) 1156 | INSTRUCTION_CASE_VARIANT(0xA7, instr_lax, addr_zp) 1157 | INSTRUCTION_CASE_VARIANT(0xAB, instr_lax, addr_imm) 1158 | INSTRUCTION_CASE_VARIANT(0xAF, instr_lax, addr_absl) 1159 | INSTRUCTION_CASE_VARIANT(0xB3, instr_lax, addr_indy_rd) 1160 | INSTRUCTION_CASE_VARIANT(0xB7, instr_lax, addr_zpy) 1161 | INSTRUCTION_CASE_VARIANT(0xBF, instr_lax, addr_absy_rd) 1162 | INSTRUCTION_CASE_VARIANT(0xC3, instr_dcp, addr_xind) 1163 | INSTRUCTION_CASE_VARIANT(0xC7, instr_dcp, addr_zp) 1164 | INSTRUCTION_CASE_VARIANT(0xCB, instr_axs, addr_imm) 1165 | INSTRUCTION_CASE_VARIANT(0xCF, instr_dcp, addr_absl) 1166 | INSTRUCTION_CASE_VARIANT(0xD3, instr_dcp, addr_indy_rd) 1167 | INSTRUCTION_CASE_VARIANT(0xD7, instr_dcp, addr_zpx) 1168 | INSTRUCTION_CASE_VARIANT(0xDB, instr_dcp, addr_absy_rd) 1169 | INSTRUCTION_CASE_VARIANT(0xDF, instr_dcp, addr_absx_rd) 1170 | INSTRUCTION_CASE_VARIANT(0xE3, instr_isc, addr_xind) 1171 | INSTRUCTION_CASE_VARIANT(0xE7, instr_isc, addr_zp) 1172 | INSTRUCTION_CASE_VARIANT(0xEB, instr_sbc, addr_imm) 1173 | INSTRUCTION_CASE_VARIANT(0xEF, instr_isc, addr_absl) 1174 | INSTRUCTION_CASE_VARIANT(0xF3, instr_isc, addr_indy_rd) 1175 | INSTRUCTION_CASE_VARIANT(0xF7, instr_isc, addr_zpx) 1176 | INSTRUCTION_CASE_VARIANT(0xFB, instr_isc, addr_absy_rd) 1177 | INSTRUCTION_CASE_VARIANT(0xFF, instr_isc, addr_absx_rd) 1178 | INSTRUCTION_CASE_DEFAULT(0x1A, instr_nop) 1179 | INSTRUCTION_CASE_DEFAULT(0x3A, instr_nop) 1180 | INSTRUCTION_CASE_DEFAULT(0x5A, instr_nop) 1181 | INSTRUCTION_CASE_DEFAULT(0x7A, instr_nop) 1182 | INSTRUCTION_CASE_DEFAULT(0xDA, instr_nop) 1183 | INSTRUCTION_CASE_DEFAULT(0xFA, instr_nop) 1184 | INSTRUCTION_CASE_VARIANT(0x04, instr_skb, addr_zp) 1185 | INSTRUCTION_CASE_VARIANT(0x44, instr_skb, addr_zp) 1186 | INSTRUCTION_CASE_VARIANT(0x64, instr_skb, addr_zp) 1187 | INSTRUCTION_CASE_VARIANT(0x14, instr_skb, addr_zpx) 1188 | INSTRUCTION_CASE_VARIANT(0x34, instr_skb, addr_zpx) 1189 | INSTRUCTION_CASE_VARIANT(0x54, instr_skb, addr_zpx) 1190 | INSTRUCTION_CASE_VARIANT(0x74, instr_skb, addr_zpx) 1191 | INSTRUCTION_CASE_VARIANT(0xD4, instr_skb, addr_zpx) 1192 | INSTRUCTION_CASE_VARIANT(0xF4, instr_skb, addr_zpx) 1193 | INSTRUCTION_CASE_VARIANT(0x80, instr_skb, addr_imm) 1194 | INSTRUCTION_CASE_VARIANT(0x82, instr_skb, addr_imm) 1195 | INSTRUCTION_CASE_VARIANT(0x89, instr_skb, addr_imm) 1196 | INSTRUCTION_CASE_VARIANT(0xC2, instr_skb, addr_imm) 1197 | INSTRUCTION_CASE_VARIANT(0xE2, instr_skb, addr_imm) 1198 | INSTRUCTION_CASE_VARIANT(0x0C, instr_skb, addr_absl) 1199 | INSTRUCTION_CASE_VARIANT(0x1C, instr_skb, addr_absx_rd) 1200 | INSTRUCTION_CASE_VARIANT(0x3C, instr_skb, addr_absx_rd) 1201 | INSTRUCTION_CASE_VARIANT(0x5C, instr_skb, addr_absx_rd) 1202 | INSTRUCTION_CASE_VARIANT(0x7C, instr_skb, addr_absx_rd) 1203 | INSTRUCTION_CASE_VARIANT(0xDC, instr_skb, addr_absx_rd) 1204 | INSTRUCTION_CASE_VARIANT(0xFC, instr_skb, addr_absx_rd) 1205 | default: 1206 | LOG("Unsupported instruction: 0x%02X\n", op); 1207 | instr_nop(state); 1208 | break; 1209 | } 1210 | 1211 | #undef INSTRUCTION_CASE_DEFAULT 1212 | #undef INSTRUCTION_CASE_VARIANT 1213 | } 1214 | 1215 | void cpu_init(nes_t* state) { 1216 | state->cpu.a = 0x00; 1217 | state->cpu.x = 0x00; 1218 | state->cpu.y = 0x00; 1219 | state->cpu.s = 0x00; 1220 | state->cpu.p = 0x00 | (1 << STATUS_INT_DISABLE) | (1 << STATUS_UNUSED); 1221 | state->cpu.nmi = 0; 1222 | state->cpu.irq = 0; 1223 | state->cpu.cycle = 0; 1224 | interrupt_reset(state); 1225 | } 1226 | 1227 | void cpu_step(nes_t* state) { 1228 | if (state->cpu.nmi) { 1229 | interrupt_nmi(state); 1230 | } else if (state->cpu.irq && !NTH_BIT(state->cpu.p, STATUS_INT_DISABLE)) { 1231 | interrupt_irq(state); 1232 | } 1233 | execute_instruction(state); 1234 | } 1235 | 1236 | void cpu_set_nmi(nes_t* state, bool enable) { 1237 | state->cpu.nmi = enable; 1238 | } 1239 | 1240 | void cpu_set_irq(nes_t* state, bool enable) { 1241 | state->cpu.irq = enable; 1242 | } 1243 | -------------------------------------------------------------------------------- /src/include/bitmask.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define NTH_BIT(reg, n) (((reg) >> (n)) & 1U) 4 | 5 | #define SET_NTH_BIT(reg, n) reg |= (1U << (n)) 6 | #define CLEAR_NTH_BIT(reg, n) reg &= ~(1U << (n)) 7 | #define FLIP_NTH_BIT(reg, n) reg ^= ~(1U << (n)) 8 | #define ASSIGN_NTH_BIT(reg, n, x) reg = (reg & ~(1U << (n))) | (!!(x) << (n)) 9 | -------------------------------------------------------------------------------- /src/include/cartridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nes.h" 4 | 5 | typedef enum { 6 | CARTRIDGE_SUCCESS = 0, 7 | CARTRIDGE_NOT_FOUND, 8 | CARTRIDGE_INVALID, 9 | CARTRIDGE_UNSUPPORTED, 10 | } cartridge_result_t; 11 | 12 | cartridge_result_t cartridge_init(nes_t* nes, char const* filename); 13 | u8 cartridge_prg_rd(nes_t* nes, u16 addr); 14 | u8 cartridge_chr_rd(nes_t* nes, u16 addr); 15 | void cartridge_prg_wr(nes_t* nes, u16 addr, u8 data); 16 | void cartridge_chr_wr(nes_t* nes, u16 addr, u8 data); 17 | void reset(nes_t* nes); 18 | -------------------------------------------------------------------------------- /src/include/cpu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nes.h" 4 | 5 | void cpu_init(nes_t* state); 6 | void cpu_step(nes_t* state); 7 | void cpu_set_nmi(nes_t* state, bool enable); 8 | void cpu_set_irq(nes_t* state, bool enable); 9 | -------------------------------------------------------------------------------- /src/include/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef PRINTF_SUPPORTED 4 | #include 5 | #define LOG(...) printf(__VA_ARGS__) 6 | #else 7 | #define LOG(...) 8 | #endif // PRINTF_SUPPORTED 9 | -------------------------------------------------------------------------------- /src/include/mappers/mapper0.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | void mapper0_init(u32* prg_map, u32* chr_map, u8 prg_units); 6 | -------------------------------------------------------------------------------- /src/include/memory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nes.h" 4 | 5 | void memory_init(nes_t* state); 6 | u8 memory_read(nes_t* state, u16 addr); 7 | void memory_write(nes_t* state, u16 addr, u8 data); 8 | -------------------------------------------------------------------------------- /src/include/nes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | /* NES Memory Layout */ 6 | // $0000 - $00FF(256 bytes) - Zero Page 7 | // $0100 - $01FF(256 bytes) - Stack memory 8 | // $0200 - $07FF(1536 bytes) - RAM 9 | // $0800 - $0FFF(2048 bytes) - Mirror of $000 - $07FF 10 | // $1000 - $17FF(2048 bytes) - Mirror of $000 - $07FF 11 | // $1800 - $1FFF(2048 bytes) - Mirror of $000 - $07FF 12 | // $2000 - $2007(8 bytes) - I/O registers 13 | // $2008 - $3FFF(8184 bytes) - Mirror of $2000 - $2007 (repeated) 14 | // $4000 - $401F(32 bytes) - I/O registers 15 | // $4020 - $5FFF(8160 bytes) - Expansion ROM 16 | // $6000 - $7FFF(8192 bytes) - SRAM 17 | // $8000 - $FFFF(32768 bytes) - PRG-ROM 18 | // $FFFA - $FFFB(2 bytes) - NMI handler routine 19 | // $FFFC - $FFFD(2 bytes) - Power on reset handler routine 20 | // $FFFE - $FFFF(2 bytes) - IRQ/BRK handler routine 21 | 22 | #define NES_STACK_OFFSET 0x100 23 | #define NES_PRG_RAM_OFFSET 0x6000 24 | #define NES_PRG_DATA_OFFSET 0x8000 25 | #define NES_NMI_HANDLE_OFFSET 0xFFFA 26 | #define NES_RESET_HANDLE_OFFSET 0xFFFC 27 | #define NES_IRQ_BRK_HANDLE_OFFSET 0xFFFE 28 | 29 | #define NES_RAM_SIZE 0x800 30 | #define NES_HEADER_SIZE 0x10 31 | #define NES_PRG_DATA_UNIT_SIZE 0x4000 32 | #define NES_PRG_RAM_UNIT_SIZE 0x2000 33 | #define NES_PRG_SLOT_SIZE 0x2000 34 | #define NES_CHR_SLOT_SIZE 0x400 35 | 36 | #define NES_DISPLAY_WIDTH 256 37 | #define NES_DISPLAY_HEIGHT 240 38 | 39 | typedef struct { 40 | struct { 41 | u16 pc; 42 | u8 s; 43 | u8 a; 44 | u8 x; 45 | u8 y; 46 | u8 p; 47 | bool nmi; 48 | bool irq; 49 | u64 cycle; 50 | } cpu; 51 | 52 | struct { 53 | u8 ram[NES_RAM_SIZE]; 54 | } memory; 55 | 56 | struct { 57 | struct { 58 | u8 mapper; // Mapper ID 59 | u8 prg_size; // PRG size in 16kB units 60 | u8 chr_size; // CHR size in 8kB units 61 | // ppu_mirror_t mirroring; TODO // Mirroring mode if no VRAM 62 | bool has_vram; // Cart contains additional VRAM, ignore mirroring mode 63 | bool has_chr_ram; // Cart contains additional CHR RAM, set if chr_size = 0 64 | bool has_prg_ram; // Cart contains additional PRG RAM 65 | u8 prg_ram_size; // Size of PRG RAM in 8kB units if available 66 | } config; 67 | u8* rom; 68 | u8* prg; 69 | u8* prg_ram; 70 | u8* chr; 71 | u32 prg_map[4]; 72 | u32 chr_map[8]; 73 | // u8 prg_bank; 74 | // u8 chr_bank; 75 | } cartridge; 76 | } nes_t; 77 | 78 | bool nes_init(nes_t* nes, char const* file); 79 | void nes_step(nes_t* nes); 80 | -------------------------------------------------------------------------------- /src/include/ppu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | // typedef struct { 6 | 7 | // } ppu_state_t; 8 | 9 | typedef enum { 10 | WRITE, 11 | READ, 12 | } ppu_rw_t; 13 | 14 | typedef enum { 15 | HORIZONTAL, 16 | VERTICAL, 17 | } ppu_mirror_t; 18 | 19 | typedef enum { 20 | VISIBLE, 21 | POST, 22 | NMI, 23 | PRE, 24 | } ppu_scanline_t; 25 | 26 | void ppu_set_mirror(ppu_mirror_t mode); 27 | u16 ppu_nt_mirror(u16 addr); 28 | u8 ppu_rd(u16 addr); 29 | void ppu_wr(u16 addr, u8 v); 30 | u8 ppu_reg_access(u16 index, u8 v, ppu_rw_t rw); 31 | u16 ppu_get_nt_addr(); 32 | u16 ppu_get_at_addr(); 33 | u16 ppu_get_bg_addr(); 34 | void ppu_h_scroll(); 35 | void ppu_v_scroll(); 36 | void ppu_h_update(); 37 | void ppu_v_update(); 38 | void ppu_reload_shift(); 39 | void ppu_clear_oam(); 40 | void ppu_eval_sprites(); 41 | void ppu_load_sprites(); 42 | void ppu_update_pixels(); 43 | void ppu_tick_scanline(ppu_scanline_t type); 44 | void ppu_tick(); 45 | void ppu_reset(); 46 | -------------------------------------------------------------------------------- /src/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef uint64_t u64; 8 | typedef uint32_t u32; 9 | typedef uint16_t u16; 10 | typedef uint8_t u8; 11 | 12 | typedef int64_t s64; 13 | typedef int32_t s32; 14 | typedef int16_t s16; 15 | typedef int8_t s8; 16 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "cartridge.h" 2 | #include "log.h" 3 | #include "nes.h" 4 | 5 | #include 6 | 7 | int main() { 8 | nes_t nes; 9 | if (!nes_init(&nes, "roms/smb.nes")) { 10 | LOG("Failed to initialize\n"); 11 | return 1; 12 | } 13 | 14 | while (1) { 15 | nes_step(&nes); 16 | } 17 | 18 | return 0; 19 | } -------------------------------------------------------------------------------- /src/mappers/mapper0.c: -------------------------------------------------------------------------------- 1 | #include "mappers/mapper0.h" 2 | 3 | #include "nes.h" 4 | 5 | void mapper0_init(u32* prg_map, u32* chr_map, u8 prg_units) { 6 | // Perform 1:1 mapping for prg and chr 7 | for (int i = 0; i < 4; i++) { 8 | prg_map[i] = (NES_PRG_SLOT_SIZE * i) % (prg_units * NES_PRG_DATA_UNIT_SIZE); 9 | } 10 | for (int i = 0; i < 8; i++) { 11 | chr_map[i] = NES_CHR_SLOT_SIZE * i; 12 | } 13 | } -------------------------------------------------------------------------------- /src/memory.c: -------------------------------------------------------------------------------- 1 | #include "memory.h" 2 | 3 | #include "cartridge.h" 4 | 5 | void memory_init(nes_t* state) { 6 | for (size_t i = 0; i < NES_RAM_SIZE; i++) { 7 | state->memory.ram[i] = 0x00; 8 | } 9 | } 10 | 11 | u8 memory_read(nes_t* state, u16 addr) { 12 | if (addr < 0x2000) { 13 | return state->memory.ram[addr % NES_RAM_SIZE]; 14 | } else if (addr < 0x4000) { 15 | // TODO: return ppu_reg_access(addr % 0x100, 0, READ); 16 | return 0; 17 | } else if (addr <= 0x4015) { 18 | // TODO: APU, Peripherals.. 19 | return 0; 20 | } else if (addr == 0x4016) { 21 | // TODO: return controller_rd(0); 22 | return 0; 23 | } else if (addr == 0x4017) { 24 | // TODO: return controller_rd(1); 25 | return 0; 26 | } else { 27 | return cartridge_prg_rd(state, addr); 28 | } 29 | } 30 | 31 | void memory_write(nes_t* state, u16 addr, u8 data) { 32 | if (addr < 0x2000) { 33 | state->memory.ram[addr % NES_RAM_SIZE] = data; 34 | } else if (addr < 0x4000) { 35 | // TODO: ppu_reg_access(addr % 0x100, 0, WRITE); 36 | } else if (addr <= 0x4015) { 37 | // TODO: APU, Peripherals.. 38 | } else if (addr == 0x4016) { 39 | // TODO: controller_wr(data); 40 | } else if (addr == 0x4017) { 41 | // TODO 42 | } else { 43 | cartridge_prg_wr(state, addr, data); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/nes.c: -------------------------------------------------------------------------------- 1 | #include "nes.h" 2 | 3 | #include "cartridge.h" 4 | #include "cpu.h" 5 | #include "memory.h" 6 | 7 | bool nes_init(nes_t* nes, char const* file) { 8 | if (cartridge_init(nes, file) != CARTRIDGE_SUCCESS) { 9 | return false; 10 | } 11 | memory_init(nes); 12 | cpu_init(nes); 13 | 14 | return true; 15 | } 16 | 17 | void nes_step(nes_t* nes) { 18 | cpu_step(nes); 19 | } 20 | -------------------------------------------------------------------------------- /src/ppu.c: -------------------------------------------------------------------------------- 1 | #include "ppu.h" 2 | 3 | #include "bitmask.h" 4 | #include "cartridge.h" 5 | #include "cpu.h" 6 | #include "log.h" 7 | #include "nes.h" 8 | 9 | #include 10 | 11 | #define PPU_RENDERING (PPUMASK.bg || PPUMASK.spr) 12 | #define PPU_SPRITE_H (PPUCTRL.sprSz ? 16 : 8) 13 | 14 | typedef struct { 15 | u8 id; // Index in OAM 16 | u8 x; // X position 17 | u8 y; // Y position 18 | u8 tile; // Tile index 19 | u8 attr; // Attributes 20 | u8 dataL; // Tile data (low) 21 | u8 dataH; // Tile data (high) 22 | } ppu_sprite_t; 23 | 24 | /* Register File */ 25 | // PPUCTRL ($2000) register 26 | union { 27 | struct { 28 | unsigned nt : 2; // Nametable ($2000 / $2400 / $2800 / $2C00). 29 | unsigned incr : 1; // Address increment (1 / 32). 30 | unsigned sprTbl : 1; // Sprite pattern table ($0000 / $1000). 31 | unsigned bgTbl : 1; // BG pattern table ($0000 / $1000). 32 | unsigned sprSz : 1; // Sprite size (8x8 / 8x16). 33 | unsigned slave : 1; // PPU master/slave. 34 | unsigned nmi : 1; // Enable NMI. 35 | }; 36 | u8 r; 37 | } PPUCTRL; 38 | 39 | // PPUMASK ($2001) register 40 | union { 41 | struct { 42 | unsigned gray : 1; // Grayscale. 43 | unsigned bgLeft : 1; // Show background in leftmost 8 pixels. 44 | unsigned sprLeft : 1; // Show sprite in leftmost 8 pixels. 45 | unsigned bg : 1; // Show background. 46 | unsigned spr : 1; // Show sprites. 47 | unsigned red : 1; // Intensify reds. 48 | unsigned green : 1; // Intensify greens. 49 | unsigned blue : 1; // Intensify blues. 50 | }; 51 | u8 r; 52 | } PPUMASK; 53 | 54 | // PPUSTATUS ($2002) register 55 | union { 56 | struct { 57 | unsigned bus : 5; // Not significant. 58 | unsigned sprOvf : 1; // Sprite overflow. 59 | unsigned sprHit : 1; // Sprite 0 Hit. 60 | unsigned vBlank : 1; // In VBlank? 61 | }; 62 | u8 r; 63 | } PPUSTATUS; 64 | 65 | // PPUSCROLL ($2005) and PPUADDR ($2006) 66 | typedef union ADDR { 67 | struct { 68 | unsigned cX : 5; // Coarse X. 69 | unsigned cY : 5; // Coarse Y. 70 | unsigned nt : 2; // Nametable. 71 | unsigned fY : 3; // Fine Y. 72 | }; 73 | struct { 74 | unsigned l : 8; 75 | unsigned h : 7; 76 | }; 77 | unsigned addr : 14; 78 | unsigned r : 15; 79 | } PPUADDR; 80 | 81 | struct { 82 | PPUADDR vAddr; 83 | PPUADDR tAddr; 84 | unsigned fX : 3; 85 | unsigned w : 1; 86 | } PPUINTER = { .w = 0 }; 87 | 88 | /* Registers Declaration 89 | * Register files that are defined in ppu.h 90 | * - PPUCTRL $2000 91 | * - PPUMASK $2001 92 | * - PPUSATUS $2002 93 | * - PPUSCROLL $2005 94 | * - PPUADDR $2006 95 | * 96 | * Register files that are not defined explicitly (Implemented through 97 | * functions) 98 | * - OAMADDR $2003 99 | * - OAMDATA $2004 100 | * - PPUDATA $2007 101 | * - OAMDAM $4014 102 | */ 103 | 104 | /* Memory Definitions 105 | * ciRam: nametable memory space (2048 bytes) 106 | * - There are two physical nametables. 107 | * - Each nametables has Background Area (960 bytes)+ Attribute Table (64 108 | *bytes) 109 | * - Four nametable addressing spaces. 110 | * 111 | * cgRam: palettes (64 bytes) 112 | * - Used for palettes. Can be replaced by the CLUT (color look up table) 113 | * 114 | * oamMem: Sprite properties (256 bytes) 115 | * - Memory for sprite properties 116 | * - Each sprite needs 4 bytes (Y position, tile index, attributes, X 117 | *position). 118 | * - Supports 64 sprites in total. 119 | */ 120 | 121 | u8 ciRam[0x800]; 122 | u8 cgRam[0x20]; // [?] palettes may be replaced using hardware solution 123 | u8 oamMem[0x100]; 124 | ppu_sprite_t oam[8], 125 | secOam[8]; // [?] why need two? [?] Second OAM functions like a buffer 126 | 127 | u8 oamAddr; 128 | 129 | // Background latches: 130 | u8 nt, at, bgL, bgH; 131 | // Background shift registers: 132 | u8 atShiftL, atShiftH; 133 | u16 bgShiftL, bgShiftH; 134 | u8 atLatchL, atLatchH; 135 | 136 | u8 frameOdd; 137 | u16 scanline, dot; 138 | 139 | /* Graphic Memory (LCD) */ 140 | // Width: 240 Height: 320 141 | u8 GRAM[NES_DISPLAY_WIDTH * NES_DISPLAY_HEIGHT]; // [!] Can be moved to 142 | // external memory 143 | 144 | /* Configurations */ 145 | ppu_mirror_t mirroring; 146 | 147 | /* Configurations Access */ 148 | void ppu_set_mirror(ppu_mirror_t mode) { 149 | mirroring = mode; 150 | } 151 | 152 | u16 ppu_nt_mirror(u16 addr) { 153 | // [?] Should the input address be 0x0XXX instead of 0x2XXX 154 | // [!] Horizontal Implementation != LaiNES 155 | switch (mirroring) { 156 | case VERTICAL: 157 | return addr % 0x800; // Use the top two ($2000 and $2400) 158 | case HORIZONTAL: 159 | return ((addr & 0x800) + addr % 0x400); // Use the left two ($2000 and $2800) 160 | default: 161 | return addr - 0x2000; // [?] Why need this 162 | } 163 | } 164 | 165 | /* PPU Memory Access 166 | * Memory Mapping 167 | * - $0000 - $0FFF Pattern Table 0 168 | * - $1000 - $1FFF Pattern Table 1 169 | * - $2000 - $23FF Nametable 0 170 | * - $2400 - $27FF Nametable 1 171 | * - $2800 - $2BFF Nametable 2 172 | * - $2C00 - $2FFF Nametable 3 173 | * - $3000 - $3EFF Mirrors of $2000 - $2EFF 174 | * - $3F00 - $3F1F Palette RAM indexes 175 | * - $3F20 - $3FFF Mirrors of $3F00 - $3F1F 176 | * */ 177 | u8 ppu_rd(u16 addr) { 178 | switch (addr) { 179 | case 0x0000 ... 0x1FFF: 180 | return cartridge_chr_rd(addr); 181 | case 0x2000 ... 0x3EFF: 182 | return ciRam[ppu_nt_mirror(addr)]; 183 | case 0x3F00 ... 0x3FFF: 184 | // 0x3F10 0x3F14 ... 0x3F1C are the mirrors of 0x3F00 ... 0x3F0C 185 | if ((addr & 0x13) == 0x10) addr &= ~0x10; 186 | return cgRam[addr & 0x1F] & (PPUMASK.gray ? 0x30 : 0xFF); 187 | default: 188 | return 0x00; 189 | } 190 | } 191 | 192 | void ppu_wr(u16 addr, u8 v) { 193 | switch (addr) { 194 | case 0x0000 ... 0x1FFF: 195 | cartridge_chr_wr(addr, v); 196 | break; 197 | case 0x2000 ... 0x3EFF: 198 | ciRam[ppu_nt_mirror(addr)] = v; 199 | break; 200 | case 0x3F00 ... 0x3FFF: 201 | if ((addr & 0x13) == 0x10) addr &= ~0x10; 202 | cgRam[addr & 0x1F] = v; 203 | break; 204 | } 205 | } 206 | 207 | /* PPU Registers Access */ 208 | u8 ppu_reg_access(u16 index, u8 v, ppu_rw_t rw) { 209 | static u8 res = 0; // Result of the operation 210 | static u8 buffer = 0; // VRAM read buffer 211 | if (rw == WRITE) { 212 | res = v; 213 | switch (index) { 214 | case 0: // PPUCTRL ($2000) 215 | PPUCTRL.r = v; 216 | PPUINTER.tAddr.nt = PPUCTRL.nt; 217 | break; 218 | case 1: // PPUMASK ($2001) 219 | PPUMASK.r = v; 220 | break; 221 | case 3: // OAMADDR ($2003) 222 | oamAddr = v; 223 | break; 224 | case 4: // OAMDATA ($2004) 225 | oamMem[oamAddr++] = v; 226 | break; 227 | case 5: // PPUSCROLL ($2005) 228 | if (!PPUINTER.w) { 229 | PPUINTER.fX = v & 0x07; 230 | PPUINTER.tAddr.cX = v >> 3; 231 | } else { 232 | PPUINTER.tAddr.fY = v & 0x07; 233 | PPUINTER.tAddr.cY = v >> 3; 234 | } 235 | PPUINTER.w = !PPUINTER.w; 236 | break; 237 | case 6: // PPUADDR ($2006) 238 | if (!PPUINTER.w) { 239 | PPUINTER.tAddr.h = v & 0x3F; 240 | } else { 241 | PPUINTER.tAddr.l = v; 242 | PPUINTER.vAddr.r = PPUINTER.tAddr.r; 243 | } 244 | PPUINTER.w = !PPUINTER.w; 245 | break; 246 | case 7: 247 | ppu_wr(PPUINTER.vAddr.addr, v); 248 | PPUINTER.vAddr.addr += PPUCTRL.incr ? 32 : 1; 249 | } 250 | } else { 251 | switch (index) { 252 | case 2: 253 | res = (res & 0x1F) | PPUSTATUS.r; 254 | PPUSTATUS.vBlank = 0; 255 | PPUINTER.w = 0; 256 | break; 257 | case 4: 258 | res = oamMem[oamAddr]; 259 | break; 260 | case 7: 261 | if (PPUINTER.vAddr.addr <= 0x3EFF) { 262 | res = buffer; 263 | buffer = ppu_rd(PPUINTER.vAddr.addr); 264 | } else 265 | res = buffer = ppu_rd(PPUINTER.vAddr.addr); 266 | PPUINTER.vAddr.addr += PPUCTRL.incr ? 32 : 1; 267 | } 268 | } 269 | return res; 270 | } 271 | 272 | /* Calculate graphics addresses */ 273 | // Get PPU nametable address 274 | u16 ppu_get_nt_addr() { 275 | return 0x2000 | (PPUINTER.vAddr.r & 0xFFF); 276 | } 277 | // Get PPU Attribute table address 278 | u16 ppu_get_at_addr() { 279 | // [?] Why coarse X and Y are divided by 4 280 | return 0x23C0 | (PPUINTER.vAddr.nt << 10) | ((PPUINTER.vAddr.cY / 4) << 3) | 281 | (PPUINTER.vAddr.cX / 4); 282 | } 283 | // Get Background Tile Address 284 | u16 ppu_get_bg_addr() { 285 | return (PPUCTRL.bgTbl * 0x1000) + (nt * 16) + PPUINTER.vAddr.fY; 286 | } 287 | 288 | /* 289 | * ppu_h_scroll() 290 | * Horizontal Scroll 291 | * 292 | * ppu_v_scroll() 293 | * Vertical Scroll happens if rendering is enabled. It's called @ dot 256 294 | * */ 295 | void ppu_h_scroll() { 296 | if (!PPU_RENDERING) return; 297 | if (PPUINTER.vAddr.cX == 31) 298 | PPUINTER.vAddr.r ^= 0x41F; // Switch horizontal nametable and wrap coarse X. 299 | else 300 | PPUINTER.vAddr.cX++; 301 | } 302 | 303 | void ppu_v_scroll() { 304 | if (!PPU_RENDERING) return; 305 | if (PPUINTER.vAddr.fY < 7) // Check if still in the same tile after scroll 306 | PPUINTER.vAddr.fY++; 307 | else { 308 | PPUINTER.vAddr.fY = 0; // Wrap if move to the next tile. 309 | if (PPUINTER.vAddr.cY == 31) 310 | PPUINTER.vAddr.cY = 0; // Wrap the tile to the top row if needed 311 | else if (PPUINTER.vAddr.cY == 29) { 312 | PPUINTER.vAddr.cY = 0; 313 | PPUINTER.vAddr.nt ^= 0b10; 314 | } else 315 | PPUINTER.vAddr.cY++; 316 | } 317 | } 318 | 319 | /* 320 | * ppu_h_update() 321 | * Load the horizontal location from temporary address to current address. 322 | * 323 | * PPU_Vupdate() 324 | * Load the vertical location from temporary address to current address. 325 | * */ 326 | void ppu_h_update() { 327 | if (!PPU_RENDERING) return; 328 | PPUINTER.vAddr.r = (PPUINTER.vAddr.r & ~0x041F) | 329 | (PPUINTER.tAddr.r & 0x041F); // Set the NT and the coarse X from tAddr 330 | } 331 | 332 | void ppu_v_update() { 333 | if (!PPU_RENDERING) return; 334 | PPUINTER.vAddr.r = (PPUINTER.vAddr.r & ~0x7BE0) | 335 | (PPUINTER.tAddr.r & 0x7BE0); // Set the NT and fine and coarse X from tAddr 336 | } 337 | 338 | void ppu_reload_shift() { 339 | bgShiftL = (bgShiftL & 0xFF00) | bgL; 340 | bgShiftH = (bgShiftH & 0xFF00) | bgH; 341 | atLatchH = (at & 1); 342 | atLatchH = (at & 2); 343 | } 344 | 345 | /* Clear Secondary OAM */ 346 | void ppu_clear_oam() { 347 | for (int i = 0; i < 8; i++) { 348 | secOam[i].id = 64; 349 | secOam[i].y = 0xFF; 350 | secOam[i].tile = 0xFF; 351 | secOam[i].attr = 0xFF; 352 | secOam[i].x = 0xFF; 353 | secOam[i].dataL = 0; 354 | secOam[i].dataH = 0; 355 | } 356 | } 357 | 358 | /* Fill secondary OAM with the sprite info for the next scanline */ 359 | void ppu_eval_sprites() { 360 | int n = 0; 361 | for (int i = 0; i < 64; i++) { 362 | /* 363 | * - Starting from -1 because sprite cannot be drawn on the first line. 364 | * - Here is measures if the current scanline will across any sprite 365 | * */ 366 | int line = (scanline == 261 ? -1 : scanline) - 367 | oamMem[i * 4 + 0]; // Each sprite takes 4 bytes in oamMem 368 | if (line >= 0 && line < PPU_SPRITE_H) { 369 | secOam[n].id = i; 370 | secOam[n].y = oamMem[i * 4 + 0]; 371 | secOam[n].tile = oamMem[i * 4 + 1]; 372 | secOam[n].attr = oamMem[i * 4 + 2]; 373 | secOam[n].x = oamMem[i * 4 + 3]; 374 | 375 | /* Max number of sprites in a scanline is 8. 376 | * If more than 8 sprites are founded in one line, sprites overflow 377 | * interrupt is triggered. 378 | * */ 379 | if (++n > 8) { 380 | PPUSTATUS.sprOvf = 1; 381 | break; 382 | } 383 | } 384 | } 385 | } 386 | 387 | /* Load the sprite info into primary OAM and fetch their tile data */ 388 | void ppu_load_sprites() { 389 | u16 addr; 390 | for (int i = 0; i < 8; i++) { 391 | oam[i] = secOam[i]; // Load sprite data 392 | // Sprite height setting 393 | if (PPU_SPRITE_H == 16) 394 | addr = ((oam[i].tile & 1) * 0x1000) + 395 | ((oam[i].tile & ~1) * 16); // Bit 0 determined the bank index. 396 | else 397 | addr = 398 | (PPUCTRL.sprTbl * 0x1000) + (oam[i].tile * 16); // Each tile is 16B in pattern table. 399 | 400 | u8 sprY = (scanline - oam[i].y) % PPU_SPRITE_H; 401 | if (oam[i].attr & 0x80) 402 | sprY ^= PPU_SPRITE_H - 1; // [?] Why veritical flip can be achieved in this way? 403 | addr += sprY + (sprY & 8); // Check if the addr is on the second part of 404 | // the tile. Add the offset if it is 405 | oam[i].dataL = ppu_rd(addr + 0); 406 | oam[i].dataH = ppu_rd(addr + 8); 407 | } 408 | } 409 | 410 | /* Process a pixel, draw it if it's on screen */ 411 | void ppu_update_pixels() { 412 | u8 palette = 0; 413 | u8 objPalette = 0; 414 | u8 objPriority = 0; 415 | int x = dot - 2; // [?] Why need to decrement by 2? 416 | 417 | if (scanline < 240 && x >= 0 && x < 256) { 418 | // Background 419 | if (PPUMASK.bg && !(!PPUMASK.bgLeft && x < 8)) { 420 | // Background: 421 | palette = 422 | (NTH_BIT(bgShiftH, 15 - PPUINTER.fX) << 1) | NTH_BIT(bgShiftL, 15 - PPUINTER.fX); 423 | if (palette) 424 | palette |= 425 | ((NTH_BIT(atShiftH, 7 - PPUINTER.fX) << 1) | NTH_BIT(atShiftL, 7 - PPUINTER.fX)) 426 | << 2; 427 | } 428 | 429 | // Sprites 430 | if (PPUMASK.spr && !(!PPUMASK.sprLeft && x < 8)) { 431 | for (int i = 7; i >= 0; i--) // [?] Why start from i = 7 432 | { 433 | if (oam[i].id == 64) continue; 434 | u8 sprX = x - oam[i].x; 435 | if (sprX >= 8) continue; 436 | if (oam[i].attr & 0x40) sprX ^= 7; // Horizontal flip 437 | u8 sprPalette = 438 | (NTH_BIT(oam[i].dataH, 7 - sprX) << 1) | NTH_BIT(oam[i].dataL, 7 - sprX); 439 | if (sprPalette == 0) continue; 440 | if (oam[i].id == 0 && palette && x != 255) // check palette is 441 | // not transparent && 442 | // hit the first 443 | // pixel. 444 | PPUSTATUS.sprHit = 1; 445 | sprPalette |= (oam[i].attr & 3) << 2; // Add color to sprite pixel 446 | objPalette = sprPalette + 0x10; // [?] Why add 16 to sprPalette 447 | objPriority = oam[i].attr & 0x20; 448 | } 449 | } 450 | 451 | // Evaluate Priority 452 | if (objPalette && (palette == 0 || objPriority == 0)) palette = objPalette; 453 | GRAM[(x + 32) * 240 + 239 - scanline] = palette % 256; // load the CLUP index 454 | } 455 | // Perform background shifts; 456 | bgShiftL <<= 1; 457 | bgShiftH <<= 1; 458 | atShiftL = (atShiftL << 1) | (atLatchL & 0x01); 459 | atShiftH = (atShiftH << 1) | (atLatchH & 0x01); 460 | } 461 | 462 | void ppu_tick_scanline(ppu_scanline_t type) { 463 | static u16 addr; 464 | 465 | if (type == NMI && dot == 1) { 466 | PPUSTATUS.vBlank = 1; 467 | if (PPUCTRL.nmi) { 468 | cpu_set_nmi(1); 469 | } 470 | } else if (type == POST && dot == 0) { 471 | // [!] Bascially we do nothing on our platform 472 | // int updateGui = 0; 473 | } else if (type == VISIBLE || type == PRE) { 474 | // Sprites 475 | switch (dot) { 476 | case 1: 477 | ppu_clear_oam(); 478 | if (type == PRE) PPUSTATUS.sprOvf = PPUSTATUS.sprHit = 0; 479 | break; 480 | case 257: 481 | ppu_eval_sprites(); 482 | break; 483 | case 321: 484 | ppu_load_sprites(); 485 | } 486 | // Background 487 | switch (dot) { 488 | case 2 ... 255: 489 | case 322 ... 337: 490 | ppu_update_pixels(); 491 | switch (dot % 8) { 492 | // Nametable 493 | case 1: 494 | addr = ppu_get_nt_addr(); 495 | ppu_reload_shift(); 496 | break; 497 | case 2: 498 | nt = ppu_rd(addr); 499 | break; 500 | // Attribute table 501 | case 3: 502 | addr = ppu_get_at_addr(); 503 | break; 504 | case 4: 505 | at = ppu_rd(addr); 506 | if (PPUINTER.vAddr.cY & 2) at >>= 4; 507 | if (PPUINTER.vAddr.cX & 2) at >>= 2; 508 | break; 509 | case 5: 510 | addr = ppu_get_bg_addr(); 511 | break; 512 | case 6: 513 | bgL = ppu_rd(addr); 514 | break; 515 | case 7: 516 | addr += 8; 517 | case 0: 518 | bgH = ppu_rd(addr); 519 | ppu_h_scroll(); 520 | break; 521 | } 522 | break; 523 | case 256: 524 | ppu_update_pixels(); 525 | bgH = ppu_rd(addr); 526 | ppu_v_scroll(); 527 | break; 528 | case 257: 529 | ppu_update_pixels(); 530 | ppu_reload_shift(); 531 | ppu_h_update(); 532 | break; 533 | case 280 ... 304: 534 | if (type == PRE) ppu_v_update(); 535 | break; 536 | 537 | // No shift reloading 538 | case 1: 539 | addr = ppu_get_nt_addr(); 540 | if (type == PRE) PPUSTATUS.vBlank = 0; 541 | break; 542 | case 321: 543 | case 339: 544 | addr = ppu_get_nt_addr(); 545 | break; 546 | case 338: 547 | nt = ppu_rd(addr); 548 | break; 549 | case 340: 550 | nt = ppu_rd(addr); 551 | if (type == PRE && PPU_RENDERING && frameOdd) dot++; 552 | } 553 | if (dot == 260 && PPU_RENDERING) { 554 | // [!] Signal scanline to cartridge 555 | // int cartrige_int = 0; 556 | } 557 | } 558 | } 559 | 560 | /* Execute a PPU cycle */ 561 | void ppu_tick() { 562 | switch (scanline) { 563 | case 0 ... 239: 564 | ppu_tick_scanline(VISIBLE); 565 | break; 566 | case 240: 567 | ppu_tick_scanline(POST); 568 | break; 569 | case 241: 570 | ppu_tick_scanline(NMI); 571 | break; 572 | case 261: 573 | ppu_tick_scanline(PRE); 574 | break; 575 | } 576 | 577 | if (++dot > 340) { 578 | dot %= 341; 579 | if (++scanline > 261) { 580 | scanline = 0; 581 | frameOdd = (frameOdd & 0x01) ^ 1; 582 | } 583 | } 584 | } 585 | 586 | void ppu_reset() { 587 | frameOdd = 0x00; 588 | scanline = dot = 0; 589 | PPUCTRL.r = PPUMASK.r = PPUSTATUS.r = 0; 590 | memset(GRAM, 0x00, NES_DISPLAY_WIDTH * NES_DISPLAY_HEIGHT); 591 | memset(ciRam, 0xFF, sizeof(ciRam)); 592 | memset(oamMem, 0x00, sizeof(oamMem)); 593 | } 594 | -------------------------------------------------------------------------------- /src/test.c: -------------------------------------------------------------------------------- 1 | #include "cartridge.h" 2 | #include "log.h" 3 | #include "nes.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static void parse_cpu_state(nes_t* nes, char* s, int len) { 11 | snprintf( 12 | s, 13 | len, 14 | "%04X A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%lu", 15 | nes->cpu.pc, 16 | nes->cpu.a, 17 | nes->cpu.x, 18 | nes->cpu.y, 19 | nes->cpu.p, 20 | nes->cpu.s, 21 | nes->cpu.cycle); 22 | } 23 | 24 | static void parse_verification_state(char* line) { 25 | size_t n = strlen(line); 26 | assert(n > 90); 27 | // remove the instruction information 28 | memmove(line + 5, line + 48, 26); 29 | // remove the PPU counter 30 | // TODO: support this 31 | memmove(line + 31, line + 86, n - 86); 32 | // remove newline 33 | char* newline = strchr(line, '\n'); 34 | if (newline) { 35 | *newline = '\0'; 36 | } 37 | } 38 | 39 | static bool test_cpu(void) { 40 | // load verification file 41 | FILE* test = fopen("test/nestest.txt", "rb"); 42 | 43 | nes_t nes; 44 | if (!test || !nes_init(&nes, "test/nestest.nes")) { 45 | LOG("CPU TEST FAILURE\nVerification files not found.\n"); 46 | return false; 47 | } 48 | // nestest should start at 0xC000 instead of 0xC004 for emulators with no GUI 49 | nes.cpu.pc &= ~0x0F; 50 | 51 | char cpu_state[100]; 52 | char line[100]; 53 | 54 | // Run test 55 | while (fgets(line, sizeof(line), test)) { 56 | parse_cpu_state(&nes, cpu_state, sizeof(cpu_state)); 57 | parse_verification_state(line); 58 | if (strcmp(cpu_state, line)) { 59 | LOG("CPU TEST FAILURE\nExpected %s\nGot %s\n", line, cpu_state); 60 | return false; 61 | } 62 | nes_step(&nes); 63 | } 64 | 65 | LOG("CPU TEST SUCCESS\n"); 66 | return true; 67 | } 68 | 69 | int main(void) { 70 | return test_cpu() ? 0 : 1; 71 | } -------------------------------------------------------------------------------- /test/nestest.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefflongo/stm32nes/32bb84206b685eb5751a2a592cd292e569e0ba79/test/nestest.nes --------------------------------------------------------------------------------