들어가며
이 페이지에서는 소스 동기식(source-synchronous) 입력을 처리하는 방법으로 01 신호 샘플링(01-signal sampling)을 설명합니다. 이런 종류의 데이터 소스와 인터페이스하는 다른 전략은 소스 동기식 입력 전반에 관한 페이지에서 개략적으로 다룹니다. 그 페이지에는 소스 동기식 입력이 무엇인지도 설명되어 있습니다.
01 신호 샘플링의 기본 아이디어는 외부 클록을 데이터 신호처럼 취급하는 것입니다. 즉, 이 클록을 레지스터로 샘플링합니다. 이 레지스터는 외부 클록과 무관한 안정적인 내부 클록을 사용합니다.
데이터 신호는 별도의 레지스터들로 샘플링하며, 역시 같은 내부 클록을 사용합니다. 이 내부 클록은 01 신호 샘플링을 구현하는 모든 로직에도 사용됩니다.
이 로직은 외부 클록을 샘플링하는 레지스터의 값 변화를 감지합니다. 이 레지스터가 '0'에서 '1'로 바뀌면, 그것은 외부 클록에 상승 에지(rising edge)가 발생했음을 의미합니다. 로직은 이 이벤트에 반응하여 다른 레지스터들의 값을 FIFO 에 씁니다. 이 레지스터들에는 외부 클록의 상승 에지가 발생했을 때 데이터 신호에 있던 값들이 담겨 있습니다.
이 로직은 FIFO 의 클록이 외부 클록이고 FIFO 의 데이터 입력이 데이터 신호에 직접 연결된 것과 같은 결과를 냅니다. 차이는 로직이 어떤 클록, 즉 외부 클록을 쓰느냐 내부 클록을 쓰느냐입니다. 01 신호 샘플링의 장점은 모든 로직이 오직 내부 클록에만 의존한다는 점입니다. 이 클록은 안정적이고 신뢰할 수 있습니다. 외부 클록이 이상 동작을 해도 로직은 계속해서 합리적인 방식으로 동작합니다.
다음 그림은 01 신호 샘플링을 보여 줍니다.
이 그림에서 @stable_clk 는 FPGA 의 내부 클록이고, @data_clk 와 @data 는 FPGA 로 들어오는 신호입니다. @data_clk_samp 와 @data_samp 는 FPGA 내부의 레지스터입니다. 외부 신호 @data_clk 는 @data_clk_samp 로, @data 는 @data_samp 로 표현됩니다.
그림은 @data_clk_samp 에 "0 1" 패턴이 나타나면 @data_samp 의 값을 FIFO 에 쓴다는 것을 보여 줍니다. 이것이 이 방법을 "01 신호 샘플링"이라고 부르는 이유입니다.
여기서 FIFO 는 들어온 데이터를 어떻게 처리할지에 대한 한 가지 예로 제시한 것입니다. 내부 클록의 주파수가 데이터 전송 속도보다 훨씬 높은 경우가 많기 때문에, 더 낮은 클록 주파수로 동작하는 로직에서 데이터를 계속 처리하는 것이 편리할 때가 많습니다. FIFO 는 데이터를 다른 클록 도메인(clock domain)의 로직으로 넘겨줄 때 편리한 방법입니다.
그렇지만 01 신호 샘플링에 사용한 것과 같은 내부 클록으로 나머지 로직을 구현하는 것도 가능합니다. FIFO 를 사용하는 것은 한 가지 방법일 뿐입니다.
Verilog 예제
다음 Verilog 코드는 이 아이디어를 보여 줍니다. 외부 클록은 @data_clk 입니다.
module top (
input stable_clk,
input data_clk,
input [7:0] data
);
reg [7:0] data_guard, data_samp;
reg data_clk_guard, data_clk_samp, data_clk_samp_d;
wire fifo_wr_en;
always @(posedge stable_clk)
begin
data_guard <= data;
data_clk_guard <= data_clk;
data_samp <= data_guard;
data_clk_samp <= data_clk_guard;
data_clk_samp_d <= data_clk_samp;
end
assign fifo_wr_en = data_clk_samp && !data_clk_samp_d;
data_fifo fifo_i
(
.wr_clk(stable_clk),
.din(data_samp),
.wr_en(fifo_wr_en),
[ ... other ports connected here ... ]
);
endmodule
핵심 부분은 FIFO 의 wr_en, 즉 @fifo_wr_en 입니다. 이 신호는 "data_clk_samp && !data_clk_samp_d"와 같습니다. 따라서 이 신호는 @data_clk_samp 에서 "0 1" 패턴을 감지하면 high 가 됩니다. 그러면 @data_samp 의 값이 FIFO 에 쓰입니다.
로직이 클록으로 사용하는 것은 오직 @stable_clk 뿐입니다. @data_clk 는 일반적인 I/O 입력처럼 취급됩니다.
@data_guard 와 @data_clk_guard 의 타이밍 요구사항은 보장되지 않습니다. 입력 포트들이 @stable_clk 에 대해 비동기이기 때문입니다. 따라서 @data_guard 와 @data_clk_guard 는 메타스태빌리티(metastability) 방어용 레지스터들입니다. 로직은 이 두 레지스터의 값에 직접 의존하지 않습니다. 반면 @data_samp 와 @data_clk_samp 는 로직에서 직접 사용됩니다. 이 레지스터들이 메타스태빌리티 방어의 두 번째 단계이기 때문입니다.
그런데 이런 방식이 실제로 동작할까요? 그 답은 타이밍 분석에 있습니다.
타이밍 분석
01 신호 샘플링의 타이밍 분석은 일반적인 방식과 다릅니다. 보통은 클록 에지와 데이터 신호가 샘플링되는 순간 사이에 일정한 시간 차이가 있습니다. 샘플링에 사용하는 클록이 데이터 신호와 동기화되어 있기 때문입니다. 그러나 01 신호 샘플링에서는 @data 를 @stable_clk 으로 샘플링합니다. 따라서 샘플링 시점은 데이터 신호 자체의 타이밍과 아무 관련이 없습니다. 대신, 로직은 상승 클록 에지 부근에서 얻은 @data_clk_samp 값만 골라 사용합니다.
그렇다면 데이터 클록의 상승 에지가 발생한 시점과 실제 샘플링이 일어나는 시점 사이에는 임의의 시간 차이가 있습니다. 그런데 어떻게 이 방법을 믿을 수 있을까요?
로직 설계에서 타이밍의 기초에 관한 별도 페이지가 있습니다. 그 페이지에서는 tsu 와 thold 의 의미를 설명합니다. 간단히 요약하면, 플립플롭의 입력 포트는 클록의 상승 에지 전후에 안정적이어야 합니다(플립플롭이 상승 에지에서 동작한다고 가정할 때). tsu 는 상승 에지 전에 입력 포트가 안정적이어야 하는 시간을 뜻하고, thold 는 상승 에지 후에 입력 포트가 안정적이어야 하는 시간을 뜻합니다. 이 조건 중 하나라도 위반되면 클록 상승 에지에 대한 플립플롭의 응답은 예측할 수 없게 됩니다.
이 상황을 다른 방식으로 볼 수도 있습니다. 입력 포트는 상승 에지 주변의 특정 시간 구간 동안 안정적이어야 합니다. 이 구간을 Δt = tsu + thold 라고 부르겠습니다. 이제 Δt 와 다른 파라미터들을 바탕으로 안정적인 동작을 보장하는 타이밍 요구사항을 찾아보겠습니다.
전체 타이밍 분석은 @data_clk_guard 가 high 이고 @data_clk_samp 가 low 인 상황을 바탕으로 합니다. 이런 상황이 발생하면 "0 1" 패턴은 한 클록 사이클 뒤에 감지됩니다. 즉, 다음 클록 사이클에서 @data_clk_samp_d 는 '0'이 되고 @data_clk_samp 는 '1'이 됩니다. 그 결과 @fifo_wr_en 이 high 가 되므로, @data_samp 에는 데이터 소스가 보내려던 값이 들어 있어야 합니다.
따라서 분석은 다음 질문에 초점을 맞춥니다. @data_clk_guard 가 high 이고 @data_clk_samp 가 low 일 때, 정보가 올바르게 도착하도록 보장하려면 @data 에 어떤 요구사항이 필요한가?
분석은 두 부분으로 나누겠습니다. 두 부분 모두 @data_clk_guard 가 high 이고 @data_clk_samp 가 low 라고 가정합니다. 첫 번째 부분의 질문은 이렇습니다. 이 가정을 깨지 않으면서 @data_clk 가 low 에서 high 로 바뀔 수 있는 가장 늦은 시점은 언제일까요? 그런 다음 @data_samp 에 올바른 값이 들어 있음을 보장하는 @data 의 타이밍 요구사항을 찾겠습니다.
두 번째 부분에서는 반대 질문을 다룹니다. @data_clk_guard 와 @data_clk_samp 에 대한 가정을 깨지 않으면서 @data_clk 가 low 에서 high 로 바뀔 수 있는 가장 이른 시점은 언제일까요? 그런 다음 타이밍 요구사항에 대해 비슷한 분석을 하겠습니다.
그 전에 몇 가지 기호를 정의하겠습니다.
- tclk: @stable_clk 의 클록 주기.
- tskew: @data_clk 포트와 @data 포트 사이의 스큐(skew)입니다. 신호 소스에서 FPGA 의 플립플롭까지 이어지는 모든 연결의 신호 지연 사이에서 발생할 수 있는 최대 차이를 뜻합니다.
- tj: 클록 주기에 대한 지터(jitter)의 최대 기여분입니다. 다시 말해, 두 클록 에지 사이의 시간 차이는 항상 tclk - tj 와 tclk + tj 사이의 값입니다. 더 정확히 말하면, 이 범위를 벗어나는 클록 주기가 나타날 확률은 무시할 만큼 작습니다.
첫 번째 분석
이 타이밍 다이어그램은 @stable_clk 의 두 클록 사이클을 보여 줍니다. 아래 논의에서 @data_clk_guard 는 다이어그램 오른쪽에 있는 상승 에지에서 @data_clk 의 새 값을 받아 옵니다. @data_guard 도 같은 클록 에지에서 @data 의 새 값을 받아 옵니다.
같은 원리로 @data_clk_samp 와 @data_samp 는 @stable_clk 의 왼쪽에 있는 상승 에지와 관련됩니다.
이 시나리오에서 @data_clk 는 노란색 구간이 끝나는 바로 그 시점에 high 로 바뀝니다. 플립플롭의 타이밍 요구사항이 위반되므로 결과는 예측할 수 없습니다. 하지만 @data_clk_guard 가 high 이고 @data_clk_samp 가 low 가 될 가능성은 있습니다.
하지만 @data_clk 가 조금만 더 늦게 값을 바꾼다면 @data_clk_guard 는 반드시 low 가 됩니다. 플립플롭의 동작이 예측 가능하기 때문입니다. 이 시나리오에서는 노란색 구간 전체 동안 입력 포트가 low 이기 때문입니다. 따라서 다음과 같이 말할 수 있습니다. @data_clk_guard 가 high 이고 @data_clk_samp 가 low 라면, @data_clk 는 위 타이밍 다이어그램에 표시된 것보다 더 일찍 low 에서 high 로 바뀌었습니다. 즉, 위 다이어그램은 @data_clk 가 가장 늦게 바뀔 수 있는 시나리오를 보여 줍니다.
@data_guard 에 신뢰할 수 있는 값이 들어 있으려면 @data 는 노란색 구간 이전에 안정적이어야 합니다. 여기서 첫 번째 타이밍 요구사항이 나옵니다. @data 는 @data_clk 의 상승 에지 전에 Δt 시간 동안 안정적이어야 합니다.
@data_clk 가 low 에서 high 로 더 일찍 바뀌더라도, 이 타이밍 요구사항은 여전히 @data_guard 의 tsu 요구사항을 보장합니다. 따라서 @data_clk_guard 가 high 이고 @data_clk_samp 가 low 인 모든 시나리오에서 플립플롭의 tsu 는 보장됩니다.
위 타이밍 다이어그램에는 스큐와 관련된 내용이 표시되어 있지 않습니다. 스큐를 고려하면 타이밍 요구사항은 다음과 같아집니다. @data 는 @data_clk 의 상승 에지 전에 Δt + tskew 시간 동안 안정적이어야 합니다.
이 시나리오에서는 지터를 고려할 필요가 없습니다. 논의에 클록 에지가 하나만 관련되어 있기 때문입니다.
두 번째 분석
이 시나리오에 대한 타이밍 다이어그램은 다음과 같습니다.
이 시나리오에서 @data_clk 는 @stable_clk 의 이전 클록 에지에 있는 노란색 구간이 시작되는 바로 그 시점에 high 로 바뀝니다.
이 상황에서 @data_clk_guard 가 high 가 될 것에는 의심의 여지가 없습니다. 그러나 @data_clk_samp 의 값은 예측할 수 없습니다. 이전 클록 사이클에서 타이밍 위반이 있었기 때문입니다. 앞에서와 마찬가지로 @data_clk_samp 가 low 가 될 가능성도 있습니다. 하지만 @data_clk 가 이 시점보다 더 일찍 값을 바꾸면 @data_clk_samp 는 반드시 high 가 됩니다.
따라서 @data_clk_guard 가 high 이고 @data_clk_samp 가 low 라면, @data_clk 는 위 타이밍 다이어그램에 표시된 것보다 더 늦게 low 에서 high 로 바뀌었습니다. 즉, 위 다이어그램은 @data_clk 가 가장 일찍 바뀔 수 있는 시나리오를 보여 줍니다.
@data_guard 에 올바른 값이 들어 있으려면 @data 는 오른쪽에 있는 노란색 구간 이후에도 안정적이어야 합니다.
위 타이밍 다이어그램에 따르면 @data_clk 의 상승 에지에서 두 번째 노란색 구간이 끝나는 시점까지의 시간 차이는 Δt + tclk 입니다. 여기에 스큐와 지터도 고려해야 합니다. 따라서 @data 는 @data_clk 의 상승 에지 이후 Δt + tclk + tskew + tj 시간 동안 안정적이어야 합니다.
@data_clk 가 low 에서 high 로 더 일찍 바뀌더라도, 이 타이밍 요구사항은 여전히 @data_guard 의 thold 요구사항을 충족시킵니다. 따라서 @data_clk_guard 가 high 이고 @data_clk_samp 가 low 인 모든 시나리오에서 플립플롭의 thold 는 보장됩니다.
타이밍 요구사항
위 타이밍 분석을 종합하면, 01 신호 샘플링의 안정적인 동작을 보장하기 위한 타이밍 요구사항은 다음 두 가지입니다.
- @data 는 @data_clk 의 상승 에지 전에 적어도 Δt + tskew 시간 동안 안정적이어야 합니다.
- @data 는 @data_clk 의 상승 에지 후에 적어도 Δt + tclk + tskew + tj 시간 동안 안정적이어야 합니다.
이 요구사항은 복잡해 보일 수 있지만, 실제로 충족시키기는 쉬운 경우가 많습니다. 예를 들어 @data 가 @data_clk 의 하강 에지에 맞춰 바뀐다면, 이 요구사항들을 보장하기가 대체로 쉽습니다. @stable_clk 가 @data_clk 보다 3배 빠르다면 그것만으로 충분한 경우가 많습니다.
대부분의 경우 Δt, tskew, tj 의 정확한 값을 알 필요가 없습니다. 위 두 요구사항으로부터 Δt 와 Δt + tskew + tj 가 각각 얼마까지 커도 되는지만 계산하면 충분합니다. @stable_clk 의 주파수가 충분히 높으면 이 두 값에 허용되는 범위는 FPGA 에서 현실적으로 가능한 값보다 커지는 경우가 많습니다.
IOB 레지스터를 사용하여 FPGA 입력 포트 사이의 타이밍 차이(tskew)를 최소화해야 합니다. 또한 이런 입력 포트들에 대해 타이밍 제약 조건(timing constraints)을 작성하여 IOB 레지스터가 사용되도록 해야 합니다.
01 신호 샘플링의 변형
지금까지 논의에서는 @data 가 data_clk 의 하강 에지에 맞춰 바뀐다고 가정했습니다. 만약 @data 가 @data_clk 의 상승 에지에 맞춰 바뀐다면, 로직이 @data_clk_guard 가 low 이고 @data_clk_samp 가 high 일 때 동작하도록 조정해야 합니다. 다시 말해, @data_clk 의 하강 에지를 "1 0" 패턴으로 감지하도록 로직을 바꾸어야 합니다.
@data 의 값을 얻을 시점을 판단하는 기준을 다르게 정할 수도 있습니다. 예를 들어 @fifo_wr_en 을 몇 클록 사이클 늦추는 것이 더 나을 수 있고, 때로는 @fifo_wr_en 이 위에서 제안한 것보다 더 일찍 활성화되는 것이 더 나을 수도 있습니다. 이것은 @data_clk 와 @data 사이의 타이밍 관계에 따라 달라집니다. 신호 소스의 데이터시트를 참조하고, 위에서 설명한 방식대로 타이밍을 분석하십시오.
@data_clk 의 주파수가 비교적 높다면, @data_clk 와 @data 를 샘플링할 때 DDR 레지스터를 사용할 수 있습니다. 이렇게 구현하는 로직은 조금 더 복잡하지만, 적용되는 원리는 같습니다.
@data_guard 는 정말 메타스태빌리티 방어용 레지스터인가?
짧게 답하면, 그렇습니다. @data 는 @stable_clk 에 대해 비동기이므로 @data_guard 의 타이밍 요구사항은 보장되지 않습니다.
하지만 논의의 범위를 @data_guard 의 값이 실제로 사용되는 클록 사이클로 좁혀 보겠습니다. 위의 두 타이밍 요구사항은 @data_guard 를 구현하는 플립플롭이 안정적으로 동작하도록 보장합니다. 이 플립플롭들의 tsu 와 thold 가 모두 보장됩니다.
따라서 @data_guard 는 실제로는 메타스태빌리티 방어용으로 사용되는 것이 아닙니다. 이 레지스터가 사용되는 방식은 단지 지연 레지스터(delay register)로 동작할 뿐입니다. 하지만 물리적 신호(@data)가 비정상적으로 동작할 때 발생할 수 있는 타이밍 위반을 추가 레지스터가 막아 주는 것은 나쁠 것이 없습니다. 특히 신호가 커넥터를 통해 FPGA 에 연결되는 경우에는 이것이 중요합니다.
실제 사례
OV7670 카메라 센서와 연결하는 로직의 예는 다른 페이지에 있습니다. 그 로직은 01 신호 샘플링을 이용해 이 카메라 센서로부터 픽셀 데이터를 얻습니다. 카메라 센서의 입력은 다음과 같습니다.
input pclk_in;
input [7:0] D_in;
input hsync_in, vsync_in;
01 신호 샘플링을 수행하는 로직은 다음과 같습니다.
(* IOB = "TRUE" *) reg [7:0] D_guard;
(* IOB = "TRUE" *) reg pclk_guard, hsync_guard, vsync_guard;
reg [7:0] D;
reg pclk, hsync, vsync;
wire sample_valid;
reg previous_pclk;
always @(posedge stable_clk)
begin
// Metastability guards on asynchronous inputs
D_guard <= D_in;
pclk_guard <= pclk_in;
hsync_guard <= hsync_in;
vsync_guard <= vsync_in;
D <= D_guard;
pclk <= pclk_guard;
hsync <= hsync_guard;
vsync <= vsync_guard;
previous_pclk <= pclk;
end
assign sample_valid = pclk && !previous_pclk;
명확하게 설명하기 위해, 여기에 제시한 Verilog 코드와 OV7670 페이지의 Verilog 코드는 약간 다릅니다. 이 차이들은 로직의 동작에는 영향을 주지 않습니다.
이 Verilog 코드를 이 페이지 상단에서 제시한 코드와 비교해 보겠습니다. 신호 이름은 다르지만 의미는 같습니다. @data 대신 @D_in, @hsync_in, @vsync_in 이 있고, @data_clk 대신 @pclk_in 이 있습니다. 그리고 @fifo_wr_en 대신 @sample_valid 가 있습니다. 이름이 바뀌어서 혼란스러울 수 있지만, 위의 Verilog 코드와 달라진 것은 없습니다.
레지스터 선언 앞에 있는 "(* IOB = "TRUE" *)" 부분을 주목하십시오. Vivado 를 사용할 때 이렇게 하면 레지스터가 IOB 안에 배치되도록 요청할 수 있습니다.
이 예제에는 FIFO 가 없습니다. 모든 데이터를 FIFO 에 쓰고 싶지 않기 때문입니다. @sample_valid 가 high 라는 것은 @D, @hsync, @vsync 에 카메라 센서로부터 온 올바른 값이 들어 있다는 뜻입니다. 그러나 그것이 @D 를 FIFO 에 쓰고 싶다는 뜻은 아닙니다. @hsync 와 @vsync 에 따라 달라집니다. 따라서 OV7670 예제에는 픽셀만 FIFO 에 쓰이도록 하는 추가 로직이 있습니다.
이제 흥미로운 부분, 바로 타이밍 분석으로 넘어가 보겠습니다.
이 예제에서 @stable_clk 의 주파수는 100 MHz 이고 @pclk_in 의 주파수는 25 MHz 입니다. OV7670 의 데이터시트에 따르면 @D_in, @hsync_in, @vsync_in 신호는 @pclk_in 이 high 에서 low 로 바뀐(하강 에지) 후 5 ns 동안 안정적이라고 보장됩니다.
@pclk_in 의 클록 주기는 40 ns 이므로, 하강 에지에서 다음 상승 에지까지의 거리는 20 ns 입니다. 이제 타이밍 요구사항과 비교해 보겠습니다.
첫 번째 타이밍 요구사항은 @D_in, @hsync_in, @vsync_in 이 @pclk_in 의 상승 에지 전에 Δt 시간 동안 안정적이어야 한다는 것이었습니다. 실제로 이 신호들은 하강 에지 후 5 ns 부터 안정적입니다. 따라서 다음 상승 에지가 오기 최소 15 ns 전에는 안정적입니다. Δt = tsu + thold 임을 기억하십시오. 따라서 실제 요구사항은 tsu + thold 가 15 ns 보다 작다는 것입니다. 이것은 모든 FPGA 에서 성립합니다.
두 번째 요구사항은 이 신호들이 @pclk_in 의 상승 에지 후에 적어도 Δt + tclk + tskew + tj 시간 동안 안정적이어야 한다는 것이었습니다. 그런데 이 신호들은 하강 에지가 있을 때만 바뀝니다. 따라서 요구사항은 Δt + tclk + tskew + tj 가 20 ns 보다 작아야 한다는 것입니다. @stable_clk 의 주파수가 100 MHz 이므로 tclk 는 10 ns 입니다. 따라서 실제 요구사항은 Δt + tskew + tj 가 10 ns 보다 작아야 한다는 것입니다. 이것 역시 모든 FPGA 에서 당연히 성립합니다.
이 예제는 FPGA 의 정확한 타이밍 파라미터를 몰라도 타이밍 요구사항을 쉽게 충족할 수 있음을 보여 줍니다.
결론
01 신호 샘플링은 데이터 전송 속도가 FPGA 가 지원할 수 있는 클록 주파수에 비해 낮을 때 아주 좋은 해결책입니다. 데이터 클록이 안정적일 필요도 없고, 데이터 클록의 정확한 주파수를 미리 알 필요도 없습니다. 두 가지 타이밍 요구사항만 보장하면 충분합니다.
이 방법에는 추가적인 장점도 있습니다. 이 클록이 잠시 비활성화되어도, 그 시간 동안 데이터를 수집하지 못하는 것이 전부입니다. 클록이 어떤 식으로 오동작하든 그 피해는 데이터 흐름의 중단으로 제한됩니다. 이로 인해 시스템에 눈에 띄는 오동작이 생기겠지만, 그 오동작은 클록 문제처럼 보입니다. FPGA 가 귀신에 홀린 것처럼 보이지는 않는다는 뜻입니다.
따라서 데이터 신호의 샘플링에 본질적인 무작위성이 있음에도 불구하고, 01 신호 샘플링은 소스 동기식(source-synchronous) 입력에 대해 안정적이고 견고한 해결책입니다. 실제로 유일한 단점은 데이터 전송 속도에 제한이 있다는 점입니다.


