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:
- O primeiro flip-flop recebe o sinal vindo do outro domínio.
- O segundo flip-flop amostra a saída do primeiro.
- 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
- Enviamos o bit para o novo domínio usando dois flip-flops.
- Comparamos a saída dos dois flip-flops.
- 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
eff2 == 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
// Lê 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 quedata
está pronto para ser lido;ack
: B → A, indica quedata
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:
- O domínio produtor usa uma FSM para enviar dados quando estiver pronto.
- O domínio receptor usa outra FSM para reconhecer, armazenar e liberar o dado.
- Um pequeno buffer pipeline permite manter uma janela de transferência ativa.
- 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:
- Transmissor com FSM no domínio A
- Buffer pipeline entre domínios
- 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;
// Lê 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
eclk_rd
são intencionalmente diferentes.- O
dumpfile
edumpvars
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
eclk_rd
- Os ponteiros da FIFO (se forem internos visíveis)
- Os sinais de
valid
,ack
,empty
,full
, etc. - A ordem dos dados
wr_data
erd_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:
- A ferramenta de síntese tenta “alinhar” os dois domínios, gerando otimizações perigosas.
- 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.