千兆以太网(3):接收——包校验和数据筛选
前面我们实现了FPGA板卡接收以太网的数据,但是里面的数据比较乱,而且可能出现无效帧,即便是有效帧,也不是所有数据都是我们要的,必须对数据进行筛选。本篇博客详细记录一下以太网数据的校验和筛选。
一、数据的校验和筛选
根据本次工程的情况,我们按照下表来进行设计:
1、包有效校验
如上图蓝色部分即是我们的包有效校验区,包发送数据过来,而刚好蓝色位置的 5byte(40bit)数据和标准值一样,那么就可以认定该包为有效包。
//========================================================================== //== 包有效校验(udp、port源、port目的) //========================================================================== always @(posedge clk) begin if(rst) begin pkg_value <= 40'd0; end else if(PHY_rx_cnt==31 || (PHY_rx_cnt>=42 && PHY_rx_cnt<=45)) begin pkg_value <= {pkg_value[31:0],PHY_rxd}; end end always @(posedge clk) begin if(rst) begin pkg_vld <= 1'b0; end else if(PHY_rx_neg && pkg_value==40'h11_04d2_007b) begin pkg_vld <= 1'b1; end else begin pkg_vld <= 1'b0; end end
2、CRC校验
CRC校验是为了证明一个数据包是否出错,多数为 8 位或 32 位,本次采用 32 位的CRC校验,但是记住,只能用于验证数据是否出错,而不能对错误进行纠正。此外 CRC 校验是去掉了帧首部的 8 byte 数据后的校验。如下所示是32位的CRC校验模块,注意校验和解校验的电路默认初始状态都是 32‘hffffffff,即全1状态。千兆以太网的解校验结果为 32’hc704dd7b。可以用例化的方式对该模块进行使用,而实际 CRC 校验的科学原理则略微高深,此处不做讲解。
always @(posedge sclk) begin if(rst) begin crc32_value <= 32'hFFFFFFFF; end else if(crc_en) begin crc32_value[ 0] <= c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 1] <= c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 2] <= c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 3] <= c[27]^d[ 4]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[ 4] <= c[28]^d[ 3]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 5] <= c[29]^d[ 2]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[1]^d[7]; crc32_value[ 6] <= c[30]^d[ 1]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[ 7] <= c[31]^d[ 0]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^d[ 7]; crc32_value[ 8] <= c[ 0]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^d[ 6]^c[24]^d[ 7]; crc32_value[ 9] <= c[ 1]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^d[ 6]; crc32_value[10] <= c[ 2]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^d[ 7]; crc32_value[11] <= c[ 3]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^d[ 6]^c[24]^d[ 7]; crc32_value[12] <= c[ 4]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[13] <= c[ 5]^c[30]^d[ 1]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[14] <= c[ 6]^c[31]^d[ 0]^c[30]^d[ 1]^c[28]^d[ 3]^c[27]^d[ 4]^c[26]^d[5]; crc32_value[15] <= c[ 7]^c[31]^d[ 0]^c[29]^d[ 2]^c[28]^d[ 3]^c[27]^d[ 4]; crc32_value[16] <= c[ 8]^c[29]^d[ 2]^c[28]^d[ 3]^c[24]^d[ 7]; crc32_value[17] <= c[ 9]^c[30]^d[ 1]^c[29]^d[ 2]^c[25]^d[ 6]; crc32_value[18] <= c[10]^c[31]^d[ 0]^c[30]^d[ 1]^c[26]^d[ 5]; crc32_value[19] <= c[11]^c[31]^d[ 0]^c[27]^d[ 4]; crc32_value[20] <= c[12]^c[28]^d[ 3]; crc32_value[21] <= c[13]^c[29]^d[ 2]; crc32_value[22] <= c[14]^c[24]^d[ 7]; crc32_value[23] <= c[15]^c[25]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[24] <= c[16]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[25] <= c[17]^c[27]^d[ 4]^c[26]^d[ 5]; crc32_value[26] <= c[18]^c[28]^d[ 3]^c[27]^d[ 4]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[27] <= c[19]^c[29]^d[ 2]^c[28]^d[ 3]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[28] <= c[20]^c[30]^d[ 1]^c[29]^d[ 2]^c[26]^d[ 5]; crc32_value[29] <= c[21]^c[31]^d[ 0]^c[30]^d[ 1]^c[27]^d[ 4]; crc32_value[30] <= c[22]^c[31]^d[ 0]^c[28]^d[ 3]; crc32_value[31] <= c[23]^c[29]^d[ 2]; end else begin crc32_value <= 32'hFFFFFFFF; end end
3、数据的筛选
由上图可知,只有中间红色部分的 “用户数据”才是我们真正需要的,因此最终输出结果还需要进行一番筛选,去头去尾即可,这也没什么难的。
4、8bit转16bit
以太网传过来的是双沿 4bit 数据,转换后是 8bit,而图像是 RGB565 格式,是 16bit 的,因此还需要进行一下 8bit 转 16bit 的设计。
//========================================================================== //== 8bit转16bit //========================================================================== always @(posedge clk) begin if(rst) byte_cnt <= 0; else if(add_byte_rx_cnt) begin if(end_byte_rx_cnt) byte_cnt <= 0; else byte_cnt <= byte_cnt + 1; end end assign add_byte_rx_cnt = frx_vld; assign end_byte_rx_cnt = add_byte_rx_cnt && byte_cnt== 2-1; always @(posedge clk) begin if(rst) begin PHY_data <= 0; end else if(add_byte_rx_cnt) begin PHY_data <= {PHY_data[7:0],frx_data}; end end always @(posedge clk) begin if (rst == 1'b1) begin PHY_vld <= 1'b0; end else if(end_byte_rx_cnt) begin PHY_vld <= 1'b1; end else begin PHY_vld <= 1'b0; end end
二、代码设计
上面已经贴了部分代码了,难道这里我要全贴代码吗?不,贴代码没有意义,重要的是懂内部的含义。我们以 rx_filter 来命名此模块,rx_filter 的输入是网口数据经过前一讲中转换后的 8bit 数据值和对应使能,输出则是经过校验和筛选后的数据和使能。
上面说的 包有效校验 和 CRC校验,代码本身都不难,用个计数器数进来的数据使能,然后对那些关键节点进行比较即可。但是那头数据边来这头又要边校验,很可能时序出错,因此有必要建立一个 data_fifo 先缓存住过来的数据,然后进行包有效校验和 CRC 校验。data_fifo 的深度可以深一点,例如8192,这样就能容纳多帧了。校验完了后是需要判断是否丢弃该包的,因此还需要另一个 status_fifo 对校验信息进行存储,同时只要 status_fifo 由空变成不空了,说明该包校验信息写入了,即该包校验结束,那就直接设计 status_fifo 的读使能让信息数据出来,并用一个寄存器锁存住后面用。 status_fifo 的读使能有了后,data_fifo 的读使能也要立马设计出来,免得新来的各种包不断进入 data_fifo,那不得撑爆了。注意 data_fifo 的读使能持续时间应该和进来时的数据使能一样长,所以前面计数时不能光计数了,还得把一个完整包的长度存起来。怎么存?直接将长度寄存住后 和 包有效校验、CRC校验拼接一起写给 status_fifo 就行了,后面 status_fifo 读出来这些数据就能为我们所用了。现在 data_fifo 不断的写数据读数据,status_fifo 也不断的写信息数据读信息数据,下一步我们就能利用读出来的信息数据,判断里面的的信息(即包有效校验和CRC校验)是不是真的OK,如果是真的OK,那就将 data_fifo 的读数据和读使能进行数据剔除,留下中间“用户数据”部分再传出去,如果不OK就不管了,让那个 data_fifo 的读使能和读数据继续工作,但我们不使用它,相当于丢弃了。示意图如下所示:
波形图如下所示,结合上面所说,理解了下面的波形图那本模块就没问题了。
三、波形
申请一个 ila 观察数据,可以让 Matab 发送一段 0-255 的数据,查看我们的包有无问题,波形图(部分名字进行了修改)如下所示:
四、千兆以太网 + DDR3 + HDMI 显示
将千兆以太网和 DDR3、HDMI结合,即可实现 以太网 + DDR3 + HDMI 显示了,尤其注意输出端口、时钟连线和引脚约束,工程结构如下所示:
发送端在PC,利用 matlab 将一段影片转换为图像数据,图像数据格式为 RGB565,图像转换为一个个以太网的包,再利用 matlab 发送到网口,FPGA板卡接收以太网数据,经过 PHY_top 的转换,再在 DDR3_ctrl 中缓存,最后通过 HDMI_top 模块将视频输出到显示屏上。
HDMI 的设计中没有考虑声音的输出,因此 matlab 那也没有转换声音,最终显示的视频是无声的,实验结果如下所示:
参考资料:威三学院FPGA教程