例外処理のタイミング
Cortex-M3のマニュアルを読みますと、テールチェイン、横取り、後着という言葉が出てきます。例外処理優先順位によって発生する例外処理のタイミングを現す言葉です。各々の意味を簡単に説明すると以下のようになります。
テールチェイン
実行中の割り込みから別の割り込みに移る際の状態を指します。割り込み間の状態保存および復元の際のオーバヘッドを伴わない連続割り込み処理のことです。
横取り
割り込み処理中に新しく発生した例外が、処理中のフローに割り込むことを指します。
後着
横取りによる状態保存の間に、それよりも優先順位の高い割り込みが発生した場合、その優先順位の高い割り込みの為の処理に切り替わり、その割り込みのベクタフェッチを開始することを指します。基本的に、横取りを高速化する目的のメカニズムです。
テールチェイン
優先順位の異なる2つの割り込みが同時に入った場合を考えましょう。優先順位が高い方を割り込み1(この割り込みのサービスルーチンをISR1とします)。低い方を割り込み2(この割り込みのサービスルーチンをISR2とします)。プロセッサは(もちろん)最初にISR1を実行し、次にISR2を実行します。
例えばArm7の場合、ISR1に入る前にプロセッサのコンテキストをPUSHします。そして、ISR1が終るとPOPします。次にISR2のためのPUSHを行います。ところが、POPしてすぐにPUSHするということは、同じコンテキストをPUSH、POPするので、スタックの値は変わりません。すなわち、まったく無駄な動作であると言えます。そこで、Cortex-M3では、このPUSH、POPを行わないようになっています。ただ、割り込みのサービスルーチンが変わりますので、若干の内部論理の処理が必要です。この内部処理の期間をテールチェインと呼んでいます。
テールチェインに必要なサイクル数は6クロックサイクルだけです。Arm7の場合は、PUSHしてPOPして、ISR1からISR2に移る為に42クロックサイクル必要です。さらに、Arm7ではPUSHに26クロックサイクルかかっていたのが、Cortex-M3では12クロックサイクルだけです。POPもArm7では16クロックサイクルかかりますが、Cortex-M3では12クロックサイクルに短縮されています。全体としてArm7とCortex-M3を比較すると65%のクロックサイクルの節約になることがわかります。
横取り(ISRのPOPの横取り)
優先順位の異なる2つの割り込み(IRQ1とIRQ2、優先順位はIRQ1が高い)が入った場合を考えます。今回は、同時ではなくて、割り込み優先順位が低い方が後から入る場合です。後からと言っても、最初のISR1の処理が完全に終ってからではなくて、ISR1からメインルーチンに復帰(POP)している最中に発生した場合を考えます。
Arm7では、ISR1のPOPの処理中にIRQ2が受け付けられ、ISR1のPOPが終わり次第ISR2のPUSHが行われます。テールチェインのところでも述べましたが、このPOPとPUSHは同じコンテキストを出したり入れたりする動作なので、無駄な動作になります。そこで、Cortex-M3では、この動作を省略するような動きをします。
Cortex-M3はIRQ2が入った時点で、ISR1のPOPを中断し、すぐにテールチェインの処理に入ります。ただし、Cortex-M3ではPOPに16サイクルかかりますが、12サイクル目を過ぎてからIRQ2が入ると中断することができません。その場合はPOPは完了されて、ISR2用のPUSHが始まってしまいます。ISR1のPOPが1~12サイクルまでの場合、テールチェインに移りますから、ロスタイムは最大で12サイクルになると言えます。テールチェインに移った後は、ISR2が実行され最後にISR2のPOPが実行されて、メインプログラムに戻ります。
横取り(ISRの最中)
お客さんに割り込みの説明をすると、必ず聞かれるのがネスティングです。ISRの最中に別の優先順位の高い割り込みが入るとネスティングします。これも横取りの一種ですので、次はネスティングの例を説明します。今回はNMIを含め全部で4種類の割り込みが複雑なタイミングで入る場合です。優先順位は高い方からNMI、IRQ1、IRQ2、IRQ3の順です。
まずIRQ2が入り、PUSHの後にISR2が始まります。その後、IRQ1が入ります。ISR2が始まっていますので、コンテキストは変わっています。したがって、PUSHしてコンテキストの保存が必要になります。そこで、ISR2が中断されて、ISR1のためのPUSHが始まります。いわゆるネスティングの開始です。
しかし、このPUSHの最中に、さらに優先順位の高いNMIが入ります。その為ISR1用に始まったPUSHはNMIに横取りされ、PUSHの後はNMIのサービスルーチンが実行されます。
IRQ1は受け付けられていますが、NMIが先に処理されていますので、処理待ち状態になります。NMIのサービスルーチンが終わると、最初に説明しましたテールチェインの場合と同じ条件ですので、テールチェインの後にISR1が始まります。コンテキストは変わっていないので、ここではPUSHは行われません。
NMIのサービスルーチンからISR1に移るテールチェインの間に、IRQ3が入ります。IRQ3は最も優先順位が低いので、ここでは実行されずに待ち状態になります。
ISR1が終わるとISR2に戻りますが、ここでNMIのサービスルーチンに入る前にPUSHしたコンテキストを復帰(POP)させる必要があります。POPが完了した後、ISR2は中断された次のプログラムカウンタ(PC)から再開します。
ISR2が終わるとISR3に移ります。もうお分かりだと思いますが、ここもテールチェインの6サイクルだけでISR3に移ります。ISR3が終わると、ISR2に入る前にPUSHしたコンテキストをPOPして、すべての割り込み処理を終了して、メインプログラムへ戻ります。
この様に、4つの割り込みが、複雑なタイミングで発生してもCortex-M3のNVICは、最も効率の良い手順を自動的に選択して、オーバーヘッドが最小になる処理を行います。
後着
次は、優先順位の低い割り込みを一旦実行し始めるのですが、PUSHの最中に、優先順位のより高い割り込みが後から来て、PUSHを横取りする例です。優先順位の高い割り込みが後から来るので後着と呼んでいます。今回は、IRQ1とIRQ2の関係です。IRQ1の方が優先順位が高い場合です。
IRQ2がまず受け付けられて、PUSHが始まります。ところが、このPUSHの最中にIRQ1が発生します。その為IRQ2は保留されて、実行されているPUSHはISR1用に切り替わります。
Arm7では、ISR2用のPUSHと後着したISR1のPUSHを実行してISR1に移るのですが、PUSHを2回続けても、同じコンテキストをスタックするだけですので、無駄な処理だというのがすぐにわかると思います。
Cortex-M3では、PUSH1回の後にISR1が実行され、ISR1が終わるとテールチェインの後にISR2が始まります。もうここまで来たらお分かりだと思いますが、ISR1のPOPは実行されません。ISR2が終わると、最後にPOPを1回行って、一連の割り込み処理が終わります。
例外の優先度のまとめ
テールチェイン、横取り(2種類)、後着と見てきましたが、いずれの場合でも、割り込みで最も重要な事、すなわち「割り込みの優先順位に従って、いかに効率良く複数の割り込みを処理するか」をCortex-M3ではNVICというハードウェアを使って実現しています。
昔、16ビットマイコンを設計していたときに、割り込みコントローラの設計が一番大変だなぁと感じました。なんせ、想定しなければならない事象の数が、とてつもなく多いからです。もちろんCPUの設計も、多くの場合に、どの様な処理をしなければならないかを考えるのが大変なんですが、CPUの場合は各パイプライン処理で、ある程度の事象の数は限定できます。それが3段パイプラインであれば、3乗の事象の数になり、5段であれば5乗になり、結果、とてつもない場合分けになります。しかし、パイプラインで区切ることによって、容易に、かつバグが少なく設計できます。
しかし、当時の割り込みコントローラはランダム論理で構成されますので最初に事象を想定するのが大変で、次にそれらの各々の場合に対して論理を組み合わせるのがすごく大変でした。出来上がっても論理シミュレーションを行うと思いもよらないバグがたくさん出てきて、バグが収束しないんじゃないかと不安の中で設計を進めました。今は、機能記述で論理設計を行いますが、想定しなければならない事象の数の多さは変わりません。むしろCPUの性能が上がっているので、昔よりも大変かもしれません。
割り込みコントローラを設計したことのある者からみると、Cortex-M3のNVICはかなり優秀な割り込みコントローラだと言えるでしょう。
こちらも是非
“もっと見る” Cortex-M編
SysTick、電力管理
SysTick機能を有効にするには、SysTick制御およびステータスレジスタを使用します。このレジスタのENABLEビットを1にすると、カウンタは動作をはじめます。つまり、カウンタにリロード値がロードされてから、カウントダウンが開始されます。
メモリマップ
Cortex-M3のメモリマップには、一般的なマイコンのメモリマップと若干異なる特徴があります。一般的なマイコンでは、メモリ領域を変更できるものもあります。しかし、Cortex-M3のメモリマップは定義されたメモリマップになっており、アドレス領域のマッピングは固定です。
ベクタテーブル
Cortex-M3のベクタテーブルは0番地から始まります。一般的なマイコンは、ベクタテーブルの最小アドレス部(0番地)にはリセットベクタが割り当てられていますが、Cortex-M3ではメインスタック(SP_main)の初期値が割り当てられています。