/*	
 *	clip65c.h
 *
 *	P/ECE MOS6502 Emulator (高速版)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Fri Jan 28 03:55:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 *	* Sat Mar 12 20:02:00 JST 2005 Naoyuki Sawa
 *	- M6502CPU.spに、&mem[0x100|SP]を保持するのでなく、SPをそのまま保持するよう変更しました。
 *	  ファミコン用ゲームソフトでは、SPのWraparoundに依存するプログラムはほとんどないので、
 *	  高速化のために、M6502CPU.spに&mem[0x100|SP]を保持するよう設計していたのですが、
 *	  ATARI VCS用の多くのゲームソフトが、初期化時にSPのWraparoundに依存しています。
 *	  仕方がないので、前述のように仕様変更しました。
 *	  より正確なエミュレーションのための変更なので、これまで動作していたファミコンゲームが
 *	  動作しなくなることは考えられませんが、動作速度が僅かに遅くなる可能性はあります。
 */
#ifndef __CLIP_65C_H__
#define __CLIP_65C_H__

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

/* 実行用のM6502 CPU構造体です。
 *
 * [note]
 *	* nzフィールドは、PSR(N,Z)を表します。
 *	- nz[7:0]が0ならばPSR(Z)=1、さもなくばPSR(Z)=0です。
 *	- nz[15]またはnz[7]が1ならばPSR(N)=1、さもなくばPSR(N)=0です。
 *	* このような仕様にした理由は、BIT命令のフラグ変化に対応するためです。
 *	- M6502 CPUのほとんどの命令は、命令の実行結果をPSR(N,Z)に反映します。
 *	  この処理を高速化するためには、PSR(N,Z)だけを表す1byteフィールドを用意し、
 *	  命令の実行結果をそのフィールドに格納するようにすればいいのです。
 *	  1byteフィールド全体が0か否かでPSR(Z)を判定し、bit7でPSR(N)を判定します。
 *	  この方法では、PSR(Z)=1かつPSR(N)=1、という状態を表すことはできません。
 *	  1byteフィールド全体を0にしたまま、bit7を1にすることはできないからです。
 *	- ほとんどの命令はそれでも問題ないのですが、BIT命令だけが問題になります。
 *	  BIT命令はAレジスタとメモリ値のAndを取った結果をPSR(Z)に反映し、
 *	  メモリ値そのままの値をPSR(N)に反映します。
 *	  たとえばAレジスタの値が$00でメモリ値が$FFだったりすると、
 *	  PSR(Z)=1かつPSR(N)=1という結果が有り得るのです。
 *	  BIT命令に対応するには、PSR(N,Z)を表す1byteフィールドだけでは不充分です。
 *	  しかしながらBIT命令だけのために、その他のほとんどの命令のPSR(N,Z)処理が
 *	  遅くなるのは避けたいところです。
 *	- そこで、2byteフィールドを使う、現在の方法を考えました。
 *	  bit7:0が0か否かでPSR(Z)を判断するのは、前出の方法と同じです。
 *	  また、bit7が0か否かでPSR(N)を判断するのも、前出の方法と同じです。
 *	  それに加えて、bit7が0であっても、bit15が1ならばPSR(N)=1と判断します。
 *	  PSR(Z)=1かつPSR(N)=1を表現するには、$8000(bit14:8は任意)で表現できます。
 *	  たとえばAレジスタの値が$00でメモリ値が$FFでBIT命令を実行した場合は、
 *	  2byteフィールドの値は$FF00となり、PSR(Z)=1、PSR(N)=1を意味します。
 *	- bit15によるPSR(N)=1表現を使うのは、BIT命令と、1byte PSR値のunpack時のみです。
 *	  それ以外の場合は、bit7:0だけを使い、bit15:8をクリアします。
 *	  1byteフィールドを使う場合に較べて、bit15:8をクリアする手順の追加は不要です。
 *	  命令の実行結果の値は符号無し1byte値になっているはずだから、
 *	  単に転送するだけでbit15:8をクリアできるからです。
 *		ld.w %A, %data
 *		ret.d
 *		ld.w %NZ, %A		; bit7:0にPSR(N,Z)を設定しつつ、bit15:8をクリア
 *	  %NZを1byteフィールドとしていた場合と、全く同じ手数で済みます。
 *	  結果が符号無し1byteでない場合は、ld.ubを使えば1命令でbit15:8をクリアできます。
 *		ld.w [%CPU+X], %data
 *		ld.ub %NZ, %data	; bit7:0にPSR(N,Z)を設定しつつ、bit15:8をクリア
 *		ret.d
 *	  ld.ubは遅延スロットに入れられないので、ld.wに較べてわずかに遅くなります。
 *	  ↓(2005/03/12追記)
 *	  マニュアルによると不可ですが、実験によるとld.ubも遅延スロットに入れられます。
 */
typedef struct _M6502CPU {
	unsigned short ca;		/* + 0,2 -------C AAAAAAAA */
	unsigned short nz;		/* + 2,2 N------- NZZZZZZZ */
	unsigned char bdi;		/* + 4,1          001BDI00 */
	unsigned char v;		/* + 5,1          V------- */
	unsigned char x;		/* + 6,1          XXXXXXXX */
	unsigned char y;		/* + 7,1          YYYYYYYY */
	unsigned char* pc;		/* + 8,4 &mem[PC]          */
	unsigned char sp;		/* +12,1          SPSPSPSP */
	//				/* +13,3 (パディング)      */
	int flags;			/* +16,4          ------IW */
	int cycle;			/* +20,4 残りCycle         */
	void* read;			/* +24,4 メモリ読み出しルーチン */
	void* write;			/* +28,4 メモリ書き込みルーチン */
} M6502CPU;				/* =32   */

/* M6502CPU.flags */
#define M6502CPU_WAI	(1 << 0)	/* WAI状態 0=通常,1=停止 */
#define M6502CPU_IRQ	(1 << 1)	/* IRQ保留 0=無し,1=有り */

/* レジスタ設定・参照用の構造体です。
 * [note]
 *	* M6502CPU構造体は、6502のレジスタセットそのままの構造ではないので、
 *	  プログラムからレジスタを設定・取得する場合に、やや扱いづらいです。
 *	  そのままの値で設定・取得できるよう、M6502REG構造体を用意しました。
 *	  m6502reg_put()を使って、レジスタ値を設定します。
 *	  m6502reg_get()を使って、レジスタ値を取得します。
 */
typedef struct _M6502REG {
	unsigned char  a;		/* +0, 1 */
	unsigned char  x;		/* +1, 1 */
	unsigned char  y;		/* +2, 1 */
	/* ( padding ) */		/* +3, 1 */
	unsigned short pc;		/* +4, 2 */
	unsigned char  sp;		/* +6, 1 */
	unsigned char  psr;		/* +7, 1 */
} M6502REG;				/* =8 */

extern M6502CPU m6502cpu;		/* 高速RAMに配置 */
extern unsigned char m6502mem[0x10000];	/*    SRAMに配置 */

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

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

/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/

/* CPUをリセットします。
 * [in]
 *	read		メモリRead 外部関数 (NULL可)
 *	write		メモリWrite外部関数 (NULL可)
 * [note]
 *	* メモリRead外部関数にNULLを指定すると、m6502mem_read()が設定されます。
 *	  m6502mem_read()は、単純にm6502mem[]を読み出すだけのメモリRead関数です。
 *	* メモリWrite外部関数にNULLを指定すると、m6502mem_write()が設定されます。
 *	  m6502mem_write()は、単純にm6502mem[]へ書き込むだけのメモリWrite関数です。
 *	* 外部定義のメモリRead/Write関数の作り方は、下の方を参照してください。
 */
void m6502cpu_reset(void* read, void* write);

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

/* IRQを要求します。
 * [note]
 *	* 割り込みが許可されていた場合(PSR(I)=0)は、直ちにIRQを発行します。
 *	* 割り込みが禁止されていた場合(PSR(I)=1)は、IRQ要求は保留されます。
 *	  そして、割り込みが許可されたとき(PSR(I)=0)に、IRQを発行します。
 */
void m6502cpu_irq();

/* NMIを要求します。
 *	* NMIは、常に直ちに発行されます。
 */
void m6502cpu_nmi();

/* 6502レジスタセット表現にて、各レジスタの値を設定します。
 * [in]
 *	reg		設定するレジスタ値の構造体。
 * [note]
 *	* M6502REG構造体のコメントを参照してください。
 */
void m6502reg_put(const M6502REG* reg);

/* 6502レジスタセット表現にて、各レジスタの値を取得します。
 * [in]
 *	reg		レジスタ値を取得する構造体。
 * [note]
 *	* M6502REG構造体のコメントを参照してください。
 */
void m6502reg_get(M6502REG* reg);

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

外部定義のメモリRead/Writeの呼び出し規約は、S1C33のC言語呼び出し規約と互換ではありません。
外部定義のメモリRead/Writeを作るには、少なくとも一部をアセンブラで記述する必要があります。
# このような仕様になった理由は、高速化のためです。
# C言語呼び出し規約に従うと、レジスタの退避・復帰回数が増えて、性能が大きく低下します。

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

外部定義のメモリ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		破壊不可

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

m6502cpu_enterを呼んで実行コンテキストから抜けるとき、レジスタ%r4..%r15のうち必要な値はm6502cpu構造体に保存されます。
従って、m6502cpu_enterを呼んだあとは、%r4..%r15は通常のC言語呼び出し規約と同様に、破壊して構いません。(%r8は除く)

m6502cpu_leaveを呼んで実行コンテキストへ入るとき、レジスタ%r4..%r15はm6502cpu構造体から適切にセットアップされます。
m6502cpu_leaveを呼んだあとは、外部定義のメモリRead/Write関数の仕様に従って、誤ったレジスタを破壊しないよう気をつけてください。

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

メモリRead/Write関数を外部定義する場合、すべてをアセンブラで記述しても構いませんが、
処理速度と実装の簡単さを考えると、アセンブラとC言語の折衷で記述するのが良いと思います。

メモリRead/Write関数は、6502エミュレーションルーチンの実行コンテキスト内から呼ばれます。
実行コンテキスト内では、Cycle値などがレジスタに保持されているため、
m6502cpu.cycleなどの値を書き換えても、6502エミュレーションルーチンに反映されません。
一般に、外部I/OのRead/Writeでは、ウェイト調整のためにCycleを減らす必要があると思います。
そこで、外部I/Oアクセスを処理するC言語ルーチンへ分岐する前に、実行コンテキストを抜けてください。
C言語ルーチンが処理を返したら、再び実行コンテキストへ入ってください。
詳しくは、以下の例を参照してください。

以下に、メモリRead/Write関数の外部定義例を示します。
次のようなメモリマップ仕様を想定した例です。

	$0000..$1FFF:	RAM
	$2000..$7FFF:	I/Oレジスタ領域
			$2000..$3FFF: Video
			$4000..$5FFF: Audio
			$6000..$7FFF: 未使用
	$8000..$FFFF:	ROM

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

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

int
my_read_io(int addr)
{
	int data
	if(addr <= 0x3fff) {
		data = video_read(addr);
	} else if(addr <= 0x5fff) {
		data = audio_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, 0x2000		; if(addr < 0x2000)
	xjrlt	m6502mem_write		;   write RAM
	;-----------------------------------------------------------------------ここまでは、%r9,12,13以外破壊不可
	xcall.d	m6502cpu_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	m6502cpu_enter		; 実行コンテキストへ入ります。
");

void
my_write_io(int addr, int data)
{
	if(addr <= 0x3fff) {
		video_write(addr, data);
	} else if(addr <= 0x5fff) {
		audio_write(addr, data);
	} else {
		/** no job **/
	}
}

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

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

	m6502cpu_reset(my_read, my_write);

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

#endif /*__CLIP_65C_H__*/
