FPGA入门(4):时序逻辑(一)+https://developer.aliyun.com/article/1556542
tb_counter.v
`timescale 1ns/1ns module tb_counter(); //wire define wire led_out ; //reg define reg sys_clk ; reg sys_rst_n ; //初始化系统时钟、全局复位 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; #20 sys_rst_n <= 1'b1; end //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz always #10 sys_clk = ~sys_clk; initial begin $timeformat(-9, 0, "ns", 6); $monitor("@time %t: led_out=%b", $time, led_out); end //------------- counter_inst -------------- counter #( .CNT_MAX (25'd24 ) //实例化带参数的模块时要注意格式,当我们想要修改常数在当前模块的值时,直接在实例化参数名后面的括号内修改即可 ) counter_inst ( .sys_clk (sys_clk ), //input sys_clk .sys_rst_n (sys_rst_n ), //input sys_rst_n .led_out (led_out ) //output led_out ); endmodule
方法2实现:带标志信号的计数器
//方法2实现:带标志信号的计数器 module counter #( parameter CNT_MAX = 25'd24_999_999 //这是我们第一次使用参数的方式定义常量,使用参数的方式定义常量有很多好处,如:我们在RTL代码中实例化该模块时,如果需要两个不同计数值的计数器我们不必设计两个模块,而是直接修改参数的值即可;另一个好处是在编写Testbench进行仿真时我们也需要实例化该模块,但是我们需要仿真至少0.5s的时间才能够看出到led_out效果,这会让仿真时间很长,也会导致产生的仿真文件很大,所以我们可以通过直接修改参数的方式来缩短仿真的时间而看到相同的效果,且不会影响到RTL代码模块中的实际值,因为parameter定义的是局部参数,所以只在本模块中有效。为了更好的区分,参数名我们习惯上都要大写 ) ( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 output reg led_out //输出控制led灯 ); reg [24:0] cnt; //经计算得需要25位宽的寄存器才够500ms reg cnt_flag; //cnt:计数器计数,当计数到CNT_MAX的值时清零 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt <= 25'b0; else if(cnt == CNT_MAX) cnt <= 25'b0; else cnt <= cnt + 1'b1; //cnt_flag:计数到最大值产生的标志信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_flag <= 1'b0; else if(cnt == CNT_MAX - 1'b1) cnt_flag <= 1'b1; else cnt_flag <= 1'b0; //led_out:输出控制一个LED灯,每当计数满标志信号有效时取反 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) led_out <= 1'b0; else if(cnt_flag == 1'b1) led_out <= ~led_out; endmodule
第14讲:分频器:偶分频
分频器是数字系统设计中最常见的基本电路之一。所谓“分频”,就是把输入信号的频率变成成倍地低于输入频率的输出信号。
分频器分为偶数分频器和奇数分频器,和计数器非常类似,有时候甚至可以说就是一个东西。
模块设计
方法1实现:仅实现分频功能
波形绘制
divider_six.v
`timescale 1ns/1ns //方法1实现:仅实现分频功能 module divider_six ( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 output reg clk_out //对系统时钟6分频后的信号 ); //reg define reg [1:0] cnt; //用于计数的寄存器 //cnt:计数器从0到2循环计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt <= 2'b0; else if(cnt == 2'd2) cnt <= 2'b0; else cnt <= cnt + 1'b1; //clk_out:6分频50%占空比输出 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) clk_out <= 1'b0; else if(cnt == 2'd2) clk_out <= ~clk_out; else clk_out <= clk_out; endmodule
`timescale 1ns/1ns module tb_divider_six(); //wire define wire clk_out; //reg define reg sys_clk; reg sys_rst_n; //初始化系统时钟、全局复位 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; #20 sys_rst_n <= 1'b1; end //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz always #10 sys_clk = ~sys_clk; //------------- divider_six_inst ------------- divider_six divider_six_inst ( .sys_clk (sys_clk ), //input sys_clk .sys_rst_n (sys_rst_n ), //input sys_rst_n .clk_out (clk_out ) //output clk_out ); endmodule
方法2实现:实用的降频方法
//方法2实现:实用的降频方法 module divider_six( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 output reg clk_flag //指示系统时钟6分频后的脉冲标志信号 ); reg [2:0] cnt; //用于计数的寄存器 //cnt:计数器从0到5循环计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt <= 3'b0; else if(cnt == 3'd5) cnt <= 3'b0; else cnt <= cnt + 1'b1; //clk_flag:脉冲信号指示6分频 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) clk_flag <= 1'b0; else if(cnt == 3'd4) clk_flag <= 1'b1; else clk_flag <= 1'b0; endmodule
第15讲:分频器:奇分频
模块设计
方法1实现:仅实现分频功能
模型绘制
divider_five.v
`timescale 1ns/1ns //方法1实现:仅实现分频功能 module divider_five ( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 output wire clk_out //对系统时钟5分频后的信号 ); //reg define reg [2:0] cnt; reg clk1; reg clk2; //cnt:上升沿开始从0到4循环计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt <= 3'b0; else if(cnt == 3'd4) cnt <= 3'b0; else cnt <= cnt + 1'b1; //clk1:上升沿触发,占空比高电平维持2个系统时钟周期,低电平维持3个系统时钟周期 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) clk1 <= 1'b0; else if(cnt == 3'd2) clk1 <= 1'b1; else if(cnt == 3'd4) clk1 <= 1'b0; else clk1 <= clk1; //clk2:下降沿触发,占空比高电平维持2个系统时钟周期,低电平维持3个系统时钟周期 always@(negedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) clk2 <= 1'b0; else if(cnt == 3'd2) clk2 <= 1'b1; else if(cnt == 3'd4) clk2 <= 1'b0; else clk2 <= clk2; //clk_out:5分频50%占空比输出 assign clk_out = clk1 | clk2; endmodule
`timescale 1ns/1ns module tb_divider_five(); //wire define wire clk_out; //reg define reg sys_clk; reg sys_rst_n; //初始化系统时钟、全局复位 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; #20 sys_rst_n <= 1'b1; end //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz always #10 sys_clk = ~sys_clk; //------------- divider_five_inst -------------- divider_five divider_five_inst ( .sys_clk (sys_clk ), //input sys_clk .sys_rst_n (sys_rst_n ), //input sys_rst_n .clk_out (clk_out ) //output clk_out ); endmodule
方法2实现:实用的降频方法
//方法2实现:实用的降频方法 module divider_five ( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 output reg clk_flag //指示系统时钟5分频后的脉冲标志信号 ); reg [2:0] cnt; //用于计数的寄存器 //cnt:计数器从0到4循环计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt <= 3'b0; else if(cnt == 3'd4) cnt <= 3'b0; else cnt <= cnt + 1'b1; //clk_flag:脉冲信号指示5分频 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) clk_flag <= 1'b0; else if(cnt == 3'd3) clk_flag <= 1'b1; else clk_flag <= 1'b0; endmodule
第16讲:按键消抖
按键是最为常见的电子元器件之一,在电子设计中应用广泛;在日常生活中,遥控器、玩具、计算器等等电子产品都使用按键。
在FPGA的实验工程中,我们可以使用其作为系统复位信号或者控制信号的外部输入。
按键消抖主要针对的是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。
因而在闭合及断开的瞬间均伴随着一连串的抖动,为了保证系统能正确识别按键的开关,就必须对按键的抖动进行处理,这就是按键消抖。
模块设计
波形图绘制
key_filter.v
`timescale 1ns/1ns module key_filter #( parameter CNT_MAX = 20'd999_999 //计数器计数最大值 ) ( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 input wire key_in , //按键输入信号 output reg key_flag //key_flag为1时表示消抖后检测到按键被按下 //key_flag为0时表示没有检测到按键被按下 ); //reg define reg [19:0] cnt_20ms ; //计数器 //cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_20ms <= 20'b0; else if(key_in == 1'b1) cnt_20ms <= 20'b0; else if(cnt_20ms == CNT_MAX) //为低电平且已经达到最大值,不再进行清零 cnt_20ms <= CNT_MAX; else cnt_20ms <= cnt_20ms + 1'b1; //key_flag:当计数满20ms后产生按键有效标志位 //且key_flag在999_999时拉高,维持一个时钟的高电平 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) key_flag <= 1'b0; else if(cnt_20ms == CNT_MAX - 1'b1) key_flag <= 1'b1; else key_flag <= 1'b0; endmodule
tb_key_filter.v
`timescale 1ns/1ns module tb_key_filter(); //parameter define //为了缩短仿真时间,我们将参数化的时间值改小 //但位宽依然定义和参数名的值保持一致 //也可以将这些参数值改成和参数名的值一致 parameter CNT_1MS = 8'd19 , CNT_11MS = 8'd69 , CNT_41MS = 8'd149 , CNT_51MS = 8'd199 , CNT_60MS = 8'd249 ; //wire define wire key_flag ; //消抖后按键信号 //reg define reg sys_clk ; //仿真时钟信号 reg sys_rst_n ; //仿真复位信号 reg key_in ; //模拟按键输入 reg [7:0] tb_cnt ; //模拟按键抖动计数器 //初始化输入信号 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; key_in <= 1'b0; #20 sys_rst_n <= 1'b1; end //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz always #10 sys_clk = ~sys_clk; //tb_cnt:按键过程计数器,通过该计数器的计数时间来模拟按键的抖动过程 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) tb_cnt <= 8'b0; else if(tb_cnt == 8'd249) //计数器计数到CNT_60MS完成一次按键从按下到释放的整个过程 tb_cnt <= 8'b0; else tb_cnt <= tb_cnt + 1'b1; //key_in:产生输入随机数,模拟按键的输入情况 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) key_in <= 1'b1; //按键未按下时的状态为为高电平 else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS) || (tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS)) //在该计数区间内产生非负随机数0、1来模拟10ms的前抖动和10ms的后抖动 key_in <= {$random} % 2; else if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS) key_in <= 1'b1; //按键经过10ms的前抖动后稳定在低电平,持续时间需大于CNT_MAX else key_in <= 1'b0; //------------------------ key_filter_inst ------------------------ key_filter #( .CNT_MAX (20'd24 ) //修改的CNT_MAX值一定要小于(CNT_41MS - CNT_11MS) //否则就会表现为按键一直处于“抖动状态”而没有“稳定状态” //无法模拟出按键消抖的效果 ) key_filter_inst ( .sys_clk (sys_clk ), //input sys_clk .sys_rst_n (sys_rst_n ), //input sys_rst_n .key_in (key_in ), //input key_in .key_flag (key_flag ) //output key_flag ); endmodule