zoukankan      html  css  js  c++  java
  • FIFO IP核

    转载:

    说白了,IP核就是别人做好了的硬件模块,提供完整的用户接口和说明文档,更复杂的还有示例工程,你只要能用好这个IP核,设计已经完成一半了。说起来容易,从冗长的英文文档和网上各个非标准教程中汲取所需,并灵活运用还是需要下一番功夫的。

      我认为其中最重要的几点如下:

      1) 提供给IP核正确的时钟和复位条件;

      2) 明确各个重要用户接口功能;

      3) 掌握所需指令的操作时序;

      4) 知道内部寄存器地址及功能和配置方式、顺序;

      5) 会从官方示例工程中学会IP核正确使用方式;

      今天来讲讲一个最常用的IP核,FIFO。可以说它是FPGA能如此灵活处理数据的基础,常用于异步时钟域处理、位宽转换以及需要数据缓存的场合。先来说明下,对于初学者和刚接触一个IP核的人来说,不要过分关注IP核的每一个参数和功能,更没必要知道内部的具体结构和工作原理(还没忘之前使用的ILA吧,反正我是不知道具体怎么设计出来的)只需掌握最常用的和最重要的,把IP核用起来就大功告成了。先从生成IP核开始吧:

      配置向导中第一页中是选择FIFO的接口模式和实现方式。这里我们用原始的接口方式。箭头处是实现方式,如果需要异步时钟域处理选择读写独立时钟模式。

      第二页中需要特意强调的是读模式的选择。其实这里的First Word Fall Through对应的就是Altera FPGA中FIFO IP核读模式中的Show ahead模式嘛,换个名字而已。这个读模式的特点是在读使能有效之前,即把FIFO中第一个数据从读数据端口持续送出。在这种模式下,读使能信号倒像是“读清”信号,把上一次的数据清除掉,让FIFO送出下一个数据。这样做的处是符合dout 和dout_vld相配合的输出信号方式。

      第三页是配置一些可选的标志位,可以根据需要灵活实现一些标志位和握手特性(我是从来没用过)。

      第四页可选FIFO内缓存数据量计数器,由于我开始选择的是异步FIFO模式,所以此处有两个计数器分别与读侧和写侧时钟上升沿同步。注意一点:这两个计数器均表示FIFO缓存数据量,只不过在时钟上有些偏差,切不可错误理解为是写入了或者读出了多少个数据。

      最后总结页,把前边的参数和配置汇总下。没有问题可以点击OK了!

      IP核生成好了,接下来要正确用起来。我们把以太网接口数据传输作为案例背景,通常来说是FPGA逻辑+MAC IP核+外部PHY芯片的架构。若想让MAC IP核正确接收待发送数据,需要将数据进行封包并加入MAC头部信息。

      为简化设计,先只考虑对封包后数据添加MAC头部的功能,也就是说此时输入的数据即是长度符合以太网规范,且具有数据包格式的数据。由于在数据部分输出前加额外的信息,所以先要缓存输入的数据直到MAC头输出完成再将写入数据发送出来,因此需要用FIFO缓存数据。进一步分析,经过封包后的数据格式如下:

      其中sop和eop分别是包头,包尾指示信号,data_vld是数据有效指示信号。由于数据位宽此处是32位,而数据的最小单元是字节,所以每个32位数据不一定包含4个字节有效数据,使用data_mod指示出无效字节数。为了让该模块输出端知道何时输出完一个数据包,要把eop信号和数据信号拼接写入FIFO中,这样输出端发出eop时进入新一轮循环。如果根据写入sop信号来作为开始发送MAC头部和数据部分的标志,试想当一个短包紧跟着一个长包写进FIFO中时,输出端正在送出上一长包剩下的几个数据,无法响应短包的sop信号指示,那么短包即被“丢弃”了。为了避免丢包现象,需要满足“读写隔离规则”,即FIFO读操作和写操作两者不能根据一方的情况来决定另一方的行为。进一步引出“双FIFO架构”,使用数据FIFO缓存数据,而信息FIFO保留指示信息,这样讲写侧的指示信号写入信息FIFO中,数据FIFO可以根据信息FIFO读侧的信息来判断读的行为,也就满足了读写隔离规则。

      在该模块中,可以在写侧出现sop信号时写入信息FIFO一个指示信息,所以当信息FIFO非空即表示有一个数据包正在进来,此时发送MAC头信息,随之读取数据FIFO中缓存数据,当读侧出现eop信号则读清信息FIFO,循环往复完成了添加头部信息的工作。

      MAC头部信息为14字节,而数据位宽是32位,即一次发送四个字节,所以相当于头部为三个半数据。因此在发送第三个头部数据时,低16位要用数据部分填充,后边的数据也要跟着移位。如此移位操作后,数据部分就晚了一拍输出最后16位。如果最后这16位数据中有有效字节,那么mac_data_vld当前节拍也要有效,且mac_data_eop和mac_data_mod跟着晚一拍输出;如果无有效字节,则按照正常情况输出。在代码中我使用end_normal和end_lag信号来区分上述两种情况。需要特别注意的是,在移位操作后数据包中包含的无效字节个数也会发生变化。为了理清思路和时序,画出核心信号时序图:

     有了项目需求,设计思路后明确模块接口列表:

     

      开始编写代码了:

    复制代码
      1 `timescale 1ns / 1ps
      2 
      3 module add_mac_head(
      4     input clk,
      5     input rst_n,
      6     input [31:0] app_data,
      7     input app_data_vld,
      8     input app_data_sop,
      9     input app_data_eop,
     10     input [1:0] app_data_mod,//无效字节数
     11 
     12     input mac_tx_rdy,//MAC IP发送准备就绪信号
     13     output reg [31:0] mac_data,
     14     output reg mac_data_vld,
     15     output reg mac_data_sop,
     16     output reg mac_data_eop,
     17     output reg [1:0] mac_data_mod
     18     );
     19     
     20     reg [34:0] wdata;
     21     reg wrreq,rdreq;
     22     reg wdata_xx;
     23     reg wrreq_xx,rdreq_xx;
     24     reg [1:0] head_cnt;
     25     reg head_flag,head_tmp,rd_flag,rd_flag_tmp;
     26     reg [34:0] q_tmp;
     27     
     28     wire [31:0] data_shift;
     29     wire add_head_cnt,end_head_cnt;
     30     wire head_neg;
     31     wire [34:0] q;
     32     wire rdempty_xx;
     33     wire sop_in;
     34     wire [2:0] head_len;
     35     wire [111:0] mac_head;
     36     wire [47:0] des_mac,sour_mac;
     37     wire [15:0] pack_type;
     38     wire rd_neg;
     39     
     40     fifo_generator_0 fifo_data (
     41   .clk(clk),      // input wire clk
     42   .din(wdata),      // input wire [34 : 0] din
     43   .wr_en(wrreq),  // input wire wr_en
     44   .rd_en(rdreq),  // input wire rd_en
     45   .dout(q),    // output wire [34 : 0] dout
     46   .full(),    // output wire full
     47   .empty()  // output wire empty
     48 );
     49 
     50     fifo_generator_1 fifo_message (
     51   .clk(clk),      // input wire clk
     52   .din(wdata_xx),      // input wire [0 : 0] din
     53   .wr_en(wrreq_xx),  // input wire wr_en
     54   .rd_en(rdreq_xx),  // input wire rd_en
     55   .dout(),    // output wire [0 : 0] dout
     56   .full(),    // output wire full
     57   .empty(rdempty_xx)  // output wire empty
     58 );
     59     
     60     //数据fifo写数据
     61     always@(posedge clk or negedge rst_n)begin
     62         if(!rst_n)
     63             wdata <= 0;
     64         else if(app_data_vld)    
     65             wdata <= {app_data_eop,app_data_mod,app_data};
     66     end
     67     
     68     always@(posedge clk or negedge rst_n)begin
     69         if(!rst_n)
     70             wrreq <= 0;
     71         else if(app_data_vld)
     72             wrreq <= 1;
     73         else 
     74             wrreq <= 0;
     75     end
     76     
     77     always@(posedge clk or negedge rst_n)begin
     78         if(!rst_n)
     79             wdata_xx <= 0;
     80         else if(sop_in)
     81             wdata_xx <= 1;
     82         else 
     83             wdata_xx <= 0;
     84     end
     85     
     86     assign sop_in = app_data_vld && app_data_sop;
     87     
     88     //当写侧出现sop时表明有一个数据包正在写入,此时写信息FIFO任意数据告知读侧开始发送MAC头部信息
     89     always@(posedge clk or negedge rst_n)begin
     90         if(!rst_n)
     91             wrreq_xx <= 0;
     92         else if(sop_in)
     93             wrreq_xx <= 1;
     94         else 
     95             wrreq_xx <= 0;
     96     end
     97     
     98     //MAC头部有14个字节 数据位宽是32位,即一个数据4个字节,需要发送4个数据(最后一个数据只有2个字节是头部)
     99     always@(posedge clk or negedge rst_n)begin
    100         if(!rst_n)
    101             head_cnt <= 0;
    102         else if(add_head_cnt)begin
    103             if(end_head_cnt)
    104                 head_cnt <= 0;
    105             else 
    106                 head_cnt <= head_cnt + 1'b1;
    107         end
    108     end
    109     
    110     assign add_head_cnt = head_flag && mac_tx_rdy;
    111     assign end_head_cnt = add_head_cnt && head_cnt == head_len - 1 - 1;
    112     assign head_len = 4;
    113     
    114     //发送MAC头部标志位
    115     always@(posedge clk or negedge rst_n)begin
    116         if(!rst_n)
    117             head_flag <= 0;
    118         else if(end_head_cnt)
    119             head_flag <= 0;
    120         else if(!rdempty_xx && !rd_flag)
    121             head_flag <= 1;
    122     end
    123     
    124     //读数据FIFO标志位
    125     always@(posedge clk or negedge rst_n)begin
    126         if(!rst_n)
    127             rd_flag <= 0;
    128         else if(end_head_cnt)
    129             rd_flag <= 1;
    130         else if(rd_eop)
    131             rd_flag <= 0;
    132     end
    133     
    134     assign rd_eop = rdreq && q[34];
    135     
    136     always@(*)begin
    137         if(rd_flag && mac_tx_rdy)
    138             rdreq <= 1;
    139         else
    140             rdreq <= 0;
    141     end
    142     
    143     //读侧出现eop读取完整版报文,此时读清信息FIFO
    144     always@(*)begin
    145         if(rd_eop)
    146             rdreq_xx <= 1;
    147         else
    148             rdreq_xx <= 0;
    149     end
    150     
    151     //寄存头部标志位找出下降沿
    152     always@(posedge clk or negedge rst_n)begin
    153         if(!rst_n)
    154             head_tmp <= 0;
    155         else 
    156             head_tmp <= head_flag;
    157     end
    158     
    159     assign head_neg = head_flag == 0 && head_tmp == 1;
    160     
    161     //寄存q用于移位操作
    162     always@(posedge clk or negedge rst_n)begin
    163         if(!rst_n)
    164             q_tmp <= 0;
    165         else 
    166             q_tmp <= q;
    167     end
    168     
    169     assign data_shift = {q_tmp[15:0],q[31:16]};
    170     
    171     //MAC头 14字节
    172     assign mac_head  = {des_mac,sour_mac,pack_type};
    173     assign des_mac        = 48'hD0_17_C2_00_E5_40  ;//目的MAC PC网卡物理地址
    174     assign sour_mac       = 48'h01_02_03_04_05_06  ;//源MAC地址为01_02_03_04_05_06
    175     assign pack_type      = 16'h0800               ;//IP数据报
    176     
    177     always@(posedge clk or negedge rst_n)begin
    178         if(!rst_n)
    179             rd_flag_tmp <= 0;
    180         else 
    181             rd_flag_tmp <= rd_flag;
    182     end
    183     
    184     assign rd_neg = rd_flag == 0 && rd_flag_tmp == 1;
    185     
    186     //数据输出
    187     always@(posedge clk or negedge rst_n)begin
    188         if(!rst_n)
    189             mac_data_sop <= 0;
    190         else if(add_head_cnt && head_cnt == 0)
    191             mac_data_sop <= 1;
    192         else 
    193             mac_data_sop <= 0;
    194     end
    195     
    196     always@(posedge clk or negedge rst_n)begin
    197         if(!rst_n)
    198             mac_data_eop <= 0;
    199         else if(end_normal || end_lag)
    200             mac_data_eop <= 1;
    201         else 
    202             mac_data_eop <= 0;
    203     end
    204     
    205     assign end_normal = rd_eop && q[33:32]      > 2'd1;
    206     assign end_lag    = rd_neg && q_tmp[33:32] <= 2'd1;
    207     
    208     always@(posedge clk or negedge rst_n)begin
    209         if(!rst_n)
    210             mac_data <= 0;
    211         else if(add_head_cnt)//由于MAC不是32位数据的整数倍,需要对数据进行移位
    212             mac_data <= mac_head[111 - head_cnt*32 -: 32];
    213         else if(head_neg)
    214             mac_data <= {mac_head[15:0],q[31:16]};
    215         else 
    216             mac_data <= data_shift;
    217     end
    218     
    219     always@(posedge clk or negedge rst_n)begin
    220         if(!rst_n)
    221             mac_data_vld <= 0;
    222         else if(head_flag || rd_flag || end_lag)
    223             mac_data_vld <= 1;
    224         else 
    225             mac_data_vld <= 0;
    226     end
    227     
    228     //输出无效字节个数 由于输出端进行了数据移位,导致无效数据个数发生变化
    229     always@(posedge clk or negedge rst_n)begin
    230         if(!rst_n)
    231             mac_data_mod <= 0;
    232         else if(end_normal)
    233             mac_data_mod <= q[33:32] - 2;
    234         else if(end_lag && q_tmp[33:32] == 2'd1)
    235             mac_data_mod <= 1;
    236         else if(end_lag && q_tmp[33:32] == 0)
    237             mac_data_mod <= 2;
    238         else 
    239             mac_data_mod <= 0;
    240     end
    241     
    242 endmodule
    复制代码

     编写测试激励验证功能:

    复制代码
     1 `timescale 1ns / 1ps
     2 
     3 module add_mac_head_tb;
     4 
     5     
     6     reg clk,rst_n;
     7     reg [31:0] app_data;
     8     reg app_data_sop,app_data_eop,app_data_vld;
     9     reg [1:0] app_data_mod;
    10     reg mac_tx_rdy;
    11     
    12     wire [31:0] mac_data;
    13     wire mac_data_vld,mac_data_sop,mac_data_eop;
    14     wire [1:0] mac_data_mod;
    15 
    16     add_mac_head add_mac_head(
    17     .clk(clk),
    18     .rst_n(rst_n),
    19     .app_data(app_data),
    20     .app_data_vld(app_data_vld),
    21     .app_data_sop(app_data_sop),
    22     .app_data_eop(app_data_eop),
    23     .app_data_mod(app_data_mod),//无效字节数
    24 
    25     .mac_tx_rdy(mac_tx_rdy),//MAC IP发送准备就绪信号
    26     .mac_data(mac_data),
    27     .mac_data_vld(mac_data_vld),
    28     .mac_data_sop(mac_data_sop),
    29     .mac_data_eop(mac_data_eop),
    30     .mac_data_mod(mac_data_mod)
    31     );
    32     
    33     parameter CYC = 5,
    34               RST_TIME = 2;
    35               
    36     integer i;
    37               
    38     initial begin
    39         clk = 1;
    40         forever #(CYC / 2.0) clk = ~clk;
    41     end
    42     
    43     initial begin
    44         rst_n = 1;
    45         #1;
    46         rst_n = 0;
    47         #(CYC*RST_TIME);
    48         rst_n = 1;
    49     end
    50     
    51     initial begin
    52         #1;
    53         app_data = 0;
    54         app_data_sop = 0;
    55         app_data_eop = 0;
    56         app_data_mod = 0;
    57         app_data_vld = 0;
    58         mac_tx_rdy = 1;
    59         #(CYC*RST_TIME);
    60         packet_gen(10,0);
    61         packet_gen(5,0);
    62         packet_gen(15,2);
    63         #1000;
    64         $stop;
    65     end
    66     
    67     task packet_gen;
    68         input [15:0] length;
    69         input [1:0] invld_num;
    70         begin
    71             app_data_vld = 1;
    72             app_data_sop = 1;
    73             app_data = 32'h01020300;
    74             for(i = 0;i < length;i = i + 1'b1)begin
    75                 if(i == 1)
    76                     app_data_sop = 0;
    77                 else if(i == length - 1)begin
    78                     app_data_mod = invld_num;
    79                     app_data_eop = 1;
    80                 end
    81                 app_data = app_data +1'b1;
    82                 #(CYC*1);
    83             end
    84             app_data_eop = 0;
    85             app_data_vld = 0;
    86             app_data_mod = 0;
    87         end
    88     endtask
    89 
    90 endmodule
    复制代码

       连续输入三个长度不同的报文,此处为了设计重用,用可参数化的task对激励报文进行封装。需要输入报文时只需调用packet_gen任务即可实现具有不同长度,不同无效字节个数的数据包。观察输出波形:

     mac侧输出三个包文数据如下:

       可以看出mac侧数据发送正确。本博文由于主要讲述FIFO应用,这里只做出行为仿真,读者可以灵活运用,添加在自己的项目中。

  • 相关阅读:
    [原]百度公交离线数据格式分析——4.小结
    [原]百度公交离线数据格式分析——3.加载城市列表
    [原]百度公交离线数据格式分析——2.从界面点击下载的流程
    [原]百度公交离线数据格式分析——1.准备工作
    网页版迅雷离线下载过程分析
    OPKG命令执行过程分析
    Python 随笔两则
    心理学在前端的应用--《设计师要懂心理学》读书笔记之人如何记忆和思考
    心理学在前端的应用---(设计师要懂心理学)读书笔记之人如何观察和阅读
    React阻止事件冒泡的正确打开方式
  • 原文地址:https://www.cnblogs.com/lionsde/p/9575763.html
Copyright © 2011-2022 走看看