芯片:zynq7035
1、打开vivado,新建一个工程,工程路径和名字自己定。
2、点击左侧的“IP Catalog”。
3、输入“MIG”,搜索MIG控制器。
4、双击“MIG”控制器,对MIG控制器进行设置。
5、然后会出来一个MIG控制器编辑界面,如下。直接NEXT.
6、选择“Create Design”,然后Next。(Number of Controllers 指的是你要几个控制器,AXI4 Interface指的是MIG是AXI4接口的,其他的选项你不用了解了)
7、 这里问的是你要不要兼容其他芯片,这里不用兼容,直接Next。
8、 选择“DDR3 SDRAM”,然后Next
9、这个界面很重要,有很多东西需要理解。按照如下设置,其他不变,然后Next。
解释:1、这里的400MHz指的是在DDR3这个芯片里面实际跑的时钟频率
2、4:1,指的是 DDR3时钟频率 : MIG控制器给用户的时钟频率 = 4:1。也就是,如果你设置DDR3的工作频率是400MHz,那么MIG控制器会给你一个100MHz的用户时钟。那这个用户时钟拿来跟什么呢?这个用户时钟是用来作为读MIG控制器地址生成的时钟。
3、Components指的是DDR3的型号是元件类,而不是像笔记本那种的插条类。笔记本是SODIMMs。这里一共有四个选项。
4、这是DDR3芯片型号,根据你的DDR3芯片手册来选。
5、电压,根据你的DDR3芯片手册来选。
6、DDR3的物理位宽,这个需要根据你自己的芯片来选择。
10、按如下设置,这个页面的选项也很重要。
1、这里的Input Clock Period 指的是输入到MIG里面的时钟是400MHz
2、这里设置读写为顺序读写,并且burst的长度设置为8。注意界面的小字部分。
3、这个我也不太清楚。
11、这个界面也很重要。按照如下设置,然后Next.
第一个是系统时钟System Clock: No Buffer, 为甚么选No Buffer,我也不太清楚,有知道的朋友可以在评论区下留言。
第二个是参考时钟Reference Clock:No Buffer,为甚么选No Buffer,我也不太清楚,有知道的朋友可以在评论区下留言。
第三个是系统复位极性,是对MIG控制器复位。这里选择低电平复位。
第四个是不需要系统调试信号。所以第五个Sample Data Depth 就不可选了。
第七个IO Power Reduction 是指打开低功耗。
第八个是XADC补偿使能。
12、内部终端电阻设置50欧姆。然后Next.
13、选择DDR3芯片引脚。这些引脚可以在电路原理图上查到,但是这样配置芯片引脚会很慢的。选择第二个,然后Next
选择第二个,直接读取DDR3的引脚,这样就不用进行配置了。
14、选“Read XDC/UCF” 直接读取DDR3的引脚。然后再点“Validate”,验证已经选择的引脚是否正确。只有验证正确了才可以点击Next.
15、剩下的就一直Next,遇到Accept就Accept ,最后Generate。
这样就配置完一个MIG控制器了。
关于DDR3的基本知识在这里我就不详细说了,只有在相关的地方会提上一嘴。本教程的目的只是教会大家如何使用MIG控制器,大家一定不要觉得MIG控制器有多难,其实很简单的,跟着我在心里默念“MIG就像BRAM一样简单”。确实哈,当你回过头来看,MIG控制器的使用基本和BRAM的使用方法很像。
话不多说了,那么这第一个系列,我就先交大家来例化一个MIG控制器。
VIVADO:2016.4
芯片:zynq7035
上一节已经介绍了如何配置一个MIG控制器 Xilinx MIG 控制器使用详解(一)
现在让我们来看看生成的MIG控制器模块。
-
mig_7series_0 u_mig_7series_0 ( -
-
-
// Memory interface ports -
// 下面这些都是DDR3的物理管脚,这些已经约束好了,不需要我们自己约束,不用管。 -
.ddr3_addr(ddr3_addr), // output [14:0] ddr3_addr -
.ddr3_ba(ddr3_ba), // output [2:0] ddr3_ba -
.ddr3_cas_n(ddr3_cas_n), // output ddr3_cas_n -
.ddr3_ck_n(ddr3_ck_n), // output [0:0] ddr3_ck_n -
.ddr3_ck_p(ddr3_ck_p), // output [0:0] ddr3_ck_p -
.ddr3_cke(ddr3_cke), // output [0:0] ddr3_cke -
.ddr3_ras_n(ddr3_ras_n), // output ddr3_ras_n -
.ddr3_reset_n(ddr3_reset_n), // output ddr3_reset_n -
.ddr3_we_n(ddr3_we_n), // output ddr3_we_n -
.ddr3_dq(ddr3_dq), // inout [31:0] ddr3_dq -
.ddr3_dqs_n(ddr3_dqs_n), // inout [3:0] ddr3_dqs_n -
.ddr3_dqs_p(ddr3_dqs_p), // inout [3:0] ddr3_dqs_p -
-
//初始化完成信号,需要关注 -
.init_calib_complete(init_calib_complete), // outp init_calib_complete -
-
//这也是DDR3的一些物理管脚,我们也不需要管 -
.ddr3_cs_n(ddr3_cs_n), // output [0:0] ddr3_cs_n -
.ddr3_dm(ddr3_dm), // output [3:0] ddr3_dm -
.ddr3_odt(ddr3_odt), // output [0:0] ddr3_odt -
-
// Application interface ports -
//下面的app开头的信号才是我们使用MIG控制器需要关注的 -
.app_addr(app_addr), // input [28:0] app_addr -
.app_cmd(app_cmd), // input [2:0] app_cmd -
.app_en(app_en), // input app_en -
.app_wdf_data(app_wdf_data), // input [255:0] app_wdf_data -
.app_wdf_end(app_wdf_end), // input app_wdf_end -
.app_wdf_wren(app_wdf_wren), // input app_wdf_wren -
.app_rd_data(app_rd_data), // output [255:0] app_rd_data -
.app_rd_data_end(app_rd_data_end), // output app_rd_data_end -
.app_rd_data_valid(app_rd_data_valid), // output app_rd_data_valid -
.app_rdy(app_rdy), // output app_rdy -
.app_wdf_rdy(app_wdf_rdy), // output app_wdf_rdy -
-
//下面这三个再例化的时候直接给0就可以 -
.app_sr_req(app_sr_req), // input app_sr_req -
.app_ref_req(app_ref_req), // input app_ref_req -
.app_zq_req(app_zq_req), // input app_zq_req -
-
//下面三个是输出,直接输出到三个wire信号即可 -
.app_sr_active(app_sr_active), // output app_sr_active -
.app_ref_ack(app_ref_ack), // output app_ref_ack -
.app_zq_ack(app_zq_ack), // output app_zq_ack -
-
//ui_clk就是MIG输出给用户的时钟,在上一节中我们说的是100MHz -
.ui_clk(ui_clk), // output ui_clk -
-
//输出的这个复位信号不用管,接到一个wire信号上就可以 -
.ui_clk_sync_rst(ui_clk_sync_rst), // output ui_clk_sync_rst -
-
//这个是掩码信号,直接赋0,不用管 -
.app_wdf_mask(app_wdf_mask), // input [31:0] app_wdf_mask -
-
-
// System Clock Ports -
//系统时钟,上一节说的是400MHz -
.sys_clk_i(sys_clk_i), -
// Reference Clock Ports -
//参考时钟 -
.clk_ref_i(clk_ref_i), -
//系统复位 -
.sys_rst(sys_rst) // input sys_rst -
);
好,到这我们就来看看app打头的这些信号是什么意思。
首先,对DDR3应该有读写两种操作,我们先来看看写操作。
写操作相关信号:
-
app_cmd :操作命令,确定是读还是写。读: app_cmd = 3'b001; 写:app_cmd = 3'b000; -
app_addr:操作地址(往哪写,从哪读) -
app_en :操作地址app_addr的使能,只有它拉高的时候,对应的app_addr才是有效的 -
app_wdf_data:写入数据的接口(往DDR3里面写什么) -
app_wdf_wren:写入的数据接口app_wdf_data的使能,只有它拉高的时候。对应的app_wdf_data才是有效的 -
app_wdf_end:这里不需要管他。只需要使app_wdf_end = app_wdf_wren。 -
-
所以写入数据的时候你只需要处理好这六个信号就可以 -
-
app_cmd -
app_addr -
app_en -
app_wdf_data -
app_wdf_wren -
app_wdf_end
读操作相关信号:
-
读数据的时候只需要操作以下三个信号,是不是更简单呢?(这里的三个信号和上面是一样的) -
app_cmd -
app_addr -
app_en
看完了相关的信号,我们就该来看看看读写操作是如何操作的了。
写操作:
写操作时序如下:(这个图示从别人的文档里面弄出来的不想去翻手册了)
可以看到写的时候可以有三种情况,分别对应1、2、3种情况。第一种是地址和数据严格对齐的,第二三种是数据和地址不对齐的,推荐曹勇第一种数据和地址对齐的方式。
在这个图里面,我们发现有两个信号之前没有说明。就是
-
app_rdy:这个信号由DDR3输出,告诉用户在app_rdy拉高的时候拉高app_en。地址app_addr才是有效的。 -
-
app_wdf_rdy:这个信号由DDR3输出,告诉用户在app_wdf_rdy拉高的时候拉高app_wdf_wren,写入的数据app_wdf_data才是有效的。
由上图的第一种情况可以看出,在app_rdy和app_wdf_rdy都拉高的时候,把app_en拉高、再给出相应的地址、再写入相应的数据、再给出对应的写使能就可以把相应的数据写入到DDR3中相应的地址。但是,需要注意的是,在app_rdy 或者app_wdf_rdy没有拉高的时候,需要把相应的数据和地址保持不变。
读操作:
读操作就更简单了,先把读操作时序贴出来。(这个图也是从别人的文档里面截取的)
在读的时候,只需要在app_rdy拉高的时候给出地址app_addr和使能app_en即可。然后就等着接数就可以,给几个周期的使能就出几个周期的数据。
所以,回到第一节Xilinx MIG 控制器使用详解(一),当时说的MIG的使用就像BRAM一样简单。
你看,我没说错吧。BRAM在写的时候也是给出写地址和写使能,MIG也是,不同的是MIG需要在app_rdy、app_wdf_rdy拉高的时候给出。BRAM在读的时候,也是给出一个地址就可以了,MIG还需拉高app_en。
现在让我们来算一下DDR3的带宽吧。
DDR3的物理位宽是32bit的,DDR3跑的时钟频率是400MHz, 又因为是上下沿都采样,所以带宽应该为:400MHz*2*32bit=800MHz * 32bit。那么MIG控制器的读写数据端口的位宽是多少呢?也就是app_wdf_data 和app_rd_data的位宽是多少呢?答案是:256bit。 怎么算出来的呢?800MHz * 32 bit = 100MHz *32*8bit, 所以是32*8=256bit。
下一篇就介绍如何使用MIG控制器进行读写。
关于DDR3的基本知识在这里我就不详细说了,只有在相关的地方会提上一嘴。本教程的目的只是教会大家如何使用MIG控制器,大家一定不要觉得MIG控制器有多难,其实很简单的,跟着我在心里默念“MIG就像BRAM一样简单”。确实哈,当你回过头来看,MIG控制器的使用基本和BRAM的使用方法很像。
VIVADO版本:2016.4
芯片:zynq7035
读写测试总模块如下图:
外部晶振输入的是100MHz,但是MIG需要400MHz的输入,所以需要经过一个始终管理模块进行倍频到400MHz。DDR_CTRL的主要作用就是产生第二节中说明的那些读写控制信号。
代码结构如下:
现在让我们来分析分析代码吧,先从top.v开始。
top.v
-
`timescale 1ns / 1ps -
-
module top( -
inout [31:0] ddr3_dq , -
inout [3:0] ddr3_dqs_n , -
inout [3:0] ddr3_dqs_p , -
-
output [14:0] ddr3_addr , -
output [2:0] ddr3_ba , -
output ddr3_ras_n , -
output ddr3_cas_n , -
output ddr3_we_n , -
output ddr3_reset_n , -
output ddr3_ck_p , -
output ddr3_ck_n , -
output ddr3_cke , -
output ddr3_cs_n , -
output [3:0] ddr3_dm , -
output ddr3_odt , -
-
input sys_clk_i //外部输入的100MHz时钟 -
); -
-
-
wire clk400; -
wire rst_n; -
clk_wiz_0 clk_wiz_0( -
.clk_out1 ( clk400 ), -
.locked ( rst_n ), -
.clk_in1 ( sys_clk_i ) -
); -
-
-
-
-
(*keep = "true"*)(*mark_debug = "true"*)wire [28:0] app_addr ; -
(*keep = "true"*)(*mark_debug = "true"*)wire [2:0] app_cmd ; -
(*keep = "true"*)(*mark_debug = "true"*)wire app_en ; -
(*keep = "true"*)(*mark_debug = "true"*)wire [255:0] app_wdf_data ; -
(*keep = "true"*)(*mark_debug = "true"*)wire app_wdf_end ; -
(*keep = "true"*)(*mark_debug = "true"*)wire app_wdf_wren ; -
(*keep = "true"*)(*mark_debug = "true"*)wire [255:0] app_rd_data ; -
(*keep = "true"*)(*mark_debug = "true"*)wire app_rd_data_end ; -
(*keep = "true"*)(*mark_debug = "true"*)wire app_rd_data_valid ; -
(*keep = "true"*)(*mark_debug = "true"*)wire app_rdy ; -
(*keep = "true"*)(*mark_debug = "true"*)wire app_wdf_rdy ; -
-
(*keep = "true"*)(*mark_debug = "true"*)wire ddrdata_test_err ; -
-
-
wire [31:0] app_wdf_mask ; -
wire app_sr_req ; -
wire app_ref_req ; -
wire app_zq_req ; -
wire app_sr_active ; -
wire app_ref_ack ; -
wire app_zq_ack ; -
wire ui_clk ; -
wire ui_clk_sync_rst ; -
-
wire init_calib_complete ; -
-
-
-
-
//例化ddr_ctrl模块 -
ddr_ctrl ddr_ctrl( -
.clk ( ui_clk ), -
.rst_n ( rst_n ), -
-
.app_addr ( app_addr ), -
.app_cmd ( app_cmd ), -
.app_en ( app_en ), -
.app_wdf_data ( app_wdf_data ), -
.app_wdf_mask ( app_wdf_mask ), -
-
.app_rd_data ( app_rd_data ), -
.app_rd_data_end ( app_rd_data_end ), -
.app_rd_data_valid ( app_rd_data_valid ), -
-
.app_rdy ( app_rdy ), -
.app_wdf_rdy ( app_wdf_rdy ), -
.app_wdf_end ( app_wdf_end ), -
.app_wdf_wren ( app_wdf_wren ), -
-
.init_calib_complete ( init_calib_complete ), -
.ddrdata_test_err ( ddrdata_test_err ) -
); -
-
-
-
-
-
//例化MIG控制器 -
-
mig_7series_0 u_mig_7series_0 ( -
// Memory interface ports -
.ddr3_addr (ddr3_addr), // output [14:0] ddr3_addr -
.ddr3_ba (ddr3_ba), // output [2:0] ddr3_ba -
.ddr3_cas_n (ddr3_cas_n), // output ddr3_cas_n -
.ddr3_ck_n (ddr3_ck_n), // output [0:0] ddr3_ck_n -
.ddr3_ck_p (ddr3_ck_p), // output [0:0] ddr3_ck_p -
.ddr3_cke (ddr3_cke), // output [0:0] ddr3_cke -
.ddr3_ras_n (ddr3_ras_n), // output ddr3_ras_n -
.ddr3_reset_n (ddr3_reset_n), // output ddr3_reset_n -
.ddr3_we_n (ddr3_we_n), // output ddr3_we_n -
.ddr3_dq (ddr3_dq), // inout [31:0] ddr3_dq -
.ddr3_dqs_n (ddr3_dqs_n), // inout [3:0] ddr3_dqs_n -
.ddr3_dqs_p (ddr3_dqs_p), // inout [3:0] ddr3_dqs_p -
.init_calib_complete (init_calib_complete), // output init_calib_complete -
-
.ddr3_cs_n (ddr3_cs_n), // output [0:0] ddr3_cs_n -
.ddr3_dm (ddr3_dm), // output [3:0] ddr3_dm -
.ddr3_odt (ddr3_odt), // output [0:0] ddr3_odt -
// Application interface ports -
.app_addr (app_addr), // input [28:0] app_addr -
.app_cmd (app_cmd), // input [2:0] app_cmd -
.app_en (app_en), // input app_en -
.app_wdf_data (app_wdf_data), // input [255:0] app_wdf_data -
.app_wdf_end (app_wdf_end), // input app_wdf_end -
.app_wdf_wren (app_wdf_wren), // input app_wdf_wren -
.app_rd_data (app_rd_data), // output [255:0] app_rd_data -
.app_rd_data_end (app_rd_data_end), // output app_rd_data_end -
.app_rd_data_valid (app_rd_data_valid), // output app_rd_data_valid -
.app_rdy (app_rdy), // output app_rdy -
.app_wdf_rdy (app_wdf_rdy), // output app_wdf_rdy -
.app_sr_req (0), // input app_sr_req -
.app_ref_req (0), // input app_ref_req -
.app_zq_req (0), // input app_zq_req -
.app_sr_active (app_sr_active), // output app_sr_active -
.app_ref_ack (app_ref_ack), // output app_ref_ack -
.app_zq_ack (app_zq_ack), // output app_zq_ack -
.ui_clk (ui_clk), // output ui_clk -
.ui_clk_sync_rst (ui_clk_sync_rst), // output ui_clk_sync_rst -
.app_wdf_mask (app_wdf_mask), // input [31:0] app_wdf_mask -
// System Clock Ports -
.sys_clk_i (clk400), -
// Reference Clock Ports -
.clk_ref_i (clk400), -
.sys_rst (rst_n) // input sys_rst -
); -
-
endmodule
ddr_ctrl.v模块
-
`timescale 1ns / 1ps -
-
module ddr_ctrl( -
input clk , -
input rst_n , -
-
output [28:0] app_addr , -
output reg [2:0] app_cmd , -
output reg app_en , -
output reg [255:0] app_wdf_data , -
output [31:0] app_wdf_mask , -
-
input [255:0] app_rd_data , -
input app_rd_data_end , -
input app_rd_data_valid , -
-
input app_rdy , -
input app_wdf_rdy , -
output app_wdf_end , -
output app_wdf_wren , -
input init_calib_complete , // DDR3 鍒濆鍖栧畬鎴� -
output reg ddrdata_test_err -
); -
-
-
reg [3:0] test_state ; -
reg [15:0] send_cnt ; -
reg [28:0] write_addr ; -
reg [28:0] read_addr ; -
reg [255:0] data_buff ; -
-
-
assign app_wdf_wren = app_en & app_wdf_rdy & app_rdy & (app_cmd == 3'd0); -
assign app_wdf_end = app_wdf_wren; -
-
assign app_addr = (app_cmd == 3'd0) ? write_addr : read_addr; -
-
assign app_wdf_mask = 32'd0;//这个直赋0 -
-
-
-
always@(posedge clk or negedge rst_n) -
begin -
if(!rst_n) -
begin -
test_state <= 4'd0; -
send_cnt <= 16'd0; -
write_addr <= 29'd0; -
read_addr <= 29'd0; -
app_cmd <= 3'd0; -
app_en <= 1'b0; -
app_wdf_data <= 256'd0; -
end -
else -
begin -
case (test_state) -
4'd0 : -
begin -
app_cmd <= 3'd0; -
app_en <= 1'b0; -
app_wdf_data <= 256'd0; -
send_cnt <= 16'd0; -
write_addr <= 29'd0; -
read_addr <= 29'd0; -
if(init_calib_complete) -
test_state <= 4'd1; //如果DDR初始化完成就进入下一个状态 -
else -
test_state <= 4'd0; //否则等待DDR初始化完成 -
end -
-
4'd1 : -
begin -
if(app_rdy & app_wdf_rdy) //等待这两个信号拉高就使命令有效 -
begin -
app_cmd <= 3'd0; -
app_en <= 1'b1; -
send_cnt <= send_cnt + 1'b1; -
-
test_state <= 4'd2; -
end -
end -
-
4'd2 : -
begin -
if(app_rdy & app_wdf_rdy) -
begin -
if(send_cnt == 16'd199) -
begin -
app_wdf_data <= 256'd0; -
write_addr <= 29'd0; -
send_cnt <= 16'd0; -
test_state <= 4'd3; //进入读状态 -
app_en <= 1'b0; -
end -
else -
begin -
send_cnt <= send_cnt + 1'b1; -
app_cmd <= 3'd0; -
app_en <= 1'b1; -
write_addr <= write_addr + 29'd8; -
app_wdf_data <= app_wdf_data + 256'd1;//写入的数据,从1到198 -
end -
end -
end -
-
4'd3 : -
begin -
if(app_rdy & app_wdf_rdy) -
begin -
app_cmd <= 3'd1; //读命令有效,实际上这时候已经有数据出来了 -
app_en <= 1'b1; -
send_cnt <= send_cnt + 1'b1; -
test_state <= 4'd4; -
end -
end -
-
4'd4 : -
begin -
if(app_rdy & app_wdf_rdy) -
begin -
if(send_cnt == 16'd199) -
begin -
read_addr <= 29'd0; -
send_cnt <= 16'd0; -
test_state <= 4'd5; -
app_en <= 1'b0; -
end -
else -
begin -
send_cnt <= send_cnt + 1'b1; -
app_cmd <= 3'd1; -
app_en <= 1'b1; -
read_addr <= read_addr + 29'd8; //读地址 -
-
end -
end -
end -
-
4'd5 ://不读也不写 -
begin -
app_cmd <= 3'd0; -
app_en <= 1'b0; -
send_cnt <= send_cnt + 1'b1; -
if(send_cnt == 16'd200) -
begin -
send_cnt <= 16'd0; -
test_state <= 4'd1; -
end -
end -
-
default : test_state <= 4'd0; -
-
endcase -
end -
end -
//比较写入与独处的数据是否相等 -
always@(posedge clk or negedge rst_n) -
begin -
if(!rst_n) -
begin -
data_buff <= 256'd0; -
ddrdata_test_err <= 1'b0; -
end -
else if (test_state == 4'd3) -
begin -
data_buff <= 256'd0; -
end -
else -
begin -
if(app_rd_data_valid) -
begin -
data_buff <= data_buff + 256'd1; -
if(data_buff != app_rd_data) //如果写入与独处的数据不相等就拉高 -
ddrdata_test_err <= 1'b1; -
end -
end -
end -
-
endmodule
到此程序已经分析完毕了,想到什么问题再补充吧。
工程代码。