/***************************************************************
 *  Alcuin: A HP48 emulator for Amiga Computers
 * 
 *  (C) 1995 Stephan Schupfer (schupfer@sbox.tu-graz.ac.at)
 *
 */

#include <stdio.h>

#include "A48.h"
#include "hp48.h"
#include "device.h"
#include "timer.h"
long	schedule_event = 0;
#include "saturn-opc.h"
#include "cpu-func.h"
unsigned char last_op[21],*lop2,*lop5;
struct opcode *op;
#include "insn_read.c"
#include "exec-switch.c"
#include "table.h"
#include "romio.h"

extern unsigned long timer_set;

#define		SrvcIoStart		0x3c0
#define		SrvcIoEnd		0x5ec
#define		SCHED_TIMER2		0xf        /* 1e*/
#define		SCHED_RECEIVE		0x7ff
#define		SCHED_EVENT			0x1ff
#define		SCHED_TIMER1		0x1e00		/*2e00*/
#define		SCHED_ADJTIME		0x1ffe		/*2ffe*/ 
#define		SCHED_STATISTICS	0x7ffff
#define		SCHED_INSTR_ROLLOVER 0x3fffffff
#define		SCHED_NEVER			0x7fffffff
#define		NR_SAMPLES 10
#define		NR_SAMPLES1 9

long		sched_timer2 = SCHED_TIMER2;
long		sched_receive = SCHED_RECEIVE;
long		sched_event = SCHED_EVENT;
long		sched_timer1 = SCHED_TIMER1;
long		sched_adjtime = SCHED_ADJTIME;
long		sched_statistics = SCHED_STATISTICS;
long		sched_display = SCHED_NEVER;
long		sched_instr_rollover = SCHED_INSTR_ROLLOVER;


unsigned long	t1_i_per_tick;
unsigned long	t2_i_per_tick;
unsigned long	s_1;
unsigned long	s_16;
unsigned long	old_s_1;
unsigned long	old_s_16;
unsigned long	delta_t_1;
unsigned long	delta_t_16;
unsigned long	delta_i;

unsigned long   instructions=0;
int		device_check = 0;
int		adj_time_pending = 0;
int		set_t1;

/************************************************************/
inline void step_instruction(void)
{
struct optab *p, *tab = opcodes;
int opcount=0;

read_instr (saturn_PC,last_op);
p = tab[last_op[0]].table;
while (p) {
	tab = p;
	p = tab[last_op[++opcount]].table;
	}
op = tab[last_op[opcount]].opcode;
op->operation ();
instructions++;
}

/************************************************************/
inline void schedule(void)
{
t1_t2_ticks		ticks;
unsigned long		steps;
static unsigned long	old_stat_instr;
static unsigned long	old_sched_instr;
word_64		run;

steps = instructions - old_sched_instr;
old_sched_instr = instructions;

if ((sched_timer2 -= steps) <= 0) {
	if (!saturn_intenable)
		sched_timer2 = SCHED_TIMER2;
	else
		sched_timer2 = saturn_t2_tick;
	saturn_t2_instr += steps;
	if (saturn_t2_ctrl & 0x01)
		saturn_timer2--;
	if (!saturn_timer2  && (saturn_t2_ctrl & 0x02)) {
		saturn_t2_ctrl |= 0x08;
		do_interupt();
		}
	}
schedule_event = sched_timer2;

if (device_check) {
	device_check = 0;
	if ((sched_display -= steps) <= 0) {
		if (device.display_touched) device.display_touched -= steps;
		if (device.display_touched < 0) device.display_touched = 1;
		}
	check_devices();
	sched_display = SCHED_NEVER;
	if (device.display_touched) {
		if (device.display_touched < sched_display)
			sched_display = device.display_touched - 1;
		if (sched_display < schedule_event) schedule_event = sched_display;
		}
	}
if ((sched_event -= steps) <= 0) {
	sched_event = SCHED_EVENT;
	GetEvent();
  	}
if (sched_event < schedule_event) schedule_event = sched_event;

if ((sched_receive -= steps) <= 0) {
	sched_receive = SCHED_RECEIVE;
	if (!(saturn_rcs & 0x01))
		receive_char();
  	}
if (sched_receive < schedule_event) schedule_event = sched_receive;

if ((sched_adjtime -= steps) <= 0) {
	sched_adjtime = SCHED_ADJTIME;
	if (saturn_PC < SrvcIoStart || saturn_PC > SrvcIoEnd) {
		ticks = get_t1_t2();
		if (saturn_t2_ctrl & 0x01)
			saturn_timer2 = ticks.t2_ticks;
		if (!(saturn_t2_ctrl & 0x08) && saturn_timer2 <= 0)
			if (saturn_t2_ctrl & 0x02) {
				saturn_t2_ctrl |= 0x08;
				do_interupt();
				}

		adj_time_pending = 0;

		saturn_timer1 = set_t1 - ticks.t1_ticks;
		if ((!(saturn_t1_ctrl & 0x08)) && saturn_timer1 <= 0) {
			if (saturn_t1_ctrl & 0x02) {
				saturn_t1_ctrl |= 0x08;
				do_interupt();
				}
			}
		saturn_timer1 &= 0x0f;
		}
	else adj_time_pending = 1;
	}
if (sched_adjtime < schedule_event) schedule_event = sched_adjtime;

if ((sched_timer1 -= steps) <= 0) {
	if (!saturn_intenable)
		sched_timer1 = SCHED_TIMER1;
	else
		sched_timer1 = saturn_t1_tick;
	saturn_t1_instr += steps;
	saturn_timer1 = (saturn_timer1 - 1) & 0xf;
	if (!saturn_timer1 && (saturn_t1_ctrl & 0x02)) {
		saturn_t1_ctrl |= 0x08;
		do_interupt();
		}
	}
if (sched_timer1 < schedule_event) schedule_event = sched_timer1;

if ((sched_statistics -= steps) <= 0) {
	sched_statistics = SCHED_STATISTICS;
	run = get_timer(RUN_TIMER);
	s_1 = (run.hi << 19) | (run.lo >> 13);
	s_16 = (run.hi << 23) | (run.lo >> 9);
	delta_t_1 = s_1 - old_s_1;
	delta_t_16 = s_16 - old_s_16;
	old_s_1 = s_1;
	old_s_16 = s_16;
	delta_i = instructions - old_stat_instr;
	old_stat_instr = instructions;
	if (delta_t_1 > 0) {
		t1_i_per_tick = ((NR_SAMPLES1) * t1_i_per_tick + (delta_i / delta_t_16)) / NR_SAMPLES;
		t2_i_per_tick = t1_i_per_tick / 512;
		saturn_i_per_s = ((NR_SAMPLES1) * saturn_i_per_s +(delta_i / delta_t_1)) / NR_SAMPLES;
		}
	else {
		t1_i_per_tick = 8192;
		t2_i_per_tick = 16;
		}
	saturn_t1_tick = t1_i_per_tick;
	saturn_t2_tick = t2_i_per_tick;
	}
if (sched_statistics < schedule_event)
	schedule_event = sched_statistics;
if ((sched_instr_rollover -= steps) <= 0) {
	sched_instr_rollover = SCHED_INSTR_ROLLOVER;
	instructions = 1;
	old_sched_instr = 1;
/*	reset_timer(RUN_TIMER);*/
	reset_timer(IDLE_TIMER);
	restart_timer(RUN_TIMER);
	}
if (sched_instr_rollover < schedule_event)
	schedule_event = sched_instr_rollover;

schedule_event--;
}

/************************************************************/
int emulate(void)
{
__label__ loop;

lop2=&(last_op[2]);
lop5=&(last_op[5]);
/*
reset_timer(T1_TIMER);
reset_timer(RUN_TIMER);
*/
reset_timer(IDLE_TIMER);
restart_timer(T1_TIMER);
set_accesstime();
restart_timer(RUN_TIMER);
sched_timer1 = t1_i_per_tick = saturn_t1_tick;
sched_timer2 = t2_i_per_tick = saturn_t2_tick;
set_t1 = saturn_timer1;
loop:
	step_instruction();
	if (!schedule_event--) schedule();
	goto loop;
return 0;
}

/************************************************************/
