こんにちは、めのんです!
今回実装する命令は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個が埋まりました。
埋められた数は少ないですが、大切な命令ですからね。
それでは次回またお会いしましょう!