MCU.TEC FPGA Como Implementar Checksum e CRC em Verilog para FPGAs com Eficiência e Confiabilidade

Como Implementar Checksum e CRC em Verilog para FPGAs com Eficiência e Confiabilidade


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 + 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:

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érioCRC Serial (bit a bit)CRC Paralelo (byte a byte)
LógicaSimplesModeradamente complexa
Tempo por byteAltoBaixo (1 ciclo)
Área (flip-flops)BaixaModerada
Uso em protocolosUART, SPI lentoEthernet, PCIe, AXI
Facilidade de debugAltaMé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:

CampoTamanhoDescrição
Header8 bits0xAA (marcador de início)
Payload32 bitsDados úteis (ex: 4 bytes)
Checksum8 bitsSoma 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

  1. Compile o módulo e o testbench: vlog crc8_parallel.v tb_crc8_parallel.v vsim work.tb_crc8_parallel
  2. Rode a simulação e abra os sinais: add wave -position end sim:/tb_crc8_parallel/* run 500ns
  3. 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

FerramentaFunção
ModelSimSimulação avançada e debugging
VivadoIntegração com hardware Xilinx
GTKWaveVisualização rápida de sinais
IcarusSimulador open source leve
LiteXSoftcores 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

  1. 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)
  2. 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)
  3. 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
  4. Documente todas as características do CRC:
    • Polinômio
    • Valor inicial
    • RefIn / RefOut
    • XOR final

📌 Comparativo Final: Checksum vs. CRC

CritérioChecksumCRC
ComplexidadeBaixaModerada/Alta
RecursosPoucos registros e somadoresXORs e lógica sequencial
Detecção de ErrosBásica (1 ou 2 erros)Alta (múltiplos erros e rajadas)
Aplicação típicaUART, controle simplesSPI, 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

0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x