最近迷上了FPGA的网络通信和GTP光通信,个人感觉光通信简单一些,那就从难得网络通信开始吧,先搞个最简单的,使用MDIO配置和读取网络PHY的信息。
板子:米联客的MA703FA(A7-35T板子);
参考例程:正点原子达芬奇开发板例程;
IDE:vivado2020.2;
具体的原理啥的建议去看正点原子的文档吧,讲得很好,但原子的例程感觉不贴近实际项目,所以我改了一下,使之适合真是项目。
先来看看这块芯片RTL8211FD的数据手册。

这是官方给的应用架构,很简单,RTL8211FD与MAC通信,通过MDC和MDIO配置。

芯片BD,没啥好说的,典型的rgmii接口,内部模块电路感觉没必要深究,反正也不懂,能用就行了。

硬件复位这里要注意,官方说应至少保持10ms低电平,最好还是按官方说的做,菜就要多听话。

芯片地址这里注意了,RTL8211FD器件地址由5位构成,高两位固定为2’b00,第三位后这三个引脚的上下拉电平决定,所以看看米联客板子的原理图:

由此可得:此RTL8211FD器件地址为:5’b00001;
这块芯片是不需要配置也能使用的,参考官方给的寄存器默认值可知,默认状态下芯片开启自协商,速率1000M,这就行了,已经不需要配置了,但为了学习,我们可以读状态寄存器,从而获得连接状态和通信速率。需要读两个状态寄存器:

第一是这个,需要读他的第2和第5位,


第2位:芯片和MAC连接成功;
第5位:芯片和MAC自协商成功;

还有这个寄存器,主要读芯片的通信速率;

第4到5位是通信速率;
好了,在看代码:
MDIO控制器直接用原子的,写法和他们家的iic控制器类似,够骚,也够繁琐,但能用,菜就别讲究了吧。

//****************************************************************************************//module mdio_dri #(parameter  PHY_ADDR = 5'b00001,//PHY地址parameter  CLK_DIV  = 6'd10    //分频系数)(input                clk         , //时钟信号input                rst_n       , //复位信号,低电平有效input                i_op_exec   , //触发开始信号input                i_op_rh_wl  , //低电平写,高电平读input        [4:0]   i_op_addr   , //寄存器地址input        [15:0]  i_op_wr_data, //写入寄存器的数据output  reg          o_op_done   , //读写完成output  reg  [15:0]  o_op_rd_data, //读出的数据output  reg          o_op_rd_ack , //读应答信号 0:应答 1:未应答output  reg          o_dri_clk   , //驱动时钟    output  reg          o_eth_mdc   , //PHY管理接口的时钟信号inout                i_eth_mdio    //PHY管理接口的双向数据信号);//parameter define
localparam st_idle    = 6'b00_0001;  //空闲状态
localparam st_pre     = 6'b00_0010;  //发送PRE(前导码)
localparam st_start   = 6'b00_0100;  //开始状态,发送ST(开始)+OP(操作码)
localparam st_addr    = 6'b00_1000;  //写地址,发送PHY地址+寄存器地址
localparam st_wr_data = 6'b01_0000;  //TA+写数据
localparam st_rd_data = 6'b10_0000;  //TA+读数据//reg define
reg    [5:0]  cur_state ;
reg    [5:0]  next_state;reg    [5:0]  clk_cnt   ;  //分频计数
reg   [15:0]  wr_data_t ;  //缓存写寄存器的数据
reg    [4:0]  addr_t    ;  //缓存寄存器地址
reg    [6:0]  cnt       ;  //计数器
reg           st_done   ;  //状态开始跳转信号
reg    [1:0]  op_code   ;  //操作码  2'b01(写)  2'b10(读)
reg           mdio_dir  ;  //MDIO数据(SDA)方向控制
reg           mdio_out  ;  //MDIO输出信号
reg   [15:0]  rd_data_t ;  //缓存读寄存器数据//wire define
wire          mdio_in    ; //MDIO数据输入
wire   [5:0]  clk_divide ; //PHY_CLK的分频系数assign i_eth_mdio = mdio_dir ? mdio_out : 1'bz; //控制双向io方向
assign mdio_in = i_eth_mdio;                    //MDIO数据输入
//将PHY_CLK的分频系数除以2,得到dri_clk的分频系数,方便对MDC和MDIO信号操作
assign clk_divide = CLK_DIV >> 1;//分频得到dri_clk时钟
always @(posedge clk) beginif(!rst_n) begino_dri_clk <=  1'b0;clk_cnt <= 1'b0;endelse if(clk_cnt == clk_divide[5:1] - 1'd1) beginclk_cnt <= 1'b0;o_dri_clk <= ~o_dri_clk;endelse clk_cnt <= clk_cnt + 1'b1;
end//产生PHY_MDC时钟
always @(posedge o_dri_clk) beginif(!rst_n) o_eth_mdc <= 1'b1;else if(cnt[0] == 1'b0) o_eth_mdc <= 1'b1;else o_eth_mdc <= 1'b0;
end//(三段式状态机)同步时序描述状态转移
always @(posedge o_dri_clk) beginif(!rst_n) cur_state <= st_idle;else cur_state <= next_state;
end  //组合逻辑判断状态转移条件
always @(*) beginnext_state = st_idle;case(cur_state)st_idle : beginif(i_op_exec) next_state = st_pre;else next_state = st_idle;   end  st_pre : begin  //发送前导码,32'hffffffffif(st_done) next_state = st_start;else next_state = st_pre;endst_start : begin   //ST+OP=2'b01+2'bxxif(st_done) next_state = st_addr;else next_state = st_start;endst_addr : begin    //PHYAD+REGADif(st_done) beginif(op_code == 2'b01) next_state = st_wr_data;    //MDIO接口写操作                     else next_state = st_rd_data; //MDIO接口读操作                            endelse next_state = st_addr;               endst_wr_data : beginif(st_done) next_state = st_idle;else next_state = st_wr_data;end        st_rd_data : beginif(st_done) next_state = st_idle;else next_state = st_rd_data;end                                                                          default : next_state = st_idle;endcaseend//时序电路描述状态输出
always @(posedge o_dri_clk) beginif(!rst_n) begincnt <= 5'd0;op_code <= 1'b0;addr_t <= 1'b0;wr_data_t <= 1'b0;rd_data_t <= 1'b0;o_op_done <= 1'b0;st_done <= 1'b0; o_op_rd_data <= 1'b0;o_op_rd_ack <= 1'b1;mdio_dir <= 1'b0;mdio_out <= 1'b1;endelse beginst_done <= 1'b0 ;                            cnt     <= cnt +1'b1 ;          case(cur_state)st_idle : beginmdio_out <= 1'b1;                     mdio_dir <= 1'b0;                     o_op_done <= 1'b0;                     cnt <= 7'b0;  if(i_op_exec) beginop_code <= {i_op_rh_wl,~i_op_rh_wl}; //OP_CODE: 2'b01(写)  2'b10(读) addr_t <= i_op_addr;wr_data_t <= i_op_wr_data;o_op_rd_ack <= 1'b1;end     end st_pre : begin                          //发送前导码:32个1bit mdio_dir <= 1'b1;                   //切换MDIO引脚方向:输出mdio_out <= 1'b1;                   //MDIO引脚输出高电平if(cnt == 7'd62) st_done <= 1'b1;else if(cnt == 7'd63) cnt <= 7'b0;end            st_start  : begincase(cnt)7'd1 : mdio_out <= 1'b0;        //发送开始信号 2'b017'd3 : mdio_out <= 1'b1; 7'd5 : mdio_out <= op_code[1];  //发送操作码7'd6 : st_done <= 1'b1;7'd7 : beginmdio_out <= op_code[0];cnt <= 7'b0;  end    default : ;endcaseend    st_addr : begincase(cnt)7'd1 : mdio_out <= PHY_ADDR[4]; //发送PHY地址7'd3 : mdio_out <= PHY_ADDR[3];7'd5 : mdio_out <= PHY_ADDR[2];7'd7 : mdio_out <= PHY_ADDR[1];  7'd9 : mdio_out <= PHY_ADDR[0];7'd11: mdio_out <= addr_t[4];  //发送寄存器地址7'd13: mdio_out <= addr_t[3];7'd15: mdio_out <= addr_t[2];7'd17: mdio_out <= addr_t[1];  7'd18: st_done <= 1'b1;7'd19: beginmdio_out <= addr_t[0]; cnt <= 7'd0;end    default : ;endcase                end    st_wr_data : begincase(cnt)7'd1 : mdio_out <= 1'b1;         //发送TA,写操作(2'b10)7'd3 : mdio_out <= 1'b0;7'd5 : mdio_out <= wr_data_t[15];//发送写寄存器数据7'd7 : mdio_out <= wr_data_t[14];7'd9 : mdio_out <= wr_data_t[13];7'd11: mdio_out <= wr_data_t[12];7'd13: mdio_out <= wr_data_t[11];7'd15: mdio_out <= wr_data_t[10];7'd17: mdio_out <= wr_data_t[9];7'd19: mdio_out <= wr_data_t[8];7'd21: mdio_out <= wr_data_t[7];7'd23: mdio_out <= wr_data_t[6];7'd25: mdio_out <= wr_data_t[5];7'd27: mdio_out <= wr_data_t[4];7'd29: mdio_out <= wr_data_t[3];7'd31: mdio_out <= wr_data_t[2];7'd33: mdio_out <= wr_data_t[1];7'd35: mdio_out <= wr_data_t[0];7'd37: beginmdio_dir <= 1'b0;mdio_out <= 1'b1;end7'd39: st_done <= 1'b1;           7'd40: begincnt <= 7'b0;o_op_done <= 1'b1;      //写操作完成,拉高op_done信号 end    default : ;endcase    endst_rd_data : begincase(cnt)7'd1 : beginmdio_dir <= 1'b0;            //MDIO引脚切换至输入状态mdio_out <= 1'b1;end7'd2 : ;                         //TA[1]位,该位为高阻状态,不操作             7'd4 : o_op_rd_ack <= mdio_in;     //TA[0]位,0(应答) 1(未应答)7'd6 : rd_data_t[15] <= mdio_in; //接收寄存器数据7'd8 : rd_data_t[14] <= mdio_in;7'd10: rd_data_t[13] <= mdio_in;7'd12: rd_data_t[12] <= mdio_in;7'd14: rd_data_t[11] <= mdio_in;7'd16: rd_data_t[10] <= mdio_in;7'd18: rd_data_t[9] <= mdio_in;7'd20: rd_data_t[8] <= mdio_in;7'd22: rd_data_t[7] <= mdio_in;7'd24: rd_data_t[6] <= mdio_in;7'd26: rd_data_t[5] <= mdio_in;7'd28: rd_data_t[4] <= mdio_in;7'd30: rd_data_t[3] <= mdio_in;7'd32: rd_data_t[2] <= mdio_in;7'd34: rd_data_t[1] <= mdio_in;7'd36: rd_data_t[0] <= mdio_in;7'd39: st_done <= 1'b1;7'd40: begino_op_done <= 1'b1;             //读操作完成,拉高op_done信号          o_op_rd_data <= rd_data_t;rd_data_t <= 16'd0;cnt <= 7'd0;enddefault : ;endcase   end                default : ;endcase               end
end                    endmodule

然后是控制逻辑,原子的例程是通过触摸按键软复位RTL8211FD,然后再读那两个寄存器,感觉没啥用,自己重写了一个,功能是硬件复位完成后直接循环读那两个寄存器,如果自协商完成且连接成功,led亮,否则灭,再用另个led来只是当前的通信速率,直接上代码:

//****************************************************************************************//
module mdio_ctrl(
(* mark_debug ="true" *)    input                clk           ,
(* mark_debug ="true" *)    input                rst_n         ,(* mark_debug ="true" *)    input                i_op_done     , //读写完成
(* mark_debug ="true" *)    input        [15:0]  i_op_rd_data  , //读出的数据
(* mark_debug ="true" *)    input                i_op_rd_ack   , //读应答信号 0:应答 1:未应答
(* mark_debug ="true" *)    output  reg          o_op_exec     , //触发开始信号
(* mark_debug ="true" *)    output  reg          o_op_rh_wl    , //低电平写,高电平读
(* mark_debug ="true" *)    output  reg  [4:0]   o_op_addr     , //寄存器地址(* mark_debug ="true" *)   input                i_eth_rst_n   ,
(* mark_debug ="true" *)   output  reg          o_link_led    ,
(* mark_debug ="true" *)   output  reg  [1:0]   o_speed_led );(* mark_debug ="true" *) reg  [2:0]   flow_cnt;        //流程控制计数器 always @(posedge clk) beginif(!rst_n) beginflow_cnt <= 3'd0;
//      link_error <= 1'b0;o_op_exec <= 1'b0; o_op_rh_wl <= 1'b0; o_op_addr <= 1'b0;
//      op_wr_data <= 1'b0;o_link_led <= 1'b0;o_speed_led<= 2'b0;endelse begincase(flow_cnt)3'd0:beginif(i_eth_rst_n) flow_cnt <= 3'd1;    //reste_n is okend3'd1: begino_op_exec <= 1'b1;   //开始操作MDIOo_op_rh_wl<= 1'b1;   //读操作o_op_addr <= 5'h01;   //读BMSR状态寄存器flow_cnt<= 3'd2;end3'd2: beginif(i_op_done) begin //读操作完成if(!i_op_rd_ack) flow_cnt<= 3'd3; //phy应答,做后续判断else flow_cnt<= 3'd1;           //phy未应答,重新发起读BMSR状态寄存器             endend3'd3: beginif(i_op_rd_data[5] == 1'b1 && i_op_rd_data[2] == 1'b1) begino_link_led<=1'b1;  //自协商完成,link完成flow_cnt<=3'd4;endelse o_link_led<=1'b0;            end3'd4: begin     //读PHYSR特定状态寄存器o_op_addr <= 5'h1A;flow_cnt<= 3'd5;            end3'd5: beginif(i_op_done) begin  //读操作完成if(!i_op_rd_ack) flow_cnt<= 3'd6; //phy应答,做后续判断else flow_cnt<= 3'd4;           //phy未应答,重新发起读PHYSR特定状态寄存器endend                        3'd6: beginflow_cnt<=3'd1;if(i_op_rd_data[5:4] == 2'b10) o_speed_led<=2'b11;      //1000Mbpselse if(i_op_rd_data[5:4] == 2'b01) o_speed_led<=2'b01;   //100Mbpselse if(i_op_rd_data[5:4] == 2'b00) o_speed_led<=2'b10;    //100Mbpselse o_speed_led<= 2'b0;                                 enddefault: flow_cnt <= 3'd0;endcaseend
endendmodule

最后是顶层,这里加了硬件复位的10ms,之前说了,菜就别嫌麻烦,跟官方走:

module mdio_rw_test(input          sys_clk    ,output         eth_mdc    , //PHY管理接口的时钟信号inout          eth_mdio   , //PHY管理接口的双向数据信号output         eth_rst_n  , //以太网复位信号output         o_link_led ,output  [1:0]  o_speed_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    ;  //驱动时钟
wire rsn_n;
wire clk_50m;clk_wiz_0 u_clk_wiz_0(// Clock out ports.clk_out1(clk_50m),     // output clk_out1// Status and control signals.locked(rsn_n),       // output locked// Clock in ports.clk_in1(sys_clk));      // input clk_in1localparam T_10MS = 10_000_000;    //10ms=10_000_000ns
localparam T_P    = 20        ; //50M周期=20ns
localparam RST_CNT= T_10MS/T_P+10;
reg  [18:0]  eth_rsrn_cnt;
reg o_eth_rst_n;
//根据数据手册,硬件复位至少10ms的低电平
always @(posedge clk_50m) beginif(!rsn_n) eth_rsrn_cnt<='d0;else if(eth_rsrn_cnt==RST_CNT) eth_rsrn_cnt<=RST_CNT;else eth_rsrn_cnt<=eth_rsrn_cnt+19'd1;
endalways @(posedge clk_50m) beginif(!rsn_n) o_eth_rst_n<='d0;else if(eth_rsrn_cnt==RST_CNT) o_eth_rst_n<=1'd1;else o_eth_rst_n<=1'd0;
end//硬件复位
assign eth_rst_n = o_eth_rst_n;//MDIO接口驱动
mdio_dri #(.PHY_ADDR    (5'h01),   //PHY地址.CLK_DIV     (6'd10)    //分频系数)u_mdio_dri(.clk         (clk_50m   ),.rst_n       (rsn_n     ),.i_op_exec   (op_exec   ),.i_op_rh_wl  (op_rh_wl  ),   .i_op_addr   (op_addr   ),   .i_op_wr_data(op_wr_data),   .o_op_done   (op_done   ),   .o_op_rd_data(op_rd_data),   .o_op_rd_ack (op_rd_ack ),   .o_dri_clk   (dri_clk   ),                   .o_eth_mdc   (eth_mdc   ),   .i_eth_mdio  (eth_mdio  )
);      mdio_ctrl u_mdio_ctrl(.clk           (dri_clk    ),.rst_n         (rsn_n      ),.i_op_done     (op_done    ), //读写完成.i_op_rd_data  (op_rd_data ), //读出的数据.i_op_rd_ack   (op_rd_ack  ), //读应答信号 0:应答 1:未应答.o_op_exec     (op_exec    ), //触发开始信号.o_op_rh_wl    (op_rh_wl   ), //低电平写,高电平读.o_op_addr     (op_addr    ), //寄存器地址.i_eth_rst_n   (o_eth_rst_n),.o_link_led    (o_link_led ),.o_speed_led   (o_speed_led));
endmodule

结果:灯亮,成功

浅谈FPGA网络PHY芯片RTL8211FD的配置和简单使用相关推荐

  1. FPGA基于SFP光口实现1G千兆网UDP通信 1G/2.5G Ethernet PCS/PMA or SGMII替代网络PHY芯片 提供工程源码和技术支持

    目录 1.前言 2.我这里已有的UDP方案 3.详细设计方案 4.vivado工程详解 5.上板调试验证并演示 6.福利:工程代码的获取 1.前言 目前网上的fpga实现udp基本生态如下: 1:ve ...

  2. FPGA基于SFP光口实现10G万兆网UDP通信 10G Ethernet Subsystem替代网络PHY芯片 提供工程源码和技术支持

    目录 1.前言 2.我这里已有的UDP方案 3.详细设计方案 4.vivado工程详解 5.上板调试验证并演示 6.福利:工程代码的获取 1.前言 目前网上的fpga实现udp基本生态如下: 1:ve ...

  3. 千兆网络PHY芯片 RTL8211E的实践应用(自我总结篇)

    这篇算是对FPGA做千兆以太网的电路搭建这一块儿的最全的扫盲篇了吧,废话不多说,直接上干货. 一.千兆以太网的系统搭建 1.硬件系统搭建 以太网MAC模块负责实现以太网MAC子层的功能,完成802.3 ...

  4. 浅谈软件定义网络(SDN)技术研究现状和发展趋势

                       浅谈软件定义网络(SDN)技术研究现状和发展趋势 友情全文PDF链接:浅谈软件定义网络(SDN)技术研究现状和发展趋势.pdf-网络基础文档类资源-CSDN下载 ...

  5. 浅谈Java网络编程之Socket (2)

    <浅谈Java网络编程之Socket (1)>中我们已经和大家说到客户端的网络编程,下面和大家分享的是服务器的实现代码. import java.net.*; import java.io ...

  6. 浅谈Openstack网络原理(openstack无法上网?)

    浅谈Openstack网络 本版本为Stein版本,采用OVS网络 测试环境:1控制节点,2个计算节点 目录 浅谈Openstack网络 一.网络结构图 二.OVS分析 三.DHCP服务 四.Rout ...

  7. 一文读懂如何使用FPGA驱动PHY芯片

    这里写自定义目录标题 如何使用FPGA驱动PHY芯片 前言 必要的硬件知识 如何确定PHY芯片的物理地址? 如何确定PHY芯片的工作模式? 如何驱动PHY芯片? MDIO的通讯协议是什么? PHY芯片 ...

  8. 浅谈软件定义网络SDN

    浅谈软件定义网络SDN 前言 学习主要内容 一.SDN简介 二.SDN的三个主要特征 转控分离 集中控制 开放接口 三.SDN的工作原理 SDN网络架构的三层模型 SDN网络架构下的三个接口 SDN基 ...

  9. 校园服务器系统的意义,浅谈校园网络建设的意义与作用.doc

    PAGE PAGE 7 浅谈校园网络建设的意义和作用 摘要:本文围绕校园网建设的意义和作用.结合本校校园网建设的基本经验,运用大量具体实例阐述大力建设校园网络的必要性和可行性,供大家借鉴. 关键字:校 ...

最新文章

  1. 计算机硬盘登记表,硬盘固件的 P 表与 G 表
  2. activex for chrome扩展程序 下载”_chrome系列-扩展程序开发学习-从无到有
  3. 欢迎光临CAX软件二次开发开源社区!
  4. matlab pca降维_手撸PCA(Python七行代码实现)
  5. CSS3 多列布局的column-gap 和 column-rule属性
  6. [素数拓展] 质因数的个数 [2007年清华大学计算机研究生机试真题]
  7. Android ViewPropertyAnimator:让动画变得简单起来!
  8. cocoapods 总结
  9. php format tool,usb 開機碟製作工具HP USB Disk Storage format Tool 2.23
  10. wordpress网站提示“建立数据库连接时出错”
  11. java-房屋出租系统
  12. 网站被封申诉通道方式方法
  13. SCUT校赛130:对抗女巫的魔法碎片(思维)
  14. HDU1757(矩阵快速幂+简单的矩阵构造)
  15. UG 信息窗口弹不出来 测量 长度 角度 信息 窗口 弹不出来
  16. 何水无鱼?何山无石?何人无父?何女无夫?何树无枝?何城无市?
  17. 小程序前端获取手机号码
  18. 严格模式和普通模式的区别
  19. cf Round#779 D 388535
  20. elementui下拉选择框

热门文章

  1. 这些Android内存管理知识你都知道吗
  2. 传智博客第一天——传智播客的老毕
  3. Vmware 官网不登录下载vmware workstation pro
  4. 看准再下手 6000以下最值得购买8款本
  5. VC编程实现位图图像二值化、反相
  6. 2022初级前端必会面试题持续更新
  7. 关于android自带浏览器无法下载站点apk最终解决方案
  8. emeditor 快捷键
  9. 命令提示符的打开以及简单的Dos命令
  10. ubuntu20.04不能播放bilibili网页视频解决方法