Implementação de Saída de Vídeo HDMI Utilizando FPGA

Inicialmente, é essencial compreender como o HDMI transmite dados em sua camada física. Este padrão utiliza a codificação TMDS (Transition Minimized Differential Signaling), um algoritmo que converte 8 bits de dados de pixel em 10 bits para transmissão. Três canais de dados transmitem as componentes RGB, enquanto um canal de clock mantém a sincronização. Para uma resolução padrão de 640x480 a 60Hz, o clock de pixel deve operar em aproximadamente 25.175MHz, embora 25MHz seja frequentemente utilizado na prática.

A seguir, apresentamos os módulos Verilog necessários para implementar esta funcionalidade.

Módulo de Geração de Temporização de Vídeo

Este módulo gera os sinais de varredura horizontal e vertical, coordenadas de pixel e sinais de sincronização:

module gerador_timing(
    input clock_pixel,
    output reg [10:0] posicao_x,
    output reg [10:0] posicao_y,
    output reg sincronismo_h,
    output reg sincronismo_v,
    output reg area_ativa
);

// Parâmetros de temporização para 640x480@60Hz
localparam LARGURA_ATIVA = 640;
localparam FRONT_PORCH_H = 16;
localparam PULSO_H = 96;
localparam BACK_PORCH_H = 48;

localparam ALTURA_ATIVA = 480;
localparam FRONT_PORCH_V = 10;
localparam PULSO_V = 2;
localparam BACK_PORCH_V = 33;

localparam TOTAL_H = LARGURA_ATIVA + FRONT_PORCH_H + PULSO_H + BACK_PORCH_H;
localparam TOTAL_V = ALTURA_ATIVA + FRONT_PORCH_V + PULSO_V + BACK_PORCH_V;

always @(posedge clock_pixel) begin
    if(posicao_x < TOTAL_H - 1)
        posicao_x <= posicao_x + 1;
    else begin
        posicao_x <= 0;
        if(posicao_y < TOTAL_V - 1)
            posicao_y <= posicao_y + 1;
        else
            posicao_y <= 0;
    end

    sincronismo_h <= (posicao_x >= LARGURA_ATIVA + FRONT_PORCH_H) && 
                     (posicao_x < LARGURA_ATIVA + FRONT_PORCH_H + PULSO_H);
    
    sincronismo_v <= (posicao_y >= ALTURA_ATIVA + FRONT_PORCH_V) && 
                     (posicao_y < ALTURA_ATIVA + FRONT_PORCH_V + PULSO_V);
    
    area_ativa <= (posicao_x < LARGURA_ATIVA) && (posicao_y < ALTURA_ATIVA);
end
endmodule

Este circuito mantém o controle da posição atual de varredura através dos registradores posicao_x e posicao_y. O sinal area_ativa indica quando dados de pixel válidos devem ser transmitidos. Os sinais de sincronismo são ativos em nível baixo, característica importante que deve ser observada durante a implementação.

Módulo Codificador TMDS

A codificação TMDS constitui o núcleo deste projeto. A implementação utiliza uma abordagem baseada em tabelas e lógica combinacional:

module codificador_tmds(
    input [7:0] dado_pixel,
    input bit_co0,
    input bit_co1,
    input habilitacao_dados,
    output reg [9:0] palavra_tmds
);

// Função para calcular quantidade de bits iguais a 1
function [3:0] contar_uns;
    input [7:0] dados;
    integer contador;
    begin
        contar_uns = 0;
        for(contador = 0; contador < 8; contador = contador + 1)
            contar_uns = contar_uns + dados[contador];
    end
endfunction

reg [8:0] dados_encodados;
reg [3:0] contagem_uns;
reg sel_xor_xnor;

always @(*) begin
    if(!habilitacao_dados) begin
        // Período de controle - mapeia estados de sincronismo
        case({bit_co1, bit_co0})
            2'b00: palavra_tmds = 10'b1101010100;
            2'b01: palavra_tmds = 10'b0010101011;
            2'b10: palavra_tmds = 10'b0101010100;
            2'b11: palavra_tmds = 10'b1010101011;
            default: palavra_tmds = 10'b1101010100;
        endcase
    end
    else begin
        // Período de dados - codificação TMDS
        contagem_uns = contar_uns(dado_pixel);
        
        // Seleção entre XOR e XNOR baseada na distribuição de bits
        if(contagem_uns > 4 || (contagem_uns == 4 && dado_pixel[0] == 0)) begin
            // Codificação XNOR
            dados_encodados[0] = dado_pixel[0];
            dados_encodados[1] = dado_pixel[0] ^ ~dado_pixel[1];
            dados_encodados[2] = dados_encodados[1] ^ ~dado_pixel[2];
            dados_encodados[3] = dados_encodados[2] ^ ~dado_pixel[3];
            dados_encodados[4] = dados_encodados[3] ^ ~dado_pixel[4];
            dados_encodados[5] = dados_encodados[4] ^ ~dado_pixel[5];
            dados_encodados[6] = dados_encodados[5] ^ ~dado_pixel[6];
            dados_encodados[7] = dados_encodados[6] ^ ~dado_pixel[7];
            dados_encodados[8] = 0;
        end
        else begin
            // Codificação XOR
            dados_encodados[0] = dado_pixel[0];
            dados_encodados[1] = dado_pixel[0] ^ dado_pixel[1];
            dados_encodados[2] = dados_encodados[1] ^ dado_pixel[2];
            dados_encodados[3] = dados_encodados[2] ^ dado_pixel[3];
            dados_encodados[4] = dados_encodados[3] ^ dado_pixel[4];
            dados_encodados[5] = dados_encodados[4] ^ dado_pixel[5];
            dados_encodados[6] = dados_encodados[5] ^ dado_pixel[6];
            dados_encodados[7] = dados_encodados[6] ^ dado_pixel[7];
            dados_encodados[8] = 1;
        end
        
        // Balanceamento DC
        contagem_uns = contar_uns(dados_encodados[7:0]) + dados_encodados[8];
        
        if(contagem_uns > 5 || (contagem_uns == 5 && dados_encodados[8] == 0)) begin
            palavra_tmds = {~dados_encodados[8], ~dados_encodados[7:0], 1'b1};
        end
        else begin
            palavra_tmds = {dados_encodados[8], dados_encodados[7:0], 1'b0};
        end
    end
end
endmodule

A codificação TMDS emprega uma técnica sofisticada: analisa a quantidade de bits iguais a 1 nos dados originais para decidir dinamicamente entre usar portas XOR ou XNOR. Após a codificação, um estágio de balanceamento de corrente contínua (DC) garante que a média dos níveis lógicos permaneça equilibrada, evitando distorção do sinal.

Módulo Principal de Interface HDMI

O módulo de nível superior integra todos os componentes e realiza a conversão paralela-serial:

module interface_hdmi(
    input clk_sistema,
    output [2:0] canal_tmds,
    output canal_clock
);

// Sinais internos
wire clock_25mhz, clock_250mhz;
wire [10:0] pos_x, pos_y;
wire sinc_h, sinc_v, ativo;
wire [9:0] codigo_r, codigo_g, codigo_b;

// Geração de clocks
PLL_analogo gerador_pll(
    .entrada(clk_sistema),
    .saida_25mhz(clock_25mhz),
    .saida_250mhz(clock_250mhz)
);

// Instanciação do gerador de timing
gerador_timing timing_inst(
    .clock_pixel(clock_25mhz),
    .posicao_x(pos_x),
    .posicao_y(pos_y),
    .sincronismo_h(sinc_h),
    .sincronismo_v(sinc_v),
    .area_ativa(ativo)
);

// Geração de padrão de teste - gradiente cromático
assign cor_vermelha = {pos_x[7:0] ^ pos_y[7:0], 8'h00};
assign cor_verde = {8'h00, pos_x[7:0]};
assign cor_azul = {8'h00, pos_y[7:0]};

// Codificadores TMDS para cada canal de cor
codificador_tmds encoder_r(
    .dado_pixel(cor_vermelha[7:0]),
    .bit_co0(sinc_v),
    .bit_co1(sinc_h),
    .habilitacao_dados(ativo),
    .palavra_tmds(codigo_r)
);

codificador_tmds encoder_g(
    .dado_pixel(cor_verde[7:0]),
    .bit_co0(1'b0),
    .bit_co1(1'b0),
    .habilitacao_dados(ativo),
    .palavra_tmds(codigo_g)
);

codificador_tmds encoder_b(
    .dado_pixel(cor_azul[7:0]),
    .bit_co0(sinc_v),
    .bit_co1(sinc_h),
    .habilitacao_dados(ativo),
    .palavra_tmds(codigo_b)
);

// Conversão paralela para serial - 10:1
genvar indice;
generate
    for(indice = 0; indice < 3; indice = indice + 1) begin: conversor_serial
        OSERDESE2 #(
            .DATA_RATE_OQ("DDR"),
            .DATA_WIDTH(10),
            .SERDES_MODE("MASTER")
        ) serialize_inst (
            .CLK(clock_250mhz),
            .CLKDIV(clock_25mhz),
            .D1(codigo_r[indice]),
            .D2(codigo_r[indice + 5]),
            .D3(codigo_g[indice]),
            .D4(codigo_g[indice + 5]),
            .D5(codigo_b[indice]),
            .D6(codigo_b[indice + 5]),
            .D7(1'b0),
            .D8(1'b0),
            .OCE(1'b1),
            .RST(1'b0),
            .OQ(canal_tmds[indice])
        );
    end
endgenerate

// Canal de clock (réplica do sinal de clock)
assign canal_clock = clock_25mhz;

endmodule

Este módulo utiliza primitive OSERDESE2 para realizar a conversão 10:1, operando a 250MHz para transmitir os 10 bits de cada símbolo TMDS dentro de um único período de clock de 25MHz. O padrão de teste gerado cria um gradiente cromático que permite verificar rapidamente o funcionamento correto do sistema.

Considerações Práticas e depuração

Durante a implementação em hardware real, diversos problemas podem surgir. O mais frequente envolve a inversão dos pares diferenciais — a polaridade dos pinos do cnoector HDMI deve corresponder exatamente à definição dos pinos do FPGA. Recomenda-se verificar cuidadosamente o esquemático da placa de desenvolvimento.

Outro problema comum ocorre quando o monitor não reconhece o sinal. Frequentemente, isto acontece devido a larguras de pulso de sincronismo ligeiramente fora da especificação. Uma diferença de apenas um ciclo de clock pode fazer com que alguns monitores recusem o sinal. A utilização de ferramentas como SignalTap ou ChipScope para capturar e analisar os waveforms codificados é altamente recomendada.

Este projeto fornece uma base sólida que pode ser expandida para resoluções superiores, como 1080p, ou integrada com pipelines de renderização graphics mais sofisticadas. A implementação demonstra que, com conhecimento adequado dos protocolos de comunicação digital, o FPGA pode funcionar como uma fonte de vídeo completamente funcional.

Tags: FPGA hdmi Verilog tmds video

Publicado em 7-11 01:22