有了前面的一堆铺垫。现在终于开始正式准备读写DDR了,开发环境:VIVADO2014.2 + SDK。
一、首先要想在PL端通过AXI去控制DDR,我们必须要有一个AXI master,由于是测试,就不自己写了,直接用package IP生成,方法如下:
1.选择package IP工具
2.创建新的AXI外设
3.接口类型选择Full,模式选择master,如果你不关心里面的详细实现过程,那么直接finish就好了。(后面我们会继续分析里面的过程)
二、创建好了IP,自然要加入到IP库里,如图,在IP Catalog空白处右键,设置,把刚刚生成IP的路径放进去:
三、接下来创建BD块,把整个硬件系统搭建好:
需要指出的是,由于我们需要用到HP,所以在zynq的配置里面把HP勾选上,任选一个通道就行
四、然后校验正确性,产生输出文件,创建BD块顶层,这都是套路,一路走下来就行。如果你想在调试里看到产生的AXI信号,那么需要对AXI标记一下debug
五、综合,set up debug,然后生成比特流,并将其导入到SDK;在SDK里跑个hello world 就行,主要目的是用CPU去把DDR控制器初始化。
到这里整个过程基本就结束了,接下来看仿真波形:
放大一点,可以看到每次地址的步进长度是十进制的64,这是因为我们的突发长度设置的是16,位宽为32bit。
但是问题来了,我们在上一节里面说过,有一部分地址是连到了OCM的,那么这一部分地址是多少呢?UG585里给出了如下说明:
我们是从全0地址开始写数据的,然而全0的地址刚好分配到了OCM,这TM就很尴尬了。一开始想让程序运行的时间长一点,这样地址是不是就可以跑到0x0008_0000了?然而并没有什么用,因为地址只跑到00001000就停止了,如图:
还记得前面打包AXI IP时候我们说过要分析其过程吗?其实那里就已经挖了一个坑了,具体见代码:
这是AXI的写数据状态机,(可以看到,官方也是用一段式状态机来实现整个时序的,印证了前面三段式状态机不好实现的说法),从写状态到读状态的跳变是由writes_done信号来控制的,那么这个writes_done又是怎么产生的呢?继续看代码:
writes_done是由write_burst_counter的高位进位来控制的,再继续找write_burst_counter:
在这个计数器里有一个很关键的位C_NO_BURSTS_REQ ,在代码的低179行,它的定义如下:
localparam integer C_NO_BURSTS_REQ = C_MASTER_LENGTH-clogb2((C_M_AXI_BURST_LEN*C_M_AXI_DATA_WIDTH/8)-1);
C_M_AXI_BURST_LEN我们设置的是16,C_M_AXI_DATA_WIDTH是32,clogb2可以理解为计算以2为底的某个数的对数,那么最后得到的C_NO_BURSTS_REQ = 6;也就是说write_burst_counter的位宽是7为,当最高位为1时,写数据停止。也就是只会发生64次写数据,之后计数器和写地址就会归零。那么64次写数据乘以每次突发长度16再乘以位宽4个字节,最后得到的数值是1024,换算成16进制刚好是0x00001000。 所以要想真正的往DDR里面写数据,我们还需要对代码进行修改。 到这里离成功就已经不远了