AXI协议相较于UART,SPI,I2C来说,无论是内容还是难度都上了一个层级,放在一篇文章中进行解读未免篇幅过长,因此,有关AXI一些共性的、通用的问题,作者单独以前缀为【AXI】的标题进行小范围的串联,最终再汇总为深入浅出解读AXI协议,与从零开始的Verilog AXI协议设计,此为作者所思所考的推进顺序,单看【AXI】的每一篇,可能很多读者未免感到有些管中窥豹的疑惑,但若等作者更完此专栏再行观看,从头到尾进行阅读,应该就会有有茅塞顿开的收获与领悟。
我们在之前讨论的AXI协议,无论是握手还是突发传输,其实默认的都是主设备(如CPU Core)和从设备直接相连。但是实际情况中,主设备还需要通过分层存储的形式连接在Cache上,那么通过AXI协议读取的数据是应该在Cache上还是应该在外存上呢? 通过AXI 写回的数据是应该写回在Cache上还是应该在外存上呢? 这个问题的相关讨论就是我们今天这篇文章Transaction Attributes所涉及到的内容,此为前提。
Cache是一种存储介质,一般介于CPU和主存之间。无论是受限于距离,亦或是类型,主存都是在牺牲了读取速度的情况下换取了更多的存储容量,假如CPU计算所需的每一笔数据都来自于主存,那么每一次load和store,CPU需要等待很久,性能也很差。
在设计CPU的过程中,我们发现,通常一次程序的运算,只需要使用主存中的一部分数据,因此我们考虑,是否可以将需要使用的这部分数据拿出来,放到虽然容量小,但是离CPU更近,读取速度更快的内存中,Cache应运而生。
Cache,介于CPU和主存之间,为了提升效率,一般也分为多层(L1 Cache,L2 Cache,L3 Cache),最终再和主存相连接,示意图如下所示。一般情况下现代CPU Core的Cache都是多核的,因此一种普遍的说法是L1 Cache和L2 Cache只服务于一个CPU核而L3 Cache是服务于多个核的。
而我们本篇博客中讨论的Cache行为实际上是System Level Cache,肤浅条件下可以当作L3 Cache(当然不同的SOC设置的Cache级数不同,不一定都是L3 Cache作为System Level Cache,但是系统级Cache的概念要和L1 Cache这二者的概念区别是很大的)
Cache本身很复杂,涉及到很多定义和算法,但是我们摘取和AXI Transcation Attributes核心相关的内容进行知识的回顾,其中最核心的AXI协议相关的,即为Cache的读写行为。读者为了真正理解Transcation Attribute,需要重点理解下面的这些名词解释。
若hit:
说明可以在Cache中找到对应地址的数据,直接从Cache中读取数据即可
若miss,处理方式区分如下:
①Read through: 从slave中读取数据,但是不缓存到Cache处。
②Read allocate:从slave中读取数据,同时缓存到Cache处。
1.为什么我们要区分read through和read allocate呢?
这是因为通常情况下,我们认为Cache上的数据,对于整个SOC性能的提升是有价值的,使用的次数越多,Cache上数据的价值越大,对有价值的数据的替换,会降低Cache的效率,因此,假如我们从一个Slave(如UART)读取到一个只使用一次的数据,那么我们更希望采用read through的方式来进行,而假如从主存中读取到一个反复调用的地址对应的数据,那么我们希望使用Read allocate的方法来将其保存到Cache中。
Cache的写行为与读行为相似:
若hit:
说明可以在Cache 找到对应地址 以及这个地址所对应的数据,那么出现了两种情况的区分
①Write through: 把数据同时写到Cache和内存中
②Write back:先把数据写到Cache中,再通过flush方式写到内存中
若miss:
①Write allocate: 先把要写的地址和其所对应的地址块载入到Cache中,再write through or write back.
②No write allocate:直接把要写的数据写入到内存中而不经过Cache.
2.什么叫flush呢?
flush是Cache的一种指令,目的是在保留Cache中数据的前提下,将Cache的数据写回到主存中,这里的先Cache后flush相当于把一步操作拆成了两步操作(flush这种操作可能会造成在某些特定情况下,同一个地址Cache中的数据和外存中的数据不一致)。
3.为什么要区分write allocate和No write allocate呢?
与问题1的原因相似,即希望在写行为中,高价值地址数据和低价值地址数据的行为是否进入Cache进行合理的区分。
受限于篇幅所限,这里作者就不再讲解AXI3进化到AXI4的种种提升了,仅参考AXI4.0协议来给出所需信号和所需要求,即AxCACHE信号对应的位宽和含义,AxCACHE包括ARCACHE和AWCACHE两个信号,分别针对读行为和写行为,同时这两个信号存在于读控制通路和写控制通路中。
AxCACHE | 值 | 含义 |
---|---|---|
AxCACHE[0] | 0 | Non-bufferable |
1 | bufferable | |
AxCACHE[1] | 0 | Non-modifiable |
1 | modifiable | |
AxCACHE[2] | 0 | No allocate |
1 | Read-allocate | |
AxCACHE[3] | 0 | No allocate |
1 | Write-allocate |
初学的读者对于这些信号的含义往往倍感迷惑,而官方协议中的解释又稍显简单,作者解释如下2.4节所示。
buffer:缓冲
这个信号是用来衡量AXI传输过程中过的缓冲能力的,为什么需要缓冲?
就跟我们上篇文章中讨论的Outstanding传输机制有关,先传输的命令会进行等待,依次吐出数据,以此来提升传输效率,因此这个信号置0,即无缓冲能力,置1,信号在到达其最终地址前可以缓冲数个周期(无论是读还是写都是如此)
具体的bufferable结构如何实现也存在很多的方法,一个最简单的肯定是在同时钟域的条件下,用类似同步FIFO的结构,来缓冲载入的命令,再依次读出,即可参考文章如下。
【数字IC手撕代码】Verilog同步FIFO|题目|原理|设计|仿真
AXI3.0中AxCACHE[1]信号被称作Cacheable,而非modifiable,我们这里还是取用AXI4.0协议来做讲解,采用modifiable的概念
modify:改变
这个信号是用来衡量在传输过程中相关的地址与控制信号是否会发生改变,假如AxCACHE[1]置1,信号可以发生改变,置0,信号不能发生改变,具体是哪些信号不能发生改变呢?
Parameter | Signal |
---|---|
Tranfer address | AxADDR |
Burst Size | AxSIZE |
Burst length | AxLEN |
Burst type | AxBURST |
Lock type | AxLOCK |
Protection type | AxPORT |
【AXI】解读AXI协议中的burst突发传输机制中,核心的起始地址,传输大小,传输长度,传输种类都进行过解读,这些是决定传输形式的最重要的四个信号,而不核心的Lock Type,Protection Type属于进阶内容,以后再讨论,这里也可以暂时一放,但是读者需要注意的是,就算是能改变,改变的也是前四个信号,后两个“Lock type和Protection type”在AxCACHE[1]为1的时候同样不能改变。
请读者思考,为什么在传输过程中我们需要设定控制信号可变或者控制信号不可变呢? 列举以下这些例子,以供参考
2.多个传输合并成了一个传输
先后发送了两个burst读传输操作,前一个是针对于32bit数据通路的低16位进行,后一个是针对于32bit数据通路的高16bit进行,其他的burst参数一致,假如我们设定了modifiable后,在outstanding传输机制中,我们更倾向于将其合并,将两个读操作进行合并为一个来进行,当然这里面涉及到很多的考量,比如说地址之间依赖关系,或者是什么情况下可以进行合并、等等等。但若是这些事情都被解决了,传输的合并势必会提高整个AXI协议的效率。
3.读传输多抓了一些数据存在Cache里
之前就一直在讨论Cache中应该放什么样的数据,什么样的数据价值更高,一个普遍的观点是,假如我们抓取了0x0000_1000这个地址的数据,那这个地址周边的数据如0x0000_0111这种的离得很近的地址最终被读取的可能型一定比地址离它远的高,因此我们可不可以把它周边的数据也同样放到离CPU更近的位置来提高效率?modifiable给出了这种操作的可能性。
这个信号在AXI3中直接当作read-allocate的判断,但是在AXI4.0中被分配了新的意义,具体如下
对于读transaction而言
AxCACHE[2]置1,说明需要去cache中检查读地址是否之前被分配
AxCACHE[2]置0,说明不需要去cache中检查读地址是否被分配
对于写transaction而言
AxCACHE[2]置1,同样需要去cache中检查写地址是否被分配(虽然是写transaction,但是之前的读行为或其他主设备transaction行为同样可能使cache中存在这个地址)
AxCACHE[2]置0,说明不需要去cache中检查地址是否之前被分配
与AxCACHE[2]极其相似
对于写transaction而言
AxCACHE[3]置1,说明需要去cache中检查写地址是否之前被分配
AxCACHE[3]置0,说明不需要去cache中检查写地址是否被分配
对于读transaction而言
AxCACHE[3]置1,同样需要去cache中检查写地址是否被分配(虽然是读transaction,但是之前的写行为或其他主设备transaction行为可能使cache中存在这个地址)
AxCACHE[3]置0,说明不需要去cache中检查地址是否之前被分配
具体是否应该去cache中检查是否allocate,由两个信号决定,即AxCACHE[2]和AxCACHE[3]共同决定。检查分配这个行为,只有 AxCACHE[3:2] = 2’b00 时才不需要检查cache中的数据,不过AxCACHE[3]和AxCAHE[2]置1,所代表的为什么cache中可能被分配的原因是不一样的,即“是读或写操作产生了分配,是自身还是其他主设备的操作产生了分配。”的区分
除此以外,AxCACHE[1]决定信号可否改变,AxCACHE[0]决定信号可否缓冲。
为什么我们要用memory做例子解读?
因为对于内存来说,需要支持尽可能多的transaction种类,而对于很多其他从设备而言,只需要支持一部分transaction特性即可,使用meemory做例子,覆盖最广。
ARCACHE[3:0] | AWCACHE[3:0] | Memory type |
---|---|---|
0000 | 0000 | Non-bufferable device |
0001 | 0001 | Bufferable device |
0010 | 0010 | Normal Non-cacheable Non-bufferable |
0011 | 0011 | Normal Non-cacheable Bufferable |
1010 | 0110 | Write-through No-allocate |
1110(0110) | 0110 | Write-through Read-allocate |
1010 | 1110(1010) | Write-through Write-allocate |
1110 | 1110 | Write-through Read and Write-allocate |
1011 | 0111 | Write-back No-allocate |
1111(0111) | 0111 | Write-back Write-allocate |
1011 | 1111(1011) | Write-back Read-allocate |
1111 | 1111 | Write-back Read and Write-allocate |
1.bufferable指能否缓冲
2.cacheable指是否需要去cache查找是否allocate
3.所谓的No-allocate并不是说读写transaction的分配被禁止
这些概念在协议中都有具体的规定,有需要的直接进行查阅即可,这些memory type的名字更多的是一种种类的划分,而非实际的含义
比如说“Write-through No-allocate”的含义是这样子
受限于作者的知识水平,本篇博客中其实还存在很多问题未进行清晰的表达与讨论,包括但不限于以下“如modifiable中的“写传输激活了更大范围的地址空间”与窄传输机制和WSTRB之间的交互关系”、“AxLOCK、AxPORT,Transaction ID与QoS这些额外信号的意义”、“AxCACHE的信号置高置低与through和back的关系”、“不同类型从设备Transaction Attribute的考量”以及“基础但重要的Cache结构、它是如何进行判断hit和miss的、写操作下Cache的一致性原理”
但作者相信,面面俱到往往一到不到,重心更为偏向于解释Transaction Attribute和Cache之间的交互关系,和各个信号的含义等更为基础的内容,带领大家领会AXI协议中Transaction Attribute的思想有提高需求的读者可以阅读协议原文或寻找更多的资料进行学习。
同样的,假如文章内容存在瑕疵,或读者愿意普及更多内容,若能评论区指出或补充,不胜感激。