はじめに
筆者はかつて FPGA や ASIC などの論理回路の設計を行っていました。引退した今でも趣味で VHDL で RTL を作って FPGA で動かして遊んでいます。そんな時には、作った RTL を検証するために AXI Bus Function Model があると便利です。筆者は様々な事情があって AXI Bus Function Model 「Dummy_Plug」を自作しました。
この記事では、何故、自分で AXI Bus Function Model を作ることになったのかを昔話として語ります。あくまでも昔の話なので、今のみなさんの状況に合うわけではありません。まあ、年寄りの昔話として軽く聞き流してください。
Dummy_Plug とは
リポジトリ
・https://github.com/ikwzm/Dummy_Plug
名前の由来
某有名アニメに出てくる疑似パイロットシステムの名称です。なかなかピッタリの名称だったので気に入ってます。
特徴
-
シナリオによるシミュレーション
- シナリオは外部ファイル
- シナリオファイルは YAML フォーマット準拠
- シナリオには変数、ループ、サブプログラム等のプログラミング要素は一切排除
- シナリオにはランダム要素は一切排除
- シナリオを演じる(Play)のは VHDL で記述された Bus Function Model
-
VHDL だけで記述された Bus Function Model
- VHDL-93 で記述
- 使っているライブラリは ieee.std_logic_1164 と std.textio のみ
- Bus Function Model にはランダム要素は一切排除
特にランダム要素を一切排除しているのが目新しいかもしれません。これにはちゃんとした理由があります。その理由は次章の「Dummy_Plug を作った理由」で説明します。
構成例
下の図に Dummy_Plug を使った時のテストベンチの構成例を示します。
Fig.1 テストベンチの構成例
DUT (Device Under Test) はその名の通り、テスト対象デバイスです。この例では DUT には AXI4 スレーブポート(ポート名S)、AXI4 マスターポート(ポート名M)、AXI4-Stream スレーブポート(ポート名I)、AXI4-Stream マスターポート(ポート名O)を持っているとします。
それぞれのポートに対して、ポートの種類に対応した Bus Function Model を接続しています。この Bus Function Model を提供するのが Dummy_Plug です。
シナリオファイル(Scenario)は各 Bus Function Model が読み込む脚本のようなものです。シナリオファイルには、どの Bus Function Model(Player)が演じる(Playする) かを示す行があります(Scenario 中の太字部分)。Player の各々はシナリオファイルに書かれている自分のパートを演じます。
シナリオファイルは YAML フォーマットに準拠していて、人手で書くことも、Ruby や Python などのスクリプト言語に生成させることも出来ます。
Dummy_Plug を作った理由
一般的な検証手法
私はもう引退した身なので、現在の最新の検証手法は詳しくないのですが、数年前くらいでは一般的に次のようにしていました。
Fig.2 一般的な Bus Function Model を使った検証手法の構成例
DUT (Device Under Test) はその名の通り、テスト対象デバイスです。この例では DUT には AXI4 スレーブポート(ポート名S)、AXI4 マスターポート(ポート名M)、AXI4-Stream スレーブポート(ポート名I)、AXI4-Stream マスターポート(ポート名O)を持っているとします。
それぞれのポートに対して、ポートの種類に対応した Bus Function Model を接続しています。
ここまでは Dummy_Plug と同様ですが、一般的な検証手法では、Bus Function Model を動かすために SystemVerilog や VHDL などからタスク/関数を呼び出す形をとります。つまり検証のためのシナリオは SystemVerilog か VHDL で記述する必要があります。最近は DPI-C 等を使って別のプログラミング環境から呼び出すことができるものもあるようです。
大規模な LSI/ASIC の検証には、「アサーション・ベース検証」や「ランダム検証」、「機能カバレッジ」が求められます。これらを検証するシナリオをSystemVerilog や VHDL で一から書くのはさすがに生産性が悪いので、(昔は)、UVM(Universal Verification Methodology) や OS-VVM(Open Source VHDL Verification Methodology) などのメソドロジのライブラリを使うことが一般的でした(たぶん今でも使ってるんじゃないかな)。
大規模な LSI/ASIC の論理設計の際、もっとも人手と時間がかかるのが、この検証工程です。体感で申し訳ないのですが、工数(人手×時間)のだいたい8割〜9割はこの検証に費やされていたと思います。したがって、この検証工程の生産性向上が最優先課題でした。
こうした背景で生まれたのが UVM あるいは OS-VVM でした。つまりこれらは検証工程の生産性向上を第一に考えられています。
一般的な検証手法の問題点
検証はいわばバグを見つける工程です。そして UVM や OS-VVM は検証工程の生産性向上のための手法です。それでバグが見つからなければ万々歳なのですが、あいにくそう上手くは行きません。たいてい、この検証工程でバグがみつかります。バグが見つかったら、当然このバグを治さなければなりません。UVM や OS-VVM などの検証手法の問題はこのデバッグ時に起きます。
大規模 LSI/ASIC の場合、設計と検証は別のエンジニア(組織)が行うことが多いです。検証工程で見つかったバグを治すのは設計者の仕事です。(まったくの余談ですが検証者にデバッグをさせるのは好ましくありません。何故なら、見つけた人が治すやり方では、なるべくバグを見つけないようなモチベーションが検証者に生じて、検証の目的に反することになるからです。)
さて、デバッグの第一歩は、バグを再現させることです。これが一筋縄では行きません。検証者からの報告書だけでは分からないことが多いです。
検証者が作ったテストベンチのソースコードをそのまま使うのが一番楽かと思いきや、検証者と設計者では開発環境(論理シミュレーターやライブラリのバージョンなど)が異なることがあり、検証者で起こったバグが設計者の開発環境では発生しない、またはそもそも最初から動かない(コンパイル出来ない)など良くある話です。
テストベンチでバグの再現に成功したら、次はバグが発生するテストベンチの検証パターンを絞り込みます。というのもランダム検証で使ったテストベンチにはたくさんの検証パターンが含まれているからです。デバッグする際には何回もテストベンチを実行してバグを再現させる必要があります。そのたびに一からテストベンチを走らせていては時間がいくらあっても足りません。どのパターンでバグが発生するのか絞り込むこんで、そのパターンだけ実行するようにすればデバッグが捗ります。
ここで問題になるのが「ランダム」です。ランダム検証ではパラメーターやデータやタイミングなどを乱数を使って生成します。乱数というのは乱数発生器から乱数を次々と発生させたものなので、テストベンチを一から実行した場合と、そこから検証パターンだけを取り出したテストベンチを実行した場合では、同じパターンになりません。結局、設計者がバグを再現するテストベンチを一から書く羽目になります。
これらの検証手法は、大きな組織が大きなLSI/ASIC を設計する際に検証工程の生産性を向上には寄与するのかもしれませんが、個人が趣味で細々と FPGA で遊ぶには向いていない気がしました。
Dummy_Plug の設計方針
シナリオプレイヤーとシナリオライターを分離
個人で論理設計する際にも、ランダム検証は有効です。しかしランダム検証をテストベンチに組み込むとデバッグの際に泣きをみることになります。
そこで Dummy_Plug では、シナリオをベースとして、シナリオを忠実に演じるシナリオプレイヤーと、シナリオを生成するシナリオライターを明確に分離することにしました。
そして、シナリオとシナリオプレイヤーからはランダムな要素を一切排除しました。ランダム検証する時は、シナリオライターがランダムにシナリオを生成します。生成されたシナリオにはパラメータやデータやタイミングなどはすべて固定値になっていてランダムな要素はありません。つまり、同じシナリオであれば、常に同じ動作をします。こうすればデバッグの際に再現性で悩むことはありません。
シナリオライターをテストベンチから分離することで、シナリオを VHDL や SystemVerilog で書く必要がなくなりました。シナリオを生成できるのであれば、C や Ruby や Python などの一般的なプログラミング言語でシナリオライターを記述することが出来ます。
シナリオは YAML フォーマット準拠
シナリオは人が読み書き出来るようにした方がいろいろと便利です。訳の判らないバイナリの羅列だとデバッグするのもシナリオを切り貼りするのも不便です。そこで Dummy_Plug ではシナリオは YAML フォーマット準拠にしました。
次にシナリオの例を示します。これはある RTL のランダム検証した際のシナリオの1シーンを抜粋したものです。このくらいであればエディタでちょこちょこっと書くことが出来ます。テキストフォーマットなので、エディタで切り貼りするのも簡単です。
---
- MARCHAL :
- SAY : PUMP_AXI4_TO_AXI4_TEST_32_32.2.201
- CSR :
- WRITE :
ADDR : 0x00000000
ID : 10
DATA :
- 0x00001001 # O_ADDR[31:00]
- 0x00000000 # O_ADDR[63:32]
- 0x00000013 # O_SIZE[31:00]
- 0x17000003 # O_CTRL[31:00]
- 0x0000FC01 # I_ADDR[31:00]
- 0x00000000 # I_ADDR[63:32]
- 0x00000004 # I_SIZE[31:00]
- 0x17000003 # I_CTRL[31:00]
RESP : OKAY
- WAIT : {GPI(0) : 1, GPI(1) : 1, TIMEOUT: 10000}
- READ :
ADDR : 0x00000000
ID : 10
DATA :
- 0x00001005 # O_ADDR[31:00]
- 0x00000000 # O_ADDR[63:32]
- 0x0000000F # O_SIZE[31:00]
- 0x07010003 # O_CTRL[31:00]
- 0x0000FC05 # I_ADDR[31:00]
- 0x00000000 # I_ADDR[63:32]
- 0x00000000 # I_SIZE[31:00]
- 0x07010003 # I_CTRL[31:00]
RESP : OKAY
- WRITE :
ADDR : 0x00000000
ID : 10
DATA :
- 0x00000000 # O_ADDR[31:00]
- 0x00000000 # O_ADDR[63:32]
- 0x00000000 # O_SIZE[31:00]
- 0x00000000 # O_CTRL[31:00]
- 0x00000000 # I_ADDR[31:00]
- 0x00000000 # I_ADDR[63:32]
- 0x00000000 # I_SIZE[31:00]
- 0x00000000 # I_CTRL[31:00]
RESP : OKAY
- WAIT : {GPI(0) : 0, GPI(1) : 0, TIMEOUT: 10000}
- I :
- READ :
ADDR : 0x0000FC01
SIZE : 4
BURST: INCR
ID : 1
DATA : [0x14,0x12,0x7C,0x04]
RESP : OKAY
- O :
- WRITE :
ADDR : 0x00001001
SIZE : 4
BURST: INCR
ID : 2
DATA : [0x14,0x12,0x7C,0x04]
RESP : OKAY
---
VHDL-93で記述
シナリオを演じるシナリオプレイヤーは VHDL-93 で記述しました。Dummy_Plug 開発当時(2010年くらい)は、まだ SystemVerilog は年間数百万円のライセンス料を払わないと使えないようなお高いシミュレーターでしか動かなかったので、個人で遊ぶにはVerilog-HDL と VHDL の2択でした。Verilog-HDL ではテキスト処理が出来そうもなかったので VHDL にしました。VHDL で YAML のパーサーを作るのは骨が折れましたが、それはそれで面白かったです。
外部ライブラリは使わない
これはなるべく自分でコントロールできるようにしたかったのと、どのみちちょうどいいライブラリがなかったのが理由です。Dummy_Plug では ieee.std_logic_1164 と std.textio のみを使っています。
Dummy_Plug の問題点
他人が使うことを考慮していない
自分が使うのを第一にしていたため、他人が使うことはまったく考慮していません。あくまでも私の趣味で作ったものです。そのため資料とかもろくに用意していません。ごめんなさい。
VHDLシミュレーターを選ぶ
VHDL で YAML パーサーを作るというかなり無茶をした結果、動作する VHDL シミュレーターと動作しない VHDL シミュレーターが出てきてしまいました。現状、動くことが確認できているのは、次の3つです。
- GHDL 0.35 以降
- Vivado Simulator 2019.2 以前
- nvc r1.5.3 以降
GHDL はhttps://github.com/ghdl/ghdl で公開されている VHDL シミュレーターです。GHDL は昔からちゃんと Dummy_Plug が動いていたこともあって、今でも愛用しています。
Vivado Simulator は 2019.2 以前では快調に動作していたのですが、2020.1 以降、EXCEPTION_STACK_OVERFLOW が発生して落ちるようになりました。一応、Xilinx の Forum を通じて報告したのですが 2021.1 に至っても修正されていません。さらに 2021 年10月ごろ Xilinx の Forum が大幅に変更されたのを期に、私が報告したスレッドも消去されたみたいです。もう無かったことにして治したくないのでしょうか。
nvc は https://github.com/nickg/nvc で公開されている VHDL シミュレーターです。こちらも最初のころはなかなか Dummy_Plug が動作しなかったのですが、作者が私のしつこい issue に対応してくれたおかげで、動くようになりました。作者には感謝しています。
参考
Dummy_Plug 公開リポジトリ
Dummy_Plug を使ったプロジェクト
- https://github.com/ikwzm/PipeWorkTest
- https://github.com/ikwzm/ZynqMP-ACP-Adapter
- https://github.com/ikwzm/PUMP_AXI4
- https://github.com/ikwzm/PTTY_AXI
- https://github.com/ikwzm/qconv-strip-vhdl
- https://github.com/ikwzm/Merge_Sorter
- https://github.com/ikwzm/msgpack-vhdl-test
- https://github.com/ikwzm/msgpack-vhdl-examples
- https://github.com/ikwzm/axi_slave_bfm_test
VHDL シミュレーター
検証手法
- https://verificationacademy.com/courses/uvm-basics
- https://osvvm.org
- 「ベリフィケーション・メソドロジ・マニュアル」
Janick Bergeron 他著 STARC,ARM,Sysnopsys 監訳 CQ 出版社 - 「IP 機能検証ガイド」 STARC