本文设计思路源自明德扬至简设计法。在之前的几篇博文中,由于设计比较简单,所有的功能都是用verilogHDL代码编写实现的。我们要学会站在巨人的肩膀上,这时候就该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信号来区分上述两种情况。需要特别注意的是,在移位操作后数据包中包含的无效字节个数也会发生变化。为了理清思路和时序,画出核心信号时序图:

  

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

  开始编写代码了:

 `timescale 1ns / 1ps

 module add_mac_head(
     input clk,
     input rst_n,
     :] app_data,
     input app_data_vld,
     input app_data_sop,
     input app_data_eop,
     :] app_data_mod,//无效字节数

     input mac_tx_rdy,//MAC IP发送准备就绪信号
     :] mac_data,
     output reg mac_data_vld,
     output reg mac_data_sop,
     output reg mac_data_eop,
     :] mac_data_mod
     );

     :] wdata;
     reg wrreq,rdreq;
     reg wdata_xx;
     reg wrreq_xx,rdreq_xx;
     :] head_cnt;
     reg head_flag,head_tmp,rd_flag,rd_flag_tmp;
     :] q_tmp;

     :] data_shift;
     wire add_head_cnt,end_head_cnt;
     wire head_neg;
     :] q;
     wire rdempty_xx;
     wire sop_in;
     :] head_len;
     :] mac_head;
     :] des_mac,sour_mac;
     :] pack_type;
     wire rd_neg;

     fifo_generator_0 fifo_data (
   .clk(clk),      // input wire clk
   .din(wdata),      // input wire [34 : 0] din
   .wr_en(wrreq),  // input wire wr_en
   .rd_en(rdreq),  // input wire rd_en
   .dout(q),    // output wire [34 : 0] dout
   .full(),    // output wire full
   .empty()  // output wire empty
 );

     fifo_generator_1 fifo_message (
   .clk(clk),      // input wire clk
   .din(wdata_xx),      // input wire [0 : 0] din
   .wr_en(wrreq_xx),  // input wire wr_en
   .rd_en(rdreq_xx),  // input wire rd_en
   .dout(),    // output wire [0 : 0] dout
   .full(),    // output wire full
   .empty(rdempty_xx)  // output wire empty
 );

     //数据fifo写数据
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             wdata <= ;
         else if(app_data_vld)
             wdata <= {app_data_eop,app_data_mod,app_data};
     end

     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             wrreq <= ;
         else if(app_data_vld)
             wrreq <= ;
         else
             wrreq <= ;
     end

     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             wdata_xx <= ;
         else if(sop_in)
             wdata_xx <= ;
         else
             wdata_xx <= ;
     end

     assign sop_in = app_data_vld && app_data_sop;

     //当写侧出现sop时表明有一个数据包正在写入,此时写信息FIFO任意数据告知读侧开始发送MAC头部信息
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             wrreq_xx <= ;
         else if(sop_in)
             wrreq_xx <= ;
         else
             wrreq_xx <= ;
     end

     //MAC头部有14个字节 数据位宽是32位,即一个数据4个字节,需要发送4个数据(最后一个数据只有2个字节是头部)
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             head_cnt <= ;
         else if(add_head_cnt)begin
             if(end_head_cnt)
                 head_cnt <= ;
             else
                 head_cnt <= head_cnt + 'b1;
         end
     end

     assign add_head_cnt = head_flag && mac_tx_rdy;
      - ;
     ;

     //发送MAC头部标志位
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             head_flag <= ;
         else if(end_head_cnt)
             head_flag <= ;
         else if(!rdempty_xx && !rd_flag)
             head_flag <= ;
     end

     //读数据FIFO标志位
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             rd_flag <= ;
         else if(end_head_cnt)
             rd_flag <= ;
         else if(rd_eop)
             rd_flag <= ;
     end

     ];

     always@(*)begin
         if(rd_flag && mac_tx_rdy)
             rdreq <= ;
         else
             rdreq <= ;
     end

     //读侧出现eop读取完整版报文,此时读清信息FIFO
     always@(*)begin
         if(rd_eop)
             rdreq_xx <= ;
         else
             rdreq_xx <= ;
     end

     //寄存头部标志位找出下降沿
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             head_tmp <= ;
         else
             head_tmp <= head_flag;
     end

      && head_tmp == ;

     //寄存q用于移位操作
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             q_tmp <= ;
         else
             q_tmp <= q;
     end

     :],q[:]};

     //MAC头 14字节
     assign mac_head  = {des_mac,sour_mac,pack_type};
     'hD0_17_C2_00_E5_40  ;//目的MAC PC网卡物理地址
     'h01_02_03_04_05_06  ;//源MAC地址为01_02_03_04_05_06
     'h0800               ;//IP数据报

     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             rd_flag_tmp <= ;
         else
             rd_flag_tmp <= rd_flag;
     end

      && rd_flag_tmp == ;

     //数据输出
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             mac_data_sop <= ;
         )
             mac_data_sop <= ;
         else
             mac_data_sop <= ;
     end

     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             mac_data_eop <= ;
         else if(end_normal || end_lag)
             mac_data_eop <= ;
         else
             mac_data_eop <= ;
     end

     :]      > 'd1;
     :] <= 'd1;

     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             mac_data <= ;
         else if(add_head_cnt)//由于MAC不是32位数据的整数倍,需要对数据进行移位
             mac_data <= mac_head[ - head_cnt* -: ];
         else if(head_neg)
             mac_data <= {mac_head[:],q[:]};
         else
             mac_data <= data_shift;
     end

     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             mac_data_vld <= ;
         else if(head_flag || rd_flag || end_lag)
             mac_data_vld <= ;
         else
             mac_data_vld <= ;
     end

     //输出无效字节个数 由于输出端进行了数据移位,导致无效数据个数发生变化
     always@(posedge clk or negedge rst_n)begin
         if(!rst_n)
             mac_data_mod <= ;
         else if(end_normal)
             mac_data_mod <= q[:] - ;
         :] == 'd1)
             mac_data_mod <= ;
         :] == )
             mac_data_mod <= ;
         else
             mac_data_mod <= ;
     end

 endmodule

编写测试激励验证功能:

 `timescale 1ns / 1ps

 module add_mac_head_tb;

     reg clk,rst_n;
     :] app_data;
     reg app_data_sop,app_data_eop,app_data_vld;
     :] app_data_mod;
     reg mac_tx_rdy;

     :] mac_data;
     wire mac_data_vld,mac_data_sop,mac_data_eop;
     :] mac_data_mod;

     add_mac_head add_mac_head(
     .clk(clk),
     .rst_n(rst_n),
     .app_data(app_data),
     .app_data_vld(app_data_vld),
     .app_data_sop(app_data_sop),
     .app_data_eop(app_data_eop),
     .app_data_mod(app_data_mod),//无效字节数

     .mac_tx_rdy(mac_tx_rdy),//MAC IP发送准备就绪信号
     .mac_data(mac_data),
     .mac_data_vld(mac_data_vld),
     .mac_data_sop(mac_data_sop),
     .mac_data_eop(mac_data_eop),
     .mac_data_mod(mac_data_mod)
     );

     ,
               RST_TIME = ;

     integer i;

     initial begin
         clk = ;
         forever #(CYC / 2.0) clk = ~clk;
     end

     initial begin
         rst_n = ;
         #;
         rst_n = ;
         #(CYC*RST_TIME);
         rst_n = ;
     end

     initial begin
         #;
         app_data = ;
         app_data_sop = ;
         app_data_eop = ;
         app_data_mod = ;
         app_data_vld = ;
         mac_tx_rdy = ;
         #(CYC*RST_TIME);
         packet_gen(,);
         packet_gen(,);
         packet_gen(,);
         #;
         $stop;
     end

     task packet_gen;
         :] length;
         :] invld_num;
         begin
             app_data_vld = ;
             app_data_sop = ;
             app_data = 'h01020300;
             ;i < length;i = i + 'b1)begin
                 )
                     app_data_sop = ;
                 )begin
                     app_data_mod = invld_num;
                     app_data_eop = ;
                 end
                 app_data = app_data +'b1;
                 #(CYC*);
             end
             app_data_eop = ;
             app_data_vld = ;
             app_data_mod = ;
         end
     endtask

 endmodule

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

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

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

IP核之初——FIFO添加以太网MAC头部的更多相关文章

  1. Xilinx IP核使用(一)--FIFO

    今天在将SRIO的数据存入FIFO后,然后把FIFO中的数据不断送入FFT进行运算时,对于几个控制信号总产生问题.所以单独对FIFO进行了仿真.原来感觉FIFO的几个参数端口一目了然啊,还需要什么深入 ...

  2. FIFO IP核仿真

    FIFO IP核仿真 1.FIFO IP核配置 2.FIFO测试逻辑代码 首先往FIFO里面写入512个数据(FIFO深度的一半),然后再开始同时往FIFO里面写入,读出数据.FIFO读和写的时钟域不 ...

  3. 如何用ModelsimSE仿真IP核-以PLL为例

    我们之前介绍了如何使用Modelsim SE进行仿真和利用do文件的仿真方法,但是其中待仿真的模块是我们自己编写的Verilog模块,但是在实际工作中,我们的设计中会经常用到FPGA厂商给我们提供的现 ...

  4. 浅析Xilinx 三速以太网MAC IP核

    之前在使用Altera的三速以太网MAC IP的基础上,完成了UDP协议数据传输.此次为了将设计移植到xilinx FPGA上,需要用到xilinx的三速以太网MAC IP核,当然也可以自己用HDL编 ...

  5. FIFO IP核

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

  6. Altera三速以太网IP核快速仿真与使用(上篇)

    对于比较高级的ip核,altera一般都会提供仿真案例,网上有关于这个IP核的各种仿真方法,但都比较繁琐,前几日,朋友跟我分享了一个比较快速高效的仿真方法,这个方法也是他摸索折腾了一段时间才总结出来的 ...

  7. Altera FIFO IP核时序说明

    ALTERA在LPM(library of parameterized mudules)库中提供了参数可配置的单时钟FIFO(SCFIFO)和双时钟FIFO(DCFIFO).FIFO主要应用在需要数据 ...

  8. Altera三速以太网IP核使用(下篇)--- 百兆网接口设计与使用

    MAC IP核的主要作用是:实现数据链路层协议,分为TX方向与RX方向,TX方向实现的是在原包文的前面加上7个55和1个D5,RX方向则相反.在使用这个 MAC IP核之前,首先确认下自己使用的网卡是 ...

  9. FPGA基础学习(2) -- FIFO IP核(Quartus)

    ALTERA在LPM(library of parameterized mudules)库中提供了参数可配置的单时钟FIFO(SCFIFO)和双时钟FIFO(DCFIFO).FIFO主要应用在需要数据 ...

随机推荐

  1. Jquery基础添加删除内容

    直入主题,工作中比较常用的功能在input框内添加内容,不白话了,上代码! 布局: <div id="content"> <input type="te ...

  2. Spring @RequestParam乱码问题

    在网上找了很多资料才找到解决的方法,通过URL传递命名参数,后台接收的却是乱码解决方法如下: 方法一:将接收的参数重新编码 @RequestMapping(value="/handle&qu ...

  3. 微信小程序--图片相关问题合辑

    图片上传相关文章 微信小程序多张图片上传功能 微信小程序开发(二)图片上传 微信小程序上传一或多张图片 微信小程序实现选择图片九宫格带预览 ETL:微信小程序之图片上传 微信小程序wx.preview ...

  4. 笔记本win10安装node的尖酸历程。。。。。。

    在公司的电脑搭建vue环境分分钟搞定,周末闲的无聊给自己的电脑也搭建一个环境,谁知道这么多的问题,记录下这些问题,希望对那些安装node环境有问题的朋友一些帮助. 1.下载安装node 下载地址htt ...

  5. 再起航,我的学习笔记之JavaScript设计模式26(解释器模式)

    解释器模式 概念介绍 解释器模式(Interpreter):给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子. 获取元素在页面中的路径 我们都知道获取一个 ...

  6. 深入理解计算机系统chapter6

    1. 2. 3. 存储器山

  7. [SDOI2009]HH的项链解题报告

    原题目:洛谷P1972 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义.HH 不断地收集新的贝壳,因此 ...

  8. 个人从源码理解JIT模式下angular编译AppModule的过程

    承接上文.笔者之前将一个angular项目的启动过程分为了两步: 创建平台得到 PlatformRef ,以及执行平台引用提供的方法编译根模块 AppModule .本文就将着眼于创建好的平台,从an ...

  9. Merge Two Binary Trees

    Given two binary trees and imagine that when you put one of them to cover the other, some nodes of t ...

  10. 点聚合功能---基于ARCGIS RUNTIME SDK FOR ANDROID

    一直不更新博客的原因,如果一定要找一个,那就是忙,或者说懒癌犯了. 基于ArcGIS RunTime SDK for Android的点聚合功能,本来是我之前做过的一个系统里面的一个小部分,今天抽出一 ...