Em projetos com FPGAs (Field Programmable Gate Arrays), a verificação da integridade de dados é tão essencial quanto em sistemas baseados em microcontroladores. Isso é especialmente verdadeiro quando os dados trafegam por interfaces seriais, barramentos internos, ou entre blocos lógicos sincronizados por clock. A possibilidade de erros de sincronismo, glitches ou ruídos pode corromper bits e comprometer o funcionamento do sistema — e detectar esses erros de forma eficiente é parte crucial do design digital.
Diferentemente dos sistemas baseados em software, os FPGAs trabalham com lógica combinacional e sequencial implementada diretamente no hardware. Isso permite uma abordagem altamente paralela e de baixa latência para o cálculo de verificações como checksums e CRCs (Cyclic Redundancy Check). Entretanto, essa abordagem exige que o engenheiro de hardware compreenda como traduzir operações matemáticas binárias em estruturas digitais, como LFSRs (Registradores de Deslocamento com Realimentação), somadores, muxes, e máquinas de estado.
Neste tutorial, vamos abordar duas técnicas clássicas de verificação de integridade em hardware:
- O checksum, simples de implementar, ideal para sistemas de baixa complexidade ou onde o erro mais comum é a perda de bits.
- O CRC, robusto, capaz de detectar múltiplos erros de bits e alterações sistemáticas, usado em protocolos como Ethernet, SPI, USB, CAN e PCIe.
Ambas as técnicas serão implementadas em Verilog, com foco em projetos para FPGA. A implementação incluirá exemplos práticos, módulos parametrizáveis, simulações com testbenches, e dicas para integração com interfaces seriais como UART e SPI. Também discutiremos estratégias de otimização em hardware e como garantir que o sistema reaja corretamente à detecção de erros.
Este guia é voltado tanto para iniciantes quanto para desenvolvedores experientes que desejam refinar seus módulos de transmissão e recepção de dados, garantindo robustez e confiabilidade em projetos FPGA profissionais.
📘 Capítulo 2 – Conceitos Fundamentais
📌 Bits, Registradores e Sinais em Verilog
Em Verilog, os dados são representados por vetores de bits, que podem ser armazenados em registradores (reg
) ou manipulados como fios (wire
). Esses dados trafegam entre módulos e são processados de forma síncrona (com clock) ou assíncrona (combinacional).
Por exemplo, um vetor de 8 bits que representa um byte pode ser declarado assim:
reg [7:0] dado; // um byte armazenado em registrador
wire [15:0] resultado; // 2 bytes como fio combinacional
Em um sistema de comunicação serial, a verificação de integridade ocorre normalmente após o recebimento completo de um pacote de dados, seja por meio de shift registers, buffers ou blocos de controle (FSMs).
📌 Redundância e Detecção de Erros
A redundância controlada é a chave para validar a integridade dos dados transmitidos. A ideia é gerar um valor de verificação (checksum ou CRC) que sintetize o conteúdo do pacote. Esse valor é transmitido junto aos dados, e no receptor é recalculado e comparado com o valor recebido.
Essa lógica se traduz em hardware por:
- Somadores para checksum
- LFSRs (Linear Feedback Shift Registers) para CRC
- Comparadores de igualdade para validação
Tudo isso precisa ser projetado considerando timing, largura de dados, clock domain crossing e latência.
📌 Checksum e CRC como Operadores Lógicos
Em software, o cálculo de checksum ou CRC é sequencial, byte por byte. Em hardware, podemos projetar blocos que executam essas operações em paralelo, reduzindo drasticamente a latência de verificação.
Por exemplo:
- Um checksum pode ser a soma direta de todos os bytes armazenados em um array de registradores.
- Um CRC pode ser gerado a cada novo bit recebido, com o registrador CRC sendo atualizado bit a bit ou byte a byte, dependendo do projeto.
Além disso, o Verilog permite o uso de operações bitwise (^
, &
, |
) e deslocamentos (<<
, >>
), que casam perfeitamente com a lógica de CRC.
📌 Reflexão e Ordem de Bits
Alguns protocolos exigem que os bits sejam refletidos (bit reversal) antes de entrar no cálculo do CRC. Esse processo pode ser implementado com lógica combinacional simples ou com um laço de deslocamento:
function [7:0] refletir;
input [7:0] dado;
integer i;
begin
for (i = 0; i < 8; i = i + 1)
refletir[i] = dado[7 - i];
end
endfunction
Reflexão de bits, ordem dos bytes (little vs. big endian), e valores iniciais do CRC devem ser claramente definidos em projetos digitais, pois pequenas diferenças levam a grandes incompatibilidades na verificação.
📘 Capítulo 3 – Checksum em Verilog
O checksum é um método direto para verificar a integridade de dados. Ele consiste em somar todos os bytes transmitidos e, opcionalmente, aplicar uma operação complementar (como o complemento de 1 ou 2). Em hardware, essa operação é simples de implementar com somadores e registradores acumuladores.
📌 Estrutura do Módulo Checksum
Nosso exemplo usará:
- Entrada paralela de dados de 8 bits (
data_in
) - Sinal de habilitação (
enable
) - Reset síncrono (
rst
) - Um sinal de validação (
done
) indicando o fim do pacote - Saída do checksum (
checksum_out
)
🧪 Exemplo: Checksum acumulativo de 8 bits
module checksum8 (
input clk,
input rst,
input enable,
input done,
input [7:0] data_in,
output reg [7:0] checksum_out
);
reg [7:0] soma;
always @(posedge clk) begin
if (rst) begin
soma <= 8'b0;
checksum_out <= 8'b0;
end else if (enable) begin
soma <= soma + data_in;
end else if (done) begin
checksum_out <= soma; // ou ~soma para complemento de 1
end
end
endmodule
🧪 Testbench para Simulação
module tb_checksum8;
reg clk = 0, rst = 0, enable = 0, done = 0;
reg [7:0] data_in;
wire [7:0] checksum_out;
checksum8 uut (.clk(clk), .rst(rst), .enable(enable), .done(done), .data_in(data_in), .checksum_out(checksum_out));
always #5 clk = ~clk; // Clock de 10ns
initial begin
rst = 1; #10;
rst = 0;
// Enviar sequência de dados: 0x12, 0x34, 0x56
data_in = 8'h12; enable = 1; #10;
data_in = 8'h34; #10;
data_in = 8'h56; #10;
enable = 0; done = 1; #10;
done = 0;
// Esperado: 0x12 + 0x34 + 0x56 = 0x9C
$display("Checksum calculado: %h", checksum_out);
$stop;
end
endmodule
📌 Considerações
- Esse módulo é sequencial: a soma ocorre a cada
enable
positivo. - É simples de estender para 16 bits (
reg [15:0] soma
) ou usar complemento de 1 com~soma
. - Pode ser integrado diretamente em uma máquina de estados (FSM) responsável por controlar a recepção dos dados.
📘 Capítulo 4 – Fundamentos de CRC em Hardware
Enquanto o checksum soma os dados para gerar um valor de verificação, o CRC (Cyclic Redundancy Check) realiza uma divisão polinomial binária no campo GF(2). Em hardware, isso é modelado como um registrador de deslocamento com realimentação, conhecido como LFSR (Linear Feedback Shift Register).
📌 Representação Polinomial
Cada valor de dado é interpretado como um polinômio, onde cada bit representa um coeficiente. Por exemplo:
Byte: 11010010 (0xD2) → x⁷ + x⁶ + x⁴ + x¹
O cálculo do CRC equivale a dividir o polinômio formado pelos dados por um polinômio gerador padrão, como:
- CRC-4-ITU: x⁴ + x + 1 →
0b10011
- CRC-8: x⁸ + x² + x + 1 →
0x07
- CRC-16-IBM: x¹⁶ + x¹⁵ + x² + 1 →
0x8005
- CRC-32 (IEEE 802.3): x³² + x²⁶ + x²³ + … + x + 1 →
0x04C11DB7
📌 LFSR: Implementação em Hardware
O LFSR realiza essa divisão bit a bit. Ele contém:
- Um registrador de estado com o tamanho do CRC (8, 16 ou 32 bits)
- Um conjunto de XORs conectados nas posições definidas pelo polinômio gerador
- Entrada de dados serial ou paralela
🧱 Estrutura básica de um LFSR com entrada serial:
Entrada --> XOR --> [bit7] --> [bit6] --> ... --> [bit0] --> Saída CRC
↘__________/ (feedback baseado no polinômio)
Cada bit de entrada afeta o estado do registrador, com base nas conexões de realimentação definidas pelo polinômio.
📌 Reflexão e XOR Final
Na prática, muitas implementações de CRC utilizam:
- Reflexão dos bits de entrada (bit reversal)
- Reflexão dos bits do resultado (output)
- XOR com um valor final constante
Esses ajustes são usados para compatibilidade com protocolos de comunicação e devem ser documentados com clareza.
📌 Exemplo: CRC-8 com polinômio 0x07
Esse polinômio é muito usado em sensores e protocolos simples:
Polinômio: x⁸ + x² + x + 1 → 0x07 → taps em bits 8, 2 e 1
Representado como LFSR:
- Feedback do bit mais significativo (MSB) é aplicado nos bits 2 e 1 através de XOR
- A entrada serial é XORada com o MSB antes de entrar no LFSR
📌 Interpretação Visual
Para CRC-8, temos 8 flip-flops (reg [7:0] crc_reg
) e uma lógica de feedback do bit mais à esquerda (bit 7), aplicado sobre bits específicos. Cada novo bit de entrada avança o registrador e modifica seu valor.
📘 Capítulo 5 – Implementando CRC em Verilog
A implementação de CRC em Verilog é baseada em circuitos sequenciais com realimentação condicional via XOR. Essa estrutura pode ser ajustada para entrada serial (um bit por ciclo) ou paralela (um byte por ciclo), dependendo da complexidade e da taxa de dados exigida.
📌 CRC Serial com LFSR – CRC-8 (Polinômio 0x07)
Essa versão processa 1 bit por ciclo de clock. Ideal para interfaces como UART ou SPI com buffer serial.
🧪 Módulo CRC-8 serial:
module crc8_serial (
input clk,
input rst,
input enable,
input bit_in,
output reg [7:0] crc_out
);
wire feedback;
assign feedback = crc_out[7] ^ bit_in;
always @(posedge clk) begin
if (rst)
crc_out <= 8'h00; // valor inicial do CRC
else if (enable) begin
crc_out[7] <= crc_out[6];
crc_out[6] <= crc_out[5];
crc_out[5] <= crc_out[4];
crc_out[4] <= crc_out[3] ^ feedback;
crc_out[3] <= crc_out[2];
crc_out[2] <= crc_out[1] ^ feedback;
crc_out[1] <= crc_out[0] ^ feedback;
crc_out[0] <= feedback;
end
end
endmodule
📌 Essa estrutura reproduz exatamente o efeito do polinômio x⁸ + x² + x + 1
(0x07).
📌 CRC Paralelo – Processando um Byte por Ciclo
Em aplicações que exigem mais velocidade, é comum processar dados 8 bits por vez usando uma lógica combinacional que aplica o efeito completo do polinômio sobre um byte inteiro.
🧪 Módulo CRC-8 paralelo (combinacional):
module crc8_parallel (
input clk,
input rst,
input enable,
input [7:0] data_in,
output reg [7:0] crc_out
);
function [7:0] next_crc;
input [7:0] data;
input [7:0] crc;
integer i;
reg [7:0] temp;
begin
temp = crc ^ data;
for (i = 0; i < 8; i = i + 1) begin
if (temp[7])
temp = (temp << 1) ^ 8'h07;
else
temp = (temp << 1);
end
next_crc = temp;
end
endfunction
always @(posedge clk) begin
if (rst)
crc_out <= 8'h00;
else if (enable)
crc_out <= next_crc(data_in, crc_out);
end
endmodule
💡 Essa versão simula uma lookup table embutida, executando o efeito cumulativo de 8 entradas em uma única operação.
📌 Considerações para CRC-16 e CRC-32
- A estrutura é análoga: o registrador tem 16 ou 32 bits e os taps (feedbacks) são ajustados conforme o polinômio.
- CRC-16 (0x8005) e CRC-32 (0x04C11DB7) são amplamente usados em redes e armazenamento.
- Em CRCs maiores, é comum gerar os módulos automaticamente a partir de scripts Python ou ferramentas como crcgen.
📌 Verificação com Vetores de Teste
Recomenda-se sempre testar sua implementação com vetores conhecidos. Você pode usar sites como:
- https://crccalc.com/
- Ferramentas de referência como RevEng ou pycrc
Simule a entrada do valor byte a byte e verifique se o valor do crc_out
final corresponde.
📘 Capítulo 6 – Otimizações para FPGA
A implementação de checksum ou CRC em FPGAs pode ser ajustada para obter melhor desempenho, menor uso de área lógica, ou menor consumo de energia, dependendo da aplicação. Otimizar bem significa balancear esses fatores para obter o melhor custo-benefício no projeto final.
📌 Pipeline e Paralelismo
FPGAs permitem inserir pipeline registers entre as etapas de processamento, o que aumenta a frequência máxima da lógica. Para CRC, isso pode ser aplicado entre cada estágio do LFSR, ou entre operações de XOR em módulos paralelos.
🧱 Exemplo:
always @(posedge clk) begin
feedback_stage <= crc_out[7] ^ bit_in;
stage1 <= crc_out[6];
stage2 <= crc_out[5];
...
end
O uso de pipeline reduz o tempo crítico de propagação entre sinais, permitindo que o circuito opere em frequências mais altas, o que é essencial em links seriais de alta velocidade como SPI rápido ou Ethernet softcore.
📌 CRC Paralelo com LUTs (ROM)
Para acelerar ainda mais o processamento, você pode substituir a lógica de XOR por lookup tables (LUTs), geradas previamente em ROM ou combinacionalmente.
🧪 Exemplo: ROM com valores CRC-8 para cada byte possível
reg [7:0] crc_table [0:255];
initial begin
$readmemh("crc8_table.mem", crc_table);
end
always @(posedge clk)
if (enable)
crc_out <= crc_table[crc_out ^ data_in];
Essa abordagem reduz drasticamente o uso de XORs em série, que são caros em termos de lógica combinacional, e aumenta a previsibilidade do tempo de propagação — ótimo para FPGAs pequenos com boa capacidade de BRAM.
📌 Uso Eficiente de Recursos
Dependendo do tamanho da FPGA e do número de blocos de CRC utilizados:
- Prefira módulos genéricos e parametrizáveis (
parameter CRC_WIDTH
,POLYNOMIO
) para reaproveitar a mesma lógica em múltiplos contextos. - Utilize Shift Registers em blocos LUT (SRL) para reduzir flip-flops.
- Considere mover partes menos críticas para lógica combinacional fora do caminho de tempo (ex: XOR final ou bit reflection).
📌 Clock e Reset
Em projetos de CRC para comunicação, é fundamental garantir:
- Sincronismo com o domínio de clock correto — especialmente se o dado vier de um domínio diferente (ex: FIFO, UART RX).
- Uso de resets síncronos ou assíncronos bem definidos, pois valores iniciais do CRC (geralmente 0 ou
0xFF
) afetam diretamente o valor final.
📌 CRC Seriais vs. Paralelos
Critério | CRC Serial (bit a bit) | CRC Paralelo (byte a byte) |
---|---|---|
Lógica | Simples | Moderadamente complexa |
Tempo por byte | Alto | Baixo (1 ciclo) |
Área (flip-flops) | Baixa | Moderada |
Uso em protocolos | UART, SPI lento | Ethernet, PCIe, AXI |
Facilidade de debug | Alta | Média |
📘 Capítulo 7 – Integração com Interfaces Seriais
A utilidade prática dos módulos de CRC e checksum em Verilog se concretiza quando integrados a interfaces de comunicação reais, como UART, SPI ou até protocolos internos customizados entre blocos de IP. Neste capítulo, mostramos como aplicar essas técnicas em FSMs, buffers, e fluxos de dados sequenciais, com exemplos estruturados.
📌 Enquadramento de Pacote
Um protocolo simples pode seguir o seguinte formato:
Campo | Tamanho | Descrição |
---|---|---|
Header | 8 bits | 0xAA (marcador de início) |
Payload | 32 bits | Dados úteis (ex: 4 bytes) |
Checksum | 8 bits | Soma dos dados |
O envio e recepção são controlados por uma FSM (Finite State Machine), que ativa o cálculo de verificação conforme os dados são recebidos.
🧪 Exemplo de FSM de Recepção UART com Checksum
module uart_rx_fsm (
input clk, rst,
input [7:0] rx_data,
input rx_valid,
output reg erro_checksum
);
reg [2:0] estado;
reg [7:0] dados[0:3]; // 4 bytes
reg [1:0] contador;
reg enable_checksum;
wire [7:0] checksum_out;
checksum8 checksum_mod (
.clk(clk), .rst(rst),
.enable(enable_checksum),
.done(estado == 3),
.data_in(rx_data),
.checksum_out(checksum_out)
);
always @(posedge clk) begin
if (rst) begin
estado <= 0;
contador <= 0;
enable_checksum <= 0;
erro_checksum <= 0;
end else if (rx_valid) begin
case (estado)
0: if (rx_data == 8'hAA) estado <= 1;
1: begin
dados[contador] <= rx_data;
enable_checksum <= 1;
if (contador == 3) begin
contador <= 0;
estado <= 2;
end else
contador <= contador + 1;
end
2: begin
enable_checksum <= 0;
if (rx_data != checksum_out)
erro_checksum <= 1;
estado <= 0;
end
endcase
end
end
endmodule
Esse exemplo mostra como ativar o cálculo de checksum dinamicamente, a cada byte válido recebido. Para CRC, basta trocar o módulo checksum8
por um módulo crc8_serial
ou crc8_parallel
.
📌 Integração com SPI
No SPI, os dados são frequentemente recebidos de forma síncrona e em blocos. O mestre pode incluir o campo CRC no final da transação, e o escravo o calcula internamente.
Estratégia:
- Capturar os bytes em um shift register ou buffer RAM
- Calcular o CRC ao final da transação
- Comparar o valor recebido com o CRC calculado
📌 Comunicação entre Blocos Internos (SoC)
Em sistemas com múltiplos blocos (como UART + controle de motor), o uso de CRC em barramentos internos ajuda a detectar falhas na interligação, especialmente quando há cross-clock domains.
💡 Recomenda-se aplicar o CRC no transmissor, armazenar os dados e o CRC em um FIFO, e validar no receptor com base em sinais valid
/ ready
.
📌 Sinalização de Erro
Erros de verificação devem gerar flags internas no sistema:
if (crc_out != crc_rx)
erro_crc <= 1;
else
erro_crc <= 0;
Esses sinais podem:
- Acionar LEDs para debug
- Gerar interrupções
- Resetar FSMs
- Inibir comandos críticos (ex: ativar motor)
📘 Capítulo 8 – Casos de Estudo e Simulação
Implementar checksum e CRC é apenas parte da tarefa. Para garantir sua eficácia, é fundamental validar a lógica com simulação, inspecionar sinais, injetar falhas e verificar a reação do sistema. Neste capítulo, mostramos como aplicar os módulos em testes reais usando ferramentas como ModelSim, Vivado e GTKWave.
📌 Estudo de Caso 1: Comunicação entre dois blocos internos
Contexto: Dois módulos em um FPGA (ex: UART e controle de atuador) trocam dados via um barramento local com verificação CRC-8.
📟 Transmissor (envia 4 bytes + CRC):
// bloco de controle envia dados com crc8_parallel
data_in = 8'h12; enable = 1; // dados
data_in = 8'h34; ...
data_in = 8'h56; ...
data_in = 8'h78; ...
data_in = crc8_resultado; // último byte: CRC
📟 Receptor (valida e compara CRC):
// módulo receptor
if (crc_out != recebido_crc)
erro <= 1;
📈 A simulação mostra erro = 1
quando o CRC é corrompido, e erro = 0
quando válido.
📌 Simulação com ModelSim/GTKWave
- Compile o módulo e o testbench:
vlog crc8_parallel.v tb_crc8_parallel.v vsim work.tb_crc8_parallel
- Rode a simulação e abra os sinais:
add wave -position end sim:/tb_crc8_parallel/* run 500ns
- Inspecione:
data_in
,crc_out
- Flags de erro (
erro
) - Fluxo do FSM
🔍 GTKWave: pode ser usado para simulações feitas no Icarus Verilog (iverilog
) com dump em .vcd
.
📌 Estudo de Caso 2: Injeção de Erros
Durante a simulação, podemos forçar um erro:
// Testbench - simula erro no último byte
data_in = 8'h78 ^ 8'hFF; // erro intencional
No GTKWave ou ModelSim, o sinal erro
deve ir para 1. Isso valida a sensibilidade do circuito à corrupção de dados.
📌 Estudo de Caso 3: Interface UART com Checksum
Simule a recepção de bytes com uma máquina de estados e valide com o módulo checksum8
. Use rx_valid
e rx_data
para alimentar a FSM, e observe o comportamento do checksum.
💡 Uma técnica útil é ligar um LED
interno (simulado) ou uma flag
para indicar falha:
led_erro <= erro_checksum;
📌 Estratégias de Debug
- Counters: Contar bytes recebidos ajuda a sincronizar pacotes.
- LEDs virtuais: Sinais que indicam falha podem ser conectados a GPIOs em placas reais.
- Log Serial: Em FPGAs com softcore, log via UART é útil para exibir CRCs calculados.
📌 Ferramentas recomendadas
Ferramenta | Função |
---|---|
ModelSim | Simulação avançada e debugging |
Vivado | Integração com hardware Xilinx |
GTKWave | Visualização rápida de sinais |
Icarus | Simulador open source leve |
LiteX | Softcores com CRC embutido |
📘 Capítulo 9 – Considerações Finais
A implementação de CRC e Checksum em Verilog é uma etapa fundamental no desenvolvimento de sistemas digitais confiáveis, especialmente quando se trata de comunicação entre blocos, interfaces seriais, ou integração de sensores e periféricos. Esses mecanismos protegem o sistema contra ruídos, glitches e falhas de sincronismo, garantindo que somente dados válidos sejam processados.
📌 Boas Práticas de Projeto
- Defina com clareza o protocolo de comunicação:
- Formato do frame (bytes de controle, dados, verificação)
- Posição do checksum/CRC
- Política de reação a erros (retransmissão, reset, bloqueio)
- Escolha o tipo de verificação com base no risco:
- Use checksum em sistemas simples ou internos ao chip
- Prefira CRC-8 ou CRC-16 para interfaces expostas ao ruído ou com dados críticos
- Utilize CRC-32 em streams contínuos de dados (ex: DMA, vídeo, Ethernet)
- Teste com vetores conhecidos e simulação completa:
- Compare resultados com ferramentas online
- Use testbenches com casos positivos e negativos
- Incremente FSMs para detectar erros e recuperar o estado
- Documente todas as características do CRC:
- Polinômio
- Valor inicial
- RefIn / RefOut
- XOR final
📌 Comparativo Final: Checksum vs. CRC
Critério | Checksum | CRC |
---|---|---|
Complexidade | Baixa | Moderada/Alta |
Recursos | Poucos registros e somadores | XORs e lógica sequencial |
Detecção de Erros | Básica (1 ou 2 erros) | Alta (múltiplos erros e rajadas) |
Aplicação típica | UART, controle simples | SPI, I2C, Ethernet, USB |
📌 Caminhos Avançados
Para aplicações ainda mais críticas ou robustas, considere o uso de:
- Código de Hamming: capaz de corrigir 1 bit e detectar 2
- ECC (Error Correcting Code): usado em memórias (SDRAM, Flash)
- Reed-Solomon: utilizado em CDs, satélites, QR codes e comunicação sem fio
- CRC com paralelismo total: permite cálculo de CRC em um único ciclo para vetores inteiros (ex: 32 bits por clock)
Essas técnicas podem ser combinadas a softcores RISC-V ou MicroBlaze, ou implementadas como IP blocks reutilizáveis.
📌 Conclusão
CRC e Checksum não são apenas ferramentas matemáticas — são barreiras defensivas em nível de hardware, que garantem a robustez, confiabilidade e segurança de sistemas digitais modernos. Saber implementá-los em Verilog é uma competência fundamental para engenheiros de FPGA, seja no desenvolvimento de IPs proprietários, protocolos customizados ou sistemas embarcados críticos.
Com este tutorial, você está agora preparado para:
- Criar seus próprios módulos de verificação
- Integrar lógica de integridade em FSMs e barramentos
- Simular e validar o comportamento de forma automatizada