怎么用Verilog语言描述同步FIFO和异步FIFO

时间:2023-03-08 19:39:21

感谢

知乎龚大佬

打杂大佬

网上几个nice的博客(忘了是哪个了。。。。)

前言

虽然FIFO都有IP可以使用,但理解原理还是自己写一个来得透彻。

什么是FIFO?

Fist in first out。先入先出的数据缓存器,没有外部读写地址线,可同时读写。

规则:永远不要写一个已经写满了的fifo。

永远不要读一个读空了的fifo。

FIFO种类?

同步FIFO和异步FIFO。

同步FIFO只有一个时钟,也就是说写端和读端的时钟是一毛一样的。

异步FIFO读端和写端两个时钟则是不一样的。包括同频异相,异频异相。

FIFO用途?

  1. 数据缓冲器。比如你写端burst一个数据,没有fifo缓冲的话就炸了。Fifo会把写端的突发数据吃到肚子里,读端可以慢慢的一个个读出来。
  2. 跨时钟域。异步fifo主要使用在不同时钟域的边缘,用来同步数据到另一个时钟域。

ALTERA FIFO IP 的缺点是什么?

虽然altera贴心的提供了FIFO的IP块,但是对于可移植性与自定义位宽深度更好的话,还是自己写的更佳。

FIFO深度如何计算?(避免溢出)

对于异步fifo,如果读时钟大于写时钟且每个周期读写,那么一定是会读空的,反之一定会被写满。一般来说,不会设计这么无聊的东西。

假设写端有突发的数据,而读端是均匀的读出,怎么保证fifo不溢出呢?

怎么用Verilog语言描述同步FIFO和异步FIFO

异步FIFO快转慢的问题:可能采样踩不到某些值。

怎么用Verilog语言描述同步FIFO和异步FIFO

同步FIFO:

当缓冲器使用,可以用ram资源搭。

原理图:

怎么用Verilog语言描述同步FIFO和异步FIFO

信号定义:

clk:时钟信号

rst_n:异步复位信号

wr:写请求

rd:读请求

data:数据输入

q:   数据输出

full:满信号,表示fifo吃饱了

empty:空信号,表示fifo肚子已经空掉了

usedw:表示fifo中已有的数据个数

仿真:

没有usedw款:

怎么用Verilog语言描述同步FIFO和异步FIFO

有usedw款:

怎么用Verilog语言描述同步FIFO和异步FIFO

资源使用量:

怎么用Verilog语言描述同步FIFO和异步FIFO

如何设计一个异步FIFO?

一般用作跨时钟域,可用ram搭。

判断读空与写满,读写指针要跨时钟域,所以采用格雷码减少亚稳态。

什么是格雷码?如何与二进制码转换?

格雷码的基本特点是两个相邻的码只有一位二进制数不同。

怎么用Verilog语言描述同步FIFO和异步FIFO

怎么用Verilog语言描述同步FIFO和异步FIFO

二进制转格雷码:

简单来说就是把二进制码右移一位再与二进制异或。

assign wr_poi_gray = wr_poi ^ (wr_poi>>1); //produce wr pointer gray code;

格雷码转二进制:

格雷码转二进制是从左边第二位起,将每位与左边一位二进制码的值异或,作为该位二进制码的值。

比如四位的码:

bin[3] = gray[3];

bin[2] = gray[2]^bin[3];

bin[1] = gray[1]^bin[2];

bin[0] = gray[0]^bin[1];

原理图:

怎么用Verilog语言描述同步FIFO和异步FIFO

信号定义:

wrclk:写时钟信号

rdclk: 读时钟信号

rst_n:异步复位信号

wr:写请求

rd:读请求

data:数据输入

q: 数据输出

full:满信号,表示fifo吃饱了

empty:空信号,表示fifo肚子已经空掉了

仿真:写时钟是读时钟的两倍。

写读测试:由于两级同步器的存在,判定空满滞后两个周期。

怎么用Verilog语言描述同步FIFO和异步FIFO

同时读写测试:

怎么用Verilog语言描述同步FIFO和异步FIFO

资源使用量:

怎么用Verilog语言描述同步FIFO和异步FIFO

注意:

同步FIFO的地址扩展一位作为判断空满的状态位,读写指针低位都相同的时候,最高位相同则表示读空,不同则表示写满。

 assign full = (wr_poi[clogb2(DEPTH)-:]== rd_poi[clogb2(DEPTH)-:]) && (wr_poi[clogb2(DEPTH)] ^ rd_poi[clogb2(DEPTH)] == );  //highest bit is not same but rests bit is same;
assign empty = (wr_poi[clogb2(DEPTH)-:]== rd_poi[clogb2(DEPTH)-:]) && (wr_poi[clogb2(DEPTH)] ^ rd_poi[clogb2(DEPTH)] == ); //every bit is same;

异步FIFO的地址位也扩展一位作为状态位,通过格雷码跨过时钟域判断空满。

在写时钟域判断full:如果本时钟域的写地址格雷码和同步过来的读地址格雷码最高位和次高位均不同,其余位相同,则表示写满了。

在读时钟域判断empty:如果本时钟域的读地址格雷码和同步过来的写地址格雷码最高位和次高位都一样,其余位也一样,则表示读空。

 assign full =  (wr_poi_gray == {~rd_poi_gray2[clogb2(DEPTH):clogb2(DEPTH)-],rd_poi_gray2[clogb2(DEPTH)-:]});
assign empty = (wr_poi_gray2 == rd_poi_gray);

贴一下同步FIFO的代码:

已定制ramstyle,如果你的不同请删掉或者更改。深度必须为2^n,否则更改函数clogb2中的depth为depth>0.
仅供学习交流,请勿用于商业用途,版权所有。异步代码就不贴了,想研究请联系我。

 //************************************************
// Filename : fifo_syn.v
// Author : kingstacker
// Company : School
// Email : kingstacker_work@163.com
// Device : Altera cyclone4 ep4ce6f17c8
// Description : synchronize fifo ;8*8 ;depth shuold be 2^n,otherwise change the clogb2 funtion;
//************************************************
module fifo_syn #(parameter WIDTH = ,DEPTH = )(
//input;
input wire clk, //only one clock;
input wire rst_n,
input wire wr, //wr request;
input wire rd, //rd request;
input wire [WIDTH-:] data, //data in;
//output;
output wire [WIDTH-:] q, //data out;
output wire full, //fifo is full;
output wire empty, //fifo is empty;
output wire [clogb2(DEPTH)-:] usedw //data number in fifo;
);
function integer clogb2 (input integer depth);
begin
for (clogb2=; depth>; clogb2=clogb2+) //depth>1 when you choose depth 2^n;otherwise change it to depth>0;for example depth is 7;
depth = depth >>;
end
endfunction
(* ramstyle = "M9K" *) reg [WIDTH-:] memory [:DEPTH-];
reg [clogb2(DEPTH):] wr_poi; //wr pointer;
reg [clogb2(DEPTH):] rd_poi; //rd pointer;
reg [WIDTH-:] q_r; //reg q;
reg [clogb2(DEPTH)-:] usedw_r; //reg usedw_r;
wire wr_flag; //real wr request;
wire rd_flag; //real rd request;
assign q = q_r;
assign usedw = usedw_r;
assign full = (wr_poi[clogb2(DEPTH)-:]== rd_poi[clogb2(DEPTH)-:]) && (wr_poi[clogb2(DEPTH)] ^ rd_poi[clogb2(DEPTH)] == ); //highest bit is not same but rests bit is same;
assign empty = (wr_poi[clogb2(DEPTH)-:]== rd_poi[clogb2(DEPTH)-:]) && (wr_poi[clogb2(DEPTH)] ^ rd_poi[clogb2(DEPTH)] == ); //every bit is same;
assign wr_flag = ((wr == 'b1) && (full == 1'b0)); //wr enable;
assign rd_flag = ((rd == 'b1) && (empty == 1'b0)); //rd enable;
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
wr_poi <= ;
end //if
else begin
wr_poi <= wr_flag ? wr_poi + 'b1 : wr_poi;
memory[wr_poi[clogb2(DEPTH)-:]] <= wr_flag ? data : memory[wr_poi[clogb2(DEPTH)-:]];
end //else
end //always
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
rd_poi <= ;
q_r <= ;
end //if
else begin
rd_poi <= rd_flag ? rd_poi + 'b1 : rd_poi;
q_r <= rd_flag ? memory[rd_poi[clogb2(DEPTH)-:]] : q_r;
end //else
end //always
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
usedw_r <= ;
end //if
else begin
case ({wr_flag,rd_flag})
'b00: begin
usedw_r <= usedw_r;
end
'b10: begin
if (usedw_r == DEPTH-) begin // full;
usedw_r <= usedw_r;
end
else begin
usedw_r <= usedw_r + 'b1;
end
end
'b01: begin
if (usedw_r == ) begin //empty;
usedw_r <= usedw_r;
end
else begin
usedw_r <= usedw_r - 'b1;
end
end
'b11: begin
usedw_r <= usedw_r;
end
default: usedw_r <= ;
endcase //case
end //else
end //always endmodule

以上。