FPGA以太网入门(一)——MDIO接口读写测试实验(基于紫光同创)

这篇具有很好参考价值的文章主要介绍了FPGA以太网入门(一)——MDIO接口读写测试实验(基于紫光同创)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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(施乐)公司创建并由 XeroxIntelDEC 公司联合开发的基带局域网规范,后来被电气与电子工程师协会(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 接口样式如下图所示:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

RJ45 接口定义以及各引脚功能在不同通信速率下的定义有区别,下图是在 10M/100M 通信速率下的定义,由下图可知,RJ45 插座只使用了 1、2、3、6 这四根线,其中 1、2 这组负责传输数据(TX+TX-),而 3、6 这组负责接收数据(RX+RX-),另外四根线是备用的。

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

而在 1000M 的通信速率下,RJ45 插座的 8 根线都有用到,且都是双向引脚。需要说明的是,支持千兆网通信的 RJ45 接口是向下兼容的,即也支持 10M/100M 通信速率,只不过不同的通信速率,其引脚功能有区别。千兆网各引脚功能如下图所示:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

从硬件的角度来说,以太网接口电路主要由 MACMedia Access Control)控制器和物理层接口 PHYPhysical Layer)两大部分构成。MAC 指媒体访问控制子层协议,它和 PHY 接口既可以整合到单颗芯片内,也可以独立分开,对于本次设计来说,MAC 控制器由 FPGA 实现,PHY 芯片指开发板板载的以太网芯片。

PHY 在发送数据的时候,接收 MAC 发过来的数据(对 PHY 来说,没有帧的概念,都是数据而不管什么地址,数据还是 CRC),把并行数据转化为串行流数据,按照物理层的编码规则把数据编码转换为模拟信号发送出去,接收数据时的流程反之。

PHY 还提供了和对端设备连接的重要功能,并通过 LED 灯显示出自己目前的连接状态和工作状态。当我们给网卡接入网线的时候, PHY 芯片不断发出脉冲信号来检测对端是否有设备,它们通过标准的 “语言” 交流,互相协商并确定连接速度双工模式是否采用流控等。通常情况下,协商的结果是两个设备中能同时支持的最大速度和最好的双工模式。这个技术被称为 Auto Negotiation,即自协商


2.2 MDIO 接口

MACPHY 芯片有一个配置接口,即 MDIO 接口,可以配置 PHY 芯片的工作模式以及获取 PHY 芯片的若干状态信息。 PHY 芯片内部包含一系列寄存器,用户通过这些寄存器来配置 PHY 芯片的工作模式以及获取 PHY 芯片的若干状态信息,如连接速率、双工模式、自协商状态等。

FPGA 通过 MDIO 接口对 PHY 芯片内部的寄存器进行配置。通常情况下,PHY 芯片在默认状态下也可以正常工作,在做以太网通信实验时,对 MDIO 接口的配置不是必须的,本章旨在向大家介绍 MDIO 接口以及如何对 MDIO 接口进行读写操作。MACPHY 连接示意图如下图所示。

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

MDIO 接口也称为 SMI 接口(Serial Management Interface,串行管理接口),包括 ETH_MDC(数据管理时钟)和 ETH_MDIO(数据管理输入输出)两条信号线。ETH_MDCETH_MDIO 提供时钟,ETH_MDC 的最大时钟不能超过 12.5MhzETH_MDIO双向数据引脚,既用于发送数据,也用于接收数据。

MDIO 接口的读写通信协议如下图所示:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

名称 功能
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 接口读时序图如下图所示:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

上图是以 PHY 地址为 0x01,从寄存器地址 0x00 读出数据为例。整个读操作过程的 MDC 时钟由 MAC 驱动,同时 MAC 驱动 MDIO 引脚输出前导码+帧开始+操作码+PHY 地址+寄存器地址,随后 MDIO 引脚切换至 PHY 驱动。

在第一个 TA 位,MDIO 引脚为高阻状态,第二个 TA 位为低电平,表示 PHY 芯片成功响应,并且接下来会输出 16 位寄存器数据;而如果第二个 TA 位处于高电平,则 PHY 芯片响应失败,有可能 PHY 地址不正确或者其它时序的错误。

需要注意的是,PHYMDC 时钟的上升沿采集数据,为保证数据的稳定传输,MACMDC 的下降沿更新 MDIO 引脚的数据。当 MDIO 引脚切换至 PHY 驱动时,MDIO 数据在 MDC 时钟的下降沿更新,因此 MACMDC 时钟的上升沿采集数据。

在读操作结束后,MACMDIO 引脚输出高阻,此时 MDIO 引脚的外部上拉电阻会将 MDIO 引脚拉高,此时 MDIO 接口处于空闲状态。

MDIO 接口写时序图 如下图所示:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

上图是以 PHY 地址为 0x01,向寄存器地址 0x00 写入 0x1340 为例,在整个写操作过程中,MDC 时钟和 MDIO 引脚一直由 MAC 端驱动,按照 MDIO 接口写通信协议开始传输数据。

需要注意的是,PHYMDC 时钟的上升沿采集数据,为保证数据的稳定传输,MACMDC 的下降沿将数据更新至 MDIO 引脚。在写操作结束后,MACMDIO 引脚输出高阻,此时 MDIO 引脚的外部上拉电阻会将 MDIO 引脚拉高,此时 MDIO 接口处于空闲状态。


2.3 以太网 PHY 芯片(以YT8511为例)

  1. PHY 地址
    YT8511 芯片的 PHY 地址由 LED_ACTRXD[1:0] 引脚决定,如下图所示:PHY 地址一共有五位,其中高两位固定为 00LED_ACTRXD[1:0] 引脚表示低三位,我们可以通过硬件电路设置 LED_ACTRXD[1:0] 引脚引脚为上拉或者下来,即分配为高低电平,01,从而表示不同的地址。

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

  1. 复位
    YT8511 芯片复位后,PHY 内部寄存器的数据会恢复默认的状态,并且重新开始和 MAC 进行自协商。 YT8511 支持两种复位方式,一种是硬件复位,另外一种是软件复位。硬件复位时通过 ETH_RST_N 引脚实现对 PHY 芯片的复位,当 ETH_RST_N 引脚持续 10ms 的低电平时,即可实现对 PHY 芯片的复位。软件复位通过向寄存器地址 0x00Bit[15] 写入 1 进行复位,并且在完成复位后,该位会自动清零。

  2. 寄存器
    YT8511 共有 22 位寄存器,本实验用到的三个寄存器,控制寄存器、状态寄存器以及 PHY 芯片具体状态寄存器。关于寄存器的具体信息大家可以查阅自己开发板上以太网 PHY 芯片对应的芯片手册。



3 程序设计

根据实验任务,我们可以大致规划出系统的控制流程:首先每隔一段时间通过 MDIO 接口从 PHY 内部寄存器中读取基本状态寄存器(BMSR)和特定状态寄存器(PHYSR)的值,从而获取到自协商完成状态、连接状态和连接速度,将网口的连接速度通过 LED 灯进行指示;当 FPGA 检测到 TPAD 触摸按键按下时,开始通过 MDIO 接口对 PHY 进行软复位,在软复位完成后,PHY 会重新开始自协商,此时 LED 灯仍然会每隔一段时间获取当前网口的连接状态以及连接速度。由此画出系统的功能框图如下图所示:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

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 状态。在读或者写完数据后,状态机重新跳转至空闲状态。

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

程序中我们采用的是三段式状态机,由于代码较长,这里仅贴出部分代码(完整代码见文章开头的开源地址),代码如下:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

省略部分代码……

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

(以下所指的程序行数来源于源工程中的代码,各位读者可打开文章开头我提供的开源地址来进行源码的查看)

在程序的第 69 行,通过 mdio_dirMDIO 引脚方向选择)信号控制 eth_mdio 引脚的方向,当设置成输入时,FPGA 将该引脚输出高阻(1’bz);当设置成输出时,将 FPGA 驱动的 mdio_out 信号连接至 eth_mdio

由于 eth_mdc 需要在输入时钟的基础上进行分频,为了方便操作,这里先对输入的时钟进行分频,得到一个 dri_clk 时钟,作为 MDIO 驱动模块和 MDIO 控制模块的操作时钟。eth_mdcdri_clk 的基础上进行 2 分频,由于输入的参数 CLK_DIVeth_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 寄存器的仿真波形图如下图所示:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

由上图可知,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 中不要选错了,本文以正点原子的开发板为例来讲解

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

点击 PDS 工具栏的下载按钮,在弹出的 Fabric Configuration 界面中双击 “Boundary Scan”, 我们将生成好的 .sbit 流文件下载到开发板中去。

程序下载完成后, 等待几秒即可看到开发板 LED 灯的点亮状态,如下图所示:

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

如上图可知,两个 LED 灯都处于点亮状态,因此通信速率为 1000Mbps。如果按下复位按键可以对 PHY 进行硬件复位,按下 TPAD 触摸按键会对 PHY 进行软复位,此时 PHY 芯片会重新开始与另一端设备进行自协商,等待几秒后, LED 灯重新点亮。

另外,如果开发板另一端连接的是电脑的网口,此时可以查看自协商后的通信速率。查看方法是点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(不同的 Windows 版本操作可能存在差异,但基本相同)。

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

点击上图中的 “未识别的网络(无 Internet)”,弹出如下图所示界面。

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

点击 “更改适配器” 选项,弹出如下图所示界面。

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

如果看到上图 “以太网” 显示未识别的网络之后,说明硬件连接是没有问题的,接下来鼠标右击以太网,选择 “状态”,如下面两张图片所示。

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

eth 测试 mdio 接口,紫光同创FPGA开发笔记,fpga开发,网络

由上图可知,开发板和电脑自协商的通信速率为 1Gbps1000Mbps)。



5 总结

以上是 FPGA 以太网系列的第一篇,未来会继续更新以太网系列,感兴趣的朋友可以关注本专栏(免费),持续学习,共同进步!

希望以上的内容对您有所帮助,诚挚地欢迎各位读者在评论区或者私信我交流!

微博:沂舟Ryan (@沂舟Ryan 的个人主页 - 微博 )

GitHub:ChinaRyan666

微信公众号:沂舟无限进步(内含精品资料及详细教程)

如果对您有帮助的话请点赞支持下吧!

集中一点,登峰造极。文章来源地址https://www.toymoban.com/news/detail-852574.html

到了这里,关于FPGA以太网入门(一)——MDIO接口读写测试实验(基于紫光同创)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包