经典的verilog键盘扫描程序作者:ilove314拿到威百仕( VibesIC )的板子后就迫不及待的开始我的学习计划,从最基础的分频程序开始,但看到这个键盘扫描程序后,直呼经典,有相见恨晚的感觉,还想说一句:威百仕( VibesIC ),我很看好你!WHY?待我慢慢道来,这个程序的综合后是0error,0warning。
想想自己编码的时候那个warning是满天飞,现在才明白HDL设计有那么讲究了,代码所设计的不仅仅是简单的逻辑以及时序的关系,更重要的是你要在代码中要表现出每一个寄存器,甚至每一个走线。
想想我写过的代码,只注意到了前者,从没有注意过后者,还洋洋自得以为自己也算是个高手了,现在想来,实在惭愧啊!学习学习在学习,这也重新激发了我对HDL设计的激情,威百仕给了我一个方向,那我可要开始努力喽!废话说了一大堆,看程序吧:(本代码经过ise7.1i综合并下载到SP306板上验证通过)//当三个独立按键的某一个被按下后,相应的LED被点亮;再次按下后,LED熄灭,按键控制LED亮灭module key_debounce(clk,rst_n,s1_n,s2_n,s3_n,s4_n,s5_n,led_d1,led_d2,led_d3,led_d 4,led_d5);input clk; //主时钟信号,10MHzinput rst_n; //复位信号,低有效input s1_n,s2_n,s3_n,s4_n,s5_n;output led_d1,led_d2,led_d3,led_d4,led_d5;reg[4:0] s_rst;always @(posedge clk or negedge rst_n)if (!rst_n) s_rst <= 5'b11111;else s_rst <= {s5_n,s4_n,s3_n,s2_n,s1_n};reg[4:0] s_rst_r;always @ ( posedge clk or negedge rst_n )if (!rst_n) s_rst_r <= 5'b11111;else s_rst_r <= s_rst;wire[4:0] s_an = s_rst_r & ( ~s_rst);reg[19:0] cnt; //计数寄存器always @ (posedge clk or negedge rst_n)if (!rst_n) cnt <= 20'd0; //异步复位else if(s_an) cnt <=20'd0;else cnt <= cnt + 1'b1;reg[4:0] low_s;always @(posedge clk or negedge rst_n)if (!rst_n) low_s <= 5'b11111;else if (cnt == 20'h30D40)low_s <= {s5_n,s4_n,s3_n,s2_n,s1_n};reg [4:0] low_s_r;always @ ( posedge clk or negedge rst_n )if (!rst_n) low_s_r <= 5'b11111;else low_s_r <= low_s;wire[4:0] led_ctrl = low_s_r[4:0] & ( ~low_s[4:0]);reg d1,d2,d3,d4,d5;always @ (posedge clk or negedge rst_n)if (!rst_n) begind1 <= 1'b0;d2 <= 1'b0;d3 <= 1'b0;d4 <= 1'b0;d5 <= 1'b0; endelse begin //if ( led_ctrl[0] ) d1 <= ~d1;if ( led_ctrl[1] ) d2 <= ~d2;if ( led_ctrl[2] ) d3 <= ~d3;if ( led_ctrl[3] ) d4 <= ~d4;if ( led_ctrl[4] ) d5 <= ~d5; endassign led_d1 = d1 ? 1'b1 : 1'b0; //LED翻转输出assign led_d2 = d2 ? 1'b1 : 1'b0;assign led_d3 = d3 ? 1'b1 : 1'b0;assign led_d4 = d4 ? 1'b1 : 1'b0;assign led_d5 = d5 ? 1'b1 : 1'b0;endmodule也许初看起来这段代码似乎有点吃力,好多的always好多的wire啊,而我们通常用得最多的判断转移好像不是主流。
的确是这样,一个好的verilog代码,用多个always语句来分摊一个大的always来执行,会使得综合起来更快,这也是接前两篇日志说到代码优化的一个值得学习的方面。
其次是wire连线很多,你要是仔细研究代码,不难发现所有的锁存器的连线关系编程者都考虑到了,这样就不会平白无故的生成意想不到的寄存器了,这也是一个优秀代码的必备要素。
上面说的是代码风格,下面就看程序的编程思想吧。
前两个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灯的亮灭做一次改变,这就达到了目的。
verilog 键盘扫描程序之debug 作者:ilove314: EDN China上次的日志《经典的verilog 键盘扫描程序》承蒙厚爱,已成博客精华,在EDN 博客主页置顶多日。
但是我发现那个经典程序还是存在一点点小bug ,且听我慢慢道来。
先放上仿真波形来说明一下问题吧:仿真说明:由于20ms 检测一次按键值对于仿真来说太长了,所以只假定16个主时钟周期就做一次检测(也就是cnt[3]的下降沿锁存键值)。
图1,sw1_n 被按下(拉底)大约5个时钟周期(<16),而此时与其相应的led_d5却改变状态了。
说明的问题是,大多数时候按键消抖其实是到不了20ms 的。
其实这个小bug 通常在下载后,测试键盘是不会有什么感觉的。
但是问题是,如果真的出现那种抖动在20ms 以内(甚至远小于20ms )的外部干扰存在时,这个bug 就不可忽视了。
因此,在原程序的基础上,做了如下的改进。
其思想是在每个主时钟(50MHz )周期里都进行一次按键检测,如果前后两次键值改变了,说明有可能键盘被按下了,此时,在下一个时钟周期将复位20ms 计数值,然后20ms 后重新锁存键值,其它的和原程序基本相同,这样就达到了真正意义上的20ms 消抖。
重新修改代码后的仿真波形如下:点击看原图图2,可以看到此时在不满16个时钟周期的键值变化是不会然led 做出变化的。
图3,按键sw3_n 的按下时间明显超过了16个时钟周期,那么在cnt 重新记到16个时钟周期后,led_d4就做出了改变。
重新修改后的代码如下://当三个独立按键的某一个被按下后,相应的LED 被点亮;再次按下后,LED 熄灭,按键控制LED 亮灭key_led.vmodule key_led(input CLOCK_50,input Q_KEY,input [4:1] KEY,output reg [4:1] LED);//++++++++++++++++++++++++++++++++++++++// 获取键值开始//++++++++++++++++++++++++++++++++++++++wire [4:1] key_val; // 键值key_debounce u0(.i_clk (CLOCK_50),.i_rst_n (Q_KEY),.i_key (KEY),.o_key_val (key_val) // 按下为0,松开为1 );//--------------------------------------// 获取键值结束//--------------------------------------//++++++++++++++++++++++++++++++++++++++// 按下键后开关LED 开始//++++++++++++++++++++++++++++++++++++++always @ (posedge CLOCK_50, negedge Q_KEY)if (!Q_KEY)LED <= 4'hF; // 0灭1亮elsecase (1'b0)key_val[1] : LED[1] <= ~LED[1];key_val[2] : LED[2] <= ~LED[2];key_val[3] : LED[3] <= ~LED[3];key_val[4] : LED[4] <= ~LED[4];default : LED <= LED ; // 缺省亮灭情况不变 endcase//--------------------------------------// 按下键后开关LED 结束//--------------------------------------endmodulekey_debounce.vmodule key_debounce(input i_clk,input i_rst_n,input [4:1] i_key, // 按下为0,松开为1 output reg [4:1] o_key_val // 键值);//++++++++++++++++++++++++++++++++++++++reg [4:1] key_samp1, key_samp1_locked;// 将i_key采集至key_samp1always @ (posedge i_clk, negedge i_rst_n)if(!i_rst_n)key_samp1 <= 4'hF;elsekey_samp1 <= i_key;// 将key_samp1锁存至key_samp1_lockedalways @ (posedge i_clk, negedge i_rst_n)if(!i_rst_n)key_samp1_locked <= 4'hF;elsekey_samp1_locked <= key_samp1;//++++++++++++++++++++++++++++++++++++++wire [4:1] key_changed1;// 当key_samp1由1变为0时// key_changed1由0变为1,只维持一个时钟周期assign key_changed1 = key_samp1_locked & (~key_samp1); //++++++++++++++++++++++++++++++++++++++reg [19:0] cnt;// 一旦有按键按下,cnt立即被清零always @ (posedge i_clk, negedge i_rst_n)if(!i_rst_n)cnt <= 20'h0;else if(key_changed1)cnt <= 20'h0;elsecnt <= cnt + 1'b1;//++++++++++++++++++++++++++++++++++++++reg [4:1] key_samp2, key_samp2_locked;// 只有当按键不变化(不抖动),且维持20ms以上时// 才将i_key采集至key_samp2always @ (posedge i_clk, negedge i_rst_n)if(!i_rst_n)key_samp2 <= 4'hF;else if(cnt == 20'hF_FFFF) // 0xFFFFF/50M = 20.9715ms key_samp2 <= i_key;// 将key_samp2锁存至key_samp2_lockedalways @ (posedge i_clk, negedge i_rst_n)if(!i_rst_n)key_samp2_locked <= 4'hF;elsekey_samp2_locked <= key_samp2; //++++++++++++++++++++++++++++++++++++++wire [4:1] key_changed2;// 当key_samp2由1变为0时// key_changed2由0变为1,只维持一个时钟周期assign key_changed2 = key_samp2_locked & (~key_samp2); //++++++++++++++++++++++++++++++++++++++// 每次按键稳定后,输出键值// 按下为0,松开为1always @ (posedge i_clk, negedge i_rst_n)if(!i_rst_n)o_key_val <= 4'hF;elseo_key_val <= ~key_changed2;//--------------------------------------endmodule。