アフィリエイト広告
アフィリエイト広告

VerilogHDL クロック同期回路のシミュレーション

次の例題にいきましょう。今回は「クロック同期回路」です。
さすがに、さらにむずかしくなってきました。いろいろ、つまづいたり、てこずったり。

【問題9】 「クロック同期回路」の設計は難しい?
シミュレータを用い、ブロッキング代入とノン・ブロッキング代入の動作の違いについて調査。2つの代入の使い分けのポイントとは?

クロック同期回路とは

図 1. クロック同期回路 ブロック図

クロック同期回路とは、図 1 みたいな回路です。
これ、デジタル時計をつくったとき、同じ回路をつくりましたね。(過去記事)

前段のカウンタの出力を、次段のカウンタのクロック入力につないでしまうと、カウンタどうしが非同期になって遅延が発生する。なので、各カウンタに同じクロックを入力し、次段カウンタは、前段カウンタの桁上がり信号を受けたときだけクロックを有効にして、カウントさせる。

って、やりかたです。

クロック同期回路をシミュレーションする

今回の例題は、6MHz のクロック信号を与え、1 秒毎にカウントして 8 ビットバイナリデータを出力させる、というものです。

まず、6MHz のクロック CLOCK から 1 秒毎のカウントイネーブル信号 COUNT_ENABLE をつくります。これはつまり、6000000 進カウンタです。つぎに、1 秒ごとに CLOCK を有効にし、8 ビットカウンタでカウント、バイナリデータを出力します。これはいわゆる、イネーブル付き 8 ビット (256 進) カウンタです。

それら 2 つのカウンタを接続した回路のシミュレーションを行ないます。

回路記述

回路は 2 つありますが、1 つのファイルにまとめて記述します。

  1. module SECONDS_COUNTER_8bit(
  2.   input wire CLK, RST,
  3.   output reg [7:0] Q
  4. );
  5.   reg [22:0] NUMBER_OF_CLOCKS;
  6.   wire COUNT_ENABLE;
  7.   parameter CLOCKS_PER_SECOND = 23'd5999999; // 6MHz

モジュール名、入出力ポート、内部信号、パラメータの宣言です。
出力 Q はカウンタの出力なので reg 型変数宣言、レンジを [7:0] とします。

内部信号 NUMBER_OF_CLOCKS はカウントしたクロックの数で、値を保持するために reg 型です。クロックは 6MHz、2 進数で 23 ビットになりますから、レンジは [22:0] 。パラメータ CLOCKS_PER_SECOND として、定数 23’d5999999 を代入しておきます。

2 進数のビット数を求める

6000000 は、2 進数で何ビットですか?

2N = 6000000
N = log2(6000000)

log2 なんて計算できねぇ〜よ。んなときゃ底の変換公式を使いましょ。

N = log2(6000000) = log10(6000000) / log10(2) = 22.5 ≒ 23 ビット

log10 ならば、スマホの関数電卓でも簡単に計算できますね。

  1. // 23bit counter element
  2.   always @(posedge CLK or negedge RST) begin
  3.     if(1'b0 == RST)
  4.       NUMBER_OF_CLOCKS <= 23'd0;
  5.     else if(CLOCKS_PER_SECOND == NUMBER_OF_CLOCKS)
  6.       NUMBER_OF_CLOCKS <= 23'd0;
  7.     else
  8.       NUMBER_OF_CLOCKS <= NUMBER_OF_CLOCKS + 23'd1;
  9.   end

6000000 進カウンタ、つまり 23bit カウンタです。ビット数が異なるだけで、これまでと同じです。

  1. // Genaration of COUNT_ENABLE signal (Description of a carry)
  2.   assign COUNT_ENABLE = (CLOCKS_PER_SECOND == NUMBER_OF_CLOCKS);

クロックのカウント数が 5999999 になったら COUNT_ENABLE を出力します。
これは、いわゆる桁上がり信号を出力する記述方法。() 内が真であれば 1 になる、ということです。

  1. // 8bit counter element
  2.   always @(posedge CLK or negedge RST) begin
  3.     if (1'b0 == RST)
  4.       Q <= 8'd0;
  5.     else if(1'b1 == COUNT_ENABLE)
  6.       Q <= Q + 8'd1;
  7.   end
  8. endmodule

イネーブル付き 8bit カウンタ。COUNT_ENABLE が 1 のとき、つまり、クロックのカウント数が 5999999 から 0 にもどるタイミングでカウントアップされます。

テストベンチ

さてと、テストベンチは、ちょっとてこずりました。

  1. `timescale 1ns / 100ps

まず、コンパイラ指示子の「`timescale」。ユニット時間をどれだけにするか、その精度はどれだけか。バッククォート (`) で始まるので、注意。記述場所は、モジュールの中でも外でもいい、らしい。

6MHz の周期は 166.67ns なので、ユニット時間を 1ns にしてみた。で、精度? 100ps なら小数点以下 2 位ってことになるんじゃないのかなと。クロックが 6MHz だとか、1秒毎にカウントするとか、時間の指定があるので、設定してみましたが、よくわかってない (;´Д`)

  1. module SECONDS_COUNTER_8bit_TEST;
  2.   reg CLK, RST;
  3.   wire [7:0] Q;

モジュール名、入出力信号の宣言。入力は変数宣言 reg、出力はネット宣言 wire とする。

  1.   parameter OSC_FREQUENCY = 6000000; // 6MHz
  2.   always begin
  3.     CLK = 0; #(500000000/OSC_FREQUENCY);
  4.     CLK = 1; #(500000000/OSC_FREQUENCY);
  5.   end

パラメータは、クロックの周期を与えるのが一般的なのかなぁ。でも、それだと 166.67 になってしまい、丸めた値だから誤差がでてしまう。そこで、周波数の 6000000 としました。
10~13 行目がクロックの作成。CLK を 0 にして半周期、1 にして半周期、を繰り返します。

  1.   SECONDS_COUNTER_8bit SECONDS_COUNTER_8bit(CLK, RST, Q);

検証対象回路のインスタンス化。
ちなみに、モジュール名とインスタンス名は同じでも問題ないです。

  1.   initial begin
  2.     RST = 0;
  3.     repeat(OSC_FREQUENCY*0.5) @(posedge CLK);
  4.     RST = 1;
  5.     repeat(OSC_FREQUENCY*10) @(posedge CLK);
  6.     RST = 0;
  7.     repeat(OSC_FREQUENCY*0.5) @(posedge CLK);
  8.     $finish;
  9.   end

検証対象に入力を与えます。
initial は、シミュレーション開始後に 1 回だけ実行されます。が、いくつでも記述できるので、並行動作させることも可能。

repeat() は、# による遅延と同様のタイミング制御です。19 行目は、3000000 クロック待つ、つまり、0.5 秒間待つ、ということ。シミュレーション開始 0.5 秒後にカウント開始し、10 秒間動作してリセット、0.5 秒後にシミュレーション終了します。
動作時間が 10 秒では、8 ビットの出力を全部確認することができません。8 ビットカウンタを一周させるには、最低 256 秒必要です。が、じつは、時間が長いとシミュレーションの実行に長い時間がかかり、出力ファイルがバカでかくなってしまいます。30 秒ぐらいにするともう、パソコンも息絶え絶え (;´Д`)

  1.   initial begin
  2.     $dumpfile("counter_8bit_test.vcd");
  3.     $dumpvars(0, SECONDS_COUNTER_8bit_TEST);
  4.   end
  5. endmodule

GTKWave に渡す VCD ファイルの出力。いつもと同じです。

シミュレーション結果

10 秒間のシミュレーション

図 2. クロック同期回路のシミュレーション波形 (1)

図 2 が、シミュレーション結果です。

が、ここまでくるのも、なかなか大変でした。
ひとつには、まぁ毎度のことですが、コンパイルエラーの嵐。だいたいが syntax error なので、ひとつひとつ確認していけば止まります。スペルの間違いもあるけど、カンマ (,) とかセミコロン (;) が抜けてるとか、バッククォート (`) がシングルクォート (‘) になってた、とか、ね。

動作時間を長くとってしまって、いつまで待ってもシミュレーションが終わらない。時間を短くして、やっと終わったと思ったら、こんどは GTKWave の読み込みに時間がかかる。動いたと思ったら、パソコンがプチハングアップ状態 (;´Д`)

ようやく波形がみられるようになったら、なんだか結果がおかしい。なんだろうと悩むこと一晩。ノンブロッキング代入にしないといけないところが、ブロッキング代入になっていた。ふぅ。

実回路を組んでも、うまく動作しなくて悩むことはままあります。パソコン上のシミュレーションでも、同じですなぁ。あまくはないです。

CLK を拡大すると周期は 167ns ほどになっています。開始から 500ms で RST が立ち上がり、NUMBER_OF_CLOCKS が 5999999 になったとき COUNT_ENABLE が 1 になります。次のクロックで NUMBER_OF_CLOCKS が 0 にもどり、Q が 1 になる。ちなみに、このときの時間スケールは 1493999.75μs ほど。これは誤差がありますが、しかたがないです。
その後も、NUMBER_OF_CLOCKS が 5999999 から 0 にもどるときに、Q がカウントアップしています。10 秒間の動作は、想定通りです。

クロックを 6KHz にしてシミュレーション

図 3. クロック同期回路のシミュレーション (2)

6000000 進カウンタは正常に動作しています。では、8bit カウンタはどうでしょうか。CLOCK を 6KHz に落としてシミュレーションしてみました。

図 3 は、8bit カウンタが 0xFF までカウントして 0x00 にもどる部分です。
開始から約 256.5 秒で、COUNT_ENABLE が出力され、Q が 0xFF から 0x00 に戻っています。それまでの動作も、想定通りです。
8bit カウンタも問題ないと判断しました。

後記

部分的に、また、条件を変更して、なんとかシミュレーションできたかなぁってところです。一筋縄ではいかないと、わかりました。

きっと、もっとうまくやる方法があるんだろうと、思ってます。いろいろやっていくなかで、そうしたうまい方法がみつかればうれしいのですが。

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