2-state vs 4-state logic
소프트웨어에서 비트는 0 또는 1입니다. Verilog에서 비트는 네 가지 값 중 하나일 수 있습니다.
0- wire가 low로 구동됨.1- wire가 high로 구동됨.x- wire의 값이 unknown 임. 시뮬레이터가 알 수 없음.z- wire가 high-impedance 임. 아무것도 구동하지 않음.
이 4-state 모델은 하드웨어도 같은 문제를 갖기 때문에 존재합니다. 실제 wire는 low로 묶이거나, high로 묶이거나, undefined(두 소스가 다툼)이거나, floating(드라이버 없음)일 수 있습니다. 시뮬레이터가 유용하려면 네 가지를 모두 모델링해야 합니다.
x가 등장하는 방식
이걸 실행하고 출력을 보세요.
a는 선언되었으나 결코 쓰여지지 않아 x 상태에 머뭅니다. x + 5는 x를 만듭니다 - unknown과의 어떤 산술도 unknown을 만듭니다. 출력에는 x가 aaaa만큼 보입니다.
설계에서 x의 흔한 출처.
- 선언되었지만 reset되지 않은
reg(synthesis 가능 Verilog의 대부분은 명시적 reset으로 클리어). default가 없는case문에서 어떤 case와도 매칭되지 않는 입력에 부딪힘.- 리팩터링 후 유일한 드라이버를 잃은
wire. - 한 분기는 신호를 대입하는데 다른 분기는 대입하지 않는
if/else체인(커버되지 않으면x를 latch). - vector나 array의 끝을 넘어 읽기.
X 전파: 작은 x가 모든 걸 망친다
x의 잔인한 점은 전파된다는 것입니다. 피연산자의 단 한 x 비트가 전체 결과를 x로 만듭니다.
0 & x는 0이고(AND-with-0은 항상 0), 1 | x는 1(OR-with-1은 항상 1)입니다. 시뮬레이터는 비트별로 보수적이지만 항등법칙은 존중합니다. 산술과 비교는 그렇게 관대하지 않습니다.
이게 단 하나의 초기화 안 된 register가 출력 버스 전체를 xxxx로 만들 수 있는 이유입니다. 어떤 x에서든 거꾸로 추적하면 출처를 찾을 수 있습니다.
z가 등장하는 방식
z는 아무도 구동하지 않는 wire의 값입니다.
스니펫의 두 패턴.
floating은 선언만 되고 결코 구동되지 않습니다. 기본이z.data_out은 의도적인 tri-state입니다.enable이 low이면 출력을 명시적으로z로 풉니다. 그게 bus 드라이버가 "놓아주는" 방식이고, 다른 드라이버가 인계받을 수 있게 합니다.
내부 로직에서 z는 거의 항상 잘못입니다. 양방향 핀이나 공유 bus에서는 z가 딱 맞습니다.
== vs === 비교
일반 등호 연산자 ==는 피연산자에 x 또는 z 비트가 있으면 x를 반환합니다.
===(과 그 짝 !==)는 x와 z를 포함한 엄격한 비트별 비교를 합니다. testbench에서 x/z에 대해 테스트할 때마다 사용하세요. ===는 synthesis 가능하지 않지만, testbench의 initial 블록 안에서는 상관없습니다.
$isunknown(expr) 시스템 함수는 "이 식에 x나 z 비트가 있나?"를 묻는 가장 깔끔한 방법입니다 - 있으면 1, 없으면 0.
의도적 don't-care로서의 x
논쟁의 여지가 있지만 정당한 패턴: 상태 머신 default case의 'x는 synthesizer에게 "이 상태는 도달 불가이니 자유롭게 최적화하라"고 알립니다.
case (state)
IDLE: next_state = go ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = 'x; // 도달 불가
endcase
Synthesizer는 x를 활용해 상태를 합치고 게이트 개수를 줄일 수 있습니다. 시뮬레이션에서 추론이 틀려 default가 도달 되면 next_state에서 x가 전파되어 버그가 즉시 보입니다.
이건 default가 정말로 도달 불가인지 충분히 생각했을 때만 쓰세요. 그렇지 않다면 안전한 상태로 default를 설정하세요.
흔한 디버깅 레시피
x로 가득한 파형을 보고 있다면 레시피는
- 가장 이른
x를 찾으세요. 파형을 시간 거꾸로 따라가세요. 처음x가 된 신호가 출처에 가장 가깝습니다. - 드라이버를 찾으세요. 소스를 여세요. 이 신호를 무엇이 대입합니까?
assign?always블록? - 드라이버의 입력을 확인하세요. 드라이버의 RHS에
x가 있다면 전파가 제대로 일어난 것입니다 - 버그는 상류에 있습니다. - 드라이버 입력은 깨끗한데 출력이
x라면 드라이버가 불완전합니다. default 누락된case,else누락된if, reset 누락된 register.
대부분의 x 폭풍 버그는 다음 중 하나로 귀결됩니다: 누락된 reset, 누락된 default, 연결되지 않은 서브모듈.
다음에 볼 내용
이제 완전한 데이터 타입 이야기 - wire/reg, vector, parameter, number literal, 4-state logic 모델 - 를 갖췄습니다. 다음 장은 그걸 모두 써서 식을 만들기 시작합니다 - 모든 종류의 연산자, 그 안에는 소프트웨어에서 말이 안 되는 비트 레벨 연산자도 포함됩니다.
자주 묻는 질문
Verilog에서 x는 무엇을 의미하나요?
x는 unknown 값입니다. x인 신호는 0일 수도 1일 수도 있습니다 - 시뮬레이터가 알 수 없습니다. 신호가 구동되지 않았거나, 두 드라이버가 충돌하거나, reset 전에 register를 읽거나, undefined behavior가 조용히 전파될 만한 어디서나 등장합니다. x는 버그 신호로 다루세요 - 거의 절대 원하던 값이 아닙니다.
Verilog에서 z는 무엇을 의미하나요?
z는 high-impedance 값 - wire가 전혀 구동되지 않는 상태입니다. tri-state 출력(데이터 버스, 양방향 핀)의 정당한 상태이지만, 내부 신호에서 z는 보통 '아무것도 연결되어 있지 않다'는 실수입니다. Synthesizer는 명시적 tri-state output enable 밖의 대부분의 z 패턴을 거부합니다.
Verilog 출력이 왜 xxxx인가요?
거의 항상 신호가 무엇으로도 구동되지 않거나, 연산이 다른 신호에서 x를 전파했기 때문입니다. 거꾸로 추적하세요: 어느 신호가 x인지, 무엇이 그것을 공급하는지, 드라이버가 활성인지. 흔한 원흉은 case 문의 default 누락, reset되지 않은 register, 리팩터링 후 드라이버를 잃은 wire입니다.
Verilog에서 x나 z를 어떻게 검사하나요?
x와 z를 포함해 비트 단위로 정확하게 비교하는 === 연산자를 사용합니다. a === 1'bx는 a가 실제로 x일 때 참입니다. 일반 ==는 피연산자에 x 비트가 있으면 x를 반환하므로, a == 1'bx는 원하는 답이 결코 아닙니다. 깔끔한 boolean인 $isunknown(a)도 있습니다.
Verilog에서 x를 기본값으로 대입할 수 있나요?
네, 그리고 case 문에서 의도적인 기법입니다: default: out = 'x;는 synthesizer에게 '이 case는 결코 일어나지 않음을 약속하니 자유롭게 최적화하라'고 알립니다. 비용은 시뮬레이션에서 만약 일어난다면 x가 전파되어 버그가 드러난다는 점입니다. 기본 case를 작성하지 않으려는 회피책이 아니라, default가 도달 불가임을 이미 증명했을 때 사용하세요.