This commit is contained in:
william 2024-01-06 14:27:09 -05:00
parent 9c7b882f46
commit cebee66076
52 changed files with 1784 additions and 620 deletions

View File

@ -14,6 +14,7 @@
<config projectName="NESEmulator" targetName="NESEmulator" /> <config projectName="NESEmulator" targetName="NESEmulator" />
<config projectName="NESEmulator" targetName="ROM" /> <config projectName="NESEmulator" targetName="ROM" />
<config projectName="NESEmulator" targetName="Mappers" /> <config projectName="NESEmulator" targetName="Mappers" />
<config projectName="NESEmulator" targetName="PPU" />
<config projectName="NESEmulator" targetName="CPU" /> <config projectName="NESEmulator" targetName="CPU" />
</generated> </generated>
</component> </component>
@ -23,12 +24,59 @@
</configurations> </configurations>
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="0c3b231e-0637-4ac1-8964-c60fc9e9e691" name="Changes" comment="CPU"> <list default="true" id="0c3b231e-0637-4ac1-8964-c60fc9e9e691" name="Changes" comment="Added logging for operand decoding">
<change afterPath="$PROJECT_DIR$/include/ppu.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/include/system.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/include/types.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ppu/CMakeLists.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ppu/ppu.c" afterDir="false" />
<change afterPath="$PROJECT_DIR$/system.c" afterDir="false" />
<change afterPath="$PROJECT_DIR$/test_roms/nestest.fdb" afterDir="false" />
<change afterPath="$PROJECT_DIR$/test_roms/nestest.nes" afterDir="false" />
<change afterPath="$PROJECT_DIR$/test_roms/smb.fdb" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/conandata.yml" beforeDir="false" afterPath="$PROJECT_DIR$/conandata.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/CMakeLists.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/cpu.c" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/cpu.c" afterDir="false" /> <change beforePath="$PROJECT_DIR$/cpu/cpu.c" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/cpu.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/cpu.h" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/cpu.h" afterDir="false" /> <change beforePath="$PROJECT_DIR$/cpu/cpu.h" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/cpu.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/memory.c" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/memory.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/memory.h" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/memory.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/op.c" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/op.c" afterDir="false" /> <change beforePath="$PROJECT_DIR$/cpu/op.c" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/op.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/op.h" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/op.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/ram.c" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cpu/ram.h" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/include/cpu.h" beforeDir="false" afterPath="$PROJECT_DIR$/include/cpu.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/include/mapper.h" beforeDir="false" afterPath="$PROJECT_DIR$/include/mapper.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/include/rom.h" beforeDir="false" afterPath="$PROJECT_DIR$/include/rom.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/main.c" beforeDir="false" afterPath="$PROJECT_DIR$/main.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/mappers/simple_mapper.c" beforeDir="false" afterPath="$PROJECT_DIR$/mappers/simple_mapper.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/rom/ines.c" beforeDir="false" afterPath="$PROJECT_DIR$/rom/ines.c" afterDir="false" /> <change beforePath="$PROJECT_DIR$/rom/ines.c" beforeDir="false" afterPath="$PROJECT_DIR$/rom/ines.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/rom/rom.c" beforeDir="false" afterPath="$PROJECT_DIR$/rom/rom.c" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/readme.txt" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/readme.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/ascii_1.chr" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/ascii_1.chr" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/ascii_2.chr" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/ascii_2.chr" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/ascii_3.chr" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/ascii_3.chr" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/build_rom.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/build_rom.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/colors.inc" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/colors.inc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/console.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/console.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/crc.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/crc.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/delay.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/delay.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/devcart.bin" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/devcart.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/macros.inc" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/macros.inc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/neshw.inc" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/neshw.inc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/ppu.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/ppu.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/print.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/print.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/shell.inc" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/shell.inc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/shell.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/shell.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/testing.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/testing.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/text_out.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/common/text_out.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/readme.txt" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/readme.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/test_cpu_exec_space_apu.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/test_cpu_exec_space_apu.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/source/test_cpu_exec_space_ppuio.s" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/source/test_cpu_exec_space_ppuio.s" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/test_cpu_exec_space_apu.nes" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/test_cpu_exec_space_apu.nes" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/cpu_exec_space/test_cpu_exec_space_ppuio.nes" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/cpu_exec_space/test_cpu_exec_space_ppuio.nes" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/smb.nes" beforeDir="false" afterPath="$PROJECT_DIR$/test_roms/smb.nes" 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" />
@ -49,6 +97,9 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="CurrentFile" />
</component>
<component name="ProjectApplicationVersion"> <component name="ProjectApplicationVersion">
<option name="ide" value="CLion" /> <option name="ide" value="CLion" />
<option name="majorVersion" value="2023" /> <option name="majorVersion" value="2023" />
@ -92,17 +143,148 @@
}</component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/rom" />
<recent name="$PROJECT_DIR$/include" /> <recent name="$PROJECT_DIR$/include" />
<recent name="$PROJECT_DIR$/rom" />
<recent name="$PROJECT_DIR$/cpu" /> <recent name="$PROJECT_DIR$/cpu" />
<recent name="$PROJECT_DIR$/include/cpu" /> <recent name="$PROJECT_DIR$/include/cpu" />
<recent name="$PROJECT_DIR$" /> <recent name="$PROJECT_DIR$" />
</key> </key>
</component> </component>
<component name="RunManager" selected="CMake Application.NESEmulator"> <component name="RunManager" selected="CMake Application.NESEmulator">
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
<module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" />
<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 name="NESEmulator" type="CMakeListConfigurationType" factoryName="CMakeListConfigurationFactory" temporary="true"> <configuration name="NESEmulator" type="CMakeListConfigurationType" factoryName="CMakeListConfigurationFactory" temporary="true">
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration name="NESEmulator" type="CMakeListConfigurationType" factoryName="CMakeListConfigurationFactory" temporary="true">
<method v="2" />
</configuration>
<configuration name="CPU" 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="CPU" CONFIG_NAME="Debug">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="CPU" 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="CPU" CONFIG_NAME="Debug"> <configuration name="CPU" 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="CPU" CONFIG_NAME="Debug">
<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" />
@ -113,11 +295,31 @@
<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 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">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="PPU" 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="PPU" CONFIG_NAME="Debug">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="ROM" 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="ROM" CONFIG_NAME="Debug">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="ROM" 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="ROM" CONFIG_NAME="Debug"> <configuration name="ROM" 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="ROM" CONFIG_NAME="Debug">
<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" />
@ -250,6 +452,7 @@
<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 Application.Mappers" />
<item itemvalue="CMake Application.PPU" />
<item itemvalue="CMake Application.ROM" /> <item itemvalue="CMake Application.ROM" />
<item itemvalue="CMake Debug.NESEmulator" /> <item itemvalue="CMake Debug.NESEmulator" />
</list> </list>
@ -283,6 +486,12 @@
<workItem from="1701463001105" duration="2058000" /> <workItem from="1701463001105" duration="2058000" />
<workItem from="1701558929054" duration="14565000" /> <workItem from="1701558929054" duration="14565000" />
<workItem from="1703367277258" duration="1000" /> <workItem from="1703367277258" duration="1000" />
<workItem from="1703810207562" duration="27667000" />
<workItem from="1703912983973" duration="23543000" />
<workItem from="1704429138262" duration="2629000" />
<workItem from="1704484992884" duration="2000" />
<workItem from="1704501418104" duration="8204000" />
<workItem from="1704569084127" duration="3000" />
</task> </task>
<task id="LOCAL-00001" summary="Cpu opcodes implementation"> <task id="LOCAL-00001" summary="Cpu opcodes implementation">
<option name="closed" value="true" /> <option name="closed" value="true" />
@ -300,7 +509,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1701463073548</updated> <updated>1701463073548</updated>
</task> </task>
<option name="localTasksCounter" value="3" /> <task id="LOCAL-00003" summary="Added logging for operand decoding">
<option name="closed" value="true" />
<created>1703369431911</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1703369431911</updated>
</task>
<option name="localTasksCounter" value="4" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@ -316,6 +533,18 @@
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" /> <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="Cpu opcodes implementation" /> <MESSAGE value="Cpu opcodes implementation" />
<MESSAGE value="Gitignore" /> <MESSAGE value="Gitignore" />
<option name="LAST_COMMIT_MESSAGE" value="Gitignore" /> <MESSAGE value="Added logging for operand decoding" />
<option name="LAST_COMMIT_MESSAGE" value="Added logging for operand decoding" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="com.jetbrains.cidr.execution.debugger.OCBreakpointType">
<url>file://$PROJECT_DIR$/system.c</url>
<line>51</line>
<option name="timeStamp" value="8" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component> </component>
</project> </project>

View File

@ -2,19 +2,24 @@ cmake_minimum_required(VERSION 3.10)
project(NESEmulator VERSION 0.1) project(NESEmulator VERSION 0.1)
add_subdirectory(cpu) add_subdirectory(cpu)
add_subdirectory(ppu)
add_subdirectory(mappers) add_subdirectory(mappers)
add_subdirectory(rom) add_subdirectory(rom)
list(APPEND EXTRA_INCLUDES list(APPEND EXTRA_INCLUDES
"${PROJECT_SOURCE_DIR}/cpu" "${PROJECT_SOURCE_DIR}/cpu"
"${PROJECT_SOURCE_DIR}/ppu"
"${PROJECT_SOURCE_DIR}/mappers" "${PROJECT_SOURCE_DIR}/mappers"
"${PROJECT_SOURCE_DIR}/rom") "${PROJECT_SOURCE_DIR}/rom")
add_executable(NESEmulator main.c) add_executable(NESEmulator main.c
system.c
include/system.h
include/types.h)
find_package(log.c) find_package(log.c)
target_link_libraries(NESEmulator CPU Mappers ROM log.c::log.c) target_link_libraries(NESEmulator CPU PPU Mappers ROM 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

@ -2,4 +2,5 @@
# To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements # To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements
requirements: requirements:
- "libcheck/0.15.2"
- "log.c/cci.20200620" - "log.c/cci.20200620"

View File

@ -1,7 +1,6 @@
add_library(CPU add_library(CPU
cpu.c cpu.c
op.c op.c
ram.c
memory.c memory.c
cpu.h) cpu.h)

152
cpu/cpu.c
View File

@ -1,4 +1,6 @@
#include <log.h> #include <log.h>
#include <assert.h>
#include <string.h>
#include "../include/cpu.h" #include "../include/cpu.h"
#include "cpu.h" #include "cpu.h"
#include "memory.h" #include "memory.h"
@ -22,104 +24,120 @@
* ===================================================================================== * =====================================================================================
*/ */
CpuRegisters registers; void cpu_init(CPU *cpu) {
Mapper mapper; cpu->program_counter = 0x8000;
unsigned int wait_cycle_count = 0; cpu->stack_pointer = 0xfd;
cpu->accumulator = 0x00;
void cpu_init() { cpu->x = 0x00;
registers.program_counter = 0xc000; cpu->y = 0x00;
registers.stack_pointer = 0xff; cpu->status = 0x04;
registers.accumulator = 0x00; cpu->oam_dma_triggered = false;
registers.x = 0x00;
registers.y = 0x00;
registers.status = 0x00;
mapper = get_mapper(MAPPER_TYPE_SIMPLE);
} }
void cpu_step() { void print_registers(CPU cpu, byte op, unsigned long cycle_count) {
int i = 0; log_debug("%#02x %#02x %s \t A:%#02x X:%#02x Y:%#02x F:%#02x SP:%#02x \t [%d]",
while (i < 10) { cpu.program_counter,
byte op = cpu_get_next_byte(); op,
process_op_code(op); get_op_code_name(op),
i += 1; cpu.accumulator,
cpu.x,
cpu.y,
cpu.status,
cpu.stack_pointer,
cycle_count);
}
void oam_dma_upload(System *system) {
byte page_high_addr = *system->ppu.oam_dma_register;
address page_addr = ((address) page_high_addr) << 8;
byte n = 0xff;
byte *ram_source = &system->ram[page_addr];
byte *oam_destination = system->ppu.oam;
memcpy(oam_destination, ram_source, n);
log_debug("OAM DMA %#04x", page_addr);
cpu_add_cycles(system, 513); // TODO
}
void cpu_cycle(System *system) {
if (system->cpu.oam_dma_triggered) {
oam_dma_upload(system);
system->cpu.oam_dma_triggered = false;
return;
} }
CPU registers = system->cpu;
byte op = cpu_get_next_byte(system);
print_registers(registers, op, system->cycle_count);
process_op_code(system, op);
} }
void cpu_add_cycles(unsigned int cycle_count) { void cpu_add_cycles(System *system, unsigned int cycle_count) {
wait_cycle_count += cycle_count; system->cycle_count += cycle_count;
log_trace("Waiting for %d cycles", cycle_count);
} }
// === Registers === // === Registers ===
CpuRegisters *cpu_get_registers() { bool cpu_get_flag(System *system, byte mask) {
return &registers; return system->cpu.status & mask;
} }
byte cpu_get_flag(byte mask) { void cpu_set_flag(System *system, byte mask, bool set) {
return registers.status & mask;
}
void cpu_set_flag(bool set, byte mask) {
if (set) { if (set) {
registers.status |= mask; system->cpu.status |= mask;
} else { } else {
registers.status &= ~mask; system->cpu.status &= ~mask;
} }
} }
// === Memory === byte cpu_get_next_byte(System *system) {
byte cpu_get_next_byte() { byte next_byte = mem_get_byte(system, system->cpu.program_counter);
byte next_byte = mem_get_byte(&mapper, registers.program_counter); system->cpu.program_counter++;
registers.program_counter++;
return next_byte; return next_byte;
} }
word cpu_get_next_word() { word cpu_get_next_word(System *system) {
word next_word = mem_get_word(&mapper, registers.program_counter); word next_word = mem_get_word(system, system->cpu.program_counter);
registers.program_counter += 2; system->cpu.program_counter += 2;
return next_word; return next_word;
} }
byte cpu_peek_byte(address addr) { void cpu_stack_push(System *system, byte value) {
return mem_get_byte(&mapper, addr); assert(system->cpu.stack_pointer > 0);
address mem_addr = CPU_STACK_ADDR | system->cpu.stack_pointer;
mem_set_byte(system, mem_addr, value);
system->cpu.stack_pointer--;
} }
word cpu_peek_word(address addr) { byte cpu_stack_pop(System *system) {
return mem_get_word(&mapper, addr); assert(system->cpu.stack_pointer < 0xff);
}
void cpu_push_byte(byte value, address addr) { system->cpu.stack_pointer++;
mem_set_byte(&mapper, addr, value); address mem_addr = CPU_STACK_ADDR | system->cpu.stack_pointer;
} byte value = mem_get_byte(system, mem_addr);
// === 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; return value;
} }
void cpu_stack_push_context() { void cpu_stack_push_context(System *system) {
cpu_stack_push(registers.program_counter >> 8); cpu_stack_push(system, system->cpu.program_counter >> 8);
cpu_stack_push(registers.program_counter & 0xff); cpu_stack_push(system, system->cpu.program_counter & 0xff);
cpu_stack_push(registers.status); cpu_stack_push(system, system->cpu.status);
} }
void cpu_stack_pop_context() { void cpu_stack_pop_context(System *system) {
registers.status = cpu_stack_pop(); byte value = cpu_stack_pop(system);
value &= 0xef; // The B mask cannot be set as it is a CPU signal
value |= 0x20; // This value is always set
system->cpu.status = value;
byte lo = cpu_stack_pop(); byte lo = cpu_stack_pop(system);
address pc = cpu_stack_pop() << 8; address pc = cpu_stack_pop(system) << 8;
pc += lo; pc += lo;
registers.program_counter = pc; system->cpu.program_counter = pc;
} }
char *operand_name(Operand *operand) { char *operand_name(Operand *operand) {

102
cpu/cpu.h
View File

@ -14,22 +14,11 @@
#define CPU_STATUS_INTERRUPT_DISABLE_MASK 0x04 #define CPU_STATUS_INTERRUPT_DISABLE_MASK 0x04
#define CPU_STATUS_DECIMAL_MASK 0x08 #define CPU_STATUS_DECIMAL_MASK 0x08
#define CPU_STATUS_B_MASK 0x10 #define CPU_STATUS_B_MASK 0x10
#define CPU_STATUS_I_MASK 0x20
#define CPU_STATUS_OVERFLOW_MASK 0x40 #define CPU_STATUS_OVERFLOW_MASK 0x40
#define CPU_STATUS_NEGATIVE_MASK 0x80 #define CPU_STATUS_NEGATIVE_MASK 0x80
#define CPU_STACK_ADDR 0x0100 #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 { enum OperandType {
OPERAND_TYPE_ACCUMULATOR, OPERAND_TYPE_ACCUMULATOR,
OPERAND_TYPE_IMMEDIATE, OPERAND_TYPE_IMMEDIATE,
@ -42,24 +31,87 @@ typedef struct {
bool is_page_crossing; bool is_page_crossing;
} Operand; } Operand;
char* operand_name(Operand *operand); /**
* Gets the name of the type of an operand, for logging.
*
* @param operand The operand
* @return The name of the operand's type.
*/
char *operand_name(Operand *operand);
CpuRegisters* cpu_get_registers(); /**
byte cpu_get_flag(byte mask); * Gets a flag from the CPU registers.
void cpu_set_flag(bool set, byte mask); *
* @param system The system
* @param mask The flag mask
* @return The value of the flag.
*/
bool cpu_get_flag(System *system, byte mask);
byte cpu_get_next_byte(); /**
word cpu_get_next_word(); * Sets a flag in the CPU registers.
*
* @param system The system
* @param mask The flag mask
* @param set If the flag is set or not
*/
void cpu_set_flag(System *system, byte mask, bool set);
byte cpu_peek_byte(address addr); /**
word cpu_peek_word(address addr); * Gets the next byte in the program.
void cpu_push_byte(byte value, address addr); * Increases the system program counter.
*
* @param system The system
* @return The value of the next byte.
*/
byte cpu_get_next_byte(System *system);
void cpu_stack_push(byte value); /**
void cpu_stack_push_context(); * Gets the next word in the program.
byte cpu_stack_pop(); * Increases the system program counter by 2.
void cpu_stack_pop_context(); *
* @param system The system
* @return The value of the next word.
*/
word cpu_get_next_word(System *system);
void cpu_add_cycles(unsigned int cycle_count); /**
* Pushes a byte in to the stack.
*
* @param system The system
* @param value The value to push to the stack
*/
void cpu_stack_push(System *system, byte value);
/**
* Pushes the execution context to the stack.
* This includes the program counter and the CPU status.
*
* @param system The system
*/
void cpu_stack_push_context(System *system);
/**
* Pops a byte from the stack.
*
* @param system The system
* @return The value of the byte
*/
byte cpu_stack_pop(System *system);
/**
* Pops an execution context from the stack and overwrite the current context.
* This includes the program counter and the CPU status.
*
* @param system The system
*/
void cpu_stack_pop_context(System *system);
/**
* Adds wait cycles to the CPU.
*
* @param cycle_count The number of cycle to wait
*/
void cpu_add_cycles(System *system, unsigned int cycle_count);
#endif //CPU_CPU_H #endif //CPU_CPU_H

View File

@ -2,35 +2,76 @@
// Created by william on 10/15/23. // Created by william on 10/15/23.
// //
#include <assert.h>
#include <log.h>
#include "memory.h" #include "memory.h"
#include "ram.h"
#include "../include/rom.h" #include "../include/rom.h"
byte mem_get_byte(Mapper *mapper, address addr) { #define RAM_MAX_ADDR 0x2000
address redirected_addr = mapper->redirect_addr(addr); #define RAM_BANK_SIZE 0x800
#define PPU_MAX_ADDR 0x4000
#define PPU_BANK_SIZE 0x8
#define APU_MAX_ADDR 0x4020
#define MAX_ADDR 0xffff
if (redirected_addr < 0x0800) { byte mem_get_byte(System *system, address addr) {
return ram_get_byte(redirected_addr); assert(addr <= MAX_ADDR);
} else if (redirected_addr >= 0x4020) {
return rom_prg_get_byte(redirected_addr - 0x4020); if (addr >= RAM_MAX_ADDR && addr < PPU_MAX_ADDR) {
byte reg = (addr - RAM_MAX_ADDR) % PPU_BANK_SIZE;
ppu_read_register(&system->ppu, reg);
return system->ppu.registers[reg];
} }
return 0; if (addr >= PPU_MAX_ADDR && addr < APU_MAX_ADDR) {
} byte apu_addr = addr - PPU_MAX_ADDR;
return system->apu_registers[apu_addr];
word mem_get_word(Mapper *mapper, address addr) {
address redirected_addr = mapper->redirect_addr(addr);
if (redirected_addr < 0x0800) {
return ram_get_word(redirected_addr);
} else if (redirected_addr >= 0x4020) {
return rom_prg_get_word(redirected_addr - 0x4020);
} }
return 0; return system->ram[addr];
} }
void mem_set_byte(Mapper *mapper, address addr, byte byte) { word mem_get_word(System *system, address addr) {
address redirected_addr = mapper->redirect_addr(addr); assert(addr < MAX_ADDR);
ram_set_byte(redirected_addr, byte);
if (addr >= RAM_MAX_ADDR && addr < APU_MAX_ADDR) {
assert(false);
}
word word = system->ram[addr];
word += system->ram[addr + 1] << 8; // Little endian
return word;
}
void mem_set_byte(System *system, address addr, byte byte) {
assert(addr < MAX_ADDR);
log_trace("Writing '%02x' to address 0x%04x", byte, addr);
if (addr < RAM_MAX_ADDR) {
address init_ram_addr = addr % RAM_BANK_SIZE;
// The value must also be cloned in the three mirrors
for (int i = 0; i < 4; i++) {
address ram_addr = init_ram_addr + RAM_BANK_SIZE * i;
system->ram[ram_addr] = byte;
}
} else if (addr < PPU_MAX_ADDR) {
address reg_addr = (addr - RAM_MAX_ADDR) % PPU_BANK_SIZE;
int bank_count = (PPU_MAX_ADDR - RAM_MAX_ADDR) / PPU_BANK_SIZE;
for (int i = 0; i < bank_count; i++) {
address ram_addr = reg_addr + PPU_BANK_SIZE * i;
system->ppu.registers[ram_addr] = byte;
}
ppu_write_register(&system->ppu, reg_addr);
} else {
system->ram[addr] = byte;
if (addr == PPU_REGISTER_OAM_DMA_ADDR) {
// Writing to this address triggers an upload to the PPU memory
system->cpu.oam_dma_triggered = true;
}
}
} }

View File

@ -3,12 +3,36 @@
// //
#include "../include/mapper.h" #include "../include/mapper.h"
#include "../include/system.h"
#ifndef NESEMULATOR_MEMORY_H #ifndef NESEMULATOR_MEMORY_H
#define NESEMULATOR_MEMORY_H #define NESEMULATOR_MEMORY_H
byte mem_get_byte(Mapper *mapper, address addr); /**
word mem_get_word(Mapper *mapper, address addr); * Gets a byte from a system's memory.
void mem_set_byte(Mapper *mapper, address addr, byte byte); *
* @param system A reference to the system
* @param addr The address to get
* @return The value of the byte at the given address.
*/
byte mem_get_byte(System *system, address addr);
/**
* Gets a word from a system's memory.
*
* @param system A reference to the system
* @param addr The address to get
* @return The value of the word at the given address.
*/
word mem_get_word(System *system, address addr);
/**
* Sets a byte in a system's memory.
*
* @param system A reference to the system
* @param addr The address to set
* @param value The value to set
*/
void mem_set_byte(System *system, address addr, byte value);
#endif //NESEMULATOR_MEMORY_H #endif //NESEMULATOR_MEMORY_H

1288
cpu/op.c

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ typedef enum {
ADDR_MODE_ZERO_PAGE_INDEXED_Y, // d,y ADDR_MODE_ZERO_PAGE_INDEXED_Y, // d,y
} AddressingMode; } AddressingMode;
void process_op_code(byte op); void process_op_code(System *system, byte op);
char* get_op_code_name(byte op);
#endif #endif

View File

@ -1,22 +0,0 @@
//
// 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;
}

View File

@ -1,19 +0,0 @@
//
// Created by william on 30/09/23.
//
#include "cpu.h"
#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 ram_set_byte(address addr, byte byte);
byte ram_get_byte(address addr);
word ram_get_word(address addr);
#endif //NESEMULATOR_RAM_H

View File

@ -16,14 +16,14 @@
* ===================================================================================== * =====================================================================================
*/ */
#include "types.h"
#include "system.h"
#ifndef NESEMULATOR_CPU_H #ifndef NESEMULATOR_CPU_H
#define NESEMULATOR_CPU_H #define NESEMULATOR_CPU_H
typedef unsigned char byte; void cpu_init(CPU *cpu);
typedef unsigned short address;
typedef unsigned short word;
void cpu_init(); void cpu_cycle(System *system);
void cpu_step();
#endif #endif

View File

@ -2,13 +2,15 @@
// Created by william on 10/15/23. // Created by william on 10/15/23.
// //
#include "types.h"
#ifndef NESEMULATOR_MAPPER_H #ifndef NESEMULATOR_MAPPER_H
#define NESEMULATOR_MAPPER_H #define NESEMULATOR_MAPPER_H
#include "../include/cpu.h" typedef struct mapper {
address prg_rom_start_addr;
typedef struct { void (*post_prg_load)(ram, unsigned int);
address (*redirect_addr)(unsigned short);
} Mapper; } Mapper;
enum MapperType { enum MapperType {

96
include/ppu.h Normal file
View File

@ -0,0 +1,96 @@
//
// Created by william on 12/30/23.
//
#include <stdbool.h>
#include <stddef.h>
#include "types.h"
#ifndef NESEMULATOR_PPU_H
#define NESEMULATOR_PPU_H
#define PPU_REGISTER_SIZE 0x8
#define PPU_VRAM_SIZE 0x4000
#define PPU_OAM_SIZE 0xff
#define PPU_REGISTER_CTRL 0x00
#define PPU_REGISTER_MASK 0x01
#define PPU_REGISTER_STATUS 0x02
#define PPU_REGISTER_OAM_ADDR 0x03
#define PPU_REGISTER_OAM_DATA 0x04
#define PPU_REGISTER_SCROLL 0x05
#define PPU_REGISTER_ADDR 0x06
#define PPU_REGISTER_DATA 0x07
#define PPU_CTRL_BASE_NAMETABLE_ADDR 0x3
#define PPU_CTRL_VRAM_ADDR_INCREMENT 0x4
#define PPU_CTRL_SP_PATTERN_TABLE_ADDR 0x8
#define PPU_CTRL_BG_PATTERN_TABLE_ADDR 0x10
#define PPU_CTRL_SP_SIZE 0x20
#define PPU_CTRL_MODE_SELECT 0x40
#define PPU_CTRL_GEN_VBLANK_NMI 0x80
#define PPU_MASK_GREYSCALE 0x1
#define PPU_MASK_SHOW_BG_LEFT 0x2
#define PPU_MASK_SHOW_SP_LEFT 0x4
#define PPU_MASK_SHOW_BG 0x8
#define PPU_MASK_SHOW_SP 0x10
#define PPU_MASK_EMP_RED 0x20
#define PPU_MASK_EMP_GREEN 0x40
#define PPU_MASK_EMP_BLUE 0x80
#define PPU_STATUS_OPEN_BUS 0x1f
#define PPU_STATUS_SP_OVERFLOW 0x20
#define PPU_STATUS_SP_0_HIT 0x40
#define PPU_STATUS_VBLANK 0x80
#define PPU_MASK_NONE 0xff
typedef struct ppu {
byte* registers;
byte* oam_dma_register;
byte vram[PPU_VRAM_SIZE];
byte oam[PPU_OAM_SIZE];
bool odd_frame;
address v;
address t;
byte x;
bool w;
} PPU;
/**
* Initializes the PPU, according to the power up state.
* https://www.nesdev.org/wiki/PPU_power_up_state
*
* @param ppu
*/
void ppu_init(PPU *ppu, byte *registers_ram, byte *oam_dma_register);
/**
* Cycles the PPU.
*
* @param ppu
* @param ram
*/
void ppu_cycle(PPU *ppu);
/**
* Read a flag from the PPU registers.
*
* @param reg The register index
* @param mask The flag mask
*/
bool ppu_read_flag(PPU *ppu, size_t reg, byte mask);
/**
* Read a value from the PPU registers. Does not apply any offset to the value, a mask of 0x20 will either result in 0x20 (true) or 0x0 (false).
* Read a value from the PPU registers. Does not apply any offset to the value, a mask of 0x20 will either result in 0x20 (true) or 0x0 (false).
*
* @param reg The register index
* @param mask The value mask
*/
void ppu_read_register(PPU *ppu, byte reg);
void ppu_write_register(PPU *ppu, byte reg);
#endif //NESEMULATOR_PPU_H

View File

@ -2,12 +2,14 @@
// Created by william on 12/2/23. // Created by william on 12/2/23.
// //
#include <stdbool.h>
#include "types.h"
#include "system.h"
#ifndef NESEMULATOR_ROM_H #ifndef NESEMULATOR_ROM_H
#define NESEMULATOR_ROM_H #define NESEMULATOR_ROM_H
// The size of the header in a ROM file, in bytes // The size of the header in a ROM file, in bytes
#include "cpu.h"
#define ROM_HEADER_SIZE 16 #define ROM_HEADER_SIZE 16
// The size of the trainer in a ROM file, in bytes // The size of the trainer in a ROM file, in bytes
#define ROM_TRAINER_SIZE 512 #define ROM_TRAINER_SIZE 512
@ -18,12 +20,13 @@ typedef struct {
void *header; void *header;
} Rom; } Rom;
int rom_load(char *path); /**
* Loads a ROM from a specified file path.
void rom_uninit(); *
* @param path The file path
byte rom_prg_get_byte(address addr); * @param rom ROM
* @return A boolean indicating a success (true) or an error.
word rom_prg_get_word(address addr); */
bool rom_load(char *path, System *system);
#endif //NESEMULATOR_ROM_H #endif //NESEMULATOR_ROM_H

66
include/system.h Normal file
View File

@ -0,0 +1,66 @@
//
// Created by william on 12/23/23.
//
#include "types.h"
#include "mapper.h"
#include "ppu.h"
#ifndef NESEMULATOR_SYSTEM_H
#define NESEMULATOR_SYSTEM_H
// NTSC NES Master Clock (~21.47 MHz)
#define MASTER_CLOCK 21477272
#define CPU_CLOCK_DIVISOR 12
#define PPU_CLOCK_DIVISOR 4
#define FRAME_RATE 60
#define PPU_REGISTERS_BASE_ADDR 0x2000
#define PPU_REGISTER_OAM_DMA_ADDR 0x4014
#define APU_REGISTERS_COUNT 24
// Reference: https://www.nesdev.org/obelisk-6502-guide/registers.html
typedef struct cpu {
address program_counter;
byte stack_pointer;
byte accumulator;
byte x;
byte y;
byte status;
bool oam_dma_triggered;
} CPU;
typedef struct system {
void *rom_header;
CPU cpu;
PPU ppu;
Mapper mapper;
ram ram;
byte apu_registers[APU_REGISTERS_COUNT];
unsigned long cycle_count;
} System;
/**
* Initialize all components of a system.
*
* @param system The system to initialize
*/
void system_init(System *system);
void system_start(System *system);
/**
* Starts the main loop of a system.
*
* @param system The system
*/
void system_loop(System *system);
/**
* De-initialize the components of a system.
*
* @param system The system to de-initialize
*/
void system_uninit(System *system);
#endif //NESEMULATOR_SYSTEM_H

18
include/types.h Normal file
View File

@ -0,0 +1,18 @@
//
// Created by william on 12/26/23.
//
#ifndef NESEMULATOR_TYPES_H
#define NESEMULATOR_TYPES_H
#define RAM_SIZE 0xffff
#define VRAM_SIZE 0x4000
typedef unsigned char byte;
typedef unsigned short address;
typedef unsigned short word;
typedef byte ram[RAM_SIZE];
typedef byte vram[VRAM_SIZE];
#endif //NESEMULATOR_TYPES_H

20
main.c
View File

@ -16,19 +16,27 @@
* ===================================================================================== * =====================================================================================
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <log.h>
#include "include/rom.h" #include "include/rom.h"
#include "include/system.h"
int main() { int main() {
char *rom_path = "../tests/smb.nes"; log_set_level(LOG_INFO);
cpu_init(); char *rom_path = "../test_roms/nestest.nes";
rom_load(rom_path); System system;
cpu_step(); system_init(&system);
rom_uninit(); if (!rom_load(rom_path, &system)) {
system_uninit(&system);
return EXIT_FAILURE;
}
system_start(&system);
system_loop(&system);
system_uninit(&system);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -1,11 +1,25 @@
#include "../include/mapper.h" #include "../include/mapper.h"
#include "../include/rom.h"
#include <string.h>
address redirect_addr(address addr) { #define SIMPLE_MAPPER_PRG_START_ADDR 0x8000
return addr; #define PRG_PART_SIZE 0x4000 // 16Kb
void post_prg_load(ram ram, unsigned int prg_size) {
if (prg_size == 2) {
// The whole space is occupied, nothing to do
return;
}
// We need to mirror the data in the upper ram
byte *source = (byte *) &ram[SIMPLE_MAPPER_PRG_START_ADDR];
byte *destination = (byte *) &ram[SIMPLE_MAPPER_PRG_START_ADDR + PRG_PART_SIZE];
memcpy(destination, source, PRG_PART_SIZE);
} }
Mapper get_simple_mapper() { Mapper get_simple_mapper() {
Mapper mapper; Mapper mapper;
mapper.redirect_addr = &redirect_addr; mapper.prg_rom_start_addr = SIMPLE_MAPPER_PRG_START_ADDR;
mapper.post_prg_load = &post_prg_load;
return mapper; return mapper;
} }

5
ppu/CMakeLists.txt Normal file
View File

@ -0,0 +1,5 @@
add_library(PPU
ppu.c)
find_package(log.c)
target_link_libraries(PPU log.c::log.c)

47
ppu/ppu.c Normal file
View File

@ -0,0 +1,47 @@
//
// Created by william on 12/30/23.
//
#include <stddef.h>
#include "../include/ppu.h"
void ppu_init(PPU *ppu, byte *registers_ram, byte *oam_dma_register) {
ppu->registers = registers_ram;
ppu->registers[PPU_REGISTER_CTRL] = 0x00;
ppu->registers[PPU_REGISTER_MASK] = 0x00;
ppu->registers[PPU_REGISTER_STATUS] = 0x00;
ppu->registers[PPU_REGISTER_OAM_ADDR] = 0x00;
ppu->registers[PPU_REGISTER_OAM_DATA] = 0x00;
ppu->registers[PPU_REGISTER_SCROLL] = 0x00;
ppu->registers[PPU_REGISTER_ADDR] = 0x00;
ppu->registers[PPU_REGISTER_DATA] = 0x00;
ppu->oam_dma_register = oam_dma_register;
ppu->odd_frame = false;
}
void ppu_cycle(PPU *ppu) {
}
bool ppu_read_flag(PPU *ppu, size_t reg, byte mask) {
return ppu->registers[reg] & mask;
}
//byte ppu_read_register(PPU *ppu, size_t reg, byte mask) {
// return ppu->registers[reg] & mask;
//}
void ppu_read_register(PPU *ppu, byte reg) {
if (reg == PPU_REGISTER_STATUS) {
ppu->w = false;
}
}
void ppu_write_register(PPU *ppu, byte reg) {
if (reg == PPU_REGISTER_SCROLL || reg == PPU_REGISTER_ADDR) {
ppu->w = !ppu->w;
}
if (reg == PPU_REGISTER_OAM_DATA) {
ppu->registers[PPU_REGISTER_OAM_ADDR]++;
}
}

View File

@ -4,9 +4,9 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <log.h> #include <log.h>
#include "../include/rom.h" #include "../include/rom.h"
#include "../include/system.h"
// Flag 6 // Flag 6
#define NES_HEADER_FLAG_MIRRORING 0x01 #define NES_HEADER_FLAG_MIRRORING 0x01
@ -126,32 +126,30 @@ bool rom_ines_read_trainer(FILE *file, INesHeader *header) {
return false; return false;
} }
bool rom_ines_read_prg_rom(FILE *file, INesHeader *header, Rom *rom) { bool rom_ines_read_prg_rom(FILE *file, INesHeader *header, System *system) {
unsigned int prg_rom_size = header->prg_rom_size * 16384; unsigned int prg_rom_size = header->prg_rom_size * 16384;
rom->prg_rom = (byte *) malloc(prg_rom_size * sizeof(byte));
log_debug("Reading %d bytes PRG ROM", prg_rom_size); log_debug("Reading %d bytes PRG ROM", prg_rom_size);
if (fread(rom->prg_rom, sizeof(byte), prg_rom_size, file) < prg_rom_size) { if (fread(&system->ram[system->mapper.prg_rom_start_addr], sizeof(byte), prg_rom_size, file) < prg_rom_size) {
log_error("Failed to read PRG ROM"); log_error("Failed to read PRG ROM");
return false; return false;
} }
system->mapper.post_prg_load(&system->ram[0], header->prg_rom_size);
return true; return true;
} }
bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, Rom *rom) { bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, System *system) {
if (header->chr_rom_size <= 0) { if (header->chr_rom_size <= 0) {
log_debug("No CHR ROM to read"); log_debug("No CHR ROM to read");
return true; return true;
} }
unsigned int chr_rom_size = header->chr_rom_size * 8192; unsigned int chr_rom_size = header->chr_rom_size * 8192;
rom->chr_rom = (byte *) malloc(chr_rom_size * sizeof(byte));
log_debug("Reading %d bytes CHR ROM", chr_rom_size); log_debug("Reading %d bytes CHR ROM", chr_rom_size);
if (fread(rom->chr_rom, sizeof(byte), chr_rom_size, file) < chr_rom_size) { if (fread(system->ppu.vram, sizeof(byte), chr_rom_size, file) < chr_rom_size) {
log_error("Failed to read CHR ROM"); log_error("Failed to read CHR ROM");
return false; return false;
} }
@ -159,11 +157,11 @@ bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, Rom *rom) {
return true; return true;
} }
bool rom_ines_read(const char header_buf[ROM_HEADER_SIZE], FILE *file, Rom *rom) { bool rom_ines_read(const char header_buf[ROM_HEADER_SIZE], FILE *file, System *system) {
INesHeader header = read_header(header_buf); INesHeader header = read_header(header_buf);
rom->header = &header; system->rom_header = &header;
return rom_ines_read_trainer(file, &header) && return rom_ines_read_trainer(file, &header) &&
rom_ines_read_prg_rom(file, &header, rom) && rom_ines_read_prg_rom(file, &header, system) &&
rom_ines_read_chr_rom(file, &header, rom); rom_ines_read_chr_rom(file, &header, system);
} }

View File

@ -3,74 +3,37 @@
// //
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "../include/rom.h" #include "../include/rom.h"
#include "ines.c" #include "ines.c"
#include "../include/system.h"
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) bool rom_load(char *path, System *system) {
Rom rom;
void rom_init() {
rom.header = NULL;
rom.prg_rom = NULL;
rom.chr_rom = NULL;
}
int rom_load(char *path) {
rom_init();
FILE *file = fopen(path, "r"); FILE *file = fopen(path, "r");
if (!file) { if (!file) {
log_error("Failed to open ROM"); log_error("Failed to open ROM");
return EXIT_FAILURE; return false;
} }
char header_buffer[ROM_HEADER_SIZE] = {0}; char header_buffer[ROM_HEADER_SIZE] = {0};
size_t read_size = fread(header_buffer, sizeof(char), ARRAY_SIZE(header_buffer), file); size_t read_size = fread(header_buffer, sizeof(char), ROM_HEADER_SIZE, file);
if (read_size < ARRAY_SIZE(header_buffer)) { if (read_size < ROM_HEADER_SIZE) {
log_error("Failed to read ROM"); log_error("Failed to read ROM");
return EXIT_FAILURE; return false;
} }
if (!rom_is_ines(header_buffer)) { if (!rom_is_ines(header_buffer)) {
log_error("Only iNes ROMs are supported"); log_error("Only iNes ROMs are supported");
return EXIT_FAILURE; return false;
} }
log_info("Reading iNes 1.0 ROM at %s", path); log_info("Reading iNes 1.0 ROM at %s", path);
rom_ines_read(header_buffer, file, &rom); rom_ines_read(header_buffer, file, system);
if (fclose(file) != 0) { if (fclose(file) != 0) {
log_error("Failed to close ROM file"); log_error("Failed to close ROM file");
return EXIT_FAILURE; return false;
} }
return 0; return true;
}
void rom_uninit() {
assert(rom.prg_rom != NULL);
assert(rom.chr_rom != NULL);
free(rom.prg_rom);
free(rom.chr_rom);
log_info("Cleared ROM data");
}
byte rom_prg_get_byte(address addr) {
assert(rom.prg_rom != NULL);
return rom.prg_rom[addr];
}
word rom_prg_get_word(address addr) {
assert(rom.prg_rom != NULL);
word word = rom.prg_rom[addr];
word += rom.prg_rom[addr + 1] << 8; // Little endian
return word;
} }

57
system.c Normal file
View File

@ -0,0 +1,57 @@
//
// Created by william on 12/23/23.
//
#include "include/cpu.h"
#include "include/system.h"
#include "memory.h"
#include <unistd.h>
#include <assert.h>
#include <log.h>
void system_init(System *system) {
byte *registers_base_addr = &system->ram[PPU_REGISTERS_BASE_ADDR];
byte *oam_dma_register = &system->ram[PPU_REGISTER_OAM_DMA_ADDR];
cpu_init(&system->cpu);
ppu_init(&system->ppu, registers_base_addr, oam_dma_register);
system->mapper = get_mapper(MAPPER_TYPE_SIMPLE);
system->cycle_count = 7;
}
void system_start(System *system) {
address pc = mem_get_word(system, 0xfffc);
system->cpu.program_counter = pc;
}
void system_loop(System *system) {
assert(CPU_CLOCK_DIVISOR > PPU_CLOCK_DIVISOR);
unsigned int master_cycle_per_frame = MASTER_CLOCK / FRAME_RATE;
unsigned int cpu_cycle_per_frame = master_cycle_per_frame / CPU_CLOCK_DIVISOR;
unsigned int ppu_cycle_per_cpu_cycle = CPU_CLOCK_DIVISOR / PPU_CLOCK_DIVISOR;
long frame = 1;
long cpu_cycle_count = 0;
while (true) {
log_info("Frame %d", frame);
while (system->cycle_count < cpu_cycle_per_frame * frame) {
if (cpu_cycle_count == system->cycle_count) {
cpu_cycle(system);
}
cpu_cycle_count++;
for (int ppu_c = 0; ppu_c < ppu_cycle_per_cpu_cycle; ppu_c++) {
ppu_cycle(&system->ppu);
}
}
frame++;
usleep(17000); // Wait 16.6666ms
}
}
void system_uninit(System *system) {
}

3
test_roms/nestest.fdb Normal file
View File

@ -0,0 +1,3 @@
BreakPoint: startAddr=00000014 endAddr=00000000 flags=ER--X- condition="" desc=""
BreakPoint: startAddr=00000023 endAddr=00000000 flags=ER--X- condition="" desc=""
BreakPoint: startAddr=00000000 endAddr=00000000 flags=EC--X- condition="" desc=""

BIN
test_roms/nestest.nes Normal file

Binary file not shown.

1
test_roms/smb.fdb Normal file
View File

@ -0,0 +1 @@
BreakPoint: startAddr=00000022 endAddr=00000000 flags=ER--X- condition="" desc=""