4ビットカウンタでわかる FPGA のための論理回路 入門 (4)

みなさんこんにちは。このコースでは FPGA を使いこなすために理解しておきたい論理回路の基本について説明します。FPGA を使って開発しているけどハードウェアはよく分からないという方は、ぜひお付き合いください。

前回は、4ビットカウンタ回路を SystemVerilog で記述し、そのコードを Vivado を使って論理合成して結果を確認しました。今回は記述した4ビットカウンタ回路の挙動をシミュレーションで確認します。

今回の記事は、「シリアル通信で Hello, FPGA (3)」と重複する内容があります。そちらの記事で説明されている内容は簡単に済ませるので合わせて読むと理解が深まります。

論理回路設計とシミュレーション

この記事は論理回路の基本を学ぶシリーズなので、手を動かす前に回路のシミュレーションについて説明します。

レジスタ

はじめに、論理回路でよく使われるレジスタについて紹介します。前回の記事で説明せずに登場させていたのですが…あらためて説明します。

レジスタ値を記憶する素子で、一般には1つ以上のフリップフロップで構成されます。フリップフロップは1ビットの信号を記憶する素子ですが、レジスタは「値」を記憶する素子です。あえて抽象的に表現しましたが、例えば、値が4ビットであれば、4ビット値を記憶するレジスタという言い方になります。もちろん中身はフリップフロップ4個です。

フリップフロップの抽象度は AND ゲートなどの論理ゲートと同じレベルですが、レジスタはもう少し高い抽象度で表現した記憶素子になります。

論理回路設計とシミュレーション

回路を設計して最終的に作られるモノは集積回路(IC)であったり FPGA に搭載する回路です。ハードウェアの開発では、仕様の策定、ハードウェア記述言語もしくは高位合成可能な言語で記述、論理合成、などといった段階を経て最終的なモノが作られます。ここで、開発の段階は大きく論理設計と物理設計に分けられます。

集積回路を作る場合、論理設計は仕様設計から AND ゲートなどの論理ゲートやフリップフロップのレベルで回路を設計するまでを指します。物理設計は論理ゲートなどをトランジスタレベルの設計に置き換え、製造するチップの規格に合わせて素子の配置を決定して配線でつなぎ(配置配線といいます)、さらにシリコンウェハに回路を焼き付けるためのパターンを作成し、といった工程を経てモノが作られるまでを指します。

FPGA に搭載する回路の場合は、論理設計は論理ゲートではなく LUT とフリップフロップのレベルで回路を設計するまでになります。物理設計は、回路を載せる FPGA チップに合わせて LUT やフリップフロップをチップ上の論理ブロックに配置し配線をつなぎ (これも配置配線といいます)、FPGA チップに転送する回路データを生成するまでになります。

設計が進みモノに近づくにつれて様々な制約が加わります。そのたびに動作を検証する必要があります。

いろいろな回路シミュレーション

回路のシミュレーションには設計の段階に応じて抽象度が異なるいくつかの種類があります。論理設計の段階では、システムレベルシミュレーション、レジスタ転送レベル (RTL) シミュレーション、ゲートレベルシミュレーションが行われます。物理設計の段階ではより詳細なゲートレベルシミュレーションやトランジスタレベルシミュレーションなどが行われます。

システムレベルシミュレーションは、開発するシステムを仕様のレベルで検証するシミュレーションです。レジスタや回路といった概念よりも抽象度が高いモデルでの検証です。

レジスタ転送レベル (RTL) シミュレーションは、レジスタとレジスタ間の演算をクロックサイクルレベルでシミュレーションします。毎クロックごとのレジスタの値が設計通りかを検証します。演算や記憶素子の遅延は含まないシミュレーションです。この記事で4ビットカウンタ回路を使って行うシミュレーションは RTL (Register Transfer Level、レジスタ転送レベル) シミュレーションです。ここでの演算は「+1する」操作で、前回の記事で確認した論理合成後の真理値表のレベルではありません。

ゲートレベルシミュレーションは、論理ゲートやフリップフロップのレベルでそれぞれの挙動をシミュレーションします。つまり、論理合成した回路の論理動作を検証します。レジスタ転送レベルでは「演算」だった部分は論理合成により組み合わせ回路に変換されます。また、それぞれのゲートの遅延を含むシミュレーションを行い、クリティカルパスなどを解析します。物理設計の段階では配置配線した回路の検証を行うため、配線の遅延やより詳細なゲート遅延を考慮したゲートレベルシミュレーションを行います。

トランジスタレベルシミュレーションは、回路素子の電気的な特性を詳細にシミュレーションします。回路素子の電圧の変化や温度による特性のばらつきを考慮した回路の動作検証を行います。

詳細なシミュレーションになるほど時間がかかるため、正しく動作する回路の設計はとても大変です。FPGA で設計する場合は集積回路を作る場合ほど詳細なシミュレーションは必要なく、シミュレーションに必要な時間が格段に少なくお手軽に回路設計ができます。FPGA 素晴らしいですね。

4ビットカウンタ回路の Vivado を使った論理シミュレーション

前回 Vivado で作成した counter プロジェクトにテストベンチを追加して、シミュレーションします。テストベンチに回路からの出力を表示させるコードを記述して、printf デバッグのように回路からの出力を確認します。また、波形ビューワを使って回路の中の信号の時間変化を確認します。

テストベンチ (SystemVerilog)

4ビットカウンタ回路のテストベンチを SystemVerilog で記述すると下のようになります。

このテストベンチは、4ビットカウンタ回路に入出力を接続し、リセットをかけ、出力の値を確認します。20クロックサイクルにわたりカウンタ回路を動作させます。4ビットカウンタ回路なので、20クロックサイクルの間にカウンタ値は0から15まで増え、0に戻り、また増えるという様子が確認できるはずです。

`timescale 1ns / 1ns

module counter_test();
    parameter STEP = 10;

    logic CLK;
    initial begin
        CLK = 1;
        forever #(STEP/2) CLK = ~CLK;
    end
    
    logic RST;
    initial begin
        RST =0;
        #(2)  RST = 1;
        #(12) RST = 0;
    end
    
    logic [3:0] out;
    counter cnt(.CLK(CLK), .RST(RST), .COUNT(out));
    
    initial begin
        $monitor($time, " count=%b", out);
        #(STEP*20) $finish;
    end
endmodule

1行目の「`timescale 1ns/1ns」はシミュレーション時刻の単位を指定します。スラッシュの前の「1ns」はシミュレーション単位を1ナノ秒で行うという指定です。コード中に「#(2)」といった記述がありますが、これは2ns を意味することになります。スラッシュの後の「1ns」はタイミングを計算する際の丸め精度を指定しています。今回はこちらも1ns としているので、例えば「#(2.1)」と記述しても「#(2)」と同じになります。より細かい精度でシミュレーションが必要であればスラッシュの後を「1ps」のように指定します。今回の RTL シミュレーションではあまり精度は必要ありませんが、ゲートレベルシミュレーションで詳細な遅延を検証する場合は精度が必要になります。

3行目、モジュール名は counter_test です。このモジュールに入出力はないので名前の宣言のみです。

4行目ではクロック周期を定数として定義しています。このシミュレーションではクロック周期が 10ns のクロック信号を使います。クロック信号は 5ns ごとに0と1を反転させます。

6~10行目はクロック信号の定義です。ビット幅を指定していないため、CLK は1ビットです。シミュレーション開始時はクロック信号は1で、クロック周期の定数により 5ns ごとに反転します (forever #(STEP/2) CLK = ~CLK;)。forever 文は無限ループです。「~」はビット演算子で否定です。

12~17行目はリセット信号の定義です。CLK と同様に RST も1ビットです。シミュレーション開始時はリセット信号は0で、時刻 2ns で1、時刻 12ns で0、以降変化しません。

19~20行目では、4ビットカウンタのモジュールからの出力を接続する out を宣言し、4ビットカウンタのインスタンスを生成します。out はカウンタの出力に合わせて4ビットにします。インスタンス生成時にカウンタのモジュールの入出力とテストモジュール内の信号との接続が記述されています。

22~25行目ではシミュレーションの出力と終了について記述します。monitor 文で、カウンタの出力をとっている out の値に変化があればその時刻($time)と out の値を2進数(%b)で出力します。シミュレーションは 200ns 時間が経過すると終了します (#(STEP*20) $finish)。

Vivado でシミュレーション

Vivado でシミュレーションするために、テストベンチのコードを Vivado のプロジェクトに追加します。前回の記事からの続きの作業です。

まずは、上のテストベンチのコードをコピー&ペーストして counter_test.sv というテキストファイルを作成してください。

「Sources」の「+」からソースコードを追加します。

「Add or create simulation sources」を選択し、次に進みます。

「Add Files」で作成した counter_test.sv を指定し、「Copy sources into project」にチェックを入れ、「Finish」します。

「Sources」の「Simulation Sources」に counter_test が追加されます。ソースコードはプロジェクトのファイル構造の中にコピーされます。

「Run Simulation」から「Run Behavioral Simulation」を選択するとシミュレーションが実行されます。

シミュレーションが終了し、ウインドウ下部の「Tcl Console」を確認すると、monitor 文で出力されたカウンタの値 (2進数) が確認できます。

シミュレーション開始時はカウンタの値を設定していないので出力は「xxxx」です。「x」は不定値を表していて、0か1か決まっていない状態です。2ns の時点でリセット信号によってカウンタの値は「0000」に初期化されます。

回路に電源を入れた瞬間のフリップフロップの値は0か1か分からない状態です。フリップフロップの値を初期化せずに回路を動作させると想定外の挙動をする可能性があるので、回路を設計する時は必ず初期化するようにしてください。C言語のプログラムなどで、変数を宣言して初期化しないまま使うと場合によっては結果がおかしくなる状況と似たイメージです。

クロック周期は 10ns なので、時刻が 10ns のところで0から1に立ち上がりますが、リセット信号は 12ns まで1にしているので、この時点のカウンタの値は0のまま変わりません(値が変わらないので出力なし)。

リセット信号を0にした後は 10ns ごとにカウンタの値が +1 されています。時刻が 160ns でカウンタの値は「1111」(10進で15) になり、次の 170ns で「0000」に戻り、また 10ns ごとに +1 されています。想定通りの挙動が確認できました。

同様の挙動が、波形のウインドウでも確認できます。波形の確認方法は「シリアル通信で Hello, FPGA (3)」を参考にしてください。

設計した回路が想定の挙動をしない時は、monitor 文を使った値の出力や波形を確認しながらデバッグをします。

論理ゲートや配線の遅延を含むシミュレーションの場合、回路が正しく動作しない原因が遅延のわずかなタイミングの差であることもあり、デバッグ作業は大変です。シミュレーションを繰り返してここまでは正しく動作するという確証を積み上げていくことが大事です。

まとめ

今回は回路のシミュレーションについて説明し、4ビットカウンタ回路の RTL シミュレーションで回路の挙動を確認しました。

このシリーズは4ビットカウンタ回路を通して論理回路の基本を説明してきました。今回までの記事であらかた説明してしまったので、最終回の次回は、同期回路を設計する上で重要なもう少し踏み込んだ内容を説明します。

東工大 佐藤真平

タイトルとURLをコピーしました