野火FPGA入门(4):时序逻辑电路

news2025/7/18 7:57:30

文章目录

    • 第11讲:寄存器
    • 第12讲:阻塞赋值与非阻塞赋值
    • 第13讲:计数器
    • 第14讲:分频器:偶分频
    • 第15讲:分频器:奇分频
    • 第16讲:按键消抖

组合逻辑存在竞争冒险

第11讲:寄存器

寄存器具有存储功能,一般是由D触发器构成,由时钟脉冲控制,每个D触发器能够存储一位二进制码。

D触发器的工作原理:在一个脉冲信号(一般为晶振产生的时钟脉冲)上升沿或下降沿的作用下,将信号从输入端D送到输出端Q,如果时钟脉冲的边沿信号未出现,即使输入信号改变,输出信号仍然保持原值,且寄存器拥有复位清零功能,其复位又分为同步复位和异步复位。

模块设计
在这里插入图片描述
在这里插入图片描述

异步复位解决毛刺的影响,提高了可靠性
在这里插入图片描述
异步复位有延迟一拍的效果
在这里插入图片描述

波形图绘制
在这里插入图片描述

flip_flop.v

`timescale  1ns/1ns

module  flip_flop
(
    input   wire    sys_clk     ,   //系统时钟50Mhz,后面我们都是设计的时序电路,所以一定要有时钟,时序电路中几乎所有的信号都是伴随着时钟的沿(上升沿或下降沿,习惯上用上升沿)进行工作的
    input   wire    sys_rst_n   ,   //全局复位,复位信号的主要作用是在系统出现问题是能够回到初始状态,或一些信号的初始化时需要进行复位
    input   wire    key_in      ,   //输入按键

    output  reg     led_out         //输出控制led灯
);

//同步复位
//led_out:led灯输出的结果为key_in按键的输入值
always@(posedge sys_clk)    //当always块中的敏感列表为检测到sys_clk上升沿时执行下面的语句
    if(sys_rst_n == 1'b0)   //sys_rst_n为低电平时复位,但是这个复位有个大前提,那就是当sys_clk的上升沿到来时,如果检测到sys_rst_n为低电平则复位有效。
        led_out <= 1'b0;    //复位的时候一定要给寄存器变量赋一个初值,一般情况下赋值为0(特殊情况除外),在描述时序电路时赋值符号一定要使用“<=”
    else
        led_out <= key_in;

/*
//异步复位
//led_out:led灯输出的结果为key_in按键的输入值
always@(posedge sys_clk or negedge sys_rst_n) //当always块中的敏感列表为检测到sys_clk上升沿或sys_rst_n下降沿时执行下面的语句
    if(sys_rst_n == 1'b0)                     //sys_rst_n为低电平时复位,且是检测到sys_rst_n的下降沿时立刻复位,不需等待sys_clk的上升沿来到后再复位
        led_out <= 1'b0;
    else
        led_out <= key_in;
 */

endmodule

同步复位RTL视图:
在这里插入图片描述
异步复位RTL视图
在这里插入图片描述

tb_flip_flop.v

`timescale  1ns/1ns

module  tb_flip_flop();

//wire  define
wire            led_out     ;

//reg   define
reg             sys_clk     ;
reg             sys_rst_n   ;
reg             key_in      ;

//初始化系统时钟、全局复位和输入信号
initial begin
    sys_clk    = 1'b1;  //时钟信号的初始化为1,且使用“=”赋值,其他信号的赋值都是用“<=”
    sys_rst_n <= 1'b0;  //因为低电平复位,所以复位信号的初始化为0
    key_in    <= 1'b0;  //输入信号按键的初始化,为0和1均可
    #20
    sys_rst_n <= 1'b1;  //初始化20ns后,复位释放,因为是低电平复位,所示释放时,把信号拉高,电路开始工作
    #210
    sys_rst_n <= 1'b0;  //为了观察同步复位和异步复位的区别,在复位释放后电路工作210ns后再让复位有效。之所以选择延时210ns而不是200ns或220ns,是因为能够使复位信号在时钟下降沿时复位,能够清晰的看出同步复位和异步复位的差别
    #40
    sys_rst_n <= 1'b1;  //复位40ns后再次让复位释放掉
end

//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz
always #10 sys_clk = ~sys_clk; //使用always产生时钟信号,让时钟每隔10ns反转一次,即一个时钟周期为20ns,换算为频率为50Mhz

//key_in:产生输入随机数,模拟按键的输入情况
always #20 key_in <= {$random} % 2; //取模求余数,产生非负随机数0、1,每隔20ns产生一次随机数(之所以每20ns产生一次随机数而不是之前的每10ns产生一次随机数,是为了在时序逻辑中能够保证key_in信号的变化的时间小于等于时钟的周期,这样就不会产生类似毛刺的变化信号,虽然产生的毛刺在时序电路中也能被滤除掉,但是不便于我们观察波形)

initial begin
    $timeformat(-9, 0, "ns", 6);
    $monitor("@time %t: key_in=%b led_out=%b", $time, key_in, led_out);
end

//------------- flip_flop_inst -------------
flip_flop   flip_flop_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

    .led_out    (led_out    )   //output    led_out
);

endmodule

第12讲:阻塞赋值与非阻塞赋值

阻塞赋值的赋值号用“=”表示,对应的电路结构往往与触发沿没有关系,只与输入电平的电话有关系。它的操作可以认为是只有一个步骤的操作,即计算赋值号右边的语句并更新赋值号左边的语句,此时不允许有来自任何其他Verilog语句的干扰,直到现行得到赋值完成,才允许下一条的赋值语句的执行。

串行块(begin-end)中,各条阻塞赋值语句将以它们在顺序块中的排列次序依次执行。

非阻塞赋值的赋值号用“<=”表示,对应的电路结构往往与触发沿有关系,只有在触发沿的时刻才能进行非阻塞赋值。
它的操作可以看作为两个步骤的过程:在赋值开始时刻,计算赋值号右边的语句。在赋值结束时刻,更新赋值号左边的语句。
在计算非阻塞语句赋值号右边的语句和更新赋值号左边的语句期间,允许其他的Verilog语句同时进行操作。
非阻塞操作只能用于对寄存器类型变量进行赋值,因此只能用于“initial”和“always”块中,不允许用于连续赋值“assign”

阻塞赋值例子
blocking.v

`timescale  1ns/1ns

module  blocking
(
    input   wire            sys_clk     ,   //系统时钟50Mhz
    input   wire            sys_rst_n   ,   //全局复位
    input   wire    [1:0]   in          ,   //输入信号

    output  reg     [1:0]   out             //输出信号
);

reg     [1:0]   in_reg;

//in_reg:给输入信号打一拍
//out:输出控制一个LED灯
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            in_reg = 2'b0;
            out    = 2'b0;
        end
    else
        begin
            in_reg = in;
            out    = in_reg;
        end

endmodule

在这里插入图片描述
tb_blocking.v

`timescale  1ns/1ns

module tb_blocking();

wire    [1:0]   out         ;

reg             sys_clk     ;
reg             sys_rst_n   ;
reg     [1:0]   in          ;

//初始化系统时钟、全局复位和输入信号
initial begin
    sys_clk    = 1'b1;
    sys_rst_n <= 1'b0;
    in        <= 2'b0;
    #20;
    sys_rst_n <= 1'b1;
end

//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz
always #10 sys_clk = ~sys_clk;

//in:产生输入随机数,模拟按键的输入情况
always #20 in <= {$random} % 4; //取模求余数,产生非负随机数0、1,2,3,每隔20ns产生一次随机数

//------------------------ blocking_inst ------------------------
blocking    blocking_inst
(
    .sys_clk    (sys_clk    ),  //input             sys_clk
    .sys_rst_n  (sys_rst_n  ),  //input             sys_rst_n
    .in         (in         ),  //input     [1:0]   in

    .out        (out        )   //output    [1:0]   out
);

endmodule

非阻塞赋值例子
non_blocking.v

`timescale  1ns/1ns

module  non_blocking
(
    input   wire            sys_clk     ,   //系统时钟50Mhz
    input   wire            sys_rst_n   ,   //全局复位
    input   wire    [1:0]   in          ,   //输入按键

    output  reg     [1:0]   out             //输出控制led灯
);

reg     [1:0]   in_reg;

//in_reg:给输入信号打一拍
//out:输出控制一个LED灯
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            in_reg <= 2'b0;
            out    <= 2'b0;
        end
    else
        begin
            in_reg <= in;
            out    <= in_reg;
        end

endmodule

在这里插入图片描述
非阻塞的仿真代码与阻塞的一样


第13讲:计数器

计数是一种最简单基本的运算,计数器就是实现这种运算的逻辑电路,计数器在数字系统中主要是对脉冲的个数进行计数,以实现测量、计数和控制的功能,同时兼有分频功能。

计数器在数字系统中应用广泛,如电子计算机的控制器中对指令地址进行计数,以便顺序取出下一条指令,在运算器中作乘法、除法运算时记下加法、减法次数,又如在数字仪器中对脉冲的计数等等。

模块设计
在这里插入图片描述

波形绘制:每个周期前0.5秒点亮LED,后0.5秒熄灭
在这里插入图片描述


方法1实现:不带标志信号的计数器

`timescale  1ns/1ns

//方法1实现:不带标志信号的计数器
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   define
reg     [24:0]  cnt;                //经计算得需要25位宽的寄存器才够500ms

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

//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 == CNT_MAX)
        led_out <= ~led_out;

endmodule

在这里插入图片描述

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



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8648.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Debug】关于 nginx 上传文件时出现 413 及 500 错误码解决方法

先简单介绍一下 Nginx…   Nginx 作为一个高性能的 HTTP 和 反向代理 web 服务器具有占用内存少, 并发能力强等特点,可以说 Nginx 专为性能和效率而生, 如 tomcat 的并发量大约在 100 多, 而 Nginx 的并发量可以达到 5 万之多;   Nginx 的主要作用还是反向代理, 实现负载均衡…

什么是扩散模型(Diffusion Model)?

扩散模型是什么&#xff1f;如何工作以及他如何解决实际的问题 在计算机视觉中&#xff0c;生成模型是一类能够生成合成图像的模型&#xff08;文本生成图像【DALL2、Stable Diffusion】、图像生成图像【Diffusion-GAN】&#xff09;。例如&#xff0c;一个被训练来生成人脸的…

2023年天津市大学软件学院高职升本科联合招生专业考试大纲

天津市大学软件学院 2023年“高职升本科”联合招生专业考试大纲一、考试性质 天津市大学软件学院“高职升本科”联合招生专业考试是由合格的高职高专毕业生参加的选拔性考试。高等院校根据考生的成绩&#xff0c;按照已确定的招生计划&#xff0c;德、智、体全面衡量&#xff0…

MATLAB if...else...end 语句

在MATLAB的 if...else...end 语句中&#xff0c;if 语句后面可以跟一个可选择的 else 语句&#xff0c;当执行的表达式为假的时候&#xff0c;执行 else 语句。 if...else...end 语句语法&#xff1a; MATLAB 中一个 if ... else 语句的语法示例&#xff1a; if <expressio…

【python】一篇玩转正则表达式

目录 前言 正则表达式 行定位符 1.^ 2.$ 元字符 常见的元字符 限定符 常用的限定符 字符类 排除字符 选择字符 转义字符 &#xff08;&#xff09; python使用正则表达式 匹配字符串 match() search() findall() sub() 替换敏感字符 split() 前言 正则表…

柯桥托业TOEIC考试和PETS哪个含金量高?

说到对职场有益的证书&#xff0c;无外乎托业和BEC证书。但还有一种面向社会人士的考试&#xff0c;也有很多小伙伴很感兴趣。那就是PETS考试。 很多小伙伴也很好奇托业和PETS的区别&#xff0c;今天来给大家科普下喽。 TOEIC-托业考试 托业考试由美国教育考试服务中心(ETS)开…

LaTeX使用(公式,表格,图片,中文字符)

是一种基于ΤΕΧ的排版系统&#xff0c;由美国计算机学家莱斯利兰伯特&#xff08;Leslie Lamport&#xff09;在20世纪80年代初期开发&#xff0c;利用这种格式&#xff0c;即使使用者没有排版和程序设计的知识也可以充分发挥由TeX所提供的强大功能&#xff0c;能在几天、甚至…

HTML小游戏8 —— 小霸王游戏机网页版(附完整源码)

&#x1f482; 网站推荐:【神级源码资源网】【摸鱼小游戏】&#x1f91f; 风趣幽默的前端学习课程&#xff1a;&#x1f449;28个案例趣学前端&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且实用的计算机相关知…

RabbitMQ-04 Hello,World

首先我们还是先看一下Rabbitmq的工作原理图 从图上我们可以看到&#xff0c;无论是生产者还是消费者我们都需要进行connection并且获取相应的channel&#xff0c;所以为了方便&#xff0c;建议把这部分操作抽取成一个工具类RabbitMqUtils。 工具类RabbitMqUtils public class…

【C++笔试强训】第二十五天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

S7-1200通过MODBUS转PROFINET网关控制英威腾GD200A变频器的具体方法示例

S7-1200通过MODBUS转PROFINET网关控制英威腾GD200A变频器的具体方法示例 需要的设备: 西门子S7-1200PLC一台 MODBUS转PROFINET网关一台 英威腾GD200A变频器一台 具体配置方法: 1、 如下图所示,打开博途软件,新建项目并添加网关的gsd文件; 2、 如下图所示,建立profinet连…

Gradle介绍1-入门和IDEA整合(Gradle Wrapper)

1. Gradle 入门 1.1、Gradle 简介 Gradle 是一款Google 推出的基于 JVM、 通用灵活的项目构建工具&#xff0c; 支持 Maven&#xff0c;JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件&#xff0c;转而使用简洁的、支持多种语言(例如&#xff1a;java、gr…

前端—微信小程序开发

随着微信的普及和微小程序的广泛应用&#xff0c;微信小程序开发越来越多受到人们的关注&#xff0c;正在成为新工科和人工智能背景下当代大学生的必备技能。 适应对象 该课程适合电子信息类专业学生进行学习。 微信小程序开发课程共六章&#xff0c;通过对微信小程序开发的…

Java继承

一、知识点 继承是Java面向对象编程的一块基石&#xff0c;因为它允许创建分等级层次的类。继承可以理解为一个对象从另一个对象获取属性的过程。 如果类A是类B的父类&#xff0c;而类B是类C的父类&#xff0c;我们也称C是A的子类&#xff0c;类C是从类A继承而来的。在Java中&a…

理解case when then else end 的使用,基础概念,建表语句,用例讲解

文章目录一、基础概念二、建表语句三、用例讲解参考文档一、基础概念 case &#xff1a;表示需要处理的字段when &#xff1a;表示条件then &#xff1a;表示当when执行为true时&#xff0c;再执行的语句else &#xff1a;表示当所有的when执行为false时&#xff0c;再执行的语…

Go分布式缓存 一致性哈希(hash)(day4)

Go分布式缓存 一致性哈希(hash)(day4) 1 为什么使用一致性哈希 今天我们要实现的是一致性哈希算法&#xff0c;一致性哈希算法是 GeeCache 从单节点走向分布式节点的一个重要的环节。那你可能要问了&#xff0c; 童鞋&#xff0c;一致性哈希算法是啥&#xff1f;为什么要使用…

基于几何约束的传动机构设计

本文介绍如何使用参数化 CAD 软件中几何约束的强大功能来加速机构的开发。 许多 CAD 程序提供了用于分析和改进机制的工具。但是&#xff0c;这些假设你已经有了初始设计。合成机构的经典图形方法提供了确定连杆长度和关节位置以产生特定运动的方法。 这些方法可以使用参数化…

工作这么久了,还不懂多线程吗?

浩哥Java多线程整理学习系列之01 基础知识整理 浩哥Java多线程整理学习系列之01基础知识整理1. 如何查看电脑核数及线程数Linux查询CPU核心数2. 线程和进程、协程之间的区别3. 时间片轮转&#xff08;RR&#xff09;调度算法4. 并行和并发的区别5. 系统限制线程数6. 并发的优缺…

[go学习笔记.第十四章.协程和管道] 1.协程的引入,调度模型以及运行cpu数目,协程资源竞争问题

1.先看一个需求 需求&#xff1a; 要求统计 1~9000000000 的数字中&#xff0c;哪些是素数&#xff1f; 分析思路&#xff1a; (1).传统的方法&#xff0c;就是使用一个循环&#xff0c;循环的判断各个数是不是素数.(很慢) (2).使用并发或者并行的方式&#xff0c;将统计素数的…

CEX暴雷怎么办 一文读懂加密钱包产业现状

你的钱其实并不在你的借记卡里&#xff0c;借记卡只是授权你的银行帐户向银行系统数据库发送交易。同样&#xff0c;你的代币也并不在你的加密钱包里。加密钱包只是持有私有密钥以证明对数字资产的所有权&#xff0c;而这些资产是存储在公共区块链网络上的。私钥能让你对加密钱…