学研刊行の「大人の科学マガジン」はご存知だろうか。過去に発売された「科学と学習」シリーズの教材や雰囲気を残したまま対象年齢を引き上げたような形の、昔同じような教材を使用した人たちのノスタルジーを刺激して購入を促す定期刊行ムックだ。今までにもテルミン、プラネタリウムなどを付録につけたムックや電子ブロック、シンセサイザーなどで話題に上がっていたことだろう。え? 知らない?
それはともかく、今から半年ほど前の6月末、大人の科学マガジンvol24が発売された。そのときの付録が4ビットマイコンGMC-4である。過去に電子ブロックシリーズのFX-マイコンの復刻品という形のものらしい。発売当初は(一部の界隈で)盛り上がっていた。
もともと毎月の付録目当てで某通信ゼミ購読を親に頼み込んだ自分としては、こういう付録は結構魅力的で書店で見かけるたびに買うかどうか迷いながら財布を覗いて肩を落として帰る日々を繰り返していた。しかし、発売から3ヶ月ほどたった某日、会誌のネタを探しに生協の書店コーナに立ち寄ったところ、いまだにワゴン平積み台に高く積まれた4ビットマイコンの姿を見てしまった。さらに不幸なことに、そのとき財布には2000円分の(図書カードではなく)図書券が入っていた。
そういうわけで、僕は誘われるままホイホイとレジに4ビットマイコンを持っていっちゃったのだ。
というわけで、この記事は「大人の科学マガジンvol24」の付録である4ビットマイコンGMC-4について、いろいろと調べたり16進数をぽちぽちしたりする記事です。Amazonによると、一時的に在庫切れだけど入荷次第発送できるらしい(1)(1)(http://www.amazon.co.jp/dp/4056054711)10月4日現在。ちなみに、この会誌の締め切りは10月5日であったなんてことは特に重要でない事実ので、気になった人はぜひ買ってみてはいかがでしょうか。また、あまり大きな声では言えませんが、電通生による購入を目論んで大量入荷した生協にはいまだに在庫が残ってるみたいです。生協組合員なら1割引きで購入できるので、こちらでもどうぞ。組合員じゃない場合でも定価で購入できた気がします。たぶん。あと、実物を持っていなくてもいろいろな人たちがエミュレータを作っているので、それをつかって遊んでみてもいいかもしれません。
今回扱う4ビットマイコンGMC-4について、詳しく見てみましょう。
普通のコンピュータと違い、キーは0からFまでの数字キーと4つのファンクションキー、リセットスイッチのみ、出力は7セグLEDと7個のLED、スピーカーとTK-80を思い起こさせるような構成となっています(2)(2)とか書いてますけど、自分はTK-80を使ったことも触ったことも、直接見たことさえありません。使用しているメモリも揮発性のものなので、電源を切ればがんばって打ち込んだプログラムも消えてなくなってしまいます。
プログラムメモリ | Aレジスタ | Bレジスタ | Yレジスタ | Zレジスタ | |
---|---|---|---|---|---|
アドレス | 0x00〜0x4F | 0x6F | 0x6C | 0x6E | 0x6D |
サイズ | 4ビット×80個 | 4ビット | 4ビット | 4ビット | 4ビット |
データメモリ | A'レジスタ | B'レジスタ | Y'レジスタ | Z'レジスタ | |
アドレス | 0x50〜0x5F | 0x69 | 0x67 | 0x68 | 0x66 |
サイズ | 4ビット×16個 | 4ビット | 4ビット | 4ビット | 4ビット |
GMC-4の記憶域の構造は、マニュアルによると表(1)のようになっているそうです。これ以外にも、条件分岐で使用する"実行フラグ"1ビットを保持しています。
これらの記憶領域を、もう少し細かく調べて見ます。
解説する必要はないと思いますが、メモリとは、CPUがデータを保持するためのものです。GMC-4でメモリを操作するためには、後述のレジスタを介する必要があります。
名前のとおり、プログラムを記録するメモリです。プログラムからこのメモリを読み書きすることはできません。なので、プログラム実行中は書き換え不可ということになります。
名前のとおり、データを記録するメモリです。プログラムメモリと違い、このメモリにはプログラムからデータを読み書きできます。また、プログラムメモリと繋がっているので、プログラムメモリの最後の命令を実行した後は、データメモリから命令を読み込みます。なので、データメモリにプログラムを書けば自己書き換えするコードを書くこともできるはずです。といっても16ブロックしかありませんが……。
レジスタとはCPUがデータを一時的に保持しておく場所のことです。1つのレジスタに4ビットのデータを保持することができます。メモリと違い、CPUから直接操作することができます。プログラムではレジスタを主に使い、レジスタに入らないデータをメモリに置いておくような形になるでしょう。
また、珍しいことにGMC-4ではレジスタにもアドレスが割り当てられています。が、レジスタにプログラムを置いて実行、なんてことはできないようです。どうやら、このアドレスはプログラム実行前にレジスタの値を変更するためのもののようです。
主に演算するときに使用するレジスタです。
主にメモリにアクセスするときにアドレスを指定するレジスタです。
上記レジスタの補助レジスタです。主にレジスタのデータを一時的に退避するのに使用します。
次に、入出力を見てみましょう。
ビープ音を鳴らすことができます。あらかじめ設定された終了音、エラー音、短音、長音の他に、指定した音階(低いラから高いソまで)の音を鳴らすことができます。和音、半音の出力はできません。
ちなみに、GMC-4の元となったFX-マイコンでは、動作周波数の関係で一部の音階が正しく出力できなかったそうです。GMC-4では正しい音階で出力できるとのこと。
所謂7セグLEDです。表示するセグメントを直接指定するのではなく、数字を指定すると16進数で表示します。
7個のLEDです。7ビットのデータを使用して点灯するLEDを指定したり、番号を指定して点灯、消灯することができます。
0からFまでのボタンを使用して16進1桁の数字を入力することができます。数字以外のボタンの検知はできません。
GMC-4の命令は省略します。16種類(厳密には30種類)の命令しかないので、プログラムコードを見てフィーリングで感じたり、検索したりしてください。次のページから、実際にGMC-4でプログラムを書いてみます。
「CPUの創りかた」という本をご存知でしょうか。この本では実際にCPUを製作することを通して論理設計の基礎やCPUの構造といったものを学習できる良著(3)(3)「CPUの創りかた」のおかげで論理設計学の単位を取れました。(神奈川県21歳男性)
(4)手作業でROMを半田付けしなければならないので、TD4のメモリはGMC-4と比べるととてもものすごく小さくなっています。そんなメモリ精一杯使っての3分15秒計ってで作ったラーメンはきっととてもおいしいのでしょうです。表紙だけを見るとよくある萌え本に見えますが、中身はかなりガチです。この本で作るCPUはTD4というオリジナルの4ビットCPUですが、そのキラーアプリは3分15秒計測できるラーメンタイマー(4)です。なので、今回はタイマーを移植しつつさらに高機能なものを作成したいと思います。
その結果、下のような仕様になりました。
2進LEDを表示するには、2つの方法があります。
CAL DSPR命令は、アドレス0x5E、0x5Fのメモリの内容を2進数でLEDに点灯させる命令です。0x5Eは下位ビット、0x5Fは上位ビットを表します。
そこで、アドレス0x5D、0x5E、0x5Fに残り時間を記録し、0x5Dの内容を数字LEDに表示すれば、CAL DSPR命令で2進数LEDを簡単に表示することができます。
作成したタイマーのプログラムは、表(2)のようになりました。このプログラムで使用している命令について、少し注釈します。
CAL DSPR命令を使用すると、0x5E、0x5Fに保存されている値が2進数で2進LEDに表示されます。0x5Fが上位3ビット、0x5Eが下位4ビットです。
JUMP命令やCAL命令は実行ビットが1のときのみ実行されます。そのため、実行ビットを使用して条件分岐を書くことができます。
基本的には、ほとんどの命令を実行後は実行ビットは1になります。ですが、以下の状態で命令を実行した場合、実行ビットは0になります。
このプログラムでは、繰り下がり処理や繰り上がり処理で実行ビットを使用しています。
これで、2048秒(5)(5)7ビット+16進1桁なので2048秒までしか表示できませんが、内部処理的には4096秒までカウントすることができますまでのタイマを作成することができました。
アドレス | 命令 | 命令コード | 説明 |
---|---|---|---|
00 | CAL DSPR | ED | 2進LEDの表示を更新 |
02 | TIY 0xD | AD | YレジスタにDを代入 |
04 | MA | 5 | Aレジスタに0x50+Yレジスタの内容を代入 |
05 | AO | 1 | Aレジスタの内容を数字LEDに出力 |
06 | CAL SHTS | E9 | 短いビープ音を鳴らす |
08 | TIA 0x4 | 84 | Aレジスタに4を代入 |
0A | CAL TIMR | EC | (Aレジスタ+1)*0.1秒待機(この場合は0.5秒) |
0C | CAL RSTO | E0 | 数字LEDを消灯 |
0E | CAL TIMR | EC | 0.5秒待機 |
10 | MA | 5 | 残り時間の一桁をAレジスタに読み込む |
11 | AIA 0xF | 9F | Aレジスタに0xF(-1)を加算 |
13 | JUMP 0x1F | F1F | 実効フラグが1なら(結果が正なら)0x1Fにジャンプ |
16 | AM | 4 | Aレジスタの内容をメモリに記録 |
17 | AIY 1 | B1 | Yレジスタを残り時間の次の桁を指すようにする |
19 | JUMP 0x23 | F23 | 最上位の桁で繰り下がりが発生したらアラーム処理へ |
1C | JUMP 0x10 | F10 | 減算処理を繰り返す |
1F | AM | 4 | レジスタの内容を記録 |
20 | JUMP 0x0 | F00 | 最初に戻り、処理を繰り返す |
23 | TIA 0xF | 8F | 2進LED全てを点灯させるためにアドレス0x5Fに0xFを書き込む |
25 | TIY 0xF | AF | |
27 | AM | 4 | |
28 | TIY 0xE | AE | 次にアドレス0x5Eに0xFを書き込む |
2A | AM | 4 | |
2B | CAL DSPR | ED | 2進LED全点灯 |
2D | TIA 0x8 | 88 | 数字LED全部を光らせるために8を表示 |
2F | AO | 1 | |
30 | CAL ENDS | E7 | 終了音を再生 |
32 | TIA 0x4 | 84 | 0.5秒待機 |
34 | CAL TIMR | EC | |
36 | TIA 0x0 | 80 | 2進LEDを消灯するためにアドレス0x5Eに0を代入 |
38 | AM | 4 | |
39 | TIY 0xF | AF | アドレス0x5Fに0を代入 |
3B | AM | 4 | |
3C | CAL DSPR | ED | 2進LED全消灯 |
3E | CAL RSTO | E0 | 数字LED消灯 |
40 | TIA 0x4 | 84 | 0.5秒待機 |
42 | CAL TIMR | EC | |
44 | JUMP 0x23 | F23 | 終了処理を繰り返す |
とりあえずGMC-4でキラーアプリであるタイマを作成しました。次に作成するプログラムですが、せっかくX680x0同好会に所属しているわけなので、なにかゲームを作ってみることにします。ゲームを作るには乱数が必要なので、まずは乱数を作ってみましょう。幸いなことに、サンプルで乱数を使用して音を再生するプログラムがあるので、それをみてみます。
うーん、内容を読むとデータメモリを全て使いきって乱数を生成しているようです(6)(6)y[i]=x=x+y[i];i=(i+1)&0xf;こんな感じ。ゲームを作るとすると、レジスタだけで足りなくなることは火をみるより明らかなので、乱数で使用するメモリをもう少し少なくしたいです。けど、少なくするとあまり品質のいい乱数が得られなさそうです。
そういうわけで、自分で乱数を作成してしまいましょう! 今回は少ないメモリの上で乱数を生成する必要があるのでメルセンヌツイスターなんて使えないので、素直に線形合同法を使用します。線形合同法で1バイトの乱数を生成し、上位4ビットを乱数として使用することとします。Wikipediaの線形合同法のページをみると、最適な定数の求めかたが書いてあったので次のように乱数を生成するようにします。
Xn+1=(5Xn+13)mod 256
ゲーム中からだと乱数を何回も呼ぶことがありそうです。そのときに同じコードを繰り返し書くのもあまり宜しくないので、関数のようにいろいろなところから乱数を呼ぶことができれば便利そうです。そこで、関数(のような物体)を作成しましょう。
関数を呼ぶときは、一般的にはスタックに戻りアドレスを積んで関数にジャンプ、関数から戻るときはスタックから戻りアドレスを降ろしてジャンプしています。GMC-4では即値ジャンプ命令しか用意されていません(7)(7)たぶんCAL命令は関数呼出命令となっています。が、当然ながらユーザが新たな関数を用意することはできません。汚いなさすが大人の科学きたない。さらに、プログラムメモリはプログラムから書き換えることも出来ないので、自己書き換えによる戻り先指定も出来なさそうです。
しかし、方法はなくはないです。GMC-4ではデータメモリに置かれた命令も実行することができます。これを利用して、関数呼出側はデータメモリに戻りアドレスを書き込み、関数にジャンプ。関数側ではデータメモリにジャンプして戻り先にジャンプ、ということをすれば関数のようなもの(8)(8)再帰することが出来ないので、関数ではなく関数のような物体ができます! ジャンプ命令をプログラムメモリの最後(0x4F)に置き、データメモリの最初に戻りアドレスを置くようにすれば、関数から戻るときにデータメモリにジャンプする必要がないので1.5バイト省略することができます。
では、実際に作ってみましょう!
0x50、0x51には戻りアドレス、0x52、0x53に生成した乱数、0x54はループカウンタに使用することとします。そのため、乱数以外のプログラムは0x55〜0x5Fのメモリを使用することができます。
では、戻るためのジャンプ命令が0x4Fになるように乱数生成関数もどきを作っていきます。作成した結果は、表(3)のようになります。0x18〜0x4F(正確には0x18〜0x51)までを使用しています。
……つまり、ゲームに使用できるメモリは0x00〜0x17までの12バイトだけということになりました。とりあえず、シードを設定してサンプルの乱数を使用した音階再生プログラムを書いてみます。
……戻りアドレスを書くためのメモリが無くなってしまいました。しかたないので、戻りアドレスを関数呼び出しの前に設定するのでは無く、プログラム開始前にあらかじめ設定しておくということにします。もはや関数もどきですら無くなってしまいました。作成したプログラムは表(4)のようになりました。
これだけ大変な思いをして生成された乱数ですが、いちおうそれなりの周期を持っているみたいです。シードの設定もしているので、サンプルプログラムよりはちゃんとランダムな音階が再生されています(9)(9)サンプルプログラムではプログラム4バイト、データ8バイトしか使ってないけどね。とりあえず、当初の目標は達成されたとしましょう。
もう時間が無いので、実際にこの乱数を使用したゲームを作成することは出来ませんでした。どなたかこの乱数を使用してゲームを作成出来た方は御連絡ください(10)(10)乱数生成を4ビットにすれば……とかは言ってはダメです!。
アドレス | 命令 | 命令コード | 説明 |
---|---|---|---|
18 | CAL CHNG | E5 | レジスタを補助レジスタと交換してレジスタを退避 |
1A | TIY 0x2 | A2 | 上位4ビットをAレジスタに読み込む |
1C | MA | 5 | |
1D | M+ | 6 | 5倍する |
1E | M+ | 6 | |
1F | M+ | 6 | |
20 | M+ | 6 | |
21 | AM | 4 | 上位4ビットをメモリに保存 |
22 | TIY 0x4 | A4 | アドレス0x54に繰り返し数0xB(-5)を保存。 13を加算するため1回多く繰り返す |
24 | TIA 0xB | 8B | |
26 | AM | 4 | |
27 | TIY 0x3 | A3 | 下位4ビットをAレジスタに読み込む |
29 | MA | 5 | |
2A | AIA 0xD | 9D | 下位4ビットに0xD(13)を加算 |
2C | JUMP 0x41 | F41 | 繰り上がり処理へ |
2F | CH | 2 | A、YレジスタをB、Zレジスタと交換してレジスタを退避 |
30 | TIY 0x4 | A4 | 繰り返し数をAレジスタに読み込む |
32 | MA | 5 | |
33 | AIA 0x1 | 91 | 繰り返し数に1をを加算 |
35 | JUMP 0x4B | F4B | 繰り返し数が負でない場合終了処理へ |
38 | AM | 4 | 繰り返し数をメモリに記録 |
39 | CH | 2 | 退避していたレジスタを戻す |
3A | M+ | 6 | Aレジスタに下位4ビットを加算 |
3B | JUMP 0x41 | F41 | あふれたら繰り上がり処理へ |
3E | JUMP 0x2F | F2F | 繰り返し数加算処理へ |
41 | CH | 2 | レジスタを退避する |
42 | TIY 0x2 | A2 | 上位4ビットをAレジスタに読み込む |
44 | MA | 5 | |
45 | AIA 0x1 | 91 | Aレジスタに1加算 |
47 | AM | 4 | 上位4ビットをメモリに記録 |
48 | JUMP 0x30 | F30 | 繰り返し数の確認処理へ |
4B | CH | 2 | 退避したレジスタを元に戻す |
4C | AM | 4 | 下位4ビットをメモリに記録 |
4D | CAL CHNG | E5 | レジスタを元に戻す |
4F | JUMP 0x0F | F0F | 戻りアドレスに帰る。アドレスは本来なら呼び出し側が設定するが、 メモリ不足のため直接設定 |
アドレス | 命令 | 命令コード | |
---|---|---|---|
00 | CH | 2 | 退避していたレジスタを戻す |
01 | AIA 0x1 | 91 | シード値に1加算 |
03 | CH | 2 | レジスタを退避 |
04 | KA | 0 | キーパッドから入力を受け取る |
05 | JUMP 0x0 | F00 | キー入力がなかったら繰り返す |
08 | CH | 2 | 退避していたレジスタを戻す |
09 | TIY 0x2 | A2 | 乱数の上位4にシード値を指定 |
0B | AM | 4 | |
0C | JUMP 0x18 | F18 | 乱数処理を呼ぶ |
0F | MA | 5 | Aレジスタに乱数の上位4ビットを読み込む |
10 | AO | 1 | 数字LEDにAレジスタを表示 |
11 | CAL SUND | EB | Aレジスタの音を鳴らす |
13 | JUMP 0xC | F0C | 処理を繰り返す |