Skip to content

Latest commit

 

History

History
178 lines (121 loc) · 7.66 KB

decode.md

File metadata and controls

178 lines (121 loc) · 7.66 KB

デコード

Note:

この記事は

- Decoding the ARM instruction set
- Decoding the THUMB instruction set

を翻訳したもので、主にGBAエミュレータ開発者向けです。

ARMモード

まず、ARM命令のバイナリオペコードフォーマットを見てみましょう。

arm_bit_pattern

しかし、この参考文献は完全に正確なものではなく、最初の命令をデコードするために必要な重要な情報を見逃しているので、これから説明します。

まず最初に、THUMB命令セットをデコードするとき、私たちは単純に命令の上位8ビットを使用していたので、命令を明確にデコードすることができました。

例えば、ビット27~20を使って、どの命令なのかを判断しようとすると、ビット27~20をすべてアンセット(0)にする命令が複数種類あるため、曖昧になってしまいます。

そこで、上位8ビット(ビット27-20)とベース4ビット(ビット7-4)を端と端で足し合わせることにします。この原理をもう少しわかりやすく説明するための絵があります。

arm_bit_pattern_1

C/C++では、この値を実際に取得するには、ビット単位での微調整が必要です。例えば、以下のようになります。

u32 instruction = fetch();
u16 _12Bits = (((instruction >> 16) & 0xFF0) | ((instruction >> 4) & 0x0F));

これで、命令をデコードするのに使う12ビットの数字ができました。では、最初から考えてみましょう。

この12ビットの数字が0、つまりすべてのビットがアンセットされていたらどうなるでしょうか。

オペコードフォーマットを見ると、これを可能にする命令フォーマットは1つだけで、1枚目の写真の一番上のData Processing/PSR命令であることがわかります。

さて、この命令がどのようなタイプの命令であるかはすでにわかっていますが、どのような命令であるかを正確に知るためには、さらに情報が必要です。

Data Processing/PSR命令では、Operand2というフィールドがあります。

このフィールドは、上記のバイナリオペコードフォーマットには示されていないフォーマットを持っており、以下にOperand2フィールドの仕様を示します。

arm_bit_pattern_2

今回の状況では、すべてのビットは0なので、当然immediateビット(bit25)がセットされていないので、シフトされたレジスタを使用していることになります。

バレルシフトについては今のところ説明しませんが、これはこの記事の範囲を超えています。ここでは、より正式な言い方として、マニュアルから直接引用します。

第2オペランドがシフトレジスタと指定されている場合、バレルシフタの動作は命令のshiftフィールドによって制御されます。

shiftフィールドは次のようになっています。

arm_bit_pattern_3

これは非常に複雑なことだと思いますが、マニュアルを読んでいただくのが一番です。

とにかく、私についてきてください。この命令について、これまでに集めた情報を確認してみましょう。12ビットすべてが0であると仮定すると...。

  1. Data Processing命令です。
  2. シフトされたレジスタのバレルシフト演算を使用します。
  3. bit24-21からAND命令です。
  4. bit4は0なので、シフトしたレジスタを即値でシフトするシフト操作であることがわかり、直前の図の左側のフォーマットを使用しています。
  5. bit6-5は0なので、シフト内容はLSLです。

よって

AND Rd, Rn, Rm, LSL #imm5

となります!

THUMBモード

まず、THUMB命令のバイナリオペコードフォーマットを見てみましょう。

thumb_bit_pattern

各命令は2バイト(ハーフワード)で構成されており、各命令には、CPUがデコードする独自のフォーマットがあります。

ご覧のように、フォーマット1(Move shifted register)のようないくつかの命令フォーマットには、これが特定の命令であることを示すオペコードフィールドがあります。Move shifted registerの場合には、12-11bitに2ビットのオペコードフィールドがあります。

さて、私が命令をデコードする方法は、命令の上位8ビットを取り、その値をチェックすることです。

例えば、上位8ビットの値が「0〜7」であれば、その命令がLSL Rd, Rs, Offsetとわかります。なぜでしょうか?

これは、上位8ビットが「0〜7」になっている間は、Move shifted registerのオペコードフィールドが0になり、この形式の他のビットはすべて正しいからです。

thumb_bit_pattern_1

上の図では上位8bitは00000101となっています。

命令フォーマットを確認すると、Move shifted registerフォーマットでは、上位8bitがこの値になる場合がありえます。

Move shifted registerフォーマットでは、先の8bit値の下位3bitが、Offset5フィールドの上位3bit(bit8,9,10)であり、このフィールドは任意の値がありうるからです。

また、任意の値をとりうるということは、次の8つのバイナリはすべてLSL Rd, Rs, Offset5となります。

00000000    (0)
00000001    (1)
00000010    (2)

00000011    (3)
00000100    (4)
00000101    (5)
00000110    (6)
00000111    (7)

次に、仮に8という値が出た場合、オペコードフィールドが1つ増えたことになるので、命令はLSR Rd, Rs, Offset5となります。

残りの命令セットのデコードも、この論理パターンに従うだけです。

ここでは、最初の数個の命令をどのようにデコードするか、コード例を紹介します

条件分岐を使う場合は、

u16 instruction = fetch();

switch (instruction >> 8)
{
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    {
        // LSL Rd, Rs, Offset5
    } break;
    case 8:
    case 9:
    case 0xA:
    case 0xB:
    case 0xC:
    case 0xD:
    case 0xE:
    case 0xF:
    {
        // LSR Rd, Rs, Offset5
    } break;
}

関数ポインタの配列を使う場合は、

void (*CPU_Thumb_Instruction[0x100]) (u16 instruction) =
{
    Thumb_LSL_Rd_Rs_Offset,        // 00h
    Thumb_LSL_Rd_Rs_Offset,        // 01h
    Thumb_LSL_Rd_Rs_Offset,        // 02h
    Thumb_LSL_Rd_Rs_Offset,        // 03h
    Thumb_LSL_Rd_Rs_Offset,        // 04h
    Thumb_LSL_Rd_Rs_Offset,        // 05h
    Thumb_LSL_Rd_Rs_Offset,        // 06h
    Thumb_LSL_Rd_Rs_Offset,        // 07h
    /*-------------------*/
    Thumb_LSR_Rd_Rs_Offset,        // 08h
    Thumb_LSR_Rd_Rs_Offset,        // 09h
    Thumb_LSR_Rd_Rs_Offset,        // 0Ah
    Thumb_LSR_Rd_Rs_Offset,        // 0Bh
    Thumb_LSR_Rd_Rs_Offset,        // 0Ch
    Thumb_LSR_Rd_Rs_Offset,        // 0Dh
    Thumb_LSR_Rd_Rs_Offset,        // 0Eh
    Thumb_LSR_Rd_Rs_Offset,        // 0Fh
};