Compare commits

..

2 Commits

Author SHA1 Message Date
FyloZ 8dd1fe6037
Removed ROMS 2024-05-06 20:23:53 -04:00
FyloZ 22401f30ac
Move states to global variables 2024-05-06 20:23:44 -04:00
58 changed files with 710 additions and 3694 deletions

1
.gitignore vendored
View File

@ -154,3 +154,4 @@ environment_run.ps1.env
environment_run.sh.env
# End of https://www.toptal.com/developers/gitignore/api/cmake,clion,conan
/test_roms/

156
cpu/cpu.c
View File

@ -11,7 +11,7 @@
/*
* =====================================================================================
*
* Filename: cpu.c
* Filename: cpu_state.c
*
* Description: 6502 CPU emulator
*
@ -26,133 +26,147 @@
* =====================================================================================
*/
void cpu_init(CPU *cpu) {
cpu->program_counter = 0x8000;
cpu->stack_pointer = 0xfd;
cpu->accumulator = 0x00;
cpu->x = 0x00;
cpu->y = 0x00;
cpu->status = 0x04;
cpu->oam_dma_triggered = false;
cpu->nmi_requested = false;
CPU cpu_state;
void cpu_init() {
cpu_state.program_counter = 0x8000;
cpu_state.stack_pointer = 0xfd;
cpu_state.accumulator = 0x00;
cpu_state.x = 0x00;
cpu_state.y = 0x00;
cpu_state.status = 0x04;
cpu_state.oam_dma_triggered = false;
cpu_state.nmi_requested = false;
}
void print_registers(CPU cpu, byte op, unsigned long cycle_count) {
log_debug("%#02x %#02x %s \t A:%#02x X:%#02x Y:%#02x F:%#02x SP:%#02x \t [%d]",
cpu.program_counter,
void print_registers(byte op, unsigned long cycle_count) {
log_info("%#02x %#02x %s \t A:%#02x X:%#02x Y:%#02x F:%#02x SP:%#02x \t [%d]",
cpu_state.program_counter,
op,
get_op_code_name(op),
cpu.accumulator,
cpu.x,
cpu.y,
cpu.status,
cpu.stack_pointer,
cpu_state.accumulator,
cpu_state.x,
cpu_state.y,
cpu_state.status,
cpu_state.stack_pointer,
cycle_count);
}
void cpu_process_nmi(System *system) {
cpu_stack_push_context(system);
void cpu_process_nmi() {
cpu_stack_push_context();
address handler_addr = mem_get_word(system, 0xfffa);
address handler_addr = mem_get_word(0xfffa);
log_debug("NMI %#04x", handler_addr);
system->cpu.nmi_requested = false;
system->cpu.program_counter = handler_addr;
cpu_state.nmi_requested = false;
cpu_state.program_counter = handler_addr;
}
void oam_dma_upload(System *system) {
byte page_high_addr = *system->ppu.oam_dma_register;
void oam_dma_upload() {
byte page_high_addr = *ppu_get_state()->oam_dma_register; // TODO
address page_addr = ((address) page_high_addr) << 8;
byte n = 0xff;
byte *ram_source = &system->ram[page_addr];
byte *oam_destination = system->ppu.oam;
byte *ram_source = mem_get_ptr(page_addr);
byte *oam_destination = ppu_get_state()->oam;
memcpy(oam_destination, ram_source, n);
log_debug("OAM DMA %#04x", page_addr);
cpu_add_cycles(system, 513); // TODO
cpu_add_cycles(513); // TODO
}
void cpu_cycle(System *system) {
if (system->cpu.nmi_requested) {
cpu_process_nmi(system);
void cpu_cycle() {
if (cpu_state.nmi_requested) {
cpu_process_nmi();
}
if (system->cpu.oam_dma_triggered) {
oam_dma_upload(system);
system->cpu.oam_dma_triggered = false;
if (cpu_state.oam_dma_triggered) {
oam_dma_upload();
cpu_state.oam_dma_triggered = false;
return;
}
CPU cpu = system->cpu;
byte op = cpu_get_next_byte(system);
CPU cpu = cpu_state;
byte op = cpu_get_next_byte();
print_registers(cpu, op, system->cycle_count);
print_registers(op, system_get_cycles());
process_op_code(system, op);
process_op_code(op);
}
void cpu_add_cycles(System *system, unsigned int cycle_count) {
system->cycle_count += cycle_count;
void cpu_add_cycles(unsigned int cycle_count) {
system_add_cycles(cycle_count);
}
// === Registers ===
bool system_get_flag(System *system, byte mask) {
return cpu_get_flag(&system->cpu, mask);
bool cpu_get_flag(byte mask) {
return cpu_state.status & mask;
}
void system_set_flag(System *system, byte mask, bool set) {
void cpu_set_flag(byte mask, bool set) {
if (set) {
system->cpu.status |= mask;
cpu_state.status |= mask;
} else {
system->cpu.status &= ~mask;
cpu_state.status &= ~mask;
}
}
byte cpu_get_next_byte(System *system) {
byte next_byte = mem_get_byte(system, system->cpu.program_counter);
system->cpu.program_counter++;
byte cpu_get_next_byte() {
byte next_byte = mem_get_byte(cpu_state.program_counter);
cpu_state.program_counter++;
return next_byte;
}
word cpu_get_next_word(System *system) {
word next_word = mem_get_word(system, system->cpu.program_counter);
system->cpu.program_counter += 2;
word cpu_get_next_word() {
word next_word = mem_get_word(cpu_state.program_counter);
cpu_state.program_counter += 2;
return next_word;
}
void cpu_stack_push(System *system, byte value) {
assert(system->cpu.stack_pointer > 0);
void cpu_stack_push(byte value) {
assert(cpu_state.stack_pointer > 0);
address mem_addr = CPU_STACK_ADDR | system->cpu.stack_pointer;
mem_set_byte(system, mem_addr, value);
system->cpu.stack_pointer--;
address mem_addr = CPU_STACK_ADDR | cpu_state.stack_pointer;
mem_set_byte(mem_addr, value);
cpu_state.stack_pointer--;
}
byte cpu_stack_pop(System *system) {
assert(system->cpu.stack_pointer < 0xff);
byte cpu_stack_pop() {
assert(cpu_state.stack_pointer < 0xff);
system->cpu.stack_pointer++;
address mem_addr = CPU_STACK_ADDR | system->cpu.stack_pointer;
byte value = mem_get_byte(system, mem_addr);
cpu_state.stack_pointer++;
address mem_addr = CPU_STACK_ADDR | cpu_state.stack_pointer;
byte value = mem_get_byte(mem_addr);
return value;
}
void cpu_stack_push_context(System *system) {
cpu_stack_push(system, system->cpu.program_counter >> 8);
cpu_stack_push(system, system->cpu.program_counter & 0xff);
cpu_stack_push(system, system->cpu.status);
void cpu_stack_push_context() {
cpu_stack_push(cpu_state.program_counter >> 8);
cpu_stack_push(cpu_state.program_counter & 0xff);
cpu_stack_push(cpu_state.status);
}
void cpu_stack_pop_context(System *system) {
byte value = cpu_stack_pop(system);
void cpu_stack_pop_context() {
byte value = cpu_stack_pop();
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;
cpu_state.status = value;
byte lo = cpu_stack_pop(system);
address pc = cpu_stack_pop(system) << 8;
byte lo = cpu_stack_pop();
address pc = cpu_stack_pop() << 8;
pc += lo;
system->cpu.program_counter = pc;
cpu_state.program_counter = pc;
}
void cpu_trigger_oam_dma() {
cpu_state.oam_dma_triggered = true;
}
void cpu_trigger_nmi() {
cpu_state.nmi_requested = true;
}
CPU *cpu_get_state() {
return &cpu_state;
}

View File

@ -19,6 +19,20 @@
#define CPU_STACK_ADDR 0x0100
// 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;
bool nmi_requested;
} CPU;
CPU *cpu_get_state();
/**
* Gets a flag from the CPU registers.
*
@ -26,11 +40,7 @@
* @param mask The flag mask
* @return The value of the flag.
*/
bool system_get_flag(System *system, byte mask);
static inline bool cpu_get_flag(CPU *cpu, byte mask) {
return cpu->status & mask;
}
bool cpu_get_flag(byte mask);
/**
* Sets a flag in the CPU registers.
@ -39,7 +49,7 @@ static inline bool cpu_get_flag(CPU *cpu, byte mask) {
* @param mask The flag mask
* @param set If the flag is set or not
*/
void system_set_flag(System *system, byte mask, bool set);
void cpu_set_flag(byte mask, bool set);
/**
* Gets the next byte in the program.
@ -48,7 +58,7 @@ void system_set_flag(System *system, byte mask, bool set);
* @param system The system
* @return The value of the next byte.
*/
byte cpu_get_next_byte(System *system);
byte cpu_get_next_byte();
/**
* Gets the next word in the program.
@ -57,7 +67,7 @@ byte cpu_get_next_byte(System *system);
* @param system The system
* @return The value of the next word.
*/
word cpu_get_next_word(System *system);
word cpu_get_next_word();
/**
* Pushes a byte in to the stack.
@ -65,7 +75,7 @@ word cpu_get_next_word(System *system);
* @param system The system
* @param value The value to push to the stack
*/
void cpu_stack_push(System *system, byte value);
void cpu_stack_push(byte value);
/**
* Pushes the execution context to the stack.
@ -73,7 +83,7 @@ void cpu_stack_push(System *system, byte value);
*
* @param system The system
*/
void cpu_stack_push_context(System *system);
void cpu_stack_push_context();
/**
* Pops a byte from the stack.
@ -81,7 +91,7 @@ void cpu_stack_push_context(System *system);
* @param system The system
* @return The value of the byte
*/
byte cpu_stack_pop(System *system);
byte cpu_stack_pop();
/**
* Pops an execution context from the stack and overwrite the current context.
@ -89,13 +99,16 @@ byte cpu_stack_pop(System *system);
*
* @param system The system
*/
void cpu_stack_pop_context(System *system);
void cpu_stack_pop_context();
/**
* 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);
void cpu_add_cycles(unsigned int cycle_count);
void cpu_trigger_oam_dma();
void cpu_trigger_nmi();
#endif //CPU_CPU_H

View File

@ -7,52 +7,52 @@
#include <assert.h>
#include "log.h"
address decode_operand_addr(System *system, AddressingMode addr_mode, bool *page_crossing) {
CPU registers = system->cpu;
address decode_operand_addr(AddressingMode addr_mode, bool *page_crossing) {
CPU *registers = cpu_get_state();
address operand_addr;
if (addr_mode == ADDR_MODE_ZERO_PAGE) {
operand_addr = cpu_get_next_byte(system);
operand_addr = cpu_get_next_byte();
} else if (addr_mode == ADDR_MODE_ZERO_PAGE_INDEXED_X) {
operand_addr = (cpu_get_next_byte(system) + registers.x) & 0xff;
operand_addr = (cpu_get_next_byte() + registers->x) & 0xff;
} else if (addr_mode == ADDR_MODE_ZERO_PAGE_INDEXED_Y) {
operand_addr = (cpu_get_next_byte(system) + registers.y) & 0xff;
operand_addr = (cpu_get_next_byte() + registers->y) & 0xff;
} else if (addr_mode == ADDR_MODE_ABSOLUTE) {
operand_addr = cpu_get_next_word(system);
operand_addr = cpu_get_next_word();
} else if (addr_mode == ADDR_MODE_ABSOLUTE_INDEXED_X) {
word addr = cpu_get_next_word(system);
word new_addr = addr + registers.x;
word addr = cpu_get_next_word();
word new_addr = addr + registers->x;
*page_crossing = (addr & 0xff00) != (new_addr & 0xff00);
operand_addr = new_addr;
} else if (addr_mode == ADDR_MODE_ABSOLUTE_INDEXED_Y) {
word addr = cpu_get_next_word(system);
word new_addr = addr + registers.y;
word addr = cpu_get_next_word();
word new_addr = addr + registers->y;
*page_crossing = (addr & 0xff00) != (new_addr & 0xff00);
operand_addr = new_addr;
} else if (addr_mode == ADDR_MODE_INDIRECT_JUMP) {
word addr = cpu_get_next_word(system);
word addr = cpu_get_next_word();
if ((addr & 0xff) == 0xff) {
// Error in NES CPU for JMP op
word result = mem_get_byte(system, addr);
result += mem_get_byte(system, addr & 0xff00) << 8;
word result = mem_get_byte(addr);
result += mem_get_byte(addr & 0xff00) << 8;
operand_addr = result;
} else {
operand_addr = mem_get_word(system, addr);
operand_addr = mem_get_word(addr);
}
} else if (addr_mode == ADDR_MODE_INDIRECT_X) {
byte arg_addr = cpu_get_next_byte(system);
byte arg_addr = cpu_get_next_byte();
word addr = mem_get_byte(system, (arg_addr + system->cpu.x) & 0xff);
addr += mem_get_byte(system, (arg_addr + system->cpu.x + 1) & 0xff) << 8;
word addr = mem_get_byte((arg_addr + registers->x) & 0xff);
addr += mem_get_byte((arg_addr + registers->x + 1) & 0xff) << 8;
operand_addr = addr;
} else if (addr_mode == ADDR_MODE_INDIRECT_Y) {
byte arg_addr = cpu_get_next_byte(system);
word addr = mem_get_byte(system, arg_addr) + (mem_get_byte(system, (arg_addr + 1) & 0xff) << 8);
word new_addr = addr + registers.y;
byte arg_addr = cpu_get_next_byte();
word addr = mem_get_byte(arg_addr) + (mem_get_byte((arg_addr + 1) & 0xff) << 8);
word new_addr = addr + registers->y;
*page_crossing = (addr & 0xff00) != (new_addr & 0xff00);
@ -65,7 +65,7 @@ address decode_operand_addr(System *system, AddressingMode addr_mode, bool *page
return operand_addr;
}
Operand decode_operand(System *system, AddressingMode addr_mode) {
Operand decode_operand(AddressingMode addr_mode) {
Operand operand;
if (addr_mode == ADDR_MODE_ACCUMULATOR) {
@ -74,25 +74,25 @@ Operand decode_operand(System *system, AddressingMode addr_mode) {
operand.is_page_crossing = false;
} else if (addr_mode == ADDR_MODE_IMMEDIATE) {
operand.type = OPERAND_TYPE_IMMEDIATE;
operand.value = cpu_get_next_byte(system);
operand.value = cpu_get_next_byte();
operand.is_page_crossing = false;
} else {
operand.type = OPERAND_TYPE_ADDRESS;
operand.value = decode_operand_addr(system, addr_mode, &operand.is_page_crossing);
operand.value = decode_operand_addr(addr_mode, &operand.is_page_crossing);
}
log_trace("Operand type: %s, value: %#02x", operand_name(&operand), operand.value);
return operand;
}
byte read_operand(System *system, Operand operand) {
byte read_operand(Operand operand) {
switch (operand.type) {
case OPERAND_TYPE_ACCUMULATOR:
return system->cpu.accumulator;
return cpu_get_state()->accumulator;
case OPERAND_TYPE_IMMEDIATE:
return (byte) operand.value;
case OPERAND_TYPE_ADDRESS:
return mem_get_byte(system, operand.value);
return mem_get_byte(operand.value);
default:
assert(false);
}

View File

@ -37,11 +37,11 @@ typedef struct {
bool is_page_crossing;
} Operand;
address decode_operand_addr(System *system, AddressingMode addr_mode, bool *page_crossing);
address decode_operand_addr(AddressingMode addr_mode, bool *page_crossing);
Operand decode_operand(System *system, AddressingMode addr_mode);
Operand decode_operand(AddressingMode addr_mode);
byte read_operand(System *system, Operand operand);
byte read_operand(Operand operand);
char *get_addr_mode_name(AddressingMode addr_mode);

View File

@ -6,6 +6,7 @@
#include "log.h"
#include "memory.h"
#include "../include/rom.h"
#include "cpu.h"
#define RAM_MAX_ADDR 0x2000
#define RAM_BANK_SIZE 0x800
@ -14,36 +15,34 @@
#define APU_MAX_ADDR 0x4020
#define MAX_ADDR 0xffff
byte mem_get_byte(System *system, address addr) {
byte ram[RAM_SIZE];
byte mem_get_byte(address addr) {
assert(addr <= MAX_ADDR);
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];
ppu_read_register(reg);
}
if (addr >= PPU_MAX_ADDR && addr < APU_MAX_ADDR) {
byte apu_addr = addr - PPU_MAX_ADDR;
return system->apu_registers[apu_addr];
}
return system->ram[addr];
return ram[addr];
}
word mem_get_word(System *system, address addr) {
byte *mem_get_ptr(address addr) {
assert(addr <= MAX_ADDR);
return &ram[addr];
}
word mem_get_word(address addr) {
assert(addr < MAX_ADDR);
if (addr >= RAM_MAX_ADDR && addr < APU_MAX_ADDR) {
assert(false);
}
word word = system->ram[addr];
word += system->ram[addr + 1] << 8; // Little endian
word word = ram[addr];
word += ram[addr + 1] << 8; // Little endian
return word;
}
void mem_set_byte(System *system, address addr, byte byte) {
void mem_set_byte(address addr, byte byte) {
assert(addr < MAX_ADDR);
log_trace("Writing '%02x' to address 0x%04x", byte, addr);
@ -54,24 +53,24 @@ void mem_set_byte(System *system, address addr, byte byte) {
// 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;
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;
address ram_addr = reg_addr + PPU_BANK_SIZE * i + RAM_MAX_ADDR;
ram[ram_addr] = byte;
}
ppu_write_register(&system->ppu, reg_addr);
ppu_write_register(reg_addr);
} else {
system->ram[addr] = byte;
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;
cpu_trigger_oam_dma();
}
}
}

View File

@ -11,28 +11,34 @@
/**
* Gets a byte from a system's memory.
*
* @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);
byte mem_get_byte(address addr);
/**
* Gets a pointer to a byte in the memory.
* Should not be used by the CPU, because the PPU will not be triggered if reading some addresses.
*
* @param addr The address to get a pointer to
* @return A pointer to the byte in memory
*/
byte* mem_get_ptr(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);
word mem_get_word(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);
void mem_set_byte(address addr, byte value);
#endif //NESEMULATOR_MEMORY_H

732
cpu/op.c

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ enum op_code_base {
OP_CODE_BASE_SBC = 0xe0
};
void process_op_code(System *system, byte op);
void process_op_code(byte op);
AddressingMode get_op_addr_mode(byte op_code);

View File

@ -7,24 +7,24 @@
#include "../cpu/cpu.h"
void cv_print(CpuView *view) {
window_print(view->window, 0, 0, "PC: $%04x", view->cpu->program_counter);
window_print(view->window, 0, 1, "SP: %02x", view->cpu->stack_pointer);
window_print(view->window, 0, 2, "A: %02x", view->cpu->accumulator);
window_print(view->window, 0, 3, "X: %02x", view->cpu->x);
window_print(view->window, 0, 4, "Y: %02x", view->cpu->y);
window_print(view->window, 0, 5, "C: %01x", cpu_get_flag(view->cpu, CPU_STATUS_CARRY_MASK));
window_print(view->window, 0, 6, "Z: %01x", cpu_get_flag(view->cpu, CPU_STATUS_ZERO_MASK));
window_print(view->window, 0, 7, "I: %01x", cpu_get_flag(view->cpu, CPU_STATUS_INTERRUPT_DISABLE_MASK));
window_print(view->window, 0, 8, "D: %01x", cpu_get_flag(view->cpu, CPU_STATUS_DECIMAL_MASK));
window_print(view->window, 0, 9, "B: %01x", cpu_get_flag(view->cpu, CPU_STATUS_B_MASK));
window_print(view->window, 0, 10, "O: %01x", cpu_get_flag(view->cpu, CPU_STATUS_OVERFLOW_MASK));
window_print(view->window, 0, 11, "N: %01x", cpu_get_flag(view->cpu, CPU_STATUS_NEGATIVE_MASK));
CPU *cpu_state = cpu_get_state();
window_print(view->window, 0, 0, "PC: $%04x", cpu_state->program_counter);
window_print(view->window, 0, 1, "SP: %02x", cpu_state->stack_pointer);
window_print(view->window, 0, 2, "A: %02x", cpu_state->accumulator);
window_print(view->window, 0, 3, "X: %02x", cpu_state->x);
window_print(view->window, 0, 4, "Y: %02x", cpu_state->y);
window_print(view->window, 0, 5, "C: %01x", cpu_get_flag(CPU_STATUS_CARRY_MASK));
window_print(view->window, 0, 6, "Z: %01x", cpu_get_flag(CPU_STATUS_ZERO_MASK));
window_print(view->window, 0, 7, "I: %01x", cpu_get_flag(CPU_STATUS_INTERRUPT_DISABLE_MASK));
window_print(view->window, 0, 8, "D: %01x", cpu_get_flag(CPU_STATUS_DECIMAL_MASK));
window_print(view->window, 0, 9, "B: %01x", cpu_get_flag(CPU_STATUS_B_MASK));
window_print(view->window, 0, 10, "O: %01x", cpu_get_flag(CPU_STATUS_OVERFLOW_MASK));
window_print(view->window, 0, 11, "N: %01x", cpu_get_flag(CPU_STATUS_NEGATIVE_MASK));
}
CpuView *cv_init(CPU *cpu, int x, int y) {
CpuView *cv_init(int x, int y) {
CpuView *view = malloc(sizeof(CpuView));
view->window = malloc(sizeof(Window));
view->cpu = cpu;
window_init(view->window, x, y, CPU_VIEW_WIDTH, CPU_VIEW_HEIGHT, "CPU VIEW");
cv_print(view);

View File

@ -14,13 +14,12 @@
typedef struct cpu_view {
Window *window;
CPU *cpu;
} CpuView;
/**
* Initializes a CPU view for a system RAM.
*/
CpuView *cv_init(CPU *cpu, int x, int y);
CpuView *cv_init(int x, int y);
void cv_uninit(CpuView *cpu_view);

View File

@ -23,18 +23,18 @@ void debugger_create_window() {
keypad(stdscr, true);
}
LinkedList debugger_create_interactive_windows(System *system) {
LinkedList debugger_create_interactive_windows() {
LinkedList interactive_windows;
InteractWindow *window;
interactive_windows = linked_list_init(true);
window = malloc(sizeof(InteractWindow));
mv_init(window, system->ram, 0, 0);
mv_init(window, 0, 0);
linked_list_add(&interactive_windows, window);
window = malloc(sizeof(InteractWindow));
pv_init(window, system, MEMORY_VIEW_WIDTH, 0);
pv_init(window, MEMORY_VIEW_WIDTH, 0);
linked_list_add(&interactive_windows, window);
return interactive_windows;
@ -62,8 +62,8 @@ void start_debugger(System *system) {
interactive_windows = debugger_create_interactive_windows(system);
current_window = interactive_windows.current->data;
cpu_view = cv_init(&system->cpu, 0, MEMORY_VIEW_HEIGHT);
ppu_view = ppv_init(&system->ppu, CPU_VIEW_WIDTH, MEMORY_VIEW_HEIGHT);
cpu_view = cv_init(0, MEMORY_VIEW_HEIGHT);
ppu_view = ppv_init(CPU_VIEW_WIDTH, MEMORY_VIEW_HEIGHT);
cursor_enable(&current_window->cursor);

View File

@ -7,6 +7,6 @@
#include "../include/system.h"
void start_debugger(System *system);
void start_debugger();
#endif //NESEMULATOR_DEBUGGER_H

View File

@ -3,6 +3,7 @@
#include "memory_view.h"
#include "dialog.h"
#include "keys.h"
#include "../cpu/memory.h"
//
// Created by william on 6/1/24.
@ -40,10 +41,9 @@ void mv_handle_key_down(InteractWindow *window, int keycode) {
}
}
void mv_init(InteractWindow *interact, ram ram, int x, int y) {
void mv_init(InteractWindow *interact, int x, int y) {
MemoryView *view = malloc(sizeof(MemoryView));
view->window = interact;
view->ram = ram;
view->base_address = 0x0000;
interact->view = view;
@ -61,7 +61,7 @@ void mv_init(InteractWindow *interact, ram ram, int x, int y) {
void mv_print(MemoryView *view) {
for (int line = 0; line <= MEMORY_VIEW_LINE_COUNT; line++) {
address line_address = view->base_address + line * (MEMORY_VIEW_LINE_BYTE_COUNT + 1);
byte *data = &view->ram[line_address];
byte *data = mem_get_ptr(line_address);
mv_write_line(view, line, line_address, data);
}

View File

@ -18,7 +18,6 @@
typedef struct memory_view {
InteractWindow *window;
byte *ram;
address base_address;
} MemoryView;
@ -27,9 +26,8 @@ typedef struct memory_view {
* The viewer base address will be set to 0x0000, and the cursor (0, 0).
* The content of the memory will be printed on a new curses window.
* @param view A pointer to the view to initialize
* @param ram A pointer to the RAM
*/
void mv_init(InteractWindow *interact, ram ram, int x, int y);
void mv_init(InteractWindow *interact, int x, int y);
/**
* Prints the RAM content from the viewer base address.

View File

@ -6,7 +6,7 @@
#include "ppu_view.h"
void ppv_print_line(PpuView *view, byte reg, int line, char *fmt) {
int reg_value = view->ppu->registers[reg];
int reg_value = ppu_get_state()->registers[reg];
window_print(view->window, 0, line, fmt);
for (int i = 0; i < 0x8; i++) {
@ -33,10 +33,9 @@ void ppv_print(PpuView *view) {
ppv_print_line(view, PPU_REGISTER_DATA, 7, " DATA:");
}
PpuView *ppv_init(PPU *ppu, int x, int y) {
PpuView *ppv_init(int x, int y) {
PpuView *view = malloc(sizeof(PpuView));
view->window = malloc(sizeof(Window));
view->ppu = ppu;
window_init(view->window, x, y, PPU_VIEW_WIDTH, PPU_VIEW_HEIGHT, "PPU VIEW");
ppv_print(view);

View File

@ -13,10 +13,9 @@
typedef struct ppu_view {
Window *window;
PPU *ppu;
} PpuView;
PpuView *ppv_init(PPU *ppu, int x, int y);
PpuView *ppv_init(int x, int y);
void ppv_uninit(PpuView *ppu_view);

View File

@ -19,7 +19,7 @@ void decode_operands(ProgramView *view) {
exit(EXIT_FAILURE);
}
byte op_code = view->ram[pc];
byte op_code = mem_get_byte(pc);
operand->addr = pc;
operand->op_code = op_code;
operand->addr_mode = get_op_addr_mode(op_code);
@ -36,12 +36,12 @@ void decode_operands(ProgramView *view) {
case ADDR_MODE_ZERO_PAGE:
case ADDR_MODE_ZERO_PAGE_INDEXED_X:
case ADDR_MODE_ZERO_PAGE_INDEXED_Y:
operand->value = view->ram[pc];
operand->value = mem_get_byte(pc);
pc += 1;
break;
default:
operand->value = view->ram[pc];
operand->value += view->ram[pc + 1] << 8;
operand->value = mem_get_byte(pc);
operand->value += mem_get_byte(pc + 1) << 8;
pc += 2;
break;
}
@ -183,11 +183,10 @@ void pv_deinit(InteractWindow *window) {
linked_list_uninit(&view->operands);
}
void pv_init(InteractWindow *interact, System *system, int x, int y) {
void pv_init(InteractWindow *interact, int x, int y) {
ProgramView *view = malloc(sizeof(ProgramView));
view->window = interact;
view->ram = system->ram;
view->pc = &system->cpu.program_counter;
view->pc = &cpu_get_state()->program_counter;
view->operands = linked_list_init(false);
interact->view = view;

View File

@ -25,13 +25,12 @@ typedef struct debug_operand {
typedef struct program_view {
InteractWindow *window;
byte *ram;
address *pc;
LinkedList operands;
LinkedListNode *first_operand_node;
LinkedListNode *last_operand_node;
} ProgramView;
void pv_init(InteractWindow *interact, System *system, int x, int y);
void pv_init(InteractWindow *interact, int x, int y);
#endif //NESEMULATOR_PROGRAM_VIEW_H

View File

@ -1,7 +1,7 @@
/*
* =====================================================================================
*
* Filename: cpu.h
* Filename: cpu_state.h
*
* Description: 6502 CPU emulator headers
*
@ -22,8 +22,8 @@
#ifndef NESEMULATOR_CPU_H
#define NESEMULATOR_CPU_H
void cpu_init(CPU *cpu);
void cpu_init();
void cpu_cycle(System *system);
void cpu_cycle();
#endif

View File

@ -10,7 +10,7 @@
typedef struct mapper {
address prg_rom_start_addr;
void (*post_prg_load)(ram, unsigned int);
void (*post_prg_load)(unsigned int);
} Mapper;
enum MapperType {

View File

@ -47,8 +47,8 @@
#define PPU_MASK_NONE 0xff
typedef struct ppu {
byte* registers;
byte* oam_dma_register;
byte *registers;
byte *oam_dma_register;
byte vram[PPU_VRAM_SIZE];
byte oam[PPU_OAM_SIZE];
bool odd_frame;
@ -56,15 +56,19 @@ typedef struct ppu {
address t;
byte x;
bool w;
void (*trigger_nmi)();
} PPU;
PPU *ppu_get_state();
/**
* 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);
void ppu_init(byte *registers_ram, byte *oam_dma_register);
/**
* Cycles the PPU.
@ -72,7 +76,7 @@ void ppu_init(PPU *ppu, byte *registers_ram, byte *oam_dma_register);
* @param ppu
* @param ram
*/
void ppu_cycle(PPU *ppu);
void ppu_cycle();
/**
* Read a flag from the PPU registers.
@ -80,7 +84,7 @@ void ppu_cycle(PPU *ppu);
* @param reg The register index
* @param mask The flag mask
*/
bool ppu_read_flag(PPU *ppu, size_t reg, byte mask);
bool ppu_read_flag(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).
@ -89,8 +93,8 @@ bool ppu_read_flag(PPU *ppu, size_t reg, byte mask);
* @param reg The register index
* @param mask The value mask
*/
void ppu_read_register(PPU *ppu, byte reg);
void ppu_read_register(byte reg);
void ppu_write_register(PPU *ppu, byte reg);
void ppu_write_register(byte reg);
#endif //NESEMULATOR_PPU_H

View File

@ -24,9 +24,8 @@ typedef struct {
* Loads a ROM from a specified file path.
*
* @param path The file path
* @param rom ROM
* @return A boolean indicating a success (true) or an error.
*/
bool rom_load(char *path, System *system);
bool rom_load(char *path);
#endif //NESEMULATOR_ROM_H

View File

@ -19,49 +19,33 @@
#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;
bool nmi_requested;
} 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_init();
void system_start(System *system);
void system_start();
/**
* Starts the main loop of a system.
*
* @param system The system
*/
void system_loop(System *system);
void system_loop();
/**
* De-initialize the components of a system.
*
* @param system The system to de-initialize
*/
void system_uninit(System *system);
void system_uninit();
unsigned int system_get_cycles();
void system_add_cycles(unsigned int cycles);
Mapper *system_get_mapper();
#endif //NESEMULATOR_SYSTEM_H

View File

@ -12,7 +12,7 @@ typedef unsigned char byte;
typedef unsigned short address;
typedef unsigned short word;
typedef byte ram[RAM_SIZE];
//typedef byte ram[RAM_SIZE];
typedef byte vram[VRAM_SIZE];
#endif //NESEMULATOR_TYPES_H

16
main.c
View File

@ -23,22 +23,20 @@
#include "include/system.h"
int main() {
System system;
log_set_level(LOG_INFO);
system_init(&system);
system_init();
char *rom_path = "../test_roms/smb.nes";
if (!rom_load(rom_path, &system)) {
system_uninit(&system);
if (!rom_load(rom_path)) {
system_uninit();
return EXIT_FAILURE;
}
system_start(&system);
// start_debugger(&system);
system_loop(&system);
system_start();
// start_debugger();
system_loop();
system_uninit(&system);
system_uninit();
return EXIT_SUCCESS;
}

View File

@ -1,19 +1,20 @@
#include "../include/mapper.h"
#include "../include/rom.h"
#include "../cpu/memory.h"
#include <string.h>
#define SIMPLE_MAPPER_PRG_START_ADDR 0x8000
#define PRG_PART_SIZE 0x4000 // 16Kb
void post_prg_load(ram ram, unsigned int prg_size) {
void post_prg_load(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];
byte *source = mem_get_ptr(SIMPLE_MAPPER_PRG_START_ADDR);
byte *destination = mem_get_ptr(SIMPLE_MAPPER_PRG_START_ADDR + PRG_PART_SIZE);
memcpy(destination, source, PRG_PART_SIZE);
}

View File

@ -14,31 +14,56 @@
// 10. This is where I'm stuck. I think I need to read the "sprites" section of https://wiki.nesdev.com/w/index.php/PPU_rendering very carefully.
//
#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;
PPU ppu_state;
void ppu_init(byte *registers_ram, byte *oam_dma_register) {
ppu_state.registers = registers_ram;
ppu_state.registers[PPU_REGISTER_CTRL] = 0x00;
ppu_state.registers[PPU_REGISTER_MASK] = 0x00;
ppu_state.registers[PPU_REGISTER_STATUS] = 0x00;
ppu_state.registers[PPU_REGISTER_OAM_ADDR] = 0x00;
ppu_state.registers[PPU_REGISTER_OAM_DATA] = 0x00;
ppu_state.registers[PPU_REGISTER_SCROLL] = 0x00;
ppu_state.registers[PPU_REGISTER_ADDR] = 0x00;
ppu_state.registers[PPU_REGISTER_DATA] = 0x00;
ppu_state.oam_dma_register = oam_dma_register;
ppu_state.odd_frame = false;
}
PPU *ppu_get_state() {
return &ppu_state;
}
void ppu_status_set(byte mask, bool enabled) {
if (enabled) {
ppu_state.registers[PPU_REGISTER_STATUS] |= mask;
} else {
ppu_state.registers[PPU_REGISTER_STATUS] &= ~mask;
}
}
long frame = 0;
int x, y = 0;
void ppu_cycle(PPU *ppu) {
void ppu_cycle() {
if (x == 1) {
if (y == 241) {
// VBlank start
ppu_status_set(PPU_STATUS_VBLANK, true);
}
if (y == 261) {
// VBlank clear
ppu_status_set(PPU_STATUS_VBLANK, false);
}
}
int frame_width = 341;
int frame_height = 262;
bool rendering_enabled = ppu_read_flag(ppu, PPU_REGISTER_MASK, PPU_MASK_SHOW_BG | PPU_MASK_SHOW_SP);
if (rendering_enabled && ppu->odd_frame) {
bool rendering_enabled = ppu_read_flag(PPU_REGISTER_MASK, PPU_MASK_SHOW_BG | PPU_MASK_SHOW_SP);
if (rendering_enabled && ppu_state.odd_frame) {
// With rendering enabled, the odd frames are shorter
// TODO: and doing the last cycle of the last dummy nametable fetch there instead
frame_width = 339;
@ -54,30 +79,30 @@ void ppu_cycle(PPU *ppu) {
if (y >= frame_height) {
y = 0;
frame++;
ppu->odd_frame = !ppu->odd_frame;
ppu_state.odd_frame = !ppu_state.odd_frame;
}
}
bool ppu_read_flag(PPU *ppu, size_t reg, byte mask) {
return ppu->registers[reg] & mask;
bool ppu_read_flag(size_t reg, byte mask) {
return ppu_state.registers[reg] & mask;
}
//byte ppu_read_register(PPU *ppu, size_t reg, byte mask) {
// return ppu->registers[reg] & mask;
//byte ppu_read_register(size_t reg, byte mask) {
// return ppu_state.registers[reg] & mask;
//}
void ppu_read_register(PPU *ppu, byte reg) {
void ppu_read_register(byte reg) {
if (reg == PPU_REGISTER_STATUS) {
ppu->w = false;
ppu_state.w = false;
}
}
void ppu_write_register(PPU *ppu, byte reg) {
void ppu_write_register(byte reg) {
if (reg == PPU_REGISTER_SCROLL || reg == PPU_REGISTER_ADDR) {
ppu->w = !ppu->w;
ppu_state.w = !ppu_state.w;
}
if (reg == PPU_REGISTER_OAM_DATA) {
ppu->registers[PPU_REGISTER_OAM_ADDR]++;
ppu_state.registers[PPU_REGISTER_OAM_ADDR]++;
}
}

View File

@ -7,6 +7,7 @@
#include "log.h"
#include "../include/rom.h"
#include "../include/system.h"
#include "../cpu/memory.h"
// Flag 6
#define NES_HEADER_FLAG_MIRRORING 0x01
@ -126,21 +127,22 @@ bool rom_ines_read_trainer(FILE *file, INesHeader *header) {
return false;
}
bool rom_ines_read_prg_rom(FILE *file, INesHeader *header, System *system) {
bool rom_ines_read_prg_rom(FILE *file, INesHeader *header) {
unsigned int prg_rom_size = header->prg_rom_size * 16384;
log_debug("Reading %d bytes PRG ROM", prg_rom_size);
if (fread(&system->ram[system->mapper.prg_rom_start_addr], sizeof(byte), prg_rom_size, file) < prg_rom_size) {
byte *prg_rom_location = mem_get_ptr(system_get_mapper()->prg_rom_start_addr);
if (fread(prg_rom_location, sizeof(byte), prg_rom_size, file) < prg_rom_size) {
log_error("Failed to read PRG ROM");
return false;
}
system->mapper.post_prg_load(&system->ram[0], header->prg_rom_size);
system_get_mapper()->post_prg_load(header->prg_rom_size);
return true;
}
bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, System *system) {
bool rom_ines_read_chr_rom(FILE *file, INesHeader *header) {
if (header->chr_rom_size <= 0) {
log_debug("No CHR ROM to read");
return true;
@ -149,7 +151,8 @@ bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, System *system) {
unsigned int chr_rom_size = header->chr_rom_size * 8192;
log_debug("Reading %d bytes CHR ROM", chr_rom_size);
if (fread(system->ppu.vram, sizeof(byte), chr_rom_size, file) < chr_rom_size) {
byte *chr_rom_location = ppu_get_state()->vram;
if (fread(chr_rom_location, sizeof(byte), chr_rom_size, file) < chr_rom_size) {
log_error("Failed to read CHR ROM");
return false;
}
@ -157,11 +160,11 @@ bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, System *system) {
return true;
}
bool rom_ines_read(const char header_buf[ROM_HEADER_SIZE], FILE *file, System *system) {
bool rom_ines_read(const char header_buf[ROM_HEADER_SIZE], FILE *file) {
INesHeader header = read_header(header_buf);
system->rom_header = &header;
// system->rom_header = &header;
return rom_ines_read_trainer(file, &header) &&
rom_ines_read_prg_rom(file, &header, system) &&
rom_ines_read_chr_rom(file, &header, system);
rom_ines_read_prg_rom(file, &header) &&
rom_ines_read_chr_rom(file, &header);
}

View File

@ -8,7 +8,7 @@
#include "ines.c"
#include "../include/system.h"
bool rom_load(char *path, System *system) {
bool rom_load(char *path) {
FILE *file = fopen(path, "r");
if (!file) {
log_error("Failed to open ROM");
@ -28,7 +28,7 @@ bool rom_load(char *path, System *system) {
}
log_info("Reading iNes 1.0 ROM at %s", path);
rom_ines_read(header_buffer, file, system);
rom_ines_read(header_buffer, file);
if (fclose(file) != 0) {
log_error("Failed to close ROM file");

View File

@ -7,25 +7,27 @@
#include "memory.h"
#include <unistd.h>
#include <assert.h>
#include "log.h"
#include "cpu.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];
System current_sys;
cpu_init(&system->cpu);
ppu_init(&system->ppu, registers_base_addr, oam_dma_register);
void system_init() {
byte *registers_base_addr = mem_get_ptr(PPU_REGISTERS_BASE_ADDR);
byte *oam_dma_register = mem_get_ptr(PPU_REGISTER_OAM_DMA_ADDR);
system->mapper = get_mapper(MAPPER_TYPE_SIMPLE);
system->cycle_count = 7;
cpu_init();
ppu_init(registers_base_addr, oam_dma_register);
current_sys.mapper = get_mapper(MAPPER_TYPE_SIMPLE);
current_sys.cycle_count = 7;
}
void system_start(System *system) {
address pc = mem_get_word(system, 0xfffc);
system->cpu.program_counter = pc;
void system_start() {
address pc = mem_get_word(0xfffc);
cpu_get_state()->program_counter = pc;
}
void system_loop(System *system) {
void system_loop() {
assert(CPU_CLOCK_DIVISOR > PPU_CLOCK_DIVISOR);
unsigned int master_cycle_per_frame = MASTER_CLOCK / FRAME_RATE;
@ -37,14 +39,14 @@ void system_loop(System *system) {
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);
while (current_sys.cycle_count < cpu_cycle_per_frame * frame) {
if (cpu_cycle_count == current_sys.cycle_count) {
cpu_cycle();
}
cpu_cycle_count++;
for (int ppu_c = 0; ppu_c < ppu_cycle_per_cpu_cycle; ppu_c++) {
ppu_cycle(&system->ppu);
ppu_cycle();
}
}
@ -53,5 +55,17 @@ void system_loop(System *system) {
}
}
void system_uninit(System *system) {
void system_uninit() {
}
unsigned int system_get_cycles() {
return current_sys.cycle_count;
}
void system_add_cycles(unsigned int cycles) {
current_sys.cycle_count += cycles;
}
Mapper *system_get_mapper() {
return &current_sys.mapper;
}

View File

@ -1,158 +0,0 @@
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>

View File

@ -1,96 +0,0 @@
; 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

@ -1,59 +0,0 @@
.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

@ -1,282 +0,0 @@
; 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

@ -1,118 +0,0 @@
; 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

@ -1,190 +0,0 @@
; 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

View File

@ -1,169 +0,0 @@
; 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

@ -1,37 +0,0 @@
; 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

@ -1,142 +0,0 @@
; 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

@ -1,380 +0,0 @@
; 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

@ -1,32 +0,0 @@
; 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

@ -1,331 +0,0 @@
; 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

@ -1,106 +0,0 @@
; 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

@ -1,61 +0,0 @@
; 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

@ -1,105 +0,0 @@
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

@ -1,243 +0,0 @@
; 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

@ -1,524 +0,0 @@
; 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

View File

@ -1,4 +0,0 @@
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=""
Bookmark: addr=C10F desc=""

Binary file not shown.

View File

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

Binary file not shown.