一、FIFO原理与设计
查看Xilinx官方FIFO IP核,其主要的信号有时钟信号、写端口信号、读端口信号,其中,写端口信号包括写满信号full、写使能信号wr_en、写数据输入din、几乎满信号almost_full;读端口信号包括读空信号empty、读使能信号rd_en、读数据输出dout、几乎空信号almost_empty。几乎满信号almost_full与几乎空信号almost_empty是可选的。
根据Xilinx官方的FIFO IP核,可以仿照写一个简单的同步FIFO(读写在同一时钟域)。在这里我们设计一个宽度为DATA_WIDTH,深度为DATA_DEPTH的同步FIFO,其输入输出端口如下:
-
输入端口
clk:时钟信号
rst_n:异步复位信号
wr_en:写使能信号,在wr_en为高电平,且满信号full为低电平期间,数据写入FIFO;
rd_en:读使能信号,在rd_en高电平,且空信号empty为低电平期间,从FIFO中读出数据;
din:写数据输入,位宽为DATA_WIDTH; -
输出端口
dout:读数据输出,位宽为DATA_WIDTH;
full:写满信号,当FIFO中存储的数据个数达到DATA_DEPTH时,full为高电平,表示FIFO写满了,不再向FIFO写入数据(即使写使能wr_en为高电平);当FIFO中存储的数据个数小于DATA_DEPTH时,full为低电平,表示可以继续往FIFO中写入数据;
empty:读空信号,当FIFO中存储的数据个数为0时,empty为高电平,表示FIFO中已无数据,不再从FIFO中读出数据(即使读使能rd_en为高电平);当FIFO中存储的数据个数大于0时,empty为低电平,表示FIFO中还有数据,可以继续从FIFO中读出数据;
那么,FIFO本质还是一个存储器,在这里使用一个宽度为DATA_WIDTH,深度为DATA_DEPTH的SRAM实现存储,使用写指针地址wr_ptr控制写地址,每进行一次写操作,写指针地址自加1,使用读指针地址rd_ptr控制读地址,每进行一次读操作,读指针地址自加1。在写使能信号为高电平,FIFO未写满(full为低电平)时,执行写操作;在读使能信号为高电平,FIFO未读空(empty为低电平)时,执行读操作。
//===========write pointer address===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_ptr <= 'd0;
else if(wr_en && !full)
wr_ptr <= wr_ptr + 1'b1;
else
wr_ptr <= wr_ptr;
end
//===========read pointer address===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
rd_ptr <= 'd0;
else if(rd_en && !empty)
rd_ptr <= rd_ptr + 1'b1;
else
rd_ptr <= rd_ptr;
end
那么,用一个计数器cnt来计算当前FIFO中存储的数据。计数器的控制分为四种情况:
(1)单时钟只执行写操作:当FIFO只进行写操作,而不执行读操作,即写使能wr_en为高电平,写满信号full为低电平,读使能rd_en为低电平时,计数器cnt自加1;
(2)单时钟只执行读操作:当FIFO只进行读操作,而不进行写操作,即读使能rd_en为高电平,读空信号empty为低电平,写使能wr_en为低电平时,计数器自加1;
(3)单时钟同时执行写操作与地操作:当FIFO同时执行写操作与读操作,即写使能wr_en为高电平,写满信号full为低电平,读使能rd_en为高电平,读空信号empty为低电平时,计数器cnt不变;
(4)单时钟既不执行写操作也不执行读操作:当FIFO既不执行写操作也不执行读操作时,计数器cnt不变;
//===========counter===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 'd0;
else if(wr_en && !full && !rd_en)
cnt <= cnt + 1'b1;
else if(rd_en && !empty && !wr_en)
cnt <= cnt - 1'b1;
else
cnt <= cnt;
end
另外,用FIFO中存储的数据计数器cnt来判断写满信号full、读空信号empty、几乎满信号almost_full、几乎空信号almost_empty。当计数器cnt为0时,表示FIFO中数据为空,读空信号empty为高电平;当计数器cnt为1时,表示FIFO中数据几乎空,几乎空信号almost_empty为高电平;当计数器为DATA_DEPTH-1时,表示FIFO中的存储的数据个数几乎为满,则几乎满信号almost_full信号为高电平;当计数器为DATA_DEPTH时,表示FIFO中存储的数据个数满了,则写满信号full为高电平。
//===========full===========//
assign full = (cnt == DATA_DEPTH) ? 1'b1 : 1'b0;
//===========empty===========//
assign empty = (cnt == 'd0) ? 1'b1 : 1'b0;
//===========almost_full===========//
assign almost_full = (cnt == DATA_DEPTH-1) ? 1'b1 : 1'b0;
//===========almost_empty===========//
assign almost_empty = (cnt == 'd1) ? 1'b1 : 1'b0;
最后,根据读操作以及读地址,从FIFO中读出数据;根据写操作及写地址,将数据写入FIFO。
//===========read data===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dout_r <= 'd0;
else if(rd_en && !empty)
dout_r <= mem[rd_ptr];
end
assign dout = dout_r;
//===========write data===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
for(i=0;i<DATA_DEPTH;i=i+1) begin
mem[i] <= 'd0;
end
else if(wr_en && !full)
mem[wr_ptr] <= din;
end
二、完整代码与仿真结果
同步FIFO完整代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/05/16 09:23:07
// Design Name:
// Module Name: sync_fifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 同步FIFO
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module sync_fifo
#(
parameter DATA_WIDTH = 8,
parameter DATA_DEPTH = 8,
parameter CNT_WIDTH = $clog2(DATA_DEPTH)
)
(
input clk ,
input rst_n ,
input wr_en ,
input [DATA_WIDTH-1:0] din ,
input rd_en ,
output [DATA_WIDTH-1:0] dout ,
output almost_full ,
output full ,
output almost_empty,
output empty
);
integer i;
reg [DATA_WIDTH-1:0] mem [DATA_DEPTH-1:0];
reg [CNT_WIDTH:0] cnt;
reg [CNT_WIDTH-1:0] wr_ptr,rd_ptr;
reg [DATA_WIDTH-1:0] dout_r;
//===========write pointer address===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_ptr <= 'd0;
else if(wr_en && !full)
wr_ptr <= wr_ptr + 1'b1;
else
wr_ptr <= wr_ptr;
end
//===========read pointer address===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
rd_ptr <= 'd0;
else if(rd_en && !empty)
rd_ptr <= rd_ptr + 1'b1;
else
rd_ptr <= rd_ptr;
end
//===========counter===========//
//原版
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 'd0;
else if(wr_en && !full && !rd_en)
cnt <= cnt + 1'b1;
else if(rd_en && !empty && !wr_en)
cnt <= cnt - 1'b1;
else
cnt <= cnt;
end
/*
//修改版
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 'd0;
else if(wr_en && !full && !rd_en) //写使能wr_en有效,读使能rd_en无效
cnt <= cnt + 1'b1;
else if(wr_en && !full && rd_en && empty) //rd_en为高电平,但是empty为高电平,所以实际上读使能rd_en无效
cnt <= cnt + 1'b1;
else if(rd_en && !empty && !wr_en) //读使能rd_en有效,写使能wr_en无效
cnt <= cnt - 1'b1;
else if(rd_en && !empty && wr_en && full) //wr_en为高电平,但是full为高电平,所以实际上写使能wr_en无效
cnt <= cnt - 1'b1;
else
cnt <= cnt;
end
*/
//===========full===========//
assign full = (cnt == DATA_DEPTH) ? 1'b1 : 1'b0;
//===========empty===========//
assign empty = (cnt == 'd0) ? 1'b1 : 1'b0;
//===========almost_full===========//
assign almost_full = (cnt >= DATA_DEPTH-1) ? 1'b1 : 1'b0;
//===========almost_empty===========//
assign almost_empty = (cnt <= 'd1) ? 1'b1 : 1'b0;
//===========read data===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dout_r <= 'd0;
else if(rd_en && !empty)
dout_r <= mem[rd_ptr];
end
assign dout = dout_r;
//===========write data===========//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
for(i=0;i<DATA_DEPTH;i=i+1) begin
mem[i] <= 'd0;
end
else if(wr_en && !full)
mem[wr_ptr] <= din;
end
endmodule
仿真文件如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/05/16 11:22:57
// Design Name:
// Module Name: tb_sync_fifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_sync_fifo;
parameter DATA_WIDTH = 8;
parameter DATA_DEPTH = 16;
parameter CNT_WIDTH = $clog2(DATA_DEPTH);
reg clk,rst_n;
reg wr_en ;
reg [DATA_WIDTH-1:0] din ;
reg rd_en ;
wire [DATA_WIDTH-1:0] dout ;
wire full ;
wire empty ;
wire almost_full ;
wire almost_empty;
parameter clk_period = 10;
initial
begin
clk = 1'b1;
rst_n <= 1'b1;
rd_en <= 1'b0;
wr_en <= 1'b0;
din <= 'd0;
#(clk_period*10)
rst_n <= 1'b0;
#(clk_period*1)
rst_n <= 1'b1;
repeat(50) begin
@(posedge clk) begin
wr_en <= {$random}%2;
rd_en <= {$random}%2;
din <= din + 1'b1;
end
end
#(clk_period)
#(clk_period*10)
$stop;
end
always #(clk_period/2) clk = ~clk;
sync_fifo
#(
.DATA_WIDTH(DATA_WIDTH),
.DATA_DEPTH(DATA_DEPTH)
)sync_fifo
(
.clk (clk ),
.rst_n (rst_n ),
.wr_en (wr_en ),
.din (din ),
.rd_en (rd_en ),
.dout (dout ),
.almost_full (almost_full ),
.full (full ),
.almost_empty(almost_empty),
.empty (empty )
);
endmodule
三、仿真结果
对同步FIFO进行仿真,可以看到,初始化复位下,空信号empty与几乎空信号almost_empty均为高电平,在成功写入数据,FIFO中数据个数大于0时,empty信号被拉低;当FIFO中存储数据大于1时,几乎空信号almost_empty被拉低。
在这里仍然存在问题:修改仿真文件,使得读使能早于写使能,可以看到数据成功写入FIFO,因为写使能wr_en有效取决于满信号full为低电平,而数据并没有成功从FIFO中读出,因为读使能rd_en有效取决于空信号empty为低电平,而在FIFO成功写入数据后,空信号并没有拉低,从而导致读数据失败,但空信号empty取决于FIFO中的数据计数器,计数器由读使能与写使能同时控制,由于写使能与读使能同为高,所以计数器并没有加1,从而导致空信号没有拉低。
于是,找到问题的关键所在,把同步FIFO设计中的计数器cnt部分原版修改为修改版,增加了两种情况:写使能wr_en有效读使能rd_en无效、读使能rd_en有效写使能wr_en无效,重新仿真,可以看到结果正确。文章来源:https://www.toymoban.com/news/detail-460004.html
文章来源地址https://www.toymoban.com/news/detail-460004.html
到了这里,关于手撕代码——同步FIFO的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!