Table of Contents
導入事例
デジタル信号処理(DSP)は、リアルタイムのオーディオ、ビデオ、テレメトリー、通信操作を可能にする、現代の組み込みシステムのバックボーンです。 DSPタスクの効率的なCコードを書くことは、システムスループット、電力消費、遅延に直接影響を与えます。 汎用コードとは異なり、DSPアルゴリズムは、限られたメモリと処理リソースを最大限に活用しながら、厳格なタイミング制約内で実行する必要があります。 このガイドは、コア原則の有効活用を拡大し、DSPアプリケーションから固定されたハードウェアに、生産グレードのCコードを書くための実用的な技術を提供します。
CにおけるDSPの基礎を理解する
DSPは、フィルタリング、変換、コンボリューション、およびサンプル信号に関するスペクトル解析などの数学的操作を含みます。Cでは、プログラマは、データ表現とフローのあらゆる側面を制御し、決定的な実行のために不可欠です。DSPコードは、ハードウェアが密接に結合されるマイクロコントローラまたはデジタル信号プロセッサで実行されます。例えば、MAC(マルチプレクサー)ユニットまたはSIMDベクターエンジンを専用の。ターゲットアーキテクチャの理解を深めると、CerarchyとCerarchyの構成要素が不可欠です。
DSPコードの主な特徴:
- [] 繰り返し算術:[ 複数重追加操作ドミナミ(例えば、FIRフィルタ) のループ。
- ]リアルタイム制約:[の各サンプルは、サンプル期間内に処理する必要があります。
- データストリーミング:]連続入力/出力ストリームは、効率的なバッファリングと最小のコピーを必要とします。
- メモリー帯域幅:] は、演算ではなく、高速なデータが移動できる方法によって、多くのDSPアルゴリズムが制限されます。
基礎参照については、[]]のアナログデバイスDSP基本を参照してください。
固定点の有利運動:浮動小数点の頭上なしで精密
多くの DSP プロセッサは、ハードウェアのフローティングポイント単位 (FPU) を欠いているか、または遅く FPU を持っています。固定ポイント算数処理は、暗黙のポイントで整数操作を使用し、決定的なパフォーマンスと低消費電力を提供します。最も一般的な表現は Q[m]]]n] で ビットは、 ビットは [FLT] ビットの [FLT] ビットは、 [FLT] ビットの [FLT] ビットは、 [FLT] ビットの [[FLT] ビットの] ビットは、 [[FLT] ビットの [[FLT] ビットの[F] ビットの[F] ビットの[F] ビットの[F] ビットの[F] ビットの[F] ビットの[F] ビットの[[F] ビットの[[F] ビットの[[[F] ビットの[[[[[F] ビット] ビット] ビットの[
C における固定ポイントの運用の実装
固定ポイントの追加は簡単です(厳密に整数を追加)、しかし、乗算は半径点を調整する必要があります。 Q15乗算では、2つのQ15数字の製品は32ビット中間結果を必要とします、その後、Q15に戻るために15ビットで右シフトします。 例:
typedef int16_t q15_t;
q15_t q15_mul(q15_t a, q15_t b) {
int32_t temp = (int32_t)a * (int32_t)b;
return (q15_t)(temp >> 15);
}
蓄積が(例えば、フィルタで)起こるとき、ガードビットはオーバーフローを防ぐ。32ビットまたは64ビットの蓄積装置を使用して、結果を飽和して下さい。[のような固定ポイント ライブラリは、ARM CMSIS-DSP]をろ過、変形およびマトリックス操作を含む最大限に活用された固定ポイント機能を提供します。
固定ポイントとフローティングポイントを使用するとき
FPU(Cortex-M4/M7) のモダンプロセッサーは、固定ポイントとして高速にフローティングポイント操作を実行できます。フローティングポイントは、次の場合に使用します。
- アルゴリズムの動的範囲は高い(例えば、適応フィルター)です。
- コードの維持性は優先的に(スケーリング解析がなければ)。
- FPU ハードウェアは存在し、パイプラインは重複して追加し、多重化することができます。
FPUのない大容量デバイスでは、固定ポイントはコスト感度の高いアプリケーションの標準を維持します。
DSPのメモリアクセスの最適化
DSPアルゴリズムは、大量のデータ配列を順次処理することが多いです。キャッシュミスとバスのストールはパフォーマンスを殺すことができます。次の原則に従ってください。
- [Linearデータアクセス:] 連続した順番(Cのrow-major)のトラバース配列。アルゴリズム(例えば、FFTビット反転)で必要としないで、アクセスパターンが固まりません。
- []データ整列:]] は、配列がキャッシュライン境界に整列されていることを確認します。 ] や特殊なメモリセクションなどのコンパイラ属性を使用します。
- バッファリング:]]は、CPU処理でDMA転送をオーバーラップするためにダブルバッファリングを使用します。 CPUは1つのバッファで動作しますが、次のサンプルブロックがロードされます。
- キーワードを制限:]] は、ポインタがエイリアスをしないコンパイラを通知するポインタのC99のを使用します。 ベクター化とより良い指示スケジューリングを有効にします。
例えば、入力と出力バッファが別々の場合、単純な FIR フィルター関数は `restrict` で記述されるべきです。
void fir_lowpass(const int16_t * restrict x, int16_t * restrict y,
const int16_t * restrict coeffs, int len, int order) {
for (int i = 0; i < len; i++) {
int32_t acc = 0;
for (int j = 0; j < order; j++) {
acc += (int32_t)x[i + j] * coeffs[j];
}
y[i] = (int16_t)(acc >> 15);
}
}
効率的なアルゴリズム選択と実装
アルゴリズムの複雑性は、直接実行時間と電力に変換します。タスクの最も効率的なアルゴリズムを常に選択します。
- Fast Fourier Transform (FFT):[]]は、Cooley-Tukey の半径x-2または2つの電源の分割軸を使用します。 O(N2)であるNive DFTを避けてください。 ROMにプレコンプトされた小文字と格納。
- FIRフィルタ:]]は、偏見/補間のために多相分解を使用する; 線形相フィルタの対称性を悪用して、乗算数を半減する。
- []IIRフィルタ:]]は、より優れた数値安定性のために転移された直接フォームIIを使用します。 測定された二角セクション(秒順)を使用して、係数量子化に対する感度を低下させます。
- 長いシーケンスのためのConvolution:[]] は、FFTベースのオーバーラップアライドまたはオーバーラップセーブメソッドを直接のコンボリューションではなく使用します。
現代のFFT技術に関する参照は、(Cではない場合、その原則は組み込みDSPライブラリで広くコピーされています) []FFTWライブラリ[を参照してください。
ハードウェア機能の活用:SIMDおよびDSPの指示
ほぼすべての近代的なマイクロコントローラは、SIMD(単一指示複数のデータ)またはDSP-enhanced命令を含みます。 たとえば:
- ARM Cortex-M4/M7: SIMD (SADD、SMUADなど)、飽和算処理、および僅かな操作(QADD、QSUB)。 CMSIS-DSPの侵入機能を使用してください。
- TI C6000 DSP: 8つの乗用ユニット、デュアルMAC、ソフトウェアのパイプライン。 []TI DSP最適化ガイド]は、詳細な技術を提供します。
- P-extensions で RISC-V: 将来のコアは DSP のような指示を持つ。
C でこれらの機能を使用するには、コンパイラが自動ベクトル化(例えば、依存関係のない単純なループ)できるコードを記述するか、コンパイラのイントラニシック関数を使う。 FIR フィルターの ARM CMSIS-DSP を使った例:
#include "arm_math.h"
arm_fir_instance_f32 S;
float32_t firState[128];
arm_fir_init_f32(&S, numTaps, coeffs, firState, blockSize);
arm_fir_f32(&S, input, output, blockSize);
そのようなライブラリは、最大性能のためのアセンブリで手調です。 一般的なCからライブラリ機能に切り替える前後のプロファイルは常に。
ループ最適化技術
DSPアルゴリズムはループ重いので、ループレベルでの最適化は大きな配当を支払います。
- []ループアンローリング:[]]手動でまたはコンパイラpragmas(`#pragma unroll N`)でループオーバーヘッドを削減し、指示レベルの並列性を増加させる。
- []ソフトウェアのパイプライン:[]]は、複数の反復が同時に飛行されるようにループを再構成します。 一部のコンパイラは、自動的にこれを行います。 `-O3` とアーキテクチャ固有のフラグを使用します。
- []ブランチングを削減:[]]] 条件を算術(例、境界線を使用して最小/最大)に置き換え、または非線形関数の検索テーブルを使用します。
- [] ローカル変数を使用します:[]]] 頻繁に、ループ内の変数を宣言するか、`register` ヒントを使用して、レジスタ内のデータにアクセスします。
- [] 分岐部の最小化:[] 分岐部を、共焦点による乗算で一定に置き換える;2つの電力のシフトを使用する。
定数とルックアップテーブルの事前入力
DSP は、三角的値、係数、およびトワイドル係数などの機能がオフラインでプリコンプされ、ROM 内の定数配列として保存されるべきです。非リアルタイムの起動のために、一度それらを計算し、再使用することができます。例: 1024 ポイント FFT の場合、各ステージの正弦/コサイン値が優先されます。これはランタイムの評価をなくし、電力を削減します。
ラップテーブル(LUT)は、DSP(例えば、スピーチ処理)で使用される平方根、指数関数、およびログなどの機能にも役立ちます。テーブルエントリ間の線形補間を使用して、メモリと精度を取引します。
プロファイルとチューニング
測定なしで最適化は完了しません。これらの技術を使用してボトルネックを特定します。
- []サイクル精度のプロファイリング:[]は、オンボードサイクルカウンター(Cortex-MのDWT CYCCNT)を使用して、機能の持続時間を測定します。
- [ 統計的プロファイリング:[ どの関数がCPU時間を消費するかを調べるために、サンプルプログラムのカウンター(PC)。
- []メモリープロファイリング:[]]はキャッシュのミス(利用可能な場合)とバスの取引を監視するためのツールを使用します。
- []コンパイラの最適化レポート(GCCの`-fopt-info-vec-optimized`)が、ループがベクター化されたかどうかを確認できます。
反復:測定、変更、測定を再び。多くの場合、最大の利益は、算術を微調整するよりもメモリアクセスパターンを改善することから来ています。
実用的な概要: それを一緒に持って来る
C の効率的な DSP コードを書くには、包括的なアプローチが必要です。
- 適切なデータ表現(固定ポイントとフローティングポイント)を選択します。
- 順次アクセスとアライメントのためのデータ構造の設計。
- 複雑度が低いアルゴリズム(FFT、多相)を選択します。
- 利用可能なときにベンダーDSPライブラリを使用します。
- ループをアンロールし、分岐を削減します。
- ROM のプレコンプト定数。
- プロファイルは、完全に非公開にし、コンパイラの助けをしましょう。
これらの原則を適用することにより、Cの移植性と保守性を維持しながら、開発者は手作業で調整されたアセンブリに匹敵する信号処理のスループットを達成することができます。その結果、補聴器から5G基地局まで、現代の埋め込まれた製品の要求を満たす信頼性が高く、リアルタイムのDSPシステムです。