写在前面
本系列为 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) begin
if (rst) begin
state <= IDLE;
end
else begin
state <= next_state;
end
end
//状态机第二段,状态跳转,组合逻辑阻塞赋值
always @(*) begin
if (rst) begin
next_state = state;
end
else begin
case(state)
IDLE: begin
next_state = ARBIT;
end
ARBIT: begin
if (wr_req) begin
next_state = WRITE;
end
else if (rd_req) begin
next_state = READ;
end
else begin
next_state = ARBIT;
end
end
WRITE: begin
if (wr_end) begin
next_state = ARBIT;
end
else begin
next_state = WRITE;
end
end
READ: begin
if (rd_end) begin
next_state = ARBIT;
end
else begin
next_state = READ;
end
end
default: begin
next_state = IDLE;
end
endcase
end
end
//状态机第三段,结果输出,时序逻辑非阻塞赋值
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
wr_start <= 'd0;
rd_start <= 'd0;
end
else begin
case(state)
IDLE: begin
wr_start <= 'd0;
rd_start <= 'd0;
end
ARBIT: begin
if (wr_req) begin
wr_start <= 'd1;
rd_start <= 'd0;
end
else if (rd_req) begin
wr_start <= 'd0;
rd_start <= 'd1;
end
else begin
wr_start <= 'd0;
rd_start <= 'd0;
end
end
WRITE: begin
wr_start <= 'd0;
rd_start <= 'd0;
end
READ: begin
wr_start <= 'd0;
rd_start <= 'd0;
end
default: begin
wr_start <= 'd0;
rd_start <= 'd0;
end
endcase
end
end
endmodule
将仲裁模块进行例化,并对端口信号进行申明,对相应的信号进行相连即可。
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* 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(
// Inouts
inout [15:0] ddr3_dq,
inout [1:0] ddr3_dqs_n,
inout [1:0] ddr3_dqs_p,
// Outputs
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 [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 clocks
input 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
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* 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 / 1ps
module 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 begin
sys_clk = 'd1;
rst_n <= 'd0;
#200
rst_n <= 'd1;
end
initial begin
data_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;
end
initial begin
#100
gen_req();
end
always @(posedge ui_clk or posedge rd_rst) begin
if (rd_rst) begin
rd_req <= 'd0;
end
else if (wr_req) begin
rd_req <= 'd1;
end
else if (rd_start) begin
rd_req <= 'd0;
end
else begin
rd_req <= rd_req;
end
end
always @(posedge ui_clk or posedge rd_rst) begin
if (rd_rst) begin
wr_data <= 'd0;
end
else if (wr_data == 'd63) begin
wr_data <= 'd0;
end
else if (data_req) begin
wr_data <= wr_data + 'd1;
end
else begin
wr_data <= wr_data;
end
end
task gen_req;
begin
@ (negedge wr_rst);
@ (posedge ui_clk);
@ (posedge ui_clk);
@ (posedge ui_clk);
@ (posedge ui_clk);
@ (posedge ui_clk);
wr_req <= 'd1;
#200
wr_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
// endtask
always #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 拉高。
仲裁部分和用户部分
主要分为以下几个阶段:
可以看到在用户端的信号也是和设计呈现的一致。
控制台部分
在控制台打印的信息,首先是写操作,从地址0开始写入数据,依次写入0-63。
最后一个数据为63,最后一个地址为511(16进制的1ff),也就是写入地址为0-511,符合设计要求。
读操作和写操作也是一样,读出数据0-63,读出地址0-511。
至此完成仲裁模块设计的验证!
本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。
【DDR3 控制器设计】系列博客汇总篇(附直达链接)