COM命令とNEG命令の実装

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

まだ実装していない命令は残りわずかになってきました。
今回も何が残っているのか探して見つけたのがこの2つの命令です。

もしかすると全然違う命令だと思われた方もいらっしゃるかもしれませんが、要するに1の補数を求める命令と2の補数を求める命令です。
ステータスレジスタの振る舞いは結構違いますけどね。

COM命令の実装

まずはCOM命令の実装から始めます。
この命令は指定した汎用レジスタの1の補数(全ビット反転)を求めて、同じ汎用レジスタに格納します。

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

ステータスレジスタの振る舞いはそんなに難しくありませんけど、Cフラグが常に1になるのが面白いですね。

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

static void com(atmega328_t *cpu, uint16_t op)
{
  int d = (op >> 4) & 0x1f;
  int value = 0xff - cpu->r[d];
  cpu->r[d] = value;

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

  ++cpu->clock;
}

ほぼほぼ仕様通りに素直にコードを書きました。

NEG命令の実装

次にNEG命令を実装していきます。
こちらはCOM命令同様、指定した汎用レジスタの2の補数を求めて元の汎用レジスタに格納します。

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

命令の趣旨はCOM命令とそんなに変わらないのに、ステータスレジスタの振る舞いはちょっと複雑ですね。

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

static void neg(atmega328_t *cpu, uint16_t op)
{
  int d = (op >> 4) & 0x1f;
  int rd = cpu->r[d];
  int value = 0x100 - rd;
  cpu->r[d] = value;

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

  ++cpu->clock;
}

算術命令のときはステータスレジスタの振る舞いは定義通り愚直に実装しましたが、今回は少し簡略化しています。
簡略化しやすいこともあるんですよね。

op_tableへの登録

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

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

for ($d = 0; $d < 32; ++$d)
{
  $opcode_table[0b1001_0100_0000_0000 | $i] = 'com';
  $opcode_table[0b1001_0100_0000_0001 | $i] = 'neg';
}

今回は65,536命令のうち 32 × 2 = 64 個が埋まりました。

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