首页 > 代码库 > 简易SDRAM控制器的verilog代码实现

简易SDRAM控制器的verilog代码实现

SDRAM是每隔15us进行刷新一次,但是如果当SDRAM需要进行刷新时,而SDRAM正在写数据,这两个操作之间怎么进行协调呢?

需要保证写的数据不能丢失,所以,如果刷新的时间到了,先让写操作把正在写的4个数据(突发长度为4)写完,然后再去进行刷新操作;

而如果在执行读操作也遇到需要刷新的情况,也可以先让数据读完,再去执行刷新操作。

思路:SDRAM控制器包括初始化、读操作、写操作及自动刷新这些操作,给每一个操作写上一个模块独立开来,也便于我们每个模块的调试,显然这种思路是正确的;

虽然都是独立的模块,但很显然这几个模块之间又是相互关联的。如果SDRAM需要刷新了,而SDRAM却正在执行写操作,为了控制各个模块之间的工作关系,引入仲裁机制。


仲裁状态机 ↓

技术分享

仲裁机工作原理框图 ↓

技术分享

在仲裁模块中,初始化操作完成之后便进入到了“ARBIT”仲裁状态,只有处于仲裁状态的时候,仲裁机才能向其他模块发送命令。

当状态机处于“WRITE”写状态时,如果SDRAM刷新的时间到了,刷新模块同时向写模块和仲裁模块发送刷新请求ref_req信号,当写模块接受到ref_req之后,写模块在写完当前4个数据(突发长度为4)之后,写模块的写结束标志flag_wr_end拉高,然后状态机进入“ARBIT”仲裁状态;

处于仲裁状态之后,此时有刷新请求ref_req,然后状态机跳转到“AREF”状态并且仲裁模块发送ref_en刷新使能,然后刷新模块将刷新请求信号ref_req拉低并给sdram发送刷新的命令。

等刷新完毕之后,刷新模块给仲裁模块发送flag_ref_end刷新结束标志,状态机跳转到“ARBIT”仲裁状态。

当刷新完跳转到“ARBIT”仲裁状态之后,如果之前全部数据仍然没有写完(指的是全部数据,并不是一个突发长度的4个数据),那么此时仍然要给仲裁模块写请求“wr_req”,然后仲裁模块经过一系列判断之后,如果符合写操作的时机,那就给写模块一个写使能信号“wr_en”,然后跳转到“WRITE”写状态并且写模块开始工作。


 

 仲裁模块中状态机定义 ↓

//state
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            state    <=    IDLE;
        else case(state)
            IDLE:
                if(key[0] == 1b1)
                    state    <=    INIT;
                else
                    state    <=    IDLE;
            INIT:
                if(flag_init_end == 1b1)    //初始化结束标志
                    state    <=    ARBIT;
                else
                    state    <=    INIT;
            ARBIT:
                if(ref_req == 1b1)    //刷新请求到来且已经写完
                    state    <=    AREF;
                else if(ref_req == 1b0 && rd_en == 1b1)    //默认读操作优先于写操作
                    state    <=    READ;            
                else if(ref_req == 1b0 && wr_en == 1b1)    //无刷新请求且写请求到来
                    state    <=    WRITE;
                else
                    state    <=    ARBIT;
            AREF:
                if(flag_ref_end == 1b1)
                    state    <=    ARBIT;
                else
                    state    <=    AREF;
            WRITE:
                if(flag_wr_end == 1b1)
                    state    <=    ARBIT;
                else
                    state    <=    WRITE;
            READ:
                if(flag_rd_end == 1b1)
                    state    <=    ARBIT;
                else
                    state    <=    READ;
            default:
                state    <=    IDLE;            
        endcase    

key[0]作为我们初始化的一个使能信号,如果是实际下板子的时候,我们还需要给按键加一个按键消抖模块。当按键0按下之后,代表我们的SDRAM的初始化使能信号来了;

所以状态机从“IDLE”跳转到了“INIT”状态。在初始化状态,如果我们的初始化模块传来了初始化结束标志“flag_init_end”,那状态机跳转到“ARBIT”仲裁状态;

在仲裁状态中,第一个“if”是判断刷新请求的,这也就说明了我们刷新的优先级最高。

之后,如果处于仲裁状态,来了读使能信号或者写使能信号并且没有刷新请求,那状态机就跳转到对应的状态。

如果处于读或写的状态,当读结束标志或者写结束标志来临的时候(这里的写结束标志和读结束标志都是指突发读或突发写的结束标志),那么就会跳转到仲裁状态。


初始化模块 ↓

module    sdram_init(
        input    wire        sclk,        //系统时钟为50M,即T=20ns
        input    wire        s_rst_n,
        
        output    reg    [3:0]    cmd_reg,    //sdram命令寄存器
        output    reg    [11:0]    sdram_addr,    //地址线
        output    reg    [1:0]    sdram_bank,    //bank地址
        output    reg        flag_init_end    //sdram初始化结束标志    
        );
        
    parameter    CMD_END        =    4d11,        //初始化结束时的命令计数器的值
            CNT_200US    =    14d1_0000,    
            NOP        =    4b0111,    //空操作命令
            PRECHARGE    =    4b0010,    //预充电命令
            AUTO_REF    =    4b0001,    //自刷新命令
            MRSET        =    4b0000;    //模式寄存器设置命令
 
    reg    [13:0]    cnt_200us;        //200us计数器
    reg        flag_200us;        //200us结束标志(200us结束后,一直拉高)
    reg    [3:0]    cnt_cmd;        //命令计数器,便于控制在某个时候发送特定指令
    reg        flag_init;        //初始化标志:初始化结束后,该标志拉低
//flag_init 
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_init    <=    1b1;
        else if(cnt_cmd == CMD_END)
            flag_init    <=    1b0;    
//cnt_200us    
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cnt_200us    <=    14d0;
        else if(cnt_200us == CNT_200US)
            cnt_200us    <=    14d0;
        else if(flag_200us == 1b0)
            cnt_200us    <=    cnt_200us + 1b1;
//flag_200us
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_200us    <=    1b0;
        else if(cnt_200us == CNT_200US)
            flag_200us    <=    1b1;
//cnt_cmd
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cnt_cmd    <=    4d0;
        else if(flag_200us == 1b1 && flag_init == 1b1)
            cnt_cmd    <=    cnt_cmd + 1b1;
//flag_init_end
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_init_end    <=    1b0;
        else if(cnt_cmd == CMD_END)
            flag_init_end    <=    1b1;
        else
            flag_init_end    <=    1b0;
//cmd_reg
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cmd_reg    <=    NOP;
        else if(cnt_200us == CNT_200US)
            cmd_reg    <=    PRECHARGE;
        else if(flag_200us)
            case(cnt_cmd)
                4d0:
                    cmd_reg    <=    AUTO_REF;    //预充电命令
                4d6:
                    cmd_reg    <=    AUTO_REF;
                4d10:
                    cmd_reg    <=    MRSET;        //模式寄存器设置
                default:
                    cmd_reg    <=    NOP;
            endcase
//sdram_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_addr    <=    12d0;
        else case(cnt_cmd)
            4d0:
                sdram_addr    <=    12b0100_0000_0000;    //预充电时,A10拉高,对所有Bank操作
            4d10:
                sdram_addr    <=    12b0000_0011_0010;    //模式寄存器设置时的指令:CAS=2,Burst Length=4;
            default:
                sdram_addr    <=    12d0;
        endcase
//sdram_bank
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_bank    <=    2d0;    //这里仅仅只是初始化,在模式寄存器设置时才会用到且其值为全零,故不赋值
 
//sdram_clk
    assign    sdram_clk    =    ~sclk;
            
endmodule

首先,我们需要有200us的稳定期,所以我们便有了一个200us的计数器cnt_200us,而这个计数器是根据flag_200us的低电平来工作的。

falg_200us在200us计时之后一直拉高。在200us计满,即flag_200us拉高之后,我们就需要先给一个“NOP”命令,然后给两次“Precharge”命令,同时选中ALL Banks。


写操作模块 ↓

module    sdram_write(
        input    wire        sclk,
        input    wire        s_rst_n,
        input    wire        key_wr,
        input    wire        wr_en,        //来自仲裁模块的写使能
        input    wire        ref_req,    //来自刷新模块的刷新请求
        input    wire    [5:0]    state,        //顶层模块的状态
        
        output    reg    [15:0]    sdram_dq,    //sdram输入/输出端口
        //output    reg    [3:0]    sdram_dqm,    //输入/输出掩码
        output    reg    [11:0]    sdram_addr,    //sdram地址线
        output    reg    [1:0]    sdram_bank,    //sdram的bank地址线
        output    reg    [3:0]    sdram_cmd,    //sdram的命令寄存器
        output    reg        wr_req,        //写请求(不在写状态时向仲裁进行写请求)
        output    reg        flag_wr_end    //写结束标志(有刷新请求来时,向仲裁输出写结束)
        );
        
    parameter    NOP    =    4b0111,    //NOP命令
            ACT    =    4b0011,    //ACT命令
            WR    =    4b0100,    //写命令(需要将A10拉高)
            PRE    =    4b0010,    //precharge命令
            CMD_END    =    4d8,
            COL_END    =    9d508,        //最后四个列地址的第一个地址
            ROW_END    =    12d4095,    //行地址结束
            AREF    =    6b10_0000,    //自动刷新状态
            WRITE    =    6b00_1000;    //状态机的写状态
            
    reg        flag_act;            //需要发送ACT的标志            
    reg    [3:0]    cmd_cnt;            //命令计数器
    reg    [11:0]    row_addr;            //行地址
    reg    [11:0]    row_addr_reg;            //行地址寄存器
    reg    [8:0]    col_addr;            //列地址
    reg        flag_pre;            //在sdram内部为写状态时需要给precharge命令的标志
 
//flag_pre
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_pre    <=    1b0;
        else if(col_addr == 9d0 && flag_wr_end == 1b1)
            flag_pre    <=    1b1;
        else if(flag_wr_end == 1b1)
            flag_pre    <=    1b0;
    
//flag_act
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_act    <=    1b0;
        else if(flag_wr_end)
            flag_act    <=    1b0;
        else if(ref_req == 1b1 && state == AREF)
            flag_act    <=    1b1;
//wr_req
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            wr_req    <=    1b0;
        else if(wr_en == 1b1)
            wr_req    <=    1b0;
        else if(state != WRITE && key_wr == 1b1)
            wr_req    <=    1b1;
//flag_wr_end
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_wr_end    <=    1b0;
        else if(cmd_cnt == CMD_END)
            flag_wr_end    <=    1b1;
        else
            flag_wr_end    <=    1b0;
//cmd_cnt
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cmd_cnt    <=    4d0;
        else if(state == WRITE)
            cmd_cnt    <=    cmd_cnt + 1b1;
        else 
            cmd_cnt    <=    4d0;
        
//sdram_cmd
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_cmd    <=    4d0;
        else case(cmd_cnt)
            3d1:
                if(flag_pre == 1b1)
                    sdram_cmd    <=    PRE;
                else
                    sdram_cmd    <=    NOP;
            3d2:
                if(flag_act == 1b1 || col_addr == 9d0)
                    sdram_cmd    <=    ACT;
                else
                    sdram_cmd    <=    NOP;
            3d3:         
                sdram_cmd    <=    WR;
 
            default:
                sdram_cmd    <=    NOP;        
        endcase
//sdram_dq
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_dq    <=    16d0;
        else case(cmd_cnt)
            3d3:
                sdram_dq    <=    16h0012;
            3d4:
                sdram_dq    <=    16h1203;
            3d5:
                sdram_dq    <=    16h562f;
            3d6:
                sdram_dq    <=    16hfe12;
            default:
                sdram_dq    <=    16d0;
        endcase
/* //sdram_dq_m
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1‘b0)
            sdram_dqm    <=    4‘d0; */
//row_addr_reg
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            row_addr_reg    <=    12d0;
        else if(row_addr_reg == ROW_END && col_addr == COL_END && cmd_cnt == CMD_END)
            row_addr_reg    <=    12d0;
        else if(col_addr == COL_END && flag_wr_end == 1b1)
            row_addr_reg    <=    row_addr_reg + 1b1;
        
//row_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            row_addr    <=    12d0;
        else case(cmd_cnt)
        //因为下边的命令是通过行、列地址分开再给addr赋值,所以需要提前一个周期赋值,以保证在命令到来时能读到正确的地址
            3d2:
                row_addr    <=    12b0000_0000_0000;    //在写命令时,不允许auto-precharge    
            default:
                row_addr    <=    row_addr_reg;
        endcase
//col_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            col_addr    <=    9d0;
        else if(col_addr == COL_END && cmd_cnt == CMD_END)
            col_addr    <=    9d0;
        else if(cmd_cnt == CMD_END)
            col_addr    <=    col_addr + 3d4;
//sdram_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_addr    <=    12d0;
        else case(cmd_cnt)
            3d2:
                sdram_addr    <=    row_addr;
            3d3:
                sdram_addr    <=    col_addr;
            
            default:
                sdram_addr    <=    row_addr;
        endcase
//sdram_bank
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_bank    <=    2b00;
endmodule

在模块端口列表中,用key_wr来接收写请求信号,这个写请求信号,是在没有写完之前一直拉高的,在写完了全部数据之后才拉低的。

在写模块中,让SDRAM循环着写16’h0012,16’h1203,16’h562f,16’hfe12这四个数据。

另外一点,在这个写模块中,每写完4个数据,也就是突发结束后,有一个写完标志,从而使状态机跳转到仲裁状态,然后如果数据没写完,由于写请求是拉高的,所以如果此时没有刷新请求,那状态机还是会跳转到写状态继续写的。


读操作模块 ↓

module    sdram_read(
        input    wire        sclk,
        input    wire        s_rst_n,
        input    wire        rd_en,
        input    wire    [5:0]    state,
        input    wire        ref_req,        //自动刷新请求
        input    wire        key_rd,            //来自外部的读请求信号
        input    wire    [15:0]    rd_dq,        //sdram的数据端口
        
        output    reg    [3:0]    sdram_cmd,
        output    reg    [11:0]    sdram_addr,
        output    reg    [1:0]    sdram_bank,
        output    reg        rd_req,            //读请求
        output    reg        flag_rd_end        //突发读结束标志
        );
 
    parameter    NOP    =    4b0111,
            PRE    =    4b0010,
            ACT    =    4b0011,
            RD    =    4b0101,        //SDRAM的读命令(给读命令时需要给A10拉低)
            CMD_END    =    4d12,            //
            COL_END    =    9d508,            //最后四个列地址的第一个地址
            ROW_END    =    12d4095,        //行地址结束
            AREF    =    6b10_0000,        //自动刷新状态
            READ    =    6b01_0000;        //状态机的读状态
        
    reg    [11:0]    row_addr;
    reg    [8:0]    col_addr;
    reg    [3:0]    cmd_cnt;
    reg        flag_act;                //发送ACT命令标志(单独设立标志,便于跑高速)
 
//flag_act
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_act    <=    1b0;
        else if(flag_rd_end == 1b1 && ref_req == 1b1)
            flag_act    <=    1b1;
        else if(flag_rd_end == 1b1)
            flag_act    <=    1b0;
//rd_req
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            rd_req    <=    1b0;
        else if(rd_en == 1b1)
            rd_req    <=    1b0;
        else if(key_rd == 1b1 && state != READ)
            rd_req    <=    1b1;
//cmd_cnt
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cmd_cnt    <=    4d0;
        else if(state == READ)
            cmd_cnt    <=    cmd_cnt + 1b1;
        else
            cmd_cnt    <=    4d0;
//flag_rd_end
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_rd_end    <=    1b0;
        else if(cmd_cnt == CMD_END)
            flag_rd_end    <=    1b1;
        else
            flag_rd_end    <=    1b0;
//row_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            row_addr    <=    12d0;
        else if(row_addr == ROW_END && col_addr == COL_END && flag_rd_end == 1b1)
            row_addr    <=    12d0;
        else if(col_addr == COL_END && flag_rd_end == 1b1)
            row_addr    <=    row_addr + 1b1;
//col_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            col_addr    <=    9d0;
        else if(col_addr == COL_END && flag_rd_end == 1b1)
            col_addr    <=    9d0;
        else if(flag_rd_end == 1b1)
            col_addr    <=    col_addr + 3d4;
//cmd_cnt
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cmd_cnt    <=    4d0;
        else if(state == READ)
            cmd_cnt    <=    cmd_cnt + 1b1;
        else
            cmd_cnt    <=    4d0;
//sdram_cmd
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_cmd    <=    NOP;
        else case(cmd_cnt)
            4d2:
                if(col_addr == 9d0)
                    sdram_cmd    <=    PRE;
                else
                    sdram_cmd    <=    NOP;
            4d3:    
                if(flag_act == 1b1 || col_addr == 9d0)
                    sdram_cmd    <=    ACT;
                else
                    sdram_cmd    <=    NOP;
            4d4:
                sdram_cmd    <=    RD;
            default:
                sdram_cmd    <=    NOP;
        endcase
//sdram_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_addr    <=    12d0;
        else case(cmd_cnt)
            4d4:
                sdram_addr    <=    {3d0, col_addr};
            default:
                sdram_addr    <=    row_addr;
        endcase
//sdram_bank
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_bank    <=    2d0;
endmodule

自动刷新模块 ↓

module    auto_refresh(
        input    wire        sclk,
        input    wire        s_rst_n,
        input    wire        ref_en,
        input    wire        flag_init_end,    //初始化结束标志(初始化结束后,启动自刷新标志)
        
        output    reg    [11:0]    sdram_addr,
        output    reg    [1:0]    sdram_bank,
        output    reg        ref_req,
        output    reg    [3:0]    cmd_reg,
        output    reg        flag_ref_end
        );
 
    parameter    BANK    =    12d0100_0000_0000,    //自动刷新是对所有bank刷新
            CMD_END    =    4d10,
            CNT_END    =    10d749,    //15us计时结束
            NOP    =    4b0111,    //
            PRE    =    4b0010,    //precharge命令
            AREF    =    4b0001;    //auto-refresh命令
            
            
    reg    [9:0]    cnt_15ms;    //15ms计数器
    reg        flag_ref;    //处于自刷新阶段标志
    reg        flag_start;    //自动刷新启动标志
    reg    [3:0]    cnt_cmd;    //指令计数器
//flag_start
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_start    <=    1b0;
        else if(flag_init_end == 1b1)
            flag_start    <=    1b1;
//cnt_15ms
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cnt_15ms    <=    10d0;
        else if(cnt_15ms == CNT_END)
            cnt_15ms    <=    10d0;
        else if(flag_start == 1b1)
            cnt_15ms    <=    cnt_15ms + 1b1;
//flag_ref
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_ref    <=    1b0;
        else if(cnt_cmd == CMD_END)
            flag_ref    <=    1b0;
        else if(ref_en == 1b1)
            flag_ref    <=    1b1;
//cnt_cmd
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cnt_cmd    <=    4d0;
        else if(flag_ref == 1b1)
            cnt_cmd    <=    cnt_cmd + 1b1;
        else
            cnt_cmd    <=    4d0;
//flag_ref_end
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_ref_end    <=    1b0;
        else if(cnt_cmd == CMD_END)
            flag_ref_end    <=    1b1;
        else
            flag_ref_end    <=    1b0;
//cmd_reg
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            cmd_reg    <=    NOP;
        else case(cnt_cmd)
            3d0:
                if(flag_ref == 1b1)
                    cmd_reg    <=    PRE;
                else
                    cmd_reg    <=    NOP;
            3d1:
                cmd_reg    <=    AREF;
            3d5:
                cmd_reg    <=    AREF;
            default:
                cmd_reg    <=    NOP;
        endcase
//sdram_addr
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_addr    <=    12d0;
        else case(cnt_cmd)
            4d0:
                sdram_addr    <=    BANK;    //bank进行刷新时指定allbank or signle bank
            default:
                sdram_addr    <=    12d0;
        endcase
//sdram_bank
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            sdram_bank    <=    2d0;        //刷新指定的bank
//ref_req
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            ref_req    <=    1b0;
        else if(ref_en == 1b1)
            ref_req    <=    1b0;
        else if(cnt_15ms == CNT_END)
            ref_req    <=    1b1;
//flag_ref_end
    always    @(posedge sclk or negedge s_rst_n)
        if(s_rst_n == 1b0)
            flag_ref_end    <=    1b0;
        else if(cnt_cmd == CMD_END)
            flag_ref_end    <=    1b1;
        else
            flag_ref_end    <=    1b0;
 
endmodule

。。。。。。。。。。。。。。。待续。。。2017-06-05。。。


 

简易SDRAM控制器的verilog代码实现