Renesas



Section1
Section2
Section3
Section4
Section5




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

5. 効率アップのワンポイントアドバイス


CPU機能から考えた場合、絶対に使って頂きたいのがキャッシュメモリです。最速200MHzで動作するSH-2AのCPUに対して、外部メモリのバスは最速でも70MHzです。外部バスが遅い関係上、このままではCPUの性能を発揮することができません。これに対してキャッシュメモリは、CPUの速度に対応可能です。CPUの性能を引き出すためには、必ずキャッシュメモリを有効に活用しなければなりません。

キャッシュメモリの操作はCCRで行います。SH-2Aの場合、それはCCR1となります。また、SH-2Aはハーバードアーキテクチャを採用しているため、命令用のバスとデータ用のバスが分かれています。それに伴って、キャッシュメモリも命令キャッシュとデータキャッシュ、別名オペランドキャッシュに分かれています。そして、このレジスタのビット8とビット0が、各々のキャッシュのイネーブルビットです。これらのビットに"1"を設定することでキャッシュメモリが有効となります。また、その際にキャッシュメモリを初期状態に戻す必要があれば、ビット11とビット3のインバリデイトのビットに"1"を設定します。なお、CCRを操作するに当たっては2つ程、制約事項があります。1点目はCCRを操作する命令はキャッシュメモリの無効な領域に配置しなければならないことです。2点目はCCRを操作後にダミーリードを行うことです。

これがキャッシュメモリを有効とする関数のサンプルプログラムです。CCRの1番に対して、0x909を設定し、命令キャッシュとオペランドキャッシュを共に有効とし、その後はif文を使ってダミーリードを行っています。なお、この関数には#pragma sectionの機能を指定し、セクション名を「PCache」に変更しています。リンケージの際はセクション「PCache」をキャッシュの無効領域に配置する必要があります。

キャッシュメモリは有効にさえしてしまえば、外部メモリとの間の入れ替え操作はキャッシュメモリが自動的に行ってくれます。従って、プログラマは何も考えることなく、CPUの性能を発揮することが可能となります。

ただし、1つだけ注意しなければならないことがあります。それはキャッシュメモリと外部メモリとの間のコヒーレンシ、すなわち一貫性の確保です。SH-2Aはコヒーレンシを確保するための命令を持っていません。コヒーレンシ確保はメモリ割り付けキャッシュを使うことになります。

例えば、命令キャッシュの場合、アドレス指定の示す番地にキャッシュの制御データがあり、その中のVビットに"0"を設定することで無効化を行うことが可能です。同様にオペランドキャッシュも、アドレス指定の示す番地にキャッシュの制御データがあり、その中のUビットやVビットに"0"を設定することでパージやライトバック等の書き戻しを行うことが可能です。

2つ目の関数が特定の番地の命令キャッシュを無効化する関数です。見て分かるようにメモリ割り付けキャッシュを操作して、命令キャッシュの無効化を行っています。そして、3つ目の関数が全ての命令キャッシュを無効化する関数です。全ての命令キャッシュを無効化する場合はメモリ割り付けキャッシュを操作するよりも、CCRの11ビット目にあるインバリデイトのビットを利用した方が効率的に優れています。

こちらはオペランドキャッシュのパージやライトバックを行う関数です。最初の関数が特定番地のオペランドキャッシュをパージする関数、2つ目の関数が特定番地のライトバックを行う関数、そして最後の3つ目が全てのオペランドキャッシュをパージする関数です。オペランドキャッシュの場合、CCRの3ビット目にあるインバリデイトのビットは使用できません。インバリデイトのビットは外部メモリに書き戻しを行わずに無効化してしまうため、これらの関数は全てメモリ割り付けキャッシュを操作しています。

SH-2Aのオペランドキャッシュにはロック機能があります。ウェイの2と3には、データをロックし、キャッシュメモリから逃がさないことが可能です。もし、システムの中で頻繁に使うデータ領域は、このキャッシュロック機能を使った方が良いでしょう。

キャッシュのロック機能はCCR2で制御します。16ビット目がロックイネーブルです。ロック機能を使用する場合、このビットには必ず"1"を設定してください。そして、9ビット目と8ビット目がウェイ3に対するロック・ビット、1ビット目と0ビット目がウェイ2に対するロック・ビットです。この両方のビットに"1"を立てるとキャッシュが開いた状態になります。開いたら、プリフェッチという命令でデータ領域をキャッシュにロードしてください。ロードが完了したら、ロード・ビットにだけ"0"を書き込んでください。その瞬間、目的のキャッシュの当該ウェイはロックされた状態となります。

これがロック機能を利用するためのサンプルプログラムです。第1引数はウェイ2にロックするデータ領域のアドレス、第2引数にはウェイ3にロックするデータ領域のアドレスを指定します。SH-2Aのオペランドキャッシュは1つのウェイが2KByteです。サンプルプログラムでは引数で指定された番地から2KByteのデータ領域を各々のウェイにロックしています。なお、このサンプルプログラムをシステムで利用する場合、その用途に応じて修正を行ってください。例えば、ロック機能は必ずウェイ2とウェイ3の両方に適用する必要はありません。片側だけのウェイをロックすることも可能ですから、必要に応じて修正を行ってください。

SuperHファミリの中で割り込み応答速度の一番速いのはSH-2Aです。その理由はレジスタバンクを15面持っているからです。例えば、SH-2A以外のSuperHファミリの場合、CPU内部のレジスタは1組しかありません。従って、通常のプログラムが実行中に割り込みが発生すると、割り込みプログラムでは使用するレジスタの退避・復旧を行わなければなりません。

これに対してSH-2Aは割り込みを受け付けるとレジスタバンクを切り替える機能を持っています。これにより、割り込みプログラムはレジスタの退避・復旧を一切行わずに割り込み処理を行うことが可能となります。なお、このようなレジスタバンクを15面も持っている理由は全ての割り込み優先レベルでレジスタバンクを切り替えるためです。割り込み優先レベルは1から15までの15レベルありますから、15面レジスタバンクを持てば全ての割り込み優先レベルでレジスタの退避・復旧が不要となるからです。

レジスタバンクの使い方は簡単です。「割り込まれたらバンクを切り替える」という指示と割り込みを終了する時に「バンクを元に戻す」という指示を与えるだけです。

バンクの切り替え指示はIBCRというレジスタで行います。各ビットが割り込み優先レベルに対するレジスタバンクの使用許可を意味し、"1"を設定した割り込み優先レベルでレジスタバンクが切り替わります。ただし、全ての割り込み優先レベルでレジスタバンクを切り替えるのが一般的な使い方です。

その際はIBNRのBEビットに対して"01"を設定します。そうするとNMI、ユーザブレークコントローラ以外の全ての割り込みでレジスタバンクの使用を許可することになります。なお、その際はIBCRの設定値は無効です。IBCRは、IBNRのBEビットが"11"のときだけ有効となります。

レジスタバンクの切り替え指示を行ったサンプルプログラムが上段の2つのプログラムです。左側が特定の割り込み優先レベルに対してのみレジスタバンクを有効に設定した例、右側が全ての割り込み優先レベルでレジスタバンクを有効に設定した例です。

また、割り込み終了時にレジスタバンクを元に戻す指示は#pragma interruptの後に割り込みしようとして記述します。割り込み仕様としてresbankが記述された割り込み関数は割り込み終了時にレジスタバンクを元に戻すRESBANKの命令を発行してから終了します。

ここからはコンパイラのオプションを使うことによって、効率を上げる方法を紹介します。最初は外部変数アクセスの最適化です。そこでオプションの内容を紹介する前に1つ理解して頂きたいことがあります。それはSuperHファミリは大域変数の操作が苦手なマイコンであるということです。

例えば、大域変数は絶対にメモリに割り付きます。従って、大域変数を操作する時は目的の変数の番地情報が命令中のどこかに必要です。SuperHファミリは4GByteのメモリ空間を持っているため、変数の番地情報は32ビット長になります。しかしながら、SuperHファミリは16ビット固定長命令を採用しています。そうなると命令の中に32ビットのアドレス情報を持つことができません。その結果、大域変数のアドレスはテーブルとして用意されます。そして、大域変数を使う時にはアドレス情報を汎用レジスタに読んでから、その内容を扱うことになります。簡単に言えば、大域変数はポインタ変数経由で操作するしかないのです。

このプログラムのように個々に大域変数を宣言した場合、17行目から21行目のように宣言した数だけアドレステーブルが確保され、3行目、4行目、9行目、10行目、12行目のようにアドレスをロードしてからでなければ大域変数を操作することができないのです。もし、アドレスロードの回数を削減したいのであれば、大域変数はプログラマ自らが構造体化しなければなりません。

その大変な作業となる大域変数の構造体化を自動的に行うのがコンパイラの持つ外部変数アクセスの最適化オプションです。このオプションには「モジュール内」と「モジュール間」の2つの設定がありますから、以下順番にコンパイル結果と共にオプションの内容を紹介します。

これは「モジュール内」の設定例です。「モジュール内」の設定ではソースリスト上に実体の宣言が記述されている大域変数のみ構造体化します。従って、変数a、b、cは構造体化可能となり、アドレステーブルは15行目の a 1つしかありません。しかしながら、extern宣言している実体のない変数 y と z は構造体化されません。16行目と17行目のように個々にアドレステーブルが準備されます。このように実体の宣言がある大域変数だけを構造体化するのがモジュール内です。

これは「モジュール間」の設定例です。「モジュール間」の場合、実体の宣言があってもなくても可能な限り構造体化します。従って、全ての大域変数が構造体化され、アドレステーブルは13行目の a 1つしかありません。つまり、「モジュール内」よりも「モジュール間」の方が、より強力に最適化の効果を得ることができます。

これから紹介するオプションはキャッシュメモリを持つSuperHファミリをご使用の方だけが対象となります。

キャッシュメモリを効果的に使うためにはキャッシュメモリの構造を理解する必要があります。その中でもキャッシュメモリのライン長だけは覚えた方が良いでしょう。例えば、SH-2AやSH-3はライン長が16バイトであり、SH-4、SH-4A、SH4AL-DSPはライン長が32バイトです。キャッシュメモリはデータの入れ替えを行う場合、このライン長単位で入れ替えを行います。

そして、このライン長を意識した配置に関数のマッピングを行うのがラベルの16/32バイト整合のオプションです。例えば、SH-2Aのライン長を意識し、ラベルの16/32バイト整合のオプションを16バイトに設定した場合、関数の先頭アドレス、並びに関数内の無条件分岐先やアドレステーブルの配置はアライン16の16バイト境界となります。

そのコンパイル結果が、このリストとなります。コンパイル結果の1行目や10行目が示す通り、関数の先頭には「ALIGN=16」が設定されます。また、19行目のように関数内に必要となるアドレステーブルに対しても「ALIGN=16」が設定されます。これで効果的に関数がキャッシュメモリに配置されることになり、指定しなかった場合に比べてキャッシュのヒット率を上げるということが可能となります。

なお、このオプションは変数領域に対しては効きません。関数だけが対象です。変数領域をライン長の先頭に配置するのはプログラマ自らが最適化リンケージエディタのオプションで設定する必要があります。