异步FIFO Verilog HDL设计
一.功能需求分析及模块划分
1.1 功能需求分析
基本原理概述:FIFO (First In First Out)即是先进先出之意,其本质是特殊的RAM存储器,特殊于不能随意指定地址写入或者读出,只能够按自底向上写入或读出。
优点:
1、可以较好的解决多通道数据信号跨时钟域同步问题(单通道数据一般打两拍就可以同步成功,成功率可达90%,基本不会产生亚稳态,关于亚稳态问题可以留意我的另一篇文章,单独刨析亚稳态的产生原理及其解决方案)。
2、对于片内或者片间数据位数转换适用性较强(比如32位转64位)。
3、控制简单,稳定性尚可。
缺点:
读取地址指针指向自由度低,不能指定存储和读取,必须按其规则进行存储和读取操作。
FIFO设计几个关键点
1、写指针:写指针在复位(初试状态)时指向地址0,它总是指向下一个将要写入数据的地址,自下至上。
2、读指针:同样,读指针在复位(初试状态)时指向地址0,它总是指向下一个要读出的数据的地址,自下至上读取,如下图所示;在同一回合中,读指针不可越过写指针读取数据,否则读出的是无效数据,而且会引起FIFO状态紊乱。
3、FIFO满:假设FIFO深度为16,当(写指针-读指针)>= 15,即是相差一个深度,表示FIFO满,注意读指针指向的当前位置是还未读取的,最简单的情况就是读指针0000,写指针1111。
为了更好的表达满状态,通常给FIFO最高位加一个MSB状态位,那么最简单的情况为读指针00000,写指针10000,除最高位其余位相同;除了上述简单满情况,另一种情况就是写指针转完第一轮,在第二轮又赶上读指针,同样MSB不同,其余位相同;可以总结为:当任意深度 FIFO MSB位不同,其他位相同时为满。
4、FIFO空:基于上述添加MSB位的FIFO,FIFO空时的MSB位以及其余位都相同,即全等。
5、格雷码空满判断:由于异步FIFO是多位数据的跨时钟域处理,就本16位FIFO而言,存在多位或所有位同时跳变的情况,容易产生亚稳态,将二进制指针转换成格雷码的好处是每次临近跳变只有一位变化,不容易产生亚稳态。
16位带MSB位FIFO二进制转换成格雷码如下图所示,格雷码空状态的判断方式和二进制一样,全等情况就是空状态;但是满状态格雷码与二进制有异同之处,如下图所示;应满足读写指针的MSB位(最高位)和次高位不同,其余位置相同。
6、打两拍:为了防止亚稳态,将异步输入数据打两拍稳定下来的概率可达90%,目的是让信号满足后续寄存器的建立时间。
7、二进制转格雷码:二进制转格雷码只需与本二进制向右移一位然后按位异或操作即可。如:gray_w = (addr_w ^ (addr_w>>1));
1.2 模块划分
地址控制模块、存储模块、顶层模块;
二.代码实例
2.1 顶层模块
module fifo1(
input clk_w,
input clk_r,
input rst_n,
input [7:0] data_in,
input wr_en,
input rd_en,
input cs_n,
output wire fifo_empty,
output wire fifo_full,
output [7:0] data_out
);
//中间变量定义
wire [7:0] addr_w;
wire [7:0] addr_r;
reg [7:0] data_in0;
reg [7:0] data_in1;
reg [7:0] data_in2;
reg wr_en0;
reg wr_en1;
reg wr_en2;
reg rd_en0;
reg rd_en1;
reg rd_en2;
//打两拍再加一拍
initial begin
data_in0 <= 0;
data_in1 <= 0;
wr_en0 <= 0;
wr_en1 <= 0;
rd_en0 <= 0;
rd_en1 <= 0;
end
always@( posedge clk_w ) begin
data_in0 <= data_in;
wr_en0 <= wr_en;
data_in1 <= data_in0;
wr_en1 <= wr_en0;
data_in2 <= data_in1;
wr_en2 <= wr_en1;
end
always@( posedge clk_r ) begin
rd_en0 <= rd_en;
rd_en1 <= rd_en0;
rd_en2 <= rd_en1;
end
//模块interger
addr_strl1 addr_strl1(
.clk_w(clk_w),
.clk_r(clk_r),
.rst_n(rst_n),
.rd_en(rd_en2),
.wr_en(wr_en2),
.cs_n(cs_n),
.addr_w(addr_w),
.addr_r(addr_r),
.fifo_empty(fifo_empty),
.fifo_full(fifo_full)
);
ram_fifo1 ram_fifo1(
.clk_w(clk_w),
.clk_r(clk_r),
.rst_n(rst_n),
.data_in(data_in2),
.wr_en(wr_en2),
.rd_en(rd_en2),
.addr_w(addr_w[7:0]),
.addr_r(addr_r[7:0]),
.fifo_empty(fifo_empty),
.fifo_full(fifo_full),
.data_out(data_out)
);
endmodule
2.2 地址控制模块
module addr_strl1(
input clk_w,
input clk_r,
input rst_n,
input rd_en,
input wr_en,
input cs_n,
output reg [8:0] addr_w,
output reg [8:0] addr_r,
output fifo_empty,
output fifo_full
);
wire [8:0] gray_w;
wire [8:0] gray_r;
//地址生成
always@( posedge clk_w or negedge rst_n ) begin
if( !rst_n )
addr_w <= 9'b0;
else if( wr_en && cs_n )
if( addr_w == 9'd257 )
addr_w <= 9'b0;
else
addr_w <= addr_w + 9'b1;
else
addr_w <= addr_w;
end
always@( posedge clk_r or negedge rst_n ) begin
if( !rst_n )
addr_r <= 9'b0;
else if( rd_en && cs_n )
if( addr_r == 9'd257 )
addr_r <= 9'b0;
else
addr_r <= addr_r + 9'b1;
else
addr_r <= addr_r;
end
//跨时钟域数据对比,格雷码转换,防止亚稳态
assign gray_w = (addr_w ^ (addr_w>>1));
assign gray_r = (addr_r ^ (addr_r>>1));
//空/满逻辑判断
assign fifo_full = ( (gray_w[8] != gray_r[8]) && (gray_w[7] != gray_r[7]) && (gray_w[6:0] == gray_r[6:0]) )?1'b1:1'b0;
assign fifo_empty = ( gray_w == gray_r )?1'b1:1'b0;
endmodule
2.3 RAM存储模块
module ram_fifo1(
input clk_w,
input clk_r,
input rst_n,
input [7:0] data_in,
input wr_en,
input rd_en,
input [7:0] addr_w,
input [7:0] addr_r,
input fifo_empty,
input fifo_full,
output reg [7:0] data_out
);
//ram存储器配置,宽度8,深度256
reg [7:0] ram[255:0];
//读逻辑设计
always@( posedge clk_w ) begin
if( wr_en && !fifo_full && !rd_en )
ram[addr_w] <= data_in;
else if( wr_en && fifo_empty && rd_en )
ram[addr_w] <= data_in;
else if( wr_en && !fifo_empty && !fifo_full && rd_en ) begin
ram[addr_w] <= data_in;
end
end
always@( posedge clk_r or negedge rst_n ) begin //功能覆盖还不够,要加,比如空了还会继续读。
if( !rst_n ) begin
data_out <= 0;
end
else if( rd_en && !fifo_empty && !wr_en )
data_out <= ram[addr_r];
else if( rd_en && fifo_full && wr_en )
data_out <= ram[addr_r];
else if( wr_en && !fifo_empty && !fifo_full && rd_en )
data_out <= ram[addr_r];
end
//写逻辑设计
endmodule
三.功能仿真
3.1 基础仿真代码
此处考虑到篇幅问题,对上述代码的代码覆盖率和功能覆盖率皆不全面,对性能要求严苛的同学欢迎交流学习。
`timescale 1ns/1ns
`define clock_period 20
module fifo1_tb();
reg clk_w;
reg clk_r;
reg rst_n;
reg [7:0] data_in;
reg wr_en;
reg rd_en;
reg cs_n;
wire fifo_empty;
wire fifo_full;
wire [7:0] data_out;
fifo1 fifo1(
.clk_w(clk_w),
.clk_r(clk_r),
.rst_n(rst_n),
.data_in(data_in),
.wr_en(wr_en),
.rd_en(rd_en),
.cs_n(cs_n),
.fifo_empty(fifo_empty),
.fifo_full(fifo_full),
.data_out(data_out)
);
initial begin
clk_w = 0;
clk_r = 0;
data_in = 0;
wr_en = 0;
rd_en = 0;
cs_n = 0;
end
always#( `clock_period/2 ) clk_w = ~clk_w;
always#( `clock_period/4 ) clk_r = ~clk_r;
initial begin
rst_n = 0;
#( `clock_period*10 );
rst_n = 1;
#( `clock_period*10 + 1'b1 );
cs_n = 1;
wr_en = 1;
repeat(255)
begin
#( `clock_period );data_in = data_in + 8'b1;
end
#( `clock_period );
wr_en = 0;
#( `clock_period*10 + 1'b1 );
rd_en = 1;
@(posedge fifo_empty);
cs_n = 0;
rd_en = 0;
#2000;
$stop;
end
endmodule
3.2 基础功能仿真通过
因作者才疏学浅,本文有误之处劳烦同学们批评指正。