다중 분기
case는 Verilog의 평탄한 dispatch 구문입니다. 식을 주면 매칭 분기를 고릅니다.
case (expression)
pattern_1: statement_1;
pattern_2: statement_2;
pattern_3: begin
statement_3a;
statement_3b;
end
default: default_statement;
endcase
구조적으로는 C의 switch와 비슷하지만,
break가 없습니다 - 각 분기는 다음 분기로 암시적으로 종료됩니다.- 패턴이 정수만이 아니라 vector도 됩니다.
- synthesizer가 전체를 평탄한 mux(또는 패턴이 가능하게 하면 one-hot 디코더)로 변환합니다.
동작 예시: 4-to-1 Mux
case 본문에는 네 개의 명시적 패턴에 더해 default가 있습니다. synthesizer는 2비트 입력이 네 값 중 하나로 매핑되는 것을 보고 4-to-1 멀티플렉서를 내보냅니다. 깔끔하고, 평탄하고, 빠릅니다.
왜 default가 필요한가
조합 case에서 default를 생략하는 건 else 없는 if와 같은 함정입니다: 매칭되지 않는 입력 값이 out을 미할당으로 두고, synthesizer는 latch를 inferred합니다.
2비트 sel이라면 위 패턴이 가능한 네 값을 모두 커버하므로 이론상 default는 불필요합니다. 실전에서는
- synthesizer가 항상 case가 exhaustive함을 증명하지는 못합니다.
- 시뮬레이션에서 selector가
x나z일 수 있고, 이는 어떤 명시적 case와도 매칭되지 않습니다. - 나중에 새 case를 추가하면 기본 동작이 명시되지 않은 채 남을 수 있습니다.
항상 default를 쓰세요. default가 도달 불가임을 아는 상태 머신과 mux 로직에서는 'x 대입:
default: out = 8'bx;
…이 synthesizer에게 "이건 don't-care이니 자유롭게 최적화하라"고 알리고, 만약 도달 불가 case가 실제로 도달되면 시뮬레이션에서 밝은 빨간 x로 드러납니다. 양쪽의 좋은 점을 다 갖는 셈입니다.
case로 상태 머신
case의 고전적 용도는 유한 상태 머신의 상태 전이 로직입니다.
case (state) 블록이 상태 전이 로직입니다. 각 분기가 다음 상태와 그 상태에 머물 시간을 결정합니다. 여기서 default는 도달 불가이지만(2비트 공간에서 RED/GREEN/YELLOW가 exhaustive) 안전망으로 둡니다 - state가 어쩌다 2'd3이 되면 FSM이 latch하지 않고 RED로 깔끔하게 reset됩니다.
Finite State Machines에서 이 패턴을 더 깊이 다룹니다.
한 분기에 여러 패턴
쉼표로 구분해 여러 패턴이 한 문장을 공유하게 할 수 있습니다.
case (opcode)
4'h0, 4'h1, 4'h2: result = a + b;
4'h3, 4'h4: result = a - b;
4'h8: result = a & b;
default: result = 8'd0;
endcase
"빼기"인 opcode 둘, "더하기"인 opcode 셋. synthesizer는 비교기를 위해 패턴들을 OR로 묶습니다.
casez와 casex: don't-care 매칭
일부 비트가 명시되지 않은 패턴 - "010으로 시작하는 어떤 opcode든" - 을 매칭하고 싶을 때가 있습니다.
casez (opcode)
8'b010?_????: instruction = ALU_OP;
8'b110?_????: instruction = LOAD_OP;
8'b1110_????: instruction = JUMP_OP;
default: instruction = UNKNOWN;
endcasez
casez는 패턴의 ?(와 z)를 don't-care로 다룹니다. 각 ?는 0이나 1과 매칭됩니다. 일부 비트 위치가 특정 opcode 클래스에서 미사용인 명령어 포맷 디코딩에 유용합니다.
casex는 이를 x까지 don't-care로 확장합니다. casex는 위험합니다 - 초기화되지 않은 신호(시뮬레이션에서 x)가 모든 case와 매칭되어 놀라운 동작을 만들어 냅니다. 대부분의 현대 스타일 가이드가 casez를 권하고 casex를 금합니다.
SystemVerilog에는 가장 깔끔한 버전인 case inside도 있습니다 - 범위와 목록을 받아들임 - 일반 Verilog은 casez까지입니다.
case vs if/else if 체인
두 구조 모두 다중 분기 결정을 표현할 수 있지만, 다른 하드웨어로 synthesize됩니다.
case는 평탄한 dispatch. synthesizer가 one-hot 디코더, 균형 잡힌 mux 트리 등 평탄 구조를 만들 수 있습니다. 평가 시간 상수.if/else if는 priority 체인. synthesizer가 단계마다 지연이 추가되는 cascade를 만듭니다. 로그 단위로 느립니다.
기능적으로는 겹칩니다. 스타일적으로는: 조건이 단일 식의 값에 관한 것이면 항상 case를 쓰세요. 진짜 priority가 있거나 조건이 서로 다른 신호를 다룬다면 if/else if를 쓰세요.
// case로 더 나음:
if (sel == 2'd0) out = a;
else if (sel == 2'd1) out = b;
else if (sel == 2'd2) out = c;
else out = d;
// if/else if로 더 나음:
if (urgent_event) next_state = HANDLE_URGENT;
else if (timer_expired) next_state = TIMEOUT;
else if (data_ready) next_state = PROCESS;
else next_state = state;
첫 셋은 모두 "sel이 무엇인가?"입니다 - case가 더 자연스럽게 읽히고 더 평탄하게 synthesize됩니다. 둘째 셋은 명백한 priority가 있는 독립 이벤트들입니다 - if/else if가 더 맞습니다.
다음에 볼 내용
이 장의 마지막 문서 - For Loops - 는 Verilog의 for와 synthesizable 코드에서 그것을 쓸 때 일어나는 놀라운 일을 다룹니다. 그다음 본격적으로 순차 논리와 FSM으로 넘어갑니다.
자주 묻는 질문
Verilog의 case 문이란?
case (expr) ... endcase는 Verilog의 다중 분기 구문입니다. 식을 한 번 평가해 매칭되는 분기로 dispatch합니다. 상태 머신, opcode 디코더, mux 선택기 등 상호 배타적인 여러 옵션 중에서 고르는 모든 경우에 정석 선택입니다.
case, casex, casez의 차이는?
case는 x와 z 값을 포함해 비트 정확히 매칭합니다. casez는 case item의 z(와 ?)를 don't-care로 다룹니다. casex는 x와 z 모두 don't-care로 다룹니다. don't-care 매칭은 일부 비트가 무관한 opcode 패턴에 유용하지만, casex는 초기화되지 않은 신호(x)가 우연히 모든 case와 매칭될 수 있어 시뮬레이션에서 위험합니다.
Verilog case 문에서 왜 default가 필요한가요?
default 없이는 어떤 case도 매칭되지 않을 가능성을 synthesis 도구가 보고, 출력 신호가 이전 값을 유지해야 한다고 판단해 원치 않는 latch를 inferred합니다. default 분기가 매칭되지 않은 모든 값을 처리합니다 - 보통 출력을 안전한 값으로 설정하거나 x 대입으로 case가 도달 불가임을 표시합니다. 항상 포함하세요.
case와 if-else 중 무엇을 써야 하나요?
조건이 상호 배타적이고 단일 식의 값에 따라 dispatch한다면 case를 쓰세요 - 상태 머신, opcode 디코더, mux 선택기. 진짜 priority 순서가 있거나 조건이 서로 다른 신호를 다룬다면 if/else를 쓰세요. case는 긴 else if 체인보다 더 평탄하고 빠른 하드웨어로 synthesize됩니다.