SA‐1 RAM protection test - absindx/SNES-TestRoms GitHub Wiki

使用テクニック

このテストROMではテスト実行の都合上、I-RAM, BW-RAMはプロテクションにより書き込めなかったり、メモリクリアされてしまいます。
そのため、SA-1側ではメモリを1バイトも使用することができません。
また、SNES CPUとの連携時のパラメータとしてI-RAM, BW-RAMへリクエスト情報を書き込むこともできません。
いくつかのテクニックを用いて制約を緩和しつつ処理を構築しています。

I-RAMが使用できないケースは普通では発生しないため、このテストROM特有の事情です。
他の用途に転用できる機会はあまりないかもしれません。

SA-1 IRQ メッセージバイト送信

このテストROMではSA-1がテストの主導権を握り、SNES CPUへテスト情報をIRQ経由で送信しています。
標準のI/Oレジスタでは $2209 SCNT のメッセージポート4bitしかありません。
これではデータ送信がままなりません。(試験的にこの4bitだけでのやりとりを設計したところ、1バイトの送信に4回のIRQが必要になりました。)

そこで、SNES CPUのIRQベクタルーチンの前に256バイトのNOPスライドを用意しておき、このNOP領域に着地させることでIRQベクタアドレス下位バイトを任意の値にできるようにしました。
このアドレス下位バイトを追加で送信できるメッセージ領域として利用します。
SNES CPUのIRQベクタアドレスはSA-1から CIV レジスタを通して制御可能です。
$00FFEE NativeIRQL を読むことで追加メッセージ(IRQ着地点)がわかります。
少しのサイクル数と引き換えに4bitしかなかったメッセージポートを12bitに拡張できました。

本テストROMでの用途

$2209 SCNT
IS-Nmmmm
|| |++++- `CMSG` 送信メッセージの種別 (!Message_XXX)
|| +----- `NVSW` 未使用 (常に0)
|+------- `IVSW` 使用 (常に1)
+-------- `IRQ`  IRQしか使用しない (常に1)

$2207-$2208 CIV
HHHHHHHH LLLLLLLL
|||||||| ++++++++- メッセージバイト ($00-$FF の任意値)
++++++++---------- IRQベクタ(NativeIRQ) 上位バイト

%SendSA1Message(messageType, messageValue) マクロでこのレジスタ書き込みをラッピングしています。

BW-RAM protectionのテスト関係で8bit+1bitを送信するためにメッセージ2つを割り当てているのは、後々考えるともったいなかったですね。
NMIや CMSG 最下位ビットで9bitまとめられたかもしれません。

NOPスライド最適化

NOPスライドには通常の NOP ($EA) 命令を使用してもよいですが、最適化として CMP long ($CF) 命令を使用しています。

CIVL=$00 で実行した時のサイクル数(最悪実行時間)

分岐命令も混ぜればもっと短縮できそうではありますが、256パターンすべての最短パスを構築するのが面倒で妥協しました。

ソフトウェアサブルーチン

SA-1ではRAMへの書き込みができないため、サブルーチンの戻りアドレスを保存できません。
SNES CPUではDMAレジスタが汎用RAMとして使用できたのですが、SA-1にはそういった都合の良いI/Oレジスタはありません。

幸い、65816ではスタックポインタが16bitであるため、「戻りアドレスが記載されたROMのアドレス」を指すことができます。
呼び出し側で調整をすることで、サブルーチン側は通常通り RTS で戻ってくることができます。
スタックポインタの指せる領域であることが必須条件のためバンク0固定になってしまいますが、1段だけであればサブルーチン呼び出しが実現できます。

動作例

%SoftwareJSR(addr) マクロでサブルーチン呼び出しをラッピングしています。

なお、この実装はサブルーチン呼び出しのたびにスタックポインタは再設定されるため、呼び出し元ではスタックポインタが破壊可能です。
サブルーチン呼び出しをしない一部テストでは汎用レジスタとして使っています。

マクロ

ラベル名を引数と結合して生成しています。

macro	TestDefineMain(id, name)
	!TestDefined_<id>	= 1

	pushpc
		org	TestResults+<id>
		if <id> >= 100
			TestResults_<id>_<name>:	skip 1
		elseif <id> >= 10
			TestResults_0<id>_<name>:	skip 1
		else
			TestResults_00<id>_<name>:	skip 1
		endif
	pullpc

endmacro
macro	TestDefine(id)
		%TestDefineMain(!<id>, <id>)
endmacro

; 使う側
!TestID_SNES_IRamProtection_Boot	= 1
%TestDefine(TestID_SNES_IRamProtection_Boot)
; 以下が定義される
; !TestDefined_1	= 1
; TestResults_001_TestID_SNES_IRamProtection_Boot:

多分アップデートで壊れる。
0埋め分岐もダサい。
そういう機能を用意しないのが悪い。

⚠️ **GitHub.com Fallback** ⚠️