본문으로 건너뛰기
code0.HDL

리터럴·signed·식의 폭

폭을 적은 리터럴을 쓰고, 폭을 생략한 리터럴은 최소 32비트라는 규칙을 알고, 같은 패턴을 signed와 unsigned로 읽고, 덧셈이 자리올림을 잃지 않을 폭을 대상에 줍니다.

이 장에서 배울 내용

  • 어떤 진법으로든 크기 지정 리터럴을 읽고 쓰고, 크기 미지정 리터럴이 Icarus에서 갖는 폭을 말할 수 있습니다
  • x와 z의 뜻, x가 연산자를 거쳐 퍼지는 방식, %d·%0d·%b·%h가 같은 비트를 보여 주는 방식을 설명할 수 있습니다
  • 더 좁거나 넓은 대상이 unsigned 값과 signed 값을 각각 어떻게 바꾸는지 예측할 수 있습니다
  • 식이 계산되는 폭을 판단하고, 자리올림이 남도록 식을 다시 쓸 수 있습니다
이 장에서 배우는 개념
  • 리터럴·signed·식 폭
먼저 알아야 할 개념

1. 리터럴은 자기 폭을 가지고 있습니다

Verilog의 숫자는 폭, 작은따옴표, 진법 문자, 숫자를 차례로 씁니다. 폭은 값의 일부입니다. 8'hA5는 8비트이고 4'b1010은 4비트입니다.

형식

<폭>'<진법><숫자>     // 8'hA5, 4'b1010, 8'd7
진법 문자진법예값
b2진수4'b101010
o8진수6'o1715
d10진수8'd77
h16진수8'hA5165

몇 가지 규칙을 더 알아 두면 리터럴을 정확히 읽을 수 있습니다.

  • 숫자 사이의 밑줄 _은 값에 영향을 주지 않습니다. 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의 출력
%b2진수10100101
%h16진수a5
%d10진수. 폭이 가질 수 있는 최대 자릿수만큼 칸을 확보합니다165
%0d10진수. 앞쪽 공백 없이 출력합니다165

형식 지정자는 같은 비트를 어떤 모양으로 보여 줄지만 정합니다. 저장된 값은 바뀌지 않습니다.

4. 실행: 한 바이트를 세 진법으로, 칸 채움, 그리고 x

Literals.v는 리터럴, 형식 지정자, x의 동작을 한 번에 확인하는 예제입니다. 출력 줄마다 맨 앞에 RADIX, PAD 같은 표식이 있어서 각 줄이 어느 $display에서 나왔는지 바로 찾을 수 있습니다. 코드를 읽을 때는 다음 세 가지에 주목하세요.

  • value에는 8'hA5를 대입합니다.
  • nibble에는 bit 1이 x인 4'b10x1을 대입합니다.
  • never_set은 선언만 하고 값을 대입하지 않습니다.
Literals.v
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

실행하기 전에 예측해 보세요.

  1. RADIX 줄에서 8'hA5의 16진수, 2진수, 10진수 출력은 각각 무엇일까요?
  2. PAD 줄에서 %d로 출력한 7 앞에는 공백이 몇 개 붙을까요?
  3. 대입하지 않은 never_set은 0으로 출력될까요?
  4. nibble + 4'd1에서 x는 bit 1 하나뿐인데, 결과에서도 한 비트만 x일까요?

확인 문제

PAD 줄에서 8'd7을 %d로 출력합니다. 7 앞에 공백이 몇 개 붙습니까?

확인 문제

SIZES 줄은 $bits(8'hA5)와 $bits('hA5)를 차례로 출력합니다. Icarus에서 두 번째 수는 무엇입니까?

결과 확인. 실행을 누른 뒤 줄마다 다음 내용을 확인하세요.

출력 줄확인할 내용
RADIX같은 8비트가 a5, 10100101, 165로 출력됩니다
PAD8비트 최댓값 255가 세 자리이므로 %d는 세 칸을 잡아 7을 출력합니다. %0d는 7만 출력합니다
FOUR-STATEx가 bit 1에 그대로 출력됩니다
UNSET대입하지 않은 변수는 0이 아니라 x 여덟 개입니다
X-ADDVerilog 덧셈은 피연산자에 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일 때만 signedunsigned 피연산자가 하나라도 있으면 덧셈은 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} + b9비트9비트 피연산자 하나가 식 전체를 넓힙니다
a + b + 0최소 32비트크기 미지정 0이 덧셈을 넓힙니다. Icarus는 크기 미지정 식의 폭을 더 늘릴 수도 있습니다
{a + b}8비트중괄호 안의 항목은 자체 결정 폭으로 계산됩니다

주의. 중괄호 안에 쓴 합은 대입 대상이 아무리 넓어도 피연산자 폭으로 계산됩니다. 연결 연산이 결과를 넓히는 것처럼 보여도, 넓어지기 전에 덧셈의 자리올림은 이미 버려집니다.

6.3 실행: 두 폭에서 평균 계산하기

Average.v는 #(parameter W = 8)로 폭을 받는 모듈이며 모든 포트 폭을 W로 표현합니다. parameter를 선언하고 재정의하는 방법은 인스턴스·계층·parameter 장에서 다루며, 여기서는 "같은 모듈을 두 폭으로"라는 뜻으로만 읽으면 됩니다. 이 모듈은 같은 두 입력의 평균을 두 가지 방법으로 계산하고 합도 따로 출력합니다.

출력계산덧셈 폭
avg_short(a + b) >> 1W
avg_full({1'b0, a} + b) >> 1W+1
totala + b를 W+1비트 출력에 대입W+1

avg_short는 W비트로 더한 뒤 shift합니다. avg_full은 한 피연산자 앞에 1'b0을 붙여 덧셈 전체를 W+1비트로 넓힙니다. total은 W+1비트 출력에 대입되므로 대상이 덧셈을 넓힙니다.

Average.v
// 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;
endmodule

Tb.v는 인스턴스 두 개를 배치합니다. average #(.W(8)) wide는 기본값을 그대로 쓰고, average #(.W(4)) narrow는 W를 4로 재정의합니다. 따라서 narrow의 입력은 4비트, total은 5비트입니다. 두 인스턴스에 합이 폭 안에 들어가는 입력 쌍을 먼저 넣고, 합이 폭을 넘는 입력 쌍을 다음에 넣습니다.

Tb.v
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비트가 필요합니다.

  1. avg_short는 22일까요, 150일까요?
  2. avg_full과 total은 각각 무엇일까요?
  3. 4비트 인스턴스의 두 번째 쌍 12 + 10에서 short와 full은 각각 무엇일까요?

확인 문제

8비트 인스턴스에 200과 100을 넣었을 때 short는 무엇을 출력합니까?

답

확인 문제

4비트 인스턴스에 12와 10을 넣었을 때 full은 무엇을 출력합니까?

결과 확인. 실행을 누르고 W8 두 줄과 W4 두 줄을 답과 비교하세요.

출력 줄확인할 내용
두 W8, W4 첫 줄합이 폭 안에 들어가므로 short와 full이 같습니다
W8 200,100short는 300을 8비트로 절단한 44를 shift한 22, full은 9비트 덧셈을 유지한 150, total은 300입니다
W4 12,1022가 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. 확인 문제

  1. nibble의 한 비트만 x인데 nibble + 4'd1의 네 비트가 모두 x인 이유는 무엇일까요?

    정답 보기

    Verilog는 피연산자에 x나 z가 하나라도 있으면 덧셈 결과 전체를 x로 만듭니다. 비트별 회로 분석으로는 값을 정할 수 있는 비트도 미지값으로 처리하는 보수적인 시뮬레이션 규칙입니다. 최하위 결과 비트까지 x인 이유를 자리올림 전파만으로 설명할 수는 없습니다.

  2. average #(.W(4)) narrow에서 total의 폭과 avg_short를 구동하는 덧셈의 폭은 각각 얼마일까요?

    정답 보기

    total은 [W:0]이므로 5비트입니다. avg_short의 덧셈은 피연산자와 대상이 모두 4비트이므로 4비트로 계산됩니다.

  3. {1'b0, a} + b는 자리올림을 보존하고 {a + b}는 보존하지 못하는 이유는 무엇일까요?

    정답 보기

    {1'b0, a}는 W+1비트 피연산자이므로 덧셈 전체가 W+1비트로 계산됩니다. {a + b}는 중괄호 안의 항목이라 자체 결정 폭인 W비트로 계산되고, 자리올림 비트가 만들어지지 않습니다.

확인 지점

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

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

참고 자료

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