/*	
 *	clipdmc.h
 *
 *	P/ECE DMG-CPU (Sharp LR35902) Emulator (高速版)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Sun Feb 13 03:10:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 *	* Wed Nov 18 21:37:53 JST 2015 Naoyuki Sawa
 *	- 'extern "C" {'〜'}'で囲みました。.cppを含むWin32プロジェクトからもインクルード出来るようにするためです。
 */
#ifndef __CLIP_DMC_H__
#define __CLIP_DMC_H__

#ifdef  __cplusplus
extern "C" {
#endif//__cplusplus

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

/* 実行用のDMG-CPU構造体です。 */
typedef struct _DMGCPU {
	unsigned short cz;		/* + 0,2: -------C zzzzzzzz (C=F(C),z=!F(Z)) */
	unsigned char  nh;		/* + 2,1:          --NH---- (N=F(N),H= F(H)) */
	unsigned char   a;		/* + 3,1: A  */
	unsigned short bc;		/* + 4,2: BC */
	unsigned short de;		/* + 6,2: DE */
	unsigned short hl;		/* + 8,2: HL */
	unsigned short sp;		/* +10,2: SP */
	unsigned short pc;		/* +12,2: PC */
	unsigned char flags;		/* +14,1: -----SHI (S=STOP,H=HALT,I=IME) */
	//				/* +15,1: (パディング) */
	int cycle;			/* +16,4: 残り実行サイクル数 */
	void* read;			/* +20,4: メモリ読み出しルーチン */
	void* write;			/* +24,4: メモリ書き込みルーチン */
} DMGCPU;				/* =28 */

/* DMGCPU.flags */
#define DMGCPU_IME				(1<<0)
#define DMGCPU_HALT				(1<<1)
#define DMGCPU_STOP				(1<<2)

/* $FF0F(IF)、$FFFF(IE) */
#define DMGCPU_V_BLANK				(1<<0)	/* RST $40 */
#define DMGCPU_LCDC_STATUS			(1<<1)	/* RST $48 */
#define DMGCPU_TIMER_OVERFLOW			(1<<2)	/* RST $50 */
#define DMGCPU_SERIAL_TRANSFER_COMPLETION	(1<<3)	/* RST $58 */
#define DMGCPU_HIGH_TO_LOW_OF_P10_P13		(1<<4)	/* RST $60 */

/* レジスタ設定・参照用の構造体です。 */
typedef struct _DMGREG {
	unsigned short af;		/* + 0,2 */
	unsigned short bc;		/* + 2,2 */
	unsigned short de;		/* + 4,2 */
	unsigned short hl;		/* + 6,2 */
	unsigned short sp;		/* + 8,2 */
	unsigned short pc;		/* +10,2 */
} DMGREG;				/* =12 */

extern DMGCPU dmgcpu;			/* 高速RAMに配置 */
extern unsigned char dmgmem[0x10000];	/*    SRAMに配置 */

/****************************************************************************
 *	内部関数
 ****************************************************************************/

int/*%data*/ dmgmem_read(/*%addr*/);	/* 単純なメモリ読み出しルーチン */
void  dmgmem_write(/*%addr,%data*/);	/* 単純なメモリ書き込みルーチン */

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

/* CPUをリセットします。
 * [in]
 *	read		メモリRead 外部関数 (NULL可)
 *	write		メモリWrite外部関数 (NULL可)
 *	gb_ipl		0以外を指定すると、GameBoyの疑似IPLプログラムを実行します。
 *			GameBoyの疑似IPLを実行した場合、開始アドレスは$0100となります。
 *			0を指定した場合は、GameBoyの疑似IPLを行わず、$0000から開始します。
 * [note]
 *	* メモリRead外部関数にNULLを指定すると、dmgmem_read()が設定されます。
 *	  dmgmem_read()は、単純にdmgmem[]を読み出すだけのメモリRead関数です。
 *	* メモリWrite外部関数にNULLを指定すると、dmgmem_write()が設定されます。
 *	  dmgmem_write()は、単純にdmgmem[]へ書き込むだけのメモリWrite関数です。
 *	* 外部定義のメモリRead/Write関数の作り方は、下の方を参照してください。
 */
void dmgcpu_reset(void* read, void* write, int gb_ipl);

/* CPUを実行します。
 * [in]
 *	cycle		実行サイクル数。
 */
void dmgcpu_run(int cycle);

/* 保留されている割り込み要求を調べ、実行可能ならば実行します。
 * [note]
 *	- アプリケーションプログラムが明示的に割り込みを要求する方法は、
 *	  $FF0F(IE)の適切なビットをSetしたあと、この関数を呼び出してください。
 *	  <例>
 *		dmgmem[0xff0f] |= DMGCPU_V_BLANK;
 *		dmgcpu_check_pending();
 *
 *	- $FF0F(IF)、および$FFFF(IE)の変更時に、この関数が呼び出されるよう、
 *	  外部定義のWrite関数を実装してください。
 *	  <例>
 *		void my_write(int addr, int data) {
 *			switch(addr) {
 *			case 0xff0f: // (IF)
 *			case 0xffff: // (IE)
 *				if(dmgmem[addr] != data) {	//この行は無くても構いません
 *					dmgmem[addr] = data;
 *					dmgcpu_check_pending();
 *				}				//この行は無くても構いません
 *				return;
 *			}
 *			dmgmem_write(addr, data);
 *		}
 */
void dmgcpu_check_pending();

/* DMG-CPUのレジスタセット表現にて、各レジスタの値を設定します。
 * [in]
 *	reg		設定するレジスタ値の構造体。
 */
void dmgreg_put(const DMGREG* reg);

/* DMG-CPUのレジスタセット表現にて、各レジスタの値を取得します。
 * [in]
 *	reg		レジスタ値を取得する構造体。
 */
void dmgreg_get(DMGREG* reg);

/****************************************************************************
 *	外部定義のメモリRead/Write関数の作り方について
 ****************************************************************************/
#if 0

外部定義のメモリRead/Write関数の作り方は、MOS6502エミュレーション(高速版)と基本的に同じです。
clip65c.hの「外部定義のメモリRead/Write関数の作り方について」を参照してください。
以下、呼び出し規約と、実装例を示します。

/*--------------------------------------------------------------------------*/

外部定義のメモリRead関数の呼び出し規約は、次の通りです。

	[in]
		%r0		読み出すアドレス ($0000..$FFFF)
	[out]
		%r1		読み出したデータ ($00-$FF)
				★★★重要★★★
				$00-$FF以外の値を返してはいけません!!
				たとえば、%r1=$FF を返すつもりで $r1=-1($FFFFFFFF)を返すと、誤動作の原因になります。
				高速化のために、メモリWrite関数から返された値を符号無し1byteにマスクせず、そのまま使っているからです。
	[use]
		%r0		破壊不可	渡されたアドレス値をそのまま維持してください。
		%r1		戻り値		戻り値を格納するまでは、ワークレジスタとして使用可能です。
		%r2		破壊不可
		%r3		破壊不可
		%r4		破壊不可
		%r5		破壊不可
		%r6		破壊不可
		%r7		破壊不可
		%r8		破壊不可	S1C33規約のベースレジスタです。
		%r9		破壊可		ワークレジスタとして使用可能です。ext33のスクラッチレジスタであることに注意してください。
		%r10		破壊不可
		%r11		破壊不可
		%r12		破壊可		ワークレジスタとして使用可能です。
		%r13		破壊可		ワークレジスタとして使用可能です。
		%r14		破壊可		ワークレジスタとして使用可能です。
		%r15		破壊可		ワークレジスタとして使用可能です。

/*--------------------------------------------------------------------------*/

外部定義のメモリWrite関数の呼び出し規約は、次の通りです。

	[in]
		%r0		書き込むアドレス ($0000..$FFFF)
		%r1		書き込むデータ   ($00-$FF)
	[use]
		%r0		破壊不可	渡されたアドレス値をそのまま維持してください。
		%r1		破壊不可	渡されたデータ値をそのまま維持してください。
		%r2		破壊不可
		%r3		破壊不可
		%r4		破壊不可
		%r5		破壊不可
		%r6		破壊不可
		%r7		破壊不可
		%r8		破壊不可	S1C33規約のベースレジスタです。
		%r9		破壊可		ワークレジスタとして使用可能です。ext33のスクラッチレジスタであることに注意してください。
		%r10		破壊不可
		%r11		破壊不可
		%r12		破壊可		ワークレジスタとして使用可能です。
		%r13		破壊可		ワークレジスタとして使用可能です。
		%r14		破壊可		ワークレジスタとして使用可能です。
		%r15		破壊可		ワークレジスタとして使用可能です。

/*--------------------------------------------------------------------------*/

asm("
	.code
	.align	1
	.global	my_read
my_read:
	xcmp	%r0, 0xff00		; if(addr < 0xff00)
	xjrlt	dmgmem_read		;   read RAM
	;-----------------------------------------------------------------------ここまでは、%r1,9,12,13,14,15以外破壊不可
	xcall	dmgcpu_leave		; 実行コンテキストを抜けます。
	xcall.d	my_read_io		; read I/O
	ld.w	%r12, %r0		; %r12(C言語の第1引数) <- %addr *delay*
	xjp.d	dmgcpu_enter		; 実行コンテキストへ入ります。
	ld.w	%r1, %r10		; %r10(C言語の戻り値 ) -> %data *delay*
");

int
my_read_io(int addr)
{
	int data;
	if(addr >= 0xff10 && addr <= 0xff26) {
		data = sound_read(addr);
	} else if(addr >= 0xff40 && addr <= 0xff4b) {
		data = video_read(addr);
	} else {
		/* I/O未接続領域なので、$FFを返すことにします。
		 * "data = -1" としてはいけません!! 戻り値は必ず$00..$FFである必要があります。
		 * (戻り値を一次格納するためのdata変数を、unsigned charにしておくのも手ですね)
		 */
		data = 0xff;
	}
	return data;
}

/*--------------------------------------------------------------------------*/

asm("
	.code
	.align	1
	.global	my_write
my_write:
	xcmp	%r0, 0xff00		; if(addr < 0xff00)
	xjrlt	dmgmem_write		;   write RAM
	;-----------------------------------------------------------------------ここまでは、%r9,12,13,14,15以外破壊不可
	xcall.d	dmgcpu_leave		; 実行コンテキストを抜けます。
	ld.w	%r12, %r0		; %r12(C言語の第1引数) <- %addr *delay*
	xcall.d	famicom_write_io	; write I/O
	ld.w	%r13, %r1		; %r13(C言語の第2引数) <- %data *delay*
	xjp	dmgcpu_enter		; 実行コンテキストへ入ります。
");

void
my_write_io(int addr, int data)
{
	if(addr >= 0xff10 && addr <= 0xff26) {
		sound_write();
	} else if(addr >= 0xff40 && addr <= 0xff4b) {
		video_write();
	} else {
		/** no job **/
	}
}

/*--------------------------------------------------------------------------*/

以上のように定義したメモリRead/Write関数を、CPUリセット時に指定してください。

	dmgcpu_reset(my_read, my_write, 0or1);

#endif
/****************************************************************************
 *	
 ****************************************************************************/

#ifdef  __cplusplus
}//extern "C"
#endif//__cplusplus

#endif /*__CLIP_DMC_H__*/
