分频操作重点(计数器参数、触发条件、占空比)
-
N分频表示:主时钟clk打N拍,分频后时钟打一拍。
【一拍:乐理中“一上一下”为一拍。在此处即形容clk的一个上升沿和一个下降沿,非常形象。】 -
用计数器cnt计数:可以选择“仅在clk上升沿计数”,也可以选择“在clk上升沿和下降沿都计数”。对于博主而言,在进行时钟奇分频时,我更倾向于使用后者,即上升沿和下降沿都计数,这样操作难度会小一些。
仅在clk上升沿计数:每个周期需要计数N-1次。每个周期从0开始,到N-1结束。
一个周期内clk要打N拍,每一拍有一个上升沿,每个上升沿计数+1。所以N拍计数+N。最后一拍衔接下一周期的第一拍,上升沿属于下一个周期,计数-1。所以最终需要计数N-1。
在clk上升沿和下降沿都计数:每个周期要计数2N-1次。
一个周期内clk要打N拍,每一拍有一个上升沿和一个下降沿,每个上升沿和下降沿都计数+1。所以N拍计数+2N。最后一拍衔接下一周期的第一拍,上升沿属于下一个周期,下降沿还属于本周期,所以计数-1。所以最终需要计数2N-1。
我们也可以通过画图加推倒,很清楚地来解释,比如我现在要实现5分频(占空比40%):
-
奇分频的时钟边沿触发,必须是上升沿和下降沿同时作为敏感列表的。因为奇分频会产生0.5拍,即仅仅越过半个clk周期的一个边沿,这个时候很有可能是下降沿。
【说的比较抽象,一会儿实例上了就知道了】 -
占空比控制:抓住电平反转时cnt的值。画图即可解出。
实例——7分频,占空比50%
波形分析:
可以看到:
①计数周期:2N-1=13。
②为了满足占空比50%,电平反转的点为:6和13。
经过分析,代码非常简单:
①计数器参数定义:
parameter FD = 7; //分频数
parameter CNT_MAX_NUM = 2*FD - 1; //计数器最大计数14-1=13
parameter CHANGE_NUM_1 = 6; //电平反转的cnt值
parameter CHANGE_NUM_2 = 13; //电平反转的cnt值
②计数器cnt控制块:
reg [3:0] cnt_ctrl;
always@(posedge clk_in or negedge clk_in or negedge rst)
begin
if(!rst) cnt_ctrl <= 0;
else cnt_ctrl <= (cnt_ctrl == CNT_MAX_NUM) ? 0 : (cnt_ctrl + 1);
end
③分频后时钟电平控制:
reg clk_temp;
always@(posedge clk_in or negedge clk_in or negedge rst)
begin
if(!rst) clk_temp <= 0;
else clk_temp <= (cnt == 1 || cnt == 5) ? ~clk_temp : clk_temp;
end
assign clk_out5 = clk_temp;
代码心得总结:
①多用三目运算符?:取代if else,能大幅度简化代码的复杂度。
②wire型不能直接在always内操作,但是reg型变量可以赋值给wire。我们可以定义一个reg型变量参与always的操作,再把这个值assign给wire变量。(比如本代码中的clk_temp)
③学会用parameter定义和强调一些常量,如同c语言的define一样,很方便也有很高的可读性。
完整代码:
module odo_div_or
(
input wire rst ,
input wire clk_in,
output wire clk_out7
);
//*************code***********//
parameter FD = 7; //分频数
parameter CNT_MAX_NUM = 2*FD - 1; //计数器最大计数14-1=13
parameter CHANGE_NUM_1 = 6; //电平反转的cnt值
parameter CHANGE_NUM_2 = 13; //电平反转的cnt值
reg [3:0] cnt_ctrl;
always@(posedge clk_in or negedge clk_in or negedge rst)
begin
if(!rst) cnt_ctrl <= 0;
else cnt_ctrl <= (cnt_ctrl == CNT_MAX_NUM) ? 0 : (cnt_ctrl + 1);
end
reg clk_temp; //reg型,暂存分频溢出。
always@(posedge clk_in or negedge clk_in or negedge rst)
begin
if(!rst) clk_temp <= 0;
else clk_temp <= (cnt_ctrl == CHANGE_NUM_1 || cnt_ctrl == CHANGE_NUM_2) ? (~clk_temp) : (clk_temp);
end
assign clk_out7 = clk_temp;
//*************code***********//
endmodule
经验总结
时序图多画多分析,多debug!