diff options
| author | Peter Fors <peter.fors@mindkiller.com> | 2025-06-04 15:26:11 +0200 |
|---|---|---|
| committer | Peter Fors <peter.fors@mindkiller.com> | 2025-06-04 15:26:11 +0200 |
| commit | 6dd73982c514445c4d2a4787c37666d0812a3dad (patch) | |
| tree | 78f4e43890a9f7aeb6460066bcfe117e1f96cdd8 | |
| parent | f02853edb8a624fbe85ea322eaba9e643756d44e (diff) | |
added scaffolding for apu
| -rw-r--r-- | apu.c | 164 | ||||
| -rw-r--r-- | memory.c | 58 | ||||
| -rw-r--r-- | mknes.c | 6 | ||||
| -rw-r--r-- | mknes.h | 15 | ||||
| -rw-r--r-- | ppu_registers.c | 6 |
5 files changed, 226 insertions, 23 deletions
@@ -0,0 +1,164 @@ + + +static inline void ppu_tick(struct nes_state *state); +static inline uint8_t memory_read(struct nes_state *state, uint32_t offset); + +// DMC frequency table (NTSC) +static const uint16_t dmc_rate_table[16] = { + 428, 380, 340, 320, 286, 254, 226, 214, + 190, 160, 142, 128, 106, 85, 72, 54 +}; + +// $4015 write +static void apu_write4015(struct nes_state *state, uint8_t val) { + struct apu_state *apu = &state->apu; + + if(val & 0x10) { + if(!apu->dmc_dma_enabled || apu->dmc_bytes_remaining == 0) { + apu->dmc_current_addr = 0xC000 + ((uint16_t)apu->dmc_sample_addr << 6); + apu->dmc_bytes_remaining = ((uint16_t)apu->dmc_sample_len << 4); + } + apu->dmc_dma_enabled = 1; + } else { + apu->dmc_dma_enabled = 0; + } +} + +// $4015 read +static uint8_t apu_read4015(struct nes_state *state) { + struct apu_state *apu = &state->apu; + uint8_t result = 0; + + if(apu->dmc_bytes_remaining > 0) { + result |= 0x10; + } + if(apu->irq_pending) { + result |= 0x40; + apu->irq_pending = 0; + } + return result; +} + +// $4010–$4013, $4015 write +static void apu_write(struct nes_state *state, uint16_t addr, uint8_t val) { + struct apu_state *apu = &state->apu; + + switch(addr) { + case 0x4010: { + apu->dmc_irq_enable = (val >> 7) & 1; + apu->dmc_loop_flag = (val >> 6) & 1; + apu->dmc_freq_index = val & 0x0F; + } break; + case 0x4011: { + // DAC write ignored + } break; + case 0x4012: { + apu->dmc_sample_addr = val; + } break; + case 0x4013: { + apu->dmc_sample_len = val; + } break; + case 0x4015: { + apu_write4015(state, val); + } break; + } +} + +// APU tick +static inline void apu_tick(struct nes_state *state) { + struct apu_state *apu = &state->apu; + + apu->frame_cycle++; + + if(apu->mode == 0) { + if(apu->frame_cycle == 7457) { + // Quarter frame + } else if(apu->frame_cycle == 14913) { + // Quarter frame + // Half frame + } else if(apu->frame_cycle == 22371) { + // Quarter frame + } else if(apu->frame_cycle == 29829) { + // Half frame + if(!apu->irq_inhibit) { + apu->irq_pending = 1; + } + } else if(apu->frame_cycle >= 29830) { + apu->frame_cycle = 0; + } + } else { + if(apu->frame_cycle == 7457) { + // Quarter frame + } else if(apu->frame_cycle == 14913) { + // Quarter frame + // Half frame + } else if(apu->frame_cycle == 22371) { + // Quarter frame + } else if(apu->frame_cycle == 29829) { + // Half frame + } else if(apu->frame_cycle == 37281) { + // Quarter frame + } else if(apu->frame_cycle >= 37282) { + apu->frame_cycle = 0; + } + } + + // if(apu->mode == 0) { + // if(apu->frame_cycle == 7457 || apu->frame_cycle == 14913 || apu->frame_cycle == 22371) { + // // Quarter frame + // } + // if(apu->frame_cycle == 14913 || apu->frame_cycle == 29829) { + // // Half frame + // } + // if(apu->frame_cycle == 29829 && !apu->irq_inhibit) { + // apu->irq_pending = 1; + // } + // if(apu->frame_cycle >= 29830) { + // apu->frame_cycle = 0; + // } + // } else { + // if(apu->frame_cycle == 7457 || apu->frame_cycle == 14913 || apu->frame_cycle == 22371 || apu->frame_cycle == 37281) { + // // Quarter frame + // } + // if(apu->frame_cycle == 14913 || apu->frame_cycle == 29829) { + // // Half frame + // } + // if(apu->frame_cycle >= 37282) { + // apu->frame_cycle = 0; + // } + // } + + if(apu->dmc_dma_enabled && apu->dmc_bytes_remaining > 0) { + apu->dmc_sample_timer++; + if(apu->dmc_sample_timer >= dmc_rate_table[apu->dmc_freq_index]) { + apu->dmc_sample_timer = 0; + + uint8_t val = memory_read(state, apu->dmc_current_addr); + (void)val; + + apu->dmc_current_addr++; + if(apu->dmc_current_addr == 0x0000) { + apu->dmc_current_addr = 0x8000; + } + + apu->dmc_bytes_remaining--; + + if(apu->dmc_bytes_remaining == 0) { + if(apu->dmc_loop_flag) { + apu->dmc_current_addr = 0xC000 + ((uint16_t)apu->dmc_sample_addr << 6); + apu->dmc_bytes_remaining = ((uint16_t)apu->dmc_sample_len << 4); + } else { + if(apu->dmc_irq_enable) { + apu->irq_pending = 1; + } + apu->dmc_dma_enabled = 0; + } + } + + for(uint32_t i = 0; i < 4; i++) { + state->cpu.cycles++; + ppu_tick(state); + } + } + } +} @@ -1,10 +1,46 @@ +#if 1 __attribute__((hot)) static inline uint8_t memory_read(struct nes_state *state, uint32_t offset) { state->cpu.cycles++; ppu_tick(state); - // apu_tick(state); + apu_tick(state); + + uint8_t result = 0; + + if(offset >= 0x8000) { // MOST + result = state->mapper_function.prg_rom_read(state, offset); + + } else if(offset < 0x2000) { // SECOND + result = state->ram[offset & 0x07ff]; + + } else if(offset < 0x4000) { // THIRD + result = ppu_read(state, offset); + + } else if(offset == 0x4015) { // APU status + result = apu_read4015(state); + + } else if(offset == 0x4016 || offset == 0x4017) { + uint32_t index = offset & 1; + uint8_t value = (state->ppu.input_latch[index] >> state->ppu.input_bit[index]) & 1; + state->ppu.input_bit[index]++; + result = value | 0x40; // Bit 6 open bus high, bit 7 low + + } else if(offset >= 0x6000) { + result = state->mapper_function.prg_ram_read(state, offset); + } + + return result; +} + +#else + +__attribute__((hot)) +static inline uint8_t memory_read(struct nes_state *state, uint32_t offset) { + state->cpu.cycles++; + ppu_tick(state); + apu_tick(state); uint8_t result = 0; @@ -29,12 +65,13 @@ static inline uint8_t memory_read(struct nes_state *state, uint32_t offset) { return result; } +#endif __attribute__((hot)) static inline uint8_t memory_read_dummy(struct nes_state *state, uint32_t offset) { state->cpu.cycles++; ppu_tick(state); - // apu_tick(state); + apu_tick(state); if(offset >= 0x2000 && offset < 0x4000) { return ppu_read(state, offset); @@ -46,7 +83,7 @@ __attribute__((hot)) static inline void memory_write(struct nes_state *state, uint32_t offset, uint8_t value) { state->cpu.cycles++; ppu_tick(state); - // apu_tick(state); + apu_tick(state); if(offset < 0x2000) { state->ram[offset & 0x07ff] = value; @@ -55,32 +92,23 @@ static inline void memory_write(struct nes_state *state, uint32_t offset, uint8_ ppu_write(state, offset, value); } else if(offset < 0x4018) { - // APU + joypad + DMA if(offset == 0x4014) { ppu_dma_4014(state, value); - // } else if(offset == 0x4015) { // DMC Enable (APU) - // state->apu.dmc_dma_enabled = (value & 0x10) ? 1 : 0; - } else if(offset == 0x4016) { + // joypad strobe uint8_t prev = state->ppu.input_strobe; state->ppu.input_strobe = value & 1; if(prev == 1 && (value & 1) == 0) { - // Latch current inputs state->ppu.input_latch[0] = state->ppu.input[0]; state->ppu.input_latch[1] = state->ppu.input[1]; state->ppu.input_bit[0] = 0; state->ppu.input_bit[1] = 0; } - // } else if(offset == 0x4017) { // Frame Counter (APU) - // state->apu.mode = (value >> 7) & 1; - // state->apu.irq_inhibit = (value >> 6) & 1; - // state->apu.frame_cycle = 0; - // if(state->apu.irq_inhibit) { - // state->apu.irq_pending = 0; - // } + } else if(offset >= 0x4010 && offset <= 0x4017) { + apu_write(state, offset, value); } } else if(offset >= 0x6000) { // NOTE(peter): Might need to move this upwards when we implement a mapper that has prg_ram_* @@ -92,7 +92,7 @@ static uint32_t frames; // debug information // NES core #include "mappers/mapper.h" #include "mknes.h" -// #include "apu.c" +#include "apu.c" #include "ppu.c" #include "ppu_registers.c" #include "memory.c" @@ -142,11 +142,11 @@ int main(int argc, char **argv) { // ines2_load(nstate, "data/0000/raster_demos/RasterTest3e.NES"); // ines2_load(nstate, "data/0000/NEStress.NES"); // ines2_load(nstate, "data/0000/Super Mario Bros. (World) (HVC-SM).zip"); - // ines2_load(nstate, "data/0042/Super Mario Bros. + Duck Hunt (USA).zip"); + ines2_load(nstate, "data/0042/Super Mario Bros. + Duck Hunt (USA).zip"); // ines2_load(nstate, "data/0000/Xevious - The Avenger (USA).zip"); // ines2_load(nstate, "data/tv.nes"); - ines2_load(nstate, "data/Life Force (USA).zip"); // 2002 + // ines2_load(nstate, "data/Life Force (USA).zip"); // 2002 // ines2_load(nstate, "data/0003/Flipull - An Exciting Cube Game (Japan) (En).zip"); // ines2_load(nstate, "data/0003/Friday the 13th (USA).zip"); @@ -68,11 +68,22 @@ struct ppu_state { struct apu_state { uint32_t frame_cycle; + uint8_t mode; uint8_t irq_inhibit; uint8_t irq_pending; + + uint8_t dmc_irq_enable; + uint8_t dmc_loop_flag; uint8_t dmc_dma_enabled; - uint32_t dmc_sample_timer; + + uint8_t dmc_freq_index; + uint8_t dmc_sample_addr; + uint8_t dmc_sample_len; + + uint16_t dmc_current_addr; + uint16_t dmc_bytes_remaining; + uint16_t dmc_sample_timer; } __attribute__((packed, aligned(64))); struct cpu_state { @@ -110,7 +121,7 @@ struct nes_state { union mapper_data mapper_data; struct cpu_state cpu; struct ines_state ines; - // struct apu_state apu; + struct apu_state apu; uint8_t ram[RAM_SIZE] __attribute__((aligned(4096))); uint8_t ciram[CIRAM_SIZE] __attribute__((aligned(4096))); diff --git a/ppu_registers.c b/ppu_registers.c index 6217908..8423774 100644 --- a/ppu_registers.c +++ b/ppu_registers.c @@ -128,7 +128,7 @@ static inline void ppu_dma_4014(struct nes_state *state, uint8_t page) { for(uint8_t i = 0; i < idle_cycles; i++) { state->cpu.cycles++; ppu_tick(state); - // apu_tick(state); + apu_tick(state); } for(uint32_t i = 0; i < 256; i++) { @@ -136,12 +136,12 @@ static inline void ppu_dma_4014(struct nes_state *state, uint8_t page) { state->cpu.cycles++; ppu_tick(state); - // apu_tick(state); + apu_tick(state); uint8_t value = memory_read_dma(state, addr); state->cpu.cycles++; ppu_tick(state); - // apu_tick(state); + apu_tick(state); ppu_write(state, 4, value); } |
