赞
踩
前言笔者:人生建议从第四章开始看。。。。
物理 Bank:传统内存系统为了保证 CPU 的正常工作,必须一次传输完 CPU 在一个传输周期内所需的数据。而CPU 在一个传输周期能接受的数据容量就是 CPU 数据总线的位宽当时控制内存与 CPU之间数据交换的北桥芯片也因此将内存总线的数据单位是 bit (位)位宽等同于 CPU 数据总线的位宽,而这个位宽就称之为物理 Bank (Physical Bank) 的位宽。
芯片位宽:每一片SDRAM缓存芯片本身的位宽。
CPU需要多少位宽数据,SDRAM就要提供多少位宽数据,位宽不够使用多片SDRAM级联。、
CS#:片选 RAS#:行选通 CAS#:列选通 WE#:读写切换 DQM:数据掩码 ADDR:数据总线 DQ:数据 Notes:参考详细说明
A0~2:突发模式 A3:突发类型 A4~6:CL延迟
(6)点对点连接
DDR3
(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激活结束。
BL:突发长度 CL:等待 RBT:读的突发类型 CAS Latency:延时配置
TM:工作模式(常规/测试) DLL:重置(back返回值归零)
WR:写复原(重新写回原本数据) PPD:退出速度( )
DLL:使能(必须使能DDL才可以工作) D.I.C:输出驱动阻抗 Rtt_Nom:终止值
AL:加性延迟 Level:写均衡
TDQS:终止数据频闪(TDQS功能仅在x8 DDR3(L)SDRAM中可用,对于x16配置必须通过MR1中的模式寄存器A11=0禁用)
PASR:默认关闭 CWL:CAS写延迟 ASR:自动刷新 SRT:自刷新温度
Rtt_WR:动态ODT
注意延迟时间!!
ddr_ad_dr:地址线 ddr_ba:bank地址线 ddr_cas_n:指令线 ddr_ck:差分时钟线
ddr_ck:类似使能线 ddr_cs_n:片选 ddr_dm:数据屏蔽 ddr_o_dt:ODT校准
ddr_parity:(未使用到)
app_addr:用户地址输入 app_cmd:读写控制命令 app_en:命令写入使能,高有效
app_hi_pri:改变优先级 app_wdf_data:用户写入数据
app_wdf_end:当前时钟突发写最后一个时钟,高有效
app_wdf_wren:数据写使能,高有效 app_rdy:读写命令接受准备完毕,高有效
app_wdf_rdy:数据接收准备完毕,高有效
(1)等待app_rdy拉高(表示可以接受数据)
(2)app_cmd给WRITE指令,同时app_addr给地址(DDR3地址),app_en同步拉高
与写时序不同之处,app_rd_data_valid拉高是数据有效
先往DDR3 的若干连续地址中分别写入数据,再读出来进行比较
- module ddr3_rw_top(
- input sys_clk, //系统时钟
- input sys_rst_n, //复位,低有效
- // DDR3
- inout [15:0] ddr3_dq, //DDR3 数据
- inout [1:0] ddr3_dqs_n, //DDR3 dqs负
- inout [1:0] ddr3_dqs_p, //DDR3 dqs正
- output [13:0] ddr3_addr, //DDR3 地址
- output [2:0] ddr3_ba, //DDR3 banck 选择
- output ddr3_ras_n, //DDR3 行选择
- output ddr3_cas_n, //DDR3 列选择
- output ddr3_we_n, //DDR3 读写选择
- output ddr3_reset_n, //DDR3 复位
- output [0:0] ddr3_ck_p, //DDR3 时钟正
- output [0:0] ddr3_ck_n, //DDR3 时钟负
- output [0:0] ddr3_cke, //DDR3 时钟使能
- output [0:0] ddr3_cs_n, //DDR3 片选
- output [1:0] ddr3_dm, //DDR3_dm
- output [0:0] ddr3_odt, //DDR3_odt
- //用户
- output led //错误指示信号
- );
-
- //wire define
- wire clk_330;
- wire error_flag;
- wire ui_clk ; //用户时钟
- wire [27:0] app_addr; //DDR3 地址
- wire [2:0] app_cmd; //用户读写命令
- wire app_en; //MIG IP核使能
- wire app_rdy; //MIG IP核空闲
- wire [127:0] app_rd_data; //用户读数据
- wire app_rd_data_end; //突发读当前时钟最后一个数据
- wire app_rd_data_valid; //读数据有效
- wire [127:0] app_wdf_data; //用户写数据
- wire app_wdf_end; //突发写当前时钟最后一个数据
- wire [15:0] app_wdf_mask; //写数据屏蔽
- wire app_wdf_rdy; //写空闲
- wire app_sr_active; //保留
- wire app_ref_ack; //刷新请求
- wire app_zq_ack; //ZQ 校准请求
- wire app_wdf_wren; //DDR3 写使能
- wire locked; //锁相环频率稳定标志
- wire clk_ref_i; //DDR3参考时钟
- wire sys_clk_i; //MIG IP核输入时钟
- wire clk_200; //200M时钟
- wire ui_clk_sync_rst; //用户复位信号
- wire init_calib_complete; //校准完成信号
- wire [20:0] rd_cnt; //实际读地址计数
- wire [1 :0] state; //状态计数器
- wire [23:0] rd_addr_cnt; //用户读地址计数器
- wire [23:0] wr_addr_cnt; //用户写地址计数器
-
- //*****************************************************
- //** main code
- //*****************************************************
-
- //读写模块
- ddr3_rw u_ddr3_rw(
- .ui_clk (ui_clk),
- .ui_clk_sync_rst (ui_clk_sync_rst),
- .init_calib_complete (init_calib_complete),
- .app_rdy (app_rdy),
- .app_wdf_rdy (app_wdf_rdy),
- .app_rd_data_valid (app_rd_data_valid),
- .app_rd_data (app_rd_data),
-
- .app_addr (app_addr),
- .app_en (app_en),
- .app_wdf_wren (app_wdf_wren),
- .app_wdf_end (app_wdf_end),
- .app_cmd (app_cmd),
- .app_wdf_data (app_wdf_data),
- .state (state),
- .rd_addr_cnt (rd_addr_cnt),
- .wr_addr_cnt (wr_addr_cnt),
- .rd_cnt (rd_cnt),
-
- .error_flag (error_flag),
- .led (led)
- );
-
- //MIG IP核模块
- mig_7series_0 u_mig_7series_0 (
- // 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 [31:0] ddr3_dq
- .ddr3_dqs_n (ddr3_dqs_n), // inout [3:0] ddr3_dqs_n
- .ddr3_dqs_p (ddr3_dqs_p), // inout [3:0] ddr3_dqs_p
- .init_calib_complete (init_calib_complete),
- // init_calib_complete
- .ddr3_cs_n (ddr3_cs_n), // output [0:0] ddr3_cs_n
- .ddr3_dm (ddr3_dm), // output [3: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 [255:0] app_wdf_data
- .app_wdf_end (app_wdf_end), // input app_wdf_end
- .app_wdf_wren (app_wdf_wren),// input app_wdf_wren
- .app_rd_data (app_rd_data), // output [255: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 (), // input app_sr_req
- .app_ref_req (), // input app_ref_req
- .app_zq_req (), // 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 (31'b0), // input [31:0] app_wdf_mask
- // System Clock Ports
- .sys_clk_i (clk_200),
- // Reference Clock Ports
- .clk_ref_i (clk_200),
- .sys_rst (sys_rst_n) // input sys_rst
- );
-
- //PLL模块
- clk_wiz_0 u_clk_wiz_0
- (
- // Clock out ports
- .clk_out1(clk_200), // output clk_out1
- // Status and control signals
- .reset(1'b0), // input resetn
- .locked(locked), // output locked
- // Clock in ports
- .clk_in1(sys_clk)
- ); // input clk_in1
-
- endmodule
产生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的吞吐速度(八倍预取)。
注意复位是高电平有效,使用过程可能需要取反。
- module ddr3_rw (
- input ui_clk, //用户时钟
- input ui_clk_sync_rst, //复位,高有效
- input init_calib_complete, //DDR3初始化完成
- input app_rdy, //MIG 命令接收准备好标致
- input app_wdf_rdy, //MIG数据接收准备好
- input app_rd_data_valid, //读数据有效
- input [127:0] app_rd_data, //用户读数据
- output reg [27:0] app_addr, //DDR3地址
- output app_en, //MIG IP发送命令使能
- output app_wdf_wren, //用户写数据使能
- output app_wdf_end, //突发写当前时钟最后一个数据
- output [2:0] app_cmd, //MIG IP核操作命令,读或者写
- output reg [127:0] app_wdf_data, //用户写数据
- output reg [1 :0] state, //读写状态
- output reg [23:0] rd_addr_cnt, //用户读地址计数
- output reg [23:0] wr_addr_cnt, //用户写地址计数
- output reg [20:0] rd_cnt, //实际读地址标记
- output reg error_flag, //读写错误标志
- output reg led //读写测试结果指示灯
- );
-
- //parameter define
- parameter TEST_LENGTH = 1000;
- parameter L_TIME = 25'd25_000_000;
- parameter IDLE = 2'd0; //空闲状态
- parameter WRITE = 2'd1; //写状态
- parameter WAIT = 2'd2; //读到写过度等待
- parameter READ = 2'd3; //读状态
-
- //reg define
- reg [24:0] led_cnt; //led计数
-
- //wire define
- wire error; //读写错误标记
- wire rst_n; //复位,低有效
-
- //*****************************************************
- //** main code
- //*****************************************************
-
- assign rst_n = ~ui_clk_sync_rst;
- //读信号有效,且读出的数不是写入的数时,将错误标志位拉高
- assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data));
-
- //在写状态MIG IP 命令接收和数据接收都准备好,或者在读状态命令接收准备好,此时拉高使能信号,
- assign app_en = ((state == WRITE && (app_rdy && app_wdf_rdy))
- ||(state == READ && app_rdy)) ? 1'b1:1'b0;
-
- //在写状态,命令接收和数据接收都准备好,此时拉高写使能
- assign app_wdf_wren = (state == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
-
- //由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
- assign app_wdf_end = app_wdf_wren;
-
- //处于读的时候命令值为1,其他时候命令值为0
- assign app_cmd = (state == READ) ? 3'd1 :3'd0;
-
- //DDR3读写逻辑实现
- always @(posedge ui_clk or negedge rst_n) begin
- if((~rst_n)||(error_flag)) begin
- state <= IDLE;
- app_wdf_data <= 128'd0;
- wr_addr_cnt <= 24'd0;
- rd_addr_cnt <= 24'd0;
- app_addr <= 28'd0;
- end
- else if(init_calib_complete)begin //MIG IP核初始化完成
- case(state)
- IDLE:begin
- state <= WRITE;
- app_wdf_data <= 128'd0;
- wr_addr_cnt <= 24'd0;
- rd_addr_cnt <= 24'd0;
- app_addr <= 28'd0;
- end
- WRITE:begin
- if(wr_addr_cnt == TEST_LENGTH - 1 &&(app_rdy && app_wdf_rdy))
- state <= WAIT; //写到设定的长度跳到等待状态
- else if(app_rdy && app_wdf_rdy)begin //写条件满足
- app_wdf_data <= app_wdf_data + 1; //写数据自加
- wr_addr_cnt <= wr_addr_cnt + 1; //写地址自加
- app_addr <= app_addr + 8; //DDR3 地址加8
- end
- else begin //写条件不满足,保持当前值
- app_wdf_data <= app_wdf_data;
- wr_addr_cnt <= wr_addr_cnt;
- app_addr <= app_addr;
- end
- end
- WAIT:begin
- state <= READ; //下一个时钟,跳到读状态
- rd_addr_cnt <= 24'd0; //读地址复位
- app_addr <= 28'd0; //DDR3读从地址0开始
- end
- READ:begin //读到设定的地址长度
- if(rd_addr_cnt == TEST_LENGTH - 1 && app_rdy)
- state <= IDLE; //则跳到空闲状态
- else if(app_rdy)begin //若MIG已经准备好,则开始读
- rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址每次加一
- app_addr <= app_addr + 8; //DDR3地址加8
- end
- else begin //若MIG没准备好,则保持原值
- rd_addr_cnt <= rd_addr_cnt;
- app_addr <= app_addr;
- end
- end
- default:begin
- state <= IDLE;
- app_wdf_data <= 128'd0;
- wr_addr_cnt <= 24'd0;
- rd_addr_cnt <= 24'd0;
- app_addr <= 28'd0;
- end
- endcase
- end
- end
-
- //对DDR3实际读数据个数编号计数
- always @(posedge ui_clk or negedge rst_n) begin
- if(~rst_n)
- rd_cnt <= 0; //若计数到读写长度,且读有效,地址计数器则置0
- else if(app_rd_data_valid && rd_cnt == TEST_LENGTH - 1)
- rd_cnt <= 0; //其他条件只要读有效,每个时钟自增1
- else if (app_rd_data_valid )
- rd_cnt <= rd_cnt + 1;
- end
-
- //寄存状态标志位
- always @(posedge ui_clk or negedge rst_n) begin
- if(~rst_n)
- error_flag <= 0;
- else if(error)
- error_flag <= 1;
- end
-
- //led指示效果控制
- always @(posedge ui_clk or negedge rst_n) begin
- if((~rst_n) || (~init_calib_complete )) begin
- led_cnt <= 25'd0;
- led <= 1'b0;
- end
- else begin
- if(~error_flag) //读写测试正确
- led <= 1'b1; //led灯常亮
- else begin //读写测试错误
- led_cnt <= led_cnt + 25'd1;
- if(led_cnt == L_TIME - 1'b1) begin
- led_cnt <= 25'd0;
- led <= ~led; //led灯闪烁
- end
- end
- end
- end
-
- endmodule
外部输入数据流通过输入数据流选择单元将数据流输入到数据缓存模块,比较常用的存储单元有双口RAM,FIFO,SDRAM等。在第一个缓冲周期,数据流通过“输入数据流选择单元”将数据写入“数据缓冲模块1”。写完之后进入第二个缓冲周期,在第二个缓冲周期数据流通过“输入数 据流选择单元”将数据写入到“数据缓冲模块2”的同时“输出数据流选择单元”将“数据缓冲模块1”的数据流读出,此时进入第三个缓冲周期。在第三个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓存模块1”的同时将“数据缓冲模块2”的数据读出。如此反复循环地操作,即为乒乓操作。
乒乓操作的最大特点是通过“输入数据流选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿的,因此非常适合对数据流进行流水线 式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。
乒乓操作的第二个特点是可以节约缓存空间,使用双存储单元比单存储单元更节省存储空间,这是很明显的。同时在某些数据处理时,必须要数据达到一定个数才能进行运算,故还可以达到数据缓存的目的。 乒乓操作还可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。
例如设置写入的数据位宽为8位,时钟频率50MHz。读出的数据位宽为16位,时钟频率25MHz,深度都设置为128。当然大家也可自行设置时钟频率与数据位宽,只要频率与位宽的乘积相等即可。
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状态开始 下一包的数据写入以及读取,如此循环我们就能无缝地将连续的输入数据读取出来了。
ram1_wr_en:ram1写使能,初始值为0。
ram1_wr_addr:ram1写地址,初使值为0。
ram1_wr_data:ram1写数据。
ram1_rd_en:ram1读使能,初始值为0。
ram1_rd_addr:ram1读地址,初使值为0。
ram1_rd_data:ram1读数据。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。