Main Content

手動による固定小数点の変換のベスト プラクティス

Fixed-Point Designer™ ソフトウェアは、アルゴリズムを固定小数点に設計および変換するうえで役立ちます。MATLAB® で単純に固定小数点アルゴリズムを設計するにしても、Fixed-Point Designer を MathWorks® コード生成製品と共に使用するにしても、これらのベスト プラクティスは汎用 MATLAB コードから効率的な固定小数点の実装を行うのに役立ちます。これらのベスト プラクティスは次の Web セミナーでも説明しています。手動による固定小数点の変換のベスト プラクティス Web セミナー

テスト ファイルの作成

コードの構造化のベスト プラクティスは、結果のテストと検証に使用する他のコードと、コア アルゴリズムを分離することです。元の MATLAB アルゴリズムと固定小数点バージョンのアルゴリズムを呼び出すテスト ファイルを作成します。たとえば、以下の表に示すように、入力データをアルゴリズムに供給するように設定し、それらのデータを処理した後に結果を確認するプロットを作成します。アルゴリズム部分のみを固定小数点に変換する必要があるので、入力の作成、アルゴリズムの呼び出し、結果のプロットを行うテスト ファイルおよびコア プロセッシングを行う 1 つ (または複数) のファイルをもつようにコードを構造化するのがより効率的です。

元のコードベスト プラクティス変更されたコード
% TEST INPUT
x = randn(100,1);

% ALGORITHM
y = zeros(size(x));
y(1) = x(1);
for n=2:length(x)
  y(n)=y(n-1) + x(n);
end

% VERIFY RESULTS
yExpected=cumsum(x);
plot(y-yExpected)
title('Error')

問題点

テスト入力の生成および結果の確認がアルゴリズム コードと混在している。

修正法

アルゴリズムから分離したテスト ファイルを作成します。アルゴリズムを独自の関数に含めます。

テスト ファイル

% TEST INPUT
x = randn(100,1);

% ALGORITHM
y = cumulative_sum(x);

% VERIFY RESULTS
yExpected = cumsum(x);
plot(y-yExpected)
title('Error')

アルゴリズム独自の関数

function y = cumulative_sum(x)
  y = zeros(size(x));
  y(1) = x(1);
  for n=2:length(x)
    y(n) = y(n-1) + x(n);
  end
end

このテスト ファイルは以下の目的で使用できます。

  • 固定小数点に変換する前に浮動小数点アルゴリズムが期待どおりに動作することを検証します。浮動小数点アルゴリズムの動作は、固定小数点バージョンのアルゴリズムの動作と比較する際のベースラインとなります。

  • 固定小数点データ型を推奨します。

  • 固定小数点バージョンのアルゴリズムの動作を浮動小数点ベースラインと比較します。

シミュレーション範囲が正確になるよう、テスト ファイルによってアルゴリズムが動作範囲全体にわたって実行されます。フィルターの現実的な入力の例としては、インパルス、正弦波の和、チャープ信号などがあります。これらの入力を線形理論で使用すると、出力が正しいことを確認できます。最大出力を生成する信号は、システムがオーバーフローしないことを検証するうえで役立ちます。推奨される固定小数点データ型の性質は、テスト ファイルが適切な精度でアルゴリズムの動作範囲をどの程度カバーしているかによって決まります。

コード高速化またはコード生成のためのアルゴリズムの準備

Fixed-Point Designer を使用して、以下のことを実行できます。

  • 以下の関数を使用した、コードのインストルメント化およびアルゴリズムを固定小数点に変換する際に役立つデータ型の推奨を提供。

    • buildInstrumentedMex。ログ作成可能なインストルメンテーションを含むコンパイル済みの C コードを生成します。

    • showInstrumentationResults。インストルメント化されたコンパイル済み C コードによってログ作成された結果を表示します。

    • clearInstrumentationResults。ログ作成されたインストルメンテーションの結果をメモリからクリアします。

  • 関数 fiaccel を使用して MEX ファイルを作成し、固定小数点アルゴリズムを高速化。

buildInstrumentedMex を使用してインストルメント化するすべての MATLAB アルゴリズムおよび fiaccel を使用して高速化するすべての固定小数点アルゴリズムは、コード生成の要件と規則を遵守しなければなりません。コード生成でサポートされている MATLAB 言語のサブセットを確認するには、C/C++ コードの生成でサポートされている関数およびオブジェクトを参照してください。

MATLAB コード内のサポートされていない関数または構造体を特定できるようにするには、以下のいずれかのツールを使用します。

  • MATLAB ファイルの先頭に %#codegen プラグマを追加します。MATLAB コード アナライザーにより、コード生成でサポートされていない MATLAB 言語のサブセット内で使用できない関数と構造体にフラグが付けられます。このアドバイスは、MATLAB エディターでコードを編集する際にリアルタイムで表示されます。

    詳細は、MATLAB コード アナライザーを使用したコードのチェックを参照してください。

  • コード生成の準備状態ツールを使用してコードの静的レポートを生成します。このレポートは、コード生成でサポートされていない関数への呼び出しとデータ型の使用を特定します。関数 myFunction1 のレポートを生成するには、コマンド ラインで「coder.screener('myFunction1')」と入力します。

    詳細は、コード生成の準備状態ツールを使用したコードのチェックを参照してください。

アルゴリズム内で使用される関数の固定小数点サポートの確認

固定小数点の変換を開始する前に、アルゴリズム内で使用されている関数のうち固定小数点がサポートされていない関数を特定します。それらを置き換える方法を考慮するか、または組み込みターゲットについてより最適化されるように実装を変更してください。たとえば、log2fft および exp などの関数の置換を見つける (または独自に記述する) 必要がある場合もあります。sincos および sqrt などの他の関数は固定小数点をサポートしますが、効率性を向上させるためにルックアップ テーブルや CORDIC ベースのアルゴリズムなどの代替の実装を考慮する必要がある場合があります。

置換項目をすぐに見つけられない場合は、入力で double をキャストし、出力で固定小数点型にキャストし直すことで、固定小数点をサポートしない関数の影響を受けないようにしてアルゴリズムの残りの関数を固定小数点に変換することができます。

元のコードベスト プラクティス変更されたコード
y = 1/exp(x);

問題点

関数 exp() が固定小数点入力用に定義されていない。

修正法

置換項目が見つかるまで入力を double にキャストします。この場合、1/exp(x) は、固定小数点成長に関して exp(x) よりも優れているため、式全体を関数 1/exp で (可能な場合はルックアップ テーブルとして) 置き換えます。

y = 1/exp(double(x));

データ型の管理とビット成長の抑制

(:)= 構文は添字による代入として知られています。この構文を使用する場合、MATLAB は左辺引数の値を上書きしますが、既存のデータ型と配列サイズは保持します。これは、固定小数点変数が誤って double に変換されることなく固定小数点として維持され、出力で特定データ型を維持する際のビット成長を防ぐという点で特に重要です。

元のコードベスト プラクティス 変更されたコード
acc = 0;
for n = 1:numel(x)
  acc = acc + x(n);
end

問題点

acc = acc + x(n)accacc + x(n) で上書きする。すべて double 型を使用している場合、この動作は問題ありません。しかし、コード内で固定小数点データ型を導入する場合、acc が上書きされると acc のデータ型が変化することがあります。

修正法

acc の元のデータ型を保持するには、acc(:)= を使用して acc に代入します。添字による代入を使用すると、右辺の値が acc と同じデータ型にキャストされてビット成長を防ぎます。

acc = 0;
for n = 1:numel(x)
  acc(:) = acc + x(n);
end

詳細については、ビット成長率の抑制を参照してください。

データ型定義のアルゴリズムからの分離

インストルメンテーションとコード生成を行う場合は、固定小数点に変換する関数を呼び出すエントリポイント関数を作成します。その次に、この関数の入力をさまざまなデータ型にキャストできます。この関数のさまざまなバリエーションの呼び出しを追加し、比較することができます。エントリポイント関数を使用すると、固定小数点と浮動小数点の両方のバリアントのアルゴリズムを実行するだけでなく、固定小数点のさまざまなバリアントを実行することもできます。このアプローチにより、コードをより高速に反復して、最適な固定小数点設計を得ることができます。

この固定小数点の変換方法は、複数の異なる固定小数点の実装をより簡単に比較できるようにし、さらに、アルゴリズムを他のデバイスのターゲットに再指定することもできます。

データ型定義をアルゴリズムから分離するには、以下を実行します。

  1. 変数が最初に定義されるときに、cast(x,'like',y) または zeros(m,n,'like',y) を使用して目的のデータ型にキャストします。

  2. コード内で使用されている元のデータ型から始めて、データ型定義のテーブルを作成します。固定小数点に変換する前に、すべての single データ型を使用するデータ型テーブルを作成して型の不一致および他の問題を検出します。

  3. 各テーブルに接続されたコードを実行し、結果を見て接続を確認します。

元のコードベスト プラクティス変更されたコード
% Algorithm
n = 128;
y = zeros(size(n));

問題点

MATLAB の既定のデータ型が倍精度浮動小数点である。

修正法

  1. cast(...,'like',...) および zeros(...'like',...) を使用して別のテーブル内で定義されている型にプログラムで指定します。

  2. 元の型テーブルを (通常、別の関数内で) 作成します。

  3. コードとの接続を確認できるようにするため、テーブルに single データ型を追加します。

% Algorithm
T = mytypes('double');
n = cast(128,'like',T.n);
y = zeros(size(n),'like',T.y);
function T = mytypes(dt)
  switch(dt)
    case 'double'
      T.n = double([]);
      T.y = double([]);
      
    case 'single'
      T.n = single([]);
      T.y = single([]);
  end
end

データ型の指定をアルゴリズム コードから分離すると、以下のことができます。

  • 異なるデータ型でアルゴリズム コードを再利用

  • データ型指定を切り離してアルゴリズムを整理し、データ型ごとにステートメントを切り替え

  • アルゴリズム コードの可読性を向上

  • 固定小数点データ型と浮動小数点データ型を切り替えてベースラインを比較

  • アルゴリズム コードを変更しないで固定小数点設定のバリエーションを切り替え

固定小数点への変換

固定小数点へ変換する目的

変換を開始する前に、固定小数点に変換する目的を考慮します。アルゴリズムを C または HDL に実装するのか。ターゲットの制約は何か。これらの質問への回答により、使用可能な語長、小数部の長さおよび算術モードなどの多くの固定小数点プロパティ、ならびに使用可能な数学ライブラリが決定されます。

インストルメント化された MEX 関数のビルドと実行

インストルメント化された MEX 関数をビルドして実行し、関数 buildInstrumentedMex および showInstrumentationResults を使用して固定小数点型の推奨を取得します。テスト ファイルによってアルゴリズムが動作範囲全体にわたって実行されます。推奨される固定小数点データ型の性質は、テスト ファイルが適切な精度でアルゴリズムの動作範囲をどの程度カバーしているかによって決まります。単純なテスト ベクトルのセットは型の全範囲を実行しない可能性があるので、推奨された型は固定小数点型の初期設定を選択するガイドラインとして使用し、最良の判断と経験を活用して型を調整してください。ループのインデックスがインデックス変数としてのみ使用される場合、整数型に自動的に変換されます。そのため、明示的に固定小数点へ変換する必要はありません。

アルゴリズム コード テスト ファイル
function [y,z] = myfilter(b,x,z)
  y = zeros(size(x));
  for n = 1:length(x)
    z(:) = [x(n); z(1:end-1)];
    y(n) = b * z;
  end
end
% Test inputs
b = fir1(11,0.25);
t = linspace(0,10*pi,256)';
x = sin((pi/16)*t.^2);  % Linear chirp
z = zeros(size(b'));

% Build
buildInstrumentedMex myfilter ...
  -args {b,x,z} -histogram

% Run
[y,z] = myfilter_mex(b,x,z);

% Show
showInstrumentationResults myfilter_mex ...
  -defaultDT numerictype(1,16) -proposeFL

型テーブルの作成

変数のプロトタイプをもつ構造体を使用して型テーブルを作成します。推奨型はシミュレーションの実行により計算されます。予期されるデータ範囲が広いシミュレーションを長く実行すると、より適切な推奨が得られます。推奨された型を使用するか、アルゴリズムおよび実装の制約に関する知識を活用して、推奨を改善できます。

値ではなくデータ型が使用されるので、プロトタイプの値を空 ([]) として指定します。

場合によっては、コードの一部を浮動小数点のままにしておく方が効率性が高いこともあります。たとえば、ハイ ダイナミック レンジがある場合や、コードの該当部分が丸め誤差に対して敏感な場合などです。

アルゴリズム コード型テーブルテスト ファイル
function [y,z]=myfilter(b,x,z,T)
  y = zeros(size(x),'like',T.y);
  for n = 1:length(x)
    z(:) = [x(n); z(1:end-1)];
    y(n) = b * z;
  end
end
function T = mytypes(dt)
  switch dt
    case 'double'
      T.b = double([]);
      T.x = double([]);
      T.y = double([]);

     case 'fixed16'
      T.b = fi([],true,16,15);
      T.x = fi([],true,16,15);
      T.y = fi([],true,16,14);
  end
end
% Test inputs
b = fir1(11,0.25);
t = linspace(0,10*pi,256)';
x = sin((pi/16)*t.^2); 
% Linear chirp

% Cast inputs
T=mytypes('fixed16');
b=cast(b,'like',T.b);
x=cast(x,'like',T.x);
z=zeros(size(b'),'like',T.x);

% Run
[y,z] = myfilter(b,x,z,T);

固定小数点型を使用した実行および結果の比較

固定小数点に変換する前に浮動小数点アルゴリズムが期待どおりに機能することを検証するテスト ファイルを作成します。これと同じテスト ファイルを使用して、固定小数点データ型の推奨を得て、変換後に固定小数点の結果を浮動小数点のベースラインと比較することができます。

データ型の最適化

スケーリングされた double の使用

スケーリングされた double を使用して潜在的なオーバーフローを検出します。スケーリングされた double は浮動小数点数と固定小数点数のハイブリッドです。Fixed-Point Designer は、スケーリング、符号および語長の情報を維持したまま double として保存します。スケーリングされた double を使用するには、データ型オーバーライド (DTO) プロパティを使用するか、fi または numerictype コンストラクター内で 'DataType' プロパティを 'ScaledDouble' に設定します。

目的使用するプロパティ

[データ型オーバーライド] をローカルで設定

numerictype DataType プロパティ

T.a = fi([],1,16,13,'DataType', 'ScaledDouble');
a = cast(pi, 'like', T.a)
a =
    3.1416

   DataTypeMode: Scaled double: binary point scaling
           Signedness: Signed
         WordLength: 16
     FractionLength: 13

[データ型オーバーライド] をグローバルに設定

fipref DataTypeOverride プロパティ

fipref('DataTypeOverride','ScaledDoubles')
T.a = fi([],1,16,13);
a =
  3.1416

  DataTypeMode:Scaled double: binary point scaling
    Signedness: Signed
    WordLength:16
FractionLength:13

詳細は、スケーリングされた doubleを参照してください。

ヒストグラムを使用したデータ型の設定の微調整

固定小数点型の設定を微調整するには、関数 buildInstrumentedMex–histogram フラグを指定して実行した後、生成された MEX 関数を望ましいテスト入力で実行します。showInstrumentationResults を使用してコード生成レポートを表示すると、レポートに [ヒストグラム] アイコンが表示されます。アイコンをクリックして NumericTypeScope を開き、選択された変数のシミュレーション内での値の分布を確認します。

コード生成レポート内に赤で示されたオーバーフローは、NumericTypeScope の「範囲外」のビンに表示されます。ヒストグラム ビューのアイコン をクリックして、関連付けられた変数または式の NumericTypeScope を起動します。

設計トレードオフの調査

固定小数点のデータ型の最初のセットを決定した後は、固定小数点値のさまざまな変化を型テーブルに追加できます。オーバーフローを防ぐための変更や反復、小数部の長さの調整およびバイアスを排除するための丸め手法の変更が可能です。

アルゴリズム コード型テーブルテスト ファイル
function [y,z] = myfilter(b,x,z,T)
  y = zeros(size(x),'like',T.y);
  for n = 1:length(x)
    z(:) = [x(n); z(1:end-1)];
    y(n) = b * z;
  end
end
function T = mytypes(dt)
  switch dt
    case 'double'
      T.b = double([]);
      T.x = double([]);
      T.y = double([]);

    case 'fixed8'
      T.b = fi([],true,8,7);
      T.x = fi([],true,8,7);
      T.y = fi([],true,8,6);

    case 'fixed16'
      T.b = fi([],true,16,15);
      T.x = fi([],true,16,15);
      T.y = fi([],true,16,14);
  end
end
function mytest
  % Test inputs
  b = fir1(11,0.25);
  t = linspace(0,10*pi,256)';
  x = sin((pi/16)*t.^2);  % Linear chirp
  
  % Run
  y0  = entrypoint('double',b,x);
  y8  = entrypoint('fixed8',b,x);
  y16 = entrypoint('fixed16',b,x);
  
  % Plot
  subplot(3,1,1)
  plot(t,x,'c',t,y0,'k')
  legend('Input','Baseline output')
  title('Baseline')
         
  subplot(3,2,3)
  plot(t,y8,'k')
  title('8-bit fixed-point output')
  subplot(3,2,4)
  plot(t,y0-double(y8),'r')
  title('8-bit fixed-point error')

  subplot(3,2,5)
  plot(t,y16,'k')
  title('16-bit fixed-point output')
  xlabel('Time (s)')
  subplot(3,2,6)
  plot(t,y0-double(y16),'r')
  title('16-bit fixed-point error')
  xlabel('Time (s)')
end

function [y,z] = entrypoint(dt,b,x)
  T = mytypes(dt);
  b = cast(b,'like',T.b);
  x = cast(x,'like',T.x);
  z = zeros(size(b'),'like',T.x);
  [y,z] = myfilter(b,x,z,T);
end

アルゴリズムの最適化

C または HDL のナチュラルな型取得のための fimath の使用

fimath プロパティは、fi オブジェクトに数式、丸めおよびオーバーフロー プロパティを含む算術演算を実行するためのルールを定義します。fimath ProductMode および SumMode プロパティを使用して C および HDL のナチュラルなデータ型を保持できます。ProductMode および SumModeKeepLSB 設定は C 言語の整数演算の動作をモデル化し、KeepMSB は多数の DSP デバイスの動作をモデル化します。丸め手法の種類によって、必要なオーバーヘッド コードの量が異なります。RoundingMethod プロパティを Floor に設定 (2 の補数切り捨てと同等の設定) すると、丸めの実装が最も効率的になります。同様に、オーバーフローを処理する標準的な方法は、モジュロ演算を使用してラップすることです。他のオーバーフローの処理方法は過剰なロジックを作成します。可能な限り、OverflowActionWrap に設定してください。

MATLAB コードベスト プラクティス生成した C コード
% Code being compiled

function y = adder(a,b)
  y = a + b;
end

With types defined with
default fimath settings:

T.a = fi([],1,16,0);
T.b = fi([],1,16,0);

a = cast(0,'like',T.a);
b = cast(0,'like',T.b);

問題点

飽和オーバーフロー、最も近い整数への丸めおよび完全精度の算術を実装するために追加のコードが生成される。

int adder(short a, short b)
{
  int y;
  int i;
  int i1;
  int i2;
  int i3;
  i = a;
  i1 = b;
  if ((i & 65536) != 0) {
    i2 = i | -65536;
  } else {
    i2 = i & 65535;
  }

  if ((i1 & 65536) != 0) {
    i3 = i1 | -65536;
  } else {
    i3 = i1 & 65535;
  }

  i = i2 + i3;
  if ((i & 65536) != 0) {
    y = i | -65536;
  } else {
    y = i & 65535;
  }

  return y;
}

コンパイルするコード

function y = adder(a,b)
  y = a + b;
end

プロセッサの種類に一致した fimath 設定で定義された型の場合:

F = fimath(...
 'RoundingMethod','Floor', ...
 'OverflowAction','Wrap', ...
 'ProductMode','KeepLSB', ...
 'ProductWordLength',32, ...
 'SumMode','KeepLSB', ...
 'SumWordLength',32);

T.a = fi([],1,16,0,F);
T.b = fi([],1,16,0,F);
a = cast(0,'like',T.a);
b = cast(0,'like',T.b);

修正法

生成されるコードをより効率的にするには、使用するプロセッサの種類に一致した固定小数点の算術設定を選択します。

int adder(short a, short b)
{
  return a + b;
}

組み込み関数のより効率的な固定小数点の実装への置換

一部の MATLAB 組み込み関数は、固定小数点の実装向けにより効率的にすることができます。たとえば、組み込み関数をルックアップ テーブルの実装で置き換えたり、反復的なシフト加算の演算のみを必要とする CORDIC の実装で置き換えたりできます。

除算演算の再実装 (可能な場合)

多くの場合、除算はハードウェアによって完全にサポートされておらず、処理が低下する場合があります。アルゴリズムで除算が必要な場合、以下のオプションのいずれかで置き換えることを考慮してください。

  • 分母が 2 のべき乗の場合はビット シフトを使用。たとえば、x/8 の代わりに bitsra(x,3) を使用します。

  • 分母が定数の場合は逆数で乗算。たとえば、x/5 の代わりに x*0.2 を使用します。

浮動小数点変数の排除

コードをより効率的にするため、浮動小数点変数を排除します。唯一の例外はループのインデックスです。これは、通常、インデックスは整数型になるためです。