iNes 1.0 file format ROM

This commit is contained in:
william 2023-12-03 00:27:07 -05:00
parent d4fc28505a
commit 41d70a5e1d
33 changed files with 3362 additions and 47 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="ROM" />
<config projectName="NESEmulator" targetName="Mappers" /> <config projectName="NESEmulator" targetName="Mappers" />
<config projectName="NESEmulator" targetName="CPU" /> <config projectName="NESEmulator" targetName="CPU" />
</generated> </generated>
@ -22,7 +23,41 @@
</configurations> </configurations>
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="0c3b231e-0637-4ac1-8964-c60fc9e9e691" name="Changes" comment="Cpu opcodes implementation" /> <list default="true" id="0c3b231e-0637-4ac1-8964-c60fc9e9e691" name="Changes" comment="Gitignore">
<change afterPath="$PROJECT_DIR$/include/rom.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/rom/CMakeLists.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/rom/ines.c" afterDir="false" />
<change afterPath="$PROJECT_DIR$/rom/rom.c" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/readme.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/ascii_1.chr" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/ascii_2.chr" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/ascii_3.chr" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/build_rom.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/colors.inc" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/console.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/crc.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/delay.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/devcart.bin" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/macros.inc" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/neshw.inc" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/ppu.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/print.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/shell.inc" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/shell.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/testing.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/common/text_out.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/readme.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/test_cpu_exec_space_apu.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/source/test_cpu_exec_space_ppuio.s" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/test_cpu_exec_space_apu.nes" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/cpu_exec_space/test_cpu_exec_space_ppuio.nes" 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$/cpu/cpu.h" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/cpu.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/ram.h" beforeDir="false" afterPath="$PROJECT_DIR$/cpu/ram.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/main.c" beforeDir="false" afterPath="$PROJECT_DIR$/main.c" afterDir="false" />
</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" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@ -58,41 +93,46 @@
<component name="ProjectViewState"> <component name="ProjectViewState">
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"ASKED_ADD_EXTERNAL_FILES": "true", &quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,
"RunOnceActivity.OpenProjectViewOnStart": "true", &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.cidr.known.project.marker": "true", &quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
"WebServerToolWindowFactoryState": "false", &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
"cf.first.check.clang-format": "false", &quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
"cidr.known.project.marker": "true", &quot;cidr.known.project.marker&quot;: &quot;true&quot;,
"com.jfrog.conanplugin.addconansupport": "true", &quot;com.jfrog.conanplugin.addconansupport&quot;: &quot;true&quot;,
"com.jfrog.conanplugin.automanage.cmake.advanced.settings": "true", &quot;com.jfrog.conanplugin.automanage.cmake.advanced.settings&quot;: &quot;true&quot;,
"com.jfrog.conanplugin.conanexecutable": "conan", &quot;com.jfrog.conanplugin.conanexecutable&quot;: &quot;conan&quot;,
"com.jfrog.conanplugin.hasbeensetup": "true", &quot;com.jfrog.conanplugin.hasbeensetup&quot;: &quot;true&quot;,
"git-widget-placeholder": "master", &quot;git-widget-placeholder&quot;: &quot;master&quot;,
"last_opened_file_path": "/home/william/Dev/ETS/LOG710/Lab2/CMakeLists.txt", &quot;last_opened_file_path&quot;: &quot;/home/william/Dev/ETS/LOG710/Lab2/CMakeLists.txt&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"nodejs_package_manager_path": "npm", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"settings.editor.selected.configurable": "preferences.pluginManager", &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
"structure.view.defaults.are.configured": "true", &quot;structure.view.defaults.are.configured&quot;: &quot;true&quot;,
"vue.rearranger.settings.migration": "true" &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
} }
}]]></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$/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$" />
<recent name="$PROJECT_DIR$/src" />
</key> </key>
</component> </component>
<component name="RunManager" selected="CMake Application.NESEmulator"> <component name="RunManager" selected="CMake Application.NESEmulator">
<configuration default="true" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true">
<method v="2">
<option name="CLION.EXTERNAL.BUILD" enabled="true" />
</method>
</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>
@ -111,6 +151,11 @@
<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="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 default="true" type="PythonConfigurationType" factoryName="Python"> <configuration default="true" type="PythonConfigurationType" factoryName="Python">
<module name="nesemu" /> <module name="nesemu" />
<option name="INTERPRETER_OPTIONS" value="" /> <option name="INTERPRETER_OPTIONS" value="" />
@ -238,6 +283,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.ROM" />
<item itemvalue="CMake Debug.NESEmulator" /> <item itemvalue="CMake Debug.NESEmulator" />
</list> </list>
<recent_temporary> <recent_temporary>
@ -267,6 +313,8 @@
<workItem from="1697491210595" duration="714000" /> <workItem from="1697491210595" duration="714000" />
<workItem from="1698343311430" duration="84000" /> <workItem from="1698343311430" duration="84000" />
<workItem from="1700970472703" duration="9613000" /> <workItem from="1700970472703" duration="9613000" />
<workItem from="1701463001105" duration="2058000" />
<workItem from="1701558929054" duration="14565000" />
</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" />
@ -276,7 +324,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1701018709236</updated> <updated>1701018709236</updated>
</task> </task>
<option name="localTasksCounter" value="2" /> <task id="LOCAL-00002" summary="Gitignore">
<option name="closed" value="true" />
<created>1701463073548</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1701463073548</updated>
</task>
<option name="localTasksCounter" value="3" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@ -291,6 +347,18 @@
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<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" />
<option name="LAST_COMMIT_MESSAGE" value="Cpu opcodes implementation" /> <MESSAGE value="Gitignore" />
<option name="LAST_COMMIT_MESSAGE" value="Gitignore" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="com.jetbrains.cidr.execution.debugger.OCBreakpointType">
<url>file://$PROJECT_DIR$/rom/rom.c</url>
<line>32</line>
<option name="timeStamp" value="17" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component> </component>
</project> </project>

View File

@ -3,15 +3,18 @@ project(NESEmulator VERSION 0.1)
add_subdirectory(cpu) add_subdirectory(cpu)
add_subdirectory(mappers) add_subdirectory(mappers)
add_subdirectory(rom)
list(APPEND EXTRA_INCLUDES list(APPEND EXTRA_INCLUDES
"${PROJECT_SOURCE_DIR}/cpu" "${PROJECT_SOURCE_DIR}/cpu"
"${PROJECT_SOURCE_DIR}/mappers") "${PROJECT_SOURCE_DIR}/mappers"
"${PROJECT_SOURCE_DIR}/rom")
add_executable(NESEmulator main.c) add_executable(NESEmulator main.c)
find_package(log.c) find_package(log.c)
target_link_libraries(NESEmulator CPU Mappers log.c::log.c) target_link_libraries(NESEmulator CPU 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

@ -6,6 +6,7 @@
#define CPU_CPU_H #define CPU_CPU_H
#include <stdbool.h> #include <stdbool.h>
#include "memory.h"
// Reference: https://www.nesdev.org/wiki/Status_flags // Reference: https://www.nesdev.org/wiki/Status_flags
#define CPU_STATUS_CARRY_MASK 0x01 #define CPU_STATUS_CARRY_MASK 0x01

View File

@ -1,17 +1,17 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <log.h>
#include "op.h" #include "op.h"
#include "cpu.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 // 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: \
op_ ## op(ADDR_MODE_ ## addr_mode); \ log_debug("OP: %s", "op"); \
op_ ## op(ADDR_MODE_ ## addr_mode); \
break; break;
#define IS_OP_CODE(op, op_code) \ #define IS_OP_CODE(op, op_code) \
@ -254,7 +254,7 @@ void add_with_carry(byte value) {
cpu_get_registers()->accumulator = acc; cpu_get_registers()->accumulator = acc;
cpu_set_flag(overflow, CPU_STATUS_CARRY_MASK); cpu_set_flag(overflow, CPU_STATUS_CARRY_MASK);
cpu_set_flag(is_sign_overflow(acc, value, result)); cpu_set_flag(is_sign_overflow(acc, value, result), CPU_STATUS_OVERFLOW_MASK);
set_acl_flags(result); set_acl_flags(result);
} }

View File

@ -2,6 +2,8 @@
// Created by william on 30/09/23. // Created by william on 30/09/23.
// //
#include "cpu.h"
#ifndef NESEMULATOR_RAM_H #ifndef NESEMULATOR_RAM_H
#define NESEMULATOR_RAM_H #define NESEMULATOR_RAM_H
@ -10,9 +12,6 @@
typedef unsigned short address; typedef unsigned short address;
void init_ram();
void clean_ram();
void ram_set_byte(address addr, byte byte); void ram_set_byte(address addr, byte byte);
byte ram_get_byte(address addr); byte ram_get_byte(address addr);
word ram_get_word(address addr); word ram_get_word(address addr);

16
include/rom.h Normal file
View File

@ -0,0 +1,16 @@
//
// Created by william on 12/2/23.
//
#ifndef NESEMULATOR_ROM_H
#define NESEMULATOR_ROM_H
typedef struct {
char* prg_rom;
char* chr_rom;
void* header;
} Rom;
int read_rom(char* path);
#endif //NESEMULATOR_ROM_H

13
main.c
View File

@ -15,19 +15,14 @@
* *
* ===================================================================================== * =====================================================================================
*/ */
#include <log.h>
#include <stdlib.h> #include <stdlib.h>
#include "include/cpu.h" #include "include/rom.h"
#include "ram.h"
int main() { int main() {
init_ram(); char *rom_path = "../tests/cpu_exec_space/test_cpu_exec_space_ppuio.nes";
read_rom(rom_path);
ram_set_byte(0x10, 0xf); return EXIT_SUCCESS;
ram_get_byte(0xffff);
clean_ram();
return -1;
} }

6
rom/CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
add_library(ROM
rom.c
ines.c)
find_package(log.c)
target_link_libraries(ROM log.c::log.c)

140
rom/ines.c Normal file
View File

@ -0,0 +1,140 @@
//
// Created by william on 12/2/23.
//
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <log.h>
#include "../include/rom.h"
// Flag 6
#define NES_HEADER_FLAG_MIRRORING 0x01
#define NES_HEADER_FLAG_BATTERY 0x02
#define NES_HEADER_FLAG_TRAINER 0x04
#define NES_HEADER_FLAG_IGNORE_MIRRORING 0x08
// Lower nybble of mapper number
#define NES_HEADER_FLAG_LOWER_MAPPER_NUMBER 0xf0
// Flag 7
#define NES_HEADER_FLAG_VS_UNISYSTEM 0x01
#define NES_HEADER_FLAG_PLAYCHOICE_10 0x02
// Upper nybble of mapper number, not used in iNes 1.0 format
#define NES_HEADER_FLAG_UPPER_MAPPER_NUMBER 0xf0
// Flag 9
#define NES_HEADER_FLAG_TV_SYSTEM 0x01
// Flag 10
#define NES_HEADER_FLAG_PRG_RAM 0x10
#define NES_HEADER_FLAG_BUS_CONFLICT 0x20
struct INesHeaderFlags {
/* Mirroring: 0: horizontal (vertical arrangement) (CIRAM A10 = PPU A11)
1: vertical (horizontal arrangement) (CIRAM A10 = PPU A10) */
bool nametable_mirrored;
// 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM
bool ignore_mirroring;
// 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory
bool has_battery;
// 1: 512-byte trainer at $7000-$71FF (stored before PRG data)
bool has_trainer;
bool vs_unisystem;
// PlayChoice-10 (8 KB of Hint Screen data stored after CHR data)
bool playchoice_10;
bool tv_system;
bool has_prg_ram;
bool has_bus_conflict;
};
typedef struct {
// PRG ROM size in 16KB units
unsigned char prg_rom_size;
unsigned char prg_ram_size;
// CHR ROM size in 8KB units
unsigned char chr_rom_size;
unsigned char mapper_number;
struct INesHeaderFlags flags;
} INesHeader;
bool rom_is_ines(const char header[16]) {
return header[0] == 'N' && header[1] == 'E' && header[2] == 'S';
}
INesHeader read_header(const char header_buf[16]) {
INesHeader header;
unsigned char flag6 = header_buf[6];
unsigned char flag7 = header_buf[7];
unsigned char flag9 = header_buf[9];
unsigned char flag10 = header_buf[10];
header.flags.nametable_mirrored = flag6 & NES_HEADER_FLAG_MIRRORING;
header.flags.ignore_mirroring = flag6 & NES_HEADER_FLAG_IGNORE_MIRRORING;
header.flags.has_battery = flag6 & NES_HEADER_FLAG_BATTERY;
header.flags.has_trainer = flag6 & NES_HEADER_FLAG_TRAINER;
header.flags.vs_unisystem = flag7 & NES_HEADER_FLAG_VS_UNISYSTEM;
header.flags.playchoice_10 = flag7 & NES_HEADER_FLAG_PLAYCHOICE_10;
header.flags.tv_system = flag9 & NES_HEADER_FLAG_TV_SYSTEM;
header.flags.has_prg_ram = flag10 & NES_HEADER_FLAG_PRG_RAM | 0x01;
header.flags.has_bus_conflict = flag10 & NES_HEADER_FLAG_BUS_CONFLICT;
header.prg_rom_size = header_buf[4];
header.prg_ram_size = header_buf[8];
header.chr_rom_size = header_buf[5];
header.mapper_number = (flag6 & NES_HEADER_FLAG_LOWER_MAPPER_NUMBER) >> 4;
if (!header.prg_ram_size) {
// For compatibility, a value of 0 unit of PRG RAM infers 8KB, or 1 unit.
header.prg_ram_size = 1;
}
log_debug("=== iNes ROM Header ===");
log_debug("PRG: %dx16KB (ROM), %dx8KB (RAM)",
header.prg_rom_size,
header.prg_ram_size);
log_debug("CHR: %dx8KB (ROM)",
header.chr_rom_size);
log_debug("Mapper number: %d",
header.mapper_number);
log_debug("Nametable mirrored: %d", header.flags.nametable_mirrored);
log_debug("Ignore mirroring: %d", header.flags.ignore_mirroring);
log_debug("Has PRG RAM: %d", header.flags.has_prg_ram);
log_debug("Has bus conflict: %d", header.flags.has_bus_conflict);
log_debug("Has battery: %d", header.flags.has_battery);
log_debug("Has trainer: %d", header.flags.has_trainer);
log_debug("VS Unisystem: %d", header.flags.vs_unisystem);
log_debug("Playchoice 10: %d", header.flags.playchoice_10);
log_debug("TV System: %d", header.flags.tv_system);
return header;
}
bool rom_nes_read(const char header_buf[16], FILE *file, Rom *rom) {
INesHeader header = read_header(header_buf);
rom->header = &header;
// We don't support the trainer, so we skip ahead by 512 bytes if needed.
if (header.flags.has_trainer && !fseek(file, 512, SEEK_CUR)) {
perror("Failed to seek ahead of trainer ROM section");
return false;
}
unsigned int prg_rom_size = header.prg_rom_size * 16384;
rom->prg_rom = (char *) malloc(prg_rom_size * sizeof(char));
if (fread(rom->prg_rom, sizeof(char), prg_rom_size, file) < prg_rom_size) {
perror("Failed to read PRG ROM");
return false;
}
if (header.chr_rom_size > 0) {
unsigned int chr_rom_size = header.chr_rom_size * 8192;
rom->chr_rom = (char *) malloc(chr_rom_size * sizeof(char));
if (fread(rom->chr_rom, sizeof(char), chr_rom_size, file) < chr_rom_size) {
perror("Failed to read CHR ROM");
return false;
}
}
return true;
}

54
rom/rom.c Normal file
View File

@ -0,0 +1,54 @@
//
// Created by william on 12/2/23.
//
#include <stdio.h>
#include <stdlib.h>
#include "../include/rom.h"
#include "ines.c"
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
void rom_init(Rom *rom) {
rom->header = NULL;
rom->prg_rom = NULL;
rom->chr_rom = NULL;
}
void rom_uninit(Rom *rom) {
free(rom->prg_rom);
free(rom->chr_rom);
}
int read_rom(char *path) {
FILE *file = fopen(path, "r");
if (!file) {
perror("Failed to open ROM");
return EXIT_FAILURE;
}
char header_buffer[16] = {0};
size_t read_size = fread(header_buffer, sizeof(char), ARRAY_SIZE(header_buffer), file);
if (read_size < ARRAY_SIZE(header_buffer)) {
perror("Failed to read ROM");
return EXIT_FAILURE;
}
if (!rom_is_ines(header_buffer)) {
perror("Only iNes ROMs are supported");
return EXIT_FAILURE;
}
Rom rom;
rom_init(&rom);
rom_nes_read(header_buffer, file, &rom);
rom_uninit(&rom);
if (fclose(file) != 0) {
perror("Failed to close ROM file");
return EXIT_FAILURE;
}
return 0;
}

View File

@ -0,0 +1,158 @@
NES Memory Execution Tests
----------------------------------
These tests verify that the CPU can execute code from any possible
memory location, even if that is mapped as I/O space.
In addition, two obscure side effects are tested:
1. The PPU open bus. Any write to PPU will update the open bus.
Reading from 2002 updates the low 5 bits. Reading from 2007
updates 8 bits. The open bus is shown in any addresss/bit
that the PPU does not write to. Read from 2000, you get open bus.
Read from 2006, ditto. Read from 2002, you get that in high 3 bits.
Additionally, the open bus decays automatically to zero in about one
second if not refreshed.
This test requires that a value written to $2003 can be read back
from $2001 within a time window of one or two frames.
2. One-byte opcodes must issue a dummy read to the byte immediately
following that opcode. The CPU always does a fetch of the second
byte, before it has even begun executing the opcode in the first
place.
Additionally, the following PPU features must be working properly:
1. PPU memory writes and reads through $2006/$2007
2. The address high/low toggle reset at $2002
3. A single write through $2006 must not affect the address used by $2007
4. NMI should fire sometimes to salvage a broken program, if the JSR/JMP
never reaches its intended destination. (Only required in the
test IF the CPU and/or open bus are not working properly.)
The test is done FIVE times: Once with JSR $2001, again with JMP $2001,
and then with RTS (with target address of $2001), and then with a JMP
that expects to return with an RTI opcode. Finally, with a regular
JSR, but the return from the code is done through a BRK instruction.
Tests and results:
#2: PPU memory access through $2007 does not work properly. (Use other tests to determine the exact problem.)
#3: PPU open bus implementation is missing or incomplete: A write to $2003, followed by a read from $2001 should return the same value as was written.
#4: The RTS at $2001 was never executed. (If NMI has not been implemented in the emulator, the symptom of this failure is that the program crashes and does not output either "Fail" nor "Passed").
#5: An RTS opcode should still do a dummy fetch of the next opcode. (The same goes for all one-byte opcodes, really.)
#6: I have no idea what happened, but the test did not work as supposed to. In any case, the problem is in the PPU.
#7: A jump to $2001 should never execute code from $8001 / $9001 / $A001 / $B001 / $C001 / $D001 / $E001.
#8: Okay, the test passed when JSR was used, but NOT when the opcode was JMP. I definitely did not think any emulator would trigger this result.
#9: Your PPU is broken in mind-defyingly random ways.
#10: RTS to $2001 never returned. This message never gets displayed.
#11: The test passed when JSR was used, and when JMP was used, but NOT when RTS was used. Caught ya! Paranoia wins.
#12: Your PPU gave up reason at the last moment.
#13: JMP to $2001 never returned. Again, this message never gets displayed.
#14: An RTI opcode should still do a dummy fetch of the next opcode. (The same goes for all one-byte opcodes, really.)
#15: An RTI opcode should not destroy the PPU. Somehow that still appears to be the case here.
#16: IRQ occurred uncalled
#17: JSR to $2001 never returned. (Never displayed)
#18: The BRK instruction should issue an automatic fetch of the byte that follows right after the BRK. (The same goes for all one-byte opcodes, but with BRK it should be a bit more obvious than with others.)
#19: A BRK opcode should not destroy the PPU. Somehow that still appears to be the case here.
Expected output:
TEST:test_cpu_exec_space_ppuio
This program verifies that the
CPU can execute code from any
possible location that it can
address, including I/O space.
In addition, it will be tested
that an RTS instruction does a
dummy read of the byte that
immediately follows the
instructions.
JSR+RTS TEST OK
JMP+RTS TEST OK
RTS+RTS TEST OK
JMP+RTI TEST OK
JMP+BRK TEST OK
Passed
Expected output in the other test:
TEST: test_cpu_exec_space_apu
This program verifies that the
CPU can execute code from any
possible location that it can
address, including I/O space.
In this test, it is also
verified that not only all
write-only APU I/O ports
return the open bus, but
also the unallocated I/O
space in $4018..$40FF.
40FF 40
Passed
Flashes, clicks, other glitches
-------------------------------
If a test prints "passed", it passed, even if there were some flashes or
odd sounds. Only a test which prints "done" at the end requires that you
watch/listen while it runs in order to determine whether it passed. Such
tests involve things which the CPU cannot directly test.
Alternate output
----------------
Tests generally print information on screen, but also report the final
result audibly, and output text to memory, in case the PPU doesn't work
or there isn't one, as in an NSF or a NES emulator early in development.
After the tests are done, the final result is reported as a series of
beeps (see below). For NSF builds, any important diagnostic bytes are
also reported as beeps, before the final result.
Output at $6000
---------------
All text output is written starting at $6004, with a zero-byte
terminator at the end. As more text is written, the terminator is moved
forward, so an emulator can print the current text at any time.
The text output may include ANSI color codes, which take the form of
an esc character ($1B), an opening bracket ('['), and a sequence of
numbers and semicolon characters, terminated by a non-digit character ('m').
The test status is written to $6000. $80 means the test is running, $81
means the test needs the reset button pressed, but delayed by at least
100 msec from now. $00-$7F means the test has completed and given that
result code.
To allow an emulator to know when one of these tests is running and the
data at $6000+ is valid, as opposed to some other NES program, $DE $B0
$G1 is written to $6001-$6003.
Audible output
--------------
A byte is reported as a series of tones. The code is in binary, with a
low tone for 0 and a high tone for 1, and with leading zeroes skipped.
The first tone is always a zero. A final code of 0 means passed, 1 means
failure, and 2 or higher indicates a specific reason. See the source
code of the test for more information about the meaning of a test code.
They are found after the set_test macro. For example, the cause of test
code 3 would be found in a line containing set_test 3. Examples:
Tones Binary Decimal Meaning
- - - - - - - - - - - - - - - - - - - -
low 0 0 passed
low high 01 1 failed
low high low 010 2 error 2
--
Shay Green <gblargg@gmail.com>
Joel Yliluoma <bisqwit@iki.fi>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,96 @@
; Builds program as iNES ROM
; Default is 16K PRG and 8K CHR ROM, NROM (0)
.if 0 ; Options to set before .include "shell.inc":
CHR_RAM=1 ; Use CHR-RAM instead of CHR-ROM
CART_WRAM=1 ; Use mapper that supports 8K WRAM in cart
CUSTOM_MAPPER=n ; Specify mapper number
.endif
.ifndef CUSTOM_MAPPER
.ifdef CART_WRAM
CUSTOM_MAPPER = 2 ; UNROM
.else
CUSTOM_MAPPER = 0 ; NROM
.endif
.endif
;;;; iNES header
.ifndef CUSTOM_HEADER
.segment "HEADER"
.byte $4E,$45,$53,26 ; "NES" EOF
.ifdef CHR_RAM
.byte 2,0 ; 32K PRG, CHR RAM
.else
.byte 2,1 ; 32K PRG, 8K CHR
.endif
.byte CUSTOM_MAPPER*$10+$01 ; vertical mirroring
.endif
.ifndef CUSTOM_VECTORS
.segment "VECTORS"
.word -1&$FFFF,-1&$FFFF,-1&$FFFF, nmi, reset, irq
.endif
;;;; CHR-RAM/ROM
.ifdef CHR_RAM
.define CHARS "CHARS_PRG"
.segment CHARS
ascii_chr:
.segment "CHARS_PRG_ASCII"
.align $200
.incbin "ascii.chr"
ascii_chr_end:
.else
.define CHARS "CHARS"
.segment "CHARS_ASCII"
;.align $200
.incbin "ascii_3.chr"
;.align $200
.incbin "ascii_2.chr"
;.align $200
.incbin "ascii_1.chr"
.res $E00
.endif
;.segment CHARS
;.res $10,0
;;;; Shell
.ifndef NEED_CONSOLE
NEED_CONSOLE=1
.endif
; Move code to $C000
;.segment "DMC"
; .res $4000
.include "shell.s"
std_reset:
lda #0
sta PPUCTRL
sta PPUMASK
jmp run_shell
init_runtime:
.ifdef CHR_RAM
load_ascii_chr
.endif
rts
post_exit:
jsr set_final_result
jmp forever
; This helps devcart recover after running test.
; It is never executed by test ROM.
.segment "LOADER"
.incbin "devcart.bin"
.code
.align 256

View File

@ -0,0 +1,59 @@
.define color2 $A0
.define color1 $40
.define color3 $E0
zp_res color_ptr,2
.pushseg
.segment "RODATA"
Color1Esc: .byte 27, "[0;33m", 0
Color2Esc: .byte 27, "[1;34m", 0
Color3Esc: .byte 27, "[0;37m", 0
.segment "LIB"
TextColor1:
pha
setw color_ptr, Color1Esc
lda #color1
bne ColorPrint_Sub
TextColor2:
pha
setw color_ptr, Color2Esc
lda #color2
bne ColorPrint_Sub
TextColor3:
pha
setw color_ptr, Color3Esc
lda #color3
ColorPrint_Sub:
sta text_color
tya
pha
ldy #0
@loop:
lda (color_ptr),y
beq :+
jsr write_text_out
incw color_ptr
bne @loop
: pla
tay
pla
rts
.popseg
.macro text_color1
jsr TextColor1
.endmacro
.macro text_color2
jsr TextColor2
.endmacro
.macro text_white
jsr TextColor3
.endmacro

View File

@ -0,0 +1,282 @@
; Scrolling text console with line wrapping, 30x29 characters.
; Buffers lines for speed. Will work even if PPU doesn't
; support scrolling (until text reaches bottom). Keeps border
; along bottom in case TV cuts it off.
;
; Defers most initialization until first newline, at which
; point it clears nametable and makes palette non-black.
;
; ** ASCII font must already be in CHR, and mirroring
; must be vertical or single-screen.
; Number of characters of margin on left and right, to avoid
; text getting cut off by common TVs
console_margin = 1
console_buf_size = 32
console_width = console_buf_size - (console_margin*2)
zp_byte console_pos
zp_byte console_scroll
zp_byte console_temp
zp_byte text_color
bss_res console_buf,console_buf_size
; Initializes console
console_init:
; Flag that console hasn't been initialized
setb console_scroll,-1&$FF
lda #0
sta text_color
jmp console_clear_line_
; Hides console by blacking palette and disabling PPU.
; Preserved: A, X, Y
console_hide:
pha
txa
pha
tay
pha
jsr console_wait_vbl_
setb PPUMASK,0
lda #$0F
tax
tay
jsr console_load_palette_
pla
tay
pla
tax
pla
rts
console_wait_vbl_:
lda console_scroll
cmp #-1&$FF
jne wait_vbl_optional
; Deferred initialization of PPU until first use of console
; In case PPU doesn't support scrolling, start a
; couple of lines down
setb console_scroll,16
jsr console_hide
txa
pha
; Fill nametable with spaces
setb PPUADDR,$20
setb PPUADDR,$00
ldx #240
;lda #$E0
;sta text_color
lda #0
: sta PPUDATA
sta PPUDATA
sta PPUDATA
sta PPUDATA
dex
bne :-
; Clear attributes
lda #0
ldx #$40
: sta PPUDATA
dex
bne :-
pla
tax
jmp console_show
; Shows console display
; Preserved: X, Y
console_show:
pha
txa
pha
tay
pha
jsr console_wait_vbl_
setb PPUMASK,PPUMASK_BG0
lda #$22 ; red
ldx #$27 ; green
ldy #$30 ; white
jsr console_load_palette_
pla
tay
pla
tax
jmp console_apply_scroll_
; Shows console display
; Preserved: X, Y
console_show_nowait:
pha
txa
pha
tay
pha
setb PPUMASK,PPUMASK_BG0
lda #$22 ; red
ldx #$27 ; green
ldy #$30 ; white
jsr console_load_palette_
pla
tay
pla
tax
jmp console_apply_scroll_
console_load_palette_:
pha
setb PPUADDR,$3F
setb PPUADDR,$00
setb PPUDATA,$0F ; black
pla
sta PPUDATA
stx PPUDATA
sty PPUDATA
rts
; Prints char A to console. Will not appear until
; a newline or flush occurs.
; Preserved: A, X, Y
console_print:
cmp #10
beq console_newline
stx console_temp
; Newline if buf full and next char isn't space
ldx console_pos
bpl :+
cmp #' '
beq @ignore_space
ldx console_temp
jsr console_newline
stx console_temp
ldx console_pos
:
; Write to buffer
clc
adc text_color
sta console_buf+console_margin,x
dex
stx console_pos
@ignore_space:
ldx console_temp
rts
; Displays current line and starts new one
; Preserved: A, X, Y
console_newline:
pha
jsr console_wait_vbl_
jsr console_flush_
jsr console_clear_line_
; Scroll up 8 pixels and clear one line AHEAD
lda console_scroll
jsr console_add_8_to_scroll_
sta console_scroll
jsr console_add_8_to_scroll_
jsr console_flush_a
jmp console_apply_scroll_
; A = (A + 8) % 240
console_add_8_to_scroll_:
cmp #240-8
bcc :+
adc #16-1;+1 for set carry
: adc #8
rts
console_clear_line_:
stx console_temp
; Start new clear line
lda #0
ldx #console_buf_size-1
: sta console_buf,x
dex
bpl :-
ldx #console_width-1
stx console_pos
ldx console_temp
rts
; Displays current line's contents without scrolling.
; Preserved: A, X, Y
console_flush:
pha
jsr console_wait_vbl_
jsr console_flush_
console_apply_scroll_:
lda #0
sta PPUADDR
sta PPUADDR
sta PPUSCROLL
lda console_scroll
jsr console_add_8_to_scroll_
jsr console_add_8_to_scroll_
sta PPUSCROLL
pla
rts
console_flush_:
lda console_scroll
console_flush_a:
; Address line in nametable
sta console_temp
lda #$08
asl console_temp
rol a
asl console_temp
rol a
sta PPUADDR
lda console_temp
sta PPUADDR
; Copy line
stx console_temp
ldx #console_buf_size-1
: lda console_buf,x
sta PPUDATA
dex
bpl :-
ldx console_temp
rts

View File

@ -0,0 +1,118 @@
; CRC-32 checksum calculation
zp_res checksum,4
zp_byte checksum_temp
zp_byte checksum_off_
; Turns CRC updating on/off. Allows nesting.
; Preserved: A, X, Y
crc_off:
dec checksum_off_
rts
crc_on: inc checksum_off_
beq :+
jpl internal_error ; catch unbalanced crc calls
: rts
; Initializes checksum module. Might initialize tables
; in the future.
init_crc:
jmp reset_crc
; Clears checksum and turns it on
; Preserved: X, Y
reset_crc:
lda #0
sta checksum_off_
lda #$FF
sta checksum
sta checksum + 1
sta checksum + 2
sta checksum + 3
rts
; Updates checksum with byte in A (unless disabled via crc_off)
; Preserved: A, X, Y
; Time: 357 clocks average
update_crc:
bit checksum_off_
bmi update_crc_off
update_crc_:
pha
stx checksum_temp
eor checksum
ldx #8
@bit: lsr checksum+3
ror checksum+2
ror checksum+1
ror a
bcc :+
sta checksum
lda checksum+3
eor #$ED
sta checksum+3
lda checksum+2
eor #$B8
sta checksum+2
lda checksum+1
eor #$83
sta checksum+1
lda checksum
eor #$20
: dex
bne @bit
sta checksum
ldx checksum_temp
pla
update_crc_off:
rts
; Prints checksum as 8-character hex value
print_crc:
jsr crc_off
; Print complement
ldx #3
: lda checksum,x
eor #$FF
jsr print_hex
dex
bpl :-
jmp crc_on
; EQ if checksum matches CRC
; Out: A=0 and EQ if match, A>0 and NE if different
; Preserved: X, Y
.macro is_crc crc
jsr_with_addr is_crc_,{.dword crc}
.endmacro
is_crc_:
tya
pha
; Compare with complemented checksum
ldy #3
: lda (ptr),y
sec
adc checksum,y
bne @wrong
dey
bpl :-
pla
tay
lda #0
rts
@wrong:
pla
tay
lda #1
rts

View File

@ -0,0 +1,190 @@
; Delays in CPU clocks, milliseconds, etc. All routines are re-entrant
; (no global data). No routines touch X or Y during execution.
; Code generated by macros is relocatable; it contains no JMPs to itself.
zp_byte delay_temp_ ; only written to
; Delays n clocks, from 2 to 16777215
; Preserved: A, X, Y, flags
.macro delay n
.if (n) < 0 .or (n) = 1 .or (n) > 16777215
.error "Delay out of range"
.endif
delay_ (n)
.endmacro
; Delays n milliseconds (1/1000 second)
; n can range from 0 to 1100.
; Preserved: A, X, Y, flags
.macro delay_msec n
.if (n) < 0 .or (n) > 1100
.error "time out of range"
.endif
delay ((n)*CLOCK_RATE+500)/1000
.endmacro
; Delays n microseconds (1/1000000 second).
; n can range from 0 to 100000.
; Preserved: A, X, Y, flags
.macro delay_usec n
.if (n) < 0 .or (n) > 100000
.error "time out of range"
.endif
delay ((n)*((CLOCK_RATE+50)/100)+5000)/10000
.endmacro
.align 64
; Delays A clocks + overhead
; Preserved: X, Y
; Time: A+25 clocks (including JSR)
: sbc #7 ; carry set by CMP
delay_a_25_clocks:
cmp #7
bcs :- ; do multiples of 7
lsr a ; bit 0
bcs :+
: ; A=clocks/2, either 0,1,2,3
beq @zero ; 0: 5
lsr a
beq :+ ; 1: 7
bcc :+ ; 2: 9
@zero: bne :+ ; 3: 11
: rts ; (thanks to dclxvi for the algorithm)
; Delays A*256 clocks + overhead
; Preserved: X, Y
; Time: A*256+16 clocks (including JSR)
delay_256a_16_clocks:
cmp #0
bne :+
rts
delay_256a_11_clocks_:
: pha
lda #256-19-22
jsr delay_a_25_clocks
pla
clc
adc #-1&$FF
bne :-
rts
; Delays A*65536 clocks + overhead
; Preserved: X, Y
; Time: A*65536+16 clocks (including JSR)
delay_65536a_16_clocks:
cmp #0
bne :+
rts
delay_65536a_11_clocks_:
: pha
lda #256-19-22-13
jsr delay_a_25_clocks
lda #255
jsr delay_256a_11_clocks_
pla
clc
adc #-1&$FF
bne :-
rts
max_short_delay = 41
; delay_short_ macro jumps into these
.res (max_short_delay-12)/2,$EA ; NOP
delay_unrolled_:
rts
.macro delay_short_ n
.if n < 0 .or n = 1 .or n > max_short_delay
.error "Internal delay error"
.endif
.if n = 0
; nothing
.elseif n = 2
nop
.elseif n = 3
sta <delay_temp_
.elseif n = 4
nop
nop
.elseif n = 5
sta <delay_temp_
nop
.elseif n = 6
nop
nop
nop
.elseif n = 7
php
plp
.elseif n = 8
nop
nop
nop
nop
.elseif n = 9
php
plp
nop
.elseif n = 10
sta <delay_temp_
php
plp
.elseif n = 11
php
plp
nop
nop
.elseif n = 13
php
plp
nop
nop
nop
.elseif n & 1
sta <delay_temp_
jsr delay_unrolled_-((n-15)/2)
.else
jsr delay_unrolled_-((n-12)/2)
.endif
.endmacro
.macro delay_nosave_ n
; 65536+17 = maximum delay using delay_256a_11_clocks_
; 255+27 = maximum delay using delay_a_25_clocks
; 27 = minimum delay using delay_a_25_clocks
.if n > 65536+17
lda #^(n - 15)
jsr delay_65536a_11_clocks_
; +2 ensures remaining clocks is never 1
delay_nosave_ (((n - 15) & $FFFF) + 2)
.elseif n > 255+27
lda #>(n - 15)
jsr delay_256a_11_clocks_
; +2 ensures remaining clocks is never 1
delay_nosave_ (<(n - 15) + 2)
.elseif n >= 27
lda #<(n - 27)
jsr delay_a_25_clocks
.else
delay_short_ n
.endif
.endmacro
.macro delay_ n
.if n > max_short_delay
php
pha
delay_nosave_ (n - 14)
pla
plp
.else
delay_short_ n
.endif
.endmacro

Binary file not shown.

View File

@ -0,0 +1,169 @@
; jxx equivalents to bxx
.macpack longbranch
; blt, bge equivalents to bcc, bcs
.define blt bcc
.define bge bcs
.define jge jcs
.define jlt jcc
; Puts data in another segment
.macro seg_data seg,data
.pushseg
.segment seg
data
.popseg
.endmacro
; Reserves size bytes in zeropage/bss for name.
; If size is omitted, reserves one byte.
.macro zp_res name,size
.ifblank size
zp_res name,1
.else
seg_data "ZEROPAGE",{name: .res size}
.endif
.endmacro
.macro bss_res name,size
.ifblank size
bss_res name,1
.else
seg_data "BSS",{name: .res size}
.endif
.endmacro
.macro nv_res name,size
.ifblank size
nv_res name,1
.else
seg_data "NVRAM",{name: .res size}
.endif
.endmacro
; Reserves one byte in zeropage for name (very common)
.macro zp_byte name
seg_data "ZEROPAGE",{name: .res 1}
.endmacro
; Passes constant data to routine in addr
; Preserved: A, X, Y
.macro jsr_with_addr routine,data
.local Addr
pha
lda #<Addr
sta addr
lda #>Addr
sta addr+1
pla
jsr routine
seg_data "RODATA",{Addr: data}
.endmacro
; Calls routine multiple times, with A having the
; value 'start' the first time, 'start+step' the
; second time, up to 'end' for the last time.
.macro for_loop routine,start,end,step
lda #start
: pha
jsr routine
pla
clc
adc #step
cmp #<((end)+(step))
bne :-
.endmacro
; Calls routine n times. The value of A in the routine
; counts from 0 to n-1.
.macro loop_n_times routine,n
for_loop routine,0,n-1,+1
.endmacro
; Same as for_loop, except uses 16-bit value in YX.
; -256 <= step <= 255
.macro for_loop16 routine,start,end,step
.if (step) < -256 || (step) > 255
.error "Step must be within -256 to 255"
.endif
ldy #>(start)
lda #<(start)
: tax
pha
tya
pha
jsr routine
pla
tay
pla
clc
adc #step
.if (step) > 0
bcc :+
iny
.else
bcs :+
dey
.endif
: cmp #<((end)+(step))
bne :--
cpy #>((end)+(step))
bne :--
.endmacro
; Copies byte from in to out
; Preserved: X, Y
.macro mov out, in
lda in
sta out
.endmacro
; Stores byte at addr
; Preserved: X, Y
.macro setb addr, byte
lda #byte
sta addr
.endmacro
; Stores word at addr
; Preserved: X, Y
.macro setw addr, word
lda #<(word)
sta addr
lda #>(word)
sta addr+1
.endmacro
; Loads XY with 16-bit immediate or value at address
.macro ldxy Arg
.if .match( .left( 1, {Arg} ), # )
ldy #<(.right( .tcount( {Arg} )-1, {Arg} ))
ldx #>(.right( .tcount( {Arg} )-1, {Arg} ))
.else
ldy (Arg)
ldx (Arg)+1
.endif
.endmacro
; Increments word at Addr and sets Z flag appropriately
; Preserved: A, X, Y
.macro incw Addr
.local @incw_skip ; doesn't work, so HOW THE HELL DO YOU MAKE A LOCAL LABEL IN A MACRO THAT DOESN"T DISTURB INVOKING CODE< HUH?????? POS
inc Addr
bne @incw_skip
inc Addr+1
@incw_skip:
.endmacro
; Increments XY as 16-bit register, in CONSTANT time.
; Z flag set based on entire result.
; Preserved: A
; Time: 7 clocks
.macro incxy7
iny ; 2
beq *+4 ; 3
; -1
bne *+3 ; 3
; -1
inx ; 2
.endmacro

View File

@ -0,0 +1,37 @@
; NES I/O locations and masks
; Clocks per second
.ifndef CLOCK_RATE
CLOCK_RATE = 1789773 ; NTSC
; CLOCK_RATE = 1662607 ; PAL
.endif
.ifndef BUILD_NSF
; PPU
PPUCTRL = $2000
PPUMASK = $2001
PPUSTATUS = $2002
SPRADDR = $2003
SPRDATA = $2004
PPUSCROLL = $2005
PPUADDR = $2006
PPUDATA = $2007
SPRDMA = $4014
PPUCTRL_NMI = $80
PPUMASK_BG0 = $0A
PPUCTRL_8X8 = $00
PPUCTRL_8X16 = $20
PPUMASK_SPR = $14
PPUMASK_BG0CLIP = $08
.endif
; APU
SNDCHN = $4015
JOY1 = $4016
JOY2 = $4017
SNDMODE = $4017
SNDMODE_NOIRQ = $40

View File

@ -0,0 +1,142 @@
; PPU utilities
bss_res ppu_not_present
; Sets PPUADDR to w
; Preserved: X, Y
.macro set_ppuaddr w
bit PPUSTATUS
setb PPUADDR,>w
setb PPUADDR,<w
.endmacro
; Delays by no more than n scanlines
.macro delay_scanlines n
.if CLOCK_RATE <> 1789773
.error "Currently only supports NTSC"
.endif
delay ((n)*341)/3
.endmacro
; Waits for VBL then disables PPU rendering.
; Preserved: A, X, Y
disable_rendering:
pha
jsr wait_vbl_optional
setb PPUMASK,0
pla
rts
; Fills first nametable with $00
; Preserved: Y
clear_nametable:
ldx #$20
bne clear_nametable_
clear_nametable2:
ldx #$24
clear_nametable_:
lda #0
jsr fill_screen_
; Clear pattern table
ldx #64
: sta PPUDATA
dex
bne :-
rts
; Fills screen with tile A
; Preserved: A, Y
fill_screen:
ldx #$20
bne fill_screen_
; Same as fill_screen, but fills other nametable
fill_screen2:
ldx #$24
fill_screen_:
stx PPUADDR
ldx #$00
stx PPUADDR
ldx #240
: sta PPUDATA
sta PPUDATA
sta PPUDATA
sta PPUDATA
dex
bne :-
rts
; Fills palette with $0F
; Preserved: Y
clear_palette:
set_ppuaddr $3F00
ldx #$20
lda #$0F
: sta PPUDATA
dex
bne :-
; Fills OAM with $FF
; Preserved: Y
clear_oam:
lda #$FF
; Fills OAM with A
; Preserved: A, Y
fill_oam:
ldx #0
stx SPRADDR
: sta SPRDATA
dex
bne :-
rts
; Initializes wait_vbl_optional. Must be called before
; using it.
.align 32
init_wait_vbl:
; Wait for VBL flag to be set, or ~60000
; clocks (2 frames) to pass
ldy #24
ldx #1
bit PPUSTATUS
: bit PPUSTATUS
bmi @set
dex
bne :-
dey
bpl :-
@set:
; Be sure flag didn't stay set (in case
; PPUSTATUS always has high bit set)
tya
ora PPUSTATUS
sta ppu_not_present
rts
; Same as wait_vbl, but returns immediately if PPU
; isn't working or doesn't support VBL flag
; Preserved: A, X, Y
.align 16
wait_vbl_optional:
bit ppu_not_present
bmi :++
; FALL THROUGH
; Clears VBL flag then waits for it to be set.
; Preserved: A, X, Y
wait_vbl:
bit PPUSTATUS
: bit PPUSTATUS
bpl :-
: rts

View File

@ -0,0 +1,380 @@
; Prints values in various ways to output,
; including numbers and strings.
newline = 10
zp_byte print_temp_
; Prints indicated register to console as two hex
; chars and space
; Preserved: A, X, Y, flags
print_a:
php
pha
print_reg_:
jsr print_hex
lda #' '
jsr print_char_
pla
plp
rts
print_x:
php
pha
txa
jmp print_reg_
print_y:
php
pha
tya
jmp print_reg_
print_p:
php
pha
php
pla
jmp print_reg_
print_s:
php
pha
txa
tsx
inx
inx
inx
inx
jsr print_x
tax
pla
plp
rts
; Prints A as two hex characters, NO space after
; Preserved: A, X, Y
print_hex:
jsr update_crc
pha
lsr a
lsr a
lsr a
lsr a
jsr print_hex_nibble
pla
pha
and #$0F
jsr print_hex_nibble
pla
rts
print_hex_nibble:
cmp #10
blt @digit
adc #6;+1 since carry is set
@digit: adc #'0'
jmp print_char_
; Prints character and updates checksum UNLESS
; it's a newline.
; Preserved: A, X, Y
print_char:
cmp #newline
beq :+
jsr update_crc
: pha
jsr print_char_
pla
rts
; Prints space. Does NOT update checksum.
; Preserved: A, X, Y
print_space:
pha
lda #' '
jsr print_char_
pla
rts
; Advances to next line. Does NOT update checksum.
; Preserved: A, X, Y
print_newline:
pha
lda #newline
jsr print_char_
pla
rts
; Prints string
; Preserved: A, X, Y
.macro print_str str,str2,str3,str4,str5,str6,str7,str8,str9,str10,str11,str12,str13,str14,str15
jsr print_str_
.byte str
.ifnblank str2
.byte str2
.endif
.ifnblank str3
.byte str3
.endif
.ifnblank str4
.byte str4
.endif
.ifnblank str5
.byte str5
.endif
.ifnblank str6
.byte str6
.endif
.ifnblank str7
.byte str7
.endif
.ifnblank str8
.byte str8
.endif
.ifnblank str9
.byte str9
.endif
.ifnblank str10
.byte str10
.endif
.ifnblank str11
.byte str11
.endif
.ifnblank str12
.byte str12
.endif
.ifnblank str13
.byte str13
.endif
.ifnblank str14
.byte str14
.endif
.ifnblank str15
.byte str15
.endif
.byte 0
.endmacro
print_str_:
sta print_temp_
pla
sta addr
pla
sta addr+1
jsr inc_addr
jsr print_str_addr
lda print_temp_
jmp (addr)
; Prints string at addr and leaves addr pointing to
; byte AFTER zero terminator.
; Preserved: A, X, Y
print_str_addr:
pha
tya
pha
ldy #0
beq :+ ; always taken
@loop: jsr print_char
jsr inc_addr
: lda (addr),y
bne @loop
pla
tay
pla
; FALL THROUGH
; Increments 16-bit value in addr.
; Preserved: A, X, Y
inc_addr:
inc addr
beq :+
rts
: inc addr+1
rts
.pushseg
.segment "RODATA"
; >= 60000 ? (EA60)
; >= 50000 ? (C350)
; >= 40000 ? (9C40)
; >= 30000 ? (7530)
; >= 20000 ? (4E20)
; >= 10000 ? (2710)
digit10000_hi: .byte $00,$27,$4E,$75,$9C,$C3,$EA
digit10000_lo: .byte $00,$10,$20,$30,$40,$50,$60
; >= 9000 ? (2328 (hex))
; >= 8000 ? (1F40 (hex))
; >= 7000 ? (1B58 (hex))
; >= 6000 ? (1770 (hex))
; >= 5000 ? (1388 (hex))
; >= 4000 ? (FA0 (hex))
; >= 3000 ? (BB8 (hex))
; >= 2000 ? (7D0 (hex))
; >= 1000 ? (3E8 (hex))
digit1000_hi: .byte $00,$03,$07,$0B,$0F,$13,$17,$1B,$1F,$23
digit1000_lo: .byte $00,$E8,$D0,$B8,$A0,$88,$70,$58,$40,$28
; >= 900 ? (384 (hex))
; >= 800 ? (320 (hex))
; >= 700 ? (2BC (hex))
; >= 600 ? (258 (hex))
; >= 500 ? (1F4 (hex))
; >= 400 ? (190 (hex))
; >= 300 ? (12C (hex))
; >= 200 ? (C8 (hex))
; >= 100 ? (64 (hex))
digit100_hi: .byte $00,$00,$00,$01,$01,$01,$02,$02,$03,$03
digit100_lo: .byte $00,$64,$C8,$2C,$90,$F4,$58,$BC,$20,$84
.popseg
.macro dec16_comparew table_hi, table_lo
.local @lt
cmp table_hi,y
bcc @lt
bne @lt ; only test the lo-part if hi-part is equal
pha
txa
cmp table_lo,y
pla
@lt:
.endmacro
.macro do_digit table_hi, table_lo
pha
; print Y as digit; put X in A and do SEC for subtraction
jsr @print_dec16_helper
sbc table_lo,y
tax
pla
sbc table_hi,y
.endmacro
; Prints A:X as 2-5 digit decimal value, NO space after.
; A = high 8 bits, X = low 8 bits.
print_dec16:
ora #0
beq @less_than_256
ldy #6
sty print_temp_
; TODO: Use binary search?
: dec16_comparew digit10000_hi,digit10000_lo
bcs @got10000
dey
bne :-
;cpy print_temp_
;beq @got10000
@cont_1000:
ldy #9
: dec16_comparew digit1000_hi,digit1000_lo
bcs @got1000
dey
bne :- ; Y = 0.
cpy print_temp_ ; zero print_temp_ = print zero-digits
beq @got1000
@cont_100:
ldy #9
: dec16_comparew digit100_hi,digit100_lo
bcs @got100
dey
bne :-
cpy print_temp_
beq @got100
@got10000:
do_digit digit10000_hi,digit10000_lo
; value is now 0000..9999
ldy #0
sty print_temp_
beq @cont_1000
@got1000:
do_digit digit1000_hi,digit1000_lo
; value is now 000..999
ldy #0
sty print_temp_
beq @cont_100
@got100:
do_digit digit100_hi,digit100_lo
; value is now 00..99
txa
jmp print_dec_00_99
@less_than_256:
txa
jmp print_dec
@print_dec16_helper:
tya
jsr print_digit
txa
sec
rts
; Prints A as 2-3 digit decimal value, NO space after.
; Preserved: Y
print_dec:
; Hundreds
cmp #10
blt print_digit
cmp #100
blt print_dec_00_99
ldx #'0'-1
: inx
sbc #100
bge :-
adc #100
jsr print_char_x
; Tens
print_dec_00_99:
sec
ldx #'0'-1
: inx
sbc #10
bge :-
adc #10
jsr print_char_x
; Ones
print_digit:
ora #'0'
jmp print_char
; Print a single digit
print_char_x:
pha
txa
jsr print_char
pla
rts
; Prints one of two characters based on condition.
; SEC; print_cc bcs,'C','-' prints 'C'.
; Preserved: A, X, Y, flags
.macro print_cc cond,yes,no
; Avoids labels since they're not local
; to macros in ca65.
php
pha
cond *+6
lda #no
bne *+4
lda #yes
jsr print_char
pla
plp
.endmacro

View File

@ -0,0 +1,32 @@
; Included at beginning of program
.ifdef CUSTOM_PREFIX
.include "custom_prefix.s"
.endif
; Sub-test in a multi-test ROM
.ifdef BUILD_MULTI
.include "build_multi.s"
.else
; NSF music file
.ifdef BUILD_NSF
.include "build_nsf.s"
.endif
; Devcart
.ifdef BUILD_DEVCART
.include "build_devcart.s"
.endif
; NES internal RAM
.ifdef BUILD_NOCART
.include "build_nocart.s"
.endif
; NES ROM (default)
.ifndef SHELL_INCLUDED
.include "build_rom.s"
.endif
.endif ; .ifdef BUILD_MULTI

View File

@ -0,0 +1,331 @@
; Common routines and runtime
; Detect inclusion loops (otherwise ca65 goes crazy)
.ifdef SHELL_INCLUDED
.error "shell.s included twice"
.end
.endif
SHELL_INCLUDED = 1
;**** Special globals ****
; Temporary variables that ANY routine might modify, so
; only use them between routine calls.
temp = <$A
temp2 = <$B
temp3 = <$C
addr = <$E
ptr = addr
.segment "NVRAM"
; Beginning of variables not cleared at startup
nvram_begin:
;**** Code segment setup ****
.segment "RODATA"
; Any user code which runs off end might end up here,
; so catch that mistake.
nop ; in case there was three-byte opcode before this
nop
jmp internal_error
; Move code to $E200 ($200 bytes for text output)
;.segment "DMC"
; .res $2200
; Devcart corrupts byte at $E000 when powering off
.segment "CODE"
nop
;**** Common routines ****
.include "macros.inc"
.include "neshw.inc"
.include "print.s"
.include "delay.s"
.include "crc.s"
.include "testing.s"
.ifdef NEED_CONSOLE
.include "console.s"
.else
; Stubs so code doesn't have to care whether
; console exists
console_init:
console_show:
console_hide:
console_print:
console_flush:
rts
.endif
.ifndef CUSTOM_PRINT
.include "text_out.s"
print_char_:
jsr write_text_out
jmp console_print
stop_capture:
rts
.endif
;**** Shell core ****
.ifndef CUSTOM_RESET
reset:
sei
jmp std_reset
.endif
; Sets up hardware then runs main
run_shell:
sei
cld ; unnecessary on NES, but might help on clone
ldx #$FF
txs
jsr init_shell
set_test $FF
jmp run_main
; Initializes shell
init_shell:
jsr clear_ram
jsr init_wait_vbl ; waits for VBL once here,
jsr wait_vbl_optional ; so only need to wait once more
jsr init_text_out
jsr init_testing
jsr init_runtime
jsr console_init
rts
; Runs main in consistent PPU/APU environment, then exits
; with code 0
run_main:
jsr pre_main
jsr main
lda #0
jmp exit
; Sets up environment for main to run in
pre_main:
.ifndef BUILD_NSF
jsr disable_rendering
setb PPUCTRL,0
jsr clear_palette
jsr clear_nametable
jsr clear_nametable2
jsr clear_oam
.endif
lda #$34
pha
lda #0
tax
tay
jsr wait_vbl_optional
plp
sta SNDMODE
rts
.ifndef CUSTOM_EXIT
exit:
.endif
; Reports result and ends program
std_exit:
sei
cld
ldx #$FF
txs
pha
setb SNDCHN,0
.ifndef BUILD_NSF
setb PPUCTRL,0
.endif
pla
pha
jsr report_result
;jsr clear_nvram ; TODO: was this needed for anything?
pla
jmp post_exit
; Reports final result code in A
report_result:
jsr :+
jmp play_byte
: jsr print_newline
jsr console_show
; 0: ""
cmp #1
bge :+
rts
:
; 1: "Failed"
bne :+
print_str {"Failed",newline}
rts
; n: "Failed #n"
: print_str "Failed #"
jsr print_dec
jsr print_newline
rts
;**** Other routines ****
; Reports internal error and exits program
internal_error:
print_str newline,"Internal error"
lda #255
jmp exit
.import __NVRAM_LOAD__, __NVRAM_SIZE__
; Clears $0-($100+S) and nv_ram_end-$7FF
clear_ram:
lda #0
; Main pages
tax
: sta 0,x
sta $300,x
sta $400,x
sta $500,x
sta $600,x
sta $700,x
inx
bne :-
; Stack except that above stack pointer
tsx
inx
: dex
sta $100,x
bne :-
; BSS except nvram
ldx #<__NVRAM_SIZE__
: sta __NVRAM_LOAD__,x
inx
bne :-
rts
; Clears nvram
clear_nvram:
ldx #<__NVRAM_SIZE__
beq @empty
lda #0
: dex
sta __NVRAM_LOAD__,x
bne :-
@empty:
rts
; Prints filename and newline, if available, otherwise nothing.
; Preserved: A, X, Y
print_filename:
.ifdef FILENAME_KNOWN
pha
jsr print_newline
setw addr,filename
jsr print_str_addr
jsr print_newline
pla
.endif
rts
.pushseg
.segment "RODATA"
; Filename terminated with zero byte.
filename:
.ifdef FILENAME_KNOWN
.incbin "ram:nes_temp"
.endif
.byte 0
.popseg
;**** ROM-specific ****
.ifndef BUILD_NSF
.include "ppu.s"
avoid_silent_nsf:
play_byte:
rts
; Loads ASCII font into CHR RAM
.macro load_ascii_chr
bit PPUSTATUS
setb PPUADDR,$00
setb PPUADDR,$00
setb addr,<ascii_chr
ldx #>ascii_chr
ldy #0
@page:
stx addr+1
: lda (addr),y
sta PPUDATA
iny
bne :-
inx
cpx #>ascii_chr_end
bne @page
.endmacro
; Disables interrupts and loops forever
.ifndef CUSTOM_FOREVER
forever:
sei
lda #0
sta PPUCTRL
: beq :-
.res $10,$EA ; room for code to run loader
.endif
; Default NMI
.ifndef CUSTOM_NMI
zp_byte nmi_count
nmi:
inc nmi_count
rti
; Waits for NMI. Must be using NMI handler that increments
; nmi_count, with NMI enabled.
; Preserved: X, Y
wait_nmi:
lda nmi_count
: cmp nmi_count
beq :-
rts
.endif
; Default IRQ
.ifndef CUSTOM_IRQ
irq:
bit SNDCHN ; clear APU IRQ flag
rti
.endif
.endif

View File

@ -0,0 +1,106 @@
; Utilities for writing test ROMs
; In NVRAM so these can be used before initializing runtime,
; then runtime initialized without clearing them
nv_res test_code ; code of current test
nv_res test_name,2 ; address of name of current test, or 0 of none
; Sets current test code and optional name. Also resets
; checksum.
; Preserved: A, X, Y
.macro set_test code,name
pha
lda #code
jsr set_test_
.ifblank name
setb test_name+1,0
.else
.local Addr
setw test_name,Addr
seg_data "RODATA",{Addr: .byte name,0}
.endif
pla
.endmacro
set_test_:
sta test_code
jmp reset_crc
; Initializes testing module
init_testing:
jmp init_crc
; Reports that all tests passed
tests_passed:
jsr print_filename
print_str newline,"Passed"
lda #0
jmp exit
; Reports "Done" if set_test has never been used,
; "Passed" if set_test 0 was last used, or
; failure if set_test n was last used.
tests_done:
ldx test_code
jeq tests_passed
inx
bne test_failed
jsr print_filename
print_str newline,"Done"
lda #0
jmp exit
; Reports that the current test failed. Prints code and
; name last set with set_test, or just "Failed" if none
; have been set yet.
test_failed:
ldx test_code
; Treat $FF as 1, in case it wasn't ever set
inx
bne :+
inx
stx test_code
:
; If code >= 2, print name
cpx #2-1 ; -1 due to inx above
blt :+
lda test_name+1
beq :+
jsr print_newline
sta addr+1
lda test_name
sta addr
jsr print_str_addr
jsr print_newline
:
jsr print_filename
; End program
lda test_code
jmp exit
; If checksum doesn't match expected, reports failed test.
; Clears checksum afterwards.
; Preserved: A, X, Y
.macro check_crc expected
jsr_with_addr check_crc_,{.dword expected}
.endmacro
check_crc_:
pha
jsr is_crc_
bne :+
jsr reset_crc
pla
rts
: jsr print_newline
jsr print_crc
jmp test_failed

View File

@ -0,0 +1,61 @@
; Text output as expanding zero-terminated string at text_out_base
; The final exit result byte is written here
final_result = $6000
; Text output is written here as an expanding
; zero-terminated string
text_out_base = $6004
bss_res text_out_temp
zp_res text_out_addr,2
init_text_out:
ldx #0
; Put valid data first
setb text_out_base,0
lda #$80
jsr set_final_result
; Now fill in signature that tells emulator there's
; useful data there
setb text_out_base-3,$DE
setb text_out_base-2,$B0
setb text_out_base-1,$61
ldx #>text_out_base
stx text_out_addr+1
setb text_out_addr,<text_out_base
rts
; Sets final result byte in memory
set_final_result:
sta final_result
rts
; Writes character to text output
; In: A=Character to write
; Preserved: A, X, Y
write_text_out:
sty text_out_temp
; Write new terminator FIRST, then new char before it,
; in case emulator looks at string in middle of this routine.
ldy #1
pha
lda #0
sta (text_out_addr),y
dey
pla
sta (text_out_addr),y
inc text_out_addr
bne :+
inc text_out_addr+1
:
ldy text_out_temp
rts

View File

@ -0,0 +1,105 @@
These tests are created by Joel Yliluoma.
The test framework however is created by Shay Green, and is documented below.
NES Tests Source Code
---------------------
Building with ca65
------------------
To assemble a test with ca65, use the following commands:
ca65 -I common -o rom.o source_filename_here.s
ld65 -C nes.cfg rom.o -o rom.nes
your_favorite_nes_emulator rom.nes
Don't bother trying to build a multi-test ROM, since it's not worth the
complexity. Also, tests you build won't print their name if they fail,
since that requires special arrangements.
Framework
---------
Each test is in a single source file, and makes use of several library
source files from common/. This framework provides common services and
reduces code to only that which performs the actual test. Virtually all
tests include "shell.inc" at the beginning, which sets things up and
includes all the appropriate library files.
The reset handler does minimal NES hardware initialization, clears RAM,
sets up the text console, then runs main. Main can exit by returning or
jumping to "exit" with an error code in A. Exit reports the code then
goes into an infinite loop. If the code is 0, it doesn't do anything,
otherwise it reports the code. Code 1 is reported as "Failed", and the
rest as "Error <code>".
Several routines are available to print values and text to the console.
Most update a running CRC-32 checksum which can be checked with
check_crc, allowing ALL the output to be checked very easily. If the
checksum doesn't match, it is printed, so you can run the code on a NES
and paste the correct checksum into your code.
The default is to build an iNES ROM, with other build types that I
haven't documented (devcart, sub-test of a multi-test ROM, NSF music
file). My nes.cfg file puts the code at $E000 since my devcart requires
it, and I don't want the normal ROM to differ in any way from what I've
tested.
Library routines are organized by function into several files, each with
short documentation. Each routine may also optionally list registers
which are preserved, rather than those which are modified (trashed) as
is more commonly done. This is because it's best for the caller to
assume that ALL registers are NOT preserved unless noted.
Some macros are used to make common operations more convenient. The left
is equivalent to the right:
Macro Equivalent
-------------------------------------
blt bcc
bge bcs
jne label beq skip
jmp label
skip:
etc.
zp_byte name .zeropage
name: .res 1
.code
zp_res name,n .zeropage
name: .res n
.code
bss_res name,n .bss
name: .res n
.code
for_loop r,b,e,s calls a routine with A set to successive values
--
Shay Green <gblargg@gmail.com>
Some tests might turn the screen off and on, since that affects the
behavior being tested. This does not indicate failure, and should be
ignored. Only the test result reported at the end is important.
The error code at the end is also reported audibly with a series of
tones, in case the picture isn't visible for some reason. The code is in
binary, with a low tone indicating 0 and a high tone 1. The first tone
is always a zero, so you can tell the difference. A code of 0 means
passed, 1 means failure, and 2 or higher indicates a specific reason as
listed in the source code by the corresponding set_code line. Examples:
low = 0 = passed
low high = 1 = failed
low high low = 2 = error 2
low high high = 3 = error 3
See the source code for more information about a particular test and why
it might be failing. Each test has comments and correct output at top.
--
Shay Green <gblargg@gmail.com>

View File

@ -0,0 +1,243 @@
; Expected output, and explanation:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TEST: test_cpu_exec_space_apu
; This program verifies that the
; CPU can execute code from any
; possible location that it can
; address, including I/O space.
;
; In this test, it is also
; verified that not only all
; write-only APU I/O ports
; return the open bus, but
; also the unallocated I/O
; space in $4018..$40FF.
;
; 40FF 40
; Passed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Written by Joel Yliluoma - http://iki.fi/bisqwit/
.segment "LIB"
.include "shell.inc"
.include "colors.inc"
.segment "CODE"
zp_res nmi_count
zp_res maybe_crashed
zp_res temp_code,8
zp_res console_save,2
bss_res empty,$500
.macro print_str_and_ret s,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15
print_str s,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15
rts
.endmacro
.macro my_print_str s,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15
.local Addr
jsr Addr
seg_data "RODATA",{Addr: print_str_and_ret s,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15}
.endmacro
set_vram_pos:
ldy PPUSTATUS
sta PPUADDR ; poke high 6 bits
stx PPUADDR ; poke low 8 bits
rts
test_failed_finish:
jsr crash_proof_end
; Re-enable screen
jsr console_show
text_white
jmp test_failed
open_bus_pathological_fail:
jmp test_failed_finish
main:
jsr intro
; Disable all APU channels and frame IRQ (ensure that $4015 reads back as $00)
lda #$00
sta $4015
lda #$40
sta $4017
ldx #>empty
ldy #<empty
lda #0
sta console_save
@ram_clear_loop:
stx console_save+1
: sta (console_save),y
iny
bne :-
inx
cpx #8
bne @ram_clear_loop
text_color2
lda console_pos
sta console_save+0
lda console_scroll
sta console_save+1
lda #>temp_code
jsr print_hex
lda #<temp_code
jsr print_hex
lda #' '
jsr print_char
jsr console_flush
lda #$60
sta temp_code
lda #$EA
sta temp_code+1
jsr temp_code
ldy #$40
ldx #0
@loop:
lda console_save+0
sta console_pos
lda console_save+1
sta console_scroll
lda #13
jsr write_text_out ;CR
cpx #$15
beq :+
tya
jsr print_hex
txa
jsr print_hex
lda #' '
jsr print_char
lda $4000,x
jsr print_hex
lda #' '
jsr print_char
jsr console_flush
; prepare for RTI
lda #>(:+ )
pha
lda #<(:+ )
pha
php
;
lda #$4C ; jmp abs
sta temp_code+0
stx temp_code+1
sty temp_code+2
jmp temp_code
: inx
bne @loop
text_white
jsr console_show
jsr wait_vbl
jmp tests_passed
.pushseg
.segment "RODATA"
intro: text_white
print_str "TEST: test_cpu_exec_space_apu",newline
text_color1
jsr print_str_
; 0123456789ABCDEF0123456789ABCD
.byte "This program verifies that the",newline
.byte "CPU can execute code from any",newline
.byte "possible location that it can",newline
.byte "address, including I/O space.",newline
.byte newline
.byte "In this test, it is also",newline
.byte "verified that not only all",newline
.byte "write-only APU I/O ports",newline
.byte "return the open bus, but",newline
.byte "also the unallocated I/O",newline
.byte "space in $4018..$40FF.",newline
.byte newline,0
text_white
rts
.popseg
nmi:
pha
lda maybe_crashed
beq :+
inc nmi_count
lda nmi_count
cmp #4
bcc :+
jmp test_failed_finish
:
pla
rti
crash_proof_begin:
lda #$FF
sta nmi_count
sta maybe_crashed
; Enable NMI
lda #$80
sta $2000
rts
crash_proof_end:
; Disable NMI
lda #0
sta $2000
sta maybe_crashed
rts
irq:
; Presume we got here through a BRK opcode.
; Presumably, that opcode was placed in $8000..$E000 to trap wrong access.
plp
wrong_code_executed_somewhere:
text_white
print_str "ERROR",newline
text_color1
; 0123456789ABCDEF0123456789ABC|
print_str "Mysteriously Landed at $"
pla
tax
pla
jsr print_hex
txa
jsr print_hex
text_white
jsr print_str_
.byte newline
; 0123456789ABCDEF0123456789ABCD
.byte "Program flow did not follow",newline
.byte "the planned path, for a number",newline
.byte "of different possible reasons.",newline
.byte 0
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 2,"Failure To Obey Predetermined Execution Path"
jmp test_failed_finish
.pushseg
.segment "WRONG_CODE_8000"
.repeat $6200
brk
.endrepeat
; zero-fill
.popseg

View File

@ -0,0 +1,524 @@
; Expected output, and explanation:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TEST: test_cpu_exec_space
; This program verifies that the
; CPU can execute code from any
; possible location that it can
; address, including I/O space.
;
; In addition, it will be tested
; that an RTS instruction does a
; dummy read of the byte that
; immediately follows the
; instructions.
;
; JSR+RTS TEST OK
; JMP+RTS TEST OK
; RTS+RTS TEST OK
; JMP+RTI TEST OK
; JMP+BRK TEST OK
;
; Passed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Written by Joel Yliluoma - http://iki.fi/bisqwit/
.segment "LIB"
.include "shell.inc"
.include "colors.inc"
.segment "CODE"
zp_res nmi_count
zp_res brk_issued
zp_res maybe_crashed
.macro print_str_and_ret s,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15
print_str s,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15
rts
.endmacro
.macro my_print_str s,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15
.local Addr
jsr Addr
seg_data "RODATA",{Addr: print_str_and_ret s,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15}
.endmacro
set_vram_pos:
ldy PPUSTATUS
sta PPUADDR ; poke high 6 bits
stx PPUADDR ; poke low 8 bits
rts
test_failed_finish:
jsr crash_proof_end
; Re-enable screen
jsr console_show
text_white
jmp test_failed
open_bus_pathological_fail:
jmp test_failed_finish
main:
lda #0
sta brk_issued
; Operations we will be doing are:
;
jsr intro
text_color2
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 2,"PPU memory access through $2007 does not work properly. (Use other tests to determine the exact problem.)"
jsr console_hide
jsr crash_proof_begin
; Put byte $55 at $2400 and byte $AA at $2411.
lda #$24
ldx #$00
jsr set_vram_pos
ldy #$55
sty PPUDATA
ldx #$11
jsr set_vram_pos
ldy #$AA
sty PPUDATA
; Read from $2400 and $2411.
lda #$24
ldx #$00
jsr set_vram_pos
ldy PPUDATA ; Discard the buffered byte; load $55 into buffer.
ldx #$11
jsr set_vram_pos
lda PPUDATA ; Load buffer ($55); place $AA in buffer.
cmp #$55
bne test_failed_finish
lda PPUDATA ; Load buffer ($AA); place unknown in buffer.
cmp #$AA
bne test_failed_finish
jsr crash_proof_end
jsr console_show
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 3,"PPU open bus implementation is missing or incomplete: A write to $2003, followed by a read from $2001 should return the same value as was written."
jsr wait_vbl
lda #$B2 ; sufficiently random byte.
sta $2003 ; OAM index, but also populates open bus
eor $2001
bne open_bus_pathological_fail
; Set VRAM address ($2411). This is the address we will be reading from, if the test worked properly.
jsr console_hide
jsr crash_proof_begin
lda #$24
ldx #$00
jsr set_vram_pos
; Now, set HALF of the other address ($2400). If the test did NOT work properly, this address will be read from.
lda #$24
sta PPUADDR
; Poke the open bus again; it was wasted earlier.
lda #$60 ; rts
sta $2003 ; OAM index, but also populates open bus
set_test 4,"The RTS at $2001 was never executed."
jsr $2001 ; should fetch opcode from $2001, and do a dummy read at $2002
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 5,"An RTS opcode should still do a dummy fetch of the next opcode. (The same goes for all one-byte opcodes, really.)"
; Poke the OTHER HALF of the address ($2411). If the RTS did a dummy read at $2002, as it should,
; this ends up being a first HALF of a dummy address.
lda #$11
sta PPUADDR
; Read from PPU.
lda PPUDATA ; Discard the buffered byte; load something into buffer
lda PPUDATA ; eject buffer. We should have $2400 contents ($55).
pha
jsr crash_proof_end
jsr console_show
pla
cmp #$55
beq passed_1
cmp #$AA ; $AA is the expected result if the dummy read was not implemented properly.
beq :+
;
; If we got neither $55 nor $AA, there is something else wrong.
;
jsr print_hex
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 6,"I have no idea what happened, but the test did not work as supposed to. In any case, the problem is in the PPU."
: jmp test_failed_finish
passed_1:
; ********* Do the test again, this time using JMP instead of JSR
print_str "JSR+RTS TEST OK",newline
jsr console_hide
; Set VRAM address ($2411). This is the address we will be reading from, if the test worked properly.
jsr crash_proof_begin
lda #$24
ldx #$00
jsr set_vram_pos
; Now, set HALF of the other address ($2400). If the test did NOT work properly, this address will be read from.
lda #$24
sta PPUADDR
; Poke the open bus again; it was wasted earlier.
lda #$60 ; rts
sta $2003 ; OAM index, but also populates open bus
jsr do_jmp_test
; should return here!
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 8,"Okay, the test passed when JSR was used, but NOT when the opcode was JMP. How can an emulator possibly get this result? You may congratulate yourself now, for finding something that is even more unconventional than this test."
; Poke the OTHER HALF of the address ($2411). If the RTS did a dummy read at $2002, as it should,
; this ends up being a first HALF of a dummy address.
lda #$11
sta PPUADDR
; Read from PPU.
lda PPUDATA ; Discard the buffered byte; load something into buffer
lda PPUDATA ; eject buffer. We should have $2400 contents ($55).
pha
jsr crash_proof_end
jsr console_show
pla
cmp #$55
beq passed_2
cmp #$AA ; $AA is the expected result if the dummy read was not implemented properly.
beq :+
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
;
; If we got neither $55 nor $AA, there is something else wrong.
;
jsr print_hex
set_test 9,"Your PPU is broken in mind-defyingly random ways."
: jmp test_failed_finish
do_jmp_test:
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 4,"The RTS at $2001 was never executed."
jmp $2001 ; should fetch opcode from $2001, and do a dummy read at $2002
passed_2:
print_str "JMP+RTS TEST OK",newline
; ********* Do the test once more, this time using RTS instead of JSR
jsr console_hide
; Set VRAM address ($2411). This is the address we will be reading from, if the test worked properly.
jsr crash_proof_begin
lda #$24
ldx #$00
jsr set_vram_pos
; Now, set HALF of the other address ($2400). If the test did NOT work properly, this address will be read from.
lda #$24
sta PPUADDR
; Poke the open bus again; it was wasted earlier.
lda #$60 ; rts
sta $2003 ; OAM index, but also populates open bus
jsr do_rts_test
; should return here!
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 11,"The test passed when JSR was used, and when JMP was used, but NOT when RTS was used. Caught ya! Paranoia wins."
; Poke the OTHER HALF of the address ($2411). If the RTS did a dummy read at $2002, as it should,
; this ends up being a first HALF of a dummy address.
lda #$11
sta PPUADDR
; Read from PPU.
lda PPUDATA ; Discard the buffered byte; load something into buffer
lda PPUDATA ; eject buffer. We should have $2400 contents ($55).
pha
jsr crash_proof_end
jsr console_show
pla
cmp #$55
beq passed_3
cmp #$AA ; $AA is the expected result if the dummy read was not implemented properly.
beq :+
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
;
; If we got neither $55 nor $AA, there is something else wrong.
;
jsr print_hex
set_test 12,"Your PPU gave up reason at the last moment."
: jmp test_failed_finish
do_rts_test:
set_test 10,"RTS to $2001 never returned." ; This message never gets displayed.
lda #$20
pha
lda #$00
pha
rts
passed_3:
print_str "RTS+RTS TEST OK",newline
; Do the second test (JMP) once more. This time, use RTI rather than RTI.
; Set VRAM address ($2411). This is the address we will be reading from, if the test worked properly.
jsr console_hide
jsr crash_proof_begin
lda #$24
ldx #$00
jsr set_vram_pos
; Now, set HALF of the other address ($2400). If the test did NOT work properly, this address will be read from.
lda #$24
sta PPUADDR
; Poke the open bus again; it was wasted earlier.
lda #$40 ; rti
sta $2003 ; OAM index, but also populates open bus
set_test 13,"JMP to $2001 never returned." ; This message never gets displayed, either.
lda #>(:+ )
pha
lda #<(:+ )
pha
php
jmp $2001 ; should fetch opcode from $2001, and do a dummy read at $2002
:
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 14,"An RTI opcode should still do a dummy fetch of the next opcode. (The same goes for all one-byte opcodes, really.)"
; Poke the OTHER HALF of the address ($2411). If the RTI did a dummy read at $2002, as it should,
; this ends up being a first HALF of a dummy address.
lda #$11
sta PPUADDR
; Read from PPU.
lda PPUDATA ; Discard the buffered byte; load something into buffer
lda PPUDATA ; eject buffer. We should have $2400 contents ($55).
pha
jsr crash_proof_end
jsr console_show
pla
cmp #$55
beq passed_4
cmp #$AA ; $AA is the expected result if the dummy read was not implemented properly.
beq :+
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
;
; If we got neither $55 nor $AA, there is something else wrong.
;
jsr print_hex
set_test 15,"An RTI opcode should not destroy the PPU. Somehow that still appears to be the case here."
: jmp test_failed_finish
passed_4:
print_str "JMP+RTI TEST OK",newline
; ********* Do the test again, this time using BRK instead of RTS/RTI
jsr console_hide
jsr crash_proof_begin
; Set VRAM address ($2411). This is the address we will be reading from, if the test worked properly.
lda #$24
ldx #$00
jsr set_vram_pos
; Now, set HALF of the other address ($2400). If the test did NOT work properly, this address will be read from.
lda #$24
sta PPUADDR
; Poke the open bus again; it was wasted earlier.
lda #$00 ; brk
sta $2003 ; OAM index, but also populates open bus
lda #1
sta brk_issued
set_test 17,"JSR to $2001 never returned." ; This message never gets displayed, either.
jmp $2001
nop
nop
nop
nop
returned_from_brk:
nop
nop
nop
nop
; should return here!
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 18,"The BRK instruction should issue an automatic fetch of the byte that follows right after the BRK. (The same goes for all one-byte opcodes, but with BRK it should be a bit more obvious than with others.)"
; Poke the OTHER HALF of the address ($2411). If the BRK did a dummy read at $2002, as it should,
; this ends up being a first HALF of a dummy address.
lda #$11
sta PPUADDR
; Read from PPU.
lda PPUDATA ; Discard the buffered byte; load something into buffer
lda PPUDATA ; eject buffer. We should have $2400 contents ($55).
pha
jsr crash_proof_end
jsr console_show
pla
cmp #$55
beq passed_5
cmp #$AA ; $AA is the expected result if the dummy read was not implemented properly.
beq :+
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
;
; If we got neither $55 nor $AA, there is something else wrong.
;
jsr print_hex
set_test 19,"A BRK opcode should not destroy the PPU. Somehow that still appears to be the case here."
: jmp test_failed_finish
passed_5:
print_str "JMP+BRK TEST OK",newline
text_white
jsr console_show
jsr wait_vbl
jmp tests_passed
.pushseg
.segment "RODATA"
intro: text_white
print_str "TEST:test_cpu_exec_space_ppuio",newline
text_color1
jsr print_str_
; 0123456789ABCDEF0123456789ABCD
.byte "This program verifies that the",newline
.byte "CPU can execute code from any",newline
.byte "possible location that it can",newline
.byte "address, including I/O space.",newline
.byte newline
.byte "In addition, it will be tested",newline
.byte "that an RTS instruction does a",newline
.byte "dummy read of the byte that",newline
.byte "immediately follows the",newline
.byte "instructions.",newline
.byte newline,0
text_white
rts
.popseg
; Prospects (bleak) of improving this test:
;
; $2000 is write only (writing updates open_bus, reading returns open_bus)
; $2001 is write only (writing updates open_bus, reading returns open_bus)
; $2002 is read only (writing updates open_bus, reading UPDATES open_bus (but only for low 5 bits))
; $2003 is write only (writing updates open_bus, reading returns open_bus)
; $2004 is read-write (writing updates open_bus, however for %4==2, bitmask=11100011. Reading is UNRELIABLE.)
; $2005 is write only (writing updates open_bus, reading returns open_bus)
; $2006 is write only (writing updates open_bus, reading returns open_bus)
; $2007 is read-write (writing updates open_bus, reading UPDATES open_bus)
irq:
; Presume we got here through a BRK opcode.
lda brk_issued
beq spurious_irq
cmp #1
beq brk_successful
; If we got a spurious IRQ, and already warned of it once, do a regular RTI
rti
spurious_irq:
lda #2
sta brk_issued
set_test 16,"IRQ occurred uncalled"
jmp test_failed_finish
brk_successful:
jmp returned_from_brk
nmi:
pha
lda maybe_crashed
beq :+
inc nmi_count
lda nmi_count
cmp #4
bcc :+
jmp test_failed_finish
:
pla
rti
crash_proof_begin:
lda #$FF
sta nmi_count
sta maybe_crashed
; Enable NMI
lda #$80
sta $2000
rts
crash_proof_end:
; Disable NMI
lda #0
sta $2000
sta maybe_crashed
rts
wrong_code_executed_somewhere:
pha
txa
pha
text_white
print_str "ERROR",newline
text_color1
print_str "Mysteriously Landed at $"
pla
jsr print_hex
pla
jsr print_hex
jsr print_newline
text_color1
; 0123456789ABCDEF0123456789ABC|
print_str "CPU thinks we are at: $"
pla
tax
pla
jsr print_hex
txa
jsr print_hex
; 0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|0123456789ABCDEF0123456789ABC|
set_test 7,"A jump to $2001 should never execute code from anywhere else than $2001"
jmp test_failed_finish
.pushseg
.segment "WRONG_CODE_8000"
.repeat $6200/8, I
.byt $EA
lda #<( $8001+ 8*I)
ldx #>( $8001+ 8*I)
jsr wrong_code_executed_somewhere
.endrepeat
; CODE BEGINS AT E200
.popseg

Binary file not shown.

Binary file not shown.