sdram读写控制器
实验目标:
设计并实现一个 SDRAM 数据读写控制器,使用 PC 机通过串口向 SDRAM 写入 10 字
节数据,并将写入的 10 字节数据读出,通过串口回传至 PC 机,在串口助手上位机上打印
显示回传数据。
框图设计:
第一部分:
sdram基本操作实的实现sdram_ctrl
要实现数据的读写,还要有初始化和刷新操作。所以该模块要有分别产生这四条指令的模块。
由于时序冲突问题,刷新,和读写指令存在优先级的问题。所以还应有仲裁模块。
如下:
指令产生模块应该有指令端口,bank地址端口,行列地址端口,结束标志信号。
刷新模块的请求信号传向仲裁模块,反馈给刷新模块一个使能信号。


写指令模块的写数据,写地址,写突发长度。必不可少。



需要注意的是有几个输入端口是外界给的,有几个输出端口直接输出出去。
整体:

第二部分:
sdram控制器sdram_top
fifo控制模块,实现sdram读写数据的跨时钟域处理。



第三部分:
uart_sdram。uart与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读写控制器工程确实挺大的。
代码设计:
sdram_ctrl:
sdram_init:
设计思路:
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

sdram_aref:

设计思路:
分析时序图可知,刷新模块要做的是先传预充电时序,然后传刷新时序>=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记录刷新次数,要刷新两次才可以。

时序图:

代码:
// 模块说明:刷新模块,在初始化完成后,以750us为周期进行刷新时序的输出(要受仲裁模块控制),但刷新周期不会大于750us。
module sdram_aref(
input wire sys_clk ,
input wire sys_rst_n ,
input wire init_end ,
input wire aref_en ,
output reg aref_req ,
output reg [3:0] aref_cmd ,
output wire [1:0] aref_ba ,
output wire [12:0] aref_addr ,
output wire aref_end
);
// parameter
parameter MAX_CNT_750US = 750 ;
// localparam
localparam T_RP = 2 , // 2个时钟周期的预充电时间。
T_RFC = 7 , // 7个时钟周期的刷新时间。
MAX_CNT_AR = 2 , // 刷新次数。
AREF_NOP = 4'b0111 , // 空指令
AREF_PREC = 4'b0010 , // 预充电
AREF_REFC = 4'b0001 ; // 自动刷新,指令与状态机命名冲突了,后面加个C。
localparam AREF_IDLE = 6'b00_0001 ,
AREF_PCHA = 6'b00_0010 ,
AREF_TRP = 6'b00_0100 ,
AREF_REF = 6'b00_1000 ,
AREF_TRF = 6'b01_0000 ,
AREF_END = 6'b10_0000 ;
// reg signal define
reg [5:0] state_c ;
reg [5:0] state_n ;
reg [9:0] cnt_750us ;
reg [3:0] cnt_aref ;
reg cnt_ar ;
// wire signal define
wire AREF_IDLEtoAREF_PCHA;
wire AREF_TRPtoAREF_REF ;
wire AREF_TRFtoAREF_REF ;
wire AREF_TRFtoAREF_END ;
/*************************************************************/
// reg [5:0] state_c ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
state_c <= AREF_IDLE ;
else
state_c <= state_n ;
end
// reg [5:0] state_n ;
always @(*) begin
case(state_c)
AREF_IDLE : if(AREF_IDLEtoAREF_PCHA)
state_n = AREF_PCHA ;
else
state_n = AREF_IDLE ;
AREF_PCHA : state_n = AREF_TRP ;
AREF_TRP : if(AREF_TRPtoAREF_REF)
state_n = AREF_REF ;
else
state_n = AREF_TRP ;
AREF_REF : state_n = AREF_TRF ;
AREF_TRF : if(AREF_TRFtoAREF_END)
state_n = AREF_END ;
else if(AREF_TRFtoAREF_REF)
state_n = AREF_REF ;
else
state_n = AREF_TRF ;
AREF_END : state_n = AREF_IDLE ;
default : state_n = AREF_IDLE ;
endcase
end
assign AREF_IDLEtoAREF_PCHA = (state_c == AREF_IDLE) && ( aref_en ) ;
assign AREF_TRPtoAREF_REF = (state_c == AREF_TRP ) && ( cnt_aref ) ;
assign AREF_TRFtoAREF_REF = (state_c == AREF_TRF ) && ( cnt_aref == 6 && ~cnt_ar ) ;
assign AREF_TRFtoAREF_END = (state_c == AREF_TRF ) && ( cnt_aref == 6 && cnt_ar ) ;
// reg [9:0] cnt_750us ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
cnt_750us <= 10'd0 ;
else if(init_end)
begin
if(cnt_750us == MAX_CNT_750US - 1)
cnt_750us <= 10'd0 ;
else
cnt_750us <= cnt_750us + 1'b1 ;
end
else
cnt_750us <= 10'd0 ;
end
// reg [3:0] cnt_aref ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
cnt_aref <= 4'd0 ;
else
case (state_c)
AREF_IDLE : cnt_aref <= 4'd0 ;
AREF_PCHA : cnt_aref <= 4'd0 ;
AREF_TRP : if(cnt_aref == T_RP -1)
cnt_aref <= 4'd0 ;
else
cnt_aref <= cnt_aref + 1'b1 ;
AREF_REF : cnt_aref <= 4'd0 ;
AREF_TRF : if(cnt_aref == T_RFC - 1)
cnt_aref <= 4'd0 ;
else
cnt_aref <= cnt_aref + 1'b1 ;
AREF_END : cnt_aref <= 4'd0 ;
default : cnt_aref <= 4'd0 ;
endcase
end
// reg cnt_ar ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
cnt_ar <= 1'b0 ;
else if(state_c == AREF_TRF && cnt_aref == T_RFC - 1) // 由于是一位宽的,就不用写归零情况了。
cnt_ar <= cnt_ar + 1'b1 ;
else
cnt_ar <= cnt_ar ;
end
// output signal describe
// reg aref_req ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
aref_req <= 1'b0 ;
else if(cnt_750us == MAX_CNT_750US - 1)
aref_req <= 1'b1 ;
else if(state_c == AREF_PCHA)
aref_req <= 1'b0 ;
else
aref_req <= aref_req ;
end
// reg [3:0] aref_cmd ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
aref_cmd <= 4'd0 ;
else
case (state_c)
AREF_IDLE : aref_cmd <= AREF_NOP ;
AREF_PCHA : aref_cmd <= AREF_PREC;
AREF_TRP : aref_cmd <= AREF_NOP ;
AREF_REF : aref_cmd <= AREF_REFC;
AREF_TRF : aref_cmd <= AREF_NOP ;
AREF_END : aref_cmd <= AREF_NOP ;
default : aref_cmd <= AREF_NOP ;
endcase
end
// wire [1:0] aref_ba ;
assign aref_ba = 2'b11 ;
// reg [12:0] aref_addr ;
assign aref_addr = 13'h1FFF ;
// wire aref_end ;
assign aref_end = (state_c == AREF_END) ? 1'b1 : 1'b0 ;
endmodule
仿真:
在仿真代码中模拟了一小部分仲裁。
`timescale 1ns/1ns
module test_sdram_aref ();
reg sys_clk ; // 50mHz
reg sys_rst_n ;
reg aref_en ; // 自刷新模块的输入,应该是仲裁模块传出。
wire [3:0] init_cmd ; // 初始化模块的输出。
wire [1:0] init_ba ;
wire [12:0] init_addr ;
wire init_end ;
wire aref_req ; // 自刷新模块输出
wire [3:0] aref_cmd ;
wire [1:0] aref_ba ;
wire [12:0] aref_addr ;
wire aref_end ;
wire [3:0] sdram_cmd ;
wire [1:0] sdram_ba ;
wire [12:0] sdram_addr ;
assign sdram_cmd = (init_end) ? aref_cmd : init_cmd ; // 经过仲裁后的命令与地址。
assign sdram_ba = (init_end) ? aref_ba : init_ba ;
assign sdram_addr = (init_end) ? aref_addr : init_addr ;
wire c0_sig ; // pll的输出。
wire c1_sig ;
wire c2_sig ;
wire locked_sig ;
wire rst_n ; // 全局复位。
assign rst_n = sys_rst_n && (locked_sig) ;
sdram_aref sdram_aref_inst(
.sys_clk ( c1_sig ) ,
.sys_rst_n ( rst_n ) ,
.init_end ( init_end ) ,
.aref_en ( aref_en ) ,
.aref_req ( aref_req ) ,
.aref_cmd ( aref_cmd ) ,
.aref_ba ( aref_ba ) ,
.aref_addr ( aref_addr ) ,
.aref_end ( aref_end )
);
clk_gen clk_gen_inst (
.areset (~sys_rst_n ) ,
.inclk0 ( sys_clk ) ,
.c0 ( c0_sig ) , // 50
.c1 ( c1_sig ) , // 100
.c2 ( c2_sig ) , // 100+shift30
.locked ( locked_sig )
);
sdram_model_plus sdram_model_plus_inst(
.Dq ( ) ,
.Addr ( sdram_addr ) ,
.Ba ( sdram_ba ) ,
.Clk ( c2_sig ) ,
.Cke ( 1'b1 ) ,
.Cs_n ( sdram_cmd[3]) ,
.Ras_n ( sdram_cmd[2]) ,
.Cas_n ( sdram_cmd[1]) ,
.We_n ( sdram_cmd[0]) ,
.Dqm ( 2'b00 ) ,
.Debug ( 1'b1 )
);
sdram_init sdram_init_inst(
.sys_clk ( c1_sig ) ,
.sys_rst_n ( rst_n ) ,
.init_cmd ( init_cmd ) ,
.init_ba ( init_ba ) ,
.init_addr ( init_addr) ,
.init_end ( init_end )
);
parameter CYCLE = 20 ; // 10ns
defparam sdram_model_plus_inst.addr_bits = 13 ;
defparam sdram_model_plus_inst.data_bits = 16 ;
defparam sdram_model_plus_inst.col_bits = 9 ;
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024 ;
// aref_en 仲裁
always @(posedge c1_sig or negedge sys_rst_n) begin
if(~sys_rst_n)
aref_en <= 1'b0 ;
else if(init_end && aref_req)
aref_en <= 1'b1 ;
else if(aref_end)
aref_en <= 1'b0 ;
else
aref_en <= aref_en ;
end
initial begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#(CYCLE) ;
sys_rst_n <= 1'b1 ;
#(CYCLE * 250_00) ;
end
always #(CYCLE / 2) sys_clk = ~sys_clk ;
endmodule

sdram_write:
设计思路:

&n

本文围绕SDRAM读写控制器展开,目标是实现PC机通过串口向SDRAM读写数据并回传显示。介绍了框图设计,包括sdram_ctrl、sdram_top等模块;给出了各模块的指令;详细阐述代码设计,含sdram_init、sdram_aref等模块的设计思路、时序图、代码及仿真;最后进行了上板验证。
最低0.47元/天 解锁文章
164

被折叠的 条评论
为什么被折叠?



