IJMP命令とICALL命令の実装

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

今回実装する命令はZレジスタでアドレスを指定する分岐命令です。
XレジスタやYレジスタではこういうことはできませんので、AVRではZレジスタはちょっと特別な存在ですね。

IJMP命令の実装

まずはIJMP命令からです。
この命令は分岐先アドレスをZレジスタで指定することを除けばJMP命令と同じです。
命令のニーモニックは違っていますが、アドレッシングモード違いのJMP命令と考えてもいいでしょう。

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

①と②の2種類が記載されていますね。
今扱っているATmega328は16ビットPCですので①だけを考えることにします。

早速コードを見ていきます。

static void ijmp(atmega328_t *cpu, uint16_t op)
{
  cpu->pc = cpu->z;
  cpu->clock += 2;
}

こちらも特別なことは何もやっていません。
プログラムカウンタにZレジスタの値を代入することで分岐させています。

ICALL命令の実装

ICALL命令は分岐先アドレスをZレジスタで指定するCALL命令です。
やはりアドレッシングモード違いのCALL命令と考えていいでしょう。

Cでいう関数へのポインタを使って関数を呼び出すには、この命令を使うことになるのだと思います。
もし関数へのポインタがメモリ上の変数に格納されている場合は、いったんZレジスタに変数の値を読み込んでからICALL命令という流れになるはずです。

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

IJMP命令同様①と②の2種類がありますが、16ビットPCなので①だけを考えます。

それではコードです。

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

  cpu->pc = cpu->z;
  cpu->clock += 3;
}

スタックに戻り先アドレスを積むところはCALL命令といっしょです。
あとはIJMP命令と同じでプログラムカウンタにZレジスタの値を代入しています。

op_tableへの登録

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

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

$opcode_table[0b1001_0100_0000_1001] = 'ijmp';
$opcode_table[0b1001_0101_0000_1001] = 'icall';

今回は65,536命令のうち2個が埋まりました。
埋められた数は少ないですが、大切な命令ですからね。

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