理論知識
FIFO(First In First Out, 先入先出 ),是一種數據緩沖器,用來實現數據先入先出的讀寫方式。數據按順序寫入 FIFO,先被寫入的數據同樣在讀取的時候先被讀出,所以 FIFO存儲器沒有地址線,有一個寫端口和一個讀端口。
FIFO 存儲器主要是作為緩存,應用在同步時鐘系統和異步時鐘系統中,分為 SCFIFO(同步 FIFO)和 DCFIFO(異步 FIFO)。后面實例中如:多比特數據做跨時鐘域的轉換、前后帶寬不同步等都用到了FIFO。
同步FIFO-SCFIFO
SCFIFO IP核配置
full:寫滿標志位,有效表示 FIFO 已經存儲滿了,此時禁止再往FIFO中寫入數據,防止數據溢出丟失。當寫入數據量達到FIFO設置的最大空間時,時鐘上升沿寫入最后一個數據同時full拉高;讀取數據時隨時鐘上升沿觸發同時拉低。
empty:讀空標志位,有效表示 FIFO 中已經沒有數據了,此時禁止FIFO繼續再讀出數據,否則讀出的將是無效數據。寫入數據同時拉低;讀到最后一個數據同時拉高。
usedw:顯示當前FIFO中已存數據個數,寫第一個數據時就置1,空或滿時值為0(滿是因為寄存器溢出)。
almost full:幾乎滿標志信號,我們可以控制FIFO快要被寫滿的時候和full信號的作用一樣。
almost empty:幾乎空標志信號,我們可以控制FIFO快要被讀空的時候和empty信號的作用一樣。
Asynchronous clear:異步復位信號,用于清空FIFO。
Synchronous clear:同步復位信號,用于清空FIFO。
后面三個沒有使用
SCFIFO IP核調用
我們需要寫一個頂層模塊,并通過testbench定義激勵來觀察信號變化,驗證SCFIFO IP核。我們可以看到生成的模塊文件中有以下幾個端口,頂層模塊需要進行實例化
輸入信號有:sys_clk、輸入256個8bit的數據pi_data(值為十進制0~255),輸入數據有效的標志信號pi_flag,寫請求信號rdreq。輸出信號有:讀取的數據po_data、空標志信號empty、滿標志信號full、指示FIFO中存在數據個數的信號usedw。
編寫代碼
module fifo(
input wire sys_clk ,
input wire [7:0] pi_data ,
input wire pi_flag ,
input wire rdreq ,
output wire [7:0] po_data ,
output wire empty ,
output wire full ,
output wire [7:0] usedw
);
scfifo_256x8 scfifo_256x8_inst(
.clock (sys_clk ),
.data (pi_data ),
.rdreq (rdreq ),
.wrreq (pi_flag ),
.empty (empty ),
.full (full ),
.q (po_data ),
.usedw (usedw )
);
endmodule
編寫testbench
//reg define
reg sys_clk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rdreq ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;
//wire define
wire [7:0] po_data ;
wire empty ;
wire full ;
wire [7:0] usedw ;
//初始化系統時鐘、復位
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
end
//sys_clk:模擬系統時鐘,每10ns電平翻轉一次,周期為20ns,頻率為50MHz
always #10 sys_clk = ~sys_clk;
//cnt_baud:計數從0到3的計數器,用于產生輸入數據間的間隔
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_baud <= 2'b0;
else if(&cnt_baud == 1'b1)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
//pi_flag:輸入數據有效標志信號,也作為FIFO的寫請求信號
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_flag <= 1'b0;
else if((cnt_baud == 2'd0) && (rdreq == 1'b0))
pi_flag <= 1'b1;
else
pi_flag <= 1'b0;
//pi_data:輸入頂層模塊的數據,要寫入到FIFO中的數據
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data <= 8'b0;
else if((pi_data == 8'd255) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1)
pi_data <= pi_data + 1'b1;
//rdreq:FIFO讀請求信號
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rdreq <= 1'b0;
else if(full == 1'b1)
rdreq <= 1'b1;
else if(empty == 1'b1)
rdreq <= 1'b0;
fifo fifo_inst(
.sys_clk (sys_clk ),
.pi_data (pi_data ),
.pi_flag (pi_flag ),
.rdreq (rdreq ),
.po_data (po_data ),
.empty (empty ),
.full (full ),
.usedw (usedw )
);
endmodule
初始化:初始時鐘為高電平,復位有效,延遲100ns后復位釋放
模擬時鐘:每隔10ns翻轉,時鐘周期20ns,頻率50MHz
輸入間隔計數器cnt_baud:從0-3計數,復位和溢出歸0,其他情況+1,這里溢出判斷條件(cnt_baud=11)用的是位與為1
輸入有效標志信號pi_flag:也是寫請求信號,變化條件是時鐘上升沿和復位下降沿。復位有效時歸0;計數0且沒有讀請求時拉高相當于四個時鐘周期產生一次;其他情況歸0
輸入數據pi_data:是要寫到FIFO中的數據,變化條件是時鐘上升沿和復位下降沿。復位有效時歸0;輸入255且pi_flag有效時歸0;其他情況+1,意味著輸入數據從0-255循環
讀請求rdreq:變化條件是時鐘上升沿和復位下降沿。復位有效時歸0;full信號拉高時也拉高說明存滿該讀了;empty拉高時就歸0說明讀空禁讀了
實例化
波形變化
我們選擇的是普通模式,讀出數據比讀使能晚一拍
對比一下"先出數據FIFO模式"的波形,就可以看出延遲不延遲一拍的區別,這里沒有演示,實際上只需要在下面這一步時選擇先出數據模式即可
異步FIFO-SCFIFO
DCFIFO IP核配置
命名為"dcfifo_256x8to128x16",我們調用的dcfifo是輸入256個深度8位寬、輸出128個深度16位寬
DSCFIFO IP核調用
我們需要寫一個頂層模塊,并通過testbench定義激勵來觀察信號變化,驗證DCFIFO IP核。我們可以看到生成的模塊文件中有以下幾個端口,頂層模塊需要進行實例化
輸入信號 :50MHz寫時鐘wrclk,輸入256個8bit的數據pi_data(值為十進制0~255),輸入數據有效的標志信號pi_flag,25MHz的讀時鐘rdclk,寫請求信號rdreq。 輸出信號 :同步于wrclk的空標志信號wrempty,同步于wrclk的滿標志信號wrfull,同步于wrclk指示FIFO中存在數據個數的信號wrusedw,從FIFO中讀取的數據po_data,同步于rdclk的FIFO空標志信號rdempty,同步于rdclk 的FIFO滿標志信號rdfull,同步于rdclk指示FIFO中存在數據個數的信號rdusedw。
編寫代碼
module fifo
(
//同步于FIFO寫時鐘
input wire wrclk ,
input wire [7:0] pi_data ,
input wire pi_flag ,
//同步于FIFO讀時鐘
input wire rdclk ,
input wire rdreq ,
//同步于FIFO寫時鐘
output wire wrempty ,
output wire wrfull ,
output wire [7:0] wrusedw ,
//同步于FIFO讀時鐘
output wire [15:0] po_data ,
output wire rdempty ,
output wire rdfull ,
output wire [6:0] rdusedw
);
dcfifo_256x8to128x16 dcfifo_256x8to128x16_inst
(
.data (pi_data), //input [7:0] data
.rdclk (rdclk ), //input rdclk
.rdreq (rdreq ), //input rdreq
.wrclk (wrclk ), //input wrclk
.wrreq (pi_flag), //input wrreq
.q (po_data), //output [15:0] q
.rdempty(rdempty), //output rdempty
.rdfull (rdfull ), //output rdfull
.rdusedw(rdusedw), //output [6:0] rdusedw
.wrempty(wrempty), //output wrempty
.wrfull (wrfull ), //output wrfull
.wrusedw(wrusedw) //output [7:0] wrusedw
);
endmodule
編寫testbench
`timescale 1ns/1ns
module tb_fifo();
//reg define
reg wrclk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rdclk ;
reg rdreq ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;
reg wrfull_reg0 ;
reg wrfull_reg1 ;
//wire define
wire wrempty ;
wire wrfull ;
wire [7:0] wrusedw ;
wire [15:0] po_data ;
wire rdempty ;
wire rdfull ;
wire [6:0] rdusedw ;
//初始化時鐘、復位
initial begin
wrclk = 1'b1;
rdclk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
end
//wrclk:模擬FIFO的寫時鐘,每10ns電平翻轉一次,周期為20ns,頻率為50MHz
always #10 wrclk = ~wrclk;
//rdclk:模擬FIFO的讀時鐘,每20ns電平翻轉一次,周期為40ns,頻率為25MHz
always #20 rdclk = ~rdclk;
//cnt_baud:計數從0到3的計數器,用于產生輸入數據間的間隔
always@(posedge wrclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_baud <= 2'b0;
else if(&cnt_baud == 1'b1)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
//pi_flag:輸入數據有效標志信號,也作為FIFO的寫請求信號
always@(posedge wrclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_flag <= 1'b0;
else if((cnt_baud == 2'd0) && (rdreq == 1'b0))
pi_flag <= 1'b1;
else
pi_flag <= 1'b0;
//pi_data:輸入頂層模塊的數據,要寫入到FIFO中的數據
always@(posedge wrclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data <= 8'b0;
else if((pi_data == 8'd255) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1)
pi_data <= pi_data + 1'b1;
//將同步于rdclk時鐘的寫滿標志信號wrfull在rdclk時鐘下打兩拍
always@(posedge rdclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
wrfull_reg0 <= 1'b0;
wrfull_reg1 <= 1'b0;
end
else
begin
wrfull_reg0 <= wrfull;
wrfull_reg1 <= wrfull_reg0;
end
//rdreq:FIFO讀請求信號同步于rdclk時鐘
always@(posedge rdclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rdreq <= 1'b0;
//如果wrfull信號有效就立刻讀,則不會看到rd_full信號拉高,
//所以此處使用wrfull在rdclk時鐘下打兩拍后的信號
else if(wrfull_reg1 == 1'b1)
rdreq <= 1'b1;
else if(rdempty == 1'b1)//當FIFO中的數據被讀空時停止讀取FIFO中的數據
rdreq <= 1'b0;
fifo fifo_inst(
.wrclk (wrclk ),
.pi_data(pi_data),
.pi_flag(pi_flag),
.rdclk (rdclk ),
.rdreq (rdreq ),
.wrempty(wrempty),
.wrfull (wrfull ),
.wrusedw(wrusedw),
.po_data(po_data),
.rdempty(rdempty),
.rdfull (rdfull ),
.rdusedw(rdusedw)
);
endmodule
初始化:和同步fifo的區別在于讀寫時鐘是異步的,需要初始化兩個時鐘
時鐘模擬:寫時鐘50MHz每隔10ns翻轉一次,讀時鐘25MHz每隔20ns翻轉一次
cnt_baud:計數器是基于寫時鐘的,將變化條件中時鐘上升沿改為寫時鐘的上升沿
pi_flag:輸入數據有效標志信號,基于寫時鐘,同上
pi_data:輸入數據,基于寫時鐘,同上
wrfull:寫滿標志信號,基于讀時鐘,變化條件是讀時鐘上升沿和復位下降沿。wrfull在讀時鐘下打兩拍,(always塊中的語句是順序執行的,使用兩個寄存器b,c,令b=輸入a,c=b,并輸出c,那么c相對于a而言波形不會產生變化,只是有兩個時鐘周期的延遲,這個就叫打拍,使用幾個寄存器就是延遲幾個周期就是打幾拍),這里打兩拍的原因是如果wrfull有效立刻就讀,就看不到rd_full拉高
rdreq:讀請求信號,基于讀時鐘,變化條件是讀時鐘上升沿和復位下降沿。復位時歸0;寫滿信號打兩拍之后的信號拉高就拉高表示寫滿需要讀,讀空信號拉高就拉低表示讀空不能讀
波形變化
可以看到當pi_flag為高且pi_data為255的同時wrfull滿標志信號先拉高了,延后一段時間rdfull滿標志信號也拉高了,說明FIFO的存儲空間已經滿了。wrfull滿標志信號和rdfull滿標志信號同步于 不同的時鐘 ,所以拉高的時間不同步。
還可以看到wrusedw信號是8位的計數到255,而rdusedw信號是7位的計數到127,因為輸入是8btit的,輸出是16bit的,8256=16128。wrusedw信號從255變成了0、rdusedw信號從127變成0的原因和SCFIFO中的情況一樣,都是因為數據存儲滿了,FIFO內部的計數器溢出所導致的。
另外可以看出讀出的16bit數據,是兩次的輸入8bit數據拼湊而成,先輸入的在低位,后輸入的在高位,例如輸入00,01,02,03...,兩次輸入的數據拼湊為01_00,03_02進行輸出。
如果不打兩拍會發生什么呢?我們來看波形
-
FPGA
+關注
關注
1626文章
21665瀏覽量
601818 -
存儲器
+關注
關注
38文章
7452瀏覽量
163602 -
緩沖器
+關注
關注
6文章
1917瀏覽量
45450 -
fifo
+關注
關注
3文章
387瀏覽量
43548 -
IP核
+關注
關注
4文章
326瀏覽量
49428
發布評論請先 登錄
相關推薦
評論