下冰雹 · 3月21日

一周掌握 FPGA Verilog HDL语法 day 1

今天给大侠带来的是一周掌握 FPGA Verilog HDL 语法,今天开启第一天,下面咱们废话就不多说了,一起来看看吧。

在学习中,学习任何东西都有一个过程,一个初步认识到慢慢了解再到精通掌握的过程,当然,学习 Verilog HDL 语法也是一样,首先你要了解什么是 Verilog HDL,然后结合实践再遵从理论,你才可能理解的更加迅速更加透彻。

Verilog HDL 是一种用于数字逻辑电路设计的语言。用 Verilog HDL 描述的电路设计就是该电路的 Verilog HDL 模型。Verilog HDL 既是一种行为描述的语言也是一种结构描述的语言。这也就是说,既可以用电路的功能描述也可以用元器件和它们之间的连接来建立所设计电路的 Verilog HDL 模型。Verilog 模型可以是实际电路的不同级别的抽象。这些抽象的级别和它们对应的模型类型共有以下五种。

  • 系统级(system):用高级语言结构实现设计模块的外部性能的模型。
  • 算法级(algorithm):用高级语言结构实现设计算法的模型。
  • RTL 级(Register Transfer Level):描述数据在寄存器之间流动和如何处理这些数据的模型。
  • 门级(gate-level):描述逻辑门以及逻辑门之间的连接的模型。
  • 开关级(switch-level):描述器件中三极管和储存节点以及它们之间连接的模型。

一个复杂电路系统的完整 Verilog HDL 模型是由若干个 Verilog HDL 模块构成的,每一个模块又可以由若干个子模块构成。其中有些模块需要综合成具体电路,而有些模块只是与用户所设计的模块交互的现存电路或激励信号源。利用 Verilog HDL 语言结构所提供的这种功能就可以构造一个模块间的清晰层次结构来描述极其复杂的大型设计,并对所作设计的逻辑电路进行严格的验证。Verilog HDL 行为描述语言作为一种结构化和过程性的语言,其语法结构非常适合于算法级和 RTL 级的模型设计。这种行为描述语言具有以下功能:

  • 可描述顺序执行或并行执行的程序结构。
  • 用延迟表达式或事件表达式来明确地控制过程的启动时间。
  • 通过命名的事件来触发其它过程里的激活行为或停止行为。
  • 提供了条件、if-else、case、循环程序结构。
  • 提供了可带参数且非零延续时间的任务(task)程序结构。
  • 提供了可定义新的操作符的函数结构(function)。
  • 提供了用于建立表达式的算术运算符、逻辑运算符、位运算符。

Verilog HDL 语言作为一种结构化的语言也非常适合于门级和开关级的模型设计。因其结构化的特点又使它具有以下功能:

  • 提供了完整的一套组合型原语(primitive);
  • 提供了双向通路和电阻器件的原语;
  • 可建立 MOS 器件的电荷分享和电荷衰减动态模型。

Verilog HDL 的构造性语句可以精确地建立信号的模型。这是因为在 Verilog HDL 中,提供了延迟和输出强度的原语来建立精确程度很高的信号模型。信号值可以有不同的的强度,可以通过设定宽范围的模糊值来降低不确定条件的影响。Verilog HDL 作为一种高级的硬件描述编程语言,有着类似 C 语言的风格。其中有许多语句如:if 语句、case 语句等和 C 语言中的对应语句十分相似。如果读者已经掌握 C 语言编程的基础,那么学习 Verilog HDL 并不困难,我们只要对 Verilog HDL 某些语句的特殊方面着重理解,并加强上机练习就能很好地掌握它,利用它的强大功能来设计复杂的数字逻辑电路。下面我们将对 Verilog HDL 中的基本语法逐一加以介绍。

简单的 Verilog HDL 模块

简单的 Verilog HDL 程序介绍

下面先介绍几个简单的 Verilog HDL 程序,然后从中分析 Verilog HDL 程序的特性。

module adder ( count,sum,a,b,cin );

  input [2:0] a,b; 
  input cin;

  output count; 
  output [2:0] sum;

  assign {count,sum} = a + b + cin;

endmodule

上面这个例子通过连续赋值语句描述了一个名为 adder 的三位加法器可以根据两个三比特数 a、b 和进位(cin)计算出和(sum)和进位(count)。从例子中可以看出整个 Verilog HDL 程序是嵌套在 module 和 endmodule 声明语句里的。

module compare ( equal,a,b ); 
 
  input [1:0] a,b; //声明输入信号a,b  
  output equal; //声明输出信号equal

  assign equal=(a==b)?1:0; 
   /*如果a、b 两个输入信号相等,输出为1。否则为0*/ 

endmodule

上面这个程序通过连续赋值语句描述了一个名为 compare 的比较器。对两比特数 a、b 进行比较,如 a 与 b 相等,则输出 equal 为高电平,否则为低电平。在这个程序中,/......../和//.........表示注释部分,注释只是为了方便程序员理解程序,对编译是不起作用的。

module trist2(out,in,enable);

  input in, enable; 
  output out;

  bufif1 mybuf(out,in,enable); 
  
endmodule

上面这个程序描述了一个名为 trist2 的三态驱动器。程序通过调用一个在 Verilog 语言库中现存的三态驱动器实例元件 bufif1 来实现其功能。

module trist1(out,in,enable);

  input in, enable; 
  output out;

  mytri tri_inst(out,in,enable); //调用由mytri模块定义的实例元件tri_inst 

endmodule
module mytri(out,in,enable);

  input in, enable; 
  output out;

   assign out = enable? in : 'bz;

endmodule

这个程序例子通过另一种方法描述了一个三态门。在这个例子中存在着两个模块。模块 trist1 调用由模块 mytri 定义的实例元件 tri_inst。模块 trist1 是顶层模块。模块 mytri 则被称为子模块。

通过上面的例子可以了解到:

  • Verilog HDL 程序是由模块构成,每个模块的内容都是嵌在 module 和 endmodule 两个语句之间。每个模块实现特定的功能。模块是可以进行层次嵌套的。正因为如此,才可以将大型的数字电路设计分割成不同的小模块来实现特定的功能,最后通过顶层模块调用子模块来实现整体功能。
  • 每个模块要进行端口定义,并说明输入输出口,然后对模块的功能进行行为逻辑描述。
  • Verilog HDL 程序的书写格式自由,一行可以写几个语句,一个语句也可以分写多行。
  • 除了 endmodule 语句外,每个语句和数据定义的最后必须有分号。
  • 可以用/...../和//.......对 Verilog HDL 程序的任何部分作注释。一个有使用价值的源程序都应当加上必要的注释,以增强程序的可读性和可维护性。

模块的结构

Verilog 的基本设计单元是“模块”(block)。一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。下面举例说明:

module block (a,b,c,d);
  
  input a,b;
  output c,d;
  
  assign c= a | b ;
  assign d= a & b;
  
endmodule

image.png

上面的例子,程序模块下面是一个电路图的符号。在众多时候,程序模块和电路图符号是一致的,这是因为电路图符号的引脚也就是程序模块的接口。而程序模块描述了电路图符号所实现的逻辑功能。

上面的 Verilog 设计中,模块中的第 3、第 4 行说明接口的信号流向,第 6、第 7 行说明了模块的逻辑功能。以上就是设计一个简单的 Verilog 程序模块所需的全部内容。从上面的例子可以看出,Verilog 结构完全嵌在 module 和 endmodule 声明语句之间,每个 Verilog 程序包括四个主要部分:端口定义、I/O 说明、内部信号声明、功能定义。

模块的端口定义

模块的端口声明了模块的输入输出口。

其格式如下:module 模块名(口 1,口 2,口 3,口 4, ………);

模块内容  

模块的内容包括 I/O 说明、内部信号声明、功能定义。

1、I/O 说明的格式如下:

输入口:input 端口名 1,端口名 2,………,端口名 i; //(共有 i 个输入口)

输出口:output 端口名 1,端口名 2,………,端口名 j; //(共有 j 个输出口)

I/O 说明也可以写在端口声明语句里。其格式如下:

module module_name(input port1,input port2,…output port1,output port2… );

2、内部信号说明:

在模块内用到的和与端口有关的 wire 和 reg 变量的声明。

如:reg [width-1 : 0] R 变量 1,R 变量 2 。。。。;

wire [width-1 : 0] W 变量 1,W 变量 2 。。。。;……

3、功能定义: 

模块中最重要的部分是逻辑功能定义部分。有三种方法可在模块中产生逻辑。

1). 用“assign”声明语句。

如:assign a = b & c; 这种方法的句法很简单,只需写一个“assign”,后面再加一个方程式即可。例子中的方程式描述了一个有两个输入的与门。

2). 用实例元件 。

如:and and_inst( q, a, b ); 采用实例元件的方法象在电路图输入方式下,调入库元件一样。键入元件的名字和相连的引脚即可,表示在设计中用到一个跟与门(and)一样的名为 and_inst 的与门,其输入端为 a, b,输出为 q。要求每个实例元件的名字必须是唯一的,以避免与其他调用与门(and)的实例混淆。

3). 用“always”块。

always @(posedge clk or posedge clr) 
  begin 
    if(clr) q <= 0; 
    else if(en) q <= d; 
  end

采用“assign”语句是描述组合逻辑最常用的方法之一。而“always”块既可用于描述组合逻辑也可描述时序逻辑。上面的例子用“always”块生成了一个带有异步清除端的 D 触发器。“always”块可用很多种描述手段来表达逻辑,例如上例中就用了 if...else 语句来表达逻辑关系。如按一定的风格来编写“always”块,可以通过综合工具把源代码自动综合成用门级结构表示的组合或时序逻辑电路。

注意:如果用 Verilog 模块实现一定的功能,首先应该清楚哪些是同时发生的,哪些是顺序发生的。上面三个例子分别采用了“assign”语句、实例元件和“always”块。这三个例子描述的逻辑功能是同时执行的。也就是说,如果把这三项写到一个 VeriIog 模块文件中去,它们的次序不会影响逻辑实现的功能。这三项是同时执行的,也就是并发的。

然而,在“always”模块内,逻辑是按照指定的顺序执行的。“always”块中的语句称为“顺序语句”,因为它们是顺序执行的。请注意,两个或更多的“always”模块也是同时执行的,但是模块内部的语句是顺序执行的。看一下“always”内的语句,你就会明白它是如何实现功能的。if..else… if 必须顺序执行,否则其功能就没有任何意义。如果 else 语句在 if 语句之前执行,功能就会不符合要求。为了能实现上述描述的功能,“always”模块内部的语句将按照书写的顺序执行。

数据类型及其常量、变量

Verilog HDL 中总共有十九种数据类型,数据类型是用来表示数字电路硬件中的数据储存和传送元素的。在这里我们先只介绍四个最基本的数据类型,它们是: reg 型、wire 型、integer 型、parameter 型,其它数据类型在后面逐步介绍,也可以查阅相关 Verilog HDL 语法参考书,其它的类型如下:large 型、medium 型、scalared 型、time 型、small 型、tri 型、trio 型、tri1 型、triand 型、trior 型、trireg 型、vectored 型、wand 型、wor 型。这些数据类型除 time 型外都与基本逻辑单元建库有关,与系统设计没有很大的关系。

在一般电路设计自动化的环境下,仿真用的基本部件库是由半导体厂家和 EDA 工具厂家共同提供的。系统设计工程师不必过多地关心门级和开关级的 Verilog HDL 语法现象。Verilog HDL 语言中也有常量和变量之分。它们分别属于以上这些类型。下面就最常用的几种进行介绍。

常量

在程序运行过程中,其值不能被改变的量称为常量。下面首先对在 Verilog HDL 语言中使用的数字及其表示方式进行介绍。

一.数字型  

整数: 

在 Verilog HDL 中,整型常量即整常数有以下四种进制表示形式:

  1. 二进制整数(b 或 B)
  2. 十进制整数(d 或 D)
  3. 十六进制整数(h 或 H)
  4. 八进制整数(o 或 O)

数字表达方式有以下三种:

  1. <位宽><进制><数字>这是一种全面的描述方式。
  2. <进制><数字>在这种描述方式中,数字的位宽采用缺省位宽(这由具体的机器系统决定,但至少 32 位)。
  3. <数字>在这种描述方式中,采用缺省进制十进制。在表达式中,位宽指明了数字的精确位数。例如:一个 4 位二进制数的数字的位宽为 4,一个 4 位十六进制数的数字的位宽为 16(因为每单个十六进制数就要用 4 位二进制数来表示)。见下例: 8'b10101100 //位宽为 8 的数的二进制表示, 'b 表示二进制 8'ha2 //位宽为 8 的数的十六进制,'h 表示十六进制。

x 和 z 值: 

在数字电路中,x 代表不定值,z 代表高阻值。一个 x 可以用来定义十六进制数的四位二进制数的状态,八进制数的三位,二进制数的一位。z 的表示方式同 x 类似。z 还有一种表达方式是可以写作?。在使用 case 表达式时建议使用这种写法,以提高程序的可读性。见下例: 4'b10x0 //位宽为 4 的二进制数从低位数起第二位为不定值 4'b101z //位宽为 4 的二进制数从低位数起第一位为高阻值 12'dz //位宽为 12 的十进制数其值为高阻值(第一种表达方式) 12'd? //位宽为 12 的十进制数其值为高阻值(第二种表达方式) 8'h4x //位宽为 8 的十六进制数其低四位值为不定值。

负数: 

一个数字可以被定义为负数,只需在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。注意减号不可以放在位宽和进制之间也不可以放在进制和具体的数之间。见下例: -8'd5 //这个表达式代表 5 的补数(用八位二进制数表示) 8'd-5 //非法格式。

下划线(underscore_): 

下划线可以用来分隔开数的表达以提高程序可读性。但不可以用在位宽和进制处,只能用在具体的数字之间。

见下例:

16'b1010_1011_1111_1010 //合法格式

8'b_0011_1010 //非法格式

当常量不说明位数时,默认值是 32 位,每个字母用 8 位的 ASCII 值表示。  例:

10 = 32’d10 = 32’b1010 21

1=32’d1=32’b1

-1=-32’d1=32’hFFFFFFFF

‘BX=32’BX=32’BXXXXXXX…X

“AB”=16’B01000001_01000010

二.参数(Parameter)型  

在 Verilog HDL 中用 parameter 来定义常量,即用 parameter 来定义一个标识符代表一个常量,称为符号常量,即标识符形式的常量,采用标识符代表一个常量可提高程序的可读性和可维护性。parameter 型数据是一种常数型的数据,其说明格式如下:

parameter 参数名 1 =表达式,参数名 2 =表达式, …, 参数名 n =表达式;

parameter 是参数型数据的确认符,确认符后跟着一个用逗号分隔开的赋值语句表。在每一个赋值语句的右边必须是一个常数表达式。也就是说,该表达式只能包含数字或先前已定义过的参数。见下例:

parameter msb=7; //定义参数msb为常量7 
parameter e=25, f=29; //定义二个常数参数 
parameter r=5.7; //声明r为一个实型参数 
parameter byte_size=8, byte_msb=byte_size-1; //用常数表达式赋值 
parameter average_delay = (r+f)/2; //用常数表达式赋值

参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时可通过参数传递改变在被引用模块或实例中已定义的参数。下面将通过两个例子进一步说明在层次调用的电路中改变参数常用的一些用法。

module Decode(A,F);

  parameter Width=1, Polarity=1; 
  ……………
  
endmodule 

module Top;

    wire[3:0] A4; 
    wire[4:0] A5; 
    wire[15:0] F16; 
    wire[31:0] F32; 
    Decode #(4,0) D1(A4,F16); 
    Decode #(5) D2(A5,F32); 
    
endmodule

引用 Decode 实例时,D1,D2 的 Width 将采用不同的值 4 和 5,且 D1 的 Polarity 将为 0。可用例子中所用的方法来改变参数,即用 #(4,0)向 D1 中传递 Width=4,Polarity=0; 用#(5)向 D2 中传递 Width=5,Polarity 仍为 1。

Module Test;

   wire W;
   Top T ( );
 
emdmodule
module Top;

   wire W
   Block B1 ( );
   Block B2 ( );
 
endmodule
module Block;

   Parameter P = 0;
 
endmodule
module Annotate;

 defparam
 Test.T.B1.P = 2,
 Test.T.B2.P = 3;
 
endmodule

Image

上面是一个多层次模块构成的电路,在一个模块中改变另一个模块的参数时,需要使用 defparam 命令。

Day 1 就到这里,Day 2 继续开始变量,大侠保重,告辞。

END

作者:The last one
原文:FPGA技术江湖

相关文章推荐

更多 FPGA 干货请关注FPGA的逻辑技术专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
10654
内容数
613
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息