OpenCoresではWISHBONEバスというインターフェース・プロトコルの使用が奨励されており、 また、実際にWISHBONEバスが多く使われています。このため、OpenCoresのIPコアをQuartus II(Prime)のQsysにモジュールとして登録するには、WISHBONEバスをAvalon-MMに変換する必要があります。(もちろん、Qsysにモジュールとして登録しないで利用する方法もありますが)。
具体的な例
例えば、OpenCoresのUSBホストIPコア(USB 1.1 Host and Function IP core)をQsysにモジュールとして登録するには、Avalon-MMスレーブの変換回路が必要です。
Avalon-MMスレーブとWISHBONEバスの変換
次のタイミングチャートは、Avalon-MMスレーブとWISHBONEバスの変換例です。上半分がAvalon MMスレーブ、下半分がWISHBONEバスの信号です。動作としては、Avalon-MMスレーブからシングルのリード・アクセスとライト・アクセスを連続して行っています。バス・プロトコル変換のポイントは、
- ストローブ信号
- アドレス
- データ・バス幅
- アクノリッジ信号
の4つです。
1.ストローブ信号の変換
Avalon-MMは、リード時とライト時のストローブ信号が分かれています(i_av_rとi_av_w)。一方、WISHBONE バスはリード/ライトでストローブ信号は共通です(strobe_i)。WISHBONEバスでは、ストローブが共通のため、その種別を表す信号we_iが必要になります。we_iは、Avalon-MMのi_av_wがそのまま利用できます。具体的なVerilogコードは、次のようになります。
assign strobe_i = i_av_w | i_av_r; assign we_i = i_av_w;
2.アドレスの変換
OpenCoresのIPコアは、インターフェースが同じWISHBONEバスであっても、データバス幅やアドレスの扱いは異なります。例えば、データバス幅は、8ビットもあれば、32ビットもあります。アドレスの扱いも統一されていません。例えば、32ビットのデータバス幅では、本来バイト/ハーフワードの信号(アドレスの下位2ビット)は情報としては不要ですが、IPコアによってその扱いはまちまちです。USB ホスト IPコアの場合は、8ビットデータバス幅のWISHBONEバスです。このため、Avalon-MM側のバイトイネーブル信号をWISHBONEバスの下位2ビットに追加する必要があります。具体的なVerilogコードは次のとおりです。
wire [1:0] w_ba; assign w_ba = i_av_be[1] ? 2'd1 : i_av_be[2] ? 2'd2 : i_av_be[3] ? 2'd3 : 2'd0 ; // WISHBONEバスのアドレス assign address_i = {i_av_adr,w_ba};
このような変換がわずらわしい場合は、32ビットのデータバスの上位24ビットを無効フィールドとして、Avalon-MMのデータバスの下位8ビットに直接マッピングする方法もあります。
3.データ・バス幅の変換
Avalon-MMスレーブ側が32ビット、WISHBONEバス側が8ビットの場合、バス幅の変換を行う必要があります。ライト・データは、Avalon-MMスレーブ側のアドレスの下位2ビットの値に応じて、32ビットから8ビットを選択してWISHBONEバス側のライトデータとします。一方のリードデータは、WISHBONEバス側の8ビットを単純に4回並べて32ビットに変換します。具体的なVerilogコードは次のとおりです。
wire [1:0] w_ba; assign w_ba = i_av_be[1] ? 2'd1 : i_av_be[2] ? 2'd2 : i_av_be[3] ? 2'd3 : 2'd0 ; // ライト・データ assign dada_i = (w_ba == 'd1) ? i_av_wd[15:8]: (w_ba == 'd2) ? i_av_wd[23:16]: (w_ba == 'd3) ? i_av_wd[31:24]: i_av_wd[7:0]; // リード・データ assign o_av_rd = {'d4{data_o}};
4.アクノリッジ信号の変換
Avalon-MMスレーブ側のアクノリッジ信号o_av_waitは、次のような論理になります。
- 通常状態(IDLE状態)ではLレベル
- i_av_rまたはi_av_wがHレベルになると、ウェイト状態(Hレベル)になって、WISHBONEバス側の応答を待つ
- WISHBONEバス側のアクノリッジ信号(ack_o)を認識するとLレベルになる
具体的なVerilogコードは次のとおりです。
assign o_av_wait = !(!(i_av_r|i_av_w) & (r_state == P_IDLE) | (r_state == P_ACK_OUT));
全コード
実際には、タイミングに余裕を持たせるために、Avalon-MMからのアクセスを一度サンプリングしています。
module fm_avalon_wb( clk_core, rst_x, // AVALON bus i_av_adr, i_av_be, i_av_r, o_av_rd, i_av_w, i_av_wd, o_av_wait, // WISHBONE strobe_i, we_i, address_i, ack_o, data_i, data_o ); parameter P_AVALON_ADR_WIDTH='d8; parameter P_AVALON_BE_WIDTH='d4; parameter P_AVALON_DATA_WIDTH='d32; parameter P_INTERNAL_ADR_WIDTH='d10; parameter P_INTERNAL_DATA_WIDTH='d8; input clk_core; input rst_x; // AVALON Bus input [P_AVALON_ADR_WIDTH-1:0] i_av_adr; input [P_AVALON_BE_WIDTH-1:0] i_av_be; input i_av_r; output [P_AVALON_DATA_WIDTH-1:0] o_av_rd; input i_av_w; input [P_AVALON_DATA_WIDTH-1:0] i_av_wd; output o_av_wait; // WISHBONE output strobe_i; output we_i; output [P_INTERNAL_ADR_WIDTH-1:0] address_i; input ack_o; output [P_INTERNAL_DATA_WIDTH-1:0] data_i; input [P_INTERNAL_DATA_WIDTH-1:0] data_o; ////////////////////////////////// // state definition ////////////////////////////////// localparam P_IDLE = 2'h0; localparam P_WAIT_ACK = 2'h1; localparam P_R_WAIT_RDATA = 2'h2; localparam P_ACK_OUT = 2'h3; ////////////////////////////////// // reg ////////////////////////////////// reg [1:0] r_state; reg r_req; reg r_wr; reg [P_INTERNAL_ADR_WIDTH-1:0] r_adrs; reg [P_INTERNAL_DATA_WIDTH-1:0] r_rdata; reg [P_INTERNAL_DATA_WIDTH-1:0] r_wd; ////////////////////////////////// // wire ////////////////////////////////// wire [P_INTERNAL_ADR_WIDTH-1:0] w_adrs; wire [P_INTERNAL_DATA_WIDTH-1:0] w_rdata; wire [P_INTERNAL_BE_WIDTH-1:0] w_be; wire [P_INTERNAL_DATA_WIDTH-1:0] w_wd; wire [1:0] w_ba; ////////////////////////////////// // assign ////////////////////////////////// assign o_av_rd = {'d4{r_rdata}}; assign w_ba = i_av_be[1] ? 2'd1 : i_av_be[2] ? 2'd2 : i_av_be[3] ? 2'd3 : 2'd0 ; assign w_adrs = {i_av_adr,w_ba}; assign w_wd = (w_ba == 'd1) ? i_av_wd[15:8]: (w_ba == 'd2) ? i_av_wd[23:16]: (w_ba == 'd3) ? i_av_wd[31:24]: i_av_wd[7:0]; assign strobe_i = r_req; assign we_i = r_wr; assign address_i = r_adrs; assign data_i = r_wd; assign o_av_wait = !(!(i_av_r|i_av_w) & (r_state == P_IDLE) | (r_state == P_ACK_OUT)); ////////////////////////////////// // state machine ////////////////////////////////// always @(posedge clk_core or negedge rst_x) begin if (~rst_x) begin r_state <= P_IDLE; r_req <= 1'b0; r_wr <= 1'b0; r_adrs <= {P_INTERNAL_ADR_WIDTH{1'b0}}; r_rdata <= {P_INTERNAL_DATA_WIDTH{1'b0}}; r_wd <= {P_INTERNAL_DATA_WIDTH{1'b0}}; end else begin case (r_state) P_IDLE: begin if (i_av_w) begin // write r_req <= 1'b1; r_wr <= 1'b1; r_adrs <= w_adrs; r_wd <= w_wd; r_state <= P_WAIT_ACK; end else if (i_av_r) begin // read r_req <= 1'b1; r_wr <= 1'b0; r_adrs <= w_adrs; r_state <= P_WAIT_ACK; end end P_WAIT_ACK: begin if (ack_o) begin r_req <= 1'b0; if (r_wr) begin // write r_state <= P_ACK_OUT; end else begin r_rdata <= data_o; r_state <= P_ACK_OUT; end end end P_ACK_OUT: begin r_state <= P_IDLE; end endcase end end endmodule