一、阻塞赋值与非阻塞赋值
非阻塞赋值的赋值号用“<=”表示。只有在触发沿的时刻才能进行非阻塞赋值。非阻塞操作开始时计算非阻塞赋值符的赋值号右边的语句,赋值操作结束时刻才更新赋值号左边的语句,可以认为是两个步骤(赋值开始时刻和结束时刻)来完成非阻塞赋值:在赋值开始时刻,计算赋值号右边的语句。在赋值结束时刻,更新赋值号左边的语句。
注意:非阻塞操作只能用于对寄存器类型变量进行赋值,因此只能用于“initial”和“always”块中,不允许用于连续赋值“assign”。
二、分频和降频
//方法1实现:仅实现分频功能
module divider_six
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //全局复位
output reg clk_out //对系统时钟6分频后的信号
);
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;
endmodule
//方法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
方法一中的 clk_out 输出信号是我们想要的分频后的信号,然后很多同学就直接把这个信号当作新的低频时钟来使用,这往往忽略了一些隐患的存在,这种做法所衍生的潜在问题在低速系统中不易察觉,而在高速系统中就很容易出现问题。因为我们通过这种方式分频得到的时钟虽然表面上是对系统时钟进行了分频产生了一个新的低频时钟,但实际上和真正的时钟信号还是有很大区别的。因为在 FPGA 中凡是时钟信号都要连接到全局时钟网络上,全局时钟网络也称为全局时钟树,是 FPGA 厂商专为时钟路径而特殊设计的,它能够使时钟信号到达每个寄存器的时间都尽可能相同,以保证更低的时钟偏斜(Skew)和抖动(Jitter)。而我们用这种分频的方式产生的 clk_out 信号并没有连接到全局时钟网络上,但 sys_clk 则是由外部晶振直接通过管脚连接到了 FPGA 的专用时钟管脚上,自然就会连接到全局时钟网络上,所以在 sys_clk 时钟工作下的信号要比在 clk_out 时钟工作下的信号更容易在高速系统中保持稳定。
怎么办:我们可以产生一个用于标记 6 分频的 clk_flag 标志信号,这样每两 clk_flag 脉冲之间的频率就是对 sys_clk 时钟信号的 6 分频,但是计数器计数的个数我们需增加一些,如图 18-4 所示需要从 0~5 共 6 个数,否则不能实现 6 分频的功能。和方法 1 对比可以发现,相当于把 clk_out 的上升沿信号变成了 clk_flag 的脉冲电平信号,为后级模块实现相同的降频效果。虽然这样会多使用一些寄存器资源,不过不用担心我们的系统是完全可以承担的起的,而得到的好处却远远大于这点资源的使用,能让系统更加稳定。
三、边沿检测
module touch_ctrl_led
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire touch_key , //触摸按键信号
output reg led //led输出信号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//wire define
wire touch_en ; //触摸使能信号
//reg define
reg touch_key_dly1 ; //touch_key延迟一个时钟信号
reg touch_key_dly2 ; //touch_key延迟两个时钟信号
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//根据触摸按键信号的下降沿判断触摸了触摸按键
assign touch_en = touch_key_dly2 & (~touch_key_dly1);
//对touch_key信号延迟两个时钟周期用来产生触摸按键信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
touch_key_dly1 <= 1'b0;
touch_key_dly2 <= 1'b0;
end
else
begin
touch_key_dly1 <= touch_key;
touch_key_dly2 <= touch_key_dly1;
end
//根据触摸使能信号控制led状态
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led <= 1'b1;
else if(touch_en == 1'b1)
led <= ~led;
endmodule