01signal.com

메타안정성과 클록 도메인 교차의 기초

이 페이지는 클록 도메인에 관한 연재 중 두 번째 글입니다.

범위

이미 이전 페이지에서 언급했듯이, 경로(path)가 관련 클록의 클록 도메인 사이를 가로지르는 경우 로직에 특별한 처리는 필요하지 않습니다. 그렇지만 도착점의 동기식 요소에 대한 타이밍 요구 조건(셋업 시간과 홀드 시간)을 보장하려면 FPGA 설계 도구가 이 경로에 타이밍 제약 조건(timing constraints)을 적용하도록 해야 합니다. 따라서 이런 경우에는 경로 타이밍이 계산된다는 점에서, 클록 도메인 교차(clock domain crossing)가 전혀 없는 것처럼 취급합니다.

그러나 두 클록이 서로 비관련 클록이라면 재동기화 로직(resynchronization logic)이 필요합니다. 즉 이 문제를 해결하기 위한 전용 코드가 Verilog(또는 다른 언어)로 작성되어 있어야 합니다. 이 페이지에서는 그 방법의 기초를 살펴봅니다.

가능하면 FIFO를 사용하라

골칫거리를 아예 피할 수 있는 방법부터 소개하는 것이 예의라고 생각했습니다. 피할 수 있는 분들을 위해서요. 방법은 다음과 같습니다.

FPGA 설계 도구가 생성해 주는 듀얼 클록 FIFO(dual-clock FIFO)는 클록 도메인을 건너는 가장 안전한 방법임에 틀림없습니다. 이것은 가장 널리 쓰이는 해법이며, 그만큼 많이 사용된다는 사실 자체가 이 방법을 신뢰할 이유가 됩니다.

특히 FPGA를 처음 접하는 경우라면, 자신의 설계를 조금 바꿔야 하더라도 FIFO를 선택할 충분한 이유가 있습니다. FIFO를 사용하면 목적에 맞게 직접 만든 로직보다 리소스를 조금 더 쓸 수도 있습니다. 하지만 FIFO 깊이가 얕으면 블록 RAM(block RAM)이 필요 없는 경우가 대부분이라는 점을 기억하세요. 그렇다면 리소스 차이가 안전을 포기할 만큼 큰 것은 아닙니다.

데이터 스트림이 한 기능 블록에서 다른 기능 블록으로 흐르는 경우라면 FIFO는 클록 도메인을 건너는 자연스러운 해법입니다. 그렇게 자연스럽지 않은 경우에도 로직을 재구성하면 맞출 수 있는 경우가 많습니다. 예를 들어, 어떤 클록 도메인의 로직이 다른 클록 도메인의 로직에 특정 이벤트를 알려야 한다면 메시지 워드를 인코딩해서 얕은 FIFO에 쓰는 방식으로 처리할 수 있습니다. 이런 해법은 버그가 생길 가능성이 적을 뿐 아니라, 설계가 더 체계적이고 읽기 쉬워지는 효과도 있습니다.

FIFO를 연결하고 사용하는 방법에 대해서는 이 연재에서 자세히 다룹니다.

단일 비트 신호를 위한 재동기화 로직

FIFO로 문제를 해결할 수 없다면, 소매를 걷어붙이고 안전한 클록 도메인 교차를 위한 재동기화 로직을 직접 구현할 차례입니다. 이 페이지의 나머지 부분에서는 폭이 1비트인 신호를 클록 도메인 사이로 건너보내는 경우만 다룹니다. 이것은 모든 재동기화 로직의 초석이며, 이렇게 단순해 보이는 경우에도 다룰 내용이 많습니다.

이 연재의 다음 페이지에서는 아래에서 소개하는 메타안정성 가드 기법을 바탕으로 폭이 더 넓은 신호를 클록 도메인 사이로 보내는 방법을 다룹니다.

메타안정성

해법을 다루기 전에, 플립플롭의 타이밍 요구 조건이 위반되면 어떤 일이 벌어지는지를 이해하는 것이 중요합니다. 다시 말해 데이터 입력의 신호가 클록 에지보다 tsu 앞선 시점부터 그 에지로부터 thold가 지난 시점까지의 구간 동안 안정적이지 못한 경우입니다. 물론 해당 클록 에지는 플립플롭의 클록 입력에 걸리는 에지이며, 그 에지가 데이터 입력을 샘플링하게 합니다.

이 구간 동안 데이터가 변한다면 그 클록 에지 이후 플립플롭의 출력이 무엇이 될지는 예측할 수 없습니다. 하지만 그것보다 더 심각합니다. 플립플롭이 출력에 정상적인 논리값 '0'이나 '1'을 내보낼 때까지 평소보다 훨씬 오래 걸릴 수 있습니다. 플립플롭은 출력이 두 값 중 하나로 안정적으로 유지되도록 설계되어 있지만, 타이밍 요구 조건이 위반되면 잠시 동안 불안정한 상태에 머물 수 있습니다. 공식 용어로 말하면 플립플롭이 메타안정 상태(metastable state)에 빠진다고 합니다.

플립플롭이 빠질 수 있는 불안정 상태를 설명할 때 흔히 쓰는, 언덕 꼭대기에 공이 서 있는 클리셰 이미지를 빼먹을 수 없겠네요.

Ball on tip of hill, illustrating metastability

메타안정성(metastability)은 단지 플립플롭이 결국 어느 값으로 떨어질지 불확실하다는 것보다 훨씬 큰 문제입니다. 특히 그 플립플롭의 출력이 두 개 이상의 목적지에 연결되어 있다면 더욱 그렇습니다. '0' 또는 '1'로 안정되는 데 시간이 더 걸리기 때문에, 신호가 도착점의 플립플롭들 입장에서 너무 늦게 안정될 수 있습니다. 그 결과 목적지의 플립플롭 중 일부는 출렁이는 신호를 '0'으로 받아들이고, 다른 일부는 '1'로 받아들일 수 있습니다. 이런 불일치로 로직이 허용되지 않는 상태에 빠질 수 있고, 그러면 로직은 블랙 매직(black magic)처럼 동작할 수 있습니다. 따라서 메타안정성 위험이 있는 플립플롭은 절대로 두 개 이상의 논리 요소에 연결하면 안 됩니다.

메타안정성에 대처하려면 플립플롭이 안정 상태 중 하나로 떨어지는 데 얼마나 걸리는지 알면 좋을 것입니다. 안타깝게도 그에 대한 명확한 답은 없습니다. 언덕 꼭대기에서 공이 얼마나 오래 버틸까를 묻는 것과 똑같습니다. 많은 요인에 달려 있고, 결국에는 무작위적인 진동 때문에 어느 쪽으로든 떨어질 가능성이 높습니다. 플립플롭의 메타안정성도 마찬가지입니다. 전자 회로의 무작위 잡음이나 다른 무언가에 의해 그 상태에서 벗어나게 됩니다.

그래서 이론적으로는 플립플롭이 메타안정 상태에 영원히 머무를 수도 있지만, 현실에서는 잠시 후에 안정 상태 중 하나로 떨어집니다. 그 상태에 머무는 시간은 확률 변수(random variable)입니다. 그리고 이 확률 변수의 특성을 추정하려는 많은 실험과 시뮬레이션이 있었습니다. 하지만 그런 시도들은 실제로 별 도움이 되지 않습니다. 그 특성이 실리콘 제조 공정, 온도, 크로스토크(crosstalk)로 인한 잡음 준위 등 여러 요인에 달려 있기 때문입니다.

그래서 다시 말하지만, 플립플롭이 메타안정 상태에서 절대 넘지 않을 최대 시간이 정해져 있으면 편리하겠지만 그런 상한은 존재하지 않습니다. 심지어 근사값조차 구하기 어렵습니다. 최신 제조 공정으로 만든 플립플롭은 메타안정 상태를 더 빨리 벗어나는 경향이 있기 때문입니다.

대신 이런 생각에 익숙해져야 합니다. 비관련 클록으로 클록 도메인을 건널 때는, 어떤 플립플롭이 설계가 허용할 수 있는 시간보다 더 오래 메타안정 상태에 머물러서 그 결과 문제가 생길 가능성이 항상 존재합니다. 설계자로서 우리가 할 수 있는 일은 이 위험을 줄이는 것뿐입니다. 기껏해야 견딜 만한 MTBF(Mean Time Between Failure, 평균 고장 간격)를 달성하는 것이 최선입니다.

운 좋게도 정확히 그 목표를 달성하기 위한 잘 정립된 기법이 있으며, 다음 주제로 넘어가겠습니다.

메타안정성 가드

길게 설명하지 않고, 이전 페이지의 첫 번째 코드 예로 다시 돌아가겠습니다. @clk1과 @clk2가 비관련 클록이라면, @foo에서 안정적인 @bar를 얻기 위한 일반적인 재동기화 로직은 다음과 같습니다.

reg foo, bar, bar_metaguard;

always @(posedge clk1)
  foo <= !foo;

always @(posedge clk2)
  begin
    bar_metaguard <= foo;
    bar <= bar_metaguard;
  end

이름이 암시하듯 @bar_metaguard는 메타안정성 가드(metastability guard)입니다. @bar_metaguard를 구현하는 플립플롭은 @foo를 샘플링하는 동안 타이밍 요구 조건이 때때로 위반됩니다. 따라서 @bar_metaguard는 짧은 메타안정 상태를 겪을 수 있습니다. 이 플립플롭은 그 상태에서 빨리 회복될 것으로 기대되므로, @bar의 셋업 타이밍 요구 조건을 충족할 만큼 충분히 빨리 안정될 것입니다. 따라서 @bar는 @clk2의 클록 도메인 안에서 안정적으로 사용할 수 있습니다.

이 설명은 정확하지 않게 들릴 수 있는데, 실제로도 정확하지 않습니다. 앞에서 메타안정성에 대해 쓴 내용과도 모순됩니다. 메타안정성에는 완벽하게 안전한 해법이 없기 때문입니다. 이에 대해서는 아래에서 다시 다루겠습니다. 하지만 일단은 위처럼 메타안정성 가드를 추가하고 더 이상 걱정하지 않는 일반적인 관행을 따르기로 합시다. 솔직히 말해, 이 방법 때문에 문제가 생겼다는 이야기는 들어 본 적이 없습니다.

더 안전하게 만들고 싶다면 레지스터를 더 추가하면 됩니다. 이중 메타안정성 가드(double metastability guard)는 대략 이런 형태입니다.

reg foo, bar, bar_metaguard_a, bar_metaguard_b;

always @(posedge clk1)
  foo <= !foo;

always @(posedge clk2)
  begin
    bar_metaguard_a <= foo;
    bar_metaguard_b <= bar_metaguard_a;
    bar <= bar_metaguard_b;
  end

@bar_metaguard_a는 첫 번째 메타안정성 가드입니다. 운이 나쁘면 이 플립플롭이 메타안정 상태에 너무 오래 머물러 @bar_metaguard_b의 타이밍 요구 조건이 위반될 수 있습니다. 그러면 @bar_metaguard_b도 다음 클록 사이클에서 메타안정 상태를 겪게 됩니다. 하지만 이번에는 그 메타안정 상태가 짧기를 바랄 수 있습니다. 번개는 같은 곳에 두 번 떨어지지 않는다고 하지 않습니까. 물론 @bar_metaguard_b도 @bar의 타이밍을 위반할 만큼 오래 메타안정 상태에 머무를 수 있습니다. 하지만 그럴 확률은 얼마나 될까요? 목표가 합리적인 MTBF를 얻는 것임을 기억하십시오.

정리하면, 단일 비트로 클록 도메인을 건너는 요리책식 해법을 찾고 있다면 바로 이것입니다. 메타안정성 가드 하나나 둘이면 충분히 잘 동작합니다. 절대 실패하지 않는 해법을 원한다면 안타깝지만 그것은 불가능합니다. 하지만 사고 확률을 가능한 한 줄이고 싶다면 계속 읽으십시오.

메타안정성의 타이밍 분석

이제 위의 메타안정성 가드 하나짜리 예로 돌아가서, 왜 @bar_metaguard가 메타안정 상태에서 충분히 빨리 회복한다고 그렇게 확신했는지 묻는다면, 답은 앞서 말했듯이 확신할 이유가 전혀 없다는 것입니다.

그렇더라도 약간의 타이밍 분석을 해 보겠습니다. @bar_metaguard와 @bar는 둘 다 같은 클록에 동기화되어 있습니다. 이 레지스터들에 특별한 타이밍 제약 조건이 따로 지정되지 않았다고 가정하면, 두 레지스터 사이의 경로는 @clk2의 타이밍 제약 조건(클록 주기)에 의해 제한됩니다. 다시 말해 설계 도구는 메타안정성이 없다면 @bar의 입력 신호가 정상적인 타이밍으로 샘플링되도록 보장합니다.

그러나 @bar_metaguard의 존재 이유는 때때로 메타안정 상태를 허용하기 위함이었습니다. 따라서 타이밍 분석만으로는 부족합니다. 현실에서는 플립플롭이 메타안정 상태에서 보내는 시간이 클록-출력 지연(clock-to-output delay)에 추가됩니다. 즉 메타안정성 때문에 플립플롭 출력이 평소 지연보다 늦게 안정된다는 뜻입니다. 따라서 @bar_metaguard에서 @bar로 가는 경로의 슬랙(slack)이 거의 0이라면(즉 전파 지연(propagation delay)이 타이밍을 달성하기에는 충분하지만 여유는 없는 상태라면), 메타안정성 때문에 경로의 전체 지연이 허용 한계를 넘을 수 있습니다. 그런 사건이 발생하면 @bar 입력에 타이밍 위반이 생깁니다. 물론 이것은 온도, 전압, 제조 공정의 최악 조건에 해당하는 이야기입니다. 그런데도 설계 도구가 메타안정성 가드 출력에 일반적인 타이밍 제약 조건을 적용한다는 사실은, 필요한 타이밍 요구 조건을 보장하지 못한다는 뜻입니다.

다행히 고치기 쉽습니다. 아니, 개선하기 쉽다고 말해야겠네요. 방법은 메타안정성 가드에서 안정적으로 동작해야 하는 레지스터로 가는 모든 경로에 더 빡빡한 타이밍 제약 조건을 추가하는 것입니다. 즉 이 경로들에 허용되는 전파 지연을 더 줄이는 것입니다. 전파 지연에 허용되는 시간을 줄여서, 그만큼의 시간을 메타안정성 회복에 쓸 수 있게 하는 것입니다.

예를 들어 모든 메타안정성 가드 레지스터 이름에 *_metaguard 접미사가 붙는다면, 이들에서 시작하는 모든 경로가 안정화를 위한 추가 시간을 확보하도록 요구하는 타이밍 제약 조건 하나를 작성할 수 있습니다. Vivado에서는 다음과 같이 씁니다.

set_max_delay -from [ get_cells -hier -filter {name=~*_metaguard*} ] 0.75

(set_max_delay에 대한 설명은 타이밍 예외 페이지에 있습니다.)

이 타이밍 제약 조건은 메타안정성 가드에서 시작하는 모든 경로가 0.75ns 안에 목적지에 도달해야 한다는 뜻입니다(레지스터 이름의 접미사로 해당 경로를 선택합니다). 이 값은 제가 직접 시도한 특정 FPGA에서 타이밍 제약 조건을 실패하지 않고 얻을 수 있었던 최소 지연입니다(따라서 다른 FPGA에서는 다를 수 있습니다). 이 숫자를 찾는 방법은 설계 도구가 제약 조건을 달성하지 못할 때까지 값을 점점 낮춰 보는 것입니다. 실패가 발생하면 슬랙이 아주 작게(예: 0.2ns) 나올 때까지 값을 올리면 됩니다. 그러면 설계 도구는 이 경로들에 최선을 다하게 됩니다.

set_max_delay 명령은 클록 주기 0.75ns(1333MHz)를 요구하는 것과 같습니다. 예를 들어 실제 클록 주기가 250MHz(4ns)라면, 이 타이밍 제약 조건을 달성하면 최소 4ns - 0.75ns = 3.25ns의 여유가 생깁니다. 따라서 메타안정성 가드가 최대 3.25ns 동안 메타안정 상태에 머물더라도, 이 set_max_delay 제약 조건 덕분에 타이밍 위반이 발생하지 않습니다.

물론 아무런 보장이 없는 것보다는 좋습니다. 하지만 3.25ns면 충분할까요? 많은 시간일까요? 클록 도메인 교차가 사실상 실패하지 않도록 보장하기에 충분할까요?

앞서 말했듯이 답은 여러 요인에 달려 있습니다. 발표된 실험 결과로 미루어 볼 때, 오늘날 실제로 사용되는 FPGA 어디에서든 플립플롭이 1ns나 되는 동안 메타안정 상태에 머무는 경우는 극히 드물 것이라는 것이 개인적인 생각입니다. 하지만 이 문제에 대해 확실히 말하기는 어렵습니다.

하지만 이런 타이밍 제약 조건을 추가해 설계 도구가 최선을 다하도록 만든다면, 여러분의 FPGA 설계는 다른 모든 사람들의 상황과 같거나 더 나은 수준이 됩니다. 네 번째 황금률(Golden Rule #4)의 정신, 즉 운에 기대지 말라는 정신에 따라, 만약 여러분에게 실패가 발생한다면 다른 많은 사람들도 불평할 이유가 있을 만한 위치에 서십시오.

이 타이밍 논의에서 얻는 또 하나의 통찰은, 사용 중인 FPGA의 관점에서 클록 주파수가 높다면 이중 메타안정성 가드가 좋은 생각일 수 있다는 것입니다. 특히 위에서 제안한 추가 타이밍 제약 조건을 사용하지 않을 때 그렇습니다. 앞서 언급했듯이 메타안정성은 경로의 슬랙을 소모합니다. 클록 주기가 짧아질수록(주파수가 높아질수록) 슬랙이 적어지는 경우가 많으므로, 메타안정성 회복에 쓸 수 있는 추가 시간도 줄어듭니다.

마지막으로, 왜 거의 모든 사람이 이 타이밍 문제를 통째로 무시하면서도 아무도 문제를 겪지 않는지 한번 묻고 싶습니다. 몇 가지 가능한 설명을 드리겠지만, 어디까지나 추측입니다.

첫 번째 설명은, 설계 도구가 메타안정성 가드를 FPGA 로직 패브릭(logic fabric)에서 물리적으로 다른 플립플롭과 매우 가깝게 배치할 가능성이 높다는 것입니다. 따라서 두 플립플롭(예: @bar_metaguard에서 @bar) 사이의 전파 지연은 해당 FPGA 기준으로 어차피 매우 짧습니다. 하지만 앞서 논의했듯이 명시적인 타이밍 제약 조건 없이는 이러한 밀착 배치가 보장되지 않습니다.

FPGA 제조사가 제공하는 공식 FIFO 내부에는 이런 용도의 플립플롭 쌍이 같은 슬라이스(slice) 안에 들어 있는 경우가 많습니다. 어쨌든 공식 FIFO는 이 문제를 해결해 두었다고 신뢰할 수 있습니다.

또 하나는 FPGA가 빨라지고 더 높은 클록 주파수가 가능해짐에 따라, 플립플롭도 메타안정 상태를 더 빨리 벗어나게 되었다는 점입니다. 그래서 메타안정 상태에서 회복하는 데 걸리는 시간에 전파 지연을 더하더라도 여전히 클록 주기보다 훨씬 짧습니다.

그래서 대부분의 사람들이 그냥 메타안정성 가드를 추가하고 더 이상 걱정하지 않는 것도 놀랍지 않습니다. 하지만 그렇다고 제약 조건 추가를 게을리해도 된다는 변명이 되지는 않습니다.

클록 주파수 제한 사항

지금까지 양쪽 클록 도메인의 클록 주파수에 대해서는 별로 언급하지 않았습니다. 예를 들어, 보내는 쪽(소스, source) 클록 도메인의 클록(@clk1)이 받는 쪽(목적지, destination) 클록 도메인의 클록(@clk2)보다 빠르면 어떤 일이 생길까요?

클록들 사이에 동기화가 필요 없기 때문에 소스 클록의 주파수가 더 높은지 낮은지 자체는 중요하지 않습니다. 하지만 소스의 신호(@foo)가 너무 빨리 변하면, 목적지의 플립플롭이 그 변화를 샘플링할 기회를 얻기 전에 신호가 이리저리 바뀔 수 있습니다. 따라서 모든 신호 변화가 목적지에서 관찰되어야 한다면, 소스 클록 주기는 목적지 클록 주기보다 약간 길어야(즉 주파수가 낮아야) 합니다.

얼마나 더 길어야 할까요? 사실 많이 길 필요는 없습니다. 정확한 답을 원한다면, 주로 목적지 플립플롭의 타이밍 요구 조건에 달려 있습니다. 여기 간단한 계산이 있습니다(정말 관심이 없다면 건너뛰어도 됩니다).

목적지 플립플롭의 입력이 그 플립플롭의 클록 에지보다 정확히 tsu 앞서 변한다면, 제대로 샘플링될까 말까 하는 경계에 있는 것입니다. 따라서 그 변화는 다음 클록 사이클에서 샘플링되어야 합니다. 그렇지 않으면 그 변화를 놓칠 수 있습니다. 이 변화를 다음 클록 사이클에서 확실하게 샘플링하려면, 입력 신호가 다음 클록 에지 이후 thold까지 안정적으로 유지되어야 합니다. 따라서 소스 클록 주기는 목적지 클록 주기보다 tsu + thold + 2tj만큼 길어야 합니다. 여기서 tj는 클록 주기의 불확실성, 즉 지터(jitter)입니다. 지터는 소스 클록과 목적지 클록 각각 한 번씩, 두 번 계산됩니다.

그래서 제가 소스 클록의 클록 주기가 '약간 길어야' 한다고 말할 때 그 '약간'이 바로 tsu + thold + 2tj입니다.

메타안정성 가드를 설계 도구에 알리기

일부 FPGA 도구 문서에서는 메타안정성 가드로 사용되는 플립플롭에 속성(attribute)을 추가할 것을 권장합니다. 이 속성은 설계 도구가 그 플립플롭을 마음대로 조작하지 못하게 막아 줍니다. 또한 설계 도구가 메타안정성 가드를 다음 플립플롭과 같은 슬라이스(slice)에 배치하도록 유도하여, 라우팅을 가능한 한 짧게 만들어 줍니다.

예를 들어 Vivado에는 ASYNC_REG이라는 속성이 있습니다. 앞에서와 같이 모든 메타안정성 가드 레지스터에 *_metaguard 접미사가 붙어 있다면, XDC 파일에 아래 한 줄만 추가하면 필요한 속성이 설정됩니다.

set_property ASYNC_REG true [get_cells -hier -filter {name=~*_metaguard*}]

이 속성은 Verilog 코드 안에서 설정할 수도 있습니다.

하지만 이 속성은 큰 차이를 만들지는 못할 것입니다. 설계 도구가 대개 메타안정성 가드를 알아서 올바르게 처리해 주기 때문입니다.

이상으로 이 연재의 두 번째 페이지를 마칩니다. 다음 페이지에서는 클록 도메인 사이로 데이터를 보내는 기법을 소개합니다.

이 페이지는 영어 원문을 기계 번역한 것입니다. 의문이 드는 부분이 있으면 원문을 참조하시기 바랍니다.
Copyright © 2021-2026. All rights reserved. (dcc38493)