implement basic iNES and Mapper 0 ROM loading
It seems like the test ROM loads fine but the instructions are not, but that's exactly what the test ROM is for I suppose.
This commit is contained in:
parent
1f6e3b054c
commit
77d37dd455
64
cpu.c
64
cpu.c
@ -1,8 +1,11 @@
|
|||||||
|
#include <libgen.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "rom.h"
|
||||||
|
|
||||||
#define MAX(a, b) ((a > b) ? a : b)
|
#define MAX(a, b) ((a > b) ? a : b)
|
||||||
|
|
||||||
#define STATUS_UPDATE_ZERO(r) \
|
#define STATUS_UPDATE_ZERO(r) \
|
||||||
@ -37,7 +40,9 @@ struct registers {
|
|||||||
struct cpu_flags status;
|
struct cpu_flags status;
|
||||||
uint16_t pc;
|
uint16_t pc;
|
||||||
};
|
};
|
||||||
struct registers regs;
|
struct registers regs = {0};
|
||||||
|
|
||||||
|
struct Rom rom = {0};
|
||||||
|
|
||||||
enum addressing_mode {
|
enum addressing_mode {
|
||||||
AM_IMM,
|
AM_IMM,
|
||||||
@ -56,9 +61,6 @@ enum addressing_mode {
|
|||||||
/* 64K address space, 16bit words */
|
/* 64K address space, 16bit words */
|
||||||
uint8_t memory[0x16000];
|
uint8_t memory[0x16000];
|
||||||
|
|
||||||
/* example program taken from https://bugzmanov.github.io/nes_ebook/chapter_3_1.html */
|
|
||||||
uint8_t program[] = { 0xa9, 0xc0, 0xaa, 0xe8, 0x00 };
|
|
||||||
|
|
||||||
uint32_t cycles = 0;
|
uint32_t cycles = 0;
|
||||||
|
|
||||||
static uint8_t
|
static uint8_t
|
||||||
@ -66,7 +68,14 @@ peek(uint16_t addr)
|
|||||||
{
|
{
|
||||||
MEMORY_MIRROR(addr);
|
MEMORY_MIRROR(addr);
|
||||||
|
|
||||||
return memory[addr];
|
if (addr > 0x7FFF) {
|
||||||
|
if (rom.prg_rom_size == 0x4000)
|
||||||
|
return rom.prg_rom[(addr - 0x8000) % 0x4000];
|
||||||
|
else if (rom.prg_rom_size == 0x8000)
|
||||||
|
return rom.prg_rom[addr - 0x8000];
|
||||||
|
} else {
|
||||||
|
return memory[addr];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t
|
static uint16_t
|
||||||
@ -738,7 +747,7 @@ interpret(void)
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
opcode = peek(regs.pc++);
|
opcode = peek(regs.pc++);
|
||||||
|
|
||||||
printf("opcode: $%02X\n", opcode);
|
printf("opcode: $%02X @ $%04X\n", opcode, regs.pc - 1);
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0x69:
|
case 0x69:
|
||||||
@ -1403,7 +1412,7 @@ interpret(void)
|
|||||||
regs.status.decimal_mode, regs.status.brk,
|
regs.status.decimal_mode, regs.status.brk,
|
||||||
regs.status.unused, regs.status.overflow,
|
regs.status.unused, regs.status.overflow,
|
||||||
regs.status.negative);
|
regs.status.negative);
|
||||||
printf("A: $%02X, X: $%02X, Y: $%02X, SP: $%02X, PC: $%02X\n",
|
printf("A: $%02X, X: $%02X, Y: $%02X, SP: $%02X, PC: $%04X\n",
|
||||||
regs.a, regs.x, regs.y, regs.sp, regs.pc);
|
regs.a, regs.x, regs.y, regs.sp, regs.pc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1416,26 +1425,51 @@ cpu_init(void)
|
|||||||
regs.pc = 0xFFFC;
|
regs.pc = 0xFFFC;
|
||||||
regs.sp = 0xFD;
|
regs.sp = 0xFD;
|
||||||
|
|
||||||
memset(®s.status, 0, sizeof(regs.status));
|
//memset(®s.status, 0, sizeof(regs.status));
|
||||||
regs.status.unused = 1;
|
regs.status.unused = 1;
|
||||||
|
|
||||||
cycles += 7;
|
cycles += 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(void)
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
cpu_init();
|
FILE *fp;
|
||||||
|
uint8_t *buf;
|
||||||
|
size_t buflen;
|
||||||
|
|
||||||
if (sizeof(program) > (0x10000 - 0x8000)) {
|
if (argc != 2) {
|
||||||
fprintf(stderr, "program is too big for memory\n");
|
fprintf(stderr, "Usage: %s rom.nes\n", basename(argv[0]));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(memory + 0x8000, program, sizeof(program));
|
fp = fopen(argv[1], "r");
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
buflen = ftell(fp);
|
||||||
|
buf = calloc(1, buflen);
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
if (fread(buf, 1, buflen, fp) != buflen && ferror(fp)) {
|
||||||
|
fprintf(stderr, "file %s was not read properly\n", argv[1]);
|
||||||
|
clearerr(fp);
|
||||||
|
fclose(fp);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
parse_rom(buf, buflen, &rom);
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
cpu_init();
|
||||||
|
|
||||||
|
/* TODO: move to separate file? */
|
||||||
|
if (rom.mapper != 0) {
|
||||||
|
fprintf(stderr, "Only iNES ROMs using Mapper 0 are supported for now.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memwrite16(0xFFFC, 0x8000);
|
||||||
regs.pc = 0x8000;
|
regs.pc = 0x8000;
|
||||||
|
|
||||||
printf("Initial State:\n");
|
|
||||||
printf("status: %i%i%i%i%i%i%i%i\n", regs.status.carry,
|
printf("status: %i%i%i%i%i%i%i%i\n", regs.status.carry,
|
||||||
regs.status.zero, regs.status.interrupt_disable,
|
regs.status.zero, regs.status.interrupt_disable,
|
||||||
regs.status.decimal_mode, regs.status.brk,
|
regs.status.decimal_mode, regs.status.brk,
|
||||||
@ -1448,5 +1482,7 @@ main(void)
|
|||||||
|
|
||||||
interpret();
|
interpret();
|
||||||
|
|
||||||
|
free_rom(&rom);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
56
rom.c
Normal file
56
rom.c
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "rom.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom)
|
||||||
|
{
|
||||||
|
size_t prg_rom_offset = 16;
|
||||||
|
size_t chr_rom_offset = 0;
|
||||||
|
|
||||||
|
if (data_len < 16) {
|
||||||
|
fprintf(stderr, "Invalid ROM: size less than 16 bytes\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[0] != 'N' || data[1] != 'E' || data[2] != 'S' || data[3] != 0x1A) {
|
||||||
|
fprintf(stderr, "Invalid ROM: not in iNES format\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* trainer data present */
|
||||||
|
if (data[6] & (1 << 2))
|
||||||
|
prg_rom_offset += 512;
|
||||||
|
|
||||||
|
rom->prg_rom_size = data[4] * 0x4000;
|
||||||
|
rom->prg_rom = calloc(rom->prg_rom_size, 1);
|
||||||
|
memcpy(rom->prg_rom, data + prg_rom_offset, rom->prg_rom_size);
|
||||||
|
|
||||||
|
rom->chr_rom_size = data[5] * 0x2000;
|
||||||
|
if (rom->chr_rom_size > 0) {
|
||||||
|
rom->chr_rom = calloc(rom->chr_rom_size, 1);
|
||||||
|
chr_rom_offset = prg_rom_offset + rom->prg_rom_size;
|
||||||
|
memcpy(rom->chr_rom, data + chr_rom_offset, rom->chr_rom_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[6] & (1 << 3)) != 0)
|
||||||
|
rom->mirror = M_FOUR;
|
||||||
|
else if ((data[6] & 1) == 1)
|
||||||
|
rom->mirror = M_VERTICAL;
|
||||||
|
else if ((data[6] & 1) == 0)
|
||||||
|
rom->mirror = M_HORIZONTAL;
|
||||||
|
|
||||||
|
rom->mapper = (data[6] & (0xF << 4)) >> 4;
|
||||||
|
rom->mapper |= data[7] & (0xF << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
free_rom(struct Rom *rom)
|
||||||
|
{
|
||||||
|
free(rom->prg_rom);
|
||||||
|
if (rom->chr_rom_size > 0)
|
||||||
|
free(rom->chr_rom);
|
||||||
|
}
|
17
rom.h
Normal file
17
rom.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
enum screen_mirroring {
|
||||||
|
M_HORIZONTAL,
|
||||||
|
M_VERTICAL,
|
||||||
|
M_FOUR,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Rom {
|
||||||
|
char *prg_rom;
|
||||||
|
char *chr_rom;
|
||||||
|
size_t prg_rom_size;
|
||||||
|
size_t chr_rom_size;
|
||||||
|
uint8_t mapper;
|
||||||
|
enum screen_mirroring mirror;
|
||||||
|
};
|
||||||
|
|
||||||
|
void parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom);
|
||||||
|
void free_rom(struct Rom *rom);
|
Loading…
Reference in New Issue
Block a user