?文章來源地址http://www.zghlxwxcb.cn/news/detail-483852.html
?之前也在CSDN上面寫過兩個FIFO相關的文章,不過代碼看起來比較復雜,且注釋也比較少,不利于新手入門。很多時候都沒有耐心繼續(xù)看下去。
http://t.csdn.cn/0dPX6
http://t.csdn.cn/lYvoY?
因為自己本身是一個初學者,就從初學者的視角來看待并學習FIFO。
為什么選擇學習FIFO?
在學完雙端口RAM之后看待FIFO,會覺得為什么要用FIFO呢?雙端口的RAM也可以實現(xiàn)數(shù)據(jù)的存儲與讀取,讀寫的時鐘也可以不一樣,為什么不用RAM而要基于RAM來設計一個FIFO呢?
FIFO與RAM的區(qū)別是先進先出,不需要讀地址和寫地址。?寫時鐘下,將數(shù)據(jù)寫入FIFO中,在讀時鐘下將先寫入的數(shù)據(jù)先讀出來,不需要向FIFO輸入寫地址和讀地址即可完成數(shù)據(jù)的存儲以及不同時鐘下的讀寫操作,這樣一聽是非常方便的。在SDRAM的學習過程中,我們知道有突發(fā)長度這個東西,當突發(fā)長度為1的時候,即一個寫地址對應一個寫數(shù)據(jù),這樣是非常麻煩的,所以很多SDRAM如DDR3這種,都會將突發(fā)長度設置為8,即給一個首地址,后面連續(xù)讀取8個數(shù)據(jù)。
?文章來源:http://www.zghlxwxcb.cn/news/detail-483852.html
再貼一張異步FIFO的圖
?
在寫代碼之前,需要了解幾個概念。
?先思考,F(xiàn)IFO的存儲空間和RAM是一樣的,就像一個n行x1列的表格,每個表格里面存放一個數(shù)據(jù),并且對應一個地址,在讀寫的過程中肯定會存在表格寫滿的情況和讀的時候里面沒有數(shù)據(jù)的情況,那應該怎么判斷呢?
讀寫同時進行時
①首先是在讀的視角,如果如果讀一行數(shù)據(jù)的時候,剛好也在往這一行數(shù)據(jù)里面寫數(shù)據(jù),那這個時候即可判斷讀空了,如果再繼續(xù)向下讀的話,里面就沒有寫進的數(shù)據(jù),讀出的數(shù)據(jù)也不是我們寫進去的,就是無效的。
所以讀空的判斷條件是:在讀時鐘的視角下,寫時鐘同步過來的地址等于我目前正在讀的地址。
關于跨時鐘域的問題,大家可以去搜索一下跨時鐘域以及亞穩(wěn)態(tài)。也可以看我的這篇文章。
http://t.csdn.cn/hvJTa
②在寫的視角下,?那什么時候寫滿呢?因為地址是有限的嘛,當讀完一個數(shù)據(jù)的時候,讀對應哪個地址的數(shù)據(jù)就已經(jīng)不需要了,因為我們以及讀了,即讀完的那個“位置”空了。所以當寫完一圈,并且追上下一輪的讀的時候,就代表寫滿了。
所以寫滿判斷的條件是:在寫的時鐘下,寫完一圈對應的地址,等于同步過來的讀地址。
?
其次在寫代碼的時候,還需要了解格雷碼,地址是按照0000-0001-0010-xxxx這種增長的,但是在地址變化的過程中,地址中的位數(shù)會存在”跳變“,如從0001-0010這兩個相鄰碼的時候,有兩位發(fā)生了變化,這樣是不好的。?具體可以參考這篇文章
http://t.csdn.cn/OiesB
以下是代碼Verilog的代碼
`timescale 1ns / 1ps
module asyn_fifo1
(
input rst_n ,
input wr_clk ,
input wr_en ,
input [7:0] data_in ,
input rd_clk ,
input rd_en ,
output full ,
output empty ,
output reg [7:0] data_out
);
reg [7:0] ram_mem[255:0] ; //定義一個位寬為8bit深度為256的雙端口RAM
wire [7:0] rd_addr ;
wire [7:0] wr_addr ;
reg [8:0] rd_addr_ptr ; //格雷碼需要移位運算,且判斷寫滿信號也需要多一位
reg [8:0] wr_addr_ptr ;
wire [8:0] rd_addr_gray ;
reg [8:0] rd_addr_gray1 ;
reg [8:0] rd_addr_gray2 ;
wire [8:0] wr_addr_gray ;
reg [8:0] wr_addr_gray1 ;
reg [8:0] wr_addr_gray2 ;
assign rd_addr[7:0] = rd_addr_ptr[7:0];
assign wr_addr[7:0] = wr_addr_ptr[7:0];
assign rd_addr_gray = (rd_addr_ptr>>1) ^ rd_addr_ptr; //bin to gray
assign wr_addr_gray = (wr_addr_ptr>>1) ^ wr_addr_ptr;
//dual port ram
integer i;
always @(posedge wr_clk or negedge rst_n) //寫時鐘下初始化RAM
begin
if(rst_n == 1'b0)
for(i=0;i<256;i=i+1)
ram_mem[i] <= 1'b0;
else if(wr_en && ~full) //寫使能且沒有寫滿
ram_mem[wr_addr] = data_in;
else
ram_mem[wr_addr] = ram_mem[wr_addr];
end
//rd_addr_ptr++ and wr_addr_ptr++
always @(posedge rd_clk or negedge rst_n) //讀時鐘下,對讀地址進行操作
begin
if(rst_n == 1'b0)
rd_addr_ptr <= 1'b0;
else if(rd_en && ~empty)
rd_addr_ptr <= rd_addr_ptr + 1'b1;
else
rd_addr_ptr <= rd_addr_ptr;
end
always @(posedge wr_clk or negedge rst_n) //寫時鐘下,對寫地址進行操作
begin
if(rst_n == 1'b0)
wr_addr_ptr <= 1'b0;
else if(wr_en && ~full)
wr_addr_ptr <= wr_addr_ptr + 1'b1;
else
wr_addr_ptr <= wr_addr_ptr;
end
//gray and two regsiter
always @(posedge wr_clk or negedge rst_n) //寫時鐘視角下,把讀時鐘同步到自己的時鐘下
begin
if(rst_n == 1'b0)begin
rd_addr_gray1 <= 1'b0;
rd_addr_gray2 <= 1'b0;
end
else begin
rd_addr_gray1 <= rd_addr_gray;
rd_addr_gray2 <= rd_addr_gray1;
end
end
always @(posedge rd_clk or negedge rst_n) //讀時鐘視角下,把寫時鐘同步到自己的時鐘下
begin
if(rst_n == 1'b0)begin
wr_addr_gray1 <= 1'b0;
wr_addr_gray2 <= 1'b0;
end
else begin
wr_addr_gray1 <= wr_addr_gray;
wr_addr_gray2 <= wr_addr_gray1;
end
end
//data_out
always @(posedge rd_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
data_out <= 1'b0;
else if(rd_en && ~empty)
data_out <= ram_mem[rd_addr];
else
data_out <= 1'b0;
end
assign empty = (rd_addr_gray == wr_addr_gray2)?1'b1:1'b0; //判斷讀沒讀空
assign full = (wr_addr_gray[8:7] != rd_addr_gray2[8:7]) && (wr_addr_gray[6:0] == rd_addr_gray2[6:0]); //判斷是否寫滿
endmodule
?
以下是tb仿真文件代碼
`timescale 1ns / 1ps
module asyn_fifo1_tb();
reg rst_n ;
reg wr_clk ;
reg wr_en ;
reg [7:0] data_in ;
reg rd_clk ;
reg rd_en ;
wire full ;
wire empty ;
wire [7:0] data_out ;
asyn_fifo1 asyn_fifo1_inst
(
.rst_n (rst_n) ,
.wr_clk (wr_clk) ,
.wr_en (wr_en) ,
.data_in (data_in) ,
.rd_clk (rd_clk) ,
.rd_en (rd_en) ,
.full (full) ,
.empty (empty) ,
.data_out (data_out)
);
initial wr_clk = 0;
always #10 wr_clk = ~wr_clk; //寫時鐘為50MHz
initial rd_clk = 0;
always #30 rd_clk = ~rd_clk; //讀時鐘頻率為寫時鐘的1/3
always @(posedge wr_clk or negedge rst_n) //不停的向FIFO中寫0-255的數(shù)據(jù)
begin
if(rst_n == 1'b0)
data_in <= 0;
else if (wr_en)
data_in <= data_in+1'b1;
else
data_in <= data_in;
end
initial begin
rst_n = 0;
wr_en = 0;
rd_en = 0;
#200; //時間為200ns時,允許寫入
rst_n = 1;
wr_en = 1;
#20000; //時間再過20000ns時,不允許寫,開始讀
wr_en = 0;
rd_en = 1;
#20000; //時間再過20000ns時,讀停止
rd_en=0;
$stop;
end
endmodule
波形分析
200ns時,數(shù)據(jù)開始寫入FIFO中,如下圖
?寫滿時,full信號拉高,后面繼續(xù)不停寫入,但滿了,都是無效的寫入。
?
20200ns時,開始讀數(shù)據(jù)
?讀完后,empty信號拉高,表示讀空。
?
由于寫時鐘為讀時鐘的三倍,從整體的波形圖中也可以看出,寫滿數(shù)據(jù)的時間是讀完數(shù)據(jù)時間的1/3.
?
?
?
?
?
?
?
到了這里,關于【小白入門】Verilog實現(xiàn)異步FIFO的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!