欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于FPGA的SDRAM控制器设计(3)

程序员文章站 2024-02-25 11:45:28
...

SDRAM工作状态转移图

SDRAM芯片是按照一定的状态时序操作的,所以我们为了更好的写代码需要清楚SDRAM的状态转移图,如下:
基于FPGA的SDRAM控制器设计(3)
其中粗线是执行完该状态后,不需要我们给什么命令,自动转移到下一个状态,但是细线需要我们给出相应的命令。
这里引用Kevin老师对其中一个过程的描述,从“IDLE”状态跳到“WRITE”状态的路线以及从“WRITE”状态跳到“IDLE”状态的路线:
IDLE 状态到 WRITE 状态:

  1. 在 IDLE 状态需要先给 ACT 命令**某一行, 此时处于 Row Active 状态;
  2. 在 Row Active 状态之后, 给 Write 命令则会进入 WRITE 状态;
  3. 在 WRITE 状态后, 再给一次 Write 命令, 就可以继续写入数据。
    WRITE 状态到 IDLE 状态:
  4. 在 WRITE 状态给 PRE 命令, 则 SDRAM 将跳出 WRITE 状态进入 Precharge
    状态;
  5. 在 Precharge 状态后, 就会自动进入 IDLE 状态了
    要从 WRITE 状态跳到 IDLE 状态的一个原因是, 我们需要进行刷新操作, 进入刷新操作, 必须从 IDLE 状态进入。我们注意到WRITE 状态下边还有一个 WRITEA 状态,执行完一次WRITE状态它会自动的进入到
    Precharge 状态,因此比在 WRITE 状态的工作效率要低很多,也比较少使用。

SDRAM写时序图

从SDRAM的技术手册中我们很容易找到对应的写时序图,如下:
基于FPGA的SDRAM控制器设计(3)
上面对应的时间也可以从手册中找到,在上两篇关于SDRAM的文章中我们已经给出了每个时间的最小值。

SDRAM的读时序图

同理,我们给出SDRAM的读时序图,如下:
基于FPGA的SDRAM控制器设计(3)
这里是给出读延迟2个时钟的时序图,注意我们模式寄存器中设置的是三个时钟延迟。同时,我们注意,***SDRAM的状态转移图,读写操作是对称的,读写的时序也是对称的,所以我们在写完写操作之后可以利用notepad++的全部替换功能,替换一下写模块的名称、写操作的命令、删除写模块的数据端口并增加都模块的使能端口,便可以把写模块转换成读模块。***所以接下来我们将以写模块为例进行设计SDRAM的读写模块。

同学们也可以对比上面两个时序图与SDRAM的状态转移图会发现两者完美契合。

SDRAM写模块状态机

假设我们现在需要往 SDRAM 中写入两行数据, 那什么时候可以退出仲裁状
态机的写状态:

  1. 数据已经写完;
  2. SDRAM 需要进行刷新操作;
  3. 数据未写完, 需要**下一行继续写。

为什么按照上面的条件进行设计SDRAM的写模块,因为上面的条件比较齐全,可以将我们所需要考虑的情况考虑的比较完善。

这里也是使用了Kevin的状态机,但是没有按照该老师的时序图设计,因为自我感觉有了状态机再看时序图反倒有些多余。
基于FPGA的SDRAM控制器设计(3)
这里虽然没使用Kevin老师的时序图,这里也给出来,方便大家调试,对这里有问题的同学可以参考该老师的教学视频,但是还是支持大家自我完成该模块的设计。
基于FPGA的SDRAM控制器设计(3)
基于FPGA的SDRAM控制器设计(3)
至此我们便完成了SDRAM的读写理论设计,接下来我们便完成该模块的代码设计。

SDRAM的读写操作代码

SDRAM顶层模块的设计,这里包括仲裁模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author       : zhangningning
// Email        : aaa@qq.com
// Website      : 
// Module Name  : sdram_top.v
// Create Time  : 2020-02-09 17:22:24
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module sdram_top(
    //System Interfaces
    input                   sclk            ,
    input                   rst_n           ,
    //SDRAM Interfaces
    output  wire            sdram_clk       ,
    output  wire            sdram_cke       ,
    output  reg             sdram_cs_n      ,
    output  reg             sdram_cas_n     ,
    output  reg             sdram_ras_n     ,
    output  reg             sdram_we_n      ,
    output  reg     [ 1:0]  sdram_bank      ,
    output  reg     [11:0]  sdram_addr      ,
    output  wire    [ 1:0]  sdram_dqm       ,
    inout           [15:0]  sdram_dq        ,
    //Others
    input                   wr_trig         ,
    input                   rd_trig                 
);
 
//========================================================================================\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
localparam      NOP     =   4'b0111         ;

localparam      IDLE    =   5'b0_0001       ;
localparam      ARBIT   =   5'b0_0010       ;
localparam      AREF    =   5'b0_0100       ;
localparam      WRITE   =   5'b0_1000       ;
localparam      READ    =   5'b1_0000       ;
//sdram_init
wire                [ 3:0]  init_cmd        ;
wire                [11:0]  init_addr       ;
wire                        init_done       ;
//AREF
wire                        aref_req        ;
reg                         aref_en         ;
wire                        aref_end        ;
wire                [ 3:0]  aref_cmd        ;
wire                [11:0]  aref_addr       ;
//WRITE
wire                [ 3:0]  wr_cmd          ;
wire                [11:0]  wr_addr         ;
wire                [ 1:0]  wr_bank_addr    ;
wire                [15:0]  wr_data         ;    
reg                         wr_en           ;
wire                        wr_end          ;
wire                        wr_req          ;
//READ
wire                [ 3:0]  rd_cmd          ;
wire                [11:0]  rd_addr         ;
wire                [ 1:0]  rd_bank_addr    ;
reg                         rd_en           ;
wire                        rd_end          ;
wire                        rd_req          ;
wire                        rd_data_en      ;
//ARBIT
reg                 [ 4:0]  state           ;

//Others
reg                 [15:0]  sdram_dq1       ;
reg                         sdram_dq_en     ;
 
//========================================================================================\
//**************     Main      Code        **********************************
//========================================================================================/
assign      sdram_dqm       =       2'b00;
assign      sdram_clk       =       ~sclk;
assign      sdram_cke       =       1'b1;
assign      sdram_dq        =       sdram_dq_en == 1'b1 ? sdram_dq1 : 16'hzzzz;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        state       <=      IDLE;
    else case(state)
        IDLE    :   if(init_done == 1'b1)
                        state           <=      ARBIT;
                    else 
                        state           <=      state;
        ARBIT   :   if(aref_req == 1'b1)
                        state           <=      AREF;
                    else if(wr_req == 1'b1)
                        state           <=      WRITE;
                    else if(rd_req == 1'b1)
                        state           <=      READ;
                    else
                        state           <=      state;
        AREF    :   if(aref_end == 1'b1)
                        state           <=      ARBIT;
                    else
                        state           <=      state;
        WRITE   :   if(wr_end == 1'b1)
                        state           <=      ARBIT;
                    else 
                        state           <=      state;
        READ    :   if(rd_end == 1'b1)
                        state           <=      ARBIT;
                    else
                        state           <=      state;                        
        default :   state           <=      IDLE;
    endcase

always @(*)
    case(state)
        IDLE    :   begin
                        sdram_addr      =      init_addr;
                        {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n}      =       init_cmd;
                        sdram_dq_en     =       1'b0;
                    end
        AREF    :   begin
                        sdram_addr      =      aref_addr;
                        {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n}      =       aref_cmd;
                    end
        WRITE   :   begin
                        sdram_addr      =      wr_addr;
                        {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n}      =       wr_cmd;
                        sdram_dq1       =      wr_data;
                        sdram_bank      =      wr_bank_addr;
                        sdram_dq_en     =      1'b1;
                    end
        READ    :   begin
                        sdram_addr      =      rd_addr;
                        {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n}      =       rd_cmd;
                        sdram_bank      =      rd_bank_addr;
                        sdram_dq_en     =      1'b0;  
                    end
        default :   begin
                        sdram_addr      =      12'd0;
                        {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n}      =       NOP;
                        sdram_dq1       =      16'd0;
                        sdram_bank      =      2'b00;
                        sdram_dq_en     =      1'b0;
                    end
    endcase

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        aref_en             <=      1'b0;
    else if(state == ARBIT && aref_req == 1'b1) 
        aref_en             <=      1'b1;
    else
        aref_en             <=      1'b0;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        wr_en               <=      1'b0;
    else if(state == ARBIT && aref_req == 1'b1) 
        wr_en               <=      1'b0;
    else if(state == ARBIT && wr_req == 1'b1) 
        wr_en               <=      1'b1;
    else
        wr_en               <=      1'b0;
        
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        rd_en               <=      1'b0;
    else if(state == ARBIT && aref_req == 1'b1) 
        rd_en               <=      1'b0;
    else if(state == ARBIT && wr_req == 1'b1)
        rd_en               <=      1'b0;
    else if(state == ARBIT && rd_req == 1'b1)
        rd_en               <=      1'b1;
    else
        rd_en               <=      1'b0;

sdram_init sdram_init_inst(
    //System Interfaces
    .sclk                   (sclk                   ),
    .rst_n                  (rst_n                  ),
    //SDRAM Interfaces
    .sdram_cmd              (init_cmd               ),
    .sdram_addr             (init_addr              ),
    //Others
    .init_done              (init_done              )
);

sdram_aref sdram_aref_inst(
    //Sysytem Interfaces
    .sclk                   (sclk                   ),
    .rst_n                  (rst_n                  ),
    //SDRAM Interfaces
    .aref_cmd               (aref_cmd               ),
    .aref_addr              (aref_addr              ),
    //Others
    .aref_req               (aref_req               ),
    .aref_end               (aref_end               ),
    .aref_en                (aref_en                ),
    .init_done              (init_done              )
);

sdram_write sdram_write_inst(
    //System Interfaces
    .sclk                   (sclk                   ),
    .rst_n                  (rst_n                  ),
    //SDRAM Interfaces
    .wr_cmd                 (wr_cmd                 ),
    .wr_addr                (wr_addr                ),
    .bank_addr              (wr_bank_addr           ),
    .wr_data                (wr_data                ), 
    //Communication Interfaces
    .wr_trig                (wr_trig                ),
    .wr_en                  (wr_en                  ),
    .wr_end                 (wr_end                 ),
    .wr_req                 (wr_req                 ),
    .aref_req               (aref_req               )      
);

sdram_read sdram_read_inst(
    //System Interfaces
    .sclk                   (sclk                   ),
    .rst_n                  (rst_n                  ),
    //SDRAM Interfaces
    .rd_cmd                 (rd_cmd                 ),
    .rd_addr                (rd_addr                ),
    .bank_addr              (rd_bank_addr           ),    
    //Communication Interfaces
    .rd_trig                (rd_trig                ),
    .rd_en                  (rd_en                  ),
    .rd_end                 (rd_end                 ),
    .rd_req                 (rd_req                 ),
    .aref_req               (aref_req               ),
    .rd_data_en             (rd_data_en             )
);

 
endmodule

SDRAM上电初始化模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author       : zhangningning
// Email        : aaa@qq.com
// Website      : 
// Module Name  : sdram_init.v
// Create Time  : 2020-02-09 16:20:31
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module sdram_init(
    //System Interfaces
    input                   sclk            ,
    input                   rst_n           ,
    //SDRAM Interfaces
    output  reg     [ 3:0]  sdram_cmd       ,
    output  reg     [11:0]  sdram_addr      ,
    //Others
    output  reg             init_done  
);
 
//========================================================================================\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
localparam  DELAY_200US =  20000           ;
//SDRAM Command
localparam  NOP         =  4'b0111          ;
localparam  PRE         =  4'b0010          ;
localparam  AREF        =  4'b0001          ;
localparam  MSET        =  4'b0000          ;

reg                 [14:0]  cnt_200us       ;
reg                         flag_200us      ;
reg                 [ 4:0]  cnt_cmd         ;
 
//========================================================================================\
//**************     Main      Code        **********************************
//========================================================================================/
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        cnt_200us       <=      11'd0;
    else if(flag_200us == 1'b0)
        cnt_200us       <=      cnt_200us + 1'b1;
    else
        cnt_200us       <=      cnt_200us;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_200us      <=      1'b0;
    else if(cnt_200us >= DELAY_200US) 
        flag_200us      <=      1'b1;
    else
        flag_200us      <=      flag_200us;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        cnt_cmd         <=      5'd0;
    else if(flag_200us == 1'b1 && cnt_cmd <= 5'd19)
        cnt_cmd         <=      cnt_cmd + 1'b1;
    else 
        cnt_cmd         <=      cnt_cmd;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        sdram_cmd       <=      NOP;
    else case(cnt_cmd)
        1       :   sdram_cmd       <=      PRE;
        3       :   sdram_cmd       <=      AREF;
        11      :   sdram_cmd       <=      AREF;
        19      :   sdram_cmd       <=      MSET;
        default :   sdram_cmd       <=      NOP;
    endcase

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        sdram_addr      <=      12'b0100_0000_0000;
    else if(cnt_cmd == 5'd19) 
        sdram_addr      <=      12'b0000_0011_0010;
    else
        sdram_addr      <=      12'b0100_0000_0000;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        init_done       <=      1'b0; 
    else if(cnt_cmd > 5'd19) 
        init_done       <=      1'b1;
    else
        init_done       <=      init_done;
        
endmodule

SDRAM自刷新模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author       : zhangningning
// Email        : aaa@qq.com
// Website      : 
// Module Name  : sdram_aref.v
// Create Time  : 2020-02-10 13:34:03
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module sdram_aref(
    //Sysytem Interfaces
    input                   sclk            ,
    input                   rst_n           ,
    //SDRAM Interfaces
    output  reg     [ 3:0]  aref_cmd        ,
    output  wire    [11:0]  aref_addr       ,
    //Others
    input                   init_done       ,
    output  reg             aref_req        ,
    output  reg             aref_end        ,
    input                   aref_en         
);
 
//========================================================================================\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
localparam  NOP         =   4'b0111         ;
localparam  PRE         =   4'b0010         ;
localparam  AREF        =   4'b0001         ;
localparam  DELAY_15US  =   11'd1500        ;

reg                         aref_flag       ;
reg             [ 2:0]      cnt_cmd         ;
reg             [10:0]      cnt_15ms        ;

 
//========================================================================================\
//**************     Main      Code        **********************************
//========================================================================================/
assign  aref_addr           =       12'b0100_0000_0000;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        aref_flag           <=      1'b0; 
    else if(cnt_cmd >= 3'd7)
        aref_flag           <=      1'b0;
    else if(aref_en == 1'b1)
        aref_flag           <=      1'b1;
    else
        aref_flag           <=      aref_flag;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        cnt_cmd             <=      3'd0;
    else if(cnt_cmd >= 3'd7)
        cnt_cmd             <=      3'd0;
    else if(aref_flag == 1'b1)
        cnt_cmd             <=      cnt_cmd + 1'b1; 
    
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        aref_cmd            <=      NOP;    
    else case(cnt_cmd)
        1       :   aref_cmd            <=      PRE;
        4       :   aref_cmd            <=      AREF;
        default :   aref_cmd            <=      NOP;
    endcase
   
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        aref_end            <=      1'b0;
    else if(cnt_cmd >= 3'd7)
        aref_end            <=      1'b1;
    else
        aref_end            <=      1'b0;   
          
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        cnt_15ms            <=      20'd0;
    else if(cnt_15ms == DELAY_15US)
        cnt_15ms            <=      20'd0;
    else if(init_done == 1'b1) 
        cnt_15ms            <=      cnt_15ms + 1'b1;
    else
        cnt_15ms            <=      20'd0;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        aref_req            <=      1'b0; 
    else if(cnt_15ms == DELAY_15US)
        aref_req            <=      1'b1;
    else if(aref_en == 1'b1)
        aref_req            <=      1'b0;
    else
        aref_req            <=      aref_req;
            

endmodule

SDRAM写模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author       : zhangningning
// Email        : aaa@qq.com
// Website      : 
// Module Name  : sdram_write.v
// Create Time  : 2020-02-10 20:05:26
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module sdram_write(
    //System Interfaces
    input                   sclk            ,
    input                   rst_n           ,
    //SDRAM Interfaces
    output  reg     [ 3:0]  wr_cmd          ,
    output  reg     [11:0]  wr_addr         ,
    output  wire    [ 1:0]  bank_addr       ,
    output  reg     [15:0]  wr_data         ,     
    //Communication Interfaces
    input                   wr_trig         ,
    input                   wr_en           ,
    output  reg             wr_end          ,
    output  reg             wr_req          ,
    input                   aref_req        
);
 
//========================================================================================\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
// Define State
localparam  S_IDLE      =   5'b0_0001       ;
localparam  S_REQ       =   5'b0_0010       ;
localparam  S_ACT       =   5'b0_0100       ;
localparam  S_WR        =   5'b0_1000       ;
localparam  S_PRE       =   5'b1_0000       ;
// SDRAM Command
localparam  CMD_NOP     =   4'b0111         ;
localparam  CMD_PRE     =   4'b0010         ;
localparam  CMD_AREF    =   4'b0001         ;
localparam  CMD_ACT     =   4'b0011         ;
localparam  CMD_WR      =   4'b0100         ;

reg                 [ 4:0]  state           ;
reg                         flag_act_end    ;
reg                         row_end         ;
reg                 [ 1:0]  burst_cnt       ;
reg                         data_end        ;
reg                         flag_row_end    ;
reg                         flag_data_end   ;
reg                         flag_aref_req   ;
reg                 [ 8:0]  col_addr        ;
reg                 [ 1:0]  burst_cnt_r     ;
reg                 [11:0]  row_addr        ;
reg                         data_end_r      ;
reg                         data_end_r2     ;
reg                         row_end_r       ;
reg                         row_end_r2      ; 
reg                         flag_s_wr       ;


//========================================================================================\
//**************     Main      Code        **********************************
//========================================================================================/
assign      bank_addr       =       2'b00;


always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        state           <=      S_IDLE;
    else case(state)
        S_IDLE    :   if(wr_trig == 1'b1)
                        state           <=      S_REQ;
                    else
                        state           <=      state;                        
        S_REQ   :   if(wr_en == 1'b1)
                        state           <=      S_ACT;
                    else
                        state           <=      state;                        
        S_ACT   :   if(flag_act_end == 1'b1)
                        state           <=      S_WR;
                    else
                        state           <=      state;                        
        S_WR    :   if(data_end_r == 1'b1 || row_end_r == 1'b1)
                        state           <=      S_PRE;
                    else if(burst_cnt_r == 2'd2 && aref_req == 1'b1)
                        state           <=      S_PRE;
                    else
                        state           <=      state;                        
        S_PRE   :   if(flag_data_end == 1'b1)
                        state           <=      S_IDLE;
                    else if(flag_aref_req == 1'b1)
                        state           <=      S_REQ;
                    else if(flag_row_end == 1'b1)
                        state           <=      S_ACT;
                    else 
                        state           <=      state;
        default :   state           <=      S_IDLE;
    endcase
  
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_aref_req       <=      1'b0;
    else if(state == S_PRE && aref_req == 1'b1)
        flag_aref_req       <=      1'b1;
    else
        flag_aref_req       <=      1'b0; 

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_act_end    <=      1'b0;    
    else if(state == S_ACT && wr_cmd == CMD_ACT)
        flag_act_end    <=      1'b1;
    else 
        flag_act_end    <=      1'b0; 

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        row_end         <=      1'b0;
    else if(col_addr == 9'd508 && burst_cnt == 2'd1)
        row_end         <=      1'b1;
    else
        row_end         <=      1'b0;

always @(posedge sclk)begin
    row_end_r   <=      row_end;
    row_end_r2  <=      row_end_r;
end
          
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        burst_cnt       <=      2'd0;
    else if(state == S_WR)
        burst_cnt       <=      burst_cnt + 1'b1;
    else
        burst_cnt       <=      2'd0;

always @(posedge sclk)
    burst_cnt_r     <=      burst_cnt;

 always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        data_end    <=      1'b0;     
    else if(row_addr == 12'd1 && col_addr == 9'd508 && burst_cnt == 2'd1) 
        data_end    <=      1'b1;
    else
        data_end    <=      1'b0;
            
always @(posedge sclk)begin
    data_end_r      <=      data_end;
    data_end_r2     <=      data_end_r;  
end  

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_row_end    <=      1'b0;   
    else if(state == S_PRE && row_end_r2 == 1'b1)
        flag_row_end    <=      1'b1;
    else 
        flag_row_end    <=      1'b0;
         
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_data_end   <=      1'b0;  
    else if(state == S_PRE && data_end_r2 == 1'b1) 
        flag_data_end   <=      1'b1;
    else
        flag_data_end   <=      1'b0;
        
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        col_addr        <=      12'd0;
    else if(state == S_WR && wr_cmd == CMD_WR && col_addr == 'd508)
        col_addr        <=      12'd0;   
    else if(state == S_WR && wr_cmd == CMD_WR)
        col_addr        <=      col_addr + 3'd4;
    else
        col_addr        <=      col_addr;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        row_addr        <=      12'd0;  
    else  if(data_end == 1'b1)
        row_addr        <=      12'd0;
    else if(state == S_WR && wr_cmd == CMD_WR && col_addr == 'd508)
        row_addr        <=      row_addr + 1'b1;
    else
        row_addr        <=      row_addr;
          
    
          
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        wr_cmd          <=      CMD_NOP;    
    else case(state)
        S_ACT   :   if(wr_cmd != CMD_ACT && flag_act_end == 1'b0)
                        wr_cmd          <=      CMD_ACT;
                    else
                        wr_cmd          <=      CMD_NOP;
        S_WR    :   if(data_end == 1'b1 || row_end == 1'b1)
                        wr_cmd          <=      CMD_NOP;
                    else if(burst_cnt_r == 2'd3 && aref_req == 1'b1)
                        wr_cmd          <=      CMD_NOP;
                    else if(burst_cnt == 2'd0)
                        wr_cmd          <=      CMD_WR;
                    else
                        wr_cmd          <=      CMD_NOP; 
        S_PRE   :   if(wr_cmd != CMD_PRE)
                        wr_cmd          <=      CMD_PRE;
                    else 
                        wr_cmd          <=      CMD_NOP;
                
        default :   wr_cmd          <=      CMD_NOP;
    endcase

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        wr_addr         <=      12'd0;    
    else case(state)
        S_ACT   :   if(wr_cmd != CMD_ACT)
                        wr_addr         <=      row_addr;
                    else
                        wr_addr         <=      12'd0;
        S_WR    :   if(data_end == 1'b1 || row_end == 1'b1)
                        wr_addr         <=      12'd0;
                    else if(burst_cnt_r == 2'd3 && aref_req == 1'b1)
                        wr_addr         <=      12'd0;
                    else if(burst_cnt == 2'd0)
                        wr_addr         <=      {3'b000,col_addr};
                    else
                        wr_addr         <=      12'd0; 
        S_PRE   :   wr_addr         <=      12'b0100_0000_0000;
        default :   wr_addr         <=      12'd0;
    endcase

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_s_wr       <=      1'b0;
    else if(state == S_WR) 
        flag_s_wr       <=      1'b1;
    else
        flag_s_wr       <=      1'b0;
        

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        wr_data         <=      16'd0;    
    else if(flag_s_wr == 1'b1)
        wr_data         <=      wr_data + 1'b1;
    else
        wr_data         <=      wr_data;   

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        wr_end          <=      1'b0;   
    else if(flag_data_end == 1'b1)
        wr_end          <=      1'b1;
    else if(state != S_IDLE && flag_aref_req == 1'b1 && wr_end == 1'b0)
        wr_end          <=      1'b1;
    else
        wr_end          <=      1'b0;
          
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        wr_req          <=      1'b0;  
    else if(wr_en == 1'b1)
        wr_req          <=      1'b0;
    else if(state == S_REQ)
        wr_req          <=      1'b1;
    else
        wr_req          <=      wr_req;
          
endmodule

SDRAM读模块:

`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author       : zhangningning
// Email        : aaa@qq.com
// Website      : 
// Module Name  : sdram_read.v
// Create Time  : 2020-02-11 20:48:41
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module sdram_read(
    //System Interfaces
    input                   sclk            ,
    input                   rst_n           ,
    //SDRAM Interfaces
    output  reg     [ 3:0]  rd_cmd          ,
    output  reg     [11:0]  rd_addr         ,
    output  wire    [ 1:0]  bank_addr       ,    
    //Communication Interfaces
    input                   rd_trig         ,
    input                   rd_en           ,
    output  reg             rd_end          ,
    output  reg             rd_req          ,
    input                   aref_req        ,
    output  reg             rd_data_en 
);
 
//========================================================================================\
//**************Define Parameter and  Internal Signals**********************************
//========================================================================================/
// Define State
localparam  S_IDLE      =   5'b0_0001       ;
localparam  S_REQ       =   5'b0_0010       ;
localparam  S_ACT       =   5'b0_0100       ;
localparam  S_RD        =   5'b0_1000       ;
localparam  S_PRE       =   5'b1_0000       ;
// SDRAM Command
localparam  CMD_NOP     =   4'b0111         ;
localparam  CMD_PRE     =   4'b0010         ;
localparam  CMD_AREF    =   4'b0001         ;
localparam  CMD_ACT     =   4'b0011         ;
localparam  CMD_RD      =   4'b0101         ;

reg                 [ 4:0]  state           ;
reg                         flag_act_end    ;
reg                         row_end         ;
reg                 [ 1:0]  burst_cnt       ;
reg                         data_end        ;
reg                         flag_row_end    ;
reg                         flag_data_end   ;
reg                         flag_aref_req   ;
reg                 [ 8:0]  col_addr        ;
reg                 [ 1:0]  burst_cnt_r     ;
reg                 [11:0]  row_addr        ;
reg                         data_end_r      ;
reg                         data_end_r2     ;
reg                         row_end_r       ;
reg                         row_end_r2      ; 
reg                 [ 3:0]  cnt_data        ;



//========================================================================================\
//**************     Main      Code        **********************************
//========================================================================================/
assign      bank_addr       =       2'b00;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        state           <=      S_IDLE;
    else case(state)
        S_IDLE    :   if(rd_trig == 1'b1)
                        state           <=      S_REQ;
                    else
                        state           <=      state;                        
        S_REQ   :   if(rd_en == 1'b1)
                        state           <=      S_ACT;
                    else
                        state           <=      state;                        
        S_ACT   :   if(flag_act_end == 1'b1)
                        state           <=      S_RD;
                    else
                        state           <=      state;                        
        S_RD    :   if(data_end_r == 1'b1 || row_end_r == 1'b1)
                        state           <=      S_PRE;
                    else if(burst_cnt_r == 2'd2 && aref_req == 1'b1)
                        state           <=      S_PRE;
                    else
                        state           <=      state;                        
        S_PRE   :   if(flag_data_end == 1'b1)
                        state           <=      S_IDLE;
                    else if(flag_aref_req == 1'b1)
                        state           <=      S_REQ;
                    else if(flag_row_end == 1'b1)
                        state           <=      S_ACT;
                    else 
                        state           <=      state;
        default :   state           <=      S_IDLE;
    endcase
  
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_aref_req       <=      1'b0;
    else if(state == S_PRE && aref_req == 1'b1)
        flag_aref_req       <=      1'b1;
    else
        flag_aref_req       <=      1'b0; 

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_act_end    <=      1'b0;    
    else if(state == S_ACT && rd_cmd == CMD_ACT)
        flag_act_end    <=      1'b1;
    else 
        flag_act_end    <=      1'b0; 

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        row_end         <=      1'b0;
    else if(col_addr == 9'd508 && burst_cnt == 2'd1)
        row_end         <=      1'b1;
    else
        row_end         <=      1'b0;

always @(posedge sclk)begin
    row_end_r   <=      row_end;
    row_end_r2  <=      row_end_r;
end
          
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        burst_cnt       <=      2'd0;
    else if(state == S_RD)
        burst_cnt       <=      burst_cnt + 1'b1;
    else
        burst_cnt       <=      2'd0;

always @(posedge sclk)
    burst_cnt_r     <=      burst_cnt;

 always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        data_end    <=      1'b0;     
    else if(row_addr == 12'd1 && col_addr == 9'd508 && burst_cnt == 2'd1) 
        data_end    <=      1'b1;
    else
        data_end    <=      1'b0;
            
always @(posedge sclk)begin
    data_end_r      <=      data_end;
    data_end_r2     <=      data_end_r;  
end  

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_row_end    <=      1'b0;   
    else if(state == S_PRE && row_end_r2 == 1'b1)
        flag_row_end    <=      1'b1;
    else 
        flag_row_end    <=      1'b0;
         
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        flag_data_end   <=      1'b0;  
    else if(state == S_PRE && data_end_r2 == 1'b1) 
        flag_data_end   <=      1'b1;
    else
        flag_data_end   <=      1'b0;
        
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        col_addr        <=      12'd0;
    else if(state == S_RD && rd_cmd == CMD_RD && col_addr == 'd508)
        col_addr        <=      12'd0;   
    else if(state == S_RD && rd_cmd == CMD_RD)
        col_addr        <=      col_addr + 3'd4;
    else
        col_addr        <=      col_addr;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        row_addr        <=      12'd0;  
    else  if(data_end == 1'b1)
        row_addr        <=      12'd0;
    else if(state == S_RD && rd_cmd == CMD_RD && col_addr == 'd508)
        row_addr        <=      row_addr + 1'b1;
    else
        row_addr        <=      row_addr;
          
    
          
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        rd_cmd          <=      CMD_NOP;    
    else case(state)
        S_ACT   :   if(rd_cmd != CMD_ACT && flag_act_end == 1'b0)
                        rd_cmd          <=      CMD_ACT;
                    else
                        rd_cmd          <=      CMD_NOP;
        S_RD    :   if(data_end == 1'b1 || row_end == 1'b1)
                        rd_cmd          <=      CMD_NOP;
                    else if(burst_cnt_r == 2'd3 && aref_req == 1'b1)
                        rd_cmd          <=      CMD_NOP;
                    else if(burst_cnt == 2'd0)
                        rd_cmd          <=      CMD_RD;
                    else
                        rd_cmd          <=      CMD_NOP; 
        S_PRE   :   if(rd_cmd != CMD_PRE)
                        rd_cmd          <=      CMD_PRE;
                    else 
                        rd_cmd          <=      CMD_NOP;
                
        default :   rd_cmd          <=      CMD_NOP;
    endcase

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        rd_addr         <=      12'd0;    
    else case(state)
        S_ACT   :   if(rd_cmd != CMD_ACT)
                        rd_addr         <=      row_addr;
                    else
                        rd_addr         <=      12'd0;
        S_RD    :   if(data_end == 1'b1 || row_end == 1'b1)
                        rd_addr         <=      12'd0;
                    else if(burst_cnt_r == 2'd3 && aref_req == 1'b1)
                        rd_addr         <=      12'd0;
                    else if(burst_cnt == 2'd0)
                        rd_addr         <=      {3'b000,col_addr};
                    else
                        rd_addr         <=      12'd0; 
        S_PRE   :   rd_addr         <=      12'b0100_0000_0000;
        default :   rd_addr         <=      12'd0;
    endcase

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        rd_end          <=      1'b0;   
    else if(flag_data_end == 1'b1)
        rd_end          <=      1'b1;
    else if(state != S_IDLE && flag_aref_req == 1'b1 && rd_end == 1'b0)
        rd_end          <=      1'b1;
    else
        rd_end          <=      1'b0;
          
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        rd_req          <=      1'b0;  
    else if(rd_en == 1'b1)
        rd_req          <=      1'b0;
    else if(state == S_REQ)
        rd_req          <=      1'b1;
    else
        rd_req          <=      rd_req;
          
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        cnt_data        <=      4'd0;
    else if(cnt_data == 4'd7)
        cnt_data        <=      4'd0;
    else if(cnt_data >= 4'd1)
        cnt_data        <=      cnt_data + 1'b1;
    else if(rd_cmd == CMD_RD)
        cnt_data        <=      cnt_data + 1'b1;
    else
        cnt_data        <=      4'd0;

always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
        rd_data_en      <=      1'b0;
    else if(cnt_data >= 4'd3 && cnt_data <= 4'd5)
        rd_data_en      <=      1'b1; 
    else
        rd_data_en      <=      1'b0;  

endmodule

观察上述模块很容易发现读写模块中最重要的就是上面的那个状态机。

SDRAM读写测试模块

这里给出顶层测试模块的编写:

`timescale 1ns / 1ps
`define CLOCK    10
// *********************************************************************************
// Project Name : OSXXXX
// Author       : zhangningning
// Email        : aaa@qq.com
// Website      : 
// Module Name  : sdram_init_tb.v
// Create Time  : 2020-02-09 17:10:08
// Editor       : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date             By              Version                 Change Description
// -----------------------------------------------------------------------
// XXXX       zhangningning          1.0                        Original
//  
// *********************************************************************************

module sdram_init_tb;
reg                         sclk            ;
reg                         rst_n           ;
wire                        sdram_clk       ;
wire                        sdram_cke       ;
wire                        sdram_cs_n      ;
wire                        sdram_cas_n     ;
wire                        sdram_ras_n     ;
wire                        sdram_we_n      ;
wire    [ 1:0]              sdram_bank      ;
wire    [11:0]              sdram_addr      ;
wire    [ 1:0]              sdram_dqm       ;
wire    [15:0]              sdram_dq        ;
reg                         wr_trig         ;
reg                         rd_trig         ;

initial begin
    sclk        =       1'b0;
    rst_n       <=      1'b0;
    wr_trig     <=      1'b0;
    rd_trig     <=      1'b0;
    #(100*`CLOCK);
    rst_n       <=      1'b1;
    #(250_000)

    wr_trig     <=      1'b1;
    #(`CLOCK)
    wr_trig     <=      1'b0;
    #(20_000)
    rd_trig     <=      1'b1;
    #(`CLOCK)
    rd_trig     <=      1'b0;
    #(20_000);

    wr_trig     <=      1'b1;
    #(`CLOCK)
    wr_trig     <=      1'b0;
    #(20_000);
    rd_trig     <=      1'b1;
    #(`CLOCK)
    rd_trig     <=      1'b0;
    #(20_000);
      
    $stop;
end
always  #(`CLOCK/2)     sclk        =       ~sclk;  

sdram_top sdram_top_inst(
    //System Interfaces
    .sclk                   (sclk                   ),
    .rst_n                  (rst_n                  ),
    //SDRAM Interfaces
    .sdram_clk              (sdram_clk              ),
    .sdram_cke              (sdram_cke              ),
    .sdram_cs_n             (sdram_cs_n             ),
    .sdram_cas_n            (sdram_cas_n            ),
    .sdram_ras_n            (sdram_ras_n            ),
    .sdram_we_n             (sdram_we_n             ),
    .sdram_bank             (sdram_bank             ),
    .sdram_addr             (sdram_addr             ),
    .sdram_dqm              (sdram_dqm              ),
    .sdram_dq               (sdram_dq               ),
    //Others
    .wr_trig                (wr_trig                ),
    .rd_trig                (rd_trig                )
);

defparam        sdram_model_plus_inst.addr_bits =       12;
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;            // 2M

sdram_model_plus sdram_model_plus_inst(
    .Dq                     (sdram_dq               ), 
    .Addr                   (sdram_addr             ), 
    .Ba                     (sdram_bank             ), 
    .Clk                    (sdram_clk              ), 
    .Cke                    (sdram_cke              ), 
    .Cs_n                   (sdram_cs_n             ), 
    .Ras_n                  (sdram_ras_n            ), 
    .Cas_n                  (sdram_cas_n            ), 
    .We_n                   (sdram_we_n             ), 
    .Dqm                    (sdram_dqm              ),
    .Debug                  (1'b1                   )
);

endmodule

这里为了博客的简洁性不再给出相应的仿真模型文件,同学们可以关注前几篇SDRAM的博客,也可以进群下载相应的工程。

SDRAM仿真测试

这里也不给出具体的仿真图形,因为数据太长,截图不方便,感兴趣的同学可以自己仿真试试,但是这里要给出一个之前所说奇怪的实验现象,就是4突发的最后一个数据的有限长度比一个时钟周期短的多:
基于FPGA的SDRAM控制器设计(3)
其实这里只需要下板验证一下,但是因为在家开发板没带回来,也就无法验证,这里很有可能是仿真模型的问题。即使SDRAM就是有这个坑人的特性,我们也可以用SDRAM来捕捉读数据,实在不行,我们还可以对sclk进行PLL移相操作。

参考文献

[1]、开源骚客

总结

创作不易,认为文章有帮助的同学们可以关注点赞支持。(工程也都在群中)对文章有什么看法或者需要更近一步交流的同学,可以加入下面的群:
基于FPGA的SDRAM控制器设计(3)