画像も文章もプログラムも、メモリにはビット列として置かれます。それでもCPUが画像を足し算の命令として実行しないのは、どこから何を読み、どう解釈するかを回路とソフトウェアが決めているためです。まず「ビット列そのもの」と「その解釈」を分けて考えましょう。
二進数は桁の重みで読む
1ビットは0か1を表します。4ビットの1011は、左から8、4、2、1の重みを持つので、8+0+2+1=11です。nビットには2のn乗通りの並びがあります。符号なし整数として読むなら、0から2のn乗−1までを表せます。8ビットなら0〜255です。
16進数は4ビットを1桁にまとめた表記です。1010はA、1111はFなので、1010 1111は0xAFになります。見やすく書き換えているだけで、メモリに保存される値が変わるわけではありません。アドレスやマスクを読むときに役立ちます。
負の数は2の補数で表す
8ビットの2の補数では、最上位ビットの重みを−128と考えます。1111 1111は−128+64+32+16+8+4+2+1=−1です。同じ並びを符号なし整数として読めば255になります。表現できる範囲は−128〜127で、正負は対称になりません。
0000 0101 # 5
反転 1111 1010
加1 1111 1011 # 8ビットの -5
0000 0101 + 1111 1011 = 1 0000 0000
下位8ビットを残すと 0000 0000この性質によって、同じ加算回路を負数にも使えます。範囲を超えた結果の扱いは別に考えます。例えば8ビット符号付きとして127+1を計算すると、下位8ビットは1000 0000ですが、数学上の答え128はこの範囲では表せません。言語ごとのオーバーフロー規則と、CPU命令の規則も区別が必要です。
論理演算から加算回路へ
ANDは両方が1のときに1、ORはどちらかが1なら1、XORは二つが異なるときに1、NOTは0と1を反転します。1ビット同士の加算では、和の下位ビットをXOR、桁上がりをANDで作れます。桁上がりの入力も受け取る全加算器を並べると、多ビットの加算器になります。
実際のCPUでは、論理演算や加算を行うALU、値を一時的に保つレジスタ、次に読む命令の位置を示すプログラムカウンタなどを組み合わせます。回路は1か所で全部を計算するのではなく、制御信号に応じて値の通り道や演算を選びます。
命令セットはソフトウェアとの約束
ISAは、プログラムから見える命令、レジスタ、命令の意味などを定める約束です。同じISAでも、パイプラインの段数やキャッシュ容量が違うCPUを作れます。ISAと、その実現方法であるマイクロアーキテクチャを分けると、互換性と速度の違いを説明できます。
lw t0, 0(a0) # a0が指す場所から32ビットを読む
addi t0, t0, 1 # t0の値に1を足す
sw t0, 0(a0) # 結果を同じ場所へ書くこれはRISC-Vの例です。RV32Iでは、メモリへのアクセスはロードとストアが担当し、整数演算はレジスタ上の値を扱います。上の3行は「メモリ上の整数を1増やす」という一つの目的でも、読取り・演算・書込みという別の命令です。この違いは、後で競合状態を考えるときにも効いてきます。
確かめてみよう
問1:8ビットの1111 1110を符号なし整数と2の補数で読むと、いくつでしょうか。答えは254と−2です。最上位ビットを含め、どの重みを割り当てるかが変わります。
問2:同じRISC-Vプログラムが動く二つのCPUは、同じ速度でしょうか。速度は一致しません。ISAが同じでも、命令を処理する回路、キャッシュ、動作周波数などが違うためです。
命令の重なり方は、CPU・命令サイクル・パイプラインで追います。