FPGA 版 FM 音源 (66) -- FPGA 版 EG (10)

S/PDIF 送信モジュール「spdif_tx()」の Verilog ソースを下に示します。
"spdif_tx.v"

/*****************************************************/
/* S/PDIF TX module (L/R mux'ed 16/24 bit para. data */
/*                                                   */
/* 2017/01/15 Added da_ext[]                         */
/* 2013/11/30 Created by pcm1723                     */
/*****************************************************/
module spdif_tx(reset,       
                m_clk, clk_128fs_en, lrck, cs_fs48k, 
                da, da_ext,
                clk_64fs, sosf,      sof,    sob,  tx_out
                );
// Input port                
  input         reset;    // async reset
  input         m_clk;    // master clock
  input         clk_128fs_en; // clock enable for 128fs clock
  input         lrck;     // LRCK (50 % duty)
  input         cs_fs48k; // ch status fs bit ('0' for 44.1 kHz / '1' for 48 kHz)
  
  input [15:0]  da;       // L/R mux'ed MS 16 bit parallel data
  input [ 7:0]  da_ext;   // L/R mux'ed LS 8  bit parallel data
// Output port  
  output        clk_64fs; // 64fs clock output (square wave)
  output        sosf;     // Start of SubFrame
  output        sof;      // Start of Frame
  output        sob;      // Start of channel status Block
  output        tx_out;   // encoded TX output
//  
  reg           clk_64fs;
  
  localparam [7:0] BLK_LEN     = 192; // Channel Status block length
  localparam [7:0] CS_COPY_BIT = 2;   // Channel Status Copy bit (CS2)
  localparam [7:0] CS_FS1_BIT  = 25;  // Channel Status fs bit 1 (CS25)
  localparam       DEBUG       = 0;
  
  wire [30:0] sr_para_in;   // 31-bit shift register parallel data input
  wire [3:0]  mask_para_in; // 1st edge mask shift register parallel data input
  
  wire       B_pre;
  wire       W_pre;
  wire       M_pre;
  wire       ch_status;
  wire       Lch;
  wire       Rch;
  
  reg [7:0]  blk_cnt;    // Channel Status bit counter in the Block (0..191)
  reg        parity;     // parity calculation register
  reg [30:0] sr;         // 31-bit shift register for the SubFrame data but parity
                         // {sr, sr_out} forms 32-bit shift register
  reg        sr_out;     // parity of prev SubFrame loaded at parallel load time,
                         // else, serial output of 32-bit shift register
  reg [3:0]  edge1_mask; // biphase encoding 1st edge mask
  reg [1:0]  lrck_dly;   // 64fs clk delay element(s) for LRCK
  reg        sr_out_dly; // 128fs clk delay for biphase encoding
  
  reg [4:0]  bit_cnt;    // bit position counter in the SubFrame (0..31)
  
  assign Lch =   lrck_dly[0];   // L channel indication
  assign Rch = (~lrck_dly[0]);  // R channel indication
 
 // preamble indication
  assign B_pre = ( Lch &  (sob) ); // B preamble (L_ch & Start_of_Block)
  assign W_pre = ( Rch          ); // W preamble (R_ch)
  assign M_pre = ( Lch & (~sob) ); // M preamble (L_ch & not Start_of_Block)

// channel status
  assign ch_status = (
                         (CS_COPY_BIT == blk_cnt)  // Copy bit
                      | ((CS_FS1_BIT  == blk_cnt) & cs_fs48k)  // fs bit1
                     );
                     
  assign tx_out = sr_out_dly;
  
// 31-bit shift register parallel data input at Start of SubFrame
  assign sr_para_in = { 
                        ch_status, // b30 (C bit) 
                        1'b0,      // b29 (U bit)
                        1'b0,      // b28 (V bit)
                        da,        // b27..b12 (AUDIO data MS 16 bit)
                        da_ext,    // b11..b4  (AUDIO data LS  8 bit)
                                   // preamble
                        M_pre,     // b3 (omit 2nd edge if not 'M')
                       ~M_pre,     // b2 (omit 2nd edge if 'M')
                        1'b1,      // b1
                        1'b0       // b0 (omit 2nd edge always)
                      };
                      
// biphase encoding first edge mask shift register parallel data input
  assign mask_para_in = {
                         ~B_pre,  // b3 (omit 1st edge if 'B')
                          B_pre,  // b2 (omit 1st edge if not 'B')
                          1'b0,   // b1 (omit 1st edge always)
                          1'b1    // b0
                        };

// Start of Frame (rising edge of LRCK)
  assign sof  = ((~lrck_dly[1]) & (lrck_dly[0]));
  
// Start of SubFrame (both edge of LRCK)
  assign sosf = ( (lrck_dly[1]) ^ (lrck_dly[0]));
  
// Start of Block
  assign sob  = ( 8'h00 == blk_cnt);
 
// 64fs clk divider 
  always @(posedge reset or posedge m_clk)
    begin
      if (reset)
        clk_64fs <= 1'b0;
      else
        if (clk_128fs_en)
          clk_64fs <= (~clk_64fs);
    end // always
  
// LRCK delay(s) 
  always @(posedge reset or posedge m_clk)
    begin
      if (reset)
        lrck_dly <= 2'b00;
      else
        if (clk_128fs_en & clk_64fs)
          lrck_dly <= {lrck_dly[0], lrck};
    end // always
  
// parity calculation 
  always @(posedge reset or posedge m_clk)
    begin
      if (reset)
        parity <= 1'b0;
      else
        if (clk_128fs_en & clk_64fs)
          parity <= ( (sosf) ? (1'b0) : (parity ^ sr[0]) );
    end // always
  
// Channel Status bit number counter in the Block
  always @(posedge reset or posedge m_clk)
    begin
      if (reset)
        blk_cnt <= (BLK_LEN - 1'b1);
      else
        if (clk_128fs_en & clk_64fs & sosf & (~sof))
          begin
            if ((BLK_LEN - 1'b1) == blk_cnt) // max count
              blk_cnt <= 8'h00; // reset to '0'
            else                // increment counter
              blk_cnt <= (blk_cnt + 1'b1);
          end // if (clk_128fs_en & clk_64fs & sof) ...
    end // always
    
// bit counter in the SubFrame (debug purpose only, not used in timing generation) 
  always @(posedge reset or posedge m_clk)
    begin
      if (DEBUG)
        begin
          if (reset)
            bit_cnt <= 5'd31;
          else
            if (clk_128fs_en & clk_64fs)
              bit_cnt <= ( (sosf) ? (5'd31) : (bit_cnt + 1'b1) );
        end // if (DEBUG)
    end // always
    
// 32 bit shift register / biphase 1st edge mask
  always @(posedge reset or posedge m_clk)
    begin
      if (reset)
        begin
          {sr, sr_out} <= 31'h00_00_00_00;
          edge1_mask   <= 4'b0000;
        end
      else if (clk_128fs_en & clk_64fs)
        begin // parallel input at Start of SubFrame, else shift
          {sr, sr_out} <= ( (sosf) ? {sr_para_in, parity} : {1'b0, sr} );
          edge1_mask   <= ( (sosf) ? (mask_para_in)       : {1'b1, edge1_mask[3:1]} );
        end
	   else // hold
		  begin
          {sr, sr_out} <= {sr, sr_out};
          edge1_mask   <= edge1_mask;
		  end
    end // always
    
// biphase encoder
  always @(posedge reset or posedge m_clk)
    begin
      if (reset)
        sr_out_dly <= 1'b0;
      else
        if (clk_128fs_en)
          begin
            if (clk_64fs) // 1st edge
              sr_out_dly <= (edge1_mask[0]) ^ sr_out_dly;
            else          // 2nd edge
              sr_out_dly <= (sr_out) ^ sr_out_dly;
          end // if (clk_128fs_en) ...
    end //  always
      
endmodule

このモジュールでは、

  • 非同期リセット (reset)
  • マスター・クロック (m_clk)
  • 128 fs クロック・イネーブル信号 (clk_128fs_en) (6.144 MHz)
  • LR クロック (lrck) (デューティー 50 % の 48 kHz 方形波)
  • チャネル・ステータス中の fs = 48 kHz を示すビット (cs_fs48k)
  • 16 ビット・パラレルのオーディオ・データ (da[15:0]) (L/R マルチプレクス)
  • 8 ビット・パラレルのオーディオ・データ拡張部 (da_ext[7:0]) (L/R マルチプレクス)

を入力とし、

  • 64 fs クロック (clk_64fs) (デューティー 50 % の 3.072 MHz 方形波)
  • サブフレーム開始信号 (sosf)
  • フレーム開始信号 (sof)
  • ブロック開始信号 (sob)
  • SPDIF 送信出力 (tx_out)

を出力します。
モジュール内部ではフレーム数カウントは行なっていますが、フレーム/サブフレームのビット・カウントは行なっておらず、LR クロックからタイミングを作り出しています。 したがって、lrck 信号は、

  • マスター・クロック (m_clk) に同期し、
  • 位相が安定していて、128 fs クロックや 64 fs クロックでの再サンプリングに対してジッタが出ない。
  • 50 % デューティーで H 期間 / L 期間ともに、(再サンプリング後に) 128 fs クロックでびったり 64 周期分の長さ

であることが求められます。
チャネル・ステータスについては、

  • 内容は左右チャネル同一で、
  • コピー許可ビット (b2)
  • サンプリング周波数識別ビット (b25)

のみサポートしています。
チャネル・ステータス b25 には cs_fs48k 入力の値をそのまま反映させているので、cs_fs48k = 0 で fs = 44.1 kHz、cs_fs48k = 1 で fs = 48 kHz を意味することになります。
SPDIF では BMC (Biphase Mark Code) と呼ばれる方式で出力が変調されます。

(普通の) データの変調では、前のビットとの境界では必ず出力信号が反転し、データが「0」の場合にはそのまま、データが「1」の場合には当該ビット・タイムの中央で出力信号が反転します。
前ビットとの境界のエッジを「ファースト・エッジ」、当該ビット・タイムの中央での反転を「セカンド・エッジ」と表現すると、普通のデータに対しては、

  • ファースト・エッジでは必ず反転
  • データが「1」の場合のみセカンド・エッジで反転

する規則となっています。
フレーム/サブフレームの先頭には、フレーム同期のための同期パターンが存在し、「プリアンブル」と呼ばれています。
プリアンブルでは、前述のコーディング規則が破られていて、受信側で同期を取るための手がかりとなっています。
上の図のようにプリアンブルには「B」、「M」、「W」の 3 種類があって、

  • B — ブロック開始および左チャネル (フレーム開始)
  • M — 左チャネル (ブロック開始フレーム以外)
  • W — 右チャネル (サブフレーム開始)

という役割になっています。
いずれも、ファースト・エッジでの反転がないパターンが含まれていて、通常データにはない 1.5 T 幅のパルスを含んでいます。
サブフレーム・データのシフト・レジスタおよびバイフェース・マーク・エンコーディング部のブロック図を下に示します。

「P」はパリティ計算回路で、ひとつ前のサブフレームに対する結果です。
「B」はプリアンブル「B」を出力するタイミングを表す信号、「M」はプリアンブル「M」を出力するタイミングを表す信号です。
「C」はチャネル・ステータス・ビットです。
サブフレームのデータをパラレル・ロードする 31 ビットのシフトレジスタでセカンド・エッジを制御しています。
それに加え、プリアンブル部のファースト・エッジを制御する 4 ビットのシフトレジスタを設けています。
プリアンブル部以外は常にファースト・エッジが存在するので、シフトレジスタ長は 4 ビットとし、シリアル・シフト入力を「1」に吊っておいてプリアンブル部以外では常に「1」が出力されるようにしています。
ネガティブ・エッジで D-FF をトリガすることを許せば、128 fs クロックではなく 64 fs クロックで BMC 符号化回路を構成することも可能ですが、FPGA 回路では好ましくないので、普通にポジティブ・エッジのみ有効な 128 fs クロックを使う回路としています。
ブロック開始部分付近のシミュレーション結果を下に示します。 (図をクリックすると拡大します)

spdif_tx_modelsim.png