![b241e2068944be9d1a0bde1e7003313a.png](https://i-blog.csdnimg.cn/blog_migrate/cf360884a0f94b318ee309ad473137b9.jpeg)
前文完成了ICMP的回显请求处理,已经实现可以ping通板子的功能了,接下来就是传递数据了,无非就是采用UDP协议将一段数据从一台主机传输到另一台主机,也就是“搬砖”。
![6072a771915ab0d589bf0f0bb7923950.png](https://i-blog.csdnimg.cn/blog_migrate/26d40b724bb8a5bcff18625157302b0c.png)
前文的缘起和分类两章中简单介绍了UDP,所以本文只对其设计进行介绍。
因为本协议栈只要是为了UDP的数据传输,所以分别就其的接收端和发送端进行介绍,为方便起见,笔者用Visio画了张简图加以描述:
![9c1b2b45de511b758d92f4ce8f939f94.png](https://i-blog.csdnimg.cn/blog_migrate/21f2f7a4c236a9353794300a549aef15.png)
接收端
前文已经获得了UDP数据部分和校验信息,接收端的功能就是将正确的UDP数据传给用户使用,所以笔者在设计中如下:
根据前文分类时得到的UDP数据和状态信息(FCS、Checksum等校验)进行处理,如果校验均正确,并且也是UDP数据包,则根据设计方案加上自定义的帧头(源IP地址、源端口号、目的端口号、帧长)存入到FIFO当中进行缓存,其中该缓冲区可以进行配置深度,然后传输到用户侧使用。
当然如果校验等信息有误,则当场将数据丢掉处理。
RTL实现可如下所示:
//移位寄存
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
udp_data_valid_r <= 1'h0;
udp_data_shift <= 96'h0;
udp_data_valid_edge_shift <= 16'h0;
udp_data_valid_shift <= 16'h0;
end
else begin
udp_data_valid_r <= udp_data_valid;
udp_data_shift <= {udp_data_shift[87:0],udp_data};
udp_data_valid_edge_shift <= {udp_data_valid_edge_shift[14:0],udp_data_valid&(~udp_data_valid_r)};
udp_data_valid_shift <= {udp_data_valid_shift[14:0],udp_data_valid};
end
end
//---current src_ip/src_port/dst_port/udp_length
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
udp_custom_header_shift <= 80'h0;
end
else if (udp_data_valid && (!udp_data_valid_r)) begin
udp_custom_header_shift <= {current_frame_src_ip,current_frame_src_port,current_frame_dst_port,current_frame_length};
end
else begin
udp_custom_header_shift <= {udp_custom_header_shift[71:0],8'h0};
end
end
//加上自定义帧头{源IP、源端口、目的端口、帧长}
assign udp_custom_header_valid = |udp_data_valid_edge_shift[9:0];
assign udp_custom_header = udp_custom_header_valid?{1'b0,udp_custom_header_shift[79:72]}:9'h0;
//----UDP 纯数据
assign udp_pure_data_valid = udp_data_valid_shift[10];
assign udp_pure_data = udp_pure_data_valid?{udp_pure_data_last,udp_data_shift[87:80]}:9'h0;
assign udp_pure_data_last = (~udp_data_valid_shift[9]) & udp_data_valid_shift[10];
//----UDP 自定义数据
assign udp_custom_data_valid = udp_custom_header_valid | udp_pure_data_valid;
assign udp_custom_data = udp_custom_header | udp_pure_data;
发送端
发送端处理与接收端处理机制相反,是用户想将自己的应用层数据(比如采集的数据等)通过该协议栈发送出去给其他网络设备,而前面已经订好了自定义的帧头,即发送端获得的数据已经知道该数据是发给哪个IP、发给哪个端口、发多长的了。
那发送端要做的事情,就是:发!
那问题又来了,又不能直接将纯UDP数据发出去,还得加上UDP头、IP头、MAC头才行,那似乎这个发送端逻辑不用写也行,直接写入缓存FIFO当中,然后送给封包模块封装成以太网帧发送不就好了。
但是,UDP头里有个checksum字段,如果直接丢给封包模块,那还的重新计算一遍,为降低延迟,所以笔者决定在收到用户侧需要发送的数据的时候就进行累加计算,这样封包的时候,将这个累加值加上其他几个UDP头的字段并取反,就可以作为UDP头的Checksum了。
这里UDP纯数据部分的累加计算RTL如下:
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
udp_data_valid_r <= 1'b0;
udp_data_r <= 8'h0;
end
else begin
udp_data_valid_r <= udp_axis_tx_valid_i && udp_axis_tx_ready_o;
udp_data_r <= udp_axis_tx_data_i; //寄存
end
end
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
udp_data_calc_en <= 1'b0;
end
else if (udp_axis_tx_valid_i && udp_axis_tx_ready_o) begin
udp_data_calc_en <= ~udp_data_calc_en; //高8bit计算使能
end
else if (udp_tx_status_fifo_wr_en) begin
udp_data_calc_en <= 1'b0;
end
end
assign udp_data_calc_en_n = ~udp_data_calc_en; //低8bit计算使能
//计算得到32bit的UDP纯数据的累加值
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
udp_data_checksum_32 <= 32'h0;
end
else if (udp_data_calc_valid && udp_data_calc_en && udp_data_valid_r) begin
udp_data_checksum_32 <= udp_data_checksum_32 + {udp_data_r,8'h0};
end
else if (udp_data_calc_valid && udp_data_calc_en_n && udp_data_valid_r) begin
udp_data_checksum_32 <= udp_data_checksum_32 + {8'h0,udp_data_r};
end
else if (udp_tx_status_fifo_wr_en) begin
udp_data_checksum_32 <= 32'h0;
end
end
然后用户需要发送的数据存入缓冲FIFO当中,此外将以上计算得到的累加值、目的IP、目的端口、源端口等信息存入状态信息FIFO当中,供后级封包发送使用。
AXI-Stream接口协议
UDP处理模块的对用户侧的接口采用AXIS接口协议,这里不再赘述AXIS协议的传输了,可以参考下面知乎文章:
ljgibbs:深入 AXI4总线(C1)旧版存档:AXI4 的兄弟协议zhuanlan.zhihu.com![ad48cdc9c3eed46f42eab52eaa167e3b.png](https://i-blog.csdnimg.cn/blog_migrate/0aec9b3aa6a90880a2c1f499e0386bb6.jpeg)
笔者只利用FIFO的接口简单转成AXI-Stream接口,即UDP接收缓冲FIFO的输出接口转为对用户侧的AXIS_Rx接口,若用户侧处理不过来可暂存于FIFO当中,同样,即AXI_Tx接口转为UDP的发送缓冲区FIFO的输入接口,当UDP发送阻塞时,用户待发送的数据也可暂存于FIFO中,避免数据丢失。
以上即UDP处理模块所有内容,当然实现的比较简单,并未做特定端口号的过滤提取,反正接收到的UDP数据采用自定义帧格式,帧头包含了源IP、源端口、目的端口,直接供用户侧处理就好了。
如有不对之处,还望批评指正~
十二点过九分:UDP/IP硬件协议栈设计(八):封包zhuanlan.zhihu.com
![e6256aabae133e7182da0e220a7b7e68.png](https://i-blog.csdnimg.cn/blog_migrate/01dfc1f857874fb27fc10a1b684d1d33.jpeg)