文章目录

  • 一、FIFO概述
  • 二、FIFO分类
  • 三、FIFO重要信号与参数
    • 3.1 信号
    • 3.2 参数
      • 3.2.1 data_depth的确定
  • 四、FIFO存储原理
  • 五、同步FIFO
    • 5.1 空满信号判断
    • 5.2 同步FIFO源码
    • 5.3 测试源码
    • 5.4 功能仿真结果
    • 在这里插入图片描述
  • 六、异步FIFO
    • 6.1 异步FIFO架构
    • 6.2 设计源码
      • 6.2.1 二进制-格雷码转换器
      • 6.2.2 信号同步器(dff)
      • 6.2.3 异步FIFO顶层
      • 6.2.4 测试源码
      • 6.2.5 功能仿真结果
  • 七、非2的次幂FIFO
    • 7.1 设计思路
    • 7.2 设计源码
    • 7.3 测试程序
    • 7.4 功能仿真
  • 八、非2次幂FIFO(带将空将满信号与FIFO余量)
    • 8.1 设计源码
    • 8.2 测试源码
    • 8.3 功能仿真结果

(码字不易、三连支持)

一、FIFO概述

  FIFO(first in first out)是一种先进先出的存储器,与栈不同,栈对应的是一种先进后出的数据存储理念。
  FIFO无论是在IC设计中、IP核设计中、SOC设计中都存在广泛的应用。特别是随着设计复杂度的提高,在一个系统中往往会引入多个时钟,这也就使得数据的跨时钟域处理显得尤为重要,而FIFO正是解决这一问题的有效方法。
  FIFO的设计应用:
  1.德州仪器tm4c123g单片机的UART模块使用FIFO做缓冲。
  2.NXP单片机正交解码器缓冲。
  3.摄像头的数据缓冲。
  4.使用ADC配合FIFO与DMA实现高速采集。
  5.提高状态机模块的数据吞吐率(软件不需要再读状态机的busy标志位,只需关心fifo是否空满)。

二、FIFO分类

  FIFO根据读写时钟域的不同可分为同步FIFO与异步FIFO。
  同步FIFO是指读写通道均在相同的时钟下进行信号采样的FIFO,主要用于设备之间数据传输速率的匹配。

  异步FIFO是指读写通道在不同的时钟下进行信号采样的FIFO,主要处理跨时钟域之间的数据传输问题。
  系统2如果直接去采样处于时钟域1的系统数据,很有可能会采样到处于亚稳态的数据。使用异步FIFO对数据进行缓冲一定程度上减少了亚稳态发生的概率。

  (无论是同步还是异步的FIFO,都是面向数据流的一种数据存储器,都具有数据缓冲作用)。


三、FIFO重要信号与参数

3.1 信号

通道 信号 位宽 描述
nrst 1 复位信号,有效时将清空FIFO中已缓冲的数据
写通道 clk_w 1 写数据时钟,上升沿采样写通道信号
写通道 wr_en 1 写有效信号,当其无效时,FIFO将忽略写通道传输的数据;也可视为写数据的同步帧;当其有效时,FIFO会写入当前数据;
写通道 wrdata 任意(需与RAM匹配) 写数据输入,位宽视需求任意;
写通道 full 1 FIFO满信号,当写满时,此信号有效,此时FIFO将阻塞数据的输入;
写通道 almost_full 1 将满信号,当FIFO中存储的数据达到或超过设定的余量时,将有效;此时FIFO不会阻塞数据的写入,但设备可以通过此信号来决定是否继续写入数据;
读通道 clk_r 1 读数据通道时钟;(在同步FIFO中,该信号必须与clk_w接至同一时钟)
读通道 rd_en 1 读有效信号,当其无效时,FIFO停止数据的读出;也可视为读数据的同步帧;当其有效时,FIFO会读出数据;
读通道 rddata 任意(需与RAM匹配) 读数据输出,位宽视需求任意;
读通道 empty 1 空信号,当FIFO中数据为空时有效;此时FIFO将阻塞数据的读取;
读通道 almost_empty 1 将空信号,当FIFO中存储的数据达到或低于设定的最小余量时,将有效;此时FIFO不会阻塞数据的读出,但设备可以通过此信号来判断是否继续读出数据;
data_count 任意(与数据深度匹配) 存储计数器,表示当前FIFO中存储数据的数量,设备可根据该信号来判断是否写入或读出数据;

在FIFO实际的应用中,一般将empty与almost_full配合起来使用。为了减少FIFO上溢的风险,full信号很少使用。

3.2 参数

参数 描述
data_depth 数据深度,代表了FIFO缓冲数据的能力;一般为2的次幂;在支持非2次幂深度的FIFO中,可任意;
data_width 数据读写宽度,一般与内部RAM匹配数据宽度,在不匹配情况下需要对数据进行补位处理;
addr_width 地址宽度,与深度对应,其关系式:addr_width = log2(data_depth)

data_depth是否越大越好?
否,data_depth应该根据读写数据方的速率进行合理确定。
过大的data_depth会消耗过多的资源(若RAM是LUT实现,则消耗大量的LUT,若是Block RAM 则消耗Block RAM资源)

3.2.1 data_depth的确定

  深度的确定既要满足数据不丢失,且不能过多的浪费资源;
  这里首先考虑极端情况:
  如果写数据方的写入速率大于读数据方的读出速率,则FIFO的数据深度只有无穷大时,才能确保数据不溢出;显然无穷大深度的FIFO是不存在的;这种情况是无解的;
  相较于上述的极端情况,更多的是考虑写入Burst数据的情况,虽然写入数据流是连续的,但写Burst之间数据往往存在时间间隔;
  fw>fr,且读写之间没有空闲周期:
  假设fw为100Mhz,fr为80Mhz,一个Burst传输长度为2400。
  根据fw,可以知道写入一个数据需要10ns
  同理根据fr,可知读出一个数据需要12.5ns
  将2400个数据写入FIFO需要2400*10ns = 24000ns
  而在这2400个数据被写入期间,FIFO实际被读出的数据为24000/12.5 = 1920个
  则该情况下FIFO深度需要2400-1920 = 480

  其他情况下的FIFO深度计算也可以参考上述的情况,无论情形如何,最终需要计算的都是写入数据与读出数据之差;
  如写时钟大于读时钟,且连写入之间会有1时钟空闲,读出之间会有3时钟空闲;
  这种情况下其实写入的频率变为了100Mhz/(1+1) = 50Mhz,读出频率变为了80Mhz/(3+1) = 20Mhz,后面的解法就和上面一样了;
  还有些情况会给出读写使能信号的占空比,这其实也变相的给出了读写频率,如wr_en占空比50%,rd_en占空比25%则fw = 100Mhz*(50%) = 50Mhz,fr = 80Mhz*(25%) = 20Mhz;总之万变不离其宗;


四、FIFO存储原理

  FIFO的存储地址是不需要设备给出的,每次读使能有效则地址自增1,每次写使能有效则地址自增1;
  如果说RAM对应的是存储地址首尾不相接的数据空间,则FIFO的地址是首位相连的;
  如下图在初始化后,写指针与读指针指向地址0,此时FIFO为空状态;此时若强行读出数据,r_ptr则增1,到地址1的位置,很显然此处并无数据写入这个数据是错误的,这种情况为为数据下溢;

  当FIFO写入了7个数据后,w_ptr指向地址7,此时若再写入一个数据,写指针w_ptr将回到地址0,w_ptr与r_ptr相同此时FIFO写满,若继续写入数据,则会覆盖之前的数据,这就是数据的上溢。

  在FIFO实际使用中,写和读操作往往同时进行,此时w_ptr与r_ptr就会在这个环式地址空间上赛跑;假设写入了5个数据,则此时w_ptr指向了地址5,此时读出了两个数据,则r_ptr指向了地址2,如下图(红色为写入数据,蓝色为已读出的数据)。
  被读出的数据可以看作是被释放了出来,可以再次被写入数据;读写指针就这不断进行绕圈;

  在绕圈情况中,如果r_ptr超过了w_ptr(蓝色部分完全覆盖了红色部分),则会读出错误数据;
  若w_ptr超过r_ptr(红色部分覆盖了蓝色部分),则会出现覆盖数据的情况;
  上述两种情况需要避免,可以通过空满信号来避免;
  当r_ptr 赶上w_ptr时,则判断为空,此时FIFO阻止数据继续读出;
  当w_ptr赶上r_ptr时,则判断为满,此时FIFO阻止数据继续写入;


五、同步FIFO

5.1 空满信号判断

  同比FIFO设计中,空满信号的判断比较简单,我们只需要想办法描述上述的两种“赶上”情况就可以了;
  描述方法有很多,这里给出一个最简单的方法:
  假设深度为8,则地址宽度为3,我们在设计中对实际的地址进行1位扩位来存储“圈数”。
  此时地址枚举出来如下:
  我们可以看到在自增8之后,高位从0变为了1,此时就代表该指针已经开始跑第二圈了;若此时写指针追上了还在跑上一圈(高位不同)的读指针,则说明此时FIFO满;
  空则是相同圈内(高位相同),读指针赶上了写指针;

上述逻辑则可以写为:

assign fifo_empty = ((w_ptr == r_ptr)||((w_ptr=='b0)&(r_ptr=='b0))) ? 1:0;
assign  fifo_full = ((w_ptr[FIFO_ADDR_WIDTH] != r_ptr[FIFO_ADDR_WIDTH])&&(w_ptr[FIFO_ADDR_WIDTH-1:0] == r_ptr[FIFO_ADDR_WIDTH-1:0])) ? 1:0;

5.2 同步FIFO源码

module Synchronous_FIFO#(parameter   integer RAM_ADDR_WIDTH = 5,parameter   integer FIFO_DATA_DEPTH = 8,parameter   integer FIFO_ADDR_WIDTH = $clog2(FIFO_DATA_DEPTH),parameter   integer FIFO_DATA_WIDTH = 8)(input   wire                              nrst,input   wire                             clk_w,input   wire                             wr_en,input   wire [FIFO_DATA_WIDTH-1:0]      wrdata,output  wire                         fifo_full,input   wire                             clk_r,input   wire                             rd_en,output  wire [FIFO_DATA_WIDTH-1:0]      rddata,output  wire                        fifo_empty);/
/*w_ptr : the write data pointer (RAM Write address)r_ptr : the read data pointer (RAM Read address)
*/
reg   [FIFO_ADDR_WIDTH:0] w_ptr;reg   [FIFO_ADDR_WIDTH:0] r_ptr;wire [RAM_ADDR_WIDTH-1:0] ram_wr_addr;wire [RAM_ADDR_WIDTH-1:0] ram_rd_addr;assign ram_wr_addr = {{(RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){1'b0}},w_ptr[FIFO_ADDR_WIDTH-1:0]};assign ram_rd_addr = {{(RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){1'b0}},r_ptr[FIFO_ADDR_WIDTH-1:0]};
/
/*fifo_wr :  (RAM Write enable). valid only when fifo is not full and fifo data write enable  fifo_rd :  (RAM Read enable). valid only when fifo is not empty and fifo data read enable
*/
wire   fifo_wr;wire   fifo_rd;assign fifo_wr = (~fifo_full)&wr_en;assign fifo_rd = (~fifo_empty)&rd_en;/
/*fifo_empty : valid only when w_ptr is equal with r_ptr   fifo_full :  valid only when w_ptr's 1 MSB is equal with not( r_ptr's 1 MSB) and w_ptr's other bits equals r_ptr's
*/
assign fifo_empty = ((w_ptr == r_ptr)||((w_ptr=='b0)&(r_ptr=='b0))) ? 1:0;assign  fifo_full = ((w_ptr[FIFO_ADDR_WIDTH] != r_ptr[FIFO_ADDR_WIDTH])&&(w_ptr[FIFO_ADDR_WIDTH-1:0] == r_ptr[FIFO_ADDR_WIDTH-1:0])) ? 1:0;always @(posedge clk_w,negedge nrst) beginif (~nrst) begin// resetw_ptr <= 'b0;endelse beginif(fifo_wr)w_ptr <= w_ptr + 'b1;elsew_ptr <= w_ptr ;endendalways @(posedge clk_r,negedge nrst) beginif (~nrst) begin// resetr_ptr <= 'b0;endelse beginif(fifo_rd)r_ptr <= r_ptr + 'b1;elser_ptr <= r_ptr ;endendblk_mem_gen_0 ram0(.addra (ram_wr_addr),.clka  (clk_w),.dina  (wrdata),.wea   (1),.ena   (fifo_wr),.addrb (ram_rd_addr),.clkb  (clk_r),.doutb (rddata),.enb   (fifo_rd));
endmodule

5.3 测试源码

module Synchronous_FIFO_tb();parameter   integer FIFO_DATA_DEPTH = 8;parameter   integer FIFO_DATA_WIDTH = 8;reg                              nrst;reg                             clk;reg                             wr_en;reg [FIFO_DATA_WIDTH-1:0]      wrdata;wire                        fifo_full;reg                             rd_en;wire [FIFO_DATA_WIDTH-1:0]     rddata;wire                       fifo_empty;integer i;initial beginclk = 0;forever begin#1 clk = ~clk;endendinitial beginnrst = 0;#2  nrst = 1;wr_en = 1;wrdata = 0;rd_en = 0;while(wrdata <= 8)begin#2 wr_en  = 1;wrdata = wrdata + 1;endwr_en = 0;rd_en = 1;endSynchronous_FIFO#(. FIFO_DATA_DEPTH(FIFO_DATA_DEPTH),. FIFO_DATA_WIDTH(FIFO_DATA_WIDTH))Synchronous_FIFO_inist0(. nrst(nrst),. clk_w(clk),. wr_en(wr_en),. wrdata(wrdata),. fifo_full(fifo_full),. clk_r(clk),. rd_en(rd_en),. rddata(rddata),. fifo_empty(fifo_empty));
endmodule

5.4 功能仿真结果

六、异步FIFO

6.1 异步FIFO架构

  异步FIFO设计则需要考虑同步问题;
  此时同步FIFO中指针计数器会出现亚稳态问题,因为地址从上一个变化到下一个会产生多个比特位的变化,如0111到1000,变化的比特位为4位,由于布局布线的问题,每一比特位的变化速度不同,这将导致采集到不可预测的错误数据;
  因此我们需要另一种比特位变化更少的编码,即格雷码;
*二进制码与格雷码对照表

  如上表,相邻的两位变化的比特位仅为1;除此之外,格雷码还具有很重要的对称性,我们可以在上图的7到8之间画一条分界线,除了高位,其余为按照这条分界线对称;
  另一方面,需要将转换后的格雷码进行时钟域同步,这里采用典型的二级同步器;
  空逻辑判断:
  将w_ptr对应的格雷码w2r_ptr_gray同步到读时钟域,再将同步后的w2r_ptr_gray与r_ptr对应的格雷码r_ptr_gray进行对比,若两者相等则判定为空;

assign fifo_empty = (r_ptr_gray == w2r_ptr_gray) ? 1:0;

  满逻辑判断:
  将r_ptr对应的格雷码r2w_ptr_gray同步到写时钟域,再将同步后的r2w_ptr_gray与w_ptr对应的格雷码w_ptr_gray进行对比,若两者高两位为取反关系,剩余位相同,则判断为满;

 assign  fifo_full = ((w_ptr_gray[FIFO_ADDR_WIDTH:FIFO_ADDR_WIDTH-1] == ~(r2w_ptr_gray[FIFO_ADDR_WIDTH:FIFO_ADDR_WIDTH-1]))&&(w_ptr_gray[FIFO_ADDR_WIDTH-2:0] == r2w_ptr_gray[FIFO_ADDR_WIDTH-2:0])) ? 1:0;

  综上,异步FIFO的架构可以总结为下图所示:

6.2 设计源码

6.2.1 二进制-格雷码转换器

module Gray_encoder#(parameter integer   DATA_WIDTH = 8)(input   wire    [DATA_WIDTH - 1 : 0]     data_in,output  wire    [DATA_WIDTH - 1 : 0]    data_out);assign data_out = (data_in >> 1) ^ data_in;endmodule
module Gray_decoder#(parameter integer   DATA_WIDTH = 8)(input   wire    [DATA_WIDTH - 1 : 0]     data_in,output  wire    [DATA_WIDTH - 1 : 0]    data_out);reg [DATA_WIDTH - 1 : 0] data_out_r;assign data_out = data_out_r;integer index;always @(*) beginfor(index = 0; index < DATA_WIDTH; index = index + 1)data_out_r[index] <= ^(data_in>>index);endendmodule

6.2.2 信号同步器(dff)

module dff#(parameter integer DFF_LEVEL  = 1,parameter integer DATA_WIDTH = 8
)(input wire                     clk,input wire [DATA_WIDTH - 1:0]  din,input wire [DATA_WIDTH - 1:0] dout,input wire                    nrst);reg [DATA_WIDTH - 1:0] din_buff [DFF_LEVEL-1:0];assign dout = din_buff[DFF_LEVEL-1];integer i;always @(posedge clk or negedge nrst) beginif (~nrst) begin// resetfor(i=0;i<DFF_LEVEL;i=i+1)begindin_buff[i] <= 'b0;endendelse beginfor(i=1;i<DFF_LEVEL;i=i+1)begindin_buff[i] <= din_buff[i-1];enddin_buff[0] <= din;endendendmodule

6.2.3 异步FIFO顶层

module Asynchronous_FIFO#(parameter   integer RAM_ADDR_WIDTH = 5,parameter   integer FIFO_DATA_DEPTH = 8,parameter   integer FIFO_ADDR_WIDTH = $clog2(FIFO_DATA_DEPTH),parameter   integer FIFO_DATA_WIDTH = 8)(input   wire                              nrst,input   wire                             clk_w,input   wire                             wr_en,input   wire [FIFO_DATA_WIDTH-1:0]      wrdata,output  wire                         fifo_full,input   wire                             clk_r,input   wire                             rd_en,output  wire [FIFO_DATA_WIDTH-1:0]      rddata,output  wire                        fifo_empty);/
/*w_ptr : the write data pointer (RAM Write address)r_ptr : the read data pointer (RAM Read address)
*/
reg   [FIFO_ADDR_WIDTH:0] w_ptr;reg   [FIFO_ADDR_WIDTH:0] r_ptr;wire [RAM_ADDR_WIDTH-1:0] ram_wr_addr;wire [RAM_ADDR_WIDTH-1:0] ram_rd_addr;assign ram_wr_addr = {{(RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){1'b0}},w_ptr[FIFO_ADDR_WIDTH-1:0]};assign ram_rd_addr = {{(RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){1'b0}},r_ptr[FIFO_ADDR_WIDTH-1:0]};wire   fifo_wr;wire   fifo_rd;/
/*fifo_wr :  (RAM Write enable). valid only when fifo is not full and fifo data write enable  fifo_rd :  (RAM Read enable). valid only when fifo is not empty and fifo data read enable
*/
assign fifo_wr = (~fifo_full)&wr_en;assign fifo_rd = (~fifo_empty)&rd_en;wire [FIFO_ADDR_WIDTH : 0] w_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] r_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] w2r_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] r2w_ptr_gray;
/
/*fifo_empty : valid only when r_ptr_gray(read ptr's gray code) is equal with w2r_ptr_gray(w_ptr's gray code Sync from write clock domain)  fifo_full :  valid only when w_ptr_gray's 2 MSB is equal with not( r2w_ptr_gray's 2 MSB) and w_ptr_gray's other bits equals r2w_ptr_gray's
*/
assign fifo_empty = (r_ptr_gray == w2r_ptr_gray) ? 1:0;assign  fifo_full = ((w_ptr_gray[FIFO_ADDR_WIDTH:FIFO_ADDR_WIDTH-1] == ~(r2w_ptr_gray[FIFO_ADDR_WIDTH:FIFO_ADDR_WIDTH-1]))&&(w_ptr_gray[FIFO_ADDR_WIDTH-2:0] == r2w_ptr_gray[FIFO_ADDR_WIDTH-2:0])) ? 1:0;
/
/*Gray code exchange logic
*/
Gray_encoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_encoder_inist0(. data_in(w_ptr),. data_out(w_ptr_gray));Gray_encoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_encoder_inist1(. data_in(r_ptr),. data_out(r_ptr_gray));always @(posedge clk_w,negedge nrst) beginif (~nrst) begin// resetw_ptr <= 'b0;endelse beginif(fifo_wr)w_ptr <= w_ptr + 'b1;elsew_ptr <= w_ptr ;endendalways @(posedge clk_r,negedge nrst) beginif (~nrst) begin// resetr_ptr <= 'b0;endelse beginif(fifo_rd)r_ptr <= r_ptr + 'b1;elser_ptr <= r_ptr ;endend
/
/*Synchronize logic across clock domains
*/
dff#(.DFF_LEVEL(2),.DATA_WIDTH(FIFO_ADDR_WIDTH+1)
)dff_inist0(.  clk(clk_r),.  din(w_ptr_gray),. dout(w2r_ptr_gray),. nrst(nrst));dff#(.DFF_LEVEL(2),.DATA_WIDTH(FIFO_ADDR_WIDTH+1)
)dff_inist1(.  clk(clk_w),.  din(r_ptr_gray),. dout(r2w_ptr_gray),. nrst(nrst));blk_mem_gen_0 ram0(.addra (ram_wr_addr),.clka  (clk_w),.dina  (wrdata),.wea   (1),.ena   (fifo_wr),.addrb (ram_rd_addr),.clkb  (clk_r),.doutb (rddata),.enb   (fifo_rd));
endmodule

6.2.4 测试源码

module async_fifo_tb();parameter   integer RAM_ADDR_WIDTH = 5;parameter   integer FIFO_DATA_DEPTH = 8;parameter   integer FIFO_DATA_WIDTH = 8;reg                              nrst;reg                             clk_w;reg                             wr_en;reg [FIFO_DATA_WIDTH-1:0]      wrdata;wire                        fifo_full;reg                             clk_r;reg                             rd_en;wire [FIFO_DATA_WIDTH-1:0]     rddata;wire                       fifo_empty;integer i;initial beginclk_w = 0;forever begin#2 clk_w = ~clk_w;endendinitial beginclk_r = 0;#1forever begin#2 clk_r = ~clk_r;endendinitial beginnrst = 0;#4  nrst = 1;wr_en = 1;wrdata = 0;rd_en = 0;while(wrdata <= 8)begin#4 wr_en  = 1;wrdata = wrdata + 1;endwr_en = 0;rd_en = 1;endAsynchronous_FIFO#(. RAM_ADDR_WIDTH(RAM_ADDR_WIDTH),. FIFO_DATA_DEPTH(FIFO_DATA_DEPTH),. FIFO_DATA_WIDTH(FIFO_DATA_WIDTH))Asynchronous_FIFO_inist0(. nrst(nrst),. clk_w(clk_w),. wr_en(wr_en),. wrdata(wrdata),. fifo_full(fifo_full),. clk_r(clk_r),. rd_en(rd_en),. rddata(rddata),. fifo_empty(fifo_empty));
endmodule

6.2.5 功能仿真结果

  我们观察上面的仿真图,fifo_empty在写入两个数据后才拉低,fifo_full在读出两个数据后才拉低,这就是假空假满现象;
  本质上这个现象是由同步器造成的,空信号是在读时钟与将读指针与同步过来的写指针进行比较,由于同步器采用了两级,因此会有两个clk的滞后输出,即实际的写地址已经增至2了,但同步过来的地址仍为0;假满状态同理;
  但这种现象并不会影响FIFO的使用,因为这样产生的空满信号实际上是悲观的,虽然FIFO中有数据,但空信号仍然会阻止数据的读出,因此只会在FIFO的效率上产生影响。


七、非2的次幂FIFO

7.1 设计思路

  非2次幂FIFO实际上与2次幂异步FIFO差不多,只不过在编码格式上我们需要有所改变,对应的空满信号的判断也需要稍加改动;
  假设是6深度的FIFO,使用顺序的格雷码,变化的比特位就不再是1位了;
  如从6变化到0,共两个码位发生了变化;

  针对这个问题,可以利用格雷码的对称性解决;如下图
  由13到2,(1011)到(0011)只变化了1位;

  将原来的顺序格雷码映射到对称格雷码也比较简单;
  只需在指针与格雷码的转换逻辑上,每次转换的指针加上一个偏置项FIFO_DEPTH_COMPLE;

Gray_encoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_encoder_inist0(. data_in(w_ptr+FIFO_DEPTH_COMPLE),. data_out(w_ptr_gray));Gray_encoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_encoder_inist1(. data_in(r_ptr+FIFO_DEPTH_COMPLE),. data_out(r_ptr_gray));

  FIFO_DEPTH_COMPLE同时也为地址的补偿比特位,其使得FIFO_DATA_DEPTH + FIFO_DEPTH_COMPLE 为大于FIFO_DATA_DEPTH的最小2的次幂整数;
  如FIFO_DATA_DEPTH为6,则大于6的最小2次幂整数为8,则FIFO_DEPTH_COMPLE = 8 - FIFO_DATA_DEPTH = 2;

  采用这种编码方式,满信号的逻辑无法再使用两格雷码高两位取反,其余位相同的逻辑来进行了;
  这里采用将同步过来的格雷码形式的指针转换为二进制,再通过对两二进制指针作差取绝对值算出FIFO中当前写入的数据数,来判断是否空满;

7.2 设计源码

module Asynchronous_FIFO_npow2#(//BLOCK RAM address width parameter   integer RAM_ADDR_WIDTH = 5,//FIFO true depthparameter   integer FIFO_DATA_DEPTH = 6,//FIFO compensate depth,(FIFO_DATA_DEPTH+FIFO_DEPTH_COMPLE) must be the data of power 2parameter   integer FIFO_DEPTH_COMPLE = 2,parameter   integer FIFO_ADDR_WIDTH = $clog2(FIFO_DATA_DEPTH+FIFO_DEPTH_COMPLE),parameter   integer FIFO_DATA_WIDTH = 8)(input   wire                              nrst,input   wire                             clk_w,input   wire                             wr_en,input   wire [FIFO_DATA_WIDTH-1:0]      wrdata,output  wire                         fifo_full,input   wire                             clk_r,input   wire                             rd_en,output  wire [FIFO_DATA_WIDTH-1:0]      rddata,output  wire                        fifo_empty);/
/*w_ptr : the write data pointer (RAM Write address)r_ptr : the read data pointer (RAM Read address)
*/
reg   [FIFO_ADDR_WIDTH:0] w_ptr;reg   [FIFO_ADDR_WIDTH:0] r_ptr;wire [RAM_ADDR_WIDTH-1:0] ram_wr_addr;wire [RAM_ADDR_WIDTH-1:0] ram_rd_addr;assign ram_wr_addr = {{(RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){1'b0}},w_ptr[FIFO_ADDR_WIDTH-1:0]};assign ram_rd_addr = {{(RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){1'b0}},r_ptr[FIFO_ADDR_WIDTH-1:0]};wire   fifo_wr;wire   fifo_rd;/
/*fifo_wr :  (RAM Write enable). valid only when fifo is not full and fifo data write enable  fifo_rd :  (RAM Read enable). valid only when fifo is not empty and fifo data read enable
*/
assign fifo_wr = (~fifo_full)&wr_en;assign fifo_rd = (~fifo_empty)&rd_en;wire [FIFO_ADDR_WIDTH : 0] w_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] r_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] w2r_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] r2w_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] w2r_ptr_bin;wire [FIFO_ADDR_WIDTH : 0] r2w_ptr_bin;wire [FIFO_ADDR_WIDTH : 0] w_fifo_data_count;wire [FIFO_ADDR_WIDTH : 0] r_fifo_data_count;assign r_fifo_data_count = ((w2r_ptr_bin - FIFO_DEPTH_COMPLE)> r_ptr) ? (w2r_ptr_bin - r_ptr - FIFO_DEPTH_COMPLE):(r_ptr - w2r_ptr_bin + FIFO_DEPTH_COMPLE);assign w_fifo_data_count = ((r2w_ptr_bin - FIFO_DEPTH_COMPLE)> w_ptr) ? (r2w_ptr_bin - w_ptr - FIFO_DEPTH_COMPLE):(w_ptr - r2w_ptr_bin + FIFO_DEPTH_COMPLE);
/
/*fifo_empty : valid only when r_ptr_gray(read ptr's gray code) is equal with w2r_ptr_gray(w_ptr's gray code Sync from write clock domain)  fifo_full :  valid only when w_ptr_gray's 2 MSB is equal with not( r2w_ptr_gray's 2 MSB) and w_ptr_gray's other bits equals r2w_ptr_gray's
*/
assign fifo_empty = (r_fifo_data_count == 'b0) ? 1:0;assign  fifo_full = (w_fifo_data_count == FIFO_DATA_DEPTH) ? 1:0;
/
/*Gray code exchange logic
*/
Gray_encoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_encoder_inist0(. data_in(w_ptr+FIFO_DEPTH_COMPLE),. data_out(w_ptr_gray));Gray_encoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_encoder_inist1(. data_in(r_ptr+FIFO_DEPTH_COMPLE),. data_out(r_ptr_gray));always @(posedge clk_w,negedge nrst) beginif (~nrst) begin// resetw_ptr <= 'b0;endelse beginif(fifo_wr)w_ptr <= w_ptr + 'b1;elsew_ptr <= w_ptr ;endendalways @(posedge clk_r,negedge nrst) beginif (~nrst) begin// resetr_ptr <= 'b0;endelse beginif(fifo_rd)r_ptr <= r_ptr + 'b1;elser_ptr <= r_ptr ;endend
/
/*Synchronize logic across clock domains
*/
dff#(.DFF_LEVEL(2),.DATA_WIDTH(FIFO_ADDR_WIDTH+1)
)dff_inist0(.  clk(clk_r),.  din(w_ptr_gray),. dout(w2r_ptr_gray),. nrst(nrst));dff#(.DFF_LEVEL(2),.DATA_WIDTH(FIFO_ADDR_WIDTH+1)
)dff_inist1(.  clk(clk_w),.  din(r_ptr_gray),. dout(r2w_ptr_gray),. nrst(nrst));
/
/*read and write pointer decode logic
*/
Gray_decoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_decoder_inist0(.  data_in(w2r_ptr_gray),. data_out(w2r_ptr_bin));Gray_decoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_decoder_inist1(.  data_in(r2w_ptr_gray),. data_out(r2w_ptr_bin));blk_mem_gen_0 ram0(.addra (ram_wr_addr),.clka  (clk_w),.dina  (wrdata),.wea   (1),.ena   (fifo_wr),.addrb (ram_rd_addr),.clkb  (clk_r),.doutb (rddata),.enb   (fifo_rd));
endmodule

7.3 测试程序

module Asynchronous_FIFO_npow2_tb();parameter   integer RAM_ADDR_WIDTH = 5;parameter   integer FIFO_DATA_DEPTH = 3;parameter   integer FIFO_DEPTH_COMPLE = 5;parameter   integer FIFO_DATA_WIDTH = 8;reg                              nrst;reg                             clk_w;reg                             wr_en;reg [FIFO_DATA_WIDTH-1:0]      wrdata;wire                        fifo_full;reg                             clk_r;reg                             rd_en;wire [FIFO_DATA_WIDTH-1:0]     rddata;wire                       fifo_empty;integer i;initial beginclk_w = 0;forever begin#2 clk_w = ~clk_w;endendinitial beginclk_r = 0;#1forever begin#2 clk_r = ~clk_r;endendinitial beginnrst = 0;#4  nrst = 1;wr_en = 1;wrdata = 0;rd_en = 0;while(wrdata <= 8)begin#4 wr_en  = 1;wrdata = wrdata + 1;endwr_en = 0;rd_en = 1;endAsynchronous_FIFO_npow2#(//BLOCK RAM address width . RAM_ADDR_WIDTH (RAM_ADDR_WIDTH),//FIFO true depth. FIFO_DATA_DEPTH (FIFO_DATA_DEPTH),//FIFO compensate depth,(FIFO_DATA_DEPTH+FIFO_DEPTH_COMPLE) must be the data of power 2. FIFO_DEPTH_COMPLE (FIFO_DEPTH_COMPLE),. FIFO_DATA_WIDTH (FIFO_DATA_WIDTH))Asynchronous_FIFO_npow2_inist(.      nrst(nrst),.     clk_w(clk_w),.     wr_en(wr_en),.    wrdata(wrdata),. fifo_full(fifo_full),.     clk_r(clk_r),.     rd_en(rd_en),.    rddata(rddata),.fifo_empty(fifo_empty));
endmodule

7.4 功能仿真

八、非2次幂FIFO(带将空将满信号与FIFO余量)

  进一步在第七章中的FIFO上进行将空将满信号的设计;
 将空将满已经在之前介绍过了,可以视作一个阈值,当FIFO中的数据数大于等于这个阈值时将满信号拉高,反之将空信号拉高;
  上一章已经介绍了r_fifo_data_count与w_fifo_data_count的计算方法,我们使用这两个计数值来增加将空将满逻辑,如下:

assign fifo_almost_full = (w_fifo_data_count >= (FIFO_DATA_DEPTH - FIFO_DATA_DEPTH_MARGIN)) ? 1'b1:1'b0;
assign fifo_almost_empty = ((r_fifo_data_count <= FIFO_DATA_DEPTH_MARGIN)) ? 1'b1:1'b0;

  FIFO_DATA_DEPTH_MARGIN为余量值;

8.1 设计源码

module Asynchronous_FIFO_npow2#(//BLOCK RAM address width parameter   integer RAM_ADDR_WIDTH = 5,//FIFO true depthparameter   integer FIFO_DATA_DEPTH = 6,//FIFO degth's marginparameter   integer FIFO_DATA_DEPTH_MARGIN = 2,//FIFO compensate depth,(FIFO_DATA_DEPTH+FIFO_DEPTH_COMPLE) must be the data of power 2parameter   integer FIFO_DEPTH_COMPLE = 2,parameter   integer FIFO_ADDR_WIDTH = $clog2(FIFO_DATA_DEPTH+FIFO_DEPTH_COMPLE),parameter   integer FIFO_DATA_WIDTH = 8)(input   wire                                     nrst,input   wire                                    clk_w,input   wire                                    wr_en,input   wire [FIFO_DATA_WIDTH-1:0]             wrdata,output  wire                                fifo_full,output  wire                         fifo_almost_full,output  wire [FIFO_ADDR_WIDTH : 0]      fifo_count_wr,input   wire                                    clk_r,input   wire                                    rd_en,output  wire [FIFO_DATA_WIDTH-1:0]             rddata,output  wire                               fifo_empty,output  wire [FIFO_ADDR_WIDTH : 0]      fifo_count_rd,output  wire                        fifo_almost_empty);/
/*w_ptr : the write data pointer (RAM Write address)r_ptr : the read data pointer (RAM Read address)
*/
reg   [FIFO_ADDR_WIDTH:0] w_ptr;reg   [FIFO_ADDR_WIDTH:0] r_ptr;wire [RAM_ADDR_WIDTH-1:0] ram_wr_addr;wire [RAM_ADDR_WIDTH-1:0] ram_rd_addr;assign ram_wr_addr = {{(RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){1'b0}},w_ptr[FIFO_ADDR_WIDTH-1:0]};assign ram_rd_addr = {{(RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){1'b0}},r_ptr[FIFO_ADDR_WIDTH-1:0]};wire   fifo_wr;wire   fifo_rd;/
/*fifo_wr :  (RAM Write enable). valid only when fifo is not full and fifo data write enable  fifo_rd :  (RAM Read enable). valid only when fifo is not empty and fifo data read enable
*/
assign fifo_wr = (~fifo_full)&wr_en;assign fifo_rd = (~fifo_empty)&rd_en;wire [FIFO_ADDR_WIDTH : 0] w_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] r_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] w2r_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] r2w_ptr_gray;wire [FIFO_ADDR_WIDTH : 0] w2r_ptr_bin;wire [FIFO_ADDR_WIDTH : 0] r2w_ptr_bin;reg [FIFO_ADDR_WIDTH : 0] w_fifo_data_count;reg [FIFO_ADDR_WIDTH : 0] r_fifo_data_count;assign fifo_count_wr = w_fifo_data_count;assign fifo_count_rd = r_fifo_data_count;always@(*)beginif(~nrst)beginr_fifo_data_count <= 'b0;endelsebeginif(w2r_ptr_bin < FIFO_DEPTH_COMPLE)beginr_fifo_data_count <= 'b0;endelse beginif((w2r_ptr_bin - FIFO_DEPTH_COMPLE)> r_ptr)r_fifo_data_count <= (w2r_ptr_bin - r_ptr - FIFO_DEPTH_COMPLE);elser_fifo_data_count <= (r_ptr - w2r_ptr_bin + FIFO_DEPTH_COMPLE);endendendalways@(*)beginif(~nrst)beginw_fifo_data_count <= 'b0;endelsebeginif(r2w_ptr_bin < FIFO_DEPTH_COMPLE)beginw_fifo_data_count <= 'b0;endelse beginif((r2w_ptr_bin - FIFO_DEPTH_COMPLE)> w_ptr)w_fifo_data_count <= (r2w_ptr_bin - w_ptr - FIFO_DEPTH_COMPLE);elsew_fifo_data_count <= (w_ptr - r2w_ptr_bin + FIFO_DEPTH_COMPLE);endendend
/
/*fifo_empty : valid only when r_ptr_gray(read ptr's gray code) is equal with w2r_ptr_gray(w_ptr's gray code Sync from write clock domain)  fifo_full :  valid only when w_ptr_gray's 2 MSB is equal with not( r2w_ptr_gray's 2 MSB) and w_ptr_gray's other bits equals r2w_ptr_gray's
*/
assign fifo_empty = (r_fifo_data_count == 'b0) ? 1:0;assign  fifo_full = (w_fifo_data_count == FIFO_DATA_DEPTH) ? 1:0;assign fifo_almost_full = (w_fifo_data_count >= (FIFO_DATA_DEPTH - FIFO_DATA_DEPTH_MARGIN)) ? 1'b1:1'b0;assign fifo_almost_empty = ((r_fifo_data_count <= FIFO_DATA_DEPTH_MARGIN)) ? 1'b1:1'b0;
/
/*Gray code exchange logic
*/
Gray_encoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_encoder_inist0(. data_in(w_ptr+FIFO_DEPTH_COMPLE),. data_out(w_ptr_gray));Gray_encoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_encoder_inist1(. data_in(r_ptr+FIFO_DEPTH_COMPLE),. data_out(r_ptr_gray));always @(posedge clk_w,negedge nrst) beginif (~nrst) begin// resetw_ptr <= 'b0;endelse beginif(fifo_wr)w_ptr <= w_ptr + 'b1;elsew_ptr <= w_ptr ;endendalways @(posedge clk_r,negedge nrst) beginif (~nrst) begin// resetr_ptr <= 'b0;endelse beginif(fifo_rd)r_ptr <= r_ptr + 'b1;elser_ptr <= r_ptr ;endend
/
/*Synchronize logic across clock domains
*/
dff#(.DFF_LEVEL(2),.DATA_WIDTH(FIFO_ADDR_WIDTH+1)
)dff_inist0(.  clk(clk_r),.  din(w_ptr_gray),. dout(w2r_ptr_gray),. nrst(nrst));dff#(.DFF_LEVEL(2),.DATA_WIDTH(FIFO_ADDR_WIDTH+1)
)dff_inist1(.  clk(clk_w),.  din(r_ptr_gray),. dout(r2w_ptr_gray),. nrst(nrst));
/
/*read and write pointer decode logic
*/
Gray_decoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_decoder_inist0(.  data_in(w2r_ptr_gray),. data_out(w2r_ptr_bin));Gray_decoder#(. DATA_WIDTH(FIFO_ADDR_WIDTH+1))Gray_decoder_inist1(.  data_in(r2w_ptr_gray),. data_out(r2w_ptr_bin));blk_mem_gen_0 ram0(.addra (ram_wr_addr),.clka  (clk_w),.dina  (wrdata),.wea   (1),.ena   (fifo_wr),.addrb (ram_rd_addr),.clkb  (clk_r),.doutb (rddata),.enb   (fifo_rd));
endmodule

8.2 测试源码


module Asynchronous_FIFO_npow2_tb();parameter   integer RAM_ADDR_WIDTH = 5;parameter   integer FIFO_DATA_DEPTH = 14;parameter   integer FIFO_DATA_DEPTH_MARGIN = 2;parameter   integer FIFO_DEPTH_COMPLE = 2;parameter   integer FIFO_DATA_WIDTH = 8;parameter   integer FIFO_ADDR_WIDTH = $clog2(FIFO_DATA_DEPTH+FIFO_DEPTH_COMPLE);reg                                       nrst;reg                                      clk_w;reg                                      wr_en;reg [FIFO_DATA_WIDTH-1:0]               wrdata;wire                                 fifo_full;wire                          fifo_almost_full;wire [FIFO_ADDR_WIDTH : 0]       fifo_count_wr;reg                                      clk_r;reg                                      rd_en;wire [FIFO_DATA_WIDTH-1:0]              rddata;wire                                fifo_empty;wire [FIFO_ADDR_WIDTH : 0]       fifo_count_rd;wire                         fifo_almost_empty;integer i;initial beginclk_w = 0;forever begin#2 clk_w = ~clk_w;endendinitial beginclk_r = 0;#1forever begin#2 clk_r = ~clk_r;endendinitial beginnrst = 0;#4  nrst = 1;wr_en = 1;wrdata = 0;rd_en = 0;while(wrdata <= 14)begin#4 wr_en  = 1;wrdata = wrdata + 1;endwr_en = 0;rd_en = 1;endAsynchronous_FIFO_npow2#(//BLOCK RAM address width . RAM_ADDR_WIDTH (RAM_ADDR_WIDTH),//FIFO true depth. FIFO_DATA_DEPTH (FIFO_DATA_DEPTH),. FIFO_DATA_DEPTH_MARGIN (FIFO_DATA_DEPTH_MARGIN),//FIFO compensate depth,(FIFO_DATA_DEPTH+FIFO_DEPTH_COMPLE) must be the data of power 2. FIFO_DEPTH_COMPLE (FIFO_DEPTH_COMPLE),. FIFO_DATA_WIDTH (FIFO_DATA_WIDTH))Asynchronous_FIFO_npow2_inist(.      nrst(nrst),.     clk_w(clk_w),.     wr_en(wr_en),.    wrdata(wrdata),. fifo_full(fifo_full),. fifo_almost_full(fifo_almost_full),. fifo_count_wr(fifo_count_wr),.     clk_r(clk_r),.     rd_en(rd_en),.    rddata(rddata),. fifo_empty(fifo_empty),. fifo_almost_empty(fifo_almost_empty),. fifo_count_rd(fifo_count_rd));
endmodule

8.3 功能仿真结果

  这里观察almost_empty和almost_empty信号,其假将空与假将满产生的原因与empty和full的假空假满相同;

各种FIFO硬件设计(FIFO概念、异步、同步、非2次幂深度FIFO)相关推荐

  1. 1311_硬件设计_ICT概念、应用以及优缺点学习小结

    全部学习汇总: GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware ...

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

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

  3. Asynchronous FIFO with gray code(异步FIFO verilog设计理念)

    代码来自asic world 和paper" Simulation and Synthesis Techniques for Asynchronous FIFO Design",文 ...

  4. 基于Montgomery算法的高速、可配置 RSA密码IP核硬件设计系列(五)——模幂模块(抵抗侧信道攻击)模块的设计实现方案

    基于Montgomery算法的高速.可配置RSA密码IP核硬件设计系列(五) 2.2 模幂模块设计(抵抗测信道攻击模块) 2.2.1 模幂模块及内部模块的功能 2.2.3 模幂各模块的实现方案 2.2 ...

  5. 硬件架构的艺术:同步FIFO设计

    目录 1. 概述 2. 同步FIFO设计 2.1 同步FIFO结构 2.2 同步FIFO空满信号产生 2.2.1 时序逻辑产生空满 2.2.1.1 fifo满信号产生 2.2.1.2 fifo空信号产 ...

  6. (87)FPGA面试题-同步FIFO与异步FIFO区别?异步FIFO代码设计

    1.1 FPGA面试题-同步FIFO与异步FIFO区别?异步FIFO代码设计 1.1.1 本节目录 1)本节目录: 2)本节引言: 3)FPGA简介: 4)FPGA面试题-同步FIFO与异步FIFO区 ...

  7. 异步FIFO的设计详解(格雷码计数+两级DFF同步)

    文章目录 一.异步FIFO介绍 1.1.空满判断 1.2.跨时钟域问题 1.3.格雷码转换 1.4.格雷码计数器 二.代码code 一.异步FIFO介绍   FIFO有同步和异步两种,同步即读写时钟相 ...

  8. 74ls390设计任意进制计数器_异步FIFO:设计原理及Verliog源码

    1.  异步FIFO的概念 异步FIFO为读取与写入采用不同的时钟,使用异步FIFO用于在不同的时钟域传输数据,主要用于跨时钟域传输多bit数据. 2.  异步FIFO的设计难点 同步异步信号,避免亚 ...

  9. FPGA基础知识极简教程(3)从FIFO设计讲起之同步FIFO篇

    博文目录 写在前面 正文 FPGA/ASIC中的FIFO 同步FIFO的设计 参考资料 交个朋友 写在前面 个人博客首页 注:学习交流使用! 正文 FPGA/ASIC中的FIFO FIFO缓冲区如何用 ...

最新文章

  1. 栈的应用——迷宫的非递归解法
  2. 【Alertmanager】腾讯企业邮箱配置
  3. [云炬创业基础笔记] 第四章测试15
  4. windows下利用sox批量将PCM转为WAV
  5. ld-linux.so.2 重定向,2-Linux重定向和管道、Shell编程.doc
  6. 关于numpy mean函数的axis参数
  7. Android 系统(89)---ART
  8. python交叉验证结合线性回归_Python数据分析-线性回归、逻辑回归
  9. sharepoint2013列表实现项目级权限控制
  10. 20155303 2016-2017-2 《Java程序设计》第九周学习总结
  11. 【双十一精选】史上最强的宝贝详情页设计思路以及操作流程
  12. 计算机报名照片像素大小,证件照尺寸怎么修改-三种方法搞定证件照要求,让你不用再为图像分辨率和大小发愁!...
  13. 微信小程序如何快速累计独立访客(UV)不低于 1000
  14. 自恋的人脑袋有啥不一样?| 自恋型人格特质和前额脑结构
  15. 解读txt文件中的乱码
  16. No signing certificate “iOS Distribution“ found No “iOS Distribution“ signing certificate matching
  17. Guide哥|我写了四年博客,收获了20w+读者。我为什么推荐你写博客?
  18. 获取某一年的起始时间和结束时间
  19. 时下火热的 NFT 究竟有什么用?
  20. STM32F1XX的GPIO的8种工作模式以及GPIO的寄存器简介

热门文章

  1. 关于sqoop抽取数据时显示ERROR :/QueryResult.java‘ already exists 解读
  2. JVM性能优化一些概念简介
  3. 美团和大众点评早期分别以交易和用户评价进军团购行业
  4. 行进中换轮胎——万字长文解析美团和大众点评两大数据平台是怎么融合的
  5. linux云计算架构师:搭建DHCP服务和NTP网络时间同步
  6. 3.25 使用钢笔工具选择平滑形状的叶子 [原创Ps教程]
  7. hdcp key校验流程
  8. 2021 年第十三届四川省 ACM-ICPC 大学生程序设计竞赛(A/B/D/H/E/K/M/L)
  9. 图的广度优先搜索(BFS)和深度优先搜索(DFS)算法解析
  10. 1号店两年即被资本俘获 创始人离开仅是时间问题