公開日:2021/04/24
title.png

15th Season


#15-9 割り込み機能を追加する

 マイコンを完成させることが目標だったシーズン15も残りあと2回です。前回、マイコンとして形が整ったので、「完結」と、思われましたか?やっぱり、割り込み機能のないマイコンって、どうなんでしょうか?マイコンとは、外部回路を制御するための装置ですから、何らかのイベントを割り込みとして受け取り、通常処理に割り込んで別な処理をさせたいケースがあるはずです。
 残り2回でオリジナル16ビットCPUコアMoscoviumに割り込み機能を実装して、完結させたいと思います。

まずはCPU

 最初に、オリジナル16ビットCPUコアMoscoviumに割り込み機能を追加しなければなりません。
 インターフェイスとしてはCPU Moscoviumの最上位に割り込み要求信号irqを追加します。irqがアサートされると、CPUが割り込み処理を開始します。と、ここで、CPU様のキャラクタ設定を確認しておきましょう。

  CPU様のキャラクタ設定:

 (1^^;)……な・の・で、irq信号で割り込み要求は受け取るけど、その割り込みを受け付ける(実行する)かどうかは、CPUが決めます。
 従って、状態レジスタsrに割り込み許可IEビットを設けて、リセット解除後の初期値をIE=0(割り込み禁止)とします。CPUはリセット解除後、プログラムの初期化を実行し、割り込みを受け付けられる状態になったら、srレジスタのIE=1として割り込みを許可します。

割り込み処理

 そもそも割り込みと言うのは、CPUが通常の処理を実行中に、緊急度が高かったり、タイムリに処理すべきイベントが発生した時、一時的に通常処理に割り込んで別な処理を実行する事を指します。
 従って、割り込みサービスルーチンの実行が終了したら、何事も無かったかの如く元の通常処理に戻らなければなりません。このためには最低限、割り込み処理に入る直前の状態レジスタsrと、プログラムカウンタpcの値を保存して、割り込みサービスルーチンから戻る時に復帰できるようにしておく必要があります。

 CPUは割り込みを受け付けると、以下の様な割り込み処理(手順)を実行します。

 ATmega328等のマイコンでは、「割り込みベクタテーブル」を持っていることが多いです。ATmega328の割り込みベクタテーブルは、プログラム空間のアドレス0x0000〜の数10バイトです。データシートなどで確認して見てください。このような方式をベクタ割り込み方式と言います。
 この方式は、例えば、「タイマ1割り込み」「UART受信割り込み」「UART送信終了割り込み」など、割り込み要因毎に異なるベクタを指定できるので、あるベクタにジャンプしてきた時には、どの割り込み要因が発生したのか、自明なのです。この仕組みでは、割り込みベクタの先頭命令からすぐに処理を開始することが出来、タイムリに処理できることが特長です。

 一方、リセット解除後、アドレス0x0000番地からプログラムを実行するとしたMoscoviumの仕様では、割り込みベクタテーブルを設置する余地がありません。そこで、割り込みベクタivを新設して、割り込みが起きた時の割り込みサービスルーチン先頭アドレスをユーザに指定してもらう仕様としました。
 この方式では、全ての割り込み要因が同じivレジスタアドレスにジャンプするため、割り込みサービスルーチンの最初の処理は、「どの割り込み要因で割り込みがかかったのか?」を知ることになります。割り込み要因を確認する方法については、後ほど割り込みコントローラINTCの設計をする時に検討します。

実装

 具体的には以下の実装が必要となります。

ついでの命令拡張

 ソフトウェア(OS等含む)の処理規定次第なのですが、割り込み処理に汎用レジスタバンクを予約すると、割り込みサービスルーチン内の処理を大幅に簡略化・高速化することが出来ます。

 割り込みサービスルーチンは、元の通常処理に割り込んで実行されるのですが、割り込み処理終了後は、元の通常処理に戻らなくてはなりません。割り込み開始処理でスタックに退避するレジスタは、srとpcだけなので、それ以外の汎用レジスタなど、割り込みサービスルーチン内で使用するレジスタは全て、プログラムでスタック等の外部メモリに退避・回復する必要があります。

  例)
  int_vect:
  pushwm r0,r1,r2,r3  // 退避
  // この先、汎用レジスタr0〜r3を使用可能
    :
  popwm r3,r2,r1,r0  // 回復
  // 最初に退避したレジスタを回復してから戻る。
  rti



 予めプログラムを組む時に、「割り込みサービスルーチン内ではバンク3を使うので、通常処理でのバンク3使用は禁止」と、取り決めしておくと、上の例は下の様に簡単になります。

  例)
  int_vect:
  ldcl sr,0x03  // バンク3を選択
  // この先、全汎用レジスタr0〜r7を使用可能
    :
  // rti命令がsrレジスタを回復するので、自動的に元のバンクに戻る。
  rti


 汎用レジスタバンク機能によって、安いコストで8個の汎用レジスタをフリーに使用可能となりました。それでも、たったの8個なんですよ。割り込みサービスルーチン内の処理が複雑になって来ると、汎用レジスタ8個なんてすぐに使い果たして、足りなくなってしまうでしょう。

 C言語のローカル(auto)変数みたいなデータ領域が欲しくなります。通常、こうした領域はスタック上に確保されます。すると、スタックポインタspからの相対アドレスで外部メモリにアクセスできる命令が欲しくなります。そこで、スタックポインタ相対間接アドレッシング命令、ldwsp命令stwsp命令を追加しておこうと思います。

  例)
  int_vect:
  ldcl sr,0x03  // バンク3を選択
  // この先、全汎用レジスタr0〜r7を使用可能
    :
  movfc r0,sp  // スタック上に4ワード(8バイト)のデータ領域を確保
  subi r0,8
  movtc sp,r0
  // この先、[sp+0]〜[sp+6]までの4ワード(8バイト)を使用可能
    :
  ldwi r0,0x1234
  ldbiu r1,4
  stwsp [sp+r1],r0
  ldwsp r2,[sp+r1]
    :
  movfc r0,sp  // スタック上に確保したデータ領域を開放
  addi r0,8
  movtc sp,r0
  rti


 ただ、この命令を実行するためには、レジスタが1個足りません。下のCPUブロック図を眺めてみても、相対アドレスSp+Rnを計算して、MEMブロックからアドレスとして出力するパスが無いのです。

a153_cpublk.png


 Sp+Rnの演算はALUを通すしかないので、Sp+Rnを演算した結果を一時的に保管するレジスタが必要となりました。そこで、制御レジスタに一時レジスタtrを追加することにしました。以下、新しいプログラマモデルです。

a153_prgmdl.png


 RGFブロックのブロック図も更新しておきました(下図)。

a154_rgf.png


 当面、ldwsp命令とstwsp命令でしか使わない一時レジスタtrを、CPUリソースにしてしまってはもったいないので、ユーザリソースとします。つまり、trレジスタはユーザが自由に使用して良いです。ただし、特定の命令を使うと、trレジスタの内容は破壊されてしまうことに注意を払う事が条件です。

※割り込みサービスルーチン内でtrレジスタを使う場合、trレジスタをスタックなどに退避・復帰することが必要となります。

 以下の様に、バンク間で特定のレジスタ値を転送したい場合等にも使用可能です。

  例)
  ldcl sr,0x01  // バンク1を選択
  movtc tr,r1
  ldcl sr,0x02  // バンク2を選択
  movfc r1,tr


 一時レジスタtrを経由して、汎用レジスタr1の値をバンク1からバンク2へ転送出来ました。

割り込みコントローラ

 前節で検討した通り、Moscoviumはベクタ割り込み方式ではないので、割り込みサービスルーチンの先頭で実施すべき処理は、「どの割り込み要因で割り込みが起きたか」を知ることです。これを知るために、割り込みを起こす可能性のあるI/Oユニットのレジスタを一々見に行って、割り込み要因を確かめるのは骨が折れます。
 それに、I/Oユニットは必要に応じて増・減があるのに対して、CPUの割り込み要求信号入力はirq信号1本だけですので、各I/Oユニットからの割り込み要求を束ねて、1本の割り込み要求信号にしてCPUへ送信する役目を持った制御回路が必要です。
 これを割り込みコントローラINTCにやらせようと思います。

 INTCは以下の役割を担います。

INTCのI/Oレジスタ仕様

 INTCのI/Oレジスタ一覧を以下に示します。

Addr Name R/W Bit:15〜0 Memo
0xf060 intnum R INUM[15:0] interrupt number
intofs W OFST[15:0] offset
0xf062 intext RW EI
F1
EI
F0
EI
E1
EI
E0
EIT1
[1:0]
EIT0
[1:0]
external interrupt
0xf064 reserved
0xf066 reserved
0xf068 reserved
0xf06a intfct R OV
FR
1
CM
AR
1
CM
BR
1
OV
FR
0
CM
AR
0
CM
BR
0
EI
R1
EI
R0
interrupt factor
0xf06c reserved
0xf06e intmsk RW OV
FM
1
CM
AM
1
CM
BM
1
OV
FM
0
CM
AM
0
CM
BM
0
EI
M1
EI
M0
interrupt mask

 BUSC出力のセレクト信号はbcs_intc_nです。上表の範囲でアサートされるので、INTCユニット内では、アドレスbadr[3:0]をデコードしてレジスタを特定しています。このI/Oユニットはバイトアクセス禁止です。必ずワードアクセスしてください。

割り込み受付までの道のり

 I/Oユニットで発生した割り込み要求が、CPUで受け付けられるまでの道のりを下図に示します。

a159_irqack.png

 ATmega328等のマイコンに比べて、1段(INTCのマスクレジスタ分)多くなっています。
 あまり、設定を複雑にするのは好みではないのですが、ここでは敢えて、INTCのマスクレジスタintmskを追加して3段にしてあります。INTCのintmskレジスタは初期値=許可で、何も操作しなければ設定不要なので、許してください。I/Oユニットのレジスタには触らずに、一時的に特定の割り込みをマスクする仕組みとして追加しました。
 そのような用途で使わないなら、intmskレジスタを設定しなければ、無いのと同じです。

INTCブロック図

 INTCのブロック図を以下に示しておきます。

a159_intc162.png

 筆者の場合、コーディングする前にブロック図は描きますね。どういう機能ブロックに分割して、どのブロックにどんな機能を持たせるかをイメージしながら、基本設計を進めます。ただ、コーディングしている途中で、「ダメだな」とか思い、ブロック図に戻って修正して出直すことはしばしばです。

割り込みサービスルーチン考察

 割り込み受付後、割り込みサービスルーチンでのプログラムの想定動作フロウを検討しておきます(↓)。

 手順2)と2’)、5)と5’)は、どちらか一方必要な方を選んで実行すればOKです。特に優先順位を気にせず、割り込み要因を1個づつ処理して行けば良い場合には、2’)ではなくて2)を実行すれば良いし、多少のオーバヘッドを許容する場合は、5’)の代わりにそのまま5)を実行してしまえば良いです。


 次回、シーズン15の最後に、CPUに対して割り込みを要求するI/Oユニットとして16ビットタイマを設計してこのシーズンを閉じようと思います。


btn_arduino.png btn_moscovium.png

btn_prev.png btn_next.png

home