Cmod A7ボード(FPGA)を使って、オリジナル16ビットCPUコアMoscoviumの開発を進めています。以下記事などで、Moscoviumマイコンのプログラム開発にBlocklyを使用できるように整備しました。
「#31-6 Moscoviumの開発環境(カスカス)」
まだまだ、漸くBlocklyを使ってプログラム開発可能な環境が整った、というレベルで発展途上です。
Moscoviumでは、デュアルコアを選択可能ですので、Blocklyでも、デュアルコア用プログラムを開発できる様に改定して行きたいと思います。それが今回のテーマです。
M5StickC Plus(ESP32)のFreeRTOSを使って、マルチタスクプログラムを組んだことがありました(以下記事を参照)。
「#24-4 FreeRTOSでマルチタスク」
上の記事では、FreeRTOSのAPIを叩いて、スレッドと呼ばれる実行単位を起動し、マルチタスクを実現しました。
FreeRTOSはプリエンプティブマルチタスクなので、1個のCPUが時分割で複数の処理を並列に進め、疑似的にマルチタスクを実行することもできます(下図b)。加えてESP32もデュアルコアなので、2個のCPUコアにタスクを割り振ることも可能です(下図c)。
更に、2個のCPUコアに於いて、プリエンプティブマルチタスクを適応すると、3タスク以上の並列処理も可能なのです。
本当はMoscoviumでも、ちゃんとしたOSらしきシステムを組んで、スレッドと呼ばれる実行単位を自由に起動できる様にしたかったのですが、それは筆者の実力的に無理がありました……。
本ページでは、せめて、CPUコア毎に別々なプログラムに分岐させて、2本のタスクを同時並列に実行可能としたいと思います。スレッドと呼ぶには程遠いので、これをマルチコアタスクと呼ぶことにします。上図のc)に相当する動作です。
Moscoviumのデュアルコアは、SMPとAMPを選択することができます。
Blocklyでは、CPUコア毎に異なるプログラムを用意して、実行させることもできます。一方、基本的な関数や手続きなどは、両CPUコアから共通して実行したいケースもあるでしょう。
数値変数や演算には、符号付整数演算と浮動小数演算を選択することができます。整数演算に至っては、乗除算器を使うかどうかも含めて、ランタイムライブラリでエラーチェックしています。
Blocklyのプログラムとしては、実行するCPUコアの性能がどうか?は関係なく、プログラムで使用した機能に応じたコードを出力します。
以上をまとめると、CPUコア毎に性能や機能が異なる、AMPデュアルコアは非常に都合が悪いのです。
※AMPにはAMPの長所があります。同じプログラムコードを共有して実行する、今回の応用ではSMPの方が適していただけの事です。
よって、Blocklyの対象はSMPデュアルコアとし、AMPデュアルコアで実行しようとすると、ランタイムライブラリがエラー停止する事とします。なお、SMPデュアルコアとAMPデュアルコアの判定は、CPU状態レジスタsrのIDフィールドで実施可能です。
共有メモリとは、デュアルコアCPU1およびCPU2から、共通してアクセスされるメモリ領域を指します。主にCPU間の通信などデータ授受に使用されます。
一般に、共有メモリのアクセスには、排他制御(セマフォ等)が必要です。
例)Blocklyの変数
ぱっと考えつくだけでも、回避策2)は不都合が多い上に、影響も大き過ぎます。もし、Blocklyが使用する変数を共有メモリに割り当ててしまうと、全ての変数アクセスが排他制御を必要としてしまい、破綻するでしょう。
よって、各CPUコアが参照する変数領域は、異なるRAM領域(アドレス範囲)に設定しなくてはなりません(回避策1)。回避策1)の短所は、メモリ(RAM)の容量を確保することで容易に回避可能です(力業💪ですが……)。
サブルーチン分岐や、一時的なデータの退避・回復などに使用するスタック領域もしかり、です。
Blocklyが使うRAMは、大きく2つの領域に分かれています。
Blocklyの記憶領域:
今までのシングルコア版Blocklyでは、以下図左側メモリマップの様に割り当てていました。
RAM領域の先頭(上位アドレス)から下位アドレスに向かって、変数領域を割り当てて行きます。
Moscoviumのスタックはフル下降型なので、RAM領域の底(最下位アドレス)から、先頭(上位アドレス)に向かって使って行きます。
デュアルコアにすると、どうなるか?と言うと、上図右側のメモリマップと、なります。
「変数領域・スタック領域共、CPUコア毎に分離しなければならない」と言う事が、趣旨です。各領域の割り当てはランタイムライブラリが担当し、以下の様に決定します。
ランタイムライブラリによる、領域割り当て規則:
命令RAM(I-RAM)は、RAMの様に読み/書きできて、CPUが命令コードをフェッチすることも可能なメモリです。
I-RAMはメモリバス側からのオペランドアクセスと、フェッチバス側からのフェッチアクセスに対処するため、デュアルポートRAMマクロを使っています。この上、2個のCPUコアから同時にフェッチアクセスがあると、ポートが足りないためアクセスできません。
デュアルコア版で機能させるため、I-RAMはCPUコア毎に、物理的に異なるRAMが配置されています(下図)。
つまり、CPU1から見えるI-RAMと、CPU2から見えるI-RAMは、アドレス範囲は重なっていて同じですが、物理的に異なる別物であり、各CPUの専用メモリ(⇔共有メモリ)です。
ユーザが「I-RAMに命令コードは一切配置せず、プログラム実行しない」と決めれば、CPUコア毎に専用のメモリであることを除いて、普通のRAMとして使うことができます。
前の節で示したエリアマップを思い出していただくと、Blocklyの変数領域などを非共有化するために、RAMの記憶領域を分離しています。
この変数領域をI-RAMに割り当てると、以下図の様になります。
同じアドレス範囲にマッピングされたI-RAMですが、CPUコア毎に物理的に異なるRAMですので、上図の様に、同じアドレス範囲でありながら、変数領域を分離することができます。
変数領域にI-RAM領域を割り当てるには、変換オプション"@var_iram@"を設定します。
ランタイムライブラリによる、領域割り当て規則(I-RAM活用時):
ここまで、変数領域やスタック領域を分離する(非共有化)施策を取ってきました。BlocklyのプログラムをCPUコア毎に独立して実行するために必要な措置だったから、です。
逆に今後、より高度なプログラムを作る過程では、共有メモリなどを含め、CPUコア間でデータをやり取りする手段の実装方法がカギとなります。
CPU間の通信手段(例):
各々、mem read/writeブロックなどを使ってI/Oレジスタを設定し、ユーザが制御可能なので、適宜対応してください。
対応方針の検討と基本設計が終わりましたので、残りは次回に完成させようと思います。