首页 > 其他分享 >用verilog/systemverilog 设计fifo (2)

用verilog/systemverilog 设计fifo (2)

时间:2024-06-22 18:21:06浏览次数:10  
标签:verilog clk fifo rd wr 指针 systemverilog 时钟

目录

异步fifo实现中要解决的问题

异步fifo和同步fifo功能相似,但是它的读写由两个时钟信号控制,所以它的设计和同步fifo不同,需要考虑更多的因素。
image

信号同步到那个时钟域

我们知道,写fifo和写地址更新肯定在写时钟域,也就是在wr_clk的时钟上升沿用以下代码进行更新。

always @ (posedge wr_clk or negedge wr_rst_n) begin
	if (!wr_rst_n)
		wr_ptr <= 0;
	else if (!full && wr_en)begin								//写使能有效且非满
		wr_ptr <= wr_ptr + 1'd1;
		fifo_buffer[wr_ptr_true] <= data_in;
	end	

同理,读fifo和读地址更新在读时钟域,也就是在rd_clk的时钟上升沿用以下代码进行更新。

always @ (posedge rd_clk or negedge rd_rst_n) begin
	if (!rd_rst_n)
		rd_ptr <= 'd0;
	else if (rd_en && !empty)begin								//读使能有效且非空
		data_out <= fifo_buffer[rd_ptr_true];
		rd_ptr <= rd_ptr + 1'd1;
	end

但是fifo为空和为满的判断要同时用到读地址和写地址,这个时候应该在写时钟域还是读时钟域来做这个判断呢?如果在写时钟域判断,读地址必须同步到写时钟域,如果在读时钟域判断,写地址必须同步到读时钟域。

  • 同步到写时钟域
    读指针同步到写时钟域需要时间T,在经过T时间后,可能原来的读指针会增加或者不变,也就是说同步后的读指针一定是小于等于原来的读指针的。写指针也可能发生变化,但是写指针本来就在这个时钟域,所以是不需要同步的,也就意味着进行对比的写指针就是真实的写指针。
    • 现在来进行写满的判断:也就是写指针超过了同步后的读指针一圈。但是原来的读指针是大于等于同步后的读指针的,所以实际上这个时候写指针其实是没有超过读指针一圈的,也就是说这种情况是“假写满”。“假写满”不会造成功能错误,只会造成性能损失,大不了FIFO的深度我少用一些。事实上这还可以算是某种程度上的保守设计。
    • 接着进行读空的判断:也就是同步后的读指针追上了写指针。但是原来的读指针是大于等于同步后的读指针的,所以实际上这个时候读指针实际上是超过了写指针。这种情况意味着已经发生了“读空”,却仍然有错误数据读出。所以这种情况会造成FIFO的功能错误。
  • 同步到读时钟域
    写指针同步到读时钟域需要时间T,在经过T时间后,可能原来的读指针会增加或者不变,也就是说同步后的写指针一定是小于等于原来的写指针的。读指针也可能发生变化,但是读指针本来就在这个时钟域,所以是不需要同步的,也就意味着进行对比的读指针就是真实的读指针。
    • 现在来进行写满的判断:也就是同步后的写指针超过了读指针一圈。但是原来的写指针是大于等于同步后的写指针的,所以实际上这个时候写指针已经超过了读指针不止一圈,这种情况意味着已经发生了“写满”,却仍然数据被覆盖写入。所以这种情况就造成了FIFO的功能错误。
    • 接着进行读空的判断:也就是读指针追上了同步后的指针。但是原来的写指针是大于等于同步后的写指针的,所以实际上这个时候读指针其实还没有追上写指针,也就是说这种情况是“假读空”。“假读空”不会造成功能错误,只会造成性能损失,大不了我先不读了,等数据多了再读就是的。事实上这还可以算是某种程度上的保守设计。

综合起来就是:
“写满”的判断:需要将读指针同步到写时钟域,再与写指针判断
“读空”的判断:需要将写指针同步到读时钟域,再与读指针判断

读写指针转化为格雷码

跨时钟域传输的一旦没处理好就会引起亚稳态问题,造成读写地址的值异常,从而引发FIFO的功能错误。那么应该如何将读写指针同步到对方的时钟域呢?
将二进制的读写地址转化成格雷码后再进行同步可以有效减少亚稳态问题。格雷码转化verilog实现可以参考
读写二进制地址转化为格雷码

格雷码表示的读写地址如何判断空满?

二进制表示的读写地址,我们可以扩展一位地址,用扩展的高位和其余位地址来比较。

  • 读写地址高位相同,其它位也相同,则fifo为空。
  • 读写地址高位不同,其它位相同,则fifo为满。

但格雷码的特点决定我们不能用这种方法。

10进制数 2进制数 典型格雷码
0 4'b0000 4'b0000
1 4'b0001 4'b0001
2 4'b0010 4'b0011
3 4'b0011 4'b0010
4 4'b0100 4'b0110
5 4'b0101 4'b0111
6 4'b0110 4'b0101
7 4'b0111 4'b0100
8 4'b1000 4'b1100
9 4'b1001 4'b1101
10 4'b1010 4'b1111
11 4'b1011 4'b1110
12 4'b1100 4'b1010
13 4'b1101 4'b1011
14 4'b1110 4'b1001
15 4'b1111 4'b1000

比如下面图中,左边是空,写地址和读地址相等,无论二进制码和格雷码都是如此。
右边是满,写地址绕了一圈回来,其值是11,其对应的格雷码是4'b1110, 读地址3对应的格雷码是4'b0010,可见其高两位相反,其余位相同。

总结起来用格雷码判断空满的方法就是:

  • 当最高位和次高位相同,其余位相同认为是读空
  • 当最高位和次高位不同,其余位相同认为是写满

image

异步fifo verilog代码

文件名称: code4_43.v

`timescale 1ns/1ns	
 
module async_fifo_tb;
  
	logic	wr_clk;				
	logic	wr_rst_n;       		
	logic	wr_en;       		
	logic	[7:0] data_in;       	
 
	logic	rd_clk;			
	logic	rd_rst_n;       	
	logic	rd_en;						                                        
	logic  [7:0] data_out;			
	logic   empty;	
	logic   full;  

	initial begin

    	$display("start a clock pulse");
    	$dumpfile("async_fifo.vcd"); 
    	$dumpvars(0, async_fifo_tb); 
   		#600 $finish;
	end
 

	async_fifo
		#(
			.DATA_WIDTH	(8),			//FIFO位宽
    		.DATA_DEPTH	(8)			//FIFO深度
		)
	async_fifo_inst(
		.wr_clk		(wr_clk		),
		.wr_rst_n	(wr_rst_n	),
		.wr_en		(wr_en		),
		.data_in	(data_in	),	
		.rd_clk		(rd_clk		),               
		.rd_rst_n	(rd_rst_n	),	
		.rd_en		(rd_en		),	
		.data_out	(data_out	),
	
		.empty		(empty		),		
		.full		(full		)
	);
 

initial begin
	rd_clk = 1'b0;					//初始时钟为0
	wr_clk = 1'b0;					//初始时钟为0
	wr_rst_n <= 1'b0;				//初始复位
	rd_rst_n <= 1'b0;				//初始复位
	wr_en <= 1'b0;
	rd_en <= 1'b0;	
	data_in <= 'd0;
	#5
	wr_rst_n <= 1'b1;				
	rd_rst_n <= 1'b1;					
//重复10次写操作
	repeat(10) begin
		@(negedge wr_clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//生成8位随机数
		end
	end
//拉低写使能	
	@(negedge wr_clk)	wr_en <= 1'b0;
	
//重复6次读操作,让FIFO读空 
	repeat(6) begin
		@(negedge rd_clk) rd_en <= 1'd1;		
	end
//拉低读使能
	@(negedge rd_clk) rd_en <= 1'd0;		
//再写2次
	repeat(2) begin
		@(negedge wr_clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//生成8位随机数
		end
	end
//持续同时对FIFO读
	@(negedge rd_clk)rd_en <= 1'b1;

//持续同时对FIFO写,写入数据为随机数据	
	forever begin
		@(negedge wr_clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//生成8位随机数
		end

	end
end
 

always #5 rd_clk = ~rd_clk;		

always #10 wr_clk = ~wr_clk;			
 
endmodule

//异步FIFO
module	async_fifo
#(
	parameter   DATA_WIDTH = 'd8,								
    parameter   DATA_DEPTH = 'd16							
)		
(		
//写数据		
	input	wire wr_clk,				
	input	wire wr_rst_n, 
	input	wire wr_en, 
	input	[DATA_WIDTH-1:0]		data_in,  
//读数据			
	input	wire rd_clk,
	input	wire rd_rst_n, 
	input	wire rd_en,				                                        
	output	logic	[DATA_WIDTH-1:0]	data_out,
//状态标志					
	output	logic	empty,	
	output	logic	full	
);                                                              
 

logic [DATA_WIDTH - 1 : 0]			fifo_buffer[DATA_DEPTH - 1 : 0];
	
logic [$clog2(DATA_DEPTH) : 0]		wr_ptr;		
logic [$clog2(DATA_DEPTH) : 0]		rd_ptr;	
logic	[$clog2(DATA_DEPTH) : 0]		rd_ptr_g_d1;				//读指针格雷码在写时钟域下同步1拍
logic	[$clog2(DATA_DEPTH) : 0]		rd_ptr_g_d2;				//读指针格雷码在写时钟域下同步2拍
logic	[$clog2(DATA_DEPTH) : 0]		wr_ptr_g_d1;				//写指针格雷码在读时钟域下同步1拍
logic	[$clog2(DATA_DEPTH) : 0]		wr_ptr_g_d2;				//写指针格雷码在读时钟域下同步2拍
	
//wire define
wire [$clog2(DATA_DEPTH) : 0]		wr_ptr_g;					//写地址指针,格雷码
wire [$clog2(DATA_DEPTH) : 0]		rd_ptr_g;					//读地址指针,格雷码
wire [$clog2(DATA_DEPTH) - 1 : 0]	wr_ptr_true;				//真实写地址指针,作为写ram的地址
wire [$clog2(DATA_DEPTH) - 1 : 0]	rd_ptr_true;				//真实读地址指针,作为读ram的地址
 
//地址指针从二进制转换成格雷码
assign 	wr_ptr_g = wr_ptr ^ (wr_ptr >> 1);					
assign 	rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
//读写RAM地址赋值
assign	wr_ptr_true = wr_ptr [$clog2(DATA_DEPTH) - 1 : 0];		//写RAM地址等于写指针的低DATA_DEPTH位(去除最高位)
assign	rd_ptr_true = rd_ptr [$clog2(DATA_DEPTH) - 1 : 0];		//读RAM地址等于读指针的低DATA_DEPTH位(去除最高位)
 
 
//写操作,更新写地址
always @ (posedge wr_clk or negedge wr_rst_n) begin
	if (!wr_rst_n)
		wr_ptr <= 0;
	else if (!full && wr_en)begin								//写使能有效且非满
		wr_ptr <= wr_ptr + 1'd1;
		fifo_buffer[wr_ptr_true] <= data_in;
	end	
end
//将读指针的格雷码同步到写时钟域,来判断是否写满
always @ (posedge wr_clk or negedge wr_rst_n) begin
	if (!wr_rst_n)begin
		rd_ptr_g_d1 <= 0;										//寄存1拍
		rd_ptr_g_d2 <= 0;										//寄存2拍
	end				
	else begin												
		rd_ptr_g_d1 <= rd_ptr_g;								//寄存1拍
		rd_ptr_g_d2 <= rd_ptr_g_d1;								//寄存2拍
	end	
end
//读操作,更新读地址
always @ (posedge rd_clk or negedge rd_rst_n) begin
	if (!rd_rst_n)
		rd_ptr <= 'd0;
	else if (rd_en && !empty)begin								//读使能有效且非空
		data_out <= fifo_buffer[rd_ptr_true];
		rd_ptr <= rd_ptr + 1'd1;
	end
end
//将写指针的格雷码同步到读时钟域,来判断是否读空
always @ (posedge rd_clk or negedge rd_rst_n) begin
	if (!rd_rst_n)begin
		wr_ptr_g_d1 <= 0;										//寄存1拍
		wr_ptr_g_d2 <= 0;										//寄存2拍
	end				
	else begin												
		wr_ptr_g_d1 <= wr_ptr_g;								//寄存1拍
		wr_ptr_g_d2 <= wr_ptr_g_d1;								//寄存2拍		
	end	
end

//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign	empty = ( wr_ptr_g_d2 == rd_ptr_g ) ? 1'b1 : 1'b0;
//高两位相反,其它位相同,FIFO被写满
assign	full  = ( wr_ptr_g == { ~(rd_ptr_g_d2[$clog2(DATA_DEPTH) : $clog2(DATA_DEPTH) - 1])
				,rd_ptr_g_d2[$clog2(DATA_DEPTH) - 2 : 0]})? 1'b1 : 1'b0;
endmodule

在vscode中,使用下面命令编译,运行代码,然后用gtkwave 打开波形文件:

iverilog -o myrun -g 2012 -s TestMem code4_43.v
vvp myrun
gtkwave async_fifo.vcd

image

  • 在wr_clk时钟域,从第2个时钟上升沿到第9个时钟上升沿,写入8个数据,fifo full
  • 在rd_clk时钟域,从第24个时钟上升沿到29个时钟上升沿,读取8个数据
  • 在wr_clk时钟域,从第16个时钟上升沿开始,在每个时钟上升沿写fifo
  • 在rd_clk时钟域,从第34个时钟上升沿开始,在每个时钟上升沿读fifo,由于rd_clk更快,所以它很快就会读空fifo,然后写时钟域每写一个,读时钟域就读一个。

标签:verilog,clk,fifo,rd,wr,指针,systemverilog,时钟
From: https://www.cnblogs.com/Hutuerdan/p/18249626

相关文章

  • 【Emacs Verilog mode保姆级的使用指南】
    ......
  • verilog 设计与综合实验报告(6)
    题目6:设计一个交通信号灯控制器1、设计方案输入为car车辆到来时为1,无车时为0,输出o_signal为交通信号灯,0001时为红灯,0010时为黄灯,0100时为绿灯,1000时为左拐灯,复位之后,交通灯在空闲状态,当车辆到来时交通灯到下一状态绿灯,等待40s,到下一状态左拐灯,等待15s,到黄灯,然后等待5s转到空......
  • FIFO in C
    /*fifo.c Description:ImplementsaFIFObufferLicense:RevisedBSDLicense,seeLICENSE.TXTfileincludeintheprojectMaintainer:MiguelLuisandGregoryCristian*/#include"fifo.h"staticuint16_tFifoNext(Fifo_t*fifo,uint16_tind......
  • verilog实现格雷码和二进制码的相互转换
    目录格雷码的介绍二进制码转化为格雷码格雷码转化为二进制码verilog实现代码格雷码的介绍在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(GrayCode),另外由于最大数与最小数之间也仅一位数不同,即“首尾相连”,因此又称循环码或反射码。在数字系统......
  • verilog 设计与综合实验报告(5)
    题目5:序列检测器1、设计方案2、程序代码modulesequential5_detector(inputi_clk, inputi_rst, inputi_seq, outputregout);parameteridle=5'b00000,s1=5'b00001,s2=5'b00010,s3=5'b00100,......
  • Verilog Hdl 计数器分频
    “分频”:是累加多个输入时钟信号clk_in的周期,最终使得,输出时钟信号clk_out的周期变大,频率变小。一、偶数分频例:计数器要实现6分频,输入时钟信号clk_in的6个周期要变成1个周期输出,输出6分频的输出时钟信号clk_out的半个周期占3个输入时钟信号clk_in的周期,相当于clk_out每次在3......
  • 【操作系统】pipe&mkfifo|管道详解
     ......
  • 深入解析 Cognex VisionPro 的 CogAcqFifoTool
    深入解析CognexVisionPro的CogAcqFifoTool在现代工业自动化和机器视觉领域,图像获取是实现各种视觉检测、识别和分析的第一步。而CognexVisionPro提供了一系列强大的工具,其中CogAcqFifoTool是专门用于图像获取的重要工具。本文将深入解析CogAcqFifoTool,帮助您了解其功......
  • ncverilog与finesim联合进行混合仿真的详细过程(以spice为顶层)
    第一步:Makefile仿真命令one:ncverilog+access+rwc+nc64bit+loadvpi=finesim.so:finesim_startup-frun.f第二步:环境结构(1)以模拟为顶层,顾名思义是把CDL网表中某一个模块替换为数字的function,其余全是CDL,以上图为例,把其中inv替换为数字的function。(2)需要文件:testben......
  • 用verilog/systemverilog 设计fifo (1)
    目录fifo的基本原理基于计数器的同步fifo实现(1)基于计数器的同步fifo实现(2)基于高位补偿法的fifo实现fifo的基本原理FIFO(firstinfirstout),即先进先出存储器,功能与数据结构中的队列相似。在IC设计中,FIFO常用来缓冲突发数据,流式数据与块数据的转换等等。比如上图中,在两个......