在FPGA设计中,我们经常会使用分频的方法来得到一个我们需要的时钟频率,而在很多开发板厂家配套的教程里,他们常常会使用计数器分频得到的高低电平时钟来当做驱动时钟,这种方法简单易懂,但是,在工程设计中,这种方法是不被允许的。
门控时钟
门控时钟就是使用计数器和逻辑门翻转来产生的时钟,下面是典型的门控时钟
//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
dri_clk <= 1'b1;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
if(rst_n == 1'b0)
cur_state <= st_idle;
else
cur_state <= next_state;
end
在上面代码中,先使用sys_clk生成一个dri_clk,然后再使用dri_clk来驱动其他的电路,这种方法是绝对不允许的,主要有以下两点原因:
1、dri_clk是由逻辑门生成的,会有可能产生毛刺
2、dri_clk经过了逻辑门,从sys_clk触发到dri_clk稳定输出会有一定的延迟,这样dri_clk和sys_clk以及其他的门控时钟会有一定的相位差,这在大型系统或者比较严格的系统中是很危险的。
时钟使能
时钟使能的方法就能有效的解决以上两个缺点
时钟使能也会用到计数器,但是不会用到反相器,生成的信号也不会直接用于其他电路的触发,就以上面的代码为例子进行修改,时钟使能的代码如下:
//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= 1'b1;
end
else begin
clk_cnt <= clk_cnt + 1'b1;
dri_clk <= 1'b0;
end
end
//(三段式状态机)同步时序描述状态转移
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
cur_state <= st_idle;
else
begin
if(dri_clk == 1'b1)
cur_state <= next_state;
else
;
end
end
可以看到,在上面代码中,也使用到了计数器,但是仅仅是用于产生一个时钟使能脉冲信号,只持续一个sys_clk。
而dri_clk也没有直接用于驱动其他电路,在整个系统中,所有的时钟都是sys_clk,这样就不会有相位不匹配的问题存在了,而且也不会出现由于毛刺带来的影响。