2012年7月2日月曜日

FPGA FMトランスミッタ: 15kHzローパスフィルタ その2

FIRフィルタIPコアを使わずにつくるローパスフィルタ
FIR Compilerを使わずに、乗算器やメモリを組み合わせたローパスフィルタです。見た目はシンプルながら、動作はまるでパズルのようです。ここでは、頂きもののverilogコードに直通機能(l15k_thru)を追加(改悪?)しました。JA2SVZ様、ご提供頂きましてありがとうございました。


全体構成
verilogを眺めるだけでは動作を理解できなかったので、ブロック図を書き起こしてみました。図の書き方は独自様式的なのでわかりにくいかもしれません。
入力信号はまずリングバッファに書き込まれます。今回は256タップなので過去256個分のデータを記録して行きます。入出力のサンプルレートは同一なので、1サンプルごとにリングバッファのデータとフィルタ係数について256回の積和演算を行います。この処理は、リソースの節約のため1個の乗算器multiplierでL/Rチャンネルを時分割で処理しています。



タイミング関係
Verilogを見れば一目瞭然なんでしょうが、私自身の理解が深まらないので波形も示します。

(1)ステートカウンタ
"state conters and control signals"で動作の基本となるクロック信号を作っています。
state512は、クロック信号 clk(512fs)毎にカウントアップして512回でゼロにリセットされるので、その周期は音声信号1サンプルの等しいです。
ring256は、state512がリセットされるたびにカウントアップし、256回目でゼロにリセットされます。ring256の周期は音声信号256サンプルに等しいです。
init_cycleは、ring256が初回に256カウントするときに0に変化します。
state512とかring256という命名は、わかりやすいですね!


(2)リングバッファの書き込み
L/Rの入力信号を束ねて36ビット幅としてリングバッファに書き込みます。アドレスはring256の値を使い、state512 = 5 の時に書き込み(write enable)します。


(3)リングバッファの読み出し
デジタルフィルタとしての処理を行うため、ring256で指定されたアドレスを先頭に256個のデータをを読み出し、それぞれに対してデジタルフィルタ係数との積和演算を行います。リングバッファの読み出しアドレスは (state512[8:1] + ring256) % 256 で表されます。


(4)積和演算
L/Rチャンネルを一つの乗算器で処理するため、その入出力をマルチプレクサで切り替えて時分割で処理します。入力信号と係数の乗算結果をアキュムレータで総和をとるため、まず acc_init = (state512 == 5) || (state512 == 6)クリアしたのち256回の総和の計算を行い、acc_out = (state512 == 5) で出力を確定させます。


ここで、タイミング関係が複雑だなあと感じたのがリングバッファ、係数ROM、乗算器レイテンシです。レイテンシはそれぞれ、リングバッファ2、係数ROM2、乗算器3です。データ処理の時間関係を図にしてみました。(合っているか心配ですが・・・)

2012.7.2追記
FIRフィルタの処理は動作速度のボトルネックになりがちとのことで、動作速度を確実に確保する手段としてレイテンシを導入(追加)しているそうです。動作速度を稼げる代償として制御がわかりにくくなっています。


データ処理の時間関係

(5)アキュムレータ出力のサチュレーション処理
"output data register"では、アキュムレータ出力の36ビットを24ビットに丸める・・・というか切り捨てる際にサチュレーション処理を行っています。サチュレーションとは、計算結果がオーバーフローして24ビットの範囲に収まらなかった場合は、24ビットの正負いずれかの最大値を出力させる処理です。


2012.7.2追記
入出力24ビットなのにアキュムレータが32ビットなのは、256個のデータの積和を行うため8ビットを足さないと情報の欠落が起こるためだそうです。
また、32ビットから24ビットへのまるめ処理とサチュレーションは無関係。フィルタでは、入力信号の波形によって出力にどのような信号が出てくるか完全に予測できないため、最上位2ビットでサチュレーションを行いフルスケールの50%以上の信号を50%にクリップさせているとのことです。



lpf15kコード

module lpf15k(
  l15k_l_i, l15k_r_i, l15k_l_o, l15k_r_o,
  clk, reset, l15k_thru
  );

  input clk;
  input reset;
  input l15k_thru;
  input [23:0] l15k_l_i;
  input [23:0] l15k_r_i;
  output [23:0] l15k_l_o;
  output [23:0] l15k_r_o;

  reg [8:0] state512;
  reg [7:0] ring256;
  reg init_cycle;
  wire acc_init;
  wire acc_out;
  wire sel_1 ;
  wire [7:0] COEF_ADDR;
  wire [17:0] COEF_DATA;
  wire [7:0] RING_RADDR;
  wire [35:0] RING_RDATA;
  wire [7:0] RING_WADDR;
  wire [35:0] RING_WDATA;
  wire [0:0] RING_WEN;
  wire [17:0] mul_in1;
  wire [17:0] mul_in2;
  wire [35:0] mul_out;
  wire [31:0] acc_in;
  reg [31:0] acc1;
  reg [31:0] acc2;
  reg [23:0] l15k_l_o;
  reg [23:0] l15k_r_o;

  ////   state counters and control signals   ////
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state512 <= 0;
      ring256 <= 0;
      init_cycle <= 1;
    end
    else if (state512 == 511) begin
      state512 <= 0;
      if (ring256 == 255) begin
        ring256 <= 0;
        init_cycle <= 0;
      end
      else
        ring256 <= ring256 + 1;
    end
    else
      state512 <= state512 + 1;
  end

  assign sel_1 = ~state512[0];
  assign acc_init = (state512 == 5) || (state512 == 6);
  assign acc_out = (state512 == 5) && (~init_cycle);
  assign RING_WEN = (state512 == 5);

  ////   ring-buffer RAM and coefficient ROM address generation   ////
  assign COEF_ADDR = state512[8:1];
  assign RING_WADDR = ring256;
  assign RING_RADDR = (state512[8:1] + ring256) % 256;
  
  ////   input data acquisition to ring-buffer RAM   ////
  assign RING_WDATA = {l15k_r_i[23:6], l15k_l_i[23:6]};

  ////   ring-buffer RAM and coefficient ROM read, input MUX and multiplier   ////
  assign mul_in1 = COEF_DATA;
  assign mul_in2 = (sel_1)? RING_RDATA[17:0] : RING_RDATA[35:18];
  assign acc_in = {{4{mul_out[34]}}, mul_out[34:7]};

  ////   accumulator   ////
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      acc1 <= 0;
      acc2 <= 0;
    end
    else
      if (~sel_1)
        acc1 <= (acc_init)? acc_in : acc1 + acc_in;
      else
        acc2 <= (acc_init)? acc_in : acc2 + acc_in;
  end

  ////   output data register   ////
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      l15k_l_o <= 0;
      l15k_r_o <= 0;
    end
    else if (l15k_thru) begin
      l15k_l_o <= l15k_l_i;
      l15k_r_o <= l15k_r_i;
    end
    else
      if (acc_out) begin
        // left channel
        if (acc1[31:30] == 2'b01)
          l15k_l_o <= 8388607;    // 24-bit positive max
        else if (acc1[31:30] == 2'b10)
          l15k_l_o <= -8388608;    // 24-bit negative max
        else
          l15k_l_o <= acc1[30:6];
        // right channel
        if (acc2[31:30] == 2'b01)
          l15k_r_o <= 8388607;    // 24-bit positive max
        else if (acc2[31:30] == 2'b10)
          l15k_r_o <= -8388608;    // 24-bit negative max
        else
          l15k_r_o <= acc2[30:6];
      end
  end

  ////   memory and multiplier instantiation (Xilinx)   //// 
  lpf15k_coef U1 (
    .clka(clk),
    .rsta(reset),
    .addra(COEF_ADDR),
    .douta(COEF_DATA)
    );

  lpf15k_rbuf U2 (
    .clka(clk),
    .wea(RING_WEN),
    .addra(RING_WADDR),
    .dina(RING_WDATA),
    .clkb(clk),
    .rstb(reset),
    .addrb(RING_RADDR),
    .doutb(RING_RDATA)
    );

  mul_p U3 (
    .clk(clk),
    .sclr(reset),
    .a(mul_in1),
    .b(mul_in2),
    .p(mul_out)
    );

endmodule


Block Memory Generator(ring-buffer RAM)の設定
リングバッファ用デュアルポートRAMです。読み出しのレイテンシは2クロックです。
Total Port B Read Latency (From Rising Edge of Read Clock): 2 Clock Cycles










Block Memory Generator(coefficient ROM)の設定

係数用ROMです。読み出しのレイテンシは2クロックです。
Total Port A Read Latency (From Rising Edge of Read Clock): 2 Clock Cycles










Multiplierの設定

乗算器です。演算のレイテンシは3クロックです。







0 件のコメント:

コメントを投稿