Fix memory mapping

This commit is contained in:
FyloZ 2024-05-23 23:52:04 -04:00
parent 07d044c47f
commit 66785039a9
Signed by: william
GPG Key ID: 835378AE9AF4AE97
16 changed files with 131 additions and 185 deletions

View File

@ -11,7 +11,7 @@ add_subdirectory(cpu)
add_subdirectory(ppu)
add_subdirectory(mappers)
add_subdirectory(rom)
add_subdirectory(debugger)
#add_subdirectory(debugger)
add_subdirectory(utils)
add_subdirectory(gui)
@ -29,7 +29,7 @@ set(SOURCE main.c system.c)
add_executable(nes_emulator ${HEADERS} ${SOURCE})
target_link_libraries(nes_emulator nes_cpu nes_ppu nes_mappers nes_rom nes_debugger nes_utils nes_gui log.c)
target_link_libraries(nes_emulator nes_cpu nes_ppu nes_mappers nes_rom nes_utils nes_gui log.c)
target_include_directories(nes_emulator PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES})

View File

@ -64,7 +64,7 @@ void cpu_process_nmi() {
}
void oam_dma_upload() {
byte page_high_addr = *ppu_get_state()->oam_dma_register; // TODO
byte page_high_addr = ppu_get_state()->oam_dma_register; // TODO
address page_addr = ((address) page_high_addr) << 8;
byte n = 0xff;

View File

@ -8,62 +8,107 @@
#include "../include/rom.h"
#include "cpu.h"
#define RAM_SIZE 0x0800
#define RAM_MAX_ADDR 0x2000
#define RAM_BANK_SIZE 0x800
#define PPU_MAX_ADDR 0x4000
#define PPU_BANK_SIZE 0x8
#define APU_MAX_ADDR 0x4020
#define MAX_ADDR 0xffff
#define UNMAPPED_MAX_ADDR 0x6000
#define CARTRIDGE_RAM_MAX_ADDR 0x8000
#define MEM_ADDR_MAX 0xffff
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;
return ppu_read_reg(reg);
}
return ram[addr];
}
byte *mem_get_ptr(address addr) {
assert(addr <= MAX_ADDR);
assert(addr <= RAM_MAX_ADDR);
return &ram[addr];
if (addr < RAM_MAX_ADDR) {
address ram_addr = addr % RAM_SIZE; // There is four mirror of RAM
return &ram[ram_addr];
}
// Only supported for RAM
assert(false);
}
byte mem_get_byte(address addr) {
assert(addr <= MEM_ADDR_MAX);
if (addr < RAM_MAX_ADDR) {
address ram_addr = addr % RAM_SIZE;
return ram[ram_addr];
} else if (addr < PPU_MAX_ADDR) {
address relative_addr = addr - RAM_MAX_ADDR;
byte ppu_reg = relative_addr % 8;
return ppu_read_reg(ppu_reg);
} else if (addr < APU_MAX_ADDR) {
// TODO NES API and I/O registers
return 0;
} else if (addr < UNMAPPED_MAX_ADDR) {
// Unmapped
assert(false);
} else if (addr < CARTRIDGE_RAM_MAX_ADDR) {
// TODO Cartridge RAM
return 0;
} else {
address rom_addr = addr - CARTRIDGE_RAM_MAX_ADDR;
Mapper *mapper = system_get_mapper();
return *(mapper->mem_read(rom_addr));
}
}
word mem_get_word(address addr) {
assert(addr < MAX_ADDR);
assert(addr <= MEM_ADDR_MAX - 1);
word word = ram[addr];
word += ram[addr + 1] << 8; // Little endian
return word;
byte data1;
byte data2;
if (addr < RAM_MAX_ADDR - 1) {
address ram_addr = addr % RAM_SIZE;
data1 = ram[ram_addr];
data2 = ram[ram_addr + 1];
} else if (addr < UNMAPPED_MAX_ADDR) {
// Unsupported
assert(false);
} else if (addr < CARTRIDGE_RAM_MAX_ADDR) {
// TODO Cartridge RAM
return 0;
} else {
address rom_addr = addr - CARTRIDGE_RAM_MAX_ADDR;
Mapper *mapper = system_get_mapper();
byte *location = mapper->mem_read(rom_addr);
data1 = *location;
data2 = *(location + 1);
}
void mem_set_byte(address addr, byte byte) {
assert(addr < MAX_ADDR);
return data1 + (data2 << 8);
}
log_trace("Writing '%02x' to address 0x%04x", byte, addr);
void mem_set_byte(address addr, byte data) {
assert(addr < MEM_ADDR_MAX);
if (addr < RAM_MAX_ADDR) {
address init_ram_addr = addr % RAM_BANK_SIZE;
// 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;
ram[ram_addr] = byte;
}
address ram_addr = addr % RAM_SIZE;
ram[ram_addr] = data;
} else if (addr < PPU_MAX_ADDR) {
address reg_addr = (addr - RAM_MAX_ADDR) % PPU_BANK_SIZE;
ppu_write_reg(reg_addr, byte);
} else {
ram[addr] = byte;
if (addr == PPU_REGISTER_OAM_DMA_ADDR) {
address relative_addr = addr - RAM_MAX_ADDR;
byte ppu_reg = relative_addr % 8;
ppu_write_reg(ppu_reg, data);
} else if (addr == PPU_REGISTER_OAM_DMA_ADDR) {
// Writing to this address triggers an upload to the PPU memory
cpu_trigger_oam_dma();
}
} else if (addr < APU_MAX_ADDR) {
// TODO NES API and I/O registers
} else if (addr < UNMAPPED_MAX_ADDR) {
// Unmapped
assert(false);
} else if (addr < CARTRIDGE_RAM_MAX_ADDR) {
// TODO Cartridge RAM
} else {
// ROM is read-only
assert(false);
}
}

View File

@ -37,8 +37,8 @@ word mem_get_word(address addr);
* Sets a byte in a system's memory.
*
* @param addr The address to set
* @param value The value to set
* @param data The data to set
*/
void mem_set_byte(address addr, byte value);
void mem_set_byte(address addr, byte data);
#endif //NESEMULATOR_MEMORY_H

View File

@ -5,7 +5,6 @@
#include <stdbool.h>
#include <stddef.h>
#include "types.h"
#include "../ppu/memory.h"
#ifndef NESEMULATOR_PPU_H
#define NESEMULATOR_PPU_H
@ -61,11 +60,18 @@ typedef struct ppu_memory {
byte *palette;
} PPUMemory;
typedef struct ppu_tile_fetch {
byte nametable;
byte attribute_table;
byte pattern_table_tile_low;
byte pattern_table_tile_high;
} PPUTileFetch;
typedef struct ppu {
PPUMemory memory;
byte *registers;
byte *oam_dma_register;
byte registers[8];
byte oam_dma_register;
byte vram[PPU_VRAM_SIZE];
byte oam[PPU_OAM_SIZE];
bool odd_frame;
@ -90,8 +96,7 @@ PPU *ppu_get_state();
*
* @param ppu
*/
void ppu_init(byte *registers_ram, byte *oam_dma_register);
void ppu_uninit();
void ppu_init();
/**
* Cycles the PPU.

View File

@ -19,7 +19,7 @@ typedef struct {
bool nametable_mirrored;
byte *prg_rom;
int prg_rom_size;
unsigned int prg_rom_size;
byte *chr_rom;
} Rom;

View File

@ -5,14 +5,14 @@
#ifndef NESEMULATOR_TYPES_H
#define NESEMULATOR_TYPES_H
#define RAM_SIZE 0xffff
#define VRAM_SIZE 0x4000
//#define RAM_SIZE 0xffff
//#define VRAM_SIZE 0x4000
typedef unsigned char byte;
typedef unsigned short address;
typedef unsigned short word;
//typedef byte ram[RAM_SIZE];
typedef byte vram[VRAM_SIZE];
//typedef byte vram[VRAM_SIZE];
#endif //NESEMULATOR_TYPES_H

2
main.c
View File

@ -27,7 +27,7 @@ int main() {
log_set_level(LOG_INFO);
system_init();
char *rom_path = "../test_roms/dk_japan.nes";
char *rom_path = "../test_roms/dk_jp.nes";
if (!rom_load(rom_path)) {
system_uninit();

View File

@ -2,23 +2,17 @@
#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];
return &rom->prg_rom[addr];
}
// The second bank is mirrored
return &rom->prg_rom[relative_addr - PRG_BANK_SIZE];
}
return NULL;
return &rom->prg_rom[addr - PRG_BANK_SIZE];
}
byte *nrom_ppu_read(address addr) {

View File

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

View File

@ -1,48 +0,0 @@
//
// 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);
}
}
}

View File

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

View File

@ -29,12 +29,8 @@
PPU ppu_state;
void ppu_init(byte *registers_ram, byte *oam_dma_register) {
void ppu_init() {
memset(&ppu_state, 0, sizeof(PPU));
ppu_state.oam_dma_register = oam_dma_register;
ppu_state.registers = registers_ram;
memset(&ppu_state.registers, 0, 8);
}
PPU *ppu_get_state() {
@ -62,24 +58,24 @@ void ppu_visible_frame(unsigned int cycle) {
if (cycle == 0) {
// Idle...
} else if (cycle <= 256) {
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;
}
// 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;
// }
} else if (cycle <= 320) {
// OAMADDR is cleared on sprite loading for pre-render and visible lines
ppu_write_reg(PPU_REGISTER_OAM_ADDR, 0);
@ -180,15 +176,6 @@ byte ppu_read_reg(byte reg) {
return ppu_state.registers[reg];
}
void ppu_write_reg_ram(byte reg, byte data) {
byte *ppu_ram = mem_get_ptr(PPU_RAM_BASE_ADDR);
for (int i = 0; i < PPU_RAM_BANK_COUNT; i++) {
byte ram_offset = (i * PPU_RAM_BANK_SIZE) + reg;
*(ppu_ram + ram_offset) = data;
}
}
void ppu_write_reg(byte reg, byte data) {
ppu_state.registers[reg] = data;
@ -234,7 +221,7 @@ void ppu_write_reg(byte reg, byte data) {
ppu_write_reg(PPU_REGISTER_OAM_ADDR, oam_addr + 1);
}
ppu_write_reg_ram(reg, data);
ppu_state.registers[reg] = data;
}
byte ppu_read(address addr) {

View File

@ -59,7 +59,7 @@ typedef struct {
struct INesHeaderFlags flags;
} INesHeader;
bool rom_is_ines(const char header[16]) {
bool rom_is_ines(const byte header[16]) {
return header[0] == 'N' && header[1] == 'E' && header[2] == 'S';
}
@ -139,8 +139,6 @@ bool rom_ines_read_prg_rom(FILE *file, INesHeader *header, Rom *rom) {
return false;
}
system_get_mapper()->post_prg_load(header->prg_rom_size);
return true;
}

View File

@ -21,14 +21,13 @@ bool rom_load(char *path) {
return false;
}
char header_buffer[ROM_HEADER_SIZE] = {0};
size_t read_size = fread(header_buffer, sizeof(char), ROM_HEADER_SIZE, file);
size_t read_size = fread(&rom.header, sizeof(byte), ROM_HEADER_SIZE, file);
if (read_size < ROM_HEADER_SIZE) {
log_error("Failed to read ROM");
return false;
}
if (!rom_is_ines(header_buffer)) {
if (!rom_is_ines(rom.header)) {
log_error("Only iNes ROMs are supported");
return false;
}

View File

@ -13,11 +13,8 @@
System current_sys;
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);
cpu_init();
ppu_init(registers_base_addr, oam_dma_register);
ppu_init();
current_sys.mapper = get_mapper(MAPPER_TYPE_SIMPLE);
current_sys.cycle_count = 7;
@ -41,7 +38,6 @@ void system_next_frame() {
}
void system_uninit() {
ppu_uninit();
}
unsigned int system_get_cycles() {