리미창고

[CDC] 3_Single Bit CDC: Simulation Result 본문

VLSI/Design

[CDC] 3_Single Bit CDC: Simulation Result

리미와감자 2025. 12. 7. 21:42

이번에는 2-FF Synchronizer, Pulse Stretching, Handshake, Toggle Synchronizer 4가지 Single bit Synchronizer에 대해 직접 RTL을 구현하고 시뮬레이션해볼 예정이다.

 

2-FF Synchronizer

 

Level Signal

 

Level Signal은 문제없이 clk_src(송신 도메인)에서 clk_dst(수신 도메인)으로 잘 넘어간다.

 

Code

`timescale 1ns/1ps

module two_ff_synchronizer_tb;

  // Clock signals
  reg clk_src = 0;  // 송신 도메인
  reg clk_dst = 0;  // 수신 도메인

  // Active Low Reset signals
  reg rst_src_n = 0;
  reg rst_dst_n = 0;

  // Input level signal in source domain
  reg async_level = 0;

  // Synchronized output in destination domain
  wire sync_out;

  // Clock generation
  always #5 clk_src = ~clk_src;   // Source clock
  always #15 clk_dst = ~clk_dst;  // Destination clock

  // Release reset after some time
  initial begin
    #20 rst_src_n = 1;   // Deassert reset
    #20 rst_dst_n = 1;
  end

  // Level signal generation in source domain
  initial begin
    #30 async_level = 1;    // Assert level
    #100 async_level = 0;   // Deassert level
    #50  async_level = 1;   // Assert again
    #100 async_level = 0;   // Deassert
    #200 $finish;
  end

  // Instantiate 2-FF Synchronizer
  two_ff_sync u_sync (
    .clk_dst(clk_dst),
    .rst_n(rst_dst_n),
    .async_in(async_level),
    .sync_out(sync_out)
  );

  // Waveform dump for EDA Playground
  initial begin
    $dumpfile("two_ff_sync.vcd");
    $dumpvars(0, two_ff_synchronizer_tb);
  end

endmodule

// 2-FF Synchronizer module
module two_ff_sync(
  input  wire clk_dst,
  input  wire rst_n,       // Active Low Reset
  input  wire async_in,
  output wire sync_out
);
  reg ff1, ff2;

  always_ff @(posedge clk_dst or negedge rst_n) begin
    if (!rst_n) begin
      ff1 <= 0;
      ff2 <= 0;
    end else begin
      ff1 <= async_in;
      ff2 <= ff1;
    end
  end

  assign sync_out = ff2;

endmodule

 

Pulse Signal

 

이번에는 Fast → Slow 상황에서 Pulse Signal을 넘기는 경우이다.

 

  • Fast clock period = 10ns
  • Slow clock period = 40ns

시뮬레이션 결과를 보면 clock frequency가 느린 수신 도메인에서 받지 못하는 것을 볼 수 있다. 그럼 이제 어떻게 해야할까. 이 pulse 신호는 매우 중요한 신호라서 반드시 sampling을 해야한다.

 

Code

`timescale 1ns/1ps

module pulse_2ff_tb;

  // Clocks
  reg clk_src = 0;  // 송신(Fast)
  reg clk_dst = 0;  // 수신(Slow)

  // Active-low resets
  reg rst_src_n = 0;
  reg rst_dst_n = 0;

  // Fast domain pulse signal
  reg async_pulse = 0;

  // Synchronized pulse output in slow domain
  wire sync_out;

  // Clock generation
  always #5 clk_src = ~clk_src;   // Fast clock period = 10ns
  always #20 clk_dst = ~clk_dst;  // Slow clock period = 40ns

  // Release resets
  initial begin
    #15 rst_src_n = 1;
    #15 rst_dst_n = 1;
  end

  // Generate pulse aligned to clk_src rising edge
  initial begin
    #25 async_pulse = 1;
    #10 async_pulse = 0;
    #60 async_pulse = 1;
    #10 async_pulse = 0;
    #200 $finish;
  end

  // Instantiate 2-FF Synchronizer for pulse
  pulse_2ff u2ff (
    .clk_dst(clk_dst),
    .rst_n(rst_dst_n),
    .async_in(async_pulse),
    .sync_out(sync_out)
  );

  // Waveform dump
  initial begin
    $dumpfile("pulse_2ff.vcd");
    $dumpvars(0, pulse_2ff_tb);
  end

endmodule

// 2-FF Synchronizer (single bit)
module pulse_2ff(
  input  wire clk_dst,
  input  wire rst_n,
  input  wire async_in,
  output wire sync_out
);
  reg ff1, ff2;

  always_ff @(posedge clk_dst or negedge rst_n) begin
    if (!rst_n) begin
      ff1 <= 0;
      ff2 <= 0;
    end else begin
      ff1 <= async_in;
      ff2 <= ff1;
    end
  end

  assign sync_out = ff2;
endmodule

 

 

Pulse Stretching

 

우선 가장 직관적인 방법이 Pulse → Level로 Streching 하는 방식이다. Pulse는 위험하니 Level로 바꿔서 해보자는게 기본적인 아이디어이다.

 

 

 

Code

`timescale 1ns/1ps

module pulse_stretch_tb;

  // Clocks
  reg clk_src = 0;  // 송신(Fast)
  reg clk_dst = 0;  // 수신(Slow)

  // Active-low resets
  reg rst_src_n = 0;
  reg rst_dst_n = 0;

  // Fast domain pulse
  reg async_pulse = 0;

  // Synchronized pulse output in slow domain
  wire sync_out;

  // Clock generation
  always #5 clk_src = ~clk_src;   // Fast clock period = 10ns
  always #20 clk_dst = ~clk_dst;  // Slow clock period = 40ns

  // Release resets
  initial begin
    #5 rst_src_n = 1;
    #5 rst_dst_n = 1;
  end

  // Generate pulse aligned to clk_src rising edge
  initial begin
    #25 async_pulse = 1;    // First pulse
    #10 async_pulse = 0;
    #100 async_pulse = 1;   // Second pulse
    #10 async_pulse = 0;
    #200 $finish;
  end

  // Instantiate Pulse Stretching module
  pulse_stretch u_stretch (
    .clk_src(clk_src),
    .clk_dst(clk_dst),
    .rst_n(rst_dst_n),
    .async_in(async_pulse),
    .sync_out(sync_out)
  );

  // Waveform dump
  initial begin
    $dumpfile("pulse_stretch.vcd");
    $dumpvars(0, pulse_stretch_tb);
  end

endmodule

// Pulse Stretching module with hold time
module pulse_stretch #(
  parameter HOLD_CYCLES = 6 // Fast clock cycles to hold pulse
)(
  input  wire clk_src,      // Fast domain clock
  input  wire clk_dst,      // Slow domain clock
  input  wire rst_n,        // Active low reset
  input  wire async_in,     // Pulse signal in fast domain
  output wire sync_out      // Synchronized pulse in slow domain
);

  // 2-FF Synchronizer for slow domain
  reg ff1, ff2;

  // Pulse stretching in Fast domain
  reg [$clog2(HOLD_CYCLES)-1:0] hold_cnt;
  reg pulse_latch;

  always_ff @(posedge clk_src or negedge rst_n) begin
    if (!rst_n) begin
      pulse_latch <= 0;
      hold_cnt    <= 0;
    end else if (async_in) begin
      pulse_latch <= 1;
      hold_cnt    <= HOLD_CYCLES - 1;
    end else if (hold_cnt != 0) begin
      pulse_latch <= 1;
      hold_cnt    <= hold_cnt - 1;
    end else begin
      pulse_latch <= 0;
    end
  end

  // 2-FF Synchronizer in Slow domain
  always_ff @(posedge clk_dst or negedge rst_n) begin
    if (!rst_n) begin
      ff1 <= 0;
      ff2 <= 0;
    end else begin
      ff1 <= pulse_latch;
      ff2 <= ff1;
    end
  end

  assign sync_out = ff2;

endmodule

 

위 코드를 보면 HOLD_CYCLES을 6으로 설정하였다. pulse streching 기법은 매우 직관적이고 단순하지만, clock의 spec이 바뀔 때마다 매번 계산해서 바꿔야한다는 단점이 있다. 또한 내 코드를 다른 사람에게 공유할 때마다 매번 말해주어야 할 것이다. 

 

 

Handshake

다음은 Handshake 기법이다. 가장 많이 사용하는 CDC 기법이다.

 

 

 

  • pulse_in: Fast domain에서 들어오는 1-bit pulse 신호.
  • req: slow domain으로 보낼 요청(Request) 신호.
  • req_latched: pulse가 이미 req로 변환되어 ack를 기다리는 상태를 잠그는 용도.
  • ack_sync: slow domain에서 보내진 ack 신호를 fast domain에서 동기화한 것.
  • ff1, ff2: 2-FF Synchronizer로 req 신호를 slow domain으로 안전하게 전달.
  • Rising edge 검출: ff1이 1이고 ff2가 0이면 slow domain에서 pulse 생성.
  • pulse_out: slow domain에서 1-clock width pulse 생성.
  • ack_ff1, ack_ff2: ack 신호를 2-FF로 metastability 격리 후 fast domain으로 전달.
  • ack_sync: fast domain에서 pulse_in 처리 후 req를 내릴 때 사용.
  1. Fast → Slow pulse 전달
    • pulse_in 들어오면 req = 1
    • slow domain에서 2-FF로 안전하게 req 전달
    • slow domain에서 rising edge 검출 → pulse_out 생성
    • ack 생성 → fast domain으로 동기화
  2. Pulse 손실 방지
    • ack가 오기 전까지 새로운 pulse_in은 무시 (req_latched)
  3. Metastability 격리
    • req → ff1/ff2
    • ack → ack_ff1/ack_ff2

 

Code

`timescale 1ns/1ps

module handshake_tb;

  // Clocks
  reg clk_src = 0;  // Fast (송신)
  reg clk_dst = 0;  // Slow (수신)

  // Active-low resets
  reg rst_src_n = 0;
  reg rst_dst_n = 0;

  // Fast domain pulse
  reg async_pulse = 0;

  // Synchronized pulse output in slow domain
  wire sync_out;

  // Clock generation
  always #5 clk_src = ~clk_src;   // Fast clock = 10ns
  always #20 clk_dst = ~clk_dst;  // Slow clock = 40ns

  // Release resets
  initial begin
    #10 rst_src_n = 1;
    #10 rst_dst_n = 1;
  end

  // Generate pulses in fast domain
  initial begin
    #25 async_pulse = 1;
    #10 async_pulse = 0;
    #120async_pulse = 1;
    #10 async_pulse = 0;
    #200 $finish;
  end

  // Instantiate Handshake CDC
  handshake_cdc u_handshake (
    .clk_src(clk_src),
    .clk_dst(clk_dst),
    .rst_src_n(rst_src_n),
    .rst_dst_n(rst_dst_n),
    .pulse_in(async_pulse),
    .pulse_out(sync_out)
  );

  // Waveform dump
  initial begin
    $dumpfile("handshake.vcd");
    $dumpvars(0, handshake_tb);
  end

endmodule

module handshake_cdc(
  input  wire clk_src,     // Fast domain
  input  wire clk_dst,     // Slow domain
  input  wire rst_src_n,   // Active low reset for fast domain
  input  wire rst_dst_n,   // Active low reset for slow domain
  input  wire pulse_in,    // Fast domain pulse input
  output wire pulse_out    // Slow domain synchronized pulse
);

  //-----------------------
  // Fast domain logic
  //-----------------------
  reg req, req_latched;
  wire ack_sync;  // Synchronized ACK from slow domain

  always_ff @(posedge clk_src or negedge rst_src_n) begin
    if (!rst_src_n) begin
      req         <= 0;
      req_latched <= 0;
    end else begin
      // latch pulse and wait for ack
      if (pulse_in & ~req_latched) begin
        req         <= 1;
        req_latched <= 1;
      end else if (ack_sync) begin
        req         <= 0;
        req_latched <= 0;
      end
    end
  end

  //-----------------------
  // Slow domain logic
  //-----------------------
  reg ff1, ff2;  // 2-FF synchronizer for req
  reg ack;       // ack in slow domain

  always_ff @(posedge clk_dst or negedge rst_dst_n) begin
    if (!rst_dst_n) begin
      ff1 <= 0; ff2 <= 0; ack <= 0;
    end else begin
      ff1 <= req;
      ff2 <= ff1;
      // detect rising edge of synchronized req
      if (ff1 & ~ff2) begin
        ack <= 1;
      end else begin
        ack <= 0;
      end
    end
  end

  assign pulse_out = ff1 & ~ff2;  // single clock pulse in slow domain

  //-----------------------
  // Ack back to fast domain
  //-----------------------
  reg ack_ff1, ack_ff2;

  always_ff @(posedge clk_src or negedge rst_src_n) begin
    if (!rst_src_n) begin
      ack_ff1 <= 0;
      ack_ff2 <= 0;
    end else begin
      ack_ff1 <= ack;
      ack_ff2 <= ack_ff1;
    end
  end

  assign ack_sync = ack_ff2;

endmodule

 

 

 

Toggle Synchronizer

Toggle Synchronizer는 Fast → Slow 에서 안전하게 pulse를 전달하기 위해 pulse를 toggle 신호로 변환하고, slow domain에서 edge를 검출하는 방식이다.

 

Fast domain

  • pulse_in → toggle_ff: pulse가 들어올 때마다 toggle 신호를 반전.
  • Pulse의 길이나 타이밍에 상관없이, 단순히 변화만을 전달.

Slow domain

  • sync_ff1, sync_ff2: 2-FF로 toggle_ff를 동기화
  • pulse_slow = sync_ff1 ^ sync_ff2: XOR 연산으로 toggle 신호의 rising/falling edge 감지 → 1-clock pulse 생성.

 

Code

`timescale 1ns/1ps

module toggle_sync_tb;

  // Clocks
  reg clk_src = 0;  // Fast domain
  reg clk_dst = 0;  // Slow domain

  // Active-low resets
  reg rst_src_n = 0;
  reg rst_dst_n = 0;

  // Fast domain pulse
  reg async_pulse = 0;

  // Synchronized pulse output in slow domain
  wire sync_out;

  // Clock generation
  always #5  clk_src = ~clk_src;   // Fast clock: 10ns period
  always #20 clk_dst = ~clk_dst;   // Slow clock: 40ns period

  // Release resets
  initial begin
    #15 rst_src_n = 1;
    #15 rst_dst_n = 1;
  end

  // Generate pulse aligned to clk_src rising edge
  initial begin
    #25 async_pulse = 1;
    #10 async_pulse = 0;
    #120async_pulse = 1;
    #10 async_pulse = 0;
    #200 $finish;
  end

  // Instantiate Toggle Synchronizer
  toggle_sync u_toggle (
    .clk_src(clk_src),
    .clk_dst(clk_dst),
    .rst_src_n(rst_src_n),
    .rst_dst_n(rst_dst_n),
    .pulse_in(async_pulse),
    .pulse_out(sync_out)
  );

  // Waveform dump
  initial begin
    $dumpfile("toggle_sync.vcd");
    $dumpvars(0, toggle_sync_tb);
  end

endmodule

module toggle_sync(
  input  wire clk_src,      // Fast domain
  input  wire clk_dst,      // Slow domain
  input  wire rst_src_n,    // Active low reset for fast domain
  input  wire rst_dst_n,    // Active low reset for slow domain
  input  wire pulse_in,     // Fast domain pulse input
  output wire pulse_out     // Slow domain synchronized pulse
);

  //-----------------------
  // Fast domain logic: convert pulse -> toggle
  //-----------------------
  reg toggle_ff;
  always_ff @(posedge clk_src or negedge rst_src_n) begin
    if (!rst_src_n)
      toggle_ff <= 0;
    else if (pulse_in)
      toggle_ff <= ~toggle_ff;  // toggle on each pulse
  end

  //-----------------------
  // Slow domain logic: synchronize toggle and detect edge
  //-----------------------
  reg sync_ff1, sync_ff2;
  reg pulse_slow;

  always_ff @(posedge clk_dst or negedge rst_dst_n) begin
    if (!rst_dst_n) begin
      sync_ff1   <= 0;
      sync_ff2   <= 0;
      pulse_slow <= 0;
    end else begin
      sync_ff1   <= toggle_ff;       // 2-FF Synchronizer
      sync_ff2   <= sync_ff1;
      pulse_slow <= sync_ff1 ^ sync_ff2;  // XOR: edge detect
    end
  end

  assign pulse_out = pulse_slow;

endmodule