オートメーションの夢 — 自動加算器から「コンピュータ」へ
前講「バイトとメモリ」は、フリップフロップを整然と並べただけの装置が、64キロバイトの数を好きな順番で 読み書きできるRAMアレイになる——という驚きで幕を閉じた。人は昔から、面倒で単調な作業を機械に押しつけたがる 生き物である。手作業を数分縮めるためだけの装置に、何百時間も費やしてしまうくらいには。 この講では、その怠惰さに従って足し算の作業を機械に丸投げしていく。 ところが、RAMとカウンタと発振器を足しただけの素朴な自動加算器は、たちまち壁にぶつかる。止まらない。 引き算ができない。途中の結果を残せない。その壁を一つずつ壊していった先で、私たちは思いがけないものを 組み立ててしまう——「コンピュータ」と呼んで差し支えないマシンである。 全6セクション・演習28問。回答はブラウザに保存されるので、途中でやめても続きから再開できる。
進捗 0 / 28 問(正解 0 問)
1. 自動加算器を作る — RAMと発振器で人手を減らす
前講の終わりに組み上げたRAMアレイは、フリップフロップを並べただけの装置でありながら、64キロバイトの数を好きな順番で読み書きできた。ここではそのRAMアレイを、第14章で作った8ビット加算器+ラッチ(累算器)の入力側につなぐ。人がスイッチを叩く代わりに16ビットカウンタがアドレスを進め、人が手でクロックを刻む代わりに発振器が刻む——それだけで「たくさんの数を足し合わせる」という退屈な作業から人手が消える。ただし、この最初の自動加算器はまだ致命的な欠点をいくつも抱えている。止まる方法がなく、足し算しかできず、途中経過を残す場所もない。この欠点の一覧こそが、以降のセクションで一つずつ埋めていく設計課題になる。
出発点は、第14章で組み上げた8ビット加算器とラッチの組み合わせである。加算器の出力をラッチに保存し、 そのラッチの出力を加算器のもう一方の入力に戻してやると、数を入力するたびに和が積み上がっていく。 この「和を溜め込む」ラッチには専用の呼び名がある——累算器である。
この装置で100個の数を足すには、100回スイッチを叩き、100回クロックを刻まなければならない。それだけなら まだ辛抱できるが、本当の問題は別にある。60個目を入力したあたりで打ち間違いに気づいたら、 最初からやり直すしかない。入力した数がどこにも残っていないからである。
そこで前講のRAMアレイを引っぱり出してくる。あらかじめ100個の数をRAMアレイに書き込んでおけば、間違いに 気づいたときはその番地だけを直せばよい。Takeoverスイッチを切り替えてRAMアレイの制御をマシン側に渡し、 加算器の入力をRAMアレイの出力につなぐ。そして16ビットカウンタにRAMアレイのアドレスを 担当させ、人が刻んでいたクロックを発振器に置き換える。
クリアスイッチを開いた瞬間から、装置は勝手に動き出す。クロックが0から1に変わるたびに、 累算器が新しい和を保存し、それと同時に16ビットカウンタが1つ進む。 カウンタが進めば次の数がRAMアレイの出力に現れ、加算器はそれを足し込む。人の仕事は、最初に数を 書き込むことだけになった。
しかしこの自動加算器は、まだいくつもの欠陥を抱えている。第一に、止まらない。 100個を足し終わってもカウンタは進み続け、FFFFhの次には車のオドメーターのように0000hへ巻き戻って、 すでに足した数をまた足し始めてしまう。第二に、足し算しかできない。第三に、8ビットに収まらない大きな数を 扱えない。第四に、複数のグループの和を別々に残しておけない。この4つの欠陥が、以降のセクションの 設計課題そのものになる。
4つの欠陥のうち、まず第一の「止まらない」を自分の手で確かめよう。下のシミュレータは、RAMアレイに 並べた数をカウンタが順に指し、累算器へ足し込んでいく自動加算器である。「1ステップ」を押していくと 正しい合計が出るが、そこで止まらずカウンタが進み続け、末尾を過ぎると先頭へ巻き戻って同じ数を 二重に足してしまう。停止命令を置いたモードに切り替えると何が変わるか、対比して見てほしい。
待機中
RAMアレイ(8番地に縮小)
色付きのマスが、カウンタの指している番地です
0〜2番地に3つの数が入っています。合計は50になるはずです。マシンを止める仕組みは、どこにもありません。
この自動加算器には「止まる」という動作そのものがありません。3つの数を足し終えて正しい合計50が出ても、 カウンタは素知らぬ顔で進み続け、末尾の次には0番地へ巻き戻って、足したはずの数をまた足してしまいます。 機械を正しく止めるには、部品を増やすのではなく、「止まれ」という指示=停止命令をメモリの中に 書いておくという発想の転換が必要です。次のセクションで、この「命令」という発想が マシン全体を作り替えていきます。
第14章の加算器に、なぜRAMアレイとTakeoverスイッチを組み合わせる工夫が必要になったか
この自動加算器で、16ビットカウンタが担う役割はどれか
クリアスイッチが開かれた後、発振器のクロックが最初に0から1に変わるとき、16ビットカウンタの値は0000hからいくつに変わるか
この自動加算器が抱える限界として正しい説明はどれか
このセクション: 0 / 4 問正解
2. 命令という発想 — ロード・保存・加算
ただ足し続けるだけの機械を、「3個の数の和」「別の2個の数の和」というように、グループごとの結果を別々に残せる機械に変えたい。そのためにはマシンに対して「今から新しい計算を始めよ」「この数を足せ」「結果を書き出せ」「止まれ」と伝える手段が要る。ここで生まれるのが命令という発想である。ロード・保存・加算・停止という4つの操作に数値コードを割り当て、そのコードだけを収めた専用のRAMアレイ——コードRAMアレイ——を、数値を収めたデータRAMアレイと並べて置く。この数値コードは、操作コード・命令コード、あるいはオプコードと呼ばれる。累算器への入力をロード時と加算時で切り替えるために、2線-1線セレクタという小さな部品も新しく加わる。
欠陥のうち、最も根の深いものから手をつける。「3個の数の和」「次の2個の数の和」「さらに別の3個の数の和」 というように、グループごとの結果を別々に残したい——この要求に、ただ足し続けるだけの 装置は答えられない。答えるためには、マシンに対して4種類の異なる指示を出せなければならない。
- ロード: 今のグループの計算を始める(累算器に最初の数を読み込む)
- 加算: この数を累算器に足す
- 保存: 今の累算器の内容を、RAMアレイに書き出す
- 停止: マシンを止める
ここが本講の最初の転回点である。それぞれの操作に数値コードを割り当てるという発想が 生まれる。ロードは何番、加算は何番、というように番号を決めておけば、「何をすべきか」を数値で表せる。 この数値コードは操作コード、命令コード、あるいは短く オプコードと呼ばれる。
問題は置き場所である。元のRAMアレイには、加えるべき数値そのものしか入っていない。指示を書き込む すきまがない。そこでコードRAMアレイをもう1つ用意し、データRAMアレイと同じ16ビット カウンタで同時にアクセスする。カウンタが番地Nを指すと、データRAMからは「数値」が、コードRAMからは 「その数値に対して何をすべきか」が、同時に出てくるという構図になる。
もう1つ小さな部品が増える。ロード命令のときは累算器にデータRAMの出力をそのまま書き込みたいが、 加算命令のときは加算器で計算した結果を書き込みたい。この2つの入力のどちらを累算器へ渡すかを、 オプコードに応じて選ぶ回路が必要になる——2線-1線セレクタである。前講までに 組み立てた部品の組み合わせで作れる、地味だが欠かせない交通整理役である。
累算する(足しっぱなしにする)だけでなく、「ロード」「加算」「保存」という複数の異なる操作が必要になった理由はどれか
何をすべきか(ロード・加算・保存・停止)を指示するための数値コードを、データとは別のRAMアレイ(コードRAMアレイ)に保存することにした理由はどれか
改良版の自動加算器で「2線-1線セレクタ」が新たに必要になった理由はどれか
このセクション: 0 / 3 問正解
3. 桁を超える工夫 — 減算とキャリー付き加算
足し算だけの機械に引き算を足すのに、引き算専用の回路を新しく作る必要はない。第7講で確かめた2の補数の仕組み——反転して1を足す——を思い出せば、データを反転して加算器のキャリー入力を1にセットするだけで、既存の加算器がそのまま減算器として働く。オプコード表に減算が加わる。さらに、8ビットに収まらない16ビット・24ビット・32ビットの数を扱うには、下位バイトの計算で生じた繰り上げを上位バイトの計算へ引き継ぐ必要がある。この「覚えておく」役を1ビットのキャリーラッチに任せ、その値をキャリー入力として使うキャリー付き加算とボロー付き減算を追加すると、オプコードは7種類に育つ。
次は「足し算しかできない」という欠陥である。引き算専用の回路を新しく作るのだろうか——そうではない。 第7講「たし算とひき算の回路」で確かめた2の補数の仕組みが、ここで実務に投入される。 引きたい数のビットをすべて反転し、加算器へのキャリー入力を1にセットしてから足す。それだけで、 加算器のハードウェアがそのまま減算器として働く。
つまり減算命令の追加は、回路の新設ではなく、オプコードの追加とインバータの制御で済む。 減算命令が来たらデータRAMの出力を8ビットインバータに通し、キャリー入力を1にする。加算命令が来たら インバータを素通しにし、キャリー入力を0にする。この2つを切り替えるだけである。
3Ch - 12h = 2Ah
(10進で 60 - 18 = 42)
続いて「8ビットに収まらない大きな数を扱えない」という欠陥に取りかかる。16ビットの数を扱うには、 下位バイトと上位バイトを別々に足すことになるが、ここに落とし穴がある。下位バイトの足し算で 繰り上げ(キャリー)が出たとき、それを上位バイトの足し算に引き継がなければ、答えがずれてしまう。 筆算で桁上がりを書き添えるのと同じことを、マシンにもさせなければならない。
そのために1ビットのキャリーラッチを加える。加算器のキャリー出力を保存しておく、 たった1ビットの記憶である。そして、その値をキャリー入力として使う新しい命令 ——キャリー付き加算とボロー付き減算——を用意する。通常の加算命令が キャリー入力を常に0にするのに対し、キャリー付き加算はキャリーラッチの値をそのまま使う。この違いだけで、 何バイトにまたがる数でも正しくつなげられる。
使い方は単純である。最下位バイトの足し算にはキャリー入力が要らないので通常の加算命令を使い、 2バイト目から先はキャリー付き加算命令を使う。3バイト(24ビット)の数どうしなら、 加算1回とキャリー付き加算2回。4バイトなら加算1回とキャリー付き加算3回。オプコードの表は、 ロード・保存・加算・減算・キャリー付き加算・ボロー付き減算・停止の7種類まで育った。
減算命令が、加算命令のハードウェアをほぼそのまま使って実現できる理由はどれか
累算器に3Chが入っている。減算命令でデータRAMの値12hを引く(3Ch-12h)と、結果はいくつになるか
16ビットなど複数バイトにまたがる数を足すときに、1ビットの「キャリーラッチ」が必要になる理由はどれか
キャリー付き加算命令(ADC)が通常の加算命令(ADD)と異なる点はどれか
24ビット(3バイト)の数どうしを正しく足し合わせるには、加算命令(ADD)とキャリー付き加算命令(ADC)をそれぞれ何回使うべきか
このセクション: 0 / 5 問正解
4. コードとデータをひとつに、そしてジャンプ
ここまでの設計には見過ごせない欠陥がある。コードRAMとデータRAMを同じ16ビットカウンタで同時に進めているため、コードの番地とデータの番地が固定的に対応してしまい、一度保存した結果を後から読み戻せない。解決策は思い切った再設計になる——各命令をオプコード1バイトと16ビットアドレス2バイトの、合計3バイトにする。命令が自分で対象の番地を指定できるようになれば、コードとデータを同じ番地に並べておく理由は消え、2つのRAMアレイは1つに統合できる。そして統合の副作用として新たな問題が現れ、その解決として、プログラムカウンタに新しいアドレスを直接ロードするジャンプ命令が生まれる。
ここで、これまでの設計に潜んでいた重大な欠陥が表面化する。コードRAMとデータRAMを同じ16ビットカウンタで 同時に進めているということは、コードの番地とデータの番地が、常に同じ数で固定されている ということである。番地20hの命令が扱えるデータは、番地20hのデータだけ。
すると、保存命令でデータRAMに書き出した計算結果を、後から累算器へ読み戻す方法がない。読み戻したければ、 その値を書き込んだ番地の命令をもう一度実行するしかなく、それは同じ計算をやり直すことを意味する。 計算結果を材料にして次の計算をする——ごく当たり前のことが、この設計ではできない。
解決策は思い切った再設計になる。各命令を、オプコード1バイトと16ビットアドレス2バイト、合計3バイト にする。命令自身が「どの番地を対象にするか」を持ち歩くのである。マシンは3個のラッチを使って、 1バイト目に命令コードを、2バイト目と3バイト目にアドレスの上位と下位を順に取り込む——これが 命令のフェッチである。
1個目の命令: 0000h 〜 0002h
2個目の命令: 0003h 〜 0005h
3個目の命令: 0006h 〜 0008h
この変更が、思わぬ副産物をもたらす。命令が自分でアドレスを指定できるのなら、 コードとデータを同じ番地に並べておく理由がもうない。コードRAMアレイとデータRAMアレイを 1つに統合し、同じアレイの別々の場所に命令とデータを自由に配置してよいことになる。アドレスの出所を 「プログラムカウンタから」と「命令の中のアドレス部分から」で切り替えるために、ここでも2線-1線セレクタが 働く。
しかし統合されたRAMアレイは、新しい危険も連れてくる。命令を書き足そうとして末尾の停止命令を単純に 取り除くと、その後ろに置いてあったデータのバイト列が、命令コードとして読まれてしまう。 マシンには、そのバイトが命令なのかデータなのかを区別する手立てがないからである。ここで ジャンプ命令が登場する。プログラムカウンタは16ビットカウンタであり、それを構成する フリップフロップにはプリセットとクリアの入力がある。そこへ命令が持っているアドレスを直接書き込めば、 実行の流れを好きな番地へ移せる。実行順序が、初めて人の設計下に入った。
コード用RAMとデータ用RAMを別々に用意し、同じ16ビットカウンタで両方を同時に進めていく初期設計の問題点はどれか
各命令を「オプコード1バイト+16ビットアドレス2バイト」の合計3バイトにした狙いはどれか
停止以外の各命令が3バイト(オプコード1+アドレス2)を占める設計で、アドレス0000hから命令を3個連続して並べたとき、3個目の命令のオプコードは何番地から始まるか
コード用とデータ用に分かれていた2つのRAMアレイを、1つのRAMアレイにまとめられるようになった理由はどれか
ジャンプ命令が新たに必要になった理由はどれか
このセクション: 0 / 5 問正解
5. 条件分岐でループを作る — これがコンピュータだ
ジャンプ命令を手に入れても、掛け算のような「決まった回数だけ同じ処理を繰り返す」仕事はまだできない。同じ命令列を掛ける回数だけ書き写すか、リセットボタンを同じ回数押すしかないからである。ここで加算器の8ビット出力を監視するNORゲートを置き、その出力を保持するゼロラッチを加える。加算器の出力が8ビットすべて0のときだけ1になるこの1ビットが、ゼロフラグである。キャリーフラグと合わせて、ゼロならジャンプ・キャリーならジャンプ・ゼロでないならジャンプ・キャリーでないならジャンプという条件ジャンプが作れる。条件が満たされる間だけ同じ命令列へ戻り続け、満たされなくなった瞬間に先へ進む——この制御された反復こそが、単なる計算機とコンピュータを分ける決定的な一線である。
ジャンプ命令を手に入れたマシンで、掛け算をさせてみる。掛け算は繰り返しの足し算だから、原理的にはできる はずである——被乗数を、乗数の回数だけ足し込めばよい。ところが、実際に書こうとすると手が止まる。 「乗数の回数だけ」という指定ができない。
できることは2つしかない。同じ命令列を掛ける回数だけ書き写すか、1回分の命令列の末尾からジャンプで戻る ようにしておいて、リセットボタンを回数分だけ押すか。どちらにしても、掛ける数が大きくなればなるほど 人間の作業が比例して増える。オートメーションを求めてここまで来たのに、また人手に戻ってきてしまった。
足りないのは、マシン自身が状況を見て進路を選ぶ能力である。そこで加算器の8ビット出力を 1つの8ビットNORゲートに通し、その出力をゼロラッチに保存する。NORゲートは入力が すべて0のときだけ1を出すから、このラッチは加算器の出力8ビットがすべて0のときだけ1になる。 これがゼロフラグである。名前の印象とは逆で、「結果がゼロのときに1が立つ」という点は 取り違えやすいので、正確に覚えておきたい。加算・減算・キャリー付き加算・ボロー付き減算のいずれかが 実行されるたびに更新され、保存命令では更新されない。
ゼロフラグとキャリーフラグを見て動く命令を追加すれば、オプコードの一覧は完成する ——ジャンプ、ゼロならジャンプ、キャリーならジャンプ、ゼロでないならジャンプ、キャリーでないなら ジャンプ。掛け算はこうなる。乗数を1ずつ減らしながら被乗数を足し込み、末尾に「ゼロでないならジャンプ」を 置く。乗数が0になった瞬間だけゼロフラグが1になり、マシンはループを抜けて先へ進む。
これが決定的な一線である。条件が満たされる間だけ同じ命令列へ戻り、満たされなくなったら 先へ進む——この制御された反復を手に入れた瞬間、目の前の装置は単なる計算機ではなくなる。 掛け算ができるなら割り算もでき、割り算ができるなら平方根も対数も三角関数も、原理的には計算できる。 残りはすべてソフトウエアの仕事である。速いからでも、たくさん覚えられるからでもない。 条件を見て自分で戻れるから、それはコンピュータなのである。
単純なジャンプ命令だけで2つの数の掛け算(繰り返し足し算)をさせようとすると、何が現実的な問題になるか
ゼロフラグ(ゼロラッチ)が1にセットされるのはどんなときか
「ゼロでないならジャンプ」のような条件ジャンプ命令が、掛け算のような繰り返し処理(ループ)を可能にする仕組みはどれか
被乗数34hに乗数0Bh(10進11)を掛けるプログラムで、乗数を1ずつ減らしながら被乗数を足し込むループを組んだ場合、合計で何回の足し込みが実行されるか
このセクション: 0 / 4 問正解
6. マシン語からアセンブリ言語へ — ハードとソフトの境界
組み上がったマシンを、今度は言葉で整理し直す。加算器とインバータをまとめて算術論理演算器、略してALUと呼ぶ。16ビットカウンタはプログラムカウンタという別名を持ち、ALU・累算器・プログラムカウンタ・制御回路をひとまとめにしたものがプロセッサ、すなわちCPUである。リレーや導線のような物理的な部品がハードウエア、メモリに入れる命令や数値がソフトウエアで、後者が「ソフト」と呼ばれるのは書き換えの容易さゆえである。オプコードの並びはマシンコード(マシン語)と呼ばれ、人が読み書きしやすいように略記号とラベルで書き直したものがアセンブリ言語になる。両者は同じものを別の方法で見ているにすぎない。最後に、ここで自明のように使った考え方の多くが、リレーコンピュータが実際に作られた時代にはまだ理解されていなかったという事実にも触れる。
組み上がったものに名前を与えていく。デジタルコンピュータは4つの主要部品からなる ——プロセッサ、メモリ、入力デバイス、出力デバイス。このうちプロセッサは CPU(中央処理装置)とも呼ばれ、ここまで組み立ててきた累算器・命令ラッチ・ プログラムカウンタ・制御回路をひとまとめにしたものにあたる。「頭脳」という比喩はあえて使わない ことにする——この装置がやっているのは、あくまで数を足したり引いたり、番地を選んだりすることだけである。
ALU(算術論理演算器)とは、この章では8ビット加算器と8ビットインバータをまとめて 呼んだ名前にすぎない。念のため断っておくと、この章のALUができるのは算術だけである。 AND・OR・XORのような論理演算までできるのは、もっと洗練されたコンピュータのALUの話になる。そして 16ビットカウンタにはプログラムカウンタという、より通りのよい別名がある。
リレー・導線・スイッチ・電球といった物理的な部品はハードウエア、メモリに入れる命令や 数値はソフトウエアと呼ばれる。後者が「ソフト」と呼ばれるのは、 ハードウエアよりずっと簡単に変更できるからである。配線をやり直すのは大仕事だが、 メモリの中身を書き換えるのはスイッチを何度か操作すればよい。
オプコードの並びはマシンコード(マシン語・機械語)と呼ばれる。しかし 「番地3000hのバイトを累算器に足す」と毎回書くのは面倒だし、生の数値の羅列は人の目にはほとんど 読めない。そこで各命令に覚えやすい略記号を与え、命令文の形で書き表す ——これがアセンブリ言語である。書き方には決まりがあり、 「行き先, 出所」の順に並べる。角括弧で囲まれた数値はその番地に入っている値を、 角括弧のない数値は番地そのもの(ジャンプ命令の飛び先)を意味する。
数値アドレスの代わりにラベル(記号名)を書くこともできる。ラベルを使うと、後でメモリの 配置を変えたときに直す箇所が激減する。番地を直接書いていれば、その番地を参照するすべての命令文を 書き直さなければならないが、ラベルなら定義の1箇所を直すだけで済む。セミコロンから行末までを コメントとして書き添えれば、後で読み返すときの助けにもなる。
ここで強調しておきたいのは、マシンコードとアセンブリ言語は同じものを異なる方法で見ている だけだということである。アセンブリ言語の各命令文は、必ずマシンコードの特定のバイト列に1対1で 対応している。そして最後に、少し落ち着かない事実にも触れておく。この講で当たり前のように使った 考え方——2の補数を使った減算、メモリに保存されたプログラム、条件分岐——の多くは、リレーコンピュータが 実際に作られていた1930年代には、まだ誰にも明らかになっていなかった。それらが理解され 始めたのは1945年頃のことである。当時のコンピュータには、内部で10進数を使うものもあれば、プログラムが メモリではなく紙テープにコードされているものもあった。私たちが数十ページで駆け抜けた道のりを、 当時の技術者たちは手探りで歩いていた。
この章でALU(算術論理演算器)と呼んでいるものはどれか
「ソフトウエア」という言葉が「ハードウエア」と対比して名付けられた理由はどれか
アセンブリ言語の命令 "STO [2010h],A" が実行されると何が起きるか
マシンコードとアセンブリ言語の関係について正しい説明はどれか
命令文の中で実際の数値アドレスの代わりにラベル(記号名)を使う利点はどれか
著者は、この章で組み立てたリレーコンピュータの設計に使われている考え方(2の補数を使った演算など)について、歴史的にどう述べているか
初期のコンピュータについて、この章で触れられている事実はどれか
このセクション: 0 / 7 問正解