| @@ -1,25 +1,35 @@ | |||||
| #include "memory.h" | #include "memory.h" | ||||
| //#define NESE_DEBUG "APU" | |||||
| #include "log.h" | |||||
| void nes_apu_init(nes_APU* apu, nes_APU_Memory* mem, int freq) { | 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; | nes_APU_Memory* mem = &core_mem->apu; | ||||
| for (int i = 0; i < core_mem->apu.n_events; ++i) { | for (int i = 0; i < core_mem->apu.n_events; ++i) { | ||||
| int reg = core_mem->apu.events[i].reg; | int reg = core_mem->apu.events[i].reg; | ||||
| int val = core_mem->apu.events[i].val; | int val = core_mem->apu.events[i].val; | ||||
| LOGD("$40%02X < %02X", reg, val); | |||||
| switch (reg) { | switch (reg) { | ||||
| // TODO: 0x00 - 0x13 | // TODO: 0x00 - 0x13 | ||||
| // TODO: 0x15 | // TODO: 0x15 | ||||
| case 0x17: // Frame Counter | 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 = 0; | ||||
| apu->frame_delay = 0; | |||||
| apu->frame_delay = nes_apu_quarter_frame_ticks; | |||||
| if (val & apu_Frame_Mode) { | if (val & apu_Frame_Mode) { | ||||
| // TODO | // TODO | ||||
| @@ -32,9 +42,8 @@ int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||||
| if (val & apu_Frame_Inhibit) { | if (val & apu_Frame_Inhibit) { | ||||
| mem->status &= ~apu_Status_Frame_Int; | mem->status &= ~apu_Status_Frame_Int; | ||||
| } | } | ||||
| #endif | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -42,10 +51,11 @@ int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||||
| // Frame advance | // 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 end = 0; | ||||
| int quarter_frame = 1; | int quarter_frame = 1; | ||||
| @@ -80,8 +90,10 @@ int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||||
| } | } | ||||
| if (end) { | if (end) { | ||||
| LOGD("Frame sequence end (frame_reg %02X)", apu->frame_reg); | |||||
| if (0 == apu->frame_reg) { | if (0 == apu->frame_reg) { | ||||
| mem->status |= apu_Status_Frame_Int; | mem->status |= apu_Status_Frame_Int; | ||||
| // ret = 1; | |||||
| } | } | ||||
| apu->frame = 0; | apu->frame = 0; | ||||
| } else { | } else { | ||||
| @@ -92,4 +104,17 @@ int nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||||
| // TODO: DMC Interrupt | // TODO: DMC Interrupt | ||||
| return (mem->status & apu_Status_Frame_Int); | 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> | #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 { | typedef struct { | ||||
| @@ -90,6 +91,7 @@ typedef struct { | |||||
| nes_apu_Frame frame_reg; | nes_apu_Frame frame_reg; | ||||
| int frame; | int frame; | ||||
| int frame_delay; | int frame_delay; | ||||
| int ticks_handled; | |||||
| uint8_t reg[20]; | uint8_t reg[20]; | ||||
| @@ -100,6 +102,7 @@ struct nes_Memory; | |||||
| void nes_apu_init(nes_APU* apu, nes_APU_Memory* mem, int freq); | 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_hsync(nes_APU*, struct nes_Memory*); | ||||
| int nes_apu_finish_qframe(nes_APU*, struct nes_Memory*); | |||||
| #endif // NESE_APU_H_ | #endif // NESE_APU_H_ | ||||
| @@ -9,12 +9,12 @@ | |||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #endif | #endif | ||||
| #ifdef F6502_TEST | |||||
| #include "nes.h" | |||||
| #include <stddef.h> | #include <stddef.h> | ||||
| #define container_of(ptr, type, member) ({ \ | #define container_of(ptr, type, member) ({ \ | ||||
| const typeof( ((type *)0)->member ) *__mptr = (ptr); \ | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ | ||||
| (type *)((char *)__mptr - offsetof(type,member));}) | (type *)((char *)__mptr - offsetof(type,member));}) | ||||
| #endif | |||||
| //#define NESE_DEBUG "Core" | //#define NESE_DEBUG "Core" | ||||
| #include "log.h" | #include "log.h" | ||||
| @@ -98,8 +98,12 @@ static inline uint8_t f6502_read(nes_Memory* mem, | |||||
| case 0x4000: | case 0x4000: | ||||
| switch (addr & 0x1FU) { | switch (addr & 0x1FU) { | ||||
| case 0x15: /* APU Status */ | 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; | mem->apu.status &= ~apu_Status_Frame_Int; | ||||
| f6502_set_IRQ(core, 0); | |||||
| return ret; | return ret; | ||||
| } | } | ||||
| @@ -264,12 +268,22 @@ static inline int f6502_write(nes_Memory* mem, | |||||
| case 0x4000: | case 0x4000: | ||||
| switch (addr & 0x1FU) { | switch (addr & 0x1FU) { | ||||
| case 0x17: // APU Frame | 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 0x00: | ||||
| case 0x01: | case 0x01: | ||||
| case 0x02: | 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) { | static inline int f6502_check_interrupts(f6502_Core* core) { | ||||
| if (f6502_nmi_ready(core)) { | if (f6502_nmi_ready(core)) { | ||||
| core->interrupts |= f6502_Int_NMI_Serviced; | core->interrupts |= f6502_Int_NMI_Serviced; | ||||
| LOGI("%lu: NMI", core->clocks); | |||||
| return f6502_interrupt(core, f6502_Vector_NMI); | return f6502_interrupt(core, f6502_Vector_NMI); | ||||
| } else if (f6502_irq_ready(core)) { | } else if (f6502_irq_ready(core)) { | ||||
| LOGI("%lu: IRQ", core->clocks); | |||||
| return f6502_interrupt(core, f6502_Vector_IRQ); | return f6502_interrupt(core, f6502_Vector_IRQ); | ||||
| } | } | ||||
| return 0; | return 0; | ||||
| @@ -1704,6 +1720,8 @@ int f6502_step(f6502_Core* core, int clocks) { | |||||
| } | } | ||||
| */ | */ | ||||
| clocks_elapsed += f6502_check_interrupts(core); | 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; | } f6502_Interrupt; | ||||
| struct f6502_Core { | struct f6502_Core { | ||||
| uint64_t clocks; | |||||
| f6502_Registers registers; | f6502_Registers registers; | ||||
| f6502_Interrupt interrupts; | f6502_Interrupt interrupts; | ||||
| nes_Memory memory; | nes_Memory memory; | ||||
| @@ -3,7 +3,7 @@ | |||||
| #include "nes.h" | #include "nes.h" | ||||
| #include "port.h" | #include "port.h" | ||||
| //#define NESE_DEBUG "NES" | |||||
| #define NESE_DEBUG "NES" | |||||
| #include "log.h" | #include "log.h" | ||||
| @@ -88,7 +88,6 @@ static int nes_hsync(nes* sys, void* plat) { | |||||
| // Emulate the happy part of the backdrop override quirk | // Emulate the happy part of the backdrop override quirk | ||||
| int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? | int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? | ||||
| (mem->addr & 0x1FU) : 0; | (mem->addr & 0x1FU) : 0; | ||||
| LOGD("Background: %d", pal_idx); | |||||
| // Don't use the rendering palette (masked transparency) | // 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_bank[0x300 + pal_idx] & 0x3FU); | ||||
| @@ -117,7 +116,6 @@ int nes_loop(nes* sys, void* plat) { | |||||
| bool vbl_clear = false; | bool vbl_clear = false; | ||||
| while (0 == status) { | while (0 == status) { | ||||
| // TODO: Move to inline PPU function? | |||||
| int target_dot = nes_ppu_scanline_dots; | int target_dot = nes_ppu_scanline_dots; | ||||
| if ( nes_ppu_frame_end_line - 1 == sys->ppu.scanline && | 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; | 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) { | if (dots_to_run > 0) { | ||||
| int cpu_cycles = (dots_to_run + 2) / 3; | int cpu_cycles = (dots_to_run + 2) / 3; | ||||
| dot += 3 * f6502_step(&sys->core, cpu_cycles); | dot += 3 * f6502_step(&sys->core, cpu_cycles); | ||||