割り込み
ハードウェアおよびソフトウェアによる割り込みについて説明します。
概要
割り込みは、実行中の処理を一時的に中断し、あらかじめ決められたルーチン(割込処理ルーチン)を実行する仕組みです。割込処理ルーチンを終えたらRTI命令で元の処理に戻ります。
SNESのCPU(5A22)は65C816の割り込みをそのまま受け継いでおり、次の種類の割り込みがあります。
| 種類 | 発生源 | 割込禁止フラグ(I)による禁止 | 主な用途 |
|---|---|---|---|
| RESET | 電源投入・リセットボタン | 不可 | プログラムの開始 |
| NMI | PPU(Vブランクの開始) | 不可 | 毎フレームの処理、VRAM等への転送 |
| IRQ | H/Vカウントタイマー、カートリッジ | 可 | 画面の途中での処理の切り替え、コプロセッサとの連携 |
| ABORT | (SNESでは非対応) | 不可 | - |
| BRK | BRK命令 | 不可 | デバッグ |
| COP | COP命令 | 不可 | ソフトウェア割り込み |
複数の割り込みが同時に発生した場合は、リセット、NMI、IRQの順に優先されます。
割り込みベクタ
割り込みが発生すると、CPUはバンク$00の末尾にある割り込みベクタから割込処理ルーチンのアドレスを読み込みます。ベクタはバンク$00内の16ビット絶対アドレスで、ネイティブモードとエミュレーションモードで別の場所が使われます。
| アドレス | ネイティブモード | エミュレーションモード |
|---|---|---|
| $00:FFE4 | COP | - |
| $00:FFE6 | BRK | - |
| $00:FFE8 | ABORT | - |
| $00:FFEA | NMI | - |
| $00:FFEC | (未使用) | - |
| $00:FFEE | IRQ | - |
| $00:FFF4 | - | COP |
| $00:FFF6 | - | (未使用) |
| $00:FFF8 | - | ABORT |
| $00:FFFA | - | NMI |
| $00:FFFC | - | RESET |
| $00:FFFE | - | IRQ・BRK |
リセットは常にエミュレーションモードで処理されるため、ネイティブモードのリセットベクタはありません。
割込処理ルーチンは先に述べたようにバンク$00から実行されるので、バンク$00から見えるアドレス($8000-$FFFF)に置く必要があります。
ROM上でのベクタの物理アドレスは、LoROMでは$00:7FE4、HiROMでは$00:FFE4です。詳しくはメモリマッピングを参照してください。
割り込みの処理手順
ハードウェア割り込み(NMI・IRQ)が受け付けられると、CPUは実行中の命令を終えてから次の処理を行います。
・ネイティブモード
- プログラムバンクレジスタをスタックにプッシュします。
- プログラムカウンタ(次に実行するはずだった命令のアドレス)をスタックにプッシュします。
- ステータスレジスタをスタックにプッシュします。
- 割込禁止フラグをセットし、デシマルモードフラグをクリアします。
- プログラムバンクレジスタをゼロクリアし、プログラムカウンタをベクタから読み込みます。
・エミュレーションモード
- プログラムカウンタをスタックにプッシュします。
- ブレークフラグをクリアしたステータスレジスタをスタックにプッシュします。
- 割込禁止フラグをセットし、デシマルモードフラグをクリアします。
- プログラムバンクレジスタをゼロクリアし、プログラムカウンタをベクタから読み込みます。
エミュレーションモードではIRQとBRKが同じベクタを使うため、スタックに積まれたステータスレジスタのブレークフラグで区別します。
BRK・COPの処理手順はそれぞれの命令のページを参照してください。
RTI命令は、ネイティブモードではステータスレジスタ、プログラムカウンタ、プログラムバンクレジスタの順に、エミュレーションモードではステータスレジスタ、プログラムカウンタの順にスタックから取り出して、元の処理に戻ります。
割込処理ルーチンを書く際の注意
割り込みは、CPUの状態がどうなっているかに関係なく発生します。割り込みの開始時に自動で保存されるのはプログラムバンクレジスタ・プログラムカウンタ・ステータスレジスタだけなので、次の点に注意してください。
- A・X・Yレジスタ、データバンクレジスタ、ダイレクトページレジスタは保存されません。割込処理ルーチンの中で使う場合は、最初にスタックに退避し、最後に元に戻します。
- レジスタの幅(mフラグ・xフラグ)は割込発生時のままです。最初にREP・SEPで必要な幅に設定してください。ステータスレジスタはRTIで元に戻るので、戻す必要はありません。
- データバンクレジスタとダイレクトページレジスタも割込発生時のままです。I/Oレジスタやワークメモリにアクセスする場合は、必要に応じて設定し直してください。
- 割込処理ルーチンは必ずバンク$00から始まります。FastROMで高速に実行したい場合は、ルーチンの先頭で JML 命令によりバンク$80以降へ移るのが一般的です。
次はNMI処理ルーチンの例です。
NMI:
REP #$30 ; A・X・Yを16ビットにしてから退避する
PHA
PHX
PHY
PHB
PHD
SEP #$20
LDA $4210 ; RDNMI: NMIフラグのクリア
; ... ここにVブランク中の処理を書く ...
REP #$30
PLD
PLB
PLY
PLX
PLA
RTI
RESET
電源投入時やリセットボタンが押されたときに発生します。CPUはエミュレーションモードになり、リセットベクタ($00:FFFC)のアドレスから実行を開始します。
リセット直後は割込禁止フラグがセットされ、デシマルモードフラグがクリアされます。また、m・xフラグはセット(8ビット)、プログラムバンクレジスタ・データバンクレジスタ・ダイレクトページレジスタは$00、スタックポインタの上位バイトは$01になります。
システム起動処理はほとんど定型化されています。以下にその一例を示します。
RESET:
SEI ; 起動準備中に割込要求が発生しても拒否するようにする
LDA #$80
STA $2100 ; INIDISP: 強制ブランク有効
LDA #$01
STA $4200 ; NMITIMEN: ジョイパッド自動読取有効
STA $420D ; MEMSEL: FastROM有効(非対応の場合はこの行を削除する)
DEC A
STA $420C ; HDMAEN: HDMA全チャネル無効
CLC
XCE ; キャリークリアしてXCEを実行することでネイティブモードに移行する
REP #$30 ; A, X, Yレジスタを16ビットモードに
LDX #$01FF
TXS ; スタックポインタを初期化する(例では$01ff)
LDA #$0000
TCD ; ダイレクトページを初期化する(例では$0000)
PEA $8080
PLB
PLB ; データバンクレジスタを初期化する(例では$80)
JML main ; メインルーチンへ処理を渡す
起動準備中は、システムのほかのコンポーネントに入っている不定なデータによる出力を防ぐため、これらの機能を無効にすることがポイントです。
たとえばPPUに対しては、VRAMに描画データを渡すまでは強制ブランク状態にして映像出力を止めるといったことを行います。
NMI
NMI(Non-Maskable Interrupt)は、割込禁止フラグで禁止できない割り込みです。SNESでは、PPUのVブランクの開始時に発生します。
- NMIを発生させるには、NMITIMEN($4200)のビット7をセットします。このビットをクリアしている間はNMIは発生しません。
- Vブランクの開始時には、NMIの有効・無効に関係なく RDNMI($4210)のビット7(NMIフラグ)がセットされます。このフラグはRDNMIを読み取るとクリアされます。
- NMIフラグがセットされたままの状態でNMITIMENのビット7をセットすると、その時点でNMIが発生します。Vブランク中にNMIを有効にする場合は注意してください。
Vブランク中はVRAM・OAM・CGRAMに自由にアクセスできるので、NMI処理ルーチンではこれらへのDMA転送などを行うのが一般的です(画面のレンダリング、DMAおよびHDMA)。
IRQ
IRQ(Interrupt Request)は、割込禁止フラグがセットされている間は受け付けられない割り込みです。割込禁止フラグはSEI命令でセット、CLI命令でクリアします。
SNESでは、次の2つがIRQの発生源になります。
H/Vカウントタイマー
PPUの走査位置が指定した位置に来たときにIRQを発生させます。位置は HTIME・VTIME($4207~$420A)で、発生条件は NMITIMEN($4200)のビット5-4で設定します。
| ビット5 (V) | ビット4 (H) | IRQの発生条件 |
|---|---|---|
| 0 | 0 | 発生しない |
| 0 | 1 | 毎ラインの HTIME の位置 |
| 1 | 0 | VTIME のラインの先頭 |
| 1 | 1 | VTIME のラインの HTIME の位置 |
IRQが発生すると TIMEUP($4211)のビット7がセットされます。このフラグはTIMEUPを読み取るとクリアされます。IRQ処理ルーチンの中でTIMEUPを読み取らないと、RTIで戻った直後に再びIRQが発生します。
画面の途中でBGモードやスクロール位置を切り替える、ステータス表示の部分だけ別の設定で描画する、といった用途に使われます。
カートリッジ
カートリッジ端子にはIRQ信号線があり、SA-1やSuper FXなどのコプロセッサを搭載したカートリッジは、これを使ってCPUにIRQを発生させることができます。
この場合、TIMEUPのフラグはセットされません。IRQの発生源の判別やフラグのクリアは、それぞれのコプロセッサのレジスタで行います。
WAI命令とSTP命令
WAI命令を実行すると、CPUは割り込みが発生するまで停止します。Vブランクを待つ場合などに使用します。
割込禁止フラグがセットされている状態でIRQが発生した場合は、割込処理ルーチンは実行されず、WAI命令の次の命令から実行を再開します。
STP命令を実行すると、CPUはリセットされるまで停止します。
参照
割込命令
NMITIMEN レジスタ
RDNMI レジスタ
TIMEUP レジスタ
HTIME・VTIME レジスタ
メモリマッピング