For Good FPGA Design

UVMの環境構築!(1) 解説編

今回から、UVMの検証環境構築について解説していきます。UVMは一見難しそうですが、一度環境を作ってみると、そんなに難しくありません。UVMは検証環境の再利用性向上を目的としていますので、FPGAの検証(シミュレーション)の生産性を向上させることができます。

目次

UVMとは

UVMはUniversal Verification Methodologyの略です。論理回路のシミュレーションを行うためのライブラリです。SystemVerilogで記述されています。UVMはIEEE Std 1800.2-2017となっており、論理回路の設計者や検証(シミュレーション)の担当者にとって、必要なものとなっています。

その特徴は、検証環境の再利用を可能にすることで、検証の生産性を向上するという点にあります。

検証環境の開発は、SystemVerilogのclassの定義(作成)によります。UVMには検証環境開発のためのベースとなるクラスが定義されています。ユーザーはそのクラスを継承してclassを定義します。

よく使う用語

  • トランザクション:回路記述で使われるシグナルレベルよりも高位の記述法です。コンポーネント間の通信で使われる、情報のかたまりです。
  • コンポーネント:検証環境を構成する”モノ”たちです。例えるならば、コンポーネントはパソコン、トランザクションはパソコン間でやりとりされるデータです。
  • オブジェクト:トランザクションや、トランザクションを生成する手順などです。上記の例えでは、データに属します。

関連記事

UVMの構成

UVMのテストベンチの構成を図1に、構成要素についての説明を表1に示します。

図1 UVMのテストベンチの構成
ブロック説明
Test benchシミュレーションのトップモジュール module top(); です。
Testuvm_testを継承して定義します。
UVM Testは、テストケースのトップレベルのクラスです。トップモジュールでこのクラスをインスタンス、またはコマンドライン引数によってテストケースを選択します。
Environmentuvm_envを継承して定義します。
UVM EnvironmentはUVM Agent, UVM Scoreboard, 別のUVM Environmentをインスタンスし、検証環境の階層をつくります。
今回の解説では、1つのUVM Agentと1つのUVM Scoreboardをインスタンスします。
Agentuvm_agentを継承して定義します。
UVM Agentは、DUTを取り扱うコンポーネントで構成される、基本的な検証コンポーネントです。UVM Sequencer, UVM Driver, UVM Monitorなどで構成されます。
Sequenceruvm_sequencerを継承して定義します。
UVM Sequencerは、シーケンスを定義・実行することで、トランザクションを生成します。そして、そのトランザクションをUVM Driverに渡します。
(役割は上記ですが、実際にユーザーが書くことはほぼありません。)
Driveruvm_driverを継承して定義します。
UVM Driverは、UVM Sequencerからトランザクションを取得し、シグナルレベルに変換します。そして、DUTをドライブします。
Collectoruvm_componentを継承して定義します。
Collectorは、シグナルレベルであるDUTのレスポンスをサンプリングし、トランザクションに変換します。そして、そのトランザクションをUVM Monitorに渡します。
UVMのマニュアルでは「UVM Collector」というのは定義されていません。レスポンスのサンプリングはUVM Monitorで行うこともできますが、Collectorとしてサンプリング機能を分離することで、再利用性が高まります。
Monitoruvm_monitorを継承して定義します。
UVM Monitorは、Collectorからトランザクションを受け取り、DUTのレスポンスのチェックや、カバレッジ計算を行います。また、他の解析用コンポーネントにトランザクションを転送します。
Scoreboarduvm_scoreboardを継承して定義します。
UVM ScoreboardはDUTの動作のチェックを行います。通常、UVM AgentからDUTの入力、出力の情報をトランザクションで取得します。その後、期待値の生成と比較などを行います。
Sequencesuvm_sequenceを継承して定義します。
UVM Sequenceはコンポーネントではなく、オブジェクトです。
DUTをドライブするためのトランザクションを生成する手順を保有します。
表1 UVMのテストベンチの構成要素の説明

今回のDUT

構築するUVM検証環境のDUTとして、Muti-Clock Multiplier (MC_MULT)を作成しました(図2)。FPGAのハードウェア乗算器(DSP)を使わずに、ロジックのみで乗算を行うモジュールです。筆算の原理を利用します(図3)。名前の通り、積を算出するまでに複数クロック必要です(図4)。

図2 MC_MULTモジュール
図3 MC_MULTの原理(2進数の筆算)
図4 MC_MULTの動作

ソースコードは下記です。ちょっと長いです。パラメータ P_BW_A と P_BW_B で入出力データのビット幅を指定できます。STARTがアサートされてから積が出力されるまで、(P_BW_A+1)クロック必要です。

MC_MULT.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: MC_MULT
// Function: Multi clock multiplier
//////////////////////////////////////////////////////////////////////////////////
module MC_MULT #(
    parameter P_BW_A = 8,                               /* Bit width of A_IN. P_BW_A+1 clocks needed for calculation */
    parameter P_BW_B = 8                                /* Bit width of B_IN */
) (
    input  wire                         CLK,
    input  wire                         RST,            /* Active high */
    input  wire [P_BW_A - 1:0]          A_IN,
    input  wire [P_BW_B - 1:0]          B_IN,
    input  wire                         START,          /* Caluculation start */
    output wire [P_BW_A + P_BW_B - 1:0] MULT_OUT,       /* Multiply result. This value is valid when VALID==1'b1 */
    output reg                          VALID           /* Result valid. 1 clock pulse */
);
/********************************************************************************/
/**** Functions *****************************************************************/
/********************************************************************************/
function integer min_bw (input integer n);    /* Minimum required bit width for n */
    integer cnt;
    integer n_shift;
begin    
    cnt = 0;
    n_shift = n;
    while (n_shift > 0) begin
        n_shift = n_shift >> 1;
        cnt = cnt + 1;
    end
 
    min_bw = cnt;
end
endfunction
/********************************************************************************/
/**** Local parameters **********************************************************/
/********************************************************************************/
localparam                  P_BW_CNT = min_bw(P_BW_A - 1);
localparam [P_BW_CNT - 1:0] P_CNT_MAX = P_BW_A - 1;
/********************************************************************************/
/**** Signals *******************************************************************/
/********************************************************************************/
/* Rising edge of START*/
reg                                     start_ff1;
wire                                    start_redg;
/* Caluculation running */
reg                                     calc_running;
/* Counter */
reg  [P_BW_CNT - 1:0]                   counter;
/* Latch input data and shif */
reg  [P_BW_A + P_BW_B - 1:0]            a_lat;
reg  [P_BW_A + P_BW_B - 1:0]            b_lat;
/* Accumlator */
reg  [P_BW_A + P_BW_B - 1:0]            accumlator;
/********************************************************************************/
/**** Behaviour *****************************************************************/
/********************************************************************************/
/* Rising edge of START*/
always @ (posedge CLK or posedge RST) begin
    if (RST) begin
        start_ff1   <=  1'b0;
    end else begin
        start_ff1   <=  START;
    end
end
assign start_redg = START & (~start_ff1);
/* Caluculation running */
always @ (posedge CLK or posedge RST) begin
    if (RST) begin
        calc_running   <=  1'b0;
    end else begin
        if (start_redg == 1'b1) begin
            calc_running   <=  1'b1;
        end else begin
            if (VALID == 1'b1) begin
                calc_running   <=  1'b0;
            end else begin
                calc_running   <=  calc_running;
            end
        end
    end
end
/* Counter */
always @ (posedge CLK or posedge RST) begin
    if (RST) begin
        counter   <=  'd0;
    end else begin
        if (calc_running == 1'b1) begin
            if (counter <= P_CNT_MAX) begin
                counter <=  counter + 1'b1;
            end else begin
                counter <=  'd0;
            end
        end else begin
            counter <=  'd0;
        end
    end
end
/* Valid signal */
always @ (posedge CLK or posedge RST) begin
    if (RST) begin
        VALID   <=  1'b0;
    end else begin
        if ((calc_running == 1'b1) && (counter == P_CNT_MAX)) begin
            VALID   <=  1'b1;
        end else begin
            VALID   <=  1'b0;
        end
    end
end
/* Latch input data A and shift */
always @ (posedge CLK or posedge RST) begin
    if (RST) begin
        a_lat   <=  'd0;
    end else begin
        if (calc_running == 1'b1) begin
            a_lat   <=  a_lat >> 1;
        end else begin
            if (start_redg == 1'b1) begin
                a_lat   <=  A_IN;
            end else begin
                a_lat   <=  'd0;
            end
        end
    end
end
/* Latch input data B and shift */
always @ (posedge CLK or posedge RST) begin
    if (RST) begin
        b_lat   <=  'd0;
    end else begin
        if (calc_running == 1'b1) begin
            b_lat   <=  b_lat << 1;       /* This is correct */
//            b_lat   <=  b_lat >> 1;       /* This is miss cording */
        end else begin
            if (start_redg == 1'b1) begin
                b_lat   <=  B_IN;
            end else begin
                b_lat   <=  'd0;
            end
        end
    end
end
/* Accumlator */
always @ (posedge CLK or posedge RST) begin
    if (RST) begin
        accumlator   <=  'd0;
    end else begin
        if (calc_running == 1'b1) begin
            if (a_lat[0] == 1'b1) begin
                accumlator  <=  accumlator + b_lat;
            end else begin
                accumlator  <=  accumlator;
            end
        end else begin
            accumlator   <=  'd0;
        end
    end
end
/* Result output */
assign MULT_OUT    =  accumlator;
endmodule

まとめ

今回は、UVMの検証環境全体について解説しました。次回から各コンポーネントの作成方法について詳しく解説していきます。

シリーズ目次

ちょっと長くなっちゃいました (汗)

アバター画像
この記事を書いた人
ジーノ。大手電機メーカーで、基板設計の全般と、FPGAの設計に従事した経験を活かし、FPGAについて情報発信中。
RTL設計、シミュレーション、タイミング・クロージャ、FPGAまわりのハードウェア開発まで、幅広く取り扱っております。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA