Spartan6 FPGA DDR3自建写测试平台
FPGA
先简单总结一下前面建立FPGA DDR3 IP Core,以及简单简述一下DDR3 IP Core的信号。
1.DDR3 IP core建立的时候,如果网上没有搭建这个IP核的资料,我们要怎么搭建呢?
2.打开后得到这个文档,点击Click here,可以获得这个IP Core的搭建设置方法
3.点击Click here后,得到User Guide手册
DDR3 IP Core各个信号,查看MCB这个文档
他的引脚信号分布
我们控制的就是右边的IOB信号。左边的信号有几个端口就要看你创建IP核的时候的Port设置了。
因为我之前创建IP核的时候是将他设置成了2个64bit的Port。所以这里是有P0和P1信号端口。
接口的分类
command path 中比较有用的几个信号
(c3_p0_cmd_clk),//cmd FIFO的用户时钟,上升沿有效
(c3_p0_cmd_en), //该高电平有效信号是用于写入的写入使能信号
(c3_p0_cmd_instr),//指令端口
(c3_p0_cmd_bl),//突发长度,0-63
(c3_p0_cmd_byte_addr),//字节开始的地址,他的后面几位不能为0
(c3_p0_cmd_empty),//FIFO的空标志,高有效
(c3_p0_cmd_full),//FIFO的满标志由MCB的手册可知,这些信号都是Command path信号
那我们如何向DDR3写入command呢?打开目录,找到command path timing,找到他的时序图,根据手册时序图写程序就可以向DDR3写入指令。
command path的读写时序。
写时序
注意:这里需要注意时钟,这里有好几个时钟,command path timing(write)时钟是cmd_clk,这个时钟主要是用来检测cmd_en信号的,而我们如果需要将instr指令端口信号写入DDR3,则使用的是Wr_clk
我们看看cmd_clk和wr_clk手册怎么说的吧
cmd_clk
wr_clk
write_path_timing
那cmd和wirte是怎么协调的呢?
我们可以看到在command path timing 时序和write path timing都可以看到cmd和write都有调用到FIFO。cmd_FIFO存储的是命令指令,write data fifo里面存储是要写入到DDR3中的数据。那我们要往DDR3写入数据,那么DDR3先读入cmd_instr的数据。先知道是读操作还是写操作。得知是写操作后,会去访问Write Data FIFO里面的数据,将Write FIFO里面的数据写入DDR3 FIFO。读操作,就是讲Read FIFO里面的数据读出来。因此,在写命令的时候,我们需要确保DDR3的Wr_data_FIFO里面是有数据的。所以我们要将上面的步骤反过来,先向Write data fifo写入想要DDR3芯片内部数据,之后再向cmd fifo中写入相应的写指令。这样的话,我们的时序图可以这么写
cmd_en也就是去检测wr_en的下降沿。
代码的实现
将顶层文件DDR3的command 引脚和 write引脚引出来。
因为我们是仿真时序,所以我们新建一个wr_trig来控制wr_en。
仿真时序图
新建一个ddr3_drive文件。
上图是ddr3_drive的引脚信息。因为我们是仿真,所以我们将instr直接固定成3'b000写模式,将Bl,mask也固定。
ddr3_drive代码:
module ddr3_drive(
//system signal
input s_clk ,
input s_rst_n ,
//DDR3 User Interface
output wire p0_cmd_en ,
output wire [2:0] p0_cmd_instr ,
output wire [5:0] p0_cmd_bl ,
output wire [29:0] p0_cmd_byte_addr ,
output reg p0_wr_en ,
output wire [7:0] p0_wr_mask ,
output reg [63:0] p0_wr_data ,
//Debug
input wr_trig
);
//========================================================================\
// =========== Define Parameter and Internal signals ===========
//========================================================================/
reg p0_wr_en_r1 ; //用于捕获wr_en的下降沿
//=============================================================================
//************** Main Code **************
//=============================================================================
always @(posedge s_clk or negedge s_rst_n) begin
if(s_rst_n == 1'b0)
p0_wr_en <= 'd0;
else if(p0_wr_data >= 'd15)
p0_wr_en <= 'd0;
else if(wr_trig == 1'b1)
p0_wr_en <= 1'b1;
end
always @(posedge s_clk or negedge s_rst_n) begin
if(s_rst_n == 1'b0)
p0_wr_data <= 'd0;
else if(p0_wr_en == 1'b1)
p0_wr_data <= p0_wr_data + 1'b1;
end
always @(posedge s_clk) begin
p0_wr_en_r1 <= p0_wr_en;
end
assign p0_cmd_en = ~p0_wr_en & p0_wr_en_r1;
assign p0_cmd_instr = 3'b000;//write
assign p0_cmd_bl = 'd15;
assign p0_cmd_byte_addr = 'd0;
assign p0_wr_mask = 8'b0;
endmodule
顶层文件将ddr3_drive文件例化进去。
注意:这里给ddr3_drive的是ddr3例化文件给出的user clock和user reset。reset是高电平有效的,即高电平复位。所以给ddr3_drive传入的是~c3_rst0。需要去一次反。
让ddr3_drive的信号与ddr3的例化文件信号一致。
将cmd_clk和wr_clk都用c3_clk0传入。
在top文件中再声明一下即可。
接下来就是写仿真的脚本文件了
主要就是去产生一个wr_trig信号
代码如下
initial begin
wr_trig = 0;
@(posedge c3_calib_done)
#10_0000;//10ns
wr_trig = 1;
#25600;
wr_trig = 0;
end
因为ddr3的操作要在calib_done完成后(calib_done是指MCB初始化完成的信号)才能读写ddr3.所以top文件也需要将c3_calib_done引出,在tb文件中例化进来。
接着因为我们需要查看ddr3_drive_inst,u_mig_39_2的信号,为了提高效率,我们通过tcl脚本添加。
在工程文件中有fdo和udo两个文件。这就相当于我们平时自己写的do文件。fdo是ISE产生的,一般不改变,udo是ISE给用户添加的do文件。udo也就是user do的意思。
将分组和添加信号的脚本卸载udo文件中。
点击开始仿真。
波形的分析
我们要先找到wr_trig信号。因为真正的数据端口是ddr3_dq引脚,我们主要查看的是wr_trig拉高后的ddr3_dq引脚信号。看波形我们可以知道wr_trig引脚在34855200ps后拉高。我们就去看这个时间后的打印信息。
注意:因为我们FPGA DDR3 IP是64bit port,而我们DDR3实际是16bit的port。所以我们这里是并不是0000,0001,0002这样下去。
而是这样四个一组的。
因为我们测试是0-15,所以最终到0x0f,看打印信息,可知仿真正确。
欢迎关注微信公众号:文鸿开源工作室