STM32F7を使ってみる5 -AXIMとITCM-

STM32F7の内蔵フラッシュメモリは以下に示す2系統のインターフェースで命令
コードを読み出し実行することが可能です。

The AXI-Master (AXIM) interface

TCM Interface

Embedded Workbenchマニアのページさんの記事を参考にして私なりに両者の
性能の違いを確かめてみました。また、各モードで上手く使いこなすための
コツも紹介します。


●AXIM経由のフラッシュ
こちらはCubeF7の各種サンプルプログラムでデフォルトで使用されているスタン
ダードなものです。フラッシュメモリのアドレスは0x08000000に配置されており
過去のSTM32シリーズと同様の感覚で使用できます。
フェッチされた命令はCortex-M7コア内のI-Cacheを有効にしていればフラッシュ
のアクセス速度の遅さを補うことができますのでフラッシュの素のアクセス速度
の遅さを気にせず過去のSTM32シリーズとほぼ同様に使用することができます。


そんなわけで早速表示です。SDRAMのタイミングを正しい値に修正したこと以外は
前回お見せした対策その1と同じ構成です。


●ITCM経由のフラッシュ
ITCMバスにはF2/F4シリーズでおなじみのARTアクセラレータが乗っかっています。
I-Cacheは経由しませんがアクセラレータのおかげでフラッシュの素の(ry
ちなみにITCM経由のフラッシュアクセスの場合、アドレスは0x00200000に配置され
ている事になります。ITCM経由で動かしたい場合はリンカスクリプトの開始アドレスを
0x00200000にしましょう。


なんとこちらの方が高速です!Embedded Workbenchマニアのページさんでも述べられ
ているとおり
AXIM経由の場合はフラッシュの命令の他に様々なペリフェラルが行き
かっているので命令はITCMに押しやるのが本来取るべきスジなのでしょう。


●ITCMるコツ
上記の結果だけ見るとAXIMのメリットないじゃんてお思いの方が多いと思います。
残念ながらITCMも結構ピーキーな性質でスムーズに開発を行うために知っておかな
ければならないいくつかのコツがあります。

1.ITCMのアドレスではDMA出来ない
 RMを読む限りではバスがDMAコントローラのどこにもつながってないのでこの
 アドレスではDMAが一切出来ないと思います。DMA元にconstを決め打ちする
 ような転送ではHardFaultになって失敗するでしょう。
 もちろんITCMバスにぶら下がっているITCM-RAMもDMA不可能です。
 このへんF4のCCMと同じ位置づけですね。
2.フラッシュは0x08000000に焼く
 ITCMから見えるフラッシュのアドレスは0x00200000ですがそのアドレスには
 フラッシュメモリの実体はなく、過去のSTM32シリーズと同じ0x08000000に
 あります。0x00200000に無理やり書こうとしても弾かれます。
 
 ↑こんな風にエラー出て駄目です。
 したがってOpenOCDではhexやelf形式のフラッシュ書き込みは100%失敗しますので
 binファイルを0x08000000に向かって書き込むようにしましょう。
 
 makefile上ではこんな感じです。9/Sにリリースする新版のいつものではフラッシュに
 ITCMを選択すると必ずbin形式で書き込むようにmakefileをいじって置きますので
 ねむいさんと同じ開発スタイルの人は特に気にせず快適に書き込み&デバッグが
 できると思います★

 おまけです。
 
 ↑AXIMで動かしてる時のデバッグ画面。
 
 ↑ITCMで動かしているときのデバッグ画面。PCの値に注目。

 F7シリーズはBOOT0の状態で起動できるアドレスの番地が可変になりましたが、
 デフォルトではフラッシュメモリからブートする場合、ITCMインターフェースの先頭
 0x00200000から開始になっています。AXIMでもきちんとスタートアップを記述
 していれば0x08000000の番地にすぐに飛んでくれるので問題ありません。



・・・徐々に、そして確実にSTM32F7の真の力が明らかになってきています。
今までの私の使ってきたワンチップマイコンの概念のままでは到底その力を引き出す
ことはできないでしょう。しかし私はそれに臆さずどんどん新しい概念に切り込んで
いきたいと思います!!

STM32F7を使ってみる4 -CPUキャッシュとDMAを考慮する-

前回DMA転送がうまくいかなかったと抜かしていましたが原因は至って単純な
もので私にCPUキャッシュについての知識が無かっただけでした!!!!!
めでたしめでたし♥

20231123追:
古いCortex-M7コアにはキャッシュ周りにバグがあります!!
こちらのエントリも必ず見てください!!1!!1!!

20231123追:




今回は上の三行で終わる内容ですが前回の記事そのものがCortex-M7アーキテク
チャを全く理解してないおまぬけなねむいさんのままで終わってしまう(あたしはまだ
終わってはない!)のでもう少し突っ込んで解説したいと思います。

STM32F7にはI-CacheとD-Cacheの2つのレベル1キャッシュメモリがありこれらを
有効化させることによりリアルタイム性が落ちる代わりに全体の処理の高速化を
図っています。今回はデータ用キャッシュメモリD-Cacheに着目します。
今回の記事を書くにあたってユークエストのビリー様のサイトを参考にさせていただきました。



私のいつものではプログラム開始時にI-CacheとD-Cacheを有効にし、さらにMPUで
SRAM1,2領域(256kByte分)のWriteThroughを有効にしています。CPUからのこの
SRAM領域(以下本メモリ)への読み書きはWriteThroughによって常に本メモリと
D-Cacheの一貫性(Cache Coherency)が保たれています。WriteBackよりも多少は
処理速度は落ちますがそれでもD-Cacheの恩恵に与かれるので確実で強力です。

しかしながらこの関係が破れるのがDMA転送をする時です。

DMAコントローラはキャッシュメモリを介さないのでD-Cacheと本メモリの一貫性を
保てなくなりDMA転送後に本メモリから読みだしたはずの本当のデータはD-Cache
に残った古いデータで正常に読みだせない&転送がコケる(ように見えた)という事に
なります。以下にlibjpegで画像をデコードした際の実例を示します。




こちらは虹裏的美少女画像をSDMMCのデータ転送にDMAを一切使わずFIFOのポーリ
ングで読みだした所の写真です。右下に表示にデコード完了に掛かった時間を表示して
います。I/D-Cacheは有効です。


無対策でDMAで読みだした時の写真です。途中でデータがおかしくなってひでぶ
しているのが分かります。上記のとおりD-Cacheに残った古いデータのせいでめちゃ
くちゃになってる訳ですが、当たり前ですが表示にすら至らないケースもあります。



その前に脇道にそれますがlibjpegとFatFsを結合している関数について、この関数
内ではlibjpeg側で先にmallocで取得したデータ読み出し用バッファをDMAの読み出し
先にしてしまいます。

つまり私のいつもので使うFatFsのデータ転送用汎用バッファBuff(上記画像参照)が
どのメモリ領域にいるかは関係がなく、リンカスクリプトでHEAP領域にD-Cacheの影響
を受けるSRAM1,2を割り当てているとDMA転送時の上記の問題に思い切り引っ掛かる
ことになります。それを踏まえたうえで本題の比較に入ります。



プログラム開始時の時点でD-Cacheそのものを無効にすると当たり前ですがデータ
の齟齬が発生する余地が全く無くなりますので表示がおかしくなることはありません。
しかしながらパフォーマンスはガタ落ちになります。これじゃF7使う価値がないです。


対策その1です。
FatFsの汎用バッファをキャッシュの影響を受けないDTCM領域に配置し、さらにDMAの
4バイトアライメント対策もDTCMの1セクタバッファでフォローする作戦です!
CPUコアと同速度で動くDTCMでキャッシュの影響とレイテンシの不確実性を最小限に
でき、確実な転送が可能です!!

しかしこの方法は以下の制約を受けるのでご留意願います。
1.リンカスクリプトでDATA,BSS,STACK領域を必ずDTCMに割り当てるべし。
2.mallocで引っ張ってきたメモリドレスがDTCMのアドレスじゃなかった場合の
 対処を講じる必要がある。実際は読み書き元のアドレスを検知してDTCMの1セクタ
 事の読み書きに変えるかキャッシュコントロール関数を使用するかです。


私のSTM32F7向けのいつものはこの対策1を施しておあります。


対策その2です。
DMA転送する前にD-Cacheの無効(Invalidate)だけを行います。CMSISのヘッダライ
ブラリでは上記の無効にする関数が用意されています。STM32F7のキャッシュサイズ
は4kByteでlibjpegの一度に読みだすサイズもデフォルトでは4kByteとなってます。
(注:Cortex-M7のキャッシュは1ライン32byte*128本で合計4096Byte)
D-Cacheの1ラインごとにInvalidateする関数もありますが今回は読み出しサイズが
キャッシュサイズと同じなので全部Invalidateする関数を呼び出してます。
SDMMCのDMA転送を行う関数の頭にSCB_InvalidateDCache()を置いて実行してい
ますが対策その1よりも若干遅くなっています。

そういえば・・・Disableも無効って意味なんですがあちらさんではどう
使いわけてるんでしょ…



対策その3です。
DMA転送する前にD-CacheのInvalidateとCleanを行います。
SCB_CleanInvalidateDCache()を呼び出しますが必然的に対策その2よりもさらに
遅くなっています。



対策その2と3は単純にCMSISヘッダライブラリの関数を実行すればいいという訳では
なく、DMA元/先として使うバッファのアライメントとサイズに制約があります。
D-Cacheのキャッシュラインのサイズにアライメントを合わせ、なおかつそのサイズの
整数倍にしないとやっぱりデータの齟齬が起こってしまいます。
特に読み出し時はDMA用に確保したバッファの近辺のアドレスに配置されている変数
までしっかり破壊してくれやがります。STM32F7の場合は1キャッシュライン当たり
32Byteの固定値となっているので32バイトのアラインメント且つデータサイズが32の
倍数になる必要があります。

もしバッファ用のメモリ領域が上の条件を満たさない場合はアライメントとデータサイズ
の丸め込みを行う必要性があり更にオーバヘッドが増えます。RTOS向けのデバイス
ドライバを作成される際はこの点も考慮する必要があります。

以上の点を踏まえると別に無理してDMAしなくてもいいんじゃないかと思われるかも
しれませんがそれではロマンが無いので是が非でもDMAらせていただきます!


そんなわけでD-Cacheを考慮した修正版のいつものをあげておきます・・・


おまけ
libpngやlibjpegは無対策でも平気だったのですが何故ひでぶしないで平気だったのかも
調べてみました。


giflibはSDMMCのブロック転送時にfptrがクラスタの境界になく、その際の一時
転送先がDTCM領域のFatFsオブジェクト内(のwin配列)に確保されています。
つまり対策その1とほぼ同じ事をFatFsライブラリ内でやってくれているわけです。



libpngもgiflibと同様になっています。

Go to top of page