Browse Source

Add proper timing code, etc.

master
Ben Kurtovic 9 years ago
parent
commit
a202baa3a6
7 changed files with 120 additions and 19 deletions
  1. +38
    -5
      src/gamegear.c
  2. +4
    -4
      src/gamegear.h
  3. +7
    -5
      src/iomanager.c
  4. +31
    -0
      src/util.c
  5. +1
    -0
      src/util.h
  6. +32
    -3
      src/z80.c
  7. +7
    -2
      src/z80.h

+ 38
- 5
src/gamegear.c View File

@@ -5,6 +5,10 @@

#include "gamegear.h"
#include "logging.h"
#include "util.h"

/* Clock speed in Hz was taken from the official Sega GG documentation */
#define CPU_CLOCK_SPEED 3579545

/*
Create and return a pointer to a new GameGear object.
@@ -18,7 +22,7 @@ GameGear* gamegear_create()
OUT_OF_MEMORY()

// mmu_init(&gg->mmu, ...);
z80_init(&gg->cpu, CPU_CLOCK_SPEED);
z80_init(&gg->cpu, &gg->mmu);
gg->powered = false;
return gg;
}
@@ -30,6 +34,7 @@ GameGear* gamegear_create()
*/
void gamegear_destroy(GameGear *gg)
{
// mmu_free(&gg->mmu);
free(gg);
}

@@ -64,6 +69,9 @@ void gamegear_power(GameGear *gg, bool state)
if (state) {
// mmu_power(&gg->mmu);
z80_power(&gg->cpu);
gg->last_tick = get_time_ns();
} else {
// TODO: free exception buffer
}
gg->powered = state;
}
@@ -75,12 +83,37 @@ void gamegear_power(GameGear *gg, bool state)
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.

The return value indicates whether an exception flag has been set
somewhere. If true, emulation must be stopped. gamegear_get_exception() can
be used to fetch exception information. Power-cycling the GameGear with
gamegear_power(gg, false) followed by gamegear_power(gg, true) will reset
the exception flag and allow emulation to restart normally.
*/
void gamegear_simulate(GameGear *gg)
bool gamegear_simulate(GameGear *gg)
{
if (!gg->powered)
return;
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);
}

/*
If an exception flag has been set in the GameGear, return the reason.

This function returns a const pointer to a buffer holding a human-readable
exception string (although it may be cryptic to an end-user). The buffer is
owned by the GameGear object and should not be freed - it lasts until the
GameGear's power state is changed. If no exception flag is set, this
function returns NULL.
*/
const char* gamegear_get_exception(GameGear *gg)
{
if (!gg->powered)
return NULL;

// TODO
// z80_do_cycles(&gg->cpu, ...);
// TODO: return ptr to exception buffer
return "Unknown exception";
}

+ 4
- 4
src/gamegear.h View File

@@ -4,20 +4,19 @@
#pragma once

#include <stdbool.h>
#include <stdint.h>

#include "mmu.h"
#include "rom.h"
#include "z80.h"

/* Clock speed in Hz was taken from the official Sega GG documentation */
#define CPU_CLOCK_SPEED 3579545

/* Structs */

typedef struct {
MMU mmu;
Z80 cpu;
bool powered;
uint64_t last_tick;
} GameGear;

/* Functions */
@@ -26,4 +25,5 @@ GameGear* gamegear_create();
void gamegear_destroy(GameGear*);
void gamegear_load(GameGear*, ROM*);
void gamegear_power(GameGear*, bool);
void gamegear_simulate(GameGear*);
bool gamegear_simulate(GameGear*);
const char* gamegear_get_exception(GameGear*);

+ 7
- 5
src/iomanager.c View File

@@ -16,13 +16,15 @@ void iomanager_emulate(GameGear *gg)
DEBUG("IOManager powering GameGear")
gamegear_power(gg, true);

#ifdef DEBUG_MODE
z80_dump_registers(&gg->cpu);
#endif

// TODO: use SDL events
while (1) {
gamegear_simulate(gg);
if (gamegear_simulate(gg)) {
DEBUG("IOManager caught exception: %s", gamegear_get_exception(gg))
#ifdef DEBUG_MODE
z80_dump_registers(&gg->cpu);
#endif
break;
}
usleep(1000 * 1000 / 60);
}



+ 31
- 0
src/util.c View File

@@ -3,6 +3,19 @@

#include "util.h"

#if defined __APPLE__
#include <mach/mach_time.h>
#elif defined __linux__
#include <time.h>
#ifndef CLOCK_MONOTONIC
#error "Unsupported operating system or compiler."
#endif
#else
#error "Unsupported operating system or compiler."
#endif

#define NS_PER_SEC 1000000000

/*
Convert a BCD-encoded hexadecimal number to decimal.
*/
@@ -10,3 +23,21 @@ uint8_t bcd_decode(uint8_t num)
{
return ((num >> 4) * 10) + (num & 0x0F);
}

/*
Return monotonic time, in nanoseconds.
*/
uint64_t get_time_ns()
{
#if defined __APPLE__
static mach_timebase_info_data_t tb_info;
if (tb_info.denom == 0)
mach_timebase_info(&tb_info);
// Apple's docs pretty much say "screw overflow" here...
return mach_absolute_time() * tb_info.numer / tb_info.denom;
#elif defined __linux__
struct timespec spec;
clock_gettime(CLOCK_MONOTONIC, &spec);
return spec.tv_sec * NS_PER_SEC + spec.tv_nsec;
#endif
}

+ 1
- 0
src/util.h View File

@@ -8,3 +8,4 @@
/* Functions */

uint8_t bcd_decode(uint8_t);
uint64_t get_time_ns();

+ 32
- 3
src/z80.c View File

@@ -14,14 +14,21 @@

/*
Initialize a Z80 object.

Register values are invalid until z80_power() is called. No other Z80
functions should be called before it.
*/
void z80_init(Z80 *z80, uint64_t clock_speed)
void z80_init(Z80 *z80, MMU *mmu)
{
z80->clock_speed = clock_speed;
z80->mmu = mmu;
z80->except = true;
}

/*
Power on the Z80, setting registers to their default values.

This also clears the exception flag, which is necessary before the Z80 can
begin emulation.
*/
void z80_power(Z80 *z80)
{
@@ -46,6 +53,9 @@ void z80_power(Z80 *z80)

regfile->im_a = regfile->im_b = 0;
regfile->iff1 = regfile->iff2 = 0;

z80->except = false;
z80->pending_cycles = 0;
}

/*
@@ -76,6 +86,24 @@ static inline uint8_t get_interrupt_mode(Z80 *z80)
return 2;
}

/*
Emulate the given number of cycles of the Z80, or until an exception.

The return value indicates whether the exception flag is set. If it is,
then emulation must be stopped because further calls to z80_do_cycles()
will have no effect. The exception flag can be reset with z80_power().
*/
bool z80_do_cycles(Z80 *z80, double cycles)
{
if (z80->except)
return true;

z80->pending_cycles += cycles;
// TODO: fetch instruction...

return false;
}

#ifdef DEBUG_MODE
/*
DEBUG FUNCTION: Print out all register values to stdout.
@@ -119,7 +147,8 @@ void z80_dump_registers(Z80 *z80)
DEBUG("- I: 0x%2X", regfile->i)
DEBUG("- R: 0x%2X", regfile->r)

DEBUG("- IM: 0b%u%u (mode: %u)", regfile->im_a, regfile->im_b, get_interrupt_mode(z80))
DEBUG("- IM: 0b%u%u (mode: %u)", regfile->im_a, regfile->im_b,
get_interrupt_mode(z80))
DEBUG("- IFF1: %u", regfile->iff1)
DEBUG("- IFF2: %u", regfile->iff2)
}


+ 7
- 2
src/z80.h View File

@@ -6,6 +6,8 @@
#include <stdbool.h>
#include <stdint.h>

#include "mmu.h"

/* Structs */

typedef struct {
@@ -19,13 +21,16 @@ typedef struct {

typedef struct {
Z80RegFile regfile;
uint64_t clock_speed;
MMU *mmu;
bool except;
double pending_cycles;
} Z80;

/* Functions */

void z80_init(Z80*, uint64_t);
void z80_init(Z80*, MMU*);
void z80_power(Z80*);
bool z80_do_cycles(Z80*, double);

#ifdef DEBUG_MODE
void z80_dump_registers(Z80*);


Loading…
Cancel
Save