前言#
最近、ADC の性能を可能な限り限界まで引き上げたいと思い、関連する周辺機能について調査しました。
その中で、キャッシュと MPU 属性の適合部分は以前はあまり理解できていませんでしたが、今回は比較的明確に理解できました。キャッシュ機能を持つ ARM チップでは、MPU 周辺機能の設定と組み合わせて初めてキャッシュ機能を使用できます。
- キャッシュ:バッファは主にプロセッサコアがメモリをより連続的に操作できるようにするためのもので、いくつかのメモリ操作はバスを越える必要があるため、キャッシュは読み取り時にヒットしたメモリの近くをプリフェッチし、書き込み時にはある程度の遅延で連続性を確保し、最も重要なのは内部バスの衝突を減らしてデータ処理を加速できることです。
- MPU(メモリ保護ユニット):メモリ保護ユニットは ARM チップのメモリ保護メカニズムで、特定のメモリ領域のキャッシュ属性を設定するために主に使用されます。
他の用途もありますが、正直なところ他の用途はありません。例えば、異なるタスクスレッドのロード時にメモリ領域の設定を細分化することで、MMU に似た機能を実現できますが、普通の人はそんなに複雑なアプリケーションを作ることはありません。 - DMA(ダイレクトメモリアクセス):直接レジスタアクセスは、プロセッサの計算力を介さずに、周辺機器のレジスタからメモリに値をコピーでき、一定数(例えば 1024 個)をコピーした後に割り込み通知を生成し、そのデータを処理するために使用します。
MPU と MMU の主な違い
MPU | MMU | |
---|---|---|
バッファヒット周期 | 固定 1 命令周期 | 1〜20 周期不等 |
管理精度 | 限られた領域(十数個)管理 | RAM をページ単位で細分化 |
マルチタスクサポート | 物理領域を分割して有限隔離 | 仮想アドレスプロセス完全隔離 |
言い換えれば | リアルタイムアプリケーション用 | Linux を扱うための |
他の多くのチップにも類似のキャッシュと MPU 機能がありますが、ここで主に議論している ARM チップはその機能が類似しており、管理粒度、区分数、または管理できない固定属性の違いがあるだけで、その属性と対応する機能は類似しています。
バスの理解#
MCU チップの周辺機器は異なるバスに分布しており、同じバスの周辺機器とメモリ間の読み書きは相対的に速くなります。ここでは ADC、DMA、およびメモリの組み合わせについて、バスを理解します。
私のこのアプリケーションでは、DMA は同じバス上の周辺機器とメモリのみを操作できるため、ADC1/2/3、DMA1/2、BDMA1 を使用しています。
以下のバスの概念図を参考にしてください。ADC1 は DMA1 と組み合わせてサンプリングし、同時に D2 領域のメモリの一部を占有し、ADC3 は BDMA と組み合わせてサンプリングし、同時に D3 領域のメモリの一部を占有し、最終的にデータを初期処理してサンプリング値を D1 領域のメモリに置き、CPU がその後の計算に使用します。このように配分することで、可能な限りドメイン間の性能損失を減らします。
キャッシュ属性の設定#
MPU 設定はキャッシュ属性を制御し、最も重要な 3 つの設定項目は以下の通りです。私自身の簡単なまとめがあります。
- IsCacheable はキャッシュを有効にするかどうかを決定します。
- IsBufferable は書き込み命令もキャッシュバッファを使用してメモリに統一的に書き込むかどうかを決定します。IsCache を有効にしない場合、意味がありません。
- IsShareable は厳密なタイミング制御を採用するかどうかを決定します。これは、FMC が外部メモリにアクセスするような周辺機器にとって非常に重要です。なぜなら、リソースがチップを越えるため、厳密なタイミングを有効にする必要があります。
およびそれに対応する chat GPT の説明は、私の説明よりも明確で詳細です。
- IsCacheable は D-Cache の使用を制御し、M7 上で TEX/C/B と組み合わせてキャッシュ戦略を決定します。
- IsBufferable は書き込みバッファ(write buffering/combining)を許可し、外部に見える時間を遅延させる可能性があり、より高いスループットを得るために行われます。
- IsShareable はその領域が「複数のホストで共有可能」であることを示し、M7 上ではキャッシュ戦略を変更します:Cacheable+Shareable は Write-Through/No-Write-Allocate を強制します。
詳細な説明
- IsCacheable
- 関心点:D-Cache を通るかどうか;TEX/C/B と組み合わせて Write-Back/Write-Allocate または Write-Through/No-Allocate を決定します。
- 影響:CPU アクセスの遅延と帯域幅、バスのトラフィック;周辺機器のサンプリングレートを直接変更することはありませんが、CPU の処理速度に影響します。
- IsBufferable
- 意味:書き込みを先に書き込みバッファに入れ、可能であれば統合し、非同期でメモリ / バスにフラッシュすることを許可し、バストランザクションを減らし、スループットを向上させます。
- 影響:
- 性能:連続書き込み(memcpy、ストリーミング書き込み)がより速くなります。
- 可視性 / 順序:書き込みが他のホスト(DMA / 周辺機器)に「見える時刻」が遅れる可能性があります;必要に応じてバリア命令を使用して順序を保証します。
- 典型的な使用法:通常、Normal メモリは BUFFERABLE として設定されます;Device/MMIO は通常設定しないことで再配置 / 統合を避けます。
- IsShareable
- 意味:その領域は複数のホスト(CPU、DMA など)で共有される可能性があります。Cortex-M7 上で Cacheable の Normal 領域に特別な影響があります:
- Cacheable + Shareable = Write-Through、No-Write-Allocate で、他のホストが古いデータを見るウィンドウを減らしますが、CPU 側の性能は WB/WA よりわずかに低下します。
- Cacheable + Non-shareable = 通常は Write-Back/Write-Allocate(TEX=1、C=1、B=1 と組み合わせ)で、CPU 性能が最適です。
- 影響:
- 順序と可視性のセマンティクスがより保守的で、他のホストとの協力に有利です。
- 「ハードウェアキャッシュの一貫性」とは異なりますが、キャッシュの書き込み戦略とメモリバリアのセマンティクスの範囲を変更します。
一般的な組み合わせの概要(Normal メモリ)
- B=1, C=1, Shareable=0, TEX=1 → Write-Back, Write-Allocate(CPU が最速で、大多数の計算バッファに適しています)
- B=1, C=1, Shareable=1, TEX=1 → Write-Through, No-Write-Allocate(より保守的な共有セマンティクス)
- B=0/1, C=0 → Non-cacheable(書き込みバッファを許可するかどうかを選択できます;B=0 はより厳密なタイミング)
前述のバスの概念図を参照してください。キャッシュは実際には CPU の一部に属するため、DMA が取得するメモリデータ(D2/D3 領域のメモリ領域)は必然的にドメインを越えます。ドメインを越えたアプリケーションでは、メモリの一貫性の問題を解決する必要があります。これには、一貫性の問題を強制的に解決するためにメモリ領域のサイズの while ループが必要で、逆に不必要な消耗を引き起こします。
したがって、MPU 設定では、D2、D3 領域に対応するアドレス範囲ではキャッシュを有効にしません。上記の第三の組み合わせに対応します。D1 領域の一般的なメモリ領域では、最大の性能を得るために IsCache を有効にし、IsBuffer を有効にし、IsShare を無効にします。これは上記の第一の組み合わせに対応します。
上記の第二の組み合わせについては、現時点では使用シーンに遭遇したことがなく、想像もできません。
以下は上記の設定に対応するコードで、参考用として私自身のバックアップです。
/**
* @brief MPU設定
* @param None
* @retval None
*/
static void MPUInit(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* MPUを無効にする */
HAL_MPU_Disable( );
/* AXI SRAMのMPU属性をWrite back, Read allocate, Write allocateに設定
最適な性能、CPUの計算処理に使用 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* D2領域のMPU設定
D2領域の周辺機器のDMAはキャッシュを無効にする */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* イーサネット受信送信ディスクリプタ部分をStrongly Orderedに設定 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30040000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* D3領域のMPU設定
D3領域の周辺機器のDMAはキャッシュを無効にする */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x38000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER3;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* FMC チップ選択3のサポートを設定 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x68000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // この周辺機器はキャッシュを無効に設定する必要があります。そうしないと、チップ選択と読み書きの有効化が繰り返されます。
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER4;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* MPUを有効にする */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
この文は Mix Space によって xLog に同期更新されました。元のリンクは https://www.yono233.cn/posts/novel/25_10_13_arm-cache_mpu