2026年07月01日

Z80での全メモリクリアプログラム


1.はじめに
 X(旧Twitter)で"Z80"の検索結果を眺めていたところ、興味深いお題を発見しました。
 このブログでも以前「Z80での全メモリ同一値設定処理」の記事で似たような問題をXで出したことがありますが、今回発見したお題は「0000Hから始まる最短の全メモリクリアプログラムを考えよ」という条件が付いています。



2.お題の解答例
 このお題の最短の解答が下記の7バイトのものです。PUSH HL と JP (HL) を使ったループでうまくメモリをクリアしていますね。2バイトずつ消していきますが、最後に LD L,H が残らないように工夫されています。



3.別な回答
 このお題はプログラムが長くてもいいのであれば割合簡単なのですが、上記の例のように7バイト以下にしようと思うと中々難問です。散歩中に思い付いたのが下記のプログラムです。ネット上でも殆ど見かけない Z80 のマシン語コードの特徴を利用したかなりトリッキーなものです。もう一つの効果として PUSH 命令を AND 命令化することでキャリーがクリアされるので2バイトで HL レジスタをクリアしています。※説明追記 2026/07/05
 今のところ6バイト以下のものは思い付いていません。

       
Z80メモリクリアプログラム(アセンブラ)  
                        ;+++++++++++++++++++++++++++++++++
                        ; Fill all memory with zeros.
                        ;  Ver 0.01 20260701 skyriver
                        ;+++++++++++++++++++++++++++++++++
                        
[0000] :                	ORG	0000H
                        
[0000] : E6 E9          Start:	AND	0E9H
[0002] : ED 62          	SBC	HL,HL
[0004] : F9             	LD	SP,HL
[0005] : 35             	DEC	(HL)
[0006] : E9             	JP	(HL)
                        
                        	END



posted by skyriver at 22:49| Comment(2) | Z80 | このブログの読者になる | 更新情報をチェックする

2026年06月25日

Z80用picle言語コンパイラの開発(その2)


1.はじめに
 前回の記事で書いたように、Z80用の独自言語であるpicle言語を今更ながら開発中で、それなりに動くようになってきました。
 前回の記事で書いたように将来的にはpicleコンパイラ自体をpicle言語で記述するセルフホスティングを実現したいと考えています。
 そのために従来の「PIC24用のpicleコンパイラ」に追加した機能等について記録しておきたいと思います。


2.従来の言語仕様に追加した機能
 セルフホスティングを実現する上で必要と思われる機能とコンパイラに追加した対処について以下に記載します。

2-1.必要な機能
 picle言語ではfuncで関数宣言することでリターン値を返すことができますが、コンパイラを構築するためには2個の返却値が必要な場面がありました。C言語の場合であれば引数をポインタ渡しにして引数の値を変更して返したり、構造体のポインタをリターン値として返すことで実現できますが、現状のpicle言語ではこのような機能はありません。

2-2.対処
  1. ポインタ渡し対応
     C言語のように変数のポインタを求める & 演算子に対応することで、関数への引数をポインタ渡しすることを可能にしました。また、引数のポインタの中身をアクセスする際にオフセット無しの配列(例 arg[0])の場合、オフセット演算をしないようにして高速化しました。
           
    サンプル1(picle言語)  
    :l
    
       1:# pointer argment sample
       2:# ver 0.01 2026/06/25 skyriver
       3:
       4:func hoge( a, b ) {
       5:   return = a[0] + b;
       6:   a[0] = a[0] + 1;
       7:}
       8:
       9:proc main() {
      10:   var arg;
      11:   arg = 123;
      12:   PrnStr_( "return value : " );
      13:   PrnDec_( hoge( &arg, 10 ) );
      14:   PrnStr_( "\narg : " );
      15:   PrnDec_( arg );
      16:}
    :run
    
    return value : 133
    arg : 124
    :
    

  2. リターン後の引数値の参照可
     上記の対策は引数をアクセスする際にZ80ではどうしてもオーバーヘッドが生じてしまうので、関数コール時にコール側が事前にPUSHする引数を関数リターン後に破棄する際、第一引数と第二引数のPOPをそれぞれDE,BCレジスタで行うようにしました。
     こうすることで関数内での引数の参照時のオーバーヘッドは生じず、関数コール元に戻った後に関数内での引数の最終値をasm()による埋め込みのマシン語を使うことで参照することが可能になります。
    ※追記 2026/07/13 高速化のため、引数が5個以上の場合はPOPせずにSPを加算処理するように変更
           
    サンプル2(picle言語)  
    :l
    
       1:# pointer argment sample2
       2:# ver 0.01 2026/06/25 skyriver
       3:
       4:func hoge( a, b ) {
       5:   return = a + b;
       6:   a = a + 1;
       7:}
       8:
       9:proc main() {
      10:   var retval, arg;
      11:   arg = 123;
      12:   retval = hoge( arg, 10 );
      13:   asm_($dd,$73,$fc,$dd,$72,$fb);  # LD (IX-4),E; LD (IX-5),D
      14:   PrnStr_( "return value : " );
      15:   PrnDec_( retval );
      16:   PrnStr_( "\narg : " );
      17:   PrnDec_( arg );
      18:}
    :run
    
    return value : 133
    arg : 124
    :
    


3.高速化の改善
 今回、Z80用のpicle言語コンパイラを作成するにあたり、高速化した点をメモしておきます。現在開発中のコンパイラはメモリ上のソースをコンパイルし、即実行する運用を想定しているのでメモリ上にはコンパイラとコンパイルしたコードが共存します。このためコンパイラのサイズをなるべく小さくしたいので改善内容を精査し、下記の項目だけに留めています。
  1. for ループのコンパイル
     前回の記事にも書きましたが for 文のコンパイルではコードの生成順を入れ替えることでループ内のジャンプ命令が一つになるようにしています。

  2. オフセット無しの配列のアクセス
     上記で記載したようにオフセット無しの配列(例 arg[0])のアクセス時はオフセット計算をしないようにしました。

  3. 変数のインクリメント/ディクリメント
     for 文等で使用頻度の高い変数のインクリメント(例 i=i+1)とディクリメント(例 i=i-1)はマシン語の INC HL/DEC HL にコンパイルするようにしました。


4.コンパイル時間
 開発中のZ80用独自言語のpicleはrun一発で実行できるインタプリタのようなコンパイラを目指しています。下の動画はX(旧Twitter)のメッセージに貼ったもので、固定小数点の乗算を使った asciiart(マンデルブロ)を表示するプログラムの実行状況です。
 runコマンド入力直後から表示が始まるまでの一瞬の待ち時間がコンパイルしている時間で、オンメモリでの1パスコンパイルなのでコンパイルは想定通りの速さです(この時の環境はZ80 12.5MHz)。



5.ベンチマーク
 「AKI-80用モニターROM&Cコンパイラーセットの購入(その2)」の記事で書いたベンチマーク2を実行して比較してみました。動作環境はZ80がwait無しで20MHzで動作するCP/M環境です。
 今回のコンパイラはコンパイルがかなり高速ですがその反面、高速化対策はあまり注力していません。しかし、下表のベンチマーク結果表を見ると BDS C はもとより Z88DK よりも高速だったのは予想外な結果でした。

整数処理ベンチマーク2実行結果
※変更 2026/07/13 若干高速化(80.6s → 78.2s)

 picleでベンチマークプログラムを実行した画面のキャプチャも貼っておきます。

picleでのベンチマーク2実行画面


6.おわりに
 今回開発中の Z80 用の picle 言語コンパイラはコンパイル速度を速くするために1パスコンパイル方式にし、上述のようにコンパイル速度は想定通り速くできました。更にコンパイルしたコードに関しても上述のベンチマークで確認したように想定以上の結果になりました。
 今後は気が向いた時にセルフホスティング化していきたいと思います。
 また、コンパイル結果をファイル化して独立した実行ファイルを生成できるようにしたり、ポケコン等で使われている特殊なCPUに対応する等、その気さえあれば色々な展開が可能だと思います。



[TOP] [ 前へ ] Z80用picle言語コンパイラの開発 [ 次へ ]

posted by skyriver at 21:37| Comment(3) | Z80 | このブログの読者になる | 更新情報をチェックする

2026年05月21日

Z80用picle言語コンパイラの開発


1.はじめに
 ソフトウェアを作ることが好きな人であれば誰しも一度はプログラミング言語を作ってみたいと思ったことがあるのではないでしょうか?
 「TAKE IT EASY」のコラムに書かれているように「プログラミング言語の制作は"全てを把握できる世界"の創造主になる」ことかもしれません。
 以前開発した PIC24 用の picle コンパイラの Z80 版を作ってみることにしました。
 PIC 版ではフラッシュメモリへの書込み回数を減らすため2パス方式にしていましたが、Z80 版では生成したコードを直接メモリに書き込む1パス処理にしたいと思います。
 "run" コマンド1発での「コンパイル&Go」を実現します。


2.構想
 何故、古の Z80 用のコンパイラを作ろうと思ったかというと
  • 1パス処理の実現
     コンパイル時の使用メモリを殆ど増やさずに1パス処理できそうなやり方を思いついたので実現性と効果を確認したいため
  • picle 言語の実用性の確認
     今回のコンパイラはC言語で開発していますが、完成後に picle 言語自身で記述し直したい(セルフホスティングの実現)と考えています。コンパイラを記述できればある程度は汎用的な処理を記述できると言えます。
    ※コンパクトに仕上げるには機能を絞る必要があるが低機能すぎるとセルフホスティング困難という悩ましさがある
  • 他の CPU への移植性
     特に自作の CPU 等ではセルフコンパイラの開発はアセンブラレベルでの開発になり、手間がかかります。そこで、セルフホスティングしたコンパイラがあればクロスコンパイラ化することで容易にセルフコンパイラを構築でき、ブートストラップ的な機能追加も可能になります。


3.スタックフレーム管理
 Z80はスタック関連の機能が貧弱で遅いのですが、言語として再帰呼出し機能がないとセルフホスティングの手間が膨大になってしまうので従来通り再帰呼出しに対応することにしました。
 処理/関数呼出し時の処理の概要を次のようにします。

【関数呼出し時の処理】
  1. 引数を左側から2バイト単位でスタックに PUSH
    インテルの呼び出し規約(Intel ABI)秋月Cコンパイラでは引数は右からPUSHされる
  2. CALL 命令で関数呼び出し
  3. 呼び出し関数からリターン後、POP を使ってスタック上の引数領域を解放
  4. 関数呼び出しの場合、HL レジスタにリターン値が設定される

【関数側の処理】
  1. フレームスタックレジスタ(IX)をスタックに PUSH
  2. 関数の場合はリターン値用に 0000h を PUSH
  3. 引数とオート変数アクセス用の IX に SP と同値を設定
  4. オート変数分 SP 値を削減
  5. 関数の処理を実行
  6. SP に IX の値を入れる
  7. 関数の場合は POP HL でリターン値を HL に設定
  8. POP IX でIXの値を復帰
  9. RET で呼び出し元に戻る

処理(proc)のスタックフレーム


関数(func)のスタックフレーム



4.実装状況
 それでは現状の実装状況について以降にメモしておきます。


4-1."hello, world" の表示
 Z80 用コンパイラのコーディングが一通り完了(ディバッグは未)し、はじめて"hello, world"が表示された時の画面のキャプチャが下図の左側になります。ソース中で名前の末尾がアンダースコアな関数は組み込み関数です。また、ニーモニックはディバッグのための表示で
  LD BC,705B
  CALL 0000
等の部分は表示後にアドレスが設定されます。1パスなのでニーモニック表示時点では未確定なためです。
 下図の右側はコンパイル直後にメモリ上にあるコードを ZSID で逆アセンブル表示したものです。参考として picle 言語のソースも貼り付けました。上記のスタックフレーム管理を踏まえて眺めると問題なくコンパイルされていることが判ります。

はじめての "Hello, world"
ZSID での逆アセンブル表示


4-2.fibonacci 数列の表示
 "for"や"if"文等が動くようになったので再帰呼出しで fibonacci 数列を表示してみました。for 文のコンパイルではコードの生成順を入れ替えることでループ内のジャンプ命令が一つになるようにしました。
 下図の左側がまだ最適化未対応の今回のコンパイラです。環境は no wait 12.5MHz の Z80(TMPZ84Compact)です。TeraTerm のマクロで時間を計測(コンパイル時間も含む)しています。
 右側は GAME 言語で再起呼出しするソースをコンパイル後、起動した結果です。上記のフレームワーク管理と同様な処理を配列を使って実現しています。このソースを見ると再帰処理により、再帰呼出しが膨大に発生する状況をより実感できますね。
 因みに GAME 言語インタープリタではサブルーチンスタックが直ぐにオーバーフローして実行できませんでした。
★追記 2026/05/22 CP/M用のGAME言語はここからダウンロードできます。

Fibonacci 数列の表示(picle言語)
Fibonacci 数列の表示(GAME言語)


4-3.固定小数点での ASCIIART
 複数あり得る "break" の箇所に前方参照のジャンプ命令を埋め込む(ループ処理が入れ子の場合も考慮)という1パス処理では悩ましい処理ですが、想定通り実装できました。"break" 機能が動作するようになったので asciiart を動かしてみました。コンパイラで扱う変数型は2バイト整数ですが2バイトの固定小数点乗算関数を用意すれば加減算処理は整数のものを利用できます。変数はアクセスが速いグローバル変数を使用してみました。

       
ASCIIART(picle言語)  
# ASCIIART in the picle compiler
#  ver 0.01 2026/05/20 skyriver
# https://piclabo.seesaa.net/article/GameAsciiart.html

var X,Y,I;
var A,B,Ca,Cb,T;
var _Hex;

# Fixed-point multiplication
func fmul( X, Y ) {
    asm_($DD,$5E,$08,$DD,$56,$09,$DD,$6E);
    asm_($06,$DD,$66,$07,$01,$00,$0F,$CB);
    asm_($7C,$28,$07,$0C,$AF,$95,$6F,$9C);
    asm_($95,$67,$EB,$29,$30,$07,$0C,$AF);
    asm_($95,$6F,$9C,$95,$67,$CB,$19,$08);
    asm_($4C,$7D,$21,$00,$00,$29,$17,$CB);
    asm_($11,$30,$03,$19,$CE,$00,$10,$F5);
    asm_($6C,$67,$08,$30,$06,$AF,$95,$6F);
    asm_($9C,$95,$67,$DD,$F9,$D1,$DD,$E1);
    asm_($C9);
}


proc main() {
    _Hex = "0123456789ABCDEF ";
    for ( Y = -12; Y < 13; Y = Y + 1 ) {
        for ( X = -39; X < 40; X = X + 1 ) {
            Ca = fmul( $0bb9, X );  # 0.0458f * X
            Cb = fmul( $1555, Y );  # 0.08333f * Y
            A = Ca;
            B = Cb;
            for ( I = 0; I < 16; I=I+1 ) {
                T = fmul( A + B, A - B ) + Ca;
                B = fmul( A, B );
                B = B + B + Cb;
                A = T;
                if ( (fmul( A, A ) + fmul( B, B )) > (4 * 256) ) {
                    break;
                }
            }
            PrnChar_( _Hex[I] );
        }
        PrnChar_( 10 );
    }
}

 下図は実行後の画面キャプチャです。TeraTerm のマクロで実行時間を計測しています。

ASCIIART 実行画面

 今回の試験は CP/M 上で行っているので当然 MSXPen でも動作しました。

MSXPen 上で実行中の ASCIIART

★追記 2026/06/27 {
 プロトタイプ宣言と外部処理定義のテスト。1パスコンパイラなのでプロトタイプ処理の実体のコードに遭遇した時にアドレスが決定し、この時前方参照してコールしている箇所のアドレスも設定している。外部処理のGoCpm()を実行することでCP/Mに戻っている。

プロトタイプ宣言と外部処理定義のテスト
}


5.おわりに
 まだ未完成ではありますが、Z80 用の picle 言語コンパイラが何とか動作するようになってきました。気が向いた時に作業を進め、冒頭に書いた構想の実現に向けて進めていきたいと思います。



[TOP] [ 前へ ] Z80用picle言語コンパイラの開発 [ 次へ ]

posted by skyriver at 20:48| Comment(0) | Z80 | このブログの読者になる | 更新情報をチェックする