シリーズ「コンピュータの CPU のしくみ」 第5回

回路が「分かれる」とは?

見てから、決める

ここまでの 4 回を、短く振り返っておこう。

手順を 0 と 1 のパターンにして、メモリに並べた。1 つぶんの手順が命令で、上 4 桁が「何をするか」、下 4 桁が「どの番地か」だ(第1回)。 上 4 桁をデコーダに通すと、16 本の線のうち 1 本だけが 1 になり、その先の入口が開く(第2回)。 クロックを「読む拍」と「こなす拍」に分けて交互に回すと、命令が上から順に実行された(第3回)。 そして、番地のカウンタの入口の手前に選ぶ箱を置くと、命令の下 4 桁へ飛べるようになった(第4回)。

前回の終わりに、こう書いた。

飛ぶ命令は、そこに着いた瞬間に、飛ぶ。足し算の答えが何であっても、飛ぶ。 けれど、ランプ C は、15 + 5 のときだけ点いて、あとは消えていた。ランプ C は「計算の結果」を覚えている。

計算の結果を見て、飛ぶか飛ばないかを決められないか。

ただ、いまのランプ C が教えてくれるのは「4 桁からあふれた」ことだけだ。 本当に知りたいのは、たとえば「どちらの数が大きいか」のようなことだろう。

2 つの数を比べるには、どうすればいいか。 片方から、もう片方を引いてみればいい。

引く

この回路には、加算器しかない。引き算をする箱は無い。 けれど、新しい箱は要らない。引き算は、足し算に化けさせられる。

やり方は 2 つだけだ。

  1. 引く数の 4 桁を、すべて反転する(0 を 1 に、1 を 0 に)
  2. そのうえで、1 を足す

たとえば 7 − 3。3 は 0011。反転すると 1100(12)。 7 + 12 + 1 = 20。2 進数で 10100。 5 桁目の 1 を捨てると、0100。4 だ。 7 − 3 の答えになっている。

なぜこれでうまくいくのかは、次の節で見る。先に、回路に組み込もう。

命令に、新しい上 4 桁を割り当てる。0100、引くだ。 「引く 15 番地」は、A の箱から、15 番地の数を引いて、答えを A の箱に入れる。

デコーダの 0100 の線は、これまで、どこにもつながっていなかった。ここを 3 か所につなぐ。

  • 加算器の手前に、反転の箱を置く。中身は XOR が 4 つ。線 0100 が 1 のときだけ、メモリから出てきた数を反転する。0 なら素通りだ
  • 加算器の、いちばん下の桁の繰り上がりの入口。これまでは、いつも 0 だった。ここに線 0100 をつなぐ。引くときだけ、1 が足される
  • 「加算器 → A の箱」の入口。足すの線 0010 か、引くの線 0100 のどちらかが 1 なら開く。ランプ C も、足すか引くのときに受け取る

加算器の中身は、1 つも変えていない。変わったのは、入口につながる線だけだ。

反転の箱と加算器だけを、取り出してみよう。 A の箱の数は 7 に決めてある。上の 4 つのマスで引く数を、左のスイッチで線 0100 を切り替えてほしい。

引く数(メモリから)反転0011 = 3加算器(A の箱の 7 に足す)1010 = 10+15 桁目0押せる0011線 01000
線 0100 は 0。反転の箱は素通りで、いちばん下の桁にも何も足されない。7 + 3 = 10。足し算のまま。線 0100 を押してみてほしい。

線 0100 が 0 のときは、ただの足し算だ。7 + 3 = 10。 1 にすると、反転の箱が青くなって 0011 を 1100 に変え、加算器の「+1」に 1 が入る。答えは 0100、4 になる。

『計算のしくみ』を読んだ人は、第3回の「スイッチ 1 つで、両方をやる」を思い出してほしい。モードのスイッチ 1 本が、XOR の反転と、いちばん下の +1 の両方を引き受けていた。あのスイッチの役を、ここではデコーダの線 0100 が引き受けている。

5 桁目のランプ

さっきの 7 − 3 では、7 + 12 + 1 = 20 で、5 桁目に 1 があふれた。 加算器の 5 桁目は、ランプ C に入る。だから、引いたあと、ランプ C が点く。

では、3 − 7 ならどうか。 7 は 0111。反転すると 1000(8)。3 + 8 + 1 = 12。1100 で、5 桁目は 0。 ランプ C は、消える。 答えの 1100 は 12 だ。0〜15 の読み方では、3 − 7 の答えになっていない。3 から 7 は、引ききれないのだ。

どうしてこうなるのか。

4 桁の数 n を反転したものは、1111 − n、つまり 15 − n だ(1 の桁は 0 に、0 の桁は 1 に。どの桁も繰り下がらない)。 だから、引くとき加算器が計算しているのは、

A +(15 − n)+ 1 = A + 16 − n

16 は 10000、ちょうど 5 桁目の 1 だ。

  • A が n 以上なら、A − n は 0 以上。A + 16 − n は 16 以上になり、5 桁目に 1 があふれる。 残った 4 桁が A − n だ
  • A が n より小さいなら、A + 16 − n は 16 に届かない。5 桁目は 0 のまま

ランプ C は、引く前の A が、引く数以上かを、そのまま教えてくれる。

引く前の A と引く数の、256 通りを全部並べてみよう。緑のマスが、ランプ C が点いた引き算だ。

A\数0123456789101112131415
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
縦が引く前の A の箱、横が引く数。緑のマスが、ランプ C が点いた引き算(256 通りのうち 136 通り)。いま選んでいるのは 7 − 3:答えの 4 桁は 0100(4)、ランプ C は点く(7 は 3 以上)。マスを押すと、ほかの引き算も見られる。

緑は、左下の三角形にきれいに並ぶ。どの行も、A と同じ数の列までが緑で、その右から灰色になる。 ランプ C =「A は、その数以上」。 256 通りのうち、例外は 1 つもない。

『計算のしくみ』を読んだ人は、第4回の「5 桁目のランプが教えていたこと」を思い出してほしい。引き算でランプが消えるのは、0〜15 の読み方で答えが 0 を割り込んだという知らせだった。ここで使うのは、その知らせそのものだ。

同じランプでも、足すのあとは「4 桁からあふれた」、引くのあとは「引く前の数が、その数以上」を覚えている。 ランプは、最後に加算器を使った命令の 5 桁目を覚えているだけだ。読み方を決めるのは、手順のほうだ。

ランプなら、飛ぶ

比べた結果は、ランプ C に残った。 あとは、それを見て、飛ぶかどうかを決めればいい。

命令に、もう 1 つ上 4 桁を割り当てる。0110、ランプなら飛ぶだ。 ランプ C が点いていれば、番地のカウンタを命令の下 4 桁に書き換える。消えていれば、何もしない。番地のカウンタは、いつもどおり次の番地を指したままだ。

回路に足すものは、少ない。 前回作った次の番地を選ぶ箱は、こうなっていた。

  • 線 0101(飛ぶ)AND こなす拍 → 命令の下 4 桁を通す

ここに、もう 1 つ条件を足す。

  • 線 0110(ランプなら飛ぶ)AND こなす拍 AND ランプ C → 命令の下 4 桁を通す

2 つを OR で合流させる。どちらかが 1 なら、下 4 桁を通す。 ランプ C の線を、1 本、選ぶ箱に引き込むだけだ。

選ぶ箱だけを取り出したのが、下の図だ。 1 を足した数は 0100(4)、命令の下 4 桁は 0110(6)にしてある。あとで動かす「3 番地の、ランプなら飛ぶ 6 番地」を読んだ場面と同じ数だ。

+1 した数0100 = 4命令の下 4 桁0110 = 6次の番地を選ぶ+1+1下 4 桁番地のカウンタの入口へ0100 = 4線 01010線 01100こなす拍0ランプ C0
どの線も 0。選ぶ箱は +1 した数(4)を通す。左の 4 つのスイッチを押してみてほしい。

線 0110 とこなす拍を 1 にしても、ランプ C が 0 なら「+1」のまま。3 つとも 1 にしたときだけ「下 4 桁」に変わる。 線 0101 のほうは、前回と同じく、ランプ C に関係なく飛ぶ。

番地のカウンタが受け取る合図にも、同じ条件を足す。

  • 読む拍のクロック(これまでどおり)
  • 飛ぶ拍のクロック … こなす拍のクロック AND 線 0101(前回)
  • ランプなら飛ぶ拍のクロック … こなす拍のクロック AND 線 0110 AND ランプ C

3 つを OR で合流させる。

ここで、前回と同じ落とし穴がある。条件は、どれも省けない。

選ぶ箱から「こなす拍」を外すと、飛んだ次の読む拍で困る。命令の置き場には、まだランプなら飛ぶが残っていて、ランプ C も点いたままだ。読む拍なのに下 4 桁が選ばれ、番地のカウンタは 1 進まない。前回の飛ぶと、まったく同じことが起きる。

合図から「ランプ C」を外すと、今度は飛ばないときに困る。ランプ C が消えていても、こなす拍に番地のカウンタが受け取ってしまう。そのとき選ぶ箱が通しているのは「+1 した数」だ。番地のカウンタは 1 余計に進み、次の命令を 1 つ読み飛ばす。

あとで動かすプログラムを、本物の回路と、条件を 1 つずつ外した回路で回した記録が、下の表だ。

表の数は、どれもその拍が終わったあとの値

拍段階命令の置き場番地のカウンタ13 番地
1読む0001 111010
2こなす0001 111010
3読む0011 110120
4こなす0011 110127
5読む0100 111137
6こなす0100 111137
7読む0110 011047
8こなす0110 011067
9読む1111 000067
10止まる1111 000067
8 拍目に飛んで、9 拍目は 6 番地を読む。そのとき命令の置き場には、まだランプなら飛ぶが残っていて、ランプ C も点いたまま。読む拍なのに下 4 桁が選ばれ、番地のカウンタは 7 に進まず 6 のまま。ここでは 6 番地が止まるなので、そのまま止まって答えも合う。けれど飛び先が足すなら、次の読む拍でもう一度 6 番地を読み、2 回足してしまう(第4回と同じ落とし穴)。 色の付いた行が、本番の回路と食い違ったところ。

「合図から『ランプ C』を外す」のボタンで「3 と 7」を選ぶと、8 拍目に番地のカウンタが 4 ではなく 5 になる。4 番地を読み飛ばして、13 番地には 7 ではなく 12 が入ってしまう。 ランプ C は、選ぶ箱と、受け取る合図の、2 か所に要る。

押して、確かめる

2 つの数のうち、大きいほうを 13 番地に書き込むプログラムを置いた。

番地中身読み方
00001 1110持ってくる 14 番地
10011 1101書き込む 13 番地
20100 1111引く 15 番地
30110 0110ランプなら飛ぶ 6 番地
40001 1111持ってくる 15 番地
50011 1101書き込む 13 番地
61111 0000止まる
130000 0000大きいほうを置く場所
140000 0111数 7(1 つ目)
150000 0011数 3(2 つ目)

考え方は、こうだ。

  • まず、1 つ目の数を、ひとまず 13 番地に書いておく(0・1 番地)
  • 1 つ目から 2 つ目を引いてみる(2 番地)。ランプ C が点けば、1 つ目は 2 つ目以上だ
  • 点いていれば、そのまま止まる番地へ飛ぶ(3 番地)
  • 点いていなければ、2 つ目の数で 13 番地を書き直す(4・5 番地)

分かれる シミュレータ

14・15 番地の 2 つの数を替えて、13 番地に大きいほうが残るかを確かめてください

拍の状態読む拍クロックの門開次の番地を選ぶ+1+1番地のカウンタ0000 = 0命令の置き場0000 0000デコーダ0011000101000010111101010110番地を選ぶ0000 = 0メモリ(16 語)0001 1110メモリ → A の箱閉反転加算器 → A の箱閉A の箱0000 = 0加算器1110 = 14+1ランプ C0クロック0

太い線は束(番地と数は 4 本、語は 8 本)。反転の箱は、引くときだけ青くなる。ランプ C の線は、右のふちを上って「次の番地を選ぶ」へ入る

はじめは読む拍。番地のカウンタは 0。次の番地を選ぶ箱は、いつもは「+1」を通している。クロックを入れると、0 番地の語が命令の置き場に届く。

読んだ番地の順:まだ読んでいない

拍ごとの記録

拍段階番地起きたこと
まだクロックを入れていない
番地中身読み方(人間のメモ)
0000(0)持ってくる 14 番地
0001(1)書き込む 13 番地
0010(2)引く 15 番地
0011(3)ランプなら飛ぶ 6 番地
0100(4)持ってくる 15 番地
0101(5)書き込む 13 番地
0110(6)止まる
0111(7)0000 0000
1000(8)0000 0000
1001(9)0000 0000
1010(10)0000 0000
1011(11)0000 0000
1100(12)0000 0000
1101(13)0000 0000数 0(大きいほうを置く場所)
1110(14)0000数 7(1 つ目)
1111(15)0000数 3(2 つ目)

青い行は、読む拍で使う番地(番地のカウンタ)。琥珀色の行は、こなす拍で使う番地(命令の下 4 桁)。0〜6 番地は 8 桁すべてを、14・15 番地は下 4 桁を、押して替えられる(替えると、はじめからやり直す)

第4回の図に、新しいものが 2 つ増えた。 列のあいだの細長い反転の箱と、右下のランプ C から、右のふちを上って「次の番地を選ぶ」へ入る線だ。

「クロックを入れる」を押していこう。

拍段階番地何が起きたか
1読む00 番地の命令を読む
2こなす1414 番地の 7 を A の箱へ
3読む11 番地の命令を読む
4こなす13A の箱の 7 を 13 番地へ(ひとまず)
5読む22 番地の命令を読む
6こなす15A の箱 ← 7 − 3 = 4。ランプ C が点く
7読む33 番地の命令を読む
8こなす6ランプ C が点いているので、番地のカウンタ ← 6
9読む66 番地の命令を読む
10こなす0クロックの門が閉じる

6 拍目、反転の箱が青くなり、「加算器 → A の箱」が開く。 8 拍目、選ぶ箱の文字が「下 4 桁」に変わる。4・5 番地は読まれない。10 拍で止まり、13 番地は 7 だ。

では、表の 14 番地と 15 番地を入れ替えてみよう。14 番地を 0011(3)、15 番地を 0111(7)にする。

今度は 6 拍目に 3 − 7。A の箱は 1100(12)になり、ランプ C は消える。 8 拍目、ランプなら飛ぶの線は 1 なのに、選ぶ箱は「+1」のまま。番地のカウンタは 4 を指したままで、9 拍目に 4 番地を読む。 15 番地の 7 を持ってきて、13 番地を書き直し、14 拍で止まる。13 番地は、やはり 7 だ。

同じ手順なのに、数によって、通る道が違った。 7 と 3 なら 0、1、2、3、6 番地。3 と 7 なら 0、1、2、3、4、5、6 番地。 2 つの数を同じにすると、ランプ C は点く(その数以上だから)。飛んで、1 つ目の数が残る。

ほかの数でも試してみてほしい。16 × 16 の 256 通り、どれでも 13 番地には大きいほうが残り、必ず止まる。

この箱の中では、ゲートが 1900 個働いている。前回より 8 個増えた。

判断は、どこに

このプログラムは、外から見ると、2 つの数を比べて、大きいほうを選んでいる。 「判断」をしているように見える。

けれど、回路の中を探しても、「大きい」を知っている場所はない。

起きたのは、こういうことだけだ。

  • 反転の箱と加算器が、A + 16 − n を計算した
  • 5 桁目に 1 があふれるかどうかが、ランプ C に残った
  • 選ぶ箱が、ランプ C の線を 1 本見て、「+1」か「下 4 桁」かを選んだ

引き算のあとの 5 桁目のランプ 1 つで、次に読む番地を選べる。それだけで、回路は「判断」をする。

回路は、比べていない。大小も知らない。ランプ C が点いた理由も知らない。 5 桁目があふれたかどうかで、次の番地が変わる。それだけだ。 「大きいほうを選ぶ」という意味は、命令をそう並べた人間の側にある。

こういう、条件によって次に読む番地が変わることを、条件分岐と呼ぶ。

『記憶のしくみ』を読んだ人は、最終回の自動販売機の「足りる」の線を思い出してほしい。金額から値段を引く加算器の、5 桁目への繰り上がりが「足りる」だった。あの回路は、その線を見て、次の段階を選んでいた。ここでは同じ線が、次に読む番地を選んでいる。

本物の CPU には、ランプ C のような印が、いくつもある。 4 桁の CPU 4004 の条件つきの飛ぶは、「A の箱(アキュムレータ)が 0」「キャリー(ランプ C にあたるもの)が 1」「外から来る TEST という線が 0」を見ることができ、条件を逆にすることもできた。 この回路は、ランプ C の 1 つだけで進む。

止められる

前回の、止まらないプログラムに戻ろう。 足して、書き込んで、0 番地へ飛ぶ。13 番地は 5、10、15、4、9……と回り続けた。

あのとき、15 + 5 でランプ C が点くのを見た。足すのランプは「4 桁からあふれた」を覚えている。 そこで、足したすぐあとに、ランプなら飛ぶを 1 つはさむ。

番地中身読み方
00010 1110足す 14 番地
10110 0100ランプなら飛ぶ 4 番地
20011 1101書き込む 13 番地
30101 0000飛ぶ 0 番地
41111 0000止まる
130000 0000合計を置く場所
140000 0101数 5

あふれなければ、ランプなら飛ぶは何もしない。書き込んで、0 番地へ戻る。 あふれたら、書き込む前に、止まる命令へ飛ぶ。

あふれたら止まる シミュレータ

1 番地の「ランプなら飛ぶ」で、手順がいつ分かれるかを見てください

拍の状態読む拍クロックの門開次の番地を選ぶ+1+1番地のカウンタ0000 = 0命令の置き場0000 0000デコーダ0011000101000010111101010110番地を選ぶ0000 = 0メモリ(16 語)0010 1110メモリ → A の箱閉反転加算器 → A の箱閉A の箱0000 = 0加算器1110 = 14+1ランプ C0クロック0

太い線は束(番地と数は 4 本、語は 8 本)。反転の箱は、引くときだけ青くなる。ランプ C の線は、右のふちを上って「次の番地を選ぶ」へ入る

はじめは読む拍。番地のカウンタは 0。次の番地を選ぶ箱は、いつもは「+1」を通している。クロックを入れると、0 番地の語が命令の置き場に届く。

読んだ番地の順:まだ読んでいない

拍ごとの記録

拍段階番地起きたこと
まだクロックを入れていない
番地中身読み方(人間のメモ)
0000(0)足す 14 番地
0001(1)ランプなら飛ぶ 4 番地
0010(2)書き込む 13 番地
0011(3)飛ぶ 0 番地
0100(4)止まる
0101(5)0000 0000
0110(6)0000 0000
0111(7)0000 0000
1000(8)0000 0000
1001(9)0000 0000
1010(10)0000 0000
1011(11)0000 0000
1100(12)0000 0000
1101(13)0000 0000数 0(合計を置く場所)
1110(14)0000数 5
1111(15)0000 0000

青い行は、読む拍で使う番地(番地のカウンタ)。琥珀色の行は、こなす拍で使う番地(命令の下 4 桁)。0〜4 番地は 8 桁すべてを、14 番地は下 4 桁を、押して替えられる(替えると、はじめからやり直す)

「自動で進める」を押して、眺めてほしい。 読んだ番地は、0、1、2、3 を 3 周したあと、0、1、4。 13 番地には 5、10、15 と書かれ、15 + 5 であふれたところで、30 拍で止まる。 13 番地は 15 のままだ。

14 番地の数を替えても、13 番地には 15 を超えない最後の合計が残って止まる。1 にすると 15 まで数えて、126 拍。 ただし 0 にすると、いつまでもあふれないので、止まらない。

何度も、決める

いまのプログラムを、もう一度見てほしい。

0 番地から 3 番地までの 4 つの命令を、何度も通った。そして、ランプが点いたところで、抜けた。 飛ぶだけなら、止まらなかった。ランプなら飛ぶだけなら、1 回分かれて終わりだった。 2 つを組み合わせると、同じ命令を何度も通り、決めたところで抜ける道が作れる。

ここで、ふと気になる。

この回路にできる計算は、足すと引くだけだ。 掛け算は、足し算を何度も繰り返せばできる。3 × 5 は、3 を 5 回足せばいい。

足し算しかできない回路に、掛け算をさせられないか。

次回は、繰り返す回数を数えながら、プログラムで掛け算をしてみる。

参考文献