Xilinx MicroBlaze软核驱动DDR3
说明:通过Vivado生成MicroBlaze工程导入SDK实现DDR3的读写。
环境:Vivado2018.3。
IP核:MicroBlaze。
参考手册:
pg142: AXI UART Lite v2.0
ug586:7Series_MIS
工程:DDR3
文章目录
1.DDR简介
MT41K256M16xx-125:
内存大小为512MB,数据接口为16bit。
为什么是512MB:256M空间,数据线为16bit,1Byte=8bit,256M×16bit=512M×8bit=512MB。
下图为型号参数的具体解析:
1.1DDR3地址
DDR3的内部是一个存储阵列,将数据“填”进去,你可以它想象成一张表格。和表格的检索原理一样,先指定一个行(Row),再指定一个列(Column),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,那么这个表格(存储阵列)就是逻辑 Bank(Logical Bank,下面简称Bank)。 DDR3内部Bank示意图,这是一个NXN的阵列,B代表Bank地址编号,C代表列地址编号,R代表行地址编号。
以下列举了不同容量的DDR3地址:
注意以上单位为GBit。比如MT41K256M16TW-107容量为512MB=512×8Mbit=4096Mbit=4Gbit,就要看表2.11.4 4Gb中的256M×16。
2. MicroBlaze简介
MicroBlaze嵌入式软核是一个被Xilinx公司优化过的可以嵌入在FPGA中的RISC处理器软核,具有运行速度快、占用资源少、可配置强等优点,和其他外设IP核一起,可以完成可编程系统芯片(SOPC)的设计。
FPGA是可编程的硬件逻辑电路,MicroBlaze是一种处理器电路,使用MicroBlaze就相当于在FPGA内部做了一个CPU在里面,可以用C语言编写程序,在这个CPU上跑C语言的软件程序,FPGA偏向逻辑,做控制比较麻烦,CPU做控制比较方便。
3.MicroBlaze设计流程
新建Vivado工程…
3.2 DDR Block Design 流程
添加MicroBlaze IP:
Run Block Automation:
添加串口IP:
添加MIG IP(DDR控制IP):
MIG介绍:详细请看FPGA_MIG驱动DDR3
如下图所示:FPGA用户逻辑↔MIG↔DDR3的端口连接示意图(可以理解MIG作为中间介质让我们间接的控制了DDR,简化了控制接口):
配置MIG IP:双击mig_7series
Next:
根据自己板子型号选择:
DDR类型:
时钟配置说明:
1.Clock Period
即MIG对DDR接口的速率为800M*2=1600M(双沿)。
MIG输出到app接口上的时钟ui_clk为800M/4=200M。
UI时钟频率ui_clk为DDR时钟频率的1/4,也就是一个用户时钟周期(两个边沿)对应8个DDR时钟边沿。burst length可以理解为MIG连续操作DDR地址的个数,故在4:1时钟比例下,一个用户时钟周期正好对8个地址进行了读/写操作,256bit数据分8次(32bit)写入DDR中,即逻辑用户以时钟ui_clk写256bit数据至MIG,MIG控制器将以时钟ddr_clk向DDR写8次(32bit(数据位宽配置为32bit))数据。由此分析,在写数据时让app_wdf_end = app_wdf_wren即可,并且读/写操作时地址递增步长为8。
2.Input Clock Period
输入给MIG的时钟为200M。
根据自己的DDR以及FPGAA性能选择工作时钟、位宽、型号:
①:工作时钟为什么选择400MHz
我所选FPGA部件和FPGA速度等级据定了允许该时钟的周期范围(1875-3300ps)
所以支持的频率为303.03-533.33MHz,在中间选择了400Mhz。即MIG对DDR接口的数据速率为400M×2=800M(双沿)。
②:UI时钟频率ui_clk为DDR时钟频率的1/4。
③:根据自己DDR型号
④数据位宽:用了一片MK41K256M16,数据位宽为16bit。
输入给MIG的时钟:
选择所需的时钟配置:
内部终端阻抗:
管脚分配:输入或者直接导入XDC、UCK文件
…一直到Generate
执行Run Connection Automation
全貌:
対生成的网络做以下修改:
1、时钟
根据自己的板子,选择输入时钟大小和时钟方式,单端还是差分
PLL分别输出100MHz和200MHz的时钟、去除reset和locked信号:
删除多余端口、并将clk_in1引出:
2、复位
去除复位引脚,引入常量IP constant
因为是低电平复位,常量IP设置值为1
3、串口波特率
双击axi_uartlite,更改波特率:
4、配置DDR时钟
sys_clk、clk_ref说明如下,clk_ref输入时钟设置为200MHz
PLL clk_out2输出为200MHz连接至migG clk_ref
5、引出DDR3端口
进行设计验证
观察地址映射:
全貌:
Generate Block Design:
进行管脚约束:
DDR在IP里已经约束完成,在这里也可以看到DDR的约束
生成bit流
Export Hardware:要包含.bit文件
Lanuch SDK:
4.SDK工程
新建一个hello word 工程
在.hdf文件中查看DDR3的地址范围位为0x8000_0000-0x9FFF_FFFF
寻址空间为1FFF_FFFF即536870912Byte=512MB。
将数据0-99写入DDR,并读出0-9、20、50。
介入以下测试代码:
#include <stdio.h>
#include "platform.h"
#include "xparameters.h"
#include "xil_printf.h"
int main()
{
int i;
u32 DDR_BUFF[10];
u32 DDR_Val[3];
init_platform();
u32 *DDR_ADDR;
DDR_ADDR = (u32 *)0x80000000;
for(i=0;i<100;i++)
{
*(DDR_ADDR+i) = i;
}
for(i=0;i<10;i++)
{
DDR_BUFF[i] = *(DDR_ADDR+i);
}
DDR_Val[0] = *(DDR_ADDR+20);
DDR_Val[1] = *(DDR_ADDR+50);
DDR_Val[2] = *(DDR_ADDR+90);
print("Finshed\n\r");
cleanup_platform();
return 0;
}
调试:先进行调试设置
结果:
更改地址:
#include <stdio.h>
#include "platform.h"
#include "xparameters.h"
#include "xil_printf.h"
int main()
{
u32 i;
u32 DDR_BUFF[10];
u32 DDR_Val[3];
init_platform();
u32 *DDR_ADDR;
DDR_ADDR = (u32 *)0x81000000;
for(i=0;i<1000;i++)
{
*(DDR_ADDR+i) = i;
}
for(i=0;i<10;i++)
{
DDR_BUFF[i] = *(DDR_ADDR+i);
}
DDR_Val[0] = *(DDR_ADDR+200);
DDR_Val[1] = *(DDR_ADDR+500);
DDR_Val[2] = *(DDR_ADDR+999);
print("Finshed\n\r");
cleanup_platform();
return 0;
}
注意:将DDR配置为内存时,系统内存区域就会映射到了DDR中,用的时候注意跳开此区域:
5.SDK DDR用指令分别访问 DDR、BRAM速度
DDR3的频率配置的为200MHz、Mircroblaze时钟为100MHz。
以下时间仅仅是通过for循环对DDR和BRAM读写做了个时间对比,不要理解为DDR就这么慢…
利用for循环测试DDR3读写10000个数据:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"
u32 DDR_BUFF[10000];
int main()
{
u32 i;
u32 j;
init_platform();
u32 *DDR_ADDR;
u32 *LED_ADDR;
DDR_ADDR = (u32 *)0x81000000;
LED_ADDR = (u32 *)0x40000000;
*LED_ADDR = 0x03;
*LED_ADDR = 0x00;
for(i=0;i<10000;i++)
{
*(DDR_ADDR+i) = i;
}
for(i=0;i<10000;i++)
{
DDR_BUFF[i] = *(DDR_ADDR+i) ;
}
*LED_ADDR = 0x03;
cleanup_platform();
return 0;
}
所需时间(低电平持续时间):8.1ms
利用for循环测试BRAM读写10000个数据:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"
u32 BRAM_BUFF[10000];
int main()
{
u32 i;
u32 j;
init_platform();
u32 *DDR_ADDR;
u32 *LED_ADDR;
BRAM_ADDR = (u32 *)0x0000FFFF;
LED_ADDR = (u32 *)0x40000000;
*LED_ADDR = 0x03;
*LED_ADDR = 0x00;
for(i=0;i<10000;i++)
{
*(BRAM_ADDR -i) = i;
}
for(i=0;i<10000;i++)
{
BRAM_BUFF[i] = *(BRAM_ADDR -i) ;
}
*LED_ADDR = 0x03;
cleanup_platform();
return 0;
}
所需时间(低电平持续时间):5.4ms
测试20000个空for循环需要的时间:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"
u32 DDR_BUFF[10000];
int main()
{
u32 i;
u32 j;
init_platform();
u32 *DDR_ADDR;
u32 *LED_ADDR;
LED_ADDR = (u32 *)0x40000000;
*LED_ADDR = 0x03;
*LED_ADDR = 0x00;
for(i=0;i<10000;i++)
{
}
for(i=0;i<10000;i++)
{
}
*LED_ADDR = 0x03;
cleanup_platform();
return 0;
}
所需时间(低电平持续时间):2.4ms
★★★如有错误欢迎指导!!!