HC-40でプログラム開発してみた その3

とりあえず何か作ってみたい

せっかく調子の良い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を切り替えつつ表示をさせてみる

  ↓

 画面クリアを追加

 

これで動いてる状態となる。

動いたよ…動いたけど……

うん、激重。

予想はしていたけれども激重(^^;;

オールC言語で書いてるってのもあるけど!

でもここからが楽しい高速化じゃないですか!(楽しいですよね?)

 

高速化のその前に

高速化に手を付ける前に、やらないといけない事があります。

それは「正確な速度計測出来る方法」を調べないといけないです。

作ったプログラムを、毎回総クロック数を数えるわけにはいかないですし、動かしてみて「速くなった、変わらなかった、逆に遅くなった」が、感覚的ではなく、数値で見られる事が大事です。

ふつーに考えたら、タイマー割り込みの入った回数などを調べるんでしょうけれども、HC-40に都合の良い仕組みがあれば良いんだけどな…。


…と思いつつ調べていると、どうやらポート0,1に分解能の高いTick値が出てるっぽい。

2バイトの値で95.26msくらいまで計れるっぽいです。

これだけ幅があれば今回に限らずいろんな事に使えそう!

 

で、その計った数値を表示する仕組みが欲しい…と。

面倒なので、モニタを作る時に使ったフォントを使って表示する仕組みを作る。

よし、これで視覚的に高速化の効果を見ることが出来る!

16進数で表示されるのはご愛敬!(16進変換が面倒なんだもん)

f:id:PocketGriffon:20200520100016j:plain

高速化するよ!

上の写真の数字をみて驚愕!

この数字の意味するところは「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)

 

最終的にはこんな感じになりました。

 

 久しぶりにZ80の高速化してみたけど、やっぱり楽しいですね!

RISC的なRxxのような数字のレジスタよりも、HL,BCなどの固有名詞があるレジスタの方が、なんとなく親しみが湧いてくる気がしてます!

また他のマシンでも頑張ります!