写在前面
本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。
【DDR3 控制器设计】系列博客汇总篇(附直达链接)
目录
实验任务
实验环境
实验介绍
仲裁模块设计
程序设计
仲裁模块设计
顶层模块设计
testbench 设计
仿真波形
汇总篇
实验任务
在实验的基础上添加一个仲裁模块,控制写读指令的执行。
实验环境
开发环境:Vivado 2018.2,
FPGA 芯片型号:xc7a100tffg484-2
DDR3 型号:MT41J256M16HA-125
实验介绍
由于现在设计的框架是写命令和读命令分开执行的,当写命令和读命令同时执行时就会出错,并且现在的写和读命令总线是两根独立的总线,可以将其整合到一条总线上。因此可以添加一个仲裁模块,基本思路就是优先写操作,当写使能为高时,命令总线为写命令,否则为读命令,对于 DDR 读写模块的 app_addr 信号,可以在不使用时将其置为 0,这样将 DDR 读写模块的 app_addr 进行按位或,结果即为对MIG IP 核操作的 app_addr 信号。对于 app_en 也是用同样的方法。
仲裁模块设计
仲裁设计一般分为两种:设定优先级和轮询,这里采用的是设定优先级。基本设计思想就是写操作比读操作优先程度更高,当检测到wr_req为高时,仲裁模块输出wr_start信号,标志开始进行写操作,当写操作完成后等待rd_req的信号,如果检测到rd_req信号拉高,仲裁模块则输出rd_start开始执行读操作,直至读操作完成便可开始接收wr_req信号,一直这么循环操作,读写操作有条不紊的进行。
这种读写状态的来回切换很适合设计成状态机,以下为状态机跳转图,初始状态为IDLE,当复位完成后直接跳转到ARBIT仲裁状态,接收到写请求wr_req 跳转到写状态,写完成信号wr_end拉高后又进入到仲裁状态,接收到读请求rd_req 跳转到读状态,读完成信号rd_end拉高后又进入到仲裁状态,循环往复。
程序设计
仲裁模块设计
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Engineer : Linest-5
/* File : ddr3_arbit.v
/* Create : 2022-09-24 15:56:54
/* Revise : 2022-09-24 15:56:54
/* Module Name : ddr3_arbit
/* Description : ddr3的读写仲裁模块,设定为写优先
/* Editor : sublime text3, tab size (4)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/module ddr3_arbit(//时钟和复位input ui_clk, //用户时钟,由MIG提供input rst, //用户复位,高有效input wr_req, //写请求input rd_req, //读请求input wr_end, //写结束标志信号input rd_end, //读结束标志信号output reg wr_start, //开始写操作标志信号output reg rd_start //开始读操作标志信号);/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* 定义参数和信号 */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
parameter IDLE = 4'b0001;
parameter ARBIT = 4'b0010;
parameter WRITE = 4'b0100;
parameter READ = 4'b1000;reg [3:0] state;
reg [3:0] next_state;/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* 三段状态机设计 */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
//状态机第一段,状态初始化,时序逻辑非阻塞赋值
always @(posedge ui_clk or posedge rst) beginif (rst) beginstate <= IDLE;endelse beginstate <= next_state;end
end//状态机第二段,状态跳转,组合逻辑阻塞赋值
always @(*) beginif (rst) beginnext_state = state;endelse begincase(state)IDLE: beginnext_state = ARBIT;endARBIT: beginif (wr_req) beginnext_state = WRITE;endelse if (rd_req) beginnext_state = READ;endelse beginnext_state = ARBIT;endendWRITE: beginif (wr_end) beginnext_state = ARBIT;endelse beginnext_state = WRITE;endendREAD: beginif (rd_end) beginnext_state = ARBIT;endelse beginnext_state = READ;endenddefault: beginnext_state = IDLE;endendcaseend
end//状态机第三段,结果输出,时序逻辑非阻塞赋值
always @(posedge ui_clk or posedge rst) beginif (rst) beginwr_start <= 'd0;rd_start <= 'd0;endelse begincase(state)IDLE: beginwr_start <= 'd0;rd_start <= 'd0;endARBIT: beginif (wr_req) beginwr_start <= 'd1;rd_start <= 'd0;endelse if (rd_req) beginwr_start <= 'd0;rd_start <= 'd1;endelse beginwr_start <= 'd0;rd_start <= 'd0; end endWRITE: beginwr_start <= 'd0;rd_start <= 'd0;endREAD: beginwr_start <= 'd0;rd_start <= 'd0; enddefault: beginwr_start <= 'd0;rd_start <= 'd0;endendcaseend
endendmodule
顶层模块设计
将仲裁模块进行例化,并对端口信号进行申明,对相应的信号进行相连即可。
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Engineer : Linest-5
/* File : top_ddr3_init.v
/* Create : 2022-09-15 09:58:59
/* Revise : 2022-09-24 21:11:50
/* Module Name :
/* Description :
/* Editor : sublime text3, tab size (4)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/module top_ddr3_init(// Inoutsinout [15:0] ddr3_dq,inout [1:0] ddr3_dqs_n,inout [1:0] ddr3_dqs_p,// Outputsoutput [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 [0:0] ddr3_ck_p,output [0:0] ddr3_ck_n,output [0:0] ddr3_cke,output [0:0] ddr3_cs_n,output [1:0] ddr3_dm,output [0:0] ddr3_odt,// Inputs// Differential system clocksinput sys_clk,input rst_n
);wire init_calib_complete;
wire ui_clk;
wire ui_clk_sync_rst;
wire [127:0] wr_data;
wire [7:0] wr_brust_len;
wire wr_start;
wire [28:0] wr_addr;
wire [2:0] wr_cmd;
wire [15:0] wr_mask;
wire data_req;
wire wr_end;
wire app_rdy;
wire app_wdf_rdy;
wire [2:0] app_cmd;
wire app_en;
wire [28:0] app_addr;
wire [127:0] app_wdf_data;
wire app_wdf_wren;
wire [15:0] app_wdf_mask;
wire app_wdf_end;wire [7:0] rd_brust_len;
wire rd_start;
wire [28:0] rd_addr;
wire [2:0] rd_cmd;
wire [127:0] rd_data;
wire rd_data_valid;
wire rd_end;
wire [127:0] app_rd_data;
wire app_rd_data_end;
wire app_rd_data_valid;wire wr_req;
wire rd_req;wire [2:0] app_wr_cmd;
wire app_wr_en;
wire [28:0] app_wr_addr;wire [2:0] app_rd_cmd;
wire app_rd_en;
wire [28:0] app_rd_addr;assign app_en = app_wr_en | app_rd_en;
assign app_addr = app_wr_addr | app_rd_addr;
assign app_cmd = (app_wr_en == 'd1) ? 3'b000 : 3'b001;//DDR工作时钟PLL例化
ddr3_clock ddr3_clock_inst(.clk_out1(sys_clk_in), // output clk_out1.clk_in1(sys_clk) // input clk_in1
); //DDR初始化模块例化
ddr3_init u_ddr3_init (// 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 [15:0] ddr3_dq.ddr3_dqs_n (ddr3_dqs_n), // inout [1:0] ddr3_dqs_n.ddr3_dqs_p (ddr3_dqs_p), // inout [1: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 [1: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 [127:0] app_wdf_data.app_wdf_end (app_wdf_wren), // input app_wdf_end.app_wdf_wren (app_wdf_wren), // input app_wdf_wren.app_rd_data (app_rd_data), // output [127: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 (1'b0), // input app_sr_req.app_ref_req (1'b0), // input app_ref_req.app_zq_req (1'b0), // 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 [15:0] app_wdf_mask// System Clock Ports.sys_clk_i (sys_clk_in), // input sys_clk_i.sys_rst (rst_n) // input sys_rst
);//DDR仲裁模块例化
ddr3_arbit inst_ddr3_arbit (.ui_clk (ui_clk),.rst (ui_clk_sync_rst || (~init_calib_complete)),.wr_req (wr_req),.rd_req (rd_req),.wr_end (wr_end),.rd_end (rd_end),.wr_start (wr_start),.rd_start (rd_start)
);//DDR写操作模块例化
ddr3_wr_ctrl inst_ddr3_wr_ctrl (.ui_clk (ui_clk),.rst (ui_clk_sync_rst || (~init_calib_complete)),.wr_data (wr_data),.wr_brust_len (wr_brust_len),.wr_start (wr_start),.wr_addr (wr_addr),.wr_cmd (wr_cmd),.wr_mask (wr_mask),.data_req (data_req),.wr_end (wr_end),.app_rdy (app_rdy),.app_wdf_rdy (app_wdf_rdy),.app_cmd (app_wr_cmd),.app_en (app_wr_en),.app_addr (app_wr_addr),.app_wdf_data (app_wdf_data),.app_wdf_wren (app_wdf_wren),.app_wdf_mask (app_wdf_mask),.app_wdf_end (app_wdf_end)
);//DDR读操作模块例化
ddr3_rd inst_ddr3_rd (.ui_clk (ui_clk),.rst (ui_clk_sync_rst || (~init_calib_complete)),.init_calib_complete (init_calib_complete),.rd_brust_len (rd_brust_len),.rd_start (rd_start),.rd_addr (rd_addr),.rd_cmd (rd_cmd),.rd_data (rd_data),.rd_data_valid (rd_data_valid),.rd_end (rd_end),.app_rdy (app_rdy),.app_rd_data (app_rd_data),.app_rd_data_end (app_rd_data_end),.app_rd_data_valid (app_rd_data_valid),.app_cmd (app_rd_cmd),.app_en (app_rd_en),.app_addr (app_rd_addr)
);endmodule
testbench 设计
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Engineer : Linest-5
/* File : tb_top_ddr3_init.v
/* Create : 2022-09-15 10:10:36
/* Revise : 2022-09-24 19:48:54
/* Module Name :
/* Description :
/* Editor : sublime text3, tab size (4)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
`timescale 1ns / 1psmodule tb_top_ddr3_init();reg sys_clk;
reg rst_n;wire [15:0] ddr3_dq;
wire [1:0] ddr3_dqs_n;
wire [1:0] ddr3_dqs_p;
wire [14:0] ddr3_addr;
wire [2:0] ddr3_ba;
wire ddr3_ras_n;
wire ddr3_cas_n;
wire ddr3_we_n;
wire ddr3_reset_n;
wire [0:0] ddr3_ck_p;
wire [0:0] ddr3_ck_n;
wire [0:0] ddr3_cke;
wire [0:0] ddr3_cs_n;
wire [1:0] ddr3_dm;
wire [0:0] ddr3_odt;//wr_ddr
reg ui_clk;
reg wr_rst;
reg data_req;
reg [127:0] wr_data;
reg [7:0] wr_brust_len;
reg wr_start;
reg [28:0] wr_addr;
reg [2:0] wr_cmd;//rd_ddr
reg rd_rst;
reg [7:0] rd_brust_len;
reg [127:0] rd_data;
reg rd_start;
reg [28:0] rd_addr;
reg [2:0] rd_cmd;
reg app_rdy;
reg [127:0] app_rd_data;
reg app_rd_data_end;
reg app_rd_data_valid;
reg [2:0] app_cmd;reg wr_req;
reg rd_req;initial beginsys_clk = 'd1;rst_n <= 'd0;#200rst_n <= 'd1;
endinitial begindata_req = 'd0;wr_data = 'd0;wr_brust_len = 'd64;wr_addr = 'd0;wr_cmd = 3'b000;force ui_clk = inst_top_ddr3_init.inst_ddr3_wr_ctrl.ui_clk;force wr_rst = inst_top_ddr3_init.inst_ddr3_wr_ctrl.rst;force data_req = inst_top_ddr3_init.inst_ddr3_wr_ctrl.data_req;force wr_start = inst_top_ddr3_init.inst_ddr3_wr_ctrl.wr_start;force inst_top_ddr3_init.wr_brust_len = wr_brust_len;force inst_top_ddr3_init.wr_addr = wr_addr;force inst_top_ddr3_init.wr_cmd = wr_cmd;force inst_top_ddr3_init.wr_data = wr_data;force inst_top_ddr3_init.wr_req = wr_req;force inst_top_ddr3_init.rd_req = rd_req;rd_brust_len = 'd64;rd_addr = 'd0;rd_cmd = 3'b001;force ui_clk = inst_top_ddr3_init.inst_ddr3_rd.ui_clk;force rd_rst = inst_top_ddr3_init.inst_ddr3_rd.rst;force rd_data = inst_top_ddr3_init.inst_ddr3_rd.rd_data;force rd_start = inst_top_ddr3_init.inst_ddr3_rd.rd_start;force inst_top_ddr3_init.rd_brust_len = rd_brust_len;force inst_top_ddr3_init.rd_addr = rd_addr;force inst_top_ddr3_init.rd_cmd = rd_cmd;
endinitial begin#100gen_req();
endalways @(posedge ui_clk or posedge rd_rst) beginif (rd_rst) beginrd_req <= 'd0;endelse if (wr_req) beginrd_req <= 'd1;endelse if (rd_start) beginrd_req <= 'd0;endelse beginrd_req <= rd_req;end
endalways @(posedge ui_clk or posedge rd_rst) beginif (rd_rst) beginwr_data <= 'd0;endelse if (wr_data == 'd63) beginwr_data <= 'd0;endelse if (data_req) beginwr_data <= wr_data + 'd1;endelse beginwr_data <= wr_data;end
endtask gen_req;begin@ (negedge wr_rst);@ (posedge ui_clk);@ (posedge ui_clk);@ (posedge ui_clk);@ (posedge ui_clk);@ (posedge ui_clk);wr_req <= 'd1;#200wr_req <= 'd0;end
endtask// task gen_data;
// integer i;
// begin
// @ (posedge data_req);
// for (i=0;i<64;i=i+1) begin
// wr_data = {96'd0,i[31:0]};
// @ (posedge ui_clk);
// if (data_req == 'd0) begin
// i = i - 1;
// end
// end
// wr_data = 'd0;
// @ (posedge ui_clk);
// end
// endtaskalways #10 sys_clk = ~sys_clk;top_ddr3_init inst_top_ddr3_init (.ddr3_dq (ddr3_dq),.ddr3_dqs_n (ddr3_dqs_n),.ddr3_dqs_p (ddr3_dqs_p),.ddr3_addr (ddr3_addr),.ddr3_ba (ddr3_ba),.ddr3_ras_n (ddr3_ras_n),.ddr3_cas_n (ddr3_cas_n),.ddr3_we_n (ddr3_we_n),.ddr3_reset_n (ddr3_reset_n),.ddr3_ck_p (ddr3_ck_p),.ddr3_ck_n (ddr3_ck_n),.ddr3_cke (ddr3_cke),.ddr3_cs_n (ddr3_cs_n),.ddr3_dm (ddr3_dm),.ddr3_odt (ddr3_odt),.sys_clk (sys_clk),.rst_n (rst_n));ddr3_model u_comp_ddr3 (.rst_n (ddr3_reset_n),.ck (ddr3_ck_p),.ck_n (ddr3_ck_n),.cke (ddr3_cke),.cs_n (ddr3_cs_n),.ras_n (ddr3_ras_n),.cas_n (ddr3_cas_n),.we_n (ddr3_we_n),.dm_tdqs ({ddr3_dm[1],ddr3_dm[0]}),.ba (ddr3_ba),.addr (ddr3_addr),.dq (ddr3_dq[15:0]),.dqs ({ddr3_dqs_p[1],ddr3_dqs_p[0]}),.dqs_n ({ddr3_dqs_n[1],ddr3_dqs_n[0]}),.tdqs_n (),.odt (ddr3_odt));endmodule
仿真波形
对信号进行分组,分别为初始化部分、写部分、读部分、仲裁部分、用户部分。
总体部分
可以看到下图的仿真波形中,首先进行了写操作,随后进行读操作,对具体的模块进行观察。
初始化部分
看到 init_calib_complete 信号拉高表示 DDR3 完成初始化,可以开始对其进行读写操作。其余信号暂时不管。
写操作部分
首先开始写操作标志信号拉高,表示开始进行写操作,指令部分:3'b000执行写操作、突发长度为64、初始写地址为0,wr_req 拉高获取数据,写入数据依次加1,wr_end 信号拉高表示写操作完成,写入数据0-63。
将波形放大观察,可以看到当写请求拉高时,写入的数据依次加1,并且地址依次累加8,因为写入的数据为128bit,而DDR中地址每个位宽为16,每次写入填充8个地址。
读操作部分
当rd_start拉高标志着开始进行读操作,突发读长度64,初始地址为0,从下图可以看到当读使能拉高时,读地址依次加8,原因和写操作一样,但是读出的数据并不是立即呈现,而是要经过若干个时钟周期。
读出的数据为0-63,当最后一个数被读出后,同步的读操作完成信号 rd_end 拉高。
仲裁部分和用户部分
主要分为以下几个阶段:
- 复位完成,状态跳转至 ARBIT 仲裁状态
- wr_req 和 rd_req 同时拉高,但是仲裁器先执行写操作,写操作标志信号 wr_start 拉高,跳转至 WRITE 写状态
- 当写完成信号 wr_end 拉高,此时 rd_req 仍然拉高,读操作标志信号 rd_start 拉高,跳转至 READ 写状态
- 读完成信号 rd_end 拉高,依次写读操作完成,状态重新跳转至 ARBIT 仲裁状态
可以看到在用户端的信号也是和设计呈现的一致。
控制台部分
在控制台打印的信息,首先是写操作,从地址0开始写入数据,依次写入0-63。
最后一个数据为63,最后一个地址为511(16进制的1ff),也就是写入地址为0-511,符合设计要求。
读操作和写操作也是一样,读出数据0-63,读出地址0-511。
至此完成仲裁模块设计的验证!
汇总篇
本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。
【DDR3 控制器设计】系列博客汇总篇(附直达链接)