赞
踩
目录
前面一节我们学习了偶数分频器的设计方法,本节我们来学习下奇数分频器的设计方法。实现偶数分频可通过一个简单计数器实现,而如果需要三分频,五分频,七分频等奇数分频,一个计数器是不够的。奇数分频器的设计相对偶数分频器设计要复杂一点,我们来看下奇数分频设计方法。
在《偶数分频器电路设计》章节提到实现分频一般有两个方法,一个方法是直接使用 PLL 进行分频,比如 FPGA 或者 ASIC 设计中,都可以直接使用 PLL 进行分频。还有一种实现方法就是直接使用逻辑实现,即使用代码实现分频设计。我们本节介绍的是使用代码进行设计奇数分频器。本节我们先看下奇数分频设计。
使用 Verilog 语言设计一个任意奇数分频电路,默认进行 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 视图。
只需要对时钟以及复位信号进行激励,代码编写如下:
- `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
测试程序在 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个基准时钟周期。
三段式状态机的基本格式是:
在开始编写状态机代码之前,一般先画出状态跳转图,这样在编写代码时思路会比较清晰。
- `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
-
- //采用这种描述方法虽然代码结构复杂了一些,
- //但是这样做的好处是可以有效地滤去组合逻辑输出的毛刺,
- //同时也可以更好的进行时序计算与约束,另外对于总线形式的输出信号来说,
- //容易使总线数据对齐,减小总线数据间的偏移,从而降低接收端数据采样出错的频率。
采用这种描述方法虽然代码结构复杂了一些,但是这样做的好处是可以有效地滤去组合逻辑输出的毛刺,同时也可以更好的进行时序计算与约束,另外对于总线形式的输出信号来说,容易使总线数据对齐,减小总线数据间的偏移,从而降低接收端数据采样出错的频率。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。