01signal.com

FPGA를 제대로 기동하고 리셋하기 위한 로직

이 페이지는 FPGA의 리셋에 관한 시리즈의 세 번째이자 마지막 글입니다. 이 글을 읽기 전에 처음 두 페이지를 먼저 읽어 보시기를 권합니다.

개요

리셋 신호를 생성하는 적절한 로직을 구축하는 데 시간을 투자하는 것은 언제나 좋은 생각입니다. 현재 설계에 딱히 문제가 없어 보여도, 이 단계를 건너뛰면 나중에 대가를 치르게 될 가능성이 높습니다. 나중에 어떤 불안정 문제를 해결하느라 며칠을 보내면서도, 그 원인이 로직이 제대로 초기화되지 않았기 때문이라는 사실을 깨닫지 못할 수도 있습니다. 시간이 지나면서 이런 문제를 해결하려는 시도 속에서, 설계에는 문제의 근본 원인을 이해하지 못한 채 만들어진 지저분한 우회책들이 쌓이게 마련입니다. 이상한 불안정성에 관해서는 이 페이지를 참조하시기 바랍니다.

프로젝트의 시작 단계부터 리셋 신호를 깊이 있게 고민하고 설계하는 것은, 새로운 기능이 추가되면서 프로젝트가 올바르게 성장하도록 보장하는 데에도 중요합니다. FPGA 프로젝트를 처음부터 시작할 때는 보통 핵심 기능을 먼저 구현하고, 시간이 지나면서 기능을 추가하게 됩니다. 서로 다른 모듈이 각각 별도의 클록과 리셋을 필요로 하는 경우가 많기 때문에, 각 부분을 위해 임시방편으로 빠르게 코드를 끼워 맞추는 함정에 빠지기가 매우 쉽습니다. 그렇게 되면 프로젝트가 진행될수록 점점 더 혼란스러워지곤 합니다.

리셋과 클록을 담당하는 중앙 모듈을 프로젝트 첫날부터 제대로 작성해 두면 지저분한 프로젝트를 피하기가 더 쉽습니다. 그리고 아래에서 더 설명하듯이, 리셋 컨트롤러와 클록 리소스(필요에 따라 PLL과 클록 버퍼)는 서로 영향을 주고받으므로, 이들을 같은 모듈에 두는 것이 효과적입니다. 저는 보통 이 모듈의 이름을 clkrst.v라고 짓습니다.

하지만 일부 IP 코어(IP core), 서브시스템 또는 설계 블록이 자체적인 리셋과 클록 그룹을 생성할 수 있기 때문에, 이 모든 것을 하나의 모듈에 집중시키는 것이 항상 가능하지는 않습니다. 이런 경우에는 무엇이 무엇에 의존하는지, 그리고 서로 다른 소스에서 들어오는 리셋 요청에 전체 시스템이 어떻게 반응해야 하는지를 주의 깊게 고민해야 합니다. 예를 들어 PCIe 블록으로 가는 리셋은 거의 항상 버스 자체에서 들어오며, 그 블록은 이 블록에 연결된 로직이 사용할 리셋 신호를 생성합니다. 이런 상황에서는 예를 들어 PCIe 버스에서 리셋이 들어왔을 때(전혀 반응하지 않을 가능성도 포함하여) 시스템 전체가 어떻게 대응해야 하는지를 고려할 필요가 있습니다.

프로젝트마다 사정이 다르기 때문에 모든 경우에 적용되는 단일 해법은 없습니다. 이 페이지에서는 개념을 설명하고, 여러분 자신의 리셋 컨트롤러를 만들 때 조립 블록으로 사용할 수 있는 아이디어와 코드 조각을 제시합니다. 다만 아래 코드 조각들은 프로젝트에 바로 복사해서 붙여 넣기 위한 것이 아니라, 개념을 보여 주기 위한 예시로 취급해야 합니다.

설명을 단순하게 유지하기 위해, 시스템의 다른 어떤 블록도 클록이나 리셋을 생성하지 않는다고 가정하겠습니다. 하지만 이 컨트롤러를 일반적인 경우로 확장하는 것은 비교적 간단합니다.

리셋 상태 머신

대부분의 설계에서 리셋이 필요한 주요 시나리오는 두 가지입니다:

그 외에도 워치독 타이머가 만료되거나 다른 수단으로 중대한 시스템 오류가 감지되면 리셋이 필요할 수 있습니다.

이 모든 시나리오에 대한 예상되는 응답은 근본적인 재시작입니다. 즉, 무엇이 잘못되었든 그것이 바로잡히도록 보장하는 종류의 재시작입니다. 이는 대개 어떤 상태 머신(state machine)을 통해 구현하는 것이 가장 좋습니다. 그러면 어떤 이유로 시작되었든 일관되고 반복 가능한 리셋 시퀀스를 보장할 수 있습니다.

그런데 말하자면, 국소적이고 어쩌면 주기적으로 반복되는 리셋이 타당한 시나리오도 있습니다. 예를 들어 비디오 이미지 프레임을 처리하는 로직은 각 프레임이 시작되기 전에 리셋될 수 있습니다. 이런 경우에는 가벼운 메커니즘이 요구되는데, 결국 로컬 동기 리셋(synchronous reset)이 활성화될 때 여러 레지스터에 초기 값을 할당하는 것으로 귀결됩니다. 이것은 여타 리셋 메커니즘과 마찬가지이며, 견고한 동작을 보장하는 깔끔하고 간단한 방법인 경우가 많습니다.

하지만 이 가능성에 대해 더 추가할 내용은 많지 않으므로, 이 페이지의 나머지 부분에서는 FPGA 전체를 포괄하는 근본적인 리셋에 초점을 맞추겠습니다.

불안정한 클록과 리셋의 필요성

FPGA 자체의 PLL을 사용하여 클록을 생성하는 것은 매우 흔한 일이며(그리고 일반적으로 권장됩니다). 하지만 이러한 PLL은 보통 FPGA의 다른 모든 로직이 활성화되는 동시에 동작을 시작합니다. 그 결과, 이 클록들에 의존하는 로직은 정상보다 크게 높을 수 있는 주파수의 불안정한 클록을 공급받게 됩니다.

이런 일이 발생하면 관련 로직 경로(path)의 타이밍은 보장되지 않습니다. 따라서 PLL로 생성된 클록에 의존하는 로직은 PLL이 잠길(lock) 때까지 타이밍 제약(timing constraints)을 달성하지 못한 것처럼 취급해야 합니다.

적절하고 일반적인 해결책은 PLL이 잠길 때까지 그러한 모든 로직을 리셋 상태로 유지하는 것입니다. 또는 이 로직이 PLL이 잠길 때까지 클록을 무시하도록 만들 수도 있습니다. 예를 들어 플립플롭의 클록 인에이블(clock enable) 입력(CE)이 비활성 상태가 되도록 하면 됩니다.

외부 클록이 FPGA의 핀에서 로직 요소로 직접 연결되는 경우에는, 클록 생성기(보통 PLL)가 FPGA가 비트스트림(bitstream)을 로딩하는 것보다 더 빨리 잠겼는지가 문제가 됩니다. 이것을 확실하게 알기가 항상 쉬운 것은 아닙니다.

결과적으로 대부분의 설계에서는 많은 동기 소자에 리셋이 필요합니다. 더 정확히 말하면, 불안정한 클록 때문에 리셋이 필요한지의 문제와 관련하여, 이러한 소자들을 세 가지 그룹으로 나눌 수 있습니다:

일부 FPGA에서는 설정(configuration) 과정(즉, 비트스트림(bitstream)을 로딩하고 FPGA를 초기화하는 과정)을, 모든 PLL이 잠길 때까지 FPGA의 웨이크업을 지연시키도록 구성할 수 있다는 점도 언급할 가치가 있습니다. 이 방법이 이 문제를 해결하는 한 가지 방법이 될 수 있습니다. 이 해결책의 단점은 외부 클록에 문제가 있으면 FPGA가 아예 시작되지 않는다는 것입니다. 이런 상황은 매우 혼란스러울 수 있습니다.

간단한 상태 머신

근본적인 시작 또는 재시작은 아주 드물게 발생하는 사건이므로, 필요 이상으로 몇 마이크로초가 더 걸려도 문제되지 않습니다. 많은 경우 최대 100ms 정도까지도 괜찮으며, 이것을 활용할 수 있습니다. 따라서 단순한 카운터는 일련의 이벤트 순서를 구현하는 간단한 방법입니다.

가장 단순한 형태는 대략 다음과 같습니다:

reg [4:0] reset_count;
reg       rst_src_pll, rst_src_shreg, rst_src_debounce;
reg       clear_counter;
reg       master_reset;

initial reset_count = 0;
initial master_reset = 1;
initial clear_counter = 1;

always @(posedge wakeup_clk)
  begin
    clear_counter <= rst_src_pll || rst_src_shreg || rst_src_debounce;

    master_reset <= (reset_count != 31);

    if (clear_counter)
      reset_count <= 0;
    else if (reset_count != 31)
      reset_count <= reset_count + 1;
  end

@rst_src_pll, @rst_src_shreg, @rst_src_debounce는 시스템을 리셋해야 하는 서로 다른 이유를 나타냅니다. 이 레지스터들은 다른 어떤 로직에 의해 값이 주어집니다. 그러한 로직의 예를 몇 가지 살펴보겠습니다. 하지만 지금 중요한 점은 이 레지스터들이 @wakeup_clk와 동기(synchronous)라는 것입니다(따라서 클록 도메인 크로싱(clock domain crossing)은 필요하지 않습니다).

@clear_counter는 이러한 리셋 사유들의 논리 OR입니다. 이 레지스터는 @reset_count를 0으로 만듭니다. 그렇지 않으면 이 카운터는 (이 예제에서) 0부터 31까지 올라간 다음 멈춥니다.

마지막으로, @master_reset은 @reset_count가 카운팅을 끝마칠 때까지 활성 상태입니다. 이것이 리셋 상태 머신(state machine)의 가장 단순한 형태이며, 31까지 세는 것도 상당히 온건한 편입니다.

그래서 어떤 @rst_src_N 신호가 단 하나의 클록 사이클 동안만 활성화되더라도, 동기 리셋(synchronous reset)은 31 클록 사이클 동안 활성화됩니다.

긴 리셋 펄스에는 두 가지 장점이 있습니다. 첫째, 관련 @rst_src_N 신호가 무작위로 켜졌다 꺼졌다 해도(예: 출렁이는 PLL 락(lock) 검출기, 푸시버튼, 리셋을 여러 번 요청하는 소프트웨어), 이러한 다중 활성화는 눈에 보이는 동기 리셋(synchronous reset)으로 전파되지 않습니다. 그러한 다중 활성화는 대개 무해하지만, 출력 핀에서 불필요한 활동을 일으킬 수 있습니다. 이러한 활동은 예를 들어 전자 제품을 테스트하는 사람이 무언가 잘못되었다고 혼동하게 만드는 등 부정적인 영향을 줄 수 있습니다.

그런 의미에서 31 클록 사이클의 예제는 상당히 미니멀합니다. 그러한 지연이 허용된다면 10~100ms에 해당하는 값까지 세는 것이 더 좋습니다. 그렇게 하면 그보다 짧은 출렁임들은 리셋 컨트롤러가 숨겨 줍니다.

긴 리셋 펄스의 두 번째 이유는, 원래의 동기 리셋(synchronous reset)을 로컬 사본들로 분배하는 과정(앞서 설명한 대로)에 한 클록 사이클의 지연이 포함되기 때문입니다. 리셋을 로직 전체에 분배하려면 신호를 여러 번 복사해야 할 수 있는데, 그러면 전체 지연이 단순히 길어질 뿐만 아니라 고르지 않게 될 가능성도 있습니다. 긴 리셋 펄스는 어느 시점에서 모든 로직이 활성 동기 리셋(synchronous reset)에 노출되도록 보장합니다. 리셋 경로(path)에 고르지 않은 지연이 있으면 리셋 비활성화도 고르지 않게 되므로 여전히 좋은 생각은 아니지만, 때로는 그런 것이 문제가 되지 않기도 합니다. 그런 점에서 31 클록 사이클의 리셋 펄스는 아마 필요한 것보다 훨씬 길지만, 그렇다고 해서 해가 되지는 않습니다.

다른 클록 도메인을 위한 리셋

@master_reset은 일반적인 동기 리셋(synchronous reset)이지만, 애플리케이션 로직이 사용하는 클록과 다를 수 있는 어떤 클록에 동기화되어 있습니다. 다른 클록을 위한 동기 리셋(synchronous reset)을 만들려면 각 클록에 대해 다음과 같은 작업을 수행해야 합니다:

reg reset_clk_pre1, reset_clk_pre2;
reg reset_clk;

always @(posedge clk)
  begin
    reset_clk <= reset_clk_pre2;
    reset_clk_pre2 <= reset_clk_pre1;
    reset_clk_pre1 <= master_reset;
  end

이것은 @reset_clk를 생성하는 단순한 3단 클록 도메인 크로싱(clock domain crossing)입니다. 이 신호는 @clk와 함께 사용되는 동기 리셋(synchronous reset)입니다. 실제로는 2단이면 충분하지만, 중요한 신호이므로 안전을 위해 레지스터 하나를 더 추가했습니다.

리셋 컨트롤러의 클록

원칙적으로 @wakeup_clk, 즉 리셋 상태 머신(state machine)의 클록으로 사용할 수 있는 클록은 세 가지 종류가 있습니다:

첫 번째 옵션이 가장 다루기 쉽습니다. FPGA의 PLL을 구동하는 기준 클록(reference clock)이 거의 항상 있으므로, 이 기준 클록을 웨이크업 클록으로 직접 사용할 수 있는 경우가 많습니다. 하지만 FPGA가 깨어날 때 클록이 실제로 안정적인지, 즉 FPGA가 비트스트림(bitstream)을 로딩하는 데 걸리는 시간이 외부 발진기가 유효한 클록을 만들어 내는 데 걸리는 시간보다 긴지를 확인하는 것이 중요합니다. 데이터시트를 보면 보통 큰 여유를 두고 이 조건이 충족됩니다. 하지만 보드의 전원 투입 시퀀스가 제대로 계획되지 않았다면, 발진기의 공급 전압이 올바른 수준에 도달하기 훨씬 전에 FPGA가 비트스트림(bitstream)을 읽으라는 신호를 받을 수도 있습니다.

두 번째 옵션은 FPGA 자체의 PLL로 생성된 클록을 사용하는 것입니다. 분명한 장점은 이 클록이 애플리케이션 로직에도 사용될 수 있으므로 클록 리소스와 전력 측면에서 더 효율적이라는 점입니다. 이 선택지를 사용하려면 클록이 안정될 때까지 리셋 상태 머신(state machine)을 초기 상태로 유지해야 하는데, 다음과 같은 방식으로 할 수 있습니다:

reg rst_src_pll;
reg rst_src_pll_pre;

initial rst_src_pll = 1;
initial rst_src_pll_pre = 1;

always @(posedge wakeup_clk)
  begin
    rst_src_pll <= rst_src_pll_pre;
    rst_src_pll_pre <= !pll_locked;
  end

@pll_locked는 @wakeup_clk를 생성하는 PLL의 락(lock) 검출기 출력입니다(액티브 하이). 이 신호는 비동기(asynchronous)이므로, 먼저 @wakeup_clk로 동기화한 다음 위에서 본 것처럼 @clear_counter를 활성화하는 사유 중 하나로 사용됩니다(즉, @rst_src_pll을 통해).

이 옵션의 또 다른 장점은 기준 클록(reference clock)이 일시적으로 불안정하거나 없을 때(특히 보드에 전원을 켠 직후), 락(lock) 검출기도 함께 불안정할 가능성이 높다는 점입니다. 따라서 @reset_count가 @master_reset을 비활성화하기 전에 (31보다 훨씬 큰) 큰 값까지 센다면, FPGA가 기준 클록이 정상적으로 동작할 때까지 확실하게 리셋 상태를 유지할 가능성이 있습니다. 다만 이것에 의존할 수는 없습니다.

어쨌든 @wakeup_clk가 PLL의 출력이라는 사실은, 이 클록이 안정되기 전에 이미 로직에 공급된다는 것을 의미합니다. 따라서 그 기간 동안 상태 머신(state machine)이 제대로 동작하는지는 명확하지 않습니다. 이것은 중요하지 않다고 주장할 수도 있습니다. 어느 시점에 클록이 충분히 좋아지고 그때 적절한 리셋이 생성될 것이기 때문입니다. 리셋 후에는 과거의 모든 일이 과거로 남는다면, 그 이전에 무슨 일이 있었는지 누가 신경 쓸까요?

더 엄밀한 접근 방식은 웨이크업 클록이 잠길 때까지 리셋을 꾸준히 활성 상태로 유지하여, FPGA가 비트스트림(bitstream) 설정 직후에 이상하게 동작하지 않도록 하는 것입니다. 그러려면 이 클록을 사용하는 로직은 매우 간단한 것만 사용해야 합니다. 다시 말해, 클록의 주파수가 예상보다 높아도 크게 문제가 되지 않는 종류의 로직이어야 합니다.

@wakeup_clk가 일시적으로 너무 높은 주파수를 가질 때 어떤 일이 일어나는지 분석해 보면, @reset_count가 리셋 상태 머신(state machine)에서 유일한 벡터(vector) 레지스터라는 점에 주목할 수 있습니다. 즉, 다른 모든 플립플롭은 타이밍 위반 때문에 기껏해야 계산된 '다음 값'을 한 클록 늦게 샘플링(sampling)할 수 있습니다. 특히 PLL이 잠기지 않는 동안 @pll_locked는 로우(low)이므로 @rst_src_pll은 곧 꾸준히 하이(high)가 되고, 따라서 @clear_counter도 꾸준히 하이가 됩니다. 플립플롭의 D 입력(다음 값)이 변하지 않으면 클록이 아무리 빨라도 상관없습니다.

따라서 유일하게 가능한 문제는 @reset_count입니다. @clear_counter 때문에 계산된 다음 값이 꾸준히 0으로 유지되기 전까지, @reset_count가 잘못 카운팅될 수 있습니다. 예를 들어 현재 값이 3(이진수 011)이면 계산된 다음 값은 4(이진수 100)입니다. 그런데 타이밍 문제로 두 하위 비트가 샘플링되지 않고 세 번째 비트는 샘플링된다면, 카운터 값은 대신 7(이진수 111)로 튈 수 있습니다.

이런 일이 발생하지 않도록, @pll_locked에서 @clear_counter까지의 레지스터 체인의 초기 값은 모두 @reset_count를 꾸준히 0으로 유지하는 방향으로 할당됩니다. 따라서 @wakeup_clk가 안정될 때까지 @pll_locked가 꾸준히 로우(low) 상태를 유지한다면(그래야 정상입니다), @reset_count는 0에서 움직이지 않으며 @master_reset은 꾸준히 활성 상태를 유지합니다.

마지막 옵션인 FPGA의 링 오실레이터를 리셋 컨트롤러에 사용하는 방법에 대해 말하자면, 제가 직접 시도해 본 적은 없어서 이것이 얼마나 좋은 생각인지 확신할 수 없습니다. 하지만 다른 선택지가 없어 막힌 누군가에게 도움이 된다면, Xilinx FPGA에서는 대략 다음과 같이 하면 됩니다. 해당 FPGA의 Configuration User Guide에서 STARTUPE2(또는 비슷한 이름)라는 프리미티브(primitive)를 찾아보십시오. 그 프리미티브에는 CFGMCLK라는 출력이 있을 텐데, 이것은 FPGA 자체의 부정확한 링 오실레이터에서 나오는 클록입니다. 주파수는 약 50~65MHz입니다. 이 클록은 FPGA가 깨어날 때 안정적이라고 보장되며, 저라면 타이밍 제약(timing constraints)을 상당히 높은 주파수(예: 100MHz)로 설정하겠습니다.

하지만 이것은 정말 다른 선택지가 없을 때나 사용할 방법입니다. 예를 들어 외부 기준 클록(reference clock)이 FPGA가 깨어날 때 안정적이지 않아서 로직에 추가 지연을 구현해야 하는 경우 같은 상황입니다.

PLL 리셋하기

일반적으로 PLL을 리셋 시퀀스의 일부로 리셋하는 것은 좋은 생각입니다. 이렇게 하면 기준 클록(reference clock)이 유효하다고 알려진 시점에 PLL이 리셋됩니다. 또한 사용자가 리셋(예: 리셋 푸시버튼 누르기)을 시작했다면, 그것은 PLL이 제대로 잠기지 못한 데서 비롯된 문제에 대한 대응일 수도 있습니다. 그런 일은 없어야 하지만, 만약을 대비하는 것입니다.

@clear_counter는 PLL을 리셋하는 데 사용할 수 없습니다. PLL이 잠금을 잃는 순간 @clear_counter가 활성화되기 때문입니다. 만약 그렇게 사용한다면 PLL은 리셋 상태에 머물게 되고, 절대 잠기지 않으며, 리셋도 절대 해제되지 않을 것입니다. 같은 이유로 PLL의 리셋을 @reset_count에서 파생시킬 수도 없습니다. @reset_count는 모든 PLL이 잠겨 있을 때를 제외하면 0으로 유지되기 때문입니다.

해결책은 @clear_counter와 유사한, PLL 전용의 별도 리셋 레지스터를 만드는 것입니다. 위에서 사용한 표기법을 그대로 쓰면, 잠기지 않은 PLL이 @rst_src_pll로 반영되는 상황에서 대략 다음과 같이 됩니다:

reg clear_counter;
reg reset_plls;

initial clear_counter = 1;
initial reset_plls = 1;

assign reset_sources = rst_src_shreg || rst_src_debounce;

always @(posedge wakeup_clk)
  begin
    clear_counter <= rst_src_pll || reset_sources;
    reset_plls <= reset_sources;

[ ... ]

이 코드 조각에서는 @rst_src_pll이 @reset_sources에서 제외되었으며, @clear_counter에만 사용됩니다. 그 결과, PLL은 자기 스스로 잠기지 못한 결과로 리셋되는 경우를 제외하고는 FPGA 전체와 함께 리셋됩니다.

@wakeup_clk 자체는 리셋되는 PLL로 생성될 수 없다는 점을 명확히 해 둘 가치가 있습니다. 따라서 이 클록은 다른 가능성에 기반하여 생성되어야 합니다.

@reset_sources가 무작위로 로우(low)와 하이(high)를 오가면, 여기에 표시된 코드에 따라 @reset_plls도 그렇게 될 것이라는 점에 유의하십시오. PLL의 리셋이 미친 듯이 켜졌다 꺼졌다 해도 PLL에는 나쁜 일이 일어나지 않으므로, 이것은 대개 무해합니다. 그렇지만 이것도 피할 수 있는 방법이 있으며, 다음 절에서 설명합니다.

더 복잡한 기동 시퀀스

단순한 카운터(@reset_count)를 상태 변수로 사용하면 더 복잡한 기동 시퀀스를 구현하기가 쉬워집니다. 예를 들어 클록을 끈 상태에서 활성화되는 적절한 비동기 리셋(asynchronous reset)을 생성하는 것은 매우 간단합니다. 클록이 꺼지는 시간 구간과 리셋이 활성화되는 시간 구간을 정의하는 간단한 논리 표현식으로 구현하면 됩니다.

따라서 이 단순한 카운터 방식은 프로젝트 시작 시점에 리셋 상태 머신(state machine)이 단순한 요구만 가진 것처럼 보이는 설계에서도 좋은 출발점이 됩니다. 나중에 복잡한 기동 시퀀스가 필요하다는 것이 밝혀지더라도, 기존 로직을 확장하여 그 목표를 달성하기가 쉽습니다.

어쨌든 기동 시퀀스를 계획하는 것은 결국 시퀀스의 각 단계가 적용되는 시간 구간을 정의하는 일로 귀결됩니다. 각 구간은 @reset_count가 가져야 하는 값의 범위로 변환됩니다.

예를 들어 설계에 서로 무관한 클록이 두 개 이상 있다면, 위에서 설명한 방식(다른 클록 도메인을 위한 리셋 절 참조)으로 각 클록 도메인(clock domain)에 대한 리셋 신호를 만들 수 있습니다. 하지만 그 방법을 채택하면 각 클록 도메인은 사실상 무작위한 순서로 리셋에서 풀려납니다. 이것은 대개 문제가 되지 않지만, 문제가 된다면 @reset_count의 진행 상황에 따라 각 클록 도메인의 리셋이 정의된 시점에 비활성화되도록 할 수 있습니다.

카운터를 두 개 이상 구현하는 것도 가능합니다. 이것은 리셋 시퀀스가 계속 진행되기 전에 특정 조건이 충족되기를 기다려야 할 때 유용합니다. 예를 들어 리셋 시퀀스가 PLL을 리셋하고, PLL이 잠길 때까지 기다린 다음, 리셋 시퀀스를 계속하는 것이라면, 모든 PLL이 잠길 때까지 0으로 유지되는 카운터 하나를 두는 것이 합리적입니다. 두 번째 카운터는 첫 번째 카운터가 카운팅을 끝마칠 때까지 0으로 유지됩니다.

PLL이 잠금을 잃으면 PLL 자체는 리셋되지 않지만, PLL에 의존하는 모든 것은 리셋된다는 점에 유의하십시오. 대부분의 설계에서 PLL이 잠금을 잃는 것은 심각한 오류에 해당하므로, 이것이 바람직한 동작이 아닐 수 있습니다. 잠금 상실 시 전체 리셋을 요청하려면 아래 정의된 @pll_restart를 @reset_sources를 얻기 위해 OR로 묶는 신호들에 추가하십시오:

assign pll_restart = rst_src_pll && !master_reset;

이것은 간단히 말해, 마스터 리셋이 비활성화된 후에 PLL이 잠금을 잃었다면 PLL을 포함한 모든 것을 다시 리셋하라는 뜻입니다. 이것이 동작하려면 리셋 카운트가 락(lock) 검출기의 가능한 출렁임을 견딜 수 있을 만큼 길어야 합니다. 다시 말해, 리셋 카운트는 PLL이 아직 잠기지 않았는데도 PLL의 락(lock) 검출기가 하이(high)를 유지할 수 있는 시간보다 길어야 합니다. 이것을 PLL이 잠금을 획득하는 데 걸리는 시간과 혼동해서는 안 됩니다. 일부 락(lock) 검출기는 전혀 출렁이지 않으며, 그런 출렁임이 있더라도 (데이터시트에 명시된) PLL의 잠금 시간보다 훨씬 짧을 가능성이 높습니다.

웨이크업 시프트 레지스터

약간 과하다고 생각할 수도 있지만, 저는 보통 다음과 같은 웨이크업 시프트 레지스터(shift register)를 설계에 추가합니다:

reg [15:0]   wakeup_shift;
reg          rst_src_shreg;

initial rst_src_shreg = 1;
initial wakeup_shift = 0;

always @(posedge wakeup_clk)
  begin
    rst_src_shreg <= !wakeup_shift[15];
    wakeup_shift <= { wakeup_shift, 1'b1 };
  end

대부분의 FPGA가 @wakeup_shift를 시프트 레지스터(shift register) 프리미티브(primitive)로 구현하며, 이는 LUT 하나에 해당하는 리소스만 소비하므로 값이 쌉니다. 게다가 이 레지스터는 전원 투입 시 리셋이 반드시 일어나도록 보장하는 또 다른 메커니즘을 제공합니다. PLL이 없는 설계에서는 @clear_counter를 활성화할 다른 수단이 없기 때문에 이 메커니즘이 필요합니다. 하지만 PLL이 있더라도, 일부 FPGA에서는 비트스트림(bitstream) 설정(configuration) 옵션에 따라 FPGA가 깨어날 때 PLL이 이미 잠겨 있을 가능성도 있습니다.

따라서 어떤 경우에도 이것은 권장되는 추가 요소입니다. 안전하게 가는 것이 나중에 후회하지 않는 길입니다.

외부 리셋 버튼

리셋 버튼은 꽤 흔합니다. 버튼이 정확히 무엇을 하는지는 설계마다 다릅니다. 한 가지 가능성은 사용자가 '리셋'이라고 생각하는 버튼이 FPGA에 비트스트림(bitstream)을 로딩하는 과정을 시작시키는 FPGA 핀에 연결되어 있는 경우입니다. 또한 임베디드 프로세서가 있는 FPGA에서는 프로세서의 리셋 핀에 연결되어 있을 수도 있습니다.

그리고 이 리셋 버튼은 FPGA 로직을 리셋할 목적으로 FPGA의 범용 I/O 핀에 연결될 수도 있습니다. 이 경우에는 @clear_counter를 활성화하는 또 하나의 사유가 됩니다.

@reset_count가 10ms 이상에 해당하는 값까지 센다면, 입력 핀 신호의 디바운스(debounce)는 필요하지 않습니다. 출렁임이 카운터 자체에 흡수되기 때문입니다. 이 경우에는 다음과 같이 충분합니다:

reg rst_src_debounce;
reg rst_src_debounce_pre;

initial rst_src_debounce = 1;
initial rst_src_debounce_pre = 1;

always @(posedge wakeup_clk)
  begin
    rst_src_debounce <= rst_src_debounce_pre;
    rst_src_debounce_pre <= reset_button_pin;
  end

하지만 카운터가 빨리 끝나면(위 예제처럼 단지 31까지 도달하는 경우), 푸시버튼에는 디바운스(debounce)가 필요합니다. 이를 수행하는 방법은 여러 가지가 있습니다. 예를 들어:

reg [17:0] debounce_count = 0;
reg        reset_button_d, reset_button_d2, reset_button_d3;

wire       debounce_reached = (debounce_count == 250000);

initial debounce_count = 0;
initial rst_src_debounce = 1;

always @(posedge wakeup_clk)
  begin
    reset_button_d3 <= reset_button_d2;
    reset_button_d2 <= reset_button_d;
    reset_button_d <= reset_button_pin;

    if (reset_button_d2 != reset_button_d3)
      debounce_count <= 0;
    else if (!debounce_reached)
      debounce_count <= debounce_count + 1;

    if (debounce_reached)
      rst_src_debounce <= reset_button_d3;
  end

이것은 비교적 엄격한 디바운서(debouncer)로, 노이즈가 심한 입력 신호에는 잘 동작하지 않습니다. 이것은 필요에 따라 장점이 될 수도 있고 단점이 될 수도 있습니다.

이 코드 예제는 25MHz 클록을 기준으로 작성되었습니다. @reset_button_pin이 10ms 동안 같은 값을 유지하면 그 값이 @rst_src_debounce로 복사됩니다. @reset_button_d3의 값이 변하는 것과 같은 클록 사이클에 @debounce_count가 0으로 변한다는 점에 유의하십시오. 따라서 @reset_button_d3의 값이 변하면 그것이 즉시 @rst_src_debounce로 복사되는 일은 결코 없으며, 같은 값을 오랫동안 유지한 후에야 비로소 복사됩니다.

요약

FPGA의 초기화를 설계할 때 고려해야 할 사항이 많습니다. 이 페이지에서는 몇 가지 개념과 아이디어를 제시했지만, 실제 핵심 작업은 어떤 사건이 조치를 개시해야 하는지 정확히 인식하는 것임을 명심하는 것이 중요합니다. 또한 각 사건에 대한 올바른 응답을 정의하여 로직이 확실하게 동작 가능한 상태로 이끄는 것도 중요합니다.

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