Compare commits
10 Commits
85c6a414f9
...
e14776a473
Author | SHA1 | Date | |
---|---|---|---|
e14776a473 | |||
2084552c53 | |||
f591b4adf2 | |||
279478f16f | |||
c0679b404d | |||
27b9d4c120 | |||
97332c8a38 | |||
362341eac3 | |||
7d148924e2 | |||
2df7df5f5b |
4
Makefile
4
Makefile
@ -1,5 +1,7 @@
|
|||||||
|
CFLAGS = -O2 -Wall -Wextra -pedantic -Wno-unused-parameter
|
||||||
|
|
||||||
nes:
|
nes:
|
||||||
${CC} -o nes cpu.c rom.c
|
${CC} ${CFLAGS} -o nes cpu.c rom.c ppu.c apu.c
|
||||||
|
|
||||||
test: nes
|
test: nes
|
||||||
./nes ${HOME}/src/other/nes-test-roms/other/nestest.nes
|
./nes ${HOME}/src/other/nes-test-roms/other/nestest.nes
|
||||||
|
29
README.md
29
README.md
@ -1,6 +1,29 @@
|
|||||||
emu_nes
|
# emu_nes
|
||||||
=======
|
|
||||||
A Nintendo Entertainment System (NES) emulator written for the purpose of
|
A Nintendo Entertainment System (NES) emulator written for the purpose of
|
||||||
understanding how the NES and similar 8-bit computers of that era worked.
|
understanding how the NES and similar 8-bit computers of that era worked.
|
||||||
|
|
||||||
Makes heavy use of https://www.nesdev.org/.
|
The NES used a variant of the MOS 6502 8-bit microcontroller called a
|
||||||
|
Ricoh 2A03 that contained the 6502 without the BCD mode for arithmetic,
|
||||||
|
an audio signal generator (APU), controlling the gamepads, and for DMA.
|
||||||
|
([Source: NESdev](https://www.nesdev.org/wiki/DMA))
|
||||||
|
|
||||||
|
A major goal for the emulator is finishing the software implementation
|
||||||
|
in C and later implement this in hardware with either an FPGA or another
|
||||||
|
CPU like a RISC-V. Later, I want to generalize the 2A03 simulator into
|
||||||
|
a full 6502 for use with a Commodore 64 and Apple II.
|
||||||
|
|
||||||
|
Makes heavy use of the [NESdev wiki](https://www.nesdev.org/wiki).
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
- [x] CPU (Ricoh 2A03, variant of MOS 6502)
|
||||||
|
- [ ] PPU (Graphics)
|
||||||
|
- [ ] APU (Audio)
|
||||||
|
|
||||||
|
Currently, running the emulator with nestest.nes results in incorrect
|
||||||
|
results in the memory space of the APU registers. This is due to the
|
||||||
|
APU not being implemented yet.
|
||||||
|
|
||||||
|
Also, the only ROM mapper that is implemented is Mapper 0 to
|
||||||
|
run the test ROM, but it also supports many other ROMs listed
|
||||||
|
[here](https://nesdir.github.io/mapper0.html) such as Donkey Kong and
|
||||||
|
Super Mario Bros.
|
||||||
|
55
apu.c
Normal file
55
apu.c
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "apu.h"
|
||||||
|
|
||||||
|
struct apu apu = {0};
|
||||||
|
|
||||||
|
void
|
||||||
|
apu_init(void)
|
||||||
|
{
|
||||||
|
memset(&apu, 0, sizeof(apu));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
apu_read(uint16_t addr)
|
||||||
|
{
|
||||||
|
switch (addr) {
|
||||||
|
/* pulse1 */
|
||||||
|
case 0x4000:
|
||||||
|
return (apu.pulse1.duty << 6) | (apu.pulse1.envelope << 5)
|
||||||
|
| (apu.pulse1.const_vol << 4) | apu.pulse1.envelope_vol;
|
||||||
|
case 0x4001:
|
||||||
|
return (apu.pulse1.sweep_enable << 7) | (apu.pulse1.sweep_period << 4)
|
||||||
|
| (apu.pulse1.sweep_negative << 3) | apu.pulse1.sweep_shift_count;
|
||||||
|
case 0x4002:
|
||||||
|
return apu.pulse1.timer_low;
|
||||||
|
case 0x4003:
|
||||||
|
return (apu.pulse1.length_counter_load << 3) | apu.pulse1.timer_high;
|
||||||
|
/* pulse2 */
|
||||||
|
case 0x4004:
|
||||||
|
return (apu.pulse2.duty << 6) | (apu.pulse2.envelope << 5)
|
||||||
|
| (apu.pulse2.const_vol << 4) | apu.pulse2.envelope_vol;
|
||||||
|
case 0x4005:
|
||||||
|
return (apu.pulse1.sweep_enable << 7) | (apu.pulse1.sweep_period << 4)
|
||||||
|
| (apu.pulse1.sweep_negative << 3) | apu.pulse1.sweep_shift_count;
|
||||||
|
case 0x4006:
|
||||||
|
return apu.pulse1.timer_low;
|
||||||
|
case 0x4007:
|
||||||
|
return (apu.pulse1.length_counter_load << 3) | apu.pulse1.timer_high;
|
||||||
|
/* status */
|
||||||
|
case 0x4015:
|
||||||
|
return (apu.status.dmc_int << 7) | (apu.status.frame_int << 6)
|
||||||
|
| (apu.status.dmc_active << 4) | (apu.status.lc_noise << 3)
|
||||||
|
| (apu.status.lc_triangle << 2) | (apu.status.lc_pulse2 << 1)
|
||||||
|
| apu.status.lc_pulse1;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Invalid APU read at $%04X!\n", addr);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
apu_write(uint16_t addr, uint8_t byte)
|
||||||
|
{
|
||||||
|
}
|
105
apu.h
Normal file
105
apu.h
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#ifndef APU_H
|
||||||
|
#define APU_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct pulse {
|
||||||
|
/* $4000/$4004 */
|
||||||
|
uint8_t duty : 2;
|
||||||
|
uint8_t envelope : 1;
|
||||||
|
uint8_t const_vol : 1;
|
||||||
|
uint8_t envelope_vol : 4;
|
||||||
|
/* $4001/$4005 */
|
||||||
|
uint8_t sweep_enable : 1;
|
||||||
|
uint8_t sweep_period : 3;
|
||||||
|
uint8_t sweep_negative : 1;
|
||||||
|
uint8_t sweep_shift_count : 3;
|
||||||
|
/* $4002/$4006 */
|
||||||
|
uint8_t timer_low;
|
||||||
|
/* $4003/$4007 */
|
||||||
|
uint8_t length_counter_load : 5;
|
||||||
|
uint8_t timer_high : 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct triangle {
|
||||||
|
/* $4008 */
|
||||||
|
uint8_t counter_disable : 1;
|
||||||
|
uint8_t counter_reload : 7;
|
||||||
|
/* $400A */
|
||||||
|
uint8_t timer_low;
|
||||||
|
/* $400B */
|
||||||
|
uint8_t counter_load : 5;
|
||||||
|
uint8_t timer_high : 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct noise {
|
||||||
|
/* $400C */
|
||||||
|
uint8_t padding1 : 2;
|
||||||
|
uint8_t loop_envelope : 1;
|
||||||
|
uint8_t constant_volume : 1;
|
||||||
|
uint8_t envelope_volume : 4;
|
||||||
|
/* $400E */
|
||||||
|
uint8_t loop_noise : 1;
|
||||||
|
uint8_t padding2 : 3;
|
||||||
|
uint8_t noise_period : 4;
|
||||||
|
/* $400F */
|
||||||
|
uint8_t length_counter_load : 5;
|
||||||
|
uint8_t padding3 : 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dmc {
|
||||||
|
/* $4010 */
|
||||||
|
uint8_t irq_enable : 1;
|
||||||
|
uint8_t loop : 1;
|
||||||
|
uint8_t padding1 : 2;
|
||||||
|
uint8_t frequency : 4;
|
||||||
|
/* $4011 */
|
||||||
|
uint8_t padding2 : 1;
|
||||||
|
uint8_t load_counter : 7;
|
||||||
|
/* $4012 */
|
||||||
|
uint8_t sample_addr;
|
||||||
|
/* $4013 */
|
||||||
|
uint8_t sample_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct apu {
|
||||||
|
/* $4000-$4013 (write) */
|
||||||
|
struct pulse pulse1;
|
||||||
|
struct pulse pulse2;
|
||||||
|
struct triangle triangle;
|
||||||
|
struct noise noise;
|
||||||
|
struct dmc dmc;
|
||||||
|
/* $4015 (write) */
|
||||||
|
struct control {
|
||||||
|
uint8_t padding : 3;
|
||||||
|
uint8_t dmc_enable : 1;
|
||||||
|
uint8_t noise_enable : 1;
|
||||||
|
uint8_t triangle_enable : 1;
|
||||||
|
uint8_t pulse2_enable : 1;
|
||||||
|
uint8_t pulse1_enable : 1;
|
||||||
|
} control;
|
||||||
|
/* $4015 (read) */
|
||||||
|
struct status {
|
||||||
|
uint8_t dmc_int : 1;
|
||||||
|
uint8_t frame_int : 1;
|
||||||
|
uint8_t padding : 1;
|
||||||
|
uint8_t dmc_active : 1;
|
||||||
|
uint8_t lc_noise : 1;
|
||||||
|
uint8_t lc_triangle : 1;
|
||||||
|
uint8_t lc_pulse2 : 1;
|
||||||
|
uint8_t lc_pulse1 : 1;
|
||||||
|
} status;
|
||||||
|
/* $4017 (write) */
|
||||||
|
struct frame_counter {
|
||||||
|
uint8_t mode : 1;
|
||||||
|
uint8_t irq_inhibit : 1;
|
||||||
|
uint8_t padding : 6;
|
||||||
|
} frame_counter;
|
||||||
|
};
|
||||||
|
extern struct apu apu;
|
||||||
|
|
||||||
|
void apu_tick(void);
|
||||||
|
uint8_t apu_read(uint16_t addr);
|
||||||
|
void apu_write(uint16_t addr, uint8_t byte);
|
||||||
|
|
||||||
|
#endif /* APU_H */
|
45
cpu.c
45
cpu.c
@ -5,8 +5,10 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "apu.h"
|
||||||
#include "cpu.h"
|
#include "cpu.h"
|
||||||
#include "opcodes.h"
|
#include "opcodes.h"
|
||||||
|
#include "ppu.h"
|
||||||
#include "rom.h"
|
#include "rom.h"
|
||||||
|
|
||||||
#define MAX(a, b) ((a > b) ? a : b)
|
#define MAX(a, b) ((a > b) ? a : b)
|
||||||
@ -26,16 +28,14 @@
|
|||||||
|
|
||||||
#define MEMORY_MIRROR(addr) \
|
#define MEMORY_MIRROR(addr) \
|
||||||
if (addr < 0x2000) \
|
if (addr < 0x2000) \
|
||||||
addr &= 0x07FF; \
|
addr &= 0x07FF;
|
||||||
else if (addr < 0x4000) \
|
|
||||||
addr &= 0x2007;
|
|
||||||
|
|
||||||
#define PUSH(b) \
|
#define PUSH(b) \
|
||||||
(memwrite(0x0100 + regs.sp--, b))
|
(memwrite(0x0100 + regs.sp--, b))
|
||||||
#define PULL() \
|
#define PULL() \
|
||||||
(peek(0x0100 + ++regs.sp))
|
(peek(0x0100 + ++regs.sp))
|
||||||
|
|
||||||
struct Rom rom = {0};
|
struct rom rom = {0};
|
||||||
uint32_t cycles = 0;
|
uint32_t cycles = 0;
|
||||||
|
|
||||||
bool page_crossed = false;
|
bool page_crossed = false;
|
||||||
@ -52,9 +52,10 @@ peek(uint16_t addr)
|
|||||||
return rom.prg_rom[addr - 0x8000];
|
return rom.prg_rom[addr - 0x8000];
|
||||||
else
|
else
|
||||||
fprintf(stderr, "PRG ROG size is not 0x4000 nor 0x8000\n"), exit(1);
|
fprintf(stderr, "PRG ROG size is not 0x4000 nor 0x8000\n"), exit(1);
|
||||||
} else {
|
} else if ((addr >= 0x4000 && addr <= 0x4013) || addr == 0x4015 || addr == 0x4017)
|
||||||
|
return apu_read(addr);
|
||||||
|
else
|
||||||
return memory[addr];
|
return memory[addr];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t
|
static uint16_t
|
||||||
@ -67,6 +68,11 @@ peek16(uint16_t addr)
|
|||||||
static void
|
static void
|
||||||
memwrite(uint16_t addr, uint8_t byte)
|
memwrite(uint16_t addr, uint8_t byte)
|
||||||
{
|
{
|
||||||
|
if (addr >= 0x4000 && addr <= 0x4017) {
|
||||||
|
apu_write(addr, byte);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MEMORY_MIRROR(addr);
|
MEMORY_MIRROR(addr);
|
||||||
|
|
||||||
memory[addr] = byte;
|
memory[addr] = byte;
|
||||||
@ -145,15 +151,24 @@ opcode_mem(enum addressing_mode mode)
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tick(void)
|
||||||
|
{
|
||||||
|
cycles++;
|
||||||
|
ppu_tick();
|
||||||
|
ppu_tick();
|
||||||
|
ppu_tick();
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
branch(uint16_t addr, bool cond)
|
branch(uint16_t addr, bool cond)
|
||||||
{
|
{
|
||||||
if (!cond)
|
if (!cond)
|
||||||
return;
|
return;
|
||||||
cycles++;
|
tick();
|
||||||
|
|
||||||
if (((regs.pc + 1) & 0xFF00) != (addr & 0xFF00))
|
if (((regs.pc + 1) & 0xFF00) != (addr & 0xFF00))
|
||||||
cycles++;
|
tick();
|
||||||
|
|
||||||
regs.pc = addr;
|
regs.pc = addr;
|
||||||
}
|
}
|
||||||
@ -946,18 +961,21 @@ interpret(void)
|
|||||||
|
|
||||||
while (spaces--) putchar(' ');
|
while (spaces--) putchar(' ');
|
||||||
|
|
||||||
printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%d\n",
|
printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X PPU:%3d,%3d CYC:%d\n",
|
||||||
regs.a, regs.x, regs.y, STATUS_TO_INT(), regs.sp, cycles);
|
regs.a, regs.x, regs.y, STATUS_TO_INT(), regs.sp,
|
||||||
|
ppu.scanlines, ppu.cycles, cycles);
|
||||||
|
|
||||||
if (opcodes[op].memread)
|
if (opcodes[op].memread)
|
||||||
opcodes[op].instr(peek(arg));
|
opcodes[op].instr(peek(arg));
|
||||||
else
|
else
|
||||||
opcodes[op].instr(arg);
|
opcodes[op].instr(arg);
|
||||||
|
|
||||||
cycles += opcodes[op].cycles;
|
for (uint8_t i = 0; i < opcodes[op].cycles; i++)
|
||||||
|
tick();
|
||||||
|
|
||||||
if (page_crossed) {
|
if (page_crossed) {
|
||||||
if (opcodes[op].page_cross)
|
if (opcodes[op].page_cross)
|
||||||
cycles++;
|
tick();
|
||||||
page_crossed = false;
|
page_crossed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -976,6 +994,8 @@ cpu_init(void)
|
|||||||
regs.status.unused = 1;
|
regs.status.unused = 1;
|
||||||
|
|
||||||
cycles += 7;
|
cycles += 7;
|
||||||
|
for (uint8_t i = 0; i < 7 * 3; i++)
|
||||||
|
ppu_tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -1005,6 +1025,7 @@ main(int argc, char *argv[])
|
|||||||
|
|
||||||
parse_rom(buf, buflen, &rom);
|
parse_rom(buf, buflen, &rom);
|
||||||
free(buf);
|
free(buf);
|
||||||
|
ppu.rom = &rom;
|
||||||
|
|
||||||
cpu_init();
|
cpu_init();
|
||||||
|
|
||||||
|
156
ppu.c
Normal file
156
ppu.c
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
#include "ppu.h"
|
||||||
|
|
||||||
|
struct ppu ppu = {0};
|
||||||
|
|
||||||
|
static void
|
||||||
|
vram_addr_inc(void)
|
||||||
|
{
|
||||||
|
ppu.regs.v += (ppu.ctrl.inc_mode) ? 32 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t
|
||||||
|
vram_addr_mirror(uint16_t addr)
|
||||||
|
{
|
||||||
|
uint8_t nametable;
|
||||||
|
|
||||||
|
addr &= 0x2fff;
|
||||||
|
addr -= 0x2000;
|
||||||
|
nametable = addr / 0x400;
|
||||||
|
|
||||||
|
switch (ppu.rom->mirror) {
|
||||||
|
case M_HORIZONTAL:
|
||||||
|
if (nametable == 1 || nametable == 2)
|
||||||
|
return addr - 0x400;
|
||||||
|
else if (nametable == 3)
|
||||||
|
return addr - 0x800;
|
||||||
|
break;
|
||||||
|
case M_VERTICAL:
|
||||||
|
if (nametable == 2 || nametable == 3)
|
||||||
|
return addr - 0x800;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ppu_tick(void)
|
||||||
|
{
|
||||||
|
ppu.cycles++;
|
||||||
|
|
||||||
|
if (ppu.cycles > 256 && ppu.cycles <= 320)
|
||||||
|
ppu.oam_addr = 0;
|
||||||
|
|
||||||
|
if (ppu.cycles >= 341) {
|
||||||
|
ppu.cycles -= 341;
|
||||||
|
ppu.scanlines++;
|
||||||
|
}
|
||||||
|
if (ppu.scanlines >= 262)
|
||||||
|
ppu.scanlines -= 262;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
ppu_read(uint16_t addr)
|
||||||
|
{
|
||||||
|
uint8_t tmp;
|
||||||
|
|
||||||
|
switch (addr) {
|
||||||
|
case 0x2002:
|
||||||
|
tmp = (ppu.status.vblank << 7)
|
||||||
|
| (ppu.status.sprite0_hit << 6)
|
||||||
|
| (ppu.status.sprite_overflow << 5);
|
||||||
|
|
||||||
|
ppu.status.vblank = 0;
|
||||||
|
ppu.regs.write_toggle = 0;
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
case 0x2004:
|
||||||
|
return ppu.oam[ppu.oam_addr];
|
||||||
|
case 0x2007:
|
||||||
|
tmp = ppu.last_read;
|
||||||
|
|
||||||
|
if (addr <= 0x1fff)
|
||||||
|
ppu.last_read = ppu.rom->chr_rom[ppu.regs.v];
|
||||||
|
else if (addr <= 0x2fff)
|
||||||
|
ppu.last_read = ppu.vram[vram_addr_mirror(addr)];
|
||||||
|
|
||||||
|
vram_addr_inc();
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Invalid PPU read at address $%04\n", addr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ppu_write(uint16_t addr, uint8_t byte)
|
||||||
|
{
|
||||||
|
switch (addr) {
|
||||||
|
case 0x2000:
|
||||||
|
ppu.ctrl.nametable_base = byte & 3;
|
||||||
|
ppu.ctrl.inc_mode = byte & (1 << 2);
|
||||||
|
ppu.ctrl.sprite_tile_sel = byte & (1 << 3);
|
||||||
|
ppu.ctrl.bg_tile_sel = byte & (1 << 4);
|
||||||
|
ppu.ctrl.sprite_height = byte & (1 << 5);
|
||||||
|
ppu.ctrl.master_slave = byte & (1 << 6);
|
||||||
|
ppu.ctrl.nmi_enable = byte & (1 << 7);
|
||||||
|
|
||||||
|
if (ppu.ctrl.nametable_base & 1)
|
||||||
|
ppu.regs.scroll_x += 256;
|
||||||
|
if (ppu.ctrl.nametable_base & 2)
|
||||||
|
ppu.regs.scroll_y += 240;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: send NMI if vblank flag still set and nmi_enable
|
||||||
|
* changes from 0 to 1
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
case 0x2001:
|
||||||
|
ppu.mask.grayscale = byte & 1;
|
||||||
|
ppu.mask.bg_left_enable = byte & (1 << 1);
|
||||||
|
ppu.mask.sprite_left_enable = byte & (1 << 2);
|
||||||
|
ppu.mask.bg_enable = byte & (1 << 3);
|
||||||
|
ppu.mask.sprite_enable = byte & (1 << 4);
|
||||||
|
ppu.mask.colour_emphasis = byte & (1 << 5);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 0x2003:
|
||||||
|
ppu.oam_addr = byte;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 0x2004:
|
||||||
|
ppu.oam[ppu.oam_addr++] = byte;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 0x2005:
|
||||||
|
if (ppu.regs.write_toggle == 0)
|
||||||
|
ppu.regs.scroll_x = byte;
|
||||||
|
else
|
||||||
|
ppu.regs.scroll_y = byte;
|
||||||
|
ppu.regs.write_toggle = !ppu.regs.write_toggle;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 0x2006:
|
||||||
|
if (ppu.regs.write_toggle == 0)
|
||||||
|
ppu.regs.v = (byte << 8);
|
||||||
|
else {
|
||||||
|
ppu.regs.v |= byte;
|
||||||
|
ppu.regs.v &= 0x3fff;
|
||||||
|
}
|
||||||
|
ppu.regs.write_toggle = !ppu.regs.write_toggle;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 0x2007:
|
||||||
|
ppu.vram[ppu.regs.v] = byte;
|
||||||
|
vram_addr_inc();
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 0x4014:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Invalid PPU write at address $%04X\n", addr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
55
ppu.h
Normal file
55
ppu.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#ifndef PPU_H
|
||||||
|
#define PPU_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "rom.h"
|
||||||
|
|
||||||
|
struct ppu {
|
||||||
|
uint8_t vram[2048];
|
||||||
|
uint8_t oam[64*4];
|
||||||
|
uint8_t oam_addr; /* $2003 */
|
||||||
|
uint8_t palette[16*2];
|
||||||
|
uint16_t cycles, scanlines;
|
||||||
|
struct rom *rom;
|
||||||
|
|
||||||
|
uint8_t last_read;
|
||||||
|
|
||||||
|
struct ppu_regs {
|
||||||
|
uint16_t v : 15; /* current vram address */
|
||||||
|
uint16_t t : 15; /* temporary vram address */
|
||||||
|
uint16_t scroll_x : 9; /* x scroll position */
|
||||||
|
uint16_t scroll_y : 9; /* y scroll position */
|
||||||
|
uint8_t write_toggle : 1; /* first/second write toggle */
|
||||||
|
} regs;
|
||||||
|
|
||||||
|
struct ppu_ctrl { /* $2000 */
|
||||||
|
uint8_t nmi_enable : 1;
|
||||||
|
uint8_t master_slave : 1;
|
||||||
|
uint8_t sprite_height : 1;
|
||||||
|
uint8_t bg_tile_sel : 1;
|
||||||
|
uint8_t sprite_tile_sel : 1;
|
||||||
|
uint8_t inc_mode : 1;
|
||||||
|
uint8_t nametable_base : 2;
|
||||||
|
} ctrl;
|
||||||
|
struct ppu_mask { /* $2001 */
|
||||||
|
uint8_t colour_emphasis : 3;
|
||||||
|
uint8_t sprite_enable : 1;
|
||||||
|
uint8_t bg_enable : 1;
|
||||||
|
uint8_t sprite_left_enable : 1;
|
||||||
|
uint8_t bg_left_enable : 1;
|
||||||
|
uint8_t grayscale : 1;
|
||||||
|
} mask;
|
||||||
|
struct ppu_status { /* $2002 */
|
||||||
|
uint8_t vblank : 1;
|
||||||
|
uint8_t sprite0_hit : 1;
|
||||||
|
uint8_t sprite_overflow : 1;
|
||||||
|
uint8_t pad : 5;
|
||||||
|
} status;
|
||||||
|
};
|
||||||
|
extern struct ppu ppu;
|
||||||
|
|
||||||
|
void ppu_tick(void);
|
||||||
|
|
||||||
|
#endif /* PPU_H */
|
4
rom.c
4
rom.c
@ -6,7 +6,7 @@
|
|||||||
#include "rom.h"
|
#include "rom.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom)
|
parse_rom(const uint8_t *data, size_t data_len, struct rom *rom)
|
||||||
{
|
{
|
||||||
size_t prg_rom_offset = 16;
|
size_t prg_rom_offset = 16;
|
||||||
size_t chr_rom_offset = 0;
|
size_t chr_rom_offset = 0;
|
||||||
@ -48,7 +48,7 @@ parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
free_rom(struct Rom *rom)
|
free_rom(struct rom *rom)
|
||||||
{
|
{
|
||||||
free(rom->prg_rom);
|
free(rom->prg_rom);
|
||||||
if (rom->chr_rom_size > 0)
|
if (rom->chr_rom_size > 0)
|
||||||
|
11
rom.h
11
rom.h
@ -1,10 +1,13 @@
|
|||||||
|
#ifndef ROM_H
|
||||||
|
#define ROM_H
|
||||||
|
|
||||||
enum screen_mirroring {
|
enum screen_mirroring {
|
||||||
M_HORIZONTAL,
|
M_HORIZONTAL,
|
||||||
M_VERTICAL,
|
M_VERTICAL,
|
||||||
M_FOUR,
|
M_FOUR,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Rom {
|
struct rom {
|
||||||
char *prg_rom;
|
char *prg_rom;
|
||||||
char *chr_rom;
|
char *chr_rom;
|
||||||
size_t prg_rom_size;
|
size_t prg_rom_size;
|
||||||
@ -13,5 +16,7 @@ struct Rom {
|
|||||||
enum screen_mirroring mirror;
|
enum screen_mirroring mirror;
|
||||||
};
|
};
|
||||||
|
|
||||||
void parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom);
|
void parse_rom(const uint8_t *data, size_t data_len, struct rom *rom);
|
||||||
void free_rom(struct Rom *rom);
|
void free_rom(struct rom *rom);
|
||||||
|
|
||||||
|
#endif /* ROM_H */
|
||||||
|
Loading…
Reference in New Issue
Block a user