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