コンピュータの基本的な部分を学ぶことができるnand2tetrisというサイト。
タイトルにあるように、まずは論理回路のNANDを組み合わせてCPUやメモリ、そしてコンピュータまでをエミュレータ上で作る。次に作成したコンピュータ上で動くマシン語へ変換するアセンブラ、中間言語からアセンブラへの変換器、高級言語から中間言語へのコンパイラと順を追って作成。最後に高級言語を使ってOS(というかライブラリ)を作成したらtetris(実際にはPong)を動かす!というのがサイトの内容。
やることはかなり盛り沢山で、年末から1ヶ月程かかってやっと最後まで終わらせることができたので簡単に振り返ってみる。
ハードウェアのお話
nand2tetrisは全部で12章から成っている。1〜5章がハードウェアで6〜12章がソフトウェアの話。
ハードウェアの部分ではまず最初に論理演算について学ぶ。
and, or, xor, not の入り混ざった式をあれこれ変換して簡潔な式に変換していく。ド・モルガンの法則でビシっとnandを使った短い式になるとガッツポーズ!
また、2進数、符号付き数値、2の補数など数値の表し方についても学ぶ。コンピュータの中で数字がどの様に表されどの様に計算されているのかがわかる。
そして肝心の回路の作成。これはハードウェアシミュレータを使って進めていくことになる。具体的には作成する回路内の接続を構文に従って記述していく。
CHIP Xor {
IN a, b;
OUT out;
PARTS:
Not(in=a, out=nota);
Not(in=b, out=notb);
And(a=a, b=notb, out=aAndNotb);
And(a=nota, b=b, out=notaAndb);
Or(a=aAndNotb, b=notaAndb, out=out);
}
しかし、この構文が分かりにくいというかマニュアルに細かいことが書いてない気がする。出力を複数書けるとかバスの一部を使うとかサラッと読んだだけじゃ気づかなかった。(英語のサイトを見ながらなので見落としている可能性が大だけど…)
シミュレータで作成した回路の動作確認にはテストスクリプトが用意されている。後のソフトウェアの章でもそうだが、スクリプトで答え合わせができるのにはとても助かった。
後はNAND回路を組み合わせて加算器が、マルチプレクサが、デマルチプレクサが、そしてALUが出来上がり最後にCPUが完成する。
たぶん一番大変だったのはALUの作成。ブロック図が掲載されていてその通りに作れば良いのだが、論理式がきれいにならないので色々と悩んだ末に結局そのままの論理式にしてしまった。
それにしても、あんな組み合わせであれだけの種類の計算ができるようになるのは凄い。これにビットシフトができるようになったら掛け算とか速くなりそうだけど、シフトレジスタ回路を作るの大変だったりする?
こうしてCPUとメモリを作成して、入出力はメモリにマップすればコンピュータの出来上がり。立派な16ビットマシンの完成だ!
ソフトウェアのお話
コンピュータが完成すると次はソフトウェの話。
なのだが、ここでちょっと問題が…。6章まではウェブサイトにドキュメントが掲載されていた。けど、7章以降はドキュメントへのリンクがグレーになっている。まぁスライドがあるのでなんとかなるのだが、教科書がない状況はちょっと心配。(逆に英文を読む時間が必要なくなるので進みは速くなる!)
前章まででCPUの内部動作を理解しているので、マシン語を吐き出すアセンブラの作成は特に難しいことはない。ビットとCPUの動作を対応づければ良いだけ。行毎にトークン(という程でもないが)に分けて対応するビットをオンにして出力。問題ない。
ちなみにソフトウェア部分は、課題のソフトウェア作成を好みの言語を使って行うことになる。自分は前にpythonでLISPを書いた時のコードが使えると思ったのでpythonを使った。
アセンブラで出力したマシン語はCPUEmulatorで実行して動作確認を行う。論理回路の時同様、動作確認用のスクリプトが用意されているのでそれに従えば簡単に進められる。
次に仮想マシンで動作するコードからアセンブラに変換するVMTranslatorを作成する。ここで一気にハードウェアからソフトウェアに話が移る。local, argument, field, staticなどメモリのセグメントごとにcall/return時の動作を変える必要がありややこしいことになるが、これがキチンとできないと後の高級言語で困ってしまう。
個人的にはこの仕組みをしっかり理解するのに時間がかかった。
いよいよ高級言語(!)jackから仮想マシンのコードへ変換するコンパイラの作成。トークンに分けたり、パーサやシンボルテーブルを作ったりと手順を追って課題が示されているのでどう作れば良いのか迷子になることはない。
メモリのセグメントの話が分かっていればそれぞれに応じた仮想マシンのコードも書き分けられる。
最後にOS(というか基本ライブラリ)を作成する。メモリの確保や入出力、それに配列と文字列もライブラリで用意する。
ところで、メモリのpeek, pokeはどうメモリにアクセスするのかと思ったら、「jackは弱い型付けの言語なのでこんなことしてもコンパイラに叱られない」ということで下記のようだったのは笑った。
static Array ram;
function void init() {
let ram = 0;
}
function int peek(int address) {
return memory[address];
}
ポインタ越しにどこでもアクセスできるぜ!という感じで好き。
というわけで、ここまででコンピュータとOSが完成したので、最後にjackで書かれたピンポンゲームPongを実行してコースは終了になる。tetrisでなくて微妙に残念ではあるけど、ちゃんと動くPongを見たときは嬉しかった〜。
最後に
このコース、サイトは英語。で、英語の本も売っている。
しかし、実は英語だけではなく翻訳された日本語バージョンの本もある。
知ってはいたのだけどせっかくだから英語の勉強も兼ねて英語サイトで進めてみた。結果、教科書部分を読むのに時間がかかって(寝てしまって)かなり時間がかかってしまったが、楽しく最後まで進めることができた。
コースの進め方としては、実際に手を動かしながら動作するものを少しずつ作れるのが楽しい。ただ、サイトには正解が載っていないので動作確認で動いたとしてもそれが最適解なのかどうかがわからない。力業で動いた回路がもっと部品数を減らせたのではないか?とか、vmからアセンブラへの変換のコード、もっとステップ数を減らせるのではないか?とか、色々と気になることは残っている。
というわけで、自分はもう少しnand2tetrisで遊んでいこうと思っています。