前段时间粗略过了一下riscv指令集相关内容,并对开源项目tinyriscv进行了一些学习,主要是对cpu指令集架构有个感性认识。最近在学习soc中amba总线协议相关内容,本着不能只输入不输出的原则,打算通过公众号做一下学习记录。
目前是打算先学习,然后独立写一个小项目:
master1(假装它是个CPU)控制DMA实现两个SRAM的数据搬运,所以需要两个master(CPU、DMAC)和三个slave(SRAMC1、SRAMC2、DMAC),slave地址范围我是这样定义的:
DMAC(DMA Controller)既做master又是一个slave。这篇文章先记录目前的学习进度:ahb_bus(我把它叫做ahb_connect)的rtl级实现。其中所有代码都会开源在个人gitee账户上。
(1)基于AMBA总线的架构图
(2)基本特性
①支持Burst传输;
②支持Split事务处理;
③无三态(ASB有三态);
④单一时钟沿操作(上升沿,ASB支持双边沿);
⑤更宽的数据总线配置(64/128 bits);
⑥流水线操作(第一个时钟传地址,第二个时钟传数据,同时传下一次地址);
⑦支持多个总线主设备(最多16个,master越多IO资源用的越多)。
(3)AHB组成
AHB总线支持多个主设备、多个从设备同时挂载在总线上,通过寻址方式。从我理解上来说我把一个完整的ahb系统分成ahb_master、ahb_connect、ahb_slave。画了个图
看着挺复杂但是master和slave的接口是一样的。ahb_connect就是ahb_bus了,连接主、从设备之间的数据交换。
(4)AHB信号
各个信号是干什么的,从下一节理解起来比较轻松。
AHB信号从模块角度更好理解,HCLK全局时钟信号,AHB规定上升沿进行操作,HRESETn表示低电平有效,n=negedge。这需要将ahb_connect模块进一步划分:
HBUSREQx1是master1申请总线信号,HGRANTx1为高表示总线权限授权给master1,HMASTER是需要split事务的时候传给具有split功能的slave的,但是我没有用到这样的slave,所以这个信号其实是不需要的。但是我还是写了,我用它是传给mux判断哪个主设备被授权了。
实现核心代码 & rtl模块图如下
对主设备的授权是比较简单的,有一个申请就授权,两个master的申请信号都为高就先判断是同时申请还是本来有个申请到了,另一个过来抢占,如果是抢占,我用了一种简单的方法,等待(但是ahb总线是支持总线抢占的),如果是同时申请,授权给master1(我令CPU优先级高)。
关于arbiter涉及到的信号还有以下:
HLOCKx:master发出此信号表示不想被抢占总线,我设计时默认让其他申请的master等待,因此没有用到此信号;
HMASTERLOK & HSPLIT:需要slave具有分块能力,没用到。
b.ahb_decoder
decoder译码模块的功能简单,arbiter授权某个master后,mux多路选择模块将master输入的HADDRx1、HADDRx2等信号对应复制给HADDR,之后译码器它是根据主机想要建立从机的通信地址,来给对应的slave选通信号HSEL。
实现核心代码 & rtl模块图如下
c.ahb_mux
mux我把它分为四个部分,有一个是地址mux,一个是控制mux,一个是写数据mux,一个是读数据mux。
上述AHB特性饿流水线想必各位还有印象,说的是第一个周期写地址,第二个周期写数据,同时写下一个地址。当一个master掌管总线后,它首先要干的事就是发送一堆东西:首先地址HADDRx1(表示master1发送的HADDR),表示我想找地址为HADDRx1的slave通信;假定是slave1吧;
然后是一些控制信号:传输类型HTRANSx1、传输方向HWRITEx1(1表示master向slave写数据,0表示从slave读数据)、每一个传输的数据大小HSIZEx1、以及burst类型HBURSTx1;
然后slave当前能传输数据的时候把它的HREADYx1(表示slave1的HREADY)信号拉高;
然后master接收到HREADY信号知道了slave1能通信,然后发送数据HWDATAx1;或者接收数据HRDATA;
最后传输完之后slave发送HRESPx1响应信号。
那么问题来了,虽然master1和slave1在通信,master2和slave2比较调皮,你master1发HADDR,叫HADDRx1,那我master2我也要发,我发个HADDRx2吧,这时候ahb_connect应该选哪个呢?这就是mux的作用了。
mux从arbiter知道了HGRANTx1为高,说明master1被授权,那我多路选择器你俩HADDRx1、HADDRx2都输入进来,那我就把HADDRx1赋值给HADDR。其他同理。
总结如下
地址信号mux:把master的地址信号HADDRx1选择正确的送到slave;
控制信号mux:把master的控制信号HTRANSx1、HWRITEx1、HSIZEx1以及HBURSTx1选择正确的送到slave;
写数据mux:把master的写数据值HWRITEx1选择正确的送到slave;
读数据mux:把slave读出的数据值HRDATAx1选择正确的送到master。
实现核心代码 & rtl模块图如下
对传输类型HTRANS、每次传输位宽HSIZE以及burst类型HBURST信号补充如下
至此,AHB的信号就清晰明了了,附一张ahb_connect顶层模块综合rtl图。
有些代码设计的并不规范,比如说slave地址我直接在decoder模块里定义的,是不是设计成parameter传递参数由顶层模块定义更好?Whatever,本意就是cpu控制dma来搬运两个sram数据,所以没考虑后面的可拓展性,下次注意。
本次学习没有涉及具体的几种传输方法的读写时序,也还没有对ahb_connect进行testbench仿真,接下来就是结合传输时序对几种传输方法进行学习,然后完成对ahb_connect的仿真,没问题之后就继续后续SRAMC和DMAC的编写。如果后续仿真发现已经写的模块存在问题的话会特别说明。
如果有可能的话后期会拿这个修改来做我基于riscv指令集架构的soc的总线(但是AMBA总线好吃资源啊啊啊啊啊啊啊啊啊啊啊!)走一步看一步吧。