2018年11月4日日曜日

FPGAでΔΣ型ADCの実験(その2:実装)

ずいぶん前にΔΣ型ADCのシミュレーションを行いました。
ようやくその実装ができましたので備忘録です。

今回はこちらを参考にさせていただきました。

構成

実装した構成を下図に示します。
前回シミュレーションしたものと回路は異なりますが、構成要素としてはほぼ同じとなります。



LVDSの+側にアナログの電圧を入力し、-側にはCR回路を接続します。
ΔΣ型ADCには1bitの量子化器(ADC)と積分器が必要です。
ここではLVDSの差動入力を1bitの量子化器として利用し、CR回路が積分回路となります。

DFFの出力はΔΣ変調された信号が出力されます。
そのままでは1ビット幅のデータしか得られないので、デシメーションフィルタを通して多ビット幅のデータにします。今回はデシメーションフィルタとしてCICフィルタを実装してみました。
CICフィルタの出力には高い周波数成分が含まれるため、さらにLPFを通して余計な高周波成分を取り除いてあげます。

実装にはFPGAはわれらがMAX10ボード(CQ出版社 FPGA電子工作スーパーキットのボード)を使用します。
MAX10の一部にはADCが内蔵されており、今回使用したボードにもADCが内蔵されていますが、ガン無視して一からΔΣ型ADCを実装します。


ΔΣ変調部

ΔΣ変調部のHDLは非常に簡単なもので、クロックの立ち上がりのタイミングでLVDSの入力をサンプルするだけです。

//----------------------------------------------------------
// Delta Sigma Modulator
//----------------------------------------------------------
// DFF
reg ovs_ff;

always @(posedge clk_div, negedge res_n)
begin
 if(~res_n)
  ovs_ff <= 1'b0;
 else
  ovs_ff <= adc_in;
end

assign adc_fb = ovs_ff;

抵抗とコンデンサの値については計算すれば最適な値が求められそうですが、今回はカットアンドトライでそれなりに動作するような値を決めています。

デシメーションフィルタ

ΔΣ変調された信号は1ビットのデータですので、これを多ビットのデータにする必要があります。
デシメーションフィルタを使用することで、高速でサンプリングされた1ビットのデータを、サンプリング周波数を下げてビット数を増やすサンプリング周波数変換を行います。
今回はデシメーションフィルタとしてCIC(Cascade Integrated Comb)フィルタを使用しました。 CICフィルタについては下記が参考になります。
https://dspguru.com/dsp/tutorials/cic-filter-introduction

CICフィルタは比較的簡単に実装できます。CICフィルタの構成を下図に示します。



左側に3つ並んでいるのが積分器、右側に3つ並んでいるのが微分器です。
真ん中にあるのがリサンプリングです。下矢印はサンプリング周波数を下げていることを示しています。
この図は積分器および微分器のタップ数を3とした例を示しています。

最終的な最終的な出力のビット数は下記のようになります。
\[
 B_{out} = \lceil N \log_2{RM} + B_{in} \rceil
\]
ここでBoutは出力のビット数、Nは積分器及び微分器のタップ数、Rはリサンプリング比、Mは微分器の遅延(今回は1です)、Binは入力のビット数です。

積分器の実装

まずは積分器を実装します。
実装の際にはこちらのページを参考にさせていただきました。


//-----------------------------------
// Integrator unit for CIC filter
//-----------------------------------

module cic_integration(
    clk,            // clock in
    res_n,        // reset negative
    in_data,        // input data
    out_data        // output data
);

//--- Parameter declarations ----
parameter TAP_NUM = 4;
parameter BIT_W = 9;

//---- IO declarations ----------
input clk;
input res_n;
input [BIT_W-1:0] in_data;
output [BIT_W-1:0] out_data;
    
//---- local declarations -------
reg [BIT_W-1:0] data[0:TAP_NUM-1];

integer i;

//---- module body --------------
always @(posedge clk, negedge res_n)
begin
    if(~res_n) begin
        for(i=0; i<TAP_NUM; i=i+1)
            data[i] <= {BIT_W{1'b0}};
    end else begin
        data[0] <= in_data + data[0];
        
        for(i=1; i<TAP_NUM; i=i+1)
            data[i] <= data[i-1] + data[i];
    end
end

assign out_data = data[TAP_NUM-1];

endmodule


特に難しいことはないです。

微分器の実装

次に微分器を実装します。

 //-----------------------------------
// Dffirentiation unit for CIC filter
//-----------------------------------

module cic_differentiation(
    clk,            // clock in
    res_n,        // reset negative
    in_data,        // input data
    out_data        // output data
);

//--- Parameter declarations ----
parameter TAP_NUM = 4;
parameter BIT_W = 9;

//---- IO declarations ----------
input clk;
input res_n;
input [BIT_W-1:0] in_data;
output [BIT_W-1:0] out_data;
   
//---- local declarations -------
reg [BIT_W-1:0] data[0:TAP_NUM-1];
reg [BIT_W-1:0] delay_data[0:TAP_NUM-1];

integer i;

//---- module body --------------
always @(posedge clk, negedge res_n)
begin
    if(~res_n) begin
        for(i=0; i<TAP_NUM; i=i+1) begin
            data[i] <= {BIT_W{1'b0}};
            delay_data[i] <= {BIT_W{1'b0}};
        end
    end else begin
        delay_data[0] <= in_data;
        data[0] <= in_data + ~delay_data[0] + {{BIT_W-1{1'b0}},1'b1};
       
        for(i=1; i<TAP_NUM; i=i+1) begin
            delay_data[i] <= data[i-1];
            data[i] <= data[i-1] + ~delay_data[i] + {{BIT_W-1{1'b0}},1'b1};
        end
    end
end

assign out_data = data[TAP_NUM-1];

endmodule


CICフィルタの実装

そして積分器と微分器を組み合わせてCICフィルタを実装します。
微分器はリサンプリングした後のクロックで動かしています。

//--------------------------
// CIC Filter
//--------------------------

module cic_filter(
 clk,     // clock input
 res_n,    // reset_n input
 sig_in,    // signal input (1bit)
 sig_out,    // signal output
 clk_out    // clock out
);

//---- Parameter declaration -----------------------
// Data bit + TAP_NUM * log2(RE_SAMP * DELAY_NUM) 
parameter TAP_NUM = 4;
parameter BIT_W = 9;
parameter RE_SAMP = 2;

//---- module IO declaration -----------------------
input clk;
input res_n;
input sig_in;      // only 1bit
output [BIT_W-1:0] sig_out;
output clk_out;

//---- clocl divider -------------------------------
reg [RE_SAMP-1:0] counter;
wire clk_4;
assign clk_4 = (counter == {RE_SAMP{1'b0}}) ? 1'b1 : 1'b0;
assign clk_out = clk_4;

always @(posedge clk, negedge res_n)
begin
 if(~res_n)
  counter <= {RE_SAMP{1'h0}};
 else
  counter <= counter + {{RE_SAMP-1{1'b0}},1'h1};
end

//---- Integrator ----------------------------------
wire [BIT_W-1:0] int_out;
wire [BIT_W-1:0] in_data;

assign in_data = {{BIT_W-1{1'b0}},sig_in};

cic_integration #(TAP_NUM,BIT_W) cint(
 .clk (clk),
 .res_n (res_n),
 .in_data (in_data),
 .out_data (int_out)
);

//---- Differentiator------------------------------
cic_differentiation #(TAP_NUM,BIT_W) cdiff(
 .clk (clk_4),
 .res_n (res_n),
 .in_data (int_out),
 .out_data (sig_out)
);

endmodule



LPF

LPFとして移動平均フィルタを実装しました。
単純に平均する時間分だけシフトレジスタを用意しておき、その平均を算出します。

//--------------------------------------------------------
// moving avarage filter
//--------------------------------------------------------

module mav_filter(
    clk,                    // clk
    res_n,                // reset_n
    in_data,                // input data
    out_data
);

//---- parameter declaration -----------------------------
parameter TAP_NUM = 8;
parameter BIT_W = 9;
parameter SHIFT_NUM = 3;

//---- IO declaration  -----------------------------------
input clk;
input res_n;
input [BIT_W-1:0] in_data;
output [BIT_W-1:0] out_data;

//---- local register and wire ---------------------------
integer i;

reg [BIT_W-1:0] data[0:TAP_NUM-1];
reg [BIT_W+SHIFT_NUM-1:0] add_data;
reg [BIT_W+SHIFT_NUM-1:0] add_result;

//---- module declaration --------------------------------
always @(posedge clk, negedge res_n)
begin
    if(~res_n) begin
        for(i=0; i<TAP_NUM; i=i+1) begin
            data[i] <= {BIT_W{1'b0}};
            add_data <= {BIT_W+SHIFT_NUM{1'b0}};
        end
    end else begin
        data[0] <= {{SHIFT_NUM{1'b0}},in_data};
        add_data = in_data;
        for(i=1; i<TAP_NUM; i=i+1) begin
            data[i] <= data[i-1];
            add_data = add_data + data[i];
        end    
    end
end

assign out_data = add_data[BIT_W+SHIFT_NUM-1:SHIFT_NUM];

endmodule


動作確認

正弦波信号を入力し、ADCの出力信号を直接signaltapで取り出して動作を確認しました。 DACを構築して出力してもいいですが、DACの特性も影響するため、今回は直接観測しています。

実装したパラメータは下記のとおりです。
・ΔΣ変調オーバサンプリングクロック:6MHz (48MHz/8)
・CICフィルタ積分器・微分器タップ数:4
・リサンプリング比:2^2=4
・移動平均フィルタタップ数:32
入力が1ビットなので、出力ビットは
\[
 B_{out} = \lceil 4 \log_2{(4 \times 1)} + 1 \rceil = 9
\]
 です。

LVDS入力およびフィードバック用の出力は2.5Vとしています。

最終的なADCのサンプリング周波数としては6MHzをリサンプリング比4で割った1.25MHzとなります。

テストモジュールを下記のように実装しました。

//------------------------------------------------------------
// Delta-Sigma ADC 
//------------------------------------------------------------

// clock
`define CYCLE_1SEC 48000000

//--------------------
// Top of the FPGA
//--------------------
module FPGA
(
 input wire clk,    // 48MHz Clock
 input wire res_n,    // Reset Switch
 input wire adc_in,
 output wire adc_fb,
 output wire delta_sigma_out,
 output wire clk_div
);

//---- parameter declaration ---------------------------------
parameter CIC_TAP_NUM = 4;
parameter BIT_W = 9;
parameter MAF_TAP_NUM = 32;
parameter MAF_SHIFT_NUM = 5;

//----------------------
// Divider
//----------------------
reg [2:0] cnt_div;

assign clk_div = (cnt_div == 3'd0) ? 1'b1 : 1'b0;

always @(posedge clk, negedge res_n)
begin
 if(~res_n)
  cnt_div <= 3'd0;
 else
  cnt_div <= cnt_div + 3'd1;
end

//----------------------------------------------------------
// Delta Sigma Modulator
//----------------------------------------------------------
// DFF
reg ovs_ff;

always @(posedge clk_div, negedge res_n)
begin
 if(~res_n)
  ovs_ff <= 1'b0;
 else
  ovs_ff <= adc_in;
end

assign adc_fb = ovs_ff;

//---- CIC filter ------------------------------------------
wire [BIT_W-1:0] filter1_out;
wire clk4;

cic_filter #(CIC_TAP_NUM,BIT_W) filter1(
 .clk  (clk_div),    // clock input
 .res_n (res_n),    // reset_n input
 .sig_in (ovs_ff),   // signal input (1bit)
 .sig_out (filter1_out),  // signal output (32bit)
 .clk_out (clk4)    // clock out
);

//--------------------------------
// moving average filter
//--------------------------------
wire [BIT_W-1:0] filter2_out;

mav_filter #(MAF_TAP_NUM,BIT_W,MAF_SHIFT_NUM) filter2(
 .clk  (clk4),
 .res_n (res_n),     // reset_n
 .in_data (filter1_out),   // input data
 .out_data (filter2_out)
);

assign delta_sigma_out = filter2_out[3];
 
endmodule


実際にMAX10ボードに書き込み、動作させました。
入力信号としては振幅2.4V(オフセット1.2V)、2kHzの正弦波を使用しました。

SignalTapで取得したCICフィルタの出力とLPFの出力を下記に示します。


CICフィルタの出力を見ると、正弦波っぽい波形が得られているものの、ノイズのようなものが乗っていることがわかります。
 これに対して、LPFを通した後の波形を見るときれいな正弦波が得られていることがわかります。
無事に入力信号をAD変換することができていそうです。

出力は9ビットですが、振幅が200程度になっているのは1ビットは符号ビットとなっているためと思われます。(微分器は2の補数で計算している)
負の電圧は入力できないので、実質8ビットの出力となります。
振幅2.4Vなので出力ビットの最大値は 2.4V/2.5V * 255 ≒ 244程度になるはずです。
実際の出力電圧の振幅を見ると2.24V程度でした。 2.24V/2.5 * 255 ≒ 228 が程度となります。LPFの出力はLPFを通していますので、そのために振幅が小さくなっていると思われます。

積分器であるCR回路の定数と各フィルタの特性をいじればもっときれいな波形が得られると思いますが、精度のいらない用途ではこのままでも十分使用できるかなと思います。

0 件のコメント:

コメントを投稿