Protocolo MDIO para Interfaces Ethernet en FPGA

El bus MDIO (Management Data Input/Output) es una interfaz serie definida en el estándar IEEE 802.3, utilizada específicamente para la configuración y gestión de los transceptores PHY (Physical Layer) en redes Ethernet. Esta interfaz permite a un controlador MAC (Media Access Control) leer y escribir registros itnernos de los dispositivos PHY conectados.

La interfaz MDIO consta de dos señales fundamentales: MDC (Management Data Clock), una señal de reloj generada por el MAC, y MDIO (Management Data Input/Output), una línea de datos bidireccional para la transferencia de infomración. Los datos se muestrean en el flanco de subida de MDC.

Una transacción MDIO completa sigue una estructura de trama específica. Comienza con un preámbulo de 32 bits de valor lógico '1', seguido por un código de inicio (ST) de 2 bits con valor '01'. El código de operación (OP) de 2 bits determina la acción: '01' para escritura y '10' para lectura. A continuación, se indica la dirección del PHY (PHYAD) de 5 bits, permitiendo direccionar hasta 32 dispositivos. La dirección del registro (REGAD) de 5 bits selecciona uno de los 32 registros disponibles dentro del PHY. Un campo de giro (TA) de 2 bits gestiona el cambio de dirección del bus MDIO durante las operaciones de lectura. Finalmente, el campo de datos (DATA) de 16 bits transporta la información, enviada con el bit más significativo primero.

La implementación de este protocolo en una FPGA requiere una máquina de estados precisa para generar los ciclos de reloj MDC y secuenciar la trama MDIO en la línea de datos. A continuación, se presenta un ejemplo de implementación modular en Verilog, estructurado para facilitar su integración en un sistema más amplio.

Módulo de Interfaz MDIO (Interfaz_MDIO.v)

Este módulo es el núcleo de la implementación, encargado de generar las señales MDC y MDIO según el protocolo. Incluye una máquina de estados para controlar el flujo de la trama y lógica para manejar la alta impedancia (tri-state) de la línea MDIO.


// Descripción: Módulo de interfaz para el bus MDIO.
module Interfaz_MDIO #(
    parameter RETARDO_SYNC = 2  // Profundidad de sincronización para la entrada MDIO
)(
    // Reloj y reset del sistema
    input  wire        clk_sistema,
    input  wire        rst_sistema_n,
    // Control
    input  wire        habilitar_mdio,
    input  wire [31:0] divisor_frecuencia,
    // Interfaz de datos (lado del MAC)
    input  wire        solicitud_tx,
    input  wire [29:0] datos_tx,
    output reg         listo_tx,
    output wire [16:0] datos_rx,
    output reg         datos_rx_validos,
    // Conexión al bus MDIO físico
    output wire        mdio_mdc,
    output wire        mdio_out,
    output wire        mdio_tristate, // 0 = Salida activa, 1 = Alta impedancia (entrada)
    input  wire        mdio_in
);

    // Codificación de estados
    localparam [4:0] 
        ESPERA      = 5'b00001,
        PRE_AMBULO  = 5'b00010,
        CAMPO_COD_OP = 5'b00100,
        ENVIO_DATOS = 5'b01000,
        RECEPCION   = 5'b10000;

    reg [4:0] estado_actual, estado_siguiente;
    reg [6:0] contador_bits_tx;
    reg [4:0] contador_bits_rx;
    reg [63:0] registro_datos_tx;
    reg [1:0] operacion_actual;
    reg [31:0] acumulador_frecuencia;
    reg [1:0] sincronizador_entrada;
    reg [1:0] detector_flancos_mdc;
    reg [16:0] registro_datos_rx;
    reg [RETARDO_SYNC-1:0] registro_mdc_retardado;

    // Generación de MDC usando un acumulador de fase
    wire mdc_generado = acumulador_frecuencia[31];
    always @(posedge clk_sistema) begin
        if (!rst_sistema_n || !habilitar_mdio)
            acumulador_frecuencia <= 0;
        else
            acumulador_frecuencia <= acumulador_frecuencia + divisor_frecuencia;
    end

    // Detección de flancos de MDC
    reg mdc_previo;
    always @(posedge clk_sistema) mdc_previo <= mdc_generado;
    wire flanco_subida_mdc = ~mdc_previo & mdc_generado;
    wire flanco_bajada_mdc = mdc_previo & ~mdc_generado;

    // Sincronización y detección de flancos para la entrada MDIO
    always @(posedge clk_sistema) begin
        sincronizador_entrada <= {sincronizador_entrada[0], mdio_in};
        registro_mdc_retardado <= {registro_mdc_retardado[RETARDO_SYNC-2:0], mdc_generado};
    end
    wire mdio_entrada_sincronizada = sincronizador_entrada[1];
    // Flanco de subida retardado para muestrear datos de entrada (solo activo en recepción)
    wire flanco_muestreo_rx = (estado_actual == RECEPCION) ? 
                             (~registro_mdc_retardado[RETARDO_SYNC-1] & registro_mdc_retardado[RETARDO_SYNC-2]) : 1'b0;

    // Máquina de estados
    always @(posedge clk_sistema) begin
        if (!rst_sistema_n)
            estado_actual <= ESPERA;
        else
            estado_actual <= estado_siguiente;
    end

    always @(*) begin
        estado_siguiente = estado_actual;
        case (estado_actual)
            ESPERA: begin
                if (solicitud_tx && flanco_bajada_mdc)
                    estado_siguiente = PRE_AMBULO;
            end
            PRE_AMBULO: begin
                if (contador_bits_tx == 7'd33) // Fin del preámbulo + ST
                    estado_siguiente = CAMPO_COD_OP;
            end
            CAMPO_COD_OP: begin
                if (contador_bits_tx == 7'd35) // Fin del código de operación
                    if (operacion_actual == 2'b01) // Escritura
                        estado_siguiente = ENVIO_DATOS;
                    else if (operacion_actual == 2'b10 && contador_bits_tx == 7'd35) // Lectura
                        estado_siguiente = RECEPCION;
            end
            ENVIO_DATOS: begin
                if (contador_bits_tx == 7'd65) // 33 (pre+st) + 2 (op) + 5 (phy) + 5 (reg) + 2 (ta) + 16 (data) - 1
                    estado_siguiente = ESPERA;
            end
            RECEPCION: begin
                if (contador_bits_rx == 5'd17) // 2 (ta) + 16 (data) - 1
                    estado_siguiente = ESPERA;
            end
            default: estado_siguiente = ESPERA;
        endcase
    end

    // Lógica de transmisión
    reg transmitiendo;
    always @(posedge clk_sistema) begin
        if (!rst_sistema_n) begin
            registro_datos_tx <= 0;
            contador_bits_tx <= 0;
            transmitiendo <= 0;
            operacion_actual <= 0;
            listo_tx <= 0;
        end else begin
            listo_tx <= (estado_actual == ESPERA) && solicitud_tx;
            if (solicitud_tx && estado_actual == ESPERA) begin
                // Cargar nueva trama: Preámbulo + ST + Operación + Datos de entrada
                registro_datos_tx <= {32'hFFFFFFFF, 2'b01, datos_tx};
                operacion_actual <= datos_tx[29:28];
                contador_bits_tx <= 0;
                transmitiendo <= 1;
            end else if (flanco_bajada_mdc && transmitiendo) begin
                // Desplazar el siguiente bit en la salida
                registro_datos_tx <= {registro_datos_tx[62:0], 1'b0};
                contador_bits_tx <= contador_bits_tx + 1;
            end
        end
    end

    // Lógica de recepción
    reg recibiendo;
    always @(posedge clk_sistema) begin
        if (!rst_sistema_n) begin
            registro_datos_rx <= 0;
            contador_bits_rx <= 0;
            recibiendo <= 0;
            datos_rx_validos <= 0;
        end else begin
            datos_rx_validos <= 0;
            if (estado_actual == RECEPCION && !recibiendo) begin
                recibiendo <= 1;
                contador_bits_rx <= 0;
            end else if (flanco_muestreo_rx) begin
                registro_datos_rx <= {registro_datos_rx[15:0], mdio_entrada_sincronizada};
                contador_bits_rx <= contador_bits_rx + 1;
                if (contador_bits_rx == 5'd17) begin
                    datos_rx_validos <= 1;
                    recibiendo <= 0;
                end
            end
        end
    end

    // Asignación de salidas y control del tri-state
    assign mdio_mdc = mdc_generado;
    assign mdio_out = registro_datos_tx[63];
    // Activar la salida solo durante los estados de transmisión
    assign mdio_tristate = ~( (estado_actual == PRE_AMBULO) || 
                             (estado_actual == CAMPO_COD_OP) || 
                             (estado_actual == ENVIO_DATOS) );
    assign datos_rx = registro_datos_rx;

endmodule

Módulo de Control AXI4-Lite (Controlador_AXI_MDIO.v)

Este módulo proporciona una interfaz AXI4-Lite para acceder y controlar el núcleo MDIO desde un procesador o lógica de alto nivel. Administra registros de control, un divisor de frecuencia y buffers FIFO para las operaciones de lectura y escritura.


// Descripción: Controlador con interfaz AXI4-Lite para el núcleo MDIO.
module Controlador_AXI_MDIO #(
    parameter PROFUNDIDAD_FIFO = 256
)(
    // Reloj y reset del sistema (dominio AXI)
    input  wire        s_axi_aclk,
    input  wire        s_axi_aresetn,
    // Interfaz AXI4-Lite de escritura
    input  wire [31:0] s_axi_awaddr,
    input  wire [2:0]  s_axi_awprot,
    input  wire        s_axi_awvalid,
    output wire        s_axi_awready,
    input  wire [31:0] s_axi_wdata,
    input  wire [3:0]  s_axi_wstrb,
    input  wire        s_axi_wvalid,
    output wire        s_axi_wready,
    output wire [1:0]  s_axi_bresp,
    output wire        s_axi_bvalid,
    input  wire        s_axi_bready,
    // Interfaz AXI4-Lite de lectura
    input  wire [31:0] s_axi_araddr,
    input  wire [2:0]  s_axi_arprot,
    input  wire        s_axi_arvalid,
    output wire        s_axi_arready,
    output wire [31:0] s_axi_rdata,
    output wire [1:0]  s_axi_rresp,
    output wire        s_axi_rvalid,
    input  wire        s_axi_rready,
    // Interfaz hacia el módulo MDIO
    output reg         mdio_habilitado,
    output reg         modulo_habilitado,
    output reg  [31:0] valor_divisor_frec,
    input  wire        mdio_listo_tx,
    output wire [29:0] mdio_datos_tx,
    output wire        mdio_solicitud_tx,
    input  wire [16:0] mdio_datos_rx,
    input  wire        mdio_datos_rx_validos
);

    // Decodificación de registros internos
    localparam ADDR_CONTROL     = 8'h00; // [0]=Hab. Módulo, [8]=Hab. MDIO
    localparam ADDR_DIVISOR     = 8'h04; // Divisor de frecuencia
    localparam ADDR_TX_FIFO     = 8'h08; // Escritura a FIFO de transmisión
    localparam ADDR_RX_FIFO     = 8'h0C; // Lectura de FIFO de recepción
    localparam ADDR_STATUS      = 8'h10; // Estado: [15:0]=Cuenta TX, [31:16]=Cuenta RX

    // Señales internas del esclavo AXI
    wire [31:0] direccion_escritura;
    wire        hab_escritura;
    wire [31:0] datos_escritura;
    wire [3:0]  mascara_escritura;
    wire [31:0] direccion_lectura;
    reg  [31:0] datos_para_lectura;
    wire        reset_fifo;

    // Instanciación del esclavo AXI4-Lite
    Esclavo_AXI4_Lite u_esclavo_axi (
        .clk(s_axi_aclk),
        .rst_n(s_axi_aresetn),
        .axi_awaddr(s_axi_awaddr),
        .axi_awvalid(s_axi_awvalid),
        .axi_awready(s_axi_awready),
        .axi_wdata(s_axi_wdata),
        .axi_wstrb(s_axi_wstrb),
        .axi_wvalid(s_axi_wvalid),
        .axi_wready(s_axi_wready),
        .axi_bresp(s_axi_bresp),
        .axi_bvalid(s_axi_bvalid),
        .axi_bready(s_axi_bready),
        .axi_araddr(s_axi_araddr),
        .axi_arvalid(s_axi_arvalid),
        .axi_arready(s_axi_arready),
        .axi_rdata(s_axi_rdata),
        .axi_rresp(s_axi_rresp),
        .axi_rvalid(s_axi_rvalid),
        .axi_rready(s_axi_rready),
        .dir_escritura(direccion_escritura),
        .hab_escritura(hab_escritura),
        .datos_escritura(datos_escritura),
        .mascara_escritura(mascara_escritura),
        .dir_lectura(direccion_lectura),
        .datos_lectura(datos_para_lectura)
    );

    // Lógica de registros
    always @(posedge s_axi_aclk) begin
        if (!s_axi_aresetn) begin
            modulo_habilitado <= 0;
            mdio_habilitado <= 0;
            valor_divisor_frec <= 0;
        end else if (hab_escritura) begin
            case (direccion_escritura[7:2])
                ADDR_CONTROL[7:2]: begin
                    if (mascara_escritura[0]) modulo_habilitado <= datos_escritura[0];
                    if (mascara_escritura[1]) mdio_habilitado <= datos_escritura[8];
                end
                ADDR_DIVISOR[7:2]: begin
                    if (mascara_escritura[0]) valor_divisor_frec[7:0]   <= datos_escritura[7:0];
                    if (mascara_escritura[1]) valor_divisor_frec[15:8]  <= datos_escritura[15:8];
                    if (mascara_escritura[2]) valor_divisor_frec[23:16] <= datos_escritura[23:16];
                    if (mascara_escritura[3]) valor_divisor_frec[31:24] <= datos_escritura[31:24];
                end
                default: ; // Otras direcciones ignoradas
            endcase
        end
    end

    // FIFO para transmisión (escritura desde AXI a MDIO)
    wire        fifo_tx_llena;
    wire [15:0] cuenta_fifo_tx;
    assign reset_fifo = s_axi_aresetn & modulo_habilitado;
    wire escritura_fifo_tx = hab_escritura && (direccion_escritura[7:2] == ADDR_TX_FIFO[7:2]);

    FIFO_Asincrono #(
        .ANCHO(30),
        .PROFUNDIDAD($clog2(PROFUNDIDAD_FIFO))
    ) u_fifo_tx (
        .rst_n(reset_fifo),
        .clk_escritura(s_axi_aclk),
        .solicitud_escritura(escritura_fifo_tx),
        .datos_escritura(datos_escritura[29:0]),
        .fifo_llena(fifo_tx_llena),
        .clk_lectura(s_axi_aclk), // Reloj de lectura conectado al mismo dominio para simplificar
        .solicitud_lectura(mdio_listo_tx),
        .datos_lectura(mdio_datos_tx),
        .fifo_vacia()
    );
    assign mdio_solicitud_tx = !fifo_tx_vacia; // La señal de solicitud es inversa a vacía

    // FIFO para recepción (lectura desde MDIO a AXI)
    wire        fifo_rx_vacia;
    wire [15:0] datos_fifo_rx;
    wire [15:0] cuenta_fifo_rx;
    wire lectura_fifo_rx = s_axi_arvalid && s_axi_arready && (direccion_lectura[7:2] == ADDR_RX_FIFO[7:2]);

    FIFO_Asincrono #(
        .ANCHO(17),
        .PROFUNDIDAD($clog2(PROFUNDIDAD_FIFO))
    ) u_fifo_rx (
        .rst_n(reset_fifo),
        .clk_escritura(s_axi_aclk),
        .solicitud_escritura(mdio_datos_rx_validos),
        .datos_escritura(mdio_datos_rx),
        .fifo_llena(),
        .clk_lectura(s_axi_aclk),
        .solicitud_lectura(lectura_fifo_rx),
        .datos_lectura(datos_fifo_rx),
        .fifo_vacia(fifo_rx_vacia)
    );

    // Multiplexor de datos de lectura
    always @(*) begin
        datos_para_lectura = 32'd0;
        case (direccion_lectura[7:2])
            ADDR_CONTROL[7:2]: datos_para_lectura = {23'd0, mdio_habilitado, 7'd0, modulo_habilitado};
            ADDR_DIVISOR[7:2]: datos_para_lectura = valor_divisor_frec;
            ADDR_RX_FIFO[7:2]: datos_para_lectura = {15'd0, fifo_rx_vacia, datos_fifo_rx}; // Incluye bandera de vacío
            ADDR_STATUS[7:2]:  datos_para_lectura = {cuenta_fifo_rx, cuenta_fifo_tx};
            default: datos_para_lectura = 32'd0;
        endcase
    end

endmodule

La integración de estos módulos permite controlar un transceiver PHY Ethernet desde un procesador a través de un bus AXI4-Lite estándar. La generación del reloj MDC es configurable mediante un divisor, y la secuencia de la trama MDIO se gestiona automáticamente, simplificando significativamente el software de control. Este diseño es escalable y puede conectarse a múltiples dispositivos PHY mediante el campo de dirección PHY (PHYAD).

Etiquetas: FPGA MDIO Ethernet AXI4-Lite Verilog

Publicado el 7-20 20:59