RETI命令の実装

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

今回は割り込みから元の場所に戻るためのRETI命令を実装することにします。
割り込みについてはここでは詳しく解説しませんので、もし興味ある方がいらっしゃいましたらご自身で調べてくださいね。

RETI命令の実装

RETI命令は割り込み処理ルーチンから割り込まれる前のプログラム位置に戻るための命令です。

割り込みが発生すると、CALL命令などでサブルーチンを呼び出すのと同じように、あらかじめ決められた割り込み処理ルーチンのアドレスに分岐が発生します。
このとき、CPUのアーキテクチャによっては、戻り先アドレスといっしょにステータスレジスタもスタックに待避されるのですが、AVRではアプリケーションプログラムが自分でステータスレジスタを待避・復帰させないといけません。

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

同じAVRでも型版によって動作が変わるようですね。
今作っているのはATmega328の命令セットシミュレータです。
ATmega328はAVRe+ですのでAVReの一種になります。

AVReだと、ATmega328は16ビットのPCですから実行周期数は4ですし、RETI命令が実行されたときにはステータスレジスタのIビットが1に設定されるようです。
割り込みが発生するといったんは割り込みを禁止にして、アプリケーションプログラムが多重割り込みを使うのなら自分でIビットに1を設定するということになります。

最初、割り込みが発生したときのIビットを覚えておいて復帰させないといけないんじゃないかな? と思ったんですが、よく考えるとIビットが1でないと割り込みそのものが発生しないんですよね。
だから、割り込みが発生したときはIビットは1に決まっているんです。

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

static void reti(atmega328_t *cpu, uint16_t op)
{
  int sp = cpu->sph << 8 | cpu->spl;
  int return_address = cpu->data[++sp] << 8;
  return_address |= cpu->data[++sp];
  cpu->sph = sp >> 8;
  cpu->spl = sp & 0xff;

  cpu->pc = return_address;
  cpu->sr |= flag_I;
  cpu->clock += 4;
}

サブルーチンから戻るRET命令とほとんど同じです。
違いは最後にIビットをセットしているところだけですね。

op_tableへの登録

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

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

$opcode_table[0b1001_0101_0001_1000] = 'reti';

今回は65,536命令のうち1個だけが埋まりました。
前回同様、埋められた数は少ないですが、欠かせない命令です。

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