Menu

Verilog If-Else: procedural 블록의 조건 로직

always 블록 안에서 if/else가 어떻게 동작하는지, 초보자를 잡는 latch 함정, 그리고 else if 체인이 만들어 내는 priority encoder 하드웨어.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

익숙한 문법, 다른 사고방식

if/else는 C와 완전히 똑같이 생겼습니다.

if (condition) begin
    // ... 문장 ...
end else begin
    // ... 문장 ...
end

하지만 Verilog은 소프트웨어가 아니라서 규칙이 다릅니다. 두 가지 유의점.

  1. if/else는 procedural 블록 안에만 존재합니다. 모듈 레벨에 독립형 if를 쓸 수 없습니다.
  2. if/else가 무엇으로 synthesize되는지는 블록 타입 에 달려 있습니다. 조합 블록에서는 멀티플렉서나 priority encoder가 됩니다. clocked 블록에서는 조건부 갱신 로직이 있는 플립플롭이 됩니다.

조합 블록 안에서

조합 always @(*) 블록은 a, b, c 중 어느 것이라도 변하면 재실행됩니다. if/else 체인이 한 분기를 골라 max에 대입하고 블록이 끝납니다. max항상 대입되므로(모든 경로에 대입이 있음) synthesizer는 순수 조합 논리 - latch 없음 - 를 만들어 냅니다.

maxreg로 선언되긴 했지만 하드웨어에 플립플롭은 없다는 점에 주목하세요. 같은 규칙: always 안에서 대입되는 것은 reg여야 합니다.

Latch 함정

이게 초보 조합 코드의 가장 흔한 버그입니다.

// 잘못 - latch가 inferred됨
always @(*) begin
    if (enable)
        out = data;
    // else 없음! enable이 low이면 out은 어떻게 되나?
end

synthesizer는 "enable이 low이면 out이 대입되지 않음"을 읽고 out이전 값을 기억해야 한다고 판단합니다. 값 기억에는 저장 셀이 필요하므로 도구가 latch를 끼웁니다. 동기 설계의 latch는 timing 문제를 일으키고, reset이 어렵고, 거의 의도한 게 아닙니다.

두 가지 해법.

둘 다 같은 조합 하드웨어 - 2-to-1 mux - 를 만들어 냅니다. 같은 신호에 조건부 대입이 많을 때는 "맨 위에 기본값" 패턴이 더 잘 확장됩니다.

Clocked 블록 안에서

조합 케이스와 다른 점.

  • 블록이 always @(posedge clk) - 플립플롭 영역.
  • 대입이 <=(non-blocking) 사용.
  • "reset도 enable도 아님" 케이스에 else가 없음. 괜찮습니다. clocked 블록에서는 어떤 분기도 발사되지 않으면 플립플롭이 단순히 이전 값을 유지합니다 - 플립플롭이 물리적으로 하는 바로 그것. 신호가 이미 register이기 때문에 latch가 inferred되지 않습니다.

clocked 블록 외부에서 else를 생략하는 게 안전한 유일한 자리입니다. 그 외에서는 항상 모든 경로를 처리하세요.

체인 else if: priority encoder

else if 체인은 암시적 우선순위를 가집니다 - 앞 조건이 뒤 조건을 능가합니다.

requests[0]이 최고 우선순위입니다 - 그게 set이면 더 높은 번호 비트가 무얼 하든 grant는 0. synthesizer는 체인을 cascaded mux로 만듭니다: 비트 0 먼저 검사, 그다음 1, 2, 3. 단계마다 약간의 지연이 더해집니다.

조건이 상호 배타적 이라면 - 가령 one-hot 입력 디코딩 - case 문(다음 문서)이 else if 체인보다 더 평탄하고 빠른 하드웨어를 만듭니다. 진짜 priority 요구가 없을 때는 case 형태를 쓰세요.

clocked 코드에서 else 없는 if

clocked 블록은 "이전 값 유지"가 기본이라 else가 필요 없습니다. 이게 enable 을 만드는 방법입니다.

always @(posedge clk) begin
    if (load) target <= incoming;
    // else 없음: load가 low이면 target이 값 유지
end

load-enabled register입니다. 대부분의 파이프라인 register, 카운터, 설정 register가 이 패턴을 사용합니다.

begin/end와 단일 문장

C처럼 단일 문장이면 begin/end를 생략할 수 있습니다.

if (a) out = 1;
else   out = 0;

한 문장 이상이면 블록을 사용하세요.

if (a) begin
    out = 1;
    flag = 1;
end else begin
    out = 0;
    flag = 0;
end

두 패턴은 자유롭게 섞입니다. 스타일 가이드는 보통 두 번째 문장 추가가 고통 없도록 항상 begin/end를 쓰길 권장합니다.

다음에 볼 내용

다음 문서 - Case Statement - 는 다중 분기 디코딩(상태 머신, opcode dispatch, ROM 테이블)에 맞는 도구인 case를 다룹니다. 그다음에는 elaboration 시점에 unroll되어 소프트웨어 사촌과 미묘하게 다른 for 루프입니다.

자주 묻는 질문

Verilog의 if 문은 어떻게 동작하나요?

if (cond) statement;cond가 non-zero이면 statement를 실행합니다. 여러 문장은 begin ... end로 감쌀 수 있습니다. 대안 분기에 else statement;를 추가하거나, else if (other_cond) ...로 체인할 수 있습니다. if/else는 procedural 블록 안에만 존재합니다 - 모듈 최상위에는 없습니다.

Verilog의 inferred latch란?

여러분이 요청하지 않았는데 synthesis 도구가 만든 latch입니다. 조합 always 블록이 어떤 경로에서 신호를 대입하지 않아서 생깁니다. 도구는 'a이면 out=1, else 없음'을 보고 미할당 케이스가 이전 값을 기억해야 한다고 판단해 latch를 만듭니다. latch는 거의 항상 잘못이고, 해법은 블록 맨 위에 모든 신호에 기본값을 주거나 명시적 else를 두는 것입니다.

Verilog에서 inferred latch를 어떻게 피하나요?

조합 always @(*) 블록에서 모든 출력 reg가 모든 코드 경로에서 대입되도록 하세요. 가장 깔끔한 패턴은 블록 맨 위에 기본값을 설정하고 조건적으로 오버라이드하는 것입니다. 컴파일러가 보통 latch inference 시 경고를 띄웁니다 - 경고를 오류로 다루세요.

Verilog의 if-else 체인은 무엇으로 synthesize되나요?

priority encoder입니다. 첫 if가 최고 우선순위, 다음 else if는 첫 번째가 false일 때만 검사, 이런 식. 하드웨어에서는 우선순위가 굳혀진 mux 체인이 됩니다. 조건이 상호 배타적이라면 같은 로직을 case 문으로 쓰면 더 평탄한 하드웨어가 되고 더 명확합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기