JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

真實世界的 DSP:多速率、定點運算與 DSP 處理器

你學過的每一個轉換與濾波器,最終都得在真實的矽晶片上、用真實的時脈、用只有那麼多位元寬的數字跑起來。這最後一級離開黑板、走進晶片:在不浪費不需要的取樣下改變取樣率、用整數而非無限精度的實數存活下來、把上千次乘加塞進每一微秒。讀完你會看見 Wi-Fi 收發器、助聽器與雷達,其實都藏著同一小撮招式。

從方程式到矽晶片:沒人事先警告你的鴻溝

在課本裡,一個濾波器是一行乾淨的代數:把每個取樣乘上一個係數,加總,完成。數字是實數、時脈是無限的、加總永遠不會溢位。真實硬體一個恩惠都不給你。你的取樣不管你準備好沒,都以每秒比方說 48,000 個的速度湧入;你的數字是 16 位元整數,不是整條實數線;而如果你每個輸出要算 192 次乘加,那你最好在下一個取樣到來前算完。這一級談的就是這道鴻溝——以及工程師用來跨越它的三個經典招式:聰明地改變取樣率(多速率 DSP)、活在固定寬度的整數裡(定點運算)、以及把這一切跑在一台為此打造的機器上(DSP 處理器)。

一路走下去時,腦中請守著一張圖。訊號是一條流過某個固定時間點的數字之河。你做的每件事——用 FIR 濾波器濾波、做 FFT、對著範本做相關——都是你必須在下一滴水到來前對每一滴水做完的事。真實世界 DSP 的藝術,就是把工作安排好,讓你永遠不落後於這條河。

多速率 DSP:別去算你要丟掉的取樣

假設你的麥克風以 48 kHz 給你音訊,但你的語音模型只要 8 kHz。最天真的做法是在 48 kHz 濾波,然後每六個取樣留一個。抽取(降取樣,decimation)正是這麼做,但用聰明的順序:先用一個低通濾波器把 4 kHz 以上的東西全殺掉——因為丟取樣本身就是一種取樣,任何超過新的奈奎斯特上限的東西都會折疊回來變成混疊——*然後*才每六個丟掉五個。反過來,內插(升取樣,interpolation)在取樣之間塞入零來提高速率,再用低通濾波器把那些零所造成的頻譜鏡像撫平。兩者合起來,讓你能以任意有理數倍率 L/M 重新取樣。

Decimate by 6   (48 kHz  ->  8 kHz)

  in @48k --> [ LPF  fc=4kHz ] --> [ keep 1 of 6 ] --> out @8k
              (anti-alias)          (downsample)

Interpolate by 3  (16 kHz  ->  48 kHz)

  in @16k --> [ insert 2 zeros ] --> [ LPF fc=8kHz ] --> out @48k
              (upsample, x3)          (anti-image)

Key insight: in decimation the filter runs at the HIGH rate but
you only need to compute outputs you keep -> a polyphase filter
computes only those, doing 6x less arithmetic.
抽取先濾波再丟取樣;內插先塞零再濾波。多相(polyphase)結構直接略過那些你本來就會丟掉的運算。

真正深刻的勝利是多相分解。在一個降六倍的濾波器裡,抗混疊濾波器每六個輸出有五個算出來只為了被丟掉——純粹的浪費。多相實作把濾波器拆解,讓它*只*產生你會留下的輸出,把乘法次數同樣砍掉六倍。這就是為什麼你的手機能在十幾種取樣率之間重新取樣,電池卻渾然不覺。

定點運算:用整數做正經的數學

紙上一個係數是 0.7071。在便宜、低功耗的硬體裡,沒有浮點運算單元能存它——只有整數。定點運算的解法是默默地約定小數點住在哪裡。在 Q15 格式裡,一個帶號 16 位元整數表示一個介於 −1 到略小於 +1 之間的分數:存下的整數就是真實值乘以 2¹⁵。所以 0.7071 存成 round(0.7071 × 32768) = 23170。兩個 Q15 數相乘,得到一個放在 32 位元裡的 Q30 結果;右移 15 位就回到 Q15。硬體永遠只看到整數;是*程式設計師*在心裡記著小數點。

Q15 fixed-point  (one sign bit . fifteen fraction bits)

  real 0.7071  ->  0.7071 * 2^15 = 23170   (stored int16)
  real -1.0    ->  -32768                   (most negative)
  largest      ->  +32767  = 0.99997        (can't reach +1.0!)

  multiply:  int32 prod = (int32)a * (int32)b;   // Q15 * Q15 = Q30
             int16 out  = prod >> 15;            // back to Q15

  resolution (one LSB) = 2^-15 = 0.0000305
  -> rounding it away injects QUANTIZATION noise
Q15:整數就是被縮放 2^15 倍的真實值。每一次乘積在被移回去之前,都暫時需要雙倍寬度。

這份節儉有兩個代價,尊重它們,就是能動的韌體與一團毛病的差別。第一個是[[quantization|量化]]雜訊:把每個係數、每個結果都四捨五入到最近的 LSB,會注入一點微小誤差,像鏡頭上的灰塵。用 16 位元你得到大約 96 dB 的動態範圍(每位元約 6 dB);用 24 位元,約 144 dB——這就是為什麼錄音室音訊與地震儀都伸手要更寬的字長。第二個代價是溢位:把兩個很大的 Q15 數相加,總和可能超過 +1,把一個接近最大的正數繞回成一個很大的負數——在音訊裡是一聲令人作嘔的*喀噠*,在控制迴路裡則是一場不穩定。

  1. 選一個留有餘裕的格式。如果乘積的總和可能到 4,就留 2–3 個整數位元(用 Q13,不是 Q15)。在你寫下一行程式之前,先把位元預算算好。
  2. 用寬的累加器累加。在一個帶保護位元(guard bit)的 40 位元累加器裡加總你的乘積,而不是 16 位元暫存器,這樣即使最終答案放得下,中間的和也不會溢位。
  3. 飽和,別繞回。使用飽和運算,讓溢位被夾在最大值,而不是繞回成一個巨大的反號數——溫和的失真好過一聲暴烈的喀噠。
  4. 在冒險前先縮放。在一個高增益的階段前把訊號降幾個位元,過後再縮放回來——拿一點點雜訊底噪,換來保證不溢位的安全。

DSP 處理器:一台靠乘法與加法維生的機器

湊近看任何一個 DSP 演算法——一個 FIR 濾波器、一個卷積、一個 FFT 蝶形運算——你都會找到同一顆原子被重複上百萬次:acc = acc + (x × h),一次乘法接著一次加法。一顆通用 CPU 要用好幾個指令做這件事。一顆 DSP 處理器用*一個*指令做,每個時脈週期一次,在一個專用的 MAC 單元(乘加單元)裡完成。光是這一個架構抉擇,就是跟得上河流與被淹沒之間的差別。

FIR filter:  y[n] = sum_{k=0..N-1} h[k] * x[n-k]

On a DSP, the inner loop is ONE instruction per tap:

  loop  MAC  *AR0+, *AR1+, A    ; A += (*AR0) * (*AR1)
             ; AR0 walks coeffs h[], AR1 walks a CIRCULAR buffer x[]
             ; single cycle: fetch 2 operands, multiply, add, bump ptrs

  A 64-tap FIR at 48 kHz  = 64 MACs x 48000 = 3.07 MMAC/s
  A 1GHz single-MAC DSP   = 1000 MMAC/s  ->  loafing at 0.3% load

The circular buffer is the trick: AR1 wraps from the end of x[]
back to the start automatically, so the delay line needs NO
memory shuffling -- just a moving write pointer.
每個濾波器抽頭一個 MAC 指令,搭配硬體環形定址,讓延遲線永遠不必被複製搬動。

另外兩個硬體特性讓 MAC 單元勢不可擋。第一,環形緩衝區:一個 FIR 的延遲線是最近 N 個取樣,天真的做法是每個輸出把每個取樣往下挪一格——N 次純開銷的複製。取而代之的是,位址產生器讓一個寫指標在固定緩衝區裡繞圈,新取樣直接覆蓋最舊的,完全不必搬動。第二,哈佛記憶體架構:指令與資料分開的匯流排(常常是兩條資料匯流排)讓處理器在同一個週期裡取一個係數、*又*取一個取樣、*又*取下一個指令,於是 MAC 永遠不會餓著。再加上零開銷的硬體迴圈,你就有了一台能無限維持每時脈一個抽頭的機器。

相關:找一根收發器早已熟知的針

我們以一個展現 DSP 觸角遠超濾波的操作作結。相關把一個訊號滑過另一個,在每一個位移處問:*這兩者長得有多像?*它是不做時間翻轉的卷積——同樣那串 MAC——它回答一個極其有用的問題:一個已知的圖案,藏在一條充滿雜訊的串流裡的哪裡、藏得有多強?一個高的相關峰值在說:*範本就在這裡,就在這個確切的延遲處*。

當你拿來相關的圖案,是*一個被發射出去的脈衝的確切形狀*時,相關就成了[[ee-matched-filter|匹配濾波器]]——可被證明是偵測埋在白雜訊裡的已知訊號的最佳線性方法,因為它把訊號的全部能量集中成一根尖銳的峰,而雜訊仍攤散著。這是藏在驚人數量科技背後那具安靜的引擎。

Correlation of received r[n] with known template s[n]:

  R[d] = sum_n  r[n] * s[n-d]      (just a sliding MAC, like FIR)

  --- noise floor ---  ...,-2,1,-1,3,-1, |  17  | ,-2,1,3,-1,...
                                          ^^^^^^
                       sharp peak at lag d* = the template is HERE

  Radar:   d* * c / 2          -> target distance
  GPS/CDMA: align local PN code -> lock + which satellite
  5G/Wi-Fi: correlate preamble  -> frame start & timing
  Sonar/ultrasound, DNA seq, even audio fingerprinting (Shazam)
相關就是一個滑動的乘加;峰值的位置就是答案。依應用不同,把延遲換算成距離、時序或身分。

對單一個操作而言,這份觸角的廣度真的令人咋舌。雷達把回波與它送出的脈衝相關;峰值的延遲,乘以光速再除以二,就是目標的距離。一台 GPS 接收器把天空與每顆衛星已知的偽隨機碼相關,同時完成鎖定、辨識衛星與測距——這正是讓數十支 CDMA 手機共用一個頻段的同一個展頻想法。一台 5G 或 Wi-Fi 接收器對著一個已知的前導碼相關,找出每個訊框確切從哪裡開始。一個滑動的 MAC,在一顆 DSP 處理器上以定點跑,你就有了同步、測距與偵測——無線世界的結締組織。

系統視角:一切咬合在一起之處

退一步,看一台軟體定義無線電吞下一段 FM 廣播,你會看見這條學習軌的每一級,像一個生物體般協同運作。天線的寬頻串流被混頻下降、抽取以隔離出一個電台——多速率。取樣以定點整數存活,好讓前端便宜又涼。一排跑在帶環形緩衝區MAC 單元上的 FIR 濾波器把音訊從雜訊裡分出來——這就是 DSP 處理器。一個對著已知同步字的相關找出每個資料訊框的起點——而一個 FFT把剩下的頻譜變成你調台時盯著看的那張圖。

這正是這最後一級的全部重點。先前那些轉換、濾波器與取樣定理,從來就不是各自獨立的工具——它們是一些零件,在等一副底盤。多速率決定數字流得*多快*,定點決定每個數字*多寬*,而 DSP 處理器決定你每秒能負擔得起*多少*次運算。掌握這三項約束,你就能把任何一篇論文裡的方塊圖拿來,讓它即時地,跑在一顆你握得住的晶片上。