前回 からBGM追加作業の続きで、クロスアセンブラ環境作業についてです。
deexo.asm
実作業でサクラエディタを使ったわけではないんですが、 asm でもシンタックスハイライトしてくれたのでスクショ用として採用
実装の前にコード設計、ということでまずは Exomizer の Z80 用展開ルーチンの deexo.asm をチェックします。
前回も書いたように使い方のマニュアルなどというものは存在しないのですが、上記のように一応ソース先頭のコメントに使い方は書いてあります。コメントの通り
のはいいとして、「どのルーチンをコールするのか」が書いてなかったりしますが、先頭の deexo を呼べば良いようです。レジスタ破壊有無についても特に書かれていませんが、 IX, IY を含むすべてのレジスタを使用しているようです。
これを書いてていまさら気づいたのですが、BGM 追加の改造版 Tinyみずいろのプログラムでは展開ルーチンコール時に IX と IY を保存してませんでした……。 BASIC では使われてないんですかね
グラフィック展開ルーチン改修
Tinyみずいろの オリジナルのランレングス展開ルーチン では DEレジスタが展開前データ、 HLレジスタが展開先 (=VRAM 先頭) アドレスを入れるようになっていて、 DE レジスタへのロード命令のオペランド即値を BASIC 側から POKE 文で書き換えるという構成になっています。
9F00: PUSH AF
9F01: PUSH BC
9F02: PUSH DE
9F03: PUSH HL
9F04: LD DE,0A00AH
9F07: LD HL,0E200H
9F0A: LD A,(DE)
9F0B: CP FFH
BASIC 側は以下のようなコードです。
180 POKE &H9F05,PEEK(&HA000+(D-5)*2)
190 POKE &H9F06,PEEK(&HA001+(D-5)*2)
初回の 「Tinyみずいろ」にBGMを追加するまで(1) に書いたメモリマップを再度見てもらうとわかりますが、上記の BASIC コードの PEEK文の中にある A000H は 各グラフィックデータの先頭アドレスが入ったテーブルです。その後ろの "D" はデモ本体制御部でグラフィックを指定する変数で 5〜9 の値を取ります。つまり、指定されたグラフィックデータに対応するアドレスを読み出してLD DE,A00AH
の命令のアドレス部分 2バイトを直接書き換えている、ということになります。
BASIC 部分および既存のアセンブラルーチンはなるべく修正しない、という方針で Exomizer 展開ルーチンに置き換えるため、
- deexo の展開ルーチン本体は別のアドレスに置く
- 既存の展開ルーチンから deexo の展開ルーチン本体をコールする
- 展開元アドレスのレジスタロード命令は元のコードと同じ位置に置く
としました。
9F00: PUSH AF
9F01: PUSH BC
9F02: PUSH DE
9F03: PUSH HL
9F04: LD HL,0A00AH
9F07: LD DE,0E200H
9F0A: CALL deexo
9F0D: POP HL
9F0E: POP DE
9F0F: POP BC
9F10: POP AF
9F11: RET
9F00H からの展開ルーチンは BASIC プログラム側に DATA 文として埋め込まれているコードですが、 deexo のアドレスを埋め込んでやる必要があるので、 A000H 以降のグラフィックデータおよび展開ルーチンとあわせてアセンブルするように1つのアセンブラソースに含めてしまいます。 9F00H 以降のアセンブル結果については、大した長さではないのでとりあえず BASIC 側の DATA文を手修正することにしました。
グラフィックデータについてもアドレス計算をアセンブラにさせることにして、グラフィックデータをアセンブラ用の DB文でデータを記述するソースに変換した上で
- 9F00H の BASIC からコールする展開ルーチン
- 9F28H のテープロードルーチン
- A000H のグラフィックデータアドレステーブル
- A00AH 以降にグラフィックデータ×5枚分
- その後ろに Exomizer 展開ルーチン本体
というアドレス構成でテストプログラムを作ってみることにしました。
クロスアセンブラ環境
Z80 アセンブラといえば大昔 PC-8801mkIISR 上で書いて以来だったのですが、さすがに Z80 実機のネイティブのアセンブラを持ち出そうという元気はなく、とりあえず普段使いの環境である NetBSD で使えそうなクロスアセンブラはないかと探してみました。
ちなみに P6 PSG音源ドライバ作者のよっしゅさんは Z80 のアセンブルについて PC-8801 上で HIT-88 (以下のおっさんホイホイ動画でも出てくる PC88用アセンブラ) を使われていたようです
HIT-88 はフロッピー箱を探せば出てくるという気はするけれど、ZASM用に書き換えて作業するのが正解だろうか
— Izumi Tsutsui (@tsutsuii) 2016年6月18日
Z80-ASM というのが pkgsrc に入っているようだ https://t.co/GilBMRK8tn
— Izumi Tsutsui (@tsutsuii) 2016年6月20日
が、イマドキ(?)のアセンブラは ROM/RAM に直接配置できるようなコードを吐くのではなくリンカでロードするのが前提だったり、 include 文が使えなかったりで、結局 TINY野郎さんオススメの「Windows上でZASM」という方法に落ち着きました。メインの NetBSD機とは samba でやり取り
Z80アセンブラはやっぱりTINY野郎さんのページにあるように ZASMが一番手軽という感じですね https://t.co/N3hK0snCVD 変にNetBSDでやろうとするとアレだけど
— Izumi Tsutsui (@tsutsuii) 2016年6月20日
上記の TINY野郎さんによる 「アセンブラでソフトを開発しよう」 の解説は PC-6001 に限らず過去の Z80 マシン開発に使えるのでおすすめです。また、 ZASM では include 文が使用できるので、他から流用するアセンブラソースや生成データを別ソースのままにすることも簡単です。
Exomizer ルーチン改修
事前のコード設計(?)に従って、
圧縮データを zasm の db 形式に変換するという本末転倒作業
— Izumi Tsutsui (@tsutsuii) 2016年6月21日
逆アセンブルした結果をアセンブラソースファイルに打ち込むという作業
— Izumi Tsutsui (@tsutsuii) 2016年6月21日
……という具合に実装作業を進めたのですが、 ZASM で deexo.asm の展開ルーチンをアセンブルしようとすると一つ問題がありました。
ZASM は Z80未定義命令 (dec ixl とか) 解釈しないんか(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年6月21日
deexo.asm は 「IX, IY のインデックスレジスタの下位 8ビットに対するアクセス・演算」という、いわゆる「Z80 未定義命令」を使っているのですが、 ZASM はこれらに対応していないようでした。
該当する命令は 2行だけだったので、とりあえず 検索で出てきた命令表 を見て DB 文のコード直書きに置き換えてやります。
; ld ixl, c
db 0ddh,069h
; dec ixl
db 0ddh,02dh
また、 ZASM とは全然関係ないのですが、 deexo.asm の先頭のコメントには以下のようなことが書いてあります。
;ATTENTION!
;A huge speed boost (around 14%) can be gained at the cost of only 5 bytes.
;If you want this, replace all instances of "call exo_getbit" with "srl a" followed by
;"call z,exo_getbit", and remove the first two instructions in exo_getbit routine.
要は、サブルーチン側に記述されている SRL A (右シフト) とその結果 (=Aレジスタの最上位ビット) の Zフラグ判定の 2命令をサブルーチン呼び出し元で記載してやると 5バイト大きくなるが 14% 速くなる、ということのようです。 今回の用途では 5バイトより 14% の速度向上のほうがよっぽど重要なので、この修正も採用することにします。なぜ ifdef とかになっていないのか……
この deexo.asm に対する修正の diff を gist に置いたので、もし流用される方がいれば参考まで。
ZASM の ORG 問題
ZASM でコードを書いているともう一つ問題がありました。
思い通りのコードが生成されなくてストレスがマッハ(ヽ´ω`)
— Izumi Tsutsui (@tsutsuii) 2016年6月21日
Tinyみずいろでは 9F00H, 9F28H, A000H と途中にすき間を空けつつコードを配置する必要があるので、コード開始アドレスを指定する ORG 擬似命令を複数置いたのですが、
ORG を複数書いてもできあがるバイナリが詰められてしまうのはなんで(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年6月21日
なぜか ZASM が吐くバイナリを見ると ORG のアドレスを無視してすき間を詰めたバイナリが生成されてしまいます。
アセンブラの使い方で格闘するのは本題ではなかったのでとりあえず別のアセンブラを使って回避しようとしたりしたのですが、この問題の解決策は TINY野郎さんに教えていただきました。
@tsutsuii ZASMの-Cオプションで直接バイナリーを吐くと詰められちゃいますよね。私はいつも、ラベルとDS命令を使ってスペースを開けてます。
— TINY野郎 (@tiny_yarou) 2016年6月21日
例えば、
ORG 0D000H
RET
ORG_E000: DS 0E000H-ORG_E000 ;←こうする
RET
こうして作ったバイナリを動かしてみると Exomizer 展開ルーチンでも問題なくちゃんとグラフィック表示できるのが確認できました。
うん。確かにバッファ156バイトで動いているようだ
— Izumi Tsutsui (@tsutsuii) 2016年6月21日
左:オリジナルの FFh のみランレングス圧縮で DC1Eh まで使用
— Izumi Tsutsui (@tsutsuii) 2016年6月21日
右:exomizer 2 圧縮して展開ルーチン込みで D14Bh まで使用
不要になる元の展開ルーチンとかもう少し詰めれば 3KB は空きそう pic.twitter.com/bPcy2VQNl9
このツイートの「ランレングス圧縮で DC1Eh まで」というのは最後の 00h 終端を忘れていた数字で、実際は DC1Fh が正解です
表示速度も問題ないというか、むしろちょうどよくなった感じです。
展開速度は 元のランレングス圧縮版の展開(処理はほぼバイトコピー)がほぼ一瞬で書かれるのに対して exomizer 版は BASIC の LINE 文の ,BF の塗りつぶしと同じくらい。比較級だと遅いけどデモとしては全然気にならないレベル
— Izumi Tsutsui (@tsutsuii) 2016年6月21日
というわけで、次はいよいよ PSG音源ドライバを組み込んでみます。
PSG音源ドライバ組み込み
よっしゅさんの PSG音源ドライバ のソースは前述の通り PC-8801用のアセンブラ HIT-88 用に書かれているものですが、 当時の Z80 アセンブラの構文は先駆者(?)である CP/M の ZASM 準拠のものが多かったせいかアセンブラ書式の修正は不要で、かつ deexo.asm のような未定義命令もなかったため、ほぼそのまま ZASM でアセンブル可能でした。
よっしゅさんのPSG音源ドライバ、ZASMでほぼそのまま通るな
— Izumi Tsutsui (@tsutsuii) 2016年6月22日
修正したのは以下の 3点です。
- ソースの最後の方の
ORG 0F090H,0C800H
で始まる FADE 関連のコードを、
ソース先頭のORG 0F100H,0C000H
で始まるエントリポイントの前に移動
ZASMは ORG を無視して前から順にコードを置くようなので - 上述の
ORG
文についてそれぞれ 2つ目のオペランド (=出力コードの実配置アドレス?) を削除 - 演奏データを置くアドレスの OBJSAD について、実際に曲データを配置する位置でラベル定義
なお、曲データはとりあえず TINY野郎さんの MML2P6PSGDRV コンパイラ付属の YS2OP のリストからコンパイルして生成したものを使用したのですが、 PSG音源ドライバの曲データの先頭には曲データ自身のアドレスが埋め込まれているので、データ生成の際には実際に配置するアドレスを正しく入力してやる必要があります。
うーん。音源ドライバが上手く鳴らない
— Izumi Tsutsui (@tsutsuii) 2016年6月22日
データのアドレスが正しくないな(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年6月22日
あー。もしかして音源データの方にもアドレス埋め込まれてるのか
— Izumi Tsutsui (@tsutsuii) 2016年6月22日
というわけで、ちょっとハマりましたがわりとあっさりと
鳴りました。
— Izumi Tsutsui (@tsutsuii) 2016年6月22日
このときのメモリ配置ですが、グラフィックデータが結果として A000h〜CFFAh と絶妙にちょうどいいアドレスに収まったので、 以下のようにしました。
- A000h〜 : グラフィックデータ
- D000h〜 : PSG音源ドライバワーク
- D740h〜 : deexo.asm 展開ルーチン
- D800h〜 : PSG音源ドライバ曲データ
また、 deexo.asm で使用する 156バイトのバッファについては、もともと未使用だった 9F28h のテープロードルーチン以降の領域に取りました。
イース2のOP曲をバックに流れるTinyみずいろOPデモ、超シュールである(動画省略) pic.twitter.com/43Rz5LnESF
— Izumi Tsutsui (@tsutsuii) 2016年6月22日
なかなかにわかりづらいスクリーンショットですが、 PSG音源ドライバの 2ms エントリポイントである D1EDh (= MAIN ラベル) のアドレスでブレークしてから、演奏ルーチン本体の D1FBh (= MAIN1 ラベル) に飛んでいる、というところをトレースしたものです。 我ながらいま見ると自分でもわかりませんね……
CLOAD
— Izumi Tsutsui (@tsutsuii) 2016年6月22日
Ok
RUN
Now Loading....
HIT ANY KEY
☟ターン
→ チャラララー テテテ タンタンタンタン♫ とBGMが流れ出してデモ開始
まではできた。あとは曲データだけだ(それが無理やろという説)
ちなみに、 「Tinyみずいろ」を動かしてみよう つづき のエントリで紹介した互換BASICでのデモ動画でロードしてしまったのがこのときの YS2OP BGM のテスト用プログラムだったりします。
最終的には、PSGドライバの配置を変更しても音源データをリロケートできるようにドライバに以下の修正を入れました。
- 曲データ側の各パートのアドレステーブルには先頭からのオフセットを埋め込む仕様にする
- 曲データの絶対アドレスについてはドライバ側で OBJSAD のアドレスを使って計算する
Z80 の 16ビットレジスタのインクリメント (INC HL とか INC DE とか) は Zフラグや Cフラグには影響しなかったよなあ、などという遠い記憶の真偽を確認するには(ググれ)
— Izumi Tsutsui (@tsutsuii) 2016年6月23日
よっしゅさんのPSG音源ドライバのデータ部分は固定アドレスに置く仕様なのにどうしてデータの中にも絶対アドレスが含まれるのかと思ったけど、オフセットだけを入れてベースアドレスからの足し算をするとそれだけでドライバが10バイト増えてしまった件
— Izumi Tsutsui (@tsutsuii) 2016年6月23日
とりあえずかなりテキトーなコードですが、修正差分は以下のとおりです。
@@ -62,12 +150,21 @@
PSGIT:
- LDI
- LDI
+ LD BC,OBJSAD
+ LD A,(HL)
+ ADD A,C
+ LD (DE),A
+ INC HL
+ INC DE
+ LD A,(HL)
+ ADC A,B
+ LD (DE),A
+ INC HL
+ INC DE
PUSH HL
LD HL,PWD
LD BC,7
LDIR
「Tinyみずいろ」 BGMデータ
ここまでの作業で、当初の課題だった BGM および音源ドライバを入れるためのメモリの確保、 PSG音源ドライバそのものの組み込みも完了して、残るは BGM曲データの作成のみとなったわけですが、この作業についてはまたまた次回以降に続きます。