標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第194回]

●減算命令の回路の説明の続きです

前回に引き続いて、減算命令の回路の説明をいたします。
2進数の減算は、引く数の「2の補数」を作って、それと引かれる数との加算を行えばよい、という説明をしました。
そして「2の補数」を作るために、ALU回路のレジスタ”B”に、引く数の1と0を反転した数を入れるように、考えました。
しかし、それだけではまだ足りません。
1と0を反転しただけでは、まだ「2の補数」にはなりません。「1の補数」になっただけです。
「1の補数」にさらに+1すると「2の補数」になります。

1と0を反転するところまでは、インバータ74HC04を使ってうまくいきました。
しかし、ここにさらに+1するために、何かの計算回路を付け加えるのは、どうにも面白くありません。
すぐに思いつくのは、INR回路で使った74HC191などのUP/DOWNカウンタを使うことです。
でも、たった+1するためだけに、カウンタを使うのは、なんともばかばかしい感じですし、おまけに、レジスタは8ビットなのに、カウンタは4ビットですから、カウンタを使うなら、2個使わなければなりません。
なにか、よい方法はないものでしょうか。

2進数の減算の仕組みをじっくりと考えてみましょう。
いま、Aを引かれる数、Bを引く数だとします。
A−Bの計算を、2進数の場合で考えてみます。
2進数の計算では、A−Bは、A+(Bの2の補数)という計算をすればよい、ということでした。
さらにそれは、A+(Bの1の補数+1)というように考えられます。
ということは、A+(Bの1の補数)+1を計算してもよい、ということになります。

さてそれでは、もう一度、加算回路([第191回])を見てみましょう。
加算器74HC283をよくよく見てみると、キャリー入力があることに気がつきます。
これは、下位桁からの桁上がりを加算するためのものです。
下位桁からの桁上がりがあるときには、2つの数の加算に加えて、さらに+1加算をします。

そうです。これを利用してしまえばよいではありませんか。
これが、前回の宿題の答えです。

うまくいくかどうか、検証してみましょう。

1)56−27=29を計算してみましょう
10進数の56、27は16進数ではそれぞれ38、1Bになります。
38−1Bの計算です。
1B(00011011)の1と0を反転して「1の補数」をつくります。11100100ですから、E4です。
このE4と38に、さらにキャリーを加えた、加算を行います。

   2進数          16進数     10進数の計算(減算)
  00111000         38        56 
  11100100         E4         27(− 
         1(+        1(+     29
1 00011101       1 1D 

計算の結果得られた16進数の1Dは、10進数では29ですから、正しい結果が得られているようです。
ただ1つだけ問題が残ります。
計算の結果、上位桁へのキャリーが発生してしまうことです。
減算の場合には、キャリーと言わずに、ボロー(borrow)と言います。
減算の場合には、上の桁から1を借りてきますから、borrowです。
でも、10進数の計算で、56−27は、結果は正の数ですから、ボローは発生しないはずです。

そこで、今回のように計算の結果ボロー(キャリー)が出たら、それを反転する、ということにしてみましょう。
つまりキャリーが出たら、キャリーフラグをクリアする。逆にキャリーが出なかったら、キャリーフラグをセットする、ということにするのです。

それで、うまくいくかどうか、もうひとつ試してみましょう。

2)56−67=−11の計算です。
こんどは結果が負の数になりますから、キャリー(ボロー)がでる計算です。
10進数の67は16進数では43です。
16進数の、38−43の計算です。
43(01000011)の「1の補数」は、10111100(BC)です。

   2進数          16進数     10進数の計算(減算)
  00111000         38        56 
  10111100         BC        67(− 
         1(+        1(+    −11
  11110101         F5 

計算の結果得られたF5は符号付の数として考えると、−11になりますから、正しい結果が得られているようです。
今度は、計算の結果、キャリー(ボロー)は発生しませんでした。
ですから、さきほどの約束にしたがって、今回はキャリーフラグをセットします。
今回は引く数の方が引かれる数よりも大きいですから、もしまだ上位桁がある数の、下位桁部分の計算だったとすると、この桁だけでは引ききれませんから、上位桁へのボローを出すことになります。

上の2つの計算は、正の数同士の計算でした。
では、負数の減算はどうなるでしょうか?

3)56−(−67)=123の計算です。
−67は、16進数ではBDです(つまり67の「2の補数」です)。
16進数の38−BDの計算になります。
BD(10111101)の「1の補数」は、01000010です。16進数では42です。

   2進数          16進数     10進数の計算(減算)
  00111000         38        56 
  01000010         42       −67(− 
         1(+        1(+    123
  01111011         7B 

計算の結果得られた7Bは10進数に直すと、123になりますから、正しい結果が得られているようです。
ところがここでひとつ問題が出てきます。
今回の計算は、ボローが発生しませんから、さきほどの約束で、キャリーフラグをセットすることになります。
あれえ。おかしいですねえ。

ここでキャリーフラグがセットされては、まずいのでは?

いえ。じつは、この計算ではキャリーフラグがセットされるのが正解なのです。
なぜでしょうか?

10進数の計算で、56−(−67)の計算は、実は56+67=123という計算をしていることになります。
では2進数の計算はどうでしょうか?
実は、上の3)の例はフェイク(fake)なのです。
2進数8ビットの加算命令ADD、減算命令SUBは、本当は符号付の数としての加算、減算は行わないのです。
3)の例で、−67は16進数ではBDなので…、と説明しましたが、ここがフェイクなのです。
16進数のBDは10進数の−67なのだから、と考えて、38−BDという計算をしたつもりでも、CPUには、それが符号付の−67という数なのか、それとも符号無しの数なのかはわかりません(符号無しの数としてのBDは10進数では、189になります)。
なぜなら、その数を符号付の数とするか、符号無しの数とするかは「プログラマの勝手([第14回]参照)」なので、CPUのあずかり知らぬところだからです。

CPUはあくまで、ふつうの(符号無しの)数としての計算をします。
つまり、16進数の38−BDという計算は、10進数の56−(−67)ではなくて、56−189の計算になるのです。
当然、56から189は引けませんから、上位桁の1を借りてくることになります。つまりボローが発生するのです。
2進数の計算で上位桁の1を借りる、ということは、256を借りてくることになります。

注)この表現は舌足らずでした。
ここでの計算例は8ビットの数同士の計算なので、その上位ビット、つまり9ビット目は100000000なのでこれを10進数に直すと256になります。
ですから8ビットの数で、上の桁(上のビット)から1を借りてくる、ということは、10進数に直せば、256を借りてくる、ということになるのです。
(この部分、3月31日追記)

上位桁があることが前提での減算ならば、138−BD=7Bという計算が行われることになります。
上位桁へのボローが出るため、計算の結果、上位桁の1が無くなっています。
16進数の138は10進数の312になりますから、
10進数では、312−189=123という計算がおこなわれたことになります。

なんだか、わかったような、わからないような…。
でも、そういうことなのです。
とにかく、減算の場合には、引く数の「1の補数」と引かれる数と、それにキャリーの1を加算すればよいらしい、ということがわかりました。

ところが、ここでまた難問が出てきます。
今までの、減算の方法は、SUB命令についてはあてはまります。
SUB命令は、計算前のボロー(キャリー)は無視する減算命令です。
では、下位桁からのボローを引く、SBB命令については、どうすればよいのでしょうか?

2009.3.30upload
2009.3.31追記

前へ
次へ
ホームページトップへ戻る