1. Bool은 참·거짓이고 Bit#(n)은 n개의 선입니다
이 장은 값을 다룹니다. 값은 회로가 같은 사이클 안에서 입력으로부터 계산하는 데이터이며, 기억하는
것이 없습니다. 이 장의 식은 모두 게이트와 선으로 된 조합 논리가 되고, 사이클 사이에 아무것도
저장하지 않습니다. 값을 저장하는 일은 두 장 뒤의 레지스터와 rule이 맡습니다.
1.1 Verilog가 하나로 다루는 두 타입
Verilog가 하나로 다루는 두 가지를 BSV는 구분합니다. Bool은 True 또는 False입니다.
비교의 결과이고, &&·||·!의 피연산자이며, 조건이 검사하는 값입니다. Bit#(n)은 비트
n개를 묶은 벡터입니다. 비트 연산자 &·|·^·~의 피연산자이고, 선 위를 지나는 모든 값의
타입입니다.
| 타입 | 값 | 연산자 | 쓰는 곳 |
|---|---|---|---|
Bool | True, False | &&, ||, !, == | 조건 |
Bit#(n) | n비트 패턴 전부 | &, |, ^, ~, { }, [ ] | 선 위의 데이터 |
두 타입은 저절로 섞이지 않습니다. Bool은 1비트 벡터가 아닙니다. 벡터에 넣으려면 pack을
호출해야 하고, pack(True)는 1'b1, pack(False)는 1'b0입니다. 반대로 비트 하나를
Bool로 만드는 것은 b == 1 같은 비교입니다. 크기 지정 리터럴은 폭과 진법을 함께 적습니다.
3'b101은 3비트, 8'h3f는 8비트입니다.
1.2 연산자와 결과 타입
BSV에는 C와 비슷한 이항 연산자가 있습니다. 결과 폭과 수의 해석은 피연산자의 타입이 정합니다. 아래 표는 전체 지도입니다. 이어지는 절에서 연산자 묶음마다 하드웨어로 무엇이 만들어지는지, 타입을 잘못 썼을 때 bsc 2026.01이 어떤 오류를 내는지 차례로 봅니다.
형식
피연산자1 연산자 피연산자2| 분류 | 연산자 | 피연산자 타입 | 결과 |
|---|---|---|---|
| 산술 | +, -, *, /, % | Bit#(n), UInt#(n), Int#(n) | 피연산자와 같은 타입 |
| 비교 | ==, !=, <, <=, >, >= | 같은 타입의 두 값 | Bool |
| 논리 | &&, ||, ! | Bool만 | Bool |
| 비트 단위 | &, |, ^, ~ | 비트 타입. Bool은 안 됩니다 | 피연산자와 같은 폭 |
| 시프트 | <<, >> | 왼쪽은 비트 타입, 오른쪽은 이동량 | 왼쪽 피연산자와 같은 타입 |
UInt#(8) a = 200;
UInt#(8) b = 100;
UInt#(8) sum = a + b; // 44. 결과도 8비트이므로 넘친 부분이 버려집니다
Int#(8) s = -1;
UInt#(8) u = unpack(pack(s));
Bool sNeg = s < 0; // True. Int#(8)은 부호 있는 수로 비교합니다
Bool uNeg = u < 0; // False. 같은 비트가 UInt#(8)에서는 255입니다UInt#(8), Int#(8), Bit#(8)은 모두 8비트로 pack되지만 서로 다른 타입입니다. 부호 있는
산술, 부호 없는 산술, 비트 벡터 연산은 서로 다른 계약이며, 어느 계약이 적용될지는 비트
패턴이 아니라 타입이 정합니다. 같은 패턴을 세 타입으로 읽는 방법은 다음 장
타입·폭·명시적 변환에서 다룹니다.
1.3 비트 연산과 논리 연산: ~는 인버터 n개, !는 하나
두 연산자 묶음은 모양이 비슷하지만 만들어지는 회로가 다릅니다.
| 연산자 | 피연산자 | 하드웨어 |
|---|---|---|
~v | Bit#(n) | 선마다 하나씩 인버터 n개. 결과도 n비트입니다 |
a & b, a | b, a ^ b | Bit#(n) 두 개 | 비트 0은 비트 0끼리, 비트 1은 비트 1끼리 묶는 게이트 n개 |
!c | Bool | 조건 하나에 붙은 인버터 하나 |
c && d, c || d | Bool 두 개 | 두 조건을 합치는 게이트 하나 |
bsc는 두 묶음 사이를 자동으로 변환하지 않습니다. Bool에 비트 연산자를 쓰면 T0061로
멈춥니다.
Bitwise operators are not defined for Boolean values. Consider using the Boolean operators && and || instead.
반대로 비트 벡터에 논리 연산자를 쓰면 다른 오류가 납니다. Bit#(8) y = bx && 8'h0f;와
Bit#(8) y = !bx;는 둘 다 T0080으로 멈춥니다. &&와 !의 결과는 Bool뿐인데 대입할 곳은
Bit#(8)이기 때문입니다.
Type error at the use of the following function: (&&) The expected return type of the function: Bit#(8) The return type according to the use: Bool
벡터에 조건을 묻고 싶으면 그 조건을 비교식으로 적으세요. "1인 비트가 하나라도 있다"는
v != 0, "마스크 비트가 모두 1이다"는 (v & mask) == mask입니다. 비교식의 결과는 Bool이므로
&&, ||, !에 그대로 쓸 수 있습니다.
1.4 산술 결과는 피연산자 폭을 유지합니다
+, -, *의 결과 타입은 피연산자 타입과 같습니다. 8비트 덧셈과 곱셈은 8비트 가산기와
곱셈기가 되고, 비트 7 위로 넘친 자리올림은 버려집니다. 컴파일할 때도 실행할 때도 아무 경고가
없으며, 값은 2^n을 법으로 돌아갑니다.
식(모두 UInt#(8)) | 출력 | 이유 |
|---|---|---|
200 + 100 | 44 | 300은 8비트에 들어가지 않으므로 아홉째 비트가 버려집니다 |
0 - 1 | 255 | 부호 없는 뺄셈이 0 아래로 내려가면 최댓값 쪽으로 돌아갑니다 |
200 * 100 | 32 | 20000의 하위 8비트만 남습니다 |
unsignedMul(200, 100) | 20000 | 결과가 UInt#(16)이므로 어떤 곱도 다 담깁니다 |
unsignedMul과 signedMul은 n비트 피연산자 두 개를 받아 2n비트 곱 전체를 돌려줍니다.
8비트 입력이면 결과는 UInt#(16)이나 Int#(16)이고, signedMul(-16, 7)은 -112입니다. 결과
전체가 필요하면 이 함수를 쓰거나, 다음 장의 변환 함수로 피연산자를 먼저 넓히세요.
두 피연산자의 타입은 같아야 합니다. UInt#(8)에 UInt#(16)을 더하면 T0020으로 멈춥니다.
Type error at: w Expected type: UInt#(8) Inferred type: UInt#(16)
Bit#(n)에도 같은 산술 연산자를 쓸 수 있고, 이때 비트는 부호 없는 수로 해석됩니다.
8'hf0 + 8'h20은 8'h10입니다. 다만 값이 수라면 UInt#(n)으로 선언해 타입이 그 의도를
드러내게 하는 편이 좋습니다.
1.5 나눗셈과 나머지
/와 %는 Bit#(n), UInt#(n), Int#(n)에 정의되어 있습니다. /는 0 쪽으로 버림하고,
%의 부호는 피제수를 따릅니다. 그래서 Int#(8)에서 -16 / 7과 -16 % 7은 둘 다 -2입니다.
bsc 2026.01은 두 피연산자가 모두 레지스터인 나눗셈도 받아들입니다. ra와 rb가
Reg#(UInt#(8))이면 연산자를 그대로 합성 도구에 넘기는 Verilog를 만듭니다.
assign ra_QUOT_rb___d3 = ra / rb ; assign ra_REM_rb___d5 = ra % rb ;
이 한 줄은 한 클록 사이클 안에 값이 정해져야 하는 나눗셈기 전체입니다. 가산기보다 훨씬 크고
느리므로 설계에서는 의도해서 골라야 합니다. 2의 거듭제곱으로 나눌 때는 나눗셈기가 필요
없습니다. UInt에서 x / 8은 배선만으로 되는 x >> 3과 같습니다.
1.6 시프트
<<는 비트를 위쪽으로 옮기고 아래쪽을 0으로 채웁니다. >>가 위쪽을 무엇으로 채우는지는
왼쪽 피연산자의 타입이 정합니다.
| 왼쪽 피연산자 | >>가 채우는 값 | 8'hf0 >> 2를 그 타입으로 읽은 값 |
|---|---|---|
Bit#(8) | 0(논리 시프트) | 00111100 |
UInt#(8) | 0(논리 시프트) | 60 |
Int#(8) | 부호 비트의 복사본(산술 시프트) | -16에서 -4 |
결과의 타입과 폭은 왼쪽 피연산자와 같고, 밖으로 밀려난 비트는 사라집니다. 그래서 UInt#(8)
240을 왼쪽으로 2만큼 옮기면 192입니다. 이동량은 값과 타입이나 폭이 같을 필요가 없습니다.
bsc는 그 자리에 Integer, Bit#(2), UInt#(3), UInt#(8)을 모두 받아들입니다.
이동량이 상수이면 시프트는 배선일 뿐입니다. 출력 비트마다 연결될 입력 비트나 상수가 정해져 있기 때문입니다. 이동량이 레지스터나 입력에서 오면 매 사이클 연결을 고르는 멀티플렉서 층, 즉 시프터가 만들어집니다. 두 경우 모두 결과는 같으며, 아래 예제는 레지스터에 담긴 이동량을 씁니다.
왼쪽 피연산자는 비트 타입이어야 합니다. 폭을 정하지 않은 1 << 2 + 1은 Integer 식이므로
bsc가 T0062("Bitwise operators are not defined for the type: Integer")로 거부합니다.
UInt#(8) one = 1;처럼 타입을 정한 뒤에 시프트하세요.
1.7 비교는 타입을 따릅니다
비교 결과는 Bool이고, 비트를 어떻게 읽을지는 피연산자 타입이 정합니다. Bit#(n)과
UInt#(n)은 부호 없이, Int#(n)은 2의 보수 부호 있는 수로 비교합니다. 비트 8'hf0은
UInt#(8)로는 240, Int#(8)로는 -16이므로 u > 100은 True이고 s > 100은 False입니다.
둘 다 8비트라도 타입이 다르면 비교할 수 없습니다. UInt#(8)인 ra와 Int#(8)인 rm을
ra < rm으로 비교하면 T0020으로 멈춥니다.
Type error at: rm Expected type: UInt#(8) Inferred type: Int#(8)
어느 해석으로 비교할지 정하고 한쪽을 명시적으로 변환하세요. 방법은 같은 비트를 두 가지로 읽기에 있습니다.
1.8 연산자 우선순위
BSV는 Verilog·C와 같은 순서로 연산자를 묶습니다. 위에 있을수록 먼저 묶입니다.
| 순위 | 연산자 |
|---|---|
| 1 | 단항 !, ~, 단항 -, 축약 &, |, ^ |
| 2 | *, /, % |
| 3 | +, - |
| 4 | <<, >> |
| 5 | <, <=, >, >= |
| 6 | ==, != |
| 7 | & |
| 8 | ^ |
| 9 | | |
| 10 | && |
| 11 | || |
bsc 2026.01로 실행해 각 단계를 확인했습니다. 2 + 3 * 4는 14, 4 + 3 % 2는 5입니다.
one << 2 < 5는 (one << 2) < 5로, ~x & y는 x에만 보수를 취하는 식으로 묶입니다.
a | b ^ c는 a | (b ^ c), a ^ b & c는 a ^ (b & c), t || f && f는 t || (f && f)입니다.
확인 문제
Bit#(8) bx의 하위 네 비트가 모두 0인지 검사하려고 Bool y = bx & 8'h0f == 0;이라고 썼습니다. bsc는 어떻게 처리합니까?
C의 순서가 눈으로 읽는 순서와 어긋나는 곳이 함정입니다.
| 쓴 식 | 실제로 묶이는 방식 | 대신 쓸 식 |
|---|---|---|
v & mask == 0 | v & (mask == 0). 타입 오류 | (v & mask) == 0 |
a + b << 1 | (a + b) << 1 | b만 옮기려 했다면 a + (b << 1) |
x << n + 1 | x << (n + 1) | 시프트 뒤에 더하려 했다면 (x << n) + 1 |
a | b ^ c | a | (b ^ c) | OR를 먼저 하려 했다면 (a | b) ^ c |
첫 번째 함정은 타입 검사가 잡아 줍니다. 나머지 셋은 컴파일된 뒤 다른 값을 냅니다. 시프트, 산술, 비트 연산자가 섞이면 기본 순서가 우연히 원하는 순서와 같더라도 괄호를 쓰세요.
1.9 예제: 레지스터 위의 연산자
Operators.bsv는 피연산자를 레지스터에 담아 두므로 bsc가 모든 연산자를 회로로 만들고,
값은 Icarus가 계산합니다. 실행하기 전에 줄마다 결과의 방향을 정해 두세요. s>>k는 음수로
출력될까요? u>100과 s>100은 같은 답을 낼까요? 두 prec 값 가운데 어느 쪽이 넘친 합을
시프트한 값일까요?
package Operators;
(* synthesize *)
module mkTop(Empty);
// 피연산자를 레지스터에 두었으므로 bsc는 상수로 미리 계산하지 않고 연산자를 회로로 만든다.
Reg#(UInt#(8)) a <- mkReg(200);
Reg#(UInt#(8)) b <- mkReg(100);
Reg#(UInt#(8)) zero <- mkReg(0);
Reg#(Bit#(8)) pattern <- mkReg(8'hf0);
Reg#(UInt#(3)) k <- mkReg(2);
rule show;
// 같은 8비트를 부호 없는 수와 부호 있는 수로 읽는다.
UInt#(8) u = unpack(pattern);
Int#(8) s = unpack(pattern);
// + - *는 피연산자 폭을 유지하므로 넘친 부분이 버려진다. unsignedMul은 곱 전체를 남긴다.
$display("wrap: a+b=%0d 0-1=%0d a*b=%0d unsignedMul=%0d",
a + b, zero - 1, a * b, unsignedMul(a, b));
// >>는 UInt에서 0을 채우고 Int에서 부호 비트를 복사한다. <<는 항상 0을 채운다.
$display("shift: u=%0d u>>k=%0d u<<k=%0d s=%0d s>>k=%0d s<<k=%0d",
u, u >> k, u << k, s, s >> k, s << k);
// 같은 비트를 같은 리터럴과 비교한다. UInt는 부호 없이, Int는 부호 있게 비교한다.
$display("cmp: u>100=%b s>100=%b s<0=%b", u > 100, s > 100, s < 0);
// /는 0 쪽으로 버림하고 %의 부호는 피제수를 따른다.
$display("divide: u/7=%0d u%%7=%0d s/7=%0d s%%7=%0d", u / 7, u % 7, s / 7, s % 7);
// +가 <<보다 먼저 묶이므로 넘친 뒤의 합이 시프트된다.
$display("prec: a+b<<1=%0d a+(b<<1)=%0d", a + b << 1, a + (b << 1));
$finish(0);
endrule
endmodule
endpackage실행을 누르고 다섯 줄을 읽으세요.
| 줄 | 볼 곳 | 확인되는 사실 |
|---|---|---|
wrap | a+b=44, 0-1=255, a*b=32와 옆의 unsignedMul=20000 | 8비트 산술은 자리올림을 버리고, 폭을 넓히는 곱셈만 곱 전체를 남깁니다 |
shift | u>>k=60과 s>>k=-4 | 같은 비트라도 UInt에서는 0이, Int에서는 부호 비트가 채워집니다 |
cmp | u>100=1과 s>100=0 | 비트 패턴도 리터럴도 같은데 타입에 따라 답이 갈립니다 |
divide | s/7=-2와 s%7=-2 | 부호 있는 나눗셈은 0 쪽으로 버림하고, 나머지는 피제수의 부호를 따릅니다 |
prec | a+b<<1=88과 a+(b<<1)=144 | 괄호가 없으면 넘친 합 44가 시프트되고, 괄호가 있으면 b가 먼저 시프트됩니다 |
그다음 실행 패널에서 한 번에 하나씩 바꿔 보세요. pattern을 8'h70으로 바꾸면 최상위 비트가
0이므로 u와 s가 모두 112로 출력되고, >>, >100, /7의 답도 같아집니다. s<<k만은
192가 아니라 -64로 다르게 나옵니다. 시프트가 부호 비트 자리에 1을 옮겨 놓기 때문입니다.
두 번째 prec 식의 괄호를 지우면 두 값이 모두 88이 됩니다.
2. 조건 연산자
? :는 타입이 같은 두 값 가운데 하나를 고릅니다.
형식
조건 ? 참일_때_값 : 거짓일_때_값UInt#(8) magnitude = negative ? 0 - value : value;| 부분 | 의미 |
|---|---|
negative | Bool 조건입니다 |
0 - value | 조건이 참일 때의 결과입니다 |
value | 조건이 거짓일 때의 결과입니다 |
결과에 쓰이는 쪽은 선택된 분기 하나지만, 두 분기 모두 타입 검사를 통과해야 하고 타입이
같아야 합니다. Bit#(8) 분기와 Bit#(4) 분기를 섞으면 타입 오류 T0020이 발생합니다. 이
연산자가 하드웨어로 무엇인지, 그리고 case와 함수로 더 큰 선택 회로를 만드는 방법은
선택·case·don't-care 값 장에서 다룹니다.
3. 벡터는 중괄호로 조립하고 대괄호로 분해합니다
연결과 선택은 비트의 위치를 바꾸고, 축약 연산은 여러 비트의 값을 결합합니다.
| 형식 | 뜻 | 결과 폭 |
|---|---|---|
{ a, b, c } | 연결. 첫 피연산자가 상위 비트에 놓입니다 | 피연산자 폭의 합 |
v[6:4] | 비트 선택. 양 끝을 포함합니다 | 6 - 4 + 1 = 3 |
^v | 축약 연산. v의 모든 비트를 XOR합니다. &v, |v도 같은 형식입니다 | 1 |
연결과 상수 인덱스를 쓰는 선택은 배선이고, 축약 연산은 벡터 하나의 비트들을 결합하는 게이트 트리입니다.
3.1 비트 선택
대괄호로 비트 벡터의 일부 비트를 꺼냅니다. 범위는 양 끝 인덱스를 모두 포함합니다.
형식
벡터[인덱스]
벡터[상위:하위]Bit#(8) frame = 8'b1101_0111;
Bit#(1) top = frame[7]; // 1
Bit#(3) code = frame[6:4]; // 101frame[6:4]의 폭은 6 - 4 + 1 = 3비트입니다.
3.2 비트 연결
중괄호는 비트 벡터 여러 개를 이어 더 넓은 벡터를 만듭니다. 가장 왼쪽 피연산자가 최상위 쪽에 놓입니다.
형식
{ 피연산자1, 피연산자2, ... }Bit#(8) joined = { 4'ha, 4'h5 }; // 8'ha5
Bool ready = True;
Bit#(4) withFlag = { pack(ready), 3'b010 }; // 4'b1010결과 폭은 피연산자 폭의 합입니다. 선언한 폭과 다르면 T0060("bit-size mismatch")로 컴파일이
중단됩니다. Bool은 pack으로 비트로 바꾼 뒤에 연결할 수 있습니다.
3.3 축약 연산
단항 축약 연산은 벡터 하나의 모든 비트를 1비트로 줄입니다.
형식
&벡터 |벡터 ^벡터| 연산자 | 결과가 1인 경우 |
|---|---|
&v | v의 모든 비트가 1일 때 |
|v | 1인 비트가 하나라도 있을 때 |
^v | 1인 비트가 홀수 개일 때(패리티) |
Bit#(8) payload = 8'b0010_0110;
// &payload는 0, |payload는 1, ^payload는 1(1인 비트가 세 개)^v는 v에 1이 홀수 개일 때 1입니다. 그래서 패리티 연산자로 쓰입니다.
4. 함수는 호출한 자리에 놓이는 회로입니다
4.1 함수 정의
일반 값을 반환하는 순수 function은 인자를 받고, 결과 타입을 가지며, 문장 본문에서 결과를 계산합니다. 상태도
클록도 없습니다. rule이나 method에서 호출하면 그 지점에 함수의 회로가 한 벌 놓이고,
호출한 쪽과 같은 사이클에 계산됩니다.
형식
function 결과_타입 이름(인자_타입1 인자1, 인자_타입2 인자2);
문장
return 식;
endfunctionfunction UInt#(8) increment(UInt#(8) value);
return value + 1;
endfunction| 부분 | 의미 |
|---|---|
이름 앞의 UInt#(8) | 결과 타입입니다 |
(UInt#(8) value) | 인자 하나입니다 |
return value + 1; | 결과입니다 |
짧은 함수는 대입 하나로도 정의할 수 있습니다.
function UInt#(8) increment(UInt#(8) value) = value + 1;함수 안에서 =로 선언한 지역 변수는 값에 붙인 이름이고 저장 소자가 아닙니다. 결과 타입은
검사됩니다. 8비트를 반환한다고 선언한 함수는 정확히 8비트를 반환해야 합니다. 함수를 호출하면
호출한 곳에 논리가 만들어질 뿐 상태는 생기지 않습니다. 함수를 다른 함수의 인자로 전달할 수
있고, 타입클래스와 proviso 장의 proviso를 붙일
수도 있습니다. 레지스터 쓰기는 Action이나 ActionValue로 기술하고 rule이나 method에서
실행합니다. 함수가 이런 동작의 기술을 반환할 수도 있지만, 이 장의 순수 값 함수는 일반 값을
반환합니다.
4.2 함수 호출과 method 호출
함수 호출은 함수를 인자에 적용합니다. 예를 들면 increment(x)입니다. 점 표기는 인터페이스의
method를 선택합니다. 예를 들면 dispenser.issue, fifo.first입니다. method를 쓰는 방법은
method의 타입이 정합니다.
| method 종류 | 쓰는 방법 | 예 |
|---|---|---|
value method | 일반 값처럼 씁니다 | Bit#(8) x = fifo.first; |
Action method | Action 문맥의 문장으로 씁니다 | fifo.deq; |
ActionValue method | <-로 실행합니다 | let t <- dispenser.issue; |
구조체 식, tagged union 식, 인터페이스 식도 값을 만듭니다. 구조체 식은 이름 있는 필드를
채우고, tagged union 식은 멤버 하나를 고르며, 인터페이스 식은 method 구현을 제공합니다.
인터페이스의 method 구현에는 조건과 Action이 함께 들어갈 수 있습니다. 이 타입들은
사용자 정의 타입 장에서 소개합니다.
5. 식의 타입이 역할을 정합니다
지금까지 본 식은 모두 값이었습니다. BSV에는 식이 세 종류 더 있으며, 뒤 장에서 하나씩 만납니다.
지금 그 존재를 알아 두면 함수가 무엇을 할 수 없는지 이해할 수 있습니다. BSV의 모든 식에는 타입이 있고, 타입이 그 식이 나타내는 대상을 정합니다. 피연산자로 계산한
값일 수도 있고, 원자적인 상태 변경일 수도 있고, 상태를 바꾸면서 값을 반환하는 동작일 수도
있고, elaboration 중에 만드는 rule 묶음일 수도 있습니다. 식의 내부를 읽기 전에 다음 네
종류 가운데 어디에 속하는지 먼저 확인하세요.
| 식의 타입 | 나타내는 대상 | 효과가 생기는 시점 |
|---|---|---|
Bit#(n), UInt#(n), 구조체 같은 값 타입 | 피연산자로 계산한 데이터 | rule이나 method가 식을 평가할 때 |
Action | 반환값이 없는 원자적 상태 변경 | Action 문맥에서 실행할 때 |
ActionValue#(t) | 원자적 상태 변경과 타입 t의 결과 하나 | Action 문맥에서 <-로 실행할 때 |
Rules | rule 정의의 묶음 | elaboration 중에 addRules가 추가할 때 |
값 식은 조합 논리입니다. Action은 수행할 작업의 기술이므로 =로 이름을 붙여도 작업이
수행되지 않습니다. ActionValue는 같은 원자적 작업에 결과값을 더합니다. Rules는 설계
구조이며, 클록 에지에서 일어나는 일이 아니라 시뮬레이션 전에 이미 만들어져 있는 대상입니다.
확인 문제
UInt#(8)을 반환하는 순수 함수 increment(x)를 호출할 때마다 카운터 레지스터도 하나 올리고 싶습니다. 이 함수로 할 수 있습니까?
이 장은 첫 번째 종류만 다룹니다. Action과 ActionValue 식은
method: value·Action·ActionValue 장에서,
Rules 식은 값으로 다루는 rule 장에서 배웁니다.
6. 예제: 패리티 프레임 조립과 해석
BitComposition.bsv는 필드 세 개와 패리티 비트로 8비트 프레임을 만들고, 다시 각 필드로
나누어 출력합니다. makeFrame부터 읽으면서 pack(ready), code, count의 폭을 더해 보세요.
그다음 show가 프레임 전체와 선택한 필드를 출력하는 부분을 찾으세요.
호출 인자는 ready = True, code = 3'b101, count = 3'b011입니다.
pack(ready)는 비트1이 됩니다.- 세 값을 연결하면 7비트 본문
1_101_011이 됩니다. 폭은1 + 3 + 3 = 7입니다. - 본문에 1인 비트가 다섯 개이므로
^body는1입니다. { body, ^body }는 패리티를 최하위에 붙여 8비트1_101_011_1을 만듭니다. 폭은7 + 1 = 8입니다.- 비트 7, 6–4, 3–1, 0을 선택하면 세 필드와 패리티 비트가 복원됩니다.
실행 전에 예측하세요. 본문을 종이에 7비트 값 하나로 적고, ready, code, count,
parity가 각각 어떤 값으로 출력될지 정하세요.
확인 문제
프레임은 7비트 본문 뒤에 패리티 비트가 붙은 값입니다. 16진수 두 자리로 무엇입니까?
확인 문제
body가 Bit#(7)일 때 { body, ^body }의 폭은 몇 비트입니까?
package BitComposition;
function Bit#(8) makeFrame(Bool ready, Bit#(3) code, Bit#(3) count);
// 연결 연산이 프레임 안의 필드 위치를 정한다.
Bit#(7) body = { pack(ready), code, count };
// XOR 축약 연산이 7비트 본문에서 패리티 비트 하나를 계산한다.
return { body, ^body };
endfunction
(* synthesize *)
module mkTop(Empty);
rule show;
Bit#(8) frame = makeFrame(True, 3'b101, 3'b011);
$display("frame=%02h ready=%0d code=%0d count=%0d parity=%0d",
frame, frame[7], frame[6:4], frame[3:1], frame[0]);
$finish(0);
endrule
endmodule
endpackage이 개념 실습하기
다음 단계로 짧은 BSV 문제에 이 개념을 적용해 보세요.
실행을 누르세요. 출력 한 줄에 프레임과 분해된 네 필드가 보입니다.
| 출력 필드 | 값 | 확인되는 사실 |
|---|---|---|
frame | d7 | 연결 순서와 전체 폭이 추적한 대로입니다 |
ready | 1 | 비트 7에 첫 피연산자가 놓였습니다 |
code | 5 | 범위 6:4가 양 끝을 포함한 3비트입니다 |
count | 3 | 범위 3:1이 세 번째 피연산자와 일치합니다 |
parity | 1 | 축약 연산이 비트 0에 정확히 1비트를 만들었습니다 |
바이트 값 하나만으로는 확인되는 내용이 적습니다. 다시 나눈 필드가 입력한 값과 같으므로 조립하는 쪽과 해석하는 쪽이 모든 필드 위치에 동의한다는 것이 확인됩니다.
7. 한 곳만 바꿔 보세요
실행 패널에서 해석 부분은 그대로 두고 반환 줄을 return { ^body, body };로 바꾸세요. 바꾸기
전에 무엇이 바뀌고 무엇이 그대로일지 정해 두세요.
확인 문제
패리티 비트를 맨 위로 옮기고 디코더는 그대로 두면 어떻게 됩니까?
| 바꿀 곳 | 예상 결과 |
|---|---|
return { ^body, body };, 디코더는 그대로 | 프레임은 eb가 되고 필드는 ready 1, code 6, count 5, parity 1로 출력됩니다. 모든 선택의 폭이 여전히 올바르므로 컴파일은 성공하고 의미만 틀어집니다 |
선택도 frame[6], frame[5:3], frame[2:0], frame[7]로 모두 옮기기 | 필드가 다시 1, 5, 3, 1로 복원됩니다 |
주의. Bool은 직접 연결할 수 없습니다. { ready, code, count }처럼 Bool 피연산자를
쓰면 타입 오류가 발생합니다. pack(ready)가 Bool을 1비트로 만듭니다.
8. 적용: 패리티 프레임을 완성하고 연산을 선택하세요
아래 첫 채점 문제는 같은 프레임에서 패리티 비트가 빠진 버전입니다. encodeFrame은 7비트
본문을 올바르게 만든 뒤 1'b0을 붙입니다. 그래서 1이 짝수 개인 본문은 맞아 보이고, 홀수
개인 본문은 한 비트가 틀립니다. 그 상수를 완성된 본문 전체의 1비트 축약 연산으로 바꾸고, 필드
순서는 그대로 두고 실행하세요. 채점은 계약 전체를 확인합니다. 모든 입력에서 비트 7부터 1까지가
필드이고, 비트 0은 본문에 1이 홀수 개일 때만 1이어야 합니다.
두 번째 문제의 selectOp는 2비트 선택 값에 따라 두 4비트 피연산자의 AND, OR, XOR, NOR 가운데
하나를 반환해야 하는 순수 함수인데, 지금은 ?를 반환합니다. 선택 값에 대한 case나 조건
연산자 연쇄로 네 식 가운데 하나를 고르고, 각 분기는 1.2절의 연산자 표에 있는 연산자로 만드세요.
NOR 연산자는 따로 없습니다. OR 전체에 보수 ~를 적용한 식입니다.
9. 자주 하는 실수
9.1 선언한 폭을 채우지 못하는 연결
// 잘못된 코드: 3 + 4 = 7비트를 8비트 이름에 대입합니다
Bit#(8) f = { code, 4'h3 };
// 바른 코드: 빠진 비트를 명시합니다
Bit#(8) f = { 1'b0, code, 4'h3 };연결 연산은 스스로 0을 채우거나 비트를 잘라 내지 않습니다. 잘못된 코드는 비트 폭 불일치 오류
T0060으로 중단됩니다.
10. 정리
| 요소 | 뜻 |
|---|---|
| 식의 타입 | 식이 값을 계산하는지, 동작하는지, 구조를 만드는지 정합니다 |
Bool, True, False | 참·거짓 값. &&, ||, ! |
Bit#(n) | 비트 n개. &, |, ^, ~ |
| 연산자 | 두 피연산자의 타입이 같아야 하며, 타입이 부호 해석과 폭을 정합니다 |
+ - * | 피연산자 폭에서 넘친 부분을 버립니다. unsignedMul, signedMul은 곱 전체를 남깁니다 |
>> | Bit·UInt에서는 논리 시프트, Int에서는 산술 시프트 |
| 우선순위 | Verilog·C와 같은 순서. 시프트·산술·비트 연산자가 섞이면 괄호를 씁니다 |
pack(b) | Bool을 1비트로 |
c ? a : b | 타입이 같은 두 값 가운데 하나를 고릅니다 |
{ a, b } | 연결. 폭이 더해지고 첫 피연산자가 위. 선언과 폭이 다르면 T0060 |
v[h:l] | 비트 선택. 양 끝 포함 |
^v, &v, |v | 1비트로 축약 |
function … endfunction | 호출한 자리에 놓이는 회로. 상태 없음. method 호출은 종류에 따라 씁니다 |
11. 확인 문제
-
body가Bit#(7)일 때{ body, ^body }가 정확히 8비트인 이유는 무엇일까요?정답 보기
연결 연산은 피연산자 폭을 더하고, XOR 축약 연산의 결과는 항상 1비트입니다. 따라서 결과는 7 + 1 = 8비트입니다.