今回の例題はこれ。
波形から回路をつくる
問題の波形は、図 1 のとおり。4MHz のクロックから発生された信号です。このような信号を発生させる回路をつくれ、との指令です。
こんな波形、見たことありますねぇ。シフトレジスタ?リングカウンタ?
そうそう、最近つくったことがある回路、デコーダですよ。
7 セグメント LED のダイナミック点灯をやったとき、デジットを制御するための 2to4 ラインデコーダの出力波形が、こんなでした (過去記事)。そのときはアクティブローでしたので、波形は HIGH、LOW が逆転してましたが、同じことです。
なので答えは、デコーダです。
いや、シフトレジスタもリングカウンタも正解ですし。
シフトレジスタによる回路構成
デコーダに関しては、すでに 7 セグメントデコーダで類似した回路記述をやりました。例題サイトの解説にもあるように、いくつかの回路が考えられますが、例題に倣ってシフトレジスタを考えてみたいと思います。
シフトレジスタを選んだもうひとつの理由は、「連接演算子 { } 」にあります。4 ビット加算器のシミュレーションをやったときに出てきているのですが、使い方がよくわかっていませんでしたので、もう少し勉強してみましょう。
クロックは 4MHz (周期 250ns)、出力信号は 1ms 幅なので、イネーブル信号も 1ms 幅です。したがって、クロックを 4000 分周する必要があります。4000 分周回路からイネーブル信号を出力し、イネーブル付きのシフトレジスタを制御します。
回路記述
4000 分周回路と、シフトレジスタを接続した回路の記述です。
- module SHIFTREGISTER(
- input wire CLK, RES,
- output reg [3:0] Q
- );
- wire EN;
- reg [11:0] NUMBER_OF_CLOCKS;
- parameter CLOCKS_PER_EN = 12'd3999;
モジュール名、入出力ポート、内部信号、パラメータの宣言です。
4000 は 2 進数で何ビットですか? もう、スマホの電卓ですぐに計算できますね。
N = log(4000) / log(2) ≒ 12
NUMBER_OF_CLOCKS は、カウントしたクロックの数。3999 クロック目でイネーブル信号 EN を出力して 0 に戻します。パラメータ CLOCKS_PER_EN は EN を出力するカウント数、12’d3999 です。
- // 4000 Divider
- always @(posedge CLK or negedge RES) begin
- if(1'b0 == RES)
- NUMBER_OF_CLOCKS <= 12'd0;
- else if(CLOCKS_PER_EN == NUMBER_OF_CLOCKS)
- NUMBER_OF_CLOCKS <= 12'd0;
- else
- NUMBER_OF_CLOCKS <= NUMBER_OF_CLOCKS + 12'd1;
- end
- assign EN = (CLOCKS_PER_EN == NUMBER_OF_CLOCKS);
4000 分周器の回路。リセットは非同期です。21 行目が、桁上がり、ここでは EN を出力する記述。
- // Shift Register
- always @(posedge CLK or negedge RES) begin
- if(1'b0 == RES)
- Q <= 4'b0001;
- else if(1'b1 == EN)
- Q <= {Q[2:0], Q[3]};
- end
- endmodule
シフトレジスタの記述は、初めまして、です。
25~26 行目。リセットが入ったとき、出力を 0b0001 にしています。これ、本来のシフトレジスタなら 0b0000 になるべきです。ここに 0b0001 を入れるってのは、リングカウンタを構成させる、ってことだな。リングカウンタもジョンソンカウンタも、シフトレジスタでつくりますから。
そして、27~28 行目。EN が入ったとき、出力 Q を「 {Q[2:0], Q[3]} 」にしています。Q[2:0] とは、Q の bit2~bit0 を指す。たとえば 0b0010 ならば「010」。Q[3] は、Q の bit3、つまり「0」です。
{} は連接演算子で、ビット列をつないで多ビット信号としてあつかいます。
{ Q[2:0], Q[3] } = { 010, 0 } = 0100
ということで、0b0010 が 0b0100 になった。1 がシフトした、ってことです。
ちなみに、通常のシフトレジスタの場合は、シリアル入力を SERIAL として
Q <= { Q[2:0], SERIAL };
のように記述します。
連接演算子は左辺でも利用できます。4 ビット加算器のシミュレーションにでてきた
assign {carry, X} = A + B;
がそれ。
carry は 1 ビット、A、B、X はそれぞれ 4 ビットです。A と B を加算したとき、桁上がりがあると 5 ビットになる。左辺では、carry に先頭の 1 ビット (桁上がりビット)、X に 4 ビット (加算された値) が格納される。左辺と右辺のビット数は、同じにするのが吉です。
テストベンチ
今回は、テストベンチにも新しいしかけをみつけました。
- `timescale 1ns / 1ns
- module SHIFTREGISTER_TEST;
- reg CLK, RES;
- wire [3:0] Q;
- parameter OSC_PERIOD = 250; // 4MHz
- always begin
- CLK = 0; #(OSC_PERIOD /2);
- CLK = 1; #(OSC_PERIOD /2);
- end
4MHz のクロックの周期は 250ns。なので、`timescale を 1ns としました。
クロック周期を 250 としてパラメータ宣言し、クロックを発生させています。
- SHIFTREGISTER SHIFTREGISTER(CLK, RES, Q);
- //defparam SHIFTREGISTER.CLOCKS_PER_EN = 39;
シフトレジスタ回路をインスタンス化しています。
16 行目。コメントアウトしていますが、defparam は、下位モジュールのパラメータを上書きする記述です。ここのコメントを外すと、回路記述で宣言したパラメータ CLOCKS_PER_EN = 12’3999 が上書きされて、12’d39 になっちゃう。つまり、本来なら 3999 クロック数えるところが、39 クロックで EN を出力させることになる。
シミュレーションをちゃちゃっと進める、1 つの方法ってことでしょうか。あるいは、シミュレーションの流れを変化させるおまじないかもしれません (^_^;)
なお、つぎのように、下位モジュール接続 (インスタンス化) 時にパラメータ割り当てする記述スタイルもあります。
- SHIFTREGISTER #(.CLOCKS_PER_EN(39)) SHIFTREGISTER(CLK, RES, Q);
#() 内に、パラメータ名を指定する場合はドットをつけます。数字だけでもいいんですが、複数のパラメータがあるときは順番を間違えてはいけません。
このあたり、記述スタイルもいろいろあるようですが、どれかに決めておくのがよさそうです。
- initial begin
- RES = 0;
- repeat(2) @(posedge CLK);
- RES = 1;
- repeat(40000) @(posedge CLK);
- RES = 0;
- repeat(2) @(posedge CLK);
- $finish;
- end
入力を発生させます。
RES=0 でスタートし、2 クロック待って RES=1。40000 クロック (=10ms) のあいだ動作させて、RES=0 に戻す。2 クロック待って終了。
- initial begin
- $dumpfile("shiftregister_test.vcd");
- $dumpvars(0, SHIFTREGISTER_TEST);
- end
- endmodule
毎度の VCD ファイル出力です。
シミュレーション結果
シミュレーション結果です。
図 3 は、3999 カウント目で EN を出力した、設計通りのシミュレーションです。
EN が 1ms ごとに出力され、出力 Q がシフトしていっています。
図 4 は、パラメータ割り当てを 39 に変更したシミュレーション結果です。
39 カウント目で、EN が出力され、Q が変化している部分を拡大しています。クロックの周期が 250ns になっていることも、確認できます。
後記
前回の、クロック同期回路のシミュレーションでは、動作の確認にすこしばかり苦労しました。が、今回「パラメータ割り当て」という方法を知ったので、前回のようなときに役立ちそうです。
まだまだ、まぁ C 言語なんかでも同様ですが、さまざまなこと、知らないこと、理解不能なデープな機能もあることでしょう。自分なりに、うまく利用できたら、と思っています。
さて、例題にさせていただいたサイトでは、次は「期末考査」のようです。基本的なことは、これまでやってきたとおりですので、俺は、パスしま〜す。テスト、きら〜い (^_^;)