マルチコアCPUのアトミックな処理 (不可分処理)

マルチコアCPUのように複数のCPUが一つのメモリを共有するCPUシステムにおいて、
重要な命令の一つに「アトミックな処理」を担う命令があります。
「アトミックな処理」とは、複数の命令を1つの処理として行い、その途中で他の処理が割り込むことなく、全てが一度に実行されるような振る舞いを指します。

例えば、MIPSなどでは、メモリアクセスの命令はアトミックな処理を除けば
ロードとストアの命令しかなく、特定の番地のメモリの値を+1したいときは、
以下のようなアセンブリコードになるでしょう。

# メモリアドレスが$t0に格納されていると仮定
lw $t1, 0($t0)  # メモリアドレス$t0番地から値を$t1レジスタにロード
addi $t1, $t1, 1  # $t1レジスタの値を+1
sw $t1, 0($t0)  # インクリメントした値をメモリアドレス$t0にストア

※ 0($t0)の初期値は0と仮定しています。MIPSのアセンブラ表記として、0($t0)は $t0 レジスタの値がメモリのベースアドレスを示し、そこからオフセット 0 の位置にあるメモリの値という意味になります(例えば、3($t0)なら$t0番地から3ワード(12バイト)先($t0+3)のメモリの値になります)。

上記は単純に$t0番地のメモリの値をインクリメント(+1)するものですが、この命令を2つのコアに分散して(+1を)実行し、結果的にメモリの値を+2しようとした場合、以下のように処理される可能性があります。

コア0: lw $t1, 0($t0)  # メモリアドレス$t0から値を$t1にロード (コア0の$t1レジスタの値は0)
コア0: addi $t1, $t1, 1  # $t1の値を+1 (コア0の$t1レジスタの値は1)
コア1: lw $t1, 0($t0)  # メモリアドレス$t0から値を$t1にロード (コア1の$t1レジスタの値は0)
コア0: sw $t1, 0($t0)  # インクリメントした値を$t0に再度ストア ($t0番地のメモリの値は1)
コア1: addi $t1, $t1, 1  # $t1の値を+1 (コア1の$t1レジスタの値は1)
コア1: sw $t1, 0($t0)  # インクリメントした値を$t0に再度ストア ($t0番地のメモリの値は1)

※ コア0の$t1レジスタと、コア1の$t1レジスタは物理的に別であることに注意してください(もちろん$t0も物理的には別ですが、同じ値(メモリ番地)が入っていることを仮定しています)。

上記の例では、$t0番地のメモリの値の初期値が0で、インクリメントを2回行って2になることを期待しましたが、結果は1になってしまいます。これは、メモリの値を更新する際に、1命令では処理できず、どうしても複数の命令が必要になることに起因します。
つまりメモリの値の更新をアトミック(不可分)な処理として1つの命令のようにふるまわせることができれば、この問題は解決します。

例えばMIPSでは、load-link (LL) と store-conditional (SC) という2つの命令の組を利用して、アトミックなメモリ操作ができるようになっています。

LLでは、メモリ位置から値をレジスタに読み込むと同時に、その位置へのリンク(監視)を確立します。他のプロセッサがリンクアドレスに書き込むとリンクが無効になります。SCでは、指定されたメモリアドレスに値を書き込みますが、書き込みはLL命令によって確立されたリンクがまだ有効である場合にのみ成功します。書き込みが成功した場合、レジスタに1がセットされ、失敗した場合は0がセットされます。0だった場合、LLからやり直すようにコードを組みます。

これによりメモリのロードからストアまでの間に、同じアドレスのメモリに他のコアからアクセスがあった場合は再処理を繰り返すことにより、最終的にはロードからストアまでの処理をアトミックに行うことができます。もちろん、LL/SCを実装するためには、対象のメモリアドレスを保存するリンクレジスタなど、CPUにその機構(回路)を追加する必要があります。

具体的なアセンブラコードは以下のようになります。

.data
a:  .word 0                # 共有変数 a

.text
.globl main

main:
    # CP0(コアプロセッサ0: 各コアに個別に存在し、各コアのシステム制御とステータス管理を担当)レジスタからコアIDを取得
    mfc0 $t0, $15           # $t0 に CP0レジスタ 15 (プロセッサID) をロード (あくまで例)

    # プロセッサIDに基づいて異なる処理を実行
    beq $t0, 0, increment   # $t0 が0の場合、increment へジャンプ
    beq $t0, 1, increment   # $t0 が1の場合、increment へジャンプ

increment:
    # アトミック操作を使用して共有変数 a をインクリメント
retry:
    ll $t1, 0(a)            # 共有変数 a をロードリンク
    addi $t1, $t1, 1        # a をインクリメント
    sc $t1, 0(a)            # 共有変数 a にストアコンディショナル
    beqz $t1, retry         # 失敗したらリトライ

    # 各プロセッサが一度インクリメントした後、無限ループ
end:
    j end                   # 無限ループ

※実際にはmain:の中の3つの命令(mfc0と2つのbeq)は不要ですが、便宜的に入れています。

このコードでは、コア0とコア1のどちらも共有変数 a をアトミックにインクリメントしますが、処理の順番は保証されていない点に注意が必要です。例えば、コア0が先にインクリメントしてからコア1がインクリメントすることもあれば、その逆にコア1が先にインクリメントすることもあります。どちらの場合も最終的には正しい2の値となります。並列処理ができる所以です。もし、処理の順番が逆だと期待する結果が得られない場合(例えば、+1してから2倍したい場合など)は、並列処理でなく逐次処理を行う必要があるため、そもそも2つのコアで処理しても高速化は困難となります。

カテゴリー: 電子工作 | タグ: | コメントする

CPUっぽいものをシミュレータで作ってみました

シミュレーターを駆使してCPUらしきものを作りました。これまで前例のない2ビットCPUです…。最低でも4ビットのCPUにしたかったのですが、シミュレーターの制約により2ビットになりました。

2ビットCPUでは、プログラムは最大4ステップ、計算やレジスタの桁数も2ビット(0~3)です。計算は足し算のみです。これはシミュレーターの性能限界の範囲を考えてこのような仕様になりました。

図1. CircuitJS1でCPUぽいものを作る

ここから拡大できます。

回路についての説明です。まず、図2 をご覧ください。黄色のライン部分がクロックで、すべてのD-フリップフロップに繋がっています。赤がリセットで、こちらもすべてのD-フリップフロップに繋がっています。リセットをhighにすると、すべてのD-フリップフロップがリセットされ、プログラムが最初から開始(リセット)される仕組みです。青が制御線です。ここはどの回路を利用するかを制御する部分で重要です(「命令デコーダー」の部分で詳しく説明します)。

図2. 配線の説明

次に各部分の解説です。

図3. 配線の各部分の説明

①は2ビットのカウンターです。クロックごとに00→01→10→11→00と繰り返されます。ただし、細い緑の四角で囲った部分は2ビットのマルチプレクサとなっていて、制御線がHighの場合は戻ってきている2線のほうの値がカウンタにセットされるようになっています。つまりプログラムの移動を担っています。

②はプログラムを格納するいわゆるROMです。4ステップしか入力できません。1つのプログラムは6ビット(I0~I5)でセットします。左上から時計回りに繰り返し実行されます。PがHighのときY0~Y5に出力され、②の右側に出た6本の線に選択されたプログラムがそのまま出力される仕組みです。プログラムはI0,I1は即値(プログラムに直接書き込まれた値)で、I2~I5を使ってプログラムの動作を指定します(オペコード)。つまりプログラムは4ビット=16種類の動作を指定できます。

③と④はいわゆる汎用レジスタで一時的にデータを保持するものです。2ビットのレジスタが2つ分用意されています。上をAレジスタ、下をBレジスタと呼ぶことにします。

⑤は2ビット同士の加算器です。2ビット同士の和は0~6になりますが、2ビットまでしか扱えないため、計算結果が4~6の場合は桁あふれとなり、右上のレジスタにHighがセットされます。

⑥と⑦は外部入力と外部出力です。外部入力値はプログラムの即値やAレジスタ(および00や11)と加算できます。

⑧は命令デコーダーです。プログラムのI2~I5の4ビット(00~03)と桁あふれの状態から、8本の出力を使ってどの回路を有効にするかを決めます。8本の出力のうち、CRはリセット線に繋がっているので、他の制御線がどうであろうとここをHighにするとプログラムがリセットされることになります。
出力は、A0,A1はAレジスタの隣にあるマルチプレクサに繋がっていて、「00」、「Aレジスタ」、「11」、「プログラムの即値」、のうちどれを利用するか指定します。B0,B1はBレジスタの隣にあるマルチプレクサに繋がっていて、「00」、「Bレジスタ」、「11」、「外部入力」、のうちどれを利用するか指定します。D0,D1は加算の結果をどこに出力するかを決めます。D0,D1が00の場合Aレジスタに、01の場合Bレジスタに、10の場合カウンタに、11の場合外部出力レジスタに出力されます。(※カウンタの値を変えるには、CIもHighにする必要があります)。桁あふれがあった場合、右上のレジスタ(D-フリップフロップ)に1(high)が設定されます。このラインは⑧の命令デコーダーの入力に繋がっており、レジスタが入ることにより桁あふれが起きた次のクロックで動作を制御できるようになっています。つまりこれは条件分岐のためのフラグレジスタということになります。

命令デコーダーICのデフォルトの設定は以下のようになっています(?はdon’t care(=0,1の両方)。より上が優先。出力を変えれば別の機能を割り当てられます。シミュレーターのICをダブルクリックして[Edit Model…]から編集できます。):

O0,O1,O2,O3,Flg=A0,A1,B0,B1,D0,D1,CI,CR
0000?=00001000 何もしない (nop)
00010=00001000 桁あふれでない場合、何もしない
00011=11001010 桁あふれの場合、即値にジャンプ
00100=00001000 桁あふれでない場合、何もしない
00101=01001010 桁あふれの場合、Aレジスタの値にジャンプ
0011?=11001010 即値にジャンプ
0100?=00110000 外部入力をAレジスタに保存
0101?=11000000 即値をAレジスタに保存
0110?=00110100 外部入力をBレジスタに保存
0111?=11000100 即値をBレジスタに保存
1000?=01000100 Aレジスタの値をBレジスタに保存
1001?=00010000 Bレジスタの値をAレジスタに保存
1010?=01010000 Aレジスタ+Bレジスタの値をAレジスタに保存
1011?=01010100 Aレジスタ+Bレジスタの値をBレジスタに保存
1100?=00111100 外部入力の値を外部出力レジスタに保存
1101?=01001100 Aレジスタの値を外部出力レジスタに保存
1110?=00011100 Bレジスタの値を外部出力レジスタに保存
1111?=01011101 リセット
?????=00001000 何もしない

シミュレータのROMはデフォルトで以下のプログラムが設定されています:

00: 00110000: 外部入力(01)の値をAレジスタに保存(→01)
01: 11000100: 即値(10)をBレジスタに保存(→10)
10: 01010000: Aレジスタ(01)+Bレジスタ(10)の値をAレジスタに保存(→11)
11: 11000000: 即値(00)をAレジスタに保存(→00)
※00から繰り返す

結局、近年のRISC CPUでオペコードに割り当てられる機能は大きく分けて、「レジスタへの入出力」「メモリ(主記憶装置)への入出力」「即値やレジスタ(やメモリ)の値を使った演算」「条件分岐とジャンプ」の4つだと思っています(他にも権限命令等ありますが、大雑把に4つと考えます)。今回のCPUは外部への入出力の命令がある一方、メモリの搭載を考えていないので、メモリへの入出力の命令がありません。近年のCPUでは、外部入力や出力に関連する命令が直接的に存在しないことが一般的です。代わりに、外部デバイスとの通信はメモリマップドI/O(Memory-Mapped I/O)という方式を通じて行われたりします。これは、特定のメモリアドレスにデバイスのアクセスを割り当て、CPUから見るとメモリアクセスで外部デバイスとデータの送受信を行う方法です。また、今回のCPUはプログラムはROM上に設定しますが、通常プログラムはメモリに保存されたものを読み出して使用します。メモリには他にもレジスタだけでは処理しきれないデータを保存したり読み出したりする役割もあります。

メモリアクセスはCPU内蔵のレジスタに比べると非常に遅いですが、近年のCPUでもレジスタは30個くらいしかなく(しかもそれぞれ役割が決まっていて汎用的に使えるレジスタはごく少数)、そのため、レジスタは効率的に使用し、それ以外の計算やデータのやりとりはメモリを使って行うことになります。ちなみに、プログラムと計算用のメモリが物理的に別に用意されている方式を「ハーバード・アーキテクチャ」と呼びます。PICマイコンなどはこれに当たり、プログラム用メモリのバスと、計算用メモリのバスが分離できるため、回路構造が比較的シンプルになります。対して、プログラムとデータが同じメモリを共有する方式を「フォン・ノイマン・アーキテクチャ」と言います。通常のPCは「フォン・ノイマン型アーキテクチャ」ということになります。ノイマン型はメモリ装置が1つで済むため効率的に見えますが、メモリを扱う命令では「命令の取得」と「データアクセス」の要求を同時にメモリ装置に対して行うことになり、1クロックで完結させようとすると問題が生じます。

解決方法としては、1つの命令を2クロック使って実行することが考えられます。つまり1クロック目ではプログラムを取り出すためにメモリを使用し、2クロック目でデータを取り出すためにメモリを使用する(そして1クロック目に戻る)といった方法です。

もちろん、実際にはメモリは近年のCPUクロックに対して極めて低速であり、そこを考えれば(どうせ待ちが発生するので)些細な問題に見えます。実際に近年のCPUにはメモリアクセスを高速化するために、メモリの値を先読みして保存しておくキャッシュ機能があります。キャッシュは頻繁にアクセスするデータを予測し予めメモリから取り出しておき、CPUのデータアクセス効率を高めています。命令アクセス用のキャッシュと、データアクセス用のキャッシュを別々に用意すれば、同時アクセスも可能でしょう。しかしいずれにしても、1クロックですべての処理を行おうとすると、CPUの機能が複雑になるにつれて回路や装置の資源の奪い合いが起きるのは間違いなさそうです。

実際、通常のCPUは1つの命令を1クロックで完結する構造になっていることは少なく、一般的には命令サイクルという、1つの命令を複数のクロックに分けて(ステージに分けて)実行する構造になっています。これは資源の奪い合いを避けるためでもありますが、もっと重要なのは、1クロックごとのクリティカルパスを短くするためでもあります。以前説明したようにクリティカルパスが長いと、回路に信号を伝達するために十分な時間を与える必要があり、CPUのクロック数を抑える必要があります。逆に、回路を機能ごとに分割する(途中に保存レジスタを入れる構造にする)と、1命令を実行するのに必要なクロック数が増えても、回路は機能ごとのシンプルな構造になりますし、何よりクリティカルパスが短くなることにより、クロック数を高速化することができます。

クロック数を増やしても、1命令に使うクロック数が増えてしまっては意味があるのだろうかと思われるかもしれません。
実際にはパイプライン処理という方法が使われます(図4)。パイプライン処理は1命令を複数のステージに分けて実行する際に、1ステージごとにずらして複数の命令を同時に実行する方式です。例えば、命令サイクルが、「1.命令フェッチ→2.命令デコード→3.命令実行→4.メモリアクセス→5.ライトバック」のように5ステージだった場合、最初の命令が「1.命令フェッチ」を行い、次のクロックで2.を実行している間に、2番目の命令が1.を実行するという具合です。これが理想通り動けば、最初の数クロックを除いては、実質1命令1クロックで動作していることになるわけです。

図4. パイプライン処理

もちろん、1命令が終わらないうちに次の命令を実行し始めるため、例えば1つ前の命令の結果を次の命令が利用する場合や、条件分岐がある場合、また周辺資源を同時に利用しようとする場合などで問題が生じます。これをハザードといい、追加回路(や場合によってはソフト)等でうまく工夫して解決する必要があります。(実際にはこの辺は「パタヘネ本(第5版)」という書籍で私は学んでいるので、読んでいただければより詳しく理解できると思います。)そのため、パイプライン処理を使っても理想通りに1クロック1命令を実行することは実質的には難しいですが、現実的には30%~70%程度の効率で実行できるとのことです。

他にも今回のCPUになく、一般的なCPUには存在する回路として「割り込み」(インタラプト)という機能があります。割り込みとは、CPUが特定の条件やイベントに応じて、実行中のタスクを一時停止し、即座に別の処理を行うメカニズムのことです。たとえば、キーボードのキーが押されたときや、プログラムが特定の条件に達した(システムコールや例外処理等)ときに割り込みは発生します。

割り込みが発生すると、まずCPUは現在実行中のタスクを一時停止し、状態を保存(退避)します。次に、割り込みに対応する特定の処理を行う割り込みサービスルーチン(ISR:通常ユーザ側で動作をプログラムする)が実行され、これが完了すると、CPUは退避しておいた状態を復元し、元タスクの実行を再開します。割り込みが複数同時に発生した場合に備えて、通常は優先順位が設定されており、優先度の高い割り込みから処理されます。

ちなみに、割り込みの他にポーリングという方法もあります。これはCPUが定期的に特定の状態やイベントをチェックしに行く方式です。しかしこの方式だとCPUが何もすることがない場合でも、デバイスの状態を定期的にチェックする必要があり、この間CPUは通常のタスクを処理できないことになります。また、チェックのタイミングによっては、応答が遅れる原因にもなります。このため、通常CPUはポーリングではなく、割り込み方式を使うことが多いのです。

他にも現代のCPUでは、スーパースカラ技術や、インテルのハイパースレッディング技術、マルチコア化、最近ではSoC化などが進んでおりより複雑化してきています。

また、著名なCPUについては、CPUのマシン語命令セットや動作の仕様として「命令セットアーキテクチャ(Instruction Set Architecture、ISA)」が予め定義されており、これに沿って回路が設計されることになります。ISAの代表的なものとしては、x86、Arm、MIPS、PowerPC、RISC-Vなどがあります。特に、RISC-Vはオープンソースのアーキテクチャとして近年注目を集めています。
また、AI化の進展に伴い、SoC(System on Chip)ではCPUコアだけでなく、GPUコアやNPU(Neural Processing Unit)コアなど、さまざまな種類のコアを複数搭載することが増えつつあります。

カテゴリー: 電子工作 | タグ: , | コメントする

Javascriptで作ったカルノー図による論理式の簡単化

カルノー図(Karnaugh map)は、真理値表の情報を視覚的に表現する手法です。これを用いて、JavaScriptプログラムを作成しました。このプログラムで、論理式を最小化することができます。

カルノー図シミュレーター

javascript ソース

カルノー図を使用することで、ブール関数の最小化や論理回路の設計、解析が可能です。ただし、カルノー図は通常、6つの入力までに適しています。そのため、このJavaScriptプログラムは2~6つの入力に対応しています。Wikipediaによれば、7つ以上の入力の場合はクワイン・マクラスキー法などが適しているとのことです。

このシミュレーターの使い方は、デフォルトの表示ではこの投稿に示されている7セグメントデコーダの真理値表が表示されます。

操作方法は、入力数を2~6つ、出力を1~99個で選択します。すると対応した真理値表が表示されます。真理値表の出力のセルをクリックすると、0⇔1が切り替わりますので、真理値表を完成させます。

真理値表の下にある「[結果]」または「[結果(not)]」ボタンをクリックすると、論理式が簡略化された形で表示されます。また、「[結果(not)]」は否定側の結果であり、結果(1または0)を反転させることで真理値表の結果と一致します。両方の結果を好みに応じて使用できます。


javascriptで作成したクラスのメソッドの仕様を忘れてしまったため💦、後でプログラムを読み直してここに追記するかもしれません…。

カテゴリー: 電子工作 | タグ: , | コメントする

真理値表を論理式に変換する

例えば、下のような3入力(A,B,C)の真理値表を満たす論理式を作るにはどうすればよいでしょうか?ここでは分かりやすい方法として、主加法標準形、主乗法標準形、リードマラー標準形の3つを紹介します。

入力 (input)出力 (output)論理式
ABCX最小項最大項
0001A B CA+B+C
0011A B CA+B+C
0100A B CA+B+C
0110A B CA+B+C
1001A B CA+B+C
1010A B CA+B+C
1100A B CA+B+C
1111A B CA+B+C

1. 主加法標準形

例えば、A,B,Cすべてが0の場合、A,B,Cの論理積を使って出力を1にしたいとき、A B Cとすると1になります。A,B,Cどれかが1だと0になるため、A B Cを1にするには0,0,0のパターンしかありません。

同様に、例えば、(A, B, C) = (0, 1, 1)の場合のみ出力を1にしたいときは、A B Cとすればよいことになります。上表で各行のパターンのときだけ論理積が1になる式が最小項に記載されています。

つまり、上表のような出力パターンを得るためには出力が1となっている行の最小項を論理和(+)で繋げればよいことになります。

結果として X = A B C + A B C + A B C + A B C の式ができあがり、これが主加法標準形となります。

ちなみに0になる方の論理和を取ると、出力の0と1が逆になるので、X = A B C + A B C + A B C + A B C も成り立ちます。


2. 主乗法標準形

次に、論理和は全部の項が0のときのみ0となるため、この性質を利用します。
例えば、(A,B,C) = (0,1,0)のときのみ論理和で0にするには、A+B+Cとすれば0になります。0,1,0以外のパターンでは必ず1になってしまいます。

なので真理値表で出力が0の行について、上表の最大項を取り出し論理積で繋げれば、出力が0になる行は(最大項が0になるので)、論理積の結果も0になります。逆に出力が1になる行はすべての論理和部分が必ず1になる(0になる最大項を含まない)ので式が成り立つことになります。

結果として、X = (A+B+C)(A+B+C)(A+B+C)(A+B+C)の式ができあがり、これが主乗法標準形となります。

ちなみに1になる方の論理積を取ると、出力が逆になるので、X = (A+B+C)(A+B+C)(A+B+C)(A+B+C) も成り立ちます。



3. リードマラー標準形

リードマラー標準形は、「B⊕C⊕AB⊕AC⊕ABC」のように否定のない論理積の項を排他的論理和でつないだ形で表現するものです (このとき計算順位は論理積が高い(先計算))。
作り方は、まず以下のようにA,B,Cのそれぞれを0~1回使ったパターンを出します。

-,-,-: (1の項)

A,-,-: (A項)
-,B,-: (B項)
-,-,C: (C項)

A,B,-: (AB項)
A,-,C: (AC項)
-,B,C: (BC項)

A,B,C: (ABC項)

上記の8通りがあります。ここで”-“の部分は0で固定して、文字が入った部分は0と1の両方を持つ行を真理値表から抽出します。例えば、”-,-,-“は0,0,0の行のみ、”A,-,-“は0,0,0と1,0,0の2行を抽出します。”A,-,C”なら、0,0,0、1,0,0、0,0,1、1,0,1の4行といった具合です。”A,B,C”は真理値表を丸ごと抽出することになります。

次に抽出した行について、出力が1になっている行数を数えます。1になっている行の数が奇数ならその項を残して排他的論理和でつなげば完成です。

上表の真理値表で具体的に説明すると、まず0,0,0の出力は1となっているので、1の項を書きます(1⊕)。次にA項ですが、0,0,0は1で、1,0,0も1で出力の1は2個で偶数なので、A項は加えません。同様にB項は1が1個で追加(1⊕B)、C項は2個なので追加しません。AB項は0,0,0が1、1,0,0が1、0,1,0が0、1,1,0が0で1は2個となるので追加しません。同様にAC項は3個で奇数なので追加(1⊕B⊕AC)、BC項は2個なので追加なし、ABC項は4個なので追加なしとなります。

結果として、X = 1⊕B⊕AC という論理式が導かれます。


さて、なぜこの方法で式が導けるかです。
排他的論理和の性質として、1の項の数が奇数の場合出力が1、偶数の場合出力が0となります。例えば、0⊕0⊕1⊕1⊕0⊕1なら1は3個で奇数なので計算結果は1となります(0を数値の1、1を数値の-1と見立てて算術的な掛け算をしたときと似ています→1 × 1 × (-1) × (-1) × 1 × (-1) = -1)。

また例えば、A,B,Cの3入力を例とすると、すべての項を足した一番長い式は、1⊕A⊕B⊕C⊕AB⊕AC⊕BC⊕ABC の8項の式となります(4入力なら2^4=16項になる)。

このうち最初の”1″の項を使うかどうかは、真理値表の0,0,0の行の出力が0か1かで決まります。なぜなら、”1″以外の項(A、B、C、AB、AC、BC、ABCの項)は、すべて0になるからです。つまりA⊕B⊕C⊕AB⊕AC⊕BC⊕ABCが0であるため、出力に合わせるためには、0,0,0の行の出力が1の場合は “1”の項が必ず必要になり、0の場合はこの項は入れられない(または0を入れても良い)ということになります。

次にA項を使うかどうかを考えます。Aの入力以外が「0」(0,0,0または1,0,0)のときは、”1″と”A”以外の項、つまり、B⊕C⊕AB⊕AC⊕BC⊕ABCは必ず0になるため、0,0,0と1,0,0の行の出力のパターン(のみ)でAを使うかどうか決める必要があります。

具体的なパターンを考えると、
1の項がある(0,0,0の出力が1の)とき、1,0,0が0の場合Aの項は(0,1を逆転させる必要があるため)必要で、1,0,0が1の場合Aの項は不要となり、
1の項がない(0,0,0の出力が0の)とき、1,0,0が0の場合Aの項は不要で、1,0,0が1の場合Aの項は必要(0,1を逆転させる必要があるため)となります。
結局、0,0,0と1,0,0の出力の1の数が0個か2個の場合、Aの項を除くことになります (またはAの代わりに0を入れてもよい)。
Bの項もCの項もAの項と同じ考え方で入れるか入れないかを決めることができます。

以下同様なのですが、ABの項を残すかどうかは、AまたはB以外の入力(ここではC)が「0」のとき、それより後ろの項、つまりC、AC、BC、ABCの項は必ず0になるため、0,0,0と1,0,0と0,1,0と1,1,0の出力のパターンで消すか残すか決める必要があります。
ここまでに0,0,0と1,0,0と0,1,0のパターンは既に成立しているので、結局、1,1,0のパターンのとき成り立つように、AB項を残すかどうか決めればよいことになります。
ここまでで0,0,0と1,0,0と0,1,0とパターンについては、1,A,Bの項の排他的論理和の演算の結果となっているため、つまり、この3パターンの出力の1の数が奇数(=ここまでの排他的論理和が1)のとき、1,1,0の出力が0の場合反転させるためAB項を残し、1の場合除けばよいことになります。
逆に出力の1の数が偶数(=ここまでの排他的論理和が0)の場合は、1,1,0の出力が0の場合、そのままにするためAB項を除き、1の場合(反転させるため)残せばよいことになります。
同様にAC項、BC項も決まり、以下同様です。


今回は、真理値表から簡単に論理式を導く方法を紹介しましたが、さらにブール代数などで演算を行い変形して式を単純化する(=回路を単純にする)こともできるでしょう。次回は単純化の一つの方法としてカルノー図を使う方法を紹介したいと思いましたが、以下のページが参考になりこれで良い気がしてきました。


※この記事は、以下のサイトを参考とさせていただきました。

カテゴリー: 電子工作 | タグ: , | コメントする

組み合わせ回路 その6: 乗算回路

続いて3ビット同士の掛け算の回路を考えます。0から7までの2数の掛け算です。最初に足し算を繰り返して答えを出す方法を考えます。例えば4 x 3 = 4 + 4 + 4 = 12 のように被乗数(4)を乗数の数だけ(3回)足す回路です。

その回路の一例が図2-10です。見にくくてすいません。これは決して綺麗な回路ではないし効率もよくないでしょうが、説明のしやすさを優先しています。左側に入力が6つあり、上3つが被乗数、下3つが乗数の入力 (いずれも下から1ビット目~3ビット目)です。仕組みは被乗数にとりあえず1~7回足したものを用意しておいて、マルチプレクサを使って乗数でセレクトして目的の結果を出力しています。

図2-10. 足し算を繰り返す方式の乗算回路

図2-11. x1からx7の結果を出力するワイヤ

図2-11は上記の回路の一部を拡大したものですが、沢山の加算器がありますが、赤の四角で囲ったワイヤの部分がそれぞれ、被乗数を1回足した結果, 2回足した結果, 3回足した結果, …, 6回足した結果になっています(図2-11は被乗数が7のときの例. 赤い矢印部分が7回足した結果)。

そして用意された6個の各マルチプレクサの入力に1~7回足した結果が配線されています。乗数はマルチプレクサのセレクタに配線され、1~7回足された結果のうち選択された1つ(つまり選択した乗数)の結果が右側に出力される仕組みになっています。

この方法は3ビット同士の掛け算くらいなら良いですが、桁数が増えると回路が膨大な大きさになってすぐ破綻することは想像に難くないでしょう。

※全加算器の入力の1つが0v(GND)になっているものがありますが、勿論これらは半加算器に取り換えることもできます。


上記の乗算回路は桁数が増えるほど加算器も指数関数的な数が必要になるため、大きな桁数に使うのは難しそうです。 そうであれば、順序回路つまりDフリップフロップを使って同じ回路で繰り返し足し算を行えれば、それほど加算器を増やさなくても桁数を増やせそうです。この考え方を使った回路を図2-12に組んでみました。

図2-12. Dフリップフロップを使った乗算回路

使い方ですが、左側の6つのインプットに「3ビットの被乗数」と「3ビットの乗数」をセットし、一番下にあるスイッチを入れると計算が始まります。被乗数がクロックごとに足され、乗数に達すると足し算が止まるようになっています。例えば2×3なら2→4→6とクロックごとに2が加算されゆき、最後に6が右側に出力され変化が止まります。 回路はシンプルになりましたが、この回路も(加算器は少なくて済みますが)乗数が大きくなればなるほど計算が終わるまでに(クロック数ぶん)時間がかかることになります。

なお乗数を入力する下側の3ビットは0から7までインクリメントするカウンタ回路に繋がっており、カウンタの数が乗数の入力と同じになると(xor+orゲートの出力がlowとなり)、足し算を止める仕組みになっています。

またトランジスタのレベルに戻って考えると、トランジスタ単体でもゲートやベースに電荷がたまって動作するようになる(=信号を次に伝える)までには(わずかな)時間がかかります。従ってトランジスタがいくつも直列に繋がるような回路では結果が出力されるまでに時間が多く掛かることになります。クロック周期が早すぎると、結果が出力される前に次のクロックが始まってしまうことも考えられます。こうなると正しい結果が得られないため、クロック周波数は組み合わせ回路が結果を出力するまでにかかる時間より十分に遅い必要があります。Dフリップフロップから次のDフリップフロップまでの組み合わせ回路のなかでも最も時間がかかる経路をクリティカルパスと言いますが、このクリティカルパスをなるべく短くすることも回路設計の上で大変重要になります。

なお図2-12の疑似コードを書くと以下のようになると思います。

a = (被乗数: 0~7の整数)
b = (乗数: 0~7の整数)
i = 0
total = 0
while(1) {
    if(i != b) {
        total = total + a
        i++
    }
    print(total)
} 

クロックごとにwhileの中身が繰り返され、iが乗数(b)に達するまでtotalに被乗数(a)が加算されます。


筆算を模した組み合わせ回路を作ることも可能です。図2-13は4ビット同士(まで)の乗算回路です。

これまでの回路と同様に左側に4ビットずつの入力があり、右側に8ビットの出力があります。

図2-13. 筆算を模した乗算回路

回路の仕組みを図2-14にまとめています。右側に2進数での1011 x 1101の筆算を示していますが、 色付きのボックスの部分が左の回路図の対応する色のAND回路の出力になっています(ビットの組み合わせ分(4 x 4 =) 16個あります)。 筆算の茶色の①~③に示した範囲の足し算の結果が、回路図の茶色の①~③の出力に対応します。

計算結果は10001111となり、10進数にすると 11 x 13 = 143 で正しい結果が導かれているのが分かります。

図2-14. 筆算を模した回路の仕組み

つまり、掛け算をするにも色々な方法がありますが、どの方法にしても回路図に落とし込もうと思って頑張れば可能なのです。 勿論ゲートの数が現実的でなかったり、計算に必要なクロック回数が膨大で現実的でなかったりもしますが、工夫次第、 つまりアルゴリズム次第で効率的な回路図を描くことも可能です。究極的には(実は)プログラミングで記述可能なことは電子回路に置き換えることが可能なのです。

なお、今回示したような単純な乗算回路では桁数が多いと特に加算部分のキャリーを待つ必要があるため時間がかかります。 それを高速化する方法として、ウォレス木型乗算器などの方法があるそうです。

カテゴリー: 未分類 | タグ: , | コメントする

組み合わせ回路 その5: マルチプレクサ(データセレクタ)

図2-7は、4ビットのマルチプレクサです。この回路はI0からI3までのデータの中の一つ(1ビット)をQに出力する回路です。 どの入力を出力するかを決める入力が、S0とS1になります。S0,S1が0,0のときI0が、1,0のときI1が、0,1のときI2が、1,1のときI3の入力がQに出力されます。 (以下、S0,S1のように出力を選択するための入力を、選択入力と呼ぶことにします。) 4ビットのマルチプレクサがシミュレータにも用意されていたので、右側につけてみました。 プログラムの命令によって回路を切り替える場合などに有用です

図2-7. 4ビットマルチプレクサ

図2-8は2つのデータに絞った回路です。SがLのときはI0がQに反映されますが、HのときはI1がQに反映されます。

図2-8. 2ビットマルチプレクサ

図2-9はデータのコピーをシミュレートした回路になります。2つのDフリップフロップがありますが、 下のフリップフロップの出力を上のフリップフロップの出力にコピーする回路です。 2つのフリップフロップには約5秒に1回頻度のクロックを付けてみました。

下のフリップフロップはD入力がクロックの立ち上がりにQ出力に反映されるようになっています。 Q出力は、図2-8の2ビットのマルチプレクサの下側(I1)の入力にも繋がっています。 マルチプレクサの出力は上のフリップフロップのD入力に繋がっていて、 通常は(入力MがLのときは)、上のフリップフロップのQ出力が、そのまま入力に入るので出力が保持されます。 入力MがHになったとき、マルチプレクサの出力が切り替わり、下のフリップフロップの出力が上のQに反映される ようになります。MをLに戻せば、下のコピーした出力が上のフリップフロップに保持されることになります。

図2-9 1ビットレジスタコピー シミュレーション

上記は1ビットの2つの保持回路でのコピーでしたが、これを4つ並列にすれば4ビットのデータのコピーができますし、N個繋げればNビットのコピーができることになります。 マルチプレクサも2選択のものですが、こちらもビット数を増やせば別のレジスタのコピー機能や、ALUの出力をつないで結果を取り込むなど、 より多くの機能を(選択入力ごとに)割り振れることが分かるでしょう。

カテゴリー: 未分類 | タグ: , | コメントする

組み合わせ回路 その4: ALU(Arithmetic Logic Unit)

ALU(演算装置)はCPUを調べると内部構造の一部として必ず登場するものです。論理演算や算術計算などを担うものだそうです。 ALUの一例として74HC181という汎用ロジックICのデータシートにあった論理図をシミュレートしてみました(図2-6)。 このICは4ビットのand, or, xor, 足し算, 引き算, 比較などの一通りの基本的な演算ができる組み合わせ回路です。 下のテーブルが真理値表ですが入力がS0~S3, A0~A3, B0~B3, Cn, Mの 14個と、出力が F0~F3, A=B, Cn+4, X, Y の8個あります。 出力のうちのCn+4, X, Y については複数のICをつなぐ際のキャリーに なります。回路図の上部の複雑な部分はこのキャリーに関する部分なので、 ここを除けば回路を単純にできると思いますが、今回はデータシートにあるものをそのまま再現しました(手で作って、テストもしてないので キャリー部分は配線が間違えているかもしれませんが、その場合ご指摘ください)。また、今このICは手に入りづらいようですのでご了承ください。

近年のCPUにこのICが使われているわけではないですが、自作CPUなどにはとても使い物になるようですので、 様々な演算ができる組み合わせ回路の一例として説明します。入力のS0~S3はfunction Selectionで、 機能を選択する入力です。16通りが選択できることになります。MはModeでここでも2通りを選択できます。MがHのとき論理演算となり繰り上がりが起こりません。 MがLのときは算術演算で繰り上がり繰り下がりが起こることがあります。加算では繰り上がりが起きたときにCn+4 がLとなり、もう一つ用意したALU入力のCnに繋げれば、Lのときは(Hのときより)プラス1されるようになっているようです。 入力はA0~A3, B0~B3の4ビットずつの2値を扱えます。A0(B0)が2進数下1桁目、A3(B3)が4桁目となります。結果はF0~F3に出力されます。 引き算ではマイナス値になるときCn+4がHになり、2の補数表現になるようです。 2の補数とはビット反転して1を加えたものです。例えば-1であれば、0001(+1)のビット反転1110に1を足して1111になります。 (補数とは加算処理したとき桁あふれを無視すれば減算できるような数です。 例えば 5 – 3 → 0101 + (-0011) = 0101 + (1100 + 0001 – 10000) = 0101 + (1101 – 10000) = 0010。 このように2の補数を使えば加算で減算処理できます。※ -10000は桁あふれぶん。) 出力の「A=B」はAとBの入力値を比較するもので、M=L、Cn=H、S0~S3がLHHLのとき F = A MINUS B MINUS 1でA, Bが同じ値のとき演算結果が-1(2の補数で出力はHHHHになる)になることを利用し、 F0~F3出力が全部HのときA=BをHとするものです。 Cn+4を使えば大小も判別できることになります。

SELECTIONACTIVE-HIGH DATA
M = H; LOGIC FUNCTIONSM = L; ARITHMETIC OPERATIONS
S0S1S2S3 Cn = H (no carry) Cn = L (with carry)
LLLLF = AF = AF = A PLUS 1
LLLHF = A + BF = A PLUS ABF = A PLUS AB PLUS 1
LLHLF = ABF = A PLUS ABF = A PLUS AB PLUS 1
LLHHF = 1F = A PLUS AF = A PLUS A PLUS 1
LHLLF = ABF = A + BF = (A + B) PLUS 1
LHLHF = BF = (A + B) PLUS ABF = (A + B) PLUS AB PLUS 1
LHHLF = A ^ BF = A MINUS B MINUS 1F = A MINUS B
LHHHF = A + BF = (A + B) PLUS AF = (A + B) PLUS A PLUS 1
HLLLF = A + BF = A + BF = (A + B) PLUS 1
HLLHF = A ^ BF = A PLUS BF = A PLUS B PLUS 1
HLHLF = BF = (A + B) PLUS ABF = (A + B) PLUS AB + PLUS 1
HLHHF = A + BF = (A + B) PLUS AF = (A + B) PLUS A PLUS 1
HHLLF = 0F = MINUS 1 (2’s COMPL)F = ZERO
HHLHF = ABF = AB MINUS 1F = AB
HHHLF = ABF = AB MINUS 1F = AB
HHHHF = AF = A MINUS 1F = A

※ “^”はXOR(排他的論理和)を意味します。”+”は論理和、”AB”などはAとBの論理積、” “はNOTを意味します。 図2-6 ALUの一種(74HC181)の回路
カテゴリー: 電子工作 | タグ: , | コメントする

組み合わせ回路 その3: 加算器

加算器(adder)

加算器は足し算を行う組み合わせ回路です。図2-5の一番上の回路は半加算器と呼ばれるもので、左側の2つの入力のビットの和が右側の2つに出力されます。左上に示したようにANDゲートとXORゲートを1つずつ使って作ります。右上のボックスで表現するとAとBが入力、S(Sum)がAとBの加算結果の1ビット目、C(Carry)は繰り上がりを意味し、加算結果の2ビット目ということになります。

図の2段目にあるのが全加算器と呼ばれるものです。半加算器2個とORゲート1個で作ります。 入力が1つ増えて3つになりますが、加えた1つは下桁からの繰り上がりのビットとして使います。 キャリーの出力と入力を繋げることで複数桁の計算に対応することができます。

具体例として、一番下の回路が4ビット同士の加算を行う回路です。A1~A4が一方の1~4ビット目、B1~B4がもう一方の4ビットです。結果はS1~S4と、最上位のビットはC4に出力されます。 C4をさらに全加算器に繋げればさらに桁数を増やせるということになります。

図2-5 加算器

カテゴリー: 電子工作 | タグ: , | コメントする

組み合わせ回路 その2:カウンタ

カウンタ

続いて順序回路も用いた例です。 図2-3のカウンタ回路は0x0から0xF(0000~1111b)までを繰り返しカウントアップするものです。一番下に入力端子が一つあり、立ち上がりのときにカウンタが+1されます。 4つのDフリップフロップを挟んでI0~I3の4つの出力があり、7セグデコーダと7セグLEDで結果が表示されるようになっています。 一番下のDフリップフロップに着目すると、出力Qからインバータを介して元に戻る回路があり、これにより0→1→0→1…が繰り返されるようになります。 また出力が次のDフリップフロップのクロック入力に繋がることにより、桁上げが起こるようになっています。 同じ回路を上へつなげて行けば何桁でも増やすことができます。

フリップフロップ回路のように、同じ入力値でも出力値が変わる順序回路を作るときはチャタリングに注意する必要があります。 チャタリングとは、自分で回路を工作し、入力をそのまま物理的なスイッチにした場合に問題になる現象で、入力を切り替える瞬間に 何度もオンオフを繰り返す現象です。 物理スイッチの場合必ず起こる現象で、カウンター回路でも同じことが言え、一度オンにしただけなのに(ごく短時間に何度もオンオフが繰り返され)、 出力表示が思った通りにならないということが起こります。クロック入力に物理スイッチを使う場合は、チャタリング防止回路を入れたり、 On/Offを分ける回路を入れるなどの工夫が必要になります。

図2-3の回路は回路自体はシンプルですが、クロックが順々にフリップフロップを伝わる方式のため、 後ろにいくほど信号の伝達が遅くなるという欠点があります。シミュレータのSimulation Speedのスライダーを左側に移動して 速度を遅くすると顕著に分かると思いますが、特に8とか0に戻るときに正しい表示が出るまでに時間が掛かります。

図2-3 カウンタその1

図2-4図2-3を改良した回路で、すべてのフリップフロップをクロックに直接つないでいます。 こちらは切り替わりが早いですが、桁数を増やすほど回路が複雑になるという欠点があります。実際には速いのでこちらを使ったほうがよいでしょう。

図2-4 カウンタその2

カテゴリー: 電子工作 | タグ: , | コメントする