本帖最后由 UT发布 于 2025-4-10 14:24 编辑
软件版本:TD_5.6.4_Release_97693 操作系统:WIN11 64bit 硬件平台:适用安路(Anlogic)FPGA 1 概述在FPGA中,时钟分频是经常用到的。本节课讲解2分频、3分频、4分频和8分频的Verilog实现并且学习generate语法功能的应用,本文的重点是培养读者对于verilog基础编程的基本功掌握。 在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力: 1.1分频器简介在数字系统的设计中经常会碰到需要使用多个时钟的情况,这就需要对输入时钟进行分频从而得到多种时钟。时钟信号的产生通常具有两种方法,一种是在 FPGA设计中直接使用PLL(Phase Locked Loop,锁相环),可生成倍频、分频信号。但是这种方式有时候受限于 PLL 本身特性的限制,比如输入100Mhz 时钟,很多PLL都实现不了1Mhz的时钟分频,这个就是PLL本身特性的。另一种则是使用硬件描述语言构建一个分频电路,简单来说就是根据输入时钟,使用计数器计数输入时钟的上升沿个数或者时钟下降沿个数,根据设置分频数的位置进行输出信号的高低电平翻转,以此实现将输入信号的高频率信号转变为低频率输出信号。本节就是带领大家使用 Verilog 代码进行分频器电路的设计。 根据分频器的分频比例(晶振频率/分频后频率)是偶数还是奇数,将分频器分为偶数分频器和奇数分频器。 偶数分频:偶数分频器的设计较为简单,用一个简单的计数器就可以实现。假设为N(偶数)分频,只需设计一个计数器从0计数到 N/2-1(一共N/2个基准时钟),然后将输出分频时钟翻转、计数器清零,如此循环就可以得到 N分频。例如二分频,每检测到基准时钟一个上升沿/下降沿就翻转时钟。 奇数分频;奇数分频器的设计比偶数分频器复杂一些,一个计数器显然不能记录1/2个基准时钟周期。我们可以通过两个计数器分别对基准时钟的上升沿和下降沿计数,分别得到两个相差一个基准时钟周期的N-1分频时钟,再将上升沿N-1分频时钟和下降沿N-1分频时钟相或(上升沿时钟信号|下降沿时钟信号)即N分频电路。 1.2 硬件电路分析2 程序设计思路本次实验通过计数器实现不同时钟的分频。本实验顶层模块命名为Clk_Divider,需要两个输入的端口,分别为系统时钟、系统复位,四个时钟分频的输出。 只要基于基准时钟每个时钟的上升沿对o_div2_r寄存器取反 共同使用了div_cnt1计数器,4分频就是对计数器在两个基准时钟之后,即在div_cnt1==2'b00或者div_cnt1==2'b10的时候,对div4_o_r寄存器取反;而8分频是对计数器在四个基准时钟之后,即div_cnt1==2'b00的时候对o_div8_r取反 3分频的本质是我们需要在每次1.5倍的时钟周期的时候实现3分频寄存器的翻转,但是我们无法直接实现1.5倍的分频。因此分别采取2个计数器pos_cnt和neg_cnt,分别对上升沿和下降沿计数。计数周期是0-1-2,共计3个时钟周期。我们取pos_cnt == 2’d1的时候div3_o_r0输出高电平,neg_cnt == 2’d1的时候o_div3_r1输出高电平。由于o_div3_r0和o_div3_r1输出1个时钟的高电平,但是相位相差180°,因此只要执行o_div3 =o_ div3_r0 | o_div3_r1运算,就能实现1.5倍基准时钟周期的输出高电平,那么剩余的1.5倍基准时钟周期就是输出低电平了。 和流水灯实验的分频方法一样,通过延时,对寄存器取反操作得到想要的分频时钟。2HZ分频时钟周期为500ms,设置一个ms250_en的信号,计时到250ms,信号拉高,对O_div2hz_r寄存器取反,就可以得到2HZ分频时钟。
- module clk_divider#
- (
- parameter DEBUG_ENABLE = 1'b1,
- parameter REF_CLK = 32'd25_000_000
- )
- (
- input I_sysclk,
- input I_rst_n,
- output O_div2,
- output O_div3,
- output O_div4,
- output O_div8,
- output O_div2hz
- );
-
- //2分频代码:只要基于源时钟每个时钟的上升沿对O_div2_r寄存器取反
- reg O_div2_r;
- always@(posedge I_sysclk)begin
- if(!I_rst_n)
- O_div2_r <= 1'b0;
- else
- O_div2_r <= ~O_div2_r;
- end
- //4分频和8分频代码:共同使用了div_cnt1计数器
- //4分频就是对计数器在div_cnt1==2'b00或者div_cnt1==2'b10的时候对O_div4_r寄存器取反;
- //而8分频是对div_cnt1==2'b00的时候对O_div8_r取反
- reg [1:0] div_cnt1;
- always@(posedge I_sysclk)begin
- if(!I_rst_n)
- div_cnt1 <= 2'b00;
- else
- div_cnt1 <= div_cnt1+1'b1;
- end
- reg O_div4_r;
- reg O_div8_r;
- always@(posedge I_sysclk)begin
- if(!I_rst_n)
- O_div4_r <= 1'b0;
- else if(div_cnt1==2'b00 || div_cnt1==2'b10)
- O_div4_r <= ~O_div4_r;
- else
- O_div4_r <= O_div4_r;
- end
- always@(posedge I_sysclk)begin
- if(!I_rst_n)
- O_div8_r <= 1'b0;
- else if(div_cnt1==2'b00)
- O_div8_r <= ~O_div8_r;
- else
- O_div8_r <= O_div8_r;
- end
- /*3分频的本质是我们需要在每次1.5倍的时钟周期的时候实现3分频寄存器的翻转,但是我们无法直接实现1.5倍的分频。因此采取分别采取2个计数器pos_cnt和neg_cnt,分别对上升沿和下降沿计数。计数周期是0-1-2,共计3个时钟周期。我们取pos_cnt == 2'd1的时候O_div3_r0输出高电平,neg_cnt == 2'd1的时候O_div3_r1输出高电平。由于O_div3_r0和O_div3_r1输出1个时钟的高电平,但是相位相差180°,因此只要执行O_div3 = O_div3_r0 | O_div3_r1运算,就能实现1.5倍周期的输出高电平,那么剩余的1.5倍源时钟周期就是输出低电平了。*/
- reg [1:0] pos_cnt;
- reg [1:0] neg_cnt;
- //上升沿计数
- always@(posedge I_sysclk)begin
- if(!I_rst_n)
- pos_cnt <= 2'b00;
- else if(pos_cnt == 2'd2)
- pos_cnt <= 2'b00;
- else
- pos_cnt <= pos_cnt + 1'b1;
- end
- //下降沿计数
- always@(negedge I_sysclk)begin
- if(!I_rst_n)
- neg_cnt <= 2'b00;
- else if(neg_cnt == 2'd2)
- neg_cnt <= 2'b00;
- else
- neg_cnt <= neg_cnt + 1'b1;
- end
- //3分频
- reg O_div3_r0;
- reg O_div3_r1;
- always@(posedge I_sysclk)begin
- if(!I_rst_n)
- O_div3_r0 <= 1'b0;
- else if(pos_cnt < 2'd1)
- O_div3_r0 <= 1'b1;
- else
- O_div3_r0 <= 1'b0;
- end
- always@(negedge I_sysclk)begin
- if(!I_rst_n)
- O_div3_r1 <= 1'b0;
- else if(neg_cnt < 2'd1)
- O_div3_r1 <= 1'b1;
- else
- O_div3_r1 <= 1'b0;
- end
- //2HZ
- reg O_div2hz_r;
- reg [25:0] div2hz_cnt;
- wire ms250_en = (div2hz_cnt == REF_CLK/4 - 1'b1);
- always@(posedge I_sysclk)begin
- if(!I_rst_n)
- div2hz_cnt <= 0;
- else if(div2hz_cnt < REF_CLK/4 - 1'b1)
- div2hz_cnt <= div2hz_cnt + 1'b1;
- else
- div2hz_cnt <= 0;
- end
- always@(posedge I_sysclk)begin
- if(!I_rst_n)
- O_div2hz_r <= 0;
- else if(ms250_en)
- O_div2hz_r <= ~O_div2hz_r;
- else
- O_div2hz_r <= O_div2hz_r;
- end
- //将寄存器中的值输出
- assign O_div2 = O_div2_r;
- assign O_div3 = O_div3_r0 | O_div3_r1;
- assign O_div4 = O_div4_r;
- assign O_div8 = O_div8_r;
- assign O_div2hz = O_div2hz_r;
-
- endmodule
复制代码
3 FPGA工程fpga工程的创建过程不再重复,如有不清楚的请看前面实验,具体的FPGA型号以对应的开发板上芯片为准 米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹 04_pin:放fpga的pin脚约束文件或者时序约束文件 05_boot:放编译好的bit或者bin文件(一般为空) 4 Modelsim仿真
4.1 准备工作Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验 仿真测试文件源码如下,对仿真精度没什么要求的,可以设置为1ns,另外仿真阶段可以把间隔时间设置小一些,这样仿真速度就快了: - `timescale 1ns / 1ps
- module clk_divider_tb();
- // Inputs
- reg I_sysclk;
- reg I_rst_n;
- // Outputs
- wire O_div2;
- //wire div3_o;
- wire O_div4;
- wire O_div8;
- wire O_div2hz;
-
- // Instantiate the Unit Under Test (UUT)
- clk_divider#(
- .DEBUG_ENABLE(1'b0),
- .REF_CLK(1000)
- )
- clk_divider_inst
- (
- .I_sysclk(I_sysclk),
- .I_rst_n(I_rst_n),
- .O_div2(O_div2),
- .O_div4(O_div4),
- .O_div8(O_div8),
- .O_div2hz(O_div2hz)
- );
-
- initial begin
- // Initialize Inputs4
- I_sysclk= 0;
- I_rst_n = 0;
- // Wait 100 ns for global reset to finish
- #100;
- I_rst_n=1;
- end
- always #10 I_sysclk =~I_sysclk;
- endmodule
复制代码
4.2 启动modelsim仿真设置运行100ms(如果运行时间太长可以修改小一些) 5测试结果(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。) 请确保下载器和开发板已经正确连接,并且开发板已经上电。(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)
|