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).