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

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 8563|回復: 0
打印 上一主題 下一主題
收起左側

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

[復制鏈接]
跳轉到指定樓層
樓主
ID:108531 發(fā)表于 2016-3-12 22:30 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
  

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

該集主要知識點:

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

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

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

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

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

 

按鍵抖動的現象與狀態(tài)機對應的狀態(tài):

 

一、源程序

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

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

 */  

`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)機狀態(tài)

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

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

   

    // 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                        // 狀態(tài)機幾種狀態(tài)的標志

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

        DING        = 4'b0010,        // 按下時抖動狀態(tài)

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

        UING        = 4'b1000;        // 彈起時抖動狀態(tài)

   

   

    // 邊沿檢測

    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;  // 這里不能再次賦值,因為在狀態(tài)機的程序塊中需要對該信號賦值

                // 一個信號不能在多個 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;

       

       

    // 狀態(tài)機

    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):按鍵沒有被按下

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

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

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

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

                    end

                    else

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

                end

               

                DING: begin                        // 濾波抖動,按下時產生的抖動狀態(tài)

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

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

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

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

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

                    end

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

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

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

                    end

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

                        state <= DING;

                end

               

                DOWN:    begin                        // 按鍵按下狀態(tài):此時經過濾波之后處于按下狀態(tài)

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

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

                        state <= UING;

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

                    end

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

                        state <= DOWN;   

                end

               

                UING: begin                       

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

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

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

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

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

                    end

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

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

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

                    end

                    else

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

                end

               

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

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

                    state <= IDLE;   

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

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

                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、狀態(tài)機的使用

 

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

 

三、狀態(tài)機

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

 

四、仿真測試代碼

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

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

 */  

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

    endtask

   

    task key_stop;

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

    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 去終止仿真執(zhí)行。這樣頂層仿真模塊,調用這些子模塊而不會因為子模塊調用 $stop 終止仿真,導致之后的仿真無法繼續(xù)。為此我利用自己目前所掌握的知識做了一點改動,增加了輸出端口,這個端口是為了標識仿真模塊啟動和停止的狀態(tài)。在頂層模塊中只需要通過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、波形圖

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

使用道具 舉報

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

本版積分規(guī)則

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

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 久草资源在线视频 | 国产小视频在线 | 精品一区二区三区四区 | 亚洲人成一区二区三区性色 | 中文av网站 | 亚洲久草| 成人免费视频网站在线看 | 精品中文字幕一区二区三区 | 亚洲日日夜夜 | 成人妇女免费播放久久久 | 亚洲天堂成人在线视频 | 久久av网| av在线视 | 免费一二区 | gav成人免费播放视频 | 91在线精品视频 | 亚洲一区 | 国产黄色在线观看 | 99视频在线免费观看 | 伊人久久综合 | 亚洲午夜久久久 | 中文字幕av在线 | 婷婷开心激情综合五月天 | 成人免费视频观看视频 | 欧美另类日韩 | 亚洲在线免费观看 | 黄色中文字幕 | 国产在线视频一区二区 | 国产一区 | 国产综合精品一区二区三区 | 91视频精选 | 欧美国产精品 | 91亚洲精品在线观看 | 亚洲一区二区免费视频 | 欧美精品三区 | 午夜一区二区三区在线观看 | 嫩草一区二区三区 | 国产精品污www在线观看 | 国产精品日本一区二区不卡视频 | 国产精品99久久久久久www | 亚洲精品播放 |