设计并实现一个 SDRAM 数据读写控制器,使用 PC 机通过串口向 SDRAM 写入 10 字
节数据,并将写入的 10 字节数据读出,通过串口回传至 PC 机,在串口助手上位机上打印
显示回传数据。
要实现数据的读写,还要有初始化和刷新操作。所以该模块要有分别产生这四条指令的模块。
由于时序冲突问题,刷新,和读写指令存在优先级的问题。所以还应有仲裁模块。
如下:
指令产生模块应该有指令端口,bank地址端口,行列地址端口,结束标志信号。
刷新模块的请求信号传向仲裁模块,反馈给刷新模块一个使能信号。
写指令模块的写数据,写地址,写突发长度。必不可少。
需要注意的是有几个输入端口是外界给的,有几个输出端口直接输出出去。
整体:
fifo控制模块,实现sdram读写数据的跨时钟域处理。
1,取消设备选择:1XXX;
2,无操作指令:0111;
3,配置模式指令:0000;+ addr A12~A0
4,预充电指令:0010;+ addr
5,自动刷新与自刷新指令:0001;
6,激活指令:0011;+addr/bank
7,写指令:0100;+bank/col;
8,读指令:0101;+bank/col;
明天完成初始化模块,加油!!!
这个sdram读写控制器工程确实挺大的。
1 预充电指令,在写入0010指令+A10 == 1 之前,先等待200us,加载稳定时钟,并传递空操作指令。
2 写入预充电之后要等待TRP = 2 ,并传递空操作指令0111。
3 TRP时间结束后,写入自动刷新指令0001,并等待TRC = 7 ,并传递空操作指令。
4 TRC时间结束后,再次刷新指令。
5 TRC时间结束后,模式配置寄存器指令0000。+A11~A0;并等待TMRD = 时间,并传递空操作。
6 等待TMRD结束后,完成初始化。
// 初始化模块,产生控指令时序,预充电时序,刷新时序,模式配置时序。sys_clk == 100mHz
module sdram_init(
input wire sys_clk ,
input wire sys_rst_n ,
output reg [3:0] init_cmd ,
output reg [1:0] init_ba ,
output reg [12:0] init_addr ,
output reg init_end
);
// parameter
parameter T_POWER = 200_00 , // 等待时钟稳定。200us。
T_RP = 2 , // 2个时钟周期的预充电时间。
T_RFC = 7 , // 7个时钟周期的刷新时间。
T_MRD = 3 , // 3个时钟周期的模式配置时间。
MAX_CNT_AR= 8 ; // 刷新次数。
localparam INIT_NOP = 4'b0111 , // 空指令
INIT_PREC = 4'b0010 , // 预充电
INIT_REF = 4'b0001 , // 自动刷新
INIT_MOD = 4'b0000 ; // 模式配置
localparam INIT_IDLE = 8'b0000_0001 ,
INIT_PRE = 8'b0000_0010 , // 预充电
INIT_TRP = 8'b0000_0100 ,
INIT_AR = 8'b0000_1000 , // 刷新
INIT_TFR = 8'b0001_0000 ,
INIT_MRS = 8'b0010_0000 , // 模式配置
INIT_TMRD = 8'b0100_0000 ,
INIT_ENDS = 8'b1000_0000 ; // 完成
// reg signal define
reg [7:0] state_c ;
reg [7:0] state_n ;
reg [15:0] cnt_init ;
reg [3:0] cnt_ar ; // 记录刷新次数,以产生状态跳转条件。
// wire signal define
wire INIT_IDLEtoINIT_PRE ;
wire INIT_TRPtoINIT_AR ;
wire INIT_TFRtoINIT_MRS ;
wire INIT_TFRtoINIT_AR ;
wire INIT_TMRDtoINIT_ENDS;
/************************************************************************/
// reg [7:0] state_c ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
state_c <= INIT_IDLE ;
else
state_c <= state_n ;
end
// reg [7:0] state_n ;
always @(*) begin
case(state_c)
INIT_IDLE : if(INIT_IDLEtoINIT_PRE)
state_n = INIT_PRE ;
else
state_n = INIT_IDLE ;
INIT_PRE : state_n = INIT_TRP ;
INIT_TRP : if(INIT_TRPtoINIT_AR)
state_n = INIT_AR ;
else
state_n = INIT_TRP ;
INIT_AR : state_n = INIT_TFR ;
INIT_TFR : if(INIT_TFRtoINIT_MRS)
state_n = INIT_MRS ;
else if(INIT_TFRtoINIT_AR) // 没有达到规定的刷新次数,回到刷新指令。
state_n = INIT_AR ;
else
state_n = INIT_TFR ;
INIT_MRS : state_n = INIT_TMRD ;
INIT_TMRD : if(INIT_TMRDtoINIT_ENDS)
state_n = INIT_ENDS ;
else
state_n = INIT_TMRD ;
INIT_ENDS : state_n = INIT_ENDS ; // 保持在此状态。
default : state_n = INIT_IDLE ;
endcase
end
assign INIT_IDLEtoINIT_PRE = (state_c == INIT_IDLE) && (cnt_init == T_POWER - 1) ;
assign INIT_TRPtoINIT_AR = (state_c == INIT_TRP ) && (cnt_init == T_RP - 1) ;
assign INIT_TFRtoINIT_MRS = (state_c == INIT_TFR ) && ((cnt_ar == MAX_CNT_AR - 1) && (cnt_init == T_RFC - 1)) ;
assign INIT_TFRtoINIT_AR = (state_c == INIT_TFR ) && ((cnt_ar != MAX_CNT_AR - 1) && (cnt_init == T_RFC - 1)) ;
assign INIT_TMRDtoINIT_ENDS = (state_c == INIT_TMRD) && (cnt_init == T_MRD - 1) ;
// reg [15:0] cnt_init ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
cnt_init <= 16'd0 ;
else
case (state_c)
INIT_IDLE : if(cnt_init == T_POWER - 1)
cnt_init <= 16'd0 ;
else
cnt_init <= cnt_init + 1'b1 ;
INIT_PRE : cnt_init <= 16'd0 ;
INIT_TRP : if(cnt_init == T_RP - 1)
cnt_init <= 16'd0 ;
else
cnt_init <= cnt_init + 1'b1 ;
INIT_AR : cnt_init <= 16'd0 ;
INIT_TFR : if(cnt_init == T_RFC - 1)
cnt_init <= 16'd0 ;
else
cnt_init <= cnt_init + 1'b1 ;
INIT_MRS : cnt_init <= 16'd0 ;
INIT_TMRD : if(cnt_init == T_MRD - 1)
cnt_init <= 16'd0 ;
else
cnt_init <= cnt_init + 1'b1 ;
INIT_ENDS : cnt_init <= 16'd0 ;
default : cnt_init <= 16'd0 ;
endcase
end
// reg [3:0] cnt_ar ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
cnt_ar <= 4'd0 ;
else if((state_c == INIT_TFR) && (cnt_init == T_RFC - 1) && (cnt_ar == MAX_CNT_AR - 1))
cnt_ar <= 4'd0 ;
else if((state_c == INIT_TFR) && (cnt_init == T_RFC - 1))
cnt_ar <= cnt_ar + 1'b1 ;
else
cnt_ar <= cnt_ar ;
end
// reg [3:0] init_cmd ,
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
init_cmd <= INIT_NOP ;
else
case (state_c)
INIT_IDLE : init_cmd <= INIT_NOP ;
INIT_PRE : init_cmd <= INIT_PREC;
INIT_TRP : init_cmd <= INIT_NOP ;
INIT_AR : init_cmd <= INIT_REF ;
INIT_TFR : init_cmd <= INIT_NOP ;
INIT_MRS : init_cmd <= INIT_MOD ;
INIT_TMRD : init_cmd <= INIT_NOP ;
INIT_ENDS : init_cmd <= INIT_NOP ;
default : init_cmd <= INIT_NOP ;
endcase
end
// reg [1:0] init_ba ,
// reg [12:0] init_addr ,
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
init_ba <= 2'b11 ;
init_addr <= 13'h1FFF ;
end
else if(state_c == INIT_MRS) begin
init_ba <= 2'b00 ;
init_addr <= 13'b000_0_00_011_0_111 ;
end
else begin
init_ba <= 2'b11 ;
init_addr <= 13'h1FFF ;
end
end
// reg init_end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
init_end <= 1'b0 ;
else if(state_c == INIT_ENDS)
init_end <= 1'b1 ;
else
init_end <= init_end ;
end
endmodule
`timescale 1ns/1ns
module test_sdram_init ();
reg sys_clk ; // 100mHz
reg sys_rst_n ;
wire [3:0] init_cmd ;
wire [1:0] init_ba ;
wire [12:0] init_addr ;
wire init_end ;
sdram_init sdram_init_inst(
.sys_clk ( sys_clk ) ,
.sys_rst_n ( sys_rst_n) ,
.init_cmd ( init_cmd ) ,
.init_ba ( init_ba ) ,
.init_addr ( init_addr) ,
.init_end ( init_end )
);
parameter CYCLE = 10 ; // 10ns
initial begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#(CYCLE) ;
sys_rst_n <= 1'b1 ;
#(CYCLE * 250_00) ;
$stop ;
end
always #(CYCLE / 2) sys_clk = ~sys_clk ;
endmodule
分析时序图可知,刷新模块要做的是先传预充电时序,然后传刷新时序>=2次。简化版的初始化。
自动刷新周期为7.5us。设参数MAX_CNT_REF = 750.
在初始化结束信号拉高后,cnt_750us开始计数,计满后aref_req拉高,等待仲裁模块给出aref_en拉高,开始进行刷新状态机跳转。当跳转到预充电命令时,说明已经开始刷新了,aref_req拉低。
根据时序,绘制状态跳转。
当进入结束状态时,发出一个周期的aref_end==1信号,仲裁模块把aref_en拉低。完成一次刷新。
cnt_750us计数器是在init_end==1后循环计数的。
cnt_ar记录刷新次数,要刷新两次才可以。