Altifigence Academy

53 / 63 · 개념

고정소수점·반올림·포화

W,F 형식으로 수치 범위를 정하고 guard·sticky 기반 짝수 반올림과 반올림 이후 포화를 설계합니다.

선수 지식: signed 덧셈·곱셈의 비트폭, 산술 오른쪽 시프트, overflow를 이해해야 합니다.

비트열을 해석할 소수점 위치 정하기

고정소수점고정소수점 소수점 위치를 미리 고정하고 정수 비트열에 일정한 배율을 적용하는 수 표현입니다. 폭·부호·소수 비트 수를 함께 정해야 합니다.은 정수 X를 x=X2−Fx=X2^{-F}로 읽는 표현입니다. 하드웨어에 소수점 비트가 저장되는 것은 아닙니다. 이 강의는 signed 전체 폭 W에 부호 비트를 포함하고 소수 비트 수를 F라고 정의합니다. Q 표기는 문헌마다 정수부에 부호 비트를 포함하는지가 달라서, 인터페이스에 W,F를 함께 쓰는 편이 명확합니다.

−2W−1−F≤x≤2W−1−F−2−F-2^{W-1-F}\le x\le 2^{W-1-F}-2^{-F}

W=8,F=4라면 간격은 1/16이고 범위는 -8~7.9375입니다. 이 형식에서 0x18은 정수 24가 아니라 1.5를 나타냅니다. 같은 0x18을 F=3으로 해석하면 3이 됩니다. 신호선을 연결해도 양쪽이 수를 같은 뜻으로 해석한다는 보장은 없으므로, 각 포트의 배율을 문서에 명시해야 합니다.

덧셈은 소수점 위치를 먼저 맞춰야 합니다. 서로 F가 다르면 작은 F 쪽을 충분히 넓힌 뒤 왼쪽으로 이동하거나, 큰 F 쪽을 정해진 반올림 방식으로 줄입니다. 같은 W,F의 두 수를 정확히 더하려면 W+1비트와 같은 F가 필요합니다. 곱셈에서는 소수점 배율을 적용하기 전의 정수(raw)를 곱하고 소수 비트 수를 더합니다.

P=XAXB,FP=FA+FBP=X_A X_B,\qquad F_P=F_A+F_B

8비트 F=4 두 수의 완전한 곱은 signed 16비트 F=8입니다. 다시 8비트 F=4로 출력하려면 소수부 4비트를 줄인 뒤 signed 8비트 범위를 확인해야 합니다. 곱을 먼저 8비트로 잘라 놓으면 후속 포화 회로가 원래 크기를 알 수 없습니다.

버림과 반올림의 차이

d비트를 버리는 산술 오른쪽 시프트는 q=⌊P/2d⌋q=\lfloor P/2^d\rfloor입니다. 음수도 더 작은 정수 쪽으로 버립니다. 예를 들어 P=−24,d=4P=-24,d=4라면 정확한 몫은 -1.5지만 q는 -2입니다. 이는 소수 부분을 0 쪽으로 버리는 방식과 다릅니다. 양수에서 맞던 “그냥 상위 비트만 선택”이 음수에도 같은 오차 성질을 가진다고 가정하면 안 됩니다.

가장 가까운 값으로 반올림하되 정확히 중간이면 짝수 정수를 택하는 round-to-nearest, ties-to-even을 설계해 봅시다. 버리는 영역의 최상위 비트를 guard g, 나머지 비트의 OR를 sticky t, 남기는 q의 최하위 비트를 l이라고 둡니다.

inc=g∧(t∨l),qrnd=q+inc\mathrm{inc}=g\land(t\lor l),\qquad q_{\mathrm{rnd}}=q+\mathrm{inc}

g=0이면 절반 미만이므로 그대로 둡니다. g=1,t=1이면 절반보다 크므로 올립니다. g=1,t=0이면 정확한 절반이어서 q가 홀수일 때만 1을 더합니다. 음수도 같은 규칙입니다. 내림한 몫 q와 나머지 r=P−q2dr=P-q2^d를 쓰면 항상 0≤r<2d0\le r<2^d이기 때문입니다.

예를 들어 -24를 16으로 나눈 -1.5는 q=-2가 짝수라 -2, -40을 나눈 -2.5는 q=-3이 홀수라 -2가 됩니다. “음수는 무조건 1을 뺀다” 같은 별도 규칙은 오히려 틀립니다. 포화되지 않는 경우 최종 수치 오차는 출력 LSBLSB 가장 낮은 자릿값을 갖는 비트입니다. 표기 방향과 인덱스 규칙을 함께 확인해야 합니다. 자세히 보기의 절반 이하입니다. ties-to-even이 모든 입력 분포의 평균 오차를 반드시 0으로 만드는 것은 아니지만, 정확히 중간인 값을 항상 한쪽으로 반올림할 때 생기는 편향은 줄입니다.

16비트 F=8의 raw P=2040을 8비트 F=4로 변환합니다. 127.5가 짝수 128로 반올림된 뒤 signed 8비트 최댓값 127로 제한됩니다. 마지막 저장 전까지 넓은 폭을 보존해야 합니다.

반올림을 먼저, 포화를 나중에
  1. 입력 P=2040: 16비트 · F=8
  2. 스케일 축소: 2040 / 16 = 127.5
  3. ties-to-even: 반올림 raw = 128
  4. 포화 후 저장: raw 127 → 7.9375

반올림을 끝낸 뒤 포화하기

포화는 최댓값보다 크면 최댓값, 최솟값보다 작으면 최솟값으로 제한합니다. wrap과 달리 큰 양수가 음수로 뒤집히지 않습니다. 다만 이미 좁혀서 wrap된 값에는 정확한 포화를 적용할 수 없습니다.

아래는 signed 16비트 F=8 값을 signed 8비트 F=4로 바꾸는 완전한 조합 모듈입니다. d=4로 고정했으므로 guard는 p[3], sticky는 p[2:0]입니다. 17비트 signed 값으로 반올림 증가량을 더하고 범위를 비교한 뒤, 마지막에만 8비트로 줄입니다.

SystemVerilog
module requantize (
  input  logic signed [15:0] p,
  output logic signed [7:0] y,
  output logic saturated
);
  logic signed [16:0] wide, q, rounded;
  logic inc;
  always_comb begin
    wide = {p[15], p};
    q = wide >>> 4;
    inc = p[3] & ((|p[2:0]) | q[0]);
    rounded = q + (inc ? 17'sd1 : 17'sd0);
    saturated = 1'b0;
    if (rounded > 17'sd127) begin
      y = 8'sh7f;
      saturated = 1'b1;
    end else if (rounded < -17'sd128) begin
      y = 8'sh80;
      saturated = 1'b1;
    end else begin
      y = rounded[7:0];
    end
  end
endmodule

P=2040이면 P/16=127.5이므로 짝수 128로 반올림된 후 127로 포화됩니다. 최종 값은 7.9375입니다. 반올림 전 q=127만 검사하면 이 초과를 놓칩니다. saturated는 수치가 범위 밖으로 나가 제한되었음을 뜻하며, 단지 하위 소수 비트를 잃었다는 뜻은 아닙니다.

범용 모듈로 바꿀 때 d=0은 버릴 비트가 없고 d=1은 sticky 영역이 없다는 점을 분리해야 합니다. 범위를 벗어난 part-select를 조건문 안에 감추는 것만으로는 elaboration 오류를 해결하지 못할 수 있습니다.

반올림과 포화에는 덧셈기·비교기·MUX멀티플렉서 선택 신호에 따라 여러 입력 중 하나를 출력으로 연결하는 조합회로입니다. 선택 비트와 입력 번호의 대응을 명세해야 합니다. 자세히 보기가 추가로 필요하고 지연도 늘어납니다. 두 처리 방식은 각각 선택할 수 있습니다. 이런 구분은 AMD 고정소수점 형식 문서에도 나타납니다. 여러 항을 누산할 때는 중간 폭을 유지하고 마지막에 한 번 변환할지 먼저 결정하세요. 매 단계 포화하면 덧셈의 결합법칙이 깨질 수 있고, 매 단계 반올림하면 작은 오차가 반복해서 쌓일 수 있습니다.

직접 생각해 보기

signed 16비트 F=8 입력 P를 signed 8비트 F=4로 변환합니다. P가 24,40,−24,−40,2040일 때 내림한 몫 q, guard/sticky, 반올림 정수, 최종 raw 출력 및 실제 값을 구하세요. 2040에서 포화 검사를 먼저 하면 어떤 문제가 생기나요?

해설 보기

모든 입력의 하위 4비트가 1000이므로 guard=1,sticky=0입니다. P=24는 q=1(홀수)→2, raw=2, 실제 값=0.125입니다. P=40은 q=2(짝수)→2, raw=2, 0.125입니다. P=−24는 q=−2(짝수)→−2, raw=−2, −0.125입니다. P=−40은 q=−3(홀수)→−2, raw=−2, −0.125입니다. P=2040은 q=127(홀수)→128이며 signed 8비트 최댓값을 넘으므로 raw=127, 실제 값=7.9375, saturated=1입니다. q=127만 먼저 검사하고 8비트에서 1을 더하면 0x80으로 wrap되어 −8을 출력할 수 있습니다. 충분한 비트폭의 signed 값으로 증가량을 더하고 범위 비교까지 마쳐야 합니다.

선택은 이 브라우저에만 적용됩니다. 언제든 푸터에서 변경할 수 있습니다.