BizHawk/waterbox/pizza/lib/cycles.c

322 lines
7.1 KiB
C

/*
This file is part of Emu-Pizza
Emu-Pizza is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Emu-Pizza is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Emu-Pizza. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "cycles.h"
#include "global.h"
#include "gpu.h"
#include "mmu.h"
#include "serial.h"
#include "sound.h"
#include "timer.h"
#include "interrupt.h"
#include "utils.h"
interrupts_flags_t *cycles_if;
/* instance of the main struct */
cycles_t cycles = {0, 0, 0, 0};
#define CYCLES_PAUSES 256
/* hard sync stuff (for remote connection) */
uint8_t cycles_hs_mode = 0;
/* type of next */
typedef enum {
CYCLES_NEXT_TYPE_CYCLES,
CYCLES_NEXT_TYPE_CYCLES_HS,
CYCLES_NEXT_TYPE_DMA,
} cycles_next_type_enum_e;
/* closest next and its type */
uint_fast32_t cycles_very_next;
cycles_next_type_enum_e cycles_next_type;
/* set hard sync mode. sync is given by the remote peer + local timer */
void cycles_start_hs()
{
utils_log("Hard sync mode ON\n");
/* boolean set to on */
cycles_hs_mode = 1;
}
void cycles_stop_hs()
{
utils_log("Hard sync mode OFF\n");
/* boolean set to on */
cycles_hs_mode = 0;
}
/* set double or normal speed */
void cycles_set_speed(char dbl)
{
/* set global */
global_cpu_double_speed = dbl;
/* update clock */
if (global_cpu_double_speed)
cycles.clock = 4194304 * 2;
else
cycles.clock = 4194304;
/* calculate the mask */
cycles_change_emulation_speed();
}
/* set emulation speed */
void cycles_change_emulation_speed()
{
cycles.step = ((4194304 / CYCLES_PAUSES)
<< global_cpu_double_speed);
}
void cycles_closest_next()
{
int_fast32_t diff = cycles.cnt - cycles.next;
/* init */
cycles_very_next = cycles.next;
cycles_next_type = CYCLES_NEXT_TYPE_CYCLES;
int_fast32_t diff_new = cycles.cnt - mmu.dma_next;
/* DMA? */
if (diff_new < diff)
{
/* this is the new lowest */
cycles_very_next = mmu.dma_next;
cycles_next_type = CYCLES_NEXT_TYPE_DMA;
}
}
/* this function is gonna be called every M-cycle = 4 ticks of CPU */
void cycles_step()
{
cycles.cnt += 4;
cycles.sampleclock += 2 >> global_cpu_double_speed;
/*
while (cycles.cnt >= cycles_very_next)
{
switch (cycles_next_type)
{
case CYCLES_NEXT_TYPE_CYCLES:
deadline.tv_nsec += 1000000000 / CYCLES_PAUSES;
if (deadline.tv_nsec > 1000000000)
{
deadline.tv_sec += 1;
deadline.tv_nsec -= 1000000000;
}
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
&deadline, NULL);
cycles.next += cycles.step;
if (cycles.cnt % cycles.clock == 0)
cycles.seconds++;
break;
case CYCLES_NEXT_TYPE_DMA:
memcpy(&mmu.memory[0xFE00], &mmu.memory[mmu.dma_address], 160);
mmu.dma_address = 0x0000;
mmu.dma_next = 1;
break;
}
cycles_closest_next();
}
*/
/* 65536 == cpu clock / CYCLES_PAUSES pauses every second */
if (cycles.cnt == cycles.next)
{
cycles.next += cycles.step;
}
/* hard sync next step */
if (cycles.cnt == cycles.hs_next)
{
/* set cycles for hard sync */
cycles.hs_next += ((4096 * 4) << global_cpu_double_speed);
/* hard sync is on? */
if (cycles_hs_mode)
{
/* send my status and wait for peer status back */
serial_send_byte();
/* wait for reply */
serial_wait_data();
/* verify if we need to trigger an interrupt */
serial_verify_intr();
}
}
/* DMA */
if (mmu.dma_next == cycles.cnt)
{
memcpy(&mmu.memory[0xFE00], &mmu.memory[mmu.dma_address], 160);
/* reset address */
mmu.dma_address = 0x0000;
/* reset */
mmu.dma_next = 1;
}
/* update GPU state */
if (gpu.next == cycles.cnt)
gpu_step();
/* fs clock */
if (sound.fs_cycles_next == cycles.cnt)
sound_step_fs();
/* channel one */
if (sound.channel_one.duty_cycles_next == cycles.cnt)
sound_step_ch1();
/* channel two */
if (sound.channel_two.duty_cycles_next == cycles.cnt)
sound_step_ch2();
/* channel three */
if (sound.channel_three.cycles_next <= cycles.cnt)
sound_step_ch3();
/* channel four */
if (sound.channel_four.cycles_next == cycles.cnt)
sound_step_ch4();
/* update timer state */
if (cycles.cnt == timer.next)
{
timer.next += 256;
timer.div++;
}
/* timer is on? */
if (timer.sub_next == cycles.cnt)
{
timer.sub_next += timer.threshold;
timer.cnt++;
/* cnt value > 255? trigger an interrupt */
if (timer.cnt > 255)
{
timer.cnt = timer.mod;
/* trigger timer interrupt */
cycles_if->timer = 1;
}
}
/* update serial state */
if (serial.next == cycles.cnt)
{
/* nullize serial next */
serial.next -= 1;
/* reset counter */
serial.bits_sent = 0;
/* gotta reply with 0xff when asking for ff01 */
serial.data = 0xFF;
/* reset transfer_start flag to yell I'M DONE */
serial.transfer_start = 0;
/* if not connected, trig the fucking interrupt */
cycles_if->serial_io = 1;
}
}
/* things to do when vsync kicks in */
void cycles_vblank()
{
return;
}
/* stuff tied to entering into hblank state */
void cycles_hdma()
{
/* HDMA (only CGB) */
if (mmu.hdma_to_transfer)
{
/* hblank transfer */
if (mmu.hdma_transfer_mode)
{
/* transfer when line is changed and we're into HBLANK phase */
if (mmu.memory[0xFF44] < 143 &&
mmu.hdma_current_line != mmu.memory[0xFF44] &&
(mmu.memory[0xFF41] & 0x03) == 0x00)
{
/* update current line */
mmu.hdma_current_line = mmu.memory[0xFF44];
/* copy 0x10 bytes */
if (mmu.vram_idx)
memcpy(mmu_addr_vram1() + mmu.hdma_dst_address - 0x8000,
&mmu.memory[mmu.hdma_src_address], 0x10);
else
memcpy(mmu_addr_vram0() + mmu.hdma_dst_address - 0x8000,
&mmu.memory[mmu.hdma_src_address], 0x10);
/* decrease bytes to transfer */
mmu.hdma_to_transfer -= 0x10;
/* increase pointers */
mmu.hdma_dst_address += 0x10;
mmu.hdma_src_address += 0x10;
}
}
}
}
char cycles_init()
{
cycles.inited = 1;
/* interrupt registers */
cycles_if = mmu_addr(0xFF0F);
/* init clock and counter */
cycles.clock = 4194304;
cycles.cnt = 0;
cycles.hs_next = 70224;
/* mask for pauses cycles fast calc */
cycles.step = 4194304 / CYCLES_PAUSES;
cycles.next = 4194304 / CYCLES_PAUSES;
return 0;
}