MCU.TEC geral Como Criar Gateways entre Domínios de Clock em FPGAs com Verilog: Do Básico à Síntese Segura

Como Criar Gateways entre Domínios de Clock em FPGAs com Verilog: Do Básico à Síntese Segura

Vamos explorar os conceitos fundamentais dos FPGAs (Field Programmable Gate Arrays), o problema da comunicação entre blocos com diferentes domínios de clock, e os motivos pelos quais precisamos de estruturas conhecidas como Clock Domain Crossing (CDC) e gateways síncronos e assíncronos. Isso prepara o terreno para a implementação prática que virá nos capítulos seguintes.


O que é um FPGA?

Um FPGA é um circuito integrado reconfigurável, composto por milhares (ou milhões) de blocos lógicos programáveis e interconectáveis. Eles são amplamente usados em aplicações embarcadas, automotivas, aeroespaciais e de telecomunicações devido à sua flexibilidade, paralelismo massivo e desempenho em tempo real.

Blocos principais de um FPGA:

  • CLBs (Configurable Logic Blocks): unidades de lógica reprogramáveis.
  • Flip-flops e LUTs (Look-Up Tables): para armazenar e calcular sinais digitais.
  • Módulos de I/O programáveis: conectam o FPGA ao mundo externo.
  • Blocos de Clock: geradores e divisores de clock distribuídos internamente.
  • Blocos de memória (BRAMs): usados para buffers, FIFOs e caches locais.

O problema dos Domínios de Clock

Em um sistema digital com múltiplos blocos funcionais, pode haver diferentes relógios (clocks) para cada subsistema. Por exemplo:

  • Um barramento AXI rodando a 100 MHz.
  • Um periférico SPI rodando a 25 MHz.
  • Um ADC que envia dados a 10 MHz.

Como cada domínio de clock opera com tempos distintos, transferir dados entre eles pode causar problemas como:

  • Metastabilidade
  • Perda de dados
  • Geração de glitches
  • Dados corrompidos

Clock Domain Crossing (CDC)

CDC é o conjunto de técnicas usadas para garantir uma transferência segura de dados entre dois domínios de clock.

As estratégias mais comuns são:

  • Sincronizadores de bit único (ex: dois flip-flops em série).
  • FIFOs assíncronos: com escrita e leitura em domínios de clock distintos.
  • Handshakes e sinais de valid e ready.

Formulação do Problema

Seja:

  • clkAclk_A: clock do domínio A
  • clkBclk_B: clock do domínio B
  • D(t)D(t): dado transmitido do domínio A para B

Queremos garantir que D(t)D(t) seja capturado no domínio B sem:

  • Ser perdido (overflow ou underflow),
  • Ser lido duas vezes,
  • Gerar sinais instáveis ou metastáveis.

Para isso, utilizamos buffers com controle de escrita e leitura sincronizados, detectores de borda e lógica de controle entre domínios.


Estrutura do Tutorial

Nos próximos capítulos, vamos aprender na prática como construir esses gateways usando Verilog. Cada capítulo conterá:

  • Descrição do problema específico
  • Explicação detalhada da solução
  • Código Verilog comentado
  • Fórmulas se necessário
  • Dicas para síntese e simulação

Ótimo! Vamos continuar com o Capítulo 2, onde abordaremos a técnica mais básica e amplamente utilizada para comunicação entre domínios de clock: o Sincronizador de Sinal Único com Dois Flip-Flops.


Capítulo 2 – Sincronizador de Sinal Único com Dois Flip-Flops

Objetivo do Capítulo

Apresentar a técnica fundamental de sincronização de sinais digitais entre dois domínios de clock usando dois flip-flops em série. Esta é uma solução simples, mas extremamente eficaz para sinais de pulso ou estados binários (bit único), muito usada em projetos com FPGAs.


Problema a ser resolvido

Quando um sinal binário muda em um domínio de clock e precisa ser lido em outro domínio, o risco de metastabilidade é alto. A metastabilidade ocorre quando um flip-flop é amostrado em uma transição, ficando em um estado indefinido por tempo indefinido. Isso pode causar falhas intermitentes e difíceis de depurar.


Solução com Dois Flip-Flops

A técnica clássica para minimizar a chance de metastabilidade é amostrar o sinal com dois flip-flops em série no domínio de destino.

Funcionamento:

  1. O primeiro flip-flop recebe o sinal vindo do outro domínio.
  2. O segundo flip-flop amostra a saída do primeiro.
  3. Ao final, o sinal estará estável, com chance extremamente baixa de metastabilidade.

Diagrama do Circuito

Domínio de Clock A                     Domínio de Clock B
     clk_a                                                        clk_b
       |                                                                   |
   +-------+                                         +------------+       +------------+
   | sinal |---------------------------> |     FF #1     | --> |     FF #2    | --> sinal_sync
   +-------+                                         +------------+       +------------+

Código Verilog Comentado

module sincronizador_bit_unico (
    input  wire clk_b,        // Clock do domínio de destino
    input  wire sinal_a,      // Sinal gerado no domínio A
    output wire sinal_sync    // Sinal sincronizado
);

    // Flip-flops internos
    reg ff1, ff2;

    // Processo sensível ao clock do domínio B
    always @(posedge clk_b) begin
        ff1 <= sinal_a;       // 1º flip-flop recebe o sinal diretamente
        ff2 <= ff1;           // 2º flip-flop amostra o valor estabilizado
    end

    // Saída sincronizada
    assign sinal_sync = ff2;

endmodule

Comentários Técnicos

  • Essa técnica é somente válida para sinais binários estáveis por mais de um ciclo de clock.
  • Não serve para transmitir dados paralelos ou pacotes inteiros — usaremos FIFOs para isso em capítulos futuros.
  • A chance de metastabilidade não é zero, mas a probabilidade é reduzida por um fator de e−T/τe^{-T/\tau}, onde:
    • TT: tempo entre os dois flip-flops (1 período de clk_b)
    • τ\tau: constante de tempo de resolução do flip-flop (tipicamente < 1ns)

Testbench Simples

module tb_sincronizador;
    reg clk_b = 0;
    reg sinal_a = 0;
    wire sinal_sync;

    sincronizador_bit_unico uut (
        .clk_b(clk_b),
        .sinal_a(sinal_a),
        .sinal_sync(sinal_sync)
    );

    always #5 clk_b = ~clk_b; // Clock de 100MHz (10ns)

    initial begin
        $display("Início do teste");
        #17 sinal_a = 1;
        #10 sinal_a = 0;
        #50 $finish;
    end
endmodule

Esse teste envia um pulso de 1 ciclo no sinal de entrada e observa a propagação segura no domínio de clock clk_b.



Capítulo 3 – Detecção de Pulso Sincronizado entre Domínios de Clock

Objetivo do Capítulo

Quando um sinal de pulso (ex: botão, evento de interrupção, bit de controle) é gerado em um domínio de clock, e precisa ser detectado por outro domínio, usar dois flip-flops em série não é suficiente. Isso porque o pulso pode ocorrer por apenas 1 ciclo de clock, e ser perdido se não estiver alinhado com o clock de destino. Neste capítulo, vamos resolver isso com uma técnica de detecção de borda sincronizada.


O Problema do Pulso Curto

Imagine um pulso de 1 ciclo de 10ns sendo enviado para um domínio com clock de 100ns. A chance de que o pulso seja completamente ignorado é alta.

Solução:

Transformar esse pulso em um evento que dure 1 ciclo no domínio de destino, independentemente do clock de origem.


Estratégia: Pulso com Detecção de Borda

  1. Enviamos o bit para o novo domínio usando dois flip-flops.
  2. Comparamos a saída dos dois flip-flops.
  3. Se houver diferença (transição de 0 para 1), geramos um novo pulso.

Diagrama do Circuito

Domínio de origem (clk_a)                Domínio de destino (clk_b)

 pulso --> flip-flop --+                 +----------+    +----------+
                                      |                  | FF1        |      | FF2       |
                                     +----------> |                | -> |               |
                                                        +----------+     +----------+
                                                                                     |
                                                                               sinal_sync (1 ciclo)    

Código Verilog Comentado

module sincronizador_pulso (
    input  wire clk_b,        // Clock do domínio de destino
    input  wire sinal_a,      // Pulso de entrada no domínio de origem (1 ciclo)
    output wire pulso_sync    // Pulso gerado no domínio de destino (1 ciclo)
);

    reg ff1 = 0, ff2 = 0;

    always @(posedge clk_b) begin
        ff1 <= sinal_a;       // Amostra sinal vindo do outro domínio
        ff2 <= ff1;           // Segunda amostragem para evitar metastabilidade
    end

    // Detecção de borda de subida: FF1 muda de 0 para 1
    assign pulso_sync = ff1 & ~ff2;

endmodule

Como Funciona

  • ff1 captura o valor atual do sinal.
  • ff2 retém o valor anterior.
  • Se ff1 == 1 e ff2 == 0, então ocorreu uma transição 0→1 → pulso gerado!

Testbench Simples

module tb_pulso_sync;
    reg clk_b = 0;
    reg sinal_a = 0;
    wire pulso_sync;

    sincronizador_pulso uut (
        .clk_b(clk_b),
        .sinal_a(sinal_a),
        .pulso_sync(pulso_sync)
    );

    always #5 clk_b = ~clk_b; // 100MHz

    initial begin
        $display("Teste de Pulso Sincronizado");
        #12 sinal_a = 1; // Pulso muito curto!
        #10 sinal_a = 0;
        #50 $finish;
    end
endmodule

Mesmo que sinal_a dure 1 ciclo apenas, o pulso será corretamente convertido para pulso_sync no domínio clk_b.


Aplicações Típicas

  • Botões físicos
  • Flags de status
  • Eventos que precisam ser reconhecidos entre clocks diferentes
  • Comunicação entre FSMs (máquinas de estado) de clock distinto


Capítulo 4 – FIFO Assíncrona para Transferência de Dados entre Clocks

Objetivo do Capítulo

Apresentar a estrutura e o funcionamento de uma FIFO assíncrona (First In, First Out) capaz de conectar dois blocos lógicos operando com clocks diferentes. A FIFO permite a escrita e leitura de dados com controle de integridade, evitando perda ou duplicação, e é amplamente usada em sistemas de comunicação como UART, SPI, Ethernet e DMA.


O Problema da Transferência de Dados

Diferente dos capítulos anteriores, aqui queremos transmitir palavras de dados completas (ex: 8, 16, 32 bits) entre dois subsistemas com clocks distintos:

  • Um produtor escreve os dados (clock A),
  • Um consumidor lê os dados (clock B),
  • E queremos:
    • Manter a ordem dos dados,
    • Evitar sobrescrita (overflow) ou leitura de dados inválidos (underflow),
    • Garantir integridade e estabilidade.

Solução: FIFO com Ponte de Clocks

A FIFO assíncrona contém:

  • Buffer de memória circular (array de registradores),
  • Ponteiros de leitura e escrita (read_ptr, write_ptr),
  • Sincronizadores de ponteiros cruzados entre domínios, para controle de status,
  • Lógica de controle de “empty” e “full”.

Diagrama Conceitual

                  clk_wr                                 clk_rd
                 (escrita)                               (leitura)
                +---------+                 +--------------+                +------------+
DATA  | Buffer  | <=====> |   Ponteiros  | <====> |  LEITURA |
                +---------+                 +--------------+                +------------+
                                                           |          |
                                      Sync read_ptr  Sync write_ptr
                                      (para clk_wr)    (para clk_rd)

Código Verilog Simplificado (FIFO 4×8 bits)

module fifo_assincrona #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 2  // 2^2 = 4 posições
)(
    input wire                  clk_wr, clk_rd,
    input wire                  rst,
    input wire                  wr_en, rd_en,
    input wire [DATA_WIDTH-1:0] wr_data,
    output reg [DATA_WIDTH-1:0] rd_data,
    output wire                 empty, full
);

    localparam DEPTH = (1 << ADDR_WIDTH);

    // Memória da FIFO
    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

    // Ponteiros
    reg [ADDR_WIDTH:0] wr_ptr = 0;
    reg [ADDR_WIDTH:0] rd_ptr = 0;

    // Sincronização cruzada
    reg [ADDR_WIDTH:0] rd_ptr_wr_clk = 0;
    reg [ADDR_WIDTH:0] wr_ptr_rd_clk = 0;

    // Escreve no domínio clk_wr
    always @(posedge clk_wr) begin
        if (rst)
            wr_ptr <= 0;
        else if (wr_en && !full) begin
            mem[wr_ptr[ADDR_WIDTH-1:0]] <= wr_data;
            wr_ptr <= wr_ptr + 1;
        end
    end

    //  no domínio clk_rd
    always @(posedge clk_rd) begin
        if (rst)
            rd_ptr <= 0;
        else if (rd_en && !empty) begin
            rd_data <= mem[rd_ptr[ADDR_WIDTH-1:0]];
            rd_ptr <= rd_ptr + 1;
        end
    end

    // Sincroniza rd_ptr para clk_wr
    reg [ADDR_WIDTH:0] rd_ptr_wr_clk_1, rd_ptr_wr_clk_2;
    always @(posedge clk_wr) begin
        rd_ptr_wr_clk_1 <= rd_ptr;
        rd_ptr_wr_clk_2 <= rd_ptr_wr_clk_1;
    end

    // Sincroniza wr_ptr para clk_rd
    reg [ADDR_WIDTH:0] wr_ptr_rd_clk_1, wr_ptr_rd_clk_2;
    always @(posedge clk_rd) begin
        wr_ptr_rd_clk_1 <= wr_ptr;
        wr_ptr_rd_clk_2 <= wr_ptr_rd_clk_1;
    end

    // Status
    assign full  = (wr_ptr[ADDR_WIDTH]     != rd_ptr_wr_clk_2[ADDR_WIDTH]) &&
                   (wr_ptr[ADDR_WIDTH-1:0] == rd_ptr_wr_clk_2[ADDR_WIDTH-1:0]);

    assign empty = (rd_ptr == wr_ptr_rd_clk_2);

endmodule

Fórmulas e Observações

Detecção de FIFO cheia:

\[full = (MSB(wr_ptr) ≠ MSB(synced_rd_ptr)) AND (LSBs(wr_ptr) == LSBs(synced_rd_ptr))\]

Essa fórmula garante que a FIFO só seja considerada cheia quando os ponteiros se encontrarem e a volta do buffer tiver sido completada (overflow iminente).

Detecção de FIFO vazia:

\[empty = (rd_ptr == synced_wr_ptr)\]

Ou seja, nenhum dado novo foi escrito além do que já foi lido.


Aplicações

  • UARTs com DMA
  • Comunicação entre blocos de diferentes frequências
  • Processamento de áudio/vídeo em pipelines
  • Leitura/Escrita entre módulos AXI e SPI/I2C


Capítulo 5 – Gateway Bidirecional com Controle de Handshake

Objetivo do Capítulo

Até agora, vimos formas de enviar dados unidirecionalmente entre domínios de clock. Neste capítulo, exploramos um modelo de comunicação bidirecional em que cada lado participa ativamente da troca, utilizando sinais de validação e confirmação — técnica conhecida como handshake. Ela garante que:

  • Dados só sejam enviados quando o receptor estiver pronto.
  • Nenhum dado seja perdido, mesmo com clocks muito diferentes.

O Problema da Comunicação Bidirecional

Vamos supor que temos dois domínios:

  • Domínio A: produz dados esporadicamente.
  • Domínio B: processa os dados, mas pode estar ocupado ou lento.

Se A simplesmente jogar os dados para B, há risco de:

  • Sobrescrever dados ainda não lidos;
  • B ler dados inválidos;
  • Corrupção de dados.

Solução: Handshake com Valid e Ack

A estratégia usa três sinais principais:

  • data: o dado sendo enviado;
  • valid: A → B, indica que data está pronto para ser lido;
  • ack: B → A, indica que data foi lido com sucesso.

Diagrama Conceitual

     Domínio A                        Domínio B
     -----------                                    -----------
    |                |                                   |             |
    |  DATA    | --->--- data ------> |             |
    |  VALID   | --->--- valid ------> |            |
    |                | <------   ack   <----- |             |
    |-----------|                                   |---------|

Código Verilog – Emissor (Domínio A)

module emissor (
    input  wire        clk_a,
    input  wire        rst,
    input  wire        start,
    input  wire [7:0]  dado,
    output reg  [7:0]  data_out,
    output reg         valid,
    input  wire        ack
);

    always @(posedge clk_a or posedge rst) begin
        if (rst) begin
            valid <= 0;
        end else begin
            if (start && !valid) begin
                data_out <= dado;
                valid <= 1;
            end else if (valid && ack) begin
                valid <= 0; // Dado foi reconhecido
            end
        end
    end

endmodule

Código Verilog – Receptor (Domínio B)

module receptor (
    input  wire        clk_b,
    input  wire        rst,
    input  wire        valid,
    input  wire [7:0]  data_in,
    output reg         ack,
    output reg  [7:0]  dado_recebido
);

    reg valid_d1;

    always @(posedge clk_b or posedge rst) begin
        if (rst) begin
            ack <= 0;
            valid_d1 <= 0;
        end else begin
            valid_d1 <= valid;

            // Detecta borda de subida de 'valid'
            if (valid && !valid_d1) begin
                dado_recebido <= data_in;
                ack <= 1;
            end else begin
                ack <= 0;
            end
        end
    end

endmodule

Observações

  • Esse protocolo funciona bem com sinais de pulso e dados pequenos.
  • Ambos os domínios precisam de detectores de borda (como vimos no Cap. 3) para evitar múltiplas capturas ou envios.
  • Esse modelo também pode ser expandido para transações maiores, com FSMs controlando os estágios.

Vantagens

  • Comunicação robusta mesmo com clocks muito distintos.
  • Controle claro de início e fim de transações.
  • Evita buffers intermediários quando não há muito fluxo contínuo de dados.

Aplicações

  • Protocolo ponto-a-ponto entre processadores e periféricos.
  • Troca de eventos entre FSMs.
  • Interfaces customizadas de sensores com microcontroladores.
  • Comunicação de controle entre blocos com FSMs autônomas.

Excelente! Agora chegamos a um estágio avançado da construção de gateways entre domínios de clock. Neste capítulo, aprenderemos como usar máquinas de estados finitos (FSMs) e técnicas de pipeline para estruturar transferências mais complexas e eficientes entre domínios com diferentes clocks.


Capítulo 6 – Gateway Multi-Clock com FSMs e Pipeline entre Domínios

Objetivo do Capítulo

Apresentar uma arquitetura robusta para transferência de dados contínua entre dois domínios de clock distintos usando FSMs sincronizadas e buffers pipeline. Essa abordagem é útil quando há:

  • Múltiplas palavras de dados sendo transmitidas em sequência;
  • Controle mais sofisticado de fluxo (start, busy, done);
  • Interações com módulos de memória, DMA, ou protocolos externos.

Problema a ser resolvido

Considere um sistema onde:

  • Um produtor envia vários dados sucessivos (ex: aquisição de amostras);
  • Um consumidor mais lento precisa processar, armazenar ou responder;
  • O sincronismo entre esses eventos deve ser mantido sem perda.

Aqui, simples FIFOs ou handshakes diretos não são suficientes. Precisamos de:

  • Controle de início de transmissão (start),
  • Detecção de ocupação (busy),
  • Sinalização de dado pronto (valid) e consumo completo (done).

Solução: FSMs + Pipeline + Handshake Estendido

Estratégia:

  1. O domínio produtor usa uma FSM para enviar dados quando estiver pronto.
  2. O domínio receptor usa outra FSM para reconhecer, armazenar e liberar o dado.
  3. Um pequeno buffer pipeline permite manter uma janela de transferência ativa.
  4. Sinais de controle atravessam os domínios com sincronizadores.

Diagrama Simplificado

[Domínio A - clk_a]                              [Domínio B - clk_b]
      FSM_tx                                                       FSM_rx
     -----------                                                      ---------------
    |                | --> dado_valid --> sync -->   |                    |
    | ENVIA   | ---------------------------------> | RECEBE   |
    |                | <-- dado_ready <-- sync <-- |                    |
     -----------                                                      ---------------

Estrutura Geral do Módulo

Vamos estruturar o exemplo em três partes:

  1. Transmissor com FSM no domínio A
  2. Buffer pipeline entre domínios
  3. Receptor com FSM no domínio B

Código Verilog – Transmissor com FSM (Domínio A)

module transmissor_fsm (
    input wire clk_a, rst,
    input wire start, // pulso de início
    output reg [7:0] dado,
    output reg valid,
    input wire ready // vindo do receptor
);

    typedef enum reg [1:0] {
        IDLE, LOAD, WAIT_ACK
    } state_t;

    state_t estado = IDLE;

    always @(posedge clk_a or posedge rst) begin
        if (rst) begin
            estado <= IDLE;
            valid <= 0;
        end else begin
            case (estado)
                IDLE:
                    if (start) estado <= LOAD;

                LOAD: begin
                    dado <= $random; // ou dado externo
                    valid <= 1;
                    estado <= WAIT_ACK;
                end

                WAIT_ACK:
                    if (ready) begin
                        valid <= 0;
                        estado <= IDLE;
                    end
            endcase
        end
    end
endmodule

Código Verilog – Sincronizador Bidirecional + Pipeline

module buffer_pipeline (
    input  wire clk_a, clk_b, rst,
    input  wire valid_a,
    input  wire [7:0] dado_a,
    output wire ready_a,
    output reg  valid_b,
    output reg  [7:0] dado_b,
    input  wire ready_b
);

    reg [7:0] buffer;
    reg       ocupado = 0;

    // Escrita no domínio A
    assign ready_a = ~ocupado;

    always @(posedge clk_a) begin
        if (valid_a && ~ocupado) begin
            buffer <= dado_a;
            ocupado <= 1;
        end
    end

    // Leitura no domínio B
    always @(posedge clk_b) begin
        if (rst) begin
            valid_b <= 0;
        end else if (ocupado && ready_b) begin
            dado_b <= buffer;
            valid_b <= 1;
            ocupado <= 0;
        end else begin
            valid_b <= 0;
        end
    end

endmodule

Código Verilog – Receptor com FSM (Domínio B)

module receptor_fsm (
    input wire clk_b, rst,
    input wire valid,
    input wire [7:0] dado,
    output reg ready,
    output reg [7:0] buffer_final
);

    typedef enum reg [1:0] {
        ESPERANDO, RECEBENDO, PROCESSANDO
    } estado_t;

    estado_t estado = ESPERANDO;

    always @(posedge clk_b or posedge rst) begin
        if (rst) begin
            estado <= ESPERANDO;
            ready <= 0;
        end else begin
            case (estado)
                ESPERANDO:
                    if (valid) begin
                        estado <= RECEBENDO;
                        ready <= 1;
                    end

                RECEBENDO: begin
                    buffer_final <= dado;
                    estado <= PROCESSANDO;
                    ready <= 0;
                end

                PROCESSANDO:
                    estado <= ESPERANDO;
            endcase
        end
    end
endmodule

Vantagens da Arquitetura FSM + Pipeline

  • Transferência controlada, sem perda de dados mesmo em bursts.
  • Fácil expansão para largura maior de dados.
  • Permite múltiplos estágios de controle e bufferamento.
  • Ideal para interfaces de streaming, captura de sensores ou pipelines de vídeo/áudio.

Aplicações

  • Comunicação entre CPU e periféricos com clocks independentes.
  • Protocolos seriais baseados em pacotes (SPI, I2C com buffer).
  • Gateways de dados entre chips ou FPGAs.
  • Máquinas de aquisição de dados com lógica de controle complexa.


Capítulo 7 – Testes e Simulações de Gateways CDC com Verilog Testbenches

Objetivo do Capítulo

Ensinar como escrever testbenches eficazes em Verilog para verificar a funcionalidade de gateways entre domínios de clock. Vamos explorar:

  • Criação de clocks distintos,
  • Geração de estímulos assíncronos,
  • Detecção de falhas como perda de pulso ou travamentos,
  • Visualização com waves (gtkwave),
  • Técnicas de verificação simples, como scoreboards e flags.

O Desafio dos Testes CDC

Os sistemas com múltiplos clocks introduzem nondeterminismo: o comportamento varia dependendo da defasagem entre os domínios.

Por isso, é fundamental:

  • Simular com clocks reais de diferentes frequências,
  • Repetir testes com diferentes delays de fase,
  • Observar os sinais internos com cuidado.

Exemplo 1: Testbench para FIFO Assíncrona

Vamos usar a fifo_assincrona do Capítulo 4.

Testbench

module tb_fifo_assincrona;

    reg clk_wr = 0, clk_rd = 0, rst = 1;
    reg wr_en = 0, rd_en = 0;
    reg [7:0] wr_data = 0;
    wire [7:0] rd_data;
    wire empty, full;

    fifo_assincrona dut (
        .clk_wr(clk_wr),
        .clk_rd(clk_rd),
        .rst(rst),
        .wr_en(wr_en),
        .rd_en(rd_en),
        .wr_data(wr_data),
        .rd_data(rd_data),
        .empty(empty),
        .full(full)
    );

    // Clocks com frequências diferentes
    always #5  clk_wr = ~clk_wr; // 100MHz
    always #8  clk_rd = ~clk_rd; // 62.5MHz

    initial begin
        $dumpfile("fifo.vcd");
        $dumpvars(0, tb_fifo_assincrona);
        #20 rst = 0;

        // Envia 4 dados
        repeat (4) begin
            @(posedge clk_wr);
            if (!full) begin
                wr_data = $random;
                wr_en = 1;
            end
        end
        wr_en = 0;

        //  os dados
        repeat (4) begin
            @(posedge clk_rd);
            if (!empty) rd_en = 1;
        end
        rd_en = 0;

        #100 $finish;
    end
endmodule

Observações

  • clk_wr e clk_rd são intencionalmente diferentes.
  • O dumpfile e dumpvars são usados para gerar o arquivo de simulação .vcd que pode ser visualizado com GTKWave.
  • As verificações de !full e !empty evitam problemas de sobrescrita e leitura inválida.

Exemplo 2: Detectando Falhas com Flags

Uma técnica simples é usar flags de erro nos testbenches:

reg erro_overflow = 0;

always @(posedge clk_wr) begin
    if (wr_en && full) begin
        erro_overflow <= 1;
        $display("Erro: overflow na FIFO em %t", $time);
    end
end

Outros erros a detectar:

  • Underflow: rd_en && empty
  • Dados fora de ordem
  • Dados ausentes

Dicas de Verificação com Scoreboard

Você pode criar um vetor reg [7:0] esperado[0:3]; para armazenar os dados enviados e, na leitura, verificar se os valores batem com os esperados:

integer i = 0;

always @(posedge clk_rd) begin
    if (rd_en && !empty) begin
        if (rd_data !== esperado[i]) begin
            $display("Erro: dado inválido em %t. Esperado: %h, Recebido: %h", $time, esperado[i], rd_data);
        end
        i = i + 1;
    end
end

Visualização com GTKWave

Após rodar o testbench:

gtkwave fifo.vcd

Você poderá visualizar:

  • Os clocks clk_wr e clk_rd
  • Os ponteiros da FIFO (se forem internos visíveis)
  • Os sinais de valid, ack, empty, full, etc.
  • A ordem dos dados wr_data e rd_data

Isso ajuda a validar:

  • Sincronismo entre eventos
  • Ausência de metastabilidade
  • Correção da lógica de controle

Conclusão

A simulação de gateways CDC exige:

  • Clocks realistas e distintos
  • Testes repetitivos e agressivos
  • Observação dos sinais internos
  • Monitoramento do fluxo de dados

Verificar esses circuitos via simulação é uma etapa crítica antes da síntese e uso em hardware real.



Capítulo 8 – Boas Práticas de CDC para Síntese e FPGA (Constraints e Dicas de Timing)

Objetivo do Capítulo

Ensinar como aplicar boas práticas para síntese e implementação de circuitos com múltiplos domínios de clock (CDC) em FPGAs. Vamos abordar:

  • Diretrizes para evitar erros de timing,
  • Como informar corretamente à ferramenta que há dois domínios distintos,
  • Uso de false paths, multicycle paths e atributos especiais,
  • Técnicas para minimizar metastabilidade e facilitar a verificação do projeto.

Problema a ser resolvido

Os gateways funcionam bem em simulação, mas ao sintetizá-los, os erros podem surgir por dois motivos principais:

  1. A ferramenta de síntese tenta “alinhar” os dois domínios, gerando otimizações perigosas.
  2. A análise de tempo (timing analysis) gera falsos erros por não saber que certos caminhos cruzam domínios.

Isso pode levar a:

  • Metastabilidade real não tratada;
  • Violação de setup e hold times;
  • Otimizações perigosas que removem flip-flops sincronizadores.

Boas Práticas de Projeto CDC

1. Use sempre dois ou mais flip-flops em série para sinais de controle entre domínios

  • Exemplo clássico de sincronizador (Cap. 2): always @(posedge clk_b) {ff2, ff1} <= {ff1, sinal};

2. Nunca envie sinais de pulso diretamente entre domínios

  • Sempre transforme em nível sustentado e gere bordas no domínio de destino.

3. Evite lógica combinacional entre flip-flops de domínios diferentes

  • Ex: assign out = flipflop_A ^ flipflop_B; pode gerar glitches perigosos.

Uso de Constraints no FPGA

1. Defina corretamente os clocks

No arquivo de constraints (ex: .xdc para Xilinx):

create_clock -name clk_a -period 10.0 [get_ports clk_a]
create_clock -name clk_b -period 16.0 [get_ports clk_b]

2. Ignore caminhos entre domínios

set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
set_false_path -from [get_clocks clk_b] -to [get_clocks clk_a]

Isso diz à ferramenta para não tentar fechar o tempo entre domínios, pois a sincronização é feita manualmente por você.

3. Use atributos para preservar flip-flops

No Verilog:

(* ASYNC_REG = "TRUE" *) reg ff1, ff2;

Esse atributo:

  • Informa à ferramenta que os registros são parte de um sincronizador CDC;
  • Evita que sejam removidos ou otimizados incorretamente.

Verificação com Ferramentas

Ferramentas de análise CDC:

  • Xilinx Vivado: tem verificação integrada de CDC (Tcl: report_cdc)
  • Intel Quartus: usa TimeQuest para constraints e pode integrar ferramentas externas
  • SpyGlass CDC: ferramenta profissional de análise estática
  • Verificadores formais: Symbiyosys, JasperGold

Redução de Metastabilidade

A probabilidade de falha por metastabilidade pode ser reduzida com:

\[P_{fail} = f_{clk} \cdot f_{data} \cdot e^{-T_{res} / \tau}\]

Onde:

  • \(f_{clk}\) = frequência do clock de destino
  • \(f_{data}\) = frequência de transições do sinal de entrada
  • \(T_{res}\) = tempo entre os dois flip-flops
  • τ = constante de tempo de resolução do flip-flop

Conclusão: usar flip-flops rápidos, clocks menores e maior tempo de resolução ajuda a reduzir o risco.


Dicas Finais

✅ Sempre documente onde estão os domínios de clock distintos.
✅ Mantenha lógica de controle totalmente dentro de um domínio.
✅ Sempre sincronize sinais cruzando clock boundaries.
✅ Verifique simulação com .vcd + gtkwave e análise de tempo no P&R.
✅ Use assertions em simulações formais para verificar comportamento CDC.


Conclusão do Tutorial

Com este último capítulo, você está apto a:

  • Criar gateways seguros entre domínios de clock,
  • Implementar técnicas como flip-flops, FIFO, handshakes e pipelines,
  • Simular e verificar essas estruturas com confiança,
  • Sintetizá-las e rodá-las em FPGA com integridade e estabilidade garantidas.

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

Lançamento do site Basicão dos Microcontroladores: um novo espaço para aprender sobre microcontroladoresLançamento do site Basicão dos Microcontroladores: um novo espaço para aprender sobre microcontroladores

Lançamento do site Basicão dos Microcontroladores: um novo espaço para aprender sobre microcontroladores Com grande entusiasmo, anunciamos o lançamento do Basicão dos Microcontroladores, acessível pelo endereço https://mcu.tec.br. Este novo site

0
Adoraria saber sua opinião, comente.x