IP核之初——FIFO添加以太网MAC头部
本文设计思路源自明德扬至简设计法。在之前的几篇博文中,由于设计比较简单,所有的功能都是用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应用,这里只做出行为仿真,读者可以灵活运用,添加在自己的项目中。
最新文章
- FileUpload组件
- LDA(文档主题模型)
- Java与.NET随笔
- pg_stat_statements
- Python基础 练习题
- iOS,Android网络抓包教程之tcpdump
- 03_Elasticsearch如何安装以及相关插件的介绍
- 7. 稀疏表示之OMP,SOMP算法及openCV实现
- C#开发者通用性代码审查清单
- 使用 Spring RestTemplate 调用 rest 服务时自定义请求头(custom HTTP headers)
- IE网页js语法错误2行字符1,FF中正常
- Kafka概念入门(一)
- Flask從入門到入土(五)——Flask与数据库
- spring中基于注解使用AOP
- Python【第二篇】运算符及优先级、数据类型及常用操作、深浅拷贝
- Git(管理修改)
- stream to byte[], byte[] to srting
- [ 转载 ] Java 构造代码块
- 在SpringMVC中使用@RequestBody注解处理json时,报出HTTP Status 415的解决方案
- Oracle 9i 10g 11g 区别的转载