| @@ -29,7 +29,7 @@ MAPDIR = $(SRCDIR)/map | |||||
| NESE_SRC_SRCS = f6502.c f6502_opcodes.c | NESE_SRC_SRCS = f6502.c f6502_opcodes.c | ||||
| NESE_SRC_SRCS += nese.c nes.c cart.c mapper.c | NESE_SRC_SRCS += nese.c nes.c cart.c mapper.c | ||||
| NESE_SRC_SRCS += ppu.c | |||||
| NESE_SRC_SRCS += ppu.c apu.c | |||||
| NESE_SRC_SRCS += $(OS)/port.c | NESE_SRC_SRCS += $(OS)/port.c | ||||
| NESE_MAP_SRCS = $(notdir $(wildcard $(MAPDIR)/*.c)) | NESE_MAP_SRCS = $(notdir $(wildcard $(MAPDIR)/*.c)) | ||||
| @@ -0,0 +1,91 @@ | |||||
| #include "memory.h" | |||||
| void nes_apu_init(nes_APU* apu, nes_APU_Memory* mem, int freq) { | |||||
| // TODO | |||||
| } | |||||
| void nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { | |||||
| 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; | |||||
| switch (reg) { | |||||
| // TODO: 0x00 - 0x13 | |||||
| // TODO: 0x15 | |||||
| case 0x17: // Frame Counter | |||||
| apu->frame_reg = val & ( apu_Frame_Mode | | |||||
| apu_Frame_Inhibit); | |||||
| apu->frame = 0; | |||||
| apu->frame_delay = 0; | |||||
| if (val & apu_Frame_Mode) { | |||||
| // TODO | |||||
| /* | |||||
| nes_apu_clock_quarter_frame(apu); | |||||
| nes_apu_clock_half_frame(apu); | |||||
| */ | |||||
| } | |||||
| if (val & apu_Frame_Inhibit) { | |||||
| mem->status &= ~apu_Status_Frame_Int; | |||||
| } | |||||
| break; | |||||
| } | |||||
| } | |||||
| core_mem->apu.n_events = 0; | |||||
| // Frame advance | |||||
| apu->frame_delay += nes_apu_hsync_ticks; | |||||
| if (apu->frame_delay >= nes_apu_quarter_frame_ticks) { | |||||
| apu->frame_delay -= nes_apu_quarter_frame_ticks; | |||||
| int end = 0; | |||||
| int quarter_frame = 1; | |||||
| int half_frame = 0; | |||||
| if (1 == apu->frame) { | |||||
| half_frame = 1; | |||||
| } | |||||
| if (apu->frame_reg & apu_Frame_Mode) { | |||||
| if (3 == apu->frame) { | |||||
| quarter_frame = 0; | |||||
| } else if (4 <= apu->frame) { | |||||
| half_frame = 1; | |||||
| end = 1; | |||||
| } | |||||
| } else { | |||||
| if (3 <= apu->frame) { | |||||
| half_frame = 1; | |||||
| end = 1; | |||||
| } | |||||
| } | |||||
| if (half_frame) { | |||||
| // TODO | |||||
| //nes_apu_clock_half_frame(apu); | |||||
| } | |||||
| if (quarter_frame) { | |||||
| // TODO | |||||
| //nes_apu_clock_quarter_frame(apu); | |||||
| } | |||||
| if (end) { | |||||
| if (0 == apu->frame_reg) { | |||||
| mem->status |= apu_Status_Frame_Int; | |||||
| } | |||||
| apu->frame = 0; | |||||
| } else { | |||||
| apu->frame++; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,105 @@ | |||||
| #ifndef NESE_APU_H_ | |||||
| #define NESE_APU_H_ | |||||
| #include <stdint.h> | |||||
| #define nes_apu_hsync_ticks (4U) | |||||
| #define nes_apu_quarter_frame_ticks (262U) | |||||
| typedef struct { | |||||
| uint8_t reg; | |||||
| uint8_t val; | |||||
| } nes_APU_Event; | |||||
| #define APU_MAX_EVENTS (64U) | |||||
| typedef enum __attribute__ ((__packed__)) { | |||||
| apu_Status_Square_0 = 0b00000001, | |||||
| apu_Status_Square_1 = 0b00000010, | |||||
| apu_Status_Triangle = 0b00000100, | |||||
| apu_Status_Noise = 0b00001000, | |||||
| apu_Status_DMC = 0b00010000, | |||||
| apu_Status_Frame_Int = 0b01000000, | |||||
| apu_Status_DMC_Int = 0b10000000, | |||||
| } nes_apu_Status; | |||||
| typedef struct { | |||||
| nes_APU_Event events[APU_MAX_EVENTS]; | |||||
| uint8_t n_events; | |||||
| nes_apu_Status status; | |||||
| } nes_APU_Memory; | |||||
| typedef enum __attribute__ ((__packed__)) { | |||||
| apu_Frame_Inhibit = 0b01000000, | |||||
| apu_Frame_Mode = 0b10000000, | |||||
| } nes_apu_Frame; | |||||
| typedef enum { | |||||
| apu_Channel_Square_0 = 0, | |||||
| apu_Channel_Square_1 = 1, | |||||
| apu_Channel_Triangle = 2, | |||||
| apu_Channel_Noise = 3, | |||||
| apu_Channel_DMC = 4, | |||||
| } nes_apu_Channel_Index; | |||||
| // Applies to Square and Noise | |||||
| typedef enum { | |||||
| apu_Envelope_Volume = 0b00001111, | |||||
| apu_Envelope_Constant = 0b00010000, | |||||
| apu_Envelope_Halt = 0b00100000, | |||||
| apu_Envelope_Duty = 0b11000000, | |||||
| } nes_apu_Envelope_Reg_0_Mask; | |||||
| typedef enum { | |||||
| apu_Square_Shift = 0b00000111, | |||||
| apu_Square_Negate = 0b00001000, | |||||
| apu_Square_Period = 0b01110000, | |||||
| apu_Square_Enable = 0b10000000, | |||||
| } nes_apu_Square_Reg_1_Mask; | |||||
| typedef enum { | |||||
| apu_Triangle_Count = 0b01111111, | |||||
| apu_Triangle_Halt = 0b10000000, | |||||
| } nes_apu_Triangle_Reg_0_Mask; | |||||
| typedef enum { | |||||
| apu_Noise_Period = 0b00001111, | |||||
| apu_Noise_Mode = 0b10000000, | |||||
| } nes_apu_Noise_Reg_2_Mask; | |||||
| typedef enum { | |||||
| apu_Noise_Length = 0b11111000, | |||||
| } nes_apu_Noise_Reg_3_Mask; | |||||
| typedef enum { | |||||
| apu_DMC_Period = 0b00001111, | |||||
| apu_DMC_Loop = 0b01000000, | |||||
| apu_DMC_IRQ_Enable = 0b10000000, | |||||
| } nes_apu_DMC_Reg_0_Mask; | |||||
| typedef enum { | |||||
| apu_Channel_Reload = 0b00000001, | |||||
| apu_Channel_Start = 0b00000010, | |||||
| } nes_apu_Channel_Flag; | |||||
| typedef struct { | |||||
| nes_apu_Frame frame_reg; | |||||
| int frame; | |||||
| int frame_delay; | |||||
| uint8_t reg[20]; | |||||
| // TODO: Channel data | |||||
| } nes_APU; | |||||
| struct nes_Memory; | |||||
| void nes_apu_init(nes_APU* apu, nes_APU_Memory* mem, int freq); | |||||
| void nes_apu_hsync(nes_APU*, struct nes_Memory*); | |||||
| #endif // NESE_APU_H_ | |||||
| @@ -90,15 +90,21 @@ static inline uint8_t f6502_read(nes_Memory* mem, | |||||
| case 0x4000: | case 0x4000: | ||||
| switch (addr & 0x1FU) { | switch (addr & 0x1FU) { | ||||
| case 0x15: | |||||
| return mem->apu.status; | |||||
| case 0x16: | case 0x16: | ||||
| case 0x17: | case 0x17: | ||||
| { nes_Gamepad* gamepad = &mem->input.gamepads[addr & 1]; | |||||
| return ((addr >> 8) & nes_controller_bus_mask) | | return ((addr >> 8) & nes_controller_bus_mask) | | ||||
| read_gamepad_bit(gamepad); | |||||
| } break; | |||||
| read_gamepad_bit( | |||||
| &mem->input.gamepads[addr & 1] | |||||
| ); | |||||
| default: | default: | ||||
| // TODO: APU Reg | |||||
| if (mem->mapper.read_apu) { | |||||
| return mem->mapper.read_apu(&mem->mapper, mem, | |||||
| addr & 0x1FU); | |||||
| } | |||||
| } | } | ||||
| break; | break; | ||||
| @@ -246,8 +252,40 @@ static inline int f6502_write(nes_Memory* mem, | |||||
| break; | break; | ||||
| case 0x4000: | case 0x4000: | ||||
| // TODO: APU | |||||
| switch (addr & 0x1FU) { | switch (addr & 0x1FU) { | ||||
| case 0x00: | |||||
| case 0x01: | |||||
| case 0x02: | |||||
| case 0x03: | |||||
| case 0x04: | |||||
| case 0x05: | |||||
| case 0x06: | |||||
| case 0x07: | |||||
| case 0x08: | |||||
| case 0x09: | |||||
| case 0x0A: | |||||
| case 0x0B: | |||||
| case 0x0C: | |||||
| case 0x0D: | |||||
| case 0x0E: | |||||
| case 0x0F: | |||||
| case 0x10: | |||||
| case 0x11: | |||||
| case 0x12: | |||||
| case 0x13: | |||||
| case 0x15: | |||||
| case 0x17: | |||||
| if (mem->apu.n_events >= APU_MAX_EVENTS) { | |||||
| LOGE("APU event buffer full"); | |||||
| } else { | |||||
| mem->apu.events[mem->apu.n_events++] = | |||||
| (nes_APU_Event){ | |||||
| .reg = addr & 0x1FU, | |||||
| .val = val, | |||||
| }; | |||||
| } | |||||
| break; | |||||
| case 0x14: // OAM DMA | case 0x14: // OAM DMA | ||||
| { uint8_t* src = NULL; | { uint8_t* src = NULL; | ||||
| // OAM DMA | // OAM DMA | ||||
| @@ -285,6 +323,12 @@ static inline int f6502_write(nes_Memory* mem, | |||||
| mem->input.gamepads[1].shift = 0; | mem->input.gamepads[1].shift = 0; | ||||
| } | } | ||||
| break; | break; | ||||
| default: | |||||
| if (mem->mapper.write_apu) { | |||||
| ret = mem->mapper.write_apu(&mem->mapper, mem, | |||||
| addr, val); | |||||
| } | |||||
| } | } | ||||
| break; | break; | ||||
| @@ -15,7 +15,8 @@ | |||||
| #include "log.h" | #include "log.h" | ||||
| /* OS-specific file operations | |||||
| /* | |||||
| * OS-specific file operations | |||||
| * Memory mapping specifically needs to be ported for each OS | * Memory mapping specifically needs to be ported for each OS | ||||
| */ | */ | ||||
| @@ -42,7 +43,8 @@ static int nese_file_size(FILE* file) { | |||||
| } | } | ||||
| /* Platform-specific allocation | |||||
| /* | |||||
| * Platform-specific allocation | |||||
| * GPU and CPU refer to emulator memory spaces | * GPU and CPU refer to emulator memory spaces | ||||
| * Note: GPU (and possibly CPU) regions may need | * Note: GPU (and possibly CPU) regions may need | ||||
| * to be placed into internal ram for performance | * to be placed into internal ram for performance | ||||
| @@ -61,7 +63,8 @@ void* nese_alloc(int size) { | |||||
| } | } | ||||
| /* SDL-specific init and entry point | |||||
| /* | |||||
| * SDL-specific init and entry point | |||||
| * This should be maximally reusable across platforms | * This should be maximally reusable across platforms | ||||
| */ | */ | ||||
| @@ -84,6 +87,14 @@ typedef struct { | |||||
| } platform_data; | } platform_data; | ||||
| /* Input */ | |||||
| int nese_update_input(void* plat_data, nes_Input* input) { | |||||
| // Gamepad states are already updated in nese_frame_ready() | |||||
| return 0; | |||||
| } | |||||
| static const int sdl_alt_start_key = SDLK_RETURN; | static const int sdl_alt_start_key = SDLK_RETURN; | ||||
| static const int sdl_keycodes[nes_controller_num_buttons] = { | static const int sdl_keycodes[nes_controller_num_buttons] = { | ||||
| @@ -138,6 +149,9 @@ static int process_events(nes* sys) { | |||||
| return status; | return status; | ||||
| } | } | ||||
| /* Time / Video */ | |||||
| static SDL_Color nes_palette[64] = { | static SDL_Color nes_palette[64] = { | ||||
| {0x80,0x80,0x80}, {0x00,0x00,0xBB}, {0x37,0x00,0xBF}, {0x84,0x00,0xA6}, | {0x80,0x80,0x80}, {0x00,0x00,0xBB}, {0x37,0x00,0xBF}, {0x84,0x00,0xA6}, | ||||
| {0xBB,0x00,0x6A}, {0xB7,0x00,0x1E}, {0xB3,0x00,0x00}, {0x91,0x26,0x00}, | {0xBB,0x00,0x6A}, {0xB7,0x00,0x1E}, {0xB3,0x00,0x00}, {0x91,0x26,0x00}, | ||||
| @@ -278,11 +292,18 @@ int nese_frame_ready(void* plat_data) { | |||||
| return status; | return status; | ||||
| } | } | ||||
| int nese_update_input(void* plat_data, nes_Input* input) { | |||||
| // Gamepad states are already updated in nese_frame_ready() | |||||
| return 0; | |||||
| /* Audio */ | |||||
| int nese_get_audio_frequency(void*) { | |||||
| return 44100; | |||||
| } | } | ||||
| // TODO: Audio functions | |||||
| /* Platform Data */ | |||||
| static int plat_init(platform_data* plat) { | static int plat_init(platform_data* plat) { | ||||
| int status = SDL_Init( | int status = SDL_Init( | ||||
| SDL_INIT_EVENTS | | SDL_INIT_EVENTS | | ||||
| @@ -3,6 +3,7 @@ | |||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include "apu.h" | |||||
| #include "input.h" | #include "input.h" | ||||
| #include "mapper.h" | #include "mapper.h" | ||||
| #include "ppu.h" | #include "ppu.h" | ||||
| @@ -33,6 +34,7 @@ struct nes_Memory { | |||||
| nes_PPU_Memory ppu; | nes_PPU_Memory ppu; | ||||
| nes_Mapper mapper; | nes_Mapper mapper; | ||||
| nes_Input input; | nes_Input input; | ||||
| nes_APU_Memory apu; | |||||
| #endif | #endif | ||||
| }; | }; | ||||
| typedef struct nes_Memory nes_Memory; | typedef struct nes_Memory nes_Memory; | ||||
| @@ -7,10 +7,11 @@ | |||||
| #include "log.h" | #include "log.h" | ||||
| void nes_init(nes* sys) { | |||||
| void nes_init(nes* sys, void* plat) { | |||||
| f6502_init(&sys->core); | f6502_init(&sys->core); | ||||
| nes_ppu_init(&sys->ppu, &sys->core.memory.ppu); | nes_ppu_init(&sys->ppu, &sys->core.memory.ppu); | ||||
| // TODO: Init APU | |||||
| nes_apu_init(&sys->apu, &sys->core.memory.apu, | |||||
| nese_get_audio_frequency(plat)); | |||||
| } | } | ||||
| void nes_reset(nes* sys) { | void nes_reset(nes* sys) { | ||||
| @@ -47,7 +48,7 @@ static int nes_vsync(nes* sys, void* plat) { | |||||
| static int nes_hsync(nes* sys, void* plat) { | static int nes_hsync(nes* sys, void* plat) { | ||||
| int status = 0; | int status = 0; | ||||
| // TODO: APU sync | |||||
| nes_apu_hsync(&sys->apu, &sys->core.memory); | |||||
| if (sys->ppu.scanline < nes_ppu_postrender_line) { | if (sys->ppu.scanline < nes_ppu_postrender_line) { | ||||
| if (sys->ppu.scanline < nes_ppu_visible_line) { | if (sys->ppu.scanline < nes_ppu_visible_line) { | ||||
| @@ -11,10 +11,10 @@ typedef struct { | |||||
| const ines_Header* cart_header; | const ines_Header* cart_header; | ||||
| f6502_Core core; | f6502_Core core; | ||||
| nes_PPU ppu; | nes_PPU ppu; | ||||
| // TODO: APU | |||||
| nes_APU apu; | |||||
| } nes; | } nes; | ||||
| void nes_init(nes*); | |||||
| void nes_init(nes*, void*); | |||||
| void nes_reset(nes*); | void nes_reset(nes*); | ||||
| void nes_done(nes*); | void nes_done(nes*); | ||||
| int nes_loop(nes*, void*); | int nes_loop(nes*, void*); | ||||
| @@ -6,7 +6,7 @@ | |||||
| int nese_start(nes* sys, void* plat) { | int nese_start(nes* sys, void* plat) { | ||||
| nes_init(sys); | |||||
| nes_init(sys, plat); | |||||
| nes_reset(sys); | nes_reset(sys); | ||||
| @@ -10,6 +10,7 @@ int nese_frame_start(void*, uint8_t background); | |||||
| int nese_line_ready(void*, uint8_t* buffer, int line); | int nese_line_ready(void*, uint8_t* buffer, int line); | ||||
| int nese_frame_ready(void*); | int nese_frame_ready(void*); | ||||
| int nese_update_input(void*, nes_Input*); | int nese_update_input(void*, nes_Input*); | ||||
| int nese_get_audio_frequency(void*); | |||||
| void* nese_alloc_gpu(int); | void* nese_alloc_gpu(int); | ||||
| void* nese_alloc_cpu(int); | void* nese_alloc_cpu(int); | ||||