memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

Hack言語を少々(Facebookのやつじゃないからね)

春から2年生向け少人数ゼミで以下の書籍をもとに勉強会を開催しています。

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

大学生なら自分で勉強するのは当たり前でしょう?ということで、好き嫌いに関わらず教わる授業ではなく、自分で本を買って勉強しようという授業です。

週1コマなのでなかなか進まないのですが。。。やっと4章を終えようとしているところです。

4章は機械語です。

それまでの章で開発してきたCPUを、機械語(Hack言語)で動かそうという章です。

Hack言語というか、機械語をまともに授業で教えていないので、なかなか理解するのが難しいようですが、使えるリソースがRubyJavaなどに比べると圧倒的に少ないので新鮮なようです。

4章の章末問題が2つあるのですが、2つ目の問題を解説してみたいと思います。

といってもわかりやすい解説というわけではなく、あくまで自分のためのメモログ?になります。

今はとりあえず勉強して理解しているのですが、半年もすれば忘れているでしょう。一応、来年もこの本での勉強会を予定しているので、あとで思い出しやすいように自分の思考過程を残しておきたいと思う、それが目的になります。

問題:キーボードを無限ループで監視して、キーが何か押されていたら黒を、押されていなければ白を描画するプログラムを作成する。

ちなみに、Hack言語などの解説資料(英語)がここにあります。

まず、押下されたキーの値を読み取ります。

以下のように書けます。

@KBD
D=M

@0
0;JMP

@KBDは押下したキーの値が入るアドレス(0x6000番地)を指しています。次のD=MのMは前の行で指定したアドレス(0x6000)の内容を表していて、要は0x6000番地の内容を読んでDレジスタに書き込みなさい、ということを表しています。

あとは、@0は0番地を表していて、その次にくる0;JMPが無条件ジャンプなので、0番地に無条件でジャンプする。。。ということを表しています。

ここまでをCPU Emulatorで実行すると、キーを押すとDレジスタにそのキーの値が入り、キーを離すとDレジスタが0になります。

さて次です。

「キーが押されているときは」ということは、上記のプログラムで「Dレジスタの値が0以外だったとき」ということになります。これはちょうど命令があって「D;JNE」と書けばよいようです。

コードは以下のようになります。

まず赤字部。@ENDは(END)があるアドレスを表しています。D;JNEとありますので、キーが押されていなかったときはD=0になり、D;JNEはスルーします。スルーすると最初に戻るジャンプ命令(0;JMP)で繰り返されるわけです。

つまり、以下のコードでは、キーが押されていなければキー値を読み取ることを続け、キーが押されたら終了する(無限ループになる)のようです。ちなみに、青字の「(END) @END 0;JMP」は無限ループになっています。

@KBD
D=M

@END
D;JNE

@0
0;JMP

(END)
@END
0;JMP

キーが押されていなかったら画面に点を打ってみましょう。画面のRAMは@SCREENで定義されています。具体的には0x4000になります。そこに1を書き込む命令を追加すれば良さそうです。追加したのが以下です。

@KBD
D=M

@END
D;JNE

@SCREEN
M=1

@0
0;JMP

(END)
@END
0;JMP

では、画面の同じ場所に点を打ち続けても面白くないので、点を打つ場所を変えてみます。変えるためには@SCREENをメモリに入れて、その値に1足していけば良さそうです。

まず、@SCREENをメモリの0番地に入れてみます(以下の赤字)

本当はメモリの0番地をM[0]と指定できて、そこに@SCREENを代入するときにM[0] = @SCREEN と書けたらいいのでしょうけど、そんな命令はHackにはありません。そこで、@SCREENというアドレスをDレジスタに退避して、その値を0番地に書き込んでみます。面倒ですが。。。CPU上に設計された命令しか使えない。。。当たり前ですが、それが機械語です。しかたがないのです。

あと、キーボードを読み取るコードが先頭ではなくなったので無条件ジャンプの「@0 0;JMP」の行を書き換える必要があります。どこに戻せばいいかというと@KBDのところですね。。。ここが何番地かわからないので、上記の(END)と同じように、ここにラベルを置きます。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@END
D;JNE

@SCREEN
M=1

@LOOP
0;JMP

(END)
@END
0;JMP

次に0番地に格納したアドレスを1つずつ足していくコードを付け加えます。@0で0番地を指定し、その直後にMが来ていますので、右辺のM+1は、0番地のメモリの内容に1足し、その結果をさらに0番地にセットすることになります。結果的に0x4000が0x4001になり、0番地の内容が1つずつ増えていきます。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@END
D;JNE

@SCREEN
M=1

@0
M=M+1

@LOOP
0;JMP

(END)
@END
0;JMP

あともう少しです。

じゃ、上記で「@SCREEN M=1」という行を「0番地で指しているアドレスの内容の番地に1を書き込む」ように書き換えてみます。以下です。

0番地に入っている値をアドレスとして使用したいので、@0と書いたのちに、A=Mと書いて0番地に入っている値を参照してAレジスタに入れます。その次の行でM=1とありますが、このMはAレジスタに入っている値をアドレスとしてみて、その値を参照しますので、結果的に0番地に入っているアドレスに1を書き込むことができるわけです。。。わかってしまえばどうってことがないのですが。。。機械語を書き慣れないと辛いですね。。。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@END
D;JNE

@0
A=M
M=1

@0
M=M+1

@LOOP
0;JMP

(END)
@END
0;JMP

あと少し。

上記のコードは、キーを押すまで画面に点を描画しますが、押したら終了(無限ループに入る)してしまいます。これでは、この問題の要求とは違いますので、書き換えないといけません。説明の手順が悪かったかもしれませんが。。。面倒なので続けます。ごめんなさい。

キーが押されていなかったら、画面に点を打つ「@0 A=M M=1」を迂回したいと思います。キーが押されていなかったら、つまり、Dレジスタが0だったら、とするため、ジャンプ命令をJNEからJEQに書き換えました。で、ジャンプ先は迂回するためPSETというラベルを作り、そこにジャンプさせます。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@PSET
D;JEQ

@0
A=M
M=1

(PSET)
@0
M=M+1

@LOOP
0;JMP

もう最後ですね。

点では見にくいので、1バイトを塗りつぶすため「M=1」を「M=!M」に書き換えます。これで塗りつぶせます。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@PSET
D;JEQ

@0
A=M
M=!M

(PSET)
@0
M=M+1

@LOOP
0;JMP

やっと完成です。Hackコンピュータは十分に遅いので、全画面が塗りつぶされるまで待つことはしないでしょうけど、画面最後尾に行っても戻すコードはないので。。。テキトウなところで止めるようにしましょう。

実行結果は以下のようになります。

youtu.be

最初からこういうのオモシロイと思う人はなかなかいないよな。。。と思うのですが、会社に入ってからこういうの勉強する機会はなかなかないと思うので、教養として経験してもらいたいな。。。と思っています。