メモリタイプとアクセスオーダ

メモリタイプとアクセスオーダの必要性

今回はメモリに関する基礎知識として「メモリタイプとアクセスオーダ」について解説します。プロセッサは、すべての命令がプログラム順序で実行され、各命令が完全に終了してから次の命令が開始されます。最新のプロセッサでは、実行速度を上げるために、命令完了の入れ替えやキャッシュ機能を搭載し、コンパイラはプロセッサの実行速度向上のために最適化を行います。これらにより、プログラムの命令順通りにメモリアクセスが行われない可能性があります。この動作を理解するためには、「メモリタイプ」や「アクセスオーダ」、「C/C++コンパイラの最適化」の知識が必要になります。

DMAコントローラを使用して、RAM上に配置されている変数を通信コントローラに転送する場合、次の手順でメモリ上のデータ転送を行います。

  • ①RAM(ノーマルメモリ)に転送データの書き込み。
  • ②DMAコントローラ(デバイスメモリ)のレジスタに書き込み後、DMA転送を開始。

DMA転送を正常に行うためには、RAM(ノーマルメモリ)上の転送データの書き込みが、DMAコントローラ(デバイスメモリ)を起動する前に完了していることが前提となります。メモリタイプとアクセスオーダでは、次の動作が定められています。

ノーマルメモリとデバイスメモリ間でのアクセス順序は規定されていません。

この課題を理解するためには、「メモリタイプ」と「アクセスオーダ」の理解が必要で、アクセス順序を守るためには、バリア命令(第11回参照)を使用することが必要です。

図1

メモリタイプ

ARMプロセッサでは、メモリタイプとして3種類を定義しており、用途によって設定しなければなりません。メモリタイプの設定は、MMU(Cortex-Aシリーズの場合)、MPU(Cortex-Rシリーズの場合)で定義します。

No メモリタイプ 内容
1 ノーマルメモリ C/C++プログラムで作成する関数や変数(構造体を含む)を配置します。
※キャッシュを使用できます。
2 デバイスメモリ ペリフェラル(UART/GPIOなど)を配置します。
※ライトバッファは使用できますが、キャッシュは使用できません。
3 ストロングリ
オーダメモリ
ライトバッファおよびキャッシュは使用できません。

アクセスオーダ

Cortex-Aプロセッサは、メモリタイプに応じて、アクセスの順番が保障されない場合があります。LDR命令またはSTR命令を実行した場合、【アクセスオーダ 一覧表】の「〇」は、Addr1が先に完了することを保障します。

								LDR/STR  r1,[r0]          ; r0レジスタには、Addr1アドレスが設定されています。
								LDR/STR  r3,[r2]          ; r2レジスタには、Addr2アドレスが設定されています。
								
  • ●「LDR r1,[r0]」命令は、r0レジスタの示すアドレスから32ビットデータを読み込み、r1レジスタに読み込みます。
  • ●「STR r1,[r0]」命令は、r0レジスタの示すアドレスに、r1レジスタの32ビットデータを書き込みます。

ここでの注意点として、デバイスおよびストロングリオーダに配置されている場合は、アクセス順序が保障されますが、ノーマルとデバイスまたはストロングリオーダ間のアクセス順序は保障されません。デバイスに配置された場合でも、共有デバイスと非共有デバイス間では、アクセス順序は保障されません。

図2

【アクセスオーダ 一覧表】

Addr1/Addr2がオーバーラップしない、異なるアドレスのアクセスを示しています。同じアドレス範囲に対するロード/ストア命令はプログラム順に完結するので、コードの正常動作が保障されます。例えば、0x2000番地からのSTRD命令(64ビットストア命令)を実行した場合、0x2000番地から0x2007番地の範囲内のアクセスを行います。

STRB命令とLDRH命令は、0x2000番地から0x2007番地の範囲内に含まれますので、ロード/ストア命令はプログラム順に完結します。STRB命令は、8ビットストア命令、LDRH命令は16ビットロード命令です。

図1

C/C++コンパイラの最適化

C/C++コンパイラは、プログラムを最適化し、コード密度を向上させます。しかし、最適化の影響で必要なメモリアクセスが削除される場合があるため、volatile属性を使用しなければなりません。

volatile属性

volatile属性は、データの扱いに特定の属性を付与する予約語(const, volatile, restrict)です。volatile属性は、メモリ上に割り付けられた入出力ポートに相当するオブジェクト、または、非同期的な割り込み機構によってアクセスされるオブジェクトを記述するために使用します。volatile属性に宣言されたオブジェクトに対する操作は、式の評価の規則で許されている場合を除き、処理系の最適化により削除または順序の変更が行われることはありません。詳しくは、JIS規格(規格番号:X 3010/プログラム言語C(Information technology - Programming languages - C)を参照ください。

volatile属性を設定しない場合の事例

PORT3レジスタの定義にvolatile属性の設定を行わず、PORT3レジスタを2回読み込んだ結果が同一の場合、次の処理を実施するプログラムの動作確認をします。

								#define PORT3	(*(unsigned long *)0x40000000)	// ポート3レジスタ

								    while(1){
								        if( PORT3==PORT3 ){						// 2回連続読み込んで一致?
								            break;
								        }
								    }
								

【アセンブラ命令展開例】
「LDR r0,[r0]」命令でPORT3レジスタを1回読み込み、同じ値の比較を行います。

								LDR      r0,=0x40000000	; PORT3レジスタのアドレスを設定
								LDR      r0,[r0]		; PORT3レジスタを読み込み
								CMP      r0,r0			; 読み込み結果を比較
								BNE      …
								

volatile属性を設定した場合の事例

PORT3レジスタの定義にvolatile属性の設定を行い、PORT3レジスタを2回読み込んだ結果が同一の場合、次の処理を実施するプログラムの動作確認をします。

								#define PORT3	(*(volatile unsigned long *)0x40000000)	// ポート3レジスタ

								    while(1){
								        if( PORT3==PORT3 ){
								            break;
								        }
								    }
								

【アセンブラ命令展開例】
「LDR r0,[r0]」命令と「LDR r1,[r1]」命令でPORT3レジスタを2回読み込むことで、正しく動作することを確認できます。

								LDR      r0,=0x40000000	; PORT3レジスタのアドレスを設定
								LDR      r0,[r0]		; PORT3レジスタを読み込み
								LDR      r1,=0x40000000	; PORT3レジスタのアドレスを設定
								LDR      r1,[r1]		; PORT3レジスタを読み込み
								CMP      r0,r1			; 読み込み結果を比較
								BNE      …