diff --git a/src/audio/apu.h b/src/audio/apu.h index fef25af..356cf94 100644 --- a/src/audio/apu.h +++ b/src/audio/apu.h @@ -12,6 +12,7 @@ #include "noise.h" #include "dmc.h" +// apu_t emulates an NES audio processing unit (APU). typedef struct apu_t { bus_t* bus; @@ -46,12 +47,25 @@ typedef struct apu_t apu_t* apu_create(bus_t* bus, gfx_t* gfx); void apu_destroy(apu_t* apu); + +// apu_reset performs a soft reset on the APU. void apu_reset(apu_t* apu); + +// apu_exec executes a single APU cycle. void apu_exec(apu_t* apu); + +// apu_set_status writes to the APU_STATUS register (0x4015). void apu_set_status(apu_t* apu, uint8_t val); + +// apu_get_sample cycles the apu_t channels and returns a tone. float apu_get_sample(apu_t* apu); + +// apu_queue_audio queues audio samples to be played by SDL. void apu_queue_audio(apu_t* apu, gfx_t* gfx); + +// apu_read_status reads from the APU_STATUS register (0x4015). uint8_t apu_read_status(apu_t* apu); + void apu_set_frame_counter_ctrl(apu_t* apu, uint8_t val); #endif // NES_TOOLS_APU_H diff --git a/src/bus.h b/src/bus.h index 70304d8..4643817 100644 --- a/src/bus.h +++ b/src/bus.h @@ -60,6 +60,7 @@ struct cpu6502_t; struct apu_t; struct ppu_t; +// bus_t emulates an NES bus. typedef struct { // Memory. @@ -81,10 +82,17 @@ typedef struct bus_t* bus_create(mapper_t* mapper); void bus_destroy(bus_t* bus); +// bus_write writes val to addr in main memory. void bus_write(bus_t* bus, uint16_t addr, uint8_t val); + +// bus_read fetches the value at addr from main memory. uint8_t bus_read(bus_t* bus, uint16_t addr); + +// bus_get_ptr gets a pointer to the value at addr in main memory. uint8_t* bus_get_ptr(bus_t* bus, uint16_t addr); +// Eventually set all other NES circuits. bus_read/bus_write will be +// able to access memory-mapped registers. void bus_set_cpu(bus_t* bus, struct cpu6502_t* cpu); void bus_set_apu(bus_t* bus, struct apu_t* apu); void bus_set_ppu(bus_t* bus, struct ppu_t* ppu); diff --git a/src/cpu6502.h b/src/cpu6502.h index 33357a1..e47a45d 100644 --- a/src/cpu6502.h +++ b/src/cpu6502.h @@ -13,6 +13,7 @@ enum INTERRUPT_PENDING = 1 << 1 }; +// Enumerates the possible states of the CPU status register. enum cpu_flag { NEGATIVE = 1 << 7, @@ -24,6 +25,7 @@ enum cpu_flag CARRY = 1 }; +// CPU 6502 opcodes, including unofficial/undocumented. enum cpu_opcode { // Official opcodes. @@ -41,18 +43,21 @@ enum cpu_opcode ISB, RLA, RRA, SLO, SRE, SKB, IGN, }; +// 6502 instruction addressing modes. enum cpu_addr_mode { NONE, IMPL, ACC, REL, IMT, ZPG, ZPG_X, ZPG_Y, ABS, ABS_X, ABS_Y, IND, IND_IDX, IDX_IND, }; +// A CPU instruction combines an opcode and an addressing mode. struct cpu_instr { enum cpu_opcode opcode; enum cpu_addr_mode mode; }; +// 6502 instruction lookup table. static const struct cpu_instr cpu_instr_lookup[256] = { // HI\LO 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xA 0xB 0xC 0xD 0xE 0xF @@ -75,6 +80,7 @@ static const struct cpu_instr cpu_instr_lookup[256] = /* 0xF */ {BEQ, REL}, {SBC, IND_IDX}, NIL_OP, {ISB, IND_IDX}, {NOP, ZPG_X}, {SBC, ZPG_X}, {INC, ZPG_X}, {ISB, ZPG_X}, {SED, IMPL}, {SBC, ABS_Y}, {NOP, IMPL}, {ISB, ABS_Y}, {NOP, ABS_X}, {SBC, ABS_X}, {INC, ABS_X}, {ISB, ABS_X} }; +// cycle times corresponding to each instruction in cpu_instr_lookup. static const uint8_t cpu_cycle_lookup[256] = { // HI/LO 0 1 2 3 4 5 6 7 8 9 A B C D E F @@ -105,6 +111,7 @@ enum cpu_interrupt IRQ }; +// cpu6502_t emulates a 6502 microprocessor. typedef struct cpu6502_t { uint8_t state; @@ -134,9 +141,17 @@ typedef struct cpu6502_t cpu6502_t* cpu_create(bus_t* bus); void cpu_destroy(cpu6502_t* cpu); + +// cpu_reset performs a soft-reset. void cpu_reset(cpu6502_t* cpu); + +// cpu_exec executes a single CPU cycle. void cpu_exec(cpu6502_t* cpu); + +// cpu_dma_suspend suspends the CPU for 513 cycles for DMA transfer. void cpu_dma_suspend(cpu6502_t* cpu); + +// cpu_interrupt queues an interrupt. void cpu_interrupt(cpu6502_t* cpu, enum cpu_interrupt interrupt); #endif // NES_TOOLS_CPU6502_H diff --git a/src/emulator.h b/src/emulator.h index f4083c4..acf5ed4 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -10,18 +10,20 @@ #include "gfx.h" #include "timerx.h" -// Frame rate in Hz +// Frame rate in Hz. #define NTSC_FRAME_RATE 60 #define PAL_FRAME_RATE 50 // Turbo keys toggle rate (Hz): value should be a factor of FRAME_RATE -// and should never exceed FRAME_RATE for best result +// and should never exceed FRAME_RATE for best result. #define NTSC_TURBO_RATE 30 #define PAL_TURBO_RATE 25 // Sleep time when emulator is paused in milliseconds. #define IDLE_SLEEP 50 +// emulator_t tracks the state of the NES emulator. It encapsulates +// all significant NES circuits (CPU, PPU, APU, BUS). typedef struct { cpu6502_t* cpu; @@ -42,9 +44,16 @@ typedef struct } emulator_t; + emulator_t* emulator_create(mapper_t* mapper); +void emulator_destroy(emulator_t* emu); + +// emulator_reset reinitializes the emulator's state (equivalent to +// soft-resetting the NES). void emulator_reset(emulator_t* emu); + +// emulator_exec executes the emulator. It enters a loop that stops +// when the user closes the window or exits the process. void emulator_exec(emulator_t* emu); -void emulator_destroy(emulator_t* emu); #endif // NES_TOOLS_EMULATOR_H diff --git a/src/gfx.h b/src/gfx.h index db27de9..0d09b26 100644 --- a/src/gfx.h +++ b/src/gfx.h @@ -3,6 +3,7 @@ #include "system.h" +// gfx_t interfaces the SDL window/renderer. typedef struct { // Window metadata. @@ -22,8 +23,12 @@ typedef struct } gfx_t; +// gfx_create allocates a new gfx_t, creating a window with the +// specified width, height, and scaling factor. gfx_t* gfx_create(int width, int height, float scale); -void gfx_render(gfx_t* gfx, const uint32_t* buffer); void gfx_destroy(gfx_t* gfx); +// gfx_render writes the texture stored in buffer to the screen. +void gfx_render(gfx_t* gfx, const uint32_t* buffer); + #endif // NES_TOOLS_GFX_H diff --git a/src/joypad.h b/src/joypad.h index d618999..81ba19c 100644 --- a/src/joypad.h +++ b/src/joypad.h @@ -3,6 +3,8 @@ #include "system.h" +// Each button's status is enumerated as a single bit in the joypad's +// status attribute. enum { TURBO_B = 1 << 9, @@ -17,6 +19,7 @@ enum BUTTON_A = 1 }; +// joypad_t maintains the state of an NES joypad. typedef struct { uint8_t strobe; @@ -26,10 +29,23 @@ typedef struct } joypad_t; +// joypad_create initializes a new joypad_t, where player specifies +// the joypad number (two joypads may be used). joypad_t joypad_create(uint8_t player); + +// joypad_read reads from the joypad as though it were mapped to +// main memory (address 0x4016 - 0x4017). uint8_t joypad_read(joypad_t* joy); + +// joypad_write writes to the joypad as though it were mapped to +// main memory (address 0x4016). void joypad_write(joypad_t* joy, uint8_t data); + +// joypad_update updates the status of the joypad according to an SDL +// keyboard event. void joypad_update(joypad_t* joy, SDL_Event* event); + +// joypad_trigger_turbo triggers the turbo button. void joypad_trigger_turbo(joypad_t* joy); #endif // NES_TOOLS_JOYPAD_H diff --git a/src/mapper.h b/src/mapper.h index 68737ab..c7dcff8 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -3,12 +3,18 @@ #include "system.h" +// The NES console had two main variants based on different TV display +// standards: NTSC (used primarily in Japan/USA) and PAL (used in +// Europe, Australia, etc.). The variant is specified in iNES file +// format. enum tv_system { NTSC = 0, PAL }; +// Nametable mirroring configuration. See: +// https://www.nesdev.org/wiki/Mirroring#Nametable_Mirroring enum mirroring { NO_MIRRORING, @@ -17,6 +23,7 @@ enum mirroring FOUR_SCREEN }; +// mapper_t stores data for an iNES mapper/cartridge. typedef struct { uint8_t* chr_rom; @@ -39,16 +46,30 @@ typedef struct } mapper_t; +// mapper_from_file creates a mapper_t instance from a '.nes' file. mapper_t* mapper_from_file(const char* path); void mapper_destroy(mapper_t* mapper); +// mapper_read_rom fetches data from CPU-addressable locations in the +// mapper circuit's memory. If an address is invalid, it returns bus. uint8_t mapper_read_rom(mapper_t* mapper, uint8_t bus, uint16_t addr); + +// mapper_write_rom writes to a CPU-addressable location in the mapper +// circuit's memory. void mapper_write_rom(mapper_t* mapper, uint16_t addr, uint8_t val); +// mapper_read_prg decodes a CPU-addressable address to a value in the +// mapper's PRG ROM memory. uint8_t mapper_read_prg(mapper_t* mapper, uint16_t addr); + +// mapper_read_prg decodes a CPU-addressable address and writes a +// value in the mapper's PRG ROM memory. void mapper_write_prg(mapper_t* mapper, uint16_t addr, uint8_t val); +// mapper_read_chr fetches the value at addr from the mapper's CHR ROM. uint8_t mapper_read_chr(mapper_t* mapper, uint16_t addr); + +// mapper_read_chr writes val to the mapper's CHR ROM at the given addr. void mapper_write_chr(mapper_t* mapper, uint16_t addr, uint8_t val); #endif // NES_TOOLS_MAPPER_H diff --git a/src/ppu.h b/src/ppu.h index d37982f..0150d4e 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -36,6 +36,7 @@ enum Y_SCROLL_BITS = 0x73E0 }; +// ppu_t emulates an NES picture processing unit (PPU). typedef struct ppu_t { size_t frames; @@ -91,21 +92,47 @@ extern uint32_t ppu_palette[64]; ppu_t* ppu_create(bus_t* bus); void ppu_destroy(ppu_t* ppu); + +// ppu_reset performs a soft reset on the ppu. void ppu_reset(ppu_t* ppu); +// ppu_exec executes a single PPU cycle. void ppu_exec(ppu_t* ppu); +// ppu_read_status emulates reading from PPU_STATUS (0x2003). uint8_t ppu_read_status(ppu_t* ppu); + +// ppu_read emulates reading from PPU_DATA (0x2008). uint8_t ppu_read(ppu_t* ppu); + +// ppu_set_ctrl sets the PPU control register (0x2000). void ppu_set_ctrl(ppu_t* ppu, uint8_t ctrl); + +// ppu_write writes to PPU_DATA (0x2008). void ppu_write(ppu_t* ppu, uint8_t val); + +// ppu_dma triggers an OAM DMA transfer that takes ~513 CPU cycles. void ppu_dma(ppu_t* ppu, uint8_t addr); + +// ppu_set_scroll writes to the scroll register (0x2005). void ppu_set_scroll(ppu_t* ppu, uint8_t val); + +// ppu_set_addr writes to the PPU_ADDR register (0x2006). void ppu_set_addr(ppu_t* ppu, uint8_t addr); + +// ppu_set_oam_addr writes to the OAM_ADDR register (0x2003). void ppu_set_oam_addr(ppu_t* ppu, uint8_t addr); + +// ppu_read_oam reads from the OAM_DATA register (0x2004). uint8_t ppu_read_oam(ppu_t* ppu); + +// ppu_write_oam writes to the OAM_DATA register (0x2004). void ppu_write_oam(ppu_t* ppu, uint8_t val); + +// ppu_read_vram reads from the PPU's internal video memory. uint8_t ppu_read_vram(ppu_t* ppu, uint16_t addr); + +// ppu_read_vram writes to the PPU's internal video memory. void ppu_write_vram(ppu_t* ppu, uint16_t addr, uint8_t val); #endif // NES_TOOLS_PPU_H diff --git a/src/snapshot.h b/src/snapshot.h index 2a3b8bb..bd3047c 100644 --- a/src/snapshot.h +++ b/src/snapshot.h @@ -4,6 +4,7 @@ #include "system.h" #include "emulator.h" +// snapshot_t stores the emulator's state at a some point in time. typedef struct { cpu6502_t* cpu; @@ -19,9 +20,17 @@ typedef struct } snapshot_t; +// snapshot_create copies the emulator's state into a new snapshot_t. snapshot_t* snapshot_create(emulator_t* emu); + +// snapshot_update copies an emulator's state into an already +// initialized snapshot_t struct. void snapshot_update(snapshot_t* snap, emulator_t* emu); + +// snapshot_restore writes the snapshot's state into the given emulator. void snapshot_restore(snapshot_t* snap, emulator_t* emu); + +// snapshot_destroy safely deallocates a snapshot_t instance. void snapshot_destroy(snapshot_t* snap); #endif // NES_TOOLS_SNAPSHOT_H diff --git a/src/timerx.h b/src/timerx.h index d67fc0c..d48cf17 100644 --- a/src/timerx.h +++ b/src/timerx.h @@ -3,6 +3,8 @@ #include "system.h" +// timerx_t implements a timer that measures the execution time of +// emulator routines and then sleeps for the remaining cycle time. typedef struct { struct timespec start; @@ -12,11 +14,25 @@ typedef struct } timerx_t; +// timerx_create returns an initialized timerx with the given cycle +// period in nanoseconds. timerx_t timerx_create(uint64_t period); + +// timerx_mark_start marks the start of a cycle period. void timerx_mark_start(timerx_t* timer); + +// timerx_mark_end marks the end of a cycle period. void timerx_mark_end(timerx_t* timer); + +// timerx_adjusted_wait sleeps for (period - execution time) +// nanoseconds. int timerx_adjusted_wait(timerx_t* timer); + +// timerx_wait waits for the specified period (in milliseconds). int timerx_wait(uint64_t period_ms); + +// timerx_get_diff returns the elapsed time between timerx_mark_start +// and timerx_mark_end. double timerx_get_diff(timerx_t* timer); #endif // NES_TOOLS_TIMERX_H