| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- assertion
- 인덕터
- data structure
- IOT
- 디더링
- 티스토리챌린지
- 파이참
- BeautifulSoup4
- STM32
- Dither
- 오블완
- CDC
- Metastability
- systemverilog assertions
- tkinter
- SVA
- Clock Domain Crossing
- 군산가볼만한곳
- 아두이노
- Bash
- 임베디드시스템
- Dithering
- c++ 기초
- 자료구조
- openpyxl
- ADsP
- arduino
- SystemVerilog
- git
- 임베디드레시피
- Today
- Total
리미창고
[CDC] 3_Single Bit CDC: Simulation Result 본문
이번에는 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를 내릴 때 사용.
- Fast → Slow pulse 전달
- pulse_in 들어오면 req = 1
- slow domain에서 2-FF로 안전하게 req 전달
- slow domain에서 rising edge 검출 → pulse_out 생성
- ack 생성 → fast domain으로 동기화
- Pulse 손실 방지
- ack가 오기 전까지 새로운 pulse_in은 무시 (req_latched)
- 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'VLSI > Design' 카테고리의 다른 글
| [CDC] 2_Single Bit CDC: Toggle Synchronizer (0) | 2025.12.07 |
|---|---|
| [CDC] 1_Single Bit CDC: 2-FF Synchronizer (0) | 2025.12.07 |
| [CDC] 0_CDC(Clock Domain Crossing)와 Metastability (0) | 2025.12.06 |
| [SystemVerilog] generate-for loop와 for loop의 차이 (0) | 2024.10.18 |
| 인코더와 디코더(Encoder and Decoder) (1) | 2022.07.26 |