/*	
 *	clipcp8.h
 *
 *	P/ECE CHIP-8 Interpreter
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Thu Mar 18 21:17:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#ifndef __CLIP_CP8_H__
#define __CLIP_CP8_H__

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

/* このシンボルを定義すると、全ての命令実行をトレース出力します。
 * このシンボルを定義しなければ、各命令実行はトレース出力しません。
 * 全ての命令実行をトレースするととても遅くなるので、通常は定義しないでください。
 * なお、このシンボルを定義しても、全体のトレースON指示“TRACE_ON=1”がなければ、
 * トレース出力は行われません。
 */
//#define CHIP8_TRACE

/********** これ以降は書き換えないでください **********/

#ifdef CHIP8_TRACE
#define CHIP8TRACE	TRACE
#else /*CHIP8_TRACE*/
#define CHIP8TRACE	1 ? ((void)0) : TRACE
#endif /*CHIP8_TRACE*/

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

/*
 *	    RCA COSMAC VIP FrontView    
 *	┏━━━━━━━━━━━━━━┓
 *	┃　　　　　　　　　　　　　　┃
 *	┃─　　　　　　　　　　　　　┃
 *	┃─　　　　　　　　 ＲＣＡ 　┃
 *	┃─　　　　　┌┐┏━━━━┓┃
 *	┃─　　　　　││┃１２３Ｃ┃┃
 *	┃─　　　　　││┃４５６Ｄ┃┃
 *	┃─　　　　　││┃７８９Ｅ┃┃
 *	┃─　　　　　││┃Ａ０ＢＦ┃┃
 *	┃─　　　　　└┘┗━━━━┛┃
 *	┗━━━━━━━━━━━━━━┛
 */

#define CHIP8_MEM_SIZE		(1 << 12)		/* メインメモリサイズ */
#define CHIP8_MEM_MASK		(CHIP8_MEM_SIZE - 1)	/* メインメモリアドレスマスク */

/* フォントデータ配置アドレスは、CHIP-8の仕様に規定されていません。
 * フォントデータは、0x000〜0x1ffの範囲で、任意のアドレスに配置可能です。
 * 配置アドレスを知っているのは、「FX29: LD LF,VX」「FX30: LD HF,VX」命令だけです。
 */
#define CHIP8_FONT4x5_ADDRESS	0x100			/* 0x100〜0x14f: 4x5ピクセルフォント転送アドレス  ( 5*16= 80バイト) */
#define CHIP8_FONT8x10_ADDRESS	0x150			/* 0x150〜0x1ef: 8x10ピクセルフォント転送アドレス (10*16=160バイト) */

#define CHIP8_RESET_VECTOR	0x200			/* リセットベクタ */

#define CHIP8_PROGRAM_ADDRESS	CHIP8_RESET_VECTOR			/* プログラムロードアドレス */
#define CHIP8_PROGRAM_SIZE	(CHIP8_MEM_SIZE - CHIP8_PROGRAM_ADDRESS)/* プログラム最大サイズ */

/* CHIP8.mode=0(CHIP-8互換グラフィックモード) */
#define CHIP8_SCRLO_W		64			/* 横ピクセル数 */
#define CHIP8_SCRLO_X_MASK	(CHIP8_SCRLO_W - 1)	/* 横座標マスク */
#define CHIP8_SCRLO_H		32			/* 縦ピクセル数 */
#define CHIP8_SCRLO_Y_MASK	(CHIP8_SCRLO_H - 1)	/* 縦座標マスク */

/* CHIP8.mode=1(SCHIP拡張グラフィックモード) */
#define CHIP8_SCRHI_W		128			/* 横ピクセル数 */
#define CHIP8_SCRHI_X_MASK	(CHIP8_SCRHI_W - 1)	/* 横座標マスク */
#define CHIP8_SCRHI_H		64			/* 縦ピクセル数 */
#define CHIP8_SCRHI_Y_MASK	(CHIP8_SCRHI_H - 1)	/* 縦座標マスク */

#define CHIP8_SCR_W		CHIP8_SCRHI_W		/* 画面バッファ横ピクセル数 */
#define CHIP8_SCR_H		CHIP8_SCRHI_H		/* 画面バッファ縦ピクセル数 */
#define CHIP8_SCR_SIZE		(CHIP8_SCR_W * CHIP8_SCR_H)	/* 画面バッファサイズ */

/*
 *	CHIP-8コア
 */
typedef struct _CHIP8 {
	/*===== レジスタ =====*/
	unsigned char v[16];				/* +0x0000,0x0010: データレジスタ  : V0〜VF */
	unsigned short i;				/* +0x0010,0x0002: アドレスレジスタ: I */
	unsigned char dt;				/* +0x0012,0x0001: ディレイタイマ */
	unsigned char st;				/* +0x0013,0x0001: サウンドタイマ */
	/*===== 内部状態 =====*/
	unsigned char stat;				/* +0x0014,0x0001: 各種フラグ (CHIP8_*) */
	unsigned char s;				/* +0x0015,0x0001: スタックポインタ */
	unsigned short p;				/* +0x0016,0x0002: プログラムカウンタ */
	unsigned short key;				/* +0x0018,0x0002: キー状態 [0]:key0〜[F]:keyF */
	unsigned short seed;				/* +0x001a,0x0002: 乱数シード */
	unsigned char _resv1[4];			/* +0x001c,0x0004: (予約) */
	unsigned short stack[16];			/* +0x0020,0x0020: スタック */
	/*===== メモリ =====*/
	unsigned char mem[CHIP8_MEM_SIZE];		/* +0x0040,0x1000: メインメモリ */
	unsigned char scr[CHIP8_SCR_H][CHIP8_SCR_W];	/* +0x1040,0x2000: 画面バッファ */
} CHIP8;						/* =0x3040 */
#define CHIP8_SIZE					    0x3040

/* CHIP8.stat */
#define CHIP8_SCHIP	(1 << 0)	/* 0:CHIP-8mode(64x32) / 1:SCHIPmode(128x64) */
#define CHIP8_EXIT	(1 << 1)	/* EXIT命令が実行されたら1になります */
#define CHIP8_ILL	(1 << 2)	/* 不正命令が実行されたら1になります */

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

/*
 *	インストラクション関数インデクス生成
 */
#define CHIP8FN_(CODE, DESC)	CHIP8OP_##CODE,
enum {
#include "chip8/list.h"
};
#undef CHIP8FN_

/*
 *	インストラクション関数宣言/定義
 */
#define CHIP8FN_(CODE, DESC)	void chip8op_##CODE(CHIP8* chip8, unsigned short code);	/* 宣言用 */
#include "chip8/list.h"
#undef CHIP8FN_
#define CHIP8FN_(CODE, DESC)	void chip8op_##CODE(CHIP8* chip8, unsigned short code)	/* 定義用 */

/*
 *	インストラクション関数/名称テーブル
 */
typedef void (*CHIP8OP)(CHIP8* chip8, unsigned short code);
extern const CHIP8OP chip8_op_table[/*CHIP8OP_????*/];
extern const char* const chip8_op_name_table[/*CHIP8OP_????*/];

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

/* CHIP-8をリセットします。
 * [in]
 *	chip8		CHIP8構造体。
 * [note]
 *	* chip8_reset()関数は、以下の処理を行います。
 *	- プログラムカウンタ以外のレジスタを全てゼロクリアします。
 *	- メインメモリと画面バッファをゼロクリアします。
 *	- プログラムカウンタを0x200番地に初期化します。
 *	* アプリケーションプログラムの処理は、次の手順に従ってください。
 *	- chip8_reset()を呼びます。
 *	- CHIP8構造体のメインメモリの0x200番地〜にプログラムをロードします。
 *	- 必要に応じて、chip8_run()、chip8_tick()を呼びます。
 *	  また、適切なタイミングで、画面バッファを実画面に反映させます。
 */
void chip8_reset(CHIP8* chip8);

/* CHIP-8を実行します。
 * [in]
 *	chip8		CHIP8構造体。
 *	cycle		実行サイクル数。
 * [note]
 *	* CHIP-8命令は、全て1命令1サイクルと仮定します。
 *	  すなわち、cycle引数に指定した数の命令を実行します。
 *	* EXIT命令が実行されたら、CHIP8.stat[CHIP8_EXIT]を1にセットし、すぐに処理を返します。
 *	  その後、CHIP8.stat[CHIP8_EXIT]=1の状態でchip8_run()を呼んでも、何もせずに処理を返します。
 *	  処理を再開するには、CHIP8.stat[CHIP8_EXIT]を0にクリアしてからchip8_run()を呼んでください。
 *	* 不正命令が実行されたら、CHIP8.stat[CHIP8_ILL]を1にセットし、すぐに処理を返します。
 *	  その後、CHIP8.stat[CHIP8_ILL]=1の状態でchip8_run()を呼んでも、何もせずに処理を返します。
 *	  処理を再開するには、CHIP8.stat[CHIP8_ILL]を0にクリアしてからchip8_run()を呼んでください。
 */
void chip8_run(CHIP8* chip8, int cycle);

/* CHIP-8の内蔵タイマをカウントダウンします。
 * [in]
 *	chip8		CHIP8構造体。
 *	count		カウントダウン数。
 * [note]
 *	* アプリケーションプログラムから、毎秒(60/count)回の頻度でこの関数を呼び出してください。
 *	  CHIP-8のディレイタイマとサウンドタイマは、この関数によってのみカウントダウンされます。
 */
void chip8_tick(CHIP8* chip8, int count);

/* 標準エラー出力に、レジスタダンプを行います。
 * [in]
 *	chip8		CHIP8構造体。
 */
void chip8_dump(CHIP8* chip8);

/* CHIP-8プログラムファイルをロードします。
 * [in]
 *	chip8		CHIP8構造体。
 *	fname		ファイル名。
 * [out]
 *	戻り値		成功ならば、0を返します。
 *			失敗ならば、負の値を返します。
 * [note]
 *	* chip8_load()関数の成功条件は、次の通りです。
 *	- ファイルが開けること。
 *	- ファイルサイズが2バイト(=1命令)以上であること。
 *	- ファイルサイズがCHIP8_PROGRAM_SIZEバイト以下であること。
 *	- ファイルが正しく読み込めること。
 *	* ファイル内容が正しいCHIP-8プログラムか否かはテストしません。
 *	  ファイル内容のテストが必要な場合は、chip8_test()関数を使ってください。
 */
int chip8_load(CHIP8* chip8, const char* fname);

/* 指定されたファイルが、有効なCHIP-8プログラムかどうか調べます。
 * [in]
 *	fname		ファイル名。
 *	cycle		テストするサイクル数。
 *			大きな値を指定するほど、正確な結果が得られます。
 *			0を指定すると、内部既定値となります。
 * [out]
 *	戻り値		有効なCHIP-8プログラムならば、0を返します。
 *			さもなくば、負の値を返します。
 * [note]
 *	* chip8_test()関数の成功条件は、次の通りです。
 *	- chip8_load()関数の成功条件を全て満たしていること。
 *	- cycle個の命令を実行してみて、不正命令に出会わないこと。
 *	  (cycleに0を指定した場合の命令実行回数は、内部既定値を用います。)
 *	* 有効なCHIP-8プログラムでなくても、以上の条件を満たしてしまうと、
 *	  有効なCHIP-8プログラムと見なして、0を返してしまう場合があります。
 *	* cycle指定が大きいほど、テスト命令数が多くなり、正しい結果が期待できます。
 */
int chip8_test(const char* fname, int cycle);

#ifdef PIECE
/* CHIP8を実行します。(スタック切替・高速版)
 * [in]
 *	chip8		CHIP8構造体。
 *	cycle		実行サイクル数。
 * [note]
 *	* chip8_run()との違いは、この関数の実行中のみ、スタックを0x0e00〜0x1000の高速RAMに切り換えることです。
 *	- 0x0e00〜0x1000は、P/ECEカーネルが同期的に高速RAMルーチンを転送して使用するエリアです。
 *	  例えば、pceLCDTrans()やpceFileWriteSct()等が、このエリアに高速RAMルーチンを転送します。
 *	  同期的に使用されるため、これらのAPIによって0x0e00〜0x1000が破壊されることを留意しておけば、
 *	  アプリケーションプログラムがこの領域を使うことも可能です。
 *	- chip8_run()の実装は、call使用回数が非常に多いため、スタックを高速RAMに移すことには意味があります。
 *	  chip8_run2()はスタックを0x1000に移した後、chip8_run()を呼び出し、スタックを元に戻して処理を返します。
 *	- スタックエリアは0x0e00〜0x1000の512バイトしかないので、chip8_run2()の使用には条件があります。
 *	  ・chip8_run()とそこから呼ばれる命令処理関数は、大きなスタックフレームを確保してはいけません。
 *	    各命令処理の実装を変更する場合は、注意してください。
 *	  ・chip8_run()から呼ばれる外部関数(READ/WRITE)では、大きなスタックフレームを確保してはいけません。
 *	  ・スタックをたくさん消費するような割り込みルーチンとの併用は不可です。
 *	    スタックが0x0e00〜0x1000に切り替わっているときに割り込みが発生すると、スタックがあふれる恐れがあるからです。
 *	  ・chip8_run2()を割り込みルーチン内で呼び出してはいけません。
 *	    前面で、pceLCDTrans()が0x0e00〜0x1000エリアを使用中である可能性があるからです。
 *	- 以上のような制約があるので、chip8_run2()の使用には充分な注意が必要です。
 *	  chip8_run()を使った場合に較べて、chip8_run2()を使った場合の効果は、約20%の高速化となります。
 */
void chip8_run2(CHIP8* chip8, int cycle);
#endif /*PIECE*/

#endif /*__CLIP_CP8_H__*/
