固定小数点とDSP演算 ─ 飽和・スケーリング・MAC
なぜDSPは浮動小数点なしで信号処理を回せるのか。Q表記でビットを小数に割り当てる原理から、飽和・丸め・ガードビット付きMACまで、固定小数点の設計判断を内部動作で押さえられます。
- 1.固定小数点はQm.n表記で整数部mビット・小数部nビットを固定配置し、値は実際には整数として保持してスケール2の−n乗を暗黙に掛けて解釈する。ダイナミックレンジは指数を持たないため幅で決まる。
- 2.オーバーフロー時はラップアラウンド(剰余で折り返す)より飽和(上下限でクリップ)が信号処理では好まれる。乗算は小数部がn+n=2nに伸びるビット成長を起こし、丸めで戻す。
- 3.MACユニットは積を切らずにガードビット付きの広いアキュムレータへ足し込み、長い積和の途中オーバーフローを防ぐ。最後にスケール調整・丸め・飽和して目的の幅へ戻すのがDSPデータパスの定石。
なぜ固定小数点で信号処理を回すのか
浮動小数点ユニットは指数を持つため広いダイナミックレンジを自動で扱えますが、回路は大きく消費電力も高くつきます。フィルタやFFT、音声・無線のベースバンド処理のように、扱う値の大きさがおおむね既知で範囲が限られる用途では、指数を捨てて小数点の位置を設計時に固定してしまえば、演算は整数加算・整数乗算だけで済みます。これが固定小数点(fixed-point)であり、安価なDSPコアやFPGA、組み込みSoCの信号処理が浮動小数点なしで成立する理由です。
固定小数点の本質は、ハードウェアは整数として値を持ち、小数点の位置はプログラマの解釈の中にだけ存在するという点にあります。同じビットパターンを「整数」と読むか「小数を含む数」と読むかはスケールの取り決め次第です。だから固定小数点の難しさは回路ではなく、桁あふれ・精度・スケール管理という設計判断に集約されます。
Q表記とダイナミックレンジ
小数点位置の取り決めを表すのが Qm.n表記 です。Qm.n は整数部にmビット、小数部にnビットを割り当てる意味で、符号ビットを別に数える流儀では合計が 1 + m + n ビットになります(符号付きQ15なら16ビットで Q1.15、整数部は符号ぶんの1ビットだけ)。
ハードウェアが保持するのは整数値Iですが、解釈上の実数値は次の関係になります。
実数値 = I × 2^(-n) (I は 2 の補数で格納された整数)
最小刻み(分解能) = 2^(-n) (LSB が表す重み)
表現範囲(符号付き Qm.n)= −2^m 以上 2^m 未満、刻み 2^(-n)
ここから固定小数点の核心が見えます。nを増やせば分解能(刻みの細かさ)が上がるが、同じ語長ではmが減って表せる最大値が小さくなる。浮動小数点と違い指数で範囲を伸縮できないので、ダイナミックレンジは語長そのものに縛られ、精度と範囲はビットの取り合いになります。16ビットなら表せる桁数の比はおよそ 2^16、約96dB分しかなく、これをどこに割り当てるかがQ点の選択です。
音声や無線のサンプルは -1 以上 1 未満 に正規化して扱うことが多く、このとき Q1.15(符号1ビット+小数15ビット、整数部は実質なし)がぴったり合います。最大値が 1 に張り付くため、係数も信号も同じ尺度で扱え、乗算結果の小数点位置も予測しやすくなります。逆に1を超え得る中間値を持つ箇所だけ整数部ビットを足したQ点(ヘッドルームを確保したQ表記)に切り替える、という使い分けが定石です。
飽和とラップアラウンド ── あふれたらどう振る舞うか
固定小数点では範囲が固定なので、加算や累積でオーバーフローが避けられません。あふれたときの挙動には2つの流派があります。
| 挙動 | ラップアラウンド(モジュロ) | 飽和(サチュレーション) |
|---|---|---|
| あふれた結果 | 上限を越えると最小値へ折り返す(2の補数の剰余) | 上限・下限でクリップし、それ以上動かさない |
| 回路コスト | 通常の2の補数加算そのまま、追加なし | 桁あふれ検出と上下限への置換が必要 |
| 信号への影響 | 大→小へ急反転し、激しい不連続・耳障りなノイズ | なだらかに飽和、歪みは出るが破綻しにくい |
| 主な用途 | アドレス計算・ハッシュなど巡回が正しい場面 | 音声・画像・制御など信号値を扱う場面 |
2の補数の加算をそのまま使うと、最大の正値に1を足した瞬間に最小の負値へラップアラウンドします。アドレス計算ならこの巡回が正しい挙動ですが、信号処理では最大振幅の波形がいきなり逆極性の最大値へ飛ぶことになり、致命的なクリック音や画像の白黒反転を生みます。そこでDSPは飽和演算を標準装備し、あふれた値を表現可能な上限・下限に張り付かせます。歪みは生じますが、信号が破綻せず「潰れる」だけで済むため、聴感・視覚上はるかに穏当です。
飽和演算は数学的にきれいではありません。途中で上限に張り付くと情報が失われるため、(a + b) + c と a + (b + c) が一致しなくなります(飽和は結合則を満たさない)。だから後述のMACのように、途中は飽和させず広いアキュムレータに溜め、最後にだけ飽和する設計が重要になります。途中段で飽和してしまうと、本来なら上下に振れて打ち消し合うはずの和が片側で潰れ、誤差が累積してしまいます。
丸めとビット成長 ── 乗算が桁を増やす
固定小数点の乗算ではビット成長(bit growth)が起きます。Qm.n 同士を掛けると、整数として (語長×2) ビットの積になり、小数部の重みは 2^(-n) × 2^(-n) = 2^(-2n) すなわち小数部がn+n=2nビットに倍増します。Q1.15×Q1.15なら結果は32ビット幅で小数部30ビット、形式は Q2.30(符号ビットが2つ並び、うち1つは冗長な符号ビット)になります。これを元の語長へ戻すには、下位ビットを捨てるスケーリング(右シフト)と、捨て方を決める丸めが必要です。
Q1.15 × Q1.15 の流れ
16bit × 16bit → 32bit 積(小数部 30 ビット = Q2.30、符号ビットが2つ=1つ冗長)
目的が Q15 なら 小数部を 15 ビット減らす = 右へ 15 ビットシフト
捨てる 15 ビットの扱いが「丸め」:
切り捨て(truncation) : 下位を捨てるだけ。負方向に偏ったバイアスが残る
最近接丸め : 捨てる最上位ビットを見て繰り上げ判定。バイアスを抑える
収束丸め(最近接偶数) : ちょうど中間は偶数側へ。統計的バイアスを打ち消す
単純な切り捨ては実装が最も軽い一方、常に小さい側へ寄るため**直流バイアス(DCオフセット)が累積し、フィルタの出力に直流成分が漏れます。これを嫌う用途では浮動小数点の丸めと同様に最近接偶数丸め(収束丸め)**を使い、中間値を偶数側へ振り分けて平均的なバイアスをゼロに近づけます。なお乗算結果には符号付き×符号付きで現れる余分な符号ビットがあり、Q2.30 を Q1.15 に戻す際は1ビット左シフトして冗長な符号ビットを詰める実装も多く、ここを誤ると6dBのゲインずれになります。
ガードビット付きアキュムレータとMACユニット
DSPの中核演算はFIRフィルタや内積に現れる積和(Multiply-Accumulate, MAC)、すなわち acc = acc + a × b の反復です。これを支えるのがMACユニットで、Boothエンコーディングとワレスツリーで構成した高速乗算器の直後に、累積専用の広い加算器とアキュムレータレジスタを直結したデータパスです。
ここで決定的なのがガードビット(guard bits)付きアキュムレータです。N個の積を足し込むと、和は最悪で log2(N) ビットぶん成長します。256タップのFIRなら8ビット分です。これを乗算積と同じ幅のレジスタに溜めると途中で必ずあふれます。そこでDSPのアキュムレータは積より数ビット広く取り、この余剰をガードビットと呼びます。
MAC データパスの幅設計(例:16bit×16bit)
積 : 32 ビット(フル精度、切らずにそのまま足す)
アキュムレータ: 40 ビット(= 32 + 8 ガードビット)
ガードビット8 → 2^8 = 256 回までの累積で途中オーバーフロー無し
最終出力時に:右シフトでスケール調整 → 丸め → 飽和 → 16bit へ
ガードビットがあるおかげで、長い積和の途中では一切飽和も丸めもせず、フル精度の積をそのまま溜め込める点が重要です。途中で飽和すれば前述のとおり結合則が壊れて誤差が出るため、「途中は溜めるだけ、最後に一度だけ整形する」のが正しい順序です。
専用DSPコアは1サイクルあたり1MAC(あるいは複数MAC)を維持するため、独自の機構を持ちます。係数とデータを同時に取りに行く複数メモリポート(ハーバード構造)、ループ境界の分岐オーバーヘッドを消すゼロオーバーヘッドループ、フィルタの巡回バッファを自動で折り返すモジュロアドレッシング、そして広いアキュムレータと飽和論理です。これらが揃って初めて、N段のFIRがNサイクルでストールなく流れます。並列にMACを並べる発想はSIMDベクトル演算へ、さらに二次元へ展開するとシストリックアレイによる行列積へとつながります。
スケーリング戦略という設計判断
固定小数点の実装では、各段でどれだけ右シフトしてスケールを合わせるかというスケーリング戦略が品質を左右します。早めにシフトすれば(入力スケーリング)オーバーフローは確実に防げますが、下位ビットを捨てるぶんSNRが落ちます。遅らせれば(アキュムレータに溜め切ってから最後にシフト)精度は保てますが、ガードビットが足りないとあふれます。つまりオーバーフロー回避と量子化雑音の最小化はトレードオフで、信号の取り得る最大振幅(ヘッドルーム解析)に基づいてQ点とシフト量を割り付けるのがDSP設計の要諦です。係数をINT8量子化で持つ近年のエッジ推論も、本質的には同じスケール管理問題を解いています。
「Qm.n は小数部nビット、LSBの重みは 2^(-n)、ハードは整数で持ちスケールは解釈側にある」「指数を持たないためダイナミックレンジは語長で固定、精度と範囲はビットの取り合い」「あふれ時はラップアラウンド(剰余で折り返す)より飽和(クリップ)が信号処理向き。飽和は結合則を壊す」「乗算は小数部が2nに伸びるビット成長を起こし、右シフト+丸めで戻す。切り捨てはDCバイアスを残すので最近接偶数丸めを使う」「MACはガードビット付き広アキュムレータに途中は飽和せず溜め、最後にスケール・丸め・飽和する」。なぜ途中で飽和してはいけないかを説明できることが重要です。
まとめ
- 固定小数点は小数点位置を設計時に固定し、ハードは整数として値を持つ。
Qm.nはLSBの重みを2^(-n)に定め、指数を持たないぶんダイナミックレンジは語長に縛られ、精度と範囲はビットの取り合いになる。 - オーバーフロー時はラップアラウンド(剰余で折り返す)より飽和(上下限でクリップ)が信号処理で好まれる。ただし飽和は結合則を壊すため、途中で飽和させない設計が要る。
- 乗算は小数部がn+n=2nに伸びるビット成長を起こし、右シフトと丸めで語長へ戻す。切り捨てはDCバイアスを残すので最近接偶数丸めが無難。
- MACユニットは高速乗算器の積を切らずにガードビット付きの広いアキュムレータへ溜め、長い積和の途中オーバーフローを防ぐ。最後に一度だけスケール調整・丸め・飽和して目的の幅へ戻すのがDSPデータパスの定石。
CPU/メモリ/ディスク Article
固定小数点とDSP演算 ─ 飽和・スケーリング・MACを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
固定小数点
比較で見る軸
難易度: advanced / カテゴリ: CPU/メモリ/ディスク / タグ数: 6
導入後に効く点
オーバーフロー時はラップアラウンド(剰余で折り返す)より飽和(上下限でクリップ)が信号処理では好まれる。乗算は小数部がn+n=2nに伸びるビット成長を起こし、丸めで戻す。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- CPU/メモリ/ディスク
- タグ数
- 6
判断チェックリスト
- 自社の用途が「固定小数点 / DSP」に近いか確認する。
- 強みである「固定小数点はQm.n表記で整数部mビット・小数部nビットを固定配置し、値は実際には整数として保持してスケール2の−n乗を暗黙に掛けて解釈する。ダイナミックレンジは指数を持たないため幅で決まる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。