06_时序逻辑的开始 --- 寄存器
1. 理论学习
D 触发器的功能为:在一个脉冲信号(一般为晶振产生的时钟脉冲)上升沿或下降沿
的作用下,将信号从输入端 D 送到输出端 Q,如果时钟脉冲的边沿信号不来则输入信号保
持原值,且寄存器拥有复位清零功能,其复位又分为同步复位和异步复位。
1.1 同步复位的 D 触发器
同步复位的 D 触发器中的“同步”是和工作时钟同步的意思,也就是说,当时钟的上升沿(也可以是下降沿,一般习惯上为上升沿触发)来到时检测到按键的复位操作才有效,否则无效。如图所示最右边的三根红色的竖线表达的就是这种效果,sys_rst_n 被拉低后 led_out 没有立刻变为 0,而是当 syc_clk 的上升沿到来的时候 led_out 才复位成功,在复位释放的时候也是相同原因。
注意看sys_rst_n下降沿的波形!!!!!!
1.2 异步复位的 D 触发器
异步复位的 D 触发器中的“异步”是和工作时钟不同步的意思,也就是说,寄存器的复位不关心时钟的上升沿来不来,只要有检测到按键被按下,就立刻执行复位操作。如图所示最右边的两根红色的竖线表达了这种效果,sys_rst_n 被拉低后 led_out 立刻变为0,而不是等待 syc_clk 的上升沿到来的时候 led_out 才复位,而在复位释放的时候 led_out不会立刻变为 key_in 的值,因为还要等待时钟上升沿到来到时才能检测到 key_in 的值,此时才将 key_in 的值赋值给 led_out。
1.3 优点
1)相比于组合逻辑电路来讲,对于电路中产生的毛刺有着极好的屏蔽作用,
2)时序电路还有一个特点,就是“延一拍”的效果。
2. 实验目标
当按键未按下时 led 灯处于熄灭状态;当按键被按下时 led 灯被点亮。
3. RTL
`timescale 1ns/1ns
module flip_flop
(
input wire sys_clk, //位宽为1,可以省略
input wire sys_rst_n,
input wire key_in,
output reg led_out
);
// 同步复位的 D 触发器
/*
always@(posedge sys_clk)
begin
if(sys_rst_n == 1'b0)
begin
led_out <= 1'b0;
end
else
begin
led_out <= key_in;
end
end
*/
// 异步复位的 D 触发器
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
led_out <= 1'b0;
end
else
begin
led_out <= key_in;
end
end
endmodule
4. testbench
`timescale 1ns/1ns
// 在这里面不需要对端口进行定义
module tb_flip_flop();
//因为 testbench 不对外进行信号的输入输出,只是自己产生
//激励信号提供给内部实例化待测 RTL 模块使用,所以端口列表
//中没有内容,只是列出“()”,当然可以将“()”省略,括号
//后有个“;”不要忘记
//要在 initial 块和 always 块中被赋值的变量一定要是 reg 型
//在 testbench 中待测试 RTL 模块的输入永远是 reg 型变量
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
reg sys_clk;
reg sys_rst_n;
reg key_in;
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
wire led_out;
//初始化值在没有特殊要求的情况下给 0 或 1 都可以。如果不赋初值,仿真时信号
//会显示为不定态(ModelSim 中的波形显示红色)
initial
//initial 只在通电执行一次
//在仿真中 begin...end 块中的内容都是顺序执行的,
//在没有延时的情况下几乎没有差别,看上去是同时执行的,
//如果有延时才能表达的比较明了;
//而在 rtl 代码中 begin...end 相当于括号的作用, begin...end 在 Testbench 中的用法及意义(区别 -----------------------------------------------------)
//在同一个 always 块中给多个变量赋值的时候要加上
begin
sys_clk = 1'b1; //时钟信号的初始化为 1,且使用“=”赋值,
//其他信号的赋值都是用“<=”
sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为 0
key_in <= 1'b0; //输入信号按键的初始化,为 0 和 1 均可
#20 //延时20ns
sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位
//所示释放时,把信号拉高后系统才开始工作
#210
sys_rst_n <= 1'b0; //为了观察同步复位和异步复位的区别,在复位释放后
//电路工作 210ns 后再让复位有效。之所以选择延时 210ns
//而不是 200ns 或 220ns,是因为能够使复位信号在时钟下
//降沿时复位,能够清晰的看出同步复位和异步复位的差别
#40
sys_rst_n <= 1'b1; //复位 40ns 后再次让复位释放掉
end
// always语句 一直在执行
sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50Mhz
always #10 sys_clk = ~sys_clk;//取模求余数,产生随机数 1'b0、1'b1//每隔 10ns 产生一次随机数
// //key_in:产生输入随机数,模拟按键的输入情况
//取模求余数,产生非负随机数 0、1,每隔 20ns
always #20 key_in <= {$random} % 2;
//下面的语句是为了在 ModelSim 仿真中直接打印出来信息便于观察信号变化的状态
//也可以不使用下面的语句而直接观察仿真出的波形
initial
begin
$timeformat(-9, 0, "ns", 6);//设置显示的时间格式,此处表示的是(打印时间单
//位为纳秒,小数点后打印的小数位为 0 位,时间值
//后打印的字符串为“ns”,打印的最小数量字符为 6 个)
$monitor("@time %t: key_in=%b led_out=%b", $time, key_in, led_out);
end
//------------------------------------------------------------
//待测试 RTL 模块的实例化,相当于将待测试模块放到测试模块中,并将输入输出对应连接上
//测试模块中产生激励信号给待测试模块的输入,以观察待测试模块的输出信号是否正确
//------------------------------------------------
flip_flop flip_flop_inst //第一个是被实例化模块的名子,第二个是我们自己定义的在另一个
//模块中实例化后的名字。同一个模块可以在另一个模块中或不同的
//另外模块中被多次实例化,第一个名字相同,第二个名字不同
(
//前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个
//模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
//“.”可以理解为将这两个信号连接在一起
.sys_clk(sys_clk), //input in1
.sys_rst_n(sys_rst_n), //input in2
.key_in(key_in),
.led_out(led_out)
);
endmodule