QsysでのAvalon busへの独自回路追加
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,割り込み信号を持ったモジュールも同様に設定できるはずです。。。