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

●ADD、SUB、CMP命令の回路の説明の続きです

回路の説明をするためには、ADD命令とSUB命令の動作を理解することが必要です。
CMP命令はSUB命令と全く同じ動作をしますが、計算結果をAレジスタに入れないで、結果のフラグのみ変化します。CMP命令はADDやSUBとは命令コードの並びも少し異なっているため、同列には扱えません。
ですから、CMP命令については、しばらく忘れていてください。

そこで、ADD命令とSUB命令について考えていきます。
なお、前回にも説明しましたように、算術演算命令には、ADDとADIというように、レジスタ、メモリとAレジスタとの間で計算を行うものと、定数とAレジスタとの間で計算を行うものとの2種類がありますが、計算の仕組みは全く同じですから、両者は基本的な計算回路は同じになります。

ADD、ADIとADC、ACIとの違いは、前者が計算前のキャリーフラグを計算に加えない(無視する)のに対して、後者は計算前のキャリーフラグが立っていたら(CF=1だったら)、それを下位桁からの桁上がりとして、計算に加えるという点です。
これはわかりやすいと思います。
いずれの命令も、加算した結果、上位桁への桁上がりが生じたら、キャリーフラグをセットし、桁上がりが生じなければ、キャリーフラグをリセットします。
これも、そのままですから、わかりやすいと思います。

ただ1つだけ問題があります。
それは負数の扱いです。
8ビットの数には、符号無し数と符号付数の2通りがあります。
これがなかなか理解しにくいところです。
この辺の説明は、[第14回]あたりでかなりしつこくしています。

●ADD命令の計算

簡単に復習です。
8ビットの2進数は、16進数で表記すると00〜FFの256通りです。
これはこのまま10進数に直すと、0〜255の大きさの数になります。
このように扱うときの2進数の00〜FFは、「符号無し」の2進数です。
一方、同じ00〜FFを「符号付」の2進数として扱うこともできます。
この場合には、00〜7Fが正の数で、10進数の0〜127の大きさの数になります。
そして80〜FFは、−128〜−1の大きさの数になります。
なぜそうなるのか、については[第14回]で説明していますので、うーん、わからん、という方はそちらをご参照ください。

ADD命令の計算について、いくつかの例をもとにして考えてみます。
加算の場合には、2つの数の大小は関係ありません。
正負の数については、どうなるのか確認してみたいと思います。

1)まず、念のために、正数同士で、キャリーが出ない場合について。

   2進数          16進数      10進数
  00010010         12        18 
  01010110(+       56(+      86(+ 
  01101000         68       104 

いいようですね。

2)今度もキャリーは出ませんが…。

   2進数          16進数      10進数
  01010110         56        86 
  01111000(+       78(+     120(+ 
  11001110         CE       206   

今度もいいようですね。
あ。ちょっと待ってください。

今回は、計算の結果、S(サイン)フラグが立ってしまいます。
CEは符号無しの数として扱うのなら、206ですが、符号付の数だと考えると、−50になってしまいます。
正の数と正の数を加算しているはずなのに、結果が負の数になってしまいました。

なぜ、こんなおかしなことになってしまうか、といいますと、計算にムリがあるからです。
さきほど、8ビットの2進数を符号付の数として扱うときは、(正の数は)0〜127だと、申しました。
すると、符号付の数の計算では、86+120は、結果が127よりも大きくなってしまいますから、計算ができないのです。
この計算の場合、Z80ではV(オーバーフロー)フラグがセットされます(8080にはオーバーフローフラグはありません)。

できない計算をムリにさせると、おかしな結果になりますが、それはCPUのせいではありません。
そういうムリをCPUにさせようとする、プログラマが悪いのです。

符号付の数ではなくて、符号無しの数として扱うならば、上の計算には、なんの問題もありません。
符号付の数として計算をさせる場合には、計算の前後で、扱える値の範囲を超えていないかどうかを、確認する必要があります。

3)それでは、キャリーが出る場合はどうでしょう。

   2進数          16進数     10進数
  11111110         FE       254 
  11111100(+       FC(+     252(+ 
1 11111010       1 FA       506   

今回は上位桁への桁上がりが発生する計算です。

上の計算を符号付の数として考えたら、どうなるでしょう。
FEは符号付の数と考えると、−2になります。FCは−4です。
すると−2+(−4)=−6という計算になります。
FAは−6ですから、正解です。

なお、この場合に上位桁に送られるキャリーは、この計算が8ビットではなくて、16ビット以上の場合に意味をもってきます。
8ビットの数に限定しての計算だとすると、符号無しの数としては、オーバーフローですが、Z80でも、この場合にはオーバーフローフラグはセットされません。符号付の数として考えたときは、キャリーは意味のないものとして扱います。

4)正の数と負の数の加算はどうでしょう。

   2進数          16進数     10進数
  01010110         56        86 
  11001110(+       CE(+     206(+ 
1 00100100       1 24        292 

これは符号無しの2進数として考えた場合の計算です。
では、これを符号付の2進数として考えたら、どうなるでしょう。
CEは符号付の数として考えた場合には−50になります(この数は上の計算で出てきました)。
すると、上の計算は、86+(−50)=36という計算である、とも言えるはずです。
キャリーフラグが立ちますが、今回はオーバーフローではありません。このキャリーは無視をして、8ビットの結果の部分だけに注目してみます。
すると、24です。16進数の24は10進数の36です。
合ってますね(なんだか、手品でごまかされてしまったみたいな気になりますが)。

5)もうひとつ、正の数と負の数の加算を考えてみましょう。

   2進数          16進数     10進数
  00100111         27        39 
  11001110(+       CE(+     206(+ 
  11110101         F5        245 

今度は、キャリーは発生しません。
これも符号無し数としての計算です。
さきほどと同じように、符号付の数の計算として考えてみましょう。
39+(−50)=−11ということになるはずです。
F5は符号付の数として考えたときには、−11になりますから、結果は合っています。

余計な話をして、わかりにくくなってしまったかもしれません。
その計算がオーバーフローかどうか、数の範囲を超えているかどうかは、プログラマが判断することで、CPUはただの2進数として計算をします。
その計算の結果上位桁へのキャリーが出れば、それが意味のあるものかどうかにかかわらず、キャリーフラグをセットします。
また、その数が符号付の数であるか符号無しの数であるかも、プログラマが定義することで、CPUはただの2進数として計算を行います。しかし、その計算は、それを符号付の数として考えたときにも、ちゃんと辻褄があうようになっています。

加算命令とキャリーフラグとの関係を整理しようとして、余計なお話になってしまいました。
ようするに、加算の場合には、キャリーフラグは出るべくして出る、ということです。

あ。忘れていました。
ADC、ACIのときは、下位桁からの桁上がりがある場合(キャリーフラグが立っている場合)には、キャリーを1と考えて、計算結果に+1を加えます。
これも当たり前の話なので、ごく普通に理解できると思います。

まとめます。
ADD、ADIは加算前のキャリーは無視しますが、加算した結果、上位桁への桁上がりが発生した場合には、キャリーフラグをセットします。桁上がりが発生しなかった場合には、キャリーフラグはリセットされます。
ADC、ACIは加算前にキャリーフラグが立っていたら、計算結果に+1を加えます。加算した結果上位桁への桁上がりが発生した場合には、キャリーフラグをセットします。桁上がりが発生しなかった場合には、キャリーフラグはリセットされます。

さて、問題は、SUB、SUIとSBB、SBIの計算です。
さきほどは、いろいろな計算例をたくさん並べましたが、じつは減算について考えるための、いわば布石でもあったのです。
そこで、その減算の方法なのですが…。

というところで、またまた時間がなくなってしまいました。
この続きは、次回にすることにいたします。
2009.3.26upload

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