Compare commits
2 Commits
7caf88171f
...
8dd1fe6037
Author | SHA1 | Date |
---|---|---|
FyloZ | 8dd1fe6037 | |
FyloZ | 22401f30ac |
|
@ -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
156
cpu/cpu.c
|
@ -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;
|
||||
}
|
39
cpu/cpu.h
39
cpu/cpu.h
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
45
cpu/memory.c
45
cpu/memory.c
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
18
cpu/memory.h
18
cpu/memory.h
|
@ -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
|
||||
|
|
2
cpu/op.h
2
cpu/op.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(¤t_window->cursor);
|
||||
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
|
||||
#include "../include/system.h"
|
||||
|
||||
void start_debugger(System *system);
|
||||
void start_debugger();
|
||||
|
||||
#endif //NESEMULATOR_DEBUGGER_H
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
16
main.c
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
77
ppu/ppu.c
77
ppu/ppu.c
|
@ -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]++;
|
||||
}
|
||||
}
|
21
rom/ines.c
21
rom/ines.c
|
@ -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);
|
||||
}
|
|
@ -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");
|
||||
|
|
48
system.c
48
system.c
|
@ -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 ¤t_sys.mapper;
|
||||
}
|
|
@ -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>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
Binary file not shown.
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
|
@ -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.
|
@ -1 +0,0 @@
|
|||
BreakPoint: startAddr=00000022 endAddr=00000000 flags=ER--X- condition="" desc=""
|
Binary file not shown.
Loading…
Reference in New Issue