본문으로 건너뛰기
code0.HDL

선택·case·don't-care 값

?: 연산자, case, 함수로 조합 선택 회로를 만들고, 모든 경로에 값을 주며, 아무도 관찰할 수 없는 자리에만 don't-care 값 ?를 씁니다.

이 장에서 배울 내용

  • 조건식, if, case를 같은 사이클에 계산되는 멀티플렉서와 디코더로 읽습니다
  • if와 case의 모든 경로에 값을 주고, 경로가 빠졌을 때의 경고 P0042를 알아봅니다
  • don't-care 값 ?가 안전한 자리를 판단하고, 관찰될 수 있게 되는 순간 구체적인 값으로 바꿉니다
  • valid 플래그로 쓰이지 않는 payload를 프로토콜상 관찰 불가능하게 만듭니다
이 장에서 배우는 개념
  • 조건식·함수로 구성하는 선택
먼저 알아야 할 개념

1. 조건식은 멀티플렉서입니다

조건 ? a : b는 Bool 조건이 True이면 a, 아니면 b를 고릅니다. 하드웨어로는 공통 기초의 2입력 멀티플렉서입니다. a와 b가 모두 계산되고, 조건이 어느 쪽을 출력으로 보낼지 정합니다. 두 분기의 타입은 같아야 합니다. 한쪽이 Bit#(8), 다른 쪽이 Bit#(4)이면 앞 장의 T0020 오류입니다. 이 연산자의 형식과 타입 규칙은 Bool·Bit와 순수 함수 장에 있습니다.

Bit#(8) magnitude = negative ? 0 - value : value;

조건식을 이어 쓰면 셋 이상의 값 가운데 하나를 고르고, 처음으로 참인 조건이 이깁니다. 이 순서가 우선순위이고, 뒤의 장들은 이것을 의도적으로 사용합니다.

2. 블록과 if 문

2.1 begin과 end

begin과 end는 문장 하나가 올 위치에 여러 문장을 묶어 넣습니다.

형식

begin
  문장1;
  문장2;
end

2.2 if와 else

if는 Bool 조건에 따라 실행할 문장을 고릅니다.

형식

if (조건) 문장
if (조건) 문장 else 문장
UInt#(8) result = 0;
if (valid) begin
  result = payload;
end
부분의미
UInt#(8) result = 0;모든 경로에 적용되는 기본값입니다
if (valid)실행 중에 정해지는 Bool 신호입니다
result = payload;조건이 참인 경로에서 result는 payload를 가리킵니다

valid가 실행 중의 신호이면 두 경로가 모두 하드웨어가 되고, if는 result의 값을 고르는 멀티플렉서가 됩니다. elaboration이 한 경로를 미리 골라 두지 않습니다. Action 문맥에서는 분기 안에 레지스터 쓰기와 method 호출도 넣을 수 있으며, 그 쓰기는 조건이 참일 때만 일어납니다.

주의. 모든 경로에서 값을 지정하세요. 어느 경로에서 변수에 값을 지정하지 않으면 bsc는 먼저 경고 P0042(Assignment to `result' missing from some paths)를 출력하고, rule이나 method가 그 값을 쓰는 순간 elaboration이 오류 G0028(`result' uses uninitialized value)로 멈춥니다. 이전 값을 기억하는 래치가 만들어지는 것이 아니라 설계가 아예 컴파일되지 않습니다. if 앞에서 기본값을 지정하거나 모든 분기에서 값을 지정하세요.

2.3 식으로 쓰는 begin/end 블록

괄호로 감싼 begin/end 블록은 여러 문장으로 값 하나를 계산합니다. 마지막 식이 블록의 값이 됩니다.

형식

(begin
  문장;
  결과_식;
end)
Bit#(8) normalized = (begin
  Bit#(8) masked = raw & 8'h7f;
  Bit#(8) result = masked | 8'h80;
  result;
end);

raw가 8'h35이면 masked는 8'h35, normalized는 8'hb5입니다. 블록 안의 이름은 조합 중간값이며 레지스터를 만들지 않습니다.

3. case는 값 하나를 여러 상수와 비교합니다

case는 식 하나를 여러 값과 비교하고 처음 일치한 항목을 실행합니다. default는 나머지를 모두 받습니다. 함수 안에서는 항목마다 return으로 결과를 반환할 수 있고, 일반 문장에서는 항목마다 변수에 대입합니다.

형식

case (식)
  값1: 문장
  값2, 값3: 문장
  default: 문장
endcase
Bit#(8) code;
case (op)
  2'b00: code = 8'h10;
  2'b01: code = 8'h20;
  default: code = 8'hff;
endcase
항목의미
2'b00: ...op가 2'b00이면 실행됩니다
값2, 값3: 문장항목 하나에 값 여러 개를 나열할 수 있습니다
default: ...앞의 항목이 모두 일치하지 않으면 실행됩니다. 덕분에 모든 경로에서 code가 정해집니다

항목이 상수인 case는 디코더 뒤에 멀티플렉서가 붙은 회로입니다. 비교가 원핫 선택 신호를 만들고, 선택 신호가 결과를 고릅니다. 여전히 조합 회로입니다. 클록이 관여하지 않고 결과는 op와 같은 사이클에 나옵니다. 2.2절의 규칙은 여기에도 적용됩니다. default 없이 일부 항목에서만 대입하는 case는 빠진 항목에서 값이 정해지지 않으며, 그 값을 쓰면 같은 오류 G0028로 elaboration이 멈춥니다. 패턴 매칭 장에서는 구조화된 값의 형태까지 검사하도록 case를 확장합니다.

4. don't-care 값은 값이 중요하지 않다는 뜻입니다

4.1 ?의 뜻

물음표 하나 ?는 문맥이 기대하는 타입의 값이면서 내용이 없는 값입니다. "이 위치에는 어떤 비트 패턴이 와도 상관없다"는 표시이고, 컴파일러는 회로를 만들기 편한 값을 아무것이나 고를 수 있습니다.

형식

?
Bit#(8) unused = ?;

case (code)
  2'd0: return 4'h3;
  // ... 나머지 값도 모두 처리
  default: return ?;   // 도달하지 않는 분기
endcase

?는 Verilog의 x가 아니고, 난수도 아니며, x 동작을 보장하지도 않습니다. 한 번 빌드하면 패턴 하나가 정해져 유지됩니다. 어떤 입력도 도달하지 않는 분기나 비활성 union 멤버의 필드처럼, 어떤 값을 골라도 관찰되는 동작이 같은 곳에만 사용하세요.

참고. ?의 값은 정해져 있지 않습니다. 출력, 외부 인터페이스, 다른 계산에 실제로 쓰이는 위치에 두면 안 됩니다. mkReg(?)도 예측할 수 있는 리셋 값을 만들지 않습니다.

주의. 외부에서 관찰되는 상태를 ?로 초기화하면 설계의 일부가 정해지지 않은 채 남습니다. 이 경우 시뮬레이션과 합성 결과가 달라질 수 있습니다.

4.2 예제: 도달하지 않는 default 분기

DontCare.bsv의 decode는 2비트 code를 받아 4비트 값을 반환합니다. case는 0부터 3까지 네 값을 모두 처리하고, default 분기는 ?를 반환합니다. 어떤 입력도 그 항목에 도달하지 않으므로 ?가 무엇이 되든 출력은 바뀌지 않습니다.

DontCare.bsv
package DontCare;

// default 분기에는 도달할 수 없다. code가 2비트이고 위에서 모든 값을 처리한다.
// 물음표는 특정한 값이 필요하지 않다는 표시다.
function Bit#(4) decode(Bit#(2) code);
  case (code)
    2'd0: return 4'h3;
    2'd1: return 4'h7;
    2'd2: return 4'hB;
    2'd3: return 4'hF;
    default: return ?;
  endcase
endfunction

(* synthesize *)
module mkTop(Empty);

  rule show;
    $display("decode(0) = %h", decode(2'd0));
    $display("decode(3) = %h", decode(2'd3));
    $finish(0);
  endrule

endmodule

endpackage

이 개념 실습하기

다음 단계로 짧은 BSV 문제에 이 개념을 적용해 보세요.

실행 전에 예측하세요. 2비트 값은 몇 가지이고, default 분기에 도달하는 입력이 있을까요? decode(0)과 decode(3)이 16진수로 출력됩니다. 일치하는 두 항목을 읽으세요.

확인 문제

세 번째 줄로 decode(2'd2)를 출력한다면 어떤 16진수 한 자리가 나옵니까?

실행을 누르고 두 줄을 항목 2'd0, 2'd3과 비교하세요.

출력 줄확인할 내용
decode(0)명시된 분기에서 3이 반환됩니다
decode(3)명시된 분기에서 f가 반환됩니다

2비트 값 네 개가 모두 명시된 분기에서 처리되므로 default에는 도달할 수 없습니다. 그래서 ?가 어떤 값으로 정해지든 출력에 영향을 주지 않습니다. $display("decode(2) = %h", decode(2'd2));를 추가하면 예측한 대로 b가 출력됩니다.

5. 한 곳만 바꿔 보세요

실행 패널에서 2'd3 항목을 지우고 다시 실행하세요. 이제 값 3은 default로 넘어갑니다.

확인 문제

2'd3 항목을 지우면 decode(3) 줄에 무엇이 나옵니까?

답

규칙은 다음과 같습니다. ?는 도달 불가능함을 보일 수 있는 분기, 또는 별도의 플래그가 의미 없다고 표시한 데이터에만 둡니다. 채점 문제는 두 번째 경우입니다.

6. 플래그가 값을 관찰 불가능하게 만들 수 있습니다

struct는 이름 있는 필드를 값 하나로 묶습니다. 아래 문제는 다음 타입을 사용합니다.

typedef struct {
  Bool    valid;
  Bit#(8) payload;
} Decoded deriving (Bits, Eq);

값은 Decoded { valid: True, payload: 8'h11 }로 만들고 d.valid, d.payload로 읽습니다. deriving 절은 이 타입에 비트 표현과 ==를 제공합니다. 나머지는 사용자 정의 타입 장에 있습니다.

이 쌍의 계약은 프로토콜입니다. payload는 valid가 True일 때만 정보를 담습니다. valid가 False이면 올바른 호출자는 payload를 읽지 않으므로, 그 값은 약속에 의해 관찰 불가능하고 ?가 관용적인 선택입니다. 8'h00 같은 상수도 똑같이 올바릅니다. 어느 쪽을 안전하게 만드는 것은 플래그와 그것을 지키는 호출자이고, 표기법이 아닙니다.

확인 문제

decodeCommand(3'd4)는 불법 코드입니다. 프로토콜을 따르는 결과는 무엇입니까?

답

7. 적용: 불법 코드를 무효로 표시하세요

채점 문제의 decodeCommand는 코드 0부터 3을 네 페이로드로 대응시키고, default 항목이 불법 코드 4부터 7에 대해 valid: True를 반환합니다. 그 항목만 바꿔 모든 불법 코드가 valid: False를 보고하게 하고, 네 개의 합법 항목은 그대로 두세요. 테스트벤치는 합법 코드 3을 페이로드와 함께, 불법 코드 4를 플래그만 출력합니다. 채점은 여덟 코드를 모두 확인합니다.

8. 자주 하는 실수

8.1 도달할 수 있는 분기에 ? 쓰기

// 잘못된 코드: 3비트 code의 4~7이 default로 가서 정해지지 않은 값이 나갑니다
default: return ?;

// 바른 코드: 도달할 수 있는 분기에는 구체적인 값을 반환합니다
default: return 4'h0;

?는 그 값이 관찰되지 않는다는 증명이 있을 때만 씁니다. 입력 범위가 모든 분기로 덮이는지 먼저 확인하세요. code가 3비트이고 명시 항목이 네 개뿐이면 덮이지 않으므로, 4절의 예제라면 여덟 코드 가운데 네 개에서 컴파일러가 고른 값이 출력됩니다.

9. 정리

요소뜻
c ? a : b2입력 멀티플렉서. 두 분기는 같은 타입. 이어 쓰면 우선순위
begin … end문장을 묶습니다. 괄호로 감싸면 값 하나를 계산하는 식이 되고, 안의 이름은 조합 중간값입니다
if (c) … else …실행 중의 조건은 멀티플렉서가 됩니다. Action 안에서는 분기의 쓰기가 조건이 참일 때만 일어납니다
case (x) … default: … endcase상수와 비교. 첫 일치가 이김. 디코더 뒤에 멀티플렉서
모든 경로에 대입빠지면 if에서는 경고 P0042, 값을 쓰는 순간 오류 G0028. 래치는 만들어지지 않습니다
?don't-care. 컴파일러의 선택. 관찰 불가능한 자리에만. 관찰되는 상태나 mkReg(?)에는 쓰지 않습니다
타입 { 필드: 값 }, v.필드struct 만들기와 읽기. valid 플래그가 프로토콜로 payload를 관찰 불가능하게 만들 수 있습니다

확인 지점

실습 문제를 제출해 채점을 받습니다

채점은 서버의 비공개 테스트로 진행되며, 결과는 이 브라우저에만 기록됩니다.

참고 자료

참고한 자료의 라이선스와 고지