- fifo的基本原理
- 基於計數器的同步fifo實現(1)
- 基於計數器的同步fifo實現(2)
- 基於高位擴充套件法的fifo實現
fifo的基本原理
FIFO(first in first out),即先進先出儲存器,功能與資料結構中的佇列相似。
在IC設計中,FIFO常用來緩衝突發資料,流式資料與塊資料的轉換等等。
比如上圖中,在兩個block之間,透過輸入命令fifo來快取block1的輸入請求命令。
基於計數器的同步fifo實現(1)
在這種fifo實現方法中,我們用讀寫計數(或者說讀寫指標)來實現fifo的讀寫。
- 初始讀計數
rd_cnt=0
,寫計數wr_cnt=0
,fifo中資料計數為:data_cnt=wr_cnt-rd_cnt=0
。 - 寫入四個資料,每寫入一個資料時,
ram[wr_cnt]<=din;wr_cnt <= wr_cnt + 1
,所以寫入四個資料後,wr_cnt=4
, fifo中資料計數為:data_cnt=wr_cnt-rd_cnt=4
。 - 假設地址位為
3
位,3
位地址最大能表示深度為8
的fifo,但是在這種實現fifo方法中,fifo容量只能是7
或者7
以下的值。因為wr_cnt
為7
時,如果再寫一個資料,wr_cnt + 1= 3'b111 + 1'b1 = 3'000
,如果此時rd_cnt=0
,則fifo中資料量為wr_cnt-rd_cnt=0
,但實際上我們寫了8
個資料,且沒有讀取1
個資料。這個時候會發生fifo溢位。 - 透過限制fifo 容量為
7
或以下的值,可以防止fifo溢位,比如fifo容量為7
,當ram[6]
被寫以後,wr_cnt=7,rd_cnt=0,data_cnt=wr_cnt-rd_cnt=7
,此時fifo full標誌會被置位,不能繼續寫fifo。 - 接著兩個讀fifo,
rd_cnt=2
,這個時候fifo full又被清零,可以繼續寫fifo,寫入1個資料後, wr_cnt=0, 這個時候fifo中資料data_cnt = wr_cnt-rd_cnt=3'b000-3'b010=3'110=6
,無符號數減法,高位借位。
這種基於計數器的fifo實現方法中,假設fifo地址位AW=n
,則fifo容量為0<CAPACITY<2^n
,下面是實現的verilog程式碼。
檔名稱:code4_40.v
`timescale 1ns/1ps
module SyncFifo_tb;
logic clk = 1'b0;
logic rst_n = 1'b0;
initial begin
$display("start a clock pulse");
$dumpfile("sync_fifo_1.vcd");
$dumpvars(0, SyncFifo_tb);
#300 $finish;
end
always begin
#5 clk = ~clk;
end
logic [7:0] din='0,dout;
logic wr='0,rd='0;
logic [2:0] wc,rc,dc;
logic fu, em;
initial begin
//write 10 data
repeat(10) begin
@ (negedge clk) begin
rst_n = 1'b1;
wr <= 1'b1;
rd <= 1'b0;
din <= 8'($random());
end
end
//重複讀取5次
repeat(5) begin
@(negedge clk) begin
wr <= 1'b0;
rd <= 1'b1;
end
end
//再寫2次
repeat(2) begin
@(negedge clk) begin
wr <= 1'b1;
rd <= 1'b0;
din <= $random();
end
end
//重複讀取4次,讀空
repeat(4) begin
@(negedge clk) begin
wr <= 1'b0;
rd <= 1'b1;
end
end
//空4個週期
repeat(4) begin
@(negedge clk) begin
wr <= 1'b0;
rd <= 1'b0;
end
end
//寫一個,讀一個
forever begin
@(negedge clk) begin
wr <= 1'b1;
rd <= 1'b1;
din <= $random();
end
end
end
ScFifo #(.DW(8),.AW(3),.CAPACITY(7)) the_fifo(.clk(clk),.rst_n(rst_n),.din(din),.write(wr),
.read(rd),.dout(dout),.wr_cnt(wc),.rd_cnt(rc),
.data_cnt(dc),.full(fu),.empty(em));
endmodule
//DW是data width, AW是地址寬度
//CAPACITY是fifo容量,要求CAPACITY>=1 and CAPACITY<=2**AW-1
module ScFifo #(parameter DW=8, AW=10, CAPACITY=2**AW-1) (
input wire clk,
input wire rst_n,
input wire [DW-1:0] din,
input wire write,
input wire read,
output logic [DW-1:0] dout,
output logic [AW-1:0] wr_cnt='0,
output logic [AW-1:0] rd_cnt='0,
output logic [AW-1:0] data_cnt,
output logic full,empty
);
if(CAPACITY>2**AW-1) begin
$error("CAPACITY must be less than 2**AW-1");
end
if(CAPACITY<1) begin
$error("CAPACITY must be greater than 0");
end
logic [DW-1:0] ram[2**AW];
always_ff @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
wr_cnt <= '0;
end
else begin
if(write && !full) wr_cnt <= wr_cnt + 1'b1;
end
end
always_ff @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rd_cnt <= '0;
end
else begin
if(read && !empty) rd_cnt <= rd_cnt + 1'b1;
end
end
assign data_cnt = wr_cnt - rd_cnt;
assign full =(data_cnt==CAPACITY);
assign empty = (data_cnt==0);
always_ff @(posedge clk) begin
if(write && !full) ram[wr_cnt] <= din;
end
always_ff @(posedge clk) begin
if(read && !empty) dout <= ram[rd_cnt];
end
endmodule
在vscode中,使用下面命令編譯,執行程式碼,然後用gtkwave 開啟波形檔案:
iverilog -o myrun -g 2012 -s TestMem code4_40.v
vvp myrun
gtkwave sync_fifo_1.vcd
- 在第2個時鐘上升沿開始寫fifo,輸入10個資料,但在第8時鐘上升沿時,fu訊號已經被置位,所以只寫入7個資料。
- 在第12時鐘上升沿開始,從fifo中讀取5個資料,此時fifo剩下兩個資料。
- 在第17個時鐘上升沿,再寫入兩個資料。
- 在第19個時鐘上升沿,連續讀取4個資料,在第22個時鐘上升沿,fifo為空,em訊號被置位。
- 在第27時鐘上升沿,開始寫一個資料,讀一個資料。
我們嘗試設定fifo容量為8,地址寬度為3,在vscode用iverlog模擬,發現並沒有彈出異常,竟然真的用容量8做了模擬,得到了錯誤的fifo結果。
ScFifo #(.DW(8),.AW(3),.CAPACITY(8)) the_fifo(.clk(clk),.rst_n(rst_n),.din(din),.write(wr),
.read(rd),.dout(dout),.wr_cnt(wc),.rd_cnt(rc),
.data_cnt(dc),.full(fu),.empty(em));
這是因為iverilog並不支援$error,我們可以在modelsim中建立工程(使用Modelsim進行簡單模擬),模擬上面的程式碼,可以得到下面的結果:
vsim -voptargs=+acc work.TestMem
# vsim -voptargs="+acc" work.TestMem
# Start time: 09:05:42 on Jun 14,2024
# ** Note: (vsim-3812) Design is being optimized...
# ** Error: CAPACITY must be less than 2**AW-1
# Scope: TestMem.the_fifo.genblk1 File: D:\xxx\verilog\modelsim\sync_fifo.sv Line: 18
# Optimization failed
# ** Note: (vsim-12126) Error and warning message counts have been restored: Errors=1, Warnings=0.
# Error loading design
# End time: 09:05:43 on Jun 14,2024, Elapsed time: 0:00:01
# Errors: 1, Warnings: 6
基於計數器的同步fifo實現(2)
在前面第一種同步fifo實現中,fifo地址寬度AW是固定的,它決定了fifo的深度。如果我們要實現指定fifo深度,而不需要指定fifo讀寫地址寬度,可以用下面的實現方法。
指定fifo深度
parameter FIFO_DEPTH=16
fifo地址寬度為
logic [$clog2(FIFO_DEPTH)-1:0] wr_addr, rd_addr;
我們用一個變數指定fifo中的資料數目
output logic [$clog2(FIFO_DEPTH):0] fifo_cnt
注意fifo_cnt的位寬是$clog2(FIFO_DEPTH):0
,比如FIFO_DEPTH=16,則是output logic [4:0] fifo_cnt
,如果FIFO_DEPTH=15到9都是output logic [4:0] fifo_cnt
,因為$clog2(n)
函式是向上取整。
這樣,多一位可以保證fifo_cnt
不會溢位。
實現的verilog程式碼檔名稱code4_37.v
`timescale 1ns/1ps
module sync_fifo_tb;
logic clk=0;
logic rst_n;
logic wr_en,rd_en;
logic [7:0] data_in;
logic [7:0] data_out;
logic full,empty;
//fifo cnt 位數是fifo深度$clog2(fifo_depth)+1
//保證fifo_cnt不會溢位(如果fifo_depth不為2的冪次,其實不會溢位,因為$clog2函式向上取整)
logic [3:0] fifo_cnt;
always #5 clk = ~clk;
initial begin
$display("start a clock pulse");
$dumpfile("sync_fifo_2.vcd");
$dumpvars(0, sync_fifo_tb);
#300 $finish;
end
initial begin
rst_n=0;
//在時鐘下降沿改變資料
//重複10次,將會full
repeat(10) begin
@(negedge clk) begin
rst_n <= 1'b1;
wr_en <= 1'b1;
rd_en <= 1'b0;
data_in <= $random();
end
end
//重複讀取6次
repeat(6) begin
@(negedge clk) begin
wr_en <= 1'b0;
rd_en <= 1'b1;
end
end
//再寫2次
repeat(2) begin
@(negedge clk) begin
wr_en <= 1'b1;
rd_en <= 1'b0;
data_in <= $random();
end
end
//重複讀取4次,讀空
repeat(4) begin
@(negedge clk) begin
wr_en <= 1'b0;
rd_en <= 1'b1;
end
end
//空4個週期
repeat(4) begin
@(negedge clk) begin
wr_en<= 1'b0;
rd_en <= 1'b0;
end
end
//寫一個,讀一個
forever begin
@(negedge clk) begin
wr_en <= 1'b1;
rd_en <= 1'b1;
data_in <= $random();
end
end
end
sync_fifo_cnt #(.DATA_WIDTH(8),.FIFO_DEPTH(8)) the_sync_fifo_cnt
(
.clk(clk),
.rst_n(rst_n),
.wr_en(wr_en),
.rd_en(rd_en),
.data_in(data_in),
.data_out(data_out),
.full(full),
.empty(empty),
.fifo_cnt(fifo_cnt)
);
endmodule
module sync_fifo_cnt
#(
parameter DATA_WIDTH=8,
parameter FIFO_DEPTH=16
)
(
input wire clk,
input wire rst_n,
input wire wr_en,
input wire rd_en,
input wire [DATA_WIDTH-1:0] data_in,
output logic [DATA_WIDTH-1:0] data_out,
output logic full,
output logic empty,
output logic [$clog2(FIFO_DEPTH):0] fifo_cnt
);
logic [$clog2(FIFO_DEPTH)-1:0] wr_addr, rd_addr;
logic [DATA_WIDTH-1:0] fifo_buffer[FIFO_DEPTH];
//寫地址時序邏輯
always_ff @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
wr_addr <= '0;
end
else if(!full && wr_en ) begin
//先讀後寫模式,所以寫入的是加1前的wr_addr
wr_addr <= wr_addr + 1'b1;
fifo_buffer[wr_addr] <= data_in;
end
end
//讀時序邏輯
always_ff @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rd_addr <= '0;
end
else if(!empty && rd_en) begin
rd_addr <= rd_addr + 1;
data_out <= fifo_buffer[rd_addr];
end
end
//更新計數器
always_ff @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
fifo_cnt <=0;
end
else begin
case ({wr_en,rd_en})
2'b00: fifo_cnt <= fifo_cnt;
2'b01:
if(fifo_cnt!=0)
fifo_cnt <= fifo_cnt-1'b1;
2'b10:
if(fifo_cnt!=FIFO_DEPTH)
fifo_cnt <= fifo_cnt + 1'b1;
2'b11:
begin
if(empty)
//有寫沒有讀
fifo_cnt <= fifo_cnt + 1'b1;
else if(full)
//有讀沒有寫
fifo_cnt <= fifo_cnt - 1'b1;
else
fifo_cnt <= fifo_cnt;
end
endcase
end
end
assign full = (fifo_cnt == FIFO_DEPTH) ? 1'b1 : 1'b0;
assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0;
endmodule
在vscode中,使用下面命令編譯,執行程式碼,然後用gtkwave 開啟波形檔案:
iverilog -o myrun -g 2012 -s TestMem code4_37.v
vvp myrun
gtkwave sync_fifo_2.vcd
- 第2個時鐘上升沿開始寫fifo
- 第9個時鐘上升沿fifo寫滿,共寫了8個陣列,full訊號置位
- 第12個時鐘上升沿開始,連續讀6個資料
- 第18個時鐘上升沿開始,再寫兩個資料
- 第20個時鐘上升沿開始,讀取4個資料,fifo讀空,訊號empty置位
- 第28個時鐘上升沿開始,寫一個資料,讀一個資料
基於高位擴充套件法的fifo實現
前面兩種同步fifo實現方法中,讀寫地址位數都是$clog2(DEPTH)
,這樣讀寫地址加1時候可能會出現fifo地址溢位的問題,我們可以增加一位,這樣rd_addr/wr_addr都是$clog2(DEPTH)+1
位,我們用最高位msb來作為指示位。
- 讀寫地址高位相同,其它位也相同,則fifo為空。
- 上圖左邊深度為8的FIFO中,寫地址和讀地址都是4位,最高位被用來當作指示位。寫三個資料,讀三個資料,此時寫地址和讀地址,高位和其餘位都相同,所以此時fifo為空,右邊的深度為8的fifo中,則是寫11個資料,讀11個資料,讀寫地址高位都是1,其餘位也相同,顯然此時fifo也為空。
- 讀寫地址高位不同,其它位相同,則fifo為滿。
- 中間深度為8的fifo中,寫11個資料,讀3個資料,寫地址高位為1,讀地址高位為0,其餘位相同,此時fifo中的資料還有8個,顯然fifo為滿。
verilog實現程式碼檔案為:
code4_41.v
`timescale 1ns/1ps
module sync_fifo_tb;
logic clk=0;
logic rst_n;
logic wr_en,rd_en;
logic [7:0] data_in;
logic [7:0] data_out;
logic full,empty;
//fifo cnt 位數是fifo深度$clog2(fifo_depth)+1
//保證fifo_cnt不會溢位(如果fifo_depth不為2的冪次,其實不會溢位,因為$clog2函式向上取整)
logic [3:0] fifo_cnt;
always #5 clk = ~clk;
initial begin
$display("start a clock pulse");
$dumpfile("sync_fifo_3.vcd");
$dumpvars(0, sync_fifo_tb);
#300 $finish;
end
initial begin
rst_n=0;
//在時鐘下降沿改變資料
//重複10次,將會full
repeat(10) begin
@(negedge clk) begin
rst_n <= 1'b1;
wr_en <= 1'b1;
rd_en <= 1'b0;
data_in <= $random();
end
end
//重複讀取6次
repeat(6) begin
@(negedge clk) begin
wr_en <= 1'b0;
rd_en <= 1'b1;
end
end
//再寫2次
repeat(2) begin
@(negedge clk) begin
wr_en <= 1'b1;
rd_en <= 1'b0;
data_in <= $random();
end
end
//重複讀取4次,讀空
repeat(4) begin
@(negedge clk) begin
wr_en <= 1'b0;
rd_en <= 1'b1;
end
end
//空4個週期
repeat(4) begin
@(negedge clk) begin
wr_en<= 1'b0;
rd_en <= 1'b0;
end
end
//寫一個,讀一個
forever begin
@(negedge clk) begin
wr_en <= 1'b1;
rd_en <= 1'b1;
data_in <= $random();
end
end
end
sync_fifo_hibit_ext #(.DATA_WIDTH(8),.FIFO_DEPTH(8)) the_sync_fifo_ext
(
.clk(clk),
.rst_n(rst_n),
.wr_en(wr_en),
.rd_en(rd_en),
.data_in(data_in),
.data_out(data_out),
.full(full),
.empty(empty)
);
endmodule
//高位擴充套件法,讀寫地址增加一位,用高位表示滿或空
module sync_fifo_hibit_ext
#(
parameter DATA_WIDTH=8,
parameter FIFO_DEPTH=16
)
(
input wire clk,
input wire rst_n,
input wire wr_en,
input wire rd_en,
input wire [DATA_WIDTH-1:0] data_in,
output logic [DATA_WIDTH-1:0] data_out,
output logic full,
output logic empty
);
logic [$clog2(FIFO_DEPTH):0] wr_addr, rd_addr;
logic [DATA_WIDTH-1:0] fifo_buffer[FIFO_DEPTH];
//低位地址,和深度匹配
logic [$clog2(FIFO_DEPTH)-1:0] wr_addr_true, rd_addr_true;
logic msb_wr_addr, msb_rd_addr;
//連續賦值得到高位地址和低位地址
assign {msb_wr_addr,wr_addr_true} = wr_addr;
assign {msb_rd_addr,rd_addr_true} = rd_addr;
//寫地址時序邏輯
always_ff @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
wr_addr <= '0;
end
else if(!full && wr_en ) begin
//先讀後寫模式,所以寫入的是加1前的wr_addr
wr_addr <= wr_addr + 1'b1;
fifo_buffer[wr_addr_true] <= data_in;
end
end
//讀時序邏輯
always_ff @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rd_addr <= '0;
end
else if(!empty && rd_en) begin
rd_addr <= rd_addr + 1;
data_out <= fifo_buffer[rd_addr_true];
end
end
//高位相同,且讀地址=寫地址,fifo空
assign empty = (rd_addr == wr_addr) ? 1'b1 : 1'b0;
//高位不同,讀地址等於寫地址,fifo滿
assign full = ((msb_rd_addr!=msb_wr_addr) && (rd_addr_true==wr_addr_true))? 1'b1 : 1'b0;
endmodule
在vscode中,使用下面命令編譯,執行程式碼,然後用gtkwave 開啟波形檔案:
iverilog -o myrun -g 2012 -s TestMem code4_41.v
vvp myrun
gtkwave sync_fifo_3.vcd
我們可以得到和第二種實現方法一樣的波形。