FPGA实战操作(1) -- SDRAM(Verilog实现)

对SDRAM基本概念的介绍以及芯片手册说明,请参考上一篇文章SDRAM操作说明。

1. 说明

如图所示为状态机的简化图示,过程大概可以描述为:SDRAM(IS42S16320D)上电初始化完成后,进入“空闲”状态,此时一直监控外部控制模块给予的控制信号。初始化完成后,外部定时器开始定时,定时周期为SDRAM刷新周期(7.7us),一旦计数到刷新周期后,向状态机发送auto_ref_req(自动刷新请求),此时状态机进入“刷新”状态,这样就确保在无任何操作时,SDRAM能正常完成刷新。刷新完成后回到“空闲”状态。

当处于空闲状态时,接收到写命令(wr_en),进入“写”状态(有效接收读写命令的时刻有特殊要求,后面再详细说明),在full_page下连续写600个数据(100MHz,恰好耗时6us多一点,这样方便不用考虑定时刷新),写完之后,发送wr_done命令,进入“刷新”状态,相对于每次连续写完成后,提前刷新一次。此时,定时刷新的计数器清零,重新开始计数。
读多过程跟写过程类似,读完600个数据之后,手动完成刷新。
FPGA实战操作(1) -- SDRAM(Verilog实现)_第1张图片

现在就来说一说,“空闲”状态接收读写命令的特殊要求。理论上充电周期为7.8125us,为保证600次读写在充电周期内完成,并且前后预留一些其他命令的时间,所以推荐在0~1us这个时间内接受读写命令,这样读写的时候专注读写就可以了。当然这是我的设计方式,如有更好的设计方式,那更好,欢迎分享。
FPGA实战操作(1) -- SDRAM(Verilog实现)_第2张图片

2. 代码实现

状态机的代码如下所示,清晰的描述了各状态之间的跳变及其跳变条件。其中信号ctrl_valid即为上图中命令有效期的时间区间。在各状态描述的时序逻辑模块中,只是产生了读、写或刷新执行模块的使能信号,即在“写”状态的时候,使能写模块,完成相信的写操作。

    always @ (posedge clk or negedge rst_n)
        begin
            if(rst_n == 1'b0)
                begin
                    current_status <= IDLE;
                end
            else if(init_ing == 1'b0)
                begin
                    current_status <= next_status;
                end
            else
                begin
                    current_status <= IDLE;
                end
        end
        
    always @ (rst_n or current_status or sdram_wrreq or sdram_rdreq or ref_req_auto or wr_done or rd_done or ref_done or ctrl_valid)
        begin
            next_status = 5'dx;
            case(current_status)
                IDLE:
                    begin
                        if(ref_req_auto == 1'b1)                        //收到自动刷新请求
                            begin
                                next_status = AUTO_REF;
                            end
                        else if(ctrl_valid == 1'b1 && sdram_wrreq == 1'b1)//在读写控制有效区内收到写请求
                            begin
                                next_status = WRITE;
                            end
                        else if(ctrl_valid == 1'b1 && sdram_rdreq == 1'b1)       //在读写控制有效区内收到读请求
                            begin
                                next_status = READ;
                            end
                        else
                            begin
                                next_status = IDLE;
                            end
                    end
                WRITE:
                    begin
                        if(wr_done == 1'b1)
                            begin
                                next_status = AUTO_REF;
                            end
                        else
                            begin
                                next_status = WRITE;
                            end 
                    end
                READ:
                    begin
                        if(rd_done == 1'b1)
                            begin
                                next_status = AUTO_REF;
                            end
                        else
                            begin
                                next_status = READ;
                            end 
                    end
                AUTO_REF:
                    begin
                        if(ref_done == 1'b1)
                            begin
                                next_status = IDLE;
                            end
                        else
                            begin
                                next_status = AUTO_REF;
                            end
                    end
                default:
                    begin
                        next_status = IDLE;
                    end 
            endcase
        end
    //各个状态下的使能信号,以控制相应的模块执行相应的操作    
    always @ (posedge clk or negedge rst_n)
        begin
            if(rst_n == 1'b0)
                begin
                    wr_start <= 1'b0;
                    rd_start <= 1'b0;
                    ref_start <= 1'b0;
                end
            else
                begin
                    case(next_status)
                        IDLE:
                            begin
                                wr_start <= 1'b0;
                                rd_start <= 1'b0;
                                ref_start <= 1'b0;
                            end
                        WRITE:
                            begin
                                wr_start <= 1'b1;
                                rd_start <= 1'b0;
                                ref_start <= 1'b0;
                            end
                        READ:
                            begin
                                wr_start <= 1'b0;
                                rd_start <= 1'b1;
                                ref_start <= 1'b0;
                            end
                        AUTO_REF:
                            begin
                                wr_start <= 1'b0;
                                rd_start <= 1'b0;
                                ref_start <= 1'b1;
                            end
                        default:
                            begin
                                wr_start <= 1'b0;
                                rd_start <= 1'b0;
                                ref_start <= 1'b0;
                            end
                
                    endcase
                end
        
        end

以下给出写操作模块的部分代码,读操作和刷新同理。中间有些信号是我工程需要,参考一下思路即可。

        always @(posedge clk or negedge rst_n)
        begin
            if(rst_n == 1'b0)
                begin
                    cke_wr <= 1'b0;
                    cmd_wr <= NOP;
                    dqm_wr <= DQM_DIS;
                    bank_addr_wr <= BANK0;
                    addr_wr <= DONT_CARE_ADDR;
                    wr_done <= 1'b0;
                    wr_first_flag_r <= 1'b0;
                    status_wr <= 4'd0;
                end
            else if(wr_start == 1'b1)
                begin
                    case(status_wr)
                        4'd0:
                            begin                                   
                                cke_wr <= 1'b1;
                                cmd_wr <= NOP;
                                dqm_wr <= DQM_EN;
                                bank_addr_wr <= BANK0;
                                addr_wr <= DONT_CARE_ADDR;
                                
                                wr_done <= 1'b0;
                                wr_first_flag_r <= 1'b0;
                                status_wr <= status_wr + 4'd1;      
                            end
                        4'd1:
                            begin
                                cke_wr <= 1'b1;
                                cmd_wr <= ACT;
                                dqm_wr <= DQM_EN;
                                bank_addr_wr <= BANK0;
                                addr_wr <= row_addr;    //行地址
                                
                                wr_done <= 1'b0;
                                wr_first_flag_r <= 1'b0;
                                status_wr <= status_wr + 4'd1;
                            end
                        4'd2:                                   //4'd2和4'd3是为了延时T_RCD,即两个时钟
                            begin
                                
                                cke_wr <= 1'b1;
                                cmd_wr <= NOP;
                                dqm_wr <= DQM_EN;
                                bank_addr_wr <= BANK0;
                                addr_wr <= DONT_CARE_ADDR;
                                
                                wr_done <= 1'b0;
                                wr_first_flag_r <= 1'b0;            
                                status_wr <= status_wr + 4'd1;
                                    
                            end
                        4'd3:                                           
                            begin
                                
                                cke_wr <= 1'b1;
                                cmd_wr <= NOP;
                                dqm_wr <= DQM_EN;
                                bank_addr_wr <= BANK0;
                                addr_wr <= DONT_CARE_ADDR;
                                
                                wr_done <= 1'b0;
                                wr_first_flag_r <= 1'b0;                
                                status_wr <= status_wr + 4'd1;
                                    
                            end
                        4'd4:
                            begin
                                cke_wr <= 1'b1;
                                cmd_wr <= NOP;
                                dqm_wr <= DQM_EN;
                                bank_addr_wr <= BANK0;
                                addr_wr <= DONT_CARE_ADDR;
                                
                                wr_done <= 1'b0;
                                wr_first_flag_r <= 1'b1;                       //用于写入第一个数据的时序标记
                                status_wr <= status_wr + 4'd1;
                            end
                        4'd5:
                            begin
                                cke_wr <= 1'b1;
                                cmd_wr <= WR;
                                dqm_wr <= DQM_EN;
                                bank_addr_wr <= BANK0;
                                addr_wr <= column_addr;     //{A12A11,A10,column_address}
                                
                                wr_done <= 1'b0;
                                wr_first_flag_r <= 1'b0;
                                status_wr <= status_wr + 4'd1;
                            end
                        4'd6:
                            begin
                                if(sdram_wr_done == 1'b1)       //用于增加NOP持续周期
                                    begin
                                        cke_wr <= 1'b1;
                                        cmd_wr <= NOP;
                                        dqm_wr <= DQM_DIS;
                                        bank_addr_wr <= BANK0;
                                        addr_wr <= DONT_CARE_ADDR;
                                        
                                        wr_done <= 1'b1;
                                        wr_first_flag_r <= 1'b0;
                                        status_wr <= status_wr + 4'd1;
                                    end
                                else
                                    begin
                                        cke_wr <= 1'b1;
                                        cmd_wr <= NOP;
                                        dqm_wr <= DQM_EN;
                                        bank_addr_wr <= BANK0;
                                        addr_wr <= DONT_CARE_ADDR;
                                        
                                        wr_done <= 1'b0;
                                        wr_first_flag_r <= 1'b0;
                                        status_wr <= status_wr;
                                    end
                            end
                        4'd7:
                            begin
                                cke_wr <= 1'b1;
                                cmd_wr <= NOP;
                                dqm_wr <= DQM_DIS;
                                bank_addr_wr <= BANK0;
                                addr_wr <= DONT_CARE_ADDR;
                                
                                wr_done <= 1'b0;
                                wr_first_flag_r <= 1'b0;
                                status_wr <= 4'd0;
                            end
                        default:
                            begin
                                cke_wr <= 1'b1;
                                cmd_wr <= NOP;
                                dqm_wr <= DQM_EN;
                                bank_addr_wr <= BANK0;
                                addr_wr <= DONT_CARE_ADDR;
                                
                                wr_done <= 1'b0;
                                wr_first_flag_r <= 1'b0;
                                status_wr <= 4'd0;
                            end
                    endcase
                end
            else
                begin
                    cke_wr <= 1'b1;
                    cmd_wr <= NOP;
                    dqm_wr <= DQM_EN;
                    bank_addr_wr <= BANK0;
                    addr_wr <= DONT_CARE_ADDR;
                    
                    wr_done <= 1'b0;
                    wr_first_flag_r <= 1'b0;
                    status_wr <= 4'd0;
                end
        end

参考文献

SDRAM驱动篇之简易SDRAM控制器的verilog代码实现

转载于:https://www.cnblogs.com/rouwawa/p/7339102.html

你可能感兴趣的:(FPGA实战操作(1) -- SDRAM(Verilog实现))