任意深度同步FIFO设计总结(非2次幂)

同步FIFO属于是最基本的模块之一了,但是对于任意深度(非2次幂与2次幂均可以)的同步FIFO可能有些难度,但是只要了解格雷码的性质就能解决。接下来将介绍整个设计的思考流程,不单单是给出解决方法。

对于任意深度的同步FIFO而已,最重要的一点就是计数,计算读写指针的计数,循环,匹配问题。因为要实现的是任意深度的设计,对于读写指针而言,利用格雷码的轴对称特性可以完成计数,类似于异步FIFO。

因为类似于异步FIFO,首先介绍在异步FIFO中指针是如何工作的。写指针指向需要写入的下一个地址,因此,当复位时,两个指针都指向0。在FIFO的写操作中,指针指向的地址被写入数据,然后指针会加一,指向下一个要写入的地址。
同样,读地址也指向下一个要读的地址。在复位时,两个指针都为0,FIFO此时为空,读指针指向一个无效数据(因为FIFO为空并且空标志位拉高)。一旦第一个数据写入FIFO,写指针增加,空标志位拉低,读指针依然在地址首位,并且立刻将第一个数据输出。将读指针指向下一个地址好处是不用两个周期来读出数据。如果接收者需要先将FIFO读指针拉高,然后再读出数据,就会浪费一个周期。
无论地址为空还是满,读指针和写指针都相同,那么如何区分呢?解决方法是加入额外1bit数据,当读指针或写指针到达FIFO最大值时,最高位bit翻转,当(n-1)bit相同,同时第一位相同时,FIFO为空;当(n-1)bit相同,同时第一位不同时,FIFO为满。
以上就是异步FIFO的指针工作情况。
接下来将介绍任意深度FIFO指针的设计思路。首先利用格雷码的对称性,希望达到的效果是深度为n,指针需要2n,以完成翻转的效果。因此,以深度为6的FIFO为例,可以实现对称性的情况为序列2-7,8-13,可以实现对称。以深度为5的FIFO为例,可以实现对称性的情况为序列3-7,8-12。我们需要设置初始地址和结束地址以实现翻转。
任意深度同步FIFO设计总结(非2次幂)_第1张图片

//设置起始、结束位置代码
parameter DEPTH = 6;
parameter ADDR = 3; //$clog2(6)= 3
reg [ADDR:0] start_count,end_count;//设置初始、结束指针地址
//对于任意深度都可以通过下面的代码设置初始值,通过改变
//DEPTH和ADDR控制
always@(posedge clk or rst_n)begin
	if(!rst_n)begin
			start_count <= {1'b1,{ADDR{1'B0}}} - DEPTH;//深度6:8-6=2//深度5:8-5=3
			end_count <= {1‘b0,{ADDR{1'B1}}	+ DEPTH;//深度6:7+6=13//深度5:7+5=12																																																																		
	end
	else begin
			start_count <= start_count;
			end_count <=end_count;
	end
end

设置好起始、结束指针后,接下来就需要考虑的是指针变化的情况了,与2次幂的FIFO不同的是需要加入起始、结束指针

input winc,rinc;//读写使能信号
reg [ADDR:0]    write_ptr;
reg [ADDR:0]    read_ptr;

// 设计读写指针跳转
//generate write pointer
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        write_ptr <= start_count;    
    end
    else if(winc && !wfull)begin
    	if(write_ptr == end_count)//到了结束指针,跳回起始指针
    		write_ptr <= start_count;
    	else 
        write_ptr <= write_ptr + 1;//不然正常计数
    end
    else 
    	 write_ptr <= write_ptr;//FIFO满或者没有读信号保持不变
end 
 
//generate read pointer
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)
        read_ptr <= start_count;
    else if(rinc && !rempty)begin
    	if(write_ptr == end_count)//到了结束指针,跳回起始指针
    		read_ptr <= start_count;
    	else 
        	read_ptr <= read_ptr + 1;
    end
    else 
    	read_ptr <= read_ptr;//FIFO满或者没有读信号保持不变
 end

这一部分和普通情况只添加了到结束地址跳回初始地址的操作。

最后就是控制格雷码的操作了,通过对指针跳转的观察情况,做了以下的分析。首先,因为是非2次幂的数据,设置了起始和终止位置,所以他返回一次的位置并不是正常的和2次幂深度一样的,依旧拿深度6举例,当完成一个循环时,它是从13跳回2的序列的,所以它并不能完全套用异步FIFO的二进制码转换格雷码的情况。首先需要先让二进制码变成正确的情况再进行转换.

//在这里还是利用了格雷码的对称性,因为从最高位1,跳回最高位0的情况,会改变DEPTH个数。而从最高位0,跳到最高位1的情况,则不会数据的改变。因此对所有最高位为0的情况,减去DEPTH。
wire [ADDR:0]    real_write_ptr,real_read_ptr;//先改变二进制码
wire [ADDR:0]    gray_wr_ptr,gray_rd_ptr;//二进制转格雷码
wire full_o,emtpy ;
//改变二进制码
assign real_write_ptr = write_ptr[ADDR]?write_ptr:(write_ptr -start_count);
assign real_read_ptr = read_ptr[ADDR]?read_ptr:(read_ptr -start_count);

//二进制转格雷码
assign gray_rd_ptr = real_read_ptr ^ (real_read_ptr >> 1'b1);
assign gray_wr_ptr = real_write_ptr ^ (real_write_ptr >> 1'b1);

//格雷码的比较,满信号为读写指针最高位和次高位不同,其余位相同,空信号为读写指针相同
assign full_o  = (gray_wr_ptr == {~gray_rd_ptr[ADDR:ADDR-1],gray_rd_ptr[PTR_WIDTH-2:0]})? 1'b1 : 1'b0;
assign empty_o = (gray_rd_ptr == gray_wr_ptr)? 1'b1 : 1'b0;

//读写数据
parameter WIDTH = 8;//数据位宽
reg [WIDTH-1:0] ram_mem [0:DEPTH-1];
always_ff@(posedge wclk)begin
    if(wenc)
        ram_mem[real_write_ptr[ADDR-1:0]] <= wdata;
end
 
always_ff@(posedge rclk)begin
    if(renc)
        rdata <= ram_mem[real_read_ptr[ADDR-1:0]];
end

以上就是非2次幂同步FIFO(2次幂同样适用)的设计思路。
使用UVM构建验证平台,放在接下来对UVM进行总结时作为例子使用,这里不进行赘述。

你可能感兴趣的:(verilog)