@@ -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); | |||
@@ -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; | |||
} | |||
/* | |||
@@ -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*); |
@@ -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) { | |||
@@ -1,14 +1,23 @@ | |||
/* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#include <string.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). | |||
*/ | |||
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; | |||
} |
@@ -6,12 +6,25 @@ | |||
#include <stdbool.h> | |||
#include <stdint.h> | |||
#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*); | |||
@@ -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; | |||
} | |||