掌握基本的数字模块是数字IC工程师的基本要求,最近几年在笔试和面试的时候会遇到要求手撕代码,一方面是考察面试者有没有良好的coding style, 重要的则是考察面试者对常用模块的了解程度。面对这种问题,没有比较好的解决方法,只能是多看、多写,时常复习复习。之所以要把异步FIFO放到分享的第一篇技术博客,是因为本人在学习过程中,从项目中简单了解异步FIFO的IP核、到学习空满信号判断再到照着资料手敲代码,最后到面试中针对异步FIFO有问必答,一路走过来,越发了解到异步FIFO的重要性。而在和经验丰富的老工程师交谈的时候,发现他们那个年代是需要手撕异步FIFO的,当时真的是人人都会,反观现在的面试者,好多人只是了解个IP核就无了。
FIFO(First In First Out),先进先出。 ,对数据的存储具有先进先出的特性,FPGA 或者 ASIC 中常使用的数据缓存器,常被用于数据的缓存或者高速异步数据的交互。
注意点:
(1)它与普通存储器的区别是没有外部读写地址线,使用相对简单。
(2)缺点是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能读取指定位置的数据。
(3)有同步和异步FIFO,同步指的是读写时钟是同一个时钟,注意这里时钟的概念,是同一个,不是同频时钟。 异步指的是读写时钟不同。
(4)同步FIFO一般做数据缓冲,主要作用就是buffer。
(5)异步FIFO主要作用就是跨时钟域处理,除此之外可以实现两个不同数据宽度的数据连接。
FIFO从结构来区分,有两类。分别是:单时钟 FIFO(SCFIFO)和双时钟 FIFO (DCFIFO),其中双时钟FIFO又可以分为 普通双时钟(DCFIFO) 和 混合宽度双时钟 FIFO (DCFIFO_MIXED_WIDTHS)。
单时钟FIFO和双时钟FIFO的输入输出信号如图所示:(来源于Intel FPGA FIFO IP User Guide)
(1)单时钟FIFO所有输入输出信号都是在Clock的上升沿进行的,均同步于Clock信号。
(2)双时钟FIFO结构中,读写端口分别有独立的时钟,所有写相关信号同步于写时钟 wrclk ,所有读相关信号同步于读时钟 rdclk 。
(1)同步FIFO
单时钟FIFO常用于片内数据交互,例如,FPGA读取传感器数据,先写入FIFO,之后再通过串口发送出去。传感器的读取和串口数据发送都可以基于同一个时钟,因此使用单时钟FIFO做同步。
(2)异步FIFO
异步FIFO典型应用就是异步数据收发,例如高速ADC采集,采集后数据通过千兆以太网发送回PC机器。
(1)宽度(WIDTH),FIFO一次读写的数据位宽。
(2)深度(DEEPTH),FIFO可以存储多少个数据。
(3)满标志:FIFO 已满或将要满时送出的信号,停止写操作而造成溢出(overflow)。
(4)空标志:FIFO 已空或将要空时送出的信号,停止读出数据而造成无效数据的读出(underflow)。
(5)读时钟:读取数据端的时钟信号。
(6)写时钟:写入数据端的时钟信号。
(7)读指针:内部读地址,时钟有效自动加一。
(8)写指针:内部写地址,时钟有效自动加一。
(9)读使能:读取端数据有效。
(10)写使能:写入端数据有效。
如下图所示,简单的异步FIFO内部主要包含以下五个模块,分别是双端口RAM、写满标志判断write_full、读空标志判断read_empty、读时钟到写时钟同步、写时钟到读时钟同步。
这里常考的几个关键点是:
(1)双端口RAM相关知识。
(2)写满和读空信号的判断。 用格雷码怎么判断,二进制码怎么判断,有何优缺点?
(3)同步模块的作用? 二进制转格雷码怎么做?同步之后的写满和读空是真满 真空吗?
下面将附上异步FIFO的Verilog代码,这里建议初学者一定要手敲,用心理解各个模块的作用以及信号连接。同时,也能作为基础的代码练习,培养手速和coding style。最后,这里附上的代码仅供学习,重点是学习异步FIFO的内部结构,感兴趣的同学可以自己再找找异步FIFO相关的代码。
注:这里代码借鉴了猪肉白菜的博客园,各位同学一定要去看看这篇博客。
另外,还有一篇菜鸟教程的博客,也很不错
(1)猪肉白菜博客链接: link
(2)菜鸟教程博客: link
顶层包含了模块例化、内部连线、端口声明等。
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// Author : hfut904
// File : FIFO_async.v
// Create : 2022-03-04 09:47:04
// Revise : 2022-03-04 21:03:52
// Editor : sublime text4, tab size (4)
// -----------------------------------------------------------------------------
//异步fifo设计 包括五个部分 RAM、write_full、read_empty、synchronization(写到读、读到写)
module FIFO_async #(
//------------------paramter------------------------------
parameter FIFO_data_size = 6 ,
parameter FIFO_addr_size = 5
)(
//write signals
input clk_w ,
input rst_w ,
input w_en ,
//read signals
input clk_r ,
input rst_r ,
input r_en ,
//data in & out
input [FIFO_data_size-1:0] data_in ,
output [FIFO_data_size-1:0] data_out ,
//key signals
output wire empty ,
output wire full
);
//==============================================================
//------------paramter reg wire ------------------------------
wire [FIFO_addr_size:0] r_pointer_gray_sync ;
wire [FIFO_addr_size:0] w_pointer_gray_sync ;
wire [FIFO_addr_size:0] r_pointer_gray ;
wire [FIFO_addr_size:0] w_pointer_gray ;
wire [FIFO_addr_size-1:0] w_addr ;
wire [FIFO_addr_size-1:0] r_addr ;
//inst model
RAM #(
.FIFO_data_size(FIFO_data_size),
.FIFO_addr_size(FIFO_addr_size)
) inst_RAM (
.clk_w (clk_w ),
.rst_w (rst_w ),
.clk_r (clk_r ),
.rst_r (rst_r ),
.full (full ),
.empty (empty ),
.w_en (w_en ),
.r_en (r_en ),
.r_addr (r_addr ),
.w_addr (w_addr ),
.data_in (data_in ),
.data_out (data_out )
);
write_full #(
.FIFO_addr_size(FIFO_addr_size)
) inst_write_full (
.clk_w (clk_w ),
.rst_w (rst_w ),
.w_en (w_en ),
.r_pointer_gray_sync (r_pointer_gray_sync ),
.w_pointer_gray (w_pointer_gray ),
.w_addr (w_addr ),
.full (full )
);
read_empty #(
.FIFO_addr_size(FIFO_addr_size)
) inst_read_empty (
.clk_r (clk_r ),
.rst_r (rst_r ),
.r_en (r_en ),
.w_pointer_gray_sync (w_pointer_gray_sync ),
.r_pointer_gray (r_pointer_gray ),
.r_addr (r_addr ),
.empty (empty )
);
synchronization #(
.FIFO_addr_size(FIFO_addr_size)
) inst1_synchronization (
.clk (clk_r ),
.rst (rst_r ),
.din (r_pointer_gray ),
.dout (r_pointer_gray_sync )
);
synchronization #(
.FIFO_addr_size(FIFO_addr_size)
) inst2_synchronization (
.clk (clk_w ),
.rst (rst_w ),
.din (w_pointer_gray ),
.dout (w_pointer_gray_sync )
);
endmodule
双口RAM是FIFO的重要组成部分,用来完成数据存储。
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// Author : hfut904
// File : RAM.v
// Create : 2022-03-04 10:02:58
// Revise : 2022-03-04 17:03:43
// Editor : sublime text4, tab size (4)
// -----------------------------------------------------------------------------
//双口RAM模块
module RAM #(
//------------------------paramter-------------------
parameter FIFO_data_size = 3 ,
parameter FIFO_addr_size = 2
)(
//----------------------port define -----------------
//write clock & reset
input clk_w ,
input rst_w ,
//read clock & reset
input clk_r ,
input rst_r ,
//key signals
input full ,
input empty ,
//enable
input w_en ,
input r_en ,
//wr rd addr
input [FIFO_addr_size-1:0] w_addr ,
input [FIFO_addr_size-1:0] r_addr ,
input [FIFO_data_size-1:0] data_in ,
output reg [FIFO_data_size-1:0] data_out
);
//==============================================================
//------------paramter reg wire ------------------------------
reg [FIFO_data_size-1:0] mem [{FIFO_addr_size{1'b1}}:0 ] ;
integer i ;
/*------------------------------------------------------------------------------
-- wire flag_wr ;
wire flag_rd ;
------------------------------------------------------------------------------*/
//always block
always @(posedge clk_w or negedge rst_w) begin
if(~rst_w) begin
for ( i = 0; i <= FIFO_data_size; i=i+1) begin
mem[i] <= {FIFO_data_size{1'b0}} ;
end
end
else if ((w_en == 1) && (full == 0))begin
mem[w_addr] <= data_in ;
end
else begin
mem[w_addr] <= {FIFO_data_size{1'b0}} ;
end
end
//rd
always @(posedge clk_r or negedge rst_r) begin
if(~rst_r) begin
data_out <= {FIFO_data_size{1'b0}} ; //'d0
end
else if ((r_en == 1) && (empty == 0))begin
data_out <= mem[r_addr] ;
end
else begin
data_out <= {FIFO_data_size{1'b0}} ;
end
end
//assign
/*
assign flag_wr = (w_en == 1) && (full == 0) ;
assign flag_rd = (r_en == 1) && (empty == 0) ;
*/
endmodule
输出当前FIFO写满标志。
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// Author : hfut904
// File : write_full.v
// Create : 2022-03-04 10:32:09
// Revise : 2022-03-04 15:25:14
// Editor : sublime text4, tab size (4)
// -----------------------------------------------------------------------------
module write_full #(
//-----------------paramter-------------------------------
parameter FIFO_addr_size = 2
)(
//================== port define ===========================
input clk_w ,
input rst_w ,
input w_en ,
input [FIFO_addr_size:0] r_pointer_gray_sync ,
output full ,
output wire [FIFO_addr_size-1:0] w_addr ,
output wire [FIFO_addr_size:0] w_pointer_gray
);
//========================reg wire =================================
reg [FIFO_addr_size:0] w_pointer_bin ;
wire flag_wr ;
//===================Main Code=====================================
//always block
always @(posedge clk_w or negedge rst_w) begin : proc_
if(~rst_w) begin
w_pointer_bin <= 0 ;
end
else if (flag_wr)begin
w_pointer_bin <= w_pointer_bin + 1;
end
else begin
w_pointer_bin <= w_pointer_bin ;
end
end
//assign
assign flag_wr = (w_en == 1) && (full == 0) ;
assign w_pointer_gray = (w_pointer_bin >> 1)^w_pointer_bin ;
//二进制码转换为gary码 Gn-1 = Bn-1 Gi= Bi+1 ^ Bi
assign w_addr = w_pointer_bin[FIFO_addr_size-1:0] ;
assign full = (w_pointer_gray == {~r_pointer_gray_sync[FIFO_addr_size:FIFO_addr_size-1],r_pointer_gray_sync[FIFO_addr_size-2:0]} ) ? 1: 0 ;
//满信号 最高位取反,其他位相等就是满
endmodule
输出当前FIFO读空标志。
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// Author : hfut904
// File : read_empty.v
// Create : 2022-03-04 11:10:59
// Revise : 2022-03-04 17:19:20
// Editor : sublime text4, tab size (4)
// -----------------------------------------------------------------------------
//读空信号产生模块
module read_empty #(
//=====================paramter ================================================
parameter FIFO_addr_size = 2
)(
//=========================port define===========================================
input clk_r ,
input rst_r ,
input r_en ,
input [FIFO_addr_size:0] w_pointer_gray_sync ,
output wire empty ,
output wire [FIFO_addr_size-1:0] r_addr ,
output wire [FIFO_addr_size:0] r_pointer_gray
);
//======================reg wire ==================================================
reg [FIFO_addr_size:0] r_pointer_bin ;
//wire flag_rd ;
//============================Main code============================================
always @(posedge clk_r or negedge rst_r) begin : proc_
if(~rst_r) begin
r_pointer_bin <= {FIFO_addr_size{1'b0}} ;
end
else if ((r_en == 1) &&(empty == 0)) begin
r_pointer_bin <= r_pointer_bin + 1 ;
end
end
//assign
//assign flag_rd = (r_en == 1) &&(empty == 0) ;
assign r_pointer_gray = (r_pointer_bin>>1)^r_pointer_bin ;
assign r_addr = r_pointer_bin[FIFO_addr_size-1:0] ;
assign empty = r_pointer_gray == w_pointer_gray_sync? 1: 0 ; //MSB 最高位相等就判断为空
endmodule
这里用到的是单bit同步的方式,结构简单,在高速应用中则采用其他的同步方式。
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// Author : hfut904
// File : synchronization.v
// Create : 2022-03-04 14:26:40
// Revise : 2022-03-04 20:55:24
// Editor : sublime text4, tab size (4)
// -----------------------------------------------------------------------------
///同步模块 打两拍进行同步
module synchronization #(
//=======================paramter==========================================
parameter FIFO_addr_size = 2
)(
//====================port define=======================================
input clk ,
input rst ,
input [FIFO_addr_size:0] din ,
output reg [FIFO_addr_size:0] dout
);
//====================reg wire ==========================================
reg [FIFO_addr_size:0] dout_t ;
//always block
always @(posedge clk or negedge rst) begin
if(~rst) begin
dout <= {(FIFO_addr_size){1'b0}} ;
dout_t <= {(FIFO_addr_size){1'b0}} ;
end
else begin
dout_t <= din ;
dout <= dout_t ;
end
end
endmodule
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// Author : hfut904
// File : tb_FIFO_async.v
// Create : 2022-03-04 15:46:00
// Revise : 2022-03-04 16:44:32
// Editor : sublime text4, tab size (4)
// -----------------------------------------------------------------------------
//testbench 测试模块
`timescale 1ns/1ns
module tb_FIFO_async ();
//============parameter =================
parameter FIFO_data_size = 3 ;
parameter FIFO_addr_size = 2 ;
// ====== reg wire======================================
reg clk_r ;
reg rst_r ;
reg r_en ;
reg clk_w ;
reg rst_w ;
reg w_en ;
reg [FIFO_data_size-1:0] data_in ;
wire [FIFO_data_size-1:0] data_out ;
wire empty ;
wire full ;
integer i ;
// clk_w 写模块信号
initial begin
clk_w = 0 ;
rst_w = 1 ;
data_in = {FIFO_data_size{1'b0}} ;
#15
rst_w = 0 ;
#20
rst_w = 1 ;
end
//clk_r 读模块信号
initial begin
clk_r = 0 ;
rst_r = 1 ;
r_en = 0 ;
#25
rst_r = 0 ;
#50
rst_r = 1 ;
end
//w_en 写使能
initial begin
w_en = 0 ;
#450
w_en = 1 ;
#400
w_en = 0 ;
#750
w_en = 1 ;
end
//r_en 读使能
initial begin
r_en = 0 ;
#900
r_en = 1 ;
#400
r_en = 0 ;
#300
r_en = 1 ;
end
initial begin
for ( i = 0; i <= 50; i=i+1) begin
/* code */
#100
data_in = i ;
end
end
//always block
always #25 clk_w = ~clk_w ;
always #50 clk_r = ~clk_r ;
FIFO_async #(
.FIFO_data_size(FIFO_data_size),
.FIFO_addr_size(FIFO_addr_size)
) inst_FIFO_async (
.clk_w (clk_w ),
.rst_w (rst_w ),
.w_en (w_en ),
.clk_r (clk_r ),
.rst_r (rst_r ),
.r_en (r_en ),
.data_in (data_in ),
.data_out (data_out ),
.empty (empty ),
.full (full )
);
endmodule
(1)需要了解异步FIFO内部的基本结构
(2)掌握写满和读空信号的判断
(3)会在项目中使用FIFO
注:异步FIFO内部结构大差不差,但是不同结构的异步FIFO在实际项目中的性能有所差异,学习异步FIFO重要的是体会内部的基本组成,了解异步FIFO的作用。在做FPGA或者ASIC设计时,尽量使用自带的IP核。(好的设计一定是在之前的基础上优化而来的,成熟的IP是设计时的优先选择!同理,一个设计只有在经过无数次debug和移植验证后,才能称为一个可用的设计。)后续会上传本次源码和仿真文件,不过还是建议初学者按照自己的代码风格手敲一遍,解决过程中出现的小bug,IC设计需要经验而不是复制粘贴。