diff --git a/include/mapper.h b/include/mapper.h index 9717cc5..91805bc 100644 --- a/include/mapper.h +++ b/include/mapper.h @@ -8,9 +8,9 @@ #define NESEMULATOR_MAPPER_H typedef struct mapper { - address prg_rom_start_addr; + byte *(*mem_read)(address); - void (*post_prg_load)(unsigned int); + byte *(*ppu_read)(address); } Mapper; enum MapperType { diff --git a/include/ppu.h b/include/ppu.h index 27619d7..2117125 100644 --- a/include/ppu.h +++ b/include/ppu.h @@ -5,6 +5,7 @@ #include #include #include "types.h" +#include "../ppu/memory.h" #ifndef NESEMULATOR_PPU_H #define NESEMULATOR_PPU_H @@ -52,7 +53,17 @@ #define PPU_MASK_NONE 0xff +#define PATTERN_TABLE_SIZE 0x1000 + +typedef struct ppu_memory { + byte *nametable_0; + byte *nametable_1; + byte *palette; +} PPUMemory; + typedef struct ppu { + PPUMemory memory; + byte *registers; byte *oam_dma_register; byte vram[PPU_VRAM_SIZE]; @@ -63,8 +74,9 @@ typedef struct ppu { byte x; bool w; - bool fetching; + address ppu_address; + PPUTileFetch tile_fetch; unsigned long frame; unsigned int scanline; unsigned int cycle; @@ -79,6 +91,7 @@ PPU *ppu_get_state(); * @param ppu */ void ppu_init(byte *registers_ram, byte *oam_dma_register); +void ppu_uninit(); /** * Cycles the PPU. diff --git a/include/rom.h b/include/rom.h index d2018b3..c55c284 100644 --- a/include/rom.h +++ b/include/rom.h @@ -15,11 +15,17 @@ #define ROM_TRAINER_SIZE 512 typedef struct { + byte header[ROM_HEADER_SIZE]; + bool nametable_mirrored; + byte *prg_rom; + int prg_rom_size; + byte *chr_rom; - void *header; } Rom; +Rom *rom_get(); + /** * Loads a ROM from a specified file path. * @@ -28,4 +34,6 @@ typedef struct { */ bool rom_load(char *path); +void rom_unload(); + #endif //NESEMULATOR_ROM_H \ No newline at end of file diff --git a/main.c b/main.c index af07fe7..64d8364 100644 --- a/main.c +++ b/main.c @@ -27,7 +27,7 @@ int main() { log_set_level(LOG_INFO); system_init(); - char *rom_path = "../test_roms/smb.nes"; + char *rom_path = "../test_roms/dk_japan.nes"; if (!rom_load(rom_path)) { system_uninit(); @@ -51,6 +51,7 @@ int main() { } system_uninit(); + rom_unload(); gui_uninit(); //// start_debugger(); diff --git a/mappers/CMakeLists.txt b/mappers/CMakeLists.txt index 101f4ed..4b4cd9a 100644 --- a/mappers/CMakeLists.txt +++ b/mappers/CMakeLists.txt @@ -1,4 +1,4 @@ -set(SOURCE simple_mapper.c mappers.c) +set(SOURCE nrom.c mappers.c) add_library(nes_mappers ${SOURCE}) diff --git a/mappers/mappers.c b/mappers/mappers.c index 72766ce..bf44fdc 100644 --- a/mappers/mappers.c +++ b/mappers/mappers.c @@ -6,7 +6,7 @@ #include #include "../include/mapper.h" -#include "simple_mapper.c" +#include "nrom.c" Mapper get_mapper(enum MapperType type) { switch (type) { diff --git a/mappers/nrom.c b/mappers/nrom.c new file mode 100644 index 0000000..8875801 --- /dev/null +++ b/mappers/nrom.c @@ -0,0 +1,38 @@ +#include "../include/mapper.h" +#include "../include/rom.h" +#include "../cpu/memory.h" + +#define SIMPLE_MAPPER_PRG_START_ADDR 0x8000 +#define PRG_BANK_SIZE 0x4000 // 16Kb + +byte *nrom_mem_read(address addr) { + if (addr >= SIMPLE_MAPPER_PRG_START_ADDR) { + Rom *rom = rom_get(); + address relative_addr = addr - SIMPLE_MAPPER_PRG_START_ADDR; + + if (addr < PRG_BANK_SIZE || rom->prg_rom_size > PRG_BANK_SIZE) { + return &rom->prg_rom[relative_addr]; + } + + // The second bank is mirrored + return &rom->prg_rom[relative_addr - PRG_BANK_SIZE]; + } + + return NULL; +} + +byte *nrom_ppu_read(address addr) { + if (addr < 0x2000) { + Rom *rom = rom_get(); + return &rom->chr_rom[addr]; + } + + return NULL; +} + +Mapper get_simple_mapper() { + Mapper mapper; + mapper.mem_read = &nrom_mem_read; + mapper.ppu_read = &nrom_ppu_read; + return mapper; +} \ No newline at end of file diff --git a/mappers/simple_mapper.c b/mappers/simple_mapper.c deleted file mode 100644 index 769061d..0000000 --- a/mappers/simple_mapper.c +++ /dev/null @@ -1,26 +0,0 @@ -#include "../include/mapper.h" -#include "../include/rom.h" -#include "../cpu/memory.h" -#include - -#define SIMPLE_MAPPER_PRG_START_ADDR 0x8000 -#define PRG_PART_SIZE 0x4000 // 16Kb - -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 = 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); -} - -Mapper get_simple_mapper() { - Mapper mapper; - mapper.prg_rom_start_addr = SIMPLE_MAPPER_PRG_START_ADDR; - mapper.post_prg_load = &post_prg_load; - return mapper; -} \ No newline at end of file diff --git a/ppu/CMakeLists.txt b/ppu/CMakeLists.txt index 296161b..8724ea9 100644 --- a/ppu/CMakeLists.txt +++ b/ppu/CMakeLists.txt @@ -1,5 +1,7 @@ -set(HEADERS pattern_table.h ppu.h palette.h) -set(SOURCE pattern_table.c ppu.c palette.c) +set(HEADERS pattern_table.h ppu.h palette.h + memory.h) +set(SOURCE pattern_table.c ppu.c palette.c + memory.c) add_library(nes_ppu ${SOURCE} ${HEADERS}) diff --git a/ppu/memory.c b/ppu/memory.c new file mode 100644 index 0000000..d20bbdc --- /dev/null +++ b/ppu/memory.c @@ -0,0 +1,48 @@ +// +// Created by william on 5/17/24. +// + +#include +#include "memory.h" + +void ppu_vram_fetch(PPUVramFetch *fetch, address addr) { + assert(addr < VRAM_SIZE); + + if (fetch->finished) { + fetch->finished = false; + return; + } + + fetch->data = *fetch->vram[addr]; + fetch->finished = true; +} + +void ppu_tile_fetch(PPUTileFetch *fetch, address addr) { + if (fetch->fetch_cycle >= 8) { + fetch->fetch_cycle = 0; + } + + if (fetch->fetch_cycle % 2 == 0) { + // First cycle of a memory fetch + ppu_vram_fetch(&fetch->vram_fetch, addr + (fetch->fetch_cycle / 2)); + } else { + // Second cycle of a fetch, the data should be available + byte data = fetch->vram_fetch.data; + switch (fetch->fetch_cycle) { + case 1: + fetch->nametable = data; + break; + case 3: + fetch->attribute_table = data; + break; + case 5: + fetch->pattern_table_tile_low = data; + break; + case 7: + fetch->pattern_table_tile_high = data; + break; + default: + assert(false); + } + } +} \ No newline at end of file diff --git a/ppu/memory.h b/ppu/memory.h new file mode 100644 index 0000000..b27cf26 --- /dev/null +++ b/ppu/memory.h @@ -0,0 +1,28 @@ +// +// Created by william on 5/17/24. +// + +#include +#include "../include/types.h" + +#ifndef NES_EMULATOR_MEMORY_H +#define NES_EMULATOR_MEMORY_H + +typedef struct ppu_vram_fetch { + vram *vram; + byte data; + bool finished; +} PPUVramFetch; + +typedef struct ppu_tile_fetch { + byte nametable; + byte attribute_table; + byte pattern_table_tile_low; + byte pattern_table_tile_high; +} PPUTileFetch; + +void ppu_vram_fetch(PPUVramFetch *fetch, address addr); + +void ppu_tile_fetch(PPUTileFetch *fetch, address addr); + +#endif //NES_EMULATOR_MEMORY_H diff --git a/ppu/ppu.c b/ppu/ppu.c index 2f35534..ca8a0cc 100644 --- a/ppu/ppu.c +++ b/ppu/ppu.c @@ -12,12 +12,13 @@ // 8. Implement OAMDMA (and OAMDATA I guess, I implemented one on top of the other) // 9. Now you should have sprite data to render. Implement the logic for copying from primary OAM to scanline OAM. I'm doing it all as one step (not smearing it over up to 256 cycles like the actual hardware). Skip the confusing sprite overflow junk. // 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 +#include #include "ppu.h" #include "../include/ppu.h" #include "../cpu/cpu.h" +#include "../include/rom.h" #define PPU_VISIBLE_FRAME_END 240 #define PPU_POST_RENDER_LINE_START PPU_VISIBLE_FRAME_END @@ -29,22 +30,11 @@ 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_state.fetching = false; + memset(&ppu_state, 0, sizeof(PPU)); - ppu_state.frame = 0; - ppu_state.scanline = 0; - ppu_state.cycle = 0; + ppu_state.oam_dma_register = oam_dma_register; + ppu_state.registers = registers_ram; + memset(&ppu_state.registers, 0, 8); } PPU *ppu_get_state() { @@ -68,29 +58,28 @@ void ppu_trigger_vbl_nmi() { cpu_trigger_nmi(); } -typedef struct { - byte nametable; - byte attribute_table; - byte pattern_table_tile_low; - byte pattern_table_tile_high; - - byte fetch_tick; -} Tile; -Tile tile; - void ppu_visible_frame(unsigned int cycle) { if (cycle == 0) { // Idle... } else if (cycle <= 256) { - switch (tile.fetch_tick) { - + byte tile_fetch_cycle = (cycle - 1) % 8; + switch (tile_fetch_cycle) { + case 1: + ppu_state.tile_fetch.nametable = ppu_read(ppu_state.ppu_address); + break; + case 3: + ppu_state.tile_fetch.attribute_table = ppu_read(ppu_state.ppu_address); + break; + case 5: + ppu_state.tile_fetch.pattern_table_tile_low = ppu_read(ppu_state.ppu_address); + break; + case 7: + ppu_state.tile_fetch.pattern_table_tile_high = ppu_read(ppu_state.ppu_address); + ppu_state.ppu_address++; + break; + default: + break; } - - if (tile.nametable == 0) { - - } - - tile.fetch_tick++; } else if (cycle <= 320) { // OAMADDR is cleared on sprite loading for pre-render and visible lines ppu_write_reg(PPU_REGISTER_OAM_ADDR, 0); @@ -168,8 +157,8 @@ byte ppu_read_reg(byte reg) { // Access to VRAM memory is slow, so reading it a first time generally return the memory at the previous address. // So we get the data first, then update the register. byte data = ppu_state.registers[reg]; - ppu_state.registers[reg] = ppu_state.vram[ppu_state.v]; - if (ppu_state.v > 0x3eff) { + ppu_state.registers[reg] = ppu_state.vram[ppu_state.ppu_address]; + if (ppu_state.ppu_address > 0x3eff) { // But the palette data is returned immediately data = ppu_state.registers[reg]; } @@ -180,9 +169,9 @@ byte ppu_read_reg(byte reg) { increment = 32; } - ppu_state.v += increment; - if (ppu_state.v >= PPU_VRAM_SIZE) { - ppu_state.v -= PPU_VRAM_SIZE; + ppu_state.ppu_address += increment; + if (ppu_state.ppu_address >= PPU_VRAM_SIZE) { + ppu_state.ppu_address -= PPU_VRAM_SIZE; } return data; @@ -217,7 +206,7 @@ void ppu_write_reg(byte reg, byte data) { } } else if (reg == PPU_REGISTER_ADDR) { ppu_state.w = !ppu_state.w; - address addr = ppu_state.v; + address addr = ppu_state.ppu_address; if (ppu_state.w) { addr &= 0xff & data; } else { @@ -227,18 +216,18 @@ void ppu_write_reg(byte reg, byte data) { if (addr >= PPU_VRAM_SIZE) { addr -= PPU_VRAM_SIZE; } - ppu_state.v = addr; + ppu_state.ppu_address = addr; } else if (reg == PPU_REGISTER_DATA) { - ppu_state.vram[ppu_state.v] = data; + ppu_state.vram[ppu_state.ppu_address] = data; byte increment = 1; if (ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_VRAM_ADDR_INCREMENT)) { increment = 32; } - ppu_state.v += increment; - if (ppu_state.v >= PPU_VRAM_SIZE) { - ppu_state.v -= PPU_VRAM_SIZE; + ppu_state.ppu_address += increment; + if (ppu_state.ppu_address >= PPU_VRAM_SIZE) { + ppu_state.ppu_address -= PPU_VRAM_SIZE; } } else if (reg == PPU_REGISTER_OAM_DATA) { byte oam_addr = ppu_state.registers[PPU_REGISTER_OAM_ADDR]; @@ -251,5 +240,42 @@ void ppu_write_reg(byte reg, byte data) { byte ppu_read(address addr) { assert(addr < PPU_VRAM_SIZE); - return ppu_state.vram[addr]; + address relative_addr; + + if (addr < 0x2000) { + return *system_get_mapper()->ppu_read(addr); + } else if (addr < 0x2400) { + relative_addr = addr - 0x2000; + return ppu_state.memory.nametable_0[relative_addr]; + } else if (addr < 0x2800) { + relative_addr = addr - 0x2400; + byte *nametable; + + if (rom_get()->nametable_mirrored) { + nametable = ppu_state.memory.nametable_1; + } else { + nametable = ppu_state.memory.nametable_0; + } + + return nametable[relative_addr]; + } else if (addr < 0x2c00) { + relative_addr = addr - 0x2800; + byte *nametable; + + if (rom_get()->nametable_mirrored) { + nametable = ppu_state.memory.nametable_0; + } else { + nametable = ppu_state.memory.nametable_1; + } + + return nametable[relative_addr]; + } else if (addr < 0x3000) { + relative_addr = addr - 0x2c00; + return ppu_state.memory.nametable_1[relative_addr]; + } else if (addr >= 0x3f00) { + relative_addr = (addr - 0x3f00) % 0x20; + return ppu_state.memory.palette[relative_addr]; + } + + assert(false); } \ No newline at end of file diff --git a/rom/ines.c b/rom/ines.c index e3f8732..987e900 100644 --- a/rom/ines.c +++ b/rom/ines.c @@ -4,6 +4,7 @@ #include #include +#include #include "log.h" #include "../include/rom.h" #include "../include/system.h" @@ -62,7 +63,7 @@ bool rom_is_ines(const char header[16]) { return header[0] == 'N' && header[1] == 'E' && header[2] == 'S'; } -INesHeader read_header(const char header_buf[16]) { +INesHeader read_header(const byte *header_buf) { INesHeader header; unsigned char flag6 = header_buf[6]; @@ -127,12 +128,13 @@ bool rom_ines_read_trainer(FILE *file, INesHeader *header) { return false; } -bool rom_ines_read_prg_rom(FILE *file, INesHeader *header) { +bool rom_ines_read_prg_rom(FILE *file, INesHeader *header, Rom *rom) { unsigned int prg_rom_size = header->prg_rom_size * 16384; log_debug("Reading %d bytes PRG ROM", 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) { + rom->prg_rom_size = prg_rom_size; + rom->prg_rom = malloc(prg_rom_size); + if (fread(rom->prg_rom, sizeof(byte), prg_rom_size, file) < prg_rom_size) { log_error("Failed to read PRG ROM"); return false; } @@ -142,7 +144,7 @@ bool rom_ines_read_prg_rom(FILE *file, INesHeader *header) { return true; } -bool rom_ines_read_chr_rom(FILE *file, INesHeader *header) { +bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, Rom *rom) { if (header->chr_rom_size <= 0) { log_debug("No CHR ROM to read"); return true; @@ -151,8 +153,8 @@ bool rom_ines_read_chr_rom(FILE *file, INesHeader *header) { unsigned int chr_rom_size = header->chr_rom_size * 8192; log_debug("Reading %d bytes CHR ROM", 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) { + rom->chr_rom = malloc(sizeof(byte) * chr_rom_size); + if (fread(rom->chr_rom, sizeof(byte), chr_rom_size, file) < chr_rom_size) { log_error("Failed to read CHR ROM"); return false; } @@ -160,11 +162,11 @@ bool rom_ines_read_chr_rom(FILE *file, INesHeader *header) { return true; } -bool rom_ines_read(const char header_buf[ROM_HEADER_SIZE], FILE *file) { - INesHeader header = read_header(header_buf); -// system->rom_header = &header; +bool rom_ines_read(Rom *rom, FILE *file) { + INesHeader header = read_header(rom->header); + rom->nametable_mirrored = header.flags.nametable_mirrored; return rom_ines_read_trainer(file, &header) && - rom_ines_read_prg_rom(file, &header) && - rom_ines_read_chr_rom(file, &header); + rom_ines_read_prg_rom(file, &header, rom) && + rom_ines_read_chr_rom(file, &header, rom); } \ No newline at end of file diff --git a/rom/rom.c b/rom/rom.c index 3d1b528..75f05b2 100644 --- a/rom/rom.c +++ b/rom/rom.c @@ -8,6 +8,12 @@ #include "ines.c" #include "../include/system.h" +Rom rom; + +Rom *rom_get() { + return &rom; +} + bool rom_load(char *path) { FILE *file = fopen(path, "r"); if (!file) { @@ -28,7 +34,7 @@ bool rom_load(char *path) { } log_info("Reading iNes 1.0 ROM at %s", path); - rom_ines_read(header_buffer, file); + rom_ines_read(&rom, file); if (fclose(file) != 0) { log_error("Failed to close ROM file"); @@ -36,4 +42,9 @@ bool rom_load(char *path) { } return true; +} + +void rom_unload() { + free(rom.prg_rom); + free(rom.chr_rom); } \ No newline at end of file diff --git a/system.c b/system.c index 9620dea..d2cc7fd 100644 --- a/system.c +++ b/system.c @@ -41,6 +41,7 @@ void system_next_frame() { } void system_uninit() { + ppu_uninit(); } unsigned int system_get_cycles() {