| @@ -1,25 +1,35 @@ | |||
| #include "memory.h" | |||
| //#define NESE_DEBUG "APU" | |||
| #include "log.h" | |||
| void nes_apu_init(nes_APU* apu, nes_APU_Memory* mem, int freq) { | |||
| // TODO | |||
| apu->frame_delay = nes_apu_quarter_frame_ticks; | |||
| // TODO: Set LFSR? | |||
| } | |||
| int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||
| static int nes_apu_run(nes_APU* apu, nes_Memory* core_mem, | |||
| int ticks) { | |||
| // int ret = 0; | |||
| nes_APU_Memory* mem = &core_mem->apu; | |||
| for (int i = 0; i < core_mem->apu.n_events; ++i) { | |||
| int reg = core_mem->apu.events[i].reg; | |||
| int val = core_mem->apu.events[i].val; | |||
| LOGD("$40%02X < %02X", reg, val); | |||
| switch (reg) { | |||
| // TODO: 0x00 - 0x13 | |||
| // TODO: 0x15 | |||
| case 0x17: // Frame Counter | |||
| apu->frame_reg = val & ( apu_Frame_Mode | | |||
| apu_Frame_Inhibit); | |||
| (void)val; | |||
| #if 0 | |||
| val &= (apu_Frame_Mode | apu_Frame_Inhibit); | |||
| apu->frame_reg = val; | |||
| apu->frame = 0; | |||
| apu->frame_delay = 0; | |||
| apu->frame_delay = nes_apu_quarter_frame_ticks; | |||
| if (val & apu_Frame_Mode) { | |||
| // TODO | |||
| @@ -32,9 +42,8 @@ int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||
| if (val & apu_Frame_Inhibit) { | |||
| mem->status &= ~apu_Status_Frame_Int; | |||
| } | |||
| #endif | |||
| break; | |||
| } | |||
| } | |||
| @@ -42,10 +51,11 @@ int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||
| // Frame advance | |||
| apu->frame_delay += nes_apu_hsync_ticks; | |||
| apu->frame_delay -= ticks; | |||
| if (apu->frame_delay >= nes_apu_quarter_frame_ticks) { | |||
| apu->frame_delay -= nes_apu_quarter_frame_ticks; | |||
| if (apu->frame_delay <= 0) { | |||
| LOGD("Quarter Frame (%d overshoot)", -apu->frame_delay); | |||
| apu->frame_delay += nes_apu_quarter_frame_ticks; | |||
| int end = 0; | |||
| int quarter_frame = 1; | |||
| @@ -80,8 +90,10 @@ int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||
| } | |||
| if (end) { | |||
| LOGD("Frame sequence end (frame_reg %02X)", apu->frame_reg); | |||
| if (0 == apu->frame_reg) { | |||
| mem->status |= apu_Status_Frame_Int; | |||
| // ret = 1; | |||
| } | |||
| apu->frame = 0; | |||
| } else { | |||
| @@ -92,4 +104,17 @@ int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||
| // TODO: DMC Interrupt | |||
| return (mem->status & apu_Status_Frame_Int); | |||
| // return ret; | |||
| } | |||
| int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||
| int ticks = nes_apu_hsync_ticks - apu->ticks_handled; | |||
| apu->ticks_handled = 0; | |||
| return nes_apu_run(apu, core_mem, ticks); | |||
| } | |||
| int nes_apu_finish_qframe(nes_APU* apu, nes_Memory* core_mem) { | |||
| LOGD("Finishing Quarter Frame (%d remain)", apu->frame_delay); | |||
| apu->ticks_handled = apu->frame_delay; | |||
| return nes_apu_run(apu, core_mem, apu->frame_delay); | |||
| } | |||
| @@ -4,8 +4,9 @@ | |||
| #include <stdint.h> | |||
| #define nes_apu_hsync_ticks (4U) | |||
| #define nes_apu_quarter_frame_ticks (262U) | |||
| #define nes_ppu_to_apu_tick_ratio (4U) | |||
| #define nes_apu_hsync_ticks (341U * 4U) | |||
| #define nes_apu_quarter_frame_ticks (341U * 262U) | |||
| typedef struct { | |||
| @@ -90,6 +91,7 @@ typedef struct { | |||
| nes_apu_Frame frame_reg; | |||
| int frame; | |||
| int frame_delay; | |||
| int ticks_handled; | |||
| uint8_t reg[20]; | |||
| @@ -100,6 +102,7 @@ struct nes_Memory; | |||
| void nes_apu_init(nes_APU* apu, nes_APU_Memory* mem, int freq); | |||
| int nes_apu_hsync(nes_APU*, struct nes_Memory*); | |||
| int nes_apu_finish_qframe(nes_APU*, struct nes_Memory*); | |||
| #endif // NESE_APU_H_ | |||
| @@ -9,12 +9,12 @@ | |||
| #include <stdio.h> | |||
| #endif | |||
| #ifdef F6502_TEST | |||
| #include "nes.h" | |||
| #include <stddef.h> | |||
| #define container_of(ptr, type, member) ({ \ | |||
| const typeof( ((type *)0)->member ) *__mptr = (ptr); \ | |||
| (type *)((char *)__mptr - offsetof(type,member));}) | |||
| #endif | |||
| //#define NESE_DEBUG "Core" | |||
| #include "log.h" | |||
| @@ -98,8 +98,12 @@ static inline uint8_t f6502_read(nes_Memory* mem, | |||
| case 0x4000: | |||
| switch (addr & 0x1FU) { | |||
| case 0x15: /* APU Status */ | |||
| { uint8_t ret = mem->apu.status; | |||
| { f6502_Core* core = container_of(mem, f6502_Core, memory); | |||
| uint8_t ret = mem->apu.status; | |||
| mem->apu.status &= ~apu_Status_Frame_Int; | |||
| f6502_set_IRQ(core, 0); | |||
| return ret; | |||
| } | |||
| @@ -264,12 +268,22 @@ static inline int f6502_write(nes_Memory* mem, | |||
| case 0x4000: | |||
| switch (addr & 0x1FU) { | |||
| case 0x17: // APU Frame | |||
| // TODO: Clear interrupt here? | |||
| /* | |||
| if (val & apu_Frame_apu_Frame_Inhibit) { | |||
| f6502_Int_IRQ(core, 0); | |||
| { f6502_Core* core = container_of(mem, f6502_Core, memory); | |||
| nes* sys = container_of(core, nes, core); | |||
| nes_APU* apu = &sys->apu; | |||
| // printf("APU: $%04X < %02X\n", addr, val); | |||
| apu->frame_reg = val; | |||
| apu->frame = 0; | |||
| apu->frame_delay = nes_apu_quarter_frame_ticks; | |||
| // Clear this ASAP | |||
| if (val & apu_Frame_Inhibit) { | |||
| mem->apu.status &= ~apu_Status_Frame_Int; | |||
| f6502_set_IRQ(core, 0); | |||
| } | |||
| */ | |||
| } | |||
| case 0x00: | |||
| case 0x01: | |||
| case 0x02: | |||
| @@ -437,8 +451,10 @@ static inline bool f6502_irq_ready(const f6502_Core* core) { | |||
| static inline int f6502_check_interrupts(f6502_Core* core) { | |||
| if (f6502_nmi_ready(core)) { | |||
| core->interrupts |= f6502_Int_NMI_Serviced; | |||
| LOGI("%lu: NMI", core->clocks); | |||
| return f6502_interrupt(core, f6502_Vector_NMI); | |||
| } else if (f6502_irq_ready(core)) { | |||
| LOGI("%lu: IRQ", core->clocks); | |||
| return f6502_interrupt(core, f6502_Vector_IRQ); | |||
| } | |||
| return 0; | |||
| @@ -1704,6 +1720,8 @@ int f6502_step(f6502_Core* core, int clocks) { | |||
| } | |||
| */ | |||
| clocks_elapsed += f6502_check_interrupts(core); | |||
| return ( f6502_do_step(core, clocks - clocks_elapsed) + | |||
| clocks_elapsed); | |||
| clocks_elapsed += f6502_do_step(core, | |||
| clocks - clocks_elapsed); | |||
| core->clocks += clocks_elapsed; | |||
| return clocks_elapsed; | |||
| } | |||
| @@ -41,6 +41,7 @@ typedef enum { | |||
| } f6502_Interrupt; | |||
| struct f6502_Core { | |||
| uint64_t clocks; | |||
| f6502_Registers registers; | |||
| f6502_Interrupt interrupts; | |||
| nes_Memory memory; | |||
| @@ -3,7 +3,7 @@ | |||
| #include "nes.h" | |||
| #include "port.h" | |||
| //#define NESE_DEBUG "NES" | |||
| #define NESE_DEBUG "NES" | |||
| #include "log.h" | |||
| @@ -88,7 +88,6 @@ static int nes_hsync(nes* sys, void* plat) { | |||
| // Emulate the happy part of the backdrop override quirk | |||
| int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? | |||
| (mem->addr & 0x1FU) : 0; | |||
| LOGD("Background: %d", pal_idx); | |||
| // Don't use the rendering palette (masked transparency) | |||
| status = nese_frame_start(plat, mem->pal_bank[0x300 + pal_idx] & 0x3FU); | |||
| @@ -117,7 +116,6 @@ int nes_loop(nes* sys, void* plat) { | |||
| bool vbl_clear = false; | |||
| while (0 == status) { | |||
| // TODO: Move to inline PPU function? | |||
| int target_dot = nes_ppu_scanline_dots; | |||
| if ( nes_ppu_frame_end_line - 1 == sys->ppu.scanline && | |||
| @@ -148,6 +146,26 @@ int nes_loop(nes* sys, void* plat) { | |||
| } | |||
| int dots_to_run = target_dot - dot; | |||
| // Will APU quarter frame hit this line? | |||
| // TODO: Only do this when the IRQ is pending? | |||
| if (sys->apu.frame_delay <= nes_apu_hsync_ticks) { | |||
| int qframe_dot = ( | |||
| ( sys->apu.frame_delay + | |||
| (nes_ppu_to_apu_tick_ratio - 1)) / | |||
| nes_ppu_to_apu_tick_ratio | |||
| ); | |||
| if (dot >= qframe_dot) { | |||
| int irq = nes_apu_finish_qframe( | |||
| &sys->apu, &sys->core.memory | |||
| ); | |||
| // TODO: How might this interfere with MMC3? | |||
| f6502_set_IRQ(&sys->core, irq); | |||
| } else { | |||
| dots_to_run = qframe_dot - dot; | |||
| } | |||
| } | |||
| if (dots_to_run > 0) { | |||
| int cpu_cycles = (dots_to_run + 2) / 3; | |||
| dot += 3 * f6502_step(&sys->core, cpu_cycles); | |||