[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA基础篇连载-26浅谈XILINX FIFO的基本使

文档创建者:FPGA课程
浏览次数:279
最后更新:2024-09-03
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
本帖最后由 FPGA课程 于 2024-9-3 13:47 编辑

​软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述
首先来大概了解下什么是FIFO ,FIFO( First Input First Output)简单说就是指先进先出。FIFO也是缓存机制的一种,下面是我总结的FIFO的三大用途:
1)、提高传输效率,增加DDR带宽的利用率。比如我们有4路视频数据缓存到DDR中去,比较笨的方法是,每个通道视频数据对应一颗DDR。现在对于DDR来说非常浪费,因为现在的DDR3可以跑1600Mbps DDR4可以跑到2400Mbps,如果你还是把一路视频数据对应一颗DDR显然严重浪费了带宽。加入FIFO后,只要把4路数据先缓存进入DDR,在缓存的过程中,快速得把数据从FIFO取出并且写入到DDR中,只要FIFO没有满就不会出现数据丢失。现在我们带宽够用,FIFO给的足够大就可以确保数据不丢失。
2)、数据位宽转换,比如我们有32bit的数据需要转换成128bit或者32bit的数据需要转换成8bit,那么用FIFO来转换也是非常方便的。
3)、跨时钟域的应用,比如数据是2个不同步的时钟,那么我们就可以用FIFO实现跨时钟域的传输。
以上总计的三点,很多时候是混合使用的。FIFO的用途非常大,我们在后面的例子中也看到,只要涉及到DDR传输的都和FIFO有关系。
我们这里的例子通过仿真告诉大家FIFO的基本用法,有两条我总结的办法,包括:
1)半空半满法
2)关键信号法
2配置FIFO IP
点击软件左侧的IP Catalog
51635679412541abb566874fb5f77af4.jpg
输入关键词fifo,会出来非常多的FIFO类型
1)、AXI4-Stream FIFO内核旨在提供对与其他IP连接的AXI4-Stream接口(例如AXI以太网内核)的内存映射访问。 必须通过Vivado Design Suite构建系统,以连接AXI4-Stream FIFO内核,AXI以太网内核,处理器,内存,互连总线,时钟和其他嵌入式组件。
2)、AXI4-Stream Data FIFO 支持AXI4-Stream协议,具备packet包传输模式。
3)、AXI Data FIFO 就是数据FIFO 功能较为单一,接口为Stream接口
4)、FIFO Generator 支持Native 模式,AXI Memory Mapped模式 AXI Steam模式功能比较齐全,在没有AXI4或者AXI Stream协议的场合下,我们更多使用Native模式,这里的课程也以Native模式讲解。
998d8324db674bd288429b3fa2b72281.jpg
使用Block RAM,BlockRAM是FPGA内部 集成的重要内存单元,速度高资源有限,所以得充分合理利用。时钟模式采用异步方式,也就是这里选择Independent Clocks,可以把IP名字改为FIFO32_2_128
4237996855954f6c9ba45c6d32be74a4.jpg


选择First Word Fall Through 这样写入的数据,会先在读端口准备好,否则如果选择Standard FIFO需要读使能后一个时钟输出才有效。
846aa75d1117439b9543c28c8308cd40.jpg
观察almost full 和almost empty flag 这两个信号是可编程的,一些应用场景也是可以用到。
0e1d6e576f8c48a494c553847a46db5f.jpg


设置读计数器和写计数器,这不是必须的,我们第一个半空半满方法需要用到。
b224d0f7f86649f6a34c3d73d24142d7.jpg
3半空/半满法控制读写FIFO
半空/半满法,功法要点:半空是针对读FIFO计数器而言,半满是针对写FIFO计数器而言;这里强调一点,FIFO的计数器并不精准,计数器会有几个时钟的延迟,所以这里的半空,半满的计数器大部分时候都是不精确的,除非你把程序停下来等几个时钟周期,显然这样不科学,会降低程序的效率。虽然不精确但是完全够用。因为读写FIFO一直在一个动态平衡中。
3.1测试代码程序
比如,我们这里的FIFO输入32bit 深度1024;输出128bit 深度256,这里的半空值就是128,半满值就是512。下面设计我们的状态机:
1)、状态0:当写入FIFO计数器小于512 则进入状态1
2)、状态1:当连续写入FIFO 512个数据后,再次进入状态0等待
读状态机的设计,每次读出128bt数据:
1)、状态0:当读FIFO计数器大于128 则进入状态1
2)、状态1:连续读出FIFO 128个数据后,再次进入状态0等待

fifo_test.v
  1. /*************FIFO IP的仿真测试***************************************
  2. --版本号1.0
  3. --FIFO通常用于异步数据传输、数据缓存、数据位宽转换,本使用基于XILINX FIFO IP实现数据的位宽转换实验
  4. --通过FIFO实现数据缓存,以及数据位宽从32bits转为128btis
  5. --写状态机和读状态机分开运行
  6. *********************************************************************/
  7. `timescale 1ns / 1ns //仿真时间刻度/精度

  8. module fifo_test1
  9. (
  10. input I_sysclk_p,
  11. input I_sysclk_n, //系统时钟输入
  12. input I_rstn     //系统复位
  13. );

  14. wire I_clk;
  15. IBUFGDS CLK_U(
  16. .I(I_sysclk_p),
  17. .IB(I_sysclk_n),
  18. .O(I_clk)
  19. );
  20. wire clk_100m,clk_200m,clk_locked;//MMCM/PLL 时钟信号
  21. wire [127:0]rd_data; //读数据信号
  22. wire fifo_rst;       //fifo 复位,高电平有效
  23. wire full;           //FIFO满,这里没用到
  24. wire empty;          //FIFO空,这里没用到
  25. wire almost_full;    //FIFO将满,代表FIFO再写入1个数据就会满,这里没用到
  26. wire almost_empty;   //FIFO将空,代表FIFO再读出1个数据就会空,这里没用到
  27. wire [7 : 0] rd_data_count;//读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  28. wire [9 : 0] wr_data_count;//写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数

  29. //写状态机信号
  30. reg WR_REQ = 1'b0;   //写请求信号
  31. reg [0 :0]WR_S;      //写状态机
  32. reg [10:0]wr_cnt;    //写数据计数器
  33. reg wr_en;           //写使能寄存器

  34. // 读状态机
  35. reg RD_REQ = 1'b0;  //读请求信号
  36. reg [0:0]RD_S;      //读状态机
  37. reg [7:0]rd_cnt;    //读数据计数器
  38. reg rd_en;          //读使能寄存器

  39. reg[9:0] rst_cnt = 10'd0; //复位计数器

  40. assign fifo_rst = (rst_cnt[9:7] == 3'b010); //产生一个高脉冲复位
  41. //MMCM/PLL 产生200M和100M时钟
  42. clk_wiz_0 clk_inst(.clk_out1(clk_200m),.clk_out2(clk_100m),.resetn(I_rstn),.locked(clk_locked),.clk_in1(I_clk));  

  43. //复位计数器模块
  44. always @(posedge clk_100m)begin
  45.     if(!clk_locked)
  46.         rst_cnt <= 10'd0;
  47.     else if(rst_cnt[9] == 1'b0)
  48.         rst_cnt <= rst_cnt + 1'b1;
  49. end

  50. // FIFO写状态机
  51. always @(posedge clk_200m)begin //写数据用200MHZ 时钟写
  52.     if(!rst_cnt[9])begin //复位,重置相关寄存器
  53.         WR_S   <= 1'b0;
  54.         wr_cnt <= 11'd0;
  55.         wr_en  <= 1'b0;
  56.     end
  57.     else begin
  58.         case(WR_S) //状态机
  59.         0:begin
  60.             wr_cnt <= 11'd0;
  61.             if(WR_REQ) //当WR_REQ信号有效,代表了FIFO已经可以写入数据
  62.                 WR_S <= 1'b1;//进入下一状态
  63.         end
  64.         1:begin
  65.             if(wr_cnt < 512)begin //如果写入的数据小于512
  66.                 wr_en  <= 1'b1; //设置写使能
  67.                 wr_cnt <= wr_cnt+1'b1;//写计数器累加
  68.             end
  69.             else begin //否则,重置使能,并且回到状态0
  70.                 wr_en <= 1'b0;
  71.                 WR_S  <= 1'b0;
  72.             end
  73.         end
  74.         endcase
  75.     end
  76. end

  77. always @(posedge clk_100m)begin//读使用100M时钟
  78.     if(!rst_cnt[9])begin //复位,重置相关寄存器
  79.         RD_S <= 1'b0;
  80.         rd_cnt <= 8'd0;
  81.         rd_en <= 1'b0;
  82.     end
  83.     else begin
  84.         case(RD_S)//读状态机
  85.         0:begin
  86.             rd_cnt <= 8'd0;
  87.             if(RD_REQ) //RD_REQ代表FIFO中有足够的数据
  88.                 RD_S <= 1'b1;//下一状态
  89.         end
  90.         1:begin
  91.             if(rd_cnt < 128)begin //判断FIFO中读部分的数据,已经读的数量是否小于128个128bits
  92.                 rd_en  <= 1'b1;   //使能读信号
  93.                 rd_cnt <= rd_cnt+1'b1;//每读一个数据,累加1
  94.             end
  95.             else begin //否则重置读使能,状态机回到0
  96.                 rd_en <= 1'b0;
  97.                 RD_S <= 1'b0;
  98.             end
  99.         end
  100.         endcase
  101.     end
  102. end

  103. //判断写FIFO中是否有足够的空间存放下一次写的数据
  104. always @(posedge clk_200m)begin
  105.     WR_REQ <= (wr_data_count < 10'd511);
  106. end

  107. //判断读FIFO中是否有足够的数据可以被读出
  108. always @(posedge clk_100m)begin
  109.     RD_REQ <= (rd_data_count > 8'd127);
  110. end

  111. FIFO32_2_128 FIFO32_2_128_inst0 (
  112.   .rst(fifo_rst),                  //FIFO 复位,高电平有效
  113.   .wr_clk(clk_200m),               //FIFO 写时钟输入
  114.   .rd_clk(clk_100m),               //FIFO 读时钟输入
  115.   .din({24'd0,wr_cnt[7:0]}),       //FIFO 写数据输入,测试数据用wr_cnt[7:0]计数器作为输入,其他高位为0
  116.   .wr_en(wr_en),                   //FIFO 写数据使能
  117.   .rd_en(rd_en),                   //FIFO 读数据使能
  118.   .dout(rd_data),                  //FIFO 读数据输出
  119.   .full(full),                     //FIFO 写通道满,该信号这里没使用
  120.   .almost_full(almost_full),       //FIFO 写通道将满,该信号这里没使用
  121.   .empty(empty),                   //FIFO 读通道空,该信号这里没使用
  122.   .almost_empty(almost_empty),     //FIFO 读通道将空,该信号这里没使用
  123.   .rd_data_count(rd_data_count),   //FIFO 读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  124.   .wr_data_count(wr_data_count)    //FIFO 写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
  125. );
  126. endmodule
复制代码
以上代码中关键的控制状态机写FIFO读FIFO的代码如下:
  1. //判断写FIFO中是否有足够的空间存放下一次写的数据
  2. always @(posedge clk_200m)begin
  3.     WR_REQ <= (wr_data_count < 10'd511);
  4. end

  5. //判断读FIFO中是否有足够的数据可以被读出
  6. always @(posedge clk_100m)begin
  7.     RD_REQ <= (rd_data_count > 8'd127);
  8. end
复制代码
对于初学者一定要注意,FIFO复位需要经过多个时钟周期后才能完成复位,所以我们这里把FIFO复位设置了常量0,让FIFO在有时钟后就能自己完成复位,FIFO 复位的信号可以根据实际情况去应用,比如我们在后面的图像缓存方面,我们会用图像的VS去复位和同步FIFO,具体的理解还要在实际应用中加深。
3.2 RTL仿真3.2.1仿真激励文件
下面我们进行仿真,如何编写tb文件,和调用仿真波形图,我就不详细讲了,不懂的人看前面FPGA入门的几个例子。我下面直接给出仿真代码。
仿真TB文件
  1. /*********************仿真文件****************************************

  2. `timescale 1ns / 1ns//仿真时间刻度/精度

  3. module tb_fifo_test;

  4. localparam SYS_TIME = 10;//系统时钟周期10ns

  5. reg I_sysclk_p;
  6. reg I_sysclk_n;//系统时钟
  7. reg I_rstn;//系统复位

  8. //例化fifo_test1
  9. fifo_test1 fifo_test1_inst
  10. (
  11. .I_sysclk_p(I_sysclk_p),
  12. .I_sysclk_n(I_sysclk_n),
  13. .I_rstn(I_rstn)
  14. );

  15. //例化fifo_test2
  16. fifo_test2 fifo_test2_inst
  17. (
  18. .I_sysclk_p(I_sysclk_p),
  19. .I_sysclk_n(I_sysclk_n),
  20. .I_rstn(I_rstn)
  21. );

  22. //例化fifo_test2
  23. fifo_test2 fifo_test2_inst
  24. (
  25. .I_sysclk_p(I_sysclk_p),
  26. .I_sysclk_n(I_sysclk_n),
  27. .I_rstn(I_rstn)
  28. );

  29. //初始化
  30. initial begin
  31.     I_sysclk_p  = 1'b0;
  32.     I_sysclk_n  = 1'b1;
  33.     I_rstn = 1'b0;
  34.     #100;//产生100ns的系统复位
  35.     I_rstn = 1'b1;//复位完成
  36.     #20000 $finish;
  37. end

  38. //产生仿真时钟
  39. always #(SYS_TIME/2) I_sysclk_p= ~I_sysclk_p;
  40. always #(SYS_TIME/2) I_sysclk_n= ~I_sysclk_n;         
  41.                   
  42. endmodule
复制代码

3.2.2仿真结果
88b1e3fec11a4e2a9370d1a6ac281b39.jpg
写入FIFO的计数器值第一个值是1,注意不是0
9bebfc5742ec47098ce2ae69caaa7624.jpg
每次写数据计数器最后一个值是512
61ddc5bd47a14b229d84cdd119237dc0.jpg
读出FIFO的计数器值1
43b30db4153d4f899193c43506678e90.jpg
最后一个读出的值,由于采取8bit写入FIFO,所以512对应的是0
3535374c4934475584631aa04a86b0e3.jpg
4关键信号法
关键信号法就是利用关键的信号,比如FIFO满标志,FIFO将满标志,FIFO空标志,FIFO将空标志,FIFO可编程空标志,和FIFO可编程满标志,FIFO读Valid标志,去控制FIFO的读写。
事实上,FIFO的满标志是正确的,也就是说FIFO输出满标志的。
修改FIFO IP 增加valid信号观测   
24c9888bd76249dcb77888baca0241f5.jpg
4.1 almost_full 和almost_empty
1)、当FIFO非满的时候写
2)、当FIFO非空的时候读

4.1.1测试代码
  1. /*************FIFO IP的仿真测试***************************************
  2. --版本号1.0
  3. --FIFO通常用于异步数据传输、数据缓存、数据位宽转换,本使用基于XILINX FIFO IP实现数据的位宽转换实验
  4. --通过FIFO实现数据缓存,以及数据位宽从32bits转为128btis
  5. --写状态机和读状态机分开运行
  6. *********************************************************************/

  7. `timescale 1ns / 1ns //仿真时间刻度/精度

  8. module fifo_test2
  9. (
  10. input I_sysclk_p,
  11. input I_sysclk_n, //系统时钟输入
  12. input I_rstn     //系统复位
  13. );
  14. wire I_clk;
  15. IBUFGDS CLK_U(
  16. .I(I_sysclk_p),
  17. .IB(I_sysclk_n),
  18. .O(I_clk)
  19. );
  20. wire clk_100m,clk_200m,clk_locked;//MMCM/PLL 时钟信号
  21. wire [127:0]rd_data; //读数据信号
  22. wire fifo_rst;       //fifo 复位,高电平有效
  23. wire full;           //FIFO满
  24. wire empty;          //FIFO空,这里没用到
  25. wire almost_full;    //FIFO将满,代表FIFO再写入1个数据就会满
  26. wire almost_empty;   //FIFO将空,代表FIFO再读出1个数据就会空
  27. wire [7 : 0] rd_data_count;//读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  28. wire [9 : 0] wr_data_count;//写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
  29. reg  [10: 0] wr_cnt;    //写数据计数器

  30. reg[9:0] rst_cnt = 10'd0; //复位计数器

  31. assign fifo_rst = (rst_cnt[9:7] == 3'b010); //产生一个高脉冲复位

  32. //MMCM/PLL 产生200M和100M时钟
  33. clk_wiz_0 clk_inst(.clk_out1(clk_200m),.clk_out2(clk_100m),.resetn(I_rstn),.locked(clk_locked),.clk_in1(I_clk));  

  34. //复位计数器模块
  35. always @(posedge clk_100m)begin
  36.     if(!clk_locked)
  37.         rst_cnt <= 10'd0;
  38.     else if(rst_cnt[9] == 1'b0)
  39.         rst_cnt <= rst_cnt + 1'b1;
  40. end

  41. // FIFO写状态机
  42. always @(posedge clk_200m)begin //写数据用200MHZ 时钟写
  43.     if(!rst_cnt[9]) //复位,重置相关寄存器
  44.         wr_cnt <= 11'd0;
  45.     else
  46.        wr_cnt <= full ? wr_cnt : wr_cnt+1'b1;//写计数器累加
  47. end

  48. FIFO32_2_128 FIFO32_2_128_inst0 (
  49.   .rst(fifo_rst),                  //FIFO 复位,高电平有效
  50.   .wr_clk(clk_200m),               //FIFO 写时钟输入
  51.   .rd_clk(clk_100m),               //FIFO 读时钟输入
  52.   .din({24'd0,wr_cnt[7:0]}),       //FIFO 写数据输入,测试数据用wr_cnt[7:0]计数器作为输入,其他高位为0
  53.   .wr_en(!almost_full&rst_cnt[9]),                   //FIFO 写数据使能
  54.   .rd_en(!almost_empty&rst_cnt[9]),                   //FIFO 读数据使能
  55.   .dout(rd_data),                  //FIFO 读数据输出
  56.   .full(full),                     //FIFO 写通道满
  57.   .almost_full(almost_full),       //FIFO 写通道将满
  58.   .empty(empty),                   //FIFO 读通道空,该信号这里没使用
  59.   .almost_empty(almost_empty),     //FIFO 读通道将空
  60.   .rd_data_count(rd_data_count),   //FIFO 读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  61.   .wr_data_count(wr_data_count)    //FIFO 写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
  62. );

  63. endmodule
复制代码

使用almost_full和almost_empyt可以防止FIFO写满和FIFO空读
4.1.2RTL仿真结果
f504e92def6e4e40ba7cab51c53bdbe6.jpg
4.2 almost_full和valid
当读FIFO里面数据有效的时候Valid为1,所以也可以用valid信号读数据
4.2.1测试代码
  1. /*************FIFO IP的仿真测试***************************************
  2. --版本号1.0
  3. --FIFO通常用于异步数据传输、数据缓存、数据位宽转换,本使用基于XILINX FIFO IP实现数据的位宽转换实验
  4. --通过FIFO实现数据缓存,以及数据位宽从32bits转为128btis
  5. --写状态机和读状态机分开运行
  6. *********************************************************************/

  7. `timescale 1ns / 1ns //仿真时间刻度/精度

  8. module fifo_test3
  9. (
  10. input I_sysclk_p,
  11. input I_sysclk_n, //系统时钟输入
  12. input I_rstn     //系统复位
  13. );
  14. wire I_clk;
  15. IBUFGDS CLK_U(
  16. .I(I_sysclk_p),
  17. .IB(I_sysclk_n),
  18. .O(I_clk)
  19. );
  20. wire clk_100m,clk_200m,clk_locked;//MMCM/PLL 时钟信号
  21. wire [127:0]rd_data; //读数据信号
  22. wire fifo_rst;       //fifo 复位,高电平有效
  23. wire full;           //FIFO满,这里没用到
  24. wire empty;          //FIFO空,这里没用到
  25. wire almost_full;    //FIFO将满,代表FIFO再写入1个数据就会满
  26. wire almost_empty;   //FIFO将空,代表FIFO再读出1个数据就会空,这里没用到
  27. wire [7 : 0] rd_data_count;//读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  28. wire [9 : 0] wr_data_count;//写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
  29. reg [10 : 0] wr_cnt;    //写数据计数器
  30. wire         valid ;    //读通道数据有效

  31. reg[9:0] rst_cnt = 10'd0; //复位计数器

  32. assign fifo_rst = (rst_cnt[9:7] == 3'b010); //产生一个高脉冲复位

  33. //MMCM/PLL 产生200M和100M时钟
  34. clk_wiz_0 clk_inst(.clk_out1(clk_200m),.clk_out2(clk_100m),.resetn(I_rstn),.locked(clk_locked),.clk_in1(I_clk));  

  35. //复位计数器模块
  36. always @(posedge clk_100m)begin
  37.     if(!clk_locked)
  38.         rst_cnt <= 10'd0;
  39.     else if(rst_cnt[9] == 1'b0)
  40.         rst_cnt <= rst_cnt + 1'b1;
  41. end

  42. // FIFO写状态机
  43. always @(posedge clk_200m)begin //写数据用200MHZ 时钟写
  44.     if(!rst_cnt[9]) //复位,重置相关寄存器
  45.         wr_cnt <= 11'd0;
  46.     else
  47.        wr_cnt <= almost_full? wr_cnt : wr_cnt+1'b1;//写计数器累加
  48. end

  49. FIFO32_2_128 FIFO32_2_128_inst0 (
  50.   .rst(fifo_rst),                  //FIFO 复位,高电平有效
  51.   .wr_clk(clk_200m),               //FIFO 写时钟输入
  52.   .rd_clk(clk_100m),               //FIFO 读时钟输入
  53.   .din({24'd0,wr_cnt[7:0]}),       //FIFO 写数据输入,测试数据用wr_cnt[7:0]计数器作为输入,其他高位为0
  54.   .wr_en(!almost_full&rst_cnt[9]),                   //FIFO 写数据使能
  55.   .rd_en(valid),                   //FIFO 读数据使能
  56.   .dout(rd_data),                  //FIFO 读数据输出
  57.   .full(full),                     //FIFO 写通道满,该信号这里没使用
  58.   .almost_full(almost_full),       //FIFO 写通道将满,该信号这里没使用
  59.   .empty(empty),                   //FIFO 读通道空,该信号这里没使用
  60.   .valid(valid),                  // FIFO 读通道,数据有效
  61.   .almost_empty(almost_empty),     //FIFO 读通道将空,该信号这里没使用
  62.   .rd_data_count(rd_data_count),   //FIFO 读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  63.   .wr_data_count(wr_data_count)    //FIFO 写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
  64. );

  65. endmodule
复制代码
4.2.2RTL仿真结果
4416f7f708dd4af48e7bc5adb62b81bc.jpg
对于代码简单修改,仿真可以线点击1的位置重新load代码,然后再点击2运行。运行一会可以手动停止。
3c582fd8da444ddba8e798b26e0859b8.jpg
相信阅读完本文,根据本文把FIFO的例子做一遍你就能基本掌握FIFO的使用了,我是比较推荐第一种的半空半满法。
对于Stream接口的FIFO使用起来也差不多,因为本质上他们都是FIFO。在后期的课程中你们会看到Stream FIFO的应用,使用Steam接口有利于在新的FPGA设计中统一接口,方便代码的标准化,我们这里暂时就不讨论了。


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则