Zynq環境でCPUと連携する回路を作成する(シミュレーション編)

# はじめに
FPGAの授業の補助資料として
授業中には隠蔽して説明していたCPUとFPGA上の回路間で連携する仕組みと実装方法を説明します。

この記事では[前回](https://qiita.com/mmitti/items/4f1617eed4e527d850c2)作成したAXI-LiteのIPに対してAXI-検証IP(AXI Verification IP 以下検証IP)を用いたシミュレーションを行い、回路が期待どおり動くかどうかを確認します。

CPUプログラムの作成と実機での動作確認は次回の記事で説明します。

# 環境
Vivado 2018.3 HL Web Pack Edition

# 検証IPについて

最近のVivadoにはAXI関係のシミュレーション用に検証IPが用意されており、Taskの呼び出しを行うだけでAXI書き込みを行ったり、AXI読み込みを行い結果を返してくれます。

検証IPはテストベンチ側用のagent(AXI信号のトランザクション処理を行うもの)とIPで構成されており、IPとagentは仮想インターフェイスで接続し利用します。

公式資料をもとに図にすると以下のイメージです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/140084/b69c80aa-2c40-3500-4ee8-6f18ae720043.png)

テストベンチのコードからagentの関数を呼び出すと、agent側でAXIの制御を行い、仮想インターフェイス経由で回路側の検証IPを駆動させ、検証したいIPにAXI信号が届くという仕組みです。

仮想インターフェイスを繋ぐ部分はテスト環境の階層構造をもとに指定する必要があり、この点が面倒ですが、AXIのステートマシンなどをテストベンチで書く必要がなく楽にテストができます。

>System Verilogの機能
##### インターフェイス
複数の信号(regやwire)をまとめたもの。バス線などをインターフェイスとして定義しておけば、モジュール間で接続する際にインターフェイスだけ繋げば良くなり、記述量が削減される。
##### 仮想インターフェイス
動的にインターフェイスを切り替える。イメージとしては回路内にあるインターフェイスに無理やり繋ぎ変えて動かすという感じでしょうか？


# 検証IPを用いたシミュレーションの準備
検証IPを用いたシミュレーションを行うにはVivadoのブロック図で検証IPと自作IPを接続してシミュレーションを行います。
ここでは検証IPを用いたテストベンチを記述するための下準備方法を説明します。

## シミュレーション用プロジェクトの作成
シミュレーション用にVivadoのプロジェクトを作成します。ここでは「PSPL」という名前でプロジェクトを作成したものとします。

## VivadoのプロジェクトにIPのパスを登録する
自作したIPはパスを通さないと利用できないため、IPのパスを次の手順で登録します。

「Flow Navigator」内の「Project Navigator」から「Setting」をクリックして開きます。
![01.png](https://qiita-image-store.s3.amazonaws.com/0/140084/f4ce984d-d496-a1ae-f642-6db46860e27d.png)

左側「Project Settings」から「IP」内の「Repository」をクリックし、右側「IP Repositories」の追加ボタン（＋）をクリックします。
![02.png](https://qiita-image-store.s3.amazonaws.com/0/140084/0b9a16f7-b0fb-e485-c194-dda2bae4aaba.png)

IPが保存されているフォルダーを選択し、「Select」を押します。

成功するとIPの数が表示されますので「OK」を押します。
![03.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/9133b3c8-1c43-fcc9-65bd-1541f715adf0.png)


Settingダイアログは「OK」を押して閉じてください。

## 検証用ブロック図を作成する
ここではシミュレーション用に検証IPと作成したIPを接続したブロック図を作成します。

「Flow Navigator」内の「IP Integrator」から、「Create Block Design」を選択し、ブロック図を作ります。

Create Block Designダイアログが表示されるので、「Specify source set」はシミュレーション用のもの(例えばデフォルトで作成されているsim_1)に変更し「OK」を押します。
（「Design name」はお好みで変更してください。）
![04.png](https://qiita-image-store.s3.amazonaws.com/0/140084/9b28a354-981e-f0bc-a3ed-83e0dbafda82.png)

暫く待つとまっさらなブロック図が表示されると思います。
ブロック図の何処かで右クリックをし「Add IP」を選択してください。
（もしくは、＋ボタン）
![05.png](https://qiita-image-store.s3.amazonaws.com/0/140084/7a2b5577-75be-5e0f-8c5e-4806187e223c.png)

IPの検索画面が表示されるので作成したIPを探し出し、ダブルクリックをして追加します。
![06.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/5ca3b3cc-7987-ae67-61f0-b5acae31c202.png)

また、検証IPも同様に追加してください。

ここまで作業するとブロック図は図のようになると思います
![07.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/529ed95f-ee69-57db-4b4f-0d6d3680b7b3.png)

次に検証IPをMasterモードにします。（テスト先のIPがSlaveなので）
検証IPを右クリックし、「Customize Block」をクリックします。
![08.png](https://qiita-image-store.s3.amazonaws.com/0/140084/0ffba49a-5c6b-4939-291d-eaaf9dead3a4.png)

「Interface Mode」を「Master」に、「Protocol」を「AXI4LITE」に変更し「OK」を押して閉じます。
![09.png](https://qiita-image-store.s3.amazonaws.com/0/140084/32d27396-b137-4145-1b80-d6b071c2398d.png)

最後にIP間の接続を行います。
検証IPのM_AXIと作成したIPのS_AXIを接続します。

M_AXIの横に表示されているポートをクリックしながら線を引くとマウスカーソルが鉛筆マークになり、
配線を行うことができます。
この状態でS_AXIに接続しマウスのボタンを離すと配線完了です。
![10.png](https://qiita-image-store.s3.amazonaws.com/0/140084/1a41be48-0ec3-326a-5156-a25a675e8539.png)
![11.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/d83e52b4-6ab7-2b6f-0717-bd9c8300a25b.png)

最後にクロックとリセットを外に出力します。
検証IPのaclkとaresetnの横のポートを右クリックし、「Make External」をクリックします。
![12.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/9a3c117d-862b-13e6-9973-faafd64462e4.png)

最後に作成したIPのクロックとリセットを検証IPのクロック、リセットに接続すれば完成です。

最後まで操作するとブロック図は図のようになると思います。
![13.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/8f42d57c-ec83-f329-2cb4-af622ca7267f.png)

## アドレスの割当とHDLラッパーの作成

先ほど作成したブロック図を検証(Validate Design)をすると警告が出ると思います。

[前回のAXIの説明](https://qiita.com/mmitti/items/4f1617eed4e527d850c2#axi4-lite)でも記述したとおり、IPごとにどのアドレス範囲を用いるかの割当を行わないと
一意にアクセスできないため、アドレスの割当をしろと警告が出ます。

「Address Editor」タブを開き「Auto Assign Address」ボタンを押してください。
自動でアドレスが割り当てられます。
![14.png](https://qiita-image-store.s3.amazonaws.com/0/140084/5d1127c7-06a0-9a51-03e3-e9736188f978.png)

今回は作成したIP用に0x44A00000~0x44A0FFFFの範囲が割り当てられました。
![15.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/e385659a-a17d-f7ea-3218-196bff399b2b.png)

続けて、ブロック図をVerilogから扱えるようにHDLラッパーを作成します。
ソースタブを開き、「Simulation Sources」以下のブロック図を保存したフォルダー以下のブロック図(今回の例だとsim_all)を右クリックし、「Create HDL Wrapper」を押してください。
![16.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/6039509f-85d2-5daf-1694-cb95ae2d9178.png)

ダイアログが表示されるのでデフォルトのまま「OK」を押してください。
![17.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/d3ab2f53-693e-ff95-633f-c18150331fde.png)

これでテストベンチからブロック図内のIPを利用することができます。

## 検証IPのパスの確認
検証IPの仮想インターフェイスを接続する際に階層構造を把握しておく必要があるため、ここで確認を行います。

「Sources」内の「Hierarchy」タブを開き、sim_wrapper以下の階層を開きます。
![ex1.png](https://qiita-image-store.s3.amazonaws.com/0/140084/598a7def-d681-2801-bee9-a6a95b295320.png)

ここで確認するものは
* ブロック図のインスタンス名
* 検証IPのモジュール名
* 検証IPのインスタンス名
です。

これでテストベンチを記述するための下準備ができました。

# テストベンチの作成と記述
ここでは検証IPを用いたテストベンチの記述方法を説明します。
検証IPはSystemVerilog向けに作成されているため、ここではSystem Verilogでテストベンチを記述します。

## テストベンチの追加
テストベンチをVivadoのプロジェクトに追加します。
「Flow Navigator」の「Project Manager」内「Add Sources」を選択します。

「Add or create simulation sources」を選択し「Next」を押してください。
![18.png](https://qiita-image-store.s3.amazonaws.com/0/140084/731159e2-1092-3af2-28a9-7e6529917f1d.png)

「Specify simulation set」がテスト用ブロック図を追加した先と同じであることを確認し、
「Create File」をクリックしてください。
![19.png](https://qiita-image-store.s3.amazonaws.com/0/140084/67d2191a-6ad3-e4c5-a2cf-40cd584db6cc.png)

「File type」を「System Verilog」にして（「File name」はお好みで）「OK」を押してください。
![20.png](https://qiita-image-store.s3.amazonaws.com/0/140084/313df1ef-8cc6-8357-bd78-190a07189039.png)

最後に「Finish」を押して追加完了です。

入出力ポートの定義画面が表示されますが、テストベンチなので何も入力せずに閉じてください。
![21.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/3d57f726-fe49-21d3-d174-1bcb09bab794.png)

「Sources」内の「Hierarchy」タブを開くと追加したテストベンチが含まれていると思うのでダブルクリックして開けば編集できます。
![22.png](https://qiita-image-store.s3.amazonaws.com/0/140084/f8f9591c-6c34-de47-07ac-40ffc16f348e.png)

## テストベンチ例

以下にサンプルのテストベンチを示します。

検証IPと接続する周りは上で調べたモジュール名やインスタンス名を用います。

```verilog
`timescale 1ns / 1ps
import axi_検証IP_pkg::*;
// <検証IPのモジュール名>_pkg
import sim_all_axi_vip_0_0_pkg::*;

module tb_main();
// リセット・クロック生成
reg rst, clk;
parameter int RST_WAIT = 77;
parameter int CLK_WAIT = 10;
task clk_gen();
  clk = 0;
  forever #(CLK_WAIT/2) clk = ~clk;
endtask
task rst_gen();
  rst = 1;
  #(RST_WAIT);
  rst = 0;
endtask

// ブロック図と接続
// ここではラッパーのインスタンス名としてsim_all_instとする
sim_all_wrapper2 sim_all_inst(
  .aclk_0(clk),
  .aresetn_0(~rst)
);

// AXI 検証IPと接続
// <検証IPのモジュール名>_mst_t
sim_all_axi_vip_0_0_mst_t agent;
task init_agent();
  // 仮想インターフェースと接続
  // <ラッパーのインスタンス名>.<ブロック図のインスタンス名>.<検証IPのインスタンス名>.inst.IF
  agent = new("master vip agent", sim_all_inst.sim_all_i.axi_vip_0.inst.IF);
  agent.start_master();
endtask

// AXI Lite読み書きTask
task wr_reg(
  input [7:0] addr,
  input [31:0] data
  );
  xil_axi_resp_t  resp;
  agent.AXI4LITE_WRITE_BURST(addr, 0, data,resp);
endtask
task rd_reg(
  input [7:0] addr,
  output [31:0] data);
  xil_axi_resp_t  resp;
  agent.AXI4LITE_READ_BURST(addr, 0, data,resp);
endtask
reg [31:0] data;
integer test_num;
// テストベンチ本体
initial begin
  // 並行して処理を行う(クロック生成と検証IPのagentの接続)
  fork
    clk_gen();
    init_agent();
  join_none
  rst_gen();
  // 波形を見たときにどのテストのときかわかりやすいようにtest_numを更新する
  // とりあえず初期値を確認
  test_num = 1;
  rd_reg(8'h00, data);
  rd_reg(8'h04, data);
  rd_reg(8'h08, data);
  // シード値セット
  test_num = 2;
  wr_reg(8'h04, 32'd2463534242);
  // 初期データを確認(シード値から計算された乱数のはず
  test_num = 3;
  rd_reg(8'h04, data);
  rd_reg(8'h08, data);
  // 乱数生成有効
  test_num = 4;
  wr_reg(8'h00, 32'd1);
  // 乱数取得
  test_num = 5;
  rd_reg(8'h08, data);
  rd_reg(8'h08, data);
  rd_reg(8'h08, data);
  // 終了待ち
  #(CLK_WAIT * 10);
  $finish();
end
endmodule

```

## シミュレーションを行う

シミュレーションを行うには「Flow Navigator」から「Simulation」内の「Run Simulation」をクリックし「Run Behavioural Simulation」をクリックします。
![ex2.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/24d2ce27-240c-a5f6-f466-cea77ed31485.png)

適当に信号を追加して確認すると以下のようになります。
（ここではAXIの発行タイミングと結果(data)がわかりやすいようにAXIのwvalid, rvalidを追加しています）
![23.PNG](https://qiita-image-store.s3.amazonaws.com/0/140084/20da689d-1e48-f0ef-8cc0-e3bb11b5d734.png)

>Vivadoシミュレーターの詳しい使い方は[こちら](https://qiita.com/mmitti/items/633900ad1d0c1673f413)を参考にしてください。

シードとして2463534242を指定しました。
Excelで1回計算した乱数値は723471715となり、360ns付近で出てきているdataの値が初回の乱数値を読み出した結果で一致しています。
以降、500ns付近から3回読み出している乱数値もExcelで4回、8回、12回計算した乱数値と一致しており、問題なさそうです。

実際にテストベンチを作る場合は値の自動比較などを行うと良いかもしれないです。

# 次回予告
次回はCPUプログラムの作成と実機での動作確認を行います。

# サンプルコード
準備中です。最終記事公開後にリポジトリを作成します。

# 参考文献
[AXI 検証IPのドキュメント](https://japan.xilinx.com/support/documentation/ip_documentation/axi_vip/v1_1/j_pg267-axi-vip.pdf)