2019-11-10 15:38 — asano
カテゴリー:
昨日に引き続きASのMN1610対応についてです。
code***.c
を参考に推測・試行錯誤した結果です。全体を理解して書いているわけではないので、間違っていたり作者の意図しない使い方をしたりしているかもしれません。ご了承ください。
前回の SwitchTo_MN1610()
の最後で呼び出していた InitFields()
です。
static void InitFields(void)
{
InstTable = CreateInstTable(48);
AddAdr("L", 0xc000);
AddAdr("ST", 0x8000);
AddAdr("B", 0xc700);
AddAdr("BAL", 0x8700);
AddAdr("IMS", 0xc600);
AddAdr("DMS", 0x8600);
AddRegRegSkip("A", 0x5808);
AddRegRegSkip("S", 0x5800);
AddRegRegSkip("AND", 0x6808);
AddRegRegSkip("OR", 0x6008);
AddRegRegSkip("EOR", 0x6000);
AddRegRegSkip("C", 0x5008);
AddRegRegSkip("CB", 0x5000);
AddRegRegSkip("MV", 0x7808);
AddRegRegSkip("MVB", 0x7800);
AddRegRegSkip("BSWP", 0x7008);
AddRegRegSkip("DSWP", 0x7000);
AddRegRegSkip("LAD", 0x6800);
AddShift("SL", 0x200c);
AddShift("SR", 0x2008);
AddBit("SBIT", 0x3800);
AddBit("RBIT", 0x3000);
AddBit("TBIT", 0x2800);
AddBit("AI", 0x4800);
AddBit("SI", 0x4000);
AddRegImm8("RD", 0x1800);
AddRegImm8("WR", 0x1000);
AddRegImm8("MVI", 0x0800);
AddLevel("LPSW", 0x2004);
AddFixed("H", 0x2000);
AddReg("PUSH", 0x2001);
AddReg("POP", 0x2002);
AddFixed("RET", 0x2003);
}
ここで行なっているのはニーモニックの登録です。たくさん使われている Add***()
という関数は以下のような関数を定義しています。
static void AddAdr(char *NName, Word NCode)
{
AddInstTable(InstTable, NName, NCode, DecodeAdr);
}
単にパラメータを省略するためのラッパー、これよく考えたらマクロ定義でもいけますね。
ニーモニック・対応するコード・デコード関数を登録します。アセンブリソースのニーモニックがマッチすると登録した関数が呼び出されます。コードはそのままデコード関数に渡されるので複数のニーモニックでデコード関数を共有可能です。
登録した関数の中で実際にオペランド部をデコードしてオブジェクトコードを生成します。
MN1610ではデコード方法は以下の8種類に分類できました。
DecodeAdr()
メモリのアドレッシングを含むもの(ロード命令・ストア命令・ブランチ命令など)DecodeFixed()
オペランドの無いものDecodeRegImm8()
レジスタと8ビットの即値をとるもの(入出力命令・MVI)DecodeReg()
レジスタ1つのみのもの(PUSH
,POP
)DecodeRegRegSkip()
SrcとDestのレジスタをとりスキップ条件のあるもの(演算命令)DecodeShift()
レジスタとスキップ条件・Eレジスタ操作指定のあるもの(シフト命令)DecodeBit()
レジスタとスキップ条件・4ビットの即値をとるもの(ビット操作命令・定数加減算命令)DecodeLevel()
2ビットの即値をとるもの(LPSW
命令のみ)
これは単にオペランドの種類だけでなくオブジェクトコードのビットパターンの生成方法が共通なものです。MN1610では幸いオペランドの形式が共通なものはビットパターンも共通ですが、プロセッサによっては異なる場合もあるので注意が必要です。
次は SwitchFrom_MN1610()
から呼び出される DeinitFields()
です。
static void DeinitFields(void)
{
DestroyInstTable(InstTable);
}
他のCPUに切り替えたときに上で登録したニーモニックのテーブルを削除します。これもそのまま流用しています。
ここまではほとんどテンプレート通りに書いただけのようなものです。
それではデコーダ関数のうち比較的簡単な DecodeBit
を見てみましょう。
static void DecodeBit(Word Index)
{
Word reg;
Word skip = 0;
Byte imm4;
Boolean OK;
if (!ChkArgCnt(2,3)) return;
if (!Reg(ArgStr[1].Str, ®)) return;
imm4 = EvalStrIntExpression(&ArgStr[2], Int8, &OK);
if(!OK) return;
if (ArgCnt == 3)
{
if (!Skip(ArgStr[3].Str, &skip)) return;
}
WAsmCode[0] = Index | (reg << 8) | (skip << 4) | imm4;
CodeLen = 1;
}
まず ChkArgCnt(2,3)
でオペランドの個数をチェックします。操作対象となるレジスタ・4ビットの即値は必須ですがスキップ条件は任意なので、2以上3以下であることを確認し違っていればリターンします。ChkArgCnt(2,3)
の中でエラーはセットされています。
続いてオペランドの中身ですが、既に分けられて ArgStr[1]
, ArgStr[2]
, ... に入っているのでそれを使います。これは char *
型ではないので要注意です。
Reg()
はレジスタ名をコードに変換するもので、当然MN1610固有のものになりますから書きました。内容は簡単なので説明は省略します。
次に EvalStrIntExpression()
で第2オペランドを数値に変換します。提供されているこの関数はラベルや式の評価を引き受けてくれる便利なものです。おっと、値が4ビットに収まるかのチェックが必要ですがもれていますね。
オペランドの数は ArgCnt
に入っています。これが3の場合はスキップ条件が省略されていないのでコードに変換します。Skip()
も独自に作成したものです。
最後に最終的なオブジェクトコードを生成します。
Index
には Add***()
で登録したコードが渡ってきているのでレジスタやスキップ条件・即値のコードと合成してオブジェクトコードが完成します。生成したコードの長さ(単位は Grans[SegCode] = 2;)を CodeLen
に入れて終了です。
MN1610の命令はすべて1ワードですが、MN1613には2ワード命令もあり、その場合は WAsmCode[1]
に2ワード目を入れ、CodeLen = 2;
とします。
他の Decode***()
関数も同様です。DecodeAdr()
は結構厄介でまだ書きかけですが、残りはこのDecodeBit
が理解できれば難しくないはずです。
コメントを追加