チラシの裏は意外と白くない

最近忘れっぽくなったので調べたことをチラシの裏に書きます

順序回路とか

順序回路

組み合わせ回路の出力は過去の状態に影響されないのに対し、順序回路は過去の状態によって出力が変わる。always文を使うやつ。

D-FF

フリップフロップにはD,SR,JK,Tがあるが、D-FFが最も基本的なFFなので、これの動作を復習しておく。 クロックの立ち上がりエッジでデータラッチする場合はこんな感じ。

module dff(clock, in, out);
    input  clock;
    input  in;
    output out;

    reg q;

    always @(posedge clock) begin
        q <= in;
    end

    assign out = q;
endmodule

clockの立ち上がりエッジ(posede)でinをqに代入する。always @(***)のかっこの中身がセンシティビティリストで、複数信号、例えばclockと非同期リセットrst_nを入れる場合は、always @(posedge clock or negedge rst_n)みたいに書く。

メモリの記述

メモリはレジスタの配列として定義ができて、

reg [7:0] ram [0:1023]

と書くと、8bit x 1024 = 1024byteのメモリになる。[0:1023]は要素数であることに注意。

試しに同期式のSRAMとテストベンチを書いてみる。ポートは、

ポート bit幅 I/O 極性 機能
clock - I クロック
addr 10 I - アドレス
din 8 I - データ入力
cs - I L チップセレクト。Low-active
wen - I H ライトイネーブル
dout 8 O - データ出力

動作は、

  • 同期式
  • チップセレクトはLow-active
  • ライトイネーブルHでアクセスすると書き込み、Lで読み出し
  • データ入力ポートとデータ出力ポートは別々に持つ

とすると、

module singleport_ram(clock, addr, din, cs, wen, dout);
    input        clock;
    input  [9:0] addr;
    input  [7:0] din;
    input        cs;    // chip select, low-active
    input        wen;   // write enable
    output [7:0] dout;

    reg [7:0] mem [0:1023];
    reg [7:0] tmp_data;

    always @(posedge clock) begin
        if(!cs & wen) begin
            // write
            mem[addr] <= din;
        end
        else if(!cs & !wen) begin
            // read
            tmp_data <= mem[addr];
        end
    end

    assign dout = tmp_data;

endmodule

sim結果はこんな感じ。最低限の動作しか確認していないけど。 f:id:samurai375:20200330192913j:plain

always文を使った組み合わせ回路とラッチ回路

組み合わせ回路はassignやfunctionで記述するが、always文を使って書くことができる。よく見る記述がalways @(*) begin ~~ end。XORをalwaysを使って書くと、

module xor_always(in0, in1, out);
    input  in0;
    input  in1;
    output out;

    reg    tmp;

    always @(*) begin
        if(in0) tmp <= ~in1;
        else    tmp <=  in1;
    end

    assign out = tmp;
endmodule

のように書ける。一見XORらしくないが、ちゃんとXORになっている。always @(in0 or in1)としても同じ。

さて、このコードでelse節をコメントアウトした、以下のRTLの動作を考える。

module xor_always(in0, in1, out);
    input  in0;
    input  in1;
    output out;

    reg    tmp;

    always @(*) begin
        if(in0) tmp <= ~in1;
 //       else    tmp <=  in1;
    end

    assign out = tmp;
endmodule

in0=1の時の動作が規定されていない。真理値表を書くと、

in0 in1 out
0 0 x
0 1 x
1 0 1
1 1 0

こんな具合に、in0=1の時はx(不定)になる。simをやってみると、

f:id:samurai375:20200330204445j:plain

in0 in1 out 備考
0 0 x
0 1 x
1 0 1
1 1 0
0 1 0 前回の出力値が保持されている
0 0 0 前回の出力値が保持されている

というように、in=0の時、前回の値を保持するような回路になってしまっていて、これをラッチという。実際にschemaを書かせてみると、以下の通り。

f:id:samurai375:20200330204731j:plain

すべての入力の組み合わせにおける動作を定義しないと(今回のケースではin0=0が未定義)ラッチが生成されてしまう。例えばこのデザインをVivadoで合成すると以下のように警告が出る。

[Synth 8-327] inferring latch for variable 'tmp_reg' ["sandbox/verilog/xor/xor.v":9]

ブロッキング代入とノンブロッキング代入

"="がブロッキング代入、"<="がノンブロッキング代入。ブロッキング代入は記述順序に従って上からシーケンシャルに代入するプログラム的動作で、ノンブロッキング代入は右辺をすべて評価してから、左辺に代入する…という文章をよく見かける。で、あまり細かい意味を考えずに、ノンブロッキングで書いてきたが、改めて以下のようなコードで動作を確認してみる。

module tb();
    reg a;
    reg b;

    initial begin
            // initialize
            a = 1;
            b = 0;
        #10
            // blocking
            a = b;
            b = a;
        #10
            $display("blocking:    a = %d, b = %d", a, b);
        #10
            // initialize
            a = 1;
            b = 0;
        #10
            // non-blocking
            a <= b;
            b <= a;
        #10
            $display("nonblocking: a = %d, b = %d", a, b);
        #30
            $finish;
    end

endmodule

結果は以下、

blocking:    a = 0, b = 0
nonblocking: a = 0, b = 1

a,bをスワップする意図のコードだがブロッキング代入の場合、a=bを実行した時点で、a=0, b=0になってしまうので、その後にb=aを実行しても結果がa=0, b=0となってしまう。Cでスワップコードを書く場合は、ふつう退避用の変数を確保するが、同じことをしないといけない。

他方、ノンブロッキング代入の場合は一時変数を用意せずともスワップが成立している。a <= bでaにはb=0を代入する準備をして、さらにb <= aでbにa=1を代入する準備をして、それから時刻を進める(代入を行う)ため、スワップができる。右辺をすべて評価してから代入、という意味はこれ。

もう一つ、シフトレジスタの例を見ておく。2段のシフトレジスタ回路で、それぞれのFF出力を出力端子に接続している。b***はブロッキング代入の2段シフトレジスタ、nb***はノンブロッキング代入の2段シフトレジスタのつもり。

module shift(clock, in, b_out0, b_out1, nb_out0, nb_out1);
    input        clock;
    input  [1:0] in;
    output [1:0] b_out0,  b_out1;
    output [1:0] nb_out0, nb_out1;

    reg [1:0] tmp_b0,  tmp_b1;
    reg [1:0] tmp_nb0, tmp_nb1;

    always @(posedge clock) begin
        tmp_b0 = in;
        tmp_b1 = tmp_b0;
        tmp_nb0 <= in;
        tmp_nb1 <= tmp_nb0;
    end

    assign b_out0  = tmp_b0;
    assign b_out1  = tmp_b1;
    assign nb_out0 = tmp_nb0;
    assign nb_out1 = tmp_nb1;

endmodule

sim結果は以下で、ノンブロッキング代入のほうは正しくシフトレジスタになっているが、ブロッキング代入のほうはb_out0=b_out1になっている。

f:id:samurai375:20200331115916j:plain

schemaは以下の通りで、b_out0, b_out1ともに1段FFをたたいた結果が接続されていることがわかる。

f:id:samurai375:20200331120056j:plain