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.