AXI协议有可能是我们平时电路设计时经常遇到的一个协议,也是一个不错的协议,本小节我们就来熟悉一下。
乍一看,AXI协议的信号众多,眼花缭乱,容易发蒙。但其实其基本思想很简单。只要掌握以下几点:
通道分离,很容易理解,指的是AXI将不仅将读写操作分开了,而且将读写操作用到的信息分成了不同的通道。
写操作分成了3个通道,读操作分成了2个操作。
说到这里,有一点需要提醒一下:从哲学角度来说,读操作和写操作是对称的,是对等的,一写一读,一去一回,没有读哪来写,没有写何必读,就好像太极图里面的一黑一白,多么的和谐与对称。为什么AXI协议里写操作有3个通道,读操作却只有2个通道,这是明目张胆的对读操作的歧视啊!这是为什么呢?
我认为可以从两个角度来解释?
首先,可能和一致性相关。resp通道的一个重要的责任就是解除一致性(WAW,WAR,RAW),试想如果把读操作的rdata和rresp拆分成两个独立的通道,又有什么奇葩的情景下中会出现后面的写操作还没有得到读操作返回的数据就可以解除一致性呢?!!也就是说即使应读操作的强烈要求,将其也分成3个独立的通道,也不会出现rdata和rresp异步的情况,既然拆分出来的两个通道都是同步的,那么就没有必要拆分了。
其次,可能和哲学有关,读写看似平等,实际上却存在不平等,比如有WAW依赖,却没有RAR依赖,世界本来就是不平等的,你读操作何必一定要和写操作处处平等,把你和写操作拆分开就已经是高看你了,实在不爽的话,就把读写放到一起,就变成了低速,高耦合度,串行,不能乱序的协议啦,比如AHB,APB或者wishbone。
合则串行,分则并行,是合是分,合则慢,分则快,自己选吧。
当然也不是说‘合’就一无是处,‘合’会大大降低信号的数量。
是追求传输速度还是追求信号线数目少,自己选吧。
从大的角度来看,对于读/写,大多数情况下可以认为是并行的,但是读写的地址存在冲突时,读/写又存在依赖。
从小的角度来看,单纯对于写操作,AW、W、B三个通道从形式上看是独立的并行的,但是三个通道在逻辑上又存在一定的依赖。
比如,master的W通道的写数据允不允许比AW通道的写地址先发到总线呢?允许可以,不允许也可以。
如果不允许,W通道的ID信号是否可以去掉不用呢?答案是可以去掉,只要保证去掉之后WDATA发到总线上的顺序和AW通道的数据发到总线上的顺序一致就可以,而这个保证很容易做到。
slave的B通道上的valid允不允许比AW和W通道上的valid早发出去呢?
很显然是不允许的,试想,slave还没有接收完数据就告诉master你接收完了,不出错才怪。
当然,什么事情都不是绝对的,如果在AXI标准协议的基础上再对master和slave增加一些约束,slave的Bvalid也是允许比AW和W两个通道的valid早发出去的,这就是ARM A9中使用的对于写操作的奇葩的‘Early resp’。
但是有时候,单核的系统中对barrier传输的需求也是很迫切的(比如CPU需要和DMA设备同步时),但是有不想全面支持ACE协议,怎么办呢?
可以单纯引入ACE中的‘bar’信号。
对于barrier。需要搞清楚两点即可:
第一,什么是barrier之前的操作,什么是barrier之后的操作;
barrier之前的操作是指,各自通道(AW/AR)上的barrier信号有效之前所发出的操作。barrier之后的操作是指,各自通道上resp(bresp/rresp)返回之后所发出的操作。这里需要注意的是,有一些操作是在‘barrier信号发出之后,resp信号回来之前’发出去的,他们既不属于barrier之前的操作,也不属于barrier之后的操作,属于什么呢?他们就是所谓的‘outstanding 操作’。
第二,memory barrier和synchronization barrier的区别是什么。
关于memory barrier和synchronization barrier,我的理解是:
memory barrier的要求是,以barrier这条命令的resp信号回来为界线(不考虑outstanding的话),保证它后面发的命令能看到前面发的命令的结果即可。
system barrier的要求是,以barrier这条命令的resp信号回来为界线,要保证它前面发出的命令要彻底完成,即不能只被cache下来。
举两个可能的使用场景:
memory barrier:
一个物理CPU(可以多个逻辑核)上面跑了两个线程,其中一个线程(A)更新一个数组,更新完成之后需要告诉另外一个线程(B),线程B读更新之后数组的值。
对于这种场景,线程A更新完数组之后,发一个memory barrier命令就能保证线程B能读到更新后的值。
system barrier:
一个物理CPU(可以多个逻辑核)上面在跑一个线程,这个线程更新一个数组,更新完之后需要告诉VPU,VPU读更新之后的数组的值。
对于这种场景,CPU上面的那个线程更新完数组之后,发一个system barrier命令才能保证VPU读到更新后的值。如果只发一个memory barrier命令,最新的值可能被缓存在L1/L2 cache中,导致VPU读到旧值。
毕竟,协议是ARM定的,但电路是你自己设计的。鞋子合不合脚,只有你知道的最清楚。