こんにちは、めのんです!
今回実装するLPM命令はかなり特殊な命令です。
慣れないとちょっと難解かもしれませんが、がんばって実装していきます。
AVRはハーバードアーキテクチャですので、プログラムはROMを読み書き可能なデータはRAMを使うのが原則です。
でも、特定の状況ではROMであるプログラムメモリにデータを格納したいこともあるんですよね。
たとえば、Cの静的なオブジェクトで初期値を持つものは起動時に初期値を設定しないといけませんよね。
でも、RAMは揮発性のメモリなので電源を切ればデータが消えてしまいます。
だから、初期値はROMに入れておいて起動時にRAMにコピーしてあげる必要があります。
そこで必要になるのがプログラムメモリからデータを読み出すLPM命令なんです。
LPM命令の実装
LPM命令はZレジスタで指定したアドレスのプログラムメモリから1バイトの値を読み出して汎用レジスタに格納します。
プログラムメモリは16ビット単位のメモリですので、それをバイト単位でアクセスするためにはちょっと一工夫が必要になってきます。
それではいつものように「AVR®命令一式手引書」から命令の説明を引用します。
説明にもあるように、Zレジスタの最下位ビットを16ビットのデータの上位側か下位側かを指定するために使っています。
ここがちょっとわかりにくいところかもしれません。
早速コードを見ていきましょう。
static void lpm_1(atmega328_t *cpu, uint16_t op)
{
int value = cpu->program[cpu->z >> 1];
if (cpu->z & 1)
value >>= 4;
else
value &= 0xff;
cpu->r[0] = value;
cpu->clock += 3;
}
static void lpm_2(atmega328_t *cpu, uint16_t op)
{
int d = (op >> 4) & 0x1f;
int value = cpu->program[cpu->z >> 1];
if (cpu->z & 1)
value >>= 4;
else
value &= 0xff;
cpu->r[d] = value;
cpu->clock += 3;
}
static void lpm_3(atmega328_t *cpu, uint16_t op)
{
int d = (op >> 4) & 0x1f;
int value = cpu->program[cpu->z >> 1];
if (cpu->z & 1)
value >>= 4;
else
value &= 0xff;
++cpu->z;
cpu->r[d] = value;
cpu->clock += 3;
}
3つまとめてなのでちょっと長く感じるかもしれませんけど、いつもと同じです。
あえてコメントも書いていませんが、この程度なら大丈夫ですよね?
プログラムメモリ(cpu->program)には16ビット単位でしかアクセスできませんので、Zレジスタ(cpu->z)を1ビット右シフトしてアドレスを決めています。
そして、読み込んだ16ビットの値のうち、上位と下位のどちらの8ビットを使うかをZレジスタの最下位ビットで決めています。
あとはこれまでの命令と変わりませんね。
一点だけお断りしておかないといけないことがあります。
上記の③の形式で、読み込む汎用レジスタがR30またはR31のときの実行結果は不定になると書かれています。
ただ、不定といっても実際にどうなるかは決まっているはずです。
本来であればそこも再現すべきなんですけど、この命令セットシミュレータではそこまで再現していません。
あらかじめご了承ください。
op_tableへの登録
次に、いつものようにop_tableに登録します。
opcode.phpに次のコードを追加して実行してあげればOKです。
$opcode_table[0b1001_0101_1100_1000] = 'lpm_1';
for ($d = 0; $d < 32; ++$d)
{
$opcode_table[0b1001_0000_0000_0100 | $d << 4] = 'lpm_2';
$opcode_table[0b1001_0000_0000_0101 | $d << 4] = 'lpm_3';
}
今回は65,536命令のうち1 + 32×2 = 65個が埋まりました。
埋められた数は少ないですが、大切な命令ですからね。
それでは次回またお会いしましょう!