Cortex_m7内核cache深入了解和应用

一,cache概述

从下图可以看出,从M7内核才开始有的cache,这对于从M0,M3,M4一路走来的小伙伴来说,多了一个cache就多了一个障碍。

Cortex-M7 core with 32K/32K L1 I/D-Cache!这提供了极高的性能,代码无论是从芯片上的内存,外部闪存,还是外部内存运行!介绍种类包括:L1 cache, memory types, attributes and MPU(Memory Protection Unit). 指导用户如何使用缓存开发以正确和高性能方式运行应用程序。

 

cache价值:由于对这些存储器的子系统,比如 OCRAMFlexSPISEMC的访问可能需要多个周期(特别是在具有多个等待状态的外部存储器接口上),因此L1缓存的设计目的是加快对存储器的读/写操作。这带来了很大的性能提升。

 

I/DTCM(FlexRAM banks configured as TCM)CPU核直接访问,绕过L1缓存。因此,建议将关键代码和数据放入TCM中,就像向量表vector table一样

flush:在有的书中称作invalidate

存贮器结构如下图:

 

Cortex-M7是6级双发射流水线,下图是一个示意图。

 

高速缓存cache基础知识:

程序执行的局部规律性包括时间局部规律性和空间局部规律性。

时间局部规律性:在程序执行过程中,刚刚被访问的信息可能很快被再次访问,典型情况是程序中存在大量的循环。

空间局部规律性:在程序执行过程中,那些与被访问的地址相临近的信息也有可能很快被访问。典型情况是程序中存在大量的顺序执行。

按照程序执行的“局部性规律”,程序中的数据或者代码被访问后,该数据和代码以及邻近的数据代码近期再被访问的概率要远大于,近期未被访问的数据或者代码被访问的概率。因此,当数据或代码被访问后,被认为是经常访问的数据和代码,将被存入到cache,当再次访问该数据或者代码时直接从该cache读取数据或者代码的值,而不是到内存中重新读取。

有了cache后,core对内存中的数据访问流程如下图所示:

二.Cortex-M7内核的L1 Cache

 

L1 Cache由多行内存区组成,每行有32字节,每行都配有一个地址标签。

数据缓冲DCache:是每4行为一组,称为4-way set associative

指令缓冲区ICache:是2行为一组, 称为2-way set-associative

这样节省地址标签,不用每个行都标记一个地址。

Cache hit:要访问的数据/指令在cache里面.

Cache miss:要访问的数据/指令不在cache里面.

Core cache时:

hit,则直接从cache读出数据即可。

miss,有两种处理方式:

>read through ,直接从内存中读出

>read allocate,先把数据读到cache,再从cache读出。如果 CPU 要读取的 SRAM 区数据在 Cache 中已经加载好,就可以直接从 Cache 里面读取。如果没有,就用到配置 read allocate 了,意思就是在 Cache 里面开辟区域,将 SRAM 区数据加载进来,后续的操作,CPU 可以直接从 Cache 里面读取,从而时间加速。

 

 

Core cache时:

hit,两种处理方式:

>write-through模式:

可以直接写到内存中同时放到Cache里面,优点是内存和cache同步更新,没有多总线访问造成的数据一致性问题,缺点是无法在写操作上面发挥性能。

 

>Write back模式:Cache line会被标为dirty,等到此行被evicted(驱逐,赶出, flush)时,才会执行实际的写操作,将Cache Line里面的数据写入到相应的存 储区。Write back安全隐患:如果 Cache 命中的情况下,此时仅 Cache 更新了,而 SRAMSDRAM 没有更新,那么 DMA 直接从 SRAM 里面读出来的就是错误的。

 

 

miss,两种处理方式:

>write-allocate:先把要写的数据载入到cache,写cache,然后再flush进内存。

>no-write-allocate:直接写入内存。

 

Cache 命中是访问的地址落在了给定的 Cache Line 里面,所以硬件需要做少量的地址比较工作,以检查此地址是否被缓存。如果命中了,将用于缓存读操作或者写操作。如果没有命中,则分配和标记新行,填充新的读写操作。如果所有行都分配完毕了,Cache 控制器将支持 eviction 操作。根据 Cache Line 替换算法,一行将被清除 Clean,无效化 Invalid 或者重新配置。数据缓存和指令缓存是采用的伪随机替换算法。Cache支持的4种基本操作,1.使能,2.禁止,3.清空,4.无效化。

 

 

Clean清空操作是将Cache Line中标记为dirty的数据写入到内存里面,无效化Invalid是将 Cache Line 标记为无效,等同于删除操作。这样Cache 空间就都腾出来了,可以加载新的指令/数据。

三.cache配合MPU使用

MPU(memory protection unit),首先需要通过MPU配置相应memory的属性(normal, strongly-ordered, device, XN etc.)

RT1052存储器默认映射和属性

 

 

四.什么是 cache 一致性问题

对于指令缓冲I-Cache,用户不用管,这里主要说的是数据缓存 D-Cache。

所谓的 Cache 一致性问题, 主要指的是由于 D-cache 存在时,表现在有多个 Host(典型的如 MCU 的 Core, DMA 等)访问同一块内存时, 由于数据会缓存在 D-cache 中而没有更新实际的物理内存。

在实际应用中,有以下两种情况:

4.1

第一种情况是当有core写物理内存(SRAM,0x20200000)的指令时,(对应SDK例程:*(uint8_t *)(startAddr + count) = 0xffu;)Core 会先去更新相应的 cache-line(Write-back 策略),在没有 clean 的情况下,会导致其对应的实际物理内存中的数据并没有被更新,如果这个时候有其它的 Host(如 DMA)访问这段内存时,就会出现问题(由于实际物理内存并未被更新,和 D-cache 中的不一致),如果clean一下(对应SDK例程:L1CACHE_CleanDCacheByRange(startAddr, MEM_DMATRANSFER_LEN);)这就是所谓的 cache 一致性的问题。

4.2

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

五。解决 cache 一致性问题

有两种可选方案:

一.所有的共享存储器都定义为共享属性

  • 这些区域将默认不被缓存到 D-Cache。

  • 所有的操作都直接针对二级存储器(内部Flash,外部存储器),性能降低。

  • 因为缓存对这些区域是透明的,写软件更容易

二.通过软件进行cache的维护

1)Cortex-M7 的写操作要是全局可见的

    • 使用透写属性(通过 MPU 设置)。

    • 使用 SIWT@CACR(Shared = Write Through)。

    • 通过指令清 D-cache,然后所有更新位置禁止 D-Cache操作

      这种情况,在DMA从SRAM搬运数据到SDRAM时,需要先执行clean。

          L1CACHE_CleanInvalidateDCache();

2)其他主设备的写操作要对 Cortex-M7 可见

    • 比如作废 Cortex-M7 Dache 中数据

       这种情况,在DMA从SDRAM搬运数据到SRAM时,需要搬运后执行Invalidate。

     L1CACHE_CleanInvalidateDCache()

六.常见函数解释

 SCB_EnableICache() SCB_EnableDCache()

  使能 I-cache D-cache

SCB_DisableICache() SCB_DisableDCache()

  禁用 I-cache D-cache

 

SCB_InvalidateICache()

  使 I-cache 无效,I-cache invalidate 之后,当读取指令时,会忽略相应的 cache-line 中的内容(因为被 validate 了),而从真实的物理地址中去获取相应的指令

SCB_InvalidateDCache()

  使 D-cache 无效,D-cache invalidate 之后,当有 Host(如 coreDMA 等)读取数据时,会忽略相应的 cache-line 中的内容( 因为被 validate 了),从真实的物理地址中去获取相应的数据

SCB_InvalidateDCache_by_Addr()

  根据地址信息无效其对应的 cache-line

 

SCB_CleanDCache()  

  Clean 所有的 cache-line,即将 dirty cache-line 全部写到 cache line 对应的真实的物理地址中所谓的 drity 属性,

即写操作时, 更新了相应的 cache-line,但是没有更新到真实的物理地址,而这个 clean 的动作, 就是将 cache 中的内容更新到真实的物理地址中。

SCB_CleanDCache_by_Addr()

  根据地址信息 clean 其对应的 cache-line

SCB_CleanInvalidateDCache_by_Addr()

  根据地址信息 clean invalidate 其对应的 cache-line

SCB_CleanInvalidateDCache()

七. 其他指令解释

功能描述
DMB
数据存储器隔离。DMB 指令保证: 仅当所有在它前面的存储器访问操作
都执行完毕后,才提交(commit)在它后面的存储器访问操作。

DSB
数据同步隔离。比 DMB 严格: 仅当所有在它前面的存储器访问操作
都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访 问操作——译者注)

ISB
指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执
行完毕之后,才执行它后面的指令。

对于高级底层技巧:“自我更新”(self-mofifying)代码,非常有用。举例 来说,如果某个程序从下一条要执行的指令处更新了自己,

但是先前的旧指令已经被预取到流水线 中去了,此时就必须清洗流水线,把旧版本的指令洗出去,再预取新版本的指令。因此,必须在被 更新代码段的前面使用 ISB,以保证旧的代码从流水线中被清洗出去,不再有机会执行

Bootloader在跳转到app之前的清洗,就是1个例子,如下:

--------------------------------------------

  LPUART_Deinit(LPUART1);

    vSceneRenew();

__ISB();

    __DSB();

    /* Enable I cache and D cache */

    SCB_DisableDCache();

    SCB_DisableICache();

vControlSwitch();

 --------------------------------------------------------------------

L1CACHE_InvalidateICacheByRange函数也是1个例子:

/**Invalidate cortex-m7 L1 instruction cache by range.***/

void L1CACHE_InvalidateICacheByRange(uint32_t address, uint32_t size_byte)

{

#if (__DCACHE_PRESENT == 1U)

 uint32_t addr = address & (uint32_t)~(FSL_FEATURE_L1ICACHE_LINESIZE_BYTE - 1);

    int32_t size = size_byte + address - addr;

    uint32_t linesize = 32U;

 __DSB();

 while (size > 0)

    {

        SCB->ICIMVAU = addr;

        addr += linesize;

        size -= linesize;

  }

    __DSB();

    __ISB();

#endif    

}

以上内容参考了网上很多博客,还有官方文档,再次表示感谢,若有侵权,请联系删除,谢谢!

技术交流微信 18124528727.

-------------------------------------------------END---------------------------------------------------------------

原文地址:https://www.cnblogs.com/zhihui-3669/p/10908533.html