设计简单能用的SDRAM控制模块,目前我们主要包含刷新操作、写操作和读操作这三个任务。
主要是涉及优先级,所以转移条件要仔细查对。也就是某两个或多个操作请求同时到来时,先执行哪个操作,这里默认的优先级是刷新操作>写数据操作>读数据操作。
//主状态机状态
localparam IDLE = 4'b0001,
AREF = 4'b0010,
WRITE = 4'b0100,
READ = 4'b1000;
//主状态机
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)begin
main_state <= IDLE;
FF <= 1'b1;
end
else begin
case(main_state)
IDLE:begin
Command <= init_cmd;
Sa <= init_addr;
if(init_done)
main_state <= AREF;
else
main_state <= IDLE;
end
AREF:begin
if(FF==1'b0)
auto_ref;
else begin
if(ref_req)begin
main_state <= AREF;
FF <= 1'b0;
end
else if(wr_req)begin
main_state <= WRITE;
FF <= 1'b0;
end
else if(rd_req)begin
main_state <= READ;
FF <= 1'b0;
end
else
main_state <= AREF;
end
end
WRITE:begin
if(FF==1'b0)
write_data;
else begin
if(ref_req == 1'b1)begin
main_state <= AREF;
FF <=1'b0;
end
else if(wr_opt_done & wr_req)begin
main_state <= WRITE;
FF <= 1'b0;
end
else if(wr_opt_done & rd_req)begin
main_state <= READ;
FF <=1'b0;
end
else if(wr_opt_done&!wr_req&!rd_req)
main_state <= AREF;
else
main_state <= WRITE;
end
end
READ:begin
if(FF==1'b0)
read_data;
else begin
if(ref_req == 1'b1)begin
main_state <= AREF;
FF <=1'b0;
end
else if(rd_opt_done & wr_req)begin
main_state <= WRITE;
FF <=1'b0;
end
else if(rd_opt_done & rd_req)begin
main_state <= READ;
FF <=1'b0;
end
else if( rd_opt_done&!wr_req&!rd_req)
main_state <= AREF;
else
main_state <= READ;
end
end
endcase
end
end
//SDRAM 命令信号组合
assign {Cs_n,Ras_n,Cas_n,We_n} = Command;
//时钟使能信号
assign Cke = Rst_n;
写操作和读操作 3 种不同的任务的执行是通过标志位 FF 来控制的,只有在标志位为 0 时,指定的操作任务才能执行,执行完后将 FF 置 1 退出任务。
有关控制器的 4 个命令输出信号线是通过位拼接的形式用一个位宽为 4 的 Command 信号表示的;
时钟使能信号 Cke 就直接和复位信号 Rst_n 相连
结合实际使用的开发板 AC620 上使用的 SDRAM 芯片是商业级的 128Mb 的SDRAM 芯片,每个 Bank 有 4096 行,因此自动刷新需要设计成 64ms 内需要执行 4096 次自动刷新操作。考虑到控制器设计的简易性,我们采用定时刷新操作,即固定时间间隔 15.625us(等于 64ms/4096)刷新一次。刷新过程如上述所说的线性序列机方法。
(这里通过全局参数化在文件 Sdram_Params.h 中将 AUTO_REF 设置为1500)固定的时间间隔产生采用计数器产生,根据时间间隔设置计数最大值。每当计数值达到最大值1500时,产生一个时钟周期的标志信号。该标志位在产生刷新、写操作和读操作请求信号中有所使用。
//刷新定时计数器
always@(posedge Clk or negedge Rst_n)
begin
if(!Rst_n)
ref_time_cnt <= 0;
else if(ref_time_cnt == AUTO_REF)
ref_time_cnt <= 1;
else if(init_done || ref_time_cnt > 0)
ref_time_cnt <= ref_time_cnt + 10'd1;
else
ref_time_cnt <= ref_time_cnt;
end
//刷新定时时间标志位,定时到达时置 1
assign ref_time_flag = (ref_time_cnt == AUTO_REF);
如果使用 256Mb 的 SDRAM, 则每个 Bank 有 8192 行,因此需要每 64ms 完成 8192 次刷新,即每 7.8125us 执行一次刷新, 对应的 AUTO_REF 值应该设置为 750。
SDRAM 在进行读写操作时,需要对地址线进行相应的操作,为了防止在操作过程中地址的变动,用寄存器将读写操作时的各种地址进行寄存。地址在每次读写请求到来时进行变化,在读写过程中是不变的。
//读写行列地址寄存器
always@(posedge Clk or negedge Reset_n)
begin
if(!Reset_n)
begin
raddr_r <= 0;
caddr_r <= 0;
baddr_r <= 0;
end
else if(rd_req || wr_req)
begin
raddr_r <= Raddr;
caddr_r <= Caddr;
baddr_r <= Baddr;
end
else
;
end
//SDRAM 前期初始化模块例化
sdram_init sdram_init(
.Clk(Clk),
.Rst_n(Rst_n),
.Command(init_cmd),
.Saddr(init_addr),
.Init_done(init_done)
);
对不同请求信号的调配
刷新>写>读
//写操作过程刷新到记住刷新信号 ref_break_wr
assign ref_break_wr = (ref_time_flag&&wr_opt)?1'b1:((!wr_opt)?1'b0:ref_break_wr);
//读操作过程刷新到记住刷新信号 ref_break_rd
assign ref_break_rd = (ref_time_flag&&rd_opt)?1'b1:((!rd_opt)?1'b0:ref_break_rd);
//刷新请求信号
always@(*)
begin
case(main_state)
AREF:begin
if(ref_time_flag)
ref_req = 1'b1;
else
ref_req = 1'b0;
end
WRITE:begin
if(ref_break_wr && wr_opt_done)
ref_req = 1'b1;
else
ref_req = 1'b0;
end
READ:begin
if(ref_break_rd && rd_opt_done)
ref_req = 1'b1;
else
ref_req = 1'b0;
end
default:
ref_req = 1'b0;
endcase
end
//刷新过程外部写使能到记住写使能信号 wr_break_ref
assign wr_break_ref = ((Wr && ref_opt)?1'b1:((!ref_opt)?1'b0:wr_break_ref));
//写操作请求信号
always@(*)
begin
case(main_state)
AREF:begin
if((!wr_break_ref)&& Wr &&!ref_time_flag)
wr_req = 1'b1;
else if(wr_break_ref && ref_opt_done)
wr_req = 1'b1;
else
wr_req = 1'b0;
end
WRITE:begin
if(wr_opt_done && Wr && !ref_break_wr)
wr_req = 1'b1;
else
wr_req = 1'b0;
end
在刷新操作状态,外部读使能信号到来时,
//刷新过程外部读使能到记住读使能信号rd_break_ref 信号
assign rd_break_ref = ((Rd && ref_opt)?1'b1:((!ref_opt)?1'b0:rd_break_ref));
在写数据操作状态,在写数据操作完成时并且没有外部写使能和刷新定时到来的情况下,外部读使能信号到来时,就产生一个读操作请求信号。
在读数据操作状态,在读数据操作完成时并且没有外部写使能和刷新定时到来的情况下,外部读使能信号到来时,就产生一个读操作请求信号。
//读操作请求信号
always@(*)
begin
case(main_state)
AREF:begin
if((!rd_break_ref)&&(!wr_break_ref)&&(!ref_time_flag)&& !Wr && Rd )
rd_req = 1'b1;
else if(ref_opt_done &&!wr_break_ref&&rd_break_ref)
rd_req = 1'b1;
else
rd_req = 1'b0;
end
WRITE:begin
if(wr_opt_done &&(!ref_break_wr)&&
!Wr && Rd)
rd_req = 1'b1;
else
rd_req = 1'b0;
end
READ:begin
if(rd_opt_done &&(!ref_break_rd)&&
!Wr && Rd)
rd_req = 1'b1;
else
rd_req = 1'b0;
end
default:
rd_req = 1'b0;
endcase
end
以上并没有包含所有的情况,比如在刷新状态中如果刷新定时和外部写或读使能同时到来时,只让其进行刷新操作,而未对此次写或读使能进行记忆,是直接忽略此次的写或读使能,类似的情况还有在读或写过程中外部读或写使能到来时,也是忽略了此次的写或读使能,这样设计是考虑到整个 SDRAM 控制器一般是结合 FIFO 模块共同实现数据的缓存的,上面提到的外部写或读使能信号是根据写或读 FIFO 模块的可读或写数据量来控制的,并且 FIFO 本身也具有一定的大小的内存可存放数据,一次的读写使能忽略不会造成影响,后面会具体讲到与FIFO 模块相结合设计的 SDRAM 控制器。