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 == 1'b0) state <= IDLE; else case(state) IDLE: if(key[0] == 1'b1) state <= INIT; else state <= IDLE; INIT: if(flag_init_end == 1'b1) //初始化結束標志 state <= ARBIT; else state <= INIT; ARBIT: if(ref_req == 1'b1) //刷新請求到來且已經寫完 state <= AREF; else if(ref_req == 1'b0 && rd_en == 1'b1) //默認讀操作優先於寫操作 state <= READ; else if(ref_req == 1'b0 && wr_en == 1'b1) //無刷新請求且寫請求到來 state <= WRITE; else state <= ARBIT; AREF: if(flag_ref_end == 1'b1) state <= ARBIT; else state <= AREF; WRITE: if(flag_wr_end == 1'b1) state <= ARBIT; else state <= WRITE; READ: if(flag_rd_end == 1'b1) 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 = 4'd11, //初始化結束時的命令計數器的值 CNT_200US = 14'd1_0000, NOP = 4'b0111, //空操作命令 PRECHARGE = 4'b0010, //預充電命令 AUTO_REF = 4'b0001, //自刷新命令 MRSET = 4'b0000; //模式寄存器設置命令 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 == 1'b0) flag_init <= 1'b1; else if(cnt_cmd == CMD_END) flag_init <= 1'b0; //cnt_200us always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cnt_200us <= 14'd0; else if(cnt_200us == CNT_200US) cnt_200us <= 14'd0; else if(flag_200us == 1'b0) cnt_200us <= cnt_200us + 1'b1; //flag_200us always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) flag_200us <= 1'b0; else if(cnt_200us == CNT_200US) flag_200us <= 1'b1; //cnt_cmd always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cnt_cmd <= 4'd0; else if(flag_200us == 1'b1 && flag_init == 1'b1) cnt_cmd <= cnt_cmd + 1'b1; //flag_init_end always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) flag_init_end <= 1'b0; else if(cnt_cmd == CMD_END) flag_init_end <= 1'b1; else flag_init_end <= 1'b0; //cmd_reg always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cmd_reg <= NOP; else if(cnt_200us == CNT_200US) cmd_reg <= PRECHARGE; else if(flag_200us) case(cnt_cmd) 4'd0: cmd_reg <= AUTO_REF; //預充電命令 4'd6: cmd_reg <= AUTO_REF; 4'd10: cmd_reg <= MRSET; //模式寄存器設置 default: cmd_reg <= NOP; endcase //sdram_addr always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_addr <= 12'd0; else case(cnt_cmd) 4'd0: sdram_addr <= 12'b0100_0000_0000; //預充電時,A10拉高,對所有Bank操作 4'd10: sdram_addr <= 12'b0000_0011_0010; //模式寄存器設置時的指令:CAS=2,Burst Length=4; default: sdram_addr <= 12'd0; endcase //sdram_bank always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_bank <= 2'd0; //這里僅僅只是初始化,在模式寄存器設置時才會用到且其值為全零,故不賦值 //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 = 4'b0111, //NOP命令 ACT = 4'b0011, //ACT命令 WR = 4'b0100, //寫命令(需要將A10拉高) PRE = 4'b0010, //precharge命令 CMD_END = 4'd8, COL_END = 9'd508, //最后四個列地址的第一個地址 ROW_END = 12'd4095, //行地址結束 AREF = 6'b10_0000, //自動刷新狀態 WRITE = 6'b00_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 == 1'b0) flag_pre <= 1'b0; else if(col_addr == 9'd0 && flag_wr_end == 1'b1) flag_pre <= 1'b1; else if(flag_wr_end == 1'b1) flag_pre <= 1'b0; //flag_act always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) flag_act <= 1'b0; else if(flag_wr_end) flag_act <= 1'b0; else if(ref_req == 1'b1 && state == AREF) flag_act <= 1'b1; //wr_req always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) wr_req <= 1'b0; else if(wr_en == 1'b1) wr_req <= 1'b0; else if(state != WRITE && key_wr == 1'b1) wr_req <= 1'b1; //flag_wr_end always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) flag_wr_end <= 1'b0; else if(cmd_cnt == CMD_END) flag_wr_end <= 1'b1; else flag_wr_end <= 1'b0; //cmd_cnt always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cmd_cnt <= 4'd0; else if(state == WRITE) cmd_cnt <= cmd_cnt + 1'b1; else cmd_cnt <= 4'd0; //sdram_cmd always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_cmd <= 4'd0; else case(cmd_cnt) 3'd1: if(flag_pre == 1'b1) sdram_cmd <= PRE; else sdram_cmd <= NOP; 3'd2: if(flag_act == 1'b1 || col_addr == 9'd0) sdram_cmd <= ACT; else sdram_cmd <= NOP; 3'd3: sdram_cmd <= WR; default: sdram_cmd <= NOP; endcase //sdram_dq always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_dq <= 16'd0; else case(cmd_cnt) 3'd3: sdram_dq <= 16'h0012; 3'd4: sdram_dq <= 16'h1203; 3'd5: sdram_dq <= 16'h562f; 3'd6: sdram_dq <= 16'hfe12; default: sdram_dq <= 16'd0; 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 == 1'b0) row_addr_reg <= 12'd0; else if(row_addr_reg == ROW_END && col_addr == COL_END && cmd_cnt == CMD_END) row_addr_reg <= 12'd0; else if(col_addr == COL_END && flag_wr_end == 1'b1) row_addr_reg <= row_addr_reg + 1'b1; //row_addr always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) row_addr <= 12'd0; else case(cmd_cnt) //因為下邊的命令是通過行、列地址分開再給addr賦值,所以需要提前一個周期賦值,以保證在命令到來時能讀到正確的地址 3'd2: row_addr <= 12'b0000_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 == 1'b0) col_addr <= 9'd0; else if(col_addr == COL_END && cmd_cnt == CMD_END) col_addr <= 9'd0; else if(cmd_cnt == CMD_END) col_addr <= col_addr + 3'd4; //sdram_addr always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_addr <= 12'd0; else case(cmd_cnt) 3'd2: sdram_addr <= row_addr; 3'd3: sdram_addr <= col_addr; default: sdram_addr <= row_addr; endcase //sdram_bank always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_bank <= 2'b00; 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 = 4'b0111, PRE = 4'b0010, ACT = 4'b0011, RD = 4'b0101, //SDRAM的讀命令(給讀命令時需要給A10拉低) CMD_END = 4'd12, // COL_END = 9'd508, //最后四個列地址的第一個地址 ROW_END = 12'd4095, //行地址結束 AREF = 6'b10_0000, //自動刷新狀態 READ = 6'b01_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 == 1'b0) flag_act <= 1'b0; else if(flag_rd_end == 1'b1 && ref_req == 1'b1) flag_act <= 1'b1; else if(flag_rd_end == 1'b1) flag_act <= 1'b0; //rd_req always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) rd_req <= 1'b0; else if(rd_en == 1'b1) rd_req <= 1'b0; else if(key_rd == 1'b1 && state != READ) rd_req <= 1'b1; //cmd_cnt always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cmd_cnt <= 4'd0; else if(state == READ) cmd_cnt <= cmd_cnt + 1'b1; else cmd_cnt <= 4'd0; //flag_rd_end always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) flag_rd_end <= 1'b0; else if(cmd_cnt == CMD_END) flag_rd_end <= 1'b1; else flag_rd_end <= 1'b0; //row_addr always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) row_addr <= 12'd0; else if(row_addr == ROW_END && col_addr == COL_END && flag_rd_end == 1'b1) row_addr <= 12'd0; else if(col_addr == COL_END && flag_rd_end == 1'b1) row_addr <= row_addr + 1'b1; //col_addr always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) col_addr <= 9'd0; else if(col_addr == COL_END && flag_rd_end == 1'b1) col_addr <= 9'd0; else if(flag_rd_end == 1'b1) col_addr <= col_addr + 3'd4; //cmd_cnt always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cmd_cnt <= 4'd0; else if(state == READ) cmd_cnt <= cmd_cnt + 1'b1; else cmd_cnt <= 4'd0; //sdram_cmd always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_cmd <= NOP; else case(cmd_cnt) 4'd2: if(col_addr == 9'd0) sdram_cmd <= PRE; else sdram_cmd <= NOP; 4'd3: if(flag_act == 1'b1 || col_addr == 9'd0) sdram_cmd <= ACT; else sdram_cmd <= NOP; 4'd4: sdram_cmd <= RD; default: sdram_cmd <= NOP; endcase //sdram_addr always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_addr <= 12'd0; else case(cmd_cnt) 4'd4: sdram_addr <= {3'd0, col_addr}; default: sdram_addr <= row_addr; endcase //sdram_bank always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_bank <= 2'd0; 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 = 12'd0100_0000_0000, //自動刷新是對所有bank刷新 CMD_END = 4'd10, CNT_END = 10'd749, //15us計時結束 NOP = 4'b0111, // PRE = 4'b0010, //precharge命令 AREF = 4'b0001; //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 == 1'b0) flag_start <= 1'b0; else if(flag_init_end == 1'b1) flag_start <= 1'b1; //cnt_15ms always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cnt_15ms <= 10'd0; else if(cnt_15ms == CNT_END) cnt_15ms <= 10'd0; else if(flag_start == 1'b1) cnt_15ms <= cnt_15ms + 1'b1; //flag_ref always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) flag_ref <= 1'b0; else if(cnt_cmd == CMD_END) flag_ref <= 1'b0; else if(ref_en == 1'b1) flag_ref <= 1'b1; //cnt_cmd always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cnt_cmd <= 4'd0; else if(flag_ref == 1'b1) cnt_cmd <= cnt_cmd + 1'b1; else cnt_cmd <= 4'd0; //flag_ref_end always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) flag_ref_end <= 1'b0; else if(cnt_cmd == CMD_END) flag_ref_end <= 1'b1; else flag_ref_end <= 1'b0; //cmd_reg always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) cmd_reg <= NOP; else case(cnt_cmd) 3'd0: if(flag_ref == 1'b1) cmd_reg <= PRE; else cmd_reg <= NOP; 3'd1: cmd_reg <= AREF; 3'd5: cmd_reg <= AREF; default: cmd_reg <= NOP; endcase //sdram_addr always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_addr <= 12'd0; else case(cnt_cmd) 4'd0: sdram_addr <= BANK; //bank進行刷新時指定allbank or signle bank default: sdram_addr <= 12'd0; endcase //sdram_bank always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) sdram_bank <= 2'd0; //刷新指定的bank //ref_req always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) ref_req <= 1'b0; else if(ref_en == 1'b1) ref_req <= 1'b0; else if(cnt_15ms == CNT_END) ref_req <= 1'b1; //flag_ref_end always @(posedge sclk or negedge s_rst_n) if(s_rst_n == 1'b0) flag_ref_end <= 1'b0; else if(cnt_cmd == CMD_END) flag_ref_end <= 1'b1; else flag_ref_end <= 1'b0; endmodule
。。。。。。。。。。。。。。。待續。。。2017-06-05。。。
