你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】例说STM32F7高速缓存——Cache一致性问题(三)

[复制链接]
STMCU小助手 发布时间:2021-12-13 23:02
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 一致性的问题。

516a4fcb212a09183f4c3f3efa77d170.png


图3.1 Cache 一致性问题 第1种情况


第二种情况是 DMA 更新了某段物理内存(DMA 和 cache 直接没有直接通道),而这个时候 Core 再读取这段内存的时候,由于相对应地址的 cache-line 没有被 invalidate,导致 Core 读到的是 cache-line 中的数据,而非被 DMA 更新过的实际物理内存的数据。

ba3341c9a0e0ddc198b5fe0466a465dc.png


图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 设置)。
使用 SIWT@CACR(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 去访问对应的内存,从而导致数据不一致的现象。

程数据的传输流程和路径如下图所示:

d0db73574a58707717ae26695e9dc302.png


图3.3 Cache 示例数据传输框图


3.3.2 复现 cache 一致性问题
我们先来按照示例要求编写代码,复现 cache 一致性问题。有些人可能会疑惑,变量数据怎么放到 Flash、SRAM1、DTCM?实际上,可以通过一些相关的配置文件进行设置,比如 icf 文件、scatter 文件等,当然,这跟所使用开发环境和编译工具链有关。

本文所使用的环境是 IAR,其链接文件 *.icf 如下:

5377967a69d8334b1bbd7b99f41c9e23.png


然后将 aSRC_Const_Buffer 数组定义为常量,即可分配到 RO 区域,aDST_Buffer 定义为普通的全局变量或静态变量即可,因为内存区域从 0x20000000 开始,也就是 DTCM RAM。

好了,代码主体部分如下:

  1. #define SRAM1_ADDRESS_START   (0x20020000UL)

  2. static const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =
  3. {
  4.   0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
  5.   0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
  6.   0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,
  7.   0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,
  8.   0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
  9.   0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,
  10.   0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,
  11.   0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80
  12. };

  13. static uint32_t aDST_Buffer[BUFFER_SIZE];

  14. int main(void)
  15. {
  16.   uint32_t counter = 0;
  17.   uint32_t *pBuffer = (uint32_t*)SRAM1_ADDRESS_START;

  18.   if (HAL_Init() != HAL_OK)
  19.   {
  20.     Error_Handler();
  21.   }

  22.   /* Initialize LEDs */
  23.   BSP_LED_Init(LED1);

  24.   /* Configure the system clock to 216 MHz */
  25.   SystemClock_Config();
  26.   BSP_LCD_Config();

  27.   /* Set to 1 if an transfer error is detected */
  28.   transferErrorDetected = 0;
  29.         
  30.   /* Fill 128 bytes with 0x55 pattern */
  31.   memset((uint8_t*)SRAM1_ADDRESS_START, 0x55, sizeof(aSRC_Const_Buffer));

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

  35.   /* Enable Data cache */
  36.   SCB_EnableDCache();

  37.   /* Copy data from Flash to SRAM by CPU */
  38.   for (counter = 0; counter < (sizeof(aSRC_Const_Buffer)/4); counter++)
  39.   {
  40.         *pBuffer++ = aSRC_Const_Buffer[counter];
  41.   }
  42.         
  43.   //* Configure and enable the DMA stream for Memory to Memory transfer */
  44.   DMA_Config();

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

  46.   while(TransferCompleteFlag == RESET)
  47.   {
  48.   }

  49.   /* Check data integrity*/
  50.   pBuffer = (uint32_t*)&aDST_Buffer;
  51.   for(counter = 0; counter <(sizeof(aSRC_Const_Buffer)/4); counter++)
  52.   {
  53.                
  54.     if(aSRC_Const_Buffer[counter] != *pBuffer)
  55.     {
  56.         compareErrorDetected++;
  57.     }
  58.     pBuffer++;
  59.   }
  60.         
  61.   if (compareErrorDetected != 0)
  62.   {
  63.       /* Toggle LED1 */
  64.       BSP_LED_Off(LED1);
  65.       compareErrorDetected = 0;        
  66.       BSP_LCD_DisplayStringAtLine(10, (uint8_t *)"       Data comparation failed!      ");
  67.   }
  68.   else
  69.   {
  70.       /* Turn LED1 on */
  71.       BSP_LED_On(LED1);
  72.       BSP_LCD_DisplayStringAtLine(10, (uint8_t *)"       Data comparation success!      ");
  73.   }

  74.   while (1)
  75.   {

  76.   }
  77. }

  78. static void MPU_Config(void)
  79. {
  80.   /* Disable MPU */
  81.   MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

  82.   /* Configure RAM region as Region N°0, 256kB of size and R/W region */
  83.   MPU->RNR  = SRAM1_REGION_NUMBER;
  84.   MPU->RBAR = SRAM1_ADDRESS_START;
  85.          
  86.    /* Write-Back policy */
  87.   MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | MPU_RASR_B_Msk | SRAM1_ACCESS_PERMISSION | 1<<MPU_RASR_TEX_Pos;

  88.   /* Enable MPU */
  89.   MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
  90. }
复制代码

为了确保 aSRC_Const_Buffer 在 Flash,aDST_Buffer 在 DTCM,我们可以在编译完之后查看 *.map 文件,如下:

d4c481b36d81bb11f17b07a56ac1f054.png


图3.4 检查常量和变量分配情况


下载到 STM32F769I-DISCO 板子上,显然,由于此时开启了 D-Cache,会出现数据不一致的现象,执行结果如下所示:

bd661d50dcd8f45abeeabdd2ac3c0289.jpg


图3.5 Cache 数据不一致


3.3.3 解决方案
(1)不启动 D-Cache

注释掉 SCB_EnableDCache();

不启动 D-Cache,当然也就没有了 Cache 数据不一致的问题啦~

(2)将 SRAM1 相应区域设置为 shareable

通过 MPU 将 SRAM1 相应区域设置为 shareable,MPU_Config() 函数处理如下:
  1. static void MPU_Config(void)
  2. {
  3.   /* Disable MPU */
  4.   MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

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

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

  10.   /* Enable MPU */
  11.   MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
  12. }
复制代码

(3)DMA 访问 SRAM1 前先 Clean cache

在启动 DMA 访问之前,程序员需要在合适的地方将 D-Cache 数据回写到主内存中,也就是 Clean 的操作。

在本示例中,可以在 DMA_Config(); 前调用:
  1. SCB_CleanDCache();
复制代码
或者
  1. SCB_CleanDCache_by_Addr((uint32_t*)SRAM1_ADDRESS_START, sizeof(aSRC_Const_Buffer));
复制代码

(4)将 SRAM1 相应区域设置为 Write-through 策略

通过 MPU 将 SRAM1 相应区域设置为透写模式(Write-through),MPU_Config() 函数处理如下:

  1. static void MPU_Config(void)
  2. {
  3.   /* Disable MPU */
  4.   MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

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

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

  10.   /* Enable MPU */
  11.   MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
  12. }
复制代码

(5)将所有 cacheable 的空间全部强制 Write-though

通过 cache 控制寄存器,将所有 cacheable 的空间全部强制 Write-though 模式。

e131d05081f8e30ba2296e50f3e9f392.png


图3.6 CACR 寄存器(来自 PM0253)


在初始化的时候进行设置:
  1. __FORCE_WRITE_THROUGH();
复制代码

宏定义为:
  1. #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 中的内容,去真实的物理地址读取对应的数据。

9dffde7b310ed170c1b45f9dd7f75abe.jpg


图3.7 Cache 数据一致


好啦,通过上述几种方法,就可以解决 cache 数据一致性问题。当然,除了我这里提供的,还有其他方案,各种方案各有利弊,要根据实际应用场景去衡量,这就是嵌入式程序员展示才华的时候啦~



收藏 评论0 发布时间:2021-12-13 23:02

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版