最近迷上了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) begin if(!rst_n) begin o_dri_clk <= 1
'b0; clk_cnt <= 1'b0; end else if(clk_cnt == clk_divide[5:1] - 1'd1) begin
clk_cnt<= 1'b0; o_dri_clk <= ~o_dri_clk; end else clk_cnt <= clk_cnt + 1'b1; end
//产生PHY_MDC时钟 always @(posedge o_dri_clk) begin if(!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) begin if(!rst_n) cur_state <=
st_idle; else cur_state <= next_state; end //组合逻辑判断状态转移条件 always @(*) begin
next_state= st_idle; case(cur_state) st_idle : begin if(i_op_exec) next_state =
st_pre; else next_state = st_idle; end st_pre : begin //发送前导码,32'hffffffff if(
st_done) next_state = st_start; else next_state = st_pre; end st_start : begin
//ST+OP=2'b01+2'bxx if(st_done) next_state = st_addr; else next_state = st_start
; end st_addr : begin //PHYAD+REGAD if(st_done) begin if(op_code == 2'b01)
next_state= st_wr_data; //MDIO接口写操作 else next_state = st_rd_data; //MDIO接口读操作
endelse next_state = st_addr; end st_wr_data : begin if(st_done) next_state =
st_idle; else next_state = st_wr_data; end st_rd_data : begin if(st_done)
next_state= st_idle; else next_state = st_rd_data; end default : next_state =
st_idle; endcase end //时序电路描述状态输出 always @(posedge o_dri_clk) begin if(!rst_n)
begin cnt<= 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; end else begin st_done <= 1'b0 ;
cnt<= cnt +1'b1 ; case(cur_state) st_idle : begin mdio_out <= 1'b1; mdio_dir <=
1'b0; o_op_done <= 1'b0; cnt <= 7'b0; if(i_op_exec) begin op_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 : begin
case(cnt) 7'd1 : mdio_out <= 1'b0; //发送开始信号 2'b01 7'd3 : mdio_out <= 1'b1; 7'd5
: mdio_out <= op_code[1]; //发送操作码 7'd6 : st_done <= 1'b1; 7'd7 : begin mdio_out
<= op_code[0]; cnt <= 7'b0; end default : ; endcase end st_addr : begin case(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: begin mdio_out <= addr_t[0]; cnt <= 7'd0; end default : ;
endcase end st_wr_data: begin case(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: begin mdio_dir <= 1'b0;
mdio_out<= 1'b1; end 7'd39: st_done <= 1'b1; 7'd40: begin cnt <= 7'b0; o_op_done
<= 1'b1; //写操作完成,拉高op_done信号 end default : ; endcase end st_rd_data : begin
case(cnt) 7'd1 : begin mdio_dir <= 1'b0; //MDIO引脚切换至输入状态 mdio_out <= 1'b1; end 7
'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: begin o_op_done <= 1'b1; //读操作完成,拉高op_done信号 o_op_rd_data <=
rd_data_t; rd_data_t <= 16'd0; cnt <= 7'd0; end default : ; endcase end default
: ; endcase end end endmodule

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

//****************************************************************************************//
modulemdio_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) begin if(!rst_n) begin
flow_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 begin case(flow_cnt) 3'd0:begin if(i_eth_rst_n) flow_cnt <= 3'd1;
//reste_n is ok end 3'd1: begin o_op_exec <= 1'b1; //开始操作MDIO o_op_rh_wl<= 1'b1;
//读操作 o_op_addr <= 5'h01; //读BMSR状态寄存器 flow_cnt<= 3'd2; end 3'd2: begin if(
i_op_done) begin //读操作完成 if(!i_op_rd_ack) flow_cnt<= 3'd3; //phy应答,做后续判断 else
flow_cnt<= 3'd1; //phy未应答,重新发起读BMSR状态寄存器 end end 3'd3: begin if(i_op_rd_data[5]
== 1'b1 && i_op_rd_data[2] == 1'b1) begin o_link_led<=1'b1; //自协商完成,link完成
flow_cnt<=3'd4; end else o_link_led<=1'b0; end 3'd4: begin //读PHYSR特定状态寄存器
o_op_addr<= 5'h1A; flow_cnt<= 3'd5; end 3'd5: begin if(i_op_done) begin //读操作完成
if(!i_op_rd_ack) flow_cnt<= 3'd6; //phy应答,做后续判断 else flow_cnt<= 3'd4;
//phy未应答,重新发起读PHYSR特定状态寄存器 end end 3'd6: begin flow_cnt<=3'd1; if(i_op_rd_data[5
:4] == 2'b10) o_speed_led<=2'b11; //1000Mbps else if(i_op_rd_data[5:4] == 2
'b01) o_speed_led<=2'b01; //100Mbps else if(i_op_rd_data[5:4] == 2'b00)
o_speed_led<=2'b10; //100Mbps else o_speed_led<= 2'b0; end default: flow_cnt <=
3'd0; endcase end end endmodule
最后是顶层,这里加了硬件复位的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_in1 localparam 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) begin if(!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; end always @(posedge clk_50m) begin if(!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
结果:灯亮,成功

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信