1. 程式人生 > >例說STM32F7快取記憶體——Cache一致性問題(三)

例說STM32F7快取記憶體——Cache一致性問題(三)

3. Cache 一致性問題

3.1 什麼是 cache 一致性問題

  所謂的 Cache 一致性問題, 主要指的是由於 D-cache 存在時,表現在有多個 Host(典型的如 MCU 的 Core, DMA 等)訪問同一塊記憶體時, 由於資料會快取在 D-cache 中而沒有更新實際的實體記憶體。
  在實際應用中,有以下兩種情況:
  第一種情況是當有寫實體記憶體的指令時,Core 會先去更新相應的 cache-line(Write-back 策略),在沒有 clean 的情況下,會導致其對應的實際實體記憶體中的資料並沒有被更新,如果這個時候有其它的 Host(如 DMA)訪問這段記憶體時,就會出現問題(由於實際實體記憶體並未被更新,和 D-cache 中的不一致),這就是所謂的 cache 一致性的問題。

這裡寫圖片描述

圖3.1 Cache 一致性問題 第1種情況

  第二種情況是 DMA 更新了某段實體記憶體(DMA 和 cache 直接沒有直接通道),而這個時候 Core 再讀取這段記憶體的時候,由於相對應地址的 cache-line 沒有被 invalidate,導致 Core 讀到的是 cache-line 中的資料,而非被 DMA 更新過的實際實體記憶體的資料。

這裡寫圖片描述

圖3.2 Cache 一致性問題 第2種情況

3.2 如何處理 cache 一致性問題

  我們知道,Cache 機制是為了提高儲存系統的平均讀寫效能而設計的,但是這種機制帶來了資料一致性問題,然而,卻沒有對一致性的硬體支援


  因此為了解決一致性問題,一個辦法就是禁用 Cache(cache 都禁用了,肯定不會有 cache 一致性的問題啦~)。但是如果你選擇使用 STM32F7 這樣高效能的微控制器,又不使用其帶來的高效能特性,那你為什麼要用 F7 呢,用 F3、F4 不就得了麼?所以為了提高效能,還是使能 cache,並積極解決 cache 一致性問題吧。
  好吧,解決 STM32F7 的 cache 一致性問題,有兩種可選方案:

  所有的共享儲存器都定義為共享屬性
    • 這些區域將預設不被快取到 D-Cache。
    • 所有的操作都直接針對二級儲存器(內部Flash,外部儲存器),效能降低。
    • 因為快取對這些區域是透明的,寫軟體更容易。
  
  通過軟體進行cache的維護


  (1)Cortex-M7 的寫操作要是全域性可見的
    • 使用透寫屬性(通過 MPU 設定)。
    • 使用 [email protected](Shared = Write Through)。
    • 通過指令清 D-cache,然後所有更新位置禁止 D-Cache操作。
  (2)其他主裝置的寫操作要對 Cortex-M7 可見
    • 比如作廢 Cortex-M7 Dache 中資料。

3.3 示例

3.3.1 程式描述

  (1)首先將地址 0x20020000(SRAM1)處開始的 128 位元組初始化為 0x55。
  (2)將 Flash 中的 128 位元組的常量陣列 aSRC_Const_Buffer 拷貝到 SRAM1 地址 0x20020000(pBuffer)。
  (3)配置並使能 DMA,通過 DMA 將資料從 SRAM1 的地址 0x20020000 處拷貝到 DTCM RAM 中的陣列 aDST_Buffer 中。
  (4)將 Flash 中的陣列 aSRC_Const_Buffer 與 DMA 讀出的陣列 aDST_Buffer 進行比較。

  顯然,這個例子中的 cache 一致性問題, 展示的是上面(圖3.1)的第一種情況。也就是在 Write-back 策略下,CPU 先去更新相應的 cache-line,然後 DMA 去訪問對應的記憶體,從而導致資料不一致的現象。
  程資料的傳輸流程和路徑如下圖所示:

這裡寫圖片描述

圖3.3 Cache 示例資料傳輸框圖

3.3.2 復現 cache 一致性問題

  我們先來按照示例要求編寫程式碼,復現 cache 一致性問題。有些人可能會疑惑,變數資料怎麼放到 Flash、SRAM1、DTCM?實際上,可以通過一些相關的配置檔案進行設定,比如 icf 檔案、scatter 檔案等,當然,這跟所使用開發環境和編譯工具鏈有關。
  本文所使用的環境是 IAR,其連結檔案 *.icf 如下:

這裡寫圖片描述

  然後將 aSRC_Const_Buffer 陣列定義為常量,即可分配到 RO 區域,aDST_Buffer 定義為普通的全域性變數或靜態變數即可,因為記憶體區域從 0x20000000 開始,也就是 DTCM RAM。
  好了,程式碼主體部分如下:
  (完整的程式碼可以在 http://download.csdn.net/download/luckydarcy/10104739 下載)

#define SRAM1_ADDRESS_START   (0x20020000UL)

static const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =
{
  0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
  0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
  0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,
  0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,
  0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
  0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,
  0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,
  0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80
};

static uint32_t aDST_Buffer[BUFFER_SIZE];

int main(void)
{
  uint32_t counter = 0;
  uint32_t *pBuffer = (uint32_t*)SRAM1_ADDRESS_START;

  if (HAL_Init() != HAL_OK)
  {
    Error_Handler();
  }

  /* Initialize LEDs */
  BSP_LED_Init(LED1);

  /* Configure the system clock to 216 MHz */
  SystemClock_Config();
  BSP_LCD_Config();

  /* Set to 1 if an transfer error is detected */
  transferErrorDetected = 0;

  /* Fill 128 bytes with 0x55 pattern */
  memset((uint8_t*)SRAM1_ADDRESS_START, 0x55, sizeof(aSRC_Const_Buffer));

  /* TODO:Enable MPU and change SRAM region attribute 
  * set write-back policy on SRAM */
  MPU_Config();

  /* Enable Data cache */
  SCB_EnableDCache();

  /* Copy data from Flash to SRAM by CPU */
  for (counter = 0; counter < (sizeof(aSRC_Const_Buffer)/4); counter++)
  {
    *pBuffer++ = aSRC_Const_Buffer[counter];
  }

  //* Configure and enable the DMA stream for Memory to Memory transfer */
  DMA_Config();

  /* Wait for DMA end-of-transfer */

  while(TransferCompleteFlag == RESET)
  {
  }

  /* Check data integrity*/
  pBuffer = (uint32_t*)&aDST_Buffer;
  for(counter = 0; counter <(sizeof(aSRC_Const_Buffer)/4); counter++)
  {

    if(aSRC_Const_Buffer[counter] != *pBuffer)
    {
    compareErrorDetected++;
    }
    pBuffer++;
  }

  if (compareErrorDetected != 0)
  {
      /* Toggle LED1 */
      BSP_LED_Off(LED1);
      compareErrorDetected = 0; 
      BSP_LCD_DisplayStringAtLine(10, (uint8_t *)"       Data comparation failed!      ");
  }
  else
  {
      /* Turn LED1 on */
      BSP_LED_On(LED1);
      BSP_LCD_DisplayStringAtLine(10, (uint8_t *)"       Data comparation success!      ");
  }

  while (1)
  {

  }
}

static void MPU_Config(void)
{
  /* Disable MPU */
  MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

  /* Configure RAM region as Region N°0, 256kB of size and R/W region */
  MPU->RNR  = SRAM1_REGION_NUMBER;
  MPU->RBAR = SRAM1_ADDRESS_START;

   /* Write-Back policy */
  MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | MPU_RASR_B_Msk | SRAM1_ACCESS_PERMISSION | 1<<MPU_RASR_TEX_Pos;

  /* Enable MPU */
  MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}

  為了確保 aSRC_Const_Buffer 在 Flash,aDST_Buffer 在 DTCM,我們可以在編譯完之後檢視 *.map 檔案,如下:

這裡寫圖片描述

圖3.4 檢查常量和變數分配情況

  下載到 STM32F769I-DISCO 板子上,顯然,由於此時開啟了 D-Cache,會出現資料不一致的現象,執行結果如下所示:

這裡寫圖片描述

圖3.5 Cache 資料不一致

3.3.3 解決方案

(1)不啟動 D-Cache

  註釋掉 SCB_EnableDCache();
  不啟動 D-Cache,當然也就沒有了 Cache 資料不一致的問題啦~

(2)將 SRAM1 相應區域設定為 shareable

  通過 MPU 將 SRAM1 相應區域設定為 shareable,MPU_Config() 函式處理如下:

static void MPU_Config(void)
{
  /* Disable MPU */
  MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

  /* Configure RAM region as Region N°0, 256kB of size and R/W region */
  MPU->RNR  = SRAM1_REGION_NUMBER;
  MPU->RBAR = SRAM1_ADDRESS_START;

  /* Shareable */
  MPU->RASR = SRAM1_SIZE | MPU_RASR_S_Msk | SRAM1_ACCESS_PERMISSION;

  /* Enable MPU */
  MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}

(3)DMA 訪問 SRAM1 前先 Clean cache

  在啟動 DMA 訪問之前,程式設計師需要在合適的地方將 D-Cache 資料回寫到主記憶體中,也就是 Clean 的操作。
  在本示例中,可以在 DMA_Config(); 前呼叫:

SCB_CleanDCache();

或者

SCB_CleanDCache_by_Addr((uint32_t*)SRAM1_ADDRESS_START, sizeof(aSRC_Const_Buffer));

(4)將 SRAM1 相應區域設定為 Write-through 策略

  通過 MPU 將 SRAM1 相應區域設定為透寫模式(Write-through),MPU_Config() 函式處理如下:

static void MPU_Config(void)
{
  /* Disable MPU */
  MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

  /* Configure RAM region as Region N°0, 256kB of size and R/W region */
  MPU->RNR  = SRAM1_REGION_NUMBER;
  MPU->RBAR = SRAM1_ADDRESS_START;

  /*Write Through policy*/
  MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | SRAM1_ACCESS_PERMISSION;

  /* Enable MPU */
  MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}

(5)將所有 cacheable 的空間全部強制 Write-though

  通過 cache 控制暫存器,將所有 cacheable 的空間全部強制 Write-though 模式。

這裡寫圖片描述

圖3.6 CACR 暫存器(來自 PM0253)

  在初始化的時候進行設定:

__FORCE_WRITE_THROUGH();

  巨集定義為:

#define __FORCE_WRITE_THROUGH()    *(__IO uint32_t *)0xE000EF9C = 1UL<<2

  以上這是都是較為常用的方法,在實際的開發過程中,為了提高效能,一般都會開啟 cache,同時將其配置為 WB 策略,這就需要開發者在使用時特別小心!
  值得一提的是:對於第二種情況(圖3.2),就不是 clean 操作了,而是 invalidate。需要先呼叫 SCB_InvalidateDCache()SCB_InvalidateDCache_by_Addr() 去 invalidate 相應的 cache-line, 這樣當 CPU 在讀取時,會忽略 D-cache 中的內容,去真實的實體地址讀取對應的資料。

這裡寫圖片描述

圖3.7 Cache 資料一致

  好啦,通過上述幾種方法,就可以解決 cache 資料一致性問題。當然,除了我這裡提供的,還有其他方案,各種方案各有利弊,要根據實際應用場景去衡量,這就是嵌入式程式設計師展示才華的時候啦~