最近博主在根據例程做ddr3的讀寫測試,發現根本看不到好吧,雖然之前博主做過SDRAM的讀寫測試,但是ddr3更加複雜,時序寫起來很喫力,所以需要用到vivado下自帶的ip核。具體來看下面例化過程:
1.在ip核下搜索mig 雙擊打開
2.直接next 然後在當前界面修改你的ddr3ip核的名字
這裏博主是因爲已經例化了ip核,所以名字不能修改,然後next
3.這是要不要兼容芯片,不選,點擊next
4.勾選你的存儲器類型,我的是ddr3,點擊next
5.
這個配置比較多,第一個時鐘爲ddr3實際工作的時鐘,然後選擇你的內存型號,數據寬度即可,點擊next
6.
然後輸入時鐘可能需要pll倍頻得到,一般是200Mhz,這裏注意看下最後一行的用戶地址類型,它是由bank+row+column組成的,這個在後面的讀寫測試會進一步提到。
7.
時鐘選擇不差分,然後參考時鐘爲用戶時鐘。
8.下面就是默認next,然後就是分配管腳了,這個你買的開發板一般都會提高ucf文件,直接複製就行。
然後next,生成。
以上就是ip核的簡單例化過程,這個步驟網上有很多類似的,博主就不一一講解了,把精力放在讀寫測試這塊。
首先來看老三樣:ip核用戶界面下的控制命令,讀和寫
這是控制命令,可以讓用戶來發送讀或者寫命令,需要注意的事只有當app_rdy和app_en同爲高時纔有效,命令被髮出。這裏博主通過ila上電分析發現app_rdy爲ip核自己產生的輸出信號,但是它並不是一直都是高電平,所以在後續的讀寫測試時需要判斷,至於怎麼判斷,我們後面代加上電分析。
上面是寫命令,可以看到當add_wdf_wren和add_wdf_end同爲高時數據纔能有效被寫進去,同時app_wdf_rdy也要爲高。需要注意的一點是,寫數據和寫命令此時不再有關係,爲什麼,因爲寫數據其實是通過fifo緩存,當寫命令有效時,由於先進先出的特性會把它所對應數據給寫入,當然這個很拗口,下面會給出示例
上面的是讀過程,可以看出當讀命令發出後需要一個延遲讀數據纔會有效。
下面來看代碼進行講解:
module mem_burst
#(
parameter MEM_DATA_BITS = 64,
parameter ADDR_BITS = 24
)
(
input rst, /*復位*/
input mem_clk, /*接口時鐘*/
input rd_burst_req, /*讀請求*/
input wr_burst_req, /*寫請求*/
input[9:0] rd_burst_len, /*讀數據長度*/
input[9:0] wr_burst_len, /*寫數據長度*/
input[ADDR_BITS - 1:0] rd_burst_addr, /*讀首地址*/
input[ADDR_BITS - 1:0] wr_burst_addr, /*寫首地址*/
output rd_burst_data_valid, /*讀出數據有效*/
output wr_burst_data_req, /*寫數據信號*/
output[MEM_DATA_BITS - 1:0] rd_burst_data, /*讀出的數據*/
input[MEM_DATA_BITS - 1:0] wr_burst_data, /*寫入的數據*/
output rd_burst_finish, /*讀完成*/
output wr_burst_finish, /*寫完成*/
output burst_finish, /*讀或寫完成*/
///////////////////
output[ADDR_BITS-1:0] app_addr,
output[2:0] app_cmd,
output app_en,
output [MEM_DATA_BITS-1:0] app_wdf_data,
output app_wdf_end,
output [MEM_DATA_BITS/8-1:0] app_wdf_mask,
output app_wdf_wren,
input [MEM_DATA_BITS-1:0] app_rd_data,
input app_rd_data_end,
input app_rd_data_valid,
input app_rdy,
input app_wdf_rdy,
input ui_clk_sync_rst,
input init_calib_complete
);
assign app_wdf_mask = {MEM_DATA_BITS/8{1'b0}};
localparam IDLE = 3'd0;
localparam MEM_READ = 3'd1;
localparam MEM_READ_WAIT = 3'd2;
localparam MEM_WRITE = 3'd3;
localparam MEM_WRITE_WAIT = 3'd4;
localparam READ_END = 3'd5;
localparam WRITE_END = 3'd6;
localparam MEM_WRITE_FIRST_READ = 3'd7;
/*parameter IDLE = 3'd0;
parameter MEM_READ = 3'd1;
parameter MEM_READ_WAIT = 3'd2;
parameter MEM_WRITE = 3'd3;
parameter MEM_WRITE_WAIT = 3'd4;
parameter READ_END = 3'd5;
parameter WRITE_END = 3'd6;
parameter MEM_WRITE_FIRST_READ = 3'd7;*/
reg[2:0] state;
reg[9:0] rd_addr_cnt;
reg[9:0] rd_data_cnt;
reg[9:0] wr_addr_cnt;
reg[9:0] wr_data_cnt;
reg[2:0] app_cmd_r;
reg[ADDR_BITS-1:0] app_addr_r;
reg app_en_r;
reg app_wdf_end_r;
reg app_wdf_wren_r;
assign app_cmd = app_cmd_r;
assign app_addr = app_addr_r;
assign app_en = app_en_r;
assign app_wdf_end = app_wdf_end_r;
assign app_wdf_data = wr_burst_data;
assign app_wdf_wren = app_wdf_wren_r & app_wdf_rdy;
assign rd_burst_finish = (state == READ_END);
assign wr_burst_finish = (state == WRITE_END);
assign burst_finish = rd_burst_finish | wr_burst_finish;
assign rd_burst_data = app_rd_data;
assign rd_burst_data_valid = app_rd_data_valid;
assign wr_burst_data_req = (state == MEM_WRITE) & app_wdf_rdy ;
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
app_wdf_wren_r <= 1'b0;
end
else if(app_wdf_rdy)
app_wdf_wren_r <= wr_burst_data_req;
end
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
state <= IDLE;
app_cmd_r <= 3'b000;
app_addr_r <= 0;
app_en_r <= 1'b0;
rd_addr_cnt <= 0;
rd_data_cnt <= 0;
wr_addr_cnt <= 0;
wr_data_cnt <= 0;
app_wdf_end_r <= 1'b0;
end
else if(init_calib_complete === 1'b1)
begin
case(state)
IDLE:
begin
if(rd_burst_req)
begin
state <= MEM_READ;
app_cmd_r <= 3'b001;
app_addr_r <= {rd_burst_addr,3'd0};
app_en_r <= 1'b1;
end
else if(wr_burst_req)
begin
state <= MEM_WRITE;
app_cmd_r <= 3'b000;
app_addr_r <= {wr_burst_addr,3'd0};
app_en_r <= 1'b1;
wr_addr_cnt <= 0;
app_wdf_end_r <= 1'b1;
wr_data_cnt <= 0;
end
end
MEM_READ:
begin
if(app_rdy)
begin
app_addr_r <= app_addr_r + 8;
if(rd_addr_cnt == rd_burst_len - 1)
begin
state <= MEM_READ_WAIT;
rd_addr_cnt <= 0;
app_en_r <= 1'b0;
end
else
rd_addr_cnt <= rd_addr_cnt + 1;
end
if(app_rd_data_valid)
begin
//app_addr_r <= app_addr_r + 8;
if(rd_data_cnt == rd_burst_len - 1)
begin
rd_data_cnt <= 0;
state <= READ_END;
end
else
begin
rd_data_cnt <= rd_data_cnt + 1;
end
end
end
MEM_READ_WAIT:
begin
if(app_rd_data_valid)
begin
if(rd_data_cnt == rd_burst_len - 1)
begin
rd_data_cnt <= 0;
state <= READ_END;
end
else
begin
rd_data_cnt <= rd_data_cnt + 1;
end
end
end
MEM_WRITE_FIRST_READ:
begin
app_en_r <= 1'b1;
state <= MEM_WRITE;
wr_addr_cnt <= 0;
end
MEM_WRITE:
begin
if(app_rdy)
begin
app_addr_r <= app_addr_r + 8;
if(wr_addr_cnt == wr_burst_len - 1)
begin
app_wdf_end_r <= 1'b0;
app_en_r <= 1'b0;
end
else
begin
wr_addr_cnt <= wr_addr_cnt + 1;
end
end
if(wr_burst_data_req)
begin
//app_addr_r <= app_addr_r + 8;
if(wr_data_cnt == wr_burst_len - 1)
begin
state <= MEM_WRITE_WAIT;
end
else
begin
wr_data_cnt <= wr_data_cnt + 1;
end
end
end
READ_END:
state <= IDLE;
MEM_WRITE_WAIT:
begin
if(app_rdy)
begin
app_addr_r <= app_addr_r + 'b1000;
if(wr_addr_cnt == wr_burst_len - 1)
begin
app_wdf_end_r <= 1'b0;
app_en_r <= 1'b0;
if(app_wdf_rdy)
state <= WRITE_END;
end
else
begin
wr_addr_cnt <= wr_addr_cnt + 1;
end
end
else if(~app_en_r & app_wdf_rdy)
state <= WRITE_END;
end
WRITE_END:
state <= IDLE;
default:
state <= IDLE;
endcase
end
end
endmodule
這個是黑金給的例程,一開始沒看懂,搞了好幾天纔看懂整個細節,下面來講解一下:首先state在IDLE狀態,當wr_burst_req有效時進入MEM_WRITE狀態,這時候有兩個條件判斷,第一個if(app_rdy)爲真,說明寫命令是有效的,那麼隨之伴隨的是地址的累加,同時也會計數,如果寫命令發送了128次,就結束。第二個if(wr_burst_data_req)爲真,注意wr_burst_data_req爲真實際就是app_wdf_rdy爲真,所以寫的數據是被緩存到了fifo並且當讀命令有效時會依次傳入,這裏大家會問,爲啥不讓app_rdy和app_wdf_rdy同時爲真才地址增加和寫數據呀,這是因爲app_rdy和app_wdf_rdy並不是一直都爲高電平,下面是上電結果;
看到沒,rdy爲低時,app_wdf_rdy爲高,這說明數據此時相對於地址來說多寫進去一次,那麼多的那個數據就被緩存了,等到下一個rdy爲高就會去寫入之前那個緩存的數據而不是當前時刻的數據。這也就是爲什麼每個條件判斷語句都會去計數,一個計的是多少個寫命令被髮出,另一個是多少個寫的數據被髮送。
下面來看下讀過程,首先state在IDLE狀態,當rd_burst_req有效時進入MEM_READ狀態,這裏同樣有兩個if判斷,第一個if(app_rdy)是用來判斷讀命令是否有效並且地址累加,第二個if(app_rd_data_valid)是讀數據有效,根據上面的讀流程,讀數據有效並不會隨着讀命令有效就馬上出現,一般會延遲多個週期,所以同樣需要分開判斷並且計數。來看時序:
看到沒,當讀請求有效時,下一個時鐘週期地址就開始計數並且累計了,但是app_rd_data_valid還需延遲一會纔能有效。
其實把讀寫原理搞懂後就很簡單,博主一開始卡住的地方就是寫的那塊,以爲寫數據需要app_rdy和app_wdf_rdy同時有效才能成功寫入,沒有搞懂命令和數據的關係,因爲ip核的寫數據是先緩存在fifo中的,所以即使當前寫命令無效時,寫數據依舊可以成功寫入。感覺是不是和SDRAM不一樣啊,可能沒用ip核和用了還是有區別的吧。。。
感覺ddr3的時序重要的還是這兩點,其他的至於如何精確地址和數據對應,可以具體分析,會發現程序寫的還是很嚴謹的啊。。