Zynq开发实践(FPGA之ddr sdram读写)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
用fpga做图像的时候,如果图像的输入速度和显示速度不匹配,这个时候就需要把图像先放到ddr里面。因为fpga里面毕竟还是空间太小,把图像全部缓存到fpga里面是不现实的。但是提到ddr很多人都会担心学不会,一来ddr pin比较多,布局布线本身就很麻烦了,而且ddr还分成ddr1、ddr2等很多种类型,很担心自己学不会是正常的。但这也是没有必要的。
1、ddr类型多
ddr的类型确实很多,从ddr1到ddr5,类型确实不少。但是除了ddr1可以用状态机的方法去进行读取之外,其他ddr2以上的类型,一般都是用fpga厂商提供的ip,直接数据读取。也就是说,我们只要会用这些ip就可以操作ddr了。
此外,不管是哪一代的ddr,一般就是分成数据总线、地址总线和控制总线三部分。只不过由于速度越来越快,很多总线变成了差分结构,电压也是越来越低,电路上的布局布线也是越来越难,但对于使用者来说,最重要的还是怎么用起来,至于底层的原理,除了ddr1可以好好看看之外,其他的ddr只要会使用即可。
2、ddr1的读写
目前市面上大部分fpga对ddr都是基于第一代ddr进行操作的。这一代的ddr sdram封装简单,速率不高,所以用状态机的方法对它进行操作是完全可以的。ddr1 sdram也是分成数据总线、地址总线、控制总线三部分。其中数据总线和地址总线是复用的。实际使用的时候一般先发行信号,再发列信号。
3、ddr最特殊的地方
要说初始化、读、写,这些eeprom、nor flash、nand flash、tf卡都会涉及到。也就是说,我们要对数据进行读写,那么先要初始化,然后进行读写操作。但是ddr和它们不一致的地方,那就是需要对ddr进行定时刷新处理。这就意味着初始化之后处于idle状态的时候,还要定时进行ddr刷新的操作,并且刷洗的优先级是最高的,这是其他存储芯片所没有的。
4、zynq访问ddr的不同
在zynq芯片里面访问ddr有两种方式。一种是类似于7010、7020这种。即ps侧访问ddr,pl侧这边没有ddr。pl这边如果需要访问ddr的数据,基本上只有通过dma的方式去实现。第二种,就是7035或者是更好的soc,这一类的soc里面,pl侧是可以有自己的ddr,也就是说ps和pl都有自己的ddr芯片,好处是两者都可以有自己的数据访问区域,缺点就是价格比较贵。
5、特殊的inout信号
之前我们学习uart、pwm、vga和spi的时候,对于外部接口要么是输入,要么是输出,很少是有inout信号的。本质上inout就是wire,它是一种特殊接口,相当于我们可以通过这个接口发送数据,也可以通过这个接口接收数据。iic、sdio里面都有这样的信号。当然除了接口可以这么用之外,fpga内部是不允许inout信号存在的。使用的时候一般是这么用的,
module sdram_top(input clk,input rst,input oe, // 1-write, 0-readinput data,output in,inout io);assign io = (oe) ? data : 1'bz;
assign in = io;endmodule
6、先把ddr用起来
ddr本身布局布线比较麻烦,有很多要求。所以我们学习fpga的时候,可以先学习别人的板子,学习别人的verilog思路,等到自己有需求的时候,再去自己进行硬件电路设计。当然ddr1还是比较简单的,很多ddr1还是sop封装,这种不是bga封装,用起来比其他ddr容易很多,布局布线稍加注意即可。毕竟很多的学习板也是ddr1。
7、ddr1 sdram的读写代码
这部分代码很多,某火、某原子、某linx,都可以看到类似的verilog代码。除了代码,还可以在视频网站上看到对应的视频讲解。要说verilog最简单的,还是某linx,基本上不到400行就讲解清楚了。里面的实现主要就是状态机+延时操作,初始化好了之后,在idle状态下按需进入read、write、refresh的状态。状态机准备好了,就是进行输出、输入操作即可。原来的代码地址在这,
https://github.com/alinxalinx/AX301/blob/master/src/13_sdram_test/src/sdram/sdram_core.v
这里方便大家学习,粘贴一下。不同的命令、不同的输出方式、不同的延时条件,基本也是照着芯片手册去写的。
`timescale 1ns / 1ps
module sdram_core
#
(parameter T_RP = 4,parameter T_RC = 6,parameter T_MRD = 6,parameter T_RCD = 2,parameter T_WR = 3,parameter CASn = 3,parameter SDR_BA_WIDTH = 2,parameter SDR_ROW_WIDTH = 13,parameter SDR_COL_WIDTH = 9,parameter SDR_DQ_WIDTH = 16,parameter SDR_DQM_WIDTH = SDR_DQ_WIDTH/8,parameter APP_ADDR_WIDTH = SDR_BA_WIDTH + SDR_ROW_WIDTH + SDR_COL_WIDTH,parameter APP_BURST_WIDTH = 9
)
(input clk,input rst, //reset signal,high for reset//writeinput wr_burst_req, // write requestinput[SDR_DQ_WIDTH-1:0] wr_burst_data, // write datainput[APP_BURST_WIDTH-1:0] wr_burst_len, // write data length, ahead of wr_burst_reqinput[APP_ADDR_WIDTH-1:0] wr_burst_addr, // write base address of sdram write bufferoutput wr_burst_data_req, // wrtie data request, 1 clock aheadoutput wr_burst_finish, // write data is end//readinput rd_burst_req, // read requestinput[APP_BURST_WIDTH-1:0] rd_burst_len, // read data length, ahead of rd_burst_reqinput[APP_ADDR_WIDTH-1:0] rd_burst_addr, // read base address of sdram read bufferoutput[SDR_DQ_WIDTH-1:0] rd_burst_data, // read data to internaloutput rd_burst_data_valid, // read data enable (valid)output rd_burst_finish, // read data is end//sdramoutput sdram_cke, //clock enableoutput sdram_cs_n, //chip selectoutput sdram_ras_n, //row selectoutput sdram_cas_n, //colum selectoutput sdram_we_n, //write enableoutput[SDR_BA_WIDTH-1:0] sdram_ba, //bank addressoutput[SDR_ROW_WIDTH-1:0] sdram_addr, //addressoutput[SDR_DQM_WIDTH-1:0] sdram_dqm, //data maskinout[SDR_DQ_WIDTH-1: 0] sdram_dq //data
);// State machine code
localparam S_INIT_NOP = 5'd0; //Wait for the power on stable 200us end
localparam S_INIT_PRE = 5'd1; //Precharge state
localparam S_INIT_TRP = 5'd2; //Wait for precharge to complete
localparam S_INIT_AR1 = 5'd3; //First self refresh
localparam S_INIT_TRF1 = 5'd4; //Wait for the first time since end refresh
localparam S_INIT_AR2 = 5'd5; //Second self refresh
localparam S_INIT_TRF2 = 5'd6; //Wait for the second time since end refresh
localparam S_INIT_MRS = 5'd7; //Mode register set
localparam S_INIT_TMRD = 5'd8; //Wait for the mode register set complete
localparam S_INIT_DONE = 5'd9; //The initialization is done
localparam S_IDLE = 5'd10; //Idle state
localparam S_ACTIVE = 5'd11; //Row activation, read and write
localparam S_TRCD = 5'd12; //Row activation wait
localparam S_READ = 5'd13; //Read data state
localparam S_CL = 5'd14; //Wait for latency
localparam S_RD = 5'd15; //Read data
localparam S_RWAIT = 5'd16; //Precharge wait state after read completion
localparam S_WRITE = 5'd17; //Write data state
localparam S_WD = 5'd18; //Write data
localparam S_TDAL = 5'd19; //Wait for the write data and the self refresh end
localparam S_AR = 5'd20; //Self-Refresh
localparam S_TRFC = 5'd21; //Wait for the self refreshreg read_flag;
wire done_200us; //After power on, the 200us input is stable at the end of the flag bit
reg sdram_ref_req; // SDRAM self refresh request signal
wire sdram_ref_ack; // SDRAM self refresh request response signal
reg[SDR_BA_WIDTH-1:0] sdram_ba_r;
reg[SDR_ROW_WIDTH-1:0] sdram_addr_r;
reg ras_n_r;
reg cas_n_r;
reg we_n_r;
wire[APP_ADDR_WIDTH-1:0] sys_addr;
reg[14:0] cnt_200us;
reg[10:0] cnt_7p5us;
reg[SDR_DQ_WIDTH-1:0] sdr_dq_out;
reg[SDR_DQ_WIDTH-1:0] sdr_dq_in;
reg sdr_dq_oe;
reg[8:0] cnt_clk_r; //Clock count
reg cnt_rst_n; //Clock count reset signal
reg[4:0] state;
reg wr_burst_data_req_d0;
reg wr_burst_data_req_d1;
reg rd_burst_data_valid_d0;
reg rd_burst_data_valid_d1;wire end_trp = (cnt_clk_r == T_RP) ? 1'b1 : 1'b0;
wire end_trfc = (cnt_clk_r == T_RC) ? 1'b1 : 1'b0;
wire end_tmrd = (cnt_clk_r == T_MRD) ? 1'b1 : 1'b0;
wire end_trcd = (cnt_clk_r == T_RCD-1) ? 1'b1 : 1'b0;
wire end_tcl = (cnt_clk_r == CASn-1) ? 1'b1 : 1'b0;
wire end_rdburst = (cnt_clk_r == rd_burst_len-4) ? 1'b1 : 1'b0;
wire end_tread = (cnt_clk_r == rd_burst_len+2) ? 1'b1 : 1'b0;
wire end_wrburst = (cnt_clk_r == wr_burst_len-1) ? 1'b1 : 1'b0;
wire end_twrite = (cnt_clk_r == wr_burst_len-1) ? 1'b1 : 1'b0;
wire end_tdal = (cnt_clk_r == T_WR) ? 1'b1 : 1'b0;
wire end_trwait = (cnt_clk_r == T_RP) ? 1'b1 : 1'b0;always@(posedge clk or posedge rst)
beginif(rst == 1'b1)beginwr_burst_data_req_d0 <= 1'b0;wr_burst_data_req_d1 <= 1'b0;rd_burst_data_valid_d0 <= 1'b0;rd_burst_data_valid_d1 <= 1'b0;endelsebeginwr_burst_data_req_d0 <= wr_burst_data_req;wr_burst_data_req_d1 <= wr_burst_data_req_d0;rd_burst_data_valid_d0 <= rd_burst_data_valid;rd_burst_data_valid_d1 <= rd_burst_data_valid_d0;end
endassign wr_burst_finish = ~wr_burst_data_req_d0 & wr_burst_data_req_d1;
assign rd_burst_finish = ~rd_burst_data_valid_d0 & rd_burst_data_valid_d1;
assign rd_burst_data = sdr_dq_in;assign sdram_dqm = {SDR_DQM_WIDTH{1'b0}};
assign sdram_dq = sdr_dq_oe ? sdr_dq_out : {SDR_DQ_WIDTH{1'bz}};
assign sdram_cke = 1'b1;
assign sdram_cs_n = 1'b0;
assign sdram_ba = sdram_ba_r;
assign sdram_addr = sdram_addr_r;
assign {sdram_ras_n,sdram_cas_n,sdram_we_n} = {ras_n_r,cas_n_r,we_n_r};
assign sys_addr = read_flag ? rd_burst_addr:wr_burst_addr; //Read / write address bus switching control
// power on 200us time, done_200us=1
always@(posedge clk or posedge rst)
beginif(rst == 1'b1)cnt_200us <= 15'd0;else if(cnt_200us < 15'd20_000)cnt_200us <= cnt_200us + 1'b1;
endassign done_200us = (cnt_200us == 15'd20_000);//------------------------------------------------------------------------------
//7.5uS timer, every 8192 rows of 64ms storage for a Auto refresh
//------------------------------------------------------------------------------
always@(posedge clk or posedge rst)
beginif(rst == 1'b1)cnt_7p5us <= 11'd0;else if(cnt_7p5us < 11'd750)cnt_7p5us <= cnt_7p5us+1'b1;elsecnt_7p5us <= 11'd0;
endalways@(posedge clk or posedge rst)
beginif(rst == 1'b1)sdram_ref_req <= 1'b0;else if(cnt_7p5us == 11'd749)sdram_ref_req <= 1'b1; else if(sdram_ref_ack)sdram_ref_req <= 1'b0;
end
//SDRAM state machine
always@(posedge clk or posedge rst)
beginif(rst == 1'b1)state <= S_INIT_NOP;elsebegincase (state)S_INIT_NOP:state <= done_200us ? S_INIT_PRE : S_INIT_NOP; //After the end of the 200us / reset into the next stateS_INIT_PRE:state <= S_INIT_TRP; //Precharge stateS_INIT_TRP:state <= (end_trp) ? S_INIT_AR1 : S_INIT_TRP; //Precharge, waits for T_RP clock cyclesS_INIT_AR1:state <= S_INIT_TRF1; //First self refreshS_INIT_TRF1:state <= (end_trfc) ? S_INIT_AR2 : S_INIT_TRF1; //Wait for first self refresh end, T_RC clock cyclesS_INIT_AR2:state <= S_INIT_TRF2; //Second self refreshS_INIT_TRF2:state <= (end_trfc) ? S_INIT_MRS : S_INIT_TRF2; //Wait for second self refresh end T_RC clock cyclesS_INIT_MRS:state <= S_INIT_TMRD;//Mode register set(MRS)S_INIT_TMRD:state <= (end_tmrd) ? S_INIT_DONE : S_INIT_TMRD; //wait mode register setting is complete with T_MRD clock cyclesS_INIT_DONE:state <= S_IDLE; // SDRAM initialization setting complete flagS_IDLE:if(sdram_ref_req)beginstate <= S_AR; //The timing of self refresh requestread_flag <= 1'b1;endelse if(wr_burst_req)beginstate <= S_ACTIVE; //write SDRAMread_flag <= 1'b0;endelse if(rd_burst_req)beginstate <= S_ACTIVE; //read SDRAMread_flag <= 1'b1;endelsebeginstate <= S_IDLE;read_flag <= 1'b1;end//row activeS_ACTIVE:if(T_RCD == 0)if(read_flag) state <= S_READ;else state <= S_WRITE;else state <= S_TRCD;//row active waitS_TRCD:if(end_trcd)if(read_flag) state <= S_READ;else state <= S_WRITE;else state <= S_TRCD;//read data S_READ:state <= S_CL;//read data wait S_CL:state <= (end_tcl) ? S_RD : S_CL;//read dataS_RD:state <= (end_tread) ? S_IDLE : S_RD;//Precharge wait state after read completionS_RWAIT:state <= (end_trwait) ? S_IDLE : S_RWAIT;//Write data stateS_WRITE:state <= S_WD;//write dataS_WD:state <= (end_twrite) ? S_TDAL : S_WD;//Wait for writing data and ending with self refreshS_TDAL:state <= (end_tdal) ? S_IDLE : S_TDAL;//Self-RefreshS_AR:state <= S_TRFC;//Self-Refresh waitS_TRFC:state <= (end_trfc) ? S_IDLE : S_TRFC;default:state <= S_INIT_NOP;endcaseend
endassign sdram_ref_ack = (state == S_AR);// SDRAM self refresh response signal//1 clock to write ahead
assign wr_burst_data_req = ((state == S_TRCD) & ~read_flag) | (state == S_WRITE)|((state == S_WD) & (cnt_clk_r < wr_burst_len - 2'd2));
//Read the SDRAM response signal
assign rd_burst_data_valid = (state == S_RD) & (cnt_clk_r >= 9'd1) & (cnt_clk_r < rd_burst_len + 2'd1);//Time delay for generating SDRAM sequential operation
always@(posedge clk or posedge rst)
beginif(rst == 1'b1)cnt_clk_r <= 9'd0; else if(!cnt_rst_n)cnt_clk_r <= 9'd0; elsecnt_clk_r <= cnt_clk_r+1'b1;
end//Counter control logic
always@(*)
begincase (state)S_INIT_NOP: cnt_rst_n <= 1'b0;S_INIT_PRE: cnt_rst_n <= 1'b1; //Precharge delay count startS_INIT_TRP: cnt_rst_n <= (end_trp) ? 1'b0:1'b1; //Wait until the precharge delay count is over and the counter is clearedS_INIT_AR1,S_INIT_AR2:cnt_rst_n <= 1'b1; //Self refresh count startS_INIT_TRF1,S_INIT_TRF2:cnt_rst_n <= (end_trfc) ? 1'b0:1'b1; //Wait until the refresh count is finished, and the counter is clearedS_INIT_MRS: cnt_rst_n <= 1'b1; //Mode register setting, time counting startS_INIT_TMRD: cnt_rst_n <= (end_tmrd) ? 1'b0:1'b1; //Wait until the refresh count is finished, and the counter is clearedS_IDLE: cnt_rst_n <= 1'b0;S_ACTIVE: cnt_rst_n <= 1'b1;S_TRCD: cnt_rst_n <= (end_trcd) ? 1'b0:1'b1;S_CL: cnt_rst_n <= (end_tcl) ? 1'b0:1'b1;S_RD: cnt_rst_n <= (end_tread) ? 1'b0:1'b1;S_RWAIT: cnt_rst_n <= (end_trwait) ? 1'b0:1'b1;S_WD: cnt_rst_n <= (end_twrite) ? 1'b0:1'b1;S_TDAL: cnt_rst_n <= (end_tdal) ? 1'b0:1'b1;S_TRFC: cnt_rst_n <= (end_trfc) ? 1'b0:1'b1;default: cnt_rst_n <= 1'b0;endcase
endalways@(posedge clk or posedge rst)
beginif(rst == 1'b1)sdr_dq_out <= 16'd0; else if((state == S_WRITE) | (state == S_WD))sdr_dq_out <= wr_burst_data;
end
//Bidirectional data directional control logic
always@(posedge clk or posedge rst)
beginif(rst == 1'b1)sdr_dq_oe <= 1'b0;else if((state == S_WRITE) | (state == S_WD))sdr_dq_oe <= 1'b1;elsesdr_dq_oe <= 1'b0;
end//Reads data from the SDRAM
always@(posedge clk or posedge rst)
beginif(rst == 1'b1)sdr_dq_in <= 16'd0;else if(state == S_RD)sdr_dq_in <= sdram_dq;
endalways@(posedge clk or posedge rst)
beginif(rst == 1'b1) begin{ras_n_r,cas_n_r,we_n_r} <= 3'b111;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};endelsecase(state)S_INIT_NOP,S_INIT_TRP,S_INIT_TRF1,S_INIT_TRF2,S_INIT_TMRD: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b111;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};endS_INIT_PRE: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b010;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};endS_INIT_AR1,S_INIT_AR2: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b001;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};endS_INIT_MRS:begin //Mode register setting, which can be set according to actual needs{ras_n_r,cas_n_r,we_n_r} <= 3'b000;sdram_ba_r <= {SDR_BA_WIDTH{1'b0}}; sdram_addr_r <= {3'b000,1'b0, //Operation mode setting (set here to A9=0, ie burst read / burst write)2'b00, //Operation mode setting ({A8, A7}=00), the current operation is set for mode register3'b011, //CAS latency setting1'b0, //Burst mode3'b111 //Burst length ,full page};endS_IDLE,S_TRCD,S_CL,S_TRFC,S_TDAL: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b111;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};endS_ACTIVE: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b011;sdram_ba_r <= sys_addr[APP_ADDR_WIDTH - 1:APP_ADDR_WIDTH - SDR_BA_WIDTH]; sdram_addr_r <= sys_addr[SDR_COL_WIDTH + SDR_ROW_WIDTH - 1:SDR_COL_WIDTH]; endS_READ: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b101;sdram_ba_r <= sys_addr[APP_ADDR_WIDTH - 1:APP_ADDR_WIDTH - SDR_BA_WIDTH]; sdram_addr_r <= {4'b0010,sys_addr[8:0]};//Column address A10=1, set write enable, allow prechargeendS_RD: beginif(end_rdburst){ras_n_r,cas_n_r,we_n_r} <= 3'b110;else begin{ras_n_r,cas_n_r,we_n_r} <= 3'b111;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};endendS_WRITE: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b100;sdram_ba_r <= sys_addr[APP_ADDR_WIDTH - 1:APP_ADDR_WIDTH - SDR_BA_WIDTH]; sdram_addr_r <= {4'b0010,sys_addr[8:0]};//Column address A10=1, set write enable, allow prechargeendS_WD: beginif(end_wrburst) {ras_n_r,cas_n_r,we_n_r} <= 3'b110;else begin{ras_n_r,cas_n_r,we_n_r} <= 3'b111;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};endendS_AR: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b001;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};enddefault: begin{ras_n_r,cas_n_r,we_n_r} <= 3'b111;sdram_ba_r <= {SDR_BA_WIDTH{1'b1}};sdram_addr_r <= {SDR_ROW_WIDTH{1'b1}};endendcase
endendmodule