ASR命令とLSR命令

こんにちは、めのんです!

今回はシフト命令を実装していきます。
ASR命令もLSR命令も右シフト命令です。
左シフト命令としてはLSL命令というのがあるんですけど、実際には同じ汎用レジスタ同士のADD命令ですので、実装が必要なシフト命令はこの2つだけになります。

ASR命令の実装

まずはASR命令からです。
ASR命令は1ビット算術右シフトを行う命令になります。
ASRのAはArithmeticの頭文字です。

算術右シフトというのは、右シフトした際にもとの符号ビットをシフト後の符号ビットにコピーします。

それではいつものように「AVR®命令一式手引書」から命令の説明を引用します。

演算そのものは大したことがありませんけど、ステータスレジスタの変化が複雑ですね。

実は、この命令セットしミュータの実装では、ステータスレジスタの扱いが簡単なものから始めて、難しいものをあとに回しています。
今回シフト命令を実装しますので、次回からはいよいよ算術命令を実装していくことになります(算術命令ではステータスレジスタの変化が複雑です)。

それではコードを見ていきます。

static void asr(atmega328_t *cpu, uint16_t op)
{
  int d = (op >> 4) & 0x1f;
  int value = (int8_t)cpu->r[d];
  int c = value & 0x01; // 移動前のRd0を待避
  value >>= 1;
  cpu->r[d] = (unsigned char)value;

  int sr = cpu->sr & ~(flag_S | flag_V | flag_N | flag_Z | flag_C);
  int n = (value & 0x80) != 0;
  int v = n ^ c;
  if (n ^ v)
    sr |= flag_S;
  if (v)
    sr |= flag_V;
  if (n)
    sr |= flag_N;
  if (value == 0)
    sr |= flag_Z;
  if (c)
    sr |= flag_C;
  cpu->sr = sr;

  ++cpu->clock;
}

最初に着目していただきたいのは4行目です。
汎用レジスタの値を取り出してint8_t型にキャストしています。
本来であれば、こういうキャストをすると、もとの値がint8_t型の表現範囲を超える可能性があるのでダメなんです。
でも、現実の処理系では期待通りの結果が得られますし、効率も考えてこのようにしています。

そのあと、主に9行目からがステータスレジスタの処理になります。

その前に、5行目でシフト前の最下位ビットを退避しています。
この最下位ビットがそのままCフラグになるからです。

あとは素直に実装しています。
Nフラグはシフト後の値の再上位ビットになります。
VフラグやSフラグを求めるにはNフラグが必要になりますから先にNフラグの値を変数nに格納しています。

Sフラグを求めるにはVフラグが必要になりますから、Vフラグの値である変数vを先に設定しています。
ここまで来れば準備万端ですので、各フラグを設定していきます。

結構複雑でしたね。

LSR命令の実装

続いてLSR命令を実装します。
さっきのASR命令は算術右シフトでしたが、今度のLSR命令は論理右シフトです。
LSRのLはLogicalの頭文字です。

論理右シフトは、素直にもとの値を右にずらして、再上位ビットに0を詰めます。

こちらも「AVR®命令一式手引書」から命令の説明を引用します。

仕様をパッと見た感じでは、こちらもステータスレジスタの振る舞いが複雑に見えますが、実際に実装してみればASR命令に比べてずっと簡単なことがわかりますよ。

コードを見ていきますね。

static void lsr(atmega328_t *cpu, uint16_t op)
{
  int d = (op >> 4) & 0x1f;
  int value = cpu->r[d];
  int c = value & 0x01; // 移動前のRd0を待避
  value >>= 1;
  cpu->r[d] = (unsigned char)value;

  int sr = cpu->sr & ~(flag_S | flag_V | flag_N | flag_Z | flag_C);
  if (c)
    sr |= flag_S | flag_V | flag_C;
  if (value == 0)
    sr |= flag_Z;
  cpu->sr = sr;

  ++cpu->clock;
}

LSR命令を実行すると再上位ビットが必ずNフラグが0になります。
これでかなり話が簡単になります。

Nフラグが0ですから、VフラグはCフラグと必ず一致します。
SフラグもVフラグと一致します。
VフラグとCフラグは一致していますから、SフラグもCフラグと一致することになります。

こう考えると、実質的にCフラグとZフラグの条件さえ判断すればOKですね。

op_tableへの登録

次に、いつものようにop_tableに登録します。

opcode.phpに次のコードを追加して実行してあげればOKです。

for ($d = 0; $d < 32; ++$d)
{
  $opcode_table[0b1001_0100_0000_0101 | $d << 4] = 'asr';       
  $opcode_table[0b1001_0100_0000_0110 | $d << 4] = 'lsr';       
}

前回同様、今回も65,536命令のうち 32 × 2 = 64 個が埋まりました。

それでは次回またお会いしましょう!