Chapter 12

F·D·Q·Zfh: 부동소수점

RISC-V의 부동소수점은 새로 발명한 것이 아니다. IEEE 754-2008 표준을 그대로 따르고, 그 위에 레지스터 파일 f0–f31, 상태 레지스터 fcsr, 그리고 몇 가지 RISC-V다운 결정을 얹었다. 예외가 생겨도 트랩하지 않고 플래그만 세우며, NaN은 언제나 하나의 정해진 모양(정규 NaN)으로 만들고, 좁은 값을 넓은 레지스터에 넣을 때는 위쪽을 1로 채우는 NaN 박싱을 쓴다. 이 장에서는 비트 하나하나를 직접 눌러 보며 F·D·Q·Zfh와 BF16, Zfa, Zfinx까지 정리한다.

IEEE 754 형식: 부호, 지수, 가수

IEEE 754 이진 부동소수점 수는 세 부분으로 이루어진다. 부호 1비트, 치우침(bias)을 더해 저장하는 지수, 그리고 맨 앞의 1을 생략하고 저장하는 가수(fraction)다. 정규수의 값은 (−1)^s × 1.f × 2^(E − bias)다. 지수 비트가 모두 0이면 0 또는 비정규수(subnormal), 모두 1이면 무한대 또는 NaN이다. RISC-V의 다섯 가지 형식은 이 틀의 폭만 다르다.

부호지수 (bias = 2^(e−1) − 1)가수 (맨 앞 1은 생략) binary16Zfh · Zfhmin 510 16비트 · 유효 11비트 · 최댓값 65504 bfloat16Zfbfmin (BF16) 87 16비트 · 유효 8비트 · 지수 범위는 binary32와 같음 binary32F (단정도) 823 32비트 · 유효 24비트 · bias 127 binary64D (배정도) 1152 64비트 · 유효 53비트 · bias 1023 binary128Q (4배 정도) 15112 · 유효 113비트 · bias 16383 가로 길이는 비트 수에 비례 (부호 칸만 보기 쉽게 넓혔다)
그림 12-1. RISC-V 부동소수점 형식. 명령어의 2비트 fmt 필드는 S = 00, D = 01, H = 10, Q = 11로 형식을 고른다. bfloat16은 binary32의 위쪽 16비트를 잘라 낸 모양이라 지수 범위는 같고 정밀도만 낮다.
SIMULATOR

IEEE 754 비트 편집기

부호지수가수
형식
10진수 입력 (RNE로 반올림)
미리 정한 값
비트 (16진수)—
값—
FCLASS 결과—
지수—
“0.1”을 binary32와 binary16에서 비교해 보자. 0.1은 2진수로 정확히 쓸 수 없어서 형식마다 가장 가까운 값이 다르다. “가장 작은 비정규수”에서 지수 비트를 하나 켜면 정규수가 되고, “정규 NaN”의 가수 최상위 비트를 끄면(다른 비트 하나는 켜 둔 채) 신호 NaN으로 바뀌어 FCLASS 비트가 9에서 8로 옮겨 간다.

f 레지스터와 fcsr

F 확장은 32개의 부동소수점 레지스터 f0–f31을 더한다. 정수 레지스터와 달리 f0도 평범한 레지스터다(늘 0이 아니다). 레지스터 폭을 FLEN이라 부르는데, F만 있으면 32, D가 있으면 64, Q가 있으면 128이다. 정수와 부동소수점 레지스터를 따로 둔 이유를 규격은 이렇게 설명한다. 같은 명령 폭으로 더 많은 레지스터를 쓸 수 있고, 슈퍼스칼라에서 레지스터 포트를 나눠 달기 쉽고, 부동소수점 유닛을 분리해 설계하거나 내부 재부호화(recoding)를 쓰기 쉽다. F 확장은 CSR 접근을 위해 Zicsr에 의존한다.

3187543210 예약 (다른 확장용)없으면 쓰기 무시, 읽으면 0 frm동적 반올림 모드 NV DZ OF UF NX fflags · CSR 0x001 frflags / fsflags frm · CSR 0x002 frrm / fsrm fcsr · CSR 0x003 = frm + fflags frcsr / fscsr NV 잘못된 연산 (Invalid) DZ 0으로 나누기 (Divide by Zero) OF 넘침 (Overflow) UF 아래 넘침 (Underflow) NX 부정확 (Inexact) 플래그는 “쌓이기만” 한다(sticky). 소프트웨어가 지울 때까지 1로 남는다.
그림 12-2. fcsr은 32비트 읽기/쓰기 CSR이다. 같은 비트를 세 개의 CSR 주소로 나눠 접근할 수 있어서, 플래그만(0x001) 또는 반올림 모드만(0x002) 바꾸는 코드가 짧아진다. 괄호 아래는 어셈블러 의사 명령이다.
CSR주소내용의사 명령
fflags0x001누적 예외 플래그 (fcsr[4:0])frflags rd / fsflags rd, rs1
frm0x002동적 반올림 모드 (fcsr[7:5])frrm rd / fsrm rd, rs1
fcsr0x003frm + fflagsfrcsr rd / fscsr rd, rs1
운영체제는 f 레지스터를 언제 저장할까

f 레지스터 32개와 fcsr은 문맥 교환 때 저장해야 할 상태가 꽤 크다. 특권 규격의 mstatus.FS 필드가 부동소수점 상태가 Off·Initial·Clean·Dirty 중 무엇인지 추적해, 쓰지 않은 프로세스의 f 레지스터 저장을 건너뛸 수 있게 한다(17장).

다섯 반올림 모드

정확한 결과가 표현 가능한 두 값 사이에 떨어지면 하나를 골라야 한다. 반올림을 하는 명령에는 3비트 rm 필드가 있어서 명령마다 정적으로 모드를 정할 수 있고, rm = 111(DYN)이면 fcsr의 frm에 든 동적 모드를 쓴다.

rm이름뜻
000RNE가장 가까운 값, 정확히 중간이면 짝수(가수 최하위 비트 0) 쪽 — IEEE 기본값
001RTZ0 쪽으로 (버림)
010RDN−∞ 쪽으로 (내림)
011RUP+∞ 쪽으로 (올림)
100RMM가장 가까운 값, 정확히 중간이면 크기가 큰 쪽
101, 110—예약
111DYN명령의 rm에서는 “frm을 써라”. frm 레지스터에 이 값을 넣는 것은 예약
(a) 양수아래 쪽에 가까움 x RNE RTZ RDN RMM RUP (b) 음수0 쪽(오른쪽)에 가까움 x RDN RNE RTZ RUP RMM 0 (c) 정확히 중간양수, 왼쪽이 짝수 …0 (짝수)…1 (홀수) x RNE RTZ RDN RUP RMM
그림 12-3. 파란 점은 이웃한 두 표현 가능한 값, 금색 선은 정확한 결과 x, 가운데 점선은 중간점이다. RDN·RUP는 부호에 따라 “크기”를 줄이기도 늘리기도 한다. RNE와 RMM은 중간이 아닐 때는 같고, 정확히 중간일 때만 갈린다.
SIMULATOR

반올림 모드 시각화 (binary32)

10진수 값
예제
16777217(= 2^24 + 1)은 binary32의 두 이웃 16777216과 16777218의 정확히 가운데다. RNE는 가수가 짝수인 16777216을, RMM은 크기가 큰 16777218을 고른다. 16777219에서는 짝수 쪽이 위(16777220)라서 RNE도 올라간다. 3.5e38에서는 RTZ가 무한대 대신 가장 큰 유한값을 고르는 것도 확인하자.

명령어 한눈에 보기

F 확장의 명령은 정수 쪽과 거의 같은 형식을 쓴다. load/store는 I형·S형과 같은 “기준 + 12비트 오프셋” 주소 방식이고, 연산은 OP-FP 주 opcode(1010011)의 R형이다. 예외는 세 피연산자를 읽는 FMA로, 이를 위해 R4 형식이 새로 만들어졌다.

무리명령 (.S 기준, .D/.Q/.H 도 같은 꼴)요점
load/storeflw · fsw (LOAD-FP 0000111 / STORE-FP 0100111)비트를 바꾸지 않는다(비정규 NaN 페이로드도 보존). width: H 001, W 010, D 011, Q 100
산술fadd · fsub · fmul · fdiv · fsqrtrm 필드로 반올림
융합 곱셈-덧셈fmadd · fmsub · fnmsub · fnmaddR4 형식, rs3, 반올림 1번
최소/최대fmin · fmax−0 < +0. NaN 하나면 다른 쪽, 둘 다 NaN이면 정규 NaN
부호 주입fsgnj · fsgnjn · fsgnjx의사 명령 fmv.s · fneg.s · fabs.s. 플래그 없음, NaN 정규화 없음
비교feq · flt · fle결과는 정수 레지스터에 0/1. flt/fle는 NaN이면 NV, feq는 sNaN일 때만 NV
변환fcvt.w.s · fcvt.wu.s · fcvt.s.w · fcvt.s.wu (RV64: .l/.lu)범위 밖이면 가장 가까운 값으로 자르고 NV
비트 이동fmv.x.w · fmv.w.x해석 없이 32비트 그대로. 옛 이름 fmv.x.s / fmv.s.x
분류fclass.s정수 rd에 10비트 마스크, 정확히 한 비트만 1
3127262524201915141211760 rs3 fmt rs2 rs1 rm rd opcode 1000011 FMADD 1000111 FMSUB 1001011 FNMSUB 1001111 FNMADD R4 형식은 rs3 자리를 만들려고 funct7을 rs3 + fmt로 나눴다. FMA 네 개가 주 opcode 네 개를 통째로 쓴다. rs1 × rs2 rs3 정확한 곱 + rs3곱셈 결과는 반올림 안 함 반올림 1번rm / frm rd fmadd = (rs1×rs2)+rs3 · fmsub = (rs1×rs2)−rs3 · fnmsub = −(rs1×rs2)+rs3 · fnmadd = −(rs1×rs2)−rs3
그림 12-4. R4 형식과 융합 곱셈-덧셈(FMA). 곱과 합을 따로 반올림하면 오차가 두 번 생기지만, FMA는 정확한 곱에 rs3를 더한 뒤 한 번만 반올림한다. 이름이 헷갈리는 FNMSUB·FNMADD는 MIPS-IV에서 물려받은 것으로, RISC-V는 합이 아니라 곱의 부호를 바꾼다(x86·Arm의 FMA와 같은 동작).
FCLASS rd 비트뜻비트뜻
0−∞5양의 비정규수
1음의 정규수6양의 정규수
2음의 비정규수7+∞
3−08신호 NaN (sNaN)
4+09조용한 NaN (qNaN)
왜 f 레지스터에서 정수 레지스터로 바로 쓰는가

비교(feq/flt/fle), 분류(fclass), 정수 변환(fcvt.w.s)은 결과를 곧장 정수 레지스터에 쓴다. 규격은 이것을 의도된 설계로 설명한다. 구현이 f 레지스터 안에서 값을 내부 형식으로 재부호화해 둘 수 있게 하려고 f 레지스터에 정수를 두지 않으며, 덕분에 레지스터 사이 이동 명령도 줄어든다. 플래그 대신 정수 비교 결과를 써서 분기하는 것도 같은 철학이다.

예외는 트랩이 아니라 플래그로, NaN은 정규 NaN으로

IEEE 754는 예외 상황(0으로 나누기 등)에서 트랩할지 기본값을 돌려줄지를 구현에 맡긴다. RISC-V는 트랩하지 않는다. 표준이 정한 기본 결과(무한대, NaN 등)를 돌려주고 fflags의 해당 비트만 세운다. 플래그를 직접 보고 분기하는 명령도 일부러 넣지 않았다. 필요하면 소프트웨어가 frflags로 읽어 확인한다.

플래그서는 경우 (예)
NV0/0, ∞−∞, √(음수), sNaN 입력, 정수 변환 범위 밖·NaN, flt/fle에 NaN, ∞×0 (FMA에서는 덧셈항이 qNaN이어도)
DZ유한한 0 아닌 수 ÷ 0 → ±∞
OF반올림한 결과의 크기가 가장 큰 유한값을 넘음 (항상 NX도 함께)
UF결과가 아주 작고(tiny) 부정확함. RISC-V는 tininess를 반올림 뒤에 판단
NX반올림한 결과가 정확한 값과 다름
규격 원문에서
“Except when otherwise stated, if the result of a floating-point operation is NaN, it is the canonical NaN. The canonical NaN has a positive sign and all significand bits clear except the MSB, a.k.a. the quiet bit. For single-precision floating-point, this corresponds to the pattern 0x7fc00000.”

"F" Extension, NaN Generation and Propagation — 표준이 권장하는 NaN 페이로드 전파는 하드웨어 비용 때문에 하지 않는다. 표준에서도 선택 사항이라 이식성 있는 코드가 기댈 수 없기 때문이다.

정수 변환이 범위를 벗어나면 결과는 가장 가까운 표현 가능한 정수로 잘리고 NV가 선다. NaN은 “가장 큰 양수”로 간다는 점이 특이하다.

FCVT.W.SFCVT.WU.SFCVT.L.SFCVT.LU.S
유효 입력 최소 (반올림 후)−2310−2630
유효 입력 최대 (반올림 후)231−1232−1263−1264−1
범위 밖 음수, −∞−2310−2630
범위 밖 양수, +∞, NaN231−1232−1263−1264−1
SIMULATOR

fcsr 플래그 놀이터

76543210
frmfrmfrmNVDZOFUFNX
명령 버튼을 눌러 보자. 결과와 함께, 그 명령이 세운 플래그가 fflags에 누적된다.
frm (동적 반올림 모드, rm = 111인 명령이 사용)
플래그
마지막 결과—
이번에 선 플래그—
frflags (0x001)0x00
frcsr (0x003)0x00
“1.0 / 3.0”을 frm을 바꿔 가며 실행해 결과의 마지막 비트가 어떻게 달라지는지 보자. “최댓값 × 2”는 RNE에서 +∞, RTZ에서 가장 큰 유한값이 되지만 OF·NX는 둘 다 선다. flt와 feq에 같은 qNaN을 넣으면 앞의 것만 NV를 세운다. 플래그는 지우기 전까지 남아 있다(sticky).
FMIN/FMAX는 v2.2에서 바뀌었다

F v2.2부터 FMIN.S·FMAX.S는 IEEE 754-2008의 minNum/maxNum이 아니라 당시 제안된 754-201x의 minimumNumber/maximumNumber를 따른다. 둘은 신호 NaN 처리에서 다르다. 지금 규칙은 “NaN이 하나면 다른 쪽 값, 둘 다 NaN이면 정규 NaN, sNaN 입력은 결과가 NaN이 아니어도 NV”다. NaN이 하나라도 있으면 NaN을 내는 IEEE 754-2019 minimum/maximum은 Zfa의 FMINM/FMAXM이 제공한다.

D 확장과 NaN 박싱

D 확장은 f 레지스터를 64비트(FLEN = 64)로 넓히고 배정도 명령을 더한다. 이제 한 레지스터에 단정도 값과 배정도 값이 모두 들어갈 수 있다. 그렇다면 32비트 값을 64비트 레지스터에 넣을 때 위쪽 32비트는 무엇으로 채울까? RISC-V의 답은 NaN 박싱(NaN-boxing)이다. 위쪽 비트를 모두 1로 채운다.

FLEN = 64단정도 1.0 1111 … 1111 (32비트 모두 1) 0x3f800000 (1.0f) 63310 FLEN = 64반정도 (Zfh) 위쪽 48비트 모두 1 16비트 값 FLEN = 128단정도 (Q) 위쪽 64비트 모두 1 32비트 1 단정도 값 넓은 형식으로 읽으면: 부호 1, 지수 모두 1, 가수 최상위 1 → 음의 조용한 NaN
그림 12-5. NaN 박싱. 좁은 값은 레지스터의 아래쪽에 두고 위쪽을 모두 1로 채운다. Q 확장에서는 단정도가 배정도 안에, 배정도가 4배 정도 안에 재귀적으로 박싱된다. 넓은 명령으로 읽으면 늘 NaN이 보이므로, 잘못 쓴 값이 조용히 엉뚱한 숫자가 되지 않고 NaN으로 드러난다.
규격 원문에서
“The upper bits of a valid NaN-boxed value must be all 1s. Valid NaN-boxed n-bit values therefore appear as negative quiet NaNs (qNaNs) when viewed as any wider m-bit value, n < m ≤ FLEN.”

"D" Extension, NaN Boxing of Narrower Values — 예전 규격은 이 경우를 정하지 않았다. 지금 규칙은 구현마다 다른 동작을 없애면서도, 값을 내부 형식으로 재부호화하는 구현과 그렇지 않은 구현을 모두 수용한다.

ANIMATION

NaN 박싱 따라가기 (FLEN = 64)

시나리오
진행
f1—
f2—
f2 해석—
첫 시나리오는 정상 경로다. 두 번째에서는 배정도 1.0을 단정도 명령이 읽는다. 위쪽 32비트가 모두 1이 아니므로 입력이 정규 NaN으로 바뀐다(조용한 NaN이라 NV는 서지 않는다). 세 번째에서는 박싱된 단정도 1.0을 배정도 명령이 읽어 음의 qNaN으로 보고, 결과는 배정도 정규 NaN 0x7ff8000000000000이 된다.

D 확장의 나머지는 F와 거의 대칭이다. fcvt.s.d는 rm에 따라 반올림하고, fcvt.d.s는 언제나 정확하다. fcvt.d.w[u]도 늘 정확하다. 정수 레지스터와 64비트 비트 패턴을 주고받는 fmv.x.d·fmv.d.x는 XLEN ≥ 64(RV64)에서만 있다. RV32에서는 메모리를 거치거나, Zfa의 fmvh.x.d·fmvp.d.x를 쓴다. FLD·FSD는 주소가 자연 정렬되어 있고 XLEN ≥ 64일 때만 원자적으로 실행된다고 보장된다.

Q, Zfh/Zfhmin, 그리고 BF16

Q(v2.2)는 128비트 4배 정도를 더한다(FLEN = 128, D에 의존). 새 load/store flq/fsq는 width 100을 쓰고, RV32·RV64 어디에도 fmv.x.q/fmv.q.x는 없어서 정수 레지스터로 옮기려면 메모리를 거친다. Zfh(v1.0)는 16비트 반정도 연산 전부를, Zfhmin은 그중 저장과 변환만(flh, fsh, fmv.x.h, fmv.h.x, fcvt.s.h, fcvt.h.s, D가 있으면 fcvt.d.h, fcvt.h.d, Q가 있으면 fcvt.q.h, fcvt.h.q) 담는다. 둘 다 F에 의존한다. Zfhmin 소프트웨어는 반정도를 “저장 형식”으로만 쓰고 계산은 단정도로 하는 경우를 겨냥한다.

binary32 지수 8가수 상위 7가수 하위 16 (bfloat16에선 버림) fcvt.bf16.s (반올림) · fcvt.s.bf16 (정확) bfloat16 binary16 지수 5가수 10 형식최댓값정밀도 binary32≈3.40e3824비트 ≈7.2자리 bfloat16≈3.39e388비트 ≈2.4자리 binary166550411비트 ≈3.3자리 bfloat16: 범위는 넓고 정밀도는 낮다 → 기계 학습의 가중치·기울기처럼 크기 변화가 큰 값에 맞는다. binary16: 정밀도가 조금 높지만 범위가 좁다 → 그래픽, 신호 처리, 저장 형식.
그림 12-6. bfloat16은 binary32의 위쪽 16비트와 같은 배치다. 그래서 binary32 → bfloat16은 아래 16비트를 반올림해 버리는 것이고, 반대 방향은 0을 붙이기만 하면 되는 정확한 변환이다.

BF16(v1.0) 규격은 세 확장으로 나뉜다. 모두 F에 의존하며, 이 규격의 BF16 명령은 비정규수를 완전히 지원한다.

확장명령의존
Zfbfminfcvt.bf16.s, fcvt.s.bf16 + Zfh의 flh, fsh, fmv.x.h, fmv.h.x (6개)F
Zvfbfminvfncvtbf16.f.f.w, vfwcvtbf16.f.f.v (벡터 변환)Zve32f (또는 V)
Zvfbfwmavfwmaccbf16 (BF16 × BF16을 FP32에 누적하는 넓히기 곱셈-덧셈)Zvfbfmin, Zfbfmin

BF16 값도 f 레지스터에서는 반정도와 똑같이 NaN 박싱된다. 변환은 BF16 ↔ FP32만 제공한다. 규격은 이 확장들이 “BF16을 FP32의 정밀도를 줄인 형태로 쓰는” 용도를 겨냥한다고 밝히며, 다른 형식과의 변환은 FP32를 거쳐 흉내 낼 수 있다고 설명한다. 벡터 쪽 이야기는 15장에서 이어진다.

Zfa: 자주 쓰는 연산을 한 명령으로

Zfa(v1.0)는 F에 의존하며, 라이브러리와 컴파일러가 여러 명령으로 흉내 내던 연산을 더한다. 가장 눈에 띄는 것은 fli(load immediate)다. 상수 하나를 쓰려고 메모리에서 읽거나 정수 레지스터를 거칠 필요 없이, rs1 필드의 5비트로 32개 상수 중 하나를 고른다.

그림 12-7. fli.s 상수표(rs1 = 0–31). 1번은 “가장 작은 양의 정규수”로 형식마다 값이 다르다(binary32: 2−126). 30·31번은 +∞와 정규 NaN이다. 8–22번은 규칙적인 무늬를 이루고, 어떤 항목도 가수의 위쪽 두 비트 밖을 쓰지 않는다. fli.h에서는 216을 표현할 수 없어 29번이 +∞가 된다. fli는 어떤 플래그도 세우지 않는다.
명령하는 일인코딩 단서
fli.{s|d|h|q}32개 상수 중 하나를 rd에FMV.W.X 꼴, rs2 = 1
fminm · fmaxmIEEE 754-2019 minimum/maximum: NaN이 하나라도 있으면 정규 NaNFMIN/FMAX와 같고 비트 13 = 1
fround · froundnxrm에 따라 정수로 반올림(결과는 부동소수점). nx 버전은 값이 바뀌면 NXFCVT.S.D 꼴, rs2 = 4 / 5
fcvtmod.w.d늘 RTZ, 결과의 아래 32비트를 취해(모듈러) 부호 확장. ±∞·NaN → 0. JavaScript의 정수 변환 가속용FCVT.W.D 꼴, rs2 = 8, rm = 001
fleq · fltqFLE/FLT와 같지만 조용한 NaN에는 NV를 세우지 않음FLE/FLT와 같고 비트 14 = 1
fmvh.x.d · fmvp.d.xRV32+D: 배정도의 위쪽 32비트 읽기 / 정수 레지스터 두 개로 배정도 만들기funct7 1110001 / 1011001
fmvh.x.q · fmvp.q.xRV64+Q: 같은 일을 128비트에 대해funct7 1110011 / 1011011

Zfinx: f 레지스터 없는 부동소수점

작은 마이크로컨트롤러에게 32개짜리 레지스터 파일을 하나 더 두는 것은 큰 비용이다. Zfinx(v1.0, “z-f-in-x”)는 F의 연산 명령을 그대로 두되, f 레지스터를 읽고 쓰던 자리에서 같은 번호의 x 레지스터를 쓴다. f 레지스터가 없으니 전송 명령 flw, fsw, fmv.w.x, fmv.x.w, c.flw[sp], c.fsw[sp]는 빠진다. 메모리와는 정수 load/store로, 레지스터 사이는 정수 연산이나 부호 주입 명령으로 옮긴다. 문맥 교환 비용도 줄어든다.

F 확장 x0–x31정수 f0–f31부동소수점 (FLEN) fadd.s f1, f2, f3 fmv fcsr misa.F = 1 · mstatus.FS 로 상태 추적 Zfinx (+ Zdinx · Zhinx) x0–x31 하나로 정수와 부동소수점 모두 fadd.s a1, a2, a3 # x11 ← x12 + x13 RV32 Zdinx: 배정도는 짝수 쌍 x15 [63:32] x14 [31:0] fcsr (그대로) misa.F·D·Q = 0 · mstatus.FS = 0 고정 F를 가정한 소프트웨어와 Zfinx를 가정한 소프트웨어는 서로 호환되지 않는다
그림 12-8. F와 Zfinx. Zfinx에서는 부동소수점 값이 정수 레지스터에 산다. 그래서 NaN 박싱 대신, w비트 결과를 쓸 때 위쪽 비트를 부호 비트로 채운다(부호 확장). 이는 32비트 부동소수점 값을 x 레지스터로 넘길 때 위쪽을 정의하지 않는 기존 RV64 호출 규약과도 맞는다.
확장내용빠지는 전송 명령
ZfinxF의 연산을 x 레지스터에서 (Zicsr 의존)FLW, FSW, FMV.W.X, FMV.X.W, C.FLW[SP], C.FSW[SP]
ZdinxD의 연산 (Zfinx 의존). RV32는 짝수 레지스터 쌍, 아래 번호가 하위 비트FLD, FSD, FMV.D.X, FMV.X.D, C.FLD[SP], C.FSD[SP]
ZhinxZfh의 연산 (Zfinx 의존)FLH, FSH, FMV.H.X, FMV.X.H
ZhinxminFCVT.S.H, FCVT.H.S (Zdinx가 있으면 FCVT.D.H, FCVT.H.D)—

RV32 Zdinx에서 배정도 결과를 x0에 쓰면 쓰기 전체가 무시되어 x1이 바뀌지 않고, x0을 배정도 피연산자로 쓰면 (x1을 읽지 않고) 전체가 0이다. 홀수 번호 레지스터를 배정도 피연산자로 쓰는 인코딩은 예약되어 있다.

핵심 정리

  1. RISC-V 부동소수점은 IEEE 754-2008을 따른다. F(v2.2)는 f0–f31(FLEN = 32)과 fcsr을, D·Q(v2.2)는 FLEN을 64·128로 넓힌다. fmt 필드는 S 00, D 01, H 10, Q 11.
  2. fcsr = frm[7:5] + fflags[4:0](NV DZ OF UF NX). CSR 주소는 fflags 0x001, frm 0x002, fcsr 0x003.
  3. rm: RNE 000, RTZ 001, RDN 010, RUP 011, RMM 100, 101·110 예약, 111 DYN(frm 사용).
  4. FMA 네 명령은 rs3를 가진 R4 형식을 쓰고 반올림을 한 번만 한다. 비교·분류·정수 변환은 결과를 정수 레지스터에 쓴다. FCLASS는 10비트 중 한 비트를 세운다.
  5. 예외는 트랩하지 않고 누적 플래그만 세운다. NaN 결과는 늘 정규 NaN(binary32: 0x7fc00000)이며 페이로드를 전파하지 않는다. tininess는 반올림 뒤에 판단한다.
  6. NaN 박싱: 좁은 값을 넣을 때 위쪽을 모두 1로 채우고, 좁은 연산은 박싱이 깨진 입력을 정규 NaN으로 취급한다.
  7. Zfh/Zfhmin은 반정도, BF16(Zfbfmin·Zvfbfmin·Zvfbfwma)은 bfloat16 변환과 벡터 누적, Zfa는 fli·fminm·fround·fcvtmod·fleq 등, Zfinx/Zdinx/Zhinx는 f 레지스터 없이 x 레지스터에서 부동소수점을 계산한다.

확인 퀴즈

fcsr의 frm 필드 위치와 frm CSR의 주소로 옳은 것은?

frm은 fcsr[7:5]이고 따로 0x002로 접근한다. fflags는 [4:0]·0x001, fcsr 전체는 0x003이다.

단정도 정규 NaN의 비트 패턴은?

부호 0, 지수 모두 1, 가수는 최상위(quiet) 비트만 1이다. 0x7f800000은 +∞다.

RV64D 하트에서 flw f1로 1.0f(0x3f800000)를 읽은 뒤 f1의 64비트 값은?

좁은 값을 f 레지스터에 넣는 전송 명령은 위쪽을 모두 1로 채운다(NaN 박싱).

16777217을 binary32로 변환할 때 RNE와 RMM의 결과로 옳은 것은?

정확히 중간이므로 RNE는 가수가 짝수인 16777216, RMM은 크기가 큰 16777218을 고른다.

RISC-V에서 1.0 / 0.0을 실행하면?

RISC-V는 부동소수점 예외로 트랩하지 않는다. IEEE 기본 결과 +∞와 함께 DZ 플래그만 선다.

FMADD.S가 FMUL.S + FADD.S 두 명령과 다른 점은?

FMA는 정확한 곱에 rs3를 더한 뒤 한 번만 반올림한다. 그래서 R4 형식(rs3 필드)이 필요하다.

feq.s와 flt.s에 같은 조용한 NaN을 넣었을 때 NV 플래그는?

flt/fle는 IEEE의 signaling 비교라 NaN이면 NV를 세우고, feq는 quiet 비교라 sNaN일 때만 세운다. 결과는 둘 다 0이다.

Zfinx에 대한 설명으로 틀린 것은?

Zfinx는 NaN 박싱 대신 위쪽 비트를 부호 비트로 채운다(부호 확장). 같은 레지스터에 정수도 살기 때문이다.

더 읽을거리