Explorar el Código

Add main emulator loop, mapper 0, cart loading, and peripheral stubs

v2
Nathaniel Walizer hace 11 meses
padre
commit
bc911c49d5
Se han modificado 19 ficheros con 623 adiciones y 37 borrados
  1. +46
    -9
      Makefile
  2. +83
    -0
      src/cart.c
  3. +11
    -0
      src/cart.h
  4. +37
    -12
      src/f6502.c
  5. +5
    -16
      src/f6502.h
  6. +45
    -0
      src/ines.h
  7. +40
    -0
      src/linux/port.c
  8. +19
    -0
      src/log.h
  9. +31
    -0
      src/map/map000.c
  10. +9
    -0
      src/mapper.c
  11. +30
    -0
      src/mapper.h
  12. +50
    -0
      src/memory.h
  13. +52
    -0
      src/nes.c
  14. +21
    -0
      src/nes.h
  15. +49
    -0
      src/nese.c
  16. +10
    -0
      src/nese.h
  17. +9
    -0
      src/port.h
  18. +20
    -0
      src/ppu.c
  19. +56
    -0
      src/ppu.h

+ 46
- 9
Makefile Ver fichero

@@ -1,20 +1,57 @@
#CROSS_COMPILE = arm-none-eabi-
OS = linux

CC = $(CROSS_COMPILE)gcc
LD = $(CC)
#CFLAGS = -mcpu=cortex-m33 -mthumb
CFLAGS += -Wall -Werror -Wshadow
CFLAGS += -g -Ofast
CFLAGS += -DF6502_FLAT
CFLAGS += -DF6502_TEST
CFLAGS += -DF6502_HCF
CFLAGS += -I src
CFLAGS += -g #-Ofast
#CFLAGS += -DF6502_FLAT
#CFLAGS += -DF6502_TEST
#CFLAGS += -DF6502_HCF
#CFLAGS += -DF6502_TRACE

OBJS = src/f6502.o src/f6502_opcodes.o test.o

all: test
OBJDIR = obj
SRCDIR = src
BINDIR = bin


TEST_SRC_SRCS = f6502.c f6502_opcodes.c
TEST_SRCS = test.c

TEST_SRCS += $(TEST_SRC_SRCS:%=$(SRCDIR)/%)
TEST_OBJS = $(TEST_SRCS:%.c=$(OBJDIR)/%.o)


MAPDIR = $(SRCDIR)/map

NESE_SRC_SRCS = f6502.c f6502_opcodes.c
NESE_SRC_SRCS += nese.c nes.c cart.c mapper.c
NESE_SRC_SRCS += ppu.c
NESE_SRC_SRCS += $(OS)/port.c
NESE_MAP_SRCS = $(notdir $(wildcard $(MAPDIR)/*))

NESE_DEBUG = CART

NESE_SRCS += $(NESE_SRC_SRCS:%=$(SRCDIR)/%)
NESE_SRCS += $(NESE_MAP_SRCS:%=$(MAPDIR)/%)
NESE_OBJS = $(NESE_SRCS:%.c=$(OBJDIR)/%.o)


all: $(BINDIR)/nese

$(BINDIR)/test: CFLAGS += -DF6502_FLAT -DF6502_TEST -DF6502_TRACE

$(BINDIR)/nese: CFLAGS += $(foreach debug,$(NESE_DEBUG), -DDEBUG_$(debug))
$(BINDIR)/nese: $(NESE_OBJS)
@mkdir -p $(@D)
$(LD) $^ $(LDFLAGS) -o $@

test: $(OBJS)
$(LD) $(LDFLAGS) -o $@ $^
$(OBJDIR)/%.o : %.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@

clean:
rm -rf $(OBJS)
rm -rf $(OBJDIR) $(BINDIR)

+ 83
- 0
src/cart.c Ver fichero

@@ -0,0 +1,83 @@
#include <stdlib.h>
#include <string.h>

#include "cart.h"


#ifdef DEBUG_CART
#define DEBUG "Cart"
#endif
#include "log.h"


int nes_cart_load_mem(void* data, int size, nes* sys) {
int status = 0;
nes_Memory* mem = &sys->core.memory;
ines_Header* hdr = (ines_Header*)data;
void* ptr = &hdr[1];

// Verify magic
if (0 != memcmp(hdr->magic, INES_MAGIC, 4)) {
LOGE("Bad magic: got %.4s, expected %.4s", hdr->magic, INES_MAGIC);
status = -1;
}

if (0 == status) {
// Skip the trainer
if (hdr->flags_6 & ines_Flag_Trainer) {
ptr += INES_TRAINER_SIZE;
}

// Mark the PRG ROM
int rom_size = hdr->prg_size_lsb * INES_PRG_ROM_SIZE;
LOGI("PRG ROM: %d kB", rom_size / 1024);
mem->rom = ptr;
mem->n_rom_banks = rom_size / NES_PRG_ROM_PAGE_SIZE;
ptr += rom_size;

// Mark the CHR ROM
if (hdr->chr_size_lsb > 0) {
int chr_size = hdr->chr_size_lsb * INES_CHR_ROM_SIZE;
LOGI("CHR ROM: %d kB", chr_size / 1024);
mem->ppu.chr = ptr;
mem->ppu.n_chr_banks = chr_size /
NES_CHR_ROM_PAGE_SIZE;
ptr += chr_size;
}

// Fail if we're beyond the bounds
if (ptr - data > size) {
LOGE("Past ROM bounds by %d bytes", (int)((ptr - data) - size));
status = -1;
}
}

if (0 == status) {
// Unused & Palette
for (int page = 12; page < 16; ++page) {
mem->ppu.bank[page] = mem->ppu.pal_bank;
}

nes_PPU_set_mirroring(
&mem->ppu, hdr->flags_6 & ines_Flag_Vert_Mirror
);

int i_mapper = (hdr->flags_7 & ines_Mapper_Nibble_Hi) |
((hdr->flags_6 & ines_Mapper_Nibble_Lo) >> 4);
const nes_Mapper *mapper = nes_mappers[i_mapper];
if (NULL == mapper || NULL == mapper->init) {
LOGE("Mapper %d not supported", i_mapper);
status = -1;
} else {
LOGI("Mapper %d", i_mapper);
mem->mapper = *mapper;
status = mem->mapper.init(&mem->mapper, hdr, mem);
}
}

if (0 == status) {
sys->cart_header = hdr;
}

return status;
}

+ 11
- 0
src/cart.h Ver fichero

@@ -0,0 +1,11 @@
#ifndef NESE_CART_H_
#define NESE_CART_H_

#include "ines.h"
#include "nes.h"


int nes_cart_load_mem(void* mem, int size, nes* sys);


#endif // NESE_CART_H_

+ 37
- 12
src/f6502.c Ver fichero

@@ -19,18 +19,18 @@
// TOTO: Temp Hack
volatile uint8_t memval;

static inline uint8_t f6502_read_zp(f6502_Memory* mem,
static inline uint8_t f6502_read_zp(nes_Memory* mem,
uint8_t addr) {
return mem->ram[addr];
}

static inline uint16_t f6502_read16_zp(f6502_Memory* mem,
static inline uint16_t f6502_read16_zp(nes_Memory* mem,
uint8_t addr) {
return ( f6502_read_zp(mem, addr) |
((uint16_t)f6502_read_zp(mem, addr + 1) << 8));
}

static inline uint8_t f6502_read(f6502_Memory* mem,
static inline uint8_t f6502_read(nes_Memory* mem,
uint16_t addr) {
#ifdef F6502_FLAT
#ifdef F6502_TRACE
@@ -55,7 +55,9 @@ static inline uint8_t f6502_read(f6502_Memory* mem,
return 0;

case 0x6000:
return mem->sram_bank[addr & 0x1FFFU];
if (mem->sram_bank) {
return mem->sram_bank[addr & 0x1FFFU];
}
}

// Upper byte stays on the bus
@@ -63,15 +65,15 @@ static inline uint8_t f6502_read(f6502_Memory* mem,
#endif
}

static inline uint16_t f6502_read16(f6502_Memory* mem,
static inline uint16_t f6502_read16(nes_Memory* mem,
uint16_t addr) {
return ( f6502_read(mem, addr) |
((uint16_t)f6502_read(mem, addr + 1) << 8));
}

static inline int f6502_write(f6502_Memory* mem,
uint16_t addr, uint8_t val) {
int ret = 0;
static inline bool f6502_write(nes_Memory* mem,
uint16_t addr, uint8_t val) {
bool ret = 0;
#ifdef F6502_TRACE
printf("W $%04X <- %02X\n", addr, val);
#endif
@@ -100,15 +102,36 @@ static inline int f6502_write(f6502_Memory* mem,
#endif
mem->ram[addr] = val;
#else
// TODO
memval = val;
switch (addr & 0xe000) {
case 0x0000:
mem->ram[addr & 0x7FFU] = val;
break;

case 0x2000:
// TODO: PPU
break;

case 0x4000:
// TODO: APU
break;

case 0x6000:
if (mem->sram_bank) {
mem->sram_bank[addr & 0x1FFFU] = val;
mem->flags |= nes_Mem_SRAM_Used;

} else if (mem->mapper.write_sram) {
ret = mem->mapper.write_sram(&mem->mapper,
addr, val);
}
}
#endif
return ret;
}


void f6502_init(f6502_Core* core) {
// TODO: ???
// TODO: Nothing for now
}

void f6502_reset(f6502_Core* core) {
@@ -686,7 +709,6 @@ static int f6502_do_step(f6502_Core* core, int clocks) {
JMP(addr);
}
#else
// TODO: Can check for HCF loop here
JMP(A_ABS);
#endif
CLK(3);
@@ -1408,7 +1430,9 @@ static int f6502_do_step(f6502_Core* core, int clocks) {
if (int_trig) printf("Possible interrupt trigger\n");
#endif

#ifdef F6502_HCF
step_done:
#endif
core->registers = (f6502_Registers){
.PC = PC,
.S = S,
@@ -1424,6 +1448,7 @@ step_done:
int f6502_step(f6502_Core* core, int clocks) {
int clocks_elapsed = 0;
// TODO: Why are we processing seven clocks prior to NMI?
// This passes the interrupt test with 6 steps
/*
if (f6502_nmi_ready(core)) {
clocks_elapsed += f6502_do_step(core, 7);


+ 5
- 16
src/f6502.h Ver fichero

@@ -4,8 +4,7 @@
#include <stdbool.h>
#include <stdint.h>


#define F6502_RAM_SIZE (0x2000U)
#include "memory.h"


typedef enum {
@@ -41,22 +40,12 @@ typedef enum {
f6502_Int_NMI_Serviced = 0b10000000,
} f6502_Interrupt;

typedef struct __attribute__ ((__packed__)) {
#ifdef F6502_FLAT
uint8_t ram[65536];
#else
uint8_t ram[F6502_RAM_SIZE / 4]; // Mirrored 3x
uint8_t *sram_bank;
uint8_t *rom_bank[4];
// TODO
#endif
} f6502_Memory;

typedef struct __attribute__ ((__packed__)) {
struct f6502_Core {
f6502_Registers registers;
f6502_Interrupt interrupts;
f6502_Memory memory;
} f6502_Core;
nes_Memory memory;
};
typedef struct f6502_Core f6502_Core;

void f6502_init(f6502_Core*);
void f6502_reset(f6502_Core*);


+ 45
- 0
src/ines.h Ver fichero

@@ -0,0 +1,45 @@
#ifndef NESE_INES_H_
#define NESE_INES_H_

#include <stdint.h>


#define INES_TRAINER_SIZE (0x0200U)
#define INES_PRG_ROM_SIZE (0x4000U)
#define INES_CHR_ROM_SIZE (0x2000U)

#define INES_MAGIC "NES\x1a"

typedef enum __attribute__((packed)) {
ines_Flag_Vert_Mirror = 0b00000001,
ines_Flag_Battery = 0b00000010,
ines_Flag_Trainer = 0b00000100,
ines_Flag_Alt_Nametable = 0b00001000,
ines_Mapper_Nibble_Lo = 0b11110000,
} ines_6_Flag;

typedef enum __attribute__((packed)) {
ines_Console_Mask = 0b00000011,
ines_Console_NES = 0b00000000,
ines_Console_VS = 0b00000001,
ines_Console_PC10 = 0b00000010,
ines_Console_Ext = 0b00000011,
ines_NES_2_MASK = 0b00001100,
ines_NES_2_ID = 0b00001000,
ines_Mapper_Nibble_Hi = 0b11110000,
} ines_7_Flag;

typedef struct __attribute__((packed)) {
char magic[4];
uint8_t prg_size_lsb;
uint8_t chr_size_lsb;
ines_6_Flag flags_6;
ines_7_Flag flags_7;
uint8_t prg_ram_size;
uint8_t tv_system;
uint8_t flags_10;
uint8_t padding[5];
} ines_Header;


#endif // NESE_INES_H_

+ 40
- 0
src/linux/port.c Ver fichero

@@ -0,0 +1,40 @@
#include <errno.h>
#include <stdio.h>

#include <sys/mman.h>

#include "nese.h"
#include "port.h"

#define DEBUG "Port"
#include "log.h"


void* nese_map_file(FILE* file, int size) {
void* addr = mmap(NULL, size, PROT_READ, MAP_SHARED,
fileno(file), 0);
if (MAP_FAILED == addr || NULL == addr) {
fprintf(stderr, "mmap failed: %d\n", (int)errno);
addr = NULL;
}
return addr;
}

int nese_unmap_file(void* addr, int size) {
return munmap(addr, size);
}


static nes sys = {0};


int main(int argc, char* argv[]) {
int status = 0;
if (argc <= 1) {
LOGE("No ROM file provided.");
status = -1;
} else {
status = nese_start(&sys, argv[1]);
}
return status;
}

+ 19
- 0
src/log.h Ver fichero

@@ -0,0 +1,19 @@
#include <stdio.h>


#ifdef DEBUG

#define LOG(l, f, ...) fprintf(stderr, l ": " DEBUG ": " f "\n" __VA_OPT__(,) __VA_ARGS__)

#else // !DEBUG

#define LOG(...)

#endif // DEBUG


#define LOGE(f, ...) LOG("E", f, __VA_ARGS__)
#define LOGW(f, ...) LOG("W", f, __VA_ARGS__)
#define LOGI(f, ...) LOG("I", f, __VA_ARGS__)
#define LOGD(f, ...) LOG("D", f, __VA_ARGS__)
#define LOGV(f, ...) LOG("V", f, __VA_ARGS__)

+ 31
- 0
src/map/map000.c Ver fichero

@@ -0,0 +1,31 @@
#include "memory.h"


static int map000_init(nes_Mapper* map, const ines_Header* hdr,
nes_Memory* mem) {
if (mem->n_rom_banks > 2) {
mem->rom_bank[0] = prg_rom_page(mem, 0);
mem->rom_bank[1] = prg_rom_page(mem, 1);
mem->rom_bank[2] = prg_rom_page(mem, 2);
mem->rom_bank[3] = prg_rom_page(mem, 3);
} else {
mem->rom_bank[0] = prg_rom_page(mem, 0);
mem->rom_bank[1] = prg_rom_page(mem, 1);
mem->rom_bank[2] = prg_rom_page(mem, 0);
mem->rom_bank[3] = prg_rom_page(mem, 1);
}

int page = 0;
// Pattern tables
for ( ; page < 8; ++page) {
mem->ppu.bank[page] = chr_page(&mem->ppu, page);
}

return 0;
}


const nes_Mapper map000 = {
.max_chr_banks = 8,
.init = map000_init,
};

+ 9
- 0
src/mapper.c Ver fichero

@@ -0,0 +1,9 @@
#include "mapper.h"


extern const nes_Mapper map000;


const nes_Mapper* nes_mappers[256] = {
&map000,
};

+ 30
- 0
src/mapper.h Ver fichero

@@ -0,0 +1,30 @@
#ifndef NESE_MAPPER_H_
#define NESE_MAPPER_H_

#include <stdbool.h>

#include "ines.h"


struct nes_Memory;

struct nes_Mapper {
int max_chr_banks;
int (*init)(struct nes_Mapper*, const ines_Header*, struct nes_Memory*);
bool (*write_rom)(struct nes_Mapper*, uint16_t addr, uint8_t val);
bool (*write_sram)(struct nes_Mapper*, uint16_t addr, uint8_t val);
bool (*write_apu)(struct nes_Mapper*, uint16_t addr, uint8_t val);
uint8_t (*read_apu)(struct nes_Mapper*, uint16_t addr);
void (*hsync)(struct nes_Mapper*);
void (*vsync)(struct nes_Mapper*);
void (*ppu_bus)(struct nes_Mapper*, uint16_t addr);
void (*ppu_mem_mode)(struct nes_Mapper*, uint8_t mode);
void* data;
};
typedef struct nes_Mapper nes_Mapper;


extern const nes_Mapper* nes_mappers[256];


#endif // NESE_MAPPER_H_

+ 50
- 0
src/memory.h Ver fichero

@@ -0,0 +1,50 @@
#ifndef NESE_MEMORY_H_
#define NESE_MEMORY_H_

#include <stdint.h>

#include "mapper.h"
#include "ppu.h"


#define NES_RAM_SIZE (0x2000U)
#define NES_SRAM_SIZE (0x2000U)

#define NES_PRG_ROM_PAGE_SIZE (0x2000U)


typedef enum {
nes_Mem_SRAM_Used = 0b00000001,
} nes_Memory_Flag;

struct nes_Memory {
#ifdef F6502_FLAT
uint8_t ram[65536];
#else
uint8_t ram[NES_RAM_SIZE / 4]; // Mirrored 3x
nes_Memory_Flag flags;
uint8_t reserved[3];
uint8_t* sram; // Actual SRAM, if used - could this be a flag?
uint8_t* sram_bank; // Mapped to 0x6000 - 0x7FFF
uint8_t* rom;
uint8_t* rom_bank[4];
int n_rom_banks;
nes_PPU_Memory ppu;
nes_Mapper mapper;
#endif
};
typedef struct nes_Memory nes_Memory;


static inline uint8_t* prg_rom_page(nes_Memory* mem, int page) {
return &mem->rom[page * NES_PRG_ROM_PAGE_SIZE];
}

static inline uint8_t* prg_rom_last_page(nes_Memory* mem, int page) {
// TODO: Where's 0x2000U from?
return &mem->rom[ (mem->n_rom_banks * NES_PRG_ROM_PAGE_SIZE) -
((page + 1) * 0x2000U)];
}


#endif // NESE_MEMORY_H_

+ 52
- 0
src/nes.c Ver fichero

@@ -0,0 +1,52 @@
#include <stddef.h>

#include "nes.h"


void nes_init(nes* sys) {
f6502_init(&sys->core);
}

void nes_done(nes* sys) {
// TODO: deallocate RAM, etc.
}

static int nes_hsync(nes* sys) {
// TODO: APU sync

// TODO: PPU draw line if visible

// TODO: PPU update regs

// TODO: Increment scanline

// TODO: Scanline region updates
// TODO: nese_vsync called in nes_vsync

return 0;
}

int nes_loop(nes* sys) {
int status = 0;
int dot = 0;

while (0 == status) {
// TODO: Partial scanline if sprite 0 hit
int dots_remaining = nes_ppu_scanline_dots - dot;
int cpu_cycles = (dots_remaining + 2) / 3;
dot += 3 * f6502_step(&sys->core, cpu_cycles);
dot -= nes_ppu_scanline_dots;
// TODO: Validate dot >= 0?

// TODO: Frame IRQ

nes_Memory* mem = &sys->core.memory;
if (NULL != mem->mapper.hsync) {
mem->mapper.hsync(&mem->mapper);
}

status = nes_hsync(sys);
}

return status;
}

+ 21
- 0
src/nes.h Ver fichero

@@ -0,0 +1,21 @@
#ifndef NES_H_
#define NES_H_

#include "ines.h"
#include "f6502.h"


typedef struct {
const ines_Header* cart_header;
f6502_Core core;
// TODO: PPU
// TODO: APU
// TODO: Input
} nes;

void nes_init(nes*);
void nes_done(nes*);
int nes_loop(nes*);


#endif // NES_H_

+ 49
- 0
src/nese.c Ver fichero

@@ -0,0 +1,49 @@
#include <stdio.h>

#include "cart.h"
#include "nese.h"
#include "port.h"


static int nese_file_size(FILE* file) {
int size = -1;
if (0 == fseek(file, 0, SEEK_END)) {
size = ftell(file);
}
return size;
}


int nese_start(nes* sys, const char* filename) {
int status = 0;
void* cart_data = NULL;
int filesize = -1;
FILE* file = NULL;

file = fopen(filename, "rb");

if (NULL == file) {
status = -1;
}

if (0 == status) {
filesize = nese_file_size(file);
cart_data = nese_map_file(file, filesize);
if (NULL == cart_data) {
fprintf(stderr, "Failed to map %s\n", filename);
status = -1;
} else {
status = nes_cart_load_mem(cart_data, filesize, sys);
}
}

nes_init(sys);

nes_loop(sys);

if (cart_data) nese_unmap_file(cart_data, filesize);

nes_done(sys);

return 0;
}

+ 10
- 0
src/nese.h Ver fichero

@@ -0,0 +1,10 @@
#ifndef NESE_H_
#define NESE_H_

#include "nes.h"


int nese_start(nes* sys, const char* filename);


#endif // NESE_H_

+ 9
- 0
src/port.h Ver fichero

@@ -0,0 +1,9 @@
#ifndef NESE_PORT_H_
#define NESE_PORT_H_


void* nese_map_file(FILE* file, int size);
int nese_unmap_file(void* addr, int size);


#endif // NESE_PORT_H_

+ 20
- 0
src/ppu.c Ver fichero

@@ -0,0 +1,20 @@
#include "ppu.h"


static const uint8_t mirror_schemes[][4] = {
[nes_Mirror_Horizontal] = {0, 0, 1, 1},
[nes_Mirror_Vertical] = {0, 1, 0, 1},
[nes_Mirror_Only_0] = {0, 0, 0, 0},
[nes_Mirror_Only_1] = {1, 1, 1, 1},
[nes_Mirror_Four] = {0, 1, 2, 3},
};


void nes_PPU_set_mirroring(nes_PPU_Memory* mem,
nes_Nametable_Mirroring mirror) {
for (int i = 0; i < 4; ++i) {
mem->bank[nes_PPU_Nametable_Bank_Index + i] =
vram_page(mem, (int)mirror_schemes[mirror][0]);
}
}


+ 56
- 0
src/ppu.h Ver fichero

@@ -0,0 +1,56 @@
#ifndef NESE_PPU_H_
#define NESE_PPU_H_

#include <stdint.h>


#define nes_ppu_scanline_dots (341U)
#define nes_ppu_visible_line (1U)
#define nes_ppu_postrender_line (241U)
#define nes_ppu_vblank_line (242U)
#define nes_ppu_frame_end_line (262U)

#define NES_CHR_ROM_PAGE_SIZE (0x0400U)
#define NES_VRAM_PAGE_SIZE (0x0400U)

#define NES_PPU_CHR_SIZE (0x2000U)
#define NES_PPU_VRAM_SIZE (0x1000U)
#define NES_PPU_RAM_SIZE (0x4000U)
#define NES_PPU_OAM_SIZE (64U * 4U)

typedef struct __attribute__ ((__packed__)) {
uint8_t* chr;
int n_chr_banks;
uint8_t* chr_ram; // Could this be a flag?
uint8_t* bank[16];
uint8_t vram[NES_PPU_VRAM_SIZE];
uint8_t pal_bank[NES_CHR_ROM_PAGE_SIZE]; // Mirrored in banks 12-15, also pal
uint8_t oam[NES_PPU_OAM_SIZE];
} nes_PPU_Memory;


static inline uint8_t* chr_page(nes_PPU_Memory* mem,
int page) {
return &mem->chr[page * NES_CHR_ROM_PAGE_SIZE];
}

static inline uint8_t* vram_page(nes_PPU_Memory* mem,
int page) {
return &mem->vram[page * NES_VRAM_PAGE_SIZE];
}

#define nes_PPU_Nametable_Bank_Index (8U)

typedef enum {
nes_Mirror_Horizontal = 0,
nes_Mirror_Vertical,
nes_Mirror_Only_0,
nes_Mirror_Only_1,
nes_Mirror_Four,
} nes_Nametable_Mirroring;

void nes_PPU_set_mirroring(nes_PPU_Memory* mem,
nes_Nametable_Mirroring mirror);


#endif // NESE_PPU_H_

Cargando…
Cancelar
Guardar