大家好,又见面了,我是你们的朋友全栈君。
本次设计主要介绍异步FIFO中读写指针和格雷码的原理及其实现,最后会有代码和仿真文件
一、异步FIFO的重要参数及其作用
FIFO有几个最重要的参数:
1、FIFO:First Input First Output,即先入先出队列,本质是RAM。
2、wr_clk:写时钟,所有与写有关的操作都是基于写时钟;
3、rd_clk:读时钟,所有与读有关的操作都是基于读时钟;
4、FIFO_WIDTH: FIFO的位宽,即FIFO中每个地址对应的数据的位宽;
5、FIFO_DEPTH: FIFO的深度,即FIFO中能存入多少个(位宽为FIFO_WIDTH的)数据;
6、full:FIFO发出的满信号,当FIFO满了之后,将full拉高;
7、empty:FIFO发出的空信号,当FIFO空了之后,将empty拉高;
8、wr_en:主机发送给FIFO的写使能,一般受制于FIFO发出的full信号,若full信号为高,一般主机会拉低写使能信号,防止新数据覆盖原来的数据;
9、rd_en:主机发送给FIFO的读使能,一般受制于FIFO发出的empty信号,若empty信号为高,一般主机会拉低读使能信号,防止从FIFO中读出不确定的数据。
异步FIFO主要用作跨时钟域的数据缓存。
二、设计要点
异步FIFO设计中,最重要的就是空满判断,格雷码是现在使用最多用于判断空满的一种码制,虽然都知道用格雷码,那为什么要用格雷码?
先说一说空满判断:
空:读快于写时,读指针追上写指针时,写入的数据被读完,读指针和读指针相同,FIFO为空。
满:当写快于读时,当写指针追上读指针,写入的数据比读出的数据多FIFO_DEPTH个,即写指针比读指针大一个FIFO_DEPTH时,此时FIFO为满。
还有就是为什么读写指针要比FIFO_DEPTH多一位,以及格雷码在这之中起到的重要作用,这个在说完格雷码之后会有解释。
格雷码:因1953年公开的弗兰克·格雷专利 “Pulse Code Communication”而得名。
格雷码是一种安全码,因为相邻的格雷码只有一位不同,和二进制不同,二进制一般相邻的都有多位不同。格雷码在传输中,因为相邻只有一位不同,所以其误码率比二进制低得多。
在同步时,出现亚稳态的概率也比二进制低。
二进制转换为格雷码
Verilog代码描述:gray = binary ^ (binary >> 1)
注:以上两幅关于格雷码的图片均不是原创,不知道出处,若有侵权,请联系删除
1、首先是读写指针为什么可以多一位,对读写地址有没有影响
答案是,没有影响
如上图,假如FIFO_DEPTH为8,那么指针就有16个值
普通的三位的地址从000写到111,然后再写入的话,地址又为000,一直写到111,重复上述操作。
因为我们取指针的低三位作为读写地址,如图,可以看出,即使是四位的指针,因为取的低三位,所以也是在000-111中往复循环,不会出现地址溢出的情况。
2、其次是为什么要用格雷码
由于以上原因,指针多一位对读写地址没有影响,而多一位又可以很好地利用格雷码的特性进行空满判断。
如上图,3和4, 5和6, 7和8之间有什么关系呢
可以看出,3的高两位与4的高两位相反,低两位相同;5和6,7和8也有这种关系。
其实格雷码还是一种对称码,比如说3位的格雷码,可以先写出一位的格雷码,0 和 1;然后将这两位对称一下,写出 0 1 1 0,然后在前两个前面填上0,后两个前面添上1,得到两位格雷码 00 01 11 10,然后再对称得到 00 01 11 10 10 11 01 00,再在前四个前添上0,后四个 前面添上1,得到三位的格雷码,000 001 011 010 110 111 101 100.
而3和4之间还有什么关系呢,那就是他们的数值之间相差8,即一个FIFO_DEPTH,所以可以用这个来判断满。
空的判断很简单,格雷码一样就是空。
假设FIFO_DEPTH == 8
空的判断:empty =(wr_gray == rd_gray)?1:0;
满的判断:full = ({~wr_gray[3:2],wr_gray[1:0]} == rd_gray)?1:0;
3、空满信号的同步
因为空信号是对读操作有影响,所以,将空信号在rd_clk下同步(多采用两级寄存器)
因为满信号是对写操作有影响,所以,将满信号在wr_clk下同步(多采用两级寄存器)
三、源代码及仿真
1、源代码
`timescale 1ns / 1ns
module asynchronous_fifo #(
parameter FIFO_WIDTH = 8,
parameter FIFO_DEPTH = 8
)(
input wr_clk, //写时钟
input rd_clk, //读时钟
input wr_en, //写使能
input rd_en, //读使能
input wr_rst_n, //写复位
input rd_rst_n, //读复位
input [FIFO_WIDTH-1:0] wr_data, //写入的数据
output reg [FIFO_WIDTH-1:0] rd_data, //输出的数据
output reg wr_full,
output reg rd_empty,
output wr_fifo_en,
output rd_fifo_en,
output reg [$clog2(FIFO_DEPTH):0] wr_gray_g2, // 写指针
output reg [$clog2(FIFO_DEPTH):0] rd_gray_g2
);
// reg define
reg [$clog2(FIFO_DEPTH):0] wr_pointer = 0; // 写指针
reg [$clog2(FIFO_DEPTH):0] rd_pointer = 0; // 读指针
reg [$clog2(FIFO_DEPTH):0] wr_gray_g1; // 写格雷码,用格雷码来判断空满
reg [$clog2(FIFO_DEPTH):0] rd_gray_g1;
wire [$clog2(FIFO_DEPTH):0] wr_gray; // 写格雷码,用格雷码来判断空满
wire [$clog2(FIFO_DEPTH):0] rd_gray; // 读格雷码,用格雷码来判断空满
wire [$clog2(FIFO_DEPTH)-1:0] wr_addr;
wire [$clog2(FIFO_DEPTH)-1:0] rd_addr;
wire full_1;
wire empty_1;
//ram define
//(*ram_style = "distributed"*) reg [FIFO_WIDTH - 1 : 0] fifo_buffer [FIFO_DEPTH - 1:0]; //寄存器组当FIFO
reg [FIFO_WIDTH - 1 : 0] fifo_buffer [FIFO_DEPTH - 1:0]; //寄存器组当FIFO
assign wr_fifo_en = wr_en && !full_1;
assign rd_fifo_en = rd_en && !empty_1;
assign wr_gray = wr_pointer^(wr_pointer>>1);
assign rd_gray = rd_pointer^(rd_pointer>>1);
assign wr_addr = wr_pointer[$clog2(FIFO_DEPTH)-1:0];
assign rd_addr = rd_pointer[$clog2(FIFO_DEPTH)-1:0];
assign full_1 = ({~rd_gray_g2[$clog2(FIFO_DEPTH):$clog2(FIFO_DEPTH)-1], rd_gray_g2[$clog2(FIFO_DEPTH)-2:0]} == wr_gray)?1:0;
assign empty_1 = (wr_gray_g2 == rd_gray)?1:0;
//将rd_gray在写时钟域与wr_gray比较,如果wr_gray与rd_gray高两位相反,低位相等,则写满
always@(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n) begin
rd_gray_g1 <= 0;
rd_gray_g2 <= 0;
end
else begin
rd_gray_g1 <= rd_gray;
rd_gray_g2 <= rd_gray_g1;
end
end
always@(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n)
wr_full <= 1'b0;
else if(wr_full && wr_en)
wr_full <= 1'b1;
else
wr_full <= full_1;
end
//将wr_gray在读时钟域与rd_gray比较,相等为FIFO空
always@(posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n) begin
wr_gray_g1 <= 0;
wr_gray_g2 <= 0;
end
else begin
wr_gray_g1 <= wr_gray;
wr_gray_g2 <= wr_gray_g1;
end
end
always@(posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n)
rd_empty <= 1'b0;
else if(rd_empty && rd_en)
rd_empty <= 1'b1;
else
rd_empty <= empty_1;
end
// 写数据指针计数
always@(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n) begin
fifo_buffer[wr_addr] <= 'bz;
wr_pointer <= 0;
end
else if(wr_fifo_en) begin
fifo_buffer[wr_addr] <= wr_data;
wr_pointer <= wr_pointer + 1'b1;
end
else begin
fifo_buffer[wr_addr] <= fifo_buffer[wr_addr];
wr_pointer <= wr_pointer;
end
end
// 读数据指针计数
always@(posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n) begin
rd_data <= 'bz;
rd_pointer <= 0;
end
else if(rd_fifo_en) begin
rd_data <= fifo_buffer[rd_addr];
rd_pointer <= rd_pointer + 1'b1;
end
else begin
rd_data <= fifo_buffer[rd_addr];
rd_pointer <= rd_pointer;
end
end
endmodule
2、仿真文件
改变clk_period_wr和clk_period_rd就可以实现读写快慢切换
`timescale 1ns / 1ns
`define clk_period_wr 50
`define clk_period_rd 20
module asynchronous_fifo_tb();
parameter FIFO_WIDTH = 8;
parameter FIFO_DEPTH = 16;
reg wr_clk;
reg rd_clk;
reg wr_en;
reg rd_en;
reg wr_rst_n;
reg rd_rst_n;
reg [FIFO_WIDTH-1:0] wr_data;
wire [FIFO_WIDTH-1:0] rd_data;
wire wr_full;
wire rd_empty;
wire wr_fifo_en;
wire rd_fifo_en;
wire [$clog2(FIFO_DEPTH):0] wr_gray_g2;
wire [$clog2(FIFO_DEPTH):0] rd_gray_g2;
assign wr_fifo_en = wr_en && !wr_full;
assign rd_fifo_en = rd_en && !rd_empty;
initial begin
wr_clk = 0;
forever begin
#(`clk_period_wr/2) wr_clk = ~wr_clk;
end
end
initial begin
rd_clk = 0;
forever begin
#(`clk_period_rd/2) rd_clk = ~rd_clk;
end
end
initial begin
wr_rst_n = 1;
rd_rst_n = 1;
wr_en = 0;
rd_en = 0;
#5;
wr_rst_n = 0;
rd_rst_n = 0;
#5;
wr_rst_n = 1;
rd_rst_n = 1;
end
initial begin
//第一段仿真
@(negedge wr_clk) wr_en = 1;wr_data = $random;
repeat(7) begin
@(negedge wr_clk)
wr_data = $random;
end
@(negedge wr_clk) wr_en = 0;
@(negedge rd_clk) rd_en = 1;
repeat(8) begin
@(negedge rd_clk);
end
@(negedge rd_clk) rd_en = 0;
# 150
//第二段仿真
@(negedge wr_clk)
wr_en = 1;
wr_data = $random;
repeat(18) begin
@(negedge wr_clk)
wr_data = $random;
end
@(negedge wr_clk) wr_en = 0;
@(negedge rd_clk) rd_en = 1;
repeat(18) begin
@(negedge rd_clk);
end
rd_en = 0;
// 第三段仿真
repeat(9) begin
@(negedge wr_clk)
wr_data = $random;wr_en = 1;
end
rd_en = 1;
repeat(19) begin
@(negedge wr_clk)
wr_data = $random;
end
@(negedge wr_clk) wr_en = 0;
@(negedge rd_clk) rd_en = 0;
#20 $finish;
end
asynchronous_fifo #(
.FIFO_WIDTH (FIFO_WIDTH),
.FIFO_DEPTH (FIFO_DEPTH)
)
asynchronous_fifo (
.wr_clk (wr_clk),
.rd_clk (rd_clk),
.wr_rst_n (wr_rst_n),
.rd_rst_n (rd_rst_n),
.wr_en (wr_fifo_en),
.rd_en (rd_fifo_en),
.wr_data (wr_data),
.wr_full (wr_full),
.rd_empty (rd_empty),
.rd_data (rd_data)
);
endmodule
3、仿真截图
读比写快
写比读快
这个代码也还有些问题,希望大家多多包容并且指出错误或者可以改善的地方,一起进步。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/129007.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...