こんにちは、めのんです!
今回はステータスレジスタについて考えていくことにします。
ステータスレジスタをやれば、一通りCPUのレジスタについては考えたことになります。
ステータスレジスタとは?
「ステータスレジスタ」というのは、直近の演算命令の結果生じた状態を格納するためのレジスタです。
といっても何のことかわかりませんよね?
もう少し具体的に説明するようにします。
たとえば加算命令を実行したとしましょう。
加算命令ですので当然足し算の結果の値があるのですが、それとは別に、結果がゼロだったのか、桁上がりがあったのか、マイナスだったのかなどの状態をステータスレジスタに格納します。
演算結果の状態がステータスレジスタに格納されるので、たとえば直前の加算の結果がゼロであれば分岐させるなどの条件分岐に使うことができます。
大抵のCPUアーキテクチャではステータスレジスタ(またはそれに相当するもの)があるのですが、MIPSのようにステータスレジスタがないアーキテクチャもあります。
また、ステータスレジスタがある場合でも、具体的にどんな状態を保持できるのかはアーキテクチャによって異なります。
AVRのステータスレジスタ
AVRのステータスレジスタは8ビットで、「picoPower®技術を持つAVR® マイクロ コントローラ」によると次のようになっています。
各ビットにIとかTとかの文字が書かれていますが、それぞれがフラグで意味を持っています。
フラグの詳細な意味は、それを実際に使うことになったときに詳しく説明します。
ここではもっとも代表的なゼロフラグとキャリーフラグについて見ていくことにしましょう。
ゼロフラグ
ゼロフラグは直前の演算結果がゼロになったときに1がセットされます。
ゼロにならなければ0がセットされます。
比較命令でもゼロフラグが変化しますので、2つの値を減算して結果がゼロ(すなわち等しい)のときにも1がセットされます(比較だけなので減算結果は捨てられます)。
AVRではロード命令(レジスタに値を読み込む)ではゼロレジスタは変化しませんが、アーキテクチャによってはロード命令を実行するだけでもゼロレジスタが変化するものもあるようです。
キャリーフラグ
キャリーフラグは直前の演算で最上位ビットからの桁上がりや桁下がり(小学校で習う用語に合わせれば繰り上がりと繰り下がり)が発生したときに1にセットされます。
桁上がりや桁下がりが発生しなければ0がセットされます。
AVRのレジスタは8ビットなので、それ以上大きな整数値の加算を行おうとすると、まずは最下位バイトの加算を行って、次に先ほど発生したキャリーを含めてひとつ上位のバイトの加算を行う必要があります。
ちょうど筆算するのと同じだと考えてください。
算数で習ったのは0~9が1桁でしたが、AVRでは0~255を1桁とするようなイメージです。
減算のときにもキャリーフラグは変化します。
減算命令を実行したときに桁下がりがあればキャリーフラグに1がセットされます。
加算のときと同じように8ビットを超える整数値の減算を行うときは、まず最下位バイトの減算を行って、次にひとつ上位のバイトの減算を行う際にキャリーフラグの値も引きます。
減算時の桁下がりでもキャリーフラグが変化しますが、減算時の桁下がりのことは「ボロー」と呼ぶのが一般的だと思います。
AVRではボローが発生したときにキャリーフラグに1がセットされますが、アーキテクチャによってはボローが発生しなかったときにキャリーフラグが1にセットされるものもあるようです。
命令セットシミュレータでの実装
こういうビットが並んでいるデータを扱うにはビットフィールドが便利です。
たとえば次のような構造体を定義します。
struct status_flags
{
_Bool I : 1;
_Bool T : 1;
_Bool H : 1;
_Bool S : 1;
_Bool V : 1;
_Bool N : 1;
_Bool Z : 1;
_Bool C : 1;
};
ところが、ビットフィールドでどんな順にビットが割り付けられるかは処理系定義なんです。
上の例ではMSB(最上位ビット)から順に割り付けられることを前提としていますが、実際にはLSB(最下位ビット)から順に割り付けられるかもしれません。
これはリトルエンディアンやビッグエンディアンといったバイトオーダーとは別の概念ですので、バイトオーダーが決まってもビットフィールドの割付け順は決まりません。
ちょっとそれでは困るので、今回の実装ではもっとベタな方法を採用しようと思います。
uint8_t status_register;
#define flag_I 0x80
#define flag_T 0x40
#define flag_H 0x20
#define flag_S 0x10
#define flag_V 0x08
#define flag_N 0x04
#define flag_Z 0x02
#define flag_C 0x01
このようにして、その都度ビット演算で各フラグを設定・取得するようにします。
ビット演算は今どきのほとんどのプログラミング言語には備わっている機能なので大丈夫ですよね?
今回はここまでです。
AVRのメモリとレジスタを一通り見てきましたので、次回はそれらを組み合わせてCPUの基本的なデータ型を固めようと思います。
そしてできればリセット動作までいければいいなと考えています。
どうぞご期待ください!