这一篇在干嘛?

以太网是 FPGA 产品「联网」的第一步。本实验让 HX4S20 开发板工作在千兆模式,PC 端用网络调试助手把一幅图片以 UDP 包的形式发到网口,FPGA 接收后写入 RAM,再通过 VGA 实时显示出来。实验涉及 OSI 模型、UDP 协议、MAC 与物理层接口、跨时钟域 FIFO 等一整套网络知识,是官方 DEMO 里综合性很强的一篇。

实验目标与原理

实验任务

  1. 学习网口传输原理。
  2. 掌握网口传输显示图像的电路设计。

从 OSI 七层模型说起

开放系统互联模型(OSI,Open System Inter Connect),是国际标准化组织在 1985 年研究的网络互联模型。该体系结构定义了网络互联的七层框架:物理层-数据链路层-网络层-传输层-会话层-表示层-应用层。UDP 和 TCP 是传输层中非常重要的两个协议,位于 OSI 参考模型中的第四层(传输层),位于 IP 协议层(网络层)之上。各层的功能与典型协议如下表所示:

OSI七层模型功能对应的网络协议
应用层应用层是网络体系中最高的一层,也是唯一面向用户的一层,也可视为为用户提供常用的应用程序,每个网络应用都对应着不同的协议HTTP、TFTP, FTP, NFS, WAIS、SMTP
表示层主要负责数据格式的转换,确保一个系统的应用层发送的消息可以被另一个系统的应用层读取,编码转换,数据解析,管理数据的解密和加密,同时也对应用层的协议进行翻译Telnet, Rlogin, SNMP, Gopher
会话层负责网络中两节点的建立,在数据传输中维护计算机网络中两台计算机之间的通信连接,并决定何时终止通信SMTP, DNS
传输层是整个网络关键的部分,是实现两个用户进程间端到端的可靠通信,处理数据包的错误等传输问题。是向下通信服务最高层,向上用户功能最底层。即向网络层提供服务,向会话层提供独立于网络层的传送服务和可靠的透明数据传输。TCP, UDP
网络层进行逻辑地址寻址,实现不同网络之间的路径选择,IP就在网络层IP, ICMP, ARP, RARP, AKP, UUCP
数据链路层物理地址(MAC地址),网络设备的唯一身份标识。建立逻辑连接、进行硬件地址寻址,相邻的两个设备间的互相通信FDDI, Ethernet, Arpanet, PDN, SLIP, PPP, STP。HDLC,SDLC,帧中继
物理层七层模型中的最底层,主要是物理介质传输媒介(网线或者是无线),在不同设备中传输比特,将0/1信号与电信号或者光信号互相转化IEEE 802.1A, IEEE 802.2到IEEE 802

图 7-1

为什么选 UDP

UDP(User Datagram Protocol),即用户数据报协议,是一种面向无连接的传输层协议。无连接是指在传输数据时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

UDP 信息包的标题很短,只有 8 个字节,相对于 TCP 的 20 个字节信息包而言 UDP 的额外开销很小。吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。UDP 是面向报文的。发送方的 UDP 对应用程序交下来的报文,在添加首部后就向下交付给 IP 层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。

对于 FPGA 实现来说,UDP 的吸引力在于「无连接、无重传、无状态」:不需要维护握手和序号状态机,收发逻辑简单、延迟低。视频会议、音视频流这类应用宁可偶尔丢包也不要重传等待,与本实验「连续推图片流」的场景非常契合,所以它们普遍采用 UDP。至于偶尔丢失的一两个数据包,对接收结果不会产生太大影响。

方案架构

UDP 通信应用方案功能框图如图 7-2 所示:


图 7-2

其中用户应用程序负责显示传输图像的内容,UDP 上层可以与用户应用程序进行数据的接收和发送,下层通过安路的三速以太网 IP 与物理层通信,支持单播、组播和广播的通信方式。用户应用程序和 UDP 通过 FIFO 进行数据的交换。

本方案时钟结构如图所示。从 TEMAC 传来的数据通过异步 FIFO 传输给 UDP 模块,两边时钟域如图 7-3 中虚线所示,TEMAC 可以工作在三个速度下,UDP 可以相应调整输入的时钟以适应三种不同的带宽。


图 7-3

这里解释一下「三速」:以太网 PHY 支持 10M/100M/1000M 三种速率,RGMII 接口下对应的接收时钟分别是 2.5MHz、25MHz 和 125MHz。UDP 模块的工作时钟必须随之切换,跨时钟域的异步 FIFO 就是两套时钟之间的缓冲桥梁。

UDP 模块的端口

UDP 模块的主要端口如图 7-4 所示:

信号名bits方向功能描述
udp_rx_clk1inputUDP 接收时钟,千兆模式 125MHz,百兆模式 12.5MHz,十兆模式 1.25MHz。
udp_tx_clk1inputUDP 发送时钟,千兆模式 125MHz,百兆模式 12.5MHz,十兆模式 1.25MHz。
reset1input全局复位信号,高电平有效。
udp2app_tx_ready1outputUDP 和 APP 之间的握手信号,指示准备接收数据,高电平有效。
udp2app_tx_ack1outputUDP 和 APP 之间的握手信号,指示响应接收数据,高电平有效。
app_tx_request1inputUDP 和 APP 之间的握手信号,指示请求发送数据,高电平有效。
app_tx_data_valid1inputAPP 层发送数据有效指示信号,高电平有效。
app_tx_data8inputAPP 层发送数据。
app_tx_data_length16inputAPP 层发送数据长度,数据包的字节数。
app_rx_data_valid1outputAPP 层接收数据有效指示信号,高电平有效。
app_rx_data8outputAPP 层接收数据。
app_rx_data_length16outputAPP 层接收数据长度,数据包的字节数。

图 7-4

发送方向和接收方向各有一套握手:发送时 APP 先拉 app_tx_request 请求发送,UDP 用 udp2app_tx_ready/ack 应答;接收时 UDP 通过 app_rx_data_valid 通知「来了一包」,数据字节流和长度同步给出。理解这组握手信号是编写 APP 层逻辑的前提。

应用层设计

(1)app 模块负责接收 UDP 传输的数据,将数据存储在 6*32768 的伪双口 RAM 中(在数据输入前 RAM 提前加载 logo.mif 文件),通过 UDP 模块的数据以及数据有效位,将 PC 传输的数据写入 RAM 中,数据全部写完结束后将 RAM 的写使能拉低,RAM 的读使能拉高,通过 app 的子模块 vga_disp 显示接收的数据。

也就是说,RAM 里默认有一张 logo 图:上电后 VGA 先显示 logo;收到新图后,RAM 内容被 PC 发来的像素数据覆盖,VGA 立刻改显新图。读写地址独立,正是伪双口 RAM 的典型用法。

(2)将 UDP 端口的 app_rx_data、app_rx_data_length、app_rx_data_valid 信号接入到 app 端口,当 app_rx_data_valid 高有效时,RAM 数据的地址随时钟加一,同时写入 app_rx_data 的数据。接收数据时端口时序如图 7-5 所示。


图 7-5

(3)图像数据的接收采用三段式状态机接收,如下图 7-6 所示:


图 7-6

通过该状态机将数据接收并写入 RAM 中,当写入完毕后拉高 RAM 的读使能,从而进入图像显示模块。状态机把「等待包 → 接收数据 → 写完切换显示」三个阶段管理得清清楚楚,这也是初学者练习三段式状态机的好例子。

(4)图像的显示模块可参考 ROM 显示图像实验。

(5)PC 传输数据可通过网口工具 NetAssist 进行数据输入。在快捷指令中输入包含的地址校验位以及长度和数据的包并循环发送,在接收区可接收到环回程序送回的接收数据。在 VGA 显示器上可观察到想要的颜色图像。

时序约束

以太网设计对时钟约束要求严格,下面是本工程的 SDC 约束要点。首先定义输入时钟与 RGMII 接收时钟:

create_clock -name clk_in -period 40 -waveform {0 20} [get_ports {clk_25}]

create_clock -name phy1_rgmii_rx_clk -period 8 -waveform {0 4} [get_ports {phy1_rgmii_rx_clk}] create_generated_clock -name {pll_inst_125M_0} -source [get_ports {clk_25}] -master_clock {clk_in} -multiply_by 5.000 [get_pins {u_clk_gen/u_pll_0/pll_inst.clkc[0]}]

create_generated_clock -name {pll_inst_125M_1} -source [get_ports {clk_25}] -master_clock {clk_in} -multiply_by 5.000 [get_pins {u_clk_gen/u_pll_0/pll_inst.clkc[1]}]

create_generated_clock -name {pll_inst_12p5M} -source [get_ports {clk_25}] -master_clock {clk_in} -divide_by 2.000 [get_pins {u_clk_gen/u_pll_0/pll_inst.clkc[2]}]

create_generated_clock -name {pll_inst_25M} -source [get_ports {clk_25}] -master_clock {clk_in} -divide_by 1.000 [get_pins {u_clk_gen/u_pll_0/pll_inst.clkc[3]}]

#create_generated_clock -name {clk_1_25_out} -source [get_pins{u_clk_gen/u_pll_0/pll_inst.clkc[2]}] -master_clock {pll_inst_12p5M} -divide_by 10.000 [get_nets{u_clk_gen/u_udp_clk_gen_1p25/div_reg3}]
create_generated_clock -name {udp_clk_125m} -add -source [get_pins{u_clk_gen/u_pll_0/pll_inst.clkc[1]}] -master_clock {pll_inst_125M_1} -divide_by 1.000 [get_nets{udp_clk}]
create_generated_clock -name {udp_clk_12p5m} -add -source [get_pins{u_clk_gen/u_pll_0/pll_inst.clkc[2]}] -master_clock {pll_inst_12p5M} -divide_by 1.000 [get_nets{udp_clk}]
create_generated_clock -name {udp_clk_1p25m} -add -source [get_pins{u_clk_gen/u_pll_0/pll_inst.clkc[2]}] -master_clock {pll_inst_12p5M} -divide_by 10.000 [get_nets{udp_clk}]
set_clock_groups -exclusive -group [get_clocks {udp_clk_125m}]
set_clock_groups -exclusive -group [get_clocks {udp_clk_12p5m}]
set_clock_groups -exclusive -group [get_clocks {udp_clk_1p25m}]
set_clock_groups -exclusive -group [get_clocks {phy1_rgmii_rx_clk}]
derive_pll_clocks -instance [get_cells {u_rx_pll/pll_inst}]

set_clock_groups -exclusive 在告诉时序工具什么

udp_clk 这条网络可能工作在 125M、12.5M 或 1.25M 三种频率之一(三种模式互斥),把它们声明为互斥时钟组后,时序分析工具就不会在三个频率之间做毫无意义的跨时钟路径分析,既加快编译,又避免误报违例。

上板验证

首先将电脑 IP 修改为「192.168.240.2」,如图 7-7 所示:


图 7.7

然后连接开发板:网线插在开发板上,网线另一端连接到主机;插好 VGA 线缆,另一端连接显示器;插好下载线打开电源,烧写程序到开发板内,如图 7-8 所示。


图 7-8

烧写程序到开发板后,此时显示屏显示图像如图 7-9 所示:


图 7-9

打开网络调试助手,点击批量发送定义,如图 7-10 所示。


图 7-10

然后导入图像文件,如下图 7-11 所示,图像文件在工程「图像文件」内。

KONXIN


图 7-11


图 7-12

图 7-12 中导入的是 cfg 文件,图 7-13 表示成功导入:

网络调试助手
数据日志 | 用户支持 | NetAssist V5.0.3 快捷指令 | 批重发送 | 历史发送 | 自动应答 | Modbus指令 | JT806终端模拟 | 数据波形 | 浮点转换 | 校验计算 | ASCII码表 | 编号 | 延迟 | 数据
0110OF 00 00 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 1备注
0210OF 00 10 10 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
0310OF 00 20 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 1
0410OF 00 30 10 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
05提示0 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 03
06成功导入16条记录!0 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 1
07确定0 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 3
080 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 4
090 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
100 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
110 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
1210OF 00 BD 10 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F...

图 7-13

从这张截图能看出发送数据的组织方式:每一行是一条快捷指令,报文头「0F 00」之后跟目标地址与数据长度,再接图像的一行像素数据,按行拆包循环发送。

设置远程主机地址后打开端口,如下图 7-14 所示:


图 7-14

开启循环模式,点击批量发送,如下图 7-15 所示:


图 7-15

此时可以在显示器上看到中心出现的蓝色图像,验证成功,其发送的数据可以自行定义。

现象与总结

完整链路跑通后:VGA 先显示 RAM 预存的 logo,PC 用 NetAssist 循环批量发送图像包,几秒内 VGA 中央切换为 PC 发来的蓝色图像。整个过程不丢帧、不花屏,说明 UDP 接收、RAM 写入、VGA 读取三个环节的配合是正确的。

排障清单

  • PC 与开发板必须处于同一网段:先把 PC 的 IP 改成 192.168.240.2,否则 NetAssist 连远程主机都不通;
  • NetAssist 的协议类型要选 UDP,远程端口与例程一致;循环模式别忘了开启,单次发送只写一包,图像自然不完整;
  • 网线插好后注意看开发板网口的指示灯,链路灯不亮先查物理连接;
  • 图像不完整或有错位时,检查每包数据的长度字段与实际字节数是否一致。

通过本实验,我们把「PC 应用 → UDP → 以太网 MAC → PHY → FPGA 逻辑 → 显示」这条完整数据通路走了一遍。之后做图像回传、网络视频流、远程控制等项目,都是在这个框架上替换应用层数据而已。

自测