01signal.com

타이밍 제약 조건과 클록 도메인 크로싱

이 페이지는 타이밍(timing)에 관한 연재 글 모음의 일부입니다. 지금까지의 페이지에서는 타이밍 계산의 이론을 설명하고, 몇 가지 타이밍 제약 조건(timing constraints)을 작성하는 방법을 보여 주었으며, 타이밍 클로저(Timing Closure)의 원리도 다뤘습니다. 이 페이지에서는 클록 도메인(clock domain)과 관련된 타이밍 제약 조건을 정의하는 방법을 설명합니다.

서론

특정 로직 요소를 찾는 방법과 경로(path)를 정의하는 방법을 익혔으니, 이제 이 지식을 활용하는 타이밍 제약 조건들을 살펴보겠습니다.

먼저 타이밍 제약 조건과 클록 도메인 사이의 관계를 보겠습니다. 이 두 주제는 밀접하게 연관되어 있어서, 하나를 논의할 때 다른 하나를 끌어들이지 않을 수 없습니다. 그러므로 클록 도메인 주제가 낯설다면 이 연재 글 모음을 읽어 볼 것을 권합니다. 무엇보다도 아래에서 사용할 용어를 이해하는 데 필요합니다.

로직이 클록 사이의 관계를 정의한다

다음 Verilog 코드 예를 살펴보겠습니다.

module top(
    input clk,
    input foo,
    output reg bar_reg,
    output reg baz
);
    reg foo_reg;
    reg bar;
    reg baz_metaguard;
    wire pll_clk_8, pll_clk_6;

   clk_wiz_1 pll_i
   (.clk_in1(clk),
    .clk_out1(pll_clk_8),
    .clk_out2(pll_clk_6));

always @(posedge pll_clk_8)
  foo_reg <= foo;

always @(posedge pll_clk_6)
  begin
    bar <= !foo_reg;
    bar_reg <= bar;
  end

always @(posedge clk)
  begin
    baz_metaguard <= bar;
    baz <= baz_metaguard;
  end

이 코드는 앞서 본 PLL 예와 거의 같습니다. 차이점은 새 클록 도메인 크로싱(clock domain crossing)이 하나 있다는 것입니다. @bar 의 값이 메타스태빌리티(metastability) 보호용 레지스터를 거쳐 @baz 로 복사됩니다.

따라서 이 예에는 두 가지 유형의 클록 도메인 크로싱이 있습니다.

클록 도메인 크로싱의 유형을 판별할 때 저는 단 하나의 기준만 살펴봤습니다. 메타스태빌리티 보호용 레지스터의 존재 여부입니다. 로직이 무관한 클록을 기대한다면 필요한 안전 메커니즘을 사용하게 됩니다. 따라서 그 외의 다른 것은 중요하지 않습니다. 이 클록들 사이의 경로에 타이밍 요구사항을 충족시킬 수 있다고 해도, 그렇게 할 이유가 없습니다.

반대의 경우도 마찬가지입니다. 로직이 관련 클록을 기대한다면 타이밍 위반에 대한 보호 장치가 없습니다. 따라서 이 클록들 사이의 모든 경로에서 타이밍 요구사항이 충족되어야 합니다.

결론적으로, 로직은 항상 어떤 클록들이 관련 클록이고 어떤 클록들이 그렇지 않은지에 대해 가정을 합니다. 이러한 가정은 보호 메커니즘의 존재 여부로 드러납니다. 그러나 그 가정이 실제로 실현되도록 만드는 것은 FPGA 툴입니다. 특히 툴은 관련 클록 사이의 경로에서 타이밍 요구사항이 충족되도록 보장합니다.

따라서 우리는 툴이 로직의 클록에 대한 가정을 알 수 있게 해야 합니다.

위 예에서 툴은 @pll_clk_6 과 @clk 에 대한 로직의 기대를 알 방법이 없다는 점에 유의하세요. 로직은 이 둘을 관련 클록으로 가정할 수도 있고 그 반대일 수도 있습니다. 사실 저는 앞서("아이디어 #9" 참조) 비슷한 예를 관련 클록 사이의 경로를 설명하는 데 사용했습니다. 그런데 위 예에서 이 두 클록은 무관한 클록으로 취급됩니다.

툴이 @baz_metaguard 라는 이름을 보고 똑똑한 추측을 할 수도 있을 것입니다. 메타스태빌리티 보호용 레지스터의 전형적인 구조가 힌트가 될 수도 있겠지요. 그러나 그런 것으로는 이렇게 중요한 문제를 결정하기에 충분하지 않습니다.

툴에게 클록에 대해 알려 주기

지금까지 예에서 타이밍 제약 조건은 단 하나뿐이었습니다.

create_clock -period 4.000 -name clk [get_ports clk]

가장 먼저 떠오르는 질문은 툴이 기본적으로 @pll_clk_6 에서 @clk 로 가는 클록 도메인 크로싱을 어떻게 처리하는가입니다. 이것이 유일한 타이밍 제약 조건이라면, 툴이 @bar 와 @baz_metaguard 사이의 경로에 아무것도 적용하지 않을까요?

답은 어떤 FPGA 툴을 사용하는지에 따라 다르다는 것입니다. 거의 모든 FPGA 툴이 @pll_clk_6 과 @pll_clk_8 을 관련 클록으로 간주할지라도, 다른 클록 쌍에 대해서는 어떻게 처리될지 분명하지 않습니다. 툴은 대개 기본적으로 모든 클록을 관련 클록으로 취급하지만, 그것에 의존하는 것은 안전하지 않습니다.

많은 FPGA 툴이 set_clock_groups 명령을 지원합니다. 이것은 클록 사이의 관계를 정의하는 가장 좋은 옵션입니다. 이 명령이 중요하므로 설계에 사용하기 전에 툴 문서를 참조하는 것이 좋습니다.

위 예의 경우, Vivado에서는 이 명령을 다음과 같이 사용할 수 있습니다(다른 툴에서도 비슷합니다).

set_clock_groups -asynchronous \
  -group [list \
     [get_clocks -of_objects [get_pins pll_i/clk_out1] ] \
     [get_clocks -of_objects [get_pins pll_i/clk_out2] ] ] \
  -group [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

get_clocks와 get_pins 의 이런 사용법은 앞서 설명했습니다. 각 get_clocks 명령은 clk_wiz_1 의 해당 포트 이름에 따라 클록 객체를 찾습니다(clk_wiz_1 의 인스턴스 이름이 pll_i 라는 점에 유의). 예를 들어 마지막 get_clocks 명령은 @clk 에 대한 클록 객체를 가져옵니다.

이 예는 set_clock_groups가 클록 그룹을 어떻게 정의하는지 보여 줍니다. 이 경우 한 그룹은 @pll_clk_6 과 @pll_clk_8 로 이루어지고, 두 번째 그룹은 @clk 만으로 이루어집니다. 이 제약 조건은 같은 그룹에 속한 모든 클록이 관련 클록임을 툴에 알려 줍니다. 마찬가지로, 두 클록이 서로 다른 그룹에 속하면 툴은 그 클록들을 무관한 클록으로 간주합니다.

다시 말해, 툴은 경로 양쪽의 클록이 같은 그룹에 속하는 경우에만(그리고 그런 경우에만) 그 경로에 타이밍 제약 조건을 적용합니다.

설계의 타이밍 제약 조건에 set_clock_groups 명령을 여러 번 써도 됩니다. 그러나 모든 클록을 그룹으로 나누는 데는 set_clock_groups 하나만 사용하는 것이 가장 좋습니다. 모순을 피할 수 있을 뿐 아니라 혼동을 막는 데도 도움이 됩니다. 이 명령의 주요 장점은 클록 사이의 관계를 간결하게 표현한다는 것입니다. 짧고 간결하며 수학적입니다. 그것이 바로 우리가 원하는 방식입니다.

set_clock_groups는 모든 create_clock 명령 뒤에 사용해야 합니다. 언급된 클록 객체가 이미 존재해야 하기 때문입니다.

클록 사이의 관계가 일관되지 않은 경우

설계의 어떤 곳에서는 클록들이 관련 클록으로 간주되고, 다른 곳에서는 무관한 클록으로 간주된다면 set_clock_groups를 사용할 수 없습니다.

예를 들어 위 Verilog 코드에서 @clk 와 @pll_clk_6 은 무관한 클록으로 취급됩니다. 그러나 이론적으로는 이 둘을 관련 클록으로 취급하는 추가 로직이 있을 수도 있습니다. 좋은 생각은 아니지만, 그 추가 로직에 대해서는 @pll_clk_6 과 @clk 사이에 타이밍 제약 조건을 적용하는 것이 가능합니다.

이런 이유로 set_clock_groups를 사용할 수 없다면, 먼저 로직을 바꿔서 set_clock_groups를 쓸 수 있게 하고 싶지 않은지 스스로 물어보세요. 단지 이 명령이 좋아서가 아니라, 어떤 클록이 관련 클록이고 어떤 클록이 아닌지를 알려 주는 단순한 규칙이 없으면 클록 도메인 크로싱에서 실수하기 쉽기 때문입니다. 그래도 로직을 그렇게 바꾸고 싶지 않다면, 해결책은 false path를 정의하는 것입니다.

set_false_path 명령에 대해 간단히

false path를 선언하는 기본 명령은 두 가지입니다. set_clock_groups와 set_false_path입니다.

set_clock_groups 명령은 위에서 설명했습니다. 서로 다른 그룹에 속한 두 클록 사이의 모든 경로는 false path로 간주됩니다. 그러나 false path로 선언해야 하는 모든 경로에 대해 set_clock_groups를 쓸 수 있는 것은 아닙니다. 어떤 경우에는 클록 그룹을 정의하는 것보다 더 구체적인 경로 선택이 필요합니다. set_false_path 명령은 특정 경로를 선택할 수 있게 함으로써 이 문제를 해결합니다.

예를 들어, 위의 set_clock_groups 명령을 set_false_path 명령으로 바꿔 보겠습니다. 고쳐야 할 것은 @bar 에서 @baz_metaguard 로 가는 경로뿐입니다. 다른 모든 경로의 타이밍 요구사항은 현재 상태대로 올바르게 적용됩니다. 따라서 이 경로에 대한 set_false_path 명령을 작성하는 방법은 다음과 같을 수 있습니다. 그런데 이 예를 본받지는 마세요.

set_false_path -from [get_cells bar_reg__0] -to [get_cells baz_metaguard_reg]

이 명령은 get_cells를 사용하여 경로의 시작과 끝을 선택합니다. 그러려면 양쪽의 셀 객체 이름을 알아야 합니다. 이 경우 "bar_reg__0"이라는 이름이 나오는데, 이것은 이름에 의존할 때의 문제를 잘 보여 줍니다. 이 이름은 "bar_reg"였어야 하지만, 앞서 설명했듯이 우연한 사정 때문에 "bar_reg__0"이 되었습니다.

그래서 이 예는 set_false_path의 문제 중 하나를 보여 줍니다. 설계에서 로직 요소를 세밀하게 선택하려면 객체의 이름에 의존해야 하는 경우가 많다는 것입니다. 이 문제와 가능한 해결책은 앞서 논의했습니다.

이 명령은 더 단순하게 만들 수 있습니다. @baz_metaguard 는 메타스태빌리티 보호용 레지스터이므로, 이 레지스터로 들어오는 경로가 어디에서 오는지는 중요하지 않습니다. 그렇다면 이 레지스터에 도달하는 모든 경로를 무시하면 어떨까요?

set_false_path -to [get_cells baz_metaguard_reg]

이 명령의 효과는 정확히 같습니다.

set_clock_groups 대신 set_false_path

또 다른 가능성은 클록을 기준으로 경로를 정의하는 것입니다. 사실 위의 set_clock_groups 명령은 다음 제약 조건들로 대체할 수 있습니다.

set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out1]] \
               -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out2] ] \
               -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

set_false_path -from [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] \
               -to [get_clocks -of_objects [get_pins pll_i/clk_out1] ]

set_false_path -from [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] \
               -to [get_clocks -of_objects [get_pins pll_i/clk_out2] ]

이것은 같은 false path를 만드는 길고 자세한 방법입니다. 클록을 그룹으로 나누는 대신, 무관한 클록의 각 쌍마다 set_false_path 명령이 두 개씩 있습니다. 각 방향에 하나씩입니다. 이렇게 많은 제약 조건으로 실수하기가 얼마나 쉬운지는 분명합니다. 그리고 이것은 클록이 세 개뿐인 단순한 예입니다.

그럼에도 불구하고 set_false_path는 set_clock_groups 대신 이런 식으로 자주 사용됩니다. 아마 누군가가 어딘가에서 타이밍 제약 조건을 복사해 왔기 때문일 가능성이 높습니다.

실수가 어떻게 발생할 수 있는지에 대한 예

가능한 실수의 간단한 예를 살펴보겠습니다. 위의 논의에서 false path로 선언해야 하는 경로는 단 하나, 즉 @bar 에서 @baz_metaguard 로 가는 경로뿐임이 분명합니다. 따라서 마지막 예의 set_false_path 명령 네 개 가운데 다음 하나만 의미가 있습니다.

set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out2] ] \
               -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

나머지 set_false_path 명령 세 개는 어떤 경로도 다루지 않습니다. 잠깐, 제약 조건을 더 단순하게 만들면 어떨까요? @clk 로 끝나는 모든 경로를 false path로 만들면 안 될까요? 다음과 같이 하면 어떨까요?

set_false_path -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

사실 "clk"를 정의하는 create_clock 명령이 set_false_path 명령 바로 위에 있으므로, 이렇게 쓸 수도 있습니다.

set_false_path -to [get_clocks clk]

이 제약 조건은 짧고 우아합니다. 불행히도 끔찍하게 틀렸습니다. @clk 로 끝나는 모든 경로를 false path로 만들자고 제안했습니다. 그런데 @baz_metaguard 에서 @baz 로 가는 경로는 어떨까요? 그것은 @clk 에서 @clk 로 가는 경로입니다. 이 경로는 당연히 false path가 아니어야 합니다. 그런데 마지막 두 set_false_path 명령은 경로가 @clk 에서 시작하더라도 @clk 로 끝나는 모든 경로를 포함합니다.

이런 종류의 실수는 특히 false path 제약 조건을 수학적인 사고방식의 정밀함 없이 작성할 때 쉽게 일어납니다. 어떤 경로가 false path이고 어떤 경로가 아닌지 드러내 주는 특별한 타이밍 리포트를 만들어 보는 것도 도움이 될 수 있습니다.


이상으로 FPGA 내부 경로에 대한 타이밍 제약 조건의 실용적인 논의를 마칩니다. 다음 페이지에서는 이 주제에 속하는 멀티사이클 경로(multicycle path)를 설명합니다. 다만 멀티사이클 경로 제약 조건은 대개 권장되지 않습니다. 그러니 I/O 제약 조건 입문으로 건너뛰어도 좋습니다.

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