接触FPGA已经差不多两年了,在学习的过程中总能偶尔听到FPGA需要时序约束。
那个时候就是什么都不懂,不懂为什么要约束,以及约束的是什么东西,以及代码的风格对约束有什么影响。
直到在后来的一次调试中踩了雷,才明白时序约束的重要性,如下verilog代码。
/********************************************************************************************/
reg [31:0]get_num;
reg [7:0]a;
reg [7:0]b;
reg [7:0]c;
reg [7:0]d;
reg[7:0]e;
reg[7:0]f;
always@(posedge clk)
begin
get_num<=a*b*c*d/e/f;
end
/**************************************************************************************/
在调试的过程中就发现当clk的频率高于某一个值,get_num的值就会出现错误,然后我下意识的想到这种写法不符合时序约束规则。
我们来分析一下,get_num每得到一次数值就会经过三次乘法两次除法。而乘法与除法虽然是组合逻辑(也可以用多周期的时序逻辑实现)。
但是组合逻辑在硬件上也是有延迟的。假设一次乘法需要xns,一次除法需要yns,则组合逻辑的总延迟就是(3x+2y)ns。
然而FPGA的always@(posedge clk)会综合成D触发器,来一个上升沿,D触发器就会更新一次数据。
相邻两个上升沿的时间差就是clk的周期,D触发器上升沿前需要有建立时间,假设为ans,D触发器上升沿后需要有保持时间,假设为bns
(注意:综合和编译是有本质意义上的区别,综合后的结果是一堆数字门电路。编译后的结果是一堆2进制代码)
那么(3x+2y+a+b)的时间应该要小于时钟的周期,这样的时序才是收敛的,不然的话,时序就会发散。
时序发散对于FPGA来说是致命的打击。
那如果想优化上面的代码(应该叫硬件描述语言),应该怎么办呢?
我们可以用流水线法做优化。
/**************************************一级流水线**********************************************/
/******/
reg[15:0]buff1;
always@(posedge clk)
begin
buff1<=a*b;
end
/******/
reg[15:0]buff2;
always@(posedge clk)
begin
buff2<=c*d;
end
/******/
reg[15:0]buff3;
always@(posedge clk)
begin
buff3<=e*f;
end
/*******************************************二级流水线*********************************************/
reg [31:0]buff4;
always@(posedge clk)
begin
buff4<=buff1*buff2;
end
/**********/
reg [15:0]buff5;
always@(posedge clk)
begin
buff5<=buff3;
end
/*************************************************三级流水线*******************************************************/
reg [31:0]get_num;
always@(posedge clk)
begin
get_num<=buff4/buff5;
end
/********************************************************************************************************************************************************************/
优化后就变成了三级流水线实现,这种实现方式比第一种方式可接受的时钟频率要高很多。例如第一种只能跑10mhz,第二种流水线优化后的就能跑远远大于10mhz的时钟频率。
/*************************************************************************************************************************************************************************************************************/
下面再来举一个例子,外部信号上升沿触发数据传输。(ps:这要是在单片机中就要用到外部中断啦,我也是一个单片机爱好者呢,stm32,msp430,stc89c51,当然还有dsp的c2000,c5000,c6000,等,还有可以跑linux的nuc977,imx6ull等等,qt也是我的最爱。不对,我扯远了,我这个主题是FPGA)
/******************************************/
reg signal_test;
reg main_clk;
reg [7:0]adc_num;
reg [7:0]out_num;
always@(posedge signal_test)
begin
out_num<=adc_num;
end
//上面这个写法用到了门控时钟这个概念,这种写法相对于全局的工程来说是异步的,不利于时序约束。
/***************************************************************************************************************************************************************************************************************/
reg signal_test;
reg main_clk;
reg [7:0]adc_num;
reg [7:0]out_num;
reg buff_signal_testa=0;
reg buff_signal_testb=0;
/*********************************************************/
wire pos_get;
wire neg_get;
always@(posedge clk)
begin
buff_signal_testa<=signal_test;
buff_signal_testb<=buff_signal_testa;
end
assign pos_get=(buff_signal_testb==0)&&(buff_signal_testa==1);
assign neg_get=(buff_signal_testb==1)&&(buff_signal_testa==0);
/*******************************************************/
always@(posedge main_clk)
begin
if(pos_get)begin
out_num<=adc_num;
end
end
//上面的写法就比较符合时序约束
/*******************************************************************************************************************************************************************************************************/