Kinesis Advantage360 Proを親指シフトキーボードにしました!
まとめ
PC側はローマ字入力の設定まま、親指シフト用のソフトウエアを使用せずに、Kinesis Advantage360 Proから直接親指シフトが入力できるようになりました。(zmkに感謝!)
キーボードそのものは、"ぱ"や、"べ"が打ちやすいです。
注意
Kinesis Advantage360は2種類あって、Proではない無印の方(左右のキーボードを有線で繋ぐタイプ)は、ファームウエアがzmkではないので、下記の方法では親指シフトは出来ないと思います。
Kinesis Advantage360 Proはファームウエアを直接更新する作業になります。仮にキーボードが使用できなくなっても責任は負いかねますのでよろしくお願いします。(一応 ••;)
設定方法
以下のREADME.mdになります。
仕組み
右手のjキーを押した時に、キーボードから直接、"tを入力してからo"のキーの信号が出すようにしています。(PC側がローマ字入力で待っているなら、"と"が入力される。)
また、右手の親指の一番内側のキー(デフォルトだとSpaceキー)と、右手のjキーを同時に押した時に、キーボードから直接、"o"のキーの信号が出すようにしています。(PC側がローマ字入力で待っているなら、"お"が入力される。)
何が嬉しいか?
これまで親指シフトを使うには
など、それぞれのOSに合わせて親指シフトの設定が必要でした。
今回、キーボード側を親指シフトに対応させることで、親指シフト用のソフトウエアの設定が不要になりました。 そのためキーボードをPCに繋ぐだけで親指シフト入力ができます。
確認したOSのバージョン
またiPhoneでもキーボードを接続するだけで、親指シフト入力が可能です。
確認したOSのバージョン
- iOS 16.4
やったこと
レイヤの追加
jキーを押した時に、"と"を入力するには、キーボードのモードを変える必要があります。(そうでないと、"j"と出すか、"と"と出すか、キーボードからは分からないため)そのため、レイヤーを一つ増やしました。
レイヤはLAYER #4としています。
macros.dtiの準備
jキーを押した時に、"と"を入力するには、キーボードから"t"の後に"o"が出力される必要があります。 そこでマクロを次のようにmacros.dtsiで準備して、キーの設定もそれを読み込むようにしました。
macro_TO: macro_TO{ compatible = "zmk,behavior-macro"; label = "macro_TO"; #binding-cells = <0>; bindings = <&kp T>, <&kp O>; };
これを全てのキーについて設定します。
combo.keymapの準備
同時打鍵の時ですが、これはcomboという機能を使います。コンボは次のように書いて設定しています。
combo_NO { timeout-ms = <50>; key-positions = <42 70>; layers = <4>; bindings = <¯o_NO>; };
ここでkey-positionsの42や70は、adv360.keymapで定義した順番になり、今回の場合は以下です。
親指の一番押しやすいキーを、親指シフトに使ってしまうと惜しい気がしますが、これはコンボの時だけで、同時打鍵ではない普通に押したときは、BackSpaceやSpaceとして動作します。
ここで、42と70のキーを押したら、"n"の後に"o"を出力するために、当初次のようにしたのですが、エラーが出てしまいました。
(エラーになる例) combo_NO { timeout-ms = <50>; key-positions = <42 70>; layers = <4>; bindings = <&kp N>, <&kp O>; };
どうやらコンボでは、1つのキーの入力が前提のようです。そのためマクロを作っておいて、それを呼び出す方法にしました。
(実際の例) macro_NO: macro_NO{ compatible = "zmk,behavior-macro"; label = "macro_NO"; #binding-cells = <0>; bindings = <&kp N>, <&kp O>; };
コンボだと判定するキー入力の時間差timeout-ms = <50>は、少し厳しい判定かもしれないです。必要に応じて調整すると良いかと思います。
入力モード切り替えボタンの設定
日本語入力の切り替えですが、残念ながらzmkでは変換や無変換キーが使えないようです。(下のリンクのInternatianalの部分)
(PC側のソフトウエアを介して、変換と無変換を設定できる模様ですが私は未検証です)
そのため、Windowsの全角半角キーを押したようなトグル操作としました。文字入力切り替えは、Linux、Mac、Windowsで共通にしたかったのでCtrl + spaceとしました。
同時にレイヤも切り替えれると便利です。そのため、マクロとして次を設定し、それを呼び出すようにしました。
macro_LCSP4: macro_LCSP4{ compatible = "zmk,behavior-macro"; label = "macro_LCSP4"; #binding-cells = <0>; bindings = <&kp LC(SPACE)>, <&tog 4>; };
このマクロを呼び出すキーを適当な場所に設定します。
adv360.keymapでのcombo.keymapの呼び出し
先ほど準備したcombo.keymapをadv360.keymap内で呼び出しました。
#include "combos.keymap"
keymapを変更するたびに、このinclude文を追加する必要があります。
macros.dtsiはデフォルトでinclude文が書かれるので大丈夫です。
注意点
常にキーボードのモードとPC側のモードを一致させる必要があります。 たまに何かのはずみで、PCは直接入力、キーボードは親指シフトのモードになることがあり、慣れないうちは混乱しますが、もう慣れました。
"、"の位置
基本的にデフォルトのままが一番だとおもいますが、右手小指の上段"、"の位置は","と入れ替えるのも手かと思います。
、のマクロ名はJPCOMMA1
,のマクロ名はJPCOMMA2
としています。変更方法は、キーマップのページで以下の様に修正し、更新されたadv360.keymapに、忘れずに#include "combos.keymap"の一文を追加すればOKです。
ゼロから作るDeep Learning3 ステップ40 broadcast_toとsum_to関数の関係についてのメモ
ステップ40でbroadcast_toとsum_toの関数が、それぞれをお互いに使用しますが、少し混乱したので図にまとめました
sum_to関数は、utils.pyと、functions.pyにそれぞれあることに注意が必要でした。
図の通りですが、文にすると以下になります。
broadcast_to関数を呼び出す時(順伝播)、BroadcastTo(shape)(x)が呼び出され、np.broadcast_toが呼び出される
functions.pyのsum_to関数を呼び出す時(順伝播)、SumTo(shape)(x)が呼び出され、utlis.pyの方のsum_to関数が呼び出される。
BroadcastToの逆伝播の場合、functions.pyのsum_to関数が呼び出され、あとは2.と同じ。
functions.pyのsum_to関数の逆伝播の場合、broadcast_to関数が呼び出され、あとは1.と同じ。
Nandをひたすら組み合わせて、CPU(TD4)をFPGAで実装しました!🎉
まず初めにややこしいので…
以下の記事には4冊の書籍が出てきます。
タイトルが似ていることもあり、まずまとめておこうと思います。
今回作ったCPUであるTD4はこの本で創るCPUです。
- 作ろう!CPU
上記TD4をFPGAで作る方法を解説されている本です。
- 動かしてわかる CPUの作り方10講
FPGAでCPUの作り方を解説されている本です。(TD4を作るわけでは無いです)
- コンピュータシステムの理論と実装
通称Nand2tetrisと呼ばれる本です。Nandゲートをひたすら組み合わせて(シミュレーション上で)コンピュータを作ります。この本については昨年完走して、↓の記事を書きました。
プログラミング初学者の私がNand2tetrisを終えるためにやったこと - みちばたのハナニラ
できたこと
先日、Nandゲートのみを使って、TD4をFPGAで実装しました。
やっとFPGAでNandをひたすら組み合わせてTD4ができました!!🎉
— ハナニラワタル (@hananirawataru) July 3, 2021
分周を間違えて8分タイマーになってた時はちょっとびっくりしました。
動画はかなりオーバークロック(笑)させたLEDちかちかです🙌 pic.twitter.com/ArXqDTDMml
TD4とは、”とりあえず動作するだけの4bitCPU” の略で、"CPUの創りかた" という本で解説されているCPUです。
私の最終的な目標としてはNand2tetrisをFPGAで実装したいと思っているので、その前段階としてTD4をNandから作ってみました。
前段階とはいえ、実際作ってみるといろいろな気づきや難しい箇所があり、せっかくなので記録に残したいと思います。
"Nandゲートのみを使った" ことに関して
電子素子の一つであるNandのみを与えられたパーツとして、それを組み合わせてORやNOT、DFFなどのモジュールを作り、それらをさらに組み合わせてTD4を作りました。*1
Verilog HDLというハードウエア記述言語で書いたのですが、Nandモジュールを作る時にだけ、唯一次のように演算子( ~ や & )を使い、
assign out = ~(a & b);
あとはひたすらこのNandモジュール(関数)を呼び出してTD4を作りました。
実際はそんなことをせずとも、(あたりまえですが)もっと効率良く書けて、例えば "作ろう!CPU" では、たった69行でTD4を実装しています。
自分の場合は1600行ほど書くととになりました(テストコードを含めず)。
一言で言うとNand縛りでTD4を作ったという感じです。この非効率さは趣味ならではですね。なにより楽しかったです!
全体像
作ったTD4の大まかな概要としては↓になります。*2
"CPUの創りかた" にも同じように回路図があるのですが、こうして図で起こしてみて、初めて全体像がクリアになりました。
"CPUの創りかた" ではICチップで記載されていることが多いですが、ここでは自分が作ったモジュール名(nand2tetris風)で記載しています。
各モジュールに関して
Register
NandでDFFを作り、それを4個束にして4bitのRegisterとしました。TD4では、resetとloadは、Lowの時に1を割り当てられている負理論なので、そこに注意しました。(Nand2tetrisでは、Highの時に1が割り当てられている正理論だったので)
DFFに関しては、Nand2tetrisでは与えられていたパーツのため内部構造は不勉強でしたが、"作ろう!CPU" にDFFの詳しい解説があり、実際にNandと(Nandから作った)Notで作ることができました!
特に "作ろう!CPU" の "7.4 DFFの状態偏移図" が大変わかりやすく、DFFのテストの際にとても参考になりました。
Nand2tetrisの頃から、DFFって理解がなんだか難しいな、と感じていたのですが、自作のDFFにクロックを入力して、正しい挙動になることをテストしていったことで、DFFをより理解できたように思います。
ROM
書籍を参考に、4bit16入力として与えた各信号(プログラム)を、address(4bit)で、指定の命令が吐き出されるようにしました。
ALU
TD4の場合ALUは、4bitのAdderなので、Nand2tetrisのAdderを参考に作りました。
FPGAで、NandからTD4が作れそうなところまで来ました!
— ハナニラワタル (@hananirawataru) June 22, 2021
多分来週くらいにはNand2TD4をお見せできそうです!
(という予告をして自分にハッパをかけます)
Decoder
書籍の通り作ればOKでした。(改めて "CPUの創りかた" のDecodeのあたりを読むと、みるみるICが減っていって気持ちいいですね)
PC(プログラムカウント)
4bitのRegisterとAdderを使い、1clockごとに1ずつ足しされた出力になるようつなぎました。基本的にNand2tetrisで作ったものと同じでしたが、ここでもloadとresetが負理論なことに注意しました。
分周について
私のFPGAは50MHzで動作するため、"CPUの創りかた" に合わせて1Hzに落としました。
以下のようにDFFを数珠繋ぎにして周波数を落としていっただけですが、最後のDFFに信号が届かないと、その間出力がずっと不定になるのが、どうにも落ち着かず、どうやってすべてをDFFを一度にresetするか、ここが一番頭を捻りました。
TD4そのものはどうやらうまく動くところまで来ました!
— ハナニラワタル (@hananirawataru) June 26, 2021
あとは分周して、LEDと繋げたら完成のハズ…!
しばし頭を捻って、図の下の部分のように、最初のクロック入力をセレクタで選んで、resetが0(負理論なので)の時に全部のDFFに入れて解決でした。
(clock信号を直接DFFに入れないことに何故か抵抗がありました)
最終形
最終的にはシンプルですが次のような形になりました。
outputはFPGAに搭載されているLEDにつなぎ、inputはやはりFPGAに搭載されているスライドスイッチに繋いでいます。
ふりかえり
あえてNandゲートから全てを組み合わせてパーツを作っていくことで、一つ一つのパーツについて改めてよく知ることができたと感じます。
1パーツ作るごとに、テスト用のコード(仮の入力をあたえる)を書いて、Modelsimというシミュレータでテストしていき、大きなバグに悩まされることなく進めることができました。
しかし各線の0/1の切り替わりを追う度に、CPUってすごいな、自動でデコードして計算してるもんなぁ、としみじみ思いました。
— ハナニラワタル (@hananirawataru) June 26, 2021
冒頭にも書きましたが、やはり読んでみるだけではなくて作ってみるといろいろと理解が進みますね
今後は・・・
今後はNand2tetrisをFPGAで実装するのを見越して、このTD4をもう少し改善させようと思っています
- 命令コード(data 8bit)を7セグメントディスプレイに表示
ROMから吐き出される8bitのコード(プログラム)を、せっかくFPGAに7セグメントディスプレイがついているので、そこに2桁の16進数で表示したいと思います。
- ROMを改善
いまはプログラムの0と1を、直接ワイヤーにlowとhighを与える形で定義していますが、さすがに今後のことを考えるとイマイチなので、FPGAのメモリ領域(メモリブロック)を使って実装したいと思います。
- SDカードから読み出し
私のFPGAはSDカードが使えるため、最終的にはSDカードにプログラムを書いて、それをROMにまず読み込ませてから、プログラムが走るようにしたいと思います。
まだまだNand2tetrisをFPGAで実装するには遠い道のりですが、少しずつでも進めていきたいと思います!
ゼロから作るDeepLearning3の第1ステージ まで終わりました!
Nand2Tetrisの次は、ゼロから作るDeepLearning3を進めています。
Nand2Tetrisの時は、一冊終わったところで振り返り記事を書いたら、えらく苦労したので今回は、区切り区切りでメモを残そうと思います。
第1ステージメモ
- ステップ6~8の逆伝播の実装がやや難しかったです。
- 実装してから、テストするケースはステップ6~8で、入力が常に同じ(順伝播では
x = 0.5
, 逆伝播ではy.grad = 1.0
)なので、都度一つ前と同じ答えになるか確認しながら進めました。 - 入力/出力のx/yだけでなく、途中の変数a/bについても、以下の様に手計算して、ステップごとに正しく計算されている事を確かめました。(図5-5と色を合わせています)
- 以下はステップごとの感想など
ステップ1
- 一見なぜただの変数と言う型(Variable)を準備するのか分からないが、おいおい作っていくとVariableに書く内容が増えていくるので理由が分かった。
ステップ2
- Functionクラスの、
__call__
メソッドの呼び出しは、少しややこしく感じたがこういうものとして慣れました(ステップ9で改善される)。
ステップ3
- 関数の連結が出来て、万能感が出てくる。
ステップ4
- まずは数値微分をやってみる。
ステップ5
- すごく大事な項でした。
- この後のステップ8までで、流れを見失うたびにここを再読しました。
ステップ6
- 手作業でバックプロパゲーションをとにかくやってみる。
- この後のステップ7~8で洗練させていく。
ステップ7
- 変数と関数とでリンクを作り、自動的に逆向きに辿れる様にする。
ステップ8
- ステップ7を一部修正する。
ステップ9
- ここまで毎回タイプしなければならなかった事を、自動で補う様にする。
- これにより実行する時に書くことが減ってきて、洗練された感が出てくる。
ステップ10
- 自動で実行できるテストを実装する。
- 先にここを読んで都度テストケースを追加していってもよかったかも・・・。
第2ステージが終わったらまた書きたいと思います!
プログラミング初学者の私がNand2tetrisを終えるためにやったこと
昨年、9ヶ月かけてNand2tetrisをやりきりました!
ついに今日、nand2tetrisを完走することができました!🎊🙌
— ハナニラワタル (@hananirawataru) December 19, 2020
終えるまで9ヶ月間かかりましたが、すごく楽しかったです!#コンピュータシステムの理論と実装 #nand2tetris pic.twitter.com/o3jWtORaaU
AtCoderにハマり本格的にプログラミングの勉強を始めて、1年と少しが過ぎたところでした。
この記事ではNand2tetrisの紹介と、プログラミング初学者の私がNand2tetrisを進めるために参考にした本や、進め方などを紹介したいと思います。
目次
- Nand2tetrisとは
- この記事を書いている人
- 取り組んだきっかけ
- できたもの
- この本の素晴らしいところ
- 私のすすめ方
- 分かったことと感じたこと
- 章ごとの内容と感想など
- 今後やりたいこと
- 参考にした書籍
- 感謝の言葉
- 最後に
Nand2tetrisとは
正確には「コンピュータシステムの理論と実装 モダンなコンピュータの作り方」という本になります。
この本では、Nandゲート(AndゲートやOrゲートのようなもの)と言うたった1種類のパーツをひたすら、組み合わせてパソコンを作ります。
そして好きなプログラム(例えばテトリス)を作り、それを自作のコンパイラとアセンブラで機械語に変換し、先ほどのNandゲートから作った自作パソコンの上で動かす、というのが最終目的の本です。
そのため通称Nand2tetrisと呼ばれています。
公式サイトはこちらから。ちなみに英語なら無料ですべて読めます。
目次の前にある「訳者まえがき」と著者の「まえがき」「イントロダクション」が、この本がどんな本か大変分かりやすくまとまっていると同時に、素晴らしくワクワクさせてくれる内容になっています。もしこの本に興味を持った方は、この部分を読んでもらうと魅力がより伝わると思います!
自分の自由な時間のかなりをこの本に費やし、昨年は”趣味はNand2tetris”と言っても過言ではありませんでした。まさかサンリオピューロランドで娘のために順番の列に並んでいる時や、温泉に浸かっている時に、プログラミングを考えることになるとは思いませんでした。春先に始めたのに、シュトーレンを焼く間に最後の実装をしていました。
とにかく難しかった、苦しかった、それ以上に気になって進めずにはいられないワクワクがずっと続いていた幸せな時間でした。
この記事を書いている人
- 大学は理系ですが、専門は梁の曲げとかリンクとかの方で、情報系卒ではないです。仕事でプログラミングをしたこともないです。
- パズルが好きでMETIQという団体に所属しています。Nand2tetrisの至る所にパズル的な要素を感じたので、パズル好きは楽しめると思います。
- プログラミングは趣味でちょっと違うことを勉強したくなり始めました。2019年の11月頃にAtCoderを始めたので、ちょうど1年と少し経ったところです。
- AtCoderでは、A,B問題は解けるレベルでこのNand2tetrisを取り組み始めました。
- 関数は作れるけど、クラスやメソッドは作れませんでした。
- コンパイラとアセンブラの違いも分かっていませんでした。
取り組んだきっかけ
- コンピュータがどうなっているか知りたかった。特に「コンピュータは0と1で処理している」とよく言われるけど、そのレベルから理解したかった。(興味)
- 長いまとまったプログラムを書いてみたかった。C++で複数ファイルにまたがるプログラムを書いてみたかった。(憧れ)
- Makeとかして見たかった(憧れ)
と、興味と憧れが全てでした。結果としては、コンピュータの理解を深めることができ、コードもたくさん書け(全部で7600行ほど)、Makeも飽きるほどできました。
数えてみたら、全部で7600行ほどプログラムを書いてました。
— ハナニラワタル (@hananirawataru) December 24, 2020
コメント行を含めてるとはいえ、なかなか書いたもんだ…!#コンピュータシステムの理論と実装 #nand2tetris
できたもの
なかなかこの本は壮大で全体像が把握しにくいので、見通せる図を作成しました。
青背景の物がこの本を通して作るものです。私が使用した言語と、対応する章番号も記載しています。
図の上部から下に向かって、オブジェクトベースのJack言語で作成したテトリスプログラムとOSプログラムに、自作のコンパイラ、VM変換器(VMtranslator)、アセンブラで変換を重ね、最後は0と1にしてNandで作ったコンピュータで実行します。(※この説明は正確には少し違っていて、詳細は最後の"今後やりたいこと"に記載しています)
小さいですが黒背景の中にプログラムの抜粋をいれています。
最終的にテトリスを実行した様子はこのような感じです。(キャプチャしながら動作させているので、少しもっさりしてて悔しい。もう少しスムーズに動くように頑張って改善したのに…)
この本の素晴らしいところ
・各章ごとに、解説と作るものの仕様と、テストケースが準備されています。
解説(仕組み)を理解して、仕様に沿ってプログラムを書き、テストケースを通すことで、徐々に私だけのNand2tetrisが出来上がって行きます。(もちろんオリジナル仕様でも作れます)
例えるなら「週間自分だけのパソコンを作ろう」 みたいな感じで、コツコツ出来上がっていきます。
・仕様に合わせて作っていけば、不足や手戻りなく出来上がります。
後から「CPUにあの回路を追加しないと〇〇が実現できない」みたいなことはありませんでした。
・お手本としてちゃんと動くものが準備されています。
Nand2tetris Software Suiteとして、正しく動くCPUやアセンブラ、コンパイラなどが準備されています。(上記の公式サイトで誰でも無料でダウンロードできます)
それと、自分のプログラムで処理して出来たものとの差分を取れば、どこが誤っているかを理解できます。これらが無かったら、例えばうまく動かない時、それがOSのバグなのか、コンパイラのバグなのか、アセンブラなのか、判別するのに苦労しそうです。
私のすすめ方
- まずこれから進める章をざっと読んで課題を理解する。
- 次にとりあえず作りながら、わからないところが出てきたら、都度必要なページをもう一度きちんと読む。
- 全然わからなかったら、参考になる他の本を読む
という流れで進めました。
本にはヒントや、指針がきちんと書かれてして、わからない場合は改めてよく読むと「なるほど、ここに書いてあったのはそういうことか!」となることが多かったです。手を動かし始めてやっと本の意味が理解できることがあるので、最初に読んだときに理解できなくても、とりあえず取り組んでみてみたのが良かったと思います。
それ以前に、あまりに本の内容が理解できない時は、他の本で理解を深めました(具体的には後述します)。
また、自分はNand2tetrisに関しては、目標も計画も立てず、ただ興味の赴くままに取り組むことにしていました。もともとかなりストレッチしたチャレンジだと思っていたので、極論「いつ止めてもOK」くらいの気持ちで、楽しむこと第一で臨んでいました。
あえて先を急ぐこともせずに、動くようになっても気が済むまでコードに手を入れてました。
分かったことと感じたこと
- CPUで実装した、びっくりする位少ない種類の演算命令(なんと18しかない)と、スタック操作を使った緻密なカラクリによって、オブジェクトような便利で、かつ物の本質を抽象的にプログラムに落とし込めるような仕組みが出来上がっている。
- CPUでできる処理の種類が少ないので、とにかくasmファイルまで変換すると、長くて泥臭い感じになる。しかし実行スピードが凄いので魔法のように動いているように感じる。(すごいと言っても私の実行環境では推定120"k"Hz程度)
- 人とコンピュータの対話の歴史 ソフトウエアの20世紀という本によると、パソコンやアセンブラ、コンパイラが発明されたのは1940〜1950年代とのこと。まだ人類が手にして100年も経っていない技術を、こうして趣味で作れてしまうことに感銘を受けました。
章ごとの内容と感想など
1章 ブール論理
プログラミング上でNandをつないで、NotやAnd、Orを作ります。
シミュレーション上でそれらを動作させることで、論理の確認をします。
ここで作ったものが後の章でPCのパーツとして使用します
2章 ブール算術
前の章で作ったパーツで、足し算をする回路(加算器 Adder)を作ります。ブール理論で計算の実現の仕方を学びます。
そして早速ALUをここで作ります。ALUは18種類の演算をするCPUの中心部的な存在で、本書で与えられた仕様を満たす回路を作ります。
18種類というと多い気がしますが、2つの入力x,yに対して、x+yで1種類、x-yで1種類という感じで、かなり簡素な処理です。
Nand2tetrisで作るパソコンの演算はここで作るの18種類が全てで、これで掛け算、割り算はもちろん、嘘みたいですが最終的には高水準言語で書かれたオブジェクトベースのプログラムも実行します。
3章 順序回路
情報を保存するパーツ(メモリなど)がどのようにできているかを学びます。
DFFを使ってPCのメモリを作ります。
ここまでの章で、PCに必要なパーツはほぼ揃います。
(この章で作るカウンタが地味に難しく、人の回答を参考にしました。)
4章 機械語
ついに全てのパーツを組み合わせてパソコンを作る…と思ったらここで機械語とアセンブリ語について学びます。
なぜここで?と思ってしまいますが、次の5章で作るコンピュータをまずは使ってみて、コンピュータに慣れるためです。
この"作る前に使ってみる"流れは本の後半でも同じで、9章でJackという(この本ために設計された)言語でプログラミングしてみて、10、11章でJackのコンパイラを完成させることになります。
使う側として接してから、読み解く側として取り組むというのは、この本の一つの特徴かと思います。この時の視点がガラッと切り替わるのが個人的には快感でした。
5章 コンピュータアーキテクチャ
1章~3章のパーツを使って、ついに最上位のコンピュータを組み立てます。
CPUはこの本の中でも一つの難所かと思います。4章の機械語の仕様と、3章のALUの仕様をじっくり眺めながら、簡単な命令から組み立てていきました。(A命令→ジャンプ以外のC命令→ジャンプを含むC命令)
コンピュータ内を、電圧の高低(0/1)が色んなパーツを通過しながら、カチャカチャと切り替わりながら動いていく。それを人間から見ると意味のある動きになっている。そんな様子を俯瞰的に眺めることができました。
1章からここまでは、自作CPU系で名著と呼ばれる「CPUを創りかた」も同時に読みながら進めました。DFFに代表される順序回路は、理解したと思ってもすぐに分からなくなることがあり、その度にこの本の"Chapter7 1bitCPU(らしきもの)"の章を読み直していました。
またCPUの簡単な命令から一つずつ回路を組み立てていくプロセスも、この「CPUを創のつくりかた」がとても参考になりました。
6章 アセンブラ
個人的には一番難しく感じた章でした。
それまで私は前述の"この記事を書いている人"にあるように、シンプルなプログラムしか書いたことがなかったため、クラスやメソッド を使うという実装の仕様を読んでも、全く意味がわかりませんでした。
3章のカウンタ以外に、この章だけは人のプログラムをいろいろと見て参考にしたのですが、それを見たところで基礎的な点で理解が追いついていなかったので、それでも理解できませんでした。(プログラムの冒頭のint main(int argc, char* argv[]) のargc, argvって何?と言う感じでした)
そこで「ロベールのC++」を購入し、半分くらいまで読んでクラスやメソッド、ヘッダファイルとソースファイルに分けて書く書き方、makeファイルの書き方などを勉強しました。
初めてコンストラクタ内でファイルを開いて、適当な文字を書き込み、デストラクタで閉じられた時は、「これでやっとスタートラインに立てた。あとは正しく変換していくだけだと」感激したと同時にほっとしました。
アセンブリ言語は機械語と1対1の対応で、まさに機械的に変換をしていくだけなので、後に作るコンパイラに比べると単純ではあります。
ただ上記のように私のスキルが追いついていない事もあり、この本を通してこのアセンブラが完成した時がもっとも達成感がありました。(2ヶ月くらいかかりました)
少し前のことですが、コンピュータシステムの理論と実装のアセンブラが完成しました!
— ハナニラワタル (@hananirawataru) July 24, 2020
(しかもPythonではなく、学生の頃少し触って、苦手意識があったC++で)
コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方 https://t.co/T2h6MHyAy6
自分が書いたプログラムが、アセンブリ言語を01の(一見)意味不明な記号にし、それを自分が作ったCPUが読み込んで動くという、稀な体験
— ハナニラワタル (@hananirawataru) July 24, 2020
自分が作ったアセンブラのはずなのに、そのツールを使って出来上がるもの/できることは、自分の理解を超えている、という全く新しい体験でした。
— ハナニラワタル (@hananirawataru) July 24, 2020
プログラミングを生業としている人はいつもこの感じを体験してるんだろうか🤔
だとしたら、うらやましい。
7章 バーチャルマシン#1:スタック操作
ここからは中間言語をアセンブリ言語に変換するVM translatorを書きました。
中間言語のイメージは、push1, push2, addのような感じで、それをアセンブリ言語に変換できるようにしていきます。(簡単な.vmファイルを.asmファイルに変換する)
ここまでとは異なる全く新しいことが始まる感じがしますが、ファイルを開く/閉じるのような部分は、アセンブラで作ったものがそのまま生かせたので、いきなり本質的な問題に着手できて嬉しかったです。(6章で苦労したので)
また、単純なプログラミングのバグに悩まされることも減り、純粋にスタック操作のパズルを解くような感覚で楽しく進めれました。
作成したVM translatorで出力されるアセンブリ言語のボリュームが、直接コンピュータで実行する量になるため、アセンブリ言語にするときに少しでも少ない行数にできないかいろいろ考えたりしました。
8章 バーチャルマシン#2:プログラム制御
ここでは、7章で作ったVM translatorに、中間言語で書かれたifやcall, returnを、アセンブリ言語に変換できるようにしました。
関数のcallとreturnをアセンブリ言語に変換する所は、この本の中でも1、2を争う難所でしたが、純粋にややこしいパズル的な感じでなかなか楽しく、テストをパスした時はかなり快感でした。
nand2tetrisの8章をついにやり終えました!
— ハナニラワタル (@hananirawataru) August 22, 2020
functionのcallとreturnの巧みな仕組みに感嘆…!#コンピュータシステムの理論と実装 #nand2tetris
9章 高水準言語
この本のために作られた高水準言語のJackについて学びます。(あとの章になると分かりますが、コンパイルしやすくなっている分、書くことは多めです)
例の"作る前に使ってみる"流れで、Jackのコンパイラを作る前にその言語に慣るのが目的で、何を作るかも自由に設定されています。
仮に作らなくてもJackで書かれたサンプルプログラムがあるので、この章はパスしても大丈夫です。
自分は、せっかくのnand2tetrisなのでテトリスのプログラムをJackで書きました。
nand2tetris、ついにテトリスを作る段階まで来ました。
— ハナニラワタル (@hananirawataru) August 29, 2020
ここまで進めたご褒美にも感じます。
10年前に情報化の友人が教えてくれたこの動画を見て参考にしたいと思います!https://t.co/VZ6w6V6dgk#コンピュータシステムの理論と実装 #nand2tetris
10章 コンパイラ#1 構文解析
Jackで書かれたプログラムを読み解き、中間言語にするための準備として、構文解析器をつくります。
具体的にはその文の意味、その変数の意味を、Jackの文法の仕様から読み解いていきます。例えば「行頭にifとあったら、つぎは"("があって、")"が出てくるまでは、式があるはず」というような思考で、プログラムを読み解いていきます。
全く新しいことをする感じですが、ここでも基本的なファイルの読み書きは6章のものを再利用することができます。
また、ここでは再帰関数を使うシーンがありました。事前に勉強済みだったので、なんとか実装できましたが、全く初めての場合は簡単な例に触れておくとスムーズかと思います。
再帰関数に関しては、Atcoderの下記のサイトが参考になりました。
https://atcoder.jp/contests/apg4b/tasks/APG4b_v
テストでは、JackのプログラムをXML形式で「ここはif文」「ここは関数呼び出し」みたいにタグ付けをしていきます。
正しくタグ付けされたお手本があるため、それと自分の出力結果をdiffコマンドで調べて、コツコツと直していきました。
11章 コンパイラ#2 : コード生成
Jackを中間言語変換して書き出していく作業になります。
10章で正しく文を読めるようになっているので、あとは具体的に中間言語で書き出す処理を追加していきます。ここはif文だから、if文用の書き出し内容の中間言語を書き出す、と言うイメージです。
変数をまとめたシンボルテーブルを都度呼び出して、変数を具体的なメモリの場所に置き換えていくのは、なるほどこれが変数のスコープか、と納得しました。
テストが全て通った時は、あまりの嬉しさに、ベランダから「コンパイラができたー!」と叫びたくなりました。(自制しました)
12章 オペレーティングシステム
簡単なOSをJack言語で作っていきます。(全部で8ファイル)
本も終盤に近づいてきて、この章は軽く流してゴールできるウイニングロードだと思ってましたが大間違いでした。かなり大変で、終わるまでに3ヶ月もかかりました。
特に画面表示系のOutputやScreenで苦労しました。すでに書き込み済みのアドレスに上書きする際には、今あるデータとORを取る、といった操作は「30日でできる! OS自作入門」をざっと読んだ時に紹介されていて、参考になりましました。
メモリ管理の部分では、凝って書こうとすると単方向リストのデータ構造の考え方が必要でしたが、この部分で初めて遭遇しても(ここまで来ていたら)なんとかなると思います。
全てが正しく動くのを確認した後に、いざ9章で作ったテトリスのプログラムを、自分のOSで実行すると、速度が遅かったり(OSのアルゴリズムの問題)、テトリスの挙動が満足できなかったりで、納得いくまでコツコツと1ヶ月ほど手を加えていました。
もっさりしたパソコンが100倍位早くなりました!
— ハナニラワタル (@hananirawataru) November 23, 2020
そもそもMath.multplyで100倍位無駄な計算させてました苦笑#コンピュータシステムの理論と実装 #nand2tetris
大体できた!
— ハナニラワタル (@hananirawataru) December 13, 2020
大体できたけど、あとはテトリスで複数の段を消した時に、局面がワープした様に見えるのを直したい…!
今後やりたいこと
ここまでCPUからテトリスまでを全部作ってきたわけですが、実はまだ出来てないことがあります。
それは自分の作ったCPUで、自分が作ったテトリスを実行することです。
まるでそれはやっていたかのように書いてましたが、実際は中間言語から、提供されているVMエミュレータソフトで直接実行していました。
もう少し手間をかけて機械語まで変換したらいいのでは、と思いましたが、出来上がる機械語のコードが16万行のため、この本で作る16bitのCPUでは実行できないものになっていました。
それは、例えば16万行目にジャンプと命令したくても、このCPUではアドレスを(先頭の1bitはアドレス命令か計算命令かの判断に使うので)15bit分しか表現できないので約3万行目(=2の15乗)までしか正しく飛べないためです。
ではプログラムを短くしたらいいのでは?と思って頑張ってみたのですが私のスキルでは機械語で6万行が精一杯で、ここからさらに半分にする必要があり、(今の自分には)無理そうに感じました。(そのあたりVMエミュレータはうまく設計されているようです。)
そこでやっと最初のやりたいことに戻ってきましたが、最終的にはやっぱり自分で作ったCPUで動かしてみたい!というのがあるので、いつかFPGAでこのCPUを実装してテトリスを実行できたらいいな、と思ってます。
METIQ入会記念に、何か自分に買いたいと思ってたのですが、散々迷った結果、FPGAを購入しました!
— ハナニラワタル (@hananirawataru) July 31, 2020
最終目標はCPUを自作したいと思います!
(すでにだいぶ前にFPGAは購入済みです。ちなみに16bitのALUまではできています。)
完成したら、その時はまた記事にしたいと思います。
16万行なら、素数でキリのいい(?)19bitのCPUで足りるな
そしたらALUの回路を改造して、実行できる命令を増やせないかな
なんだかこれまた長いチャレンジになりそう…笑
参考にした書籍
CPUの創りかた
https://book.mynavi.jp/ec/products/detail/id=22065
表紙は可愛いですが、CPUの仕組みが本当によく学べる本でした。
アナログ回路についての解説も多く、最終的には電子工作でCPUを作ることも可能です(!)
この本で設計するCPUは、TD4という名前が付けられているので"TD4 作ってみた"と検索すると、出来上がったCPUを多数見ることができます。
ロベールのC++入門講座
http://www7b.biglobe.ne.jp/~robe/
プログラミング初学者かつ、C++初学者だったので、全般的なプログラミングの基礎をこの本で学びました。上記のHPでも学べますが、私は本を買いました。
本の索引が、一番メインの説明のページが太字で、少しでも出てきたページは普通の太さで網羅的にまとまっているのでとても便利です。
私は半分くらいまで通しで読んで、あとはわからない時に辞書的に調べる使い方をしました。とてもお世話になった本です。
30日でできる! OS自作入門
https://book.mynavi.jp/ec/products/detail/id=22078
実際に手を動かしたわけではないのですが、Nand2tetrisを取り組む前に一通り読みました。OSを作るのはこういう雰囲気なのかと勉強になりました。
Nand2tetrisの12章でOSを作る際は、すでに書き込み済みのアドレスに上書きする際には、今あるデータとORを取る、といった操作を事前に学んでおくことができてよかったです。
ちょっと話がそれますが、この本のポインタの説明がわかりやすかったのでおすすめです。
コーディングを支える技術
プログラミング言語の設計思想を、様々な言語で横断的に学べました。
直接的に参考になったわけではないですが、プログラム言語を使う側ではなく、作る側の視点に立ってみることは、コンパイラを作る時の理解に役立ったように思います。
ソフトウェアの20世紀 ヒトとコンピュータの対話の歴史
https://www.shoeisha.co.jp/book/detail/9784881359488
コンピュータ史を、同じ時期の日本や世界での出来事(時代)とあわせて学ぶことができました。Nand2tetrisで作っていくものは、まさにコンピュータ史をなぞるように進むのでとても楽しく読めました。また自分は近代史に疎いこともあり、コンピュータ史以外の歴史の勉強にもなりました。
感謝の言葉
著者と翻訳者には、この素晴らしい本を作っていただいたことに、ただただ感謝しかありません。また、以下の方に勝手ながら感謝の言葉を送らせてください
読書猿さん
学び方、独学の進め方に限らず、あらゆる面で読書猿さんのホームページや書籍のお世話になっています。困った時はいつも「〇〇 読書猿」で検索して気づきを得ています。
上記で紹介した「コーディングを支える技術」と「ソフトウエアの20世紀」も、「プログラミング 読書猿」でヒットした以下のページで紹介されていたものです。
プログラミングとなら、できること/図書館となら、できること番外編 読書猿Classic: between / beyond readers
またこの記事を書く際には、以下の記事に背中を押してもらいました。
もっとうまく書けるかもという妄執をやめれば速くうまく書ける-遅筆癖を破壊する劇作家 北村想の教え 読書猿Classic: between / beyond readers
リリアンさん
まず何の本を読んで学べばいいかわからない時、リリアンさんが定期的にアップしている勉強した本のリストがとても参考になりました。
今年の下半期に勉強した本はだいたいこんな感じ!
— Lillian (@Lily0727K) December 31, 2019
9月末に引っ越しで本をかなり処分したので、本当はもっとあったけど😄
来年も週1冊くらいのペースでどんどん新しい知識を得ていきたいな😊 pic.twitter.com/EdqZDYIca9
「CPUの創りかた」「ロベールのC++入門講座」「コーディングを支える技術」はリリアンさんが(も)紹介していた本になります。貪欲に学び続けるリリアンさんの姿勢にいつも影響を受けています。
妻
何か訳の分からない事にハマっている自分を放っておいてくれただけでなく、パソコンのことなんて何の興味もないのに
自分「ALU動いた!」 妻「すごい!!」
自分「コンパイラできたー!」 妻「すごいやーん!!」
こんな風にいつも付き合って喜んでくれてありがとう。
最後に
ここまで紹介してきたNand2tetrisですが、誰にでも勧められるかというと人を選ぶと思います。
ただ自分はこの本を終えてみて、例えばこれまでは、星をつないで星座を見つけれるくらいだったのが、”この星座を構成するこの星は〇〇という名前で、そこには陸地と海があって、巨大な海の端にある島国には、ヒトが1億人くらい住んでいる”のような、解像度をコンピュターの画面の向こうに感じられるようになりました。
それが何に活きるの?と言われると、分かりませんが、魔法の様に動くパソコンが少しだけ分かる様になったことが、素直に嬉しいですし、とても満足できました。