Verilog和FPGA的自学笔记7——流水灯与时序约束(XDC文件的编写)
先写点废话~~
刚接触FPGA时,惊叹于逻辑门的硬件处理速度之快,同时也好奇,FPGA是否能实现延时?
不久就得到了答案,因为在我看到的例程中存在流水灯的代码。
哎……顿时就来了兴趣:逻辑门咋延时的呢?这不是瞬间的事吗?
说实话吧,学完寄存器后我也还没想到答案(按说时钟也学了……)。直到实现计数器后,才算是明白咋回事了。
所以,上篇笔记写完,流水灯也就顺道了,没得难度哈,不写了吧……
延时逻辑
这部分主要是和系统时钟的对应。比如晶振频率50MHz,如果想计时一秒,那计数器就得数到50000000……?
很明确的告诉你,不是。
你想啊,数到50000000之后是什么?是0。所以两个50000000之间间隔了50000001个时钟周期(额数有点大,换个小的再解释下……)
比如需要计数1个周期,初始值为0:
clk | cnt |
---|---|
0 | 0 |
1 | 1 |
2 | 0 |
3 | 1 |
可以清楚看到,每个计数周期经历了两个时钟周期。
所以大家记住:计数值为 = 时钟周期数 - 1
移位?交换!
做嵌入式时,我们通常写个循化+移位来实现流水灯,但FPGA不行呀,每个语块都是同时执行的,循环不了啊~
虽然我们能检测到每次计数的终点,但计数终点是没有区别的,如果我们想继续用循环+移位的逻辑来实现,可能需要再引入一个状态计数器,略微有点小复杂……
试试这个~
led <= {led[0],led[3:1]};
我们每次都把最后一位搬到最前面,前面3位扔到后面。而这个语句是无需区分状态的,每个计数终点时都可以执行这个。(我第一次看到这个,顿时有种醍醐灌顶的感觉……)
一点心得体会……
不管是流水灯还是后面的呼吸灯,我发现这类问题有个共同的特征,就是过程。而据目前所学,我觉得FPGA最不擅长处理过程(毕竟这是通用处理器的特长)。所以如果我们必须要处理一段过程,最好是能够找到过程中每个状态之间的关系。
比如对于流水灯,前后状态关系就是 led <= {led[0],led[3:1]} 。而对于呼吸灯,就是脉冲占比的逐步递增。
而如果我们完全找不到每个状态之间的关系,且一段过程又拥有数千万个状态,那这时可能需要换个方式了……
代码就没啥了哈,RTL给大家贴下面了:
module flow_led(input sys_clk , //系统时钟input sys_rst_n, //系统复位,低电平有效output reg [3:0] led //4位LED灯
);reg [24:0] cnt ; //计数器//计数器计时0.5s
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)cnt <= 25'd0;else if(cnt < (25'd2500_0000 - 25'd1))cnt <= cnt + 25'd1;elsecnt <= 25'd0;
end//对LED灯进行移位控制,以输出4位LED的状态
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)led <= 4'b0001;else if(cnt == (25'd2500_0000 - 25'd1))led <= {led[0],led[3:1]};elseled <= led;
endendmodule
不过对于仿真来说,计数25000000次稍微有点多。所以为方便观察,我们可以先把后面的一堆0删了(记得下载验证时再改回来!)。
仿真如下:
把led的十进制表示换成二进制的话,可以看到完美的流水灯设计~
好了啊,以上无非就是计数器的实际应用,不是本文重点,接下来才是正菜。
布局布线精华:时序约束
当我还没入门FPGA时,就久闻时序约束大名。今天算是正式见面了~
何谓时序约束?
答曰:盖信号于 FPGA 内部传递,时延各异。欲保其传输安稳,必使 EDA 工具循用户之求以布局布线。而吾辈所提诸般苛责之求,即时序约束也。
话说当我看到这个回答时真的能笑死……
对于真正的时序约束,核心概念是很多的,今天就不一口气介绍完了(因为我一口气学完后啥也没记住),用到哪学到哪。
Vivado里自带了时序约束工具,很方便。鉴于这方面教程很多,本文就不啰嗦了。咱们主要聊聊关于XDC文件的手动编写。
对于本文工程流水灯,其核心约束无非就俩:一个时钟约束,一个管脚约束。
新建一个.xdc文件,我们先写时钟约束。
1.时钟约束
语法格式如下:
create_clock -period 时钟周期 -name 时钟名称 -waveform {上升沿位置 下降沿位置} [get_ports 连接引脚]
我知道大家一头雾水啊,没关系~
- 时钟周期:每个重复周期的时长,比如工具默认的20ns,就是说每20纳秒会输出一个相同时钟。
- 时钟名称:这个不用解释了吧应该……最好把名字起的便于他人和自己理解~
- 上升沿位置:时钟上升沿在周期内的位置,比如0ns,就是说每个周期到来就立马输出高电平。
- 下降沿位置:参考上升沿的解释!
一般的教程到这里就没了,甚至有的教程连这仨数的含义都不给解释(愤怒)。
哎,我作为一个初学者,也是半天才明白咋回事。知道这里复杂,给大家几个例子辅助理解:
- create_clock -period 20.000 -name clk -waveform {0.000 10.000} [get_ports sys_clk]
- create_clock -period 30.000 -name clk -waveform {10.000 20.000} [get_ports sys_clk]
还有关于小数的问题,Vivado是支持处理很多小数的,比如:0.123456789。不过能写是能写,但你FPGA的芯片可不见得支持如此高精度的约束。所以没必要,别给自己找麻烦~
如果你用的是系统默认时钟约束,也可以不写waveform参数,默认为高电平在0ns发生,在周期/2的位置结束。
2.管脚约束
语法格式如下:
set_property PACKAGE_PIN IO引脚 [get_ports 对应引脚名称]
比如我们要把sys_clk绑定到L15,就可以写:
set_property PACKAGE_PIN L15 [get_ports sys_clk]
常用的还有电平标准的设置,语法如下:
set_property IOSTANDARD 电平标准 [get_ports 对应引脚名称]
比如把led引脚设置为3.3V的标准,就可以写:
set_property IOSTANDARD LVCMOS33 [get_ports led]
相比时钟约束,管脚约束还是简单很多的(因为没太多参数)。
对于我们目前的水平,这些也够用了,最近几个小demo总是来回掰扯这些玩意……
既然Vivado能自动生成XDC文件,为啥我们还要学习编写?
我们目前来说确实不必,但XDC文件的编写却是FPGA工程师和芯片工程师必备技能,因为对于ASIC中非常复杂的时序逻辑,现在的软件还是完全做不到自动生成的。如果你仅仅想了解FPGA并浅尝辄止,能知道XDC里面是什么,以及大体什么意思就够了;但如果你立志于成为一名芯片工程师或者研究员,那请关掉你的EDA工具,跟我一起写下一行行公正的约束代码吧~
这里也写一句题外话,我开始并没想过介绍Vivado软件的使用,不知大家是否需要关于软件的使用教程。所以,请在评论区留下你宝贵的建议!!
如果有不明白或错误之处,也希望大家在评论区给出,帮助大家的同时也能再次提升自己对于FPGA和Verilog的理解,感谢大家!!
系列链接:
上一篇:Verilog和FPGA的自学笔记6——计数器(D触发器同步+异步方案)
下一篇:码字ing……