こんにちは、めのんです!
今回はスタック操作のためのPUSH命令とPOP命令を実装します。
スタックは基本的なデータ構造でよく使われています。
CPUがハード的に実現しているスタックはすごく簡単で、RAM上の次にPUSHする位置をスタックポインタで覚えておくだけのものです。
PUSHするごとにスタックポインタはデクリメントされ、POPするごとにスタックポインタはインクリメントされます。
つまり、RAMの上位アドレスから下位アドレスに向かってスタックは積まれていくことになります。
下位から上位に積まれてもいい気はするのですが、私が知る限りどんなCPUでも上位から下位に積まれるようになっています。
AVRの場合はリセット時にハード的にRAMの上端にスタックポインタが初期化されます。
もし、下位アドレスから上位に向かってだと、いわゆるグローバル変数の直後のアドレスで初期化しないといけませんので、ハード的に初期化するのが困難になるのではないかと思います。
PUSH命令
PUSH命令から順に実装していきます。
いつものように「AVR®命令一式手引書」から命令の説明を引用します。
X, Y, Zレジスタの代わりにスタックポインタを使うことを除き、ST命令の③とほとんど同じです。
ただし、事前現象ではなく事後現象であることに注意が必要ですね。
それではコードを見ていきます。
static void push(atmega328_t *cpu, uint16_t op)
{
int r = (op >> 4) & 0x1f;
int sp = cpu->sph << 8 | cpu->spl;
cpu->data[sp] = cpu->r[r];
--sp;
cpu->sph = sp >> 8;
cpu->spl = sp & 0xff;
cpu->clock += 2;
}
スタックポインタは16ビットのレジスタなんですが、奇数番地から割り付けられている関係で16ビット単位でアクセスすることを断念しています。
やむを得ず上位8ビットのsphと下位8ビットのsplに分解して操作します。
スタックポインタを操作するためのマクロか関数を用意してもよかったんですけど、あまり楽にはならないので今回は直に記述することにしました。
POP命令
次はPOP命令です。
こちらも「AVR®命令一式手引書」から命令の説明を引用します。
POP命令もLD命令の②にそっくりですが、事後増加だということに注意しないといけません。
それではコードを見ていきます。
static void pop(atmega328_t *cpu, uint16_t op)
{
int r = (op >> 4) & 0x1f;
int sp = cpu->sph << 8 | cpu->spl;
++sp;
cpu->r[r] = cpu->data[sp];
cpu->sph = sp >> 8;
cpu->spl = sp & 0xff;
cpu->clock += 2;
}
POP命令のコードもややこしいのは上位下位に分割されたスタックポインタの操作ぐらいかと思います。
op_tableへの登録
次に、いつものようにop_tableに登録します。
opcode.phpに次のコードを追加して実行してあげればOKです。
for ($r = 0; $r < 32; ++$r)
{
$opcode_table[0b1001_0010_0000_1111 | $r << 4] = 'push';
$opcode_table[0b1001_0000_0000_1111 | $r << 4] = 'pop';
}
今回は65,536命令のうち32 × 2 = 64個が埋まりました。
それでは次回またお会いしましょう!