8
0
0

Tircore Cache

2026-10-08
2026-10-22

Cache 到底缓存什么

主要有两类 Cache:

Cache

缓存内容

由什么访问触发

P-Cache / PCACHE

程序指令

CPU 从地址取指令

D-Cache / DCACHE

数据

CPU 执行 load/store 数据访问

例如:

uint32 value = table[100];
foo();

如果 table 位于可缓存的数据区域,那么对 table[100]的读取可能经过 D-Cache。

如果函数foo()位于可缓存的代码区域,那么 CPU 执行 foo()时的指令获取可能经过 P-Cache。

Cache Line

TC3xx 的指令缓存和数据缓存,操作的最小元素为Cache Line,每条都是 32 字节

一条Cache Line除了保存 32 字节的指令或数据,还关联着管理信息:

内容

作用

数据区

保存连续的 32 字节

Tag(标记)

标识缓存的是哪一块内存

Valid(有效位)

判断这一行是否可以使用

Dirty(脏位,数据缓存)

判断数据是否被 CPU 修改、尚未写回

ECC

用于错误检测和纠正

写回、失效和旁路

操作

RAM 中的数据

缓存行状态

写回 W

将脏数据更新到 RAM

保持有效,变为干净

失效 I

不写回脏数据

变为无效,脏数据可能丢失

写回并失效 WI

先更新 RAM

再变为无效

旁路 Bypass

后续访问绕过缓存

已有缓存行仍保留

Cache 如何工作

假设执行:

value = (uint32_t )0x90001000;

并假设 0x9... 是允许数据缓存的区域。

大致过程如下。

1. CPU 发出数据读取

CPU 计算出地址:0x90001000,硬件根据地址的最高 4 位判断它属于段 9H。

2. 判断内存属性

硬件检查:

  • 该地址是否属于 Peripheral Space(外设空间)

  • 该段的数据访问是否允许缓存

  • D-Cache 是否存在并已启用

  • 是否有其他硬件规则强制绕过 Cache

如果最终判断允许使用 D-Cache,就查询 D-Cache。

3. 查询 D-Cache

检查对应Cache内是否有 Valid 且 Tag 匹配的行。

找到就是命中,直接使用缓存中的内容。

如果存在,称为:

Cache Hit:缓存命中

CPU 直接从 D-Cache 得到数据,速度较快。

如果不存在,称为:

Cache Miss:缓存未命中

此时硬件会:

  1. 从实际存储器或总线读取对应 Cache Line。

  2. 将 Cache Line 放入 D-Cache。

  3. 把 CPU 需要的数据返回给 CPU。

  4. 后续对同一 Cache Line 的读取可能直接命中。

整个过程由硬件自动完成。

DCache数据缓存是写回型(write-back)

例如 RAM 中某个变量原来为 0:

读入缓存后:        缓存 = 0,RAM = 0,Valid = 1,Dirty = 0
CPU 将变量改为 1:  缓存 = 1,RAM = 0,Valid = 1,Dirty = 1
完成写回后:        缓存 = 1,RAM = 1,Valid = 1,Dirty = 0

CPU 的修改不一定立即到达 RAM。

只要这条 dirty line 没有被替换,也没有被显式 flush,物理 RAM 里就一直是旧值。

而且每条数据缓存行只有一个 Dirty 位,因此即使只修改其中 1 字节,需要写回时仍会写回整条 32 字节。

这也是共享内存操作容易出问题的原因:CPU 和 DMA 如果分别修改同一行内的不同字节,CPU 后续的整行写回可能覆盖 DMA 的修改。工程上通常需要让共享缓冲区按缓存行对齐,并明确安排访问权限和维护时机。

内存访问类型

内存访问类型分为3类:

  1. Cacheable Memory:可缓存内存

  2. Non-cacheable Memory:不可缓存内存

  3. Peripheral Space:外设空间

访问可缓存内存地址时,代码取指、数据读取都可以进入对应的Cache;同时CPU可以进行推测性取指、推测性读取;

访问不可缓存内存地址时,代码取值、数据读取不进入Cache;但仍允许预测性取指、读取,即CPU仍然可能提前读取某个地址;

对于一些外设寄存器来说,读取操作会造成寄存器复位,例如中断标志位;所以只把设备寄存器设置为“不可缓存”通常还不够,如果允许推测读取,就可能在程序真正执行读取指令前触发类似于“复位”的副作用,所以外设空间就单独设置了。

CPU的推测访问

CPU 为了提高性能,可能提前访问它认为程序随后会用到的地址,这就是 speculative access。

文档把访问分为两类:

  1. 必要访问

程序正确执行必然需要发生的访问。例如:

x = *ptr;

当这条语句真正执行时,对 *ptr 的读取属于必要访问。

  1. 推测访问

CPU 为了提高性能而提前执行的访问。例如:

  • 提前取后续指令

  • 分支结果还没确定时,提前取某条路径上的指令

  • 提前读取可能马上使用的数据

如果分支预测错误,某些被读取的内容最终可能根本没有被程序使用(即没有命中)。

Cache 使用限制

即使 PMA 寄存器允许缓存,硬件仍然强制执行以下限制:

  1. 外设空间永远不能缓存。

  2. 当前 CPU 的本地 DSPR (段D)内容不能进入当前 CPU 的数据 Cache。

  3. 当前 CPU 的本地 PSPR(段C) 内容不能进入当前 CPU 的程序 Cache。

本地DSPR,PSPR指的是,当前CPU核心的DSPR,PSPR

例如Core0的DSPR,PSPR为0x7段,Core1的为0x6段

值得是各个Core访问0xD段后,解析到的地址

本地CPU访问本地DSPR不会经过Cache,因为访问速度已经足够快了(一个机器周期)

这些限制由硬件强制实施,不受 PMA0/PMA1 配置影响

注:虽然对DSPR/PSPR的本地地址访问不能进入Cache,但是全局地址访问可以配置Cache(例如Core1配置Core0 DSPR0为目标DCache地址)

默认内存属性

8H、9H:默认缓存

CH、DH:即使配置了也不可缓存

EH、FH:默认外设空间

其他段:默认不缓存(除C、D段都可配置缓存)

Cache相关寄存器

PMA寄存器

用途:配置缓存段

寄存器

控制内容

PMA0

数据访问是否可缓存

PMA1

代码访问是否可缓存

PMA2

是否属于外设空间

默认PMA0,PMA1为0x0300,对应位8、位9置1,对应8段、9段是缓存段

在默认的PMA0配置中,允许Cache的地址段为8段(pflash)和9段(LMU),则Core1访问Core0 DSPR是不经过Cache缓存的

默认PMA2为0xC000,对应位14、位15置1,对应E, F段为外设空间

DCON PCON寄存器

用途:使能缓存

寄存器

功能

特点

DCON0

Data Cache 控制

DCON2

查询 DCache、DSPR 容量

只读

PCON0

Program Cache 控制

PCON1

Cache / program line buffer 操作

PCON2

查询 PCACHE、PSPR 容量

只读

DCON0 - Data Memory Control

  • DCBYP,bit 1,读写:0 = DCache/DRB 可用;1 = DCache/DRB 旁路/禁用。

  • 复位后为旁路状态。

  • DCON0 受 CPUx Endinit 保护;修改前后按安全写流程管理 Endinit。

  • 其余位保留,写入 0。

DCON2 - Data Control 2(只读)

  • DCACHE_SZE,bits [15:0]:DCache 容量,单位 KB。

  • DSCRATCH_SZE,bits [31:16]:Data Scratchpad 容量,单位 KB。

  • 复位值未定义(X);读取实现容量。

PCON0 - Program Control 0

  • PCBYP,bit 1,读写:0 = Program Cache 可用;1 = 旁路/禁用 Program Cache。

  • 复位值 bit 1 为 1,即复位后 Cache 旁路。

  • 其他位保留:读作 0,写入时应写 0。

PCON1 - Program Control 1

  • PCINV,bit 0,读写:写 1 发起整个指令 Cache 失效;读回 1 表示失效仍在进行、指令 Cache 不可用。读回 0 为正常状态。

  • PBINV,bit 1,读写:写 1 使 program line buffer 失效;写 0 无操作。该位读回恒为 0。

  • 其他位保留,写入 0。

  • 发起失效后,需依据读回状态确认操作结束,再依芯片要求执行必要的同步操作。

PCON2 - Program Control 2(只读)

  • PCACHE_SZE,bits [15:0]:Program Cache 容量,单位 KB。

  • PSCRATCH_SZE,bits [31:16]:Program Scratch 容量,单位 KB。

  • 复位值未定义(X);用途是读取实现容量,不是配置容量。

多核Cache注意点

AURIX多核之间没有硬件Cache 一致性
CPU0 不会感知 CPU1 Cache 里的 dirty 数据,CPU1 也不会感知 CPU0 对 DSPR0 的修改。

这就要求程序开发者在设计跨核变量时,考虑Cache的一致性

跨核DSPR Cache测试

我在项目中观察到,使用Tc366双核芯片,Core0和Core1中分别有各自的APP

但是全部变量都放置在Core0的DSPR中

因为没有将两个核心的应用分开到两个c文件中,分别对应编译,并连接到各自的APP中去

根据手册,了解到各自的CPU核心访问各自的DSPR,PSPR速度是最快的,而访问别的核心的就需要通过SRI总线(System Resource Interconnect),访问速度相对较慢;具体周期可见下图所示:

可见,Core1访问Core0 DSPR需要通过本地SRI0,需要stall(等待) 7个CPU周期,而Local CPU访问Local DSPR不需要Stall,极大的缩短了运行时间;

下图给出了TC3xx下的CPU架构分布图:

1. 验证本地CPU访问本地DSPR是否经过Cache

测试代码:

采用的相关CPU调试寄存器CCTRL(CPU Clock Cycle计数器)请查看——TriCore Performance Counter性能计数器

变量cache_test_value 确保在编译后定位在0x7段(DSPR0)中

测试时,保证CPU0 DCache处于开启状态(非bypass旁路)

且PCache也处于开启状态(非bypass旁路)

通过UDE在读取CPU_CCNT处打断点

观察d15 的值

次数

D15

1

2

3

4

5

现在通过修改DCON0寄存器关闭CPU0 DCache(开启Cache bypass旁路模式)

关闭DCache后,CPU负载率显著上升(因为数据读取所花费的时间变多了)

DCache开启

DCache关闭

并再次测试连续读取DSPR变量所用的CPU时钟周期

次数

D15

1

2

3

4

5

发现读取时间并没有变化,可得出结论:本地CPU访问本地DSPR不需要经过DCache

P.S. 当关闭PCache后运行时间增长,是因为CPU不从PCache中获取指令,而是直接从PFlash中取指,时间大大增加了;

PCache开启

PCache关闭

2. 验证非本地CPU访问非本地DSPR

Core1访问Core0 DSPR0,

在泛亚CEPS项目中,各个APP并没有分开放到不同的C文件中去,导致全部的变量都存放在DSPR0中;

这就导致了CPU1必须通过SRI总线访问CPU0的DSPR,这种访问通常情况下需要stall等待7个CPU周期;

测试逻辑代码与前面相同:

首先要确保的是cache_test_value_core1存放在DSPR0中

同时CPU1的DCache是开启的

经测试后发现

次数

D15

1

2

3

4

5

对比发现,10次DSPR读取增加了大概70个CPU时钟周期,对应每次读取所等待7周期时间,共7*10

后面也测试了关闭CPU1 DCache的情况下,结果相同

所以可以得出结论:CPU读取非本地DSPR比较耗时

这也可以提供一个降低CPU负载率的思路:将Core1应用的用到的变量应该放到本地DPSR1中

或者,让CPU1 DCache缓存DSPR0

打开后,Core1负载率显著下降5%(没开预驱,且电机控制不使能条件下)

0x7段缓存关闭

0x7段缓存开启

目前项目这样修改的缺点:

由于Core1中运行APP所使用的全部变量都放置在DSPR0中(非本地CPU DSPR)

当CPU1将0x7段使能DCache缓存后,会将用到的变量转存到自己本地DCache中

又由于DCache的Write-back策略特性,变量全部的load-store操作,全部在本地DCache中已经完成了,根本不需要写回到DSPR0中去!

这就导致物理地址范围(变量对应的DSPR0地址值)仍然保持不变,

就会出现:在UDE中观察不到变量变化,Core0读取不到最新Core1修改的变量值。

所以,当使用跨核心的共享变量的时候,强制!必须!不能!使用Cache缓存共享变量

或者,使用Cache相关指令刷新对应的Cache,强制使其写入到物理地址中去。

指令

含义

使用场景

CACHEA.I

将地址所在的Cache Line Invalid,不写回脏数据,丢弃Cache Line的数据

跨核读取共享变量,强制使Cache中的值失效,重新从物理地址读取

CACHEA.W

将地址所在Cache Line 写回;写回操作将清除该Cache Line的Dirty状态,不做Invalid无效化

将共享变量写回到物理地址去,使其全局可见

CACHEA.WI

先回写指定位置的脏数据,再将缓存行无效化

跨核写入并读取共享变量

多核共享DLMU测试

TC366没有LMU,但是有DLMU(Distributed LMU SRAM,分布式LMU)

CPU0对应的为DLMU0,地址范围0xB000,0000-0xB000,FFFF;

CPU1对应的为DLMU1,地址范围0xB001,0000-0xB001,FFFF;

DLMU是按CPU分布的,每个CPU都有本地的LMU,

CPU访问本地LMU速度和DSRP一样快,load-store操作同样不经过缓存

通常可以通过0x9段缓存访问方式和0xB段非缓存访问方式来访问DLMU。

通过0x9段缓存访问DLMU需要注意:

Core0访问DLMU0(地址0x9000,0000-0x9000,FFFF),为直接访问,不经过Cache;

Core0访问DLMU1(地址0x9001,0000-0x9001,FFFF),经过Cache,回写存在延迟,需要手动管理Cache

同理,Core1访问DLMU1为直接访问,访问DLMU0经过Cahce

验证过程:

Core0写入存放在DLMU0中的变量cache_dlmu0 ,Core1通过Cache和None-Cache两种方式读取

在ld中定义变量链接地址:

编译完成后在map中查看:

在Core0_1ms_task中写入变量cache_dlmu0 ,然后从Core1_1ms_task中读取cache_dlmu0 和none_cache_dlmu0

发现Core0写入后,Core1读取Cache变量的初始值0,但读取None-Cache变量为最新值

这是由于Core1在读取变量cache_dlmu0 时,在DCache中找到了(缓存命中),直接从本地Cache中取出旧值;

Core0对DLMU0的读写并不会报告给DCache1,所以DCache1中的变量依然为Clean的,不会触发从DLMU0中重新读取

而通过none_cache_dlmu0 读取时,不经过DCache,直接从物理内存中读取,所以可以读取到最新值

这种情况下,共享变量强烈建议直接使用非缓存地址读取,如有缓存要求,可以通过软件维护缓存指令,使缓存中变量失效,从而使缓存重新从DLMU0物理地址中读取。

其原理是调用CACHEA.I 指令

类似的,CPU1通过Cache写DLMU0中变量也没办法及时更新到物理内存中去,

可以通过缓存写指令CACHEA.W 将其写回;

Support

If this article helped you, feel free to share it or give support!