前言
本文中用到了如下的小标题:
- “心中有路”与综合推断
- “心中无路”与无从推断
这里所谓的路就是电路的意思,意思是逻辑工程师使用Verilog设计电路时要注重硬件思维,而不是软件编程。
心中有电路,在你使用RTL语言设计电路的时候,才能设计出综合工具能够推断出的具体硬件电路与之对应,否则可能语法过了,但是综合工具无法推断你的设计。
逻辑工程师在设计代码的时候要做到心中有电路,每一行代码的设计都是有对应硬件电路的。心中有电路,就是在编写Verilog程序的时候能明白我的设计会在FPGA中对应什么样的电路,使用的是什么样的资源,提前预知综合工具的想法,不一定全面,但是要有个大概,不要什么都交给综合工具去推断,这才是优秀的工程师应有的素质。
“心中有路”与综合推断
FPGA设计中,逻辑工程师对于逻辑资源的使用,有如下几种方式:Instantiation,Inference,IP Catalog和Macro Support;其含义分别为:
Instantiation即例化,例化一些Xilinx的原语资源,例如时钟BUFF资源:
如上差分转差分的IBUFGDS原语,Xilinx推荐的使用方式就是通过直接例化的方式。
Inference即推断,这种方式无需你自己例化原语,综合工具会根据你的设计自动推断使用那些逻辑资源或者原语等,如:
上图中的具有时钟使能以及异步复位的D触发器原语,Xilinx推荐的设计方式就是通过工具推断的方式。
IP Catalog,即IP核的使用,这种方式是FPGA设计工程师最常用的方式,简单方便且效率高,不像原语那样,参数很多且不得不在原语模板中单独配置,IP核的定制只需在GUI界面里选择自己的参数即可。如果要使用无法推断的任何FPGA原语的大块,则应使用此方法。
例如,Xilinx的吉比特串行收发器资源:
对于吉比特收发器资源的使用,Xilinx推荐是用IP核定制例化的方式。
Macro Support即支持宏的方式,某些逻辑资源具有可以使用的UniMacro,这样的资源位于Xilinx的UniMacro库中,用于例化过于复杂而无法仅使用原语进行实例化的原语。综合工具见自动将UniMacros扩展为其底层原语。
这种方式过于复杂,一般不建议手动操作。
具体参考UG768 (v14.7);
注:原语是Xilinx FPGA中一种硬件资源,无须综合。在实现的过程中与综合之后的资源一起参与映射,布局布线等过程。
下面参考文献FPGA之道,给出原语的说法:
原语,英文名称primitive,是FPGA软件集成开发环境所提供的一系列底层逻辑功能单元。由于是底层逻辑功能单元,所以它们往往跟目标FPGA芯片以及芯片厂商紧密相关,因此不同厂商、不同器件的原语往往不能通用。当编译器对我们的HDL代码进行编译时,其中间环节的一些输出往往就是由原语组成的逻辑网表。因此,原语往往是不参与综合过程的,而使用原语描述的逻辑往往也不会被综合工具所优化。例如,Xilinx公司的ISE软件集成开发环境中的unisims库中定义了所有用于综合的原语,而simprims库中则定义了所有用于实现的原语。需要注意的是,如果我们去ISE安装目录下的verilog\src\unisims或verilog\src\simprims文件夹下去看这些原语的代码,可以发现其实这些并不是真正的原语,而是在原语的基础上又封装了一层,不过人们常常将它们也泛称为原语。
总体而言,除了Inference不需要手动操作,其他都需要逻辑工程师自己动手。但并不是说Inference就很是一种傻瓜式的方式,非也!反而,对于优秀的逻辑工程师要求更高。逻辑工程师在设计代码的时候要做到心中有电路,每一行代码的设计都是有对应硬件电路的。心中有电路,就是在编写Verilog程序的时候能明白我的设计会在FPGA中对应什么样的电路,使用的是什么样的资源,提前预知综合工具的想法,不一定全面,但是要有个大概,不要什么都交给综合工具去推断,这才是优秀的工程师应有的素质。
“心中无路”与无从推断
下面就讲两个小例子,关于设计师“心中无路”导致综合工具无从推断的示例:
你的设计在FPGA中无此资源对应
如下一个例子是一个设计三分频电路的方面教材,其思路是想在参考时钟的1.5个周期处对分频时钟进行翻转来实现三分频:
`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2020/12/06 12:32:01
// Module Name: bad_demo
// Additional Comments:
// 这是一个方面素材,测试使用推断的方式来实现三分频电路,重点测试always块内双时钟的例子
//
module bad_demo(
input clk,
input rst_n,
output reg clk_divide
);
wire clk_reverse;
assign clk_reverse = ~clk;
reg [3:0] count;
always @ (posedge clk or posedge clk_reverse or negedge rst_n) begin
if(!rst_n) begin
count <= 0;
end
else if(count < 2) begin
count <= count + 1;
end
else begin
count <= 0;
end
end
always @ (posedge clk or posedge clk_reverse or negedge rst_n) begin
if(!rst_n) begin
clk_divide <= 0;
end
else if(count == 2) begin
clk_divide <= ~clk_divide;
end
else begin
clk_divide <= clk_divide;
end
end
endmodule
不得不说,理想很丰满,乍一看,还有模有样,但是没有联系实际的设计是要不得的。且看下面综合工具的报错:
综合工具报错:
[Synth 8-91] ambiguous clock in event control [“F:/Prj_blog/project_synthesis_test/project_synthesis_test.srcs/sources_1/new/bad_demo.v”:46]
综合工具说,在一个过程控制事件中,你给了一个模棱不清的时钟!
为什么会这么说呢?
那是因为综合工具认为,你的设计中使用了触发器资源,可以有!但问题是你又为何使用两个时钟的边沿来驱动同一个触发器或者寄存器呢?FPGA中的触发器资源(或者说综合库里)只有一个时钟输入引脚呀,如下:
于是综合工具不知道你要用哪一个时钟,不好意思,推断不出来,你回去修炼修炼下再做设计吧。
如上提出的写法还只是一种,很多变种也大都是同样的问题,综合工具会报同样的错误,表明这是同一类问题:
如:
always@(posedge clk or negedge clk)
always块内使用同一个时钟的不同边沿。
如:
assign clk_reverse = ~ clk;
always@(posedge clk or posedge clk_reverse)
这是上面的例子;
甚至这种也是不对的:
always@(posedge clk or negedge rst_n) begin
a <= 1'b0;
end
这种相当于给时钟换了一个名字叫rst_n。
因为你没有用rst_n作为复位或者使能或者置位信号,Verilog会认为你的rst_n也是一个时钟,这又变成和上面两种情况一样的问题了。
官网的相关链接给出了类似解释:
You have to always remember that you are using Register Transfer Language (RTL) to have the synthesis infer hardware.
Verilog HDL can do pretty much anything - the syntax of the always @() is very flexible. However, when you go to synthesize the design, the code has to map to an available piece of hardware on the FPGA.
On the FPGA we have combinatorial logic (LUTs, MUXes, etc…). These are inferred a variety of ways, including the always @() construct.
In addition we have flip-flops. A flip-flop on an FPGA (and pretty much anywhere else) is a device that has one clock and is sensitive to only one edge of that clock. So, the synthesis tool can only map to this device when the sensitivity list is always @(posedge clk). We have variants of the flip-flop that can do asynchronous preset/clear, so will map to always @(posedge clk or <posedge/negedge> rst), but that’s it.
There is no real hardware device that can do the equivalent of what you are describing - always @(posedge clk or negedge clk).
The only exception (sort of) are the IDDR and ODDR, and these need to be instantiated - they cannot be inferred from an HDL description.
唯一的例外(种类)是IDDR和ODDR,这些需要实例化 - 它们不能从HDL描述中推断出来。
So, no, this is not synthesizable Verilog.
因此,这不是可以综合的Verilog。*
多个输入直接驱动一个输出导致多驱动问题
有些小机灵鬼可能会想,既然我在一个always块内会被认为没有对应的触发器资源,但是如果我将两个时钟分开到两个always块内,主动告诉综合工具,我需要分开使用,但是处理的是同一个数据,在本例中处理的计数数据,总该可以了吧!
例如:
`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2020/12/06 12:32:01
// Module Name: bad_demo
// Additional Comments:
// 这是一个方面素材,测试使用推断的方式来实现三分频电路,重点测试always块内双时钟的例子
//
module bad_demo(
input clk,
input rst_n,
output reg clk_divide
);
wire clk_reverse;
assign clk_reverse = ~clk;
reg [3:0] count;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
count <= 0;
end
else if(count < 2) begin
count <= count + 1;
end
else begin
count <= 0;
end
end
always@(posedge clk_reverse or negedge rst_n) begin
if(!rst_n) begin
count <= 0;
end
else if(count < 2) begin
count <= count + 1;
end
else begin
count <= 0;
end
end
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_divide <= 0;
end
else if(count == 2) begin
clk_divide <= ~clk_divide;
end
else begin
clk_divide <= clk_divide;
end
end
always @(posedge clk_reverse or negedge rst_n) begin
if(!rst_n) begin
clk_divide <= 0;
end
else if(count == 2) begin
clk_divide <= ~clk_divide;
end
else begin
clk_divide <= clk_divide;
end
end
endmodule
首先验证功能是否正常,简单给出功能仿真激励:
module bad_demo_tb(
);
reg clk;
reg rst_n;
wire clk_divide;
initial begin
clk = 0;
forever begin
#6 clk = ~clk;
end
end
initial begin
rst_n = 0;
#10
rst_n = 1;
end
bad_demo u_bad_demo(
.clk ( clk ),
.rst_n ( rst_n ),
.clk_divide ( clk_divide )
);
endmodule
仿真波形:
其次验证综合是否能够通过:
实际证明,通过了,调出综合后的电路图看看:
蓝色线条标出的是clk的时钟线路,红色线条标出的是clk_reverse的时钟线路,可见,两个时钟分别对应到了不同的逻辑资源上。
综合后的原理图也许不太好看,RTL原理图一般和综合后的原理图没有什么区别(综合能通过的情况下),只是使用的逻辑资源更让人容易理解:
如下图所示:
该图是上述Verilog描述的RTL原理图,一般RTL原理图对应着综合后的原理图,但更便于理解,因此仅参考这里也是可以的。
我们使用红色线条代替相位180°的时钟线,蓝色线条代替相位为0°的参考时钟,可以看出,触发器确实使用了不同的时钟线来处理。
但是不得不说的是还是不太好,综合工具会报如下严重警告:
综合工具报出多驱动问题,原因是工具认为,多个输入直接驱动一个输出信号。试问?一个输出直接来自于多个输入,这多个输入又没有分时输出,哪输出到底会以哪个输入为准呢?
尽管功能仿真是没问题的,这种方式也是不好的。
我们应该考虑转换设计思路了。
针对该例的正确的实现方式
先给出仿真结果,同时也是设计原理:
设计实现为:
module Freq_divide(
input clk,
input rst_n,
output clk_divide
);
//先写一个占空比为1/3的分频时钟
reg clk_1_3;
reg [2:0] count;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
count <= 0;
clk_1_3 <= 0;
end
else if(count == 1) begin
clk_1_3 <= ~clk_1_3;
count <= count + 1;
end
else if(count == 2) begin
count <= 0;
clk_1_3 <= ~clk_1_3;
end
else begin
count <= count + 1;
clk_1_3 <= clk_1_3;
end
end
reg clk_1_3_r;
//下降沿采样上述时钟
always@(negedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_1_3_r <= 0;
end
else begin
clk_1_3_r <= clk_1_3;
end
end
//产生分频时钟
assign clk_divide = clk_1_3 | clk_1_3_r;
endmodule
这种实现方式就不存在任何问题,推荐大家使用。
最后想说的是,例子并不是最终的目的,目的是设计的理念以及作为逻辑设计师的目标,一定做到心中有路,方可下手!
参考文献
Xilinx 7 Series FPGA and Zynq-7000 All Programmable SoC Libraries Guide for HDL Designs
always@()的敏感源中为什么不能双边沿触发?为什么不能双时钟触发?
ambiguous-clock-in-event-control
[synth 8-91] ambiguous clk in event control
【Verilog HDL 训练】第 11 天(分频电路)