Handshake CDC에서 우리가 배운 핵심은 이거였다.
멀티비트 데이터는 ‘고정된 구간(stable window)’을 만들지 않으면 절대 안전하게 CDC할 수 없다
그런데 현실 설계에서는 이런 종류의 신호도 많다.
- 카운터 값
- 포인터
- 상태 번호
- “지금 어디까지 진행됐는지”를 나타내는 값
이 값들은 데이터 가 아니라 시간에 따라 계속 변하는 상태(state)이다. 이때마다 handshake를 거는 것은 불필요한 Latency가 발생하고 고속 파이프라인이나 포인터 전달에는 구조적으로 매우 비효율적이다. 그래서 등장하는 게 Gray Code CDC다.
Gray Code란?
간단히 말해서 연속된 값 사이에서 단 1비트만 바뀌도록 만든 코드이다.
| 값 | Binary | Gray |
| 0 | 000 | 000 |
| 1 | 001 | 001 |
| 2 | 010 | 011 |
| 3 | 011 | 010 |
| 4 | 100 | 110 |

Binary to Gray 변환
Gray = Binary ⊕ (Binary >> 1)
(⊕ : XOR)
- MSB는 그대로
- 나머지 비트는 “자기 자신 ⊕ 바로 위 상위 비트”
비트 단위로 이해해보자.
Binray가 아래와 같이 있다.
B[3] B[2] B[1] B[0]
이때 Gray는
G[3] = B[3] // MSB는 그대로
G[2] = B[3] ⊕ B[2] // 바로 위 비트 = B[3]
G[1] = B[2] ⊕ B[1] // 바로 위 비트 = B[2]
G[0] = B[1] ⊕ B[0] // 바로 위 비트 = B[1]
가 된다.
MSB는 그대로 두고, 자기보다 한 단계 상위 비트와 병렬 XOR하면 된다.
B[3] B[2] B[1] B[0]
↑ ↑ ↑
| | |
| | +-- G[0] = B[1] ⊕ B[0]
| +------- G[1] = B[2] ⊕ B[1]
+------------ G[2] = B[3] ⊕ B[2]
Gray의 각 비트는 자기 자신과 왼쪽에 있는 비트를 XOR한 것이다. 가장 왼쪽(MSB)은 왼쪽 비트가 없으므로 그냥 그대로 복사가 된다.
Gray to Binary 변환
Gray가 아래와 같이 있다.
G[3] G[2] G[1] G[0]
이때 Binary는
B[3] = G[3]
B[2] = B[3] ⊕ G[2]
B[1] = B[2] ⊕ G[1]
B[0] = B[1] ⊕ G[0]
가 된다.
B[3] = G[3]
B[2] = G[3] ⊕ G[2]
B[1] = G[3] ⊕ G[2] ⊕ G[1]
B[0] = G[3] ⊕ G[2] ⊕ G[1] ⊕ G[0]
위에서부터 하나씩 내려오며 누적 XOR한다.
Gray = 110 이라고 하자.
B[2] = 1
B[1] = 1 ⊕ 1 = 0
B[0] = 1 ⊕ 1 ⊕ 0 = 0
Binary = 100 (4)
왜 Gray Code를 CDC에 쓰는가?
CDC에서 멀티비트를 2-FF으로 하면 안된다는 것을 안다.
예를 들어,
3 -> 4
011 -> 100 (3비트가 동시에 변함)
위와 같은 상황에 Clock Domain이 다르면 각 비트가 서로 다른 타이밍에 샘플링 될 수 있다. 그 결과로 수신 쪽에서는 아래와 같이 샘플링될 위험이 있다.
000, 001, 110, 101 ...
수신쪽에서는 송신 쪽에서 보낸 적이 없는 값을 받게 된다.
Gray Code는 항상 1비트만 변하므로 Bit Skew가 발생하여도 이전 값 또는 다음 값 중 하나로 수렴하여 중간에 이상한 값은 나올 수가 없다.
// 송신
a → b → c → d → ...
// 수신
a, b, b, b, b, c, c, d ...
설령 같은 값이 여러 번 반복되어 샘플링되더라도, 관측되는 값은 항상 유효한 Gray 값이며 시간 순서가 뒤집히지 않고 앞으로 진행된다.
Gray Code CDC 사용 조건
Gray Code CDC는 모든 멀티비트 CDC 문제를 해결하는 만능 기법이 아니다. 다음 조건을 반드시 만족해야 한다.
1. 한 번에 1씩만 변화해야한다.
- 값은 +1 또는 -1만 변화해야 한다.
- 카운터나 FIFO 포인터 등이 될 수 있다.
2. 단조 증가/감소
- 증가면 계속 증가
- 감소면 계속 감소
증가 ↔ 감소 전환은 추가 보호 로직이 필요하다.
3. 값의 의미가 데이터가 아니라 State이다.
Gray Code CDC는 값의 정확성이 아니라 순서와 방향성을 보장하는 기법이다. 현재 어디까지 왔는지 얼마나 진행됐는지 알 수 있다.
4. 송신 도메인에서 Binary → Gray 변환
Binary 값은 반드시 송신 도메인에서 Gray로 변환된 후 CDC되어야 한다.
5. 수신 도메인에서 Gray → Binary 변환
CDC 이후에 Gray 값을 Binary로 복원하여 비교 및 연산을 수행한다.
(SRC : Binary → Gray) → CDC → (Gray → Binary : DST)
6. Gray 값이 충분히 오래 유지되어야 한다.
각 Gray 값은 수신 도메인에서 최소 1회 이상 샘플링될 수 있을 만큼 유지되어야 한다.
이 조건이 깨지면:
- 중간 값이 관측되지 않거나
- 값이 건너뛰는 현상이 발생할 수 있다.
즉, Fast → Slow CDC에서는 구조적인 한계가 존재한다.
다만 이 경우에도 순서와 방향성 자체는 여전히 보장된다.
Gray Code CDC는 분명히 한계가 있는 멀티비트 CDC 기법이다. 이 때문에 단독으로 사용되기보다는, 대부분 Async FIFO의 포인터 전달과 같은 구조에서 사용된다.
Code
module gray_cdc #(
parameter W = 4
)(
// Source domain
input logic clk_src,
input logic rst_src_n,
// Destination domain
input logic clk_dst,
input logic rst_dst_n,
output logic [W-1:0] dst_bin
);
//=================================
// Source domain
//=================================
logic [W-1:0] bin_cnt;
logic [W-1:0] gray_src;
// Binary counter (state)
always_ff @(posedge clk_src or negedge rst_src_n) begin
if (!rst_src_n)
bin_cnt <= '0;
else
bin_cnt <= bin_cnt + 1'b1; // +1 only (Gray CDC condition)
end
// Binary -> Gray
always_comb begin
gray_src = bin_cnt ^ (bin_cnt >> 1);
end
//=================================
// CDC synchronizer (Gray only)
//=================================
logic [W-1:0] gray_ff1, gray_sync;
always_ff @(posedge clk_dst or negedge rst_dst_n) begin
if (!rst_dst_n) begin
gray_ff1 <= '0;
gray_sync <= '0;
end else begin
gray_ff1 <= gray_src;
gray_sync <= gray_ff1;
end
end
//=================================
// Gray -> Binary (DST domain)
//=================================
integer i;
always_comb begin
dst_bin[W-1] = gray_sync[W-1];
for (i = W-2; i >= 0; i=i-1)
dst_bin[i] = dst_bin[i+1] ^ gray_sync[i];
end
endmodule
module tb_gray_cdc;
localparam W = 4;
logic clk_src = 0;
logic clk_dst = 0;
logic rst_n = 0;
always #5 clk_src = ~clk_src; // fast
always #17 clk_dst = ~clk_dst; // slow & async
logic [W-1:0] dst_bin;
gray_cdc #(.W(W)) dut (
.clk_src (clk_src),
.rst_src_n (rst_n),
.clk_dst (clk_dst),
.rst_dst_n (rst_n),
.dst_bin (dst_bin)
);
initial begin
rst_n = 0;
#40 rst_n = 1;
#600;
$finish;
end
initial begin
$display(" time | DST_bin");
$monitor("%4t | %0d", $time, dst_bin);
end
initial begin
$dumpfile("gray_cdc.vcd");
$dumpvars(0, tb_gray_cdc);
end
endmodule
Simulation

dst_bin이 중간 값을 skip하는 것을 볼 수 있다. 하지만 절대로 역행하는 경우는 없다.
'VLSI > Design' 카테고리의 다른 글
| [CDC] 7_Multi Bit CDC: Asynchronous FIFO (First-In First Out) (0) | 2025.12.20 |
|---|---|
| [CDC] 6_Multi Bit CDC: Asynchronous FIFO (Introduction) (0) | 2025.12.19 |
| [CDC] 4_Multi Bit CDC: Handshake (1) | 2025.12.15 |
| [CDC] 3_Single Bit CDC: Simulation Result (0) | 2025.12.07 |
| [CDC] 2_Single Bit CDC: Toggle Synchronizer (0) | 2025.12.07 |