正月からMSXのZ80アセンブラを書いていた

あけましておめでとうございます。
どこぞに、正月3日に起こった出来事が1年を決めるという話が流れてましたが、そうすると今年は1年、夜中にZ80アセンブラを書いて昼間寝る感じになるんでしょうか・・・
書いてたのは、こんな感じで誤差拡散でカラーテーブルを表示するプログラムです。

まずは素直なカラーテーブル

最初、年末に何を思ったかこんな感じのカラーテーブル表示プログラムを作りました。MSX2は赤緑8階調、青4階調の256色を同時表示できていたので、それを表示するとこうなるのです。

100 DEFINT A-Z
110 SCREEN 8
120 FOR I=0 TO 15
130 R=(I MOD 8)*32
140 B1=INT(I/8)
150 FOR J=0 TO 15
160 LINE (I*16,J*13)-(I*16+15,J*13+12),R+B1+(J MOD 8)*4+INT(J/8)*2,BF
170 NEXT J
180 NEXT I
190 A$=INPUT$(1)


実際の動きをWebMSXで確認できます。
カラーテーブル on WebMSX

BASIC版誤差拡散

で、そうするといまいまいまさのぶという人からこういうツッコミが。


誤差拡散では、こんな感じで誤差を散らします。

描画点 7/16
3/16 5/16 1/16


じゃあ作ってみるかと思って作り始めたものの、これがつらい。
何がつらいって、まず編集がつらい。行番号を入力して該当行を編集するという謎方式でのコード入力になるのだけど、行の挿入や削除がかなり面倒です。確認もLISTコマンドでいちいち表示させる必要があります。MSXからプログラムを始めたので当時はこれが当たり前だと思っていたのだけど、今やると本当に不可解で、よくこの入力方法が理解できたなという感じです。
あと、画面が小さすぎてつらい。MSX1のときで40x24文字、MSX2で80x24文字しか表示できないのです。しかもリストはスクロールもできなくて、該当行番号を指定してLISTコマンドで表示です。
さらに言語がつらい。変数名は2文字まで。まあ今回はそんなに複雑なプログラムじゃなかったのでいいものの、IF文も一行完結の必要があったり、関数定義できなかったり。


そして最後に実行速度がつらい。ひととおりコードを入力して動かしてみたら、全然描画が進まない。1ラインの描画に40秒くらいかかります。ただしく描画できてるかどうか確認するのに、派手に間違えているときならすぐわかるにしても30秒くらい、細かく間違えていたら10分くらい動かしてようやくわかるレベルです。もちろん試行錯誤も進みません。
ということで結局Javaで等価なコードを書いて動きを検証してからBASICのほうを修正しました。
Javaのコードはこれ。
https://github.com/kishida/msxdiffusion/blob/master/src/main/java/kis/diffusion/Diffusion.java


動かして、2時間かかることは予測できていたので、実行しながら風呂に入って出てみると全然進んでない。Firefoxは別タブで隠れたら実行が止まるらしい。。。
気を取り直して実行開始して寝る。起きたら「Illegal Function Call」のエラー。。。
修正して、時間計測コードも仕込んで、実行して寝ると、今度こそちゃんと表示されてました。


コードはこんな感じに。
https://github.com/kishida/msxdiffusion/blob/master/msx/COLORS2.BAS
WebMSXで実行すると、遅さが体感できます。
誤差拡散BASIC版 on WebMSX

マシン語版誤差拡散

BASIC版のあまりの遅さに、ちょっとこれはマシン語でやってみるとどうなんだろうという疑問が頭をもたげて離れないので、やってみることに。とはいえ、MSX上でコードを書いてアセンブルしてというのは今更さすがに無理なので、Windows上で動くZ80アセンブラを探しました。
http://www.48k.ca/zmac.html


しかし、Z80というのは計算に使えるのがAレジスタしかないし、掛け算も割り算もないし、結構つらい。あと、点を描画するのに、IOをいじってVRAMを書き込む必要があって、VRAMの書き込み方やVRAMの構造を調べる必要もあります。
なんとか全体が動くコードを書いて動かすと、やっぱ変な感じに。

VRAMアドレスを計算するために64で割るときに、右に7回シフトしていたのが敗因。


で、いろいろがんばってたら、こんな感じに。だいぶ近づいた。


最終的にちゃんと表示されました。


2時間26分かかっていたのが50秒に。175倍の高速化です。しかし2日の実装期間。。。
あとは、割り算を引き放し法で高速化して45秒に、裏レジスタを使ってメモリアクセスを減らして43秒に、と最適化して、最終的にこうなりました。
https://github.com/kishida/msxdiffusion/blob/master/msx/loop.mac
ちょっとした工夫で速くなるのは楽しいです。Javaのコードだと、ちょっとした工夫はすでに最適化で行われているので、なかなか細かいところをいじるメリットはないですけど、Z80は書いたコードを正直に実行するので、ちょっとした工夫でもちゃんと処理時間に反映されて、これはこれで楽しいです。
1/9:追記 その後、数値範囲のビット数にあわせて割り算を最適化すると35秒を切りました
1/13:追記 さらに無駄な処理を省くことで30秒を切りました。
1/14:追記 さらに最適化して29秒を切りました。


ここで動かせます。
誤差拡散マシン語版 on WebMSX


1/9:追記
最初のアセンブラバージョンと比べると、かなり速くなっているのがわかります。
誤差拡散マシン語版初期 on WebMSX

2020/5/8 追記
MSX Turbo Rが来ていたので試すと5秒!
誤差拡散マシン語版 on WebMSX


ということで、たまにZ80アセンブラを書いてみると楽しい、という話でした。
ただ、昔と違って、資料をWebで調べながらコードが書けるし、エディタは文字をたくさん表示できるし、アセンブルも速いし、断然にコード書きやすくなっていますね。おそらく、MSX上でコードを書いていると、もっと動かすのに時間がかかっていたと思います。


このときのツイートはこっちのモーメントにもまとめています。
https://twitter.com/i/moments/948840407013638145