问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 105 人浏览分享

开启左侧

[MILIANPAI-F01-EG4D]FPGA程序设计基础实验连载-08 FPGA多路分频器实验

[复制链接]
105 0
安路-FPGA课程
安路课程: 基础入门 » 新手入门实验
安路系列: EG4
本帖最后由 UT发布 于 2025-4-10 14:24 编辑

软件版本:TD_5.6.4_Release_97693
操作系统:WIN11 64bit
硬件平台:适用安路(Anlogic)FPGA
登录米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!
1 概述

FPGA中,时钟分频是经常用到的。本节课讲解2分频、3分频、4分频和8分频的Verilog实现并且学习generate语法功能的应用,本文的重点是培养读者对于verilog基础编程的基本功掌握。

在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力:

1:完成了TD软件安装

2:完成了modelsim安装以及TD库的编译

3:掌握了TD仿真环境的设置

4:掌握了modelsim通过do文件启动仿真

1.1分频器简介

在数字系统的设计中经常会碰到需要使用多个时钟的情况,这就需要对输入时钟进行分频从而得到多种时钟。时钟信号的产生通常具有两种方法,一种是在 FPGA设计中直接使用PLLPhase Locked Loop锁相环),可生成倍频、分频信号。但是这种方式有时候受限于 PLL 本身特性的限制,比如输入100Mhz 时钟,很多PLL都实现不了1Mhz的时钟分频,这个就是PLL本身特性的。另一种则是使用硬件描述语言构建一个分频电路,简单来说就是根据输入时钟,使用计数器计数输入时钟的上升沿个数或者时钟下降沿个数,根据设置分频数的位置进行输出信号的高低电平翻转,以此实现将输入信号的高频率信号转变为低频率输出信号。本节就是带领大家使用 Verilog 代码进行分频器电路的设计。

根据分频器的分频比例(晶振频率/分频后频率)是偶数还是奇数,将分频器分为偶数分频器和奇数分频器。

image.jpg

偶数分频:偶数分频器的设计较为简单,用一个简单的计数器就可以实现。假设为N(偶数)分频,只需设计一个计数器从0计数到 N/2-1(一共N/2个基准时钟),然后将输出分频时钟翻转、计数器清零,如此循环就可以得到 N分频。例如二分频,每检测到基准时钟一个上升沿/下降沿就翻转时钟。

奇数分频;奇数分频器的设计比偶数分频器复杂一些,一个计数器显然不能记录1/2个基准时钟周期。我们可以通过两个计数器分别对基准时钟的上升沿和下降沿计数,分别得到两个相差一个基准时钟周期的N-1分频时钟,再将上升沿N-1分频时钟和下降沿N-1分频时钟相或(上升沿时钟信号|下降沿时钟信号)即N分频电路。

1.2 硬件电路分析

参照流水灯硬件电路分析部分

2 程序设计思路

本次实验通过计数器实现不同时钟的分频。本实验顶层模块命名为Clk_Divider,需要两个输入的端口,分别为系统时钟、系统复位,四个时钟分频的输出。

image.jpg

2分频:

只要基于基准时钟每个时钟的上升沿对o_div2_r寄存器取反

4分频和8分频

共同使用了div_cnt1计数器,4分频就是对计数器在两个基准时钟之后,即div_cnt1==2'b00或者div_cnt1==2'b10的时候div4_o_r寄存器取反;而8分频是对计数器在四个基准时钟之后,即div_cnt1==2'b00的时候对o_div8_r取反

3分频:

3分频的本质是我们需要在每次1.5倍的时钟周期的时候实现3分频寄存器的翻转,但是我们无法直接实现1.5倍的分频。因此分别采取2个计数器pos_cntneg_cnt,分别对上升沿和下降沿计数。计数周期是0-1-2,共计3个时钟周期。我们取pos_cnt == 2’d1的时候div3_o_r0输出高电平,neg_cnt == 2’d1的时候o_div3_r1输出高电平。由于o_div3_r0o_div3_r1输出1个时钟的高电平,但是相位相差180°,因此只要执行o_div3 =o_ div3_r0 | o_div3_r1运算,就能实现1.5基准时钟周期的输出高电平,那么剩余的1.5基准时钟周期就是输出低电平了。

2HZ分频:

和流水灯实验的分频方法一样通过延时对寄存器取反操作得到想要的分频时钟2HZ分频时钟周期为500ms,设置一个ms250_en的信号,计时到250ms,信号拉高,对O_div2hz_r寄存器取反,就可以得到2HZ分频时钟。

clk_divider.v文件代码:

  1. module clk_divider#
  2. (
  3. parameter DEBUG_ENABLE = 1'b1,
  4. parameter REF_CLK      = 32'd25_000_000
  5. )
  6. (
  7. input  I_sysclk,
  8. input  I_rst_n,
  9. output O_div2,
  10. output O_div3,
  11. output O_div4,
  12. output O_div8,
  13. output O_div2hz
  14.     );
  15. //2分频代码:只要基于源时钟每个时钟的上升沿对O_div2_r寄存器取反   
  16. reg O_div2_r;
  17. always@(posedge I_sysclk)begin
  18.         if(!I_rst_n)
  19.                 O_div2_r <= 1'b0;
  20.         else
  21.                 O_div2_r <= ~O_div2_r;
  22. end
  23. //4分频和8分频代码:共同使用了div_cnt1计数器
  24. //4分频就是对计数器在div_cnt1==2'b00或者div_cnt1==2'b10的时候对O_div4_r寄存器取反;
  25. //而8分频是对div_cnt1==2'b00的时候对O_div8_r取反
  26. reg [1:0] div_cnt1;
  27. always@(posedge I_sysclk)begin
  28.         if(!I_rst_n)
  29.                 div_cnt1 <= 2'b00;
  30.         else
  31.                 div_cnt1 <= div_cnt1+1'b1;
  32. end
  33. reg O_div4_r;
  34. reg O_div8_r;
  35. always@(posedge I_sysclk)begin
  36.         if(!I_rst_n)
  37.                 O_div4_r <= 1'b0;
  38.         else if(div_cnt1==2'b00 || div_cnt1==2'b10)
  39.                 O_div4_r <= ~O_div4_r;
  40.         else
  41.                 O_div4_r <= O_div4_r;
  42. end
  43. always@(posedge I_sysclk)begin
  44.         if(!I_rst_n)
  45.                 O_div8_r <= 1'b0;
  46.         else if(div_cnt1==2'b00)
  47.                 O_div8_r <= ~O_div8_r;
  48.         else
  49.                 O_div8_r <= O_div8_r;
  50. end
  51. /*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倍源时钟周期就是输出低电平了。*/
  52. reg [1:0] pos_cnt;
  53. reg [1:0] neg_cnt;
  54. //上升沿计数
  55. always@(posedge I_sysclk)begin
  56.         if(!I_rst_n)
  57.                 pos_cnt <= 2'b00;
  58.         else if(pos_cnt == 2'd2)
  59.                 pos_cnt <= 2'b00;
  60.         else
  61.                 pos_cnt <= pos_cnt + 1'b1;
  62. end
  63. //下降沿计数
  64. always@(negedge I_sysclk)begin
  65.         if(!I_rst_n)        
  66.                 neg_cnt <= 2'b00;
  67.         else if(neg_cnt == 2'd2)
  68.                 neg_cnt <= 2'b00;
  69.         else
  70.                 neg_cnt <= neg_cnt + 1'b1;
  71. end
  72. //3分频
  73. reg O_div3_r0;
  74. reg O_div3_r1;
  75. always@(posedge I_sysclk)begin
  76.         if(!I_rst_n)
  77.                 O_div3_r0 <= 1'b0;
  78.         else if(pos_cnt < 2'd1)
  79.                 O_div3_r0 <= 1'b1;
  80.         else
  81.                 O_div3_r0 <= 1'b0;
  82. end
  83. always@(negedge I_sysclk)begin
  84.         if(!I_rst_n)
  85.                 O_div3_r1 <= 1'b0;
  86.         else if(neg_cnt < 2'd1)        
  87.                 O_div3_r1 <= 1'b1;
  88.         else
  89.                 O_div3_r1 <= 1'b0;
  90. end
  91. //2HZ
  92. reg O_div2hz_r;
  93. reg [25:0] div2hz_cnt;
  94. wire ms250_en = (div2hz_cnt == REF_CLK/4 - 1'b1);
  95. always@(posedge I_sysclk)begin
  96.         if(!I_rst_n)
  97.                 div2hz_cnt <= 0;
  98.         else if(div2hz_cnt < REF_CLK/4 - 1'b1)
  99.                 div2hz_cnt <= div2hz_cnt + 1'b1;
  100.         else
  101.                 div2hz_cnt <= 0;
  102. end
  103. always@(posedge I_sysclk)begin
  104.         if(!I_rst_n)
  105.                 O_div2hz_r <= 0;
  106.         else if(ms250_en)
  107.                 O_div2hz_r <= ~O_div2hz_r;
  108.         else
  109.                 O_div2hz_r <= O_div2hz_r;
  110. end
  111. //将寄存器中的值输出
  112. assign O_div2 = O_div2_r;
  113. assign O_div3 = O_div3_r0 | O_div3_r1;
  114. assign O_div4 = O_div4_r;
  115. assign O_div8 = O_div8_r;
  116. assign O_div2hz = O_div2hz_r;
  117. endmodule
复制代码
3 FPGA工程

fpga工程的创建过程不再重复,如有不清楚的请看前面实验,具体的FPGA型号以对应的开发板上芯片为准

image.jpg

米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹

01_rtl:放用户编写的rtl代码

02_sim:仿真文件或者工程

03_ip:放使用到的ip文件

04_pin:fpgapin脚约束文件或者时序约束文件

05_boot:放编译好的bit或者bin文件(一般为空)

06_doc:放本一些相关文档(一般为空)

image.jpg
4 Modelsim仿真
4.1 准备工作

Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验

仿真测试文件源码如下,对仿真精度没什么要求的,可以设置为1ns,另外仿真阶段可以把间隔时间设置小一些,这样仿真速度就快了:

  1. `timescale 1ns / 1ps
  2. module clk_divider_tb();
  3. // Inputs
  4. reg I_sysclk;
  5. reg I_rst_n;
  6. // Outputs
  7. wire O_div2;
  8. //wire div3_o;
  9. wire O_div4;
  10. wire O_div8;
  11. wire O_div2hz;
  12. // Instantiate the Unit Under Test (UUT)
  13. clk_divider#(
  14. .DEBUG_ENABLE(1'b0),
  15. .REF_CLK(1000)
  16. )
  17. clk_divider_inst
  18. (
  19. .I_sysclk(I_sysclk),
  20. .I_rst_n(I_rst_n),
  21. .O_div2(O_div2),
  22. .O_div4(O_div4),
  23. .O_div8(O_div8),
  24. .O_div2hz(O_div2hz)
  25. );
  26. initial begin
  27. // Initialize Inputs4
  28.     I_sysclk= 0;
  29.     I_rst_n = 0;
  30. // Wait 100 ns for global reset to finish
  31.     #100;
  32.     I_rst_n=1;
  33. end
  34. always #10 I_sysclk =~I_sysclk;
  35. endmodule
复制代码
4.2 启动modelsim仿真

启动后,右击需要观察的信号,添加到波形窗口

image.jpg

设置restart

image.jpg

设置运行100ms(如果运行时间太长可以修改小一些)

image.jpg

从仿真结果可以看到PWM占空比的调整

image.jpg
5测试结果

(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)

请确保下载器和开发板已经正确连接,并且开发板已经上电(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)

接通电源后LED闪烁,闪烁频率2Hz

image.jpg







































您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

0

粉丝

293

主题
精彩推荐
热门资讯
网友晒图
图文推荐

  • 微信公众平台

  • 扫描访问手机版