Renesas



Section1
Section2
Section3
Section4
Section5




SH-2AとSH2A-DUALの機能紹介と効率アップのワンポイントアドバイス

4. SH2A-DUALのマルチプロセッサ機能

それではデュアルコアであるがために持たなければいけないマルチプロセッサ機能を紹介します。最初はスタートアップ手順からです。デュアルコアになったらスタートアップはシングルコアとどう変わるのでしょうか?今、表示されている内容はシングルコアのスタートアップ手順です。SH-2Aのシングルコアではリセットを含めた全ての例外処理はベクタ方式。

リセット直後、CPUはベクタテーブルからリセットPCとリセットSPを読み込んでPowerON_Reset関数が実行されます。PowerON_Reset関数内ではCPGやPFC、BSCの初期化を行い、ライブラリ関数を使って静的変数の初期化を実施後、VBRを初期化してからmain関数を実行します。これがシングルコアの基本的なスタートアップ手順です。

実はデュアルコアになっても、この基本的なスタートアップ手順は変わりません。CPUが2つあるだけですから、この手順をそれぞれのCPUで実施するだけです。ただし、1つだけ注意しなければならないことがあります。

それはCPU0とCPU1は同じPC、同じSPでスタートアップすることです。リセット端子が1つであるため、パワーオン・リセット時は両方のCPUが同時にリセットされます。しかも、両者は同じPC、同じSPで立ち上がります。つまり、リセット・ベクタは一つしかないのです。そうなると同じプログラムがCPU0とCPU1の2つで動作してしまいます。そこでスタートアップ・プログラムでは「CPU IDレジスタ」でどちらのCPUであるのかを判断してください。CPU IDレジスタの30ビット目だけは、各CPUから読める値が違います。CPU0からはID=0が読み出され、CPU1からはID=1が読み出されます。このビットをプログラム内で判断し、どちらのCPUであるのかを識別してください。

結果、SH2A-DUALのスタートアップは、このようになります。リセット直後はCPU IDレジスタを読んでどちらのCPUかを判断し、その後はシングルコアと同じように各CPUのスタートアップ処理を実行してください。これがSH-2A-DUALの立ち上げ手順となります。ただし、この方法ではフローチャートに色付けした通り、3つのプロジェクトが必要となってしまいます。そこで、このフローチャートを改良し、スタートアップは次に示す2つのプロジェクト構成とします。

プロジェクトはCPU0用とCPU1用の2つです。そして、CPU0用のPowerON_Reset関数内でCPU ID番号の判定処理を入れます。その結果、ID=0ならば、そのままCPU0用のスタートアップを行い、ID=1ならば、CPU1用のPowerON_Reset関数に起動を与えて、スタートアップを行います。これならプロジェクトが2つで済むことになりますから、現状ルネサスでは、こちらのスタートアップ手順を推奨しています。それでは具体的なコーディング例を見てみましょう。初めはCPU0用のPowerON_Reset関数です。

これがCPU0用のPowerON_Reset関数です。ただし、リセット時はCPU0、CPU1共にこのプログラムが実行されます。そこで真っ先にCPUのID番号を判断し、ID=0ならば、そのままCPU0のスタートアップを実施し、ID=1ならばCPU1用のPowerON_Reset関数を呼び出します。

ただし、ここで一つの問題が発生します。それはCPU1用のPowerON_Reset関数を呼び出すと言っても、目的の関数がメモリマップ上の何番地に配置されているのかを知る手立てがありません。そこでCPU1用のリセットベクタの配置場所を予めお互いの間で決めておいてください。ここでは0x100000番地にCPU1用のリセットベクタがあると仮定しています。

そして、こちらのコーディングがCPU1用のPowerON_Reset関数です。このプログラムはCPU0用のPowerON_Reset関数から呼び出されます。こちらはシングルコアの時と同じスタートアップ手順を実施すれば良いのですが、こちらにもひとつの問題点があります。それはSPの初期化です。関数呼び出し時点において、SPはCPU0用のスタック領域を指しています。SPをCPU1用のスタック領域を指すように変更しなければなりません。現状SH-2AではSPを初期化する命令をC言語で書くことはできません。そこでSPの初期化にはインラインアセンブラの機能を利用します。その値はCPU1用のリセットSPに設定されていますから、その値を読み出し、インラインアセンブラでSPへの設定を行います。

将来デュアルコアのマイコンを利用される方には注意して頂きたい項目があります。それはデュアルコアになった場合、LEDを点滅させるだけの簡単な処理でさえ、シングルコアの時と同じように実施してはならないと言うことです。

その理由はSH2A-DUALの場合、CPUは2つ存在していますが、周辺機能は1つしか存在しないと言うことです。結果、1つしかないI/Oポートを使って、複数のLEDを制御する場合、競合が発生します。例えば、同じI/Oポートのレジスタをお互いのCPUから同時に操作した場合、バスの競合が発生します。バスの競合自体はマルチバスレイヤが回避してくれますが、その結果、CPU1がバス待ちとなり、先にCPU0が現在のI/Oポートの値を読み出すことになります。読み出しが完了したCPU0は、続いて担当している左側のビットの反転を行います。その際、CPU0はバスを利用しませんから、CPU1も現在のI/Oポートの値を読み出すことができます。反転を行ったCPU0は、その結果をI/Oポートに書き込みます。その結果、左側のLEDの点灯状態は反転されますが、そのことをCPU1は知りません。反転前のI/Oポートの値を読み込んでしまっていますから、担当している右側のビットの反転を行い、その結果をI/Oポートに書き込んでしまいます。最終的に両方のCPUがLEDの反転処理を行ったのですが、左側のLEDの点灯状態は変更されない結果となってしまいます。これがマルチコアであるがために発生する共有リソースの競合です。

このような共有リソースの競合を回避するため、SH2A-DUALには排他制御の機能があります。それが、ここに示す32個のセマフォ制御レジスタです。このレジスタの一番右側のビットはセマフォフラグであり、このフラグは、テスト&クリアが可能なフラグとなっています。つまり、現状の状態が1であるか、0であるかをテストすることができ、テスト後は必ず0にクリアされます。

読まれたら0になるわけですから、共有リソースが空いている状態は1と考えることになります。そして、共有リソースにアクセスする時は、このフラグが1であることを確認し、1であれば共有リソースが空いていると考えます。ただし、読んだ時点でフラグは0になりますから、その瞬間から共有リソースは空いていないことになります。そこで共有リソースを処理後に解放する時は1をライトしてください。これで排他制御が可能となります。

それではセマフォ制御レジスタによる排他制御のサンプルプログラムを見てみましょう。初期状態はセマフォフラグが0になっていますから、初期化時にはどちらかのCPUで1をライトしてください。これで排他制御の開始となります。

共有リソースを使用する時は、1がリードできるまでwhile文で待ってください。1が読めたら資源が獲得できたと考えて、共有リソースを処理します。処理後は1をライトしてください。これで排他処理ができます。以上のように同じ共有リソースの操作はセマフォ制御レジスタで制御してください。

なお、このような排他制御は本来であれば避けてください。紹介したコーディング例であれば、確実に排他制御を行うことが可能です。しかしながら、資源の獲得できないCPUは空回りしているだけです。その間はデュアルコアの性能を発揮することができません。つまり、一番良い方法は同じ周辺機能を異なるCPUで操作することは本来やめるべきなのです。例えば、ポートFはCPU0でしか使わない。ポートEはCPU1で使わない。このようにすれば排他制御の必要はありません。内蔵されている周辺機能は、初めからCPU0とCPU1で完璧に分離してください。そして、どうしても分離することができない部分に対してのみ、セマフォ制御レジスタで競合を回避してください。

ここでは各CPU間で連絡に利用するプロセッサ間割り込みを紹介します。やはり、1つのパッケージ内に実装されている2つのCPU間ではお互いの間で連絡を取りたい時があると思います。その機能がプロセッサ間割り込みです。

SH-2A-DUALでは8+8、合計16個のプロセッサ間割り込み制御レジスタを持っています。8+8の意味は、CPU0に対して8種類、CPU1に対して8種類、合計16個の種類の割込みがあると言うことです。プロセッサ間割り込みの使い方は簡単です。割り込みを入れたかったら、プロセッサ間割り込み制御レジスタの一番右側にあるCIビットに対して1を設定してください。その瞬間、自分ないしは相手に対して割り込みが発生します。

なお、プロセッサ間割り込みの割り込み優先レベルは8から15の範囲で固定です。プロセッサ間割り込み制御レジスタには、各々8から15までの番号が付いており、それがそのまま割り込み優先レベルに対応しています。レジスタの8番を使えばレベル8、レジスタの15番を使えばレベル15で割り込みが要求されます。

また、プロセッサ間割り込みはイネーブル/ディスエーブルを制御することが可能です。プロセッサ間割り込みイネーブルレジスタの対応したビットに1が立っているプロセッサ間割り込みのみ有効であり、0の場合は無効となります。なお、プロセッサ間割り込みはエッジで要求されます。言い換えれば、他の内蔵周辺機能と異なり、割り込みを受ければ自動的に停止します。

これがプロセッサ間割り込みのサンプルプログラムです。この例では割り込み優先レベル8のプロセッサ間割り込みを有効とし、CIビットに1を設定してお互いにプロセッサ間割り込みを要求しています。

ここでは高速内蔵RAMを紹介します。SH2A-DUALの場合、動作周波数200MHzで動作するのはCPUコア部分だけです。内蔵周辺機能や外部メモリはマルチバスレイヤに接続されており、こちらは最速でも70MHz程度が限界です。CPUのコア部分は最速200MHzのCPUバスが存在し、そこにCPU、FPU、キャッシュ、そして高速内蔵RAMが接続されています。それはCPU0、CPU1共に共通です。従って、システム作成時はキャッシュメモリと同様に出来るだけ高速内蔵RAMを有効に使ってください。

また、この高速内蔵RAMは他のCPUからも操作できるという特長を持っています。例えば、CPU0の高速内蔵RAMはCPU1からバスブリッジとマルチバスレイヤを介して操作することが可能です。同様にCPU1の高速内蔵RAMはCPU0からバスブリッジとマルチバスレイヤを介して操作することが可能です。つまり、高速内蔵RAMは、自分専用の内蔵RAMとして利用しても構いませんし、お互いから使える2ポートRAMとして利用しても構いません。CPU間で連絡を取り合う際に割り込みだけで良い場合はプロセッサ間割り込みを使い、更にデータのやり取りも行いたいのであれば、高速内蔵RAMを利用することになります。

高速内蔵RAMを自分専用の内蔵RAMとして利用するのか、2ポートRAMとして利用するのかは、アクセスする番地で操作してください。アドレス空間の示す番地で高速内蔵RAMを利用した場合、それはCPUバスを使うことを意味しています。この場合は2ポートRAMとしては使えません。逆に2ポートRAMとして使うときはシャドー空間の示す番地を利用します。シャドー空間で操作した場合、それはバスブリッジとマルチレイヤバスを介して操作します。

また、2ポートRAMとして使用するときには排他制御が必要となります。排他制御にはTAS命令を使います。セマフォ制御レジスタはテスト&クリアでしたが、こちらはテスト&セットです。テストした後の状態が違いますから、資源が空いている状態は逆の考え方となります。

高速内蔵RAMで排他制御を行うには、排他制御に利用する1バイトの領域をシャドー空間上に取ります。そして初期化時には、どちらかのCPUで0をライトします。これで排他制御が開始されます。

TAS命令は組み込み関数で記述可能です。ただし、組み込み関数のリターン値は、読み込んだ値の反転状態が返されますから、共有リソースを操作する時にはTAS命令で1が返されるまで待ってください。1が返されたら、共有リソースを操作します。そして操作後は0をライトして資源を解放してください。

この例では高速内蔵RAM0のページ2の先頭バイトを排他制御に利用する番地と考えています。また、共有資源としては高速内蔵RAM0のページ2とページ3と考えており、資源獲得時は目的の領域に対してCPU0は0xAAを書き込み、CPU1は0x55を書き込んでいます。

スタートアップ手順の紹介ではシングルコアと同じと言う説明を行いましたが、実は異なる部分が1箇所あります。それはBSCの初期化です。SH2A-DUALの場合、CPUは2つありますが、内蔵周辺機能は1つです。つまり、BSCも1つしかありません。もし、お互いのCPUで共にBSCの初期化を行った場合、正しく動作しない可能性があります。BSCの初期化は、どちらかのCPUだけで実施すれば良いことになります。

また、その際には1つの問題点があります。それは、どちらかのCPUがBSCの初期化中、他方のCPUは何も出来ないことです。命令は外部メモリに格納されていますから、BSCの初期化中には命令を読むことすらできません。何かしらの対策が必要です。

対策の(その1)は、一方のCPUがバスの初期化中、もうひとつのCPUはSLEEP命令で停止させる方法です。停止中は、命令を読まないので外部バスを使うことはありません。また、初期化が終わったら、プロセッサ間割り込みを使ってSLEEP命令を解除します。

対策の(その2)は、一方のCPUがバスの初期化中、もうひとつのCPUは高速内蔵RAMで動作させる方法です。高速内蔵RAMは自分専用のメモリですから、外部バスの影響を受けません。初期化の開始、終了はセマフォ制御レジスタを利用して連絡します。

このサンプルプログラムは、対策(その2)の例です。CPU0はセマフォ制御レジスタ1を使って、CPU1がバス待ちの状態に入るのを待ち、BSCの初期化後はセマフォ制御レジスタ2を使って、そのことをCPU1に連絡します。CPU1では下の関数を使い、高速内蔵RAMでCPU0によるBSCの初期化を待ちます。そこで上の関数ではセクション名を「P1」に変更した下の関数をwhile文で高速内蔵RAMに転送し、その関数に対して呼び出しを行います。高速内蔵RAMで動作する下の関数では、BSCの初期化待ちに入ったことをセマフォ制御レジスタ1を使ってCPU0に連絡し、その後は処理の終了をセマフォ制御レジスタ2を使って待ちます。