”FPGAで遊んでみる”では、セミナー用に作成したサンプル回路などを整理して少しづつ紹介していきます。


セミナ開催します
手ぶらでOK!手ぶらでOK!実習・1日でわかる!CANプログラミング入門
-- マイコンモジュールでマスタするプロトコルの基本と通信プログラムの作り方 
2024年11月21日(木)CQ出版社セミナルーム CQ出版社セミナ・ルーム
詳しくはこちらへ

ZYBOの本
FPGAパソコンZYBOで作るLinux I/Oミニコンピュータ CQ出版 発売中

Papilioの本
FPGA版Arduino!!Papilioで作るディジタル・ガジェット CQ出版 発売中


第8回 FIFOとアービタでデータの流れを制御してみる

 今回は、複数の回路から転送要求のあったデータを調停(アービトレーション)して順番に送り出す回路を紹介します。
RS232Cへ複数の回路からメッセーを転送する場合に調停機能のあるバッファとして活用できます。
また内部モジュールして使用しているFIFOとメモリモデルも紹介します。

メッセイージバッファ(msg_buf)のソース(zip圧縮してあります)
 msg_buf_files.zip
 msg_buf.v メッセージバッファ回路Verilo-HDLソース
 msg_buf_ap.v FPGAのトップ階層のVerilo-HDLソース
 cmd_ctrl.v 受信データを解釈して制御信号を操作
 fifo.v メッセージ保持用FIFO
 ram.v メッセージ保持用FIFO用RAM
 rs232c_txrx.v RS232Cインターフェースモジュール(第3回で紹介)
 msg_buf_test.v msg_bufのテストベンチ
 msg_buf_ap_test.v msg_buf_apのテストベンチ
 msg_buf_ap.qsf FPGAボードに改定版FPGAボードで学ぶ論理回路設計(CQ出版)付属ボード(CQ出版)を使用した場合のPIN配置指定
 ※ソースコードが長いのでダウンロードして中身を見てください。

仕様説明(msg_buf.v)

 動作概要
   A,B2本データパスを入力して,先に入力を開始したデータパスから転送を行い、転送完了後に別のデータパスの転送を行います。
 A,B同時に入力開始した場合は、Aを優先して転送します。
 ブロック図
 


 入力信号
 CLK:クロック入力/今回使用してたボードは33MHzでした。
 RESET_N:リセット入力 0でリセット
 A_DAT_EN:A側のデータイネーブル
 A_DAT[data_no:0]:A側の入力データ
 B_DAT_EN:B側のデータイネーブル
 B_DAT[data_no:0]:B側の入力データ
 O_BUSY:出力側からのビジー信号、1になるとデータの転送を中断

 出力信号
 A_N_FULL:A側入力のFIFOのニアリーフル、FIFOの空きが1データ分になったときに1
 B_N_FULL:B側入力のFIFOのニアリーフル、FIFOの空きが1データ分になったときに1
 O_DAT_EN:出力のデータイネーブル
 O_DAT[data_no:0]:出力データ

 パラメータ
 data_width:データの幅、デフォルト8bit
 data_no:データのインデックスの最大値、data_widthから算出
 adr_width:FIFOの容量指定、内部アドレスアドレスの幅で指定、デフォルト値は3
        2の場合は、アドレス2bitなので容量は4
        3の場合は、アドレス3bitなので容量は8
 adr_no:内部アドレスのインデックスの最大値、adr_widthから算出
 mem_size:FIFO内のメモリーサイズ指定、adr_widthから算出

 詳細動作
 入力データイネーブルが1になるとデータがFIFOに取り込まれ、出力の接続先がビジーでない場合(O_BUSY==0)に
 データを転送する。
  転送時の基本動作
 
   A_DAT_EN==1でデータ入力され、A_DATのデータが、O_DATに出力される。その後に入力されたB_DATは、A_DATの
 データの後に出力される。

  出力側がビジーの場合(O_BUSY==1)の動作
 
 O_BUSY==1の時は、O_DATA_EN=0となる。
 データ入力はB側が先に入力を開始したので、出力では、Bのデータ、Aのデータの順で出力されます。

 A側,B側のデータ入力が競合した場合の動作
 
 A側とB側が同時に入力開始した場合はA側のデータから出力されます。  


 内部FIFOがフルになった場合の動作
 
 内FIFOの書き空きがデータ1個分になった時にA_N_FULL,B_N_FULLが1になります。
 この状態になったらデータ入力は停止してください。デフォルト設定でのFIFOの容量はA側入力、B側入力、出力に
 それぞれデータ8個分です。波形ではA側のデータは出力側に8個、A側入力に7個の合計15個のデータが溜まった時点で
 A_N_FULLが1になっています。B側のデータは、出力側をAのデータが使っているので、B側入力に7個データが溜まった時点で
 B_N_FULLになっています。


FIFO
 FPGA開発ツールに付いてくるモジュール生成機能でFIFOを作成可能ですが、今回は汎用的に使えるよう、容量とデータ幅を
 パラメタタイズしたFIFOを紹介します。ソースはmsg_buf.zipに含まれているfifi.vです。
 入力信号
  CLK:クロック入力
  RESET_N:リセット入力 0でリセット
  WDAT_EN:入力データイネーブル
  WDAT:入力データ
  REN:出力許可、接続先のニアリーフルの反転を接続する
 出力信号
  RDAT_EN:出力データイネーブル、データを出力するときに1
  RDAT:出力データ
  FULL:内部メモリがフル状態:メモリの空きが無い場合に1  
  N_FULL:内部メモリがニアリーフル状態:メモリの空きがデータ1個分で1  
  EMPTY:内部メモリがエンプティ状態:メモリにデータが無い場合に1
 パラメータ
  data_width:データの幅、デフォルト8bit
  data_no:データのインデックスの最大値、data_widthから算出
  adr_width:FIFOの容量指定、内部アドレスアドレスの幅で指定、デフォルト値は3
        2の場合は、アドレス2bitなので容量は4
        3の場合は、アドレス3bitなので容量は8
  adr_no:内部アドレスのインデックスの最大値、adr_widthから算出
  mem_size:FIFO内のメモリーサイズ指定、adr_widthから算出

 動作
  入力動作:FULL==0の場合に書き込みが可能で、WDAT_EN=1するとWDATを取り込みます。FULL==1の場合はメモリの空きがないので
  WEN=1にしてもWDATは書きこれずに破棄されます。メモリの空きがデータ1個分なったときにN_FULL=1となるので、にN_FULL==1の場合の
  データ入力を停止すればデータを破棄されずにすみます。
  出力動作:REN==1の場合の出力が許可されて、メモリにデータがあれば、RDAT_EN=1としてRDATを出力します。
  


メモリモデル
 FIFO同様にFPGA開発ツールに付いてくるモジュール生成機能でFIFOを作成可能ですが、今回は汎用的に使えるよう、容量とデータ幅を
 パラメタタイズしたメモリモデル(書き込み/読出しポート独立)を紹介します。
 このモデルを論理合成をするとDFFで回路が作られるので、FPGAへ搭載する際はFPGA開発ツールのモジュール生成機能で作成した
 メモリに変更すればセルが節約できまので、そちらを使うのが現実です。
 ソースはmsg_buf.zipに含まれているram.vです。
 入力信号
  CLK:クロック入力
  RESET_N:リセット入力 0でリセット
  WADT:書込みアドレス
  WEN:書込みイネーブル
  WDAT:書込みデータ
  RADT:読み出しアドレス
 出力信号
  RDAT:出力データ
 パラメータ
  data_width:データの幅、デフォルト8bit
  data_no:データのインデックスの最大値、data_widthから算出
  adr_width:FIFOの容量指定、内部アドレスアドレスの幅で指定、デフォルト値は3
        2の場合は、アドレス2bitなので容量は4
        3の場合は、アドレス3bitなので容量は8
  adr_no:内部アドレスのインデックスの最大値、adr_widthから算出
  mem_size:FIFO内のメモリーサイズ指定、adr_widthから算出

 動作
 書き込み動作:WEN=1の時に、WADRで指定した番地にWDATを書込みます。
 読み出し動作:RADR で指定した番地のデータを1クロック後にRDATへ出力します。
 


FPGAでの動作
 FPGAトップモジュール(msg_buf_ap.v)
  msg_bufにRS232C通信モジュール(rs232c_tx_rx)と受信データを解釈してLED制御信号の発生と
  メッセージ生成するモジュール(cmd_ctrl)を追加して、PCのターミナルソフトからシリアル通信でLED制御します。
  msg_bufの役割は、受信データの折り返しとcmd_ctrlの生成いたメッセージを調停して送信データとしてrs232c_tx_rxに
  渡します。

   トップモジュール(msg_buf_ap.v)のブロック図
  
  msg_buf:メッセージバッファ
  cmd_ctrl:受信データの解釈、コマンド、メッセージの発行
  rs232c_tx_rx RS232C通信モジュール 

  受信データ
の解釈
  cmt_ctrlモジュールでは、rs232c_tx_txからの受信データの内容でコマンを発行します。
  PCのターミナルからのコマンドの入力フォーマットは、[1文字目][2文字目][CR][LF]にしました。
  [CR][LF]はリターンキーを押すと入力されなます。受信データの解釈は、LFが入力された時に行い、
  2個前と3個前のデータをコマンドとして発行します。

  コントロールモジュール選択部分の記述(cmd_trl.v)

//受信データの保持
always @(posedge CLK or negedge RESETB) begin
  if (RESETB==1'b0)
    begin
      rx_data_d1 <= 8'h00;
      rx_data_d2 <= 8'h00;
      rx_data_d3 <= 8'h00;
      rx_data_d4 <= 8'h00;
      rx_data_en_d1 <= 1'b0;
    end
  else
    if (RX_DATA_EN==1'b1)
      begin
        rx_data_d1 <= RX_DATA;
        rx_data_d2 <= rx_data_d1;
        rx_data_d3 <= rx_data_d2;
        rx_data_d4 <= rx_data_d3;
        rx_data_en_d1 <= 1'b1;
      end
    else
      begin
        rx_data_d1 <= rx_data_d1;
        rx_data_d2 <= rx_data_d2;
        rx_data_d3 <= rx_data_d3;
        rx_data_d4 <= rx_data_d4;
        rx_data_en_d1 <= 1'b0;
      end
end 
   
//LFの検出とコマンド発行    
always @(posedge CLK or negedge RESETB) begin
  if (RESETB==1'b0)
    begin
      rx_cmd <= 8'h00;
      rx_cmd_en <= 1'b0; 
    end
  else
    if ((rx_data_en_d1==1'b1)&&(rx_data_d1==8'h0a)) //LFが入力
      begin
        rx_cmd <= {rx_data_d4,rx_data_d3}; //LFの2,3個前のデータをコマンドとして保持
        rx_cmd_en <= 1'b1;  //コマンド発行
      end
    else
      begin
        rx_cmd <= rx_cmd;
        rx_cmd_en <= 1'b0;
      end 
end 

assign CMD = rx_cmd;

 メッセージ生成
  LFを受信すると3個前のデータを識別してメッセー時を発行しています。メッセージに内容はLED制御と一致するようにしてあります。
  メッセージは複数byteのデータで構成されているので、カウンタを起動して1byteづづ出力しています。

  メッセージ生成の記述(cmd_ctrl.v)

//メッセージの生成   
always @(posedge CLK or negedge RESETB) begin
  if (RESETB==1'b0)
    begin
      msg_data <= {"      "};
      msg_cnt_end <= 3'd0; 
    end
  else
    if ((rx_data_en_d1==1'b1)&&(rx_data_d1==8'h0a)) //LFが入力
      if (rx_data_d4==8'h41) //3個前のデータが 'A'の場合
        begin
          msg_data <= "on\n   "; //メッセージ設定
          msg_cnt_end <= 3'd3;   //メッセージ終了タイミング指定
        end
      else if (rx_data_d4==8'h42) //3個前のデータが 'B'の場合
        begin
          msg_data <= "off\n  "; //メッセージ設定
          msg_cnt_end <= 3'd4;   //メッセージ終了タイミング指定
        end
      else if (rx_data_d4==8'h43) //3個前のデータが 'C'の場合
        begin
          msg_data <= {"sel",rx_data_d3,"\n "}; //メッセージ設定
          msg_cnt_end <= 3'd5; 
        end
      else  //3個前のデータが 'a','b','C'以外の場合
        begin
          msg_data <= "err\n  "; //メッセージ設定
          msg_cnt_end <= 3'd4;   //メッセージ終了タイミング指定
        end
    else
      begin
        msg_data <= msg_data ; 
        msg_cnt_end <= msg_cnt_end ; 
      end 
end // always @ (posedge CLK or negedge RESETB)

//メッセージ生成用カウンタ
always @(posedge CLK or negedge RESETB) begin
  if (RESETB==1'b0)
    msg_cnt <= 3'd0;
  else
    if (rx_cmd_en==1'b1) //コマンド発行でカウント開始
      msg_cnt <= 3'd1;
    else
      if ((msg_cnt==3'd0)|| (msg_cnt==msg_cnt_end))//カウント停止
        msg_cnt <= 3'd0;
      else
        msg_cnt <= msg_cnt + 3'd1; //カウント 
end

//メッセージデータ送信          
always @(posedge CLK or negedge RESETB) begin
  if (RESETB==1'b0)
    TX_DATA <= 8'h00;
  else
    case (msg_cnt)
      3'd0:TX_DATA <= 8'h00;
      3'd1:TX_DATA <= msg_data[47:40];
      3'd2:TX_DATA <= msg_data[39:32];
      3'd3:TX_DATA <= msg_data[31:24];
      3'd4:TX_DATA <= msg_data[23:16];
      3'd5:TX_DATA <= msg_data[15:8];
      3'd6:TX_DATA <= msg_data[7:0];
      default TX_DATA <= 8'h00;
    endcase
end
   
//メッセージデータ・イネーブル生成        
always @(posedge CLK or negedge RESETB) begin
  if (RESETB==1'b0)
      TX_DATA_EN <= 1'b0;
  else
    if (msg_cnt==3'd1) //メッセージ生成カウンタのカウント開始でイネーブル
      TX_DATA_EN <= 1'b1;
    else if (msg_cnt==4'd0) //メッセージ生成カウンタの停止でディセーブル
      TX_DATA_EN <= 1'b0;
    else
      TX_DATA_EN <= TX_DATA_EN;
end
      

 LED制御
  cmd_ctrlから出力されるコマンド(CMD)の値でLEDの点灯方法変更しています。
  cmd[15:8]が0x41は1文字目がAの場合で、LEDを点灯します。
  cmd[15:8]が0x42は1文字目がBの場合で、LEDを消灯します。
  cmd[15:8]が0x43は1文字目がCの場合で、CMD[7:0]で点滅に使うカウンタの信号を選択します。
  cmd[15:8]が上記以外はLEDを消灯します。

  LED制御の記述(msg_buf_ap.v)

//LED制御用カウンタ   
always @ (posedge CLK or negedge RESETB )
  if (RESETB==1'b0)
    led_cnt <= 26'b0;
  else if (led_cnt == led_cnt_1s) //1秒経過 
    led_cnt <= 26'b0;             //カウント値を0
  else
    led_cnt <= led_cnt + 26'd1;   //カウントアップ

//LED制御   
assign LED_0 = (CMD[15:8]==8'h41)?1'b0: //LED点灯
               (CMD[15:8]==8'h42)?1'b1: //LED消灯
               (CMD[15:8]==8'h43)?((CMD[7:0]==8'h30)?led_cnt[15]: //LED点滅
                                   (CMD[7:0]==8'h31)?led_cnt[16]:
                                   (CMD[7:0]==8'h32)?led_cnt[17]:
                                   (CMD[7:0]==8'h33)?led_cnt[18]:
                                   (CMD[7:0]==8'h34)?led_cnt[19]:
                                   (CMD[7:0]==8'h35)?led_cnt[20]:
                                   (CMD[7:0]==8'h36)?led_cnt[21]:
                                   (CMD[7:0]==8'h37)?led_cnt[22]:
                                   (CMD[7:0]==8'h38)?led_cnt[23]:
                                   (CMD[7:0]==8'h39)?led_cnt[24]:~led_cnt[24]):
                                   1'b1 ;          

 動作の様子
   FPGAボードとPCをUSB<->RS232C変換器(FT2232D)で接続して、ターミナルソフト(TeraTeram)を使って
  通信してみました。入力に応じてメッセージが返信されています。FPGAボードのLED制御に動作しています。 
  PCとのシリアル通信のセットアップは第3回を参考にしてください。

   動作試験での通信ログ
   


   FPGAボードでの動作の様子
   
   C3,C4では選択した信号の点滅周期が早くて点滅が確認できませんが
   C5から点滅が確認できて、次第に点滅の間隔が長くなるのが分かります。


紹介した回路を試す場合は、自己責任でお願いします。
次回は、”第9回 カメラモジュールでFPGAに画像を取り込んでみる”を紹介する予定です。

リンクフリーです。
リンクされた場合はご連絡をいただけると嬉しいです。
メール:


変更履歴
(2010/11/10) 初版


TOPへ戻る