fract16型の余弦波と正弦波を生成するC++のクラスです。
まずはクラス宣言から。
// すべてのDDS型の先祖になる抽象型
class COscillator{
protected:
unsigned int state[2];
// state[0]:位相
// state[1]:周波数
// 位相は32bitで2πをあらわす
// 周波数は1サンプルごとの位相進角をあらわす。
public:
COscillator( unsigned int freq );
virtual void setFreq( unsigned int freq ); // 位相の強制変更
virtual void setPhase( unsigned int phase ); // 周波数の強制変更
virtual void run ( fract16 sin[], fract16 cos[], unsigned int count )=0;
};
// 三角関数の加法公式によるDDS
class CDDS: public COscillator{
public:
CDDS( unsigned int freq );
virtual void run ( fract16 sin[], fract16 cos[], unsigned int count );
};
COscillatorは抽象型なので実装するのはCDDSの方です。
CDDS::runメソッドはアセンブリ言語によるルーチンを呼び出すだけです。ルーチンのアルゴリズムは2191空挺団の関数表を工夫するで説明した2分割表と三角関数の和の公式を使うものです。今回、せっかくBlackfinを使うのでデュアル・ロードとデュアル演算を活用してみました。
また、32ビット・ロード1回でcosとsinの表の値を取得できるよう表の形を工夫しています。ループの中にnop;が二回続けて現れますが、これはストール・サイクルを明示的に埋めるものです。このようにしておけばあとで最適化を行うときに便利です。なお、3ヶ月間無料で使えるお試し版ならば、パイプラインのどこでどのようなストールが発生しているのか簡単に知ることができます。
// parameter list
// 符号なし整数を位相として受け取り、振幅を整数で返す
#define COS 1 // 余弦波出力テーブル
#define SIN 2 // 正弦波出力テーブル
#define COUNT 3 // 生成するサンプル数
#define STATE 4 // 位相の内部状態と周波数を含むunsigned int * 2の構造体
// void ddsbody( fract16 cos[], fract16 sin[], int count, unsigned int satate[] );
// cossinの要素はそれぞれ32ビット。各ビットは下位にcos、上位にsin
// の値を符号付16ビット固定小数点の値として収めている。
.section L1_data_a;
.byte2 cos_sina[512]="cossina.txt";
.var extmask=0x1808; // pos:24, length:8
.section L1_data_b;
.byte2 cos_sinb[512]="cossinb.txt";
// M0 : cos_sina 上位8ビットによる配列ベース
// M1 : cos_sinb 下位8ビットによる配列ベース
// I2 : 上位8ビットによる配列オフセット
// I3 : 下位8ビットによる配列オフセット
// I0 : cos
// I1 : sin
// R0 : phase
// R1 : freq
// R7 : 0x00ff
.section program;
.global _ddsbody;
_ddsbody:
link 0;
[--sp] = (r7:4, p5:3); // レジスタ退避
m0.H =cos_sina;
m0.L =cos_sina;
m1.H =cos_sinb;
m1.L =cos_sinb;
p0=[fp+20]; // 内部状態のアドレス
p5=r2; // count
r7=0x01fe; // r7 : 0x00ff
i0=r0; // p5 : COS出力配列
i1=r1; // i1 : SIN出力配列
p4.L=extmask;
p4.H=extmask;
r7.L=0x1008; // pos:16, length 8;
loop DDS lc0=p5;
r0=[p0]; // phase
r1=[p0+4]; // freq
r2.H = 0;
r6 = [p4];
loop_begin DDS;
// 16bit位相量を8bitのインデックスに分解
r2 = extract(r0, r6.L)(z); // 上位8bit
r3 = extract(r0, r7.L)(z); // 下位8bit
r2 = r2 << 2; // オフセットに変換
r3 = r3 << 2; // オフセットに変換
i2 = r2; // 上位8bitによるオフセット
i3 = r3; // 下位8bitによるオフセット
r0 = r0 + r1(ns); // 位相のアップデート
nop;
nop;
i2 += m0; // 上位8bitによる要素アドレス
i3 += m1; // 下位8bitによる要素アドレス
r4 = [i2] || r5 = [i3]; // cossin(h), cossin(L)
// cos(a+b)=cos(a)cos(b)-sin(a)sin(b)
// sin(a+b)=sin(a)cos(b)+cos(a)sin(b)
a1 =r4.L*r5.L, a0 =r4.H*r5.L; // a1:cos, a0:sin
r6.H=(a1-=r4.H*r5.H), r6.L=(a0+=r4.L*r5.H); //.H:cos, .L:sin
w[i0++] = r6.H;
w[i1++] = r6.L || r6 = [p4];
loop_end DDS;
// 更新したPHASEを戻す
[p0] = r0; // phase
(r7:4, p5:3) = [sp++]; // レジスタ復帰
unlink;
rts;
_ddsbody.end:
ループボディーのサイズは16行です。デュアル・アクセスが効果的に行えるようにメモリー配置を工夫すれば、16サイクルで実行できます。600MHzで動作させると、このルーチンはほぼ40MHzの信号を生成できます。