dgrfactory / spcplay

SNES SPC700 Player + Improved SNESAPU.DLL
https://dgrfactory.jp/spcplay/
GNU General Public License v2.0
142 stars 7 forks source link

SNESAPUCallbackProc で直前の命令サイクルぴったりで終了したことにする機能が欲しい。 #19

Closed GodGnilda closed 4 years ago

GodGnilda commented 4 years ago

機能追加要望

SetSPCDbg または SNESAPUCallbackProc に SetSPCDbg の SPC_RETURN の機能と更に SPC700.asm の clkLeft を 0 にするフラグが欲しい。

背景

SPC700 のステップ実行やブレークポイントを実装すると、想定外の動作のためどうしても避けることができない不具合が発生してしまうのを回避するため。 SNESAPUCallbackProc (effect = CBE_S700FCH) の戻り値では時間が経過しない。
→ SetSPCDbg (SetSPCDbg= SPC_TRACE | SPC_RETURN) では clkLeft が 0 にならないので、SeekAPU (time = 64000) を実行し 0.5 秒経過後に SPC_RETURN すると 次の SeekAPU (time = 64000) が SeekAPU (time = 96000) になってしまう。
→ 1 命令ずつ実行で回避可能になるが、DSP エミュレーションが(一部 ?)実行されない ?
dgrfactory commented 4 years ago

@GodGnilda さん このたびは機能追加のご要望をいただき、誠にありがとうございます。

SNESAPUCallbackProc (effect = CBE_S700FCH) の戻り値では時間が経過しない。

「時間が経過しない」とのことでございますが、こちらはタイマーや t64Cnt が進まないとの解釈でよろしいでしょうか。 戻り値の下位 8bit を 0x02 に変更することで、命令を実行せずに時間を進めることは可能でございますが、この方法は使用できない、もしくは想定されている動作と異なっておりますでしょうか。

→ SetSPCDbg (SetSPCDbg= SPC_TRACE | SPC_RETURN) では clkLeft が 0 にならないので、SeekAPU (time = 64000) を実行し 0.5 秒経過後に SPC_RETURN すると 次の SeekAPU (time = 64000) が SeekAPU (time = 96000) になってしまう。

「0.5秒経過後」は EmuAPU を 0.5 秒分実行との解釈でよろしいでしょうか。 SetSPCDbg で SPC_TRACE | SPC_RETURN を設定後、SeekAPU(time = 64000) → EmuAPU(length = 0.5sec) → SeekAPU(time = 64000) を行いましたところ、想定通り 2.5 秒後(最初の Seek + EmuAPU + 2回目の Seek)となり、事象を再現することができませんでした。 お手数をおかけして恐れ入りますが、再現手順をお知らせいただけますでしょうか。

どうぞ、よろしくお願いいたします。

GodGnilda commented 4 years ago

@dgrfactory 様 前回は誤解を招く表現などで間違った情報をお伝えしてしまい、申し訳ございません。

SNESAPUCallbackProc (effect = CBE_S700FCH) の戻り値では時間が経過しない。

改良版 snesapu.dll の SeekAPU (fast = 0) と SNESAPUCallbackProc (effect = CBE_S700FCH) を利用してステップイン機能を実現しようとしていた頃のことですが、当時は下位 8bit を 0x01 に変更することでその命令の時間だけタイマーが進まないと勘違いしてはまったことがありました。
下位 8bit を 0x02 に変更し他場合は命令を実行せずにタイマーを進めることができますが、 NOP の時間ではなく本来実行した場合に必要なサイクル数程度 [*1] の時間が経過することを 66c065a にて確認いたしました。本来実行しないはずの命令の分まで時間が経過しますので、ステップイン機能を実現することは私にはできなそうです。
[*1] SPC ファイルを読み込み後に 下位 8bit を 0x02 に変更するだけのコールバック関数で連続で停止させると、CPU サイクル数が奇数の命令で 24 APU サイクル分早く t64Cnt がカウントアップしているのを確認いたしました。
コールバック関数で連続で停止させた場合の t64Cnt の変化

→ SetSPCDbg (SetSPCDbg= SPC_TRACE | SPC_RETURN) では clkLeft が 0 にならないので...

SetSPCDbg= ではなく opts = かつ pTrace に nullptr 以外を指定した状態ですね。大変失礼いたしました。

SetSPCDbg はネットで検索をしても不明な状態でしたので、SPC700.asm で仕様を確認いたしました。
SPC700.asm の [pDebug] が nullptr ではない状態で [opts] に SPC_TRACE と SPC_RETURN がセットされていますと Opcode Fetcher (SPC700.asm ラベル SPCTrace ~) で tracing routine 呼出し後に SPCTimers にジャンプしますので、SetSPCDbg(pTrace = tracing routine, opts = SPC_TRACE | SPC_RETURN) 設定後に SeekAPU や EmuAPU を実行しても命令を実行せず時間も進めない事が可能であることを 66c065a にて再度確認いたしました。
ステップイン機能を実現するために tracing routine で 1 命令実行後(2 回目の呼び出し時)に SetSPCDbg (pTrace = tracing routine, opts = SPC_TRACE | SPC_RETURN) を実行し、SeekAPU を 1 命令で中断させ、CPU の命令サイクル数を意識することなく必要最小限の APU サイクル数で確実に停止させることはできるようになりましたが、SetSPCDbg (pTrace = tracing routine, opts = SPC_TRACE) で SPC_RETURN を無効にした状態でステップインではなく普通に SeekAPU [*2] を実行しますと、[clkLeft] が中断された残りの時間すべてを加算した時間の分の値になっていますので、[*2] で設定された値より長い時間が経過してしまいます。

背景の詳細は以上です。

現在の仕様ですと、SetSPCDbg 以外で確実に次の命令に必要な APU サイクル数を 0 にする方法を思いつきませんでした。ただ今の仕様のままですと次の命令を実行するまでの待ち時間(サイクル数)を確認する方法がなさそうですので、[clkLeft] を 0 にすることができれば他の方がステップイン機能等を実現させようとした場合や内部チェックでエミュレーションを中断させたときの正しい時間を確認することが楽になるのではないかと思いまして、機能追加をお願いいたしました。

長文失礼しました。

dgrfactory commented 4 years ago

@GodGnilda さん 詳細な情報をいただき、誠にありがとうございます。 また、動作検証等でたいへんお手数をおかけしましたこと、申し訳ありませんでした。

本件につきまして、下記の通り認識をいたしました。 内容について齟齬がございましたら、遠慮なくご指摘をお願いいたします。


■最終目的

■現状の問題点

■ご要望


上記認識に間違いがない前提として、回答申し上げます。

まず SetSPCDbg の動作についてですが、恥ずかしながら私自身使用したことがない機能であることと、ご存じの通りはっきりとした仕様記載のドキュメントが見当たらなく、 原作者の意図や互換性考慮を含めますと、こちらの動作は変更しないままとし、SNESAPUCallbackProc 側の動作を変更することが最善と思われますため、以下の対応を行いました。

上記動作仕様の変更により、ステップイン実行は、以下のプロセスで実現可能と思います。

  1. 処理を停止したいタイミングで、SNESAPUCallbackProc の戻り値として、下位 8bit に 0x01(FCH_HALT)を代入
  2. ステップイン実行したい場合、EmuAPU または SeekAPU を呼び出す
  3. 初回の SNESAPUCallbackProc では、戻り値を変えずに返却して 1命令分実行、 その次の SNESAPUCallbackProc では、処理を止めるために再び 0x01(FCH_HALT)を代入 → 残りの処理はキャンセル

処理としては少し複雑となってしまい恐縮ですが、上記方法にて実現可能であるかどうか、ご検討いただきたく存じます。 もし、まだ想定と異なる動作となっております場合は、お気軽にお問い合わせください。

本修正を含む SNESAPU.DLL は、以下よりダウンロード可能です。 snesapu-v2.18.2.7141.zip

どうぞ、よろしくお願いいたします。

GodGnilda commented 4 years ago

@dgrfactory 様
早速に、ご返信恐れ入ります。
こちらこそ簡潔にお伝えできず、たいへんお手数をおかけしまして申し訳ありませんでした。

処理としては少し複雑となってしまい恐縮ですが

以前はそのように処理していましたので、まったく問題ございません。

リビジョン 983d93e , 4d26323 の SeekAPU にて動作を確認いたしましたが、1 命令実行で time に設定した値が増える状態でした。
更にソースを確認いたしましたところ、私のお願いがおかしいため想定通りの動作と異なる状態になっていると判断いたしました。


仕様 (2,18,1,6862 のソースコードをチェック) - APU.asm - EmuAPU (void **pBuf*, u32 *len*, u8 *type*) - *`cycLeft`* - `SPC700.asm` EmuSPC (*cyc*) の戻り値 *`clkTotal`* の値 - 通常は 0 以下の値で この値と *Len*, *type* で計算される APU サイクル数を加算した値が `SPC700.asm` EmuSPC の引数 *cyc* の値になる (正の値の場合のみ呼び出す) - **tracing routine 呼び出しが有効な状態で** `SPC700.asm` の *dbgOpt* に SPC_RETURN フラグをセットして EmuSPC から戻ると 0 以上の値になってしまう - SPC700.asm - EmuSPC (s32? *cyc*) - *tmpTotal*, *tmpExec*, *tmpLeft* - コールバック関数 (*effect* = CBE_S700FCH) で FCH_HALT を返却した場合 *clkTotal*, *clkExec*, *clkLeft* を以前の値に戻すために必要 (であった? refs #19) - *`clkTotal`* - SPCTimers で **0 以下**になるとエミュレーションを終了する - tracing routine 呼び出しが有効な状態で *dbgOpt* に SPC_RETURN フラグをセットされていると **EBP に SPCExit のアドレスが設定されているため** SPCTimers でタイマー処理後に SCP700 のエミュレーションを終了し 0 以上の値になってしまう - *clkExec* - *t64kHz* 以下の値が設定される (< T64_CYC) - EmuSPC で初期化され SPCTimers で更新されるため値の保存は不要と思われる - 64kHz タイマがカウントアップする毎に *`clkTotal`* から (この値 - *`clkLeft`*) を減じる - 初期化のために EDX を 0 または ~0 に設定するが EmuSPC では XOr, SetA, Dec の 3 命令 SPCTimers では CDQ の 1 命令を使用している - *`clkLeft`* - *clkExec* と同じ値が設定される - EmuSPC で初期化され SPCTimers で更新されるため値の保存は不要と思われる - 命令を実行する毎に減少し **0 以下**になるとタイマーなどを更新する - SPCTimers - タイマー処理後に *`clkTotal`* から実際に処理した実行時間 (*clkExec* - *clkLeft*) を減じる - 0 以下になった場合は SCP700 のエミュレーションを終了する - 正の値である場合は *clkExec* と *clkLeft* を更新し再開する - **EBP に SPCExit のアドレスを設定してジャンプする** ことで *`clkTotal`* の値にかかわらずタイマー処理後に SCP700 のエミュレーションを終了することができる

clkLeft ではなく clkTotal を 0 にすることで想定通りの動作になりそうです。


大変恐縮ですがコールバック関数 (effect = CBE_S700FCH) で FCH_HALT を返却した場合の動作を、以下のように変更をお願いできないでしょうか。
( ↓ B は不要でした。申し訳ありません)

A = clkExec - clkLeft                   // A : 実際に処理した実行時間
B = clkLeft < 0 ? clkLeft : 0;          // B : clkLeft < 0 → 命令サイクルぴったりで終了したことにするには (- clkLeft) 足りない

clkTotal = A - B;                       // SPCTimers で 0 になるはず

Jmp SPCTimers                           // t64kHz を更新する必要があるので SPCTimers へジャンプ

お手を煩わせて申し訳ございません。どうぞ、よろしくお願いいたします。

dgrfactory commented 4 years ago

@GodGnilda さん 動作検証、および、修正方法のご提示をいただき、誠にありがとうございます。 とても助かります。

FCH_HALT の場合、 clkTotal = clkExec - clkLeft - (clkLeft < 0 ? clkLeft : 0) を行うよう処理を変更いたしました。 snesapu-v2.18.2.7143.zip

FCH_HALT 状態で、強制終了やフリーズ等しないことは確認いたしました。 ご希望の動作になっておりますか、たいへんお手数をおかけいたしますが、今一度ご確認いただけますでしょうか。

どうぞ、よろしくお願いいたします。

GodGnilda commented 4 years ago

@dgrfactory 様 たびたび恐れ入りますが、SPCFetch でコールバック関数を呼び出す前に clkLeft が正の値であることは保証されるようですので、B は不要ですね。申し訳ありません。

dgrfactory commented 4 years ago

@GodGnilda さん いえいえ、問題ございません。 確かに clkLeft は命令実行後に減算されますので、コールバックの時点では負にならないことを確認いたしました。

計算式上、すぐに影響があるわけではないようですが、念のため修正版を添付いたします。 snesapu-v2.18.2.7143-b2.zip

どうぞ、よろしくお願いいたします。

GodGnilda commented 4 years ago

@dgrfactory 様 最新の修正版で動作を確認いたしました。EmuAPU の画像は len の表示がおかしいですが、修正漏れです。

想定通りの動作です。ご修正いただきありがとうございます。

dgrfactory commented 4 years ago

@GodGnilda さん 早速のご確認、誠にありがとうございます。 想定通りの動作となっていること、承知いたしました。

本件は元々 HALT 状態にしたにも関わらずタイマーが意図せず進んでしまうことから、不具合扱いといたします。 多大なご協力をいただき、ありがとうございます。

本件、いったんクローズいたしますが、追加依頼事項がございましたらリオープンしていただくか、お手数ですが新しい issue を作成していただきますよう、よろしくお願いいたします。