減算命令の実装

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

前回は加算命令を実装しましたので今回は減算命令を実装していきます。
加算命令と対象になっていないことにちょっと違和感がありますが、気にせず進めます。

AVRの減算命令は大きく分けて2種類あって、ひとつは繰り下がりを考慮しないものであり、もうひとつは繰り下がりを考慮するものです。
加算命令にもCフラグを加算するものがあったように、減算命令でも繰り下がりを考慮する場合はCフラグもいっしょに引くことになります。

一度に扱うと数が多いので、今回は繰り下がり無しのSUB命令とSUBI命令だけを実装することにします。

SUB命令の実装

まずはSUB命令です。
SUB命令は汎用レジスタどうしの減算を行います。
減算の結果は一方の汎用レジスタに格納します。

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

加算命令同様、演算そのものは単なる減算ですが、ステータスレジスタの振る舞いが複雑です。
ただ、基本的には加算命令のときと同じですので、機械的にコーディングしていけば問題なさそうです。

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

static void sub(atmega328_t *cpu, uint16_t op)
{
  int d = (op >> 4) & 0x1f;
  int r = (op & 0xf) | (op >> 5) & 0x10;
  int rd = cpu->r[d];
  int rr = cpu->r[r];
  int value = rd - rr;
  cpu->r[d] = value;

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

  ++cpu->clock;
}

今回も素直に定義どおりにコーディングしてみました。

SUBI命令の実装

次はSUBI命令です。
基本はSUB命令と同じですが、一方のオペランドが即値になっています。
また、汎用レジスタとして指定できるのはR16~R31ですので注意が必要です。

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

オペランドの取り方を除けばSUB命令と同じですね。

これもコードを見ていきましょう。

static void subi(atmega328_t *cpu, uint16_t op)
{
  int d = 16 + ((op >> 4) & 0xf);
  int K = (op & 0xf) | (op >> 4) & 0xf0;
  int rd = cpu->r[d];
  int value = cpu->r[d] - K;
  cpu->r[d] = value;

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

  ++cpu->clock;
}

これも定義通りに実装しただけです。

op_tableへの登録

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

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

for ($i = 0; $i < 1024; ++$i)
{
  $opcode_table[0b0001_1000_0000_0000 | $i] = 'sub';
}

for ($i = 0; $i < 4096; ++$i)
{
  $opcode_table[0b0101_0000_0000_0000 | $i] = 'subi';
}

今回は65,536命令のうち 1,024 + 4,096 = 5,120 個が埋まりました。

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