43 / 52 · 개념
고정소수점·반올림·포화
W,F 형식으로 수치 범위를 정하고 guard·sticky 기반 짝수 반올림과 반올림 이후 포화를 설계합니다.
내용은 무료로 볼 수 있습니다. 수강 신청하면 학습 기록을 저장할 수 있어요.
선수 지식: signed 덧셈·곱셈의 비트폭, 산술 오른쪽 시프트, overflow를 이해해야 합니다.
비트열에 소수점의 위치를 계약하기
고정소수점은 정수 X를 로 읽는 표현입니다. 하드웨어에 소수점 비트가 저장되는 것은 아닙니다. 이 강의는 signed 전체 폭 W에 부호 비트를 포함하고 소수 비트 수를 F라고 정의합니다. Q 표기는 문헌마다 정수부에 부호 비트를 포함하는지가 달라서, 인터페이스에 W,F를 함께 쓰는 편이 명확합니다.
W=8,F=4라면 간격은 1/16이고 범위는 -8~7.9375입니다. 0x18은 정수 24가 아니라 이 계약에서 1.5입니다. 같은 0x18을 F=3으로 해석하면 3이 됩니다. 선을 연결했다고 수치 의미까지 맞는 것은 아니므로 각 포트의 scale을 문서화해야 합니다.
덧셈은 소수점 위치를 먼저 맞춰야 합니다. 서로 F가 다르면 작은 F 쪽을 충분히 넓힌 뒤 왼쪽으로 이동하거나, 큰 F 쪽을 정해진 반올림 방식으로 줄입니다. 같은 W,F의 두 수를 정확히 더하려면 W+1비트와 같은 F가 필요합니다. 곱셈에서는 raw 정수를 곱하고 소수 비트 수를 더합니다.
8비트 F=4 두 수의 완전한 곱은 signed 16비트 F=8입니다. 다시 8비트 F=4로 출력하려면 소수부 4비트를 줄인 뒤 signed 8비트 범위를 확인해야 합니다. 곱을 먼저 8비트로 잘라 놓으면 후속 포화 회로가 원래 크기를 알 수 없습니다.
버림과 반올림의 차이
d비트를 버리는 산술 오른쪽 시프트는 입니다. 음수도 아래쪽으로 내려갑니다. 예를 들어 라면 정확한 몫은 -1.5지만 q는 -2입니다. 이는 0 방향 버림과 다릅니다. 양수에서 맞던 “그냥 상위 비트만 선택”이 음수에도 같은 오차 성질을 가진다고 가정하면 안 됩니다.
가장 가까운 값으로 반올림하되 정확히 중간이면 짝수 정수를 택하는 round-to-nearest, ties-to-even을 설계해 봅시다. 버리는 영역의 최상위 비트를 guard g, 나머지 비트의 OR를 sticky t, 남기는 q의 최하위 비트를 l이라고 둡니다.
g=0이면 절반 미만이므로 그대로 둡니다. g=1,t=1이면 절반보다 크므로 올립니다. g=1,t=0이면 정확한 절반이어서 q가 홀수일 때만 1을 더합니다. 음수도 같은 규칙입니다. 바닥 몫 q와 나머지 를 쓰면 항상 이기 때문입니다.
예를 들어 -24를 16으로 나눈 -1.5는 q=-2가 짝수라 -2, -40을 나눈 -2.5는 q=-3이 홀수라 -2가 됩니다. “음수는 무조건 1을 뺀다” 같은 별도 규칙은 오히려 틀립니다. 포화되지 않는 경우 최종 수치 오차는 출력 LSB의 절반 이하입니다. ties-to-even이 모든 입력 분포의 평균 오차를 반드시 0으로 만드는 것은 아니지만, tie를 항상 한쪽으로 보내는 편향을 줄입니다.
반올림을 끝낸 뒤 포화하기
포화는 최댓값보다 크면 최댓값, 최솟값보다 작으면 최솟값으로 제한합니다. wrap과 달리 큰 양수가 음수로 뒤집히지 않습니다. 다만 이미 좁혀서 wrap된 값에는 정확한 포화를 적용할 수 없습니다.
아래는 signed 16비트 F=8 값을 signed 8비트 F=4로 바꾸는 완전한 조합 모듈입니다. d=4로 고정했으므로 guard는 p[3], sticky는 p[2:0]입니다. 반올림 증가와 범위 비교를 17비트 signed 공간에서 수행한 뒤 마지막에만 8비트를 취합니다.
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
endmoduleP=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일 때 floor 몫 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 값에서 끝내야 합니다.