马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
x
(原创声明:该文是作者的原创,面向对象是FPGA入门者,后续会有进阶的高级教程。宗旨是让每个想做FPGA的人轻松入门,作者不光让大家知其然,还要让大家知其所以然!每个工程作者都搭建了全自动化的仿真环境,只需要双击top_tb.bat文件就可以完成整个的仿真(前提是安装了modelsim),降低了初学者的门槛。如果需要整个工程请加微信15092150280索要即可,不收任何费用,但是仅供参考,不建议大家获得资料后从事一些商业活动!)
第二课讲述了最简单的一个流水灯的实例,大家应该对计数器模块的设计比较熟悉了。第二课还有一个非常重要的点没有讲,那就是阻塞赋值和非阻塞赋值。初学者可能会比较奇怪,为什么有时候要用reg定义信号,有时候又要用wire定义信号,统一一下不可以吗?先明确说明一下:在有些情况下信号定义成reg和wire都可以,但是在有些情况下只可以定义成reg或者只可以定义成wire!
看到上面的说明后初学者更是头大了,这就是这课需要解决的问题。笔者刚开始FPGA设计的时候也是很迷惑,开始就是reg和wire随便混着用,编译报错了再修改,用的时间长了,再加上从书本和网上各种查找,终于弄清楚了两者的用法!先说一下,上课的例子中,one_second_done既可以定义成wire也可以定义成reg,而one_second_cnt只能定义成reg,不能定义成wire。
为了让本课的内容丰满一些,我们在第二课的基础上把需求更改一下,设计一个新的工程。工程要求:系统时钟是100MHz,低电平复位,假设有4个led灯(都是高电平点亮,低电平熄灭),按照1秒的频率进行回环流水闪烁(既0001->0010->0100->1000->0100->0010->0001)。不同于昨天的一直左移流水,这个工程是先左移3次,再右移3次,如此反复流水。
首先分析一下需求,定时的时间不变,那定时模块不需要任何的改变,需要修改的是流水灯部分。不同于第二课只需要一直左移流水灯,现在既需要左移,又需要右移,而且是左移3次,再右移3次,然后再左移3次,再右移3次。。。。。。所以最简单的实现方式是设置一位的标志寄存器,在需要左移的时候该寄存器拉高,在需要右移的时候该寄存器拉低,然后在流水灯模块根据这个指示信号来进行左移或者右移。
然后确认一下这个左右移标识寄存器变化的条件,根据回环流水灯的状态可以得知在灯的状态是0001的时候开始左移,在状态是1000后开始右移,所以这个寄存器的设计就非常容易了,如下所示。定义了一个reg类型的左右移寄存器left_right_domain,该信号在复位rst_n拉低的情况下默认是1,就是默认流水灯左移;在灯的状态是0001的时候,即右移到头了,此时将left_right_domain信号赋值为1;在灯的状态是1000的时候,即左移到头了,此时将left_right_domain信号赋值为0。在流水灯模块中,根据left_right_domain信号来设置左移或者右移动即可。
我们看一下的modelsim仿真结果(由于计数99_999_999次仿真时间较长,仿真时改为了计数9次)。leds的状态确实是按照设想的0001->0010->0100->1000->0100->0010->0001变化,功能正常。
说完了工程的功能,下面进入今天的正题,阻塞赋值和非阻塞赋值。
先解释一下什么是阻塞赋值,阻塞,顾名思义就是堵住了,比如堵车,前面的没有疏通开后面的是过不去的!再想象一下独木桥,必须按照顺序一个一个的过去;再如软件编程,必须是串行执行的,必须执行完前面语句才能去执行后面的语句。所以阻塞赋值就是一个模块中的多个赋值语句,必须顺序执行,即执行完前一句再去执行后一句,执行顺序和书写有关,在verilog编码中,使用“=”号来表示阻塞赋值,而且在仿真(综合前仿真)中,阻塞赋值是不消耗仿真时间的。
如下所示,定义了三个寄存器a,b,c,在复位rst_n拉低的情况下三个信号都是0,在其它情况下先执行“a=1”,即a的值是1;再执行“b=a“,即b的值也是1;最后执行”c=b“,即c的值也是1。
下面是仿真的结果,理解一下阻塞赋值的仿真不消耗仿真时间。如下所示,在rst_n拉高后的clk的上升沿,a,b,c同时赋值为1。显示是没有消耗仿真时间,其实仿真软件处理中上下两条语句间会默认有一个Δt,可以理解成是数学上的无穷小。
再看看非阻塞赋值,非阻塞,很明显就是堵不住,比如3辆车分别行驶在3个车道上,谁也不影响谁,不同与阻塞赋值的串行执行,非阻塞赋值是并行执行。所以非阻塞赋值就是一个模块中的多个赋值语句,都是并行执行,执行顺序和书写无关,在verilog编码中,使用“<=”号来表示阻塞赋值,而且在仿真(综合前仿真)中,非阻塞赋值是消耗仿真时间的。
如下所示,定义了三个寄存器a,b,c,和上面的例子一样,就是把阻塞赋值改为了非阻塞赋值。
下面是仿真的结果,理解一下非阻塞赋值的仿真要消耗仿真时间。如下所示,在rst_n拉高后的clk的第一个上升沿,a赋值为1,第二个clk的上升沿,b赋值为1,第三个clk的上升沿,c才赋值为1。
解释一下上面非阻塞赋值的流程,非阻塞赋值是在clk上升沿的时候开始赋值,在仿真软件中,赋值有效是需要一个Δt时间的,所以在第1个时钟的上升沿,此时a,b,c都是0,语句执行Δt时间后,a才赋值为1,b赋值为0(a在第1个clk上升沿时候的值),c赋值为0(b在第1个clk上升沿时候的值);到了第2时钟的上升沿,此时a为1,b和c为0,语句执行Δt时间后,a还是1,b被赋值为1(a在第2个clk上升沿时候的值),c赋值为0(b在第2个clk上升沿时候的值);到了第3时钟的上升沿,此时a和b为1,c为0,语句执行Δt时间后,a还是1,b也是1(a在第3个clk上升沿时候的值),c赋值也为1(b在第3个clk上升沿时候的值);再往后a,b,c的值都是1了。
为什么要分阻塞赋值和非阻塞赋值?这个解释起来有点麻烦,要从组合逻辑和时序逻辑说起,下课好好的解释。
|