File:  [Isaki's NoNo m68k/m88k emulator] / nono / m680x0 / gentable.php
Revision 1.1.1.9 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:57 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

<?php
//
// nono
// Copyright (C) 2014 Tetsuya Isaki
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// gentable.php
// inst*.txt からディスパッチャ部分を生成
//
//	switch10    switch-case (10bit) を出力
//	switch13	switch-case (13bit) を出力
//	mmu8		MMU の switch-case (8bit) を出力
//

	if ($argc < 2) {
		usage();
	}

	init_ea();
	// 今の所コアも逆アセンブラも 020 以降を区別なく展開する。
	$mputype = "[2346]";
	switch ($argv[1]) {
	 case "switch10":
	 case "switch13":
		if (preg_match("/10$/", $argv[1])) {
			$nb = 10;
		} else {
			$nb = 13;
		}
		$insttable = read_instructions($mputype, "instructions.txt");
		$insttable = expandDST($insttable);
		$insttable = expandSS($insttable);
		$insttable = expandCCC($insttable);
		makeop16($insttable);
		$op10table = makeop10table();
		$op10func  = makeop10func($op10table);
		$op10hash  = makeop10hash($op10func);
		output_switch($op10table, $op10func);
		output_header($op10hash);
		break;

	 case "mmu8":
		$nb = 8;
		$insttable = read_instructions($mputype, "instmmu.txt");
		makeop16($insttable);
		$op10table = makeop10table();
		$op10func  = makeop10func($op10table);
		$op10hash  = makeop10hash($op10func);
		output_switch($op10table, $op10func);
		output_header($op10hash);
		break;
	 default:
		usage();
	}
	exit(0);
?>
<?php
function usage()
{
	global $argv;

	print "Usage: {$argv[0]} switch10 | switch13\n";
	print "  input:  instruction.txt\n";
	print "  output: switch.h.new, ops.cpp.new, ops.h.new\n";
	print "Usage: {$argv[0]} mmu8\n";
	print "  input:  instmmu.txt\n";
	print "  output: \n";
	exit(1);
}

function init_ea()
{
	global $ambits;

	// アドレッシングモードの展開パターン
	$ambits = array(
		"d"	=> "000rrr",
		"a"	=> "001rrr",
		"m"	=> "010rrr",
		"+"	=> "011rrr",
		"-"	=> "100rrr",
		"r"	=> "101rrr",
		"x"	=> "110rrr",
		"w"	=> "11100r",
		"p"	=> "11101r",
		"i"	=> "111100",
	);
}

// instructions.txt を読み込んで $insttable[] 配列にする
function read_instructions($mputype, $filename)
{
	$fp = fopen($filename, "r");
	if ($fp === false) {
		print "fopen failed: {$filename}\n";
		exit(1);
	}

	$insttable = array();

	while (($line = fgets($fp))) {
		$line = trim(preg_replace("/;.*/", "", $line));
		if ($line == "") {
			continue;
		}

		// 1行はこんな感じ
		// 00000000SSmmmrrr  d.m+-rxw..  034  ori_S  ORI.S #<imm>,<ea>
		// 列は1つ以上のタブで区切られている。
		// $bits … 1ワード目のビットパターン
		// $addr … 対応しているアドレッシングモード
		// $mpu  … MPU ごとのサポート有無
		// $name … 関数名
		// $text … コメント

		list ($bits, $addr, $mpu, $name, $text) =
			preg_split("/\t+/", $line, -1, PREG_SPLIT_NO_EMPTY);
		$name = trim($name);
		$text = trim($text);

		// ターゲット CPU の命令のみ対象にする
		if (preg_match("/{$mputype}/", $mpu) == false) {
			continue;
		}

		$newinst = array(
			"bits" => $bits,
			"addr" => $addr,
			"mpu"  => $mpu,
			"name" => $name,
			"text" => $text,
		);
		// $insttable[] に追加する。
		// ただし bits が同じものがあれば text だけ追加。
		if (isset($insttable[$bits])) {
			$insttable[$bits]["text"] .= ";{$text}";
		} else {
			$insttable[$bits] = $newinst;
		}
	}
	fclose($fp);

	// デバッグ表示
	if (0) {
		print "read_instruction\n";
		foreach ($insttable as $inst) {
			printf("%s|%s|%-14s|%s\n",
				$inst["bits"], $inst["addr"], $inst["name"], $inst["text"]);
		}
	}

	return $insttable;
}

// $insttable のうち MOVE の DST を展開する
function expandDST($insttable)
{
	$dst = array(
		array("xxx000", "dn",	"Dx"),
		array("xxx010", "anin",	"(Ax)"),
		array("xxx011", "anpi",	"(Ax)+"),
		array("xxx100", "anpd",	"-(Ax)"),
		array("xxx101", "andi", "d16(Ax)"),
		array("xxx110", "anix", "(Ax,IX)"),
		array("000111", "absw", "Abs.W"),
		array("001111", "absl", "Abs.L"),
	);
	$inst2table = array();
	foreach ($insttable as $inst) {
		if (preg_match("/RRRMMM/", $inst["bits"])) {
			foreach ($dst as $d) {
				$inst2 = $inst;
				$inst2["bits"] = preg_replace("/RRRMMM/", $d[0], $inst["bits"]);
				$inst2["name"] = preg_replace("/DD/", $d[1], $inst["name"]);
				$inst2["text"] = preg_replace("/DD/", $d[2], $inst["text"]);

				$inst2table[] = $inst2;
			}
		} else {
			$inst2table[] = $inst;
		}
	}

	// デバッグ表示
	if (0) {
		print "expandDST\n";
		foreach ($inst2table as $inst) {
			printf("%s|%s|%-14s|%s\n",
				$inst["bits"], $inst["addr"], $inst["name"], $inst["text"]);
		}
	}

	return $inst2table;
}

// $insttable の SS と s を展開する
// これらは必ず上位10ビット内にあるので (というか下位6ビット以内に現れる
// やつはテキストにする時点で手動で展開してあるので)、
// サイズ別の関数に展開できる。
function expandSS($insttable)
{
	$s3 = array(
		array("00", "_b",	".B"),
		array("01", "_w",	".W"),
		array("10", "_l",	".L"),
	);
	$s2 = array(
		array("0",	"_w",	".W"),
		array("1",	"_l",	".L"),
	);

	$inst2table = array();
	foreach ($insttable as $inst) {
		if (preg_match("/SS/", $inst["bits"])) {
			foreach ($s3 as $s) {
				$inst2 = $inst;
				$inst2["bits"] = preg_replace("/SS/", $s[0], $inst["bits"]);
				$inst2["name"] = preg_replace("/_S/", $s[1], $inst["name"]);
				$inst2["text"] = preg_replace("/\.S/", $s[2], $inst["text"]);

				$inst2table[] = $inst2;
			}
		} else if (preg_match("/s/", $inst["bits"])) {
			foreach ($s2 as $s) {
				$inst2 = $inst;
				$inst2["bits"] = preg_replace("/s/", $s[0], $inst["bits"]);
				$inst2["name"] = preg_replace("/_S/", $s[1], $inst["name"]);
				$inst2["text"] = preg_replace("/\.S/", $s[2], $inst["text"]);

				$inst2table[] = $inst2;
			}
		} else {
			$inst2table[] = $inst;
		}
	}

	// デバッグ表示
	if (0) {
		print "expandSS\n";
		foreach ($inst2table as $inst) {
			printf("%s|%s|%-14s|%s\n",
				$inst["bits"], $inst["addr"], $inst["name"], $inst["text"]);
		}
	}

	return $inst2table;
}

// $insttable の CCC (cpID) を展開する。
// cpID 0 は MMU、1 は FSAVE/FRESTORE なので展開しない。
// cpID 2 は表側で対応してあるのでこちらでは展開しない。
function expandCCC($insttable)
{
	$c3 = array(
		"011",
		"100",
		"101",
		"110",
		"111",
	);

	$inst2table = array();
	foreach ($insttable as $inst) {
		if (preg_match("/CCC/", $inst["bits"])) {
			foreach ($c3 as $c) {
				$inst2 = $inst;
				$inst2["bits"] = preg_replace("/CCC/", $c, $inst["bits"]);
				$inst2table[] = $inst2;
			}
		} else {
			$inst2table[] = $inst;
		}
	}

	// デバッグ表示
	if (0) {
		print "expandCCC\n";
		foreach ($inst2table as $inst) {
			printf("%s|%s|%-14s|%s\n",
				$inst["bits"], $inst["addr"], $inst["name"], $inst["text"]);
		}
	}

	return $inst2table;
}

// insttable を16ビットの op16table に展開。
// $optable = array(
//	0 => &$inst0,
//	1 => &$inst1, ...
// )
function makeop16($insttable)
{
	global $op16table;

	$op16table = array();

	foreach ($insttable as $inst) {
		makeop16_sub($inst, $inst["bits"]);
	}

	// デバッグ表示
	if (0) {
		print "makeop16\n";
		for ($i = 0; $i < 65536; $i++) {
			$b = str_repeat("0", 15) . decbin($i);
			$b = substr($b, -16);
			printf("%04x|%s|", $i, $b);

			if (isset($op16table[$i])) {
				$inst = $op16table[$i];
				printf("%s|%-14s|%s\n",
					$inst["bits"], $inst["name"], $inst["text"]);
			} else {
				printf("n/a\n");
			}
		}
	}
}

// inst を1ビットずつ展開する再帰関数
// lv は再帰レベル (デバッグ用)
function makeop16_sub($inst, $bits, $lv = 0)
{
	global $op16table;
	global $ambits;

	// $pos は bits の 0/1 でない最初の文字の位置
	$pos = strspn($bits, "01");
	if ($pos == 16) {
		// 0/1 しか現れていないので確定
		// $optable に代入
		$num16 = bindec($bits);
		$op16table[$num16] = $inst;
	} else if (substr($bits, 10) == "mmmrrr") {
		// アドレッシングモードによって展開 (いるかな?)
		$head = substr($bits, 0, 10);
		for ($i = 0; $i < strlen($inst["addr"]); $i++) {
			$a = substr($inst["addr"], $i, 1);
			if (isset($ambits[$a])) {
				makeop16_sub($inst, "{$head}{$ambits[$a]}", $lv + 1);
			}
		}
	} else {
		// pos 番目の文字を 0 と 1 に変えて再帰実行
		$head = substr($bits, 0, $pos);
		$tail = substr($bits, $pos + 1);
		makeop16_sub($inst, "{$head}0{$tail}", $lv + 1);
		makeop16_sub($inst, "{$head}1{$tail}", $lv + 1);
	}
}

// 16ビットテーブルから $nb (10 or 7) ビットテーブルを作成
function makeop10table()
{
	global $op16table;
	global $nb;

	// 1ブロックあたりの命令数 ($nb=10 なら 64)
	$ipb = 2 ** (16 - $nb);

	$op10table = array();
	for ($i = 0; $i < 2 ** $nb; $i++) {
		$op10table[$i] = array();
	}

	for ($i = 0; $i < 65536; $i++) {
		if (!isset($op16table[$i])) {
			continue;
		}
		$inst = $op16table[$i];
		$j = $i / $ipb;

		// op10table[j][] にすでに同じエントリがなければ、追加
		$found = false;
		foreach ($op10table[$j] as $ji) {
			if ($inst["bits"] == $ji["bits"]) {
				$found = true;
				break;
			}
		}
		if (!$found) {
			$op10table[$j][] = $inst;
		}
	}

	// デバッグ用
	if (0) {
		print "makeop10table\n";
		foreach ($op10table as $j => $arr) {
			printf("[0x%04x] $%04x|", $j, $j * $ipb);
			foreach ($arr as $inst) {
				printf(" %s", $inst["name"]);
			}
			print "\n";
		}
	}

	return $op10table;
}

// op10table から op10func を作成
// op10table は [1024] => array(inst, inst, ...)
// op10func は [1024] => 代表name
function makeop10func($op10table)
{
	global $nb;

	$op10func = array();
	for ($i = 0; $i < 2 ** $nb; $i++) {
		$op10func[$i] = "";
	}

	foreach ($op10table as $i => $arr) {
		if (count($arr) == 0) {
			$op10func[$i] = "n/a";
			continue;
		}

		// 代表関数はこのブロックの中で最多数なもの。
		$funcname = "";
		$minbits = (16 - $nb) + 1;
		foreach ($arr as $inst) {
			// fixed は bits の末尾(16-nb)ビットのうち "0" と "1" の合計数
			$chs = count_chars(substr($inst["bits"], $nb), 0);
			$fixed = $chs[0x30] + $chs[0x31];
			// fixed が小さい = 出力した命令の総数が大きい、なので採用
			if ($minbits > $fixed) {
				$minbits = $fixed;
				$funcname = $inst["name"];
			}
		}

		$op10func[$i] = $funcname;
	}

	// デバッグ用
	if (0) {
		print "makeop10func\n";
		foreach ($op10func as $i => $name) {
			printf("[%4d] %04x {$name}\n", $i, $i * 64);
		}
	}

	return $op10func;
}

// op10hash は [代表name] => 代表name のハッシュ
// 実際に使われてる関数名一覧になる。
function makeop10hash($op10func)
{
	$op10hash = array();
	foreach ($op10func as $name) {
		if ($name != "n/a") {
			$op10hash[$name] = $name;
		}
	}

	return $op10hash;
}

// コメントをいい感じにソートしたい。
// $a, $b は "%1111_000000_mmmrrr dam+-rxwpi 034 OPNAME" みたいなやつ。
// これのビットパターン部分でソート。ただし英字は "0"/"1" より前。
// ビットパターンが同じなら出現順を維持(ソースファイルに記載の順)。
function cmp_comments($comm_a, $comm_b)
{
	$va = preg_split('/\s+/', $comm_a);
	$vb = preg_split('/\s+/', $comm_b);

	$a = $va[0];
	$b = $vb[0];

	// 同じなら a, b の順。
	if ($a == $b) {
		return -1;
	}

	// "0"/"1" を英字の後ろにして比較。
	$a = str_replace(array("0","1"), array("{","}"), $a);
	$b = str_replace(array("0","1"), array("{","}"), $b);
	return $a <=> $b;
}

// switch 本体とソースの元を出力
function output_switch($op10table, $op10func)
{
	global $nb;

	$shift = 16 - $nb;
	$ipb = 2 ** (16 - $nb);

	$func2 = $op10func;

	// 先に不当命令ブロックだけ抜いておく
	$not_assigned = array();
	for ($i = 0; $i < 2 ** $nb; $i++) {
		if (isset($func2[$i]) && $func2[$i] == "n/a") {
			$not_assigned[$i] = $i;
			unset($func2[$i]);
		}
	}

	$out = "";
	$cpp = "";
	for ($i = 0; $i < 2 ** $nb; $i++) {
		if (!isset($func2[$i]))	{
			continue;
		}
		$funcname = $func2[$i];

		$cases = array();
		$comments = array();

		// この関数名をもつブロックに属する人全員のコメントを集める。
		// このブロック自身もここで集計するため $j は $i から始める。
		for ($j = $i; $j < 2 ** $nb; $j++) {
			if (isset($func2[$j]) && $func2[$j] == $funcname) {
				foreach ($op10table[$j] as $inst) {
					// case
					$cases[$j] = $j;
					// コメント
					// 同一 bits に別の命令が割り当てられているケースは
					// ここで2行のコメントに分解
					foreach (preg_split("/;/", $inst["text"]) as $text) {
						$comm = "{$inst["bits"]} {$inst["addr"]} "
							. "{$inst["mpu"]}\t{$text}";
						$comments[$comm] = $comm;
					}
				}
				// 集計したら消す
				unset($func2[$j]);
			}
		}
		// case
		foreach ($cases as $c) {
			$out .= sprintf("\t case (0x%04x >> ${shift}):\n", $c * $ipb);
		}
		// コメント
		usort($comments, "cmp_comments");
		foreach ($comments as $comm) {
			if ($nb == 8) {
				// というか MMU というべきか
				preg_match("/(...)(.....)(...)(.*)/", $comm, $m);
				$comm1 = "%{$m[1]}_{$m[2]}_{$m[3]}_{$m[4]}";
			} else {
				// 第1ワードというべきか
				preg_match("/(....)(......)(.*)/", $comm, $m);
				$comm1 = "%{$m[1]}_{$m[2]}_{$m[3]}";
			}
			$cpp .= "// {$comm1}\n";
			// ヘッダに出力するのはビットと名前だけに短縮。
			$comm2 = preg_replace('/(\S+)\s+\S+\s+\S+\s+(.*)/', "$1\t$2",
				$comm1);
			$out .= "\t\t// {$comm2}\n";
		}
		// 関数
		$out .= "\t\tOP_FUNC({$funcname});\n";
		$out .= "\t\tbreak;\n";

		// ソース
		$cpp .= output_func($funcname);
	}

	// 不当命令のブロックを出力
	foreach ($not_assigned as $i) {
		$out .= sprintf("\t case (0x%04x >> ${shift}):\n", $i * $ipb);
	}
	$out .= "\t\tOP_FUNC(illegal);\n";
	$out .= "\t\tbreak;\n";

	// 不当命令 (ILLEGAL 命令と F ライン命令と不当命令の合流先…)
	$cpp .= "// illegal (or not-assigned) instructions\n";
	$cpp .= output_func("illegal");

	// ファイルに出力
	write_file("switch.h.new", $out);
	write_file("ops.cpp.new", $cpp);
}

// 関数ソースを出力
function output_func($name)
{
	$cpp = "";
	$cpp .= "OP_DEF({$name})\n";
	$cpp .= "{\n";
	$cpp .= "\tOP_FUNC(unimpl);\n";
	$cpp .= "}\n";
	$cpp .= "\n";

	return $cpp;
}

// ヘッダを出力
function output_header($op10hash)
{
	$out = "";
	foreach ($op10hash as $name) {
		$out .= "OP_PROTO({$name});\n";
	}
	$out .= "OP_PROTO(illegal);\n";

	// ファイルに出力
	write_file("ops.h.new", $out);
}

// ファイル出力
function write_file($filename, $str)
{
	$fp = fopen($filename, "w");
	fwrite($fp, $str);
	fclose($fp);

	print "Output: {$filename}\n";
}
?>

unix.superglobalmegacorp.com

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