FPGA以太网入门(一)——MDIO接口读写测试实验(基于紫光同创)
0 致读者
此篇为专栏《紫光同创FPGA开发笔记》的第五篇,记录我的学习 FPGA 的一些开发过程和心得感悟,刚接触 FPGA 的朋友们可以先去此博客 《FPGA零基础入门学习路线》来做最基础的扫盲。
本篇内容基于笔者实际开发过程和正点原子资料撰写,将会详细讲解此 FPGA 实验的全流程,诚挚地欢迎各位读者在评论区或者私信我交流!
在以太网通信中,设备之间的物理层链路均由 PHY 芯片(物理层芯片,本文以 YT8511 为例)建立。PHY 芯片有一个配置接口,即 MDIO 接口,可以配置 PHY 芯片的工作模式以及获取 PHY 芯片的若干状态信息。本篇博客我们来学习如何使用紫光同创 FPGA 实现对 PHY 芯片的 MDIO 接口进行读写测试。
本文的工程文件开源地址如下(基于ATK-DFPGL22G,大家 clone 到本地就可以直接跑仿真,如果要上板请根据自己的开发板更改约束即可):
https://github.com/ChinaRyan666/PDS_MDIO_RW
1 实验任务
本文的实验任务是使用紫光同创 FPGA 开发板上的以太网接口,完成 MDIO 接口的读写测试实验。板载的触摸按键(TPAD)控制 MDIO 接口进行软复位,并通过两个 LED 灯实时指示当前网口的连接速度。
2 简介
2.1 以太网概述
以太网(Ethernet) 是当今现有局域网采用的最通用的通信协议标准, 该标准定义了在局域网中采用的电缆类型和信号处理方法。以太网凭借其成本低、通信速率高、抗干扰性强等优点被广泛应用在网络远程监控、 交换机、工业自动化等对通信速率要求较高的场合。
以太网是一种产生较早,使用相当广泛的局域网。其最初是由 Xerox(施乐)公司创建并由 Xerox、Intel 和 DEC 公司联合开发的基带局域网规范,后来被电气与电子工程师协会(IEEE)所采纳作为 802.3 的标准。
以太网的分类有标准以太网(10Mbit/s)、快速以太网(100Mbit/s)和千兆以太网(1000Mbit/s)。随着以太网技术的飞速发展, 市场上也出现了万兆以太网(10Gbit/s),它扩展了 IEEE 802.3 协议和 MAC 规范,使其技术支持 10Gbit/s 的传输速率。 在实际应用中,千兆以太网理论上最高通信速率为 1000Mbit/s,可以胜任大部分的使用场景。
以太网通信离不开连接端口的支持,网络数据连接的端口就是以太网接口。以太网接口类型有 RJ45 接口、RJ11 接口(电话线接口)、SC 光纤接口等。其中 RJ45 接口是我们现在最常见的网络设备接口(如:电脑网口),我们开发板使用的就是这种接口。
RJ45 接口俗称 “水晶头”,专业术语为 RJ45 连接器,由插头(接头、水晶头) 和插座(母座) 组成,属于双绞线以太网接口类型。RJ45 插头只能沿固定方向插入,设有一个塑料弹片与 RJ45 插槽卡住以防止脱落。
RJ45 接口样式如下图所示:
RJ45 接口定义以及各引脚功能在不同通信速率下的定义有区别,下图是在 10M/100M 通信速率下的定义,由下图可知,RJ45 插座只使用了 1、2、3、6 这四根线,其中 1、2 这组负责传输数据(TX+、TX-),而 3、6 这组负责接收数据(RX+、RX-),另外四根线是备用的。
而在 1000M 的通信速率下,RJ45 插座的 8 根线都有用到,且都是双向引脚。需要说明的是,支持千兆网通信的 RJ45 接口是向下兼容的,即也支持 10M/100M 通信速率,只不过不同的通信速率,其引脚功能有区别。千兆网各引脚功能如下图所示:
从硬件的角度来说,以太网接口电路主要由 MAC(Media Access Control)控制器和物理层接口 PHY(Physical Layer)两大部分构成。MAC 指媒体访问控制子层协议,它和 PHY 接口既可以整合到单颗芯片内,也可以独立分开,对于本次设计来说,MAC 控制器由 FPGA 实现,PHY 芯片指开发板板载的以太网芯片。
PHY 在发送数据的时候,接收 MAC 发过来的数据(对 PHY 来说,没有帧的概念,都是数据而不管什么地址,数据还是 CRC),把并行数据转化为串行流数据,按照物理层的编码规则把数据编码转换为模拟信号发送出去,接收数据时的流程反之。
PHY 还提供了和对端设备连接的重要功能,并通过 LED 灯显示出自己目前的连接状态和工作状态。当我们给网卡接入网线的时候, PHY 芯片不断发出脉冲信号来检测对端是否有设备,它们通过标准的 “语言” 交流,互相协商并确定连接速度、双工模式、是否采用流控等。通常情况下,协商的结果是两个设备中能同时支持的最大速度和最好的双工模式。这个技术被称为 Auto Negotiation,即自协商。
2.2 MDIO 接口
MAC 和 PHY 芯片有一个配置接口,即 MDIO 接口,可以配置 PHY 芯片的工作模式以及获取 PHY 芯片的若干状态信息。 PHY 芯片内部包含一系列寄存器,用户通过这些寄存器来配置 PHY 芯片的工作模式以及获取 PHY 芯片的若干状态信息,如连接速率、双工模式、自协商状态等。
FPGA 通过 MDIO 接口对 PHY 芯片内部的寄存器进行配置。通常情况下,PHY 芯片在默认状态下也可以正常工作,在做以太网通信实验时,对 MDIO 接口的配置不是必须的,本章旨在向大家介绍 MDIO 接口以及如何对 MDIO 接口进行读写操作。MAC 和 PHY 连接示意图如下图所示。
MDIO 接口也称为 SMI 接口(Serial Management Interface,串行管理接口),包括 ETH_MDC(数据管理时钟)和 ETH_MDIO(数据管理输入输出)两条信号线。ETH_MDC 为 ETH_MDIO 提供时钟,ETH_MDC 的最大时钟不能超过 12.5Mhz。ETH_MDIO 为双向数据引脚,既用于发送数据,也用于接收数据。
MDIO 接口的读写通信协议如下图所示:
名称 | 功能 |
---|---|
Preamble | 32 位前导码,由 MAC 端发送 32 位逻辑 “1”,用于同步 PHY 芯片。 |
ST(Start of Frame) | 2 位帧开始信号,用 01 表示。 |
OP(Operation Code) | 2 位操作码,读:10 写:01。 |
PHYAD(PHY Address) | 5 位 PHY 地址,用于表示与哪个 PHY 芯片通信,因此一个 MAC 上可以连接多个 PHY 芯片。 |
REGAD(Register Address) | 5 位寄存器地址,可以表示共 32 位寄存器。 |
TA(Turnaround) | 2 位转向,在读命令中,MDIO 在此时由 MAC 驱动改为 PHY 驱动,在第一个 TA位,MDIO 引脚为高阻状态,第二个 TA 位,PHY 将 MDIO 引脚拉低,准备发送数据;在写命令中,不需要 MDIO 方向发生变化,MAC 固定输出 2’b10,随后开始写入数据。 |
DATA | 16 位数据,在读命令中, PHY 芯片将读到的对应 PHYAD 的 REGAD 寄存器的数据写到 DATA 中;在写命令中, PHY 芯片将接收到的 DATA 写入 REGAD 寄存器中。需要注意的是,在 DATA 传输的过程中,高位在前,低位在后。 |
IDLE | 空闲状态,此时 MDIO 为无源驱动,处于高阻状态,但一般用上拉电阻使其上拉至高电平。 |
MDIO 接口读时序图如下图所示:
上图是以 PHY 地址为 0x01,从寄存器地址 0x00 读出数据为例。整个读操作过程的 MDC 时钟由 MAC 驱动,同时 MAC 驱动 MDIO 引脚输出前导码+帧开始+操作码+PHY 地址+寄存器地址,随后 MDIO 引脚切换至 PHY 驱动。
在第一个 TA 位,MDIO 引脚为高阻状态,第二个 TA 位为低电平,表示 PHY 芯片成功响应,并且接下来会输出 16 位寄存器数据;而如果第二个 TA 位处于高电平,则 PHY 芯片响应失败,有可能 PHY 地址不正确或者其它时序的错误。
需要注意的是,PHY 在 MDC 时钟的上升沿采集数据,为保证数据的稳定传输,MAC 在 MDC 的下降沿更新 MDIO 引脚的数据。当 MDIO 引脚切换至 PHY 驱动时,MDIO 数据在 MDC 时钟的下降沿更新,因此 MAC 在 MDC 时钟的上升沿采集数据。
在读操作结束后,MAC 将 MDIO 引脚输出高阻,此时 MDIO 引脚的外部上拉电阻会将 MDIO 引脚拉高,此时 MDIO 接口处于空闲状态。
MDIO 接口写时序图 如下图所示:
上图是以 PHY 地址为 0x01,向寄存器地址 0x00 写入 0x1340 为例,在整个写操作过程中,MDC 时钟和 MDIO 引脚一直由 MAC 端驱动,按照 MDIO 接口写通信协议开始传输数据。
需要注意的是,PHY 在 MDC 时钟的上升沿采集数据,为保证数据的稳定传输,MAC 在 MDC 的下降沿将数据更新至 MDIO 引脚。在写操作结束后,MAC 将 MDIO 引脚输出高阻,此时 MDIO 引脚的外部上拉电阻会将 MDIO 引脚拉高,此时 MDIO 接口处于空闲状态。
2.3 以太网 PHY 芯片(以YT8511为例)
-
PHY 地址
YT8511 芯片的 PHY 地址由 LED_ACT 和 RXD[1:0] 引脚决定,如下图所示:PHY 地址一共有五位,其中高两位固定为 00,LED_ACT 和 RXD[1:0] 引脚表示低三位,我们可以通过硬件电路设置 LED_ACT 和 RXD[1:0] 引脚引脚为上拉或者下来,即分配为高低电平,0 或 1,从而表示不同的地址。
-
复位
YT8511 芯片复位后,PHY 内部寄存器的数据会恢复默认的状态,并且重新开始和 MAC 进行自协商。 YT8511 支持两种复位方式,一种是硬件复位,另外一种是软件复位。硬件复位时通过 ETH_RST_N 引脚实现对 PHY 芯片的复位,当 ETH_RST_N 引脚持续 10ms 的低电平时,即可实现对 PHY 芯片的复位。软件复位通过向寄存器地址 0x00 的 Bit[15] 写入 1 进行复位,并且在完成复位后,该位会自动清零。 -
寄存器
YT8511 共有 22 位寄存器,本实验用到的三个寄存器,控制寄存器、状态寄存器以及 PHY 芯片具体状态寄存器。关于寄存器的具体信息大家可以查阅自己开发板上以太网 PHY 芯片对应的芯片手册。
3 程序设计
根据实验任务,我们可以大致规划出系统的控制流程:首先每隔一段时间通过 MDIO 接口从 PHY 内部寄存器中读取基本状态寄存器(BMSR)和特定状态寄存器(PHYSR)的值,从而获取到自协商完成状态、连接状态和连接速度,将网口的连接速度通过 LED 灯进行指示;当 FPGA 检测到 TPAD 触摸按键按下时,开始通过 MDIO 接口对 PHY 进行软复位,在软复位完成后,PHY 会重新开始自协商,此时 LED 灯仍然会每隔一段时间获取当前网口的连接状态以及连接速度。由此画出系统的功能框图如下图所示:
MDIO 接口驱动模块实现了对 MDIO 接口的读写驱动。MDIO 接口控制模块根据输入的触摸按键,实现了对 MDIO 接口驱动模块的写操作,并每隔一段时间对 MDIO 接口驱动模块进行读操作,将获取到的网口连接状态与速度通过 LED 灯进行指示。FPGA 顶层模块例化了以下两个模块,MDIO 接口控制模块(mdio_ctrl)和 MDIO 接口驱动模块(mdio_dri),实现了各模块之间的数据交互。其中 MDIO 接口驱动模块预留了用户接口,方便对 MDIO 接口进行读写操作。
当 FPGA 通过 MDIO 控制模块向 MDIO 驱动模式读写数据时,拉高触发控制信号 op_exec 来触发 MDIO 驱动模块, op_rh_wl 用于表示读或者写操作,当 op_rh_wl 为低电平时,MDIO 驱动模块执行写操作,当 op_rh_wl 为高电平时, MDIO 驱动模块执行读操作。op_addr 表示读写寄存器地址,op_wr_data 信号表示写入的数据,op_rd_data 信号表示从 MDIO 接口的寄存器中读到的数据。当读或者写操作完成时,MDIO 驱动模块会产生一个时钟周期的 op_done 信号,表示 MDIO 驱动模块读或者写操作完成。
顶层模块的代码如下:
module mdio_rw_test(
input sys_clk ,
input sys_rst_n,
//MDIO接口
output eth_mdc , //PHY管理接口的时钟信号
inout eth_mdio , //PHY管理接口的双向数据信号
output eth_rst_n, //以太网复位信号
input touch_key, //触摸按键
output [1:0] led //LED连接速率指示
);
//wire define
wire op_exec ; //触发开始信号
wire op_rh_wl ; //低电平写,高电平读
wire [4:0] op_addr ; //寄存器地址
wire [15:0] op_wr_data ; //写入寄存器的数据
wire op_done ; //读写完成
wire [15:0] op_rd_data ; //读出的数据
wire op_rd_ack ; //读应答信号 0:应答 1:未应答
wire dri_clk ; //驱动时钟
//硬件复位
assign eth_rst_n = sys_rst_n;
//MDIO接口驱动
mdio_dri #(
.PHY_ADDR (5'h04), //PHY地址 3'b100
.CLK_DIV (6'd10) //分频系数
)
u_mdio_dri(
.clk (sys_clk),
.rst_n (sys_rst_n),
.op_exec (op_exec ),
.op_rh_wl (op_rh_wl ),
.op_addr (op_addr ),
.op_wr_data (op_wr_data),
.op_done (op_done ),
.op_rd_data (op_rd_data),
.op_rd_ack (op_rd_ack ),
.dri_clk (dri_clk ),
.eth_mdc (eth_mdc ),
.eth_mdio (eth_mdio )
);
//MDIO接口读写控制
mdio_ctrl u_mdio_ctrl(
.clk (dri_clk),
.rst_n (sys_rst_n ),
.soft_rst_trig (touch_key ),
.op_done (op_done ),
.op_rd_data (op_rd_data),
.op_rd_ack (op_rd_ack ),
.op_exec (op_exec ),
.op_rh_wl (op_rh_wl ),
.op_addr (op_addr ),
.op_wr_data (op_wr_data),
.led (led )
);
endmodule
顶层模块主要完成对其余模块的例化。在程序的第 24 行将系统复位(sys_rst_n)直接赋值给以太网的硬件复位信号(eth_rst_n),当复位按键按下时,会对 PHY 芯片进行硬件复位。
在程序的第 28 行和 29 行代码例化了两个参数,分别表示 PHY 地址和 ETH_MDC 相对于输入时钟的分频系数。 这里将 PHY 地址设置为 5’h04, 如果 PHY 地址设置错误, 会导致对 MDIO 接口的读写操作失败。另外需要注意的是,ETH_MDC 的时钟频率不能超过 12.5Mhz。
由前面的 MDIO 接口读写时序图我们可以发现,MDIO 驱动模块非常适合采用状态机来编写。状态机的状态跳转图如下图所示,总共有 6 个状态,分别为 st_idle(空闲状态)、st_pre(发送前导码状态)、st_start(发送帧开始 + 操作码)、st_addr(发送 PHY 地址 + 寄存器地址)、st_wr_data(发送 TA + 写入数据)和 st_rd_data(接收 TA + 接收数据)。当状态机处于空闲状态时,如果触发信号拉高(op_exec = 1),状态机进入发送前导码状态。另外当状态机处于 st_addr 时,在发送完 PHY 地址和寄存器地址之后,接下来状态机根据读或者写操作来跳转至 st_wr_data 状态或者 st_rd_data 状态。在读或者写完数据后,状态机重新跳转至空闲状态。
程序中我们采用的是三段式状态机,由于代码较长,这里仅贴出部分代码(完整代码见文章开头的开源地址),代码如下:
省略部分代码……
(以下所指的程序行数来源于源工程中的代码,各位读者可打开文章开头我提供的开源地址来进行源码的查看)
在程序的第 69 行,通过 mdio_dir(MDIO 引脚方向选择)信号控制 eth_mdio 引脚的方向,当设置成输入时,FPGA 将该引脚输出高阻(1’bz);当设置成输出时,将 FPGA 驱动的 mdio_out 信号连接至 eth_mdio。
由于 eth_mdc 需要在输入时钟的基础上进行分频,为了方便操作,这里先对输入的时钟进行分频,得到一个 dri_clk 时钟,作为 MDIO 驱动模块和 MDIO 控制模块的操作时钟。eth_mdc 在 dri_clk 的基础上进行 2 分频,由于输入的参数 CLK_DIV 为 eth_mdc 相对于输入时钟的分频系数,因此为了得到 dri_clk 的分频系数,需要将 CLK_DIV 除以 2,如代码中第 72 行所示。
程序中第 74 行至第 86 行根据分频系数(clk_divide),得到 dri_clk 的时钟。在程序的第 88 行至第 96 行代码,当 cnt 一直累加时,eth_mdc 的时钟相当于对 dri_clk 进行 2 分频。当开始对 MDIO 接口进行读写操作时,cnt 累加,此时才会产生 eth_mdc 时钟;当读写操作结束后,cnt 等于 0,eth_mdc 将一直处于高电平。
需要说明的是,由于 clk_divide 等于 CLK_DIV 除以 2,程序中第 74 行至第 86 行代码只支持偶数分频,所以最终生成的 eth_mdc 的时钟频率相比于输入的 CLK_DIV 可能产生偏差。本次实验中,CLK_DIV 等于 10,因此 clk_divide 等于 5,dri_clk 的时钟频率为 12.5Mhz,eth_mdc 为 6.25Mhz。
程序的第 255 行至 289 行代码为状态机的 st_wr_data(发送 TA+写入数据)和 st_rd_data(接收 TA+接收数据)状态。在 st_wr_data 状态下,数据是在 eth_mdc 的下降沿写入,而在 st_rd_data 状态,数据在 erth_mdc 的上升沿读出。值得一提是,在 st_rd_data 状态下,程序中根据 TA 的第二位,判断 PHY 芯片有没有应答,如果没有应答,则说明读取数据失败,如程序中第 264 行代码所示。
MDIO 接口读写操作驱动模块的仿真代码:
module tb_mdio_dri;
//parameter define
parameter T = 20; //时钟周期为20ns
parameter OP_CYCLE = 100; //读写操作周期
parameter PHY_ADDR = 5'h04; //设置PHY地址
//reg define
reg sys_clk; //时钟信号
reg sys_rst_n; //复位信号
reg op_exec ;
reg op_rh_wl ;
reg [4:0] op_addr ;
reg [15:0] op_wr_data;
reg [3:0] flow_cnt ;
reg [13:0] delay_cnt ;
//wire define
wire [15:0] op_rd_data;
wire op_done ;
wire op_rd_ack ;
wire eth_mdc ;
wire eth_mdio ;
wire dri_clk ;
//*****************************************************
//** main code
//*****************************************************
//给输入信号初始值
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0; //复位
#(T+1) sys_rst_n = 1'b1; //在第21ns的时候复位信号信号拉高
end
//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(T/2) sys_clk = ~sys_clk;
always @(posedge dri_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
op_exec <= 1'b0;
op_rh_wl <= 1'b0;
op_addr <= 1'b0;
op_wr_data <= 1'b0;
flow_cnt <= 1'b0;
delay_cnt <= 1'b0;
end
else begin
case(flow_cnt)
'd0 : flow_cnt <= flow_cnt + 1'b1;
'd1 : begin
op_exec <= 1'b1; //拉高触发信号
op_rh_wl <= 1'b0; //写操作
op_addr <= 5'h05; //写地址
op_wr_data <= 16'h55aa; //写数据
flow_cnt <= flow_cnt + 1'b1;
end
'd2 : begin
op_exec <= 1'b0;
flow_cnt <= flow_cnt + 1'b1;
end
'd3 : begin
if(op_done)
flow_cnt <= flow_cnt + 1'b1;
end
'd4 : begin
delay_cnt <= delay_cnt + 1'b1;
if(delay_cnt == OP_CYCLE - 1'b1)
flow_cnt <= flow_cnt + 1'b1;
end
'd5 : begin
op_exec <= 1'b1;
op_rh_wl <= 1'b1; //读操作
op_addr <= 5'h05;
op_wr_data <= 16'h55aa;
flow_cnt <= flow_cnt + 1'b1;
end
'd6 : begin
op_exec <= 1'b0;
flow_cnt <= flow_cnt + 1'b1;
end
'd7 : begin
if(op_done)
flow_cnt <= flow_cnt + 1'b1;
end
default:;
endcase
end
end
pullup(eth_mdio); //MDIO信号上拉
//为PHY寄存器赋初始值
reg we_i;
reg strobe_i;
reg [7:0] address_i;
reg [7:0] data_i;
initial begin
we_i = 1'b0;
strobe_i = 1'b0;
address_i = 8'd0;
data_i = 8'd0;
#(10*T)
we_i = 1'b1; //像地址5'h05写入16'h55aa
strobe_i = 1'b1;
address_i = 8'h0a;
data_i = 8'h55;
#T
address_i = 8'h0b;
data_i = 8'haa;
#T
address_i = 8'h40; //设置PHY芯片的PHY地址
data_i = PHY_ADDR;
#T
we_i = 1'b0;
strobe_i = 1'b0;
address_i = 8'd0;
data_i = 8'd0;
end
//例化MDIO接口驱动
mdio_dri #(
.PHY_ADDR (PHY_ADDR), //PHY地址
.CLK_DIV (6'd10) //分频系数
)
u_mdio_dri(
.clk (sys_clk),
.rst_n (sys_rst_n),
.op_exec (op_exec ),
.op_rh_wl (op_rh_wl ),
.op_addr (op_addr ),
.op_wr_data (op_wr_data),
.op_done (op_done ),
.op_rd_data (op_rd_data),
.op_rd_ack (op_rd_ack ),
.dri_clk (dri_clk ),
.eth_mdc (eth_mdc ),
.eth_mdio (eth_mdio )
);
//例化MDIO接口从机仿真模型
mdio_slave_interface u_mdio_slave_interface(
.rst_n_i (sys_rst_n),
.mdc_i (eth_mdc),
.mdio (eth_mdio),
//wishbone interface
.clk_i (sys_clk),
.rst_i (~sys_rst_n),
.address_i (address_i),
.data_i (data_i),
.data_o (),
.strobe_i (strobe_i),
.we_i (we_i),
.ack_o ()
);
endmodule
我仿真的是 MDIO 接口的驱动模块,所以需要一个 MDIO 从接口 mdio_slave_interface.v 文件(该模型是从网上获取的),将该模块例化在仿真代码的 145~160 行。
MDIO 接口读 PHYSR 寄存器的仿真波形图如下图所示:
由上图可知,op_exec 的脉冲信号作为 MDIO 接口读写的触发信号,图中 op_rh_wl 为高电平,表示读操作,op_addr 寄存器地址为 5’h05。 在 TA 位时,mdio_dir 由高电平切换至低电平,表示 MDIO 引脚由输出切换至输入,随后 op_rd_ack 变为低电平,说明 PHY 芯片应答成功。在整个读操作结束后,MDIO 驱动模块产生一个脉冲的 op_done 信号,此时从状态寄存器中读出的数据为 16’h55aa。
MDIO 控制模块代码如下:
module mdio_ctrl(
input clk ,
input rst_n ,
input soft_rst_trig , //软复位触发信号
input op_done , //读写完成
input [15:0] op_rd_data , //读出的数据
input op_rd_ack , //读应答信号 0:应答 1:未应答
output reg op_exec , //触发开始信号
output reg op_rh_wl , //低电平写,高电平读
output reg [4:0] op_addr , //寄存器地址
output reg [15:0] op_wr_data , //写入寄存器的数据
output [1:0] led //LED灯指示以太网连接状态
);
//reg define
reg rst_trig_d0;
reg rst_trig_d1;
reg rst_trig_flag; //soft_rst_trig信号触发标志
reg [23:0] timer_cnt; //定时计数器
reg timer_done; //定时完成信号
reg start_next; //开始读下一个寄存器标致
reg read_next; //处于读下一个寄存器的过程
reg link_error; //链路断开或者自协商未完成
reg [2:0] flow_cnt; //流程控制计数器
reg [1:0] speed_status; //连接速率
//wire define
wire pos_rst_trig; //soft_rst_trig信号上升沿
//采soft_rst_trig信号上升沿
assign pos_rst_trig = ~rst_trig_d1 & rst_trig_d0;
//未连接或连接失败时led赋值00
// 01:10Mbps 10:100Mbps 11:1000Mbps 00:其他情况
assign led = link_error ? 2'b00: speed_status;
//对soft_rst_trig信号延时打拍
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rst_trig_d0 <= 1'b0;
rst_trig_d1 <= 1'b0;
end
else begin
rst_trig_d0 <= soft_rst_trig;
rst_trig_d1 <= rst_trig_d0;
end
end
//定时计数
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
timer_cnt <= 1'b0;
timer_done <= 1'b0;
end
else begin
if(timer_cnt == 24'd1_000_000 - 1'b1) begin
timer_done <= 1'b1;
timer_cnt <= 1'b0;
end
else begin
timer_done <= 1'b0;
timer_cnt <= timer_cnt + 1'b1;
end
end
end
//根据软复位信号对MDIO接口进行软复位,并定时读取以太网的连接状态
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
flow_cnt <= 3'd0;
rst_trig_flag <= 1'b0;
speed_status <= 2'b00;
op_exec <= 1'b0;
op_rh_wl <= 1'b0;
op_addr <= 1'b0;
op_wr_data <= 1'b0;
start_next <= 1'b0;
read_next <= 1'b0;
link_error <= 1'b0;
end
else begin
op_exec <= 1'b0;
if(pos_rst_trig)
rst_trig_flag <= 1'b1; //拉高软复位触发标志
case(flow_cnt)
2'd0 : begin
if(rst_trig_flag) begin //开始对MDIO接口进行软复位
op_exec <= 1'b1;
op_rh_wl <= 1'b0;
op_addr <= 5'h00;
op_wr_data <= 16'h9140; //Bit[15]=1'b1,表示软复位
flow_cnt <= 3'd1;
end
else if(timer_done) begin //定时完成,获取以太网连接状态
op_exec <= 1'b1;
op_rh_wl <= 1'b1;
op_addr <= 5'h01;
flow_cnt <= 3'd2;
end
else if(start_next) begin //开始读下一个寄存器,获取以太网通信速度
op_exec <= 1'b1;
op_rh_wl <= 1'b1;
op_addr <= 5'h11;
flow_cnt <= 3'd2;
start_next <= 1'b0;
read_next <= 1'b1;
end
end
2'd1 : begin
if(op_done) begin //MDIO接口软复位完成
flow_cnt <= 3'd0;
rst_trig_flag <= 1'b0;
end
end
2'd2 : begin
if(op_done) begin //MDIO接口读操作完成
if(op_rd_ack == 1'b0 && read_next == 1'b0) //读第一个寄存器,接口成功应答,
flow_cnt <= 3'd3; //读第下一个寄存器,接口成功应答
else if(op_rd_ack == 1'b0 && read_next == 1'b1)begin
read_next <= 1'b0;
flow_cnt <= 3'd4;
end
else begin
flow_cnt <= 3'd0;
end
end
end
2'd3 : begin
flow_cnt <= 3'd0; //链路正常并且自协商完成
if(op_rd_data[5] == 1'b1 && op_rd_data[2] == 1'b1)begin
start_next <= 1;
link_error <= 0;
end
else begin
link_error <= 1'b1;
end
end
3'd4: begin
flow_cnt <= 3'd0;
if(op_rd_data[15:14] == 2'b10)
speed_status <= 2'b11; //1000Mbps
else if(op_rd_data[15:14] == 2'b01)
speed_status <= 2'b10; //100Mbps
else if(op_rd_data[15:14] == 2'b00)
speed_status <= 2'b01; //10Mbps
else
speed_status <= 2'b00; //其他情况
end
endcase
end
end
endmodule
程序中第 48 至第 63 行代码实现计数定时的功能,每当计数器计数到 1000000 - 1 时,会产生一个周期的脉冲信号(timer_done)。该模块输入的时钟频率为 12.5Mhz,因此定时周期为 80ms。
程序中第 81 行至第 125 行代码根据软复位信号对 MDIO 接口进行软复位,并定时读取以太网的连接状态。其中程序中第 138 行至 145 行代码,根据状态寄存器的值,为连接速率状态位(speed_status)赋值。
4 下载验证
编译工程并生成比特流 .sbit
文件后,此时将下载器一端连接电脑, 另一端与开发板上的 JTAG 下载口连接, 将网线一端连接开发板的网口,另一端连接电脑的网口或者路由器,接下来连接电源线,并打开开发板的电源开关,网口的位置如下图所示。(根据自己的开发板进行管脚约束后然后连接即可,注意芯片型号在 PDS 中不要选错了,本文以正点原子的开发板为例来讲解)
点击 PDS 工具栏的下载按钮,在弹出的 Fabric Configuration 界面中双击 “Boundary Scan”, 我们将生成好的 .sbit
流文件下载到开发板中去。
程序下载完成后, 等待几秒即可看到开发板 LED 灯的点亮状态,如下图所示:
如上图可知,两个 LED 灯都处于点亮状态,因此通信速率为 1000Mbps。如果按下复位按键可以对 PHY 进行硬件复位,按下 TPAD 触摸按键会对 PHY 进行软复位,此时 PHY 芯片会重新开始与另一端设备进行自协商,等待几秒后, LED 灯重新点亮。
另外,如果开发板另一端连接的是电脑的网口,此时可以查看自协商后的通信速率。查看方法是点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(不同的 Windows 版本操作可能存在差异,但基本相同)。
点击上图中的 “未识别的网络(无 Internet)”,弹出如下图所示界面。
点击 “更改适配器” 选项,弹出如下图所示界面。
如果看到上图 “以太网” 显示未识别的网络之后,说明硬件连接是没有问题的,接下来鼠标右击以太网,选择 “状态”,如下面两张图片所示。
由上图可知,开发板和电脑自协商的通信速率为 1Gbps(1000Mbps)。
5 总结
以上是 FPGA 以太网系列的第一篇,未来会继续更新以太网系列,感兴趣的朋友可以关注本专栏(免费),持续学习,共同进步!
希望以上的内容对您有所帮助,诚挚地欢迎各位读者在评论区或者私信我交流!
微博:沂舟Ryan (@沂舟Ryan 的个人主页 - 微博 )
GitHub:ChinaRyan666
微信公众号:沂舟无限进步(内含精品资料及详细教程)
如果对您有帮助的话请点赞支持下吧!文章来源:https://www.toymoban.com/news/detail-852574.html
集中一点,登峰造极。文章来源地址https://www.toymoban.com/news/detail-852574.html
到了这里,关于FPGA以太网入门(一)——MDIO接口读写测试实验(基于紫光同创)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!