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

回路が「自分を書き換える」とは?

番地を、ずらしたい

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

手順を 0 と 1 のパターンにしてメモリに並べた。上 4 桁が「何をするか」、下 4 桁が「どの番地か」だ(第1〜3回)。 番地のカウンタに命令の下 4 桁を書き込めば飛べ(第4回)、引き算のあとのランプ C で飛ぶかどうかを選べた(第5回)。 前回は、足し算を何度も繰り返して、掛け算をさせた(第6回)。

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

12・13・14・15 番地に並べた 4 つの数の合計を取りたい。 1 つの「足す」を何度も通し、1 周するたびに、足す番地を 12、13、14、15 とずらしたい。 けれど、「足す 12 番地」の 12 は、命令の下 4 桁に書き込まれたパターンだ。ずっと 12 のままだ。

命令の中の番地を、1 つずつずらすことはできないか。

この回も、ゲートは 1 つも足さない。 足すのは、命令の見方だけだ。

命令を、数として読む

「足す 12 番地」は、メモリの中では 0010 1100 だ。 上 4 桁の 0010 が足す、下 4 桁の 1100 が 12。

第1回で、メモリの表を眺めたとき、こう書いた。 手順も、数も、メモリの中では 8 桁のパターンが並んでいるだけである。

では、この命令を、数として読んだらどうなるか。

「持ってくる」は、その番地の語の下 4 桁を、A の箱に入れる命令だった。 「足す 12 番地」が 1 番地に置いてあるなら、「持ってくる 1 番地」と書けばいい。

A の箱に入るのは、1100。12 だ。

回路は、1 番地の語が命令として置かれたものだとは知らない。 メモリの箱は、番地の入口に 1 が来たら、その語を出す。それだけだ。 出てきた語が命令の置き場に届けば、上 4 桁がデコーダに行く。A の箱に届けば、下 4 桁だけが入る。

下の図で、8 桁のマスを押して、1 語を 2 通りに読んでみてほしい。

メモリの 1 語(マスを押して替えられる)

命令として読む

読む拍で、命令の置き場へ

00101100

足す 12 番地

上 4 桁はデコーダへ

数として読む

持ってくるで、A の箱へ

00101100

12

上 4 桁は届かない

0010 1100 は、命令の置き場に届けば「足す 12 番地」、A の箱に持ってくれば 12。パターンは同じひとつで、どちらとして読むかは、どの拍に、どこへ届くかで決まる。

「1 番地(足す 12)」と「数 12」を押し比べてほしい。 命令として読めば、0010 1100 は足すで、0000 1100 は何もしない。 数として読めば、どちらも 12 だ。

同じパターンを、命令として読むか、数として読むか。 それを決めているのは、パターンではない。どの拍に、どの箱へ届くかだ。

1 足して、書き込む

A の箱に 12 が入った。 これに 1 を足せば、13 になる。1 は、どこかの番地に置いておけばいい(前回の「回数から引く 1」と同じだ)。

あとは、この 13 を 1 番地に書き戻す。「書き込む 1 番地」だ。

ここで、第3回で作ったメモリが効いてくる。 あのとき、メモリの語の下 4 桁だけを書けるようにした。上 4 桁は、読むだけのままだ。 上 4 桁のフリップフロップには、書き込みの合図の線が、はじめからつながっていない。

だから、書き込むは、下 4 桁だけを書き換える。 1 番地は 0010 1100 から 0010 1101 になる。 上 4 桁の足すはそのまま、番地だけが 13 になった。

書き込む先

A の箱の数

前00101100足す 12 番地後00101101足す 13 番地

紫の枠が、書き込みの合図が届く 4 桁。上 4 桁には、合図の線がつながっていない

1 番地は「足す 12 番地」から「足す 13 番地」に。命令の種類(上 4 桁 0010)は替わらず、番地だけが 13 になる。

書き込む先を 8 番地の止まるにしてみてほしい。 何を書き込んでも、上 4 桁は 1111 のまま、止まるは止まるのままだ。 この CPU では、プログラムが命令の種類を替えることはできない。替えられるのは、命令の番地だけだ。

押して、確かめる

この 2 つを組み合わせたのが、下のプログラムだ。15 語ある(9 番地は空き)。

番地中身読み方
00001 1010持ってくる 10 番地
10010 1100足す 12 番地(← この下 4 桁を書き換える)
20011 1010書き込む 10 番地
30001 0001持ってくる 1 番地
40010 1011足す 11 番地
50011 0001書き込む 1 番地
60110 1000ランプなら飛ぶ 8 番地
70101 0000飛ぶ 0 番地
81111 0000止まる
100000 0000合計を置く場所
110000 0001数 1(番地を 1 進める)
12〜150000 0001 など足す数(1・2・3・4)

上から、3 つのまとまりに分けて読める。

  • 0〜2 番地:合計に足す。 合計を持ってきて、1 番地の「足す」で 1 つ足し、10 番地に書き戻す
  • 3〜5 番地:1 番地を書き換える。 1 番地の命令を数として持ってきて、1 を足し、1 番地に書き戻す
  • 6〜8 番地:続けるか、止まるか。 ランプ C が点いていれば 8 番地で止まる。消えていれば 0 番地へ戻る

6 番地のランプについては、次の節で見る。まずは動かしてみよう。

自分を書き換える シミュレータ

「自動で進める」を押して、1 番地の「足す」の番地が 12・13・14・15 と書き換わっていくのを見てください

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

回路は前の 2 回とまったく同じ(1900 ゲート)。メモリの箱は、命令の行にも数の行にも、同じように下 4 桁を書き込む

速さ
拍
0
こなした命令
0

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

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

拍ごとの記録

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

青い行は、読む拍で使う番地(番地のカウンタ)。琥珀色の行は、こなす拍で使う番地(命令の下 4 桁)。紫の線は書き換わる 1 番地、紫の枠はその「足す」が指している行。12〜15 番地の下 4 桁は、押して替えられる(替えると、はじめからやり直す)

図は、前の 2 回とまったく同じだ。ゲートは 1 つも増えていない。

「自動で進める」を押して、表の 1 番地の行を見てほしい。 紫の線が引いてある行だ。読み方の欄に、書き換えの跡が残っていく。 紫の枠は、いまの「足す」が指している行だ。書き換えるたびに、枠が 1 行ずつ下へ動く。

周1 番地の命令足した数合計(10 番地)1 番地の書き換え
1足す 121112 拍目に 13 へ
2足す 132328 拍目に 14 へ
3足す 143644 拍目に 15 へ
4足す 1541060 拍目に 0 へ

1 周は 8 命令・16 拍。4 周目は 7 番地の飛ぶを通らずに 8 番地の止まるへ行くので、32 命令・64 拍で止まる。 10 番地の合計は 10。1 + 2 + 3 + 4 の答えだ。

1 つの「足す 12」が、12、13、14、15 番地の数を順に足した。 足す命令は、1 つしか書いていない。

12〜15 番地の数は、押して替えられる。 どんな数を入れても、10 番地には 4 つの和が入り(和が 15 を超えたら、下 4 桁)、必ず 64 拍で止まる。

止まったあとの 1 番地を見てほしい。0010 0000、「足す 0 番地」になっている。 プログラムは、自分を書き換えたまま終わる。 もう一度このまま動かしても、1 番地は 12 を指していない。最初の 1 周で、0 番地の命令 0001 1010 の下 4 桁を、数として合計に足してしまう。 0 番地から 15 番地まで 16 周、256 拍かかってようやく止まり、合計は 3 になる。 この画面の「はじめに戻す」は、メモリも最初のパターンに戻している。だから、何度でも同じように動く。

あふれたら、止まる

前回の掛け算は、「残りの回数」をメモリに置き、1 ずつ減らして、ランプで終わりを知った。 この回のプログラムには、回数を数える数がどこにも無い。

数えているのは、1 番地の番地そのものだ。

1 番地の下 4 桁は、4 桁しかない。15 に 1 を足すと、16 は 4 桁に入らない。 1111 + 0001 は 1 0000。4 桁には 0000 が残り、5 桁目の 1 がランプ C に入る。 第5回で、あふれたら止まるプログラムを書いたのと同じランプだ。

ランプ C

1 番地の下 4 桁

1100
  1. 足す 12
  2. 足す 13
  3. 足す 14
  4. 足す 15
  5. 足す 0
はじめは 1100(12)。3・4 番地で 1 番地を数として持ってきて、11 番地の 1 を足す。

4 番地の足すで、ランプ C が点く。 その次の 5 番地の書き込むは、ランプに触らない。 6 番地の「ランプなら飛ぶ 8」がそれを見て、8 番地の止まるへ飛ぶ。

12、13、14 のときは、足してもあふれない。ランプは消えたまま、7 番地の飛ぶで 0 番地へ戻る。

ひとつ、細かいところ。 1 番地の足すも、合計が 15 を超えるとランプ C を点ける。 けれど、6 番地で見る前に、4 番地の足すがランプを上書きする。だから、合計のあふれを、終わりの合図と取り違えることはない(前回の掛け算と同じだ)。

この止め方は、表を 15 番地で終わるように置いたから使える。 番地が 15 から 0 に戻るところが、そのまま表の終わりになっている。

同じパターン

この回のプログラムがしたことを、もう一度並べてみる。

  • 1 番地のパターンを、命令として読んで、足した
  • 同じ 1 番地のパターンを、数として読んで、1 を足した
  • その数を、また 1 番地に書き戻した

命令とデータを、別々の場所に置いていたら、こうはいかない。 同じメモリの、同じパターンだから、命令を数として読めるし、数として書き換えた結果が、そのまま次の命令になる。

命令とデータは、同じメモリの同じパターンだ。だからプログラムは、自分自身を数として読み、書き換えられる。

命令を数と同じようにメモリに置くやり方を、プログラム内蔵方式という。 1945 年に、フォン・ノイマンの名前でまとめられた報告書(「EDVAC についての報告書の第一草稿」)が、この考え方の土台を描いたとされる。報告書は、計算の手順もメモリが覚えておく材料に数え、メモリを 1 つのものとして扱おうとしていた。 そのため、この形のコンピュータはフォン・ノイマン型と呼ばれることが多い。

それより前の ENIAC という計算機では、手順は配線盤の線のつなぎ方で表していた。手順を替えるには、線をつなぎ替える。 第1回の書き出しの、配線で手順が決まっていた自動販売機や電卓と同じだ。

電子のメモリに置いたプログラムを初めて動かした計算機は、イギリスのマンチェスター大学の小さな試作機「Baby」だった。1948 年 6 月 21 日のことだ。 翌 1949 年 5 月 6 日には、ケンブリッジ大学の EDSAC が最初のプログラムを動かした(0 から 99 までの 2 乗の表を作るものだった)。

この CPU は、16 語しかない小さなものだ。けれど、形は同じだ。 第1回からずっと、命令と数を、同じ 1 つのメモリに並べてきた。

こわくもある

自分を書き換えられる、ということは、書き間違えれば、自分を壊せるということでもある。

5 番地の「書き込む 1」の 1 を、別の番地にしてみよう。 命令の中で替わるのは、下 4 桁だけだ。

5 番地「書き込む 5 番地」(0011 0101)

止=正しく止まる 違=止まるが合計が違う ∞=止まらない

書き込む 5。止まらない。28 拍目と同じ状態に 284 拍目で戻ったので、このあとも同じことを繰り返す。1 番地のかわりに書き換わった命令:5 番地。

16 通りのうち、正しく止まるのは 1 のときだけだ。 4 と 11 は止まるが、合計が違う。残りの 13 通りは、いつまでも止まらない。 1(0001)と 1 桁しか違わない 0・3・5・9 も、止まらない。

書き間違えた先が命令なら、その命令が別のものに変わる。 数なら、合計や「1」が壊れる。 しかも、壊れたプログラムは、壊れたことに気づかない。どの命令も、表どおりに淡々とこなされる。

この CPU では、上 4 桁は書けない作りにした。 だから、壊れ方にも限りがある。足すが止まるに化けることは無い。 これは、このシリーズの CPU の約束だ。本物の CPU がそうなっているとは限らない。

4 桁の CPU 4004 は、そもそもプログラムを書き換えられない形で使われていた。 プログラムは、4001 という読むだけのメモリ(ROM)に入れた。中身は、ROM を作るときの型(マスク)で、注文どおりに作り込む。動いている途中で、そこへ書き込む命令は無い。

自分を書き換えなくても番地をずらせるように、専用の箱も生まれた。 1949 年 4 月までに、マンチェスター大学の機械には、命令の中の番地に数を足してから使う箱(B ライン。いまの言葉でいうインデックスレジスタ)が付いた。 表を順に扱うのに、自分を書き換えるプログラムが要らなくなった。

それに、プログラムがメモリの中のパターンにすぎないなら、外から入ってきたパターンが、命令として読まれてしまうかもしれない。 書き換えられることは、悪用されうることでもある。

外の世界

この回のプログラムは、命令の番地をずらして、表を合計した。

けれど、振り返ってみると、足した数はどれも、はじめからメモリに置いてあった。 3 + 5 も、大きいほうを選ぶのも、掛け算も、この回の合計も、そうだった。 動き出してから、外から何かが入ってくることは、一度も無かった。

記憶のしくみの最終回で作った自動販売機を思い出してほしい。 硬貨は、いつ、いくら入ってくるか分からない。品物は、外へ出さないといけない。

外から数を受け取り、外へ何かを出すには、どうすればいいか。

次回は、いよいよ最終回。メモリの外とつながる入口と出口を足して、自動販売機をプログラムで動かしてみる。

参考文献