vivado 验证ddr引脚_vivado下ddr3的读写和测试详解
最近博主在根據例程做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的時序重要的還是這兩點,其他的至于如何精確地址和數據對應,可以具體分析,會發現程序寫的還是很嚴謹的啊。。。
總結
以上是生活随笔為你收集整理的vivado 验证ddr引脚_vivado下ddr3的读写和测试详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编译原理 python_Python局部
- 下一篇: opencv轮廓周长原理_OpenCV3