この記事は 半導体・ハードウェア開発 Advent Calendar 2017 - Qiita の6日目の記事です。
Advent-Calendarを埋めてくれるかた、今からでも募集中です!是非参加してください! 私一人では、クオリティのある記事を続けられそうにありません。。。(弱音)
ソフトウェア分野でのテストフレームワークって、非常に多くあるように思う。
- GoogleTest https://github.com/google/googletest
- CMake(CTest) https://cmake.org/cmake/help/v3.0/manual/ctest.1.html
- CppUnit https://freedesktop.org/wiki/Software/cppunit/
ただし、ハードウェア業界におけるテストフレームワークってあまり無いように思う。
主な理由として、 - シミュレーションツールが有料のものしかない。このEDAベンダが非常に古臭くてロクなテストフレームワークを提供しない。 - そもそも絶対人数が少ない - ハードウェアの開発はオープンに行われることが少なく、独自の文化がそれぞれの開発グループの中で作られている。
というわけで、数が少ないながら、オープンソースで公開されているハードウェア検証プラットふぉーおむについて調べてた。
基本的に「デジタル回路」「RTLシミュレーション」に焦点を絞っているのでご了承ください。
VUnit (https://github.com/VUnit/vunit)
個人的に大本命ではないかと思っている。
あまり癖が無く、VerilogとPythonで記述してあり使いやすい。 中身を解析しやすく、カスタマイズも行いやすい。
筆者のVUnitを使ってみた記事はこちら。
Pythonで以下のようにテスト環境を記述する。
from os.path import join, dirname from vunit.verilog import VUnit ui = VUnit.from_argv() src_path = join(dirname(__file__), "src") # ソースファイルの場所を指定できるようにする。 uart_lib = ui.add_library("uart_lib") # UART本体を格納するためのライブラリを定義する uart_lib.add_source_files(join(src_path, "*.sv")) # ライブラリにソースコードを挿入する tb_uart_lib = ui.add_library("tb_uart_lib") # UARTのテストパタンを格納するためのライブラリを定義する tb_uart_lib.add_source_files(join(src_path, "test", "*.sv")) # UARTのライブラリにソースコードを挿入する。 ui.main() # テスト実行
テストケースとテストスイート
TEST_SUITEはテストスイートを定義する。TEST_SUITEの中には、テストケースを定義している。テストケースは、テストスイート内に複数定義することが出来る。
テストケースはそのままテストを記述している。
`TEST_SUITE begin `TEST_CASE("test_send_one_byte") begin send(); check_all_was_received(); end `TEST_CASE("test_send_many_bytes") begin for (int i=0; i<7; i++) begin send(); end check_all_was_received(); end end
テストケースでは、いくつかのdefineが使用できる。
- TEST_SUITE_SETUP : 全てのテストスイート共通のセットアップを行う。
- TEST_CASE_SETUP : 全てのテストケースの共通のセットアップを行う。
- CHECK_EQUAL : アサーションを記述する。
- TEST_CASE_CLEANUP : 全てのテストケース共通で、テスト終了後の処理を行う。
- TEST_SUITE_CLEANUP : 全てのテストスイート共通で、テスト終了後の処理を行う。
- WATCHDOG : タイムアウト設定
cocotb (https://github.com/potentialventures/cocotb)
こちらもPythonで記述されたプラットフォーム。 Verilogを記述してテストを実行する。 デフォルトのVerilogシミュレータはiverilogらしい。
例えば、以下のような加算器を検証したいとする。 - hdl/adder.v
// Adder DUT module adder (input [3:0] A, input [3:0] B, output reg [4:0] X); always @(A or B) begin X = A + B; end // Dump waves initial begin $dumpfile("dump.vcd"); $dumpvars(1, adder); end endmodule
非常に単純だ。次はPythonで記述されたゴールデンモデル。 - model/adder_model.py
def adder_model(a, b): """ model of adder """ return a + b
単純だ。次に、テストパタンだ。 - cat tests/test_adder.py
# Simple tests for an adder module import cocotb from cocotb.triggers import Timer from cocotb.result import TestFailure from adder_model import adder_model import random @cocotb.test() def adder_basic_test(dut): """Test for 5 + 10""" yield Timer(2) A = 5 B = 10 dut.A = A dut.B = B yield Timer(2) if int(dut.X) != adder_model(A, B): raise TestFailure( "Adder result is incorrect: %s != 15" % str(dut.X)) else: # these last two lines are not strictly necessary dut.log.info("Ok!") @cocotb.test() def adder_randomised_test(dut): """Test for adding 2 random numbers multiple times""" yield Timer(2) for i in range(10): A = random.randint(0, 15) B = random.randint(0, 15) dut.A = A dut.B = B yield Timer(2) if int(dut.X) != adder_model(A, B): raise TestFailure( "Randomised test failed with: %s + %s = %s" % (int(dut.A), int(dut.B), int(dut.X))) else: # these last two lines are not strictly necessary dut.log.info("Ok!")
この例では、通常のテストと、ランダムテストを実施しているみたいだ。 見て分かるとおり、デザインの生成した答えと、ゴールデンモデルが生成した答えが一致しているかを確認している。
if int(dut.X) != adder_model(A, B): raise TestFailure( "Randomised test failed with: %s + %s = %s" % (int(dut.A), int(dut.B), int(dut.X)))