C または C++ を使用して制御アルゴリズムを記述し、オンザフライで同時にアルゴリズムを変更できます。たとえば、PID、PI、またはリードラグ タイプのコントローラーを C および C++ で記述できます。制御アルゴリズムは、乗算、加算、減算、飽和、除算など一連の算術演算で構成されます。C および C++ を使用すると、メモリ (中間データおよび命令格納用)、入力 (PID コントローラーのサンプルに必要な W および Y 入力など)、算術演算子、および出力 (サーボ モーターを制御する DAC 駆動用の PWM デューティ サイクル) を備えたステート マシンを容易に記述できます。PID 制御ループの W および Y 入力など、入力データが到達すると Math Sequencer の動作が開始し、その動作が完了すると PWM が更新されます。次の図に、PID アルゴリズムに最適化された Math Sequencer の例を示します。
LUTRAM に保存された命令アレイがマルチプレクサー A および B の選択を制御します。選択された算術演算は RMW (Read-Modify-Write) レジスタに保存する必要があります。この命令アレイは、どの SPFP 算術演算 (飽和、バイパス、乗算、または加算) が必要かも制御します。次の図に、この Math Sequencer の命令ビットの構成を示します。
クロック サイクルは増加しますが、必要な演算ハードウェア量を削減する方法があります。たとえば、 y = (a – b) の場合、 b × –1 -> b (b に –1 を掛けた値をレジスタ b に格納) の後、 a + b -> bという 2 段階の演算が必要となります。算術演算の結果はレジスタ アレイ A または B に格納されますが、これは算術演算の後に必要となる次のオペランド入力には望ましくありません。この問題を回避するには、データをレジスタ アレイ A から B へ、またはレジスタ アレイ B から A へ移動するバイパス命令が必要です。追加のアルゴリズムの演算性能を拡張し、クロック サイクルを削減するために、シフト、除算、平方根、またはその他の演算時にカスタム演算子を命令パイプラインに追加できます。この C++ ベースの Math Sequencer を使用した SPFP PID の例で必要な演算子は、飽和、バイパス、乗算、加算のみです。
Math Sequencer の C++ は非常に単純で、C で記述すると簡単に理解できます。
#include "ms.h"
void ms(float w_in, float y_in, float &pwm) {
// for register & instruction details see math_sequencer_rv2.xls (MS Excel Spreadsheet)
// A mux data
static float a_mux[] = {0, Gi, Gd, c, Gp, 0, 0, 0, 0, 0, 0, minus1, plus1, zeroc};
// B mux data
static float b_mux[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, minus1, plus1, zeroc};
// constant definitions
#pragma HLS ARRAY_PARTITION variable=b_mux complete dim=1
// load data from interface
a_mux[5] = (float) w_in; // cast variable to float
a_mux[6] = (float) y_in;
// setup instructions
unsigned short mnemonics[23] = {0x6CC2,0x5B31,0x633,0x2652,0x1662,0x3442,0xB272,0x82A1,0x7C42,0x7A11,0x93B1,0xA5A1,0xA03,0x4642,0xD8B5,0xA791,0x79A1,0xA23,0x8074,0x9084,0x7D5,0x8E5};
const short num_instr = 22;
ap_uint<16> instruction;
ap_uint<4> instr_sel; // instruction mux controls
ap_uint<4> store;
ap_uint<4> dsel_b, dsel_a;
float a_sel_data, b_sel_data; // variables for data management & results storage
float op_results; // 32 bit results
float fsat_o; // float data types needed to support saturate
instruction_loop : for (short inst_loop = 0; inst_loop < num_instr; inst_loop++) {
// split up instruction into tasks
#pragma HLS PIPELINE
instruction = (ap_uint<16>) mnemonics[inst_loop];
// split out instructions into sub blocks
instr_sel = instruction & 0x000F; // instruction select
store = (instruction & 0x00F0) >> 4; // store results where?
dsel_b = (instruction & 0x0F00) >> 8; // source B side mux from what register?
dsel_a = (instruction & 0xF000) >> 12; // source A side mux from what register?
// determine A select
a_sel_data = a_mux[dsel_a];
// determine B select
b_sel_data = b_mux[dsel_b];
switch(instr_sel) {
case 0: break; // invalid instruction
case 1: op_results = a_sel_data + b_sel_data; break; // a+b
case 2: op_results = a_sel_data * b_sel_data; break; // a*b
case 3: if((b_sel_data >= min_limit) && (b_sel_data <= max_limit)) // saturate
fsat_o = b_sel_data;
else if (b_sel_data < min_limit)
fsat_o = min_limit;
else if (b_sel_data > max_limit)
fsat_o = max_limit;
op_results = fsat_o; break;
case 4: op_results = a_sel_data; break; // bypass a
case 5: op_results = b_sel_data; break; // bypass b
// case 6: op_results = a_sel_data / b_sel_data; break; // a/b
break;
} // end instr_sel
switch(store) {
case 0: b_mux[8] = op_results; break; // yi
case 1: b_mux[7] = op_results; break; // yd
case 2: b_mux[1] = op_results; break; // pwm
case 3: b_mux[6] = op_results; break; // error
case 4: a_mux[7] = op_results; break; // pid_mult
case 5: a_mux[8] = op_results; break; // x1; a_mux
case 6: a_mux[9] = op_results; break; // x2; a_mux
case 7: b_mux[2] = op_results; break; // prev_x1
case 8: b_mux[3] = op_results; break; //prev_x2
case 9: b_mux[9] = op_results; break; // pid_addsub
case 10: b_mux[10] = op_results; break; // pid_addsub2
case 11: a_mux[10] = op_results; break; // tmp_a; a_mux
case 12: b_mux[11] = op_results; break; // tmp_b
case 13: b_mux[4] = op_results; break; // prev_yd
case 14: b_mux[5] = op_results; break; // prev_yi
case 15: break; // invalid
} // end store results
} // end instruction_loop
pwm = b_mux[1]; // PWM is a pass by reference variable
} // end math sequencer
VMC では、ユーザーが作成した C および C++ デザインの機能開発とデバッグが可能です。MATLAB
Simulink 環境での DSP 開発に一般的に使用される Simulink スコープ、ディスプレイ、データ ログ、MATLAB スクリプト、およびその他の手法に加え、従来の C および C++ デバッグ手法 (GNU デバッガーまたは Microsoft Visual C) と printf
オプションも利用できます。
マイクロコード命令の作成を簡単にするため、PID シーケンスに必要な 16 進数値の生成を支援する次のような Microsoft Excel スプレッドシートを使用しました。
ここでも、基準となる Simulink リファレンス モデルと Math Sequencer の間には機能やダイナミック レンジに相違がないことが論理シミュレーションで確認できます。
次の図に示すように、2 つの HLS #pragma 命令を C++ コードに適用することにより、次の PL 実装はクロック レート = 1∕1.567e–9 = 638.2 MHz、サンプル レート = 1∕(83 × 1.567e–9) = 7.7 MSPS となります。
Math Sequencer の長所は、アルゴリズム要件に基づいて演算子を容易に変更できることにあります。たとえば、除算演算子や平方根関数を追加してマグニチュードを計算できます。また、Math Sequencer なら後で命令シーケンスを変更する場合でもハードウェア実装を変更する必要がありません。さらに、Math Sequencer はレイテンシとのトレードオフでリソース使用量を削減できます。次の表に、HLS ツールボックスを使用した実装と C++ Math Sequencer で実装した PID 制御ループを直接比較した結果を示します。
DSP | LUT | FF | ブロック RAM | レイテンシ (クロック) | クロック (MHz) | サンプル レート (MSPS) | |
---|---|---|---|---|---|---|---|
VMC HLS ツールボックス | 5 | 565 | 505 | 0 | 69 | 472 | 6.8 |
C++ Math Sequencer | 4 | 513 | 962 | 0 | 83 |
638.2 |
7.7 |