大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形。
上一篇文章 《i.MXRT中FlexSPI外設對AHB Burst Read特性的支援》 裡痞子衡介紹了FlexSPI外設在不開啟Prefetch功能下響應AHB master的訪問請求完全受AHB匯流排Burst Read特性決定,這是FlexSPI外設最基礎的對Flash訪問支援功能,研究這個其實是很有意義的,這可以反映出XiP下最原始的程式碼執行效率。
我們知道在實際專案中,XiP應用程式常常是在L1 Cache和Prefetch加持下執行的,程式碼執行效率會得到大大提升,但無論是怎樣的快取策略,極限情況下(比如大資料塊搬移,長跳轉指令)最終還是拼得FlexSPI最基礎的讀訪問支援。今天痞子衡就從抓Flash訊號波形角度帶大家真切感受下這最基礎的AHB讀訪問情形(為更清晰地分析結果,本次主要涉及資料匯流排AHB訪問,暫不涉及指令匯流排AHB訪問):
一、實驗準備
痞子衡用i.MXRT1050-EVKB來做這個AHB讀訪問實驗,這塊板子上的Flash被痞子衡更換過,目前的型號是華邦W25Q64JWS-IQ。我們基於 \SDK_2.9.1_EVKB-IMXRT1050\boards\evkbimxrt1050\demo_apps\led_blinky\iar 例程(記得切換到 flexspi_nor_debug build)來簡單修改一下,把啟動頭FDCB修改如下,設定Flash工作於30MHz Fast Read Quad I/O SDR模式,調成30MHz低速是為了方便後續用示波器抓Flash訊號去分析。
const flexspi_nor_config_t qspiflash_config = {
.memConfig =
{
.tag = FLEXSPI_CFG_BLK_TAG,
.version = FLEXSPI_CFG_BLK_VERSION,
.readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackFromDqsPad,
.csHoldTime = 3u,
.csSetupTime = 3u,
.controllerMiscOption = 0x10,
.deviceType = kFlexSpiDeviceType_SerialNOR,
.sflashPadType = kSerialFlash_4Pads,
// Flash工作於30MHz
.serialClkFreq = kFlexSpiSerialClk_30MHz,
.sflashA1Size = 8u * 1024u * 1024u,
.lookupTable =
{
// Quad I/O Fast Read SDR LUTs
[4*CMD_LUT_SEQ_IDX_READ + 0] = FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18),
[4*CMD_LUT_SEQ_IDX_READ + 1] = FLEXSPI_LUT_SEQ(MODE8_SDR, FLEXSPI_4PAD, 0xF0, DUMMY_SDR, FLEXSPI_4PAD, 0x04),
[4*CMD_LUT_SEQ_IDX_READ + 2] = FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_4PAD, 0x04, STOP, FLEXSPI_1PAD, 0x00),
[4*CMD_LUT_SEQ_IDX_READ + 3] = 0,
},
},
.pageSize = 256u,
.sectorSize = 4u * 1024u,
.blockSize = 64u * 1024u,
.isUniformBlockSize = false,
};
下圖是華邦W25Q64JWS-IQ晶片的Fast Read Quad I/O SDR傳輸時序圖,Dummy Cycle連同MODE8_SDR序列一共6個SCK週期,此外還有個特別注意點,MODE8_SDR序列引數值需要被設成0xFx,我們上面修改的FDCB啟動頭是符合要求的。
現在讓我們把示波器拿出來,四路探頭分別連到板載Flash器件的CE#、SCK、SI_IO0、SO_IO1引腳(IO2、IO3因探頭有限就不抓取了,IO[1:0]足夠我們分析時序了),然後將 led_blinky 工程下載進Flash執行便可以觀測結果了。
二、實驗程式碼
因為我們下載的是一個XIP工程,程式碼的執行本身也會觸發Flash中的指令讀取,這會影響我們在示波器上觀測AHB讀資料測試結果,所以我們可以在main()函式裡把SysTick初始化去掉(不要中斷),並且呼叫如下ramfunc型函式 test_ahb_read() 來做測試(痞子衡直接利用了IAR軟體的特性),這樣程式碼跑起來後,Flash上發生的讀訪問均來自我們想要測試的AHB讀資料操作(這也意味著ICache是否開啟對本系列測試結果沒有影響,但不管怎麼,我們統一關掉):
Note: DCache和Prefetch必須要全部關閉,否則哪怕測試程式碼裡對同一個地方迴圈讀取,但在Flash引腳上根本看不到週期性訊號波形,因為系統做了快取,後續的讀取操作可能直接發生在快取區裡(32KB DCache, 1KB AHB RX prefetch buffer)了。
#define AHB_ADDR_START (0x60002400)
#if (defined(__ICCARM__))
#pragma optimize = none
__ramfunc
#endif
void test_ahb_read(void)
{
/* Disable L1 I-Cache*/
SCB_DisableICache();
/* Disable L1 D-Cache*/
SCB_DisableDCache();
/* Disable FlexSPI AHB read prefetch */
FLEXSPI->AHBCR &= ~(FLEXSPI_AHBCR_PREFETCHEN_MASK | FLEXSPI_AHBCR_CACHABLEEN_MASK);
while (1)
{
SDK_DelayAtLeastUs(10, SystemCoreClock);
for (uint32_t i = 1; i <= 8; i++)
{
SDK_DelayAtLeastUs(2, SystemCoreClock);
memcpy((void *)0x20200000, (void *)AHB_ADDR_START, i);
}
}
}
因為我們用了memcpy來做Flash資料拷貝,memcpy功能實際上是IAR軟體自帶庫 ABImemcpy.a 裡面的 __aeabi_memcpy、__aeabi_memcpy4、__aeabi_memcpy8 等函式實現的,因此我們還需要在工程連結檔案裡將 ABImemcpy.o 連結到RAM區;並且我們還用了SDK_DelayAtLeastUs()來分隔每次memcpy()波形結果,還需要將這個函式裡呼叫的相關程式碼放到RAM區(fsl_common.c裡)。
initialize by copy { readwrite,
section .textrw,
// 確保 memcpy() 相關程式碼全在RAM裡
object ABImemcpy.o,
// 確保 SDK_DelayAtLeastUs() 相關程式碼全在RAM裡
object fsl_common.o,
object I64DivZer.o,
object I64DivMod.o
};
do not initialize { section .noinit };
一切準備就緒後具體測試就是設定不同的AHB_ADDR_START值(這裡主要是考慮地址對齊)來觀測Flash訊號的實際波形。此外為了便於分辨IO[1:0]上的資料,我們最好定義一塊特殊const資料區,根據Flash傳輸時序圖,其中資料Byte[4]和Byte[0]是在IO0線上傳輸、Byte[5]和Byte[1]是在IO1線上傳輸的,這4bit共有16種不同值組合,我們將這16種不同值放在ahbRdBlock[16]陣列中,並將其連結在 0x60002400 - 0x6000240f 地址空間裡。
// 在工程原始檔中
const uint8_t ahbRdBlock[16] @ ".ahbRdBuffer" = {0x00, 0x01, 0x02, 0x03,
0x10, 0x11, 0x12, 0x13,
0x20, 0x21, 0x22, 0x23,
0x30, 0x31, 0x32, 0x33};
// 在工程連結檔案中
keep{ section .ahbRdBuffer };
place at address mem:0x60002400 { readonly section .ahbRdBuffer };
三、實驗結果
3.1 AHB_ADDR_START = 0x6002400 即八位元組對齊
我們先來看AHB_ADDR_START = 0x6002400時抓取一次完整for迴圈結果的波形(見下圖),可以看到在八位元組對齊的地址下使用memcpy拷貝1/2/4/8位元組,均僅產生一次CS訊號有效週期(拉低),在這CS有效期間完成全部所需資料的讀取。但是拷貝3/5/6/7位元組時,會拆分出多個CS有效週期。
當使用memcpy拷貝3/5/6位元組時,會拆分出2個CS有效週期(見下圖),這裡第一個CS週期看起來似乎是多餘的,為什麼是這種結果,這需要深入研究AHB機制(痞子衡會另寫文章分析);
- 當拷貝3位元組時,第一個CS週期實際讀取了前2個位元組 [0x60002400, 0x60002401],第二個CS週期讀取了全部3位元組 [0x60002400, 0x60002402]。
- 當拷貝5位元組時,第一個CS週期實際讀取了前4個位元組 [0x60002400, 0x60002403],第二個CS週期讀取了全部5位元組 [0x60002400, 0x60002404]。
- 當拷貝6位元組時,第一個CS週期實際讀取了前4個位元組 [0x60002400, 0x60002403],第二個CS週期讀取了全部6位元組 [0x60002400, 0x60002405]。
當使用memcpy拷貝7位元組時,會拆分出3個CS有效週期(見下圖),這裡前兩個CS週期看起來似乎都是多餘的;
- 當拷貝7位元組時,第一個CS週期實際讀取了前4個位元組 [0x60002400, 0x60002403],第二個CS週期實際讀取了前6個位元組 [0x60002400, 0x60002405],第三個CS週期讀取了全部7位元組 [0x60002400, 0x60002406]。
3.2 AHB_ADDR_START = 0x6002404 即四位元組對齊
AHB_ADDR_START = 0x6002404時抓取一次完整for迴圈結果的波形(見下圖),可以看到在四位元組對齊的地址下使用memcpy拷貝1/2/4位元組,均僅產生一次CS訊號有效週期(拉低),在這CS有效期間完成全部所需資料的讀取。
但是拷貝3/5/6/7/8位元組時,會拆分出多個CS有效週期。不過其中拷貝5/6/8位元組,是合理的拆分,並沒有冗餘讀取。
3.3 AHB_ADDR_START = 0x6002401 即奇地址
AHB_ADDR_START = 0x6002401時抓取一次完整for迴圈結果的波形(見下圖),這種情況下CS拆分特別嚴重,幾乎都存在冗餘讀取。
3.4 AHB_ADDR_START = 0x6002402 即偶地址
AHB_ADDR_START = 0x6002402時抓取一次完整for迴圈結果的波形(見下圖),這種情況下CS拆分特別嚴重,幾乎都存在冗餘讀取。
3.5 AHB_ADDR_START = 0x6002403
AHB_ADDR_START = 0x6002403時抓取一次完整for迴圈結果的波形(見下圖),這種情況下CS拆分特別嚴重,幾乎都存在冗餘讀取。
至此,實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形痞子衡便介紹完畢了,掌聲在哪裡~~~
歡迎訂閱
文章會同時釋出到我的 部落格園主頁、CSDN主頁、知乎主頁、微信公眾號 平臺上。
微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。