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