Переглянути джерело

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

v2
Nathaniel Walizer 9 місяці тому
джерело
коміт
bc911c49d5
19 змінених файлів з 623 додано та 37 видалено
  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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

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


extern const nes_Mapper map000;


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

+ 30
- 0
src/mapper.h Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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 Переглянути файл

@@ -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_

Завантаження…
Відмінити
Зберегти