こんにちは、めのんです!
今回はBRBS命令とBRBC命令を実装します。
AVRのプログラムをアセンブリ言語で書いたことがある方でも、これらの命令をご存じないかもしれませんね。
実はこれらの命令は、ステータスレジスタのどのフラグを調べるかによって別々のニーモニックが与えられているので、普段はそっちの方をよく使うはずだからです。
とはいえ、命令セットシミュレータを作る上では、どのフラグを調べるかはオペランドに違いだけですので、この2つの命令を実装すればOKということになります。
BRBS命令の実装
前回は無条件分岐命令を実装しましたので、今回は条件分岐の命令を実装していきます。
まずはBRBS命令からです。
いつものように「AVR®命令一式手引書」からBRBS命令の説明を引用します。
今回も実行周期数の表が重要になるので引用しています。
それでは順にBRBS命令の動作を確認していきましょう。
BRBS命令は、ステータスレジスタの中のs0からs2の3ビットで指定したフラグが1のときに分岐が発生します。
分岐が発生した場合は相対分岐で、k0からk6の7ビットで-64から+63の相対アドレスを指定するようになっています。
前回のRJMP命令同様、プログラムカウンタをインクリメントしたあとに分岐しますので、BRBS命令があったアドレスに対しては-63から+64の範囲で分岐することができます。
それではソースコードを見ていきます。
static void brbs(atmega328_t *cpu, uint16_t op)
{
int flag = 1 << (op & 0x7);
int target = ((op >> 3) & 0x7f) - 0x40;
if (cpu->sr & flag)
{
cpu->pc += target;
cpu->clock += 2;
}
else
{
++cpu->clock;
}
}
最初にs0からs2を取り出して変数「flag」に、k0からk6を取り出して変数「target」に格納しています。
targetは-64から+63になるように0x40(=64)を引いています。
6行目のif文では、ステータスレジスタとflagとのビット単位の論理積を求めて、それが0以外かどうかを判定しています。
0以外のときは分岐することになりますので、targetをプログラムカウンタに足しています。
該当するステータスレジスタのフラグが0のときは分岐しませんので何もしません。
なお、分岐が成立した場合はクロックカウンタを2増やし、分岐が成立しなかった場合はクロックカウンタを単にインクリメントしています(つまり1増やしています)。
今回は今まで以上に複雑なビット演算が登場しましたが、ひとつずつ落ち着いてみていけば決して難しい内容ではないはずです。
BRBC命令の実装
次はBRBC命令です。
BRBC命令はBRBS命令の逆で、ステータスレジスタの指定したフラグが0のときに分岐が発生します。
早速いつものように命令の説明を「AVR®命令一式手引書」から引用します。
ほとんどBRBS命令と同じなのでこれ以上の説明は不要でしょう。
それではソースコードを見ていきます。
static void brbc(atmega328_t *cpu, uint16_t op)
{
int flag = 1 << (op & 0x7);
int target = ((op >> 3) & 0x7f) - 0x40;
if (!(cpu->sr & flag))
{
cpu->pc += target;
cpu->clock += 2;
}
else
{
++cpu->clock;
}
}
ソースコードもほとんどBRBSのものと同じですね。
1箇所だけ違うのは6行目のif文の条件です。
今回はフラグが0のときに分岐が成立しますので!演算子で否定条件を調べています。
op_tableへの登録
最後の仕上げとして、いつものようにop_tableに登録します。
opcode.phpに次のコードを追加して実行してあげればOKです。
for ($k = 0; $k < 0x80; ++$k)
{
for ($s = 0; $s < 8; ++$s)
{
$opcode_table[0b1111_0000_0000_0000 | $k << 3 | $s] = 'brbs';
$opcode_table[0b1111_0100_0000_0000 | $k << 3 | $s] = 'brbc';
}
}
今回の2命令を追加することで、65,536あるオペコードのうち2,048個が埋まったことになります。
それでは次回またお会いしましょう!