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

●まずは、パリティフラグについて、少しだけ

ことのついでにパリティフラグについて、ざっと説明しておきたいと思います。
パリティの本当の意味については、よく知りません。
コンピュータの世界では…、

あ、やっぱりこういういいかげんなことは、良くありません。
parityについて辞書で調べてみると…。
同等、等価、等しいこと、均衡…、などということのようです。
うーん。なんだ、辞書を引いても、よくわからんではないか。

コンピュータの世界では、パリティとは、1であるビットの数が偶数個であるか奇数個であるかをチェックするときに用いられる言葉です。たとえば1バイトのデータ(8ビット)で、1のビットの数が、0、2、4、6、8個のときが偶数(even)で、1、3、5、7個のときが奇数(odd)です。

あ、数値が偶数であるか、奇数であるかではありませんから、お間違えなく。
2進数の場合には、最下位ビット(ビット0)が1なら奇数、0なら偶数ですから、数値の奇数偶数はわざわざフラグを用意するまでのことはありません。

では、どういうときに、パリティフラグがセット、リセットされるかというと、8080やZ80では、論理演算命令(OR、AND、XOR)の実行時に、結果によって、セット、リセットされます(このことをよーく覚えておいてください)。

そんなもの、何に必要なの?

普通はまず、使うことはありません。たいていはシリアル通信などで、誤り検査に利用したりする程度です。
シリアル通信の代表格はRS232Cですが、いまどきはUSBにお株を奪われてしまい、ずっと影が薄くなってしまいました。

ある装置から別の装置にデータを送るときに、4ビットとか8ビットとかのデータをそのまま送るのが一番簡単です。シリアル通信に対してパラレル通信といいます。プリンタケーブルがよい例ですが、これも最近はUSBになってしまいました。

以前はRS232C通信は、通信環境がよくないところで使われることがよくあって、データが化けてしまう(本当は1なのに0になってしまうとか、逆に0なのに1になってしまう)ことがありました。シリアル通信の場合には、電波と同じでノイズが乗ったり、送信のタイミングがずれたりということで、簡単に1と0がひっくり返ってしまうことがあります。
そのようなときに、本来のデータにさらに1ビットを追加してそのビットも含めてつねにパリティが偶数になるようにして送信し(偶数パリティ)、またはつねに奇数になるようにして送信します(奇数パリティ)。こうすることによって、受信側で、データが化けたりしていないかどうかを知ることができます。
この追加されたビットをパリティビットといいます。
もっともたまたま1個だけ、1と0がひっくりかえってくれればこの方法でエラーが発見できますが、2個とか4個とかひっくりかえったりすると、もう、どうしようもなくなります。

まあ、それほどひどい状態なら、通信すること自体が困難ということかもしれませんが。

●コンピュータで使われる文字コードのお話

またまた、話は脱線してしまいます。しばしお付き合いを。

いままでは十進数を2進数に置き換える話をしてきましたが、文字もコンピュータで扱えるようにするために、2進数に置き換えてしまいます。
十進数と違い文字は言葉のもとですから言語圏の違いによってさまざまなバリエーションがあります。日本ではひらがな、かたかな、漢字を使います。
同時に英語圏にもかかわりが深いのでアルファベットも一般的に使用します。

そこでまずアルファベットのお話です。
文字Aを2進コードでは、01000001(41)を割り当てて使うことが一般的です(十進数→2進数とは違って、絶対的なルールではありません)。

前に十進数と2進数のお話の時に、十進数の0〜9を2進数的なコードで表すものとしてBCDコードというものがある、という説明をしました。
BCDコードは1桁の数字0〜9を2進4ビットで表します。1バイトは8ビットですから、BCDコードなら十進数2桁を入れられます。
一般にコンピュータ、RS232C通信などでは、文字情報データを7ビットまたは8ビットで示します。
漢字は種類が多いので1バイト8ビットではとても表すことができません。漢字を2進コードで示すためには2バイト16ビットが必要になります(まあ、それは別のお話です)。

アルファベットA〜Z26文字(およびa〜z)は合わせても52文字ですから、1バイト8ビット全部をまるまる使わなくても7ビットで十分間に合います。7ビットあれば128種類の文字を表すことができますから、アルファベットだけではなくて、ついでに数字の0〜9も一緒にこの7ビットのコードに割り当ててしまいました。
それがASCIIコードです。7ビットしか使わないので、7ビットコードとも言います。厳密にはASCIIコードと7ビットコードとは同じ意味ではありませんが(と思いますが)、まあ、普通には、同じことだと考えてもよいと思います。

●ASCIIコード

参考までにASCIIコードを下に示します。

下位4ビット
上位4ビット LF CR
sp

[表の見方]表は1バイト8ビットの2進コードを上位4ビット下位4ビットに分けてそれぞれ16進数で表示しています。
たとえば文字Aは上位4ビットが4の行と下位4ビットが1の列の交点にあります。AのASCIIコードが41であることを示しています。
同様に、数字の0のASCIIコードは30であることがわかります。
文字が表示されていないところは、国によって異なる文字が割り当てられたりしているか、特殊な制御コードとして使われます。
コード5Cには¥があります。日本では用いられることが多いのでここに入れてありますが、当然のことながら世界共通ではありません。
コード20はスペース(空白)です。
コード00〜1Fは特殊な制御コードとして用いられます。

●7ビットコードと8ビットコード

ASCIIコードではコード80〜FFは使いません。つまりアルファベットは1バイト8ビットのうち実質7ビットしか使用していません。
そこで英語圏でははじめから文字として8ビットのうち7ビットのみを使い、残った最上位ビットは使わないで捨ててしまうか、別の目的に使ったりします。これが7ビットコードです。

わが国ではASCIIコードで使われていない80〜FFに半角のカナ文字を割り当てて使用することが一般的です。つまり8ビットコードです。
インターネットは日本国内だけではなく、さまざまな国の人たちと情報を共有しています。8ビットのうちコード80〜FFに半角カナを割り当てているのは、当然日本だけで、外国の人たちは、「そんなことは、知らないよ」というわけで、それぞれ別の文字や制御を割り当てています。

文字コードについてはこれ以上詳しいことは知りません。だから、以下はただの想像、推測です。
多分7ビットコードか、それ以外のコード(8ビットとか16ビット)かの切り替えマークとして7ビットコードでは使われていない80〜FFが利用されているのだと思います。
メールの見出しや送信人の名前などに「半角カナは使ってはいけない」などといわれているのは多分そういう理由からだと思います。
半角カナを区別できない、世界共通のソフトなどでは、半角カナコードを見つけると、別の文字コードや別の特殊処理への切り替えスイッチがONになってしまい、文字化けしたり、暴走したりしてしまうことになったりします。

●お話をもとに戻します。7ビットコードと8ビットコードの、別の切り口です

なんだか、どんどん別の軌道を走っていってしまいます。
お話をちょいと元に戻します(戻してもすぐに、脱線してしまいます)。

ASCIIコードは7ビットなのだ、という説明をしました。
でも1バイトは8ビットですし、メモリもたいていは8ビットか16ビットなのですから、7ビットコードだからといって、8ビットのうちの1ビットを使わないことにしても、何のメリットもないように思えます。

しかしこれがシリアル通信ということになると、大いに関係してくることになります。
ひとつの文字をパラレルからシリアルに変換して、並列ではなくて直列に伝送するわけですから、8ビットコードならば8回パルスを送らなければならないところを、7ビットコードならば7回で済んでしまいます。1割以上伝送速度が上がるわけですから、これは無視できません。

なおこれも厳密に言うと、8ビットコードの代わりに7ビットコードを使っても、単純に8/7倍のスピードアップにはなりません。
RS232C通信(非同期、調歩同期式)などでは、文字コードをサンドイッチにするようにその前後にスタートビットとストップビットを送りますから、8ビットコード1文字を送るのには最低でも10パルスは必要になります。
文字コード本体が8ビットから7ビットになったとしても、スタートビットやストップビットはそのまま必要ですから、10/9倍のスピードアップということになります。
それはともかくとして、RS232C通信をマシン語やBASICなどで使う場合に、通信速度(ボーレート)とともに、文字コードが7ビットなのか8ビットなのかを指定しなければならないのは、このあたりの事情からなのです。

●7ビットコードとパリティチェックは相性がいい

文字を通信で扱う場合に、7ビットコードを使ったとしても、1バイトは8ビットで8ビットCPUはこの単位で動くのだから、通信速度以外には、それほどメリットはない、いうならばいつも1ビットが使われないで、無駄な状態で残っている、とも考えることができます。
そこで、どうせ空いている1ビットならばパリティチェックのために利用してしまおう、というわけです。

たとえば、さきほどの例で文字AはASCIIコードでは、01000001(41)でした。7ビットコードでは、1000001ということになります。そこでここに1ビットのパリティビットを追加して、合計8ビットのパリティがつねに偶数になるようにしたのが、偶数パリティ方式です(even)。
文字Aのコードには1が2つあって、もともと偶数個ですから、パリティビットは0のままにして、1000001として送信します。
逆にパリティビットを含めた8ビットの中の1のビットが奇数個になるようなルールにしたのが、奇数パリティ方式です(odd)。
奇数パリティ方式ならば、文字Aは1000001として送られることになります。

●やっとパリティフラグの登場です

受信側ではこれをどうするか、というと、ここでやっとパリティフラグ君の登場です(ここまでくるのが、長かった!)。

一般的にはシリアル受信のバッファはCPUの外の外部I/Oとして設計されていることが多いので、その受信バッファから受信データを引き取るのには、通常は、IN命令を使います。

IN 受信バッファアドレス

の実行で、受信データ(8ビット)がAレジスタに読み込まれます。
そこで、

ORA A

を実行すると、AレジスタとAレジスタ(つまり同じ値)のORなので、この命令を実行しても、Aレジスタの中身は全く変化せず、そのときの値によって、フラグだけが変化します。
パリティフラグもON、OFFするので、この命令を実行するだけで、簡単にパリティチェックができてしまいます。

パリティチェックが済んで、受信コードに誤りがないことがわかったら、もう、送信元で追加したパリティビットは不要なので、

ANI 7F

を実行すれば、簡単にパリティビットだけを削除して、もとの(0)1000001に戻すことができます。
ANI 7F という命令は、Aレジスタの値と7FとのANDをとって、結果をAレジスタに戻す命令です。
このようにANA(またはANI)命令は8ビットのうちの特定のビット(1ビットだけに限りません)を0クリアする目的でよく用いられます。

●しかし、8ビットコードではこうは都合よくはいきません

こうやって見てくると、なんだか非常に都合良くできているように見えますが、それはあくまで7ビットコードでのお話です。
もとの文字コードを半角カナまで含めて8ビットコードにした場合には、パリティビットを追加すると、全体では9ビットになってしまいます。

すると、受信バッファから受信データを引き取る場合でも、パリティビットを含めた9ビットは一度に読み込むことはできません。
わざわざパリティビットの処理だけのために、なにもかも二度手間になってしまいます。
7ビットコードの場合には、どうせ空いている1ビットだから、ということで気安く追加した、パリティビットだったのですが、わが国の特殊事情で8ビットコードにした途端に、なにもかもが食い違ってしまい、余計なお荷物をしょいこんでしまったかのような、ゴテゴテしたプログラムになってしまうのです。

こんなときには、自分が英語圏に生まれず日本に生まれてしまったことを、ちょいとうらめしく感じてしまいます。
ひらがなもかたかなもない、中国の人たちは、もっと大変だろうなぁ、と思ってしまうのですが、それにしては、彼らは実に元気で、アグレッシブなことに、心底感心してしまいます。

●お話はさらに脱線してしまいます

パリティフラグからだんだんお話が横にそれてきてしまって、もう完全に脱線状態ですけれど、ついでなのでもう少し。

マシン語プログラムでRS232C通信を行うような場合に、勘違いをしてしまう方がいます。
文字を送受信する場合は、アルファベットならば、プログラムの中でもASCII(アスキー)コードで扱っているはずですから、たいていはそのコードをそのまま送るので問題は生じません。
しかし、数値データを送ろうとすると、はてな?ということになってしまいます。

●BCD数と十進数と2進数、その上さらに今度は文字コードです!

BCDコードならばまだなんとかなりそうな気がするのですが、2進数となると、いったいこれはどうすればよいのでしょう。
そこで、たとえば8ビットだとか16ビットだとかの2進数のデータをそのままRS232Cなどで、パソコンに向けて送信してしまう方などがでてきてしまいます。

えーっ。だめなんですかぁ?
だって、コンピュータは十進数ではなくて、2進数で計算したり処理したりするって言ったじゃないですか?
メモリに記憶するのだって、いまどきはBCDなど使わないで、全部2進数で記憶したりしてるんでしょ?

それは、そうなのですが、データを送信するときは、別なのです。
さきほど説明したASCIIコードの表をもう一度よく見てください。
コード0Aとコード0Dのところに、LF、CRと書いてあります。
これがRS232C通信などでは重要なコードなのです。

●LFとCR

LFはラインフィード、改行です。CRはキャリッジリターン、復帰です。CRLFと2つを続けて使います。復改などと言います。
これはもともとタイプライターで使われていた用語(だったと思います)で、プリンタヘッドが用紙の左端に戻るための命令がCRで紙送り命令がLFです。
RS232CはもともとTTY(テレタイプだったかな?)を制御するために使われた通信方法で、そのために、110ボーというえらく中途半端なボーレートが存在したと記憶しています。あ、なにか、化石のような、お話になってしまいました。

いまどき、そんな太古の昔の装置のようなものを制御するなどということはありませんが、どっこい、制御コードだけは生き残っていて、今でも使われているのです。
いまでも、notepad(メモ帖)などで作成されるスタンダードの、拡張子がtxtのファイルは、行の終わりにこのコードを使っています。行の最後は0Dと0Aを続けて使います。CR・LFです。

さてそこで、問題のシリアル通信です。
RS232Cも行の終わりには0D・0Aコードを送ることが一般的なルールになっています。
あれえ、0Aって、どこかで見たような…。

そうです。10進数の10を2進数に変換すると、0Aになりました。0Dは十進数の12です。
そうすると10とか12に相当する2進数データをそのまま、RS232Cでパソコンに送ったとしても、パソコンはそれをデータとしては認めてはくれません。

なんじゃい。データかと思ったら、CRとLFか?

ということになってしまいます。
それだけではありません。2進数のデータはそのままで計算に使うことができますが、その値そのものをプリンタに送っても、数字を印刷してはくれません。RS232C通信でも、全くその通りなのです。
せっかく計算しやすいように2進数に直して記憶しているのに、通信したりプリンタに出力したりするためには、またもとの十進数に戻して、その十進数をASCIIコードに直してやらなければいけないのです。

●例をあげて説明します

ここに十進数で9645というデータがあります。
これを2進数に直すと、25AD(16進数表記)になります(2進表記では、0010 0101 1010 1101になります)。
16ビットですから2バイトの数です。
このデータをパソコンなどにRS232Cを使って送信する場合に、最初に25、次にADなどと送信してはいけません。
どうするか、というと、2進数→10進数変換ルーチンを使って(インターネットを検索するとライブラリルーチンが見つかるかもしれません。でも本当は自分で書いてみないことには、なかなか上達することはできませんよ)、もとの10進数のASCIIコード表現のデータを作成します。9645をASCIIコードで表現すると、39・36・34・35になります。この4バイトを送って、最後に0D・0Aを送ります。

なお、今説明した通信のルールはごく一般的なルールで、なにがなんでもこの通り、ということではありません。
送信側と受信側で、同じルールのプログラムを動かすことができるのでしたら、いちいちせっかくの2進数を十進数に変換して送信し、受信側でまた十進数から2進数に戻して…などという面倒くさいことをしなくても、2進数のまま送信することも可能です。

(以上ここまで、2008.7.29大幅に加筆いたしました)

●それでは、やっと、オーバフローフラグのお話です

Z80では同じフラクビットをパリティフラグとオーバフローフラグで共用していますけれど、パリティとオーバーフローとは全く何の関係もかかわりもありません。むしろ、全くかかわりがないからこそ、同じビットを共用することができたのかもしれません。
そのことについては後ほど説明することにいたしましょう。

オーバーフローフラグとは、計算で扱える数値の限界を超えて計算をさせようとした場合にセットされるフラグです。
しかしその言い方はあいまいです。コンピュータはあいまいなことが苦手なので、もっと具体的にはっきり教えてやらなければ、何もしてくれません。
残念なことに、どうも8080には、オーバーフローフラグがなかったようですので、8080に対してオーバーフローについて定義してあげても、無意味かもしれません。

そうか。8080はオーバーフローしても、素知らぬふりで計算を続けてしまうんだ。

ですから、ここはZ80のお話になってしまいます。8080とは関係がないお話ですから、まあ、言ってみれば、一般教養のようなものです。

そこで、では、具体的にどういう場合にオーバーフローフラグがセットされるか、ということですが、その前に、Z80では、どのような命令でオーバーフローフラグがセット、リセットされるかをお話しておきます。
Z80では、ADD、SUBなどの数値演算命令とINC(INR*注)、DEC(DCR*注)命令を実行したときに、その結果によって、オーバーフローフラグがセット、リセットされます(実はZ80では、計算に関係のない特殊な命令でもこのフラグを使っています)。

8080にはオーバーフローフラグがなかったことがわかってしまったので、その関係の回路は削除してしまいますが、今回製作中の、TTLでCPUをつくる組立キット、の2番目の基板には、まだ数値演算(ALU)の回路は入りません。それは3番目の基板になります。
2番目の基板でオーバーフローフラグに関係するのは、INR、DCR回路だけです。

INRはincrementのことで、レジスタやメモリの値を+1増やす命令です。ループカウンタなどによく使います。これに対してDCRはdecrementのことで、INRとは逆にレジスタやメモリの値を−1減らします。

*注8080ではアセンブラニーモニックでINR、DCRと表記しますが、Z80ではINC、DECと表記します。8080とZ80でアセンブラの表記は異なりますが、マシン語コードは同じです。

●8080はオーバーフローなどいたしません(?)

8080にはオーバーフローフラグがありませんから、どんな計算をさせても、オーバーフローはしません、
というような、ことはありません。

8080でもできない計算を無理にさせれば、立派にオーバーフローしてしまいます。しかし何度も説明しましたように、8080にはオーバーフローしてしまったことを知らせるフラグがありませんから、プログラムを工夫してそのような計算にならないように注意する必要があります。

余談ですが、先日インターネットで、中国についての記事を読んでいたら、中国の某銀行のATMで、うっかり預金残高がマイナスになってしまうような引出し操作をしたところ、マイナスにならないで、逆に、現金を引き出せば引き出すだけ、預金残高がどんどん増え続けるという、前代未聞(果たしてそうかな?意外とよくあるプログラムミスなのでは)の出来事についての記事を目にしました。
その銀行プログラムのどこに誤りがあったのかは知りませんが、本来マイナスにならなければならないはずの残高がプラスになって増え続けるなどというミスは、人間ならば滅多にしそうもない誤りですけれど、コンピュータは、よほど注意してプログラムを書かないと、このテの信じられないミスをいとも簡単にやってくれてしまいます。

余談は置いて、オーバフローフラグのお話に戻ります。
ADD命令、SUB命令でオーバーフローフラグがセットされる条件は、ちょっと考えてみなければいけません。
でもINC命令、DEC命令では意外に簡単です。

INC命令では実行後に結果が80になったときに、オーバーフローになります。
DEC命令では実行後に結果が7Fになったときに、オーバーフローします。
なぜでしょう?

前に、符号付2進数のお話をしたときに、1バイト(8ビット)の符号付2進数の取り得る値の範囲は、0および正数値は00〜7F(0〜+127)で、負数値はFF〜80(−1〜−128)である、という説明をしました。
INC(INR)命令を実行すると、そのレジスタまたはメモリの値を+1しますから、実行後の値が80ということは、実行前は7Fだったということになります。
符号付数の80は−128ですし、7Fは+127です。
つまりINC(INR)命令の実行によって、+1だけカウントアップしたはずが、なんと+127からいきなり−128になってしまったことになります。これを十進数の計算式で表すと、+127+1=−128という式になってしまいますから、これはありえない計算をしてしまったことになります。

さすがのCPUも実行してしまったあとで、
わー!わて、えらいこと、して、しもうた!

と自分でもびっくりしてしまい、オーバーフローフラグをセットするのです。
わてが悪いのとちゃいまっせ!みーんな、あんさんのせいやから、あとはなんなとすきなようにしときー。

逆にDEC(DCR)のときは、実行後に7Fになったということは、実行前は80だったわけです。
これは−128−1=+127と言う計算ですから、これもいかにもむちゃくちゃな結果を出してしまったことになって、オーバーフローフラグがセットされます。

●そんな話は、おかしいよー!それじゃINC(INR)命令は127を超えて実行はできないと言うの?

あー、これは、Z80のお話で、8080にはオーバーフローフラグはありませんから、ご安心を。
いや、そういう問題では、ないでしょう!

これも前に、符号付2進数と符号無し2進数の区別は、「プログラマの勝手」だとお話したことがありました。
そういうことなのです。

さきほどの余談のように、プラスの値とマイナスの値の両方が存在するような数の計算をする場合には、プラスの数+プラスの数=マイナスの数、になってしまったり、マイナスの数−マイナスの数=プラスの数、になってしまったりしてはいけませんから、そうならないように、オーバーフローに注意するなどの工夫が必要になります。
当然INC(INR)によって、7F(+127)の次が80(−128)になってしまうような計算は絶対に禁止、です。

これに対してイベントの入場者数を数えるような場合には、マイナスの数値は存在しませんから、7Fの次に80になっても、全く問題はありません。当然この場合には、7F=(十進数の)127で、80=(十進数の)128になります。

80を−128とするかそれとも符号無し数の128と考えるかは、プログラマの勝手で、CPUの関知するところではありませんから、プログラマがどう考えていようと、Z80なら、INR(INC)命令の実行によって、結果が80になってしまったら、オーバーフローフラグを立ててしまいます。

どうしましょう?
ですか。正解は、「気にしなければ良い」のです。

どのようなフラグが立ったとしても、フラグはただのフラグです。
オーバフローしたからといって、CPUが勝手にプログラムの実行を中止することは、決してありません。
CPUはプログラマの忠実な僕(しもべ)ですから、プログラムを離れて勝手には動きません。

ここでオーバーフローしたなら、こちらの命令を実行させたい、という場合には、オーバーフローフラグの結果によってジャンプする命令を書くことになりますし、さきほどの入場者数をカウントするときのように、7Fの次が80になっても全くかまわないならば、オーバフローフラグが立っていても無視してしまえば良いのです。

こういうところが、アセンブラ(マシン語)の自由なところで、アセンブラに慣れると、自由なプログラムが書けるようになります。ほんとうにプログラマの勝手です。逆にBASICとかCなどの高級言語は、そうは簡単にはいきません。
いろいろ面倒で細かい約束事があって、それをおろそかにすると、思うように動いてはくれません。

ここは、お願いだから、目をつぶっててよ!
などと言っても聞いてはくれません。

「不正な処理です」と言って、いきなり、勝手に実行を終了してしまいます。

●Z80でパリティフラグとオーバーフローフラグが同じビットで共存できるわけ

今までの文章を注意深く読んでいただければ、その答えはすぐにわかります。
パリティフラグとオーバーフローフラグはそれぞれ別の命令でしか使われないので共存が可能なのです。

パリティフラグはAND、OR、XORといった論理演算命令によってセット、リセットされます。これに対してオーバーフローフラグはADD、SUBなどの数値演算命令によってセット、リセットされます。

だから、いま立っているフラグはパリティフラグなのかオーバーフローフラグなのか迷ってしまうようなことはないのです。
これはまあ当たり前の話で、INC(INR)、DEC(DCR)やADD、SUBで計算の結果、1のビットの数が偶数なのか奇数なのかを調べることに、多分意味はないと思いますし、逆にANDやOR命令の実行とオーバーフローがかかわりがあるなどとは考えられません。


☆前回お見せした回路図を使って、オーバーフロー回路の説明をする予定でしたが、時間がなくなってしまいました。
それについては、申し訳ありません。次回に持ち越し、とさせていただきます。
2008.7.28update
2008.7.29加筆修正
前へ
次へ
ホームページトップへ戻る