DMA とは direct memory access の略で、さまざまなコンピュータに搭載されており、
周辺機器とメモリの通信を、CPU を介すことなく行うことができる。
SNES では、DMA の通信中は CPU が一時的にストップする。

HDMA は DMA と似ていますが、こちらは少しのデータを、
スキャンライン毎の H-Blank 期間中に転送する目的で使われる。
HDMA は、表示を乱れさせることなく 1 フレーム中に PPU のレジスタを
操作する時に非常に役立つ。

DMA, HDMA 共にそれぞれ 8 つのチャネル (0 〜 7) がある。
HDMA は DMA より優先順位が高く、データの転送が重なった場合は、
全ての DMA チャネルが一時停止され、重なった DMA チャネルはその場で停止される。
また、番号が小さいチャネルが優先されて動作する。

***DMA [#q1c7c9bf]
DMA には 3 つの動作モードと、いくつかの設定用ビットがある。

-方向 ([[0x43x0>IO ポート (メイン)/0x43x0-0x43xA#p43X0]] のビット 7) : PPUの読み込み/書き込み
-アドレス固定 ([[0x43x0>IO ポート (メイン)/0x43x0-0x43xA#p43X0]] のビット 3) : アドレスを調整するかどうか
-インクリメント ([[0x43x0>IO ポート (メイン)/0x43x0-0x43xA#p43X0]] のビット 4) : アドレス調整の方向
-モード ([[0x43x0>IO ポート (メイン)/0x43x0-0x43xA#p43X0]] のビット 0 〜 2) : 下記参照
-ポート ([[0x43x1>IO ポート (メイン)/0x43x0-0x43xA#p43X1]]) : ここに 'xx' が指定された時、0x21xx にアクセスする
-Aアドレス ([[0x43x2>IO ポート (メイン)/0x43x0-0x43xA#p43X2]] 〜 [[0x43x4>IO ポート (メイン)/0x43x0-0x43xA#p43X4]]) : CPUアドレス。絶対ロングアドレッシングモードで値を入れる
-Aアドレス ([[0x43x2>IO ポート (メイン)/0x43x0-0x43xA#p43X2]] 〜 [[0x43x4>IO ポート (メイン)/0x43x0-0x43xA#p43X4]]) : CPUアドレス。[[絶対ロングアドレッシングモード>CPU/アドレッシングモード/Absolute Long]] で値を入れる
-カウント ([[0x43x5>IO ポート (メイン)/0x43x0-0x43xA#p43X5]] 〜 [[0x43x6>IO ポート (メイン)/0x43x0-0x43xA#p43X6]]) : 転送バイト数

これらの値は、DMA転送前に設定する必要がある。

[[0x43x0>IO ポート (メイン)/0x43x0-0x43xA#p43X0]] のモードビットと転送モードは互いに関係している。
注:1つのレジスタに1度書き込むのと、1つのレジスタに2度書き込むのは同じことが起きる。
 2つのレジスタに1度書き込むのと、2つのレジスタにそれぞれ交互に1度ずつ書き込むのも
 同じことが起こる。
 しかし、2つのレジスタに1度書き込むのと、2つのレジスタにそれぞれ交互に1度ずつ書き込むのは違う。
(訳者注:よく分かりません。)

DMAが1バイト転送する間に、マスターサイクルで8クロックの遅延がある。
(FastROM の設定に関係なく)
チャネルごとに8クロックの遅延があり、全て転送するまでに12〜24の遅延が起こる。

転送プロセス
+1バイトを取得して転送先に書き込む
--DMAは2つのアドレスバスと1つの共有データバスへの優先順位を得ているように見える。
AアドレスはバスAに、ポートはバスBに、それぞれ指定された方向に読み込み/書き込み信号を送る。
1つのバスは「読み込み」に設定され、データがバスに流れる。
もう1つは「書き込み」に設定され、バスのデータが書き込まれる。
--PPU/APU/WRAMレジスタはバスBからのみアクセスできる。
これらに対してAアドレスでアクセスしようとすると、[[オープンバス>IO ポート (メイン)/オープンバス]]アクセスの値を返す。
--WRAMに対してバスA、バスB ([[0x2180>IO ポート (メイン)/0x2180-0x2183#p2180]] 〜 [[0x2183>IO ポート (メイン)/0x2180-0x2183#p2183]]) の両方を使って
アクセスしようとすると失敗する。
0x2180〜0x2183 は[[オープンバス>IO ポート (メイン)/オープンバス]]化されている。
--また、DMAは [[0x4300>IO ポート (メイン)/0x43x0-0x43xA]] 〜0x437f、[[0x420b>IO ポート (メイン)/0x4200-0x421F#p420B]]、[[0x420c>IO ポート (メイン)/0x4200-0x421F#p420C]]に対してアクセスすることはできない。
書き込みは変化なし、読み込みは[[オープンバス>IO ポート (メイン)/オープンバス]]が返る。
+Aアドレスの調整
--「アドレス固定」ビットがセットされている時は何もしない。
「インクリメント」ビットがセットされている時は1減算、
セットされていない時は1加算する。
--注:バンクバイトは変更されない。
+カウント値のデクリメント。0でない場合、1から繰り返す
--カウント値が最初から0の場合、値がテストされる前に65535にラップされ、
これにより、65536バイトが転送される。

注:カウント値 ([[0x43x5>IO ポート (メイン)/0x43x0-0x43xA#p43X5]] 〜 [[0x43x6>IO ポート (メイン)/0x43x0-0x43xA#p43X6]]) は通常0になるまで繰り返されるが、
HDMAと衝突した場合、転送は途中で中止 (terminate) される。

***HDMA [#sce2f7fe]
HDMAには4つのフラグと、5つの設定項目がある。
(*) の付いている項目はHDMAの転送前にセットする必要があり、
(+) の付いている項目はHDMAの転送が始まったフレーム中に変更する必要がある。

-(*) アドレッシングモード ([[0x43x0>IO ポート (メイン)/0x43x0-0x43xA#p43X0]] のビット 6) : 0 = 直接, 1 = 間接
-(*) 転送モード ([[0x43x0>IO ポート (メイン)/0x43x0-0x43xA#p43X0]] のビット 0〜2) : 下記参照
-(*) ポート ([[0x43x1>IO ポート (メイン)/0x43x0-0x43xA#p43X1]]) : DMAと同じ
-(*) Aアドレス ([[0x43x2>IO ポート (メイン)/0x43x0-0x43xA#p43X2]] 〜[[0x43x4>IO ポート (メイン)/0x43x0-0x43xA#p43X4]]) : HDMAテーブルへのポインタ。
必ずしも転送中のフレームに変更する必要はないが、次の転送前に停止するためには変更する必要がある。
-間接アドレス ([[0x43x5>IO ポート (メイン)/0x43x0-0x43xA#p43X5]] 〜 [[0x43x6>IO ポート (メイン)/0x43x0-0x43xA#p43X6]]) : 間接バンクと一緒に使用。下記参照。
-(*) 間接バンク ([[0x43x7>IO ポート (メイン)/0x43x0-0x43xA#p43X7]]) : 間接アドレスと一緒に使用。下記参照。
-(+) アドレス ([[0x43x8>IO ポート (メイン)/0x43x0-0x43xA#p43X8]] 〜 [[0x43x9>IO ポート (メイン)/0x43x0-0x43xA#p43X9]]) : 下記参照。
-(+) 繰り返し ([[0x43xA>IO ポート (メイン)/0x43x0-0x43xA#p43XA]] のビット 7) : 全てのスキャンラインに書き込むかどうか
-(+) 行カウンタ ([[0x43xA>IO ポート (メイン)/0x43x0-0x43xA#p43XA]] のビット 0〜6) : 下記参照。
-転送実行フラグ(DoTransfer) : 内部的に使用。

モード値はDMAと同じだが、スキャンライン毎に1度だけ実行される。
One Register Write Once モードはスキャンライン毎に1バイトだけ、
One Register Write Twice モードではスキャンライン毎に2バイト書き込まれる。

スキャンライン毎にHDMAがアクティブになる度に
(つまり、そのフレームで少なくとも1つは停止も終了もされていない時)、
マスターサイクルで〜18のオーバーヘッドがかかる。
それぞれのチャネルは転送が実際に起こった場合、
スキャンラインごとに追加で8のオーバーヘッドがかかる。
(たぶんテーブルから0x42xAに値がロードされているだろう)
間接アドレスが変更される場合、ロード時に 16のマスターサイクルが必要になる。
そして、1バイトの転送ごとに8サイクルかかる。
結果として、HDMAはスキャンラインごとにマスターサイクルで
合計466サイクルの時間を必要とする。
(8チャネル全てがアクティブ、間接アドレスの変更、4バイトの転送が全て同時に起こった場合)

HDMAの動作には2つの段階があり、
最初の段階はフレーム開始時に (V(垂直)=0, H(水平)=約6)、
全てのアクティブなHDMAチャネルに対して発生する([[0x420c>IO ポート (メイン)/0x4200-0x421F#p420C]] 参照)。
+「アドレス」値に、Aアドレスがコピーされる
+テーブルから [[0x43xA>IO ポート (メイン)/0x43x0-0x43xA#p43XA]] に対して値をロードする (0x00 をロードすると、その場でチャネルを停止する…だろう)
+必要なら、間接アドレスをロードする
+転送実行フラグ(DoTransfer) をTrueにする

CPUはこの動作の間停止される。
マスターサイクルで〜18のオーバーヘッドがあり、
それぞれのチャネルが、直接モードのHDMAチャネルにセットされている時は8サイクル、
間接モードのHDMAチャネルにセットされている時は24サイクルが追加で必要となる。

フレーム開始後にHDMAを開始させた場合は基本的に、
間接モードチャネル用に [[0x43x8>IO ポート (メイン)/0x43x0-0x43xA#p43X8]] 〜 [[0x43xA>IO ポート (メイン)/0x43x0-0x43xA#p43XA]]、[[0x43x5>IO ポート (メイン)/0x43x0-0x43xA#p43X5]] 〜 [[0x43x6>IO ポート (メイン)/0x43x0-0x43xA#p43X6]] を使って
手動で初期化プロセスを実行する必要がある。
次のステップの4段階目の最初の1回は転送が行われないので注意すること。
それから、フレーム中にチャネルがすでに停止されていた場合、再スタートすることはできない。
(もしくはチャネルを有効にした時に4段階目が自動的に実行されてしまう??)

V=0 から V=0xe0 (オーバースキャンが有効の場合、V=0xef) の 
H=0x116 くらいの時に次の動作が始まる。

+転送実行フラグ(DoTransfer) が False の場合、3段階目へ
+この転送モードの時、1か2か4バイトが必要
++アドレス/間接アドレスのいずれかから1バイト読み込み、インクリメントする。
++ポートに1バイト書き込む。Port+1, Port+2, Port+3 も、転送モードによっては書き込む。
--DMAの時と同じように、[[0x2180>IO ポート (メイン)/0x2180-0x2183#p2180]] を通じてPPU から PPU、RAM から RAMに転送する時は注意が必要。
+[[0x43xA>IO ポート (メイン)/0x43x0-0x43xA#p43XA]] をデクリメント
+転送実行フラグ(DoTransfer) に「繰り返し」ビットと同じ値をセット
+行カウンタが0の場合
++「アドレス」から次の1バイトを読み込み、[[0x43xA>IO ポート (メイン)/0x43x0-0x43xA#p43XA]] に入れる(行番号と繰り返しも同じようにする)。
++間接アドレスモードの時、「アドレス」から2バイト読み込み、「間接アドレス」に入れ、
「アドレス」の値を2バイト分インクリメントする。
---注(奇妙な動作):[[0x43xA>IO ポート (メイン)/0x43x0-0x43xA#p43XA]] が 0 で、処理中のチャネルが現在の行で最後のHDMAチャネルだった場合、
「アドレス」から1バイトのみ読み込まれ、下位バイトには0x00が適用される。
「アドレス」は1つだけインクリメントされ、1少ないCPUサイクルが消費される。
++[[0x43xA>IO ポート (メイン)/0x43x0-0x43xA#p43XA]] が0の時、処理中のHDMAチャネルのこのフレームでの転送は終了する。
[[0x420c>IO ポート (メイン)/0x4200-0x421F#p420C]] のビットはクリアされないが、次のフレームには自動的に開始される。
++転送実行フラグ(DoTransfer) をTrueにする
+次のスキャンラインで1段階目から繰り返す

HDMAはV-Blank期間中は起こらない。
画面エフェクトは何も起こらないだろう。
フレーム開始時、(V-Blankの終了時)にアクティブなチャネルはリセットされる。
V-Blank中にHDMAレジスタを更新することで、
書き込み直後に転送されたり、PPUの状態を意図せず変更してしまったり
というような、厄介な挙動を避けることができる。

今までHDMAテーブルというものが暗黙的に使われてきたが、
これは項目の配列を指し、行番号と繰り返しフラグから始まる。
繰り返しフラグがFalseの時、1スキャンライン分のデータと、
次の項目が始まる前までの行番号の入ったカウント値が続く。
繰り返しフラグがTrueの時、複数のスキャンラインのデータが続く。
このデータは、ポインタ値(間接HDMA)か即値(直接HDMA)を指す。

以上のことから、フレーム処理中にHDMAを開始する時に
なぜ手動で「アドレス」、「繰り返し」、「行番号」を初期化しないと
いけないのかが見えてくると思う。
これらはフレーム開始時に初期化されている。
「アドレス」、「繰り返し」、「行番号」はHDMAによって変更されるが、
「Aアドレス」は変更されないことに注意して欲しい。

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS