1. 综合与仿真
1.1 综合
Verilog 是用代码的形式描述硬件的功能,最终在硬件电路上实现该功能。在 Verilog 描述出硬件功能后需要使用综合器对 Verilog 代码进行解释并将代码转化成实际的电路来表示,最终产生实际的电路,也被称为网表。这种将 Verilog 代码转成网表的工具就是综合器。
上图左上角是一段 Verilog 代码,该代码实现了一个加法器的功能。在经过综合器解释后该代码被转化成一个加法器电路。QUARTUS、ISE 和 VIVADO 等 FPGA 开发工具都是综合器,而在集成电路设计领域常用的综合器是 DC。
1.2 仿真
在 FPGA 设计的过程中,不可避免会出现各种 BUG。如果在编写好代码、综合成电路、烧写到FPGA 后才发现问题,此时再去定位问题就会非常地困难。而在综合前,设计师可以在电脑里通过仿真软件对代码进行仿真测试,检测出 BUG 并将其解决,最后再将程序烧写进 FPGA。一般情况下可以认为没有经过仿真验证的代码,一定是存在 BUG 的。
为了模拟真实的情况,需要编写测试文件。该文件也是用 Verilog 编写的,其描述了仿真对象的输入激励情况。该激励力求模仿最真实的情况,产生最接近的激励信号,将该信号的波形输入给仿真对象,查看仿真对象的输出是否与预期一致。需要注意的是:在仿真过程中没有将代码转成电路,仿真器只是对代码进行仿真验证。至于该代码是否可转成电路,仿真器并不关心。
由此可见,Verilog 的代码不仅可以描述电路,还可以用于测试。事实上,Verilog 定义的语法非常之多,但绝大部分都是为了仿真测试来使用的,只有少部分才是用于电路设计。Verilog 中用于设计的语法是学习的重点,掌握好设计的语法并熟练应用于各种复杂的项目是技能的核心。而其他测试用的语法,在需要时查找和参考就已经足够了。
可综合设计
Verilog 硬件描述语言有类似高级语言的完整语法结构和系统,这些语法结构的应用给设计描述带来很多方便。但是,Verilog 是描述硬件电路的,其建立在硬件电路的基础之上。而有些语法结构只是以仿真测试为目的,是不能与实际硬件电路对应起来的。也就是说在使用这些语法时,将一个语言描述的程序映射成实际硬件电路中的结构是不能实现的,也称为不可综合语法。
综合就是把编写的 rtl 代码转换成对应的实际电路。比如编写代码 assign a=b&c;EDA 综合工具就会去元件库里调用一个二输入与门,将输入端分别接上 b 和 c,输出端接上 a。
同样地,如果设计师编写了一些如下所示的语句:
assign a=b&c; assign c=e|f; assign e=x^y;
综合工具就会像搭积木一样把这些“逻辑”电路用一些“门”电路来搭起来。当然,工具会对必要的地方做一些优化,比如编写一个电路 assign a=b&~b,工具就会将 a 恒接为 0,而不会去调用一个与门来搭这个电路。
综述所述,“综合”要做的事情有:编译 rtl 代码,从库里选择用到的门器件,把这些器件按照“逻辑”搭建成“门”电路。
不可综合,是指找不到对应的“门”器件来实现相应的代码。比如“#100”之类的延时功能, 简单的门器件是无法实现延时 100 个单元的,还有打印语句等,也是门器件无法实现的。在设计的时候要确保所写的代码是可以综合的,这就依赖于设计者的能力,知道什么是可综合的代码,什么是不可综合的代码。对于初学者来说,最好是先记住规则,遵守规则,先按规则来设计电路并在这一过程中逐渐理解,这是最好的学习路径。
下面表格中列出了不可综合或者不推荐使用的代码。
代码 | 要求 |
initial | 严禁在设计中使用,只能在测试文件中使用。 |
task/function | 不推荐在设计中使用,在测试文件中可用。 |
for | 在设计中、测试文件中均可以使用。但在设计中多数会将其用错,所以建议在初期设计时不使用,熟练后按规范使用 |
while/repeat/forever | 严禁在设计中使用,只能在测试文件中使用 |
integer | 不推荐在设计中使用 |
三态门 | 内部模块不能有三态接口,三态门只有顶层文件才使用。三态门目的是为了节省管脚,FPGA 内部完全没有必要使用。 |
casex/casez | 设计代码内部不能有 X 态和 Z 态,因此 casez、casex 设计时不使用。 |
force/wait/fork | 严禁在设计中使用,只能在测试文件中使用 |
#n | 严禁在设计中使用,只能在测试文件中使用。 |
下表为推荐使用的设计代码。
代码 | 备注 |
reg/wire | 设计中所有的信号类型定义,只有 reg 和 wire 两种 |
parameter | 设计代码中所有的位宽、长度、状态机命名等,建议都用参数表示,阅读方便并且修改容易。 |
assign/always | 程序块主要部分,至简设计法对 always 使用有严格规范。如下所示 |
if else 和 case | always 里面的语句,使用 if else 和 case 两种方法用来作选择判断,可以完成全部设计 |
算术运算符(+,-,×,/,%) | 可以直接综合出相对应的电路。但除法和求余运算的电路面积一般比较大,不建议直接使用除法和求余。 |
关系运算符(==,!=,>,<,>=,<=,) | 后文介绍 |
逻辑运算符(&&,,!) | 后文介绍 |
位运算符(~,^,&) | 后文介绍 |
移 位 运 算 符 ( <<,>>) | 后文介绍 |
程序块主要部分,至简设计法对 always 使用有严格规范。组合逻辑格式为: |
always@(*) begin 代码语句; end 或者用 assign
时序逻辑格式为:
always@(posedge clk or negedgerst_n) begin if(rst_n==1’b0)begin 代码语句; end else begin 代码语句; end end
时序逻辑中,敏感列表一定是 clk 的上升沿和复位的下降沿、最开始必须判断复位。详细见本章组合逻辑和时序逻辑一节。
2. 模块结构
2.1 模块介绍
模块(module)是 Verilog 的基本描述单位,是用于描述某个设计的功能或结构及与其他模块通信的外部端口。
模块在概念上可等同一个器件,就如调用通用器件(与门、三态门等)或通用宏单元(计数器、ALU、CPU)等。因此,一个模块可在另一个模块中调用,一个电路设计可由多个模块组合而成。一个模块的设计只是一个系统设计中的某个层次设计,模块设计可采用多种建模方式。
Verilog 的基本设计单元是“模块”。采用模块化的设计使系统看起来更有条理也便于仿真和测试, 因此整个项目的设计思想就是模块套模块,自顶向下依次展开。在一个工程的设计里,每个模块实现特定的功能,模块间可进行层次的嵌套。对大型的数字电路进行设计时,可以将其分割成大小不一的小模块,每个小模块实现特定的功能,最后通过由顶层模块调用子模块的方式来实现整体功能,这就是 Top-Down 的设计思想。本书主要以 Verilog 硬件描述语言为主,模块是Verilog 的基本描述单位, 用于描述每个设计的功能和结构,以及其他模块通信的外部接口。