最近学习verilog设计FIFO,记录一下。
一. 设计原理
FIFO( First in First out) 使用在需要产生数据接口的部分,用来存储、缓冲在两个异步时钟之间的数据传输。在异步电路中,由于时钟之间周期和相位完全独立,因此数据丢失概率不为零。使用 FIFO 可以在两个不同时钟域系统之间快速而方便地传输实时数据。这次的设计我们就来学习一下设计一个 8 位 8 深度的 同步FIFO。
FIFO 的原理框图如下图所示:
我们看到图中有一个具有独立的读端口和独立的写端口的 RAM 存储器。这样选择是为了分析方便。如果是一个单端口的存储器,还应包含一个仲裁器,保证同一时刻只能进行一项操作(读或写),我们选择双口 RAM(无需真正的双口 RAM,因为我们只是希望有一个简单的相互独立的读写端口)是因为这些实例非常接近实际情况。
读、写端口拥有两个计数器( wr_ptr、 rd_ptr)产生的互相独立的读、写地址。计数器的值在读写使能信号来临时传递给“ 读指针” ( rd)和“ 写指针” ( wr)。写指针指向下一个将要写入的位置,读指针指向下一个将要读取的位置。每次写操作使写指针加 1,读操作使读指针加1。左右两侧的模块为读写指针与满空信号产生模块。这两个模块的任务是给 FIFO 提供“ 空”( empty)和“ 满” ( full)信号。这些信号告诉外部电路 FIFO 已经达到了临界条件:如果出现“ 满” 信号,那么 FIFO 为写操作的临界状态,如果出现“ 空” 信号,则 FIFO 为读操作的临界状态。写操作的临界状态表示 FIFO 已经没有空间来存储更多的数据,读操作的临界表示 FIFO 没有更多的数据可以读出。读写指针与满空信号产生模块还可告诉 FIFO 中“ 满”或“ 空” 位置的数值。这是由指针的算术运算来完成了。实际的“ 满” 或“ 空” 位置计算并不是为 FIFO 自身提供的。它是作为一个报告机构给外部电路用的。
从功能上看, FIFO 的工作原理如下所述:复位时,读、写指针均为 0。这是 FIFO 的空状态,空标志( empty)为高电平,此时满标志( full)为低电平。当 FIFO 出现空标志( empty)时,不允许读操作,只能允许写操作。写操作写入到位置 0,并使写指针加 1。此时,空标志( empty)变为低电平。假设没有发生读操作且随后的一段时间中 FIFO 只有写操作,一定时间后,写指针的值等于 7。这就意味着在存储器中,要写入数据的最后一个位置就是下一个位置。在这种情况下,写操作将写指针变为 0,并将输出满标志( full)。
为了更好地判断空状态和满状态,这里设置一个四位的计数器( sfifo_cnt),代表存储器( mem)中写入但还未读取的数据个数。当 FIFO 未进行任何读写操作时,计数器保持不变;当进行写操作时,计数器加 1;当进行读操作时,计数器减 1;当同时进行写操作和读操作时,计数器值保持不变。这样就可以根据计数器中的值来判断状态的空与满,即:当计时器 sfifo_cnt=0 时,表示存储器处于空状态,输出空标志( empty);当计数器 sfifo_cnt=8时,表示存储器处于满状态,输出满标志( full)。
二. 设计要求
读写指针都指向一个内存的初始位置,每进行一次读写操作,相应的指针就递增一次,指向下一个内存位置。当指针移动到了内存的最后一个位置时,它又重新跳回初始位置。在FIFO 非满或非空的情况下,这个过程将随着读写控制信号的变化一直进行下去。如果 FIFO处于空的状态,下一个读动作将会导致向下溢(underflow),一个无效的数据被读入;同样,对于一个满了的 FIFO,进行写动作将会导致向上溢出(overflow),一个有用的数据被新写入的数据覆盖。这两种情况都属于误动作,因此需要设置满和空两个信号,对满信号置位表示FIFO 处于满状态,对满信号复位表示 FIFO 非满,还有空间可以写入数据;对空信号置位表示 FIFO 处于空状态,对空信号复位表示 FIFO 非空,还有有效的数据可以读出。设计波形如图所示。
三. 设计流程
3.1 模型
3.1.1 模块设计
3.1.2 引脚说明
3.1.3 算法设计
已添加注释:
module sfifo(
clk,
rst_n,
data_in,
wr,
rd,
full,
empty,
data_out,
sfifo_cnt);
input clk;
input rst_n;
input [7:0] data_in;
input wr;
input rd;
output full;
output empty;
output [7:0] data_out;
output [3:0] sfifo_cnt;
wire clk;
wire rst_n;
wire [7:0] data_in;
wire wr;
wire rd;
wire full;
wire empty;
reg [7:0] data_out;
reg [3:0] sfifo_cnt;
`define DEL 1 // Clock-to-output delay
reg [7:0] sfifo_ram[0:7]; // sfifo_ram initialized
reg [2:0] rd_ptr; // Read pointer
reg [2:0] wr_ptr; // Write pointer
assign empty = ( sfifo_cnt == 0 ) ? 1 : 0; //Empty signal
assign full = ( sfifo_cnt == 8 ) ? 1 : 0; //Full signal
// sfifo_cnt changed
// While rd is valid, cnt--
// While wr is valid, cnt++
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
sfifo_cnt <= #`DEL 4'h0;
end
else if( rd && ~wr ) begin
sfifo_cnt <= #`DEL sfifo_cnt - 1;
end
else if( ~rd && wr ) begin
sfifo_cnt <= #`DEL sfifo_cnt + 1;
end
else begin
sfifo_cnt <= sfifo_cnt;
end
end
/*
//
// The change of sfifo_cnt can also be written like below.
//
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
sfifo_cnt <= #`DEL 4'h0;
end
else begin
case({ wr,rd })
2'b00 : sfifo_cnt <= #`DEL sfifo_cnt;
2'b01 : sfifo_cnt <= #`DEL (sfifo_cnt==0) ? 0 : fifo_cnt-1;
2'b10 : sfifo_cnt <= #`DEL (sfifo_cnt==8) ? 8 : fifo_cnt+1;
2'b11 : sfifo_cnt<=sfifo_cnt;
default: sfifo_cnt <= sfifo_cnt;
endcase
end
end
*/
// Increment of rd_ptr
// Check if the read pointer has gone beyond the depth of fifo
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
rd_ptr <= #`DEL 3'h0;
end
else if( rd ) begin
if( rd_ptr == 3'h7 ) begin
rd_ptr <= #`DEL 3'h0;
end
else begin
rd_ptr <= #`DEL rd_ptr +1;
end
end
else begin
rd_ptr <= rd_ptr;
end
end
// Increment of wr_ptr
// Check if the write pointer has gone beyond the depth of fifo
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
wr_ptr <= #`DEL 3'h0;
end
else if( wr ) begin
if( wr_ptr == 3'h7 ) begin
wr_ptr <= #`DEL 3'h0;
end
else begin
wr_ptr <= #`DEL wr_ptr +1;
end
end
else begin
wr_ptr <= wr_ptr;
end
end
/*
//
// The incerment of pointer can also be written like below.
//
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
wr_ptr <= #`DEL 3'h0;
rd_ptr <= #`DEL 3'h0;
end
else begin
wr_ptr <= #`DEL wr ? wr_ptr + 1 : wr_ptr;
rd_ptr <= #`DEL rd ? rd_ptr + 1 : rd_ptr;
end
end
*/
//
// Deal with the data
//
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
data_out <= #`DEL 8'h0;
end
else if( wr ) begin
sfifo_ram[wr_ptr] <= #`DEL data_in;
end
else if( rd ) begin
data_out <= #`DEL sfifo_ram[rd_ptr];
end
end
endmodule //sfifo
3.2 测试模块
3.2.1 模块设计
3.2.2 引脚说明
3.1.3 激励输入
这里测试代码写的比较简单,下一篇文章有详细的设计代码。
module sfifo_test();
reg clk;
reg rst_n;
reg [7:0] data_in;
reg wr;
reg rd;
wire full;
wire empty;
wire [7:0] data_out;
wire [3:0] sfifo_cnt;
initial begin
rst_n=1;
clk=0;
wr=0;
rd=0;
data_in=0;
#1 rst_n=0;
#5 rst_n=1;
#3 wr=1;
#5 rd=1;
#5 rd=0;
#5 wr=0;
#5 wr=1;
#10 rd=1;
#10 rd=0;
#14 $finish;
end
always begin
#5 clk=~clk;
end
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
data_in<=0;
wr<=0;
rd<=0;
end
else begin
data_in<=$random;
end
end
initial begin
$dumpfile ("F:/Robei/practice/prac_07_sfifo/sfifo_test.vcd");
$dumpvars;
end
//---Module instantiation---
sfifo sfifo1(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.wr(wr),
.rd(rd),
.full(full),
.empty(empty),
.data_out(data_out),
.sfifo_cnt(sfifo_cnt));
endmodule //sfifo_test
3.2.4 仿真查看波形
以上就实现了8位同步fifo的设计~