| @@ -7,6 +7,9 @@ | |||||
| #include "emulator.h" | #include "emulator.h" | ||||
| #include "logging.h" | #include "logging.h" | ||||
| #include "util.h" | |||||
| #define NS_PER_FRAME (1000 * 1000 * 1000 / 60) | |||||
| static volatile bool caught_signal; | static volatile bool caught_signal; | ||||
| @@ -29,18 +32,21 @@ void emulate(GameGear *gg) | |||||
| caught_signal = false; | caught_signal = false; | ||||
| signal(SIGINT, handle_sigint); | signal(SIGINT, handle_sigint); | ||||
| DEBUG("Interface powering GameGear") | |||||
| DEBUG("Emulator powering GameGear") | |||||
| gamegear_power(gg, true); | gamegear_power(gg, true); | ||||
| // TODO: use SDL events | |||||
| while (!caught_signal) { | while (!caught_signal) { | ||||
| if (gamegear_simulate(gg)) { | |||||
| uint64_t start = get_time_ns(), delta; | |||||
| if (gamegear_simulate_frame(gg)) { | |||||
| ERROR("caught exception: %s", gamegear_get_exception(gg)) | ERROR("caught exception: %s", gamegear_get_exception(gg)) | ||||
| if (DEBUG_LEVEL) | if (DEBUG_LEVEL) | ||||
| z80_dump_registers(&gg->cpu); | z80_dump_registers(&gg->cpu); | ||||
| break; | break; | ||||
| } | } | ||||
| usleep(1000 * 1000 / 60); | |||||
| // TODO: SDL draw / switch buffers here | |||||
| delta = get_time_ns() - start; | |||||
| if (delta < NS_PER_FRAME) | |||||
| usleep((NS_PER_FRAME - delta) / 1000); | |||||
| } | } | ||||
| if (caught_signal) { | if (caught_signal) { | ||||
| @@ -49,7 +55,7 @@ void emulate(GameGear *gg) | |||||
| z80_dump_registers(&gg->cpu); | z80_dump_registers(&gg->cpu); | ||||
| } | } | ||||
| DEBUG("Interface unpowering GameGear") | |||||
| DEBUG("Emulator unpowering GameGear") | |||||
| gamegear_power(gg, false); | gamegear_power(gg, false); | ||||
| signal(SIGINT, SIG_DFL); | signal(SIGINT, SIG_DFL); | ||||
| @@ -8,7 +8,9 @@ | |||||
| #include "util.h" | #include "util.h" | ||||
| /* Clock speed in Hz was taken from the official Sega GG documentation */ | /* Clock speed in Hz was taken from the official Sega GG documentation */ | ||||
| #define CPU_CLOCK_SPEED 3579545 | |||||
| #define CPU_CLOCK_SPEED (3579545.) | |||||
| #define CYCLES_PER_FRAME (CPU_CLOCK_SPEED / 60) | |||||
| #define CYCLES_PER_LINE (CYCLES_PER_FRAME / VDP_LINES_PER_FRAME) | |||||
| /* | /* | ||||
| Create and return a pointer to a new GameGear object. | Create and return a pointer to a new GameGear object. | ||||
| @@ -72,7 +74,6 @@ void gamegear_power(GameGear *gg, bool state) | |||||
| vdp_power(&gg->vdp); | vdp_power(&gg->vdp); | ||||
| io_power(&gg->io); | io_power(&gg->io); | ||||
| z80_power(&gg->cpu); | z80_power(&gg->cpu); | ||||
| gg->last_tick = get_time_ns(); | |||||
| } else { | } else { | ||||
| gg->exc_buffer[0] = '\0'; | gg->exc_buffer[0] = '\0'; | ||||
| } | } | ||||
| @@ -80,12 +81,10 @@ void gamegear_power(GameGear *gg, bool state) | |||||
| } | } | ||||
| /* | /* | ||||
| Update the simulation of the GameGear. | |||||
| Simulate the GameGear for one frame. | |||||
| This function simulates the number of clock cycles corresponding to the | |||||
| time since the last call to gamegear_simulate() or gamegear_power() if the | |||||
| system was just powered on. If the system is powered off, this function | |||||
| does nothing. | |||||
| This function simulates the number of clock cycles corresponding to 1/60th | |||||
| of a second. If the system is powered off, this function does nothing. | |||||
| The return value indicates whether an exception flag has been set | The return value indicates whether an exception flag has been set | ||||
| somewhere. If true, emulation must be stopped. gamegear_get_exception() can | somewhere. If true, emulation must be stopped. gamegear_get_exception() can | ||||
| @@ -93,14 +92,21 @@ void gamegear_power(GameGear *gg, bool state) | |||||
| gamegear_power(gg, false) followed by gamegear_power(gg, true) will reset | gamegear_power(gg, false) followed by gamegear_power(gg, true) will reset | ||||
| the exception flag and allow emulation to restart normally. | the exception flag and allow emulation to restart normally. | ||||
| */ | */ | ||||
| bool gamegear_simulate(GameGear *gg) | |||||
| bool gamegear_simulate_frame(GameGear *gg) | |||||
| { | { | ||||
| if (!gg->powered) | if (!gg->powered) | ||||
| return false; | return false; | ||||
| uint64_t last = gg->last_tick, tick; | |||||
| tick = gg->last_tick = get_time_ns(); | |||||
| return z80_do_cycles(&gg->cpu, (tick - last) * CPU_CLOCK_SPEED / 1e9); | |||||
| size_t line; | |||||
| bool except; | |||||
| for (line = 0; line < VDP_LINES_PER_FRAME; line++) { | |||||
| vdp_simulate_line(&gg->vdp); | |||||
| except = z80_do_cycles(&gg->cpu, CYCLES_PER_LINE); | |||||
| if (except) | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -21,7 +21,6 @@ typedef struct { | |||||
| VDP vdp; | VDP vdp; | ||||
| IO io; | IO io; | ||||
| bool powered; | bool powered; | ||||
| uint64_t last_tick; | |||||
| char exc_buffer[GG_EXC_BUFF_SIZE]; | char exc_buffer[GG_EXC_BUFF_SIZE]; | ||||
| } GameGear; | } GameGear; | ||||
| @@ -31,5 +30,5 @@ GameGear* gamegear_create(); | |||||
| void gamegear_destroy(GameGear*); | void gamegear_destroy(GameGear*); | ||||
| void gamegear_load(GameGear*, const ROM*); | void gamegear_load(GameGear*, const ROM*); | ||||
| void gamegear_power(GameGear*, bool); | void gamegear_power(GameGear*, bool); | ||||
| bool gamegear_simulate(GameGear*); | |||||
| bool gamegear_simulate_frame(GameGear*); | |||||
| const char* gamegear_get_exception(GameGear*); | const char* gamegear_get_exception(GameGear*); | ||||
| @@ -26,9 +26,9 @@ uint8_t io_port_read(IO *io, uint8_t port) | |||||
| } else if (port <= 0x3F) { | } else if (port <= 0x3F) { | ||||
| return 0xFF; | return 0xFF; | ||||
| } else if (port <= 0x7F && !(port % 2)) { | } else if (port <= 0x7F && !(port % 2)) { | ||||
| // TODO: Return the V counter | |||||
| return io->vdp->v_counter; | |||||
| } else if (port <= 0x7F) { | } else if (port <= 0x7F) { | ||||
| // TODO: Return the H counter | |||||
| return io->vdp->h_counter; | |||||
| } else if (port <= 0xBF && !(port % 2)) { | } else if (port <= 0xBF && !(port % 2)) { | ||||
| return vdp_read_data(io->vdp); | return vdp_read_data(io->vdp); | ||||
| } else if (port <= 0xBF) { | } else if (port <= 0xBF) { | ||||
| @@ -1,14 +1,23 @@ | |||||
| /* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | /* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | ||||
| Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
| #include <string.h> | |||||
| #include "vdp.h" | #include "vdp.h" | ||||
| #include "util.h" | |||||
| #define CODE_VRAM_READ 0 | |||||
| #define CODE_VRAM_WRITE 1 | |||||
| #define CODE_REG_WRITE 2 | |||||
| #define CODE_CRAM_WRITE 3 | |||||
| /* | /* | ||||
| Initialize the Video Display Processor (VDP). | Initialize the Video Display Processor (VDP). | ||||
| */ | */ | ||||
| void vdp_init(VDP *vdp) | void vdp_init(VDP *vdp) | ||||
| { | { | ||||
| // TODO | |||||
| vdp->vram = cr_malloc(sizeof(uint8_t) * VDP_VRAM_SIZE); | |||||
| memset(vdp->vram, 0x00, VDP_VRAM_SIZE); | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -16,7 +25,7 @@ void vdp_init(VDP *vdp) | |||||
| */ | */ | ||||
| void vdp_free(VDP *vdp) | void vdp_free(VDP *vdp) | ||||
| { | { | ||||
| // TODO | |||||
| free(vdp->vram); | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -24,35 +33,100 @@ void vdp_free(VDP *vdp) | |||||
| */ | */ | ||||
| void vdp_power(VDP *vdp) | void vdp_power(VDP *vdp) | ||||
| { | { | ||||
| vdp->regs[0x00] = 0x00; | |||||
| vdp->regs[0x01] = 0x00; | |||||
| vdp->regs[0x02] = 0xFF; | |||||
| vdp->regs[0x03] = 0xFF; | |||||
| vdp->regs[0x04] = 0xFF; | |||||
| vdp->regs[0x05] = 0xFF; | |||||
| vdp->regs[0x06] = 0xFF; | |||||
| vdp->regs[0x07] = 0x00; | |||||
| vdp->regs[0x08] = 0x00; | |||||
| vdp->regs[0x09] = 0x00; | |||||
| vdp->regs[0x0A] = 0x01; | |||||
| vdp->h_counter = 0; | |||||
| vdp->v_counter = 0; | |||||
| vdp->v_count_jump = false; | |||||
| vdp->control_code = 0; | vdp->control_code = 0; | ||||
| vdp->control_addr = 0; | vdp->control_addr = 0; | ||||
| vdp->control_flag = false; | vdp->control_flag = false; | ||||
| vdp->stat_int = vdp->stat_ovf = vdp->stat_col = 0; | |||||
| vdp->read_buf = 0; | |||||
| } | } | ||||
| /* | /* | ||||
| Read a byte from the VDP's control port. | |||||
| Simulate one scanline within the VDP. | |||||
| TODO: elaborate | |||||
| */ | |||||
| void vdp_simulate_line(VDP *vdp) | |||||
| { | |||||
| if (vdp->v_counter >= 0x18 && vdp->v_counter < 0xA8) { | |||||
| // TODO: draw current line | |||||
| } | |||||
| if (vdp->v_counter == 0xDA) | |||||
| vdp->v_count_jump = !vdp->v_count_jump; | |||||
| if (vdp->v_counter == 0xDA && vdp->v_count_jump) | |||||
| vdp->v_counter = 0xD5; | |||||
| else | |||||
| vdp->v_counter++; | |||||
| } | |||||
| /* | |||||
| Read a byte from the VDP's control port, revealing status flags. | |||||
| The status byte consists of: | |||||
| 7 6 5 4 3 2 1 0 | |||||
| F 9S C * * * * * | |||||
| - F: Interrupt flag: set when the effective display area is completed | |||||
| - 9S: 9th sprite / Sprite overflow: more than eight sprites on a scanline | |||||
| - C: Collision flag: two sprites have an overlapping pixel | |||||
| - *: Unused | |||||
| The control flag is also reset. | |||||
| */ | */ | ||||
| uint8_t vdp_read_control(VDP *vdp) | uint8_t vdp_read_control(VDP *vdp) | ||||
| { | { | ||||
| uint8_t status = | |||||
| (vdp->stat_int << 8) + (vdp->stat_ovf << 7) + (vdp->stat_col << 6); | |||||
| vdp->stat_int = vdp->stat_ovf = vdp->stat_col = 0; | |||||
| vdp->control_flag = false; | vdp->control_flag = false; | ||||
| // TODO | |||||
| return 0; | |||||
| return status; | |||||
| } | } | ||||
| /* | /* | ||||
| Read a byte from the VDP's data port. | Read a byte from the VDP's data port. | ||||
| This will return the contents of the read buffer, and then fill the buffer | |||||
| with the VRAM at the current control address, before incrementing the | |||||
| control address. The control flag is also reset. | |||||
| */ | */ | ||||
| uint8_t vdp_read_data(VDP *vdp) | uint8_t vdp_read_data(VDP *vdp) | ||||
| { | { | ||||
| uint8_t buffer = vdp->read_buf; | |||||
| vdp->read_buf = vdp->vram[vdp->control_addr]; | |||||
| vdp->control_addr = (vdp->control_addr + 1) % 0x3FFF; | |||||
| vdp->control_flag = false; | vdp->control_flag = false; | ||||
| // TODO | |||||
| return 0; | |||||
| return buffer; | |||||
| } | } | ||||
| /* | /* | ||||
| Write a byte into the VDP's control port. | Write a byte into the VDP's control port. | ||||
| Depending on the status of the control flag, this will either update the | |||||
| lower byte of the control address, or the upper six bits of the control | |||||
| address and the control code. The flag is toggled by each control write, | |||||
| and reset by each control read and data read or write. | |||||
| If the control code indicates a VRAM read, the read buffer will be filled | |||||
| with the VRAM at the given control address, which is then incremented. If | |||||
| the code indicates a register write, the corresponding register | |||||
| (byte & 0x0F) will be written with the lower byte of the control address. | |||||
| */ | */ | ||||
| void vdp_write_control(VDP *vdp, uint8_t byte) | void vdp_write_control(VDP *vdp, uint8_t byte) | ||||
| { | { | ||||
| @@ -62,15 +136,34 @@ void vdp_write_control(VDP *vdp, uint8_t byte) | |||||
| vdp->control_addr = ((byte & 0x3F) << 8) + (vdp->control_addr & 0xFF); | vdp->control_addr = ((byte & 0x3F) << 8) + (vdp->control_addr & 0xFF); | ||||
| vdp->control_code = byte >> 6; | vdp->control_code = byte >> 6; | ||||
| } | } | ||||
| if (vdp->control_code == CODE_VRAM_READ) { | |||||
| vdp->read_buf = vdp->vram[vdp->control_addr]; | |||||
| vdp->control_addr = (vdp->control_addr + 1) % 0x3FFF; | |||||
| } else if (vdp->control_code == CODE_REG_WRITE) { | |||||
| uint8_t reg = byte & 0x0F; | |||||
| if (reg <= VDP_REGS) | |||||
| vdp->regs[reg] = vdp->control_addr & 0xFF; | |||||
| } | |||||
| vdp->control_flag = !vdp->control_flag; | vdp->control_flag = !vdp->control_flag; | ||||
| } | } | ||||
| /* | /* | ||||
| Write a byte into the VDP's data port. | Write a byte into the VDP's data port. | ||||
| Depending on the control code, this either writes into the VRAM or CRAM at | |||||
| the current control address, which is then incremented. The control flag is | |||||
| also reset, and the read buffer is squashed. | |||||
| */ | */ | ||||
| void vdp_write_data(VDP *vdp, uint8_t byte) | void vdp_write_data(VDP *vdp, uint8_t byte) | ||||
| { | { | ||||
| vdp->control_flag = false; | |||||
| if (vdp->control_code == CODE_CRAM_WRITE) | |||||
| FATAL("unimplemented: VDP CRAM write @ 0x%04X", vdp->control_addr) // TODO | |||||
| else | |||||
| vdp->vram[vdp->control_addr] = byte; | |||||
| // TODO | |||||
| vdp->control_addr = (vdp->control_addr + 1) % 0x3FFF; | |||||
| vdp->control_flag = false; | |||||
| vdp->read_buf = byte; | |||||
| } | } | ||||
| @@ -6,12 +6,25 @@ | |||||
| #include <stdbool.h> | #include <stdbool.h> | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #define VDP_LINES_PER_FRAME 262 | |||||
| #define VDP_VRAM_SIZE (16 * 1024) | |||||
| #define VDP_REGS 11 | |||||
| /* Structs */ | /* Structs */ | ||||
| typedef struct { | typedef struct { | ||||
| uint8_t *vram; | |||||
| uint8_t regs[VDP_REGS]; | |||||
| uint8_t h_counter; | |||||
| uint8_t v_counter; | |||||
| bool v_count_jump; | |||||
| uint8_t control_code; | uint8_t control_code; | ||||
| uint16_t control_addr; | uint16_t control_addr; | ||||
| bool control_flag; | bool control_flag; | ||||
| bool stat_int, stat_ovf, stat_col; | |||||
| uint8_t read_buf; | |||||
| } VDP; | } VDP; | ||||
| /* Functions */ | /* Functions */ | ||||
| @@ -19,6 +32,7 @@ typedef struct { | |||||
| void vdp_init(VDP*); | void vdp_init(VDP*); | ||||
| void vdp_free(VDP*); | void vdp_free(VDP*); | ||||
| void vdp_power(VDP*); | void vdp_power(VDP*); | ||||
| void vdp_simulate_line(VDP*); | |||||
| uint8_t vdp_read_control(VDP*); | uint8_t vdp_read_control(VDP*); | ||||
| uint8_t vdp_read_data(VDP*); | uint8_t vdp_read_data(VDP*); | ||||
| @@ -320,7 +320,7 @@ static inline void trace_instruction(Z80 *z80) | |||||
| */ | */ | ||||
| bool z80_do_cycles(Z80 *z80, double cycles) | bool z80_do_cycles(Z80 *z80, double cycles) | ||||
| { | { | ||||
| cycles -= z80->pending_cycles; | |||||
| cycles += z80->pending_cycles; | |||||
| while (cycles > 0 && !z80->except) { | while (cycles > 0 && !z80->except) { | ||||
| uint8_t opcode = mmu_read_byte(z80->mmu, z80->regfile.pc); | uint8_t opcode = mmu_read_byte(z80->mmu, z80->regfile.pc); | ||||
| increment_refresh_counter(z80); | increment_refresh_counter(z80); | ||||
| @@ -329,7 +329,7 @@ bool z80_do_cycles(Z80 *z80, double cycles) | |||||
| cycles -= (*instruction_table[opcode])(z80, opcode); | cycles -= (*instruction_table[opcode])(z80, opcode); | ||||
| } | } | ||||
| z80->pending_cycles = -cycles; | |||||
| z80->pending_cycles = cycles; | |||||
| return z80->except; | return z80->except; | ||||
| } | } | ||||