BSET命令とBCLR命令の実装

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

前回前々回でCPUの命令をどんどん追加していくための準備が整いました。
今回から具体的な命令を実装していくことにします。

クロック数の加算

BSET命令とBCLR命令の実装を見ていく前に、前回クロックカウンタの更新を忘れてましたので、クロックカウンタを更新する処理を追加したnop命令をあらためて掲載しておきます。

static void nop(atmega328_t *cpu, uint16_t op)
{
  ++cpu->clock;
}

本来なら未定義命令でもクロックカウンタを更新すべきなのかもしれませんが、未定義命令を実行した時点でもうまともな状態じゃなくなるので、今回は扱わないことにします。

各命令がどれだけのクロックを必要とするかは「AVR®命令一式手引書」の「命令一式要約」に記載されています。

BSET命令の実装

今回最初に実装するのはBSET命令です。
この命令はステータスレジスタの指定したフラグに1をセットする命令です。

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

16ビットのオペランドの最下位ビットからd0, d1, …, d15とすると、d4~d6の3ビットでステータスフラグのどのフラグを対象にするかを表しています。

それでは実装を見ていきます。

static void bset(atmega328_t *cpu, uint16_t op)
{
  int pos = (op >> 4) & 0x7;
  cpu->sr |= 1 << pos;
  ++cpu->clock;
}

CPUの命令を実装していくには、このようにシフトやビット単位の演算がたくさん出てきます。
苦手な方もたくさんいらっしゃるかと思いますが、この機会にシフトやビット単位の演算に馴染んでいただければ幸いです。

やっていること自体は難しいことじゃなくて、3行目でオペコードのd4~d6を取り出しています。
posは0から7のどれかの値になるはずですね。

そして4行目で、ステータスレジスタ(sr)の該当フラグを1にしています。
1をposで左シフトすれば、該当フラグの位置にあたるビットが1になった値を作れます。
これをステータスレジスタとORしてあげれば、そのフラグがセットされることになります。

最後にクロックカウンタを更新して完了です。

BCLR命令の実装

次はBCLR命令の実装です。
この命令は先ほどのBSET命令の逆で、ステータスレジスタの指定したフラグを0にします。

BCLR命令についても「AVR®命令一式手引書」から説明を引用しておきます。

フラグを0にする以外はBSET命令と同じですね。

それでは実装を見ていきます。

static void bclr(atmega328_t *cpu, uint16_t op)
{
  int pos = (op >> 4) & 0x7;
  cpu->sr &= ~(1 << pos);
  ++cpu->clock;
}

bset環境との違いは4行目だけです。
1をpos分左シフトした結果をビット反転させたものと元のステータスレジスタANDを取れば、該当するフラグを0にすることができます。

これも最後にクロックカウンタを更新して完了です。

op_tableへの登録

できあがった2つの関数(bsetとbclr)を配列op_tableに登録してあげる必要があります。
op_tableへの登録はopcode.phpを使って行うんでしたね。

opcode.phpへの追記は今回がはじめてなので、opcode.php全体を掲載しておきます。

<?php
// オペコード配列を準備
$opcode_table = [];

// いったん全部未定義命令として初期化
for ($i = 0; $i < 0x10000; $i++)
  $opcode_table[$i] = 'undefined';

// 命令を登録する。
$opcode_table[0b0000_0000_0000_0000] = 'nop';

for ($i = 0; $i < 8; ++$i)
{
  $opcode_table[0b1001_0100_0000_1000 | $i << 4] = 'bset';
  $opcode_table[0b1001_0100_1000_1000 | $i << 4] = 'bclr';
}

// できあがった配列を出力
for ($i = 0; $i < 0x10000; $i++)
  echo "${opcode_table[$i]},\n";

このスクリプトの12行目から16行目が今回追加した箇所です。

これでBSET命令とBCLR命令の実装が終わりました。

ところでちょっと予断なんですが、AVRのアセンブリ言語にはCLC命令やSEC命令のように「CL*」や「SE*」の形式の命令がそれぞれ8種類ずつあります。
それらはBCLR命令やBSET命令の別名ですので、今回の実装ですべて片付いたことになります。
AVRの命令はたくさんあるように見えても、実際にはこんな感じで単なる別名のものもあるようです。

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