先日 X (Twitter) でこんなポストを頂きました。
※充電池に関してはパナソニックは優秀ですよね。そろそろうちのも買い替えなきゃいけなさそう
※ 20231001-1345 更新
PUTCHR:の最後ですが、このリストでは sp を戻していない状態でしたので、jp では正常に動作しません。ret として、続いてタスクリストからジャンプ先を拾うのが正しい動作となります。大変失礼しました。
これで処理中はスタックが正常化されてますので、普通に push, pop, call を使用できるようになります。割り込みが掛かっても何ら問題ありません。
※4本なら急速充電8本なら通常充電。まとめて8本は有り難いかもしれない。あと、モバイルバッテリーとしても使えるらしいので、停電時には便利かも。
これは JP (HL) の動作を取り違えているので、残念ながらループすることはありません(おそらく暴走します)が、これをヒントとして、私が Ys'II デモで使っているタスク管理について、説明していきたいと思います。Z80関連の情報を収集していたら、内藤さん(@NAITOTokihiro)の記事がヒット😃https://t.co/JcQKSeTLpn
— D.M.88 (@DM46374635) September 29, 2023
ループは速い方が嬉しいですよね😆
ということで、自分も考えてみました☺️
数値上はDJNZより速い12clockですけれど、メモリーウェイトは如何程に影響あるものなのでしょうか…🤔 pic.twitter.com/fotBvfVn3Y
- 超高速ループ
さて、先に挑もうとしてた高速ループの基本的な考え方は、ループを連続ジャンプとして捉えて、動作中はループの先頭に、そして最後はループの最後に移動させようという試みでした。所謂テーブル参照ジャンプです。メモリにジャンプ先が格納されていて、そこを参照しながらジャンプする動作って、何か思いつきませんか?
そう、スタックです。call するとスタックに戻り番地が積まれ、ret でスタックからアドレスが取得されて PC(プログラムカウンタ)に設定されます。これをループに使ってみましょう。プログラムをドンッ!
COUNT equ 5 org $C000 Main: ld hl, .jptbl - COUNT * 2 ld (.exit + 1), sp di ld sp, hl .loop ; 処理 ret .exit ld sp, 0 ei ret REPT COUNT dw Main.loop
ENDM dw Main.exit
.jptbl
ループ回数は COUNT で定義しています。どうせ固定値を与えるのですから、アドレス計算はアセンブラに任せています。そのアドレスを sp に入れて…しまうと、普通の PC は割り込みが掛かった途端におかしなことになるので、先んじて割り込み禁止 di を掛けておきます※。適当な処理を行った後に、いきなり ret とすると、ループ中は .loop ラベルに戻りますが、ループ終了で .exit に抜けてきます。
※ PC-8001 は通常は割り込みがないので di と ei は不要です。
ret だけでループが制御できていますし、なんならアライメントも適当で良いですし、256以上のループ回数でも速度低下はありません。一見、良いことづくめに見えますが、大きな問題点があります。それは、ループ中にスタックを使う処理が一切使えないことです。push, pop, call, ret は使えなくなります。さらに割込み禁止状態ですので、長時間の処理はできなくなります。その欠点を理解した上でなら、使い所はあるかもですねー
パナソニック
2023-04-25
- タスク管理
これを
call PRCSA call PRCSB call PRCSC call PRCSC jp GameEnd
こう書いたら、メインループは短くて済むよねと。でも、このままだと、サブルーチンに任意のパラメータを受け渡す事ができません。例えば、PUTCHR という描画ルーチンがあったとして、HL = XY座標、DE = 描画データと設定が必要な場合はどうするかと。それも実は簡単です。なぜならタスクを管理しているのはスタックなのです。pop という便利な命令があるではありませんか。
ld sp, .task ret .task dw PRCSA dw PRCSB dw PRCSC dw PRCSC
dw GameEnd
これで、PUTCHR に対するパラメータを自在に調整できるようになります。さて、長期間の割り込み禁止はとてもマズイので、これを解消するようにします。各タスク処理中は本来のスタックアドレスに戻せば解決します。仮に本来のスタックがアドレスの最後尾にあるとしたら、各処理の先頭でスタックの初期化を行うようにするだけです。
Main: di ld sp, .task ret .exit ei jp GameEnd .task dw INIT dw PUTCHR, $1005, GRPPLAYER, dw PUTCHR, $2005, GRPENEMY1 dw PUTCHR, $3010, GRPENEMY2 dw .exit PUTCHR: pop hl pop de ; 処理 ret
※ 20231001-1345 更新
PUTCHR:の最後ですが、このリストでは sp を戻していない状態でしたので、jp では正常に動作しません。ret として、続いてタスクリストからジャンプ先を拾うのが正しい動作となります。大変失礼しました。
これで処理中はスタックが正常化されてますので、普通に push, pop, call を使用できるようになります。割り込みが掛かっても何ら問題ありません。
スタックの初期化はどこでも同じ書き方になりますから、マクロ化してしまうと見やすくなります。
STACK equ 0 ld hl, .task ld (Main.prog), hl Main: di .prog eqe $ + 1 ld sp, .task ret .exit ei jp GameEnd .task dw INIT dw PUTCHR, $1005, GRPPLAYER, dw PUTCHR, $2005, GRPENEMY1 dw PUTCHR, $3010, GRPENEMY2 dw .exit PUTCHR: pop hl pop de ld (Main.prog), sp ld sp, STACK ei ; 処理 jp Main INIT: ld (Main.prog), sp ld sp, STACK ei ; 処理 jp Main
このように設定しておけば、
TSTART MACRO ld (Main.prog), sp ld sp, STACK ei ENDM
このように簡単に記述することが出来ます。このタスク管理技法最大の利点は、処理の流れを全てデータとして記述できることです。オートデモみたいな処理には最適です。皆さんの手によりさらなる改良が加えられることを期待しています。
PUTCHR: pop hl pop de TSTART ; 処理 jp Main INIT: TSTART ; 処理 jp Main
パナソニック(Panasonic)
2022-04-25
コメント
コメント一覧 (2)
タスク管理の3つ目のコード、PUTCHR: の最後は jp Main でなく ret ではないでしょうか。
4つ目以降は jp Main で問題ありませんが。
内藤時浩
が
しました