所有的fpga ip當中,用的最多的ip一般有pll、rom、ram和fifo。前面,我們討論過了rom,rom相比較ram和fifo而言,多一個mif文件的配置。而ram、fifo則不需要這樣的操作。今天可以繼續學習下ram和fifo,這樣在后面的開發中就會顯得游刃有余。
1、ip ram配置
a)創建ip ram的方法
要創建ip ram,首先需要打開ip ,輸入ram,選擇雙端口輸入,
接下來,系統會提示輸入文件名,不妨命名為.v。完成命名之后,就可以開始ip的配置了。這里面有幾個重要的對話框需要注意下,首先是設置好bit寬度和word數量,
確認是否共享時鐘,
確認是否需要晚一節拍輸出數據,
其他部分的頁面采用默認的配置就可以了。
b)準備測試代碼
有了ip ram核之后,下面就是需要把它實例化,并且添加必要的測試代碼。內容如下所示嵌入式網絡那些事 代碼,
module top(clk, rst);
input clk;
input rst;
wire clk;
wire rst;
reg[4:0] read_address;
reg[4:0] write_address;
reg[7:0] write_data;
reg write_en;
wire[7:0] read_data;
always@(posedge clk or negedge rst)
if(!rst)
read_address <= 5'd0;
else
read_address <= read_address + 1'b1;
always@(posedge clk or negedge rst)
if(!rst) begin
write_en <= 1'b0;

write_address <=5'd0;
write_data <= 8'd0;
end else begin
if(write_address != 5'd31) begin
write_en <= 1'b1;
write_address <= write_address + 1'b1;
write_data <= write_data + 8'd2 ;
end else begin
write_en <= 1'b0;
write_address <=5'd0;
write_data <= 8'd0;
end
end
ip_ram ip_ram0(
.clock(clk),
.data(write_data),
.rdaddress(read_address),
.wraddress(write_address),
.wren(write_en),
.q(read_data)
);
endmodule
測試的代碼邏輯并不復雜。首先,在fpga復位之后,先對ram進行賦值操作。等所有的地址都寫上數據之后,下面就是不停循環讀出這些數據。為了驗證這些數據是否真的被寫入到ram空間,可以通過的方法,通過jtag讀一下這些數據,觀察之前的寫操作究竟有沒有生效。
c)準備,并且開始測試
的配置方法前面提過多次,這里直接給出配置的截圖即可,
經過輸入F6和Esc之后,就可以看到截取的地址和數據了,
前面編寫測試代碼的時候,寫入的數據都是*2。假設輸入的地址是0,那么讀取的數據應該是0;如果輸入的地址是1,那么讀取的數據應該是2,依次類推。通過截圖,我們發現當是0x12h的時候,q需要等一個時鐘才能輸出0x24h。這說明,ram和rom其實是一樣的,兩者都是等一個時鐘才能輸出數據的。
2、ip fifo配置
a)和ram一樣,首先還是從ip 中尋找fifo,
同樣嵌入式網絡那些事 代碼,這里需要用戶自己輸入文件名。不妨命名為.v,接著就可以開始配置。配置的內容很多,最關鍵的有這么幾處。首先,輸入write bit長度、read bit長度、總長度。這個非常有用,
確定主要的輸出信號,
確認rdreq的用法,
完成主要的這些操作之后,再加上自身的默認配置,就可以完成ip fifo的基本設定了。
b)準備測試代碼
module top(clk, rst);
input clk;
input rst;
wire clk;
wire rst;
reg[7:0] write_data;
wire[31:0] read_data;
wire write_full;
wire read_empty;
reg write_req;
reg read_req;
// write state and next_write_state
reg[1:0] write_state;
reg[1:0] next_write_state;
always@(posedge clk or negedge rst)
if(!rst)
write_state <= 2'b00;
else
write_state <= next_write_state;
always@(*)
if(!rst)
next_write_state <= 2'b00;
else begin
case (write_state)
2'b00:
if(!write_full)
next_write_state <= 2'b01;
else
next_write_state <= 2'b00;

2'b01:
if(write_full)
next_write_state <= 2'b00;
else
next_write_state <= 2'b01;
default:
next_write_state <= 2'b00;
endcase
end
always @(posedge clk or negedge rst)
if(!rst)
write_req <= 1'b0;
else if(next_write_state == 2'b01)
write_req <= 1'b1;
else
write_req <= 1'b0;
always @(posedge clk or negedge rst)
if(!rst)
write_data <= 8'd0;
else if(next_write_state == 2'b01)
write_data <= write_data + 1'b1;
// read_state and next_read_state
reg[1:0] read_state;
reg[1:0] next_read_state;
always@(posedge clk or negedge rst)
if(!rst)
read_state <= 2'b00;
else
read_state <= next_read_state;
always@(*)
if(!rst)
next_read_state <= 2'b00;
else begin

case (read_state)
2'b00:
if(!read_empty)
next_read_state <= 2'b01;
else
next_read_state <= 2'b00;
2'b01:
if(read_empty)
next_read_state <= 2'b00;
else
next_read_state <= 2'b01;
default:
next_read_state <= 2'b00;
endcase
end
always @(posedge clk or negedge rst)
if(!rst)
read_req <= 1'b0;
else if(next_read_state == 2'b01)
read_req <= 1'b1;
else
read_req <= 1'b0;
// invoke ip ip_fifo
wire[7:0] rdusedw;
wire[7:0] wrusedw;
ip_fifo ip_fifo0(
.data(write_data),
.rdclk(clk),
.rdreq(read_req),
.wrclk(clk),
.wrreq(write_req),
.q(read_data),
.rdempty(read_empty),
.rdusedw(rdusedw),

.wrfull(write_full),
.wrusedw(wrusedw));
endmodule
測試代碼的內容不復雜,主要的工作就是把流程切分成和。對于而言,一旦發現fifo不滿,就開始寫入數據。而對于而言,每當發現fifo非空,就不停讀取數據。這樣,一寫一讀,就構成了fifo的基本操作。如果兩者速率不匹配,或者字節長度不同,fifo還可以充當臨時的角色。當然,為了驗證我們的測試是否正確,最終還得借助于這個工具。
c)配置和測試
中主要檢查寫入和讀取的數據,
燒入sof文件之后,經過F6和Esc之后,就可以看到相關的波形數據了,
補充1:
之前測試的時候一直沒有注意時序約束。事實上,在fpga開發過程中最好做到0 error,0 。這是比較合適的。所以,項目中最好添加必要的時序約束文件,告知軟件當前fpga希望以什么樣的時鐘運行,
# Clock constraints
create_clock -name "clk" -period 20.000ns [get_ports {clk}]
# Automatically constrain PLL and other generated clocks
derive_pll_clocks -create_base_clocks
# Automatically calculate clock uncertainty to jitter and other effects.
derive_clock_uncertainty
# tsu/th constraints
# tco constraints
# tpd constraints
補充2:
另外,如果引腳比較多,pin bind也是一個體力活。這部分可以借助于tcl腳本來完成。編寫完tcl之后,就可以通過"Tools"->"Tcl "運行腳本即可,
package require ::quartus::project
set_location_assignment PIN_E1 -to clk
set_location_assignment PIN_N13 -to rst
set_location_assignment PIN_D9 -to led[3]
set_location_assignment PIN_C9 -to led[2]
set_location_assignment PIN_F9 -to led[1]
set_location_assignment PIN_E10 -to led[0]