同步(单时钟)、异步(双时钟)FIFO的Verilog HDL实现(含Testbench仿真代码)

目录

一、FIFO的定义和应用场景

二、FIFO的结构

三、FIFO的应用场景

3.1 单时钟(同步)FIFO

3.2 双时钟(异步)FIFO

四、FIFO的结构

五、FIFO常见参数

六、实现 FIFO 的方法

6.1 IP核的使用——FIFO

6.1.1 单时钟 FIFO 实现与测试

6.1.2 混合宽度异步(双时钟) FIFO 实现与测试

6.2 纯编程实现同步FIFO

6.2.1 顶层模块

6.2.2 FIFO控制模块

 6.2.3 双端口RAM模块

 6.2.4 RTL视图

 6.2.5 TestBench代码

 6.2.6 仿真结果

6.3 纯编程实现异步混合宽度FIFO


参考资料:

《FPGA自学笔记——设计与验证》;《硬件架构的艺术》;《Verilog HDL数字集成电路高级程序设计》等

链接:https://pan.baidu.com/s/1dKOXQMg3M3vHmVIx9KdMxg 
提取码:kqy9 
--来自百度网盘超级会员V6的分享


一、FIFO的定义和应用场景

FIFO(First in First Out)是一种先进先出的数据缓冲器,通常用于接口电路的数据缓存。与普通存储器的区别是没有外部读写地址线,可以使用两个时钟分别进行读和写操作。

FIFO只能顺序写入数据和顺序读出数据,其数据地址由内部读、写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。


二、FIFO的结构

FIFO从读/写端口和同步时钟关系来分,有两类结构:单时钟FIFO(Single Clock FIFO,SCFIFO)和双时钟(Dual Clock FIFO,DCFIFO),其中DCFIFO又可以分为普通双时钟(DCFIFO)和混合宽度双时钟FIFO(DCFIFO_MIXED_WIDTHS)。分别如下图所示:

从图中可以看到,单时钟FIFO具有一个独立的时钟端口 Clock,因此所有输入信号的读取都是在Clock的上升沿进行的,所有输出信号的变化也是在Clock信号的上升沿的控制下进行的,即单时钟 FIFO 的所有输入输出信号都是同步于 Clock 信号的

而在双时钟 FIFO 结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wrclk 的,所有与读相关的信号都是同步于读时钟 rdclk 的。在双时钟 FIFO 的符号图中,位于上部分的为与相关的所有信号,位于中间部分的为与相关的所有信号,位于下部的为异步清零信号。


三、FIFO的应用场景

3.1 单时钟(同步)FIFO

单时钟 FIFO 常用于片内数据交互。
例如,在 FPGA 的控制下从外部传感器读取到的一连串传感器数据,首先被写入 FIFO 中,然后再以 UART 串口波特率将数据依次发送出去。由于传感器的单次读取数据可能很快,但并不是时刻都需要采集数据,例如某传感器使用SPI 接口的协议, FPGA 2M SPI 数据速率从该传感器中读取 20 个数据,然后以 9600的波特率通过串口发送出去。因为 2M 的数据速率远高于串口 9600 的波特率,因此需要将从传感器中采集到的数据首先用 FIFO 缓存起来,然后再以串口的数据速率缓慢发送出去。这里,由于传感器数据的读取和串口数据的发送都是可以同步于同一个时钟的,因此可以使用单时钟结构的 FIFO 来实现此功能

3.2 双时钟(异步)FIFO

双时钟 FIFO 的一个典型应用就是异步数据的收发,所谓异步数据是指数据的发送端和接收端分别使用不同的时钟域使用双时钟 FIFO 能够将不同时钟域中的数据同步到所需的时钟域系统中。
例如,在一个高速数据采集系统中,实现将高速 ADC 采集的数据通过千兆以太网发送到 PC 机。 ADC 的采样时钟 (CLK1) 由外部专用锁相环芯片产生,则高速 ADC 采样得到的数据就是同步于该 CLK1 时钟信号,在 FPGA 内部,如果 FPGA 工作时钟 (CLK2)是由独立的时钟芯片加片上锁相环产生的,则 CLK1 CLK2 就是两个不同域的时钟,他们的频率和相位没有必然的联系,假如 CLK1 65M CLK2 125M ,那么就不能使用 125M的数据来直接采集 65M 速率的数据,因为两者数据速率不匹配,在采集过程中会出现包括亚稳态问题在内的一系列问题,所以这里就可以使用一个具备双时钟结构的 FIFO 来进行异步数据的收发。下图 为使用 FIFO 进行异步数据收发的简易系统框图。

在此系统中,由于 ADC 的数据位宽为 8 位,基于 UDP 协议的以太网发送模块所需的数据也是 8 位,因此使用的是非混合宽度的双时钟 FIFO 结构。假如 CLK1 的频率为 20M ,ADC 的数据位宽为 16 位,则可以使用混合宽度的双时钟 FIFO ,在实现异步时钟域数据收发的同时,实现数据位宽的转换。通过设置双时钟 FIFO 的写入位宽为 16 位,读取位宽为 8 位,则可以实现将 16 位的 ADC 数据转换为以太网支持的 8 位发送数据,然后通过以太网发送到 PC 机。

四、FIFO的结构

一个FIFO的组成一般包括两个部分:地址控制部分和存储数据的DPRAM(双端口RAM)部分,如下图所示:

 地址控制部分可以根据读写指令生成RAM地址。RAM用于存储堆栈数据,并根据控制部分生成的地址信号进行数据的存储和读取操作。


五、FIFO常见参数

FIFO 的宽度:即 FIFO 一次读写操作的数据位;
FIFO 的深度:指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N);
满标志:FIFO 已满或将要满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 写操作继续向 FIFO 中写数据而造成溢出;
空标志:FIFO 已空或将要空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 读操作继续从 FIFO 中读出数据而造成无效数据的读出;
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据;
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。

六、实现 FIFO 的方法

Altera FPGA 中使用 FIFO 实现用户功能设计主要有三种实现方式。
第一种为用户根据需求自己编写 FIFO 逻辑(自己编写FIFO控制模块和RAM存储模块),当对于 FIFO 的功能有特殊需求时,可以使用此种方式实现。
第二种方式为使用FIFO的  IP 核, Quartus Prime 软件为用户提供了友好的图形化界面方便用户对 FIFO 的各种参数和结构进行配置,生成的 FIFO IP 核针对 Altera 不同系列的器件,还可以实现结构上的优化。该 FIFO IP 核也是通过Verilog 语言进行描述的,在 Quartus Prime 软件中,该 IP 核源码存放于 Quartus Prime软件安装目录 quartus\eda\sim_lib 下的 altera_mf.v 文件中的第 51101 行( scfifo (dcfifo 结构较多,因此代码内容很多,与之相关的代码有几千行,可以在文件中搜索 dcfifo 即可找到 ) 。 由于该 FIFO IP 核已经提供了大部分设计所需的所有功能,因此在系统设计中,推荐使用该FIFO IP 核进行系统设计。
第三种调用RAM的IP核,自己写FIFO的控制模块。
下面就这三种方法分别进行编程和仿真。

6.1 IP核的使用——FIFO

6.1.1 单时钟 FIFO 实现与测试

1、创建IP核 

2、将生成的IP核文件设为顶层文件

3、自动生成了my_fifo.v文件,打开查看一下端口,方便写激励文件

// megafunction wizard: %FIFO%
// GENERATION: STANDARD
// VERSION: WM1.0
// MODULE: scfifo 

// ============================================================
// File Name: myfifo.v
// Megafunction Name(s):
// 			scfifo
//
// Simulation Library Files(s):
// 			altera_mf
// ============================================================
// ************************************************************
// THIS IS A WIZARD-GENERATED FILE. DO NOT EDIT THIS FILE!
//
// 13.1.0 Build 162 10/23/2013 SJ Full Version
// ************************************************************



// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module myfifo (
	clock,
	data,
	rdreq,
	sclr,
	wrreq,
	almost_empty,
	almost_full,
	empty,
	full,
	q,
	usedw);

	input	  clock;
	input	[7:0]  data;
	input	  rdreq;
	input	  sclr;
	input	  wrreq;
	output	  almost_empty;
	output	  almost_full;
	output	  empty;
	output	  full;
	output	[7:0]  q;
	output	[3:0]  usedw;

	wire [3:0] sub_wire0;
	wire  sub_wire1;
	wire  sub_wire2;
	wire [7:0] sub_wire3;
	wire  sub_wire4;
	wire  sub_wire5;
	wire [3:0] usedw = sub_wire0[3:0];
	wire  empty = sub_wire1;
	wire  full = sub_wire2;
	wire [7:0] q = sub_wire3[7:0];
	wire  almost_empty = sub_wire4;
	wire  almost_full = sub_wire5;

	scfifo	scfifo_component (
				.clock (clock),
				.sclr (sclr),
				.wrreq (wrreq),
				.data (data),
				.rdreq (rdreq),
				.usedw (sub_wire0),
				.empty (sub_wire1),
				.full (sub_wire2),
				.q (sub_wire3),
				.almost_empty (sub_wire4),
				.almost_full (sub_wire5),
				.aclr ());
	defparam
		scfifo_component.add_ram_output_register = "OFF",
		scfifo_component.almost_empty_value = 2,
		scfifo_component.almost_full_value = 14,
		scfifo_component.intended_device_family = "Cyclone IV GX",
		scfifo_component.lpm_numwords = 16,
		scfifo_component.lpm_showahead = "OFF",
		scfifo_component.lpm_type = "scfifo",
		scfifo_component.lpm_width = 8,
		scfifo_component.lpm_widthu = 4,
		scfifo_component.overflow_checking = "ON",
		scfifo_component.underflow_checking = "ON",
		scfifo_component.use_eab = "ON";


endmodule

 4、编写和添加仿真激励文件fifo_tb.v

`timescale 1ns/1ps
`define clk_period 20

module fifo_tb;

//source define

	reg	  Clk;
	reg	[7:0]  data;
	reg	  rdreq;
	reg	  sclr;
	reg	  wrreq;
//probe define
	wire	  almost_empty;
	wire	  almost_full;
	wire	  empty;
	wire	  full;
	wire	[7:0]  q;
	wire	[3:0]  usedw;

//instant user module
	myfifo my_fifo(
		.clock(Clk),
		.data(data),
		.rdreq(rdreq),
		.sclr(sclr),
		.wrreq(wrreq),
		.almost_empty(almost_empty),
		.almost_full(almost_full),
		.empty(empty),
		.full(full),
		.q(q),
		.usedw(usedw)
	);

//generater clock
	initial Clk = 1;
	always #(`clk_period/2)Clk = ~Clk;
	
	integer i;

	initial begin
		wrreq = 0;
		data = 0;
		rdreq = 0;
		sclr = 0;
		#(`clk_period*20 + 1);
		for (i=0;i <= 15 ;i = i + 1)begin 
			wrreq = 1;
			data = i;
			#`clk_period;
		end
		wrreq = 0;
		#(`clk_period*20);
		for (i=0;i <= 15 ;i = i + 1)begin
			rdreq = 1;
			#`clk_period;
		end	
		rdreq = 0;
		$stop;		
	end

endmodule

 5、RTL simulation打开Modelsim,查看波形

 最后打开RTL Viewer,查看模块组成:主要是调用了FIFO的IP核,使用比较简单,引脚可以根据需要进行增删。

 

6.1.2 混合宽度异步(双时钟) FIFO 实现与测试

要求:输入的数据是16位,输出数据为8位,且输入输出由两个时钟控制

1、新建一个fifo,注意读写时钟以及 输入输出位宽的差别

 2、查看生成的IP核文件,并设为顶层文件

// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module myfifo (
	data,
	rdclk,
	rdreq,
	wrclk,
	wrreq,
	q,
	rdempty,
	rdusedw,
	wrfull,
	wrusedw);

	input	[15:0]  data;
	input	  rdclk;
	input	  rdreq;
	input	  wrclk;
	input	  wrreq;
	output	[7:0]  q;
	output	  rdempty;
	output	[4:0]  rdusedw;
	output	  wrfull;
	output	[3:0]  wrusedw;

	wire  sub_wire0;
	wire [7:0] sub_wire1;
	wire  sub_wire2;
	wire [3:0] sub_wire3;
	wire [4:0] sub_wire4;
	wire  wrfull = sub_wire0;
	wire [7:0] q = sub_wire1[7:0];
	wire  rdempty = sub_wire2;
	wire [3:0] wrusedw = sub_wire3[3:0];
	wire [4:0] rdusedw = sub_wire4[4:0];

	dcfifo_mixed_widths	dcfifo_mixed_widths_component (
				.rdclk (rdclk),
				.wrclk (wrclk),
				.wrreq (wrreq),
				.data (data),
				.rdreq (rdreq),
				.wrfull (sub_wire0),
				.q (sub_wire1),
				.rdempty (sub_wire2),
				.wrusedw (sub_wire3),
				.rdusedw (sub_wire4),
				.aclr (1'b0),
				.rdfull (),
				.wrempty ());
	defparam
		dcfifo_mixed_widths_component.intended_device_family = "Cyclone IV GX",
		dcfifo_mixed_widths_component.lpm_numwords = 16,
		dcfifo_mixed_widths_component.lpm_showahead = "OFF",
		dcfifo_mixed_widths_component.lpm_type = "dcfifo_mixed_widths",
		dcfifo_mixed_widths_component.lpm_width = 16,
		dcfifo_mixed_widths_component.lpm_widthu = 4,
		dcfifo_mixed_widths_component.lpm_widthu_r = 5,
		dcfifo_mixed_widths_component.lpm_width_r = 8,
		dcfifo_mixed_widths_component.overflow_checking = "ON",
		dcfifo_mixed_widths_component.rdsync_delaypipe = 4,
		dcfifo_mixed_widths_component.underflow_checking = "ON",
		dcfifo_mixed_widths_component.use_eab = "ON",
		dcfifo_mixed_widths_component.wrsync_delaypipe = 4;


endmodule

3、编写testbench

`timescale 1ns/1ps
`define wrclk_period 20
`define rdclk_period 10

module mydcfifo_tb;

//source define
	reg	[15:0]  data;
	reg	  rdclk;
	reg	  rdreq;
	reg	  wrclk;
	reg	  wrreq;


//probe define
	wire	[7:0]  q;
	wire	  rdempty;
	wire	[8:0]  rdusedw;
	wire	  wrfull;
	wire	[7:0]  wrusedw;

//instant user module
	myfifo my_dcfifo(
		.data(data),
		.rdclk(rdclk),
		.rdreq(rdreq),
		.wrclk(wrclk),
		.wrreq(wrreq),
		.q(q),
		.rdempty(rdempty),
		.rdusedw(rdusedw),
		.wrfull(wrfull),
		.wrusedw(wrusedw)
	);

//generater clock
	initial wrclk = 1;
	always #(`wrclk_period/2)wrclk = ~wrclk;
	
	initial rdclk = 1;
	always #(`rdclk_period/2)rdclk = ~rdclk;
	
	integer i;

	initial begin
		data = 0;
		rdreq = 0;
		wrreq = 0;
		#(`wrclk_period*20 + 1);
		for (i=0;i <= 255 ;i = i + 1)begin
			wrreq = 1;
			data = i + 1024;
			#`wrclk_period;
		end
		wrreq = 0;
		#(`rdclk_period*20);
		for (i=0;i <= 511 ;i = i + 1)begin
			rdreq = 1;
			#`rdclk_period;
		end	
		rdreq = 0;
		#(`rdclk_period*20);
		$stop;	

	end

endmodule

4、查看结果(用16进制表示)

 

 5、查看RTL视图

 调用了一个IP核,就能实现混合宽度的异步FIFO


6.2 纯编程实现同步FIFO

6.2.1 顶层模块

//一、顶层模块
module my_scfifo(
//输入信号源
	input CLK,Rst,//时钟和异步复位
	input Write_En,Read_En,//RAM/.的读写控制
	input [7:0] Data_In,//输入数据
	output wire [7:0] Data_Out,//数据输出
	output wire [3:0] FCount,
	output Full,Empty

);
	//wire Full,Empty;//RAM的空、满状态
	wire [3:0] Addr_In,Addr_Out;//中间变量
	
	FIFO_control U1(//FIFO控制模块的例化
	//U1与my_scfifo的连接
	.clk(CLK),
	.rst(Rst),
	.write_en(Write_En),
	.read_en(Read_En),
	.RAM_Full(Full),
	.RAM_Empty(Empty),
	.Fcount(FCount),
	
	.write_ptr(Addr_In),
	.read_ptr(Addr_Out)
	);
	
	RAM_dual U2(//RAM存储模块的例化
	//U2与my_scfifo的连接
	.d(Data_In),
	.q(Data_Out),
	.write_en(Write_En),
	.read_en(Read_En),
	.write_clk(CLK),
	.read_clk(CLK),
	
	.addr_in(Addr_In),
	.addr_out(Addr_Out)
	);

endmodule

6.2.2 FIFO控制模块

//二、FIFO控制模块
module FIFO_control(clk,rst,write_en,read_en,
							RAM_Full,RAM_Empty,write_ptr,read_ptr,Fcount
);
	parameter RAM_WIDTH = 8;//数据位宽为8位
	parameter RAM_DEPTH = 16;//数据深度为16,表示最多可存储16个8位数据
	parameter RAM_PTR_WIDTH = 4;//16个数据对应4位地址线
	
	input clk,rst;//输入信号源
	input write_en,read_en;//数据写入、读出RAM
	
	output reg [RAM_PTR_WIDTH - 1 : 0] write_ptr,read_ptr;//读写指针
	output RAM_Full,RAM_Empty;//空满标志
	
	output reg [RAM_PTR_WIDTH - 1 : 0] Fcount;//通过Fcount(已存数据深度)的值来判断空、满状态
	//reg [RAM_WIDTH - 1:0] data_out;//8位数据输出,让其与Data_Out连接
	//reg [RAM_WIDTH - 1:0] RAM [RAM_DEPTH - 1 : 0];//定义了16*8bit的存储器
	
	//RAM状态信号:空、满状态
	assign RAM_Full = (Fcount == RAM_DEPTH);//当Fcount已存数据深度的值等于RAM的深度,则满
	assign RAM_Empty = (Fcount == 0);//当Fcount已存数据深度等于0,则无数据,表示空
	
	always@(posedge clk, posedge rst)
		begin
			if(rst)//异步复位
				begin
					//data_out <= 0;
					write_ptr <= 0;
					read_ptr <= 0;
					Fcount <= 0;
				end
				
				//①只写不读时:在写入控制有效,非满且不能读出
				else if(write_en && (!RAM_Full) && (!read_en))				
					begin
						write_ptr <= write_ptr + 1;//写指针+1,指向下一个空单元
						Fcount <= Fcount + 1;//已存数据深度+1
					end
					
				//②只读不写时:读操作执行:在读出控制有效,非空且这时不能写入数据					
				else if(!write_en && (!RAM_Empty) && (read_en))
					begin
						read_ptr <= read_ptr + 1;//读指针+1,
						Fcount <= Fcount - 1;//数据被读出,已存数据深度减少1个
					end
					
				//③空时又读又写:只有写操作有效
				else if(write_en && RAM_Empty && read_en)
					begin
						write_ptr <= write_ptr + 1;
						Fcount <= Fcount + 1;
					end
				
				//④满时又读又写:只有读操作有效
				else if(write_en && RAM_Full && read_en)
					begin
						read_ptr <= read_ptr + 1;
						Fcount <= Fcount - 1;
					end
				
				//⑤非空非满时又读又写:读写操作都有效,Fcount不变
				else if(write_en && read_en && (!RAM_Empty) && (!RAM_Full))
					begin
						write_ptr <= write_ptr + 1;
						read_ptr <= read_ptr + 1;
					end
		end
						
endmodule

 6.2.3 双端口RAM模块

//三、双端口RAM
module RAM_dual(

	input [7:0] d,//输入数据
	input [3:0] addr_in,//写入数据的地址
	input [3:0] addr_out,//读出数据的地址
	input write_en,read_en,//读、写使能控制信号
	input write_clk,read_clk,//读、写时钟信号
	
	output reg [7:0] q//输出数据	
);

	reg[7:0] memory [15:0];//16*8bits寄存器
	
	always@(posedge write_clk)//写入数据
		begin
			if(write_en) memory[addr_in] <= d;
		end
		
	always@(posedge read_clk)//读出数据
		begin
			if(read_en)	q <= memory[addr_out];
			else q <= 0;
		end

		
endmodule

 6.2.4 RTL视图

 6.2.5 TestBench代码

`timescale 1ns/1ns
module my_scfifo_tb;
	reg clk,rst;
	reg write,read;
	reg [7:0] data_in;
	
	wire [7:0] data_out;
	wire [3:0] fcount;
	wire full,empty;
	
	my_scfifo U1(
	.CLK(clk),
	.Rst(rst),
	.Write_En(write),
	.Read_En(read),
	.Data_In(data_in),
	
	.Data_Out(data_out),
	.Empty(empty),
	.Full(full),
	.FCount(fcount)
	);
	initial clk = 1;
	always #(5) clk = ~clk;
	integer i;
	
	initial
		begin
			rst = 1; #10;//复位
			rst = 0; #10;//复位撤销
			write = 1; read = 0; data_in=0; #10;//写入数据0
			write = 0; read = 1; #10;//读出数据
			read = 0; #10;//停止读出数据
			
			for(i = 0; i<=14; i = i+1)//连续写入16个数据
				begin
					write = 1;
					data_in = i;
					#10;
				end
			 write = 0;
			 
			 #10;
			 for(i = 0; i<=14; i = i+1)//连续读出16个数据
				begin
					read = 1;
					#10;
				end
			 read = 0;
			 
			 $stop;
		end
	
endmodule

 6.2.6 仿真结果


6.3 纯编程实现异步混合宽度FIFO

思路:在6.2的基础上,增加my_scfifo顶层模块中的读、写双时钟;输入数据8bit和输出数据4bit位宽进行差异化;

混合宽度如何实现,继续探索中...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cheeky_man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值