Zynq开发实践(FPGA之ddr sdram读写)

发布于:2025-09-16 ⋅ 阅读:(23) ⋅ 点赞:(0)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱: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-read
			input 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
	//write
	input                             wr_burst_req,        //  write request
	input[SDR_DQ_WIDTH-1:0]           wr_burst_data,       //  write data
	input[APP_BURST_WIDTH-1:0]        wr_burst_len,        //  write data length, ahead of wr_burst_req
	input[APP_ADDR_WIDTH-1:0]         wr_burst_addr,       //  write base address of sdram write buffer
	output                            wr_burst_data_req,   //  wrtie data request, 1 clock ahead
	output                            wr_burst_finish,     //  write data is end
	//read
	input                             rd_burst_req,        //  read request
	input[APP_BURST_WIDTH-1:0]        rd_burst_len,        //  read data length, ahead of rd_burst_req
	input[APP_ADDR_WIDTH-1:0]         rd_burst_addr,       //  read base address of sdram read buffer
	output[SDR_DQ_WIDTH-1:0]          rd_burst_data,       //  read data to internal
	output                            rd_burst_data_valid, //  read data enable (valid)
	output                            rd_burst_finish,     //  read data is end
	//sdram
	output                            sdram_cke,           //clock enable
	output                            sdram_cs_n,          //chip select
	output                            sdram_ras_n,         //row select
	output                            sdram_cas_n,         //colum select
	output                            sdram_we_n,          //write enable
	output[SDR_BA_WIDTH-1:0]          sdram_ba,            //bank address
	output[SDR_ROW_WIDTH-1:0]         sdram_addr,          //address
	output[SDR_DQM_WIDTH-1:0]         sdram_dqm,           //data mask
	inout[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 refresh

reg                         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)
begin
	if(rst == 1'b1)
	begin
		wr_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;
	end
	else
	begin
		wr_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
end

assign 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)
begin
	if(rst == 1'b1)
		cnt_200us <= 15'd0;
	else if(cnt_200us < 15'd20_000)
		cnt_200us <= cnt_200us + 1'b1; 
end

assign 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)
begin
	if(rst == 1'b1)
		cnt_7p5us <= 11'd0;
	else if(cnt_7p5us < 11'd750)
		cnt_7p5us <= cnt_7p5us+1'b1;
	else
		cnt_7p5us <= 11'd0;
end

always@(posedge clk or posedge rst)
begin
	if(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)
begin
	if(rst == 1'b1)
		state <= S_INIT_NOP;
	else
		begin
			case (state)
				S_INIT_NOP:
					state <= done_200us ? S_INIT_PRE : S_INIT_NOP;     //After the end of the 200us / reset into the next state
				S_INIT_PRE:
					state <= S_INIT_TRP;     //Precharge state
				S_INIT_TRP:
					state <= (end_trp) ? S_INIT_AR1 : S_INIT_TRP;         //Precharge, waits for T_RP clock cycles
				S_INIT_AR1:
					state <= S_INIT_TRF1;    //First self refresh
				S_INIT_TRF1:
					state <= (end_trfc) ? S_INIT_AR2 : S_INIT_TRF1;           //Wait for first self refresh end, T_RC clock cycles
				S_INIT_AR2:
					state <= S_INIT_TRF2;    //Second self refresh
				S_INIT_TRF2:
					state <= (end_trfc) ?  S_INIT_MRS : S_INIT_TRF2;       //Wait for second self refresh end T_RC clock cycles
				S_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 cycles
				S_INIT_DONE:
					state <= S_IDLE;        // SDRAM initialization setting complete flag
				S_IDLE:
					if(sdram_ref_req)
						begin
						state <= S_AR;      //The timing of self refresh request
						read_flag <= 1'b1;
						end
					else if(wr_burst_req)
						begin
						state <= S_ACTIVE;  //write SDRAM
						read_flag <= 1'b0;
						end
					else if(rd_burst_req)
						begin
						state <= S_ACTIVE;  //read SDRAM
						read_flag <= 1'b1;
						end
					else
						begin
						state <= S_IDLE;
						read_flag <= 1'b1;
						end
				//row active
				S_ACTIVE:
					if(T_RCD == 0)
						 if(read_flag) state <= S_READ;
						 else state <= S_WRITE;
					else state <= S_TRCD;
				//row active wait
				S_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 data
				S_RD:
					state <= (end_tread) ? S_IDLE : S_RD;
				//Precharge wait state after read completion
				S_RWAIT:
					state <= (end_trwait) ? S_IDLE : S_RWAIT;
				//Write data state
				S_WRITE:
					state <= S_WD;
				//write data
				S_WD:
					state <= (end_twrite) ? S_TDAL : S_WD;
				//Wait for writing data and ending with self refresh
				S_TDAL:
					state <= (end_tdal) ? S_IDLE : S_TDAL;
				//Self-Refresh
				S_AR:
					state <= S_TRFC;
				//Self-Refresh wait
				S_TRFC:
					state <= (end_trfc) ? S_IDLE : S_TRFC;
				default:
					state <= S_INIT_NOP;
			endcase
		end
end

assign 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)
begin
	if(rst == 1'b1)
		cnt_clk_r <= 9'd0;         
	else if(!cnt_rst_n)
		cnt_clk_r <= 9'd0;  
	else
		cnt_clk_r <= cnt_clk_r+1'b1;
end

//Counter control logic
always@(*) 
begin
	case (state)
		S_INIT_NOP: cnt_rst_n <= 1'b0;
		S_INIT_PRE: cnt_rst_n <= 1'b1;                   //Precharge delay count start
		S_INIT_TRP: cnt_rst_n <= (end_trp) ? 1'b0:1'b1;  //Wait until the precharge delay count is over and the counter is cleared
		S_INIT_AR1,S_INIT_AR2:cnt_rst_n <= 1'b1;          //Self refresh count start
		S_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 cleared
		S_INIT_MRS: cnt_rst_n <= 1'b1;          //Mode register setting, time counting start
		S_INIT_TMRD: cnt_rst_n <= (end_tmrd) ? 1'b0:1'b1;   //Wait until the refresh count is finished, and the counter is cleared
		S_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
end

always@(posedge clk or posedge rst)
begin
	if(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)
begin
	if(rst == 1'b1)
		sdr_dq_oe <= 1'b0;
	else if((state == S_WRITE) | (state == S_WD))
		sdr_dq_oe <= 1'b1;
	else
		sdr_dq_oe <= 1'b0;
end

//Reads data from the SDRAM
always@(posedge clk or posedge rst)
begin
	if(rst == 1'b1)
		sdr_dq_in <= 16'd0;
	else if(state == S_RD)
		sdr_dq_in <= sdram_dq;
end

always@(posedge clk or posedge rst)
begin
	if(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}};
	end
	else
		case(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}};
			end
			S_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}};
			end
			S_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}};
			end
			S_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 register
					3'b011,         //CAS latency setting
					1'b0,           //Burst mode
					3'b111          //Burst length ,full page
					};
			end
			S_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}};
			end
			S_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]; 
			end
			S_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 precharge
			end
			S_RD: 
			begin
				if(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}};
				end
			end
			S_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 precharge
			end
			S_WD: 
			begin
				if(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}};
				end
			end
			S_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}};
			end
			default: 
			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}};
			end
		endcase
end

endmodule