找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 8596|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

FPGA學(xué)習(xí)-按鍵消抖模塊設(shè)計(jì)與驗(yàn)證A

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:108531 發(fā)表于 2016-3-12 22:30 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
  

Fpga 學(xué)習(xí)筆記7(狀態(tài)機(jī)設(shè)計(jì)實(shí)例):因內(nèi)容比較簡(jiǎn)單,而且在這篇日志中也有相關(guān)的知識(shí)點(diǎn),就不寫(xiě)了。

該集主要知識(shí)點(diǎn):

1、利用狀態(tài)機(jī)實(shí)現(xiàn)濾除物理按鍵所產(chǎn)生的抖動(dòng)波形。

2、非阻塞賦值的巧妙運(yùn)用

3、將狀態(tài)機(jī)與計(jì)數(shù)器功能組合使用

4、在仿真代碼中利用隨機(jī)數(shù)產(chǎn)生延時(shí)隨機(jī)的時(shí)間。

5、task 運(yùn)用方式、以及將仿真測(cè)試代碼模塊化

 

按鍵抖動(dòng)的現(xiàn)象與狀態(tài)機(jī)對(duì)應(yīng)的狀態(tài):

 

一、源程序

/* 實(shí)驗(yàn)名稱(chēng):按鍵消抖模塊設(shè)計(jì)與驗(yàn)證

 * 功能實(shí)現(xiàn):濾除按鍵抖動(dòng)的波形

 */  

`define  DEC_TIME_CNT    ((20 * 1000 * 1000) / 20 - 1)

module mytest(clk, rst_n, key_in, key_flag, key_state);

 

    input clk, rst_n, key_in;

    output reg key_flag, key_state;

   

    reg[3:0] state;                    // 狀態(tài)機(jī)狀態(tài)

    reg key_old, key_cur;            // key狀態(tài)

    wire pedge, nedge;                // 邊沿狀態(tài)

   

    // 50MHz的時(shí)鐘 = 1/50M = 0.02us = 20ns

    // 20ms = 20_000_000ns    / 20ns = 1000_000

    reg[19:0] time_cnt;            // 計(jì)數(shù)器計(jì)數(shù)值

    reg time_full;                    // 計(jì)數(shù)器已經(jīng)達(dá)到指定的時(shí)間       

    reg time_en;                    // 計(jì)數(shù)器使能

   

    localparam                        // 狀態(tài)機(jī)幾種狀態(tài)的標(biāo)志

        IDLE        = 4'b0001,        // 空閑,即高電平狀態(tài),為按下?tīng)顟B(tài)

        DING        = 4'b0010,        // 按下時(shí)抖動(dòng)狀態(tài)

        DOWN        = 4'b0100,        // 可以確定是處于按下?tīng)顟B(tài)而不是抖動(dòng)狀態(tài)

        UING        = 4'b1000;        // 彈起時(shí)抖動(dòng)狀態(tài)

   

   

    // 邊沿檢測(cè)

    always@(posedge clk, negedge rst_n)

        if(!rst_n)begin

                key_cur <= 1'b0;

                key_old <= 1'b0;

        end

        else

            begin   

                key_cur <= key_in;        // 這里由于采用了非阻塞賦值

                key_old <= key_cur;        // 所以在同一個(gè)時(shí)鐘內(nèi) key_old 采樣 key_curr 的是舊的值

            end

           

    // 判斷上升沿、下降沿

    assign pedge = !key_old & key_cur;    // 原來(lái)為低電平,現(xiàn)在為高電平,則表示檢測(cè)到上升沿

    assign nedge = key_old & !key_cur;    // 原來(lái)為高電平,現(xiàn)在為低電平,則表示檢測(cè)到下降沿   

   

   

    // 計(jì)數(shù)功能

    always@(posedge clk, negedge rst_n)

        if(!rst_n)begin

                time_cnt <= 20'd0;   

                //    time_en <= 1'b0;  // 這里不能再次賦值,因?yàn)樵跔顟B(tài)機(jī)的程序塊中需要對(duì)該信號(hào)賦值

                // 一個(gè)信號(hào)不能在多個(gè) always 塊中賦值

        end

        else if(time_en)

            time_cnt <= time_cnt + 1'b1;

        else

            time_cnt <= 20'd0;

   

   

    // 檢測(cè)時(shí)間是否已經(jīng)到了 這里指定 20ms 的時(shí)間

    always@(posedge clk, negedge rst_n)

        if(!rst_n)

            time_full <= 1'b0;

        else if(time_cnt == `DEC_TIME_CNT)   

            time_full <= 1'b1;

        else

            time_full <= 1'b0;

       

       

    // 狀態(tài)機(jī)

    always@(posedge clk, negedge rst_n)

        if(!rst_n)begin

            state <= IDLE;

            key_flag <= 1'b0;

            key_state <= 1'b1;

        end

        else begin

            case(state)

                IDLE: begin                        // 空閑狀態(tài):按鍵沒(méi)有被按下

                    key_flag <= 1'b0;            // 在空閑狀態(tài) 按鍵需要清零

                    if(nedge) begin            // 檢測(cè)到下降沿

                        state <= DING;            // 設(shè)置狀態(tài)為 DING,下個(gè)時(shí)鐘上升沿將會(huì)進(jìn)入另外一個(gè)分支

                        time_en <= 1'b1;         // 啟動(dòng)定時(shí)器            

                    end

                    else

                        state <= IDLE;            // 依據(jù)是高電平,設(shè)置狀態(tài)為 DING

                end

               

                DING: begin                        // 濾波抖動(dòng),按下時(shí)產(chǎn)生的抖動(dòng)狀態(tài)

                    if(time_full) begin         // 如果指定的時(shí)間內(nèi)沒(méi)有上升沿

                        key_flag <= 1'b1;        // 則表示處于穩(wěn)定狀態(tài),進(jìn)入按下?tīng)顟B(tài)

                        key_state <= 1'b0;    // 表示按下

                        state <= DOWN;            // 設(shè)置狀態(tài)為按下

                        time_en <= 1'b0;        // 關(guān)閉定時(shí)器

                    end

                    else if(pedge) begin        // 如果指定的時(shí)間內(nèi)出現(xiàn)上升沿說(shuō)明是處于抖動(dòng)狀態(tài)

                            state <= IDLE;        // 重新設(shè)置為空閑狀態(tài)

                            time_en = 1'b0;    // 并且關(guān)閉定時(shí)器

                    end

                    else                            // 時(shí)間未到,但也沒(méi)有出現(xiàn)電平變化則進(jìn)行維持此狀態(tài)

                        state <= DING;

                end

               

                DOWN:    begin                        // 按鍵按下?tīng)顟B(tài):此時(shí)經(jīng)過(guò)濾波之后處于按下?tīng)顟B(tài)

                    key_flag <= 1'b0;            // 將標(biāo)志位清0

                    if(pedge) begin            //    如果出現(xiàn)上升沿則表示要彈起

                        state <= UING;

                        time_en = 1'b1;         // 啟動(dòng)定時(shí)器

                    end

                    else                            // 如果沒(méi)有出現(xiàn)上升沿則維持此狀態(tài)

                        state <= DOWN;   

                end

               

                UING: begin                       

                    if(time_full)begin        // 到達(dá)指定時(shí)間

                        key_flag <= 1'b1;        // 設(shè)置按鍵標(biāo)志

                        key_state <= 1'b1;    // 設(shè)置按鍵狀態(tài)為彈起狀態(tài)

                        state <= IDLE;            // 將狀態(tài)設(shè)置為空閑

                        time_en <= 1'b0;        // 關(guān)閉定時(shí)器

                    end

                    else if(nedge)begin        // 如果出現(xiàn)下降沿

                        time_en <= 1'b0;        // 關(guān)閉定時(shí)器

                        state <= DOWN;            // 仍設(shè)置為按下?tīng)顟B(tài),即跳回之前的狀態(tài)

                    end

                    else

                        state <= UING;            // 如果時(shí)間未到并且未出現(xiàn)電平變化則維持此狀態(tài)       

                end

               

                default: begin                    // 如果出現(xiàn)其他狀態(tài),意味著被干擾出現(xiàn)錯(cuò)誤的狀態(tài)

                    time_en <= 1'b0;            // 關(guān)閉定時(shí)器

                    state <= IDLE;   

                    key_flag <= 1'b0;        // 設(shè)置按鍵標(biāo)志

                    key_state <= 1'b1;    // 設(shè)置按鍵狀態(tài)為彈起狀態(tài)       

                end

            endcase

        end

       

endmodule

       

        源碼中的知識(shí)點(diǎn):

1、一個(gè)信號(hào)不允許在兩個(gè)或兩個(gè)以上的always 程序塊中被賦值

2、利用非阻塞賦值語(yǔ)句實(shí)現(xiàn)識(shí)別上升沿下降沿

// 邊沿檢測(cè)

    always@(posedge clk, negedge rst_n)

            // 代碼省略..

                begin   

                key_cur <= key_in;   

                key_old <= key_cur;   

            end

    // 判斷上升沿、下降沿

    assign pedge = !key_old & key_cur;   

    assign nedge = key_old & !key_cur;   

 

3、狀態(tài)機(jī)的使用

 

二、RTL視圖(黃色是狀態(tài)機(jī)模塊)

 

三、狀態(tài)機(jī)

        從源碼和圖中可以看到狀態(tài)機(jī)實(shí)際上就是利用多個(gè)標(biāo)志位去對(duì)應(yīng)多種狀態(tài),按照我們的邏輯進(jìn)行組合,每一種狀態(tài)對(duì)應(yīng)一系列行為,每一種狀態(tài)都依賴(lài)前一種狀態(tài)的變化,從而實(shí)現(xiàn)模擬順序執(zhí)行的邏輯。

 

四、仿真測(cè)試代碼

/* 實(shí)驗(yàn)名稱(chēng):按鍵消抖模塊的驗(yàn)證

 * 功能實(shí)現(xiàn):驗(yàn)證按鍵消抖模塊是否符合設(shè)計(jì)要求

 */  

`timescale 1ns/1ns

`define clock_period 20

 

module mytest_tb;

 

    reg clk, rst_n, key_in;

    wire key_flag, key_state;

   

    mytest u1(clk, rst_n, key_in, key_flag, key_state);

   

    initial clk = 1;

    always #(`clock_period / 2) clk = ~clk;

   

    initial begin

        rst_n = 1'b0;

        key_in = 1'b1;

        #(`clock_period * 10) rst_n = 1'b1;

        // 延時(shí)10時(shí)鐘周期,再加1ns 錯(cuò)開(kāi)完整的時(shí)鐘這樣可以更加真實(shí)的模擬

        #(`clock_period * 10 + 1);

       

        key_Event;        // 模擬按鍵事件

        #10000;       

        key_Event;        // 模擬按鍵事件

        #10000;   

        key_Event;        // 模擬按鍵事件

        #10000;   

        key_Event;        // 模擬按鍵事件

        #10000;   

        key_Event;        // 模擬按鍵事件

        #10000;

        $stop;

    end

   

    reg[15:0] myrand;

   

    // 按鍵事件

    task key_Event;

        begin

            key_down;        // 按鍵按下

            key_up;            // 按鍵彈起

        end

    endtask

   

    // 按鍵按下

    task key_down;

        begin

            repeat(50) begin        // 模擬按下時(shí)的抖動(dòng)

                myrand = {$random} % 65536;    // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)

                //myrand = $random % 65536;    // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)

                #myrand key_in = ~key_in;

            end

            key_in = 0;

            #50000000;

        end

    endtask

   

    // 按鍵彈起

    task key_up;

        begin

            repeat(50) begin        // 模擬彈起時(shí)的抖動(dòng)

                myrand = {$random} % 65536;    // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)

                //myrand = $random} % 65536;    // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)

                #myrand key_in = ~key_in;

            end

            key_in = 1;

            #50000000;

        end

    endtask

   

endmodule

   

        源碼中的知識(shí)點(diǎn):

1、隨機(jī)數(shù)的使用

reg[15:0] myrand;

myrand = {$random} % 65536;        // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)

        //myrand = $random % 65536;        // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)

2、task的使用方法,task沒(méi)有返回值。

3、初始化延時(shí)時(shí),不延時(shí)一個(gè)完整的時(shí)鐘周期可以更加真實(shí)的模擬實(shí)際電路的波形

    initial begin

        rst_n = 1'b0;

        key_in = 1'b1;

        #(`clock_period * 10) rst_n = 1'b1;

        // 延時(shí)10時(shí)鐘周期,再加1ns 錯(cuò)開(kāi)完整的時(shí)鐘這樣可以更加真實(shí)的模擬

        #(`clock_period * 10 + 1);

 

五、波形圖

 

六、波形分析

全局分析


 
局部分析

 

七、仿真測(cè)試代碼模塊化

模擬手動(dòng)按下和釋放按鍵的仿真測(cè)試模塊(這里我對(duì)代碼做了一些修改)

1、模擬按鍵的模塊代碼

/* 模塊名稱(chēng):模擬物理按鍵按下釋放

 * 功能描述:利用 random 產(chǎn)生隨機(jī)數(shù)來(lái)產(chǎn)生隨機(jī)的時(shí)間模擬按鍵抖動(dòng)

 * 端口描述:

 *            i_key_in: 輸入,連接被測(cè)試模塊所檢測(cè)的按鍵信號(hào) 

 *            o_key_end: 輸出,1:啟動(dòng)模擬 0:模擬完成

 */

`timescale 1ns/1ns

`define clock_period 20

 

// i_key_in:按鍵信號(hào)輸入  o_key_end:啟動(dòng)結(jié)束標(biāo)志

module key_module_tb(i_key_in, o_key_end);

 

    output reg i_key_in;

    output reg o_key_end;

   

    initial begin   

        key_start;

        i_key_in = 1'b1;

        key_Event;

        #10000;       

        key_Event;

        #10000;   

        key_Event;

        #10000;   

        key_stop;

    end

   

    task key_start;

        o_key_end <= 1'b1;        // 修改端口狀態(tài)來(lái)通知其他模塊啟動(dòng)測(cè)試

    endtask

   

    task key_stop;

        o_key_end <= 1'b0;        // 修改端口狀態(tài)來(lái)通知其他模塊測(cè)試結(jié)束

    endtask

   

   

    reg[15:0] myrand;

   

    task key_Event;

        begin

            key_down;        // 按鍵按下

            key_up;            // 按鍵彈起

        end

    endtask

   

    task key_down;

        begin

            repeat(50) begin        // 模擬按下時(shí)的抖動(dòng)

                myrand = {$random} % 65536;    // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)

                //myrand = $random} % 65536;    // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)

                #myrand i_key_in = ~i_key_in;

            end

            i_key_in = 0;

            #50000000;

        end

    endtask

   

    task key_up;

        begin

            repeat(50) begin        // 模擬彈起時(shí)的抖動(dòng)

                myrand = {$random} % 65536;    // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)

                //myrand = $random} % 65536;    // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)

                #myrand i_key_in = ~i_key_in;

            end

            i_key_in = 1;

            #50000000;

        end

    endtask

   

endmodule

 

        該仿真代碼并不是完全照搬視頻中的代碼,我覺(jué)得既然要將仿真代碼模塊化,那么就不應(yīng)該在模塊里面調(diào)用 $stop 去終止仿真執(zhí)行。這樣頂層仿真模塊,調(diào)用這些子模塊而不會(huì)因?yàn)樽幽K調(diào)用 $stop 終止仿真,導(dǎo)致之后的仿真無(wú)法繼續(xù)。為此我利用自己目前所掌握的知識(shí)做了一點(diǎn)改動(dòng),增加了輸出端口,這個(gè)端口是為了標(biāo)識(shí)仿真模塊啟動(dòng)和停止的狀態(tài)。在頂層模塊中只需要通過(guò)always檢測(cè)下降沿就能得知模塊什么時(shí)候結(jié)束。

 

2、頂層仿真測(cè)試代碼

/* 實(shí)驗(yàn)名稱(chēng):仿真代碼模塊化的驗(yàn)證

 * 功能實(shí)現(xiàn):驗(yàn)證按鍵消抖模塊是否符合設(shè)計(jì)要求

 */

`timescale 1ns/1ns

`define clock_period 20

 

module mytest_tb;

 

    reg clk, rst_n;

    wire key_flag, key_state;

    wire key_in, key_end;

   

    mytest u1(clk, rst_n, key_in, key_flag, key_state);

   

    key_module_tb key(key_in, key_end);

   

    initial clk = 1;

    always #(`clock_period / 2) clk = ~clk;

   

    initial begin

        rst_n = 1'b0;

        #(`clock_period * 10) rst_n = 1'b1;

        // 延時(shí)10時(shí)鐘周期,再加1ns 錯(cuò)開(kāi)完整的時(shí)鐘這樣可以更加真實(shí)的模擬

        #(`clock_period * 10 + 1);

    end

   

    always@(negedge key_end)

        $stop;        //  檢測(cè)到模擬按鍵的模塊完成模擬,所以停止仿真

   

endmodule

 

3、波形圖

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表