같은 기호의 두 역할
&, |, ^, 그리고 그것들의 inverted 형제들은 두 가지 형태로 나타납니다.
- 이항 형태(피연산자 둘):
a & b- 너비가 같은 vector 간의 비트와이즈 연산. - 단항 형태(피연산자 하나):
&a-a의 모든 비트에 걸친 reduction으로 단일 비트.
컴파일러는 피연산자 개수로 둘을 구분합니다. 사람들이 쓰는 명명 관례는 이항을 "비트와이즈 연산자", 단항을 "reduction 연산자"라고 부르는 것입니다.
비트와이즈: 한 번에 한 비트
풀세트.
| 연산자 | 이름 | N번째 위치의 출력 비트 |
|---|---|---|
a & b | AND | a[N] AND b[N] |
a | b | OR | a[N] OR b[N] |
a ^ b | XOR | a[N] XOR b[N] |
a ~& b | NAND | NOT (a[N] AND b[N]) |
a ~| b | NOR | NOT (a[N] OR b[N]) |
a ~^ b | XNOR | NOT (a[N] XOR b[N]) |
~a | NOT | NOT a[N] |
~&, ~|, ~^는 tilde가 먼저, 연산자가 뒤에 옵니다. 단일 토큰이며 사이에 공백이 없습니다.
두 피연산자의 너비가 다르면 좁은 쪽이 왼쪽에서 zero-extension 되어 너비를 맞춥니다. sign-extension을 원한다면 $signed()로 부호 있는 피연산자를 명시적으로 사용하세요.
Reduction: 많은 비트에서 하나로
같은 연산자의 단항 형태는 vector를 단일 비트로 줄입니다.
각각의 의미.
&data는data의 모든 비트 가 1이면1, 아니면0을 반환. "전부 1인가?" 검사에 유용.|data는data의 어떤 비트라도 1이면1, 아니면0을 반환. "non-zero인가?" 검사에 유용.^data는 parity 를 반환 - 모든 비트의 XOR. 1의 개수가 홀수면1, 짝수면0.~&data,~|data,~^data는 위의 inverse.
실제 코드 어디서나 보이게 됩니다.
wire empty = ~|fifo_count; // count가 0이면 empty
wire all_ones = &mask; // 모든 비트가 set
wire parity_bit = ^data; // 바이트의 parity
wire any_request = |request_vector; // 무언가 요청 중인가?
reduction 형태는 간결하고, 명백한 게이트 트리로 synthesize됩니다: &는 다중 입력 AND 게이트, |는 다중 입력 OR, ^는 XOR 트리(하드웨어에서 parity generator이기도)로 줄어듭니다.
흔한 패턴
비트 set/clear
wire [7:0] data;
wire [7:0] mask = 8'b0000_1000;
wire [7:0] set = data | mask; // 비트 3을 1로 강제
wire [7:0] cleared = data & ~mask; // 비트 3을 0으로 강제
wire [7:0] toggled = data ^ mask; // 비트 3 토글
wire [7:0] tested = data & mask; // 비트 3이 0이었으면 0, 아니면 non-zero
C에서 쓰는 비트 조작 idiom과 같습니다. 단일 게이트 연산으로 synthesize됩니다.
값이 모두 1인지 검사
wire is_max = &counter; // counter의 모든 비트가 1이면 1
게이트 하나의 reduction AND. counter == 8'hFF라고 적는 것과 비교해(역시 동작하고; synthesizer가 보통 동일한 하드웨어를 만듦) 더 간결합니다.
parity bit 생성
활성 신호 감지
wire any_pending = |request_vector;
request_vector가 넓다면(가령 64개 요청자), reduction OR이 priority encoder나 arbiter로 공급할 단일 신호로 줄여 줍니다.
Shift 연산자
비트 레벨 연산자에 있는 김에 shift도 봅시다.
a << N은a를 왼쪽으로 N비트 위치 shift하고 오른쪽을 0으로 채웁니다.a >> N은a를 오른쪽으로 N비트 위치 shift하고 왼쪽을 0으로 채웁니다.a <<< N은 arithmetic left shift(부호 없는 경우<<와 같음).a >>> N은 arithmetic right shift -a가 부호 있으면 부호 비트로 채웁니다.
상수만큼의 shift는 하드웨어에서 무료입니다(그냥 재배선). 런타임 변수만큼의 shift는 barrel shifter를 만들고, 더 크지만 여전히 저렴합니다.
다음에 볼 내용
단일 값을 반환하는 모든 연산자를 봤습니다. 다음 문서 - Concatenation and Replication - 은 조각들로부터 더 넓은 vector를 만드는 {}와 {N{...}} 문법을 다루며, 너비가 다른 모듈 간 인터페이스에서 끊임없이 쓰게 됩니다.
자주 묻는 질문
Verilog의 비트와이즈 연산자란?
비트와이즈 연산자는 너비가 같은 두 vector를 위치별로 결합합니다. a & b는 a의 비트 0을 b의 비트 0과 AND하고, 비트 1을 비트 1과, 이런 식으로 입력과 같은 너비의 vector를 만듭니다. 풀세트는 &(AND), |(OR), ^(XOR), ~(NOT), 그리고 inversion 형태 ~&, ~|, ~^(NAND, NOR, XNOR)입니다.
Verilog의 reduction 연산자란?
reduction 연산자는 비트와이즈 연산자의 단항 형태로, 전체 vector를 단일 비트로 줄입니다. &data는 data의 모든 비트가 1일 때만 1을 반환합니다. |data는 어떤 비트라도 1이면 1을 반환합니다. ^data는 모든 비트의 XOR - parity를 반환합니다. reduction 형태는 왼쪽에 피연산자가 없고 오른쪽에만 있습니다.
Verilog에서 &와 &&의 차이는?
&는 비트와이즈 AND - 비트를 위치별로 짝짓고 결과는 피연산자와 같은 너비입니다. &&는 logical AND - 각 피연산자를 boolean(0 vs non-zero)으로 다루고 1비트 결과를 반환합니다. 4'b1100 & 4'b0011은 4'b0000이고, 4'b1100 && 4'b0011은 1입니다.
Verilog에서 parity는 어떻게 계산하나요?
reduction XOR 연산자를 사용합니다: parity = ^data. data의 모든 비트를 함께 XOR합니다. 8비트 vector라면 data[7] ^ data[6] ^ ... ^ data[0]이 됩니다. 결과는 1 비트의 개수가 홀수면 1, 짝수면 0입니다. ~^로 invert하면 even parity가 됩니다.
Verilog에서 ~는 무엇을 하나요?
~는 비트와이즈 NOT입니다 - 피연산자의 모든 비트를 반전합니다. ~4'b1100은 4'b0011입니다. !(logical NOT)와 혼동하지 마세요. !는 피연산자를 1비트 boolean으로 줄이고 그것을 반전합니다. !4'b1100은 1'b0입니다(피연산자가 non-zero이고, 그 진리값을 NOT하면 0).