参考书本:《硬件架构的艺术》
补充:
同步整数分频可以用moore状态机很容易实现。但是这样简单的逻辑无法产生50%占空比的输出。
七分频Moore状态机
1. 偶数分频
2. 奇数整数分频(50%占空比)
概念上:以期望输出频率的一半生成两个正交相位时钟(两个时钟之间有90°相位差),然后通过两个波形异或得到输出频率。由于存在固定90°相位差,每次异或输入只有一端会变化,这样有效消除了输出波形上的毛刺。
实现步骤:
(1)计数器:clk上升沿,0~(N-1),N是奇数
(2)使用两个开关触发器(TFF),产生使能信号
tff1_en:计数为0时使能;tff2_en:计数为(N+1)/2时使能。
(3)div1: tff1 clk上升沿 信号翻转;div2:tff2 clk下降沿 信号翻转
(4)clkout:div1和div2异或
用T触发器进行50%占空比3分频
也可以使用D触发器实现。
比如实现5分频(50%占空比)
(1)计数器cnt:clk上升沿触发,计数0~(N-1),N是奇数;
(2)div1信号产生:clk上升沿触发, cnt计数到0,信号翻转div1<=~div1;
div2信号产生:clk下降沿触发,cnt计数到(N+1)/2,信号翻转div2<=div2;
(3)clkout信号:div1信号与div2信号进行异或。
设计代码:
//5分频
`timescale 1ns/1ns
module div_5 (
input clk,
input rst_n,
output clk_div5
);
reg [2:0] cnt;
reg div1;
reg div2;
//计数5:0~4 0~(N-1)
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt<=0;
end else if(cnt==3'd4) begin
cnt<=0;
end else begin
cnt<=cnt+1'b1;
end
end
//div1:时钟上升沿,计数到0翻转
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
div1<=0;
end else if (cnt==0) begin
div1<=~div1;
end else begin
div1<=div1;
end
end
//div2:时钟下降沿,计数到3翻转 (N+1)/2
always @(negedge clk or negedge rst_n) begin
if(!rst_n) begin
div2<=0;
end else if(cnt==3'd3) begin
div2<=~div2;
end else begin
div2<=div2;
end
end
//div1和div2异或得到输出时钟
assign clk_div5=div1^div2;
endmodule
测试代码:
//5分频测试
`timescale 1ns/1ns
module div_5_tb ();
reg clk;
reg rst_n;
wire clk_div5;
initial begin
clk=0;
rst_n=0;
#20;
rst_n=1;
end
always #10 clk=~clk;
div_5 u1 (.clk(clk),.rst_n(rst_n),.clk_div5(clk_div5));
endmodule
仿真波形:
RTL分析结构图:
注意:后面两个D触发器的时钟应该一个是clk,一个是clk经过反相器之后,一个是上升沿触发,一个是下降沿触发。发现它是直接电路实现上,右下角触发器的C端前面有个小圆圈代表非,也就是下降沿触发。
综合之后的结构图:
3.非整数分频(1.5倍分频,4.5倍分频类似,非50%占空比,占空比还是有一定限制的)
设计思想:
4.5分频(非50%占空比),也就是每9个输入时钟包含2个对称脉冲,这种通过移位的方法可以保证输出波形不包含毛刺
设计步骤:
步骤一:使用复位值为0_0000_0001的9位移位寄存器,每个clk上升沿循环左移一位
步骤二:产生第1个脉冲,需要在半周期时移动第1位,再与第1位与第2位进行或操作(如何实现半周期,clk下降沿触发,前提输入时钟clk是50%占空比)
步骤三:产生第2个脉冲,需要第5位和第6位必须在半周期时移动,并与原始第6位进行或操作
注意事项:
(1)shift_cnt<={shift_cnt[7:0],shift_cnt[8]}; //实现循环移位
(2)为什么产生第一个脉冲,还要跟半周期移动第1位得到的ps_count0相或呢,shift_cnt[0]与shift_cnt[1]相或不就行了嘛?还有产生第二个脉冲的时候,为什么还要与原始第6位相或呢?
我的理解:实际电路,考虑时延等,为了补齐中间的时间空白,保证输出小数倍频时钟周期的完整性和正确性。
(3)用的[8:0]代表9位,后面的位数别搞混了
(4)其实可以修改占空比,不一定非要两个clk时长的高电平。9位移位寄存器是确定总的时长;和至少一个半周期移动一起确定*.5倍频,参与相或是几位确定了占空比。比如本设计占空比是4/9,也可以改为2/9。改法:
assign clk_div4_5=shift_cnt[0]|ps_count4;
(5)为什么用这种方法?用移位的方法可以保证输出的波形不含毛刺。是对比另一种方法(不提倡,在本小节后面会提到)
4.5倍分频设计代码:
//4.5分频(非50%占空比)
//每9个输入时钟包含2个对称脉冲,这种通过移位的方法可以保证输出波形不包含毛刺
//!!!注意:[8:0] 位数别弄混了
`timescale 1ns/1ns
module div4_5 (
input clk,
input rst_n,
output clk_div4_5
);
reg [8:0] shift_cnt;
reg ps_count0;
reg ps_count4;
reg ps_count5;
//使用复位值为0_0000_0001的9位移位寄存器,每个clk上升沿循环左移一位
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
shift_cnt<=9'b0_0000_0001;
end else begin
shift_cnt<={shift_cnt[7:0],shift_cnt[8]}; //循环移位
end
end
//产生第1个脉冲,需要在半周期时移动第1位,再与第1位与第2位进行或操作
//产生第2个脉冲,需要第5位和第6位必须在半周期时移动,并与原始第6位进行或操作 ??为什么还要原始第6位?我的理解:实际电路,考虑时延等,为了补齐中间的时间空白,保证周期性完整,正确
always @(negedge clk or negedge rst_n) begin
if(!rst_n) begin
ps_count0<=1'b0;
ps_count4<=1'b0;
ps_count5<=1'b0;
end else begin
ps_count0<=shift_cnt[0];
ps_count4<=shift_cnt[4];
ps_count5<=shift_cnt[5];
end
end
assign clk_div4_5=(shift_cnt[0]|shift_cnt[1]|ps_count0)|(shift_cnt[5]|ps_count4|ps_count5);
endmodule
4.5倍分频测试代码:
//检测4.5倍分频
`timescale 1ns/1ns
module div4_5_tb();
reg clk;
reg rst_n;
wire clk_div4_5;
initial begin
clk=0;
rst_n=0;
#10;
rst_n=1;
end
always #10 clk=~clk;
div4_5 u2(
.clk (clk),
.rst_n(rst_n),
. clk_div4_5 (clk_div4_5 )
);
endmodule
4.5倍分频仿真波形 (占空比4/9)
1.5倍分频代码设计思路:
每3个输入时钟包含2个对称脉冲,使用复位值为001的3位移位寄存器,每个clk上升沿循环左移一位,产生第1个脉冲,shift_cnt[0]即可,产生第2个脉冲,需要第2位在半周期时移动
设计代码:
//1.5分频(非50%占空比)
//每3个输入时钟包含2个对称脉冲,这种通过移位的方法可以保证输出波形不包含毛刺
`timescale 1ns/1ns
module div4_5 (
input clk,
input rst_n,
output clk_div1_5
);
reg [2:0] shift_cnt;
reg ps_count1;
//使用复位值为001的3位移位寄存器,每个clk上升沿循环左移一位
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
shift_cnt<=3'b001;
end else begin
shift_cnt<={shift_cnt[1:0],shift_cnt[2]}; //循环移位
end
end
//产生第1个脉冲,shift_cnt[0]即可
//产生第2个脉冲,需要第2位在半周期时移动
always @(negedge clk or negedge rst_n) begin
if(!rst_n) begin
ps_count1<=1'b0;
end else begin
ps_count1<=shift_cnt[1];
end
end
assign clk_div1_5=shift_cnt[0]|ps_count1;
endmodule
1.5倍数分频(占空比2/3)
另一种方法,不推荐,产生毛刺