├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── example.bin ├── images └── thumbnail.png ├── run.sh └── src ├── cpu ├── cpu.c ├── cpu.h ├── instructions.c └── instructions.h ├── main.c ├── mem ├── mem.c └── mem.h ├── peripherals ├── interface.c ├── interface.h ├── kinput.c └── kinput.h └── utils └── misc.h /.gitignore: -------------------------------------------------------------------------------- 1 | # CUSTOM 2 | dump.bin 3 | .idea 4 | 5 | # Prerequisites 6 | *.d 7 | 8 | # Object files 9 | *.o 10 | *.ko 11 | *.obj 12 | *.elf 13 | 14 | # Linker output 15 | *.ilk 16 | *.map 17 | *.exp 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Libraries 24 | *.lib 25 | *.a 26 | *.la 27 | *.lo 28 | 29 | # Shared objects (inc. Windows DLLs) 30 | *.dll 31 | *.so 32 | *.so.* 33 | *.dylib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | *.i*86 40 | *.x86_64 41 | *.hex 42 | 43 | # Debug files 44 | *.dSYM/ 45 | *.su 46 | *.idb 47 | *.pdb 48 | 49 | # Kernel Module Compile Results 50 | *.mod* 51 | *.cmd 52 | .tmp_versions/ 53 | modules.order 54 | Module.symvers 55 | Mkfile.old 56 | dkms.conf 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Leonardo Folgoni 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Wall -Wextra -pedantic -std=c99 2 | LDFLAGS = -L/usr/local/lib 3 | LDLIBS = -lm -lncurses 4 | 5 | sources = src/main.c src/mem/mem.c src/cpu/cpu.c src/cpu/instructions.c src/peripherals/interface.c src/peripherals/kinput.c 6 | headers = src/mem/mem.h src/cpu/cpu.h src/cpu/instructions.h src/peripherals/interface.h src/peripherals/kinput.h src/utils/misc.h 7 | 8 | all: bin/emulator.out 9 | 10 | bin/emulator.out: $(sources) $(headers) 11 | @mkdir -p bin 12 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(sources) $(LDLIBS) 13 | 14 | 15 | clean: 16 | rm -rf bin 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 6502 Emulator 2 | 3 | A minimal, single-stepped and beginner friendly 6502 emulator written in C using ncurses for graphics. 4 | 5 | ![thumbnail](./images/thumbnail.png) 6 | 7 | ## DISCLAIMER 8 | 9 | This main goal of this project is to understand how CPUs works by directly emulating one and to debug it by single stepping instructions. The code is meant to be readable and understandable, a lot of things could be done better, especially the graphics. 10 | 11 | The emulator only shows you what's going on under the hood of a 6502 CPU, without displaying stuff graphically (you will only see hex digits). The example program simply caluclates `10*3` and it's not optimized. 12 | 13 | ## Run 14 | 15 | You must have `ncurses` installed on your machine. This project was developed in a Linux environment. 16 | 17 | ``` 18 | make 19 | ``` 20 | 21 | ``` 22 | ./bin/emulator.out 23 | ``` 24 | 25 | Or you can run the _shortcut_ script 26 | 27 | ``` 28 | bash run.sh 29 | ``` 30 | 31 | ### Loading a custom program 32 | 33 | By default, the emulator loads `example.bin`. If you want to load a custom binary file, just provide the path as the second argument while launching the emulator: 34 | 35 | ``` 36 | ./bin/emulator.out your_binary_here 37 | ``` 38 | 39 | ## Code style 40 | 41 | The paradigm I've chosen is `modular programming`, especially because this is C. System components aren't defined in a OOP way. 42 | 43 | Everything is very verbose with a lot of comments. 44 | 45 | ## Design 46 | 47 | The project is divided in multiple components: 48 | 49 | - **cpu**: here you will find the CPU itself, including main methods to interact with the memory 50 | - **instructions handler**: here we handle OP codes 51 | - **mem**: pretty simple memory implementation, each page has a dedicated array 52 | - **peripherals** 53 | - **interface**: everyhting ncurses related 54 | - **keyboard handler**: listener for key presses 55 | 56 | ## Dump feature 57 | 58 | After quitting, the program dumps its memory to a `.bin` file. 59 | 60 | ## Example program 61 | 62 | The loaded program multiplies 10 by 3, in order to try it you must single step instructions until you see `1E` (30) in the third memory cell in the zero page. You can continue to single step it but nothing will happen. 63 | 64 | ## TODO 65 | 66 | Do you want to contribute? Here are some things that are still a WIP. 67 | 68 | - [x] check for errors on cpu_fetch() calls 69 | - due to uint16_t always being between `0x0000` and `0xFFFF` we don't have to perform extra checking while fetching memory. 70 | - [ ] add remaining comments to `instructions.c` 71 | - [ ] create a better interface 72 | - [x] add the possibility to load custom programs 73 | 74 | ## References 75 | 76 | - [obelisk.me.uk/6502](http://www.obelisk.me.uk/6502/) 77 | -------------------------------------------------------------------------------- /example.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f0lg0/6502/a6308d9950012a1ded0326e2f14b874c1d5f16b8/example.bin -------------------------------------------------------------------------------- /images/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f0lg0/6502/a6308d9950012a1ded0326e2f14b874c1d5f16b8/images/thumbnail.png -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | make 2 | ./bin/emulator.out 3 | -------------------------------------------------------------------------------- /src/cpu/cpu.c: -------------------------------------------------------------------------------- 1 | #include "cpu.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../mem/mem.h" 8 | #include "../utils/misc.h" 9 | #include "instructions.h" 10 | 11 | /** 12 | * Little-endian 8-bit microprocessor that expects addresses 13 | * to be store in memory least significant byte first 14 | * */ 15 | struct central_processing_unit cpu; 16 | 17 | // clock cycles, every fetch implies a clock cycle 18 | uint32_t cycles = 0; 19 | 20 | // reference to the memory module 21 | struct mem* mem_ptr = NULL; 22 | 23 | /** 24 | * cpu_init: Initialize CPU by linking it to the memory 25 | * @param void 26 | * @return void 27 | */ 28 | void cpu_init(void) { mem_ptr = mem_get_ptr(); } 29 | 30 | /** 31 | * cpu_extract_sr: Extract one of the 7 flags from the status reg. 32 | * @param flag The flag to be extracted 33 | * @return the bit of the wanted flag 34 | * */ 35 | uint8_t cpu_extract_sr(uint8_t flag) { return ((cpu.sr >> (flag % 8)) & 1); } 36 | 37 | /** 38 | * cpu_mod_sr: Modify the sr register (flags) 39 | * @param flag The flag to set 40 | * @param val The value 41 | * @return 0 if success, 1 if failure 42 | */ 43 | uint8_t cpu_mod_sr(uint8_t flag, uint8_t val) { 44 | if (val != 0 && val != 1) return 1; 45 | 46 | if (flag > 0 && flag < 8 && flag != 5) { 47 | if (val == 1) { 48 | SET_BIT(cpu.sr, flag); 49 | } else { 50 | CLEAR_BIT(cpu.sr, flag); 51 | } 52 | return 0; 53 | } else { 54 | return 1; 55 | } 56 | } 57 | 58 | /** 59 | * cpu_reset: Reset the CPU to its initial state. Wrapper around reset() 60 | * 61 | * @param void 62 | * @return void 63 | * */ 64 | void cpu_reset(void) { 65 | reset(); 66 | 67 | cycles = 8; 68 | } 69 | 70 | /** 71 | * get_mem: Wrapper to handle memory accessing, due to the pages being separated 72 | * @param addr The address we want to access 73 | * @return The retrieved data 74 | */ 75 | static int8_t get_mem(uint16_t addr) { 76 | // this yields "warning: comparison is always true due to limited range of 77 | // data type" if (!(addr >= 0x0000 && addr <= 0xFFFF)) return -1; 78 | debug_print("(get_mem) reading at: 0x%X\n", addr); 79 | 80 | // no need to check >= 0x0000, it's unsigned 81 | if (addr <= 0x00FF) { 82 | return mem_ptr->zero_page[addr]; 83 | } else if (addr >= 0x0100 && addr <= 0x01FF) { 84 | return mem_ptr->stack[addr - 0x0100]; 85 | } else if (addr >= 0xFFFA) { 86 | return mem_ptr->last_six[addr - 0xFDFA]; 87 | } else { 88 | debug_print("(get_mem) parsed: 0x%X\n", addr - 0x0200); 89 | return mem_ptr->data[addr - 0x0200]; 90 | } 91 | } 92 | 93 | /** 94 | * write_mem: Write bytes to a given address 95 | * @param addr The location in memory where to write to 96 | * @param data The data to be written 97 | * @return 0 if success, 1 if failure 98 | */ 99 | static uint8_t write_mem(uint16_t addr, uint8_t data) { 100 | // this yields "warning: comparison is always true due to limited range of 101 | // data type" if (!(addr >= 0x0000 && addr <= 0xFFFF)) return 1; 102 | 103 | if (addr <= 0x00FF) { 104 | mem_ptr->zero_page[addr] = data; 105 | } else if (addr >= 0x0100 && addr <= 0x01FF) { 106 | mem_ptr->stack[addr - 0x0100] = data; 107 | } else if (addr >= 0xFFFA) { 108 | mem_ptr->last_six[addr - 0xFDFA] = data; 109 | } else { 110 | mem_ptr->data[addr - 0x0200] = data; 111 | } 112 | 113 | return 0; 114 | } 115 | 116 | /** 117 | * cpu_fetch: Fetch memory from a given address 118 | * @param void 119 | * @return void 120 | */ 121 | uint8_t cpu_fetch(uint16_t addr) { 122 | debug_print("(cpu_fetch) reading at: 0x%X\n", addr); 123 | uint8_t data = get_mem(addr); 124 | debug_print("(cpu_fetch) GOT: 0x%X\n", data); 125 | if (addr == cpu.pc) cpu.pc++; 126 | 127 | return data; 128 | } 129 | 130 | /** 131 | * cpu_write: Wrapper for write_mem() 132 | * @param addr The address to be written to 133 | * @param data The data to be written 134 | * @return 0 if success, 1 if failure 135 | */ 136 | uint8_t cpu_write(uint16_t addr, uint8_t data) { 137 | return write_mem(addr, data) == 1 ? 1 : 0; 138 | } 139 | 140 | /** 141 | * cpu_exec: Execute fetched data (single stepping) 142 | * @param void 143 | * @return void 144 | */ 145 | void cpu_exec() { 146 | debug_print("(cpu_exec) cycles: %d, mem: %p\n", cycles, (void*)mem_ptr); 147 | 148 | int8_t fetched; 149 | do { 150 | debug_print("(loop) cycles: %d\n", cycles); 151 | // executing in a take 152 | if (cycles == 0) { 153 | fetched = cpu_fetch(cpu.pc); 154 | 155 | debug_print("(cpu_exec) fetched: 0x%X\n", fetched); 156 | inst_exec(fetched, &cycles); 157 | } 158 | cycles--; 159 | } while (cycles != 0); 160 | } 161 | -------------------------------------------------------------------------------- /src/cpu/cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_CPU_H 2 | #define INC_6502_CPU_H 3 | 4 | #include 5 | 6 | struct central_processing_unit { 7 | uint16_t pc; 8 | uint8_t sp; 9 | uint8_t ac; 10 | uint8_t x; 11 | uint8_t y; 12 | 13 | /* 14 | * Status Register: 15 | * 16 | * bit 0: Carry 17 | * bit 1: Zero 18 | * bit 2: Interrupt 19 | * bit 3: Decimal 20 | * bit 4: Break 21 | * bit 5: 0 22 | * bit 6: Overflow (V) 23 | * bit 7: Negative 24 | * */ 25 | uint8_t sr; 26 | }; 27 | 28 | #define C 0 29 | #define Z 1 30 | #define I 2 31 | #define D 3 32 | #define B 4 33 | #define V 6 34 | #define N 7 35 | 36 | extern struct central_processing_unit cpu; 37 | 38 | void cpu_reset(void); 39 | uint8_t cpu_extract_sr(uint8_t flag); 40 | uint8_t cpu_mod_sr(uint8_t flag, uint8_t val); 41 | uint8_t cpu_fetch(uint16_t addr); 42 | uint8_t cpu_write(uint16_t addr, uint8_t data); 43 | void cpu_exec(); 44 | void cpu_init(void); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/cpu/instructions.c: -------------------------------------------------------------------------------- 1 | /* 2 | * NOTE: this is meant to be an extension of cpu.c, in fact these two files 3 | * share the same cpu struct. 4 | * 5 | * TODO: check for errors on cpu_fetch() 6 | * TODO: add missing comments 7 | */ 8 | 9 | #include "instructions.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "../utils/misc.h" 16 | #include "cpu.h" 17 | 18 | /* 19 | * ============================================= 20 | * MODES PROTOTYPES 21 | * ============================================= 22 | */ 23 | 24 | static uint8_t IMP(void); 25 | static uint8_t IMM(void); 26 | static uint8_t ZP0(void); 27 | static uint8_t ZPX(void); 28 | static uint8_t ZPY(void); 29 | static uint8_t ABS(void); 30 | static uint8_t ABX(void); 31 | static uint8_t ABY(void); 32 | static uint8_t IND(void); 33 | static uint8_t IZX(void); 34 | static uint8_t IZY(void); 35 | static uint8_t REL(void); 36 | 37 | /* 38 | * ============================================= 39 | * OPERATIONS PROTOTYPES 40 | * ============================================= 41 | */ 42 | 43 | static uint8_t XXX(void); 44 | static uint8_t LDA(void); 45 | static uint8_t LDX(void); 46 | static uint8_t LDY(void); 47 | static uint8_t BRK(void); 48 | static uint8_t BPL(void); 49 | static uint8_t JSR(void); 50 | static uint8_t BMI(void); 51 | static uint8_t RTI(void); 52 | static uint8_t BVC(void); 53 | static uint8_t RTS(void); 54 | static uint8_t BVS(void); 55 | static uint8_t NOP(void); 56 | static uint8_t BCC(void); 57 | static uint8_t BCS(void); 58 | static uint8_t BNE(void); 59 | static uint8_t CPX(void); 60 | static uint8_t CPY(void); 61 | static uint8_t BEQ(void); 62 | static uint8_t ORA(void); 63 | static uint8_t AND(void); 64 | static uint8_t EOR(void); 65 | static uint8_t BIT(void); 66 | static uint8_t ADC(void); 67 | static uint8_t STA(void); 68 | static uint8_t STX(void); 69 | static uint8_t STY(void); 70 | static uint8_t CMP(void); 71 | static uint8_t SBC(void); 72 | static uint8_t ASL(void); 73 | static uint8_t ROL(void); 74 | static uint8_t LSR(void); 75 | static uint8_t ROR(void); 76 | static uint8_t DEC(void); 77 | static uint8_t DEX(void); 78 | static uint8_t DEY(void); 79 | static uint8_t INC(void); 80 | static uint8_t INX(void); 81 | static uint8_t INY(void); 82 | static uint8_t PHP(void); 83 | static uint8_t SEC(void); 84 | static uint8_t CLC(void); 85 | static uint8_t CLI(void); 86 | static uint8_t PLP(void); 87 | static uint8_t PLA(void); 88 | static uint8_t PHA(void); 89 | static uint8_t SEI(void); 90 | static uint8_t TYA(void); 91 | static uint8_t CLV(void); 92 | static uint8_t CLD(void); 93 | static uint8_t SED(void); 94 | static uint8_t TXA(void); 95 | static uint8_t TXS(void); 96 | static uint8_t TAX(void); 97 | static uint8_t TAY(void); 98 | static uint8_t TSX(void); 99 | static uint8_t JMP(void); 100 | 101 | // the populated matrix of opcodes, not a clean solution but it's easily 102 | // understandable 103 | struct instruction lookup[256] = { 104 | {"BRK", &BRK, &IMM, 7}, {"ORA", &ORA, &IZX, 6}, {"???", &XXX, &IMP, 2}, 105 | {"???", &XXX, &IMP, 8}, {"???", &NOP, &IMP, 3}, {"ORA", &ORA, &ZP0, 3}, 106 | {"ASL", &ASL, &ZP0, 5}, {"???", &XXX, &IMP, 5}, {"PHP", &PHP, &IMP, 3}, 107 | {"ORA", &ORA, &IMM, 2}, {"ASL", &ASL, &IMP, 2}, {"???", &XXX, &IMP, 2}, 108 | {"???", &NOP, &IMP, 4}, {"ORA", &ORA, &ABS, 4}, {"ASL", &ASL, &ABS, 6}, 109 | {"???", &XXX, &IMP, 6}, {"BPL", &BPL, &REL, 2}, {"ORA", &ORA, &IZY, 5}, 110 | {"???", &XXX, &IMP, 2}, {"???", &XXX, &IMP, 8}, {"???", &NOP, &IMP, 4}, 111 | {"ORA", &ORA, &ZPX, 4}, {"ASL", &ASL, &ZPX, 6}, {"???", &XXX, &IMP, 6}, 112 | {"CLC", &CLC, &IMP, 2}, {"ORA", &ORA, &ABY, 4}, {"???", &NOP, &IMP, 2}, 113 | {"???", &XXX, &IMP, 7}, {"???", &NOP, &IMP, 4}, {"ORA", &ORA, &ABX, 4}, 114 | {"ASL", &ASL, &ABX, 7}, {"???", &XXX, &IMP, 7}, {"JSR", &JSR, &ABS, 6}, 115 | {"AND", &AND, &IZX, 6}, {"???", &XXX, &IMP, 2}, {"???", &XXX, &IMP, 8}, 116 | {"BIT", &BIT, &ZP0, 3}, {"AND", &AND, &ZP0, 3}, {"ROL", &ROL, &ZP0, 5}, 117 | {"???", &XXX, &IMP, 5}, {"PLP", &PLP, &IMP, 4}, {"AND", &AND, &IMM, 2}, 118 | {"ROL", &ROL, &IMP, 2}, {"???", &XXX, &IMP, 2}, {"BIT", &BIT, &ABS, 4}, 119 | {"AND", &AND, &ABS, 4}, {"ROL", &ROL, &ABS, 6}, {"???", &XXX, &IMP, 6}, 120 | {"BMI", &BMI, &REL, 2}, {"AND", &AND, &IZY, 5}, {"???", &XXX, &IMP, 2}, 121 | {"???", &XXX, &IMP, 8}, {"???", &NOP, &IMP, 4}, {"AND", &AND, &ZPX, 4}, 122 | {"ROL", &ROL, &ZPX, 6}, {"???", &XXX, &IMP, 6}, {"SEC", &SEC, &IMP, 2}, 123 | {"AND", &AND, &ABY, 4}, {"???", &NOP, &IMP, 2}, {"???", &XXX, &IMP, 7}, 124 | {"???", &NOP, &IMP, 4}, {"AND", &AND, &ABX, 4}, {"ROL", &ROL, &ABX, 7}, 125 | {"???", &XXX, &IMP, 7}, {"RTI", &RTI, &IMP, 6}, {"EOR", &EOR, &IZX, 6}, 126 | {"???", &XXX, &IMP, 2}, {"???", &XXX, &IMP, 8}, {"???", &NOP, &IMP, 3}, 127 | {"EOR", &EOR, &ZP0, 3}, {"LSR", &LSR, &ZP0, 5}, {"???", &XXX, &IMP, 5}, 128 | {"PHA", &PHA, &IMP, 3}, {"EOR", &EOR, &IMM, 2}, {"LSR", &LSR, &IMP, 2}, 129 | {"???", &XXX, &IMP, 2}, {"JMP", &JMP, &ABS, 3}, {"EOR", &EOR, &ABS, 4}, 130 | {"LSR", &LSR, &ABS, 6}, {"???", &XXX, &IMP, 6}, {"BVC", &BVC, &REL, 2}, 131 | {"EOR", &EOR, &IZY, 5}, {"???", &XXX, &IMP, 2}, {"???", &XXX, &IMP, 8}, 132 | {"???", &NOP, &IMP, 4}, {"EOR", &EOR, &ZPX, 4}, {"LSR", &LSR, &ZPX, 6}, 133 | {"???", &XXX, &IMP, 6}, {"CLI", &CLI, &IMP, 2}, {"EOR", &EOR, &ABY, 4}, 134 | {"???", &NOP, &IMP, 2}, {"???", &XXX, &IMP, 7}, {"???", &NOP, &IMP, 4}, 135 | {"EOR", &EOR, &ABX, 4}, {"LSR", &LSR, &ABX, 7}, {"???", &XXX, &IMP, 7}, 136 | {"RTS", &RTS, &IMP, 6}, {"ADC", &ADC, &IZX, 6}, {"???", &XXX, &IMP, 2}, 137 | {"???", &XXX, &IMP, 8}, {"???", &NOP, &IMP, 3}, {"ADC", &ADC, &ZP0, 3}, 138 | {"ROR", &ROR, &ZP0, 5}, {"???", &XXX, &IMP, 5}, {"PLA", &PLA, &IMP, 4}, 139 | {"ADC", &ADC, &IMM, 2}, {"ROR", &ROR, &IMP, 2}, {"???", &XXX, &IMP, 2}, 140 | {"JMP", &JMP, &IND, 5}, {"ADC", &ADC, &ABS, 4}, {"ROR", &ROR, &ABS, 6}, 141 | {"???", &XXX, &IMP, 6}, {"BVS", &BVS, &REL, 2}, {"ADC", &ADC, &IZY, 5}, 142 | {"???", &XXX, &IMP, 2}, {"???", &XXX, &IMP, 8}, {"???", &NOP, &IMP, 4}, 143 | {"ADC", &ADC, &ZPX, 4}, {"ROR", &ROR, &ZPX, 6}, {"???", &XXX, &IMP, 6}, 144 | {"SEI", &SEI, &IMP, 2}, {"ADC", &ADC, &ABY, 4}, {"???", &NOP, &IMP, 2}, 145 | {"???", &XXX, &IMP, 7}, {"???", &NOP, &IMP, 4}, {"ADC", &ADC, &ABX, 4}, 146 | {"ROR", &ROR, &ABX, 7}, {"???", &XXX, &IMP, 7}, {"???", &NOP, &IMP, 2}, 147 | {"STA", &STA, &IZX, 6}, {"???", &NOP, &IMP, 2}, {"???", &XXX, &IMP, 6}, 148 | {"STY", &STY, &ZP0, 3}, {"STA", &STA, &ZP0, 3}, {"STX", &STX, &ZP0, 3}, 149 | {"???", &XXX, &IMP, 3}, {"DEY", &DEY, &IMP, 2}, {"???", &NOP, &IMP, 2}, 150 | {"TXA", &TXA, &IMP, 2}, {"???", &XXX, &IMP, 2}, {"STY", &STY, &ABS, 4}, 151 | {"STA", &STA, &ABS, 4}, {"STX", &STX, &ABS, 4}, {"???", &XXX, &IMP, 4}, 152 | {"BCC", &BCC, &REL, 2}, {"STA", &STA, &IZY, 6}, {"???", &XXX, &IMP, 2}, 153 | {"???", &XXX, &IMP, 6}, {"STY", &STY, &ZPX, 4}, {"STA", &STA, &ZPX, 4}, 154 | {"STX", &STX, &ZPY, 4}, {"???", &XXX, &IMP, 4}, {"TYA", &TYA, &IMP, 2}, 155 | {"STA", &STA, &ABY, 5}, {"TXS", &TXS, &IMP, 2}, {"???", &XXX, &IMP, 5}, 156 | {"???", &NOP, &IMP, 5}, {"STA", &STA, &ABX, 5}, {"???", &XXX, &IMP, 5}, 157 | {"???", &XXX, &IMP, 5}, {"LDY", &LDY, &IMM, 2}, {"LDA", &LDA, &IZX, 6}, 158 | {"LDX", &LDX, &IMM, 2}, {"???", &XXX, &IMP, 6}, {"LDY", &LDY, &ZP0, 3}, 159 | {"LDA", &LDA, &ZP0, 3}, {"LDX", &LDX, &ZP0, 3}, {"???", &XXX, &IMP, 3}, 160 | {"TAY", &TAY, &IMP, 2}, {"LDA", &LDA, &IMM, 2}, {"TAX", &TAX, &IMP, 2}, 161 | {"???", &XXX, &IMP, 2}, {"LDY", &LDY, &ABS, 4}, {"LDA", &LDA, &ABS, 4}, 162 | {"LDX", &LDX, &ABS, 4}, {"???", &XXX, &IMP, 4}, {"BCS", &BCS, &REL, 2}, 163 | {"LDA", &LDA, &IZY, 5}, {"???", &XXX, &IMP, 2}, {"???", &XXX, &IMP, 5}, 164 | {"LDY", &LDY, &ZPX, 4}, {"LDA", &LDA, &ZPX, 4}, {"LDX", &LDX, &ZPY, 4}, 165 | {"???", &XXX, &IMP, 4}, {"CLV", &CLV, &IMP, 2}, {"LDA", &LDA, &ABY, 4}, 166 | {"TSX", &TSX, &IMP, 2}, {"???", &XXX, &IMP, 4}, {"LDY", &LDY, &ABX, 4}, 167 | {"LDA", &LDA, &ABX, 4}, {"LDX", &LDX, &ABY, 4}, {"???", &XXX, &IMP, 4}, 168 | {"CPY", &CPY, &IMM, 2}, {"CMP", &CMP, &IZX, 6}, {"???", &NOP, &IMP, 2}, 169 | {"???", &XXX, &IMP, 8}, {"CPY", &CPY, &ZP0, 3}, {"CMP", &CMP, &ZP0, 3}, 170 | {"DEC", &DEC, &ZP0, 5}, {"???", &XXX, &IMP, 5}, {"INY", &INY, &IMP, 2}, 171 | {"CMP", &CMP, &IMM, 2}, {"DEX", &DEX, &IMP, 2}, {"???", &XXX, &IMP, 2}, 172 | {"CPY", &CPY, &ABS, 4}, {"CMP", &CMP, &ABS, 4}, {"DEC", &DEC, &ABS, 6}, 173 | {"???", &XXX, &IMP, 6}, {"BNE", &BNE, &REL, 2}, {"CMP", &CMP, &IZY, 5}, 174 | {"???", &XXX, &IMP, 2}, {"???", &XXX, &IMP, 8}, {"???", &NOP, &IMP, 4}, 175 | {"CMP", &CMP, &ZPX, 4}, {"DEC", &DEC, &ZPX, 6}, {"???", &XXX, &IMP, 6}, 176 | {"CLD", &CLD, &IMP, 2}, {"CMP", &CMP, &ABY, 4}, {"NOP", &NOP, &IMP, 2}, 177 | {"???", &XXX, &IMP, 7}, {"???", &NOP, &IMP, 4}, {"CMP", &CMP, &ABX, 4}, 178 | {"DEC", &DEC, &ABX, 7}, {"???", &XXX, &IMP, 7}, {"CPX", &CPX, &IMM, 2}, 179 | {"SBC", &SBC, &IZX, 6}, {"???", &NOP, &IMP, 2}, {"???", &XXX, &IMP, 8}, 180 | {"CPX", &CPX, &ZP0, 3}, {"SBC", &SBC, &ZP0, 3}, {"INC", &INC, &ZP0, 5}, 181 | {"???", &XXX, &IMP, 5}, {"INX", &INX, &IMP, 2}, {"SBC", &SBC, &IMM, 2}, 182 | {"NOP", &NOP, &IMP, 2}, {"???", &SBC, &IMP, 2}, {"CPX", &CPX, &ABS, 4}, 183 | {"SBC", &SBC, &ABS, 4}, {"INC", &INC, &ABS, 6}, {"???", &XXX, &IMP, 6}, 184 | {"BEQ", &BEQ, &REL, 2}, {"SBC", &SBC, &IZY, 5}, {"???", &XXX, &IMP, 2}, 185 | {"???", &XXX, &IMP, 8}, {"???", &NOP, &IMP, 4}, {"SBC", &SBC, &ZPX, 4}, 186 | {"INC", &INC, &ZPX, 6}, {"???", &XXX, &IMP, 6}, {"SED", &SED, &IMP, 2}, 187 | {"SBC", &SBC, &ABY, 4}, {"NOP", &NOP, &IMP, 2}, {"???", &XXX, &IMP, 7}, 188 | {"???", &NOP, &IMP, 4}, {"SBC", &SBC, &ABX, 4}, {"INC", &INC, &ABX, 7}, 189 | {"???", &XXX, &IMP, 7}, 190 | }; 191 | 192 | // absolute address in memory 193 | uint16_t addr_abs = 0x0000; 194 | 195 | // relative address in memory 196 | uint16_t addr_rel = 0x0000; 197 | 198 | uint8_t op = 0x00; 199 | uint32_t* cys = 0x000000; 200 | 201 | // a pointer to the fetched opcode in the cpu module 202 | uint8_t fetched = 0x00; 203 | 204 | /* 205 | * ============================================= 206 | * HELPERS 207 | * ============================================= 208 | */ 209 | 210 | /** 211 | * fetch: wrapper around cpu_fetch 212 | * @param void 213 | * @return void 214 | * */ 215 | static void fetch(void) { 216 | if (lookup[op].mode != &IMP) fetched = cpu_fetch(addr_abs); 217 | } 218 | 219 | /** 220 | * branch: executes a branch to defined, see: 221 | * https://en.wikipedia.org/wiki/Branch_(computer_science) 222 | * 223 | * @param void 224 | * @return void 225 | * */ 226 | static void branch(void) { 227 | (*cys)++; 228 | addr_abs = cpu.pc + addr_rel; 229 | 230 | if ((addr_abs & 0xFF00) != (cpu.pc & 0xFF00)) { 231 | (*cys)++; 232 | } 233 | 234 | cpu.pc = addr_abs; 235 | debug_print("(branch) now we are at 0x%X\n", cpu.pc); 236 | } 237 | 238 | /** 239 | * set_flag: sets or unsets corresponding bit in SR depending on the passed 240 | * expression 241 | * @param flag the bit you want to set in the SR 242 | * @param exp boolean that determines the bit status 243 | * @return void 244 | * */ 245 | static void set_flag(uint8_t flag, bool exp) { 246 | if (exp) { 247 | cpu_mod_sr(flag, 1); 248 | } else { 249 | cpu_mod_sr(flag, 0); 250 | } 251 | } 252 | 253 | /** 254 | * reset: actual reset process, must use the cpu_reset wrapper 255 | * @param void 256 | * @return void 257 | * */ 258 | void reset(void) { 259 | addr_abs = 0x8000; 260 | 261 | cpu.pc = addr_abs; 262 | debug_print("(reset) PC: 0x%X\n", cpu.pc); 263 | 264 | cpu.ac = 0; 265 | cpu.x = 0; 266 | cpu.y = 0; 267 | cpu.sp = 0xFD; 268 | cpu.sr = 0x00; 269 | 270 | addr_rel = 0x0000; 271 | addr_abs = 0x0000; 272 | fetched = 0x00; 273 | } 274 | 275 | /* 276 | * ============================================= 277 | * MODES 278 | * ============================================= 279 | * 280 | * [!] Return 1 if the operation needs an extra clock cycle 281 | */ 282 | 283 | /** 284 | * IMP: Implicit mode. This is used in instructions such as CLC. 285 | * we target the accumulator for instructions like PHA 286 | * @param void 287 | * @return 0 288 | */ 289 | static uint8_t IMP(void) { 290 | fetched = cpu.ac; 291 | return 0; 292 | } 293 | 294 | /** 295 | * IMM: Immediate Mode. Allow the programmer to directly specify an 8-bit 296 | * constant within the instruction. LDA #10 --> load 10 into the accumulator 297 | * @param void 298 | * @return 0 299 | */ 300 | static uint8_t IMM(void) { 301 | addr_abs = cpu.pc++; 302 | return 0; 303 | } 304 | 305 | /** 306 | * ZP0: Zero Page Mode. An instruction using zero page addressing mode has only 307 | * an 8 bit address operand. This limits it to addressing only the first 256 308 | * bytes of memory (e.g. $0000 to $00FF) where the most significant byte of the 309 | * address is always zero 310 | * --> 0xFF55 can be seen as: FF = Page, 55 = Offset in that page 311 | * @param void 312 | * @return 0 313 | */ 314 | static uint8_t ZP0(void) { 315 | addr_abs = (cpu_fetch(cpu.pc) & 0x00FF); 316 | return 0; 317 | } 318 | 319 | /** 320 | * ZPX: Same mode as ZP0 but this time we add cpu.x to the final address 321 | * @param void 322 | * @return 0 323 | */ 324 | static uint8_t ZPX(void) { 325 | addr_abs = ((cpu_fetch(cpu.pc) + cpu.x) & 0x00FF); 326 | return 0; 327 | } 328 | 329 | /** 330 | * ZPY: Same mode as ZPX but with the cpu.y register instead of x. 331 | * @param void 332 | * @return 0 333 | */ 334 | static uint8_t ZPY(void) { 335 | addr_abs = ((cpu_fetch(cpu.pc) + cpu.y) & 0x00FF); 336 | return 0; 337 | } 338 | 339 | /** 340 | * ABS: Absolute mode. Instructions using this mode contain a full 16 bit 341 | * address to identify the target location 342 | * @param void 343 | * @return 344 | */ 345 | static uint8_t ABS(void) { 346 | uint16_t low = cpu_fetch(cpu.pc); 347 | uint16_t high = cpu_fetch(cpu.pc); 348 | 349 | // combine them to form a 16 bit address word 350 | addr_abs = (high << 8) | low; 351 | return 0; 352 | } 353 | 354 | /** 355 | * ABX: Same mode as ABS but this time we add cpu.x to the final address. 356 | * @param void 357 | * @return 1 if an extra cycles is requires due to page change, 0 if not 358 | */ 359 | static uint8_t ABX(void) { 360 | uint16_t low = cpu_fetch(cpu.pc); 361 | uint16_t high = cpu_fetch(cpu.pc); 362 | 363 | // combine them to form a 16 bit address word and add the offset 364 | addr_abs = (high << 8) | low; 365 | addr_abs += cpu.x; 366 | 367 | // if the high bytes are different, we have changed page (due to overflow 368 | // from low to high) 369 | return ((addr_abs & 0xFF00) != (high << 8)) ? 1 : 0; 370 | } 371 | 372 | /** 373 | * ABY: Same mode as ABX but involving the cpu.y register instead of x 374 | * @param void 375 | * @return void 376 | */ 377 | static uint8_t ABY(void) { 378 | uint16_t low = cpu_fetch(cpu.pc); 379 | uint16_t high = cpu_fetch(cpu.pc); 380 | 381 | // combine them to form a 16 bit address word and add the offset 382 | addr_abs = (high << 8) | low; 383 | addr_abs += cpu.y; 384 | 385 | // if the high bytes are different, we have changed page (due to overflow 386 | // from low to high) 387 | return ((addr_abs & 0xFF00) != (high << 8)) ? 1 : 0; 388 | } 389 | 390 | /** 391 | * IND: Indirect mode. 6502 way of implementing pointers. 392 | * The only instruction that uses this mode is JMP 393 | * @param void 394 | * @return void 395 | */ 396 | static uint8_t IND(void) { 397 | uint16_t low = cpu_fetch(cpu.pc); 398 | uint16_t high = cpu_fetch(cpu.pc); 399 | 400 | uint16_t ptr = (high << 8) | low; 401 | 402 | /* 403 | * If the low byte of the supplied address is 0xFF, 404 | * then to read the high byte of the actual address 405 | * we need to cross a page boundary. This doesnt actually work on the chip 406 | * as designed, instead it wraps back around in the same page, yielding an 407 | * invalid actual address 408 | * 409 | * see: https://www.nesdev.com/6502bugs.txt 410 | * */ 411 | if (low == 0x00FF) { 412 | // simulate actual hardware bug! 413 | addr_abs = (cpu_fetch(ptr & 0xFF00) << 8) | cpu_fetch(ptr + 0); 414 | 415 | } else { 416 | addr_abs = (cpu_fetch(ptr + 1) << 8) | cpu_fetch(ptr + 0); 417 | } 418 | 419 | return 0; 420 | } 421 | 422 | /** 423 | * IZX: Indirect addressing of the zero page with X offset 424 | * The supplied 8-bit address is offset by X Register to index 425 | * a location in page 0x00. The actual 16-bit address is read 426 | * from this location. 427 | * @param void 428 | * @return void 429 | */ 430 | static uint8_t IZX(void) { 431 | // reading an address in the zero page 432 | uint16_t addr_0p = cpu_fetch(cpu.pc); 433 | 434 | uint16_t low = cpu_fetch((uint16_t)(addr_0p + (uint16_t)cpu.x) & 0x00FF); 435 | uint16_t high = 436 | cpu_fetch((uint16_t)(addr_0p + (uint16_t)cpu.x + 1) & 0x00FF); 437 | 438 | addr_abs = (high << 8) | low; 439 | 440 | return 0; 441 | } 442 | 443 | /** 444 | * IZY: Indirect addressing of the zero page with Y offset. 445 | * Note that this behaves in a different way from the X variation! 446 | * @param void 447 | * @return void 448 | */ 449 | static uint8_t IZY(void) { 450 | uint16_t addr_0p = cpu_fetch(cpu.pc); 451 | 452 | uint16_t low = cpu_fetch(addr_0p & 0x00FF); 453 | uint16_t high = cpu_fetch((addr_0p + 1) & 0x00FF); 454 | 455 | addr_abs = (high << 8) | low; 456 | addr_abs += cpu.y; 457 | 458 | return ((addr_abs & 0xFF00) != (high << 8)) ? 1 : 0; 459 | } 460 | 461 | /** 462 | * REL: Relative addressing mode is used by branch instructions which contain a 463 | * signed 8 bit relative offset (-128 to +127) which is added to cpu.pc if the 464 | * condition is true. 465 | * @param void 466 | * @return void 467 | */ 468 | static uint8_t REL(void) { 469 | addr_rel = cpu_fetch(cpu.pc); 470 | 471 | // reading a single byte to see if it's signed 472 | if (addr_rel & 0x80) { 473 | addr_rel |= 0xFF00; 474 | } 475 | 476 | return 0; 477 | } 478 | 479 | /* 480 | * ============================================= 481 | * OPERATIONS 482 | * ============================================= 483 | */ 484 | 485 | /** 486 | * XXX: Used to handle unknown opcodes 487 | * @param void 488 | * @return 0 489 | */ 490 | static uint8_t XXX(void) { return 0; } 491 | 492 | /** 493 | * LDA: Load Accumulator 494 | * @param void 495 | * @return 1 496 | */ 497 | static uint8_t LDA(void) { 498 | fetch(); 499 | cpu.ac = fetched; 500 | 501 | set_flag(Z, cpu.ac == 0); 502 | set_flag(N, cpu.ac & (1 << 7)); 503 | 504 | return 1; 505 | } 506 | 507 | /** 508 | * LDX: Load X register 509 | * @param void 510 | * @return 1 511 | */ 512 | static uint8_t LDX(void) { 513 | fetch(); 514 | cpu.x = fetched; 515 | 516 | set_flag(Z, cpu.x == 0); 517 | set_flag(N, cpu.x & (1 << 7)); 518 | 519 | return 1; 520 | } 521 | 522 | /** 523 | * LDY: Load Y register 524 | * @param void 525 | * @return 1 526 | */ 527 | static uint8_t LDY(void) { 528 | fetch(); 529 | cpu.y = fetched; 530 | 531 | set_flag(Z, cpu.y == 0); 532 | set_flag(N, cpu.y & (1 << 7)); 533 | 534 | return 1; 535 | } 536 | 537 | static uint8_t BRK(void) { 538 | cpu.pc++; 539 | set_flag(I, true); 540 | 541 | cpu_write(0x0100 + cpu.sp, (cpu.pc >> 8) & 0x00FF); 542 | cpu.sp--; 543 | cpu_write(0x0100 + cpu.sp, cpu.pc & 0x00FF); 544 | cpu.sp--; 545 | 546 | set_flag(B, true); 547 | cpu_write(0x0100 + cpu.sp, cpu.sr); 548 | cpu.sp--; 549 | set_flag(B, false); 550 | 551 | cpu.pc = (uint16_t)cpu_fetch(0xFFFE) | ((uint16_t)cpu_fetch(0xFFFF) << 8); 552 | return 0; 553 | } 554 | 555 | static uint8_t JSR(void) { 556 | cpu.pc--; 557 | 558 | cpu_write(0x0100 + cpu.sp, (cpu.pc >> 8) & 0x00FF); 559 | cpu.sp--; 560 | cpu_write(0x0100 + cpu.sp, cpu.pc & 0x00FF); 561 | cpu.sp--; 562 | 563 | cpu.pc = addr_abs; 564 | 565 | return 0; 566 | } 567 | 568 | static uint8_t RTI(void) { 569 | cpu.sp++; 570 | 571 | cpu.sr = cpu_fetch(0x0100 + cpu.sp); 572 | cpu.sr &= ~B; 573 | 574 | cpu.sp++; 575 | cpu.pc = (uint16_t)cpu_fetch(0x0100 + cpu.sp); 576 | cpu.sp++; 577 | cpu.pc |= (uint16_t)cpu_fetch(0x0100 + cpu.sp) << 8; 578 | 579 | return 0; 580 | } 581 | 582 | static uint8_t RTS(void) { 583 | cpu.sp++; 584 | cpu.pc = (uint16_t)cpu_fetch(0x0100 + cpu.sp); 585 | cpu.sp++; 586 | cpu.pc |= (uint16_t)cpu_fetch(0x0100 + cpu.sp) << 8; 587 | cpu.pc++; 588 | 589 | return 0; 590 | } 591 | 592 | static uint8_t NOP(void) { 593 | cpu.pc++; 594 | return 0; 595 | } 596 | 597 | static uint8_t BCC(void) { 598 | if (cpu_extract_sr(C) == 0) { 599 | branch(); 600 | } 601 | return 0; 602 | } 603 | 604 | static uint8_t BCS(void) { 605 | if (cpu_extract_sr(C) == 1) { 606 | branch(); 607 | } 608 | return 0; 609 | } 610 | 611 | static uint8_t BEQ(void) { 612 | if (cpu_extract_sr(Z) == 1) { 613 | branch(); 614 | } 615 | return 0; 616 | } 617 | 618 | static uint8_t BMI(void) { 619 | if (cpu_extract_sr(N) == 1) { 620 | branch(); 621 | } 622 | return 0; 623 | } 624 | 625 | static uint8_t BNE(void) { 626 | if (cpu_extract_sr(Z) == 0) { 627 | branch(); 628 | } 629 | return 0; 630 | } 631 | 632 | static uint8_t BPL(void) { 633 | if (cpu_extract_sr(N) == 0) { 634 | branch(); 635 | } 636 | return 0; 637 | } 638 | 639 | static uint8_t BVC(void) { 640 | if (cpu_extract_sr(V) == 0) { 641 | branch(); 642 | } 643 | return 0; 644 | } 645 | 646 | static uint8_t BVS(void) { 647 | if (cpu_extract_sr(V) == 1) { 648 | branch(); 649 | } 650 | return 0; 651 | } 652 | 653 | /** 654 | * CPX: Compare a value in mem to the X register 655 | * @param void 656 | * @return 0 657 | */ 658 | static uint8_t CPX(void) { 659 | fetch(); 660 | 661 | // comparing (I think this is just beautiful) 662 | uint16_t tmp = (uint16_t)cpu.x - (uint16_t)fetched; 663 | 664 | set_flag(C, cpu.x >= fetched); 665 | set_flag(Z, (tmp & 0x00FF) == 0x0000); 666 | set_flag(N, tmp & (1 << 7)); 667 | 668 | return 0; 669 | } 670 | 671 | /** 672 | * CPY: Compare a value in mem to the Y register 673 | * @param void 674 | * @return 0 675 | */ 676 | static uint8_t CPY(void) { 677 | fetch(); 678 | 679 | uint16_t tmp = (uint16_t)cpu.y - (uint16_t)fetched; 680 | 681 | set_flag(C, cpu.y >= fetched); 682 | set_flag(Z, (tmp & 0x00FF) == 0x0000); 683 | set_flag(N, tmp & (1 << 7)); 684 | 685 | return 0; 686 | } 687 | 688 | /** 689 | * ORA: OR bitwise op on the AC register with a fetched mem value 690 | * @param void 691 | * @return 1 692 | */ 693 | static uint8_t ORA(void) { 694 | fetch(); 695 | cpu.ac = cpu.ac | fetched; 696 | 697 | set_flag(C, cpu.ac == 0); 698 | set_flag(N, cpu.ac & (1 << 7)); 699 | 700 | return 1; 701 | } 702 | 703 | /** 704 | * AND: AND bitwise op on the AC register with a fetched mem value 705 | * @param void 706 | * @return 1 707 | */ 708 | static uint8_t AND(void) { 709 | fetch(); 710 | cpu.ac = cpu.ac & fetched; 711 | 712 | set_flag(C, cpu.ac == 0); 713 | set_flag(N, cpu.ac & (1 << 7)); 714 | 715 | return 1; 716 | } 717 | 718 | /** 719 | * EOR: XOR bitwise op on the AC register with a fetched mem value 720 | * @param void 721 | * @return 1 722 | */ 723 | static uint8_t EOR(void) { 724 | fetch(); 725 | cpu.ac = cpu.ac ^ fetched; 726 | 727 | set_flag(C, cpu.ac == 0); 728 | set_flag(N, cpu.ac & (1 << 7)); 729 | 730 | return 1; 731 | } 732 | 733 | static uint8_t BIT(void) { 734 | fetch(); 735 | uint16_t tmp = cpu.ac & fetched; 736 | 737 | set_flag(Z, (tmp & 0x00F) == 0x00); 738 | set_flag(N, (fetched & (1 << 7))); 739 | set_flag(V, (fetched & (1 << 6))); 740 | 741 | return 0; 742 | } 743 | 744 | static uint8_t ADC(void) { 745 | fetch(); 746 | 747 | uint16_t tmp = 748 | (uint16_t)cpu.ac + (uint16_t)fetched + (uint16_t)cpu_extract_sr(C); 749 | 750 | set_flag(C, tmp > 255); 751 | set_flag(Z, (tmp & 0x00FF) == 0); 752 | set_flag(V, ((~((uint16_t)cpu.ac ^ (uint16_t)fetched) & 753 | ((uint16_t)cpu.ac ^ (uint16_t)tmp)) & 754 | 0x0080)); 755 | 756 | set_flag(N, tmp & 0x0080); 757 | 758 | cpu.ac = tmp & 0x00FF; 759 | return 1; 760 | } 761 | 762 | static uint8_t STA(void) { 763 | cpu_write(addr_abs, cpu.ac); 764 | return 0; 765 | } 766 | 767 | static uint8_t STX(void) { 768 | cpu_write(addr_abs, cpu.x); 769 | return 0; 770 | } 771 | 772 | static uint8_t STY(void) { 773 | cpu_write(addr_abs, cpu.y); 774 | return 0; 775 | } 776 | 777 | static uint8_t CMP(void) { 778 | fetch(); 779 | 780 | // comparing (I think this is just beautiful) 781 | uint16_t tmp = (uint16_t)cpu.ac - (uint16_t)fetched; 782 | 783 | set_flag(C, cpu.ac >= fetched); 784 | set_flag(Z, (tmp & 0x00FF) == 0x0000); 785 | set_flag(N, tmp & (1 << 7)); 786 | 787 | return 1; 788 | } 789 | 790 | static uint8_t SBC(void) { 791 | fetch(); 792 | 793 | // inverting the bottom 8 bits 794 | uint16_t val = ((uint16_t)fetched) ^ 0x00FF; 795 | 796 | uint16_t tmp = (uint16_t)cpu.ac + val + (uint16_t)cpu_extract_sr(C); 797 | 798 | set_flag(C, tmp & 0xFF00); 799 | set_flag(Z, (tmp & 0x00FF) == 0); 800 | set_flag(V, ((tmp ^ (uint16_t)cpu.ac) & (tmp ^ val) & 0x0080)); 801 | set_flag(N, tmp & 0x0080); 802 | 803 | cpu.ac = tmp & 0x00FF; 804 | return 1; 805 | } 806 | 807 | static uint8_t ASL(void) { 808 | fetch(); 809 | uint16_t tmp = (uint16_t)fetched << 1; 810 | 811 | set_flag(C, (tmp & 0xFF00) > 0); 812 | set_flag(Z, (tmp & 0x00FF) == 0x00); 813 | set_flag(N, tmp & (1 << 7)); 814 | 815 | if (lookup[op].mode == &IMP) { 816 | cpu.ac = tmp & 0x00FF; 817 | } else { 818 | cpu_write(addr_abs, tmp & 0x00FF); 819 | } 820 | 821 | return 0; 822 | } 823 | 824 | static uint8_t ROL(void) { 825 | fetch(); 826 | uint16_t tmp = (uint16_t)(fetched << 1) | cpu_extract_sr(C); 827 | 828 | set_flag(C, tmp & 0xFF00); 829 | set_flag(Z, (tmp & 0x00FF) == 0x00); 830 | set_flag(N, tmp & (1 << 7)); 831 | 832 | if (lookup[op].mode == &IMP) { 833 | cpu.ac = tmp & 0x00FF; 834 | } else { 835 | cpu_write(addr_abs, tmp & 0x00FF); 836 | } 837 | 838 | return 0; 839 | } 840 | 841 | static uint8_t ROR(void) { 842 | fetch(); 843 | uint16_t tmp = (uint16_t)(cpu_extract_sr(C) << 7) | (fetched >> 1); 844 | 845 | set_flag(C, fetched & 0x0001); 846 | set_flag(Z, (tmp & 0x00FF) == 0x00); 847 | set_flag(N, tmp & (1 << 7)); 848 | 849 | if (lookup[op].mode == &IMP) { 850 | cpu.ac = tmp & 0x00FF; 851 | } else { 852 | cpu_write(addr_abs, tmp & 0x00FF); 853 | } 854 | 855 | return 0; 856 | } 857 | 858 | static uint8_t LSR(void) { 859 | fetch(); 860 | uint16_t tmp = (uint16_t)fetched >> 1; 861 | 862 | set_flag(C, fetched & 0x0001); 863 | set_flag(Z, (tmp & 0x00FF) == 0x00); 864 | set_flag(N, tmp & (1 << 7)); 865 | 866 | if (lookup[op].mode == &IMP) { 867 | cpu.ac = tmp & 0x00FF; 868 | } else { 869 | cpu_write(addr_abs, tmp & 0x00FF); 870 | } 871 | 872 | return 0; 873 | } 874 | 875 | static uint8_t DEC(void) { 876 | fetch(); 877 | uint16_t tmp = fetched - 1; 878 | 879 | cpu_write(addr_abs, tmp & 0x00FF); 880 | 881 | set_flag(Z, ((tmp & 0x00FF) == 0x0000)); 882 | set_flag(N, (tmp & (1 << 7))); 883 | 884 | return 0; 885 | } 886 | 887 | static uint8_t DEX(void) { 888 | cpu.x++; 889 | 890 | set_flag(Z, cpu.x == 0x00); 891 | set_flag(N, cpu.x & (1 << 7)); 892 | 893 | return 0; 894 | } 895 | 896 | static uint8_t DEY(void) { 897 | cpu.y--; 898 | 899 | set_flag(Z, cpu.y == 0x00); 900 | set_flag(N, cpu.y & (1 << 7)); 901 | 902 | return 0; 903 | } 904 | 905 | static uint8_t INC(void) { 906 | fetch(); 907 | uint16_t tmp = (uint16_t)fetched + 1; 908 | 909 | cpu_write(addr_abs, tmp & 0x00FF); 910 | 911 | set_flag(Z, ((tmp & 0x00FF) == 0x0000)); 912 | set_flag(N, tmp & (1 << 7)); 913 | 914 | return 0; 915 | } 916 | 917 | static uint8_t INX(void) { 918 | cpu.x++; 919 | 920 | set_flag(Z, cpu.x == 0x00); 921 | set_flag(N, cpu.x & (1 << 7)); 922 | 923 | return 0; 924 | } 925 | 926 | static uint8_t INY(void) { 927 | cpu.y++; 928 | 929 | set_flag(Z, cpu.y == 0x00); 930 | set_flag(N, cpu.y & (1 << 7)); 931 | 932 | return 0; 933 | } 934 | 935 | static uint8_t PHP(void) { 936 | cpu_write(0x0100 + cpu.sp, cpu.sr); 937 | cpu.sp--; 938 | 939 | return 0; 940 | } 941 | 942 | static uint8_t SEC(void) { 943 | set_flag(C, true); 944 | return 0; 945 | } 946 | 947 | static uint8_t CLC(void) { 948 | set_flag(C, false); 949 | return 0; 950 | } 951 | 952 | static uint8_t PLP(void) { 953 | cpu.sp++; 954 | cpu.sr = cpu_fetch(0x0100 + cpu.sp); 955 | 956 | return 0; 957 | } 958 | 959 | static uint8_t PLA(void) { 960 | cpu.sp++; 961 | cpu.ac = cpu_fetch(0x0100 + cpu.sp); 962 | 963 | set_flag(Z, cpu.ac == 0); 964 | set_flag(N, cpu.ac & (1 << 7)); 965 | 966 | return 0; 967 | } 968 | 969 | static uint8_t PHA(void) { 970 | // 0x0100 is the starting addr of the stack 971 | cpu_write(0x0100 + cpu.sp, cpu.ac); 972 | cpu.sp--; 973 | 974 | return 0; 975 | } 976 | 977 | static uint8_t CLI(void) { 978 | set_flag(I, 0); 979 | return 0; 980 | } 981 | 982 | static uint8_t SEI(void) { 983 | set_flag(I, true); 984 | return 0; 985 | } 986 | 987 | static uint8_t TYA(void) { 988 | cpu.ac = cpu.y; 989 | 990 | set_flag(Z, cpu.ac == 0); 991 | set_flag(N, cpu.ac & (1 << 7)); 992 | 993 | return 0; 994 | } 995 | 996 | static uint8_t CLV(void) { 997 | set_flag(V, false); 998 | return 0; 999 | } 1000 | 1001 | static uint8_t CLD(void) { 1002 | set_flag(D, false); 1003 | return 0; 1004 | } 1005 | 1006 | static uint8_t SED(void) { 1007 | set_flag(D, true); 1008 | return 0; 1009 | } 1010 | 1011 | static uint8_t TXA(void) { 1012 | cpu.ac = cpu.x; 1013 | 1014 | set_flag(Z, cpu.ac == 0); 1015 | set_flag(N, (cpu.ac & (1 << 7))); 1016 | 1017 | return 0; 1018 | } 1019 | 1020 | static uint8_t TXS(void) { 1021 | cpu.sp = cpu.x; 1022 | return 0; 1023 | } 1024 | 1025 | static uint8_t TAX(void) { 1026 | cpu.x = cpu.ac; 1027 | 1028 | set_flag(Z, cpu.x == 0); 1029 | set_flag(N, (cpu.x & (1 << 7))); 1030 | 1031 | return 0; 1032 | } 1033 | 1034 | static uint8_t TAY(void) { 1035 | cpu.y = cpu.ac; 1036 | 1037 | set_flag(Z, cpu.y == 0); 1038 | set_flag(N, (cpu.y & (1 << 7))); 1039 | 1040 | return 0; 1041 | } 1042 | 1043 | static uint8_t TSX(void) { 1044 | cpu.x = cpu.sp; 1045 | 1046 | set_flag(Z, cpu.x == 0); 1047 | set_flag(N, (cpu.x & (1 << 7))); 1048 | 1049 | return 0; 1050 | } 1051 | 1052 | static uint8_t JMP(void) { 1053 | cpu.pc = addr_abs; 1054 | return 0; 1055 | } 1056 | 1057 | /** 1058 | * inst_exec: Parse and execute a fetched instruction 1059 | * @param opcode The retrieved opcode from cpu_exec() 1060 | * @param cycles The amount of clock cycles happening 1061 | * @return void 1062 | */ 1063 | void inst_exec(uint8_t opcode, uint32_t* cycles) { 1064 | // saving variables to the corresponding global ones 1065 | op = opcode; 1066 | cys = cycles; 1067 | 1068 | *cycles = lookup[opcode].cycles; 1069 | 1070 | uint8_t additional_cycle_0 = (*(lookup[opcode].mode))(); 1071 | uint8_t additional_cycle_1 = (*(lookup[opcode].op))(); 1072 | 1073 | *cycles += (additional_cycle_0 & additional_cycle_1); 1074 | 1075 | debug_print("(inst_exec) cycles: %d, %p\n", *(cycles), (void*)cycles); 1076 | } 1077 | -------------------------------------------------------------------------------- /src/cpu/instructions.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_INSTRUCTIONS_H 2 | #define INC_6502_INSTRUCTIONS_H 3 | 4 | #include 5 | 6 | extern uint8_t DEBUG; 7 | 8 | struct instruction { 9 | char* name; 10 | uint8_t (*op)(void); 11 | uint8_t (*mode)(void); 12 | uint8_t cycles; 13 | }; 14 | 15 | void inst_exec(uint8_t opcode, uint32_t* cycles); 16 | void reset(void); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "cpu/cpu.h" 6 | #include "mem/mem.h" 7 | #include "peripherals/interface.h" 8 | #include "peripherals/kinput.h" 9 | 10 | uint8_t DEBUG = 0; 11 | 12 | int main(int argc, char** argv) { 13 | switch (argc) { 14 | case 1: 15 | mem_init("example.bin"); 16 | break; 17 | 18 | case 2: 19 | mem_init(argv[1]); 20 | break; 21 | 22 | default: 23 | fprintf(stderr, "[FAILED] Too many arguments.\n"); 24 | exit(1); 25 | } 26 | 27 | cpu_init(); 28 | cpu_reset(); 29 | 30 | WINDOW* win = newwin(WIN_ROWS, WIN_COLS, 0, 0); 31 | if ((win = initscr()) == NULL) { 32 | fprintf(stderr, "[FAILED] Error initialising ncurses.\n"); 33 | exit(1); 34 | } 35 | 36 | curs_set(0); 37 | noecho(); 38 | box(win, 0, 0); 39 | wrefresh(win); 40 | 41 | interface_display_header(); 42 | wrefresh(win); 43 | 44 | do { 45 | interface_display_cpu(); 46 | interface_display_mem(); 47 | wrefresh(win); 48 | 49 | kinput_listen(); 50 | } while (!kinput_should_quit()); 51 | 52 | delwin(win); 53 | endwin(); 54 | 55 | mem_dump(); 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /src/mem/mem.c: -------------------------------------------------------------------------------- 1 | #include "mem.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../utils/misc.h" 9 | 10 | /** 11 | * The memory: 12 | * 13 | * - RESERVED: 256 bytes 0x0000 to 0x00FF -> Zero Page 14 | * - RESERVED: 256 bytes 0x0100 to 0x01FF -> System Stack 15 | * - PROGRAM DATA: 0x10000 - 0x206 16 | * - RESERVED: last 6 bytes of memory 17 | * 18 | * pages are split into different arrays 19 | * 20 | * */ 21 | struct mem memory; 22 | 23 | /** 24 | * load_program: Loads binary into program data memory 25 | * @param path Path to binary on hosst machine 26 | * @return void 27 | * */ 28 | static void load_program(char* path) { 29 | FILE* fp = fopen(path, "rb"); 30 | 31 | if (fp == NULL) { 32 | fprintf(stderr, "[FAILED] Error while loading provided file.\n"); 33 | exit(1); 34 | } 35 | 36 | struct stat st; 37 | stat(path, &st); 38 | size_t fsize = st.st_size; 39 | 40 | size_t bytes_read = 41 | fread(memory.data + (0x8000 - 0x0200), 1, sizeof(memory.data), fp); 42 | 43 | if (bytes_read != fsize) { 44 | fprintf( 45 | stderr, 46 | "[FAILED] Amount of bytes read doesn't match read file size.\n"); 47 | exit(1); 48 | } 49 | 50 | fclose(fp); 51 | } 52 | 53 | /** 54 | * mem_init: Initialize the memory to its initial state 55 | * 56 | * @param void 57 | * @return void 58 | * */ 59 | void mem_init(char* filename) { 60 | memset(memory.zero_page, 0, sizeof(memory.zero_page)); 61 | memset(memory.stack, 0, sizeof(memory.stack)); 62 | memset(memory.data, 0, sizeof(memory.data)); 63 | 64 | // im not really sure about this 65 | memory.last_six[0] = 0xA; 66 | memory.last_six[1] = 0xB; 67 | memory.last_six[2] = 0xC; 68 | memory.last_six[3] = 0xD; 69 | memory.last_six[4] = 0xE; 70 | memory.last_six[5] = 0xF; 71 | 72 | if (filename == NULL) { 73 | fprintf(stderr, "[FAILED] No binary program was provided.\n"); 74 | exit(1); 75 | } else { 76 | load_program(filename); 77 | } 78 | } 79 | 80 | /** 81 | * mem_get_ptr: returns pointer to currently active memory struct 82 | * */ 83 | struct mem* mem_get_ptr(void) { 84 | struct mem* mp = &memory; 85 | return mp; 86 | } 87 | 88 | /** 89 | * mem_dump: Dumps the memory to a file called dump.bin 90 | * 91 | * @param void 92 | * @return 0 if success, 1 if fail 93 | * */ 94 | int mem_dump(void) { 95 | // 100% there's a better way to do this 96 | 97 | FILE* fp = fopen("dump.bin", "wb+"); 98 | if (fp == NULL) return 1; 99 | 100 | size_t wb = fwrite(memory.zero_page, 1, sizeof(memory.zero_page), fp); 101 | if (wb != sizeof(memory.zero_page)) { 102 | printf("[FAILED] Errors while dumping the zero page.\n"); 103 | fclose(fp); 104 | return 1; 105 | } 106 | wb = fwrite(memory.stack, 1, sizeof(memory.stack), fp); 107 | 108 | if (wb != sizeof(memory.stack)) { 109 | printf("[FAILED] Errors while dumping the system stack.\n"); 110 | fclose(fp); 111 | return 1; 112 | } 113 | 114 | wb = fwrite(memory.data, 1, sizeof(memory.data), fp); 115 | 116 | if (wb != sizeof(memory.data)) { 117 | printf("[FAILED] Errors while dumping the program data.\n"); 118 | fclose(fp); 119 | return 1; 120 | } 121 | 122 | wb = fwrite(memory.last_six, 1, sizeof(memory.last_six), fp); 123 | 124 | if (wb != sizeof(memory.last_six)) { 125 | printf("[FAILED] Errors while dumping the last six reserved bytes.\n"); 126 | fclose(fp); 127 | return 1; 128 | } 129 | 130 | fclose(fp); 131 | return 0; 132 | } 133 | -------------------------------------------------------------------------------- /src/mem/mem.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_MEM_H 2 | #define INC_6502_MEM_H 3 | 4 | #include 5 | #include 6 | 7 | #define TOTAL_MEM 1024 * 64 8 | 9 | struct mem { 10 | uint8_t zero_page[0x100]; 11 | uint8_t stack[0x100]; 12 | uint8_t last_six[0x06]; 13 | uint8_t data[TOTAL_MEM - 0x206]; 14 | }; 15 | 16 | void mem_init(char* filename); 17 | int mem_dump(void); 18 | struct mem* mem_get_ptr(void); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/peripherals/interface.c: -------------------------------------------------------------------------------- 1 | #include "interface.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../cpu/cpu.h" 8 | #include "../mem/mem.h" 9 | 10 | void interface_display_header(void) { 11 | mvprintw(1, 3, "6502 Emulator"); 12 | mvprintw(2, 3, "-----------------------"); 13 | mvprintw(3, 5, "Commands:"); 14 | mvprintw(4, 7, "Enter: executes next instruction"); 15 | mvprintw(5, 7, "r: Resets CPU"); 16 | mvprintw(6, 7, "q: Quits"); 17 | mvprintw(7, 3, "-----------------------"); 18 | } 19 | 20 | /** 21 | * interface_display_cpu: prints CPU status to the screen using ncurses 22 | * @param void 23 | * @return void 24 | * */ 25 | void interface_display_cpu(void) { 26 | mvprintw(10, 3, "ac: 0x%X pc: 0x%X sp: 0x%X x: 0x%X y: 0x%X sr: 0x%X", 27 | cpu.ac, cpu.pc, cpu.sp, cpu.x, cpu.y, cpu.sr); 28 | } 29 | 30 | void interface_display_mem(void) { 31 | struct mem* mp = mem_get_ptr(); 32 | mvprintw(12, 3, "Zero Page:"); 33 | 34 | uint8_t x = 3; 35 | uint8_t y = 14; 36 | for (uint16_t i = 0; i < 256; i++) { 37 | mvprintw(y, x, "%02X ", mp->zero_page[i]); 38 | if (x % WIN_ROWS == 0) { 39 | y += 1; 40 | x = 3; 41 | } else { 42 | x += 3; 43 | } 44 | } 45 | 46 | y += 2; 47 | mvprintw(y, 3, "Stack:"); 48 | y += 2; 49 | x = 3; 50 | 51 | for (uint16_t i = 0; i < 256; i++) { 52 | mvprintw(y, x, "%02X ", mp->stack[i]); 53 | if (x % WIN_ROWS == 0) { 54 | y += 1; 55 | x = 3; 56 | } else { 57 | x += 3; 58 | } 59 | } 60 | 61 | y += 2; 62 | mvprintw(y, 3, "Program Data:"); 63 | y += 2; 64 | x = 3; 65 | 66 | for (uint16_t i = 0x8000 - 0x0200; i < 0x8000 - 0x0200 + 256; i++) { 67 | mvprintw(y, x, "%02X ", mp->data[i]); 68 | if (x % WIN_ROWS == 0) { 69 | y += 1; 70 | x = 3; 71 | } else { 72 | x += 3; 73 | } 74 | } 75 | 76 | mvprintw(y + 2, 3, "[...]"); 77 | } -------------------------------------------------------------------------------- /src/peripherals/interface.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_INTERFACE_H 2 | #define INC_6502_INTERFACE_H 3 | 4 | #define WIN_ROWS 35 5 | #define WIN_COLS 50 6 | 7 | void interface_display_cpu(void); 8 | void interface_display_mem(void); 9 | void interface_display_header(void); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/peripherals/kinput.c: -------------------------------------------------------------------------------- 1 | #include "kinput.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "../cpu/cpu.h" 7 | #include "interface.h" 8 | 9 | uint8_t QUIT = 0; 10 | 11 | /** 12 | * kinput_listen: listens for keyboard events and exuctes respective actions 13 | * @param void 14 | * @return void 15 | * */ 16 | void kinput_listen(void) { 17 | char c = getch(); 18 | 19 | switch (c) { 20 | case '\n': 21 | cpu_exec(); 22 | break; 23 | 24 | case 'r': 25 | cpu_reset(); 26 | break; 27 | 28 | case 'q': 29 | QUIT = 1; 30 | break; 31 | 32 | default: 33 | break; 34 | } 35 | } 36 | 37 | // kinput_should_quit: sends quit signal by returning QUIT status 38 | uint8_t kinput_should_quit(void) { return QUIT; } 39 | -------------------------------------------------------------------------------- /src/peripherals/kinput.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_KINPUT_H 2 | #define INC_6502_KINPUT_H 3 | 4 | #include 5 | 6 | void kinput_listen(void); 7 | uint8_t kinput_should_quit(void); 8 | 9 | #endif -------------------------------------------------------------------------------- /src/utils/misc.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #define debug_print(fmt, ...) \ 5 | do { \ 6 | if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); \ 7 | } while (0) 8 | 9 | extern uint8_t DEBUG; 10 | 11 | #define SET_BIT(val, pos) (val |= (1U << pos)) 12 | #define CLEAR_BIT(val, pos) (val &= (~(1U << pos))) 13 | 14 | 15 | #endif 16 | --------------------------------------------------------------------------------