旨在学习理解,项目中还是用成熟IP靠谱~
目录
一、FIFO原理
二、同步FIFO设计
2.1 位宽和深度
2.2 空、满标志
2.3 FIFO计数
2.4 ram模型
2.5 读/写操作
三、verilog代码
四、仿真验证
后记
一、FIFO原理
FIFO( First Input First Output)是指先进先出。模型如下:
FIFO存储器是系统的缓冲环节,如果没有FIFO存储器,整个系统就不可能正常工作,它主要有几方面的功能:
1)对连续的数据流进行缓存,防止在进机和存储操作时丢失数据;
2)数据集中起来进行进机和存储,可避免频繁的总线操作,减轻CPU的负担;
3)允许系统进行DMA操作,提高数据的传输速度。这是至关重要的一点,如果不采用DMA操作,数据传输将达不到传输要求,而且大大增加CPU的负担,无法同时完成数据的存储工作。
根据FIFO工作的时钟域分为同步/异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟在时钟沿来临时同时发生读写。异步FIFO读写时钟不一致,读写相互独立。所以存在跨时钟域的处理。
本文先考虑同步FIFO设计。
为了定位读取和写入的位置需要进行读写指针的设置。
读指针:总是指向下一个将要读取的单元,复位时指向第一个单元(编号为0)。
写指针:总是指向当前要被读出的数据,复位时指向第一个单元(编号为0)。
而FIFO设计中最重要的则是空(Empty)、满(Full)信号的判断。
当第一次读写指针相等时,表明FIFO为空,这种情况发生在复位操作时或者当读指针读出FIFO中最后一个字 后,追赶上写指针时,此时读空信号有效。
当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈折回来(wrapped around)又追上了读指针。
两种方式都是以读写指针相等作为判断标志,所以我们需要寻找其它的方法进行判断。
在同步FIFO中,我们可以很容易的使用计数来判断FIFO中还剩下多少可读的数据,从而可以判断空、满。
下面我们开始按步骤进行同步FIFO设计。
二、同步FIFO设计
2.1 位宽和深度
位宽 WIDTH:表示FIFO里面每个数据的大小
深度 DEPTH:表示FIFO能存多少个数据
本文全部采用参数化设计,另外将空、满也设计为参数化:
parameter WIDTH = 16;
parameter DEPTH = 1024;
parameter PROG_EMPTY = 100;
parameter PROG_FULL = 800;
2.2空、满标志
- 空信号empty
下一次读地址 = 写地址,且只执行读操作;
- 满信号full
下一次写地址 = 读地址,且只执行写操作;
2.3FIFO计数
fifo_cnt : 只写不读,fifo_cnt ++; 只读不写,fifo_cnt --;其余情况保持不变。
2.4ram模型
verilog 2001标准支持多维数组,所有直接使用二维数组来定义memory数据类型:
reg [WIDTH-1 : 0] ram [DEPTH-1 : 0];
2.5读/写操作
- 读操作 : 读使能 && 非空,才能成功完成一次读操作:读地址+1,且从ram 中读出数据,给到输出。
- 写操作 : 写使能 && 非满,才能成功完成一次写操作:写地址+1,且将输入数据写入ram。
三、verilog代码
module syn_fifo#(
parameter WIDTH = 16,
parameter DEPTH = 1024,
parameter ADDR_WIDTH = clogb2(DEPTH),
parameter PROG_EMPTY = 100,
parameter PROG_FULL = 800
)(
input sys_clk,
input sys_rst,
input [WIDTH-1:0]din,
input wr_en,
input rd_en,
output reg [WIDTH-1:0]dout,
output reg full,
output reg empty,
output reg prog_full,
output reg prog_empty,
output reg [ADDR_WIDTH-1:0]fifo_cnt
);
//==========================================================\
//========== Define Parameter and Internal signals =========
//==========================================================/
reg [WIDTH-1:0] ram[DEPTH-1:0];
reg [ADDR_WIDTH-1:0] wr_addr;
reg [ADDR_WIDTH-1:0] rd_addr;
//==========================================================\
//===================== Main Code =========================
//==========================================================/
function integer clogb2;
input[31:0]value;
begin
value=value-1;
for(clogb2=0;value>0;clogb2=clogb2+1)
value=value>>1;
end
endfunction
//read
always@(posedge sys_clk or posedge sys_rst)begin
if(sys_rst)
rd_addr <= {ADDR_WIDTH{1'b0}};
else if(rd_en && !empty)begin
rd_addr <= rd_addr+1'd1;
dout <= ram[rd_addr];
end
else begin
rd_addr <= rd_addr;
dout <= dout;
end
end
//write
always@(posedge sys_clk or posedge sys_rst)begin
if(sys_rst)
wr_addr <= {ADDR_WIDTH{1'b0}};
else if(wr_en && !full) begin
wr_addr <= wr_addr+1'd1;
ram[wr_addr]<= din;
end
else
wr_addr <= wr_addr;
end
//fifo_cnt
always@(posedge sys_clk or posedge sys_rst)begin
if(sys_rst)
fifo_cnt <= {ADDR_WIDTH{1'b0}};
else if(wr_en && !full && !rd_en)
fifo_cnt <= fifo_cnt + 1'd1;
else if(rd_en && !empty && !wr_en)
fifo_cnt <= fifo_cnt - 1'd1;
else
fifo_cnt <= fifo_cnt;
end
//empty
always@(posedge sys_clk or posedge sys_rst)begin
if(sys_rst)
empty <= 1'b1;//reset:1
else
empty <= (!wr_en && (fifo_cnt[ADDR_WIDTH-1:1] == 'b0))&&((fifo_cnt[0] == 1'b0) || rd_en);
end
//full
always@(posedge sys_clk or posedge sys_rst)begin
if(sys_rst)
full <= 1'b1;//reset:1
else
full <= (!rd_en && (fifo_cnt[ADDR_WIDTH-1:1]=={(ADDR_WIDTH-1){1'b1}})) && ((fifo_cnt[0] == 1'b1) || wr_en);
end
//prog_full
always@(posedge sys_clk or posedge sys_rst)begin
if(sys_rst)
prog_full<= 1'b1;//reset:1
else if(fifo_cnt > PROG_FULL)
prog_full<= 1'b1;
else
prog_full<= 1'b0;
end
//prog_empty
always@(posedge sys_clk or posedge sys_rst)begin
if(sys_rst)
prog_empty<=1'b1;//reset:1
else if(fifo_cnt < PROG_EMPTY)
prog_empty<=1'b1;
else
prog_empty<=1'b0;
end
endmodule
四、仿真验证
对上面设计的FIFO进行写入/读出操作。
为了验证程序健壮性,测试会进行写溢出和读空。
test bench
`timescale 1ns/1ns
module tb;
parameter WIDTH = 16 ;
parameter DEPTH = 512 ;
parameter ADDR_WIDTH = 9 ;
parameter PROG_EMPTY = 100 ;
parameter PROG_FULL = 400 ;
reg sys_clk ;
reg sys_rst ;
reg [WIDTH-1:0] din ;
reg wr_en ;
reg rd_en ;
reg [11:0] cnt ;
wire [WIDTH-1:0] dout ;
wire full ;
wire empty ;
wire prog_full ;
wire prog_empty ;
wire [ADDR_WIDTH-1:0]fifo_cnt ;
initial begin
sys_clk = 0;
sys_rst = 1;
#100
sys_rst = 0;
end
always #5 sys_clk = ~sys_clk;
always @ (posedge sys_clk or posedge sys_rst)begin
if(sys_rst)
cnt <= 'b0;
else
cnt <= cnt + 1'd1;
end
always @ (posedge sys_clk or posedge sys_rst)begin
if(sys_rst)begin
wr_en <= 1'b0;
rd_en <= 1'b0;
din <= 16'b0;
end
else if(cnt >= 'd10 && cnt <= DEPTH + 'd40)begin
wr_en <= 1'b1;
rd_en <= 1'b0;
din <= din + 1'd1;
end
else if((cnt >= 'd100 + DEPTH) && (cnt <= 2*DEPTH + 'd140))begin
wr_en <= 1'b0;
rd_en <= 1'b1;
din <= 'b0;
end
else begin
wr_en <= 1'b0;
rd_en <= 1'b0;
din <= 'b0;
end
end
syn_fifo#(
.WIDTH ( WIDTH ),
.DEPTH ( DEPTH ),
.ADDR_WIDTH ( ADDR_WIDTH ),
.PROG_EMPTY ( PROG_EMPTY ),
.PROG_FULL ( PROG_FULL )
)u_syn_fifo(
.sys_clk ( sys_clk ),
.sys_rst ( sys_rst ),
.din ( din ),
.wr_en ( wr_en ),
.rd_en ( rd_en ),
.dout ( dout ),
.full ( full ),
.empty ( empty ),
.prog_full ( prog_full ),
.prog_empty ( prog_empty ),
.fifo_cnt ( fifo_cnt )
);
endmodule
仿真结果:
写入:
写满:
读出:
读空:
验证成功!
后记
问:文章来源:https://www.toymoban.com/news/detail-454370.html
- 如何实现FWFT模式? 即读数据时不寄存输出,读使能有效的同一时钟周期,FIFO数据即出现在输出数据总线上。
- 异步FIFO如何实现?
咱们下期见。文章来源地址https://www.toymoban.com/news/detail-454370.html
到了这里,关于【Verilog】同步FIFO原理及verilog实现(参数化)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!