SystemVerilogによるテストベンチ実践会(2017夏) 成果報告

SystemVerilogによるテストベンチ実践会(2017夏)のSystemVerilog勉強会は有意義でした。

Zynq VIPとDPI-Cを使用して、テスト用に作成したCソースコードをそのままコードは変更しないで実機テストでも使用できる確認を取りました。

成果物を誰でも実行できるように環境と整えたので公開します。

さて、いったい何が出来るようになったのか・・・?

それは見てのお楽しみ♪

環境

動作環境は次のようになります。

項目  詳細
OS Ubuntu 16.04.3LTS
ツール Vivado 2017.2

ここで実験した環境はgithubからcloneして、実行することができます。

$ git clone git://github.com/aquaxis/svDemo

プロジェクト構成

今回はZynq VIPとDPI-Cの動作確認を行うことが目的なので簡易的なプロジェクトです。

FPGAの基本はLチカでしょう。

対象のプロジェクトは図のようになります。

プロジェクトのブロック図

ZynqとZynqのGPポートに接続されたAXI GPIOの構成で、GPIOの出力はLEDに接続しています。

今回のプロジェクトはZYBOで確認できるように環境を整えています。

Zynqプロジェクトは次のように実行することで生成することができます。

$ /opt/Xilinx/Vivado/2017.2/settings64.sh
$ vivado -mode batch -source make.tcl

実行するとZynq7000というフォルダを作成し、その配下に今回対象としているプロジェクト作成します。

Zynq7000/Zynq7000.xprをVivadで開くとプロジェクトを確認することができます。

ファイル名 概要
make.tcl Zynqプロジェクトを生成するtclスクリプト
Zynq7000.tcl 本プロジェクトのブロックデザイン
Zynq7000.xdc 本プロジェクトのピン定義ファイル

コマンドを実行すると、シミュレーションを行うためのエクスポートをしますがそれは後述します。

シミュレーション内容

シミュレーションの内容は「ZynqのプロジェクトでアプリケーションからLEDを点灯させる」ことです。

つまり、シミュレーションの構成としては下図のようになります。

シミュレーションの内容

Zynq VIPの利点

今まで、Zynq VIPが無い場合はAXIバスを使用したシミュレーションが行いにくいという欠点がありました。

シミュレーションと行うために下図のようにBFMを使用するなり、自前でバスマスターモデルを作成して、Verilog HDLのテストベンチでAXIバスにデータを書き込むというシミュレーションを行っていました。

旧来のシミュレーション

私はとりわけ、後者側で自前でバスマスターモデルを用意してシミュレーションをしていました。

つまり、シミュレーションを行うためにシミュレーション用のプロジェクトを用意していました。

これがZynq VIPを使用するとシミュレーション用にプロジェクトを作成しなくても、下図のように本使用するプロジェクトでシミュレーションを行えるようになります。

Zynq VIPを使用したシミュレーション構成

これがZynq VIPを使用する最大の利点になります。

DPI-Cの利点

多くのシミュレーションベンチはVerilog HDLやVHDLなど、ハードウェア記述言語でシミュレーションする側を記述してシミュレーションを行います。

DPI-Cをしようするちと下図のようにCソースコードで記述したテストアプリをそのまま使用して、シミュレーションを行うことが可能になります。

DPI-Cを使用したテスト環境

DPI-Cを利用する利点は次の点が挙げられるでしょう。

  • テストベンチの作成が容易になる
    • アプリのテストベンチを作成するためにステートマシンを駆使する必要がなくなる
    • 本番データ使用したテストベンチの作成を行いやすくなる
  • 実機で使用するCアプリをそのままテストベンチに使用できる
    • 本運用のアプリでなくても、アプリでシミュレーションができればアプリ屋が確認するときのサンプル・コードになるのでアプリ屋との意思疎通が図りやすくなる

シミュレーション環境

シミュレーションを行うために次のソースを用意しました。

ファイル名 概要
tb_Zynq7000.sv シミュレーション用のSystemVerilogトップファイル
function.c テストアプリ(Cソースコード)

これらのファイルの構成は大雑把にですが下図のようになります。

シミュレーション構成

シミュレーションの実行

Zynqプロジェクトを生成した時に、すでにシミュレーション環境が整っています。

Zynqプロジェクトを作成した時にexportsimというフォルダが同時作成されています。

次のようにフォルダを移動するとtb_Zynq7000.shというファイルがあります。

ただし、この時点ではtb_Zynq7000.svを組み込んでシミュレーションのトップ階層の準備が整っているだけで、テストベンチなどは組み込まれていません。

ここで次のように実行するとシミュレーションを行うことができます。

$ cd exportsim/xsim
$ ./tb_Zynq7000.sh

この時点ではテストベンチが存在しないので次のようにエラー出力されます。

Simulation completed
[1505] : *ZYNQ_BFM_INFO : Starting Address(0x10000000) -> Write 4 bytes of data to DDR Memory
[1505] : *ZYNQ_BFM_INFO : Starting Address(0x10000000) -> Read 4 bytes of data from DDR Memory
             1505000, running the testbench, data read from MEM was 32'hdeadbeef
FATAL_ERROR: Vivado Simulator kernel has encounted an exception from DPI C function: cFuncStart(). Please correct.
Time: 1505 ns  Iteration: 1  Process: /tb_Zynq7000/Initial40_3
  File: /home/hidemi/workspace/svDemo/exportsim/xsim/srcs/tb_Zynq7000.sv

HDL Line: /home/hidemi/workspace/svDemo/exportsim/xsim/srcs/tb_Zynq7000.sv:72
## quit

ここでエラーが出ているのはDPI-Cのコードを埋め込んでいるためです。

テストベンチの追加

DPI-Cを実行できるように./tb_Zynq7000.shを次のように修正します。

修正前

# RUN_STEP: <elaborate>
elaborate()
{
  $xv_path/bin/xelab --relax --debug typical --mt auto -L axi_lite_ipif_v3_0_4 -L lib_cdc_v1_0_2 -L interrupt_control_v3_1_4 -L axi_gpio_v2_0_15 -L xil_defaultlib -L proc_sys_reset_v5_0_11 -L axi_infrastructure_v1_1_0 -L xil_common_vip_v1_0_0 -L smartconnect_v1_0 -L axi_protocol_checker_v1_1_14 -L axi_vip_v1_0_2 -L axi_vip_v1_0_1 -L xlconstant_v1_1_3 -L xlconcat_v2_1_1 -L unisims_ver -L unimacro_ver -L secureip -L xpm --snapshot tb_Zynq7000 xil_defaultlib.tb_Zynq7000 xil_defaultlib.glbl -log elaborate.log
}

# RUN_STEP: <simulate>
simulate()
{
  $xv_path/bin/xsim tb_Zynq7000 -key {Behavioral:sim_1:Functional:tb_Zynq7000} -tclbatch cmd.tcl -log simulate.log
}

修正後

# RUN_STEP: <elaborate>
elaborate()
{
  $xv_path/bin/xelab --relax --debug typical --mt auto -L axi_lite_ipif_v3_0_4 -L lib_cdc_v1_0_2 -L interrupt_control_v3_1_4 -L axi_gpio_v2_0_15 -L xil_defaultlib -L proc_sys_reset_v5_0_11 -L axi_infrastructure_v1_1_0 -L xil_common_vip_v1_0_0 -L smartconnect_v1_0 -L axi_protocol_checker_v1_1_14 -L axi_vip_v1_0_2 -L axi_vip_v1_0_1 -L xlconstant_v1_1_3 -L xlconcat_v2_1_1 -L unisims_ver -L unimacro_ver -L secureip -L xpm --snapshot tb_Zynq7000 xil_defaultlib.tb_Zynq7000 xil_defaultlib.glbl -log elaborate.log
  $xv_path/bin/xelab -L axi_lite_ipif_v3_0_4 -L lib_cdc_v1_0_2 -L interrupt_control_v3_1_4 -L axi_gpio_v2_0_15 -L xil_defaultlib -L proc_sys_reset_v5_0_11 -L axi_infrastructure_v1_1_0 -L xil_common_vip_v1_0_0 -L smartconnect_v1_0 -L axi_protocol_checker_v1_1_14 -L axi_vip_v1_0_2 -L axi_vip_v1_0_1 -L xlconstant_v1_1_3 -L xlconcat_v2_1_1 -L unisims_ver -L unimacro_ver -L secureip -L xpm  xil_defaultlib.tb_Zynq7000 -dpiheader dpi.h
  cp ../../function.c .
  $xv_path/bin/xsc function.c
  xelab -sv_lib dpi -L axi_lite_ipif_v3_0_4 -L lib_cdc_v1_0_2 -L interrupt_control_v3_1_4 -L axi_gpio_v2_0_15 -L xil_defaultlib -L proc_sys_reset_v5_0_11 -L axi_infrastructure_v1_1_0 -L xil_common_vip_v1_0_0 -L smartconnect_v1_0 -L axi_protocol_checker_v1_1_14 -L axi_vip_v1_0_2 -L axi_vip_v1_0_1 -L xlconstant_v1_1_3 -L xlconcat_v2_1_1 -L unisims_ver -L unimacro_ver -L secureip -L xpm --snapshot tb_Zynq7000 xil_defaultlib.tb_Zynq7000 xil_defaultlib.glbl -R
}

# RUN_STEP: <simulate>
simulate()
{
$xv_path/bin/xsim tb_Zynq7000 -key {Behavioral:sim_1:Functional:tb_Zynq7000} -tclbatch cmd.tcl -log simulate.log
#  $xv_path/bin/xsim tb_Zynq7000 -g
}

tb_Zynq7000.shを修正後、次のように実行します。

$ ./tb_Zynq7000.sh

そうすると次のように結果が表示されます。

Simulation completed
[1505] : *ZYNQ_BFM_INFO : Starting Address(0x10000000) -> Write 4 bytes of data to DDR Memory
[1505] : *ZYNQ_BFM_INFO : Starting Address(0x10000000) -> Read 4 bytes of data from DDR Memory
             1505000, running the testbench, data read from MEM was 32'hdeadbeef
svPlWrite(40000000,01234567)
[1505] : M_AXI_GP0 : *ZYNQ_BFM_INFO : Starting Address(0x40000000) -> AXI Write -> 4 bytes
      1645 : [LED] 0xzzzzzzz7
[1745] : M_AXI_GP0 : *ZYNQ_BFM_INFO : Done AXI Write for Starting Address(0x40000000) with Response 'OKAY'
svStopSim()
exit
INFO: [Common 17-206] Exiting xsim at Wed Aug 30 01:54:59 2017...

****** xsim v2017.2 (64-bit)
  **** SW Build 1909853 on Thu Jun 15 18:39:10 MDT 2017
  **** IP Build 1909766 on Thu Jun 15 19:58:00 MDT 2017
    ** Copyright 1986-2017 Xilinx, Inc. All Rights Reserved.

source xsim.dir/tb_Zynq7000/xsim_script.tcl
# xsim {tb_Zynq7000} -autoloadwcfg -tclbatch {cmd.tcl} -key {Behavioral:sim_1:Functional:tb_Zynq7000}
Vivado Simulator 2017.2
Time resolution is 1 ps
source cmd.tcl
## set curr_wave [current_wave_config]
## if { [string length $curr_wave] == 0 } {
##   if { [llength [get_objects]] > 0} {
##     add_wave /
##     set_property needs_save false [current_wave_config]
##   } else {
##      send_msg_id Add_Wave-1 WARNING "No top level signals found. Simulator will start without a wave window. If you want to open a wave window go to 'File->New Waveform Configuration' or type 'create_wave_config' in the TCL console."
##   }
## }
ERROR: [Simulator 45-9] The current simulation was compiled without trace information.  To capture and view waveform data, please recompile the design with -debug all or typical.

これでシミュレーションが完了しました。

最後の行のエラー情報は波形情報が出力されないというエラーなのでシミュレーション自体はこれで完了になります。

シミュレーションの流れ

シミュレーションは下図のように流れます。

シミュレーションフロー

  1. SystemVerilog側からcFuncStart()関数が呼び出されます(tb_Zynq7000.svのx行目)。
  2. cFunStart()関数内のsvPlWriteでtb_Zynq7000.svのsvPlWriteファンクション(tb_Zynq7000.svのx行目)を実行します。
  3. svPlWriteファンクションでEnableが1になり、x〜x行目のalwaysで実行しているx行目のウェイトが解除され、AXIバスアクセスが実行されます。
  4. AXIバス書込みでAXI-GPIOにデータが書き込まれ、LEDが点灯します。

実機で確認

次は実機で動作確認をしてしょう。

今回の目的はテストアプリをそのまま使用して実機でテストすることであり、function.cは変更しないでZYBO上で動作検証を行います。

そこで、tb_Zynq7000.svで呼び出したsvPlWriteファンクションのラッパーファイルを作成して、下図のような構成にします。

実機でのテスト構成

これを実現するラッパーソースコードが次のコードになります。

#include "dpi.h"

#define MAP_LENG 0x00010000

int svPlWrite(unsigned int addr, unsigned int data)
{
    int fd;
    unsigned int *laddr;

    fd = open( "/dev/mem", O_RDWR | O_SYNC );
    if( fd == -1 ){
      printf( "Can't open /dev/mem.\n" );
      return 0;
    }

    laddr = mmap( NULL, MAP_LENG, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & 0xFFFF0000);
    if( addr == MAP_FAILED ){
        printf( "Error: mmap()\n" );
        return 0;
    }

    laddr[(addr & 0x0000FFFC) / 4] = data;

    munmap(laddr, MAP_LENG);

    close(fd);

    return 0;
}

int main(int argc, char **argv)
{
    cFuncStart();

    return 0;
}

今回はシミュレーションと実機確認を同じテストアプリ(Cソースアプリ)を使用して確認することなのでラッパーソースコードは簡易になっています。

これを次のようにコンパイルします。

$ gcc -c function.c
$ gcc -c main.c
$ gcc -o FuncTest function.o main.o

もちろん、これらのコンパイルはクロスコンパイルでなければいけません。

私の場合は、ZYBO上のLinuxにgccをインストールしているのでZYBO上でコンパイルしました。

これでFuncTestができあがるので次のように実行してみましょう。

$ ./FuncTest

もちろん、ZYBO上でですよ。

これで図のようにfunction.cを変更せずにLEDが点灯することを確認できました。

シミュレーションで行ったことと同様の結果が同じテストコードを使用して確認できました。

まとめ

今回はCソースコードで記述したテストアプリを改変すること無く、シミュレーションと実機の両方で実行することが目的であったため定番のLチカで動作確認を行いました。 例えば、画像データを使用したシミュレーションを行う場合、Zynq VIPとDPI-Cの環境が整う前は次のように環境を整備してシミュレーションを行っていました。

  • BFMの準備(AXIバスモデルの作成)
  • 画像データをHexデータに変換(変換アプリも作成)
  • HexデータをAXIバスに書き込むテストベンチの作成(ステートマシンの作成)
  • 出力結果を表示するベンチの作成(ファイル出力するベンチの作成)
  • 出力結果を可視化するアプリの作成(画像ファイルに戻すアプリの作成)

シミュレーション後、実機確認ではまた別のテストを行います。

Cソースコードでデータを流し込むテストアプリが組めれば、わざわざ、Verilog HDL用のHexデータを用意してシミュレーションをするなどの工数が一気に減って、次のように検証工数が削減されます。

  • テストアプリの作成

そして、実機でもそのまま使用することが出来るのです。

write: 2017/08/30/ 01:32:58