今天看了一下Verilog HDL的按鍵程序,收獲如下:
1、HDL硬件描述語言和一般的軟件編程語言有著很大的差別,它跟硬件是息息相關的,什么樣的HDL就有什么樣的硬件。要學會查看QII里面的RTL視圖,這個就是比較優略的地方。
2、Verliog HDL跟C的語法比較相近,但描述的方式有很大的不同,Verilog 里面跟多的是alawys塊,這個跟VHDL的進程有異曲同工之處啊。C跟時序不相關的多,但HDL跟時序太密切了,如果忘了這一點,理解程序就很麻煩了。
3、阻塞賦值跟非阻塞賦值也得深入的學習,往往會多出一個D觸發器來。
4、雖然每個alawys塊是并行的,但是上下兩個寄存器有聯系(<=)之后就差了一個時間周期,就像今天看的按鍵程序,剛開始看的云里霧里的,后來注意到了這一點,豁然慨然了。
自從加入EDN助學—FPGA/CPLD助學小組之后,下載了許多特權同學的視頻教材,今天又把特權同學的按鍵程序看了一遍,終于有突破式的理解了。那么首先把程序黏貼如下:
`timescale 1ns / 1ps //說明:當三個獨立按鍵的某一個被按下后,相應的LED被點亮; // 再次按下后,LED熄滅,按鍵控制LED亮滅 module sw_debounce( clk,rst_n, sw1_n,sw2_n,sw3_n, led_d1,led_d2,led_d3 ); input clk; //主時鐘信號,50MHz input rst_n; //復位信號,低有效 input sw1_n,sw2_n,sw3_n; //三個獨立按鍵,低表示按下 output led_d1,led_d2,led_d3; //發光二極管,分別由按鍵控制 //------------------------第一段-------------------------------------------------- reg[2:0] key_rst; always @(posedge clk or negedge rst_n) if (!rst_n) key_rst <= 3'b111; else key_rst <= {sw3_n,sw2_n,sw1_n}; reg[2:0] key_rst_r; //每個時鐘周期的上升沿將low_sw信號鎖存到low_sw_r中 always @ ( posedge clk or negedge rst_n ) if (!rst_n) key_rst_r <= 3'b111; else key_rst_r <= key_rst; //當寄存器key_rst由1變為0時,key_an的值變為高,維持一個時鐘周期 wire[2:0] key_an = key_rst_r & ( ~key_rst); //---------------------第二段----------------------------------------------------- reg[19:0] cnt; //計數寄存器 always @ (posedge clk or negedge rst_n) if (!rst_n) cnt <= 20'd0; //異步復位 else if(key_an) cnt <=20'd0; // 去掉干擾脈沖 else cnt <= cnt + 1'b1; reg[2:0] low_sw; always @(posedge clk or negedge rst_n) if (!rst_n) low_sw <= 3'b111; else if (cnt == 20'hfffff) //滿20ms,將按鍵值鎖存到寄存器low_sw中 cnt == 20'hfffff low_sw <= {sw3_n,sw2_n,sw1_n}; //--------------------------------------------------------------------------- reg [2:0] low_sw_r; //每個時鐘周期的上升沿將low_sw信號鎖存到low_sw_r中 always @ ( posedge clk or negedge rst_n ) if (!rst_n) low_sw_r <= 3'b111; else low_sw_r <= low_sw; //當寄存器low_sw由1變為0時,led_ctrl的值變為高,維持一個時鐘周期 wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]); reg d1; reg d2; reg d3; always @ (posedge clk or negedge rst_n) if (!rst_n) begin d1 <= 1'b0; d2 <= 1'b0; d3 <= 1'b0; end else begin //某個按鍵值變化時,LED將做亮滅翻轉 if ( led_ctrl[0] ) d1 <= ~d1; if ( led_ctrl[1] ) d2 <= ~d2; if ( led_ctrl[2] ) d3 <= ~d3; end assign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻轉輸出 assign led_d2 = d2 ? 1'b1 : 1'b0; assign led_d1 = d3 ? 1'b1 : 1'b0; endmodule
然后就開始我的備忘了。
1、第一段程序中的key_rst和key_rst_r寄存器分別存儲著差一個時鐘周期的按鍵值, 這條語句 wire[2:0] key_an = key_rst_r & ( ~key_rst)的功能是檢測一個脈沖,也就是說當寄存器key_rst由1變為0時,key_an的值變為高,維持一個時鐘周期 ,這樣就檢測到了一個時鐘周期的脈沖。
2、第二段程序與第一段的程序有點相似,前兩個always語句里其實是做了一個20ms的計數,每隔20ms就會讀取鍵值,把這個鍵值放到寄存器low_sw中,接下來的一個always語句就是把low_sw的值鎖存到low_sw_r里,這樣以來,low_sw和low_sw_r就是前后兩個時鐘周期里的鍵值了,為什么要這樣呢?看下一個語句吧:
wire [2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);
仔細分析,你會發現當沒有鍵按下時,low_sw=low_sw_r=3’b111,此時的led_ctrl=3’b000;只有當low_sw和low_sw_r的某一位分別為0和1時,才可能使led_ctrl的值改變(也就是把led_ctrl的某一位拉高)。那么這意味著當鍵值由1跳變到0時才可能把led_ctrl拉高。回顧前面的20ms賦鍵值,也就是說每20ms內如果出現按鍵被按下,那么有一個時鐘周期里led_ctrl是會被拉高的,而再看后面的程序,led_ctrl的置高就使得相應的LED燈的亮滅做一次改變,這就達到了目的。
3、在單片機的按鍵檢測程序里,當按鍵按下后,然后延時20MS,再判斷是否按下,這樣就起到了按鍵去抖動的作用,而在FPGA中,HDL語言直接對應著實際的硬件電路,沒有單片機里的循環掃描的概念。