久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 8579|回復: 0
收起左側

FPGA學習-按鍵消抖模塊設計與驗證A

[復制鏈接]
ID:108531 發表于 2016-3-12 22:30 | 顯示全部樓層 |閱讀模式
  

Fpga 學習筆記7(狀態機設計實例):因內容比較簡單,而且在這篇日志中也有相關的知識點,就不寫了。

該集主要知識點:

1、利用狀態機實現濾除物理按鍵所產生的抖動波形。

2、非阻塞賦值的巧妙運用

3、將狀態機與計數器功能組合使用

4、在仿真代碼中利用隨機數產生延時隨機的時間。

5、task 運用方式、以及將仿真測試代碼模塊化

 

按鍵抖動的現象與狀態機對應的狀態:

 

一、源程序

/* 實驗名稱:按鍵消抖模塊設計與驗證

 * 功能實現:濾除按鍵抖動的波形

 */  

`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;                    // 狀態機狀態

    reg key_old, key_cur;            // key狀態

    wire pedge, nedge;                // 邊沿狀態

   

    // 50MHz的時鐘 = 1/50M = 0.02us = 20ns

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

    reg[19:0] time_cnt;            // 計數器計數值

    reg time_full;                    // 計數器已經達到指定的時間       

    reg time_en;                    // 計數器使能

   

    localparam                        // 狀態機幾種狀態的標志

        IDLE        = 4'b0001,        // 空閑,即高電平狀態,為按下狀態

        DING        = 4'b0010,        // 按下時抖動狀態

        DOWN        = 4'b0100,        // 可以確定是處于按下狀態而不是抖動狀態

        UING        = 4'b1000;        // 彈起時抖動狀態

   

   

    // 邊沿檢測

    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;        // 所以在同一個時鐘內 key_old 采樣 key_curr 的是舊的值

            end

           

    // 判斷上升沿、下降沿

    assign pedge = !key_old & key_cur;    // 原來為低電平,現在為高電平,則表示檢測到上升沿

    assign nedge = key_old & !key_cur;    // 原來為高電平,現在為低電平,則表示檢測到下降沿   

   

   

    // 計數功能

    always@(posedge clk, negedge rst_n)

        if(!rst_n)begin

                time_cnt <= 20'd0;   

                //    time_en <= 1'b0;  // 這里不能再次賦值,因為在狀態機的程序塊中需要對該信號賦值

                // 一個信號不能在多個 always 塊中賦值

        end

        else if(time_en)

            time_cnt <= time_cnt + 1'b1;

        else

            time_cnt <= 20'd0;

   

   

    // 檢測時間是否已經到了 這里指定 20ms 的時間

    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;

       

       

    // 狀態機

    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                        // 空閑狀態:按鍵沒有被按下

                    key_flag <= 1'b0;            // 在空閑狀態 按鍵需要清零

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

                        state <= DING;            // 設置狀態為 DING,下個時鐘上升沿將會進入另外一個分支

                        time_en <= 1'b1;         // 啟動定時器            

                    end

                    else

                        state <= IDLE;            // 依據是高電平,設置狀態為 DING

                end

               

                DING: begin                        // 濾波抖動,按下時產生的抖動狀態

                    if(time_full) begin         // 如果指定的時間內沒有上升沿

                        key_flag <= 1'b1;        // 則表示處于穩定狀態,進入按下狀態

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

                        state <= DOWN;            // 設置狀態為按下

                        time_en <= 1'b0;        // 關閉定時器

                    end

                    else if(pedge) begin        // 如果指定的時間內出現上升沿說明是處于抖動狀態

                            state <= IDLE;        // 重新設置為空閑狀態

                            time_en = 1'b0;    // 并且關閉定時器

                    end

                    else                            // 時間未到,但也沒有出現電平變化則進行維持此狀態

                        state <= DING;

                end

               

                DOWN:    begin                        // 按鍵按下狀態:此時經過濾波之后處于按下狀態

                    key_flag <= 1'b0;            // 將標志位清0

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

                        state <= UING;

                        time_en = 1'b1;         // 啟動定時器

                    end

                    else                            // 如果沒有出現上升沿則維持此狀態

                        state <= DOWN;   

                end

               

                UING: begin                       

                    if(time_full)begin        // 到達指定時間

                        key_flag <= 1'b1;        // 設置按鍵標志

                        key_state <= 1'b1;    // 設置按鍵狀態為彈起狀態

                        state <= IDLE;            // 將狀態設置為空閑

                        time_en <= 1'b0;        // 關閉定時器

                    end

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

                        time_en <= 1'b0;        // 關閉定時器

                        state <= DOWN;            // 仍設置為按下狀態,即跳回之前的狀態

                    end

                    else

                        state <= UING;            // 如果時間未到并且未出現電平變化則維持此狀態       

                end

               

                default: begin                    // 如果出現其他狀態,意味著被干擾出現錯誤的狀態

                    time_en <= 1'b0;            // 關閉定時器

                    state <= IDLE;   

                    key_flag <= 1'b0;        // 設置按鍵標志

                    key_state <= 1'b1;    // 設置按鍵狀態為彈起狀態       

                end

            endcase

        end

       

endmodule

       

        源碼中的知識點:

1、一個信號不允許在兩個或兩個以上的always 程序塊中被賦值

2、利用非阻塞賦值語句實現識別上升沿下降沿

// 邊沿檢測

    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、狀態機的使用

 

二、RTL視圖(黃色是狀態機模塊)

 

三、狀態機

        從源碼和圖中可以看到狀態機實際上就是利用多個標志位去對應多種狀態,按照我們的邏輯進行組合,每一種狀態對應一系列行為,每一種狀態都依賴前一種狀態的變化,從而實現模擬順序執行的邏輯。

 

四、仿真測試代碼

/* 實驗名稱:按鍵消抖模塊的驗證

 * 功能實現:驗證按鍵消抖模塊是否符合設計要求

 */  

`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;

        // 延時10時鐘周期,再加1ns 錯開完整的時鐘這樣可以更加真實的模擬

        #(`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        // 模擬按下時的抖動

                myrand = {$random} % 65536;    // 產生 0 ~ 65535 隨機數

                //myrand = $random % 65536;    // 產生 -65535 ~ 65535 隨機數

                #myrand key_in = ~key_in;

            end

            key_in = 0;

            #50000000;

        end

    endtask

   

    // 按鍵彈起

    task key_up;

        begin

            repeat(50) begin        // 模擬彈起時的抖動

                myrand = {$random} % 65536;    // 產生 0 ~ 65535 隨機數

                //myrand = $random} % 65536;    // 產生 -65535 ~ 65535 隨機數

                #myrand key_in = ~key_in;

            end

            key_in = 1;

            #50000000;

        end

    endtask

   

endmodule

   

        源碼中的知識點:

1、隨機數的使用

reg[15:0] myrand;

myrand = {$random} % 65536;        // 產生 0 ~ 65535 隨機數

        //myrand = $random % 65536;        // 產生 -65535 ~ 65535 隨機數

2、task的使用方法,task沒有返回值。

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

    initial begin

        rst_n = 1'b0;

        key_in = 1'b1;

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

        // 延時10時鐘周期,再加1ns 錯開完整的時鐘這樣可以更加真實的模擬

        #(`clock_period * 10 + 1);

 

五、波形圖

 

六、波形分析

全局分析


 
局部分析

 

七、仿真測試代碼模塊化

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

1、模擬按鍵的模塊代碼

/* 模塊名稱:模擬物理按鍵按下釋放

 * 功能描述:利用 random 產生隨機數來產生隨機的時間模擬按鍵抖動

 * 端口描述:

 *            i_key_in: 輸入,連接被測試模塊所檢測的按鍵信號 

 *            o_key_end: 輸出,1:啟動模擬 0:模擬完成

 */

`timescale 1ns/1ns

`define clock_period 20

 

// i_key_in:按鍵信號輸入  o_key_end:啟動結束標志

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;        // 修改端口狀態來通知其他模塊啟動測試

    endtask

   

    task key_stop;

        o_key_end <= 1'b0;        // 修改端口狀態來通知其他模塊測試結束

    endtask

   

   

    reg[15:0] myrand;

   

    task key_Event;

        begin

            key_down;        // 按鍵按下

            key_up;            // 按鍵彈起

        end

    endtask

   

    task key_down;

        begin

            repeat(50) begin        // 模擬按下時的抖動

                myrand = {$random} % 65536;    // 產生 0 ~ 65535 隨機數

                //myrand = $random} % 65536;    // 產生 -65535 ~ 65535 隨機數

                #myrand i_key_in = ~i_key_in;

            end

            i_key_in = 0;

            #50000000;

        end

    endtask

   

    task key_up;

        begin

            repeat(50) begin        // 模擬彈起時的抖動

                myrand = {$random} % 65536;    // 產生 0 ~ 65535 隨機數

                //myrand = $random} % 65536;    // 產生 -65535 ~ 65535 隨機數

                #myrand i_key_in = ~i_key_in;

            end

            i_key_in = 1;

            #50000000;

        end

    endtask

   

endmodule

 

        該仿真代碼并不是完全照搬視頻中的代碼,我覺得既然要將仿真代碼模塊化,那么就不應該在模塊里面調用 $stop 去終止仿真執行。這樣頂層仿真模塊,調用這些子模塊而不會因為子模塊調用 $stop 終止仿真,導致之后的仿真無法繼續。為此我利用自己目前所掌握的知識做了一點改動,增加了輸出端口,這個端口是為了標識仿真模塊啟動和停止的狀態。在頂層模塊中只需要通過always檢測下降沿就能得知模塊什么時候結束。

 

2、頂層仿真測試代碼

/* 實驗名稱:仿真代碼模塊化的驗證

 * 功能實現:驗證按鍵消抖模塊是否符合設計要求

 */

`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;

        // 延時10時鐘周期,再加1ns 錯開完整的時鐘這樣可以更加真實的模擬

        #(`clock_period * 10 + 1);

    end

   

    always@(negedge key_end)

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

   

endmodule

 

3、波形圖

回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

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

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 三级视频国产 | 亚洲最大福利网 | 中文字幕一区二区三区不卡 | 夜夜爆操 | 91爱爱·com| 无吗视频 | 99福利网| 男女羞羞视频免费看 | 午夜一区二区三区视频 | 国产精品成人在线播放 | 久久成人免费视频 | 99精品欧美一区二区三区综合在线 | 国产视频三区 | 国产成人免费视频网站视频社区 | 欧美视频第三页 | 欧美精品久久久久久久久久 | 国产不卡一区 | 伊人免费网 | 亚洲精品日韩一区二区电影 | 一区二区三区精品视频 | 欧美一级视频在线观看 | 高清色| 欧美电影网 | 成人在线视频免费观看 | 亚洲九九精品 | 91在线视频免费观看 | 欧美二区在线 | 中文字幕免费 | www.啪啪.com| 色精品 | 亚洲成av| 一二三区av| 涩涩导航 | 看片一区 | 综合久久久久久久 | 久草福利 | 亚洲一区中文字幕 | 日韩中文字幕一区 | 成人在线视频免费播放 | 精品国产乱码久久久久久丨区2区 | 一本大道久久a久久精二百 国产成人免费在线 |