【FPGA学习】- Verilog教程进阶
良好的代码风格
变量命名
在对变量命名时,要求尽可能使用有意义的名字,方便后续阅读和维护。“好的命名即是注释”。常见的变量命名方式主要有三种。
驼峰命名法:该命名方法使用大小写混合的方式来区别各个单词,一般每个单词首字母大写,其余字母小写,并且单词之间不使用任何符号隔开。例如:ChangeTime,SelectNumber,QuickSort。
蛇形命名法:该命名方法使用下划线“_”区别各个单词,单词不区分大小写,使用一种格式。例如:change_time,select_number,QUICK_SORT。
串行命名法:该命名方法使用连接符“-”区别各个单词,单词不区分大小写,使用一种格式。例如:change-time,select-number,QUICK-SORT。
诸多教程推荐使用蛇形命名法,原因是在单词较多的情况下,蛇形命名法更加清晰易读,使用也更加方便。
代码注释
原则上,每一个设计模块的开头都应当包含文件说明信息,诸如版权、模块名字、作者、日期等信息。在Vivado中,文件创建成功时,会自动生成相关的内容。
////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2023/03/15 00:00:00 // Design Name: // Module Name: what // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // //////////////////////////////////////////////////////////////////////////////////
注释一般应当以最简短的话语描述当前代码/代码段的功能,为防止出现编码格式的问题,书写注释时,应当使用英语。较短的注释一般在代码之后书写,较长的代码注释应当提前一行书写。此外注释不仅可以使用文字描述功能,也可以画出相应的时序图或者电路结构图,这样更方便后续阅读代码。
//generate new signal reg clk_1hz; //new signal
// _________ ___
//_____________| |_______|
// ___ ____ ____
//____/ \___/ \___________/ \__
逻辑优化
使用圆括号以确定程序的优先级和逻辑结构,避免优先级的问题产生错误。
多判断情况下,尽可能使用case语句替代if语句。case语句在判断情况较多时,综合后消耗的资源更少,时序也更加好。
状态机编写,尽量使用三段式。
多个功能,尽可能采用实例化的方式进行调用,不仅有利于团队设计,也有利于更新维护。
方便阅读
端口信号要对齐。单行内容过多,换行书写。模块例化时,端口信号尽量与连接信号隔开,并各自对齐。信号为向量时,指明其位宽,方便阅读和调试。
module testmodule(clk, rst_n, out); input clk ; input rst_n ; output [1:0] out ; ila ila_inst( .clk (clk) , .prob0 (rst_n) , .prob1 (out[1:0]) ); endmodule
严格的代码规范
变量声明时不应赋初值。信号赋初值会影响电路的逻辑功能,从而影响验证。
always语句
不可以在一个always语句块中,同时将一个信号的上升沿和下降沿作为触发信号。综合后电路的结果可能不会如预期。
always @(posedge clk, negedge clk) //这是不被允许的
一个变量只能在一个always块中被赋值,因为综合后的该变量寄存器会有多个激励信号,此时会影响电路的状态。
//此设计不被允许 always @(posedge clk) x = x + 1; always @(posedge clk) x = x +2;
一个 always 块中不要存在多个并行或不相关的条件语句,使用多个 always 分别描述。该结构仿真结果不准确。功能结构划分不清晰。
同步与异步
尽量采用同步设计。
综合
变量如果需要乘,除,取余运算时,应尽可能使用IP模块或集成模块,但 parameter 类型的常量就可以使用此类操作符,编译之前完成运算,不会消耗过多的硬件资源。
if语句结构要完整。
case语句结构要完整。
敏感信号要罗列完整。
避免使用不可综合的语句结构。
门级建模
多输入门
多输入门,指的是只有一个输出端口,有一个或多个输入端口的门级电路。常见的多输入门有:and(与门) nand(与非门) or(或门) nor(或非门) xor(异或门) xnor(同或门),在Verilog中,可以使用模块例化的方式来完成。例化时,第一个端口是输出,后边的端口是输入。在多输入门中,输出不可能是高阻态(Z)。
and a1(out, in1, in2); or a2(out, in1, in2); xor a3(out, in1, in2, in3);
多输出门
多输出门,指的是只有一个输入端口,有一个或多个输出端口的门级电路。常见的多输出门有:not(非门),buf(缓冲器)。和多输入门类似,可以使用模块例化的方式对多输出门进行调用。左边端口是输出,右边端口是输入。在多输出门中,输出不可能是高阻态(Z)
buf(out1, out2, out3, in); not(out, in)
三态门
Verilog 中还提供了 4 个带有控制端的 buffer 门单元,称为三态门。当使能信号有效时,按照缓冲器或者非门的正常功能输出;使能信号无效时,输出为Z。实例化格式为:(out, in, ctrl)。
高电平使能三态缓冲器(bufif1);低电平使能三态缓冲器(bufif0);高电平使能三态非门(notif1);低电平使能三态非门(notif1)。
bufif1 buf1 (OUTX, IN1, CTRL1) ; bufif0 buf2 (OUTY, IN1, CTRL2) ; notif1 buf3 (OUTZ, IN1, CTRL3) ; notif0 (OUTX1, IN1, CTRL4) ;
上下拉电阻
上拉是将不确定的信号通过一个电阻钳制在一个高电平。下拉是将不确定的信号通过一个电阻与地相连,固定在低电平。上拉或下拉电阻,具有限流、提高驱动能力、防静电等作用,可以有效保护电路。此类门电路单元只有输出,没有输入。
pullup p1(in); pulldown (out);
门延迟
实际电路中,门电路之间是有延迟。有三种延迟,上升延迟,下降延迟和关断延迟。其中上升延迟是指信号从"非1"变成"1"所需要的时间(t1);下降延迟是指信号从"非0"变成"0"所需要的时间(t2);关断延迟是指信号从"非Z"变成"Z"所需要的时间(t3)。信号从"非X"变成"X"所需要的时间 = min{ t1, t2, t3 }。

//指定一个延迟,所有的延迟均为该值 and #(1) (OUT1, IN1, IN2) ; //指定两个延迟,上升2.1,下降2,其余延迟为为二者最小值 or #(2.1, 2) (OUT2, IN1, IN2) ; //指定三个延迟,上升2,下降1,关断1.3,其余延迟为三者最小值 bufif0 #(2, 1, 1.3) (OUT3, IN1, CTRL) ;
#(最小 : 典型 : 最大)。最小延迟:门单元所具有的最小延迟;典型延迟:门单元所具有的典型延迟;最大延迟:门单元所具有的最大延迟。
用户自定义原语(UDP)
原语(Primitive),执行期间的不可分割的最小单位。Verilog中常用的内置原语有:and,or等。在Verilog中,支持用户自定义原语,即User Defined Primitive(UDP),UDP内部不可以调用module或者其它Primitive。UDP的格式如下,UDP为多输入门,并且每个端口的宽度只能为1bit。UDP 状态表是 UDP 中最重要的部分,用关键字 table 声明,它定义了如何根据输入状态和当前状态得到输出值,类似于逻辑真值表,状态表不能够处理Z值,会将Z值当作X的进行处理。UDP可以定义在文件的任何位置。
primitive name_UDP(out, in1,in2,in3, ... ); input in1, in2, in3, ...; output out; reg ...; table ... endtable endprimitive
组合逻辑UDP
组合逻辑的状态表项如下。需要注意的是,状态表中输入端口的顺序应当与UDP输入端口的顺序保持一致;对于不确定的输出,组合逻辑UDP会直接输出X值,所以在编写状态表时应当考虑所有的情况。
<input1> <input2> <input3> ... <inputN> : <output>;
//一个and门 primitive and_udp(out, a, b); input a; input b; output out; table
// a b : out; 0 0 : 0; 0 1 : 0; 1 0 : 0; 1 1 : 1;
//多考虑的情况
// 0 x : 0;
// x 0 : 0; endtable endprimitive
时序逻辑UDP
时序逻辑的状态表项如下。状态表的输入可以是电平,也可以是跳边沿的形式。输出必须声明为reg型变量。状态表中跳变沿的表示方式为:上升沿 - 01,下降沿 - 10。
<input1> <input2> <input3> ... <inputN> : <current_state> : <next_state>;
//D锁存器。清零端有效,输出为0;清零端无效,使能端无效,输出不变 //清零端无效,使能端有效,输入什么,输出什么 primitive d_latch(q, clear, en, d); output reg q ; input d, en, clear ; initial q = 0 ; table //clear en d :q :q_next ; 1 ? ? :? :0 ; //清零 0 0 ? :? :- ; //"-" 保持不变 0 1 0 :? :0 ; //输出等于输入 0 1 1 :? :1 ; endtable endprimitive
参考资料
[1] Verilog教程进阶篇,菜鸟教程,https://www.runoob.com/w3cnote/verilog2-tutorial.html。
[2] 数字电路实验教程,Vlab,https://vlab.ustc.edu.cn/guide/doc_verilog.html
[3] 黄海,于斌,Verilog HDL 设计实用教程[M],北京:清华大学出版社,2021.

浙公网安备 33010602011771号