大人の科学 4ビットマイコンGMC-4 で遊ぶ
はじめに

学研刊行の「大人の科学マガジン」はご存知だろうか。過去に発売された「科学と学習」シリーズの教材や雰囲気を残したまま対象年齢を引き上げたような形の、昔同じような教材を使用した人たちのノスタルジーを刺激して購入を促す定期刊行ムックだ。今までにもテルミン、プラネタリウムなどを付録につけたムックや電子ブロック、シンセサイザーなどで話題に上がっていたことだろう。え? 知らない?

それはともかく、今から半年ほど前の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

今回扱う4ビットマイコンGMC-4について、詳しく見てみましょう。

普通のコンピュータと違い、キーは0からFまでの数字キーと4つのファンクションキー、リセットスイッチのみ、出力は7セグLEDと7個のLED、スピーカーとTK-80を思い起こさせるような構成となっています(2)(2)とか書いてますけど、自分はTK-80を使ったことも触ったことも、直接見たことさえありません。使用しているメモリも揮発性のものなので、電源を切ればがんばって打ち込んだプログラムも消えてなくなってしまいます。

表(1) GMC-4の記憶域
プログラムメモリAレジスタBレジスタYレジスタZレジスタ
アドレス0x00〜0x4F0x6F0x6C0x6E0x6D
サイズ4ビット×80個4ビット4ビット4ビット4ビット
データメモリA'レジスタB'レジスタY'レジスタZ'レジスタ
アドレス0x50〜0x5F0x690x670x680x66
サイズ4ビット×16個4ビット4ビット4ビット4ビット

GMC-4の記憶域の構造は、マニュアルによると表(1)のようになっているそうです。これ以外にも、条件分岐で使用する"実行フラグ"1ビットを保持しています。

これらの記憶領域を、もう少し細かく調べて見ます。

メモリ

解説する必要はないと思いますが、メモリとは、CPUがデータを保持するためのものです。GMC-4でメモリを操作するためには、後述のレジスタを介する必要があります。

プログラムメモリ

名前のとおり、プログラムを記録するメモリです。プログラムからこのメモリを読み書きすることはできません。なので、プログラム実行中は書き換え不可ということになります。

データメモリ

名前のとおり、データを記録するメモリです。プログラムメモリと違い、このメモリにはプログラムからデータを読み書きできます。また、プログラムメモリと繋がっているので、プログラムメモリの最後の命令を実行した後は、データメモリから命令を読み込みます。なので、データメモリにプログラムを書けば自己書き換えするコードを書くこともできるはずです。といっても16ブロックしかありませんが……。

レジスタ

レジスタとはCPUがデータを一時的に保持しておく場所のことです。1つのレジスタに4ビットのデータを保持することができます。メモリと違い、CPUから直接操作することができます。プログラムではレジスタを主に使い、レジスタに入らないデータをメモリに置いておくような形になるでしょう。

また、珍しいことにGMC-4ではレジスタにもアドレスが割り当てられています。が、レジスタにプログラムを置いて実行、なんてことはできないようです。どうやら、このアドレスはプログラム実行前にレジスタの値を変更するためのもののようです。

A、Bレジスタ

主に演算するときに使用するレジスタです。

Y、Zレジスタ

主にメモリにアクセスするときにアドレスを指定するレジスタです。

A'、B'、Y'、Z'レジスタ

上記レジスタの補助レジスタです。主にレジスタのデータを一時的に退避するのに使用します。

次に、入出力を見てみましょう。

入出力
スピーカ

ビープ音を鳴らすことができます。あらかじめ設定された終了音、エラー音、短音、長音の他に、指定した音階(低いラから高いソまで)の音を鳴らすことができます。和音、半音の出力はできません。

ちなみに、GMC-4の元となったFX-マイコンでは、動作周波数の関係で一部の音階が正しく出力できなかったそうです。GMC-4では正しい音階で出力できるとのこと。

数字LED

所謂7セグLEDです。表示するセグメントを直接指定するのではなく、数字を指定すると16進数で表示します。

2進LED

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 SETR命令はAレジスタの数値のLEDを点灯させる命令で、CAL RSTR命令は逆にAレジスタの数値のLEDを消灯させる命令です。

CAL DSPR命令は、アドレス0x5E、0x5Fのメモリの内容を2進数でLEDに点灯させる命令です。0x5Eは下位ビット、0x5Fは上位ビットを表します。

そこで、アドレス0x5D、0x5E、0x5Fに残り時間を記録し、0x5Dの内容を数字LEDに表示すれば、CAL DSPR命令で2進数LEDを簡単に表示することができます。

プログラム

作成したタイマーのプログラムは、表(2)のようになりました。このプログラムで使用している命令について、少し注釈します。

CAL DSPR命令

CAL DSPR命令を使用すると、0x5E、0x5Fに保存されている値が2進数で2進LEDに表示されます。0x5Fが上位3ビット、0x5Eが下位4ビットです。

実行ビットとJUMP命令

JUMP命令やCAL命令は実行ビットが1のときのみ実行されます。そのため、実行ビットを使用して条件分岐を書くことができます。

基本的には、ほとんどの命令を実行後は実行ビットは1になります。ですが、以下の状態で命令を実行した場合、実行ビットは0になります。

  • KA命令を使用したが、キーパッドが押されなかった場合
  • AIA、AIY、M+命令を使用し、結果が0x10未満の場合
  • M-命令を使用し、結果が負でない場合
  • CIA、CIY命令で値が一致した場合
  • CAL SIFT命令を使用し、使用前の数値が奇数であった場合
KA命令はキーパッドをから数値を受け取るときに使用します。また、M+、M-命令はAレジスタに直接Yレジスタが指すメモリの値を加算、減算する命令です。CIA、CIY命令は与えられた値とレジスタの値を比較する命令です。CAL SIFT命令はAレジスタを右シフトする命令です。

このプログラムでは、繰り下がり処理や繰り上がり処理で実行ビットを使用しています。

これで、2048秒(5)(5)7ビット+16進1桁なので2048秒までしか表示できませんが、内部処理的には4096秒までカウントすることができますまでのタイマを作成することができました。

表(2)タイマープログラム
アドレス命令命令コード説明
00CAL DSPRED2進LEDの表示を更新
02TIY 0xD AD YレジスタにDを代入
04MA 5 Aレジスタに0x50+Yレジスタの内容を代入
05AO 1 Aレジスタの内容を数字LEDに出力
06CAL SHTS E9 短いビープ音を鳴らす
08TIA 0x4 84 Aレジスタに4を代入
0ACAL TIMR EC (Aレジスタ+1)*0.1秒待機(この場合は0.5秒)
0CCAL RSTO E0 数字LEDを消灯
0ECAL TIMR EC 0.5秒待機
10MA 5 残り時間の一桁をAレジスタに読み込む
11AIA 0xF 9F Aレジスタに0xF(-1)を加算
13JUMP 0x1FF1F実効フラグが1なら(結果が正なら)0x1Fにジャンプ
16AM 4 Aレジスタの内容をメモリに記録
17AIY 1 B1 Yレジスタを残り時間の次の桁を指すようにする
19JUMP 0x23F23最上位の桁で繰り下がりが発生したらアラーム処理へ
1CJUMP 0x10F10減算処理を繰り返す
1FAM 4 レジスタの内容を記録
20JUMP 0x0 F00最初に戻り、処理を繰り返す
23TIA 0xF 8F 2進LED全てを点灯させるためにアドレス0x5Fに0xFを書き込む
25TIY 0xF AF
27AM 4
28TIY 0xE AE 次にアドレス0x5Eに0xFを書き込む
2AAM 4
2BCAL DSPR ED 2進LED全点灯
2DTIA 0x8 88 数字LED全部を光らせるために8を表示
2FAO 1
30CAL ENDS E7 終了音を再生
32TIA 0x4 84 0.5秒待機
34CAL TIMR EC
36TIA 0x0 80 2進LEDを消灯するためにアドレス0x5Eに0を代入
38AM 4
39TIY 0xF AF アドレス0x5Fに0を代入
3BAM 4
3CCAL DSPR ED 2進LED全消灯
3ECAL RSTO E0 数字LED消灯
40TIA 0x4 84 0.5秒待機
42CAL TIMR EC
44JUMP 0x23F23終了処理を繰り返す
乱数を作る

とりあえず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ビットにすれば……とかは言ってはダメです!

表(3) 乱数生成のプログラム
アドレス命令命令コード説明
18CAL CHNG E5 レジスタを補助レジスタと交換してレジスタを退避
1ATIY 0x2 A2 上位4ビットをAレジスタに読み込む
1CMA 5
1DM+ 6 5倍する
1EM+ 6
1FM+ 6
20M+ 6
21AM 4 上位4ビットをメモリに保存
22TIY 0x4 A4 アドレス0x54に繰り返し数0xB(-5)を保存。
13を加算するため1回多く繰り返す
24TIA 0xB 8B
26AM 4
27TIY 0x3 A3 下位4ビットをAレジスタに読み込む
29MA 5
2AAIA 0xD 9D 下位4ビットに0xD(13)を加算
2CJUMP 0x41F41繰り上がり処理へ
2FCH 2 A、YレジスタをB、Zレジスタと交換してレジスタを退避
30TIY 0x4 A4 繰り返し数をAレジスタに読み込む
32MA 5
33AIA 0x1 91 繰り返し数に1をを加算
35JUMP 0x4BF4B繰り返し数が負でない場合終了処理へ
38AM 4 繰り返し数をメモリに記録
39CH 2 退避していたレジスタを戻す
3AM+ 6 Aレジスタに下位4ビットを加算
3BJUMP 0x41F41あふれたら繰り上がり処理へ
3EJUMP 0x2FF2F繰り返し数加算処理へ
41CH 2 レジスタを退避する
42TIY 0x2 A2 上位4ビットをAレジスタに読み込む
44MA 5
45AIA 0x1 91 Aレジスタに1加算
47AM 4 上位4ビットをメモリに記録
48JUMP 0x30F30繰り返し数の確認処理へ
4BCH 2 退避したレジスタを元に戻す
4CAM 4 下位4ビットをメモリに記録
4DCAL CHNG E5 レジスタを元に戻す
4FJUMP 0x0FF0F戻りアドレスに帰る。アドレスは本来なら呼び出し側が設定するが、
メモリ不足のため直接設定
表(4)ランダム再生のプログラム
アドレス命令命令コード
00CH 2 退避していたレジスタを戻す
01AIA 0x1 91 シード値に1加算
03CH 2 レジスタを退避
04KA 0 キーパッドから入力を受け取る
05JUMP 0x0 F00キー入力がなかったら繰り返す
08CH 2 退避していたレジスタを戻す
09TIY 0x2 A2 乱数の上位4にシード値を指定
0BAM 4
0CJUMP 0x18F18乱数処理を呼ぶ
0FMA 5 Aレジスタに乱数の上位4ビットを読み込む
10AO 1 数字LEDにAレジスタを表示
11CAL SUND EB Aレジスタの音を鳴らす
13JUMP 0xC F0C処理を繰り返す