SDRAM控制器设计
程序员文章站
2024-02-25 11:36:58
...
项目名称
SDRAM控制器设计
具体要求
给sdram存入100个数据并读出
设计说明
模块设计
端口设计及端口说明并包含参数文件
module sdram_ctrl (
clk,
rst_n,
wr,
rd,
caddr,
raddr,
baddr,
wr_data,
rd_data,
rd_data_vaild,
wr_data_vaild,
wdata_done,
rdata_done,
sa,
ba,
cs_n,
cke,
ras_n,
cas_n,
we_n,
dq,
dqm
);
`include "params.h"
input clk;
input rst_n;
input wr;//写使能
input rd;//读使能
input[`ASIZE-1:0]caddr;//写列地址
input[`ASIZE-1:0]raddr;//写行地址
input[`BSIZE-1:0]baddr;//写sdram 时的bank地址
input[`DSIZE-1:0]wr_data;//待写入的数据
output[`DSIZE-1:0]rd_data;//读出的数据
output reg rd_data_vaild;//读数据有效区
output reg wr_data_vaild;//写数据有效区
output wdata_done;//一次突发写完成标志
output rdata_done;//一次突发读完成标志
output reg[`ASIZE-1:0]sa;//地址总线
output reg[`BSIZE-1:0]ba;//bank总线
output cs_n;//片选信号
output cke;//时钟使能
output ras_n;//行地址选通
output cas_n;//列地址选通
output we_n;//写使能
output[`DSIZE-1:0] dq;//数据总线
output[`DSIZE/8-1:0] dqm;//数据掩码
对初始化模块进行例化
//sdram初始化模块例化
wire init_cmd;//初始化命令输出
wire init_addr;//初始化地址输出
wire init_done;//初始化完成标志
sdram_init sdram_init(
.clk(clk),
.rst_n(rst_n),
.command(init_cmd),//命令信号
.saddr(init_addr),//地址信号
.init_done(init_done)
);
使能时钟信号
assign cke=rst_n;//时钟使能信号
命令信号
reg [3:0] command;//操作命令
assign {cs_n,ras_n,cas_n,we_n}=command;//命令信号
数据总线,写数据有效区数据总线为写入的数据,否则处于高阻态
assign dq=wr_data_vaild ? wr_data:16'bz;//sdram数据线。采用三态输出
//最终数据线上的数据给读数据信号
数据掩码,,之前没有说这个信号,实际上就是数据屏蔽,有两位高位和低位,当高(低)为为高电平的时候,下一时钟上升沿数据总线的高(低)字节为高阻态,接收不到数据,一般不会操作这个,因为这个容易使sdram不稳定.
assign dqm=2'b00;//不用数据掩码
主状态机的设计
整个过程的状态机设计如下,初始化完成就进入刷新状态。
在写状态时,刷新请求来了,记住此次刷新请求,写操作完成时产生一个刷新请求操作。
在读状态时,刷新请求来了,记住此次的刷新请求,读完后产生一个刷新请求信号。
由状态机可以看出优先级是刷新>写>读
FF控制着读写刷新命令的执行,只有FF=0的时候才能执行指定的操作,执行完后将FF置1,退出任务。
注意这个状态机要严格根据优先级刷新>写>读来写,就是在条件语句中优先判断刷新请求,然后是写请求,再者是读请求,其他请求在这三者之后。
//主状态机
reg [3:0] main_state;
reg FF;//标志寄存器,为0的时候才可以进行刷新、写和读,刷新,写和读完成之后置1
reg ref_req;//刷新操作请求
reg wr_req;//写请求
reg rd_req;//读请求
reg wr_opt_done;//一次突发写操作完成标志
reg rd_opt_done;//一次读突发完成标志
localparam IDLE =4'b0001;//空闲状态
localparam AREF =4'b0010;//刷新状态
localparam WRITE =4'b0100;//写状态
localparam READ =4'b1000;//读状态
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)begin
main_state<=IDLE;
FF<=1;
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==0)
auto_ref;//自动刷新任务
else if(ref_req)begin
main_state<=AREF;
FF<=0;//开始执行任务
end
else if(wr_req)begin
main_state<=WRITE;
FF<=0;
end
else if(rd_req)begin
main_state<=READ;
FF<=0;
end
else
main_state<=AREF;
end
WRITE: begin
if(FF==0)
write_data;//写数据任务
else begin
if(ref_req)begin
main_state<=AREF;
FF<=0;
end
else if(wr_opt_done & wr_req)begin
main_state<=WRITE;
FF<=0;
end
else if(wr_opt_done & rd_req)begin
main_state<=READ;
FF<=0;
end
else if(wr_opt_done & !wr_req & !rd_req)begin
main_state<=AREF;
end
else
main_state<=WRITE;
end
end
READ : begin
if(FF==0)
read_data;
else begin
if(ref_req)begin
main_state<=AREF;
FF<=0;
end
else if(rd_opt_done & wr_req)begin
main_state<=WRITE;
FF<=0;
end
else if(rd_opt_done & rd_req)begin
main_state<=READ;
FF<=0;
end
else if(rd_opt_done & !wr_req & !rd_req)begin
main_state<=AREF;
end
else
main_state<=READ;
end
end
endcase
end
设计刷新定时计数器,自动刷新周期为64ms/4096=15625, 取计数值为1560
//刷新定时计数器
reg [31:0] ref_time_cnt;//刷新定时计数器
aaa@qq.com(posedge clk or negedge rst_n)
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)//在初始化完成开始刷新计数或者刷新计数器为1的时候开始刷新计数
ref_time_cnt<=ref_time_cnt+1;
else
ref_time_cnt<=ref_time_cnt;
wire ref_time_flag;//刷新定时标志
assign ref_time_flag=(ref_time_cnt==AUTO_REF);
定义刷新定时标志,刷新间隔计时完成时,需要进行刷新
wire ref_time_flag;//刷新定时标志
assign ref_time_flag=(ref_time_cnt==AUTO_REF);
行列bank地址寄存器,当有读写请求时对这些地址进行寄存。
//读写行列地址寄存器
reg [`ASIZE-1:0]raddr_r;//读写行地址寄存器
reg [`ASIZE-1:0]caddr_r;//读写列地址寄存器
reg [`BSIZE-1:0]baddr_r;//读写bank地址寄存器
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_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
;
自动刷新操作任务,由于在刷新过程中 涉及到所有bank,所有bank都要停止工作,所以需要先进行预充电关闭所有行。这个就感觉和初始化操作的预充电然后执行两个自动刷新命令,只是这里没有加载模式寄存器
//自动刷新操作任务
localparam ref_pre_time =1;//预充电时刻
localparam ref_ref1_time=REF_PRE+1;//ref_pre为预充电时间,预充电完成,第一次自动刷新时刻
localparam ref_ref2_time=REF_PRE+REF_REF+1;//REF_REF为自动刷新时间,第2次自动刷新时刻
localparam ref_end =REF_PRE+REF_REF*2;//自动刷新结束
//自动刷新时间计数
reg [15:0] ref_cnt;
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
ref_cnt<=0;
else if(ref_cnt==ref_end)
ref_cnt<=0;
else if(ref_req || ref_cnt>0)
ref_cnt<=ref_cnt+1;
else
ref_cnt<=ref_cnt;
//一次刷新操作完成标志
reg ref_opt_done;//一次刷新操作完成标志
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
ref_opt_done<=0;
else if(ref_cnt==ref_end)
ref_opt_done<=1;
else
ref_opt_done<=0;
//自动刷新操作
task auto_ref;
begin
case(ref_cnt)
ref_pre_time : begin
command<=C_PRE;
sa[10]=1;
end
ref_ref1_time : command<=C_AREF;
ref_ref2_time : command<=C_AREF;
ref_end : begin
FF<=1;
command<=C_NOP;
end
endcase
end
endtask
突发写操作
//一次突发写操作
localparam wr_act_time =1;//行**时刻
localparam wr_write_time=SC_RCD+1;//SC_RCD为**到读写时刻的时间,写时刻
localparam wr_pre_time =SC_RCD+SC_BL+WR_PRE+1;//SC_BL为突发长度,WR_PRE写数据到预充电间隔,写完数据之后的预充电时刻
localparam wr_end_time =SC_RCD+SC_BL+WR_PRE+REF_PRE;//REF_PRE预充电时间完成,写操作结束时刻
//一次突发写过程计数器
reg [15:0] wr_cnt;
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
wr_cnt<=0;
else if(wr_cnt==wr_end_time)
wr_cnt<=0;
else if(wr_req || wr_cnt>0)
wr_cnt<=wr_cnt+1;
else
wr_cnt<=0;
//一次写操作完成标志
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
wr_opt_done<=0;
else if(wr_cnt==wr_end_time)
wr_opt_done<=1;
else
wr_opt_done<=0;
//一次写操作完成标志
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
wr_opt_done<=0;
else if(wr_cnt==wr_end_time)
wr_opt_done<=1;
else
wr_opt_done<=0;
//一次突发写操作过程状态标识信号
reg wr_opt;
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
wr_opt<=0;
else if(wr_req)
wr_opt<=1;
else if(wr_opt_done)
wr_opt<=0;
else
wr_opt<=wr_opt;
突发写数据操作
//写数据操作,数据写入时刻有效区间
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
wr_data_vaild<=0;
else if((wr_cnt>SC_RCD)&&(wr_cnt<=SC_RCD+SC_BL)) //**到读写命令延时时刻,写突发完成时
//刻
wr_data_vaild<=1; //突发写数据时为写数据有效区
else
wr_data_vaild<=0;
//一次突发写操作数据写完标志
assign wdata_done=(wr_cnt==SC_RCD+SC_BL+1)?1:0; //突发写数据完成在下个时刻发出写完标志
//一次突发写操作任务
task write_data;
begin
case(wr_cnt)
wr_act_time : begin //行**时刻
command<=C_ACT;
sa<=raddr_r;
ba<=baddr_r;
end
wr_write_time : begin //行**延时,开始写命令时刻
command<=C_WR;
sa<={1'B0,caddr_r[8:0]};
ba<=baddr_r;
end
wr_pre_time : begin //预充电时刻
command<=C_PRE;
sa[10]<=1;
end
wr_end_time : begin //写操作完成时刻
command<=C_NOP;
FF<=1;
end
endcase
end
突发读操作
//一次读突发操作任务
localparam rd_act_time =1;//**行
localparam rd_read_time=SC_RCD+1;//读命令时刻
localparam rd_pre_time =SC_RCD+SC_BL+1;//预充电时刻
localparam rd_end_time =SC_RCD+SC_BL+SC_CL;//读操作结束时刻
//一次突发读过程计数
reg [15:0] rd_cnt;
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
rd_cnt<=0;
else if(rd_cnt==rd_end_time)
rd_cnt<=0;
else if(rd_req || rd_cnt>0)
rd_cnt<=rd_cnt+1;
else
rd_cnt<=0;
//一次突发读操作完成标志
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
rd_opt_done<=0;
else if(rd_cnt==rd_end_time)
rd_opt_done<=1;
else
rd_opt_done<=0;
//一次突发读操作过程状态标志信号
reg rd_opt;
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
rd_opt<=0;
else if(rd_req)
rd_opt<=1;
else if(rd_opt_done)
rd_opt<=0;
else
rd_opt<=rd_opt;
//一次突发读操作过程数据读完标志位
assign rdata_done=(rd_cnt==rd_end_time)?1:0;
//读数据操作,数据有效区
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
rd_data_vaild<=0;
else if((rd_cnt>SC_RCD+SC_CL)&&(rd_cnt<=SC_RCD+SC_CL+SC_BL))
rd_data_vaild<=1;
else
rd_data_vaild<=0;
//读数据
assign rd_data=dq;
//一次突发读操作
task read_data;
begin
case(rd_cnt)
rd_act_time : begin
command<=C_ACT;
sa<=raddr_r;
ba<=baddr_r;
end
rd_read_time: begin
command<=C_RD;
sa<={1'b0,caddr_r[8:0]};
ba<=baddr_r;
end
rd_pre_time : begin
command<=C_PRE;
sa[10]<=1;
end
rd_end_time : begin
FF<=1;
command<=C_NOP;
end
default:command<=C_NOP;
endcase
end
endtask
正在刷新时来读写信号或者正在读写时来刷新信号
//一次突发写过程状态标志信号
reg ref_opt;//刷新请求来正在刷新为1
aaa@qq.com(posedge clk or negedge rst_n)
if(!rst_n)
ref_opt<=0;
else if(ref_req) //刷新来时该信号为1
ref_opt<=1;
else if(ref_opt_done) //刷新完成时该信号为0
ref_opt<=0;
else
ref_opt<=ref_opt;
wire ref_break_wr;//写过程刷新到记住刷新信号
wire ref_break_rd; //读过程刷新到记住刷新信号
wire wr_break_ref;//刷新过程外部写使能到记住写使能信号
wire rd_break_ref;//刷新过程外部读使能到记住读使能信号
//写操作过程刷新到记住刷新信号ref_break_wr
assign ref_break_wr=(ref_time_flag && wr_opt)?1:( (!wr_opt)?0:ref_break_wr);
//读操作过程刷新到记住刷新信号ref_break_rd
assign ref_break_rd = (ref_time_flag&&rd_opt)?1'b1:
((!rd_opt)?1'b0:ref_break_rd);
//刷新过程外部写使能到记住写使能信号wr_break_ref
assign wr_break_ref = ((wr && ref_opt)?1'b1:
((!ref_opt)?1'b0:wr_break_ref));
//刷新过程外部读使能到记住读使能信号rd_break_ref信号
assign rd_break_ref = ((rd && ref_opt)?1'b1:
((!ref_opt)?1'b0:rd_break_ref));
刷新请求
//刷新请求信号
aaa@qq.com(*)
begin
case(main_state)
AREF: begin
if(ref_time_flag)
ref_req=1;
else
ref_req=0;
end
WRITE:begin
if(ref_break_wr && wr_opt_done)
ref_req=1;
else
ref_req=0;
end
READ: begin
if(ref_break_rd && rd_opt_done)
ref_req=1;
else
ref_req=0;
end
default:ref_req=0;
endcase
end
写操作请求
//写操作请求信号
aaa@qq.com(*)
begin
case(main_state)
AREF: begin
if( (!wr_break_ref)&& wr && !ref_time_flag)
wr_req=1;
else if(wr_break_ref && ref_opt_done)
wr_req=1;
else
wr_req<=0;
end
WRITE:begin
if(wr_opt_done && wr && !ref_break_wr)
wr_req=1;
else
wr_req=0;
end
READ: begin
if(rd_opt_done && wr && !ref_break_rd)
wr_req=1;
else
wr_req=0;
end
default:wr_req=0;
endcase
end
读请求信号
//读操作请求信号
aaa@qq.com(*)
begin
case(main_state)
AREF: begin
if( (!rd_break_ref)&& (!wr_break_ref) && (!ref_time_flag) && !wr && rd)
rd_req=1;
else if(ref_opt_done && !wr_break_ref && rd_break_ref)
rd_req=1;
else
rd_req<=0;
end
WRITE:begin
if(wr_opt_done && !wr && rd && !ref_break_wr)
rd_req=1;
else
rd_req=0;
end
READ: begin
if(rd_opt_done && !wr && !ref_break_rd && rd)
rd_req=1;
else
rd_req=0;
end
default:rd_req=0;
endcase
end