IP インテグレータを用いた回路やシステムの設計について学ぶコースの第4回です。前回までで、シリアル通信による文字送信回路の IP コアを作成し、ブロック図を使った設計で「Hello, FPGA」を PC に送信する回路を作成しました。
今回は、文字送信回路の IP コアを Xilinx 社のソフトプロセッサである MicroBlaze と組み合わせていきます。
テンプレートからの IP の作成
IP パッケージャでテンプレートを生成
第1回 や 第2回 でもざっと紹介したのですが、FPGA でプロセッサから IP コアを制御するときには、AXI という接続インタフェースが使われます。プロセッサで特定のメモリアドレスに読み書きを行おうとすると、それが IP コアへの読み書きのリクエストに変換される、という仕組みを持っています。
今回作成するシステムのブロック図を下図に示します。今回は、プロセッサ (MicroBlaze) から読み書きのリクエストを受け取り、文字送信回路のデータ入力 (DATA_IN) や書き込み有効入力 (WE) への書き込みや、ビジー出力 (BUSY) の読み出しを制御する回路を用意します。図中では serial_target と名前をつけています。この回路にプロセッサと文字送信回路との間の仲介をしてもらいます。
AXI インタフェースをもつ回路をイチから設計するのは大変ですので、ここでは IP パッケージャが作成するテンプレートを改造して、所望の回路を作成します。使用するボード (ここでは Arty A7-35) を対象とした新しいプロジェクトを作成し、「Tools → Create and Package IP」で IP パッケージャを起動します。
IP パッケージャでは、「Create a new AXI4 peripheral」を選択します。今回作成する IP コアの名前は serial_target ですので、下図に示すように、Name 欄に serial_target と入力します。作成するインタフェースはデフォルトのままとします。
最終確認の画面では「Edit IP」を選択しても良いですが、後からでも編集は可能ですので、ここでは単に「Add IP to the repository」を選び、IP コアをリポジトリに追加します (この部分の詳細は 第2回 の「テンプレートを作成してパッケージする場合」を参照してください)。
テンプレートを眺める
IP コアのテンプレートが生成できたら、IP パッケージャの IP Location 欄で指定したディレクトリの下に、IP コアの一連のファイルが作成されます。そのうち、ハードウェア記述は serial_target/hdl ディレクトリに作成された2つの Verilog HDL のファイルです。まずは、生成された記述をざっと眺めてみましょう。
serial_target_v1_0.v は IP コアのトップモジュールになります。インタフェースを複数持つ場合には、それらのインタフェース回路をまとめたモジュールになりますが、今回はインタフェースを1つだけ持つ回路ですので、単なるインタフェース回路のラッパとなっています。
serial_target_v1_0_S00_AXI.v がインタフェース回路の本体になります。今回は IP パッケージャのデフォルト設定、すなわち下図に示す画面で設定した AXI4-lite のスレーブモードの回路となりますので、関連する信号には S_AXI_ という接頭辞がついています。
余談ですが、情報分野では通信や制御にある種の上下関係があるときに、マスターとスレーブという用語が慣用的に使われてきました。しかし、近年ではこれをより適切な言葉で言い換える流れが広がっています。AXI インタフェースについても、今後用語の見直しが行われるかもしれません。今回の場合は、リクエストを発行する側 (イニシエータ) と受け取る対象 (ターゲット) くらいの意味合いで捉えてもらえればと思います。
【2022-09-07 追記】 最新の AXI の仕様書では、これらの用語は Manager (上司) と Subordinate (部下) に変更されました。頭文字が変わらない、ナイスな言い換えですね。
インタフェース回路の入出力信号について、クロック (ACLK) とリセット (ARESETN) 以外の信号を IP インテグレータの編集画面で確認した様子を下図に示します。
AXI インタフェースには、AW, W, B, AR, R で始まる5つの信号のグループ (チャネル) が存在しています。このうち、書き込みに関するチャネルは AW, W, B の3つで、それぞれアドレス、データ、書き込みの成否をやり取りします。読み出しに関するチャネル AR, R では、それぞれアドレスとデータをやり取りします。
いずれのチャネルでも、アドレスやデータ、結果を用意する側が valid という信号を出力し、受け取る側が ready という信号を出力します。送受信側が互いの信号を確認し、valid と ready がともに ‘1’ となった時に通信を行う、という仕組みになっています。厳密には他にも様々な制約がありますが、また別の機会で説明したいと思います。
ともあれ、テンプレートの大部分も、相手側の valid (ready) を確認して、一定の条件を満たした時に自身の ready (valid) を ‘1’ にする、というロジックの記述になっています。
書き込みが成立すると、インタフェース回路内部のレジスタ slv_reg0 ~ slv_reg3 にデータを書き込みます。該当する部分の記述を抜粋します。
assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 1'b0 )
begin
slv_reg0 <= 0;
slv_reg1 <= 0;
slv_reg2 <= 0;
slv_reg3 <= 0;
end
else begin
if (slv_reg_wren)
begin
case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
2'h0:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 0
slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
書き込みの成立は AW チャネルと W チャネルの valid と ready がいずれも ‘1’ となっているかどうかで判断しています。書き込みが成立すると、アドレス (axi_awaddr) の特定ビットでレジスタを指定し、各バイトに対応するストローブ信号 (S_AXI_WSTRB) が ‘1’ の場合に、レジスタへの書き込みを行います。+: という演算子は珍しいかもしれませんが、A +: B は A ビット目を LSB として B ビットを抜き出す、という意味です。つまり、: を使って書き直すと (A + B – 1) : A です。
一方、AR チャネルで読み出しのリクエストが成立した場合には、内部レジスタからデータを読み出します。こちらも該当箇所を抜粋します。
always @(*)
begin
// Address decoding for reading registers
case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
2'h0 : reg_data_out <= slv_reg0;
2'h1 : reg_data_out <= slv_reg1;
2'h2 : reg_data_out <= slv_reg2;
2'h3 : reg_data_out <= slv_reg3;
default : reg_data_out <= 0;
endcase
end
先ほどと同じく、アドレス (axi_araddr) の特定ビットでレジスタを指定しています。指定されたレジスタの値は reg_data_out に出力されますが、この値は読み出しのリクエストが成立したときに、プロセッサに渡されるデータ (axi_rdata) になります。
テンプレートを変更する
このテンプレートを使って IP コアを作成する場合は、先に示した内部レジスタへの読み書きの部分に手を入れるのが基本となります。
プロセッサが書き込んだ値を外部に出力したいなら、単に内部レジスタの値をそのまま IP コアの出力として取り出せば OK です。外部からの入力をプロセッサから読み出せるようにするには、上述した内部レジスタの読み出しの際に、内部レジスタでなくその入力を読み出すように変更します。また、書き込みをトリガーとして ‘1’ となる信号を出力するには、上述した内部レジスタへの書き込みの部分を参考にロジックを記述します。
それでは今回の変更箇所を示していきます。まずは各回路に入力・出力を追加していきます。これはトップモジュールとインタフェース回路の両方で必要です。トップモジュールでは入出力を wire で宣言して、インタフェース回路に接続します。
// Users to add ports here
output wire [7:0] DATA_IN,
input wire BUSY,
output wire WE,
// User ports ends
) serial_target_v1_0_S00_AXI_inst (
.DATA_IN(DATA_IN),
.WE(WE),
.BUSY(BUSY),
.S_AXI_ACLK(s00_axi_aclk),
インタフェース回路では、WE のみ (後で always 文の中で使うため) reg で、他は wire で宣言します。
// Users to add ports here
output wire [7:0] DATA_IN,
input wire BUSY,
output reg WE,
// User ports ends
BUSY については、内部レジスタの読み出し部分を変更し、内部レジスタの最初のバイトの最下位ビット (slv_reg0 の最下位ビット) のかわりに読み出せるようにします。
// Address decoding for reading registers
case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
2'h0 : reg_data_out <= {slv_reg0[31:1], BUSY};
2'h1 : reg_data_out <= slv_reg1;
DATA_IN と WE については、ファイル末尾にユーザロジックを記述できる部分があるので、そこに追記します。DATA_IN は内部レジスタの最初のバイト (slv_reg0 の下位8ビット) を接続します。WE は、そのバイトへの書き込みがあったときに ‘1’ となるように記述します。
// Add user logic here
assign DATA_IN = slv_reg0[7:0];
always @ ( posedge S_AXI_ACLK) begin
if ( S_AXI_ARESETN == 1'b0 ) begin
WE <= 1'b0;
end else begin
WE <= (slv_reg_wren == 1'b1 &&
axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] == 2'h0 &&
S_AXI_WSTRB[0] == 1'b1);
end
end
// User logic ends
(2020.10.06 クロックの信号名に脱字がありましたので修正しました。)
以上の変更が済んだら、Vivado 上での作業に移ります。はじめに、第3回 の最初に行ったように、リポジトリの追加 (変更) を行います。ここまでの手順の通り進めると、今回作成した serial_target を含むディレクトリだけがユーザリポジトリに追加されているはずです。ユーザリポジトリに文字送信回路の IP コアのディレクトリも追加し、文字送信回路も IP カタログから参照できる状態にしておきます。
次に、先ほどの HDL 記述の変更を IP コアの定義に反映させます。下図に示す通り、IP カタログで serial_target を右クリックし、「Edit in IP Packager」を選択すると、新たな Vivado のウィンドウが開き、そこで IP コアを編集できます。
HDL 記述上で入出力やパラメータを変更した場合、Ports and Interface 以降のいくつかの項目のアイコンが、変更ありを示すアイコンに変わります。これらのいずれかを選択し、画面上部に表示された Merge Changes… のリンクをクリックすると、HDL 記述の変更が IP コアの定義へと反映されます。各項目がチェックマークに変わったことが確認できたら、Review and Package の項目でパッケージを完了させます。
ブロック図の作成から論理合成まで
MicroBlaze の設定
これで必要な IP コアの準備ができましたので、ブロック図を作成していきます。今回はまずプロセッサである MicroBlaze から追加します。MicroBlaze をブロック図に追加したときの様子を下図に示します。画面上部に自動設定用のメニューが表示されます。
MicroBlaze にはローカルメモリやリセット・デバッグのための IP コアなどが必要ですが、Run Block Automation をクリックすると、これらを自動的に追加し、必要な設定を済ませてくれます。Run Block Automation をクリックし、今回は設定をデフォルトのまま変更せずに設定を完了させます。設定完了時のブロック図の例を下図に示します。
次に、クロックとリセットの接続と設定を行っていきます。Clocking Wizard のクロック入力にはデフォルトでは差動方式を入力する想定になっていますので、Arty ボードに合わせてこれをシングルエンド方式に変更します。Clocking Wizard をダブルクリックすると、下図に示す設定画面が表示されます。clk_in1 の Source の項目を「Single ended clock capable pin」に変更します。
これでクロックとリセットを入力ポートに接続できる状態になりました。Clocking Wizard のクロック入力 (clk_in1) とリセット入力 (reset) をそれぞれ右クリックし、「Make External」を選択し、ポートの名前を適切に変更します。また、リセットには Processor System Reset の ext_reset_in 入力も接続します。これらを済ませたときの画面の例を下図に示します。
なお、この段階では ext_reset_in の極性がおかしい (負論理になっている) ように見えますが、Validate Design を行った時点で (正論理に) 修正されるので、問題ありません。
IP コアの追加と接続
今度は文字送信回路と今回作成した IP コアとをブロック図に追加し、必要な接続を行います。第3回 でも説明しましたが、文字送信回路のリセット入力の極性は手動で ACTIVE HIGH へと変更する必要がありますので、注意してください。これらの IP コアをブロック図に追加したときの様子を下図に示します。ここでも自動設定用のメニューが表示されます。
AXI インタフェースでは、信号の接続のほかにプロセッサ側のどのメモリアドレスに IP コアを割り当てるかの設定も必要です。Run Connection Automation を使うと、接続と同時に適当なアドレスへの自動割り当ても行ってくれます。Run Connection Automation をクリックし、すべての項目にチェックを入れて実行すると、クロック・リセット・AXI インタフェースの接続が自動で行われます。
その他の接続 (serial_wrap と serial_target の間) は手動での接続が必要です。DATA_IN, WE, BUSY の3つの信号は、それぞれ同名の信号に接続します。また、DATA_OUT は「Make External」を行い、出力ポート名を TXD へと変更します。
以上で今回作成するシステムのブロック図は完成です。最終的なブロック図の例を下図に示します。
システムの論理合成とエクスポート
ブロック図が完成したら、そこから論理合成を行うまでの手続きは 第3回 と同じです。すなわち、以下の手順でプログラミングファイルを作成します。
- ブロック図に対して「Validate Design」を行う
- ブロック図から「Generate Block Design」で各種のファイルを生成する
- 制約ファイル (XDC) をプロジェクトに追加する
- 「Create HDL Wrapper」でラッパ回路を作成する
- 「Generate Bitstream」で論理合成以降の一連の処理を行う
ただ、今回はまだ動作確認はできません。なぜなら、システムを動作させるためのソフトウェアをまだ記述していないからです (パソコンはソフトがなければただの箱、と言いますが……)。
次回は Vitis を使ってソフトウェア部分を書いていくことになりますが、そのための準備として、Vitis で利用できる形式にハードウェア部分をエクスポートします。メニューの「File → Export → Export Hardware」を選択すると、下図に示すダイアログが表示されます。
「include bitstream」のチェックボックスに必ずチェックをしてから、OK ボタンを押します。デフォルトの設定では、Vivado のプロジェクトのディレクトリに design_1_wrapper.xsa というファイルが作成されます。
まとめ
今回は、文字送信回路と MicroBlaze との仲介をする IP コアを作成し、これらを含むシステムのブロック図を作成し、論理合成を行いました。今回のポイントは以下のとおりです。
- テンプレートをもとに AXI インタフェースをもつ IP コアを作成するときは、主に内部レジスタへの読み書きの部分を修正する。
- プロセッサをブロック図に配置したら、Block Automation を行って、必要な IP コアの設定と接続を行う。
- システムの論理合成が完了したら、Export Hardware を行い、Vitis からハードウェアを扱えるようにする。
次回は Vitis を使ってこのシステム上のソフトウェアを記述し、プロセッサから「Hello, FPGA」を送信させてみましょう。そこまで出来るようになれば、Vivado での基本的な設計のスキルはついたと言えるでしょう。
愛知工業大学 藤枝直輝