分频器是数字系统中最常见的基本电路之一。分频就是将输入信号的频率变成成倍地低于输入频率的输出信号。如输入信号频率为50MHZ,10分频后输出信号频率为5MHZ。这里10是分频系数,分频系数为偶数则为偶分频,分频系数为奇数则为奇分频。
分频有两种方式:一是通过HDL语言建模产生所需要的时钟信号,二是利用开发工具的PLL进行分频。前者分频灵活,需编写代码实现;后者使用场景受限,因为有的低端FPGA没有PLL,但PLL的分频效果更好,而且在进行小数分频时也比较容易实现。
1、偶分频
以6分频为例,有两种方法:分频法和降频法。
①分频法
分频系数为N(N为偶数),就是N分频,分频器实现方法和计数器原理一致,当计数值到(N/2)-1时,输出信号电平翻转,此输出信号即为N分频的信号。
module divider_six
(
input wire sys_clk,
input wire sys_rst_n,
output reg clk_out
);
reg [1:0] cnt;
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
cnt <= 2'b0;
else if(cnt == 2'd2)
cnt <= 2'b0;
else
cnt <= cnt + 1'b1;
end
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
clk_out <= 1'b0;
else if(cnt == 2'd2)
clk_out <= ~clk_out;
else
clk_out <= clk_out;
end
endmodule
`timescale 1ns/1ns
module tb_divider_six();
reg sys_clk;
reg sys_rst_n;
wire clk_out;
initial
begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#20
sys_rst_n = 1'b1;
end
always #10 sys_clk = ~sys_clk;
divider_six divider_six_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.clk_out (clk_out)
);
endmodule
上述方法生成了低频时钟clk_out,但是如果直接用clk_out作为驱动时钟实现一些功能会出现隐患。在FPGA中凡是时钟信号都需要连接到全局时钟网络(全局时钟树),是FPGA厂商专门为时钟路径设计的,它能使时钟信号到达每个寄存器的时间尽可能地相同,减少时序问题的产生。而分频方式产生的低频时钟并没有连接到全局时钟网络上,外部晶振产生的时钟信号通过管脚连接到FPGA专用的时钟引脚上,即连接到了全局时钟网络上。所以在系统时钟控制下的信号比低频时钟控制下的信号更能保持稳定。
②降频法
下面采用生成标志位(脉冲标志信号clk_flag)对上述方法进行一些改进。在计数值达到N-2(这里N-2为4,用N-2是因为时序逻辑生成信号会延迟一个周期)时,生成一个脉冲标志信号clk_flag,并保持一个时钟周期。如此仍用系统时钟作为驱动时钟,而将clk_flag作为标志信号进行条件判断,可以实现和上述方法相同的分频功能。运用标志信号的方法在高速系统中能够减少时序错误,这种方法称之为降频。
module divider_six
(
input wire sys_clk,
input wire sys_rst_n,
output reg clk_flag
);
reg [2:0] cnt;
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
cnt <= 3'b0;
else if(cnt == 3'd5)
cnt <= 3'b0;
else
cnt <= cnt + 1'b1;
end
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
clk_flag <= 3'b0;
else if(cnt == 3'd4)
clk_flag <= 1'b1;
else
clk_flag <= 1'b0;
end
endmodule
`timescale 1ns/1ns
module tb_divide_six();
reg sys_clk;
reg sys_rst_n;
wire clk_flag;
initial
begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#20
sys_rst_n = 1'b1;
end
always #10 sys_clk = ~sys_clk;
divide_six divide_six_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.clk_flag (clk_flag)
);
endmodule
总结:在高速系统用到分频时钟的时候,最好采用生成脉冲标志信号的方法,可减少时序错误。低速系统中甚至只有几个寄存器的情况下,采用两种方法皆可。
2、奇分频
以5分频为例,有两种方法:分频法和降频法。
①分频法
只需(clk_1 | clk_2)即可得到占空比为50%的clk_out。
module divider_five
(
input wire sys_clk,
input wire sys_rst_n,
output wire clk_out
);
reg [2:0] cnt;
reg clk_1;
reg clk_2;
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
cnt <= 3'd0;
else if(cnt == 3'd4)
cnt <= 3'd0;
else
cnt <= cnt + 1'b1;
end
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
clk_1 <= 1'd0;
else if(cnt == 3'd2)
clk_1 <= 1'b1;
else if(cnt == 3'd4)
clk_1 <= 1'b0;
else
clk_1 <= clk_1;
end
always@(negedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
clk_2 <= 1'b0;
else if(cnt == 3'd2)
clk_2 <= 1'b1;
else if(cnt == 3'd4)
clk_2 <= 1'b0;
else
clk_2 <= clk_2;
end
assign clk_out = (clk_1 | clk_2);
endmodule
`timescale 1ns/1ns
module tb_divider_five();
reg sys_clk;
reg sys_rst_n;
wire clk_out;
initial
begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#20
sys_rst_n = 1'b1;
end
always #10 sys_clk = ~sys_clk;
divider_five divider_five_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.clk_out (clk_out)
);
endmodule
②降频法
module divider_five
(
input wire sys_clk,
input wire sys_rst_n,
output reg clk_flag
);
reg [2:0] cnt;
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
cnt <= 3'd0;
else if(cnt == 3'd4)
cnt <= 3'd0;
else
cnt <= cnt + 1'b1;
end
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
clk_flag <= 3'd0;
else if(cnt == 3'd3)
clk_flag <= 3'd1;
else
clk_flag <= 1'b0;
end
endmodule
`timescale 1ns/1ns
module tb_divider_five();
reg sys_clk;
reg sys_rst_n;
wire clk_flag;
initial
begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#20
sys_rst_n = 1'b1;
end
always #10 sys_clk = ~sys_clk;
divider_five divider_five_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.clk_flag (clk_flag)
);
endmodule