1. 리터럴은 자기 폭을 가지고 있습니다
Verilog의 숫자는 폭, 작은따옴표, 진법 문자, 숫자를 차례로 씁니다. 폭은 값의 일부입니다.
8'hA5는 8비트이고 4'b1010은 4비트입니다.
형식
<폭>'<진법><숫자> // 8'hA5, 4'b1010, 8'd7| 진법 문자 | 진법 | 예 | 값 |
|---|---|---|---|
b | 2진수 | 4'b1010 | 10 |
o | 8진수 | 6'o17 | 15 |
d | 10진수 | 8'd7 | 7 |
h | 16진수 | 8'hA5 | 165 |
몇 가지 규칙을 더 알아 두면 리터럴을 정확히 읽을 수 있습니다.
- 숫자 사이의 밑줄
_은 값에 영향을 주지 않습니다.8'b1100_0101처럼 자릿수를 끊어 읽기 쉽게 할 때 씁니다. - 숫자 자리에
x와z를 쓸 수 있습니다.4'b10x1은 bit 1만 미지값인 4비트 값입니다. 12나'hA5처럼 폭을 생략한 리터럴은 크기 미지정 리터럴입니다. 표준은 이 리터럴의 폭을 최소 32비트로 정합니다. Icarus는 이처럼 작은 값에는 32비트를 쓰지만, 더 큰 상수는 값을 보존하는 데 필요한 만큼 폭을 늘립니다.
주의. 식 안의 크기 미지정 리터럴은 문맥이 요구하는 폭으로 확장되고, 최소 32비트인 자기 폭으로 결과
폭 계산에도 참여합니다. 그래서 무심코 쓴 + 1 하나가 계산 전체를 넓히기도 합니다(6절).
회로 코드에서는 8'd7처럼 폭을 적는 습관을 들이세요.
2. 비트 하나는 0, 1, x, z 가운데 하나입니다
Verilog의 비트 하나는 다음 네 값 가운데 하나를 가집니다.
| 값 | 뜻 | 언제 나타나는가 |
|---|---|---|
0 | 논리 0 | |
1 | 논리 1 | |
x | 알 수 없는 값(미지값) | 아직 대입하지 않은 변수, 값이 충돌하는 신호 |
z | 아무도 구동하지 않는 상태(high impedance) | 연결되지 않은 선 |
x는 "0이든 1이든 상관없다"는 뜻이 아니라 "현재 값을 알 수 없다"는 뜻입니다. 연산의 입력에
x나 z가 섞이면 그 비트에 의존하는 결과 비트도 모두 x가 됩니다. 다른 피연산자가 이미
결정하는 비트는 알려진 값을 유지하므로 0 & x는 0, 1 | x는 1입니다.
3. 형식 지정자는 비트를 보여 주는 모양만 정합니다
$display의 형식 문자열에는 C와 비슷한 형식 지정자를 씁니다.
| 지정자 | 출력 형태 | 8'hA5의 출력 |
|---|---|---|
%b | 2진수 | 10100101 |
%h | 16진수 | a5 |
%d | 10진수. 폭이 가질 수 있는 최대 자릿수만큼 칸을 확보합니다 | 165 |
%0d | 10진수. 앞쪽 공백 없이 출력합니다 | 165 |
형식 지정자는 같은 비트를 어떤 모양으로 보여 줄지만 정합니다. 저장된 값은 바뀌지 않습니다.
4. 실행: 한 바이트를 세 진법으로, 칸 채움, 그리고 x
Literals.v는 리터럴, 형식 지정자, x의 동작을 한 번에 확인하는 예제입니다. 출력 줄마다 맨
앞에 RADIX, PAD 같은 표식이 있어서 각 줄이 어느 $display에서 나왔는지 바로 찾을 수
있습니다. 코드를 읽을 때는 다음 세 가지에 주목하세요.
value에는8'hA5를 대입합니다.nibble에는 bit 1이x인4'b10x1을 대입합니다.never_set은 선언만 하고 값을 대입하지 않습니다.
module literals;
reg [7:0] value;
reg [3:0] nibble;
reg [7:0] never_set;
initial begin
value = 8'hA5;
// 저장된 패턴 하나를 세 가지 진법으로 출력한다.
$display("RADIX hex=%h bin=%b dec=%0d", value, value, value);
// %d는 8비트 값의 최대 10진 자릿수만큼 칸을 채운다.
$display("PAD [%d] [%0d]", 8'd7, 8'd7);
nibble = 4'b10x1;
$display("FOUR-STATE nibble=%b", nibble);
// 아직 대입하지 않은 변수는 모든 비트가 x다.
$display("UNSET never_set=%b", never_set);
// 입력에 미지 비트가 하나라도 있으면 합 전체가 미지값이다.
$display("X-ADD %b", nibble + 4'd1);
$display("SIZES %0d %0d", $bits(8'hA5), $bits('hA5));
$finish;
end
endmodule실행하기 전에 예측해 보세요.
RADIX줄에서8'hA5의 16진수, 2진수, 10진수 출력은 각각 무엇일까요?PAD줄에서%d로 출력한 7 앞에는 공백이 몇 개 붙을까요?- 대입하지 않은
never_set은 0으로 출력될까요? nibble + 4'd1에서x는 bit 1 하나뿐인데, 결과에서도 한 비트만x일까요?
확인 문제
PAD 줄에서 8'd7을 %d로 출력합니다. 7 앞에 공백이 몇 개 붙습니까?
확인 문제
SIZES 줄은 $bits(8'hA5)와 $bits('hA5)를 차례로 출력합니다. Icarus에서 두 번째 수는 무엇입니까?
결과 확인. 실행을 누른 뒤 줄마다 다음 내용을 확인하세요.
| 출력 줄 | 확인할 내용 |
|---|---|
RADIX | 같은 8비트가 a5, 10100101, 165로 출력됩니다 |
PAD | 8비트 최댓값 255가 세 자리이므로 %d는 세 칸을 잡아 7을 출력합니다. %0d는 7만 출력합니다 |
FOUR-STATE | x가 bit 1에 그대로 출력됩니다 |
UNSET | 대입하지 않은 변수는 0이 아니라 x 여덟 개입니다 |
X-ADD | Verilog 덧셈은 피연산자에 x나 z가 하나라도 있으면 결과 전체를 x로 만듭니다 |
SIZES | $bits는 폭을 반환합니다. 8'hA5는 8, 폭을 생략한 'hA5는 32입니다 |
UNSET 줄도 읽어 보세요. 대입하지 않은 변수는 0이 아니라 모든 비트가 x입니다.
X-ADD에서는 피연산자의 한 비트만 x여도 합 전체가 x가 됩니다. 이는 Verilog의 산술
연산 규칙이며, 실제 회로에서 합의 어느 비트까지 확정할 수 있는지 비트별로 따진 결과는 아닙니다.
직접 바꿔 보세요.
| 바꿀 곳 | 예상 결과 |
|---|---|
4'b10x1을 4'b1001로 | 미지 비트가 없어지므로 X-ADD가 1010이 됩니다 |
4'b10x1을 4'b10z1로 | z도 산술 연산에서는 값을 정할 수 없으므로 다시 xxxx가 됩니다 |
5. 같은 비트를 signed로도 unsigned로도 읽습니다
5.1 signed 선언
비트 패턴 자체에는 부호가 없습니다. 어떻게 읽을지는 선언이 정합니다. reg [3:0] u는 1111을
15로 읽고, reg signed [3:0] s는 같은 1111을 2의 보수인 −1로 읽습니다. %d는 선언이 정한
읽기 방식대로 출력합니다.
reg [3:0] u; // 1111은 15로 출력
reg signed [3:0] s; // 1111은 -1로 출력선언은 두 규칙을 통해 모든 식에 영향을 줍니다.
| 규칙 | 효과 |
|---|---|
| 이항 덧셈은 두 피연산자가 모두 signed일 때만 signed | unsigned 피연산자가 하나라도 있으면 덧셈은 unsigned |
| 더 넓은 대상은 우변을 확장 | unsigned 값은 0 확장, signed 값은 부호 확장 |
그래서 s를 8비트 대상에 대입하면 1111_1111이 되어 값 −1이 유지되고, u를 같은 대상에
대입하면 0000_1111이 되어 15가 유지됩니다. s + u처럼 섞으면 부호 읽기가 사라집니다.
두 규칙은 이미 연산자
예제에서 실행해 보았습니다. 같은 비트를 일반 벡터와 signed 벡터에 담자 >>>와 <의
결과가 달라졌습니다. 그 밖의 실행 예제는 모두 unsigned이며, 예제를 읽는 데 필요한 signed
규칙은 이 두 가지가 전부입니다.
확인 문제
s는 reg signed [3:0]으로 선언되어 1111을 담고 있습니다. %0d는 s를 무엇으로 출력합니까?
5.2 폭이 다른 값을 대입할 때
대입하는 값과 대상의 폭이 다르면 Verilog는 폭을 맞춥니다. 폭 조정은 식을 계산하기 전에 피연산자에 적용되므로, 넓은 대상은 거기에 연결된 산술의 폭도 넓힙니다(6절).
| 경우 | 동작 | 예: 8비트 1100_0101을 대입 |
|---|---|---|
| 대상이 더 좁다 | 상위 비트를 버립니다(절단) | 4비트 대상에는 0101이 남습니다 |
| 대상이 더 넓고 값이 부호 없음 | 상위를 0으로 채웁니다(0 확장) | 12비트 대상에는 0000_1100_0101이 들어갑니다 |
대상이 더 넓고 값이 signed | 상위를 부호 비트로 채웁니다(부호 확장) | 12비트 대상에는 1111_1100_0101이 들어갑니다 |
두 경우 모두 컴파일 오류가 아니고, Icarus는 기본 설정에서 경고도 출력하지 않습니다. lint
도구와 합성 도구는 보통 절단을 경고합니다. 그래서 신호를 선언할 때 적은 폭이 곧 회로 동작의
일부가 됩니다. 벡터·비트
선택·연산자 장에서 본 narrow와 wide 출력이 이 표의 첫 두 행입니다.
6. 식은 참여자 가운데 가장 넓은 폭으로 계산됩니다
덧셈의 폭은 +가 정하지 않습니다. Verilog는 대입문 전체, 곧 피연산자와 대상을 함께 봅니다.
그 가운데 가장 넓은 폭이 식을 계산하는 폭이 되고, 자리올림은 그 폭에 자리가 있을 때만
남습니다.
6.1 문맥 결정 폭과 자체 결정 폭
대부분의 식은 연산자 하나가 아니라 대입문 전체를 보고 정한 폭으로 계산됩니다. 피연산자 위치에 따라 두 가지 규칙이 적용됩니다.
| 종류 | 해당하는 위치 | 계산 폭 |
|---|---|---|
| 문맥 결정 | 산술·비트 연산자의 피연산자, ?:의 두 분기, 대입문 우변 | 해당 피연산자들과 대입 대상 가운데 가장 넓은 폭 |
| 자체 결정 | { } 안의 각 항목, shift 양, 비교 결과, 조건식 | 자기 자신의 폭 |
wire [7:0] a, b;
wire [8:0] total;
wire [7:0] y;
assign total = a + b; // 9비트로 계산. 자리올림이 bit 8에 남습니다
assign y = (a + b) >> 1; // 8비트로 계산. shift 전에 자리올림이 사라집니다첫 번째 대입은 대상 total이 9비트이므로 덧셈도 9비트로 계산됩니다. 두 번째 대입은
참여하는 값이 모두 8비트이므로 덧셈 결과의 bit 8이 처음부터 만들어지지 않습니다. 8비트
피연산자의 {a + b}가 8비트인 것도 같은 이유입니다. 중괄호 안의 항목은 자체 결정 폭으로
계산됩니다.
6.2 폭을 넓히는 방법과 좁히는 함정
| 식 | 덧셈 폭 | 이유 |
|---|---|---|
{1'b0, a} + b | 9비트 | 9비트 피연산자 하나가 식 전체를 넓힙니다 |
a + b + 0 | 최소 32비트 | 크기 미지정 0이 덧셈을 넓힙니다. Icarus는 크기 미지정 식의 폭을 더 늘릴 수도 있습니다 |
{a + b} | 8비트 | 중괄호 안의 항목은 자체 결정 폭으로 계산됩니다 |
주의. 중괄호 안에 쓴 합은 대입 대상이 아무리 넓어도 피연산자 폭으로 계산됩니다. 연결 연산이 결과를 넓히는 것처럼 보여도, 넓어지기 전에 덧셈의 자리올림은 이미 버려집니다.
6.3 실행: 두 폭에서 평균 계산하기
Average.v는 #(parameter W = 8)로 폭을 받는 모듈이며 모든 포트 폭을 W로 표현합니다.
parameter를 선언하고 재정의하는 방법은 인스턴스·계층·parameter
장에서 다루며, 여기서는 "같은 모듈을 두 폭으로"라는 뜻으로만 읽으면 됩니다. 이 모듈은 같은 두
입력의 평균을 두 가지 방법으로 계산하고 합도 따로 출력합니다.
| 출력 | 계산 | 덧셈 폭 |
|---|---|---|
avg_short | (a + b) >> 1 | W |
avg_full | ({1'b0, a} + b) >> 1 | W+1 |
total | a + b를 W+1비트 출력에 대입 | W+1 |
avg_short는 W비트로 더한 뒤 shift합니다. avg_full은 한 피연산자 앞에 1'b0을 붙여 덧셈
전체를 W+1비트로 넓힙니다. total은 W+1비트 출력에 대입되므로 대상이 덧셈을 넓힙니다.
// W비트 값 두 개의 평균을 서로 다른 두 폭으로 계산한다.
module average #(parameter W = 8) (
input [W-1:0] a,
input [W-1:0] b,
output [W-1:0] avg_short,
output [W-1:0] avg_full,
output [W:0] total
);
// 모든 피연산자가 W비트이므로 a + b의 자리올림이 사라진다.
assign avg_short = (a + b) >> 1;
// W+1비트 피연산자 하나가 식 전체를 넓힌다.
assign avg_full = ({1'b0, a} + b) >> 1;
// W+1비트 대입 대상도 덧셈을 넓힌다.
assign total = a + b;
endmoduleTb.v는 인스턴스 두 개를 배치합니다. average #(.W(8)) wide는 기본값을 그대로 쓰고,
average #(.W(4)) narrow는 W를 4로 재정의합니다. 따라서 narrow의 입력은 4비트, total은
5비트입니다. 두 인스턴스에 합이 폭 안에 들어가는 입력 쌍을 먼저 넣고, 합이 폭을 넘는 입력
쌍을 다음에 넣습니다.
module tb;
reg [7:0] a8, b8;
wire [7:0] short8, full8;
wire [8:0] total8;
reg [3:0] a4, b4;
wire [3:0] short4, full4;
wire [4:0] total4;
average #(.W(8)) wide (.a(a8), .b(b8), .avg_short(short8), .avg_full(full8), .total(total8));
average #(.W(4)) narrow (.a(a4), .b(b4), .avg_short(short4), .avg_full(full4), .total(total4));
initial begin
a8 = 8'd100; b8 = 8'd50; a4 = 4'd6; b4 = 4'd4;
#1 $display("W8 %0d,%0d short=%0d full=%0d total=%0d", a8, b8, short8, full8, total8);
$display("W4 %0d,%0d short=%0d full=%0d total=%0d", a4, b4, short4, full4, total4);
a8 = 8'd200; b8 = 8'd100; a4 = 4'd12; b4 = 4'd10;
#1 $display("W8 %0d,%0d short=%0d full=%0d total=%0d", a8, b8, short8, full8, total8);
$display("W4 %0d,%0d short=%0d full=%0d total=%0d", a4, b4, short4, full4, total4);
$finish;
end
endmodule이 개념 실습하기
다음 단계로 짧은 Verilog 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요. 8비트 인스턴스의 두 번째 쌍은 200 + 100 = 300이며, 300을 나타내려면 9비트가 필요합니다.
avg_short는 22일까요, 150일까요?avg_full과total은 각각 무엇일까요?- 4비트 인스턴스의 두 번째 쌍 12 + 10에서
short와full은 각각 무엇일까요?
확인 문제
8비트 인스턴스에 200과 100을 넣었을 때 short는 무엇을 출력합니까?
확인 문제
4비트 인스턴스에 12와 10을 넣었을 때 full은 무엇을 출력합니까?
결과 확인. 실행을 누르고 W8 두 줄과 W4 두 줄을 답과 비교하세요.
| 출력 줄 | 확인할 내용 |
|---|---|
두 W8, W4 첫 줄 | 합이 폭 안에 들어가므로 short와 full이 같습니다 |
W8 200,100 | short는 300을 8비트로 절단한 44를 shift한 22, full은 9비트 덧셈을 유지한 150, total은 300입니다 |
W4 12,10 | 22가 4비트에서 16을 잃어 6이 되므로 short는 3이고, full은 11입니다 |
자리올림을 잃게 만든 것은 + 연산자가 아닙니다. 피연산자와 대입 대상이 모두 좁아서 덧셈이
더 넓은 폭으로 계산될 문맥이 없었습니다. 같은 정의에서 폭이 다른 회로 두 개가 생성되었고, 각
인스턴스는 자기 W에서 같은 규칙을 따랐습니다.
7. 하나만 바꿔 보세요
Average.v에서 avg_short를 (a + b + 0) >> 1로 바꾸고 다시 실행하세요. 크기 미지정 0이
덧셈의 폭에 무엇을 하는지 먼저 정하세요.
확인 문제
a와 b가 8비트일 때 a + b + 0은 몇 비트로 계산됩니까?
| 바꿀 곳 | 예상 결과 |
|---|---|
avg_short를 (a + b + 0) >> 1로 | 크기 미지정 0이 덧셈을 최소 32비트로 넓히므로 두 번째 쌍의 short가 150과 11이 됩니다 |
avg_full을 {a + b} >> 1로 | 중괄호 안의 합이 W비트로 계산되므로 full이 22와 3으로 떨어져 short와 같아집니다 |
8. 자주 하는 실수
8.1 좁은 대상에 대입해 상위 비트 잃기
wire [3:0] low4;
assign low4 = 8'hC5; // low4는 4'h5. 상위 4비트가 경고 없이 사라집니다컴파일 오류가 없고 Icarus에서는 경고도 없으므로 결과가 이상하면 데이터가 지나는 신호의 폭부터 확인하세요.
8.2 %d 출력 앞의 공백
%d는 값의 폭이 가질 수 있는 최대 자릿수만큼 칸을 잡습니다. 8비트 값 7은 7로
출력됩니다. 공백 없이 출력하려면 %0d를 쓰세요.
8.3 크기 미지정 리터럴이 식을 넓힘
wire [7:0] a, b, y;
assign y = (a + b + 1) >> 1; // Icarus에서 1이 32비트이므로 덧셈 전체가 32비트
assign y = (a + b + 8'd1) >> 1; // 8비트 덧셈. 자리올림이 사라집니다어느 쪽이 맞는지는 의도에 달려 있지만, 결과가 리터럴 하나 때문에 달라진다는 점은 알아 두어야 합니다. 폭이 중요한 식에는 크기 지정 리터럴을 쓰고, 넓은 결과가 필요하면 피연산자나 대상을 명시적으로 넓히세요.
9. 실습: 자리올림 보존하기
점검 과제는 실습 문제 carry-out입니다. 4비트 덧셈기가 5비트 sum을 구동하는데, 15를 넘는
결과가 모두 정확히 16만큼 작게 출력됩니다. assign에는 {1'b0, a + b}라고 쓰여 있습니다.
덧셈이 중괄호 안에 있으므로 4비트로 자체 결정되고, 앞에 0을 붙이기 전에 자리올림이 이미
사라집니다. 중괄호 밖의 5비트 대상은 안쪽을 넓히지 못합니다. 결과 폭이 어디서 정해지는지
찾아 5비트 합 전체가 나오도록 그 줄을 다시 쓰고, 9 + 8이 17, 15 + 15가 30으로 출력되는지
확인하세요.
10. 정리
| 요소 | 뜻 |
|---|---|
8'hA5 | 크기 지정 리터럴. 폭, 진법(b, o, d, h), 숫자. _은 무시되고 x와 z도 숫자 |
12, 'hA5 | 크기 미지정. Icarus에서 이 값들은 32비트이지만 큰 상수는 더 넓어질 수 있음. 결과 폭 계산에 참여 |
0, 1, x, z | 비트 하나의 네 값. 대입하지 않은 변수는 x이고, 입력의 x는 영향이 닿는 모든 결과 비트에 퍼짐 |
%b, %h, %d, %0d | 같은 비트를 출력하는 모양. %d는 폭이 가질 수 있는 최대 자릿수만큼 칸 확보 |
reg signed [3:0] | 같은 비트를 2의 보수로 읽음. 이항 덧셈은 두 피연산자가 모두 signed일 때만 signed |
| 좁거나 넓은 대상 | 상위 비트를 절단하거나, 부호 없는 값이면 0, signed 값이면 부호 비트로 확장. Icarus는 기본 설정에서 경고하지 않고 lint·합성 도구는 보통 경고 |
더 넓은 total에 total = a + b | 피연산자와 대상 가운데 가장 넓은 폭으로 계산. 대상이 덧셈을 넓혀 자리올림 유지 |
{a + b}, shift 양, 비교 | 자체 결정. 중괄호 안은 자기 폭이므로 자리올림 소실 |
11. 확인 문제
-
nibble의 한 비트만x인데nibble + 4'd1의 네 비트가 모두x인 이유는 무엇일까요?정답 보기
Verilog는 피연산자에
x나z가 하나라도 있으면 덧셈 결과 전체를x로 만듭니다. 비트별 회로 분석으로는 값을 정할 수 있는 비트도 미지값으로 처리하는 보수적인 시뮬레이션 규칙입니다. 최하위 결과 비트까지x인 이유를 자리올림 전파만으로 설명할 수는 없습니다. -
average #(.W(4)) narrow에서total의 폭과avg_short를 구동하는 덧셈의 폭은 각각 얼마일까요?정답 보기
total은[W:0]이므로 5비트입니다.avg_short의 덧셈은 피연산자와 대상이 모두 4비트이므로 4비트로 계산됩니다. -
{1'b0, a} + b는 자리올림을 보존하고{a + b}는 보존하지 못하는 이유는 무엇일까요?정답 보기
{1'b0, a}는W+1비트 피연산자이므로 덧셈 전체가W+1비트로 계산됩니다.{a + b}는 중괄호 안의 항목이라 자체 결정 폭인W비트로 계산되고, 자리올림 비트가 만들어지지 않습니다.