Просмотр исходного кода

Various NMI and reading PPU fixes

- NMI still imperfect
 - Passes vast majority of bisqwit's ppu_read_buffer tests
v2
Nathaniel Walizer 8 месяцев назад
Родитель
Сommit
8e13f8c30b
6 измененных файлов: 94 добавлений и 63 удалений
  1. +1
    -6
      src/cart.c
  2. +48
    -36
      src/f6502.c
  3. +5
    -1
      src/linux/port.c
  4. +27
    -10
      src/nes.c
  5. +2
    -1
      src/ppu.c
  6. +11
    -9
      src/ppu.h

+ 1
- 6
src/cart.c Просмотреть файл

@@ -102,16 +102,11 @@ int nes_cart_load_mem(void* data, int size, nes* sys) {
mem->ppu.bank[page] = chr_page(&mem->ppu, page);
}

// PPU Bank 8 - 11: Mirrored VRAM
// PPU Bank 8 - 11 (+ 12 - 15): Mirrored VRAM
nes_ppu_set_mirroring(
&mem->ppu, hdr->flags_6 & ines_Flag_Vert_Mirror
);

// PPU Bank 12 - 15: Unused & Palette
for (int page = 12; page < 16; ++page) {
mem->ppu.bank[page] = mem->ppu.pal_bank;
}

if (mem->mapper.init) {
status = mem->mapper.init(&mem->mapper,
hdr, mem);


+ 48
- 36
src/f6502.c Просмотреть файл

@@ -69,28 +69,27 @@ static inline uint8_t f6502_read(nes_Memory* mem,

// TODO: Set nametable to 0 when in VBlank??

return val;
}
mem->ppu.bus = (val & ~ppu_Status_Open_Bus_Mask) |
(mem->ppu.bus & ppu_Status_Open_Bus_Mask);
} break;

case ppu_reg_oam_data:
return mem->ppu.oam[mem->ppu.oam_addr];
mem->ppu.bus = mem->ppu.oam[mem->ppu.oam_addr];
break;

case ppu_reg_data:
{ addr = mem->ppu.addr & 0x3FFFU;
addr = mem->ppu.addr & 0x3FFFU;
mem->ppu.addr += mem->ppu.addr_inc;
uint8_t data = mem->ppu.bank[addr >> 10][addr & 0x3FFU];
if (addr >= 0x3F00) {
if (addr >= NES_PPU_PAL_START) {
mem->ppu.bus = mem->ppu.pal_ram[addr & 0x1FU];
addr &= 0x2FFFU;
mem->ppu.data = mem->ppu.bank[addr >> 10][addr & 0x3FFU];
return data;
} else {
uint8_t val = mem->ppu.data;
mem->ppu.data = data;
return val;
} else {
mem->ppu.bus = mem->ppu.data;
}
mem->ppu.data = mem->ppu.bank[addr >> 10][addr & 0x3FFU];
break;
}
}
break;
return mem->ppu.bus;

case 0x4000:
switch (addr & 0x1FU) {
@@ -136,6 +135,17 @@ static inline uint16_t f6502_read16(nes_Memory* mem,
((uint16_t)f6502_read(mem, addr + 1) << 8));
}


typedef struct {
uint16_t clocks;
uint16_t interrupt;
} write_ret;

#define INT_TRIGGERED(x) (x >> 16)
#define CLOCKS(x) ((uint16_t)x)

#define TRIGGER_INT (1U << 16)

static inline int f6502_write(nes_Memory* mem,
uint16_t addr, uint8_t val) {
int ret = 0;
@@ -163,9 +173,7 @@ static inline int f6502_write(nes_Memory* mem,
f6502_set_IRQ(core, irq);

// Interrupt [may have been] triggered
// We're not strictly counting cycles,
// so just overload the counter
ret = 1000;
ret = TRIGGER_INT;
}
#endif
mem->ram[addr] = val;
@@ -178,6 +186,14 @@ static inline int f6502_write(nes_Memory* mem,
case 0x2000:
switch (addr & 0x7U) {
case ppu_reg_ctrl:
if ( (mem->ppu.status & ppu_Status_VBlank) &&
!(mem->ppu.ctrl & ppu_Control_VBlank) &&
(val & ppu_Control_VBlank)) {
f6502_Core* core = container_of(mem, f6502_Core, memory);
/* Trigger NMI if VBlank was set */
f6502_set_NMI(core, 1);
ret = TRIGGER_INT;
}
mem->ppu.ctrl &= ppu_Control_Nametable_Mask;
mem->ppu.ctrl |= (val & ~ppu_Control_Nametable_Mask);
mem->ppu.t &= ~(ppu_Control_Nametable_Mask << 10);
@@ -233,25 +249,19 @@ static inline int f6502_write(nes_Memory* mem,
addr = mem->ppu.addr & 0x3FFFU;
if (addr >= NES_PPU_CHR_SIZE || mem->ppu.chr_ram) {
LOGI("PPU W %04X < %02X", addr, val);
mem->ppu.bank[addr >> 10][addr & 0x3FFU] = val;
if (addr >= NES_PPU_PAL_START) {
// Copy to render reference
// Copy to palette RAM
addr &= 0x1FU;
uint8_t* pal = mem->ppu.palette;
mem->ppu.pal_ram[addr] = val;
if (0 != (addr & 0x3)) {
pal[addr] = val & 0x3FU;
}

// Memory-mapped mirroring
addr |= 0x300U;
for (int mirror = 0; mirror < 0x100; mirror += 0x20) {
mem->ppu.pal_bank[addr + mirror] = val;
}
if (0 == (addr & 3)) {
for (int mirror = 0; mirror < 0x100; mirror += 0x20) {
mem->ppu.pal_bank[(addr ^ 0x10U) + mirror] = val;
}
// Copy to render reference
mem->ppu.palette[addr] = val & 0x3FU;
} else {
// Mirror
mem->ppu.pal_ram[addr ^ 0x10] = val;
}
} else {
mem->ppu.bank[addr >> 10][addr & 0x3FFU] = val;
}
} else {
LOGE("PPU W %04X < %02X : INVALID", addr, val);
@@ -259,6 +269,7 @@ static inline int f6502_write(nes_Memory* mem,
mem->ppu.addr += mem->ppu.addr_inc;
break;
}
mem->ppu.bus = val;
break;

case 0x4000:
@@ -533,7 +544,7 @@ static inline int f6502_check_interrupts(f6502_Core* core) {
#define BIF_HCF() \
if (pc_prev == PC + 2) { \
++PC; \
clocks_elapsed = -clocks_elapsed; \
clocks_elapsed += INT_TRIGGER; \
goto step_done; \
}
#else
@@ -647,7 +658,8 @@ static inline int f6502_check_interrupts(f6502_Core* core) {
} \
A = res; \
}
#define ST(reg, a) clocks_elapsed += f6502_write(&core->memory, (a), reg)
#define ST(reg, a) \
clocks_elapsed += f6502_write(&core->memory, (a), reg)


static int f6502_do_step(f6502_Core* core, int clocks) {
@@ -1712,8 +1724,8 @@ int f6502_step(f6502_Core* core, int clocks) {
}
*/
clocks_elapsed += f6502_check_interrupts(core);
clocks_elapsed += f6502_do_step(core,
clocks - clocks_elapsed);
clocks = f6502_do_step(core, clocks - clocks_elapsed);
clocks_elapsed += CLOCKS(clocks);
core->clocks += clocks_elapsed;
return clocks_elapsed;
}


+ 5
- 1
src/linux/port.c Просмотреть файл

@@ -15,9 +15,11 @@
#include "port.h"
#include "save.h"

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

//#define NESE_TURBO


/*
* OS-specific file operations
@@ -321,6 +323,7 @@ int nese_frame_ready(void* plat_data) {

// TODO: Perform more actions

#ifndef NESE_TURBO
if (0 == status) {
plat->t_target += FRAME_TIME_NS;
int64_t slept_ns = time_sleep_until(plat->t_target);
@@ -330,6 +333,7 @@ int nese_frame_ready(void* plat_data) {
printf("Out of sync: %d\n", (int)slept_ns);
}
}
#endif // NESE_TURBO

return status;
}


+ 27
- 10
src/nes.c Просмотреть файл

@@ -32,12 +32,6 @@ void nes_done(nes* sys) {
static int nes_vsync(nes* sys, void* plat) {
int status = 0;

sys->core.memory.ppu.status |= ppu_Status_VBlank;
if (sys->core.memory.ppu.ctrl & ppu_Control_VBlank) {
LOGD("VBlank NMI");
f6502_set_NMI(&sys->core, 1);
}

nes_Memory* mem = &sys->core.memory;
if (0 == status && NULL != mem->mapper.vsync) {
mem->mapper.vsync(&mem->mapper);
@@ -81,6 +75,7 @@ static int nes_hsync(nes* sys, void* plat) {
sys->ppu.scanline = nes_ppu_prerender_line;
}

// Beginning of the next line
switch (sys->ppu.scanline) {
case nes_ppu_prerender_line:
{ nes_PPU_Memory* mem = &sys->core.memory.ppu;
@@ -94,7 +89,7 @@ static int nes_hsync(nes* sys, void* plat) {
int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ?
(mem->addr & 0x1FU) : 0;
// Don't use the rendering palette (masked transparency)
status = nese_frame_start(plat, mem->pal_bank[0x300 + pal_idx] & 0x3FU);
status = nese_frame_start(plat, mem->pal_ram[pal_idx] & 0x3FU);

} break;

@@ -110,21 +105,23 @@ static int nes_hsync(nes* sys, void* plat) {
return status;
}

#define vblank_set_dot (1U)
#define mapper_hsync_dot (300U)
#define render_dot (256U)
#define vblank_clear_dot (320U)
#define vblank_clear_dot (341U)

int nes_loop(nes* sys, void* plat) {
int status = 0;
int dot = 0;
bool mapper_hsync = false;
bool vbl_clear = false;
bool vbl_set = false;

while (0 == status) {
int target_dot = nes_ppu_scanline_dots;

if ( nes_ppu_frame_end_line - 1 == sys->ppu.scanline &&
sys->core.memory.ppu.status & ppu_Status_VBlank) {
(sys->core.memory.ppu.status & ppu_Status_VBlank)) {
target_dot = vblank_clear_dot;
vbl_clear = true;
}
@@ -137,6 +134,13 @@ int nes_loop(nes* sys, void* plat) {
mapper_hsync = true;
}

if ( vbl_set ||
( nes_ppu_vblank_line == sys->ppu.scanline &&
dot < vblank_set_dot )) {
target_dot = vblank_set_dot;
vbl_set = true;
}

if ( sys->ppu.hit_line == sys->ppu.scanline &&
( sys->core.memory.ppu.mask &
(ppu_Mask_Sprite | ppu_Mask_Back)) &&
@@ -145,7 +149,7 @@ int nes_loop(nes* sys, void* plat) {
if (dot >= hit_dot) {
LOGD("Line %d sprite 0 hit @ %d", sys->ppu.hit_line, hit_dot);
sys->core.memory.ppu.status |= ppu_Status_Hit;
} else {
} else if (target_dot > hit_dot) {
target_dot = hit_dot;
}
}
@@ -176,6 +180,15 @@ int nes_loop(nes* sys, void* plat) {
dot += 3 * f6502_step(&sys->core, cpu_cycles);
}

if (vbl_set && dot >= vblank_set_dot) {
sys->core.memory.ppu.status |= ppu_Status_VBlank;
if (sys->core.memory.ppu.ctrl & ppu_Control_VBlank) {
LOGD("VBlank NMI");
f6502_set_NMI(&sys->core, 1);
}
vbl_set = false;
}

if (vbl_clear && dot >= vblank_clear_dot) {
sys->core.memory.ppu.status &= ~ppu_Status_VBlank;
vbl_clear = false;
@@ -191,6 +204,10 @@ int nes_loop(nes* sys, void* plat) {
if (dot >= nes_ppu_scanline_dots) {
dot -= nes_ppu_scanline_dots;
status = nes_hsync(sys, plat);
if ( nes_ppu_vblank_line == sys->ppu.scanline &&
dot >= vblank_set_dot ) {
vbl_set = true;
}
}
}



+ 2
- 1
src/ppu.c Просмотреть файл

@@ -30,7 +30,8 @@ static const uint8_t mirror_schemes[][4] = {
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] =
mem->bank[nes_PPU_Nametable_Bank_Index + i + 4] =
mem->bank[nes_PPU_Nametable_Bank_Index + i] =
vram_page(mem, (int)mirror_schemes[mirror][i]);
}
}


+ 11
- 9
src/ppu.h Просмотреть файл

@@ -97,19 +97,21 @@ typedef struct {
uint8_t mask;
uint8_t status;
uint8_t oam_addr;
uint16_t addr; // aka "v"
uint8_t data;
uint8_t x; // Fine X scroll
uint16_t t; // temp VRAM addr
uint8_t latch; // aka "w" - TODO: Could this be a flag?
uint8_t addr_inc; // Auto-increment (1 or 32)
uint16_t addr; // aka "v"
uint8_t data; // Next incremented data val (NOT last value)
uint8_t x; // Fine X scroll
uint16_t t; // temp VRAM addr
uint8_t latch; // aka "w" - TODO: Could this be a flag?
uint8_t addr_inc; // Auto-increment (1 or 32)
uint8_t unused[2]; // 2 B unused
uint8_t bus; // Last value transmitted (NOT next data val)

// Static memory banks
int n_chr_banks;
uint8_t n_chr_banks; // TODO: Changed from int; consequences?
uint8_t palette[32]; // Rendering palette with transparency masked
uint8_t vram[NES_PPU_VRAM_SIZE];
uint8_t pal_bank[NES_CHR_ROM_PAGE_SIZE]; // Raw palette data mirrored in banks 12-15, also pal
uint8_t pal_ram[32]; // Raw palette data in RAM
uint8_t oam[NES_PPU_OAM_SIZE];
uint8_t vram[NES_PPU_VRAM_SIZE];
} nes_PPU_Memory;




Загрузка…
Отмена
Сохранить