马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
x
(原创声明:该文是作者的原创,面向对象是FPGA入门者,后续会有进阶的高级教程。宗旨是让每个想做FPGA的人轻松入门,作者不光让大家知其然,还要让大家知其所以然!每个工程作者都搭建了全自动化的仿真环境,只需要双击top_tb.bat文件就可以完成整个的仿真(前提是安装了modelsim),降低了初学者的门槛。如果需要整个工程请加微信15092150280索要即可,不收任何费用,但是仅供参考,不建议大家获得资料后从事一些商业活动!) 软件编程人员入门的第一个程序一般都是在屏幕上打印hello,world!这就像是一个里程碑,标志着大家要进入软件的编程世界!而FPGA开发人员的第一个里程碑是实现流水灯,当把编译好的下载文件烧写到板卡,看着流水灯不停的闪烁,就是在欢迎您进入FPGA的编程世界! 流水灯看似简单,实则包含硬件运行的最基本的哲理。板卡上电后流水灯会一直闪烁,就如时间日复一日,年复一年,永不停止,如果用英语的一个词来描述,那就是always!对FPGA来说,就是要上电后一直运行下去,这就是所谓的FPGA之道!所以FPGA的工程,不管大小,都是由一个个always块组合而成的! 对于第一个FPGA工程,要求很简单,系统时钟是100MHz,低电平复位,假设有4个led灯(都是高电平点亮,低电平熄灭),按照1秒的频率进行流水闪烁(第1秒1号灯亮,2-4号灯灭;第2秒2号灯亮,1,3,4号灯灭;第3秒3号灯亮,1,2,4号灯灭;第4秒4号灯灭,1-3号灯灭,然后以此反复就可以达到流水灯的效果)。 要实现流水灯,首先要实现计数器。计数器是FPGA开发中用的最多的模块,就如乐高中的那个最基本的积木。工程的要求是按照1秒的频率进行流水闪烁,那就要实现1秒的计数器,每当计数到1秒4个灯就流水一次。本工程的系统时钟是100MHz,那计数一秒就是计数100_000_000次,而FPGA计数一般是从0开始,那计数99_999_999次就是一秒。 分析差不多了,那就说一下计数器的设计要点,首先是计数器的开始条件,本工程流水灯一直运行,那计数器的开始条件就是系统上电,就是上电稳定后计数器要一直运行的;其次是计数器的清零条件(停止条件),本工程有两个清零条件,一个是系统复位(就是复位信号拉低),一个是计数时间到1秒,就是计数器加到了99_999_999;还有就是计数器累加(减)条件,本工程只要有系统时钟,复位信号没有拉低,计数器就会一直累加下去。 还有一点要特别注意,就是当计数器的清零条件和累加条件同时满足的情况下,谁的优先级高?我们一般把清零当成特殊处理,累加当成正常处理。 不管是硬件编程还是软件编程编程,一般的共识是先处理特殊,再处理正常,所以清零的优先级比累加的优先级高! 其实可以具体分析一下,比如在计数到99_999_999后,下一个时钟周期清零和累加的条件同时满足,这时候如果清零,计数器就可以从0重新开始累加,功能正常;这时候如果累加,那计数值就会变成100_000_000,第二个清零条件计数到99_999_999就会失效,计数器就会一直累加下去,从而功能异常。 下面是计数器的FPGA编程,首先定义了系统时钟SYS_CLK是100_000_000,然后定义了一个32位的计数器one_second_cnt,用于1秒时间的计数;还定义了一位线网信号one_second_done,用于指示1秒时间到。 always块中主要实现了计数器one_second_cnt的清零和递增,if的优先级最高,else if次之,else最低。就是复位清零的优先级最高,计数到1秒清零的条件次之,递增的优先级最低。初学者常常把计数到1秒和递增的优先级弄反,结果计数器一直递增下去,造成功能错误! 根据多年的FPGA开发经验,给出如下的建议:不管是计数器模块还是其它的模块,建议按照优先级把所有的特殊处理列举完,剩下else就是正常的处理! 我们看一下计数器的modelsim仿真结果(由于计数99_999_999次仿真时间较长,仿真时改为了计数9次)。如下所示,在复位rst_n信号拉低的情况下,计数器是在初始的清零状态;当计数值one_second_cnt达到9后执行的是清理操作,在其它情况下都是进行的累计操作。 我们可以把计数值改成64,重新仿真,展开计数器one_second_cnt展开看一下,就会发现一个很有趣的现象,one_second_cnt[0]是时钟clk的2分频,one_second_cnt[1]是时钟clk的4分频,one_second_cnt[2]是时钟clk的8分频,one_second_cnt[3]是时钟clk的16分频,one_second_cnt[4]是时钟clk的32分频。。。。。。就是说如果clk是128MHz,one_second_cnt[0]是64MHz,one_second_cnt[1]是32MHz,one_second_cnt[2]就是16MHz,one_second_cnt[3]是8MHz。。。。。。这些分频的信号在后续的开发中会大发神威,我们后续再说,所以计数器是不是很神奇呀! 计数器设计完毕了,下面就该流水灯登场了。基本的工作都完成了,那流水灯实现就很简单了,流水灯的模块设计如下。流水灯就是要流水闪亮起来,在复位rst_n拉低的情况下给流水灯一个默认值,就是1号灯两,2-3号等灭,即流水灯的默认值是0001。每当计数时间到,即线网信号one_second_done拉高的时候,流水灯的值左移一位,高位移出来的值补到最低位,这样就形成了一个封闭的圆圈,最终实现了流水灯! 我们看一下流水灯的modelsim仿真图,在rst_n的时候leds是0001,然后one_second_done拉高后就是在0001->0010->0100->1000->0001这样循环流水。 目前的工程里面有两个always模块了,那这两个模块是怎么运行的?如下所示,计数器模块在上面,流水灯模块在下面,那是不是一般的顺序执行,先执行计数器,再执行流水灯?不是的,我们把这两个模块换一下位置,功能还是一样的。所以大家要记住:一个工程里面的所有always块是同时执行的,没有任何的先后顺序!就像小学常用一边一边来造句,比如我一边走路,一边唱歌,走路和唱歌是同时发生的,没有先后顺序! 我们也可以把上面两个always合成一个always,功能也是一样的,但是不建议这样写,因为多个信号交织在一起,加大了设计的难度,同时也增加了后续的维护成本。比如后续要修改一个信号变化的条件,按照下面的编程方式可能会引起另外一个信号的异常变化。
|