Cpu opcodes implementation

This commit is contained in:
william 2023-11-26 12:11:49 -05:00
parent ff720237d3
commit 83f4516a46
20 changed files with 1077 additions and 261 deletions

View File

@ -12,6 +12,7 @@
<component name="CMakeRunConfigurationManager"> <component name="CMakeRunConfigurationManager">
<generated> <generated>
<config projectName="NESEmulator" targetName="NESEmulator" /> <config projectName="NESEmulator" targetName="NESEmulator" />
<config projectName="NESEmulator" targetName="Mappers" />
<config projectName="NESEmulator" targetName="CPU" /> <config projectName="NESEmulator" targetName="CPU" />
</generated> </generated>
</component> </component>
@ -22,26 +23,25 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="0c3b231e-0637-4ac1-8964-c60fc9e9e691" name="Changes" comment=""> <list default="true" id="0c3b231e-0637-4ac1-8964-c60fc9e9e691" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" /> <change afterPath="$PROJECT_DIR$/cpu/cpu.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" /> <change afterPath="$PROJECT_DIR$/cpu/memory.c" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/nes.iml" afterDir="false" /> <change afterPath="$PROJECT_DIR$/cpu/memory.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" /> <change afterPath="$PROJECT_DIR$/cpu/ram.c" afterDir="false" />
<change afterPath="$PROJECT_DIR$/conan_provider.cmake" afterDir="false" /> <change afterPath="$PROJECT_DIR$/cpu/ram.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/conandata.yml" afterDir="false" /> <change afterPath="$PROJECT_DIR$/include/mapper.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/conanfile.py" afterDir="false" /> <change afterPath="$PROJECT_DIR$/mappers/CMakeLists.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/cpu/CMakeLists.txt" afterDir="false" /> <change afterPath="$PROJECT_DIR$/mappers/mappers.c" afterDir="false" />
<change afterPath="$PROJECT_DIR$/cpu/mem.c" afterDir="false" /> <change afterPath="$PROJECT_DIR$/mappers/simple_mapper.c" afterDir="false" />
<change afterPath="$PROJECT_DIR$/cpu/op.c" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/include/cpu/cpu.h" afterDir="false" /> <change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/include/cpu/mem.h" afterDir="false" /> <change beforePath="$PROJECT_DIR$/cpu/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/CMakeLists.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Makefile" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/cpu/cpu.c" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/cpu.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/obj/cpu.o" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/cpu/mem.c" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/obj/main.o" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/cpu/op.c" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/op.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/cpu/cpu.c" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/cpu.c" afterDir="false" /> <change beforePath="$PROJECT_DIR$/include/cpu/cpu.h" beforeDir="false" afterPath="$PROJECT_DIR$/include/cpu.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/cpu/cpu.h" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/include/cpu/mem.h" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/cpu/op.c" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/include/cpu/op.h" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/op.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/cpu/op.h" beforeDir="false" afterPath="$PROJECT_DIR$/include/cpu/op.h" afterDir="false" /> <change beforePath="$PROJECT_DIR$/main.c" beforeDir="false" afterPath="$PROJECT_DIR$/main.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main.c" beforeDir="false" afterPath="$PROJECT_DIR$/main.c" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -78,35 +78,38 @@
<component name="ProjectViewState"> <component name="ProjectViewState">
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;, "ASKED_ADD_EXTERNAL_FILES": "true",
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;, "RunOnceActivity.OpenProjectViewOnStart": "true",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;, "RunOnceActivity.cidr.known.project.marker": "true",
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;, "WebServerToolWindowFactoryState": "false",
&quot;cidr.known.project.marker&quot;: &quot;true&quot;, "cf.first.check.clang-format": "false",
&quot;com.jfrog.conanplugin.addconansupport&quot;: &quot;true&quot;, "cidr.known.project.marker": "true",
&quot;com.jfrog.conanplugin.automanage.cmake.advanced.settings&quot;: &quot;true&quot;, "com.jfrog.conanplugin.addconansupport": "true",
&quot;com.jfrog.conanplugin.conanexecutable&quot;: &quot;conan&quot;, "com.jfrog.conanplugin.automanage.cmake.advanced.settings": "true",
&quot;com.jfrog.conanplugin.hasbeensetup&quot;: &quot;true&quot;, "com.jfrog.conanplugin.conanexecutable": "conan",
&quot;git-widget-placeholder&quot;: &quot;master&quot;, "com.jfrog.conanplugin.hasbeensetup": "true",
&quot;last_opened_file_path&quot;: &quot;/d/p/nes/CMakeLists.txt&quot;, "git-widget-placeholder": "master",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "last_opened_file_path": "/home/william/Dev/ETS/LOG710/Lab2/CMakeLists.txt",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.sourceCode.C/C++",
"structure.view.defaults.are.configured": "true",
"vue.rearranger.settings.migration": "true"
} }
}</component> }]]></component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/include/cpu" /> <recent name="$PROJECT_DIR$/include" />
<recent name="$PROJECT_DIR$/cpu" /> <recent name="$PROJECT_DIR$/cpu" />
<recent name="$PROJECT_DIR$/include/cpu" />
<recent name="$PROJECT_DIR$" /> <recent name="$PROJECT_DIR$" />
<recent name="$PROJECT_DIR$/src" /> <recent name="$PROJECT_DIR$/src" />
<recent name="$PROJECT_DIR$/include" />
</key> </key>
</component> </component>
<component name="RunManager" selected="CMake Application.NESEmulator"> <component name="RunManager" selected="CMake Application.NESEmulator">
@ -118,19 +121,143 @@
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" /> <option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method> </method>
</configuration> </configuration>
<configuration name="Mappers" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="NESEmulator" TARGET_NAME="Mappers" CONFIG_NAME="Debug">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="NESEmulator" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="NESEmulator" TARGET_NAME="NESEmulator" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="NESEmulator" RUN_TARGET_NAME="NESEmulator"> <configuration name="NESEmulator" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="NESEmulator" TARGET_NAME="NESEmulator" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="NESEmulator" RUN_TARGET_NAME="NESEmulator">
<method v="2"> <method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" /> <option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method> </method>
</configuration> </configuration>
<configuration default="true" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true"> <configuration default="true" type="PythonConfigurationType" factoryName="Python">
<method v="2"> <module name="nesemu" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" /> <option name="INTERPRETER_OPTIONS" value="" />
</method> <option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration default="true" type="Tox" factoryName="Tox">
<module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<method v="2" />
</configuration>
<configuration default="true" type="tests" factoryName="Autodetect">
<module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;&quot;" />
<option name="_new_targetType" value="&quot;PATH&quot;" />
<method v="2" />
</configuration>
<configuration default="true" type="tests" factoryName="Doctests">
<module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="FOLDER_NAME" value="" />
<option name="TEST_TYPE" value="TEST_SCRIPT" />
<option name="PATTERN" value="" />
<option name="USE_PATTERN" value="false" />
<method v="2" />
</configuration>
<configuration default="true" type="tests" factoryName="Nosetests">
<module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="_new_regexPattern" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;&quot;" />
<option name="_new_targetType" value="&quot;PATH&quot;" />
<method v="2" />
</configuration>
<configuration default="true" type="tests" factoryName="Twisted Trial">
<module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;&quot;" />
<option name="_new_targetType" value="&quot;PATH&quot;" />
<method v="2" />
</configuration>
<configuration default="true" type="tests" factoryName="Unittests">
<module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;&quot;" />
<option name="_new_targetType" value="&quot;PATH&quot;" />
<method v="2" />
</configuration>
<configuration default="true" type="tests" factoryName="py.test">
<module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="_new_keywords" value="&quot;&quot;" />
<option name="_new_parameters" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;&quot;" />
<option name="_new_targetType" value="&quot;PATH&quot;" />
<method v="2" />
</configuration> </configuration>
<list> <list>
<item itemvalue="CMake Application.NESEmulator" /> <item itemvalue="CMake Application.NESEmulator" />
<item itemvalue="CMake Application.CPU" /> <item itemvalue="CMake Application.CPU" />
<item itemvalue="CMake Application.Mappers" />
<item itemvalue="CMake Debug.NESEmulator" /> <item itemvalue="CMake Debug.NESEmulator" />
</list> </list>
<recent_temporary> <recent_temporary>
@ -151,6 +278,14 @@
<workItem from="1696127133169" duration="3618000" /> <workItem from="1696127133169" duration="3618000" />
<workItem from="1696203038713" duration="1394000" /> <workItem from="1696203038713" duration="1394000" />
<workItem from="1696539868309" duration="1000" /> <workItem from="1696539868309" duration="1000" />
<workItem from="1696705497743" duration="219000" />
<workItem from="1696705726453" duration="4200000" />
<workItem from="1696811216438" duration="1141000" />
<workItem from="1697310877679" duration="207000" />
<workItem from="1697340607326" duration="3636000" />
<workItem from="1697386768217" duration="19159000" />
<workItem from="1697491210595" duration="714000" />
<workItem from="1698343311430" duration="84000" />
</task> </task>
<servers /> <servers />
</component> </component>

View File

@ -2,13 +2,16 @@ cmake_minimum_required(VERSION 3.10)
project(NESEmulator VERSION 0.1) project(NESEmulator VERSION 0.1)
add_subdirectory(cpu) add_subdirectory(cpu)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/cpu") add_subdirectory(mappers)
list(APPEND EXTRA_INCLUDES
"${PROJECT_SOURCE_DIR}/cpu"
"${PROJECT_SOURCE_DIR}/mappers")
add_executable(NESEmulator main.c) add_executable(NESEmulator main.c)
find_package(log.c) find_package(log.c)
target_link_libraries(NESEmulator CPU log.c::log.c) target_link_libraries(NESEmulator CPU Mappers log.c::log.c)
target_include_directories(NESEmulator PUBLIC target_include_directories(NESEmulator PUBLIC
"${PROJECT_BINARY_DIR}" "${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}) ${EXTRA_INCLUDES})

View File

@ -1,4 +1,9 @@
add_library(CPU add_library(CPU
cpu.c cpu.c
op.c op.c
mem.c) ram.c
memory.c
cpu.h)
find_package(log.c)
target_link_libraries(CPU log.c::log.c)

View File

@ -1,3 +1,7 @@
#include "../include/cpu.h"
#include "cpu.h"
#include "memory.h"
/* /*
* ===================================================================================== * =====================================================================================
* *
@ -15,13 +19,86 @@
* *
* ===================================================================================== * =====================================================================================
*/ */
#include <stdio.h>
#include <stdlib.h>
#include "../include/cpu/op.h" CpuRegisters registers;
Mapper mapper;
long cpu_clock = 0; void cpu_init() {
registers.program_counter = 0x0000;
void cpu_step_to(long cycle) { registers.stack_pointer = 0xff;
registers.accumulator = 0x00;
registers.x = 0x00;
registers.y = 0x00;
registers.status = 0x00;
} }
// === Registers ===
CpuRegisters* cpu_get_registers() {
return &registers;
}
byte cpu_get_flag(byte mask) {
return registers.status & mask;
}
void cpu_set_flag(bool set, byte mask) {
if (set) {
registers.status |= mask;
} else {
registers.status &= ~mask;
}
}
// === Memory ===
byte cpu_get_next_byte() {
byte next_byte = mem_get_byte(&mapper, registers.program_counter);
registers.program_counter++;
return next_byte;
}
word cpu_get_next_word() {
word next_word = mem_get_word(&mapper, registers.program_counter);
registers.program_counter += 2;
return next_word;
}
byte cpu_peek_byte(address addr) {
return mem_get_byte(&mapper, addr);
}
word cpu_peek_word(address addr) {
return mem_get_word(&mapper, addr);
}
void cpu_push_byte(byte value, address addr) {
mem_set_byte(&mapper, addr, value);
}
// === Stack ===
void cpu_stack_push(byte value) {
address mem_addr = CPU_STACK_ADDR | registers.stack_pointer;
cpu_push_byte(value, mem_addr);
registers.stack_pointer--;
}
byte cpu_stack_pop() {
address mem_addr = CPU_STACK_ADDR | registers.stack_pointer;
byte value = cpu_peek_byte(mem_addr);
registers.stack_pointer++;
return value;
}
void cpu_stack_push_context() {
cpu_stack_push(registers.program_counter >> 8);
cpu_stack_push(registers.program_counter & 0xff);
cpu_stack_push(registers.status);
}
void cpu_stack_pop_context() {
registers.status = cpu_stack_pop();
byte lo = cpu_stack_pop();
address pc = cpu_stack_pop() << 8;
pc += lo;
registers.program_counter = pc;
}

60
cpu/cpu.h Normal file
View File

@ -0,0 +1,60 @@
//
// Created by william on 10/15/23.
//
#ifndef CPU_CPU_H
#define CPU_CPU_H
#include <stdbool.h>
// Reference: https://www.nesdev.org/wiki/Status_flags
#define CPU_STATUS_CARRY_MASK 0x01
#define CPU_STATUS_ZERO_MASK 0x02
#define CPU_STATUS_INTERRUPT_DISABLE_MASK 0x04
#define CPU_STATUS_DECIMAL_MASK 0x08
#define CPU_STATUS_B_MASK 0x10
#define CPU_STATUS_I_MASK 0x20
#define CPU_STATUS_OVERFLOW_MASK 0x40
#define CPU_STATUS_NEGATIVE_MASK 0x80
#define CPU_STACK_ADDR 0x0100
// Reference: https://www.nesdev.org/obelisk-6502-guide/registers.html
typedef struct {
address program_counter;
byte stack_pointer;
byte accumulator;
byte x;
byte y;
byte status;
} CpuRegisters;
enum OperandType {
OPERAND_TYPE_ACCUMULATOR,
OPERAND_TYPE_IMMEDIATE,
OPERAND_TYPE_ADDRESS
};
typedef struct {
word value;
enum OperandType type;
bool is_page_crossing;
} Operand;
CpuRegisters* cpu_get_registers();
byte cpu_get_flag(byte mask);
void cpu_set_flag(bool set, byte mask);
byte cpu_get_next_byte();
word cpu_get_next_word();
byte cpu_peek_byte(address addr);
word cpu_peek_word(address addr);
void cpu_push_byte(byte value, address addr);
void cpu_stack_push(byte value);
void cpu_stack_push_context();
byte cpu_stack_pop();
void cpu_stack_pop_context();
#endif //CPU_CPU_H

View File

@ -1,26 +0,0 @@
//
// Created by william on 30/09/23.
//
#include "../include/cpu/mem.h"
#include "../include/cpu/op.h"
#include "../include/cpu/cpu.h"
unsigned short get_memory_address(addr_mode_t addr_mode, unsigned short operand) {
if (addr_mode == ADDR_MODE_ABSOLUTE) {
cpu_add_cycles(4);
return operand;
} else if (addr_mode == ADDR_MODE_ABSOLUTE_INDEXED_X) {
unsigned char x = cpu_get_registers()->x;
cpu_add_cycles(4);
return operand + x;
} else if (addr_mode == ADDR_MODE_ABSOLUTE_INDEXED_Y) {
unsigned char y = cpu_get_registers()->y;
cpu_add_cycles(4);
return operand + y;
} else if (addr_mode == ADDR_MODE_INDEXED_INDIRECT) {
}
}

21
cpu/memory.c Normal file
View File

@ -0,0 +1,21 @@
//
// Created by william on 10/15/23.
//
#include "memory.h"
#include "ram.h"
byte mem_get_byte(Mapper *mapper, address addr) {
address redirected_addr = mapper->redirect_addr(addr);
return ram_get_byte(redirected_addr);
}
word mem_get_word(Mapper *mapper, address addr) {
address redirected_addr = mapper->redirect_addr(addr);
return ram_get_word(redirected_addr);
}
void mem_set_byte(Mapper *mapper, address addr, byte byte) {
address redirected_addr = mapper->redirect_addr(addr);
ram_set_byte(redirected_addr, byte);
}

14
cpu/memory.h Normal file
View File

@ -0,0 +1,14 @@
//
// Created by william on 10/15/23.
//
#include "../include/mapper.h"
#ifndef NESEMULATOR_MEMORY_H
#define NESEMULATOR_MEMORY_H
byte mem_get_byte(Mapper *mapper, address addr);
word mem_get_word(Mapper *mapper, address addr);
void mem_set_byte(Mapper *mapper, address addr, byte byte);
#endif //NESEMULATOR_MEMORY_H

675
cpu/op.c
View File

@ -1,6 +1,13 @@
#include "../include/cpu/op.h" #include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
#include "op.h"
#include "cpu.h"
#include "../include/cpu.h"
// Reference: https://www.nesdev.org/wiki/CPU_unofficial_opcodes // Reference: https://www.nesdev.org/wiki/CPU_unofficial_opcodes
// https://www.middle-engine.com/blog/posts/2020/06/23/programming-the-nes-the-6502-in-detail
#define IS_OP_CODE_MODE(op, op_code, addr_mode) \ #define IS_OP_CODE_MODE(op, op_code, addr_mode) \
case op_code: \ case op_code: \
@ -14,20 +21,20 @@
IS_OP_CODE_MODE(op, OP_CODE_BASE_ ## op + offset, addr_mode) IS_OP_CODE_MODE(op, OP_CODE_BASE_ ## op + offset, addr_mode)
#define IS_ALU_OP_CODE(op) \ #define IS_ALU_OP_CODE(op) \
IS_ALU_OP_CODE_(op, 0x01, INDEXED_INDIRECT) \ IS_ALU_OP_CODE_(op, 0x01, INDIRECT_X) \
IS_ALU_OP_CODE_(op, 0x05, ZERO_PAGE) \ IS_ALU_OP_CODE_(op, 0x05, ZERO_PAGE) \
IS_ALU_OP_CODE_(op, 0x09, IMMEDIATE) \ IS_ALU_OP_CODE_(op, 0x09, IMMEDIATE) \
IS_ALU_OP_CODE_(op, 0x0d, ABSOLUTE) \ IS_ALU_OP_CODE_(op, 0x0d, ABSOLUTE) \
IS_ALU_OP_CODE_(op, 0x11, INDIRECT_INDEXED) \ IS_ALU_OP_CODE_(op, 0x11, INDIRECT_Y) \
IS_ALU_OP_CODE_(op, 0x15, ZERO_PAGE_INDEXED_X) \ IS_ALU_OP_CODE_(op, 0x15, ZERO_PAGE_INDEXED_X) \
IS_ALU_OP_CODE_(op, 0x19, ABSOLUTE_INDEXED_Y) \ IS_ALU_OP_CODE_(op, 0x19, ABSOLUTE_INDEXED_Y) \
IS_ALU_OP_CODE_(op, 0x1d, ABSOLUTE_INDEXED_X) IS_ALU_OP_CODE_(op, 0x1d, ABSOLUTE_INDEXED_X)
#define IS_ALU_OP_CODE_NO_IMMEDIATE(op) \ #define IS_ALU_OP_CODE_NO_IMMEDIATE(op) \
IS_ALU_OP_CODE_(op, 0x01, INDEXED_INDIRECT) \ IS_ALU_OP_CODE_(op, 0x01, INDIRECT_X) \
IS_ALU_OP_CODE_(op, 0x05, ZERO_PAGE) \ IS_ALU_OP_CODE_(op, 0x05, ZERO_PAGE) \
IS_ALU_OP_CODE_(op, 0x0d, ABSOLUTE) \ IS_ALU_OP_CODE_(op, 0x0d, ABSOLUTE) \
IS_ALU_OP_CODE_(op, 0x11, INDIRECT_INDEXED) \ IS_ALU_OP_CODE_(op, 0x11, INDIRECT_Y) \
IS_ALU_OP_CODE_(op, 0x15, ZERO_PAGE_INDEXED_X) \ IS_ALU_OP_CODE_(op, 0x15, ZERO_PAGE_INDEXED_X) \
IS_ALU_OP_CODE_(op, 0x19, ABSOLUTE_INDEXED_Y) \ IS_ALU_OP_CODE_(op, 0x19, ABSOLUTE_INDEXED_Y) \
IS_ALU_OP_CODE_(op, 0x1d, ABSOLUTE_INDEXED_X) IS_ALU_OP_CODE_(op, 0x1d, ABSOLUTE_INDEXED_X)
@ -46,316 +53,760 @@
IS_OP_CODE_MODE(op, OP_CODE_BASE_ ## line + offset, addr_mode) IS_OP_CODE_MODE(op, OP_CODE_BASE_ ## line + offset, addr_mode)
#define IS_UNOFFICIAL_OP_CODE(op, line) \ #define IS_UNOFFICIAL_OP_CODE(op, line) \
IS_UNOFFICIAL_OP_CODE_(op, line, 0x03, INDEXED_INDIRECT) \ IS_UNOFFICIAL_OP_CODE_(op, line, 0x03, INDIRECT_X) \
IS_UNOFFICIAL_OP_CODE_(op, line, 0x07, ZERO_PAGE) \ IS_UNOFFICIAL_OP_CODE_(op, line, 0x07, ZERO_PAGE) \
IS_UNOFFICIAL_OP_CODE_(op, line, 0x0f, ABSOLUTE) \ IS_UNOFFICIAL_OP_CODE_(op, line, 0x0f, ABSOLUTE) \
IS_UNOFFICIAL_OP_CODE_(op, line, 0x13, INDIRECT_INDEXED) \ IS_UNOFFICIAL_OP_CODE_(op, line, 0x13, INDIRECT_Y) \
IS_UNOFFICIAL_OP_CODE_(op, line, 0x17, ZERO_PAGE_INDEXED_X) \ IS_UNOFFICIAL_OP_CODE_(op, line, 0x17, ZERO_PAGE_INDEXED_X) \
IS_UNOFFICIAL_OP_CODE_(op, line, 0x1b, ABSOLUTE_INDEXED_Y) \ IS_UNOFFICIAL_OP_CODE_(op, line, 0x1b, ABSOLUTE_INDEXED_Y) \
IS_UNOFFICIAL_OP_CODE_(op, line, 0x1f, ABSOLUTE_INDEXED_X) IS_UNOFFICIAL_OP_CODE_(op, line, 0x1f, ABSOLUTE_INDEXED_X)
void op_ADC(addr_mode_t addr_mode) { address decode_operand_addr(AddressingMode addr_mode, bool *page_crossing) {
CpuRegisters *registers = cpu_get_registers();
if (addr_mode == ADDR_MODE_ZERO_PAGE) {
return cpu_get_next_byte();
} else if (addr_mode == ADDR_MODE_ZERO_PAGE_INDEXED_X) {
return (cpu_get_next_byte() + registers->x) & 0xff;
} else if (addr_mode == ADDR_MODE_ZERO_PAGE_INDEXED_Y) {
return (cpu_get_next_byte() + registers->y) & 0xff;
} else if (addr_mode == ADDR_MODE_ABSOLUTE || addr_mode == ADDR_MODE_ABSOLUTE_JUMP) {
return cpu_get_next_word();
} else if (addr_mode == ADDR_MODE_ABSOLUTE_INDEXED_X) {
word addr = cpu_get_next_word();
word new_addr = addr + registers->x;
*page_crossing = (addr & 0xff00) != (new_addr & 0xff00);
return new_addr;
} else if (addr_mode == ADDR_MODE_ABSOLUTE_INDEXED_Y) {
word addr = cpu_get_next_word();
word new_addr = addr + registers->y;
*page_crossing = (addr & 0xff00) != (new_addr & 0xff00);
return new_addr;
} else if (addr_mode == ADDR_MODE_INDIRECT_JUMP) {
word addr = cpu_get_next_word();
if ((addr & 0xff) == 0xff) {
// Error in NES CPU for JMP op
word result = cpu_peek_byte(addr);
result += cpu_peek_byte(addr & 0xff00) << 8;
return result;
}
return cpu_peek_word(addr);
} else if (addr_mode == ADDR_MODE_INDIRECT_X) {
byte addr = cpu_get_next_byte();
word result = cpu_peek_byte((addr + registers->x) & 0xff);
result += cpu_peek_byte((addr + registers->x + 1) & 0xff) << 8;
return result;
} else if (addr_mode == ADDR_MODE_INDIRECT_Y) {
byte arg_addr = cpu_get_next_byte();
word addr = cpu_peek_byte(arg_addr) + (cpu_peek_byte((arg_addr + 1) & 0xff) << 8);
word new_addr = addr + registers->y;
*page_crossing = (addr & 0xff00) != (new_addr & 0xff00);
return new_addr;
} else {
assert(false);
}
}
Operand decode_operand(AddressingMode addr_mode) {
Operand operand;
if (addr_mode == ADDR_MODE_ACCUMULATOR) {
operand.type = OPERAND_TYPE_ACCUMULATOR;
operand.value = 0;
operand.is_page_crossing = false;
} else if (addr_mode == ADDR_MODE_IMMEDIATE) {
operand.type = OPERAND_TYPE_IMMEDIATE;
operand.value = cpu_get_next_byte();
operand.is_page_crossing = false;
} else {
operand.type = OPERAND_TYPE_ADDRESS;
operand.value = decode_operand_addr(addr_mode, &operand.is_page_crossing);
}
return operand;
}
byte read_operand(Operand operand) {
switch (operand.type) {
case OPERAND_TYPE_ACCUMULATOR:
return cpu_get_registers()->accumulator;
case OPERAND_TYPE_IMMEDIATE:
return (byte) operand.value;
case OPERAND_TYPE_ADDRESS:
return cpu_peek_byte(operand.value);
default:
assert(false);
}
}
void write_operand(Operand operand, byte value) {
switch (operand.type) {
case OPERAND_TYPE_ACCUMULATOR:
cpu_get_registers()->accumulator = value;
break;
case OPERAND_TYPE_ADDRESS:
cpu_push_byte(operand.value, value);
break;
default:
assert(false);
}
}
bool is_sign_overflow(byte val1, byte val2, byte result) {
return ((val1 & 0x80) == (val2 & 0x80)) &&
((val1 & 0x80) != (result & 0x80));
}
byte get_cycle_count(Operand operand, AddressingMode addr_mode) {
switch (addr_mode) {
case ADDR_MODE_ACCUMULATOR:
case ADDR_MODE_IMPLICIT:
return 2;
case ADDR_MODE_ZERO_PAGE:
return 3;
case ADDR_MODE_ZERO_PAGE_INDEXED_X:
case ADDR_MODE_ZERO_PAGE_INDEXED_Y:
case ADDR_MODE_ABSOLUTE:
return 4;
case ADDR_MODE_ABSOLUTE_INDEXED_X:
case ADDR_MODE_ABSOLUTE_INDEXED_Y:
return operand.is_page_crossing ? 5 : 4;
case ADDR_MODE_INDIRECT_X:
return 6;
case ADDR_MODE_INDIRECT_Y:
return operand.is_page_crossing ? 6 : 5;
default:
assert(false);
}
}
byte get_shift_cycle_count(AddressingMode addr_mode) {
switch (addr_mode) {
case ADDR_MODE_ACCUMULATOR:
return 2;
case ADDR_MODE_ZERO_PAGE:
return 5;
case ADDR_MODE_ZERO_PAGE_INDEXED_X:
case ADDR_MODE_ABSOLUTE:
return 6;
case ADDR_MODE_ABSOLUTE_INDEXED_X:
return 7;
default:
assert(false);
}
} }
/* === CTRL === */ void set_acl_flags(byte result) {
void op_BRK(addr_mode_t addr_mode) { cpu_set_flag(result == 0, CPU_STATUS_ZERO_MASK);
cpu_set_flag(result & 0x80, CPU_STATUS_NEGATIVE_MASK);
} }
void op_BPL(addr_mode_t addr_mode) { byte get_branch_cycle_count(bool branching, char offset) {
address target = cpu_get_registers()->program_counter;
byte cycle_count = 2;
} if (branching) {
cycle_count += 1;
void op_BIT(addr_mode_t addr_mode) { if ((target & 0xff00) != ((target - offset) & 0xff00)) {
cycle_count += 2;
}
}
return cycle_count;
} }
void op_BMI(addr_mode_t addr_mode) { void op_branch(bool branching) {
char offset = (char) cpu_get_next_byte();
if (branching) {
address counter = cpu_get_registers()->program_counter;
address target = counter + offset;
cpu_get_registers()->program_counter = target;
}
cpu_add_cycles(get_branch_cycle_count(branching, offset));
} }
void op_BVC(addr_mode_t addr_mode) { void add_with_carry(byte value) {
byte acc = cpu_get_registers()->accumulator;
} byte addition = acc + value;
bool overflow = false;
void op_BVS(addr_mode_t addr_mode) { // Check for overflow
if (addition < acc) {
overflow = true;
}
} // Add carry flag and check for overflow again
byte result = addition + cpu_get_flag(CPU_STATUS_CARRY_MASK);
if (result < addition) {
overflow = true;
}
void op_BCC(addr_mode_t addr_mode) { cpu_get_registers()->accumulator = acc;
cpu_set_flag(overflow, CPU_STATUS_CARRY_MASK);
cpu_set_flag(is_sign_overflow(acc, value, result));
set_acl_flags(result);
} }
void op_BCS(addr_mode_t addr_mode) { void op_ADC(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
} byte value = read_operand(operand);
add_with_carry(value);
void op_BNE(addr_mode_t addr_mode) { cpu_add_cycles(get_cycle_count(operand, addr_mode));
}
void op_AHX(AddressingMode addr_mode) {
assert(false);
} }
void op_BEQ(addr_mode_t addr_mode) { void op_ALR(AddressingMode addr_mode) {
assert(false);
}
void op_ANC(AddressingMode addr_mode) {
assert(false);
} }
void op_PHP(addr_mode_t addr_mode) { void op_AND(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte acc = cpu_get_registers()->accumulator;
} byte result = acc & value;
cpu_get_registers()->accumulator = result;
void op_CLC(addr_mode_t addr_mode) { set_acl_flags(result);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
}
void op_ARR(AddressingMode addr_mode) {
assert(false);
} }
void op_JSR(addr_mode_t addr_mode) { void op_ASL(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte result = value << 1;
write_operand(operand, result);
cpu_set_flag(value & 0x80, CPU_STATUS_CARRY_MASK);
set_acl_flags(result);
cpu_add_cycles(get_shift_cycle_count(addr_mode));
} }
void op_PLP(addr_mode_t addr_mode) { void op_AXS(AddressingMode addr_mode) {
assert(false);
}
void op_BCC(AddressingMode addr_mode) {
op_branch(!cpu_get_flag(CPU_STATUS_CARRY_MASK));
} }
void op_SEC(addr_mode_t addr_mode) { void op_BCS(AddressingMode addr_mode) {
op_branch(cpu_get_flag(CPU_STATUS_CARRY_MASK));
}
void op_BEQ(AddressingMode addr_mode) {
op_branch(cpu_get_flag(CPU_STATUS_ZERO_MASK));
} }
void op_RTI(addr_mode_t addr_mode) { void op_BIT(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte acc = cpu_get_registers()->accumulator;
} byte result = value & acc;
void op_PHA(addr_mode_t addr_mode) { cpu_set_flag(result == 0, CPU_STATUS_ZERO_MASK);
cpu_set_flag(result & 0x40, CPU_STATUS_OVERFLOW_MASK);
cpu_set_flag(result & 0x80, CPU_STATUS_NEGATIVE_MASK);
}
void op_BMI(AddressingMode addr_mode) {
op_branch(cpu_get_flag(CPU_STATUS_NEGATIVE_MASK));
} }
void op_JMP(addr_mode_t addr_mode) { void op_BNE(AddressingMode addr_mode) {
op_branch(!cpu_get_flag(CPU_STATUS_ZERO_MASK));
}
void op_BPL(AddressingMode addr_mode) {
op_branch(!cpu_get_flag(CPU_STATUS_NEGATIVE_MASK));
} }
void op_CLI(addr_mode_t addr_mode) { // Stops program execution, useful for debugging
void op_BRK(AddressingMode addr_mode) {
cpu_stack_push_context();
// TODO Load IRQ interrupt vector in PC at $FFFE/F
cpu_set_flag(true, CPU_STATUS_B_MASK);
cpu_add_cycles(7);
} }
void op_RTS(addr_mode_t addr_mode) { void op_BVC(AddressingMode addr_mode) {
op_branch(!cpu_get_flag(CPU_STATUS_OVERFLOW_MASK));
}
void op_BVS(AddressingMode addr_mode) {
op_branch(cpu_get_flag(CPU_STATUS_OVERFLOW_MASK));
} }
void op_PLA(addr_mode_t addr_mode) { void op_CLC(AddressingMode addr_mode) {
cpu_set_flag(false, CPU_STATUS_CARRY_MASK);
cpu_add_cycles(2);
}
void op_CLD(AddressingMode addr_mode) {
cpu_set_flag(false, CPU_STATUS_DECIMAL_MASK);
cpu_add_cycles(2);
} }
void op_SEI(addr_mode_t addr_mode) { void op_CLI(AddressingMode addr_mode) {
cpu_set_flag(false, CPU_STATUS_INTERRUPT_DISABLE_MASK);
cpu_add_cycles(2);
}
void op_CLV(AddressingMode addr_mode) {
cpu_set_flag(false, CPU_STATUS_OVERFLOW_MASK);
cpu_add_cycles(2);
} }
void op_STY(addr_mode_t addr_mode) { void op_CMP(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte acc = cpu_get_registers()->accumulator;
} byte result = acc - value;
void op_DEY(addr_mode_t addr_mode) { cpu_set_flag(acc >= value, CPU_STATUS_CARRY_MASK);
cpu_set_flag(result == 0, CPU_STATUS_ZERO_MASK);
cpu_set_flag(result & 0x80, CPU_STATUS_NEGATIVE_MASK);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_TYA(addr_mode_t addr_mode) { void op_CPX(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte x = cpu_get_registers()->x;
} byte result = x - value;
void op_LDY(addr_mode_t addr_mode) { cpu_set_flag(x >= value, CPU_STATUS_CARRY_MASK);
cpu_set_flag(result == 0, CPU_STATUS_ZERO_MASK);
cpu_set_flag(result & 0x80, CPU_STATUS_NEGATIVE_MASK);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_TAY(addr_mode_t addr_mode) { void op_CPY(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte y = cpu_get_registers()->y;
} byte result = y - value;
void op_CLV(addr_mode_t addr_mode) { cpu_set_flag(y >= value, CPU_STATUS_CARRY_MASK);
cpu_set_flag(result == 0, CPU_STATUS_ZERO_MASK);
cpu_set_flag(result & 0x80, CPU_STATUS_NEGATIVE_MASK);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_CPY(addr_mode_t addr_mode) { void op_DCP(AddressingMode addr_mode) {
assert(false);
} }
void op_INY(addr_mode_t addr_mode) { void op_DEC(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte result = value - 1;
set_acl_flags(result);
write_operand(operand, result);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_CLD(addr_mode_t addr_mode) { void op_DEX(AddressingMode addr_mode) {
byte x = cpu_get_registers()->x;
byte result = x - 1;
cpu_get_registers()->x = result;
set_acl_flags(result);
cpu_add_cycles(2);
} }
void op_CPX(addr_mode_t addr_mode) { void op_DEY(AddressingMode addr_mode) {
byte y = cpu_get_registers()->y;
byte result = y - 1;
cpu_get_registers()->y = result;
set_acl_flags(result);
cpu_add_cycles(2);
} }
void op_INX(addr_mode_t addr_mode) { void op_EOR(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte acc = cpu_get_registers()->accumulator;
acc ^= value;
cpu_get_registers()->accumulator = acc;
set_acl_flags(acc);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_SED(addr_mode_t addr_mode) { void op_INC(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
value += 1;
write_operand(operand, value);
set_acl_flags(value);
cpu_add_cycles(get_shift_cycle_count(addr_mode));
} }
/* === ALU === */ void op_INX(AddressingMode addr_mode) {
void op_ORA(addr_mode_t addr_mode) { byte x = cpu_get_registers()->x;
x += 1;
cpu_get_registers()->x = x;
set_acl_flags(x);
cpu_add_cycles(2);
} }
void op_AND(addr_mode_t addr_mode) { void op_INY(AddressingMode addr_mode) {
byte y = cpu_get_registers()->y;
} y += 1;
cpu_get_registers()->y = y;
void op_EOR(addr_mode_t addr_mode) { set_acl_flags(y);
cpu_add_cycles(2);
}
void op_ISC(AddressingMode addr_mode) {
assert(false);
} }
void op_ADC(addr_mode_t addr_mode) { void op_JMP(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte addr = read_operand(operand);
cpu_get_registers()->program_counter = addr;
// TODO WN: Handle CPU bug?
// > An original 6502 has does not correctly fetch the target address if the indirect vector falls on a page boundary (e.g. $xxFF where xx is any value from $00 to $FF).
// > In this case fetches the LSB from $xxFF as expected but takes the MSB from $xx00.
// > This is fixed in some later chips like the 65SC02 so for compatibility always ensure the indirect vector is not at the end of the page.
} }
void op_STA(addr_mode_t addr_mode) { void op_JSR(AddressingMode addr_mode) {
// Push the program counter on the stack
address program_counter = cpu_get_registers()->program_counter - 1;
cpu_stack_push(program_counter >> 8);
cpu_stack_push(program_counter & 0xff);
} // Updates the program counter to the address in the operand
address addr = decode_operand_addr(addr_mode, NULL);
cpu_get_registers()->program_counter = addr;
void op_LDA(addr_mode_t addr_mode) { cpu_add_cycles(6);
}
void op_LAX(AddressingMode addr_mode) {
assert(false);
} }
void op_CMP(addr_mode_t addr_mode) { void op_LDA(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
cpu_get_registers()->accumulator = value;
set_acl_flags(value);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_SBC(addr_mode_t addr_mode) { void op_LDX(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
cpu_get_registers()->x = value;
set_acl_flags(value);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_NOP(addr_mode_t addr_mode) { void op_LDY(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
cpu_get_registers()->y = value;
set_acl_flags(value);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
/* RMW */ void op_LSR(AddressingMode addr_mode) {
void op_ASL(addr_mode_t addr_mode) { Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
} // Put bit 0 in the carry flag
cpu_set_flag(value & 0x01, CPU_STATUS_CARRY_MASK);
void op_ROL(addr_mode_t addr_mode) { value >>= 1;
write_operand(operand, value);
set_acl_flags(value);
cpu_add_cycles(get_shift_cycle_count(addr_mode));
} }
void op_LSR(addr_mode_t addr_mode) { void op_NOP(AddressingMode addr_mode) {
cpu_add_cycles(2);
} }
void op_ROR(addr_mode_t addr_mode) { void op_ORA(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte acc = cpu_get_registers()->accumulator;
acc |= value;
cpu_get_registers()->accumulator = acc;
set_acl_flags(acc);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_STX(addr_mode_t addr_mode) { void op_PHA(AddressingMode addr_mode) {
byte acc = cpu_get_registers()->accumulator;
cpu_stack_push(acc);
cpu_add_cycles(3);
} }
void op_TXA(addr_mode_t addr_mode) { void op_PHP(AddressingMode addr_mode) {
byte status = cpu_get_registers()->status;
cpu_stack_push(status);
cpu_add_cycles(3);
} }
void op_TXS(addr_mode_t addr_mode) { void op_PLA(AddressingMode addr_mode) {
byte value = cpu_stack_pop();
cpu_get_registers()->accumulator = value;
cpu_add_cycles(4);
} }
void op_SHX(addr_mode_t addr_mode) { void op_PLP(AddressingMode addr_mode) {
byte value = cpu_stack_pop();
cpu_get_registers()->status = value;
cpu_add_cycles(4);
} }
void op_LDX(addr_mode_t addr_mode) { void op_RLA(AddressingMode addr_mode) {
assert(false);
} }
void op_TAX(addr_mode_t addr_mode) { void op_ROL(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte carry = cpu_get_flag(CPU_STATUS_CARRY_MASK);
cpu_set_flag(value & 0x80, CPU_STATUS_CARRY_MASK);
value = (value << 1) | carry;
write_operand(operand, value);
set_acl_flags(value);
cpu_add_cycles(get_shift_cycle_count(addr_mode));
} }
void op_TSX(addr_mode_t addr_mode) { void op_ROR(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
byte carry = cpu_get_flag(CPU_STATUS_CARRY_MASK);
cpu_set_flag(value & 0x01, CPU_STATUS_CARRY_MASK);
value = (value >> 1) | (carry << 7);
write_operand(operand, value);
set_acl_flags(value);
cpu_add_cycles(get_shift_cycle_count(addr_mode));
} }
void op_DEC(addr_mode_t addr_mode) { void op_RRA(AddressingMode addr_mode) {
assert(false);
}
void op_RTI(AddressingMode addr_mode) {
cpu_stack_pop_context();
cpu_add_cycles(6);
} }
void op_DEX(addr_mode_t addr_mode) { void op_RTS(AddressingMode addr_mode) {
byte lo = cpu_stack_pop();
address pc = cpu_stack_pop() << 8;
pc += lo;
cpu_get_registers()->program_counter = pc - 1;
cpu_add_cycles(6);
} }
void op_INC(addr_mode_t addr_mode) { void op_SAX(AddressingMode addr_mode) {
assert(false);
} }
// Unofficial void op_SBC(AddressingMode addr_mode) {
void op_STP(addr_mode_t addr_mode) { Operand operand = decode_operand(addr_mode);
byte value = read_operand(operand);
} add_with_carry(~value);
void op_SHY(addr_mode_t addr_mode) { cpu_add_cycles(get_cycle_count(operand, addr_mode));
}
void op_SEC(AddressingMode addr_mode) {
cpu_set_flag(1, CPU_STATUS_CARRY_MASK);
cpu_add_cycles(2);
} }
void op_SLO(addr_mode_t addr_mode) { void op_SED(AddressingMode addr_mode) {
cpu_set_flag(1, CPU_STATUS_DECIMAL_MASK);
cpu_add_cycles(2);
}
void op_SEI(AddressingMode addr_mode) {
cpu_set_flag(1, CPU_STATUS_INTERRUPT_DISABLE_MASK);
cpu_add_cycles(2);
} }
void op_RLA(addr_mode_t addr_mode) { void op_SHX(AddressingMode addr_mode) {
assert(false);
}
void op_SHY(AddressingMode addr_mode) {
assert(false);
} }
void op_SRE(addr_mode_t addr_mode) { void op_SLO(AddressingMode addr_mode) {
assert(false);
}
void op_SRE(AddressingMode addr_mode) {
assert(false);
} }
void op_RRA(addr_mode_t addr_mode) { void op_STA(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte acc = cpu_get_registers()->accumulator;
assert(operand.type == OPERAND_TYPE_ADDRESS);
} cpu_push_byte(acc, operand.value);
void op_SAX(addr_mode_t addr_mode) { operand.is_page_crossing = true;
cpu_add_cycles(get_cycle_count(operand, addr_mode));
}
void op_STP(AddressingMode addr_mode) {
assert(false);
} }
void op_LAX(addr_mode_t addr_mode) { void op_STX(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte x = cpu_get_registers()->x;
assert(operand.type == OPERAND_TYPE_ADDRESS);
cpu_push_byte(x, operand.value);
cpu_add_cycles(get_cycle_count(operand, addr_mode));
} }
void op_DCP(addr_mode_t addr_mode) { void op_STY(AddressingMode addr_mode) {
Operand operand = decode_operand(addr_mode);
byte y = cpu_get_registers()->y;
assert(operand.type == OPERAND_TYPE_ADDRESS);
} cpu_push_byte(y, operand.value);
void op_ISC(addr_mode_t addr_mode) { cpu_add_cycles(get_cycle_count(operand, addr_mode));
}
void op_TAS(AddressingMode addr_mode) {
assert(false);
} }
void op_ANC(addr_mode_t addr_mode) { void op_TAX(AddressingMode addr_mode) {
byte acc = cpu_get_registers()->accumulator;
cpu_get_registers()->x = acc;
set_acl_flags(acc);
cpu_add_cycles(2);
} }
void op_ALR(addr_mode_t addr_mode) { void op_TAY(AddressingMode addr_mode) {
byte acc = cpu_get_registers()->accumulator;
cpu_get_registers()->y = acc;
set_acl_flags(acc);
cpu_add_cycles(2);
} }
void op_ARR(addr_mode_t addr_mode) { void op_TSX(AddressingMode addr_mode) {
byte value = cpu_stack_pop();
cpu_get_registers()->x = value;
set_acl_flags(value);
cpu_add_cycles(2);
} }
void op_XAA(addr_mode_t addr_mode) { void op_TXA(AddressingMode addr_mode) {
byte x = cpu_get_registers()->x;
cpu_get_registers()->accumulator = x;
set_acl_flags(x);
cpu_add_cycles(2);
} }
void op_AXS(addr_mode_t addr_mode) { void op_TXS(AddressingMode addr_mode) {
byte x = cpu_get_registers()->x;
cpu_stack_push(x);
cpu_add_cycles(2);
} }
void op_AHX(addr_mode_t addr_mode) { void op_TYA(AddressingMode addr_mode) {
byte y = cpu_get_registers()->y;
cpu_get_registers()->accumulator = y;
set_acl_flags(y);
cpu_add_cycles(2);
} }
void op_TAS(addr_mode_t addr_mode) { void op_XAA(AddressingMode addr_mode) {
assert(false);
} }
void process_op_code(int op) { void process_op_code(int op) {
@ -509,20 +960,20 @@ void process_op_code(int op) {
IS_OP_CODE_MODE(AXS, 0xcb, IMMEDIATE) IS_OP_CODE_MODE(AXS, 0xcb, IMMEDIATE)
IS_OP_CODE_MODE(SBC, 0xeb, IMMEDIATE) IS_OP_CODE_MODE(SBC, 0xeb, IMMEDIATE)
IS_OP_CODE_MODE(SAX, 0x83, INDEXED_INDIRECT) IS_OP_CODE_MODE(SAX, 0x83, INDIRECT_X)
IS_OP_CODE_MODE(SAX, 0x87, ZERO_PAGE) IS_OP_CODE_MODE(SAX, 0x87, ZERO_PAGE)
IS_OP_CODE_MODE(XAA, 0x8b, IMMEDIATE) IS_OP_CODE_MODE(XAA, 0x8b, IMMEDIATE)
IS_OP_CODE_MODE(SAX, 0x8f, ABSOLUTE) IS_OP_CODE_MODE(SAX, 0x8f, ABSOLUTE)
IS_OP_CODE_MODE(AHX, 0x93, INDIRECT_INDEXED) IS_OP_CODE_MODE(AHX, 0x93, INDIRECT_Y)
IS_OP_CODE_MODE(SAX, 0x97, ZERO_PAGE_INDEXED_Y) IS_OP_CODE_MODE(SAX, 0x97, ZERO_PAGE_INDEXED_Y)
IS_OP_CODE_MODE(TAS, 0x9b, ABSOLUTE_INDEXED_Y) IS_OP_CODE_MODE(TAS, 0x9b, ABSOLUTE_INDEXED_Y)
IS_OP_CODE_MODE(AHX, 0x9f, ABSOLUTE_INDEXED_Y) IS_OP_CODE_MODE(AHX, 0x9f, ABSOLUTE_INDEXED_Y)
IS_OP_CODE_MODE(LAX, 0xa3, INDEXED_INDIRECT) IS_OP_CODE_MODE(LAX, 0xa3, INDIRECT_X)
IS_OP_CODE_MODE(LAX, 0xa7, ZERO_PAGE) IS_OP_CODE_MODE(LAX, 0xa7, ZERO_PAGE)
IS_OP_CODE_MODE(LAX, 0xab, IMMEDIATE) IS_OP_CODE_MODE(LAX, 0xab, IMMEDIATE)
IS_OP_CODE_MODE(LAX, 0xaf, ABSOLUTE) IS_OP_CODE_MODE(LAX, 0xaf, ABSOLUTE)
IS_OP_CODE_MODE(LAX, 0xb3, INDIRECT_INDEXED) IS_OP_CODE_MODE(LAX, 0xb3, INDIRECT_Y)
IS_OP_CODE_MODE(LAX, 0xb7, ZERO_PAGE_INDEXED_Y) IS_OP_CODE_MODE(LAX, 0xb7, ZERO_PAGE_INDEXED_Y)
IS_OP_CODE_MODE(LAX, 0xbb, ABSOLUTE_INDEXED_Y) IS_OP_CODE_MODE(LAX, 0xbb, ABSOLUTE_INDEXED_Y)
IS_OP_CODE_MODE(LAX, 0xbf, ABSOLUTE_INDEXED_Y) IS_OP_CODE_MODE(LAX, 0xbf, ABSOLUTE_INDEXED_Y)

View File

@ -22,13 +22,13 @@ typedef enum {
ADDR_MODE_ACCUMULATOR, // A ADDR_MODE_ACCUMULATOR, // A
ADDR_MODE_IMMEDIATE, // #i ADDR_MODE_IMMEDIATE, // #i
ADDR_MODE_IMPLICIT, // Imp ADDR_MODE_IMPLICIT, // Imp
ADDR_MODE_INDEXED_INDIRECT, // (d,x) ADDR_MODE_INDIRECT_X, // (d,x)
ADDR_MODE_INDIRECT_JUMP, // ADDR_MODE_INDIRECT_JUMP, //
ADDR_MODE_INDIRECT_INDEXED, // (d),y ADDR_MODE_INDIRECT_Y, // (d),y
ADDR_MODE_RELATIVE, // label ADDR_MODE_RELATIVE, // label
ADDR_MODE_ZERO_PAGE, // d ADDR_MODE_ZERO_PAGE, // d
ADDR_MODE_ZERO_PAGE_INDEXED_X, // d,x ADDR_MODE_ZERO_PAGE_INDEXED_X, // d,x
ADDR_MODE_ZERO_PAGE_INDEXED_Y, // d,y ADDR_MODE_ZERO_PAGE_INDEXED_Y, // d,y
} addr_mode_t; } AddressingMode;
#endif #endif

22
cpu/ram.c Normal file
View File

@ -0,0 +1,22 @@
//
// Created by william on 30/09/23.
//
#include "ram.h"
#include "../include/cpu.h"
byte ram[MEM_RAM_AMOUNT];
void ram_set_byte(address addr, byte byte) {
ram[addr] = byte;
}
byte ram_get_byte(address addr) {
return ram[addr];
}
word ram_get_word(address addr) {
word word = ram_get_byte(addr);
word += ram_get_byte(addr + 1) << 8; // Little endian
return word;
}

20
cpu/ram.h Normal file
View File

@ -0,0 +1,20 @@
//
// Created by william on 30/09/23.
//
#ifndef NESEMULATOR_RAM_H
#define NESEMULATOR_RAM_H
// The 6502 CPU has 2 KiB of RAM
#define MEM_RAM_AMOUNT 2048
typedef unsigned short address;
void init_ram();
void clean_ram();
void ram_set_byte(address addr, byte byte);
byte ram_get_byte(address addr);
word ram_get_word(address addr);
#endif //NESEMULATOR_RAM_H

33
include/cpu.h Normal file
View File

@ -0,0 +1,33 @@
/*
* =====================================================================================
*
* Filename: cpu.h
*
* Description: 6502 CPU emulator headers
*
* Version: 1.0
* Created: 2023-09-21 10:12:33 PM
* Revision: none
* Compiler: gcc
*
* Author: William Nolin,
* Organization:
*
* =====================================================================================
*/
#ifndef NESEMULATOR_CPU_H
#define NESEMULATOR_CPU_H
typedef unsigned char byte;
typedef unsigned short address;
typedef unsigned short word;
/**
* @brief Set clock
*/
void cpu_step_to(int cycle);
void cpu_add_cycles(int count);
#endif

View File

@ -1,51 +0,0 @@
/*
* =====================================================================================
*
* Filename: cpu.h
*
* Description: 6502 CPU emulator headers
*
* Version: 1.0
* Created: 2023-09-21 10:12:33 PM
* Revision: none
* Compiler: gcc
*
* Author: William Nolin,
* Organization:
*
* =====================================================================================
*/
#ifndef EMU_CPU_H
#define EMU_CPU_H
// Reference: https://www.nesdev.org/wiki/Status_flags
#define CPU_STATUS_CARRY_MASK = 0x01;
#define CPU_STATUS_ZERO_MASK = 0x02;
#define CPU_STATUS_INTERRUPT_DISABLE_MASK = 0x04;
#define CPU_STATUS_DECIMAL_MASK = 0x08;
#define CPU_STATUS_B_MASK = 0x10;
#define CPU_STATUS_I_MASK = 0x20;
#define CPU_STATUS_OVERFLOW_MASK = 0x40;
#define CPU_STATUS_NEGATIVE_MASK = 0x80;
// Reference: https://www.nesdev.org/obelisk-6502-guide/registers.html
typedef struct {
unsigned short program_counter;
unsigned char stack_pointer;
unsigned char accumulator;
unsigned char x;
unsigned char y;
unsigned char status;
} cpu_registers_t;
/**
* @brief Set clock
*/
void cpu_step_to(int cycle);
void cpu_add_cycles(int count);
cpu_registers_t* cpu_get_registers();
#endif

View File

@ -1,8 +0,0 @@
//
// Created by william on 30/09/23.
//
#ifndef NESEMULATOR_MEM_H
#define NESEMULATOR_MEM_H
#endif //NESEMULATOR_MEM_H

20
include/mapper.h Normal file
View File

@ -0,0 +1,20 @@
//
// Created by william on 10/15/23.
//
#ifndef NESEMULATOR_MAPPER_H
#define NESEMULATOR_MAPPER_H
#include "../include/cpu.h"
typedef struct {
address (*redirect_addr)(unsigned short);
} Mapper;
enum MapperType {
MAPPER_TYPE_SIMPLE
};
Mapper get_mapper(enum MapperType type);
#endif //NESEMULATOR_MAPPER_H

10
main.c
View File

@ -18,12 +18,16 @@
#include <log.h> #include <log.h>
#include <stdlib.h> #include <stdlib.h>
#include "config.h" #include "include/cpu.h"
#include "include/cpu/cpu.h" #include "ram.h"
int main() { int main() {
cpu_step_to(0); init_ram();
ram_set_byte(0x10, 0xf);
ram_get_byte(0xffff);
clean_ram();
return -1; return -1;
} }

6
mappers/CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
add_library(Mappers
simple_mapper.c
mappers.c)
find_package(log.c)
target_link_libraries(Mappers log.c::log.c)

19
mappers/mappers.c Normal file
View File

@ -0,0 +1,19 @@
//
// Created by william on 10/15/23.
//
#include <log.h>
#include <stdlib.h>
#include "../include/mapper.h"
#include "simple_mapper.c"
Mapper get_mapper(enum MapperType type) {
switch (type) {
case MAPPER_TYPE_SIMPLE:
return get_simple_mapper();
default:
log_error("Unsupported mapper %u", type);
exit(-1);
}
}

11
mappers/simple_mapper.c Normal file
View File

@ -0,0 +1,11 @@
#include "../include/mapper.h"
address redirect_addr(address addr) {
return addr;
}
Mapper get_simple_mapper() {
Mapper mapper;
mapper.redirect_addr = &redirect_addr;
return mapper;
}