时序逻辑的开始和计数器

1、什么是D触发器

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

区分一个设计是组合逻辑电路还是时序逻辑电路主要是看数据工作是不是在时钟沿下进行的,在 FPGA 的设计中,复杂的电路设计都要用到时序逻辑电路,往往都是以时序逻辑电路为主,组合逻辑为辅的混合逻辑电路。

2、同步复位的 D 触发器

同步复位的 D 触发器中的“同步”是和工作时钟同步的意思,也就是说,当时钟的上升沿(也可以是下降沿,一般习惯上为上升沿触发)来到时检测到按键的复位操作才有效,否则无效。下面这个图所示最右边的三根红色的竖线表达的就是这种效果,sys_rst_n 被拉低后 led_out 没有立刻变为 0,而是当 syc_clk 的上升沿到来的时候 led_out 才复位成功,在复位释放的时候也是相同原因。

3、异步复位的 D 触发器

异步复位的 D 触发器中的“异步”是和工作时钟不同步的意思,也就是说,寄存器的复位不关心时钟的上升沿来不来,只要有检测到按键被按下,就立刻执行复位操作。如图
所示最右边的两根红色的竖线表达了这种效果,sys_rst_n 被拉低后 led_out 立刻变为0,而不是等待 syc_clk 的上升沿到来的时候 led_out 才复位,而在复位释放的时候 led_out不会立刻变为 key_in 的值,因为还要等待时钟上升沿到来到时才能检测到 key_in 的值,此时才将 key_in 的值赋值给 led_out。

4、同步复位的D触发器RTL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module flip_flop 
(
input wire sys_clk , //系统时钟 50Mh,后面我们都是设计的时序电路
//所以一定要有时钟,时序电路中几乎所有的信
//号都是伴随着时钟的沿(上升沿或下降沿,习
//惯上用上升沿)进行工作的

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;

endmodule

5、异步复位的D触发器RTL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
异步复位的 D 触发器 
module flip_flop
(
input wire sys_clk , //系统时钟 50Mh
input wire sys_rst_n , //全局复位
input wire key_in , //输入按键

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

//led_out:led 灯输出的结果为 key_in 按键的输入值
//当 always 块中的敏感列表为检测到 sys_clk 上升沿或 sys_rst_n 下降沿时执行下面的语句
always@(posedge sys_clk or negedge 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

6、D触发器的测试文件TetBench编写

#20 是一个 ​​延迟控制(Delay Control)​​ 语法,表示 ​​延迟 20 个时间单位​​ 后再执行后续语句。

我们再来对下面的testbench代码做出相关的理解:

1
2
3
4
initial begin
$timeformat(-9, 0, "ns", 6); // 设置时间格式
$monitor("@time %t: key_in=%b led_out=%b", $time, key_in, led_out); // 监控信号
end

这个时间展示代码我们可以让其展示出下面这个波形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
`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

时序逻辑的开始和计数器
http://example.com/2025/05/10/时序逻辑的开始和计数器/
Author
John Doe
Posted on
May 10, 2025
Licensed under