Cmod A7ボード(FPGA)を使って、オリジナル16ビットCPUコアMoscoviumの開発を進めています。以下記事などで、Moscoviumマイコンのプログラム開発にBlocklyを使用できるように整備しました。
「#31-6 Moscoviumの開発環境(カスカス)」
今回のテーマはBlocklyの割り込み対応です。
最初に割り込みコントローラを作ってから、何回かに分けて拡張してきました(以下の記事等)。
「#15-9 割り込み機能を追加する」
「#17-2 双頭の狼(knee)」
「#23-2 割り込み機能を強化する」
その結果、現在の最新版INTC322DVLでは、以下の様な機能を提供しています。
この様な割り込み機能をBlocklyで制御できるようにしたいと思います。
以下に目標仕様をまとめてみました。
目標仕様: Blockly for Moscovium × 割り込み
検討を進めて行きましょう。
そもそも割り込みと言うのは、ユーザプログラムを実行中、非同期に受け付けられます。割り込みを受け付けた時点で突然、割り込みサービスルーチン(ISR;Interrupt Service Routine)に分岐します。従って、ISR内では以下の様な基本的な規則を遵守しなければなりません。
割り込みの基本規則:
これを踏まえて、最初に思いつくアイディアは、汎用レジスタバンクを使う事、です。
全汎用レジスタのスタックへの退避・回復には大きなオーバヘッドがあるので、これを回避できるのは大きいです。
汎用レジスタバンクを活用した、全汎用レジスタの退避・回復の長所については、前節で述べた通りです。
ところが、割り込みレベルを使用する場合、より低いレベルの割り込み処理(ISR)中に、より高いレベルの割り込みが発生するため、バンクを切り替えたとしても、ISR内設定バンクの汎用レジスタが破壊されてしまい、破綻します。
なお、割り込みレベルを使うか否かは、intctlレジスタのLEVEビットを設定することで、ユーザが選択可能で、リセット解除後の初期値は「割り込みレベルを使用しない(intctl[LEVE]=0)」です。
そう考えていくと、受け付けた割り込みレベルに従って、バンクが自動的に切り替わるような機能があると良かったのですが、Moscoviumにはそのような機能は備わっていません。その様な機能が備わっていたとしても、Nihonium 32ビットモード(NHビット=1)の場合、汎用レジスタバンクは2組しかないので、4レベル分のバンクには不足してしまいます。
※全汎用レジスタが入れ替わる汎用レジスタバンクは、ユーザプログラムでは意外と使い難く、用途が限定される印象がある。いっそ、割り込み応答時間を短縮する目的の利用に専用化してしまった方が良かったのかもしれない……。
以上をまとめてみると、以下の様になります。
以上より、デフォルトでは汎用レジスタバンクを使って全汎用レジスタを退避・回復する仕様とします。
前節までに検討した通り、割り込みレベルを有効にする場合には、スタックを使うのが順当だと解りました。実際に退避・回復するアセンブラソースリストを組んでみると、以下の様になりました。スタックを使う場合と、汎用レジスタバンクを使う場合の2通りについて検証してみます。
スタックへの全汎用レジスタ退避:
スタックからの全汎用レジスタ回復:
以上の通りオーバヘッドが大きく、コード効率も実行速度も低下します。
尚、スタックに積む(pushw命令)のとは、逆順に取り出す(popw命令)必要があるので、pushwmとpopwmでは、汎用レジスタの記述順番を逆転させる必要があることに留意してください。
汎用レジスタバンクを使った全汎用レジスタ退避:
汎用レジスタバンクを使った全汎用レジスタ回復:
汎用レジスタバンクを使うと、汎用レジスタの退避・回復に掛かるオーバヘッドが大幅に削減可能だと解りました。
ISR内の処理内容は、割り込み応答時間に直結しています。
CPUの通常処理を止めてでも、優先的に処理しなければならない仕事が、割り込みで要求されるのです。少しでも早く、割り込み処理に取り掛かりたい、即ち、割り込みに対する応答時間を極力短くしたい、これが要求事項です。
これまた難しい判断なのだけど、以下の取捨選択に迫られることになります。
何しろ、割り込みレベルは、割り込み処理に優先順位(レベル)を振って、より高い優先順位の割り込みを優先的に(先に)処理する仕組みで、より迅速に処置したい処理の優先順位を上げることができる機能です。
そのような重要な機能を使おうとすると応答時間が伸び、応答時間を短縮しようとすると割り込みレベルが使えなくなる、という板挟みです……。
前節の「板挟み」状態を解消する手段は、現在のところありません。そこで、ユーザに選択権を委ねるべく、変換オプションを用意しておきました。
汎用レジスタバンクを活用するための条件は、Moscovium/Nihonium共、割り込みレベルを使わない事(intctl[LEVE]=0)です。intctl[LEVE]=0に於いて汎用レジスタバンクを使う事により、割り込み応答時間が短縮され、コード効率も上昇します。
intctl[LEVE]も、"@isr_bank@"も、明示的に設定しなかった場合のデフォルトは、
と、整合が取れており、かつ、割り込み応答時間も最短と、なっています。
ユーザが割り込みレベルを使用する(intctl[LEVE]=1に設定する)場合、必ず忘れずに、変換オプション"@isr_bank=0@"を設定してください。
taskブロックを改定して、"IRQ"および"IRQ2"タスクを追加します。
CPUが割り込みを受け付けると、まず、ランタイムライブラリのISRに制御が移ります。ISRが全汎用レジスタを退避した後、IRQ(またはIRQ2)タスクへサブルーチン分岐します。
IRQタスクから抜けてランタイムライブラリのISRへ復帰(rtnw命令等)すると、全汎用レジスタを回復した後、割り込みから復帰します(rti命令)。
ランタイムライブラリのISRが実行する動作は、汎用レジスタの処置とIRQタスクへのサブルーチン分岐だけです。従って、割り込み要因の判定や割り込みフラグクリアなどの必要な処置は、IRQタスクの中で、ユーザが実施する必要があります。
もし、task IRQブロックが宣言されない状態で割り込みを受け付けると、ランタイムライブラリのISRは何も処置せずに復帰(rti命令)します。割り込みフラグクリアなどの必要な処置が実行されないので、無限に割り込みがかかり続け、ハングアップする事になります。
CPUの割り込み許可レベルを設定するirq enableブロックを追加します。
割り込み許可レベルは、CPU状態レジスタsrのIEフィールドに設定する値です。リセット解除後のデフォルトは、割り込み許可レベル0で、全ての割り込み受け付けを禁止する状態です。
割り込み許可レベルを制御して、割り込みを許可/禁止するためのブロックです。
Blockly for Moscovium開発環境の構築については、こちらの記事をご参照ください。
既に環境を立ち上げ済みの場合でも、今回の拡張に伴い、以下のファイルを更新する必要があります。
今回拡張したBlockly.XML→Moscovium.ASM変換スクリプトは、こちらからダウンロードしてください。
Cmod A7ボード用Moscoviumマイコンの設計データ(Vivadoプロジェクト)は、GitHubにて配布しております。以下バナーよりアクセスしてください。
今回、FPGAは使いませんが、Cmod A7ボード(FPGA)への書き込み方法に関しては、こちらをご参照ください。
Blocklyの割り込み機能検証用プログラムを設計してみましょう。
今回も、作成したプログラムを使って論理シミュレーションを実行し、動作検証と致します。
デュアルコア版Moscoviumでtask IRQ2ブロックの検証もしたいので、CPU間相互通信FIFOユニットICFF16を使い、2個のCPU間で割り込みを使いながら通信するプログラムにしたいと考えます。
まず唐突に、トランシーバの使用例からどうぞ。
例)トランシーバを使った会話(通信)
対するICFF16ユニットは、全二重通信なので、2個のCPUが同時にデータを送受信可能です。しかし、受信したデータを表示するためのuart printブロックが共有リソースなので、排他制御する必要があります。
セマフォなどを使った排他制御を実施する代わりに、トランシーバによる会話に倣って、敢えて半二重通信に制限することで、排他制御したのと同じ効果を得ることができます。以下に、機能検証用プログラムのフロウをまとめてみます。
プログラムフロウ:
pause命令は、割り込み要求IRQ信号がアサートされるまでCPUを停止して待機する命令なので、無駄な電力消費を抑止しながら、相手CPUからの割り込み要求を効率良く待機することができます。
勿論pause命令で待機する事は必須では、ありません。割り込み要求が発生するまでの間に実行すべき仕事があるなら、CPUは別な仕事を実行できるので、なお良し、です。
task IRQブロックを使ったBlocklyのコード部分を以下図に示します。
割り込みを受け付けると、ランタイムライブラリのISRから上のtask IRQブロックへサブルーチン分岐して来ます(※)。
今回のプログラムでは、受け付ける割り込みはICFF16ユニットのFRARx割り込みだけですので、IRQタスクに分岐して来たという事は、FRARx割り込みを受け付けた時だけです。
※デュアルコア版のCPU2(サブCPU)に於いて受け付けた割り込みは、IRQ2タスクへサブルーチン分岐して来ます。今回の例では、CPU1/CPU2共に同じ処理なので、同じ内容を記述してあります。
上図の通りIRQタスクではまず、icff_recv_line手続きを呼び出して、終端データ0x00を受信するまで、文字列と見なして受信します。icff_recv_line手続きでは、文字列変数lbufへ文字列データを受信します。受信が終わったら、一旦、FRARx割り込みを禁止(上図のmem writeブロック)して、IRQタスクを終了します。
受信データの処理(uart printブロックで受信データを表示)と、こちらからのメッセージ送信が終わると、もう一度FRARx割り込みを有効にして、相手からのデータ受信を待つ状態に遷移するのは、こちらのフロウで解説済みです。
上図例では、IRQとIRQ2で全く同じ処理をしている様なプログラムに見えますが、以下の様に、実は全く異なる処理をしています。
CPU1とCPU2が別々なI/Oユニットからの割り込みを受け付けるケースでは、全く異なる処理内容を記述することも可能です。逆に、上図の例が特殊なケースだった、と言う事ができるでしょう。
完成したプログラムを以下にリンクしておきます。Blockly for MoscoviumのXMLタブに貼り付けて、ご覧ください。
完成プログラム:blky_task_irq.xml
※ブラウザによっては、XMLファイルをレンダリングして表示してしまうので、「右クリック ⇒ リンク先を保存」などしてください。
変換・アセンブル手順については、こちらをご参照ください。
FPGAに書き込んで実機で確認する程の事ではないのと、返って、シミュレーションで内部信号を確認した方が、より詳細に動作検証可能な案件です。今回は、作成したプログラムを使って論理シミュレーションを実行し、動作検証といたします。
LSIのデザインとしては、SMPデュアルコアとなる、以下の型名などから選択して使ってください。
こちらのプログラムをアセンブルして、シミュレーションします。Vivadoによるシミュレーション手順についてはこちらでご確認ください。
実験サンプル:シミュレーションログ
CPU2 (#1): hello, CPU2
CPU1 (#2): how are you? CPU1
CPU2 (#3): fine thanks, &you?
CPU1 (#4): fine, thanks, too
CPU2 (#5): see you
CPU1 (#6): bye
Test Pass
CPI: 12979/ 7342( 0)= 1767/1000
finish: 539000 ns
CPU1とCPU2が3回づつメッセージを送信します。互いに、受信したメッセージをuart printブロックでエコーしています。上のログを見る限り、フロウで説明した通りに動作した様です。
GTKWaveを使って、シミュレーション波形も確認しておきます(下図)。
シミュレーション波形確認のポイントを以下にまとめます。
Blocklyで割り込みをハンドリングできるように拡張しました。ようやく、一通りマイコンとして、まともなプログラム開発にBlocklyを使えるようになったと思います。後は、使いながら改善して行く感じで良いでしょう。ただ、ちょっと通信速度が、ちんたらと遅い印象があるので、次回もう一回、この辺りを検証してみようと思います。