Browse Source

Initial commit: Everything in progress, with PPU R/W support nearly complete

master
Nathaniel Walizer 1 year ago
commit
13456ff79a
10 changed files with 638 additions and 0 deletions
  1. +35
    -0
      Makefile
  2. +1
    -0
      e6502
  3. +18
    -0
      src/apu.h
  4. +84
    -0
      src/ines.c
  5. +53
    -0
      src/ines.h
  6. +91
    -0
      src/nes.c
  7. +65
    -0
      src/nes.h
  8. +72
    -0
      src/nese.c
  9. +155
    -0
      src/ppu.c
  10. +64
    -0
      src/ppu.h

+ 35
- 0
Makefile View File

@@ -0,0 +1,35 @@
CC = gcc
LD = $(CC)
CFLAGS = -Wall -Werror -Wshadow -I.. -g #-DE6502_DEBUG
LDFLAGS =

OBJDIR = obj
SRCDIR = src
BINDIR = bin


# nese

TARGET_1 = nese
LDLIBS_1 =

SRC_SRCS_1 = nese.c ines.c nes.c ppu.c

EXT_SRCS_1 = e6502/e6502.c


SRCS_1 = $(SRC_SRCS_1:%=$(SRCDIR)/%) $(EXT_SRCS_1)
OBJS_1 = $(SRCS_1:%.c=$(OBJDIR)/%.o)


all: $(BINDIR)/$(TARGET_1)

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

$(BINDIR)/$(TARGET_1): $(OBJS_1)
@mkdir -p $(@D)
$(LD) $(LDFLAGS) $^ $(LDLIBS_1) -o $@

$(OBJDIR)/%.o: %.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@

+ 1
- 0
e6502 View File

@@ -0,0 +1 @@
../e6502/

+ 18
- 0
src/apu.h View File

@@ -0,0 +1,18 @@
#ifndef ENES_APU_H_
#define ENES_APU_H_

#include <stdint.h>


// TODO: Stubbed

typedef struct {

} nes_apu;

static inline uint8_t nes_apu_read(nes_apu*, uint16_t addr) { return 0; }
static inline void nes_apu_write(nes_apu*, uint16_t addr, uint8_t) {}
static inline void nes_apu_reset(nes_apu*) {}


#endif // ENES_APU_H_

+ 84
- 0
src/ines.c View File

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

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


#define INES_LOG(tag, fmt, ...) \
fprintf(stderr, tag ": iNES: " fmt "\n" __VA_OPT__(,) __VA_ARGS__)
#define INES_ERR(...) INES_LOG("E", __VA_ARGS__)


#define ines_trainer_size (512U)
#define ines_prg_rom_chunk (16384U)
#define ines_chr_rom_chunk (8192U)


int ines_check(ines_Header* ret, FILE* file) {
int status = 0;
ines_Header hdr = {0};

if (1 != fread(&hdr, sizeof(ines_Header), 1, file)) {
INES_ERR("Failed to read header");
status = -1;
}

if (0 == status && 0 != memcmp(hdr.magic,
ines_magic,
sizeof(hdr.magic))) {
INES_ERR("Bad file magic: expected '%.4s', got '%.4s'",
ines_magic, hdr.magic);
status =-1;
}

if (0 == status && NULL != ret) {
*ret = hdr;
}

return status;
}

int ines_load(nes_cart_rom* cart_rom, FILE* file) {
int status = 0;
ines_Header hdr = {0};

status = ines_check(&hdr, file);

if (0 == status && (hdr.flags_6 & ines_Flag_Trainer)) {
// Skip trainer
status = fseek(file, ines_trainer_size, SEEK_CUR);
}

if (0 == status) {
int prg_size = ines_prg_rom_chunk * hdr.prg_size_lsb;
if (prg_size > nes_mem_rom_size) {
INES_ERR("Program ROM too large: %d > %d",
prg_size, nes_mem_rom_size);
status = -1;

} else if (1 != fread(cart_rom->prg, prg_size, 1, file)) {
INES_ERR("Failed to read program ROM");
status = -1;

} else if (1U == hdr.prg_size_lsb) {
// If there's only one PRG_ROM chunk, duplicate it
memcpy(cart_rom->prg + ines_prg_rom_chunk,
cart_rom, ines_prg_rom_chunk);
}
}

if (0 == status) {
int chr_size = ines_chr_rom_chunk * hdr.chr_size_lsb;
if (chr_size > nes_ppu_mem_size) {
INES_ERR("Sprite ROM too large: %d > %d",
chr_size, nes_ppu_mem_size);
status = -1;

} else if (1 != fread(cart_rom->chr, chr_size, 1, file)) {
INES_ERR("Failed to read sprite ROM");
status = -1;
}
}

return status;
}

+ 53
- 0
src/ines.h View File

@@ -0,0 +1,53 @@
#ifndef INES_H_
#define INES_H_

#include <stdint.h>
#include <stdio.h>

#include "nes.h"


#define ines_magic "NES\x1a"

typedef enum {
ines_Flag_Horizontal = 0b00000001,
ines_Flag_Battery = 0b00000010,
ines_Flag_Trainer = 0b00000100,
ines_Flag_Alt_Nametable = 0b00001000,
ines_Mapper_Nibble_Lo = 0b11110000,
} ines_6_Flag;

typedef enum {
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 {
char magic[4];
uint8_t prg_size_lsb;
uint8_t chr_size_lsb;
uint8_t flags_6;
uint8_t flags_7;
uint8_t mapper_msb;
uint8_t size_msb;
uint8_t eeprom_size;
uint8_t ram_size;
uint8_t timing;
uint8_t sys_type;
uint8_t n_misc;
uint8_t expansion;
} __attribute__ (( packed )) ines_Header;


int ines_check(ines_Header*, FILE*);

int ines_load(nes_cart_rom*, FILE*);


#endif // INES_H_

+ 91
- 0
src/nes.c View File

@@ -0,0 +1,91 @@
#include "nes.h"


uint8_t nes_mem_read(nes* sys, uint16_t addr) {
uint8_t val = 0;

if (addr < nes_mem_ppu_start) {
addr = (addr - nes_mem_ram_start) & (nes_mem_ram_size - 1);
val = sys->ram[addr];

} else if (addr < nes_mem_apu_start) {
addr = (addr - nes_mem_ppu_start) & (nes_ppu_map_size - 1);
val = nes_ppu_read(&sys->ppu, nes_mem_ppu_start + addr);

} else if (addr < nes_mem_exp_start) {
val = nes_apu_read(&sys->apu, addr);

} else if (addr < nes_mem_wram_start) {
// TODO: Expansion ROM support

} else if (addr < nes_mem_rom_start) {
val = sys->cart.wram[addr - nes_mem_wram_start];

} else {
val = sys->cart.rom.prg[addr - nes_mem_rom_start];
}

return val;
}

void nes_mem_write(nes* sys, uint16_t addr, uint8_t val) {
if (addr < nes_mem_ppu_start) {
addr = (addr - nes_mem_ram_start) & (nes_mem_ram_size - 1);
sys->ram[addr] = val;

} else if (addr < nes_mem_apu_start) {
addr = (addr - nes_mem_ppu_start) & (nes_ppu_map_size - 1);
nes_ppu_write(&sys->ppu, nes_mem_ppu_start + addr, val);

} else if (addr == nes_ppu_dma_reg) {
for (int i = 0; i < nes_ppu_oam_size; ++i) {
sys->ppu.oam[(uint8_t)(i + sys->ppu.oam_addr)] =
nes_mem_read(sys, ((uint16_t)val << 8) + i);
}
sys->cpu.cycle += 513U;
sys->ppu.cycle += (513U * nes_clock_cpu_div) /
nes_clock_ppu_div;
// TODO: Increment APU cycles?

} else if (addr < nes_mem_exp_start) {
nes_apu_write(&sys->apu, addr, val);

} else if (addr < nes_mem_wram_start) {
// No ROM writes

} else if (addr < nes_mem_rom_start) {
sys->cart.wram[addr - nes_mem_wram_start] = val;

} else {
// No ROM writes
}
}

void nes_init(nes* sys) {
e6502_init(&sys->cpu, (e6502_Read*)nes_mem_read,
(e6502_Write*)nes_mem_write, sys);
nes_ppu_init(&sys->ppu, sys->cart.rom.chr);
}

void nes_reset(nes* sys) {
e6502_reset(&sys->cpu);
nes_ppu_reset(&sys->ppu);
nes_apu_reset(&sys->apu);
}

int nes_run(nes* sys, int master_cycles, int* run) {
int cpu_run = 0;
int cpu_cycles = (master_cycles + (nes_clock_cpu_div - 1)) /
nes_clock_cpu_div;
int status = e6502_run(&sys->cpu, cpu_cycles, &cpu_run, 0);

master_cycles = cpu_run * nes_clock_cpu_div;
int ppu_cycles = master_cycles / nes_clock_ppu_div;
int vblank = nes_ppu_run(&sys->ppu, ppu_cycles);

e6502_set_nmi(&sys->cpu, vblank);

if (run) *run = master_cycles;

return status;
}

+ 65
- 0
src/nes.h View File

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

#include "apu.h"
#include "ppu.h"

#include "e6502/e6502.h"


#define nes_clock_master_num (945U * 1000U * 1000U)
#define nes_clock_master_den (44U)
#define nes_clock_cpu_div (12U)
#define nes_clock_ppu_div (4U)

#define nes_mem_ram_start (0x0000U)
#define nes_mem_ram_size (0x0800U)
#define nes_mem_ppu_start (0x2000U)
#define nes_mem_ppu_size (0x2000U)
#define nes_mem_apu_start (0x4000U)
#define nes_mem_apu_size (0x0020U)
#define nes_mem_exp_start (0x4020U)
#define nes_mem_exp_size (0x1FE0U)
#define nes_mem_wram_start (0x6000U)
#define nes_mem_wram_size (0x2000U)
#define nes_mem_rom_start (0x8000U)
#define nes_mem_rom_size (0x8000U)

#define nes_ppu_mem_size (0x4000U)
#define nes_ppu_map_size (0x8U)
#define nes_ppu_dma_reg (0x4014U)

#define nes_apu_map_size (0x20U)

typedef struct {
// TODO: Dynamic size support
uint8_t prg[nes_mem_rom_size];
uint8_t chr[nes_ppu_mem_size];
} nes_cart_rom;

typedef struct {
nes_cart_rom rom;
uint8_t wram[nes_mem_wram_size];
// TODO: Mapper support
} nes_cart;

typedef struct {
e6502_Core cpu;
nes_ppu ppu;
nes_apu apu;
uint8_t ram[nes_mem_ram_size];
nes_cart cart;
} nes;

uint8_t nes_mem_read(nes*, uint16_t addr);

void nes_mem_write(nes*, uint16_t addr, uint8_t);

void nes_init(nes*);

void nes_reset(nes*);

int nes_run(nes*, int cycles, int* run);


#endif // NES_H_

+ 72
- 0
src/nese.c View File

@@ -0,0 +1,72 @@
#include "ines.h"



void e6502_print_registers(const e6502_Registers* regs,
FILE* file) {
fprintf(file, "PC: $%04x\n", regs->PC);
fprintf(file, " S: $%02x\n", regs->S);
fprintf(file, " A: $%02x\n", regs->A);
fprintf(file, " X: $%02x\n", regs->X);
fprintf(file, " Y: $%02x\n", regs->Y);
fprintf(file, " P: $%02x\n", regs->P);
}

void e6502_dump_mem(e6502_Core* core, uint16_t addr,
int len, FILE* file) {
for ( ; len > 0; --len, ++addr) {
fprintf(file, "$%04x: %02x\n",
addr, e6502_r8(core, addr));
}
}

void e6502_dump_stack(e6502_Core* core, FILE* file) {
int len = 0x100U + 2U - core->registers.S;
uint16_t addr = e6502_Memory_Stack + 0xFFU;
for ( ; len > 0; --len, --addr) {
fprintf(file, "$%03x: %02x\n",
addr, e6502_r8(core, addr));
}
}



static nes sys = {0};

int main(int argc, char* argv[]) {
int status = 0;

status = ines_load(&sys.cart.rom, stdin);

if (status == 0) {
nes_init(&sys);
nes_reset(&sys);

int total_cycles = 0;
for (int i = 0; i < 15000; ++i) {
int run = 0;
status = nes_run(&sys, 1000, &run);
total_cycles += run;
/*
float us_run = ( run * 1000. * 1000. *
nes_clock_master_den) /
nes_clock_master_num;
fprintf(stdout, "Ran %f us, %d master cycles (%s)\n",
us_run, run,
status == 0 ? "OK" : "Halted");
*/
}

float ms_run = ( total_cycles * 1000. *
nes_clock_master_den) /
nes_clock_master_num;
fprintf(stdout, "Ran %f ms, %d master cycles (%s)\n",
ms_run, total_cycles,
status == 0 ? "OK" : "Halted");

e6502_print_registers(&sys.cpu.registers, stdout);
e6502_dump_stack(&sys.cpu, stdout);
}

return status;
}

+ 155
- 0
src/ppu.c View File

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

#include "ppu.h"


// TODO: Stubbed
// TODO: Retain open bus bits

#define ppu_reg_ctrl (0x2000U)
#define ppu_reg_mask (0x2001U)
#define ppu_reg_status (0x2002U)
#define oam_reg_addr (0x2003U)
#define oam_reg_data (0x2004U)
#define ppu_reg_scroll (0x2005U)
#define ppu_reg_addr (0x2006U)
#define ppu_reg_data (0x2007U)
#define oam_reg_dma (0x4014U)


uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) {
uint8_t val = 0;

if (ppu_reg_status == addr) {
val = ppu->status;
ppu->latch = 0;

} else if (oam_reg_data == addr) {
val = ppu->oam[ppu->oam_addr];

} else if (ppu_reg_data == addr) {
val = ppu->data;
ppu->data = ppu->vram[ppu->addr -
nes_ppu_mem_vram_start];
ppu->addr += (ppu->status & ppu_Control_VRAM_Inc) ?
32 : 1;
}

fprintf(stderr, "PPU: <-R $%04x %02x\n", addr, val);

return val;
}

void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) {
fprintf(stderr, "PPU: W-> $%04x %02x\n", addr, val);

if (ppu_reg_ctrl == addr) {
ppu->control = val;
// TODO: Trigger NMI if it's enabled during VBlank?

} else if (oam_reg_addr == addr) {
ppu->oam_addr = val;

} else if (oam_reg_data == addr) {
ppu->oam[ppu->oam_addr++] = val;

} else if (ppu_reg_mask == addr) {
ppu->mask = val;

} else if (ppu_reg_scroll == addr) {
if (ppu->latch) {
ppu->scroll &= 0xFF00U;
ppu->scroll |= val;
} else {
ppu->scroll &= 0x00FFU;
ppu->scroll |= (uint16_t)val << 8;
}
ppu->latch = !ppu->latch;

} else if (ppu_reg_addr == addr) {
if (ppu->latch) {
ppu->addr &= 0xFF00U;
ppu->addr |= val;
} else {
ppu->addr &= 0x00FFU;
ppu->addr |= (uint16_t)(val & 0x3FU) << 8;
}
ppu->latch = !ppu->latch;

} else if (ppu_reg_data == addr) {
ppu->vram[ppu->addr - nes_ppu_mem_vram_start] = val;
ppu->addr += (ppu->status & ppu_Control_VRAM_Inc) ?
32 : 1;
}
}

void nes_ppu_reset(nes_ppu* ppu) {
ppu->control = 0;
ppu->mask = 0;
ppu->latch = 0;
ppu->scroll = 0;
ppu->data = 0;
ppu->frame = 0;
ppu->scanline = 0;
ppu->cycle = 0;
}

void nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem) {
ppu->chr_mem = chr_mem;
ppu->status = 0;
ppu->oam_addr = 0;
ppu->addr = 0;
nes_ppu_reset(ppu);
}

int nes_ppu_run(nes_ppu* ppu, int cycles) {
int vblank = 0;

ppu->cycle += cycles;

while (ppu->cycle >= nes_ppu_dots) {
ppu->cycle -= nes_ppu_dots;
if ( ppu->scanline <= nes_ppu_prerender &&
(ppu->frame & 1)) {
// Prerender line is one dot shorter in odd frames
// Fake it by incrementing the cycle in this case
ppu->cycle++;
}
ppu->scanline++;
if (ppu->scanline >= nes_ppu_prerender +
nes_ppu_height +
nes_ppu_postrender +
nes_ppu_vblank) {
ppu->status &= ~ppu_Status_VBlank;
ppu->scanline = 0;
ppu->frame++;
// TODO: Render callback?
} else if (ppu->scanline >= nes_ppu_prerender +
nes_ppu_height +
nes_ppu_postrender) {
ppu->status |= ppu_Status_VBlank;
if (ppu->control & ppu_Control_VBlank) {
vblank = 1;
}
}
}

return vblank;
}

#define nes_ppu_active_cycles \
(nes_ppu_dots * (nes_ppu_prerender + \
nes_ppu_height + \
nes_ppu_postrender))

#define nes_ppu_vblank_cycles (nes_ppu_dots * nes_ppu_vblank)

#define nes_frame_cycles (nes_ppu_active_cycles + \
nes_ppu_vblank_cycles)

int nes_ppu_cycles_til_vblank(nes_ppu* ppu) {
int cycles_til_vblank = nes_ppu_active_cycles - (
ppu->cycle + (ppu->scanline * nes_ppu_dots));
return (cycles_til_vblank > 0 ? cycles_til_vblank :
nes_ppu_vblank_cycles + cycles_til_vblank);
}

+ 64
- 0
src/ppu.h View File

@@ -0,0 +1,64 @@
#ifndef ENES_PPU_H_
#define ENES_PPU_H_

#include <stdint.h>


#define nes_ppu_dots (341U)
#define nes_ppu_prerender (1U)
#define nes_ppu_height (240U)
#define nes_ppu_postrender (1U)
#define nes_ppu_vblank (20U)

#define nes_ppu_mem_vram_start (0x2000U)
#define nes_ppu_mem_vram_size (0x0800U)

#define nes_ppu_oam_size (256U)

typedef enum {
ppu_Control_Nametable_Mask = 0b00000011,
ppu_Control_VRAM_Inc = 0b00000100,
ppu_Control_Sprite_Addr = 0b00001000,
ppu_Control_Back_Addr = 0b00010000,
ppu_Control_Sprite_Size = 0b00100000,
ppu_Control_Master = 0b01000000,
ppu_Control_VBlank = 0b10000000,
} nes_ppu_Control;

typedef enum {
ppu_Status_Open_Bus_Mask = 0b00011111,
ppu_Status_Overflow = 0b00100000,
ppu_Status_Hit = 0b01000000,
ppu_Status_VBlank = 0b10000000,
} nes_ppu_Status;

typedef struct {
uint8_t* chr_mem;
uint8_t oam[nes_ppu_oam_size];
uint8_t vram[nes_ppu_mem_vram_size];

int frame;
int scanline;
int cycle;

uint8_t control;
uint8_t mask;
uint8_t status;
uint16_t scroll;
uint16_t addr;
uint8_t data;
uint8_t oam_addr;

uint8_t latch;

} nes_ppu;

uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr);
void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val);
void nes_ppu_reset(nes_ppu* ppu);
void nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem);

int nes_ppu_run(nes_ppu* ppu, int cycles);
int nes_ppu_cycles_til_vblank(nes_ppu* ppu);

#endif // ENES_PPU_H_

Loading…
Cancel
Save