本文為明德揚原創及錄用文章,轉載請注明出處!
SDRAM控制器設計的主要功能是能對SDRAM進行讀寫操作,本工程實現了SDRAM的初始化、自動刷新、讀、寫等功能。
初始化功能和刷新功能在前一章的分享中已經進行了比較詳細的描述,感興趣的同學可以搜索學習下,這里不再贅述。今天我們主要討論SDRAM讀寫的功能以及實現。
一、原理功能
1、讀寫突發模式
在初始化里的模式寄存器配置中我們將讀寫的突發長度(BL)設置為4,列選通潛伏期(CL)設為3,突發類型為順序模式(每一次突發只給出起始地址即可)。對于預充電選擇(Auto Precharge)自動預充電,即外部不需要發送預充電命令
2、讀、寫時序圖
圖表1寫時序圖
圖表2讀時序圖
由時序圖可知,讀寫模塊采用線性序列機設計比較簡單。在每次讀寫突發之前都需要發送ACTIVE命令,同時給出bank地址和row地址,2拍后發出讀或寫命令,同時將A10拉高并給出col地址。對于寫操作沒有潛伏期,由于DQ是雙向端口,所以需要一個三態門控制DQ總線方向,當寫操作時為輸出方向,讀操作時為輸入方向。對于讀操作,發出讀命令后會有CL拍的潛伏期,本實驗里CL=3,即發出讀命令后3拍,數據才會出現在DQ總線上。
二、FPGA實現
1、模塊架構
注:信號方向請看箭頭
2、模塊架構解讀
要完整實現SDRAM控制器必須要完成初始化,刷新,讀寫這四部分功能。所以模塊劃分大體依照次為指導。由于SDRAM控制器工作時鐘為100MHz,且要輸出一個頻率相同相位相差180°的時鐘給SDRAM,所以要有一個鎖相環模塊。刷新需要計時刷新間隔,所以要加入一個刷新定時器模塊,由于初始化,刷新,讀,寫等模塊都要輸出sdr_cke,sdr_cs_n,sdr_cas_n,sdr_ras_n,sdr_we_n,sdr_ba,sdr_a等信號到SDRAM,所以需要一個選擇模塊。將sdr_cke,sdr_cs_n,sdr_cas_n,sdr_ras_n,sdr_we_n,sdr_ba,sdr_a等信號組合成bus信號輸入到選擇模塊。
新加入的信號說明,其他信號在前兩篇中已經詳細說明,讀者可以參考。
信號 |
功能 |
說明 |
頂層 |
|
|
local_data |
寫突發數據輸入 |
外部輸入 |
local_addr |
地址總線 |
外部輸入 |
local_q |
讀突發數據輸出 |
輸出 |
local_wrreq |
寫請求 |
外部輸入 |
local_rdreq |
讀請求 |
外部輸入 |
local_reday |
可以進行讀寫操作信號 |
輸出 |
local_rdata_vaild |
輸出數據有效信號 |
輸出 |
寫模塊 |
|
|
wr_en |
寫使能 |
仲裁模塊輸出到寫模塊 |
wr_done |
寫完成 |
寫模塊輸出到仲裁模塊 |
wr_bus |
寫操作總線 |
寫模塊輸出到選擇模塊 |
sdr_dq |
SDRAM數據總線 |
雙向端口,對于寫模塊是輸出,讀模塊是輸入 |
讀模塊 |
|
|
rd_bus |
讀操作總線 |
讀模塊輸出到選擇模塊 |
rd_en |
讀使能 |
仲裁模塊輸出到讀模塊 |
rd_done |
讀完成 |
讀模塊輸出到仲裁模塊 |
新加入了寫模塊和讀模塊。
寫模塊主要是完成一次寫突發操作,將local_data信號寫入到SDRAM中的指定地址local_addr中。local_addr主要由bank地址,行地址和列地址組合而成。在讀和寫模塊代碼中均有體現,可以參考。
讀模塊主要完成一次讀突發操作,將SDRAM中指定的地址中的數據讀出來,并賦值給local_q信號。
由于加入了寫模塊和讀模塊,所以仲裁模塊也要做出相應的修改,當收到刷新請求時,即rt_flag信號后,在結束本次讀突發或者寫突發后,拉高刷新使能(ref_en)信號,當收到寫請求且沒有刷新請求時,拉高寫使能(wr_en)信號,當收到讀請求且沒有刷新請求和寫請求時,拉高讀使能(rd_en)信號。
3、頂層模塊參考代碼
module sdram_top( clk , sys_rst_n , //其他信號,舉例dout local_addr, local_data, local_q, local_rdreq, local_wrreq, local_ready, local_rdata_valid, init_done, sdr_cke, sdr_cs_n, sdr_ras_n, sdr_cas_n, sdr_we_n, sdr_ba, sdr_a, sdr_dq, sdr_dqm, sdr_clk );
input clk; input sys_rst_n; input [24:0] local_addr; input [63:0] local_data; output [63:0] local_q; input local_rdreq; input local_wrreq; output local_ready; output local_rdata_valid; output init_done; output sdr_cke; output sdr_cs_n; output sdr_ras_n; output sdr_cas_n; output sdr_we_n; output [1:0] sdr_ba; output [12:0] sdr_a; inout [15:0] sdr_dq; output [1:0] sdr_dqm; output sdr_clk;
wire phy_clk; wire rst_n; wire rt_flag; wire rt_clear; wire rt_en; wire ref_en; wire ref_done; wire wr_done; wire wr_en; wire rd_en; wire rd_done; wire [1:0] sel_sm; wire [19:0] sdr_bus; wire [19:0] init_bus; wire [19:0] ref_bus; wire [19:0] wr_bus; wire [19:0] rd_bus;
assign {sdr_cke, sdr_cs_n, sdr_ras_n, sdr_cas_n, sdr_we_n, sdr_ba, sdr_a} = sdr_bus; assign sdr_dqm = 2'b00;
sdram_init sdram_init_inst( .clk (phy_clk) , .rst_n (rst_n) , //其他信號,舉例dout .init_done (init_done) , .init_bus (init_bus) );
arbitrate arbitrate_inst( .clk(phy_clk), .rst_n(rst_n), .rt_flag(rt_flag), .rt_en(rt_en), .rt_clear(rt_clear), .ref_done(ref_done), .ref_en(ref_en), .init_done(init_done), .sel_sm(sel_sm), .local_rdata_valid(local_rdata_valid), .local_ready(local_ready), .local_wrreq(local_wrreq), .local_rdreq(local_rdreq), .wr_done(wr_done), .wr_en(wr_en), .rd_en(rd_en), .rd_done(rd_done) );
ref_timer ref_timer_inst( .clk(phy_clk), .rst_n(rst_n), .rt_en(rt_en), .rt_clear(rt_clear), .rt_flag(rt_flag) );
sdram_ref sdram_ref_inst( .clk(phy_clk), .rst_n(rst_n), .ref_en(ref_en), .ref_done(ref_done), .ref_bus(ref_bus) );
sdram_write sdram_write_inst( .clk(phy_clk), .rst_n(rst_n), .wr_en(wr_en), .wr_done(wr_done), .wr_bus(wr_bus), .sdr_dq(sdr_dq), .local_data(local_data), .local_addr(local_addr) );
sdram_read sdram_read_inst( .clk(phy_clk), .rst_n(rst_n), .rd_en(rd_en), .rd_done(rd_done), .rd_bus(rd_bus), .sdr_dq(sdr_dq), .local_q(local_q), .local_addr(local_addr) );
sdram_mux sdram_mux_inst( .clk(phy_clk), .rst_n(rst_n), .init_bus(init_bus), .ref_bus(ref_bus), .wr_bus(wr_bus), .rd_bus(rd_bus), .sdr_bus(sdr_bus), .sel_sm(sel_sm) );
my_pll PLL( .areset (~sys_rst_n) , .inclk0 (clk) , .c0 (phy_clk) , .c1 (sdr_clk) , .locked (rst_n) );
endmodule |
4、模塊功能
PLL模塊,初始化模塊,刷新模塊在前面兩篇文章中已經討論過,這里不再描述。
(1)寫模塊
主要完成寫突發,采用線性序列機設計,當檢測到wr_en為高電平時,計數器開始計時,并發出開ACT命令,并將row地址賦值給sdr_a。兩拍之后,發出寫命令,并將A10拉高,然后開始將數據賦值給sdr_dq信號。然后計數到8-1時將寫完成信號wr_done拉高。
可以對照時序圖閱讀代碼,其代碼如下:
module sdram_write(clk, rst_n, wr_en, wr_done, wr_bus, sdr_dq, local_data, local_addr);
input clk; input rst_n; input wr_en; output reg wr_done; output [19:0] wr_bus; inout [15:0] sdr_dq; input [63:0] local_data; input [24:0] local_addr;
parameter CNT_MAX = 8; parameter NOP = 4'b0111; parameter ACT = 4'b0011; parameter WR = 4'b0100;
reg [3:0] cnt; reg [3:0] sdr_cmd; reg [1:0] sdr_ba; reg [12:0] sdr_a; reg [15:0] temp; reg out_en;
wire [9:0] col; wire [12:0] row; wire [1:0] ba; wire sdr_cke; wire add_cnt; wire end_cnt;
assign {ba, row, col} = local_addr; assign sdr_dq = out_en ? temp : 16'dz; assign sdr_cke = 1'b1; assign wr_bus = {sdr_cke, sdr_cmd, sdr_ba, sdr_a};
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt <= 0; end else if(add_cnt)begin if(end_cnt) cnt <= 0; else cnt <= cnt + 1; end end
assign add_cnt = wr_en; assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;
always @ (posedge clk or negedge rst_n)begin if(!rst_n)begin sdr_cmd <= NOP; end else if(add_cnt && cnt == 1 - 1)begin sdr_cmd <= ACT; end else if(add_cnt && cnt == 3 - 1)begin sdr_cmd <= WR; end else begin sdr_cmd <= NOP; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin sdr_ba <= 2'd0; end else begin sdr_ba <= ba; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin sdr_a <= 13'd0; end else if(add_cnt && cnt == 1 - 1)begin sdr_a <= row; end else if(add_cnt && cnt == 3 - 1)begin sdr_a <= {2'd0, 1'b1, col}; end else begin sdr_a <= 13'd0; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin temp <= 16'd0; end else if(add_cnt && cnt == 4 - 1)begin temp <= local_data[15:0]; end else if(add_cnt && cnt == 5 - 1)begin temp <= local_data[31:16]; end else if(add_cnt && cnt == 6 - 1)begin temp <= local_data[47:32]; end else if(add_cnt && cnt == 7 - 1)begin temp <= local_data[63:48]; end else begin temp <= 16'd0; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin out_en <= 1'b0; end else if(add_cnt && cnt == 4 - 1)begin out_en <= 1'b1; end else if(add_cnt && cnt == 8 - 1)begin out_en <= 1'b0; end else begin out_en <= out_en; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin wr_done <= 1'b0; end else if(add_cnt && cnt == 1 - 1)begin wr_done <= 1'b0; end else if(add_cnt && cnt == 8 - 1)begin wr_done <= 1'b1; end else begin wr_done <= wr_done; end end
endmodule |
(2)讀模塊
讀模塊主要完成讀突發,對于讀模塊來說,sdr_dq信號是輸入信號,且不是同一時鐘域信號,所以要加兩級同步寄存器。當檢測到讀使能rd_en為高時,計數器開始計數,并發出ACT命令,同時給出row地址,兩拍之后發出讀命令,并將A10拉高,由于加入了兩級同步寄存器且讀潛伏期為3,所以5拍之后才可以采集數據,即計數到9-1時采集數據,4拍之后數據采集完成,下一拍將讀完成信號rd_done拉高。
可以對照時序圖閱讀代碼,代碼如下
module sdram_read(clk, rst_n, rd_en, rd_done, rd_bus, sdr_dq, local_q, local_addr);
input clk; input rst_n; input rd_en; output reg rd_done; output [19:0] rd_bus; input [15:0] sdr_dq; output reg [63:0] local_q; input [24:0] local_addr;
parameter CNT_MAX = 14; parameter NOP = 4'b0111; parameter ACT = 4'b0011; parameter RD = 4'b0101;
reg [3:0] cnt; reg [3:0] sdr_cmd; reg [1:0] sdr_ba; reg [12:0] sdr_a; reg [15:0] temp0, temp1;
wire [9:0] col; wire [12:0] row; wire [1:0] ba; wire sdr_cke; wire add_cnt; wire end_cnt;
assign {ba, row, col} = local_addr; assign sdr_cke = 1'b1; assign rd_bus = {sdr_cke, sdr_cmd, sdr_ba, sdr_a};
always @(posedge clk)begin temp0 <= sdr_dq; temp1 <= temp0; end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt <= 0; end else if(add_cnt)begin if(end_cnt) cnt <= 0; else cnt <= cnt + 1; end end
assign add_cnt = rd_en; assign end_cnt = add_cnt && cnt==CNT_MAX - 1 ;
always @ (posedge clk or negedge rst_n)begin if(!rst_n)begin sdr_cmd <= NOP; end else if(add_cnt && cnt == 1 - 1)begin sdr_cmd <= ACT; end else if(add_cnt && cnt == 3 - 1)begin sdr_cmd <= RD; end else begin sdr_cmd <= NOP; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin sdr_ba <= 2'd0; end else begin sdr_ba <= ba; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin sdr_a <= 13'd0; end else if(add_cnt && cnt == 1 - 1)begin sdr_a <= row; end else if(add_cnt && cnt == 3 - 1)begin sdr_a <= {2'd0, 1'b1, col}; end else begin sdr_a <= 13'd0; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin local_q <= 64'd0; end else if(add_cnt && cnt == 9 - 1)begin local_q[15:0] <= temp1; end else if(add_cnt && cnt == 10 - 1)begin local_q[31:16] <= temp1; end else if(add_cnt && cnt == 11 - 1)begin local_q[47:32] <= temp1; end else if(add_cnt && cnt == 12 - 1)begin local_q[63:48] <= temp1; end else begin local_q <= local_q; end end
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin rd_done <= 1'b0; end else if(add_cnt && cnt == 1 - 1)begin rd_done <= 1'b0; end else if(add_cnt && cnt == 13 - 1)begin rd_done <= 1'b1; end else begin rd_done <= rd_done; end end
endmodule |
(3)仲裁模塊
由于加入了寫模塊和讀模塊,所以仲裁模塊也要做出相應的修改,當收到刷新請求時,即rt_flag信號后,在結束本次讀突發或者寫突發后,拉高刷新使能(ref_en)信號,當收到寫請求且沒有刷新請求時,拉高寫使能(wr_en)信號,當收到讀請求且沒有刷新請求和寫請求時,拉高讀使能(rd_en)信號。
主要代碼如下:
module arbitrate(clk, rst_n, rt_flag, rt_en, rt_clear, ref_done,ref_en, init_done, sel_sm, local_rdata_valid, local_ready,local_wrreq, local_rdreq, wr_done, wr_en, rd_en, rd_done);
input clk, rst_n; input rt_flag; output reg rt_en,rt_clear; input ref_done; output reg ref_en; input init_done; output reg [1:0]sel_sm; output reglocal_rdata_valid; output reglocal_ready; input local_wrreq,local_rdreq; input wr_done; output reg wr_en; output reg rd_en; input rd_done;
localparam SM_INIT =2'd0; localparam SM_REF = 2'd1; localparam SM_WR = 2'd2; localparam SM_RD = 2'd3;
always @(posedge clkor negedge rst_n)begin if(!rst_n)begin rt_en<= 0; end elseif(init_done)begin rt_en<= 1; end else begin rt_en<= rt_en; end end
always @(posedge clkor negedge rst_n)begin if(!rst_n)begin rt_clear<= 0; end elseif(ref_en)begin rt_clear<= 1; end elseif(ref_done)begin rt_clear<= 0; end else begin rt_clear<= rt_clear; end end
always @(posedge clkor negedge rst_n)begin if(!rst_n)begin ref_en<= 0; end elseif(rt_flag)begin ref_en<= 1; end elseif(ref_done)begin ref_en<= 0; end else begin ref_en<= ref_en; end end
always @(posedge clkor negedge rst_n)begin if(!rst_n)begin sel_sm<= SM_INIT; end elseif(!init_done)begin sel_sm<= SM_INIT; end elseif(rt_flag)begin sel_sm<= SM_REF; end elseif(local_wrreq && !rt_flag)begin sel_sm<= SM_WR; end elseif(local_rdreq && !rt_flag)begin sel_sm<= SM_RD; end else begin sel_sm<= sel_sm; end end
always @(posedge clkor negedge rst_n)begin if(!rst_n)begin wr_en<= 0; end elseif(local_wrreq && !rt_flag)begin wr_en<= 1; end elseif(wr_done)begin wr_en<= 0; end else begin wr_en<= wr_en; end end
always @(posedge clkor negedge rst_n)begin if(!rst_n)begin rd_en<= 0; end elseif(local_rdreq && !local_wrreq && !rt_flag)begin rd_en<= 1; end elseif(rd_done)begin rd_en<= 0; end else begin rd_en<= rd_en; end end
always @(posedge clkor negedge rst_n)begin if(!rst_n)begin local_ready<= 1; end elseif(wr_en || rd_en)begin local_ready<= 0; end else begin local_ready<= 1; end end
always @(posedge clkor negedge rst_n)begin if(!rst_n)begin local_rdata_valid<= 0; end elseif(rd_done)begin local_rdata_valid<= 1; end else begin local_rdata_valid<= 0; end end endmodule |
(5)仿真驗證
向SDRAM中寫入64’h1122334455667788,然后讀出來。由圖可知SDRAM正確讀出了數據。(波形顯示的是十六進制,報告是十進制)