[X]关闭

[米联派-安路飞龙DR1-FPSOC] FPGA基础篇连载-08 FPGA多路分频器实验

文档创建者:FPGA课程
浏览次数:185
最后更新:2024-09-09
文档课程分类-安路-DR1
安路-DR1: FPSOC-DR1-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
本帖最后由 FPGA课程 于 2024-9-9 19:02 编辑

​ 软件本:Anlogic -TD5.9.1-DR1_ES1.1
操作系统:WIN10 64bit
硬件平台:适用安路(Anlogic)FPGA
实验平台:米联客-MLKPAI-SF01-DR1M90M开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

1概述
在FPGA中,时钟分频是经常用到的。本节课讲解2分频、3分频、4分频和8分频的Verilog实现并且学习generate语法功能的应用,本文的重点是培养读者对于verilog基础编程的基本功掌握。
在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力:
1:完成了TD软件安装
2:完成了modelsim安装以及TD库的编译
3:掌握了TD仿真环境的设置
4:掌握了modesim通过do文件启动仿真
1.1分频器简介
在数字系统的设计中经常会碰到需要使用多个时钟的情况,这就需要对输入时钟进行分频从而得到多种时钟。时钟信号的产生通常具有两种方法,一种是在 FPGA设计中直接使用PLL(Phase Locked Loop,锁相环),可生成倍频、分频信号。但是这种方式有时候受限于 PLL 本身特性的限制,比如输入100Mhz 时钟,很多PLL都实现不了1Mhz的时钟分频,这个就是PLL本身特性的。另一种则是使用硬件描述语言构建一个分频电路,简单来说就是根据输入时钟,使用计数器计数输入时钟的上升沿个数或者时钟下降沿个数,根据设置分频数的位置进行输出信号的高低电平翻转,以此实现将输入信号的高频率信号转变为低频率输出信号。本节就是带领大家使用 Verilog 代码进行分频器电路的设计。
根据分频器的分频比例(晶振频率/分频后频率)是偶数还是奇数,将分频器分为偶数分频器和奇数分频器。
28e6b24810e5478fb85eb18ff19ed782.jpg
偶数分频:偶数分频器的设计较为简单,用一个简单的计数器就可以实现。假设为N(偶数)分频,只需设计一个计数器从0计数到 N/2-1(一共N/2个基准时钟),然后将输出分频时钟翻转、计数器清零,如此循环就可以得到 N分频。例如二分频,每检测到基准时钟一个上升沿/下降沿就翻转时钟。
奇数分频;奇数分频器的设计比偶数分频器复杂一些,一个计数器显然不能记录1/2个基准时钟周期。我们可以通过两个计数器分别对基准时钟的上升沿和下降沿计数,分别得到两个相差一个基准时钟周期的N-1分频时钟,再将上升沿N-1分频时钟和下降沿N-1分频时钟相或(上升沿时钟信号|下降沿时钟信号)即N分频电路。
1.2 硬件电路分析
参照“流水灯”硬件电路分析部分
2 程序设计思路
本次实验通过计数器实现不同时钟的分频。本实验顶层模块命名为Clk_Divider,需要两个输入的端口,分别为系统时钟、系统复位,四个时钟分频的输出。
c3a682e2045e47409cde7e269b87f6ec.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_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分频:
和流水灯实验的分频方法一样,通过延时,对寄存器取反操作得到想要的分频时钟。2HZ分频时钟周期为500ms,设置一个ms250_en的信号,计时到250ms,信号拉高,对O_div2hz_r寄存器取反,就可以得到2HZ分频时钟。
Clk_Divider.v文件
  1. `timescale 1ns / 1ps
  2. module Clk_Divider#
  3. (
  4. parameter DEBUG_ENABLE = 1'b1,
  5. parameter REF_CLK      = 32'd25_000_000
  6. )
  7. (
  8. input I_clk,
  9. input  I_rst_n,
  10. output O_div2,
  11. output O_div3,
  12. output O_div4,
  13. output O_div8,
  14. output O_div2hz
  15.     );

  16. //2分频代码:只要基于源时钟每个时钟的上升沿对O_div2_r寄存器取反   
  17. reg O_div2_r;
  18. always@(posedge I_clk)begin
  19.     if(!I_rst_n)
  20.         O_div2_r <= 1'b0;
  21.     else
  22.         O_div2_r <= ~O_div2_r;
  23. end

  24. //4分频和8分频代码:共同使用了div_cnt1计数器
  25. //4分频就是对计数器在div_cnt1==2'b00或者div_cnt1==2'b10的时候对O_div4_r寄存器取反;
  26. //而8分频是对div_cnt1==2'b00的时候对O_div8_r取反
  27. reg [1:0] div_cnt1;
  28. always@(posedge I_clk)begin
  29.     if(!I_rst_n)
  30.         div_cnt1 <= 2'b00;
  31.     else
  32.         div_cnt1 <= div_cnt1+1'b1;
  33. end

  34. reg O_div4_r;
  35. reg O_div8_r;
  36. always@(posedge I_clk)begin
  37.     if(!I_rst_n)
  38.         O_div4_r <= 1'b0;
  39.     else if(div_cnt1==2'b00 || div_cnt1==2'b10)
  40.         O_div4_r <= ~O_div4_r;
  41.     else
  42.         O_div4_r <= O_div4_r;
  43. end

  44. always@(posedge I_clk)begin
  45.     if(!I_rst_n)
  46.         O_div8_r <= 1'b0;
  47.     else if(div_cnt1==2'b00)
  48.         O_div8_r <= ~O_div8_r;
  49.     else
  50.         O_div8_r <= O_div8_r;
  51. end
  52. /*
  53. 3分频的本质是我们需要在每次1.5倍的时钟周期的时候实现3分频寄存器的翻转,但是我们无法直接实现1.5倍的分频。
  54. 因此采取分别采取2个计数器pos_cnt和neg_cnt,分别对上升沿和下降沿计数。计数周期是0-1-2,共计3个时钟周期。
  55. 我们取pos_cnt == 2'd1的时候O_div3_r0输出高电平,neg_cnt == 2'd1的时候O_div3_r1输出高电平。
  56. 由于O_div3_r0和O_div3_r1输出1个时钟的高电平,但是相位相差180°,因此只要执行O_div3 = O_div3_r0 | O_div3_r1运算,
  57. 就能实现1.5倍周期的输出高电平,那么剩余的1.5倍源时钟周期就是输出低电平了。
  58. */
  59. reg [1:0] pos_cnt;
  60. reg [1:0] neg_cnt;
  61. always@(posedge I_clk)begin
  62.     if(!I_rst_n)
  63.         pos_cnt <= 2'b00;
  64.     else if(pos_cnt == 2'd2)
  65.         pos_cnt <= 2'b00;
  66.     else
  67.         pos_cnt <= pos_cnt + 1'b1;
  68. end

  69. always@(negedge I_clk)begin
  70.     if(!I_rst_n)   
  71.         neg_cnt <= 2'b00;
  72.     else if(neg_cnt == 2'd2)
  73.         neg_cnt <= 2'b00;
  74.     else
  75.         neg_cnt <= neg_cnt + 1'b1;
  76. end

  77. reg O_div3_r0;
  78. reg O_div3_r1;
  79. always@(posedge I_clk)begin
  80.     if(!I_rst_n)
  81.         O_div3_r0 <= 1'b0;
  82.     else if(pos_cnt < 2'd1)
  83.         O_div3_r0 <= 1'b1;
  84.     else
  85.         O_div3_r0 <= 1'b0;
  86. end

  87. always@(negedge I_clk)begin
  88.     if(!I_rst_n)
  89.         O_div3_r1 <= 1'b0;
  90.     else if(neg_cnt < 2'd1)
  91.         O_div3_r1 <= 1'b1;
  92.     else
  93.         O_div3_r1 <= 1'b0;
  94. end

  95. reg O_div2hz_r;
  96. reg [25:0] div2hz_cnt;

  97. wire ms250_en = (div2hz_cnt == REF_CLK/4 - 1'b1);
  98. always@(posedge I_clk)
  99. begin
  100.     if(!I_rst_n)
  101.         div2hz_cnt <= 0;
  102.     else if(div2hz_cnt < REF_CLK/4 - 1'b1)
  103.         div2hz_cnt <= div2hz_cnt + 1'b1;
  104.     else
  105.         div2hz_cnt <= 0;
  106. end

  107. always@(posedge I_clk)
  108. begin
  109.     if(!I_rst_n)
  110.         O_div2hz_r <= 0;
  111.     else if(ms250_en)
  112.         O_div2hz_r <= ~O_div2hz_r;
  113.     else
  114.         O_div2hz_r <= O_div2hz_r;
  115. end

  116. assign O_div2 = O_div2_r;
  117. assign O_div3 = O_div3_r0 | O_div3_r1;
  118. assign O_div4 = O_div4_r;
  119. assign O_div8 = O_div8_r;
  120. assign O_div2hz = O_div2hz_r;

  121. generate  if(DEBUG_ENABLE == 1'b1) begin : debugcore
  122. //添加ila IP ,Chipscope观察信号
  123. ila_0 ila_0 (
  124.     .clk(I_clk), // input wire clk
  125.     .probe0(O_div2hz), // input wire [0:0]  probe0  
  126.     .probe1({O_div2,O_div3,O_div4,O_div8}) // input wire [3:0]  probe1
  127. );
  128. end
  129. Endgenerate

  130. endmodule
复制代码
3FPGA工程
fpga工程的创建过程不再重复,如有不清楚的请看前面实验,具体的FPGA型号以对应的开发板上芯片为准
f8fd3b95af7142ee8b8ab0ced8f1df3a.jpg
米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹
01_rtl:放用户编写的rtl代码
02_sim:仿真文件或者工程
03_ip:放使用到的ip文件
04_pin:放fpga的pin脚约束文件或者时序约束文件
05_boot:放编译好的bit或者bin文件(一般为空)
06_doc:放本一些相关文档(一般为空)
e7c47bfad1e845b58d82267bc4c241f2.jpg

4Modelsim仿真4.1准备工作
Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验
仿真测试文件源码如下,对仿真精度没什么要求的,可以设置为1ns,另外仿真阶段可以把间隔时间设置小一些,这样仿真速度就快了:
  1. `timescale 1ns / 1ps
  2. module sim_top_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 #20 I_sysclk =~I_sysclk;
  35. endmodule
复制代码
4.2启动modelsim仿真
启动后,右击需要观察的信号,添加到波形窗口
图片1.jpg
设置restart
c6e98606df9d443ea037abcd0275e4cc.jpg
设置运行500ms(如果运行时间太长可以修改小一些)
95d5336a0a984ed4a5acb4af98def9c6.jpg
从仿真结果可以看到PWM占空比的调整
图片2.jpg
5测试结果5.1硬件连接
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
请确保下载器和开发板已经正确连接,并且开发板已经上电(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)

d5f91374b4ff44dba820103cd8ca03bd.jpg
5.2运行结果
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
请确保下载器和开发板已经正确连接,并且开发板已经上电。(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)

接通电源后LED闪烁,闪烁频率2Hz
947527200c254f708daf5e6ff224893e.jpg

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

本版积分规则