从计数器逻辑中揭秘神奇的HDL

0.绪言

本文通过一些简单的计数器逻辑语句,从最底层分析硬件描述语言( HDL )的特点。

1.计数器语句

计数逻辑可谓是学习任何一门编程语言的基础中的基础,往小了说,计数可以用来控制循环次数、分频等应用;往大了说,计数逻辑是整个时序逻辑的基石。所以,理解透彻计数逻辑,对学习HDL有着至关重要的意义。有人可能觉得计数逻辑非常简单,但确实如此吗?原理上简单可并不代表运用起来你就能得心应手哦!不少FPGA初学者往往在最开始的时钟分频程序上头痛不已,关键点也正在此处。

先来看一段程序:

#include <stdio.h>
#include<windows.h>int cnt = 0;
int main(void)
{while(1){Sleep(1000);cnt = cnt + 1;if(cnt == 10)cnt = 0;printf("%d  ", cnt); }
}

这是C语言的一段计数程序,相信不难看出,10个数字为一个周期循环出现,分别是0~9而没有10(当cnt为10时,立刻被置为0),为什么要讨论这么仔细呢?可能在大多数C程序中不太常见,但是在HDL程序中,讨论这个太有必要了!这里先放上这段代码的结果,方便与下文对比。

再来看一段VHDL版本的计数程序:

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;entity test isport(clk  :in std_logic;cnt   :out    std_logic_vector(3 downto 0));
end test;architecture tt of test is
signal count:integer range 0 to 10;
beginprocess(clk)beginif(clk'event and clk = '1')thencount <= count + 1;if(count = 10)thencount <= 0;end if;end if;end process;cnt <= conv_std_logic_vector(count,4); --将整数转化为位矢量
end tt;

即使是初学者想必也能看出这个程序与之前的C语言程序语句逻辑是一致的,但是得到的结果也会是一样的吗?答案是NO!这个程序的仿真结果中11个数字为一个周期,分别为0~10,10这个数字也存在!!!

假若我们本意是想利用计数实现一个时钟十分频的功能,可结果却变成了十一分频,细节决定一切,在硬件描述语言中,这句话更是体现得淋漓尽致!FPGA的设计开发,就是要求工程师们时刻保持严谨的态度,能够把问题定位到每一个细节,做到真的懂硬件!

下面我们来分析问题出在哪:

其实这两段程序逻辑都正确,但是VHDL语法上比较特殊, VHDL对于信号的赋值不是立刻发生的,需要等待整个进程结束后才会把新值装载到信号的寄存器中 ,了解了这个本质,问题就迎刃而解了。当count是9时,给它加一变成10,但这时赋值不会立刻发生,所以count<=count+1;这条语句后count的值还是9,自然不会进入if语句内,所以有10这一数字存在。

但是, VHDL对于变量的赋值是立刻发生的 ,我们可以稍微改变一下程序:

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;entity test isport(clk  :in std_logic;cnt   :out    std_logic_vector(7 downto 0));
end test;architecture tt of test is
beginprocess(clk)variable count:integer range 0 to 10;beginif(clk'event and clk = '1')thencount := count + 1;if(count = 10)thencount := 0;end if;cnt <= conv_std_logic_vector(count,8); --将整数转化为位矢量end if;end process;
end tt;

把计数信号改成了变量,使之能够即时赋值,结果如下:

此时,终于看不见10的存在了,大功告成!

2.VHDL与Verilog HDL的对比

既然C语言和VHDL都出场了,不妨再通过计数器程序来看看Verilog HDL有啥不一样的地方:

`timescale 1ns/1psmodule test
(input clk,output reg[7:0] cnt
);always @(posedge clk)
begincnt <= cnt + 1'b1;if(cnt == 10)cnt <= 8'd0;
end
endmodule

可以看出,Verilog HDL比VHDL更简洁易懂,更偏向C语言的风格,比较容易上手,这也可能是Verilog HDL如今比较主流的原因吧。但是,Verilog也和VHDL一样,具有两种赋值语句,一种是立即发生的,另一种则需要等待进程结束,在Verilog中把它们分别叫做 阻塞赋值语句 (符号=)和 非阻塞赋值语句 (符号<=)。上面的代码采用的是非阻塞赋值,赋值需要等待进程结束才能实现,此时就会出现存在10的情况:

下面改用阻塞赋值语句:

`timescale 1ns/1psmodule test
(input clk,output reg[7:0] cnt
);always @(posedge clk)
begincnt = cnt + 1'b1;if(cnt == 10)cnt = 8'd0;
end
endmodule

结果如下:

Verilog的两段代码相差不大,只是两个<=变成了=,结果就很大不同了。Verilog确实要比VHDL更简洁易懂,但是少了一份严谨,如果没有对其语法细节非常熟悉的话,很容易出现意料之外的错误。

VHDL与Verilog的取舍,主要还是要看个人爱好,但无论选择哪种语言,严谨的态度是需要始终保持的!

3.其他计数逻辑语句

其实一个自循环的计数程序,主要有两部分——递增(减)变化、条件值跳变(有时还可能与初始条件有关),这两者的顺序并不是固定的,可以采用各种方式组合,下面给大家展示其他风格的计数逻辑语句(以Verilog 为例,都是实现10个数一个周期的计数):

always @(posedge clk)
begin               if(cnt == 10)     //1~10(也可改成0~9)cnt <= 8'd1;  //此时非阻塞赋值<=与阻塞赋值=的效果一样   elsecnt <= cnt + 1'b1;
end
endmodule
always @(posedge clk)
begin               if(cnt == 10)     //1~10(也可改成0~9)cnt = 8'd0;      //此时只能使用阻塞赋值=cnt = cnt + 1'b1;
end
endmodule

其实计数逻辑非常简单,但也非常重要,不要小看它哦!希望大家能够通过这些例子,体会到HDL的奥秘!在今后的学习中能够一直保持严谨,追求本质,在细节处见真知!

4.计数逻辑大应用

最后,给大家分享一些计数逻辑在各种项目中的实际应用,你就会明白它有多重要!

1.时钟分频:

--时钟分频:串口时钟,波特率为9600
--16个时钟发(收)1bit:1个起始位、8个数据位、1个停止位
--50Mhz时钟326分频
process(clk_50Mhz)
variable cnt:integer range 0 to 163;
begin
if(clk_50Mhz'event and clk_50Mhz = '1')thencnt := cnt + 1;if(cnt = 163)thenclk_uart <= not clk_uart;cnt := 0;end if;
end if;
end process;

2.串口收发:

--------------------------------------------
--串口接收程序:16个时钟接收一个bit
--------------------------------------------
process(clk_uart)
begin
if(clk_uart'event and clk_uart = '1')thenif(receive = '1')thencase rx_count iswhen 0     =>  rx_state <= '1'; rx_count <= rx_count + 1; rx_sig <= '0';when 24 =>    rx_state <= '1'; rx_data(0) <= rx; rx_count <= rx_count + 1; rx_sig <= '0';        --接收数据0位when 40 =>  rx_state <= '1'; rx_data(1) <= rx; rx_count <= rx_count + 1; rx_sig <= '0';        --接收数据1位when 56 =>  rx_state <= '1'; rx_data(2) <= rx; rx_count <= rx_count + 1; rx_sig <= '0';        --接收数据2位when 72 =>  rx_state <= '1'; rx_data(3) <= rx; rx_count <= rx_count + 1; rx_sig <= '0';        --接收数据3位when 88 =>  rx_state <= '1'; rx_data(4) <= rx; rx_count <= rx_count + 1; rx_sig <= '0';        --接收数据4位when 104 => rx_state <= '1'; rx_data(5) <= rx; rx_count <= rx_count + 1; rx_sig <= '0';        --接收数据5位when 120 => rx_state <= '1'; rx_data(6) <= rx; rx_count <= rx_count + 1; rx_sig <= '0';        --接收数据6位when 136 => rx_state <= '1'; rx_data(7) <= rx; rx_count <= rx_count + 1; rx_sig <= '1';        --接收数据7位when 152 => rx_state <= '1'; rx_count <= rx_count + 1; rx_sig <= '1';when others => rx_count <= rx_count + 1;                 --计数end case;elserx_state <= '0'; rx_count <= 0; rx_sig <= '0';end if;
end if;
end process;--------------------------------------------
--串口发送程序:16个时钟发送一个bit
--------------------------------------------
process(clk_uart)
begin
if(clk_uart'event and clk_uart = '1')thenif(send = '1')thencase tx_count iswhen 0    =>  tx <= '0'; tx_state <= '1'; tx_count <= tx_count + 1;      --发送起始位when 16 =>   tx <= tx_data(0); tx_state <= '1'; tx_count <= tx_count + 1;     --发送数据0位when 32 =>  tx <= tx_data(1); tx_state <= '1'; tx_count <= tx_count + 1;     --发送数据1位when 48 =>  tx <= tx_data(2); tx_state <= '1'; tx_count <= tx_count + 1;     --发送数据2位when 64 =>  tx <= tx_data(3); tx_state <= '1'; tx_count <= tx_count + 1;     --发送数据3位when 80 =>  tx <= tx_data(4); tx_state <= '1'; tx_count <= tx_count + 1;     --发送数据4位when 96 =>  tx <= tx_data(5); tx_state <= '1'; tx_count <= tx_count + 1;     --发送数据5位when 112 => tx <= tx_data(6); tx_state <= '1'; tx_count <= tx_count + 1;     --发送数据6位when 128 => tx <= tx_data(7); tx_state <= '1'; tx_count <= tx_count + 1;     --发送数据7位when 144 => tx <= '1'; tx_state <= '1'; tx_count <= tx_count + 1;      --发送停止位when 152 => tx <= '1'; tx_state <= '0'; tx_count <= tx_count + 1;       --一帧数据发送结束when others => tx_count <= tx_count + 1; --计数end case;elsetx <= '1'; tx_count <= 0; tx_state <= '0';end if;
end if;
end process;

3.VGA传输时序

--行计数器进程,计数周期为h_period
process(clk_VGA)
begin
if(clk_VGA'event and clk_VGA = '1')thenif(hcnt = h_period)then     --一个周期结束hcnt <= 1;                  --计数值范围1~h_periodelsehcnt <= hcnt + 1;end if;
end if;
end process;--产生行同步信号
process(clk_VGA)
begin
if(clk_VGA'event and clk_VGA = '1')thenif(hcnt = h_period)then     --一个周期结束,产生行同步脉冲hsync <= '0';elsif(hcnt = h_sync)then --从1到h_sync,刚好h_sync个时钟周期hsync <= '1';end if;
end if;
end process;--列计数器进程,计数周期为v_period
process(clk_VGA)
begin
if(clk_VGA'event and clk_VGA = '1')thenif(hcnt = h_period)then         --一行代表一个时钟周期if(vcnt = v_period)then        --一个周期结束vcnt <= 1;              --计数值范围1~v_periodelsevcnt <= vcnt + 1;end if;end if;
end if;
end process;--产生列同步信号
process(clk_VGA)
begin
if(clk_VGA'event and clk_VGA = '1')thenif(vcnt = v_period)then         --一帧结束,产生列同步脉冲vsync <= '0';              elsif(vcnt = v_sync)then      --从1到v_sync,刚好v_sync个周期vsync <= '1';end if;
end if;
end process;

——本文所有代码均为原创

从计数器逻辑中揭秘神奇的HDL相关推荐

  1. 实验五、计数器逻辑功能和设计

    6.实验内容及步骤 (1)测试74HC161的逻辑功能,根据测试结果总结并描述其逻辑功能,表格自行完善. 表2.5.1  74HC161的功能表 (2)测试74HC390的逻辑功能,根据测试结果总结并 ...

  2. Hystrix降级逻辑中如何获取触发的异常?

    通过之前Spring Cloud系列教程中的<Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)>一文,我们已经知道如何通过Hystrix来保护自己的服务不被外 ...

  3. Hystrix降级逻辑中如何获取触发的异常

    通过之前Spring Cloud系列教程中的<Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)>一文,我们已经知道如何通过Hystrix来保护自己的服务不被外 ...

  4. java中的神奇this

    java中的神奇"this",神奇的原因事它能不用new就可以直接创造一个对象出来,后来研究发现,其实java的"this"使用时,也是"new&qu ...

  5. Hystrix降级逻辑中如何获取触发的异常 1

    通过之前Spring Cloud系列教程中的<Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)>一文,我们已经知道如何通过Hystrix来保护自己的服务不被外 ...

  6. (转)淘淘商城系列——在业务逻辑中添加缓存

    http://blog.csdn.net/yerenyuan_pku/article/details/72871268 上文我们一起学习了如何使用Spring容器来管理Redis单机版和集群版实现,本 ...

  7. 解决极值中的神奇设k法_神奇宝贝Go拥有对您的Google帐户的完全访问权限。 这是解决方法[更新]...

    解决极值中的神奇设k法 To say Pokémon GO is wildly popular would be a vast understatement. To say the app's use ...

  8. 逻辑中的对偶原理与蕴含定理

    这两个是总结出的符号的变化规律和推理规律用于简化推理,这两个都是等值变换.约定不是乱定的,好的约定就是一个定理,在证明约定的合理性后,能简化大量的推理细节,这是一种不同于定理的封装方式. 在了解蕴含定 ...

  9. 如何在Keynote 讲演中添加神奇移动效果?新手教程

    可以在Keynote中的神奇移动功能,只需移动或缩放幻灯片中的元素,换页时就会生成流畅的过渡.macw小编带来详细操作教程,喜欢的朋友不要错过! 图文教程 1.首先,将幻灯片整页复制,确保两页具有相同 ...

最新文章

  1. golang 遍历list_golang如何把一个list遍历给一个切片
  2. mysql 连接池的作用,数据库连接池介绍、主要参数设置、作用
  3. mac os 开启redis_关于Redis,学会这8点就够了
  4. mysql latid1_mysql触发器的实战经验
  5. Windows系统自带WMI应用的查询使用
  6. android 安全 权限,[原创]Android 中的那些权限
  7. 济源一中2021高考成绩查询,济源一中2019高考成绩喜报、一本二本上线人数情况...
  8. jquery 获取 radio值 与 jQuery filter() 方法
  9. char类型怎么输入 c语言_C语言的标准 “输入输出”!今天是你学C语言的第几天?...
  10. R语言ETL工程:插入与合并(add/bind)
  11. ESP8266(2)
  12. 183.从不订购的客户
  13. 武汉涉密信息系统集成资质介绍
  14. 服务器p盘cpu占用率低,硬盘问题导致的CPU占用率100%解决实例
  15. python框架知乎_我正在学习python的flask框架?为什么样知乎没有选择 Ruby
  16. Linux基础命令01(ls , cd,clear,cat等)
  17. 【凸优化】maximal 与 maximum的不同
  18. 为了下半年的「双 11」,阿里的「赚钱机器」开始冲刺
  19. 《软件方法》第二章 自测题
  20. 企业微信scrm是什么

热门文章

  1. hadoop namenode ha方案
  2. SSL基础:20:使用x509子命令为其他证书签名
  3. 彩色matlab代码拷贝到word研究,matlab编辑器合并_彩色MATLAB代码拷贝到WORD研究
  4. 希尔伯特谱、边际谱、包络谱、瞬时频率/幅值/相位——Hilbert分析衍生方法及MATLAB实现
  5. 《培根随笔》读书笔记 (一)
  6. IrisSkin4.dll皮肤编辑器对应的皮肤图
  7. linux connect自动重连,Linux 北大网关断网重连
  8. javaweb学生竞赛管理系统
  9. 什么是EFI系统分区?
  10. CUDA C/C++ 从入门到入土 第一步——让你的CUDA跑起来