3. TSN交换机端口PHY接口配置与链路检测
任务说明
本案例在FPGA侧实现TSN交换机端口与外部PHY芯片之间的RGMII接口配置,并完成链路状态检测、自协商使能及速率适配。核心工作包括:MDIO管理接口读写PHY寄存器、RGMII数据路径的DDR收发、链路状态轮询与速率切换。
硬件与依赖
- FPGA开发板(如Xilinx Artix-7/Kintex-7系列),外部PHY芯片(如Marvell 88E1512或TI DP83867)
- PHY与FPGA之间连接:RGMII(TXC/RXC、TXD[3:0]/RXD[3:0]、TX_CTL/RX_CTL)+ MDC/MDIO
- 参考时钟:25MHz晶振输入PHY,FPGA提供125MHz(1000M)或25MHz(100M)RGMII时钟
- 开发工具:Vivado 2020.2及以上
整体结构
关键实现
以下代码实现MDIO读写状态机与链路检测逻辑。MDIO时钟分频至2.5MHz,PHY地址为5'h01。通过读取PHY寄存器1(BMSR)的bit2判断链路状态,bit5判断自协商完成;读取寄存器0(BMCR)的bit13/bit6获取速率信息。
module phy_ctrl #(
parameter CLK_DIV = 50 // 125MHz / 50 = 2.5MHz MDC
)(
input wire clk,
input wire rst_n,
// MDIO接口
output reg mdc,
output reg mdio_oe,
output reg mdio_o,
input wire mdio_i,
// 链路状态输出
output reg [1:0] link_speed, // 00:10M 01:100M 10:1000M
output reg link_up,
output reg an_done
);
localparam PHY_ADDR = 5'h01;
localparam REG_BMCR = 5'h00;
localparam REG_BMSR = 5'h01;
// 时钟分频
reg [5:0] div_cnt;
reg mdc_rise, mdc_fall;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
div_cnt <= 0; mdc <= 0;
mdc_rise <= 0; mdc_fall <= 0;
end else begin
mdc_rise <= 0; mdc_fall <= 0;
if (div_cnt == CLK_DIV-1) begin
div_cnt <= 0;
mdc <= ~mdc;
if (~mdc) mdc_rise <= 1;
else mdc_fall <= 1;
end else
div_cnt <= div_cnt + 1;
end
end
// MDIO状态机
localparam S_IDLE = 3'd0, S_ST = 3'd1, S_OP = 3'd2,
S_ADDR = 3'd3, S_REG = 3'd4, S_TA = 3'd5,
S_DATA = 3'd6, S_WAIT = 3'd7;
reg [2:0] state;
reg [4:0] bit_cnt;
reg [15:0] rd_data;
reg [15:0] wr_data;
reg [4:0] reg_addr;
reg is_read;
reg start;
reg done;
// 轮询控制
reg [19:0] poll_timer;
reg poll_en;
reg [1:0] poll_step;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE; bit_cnt <= 0; mdio_oe <= 0;
mdio_o <= 1; done <= 0; start <= 0;
link_up <= 0; an_done <= 0; link_speed <= 2'b10;
poll_timer <= 0; poll_en <= 1; poll_step <= 0;
end else begin
done <= 0;
case (state)
S_IDLE: begin
mdio_oe <= 0; mdio_o <= 1;
if (start) begin
bit_cnt <= 0; state <= S_ST;
end
end
S_ST: begin // 32'b01 起始码
mdio_oe <= 1;
if (mdc_fall) begin
mdio_o <= (bit_cnt == 0) ? 1'b0 : 1'b1;
if (bit_cnt == 1) state <= S_OP;
bit_cnt <= bit_cnt + 1;
end
end
S_OP: begin // 读:10 写:01
if (mdc_fall) begin
mdio_o <= is_read ? (bit_cnt==0 ? 1'b1 : 1'b0)
: (bit_cnt==0 ? 1'b0 : 1'b1);
if (bit_cnt == 1) begin state <= S_ADDR; bit_cnt <= 0; end
else bit_cnt <= bit_cnt + 1;
end
end
S_ADDR: begin // PHY地址5bit
if (mdc_fall) begin
mdio_o <= PHY_ADDR[4-bit_cnt];
if (bit_cnt == 4) begin state <= S_REG; bit_cnt <= 0; end
else bit_cnt <= bit_cnt + 1;
end
end
S_REG: begin // 寄存器地址5bit
if (mdc_fall) begin
mdio_o <= reg_addr[4-bit_cnt];
if (bit_cnt == 4) begin state <= S_TA; bit_cnt <= 0; end
else bit_cnt <= bit_cnt + 1;
end
end
S_TA: begin // 转向
if (is_read) begin
if (mdc_fall && bit_cnt == 0) begin
mdio_oe <= 0; bit_cnt <= 1;
end else if (mdc_fall && bit_cnt == 1) begin
state <= S_DATA; bit_cnt <= 0; rd_data <= 0;
end
end else begin
if (mdc_fall) begin
mdio_o <= (bit_cnt == 0) ? 1'b1 : 1'b0;
if (bit_cnt == 1) begin state <= S_DATA; bit_cnt <= 0; end
else bit_cnt <= bit_cnt + 1;
end
end
end
S_DATA: begin
if (is_read) begin
if (mdc_rise) begin
rd_data <= {rd_data[14:0], mdio_i};
if (bit_cnt == 15) begin
state <= S_WAIT; done <= 1;
end else bit_cnt <= bit_cnt + 1;
end
end else begin
if (mdc_fall) begin
mdio_o <= wr_data[15-bit_cnt];
if (bit_cnt == 15) begin
state <= S_WAIT; done <= 1;
end else bit_cnt <= bit_cnt + 1;
end
end
end
S_WAIT: begin
mdio_oe <= 0; mdio_o <= 1;
state <= S_IDLE;
end
endcase
// 轮询调度
if (poll_en) begin
if (poll_timer == 20'd1_000_000) begin
poll_timer <= 0;
case (poll_step)
2'd0: begin // 读BMSR
reg_addr <= REG_BMSR; is_read <= 1;
start <= 1; poll_step <= 1;
end
2'd1: begin
if (done) begin
link_up <= rd_data[2];
an_done <= rd_data[5];
start <= 0; poll_step <= 2;
end
end
2'd2: begin // 读BMCR
reg_addr <= REG_BMCR; is_read <= 1;
start <= 1; poll_step <= 3;
end
2'd3: begin
if (done) begin
link_speed <= rd_data[13] ? 2'b10 :
rd_data[6] ? 2'b01 : 2'b00;
start <= 0; poll_step <= 0;
end
end
endcase
end else
poll_timer <= poll_timer + 1;
end
end
end
endmodule
RGMII数据路径要点
- TX侧:FPGA在TXC上升沿发送TXD[3:0]低4位,下降沿发送高4位;TX_CTL同理承载TXEN与TXERR。
- RX侧:PHY在RXC双边沿输出RXD[3:0]与RX_CTL,FPGA需用IDDR原语采样。
- 1000M模式下RGMII时钟125MHz,100M/10M模式下时钟分别为25MHz/2.5MHz,需根据link_speed动态切换时钟源。
- 建议在TXC/RXC路径上加入IOBUF与IDELAY/ODELAY用于时序校准。
运行与验证
- 在Vivado中创建工程,添加上述
phy_ctrl.v,绑定引脚:mdc→PHY_MDC,mdio_o/mdio_oe/mdio_i→三态IOBUF→PHY_MDIO。 - 综合实现后下载bitstream,用ILA抓取
link_up、an_done、link_speed信号。 - 插入网线后观察:
link_up应在数百毫秒内拉高,an_done同步置1,link_speed与对端协商结果一致(千兆为2'b10)。 - 拔掉网线,
link_up应拉低;更换百兆对端,link_speed应变为2'b01。 - 用示波器测量MDC频率应为2.5MHz,MDIO在MDC上升沿采样数据稳定。
常见问题
- MDIO无响应:检查PHY地址是否与硬件strap一致,MDC分频是否过高。
- 链路始终down:确认PHY复位时序(RESET_N至少保持10ms低电平),以及自协商是否被禁用。
- RGMII数据错位:检查TXC/RXC是否加了2ns延迟(PHY内部延迟或PCB走线补偿)。