2018年03月25日

ΣΔDAC (1)

次は、サウンド用 ΣΔDAC を設計してみよう。

ハードウェアのおさらい
icedip-dac.png

4bit DAC で スピーカ用 BTL アンプに接続。負帰還でゲイン2倍。(片側1倍)
テスト用なのに音が大きすぎるかも、そこは RI(R61) か RF(R60) で調整する。RI を N 倍にすれば、それだけ小さくなる。

とりあえずスピーカーに直結するつもりだが、LC LPF を入れたほうが良いだろう。でもそれは、音が出るというレベルをクリアした後で考える。

4 bit フルに使うつもりはない。1 bit もしくは 3 bit を予定。
クロック

外付け PLL が 使えない場合のことをまず検討。

14.314MHz をリファレンスクロックとして、325 で分周して 44.056kHz。
 325 = 5 x 5 x 13

オーバーサンプリングは、64 でだいぶまともな音らしいが、13 x 5 = 65 倍はつくれる。
だが、5 clock では、FIR フィルタが 作れない。内部 PLL で 16/5 (45.8 MHz) とかは作れる。
乗算器の上限は 50 MHz なので、これで行く。

    45.8 MHz を 16 x 65 で分周して 44.056 kHz を作る。オーバーサンプリングは 65 で 16 までのタップ数の FIR フィルタを使う。

ここまで決まった。ところで、NTSC の方、エンコーダーは14.314MHz を そのまま使えば良い。
画像生成は、45.8 MHzを使う。乗算器ネックなので選択枝がない。整数倍ではないので、どうするのか?

分からないのであった。めんどくさくなったので、両方同時に使う場合は、44.056 kHz をあきらめて 15/5 = 3 で行くか。サンプリング周波数は 15/16 の 41.3 kHz 。NTSC の方は 14.314 の 3 倍クロック。予定とだいぶ変わってくる。

以上は、外部PLL が使えない場合。使えれば 44.056 kHz がいける。


オーバーサンプリングが非常に重要な部分で頑張って作りたいのだが、ここでは最終段の ΣΔDAC を検討する。これのテストをするためには、45.8 MHz の 1/16 - 2.86 MHz サンプリングの音源が必要になる。そんなデータは、その場で作るしかない。サイン波とか単純なものを鳴らすことを考えたい。


module dac1 # (
parameter WIDTH = 16
) (
input CLK
,input I_VALID
,input [WIDTH-1:0] I_DATA
,output OUT
);

reg iv_prev;
reg [WIDTH-1:0] fraction;
reg r_out;

assign OUT = r_out;

always @(posedge CLK)
begin
iv_prev <= I_VALID;
if (~iv_prev && I_VALID)
{ r_out, fraction } <= { 1'b0 ,fraction } + { 1'b0, I_DATA };
end
endmodule

大げさに書いてきたが、ΣΔDAC 自体はこれだけ。積算値に データを加算し、桁上がりの 1 bit を出力する。
これで良いのかどうかは、実験すれば分かること。ただし、高クロックのサンプリングデータが必要である。

多ビット化

仮に 1クロックを 3 つに分けて、同じデータを出力させるとどうなるか? 000 〜 111 のビット列になるだろう。このパターンのビットの数を数える。100, 010, 001 は 1 、111 は 3 である。
これは、I_DATA を 3 倍して加算し、2bit の桁あふれを出力するのと同じことになるはず。
3bit 出力なら 7倍して加算、桁あふれは 3bit 。間違ってる気もすこしするが、これで良いのではないだろうか?

module dac3 # (
parameter WIDTH = 16
) (
input CLK
,input I_VALID
,input [WIDTH-1:0] I_DATA
,output [2:0] OUT
);

reg iv_prev;
reg [WIDTH-1:0] fraction = 0;
reg [2:0] r_out;

assign OUT = r_out;

always @(posedge CLK)
begin
iv_prev <= I_VALID;
if (~iv_prev && I_VALID)
{ r_out, fraction } <= { 3'b000 ,fraction } + { 3'b000, I_DATA }
+ { 2'b00, I_DATA, 1'b0} + { 1'b0, I_DATA, 2'b00};
end
endmodule


波形生成

ちょっとテストしたいわけだが、あまり難しいのは嫌である。決まった周波数だけ出力できる簡単なものを作りたい。

のこぎり波

2.86 MHz サンプリングなので、1kHz なら、2862 ステップである。値の範囲は、65535 。ならば 22 ずつ加算していって、2862 回出力したら、初期値に戻す。これで良いのでは?


module wave_gen (
input REFCLK // 14.314 MHz
,output [3:0] VDAC
);

reg [2:0] clk_cnt;
wire [23:0] DATA;
wire [3:0] dac_out;

dac1 dac_inpl (.CLK(REFCLK) , .I_VALID(clk_cnt > 2),
.I_DATA(DATA[23:8]), .OUT(dac_out[3]));
assign VDAC = dac_out;
reg [23:0] r_data;
assign DATA = r_data;
always @(posedge REFCLK)
begin
if (4 <= clk_cnt) clk_cnt <= 0;
else clk_cnt <= clk_cnt + 1;

if (clk_cnt == 0) begin
if (DATA > (60000 * 255)) DATA <= 10;
DATA <= DATA + (22 * 255);
end
end
endmodule


サイン波

2次元に角速度一定で円を描く。そうしたときの Y座標を拾っていく。速度ベクトルは、今の座標をもとに乗算で計算できる。誤差が蓄積して 一周したとき元の座標に戻らないだろう。でも、発散しないのであれば、それで良いではないか。あるいは、一周して 0 をまたぐときに、再初期化する。波形が歪むがそれでもいい。

(30000, 0) からスタートして、最初の速度を (0, 66) とする。次の速度は、(-66, 30000) * 0.00022 (= 66/30000) 。検証しないといけないが、こんな感じ。半径が減っていく うずまきになるならそれで良い。ただし、1000 回転で 0 になってしまうようでは、1秒も鳴らない。誤差を -10 程度に収めないと。
そうなると 24 bit 乗算ぐらい必要か? 座標も小数点以下 8bit とか。

まず乗算器の定義。コピペベースで モジュールにしておかないと、乗算器が生成できるかどうか分からなくなってくる。2個で済ませたいので、24bit x 16bit にした。

module mul_24x16 (input clk,
input en,
input [23:0] a,
input [15:0] k,
output [23:0] c
);
reg [39:0] prod;
assign c = prod[39:16];
reg [23:0] a_reg;
reg [15:0] b_reg;
wire [39:0] mult_out = $signed(a_reg) * $signed(b_reg);

always@(posedge clk)
begin
if (en) begin
a_reg <= a;
b_reg <= k;
prod <= mult_out;
end
end
endmodule


次にサイン波の部分。のこぎり波との差分を書くと、

reg [23:0] X = 30000 * 255;
reg [23:0] Y = 0;
assign DATA = Y[23:8];
reg [16:0] XD;
reg [16:0] YD;
reg mul_en;
wire [23:0] result;

mul_24x16 mul_inpl (.clk(REFCLK), .en(mul_en),
.a( (clk_cnt == 0)?Y:X) ,
.k(66 * 234),
.c(result));
always @(posedge REFCLK)
begin
if (4 <= clk_cnt) clk_cnt <= 0;
else clk_cnt <= clk_cnt + 1;

mul_en <= (clk_cnt == 4 ) || (clk_cnt == 0);
if (clk_cnt == 0) begin
YD <= result[23:8];
end else if (clk_cnt == 1) begin
XD <= - $signed(result[23:8]);
end else if (clk_cnt == 2) begin
X <= X + $signed(XD);
Y <= Y + $signed(YD);
end
end

バグはあると思うが、だいたいこんな感じでスタートして詰めていく。

とりあえずここまで
 ・icedip2-samples-02.zip


iCEDIP ひとつめの基板: 2018/03/25 10:13 国際交換局(川崎東郵便局)から発送
iCEDIP ふたつめの基板: 2018/03/24 16:14 国際交換局(CHINA)から発送

という状況で、基板がもうすぐ届く。基板組み立ての前に、とりいぞぎ ネタを作った。コードの方はしばらくおやすみ。
posted by すz at 15:37| Comment(0) | TrackBack(0) | MachXO2
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/182792404
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック