본문으로 건너뛰기
code0.HDL

Bool·Bit와 순수 함수

Bool과 Bit#(n)을 구분하고, 연결·선택·축약 연산으로 비트 벡터를 조립하고 분해하며, 호출한 자리에서 조합 회로가 되는 순수 함수를 작성합니다.

이 장에서 배울 내용

  • 식을 타입으로 분류하고 Bool과 Bit#(n)을 구분합니다
  • 연결·비트 선택·축약 연산의 폭을 계산하고 값을 예측합니다
  • 순수 함수를 작성하고, 호출이 상태가 아니라 논리를 만드는 이유를 설명합니다
  • 연산자 표에서 계약에 맞는 비트 연산자와 논리 연산자를 고릅니다
  • 피연산자 타입에 따라 오버플로·시프트·비교·나눗셈 결과가 어떻게 달라지는지 예측하고, 우선순위가 의도와 다른 곳에 괄호를 씁니다
이 장에서 배우는 개념
  • Bool·Bit·식과 순수 함수
먼저 알아야 할 개념

1. Bool은 참·거짓이고 Bit#(n)은 n개의 선입니다

이 장은 값을 다룹니다. 값은 회로가 같은 사이클 안에서 입력으로부터 계산하는 데이터이며, 기억하는 것이 없습니다. 이 장의 식은 모두 게이트와 선으로 된 조합 논리가 되고, 사이클 사이에 아무것도 저장하지 않습니다. 값을 저장하는 일은 두 장 뒤의 레지스터와 rule이 맡습니다.

1.1 Verilog가 하나로 다루는 두 타입

Verilog가 하나로 다루는 두 가지를 BSV는 구분합니다. Bool은 True 또는 False입니다. 비교의 결과이고, &&·||·!의 피연산자이며, 조건이 검사하는 값입니다. Bit#(n)은 비트 n개를 묶은 벡터입니다. 비트 연산자 &·|·^·~의 피연산자이고, 선 위를 지나는 모든 값의 타입입니다.

타입값연산자쓰는 곳
BoolTrue, 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개, !는 하나

두 연산자 묶음은 모양이 비슷하지만 만들어지는 회로가 다릅니다.

연산자피연산자하드웨어
~vBit#(n)선마다 하나씩 인버터 n개. 결과도 n비트입니다
a & b, a | b, a ^ bBit#(n) 두 개비트 0은 비트 0끼리, 비트 1은 비트 1끼리 묶는 게이트 n개
!cBool조건 하나에 붙은 인버터 하나
c && d, c || dBool 두 개두 조건을 합치는 게이트 하나

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 + 10044300은 8비트에 들어가지 않으므로 아홉째 비트가 버려집니다
0 - 1255부호 없는 뺄셈이 0 아래로 내려가면 최댓값 쪽으로 돌아갑니다
200 * 1003220000의 하위 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 == 0v & (mask == 0). 타입 오류(v & mask) == 0
a + b << 1(a + b) << 1b만 옮기려 했다면 a + (b << 1)
x << n + 1x << (n + 1)시프트 뒤에 더하려 했다면 (x << n) + 1
a | b ^ ca | (b ^ c)OR를 먼저 하려 했다면 (a | b) ^ c

첫 번째 함정은 타입 검사가 잡아 줍니다. 나머지 셋은 컴파일된 뒤 다른 값을 냅니다. 시프트, 산술, 비트 연산자가 섞이면 기본 순서가 우연히 원하는 순서와 같더라도 괄호를 쓰세요.

1.9 예제: 레지스터 위의 연산자

Operators.bsv는 피연산자를 레지스터에 담아 두므로 bsc가 모든 연산자를 회로로 만들고, 값은 Icarus가 계산합니다. 실행하기 전에 줄마다 결과의 방향을 정해 두세요. s>>k는 음수로 출력될까요? u>100과 s>100은 같은 답을 낼까요? 두 prec 값 가운데 어느 쪽이 넘친 합을 시프트한 값일까요?

Operators.bsv
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

실행을 누르고 다섯 줄을 읽으세요.

줄볼 곳확인되는 사실
wrapa+b=44, 0-1=255, a*b=32와 옆의 unsignedMul=200008비트 산술은 자리올림을 버리고, 폭을 넓히는 곱셈만 곱 전체를 남깁니다
shiftu>>k=60과 s>>k=-4같은 비트라도 UInt에서는 0이, Int에서는 부호 비트가 채워집니다
cmpu>100=1과 s>100=0비트 패턴도 리터럴도 같은데 타입에 따라 답이 갈립니다
divides/7=-2와 s%7=-2부호 있는 나눗셈은 0 쪽으로 버림하고, 나머지는 피제수의 부호를 따릅니다
preca+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;
부분의미
negativeBool 조건입니다
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];   // 101

frame[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인 경우
&vv의 모든 비트가 1일 때
|v1인 비트가 하나라도 있을 때
^v1인 비트가 홀수 개일 때(패리티)
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 식;
endfunction
function 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 methodAction 문맥의 문장으로 씁니다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 문맥에서 <-로 실행할 때
Rulesrule 정의의 묶음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입니다.

  1. pack(ready)는 비트 1이 됩니다.
  2. 세 값을 연결하면 7비트 본문 1_101_011이 됩니다. 폭은 1 + 3 + 3 = 7입니다.
  3. 본문에 1인 비트가 다섯 개이므로 ^body는 1입니다.
  4. { body, ^body }는 패리티를 최하위에 붙여 8비트 1_101_011_1을 만듭니다. 폭은 7 + 1 = 8입니다.
  5. 비트 7, 6–4, 3–1, 0을 선택하면 세 필드와 패리티 비트가 복원됩니다.

실행 전에 예측하세요. 본문을 종이에 7비트 값 하나로 적고, ready, code, count, parity가 각각 어떤 값으로 출력될지 정하세요.

확인 문제

프레임은 7비트 본문 뒤에 패리티 비트가 붙은 값입니다. 16진수 두 자리로 무엇입니까?

확인 문제

body가 Bit#(7)일 때 { body, ^body }의 폭은 몇 비트입니까?

BitComposition.bsv
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 문제에 이 개념을 적용해 보세요.

실행을 누르세요. 출력 한 줄에 프레임과 분해된 네 필드가 보입니다.

출력 필드값확인되는 사실
framed7연결 순서와 전체 폭이 추적한 대로입니다
ready1비트 7에 첫 피연산자가 놓였습니다
code5범위 6:4가 양 끝을 포함한 3비트입니다
count3범위 3:1이 세 번째 피연산자와 일치합니다
parity1축약 연산이 비트 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, |v1비트로 축약
function … endfunction호출한 자리에 놓이는 회로. 상태 없음. method 호출은 종류에 따라 씁니다

11. 확인 문제

  1. body가 Bit#(7)일 때 { body, ^body }가 정확히 8비트인 이유는 무엇일까요?

    정답 보기

    연결 연산은 피연산자 폭을 더하고, XOR 축약 연산의 결과는 항상 1비트입니다. 따라서 결과는 7 + 1 = 8비트입니다.

확인 지점

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

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

참고 자료

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