ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

【正点原子MP157连载】 第十五章 IP核之FIFO实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

2022-01-22 16:34:08  阅读:204  来源: 互联网

标签:核之 wire almost IP fifo 原子 正点 FIFO 时钟


1)实验平台:正点原子STM32MP157开发板
2)购买链接:https://item.taobao.com/item.htm?&id=629270721801
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子STM32MP157技术交流群:691905614
在这里插入图片描述

第十五章 IP核之FIFO实验

FIFO的英文全称是First In First Out,即先进先出。FPGA使用的FIFO一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递。它与FPGA内部的RAM和ROM的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像RAM和ROM那样可以由地址线决定读取或写入某个指定的地址。本章我们将对Vivado软件生成的FIFO IP核进行读写测试,来向大家介绍Xilinx FIFO IP核的使用方法。
本章包括以下几个部分:
15.1 FIFO IP核简介
15.2 实验任务
15.3 硬件设计
15.4 程序设计
15.5 下载验证

15.1 FIFO IP核简介
根据FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。Xilinx的FIFO IP核可以被配置为同步FIFO或异步FIFO,其信号框图如下图所示。从图中可以了解到,当被配置为同步FIFO时,只使用wr_clk,所有的输入输出信号都同步于wr_clk信号。而当被配置为异步FIFO时,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟wr_clk,所有与读相关的信号都是同步于读时钟rd_clk。
在这里插入图片描述

图 7.5.13.1 Xilinx的FIFO IP核的信号框图
对于FIFO需要了解一些常见参数:
FIFO的宽度:FIFO一次读写操作的数据位N。
FIFO的深度:FIFO可以存储多少个宽度为N位的数据。
将空标志:almost_empty。FIFO即将被读空。
空标志:empty。FIFO已空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出。
将满标志:almost_full。FIFO即将被写满。
满标志:full。FIFO已满或将要写满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出。
读时钟:读FIFO时所遵循的时钟,在每个时钟的上升沿触发。
写时钟:写FIFO时所遵循的时钟,在每个时钟的上升沿触发。
这里请注意,“almost_empty”和“almost_full”这两个信号分别被看作“empty”和“full”的警告信号,他们相对于真正的空(empty)和满(full)都会提前一个时钟周期拉高。
对于FIFO的基本知识先了解这些就足够了,可能有人会好奇为什么会有同步FIFO和异步FIFO,它们各自的用途是什么。之所以有同步FIFO和异步FIFO是因为各自的作用不同。同步FIFO常用于同步时钟的数据缓存,异步FIFO常用于跨时钟域的数据信号的传递,例如时钟域A下的数据data1传递给异步时钟域B,当data1为连续变化信号时,如果直接传递给时钟域B则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态问题在内的一系列问题,使用异步FIFO能够将不同时钟域中的数据同步到所需的时钟域中。
15.2 实验任务
本节的实验任务是使用Vivado生成FIFO IP核,并实现以下功能:当FIFO为空时,向FIFO中写入数据,写入的数据量和FIFO深度一致,即FIFO被写满;然后从FIFO中读出数据,直到FIFO被读空为止,以此向大家详细介绍一下FIFO IP核的使用方法。
15.3 硬件设计
本章实验只用到了输入的时钟信号和按键复位信号,没有用到其它硬件外设。
本实验中,各端口信号的管脚分配如下表所示。
表 15.3.1 IP实验管脚分配
信号名 方向 管脚 端口说明 电平标准
sys_clk input U18 系统时钟,50Mhz LVCMOS33
sys_rst_n input N16 系统复位,低有效 LVCMOS33
对应的XDC约束语句如下所示:

create_clock -period 20.000 -name clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

15.4 程序设计
根据实验任务要求和模块化设计的思想,我们需要如下4个模块:fifo IP核、写fifo模块、读fifo模块以及顶层例化模块实现前三个模块的信号交互。由于FIFO多用于跨时钟域信号的处理,所以本实验我们使用异步FIFO来向大家详细介绍双时钟FIFO IP核的创建和使用。为了方便大家理解,这里我们将读/写时钟都用系统时钟来驱动。系统的功能框图如下图所示:
在这里插入图片描述

图 7.5.13.1 系统框图
首先创建一个名为ip_fifo的工程,接下来我们创建fifo IP核。在Vivado软件的左侧“Flow Navigator”栏中单击“IP Catalog”,“IP Catalog”按钮以及单击后弹出的“IP Catalog”窗口如下图所示。
在这里插入图片描述

图 7.5.13.2 “IP Catalog”按钮
在这里插入图片描述

图 7.5.13.3 “IP Catalog”窗口
在“IP Catalog”窗口中,在搜索栏中输入“fifo”关键字,这时Vivado会自动查找出与关键字匹配的IP核名称,我们双击“FIFO Generator”,如下图所示。
在这里插入图片描述

图 7.5.13.4 搜索栏中输入关键字
弹出“Customize IP”窗口,如下图所示。
在这里插入图片描述

图 7.5.13.5 “Customize IP”窗口
接下来就是配置IP核的时钟参数的过程。
最上面的“Component Name”一栏设置该IP元件的名称,这里保持默认即可。在第一个“Basic”选项卡中,“Interface Type”选项用于选择FIFO接口的类型,这里我们选择默认的“Native”,即传统意义上的FIFO接口。“Fifo Implementation”选项用于选择我们想要实现的是同步FIFO还是异步FIFO以及使用哪种资源实现FIFO,这里我们选择“Independent Clocks Block RAM”,即使用块RAM来实现的异步FIFO。如下图所示。
在这里插入图片描述

图 7.5.13.6 “Basic”选项卡
接下来是“Native Ports”选项卡,用于设置FIFO端口的参数。“Read Mode”选项用于设置读FIFO时的读模式,这里我们选择默认的“Standard FIFO”。“Data Port Parameters”一栏用于设置读写端口的数据总线的宽度以及FIFO的深度,写宽度“Write Width”我们设置为8位,写深度“Write Depth”我们设置为256,注意此时FIFO IP核所能实现的实际深度却是255;虽然读宽度“Read Width”能够设置成和写宽度不一样的位宽,且此时读深度“Read Depth”会根据上面三个参数被动地自动设置成相应的值;但是我们还是将读宽度“Read Width”设置成和写宽度“Write Width”一样的位宽,这也是在实际应用中最常用的情况。由于我们只是观察FIFO的读写,所以最下面的“Reset Pin”选项我们可以不使用,把它取消勾选。其他设置保持默认即可,如下图所示。
在这里插入图片描述

图 7.5.13.7 “Native Ports”选项卡
“Status Flags”选项卡,用于设置用户自定义接口或者用于设定专用的输入口。这里我们使用“即将写满”和“即将读空”这两个信号,所以我们把它们勾选上,其他保持默认即可,如下图所示。
在这里插入图片描述

图 7.5.13.8 “Status Flags”选项卡
“Data Counts”选项卡用于设置FIFO内数据计数的输出信号,此信号表示当前在FIFO内存在多少个有效数据。为了更加方便地观察读/写过程,这里我们把读/写端口的数据计数都打开,且计数值总线的位宽设置为满位宽,即8位,如下图所示。
在这里插入图片描述

图 7.5.13.9 “Data Counts”选项卡
最后的“Summary”选项卡是对前面所有配置的一个总结,在这里我们直接点击“OK”按钮即可,如下图所示。
在这里插入图片描述

图 7.5.13.10 “Summary”选项卡
接着就弹出了“Genarate Output Products”窗口,我们直接点击“Generate”即可,如下图所示。
在这里插入图片描述

图 7.5.13.11 “Genarate Output Products”窗口
之后我们就可以在“Design Run”窗口的“Out-of-Context Module Runs”一栏中出现了该IP核对应的run“fifo_generator_0_synth_1”,其综合过程独立于顶层设计的综合,所以在我们可以看到其正在综合,如下图所示。
在这里插入图片描述

图 7.5.13.12 “fifo_generator _0_synth_1”run
在其Out-of-Context综合的过程中,我们就可以进行RTL编码了。首先打开IP核的例化模板,在“Source”窗口中的“IP Sources”选项卡中,依次用鼠标单击展开“IP”-“fifo_generator _0”-“Instantitation Template”,我们可以看到“fifo_generator_0.veo”文件,它是由IP核自动生成的只读的verilog例化模板文件,双击就可以打开它,如下图所示。
在这里插入图片描述

图 7.5.13.13 “fifo_generator_0.veo”文件
我们创建一个verilog源文件,其名称为ip_fifo.v,作为顶层模块,其代码如下:

1  module ip_fifo(
2      input    sys_clk ,  // 时钟信号
3      input    sys_rst_n  // 复位信号
4  );
5  
6  //wire define
7  wire         fifo_wr_en         ;  // FIFO写使能信号
8  wire         fifo_rd_en         ;  // FIFO读使能信号
9  wire  [7:0]  fifo_din           ;  // 写入到FIFO的数据
10 wire  [7:0]  fifo_dout          ;  // 从FIFO读出的数据
11 wire         almost_full        ;  // FIFO将满信号
12 wire         almost_empty       ;  // FIFO将空信号
13 wire         fifo_full          ;  // FIFO满信号
14 wire         fifo_empty         ;  // FIFO空信号
15 wire  [7:0]  fifo_wr_data_count ;  // FIFO写时钟域的数据计数
16 wire  [7:0]  fifo_rd_data_count ;  // FIFO读时钟域的数据计数
17 
18 //*****************************************************
19 //**                    main code
20 //*****************************************************
21 
22 //例化FIFO IP核
23 fifo_generator_0  fifo_generator_0 (
24     .wr_clk        ( sys_clk            ),  // input wire wr_clk
25     .rd_clk        ( sys_clk            ),  // input wire rd_clk
26 
27     .wr_en         ( fifo_wr_en         ),  // input wire wr_en
28     .rd_en         ( fifo_rd_en         ),  // input wire rd_en
29 
30     .din           ( fifo_din           ),  // input wire [7 : 0] din
31     .dout          ( fifo_dout          ),  // output wire [7 : 0] dout
32     
33     .almost_full   (almost_full         ),  // output wire almost_full
34     .almost_empty  (almost_empty        ),  // output wire almost_empty
35     .full          ( fifo_full          ),  // output wire full
36     .empty         ( fifo_empty         ),  // output wire empty
37 
38  .wr_data_count ( fifo_wr_data_count ),  // output wire [7 : 0] wr_data_count    
39  .rd_data_count ( fifo_rd_data_count )   // output wire [7 : 0] rd_data_count
40 );
41 
42 //例化写FIFO模块
43 fifo_wr  u_fifo_wr(
44     .clk            ( sys_clk    ),   // 写时钟
45     .rst_n          ( sys_rst_n  ),   // 复位信号
46 
47     .fifo_wr_en     ( fifo_wr_en )  , // fifo写请求
48     .fifo_wr_data   ( fifo_din    ) , // 写入FIFO的数据
49     .almost_empty   ( almost_empty ), // fifo将空信号
50     .almost_full    ( almost_full  )  // fifo将满信号
51 );
52 
53 //例化读FIFO模块
54 fifo_rd  u_fifo_rd(
55     .clk          ( sys_clk    ),      // 读时钟
56     .rst_n        ( sys_rst_n  ),      // 复位信号
57 
58     .fifo_rd_en   ( fifo_rd_en ),      // fifo读请求
59     .fifo_dout    ( fifo_dout  ),      // 从FIFO输出的数据
60     .almost_empty ( almost_empty ),    // fifo将空信号
61     .almost_full  ( almost_full  )     // fifo将满信号
62 );
63 
64 //例化ILA IP核
65 ila_0  ila_0 (
66  .clk    ( sys_clk            ), // input wire clk
67 
68  .probe0 ( fifo_wr_en         ), // input wire [0:0]  probe0  
69  .probe1 ( fifo_rd_en         ), // input wire [0:0]  probe1 
70  .probe2 ( fifo_din           ), // input wire [7:0]  probe2 
71  .probe3 ( fifo_dout          ), // input wire [7:0]  probe3 
72  .probe4 ( fifo_empty         ), // input wire [0:0]  probe4 
73  .probe5 ( almost_empty       ), // input wire [0:0]  probe5 
74  .probe6 ( fifo_full          ), // input wire [0:0]  probe6
75  .probe7 ( almost_full        ), // input wire [0:0]  probe7 
76  .probe8 ( fifo_wr_data_count ), // input wire [7:0]  probe8 
77  .probe9( fifo_rd_data_count  )  // input wire [7:0]  probe9
78 );
79 
80 endmodule 

顶层模块主要是对FIFO IP核、写FIFO模块、读FIFO模块进行例化,除此之外本实验还生成并例化了一个ILA IP核,用于对顶层模块信号的进行在线捕获观察。
写FIFO模块fifo_wr.v源文件的代码如下:

1  module fifo_wr(
2      input                  clk    ,          // 时钟信号
3      input                  rst_n  ,          // 复位信号
4       
5      input                  almost_empty,     // FIFO将空信号
6      input                  almost_full ,     // FIFO将满信号
7      output    reg          fifo_wr_en ,      // FIFO写使能
8      output    reg  [7:0]   fifo_wr_data      // 写入FIFO的数据
9  );
10 
11 //reg define
12 reg  [1:0]  state            ;  //动作状态
13 reg         almost_empty_d0  ;  //almost_empty 延迟一拍
14 reg         almost_empty_syn ;  //almost_empty 延迟两拍
15 reg  [3:0]  dly_cnt          ;  //延迟计数器
16 //*****************************************************
17 //**                    main code
18 //*****************************************************
19 
20 //因为 almost_empty 信号是属于FIFO读时钟域的
21 //所以要将其同步到写时钟域中
22 always@( posedge clk ) begin
23  if( !rst_n ) begin
24      almost_empty_d0  <= 1'b0 ;
25      almost_empty_syn <= 1'b0 ;
26  end
27  else begin
28      almost_empty_d0  <= almost_empty ;
29      almost_empty_syn <= almost_empty_d0 ;
30  end
31 end
32 
33 //向FIFO中写入数据
34 always @(posedge clk ) begin
35     if(!rst_n) begin
36         fifo_wr_en   <= 1'b0;
37         fifo_wr_data <= 8'd0;
38         state        <= 2'd0;
39      dly_cnt      <= 4'd0;
40     end
41     else begin
42         case(state)
43             2'd0: begin 
44                 if(almost_empty_syn) begin  //如果检测到FIFO将被读空(下一拍就会空)
45                     state <= 2'd1;          //就进入延时状态
46                 end 
47                 else
48                     state <= state;
49             end 
50          2'd1: begin
51                 if(dly_cnt == 4'd10) begin  //延时10拍
52                                             //原因是FIFO IP核内部状态信号的更新存在延时
53                                             //延迟10拍以等待状态信号更新完毕                   
54                     dly_cnt    <= 4'd0;     
55                  state      <= 2'd2;        //开始写操作
56                  fifo_wr_en <= 1'b1;        //打开写使能
57              end
58              else
59                  dly_cnt <= dly_cnt + 4'd1;
60             end             
61          2'd2: begin
62                 if(almost_full) begin      //等待FIFO将被写满(下一拍就会满)
63                     fifo_wr_en   <= 1'b0;  //关闭写使能
64                     fifo_wr_data <= 8'd0;
65                     state        <= 2'd0;  //回到第一个状态
66                 end
67                 else begin                 //如果FIFO没有被写满
68                     fifo_wr_en   <= 1'b1;  //则持续打开写使能
69                     fifo_wr_data <= fifo_wr_data + 1'd1;  //且写数据值持续累加
70                 end
71             end 
72          default : state <= 2'd0;
73         endcase
74     end
75 end
76 
77 endmodule

fifo_wr模块的核心部分是一个不断进行状态循环的小状态机,如果检测到FIFO为空,则先延时10拍,这里注意,由于FIFO的内部信号的更新比实际的数据读/写操作有所延时,所以延时10拍的目的是等待FIFO的空/满状态信号、数据计数信号等信号的更新完毕之后再进行FIFO写操作,如果写满,则回到状态0,即等待FIFO被读空,以进行下一轮的写操作。
读FIFO模块fifo_rd.v源文件的代码如下:

1  module fifo_rd(
2      input               clk          ,   // 时钟信号
3      input               rst_n        ,   // 复位信号
4  
5      input        [7:0]  fifo_dout    ,   // 从FIFO读出的数据
6      input               almost_full  ,   // FIFO将满信号
7      input               almost_empty ,   // FIFO将空信号
8      output  reg         fifo_rd_en       // FIFO读使能
9  );
10 
11 //reg define
12 reg  [1:0]  state           ;  //状态
13 reg         almost_full_d0  ;  //almost_full延迟一拍
14 reg         almost_full_syn ;  //almost_full延迟两拍
15 reg  [3:0]  dly_cnt         ;  //延迟计数器
16 
17 //*****************************************************
18 //**                    main code
19 //*****************************************************
20 
21 //因为 fifo_full 信号是属于FIFO写时钟域的
22 //所以要将其同步到读时钟域中
23 always@( posedge clk ) begin
24  if( !rst_n ) begin
25      almost_full_d0  <= 1'b0 ;
26      almost_full_syn <= 1'b0 ;
27  end
28  else begin
29      almost_full_d0  <= almost_full ;
30      almost_full_syn <= almost_full_d0 ;
31  end
32 end
33 
34 //读出FIFO的数据
35 always @(posedge clk ) begin
36     if(!rst_n) begin
37         fifo_rd_en <= 1'b0;
38      state      <= 2'd0;
39      dly_cnt    <= 4'd0;
40     end
41     else begin
42         case(state)
43             2'd0: begin                     
44                 if(almost_full_syn)        //如果检测到FIFO被写满
45                     state <= 2'd1;         //就进入延时状态
46                 else
47                     state <= state;
48             end 
49          2'd1: begin
50                 if(dly_cnt == 4'd10) begin //延时10拍
51                                            //原因是FIFO IP核内部状态信号的更新存在延时
52                                            //延迟10拍以等待状态信号更新完毕
53                     dly_cnt <= 4'd0;
54                  state   <= 2'd2;          //开始读操作
55              end
56              else
57                  dly_cnt <= dly_cnt + 4'd1;
58             end
59          2'd2: begin
60                 if(almost_empty) begin     //等待FIFO将被读空(下一拍就会空)
61                     fifo_rd_en <= 1'b0;    //关闭读使能
62                     state      <= 2'd0;    //回到第一个状态
63                 end
64                 else                       //如果FIFO没有被读空
65                     fifo_rd_en <= 1'b1;    //则持续打开读使能
66             end 
67          default : state <= 2'd0;
68         endcase
69     end
70 end
71 
72 endmodule

读模块的代码结构与写模块几乎一样,也是使用一个不断进行状态循环的小的状态机来控制操作过程,读者参考着代码应该很容易能够理解,这里就不再赘述。
我们对代码进行仿真,TestBench中只要送出时钟的复位信号即可。TB文件如下:

1  `timescale 1ns / 1ps
2  module tb_ip_fifo( );
3      // Inputs
4      reg sys_clk;
5      reg sys_rst_n;
6      
7  // Instantiate the Unit Under Test (UUT)
8  ip_fifo  u_ip_fifo (
9          .sys_clk         (sys_clk), 
10         .sys_rst_n       (sys_rst_n)
11     );
12     
13 //Genarate the clk
14 parameter PERIOD = 20;
15 always begin
16     sys_clk = 1'b0;
17     #(PERIOD/2) sys_clk = 1'b1;
18     #(PERIOD/2);
19 end   
20    
21 initial begin
22     // Initialize Inputs
23     sys_rst_n = 0;
24     // Wait 100 ns for global reset to finish
25     #100  ;
26     sys_rst_n = 1;
27     // Add stimulus here 
28 end
29 
30 endmodule

写满后转为读的仿真波形图如下图所示:
在这里插入图片描述

图 7.5.13.14 Vivado仿真波形1
由波形图可知,当写满255个数据后,fifo_full满信号就会拉高。经过延时之后,fifo_rd_en写使能信号拉高,经过一拍之后就开始将fifo中的数据送到fifo_dout端口上。
写满后转为读的仿真波形图如下图所示:
在这里插入图片描述

图 7.5.13.15 Vivado仿真波形2
由波形图可知,当读完255个数据后,fifo_empty空信号就会拉高。经过延时之后,fifo_wr_en写使能信号拉高,经过一拍之后就开始向fifo中继续写入数据。
15.5 下载验证
编译工程并生成比特流.bit文件,将比特流.bit文件下载到Zynq中。
下载完成后,接下来在Vivado中会自动出现“hw_ila_1”Dashboard窗口。如下图所示:
在这里插入图片描述

图 7.5.13.1 将探针信号添加到波形窗口中
将有关探针信号添加到波形窗口中,这里我们已经完成信号的添加,方法是点击“hw_ila_1”Dashboard窗口左上角的“+”。同时我们在窗口右下角将“fifo_rd_en”信号添加到触发窗口中且设置为上升沿触发,
单击左上角的触发按钮,如下图所示:
在这里插入图片描述

图 7.5.13.2 触发按钮
最后就看到了ILA捕获得到的数据,展开波形图如下图所示:
在这里插入图片描述

图 7.5.13.3 捕获得到的波形图
从捕获得到的波形图中可以看出,其逻辑行为与仿真波形图中的一致,证明我们的代码正确地实现了预期的功能.

标签:核之,wire,almost,IP,fifo,原子,正点,FIFO,时钟
来源: https://blog.csdn.net/weixin_55796564/article/details/122639246

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有