tsutsuiの作業記録置き場

NetBSDとかPC-6001とかの作業記録のうち、Twitterの140字では収まらない内容や記事としてまとめるべき内容をとりあえず置いてみる予定

「Tinyみずいろ」にBGMを追加するまで(3)

前回 からBGM追加作業の続きで、クロスアセンブラ環境作業についてです。

こういうのは後から思い出して書くのではなくて作業してすぐに書かないとダメね、と思う今日このごろ

deexo.asm


実作業でサクラエディタを使ったわけではないんですが、 asm でもシンタックスハイライトしてくれたのでスクショ用として採用

実装の前にコード設計、ということでまずは ExomizerZ80 用展開ルーチンの deexo.asm をチェックします。

前回も書いたように使い方のマニュアルなどというものは存在しないのですが、上記のように一応ソース先頭のコメントに使い方は書いてあります。コメントの通り

  • HLレジスタに圧縮データの先頭アドレスを入れる
  • DEレジスタに展開先バッファの先頭アドレスを入れる

のはいいとして、「どのルーチンをコールするのか」が書いてなかったりしますが、先頭の 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用アセンブラ) を使われていたようです

が、イマドキ(?)のアセンブラは ROM/RAM に直接配置できるようなコードを吐くのではなくリンカでロードするのが前提だったり、 include 文が使えなかったりで、結局 TINY野郎さんオススメの「Windows上でZASM」という方法に落ち着きました。メインの NetBSD機とは samba でやり取り

上記の TINY野郎さんによる 「アセンブラでソフトを開発しよう」 の解説は PC-6001 に限らず過去の Z80 マシン開発に使えるのでおすすめです。また、 ZASM では include 文が使用できるので、他から流用するアセンブラソースや生成データを別ソースのままにすることも簡単です。

Exomizer ルーチン改修

事前のコード設計(?)に従って、

……という具合に実装作業を進めたのですが、 ZASM で deexo.asm の展開ルーチンをアセンブルしようとすると一つ問題がありました。

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 でコードを書いているともう一つ問題がありました。


Tinyみずいろでは 9F00H, 9F28H, A000H と途中にすき間を空けつつコードを配置する必要があるので、コード開始アドレスを指定する ORG 擬似命令を複数置いたのですが、

なぜか ZASM が吐くバイナリを見ると ORG のアドレスを無視してすき間を詰めたバイナリが生成されてしまいます。

アセンブラの使い方で格闘するのは本題ではなかったのでとりあえず別のアセンブラを使って回避しようとしたりしたのですが、この問題の解決策は TINY野郎さんに教えていただきました。


こうして作ったバイナリを動かしてみると Exomizer 展開ルーチンでも問題なくちゃんとグラフィック表示できるのが確認できました。

このツイートの「ランレングス圧縮で DC1Eh まで」というのは最後の 00h 終端を忘れていた数字で、実際は DC1Fh が正解です

表示速度も問題ないというか、むしろちょうどよくなった感じです。


というわけで、次はいよいよ PSG音源ドライバを組み込んでみます。

PSG音源ドライバ組み込み

よっしゅさんの PSG音源ドライバ のソースは前述の通り PC-8801用のアセンブラ HIT-88 用に書かれているものですが、 当時の Z80 アセンブラの構文は先駆者(?)である CP/M の ZASM 準拠のものが多かったせいかアセンブラ書式の修正は不要で、かつ deexo.asm のような未定義命令もなかったため、ほぼそのまま ZASM でアセンブル可能でした。

修正したのは以下の 3点です。

  1. ソースの最後の方の ORG 0F090H,0C800H で始まる FADE 関連のコードを、
    ソース先頭の ORG 0F100H,0C000H で始まるエントリポイントの前に移動
    ZASMは ORG を無視して前から順にコードを置くようなので
  2. 上述の ORG 文についてそれぞれ 2つ目のオペランド (=出力コードの実配置アドレス?) を削除
  3. 演奏データを置くアドレスの OBJSAD について、実際に曲データを配置する位置でラベル定義

なお、曲データはとりあえず TINY野郎さんの MML2P6PSGDRV コンパイラ付属の YS2OP のリストからコンパイルして生成したものを使用したのですが、 PSG音源ドライバの曲データの先頭には曲データ自身のアドレスが埋め込まれているので、データ生成の際には実際に配置するアドレスを正しく入力してやる必要があります。

というわけで、ちょっとハマりましたがわりとあっさりと

所要時間1時間未満


このときのメモリ配置ですが、グラフィックデータが結果として A000h〜CFFAh と絶妙にちょうどいいアドレスに収まったので、 以下のようにしました。

  • A000h〜 : グラフィックデータ
  • D000h〜 : PSG音源ドライバワーク
  • D740h〜 : deexo.asm 展開ルーチン
  • D800h〜 : PSG音源ドライバ曲データ

また、 deexo.asm で使用する 156バイトのバッファについては、もともと未使用だった 9F28h のテープロードルーチン以降の領域に取りました。



なかなかにわかりづらいスクリーンショットですが、 PSG音源ドライバの 2ms エントリポイントである D1EDh (= MAIN ラベル) のアドレスでブレークしてから、演奏ルーチン本体の D1FBh (= MAIN1 ラベル) に飛んでいる、というところをトレースしたものです。 我ながらいま見ると自分でもわかりませんね……

ちなみに、 「Tinyみずいろ」を動かしてみよう つづき のエントリで紹介した互換BASICでのデモ動画でロードしてしまったのがこのときの YS2OP BGM のテスト用プログラムだったりします。


最終的には、PSGドライバの配置を変更しても音源データをリロケートできるようにドライバに以下の修正を入れました。

  • 曲データ側の各パートのアドレステーブルには先頭からのオフセットを埋め込む仕様にする
  • 曲データの絶対アドレスについてはドライバ側で OBJSAD のアドレスを使って計算する


とりあえずかなりテキトーなコードですが、修正差分は以下のとおりです。

@@ -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曲データの作成のみとなったわけですが、この作業についてはまたまた次回以降に続きます。