[Verilog] RTL 설계: Glitch-free Clock Mux

저전력 설계가 중요해지면서, 칩이 바쁠 때는 고속 클럭(PLL)을 쓰고, 대기 모드일 때는 저속 클럭(Oscillator)으로 전환하는 Dynamic Frequency Scaling (DFS) 기법이 필수적이 되었습니다.

이때 초보 RTL 엔지니어들이 가장 많이 하는 실수는 clock을 데이터처럼 다루는 것입니다.

// 절대 하면 안 되는 코드 (The "Don't")
assign clk_out = select ? clk_fast : clk_slow;

이렇게 MUX를 쓰면 글리치(Glitch)가 발생합니다. 클럭이 High인 상태에서 스위칭이 일어나면, clock pulse가 잘려나가면서 아주 짧은 ‘Runt Pulse’가 생깁니다. 이 짧은 펄스는 D-FlipFlop의 최소 펄스 폭(Minimum Pulse Width) 조건을 위반하게 하여, 칩 전체를 Metastability 상태로 빠뜨리고 오동작을 일으킵니다.

이번 글에서는 언제 스위칭 신호를 보내도 안전하게 clock을 갈아타는 Glitch-free Clock Mux의 원리와 구현 코드를 알아봅니다.

1. 기본 원리: “현재 클럭이 꺼진 뒤에 켜라”

글리치를 막는 핵심 원리는 간단합니다. Break-before-make (끊고 나서 연결하기) 입니다.

  1. 선택 신호(select)가 바뀌면, 즉시 새 클럭으로 넘어가면 안 됩니다.
  2. 현재 사용 중인 클럭(clk_current)이 Low(0) 상태가 될 때까지 기다렸다가 연결을 끊습니다.
  3. 출력이 완벽하게 0이 된 것을 확인한 후, 새로운 클럭(clk_next)을 연결합니다.

이 과정을 통해 클럭의 High 구간이 잘리는 것을 원천 봉쇄합니다.

2. 회로 구조: 비동기 클럭 간의 안전한 핸드쉐이크

서로 주파수와 위상이 전혀 다른 두 클럭(Asynchronous Clocks)을 스위칭하기 위해서는 동기화 회로(Synchronizer)와 피드백 루프(Feedback Loop)가 필요합니다.

핵심 구성 요소

  1. Falling-Edge Flip-Flop: clock을 끄거나 켤 때, clock이 High인 구간을 건드리지 않기 위해 Negative Edge에서 동작하는 F/F을 사용합니다. 이렇게 하면 clock이 Low일 때만 Enable 신호가 바뀝니다.
  2. Feedback Path: 상대방 clock이 완전히 꺼졌는지 확인하기 위해, 상대방의 상태 값을 나의 Enable 조건으로 가져옵니다. (상호 배제, Mutual Exclusion).
  3. 2-DFF Synchronizer: select 신호는 어느 clock 기준으로 들어올지 모르므로, 각 clock domain에 맞게 동기화해줘야 합니다.

3. Verilog 구현 (Standard Implementation)

module glitch_free_clk_mux (
    input  wire clk0,    // Clock Source A
    input  wire clk1,    // Clock Source B
    input  wire select,  // 0: clk0, 1: clk1
    input  wire rst_n,   // Asynchronous Reset
    output wire clk_out  // Glitch-free Output
);

    reg [1:0] q0, q1; // Synchronizers (Rising Edge)
    reg       g0, g1; // Gating FFs (Falling Edge)

    // --- Path for CLK 0 ---
    // 상대방(g1)이 꺼져있고(!g1) & 선택이 0일 때(!select) 활성화 요청
    always @(posedge clk0 or negedge rst_n) begin
        if (!rst_n) q0 <= 2'b00; // Reset 시 안전하게 0
        else        q0 <= {q0[0], ~select & ~g1}; // Sync & Logic
    end

    // 실제 Gating은 Falling Edge에서 수행 (High Pulse 보호)
    always @(negedge clk0 or negedge rst_n) begin
        if (!rst_n) g0 <= 1'b0;
        else        g0 <= q0[1];
    end

    // --- Path for CLK 1 ---
    // 상대방(g0)이 꺼져있고(!g0) & 선택이 1일 때(select) 활성화 요청
    always @(posedge clk1 or negedge rst_n) begin
        if (!rst_n) q1 <= 2'b00; // Reset 시 안전하게 0
        else        q1 <= {q1[0], select & ~g0}; // Sync & Logic
    end

    // 실제 Gating은 Falling Edge에서 수행
    always @(negedge clk1 or negedge rst_n) begin
        if (!rst_n) g1 <= 1'b0;
        else        g1 <= q1[1];
    end

    // --- Output Logic ---
    // 두 클럭 중 활성화된 쪽만 통과 (AND-OR 구조)
    wire clk0_gated = clk0 & g0;
    wire clk1_gated = clk1 & g1;
    
    assign clk_out = clk0_gated | clk1_gated;

endmodule

4. 동작 시나리오 분석

사용자가 select0에서 1로 바꾸면 어떤 일이 일어날까요?

Glitch free clock mux waveform
Glitch free clock mux waveform
  1. 요청: select=1이 됩니다.
  2. CLK0 차단: CLK0 로직의 입력이 ~select이므로 0이 됩니다. CLK0의 Falling Edge에서 g00으로 떨어집니다. 이제 CLK0 경로는 차단되었습니다.
  3. 대기 (Dead Zone): g0=0 신호가 CLK1 쪽으로 넘어갑니다. 이때 두 클럭 모두 차단된 상태로 출력은 0을 유지합니다. (안전 구간)
  4. CLK1 활성화: CLK1 로직은 select=1이고 g0=0인 것을 확인합니다. CLK1의 Falling Edge에서 g11로 변합니다.
  5. 출력: 이제 clk_outCLK1을 따라 움직입니다.

이 과정 덕분에 두 클럭 중 하나라도 High인 상태에서는 절대로 스위칭이 일어나지 않습니다.

5. 결론: 클럭은 타협하지 않는다

Glitch-free Mux는 일반 Mux보다 레이턴시(Latency)가 깁니다. select 신호를 주고 나서 실제 클럭이 바뀌기까지 수 클럭 사이클(동기화 과정)이 걸립니다.

하지만 이 지연 시간은 안전(Safety)을 위한 비용입니다. 클럭 라인에 섞인 단 하나의 글리치는 수십억 개의 트랜지스터 중 어디서 에러를 낼지 모르는 시한폭탄과 같습니다.

  • 서로 다른 주파수의 클럭을 쓴다면: 위에서 소개한 비동기 핸드쉐이크 회로를 반드시 사용하세요.
  • 같은 PLL에서 나온 동기 클럭(배수 관계)이라면: 조금 더 간단한 회로로도 가능하지만, 헷갈린다면 그냥 위 회로를 쓰는 것이 가장 안전합니다.

참고: VLSI tutorials

Similar Posts