This commit is contained in:
william 2024-05-23 22:44:52 -04:00
parent 608a2b7578
commit 07d044c47f
15 changed files with 246 additions and 94 deletions

View File

@ -8,9 +8,9 @@
#define NESEMULATOR_MAPPER_H #define NESEMULATOR_MAPPER_H
typedef struct mapper { typedef struct mapper {
address prg_rom_start_addr; byte *(*mem_read)(address);
void (*post_prg_load)(unsigned int); byte *(*ppu_read)(address);
} Mapper; } Mapper;
enum MapperType { enum MapperType {

View File

@ -5,6 +5,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "types.h" #include "types.h"
#include "../ppu/memory.h"
#ifndef NESEMULATOR_PPU_H #ifndef NESEMULATOR_PPU_H
#define NESEMULATOR_PPU_H #define NESEMULATOR_PPU_H
@ -52,7 +53,17 @@
#define PPU_MASK_NONE 0xff #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 { typedef struct ppu {
PPUMemory memory;
byte *registers; byte *registers;
byte *oam_dma_register; byte *oam_dma_register;
byte vram[PPU_VRAM_SIZE]; byte vram[PPU_VRAM_SIZE];
@ -63,8 +74,9 @@ typedef struct ppu {
byte x; byte x;
bool w; bool w;
bool fetching; address ppu_address;
PPUTileFetch tile_fetch;
unsigned long frame; unsigned long frame;
unsigned int scanline; unsigned int scanline;
unsigned int cycle; unsigned int cycle;
@ -79,6 +91,7 @@ PPU *ppu_get_state();
* @param ppu * @param ppu
*/ */
void ppu_init(byte *registers_ram, byte *oam_dma_register); void ppu_init(byte *registers_ram, byte *oam_dma_register);
void ppu_uninit();
/** /**
* Cycles the PPU. * Cycles the PPU.

View File

@ -15,11 +15,17 @@
#define ROM_TRAINER_SIZE 512 #define ROM_TRAINER_SIZE 512
typedef struct { typedef struct {
byte header[ROM_HEADER_SIZE];
bool nametable_mirrored;
byte *prg_rom; byte *prg_rom;
int prg_rom_size;
byte *chr_rom; byte *chr_rom;
void *header;
} Rom; } Rom;
Rom *rom_get();
/** /**
* Loads a ROM from a specified file path. * Loads a ROM from a specified file path.
* *
@ -28,4 +34,6 @@ typedef struct {
*/ */
bool rom_load(char *path); bool rom_load(char *path);
void rom_unload();
#endif //NESEMULATOR_ROM_H #endif //NESEMULATOR_ROM_H

3
main.c
View File

@ -27,7 +27,7 @@ int main() {
log_set_level(LOG_INFO); log_set_level(LOG_INFO);
system_init(); system_init();
char *rom_path = "../test_roms/smb.nes"; char *rom_path = "../test_roms/dk_japan.nes";
if (!rom_load(rom_path)) { if (!rom_load(rom_path)) {
system_uninit(); system_uninit();
@ -51,6 +51,7 @@ int main() {
} }
system_uninit(); system_uninit();
rom_unload();
gui_uninit(); gui_uninit();
//// start_debugger(); //// start_debugger();

View File

@ -1,4 +1,4 @@
set(SOURCE simple_mapper.c mappers.c) set(SOURCE nrom.c mappers.c)
add_library(nes_mappers ${SOURCE}) add_library(nes_mappers ${SOURCE})

View File

@ -6,7 +6,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "../include/mapper.h" #include "../include/mapper.h"
#include "simple_mapper.c" #include "nrom.c"
Mapper get_mapper(enum MapperType type) { Mapper get_mapper(enum MapperType type) {
switch (type) { switch (type) {

38
mappers/nrom.c Normal file
View File

@ -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;
}

View File

@ -1,26 +0,0 @@
#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(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;
}

View File

@ -1,5 +1,7 @@
set(HEADERS pattern_table.h ppu.h palette.h) set(HEADERS pattern_table.h ppu.h palette.h
set(SOURCE pattern_table.c ppu.c palette.c) memory.h)
set(SOURCE pattern_table.c ppu.c palette.c
memory.c)
add_library(nes_ppu ${SOURCE} ${HEADERS}) add_library(nes_ppu ${SOURCE} ${HEADERS})

48
ppu/memory.c Normal file
View File

@ -0,0 +1,48 @@
//
// Created by william on 5/17/24.
//
#include <assert.h>
#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);
}
}
}

28
ppu/memory.h Normal file
View File

@ -0,0 +1,28 @@
//
// Created by william on 5/17/24.
//
#include <stdbool.h>
#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

118
ppu/ppu.c
View File

@ -12,12 +12,13 @@
// 8. Implement OAMDMA (and OAMDATA I guess, I implemented one on top of the other) // 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. // 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. // 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 <assert.h> #include <assert.h>
#include <string.h>
#include "ppu.h" #include "ppu.h"
#include "../include/ppu.h" #include "../include/ppu.h"
#include "../cpu/cpu.h" #include "../cpu/cpu.h"
#include "../include/rom.h"
#define PPU_VISIBLE_FRAME_END 240 #define PPU_VISIBLE_FRAME_END 240
#define PPU_POST_RENDER_LINE_START PPU_VISIBLE_FRAME_END #define PPU_POST_RENDER_LINE_START PPU_VISIBLE_FRAME_END
@ -29,22 +30,11 @@
PPU ppu_state; PPU ppu_state;
void ppu_init(byte *registers_ram, byte *oam_dma_register) { void ppu_init(byte *registers_ram, byte *oam_dma_register) {
ppu_state.registers = registers_ram; memset(&ppu_state, 0, sizeof(PPU));
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;
ppu_state.frame = 0; ppu_state.oam_dma_register = oam_dma_register;
ppu_state.scanline = 0; ppu_state.registers = registers_ram;
ppu_state.cycle = 0; memset(&ppu_state.registers, 0, 8);
} }
PPU *ppu_get_state() { PPU *ppu_get_state() {
@ -68,29 +58,28 @@ void ppu_trigger_vbl_nmi() {
cpu_trigger_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) { void ppu_visible_frame(unsigned int cycle) {
if (cycle == 0) { if (cycle == 0) {
// Idle... // Idle...
} else if (cycle <= 256) { } 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) { } else if (cycle <= 320) {
// OAMADDR is cleared on sprite loading for pre-render and visible lines // OAMADDR is cleared on sprite loading for pre-render and visible lines
ppu_write_reg(PPU_REGISTER_OAM_ADDR, 0); 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. // 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. // So we get the data first, then update the register.
byte data = ppu_state.registers[reg]; byte data = ppu_state.registers[reg];
ppu_state.registers[reg] = ppu_state.vram[ppu_state.v]; ppu_state.registers[reg] = ppu_state.vram[ppu_state.ppu_address];
if (ppu_state.v > 0x3eff) { if (ppu_state.ppu_address > 0x3eff) {
// But the palette data is returned immediately // But the palette data is returned immediately
data = ppu_state.registers[reg]; data = ppu_state.registers[reg];
} }
@ -180,9 +169,9 @@ byte ppu_read_reg(byte reg) {
increment = 32; increment = 32;
} }
ppu_state.v += increment; ppu_state.ppu_address += increment;
if (ppu_state.v >= PPU_VRAM_SIZE) { if (ppu_state.ppu_address >= PPU_VRAM_SIZE) {
ppu_state.v -= PPU_VRAM_SIZE; ppu_state.ppu_address -= PPU_VRAM_SIZE;
} }
return data; return data;
@ -217,7 +206,7 @@ void ppu_write_reg(byte reg, byte data) {
} }
} else if (reg == PPU_REGISTER_ADDR) { } else if (reg == PPU_REGISTER_ADDR) {
ppu_state.w = !ppu_state.w; ppu_state.w = !ppu_state.w;
address addr = ppu_state.v; address addr = ppu_state.ppu_address;
if (ppu_state.w) { if (ppu_state.w) {
addr &= 0xff & data; addr &= 0xff & data;
} else { } else {
@ -227,18 +216,18 @@ void ppu_write_reg(byte reg, byte data) {
if (addr >= PPU_VRAM_SIZE) { if (addr >= PPU_VRAM_SIZE) {
addr -= PPU_VRAM_SIZE; addr -= PPU_VRAM_SIZE;
} }
ppu_state.v = addr; ppu_state.ppu_address = addr;
} else if (reg == PPU_REGISTER_DATA) { } else if (reg == PPU_REGISTER_DATA) {
ppu_state.vram[ppu_state.v] = data; ppu_state.vram[ppu_state.ppu_address] = data;
byte increment = 1; byte increment = 1;
if (ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_VRAM_ADDR_INCREMENT)) { if (ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_VRAM_ADDR_INCREMENT)) {
increment = 32; increment = 32;
} }
ppu_state.v += increment; ppu_state.ppu_address += increment;
if (ppu_state.v >= PPU_VRAM_SIZE) { if (ppu_state.ppu_address >= PPU_VRAM_SIZE) {
ppu_state.v -= PPU_VRAM_SIZE; ppu_state.ppu_address -= PPU_VRAM_SIZE;
} }
} else if (reg == PPU_REGISTER_OAM_DATA) { } else if (reg == PPU_REGISTER_OAM_DATA) {
byte oam_addr = ppu_state.registers[PPU_REGISTER_OAM_ADDR]; 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) { byte ppu_read(address addr) {
assert(addr < PPU_VRAM_SIZE); 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);
} }

View File

@ -4,6 +4,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <malloc.h>
#include "log.h" #include "log.h"
#include "../include/rom.h" #include "../include/rom.h"
#include "../include/system.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'; 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; INesHeader header;
unsigned char flag6 = header_buf[6]; unsigned char flag6 = header_buf[6];
@ -127,12 +128,13 @@ bool rom_ines_read_trainer(FILE *file, INesHeader *header) {
return false; 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; unsigned int prg_rom_size = header->prg_rom_size * 16384;
log_debug("Reading %d bytes PRG ROM", prg_rom_size); log_debug("Reading %d bytes PRG ROM", prg_rom_size);
byte *prg_rom_location = mem_get_ptr(system_get_mapper()->prg_rom_start_addr); rom->prg_rom_size = prg_rom_size;
if (fread(prg_rom_location, sizeof(byte), prg_rom_size, file) < 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"); log_error("Failed to read PRG ROM");
return false; return false;
} }
@ -142,7 +144,7 @@ bool rom_ines_read_prg_rom(FILE *file, INesHeader *header) {
return true; 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) { if (header->chr_rom_size <= 0) {
log_debug("No CHR ROM to read"); log_debug("No CHR ROM to read");
return true; 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; unsigned int chr_rom_size = header->chr_rom_size * 8192;
log_debug("Reading %d bytes CHR ROM", chr_rom_size); log_debug("Reading %d bytes CHR ROM", chr_rom_size);
byte *chr_rom_location = ppu_get_state()->vram; rom->chr_rom = malloc(sizeof(byte) * chr_rom_size);
if (fread(chr_rom_location, sizeof(byte), chr_rom_size, file) < chr_rom_size) { if (fread(rom->chr_rom, sizeof(byte), chr_rom_size, file) < chr_rom_size) {
log_error("Failed to read CHR ROM"); log_error("Failed to read CHR ROM");
return false; return false;
} }
@ -160,11 +162,11 @@ bool rom_ines_read_chr_rom(FILE *file, INesHeader *header) {
return true; return true;
} }
bool rom_ines_read(const char header_buf[ROM_HEADER_SIZE], FILE *file) { bool rom_ines_read(Rom *rom, FILE *file) {
INesHeader header = read_header(header_buf); INesHeader header = read_header(rom->header);
// system->rom_header = &header; rom->nametable_mirrored = header.flags.nametable_mirrored;
return rom_ines_read_trainer(file, &header) && return rom_ines_read_trainer(file, &header) &&
rom_ines_read_prg_rom(file, &header) && rom_ines_read_prg_rom(file, &header, rom) &&
rom_ines_read_chr_rom(file, &header); rom_ines_read_chr_rom(file, &header, rom);
} }

View File

@ -8,6 +8,12 @@
#include "ines.c" #include "ines.c"
#include "../include/system.h" #include "../include/system.h"
Rom rom;
Rom *rom_get() {
return &rom;
}
bool rom_load(char *path) { bool rom_load(char *path) {
FILE *file = fopen(path, "r"); FILE *file = fopen(path, "r");
if (!file) { if (!file) {
@ -28,7 +34,7 @@ bool rom_load(char *path) {
} }
log_info("Reading iNes 1.0 ROM at %s", 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) { if (fclose(file) != 0) {
log_error("Failed to close ROM file"); log_error("Failed to close ROM file");
@ -36,4 +42,9 @@ bool rom_load(char *path) {
} }
return true; return true;
}
void rom_unload() {
free(rom.prg_rom);
free(rom.chr_rom);
} }

View File

@ -41,6 +41,7 @@ void system_next_frame() {
} }
void system_uninit() { void system_uninit() {
ppu_uninit();
} }
unsigned int system_get_cycles() { unsigned int system_get_cycles() {