Fix memory mapping
This commit is contained in:
parent
07d044c47f
commit
66785039a9
|
@ -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})
|
|
@ -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;
|
||||
|
||||
|
|
115
cpu/memory.c
115
cpu/memory.c
|
@ -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);
|
||||
byte *mem_get_ptr(address addr) {
|
||||
assert(addr <= RAM_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);
|
||||
if (addr < RAM_MAX_ADDR) {
|
||||
address ram_addr = addr % RAM_SIZE; // There is four mirror of RAM
|
||||
return &ram[ram_addr];
|
||||
}
|
||||
|
||||
return ram[addr];
|
||||
// Only supported for RAM
|
||||
assert(false);
|
||||
}
|
||||
|
||||
byte *mem_get_ptr(address addr) {
|
||||
assert(addr <= MAX_ADDR);
|
||||
byte mem_get_byte(address addr) {
|
||||
assert(addr <= MEM_ADDR_MAX);
|
||||
|
||||
return &ram[addr];
|
||||
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);
|
||||
}
|
||||
|
||||
return data1 + (data2 << 8);
|
||||
}
|
||||
|
||||
void mem_set_byte(address addr, byte byte) {
|
||||
assert(addr < MAX_ADDR);
|
||||
|
||||
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);
|
||||
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 {
|
||||
ram[addr] = byte;
|
||||
|
||||
if (addr == PPU_REGISTER_OAM_DMA_ADDR) {
|
||||
// Writing to this address triggers an upload to the PPU memory
|
||||
cpu_trigger_oam_dma();
|
||||
}
|
||||
// ROM is read-only
|
||||
assert(false);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
2
main.c
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
Rom *rom = rom_get();
|
||||
|
||||
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];
|
||||
if (addr < PRG_BANK_SIZE || rom->prg_rom_size > PRG_BANK_SIZE) {
|
||||
return &rom->prg_rom[addr];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
// The second bank is mirrored
|
||||
return &rom->prg_rom[addr - PRG_BANK_SIZE];
|
||||
}
|
||||
|
||||
byte *nrom_ppu_read(address addr) {
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
48
ppu/memory.c
48
ppu/memory.c
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
28
ppu/memory.h
28
ppu/memory.h
|
@ -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
|
53
ppu/ppu.c
53
ppu/ppu.c
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
6
system.c
6
system.c
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue