当前位置:   article > 正文

FPGA实现DDR3读写操作,乒乓操作——FPGA学习笔记1_fpga ddr3

fpga ddr3

前言笔者:人生建议从第四章开始看。。。。

一、初认SDRAM

物理 Bank:传统内存系统为了保证 CPU 的正常工作,必须一次传输完 CPU 在一个传输周期内所需的数据。而CPU 在一个传输周期能接受的数据容量就是 CPU 数据总线的位宽当时控制内存与 CPU之间数据交换的北桥芯片也因此将内存总线的数据单位是 bit (位)位宽等同于 CPU 数据总线的位宽,而这个位宽就称之为物理 Bank (Physical Bank) 的位宽。

芯片位宽:每一片SDRAM缓存芯片本身的位宽。

CPU需要多少位宽数据,SDRAM就要提供多少位宽数据,位宽不够使用多片SDRAM级联。、


二、SDRAM操作时序

        1、SDRAM操作指令

CS#:片选  RAS#:行选通  CAS#:列选通  WE#:读写切换  DQM:数据掩码  ADDR:数据总线  DQ:数据  Notes:参考详细说明

        2、指令时序        

                (1)ACTIVE Command(行激活指令)

                (2)READ Command(读,列激活)

                (3)WRITE Command(写,列激活)

                (4)PRECHARGE Command(手动预充电)

                (5)初始化时序,加载模式寄存器

                (6)配置模式寄存器

A0~2:突发模式        A3:突发类型        A4~6:CL延迟        

                    (7)突发读时序

                (8)连续发送读指令时序

                (9)突发写时序

                (10)连续写时序

                (11)禁止指令

三、DDR2 SDRAM

        1、OCD校准

        2、前置CAS、附加潜伏期、写入潜伏期

四、DDR3 SDRAM

        1、与DDR2的区别之处

                (1)突发长度

                (2)寻址时序

                (3)新增重置功能

                (4)新增ZQ校准功能

                (5)参考电压分成两个

                (6)点对点连接

        2、DDR3硬件设计与时序

DDR3

        3、初始化时序

        (1)上电一瞬间RESET拉低保持200us(再次期间其他信号无效)。

        (2)RESET拉高前,CKE至少保持10ns拉低。

        (3)至少等待500us后(期间CK差分始终提前CKSRX时间稳定),CKE拉高。

        (4)CKE拉高后,等待tls时间,后发送NOP指令,继续等待tXPR时间。(期间ODT处于工作状态)

        (5)发送MRS配置指令,每配置一个等待tXPR时间,MRX寄存器配置完成等待tDLIK=tMOD+tZQinit时间。

        (6)在tDLIK等待时间内,发送ZQCL指令,以及NOP指令。到此整个DDR激活结束。

        4、DDR3模式寄存器配置

                (1)MR0寄存器

BL:突发长度        CL:等待        RBT:读的突发类型        CAS Latency:延时配置

TM:工作模式(常规/测试)        DLL:重置(back返回值归零)

WR:写复原(重新写回原本数据)        PPD:退出速度( )

                (2)MR1寄存器

DLL:使能(必须使能DDL才可以工作)        D.I.C:输出驱动阻抗        Rtt_Nom:终止值

AL:加性延迟        Level:写均衡       

TDQS:终止数据频闪(TDQS功能仅在x8 DDR3(L)SDRAM中可用,对于x16配置必须通过MR1中的模式寄存器A11=0禁用)

                (3)MR2寄存器

 PASR:默认关闭        CWL:CAS写延迟        ASR:自动刷新        SRT:自刷新温度

Rtt_WR:动态ODT        

                (4)MR3寄存器

五、DDR3读写操作时序

        1、读时序

                (1)突发长度:8

                (2)突发长度:4

                (3)突发长度:8和4混用

                (4)读后跟写 

        2、写时序

                (1)写数据

        注意延迟时间!!

                (2)写后跟读 

六、DDR3读写时序程序设计

        1、程序框图

        2、MIG IP核内部原理

                (1)DDR3接口说明

ddr_ad_dr:地址线        ddr_ba:bank地址线        ddr_cas_n:指令线        ddr_ck:差分时钟线

ddr_ck:类似使能线        ddr_cs_n:片选        ddr_dm:数据屏蔽        ddr_o_dt:ODT校准

ddr_parity:(未使用到)

                (2)MIG IP核用户接口说明

app_addr:用户地址输入        app_cmd:读写控制命令        app_en:命令写入使能,高有效

app_hi_pri:改变优先级        app_wdf_data:用户写入数据

app_wdf_end:当前时钟突发写最后一个时钟,高有效

app_wdf_wren:数据写使能,高有效        app_rdy:读写命令接受准备完毕,高有效

app_wdf_rdy:数据接收准备完毕,高有效

        3、用户接口写命令时序

                (1)等待app_rdy拉高(表示可以接受数据)

                (2)app_cmd给WRITE指令,同时app_addr给地址(DDR3地址),app_en同步拉高

        4、用户接口读命令时序

                与写时序不同之处,app_rd_data_valid拉高是数据有效

        5、vivado下创建MIG IP核

        6、代码讲解(参考正点原子DDR3代码)

        先往DDR3 的若干连续地址中分别写入数据,再读出来进行比较

                (1)ddr3_rw_top

  1. module ddr3_rw_top(
  2. input sys_clk, //系统时钟
  3. input sys_rst_n, //复位,低有效
  4. // DDR3
  5. inout [15:0] ddr3_dq, //DDR3 数据
  6. inout [1:0] ddr3_dqs_n, //DDR3 dqs负
  7. inout [1:0] ddr3_dqs_p, //DDR3 dqs正
  8. output [13:0] ddr3_addr, //DDR3 地址
  9. output [2:0] ddr3_ba, //DDR3 banck 选择
  10. output ddr3_ras_n, //DDR3 行选择
  11. output ddr3_cas_n, //DDR3 列选择
  12. output ddr3_we_n, //DDR3 读写选择
  13. output ddr3_reset_n, //DDR3 复位
  14. output [0:0] ddr3_ck_p, //DDR3 时钟正
  15. output [0:0] ddr3_ck_n, //DDR3 时钟负
  16. output [0:0] ddr3_cke, //DDR3 时钟使能
  17. output [0:0] ddr3_cs_n, //DDR3 片选
  18. output [1:0] ddr3_dm, //DDR3_dm
  19. output [0:0] ddr3_odt, //DDR3_odt
  20. //用户
  21. output led //错误指示信号
  22. );
  23. //wire define
  24. wire clk_330;
  25. wire error_flag;
  26. wire ui_clk ; //用户时钟
  27. wire [27:0] app_addr; //DDR3 地址
  28. wire [2:0] app_cmd; //用户读写命令
  29. wire app_en; //MIG IP核使能
  30. wire app_rdy; //MIG IP核空闲
  31. wire [127:0] app_rd_data; //用户读数据
  32. wire app_rd_data_end; //突发读当前时钟最后一个数据
  33. wire app_rd_data_valid; //读数据有效
  34. wire [127:0] app_wdf_data; //用户写数据
  35. wire app_wdf_end; //突发写当前时钟最后一个数据
  36. wire [15:0] app_wdf_mask; //写数据屏蔽
  37. wire app_wdf_rdy; //写空闲
  38. wire app_sr_active; //保留
  39. wire app_ref_ack; //刷新请求
  40. wire app_zq_ack; //ZQ 校准请求
  41. wire app_wdf_wren; //DDR3 写使能
  42. wire locked; //锁相环频率稳定标志
  43. wire clk_ref_i; //DDR3参考时钟
  44. wire sys_clk_i; //MIG IP核输入时钟
  45. wire clk_200; //200M时钟
  46. wire ui_clk_sync_rst; //用户复位信号
  47. wire init_calib_complete; //校准完成信号
  48. wire [20:0] rd_cnt; //实际读地址计数
  49. wire [1 :0] state; //状态计数器
  50. wire [23:0] rd_addr_cnt; //用户读地址计数器
  51. wire [23:0] wr_addr_cnt; //用户写地址计数器
  52. //*****************************************************
  53. //** main code
  54. //*****************************************************
  55. //读写模块
  56. ddr3_rw u_ddr3_rw(
  57. .ui_clk (ui_clk),
  58. .ui_clk_sync_rst (ui_clk_sync_rst),
  59. .init_calib_complete (init_calib_complete),
  60. .app_rdy (app_rdy),
  61. .app_wdf_rdy (app_wdf_rdy),
  62. .app_rd_data_valid (app_rd_data_valid),
  63. .app_rd_data (app_rd_data),
  64. .app_addr (app_addr),
  65. .app_en (app_en),
  66. .app_wdf_wren (app_wdf_wren),
  67. .app_wdf_end (app_wdf_end),
  68. .app_cmd (app_cmd),
  69. .app_wdf_data (app_wdf_data),
  70. .state (state),
  71. .rd_addr_cnt (rd_addr_cnt),
  72. .wr_addr_cnt (wr_addr_cnt),
  73. .rd_cnt (rd_cnt),
  74. .error_flag (error_flag),
  75. .led (led)
  76. );
  77. //MIG IP核模块
  78. mig_7series_0 u_mig_7series_0 (
  79. // Memory interface ports
  80. .ddr3_addr (ddr3_addr), // output [14:0] ddr3_addr
  81. .ddr3_ba (ddr3_ba), // output [2:0] ddr3_ba
  82. .ddr3_cas_n (ddr3_cas_n), // output ddr3_cas_n
  83. .ddr3_ck_n (ddr3_ck_n), // output [0:0] ddr3_ck_n
  84. .ddr3_ck_p (ddr3_ck_p), // output [0:0] ddr3_ck_p
  85. .ddr3_cke (ddr3_cke), // output [0:0] ddr3_cke
  86. .ddr3_ras_n (ddr3_ras_n), // output ddr3_ras_n
  87. .ddr3_reset_n (ddr3_reset_n),// output ddr3_reset_n
  88. .ddr3_we_n (ddr3_we_n), // output ddr3_we_n
  89. .ddr3_dq (ddr3_dq), // inout [31:0] ddr3_dq
  90. .ddr3_dqs_n (ddr3_dqs_n), // inout [3:0] ddr3_dqs_n
  91. .ddr3_dqs_p (ddr3_dqs_p), // inout [3:0] ddr3_dqs_p
  92. .init_calib_complete (init_calib_complete),
  93. // init_calib_complete
  94. .ddr3_cs_n (ddr3_cs_n), // output [0:0] ddr3_cs_n
  95. .ddr3_dm (ddr3_dm), // output [3:0] ddr3_dm
  96. .ddr3_odt (ddr3_odt), // output [0:0] ddr3_odt
  97. // Application interface ports
  98. .app_addr (app_addr), // input [28:0] app_addr
  99. .app_cmd (app_cmd), // input [2:0] app_cmd
  100. .app_en (app_en), // input app_en
  101. .app_wdf_data (app_wdf_data),// input [255:0] app_wdf_data
  102. .app_wdf_end (app_wdf_end), // input app_wdf_end
  103. .app_wdf_wren (app_wdf_wren),// input app_wdf_wren
  104. .app_rd_data (app_rd_data), // output [255:0]app_rd_data
  105. .app_rd_data_end (app_rd_data_end),
  106. // output app_rd_data_end
  107. .app_rd_data_valid (app_rd_data_valid),
  108. // output app_rd_data_valid
  109. .app_rdy (app_rdy), // output app_rdy
  110. .app_wdf_rdy (app_wdf_rdy), // output app_wdf_rdy
  111. .app_sr_req (), // input app_sr_req
  112. .app_ref_req (), // input app_ref_req
  113. .app_zq_req (), // input app_zq_req
  114. .app_sr_active (app_sr_active),// output app_sr_active
  115. .app_ref_ack (app_ref_ack), // output app_ref_ack
  116. .app_zq_ack (app_zq_ack), // output app_zq_ack
  117. .ui_clk (ui_clk), // output ui_clk
  118. .ui_clk_sync_rst (ui_clk_sync_rst),
  119. // output ui_clk_sync_rst
  120. .app_wdf_mask (31'b0), // input [31:0] app_wdf_mask
  121. // System Clock Ports
  122. .sys_clk_i (clk_200),
  123. // Reference Clock Ports
  124. .clk_ref_i (clk_200),
  125. .sys_rst (sys_rst_n) // input sys_rst
  126. );
  127. //PLL模块
  128. clk_wiz_0 u_clk_wiz_0
  129. (
  130. // Clock out ports
  131. .clk_out1(clk_200), // output clk_out1
  132. // Status and control signals
  133. .reset(1'b0), // input resetn
  134. .locked(locked), // output locked
  135. // Clock in ports
  136. .clk_in1(sys_clk)
  137. ); // input clk_in1
  138. endmodule
                         ①PLL模块

        产生200Mhz时钟给DDR3 IP核使用(FPGA内部使用,并非给到DDR3颗粒)。MIG IP核内部会自己进行倍频到400MHz(ddr3_ck_n/p)给到DDR3颗粒使用,同时会降频到100MHz(ui_clk)给到DDR3读写模块用户时钟使用。当100MHz进行DDR读写上升沿都进行,速度提升一倍(2倍)。又因为DDR3颗粒时钟为400MHz,所以实际读写速度再翻4倍(8倍),由此实现16bit位宽达到16x8bit的吞吐速度(八倍预取)。

                        ②MIG IP核模块

        注意复位是高电平有效,使用过程可能需要取反。

                (2)DDR3读写模块

  1. module ddr3_rw (
  2. input ui_clk, //用户时钟
  3. input ui_clk_sync_rst, //复位,高有效
  4. input init_calib_complete, //DDR3初始化完成
  5. input app_rdy, //MIG 命令接收准备好标致
  6. input app_wdf_rdy, //MIG数据接收准备好
  7. input app_rd_data_valid, //读数据有效
  8. input [127:0] app_rd_data, //用户读数据
  9. output reg [27:0] app_addr, //DDR3地址
  10. output app_en, //MIG IP发送命令使能
  11. output app_wdf_wren, //用户写数据使能
  12. output app_wdf_end, //突发写当前时钟最后一个数据
  13. output [2:0] app_cmd, //MIG IP核操作命令,读或者写
  14. output reg [127:0] app_wdf_data, //用户写数据
  15. output reg [1 :0] state, //读写状态
  16. output reg [23:0] rd_addr_cnt, //用户读地址计数
  17. output reg [23:0] wr_addr_cnt, //用户写地址计数
  18. output reg [20:0] rd_cnt, //实际读地址标记
  19. output reg error_flag, //读写错误标志
  20. output reg led //读写测试结果指示灯
  21. );
  22. //parameter define
  23. parameter TEST_LENGTH = 1000;
  24. parameter L_TIME = 25'd25_000_000;
  25. parameter IDLE = 2'd0; //空闲状态
  26. parameter WRITE = 2'd1; //写状态
  27. parameter WAIT = 2'd2; //读到写过度等待
  28. parameter READ = 2'd3; //读状态
  29. //reg define
  30. reg [24:0] led_cnt; //led计数
  31. //wire define
  32. wire error; //读写错误标记
  33. wire rst_n; //复位,低有效
  34. //*****************************************************
  35. //** main code
  36. //*****************************************************
  37. assign rst_n = ~ui_clk_sync_rst;
  38. //读信号有效,且读出的数不是写入的数时,将错误标志位拉高
  39. assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data));
  40. //在写状态MIG IP 命令接收和数据接收都准备好,或者在读状态命令接收准备好,此时拉高使能信号,
  41. assign app_en = ((state == WRITE && (app_rdy && app_wdf_rdy))
  42. ||(state == READ && app_rdy)) ? 1'b1:1'b0;
  43. //在写状态,命令接收和数据接收都准备好,此时拉高写使能
  44. assign app_wdf_wren = (state == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
  45. //由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
  46. assign app_wdf_end = app_wdf_wren;
  47. //处于读的时候命令值为1,其他时候命令值为0
  48. assign app_cmd = (state == READ) ? 3'd1 :3'd0;
  49. //DDR3读写逻辑实现
  50. always @(posedge ui_clk or negedge rst_n) begin
  51. if((~rst_n)||(error_flag)) begin
  52. state <= IDLE;
  53. app_wdf_data <= 128'd0;
  54. wr_addr_cnt <= 24'd0;
  55. rd_addr_cnt <= 24'd0;
  56. app_addr <= 28'd0;
  57. end
  58. else if(init_calib_complete)begin //MIG IP核初始化完成
  59. case(state)
  60. IDLE:begin
  61. state <= WRITE;
  62. app_wdf_data <= 128'd0;
  63. wr_addr_cnt <= 24'd0;
  64. rd_addr_cnt <= 24'd0;
  65. app_addr <= 28'd0;
  66. end
  67. WRITE:begin
  68. if(wr_addr_cnt == TEST_LENGTH - 1 &&(app_rdy && app_wdf_rdy))
  69. state <= WAIT; //写到设定的长度跳到等待状态
  70. else if(app_rdy && app_wdf_rdy)begin //写条件满足
  71. app_wdf_data <= app_wdf_data + 1; //写数据自加
  72. wr_addr_cnt <= wr_addr_cnt + 1; //写地址自加
  73. app_addr <= app_addr + 8; //DDR3 地址加8
  74. end
  75. else begin //写条件不满足,保持当前值
  76. app_wdf_data <= app_wdf_data;
  77. wr_addr_cnt <= wr_addr_cnt;
  78. app_addr <= app_addr;
  79. end
  80. end
  81. WAIT:begin
  82. state <= READ; //下一个时钟,跳到读状态
  83. rd_addr_cnt <= 24'd0; //读地址复位
  84. app_addr <= 28'd0; //DDR3读从地址0开始
  85. end
  86. READ:begin //读到设定的地址长度
  87. if(rd_addr_cnt == TEST_LENGTH - 1 && app_rdy)
  88. state <= IDLE; //则跳到空闲状态
  89. else if(app_rdy)begin //若MIG已经准备好,则开始读
  90. rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址每次加一
  91. app_addr <= app_addr + 8; //DDR3地址加8
  92. end
  93. else begin //若MIG没准备好,则保持原值
  94. rd_addr_cnt <= rd_addr_cnt;
  95. app_addr <= app_addr;
  96. end
  97. end
  98. default:begin
  99. state <= IDLE;
  100. app_wdf_data <= 128'd0;
  101. wr_addr_cnt <= 24'd0;
  102. rd_addr_cnt <= 24'd0;
  103. app_addr <= 28'd0;
  104. end
  105. endcase
  106. end
  107. end
  108. //对DDR3实际读数据个数编号计数
  109. always @(posedge ui_clk or negedge rst_n) begin
  110. if(~rst_n)
  111. rd_cnt <= 0; //若计数到读写长度,且读有效,地址计数器则置0
  112. else if(app_rd_data_valid && rd_cnt == TEST_LENGTH - 1)
  113. rd_cnt <= 0; //其他条件只要读有效,每个时钟自增1
  114. else if (app_rd_data_valid )
  115. rd_cnt <= rd_cnt + 1;
  116. end
  117. //寄存状态标志位
  118. always @(posedge ui_clk or negedge rst_n) begin
  119. if(~rst_n)
  120. error_flag <= 0;
  121. else if(error)
  122. error_flag <= 1;
  123. end
  124. //led指示效果控制
  125. always @(posedge ui_clk or negedge rst_n) begin
  126. if((~rst_n) || (~init_calib_complete )) begin
  127. led_cnt <= 25'd0;
  128. led <= 1'b0;
  129. end
  130. else begin
  131. if(~error_flag) //读写测试正确
  132. led <= 1'b1; //led灯常亮
  133. else begin //读写测试错误
  134. led_cnt <= led_cnt + 25'd1;
  135. if(led_cnt == L_TIME - 1'b1) begin
  136. led_cnt <= 25'd0;
  137. led <= ~led; //led灯闪烁
  138. end
  139. end
  140. end
  141. end
  142. endmodule

七、DDR3乒乓操作

        1、乒乓操作简介

        外部输入数据流通过输入数据流选择单元将数据流输入到数据缓存模块,比较常用的存储单元有双口RAM,FIFO,SDRAM等。在第一个缓冲周期,数据流通过“输入数据流选择单元”将数据写入“数据缓冲模块1”。写完之后进入第二个缓冲周期,在第二个缓冲周期数据流通过“输入数 据流选择单元”将数据写入到“数据缓冲模块2”的同时“输出数据流选择单元”将“数据缓冲模块1”的数据流读出,此时进入第三个缓冲周期。在第三个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓存模块1”的同时将“数据缓冲模块2”的数据读出。如此反复循环地操作,即为乒乓操作。

        2、乒乓操作特点

        乒乓操作的最大特点是通过“输入数据流选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿的,因此非常适合对数据流进行流水线 式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。

         乒乓操作的第二个特点是可以节约缓存空间,使用双存储单元比单存储单元更节省存储空间,这是很明显的。同时在某些数据处理时,必须要数据达到一定个数才能进行运算,故还可以达到数据缓存的目的。 乒乓操作还可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。

        3、面积与速度互换原则

        例如设置写入的数据位宽为8位,时钟频率50MHz。读出的数据位宽为16位,时钟频率25MHz,深度都设置为128。当然大家也可自行设置时钟频率与数据位宽,只要频率与位宽的乘积相等即可。

        4、乒乓状态机

IDLE:初始状态,在不工作或复位时就让状态机置为初始状态。

WRAM1:写RAM1状态。该状态我们开始往RAM1中写入数据,此时由于RAM2中并没有写入数据,所以我们不用对RAM2进行读取。那什么时候跳转到这个状态呢?从前面的数据生成模块中我们可知,当输入数据使能为高时,数据有效开始传输,所以当数据使能为高时我们让状态跳转到写RAM1状态,在该状态下将第一个 数据包(8’d0~8’d99)写入RAM1之中。

WRAM2_RRAM1:写RAM2读RAM1状态,当第一包数据写入完毕之后,马上跳到该状态,将第二包数据写入到RAM2中的同时读出RAM1中的写入的第一包数据。当第二包数据写完之后,我们的第一包数据应该也是刚好读完的,此时我们跳转到下一状态。

WRAM1_RRAM2:写RAM1读RAM2状态。在该状态下我们开始向RAM1中写入第三包数据,此时第三包数据会把第一包数据覆盖,而我们的第一包数据已经读取出来了,并不会使数据丢失。在往RAM1中写入第三包数据的同时,我们读出RAM2中的第二包数据,当读写完成之后,跳回WRAM2_RRAM1状态开始 下一包的数据写入以及读取,如此循环我们就能无缝地将连续的输入数据读取出来了。

5、RAM写入波形

ram1_wr_en:ram1写使能,初始值为0。

ram1_wr_addr:ram1写地址,初使值为0。

ram1_wr_data:ram1写数据。

6、RAM读出时序

ram1_rd_en:ram1读使能,初始值为0。

ram1_rd_addr:ram1读地址,初使值为0。

ram1_rd_data:ram1读数据。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/539373
推荐阅读
相关标签
  

闽ICP备14008679号