読者です 読者をやめる 読者になる 読者になる

xiangze's sparse blog

機械学習、ベイズ統計、コンピュータビジョンと関連する数学について

QsysでのAvalon busへの独自回路追加

FPGA verilog

AlteraのSoC開発ツールQsysではAvalon busに独自に作成した回路モジュールを接続することができます。
その際にAvalon busの仕様にあるinput,output信号をモジュールでは定義する必要があります。Qsysでの設定手順と一緒にまとめます。
この記事は基本的にはmarseeさんのAvalon-MMスレーブペリフェラルを参考にさせていただきました。
Qsys追加した独自回路モジュールをAvalon busを介してNIOSで操作する構成は同じですが、今回はoutput信号が直接7segの各セグメントに接続される普通(?)の構成なので、追加モジュールはより単純になります。
追加モジュール My7seg.v
avs_s1_*がavalon busの信号です。

module My7seg(/*AUTOARG*/
   // Outputs
   avs_s1_readdata, hex0, hex1, hex2, hex3, hex4, hex5, hex6, hex7,
   // Inputs
   csi_global_reset, csi_global_clk, avs_s1_address, avs_s1_read,
   avs_s1_write, avs_s1_writedata
   );
   input         csi_global_reset;   
   input         csi_global_clk;     

   input [2:0] 	 avs_s1_address;     
   input         avs_s1_read;        
   output [31:0] avs_s1_readdata;    
   input         avs_s1_write;       
   input [31:0]  avs_s1_writedata;   

   output [6:0]  hex0;
   output [6:0]  hex1;
   output [6:0]  hex2;
   output [6:0]  hex3;

   output [6:0]  hex4;
   output [6:0]  hex5;   

   output [6:0]  hex6;
   output [6:0]  hex7;   

 reg [31:0]		avs_s1_readdata;
   reg [3:0] 		iDIG[0:7];

   // avalon bus write
   always @(posedge csi_global_clk, posedge csi_global_reset) begin : SEVEN_VALUE_PROCESS
      integer i;
      if (csi_global_reset) begin
         for (i=0; i<8 ; i=i+1) begin
            iDIG[i] <= 8'd0;
         end
      end else begin
         if (avs_s1_write) begin
            case(avs_s1_address)
              3'b000 :
                iDIG[0] <= avs_s1_writedata[3:0];
              3'b001 :
                iDIG[1] <= avs_s1_writedata[3:0];
              3'b010 :
                iDIG[2] <= avs_s1_writedata[3:0];
              3'b011 :
                iDIG[3] <= avs_s1_writedata[3:0];
              3'b100 :
                iDIG[4] <= avs_s1_writedata[3:0];
              3'b101 :
                iDIG[5] <= avs_s1_writedata[3:0];
              3'b110 :
                iDIG[6] <= avs_s1_writedata[3:0];
              3'b111 :
                iDIG[7] <= avs_s1_writedata[3:0];
            endcase
         end
      end
   end
    
    // avalon bus read
    always @* begin
        avs_s1_readdata[31:8] = 24'd0;
        case (avs_s1_address)
            3'b000 :
                avs_s1_readdata[7:0] <= {4'b0,iDIG[0]};
            3'b001 :
                avs_s1_readdata[7:0] <= {4'b0,iDIG[1]};
            3'b010 :
                avs_s1_readdata[7:0] <= {4'b0,iDIG[2]};
            3'b011 :
                avs_s1_readdata[7:0] <= {4'b0,iDIG[3]};
            3'b100 :
                avs_s1_readdata[7:0] <= {4'b0,iDIG[4]};
            3'b101 :
                avs_s1_readdata[7:0] <= {4'b0,iDIG[5]};
            3'b110 :
                avs_s1_readdata[7:0] <= {4'b0,iDIG[6]};
            default : // 3'b111
                avs_s1_readdata[7:0] <= {4'b0,iDIG[7]};
        endcase
    end

   
/*   SEG7_LUT AUTO_TEMPLATE(
 .oSEG			(hex@[]),
 .iDIG			(iDIG[@]));
  );
 */
  
   SEG7_LUT seq0(/*AUTOINST*/
		 // Outputs
		 .oSEG			(hex0[6:0]),		 // Templated
		 // Inputs
		 .iDIG			(iDIG[0]));		 // Templated
   SEG7_LUT seq1(/*AUTOINST*/
		 // Outputs
		 .oSEG			(hex1[6:0]),		 // Templated
		 // Inputs
		 .iDIG			(iDIG[1]));		 // Templated
   SEG7_LUT seq2(/*AUTOINST*/
		 // Outputs
		 .oSEG			(hex2[6:0]),		 // Templated
		 // Inputs
		 .iDIG			(iDIG[2]));		 // Templated
   SEG7_LUT seq3(/*AUTOINST*/
		 // Outputs
		 .oSEG			(hex3[6:0]),		 // Templated
		 // Inputs
		 .iDIG			(iDIG[3]));		 // Templated
   SEG7_LUT seq4(/*AUTOINST*/
		 // Outputs
		 .oSEG			(hex4[6:0]),		 // Templated
		 // Inputs
		 .iDIG			(iDIG[4]));		 // Templated
   SEG7_LUT seq5(/*AUTOINST*/
		 // Outputs
		 .oSEG			(hex5[6:0]),		 // Templated
		 // Inputs
		 .iDIG			(iDIG[5]));		 // Templated
   SEG7_LUT seq6(/*AUTOINST*/
		 // Outputs
		 .oSEG			(hex6[6:0]),		 // Templated
		 // Inputs
		 .iDIG			(iDIG[6]));		 // Templated
   SEG7_LUT seq7(/*AUTOINST*/
		 // Outputs
		 .oSEG			(hex7[6:0]),		 // Templated
		 // Inputs
		 .iDIG			(iDIG[7]));		 // Templated
   
endmodule // My7seg

アドレスの各値には表示した数値を書き込む形になっていて、それを
数値を7segへの信号へ変換するモジュールSEG7_LUTとしては以下のようなものを使いました。
http://users.ece.gatech.edu/~hamblen/DE2/DE2_demonstrations/DE2_Default/SEG7_LUT.v
SEG7_LUTモジュールが多数になる場合にはgenerate文を使ったほうがよさそうです(参考:Verilogのgenerate文でのマルチプレクサの記述)。

Qsysを開き、左側のTreeのProject->New Componentを選択し、出てきたウィンドウで最初に表示されるComponent typeタブでName,Display nameを設定します。ここではMy7segにしました。
FilesタブのSynthesis filesの部分の+ボタンをクリックしてこの記述のファイルMy7seg.vを選択し、Analyze synthesis fileを押します。Verilog HDLの記述に問題がなければ成功のメッセージが表示されます。

しかしComponent Editorのウィンドウには大概の場合エラーが出てきてしまいます。これはQsysがAvalonバスの各信号を名前から推測するのに失敗している場合がほとんどなので、手でそれを直してあげます。具体的にはComponent EditorのSignalsタブで各信号の設定を行います。ここではavalon busの信号としてInterfaceでavalon_slave0を、Signal typeではaddress,readdata,readdatavalid,writedata,writedatavalidなどを選択します。
7segへ出力されることとなるhex*の信号ではnew Conduitを選択し、Signal typeはexportにします(1つしかありませんが)。reset信号はglobal_reset,clockはglobalを選択します。

その後Interfaceタブでも設定をしてあげなけなければいけません。まずRemove interface With No Signal ボタンを押して、余計なInterfaceを消します。
その結果としてSignalsタブで選択したInterface(バスのような信号の集合)が残ります。ここで各Interfaceのclock,resetの設定をします。
"global"の囲みの部分にclockがあることを確認し、"global_reset"のassociated clockをglobalに、avalon_slave0のassociated resetをglobal_reset,associated clockをglobalに選択します。

Component Editorの下部に表示されるErrorがなくなったらFinishとします。今回はhex*はすべてoutput信号なのでErrorが出ませんでしたが、avalon以外のinput信号がある場合には同様にclock,resetを設定する必要があります。
Finish後に左側のTreeのNew Componentの下にMy7segが表示されるのでこれをクリックで選択し,Addボタンを押してsystem componentsに追加します(下図)。

その後他のQsys内のモジュールと同様にbus信号、clock,resetを白丸をクリックして接続し、Base adressを他のモジュールとかぶらないように設定します(ここでは0x5000)。7seg信号への出力となるピンも忘れないようにクリックし名前を設定します。これで準備ができたのでQsysの合成を行います。
QuartusのほうではMy7seg.v, SEG7_LUT.vをプロジェクトに追加します。7segへの出力信号hex*を追加したので、Qsysでインスタンス記述をコピーし、TOPファイルにペーストして、該当する信号に忘れずに接続し、合成します。

NIOSのソフトウェアは以下のようになります。

#include <stdint.h>
#include "altera_avalon_pio_regs.h"
#include "system.h"

static void SevenSegCount( void ){
	int count;
	int i;
	int c;

	for (count = 0; count < 16; count++){
	for(i=0;i<8;i++){
	  IOWR_ALTERA_AVALON_PIO_DATA(MY7SEG_0_BASE+i*4, (count+i)&0xf);
	}
	usleep(500000);
  }
}

int main(){ 
  /* Event loop never exits. */
  while (1){
	  SevenSegCount();
  }
  return 0;
}

IOWR_ALTERA_AVALON_PIO_DATAというマクロはaltera_avalon_pio_regsに定義されており、直接該当アドレスに書き込むことはできません(タイマー割り込みの場合の参考:NIOS2奮闘記【22】タイマー割り込み②)
MY7SEG_0_BASEはQsysで生成されたsystem.h内に記述があります。Qsysで設定したアドレスを直接叩くよりはこちらの記述のほうが汎用性が若干高いです。
わざわざヘッダファイルからマクロを探してくるのが面倒な場合はOSを入れる必要があるかもしれません。

今回はSlaveとなるモジュールの場合でしたが、Master,割り込み信号を持ったモジュールも同様に設定できるはずです。。。