本篇文章给大家讲解AHB2SRAM的设计,它的作用是通过AHB接口去访问SRAM,实际上我们可以将它理解成AHB协议到SRAM的native接口的协议转换,和之前讲的apb2reg是差不多的。
设计一个东西之前首先思考一下,为什么需要这个东西?我们直接用SRAM不可以吗?还真的不可以,因为前面讲过,我们设计的SoC主体其实是基于AHB和APB总线。普通的SRAM无法直接连在AHB总线上,必须通过ahb2sram这个模块完成协议转换,才可以连接SRAM。其实SoC设计很多时候都是做这种工作,给IP做一个AHB/AXI/APB的wrapper,然后像连连看一样,把对应的信号连接即可。
1、AHB2SRAM模块简介
我们要设计的模块为紧耦合SRAM(Tightly coupled memory),不考虑等待延迟,默认一拍回数据,不考虑出错。且只支持32bit位宽的SRAM。支持按字节写,该模块除了用在IC设计中,也可以用在FPGA设计中,因为一般FPGA提供的MIG,只支持AXI或者native接口,这种情况下还是需要AHB2SRAM这个模块完成协议转换的。
我们看一下接口,非常直观,一边是AHB接口,一边是SRAM的native接口。该模块实际就是完成协议的转换。
我们要设计的AHB2SRAM的规格说明如下:
- 完成AHB接口与SRAM接口的时序转换;
- 支持字节、半字、字的读写操作;
- 支持先写后读(不支持同时读写,单端口SRAM),并且读写地址相同的时候,读数据的拼接以及更新(比如对addr1进行写操作,写数据data1,下一拍马上就需要读,这个时候需要靠一个buffer,把上一拍写的数据data1作为这一拍读回的数据,因为data1有可能还没有写进SRAM);
2、AHB读写时序和SRAM读写时序
我们分别看一下两端的读写时序,这样就清楚怎么做相应的协议转换了(做任何协议的转换都是如此,要弄清楚两边各自的协议)。
2.1、AHB读时序
- 第一拍给出读写控制以及地址信号;(CTRL包括了HTRANS和其它的一些控制信号,这里简化了,大家理解意思就行)
- 当没有HREADY反压的时候,第二拍得到读数据信号;
- 当有HREADY反压的时候,第2拍以后的某一拍HREADY为高的时候,说明此时HRDATA有效,得到读数据信号;
2.2、AHB写时序
- 第一拍给出读写控制以及地址信号;
- 第二拍给出写数据信号;
2.3、SRAM读时序
- SRAMCS是片选信号,chip select,只有为高的时候SRAM才会真正的工作;
- 第一拍给出读写控制,地址信号;
- 第二拍给出读数据信号;(不考虑反压和错误的情况下,和AHB一模一样)
2.4、SRAM写时序
- 第一拍同时给出写控制,地址,数据信号;(和AHB不一样!)
2.5、AHB2SRAM的时序转换
可以分为以下的情况:
- 全部是读的情况:这种情况非常简单,因为本身二者就相同,第一拍给地址和控制信号,第二拍回数据,大家看下波形肯定可以理解,就不详细讲了。
- 全部是写的情况:由于SRAM的写操作命令地址和数据是在一拍完成的,而AHB写数据要在第二拍才能写入,所以需要将AHB中的地址延迟一拍再与SRAM中的地址进行同步,这样地址数据就满足了sram接口要求
- 先读后写:先读后写实际上不涉及数据更新,就是写跟在读后面而已,下面时序稍微有点问题,对于读这一部分ADDR和DATA。AHB和SRAM两侧应该是同一拍的。
- 先写后读:相对来说最复杂的一种情况,当读写地址相同的时候,需要完成读数据的拼接以及更新。
- 首先我们要判断是否是先写后读,我们需要把写的控制信号打一拍。如果这一拍是读,上一拍是写同时满足,则说明出现了先写后读。buf_pend代表buffer write data valid。为什么需要这个呢?可能是先写后读再读,这个时候可能也还没有写进去,但是我们也认为是先写后读。当然也包括先写后读后读读读。。。过会看代就一目了然了
- 然后我们判断是否读写地址相同,这个就相对简单了。上一拍的地址和这一拍的地址相同,则代表读写地址相同。
- 接下来我们就可以对读数据更新,可以看到如果buf_hit并且buf_we有效,则代表上一拍确实写了这个字节,这种情况下我们就应该把这一字节替换成上一拍写的值,而不是用SRAM读出来的值。
接下来我们看先写后读的时序情况,可以看到addr2这个地址是先写后读。可以看到buf_data_en和~HWRITE都为1,则代表出现了先写后读。可以将buf_data_en拉高。并且地址也相同,因此buf_hit也会拉高。这个时候HRDATA返回的数据就应该是完成替换以后的值。这个时序图应该还是比较清晰的,大家好好理解一下。
需要注意的是,addr2写操作不会在下一拍就写进去SRAM,因为出现了先写后读,这个写SRAM的操作会被暂时的pending住!当addr3来的时候,这个时候addr对应的data2才能够写进去。大家看后面的代码就知道了。
3、AHB2SRAM代码
完整代码链接在下:
首先我们看控制信号:我直接在代码后面注释,大家结合着看即可。
wire ahb_access = HTRANS[1] & HSEL & HREADY; //HTRANS为1表示连续或者非连续传输,HSEL拉高代表发起传输 wire ahb_write = ahb_access & HWRITE; //写控制信号 wire ahb_read = ahb_access & (~HWRITE); //读控制信号 // Stored write data in pending state if new transfer is read // buf_data_en indicate new write (data phase) // ahb_read indicate new read (address phase) // buf_pend is registered version of buf_pend_nxt //发生了先写后读的情况,注意buf_pend或buf_data_en //意味着如果是先写后读再读,也会拉高buf_pend_nxt,当然再读也是一样的会拉高。 wire buf_pend_nxt = (buf_pend | buf_data_en) & ahb_read; // RAM write happens when // - write pending (buf_pend), or // - new AHB write seen (buf_data_en) at data phase, // - and not reading (address phase) //注意,buf_data_en是ahb_write打了一拍,按照之前的逻辑设计。我们写操作控制信号本身就需要打一拍,并且新来的一拍不能是读 //如果新来的一拍是读,上一拍是写,那么就是先写后读,这个时候会一直不写,直到新来的也是写,这个才能真正的写进去SRAM //所以也叫buf_pend,意思为暂停,因此是buf_pend和buf_data_en相或。 //为什么要这么设计呢?我也不知道,我觉得有可能是写的这个数据本身就已经存好了,暂时代替了SRAM的作用,因此没必要着急直接写 //进去SRAM,可以节省一点逻辑资源。大家对其进行修改当然也是可以的。 wire ram_write = (buf_pend | buf_data_en) & (~ahb_read); // ahb_write // RAM WE is the buffered WE assign SRAMWEN = {4{ram_write}} & buf_we[3:0]; // RAM address is the buffered address for RAM write otherwise HADDR assign SRAMADDR = ahb_read ? HADDR[AW-1:2] : buf_addr; // RAM chip select during read or write assign SRAMCS = ahb_read | ram_write;
然后我们看一下读回数据的情况,这个实际上和上面的电路图保持一致。当buffer_hit即读写地址相同,并且上一拍真的写进去了。就用写的数据代替从SRAM回来的数据作为读回数据即可。
wire [ 3:0] merge1 = {4{buf_hit}} & buf_we; // data phase, buf_we indicates data is valid assign HRDATA = { merge1[3] ? buf_data[31:24] : SRAMRDATA[31:24], merge1[2] ? buf_data[23:16] : SRAMRDATA[23:16], merge1[1] ? buf_data[15: 8] : SRAMRDATA[15: 8], merge1[0] ? buf_data[ 7: 0] : SRAMRDATA[ 7: 0] };
剩下的代码比较简单,大家直接去看源码即可,有不懂的可以问我。
欢迎和我一起学习AMBA总线,完整的专栏在这里: