FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ハードウェア開発・オープンソースの検証プラットフォームを調べてみた

この記事は 半導体・ハードウェア開発 Advent Calendar 2017 - Qiita の6日目の記事です。

Advent-Calendarを埋めてくれるかた、今からでも募集中です!是非参加してください! 私一人では、クオリティのある記事を続けられそうにありません。。。(弱音)


ソフトウェア分野でのテストフレームワークって、非常に多くあるように思う。

ただし、ハードウェア業界におけるテストフレームワークってあまり無いように思う。

主な理由として、 - シミュレーションツールが有料のものしかない。このEDAベンダが非常に古臭くてロクなテストフレームワークを提供しない。 - そもそも絶対人数が少ない - ハードウェアの開発はオープンに行われることが少なく、独自の文化がそれぞれの開発グループの中で作られている。

というわけで、数が少ないながら、オープンソースで公開されているハードウェア検証プラットふぉーおむについて調べてた。

基本的に「デジタル回路」「RTLシミュレーション」に焦点を絞っているのでご了承ください。

VUnit (https://github.com/VUnit/vunit)

個人的に大本命ではないかと思っている。

あまり癖が無く、VerilogPythonで記述してあり使いやすい。 中身を解析しやすく、カスタマイズも行いやすい。

筆者のVUnitを使ってみた記事はこちら。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

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)))