JavaScriptを有効にすると、ここにメニューを表示します。

ysflight.com

FM-7, F-BASIC, 6809プログラミング

若者たちよ、我々がFM-7とかFM-TOWNSとか話すのを聞くとこう思うだろう、

こいつらマジで終わってる、
過去の栄光に生きてるオッサンだ、
ああ、聞くのが痛い、
あんたらすごかったのわかったから、痛いからもうやめてくれ〜、

と。だが、それは違う。我々は、君たちが我々の痛い話を聞いて、どうリアクションしていいか困ってるのを見て楽しんでいるのだ。

 

2017年のDemosplashでFMシリーズを紹介するプレゼンテーションの機会をいただいた。その準備をしている間に、「果たして俺はまだ6809アセンブラを書けるのか?」と、疑問になって、フリーの6809アセンブラasm6809を見つけ、実機もあることだし、試しに、コードを書いてみた。書き始めたら止まらなくなって、結局、直線補間と三次元座標変換まで書いて、ちょっとまだゲームを作るには遅いけど、3次元のワイヤーフレームのグラフィックスを書けるところまで、二週間で作った。そして、せっかくだからデモとやらを書いてみようと思って、FM-7用デモを書いてDemosplashのデモコンペに出してみたら、レトロ部門で優勝してしまった。結果は、ここ。ありがとう、我がFM-7。

ちなみに、そのデモコードはこれ。なお、これはバージョン1。XM7からも実行できるようにテープイメージ.T77ファイルと、実機で実行するように.WAVファイルで公開。

Demosplash 2017 レトロ部門優勝作品 「終わらないFujitsu Micro伝説」

そして、厳密にいうと優勝作品ではないけど、バージョン3がこれ。XM7で実行できるように、D77形式、T77形式と、実機で実行するためにデータレコーダから読み込める.WAV形式で公開。

FM7DEMO_YS_V3.D77

FM7DEMO_YS_V3.T77 

FM7DEMO_YS_V3.WAV

それから、バージョン3のソース。

DEMO2017/fm7demosrc.zip

アセンブルは、次のコマンドで。

asm6809 org2000.asm common.asm lineddademo.asm psgplay.asm stardemo.asm circledemo.asm squaredemo.asm wirecubedemo.asm flightdemo.asm lineddasubcpu.asm linedda.asm subcpu.asm div16.asm div32_16.asm mul16.asm transform.asm scrnclip.asm geometry.asm circle.asm util.asm moonlit.asm

なお、実行の様子のビデオがこれ。なぜか途中でPSGのChannel 1とChannel 2がずれるんだけど、これはバージョン3では起こらなくなってる。

https://www.youtube.com/watch?v=6VGK80il4Rs

 アセンブラってこんなに簡単だったっけ?と、思ったけど、開発はエミュレータでできるし、秀丸エディタがあるし(当時はBASICのコメント行として書いた)、バージョンコントロールシステムもあるし、環境は大幅に良くなった。それに、プログラミングの概念も当事に比べてはるかに多く知ってるし。やはり、当事14歳の僕にはFM-7でフライトシミュレータを書くのは無理だった。

6809アセンブラによる計算いろいろ

せっかく書いたので、いろいろ公開することに決定。いずれのコードも実機で動作確認済み。だったが、その後も改良してしまって、実機で動作確認してないのもあり。

なお、アセンブラは、asm6809を使用。個別のルーチンは、後からつなげて使うためにORGが入ってない。asm6809は、ORGが入ってない、あるいは、ORG $0で始めるとアドレス計算を間違う(オフセットが全部ゼロになるっぽい)というバグがあるので、使うときは適宜ORGをつける必要あり。

また、それぞれのルーチンは、Cの関数みたいに引数をスタックに積んで呼ぶようにすればより良いんだろうけど、F-BASICからテストする都合上、決まったアドレスに値を書き込んで、決まったアドレスから結果を回収する。

まず、これは、アセンブラによる割り算ルーチン。16bit/16bit, 32bit/16bit, 32bit/32bitの三種類を書いてみた。アセンブラの他のプログラムからの利用を想定してるけど、F-BASICからも利用可能。F-BASICはどうやらDEFINT A-Zとやっても、割り算は浮動小数点でやってるみたいだから、これを使うと整数の結果だけ必要という場合は速いかもしれない。

[16bit signed int / 16bit signed int]

[32bit signed int / 16bit signed int]

[32bit signed int / 32bit signed int]

FM-7でリアルタイム3次元グラフィックスをやろうとして、(x,y)をzで割る、という段階で挫折しかかっていたときに、当事のプログラミングの師匠が教えてくれた方法。一言で言うと二進数の筆算をするんだよね。ただ、当事の技術レベルでは実装してもまともに動かなかった記憶がある。

それから、16bit signed int x 16bit signed intを計算して32bit signed intにするコード。これは6809にMUL命令があるから結構簡単。

[16bit signed int * 16bit signed int -> 32bit signed int]

そして、使わなかったんだけど、平方根計算。フルに32ビットをサポートしようと思えばできるのだが、そのためだけにループの中のインストラクションが増えてしまうので、32767*32767の平方根までに限定。だから、厳密にいうと30ビット整数の平方根だね。

[Square Root of 32bit unsigned int]

F-BASICプリプロセッサ

そして、これらのアセンブリコードをF-BASICからテストしようと思ったとき、困った。F-BASIC 3.0では、ラベルに分岐はできない、よく使うルーチンを別のファイルからINCLUDEとか高度なことは夢のまた夢、アセンブリコードの特定の箇所を呼び出そうと思っても、アセンブリコードを書き換えるたびに呼び出すアドレスが変わる。それでは収集がつかないので、F-BASIC to F-BASICコンパイラ(?)を書いてみた。

[fbasic.py]

使い方は、

fbasic.py <Input> <Output>

入力は、テキストファイルで、F-BASICのソースコード、ただし、行番号はfbasic.pyが生成するから、つけなくていい。出力は、普通にFM-7が理解できるF-BASICのソースコード。考えてみたら、F-BASIC以外でも使える気がしてきた。

入力ソースコード内で、一行だけ

$$LABEL

と書いておくと、

GOTO $$LABEL

とか

GOSUB $$LABEL

みたいな感じで分岐できる。また、他のソースファイルをインクルードできて、

##INCLUDE subroutine.fbas

のように書く。また、行番号を強制的に振りたい場合は、

##10000

のように書くと、次の行が10000行になる。そして、アセンブラと組み合わせて使おうとか思うと、出てきたリストを見てPOKE &H5123,PEEK(VARPTR(A%)) みたいに書いていたが、これでは、アセンブリコードが代わるたびにアドレスが変わって収集がつかない!そこで、まず、asm6809で、SYMファイルを出力する。そして、

##SYMFILE out.sym

みたいにSYMファイルを指定すると、あとは、

POKE &&DIV16_NUMER&&,PEEK(VARPTR(A%))
POKE &&DIV16_NUMER&&+1,PEEK(VARPTR(A%)+1)
POKE &&DIV16_DENOM&&,PEEK(VARPTR(B%))
POKE &&DIV16_DENOM&&+1,PEEK(VARPTR(B%)+1)
EXEC &&DIV16&&

みたいにソースに書いておけば、fbasic.pyがアドレスをSYMファイルに書いてあるアドレスで置き換えてくれるという優れもの。

ローカル変数も実現しようかと思ったけど、そこまでしなくてもアセンブリコードのテストには十分だと思ったんで、やっぱりやめた。

高速直線補完とサブCPUインターフェース

I/O 1985年2月号の「The Emergency」はすごかった。メインCPUとサブCPUがほぼ完全に並列処理で動いていて、なんでも、130本毎秒の直線を引いたとか。ただ、どのぐらいの長さの直線で計測したのかは書いてなかったけど。下の僕が書いたこの直線補完ルーチンは、その直線補完には追い付いてないかもしらんけど、結構近いところまで行ってるんじゃないかと思う。横に長い直線に関しては、ループ展開とかパラメータをコード内に直接書き込むとかやって高速化を試みたけど、縦に長い直線はあまり大した高速化しなかった(FM-7のモニタは縦方向200しかないから一般的な直線を考えると横方向を速くした方が費用対効果は大きい)けど、縦長の直線もある程度ループ展開とかするともう少しパフォーマンス上がる鴨しれない。

説明書いてられないんで、興味のある人は各自読んでね。linedda.asmが直線補完、lineddasubcpuがサブCPUへの転送と実行コード。subcpu.asmがサブCPU全般。

[linedda.asm] [lineddasubcpu.asm] [subcpu.asm]

XM7用ユーティリティ

それから、コードは実機に持っていく前に、XM7でテストするために、Apolloさん作のFTOOLSを多用した。(Apolloさんには、ツールに関する質問やFM-7のハードの質問にお答えいただいたので、感謝!)が、なぜか、asm6809が出す.srecファイルからFMENCODEで2B0ファイルに変換しようとすると失敗する。Apolloさんが親切にも調べてくれて、asm6809の出す.srecのチェックサムがCOMを取っていない影響でエラーと判断して変換していなかったということが判明。asm6809のバグだった。そこで、バイナリから.2B0ファイルに変換するコードをC++で書いた。やっぱPythonよりC++の方が書きやすいわ。これでC++が標準ライブラリでディレクトリ一覧とか取ってこれるようになってくれるとPython捨てるんだけどな。Python嫌い。かなり使ってるけど嫌い。

[make2B0.cpp]

あと、FMENCODEで、F-BASICのアスキー形式ファイルを.0A0形式にエンコードしてディスクイメージにFMWRITEしようとするとき、なぜか失敗する現象が多発。ひとつわかったのは、既にXM7上で作成したファイルが存在する.D77ファイルに書き込むときに失敗するようなので、必ず何も入ってない(DSKINI直後の)D77イメージに書くと良いらしいということ。もうひとつは、.0A0形式ファイルは、256N+16バイトになってないと、ゴミデータが書き込まれてしまうらしいということがわかった。なので、FMWRITEする前に、.0A0形式ファイルを、256N+16バイトになるように0でパディングする必要がある。そういうプログラムも書いた。

[pad0A0.cpp]

FM-7で何かしようって人ならC++コンパイラ使えないってことはないだろうということで.CPPファイルだけ公開でいいよね?.EXE欲しい人いるだろか?

PLAY文によるPSGキャプチャ&再生

最初にデモを作った後で、BGMの演奏をBASICに頼っているのが心残りだった。以前、I/O誌に、IRQをフックして、F-BASICのPLAY文がPSGに書き込んだ値をキャプチャするプログラムとそれをバックグランドで演奏するプログラムが掲載されたことがあったと思うのだが、見つけられなかった。仕方ないので、自分で書いた。使い方は、まず、

RUN "BEGINCAP"

として、IRQのフックを仕掛ける。続いて、PSGの演奏プログラムを読み込んで演奏する。演奏中、PSGレジスタの値に変化があるたびに裏RAMにストアされる。演奏が終わって、最後の音が鳴り終わったことを確認したら、

RUN "ENDCAP"

ここで、ファイル名を入力すると、裏RAMに記録したレジスタの値を$2000からにコピーしてセーブする。再生するときは、

RUN "PSGPLAY"

として、セーブしたファイル名を入力する。使い方は、ソースを書くから各自読んでね。なお、uraram.asmは、裏RAMの内容を表RAMに持ってくるために使用。

[psgdisk.D77] [BEGINCAP.bas] [ENDCAP.bas] [psgcapture.asm] [PSGPLAY.bas] [psgplaym.asm] [uraram.asm]

なお、このプログラムで取り出したデータファイルは、1バイトずつ、[レジスタ][書き込む値]と並んでいて、特別な値として、$FFデータの末尾、$FE区切り、$FD変化なし(次の1バイト x 2ms待つ)というのが入ってるので、FTOOLSのFMREADとFMDECODEを使って普通のバイナリにして、他の環境のAYチップに持っていこうと思ったら可能だと思う。昔はPSGなんていう便利なものがあったから効果音とか簡単に作れたんだよ。なんだよ今のなんでもかんでも録音しなきゃならないのって。なんで今のPCはPSGのひとつ搭載できないんだろう?

ところで、PSGのエンベロープジェネレータなんだけど、FM-7のAY-3-8910で、エンベロープのパターンを開始させるトリガが何なのか、さんざん調べたがわからん。PLAY "S0M5000CCCC"とやったら、トーンが4回鳴る。しかし、我がキャプチャプログラムは、PSGレジスタの値の変化を検出しない。

F-BASICをXM-7のデバッガで追ったら、トーンごとに、まず音階を書き込んで、次にボリュームレジスタに$10を書き込み、レジスタ13に値を書き込んで、次に、レジスタ11と12に周期を書き込むと音が出るようだ。果たしてこの順にレジスタに書き込む必要があるのか?で、実験プログラムを書いた。順に値を書き込むと、たしかに音が出る。続いて、キーを押すたびにレジスタ13にエンベロープパターンの値を書き込むと、書き込んだ瞬間からエンベロープが始まるようだ。だから、どうやら、レジスタ13に値を書き込むのがエンベロープのタイマーを開始、あるいはリセットするトリガになってるようだ。

次のプログラムは、EXEC &H2000でボリュームレジスタ、エンベロープ周期、エンベロープパターンを書き込んで一度ポーンと音が鳴る。キーを待ってEXEC &H2027で、今度は、レジスタ13(エンベロープパターン)だけ値を書き込むと、またポーンと音が鳴る。

ENVTEST.bas

10 CLEAR ,&H1FFF
20 DEFINT A-Z
30 LOADM "ENVTESTM"
40 EXEC &H2000
50 A$=INPUT$(1)
60 EXEC &H2027
70 GOTO 50

ENVTESTM.asm

                      ORG     $2000

                    EXPORT  SETUP
                    EXPORT  TONE

SETUP               PSHS    A,B
                    LDD     #$07F8
                    BSR     WRITE_TO_PSG
                    LDD     #$0025
                    BSR     WRITE_TO_PSG
                    LDD     #$0131
                    BSR     WRITE_TO_PSG
                    LDD     #$0810
                    BSR     WRITE_TO_PSG
                    LDD     #$0B88
                    BSR     WRITE_TO_PSG
                    LDD     #$0C13
                    BSR     WRITE_TO_PSG
                    LDD     #$0D00
                    BSR     WRITE_TO_PSG
                    PULS    A,B,PC

TONE                PSHS    A,B
                    LDD     #$0D00
                    BSR     WRITE_TO_PSG
                    PULS    A,B,PC

WRITE_TO_PSG        PSHS    A,B
                    STA     $FD0E       ; PSG Register
                    LDA     #3          ; PSG Register Selection
                    STA     $FD0D
                    CLR     $FD0D       ; PSG Register Selection end

                    STB     $FD0E
                    LDA     #2          ; PSG Write
                    STA     $FD0D
                    CLR     $FD0D
                    PULS    A,B,PC

 

 

Comments are welcome.  Send E-Mail to: 

Back to http://www.ysflight.com