iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
Software Development

數位 IC 設計起手式系列 第 17

Day 17: 計數器實作

  • 分享至 

  • xImage
  •  

了解了計數器的規則,這一篇我們要來實作 Johnson Counter 和 Ring Counter ,其實兩者的實作差異不大,希望大家在學會 Johnson Counter 後,可以自己嘗試設計出 Ring Counter。

Johnson Counter

實作之前,我們再來看一次電路圖!(圖片來源:Javatpoint)

https://ithelp.ithome.com.tw/upload/images/20230720/20150982yFdNTZNsp9.png

大致統整一下步驟:

  1. 實作出正反器,可以是 DFF, JKFF, TFF, ... ,但是實作時我們會以 D 型正反器為主。
  2. 連接 4 個正反器,實作 Johnson Counter
  3. Debug 時間

建構出 Johnson Counter 其實很容易,畢竟我們已經取得電路圖的樣貌,只需要將其接起來即可。但是在第一次實作時,我花了數個小時,原因是我還當時還未熟悉 Verilog 語法。我所遇到的問題以線路該用 reg 還是 wire 為大宗。因此接下來我們會細細的去講解程式碼內部。

D 型正反器

module D_ff(
    input clk, rst, load, D,
    output reg Q
);
    always @ (posedge clk) begin
        if (rst) Q <= load;
        else Q <= D;
    end
endmodule 

過去雖然有實作過 D 型正反器,但是我們多了一個調整,就是加了 rst 輸入訊號。rst 全名應為 reset ,意思是重新設定,這邊我們新增一個輸入叫 load ,可以客製化我們的初始數值。

Johnson Counter

module johnson(
    input clk, rst,
    input [3:0] load,
    output [3:0] out
);

    D_ff d3(clk, rst, load[3], ~out[0], out[3]);
    D_ff d2(clk, rst, load[2], out[3], out[2]);
    D_ff d1(clk, rst, load[1], out[2], out[1]);
    D_ff d0(clk, rst, load[0], out[1], out[0]);

endmodule

程式碼簡單明瞭,就是按著電路圖實作的。

但是當時我其實對這份程式碼不是很理解,因為我覺得我們沒有將前一個數值儲存起來。不過仔細一想,數值已經被儲存在正反器中,這也是為什麼正反器的 Q 必須指定為 reg 的原因。況且在實體化時,我們總共只有實體化 4 個正反器,而不是每每進到 Johnson Counter 就實體化一次,因此上一次的結果還儲存在正反器中。

還有一個問題是,到底要用 wire 還是 reg ?你可以這麼想,這筆數值要不要被儲存,又或者說他需不需要一直被修改。上述的例子是,正反器的輸出需要使用 reg 。一旦確認了 reg ,那麼這個輸出所連接的就必須是 wire ,如果將 reg 連接 reg ,那麼就會跳出以下訊息:

cannot be driven by primitives or continuous assignment.

Testbench

實際來測試吧!

module Johnson_tb;
    wire [3:0] in;
    reg [3:0] load;
    reg clk, rst;
    
    initial begin 
        $dumpfile("js.vcd");
        $dumpvars(0, Johnson_tb);
        load = 4'b1000;
        #1 clk = 1; rst = 1; 
        $display("[%2d s] Data = %4b", $time, in);
        for (integer i=0; i<5; i=i+1) begin
            #1 clk = 0; rst = 0; 
            #1 clk = 1; 
            $display("[%2d s] Data = %4b", $time, in);
        end
        clk = 0;
        load = 4'b1010; rst = 1;
        #1 clk = 1; 
        for (integer i=0; i<5; i=i+1) begin
            #1 clk = 0; rst = 0; 
            #1 clk = 1; 
            $display("[%2d s] Data = %4b", $time, in);
        end
        
        #3 $finish;
    end

    johnson js(clk, rst, load, in);

endmodule

這個 testbench 中,我們使用到一個新語法叫做 for loop ,寫過 C 語言的同學肯定對這個語法不陌生。因為我要產生時脈波動,使用 for loop 我認為會相對好掌握。

讓大家想一下,在進入迴圈之前如果沒有將 rst 設為 1 ,會發生什麼事?如果 clk 設為 0 ,又會發生什麼事呢?
先從簡單的問題開始,在 DFF 的實作中,觸發功能的條件是 posedge clk ,如果沒有改變時脈,或是將它設為 0 ,後方的行為就形同虛設,無任何功用。那 rst 設為 1 帶來的影響其實就是要初始化計數器,讓他從指定初始值開始計數,這樣我們才能預測之後的行為。

看一下 testbench 的結果吧!先從 $display 的內容看起。

[ 1 s] Data = xxxx
[ 3 s] Data = 1000
[ 5 s] Data = 1100
[ 7 s] Data = 1110
[ 9 s] Data = 1111
[11 s] Data = 0111
[14 s] Data = 0000
[16 s] Data = 1000
[18 s] Data = 1100
[20 s] Data = 1110
[22 s] Data = 1111

確實就和我們理想結果一樣,因此大概可以確認計數器的正確性。

最後來看看波形圖吧!
https://ithelp.ithome.com.tw/upload/images/20230722/201509823YaLMFwzEj.png

大家可以比對上述的輸出資料和波形圖,檢查是否相同。
如果相同,就代表大家都成功時做出 Johnson Counter 了!

Ring Counter

如果看懂電路圖,應該可以清楚發現 Johnson Counter 和 Ring Counter 的差異:一個 Not-Gate ! (圖片來源:Wikipedia)
https://ithelp.ithome.com.tw/upload/images/20230721/20150982QW8Q389IFb.png

大家可以試著實作看看這個計數器,做法和 Johnson Counter 一樣:

  1. 正反器實作(和 Johnson Counter 使用同一個正反器)
  2. 計數器實作
module ring(
    input clk, rst,
    input [3:0] load,
    output [3:0] out
);

    D_ff d3(clk, rst, load[3], out[0], out[3]);
    D_ff d2(clk, rst, load[2], out[3], out[2]);
    D_ff d1(clk, rst, load[1], out[2], out[1]);
    D_ff d0(clk, rst, load[0], out[1], out[0]);

endmodule
  1. 測試檔案撰寫
module Ring_tb;
    wire [3:0] in;
    reg [3:0] load;
    reg clk, rst;
    
    initial begin 
        $dumpfile("ring.vcd");
        $dumpvars(0, Ring_tb);
        load = 4'b1000;
        #1 clk = 1; rst = 1; 
        $display("[%2d s] Data = %4b", $time, in);
        for (integer i=0; i<5; i=i+1) begin
            #1 clk = 0; rst = 0; 
            #1 clk = 1; 
            $display("[%2d s] Data = %4b", $time, in);
        end
        clk = 0;
        load = 4'b1010; rst = 1;
        #1 clk = 1; 
        for (integer i=0; i<5; i=i+1) begin
            #1 clk = 0; rst = 0; 
            #1 clk = 1; 
            $display("[%2d s] Data = %4b", $time, in);
        end
        
        #3 $finish;
    end

    ring r(clk, rst, load, in);

endmodule

這個測試檔案和 Johnson Counter 的測試檔案其實是相同的,除了 load 的數值不同。我想特別提一個地方,在兩個迴圈中間有一行 clk = 0 ,如果不加上這一行會怎麼運作?我們有說到正反器的觸發是仰賴 posedge clk ,因此我們必須先製造一個低位的時脈,才有辦法產生一個 Rising Edge 。如果不加上這一行,後方的 reset 是不會有反應的。這件事情我們無法從 $display 的輸出看出來,必須要透過波形圖才又辦法得知。因此我們要善用波形圖,否則許多錯誤都無法被查找出。

[ 1 s] Data = xxxx
[ 3 s] Data = 1000
[ 5 s] Data = 0100
[ 7 s] Data = 0010
[ 9 s] Data = 0001
[11 s] Data = 1000
[14 s] Data = 1010
[16 s] Data = 0101
[18 s] Data = 1010
[20 s] Data = 0101
[22 s] Data = 1010

https://ithelp.ithome.com.tw/upload/images/20230722/2015098269qJSpcj0j.png


上一篇
Day 16: 計數器概論
下一篇
Day 18: 有限狀態機 上
系列文
數位 IC 設計起手式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言