PHY芯片通过MDIO接口进行读写,框图如下所示:
原理很简单,就是按照时序将PHY芯片的指定寄存器信息读出或者写入。
MDC时钟需要输出到PHY芯片,一般不低于80MHz。
MDIO是双向接口,FPGA读出状态信息时为输入,FPGA写入控制信息时为输出。
基本时序如下:(来源正点原子)
编码:(使用状态机实现对PHY芯片0x11地址的特殊寄存器的状态读操作)
module mdio_interface ( input wire clk, // FPGA涓绘椂閽? input wire reset_n, // 澶嶄綅淇″彿锛屼綆鏈夋晥 output reg mdc, // MDC鏃堕挓 涓嶈秴杩?12.5MHz 80ns inout mdio // MDIO鍙屽悜鏁版嵁绾? ); reg [2:0] clk_div; always @ (posedge clk or negedge reset_n) begin if (!reset_n) begin clk_div <= 3'd0; end else if (clk_div == 3'd3) begin clk_div <= 3'd0; end else begin clk_div <= clk_div + 1; end end always @ (posedge clk or negedge reset_n) begin if (!reset_n) begin mdc <= 0; end else if (clk_div == 3'd2) begin //120ns鍛ㄦ湡 mdc <= ~mdc; end else begin mdc <= mdc; end end reg mdio_dir; //0璇?1鍐? reg mdio_out; // assign mdio = mdio_dir ? mdio_out : 1'bz; parameter IDLE = 8'b00000001; parameter PREAMBLE = 8'b00000010; //鍙戦??32浣?1 鍓嶅鐮? parameter START = 8'b00000100; parameter OPCODE = 8'b00001000; parameter PHYADDR = 8'b00010000; parameter REGADDR = 8'b00100000; parameter TURNDIR = 8'b01000000; parameter READ = 8'b10000000; reg [7:0] state,next_state; reg [7:0] cnt; always @ (posedge mdc or negedge reset_n) begin if (!reset_n) begin state <= IDLE; end else begin state <= next_state; end end initial begin next_state = IDLE; end always @ (*) begin case (state) IDLE: next_state = PREAMBLE; PREAMBLE: if (cnt == 8'd32) begin next_state = START; end else begin next_state = PREAMBLE; end START: if (cnt == 8'd34) begin next_state = OPCODE; end else begin next_state = START; end OPCODE: if (cnt == 8'd36) begin next_state = PHYADDR; end else begin next_state = OPCODE; end PHYADDR: if (cnt == 8'd41) begin next_state = REGADDR; end else begin next_state = PHYADDR; end REGADDR: if (cnt == 8'd46) begin next_state = TURNDIR; end else begin next_state = REGADDR; end TURNDIR: if (cnt == 8'd48) begin next_state = READ; end else begin next_state = TURNDIR; end READ: if (cnt == 8'd65) begin next_state = IDLE; end else begin next_state = READ; end endcase end reg [15:0] data_out; always @ (posedge mdc or negedge reset_n) begin if (!reset_n) begin cnt <= 8'd0; mdio_dir <= 1'd0; mdio_out <= 1'd0; data_out <= 16'd0; end else begin case(state) IDLE: begin cnt <= 8'd0; mdio_dir <= 1'd0; mdio_out <= 1'd0; data_out <= 16'd0; end PREAMBLE: begin cnt <= cnt + 1; mdio_dir <= 1'd1; //鍙戦?佸墠瀵肩爜锛屽啓32浣?1 mdio_out <= 1'd1; end START: begin cnt <= cnt + 1; mdio_out <= (cnt == 8'd33 ? 0 : 1); //鍥哄畾01 end OPCODE: begin cnt <= cnt + 1; mdio_out <= (cnt == 8'd35 ? 1 : 0); //璇?10 鍐?01 end PHYADDR: begin cnt <= cnt + 1; case (cnt) 8'd37: mdio_out <= 0; 8'd38: mdio_out <= 0; 8'd39: mdio_out <= 1; 8'd40: mdio_out <= 0; 8'd41: mdio_out <= 0; endcase end REGADDR: begin cnt <= cnt + 1; case (cnt) 8'd42: mdio_out <= 1; //10001瀵勫瓨鍣? 8'd43: mdio_out <= 0; 8'd44: mdio_out <= 0; 8'd45: mdio_out <= 0; 8'd46: mdio_out <= 1; endcase end TURNDIR: begin //璇籞0 鍐?10 cnt <= cnt + 1; mdio_out <= (cnt == 8'd47 ? 1'bz : 0); end READ: begin mdio_dir <= 1'd0; cnt <= cnt + 1; data_out <= {data_out[14:0],mdio}; end endcase end end ila_1 ila1 ( .clk(clk), // input wire clk .probe0(state), // input wire [7:0] probe0 .probe1(cnt), // input wire [7:0] probe1 .probe2(next_state), // input wire [7:0] probe2 .probe3(data_out), .probe4(mdio), // input wire [0:0] probe4 .probe5(mdio_out) // input wire [0:0] probe5 ); endmodule
仿真截图:
可以看出,前32位前导码均为1,在cnt==33时,下两个时钟周期为01即start信号,在cnt==35时,下两个周期即OPCODE信号为10,即读操作,PHY物理地址为00100,这和芯片设计有关,寄存器地址是10001,选取的是特殊寄存器地址,读出的数据是0xbc40。
该寄存器的高四位为1011,高两位对应10即1000Mbps,验证:
链路速率确实为1Gbps,包括
链路实时状态位为1,连接状态。没问题。
代码只实现了读操作,写操作是一样的,有待读者自己完善。
接下来要实现ARP、UDP等通信协议。