最近和 RK 研发同事在调试通信接口,排查与定位 RK3399 接收数据出错的问题。FPGA 与 RK3399 之间使用一路 RS232 串口进行通信,由于串口数据没有分包,不方便排查问题,想到可以开发一个 RS232 串口转以太网的工具,将串口接收到的数据封装为 UDP 数据报文,并通过网线传输到电脑,再进行后续问题的定位。
以下是串口转以太网工具,调试的效果图。
目录
1 模块设计
1.1 串口接收模块
1.2 以太网发送模块
2 上板调试
1 模块设计
1.1 串口接收模块
串口接收模块电路需要实现的功能包括:
(1)串口数据接收;
(2)串口数据缓存。
uart_rx_slice 是串口底层模块,负责接收单个 byte 数据。规定串口每帧传输的数据长度不超过 2048,需要例化一个深度为 4096,位宽为 8bit 的 Block RAM,并实现串口数据的乒乓缓存。
top_uart 模块代码:
`timescale 1ns / 1ps
module top_uart #(
parameter FREQ_SYS_CLK = 32'd200_000_000,
parameter BAUD_RATE = 32'd256_000
)(
// System level
input sys_rst ,
input sys_clk ,
// Uart received data flow
output [7:0] uart_rcv_data ,
output uart_rcv_valid ,
// Uart Interface
input uart_rxd ,
output uart_txd
);
wire frame_ss ;
wire [7:0] rcv_data ;
wire rcv_valid ;
reg frame_ss_r1 ;
reg frame_ss_r2 ;
reg [7:0] blk_mem_wdata ;
reg [11:0] blk_mem_waddr ;
reg [11:0] blk_mem_waddr_r ;
reg [0:0] blk_mem_wren ;
reg blk_mem_rd_busy ;
reg [11:0] blk_mem_raddr ;
wire [7:0] blk_mem_rdata ;
reg blk_mem_rdvld ;
// uart_rx_slice: Uart receive module
uart_rx_slice uart_rx_slice_inst (
.sys_rst (sys_rst ), // input
.sys_clk (sys_clk ), // input
.frame_ss (frame_ss ), // output
.rcv_data (rcv_data ), // output
.rcv_valid (rcv_valid ), // output
.uart_rxd (uart_rxd ) // input
);
defparam uart_rx_slice_inst.FREQ_SYS_CLK = FREQ_SYS_CLK;
defparam uart_rx_slice_inst.BAUD_RATE = BAUD_RATE;
// End of uart_rx_slice instantiation
// blk_mem_4096x8b: Block Memory generator
blk_mem_4096x8b blk_mem_4096x8b_inst (
.clka (sys_clk ), // input
.ena (1'b1 ), // input
.wea (blk_mem_wren ), // input
.addra (blk_mem_waddr ), // input
.dina (blk_mem_wdata ), // input
.clkb (sys_clk ), // input
.rstb (sys_rst ), // input
.enb (1'b1 ), // input
.addrb (blk_mem_raddr ), // input
.doutb (blk_mem_rdata ), // output
.rsta_busy ( ), // output
.rstb_busy ( ) // output
);
// End of blk_mem_4096x8b_inst instantiation
always @(posedge sys_rst or posedge sys_clk) begin
if (sys_rst == 1'b1) begin
frame_ss_r1 <= 1'b0;
frame_ss_r2 <= 1'b0;
end
else begin
frame_ss_r1 <= frame_ss;
frame_ss_r2 <= frame_ss_r1;
end
end
always @(posedge sys_rst or posedge sys_clk) begin
if (sys_rst == 1'b1) begin
blk_mem_waddr <= 12'd0;
blk_mem_wdata <= 8'd0;
blk_mem_wren <= 1'b0;
end
else begin
blk_mem_wdata <= rcv_data;
blk_mem_wren[0] <= rcv_valid;
if (frame_ss_r2 == 1'b1 && frame_ss_r1 == 1'b0) begin
blk_mem_waddr[11] <= ~blk_mem_waddr[11];
blk_mem_waddr[10:0] <= {11{1'b0}};
end
else if (blk_mem_wren[0] == 1'b1) begin
blk_mem_waddr[11] <= blk_mem_waddr[11];
blk_mem_waddr[10:0] <= blk_mem_waddr[10:0] + 1'b1;
end
end
end
always @(posedge sys_rst or posedge sys_clk) begin
if (sys_rst == 1'b1) begin
blk_mem_waddr_r <= 12'd0;
blk_mem_raddr <= 12'd0;
blk_mem_rd_busy <= 1'b0;
blk_mem_rdvld <= 1'b0;
end
else begin
if (blk_mem_rd_busy == 1'b0 && frame_ss_r2 == 1'b1 && frame_ss_r1 == 1'b0) begin
blk_mem_waddr_r <= blk_mem_waddr - 1'b1;
blk_mem_rd_busy <= 1'b1;
end
else if (blk_mem_rd_busy == 1'b1) begin
if (blk_mem_raddr == blk_mem_waddr_r) begin
blk_mem_raddr[11] <= ~blk_mem_raddr[11];
blk_mem_raddr[10:0] <= {11{1'b0}};
blk_mem_rd_busy <= 1'b0;
end
else begin
blk_mem_raddr[11] <= blk_mem_raddr[11];
blk_mem_raddr[10:0] <= blk_mem_raddr[10:0] + 1'b1;
end
end
blk_mem_rdvld <= blk_mem_rd_busy;
end
end
assign uart_rcv_data = blk_mem_rdata;
assign uart_rcv_valid = blk_mem_rdvld;
endmodule
1.2 以太网发送模块
以太网网络层使用 IPv4 协议,传输层使用 UDP 协议。
以太网发送模块电路需要实现的功能包括:
(1)IPv4 与 UDP 协议校验;
(2)以太网帧组帧(包括 CRC 校验);
(3)GMII 与 RGMII 桥接。
同样例化一个深度为 4096,位宽为 8bit 的 Block RAM,实现以太网帧数据的乒乓缓存,在初始化文件中写入以太网帧头,MAC 地址等信息。
使用 Python 代码生成 coe 文件,代码如下:
# 以太网帧头数据
f = b'\x55\x55\x55\x55\x55\x55\x55\xd5\xd4\x5d\x64\xad\x16\x47\x11\x22\x33\x44\x55\x66\x08\x00\x45\x00\x00\x00\x00\x00\x40\x00\x40\x11\xff\xff\xc0\xa8\x01\x08\xc0\xa8\x01\x09\x1f\x90\x1f\x90\x00\x00\x00\x00'
raw_data = list(map(lambda e: "{:02X}".format(e), f))
while len(raw_data) < 2048:
raw_data.append('00')
# 写入coe文件
with open('blk_mem_4096x8b_MAC.coe', 'w') as f:
f.write('memory_initialization_radix = 16;\n')
f.write('memory_initialization_vector = \n')
for i,e in enumerate(raw_data*2):
if i != len(raw_data*2)-1:
f.write("{:s},\n".format(e))
else:
f.write("{:s};".format(e))
在 IP 核配置界面,选择 coe 初始化文件,点击 Edit 查看数据。
top_ethernet 模块代码:
`timescale 1ns / 1ps
module top_ethernet #(
parameter local_ip_addr = 32'hC0A80109,
parameter remote_ip_addr = 32'hC0A8010A,
parameter local_udp_port = 16'h1F90,
parameter remote_udp_port = 16'h1F90
)(
// System level
input sys_rst ,
input sys_clk ,
// RGMII Interface
input rgmii_rxc ,
input [3:0] rgmii_rxd ,
input rgmii_rx_ctl ,
output rgmii_txc ,
output [3:0] rgmii_txd ,
output rgmii_tx_ctl ,
// UDP data input ports
input [7:0] eth_udp_txd ,
input eth_udp_txen
);
wire gmii_tx_clk;
wire [7:0] eth_mac_txd;
wire eth_mac_txen;
// mac_tx_slice: MAC data pack and transmit module
mac_tx_slice mac_tx_slice_inst(
.sys_rst (sys_rst ), // input
.sys_clk (sys_clk ), // input
.gmii_tx_clk (gmii_tx_clk ), // input
.eth_udp_txd (eth_udp_txd ), // input
.eth_udp_txen (eth_udp_txen ), // input
.eth_mac_txd (eth_mac_txd ), // output
.eth_mac_txen (eth_mac_txen ) // output
);
defparam mac_tx_slice_inst.local_ip_addr = local_ip_addr;
defparam mac_tx_slice_inst.remote_ip_addr = remote_ip_addr;
defparam mac_tx_slice_inst.local_udp_port = local_udp_port;
defparam mac_tx_slice_inst.remote_udp_port = remote_udp_port;
// End of mac_tx_slice_inst instantiation
// gmii_rgmii_bright: GMII to RGMII bridge
util_gmii_to_rgmii util_gmii_to_rgmii (
.reset (sys_rst ), // input
.rgmii_td (rgmii_txd ), // output
.rgmii_tx_ctl (rgmii_tx_ctl ), // output
.rgmii_txc (rgmii_txc ), // output
.rgmii_rd (rgmii_rxd ), // input
.rgmii_rx_ctl (rgmii_rx_ctl ), // input
.rgmii_rxc (rgmii_rxc ), // input
.gmii_txd (eth_mac_txd ), // input
.gmii_tx_en (eth_mac_txen ), // input
.gmii_tx_er (1'b0 ), // input
.gmii_tx_clk (gmii_tx_clk ), // output
.gmii_crs ( ), // output
.gmii_col ( ), // output
.gmii_rxd ( ), // output
.gmii_rx_dv ( ), // output
.gmii_rx_er ( ), // output
.gmii_rx_clk ( ), // output
.speed_selection(2'b10 ), // input
.duplex_mode (1'b1 ) // input
);
// End of util_gmii_to_rgmii_inst instantiation
endmodule
2 上板调试
使用 ALINX AX7035 开发板,进行工程调试。红色的线接 USB 转串口接口,蓝色的线接入千兆网口。使用电脑模拟发送串口数据,并接收以太网数据。
电脑端同时打开串口调试助手,和 wireshark 工具,开始抓包。
在串口调试助手中输入待发送数据,选择定时 1s 发送,观察 wireshark 界面是否间隔 1s 收到数据包。