利用FPGA实现简单的sdram的读写操作

没有什么事都是一帆风顺的,学业,感情。。。让人学会了很多但是又让人难以忘怀。。。人生路上起起伏伏,并不是真心的付出就可以得到相同的回报,有时候可能还会恰恰相反。。。

好了,负面情绪就不带给大家了,博主还是来讲sdram吧。其实博主搞了3,4天才搞明白sdram的大体流程,其实主要是被那么多的地址线啊,各种工作条件给懵逼了,其实学下来发现也就不过尔尔有一个规律可循,以及知道了看数据手册的必要。不过目前还是只会单字读写上。

sdram简单来说可以分为三个过程:1.初始化操作。2.写数据。3.读数据。但是由于我们的sdram并不是时时刻刻都需要读数据和写数据的,所以当我们不读数据或者不写数据的时候,需要每隔一段时间去自动刷新一下,防止数据丢失。(当然官方手册给出了的是先预充电,然后延时20ns,然后刷新)。间隔的刷新时间由数据手册决定。

1.初始化操作

官方给出的初始化过程如下:

利用FPGA实现简单的sdram的读写操作_第1张图片

各位不要慌,一开始博主就是慌了才搞了好几天,其实耐下性子来看还是很简单的,首先cke是使能时钟信号的,应该一直置高电平。

1.首先,需要对sdram延时200us,官方是100us,但是根据你的器件决定,多延迟也没事。在延时过程中,fpga发送nop无操作指令。

2.然后发送precharge预充电指令,同时对A10和BA0和BA1进行赋值,A10为1,则不用管BA0和BA1,所以的资源库都会释放。这些查手册即可。发送完延迟tRP时间,约20ns,同时指令设为无操作

3.接着发送自动刷新指令,然后延迟tRFC,根据器件决定,大约63ns,由于时钟是整数,所以一般要多一些延时。

4.重复第三步

5.设置模式寄存器

同样根据查表得到,然后延时tMRD。这个时间有CAS决定,一般是2个时钟单位。

这样初始化就完成了,感觉还是要学会看手册,要耐心。

然后就是写操作了,具体如下:

利用FPGA实现简单的sdram的读写操作_第2张图片

1.首先是发送active命令,同时需要设置bank信息来确定写哪块bank,还要设置行信息,代表要往哪一行写数据。然后延时tRCD,大约20ns

2.然后发送写命令,同时将dqm拉低,这样才能写入数据,然后将列信息,bank信息,要写入数据赋值,注意此时要拉高A10,代表自动释放资源库,然后延时tWR,大约2个时钟,再延时tRP,大约20ns。

然后就是读操作,具体如下:

利用FPGA实现简单的sdram的读写操作_第3张图片

可以发现它和写操作异曲同工

1.首先是发送active命令,同时需要设置bank信息来确定写哪块bank,还要设置行信息,代表要往哪一行写数据。然后延时tRCD,大约20ns

2.然后发送读命令,同时将dqm拉低,这样才能读到数据,同时将列信息,bank信息,注意此时要拉高A10,代表自动释放资源库,然后延时CAS时钟,大约2个,然后在延时tAC,大约6ns,然后读到数据,一般会考虑延时多一些,因为时钟信号50M的,单个周期就是20ns,读到数据后在延时大约tRP时间。需要注意的是,由于是读数据,所一开始需要将数据库设为高阻态来释放对它的控制。

这个就是sdram单个读写的基本操作了,可以看出如果按照数据手册来,其实不算难,看来以后还是要养成比较良好的阅读习惯啊

最后就是自动刷新了,这个操作是在不在读写的时候发出的,因为要保持数据不掉电。根据手册决定,我的是大约15us刷新一次。刷新过程如下

利用FPGA实现简单的sdram的读写操作_第4张图片

这是官方定义的刷新,按照它的要求来说如下

1.首先发出precharge命令,同时设置A10和bank信息,这个和初始化的第一步一样,然后延时tRP时刻

2.发送自动刷新命令,然后延时tRFC时刻,约63ns

3.重复第二步

好了,以上就是sdram简单的一个读写操作了,具体还是来看代码吧

module sdram(
clk,
rst,
choose,
inaddress,
indata,
sdb,
sa,
dqm,
command,
inidone,
wdone,
rdone,
redone,
outdata
);

input clk;
input rst;
input [3:0]choose;
input [23:0]inaddress;// 前两位bank,后13行地址,后9列地址
input [15:0]indata;
inout reg [15:0]sdb; //数据口
output reg [14:0]sa; //bank 和地址
output reg [1:0]dqm;
output reg [4:0]command; //[4]CKE , [3]CSn, [2]RAS, [1]CASn, [0]WEn
output reg inidone;
output reg wdone;
output reg rdone;
output reg redone;
output reg [15:0]outdata;

reg [4:0]i;
reg [15:0]count;
always@(posedge clk or negedge rst)
if(!rst)
begin
	sdb<=16'd0;
	sa<=15'h7fff;
	dqm<=2'b11;
	command<=5'b10111; //无操作
	i<=5'b0;
	count<=16'd0;
	inidone<=1'b0;
	wdone<=1'b0;
	rdone<=1'b0;
	redone<=1'b0;
	outdata<=16'd0;
end
else if(choose[0])//初始化
case(i)
	0: if(count==16'd10000)
		begin
			count<=16'd0;
			i<=i+1'b1;
		end
		else
			count<=count+1'b1;
	1: begin 
		command <= 5'b10010; 
		sa<=15'h7fff; 
		i <= i + 1'b1; 
		end//预充电
	2,3: begin command<=5'b10111;
	i<=i+1'b1; 
	end //延时40ns 20ns就够 无操作
	4: begin 
	command<=5'b10001; 
	i<=i+1'b1; 
	end//自动刷新
	5,6,7,8: begin 
	command<=5'b10111;
	i<=i+1'b1; 
	end
	9: begin 
	command<=5'b10001; 
	i<=i+1'b1; 
	end//自动刷新
	10,11,12,13: begin 
	command<=5'b10111;
	i<=i+1'b1;
	end
	14: begin 
	command<=5'b10000; 
	sa<={4'b0,4'b0,3'b010,4'b0011};
	i<=i+1'b1; 
	end
	15,16: begin command<=5'b10111;
	i<=i+1'b1; 
	end //延时两个时钟
	17: begin inidone<=1'b1; 
	i<=i+1'b1; 
	end
	18: begin inidone<=1'b0; 
	i<=5'b0; 
	end
endcase
else if(choose[1]) //写1数据
case(i)
	0:begin command<=5'b10011; 
	sa<=inaddress[23:9]; 
	sdb<=indata;
	i<=1+1'b1; 
	end//active 先把数据写入
	1,2: begin command<=5'b10111; 
	i<=i+1'b1; 
	end //
	3: begin command<=5'b10100; 
	sa<={inaddress[23:22],4'b0010,inaddress[8:0]}; 
	dqm<=2'b00;
	i<=i+1'b1; 
	end //列地址,a10拉高自动充电
	4,5,6: begin command<=5'b10111; 
	dqm<=2'b11; 
	i<=i+1'b1; 
	end
	7: begin wdone<=1'b1; 
	i<=i+1'b1; 
	end
	8: begin wdone<=1'b0; 
	i<=5'b0; 
	end
endcase
else if(choose[2]) //读数据
case(i)
	0:begin command<=5'b10011; 
	sa<=inaddress[23:9]; 
	i<=1+1'b1; 
	sdb<=16'hzzzz;
	end//active且拉高数据总线做输出
	1,2: begin command<=5'b10111; 
	i<=i+1'b1;
	end //先把数据写入
	3: begin command<=5'b10101; 
	sa<={inaddress[23:22],4'b0010,inaddress[8:0]};
	dqm<=2'b00; 
	i<=i+1'b1; 
	end
	4,5,6: begin command<=5'b10111; 
	dqm<=2'b11; 
	i<=i+1'b1; 
	end
	7: begin outdata<=sdb; 
	i<=i+1'b1; 
	end
	8: begin rdone<=1'b1; 
	i<=i+1'b1; 
	end
	9: begin rdone<=1'b0; 
	i<=5'b0; 
	end
endcase
else if(choose[3]) //刷新
case(i)
	0: begin command<=5'b10001; 
	i<=i+1'b1; 
	end //ar
	1: begin command<=5'b10111; 
	i<=i+1'b1; 
	end
	2: begin redone<=1'b1; 
	i<=i+1'b1; 
	end
	3: begin redone<=1'b0; 
	i<=5'b0; 
	end
endcase
endmodule
module sdram_test(
clk,
rst,
sdb,
sa,
dqm,
command,
slk,
led
);
input clk;
input rst;
inout wire [15:0]sdb; //数据口
output wire [14:0]sa; //bank 和地址
output wire [1:0]dqm;
output wire [4:0]command; //[4]CKE , [3]CSn, [2]RAS, [1]CASn, [0]WEn
output slk;
output reg [3:0]led;

assign slk=clk;


reg [3:0]choose;
reg [23:0]inaddress;
reg [15:0]indata;
wire inidone;
wire wdone;
wire rdone;
wire redone;
wire [15:0]outdata;
sdram sdram(
.clk(clk),
.rst(rst),
.choose(choose),
.inaddress(inadress),
.indata(indata),
.sdb(sdb),
.sa(sa),
.dqm(dqm),
.command(command),
.inidone(inidone),
.wdone(wdone),
.rdone(rdone),
.redone(redone),
.outdata(outdata)
);

reg [3:0]i;
reg [15:0]count;
reg [27:0]ccount;
always@(posedge clk or negedge rst)
if(!rst)
begin
	choose<=4'b0001;
	//inaddress<=24'b0;
	//indata<=16'h1234;
	i<=4'b0;
	count<=16'd390;
end
else
case(i)
	0: if(inidone) begin i<=i+1'b1; 
	choose<=4'b0010; 
	end
	else begin choose<=4'b0001; 
	end  
	1: if(wdone) begin i<=i+1'b1; 
	choose<=4'b0100; 
	end
	else begin inaddress<=24'b0; 
	indata<=16'h1234; 
	end
	2: if(rdone) begin	
	led<=outdata[3:0];
	count<=16'd390;
	ccount<=28'd0;
	i<=i+1'b1;
	end
	3: if(count==16'd390)
	begin
		choose<=4'b1000;
		count<=16'b0;
		ccount<=ccount+1'b1;
	end
	else if(redone)
	begin
		choose<=4'b0000;
	end
	else if(ccount==28'd128205)
	begin
		i<=i+1'b1;
		ccount<=28'd0;
		choose<=4'b0100;
		count<=16'd0;
	end
	else
		count<=count+1'b1;
		
	4: if(rdone) begin	
	led<=outdata[7:4];
	count<=16'd390;
	ccount<=28'd0;
	i<=i+1'b1;
	end
	5: if(count==16'd390)
	begin
		choose<=4'b1000;
		count<=16'b0;
		ccount<=ccount+1'b1;
	end
	else if(redone)
	begin
		choose<=4'b0000;
	end
	else if(ccount==28'd128205)
	begin
		i<=i+1'b1;
		ccount<=28'd0;
		choose<=4'b0100;
		count<=16'd0;
	end
	else
		count<=count+1'b1;

	6: if(rdone) begin	
	led<=outdata[11:8];
	count<=16'd390;
	ccount<=28'd0;
	i<=i+1'b1;
	end
	7: if(count==16'd390)
	begin
		choose<=4'b1000;
		count<=16'b0;
		ccount<=ccount+1'b1;
	end
	else if(redone)
	begin
		choose<=4'b0000;
	end
	else if(ccount==28'd128205)
	begin
		i<=i+1'b1;
		ccount<=28'd0;
		choose<=4'b0100;
		count<=16'd0;
	end
	else
		count<=count+1'b1;
	
	8: if(rdone) begin	
	led<=outdata[15:12];
	count<=16'd390;
	ccount<=28'd0;
	end
endcase

endmodule

这是一个简单的测试,将数据写入后每隔一秒读数据的四位来检测其稳定性。好了简单的例子就完成了,要更多的操作可以看数据手册。还是很必要的。

好了,博主快不行了,更多的负面情绪就不继续传递了。写博客的时候感觉心里很失落,总感觉少了点什么,不知道还要持续多久。。。哎。。。人生总是这样,充满了戏剧,同样也没有永远的承诺。。。

你可能感兴趣的:(FPGA)