とりあえず何か作ってみたい
せっかく調子の良いHC-40が手元にあるんだから、何か作ってみたいなぁ…という気持ちになるのはふつーの事ですよね!
とはいうものの、たくさん時間が掛かるものは作りたくない。
ちょろっと何かを表示させてみたらいっか…というワガママな考え(^^;;
というわけで、何かの絵を描画するために必要な情報を書き出してみる。
HC-40スペック
とりあえずHC-40の基本的なスペックは以下の通り。
CPU: Z80A(3.58MHz)
RAM: 64KB
LCD: 240x64ドット
基本はCP/M80の上でBASICが動くカタチになっている。
メモリは64KBフルRAMモードで動いていて、BASIC-ROMは普段は裏側にある。
LCDへのデータ書き込み…というよりは、メインメモリの一部をVRAMとして使える。
そのため、CPUから見ると普通のメモリ書き替えなイメージで描画が可能。
CP/Mが動いているので豊富な開発環境が利用出来る…ようなイメージがあるが、実際には外部記憶装置がついてるわけじゃないので、ほぼオンメモリの開発となる。
今さらWordMasterなどで編集したいとは思わないので、プログラムをホストマシンで開発をし、それを送り込む方法で進める。
基本の開発言語はSDCC(C言語)で進めることにする。
速度の問題は後まわし。
まずは動かす事を優先させるって事で!
VRAMは0xE000と0xD800の2カ所にある。
通常は0xE000側が表示されてる。
PC-8801のような水平側VRAMで、1バイトが横8ドット対応になっている。
横が240ドットだけど、VRAM的には右側に未表示分16ドットがあるイメージで、メモリ的には1行が32バイトで計算が楽ちん!
I/Oポート0x08へ上位5ビットを出力する事で表示VRAMが切り替わる。
なるほど。
よし、とりあえずこれだけ分かれば絵は出せそう。
タロット占いの絵を動かす
過去にPC-8201で画面表示をする時に打ち込んだ、「マイコンゲームの本2」に掲載されていたMZ-80B用ソフト「タロット占い」のデータを流用させてもらっている。
MZ-80BのVRAMとはMSBとLSBが逆なので、これを逆にする必要がある。
これは外部のツールでちゃちゃっとやっちゃおう。
データは32ドット×48ドットのサイズがあり、合計192バイト。
表示するだけだとつまらないので、せっかくだから動かしてみたい。
狭い画面だから横は8ドット単位じゃなくて、ドット単位に動かしたいなぁ!
上のスペックにも書いた通り、1バイトが8ドット構成なハードなので、ドット単位に動かすためには少々頑張らないといけない。
VRAMも2枚あるので、交互切り替えしてチラつきなしで実現したい!
よし!やりたい事は大体見えてきた!
まずは動かす事が最優先!
普通に考えたら速度を気にしなくちゃいけない案件だと思いつつ、モチベーションを上げるために表示させることを最優先で進めてみる。
やっぱり画面が出ると「おおっ!」ってなるじゃないですか(^^)
何も考えずに8ドット境界で標示させる(これは簡単)
↓
ドットがずれた状態で表示させてみる(ちょっと考えれば簡単)
↓
タロットにそれぞれの動きを入れてみる
↓
VRAMを切り替えつつ表示をさせてみる
↓
画面クリアを追加
これで動いてる状態となる。
動いたよ…動いたけど……
HC-40でタロット表示!でっかいタロットが画面内をドット単位に移動しまくるぜ!…のハズだったのに予想通りの激重www
— PocketGriffon (@GriffonPocket) May 17, 2020
ちなみに2画面切り替えしてるから表示はちらつかないぞ(^ ^) pic.twitter.com/32OkGmrx0u
うん、激重。
予想はしていたけれども激重(^^;;
オールC言語で書いてるってのもあるけど!
でもここからが楽しい高速化じゃないですか!(楽しいですよね?)
高速化のその前に
高速化に手を付ける前に、やらないといけない事があります。
それは「正確な速度計測出来る方法」を調べないといけないです。
作ったプログラムを、毎回総クロック数を数えるわけにはいかないですし、動かしてみて「速くなった、変わらなかった、逆に遅くなった」が、感覚的ではなく、数値で見られる事が大事です。
ふつーに考えたら、タイマー割り込みの入った回数などを調べるんでしょうけれども、HC-40に都合の良い仕組みがあれば良いんだけどな…。
…と思いつつ調べていると、どうやらポート0,1に分解能の高いTick値が出てるっぽい。
2バイトの値で95.26msくらいまで計れるっぽいです。
これだけ幅があれば今回に限らずいろんな事に使えそう!
で、その計った数値を表示する仕組みが欲しい…と。
面倒なので、モニタを作る時に使ったフォントを使って表示する仕組みを作る。
よし、これで視覚的に高速化の効果を見ることが出来る!
16進数で表示されるのはご愛敬!(16進変換が面倒なんだもん)
高速化するよ!
上の写真の数字をみて驚愕!
この数字の意味するところは「1枚のタロットを表示するのに約30.8ms」掛かってるという事! …31ms?? 長年ソフト開発をしている自分にとって、それがどのくらい大きな数字なのかは分かるつもり…。ちょっとコレはさすがにですね(T_T)
まずは高速化の基本となるアルゴリズムの見直し……ですが、やってる事が単純なので見直す事があんまりないのが実情。
さっさとアセンブラ化をする方が良いかも。
画面クリアを考える
VRAMを2枚切り替えて表示してるということで、表示してる側と描画してる側の2つを管理する事になる。表示してる側は放置で良いけれども、描画する側はいろいろと忙しい。
まずは画面をクリアするコードを考えてみる。
実装を急ぎたかったので、最初は何も考えず1バイトごと消すコードを書いてた。
bzeroすれば一発だけど、標準ライブラリすらない状態!
最初のコードは上の写真でいうところの数字で「560D」、約30.15ms掛かった!
これもすごい数字だなぁ…と思いつつ、こちらはもうほぼ回答が出てる。
Z80でイチバン高速なクリアは「PUSHを並べる」だと…思う(^^;;
でもせっかくなので順を追って高速化してみた!
・2バイトごとにクリアコードを代入していく
→3777(17.41ms)
・アセンブラ化してLDIRでクリア
→1ECD(7.13ms)
・PUSH連続でクリア(ループ回数は128回)
→955(2.98ms)
ループ展開を0に近づければさらに速くなるけれども、やりすぎは良くないのでこの辺でやめときます!
タロットデータの描画を考える
横8ドットが1バイトとなったデータを、ドット単位にずらすプログラムが必要。
速度のみを追求するのであれば、あらかじめドット単位にずれたデータを用意すれば良いんだけど、それだと面白み+ロマンがない!
やっぱりリアルタイムにずらして描画しなくちゃ(^^)
元の数値はこんな感じ。
→57A3(30.81ms)
まずはなーんにも考えずにアセンブラ化してみる。これだけでも効果は高そう。
経験上、Z80のC言語でシフト系の命令を使うと悲惨なコードが出てくる。
これを手書きしてあげるだけでも速度はどどーんと上がる。
とはいうものの、シフト系は肝な部分なのでちゃんと考えておきたい。
横32ドットということで、32bitのデータがあり、これをシフトする。
32bit=4バイトなので、レジスタが4つあれば一連の流れで出来そう。
シフトしてずれて出てきたデータを拾うレジスタも必要で、全部で5つの8ビットレジスタが必要。
Z80はレジスタがたくさんあるのでなんとかなるけど、エレガントじゃないなー。
…で、いろいろと考えてみて、こんなコードに落ち着いた。
ADD | IX,IX |
ADC | HL,HL |
ADC | A,A |
基本的に左シフトの事しか考えてない。
IXレジスタにはタロットの右側16ドットのデータが入っており、キャリーは気にしなくても良い。IXのキャリーをHLで拾い、同じ理屈でAレジスタで拾う。
これで1回のシフトに掛かる時間は34サイクル。
これを最大7回繰り返すパターン(この描画で最悪のケース)で時間を計ってみる。
→1804(4.30ms)
うおお!一気に7倍以上になった!
…これでもういいんじゃね?(^^;;
あとは申し訳ない程度の高速化をしてみる。
・一部の命令をIXH、IXLのロード命令に置き換えてみる(未定義命令)
・プログラムの自己書き換えを行う
この2つを施して
→17C4(4.20ms)
うん、さすがにあんまり効果は高くない。
あとやるとしたらループ展開ですかね…。
ということで展開してみた。
→1507(3.06ms)
最終的にはこんな感じになりました。
先日ツイートしたHC-40プログラムのリベンジ!頑張って高速化した!このサイズの図形(32x48ドット)が、この速度でドット単位に動きまくるのってちょっと感動!とりあえずHC-40でのプログラムは満足したかも(^^) pic.twitter.com/08xfG9Xk04
— PocketGriffon (@GriffonPocket) May 19, 2020
久しぶりにZ80の高速化してみたけど、やっぱり楽しいですね!
RISC的なRxxのような数字のレジスタよりも、HL,BCなどの固有名詞があるレジスタの方が、なんとなく親しみが湧いてくる気がしてます!
また他のマシンでも頑張ります!