近畿自然歩道を往く -走れ高野龍神スカイライン-

12月になるギリギリの糞寒い時期に行くやつー!
だって9月から11月の終わりまでずっと副業の仕事が忙しかったんですもん


っていうわけで今回は近畿自然歩道の中で公共交通機関からのアクセスが
一切存在せず、現地に行くだけでも難易度が高い「日光神社を訪ねるみち」を
攻略してまいりました。
なお、ルート中の98%くらいは近畿自然歩道と関係がない高野龍神スカイ
ラインをひたすら駆け抜けるランニング登山となっております!!!1



●2019.11.30 近畿自然歩道(日光神社)


虹裏メイドの朝は結構早い。ここは高野山奥の院前。
弘法大師の奥の院の参拝している人はこの時間帯でもたくさんいました。

高野山は何度も麓から訪れていますが今回はいつもの目的地が出発地点、
ここからひたすら南下して護摩壇山がある龍神村を目指します!



かつては有料道路だった高野龍神スカイラインですが現在は国道371号として
無料で開放されております。近畿自然歩道のエントリポイントはこの道をず〜〜
っと進んで30km先に鎮座しております!!走れ!!



ぐっと下がった気温に朝日が温かい…♥
かつての料金ゲートを超えてさらに進んでいきます。



毎年恒例になった秋のトレラン(?)としては今回は体力任せでかけ上がるだけ
なので難易度自体はEASYかな(と、この時は思ってました…)


熊野古道小辺路と合流してしばし道を共にします。
おそらく来年の秋のロングトレランは熊野参詣路の中で大峯奥駈道の次に
難易度が高い小辺路(の一部)となると思います。


高度は700mからどんどん上がっていきます。絶景が見えてきました。


そんでもって奈良県と和歌山県をひたすら横ぎりまくっています。



小辺路のピークの一つ、水が峰を超えていきます。


うああああもう雪降ってるー!!



水が峰集落跡です…
標高は1100m越えの地点ですが冷たい風がたたきつけて指がかじかんで動かなく
なってしまいました…念のため持ってきたゴム付き軍手が大活躍。


小辺路と別れて再び南下します。
帰りの護摩壇山発のバスに合わせるべくかなり早く出発しましたが
なるべく余裕時間を確保すべくガシガシ走っていきます!



野迫川総合案内所です。
スカイラインから道が分かれて鶴姫の墓なる場所にも行けるようですが
結構な寄り道になるのでパスしました。



ついに旧花園村に到達です!!オルルラあとたったのkmだ!!
基本的に自販機とか全くないのですが道脇には水量が多いおゑイしスがたまに
あるのがありがたいです。



標高1030m、旧花園村の生産物直売所にて。
花園村は2005年にかつらぎ町の一部になり村としては消滅しました。
ペンキで"村"のところだけ消してあってやっけつ感満載である。



ちょっとシーズンが外れてしまいましたがスカイライン沿いの紅葉は
標高の低い場所は残っております。



箕峠にて。お地蔵さんに手を合わせて残りの行程の無事を祈る。


白口峰直下のカーブにて…。
ススキと太陽と青空と緑のコントラストが効いてて最高ですね〜♥


正午に近づきだいぶ気温も上がってきました。



清水温泉に分岐する場所にある清水茶屋は更地になってました…。




高野山からひたすら駆け巡ること30km、ようやく近畿自然歩道のスタート地点の
笹の茶屋峠に到着です…。時間はまだ正午、ここまで予定通りの時間振り。
とりあえず一休みしました。


休憩中がてらこの回周ルートのどっちから攻めるか作戦を練る。
2km弱の

小休止の後まず拝伏山に向かうことにしました(この判断は正しかった)


ここでいきなりルートミスしそうになってしまいました。
ここは道標が全くないのでご注意ください!!!F***K!!!!11!1


伏拝山山頂は危なげなく到達です☆



そして日光神社に・・・
・・・なんかねむいさんのことビビらせようとしてますけど葛城二十八宿経塚巡行を
行所含めて満願った私の敵ではないですよぅへっへっへ・・・

(警告:ここから日光神社までのルートは崩落がかなり進んでマジで危険ですので
    絶対にまねして通行しないでください!!!)


ほ・・・ほらたいしたことねぇじゃんねー…(へっぴり腰





あ、あさっきのが崩落個所でしょここれもう余裕じゃんははへへh


あああ無理無理無理無理無理これ絶対無理めっちゃ落ち葉で滑るし
つかんだ岩もぽろぽろ崩れるし滑って堕ちたら30m下にスライダーだし
ごめんなさいもうわるいことしませんもうゆるしt



ああああああああああああああああああああああああああああああああああ
ああああああああああああああああああああああああああああああああああ
ああああああああああああああああああああああああああああああああああ
何この斜め45°!!!!!!

(踏ん張った瞬間にズルっと滑ってちょっとちびった)


何とか超危険区域を抜けましたが…
最後の最後まで気が抜けん・・・☠


はぁはぁ・・・
ぜ〜〜んっぜんこわくなかったですよぅ!
(警告:このルートは崩落がかなり進んでマジで危険ですので絶対にまねして
    通行しないでください!!!(重文))

    


たった1.5kmくらいの谷筋道でものすごく時間と体力と精神力を削られまくって
しまいせっかく日光神社にたどり着いたのですが全く頭に入ってきません・・・
昔は高野と熊野を結ぶ拠点たる寺院群があったそうです…。


日光神社がある場所は実は最深部の標高950m…(せっかく1150mまで登ったのに)


笹の茶屋峠に戻り護摩壇山(1372m)まであとはひたすら上りとなります…!


標高がさらに上がりどんどん視界が開けていきます!!
頑張れ私(早歩き)


うぉおおおおおおお!!



田辺市の龍神村の領域に入りました…!
あとほんとにあとちょっと!!!!


スカイタワー見えた!!!!!



・・・ッッツ!
道の駅ごまさんスカイタワーに到着です…!そして無駄にパノラマ写真。
高野龍神スカイラインが冬季になる一日前の今は駆け込みの観光客でごった返して
ました。



休息もそこそこにすぐに護摩壇山山頂に向かいます!
スカイタワーの横を通り雪がうっすら積もった道を行くと…


高野山奥の院からはるばる35kmくらい、護摩壇山に己の足で到達しました!
そしてここが次の近畿自然歩道(龍神街道)の起点となります!


護摩壇山からさらに東に向かいました行く先は…



近年発見された和歌山県最高峰、龍神岳です。


すっごい。絶景ですよぅ〜♥


今年はこの日でバスの通常運行が終わってしまうのでまた来年ですね〜
再びここに来るのが楽しみです☆


というわけで護摩壇山バス停で余裕をもってゴールとしました。
近畿自然歩道でてこずることを想定して早朝出発で時間ヘッジをかけてたのが
うまく機能してくれたかんじです。さすが私。


と余裕こいてたら道の駅の入り口のここで滑ってこけそうになりました☠
危ない危ない…



道の駅の営業もこの日が今年最終で、里芋のチーズケーキを堪能して
お土産の梅干しも購入しました!
ここは紀伊田辺と高野山と竜神村のお土産全部そろっててほんとに文化の
合流地点って感じがしました。



帰りはチャーターしていた急行バスで高野山まで帰還です。
予約制で料金も護摩壇山->高野山駅まで1880円かかります。
と、乗っている間にどんどん日が落ちていく…日が落ちるのも早くなった。



日も落ちたころに高野山駅に到着です。
帰りはごーぢぁすに特急こうやを奮発しました。でも今回だけですよぅ。


ってわけで今年も晩秋のロングトレイルが無事に終わりました。今回のルートは
龍神村から紀伊田辺or中辺路〜熊野本宮に向かう道のつなぎとなり、ここから先は
激闘になるとおもいます。私も装備や体力を整えて挑んでいく所存です。


近畿自然歩道(日光神社) GPSログ

ものすごく長くなってしまいましたが本題の近畿自然歩道のルートは
笹ノ茶屋峠周辺のチョボの部分です…

STM32H7を使ってみる5 -キャッシュ・ワンダリング(後篇)-

←前回

このぶろぐの読者様的にはねむいさんがうっかり「MDMAを使ってみる」などという
公序良俗(こうじょりょうぞく)に大幅に反したタイトルの記事を書いてしまって
警察屋さんに絞られる展開を期待してるのでしょうがそんな安易な手には引っかかり
ませんよぅ!!!てうかそんな明らかにヤバイ合成(まぜ)モノより天然自然のta
ダメ!ゼッタイ!!!








さて今回はエラッタのせいで強制的にライトバックにせざるを得なくなってしまった
キャッシュメモリでDMAとどうやってうまいこと付き合っていくかを解説していこうと
思います。
なお、DMA使うメモリ領域だけキャッシュ不可にするという軟弱な措置はねむいさんの
ライブラリに存在しませんのであしからず!!!!!!!!

●注意すべき前提
キャッシュがきいた状態のメモリアクセスでは、あるひとまとまりのデータの塊
に加えてそのデータ塊のアドレス境界で処理すべきという大大前提があります。
これはキャッシュを搭載するどのCPUでも同一となります。

「あるひとまとまり」はキャッシュメモリ上のキャッシュラインのバイト数を示し、
「アドレス境界」はアドレスがキャッシュラインのバイト数分のビットで同一である
ということを示します。

Cortex-M7においてはデータキャッシュのキャッシュラインサイズが32Byteのため、
「32バイトごと」かつ「アドレスの32バイトに当たる下位5ビットが0x0_0000」であること
が求められます。

過去にSTM32F7でキャッシュ比較したときはあらかじめDMA先を32バイトの境界に
合わせ4kByte刻みで転送という限定的な環境でやってたのでキャッシュコントロールは
かなり端折った記述をしておりましたが今回は腰を据えて汎用的に扱えるよう
落とし込んて行きます。



●DMAで読み取る場合(SDMMCのIDMAの場合)
実はライトバック設定の場合、READの取り扱いが一番やっかいです。
理論的には読み出し先のメモリのキャッシュをInvalidateしてやればよいはずなのですが
なぜかそうは問屋が卸さず、私が試した限りでは必ずCleanもしなけらばならないことが
分かりました?なんでだYO!

DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_OK;
uint32_t timer = SysTick->VAL + SD_DATATIMEOUT;

/* first ensure the SDCard is ready for a new operation */
while((SD_GetCardState() == SD_TRANSFER_BUSY))
{
if(timer < SysTick->VAL)
return RES_NOTRDY;
}

#if defined(SD_DMA_MODE) && !defined(SD_POLLING_MODE)
/*
Force Cache Data Lines content write back to memory and Invalidate them.
Someone (CPU) might be still accessing those cache lines so we need to
flush/clean recent content to memory and purge/invalid cache lines before
allowing Hardware to access RAM.
*/
SCB_CleanInvalidateDCache_by_Addr((uint32_t*)buff, count * SECTOR_SIZE);

if((uintptr_t)buff & 0x3) /* Check 4Byte Alignment */
{ /* Unaligned Buffer Address Case (Slower) */
for (unsigned int secNum = 0; secNum < count ; secNum++){

SCB_InvalidateDCache_by_Addr ((uint32_t*)dmabuf, SECTOR_SIZE);

if(SD_ReadBlocks_DMA((uint32_t*)dmabuf, (uint32_t)(sector+secNum), 1)!= MSD_OK)
{
MSG_PRINTF("Read error on unaligned buffer¥n");
res = RES_ERROR;
}

memcpy(buff+secNum*SECTOR_SIZE, dmabuf, SECTOR_SIZE);
}
} else {
/* Aligned Buffer Address Case (Faster) */
if(SD_ReadBlocks_DMA((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
{
MSG_PRINTF("Read error on DMA¥n");
res = RES_ERROR;
}
}
#else
if(SD_ReadBlocks((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
{
MSG_PRINTF("Read error on polling¥n");
res = RES_ERROR;
}
#endif
return res;
}

↑CMSIS_v5を使ってSTM32H7でDMA転送(READ)する場合です。
READアクションをとる前にSCB_CleanInvalidateDCache_by_Addr()を実行します。
Invalidateだけではだめです。CleanしてInvalidateです。ここテストに出るので
覚えておきまししょう!そうせざるを得なかった例がこちらの議論にもありますが正直
私もピンときません。だれか詳しい解説を教えてください。
当たり前ですがデータが32Byteの倍数かつ32Byte境界にビッチリそろった完全読み出し
専用のメモリ領域ならInvalidateだけで問題ないです。ぇ?自分で答え言ってるだろ
って?ねむいさんは他の人の同意とか同情がほしいのですよぅ!
ぁあそうだ、ライトスルーもInvalidateだけでおk、エラッタでどうせ使えませんが。

それと上記のルーチンでは先の述べたアドレスの丸め込みがないじゃないかと思う
方がいると思いますがCMSIS_v5ライブラリのほうに丸め込み処理が取り込まれており
ゴテゴテしたマクロ組まなくてもシンプルに記述できるようになっています。
STマイクロのCubeライブラリではこちらの更新がまだ取り入れられておらず、自前の
アドレス丸め込みマクロが記述されておりますので二度手間にならないようにご注意
願います。

そんでもってCleanInvalidateしたことによるキャッシュコントロールによるオーバー
ヘッドが懸念されますがH7はCPUのコアの速度が480MHzも出るので多分気には
ならないとおもいます(投げやり)



●DMAで送信する場合(SDMMCのIDMAの場合)
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
uint32_t timer;


#if defined(SD_DMA_MODE) && !defined(SD_POLLING_MODE)
/*
Force Cache Data Lines content write back to memory.
Someone (CPU) might be still accessing those cache lines so we need to
flush/clean recent content to memory before
allowing Hardware to access RAM.
*/
SCB_CleanDCache_by_Addr((uint32_t*)buff, count * SECTOR_SIZE);

if((uintptr_t)buff & 0x3) /* Check 4Byte Alignment */
{ /* Unaligned Buffer Address Case (Slower) */
for (unsigned int secNum = 0; secNum < count; secNum++){

memcpy(dmabuf, buff+(SECTOR_SIZE*secNum), SECTOR_SIZE);

SCB_CleanDCache_by_Addr((uint32_t*)dmabuf, SECTOR_SIZE);

if(SD_WriteBlocks_DMA((uint32_t*)dmabuf, (uint32_t)(sector+secNum), 1) != MSD_OK)
{
MSG_PRINTF("Write error on unaligned buffer¥n");
res = RES_ERROR;
}
}
} else {
if(SD_WriteBlocks_DMA((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
{
MSG_PRINTF("Write error on DMA¥n");
res = RES_ERROR;
}
}
#else
if(SD_WriteBlocks((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
{
MSG_PRINTF("Write error on polling¥n");
res = RES_ERROR;
}

#endif

/* ensure the SDCard is ready for a next operation */
timer = SysTick->VAL + SD_DATATIMEOUT;
res = RES_ERROR; /* Timeout */
/* block until SDIO IP is ready or a timeout occur */
while(timer > SysTick->VAL)
{
if(SD_GetCardState() == SD_TRANSFER_OK)
{
res = RES_OK;
break;
}
}

return res;
}

↑CMSIS_v5を使ってSTM32H7でDMA転送(WRITE)する場合です。
WRITEのほうは単純明快、転送前にCleanしてやるだけです!
READのほうが扱いがめどくて誤れば致命的なのがなんとも…


ちなみにSTM32のマイコンにおいてREAD/WRITEいずれの場合もDMAコントローラのWORD
(4Byte)制限があるので
それも加味しておきましょう。



●DMAで送信する場合(SAIのCircularDMAの場合)
ライトスル―が使えなくなったせいでいろんなコードにキャッシュコントロールを
突っ込まざるを得なくなってしまいましたがねむいさんのいつものではAudio再生に
SAIのCircularDMAを使用しており、こちらもきっちり対策しております。

ねむいさん最初にH7触った時に無対策で挑んで見事返り討ちに会いました。サンプル
音源のBeachBoysのハーモニック・サウンドがブビビビッブッというげりうんちみたいな
汚い音が得られてしまったのですが移植時にcodecの音量設定が最大になっていたのも
気づかずげりうんち音で鼓膜が破壊されるかと思いましたがどうでもいいですね。


SAIの場合はWrite専用のぶっぱなのでCleanだけで問題ないです。
問題は挿入する箇所です。

/* on the first buffer fill up,start the dma transfer */
if(EVAL_AUDIO_Init(OUTPUT_DEVICE_BOTH, DEFAULT_VOLUME, (uint32_t)mp3FrameInfo.samprate))
{
MSG_PRINTF("Mp3Decode: audio init failed¥r¥n");
nResult = -4;
break;
}
/* I2S DMA Transfer First Trigger */
#if defined(STM32H7XX)
SCB_CleanDCache_by_Addr((uint32_t*)g_pMp3DmaBuffer, MP3_DMA_BUFFER_SIZE);
#endif
/* Needed to AAC_DMA_BUFFER_SIZE*2,16-bits audio data size(2Byte) */
/* See more detail,BSP_AUDIO_OUT_Play() */
EVAL_AUDIO_Play(g_pMp3DmaBuffer, MP3_DMA_BUFFER_SIZE*2);

↑転送なのでCleanのみでおk(mp3.support.cにて)
まずはCircularDMAの最初のキックです。まぁこれは当然。
ていうかなんかtypoしてますね私orz AACじゃなくてここではMP3です。
if(unDmaBufMode == INIT_RINGBUF || unDmaBufMode == FULL_RINGBUF)
{
/* Check FirstHalf Transfer in LastHalf and BufferInit Mode */
if(em & TRANSFER_FIRST_HALF)
{
MSG_PRINTF("Mp3Decode: DMA out of sync (expected TC, got HT)¥r¥n");
nResult = -3;
break;
}
else{
g_pMp3DmaBufferPtr = g_pMp3DmaBuffer + (MP3_DMA_BUFFER_SIZE/2); /* 16bit address pointer calc */
#if defined(STM32H7XX)
SCB_CleanDCache_by_Addr((uint32_t*)g_pMp3DmaBufferPtr, MP3_DMA_BUFFER_SIZE);
#endif
}
}
else /* unDmaBufMode == HALF_RINGBUF */
{
/* Check LastHalf Transfer in FirstHalf Mode */
if(em & TRANSFER_LAST_HALF)
{
MSG_PRINTF("Mp3Decode: DMA out of sync (expected HT, got TC)¥r¥n");
nResult = -3;
break;
}
else{
g_pMp3DmaBufferPtr = g_pMp3DmaBuffer;
#if defined(STM32H7XX)
SCB_CleanDCache_by_Addr((uint32_t*)g_pMp3DmaBufferPtr, MP3_DMA_BUFFER_SIZE);
#endif
}

お次はこちら。
半分転送完了割り込み後の処理で次の転送予定のバッファにCleanをかけます。
g_pMp3DmaBufferPtrに次に転送するバッファのアドレスをコピーした後Cleanです。
g_pMp3DmaBufferPtrはuint16_tのポインタなので注意が必要です。


●DMAじゃないけどLTDCもキャッシュと競合する
ライトスルーが使えなくなった弊害はDMAと同じくキャッシュコヒーレンシーを
求められるLTDCにも波及します。

static void disp_blt (
int left, /* Left end (-32768 to 32767) */
int right, /* Right end (-32768 to 32767, >=left) */
int top, /* Top end (-32768 to 32767) */
int bottom, /* Bottom end (-32768 to 32767, >=right) */
const uint16_t *pat /* Pattern data */
)
{
int yc, xc, xs;
#if !defined(USE_TFT_FRAMEBUFFER)
int xl;
uint16_t pd;
#endif

if (left > right || top > bottom) return; /* Check varidity */
if (left > MaskR || right < MaskL || top > MaskB || bottom < MaskT) return; /* Check if in active area */

yc = bottom - top + 1; /* Vertical size */
xc = right - left + 1; xs = 0; /* Horizontal size and skip */

if (top < MaskT) { /* Clip top of source image if it is out of active area */
pat += xc * (MaskT - top);
yc -= MaskT - top;
top = MaskT;
}
if (bottom > MaskB) { /* Clip bottom of source image if it is out of active area */
yc -= bottom - MaskB;
bottom = MaskB;
}
if (left < MaskL) { /* Clip left of source image if it is out of active area */
pat += MaskL - left;
xc -= MaskL - left;
xs += MaskL - left;
left = MaskL;
}
if (right > MaskR) { /* Clip right of source image it is out of active area */
xc -= right - MaskR;
xs += right - MaskR;
right = MaskR;
}
Display_rect_if(left, right, top, bottom); /* Set rectangular area to fill */
#if defined(STM32F7XX) || defined(STM32H7XX) /* Flush Cache Datas */
SCB_CleanDCache_by_Addr((uint32_t*)pat, xc*yc*2);
#endif
#if defined(USE_TFT_FRAMEBUFFER)
Display_wr_block_if((uint8_t*)pat, xc*yc);
#else
do { /* Send image data */
xl = xc;
do {
pd = *pat++;
Display_wr_dat_if(pd);
} while (--xl);
pat += xs;
} while (--yc);
#endif
}

↑ts_fileload.cにて
Cha'N氏のTinyJPEGライブラリででコード後のデータをブロック転送するときにClean
しないと化けます。DMA2DだけじゃなくCPUが介在するFIFOコピーでも化けます。

大量のデータを一気に吐かせるimgファイル(動画データ)ならその都度キャッシュも
総入れ替えとなるので化けないようですが中途半端に転送サイズが小さいとだめな
ようですね。本来なら動画再生のほうもClean突っ込んでやるのが筋なんですけど。

なお、前回触れたライトアロケートキャッシュ設定にしてしまった場合は逆汚染に
よってせっかく書き込んだデータが潰されてしまうためデータの書き込み都度に
Cleanをしてやらないといけなくなってしまいこれによるオーバーヘッドで見かけの
処理速度が大きく落ちてしまいます。
前回も言いましたがSAIでもビットレート上がるとプチプチノイズ入りまくり使い物に
なりませんしやはりSTM32でライトアロケート設定は使わないのが無難です。






というわけで癖が非常に強いSTM32H7ですが、その特性を把握して上手に
コントロールすれば本来の性能をいかんなく発揮できることがわかりました。
STM32F7で十分と思い、食わず嫌いしてましたがSTM32H7、良い感じです!

STM32H7を使ってみる4 -キャッシュ・ワンダリング(中篇)-

←前回                       次回→

自宅のPC環境を完全にWin10に切り替えました。
ハードウエアも11年ぶりに新調してメモリも16GBも乗っけてます!!!!
システムディスクも最新のM.2SSDに換装してこれで向こう20年は戦うことが
できるでしょう!!!!!


さて、今回は前回説明した各種キャッシュ設定がパフォーマンスにどう影響
してくるのかを実動作を交えて説明していこうとおもいます。


比較ということでlibpng(倍精度浮動小数点演算あり)のデコード/表示時間を計測して
いきます。テスツ用png画像は自作の虹裏的美少女イラスツを使用してまいります!
突っ込みは受け付けません!!いないさんかわいい!!1!!!1!


また、png画像を読み出すストレージは以前紹介した産業用MicroSDの64GB品を使用
します。安くて信頼性があって使い勝手が良いので重宝します♥


●キャッシュ無し

まずはデータキャッシュそのものを無効にします。
本当はAXI-SRAMだけ無効にしたかったのですがなぜかpngのデコード時にHardFaultに
なってしまったので仕方なくぜんぶキャッシュOFFにしてます。

キャッシュ効いてないせいで遅いのですがAXIバスのクロックが200MHzと高速なので
それでも十分"早い"ですね…

●ライトバック&ライトアロケートなし

こちらはいつもので公開しているキャッシュ設定となります。
ライトスルーのエラッタが認知されてしまった今これに頼らざるを得ません。

●ライトバック&ライトアロケートあり


ライトアロケートなしとほとんど変わらず誤差程度です。

これならライトアロケートありでいいじゃんとお思いでしょうが…


残念ながらLTDCと相性が非常に悪くLTDCが管轄するフレームバッファ領域にこの
設定を行っているとご覧の通り表示が崩れてしまいます…
一応対策できないこともないですがキャッシュコントロール関数をあちこちに放り
こまないといけないため全く現実的ではないです。

それならSDRAMにはその設定しないでAXI-SRAMだけにすればとお思いででしょうが
ねむいさんもそうしようと思ったのですがなんとSAIとも相性が悪く高ビットレートの
音声再生時にブチブチノイズが入りやがりますorzこちらもあちこちでキャッシュコント
ロールしまくったら問題ないのですがそんなんするくらいならライトバック+ライト
アロケートなしのほうでいいやとなり今に至った次第です。

●ライトスルー

今となってはエラッタで禁じ手になったライトスルーモードです。
ライトバックより遅いですがキャッシュ無しより断然早い理由は実メモリに書き込みに
いったときにライトバッファが効いて速度が上がっているからです。
いちいちcleanしないで済むから楽だったんですけどねライトスルー…



●おまけ・カリカリチューンのF7

STM32F769I-Discoでも同じMicroSDを使ってやってみました。
フレームバッファを除くDMAの送受信バッファ・各変数・ヒープ領域をキャッシュ
しなくても超高速に動作できるDTCMに設定して同じようにやってみたのがこちら。
H7と比べるとコアクロックが1/2の200MHz動作なのですが意外と健闘してます。
F7シリーズではDTCM領域でもDMAできるのでこのような芸当ができるのですが
H7は直接できなってしまいましたのでライトスルーが使えなくなったのもあって
DMAするだけでもいろんな工夫が必要です。

次回はSTM32H7でキャッシュを効かせた状態でDMAをどのように安定して動かして
いけば良いのかを解説していこうと思います。


20191104追:
jujurouさんがF7ではSDRAMのライトバック+ライトアロケートなしでもDMA2Dで
表示おかしくなると報告されているので
ねむいさんも追って知らべてみました…

が…(STM32F746G-Discoveryで試した)


ううむねむいさんのいつものじゃどういう状況でもデータ化けしてぬぇ…
※9月度版の時点でライトバック版に変更済みです。


キャッシュを効かせたRAM領域にフレームバッファを確保してLTDCを動作させている
時点でDMAしてる時と同じようになりDMA2DせずともキャッシュとLTDCコントローラとの
干渉が発生してしまいます。これはライトアロケートあるなしに関係はないので
ライトバックではデータ転送前に必ずキャッシュのCleanが必要となります。

ねむいさんのいつものでこれに引っかかった例がChaNさんのjpegライブラリを使い
jpegファイルを表示させようとした時で、disp_blt関数内で転送直前にクリーンを
行うことにより対策しております。

そして前回も簡単に説明しましたがキャッシュコントロールをしているにもかかわらず
ライトアロケート時に表示が崩れてしまう原因は書き込みキャッシュミス時に実メモリ
からデータを読み出しキャッシュラインに展開している動作そのものにあります。
実メモリ(SDRAM)からのread動作によってキャッシュラインが逆汚染され、結果として
寄生Evictionが起こりまくり表示が崩れてしまうことになります。
データキャッシュサイズ上限以下の容量でチマチマ書き換える…たとえばFONTX2の
表示とかの時に一番崩れやすくなります。

↑こんな感じに崩れます。
これはLTDCに限らずSAIとかの基本書き込みっぱなしのぶっ放しのペリフェラルでも
起こりますが長くなりそうなので次回にでも解説します。

STM32H7を使ってみる3 -キャッシュ・ワンダリング(前篇)-

←前回                       次回→

今から4年以上前、STM32F7ではぢめてキャッシュという概念に対峙し、分かった
つもりでお茶を濁してきましたがH7になった今どうしても正面から向き合わざるを
得ない事態になってしまいました…それも最悪な形で!!

●Cortex-M7のキャッシュにエラッタ発覚!

オイオイオイ死ぬわ私。(一般には今年春くらいに知れ渡った内容です)
要約しますとCortex-M7系マイコンでキャッシュポリシーをライトスル―にした状態で
Double-WORD(ARMの場合8バイト)の読み書きを同一のアドレスで行った時に、読み
出しの時にデータが壊れる事があるという内容…しかも現行の全リビジョン…
そしてカテゴリーAとかいうフュージョンジャックした剣崎さんでも不覚をとりかねない
クリティカルっていうか実質使用禁止設定判定なエラッタ…すざけんな!!111!
全文はこちらで読めますので各自把握しておいてください。

おきぱのSTM32F7/H7向けFatFsデモはライトバックを使用するように
すでにコードを修正済ですので安心してお使いいただけます。


STM32F7/H7側の障害情報ではF7はもちろん最新のH745/755に至るまで当エラッタが
網羅され、全滅状態ですorz


つまり、STM32F7/H7でキャッシュを使用する際はキャッシュの取り扱いが面倒な
ライトバック以外にキャッシュポリシーがとれないことを示しますorz

ぁーぁ…


●ひとまずSTM32H7のキャッシュのおさらいっ
まずはけしかん様のブログ記事も熟読の上で…

STM32F7/H7に搭載されてたキャッシュメモリは命令(I)とデータ(D)用に独立した
キャッシュが設けられております。キャッシュの方式はI/D両者ともセット・アソシエイティブ
という回路規模とキャッシュ性能を折半した方式が採られていて、とりわけデータ用
では4ウェイセット・アソシエイティブという方式となっております。
ウエーイが4つもあってどんだけリア充なんだYO!

そしてキャッシュポリシーとしてはSTM32F7/H7は下記の3タイプ存在します。
1.ライトスルー(ライトアロケート無し固定)
 キャッシュに書き込みと同時に実メモリにも常に書きこむ。
 キャッシュの取り扱いは簡単だが動作は一番遅い。
 使い勝手一番良かったのにエラッタのせいで使用不能!!11!1

2.ライトバック・ライトアロケート有り
 キャッシュラインがいっぱいになるかCleanしないと実メモリに書きこまない。
 書き込み時キャッシュミスした場合1ライン分実メモリから読みだして
 キャッシュラインに展開する。ちょっと遅い。
 頻繁に読み書きする変数領域向けなのでフレームバッファのような書きっぱの
 用途では著しく不利になるので設定してはならない。

3.ライトバック・ライトアロケート無し
 キャッシュラインがいっぱいになるかCleanしないと実メモリに書きこまない。
 書き込み時キャッシュミスした場合も実メモリから読みだす動作はしない。
 理論上一番早い。ライトスルーが封じられた今これが一番無難。

1〜3のいずれも読み込み側動作(リードアロケート)はすべて同様に行われる。

1.はエラッタのせいで死に設定なので2.や3.を選びます。尤もライトバック・ライト
アロケートありでもmemset等の関数で同じデータを書いていくような動作を検知して
自動的にライトアロケート無しに動的に切り替わりキャッシュが動くらしいです
(↑ほんまかいな)

詳しい解説はAPSさんのサイトにあります。
Cortex-Aのキャッシュについての解説ですがCortex-M7も動作や設定は同一です。


●で、具体的な設定はどうすればよいのか
Cortex-M7に置いてはMPU(メモリプロテクションユニット)からキャッシュポリシーの
設定を行います。ねむいさんのいつもののhw_config.cにあるMPU設定のサブルーチン
より、AXI-SRAMの設定を例に紹介します。

	/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AXI-SRAM D1 */
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_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
AXI-SRAMの512kByte丸々ライトバック・ライトアロケート無しの設定をする場合
上記のようにします。
MPU_InitStruct.IsBufferable 	= MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;

↑特にこの3行は重要です。ライトバッファ有効・キャッシュ有効でライトバック
となります。また、SHAREABLEの設定は必ずMPU_ACCESS_NOT_SHAREABLEにしておいて
ください!ここをMPU_ACCESS_SHAREABLEとしてしまうとキャッシュ無効にしたのと
同じになってしまいます(次回実動作を交えて解説) 実に罠設定です。

ちなみにこの超重要な情報はNXPのM7コアのマイコンiMXのアプリケーションノート
明記されております。
しかしSTのマニュアルやアプリケーションノートには何所にも全く書いておらず
罠にはまってキャッシュの効力殺してるの気づいてない人多いんじゃないかと☠



SHAREABLE有効にしつつキャッシュ動作を有効させる方法もありますが今回のシステム
ではまだ利用価値が無いので割愛します。

また、ライトアロケート有りで行きたい場合は
MPU_TEX_LEVEL0->MPU_TEX_LEVEL1に変更します。
でもCortex-M7コアでライトアロケートありするくらいならエラッタ付きライトスルーに
した方が10000000000倍マシです☠
こちらの比較も次回以降解説しますね。

	/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AHB-SRAM1 D2 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
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_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AHB-SRAM2 D2 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30020000;
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
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_NUMBER2;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AHB-SRAM3 D2 */
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_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER3;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AHB-SRAM4 D3 */
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_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_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);

残りの内蔵SRAMの設定もライトバック・ライトアロケート無しの設定としました。
AHB-RAMはAXIバスマトリクス上にはないので意味なさそうですが実はキャッシュの
恩恵にあずかることができます。

	/* External-Memories cache setting */
/* Configure the MPU attributes as Write-Back/No-Write-Allocate for External SDRAM */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xD0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32MB; /* STM32H747I-Disco have 32Mbyte SDRAM */
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_NUMBER6;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; /* MUST be No-WriteAllocation to avoid collision LTDC-Hostage */
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* External-Memories cache setting */
/* Configure the MPU attributes as Write-Back for QSPI-DirectRemapping(ReadOnly) */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x90000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_128MB; /* STM32H747I-Disco have 64*2=128MByte QSPI-ROM */
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RO_URO;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER7;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

外部メモリ領域のSDRAMやQSPIはAXIバスでつながっているので当然のことながら
大きな影響を受けます。しっかりキャッシュ設定をしておきましょう。
また、SDRAMをLTDCのフレームバッファに使用したい場合は必ずライトアロケーション
なしを選択してください。ライトアロケーションありだといちいちCleanをしてやる必要が
出来てしまい、それを怠るとDMAの時のようにデータの一貫性が保たれなくなり表示が
おかしくなります。フレームバッファはデータを読みだすことはほぼ無く書きっぱなし
なのでライトアロケーションなしの方が都合が良いのです。
QSPIはメモリマップドモードのリードオンリーで使用するためどうでもよいですが
AccessPermission以外はSDRAMとキャッシュ設定を合わせておきましょう。

そしてSAI等の周期的にデータ投げる用途ではAXI-SRAMをライトアロケート有りにして
いるとキャッシュコントロールを今よりさらに厳格にしないと高ビットレートでポツポツノイズが
入りやがることが分かったので
もう全部一律ライトアロケート無しでやります!!!!!!!!!

次回は上記キャッシュポリシーの設定別に実動作をさせたときの性能の比較を行って
みたいと思います!

Go to top of page