Browse Source

Rework timing system for VDP scanlines; flesh out more VDP logic.

master
Ben Kurtovic 8 years ago
parent
commit
61c104a0f3
7 changed files with 151 additions and 33 deletions
  1. +11
    -5
      src/emulator.c
  2. +17
    -11
      src/gamegear.c
  3. +1
    -2
      src/gamegear.h
  4. +2
    -2
      src/io.c
  5. +104
    -11
      src/vdp.c
  6. +14
    -0
      src/vdp.h
  7. +2
    -2
      src/z80.c

+ 11
- 5
src/emulator.c View File

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


+ 17
- 11
src/gamegear.c View File

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

/*


+ 1
- 2
src/gamegear.h View File

@@ -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*);

+ 2
- 2
src/io.c View File

@@ -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) {


+ 104
- 11
src/vdp.c View File

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

+ 14
- 0
src/vdp.h View File

@@ -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*);


+ 2
- 2
src/z80.c View File

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



Loading…
Cancel
Save