@@ -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; | ||||
} | } | ||||