FIFO的工作原理及其设计

Posted CuteBaBaKiller

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FIFO的工作原理及其设计相关的知识,希望对你有一定的参考价值。

1.简介

        FIFO( First Input First Output)简单说就是指先进先出。FIFO存储器是一个先入先出的双口缓冲器,即第一个进入其内的数据第一个被移出,其中一个口是存储器的输入口,另一个口是存储器的输出口。

        对于单片FIFO来说,主要有两种结构:触发导向结构和零导向传输结构。触发导向传输结构的FIFO是由寄存器阵列构成的,零导向传输结构的FIFO是由具有读和写地址指针的双口RAM构成。

        FIFO与普通RAM存储器的区别是没有外部读写地址线(指针),使用方便,但缺点是只能顺序写入数据和读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

1.1.功能

        FIFO存储器是系统的缓冲环节,主要有几方面的功能:

                1)对连续的数据流进行缓存,防止在进机和存储操作时丢失数据;

                2)数据集中起来进行进栈和存储,可避免频繁的总线操作,减轻CPU的负担;

                3)允许系统进行DMA操作,提高数据的传输速度。这是至关重要的一点,如果不采用DMA操作,数据传输将达不到传输要求,而且大大增加CPU的负担,无法同时完成数据的存储工作。

1.2.用途

1.2.1.跨时钟域多bit数据传输

        解决一个系统多个时钟所带来的问题:异步时钟之间的接口电路。异步FIFO是解决这个问题的一种便捷简单的方案,使用异步FIFO可以在两个不同时钟系统之间快速方便地传输实时数据。

1.2.2.达到数据匹配问题(读写位宽不一致)

        对于不同宽度的数据接口也可以使用FIFO,例如单片机的8位输出而DSP可能是16位输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。

1.3.主要参数

  • 宽度(WIDTH):FIFO每个地址的数据位宽(W);
  • 深度(DEEPTH):FIFO可以存储多少个W位的数据;
  • 满(full)标志:FIFO已满或将满时,会输出一个对写操作的反压信号,以阻止被继续写入数据而溢出;
  • 空(empty)标志:FIFO已空或将空时,会输出一个对读操作的反压信号,以避免被继续读出无效数据;
  • 读/写时钟:读/写操作所遵循的时钟,每个时钟沿触发。

        根据FIFO工作的时钟域分为同步/异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟在时钟沿来临时同时发生读写。异步FIFO读写时钟不一致,读写相互独立。

        读写指针即读写地址,当前读/写操作完成后,指针自动加一指向下一个地址(连续递增)。

  • 写指针:总是指向下一个将要被写入的地址,复位时指向编号0的地址;
  • 读指针:总是指向下一个将要被读出的数据地址,复位时也指向编号0的地址且此时数据无效;

2.工作原理

2.1.空满标志

2.1.1.读空信号(rd_empty)

        一般情况下当读写指针相等时,表明FIFO已空,这种情况发生在复位操作时或当读指针读出FIFO中最后一个有效数据时(即读指针追赶上写指针),此时读空信号有效,如下左图:

2.1.2.写满信号(wr_full)

        当读写指针再次相等时,即写指针转了一圈又折回来(wrapped around)从起始低位追上了读指针(写比读快),此时表明FIFO已满,如上右图:

2.2.空满判断机制

2.2.1.同步fifo空满判断

  • 方案一:extra bit

                深度为的FIFO其地址位宽为n,若数据位宽为W则该FIFO的容量为N*W bits。

                现在在指针添加1个extra bit即地址的MSB,使其变为n+1 bits,该extra bit用来指示读/写指针是否连续递增并越过了FIFO的最后一个地址,若越过则该MSB加1,其他位清零。例如深度为8的fifo,需要采用1+3bits的地址位宽,MSB作为指针折回标志,低3bits作为地址。

                那么判断机制读指针读出FIFO最后一个有效数据后即会停止递增)为:

                ①如果两个指针的MSB不同,就说明写指针比读指针多折回一次,此时若除开MSB以外的地址位相等,则表示FIFO已满;

                ②如果两个指针的MSB相同,就说明读写指针的折回次数相同,若其他地址位相等,则表示读写指针完全相等,FIFO已空。

  • 方案二:设置数据计数器

                设置一个data_counter,当写使能有效时数据计数器加1,每读出一个数据时该计数器又减1。如此,当data_counter=0时FIFO为空,data_counter=FIFO深度时表明已满。

                缺点:计数器会占用额外资源,当FIFO较大时,可能会降低FIFO的读写速度。

2.2.2.异步fifo空满判断

  • 判断步骤如下:

                ①地址指针采用二进制(binary)+extra bit

                ②二进制指针转gray码后跨时钟域同步做比较

        当读写指针采用二进制表示且读写操作属于异步时钟时,读写指针做比较前需要先将其中一个指针同步到另一个指针的时钟域后再操作,直接同步这样容易产生亚稳态问题。

        可以使用一个二进制转gray码的转换电路,将地址转换为对应的gray码后再同步到另一个时钟域,进行对比产生空满指示,如左下图:

                例如1+3bits的二进制地址完全转换为gray后如右上图所示,此时空满标志不能按照原来二进制的方法来判断,gary码指针的空满判断标准如下:

                空标志:gray码地址完全相等(包括MSB)。

                满标志:高两位(MSB+次高位)不同,其余各位相同。

                PS:二进制与格雷码互相转换 。

  • 补充:

                ①同步方向产生保守的空满机制:

                        读指针同步到写时钟域:经过一定的同步时间后,此时同步后(写时钟域的)读指针小于或等于真实的(读时钟域)的读指针,而写指针是即时且真实的,空满判断机制可产生保守的“假写满”(正确且安全设计)和错误的“读空”。

                        反之同理,总结:写时钟域产生正确的“假写满”,读时钟域产生正确的“假读空”

                ②由于读写异步快时钟域同步慢时钟域指针可能会漏采,不会影响空满判断逻辑:

                        举例读慢写快:写指针同步到读时钟域发生漏采,即读时钟域采样到的写指针小于真实的(写时钟域的)写指针,此时不会导致“读空判断逻辑”错误,也是保守且正确的。反之亦然。

3.FIFO代码设计示例

3.1.同步FIFO代码

        同步FIFO由于没有跨时钟的操作,所以只需要使用二进制即可,不用格雷码操作。根据上面的分析,有两种方法进行表示full/empty状态,代码如下:

//1、generate full/empty signal by addr
assign full = (waddr_ptr == ~raddr_ptr[ADDR_WIDTH-1],
                              radde_ptr[ADDR_WIDTH-2:0]);
assign empty = (waddr_ptr == raddr_ptr);
//2、generate full/empty signal by conuter
always @(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		data_cnt <= ADDR_WIDTH1'b0;
	end
	else if(wen && ren && !full && !empty)begin
		data_cnt <= data_cnt;
	end
	else if(wen && !full)begin
		data_cnt <= data_cnt + 1'b1;
	end
	else if(ren && !empty)begin
		data_cnt <= data_cnt - 1'b1;
	end
end

assign full = (data_cnt == FIFO_DEPTH);
assign empty = (data_cnt == 0);

3.2.异步FIFO代码

        由于存在读写时钟不同步的问题,采用的解决方法是:加两级寄存器同步 + 格雷码(目的都是消除亚稳态),代码示例如下: 

`timescale 1ns/1ps
module async_fifo #(
	parameter	DATA_WIDTH	= 32,
	parameter	DATA_DEPTH	=  8,
	parameter	PTR_WIDTH	= $clog2(DATA_DEPTH)
)(
	//write interface	
	input  wire					 clk_wr_i,
	input  wire					 rst_n_wr_i,	
	input  wire					 wr_en_i,
	input  wire [DATA_WIDTH-1:0] wr_data_i ,
	output wire					 wr_full_o,
	
	//read interface
	input  wire					 clk_rd_i,
	input  wire					 rst_n_rd_i,
	input  wire					 rd_en_i,
	output reg  [DATA_WIDTH-1:0] rd_data_o,
	output wire					 rd_empty_o
);
	reg  [DATA_WIDTH-1:0]	fifo[DATA_DEPTH-1:0];

    reg                     wr_ptr_ext;
	reg	 [ PTR_WIDTH-1:0]	wr_ptr;
	wire [ PTR_WIDTH  :0]	wr_ptr_gray;
	reg	 [ PTR_WIDTH  :0]	wr_ptr_gray_d1;
	reg	 [ PTR_WIDTH  :0]	wr_ptr_gray_d2;

	reg                     rd_ptr_ext;
	reg  [ PTR_WIDTH-1:0]	rd_ptr;
	wire [ PTR_WIDTH  :0]	rd_ptr_gray;
	reg	 [ PTR_WIDTH  :0]	rd_ptr_gray_d1;
	reg  [ PTR_WIDTH  :0]	rd_ptr_gray_d2;
	
//------------- ptr++ and data inout--------------
always @(posedge clk_wr_i or negedge rst_n_wr_i) begin
	if(!rst_n_wr_i)begin
		wr_ptr_ext, wr_ptr <= (PTR_WIDTH+1)1'b0;
        fifo[wr_ptr]         <=  (DATA_WIDTH)1'b0;
    end
	else if(wr_en_i && !wr_full_o)begin
		wr_ptr_ext, wr_ptr <= wr_ptr_ext, wr_ptr + 1'b1;
        fifo[wr_ptr]         <= wr_data_i;
    end
end

always @(posedge clk_rd_i or negedge rst_n_rd_i) begin
	if(!rst_n_rd_i)begin
		rd_ptr_ext, rd_ptr <= (PTR_WIDTH+1)1'b0;
		rd_data_o            <=  (DATA_WIDTH)1'b0;
    end
	else if(rd_en_i && !rd_empty_o)begin
		rd_ptr_ext, rd_ptr <= rd_ptr_ext, rd_ptr + 1'b1;
		rd_data_o            <= fifo[rd_ptr];
    end
end
	
//--------- binary to gray ---------
assign rd_ptr_gray = rd_ptr_ext, rd_ptr ^ (rd_ptr_ext, rd_ptr>>1);
assign wr_ptr_gray = wr_ptr_ext, wr_ptr ^ (wr_ptr_ext, wr_ptr>>1);

//--------- pointer sync -----------
always @(posedge clk_wr_i or negedge rst_n_wr_i) begin
	if(!rst_n_rd_i) begin
		rd_ptr_gray_d1 <= (PTR_WIDTH+1)1'b0;
		rd_ptr_gray_d2 <= (PTR_WIDTH+1)1'b0;
	end
	else begin
		rd_ptr_gray_d1 <= rd_ptr_gray;
		rd_ptr_gray_d2 <= rd_ptr_gray_d1;
	end
end

always @(posedge clk_rd_i or negedge rst_n_rd_i) begin
	if(!rst_n_rd_i) begin
		wr_ptr_gray_d1 <= (PTR_WIDTH+1)1'b0;
		wr_ptr_gray_d2 <= (PTR_WIDTH+1)1'b0;
	end
	else begin
		wr_ptr_gray_d1 <= wr_ptr_gray;
		wr_ptr_gray_d2 <= wr_ptr_gray_d1;
	end
end

//------------ full_o and empty_o ------------------
assign wr_full_o  = (wr_ptr_gray==~rd_ptr_gray_d2[PTR_WIDTH:PTR_WIDTH-1],
                     rd_ptr_gray_d2[PTR_WIDTH-2:0]) ? 1'b1 : 1'b0;
assign rd_empty_o = (rd_ptr_gray==wr_ptr_gray_d2) ? 1'b1 : 1'b0;

endmodule

4.FIFO的深度计算

4.1.概念

        突发(burst)传输:In telecommunication, a burst transmission or data burst is the broadcast of a relatively high-bandwidth transmission over a short period。某个短时间内相对高带宽的数据传输。

        假如模块A不间断地往FIFO中写数据,模块B同样不间断地从FIFO中读数据,不同的是模块A写数据的时钟频率要大于模块B读数据的时钟频率,那么在一段时间内总是有一些数据没来得及被读走,如果系统一直在工作,那么那些没有被读走的数据会越累积越多,那么FIFO的深度需要是无穷大的,因此只有在突发数据传输过程中讨论FIFO深度才是有意义的。一次传递一包数据完成后再去传递下一包数据,一段时间内传递的数据个数称为burst length

        FIFO的最小深度与burst rate, burst size, read and write frequency等因素有关。要确定FIFO的深度,关键在于计算出在突发读写这段时间内有多少个数据没有被读走,即FIFO的最小深度就等于没有被读走的数据个数。

4.2.深度计算示例

        假定模块A向FIFO写数据的时钟频率为fa,模块B从FIFO读数据的时钟频率为fb。

场景1:idle cycles in both write and(or) read

假设:

  • 写数据时钟频率fa=80MHz,读数据时钟频率fb=50MHz;
  • 突发长度= number of data to be transferred = 120;
  • 每隔1个cycle写一次,每隔3个cycle读一次。

那么:

  • 每隔1个cycle写一次,意味着2个cycle才写一个数据;每隔3个cycle读一次,意味着4个cycle才读一个数据。
  • 写一个数据所需要的时间 = 2*1/80MHz = 25ns。突发传输中,写完所有数据所需要的时间 = 120*25ns = 3000ns。
  • 读一个数据所需要的时间 = 4*1/50MHz = 80ns。在3000ns内能够读走的数据个数 = 3000ns/80ns = 37.5。
  • 所以在3000ns内还没有被读走的数据个数 = 120-37.5 = 82.5,因此FIFO的最小深度为83。

场景2fa ≤ fb with no idle cycles in both write and read

假设:

  • 写数据时钟频率fa=40MHz,读数据时钟频率fb≥40MHz
  • 突发长度= number of data to be transferred = 120
  • 在突发传输过程中,数据都是连续读写的

由于读数据比写数据要快,因此FIFO只起到跨时钟域的作用,FIFO的最小深度为1即可。

场景3:Data rates are given,read and write random

        在工程设计中还存在一种情形,只给出数据在一段时间内的读写速率,怎么读写完全随机,这种情况需要考虑最坏的一种情况避免数据丢失。在最坏的情形中,读写的速率应该相差最大,也就是说需要找出最大的写速率和最小的读速率。

假设:

  • 写数据时钟频率fa=80MHz,读数据时钟频率fb=50MHz
  • 在写时钟周期内,每100个周期就有40个数据写入FIFO
  • 在读时钟周期内,每10个周期可以有8个数据读出FIFO

那么:

  • 首先没有给出数据的突发长度,从假设中可以得出每100个周期就有40个数据写入FIFO,因为数据是随机写入FIFO的,需要考虑做坏的情形,即写速率最大的情形,只有如下图背靠背的情形才是写速率最高的情形,burst length为80。

  • 注意:这里需要验证一下是否有解即写入burst数据时间必须大于等于读出burst数据时间,不然数据就会越累积越多,使得FIFO的深度必须为无穷大。首先写入80个数据需要的时间 = 1/80MHz*(80*100/40)=2500ns,读出80个数据需要的时间 = 1/50MHz*(80*10/8)=2000ns,由于写入burst数据时间大于对出burst数据时间,因此有解。
  • 下面来计算FIFO最小深度,连续写入80个数据最快所需要时间 = 1/80MHz * 80 = 1000ns。
  • 从FIFO中读出一个数据至少所需时间 = (1/50MHz) * (10/8) = 25ns。那么在1000ns内能够读出的数据 = 1000ns/25ns = 40。
  • 在1000ns内没有读出的数据 = 80 - 40 = 40,因此FIFO的最小深度为40。

         参考FIFO深度计算

4.3.异步FIFO的深度不为2的正整数次幂

4.3.1.FIFO的深度为1

        方案一:将深度加1变为2,这样地址指针用1bit表示即可,且不用添加extra bit。

         方案二:采用脉冲同步读写信号,参考深度为1的异步FIFO设计

        方案三:采用握手机制去跨时钟,不属于FIFO类型。

4.3.2.FIFO的深度为其他任意数

        无论FIFO的深度为奇数或偶数,都需要对地址指针扩展1bit来作为标志位,这样产生的地址指针循环一定为偶数,再利用格雷码的环回对称性,采用“掐头去尾+地址偏移”的方法。

        地址同步后不能采用原来2的整数次幂的gray判断空满机制来做判断,此时可以将格雷码再转回二进制后再做空满判断。

        参考任意深度异步FIFO设计

5.读写位宽不一致问题

        对于异步fifo,由于地址不能跳变,fifo的位宽可以选择输入输出位宽的最小公倍数,会有一定的保守性。

        参考FPGA之FIFO详解,读写位宽不同

UART工作原理详解

目录

概述

发展过程

硬件定义(DP9-RS232)

工作原理(DP9-RS232)

起始位、停止位、校验位(DP9-RS232)

速率计算

波特率

FIFO

FIFO存储器

FIFO的概念

超时设计

FIFO缓冲区的清空

FIFO 输入与输出中断

FIFO缓冲区取数据时的原理

UART FIFO

USART


概述

UART全拼Universal Asynchronous Receiver/Transmitter,即通用异步收发传输器

发展过程

在个人计算机诞生之前就已经存在了串口设备,如电传打字机,工控测量设备,通信调制解调器(现在的路由器),最初的串口就是用一根线直连另外一方,一方发一方收,后来厂商为了完善自己的串口又增加了一根线用于做信号位,主要用于判断流是否可用,同时当时的厂商们的设备不同,芯片的工作频率也不同以及电平信号也不同,导致厂商的设备只能跟自己通讯,没有一个统一的标准非常混乱,后来由无线电制造商协会(Radio Manufacturers' Association:RMA,现:美国电子工业协会(EIA))联合贝尔实验室在1970年代一起制定了一个标准,即历史上第一个通用串口协议标准:RS-232,它规定了波特率(即芯片工作频率),数据线,接受线,流控制线的接法,采用DB25针串口,支持异步。

支持的波特率有:50b/s、75b/s、110b/s、150b/s、300b/s、600b/s、1200b/s、2400b/s、4800b/s、9600b/s、19200b/s

115200这个速率最初是没有的,是后来才加上去的,因为人们发现9600太慢,19200频率又太高,频率越高功耗越高,所以推出一个115200这个合适的频率。

最后随着个人电脑的出现,个人电脑最初的上的接口较小,PCB板子空间也很小,DB25较大,EIA将其中保留的一些针位去除了,形成了后来的DB9针串口,协议依然是RS-232。

由于DB9接口只定义了信号量,但是没有定义与每个引脚的直接关系,导致当时的厂商需要自己去定义每个引脚的作用,这就导致了不同厂商每个引脚的作用不一样,但是功能是一样的,无法相互接在一起,最初有一个比较知名的公司:IBM,它规定了一种定义,后来大多数厂商为了统一都采用IBM的定义。

所以最终IBM成为了PC行业里DB9串口引脚关系的工业标准。

在这一刻UART才叫UART,这里的U即是通用的意思,最初叫ART之类的,因为它没有一个统一的标准,后来IBM统一了之后,才有了U,不同厂商之间的设备可以通过串口进行通讯了。

硬件定义(DP9-RS232)

工作原理(DP9-RS232)

DTR由计算机控制,当为高电平时即告诉目标设备,我已经准备好了,可以给我发送数据了

DSR由目标设备控制,当为高电平时即目标设备告诉计算机,我也可以发送数据了

RTS由计算机控制,告诉目标设备给我发送数据,此时目标设备需要立即给计算机发送数据

CTS由目标设备控制,告诉计算机给目标设备发送数据

DCD是由计算机控制,计算机需要拉高它来告诉目标设备我还在线

在RS232里要求每个字节只能一次传输一个BIT位,所以就需要一个起始位与停止位,为了确保正确性还会有个校验位,这些会在后面说,当将对应的线调整好了之后,需要TXD来确认位的开始与停止

起始位、停止位、校验位(DP9-RS232)

起始位(Start Bit)

发送器是通过发送起始位而开始一个字符传送,起始位使数据线处于逻辑0状态,提示接受器数据传输即将开始,当设定好起始位后,如起始信号为01,那么串口内部时钟开始周期性的采样,如串口内部使用8个时钟周期作为一个采样周期的因子,则每4个周期进行一次采样,如当第4个时钟周期的时候采样为0那么会记到标志寄存器中,当第8个周期采样为1则视为起始信号,这样一个周期就完成了,现在的起始信号一般都不能设置,都采用默认的,即默认的两个低电平,以16个时钟周期为采样因子,每8个时钟周期采一次,若两次之后都为低电平则视为起始信号

起始位为两个低电平00,因为RS232规定要求空闲时电平必须是1,即高电平

数据位(Data Bits)

起始位之后就是传送数据位。数据位一般为8位一个字节的数据(也有6位、7位的情况),低位(LSB)在前,高位(MSB)在后,无法修改高低位的传输优先级

停止位(Stop Bit)

最后一个BIT位,一般为1位,可以设置,一般设置为1或2,最常用的是1,它与数据位相接,当双方通讯协议一致后(停止位数据位都是一样的),当读取完数据位之后在读取一位是高电平的1时则认为收到了停止位,则代表一个数据的结束

校验位(parity Bit)

这是一个比较特殊的位,它使用奇偶的方式校验数据位是否正确,它通常是不用的,因为它属于一个对应的校验控制器来完成,总耗时需要1s,所以如果要添加它的话意味着你每次传输至少要在1s以上,它用来确认数据位上的BIT为1的数量是奇数还是偶数,是一个最简单的状态位,因为传输无法避免干扰等情况,在一些场景恶劣的环境下这个位是非常有用的,当BIT为1的数量为偶数时,校验位为1,为奇数时为0,接收方需要确认这个位与自己实际接收到的是否一致,不一致则可以要求目标方重发,它无法矫正数据,因为它无法确认哪个位有错。

小插曲

现在市面上流行的路由器有一个闭源的算法,就是能够将包的30%损坏修复,类型QR二维码的原理,有兴趣可以研究一下,QR二维码里有一段数据是放矫正信息的,当二维码有一部分无法识别时,会从这个矫正信息里在用特定的算法计算出原始信息,所以我们平时在扫二维码时你可以遮住一小部分,会发现还是可以识别出来,有些工业二维码修复能力更强,因为工业环境很容易出现油漆,污渍等其它遮挡物,遮挡住。

传输过程

如现在要传输“OK”两个字符,O对应的二进制数据为01001111 ,K对应的二进制数据为01001011,传输过程如下:

以下过程以RS-232起始信号为标准,数据位为8位,停止位为1位,没有校验位

速率计算

如果想计算一个BIT在当前波特率下每次传输需要耗时多久可以用倒数的方式计算。

如当前的波特率为115200

计算公式:

1/115200=868ns,如果加了奇偶校验位别忘记在加上1s的耗时

即每868ns传输1bit。

波特率

波特率与时钟频率不同,串口允许两个时钟频率不同的设备进行通讯,但要求波特率一致,波特率即你当前的芯片在一定周期内传输BIT的数量,这个波特率就是一定周期,因为两个设备之间的时钟频率不同,一方快,另外一方可能慢,那么就协商一个固定周期,在这个固定周期里只能传输多少BIT位,且这个固定周期里是两个设备的时钟频率都能达到的,如A设备的时钟频率是80hz,B设备的时钟频率是120hz,明显B设备快于A设备,如果不协商明显是无法通讯的,因为不在一个时间线上,则这里进行一次协商,则告诉AB设备我不管你们的时钟频率是快也好,慢也罢,我要求你们在100us里传输8个10个bit位,其中每10US传输一个BIT,那么此时A设备比较慢,但是它的频率也刚好能够达标每10US传递一个BIT,而B设备呢由于比较快,它10us甚至可以传输两个BIT,但是快也没用,因为协商好了规定时间,所以B设备每次到10毫秒时就传输一次,剩下时间则等待时间的到来,这个在芯片内部其实是可以根据设置当前工作时钟频率来完成,如当前波特率要求115200,它每86.8us传输一个bit位,那么只需要将当前工作时钟频率设置为每86.8us为一个周期即可与波特率对应起来。

这样就完成了即便两个设备频率不同也能完成一次正常的通讯,这里需要明确波特率与时钟频率不同,实现波特率的方法不止设置时钟频率一种,也有另外一种方法就是外设时钟,如芯片内部有一个外设时钟,通过设置这个外设时钟的工作频率和中断来完成这个工作,设置外设时钟的好处在于芯片可以做别的事情,等时钟中断来了去发一次数据或接一次数据就可以了。

FIFO

FIFO存储器

在了解UART FIFO功能先说一下FIFO存储器,UART的功能就是基于FIFO存储器

FIFO的概念

随着芯片性能的提升的同时通讯速率也随之提高,因传输率的问题,A设备的传输率要快于B设备的处理能力时就会产生数据流过多的情况,导致B设备的芯片无法及时处理这些数据就会导致部分数据丢失,为了解决这个问题提出了一种存储模式,即FIFO,FIFO全拼是First-In First-Out 即先进先出,就是涉及一个存储器用来临时保存这些数据,然后芯片不再从数据寄存器里去取数据而是在FIFO存储器里去取数据,因为芯片拿到数据后要进行一个处理,这个处理周期可能比较耗时,这就导致外面有新的数据来了没能及时处理,这些数据又被新的数据给覆盖掉了,导致数据的丢失,因为在串口的设计中只有一个数据寄存器,一般是8位寄存器,只能放下一个字节,当又来一个字节串口芯片就会将新的字节填充到数据寄存器里去,它不会等待CPU将数据取走后才填充,因为这样会导致丢失新的数据。

FIFO的出现就是为了解决上述丢包的问题,当数据来了之后串口芯片将数据丢到FIFO的缓冲区里,通过设置最大深度字节来产生一次中断告诉CPU来取走它们,这样就解决了当CPU工作较忙无法取数据时产生丢失的问题,FIFO缓冲区多大取决于你当前芯片的设计,这些可以在你的芯片手册上看到

同时FIFO还提供了一个功能,填充超时,因为有的时候在传输时设备可能会出现不定时的包,如A与B之间传输数据,B设备开启了FIFO功能,并设置填充超出10个字节产生一次中断告知CPU取走数据,但是由于A设备出现了一些问题,暂时不给B设备发数据了,那B设备里的一些数据就永远无法产生中断,那么就有了一个填充超时的中断,即如果特定的时间里没有将FIFO缓冲区填充到设定的指定深度时就会产生这个中断,告诉CPU填充超时了,来处理一下这些没有填满的数据

以下是超时中断的处理流程图

超时设计

这里的超时设计有点特殊,FIFO的超时是指间隔,因为有时候可能是A设备数据发慢了或者其它原因堵塞了导致的,所以FIFO每次收到数据之后会有一个时间间隔,比如我们设置为3,那么当收到一个数据后超过3个周期收到周期没有收到数据,就会产生超时中断,若恰好2个周期数据来了,那么这个周期计数就会被清空置0,这里设置的方式也不是直接设毫秒的,设置是以1分之几为单位,如当前FIFO缓冲区大小是12字节,设置1/8字节为填满字节,1/3为超时间隔,即8个字节产生填满中断,超过3个接收字节时间间隔没有收到数据产生超时中断

有的MCU里的FIFO存储器没有超时中断的功能,用户可以自己写一个,使用自己内部时钟或者写一段时间代码,来定时将RXIFLSEL与TXIFLSEL标志位置1产生中断,这两个位是用于输入与输出中断的。

FIFO缓冲区的清空

FIFO在设计时是不需要用户去清空缓冲区的,我们只需要读就可以了,FIFO是以填充的形式的去覆盖缓冲区的,FIFO自己也不会去清空缓冲区,它只会覆盖缓冲区,通过用户设定的大小将新数据填充到缓冲区里然后产生中断,这样旧数据就被新数据替代也省去一个时间周期来清空缓冲区,那么这就产生一个问题,就是接收时,出现了超时的情况,如我们预设是8个字节为填满,在经过上一次工作后新数据还没有填满8个,只填了4个,那么此时产生超时中断,也就意味着其中4个是新数据剩下4个是旧的没有填充的数据,所以这里需要注意一下

FIFO 输入与输出中断

输入的中断条件是缓冲区里的数据达到用户设置的长度后产生中断告知用户来取走

输出的中断条件是缓冲区里的数据达到用户设置的长度并发出去后产生中断

FIFO缓冲区取数据时的原理

FIFO去取数据时不能指定地址位且只能一次取一个字节,不能取指定地址上的字节,每次产生输入中断取数据时FIFO里有个地址寄存器,当我们每次去取一次以后,这个地址位自动加1,需要用户手动判断是否取完然后清空标志位

其它

一般情况下我们是不会用FIFO的因为UART是低速通讯方式,它每次只传一个BIT在波特率的速率计算下其实速度是很慢的

FIFO在对一些一个字节就要求有响应的一些工作条件下是不建议使用的

UART FIFO

跟上面说的一样,UART这里也只不过将FIFO存储器设计到了UART串口芯片里去了,会提供对应的寄存器让你去配置FIFO存储器,也提供了对应的标志位用于表示当FIFO存储器的当前状态,如TX Ready与RX Ready位用于表示输入与输出缓冲区里是否有数据,当完成一次中断后它俩会被清0,当有新数据覆盖时才会置1,RXIFLSEL与TXIFLSEL标志位用于表示输入与输出缓冲区里的数据长度是否达到用户设置的长度,这两个位会置1并产生中断,这个位不是自动清空的,是需要用户手动去置0,如果不置0会无限进入中断

FIFO缓冲区是有最大字节数的,这个具体要看你的MCU手册里UART这块针对FIFO的设置,UART若支持FIFO功能则会有对应的FIFO寄存器用于控制与开启即nofifo模式与fifo模式

FIFO有两种中断,一种是填满深度中断,一种是超时中断,这两个参数都需要手动设置,第一个中断即需要设置好当前FIFO的深度,如设置为8字节,当FIFO缓冲区填满为8个字节时则响应中断

FIFO针对发送与接收都有对应的缓冲区,当缓冲区达到标准时会将对应的中断标志位置1

 

USART

USART是支持同步的一种串口模式,它基于UART,全名是:Universal Synchronous/Asynchronous Receiver/Transmitter(通用同步/异步串行接收/发送器)

这里说一下同步、异步,半双工与全双工的意思

异步

异步是一种通讯方式,是指不需要知道何时开始通讯,也不需要知道目标方的时钟频率是多少,只需要一个起始信号,来告诉别人开始通讯了,即异步操作,不需要另外一方一直等着或者每隔一段时间来一次通讯,另外一方只需要在一个周期里进行采样,当采样到了起始信号则开始通讯

半双工

即一个线可以传也可以收,但是不能一边传一边收

全双工

即两根线,一根线穿,一根线收,UART就是异步全双工,可以同时传也可以同时收

同步

同步要求两根线在传输时必须保证数据的一致性,异步是不考虑这个的,异步是可以一边发一边收,它不管发送的与接收的数据是否具有一致性,异步是非阻塞的,而同步是阻塞的,它要求数据发送之后在没有接收到数据之前是不会进行下次通讯的

即同步要求发送与接收都完成之后才开学下一次传输,保证两根线是同步工作。

以上是关于FIFO的工作原理及其设计的主要内容,如果未能解决你的问题,请参考以下文章

verilog常考面试题之同步FIFO设计(给定位宽和深度)

verilog常考面试题之同步FIFO设计(给定位宽和深度)

FIFO

设计开发 典型同步电路设计- 同步FIFO

FIFO深度计算

异步fifo要求用verilog编写