File:  [Isaki's NoNo m68k/m88k emulator] / nono / exp / fmovemc.php
Revision 1.1.1.4 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:36 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, v024, v023, v022, HEAD
nono 1.2.0

;
; nono
; Copyright (C) 2020 nono project
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions
; are met:
; 1. Redistributions of source code must retain the above copyright
;    notice, this list of conditions and the following disclaimer.
; 2. Redistributions in binary form must reproduce the above copyright
;    notice, this list of conditions and the following disclaimer in the
;    documentation and/or other materials provided with the distribution.
;
; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
; BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
; AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
; SUCH DAMAGE.

; How to compile:
;  > has fmovemc.has
;  > hlk fmovemc.o si_util.o

	.xref	hexstr_long
	.include doscall.mac
	.include iocscall.mac
	.list
	.cpu	68030

PRINT	.macro	msg
	pea.l	msg(pc)
	DOS	_PRINT
	addq.l	#4,sp
	.endm

	.text
	.even
start:
	bra	start2

<?php
	// レジスタ割り当て
	// d0/d1	work
	// d7	1=検査対象命令でFライン例外がおきた
	//      その後は ok を表示するかどうかのカウント
	// a0/a1	work
	// a3	検査対象命令の次の位置 (Fライン例外から戻るのに使用)
	// a4	元のFライン例外ハンドラ置き場
?>
<?php
	// regs がターゲットのFPcr。
	// C..FPCR
	// S..FPSR
	// I..FPIAR の3ビット。
	// 右辺がそのターゲットの時に使える EA。レジスタ相対は省略でいい?
	//  d..Dn
	//  a..An
	//  m..(An)
	//  +..(An)+
	//  -..-(An)
	//  i..#imm

	// FMOVEM EA,regs
	$table = array(
		//regs	   EA
		"___"	=> "dam+-i",
		"__I"	=> "dam+-i",
		"_S_"	=> "d.m+-i",
		"_SI"	=> "..m+-i",
		"C__"	=> "d.m+-i",
		"C_I"	=> "..m+-i",
		"CS_"	=> "..m+-i",
		"CSI"	=> "..m+-i",
	);
	foreach ($table as $regs => $eastr) {
		$ealist = preg_split("//", $eastr, -1, PREG_SPLIT_NO_EMPTY);
		for ($id = 0; $id < 6; $id++) {
			$ea = $eastr[$id];
			if (0) printf("regs='{$regs}' id=${id} ea={$ea}\n");

			if ($ea == ".") {
				// 許されない組み合わせ。例外が出ることを確認
				subr_fail("to", $regs, $id);
			} else {
				// アクセス可能な場合のテスト
				subr_success_to($regs, $id);
			}
		}
	}

	// FMOVEM regs,EA
	foreach ($table as $regs => $eastr_src) {
		// EA が dst になる時は #imm は無効
		$eastr = preg_replace("/i/", ".", $eastr_src);

		$ealist = preg_split("//", $eastr, -1, PREG_SPLIT_NO_EMPTY);
		for ($id = 0; $id < 6; $id++) {
			$ea = $eastr[$id];
			if (0) printf("regs='{$regs}' id=${id} ea={$ea}\n");

			if ($ea == ".") {
				// 許されない組み合わせ。例外が出ることを確認
				subr_fail("from", $regs, $id);
			} else {
				// アクセス可能な場合のテスト
				subr_success_from($regs, $id);
			}
		}
	}
?>
<?php
// 1行を出力する。引数は printf のように fmt... で指定可能。
// 改行はこちらで付加する。
function out()
{
	$args = func_get_args();
	$fmt = array_shift($args);
	print vsprintf($fmt, $args);
	print "\n";
}

// regs の省略記号を数値形式に
function reg2num($str)
{
	$n = 0;
	if (preg_match("/I/", $str))
		$n += 1;
	if (preg_match("/S/", $str))
		$n += 2;
	if (preg_match("/C/", $str))
		$n += 4;
	return $n;
}

// regs 数値形式をニーモニックに
function regnum2mnemonic($regnum)
{
	if ($regnum == 0)
		return "nofpc";

	$list = array();
	if ($regnum & 4)
		$list[] = "fpcr";
	if ($regnum & 2)
		$list[] = "fpsr";
	if ($regnum & 1)
		$list[] = "fpiar";
	return implode("/", $list);
}

// regs 数値形式から転送レジスタ数に
function regnum2count($regnum)
{
	$counts = array(0, 1, 1, 2, 1, 2, 2, 3);
	return $counts[$regnum];
}

// regs 数値形式からマスクを返す。
// regs は1ビットだけ立っていること。
// %100=FPCR, %010=FPSR, %001=FPIAR
function regmask($regnum)
{
	$masks = array(
		/*0*/	0,
		/*1*/	0xffffffff,	// FPIAR
		/*2*/	0x0ffffff8,	// FPSR
		/*3*/	0,
		/*4*/	0x0000fff0,	// FPCR
	);
	return $masks[$regnum];
}

// EA 番号から命令ワードに。n はすべて0
function eanum2word($id)
{
	switch ($id) {
	 case 0:	// D0
		return 0 << 3;
	 case 1:	// A0
		return 1 << 3;
	 case 2:	// (A0)
		return 2 << 3;
	 case 3:	// (A0)+
		return 3 << 3;
	 case 4:	// -(An)
		return 4 << 3;
	 case 5:	// #imm
		return 0x3c;
	}
}

// EA 番号をラベル文字列に。
function eanum2label($id)
{
	$idstr = array(
		0 => "dn",
		1 => "an",
		2 => "anin",
		3 => "anpi",
		4 => "anpd",
		5 => "imm",
	);
	return $idstr[$id];
}

// EA 番号をニーモニックに。
function eanum2mnem($id, $n = 0)
{
	switch ($id) {
	 case 0:	return "d{$n}";
	 case 1:	return "a{$n}";
	 case 2:	return "(a{$n})";
	 case 3:	return "(a{$n})+";
	 case 4:	return "-(a{$n})";
	 case 5:	return "#imm";
	}
	return "";
}

function addfunc($name, $dir, $mnem, $cpureg)
{
	global $funclist;

	if ($cpureg == "#imm") {
		// $cpureg の #imm は常に1つだが
		// ニーモニック表記では対向と数を合わせるか。
		$cpureg = preg_replace(',([^/]+),', "#imm", $mnem);
	}
	$text = "fmovem.l ";
	if ($dir == "to") {
		$text .= "{$cpureg},{$mnem}";
	} else {
		$text .= "{$mnem},{$cpureg}";
	}

	$funclist[$name] = $text;
	out("test_{$name}:");
}

// 結果表示のサブルーチンコールを出力
function out_result($msg, $exp, $act)
{
	out("	pea.l	{$msg}(pc)");
	out("	move.l	{$exp},-(sp)");
	out("	move.l	{$act},-(sp)");
	out("	bsr	result");
	out("	lea.l	12(sp),sp");
}

// 許されない組み合わせのテスト。hard の時だけ検査する。
// Fライン例外が起きることをチェックする。
function subr_fail($dir, $regs, $id)
{
	$cpureg = eanum2mnem($id);
	$regnum = reg2num($regs);
	$mnem = regnum2mnemonic($regnum);
	$label = eanum2label($id);

	$funcname = ($dir == "to")
		? "fmovem_{$label}_to_{$regs}"
		: "fmovem_{$regs}_to_{$label}";
	addfunc($funcname, $dir, $mnem, $cpureg);

	$w1 = 0xf200 | eanum2word($id);
	$w2 = 0x8000 | ($regnum << 10);
	if ($dir == "from")
		$w2 |= 0x2000;

	out("	tst.b	is_hard(pc)");
	out("	bne		@f");
	out("	rts");
	out("@@:");
	out("	PRINT	msg_{$funcname}");
	out("	bsr	set_fline");
	out("	moveq.l	#0,d7");
	out("	lea.l	@f,a3");
	// アセンブルできない可能性があるので命令はバイナリを出力。
	out("	.dc.w	$%04x, $%04x", $w1, $w2);
	out("@@:");

	out("	bsr	restore_fline");
	// 例外が起きるはずなので D7 が非ゼロなら成功(次へ)
	out("	tst.l	d7");
	out("	bne	@f");
	out("	PRINT	msg_fail_notrap");
	out("	rts");
	out("@@:");
	out("	PRINT	msg_oktrap");
	out("	rts");
	out("");
}

// アクセス可能な場合のテスト。FMOVEM <ea>,<FPcr>
function subr_success_to($regs, $id)
{
	$cpureg = eanum2mnem($id);
	$regnum = reg2num($regs);
	$mnem = regnum2mnemonic($regnum);
	$label = eanum2label($id);

	$funcname = "fmovem_{$label}_to_{$regs}";
	addfunc($funcname, "to", $mnem, $cpureg);
	out("	PRINT	msg_{$funcname}");

	$w1 = 0xf200 | eanum2word($id);
	$w2 = 0x8000 | ($regnum << 10);
	// count は指定されたレジスタ数 (0..3)
	// txcount は転送するレジスタ数 (1..3)
	$count = regnum2count($regnum);
	$txcount = ($count == 0) ? 1 : $count;

	// FPcr を空にしておく
	out("	fmove.l	#0,fpcr");
	out("	fmove.l	#0,fpsr");
	out("	fmove.l	#0,fpiar");
	// 準備
	switch ($id) {
	 case 0:	// Dn (singleのみ)
		out("	move.l	#\$55555555,d0");
		break;
	 case 1:	// An (singleのみ)
		out("	move.l	#\$55555555,a0");
		break;
	 case 2:	// (An)
	 case 3:	// (An)+
	 case 4:	// -(An)
		out("	move.l	#\$55555555,workarea");
		if ($count >= 2)
			out("	move.l	#\$aaaaaaaa,workarea+4");
		if ($count == 3)
			out("	move.l	#\$33333333,workarea+8");

		if ($id == 4) {
			// 後ろ基準
			// nofpc でも転送は発生する
			out("	lea.l	workarea+%d(pc),a0", $txcount * 4);
		} else {
			// 前から
			out("	lea.l	workarea(pc),a0");
		}
		break;
	 case 5:	// #imm
		$cpureg = "#\$55555555";
		if ($count >= 2)
			$cpureg .= ",#\$aaaaaaaa";
		if ($count == 3)
			$cpureg .= ",#\$33333333";
		break;
	}

	out("	bsr	set_fline");
	out("	moveq.l	#0,d7");
	out("	lea.l	@f,a3");
	// 実行
	if ($mnem == "nofpc") {
		out("	.dc.w	$%04x, $%04x", $w1, $w2);
		// #imm,nofpc はアセンブル出来ないのでここで即値をおく
		if ($id == 5/*imm*/ && $count == 0) {
			out("	.dc.l \$55555555");
		}
	} else {
		out("	fmovem.l {$cpureg},{$mnem}");
	}
	out("@@:");

	out("	bsr	restore_fline");
	// 例外は起きないはずなので D7 はゼロなら成功(次へ)
	out("	tst.l	d7");
	out("	beq	@f");
	out("	PRINT	msg_fail_trap");
	out("	rts");
	out("@@:");

	// 検証
	$expected = array(
		0x55555555,
		0xaaaaaaaa,
		0x33333333,
	);
	$i = 0;

	// An の変化量が正しいか。
	// この後のレジスタの検証で A0 を使うのでその前に行う。
	// (An) なら workarea から変化しない
	// (An)+ なら workarea+count*4
	// -(An) なら workarea に戻ってくる (最初にオフセットつけてるので)
	switch ($id) {
	 case 2:	// (An)
	 case 4:	// -(An)
		out("	lea.l	workarea(pc),a1");
		out("	cmpa.l	a1,a0");
		out("	beq	@f");
		out_result("msg_fail_addr", "a1", "a0");
		out("@@:");
		break;
	 case 3:	// (An)+
		out("	lea.l workarea+%d(pc),a1", $txcount * 4);
		out("	cmpa.l	a1,a0");
		out("	beq	@f");
		out_result("msg_fail_addr", "a1", "a0");
		out("@@:");
		break;
	}

	// FPCR
	out("	fmove.l	fpcr,d0");
	if (($regnum & 4)) {
		out("	move.l	#$%08x,d1", $expected[$i] & regmask(4));
		$i++;
	} else {
		out("	moveq.l	#0,d1");
	}
	out("	cmp.l	d1,d0");
	out("	beq	@f");
	out_result("msg_fail_cr", "d1", "d0");
	out("@@:");

	// FPSR
	out("	fmove.l	fpsr,d0");
	if (($regnum & 2)) {
		out("	move.l	#$%08x,d1", $expected[$i] & regmask(2));
		$i++;
	} else {
		out("	moveq.l	#0,d1");
	}
	out("	cmp.l	d1,d0");
	out("	beq	@f");
	out_result("msg_fail_sr", "d1", "d0");
	out("@@:");

	// FPIAR
	out("	fmove.l	fpiar,a0");
	// fmovem *,nofpc は FPIAR が受け取ってしまうようだ
	if (($regnum & 1) || $regnum == 0) {
		out("	move.l	#$%08x,a1", $expected[$i] & regmask(1));
		$i++;
	} else {
		out("	suba.l	a1,a1");
	}
	out("	cmpa.l	a1,a0");
	out("	beq	@f");
	out_result("msg_fail_ir", "a1", "a0");
	out("@@:");

	out("	tst.l	d7");
	out("	bne	@f");
	out("	PRINT	msg_ok");
	out("@@:");
	out("	rts");
	out("");
}

// アクセス可能な場合のテスト。FMOVEM <FPcr>,<ea>
function subr_success_from($regs, $id)
{
	$cpureg = eanum2mnem($id);
	$regnum = reg2num($regs);
	$mnem = regnum2mnemonic($regnum);
	$label = eanum2label($id);

	$funcname = "fmovem_{$regs}_to_{$label}";
	addfunc($funcname, "from", $mnem, $cpureg);
	out("	PRINT	msg_{$funcname}");

	$w1 = 0xf200 | eanum2word($id);
	$w2 = 0xa000 | ($regnum << 10);
	// count は指定されたレジスタ数 (0..3)
	// txcount は転送するレジスタ数 (1..3)
	$count = regnum2count($regnum);
	$txcount = ($count == 0) ? 1 : $count;

	// FPcr に初期値をセット
	// nofpc の時は FPIAR
	$expected = array(
		0x55555555,
		0xaaaaaaaa,
		0x33333333,
	);
	$i = 0;
	out("	fmove.l	#$%08x,fpcr", ($regnum & 4) ? $expected[$i++] : 0);
	out("	fmove.l	#$%08x,fpsr", ($regnum & 2) ? $expected[$i++] : 0);
	out("	fmove.l	#$%08x,fpiar",
		(($regnum & 1) || $count == 0) ? $expected[$i++] : 0);

	// 準備
	switch ($id) {
	 case 2:	// (An)
	 case 3:	// (An)+
		out("	lea.l	workarea(pc),a0");
		break;
	 case 4:	// -(An)
		// nofpc でも転送は発生する
		out("	lea.l	workarea+%d(pc),a0", $txcount * 4);
		break;
	 case 5:	// #imm は dst にならない
		break;
	}

	out("	bsr	set_fline");
	out("	moveq.l	#0,d7");
	out("	lea.l	@f,a3");
	// 実行
	if ($mnem == "nofpc")
		out("	.dc.w	$%04x, $%04x", $w1, $w2);
	else
		out("	fmovem.l {$mnem},{$cpureg}");
	out("@@:");

	out("	bsr	restore_fline");
	// 例外は起きないはずなので D7 はゼロなら成功(次へ)
	out("	tst.l	d7");
	out("	beq	@f");
	out("	PRINT	msg_fail_trap");
	out("	rts");
	out("@@:");

	// 検証

	// An の変化量が正しいか
	// (An) なら workarea から変化しない
	// (An)+ なら workarea+count*4
	// -(An) なら workarea に戻ってくる (最初にオフセットつけてるので)
	switch ($id) {
	 case 2:	// (An)
	 case 4:	// -(An)
		out("	lea.l	workarea(pc),a1");
		out("	cmpa.l	a1,a0");
		out("	beq	@f");
		out_result("msg_fail_addr", "a1", "a0");
		out("@@:");
		break;
	 case 3:	// (An)+
		out("	lea.l workarea+%d(pc),a1", $txcount * 4);
		out("	cmpa.l	a1,a0");
		out("	beq	@f");
		out_result("msg_fail_addr", "a1", "a0");
		out("@@:");
		break;
	}

	$i = 0;
	switch ($id) {
	 case 0:	// Dn
		for ($r = 4; $r >= 1; $r >>= 1) {
			// 各 fpn の該当する時か、nofpc なら FPIAR
			if (($regnum & $r) || ($r == 1 && $count == 0)) {
				out("	move.l	#$%08x,d1", $expected[$i] & regmask($r));
				$i++;
				out("	cmp.l	d1,d0");
				out("	beq	@f");
				out_result("msg_fail_dst", "d1", "d0");
				out("@@:");
			}
		}
		break;
	 case 1:	// An
		for ($r = 4; $r >= 1; $r >>= 1) {
			// 各 fpn の該当する時か、nofpc なら FPIAR
			if (($regnum & $r) || ($r == 1 && $count == 0)) {
				out("	movea.l	#$%08x,a1", $expected[$i] & regmask($r));
				$i++;
				out("	cmp.l	a1,a0");
				out("	beq	@f");
				out_result("msg_fail_dst", "a1", "a0");
				out("@@:");
			}
		}
		break;
	 case 2:	// (An)
	 case 3:	// (An)+
	 case 4:	// -(An)
		for ($r = 4; $r >= 1; $r >>= 1) {
			// 各 fpn の該当する時か、nofpc なら FPIAR
			if (($regnum & $r) || ($r == 1 && $count == 0)) {
				out("	move.l	#$%08x,d1", $expected[$i] & regmask($r));
				out("	cmp.l	workarea+%d,d1", $i * 4);
				$i++;
				out("	beq	@f");
				out_result("msg_fail_dst",
					"d1", sprintf("workarea+%d", $i * 4));
				out("@@:");
			}
		}
		break;
	}

	out("	tst.l	d7");
	out("	bne	@f");
	out("	PRINT	msg_ok");
	out("@@:");
	out("	rts");
	out("");
}
?>
start2:
	clr.l	-(sp)
	DOS	_SUPER
	movem.l	d7/a3-a4,-(sp)
	; rough argument check
	cmpi.b	#4,(a2)+	; strlen(arg)>=4 ?
	bcs	@f
	cmpi.l	#$68617264,(a2)	; "hard"
	seq.b	is_hard
@@:
	tst.b	is_hard(pc)
	beq		disp_easy
disp_hard:
	PRINT	msg_hard
	bra		@f
disp_easy:
	PRINT	msg_easy
@@:
<?php
	foreach ($funclist as $name => $text) {
		// FPcr が1つもないケースは hard の時だけ実行。
		$undef_case = preg_match("/____/", $name);
		if ($undef_case) {
			out("	tst.b	is_hard(pc)");
			out("	beq		@f");
		}
		out("	bsr	test_{$name}");
		if ($undef_case) {
			out("@@:");
		}
	}
?>
	movem.l	(sp)+,d7/a3-a4
	DOS	_EXIT

; 結果表示
; スタックには、メッセージ、期待値、実際の値の順で積む。
result:
	link	a6,#0
	movem.l	d0/a0,-(sp)

	move.l	8+8(a6),-(sp)
	DOS	_PRINT
	addq.l	#4,sp

	move.l	8+4(a6),d0
	lea.l	buf(pc),a0
	bsr	hexstr_long
	PRINT	buf
	PRINT	msg_but

	move.l	8+0(a6),d0
	lea.l	buf(pc),a0
	bsr	hexstr_long
	PRINT	buf
	PRINT	msg_crlf

	addq.l	#1,d7	; errcnt
	movem.l	(sp)+,d0/a0
	unlk	a6
	rts

; Fライン例外をテスト用のものに差し替える。
; 変更前のアドレスを a4 に格納する。
; a1 は破壊する。
set_fline:
	movem.l	d0-d1,-(sp)
	moveq.l	#$b,d1
	lea.l	fline_handler(pc),a1
	IOCS	_B_INTVCS
	movea.l	d0,a4
	movem.l	(sp)+,d0-d1
	rts

; Fライン例外を元に戻す。
; a1 は破壊する。
restore_fline:
	movem.l	d0-d1,-(sp)
	moveq.l	#$b,d1
	movea.l	a4,a1
	IOCS	_B_INTVCS
	movem.l	(sp)+,d0-d1
	rts

; Fライン例外ハンドラ
; a3 に戻りアドレスをセットしてあること
; d7 を 1 にして帰る
fline_handler:
	moveq.l	#1,d7
	move.l	a3,2(sp)
	rte

msg_hard:	.dc.b	"hard mode",$d,$a,0
msg_easy:	.dc.b	"easy mode",$d,$a,0
msg_oktrap:	.dc.b	$9,"ok(trap)",$d,$a,0, 0,0
msg_fail_notrap:	.dc.b	$9,"FAIL: trap expected but not occured",$d,$a,0
msg_fail_trap:	.dc.b	$9,"FAIL: unexpected trap occured",$d,$a,0

msg_ok:		.dc.b	$9,"ok",$d,$a,0
msg_fail_cr:	.dc.b	$9,"FAIL: fpcr expects ",0
msg_fail_sr:	.dc.b	$9,"FAIL: fpsr expects ",0
msg_fail_ir:	.dc.b	$9,"FAIL: fpiar expects ",0
msg_fail_addr:	.dc.b	$9,"FAIL: an expects ",0
msg_fail_dst:	.dc.b	$9,"FAIL: dst expects ",0
msg_but:	.dc.b	" but ",0
msg_crlf:	.dc.b	$d,$a,0


<?php
	foreach ($funclist as $name => $text) {
		out("msg_{$name}:	.dc.b	\"{$text}\",0");
	}
?>

	.data
	.even
workarea:	.ds.b	24
buf:		.ds.b	20
is_hard:	.ds.b	1

	.end	start

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.