diff --git a/src/emulator.c b/src/emulator.c index e57493a..f8a1d86 100644 --- a/src/emulator.c +++ b/src/emulator.c @@ -7,6 +7,9 @@ #include "emulator.h" #include "logging.h" +#include "util.h" + +#define NS_PER_FRAME (1000 * 1000 * 1000 / 60) static volatile bool caught_signal; @@ -29,18 +32,21 @@ void emulate(GameGear *gg) caught_signal = false; signal(SIGINT, handle_sigint); - DEBUG("Interface powering GameGear") + DEBUG("Emulator powering GameGear") gamegear_power(gg, true); - // TODO: use SDL events 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)) if (DEBUG_LEVEL) z80_dump_registers(&gg->cpu); 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) { @@ -49,7 +55,7 @@ void emulate(GameGear *gg) z80_dump_registers(&gg->cpu); } - DEBUG("Interface unpowering GameGear") + DEBUG("Emulator unpowering GameGear") gamegear_power(gg, false); signal(SIGINT, SIG_DFL); diff --git a/src/gamegear.c b/src/gamegear.c index 5027e81..3db5e79 100644 --- a/src/gamegear.c +++ b/src/gamegear.c @@ -8,7 +8,9 @@ #include "util.h" /* 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. @@ -72,7 +74,6 @@ void gamegear_power(GameGear *gg, bool state) vdp_power(&gg->vdp); io_power(&gg->io); z80_power(&gg->cpu); - gg->last_tick = get_time_ns(); } else { 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 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 the exception flag and allow emulation to restart normally. */ -bool gamegear_simulate(GameGear *gg) +bool gamegear_simulate_frame(GameGear *gg) { if (!gg->powered) 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; } /* diff --git a/src/gamegear.h b/src/gamegear.h index 707335a..3792a40 100644 --- a/src/gamegear.h +++ b/src/gamegear.h @@ -21,7 +21,6 @@ typedef struct { VDP vdp; IO io; bool powered; - uint64_t last_tick; char exc_buffer[GG_EXC_BUFF_SIZE]; } GameGear; @@ -31,5 +30,5 @@ GameGear* gamegear_create(); void gamegear_destroy(GameGear*); void gamegear_load(GameGear*, const ROM*); void gamegear_power(GameGear*, bool); -bool gamegear_simulate(GameGear*); +bool gamegear_simulate_frame(GameGear*); const char* gamegear_get_exception(GameGear*); diff --git a/src/io.c b/src/io.c index 885bc6d..fc89e8a 100644 --- a/src/io.c +++ b/src/io.c @@ -26,9 +26,9 @@ uint8_t io_port_read(IO *io, uint8_t port) } else if (port <= 0x3F) { return 0xFF; } else if (port <= 0x7F && !(port % 2)) { - // TODO: Return the V counter + return io->vdp->v_counter; } else if (port <= 0x7F) { - // TODO: Return the H counter + return io->vdp->h_counter; } else if (port <= 0xBF && !(port % 2)) { return vdp_read_data(io->vdp); } else if (port <= 0xBF) { diff --git a/src/vdp.c b/src/vdp.c index 335a108..d4089e0 100644 --- a/src/vdp.c +++ b/src/vdp.c @@ -1,14 +1,23 @@ /* Copyright (C) 2014-2016 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ +#include + #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). */ 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) { - // TODO + free(vdp->vram); } /* @@ -24,35 +33,100 @@ void vdp_free(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_addr = 0; 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 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; - - // TODO - return 0; + return status; } /* 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 buffer = vdp->read_buf; + vdp->read_buf = vdp->vram[vdp->control_addr]; + vdp->control_addr = (vdp->control_addr + 1) % 0x3FFF; vdp->control_flag = false; - - // TODO - return 0; + return buffer; } /* 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) { @@ -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_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; } /* 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) { - 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; } diff --git a/src/vdp.h b/src/vdp.h index 7422f99..fd45cdc 100644 --- a/src/vdp.h +++ b/src/vdp.h @@ -6,12 +6,25 @@ #include #include +#define VDP_LINES_PER_FRAME 262 +#define VDP_VRAM_SIZE (16 * 1024) +#define VDP_REGS 11 + /* Structs */ 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; uint16_t control_addr; bool control_flag; + bool stat_int, stat_ovf, stat_col; + uint8_t read_buf; } VDP; /* Functions */ @@ -19,6 +32,7 @@ typedef struct { void vdp_init(VDP*); void vdp_free(VDP*); void vdp_power(VDP*); +void vdp_simulate_line(VDP*); uint8_t vdp_read_control(VDP*); uint8_t vdp_read_data(VDP*); diff --git a/src/z80.c b/src/z80.c index e108dac..c86afe6 100644 --- a/src/z80.c +++ b/src/z80.c @@ -320,7 +320,7 @@ static inline void trace_instruction(Z80 *z80) */ bool z80_do_cycles(Z80 *z80, double cycles) { - cycles -= z80->pending_cycles; + cycles += z80->pending_cycles; while (cycles > 0 && !z80->except) { uint8_t opcode = mmu_read_byte(z80->mmu, z80->regfile.pc); increment_refresh_counter(z80); @@ -329,7 +329,7 @@ bool z80_do_cycles(Z80 *z80, double cycles) cycles -= (*instruction_table[opcode])(z80, opcode); } - z80->pending_cycles = -cycles; + z80->pending_cycles = cycles; return z80->except; }