8087 浮點加減法拆解:1648 條微碼裡的對齊、正規化與特殊值
Ken Shirriff(righto.com) · 2026 年 10 月
Intel 8087 沒有為加法和減法各做一條硬體路徑,而是讓兩者共用同一段微碼,靠比較指數、對調運算元、用符號 XOR 決定「實際該加還是該減」來收斂成兩條無號整數路徑。這取代了 8086 用軟體模擬浮點的做法,Intel 手冊宣稱典型運算約快上「almost a factor of 100」。
Ken Shirriff 在 2026 年 10 月的文章中,對照晶片照片與微碼清單,逐步走完這條路徑:整個微碼 ROM 位於晶片中央,共 1648 條微指令,加減法只佔其中一小段。
原本的問題
8086 沒有浮點硬體,一次加法要靠軟體處理對齊、進位、正規化與捨入,每一步都是整數指令。8087 要把這些步驟壓進硬體,又要同時處理零、無限大、NaN、非正規數等特殊值,不能讓每種組合各自長出一條路徑。
內部格式是 80 位元的「temporary real」:1 位元符號、15 位元指數、64 位元有效位數,指數偏移量為 16383。暫存器堆疊共 8 個,每個帶有零、有效、特殊、空四種標記之一。
資料路徑
加減法用到三個暫存器:tmpA 與 tmpB 是 80 位元並帶兩個標記位元;tmpC 的有效位數為 68 位元,多出的位元用來放溢位、guard、round 與 sticky,沒有符號、指數與標記。
- 加法器:69 位元整數加法器,一次加法需要兩條微指令,第二個運算元固定來自
B暫存器。 - 移位器:68 位元寬,可往任一方向移任意位數。
- 前導零偵測:不用單一 64 位元偵測器,而是兩個 8 輸入偵測器。第一輪移掉整個為零的位元組,第二輪再處理剩下的 0 到 7 個零位元,輸出為 3 位元計數。
核心流程
共用微碼從 #0001 開始,先把運算元載入 tmpA 與 tmpB。若為 0±0 直接處理;若任一運算元是空、無限大、NaN 或非正規數,就交給 SPECIAL_TMPS。
正常情況下,先把兩個指數相減。結果為負就對調運算元並翻轉符號 latch,保證 tmpB 這一側永遠是較小的數。接著較小者的有效位數右移「指數差」位(CREATE_DENORM,位於 #0247),移位量經由 sum 暫存器傳入。
對齊後,微碼把兩個符號與運算類型做 XOR,決定實際上是無號加法還是無號減法:
| 路徑 | 位址 | 做法 |
|---|---|---|
| 無號加法 | #0269 | 有效位數相加;若最高位進位,右移一位並讓 expAdj 加一(#0271) |
| 無號減法 | #0251 | 相減;結果為負則取負並翻轉符號(#0253 至 #0255);結果為零則檢查捨入位元,決定 +0 或 -0(#0256、#0261 至 #0268);否則先左移掉整個零位元組,再移掉剩餘零位元,總位移量存入 tmpB 的指數欄(#0282 至 #0288) |
之後 ADJUST_PRECISION(#0290)依需要捨入到 32 或 64 位元,捨入可能改變指數。最終指數由 tmpA 指數、tmpB 的指數調整量、expAdj 與常數 -1 合成,存回 tmpA 並載入指數轉換器;若超出範圍則呼叫 NONNORMAL_RESULT。
一個小細節:expAdj
expAdj 只有 2 位元,用來追蹤那一次一位元的移動,初值為 1,這樣它才有空間往下減。這種「為了讓計數器不下溢而偏移起點」的做法,和指數欄位用偏移量表示負值是同一個思路。
影響範圍
這篇對寫浮點模擬器、FPGA 重現 8087、或要精確比對 x87 行為的人最有用:微碼清單給出了每一條特殊值路徑的來源。文章列出八種會產生 NaN 的情況,例如 0÷0、∞×0、∞÷∞,以及仿射模式下的 ∞-∞;也列出八種產生無限大的路徑。
要檢查的是模式差異:投影模式的無限大沒有符號,仿射模式有符號。加減法會保留無限大運算元的符號,乘除則可能翻轉。另外,文章指出 80387 移除了 unnormal 與 pseudo-zero 這兩種格式,所以以 8087 為基準的測試向量不能直接拿去驗 387 之後的實作。
想自己對照微碼的人,作者指向社群專案 a-mcego/granite 的 8087mc 工具。文章未給出週期數或精確時序,只給出微指令條數:共用常式 #0001 至 #0064 共 64 步(含 18 個連續條件分支),加減法子程式 #0232 至 #0298 共 67 步。
原始來源:righto.com 原文、Intel 8086 Family User's Manual、granite 8087mc