作者:李西銳??校對:陸輝
大俠好,歡迎來到FPGA技術(shù)江湖。本系列將帶來FPGA的系統(tǒng)性學(xué)習(xí),從最基本的數(shù)字電路基礎(chǔ)開始,最詳細(xì)操作步驟,最直白的言語描述,手把手的“傻瓜式”講解,讓電子、信息、通信類專業(yè)學(xué)生、初入職場小白及打算進(jìn)階提升的職業(yè)開發(fā)者都可以有系統(tǒng)性學(xué)習(xí)的機(jī)會。
系統(tǒng)性的掌握技術(shù)開發(fā)以及相關(guān)要求,對個人就業(yè)以及職業(yè)發(fā)展都有著潛在的幫助,希望對大家有所幫助。本次帶來Vivado系列,F(xiàn)IFO使用教程。話不多說,上貨。
FIFO使用教程
FIFO的英文全稱叫做First in First out,即先進(jìn)先出。這也就決定了這個IP核的特殊性,先寫進(jìn)去的數(shù)據(jù)優(yōu)先被讀出,所以,F(xiàn)IFO是不需要地址信號線的,這也是它的一大特點(diǎn),通常用來做數(shù)據(jù)的緩存,或者用來解決高速異步數(shù)據(jù)的交互,即解決了跨時鐘域的問題。此外,F(xiàn)IFO還有一個特點(diǎn),就是數(shù)據(jù)被讀出之后就不存在了,不像RAM和ROM一樣,數(shù)據(jù)被讀出后還存在。所以我們?nèi)绻脒M(jìn)行多次的讀,那么就需要進(jìn)行同樣次數(shù)的寫。
FIFO分為同步時鐘和異步時鐘,同步FIFO指的是讀寫使用同一個時鐘,在時鐘沿信號來的時候進(jìn)行讀寫。異步FIFO是指讀寫在不同時鐘下進(jìn)行,這樣我們可以實(shí)現(xiàn)讀寫不同速度。
那么接下來,我們就來實(shí)現(xiàn)一下異步FIFO的讀寫過程。
上圖為選擇異步FIFO之后的圖示,在這個圖示中,我們給大家解釋一下每個信號的含義。
FIFO_WRITE:
full:FIFO的滿信號,當(dāng)FIFO的存儲空間寫滿了之后,此信號拉高,否則為低。此信號為FIFO的輸出信號。
din[7:0]:FIFO的數(shù)據(jù)輸入,寫進(jìn)FIFO的數(shù)據(jù)通過此信號線進(jìn)入FIFO。
wr_en:FIFO的寫使能,當(dāng)我們要往FIFO里面寫入數(shù)據(jù)時,拉高此信號。此信號為FIFO的輸入。
FIFO_READ:
empty:FIFO的空信號,當(dāng)FIFO的存儲空間空了之后,此信號拉高,否則為低。此信號為FIFO的輸出信號。
dout:FIFO的數(shù)據(jù)輸出,讀出FIFO的數(shù)據(jù)通過此信號線輸出
rd_en:FIFO的讀使能,當(dāng)我們要從FIFO里面讀出數(shù)據(jù)時,拉高此信號。此信號為FIFO的輸出。
rst:FIFO復(fù)位,默認(rèn)高電平有效。
wr_clk:寫時鐘
rd_clk: 讀時鐘
wr_rst_busy:寫復(fù)位忙信號
rd_rst_busy:讀復(fù)位忙信號
在了解了FIFO的端口之后,我們來實(shí)現(xiàn)一個應(yīng)用實(shí)例。比如,我們以10MHz的速度往FIFO里面寫數(shù)據(jù),寫滿之后,在20MHz的時鐘下將數(shù)據(jù)讀出,一直讀空。當(dāng)然,在顯示應(yīng)用中,F(xiàn)IFO的讀寫是可以同步進(jìn)行的。
首先,我們先來新建工程。
新建好之后,我們先調(diào)用一下IP核:
在IP核管理器界面,搜索FIFO,然后選中圖示所選項(xiàng)雙擊打開。
在FIFO類型選項(xiàng),我們選擇異步FIFO。剛打開默認(rèn)的選項(xiàng)為同步FIFO。
在數(shù)據(jù)端口配置界面,我們將數(shù)據(jù)位寬改為8bit,深度使用1024。
復(fù)位端口在這就不再使用了,所以勾選位置取消掉。
在此界面出現(xiàn)了almost full flag和almost empty flag。這兩個信號是幾乎滿或空的標(biāo)志信號,在此實(shí)驗(yàn)中,我們不使用。
Data?count是FIFO數(shù)據(jù)用量計(jì)數(shù)器,代表了此時FIFO的內(nèi)部存儲被使用的情況。假設(shè)我們寫進(jìn)去了10個數(shù),那么兩個計(jì)數(shù)器都為10。
此界面為IP核的信息,在此界面可以看出,我們的讀寫深度發(fā)生了變化,我們在前面設(shè)置的深度為1024,但是在此處顯示的卻是1023。原因是因?yàn)镕IFO結(jié)構(gòu)的特殊性,并不是我們設(shè)置的有問題。所以,在我們這個異步FIFO中,深度為1023。
點(diǎn)擊OK直接生成。在點(diǎn)擊Generate。
此外,我們還需要兩個不同時鐘,在這里我們使用鎖相環(huán)生成。
在管理界面搜索clock。配置過程我們在此前已經(jīng)講過,就不在過多敘述。
接下來我們寫一下fifo的寫控制器,代碼如下:
1 module fifo_wr(
2
3 input wire clk,
4 input wire rst_n,
5 input wire empty,
6 input wire full,
7 output reg fifo_wr_en,
8 output reg [7:0] fifo_data_in
9 );
10
11 reg state;
12
13 always @ (posedge clk, negedge rst_n)
14 begin
15 if(rst_n == 1'b0)
16 begin
17 fifo_wr_en <= 1'b0;
18 fifo_data_in <= 8'd0;
19 state <= 1'b0;
20 end
21 else
22 case(state)
23 1'b0 : begin
24 if(empty)
25 state <= 1'b1;
26 else
27 state <= 1'b0;
28 end
29 1'b1 : begin
30 if(full)
31 begin
32 fifo_wr_en <= 1'b0;
33 fifo_data_in <= 8'd0;
34 state <= 1'b0;
35 end
36 else
37 begin
38 fifo_wr_en <= 1'b1;
39 fifo_data_in <= fifo_data_in + 1'b1;
40 state <= 1'b1;
41 end
42 end
43 endcase
44 end
45
46 endmodule
因?yàn)槲覀兊膶?shí)驗(yàn)是讀空了才寫,所以我們用狀態(tài)機(jī)來做,先判斷FIFO是否為空。讀控制器代碼如下:
1 module fifo_rd(
2
3 input wire clk,
4 input wire rst_n,
5 input wire empty,
6 input wire full,
7 output reg fifo_rd_en
8 );
9
10 reg state;
11
12 always @ (posedge clk, negedge rst_n)
13 begin
14 if(rst_n == 1'b0)
15 begin
16 fifo_rd_en <= 1'b0;
17 state <= 1'b0;
18 end
19 else
20 case(state)
21 1'b0 : begin
22 if(full)
23 state <= 1'b1;
24 else
25 state <= 1'b0;
26 end
27 1'b1 : begin
28 if(empty)
29 begin
30 fifo_rd_en <= 1'b0;
31 state <= 1'b0;
32 end
33 else
34 begin
35 fifo_rd_en <= 1'b1;
36 state <= 1'b1;
37 end
38 end
39 endcase
40 end
41
42 endmodule
頂層代碼如下:
1 module fifo(
2
3 input wire clk,
4 input wire rst_n,
5 output wire [7:0] q
6 );
7
8 wire fifo_wr_clk;
9 wire fifo_rd_clk;
10 wire locked;
11 wire empty;
12 wire full;
13 wire fifo_wr_en;
14 wire [7:0] fifo_data_in;
15 wire fifo_rd_en;
16
17 clk_wiz_0 clk_wiz_0_inst
18 (
19 // Clock out ports
20 .clk_out1(fifo_wr_clk), // output clk_out1
21 .clk_out2(fifo_rd_clk), // output clk_out2
22 // Status and control signals
23 .reset(~rst_n), // input reset
24 .locked(locked), // output locked
25 // Clock in ports
26 .clk_in1(clk)); // input clk_in1
27
28 fifo_wr fifo_wr_inst(
29
30 .clk (fifo_wr_clk),
31 .rst_n (locked ),
32 .empty (empty ),
33 .full (full ),
34 .fifo_wr_en (fifo_wr_en ),
35 .fifo_data_in (fifo_data_in)
36 );
37
38 fifo_generator_0 fifo_generator_0_inst (
39 .wr_clk(fifo_wr_clk), // input wire wr_clk
40 .rd_clk(fifo_rd_clk), // input wire rd_clk
41 .din(fifo_data_in), // input wire [7 : 0] din
42 .wr_en(fifo_wr_en), // input wire wr_en
43 .rd_en(fifo_rd_en), // input wire rd_en
44 .dout(q), // output wire [7 : 0] dout
45 .full(full), // output wire full
46 .empty(empty) // output wire empty
47 );
48
49 fifo_rd fifo_rd_inst(
50
51 .clk (fifo_rd_clk),
52 .rst_n (locked ),
53 .empty (empty ),
54 .full (full ),
55 .fifo_rd_en (fifo_rd_en)
56 );
57
58 endmodule
代碼寫完之后,我們寫個仿真驗(yàn)證一下波形,代碼如下:
1 `timescale 1ns / 1ps
2
3 module fifo_tb;
4
5 reg clk;
6 reg rst_n;
7 wire [7:0] q;
8
9 initial begin
10 clk = 0;
11 rst_n = 0;
12 #105;
13 rst_n = 1;
14 #10000;
15 $stop;
16 end
17
18 always #10 clk = ~clk;
19
20 fifo fifo_inst(
21
22 .clk (clk),
23 .rst_n (rst_n),
24 .q (q)
25 );
26
27 endmodule
打開波形之后,我們將讀寫控制模塊的信號全部添加到波形窗口:
添加好之后,點(diǎn)擊restart和run-all
由于波形默認(rèn)運(yùn)行10us,我們觀察不到全部波形,所以,在此我們繼續(xù)點(diǎn)擊run-all,然后點(diǎn)擊break,讓仿真停止。
然后,我們觀察波形:
在波形里面可以清楚的看到我們的fifo_data_in和q的波形,一長一短。這是因?yàn)樽x的速度快,所以波形維持的時間短。寫數(shù)據(jù)的時間長度是讀數(shù)據(jù)時間長度的兩倍。
然后放大波形觀察其他信號:
在黃色光標(biāo)位置,可以看到滿信號拉高了,然后寫使能就拉低了,狀態(tài)開始進(jìn)入到讀。在讀使能拉高之后,輸出q就有了數(shù)據(jù),但是我們的empty信號維持了一段時間才拉低,這是因?yàn)閒ifo的特殊結(jié)構(gòu)導(dǎo)致的,在此我們就不再過多討論。
結(jié)論:異步FIFO控制正確,仿真波形輸入和輸出信號正常。