この章では、前章で考えた仕様書に沿ったコンピュータを実装します。
前回考えた仕様を復習しておきましょう
- 8 ビットのレジスタを 2 つもつ。レジスタ A とレジスタ B とする
- movi n 命令により、レジスタ A に n が代入される
- add 命令により、レジスタ B に A + B が代入される
- swap 命令により、レジスタ A と B の値が交換される
- レジスタ B の値が LED に常に出力される
レジスタとはハードウェアに直接組み込まれている記憶領域でした。
レジスタはフリップフロップで実装します。
今回実装するコンピュータは 8 bit のレジスタを 2 つ持っているのでした。とりあえず 8 ビットのフリップフロップを 2 つ置いておきましょう。
また、レジスタ B の値が LED に常に出力されるので、レジスタ B の出力を LED に繋いでおきましょう。
movi, add, swap という命令を定義しましたが、これをコンピュータに命令するには、何かしらの信号によって伝える必要があります。命令を信号のパターン、つまり二進数で表したものを機械語といいます。
機械語としてどのようなパターンにするかは設計者の自由です。とはいえ、どのように表現するかによって実装の効率も変わってくるので、設計者の腕の見せ所でもあります。今回は命令の個数も少ないので、簡単に以下のように表すことにしましょう。
命令 | パターン(機械語) |
movi n | 00n |
add | 01000000 |
swap | 10000000 |
たとえば movi 3 であれば 00000011 となります。
つまり、コンピュータに対して以下のように 00000011 という信号を送ると、内部のレジスタの状態が以下のように変わるようにするということです。
メモリは実行中のプログラムと実行に必要なデータを格納する記憶領域です。今回データをメモリに格納することはないので、プログラムだけが格納されています。
メモリの番地と容量を 8 bit とします。つまり、0 から 255 まで番号付いた領域にそれぞれ 8 ビットの値が格納されています。
番地 | 内容 |
0 | 00010110 |
1 | 11010010 |
2 | 00100101 |
3 | 01000101 |
4 | 00001000 |
5 | 00010100 |
… | |
254 | 00101111 |
255 | 11110010 |
8 ビットメモリは、入力素子を 8 つ、出力端子を 8 つ持っています。たとえば、メモリの入力に 00000101 を入れると 5 番目の番地の値が出力として出てきます。上記の場合であれば 00010100 が出力されます。
このメモリについてはそのようなパーツがあるということにしてください。中身が気になるという方のために一応簡単な説明を以下に書きましたが、読み飛ばしてもらってもかまいません。
せっかくここまで学んできたのにメモリをブラックボックスとして放置するのが嫌な方のために簡単に動作原理を説明します。
ここでは簡単に、番地が 4 つで、各番地の容量が 4 ビットのメモリを考えます。番地が 4 つということは 2 ビット 00, 01, 10, 11 で表現できます。例えば以下のようなメモリ内容を考えましょう。
番地 | 内容 |
00 | 1011 |
01 | 0101 |
10 | 0001 |
11 | 1000 |
概念的には、メモリの記憶領域の正体はスイッチで、メモリへのアクセスの正体はセレクタです。スイッチといっても、押している間だけ通電するようなタイプではなく、レバーを倒して選択するトグルスイッチのようなものを思い浮かべてください。
4 ビットの記憶が 4 つあるので 16 個のスイッチがあることになります。ここから、どの情報を引っ張ってくるかを、番地を制御信号としたセレクタで選択します。回路図で表すと以下のようになります。
たとえば、制御信号として \(A_1 = 1, A_2 = 0\)が入ってくると、2 行目がセレクタにより選択され出力されることになります。
スイッチのレバーを倒せば、メモリの内容を書き換えることもできます。
現代のコンピュータではこのような物理的なトグルスイッチがメモリに付いているわけではありません。主にメインメモリとして使われる DRAM では、トランジスタを使って電気的なスイッチとして実現しています。また、SRAM と呼ばれる技術では、フリップフロップを使って情報を記憶しています。また、メモリへのアクセスも、上述のようにナイーブに全ての中から一つの信号を選んでいるのではなく、構造化されて効率よくアクセスできるようになっています。詳細についてはパターソン&ヘネシー下巻などを参照してください。
プログラムをメモリに記憶できたとしましょう。たとえば、
movi 3 |
swap |
movi 2 |
add |
というプログラムであれば
番地 | 内容 | 意味 |
0 | 00000011 | movi 3 |
1 | 10000000 | swap |
2 | 00000010 | movi 2 |
3 | 01000000 | add |
というようにメモリに記憶されているということになります。コンピュータはこれらの命令を一つずつ読み取り実行していかければなりません。
そこで活躍するのがプログラムカウンタです。プログラムカウンタは今どの命令を実行しているかを指し示す記憶領域です。これはその名の通り、前々章で実装したカウンタを使って実現されます。カウンタはクロックが立ち上がるごとに値が一つずつ増えていくのでした。これにより、0 番地から順に 1 つずつ命令が実行されていくことになります。回路図で表すと以下のようになります。
リセット信号を入力するとカウンタの値が 0 となり、その時点からクロックが立ち上がるたびに、メモリの 0 番地から順番に 1, 2, 3 と命令が読み出されていきます。
いよいよコンピュータの頭脳、CPU を設計します。CPU は命令が送られてくるとそれに従ってレジスタの値を適切に書き換えます。といっても実は大した仕組みではなく、命令の種類に応じてどの入力を採用するかをセレクタで選択すれば実現できてしまいます。
命令信号 (8 bit) を \(P\) と表し、上位 1 ビット目を \(P_1\)、2 ビット目を \(P_2\) と表すことにします。
- movi 命令のとき、命令の上 2 桁は 00 です。このとき命令に記述してある値に書き換わります。
- add 命令のとき、命令の上 2 桁は 01 です。このとき値は直前クロックの値のままです。
- swap 命令のとき、命令の上 2 桁は 10 です。このとき直前クロックのレジスタ B の値に書き換わります。
つまり、以下のようなセレクタ構成でレジスタ A の遷移を表すことができます。
\(P_2 = 1\) であれば swap 命令ということなので、レジスタ B の現在の値でレジスタ A を更新します。
\(P_2 = 0\) のときは、\(P_1 = 0\) であれば movi 命令ということなので、\(P\) の値でレジスタ A を更新します。
\(P_2 = 0, P_1 = 1\) のときは add 命令ということなので、レジスタ A の現在の値でレジスタ A を更新します。これはつまり値を変化させないということです。
- movi 命令のとき、命令の上 2 桁は 00 です。このとき値は直前クロックの値のままです。
- add 命令のとき、命令の上 2 桁は 01 です。このとき A + B の値に書き換わります。
- swap 命令のとき、命令の上 2 桁は 10 です。このとき直前クロックのレジスタ A の値に書き換わります。
よって、以下のようなセレクタ構成でレジスタ B の遷移を表すことができます。
以上のことを全てまとめるとコンピュータの完成です。意外とあっけなく完成してしまいました。完成した回路図の全貌を以下に示します。
3 つの命令しかないですが、これでも立派なコンピュータです。この一つの回路だけで、メモリに書き込んだ内容次第では様々なことを計算することができます。フィボナッチ数の計算や累乗の計算など、リセットを押して一定クロック経つと LED に計算結果を表示することができます。
今回考えたコンピュータは movi, add, swap 命令だけでした。これではプログラムは上から下に実行されていくだけで for 文や if 文などの制御構文は実装できません。これらを実装するには、プログラムカウンタ自体もレジスタと見なす必要があります。たとえば、jump 命令を発行すると、movi 命令の要領でプログラムカウンタの値を書き換えてしまうのです。分岐命令については、特定のレジスタの値(または計算結果)と命令内容の AND を取ってセレクタの制御信号とするなどすると実現できます。他にも、色んな命令を考えて上記の素朴なコンピュータを建て増しする方法を考えるのは面白いと思います。ぜひやってみてください。
さらなる命令や発展的な仕組みについては命令セットアーキテクチャやプロセッサの記事で扱いたいと思います。まだ準備中なので、完成するまで待てない方は次章で紹介する参考図書などを読んでみてください。
- ハードウェアに組み込まれた記憶領域をレジスタという
- コンピュータへの命令を二進数で表したものを機械語という
- メモリには機械語が保存されている
- プログラムカウンタを用いてメモリから順番に命令を読み出す
- CPU はセレクタを使って命令を元に入力を選択することで実現できる