目录
奇数分频器电路设计
前面一节我们学习了偶数分频器的设计方法,本节我们来学习下奇数分频器的设计方法。实现偶数分频可通过一个简单计数器实现,而如果需要三分频,五分频,七分频等奇数分频,一个计数器是不够的。奇数分频器的设计相对偶数分频器设计要复杂一点,我们来看下奇数分频设计方法。
1、奇数分频器电路简介
在《偶数分频器电路设计》章节提到实现分频一般有两个方法,
一个方法是直接使用 PLL
进行分频,
比如 FPGA
或者
ASIC
设计中,都可以直接使用
PLL
进行分频。
还有一种实现方法就是直接使用逻辑实现,即使用代码实现分频设计。
我们本节介绍的是使用代码进行设计奇数分频器。本节我们先看下奇数分频设计。
-
奇数分频设计的一般方法:
-
假设为 N分频,需从0计数到 N-1(一共N/2个基准时钟),一直循环。
-
设计一个对基准时钟上升沿敏感的信号,每当计数到(N-1)/2-1时,时钟翻转;计数到计数器最大值时再反转。
-
设计一个对基准时钟下降沿敏感的信号,每当计数到(N-1)/2-1时,时钟翻转;计数到计数器最大值时再反转。
-
将 上升沿敏感的信号和 下降沿敏感的信号相与(&&)即N分频电路。
2、实验任务
使用 Verilog 语言设计一个任意奇数分频电路,默认进行 7 分频
。
3、程序设计
3.1、7分频电路代码
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/05/15 10:58:02
// Design Name:
// Module Name: divider_7
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
//奇数分频设计的一般方法:
//假设为 N分频,需从0计数到 N-1(一共N/2个基准时钟),一直循环.
//设计一个对基准时钟上升沿敏感的信号,每当计数到(N-1)/2-1时,时钟翻转;计数到计数器最大值时再反转.
//设计一个对基准时钟下降沿敏感的信号,每当计数到(N-1)/2-1时,时钟翻转;计数到计数器最大值时再反转.
//将 上升沿敏感的信号和 下降沿敏感的信号相与(&&)即N分频电路.
module divider_7(
input sys_clk, //50MHz系统时钟(一个周期是20ns:1/50MHz=0.02us=20ns)
input sys_rst_n, //复位信号,低电平有效
output clk_7 //输出7分频信号
);
parameter N = 7;
//reg define
reg [2:0] cnt; //最大值为6,所以需要3位位宽
reg cnt_pos; //上升沿敏感信号
reg cnt_neg; //下降沿敏感信号
assign clk_7 = cnt_pos && cnt_neg; //组合逻辑与
//计数模块,从0计数到6共计7个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 3'd0; //复位清零
else if(cnt == 3'd6) //计满7个时钟周期,从0开始计数,所以需要-1
cnt <= 3'd0; //计满则清零
else
cnt <= cnt + 3'd1; //没计满就一直计数
end
//cnt_pos:上升沿触发
//低电平维持三个基准时钟周期,高电平维持4个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_pos <= 1'b0; //复位清零
else if(cnt==3'd2) //计满三个时钟周期,
cnt_pos <= 1'b1; //前三个时钟周期输出为0,满足条件则输出1
else if(cnt==3'd6) //计满7个时钟周期
cnt_pos <= 1'b0; //后四个时钟周期输出为1,满足条件则输出0
else
cnt_pos <= cnt_pos; //不满足条件就保持原来状态
end
//cnt_neg:下降沿触发
//低电平维持三个基准时钟周期,高电平维持四个时钟周期
always @(negedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_neg <= 1'b0; //复位清零
else if(cnt==3'd2) //计满三个时钟周期
cnt_neg <= 1'b1; //前三个时钟周期输出为0,满足条件则输出为1.
else if(cnt==3'd6) //计满7个时钟周期
cnt_neg <= 1'b0; //后四个时钟周期输出为1,满足条件则输出0
else
cnt_neg <= cnt_neg; //不满足条件就保持原来状态
end
endmodule
接下来我们使用 Vivado 的 RTL ANALYSIS,来看一下我们编写代码的 RTL 视图。
3.2、仿真验证
3.2.1、编写 TB 文件
只需要对时钟以及复位信号进行激励,代码编写如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/05/15 13:49:48
// Design Name:
// Module Name: tb_divider_7
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb_divider_7(); //仿真模块
//输入 reg 定义
reg sys_clk;
reg sys_rst_n;
//输出 wire 定义
wire clk_7;
//设置初始化条件
initial begin
sys_clk = 1'b0; //初始化时钟为0
sys_rst_n <= 1'b0; //初始复位
#10 //10个时间单位后
sys_rst_n <= 1'b1; //拉高复位
end
//always代表重复进行,#10代表每10个时间单位
//每10个时间单位反转时钟,即时钟周期为20个时间单位(20ns)
always #10 sys_clk = ~sys_clk;
//例化被测试模块
divider_7 divider_7_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.clk_7 (clk_7 )
);
endmodule
3.2.2、仿真验证
测试程序在 Xilinx 的 Vivado 软件 或者其他仿真工具运行后的波形如下显示:
从波形图可以看到:10ns后停止复位,计数器cnt一直在从0计数到6;每当cnt计数到3的上升沿(L2),cnt_pos信号输出翻转,每当cnt计数清零(L4),cnt_pos信号输出翻转;每当cnt计数到3的下降沿(L1),cnt_neg信号输出翻转,每当cnt计数清零(L3),cnt_neg信号输出翻转;从L2到L3为7分频信号的半个时钟周期(高电平),从L2到L3为8分频信号的半个时钟周期(低电平);从L2到L6为7分频信号的1个完整的时钟周期,同时也是7个基准时钟周期。
4、用状态机实现7分频电路设计
三段式状态机的基本格式是:
-
第一个 always 语句实现同步状态跳转;
-
第二个 always 语句采用组合逻辑判断状态转移条件;
-
第三个 always 语句描述状态输出(可以用组合电路输出,也可以时序电路输出)。
在开始编写状态机代码之前,一般先画出状态跳转图,这样在编写代码时思路会比较清晰。
4.1、代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/06/20 16:16:39
// Design Name:
// Module Name: divider7_fsm
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
//以一个 7 分频为例
module divider7_fsm(
//系统时钟与复位
input sys_clk,
input sys_rst_n,
//输出时钟
output reg clk_divide_7
);
//在编写状态机代码时首先要定义状态变量(代码中的参数 S0~S6)与状态寄存器(curr_st、next_st)
//parameter define
parameter S0 = 7'b0000001; //独热码定义方式
parameter S1 = 7'b0000010;
parameter S2 = 7'b0000100;
parameter S3 = 7'b0001000;
parameter S4 = 7'b0010000;
parameter S5 = 7'b0100000;
parameter S6 = 7'b1000000;
//reg define
reg [6:0] curr_st; //当前状态
reg [6:0] next_st; //下一个状态
//*********************************************************
//** main code
//**********************************************************
//状态机的第一段采用同步时序描述状态转移
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
curr_st <= S0;
else
curr_st <= next_st;
end
//状态机的第二段采用逻辑组合判断状态转移条件
always @(*) begin
case (curr_st)
S0:next_st = S1;
S1:next_st = S2;
S2:next_st = S3;
S3:next_st = S4;
S4:next_st = S5;
S5:next_st = S6;
S6:next_st = S0;
default:next_st = S0;
endcase
end
//状态机的第三段描述状态输出(这里采用时序电路输出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_divide_7 <= 1'b0;
else if ((curr_st == S0) | (curr_st == S1) | (curr_st == S2) | (curr_st == S3))
clk_divide_7 <= 1'b0;
else if ((curr_st == S4) | (curr_st == S5) | (curr_st == S6))
clk_divide_7 <= 1'b1;
else
;
end
endmodule
//采用这种描述方法虽然代码结构复杂了一些,
//但是这样做的好处是可以有效地滤去组合逻辑输出的毛刺,
//同时也可以更好的进行时序计算与约束,另外对于总线形式的输出信号来说,
//容易使总线数据对齐,减小总线数据间的偏移,从而降低接收端数据采样出错的频率。
4.2、使用状态机的好处
采用这种描述方法虽然代码结构复杂了一些,但是这样做的好处是可以有效地滤去组合逻辑输出的毛刺,同时也可以更好的进行时序计算与约束,另外对于总线形式的输出信号来说,容易使总线数据对齐,减小总线数据间的偏移,从而降低接收端数据采样出错的频率。