飞道的博客

【DDR3 控制器设计】(4)DDR3 的读操作设计

703人阅读  评论(0)

写在前面

本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。

【DDR3 控制器设计】系列博客汇总篇(附直达链接)


目录

实验任务

实验环境

实验介绍

接口详解

读时序

实验设计

读控制模块

顶层模块

testbench 设计

仿真波形

汇总篇 


实验任务

熟悉 DDR3 的 IP 核的读时序,并利用 Verilog 实现对 DDR3 进行读操作,并带有仿真时序图。

实验环境

开发环境:Vivado 2018.2,

FPGA 芯片型号:xc7a100tffg484-2

DDR3 型号:MT41J256M16HA-125

实验介绍

直接对 DDR3 进行读写时序控制是比较困难的,接口复杂且多,但是通过调取 DDR3 控制器 MIG IP 核,间接的对 DDR3 进行控制就会方便很多,控制器给用户端预留了接口,通过查看 MIG IP 核用户手册,对 IP 核进行读控制。

接口详解

以下为 MIG 和 DDR 之间的连接框图,可以看到红框图是用户接口,中间是 MIG 核,右边蓝框图是需要控制的 DDR 接口,用户只需要去配置红框中的接口信号,就可以对 DDR 进行控制读写等操作。

以下 MIG 对用户端的部分接口进行解释

端口名称

端口类型

端口解释

rst

输出

MIG 提供给用户端的复位信号,高有效

clk

输出

MIG 提供给用户端的时钟信号

app_addr

输入

地址总线。29 bit

app_cmd

输入

命令总线,3’b000代表写,3’b001 代表读。3bit

app_en

输入

命令使能信号,该信号有效(高电平),且 app_rdy 也有效时,MIG IP 核才可以接收到用户端发送的app_cmd 和 app_addr。1bit

app_hi_pri

输入

指令的优先级

app_wdf_data

输入

写数据总线,数据位宽为16bit,每次突发长度为8,因此数据总线位宽为128bit。

app_wdf_end

输入

最后一个写数据的标志,该信号有效(高电平)时,代表对应的

app_wdf_data 为当前写的最后一个数据。1bit ,当ui_clk的比例为4:1时,此信号和 app_wdf_wren 一致。

app_wdf_mask

输入

写数据掩码,该信号为写数据的掩码。16bit,每 1 bit对应 1 byte数据的掩码。

app_wdf_wren

输入

写数据有效标志,该信号有效(高电平),且 app_wdf_rdy 也有效时,MIG IP 核才可以接收到用户端发送的 app_wdf_data。1bit

app_rdy

输出

命令空闲信号,该信号有效(高电平),且 app_en 也有效时,MIG IP 核才可以接收到用户端发送的 app_cmd 和 app_addr指令

。1bit

app_rd_data

输出

读数据总线,数据位宽为16bit,每次突发长度为8,因此数据总线位宽为128bit。

app_rd_data_end

输出

最后一个读数据的标志,该信号有效(高电平)时,代表对应的

app_rd_data_end 为当前读的最后一个数据。1bit ,当ui_clk的比例为4:1时,此信号和 app_rd_data_valid 一致。

app_rd_data_valid

输出

读数据有效信号,此信号为高表示读出的数据有效,和读出的数据同步。1bit

app_wdf_rdy

输出

写数据空闲信号,表示 MIG 可以接收数据,该信号有效(高电平),且 app_wdf_wren 也有时, MIG IP 核才可以接收到用户端发送的 app_wdf_data。1bit

app_sr_req

输入

输入保留位,置 0

app_sr_active

输出

输出保留位

app_ref_req

输入

刷新请求信号,此信号为高表示请求向 DRAM 发出刷新命令。

app_ref_ack

输出

刷新请求确认信号,此信号为高表示内存控制器已将请求的刷新命令发送到PHY接口。

app_zq_req

输入

ZQ校准请求信号

app_zq_ack

输出

ZQ校准请求确认信号

读时序

读时序分为两种情况,在前面的写操作提到过,当 UI 速率比配置为 4:1 时,一次写入的数据为一次突发的数据;而当 UI 速率比配置为 2:1 时,一次写入的数据为一次突发的数据一半。

读操作也是如此,当 UI 速率比配置为 4:1 时,一次读出的数据为一次突发的数据,每次读操作只需要给一个初始地址。给出读指令、地址和命令握手信号后,若干个时钟周期后,数据被读出。

而当 UI 速率比配置为 2:1 时,一次读出的数据为一次突发的数据一半,每次读操作需要给两次初始地址。给出读指令、地址和命令握手信号后,若干个时钟周期后,数据被读出。

实验设计

实验设计框图如下,通过设计一个读控制模块,对 MIG IP 核进行读控制,在用户端的信号交互只需要提供读使能、地址等信号即可实现对 DDR3 进行读操作。本次实验仍可沿用之前的写控制模块。

读控制模块


  
  1. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  2. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  3. /* Engineer : Linest-5
  4. /* File : ddr3_rd.v
  5. /* Create : 2022-09-23 09:24:30
  6. /* Revise : 2022-09-23 09:24:30
  7. /* Module Name : ddr3_rd
  8. /* Description : ddr3的读操作模块
  9. /* Editor : sublime text3, tab size (4)
  10. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  11. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  12. module ddr3_rd(
  13. //时钟和复位
  14. input ui_clk, //用户时钟,由MIG提供
  15. input rst, //用户复位,高有效
  16. //user interface
  17. input init_calib_complete, //初始化完成标志信号
  18. input [ 7: 0] rd_brust_len, //突发读长度
  19. input rd_start, //开始写操作标志信号
  20. input [ 28: 0] rd_addr, //开始读操作的起始地址
  21. input [ 2: 0] rd_cmd, //读指令,3'b000为写,3'b001为读
  22. output reg [ 127: 0] rd_data, //用户端读出的数据
  23. output reg rd_data_valid, //用户端读出的数据有效信号
  24. output reg rd_end, //读操作结束标志信号
  25. //app interface
  26. input app_rdy, //MIG IP可以被下指令,ready
  27. input [ 127: 0] app_rd_data, //从DDR中读出的数据
  28. input app_rd_data_end, //从DDR中读出的数据突发中的最后一个数据,这里设置的为4:1,所以直接和app_rd_data_valid信号一直
  29. input app_rd_data_valid, //从DDR中读出的数据有效信号
  30. output [ 2: 0] app_cmd, //读写指令
  31. output reg app_en, //开始准备指令下发信号,valid信号,和 app_rdy信号形成握手信号
  32. output reg [ 28: 0] app_addr //每次读操作的地址,突发为8,即每次操作地址加8
  33. );
  34. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  35. /* 信号申明 */
  36. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  37. reg [ 7: 0] rd_brust_len_reg;
  38. reg [ 7: 0] cmd_cnt;
  39. reg [ 7: 0] data_cnt;
  40. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  41. /* Main Code */
  42. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  43. assign app_cmd = 3'b001; //3'b001表示进行读操作
  44. //对输入的突发长度进行寄存
  45. always @(posedge ui_clk or posedge rst) begin
  46. if (rst) begin
  47. rd_brust_len_reg <= 'd0;
  48. end
  49. else if (rd_start) begin
  50. rd_brust_len_reg <= rd_brust_len;
  51. end
  52. else begin
  53. rd_brust_len_reg <= rd_brust_len_reg;
  54. end
  55. end
  56. //对指令下达的次数进行计数
  57. always @(posedge ui_clk or posedge rst) begin
  58. if (rst) begin
  59. cmd_cnt <= 'd0;
  60. end
  61. else if (app_en && app_rdy && (cmd_cnt == (rd_brust_len_reg - 'd1))) begin
  62. cmd_cnt <= 'd0;
  63. end
  64. else if (app_en && app_rdy) begin
  65. cmd_cnt <= cmd_cnt + 'd1;
  66. end
  67. else begin
  68. cmd_cnt <= cmd_cnt;
  69. end
  70. end
  71. //指令使能信号
  72. always @(posedge ui_clk or posedge rst) begin
  73. if (rst) begin
  74. app_en <= 'd0;
  75. end
  76. else if (app_en && app_rdy && (cmd_cnt == (rd_brust_len_reg - 'd1))) begin //握手成功且为达到突发长度
  77. app_en <= 'd0;
  78. end
  79. else if (rd_start && init_calib_complete) begin //开始读信号拉高时将指令使能信号拉高
  80. app_en <= 'd1;
  81. end
  82. else begin
  83. app_en <= app_en;
  84. end
  85. end
  86. //读数据的地址
  87. always @(posedge ui_clk or posedge rst) begin
  88. if (rst) begin
  89. app_addr <= 'd0;
  90. end
  91. else if (rd_end) begin
  92. app_addr <= 'd0;
  93. end
  94. else if (app_en && app_rdy) begin //当指令信号握手成功,对地址累加8,因为数据位宽为128,单地址的数据位宽为16
  95. app_addr <= app_addr + 'd8;
  96. end
  97. else if (rd_start && init_calib_complete) begin //开始读信号拉高时将输入的初始地址赋值
  98. app_addr <= rd_addr;
  99. end
  100. else begin
  101. app_addr <= app_addr;
  102. end
  103. end
  104. //读数据打拍
  105. always @(posedge ui_clk or posedge rst) begin
  106. if (rst) begin
  107. rd_data <= 'd0;
  108. end
  109. else begin
  110. rd_data <= app_rd_data;
  111. end
  112. end
  113. //读数据有效信号打拍
  114. always @(posedge ui_clk or posedge rst) begin
  115. if (rst) begin
  116. rd_data_valid <= 'd0;
  117. end
  118. else begin
  119. rd_data_valid <= app_rd_data_valid;
  120. end
  121. end
  122. //读出的数据个数计数
  123. always @(posedge ui_clk or posedge rst) begin
  124. if (rst) begin
  125. data_cnt <= 'd0;
  126. end
  127. else if (app_rd_data_valid && (data_cnt == (rd_brust_len_reg - 'd1))) begin //数据有效信号为高且达到读突发长度
  128. data_cnt <= 'd0;
  129. end
  130. else if (app_rd_data_valid) begin
  131. data_cnt <= data_cnt + 'd1;
  132. end
  133. else begin
  134. data_cnt <= data_cnt;
  135. end
  136. end
  137. //读操作计数博标志信号
  138. always @(posedge ui_clk or posedge rst) begin
  139. if (rst) begin
  140. rd_end <= 'd0;
  141. end
  142. else if (app_rd_data_valid && (data_cnt == (rd_brust_len_reg - 'd1))) begin //当读出数据计数到突发数时拉高此信号
  143. rd_end <= 'd1;
  144. end
  145. else begin
  146. rd_end <= 'd0;
  147. end
  148. end
  149. endmodule

顶层模块

只需要将读控制模块例化,并且先将写模块给注释,此次实验不涉及写数据操作,只验证读操作是否能正常进行。

testbench 设计


  
  1. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  2. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  3. /* Engineer : Linest-5
  4. /* File : tb_top_ddr3_init.v
  5. /* Create : 2022-09-15 10:10:36
  6. /* Revise : 2022-09-23 10:51:59
  7. /* Module Name :
  8. /* Description :
  9. /* Editor : sublime text3, tab size (4)
  10. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  11. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  12. `timescale 1ns / 1ps
  13. module tb_top_ddr3_init();
  14. reg sys_clk;
  15. reg rst_n;
  16. wire [ 15: 0] ddr3_dq;
  17. wire [ 1: 0] ddr3_dqs_n;
  18. wire [ 1: 0] ddr3_dqs_p;
  19. wire [ 14: 0] ddr3_addr;
  20. wire [ 2: 0] ddr3_ba;
  21. wire ddr3_ras_n;
  22. wire ddr3_cas_n;
  23. wire ddr3_we_n;
  24. wire ddr3_reset_n;
  25. wire [ 0: 0] ddr3_ck_p;
  26. wire [ 0: 0] ddr3_ck_n;
  27. wire [ 0: 0] ddr3_cke;
  28. wire [ 0: 0] ddr3_cs_n;
  29. wire [ 1: 0] ddr3_dm;
  30. wire [ 0: 0] ddr3_odt;
  31. //wr_ddr
  32. // reg ui_clk;
  33. // reg rst;
  34. // reg data_req;
  35. // reg [127:0] wr_data;
  36. // reg [7:0] wr_brust_len;
  37. // reg wr_start;
  38. // reg [28:0] wr_addr;
  39. // reg [2:0] wr_cmd;
  40. //rd_ddr
  41. reg ui_clk;
  42. reg rst;
  43. reg [ 7: 0] rd_brust_len;
  44. reg [ 127: 0] rd_data;
  45. reg rd_start;
  46. reg [ 28: 0] rd_addr;
  47. reg [ 2: 0] rd_cmd;
  48. reg app_rdy;
  49. reg [ 127: 0] app_rd_data;
  50. reg app_rd_data_end;
  51. reg app_rd_data_valid;
  52. reg [ 2: 0] app_cmd;
  53. initial begin
  54. sys_clk = 'd1;
  55. rst_n <= 'd0;
  56. # 200
  57. rst_n <= 'd1;
  58. end
  59. initial begin
  60. rd_brust_len = 'd64;
  61. rd_start = 'd0;
  62. rd_addr = 'd0;
  63. rd_cmd = 3'b001;
  64. force ui_clk = inst_top_ddr3_init.inst_ddr3_rd.ui_clk;
  65. force rst = inst_top_ddr3_init.inst_ddr3_rd.rst;
  66. force rd_data = inst_top_ddr3_init.inst_ddr3_rd.rd_data;
  67. force inst_top_ddr3_init.rd_brust_len = rd_brust_len;
  68. force inst_top_ddr3_init.rd_start = rd_start;
  69. force inst_top_ddr3_init.rd_addr = rd_addr;
  70. force inst_top_ddr3_init.rd_cmd = rd_cmd;
  71. end
  72. initial begin
  73. # 100
  74. gen_cmd();
  75. end
  76. // always @(posedge ui_clk or posedge rst) begin
  77. // if (rst) begin
  78. // wr_data <= 'd0;
  79. // end
  80. // else if (wr_data == 'd63) begin
  81. // wr_data <= 'd0;
  82. // end
  83. // else if (data_req) begin
  84. // wr_data <= wr_data + 'd1;
  85. // end
  86. // else begin
  87. // wr_data <= wr_data;
  88. // end
  89. // end
  90. // initial begin
  91. // #100
  92. // gen_data();
  93. // end
  94. task gen_cmd;
  95. begin
  96. @ (negedge rst);
  97. @ (posedge ui_clk);
  98. @ (posedge ui_clk);
  99. @ (posedge ui_clk);
  100. @ (posedge ui_clk);
  101. @ (posedge ui_clk);
  102. rd_start = 'd1;
  103. @ (posedge ui_clk);
  104. rd_start = 'd0;
  105. end
  106. endtask
  107. // task gen_data;
  108. // integer i;
  109. // begin
  110. // @ (posedge data_req);
  111. // for (i=0;i<64;i=i+1) begin
  112. // wr_data = {96'd0,i[31:0]};
  113. // @ (posedge ui_clk);
  114. // if (data_req == 'd0) begin
  115. // i = i - 1;
  116. // end
  117. // end
  118. // wr_data = 'd0;
  119. // @ (posedge ui_clk);
  120. // end
  121. // endtask
  122. always # 10 sys_clk = ~sys_clk;
  123. top_ddr3_init inst_top_ddr3_init (
  124. .ddr3_dq (ddr3_dq),
  125. .ddr3_dqs_n (ddr3_dqs_n),
  126. .ddr3_dqs_p (ddr3_dqs_p),
  127. .ddr3_addr (ddr3_addr),
  128. .ddr3_ba (ddr3_ba),
  129. .ddr3_ras_n (ddr3_ras_n),
  130. .ddr3_cas_n (ddr3_cas_n),
  131. .ddr3_we_n (ddr3_we_n),
  132. .ddr3_reset_n (ddr3_reset_n),
  133. .ddr3_ck_p (ddr3_ck_p),
  134. .ddr3_ck_n (ddr3_ck_n),
  135. .ddr3_cke (ddr3_cke),
  136. .ddr3_cs_n (ddr3_cs_n),
  137. .ddr3_dm (ddr3_dm),
  138. .ddr3_odt (ddr3_odt),
  139. .sys_clk (sys_clk),
  140. .rst_n (rst_n)
  141. );
  142. ddr3_model u_comp_ddr3 (
  143. .rst_n (ddr3_reset_n),
  144. .ck (ddr3_ck_p),
  145. .ck_n (ddr3_ck_n),
  146. .cke (ddr3_cke),
  147. .cs_n (ddr3_cs_n),
  148. .ras_n (ddr3_ras_n),
  149. .cas_n (ddr3_cas_n),
  150. .we_n (ddr3_we_n),
  151. .dm_tdqs ({ddr3_dm[ 1],ddr3_dm[ 0]}),
  152. .ba (ddr3_ba),
  153. .addr (ddr3_addr),
  154. .dq (ddr3_dq[ 15: 0]),
  155. .dqs ({ddr3_dqs_p[ 1],
  156. ddr3_dqs_p[ 0]}),
  157. .dqs_n ({ddr3_dqs_n[ 1],
  158. ddr3_dqs_n[ 0]}),
  159. .tdqs_n (),
  160. .odt (ddr3_odt)
  161. );
  162. endmodule

仿真波形

从下图可以看出,在初始化完成标志信号拉高后,给出开始读操作标志信号,设定读突发长度为64,随后若干个时钟周期后,数据被读出。

由于事先将数据写入至 DDR 中,因此读出的数据都为xxxx,但是可以验证读出的逻辑设计是没问题的。

下图为控制台的打印信息,可以看到依次按地址读出数据,如果先将数据写入,数据就会被正常读出。在下一篇完成完整的读写操作。


汇总篇 

 本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。

【DDR3 控制器设计】系列博客汇总篇(附直达链接)


转载:https://blog.csdn.net/m0_61298445/article/details/127500547
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场