组合逻辑存在竞争冒险
第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
FPGA入门(4):时序逻辑(二)+https://developer.aliyun.com/article/1556547