SDRAM操作(FPGA實現)

對SDRAM基本概念的介紹以及芯片手冊說明,請參考上一篇文章SDRAM操作說明

1. 說明

如圖所示爲狀態機的簡化圖示,過程大概可以描述爲:SDRAM(IS42S16320D)上電初始化完成後,進入“空閒”狀態,此時一直監控外部控制模塊給予的控制信號。初始化完成後,外部定時器開始定時,定時週期爲SDRAM刷新週期(7.7us),一旦計數到刷新週期後,向狀態機發送auto_ref_req(自動刷新請求),此時狀態機進入“刷新”狀態,這樣就確保在無任何操作時,SDRAM能正常完成刷新。刷新完成後回到“空閒”狀態。

當處於空閒狀態時,接收到寫命令(wr_en),進入“寫”狀態(有效接收讀寫命令的時刻有特殊要求,後面再詳細說明),在full_page下連續寫600個數據(100MHz,恰好耗時6us多一點,這樣方便不用考慮定時刷新),寫完之後,發送wr_done命令,進入“刷新”狀態,相對於每次連續寫完成後,提前刷新一次。此時,定時刷新的計數器清零,重新開始計數。
讀多過程跟寫過程類似,讀完600個數據之後,手動完成刷新。

現在就來說一說,“空閒”狀態接收讀寫命令的特殊要求。理論上充電週期爲7.8125us,爲保證600次讀寫在充電週期內完成,並且前後預留一些其他命令的時間,所以推薦在0~1us這個時間內接受讀寫命令,這樣讀寫的時候專注讀寫就可以了。當然這是我的設計方式,如有更好的設計方式,那更好,歡迎分享。

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代碼實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章