FPGA学习笔记-知识点3-Verilog语法1

1.关键字


2.运算符

按其功能可分为以下几类:

1) 算术运算符(+,-,×,/,%)

2) 赋值运算符(=,<=)

3) 关系运算符(>,<,>=,<=)

4) 逻辑运算符(&&,||,!)

5) 条件运算符( ? :)

6) 位运算符(,|,^,&,^)

7) 移位运算符(<<,>>)

8) 拼接运算符({ })

9) 其它

按其所带操作数的个数运算符可分为三种:

1) 单目运算符(unary operator):可以带一个操作数,操作数放在运算符的右边。

2) 二目运算符(binary operator):可以带二个操作数,操作数放在运算符的两边。

3) 三目运算符(ternary operator):可以带三个操作,这三个操作数用三目运算符分隔开。

见下例:

clock = ~clock; // ~是一个单目取反运算符, clock 是操作数。

c = a | b; // 是一个二目按位或运算符, a 和 b 是操作数。

r = s ? t : u; // ?: 是一个三目条件运算符, s,t,u 是操作数。

 


3.数据类型

Verilog 中共有 19 种数据类型。

基本的四种类型: reg 型、wire 型、integer 型、parameter 型

其他类型:large 型、medium 型、small 型、scalared 型、time 型、tri 型、trio 型、tril 型、triand 型、trior 型、trireg 型、vectored 型、wand 型和 wor 型。

1) wire 型

wire 型数据常用来表示以 assign 关键字指定的组合逻辑信号。Verilog 程序模块中输入、输出信号类型默认为 wire 型。wire 型信号可以用做方程式的输入,也可以用做“assign”语句或者实例元件的输出。

wire 型信号的定义格式如下:

wire [n-1:0] 数据名 1,数据名 2,……数据名 N;

这里,总共定义了 N 条线,每条线的位宽为 n。下面给出几个例子:

wire [9:0] a, b, c; // a, b, c 都是位宽为 10 的 wire 型信号

wire d;

2) reg 型

reg 是寄存器数据类型的关键字。寄存器是数据存储单元的抽象,通过赋值语句可以改变寄存器存储的值,其作用相当于改变触发器存储器的值。reg 型数据常用来表示 always 模块内的指定信号,代表触发器。通常在设计中要由 always 模块通过使用行为描述语句来表达逻辑关系。在 always 块内被赋值的每一个信号都必须定义为 reg 型, 即赋值操作符的右端变量必须是 reg 型。

reg 型信号的定义格式如下:

reg [n-1:0] 数据名 1,数据名 2,……数据名 N;

这里,总共定义了 N 个寄存器变量,每条线的位宽为 n。下面给出几个例子:

reg [9:0] a, b, c; // a, b, c 都是位宽为 10 的寄存器

reg d;

reg 型数据的缺省值是未知的。reg 型数据可以为正值或负值。但当一个 reg 型数据是一个表达式中的操作数时,它的值被当作无符号值,即正值。如果一个 4 位的 reg 型数据被写入 -1,在表达式中运算时,其值被认为是+15。

reg 型和 wire 型的区别在于:reg 型保持最后一次的赋值,而 wire 型则需要持续的驱动。

3) integer 型

也是一种寄存器数据类型,integer 类型的变量为有符号数,而 reg 类型的变量则为无符号数,除非特别声明为有符号数。

还有就是 integer 的位宽为宿主机的字的位数,但最小为 32 位,用 integer 的变量都可以用 reg 定义,只是用于计数更方便而已。 reg, integer, real,time 都是寄存器数据类型,定义在 Verilog 中用来保存数值的变量,和实际的硬件电路中的寄存器有区别。

4) parameter 型

在 Verilog HDL 中用 parameter 来定义常量,即用 parameter 来定义一个标志符表示一个常数。采用该类型可以提高程序的可读性和可维护性。

parameter 型信号的定义格式如下:

parameter 参数名 1 = 数据名 1;

下面给出几个例子:

parameter s1 = 1;

parameter [3:0] S0=4'h0,

S1=4'h1,

S2=4'h2,

S3=4'h3,

S4=4'h4;


4. 缩位运算

缩减运算符是单目运算符,也有与或非运算。

其与或非运算规则类似于位运算符的与或非运算规则,但其运算过程不同。位运算是对操作数的相应位进行与或非运算,操作数是几位数则运算结果也是几位数。

而缩减运算则不同,缩减运算是对单个操作数进行或与非递推运算,最后的运算结果是一位的二进制数。

缩减运算的具体运算过程是这样的:

1) 第一步先将操作数的第一位与第二位进行或与非运算,

2) 第二步将运算结果与第三位进行或与非运算,

3) 依次类推,直至最后一位。

例如:reg [3:0] B;

reg C;

C = &B;

相当于:

C =( (B[0]&B[1]) & B[2] ) & B[3];


5. if-else

设计要点

1) 条件语句必须在过程块中使用。所谓过程块语句是指由 initial、always 引导的执行语句集合。除了这两个语句块引导的 begin end 块中可以编写条件语句外,模块中的其他地方都不能编写。

2) if 语句中的表达式一般为逻辑表达式或者关系表达式。系统对表达式的值进行判断;若为 0,z,X;按照假处理;若为 1 按照真处理,执行指定的语句;

3) if(a)等价于 if(a == 1);

4) if 语句可以·嵌套·使用

5) end 总是与离它最近的一份 else 配对。

如果 if 语句使用不当,没有 else,

可能会综合出来意想不到的锁存器

在 always 块里面,如果在给定的条件下变量没有被赋值,这个变量将会保持原来的值,也就是说会生成一个锁存器。

需要注意的是,这里说的是可能,

因此,不代表没有 else 就一定会出现锁存器,

同时,不代表有 else 就一定不会出现锁存器。

这个是根据具体设计来看的。


6. case

case 语句检查给定的表达式是否与列表中的其他表达式之一相匹配,并据此进行分支。它通常用于实现一个多路复用器。

如果要检查的条件很多,if-else 结构可能不合适,因为它会综合成一个优先编码器而不是多路复用器。

一个 Verilog case 语句以 case 关键字开始,以 endcase 关键字结束。在括弧内的表达式将被精确地评估一次,并按其编写顺序与备选方案列表进行比较,与给定表达式匹配的备选方案的语句将被执行。一块多条语句必须分组,并在 begin 和 end 范围内。

// Here 'expression' should match one of the items (item 1,2,3 or 4)

case (<expression>)

        case_item1 : <single statement>

        case_item2,

        case_item3 : <single statement>

        case_item4 : begin

                <multiple statements>

        end

        default : <statement>

endcase

如果所有的 case 项都不符合给定的表达式,则执行缺省项内的语句,缺省语句是可选的,在case语句中只能有一条缺省语句。case 语句可以嵌套。

如果没有符合表达式的项目,也没有给出缺省语句,执行将不做任何事情就退出 case 块。避免锁存器同 if else,case 应当加上 default,以避免锁存器出现。

注意,如果 case 的情况是完备的,可以不加。(完备意为所有情况都设计了)


7. for

在 C 语言中,经常用到 for 循环语句,但在硬件描述语言中 for 语句的使用较 C 语言等软件描述语言有较大的区别。

for 循环会被综合器展开为所有变量情况的执行语句,每个变量独立占用寄存器资源。

简单的说就是:for 语句循环几次,就是将相同的电路复制几次,因此循环次数越多,占用面积越大,综合就越慢。

注意,i 的变化不跟时钟走:

在 Verilog 中使用 for 循环的功能就是,把同一块电路复制多份,完全起不到计数的作用,所以这个 i 的意思是复制多少份你这段代码实现的电路,和时钟没有任何关系。主要是为了提高编码效率。


8. generate

Verilog 中的 generate 语句常用于编写可配置的、可综合的 RTL 的设计结构。它可用于创建模块的多个实例化,或者有条件的实例化代码块。然而,有时候很困惑 generate 的使用方法,因此看下 generate 的几种常用用法。

我们常用 generate 语句做三件事情。一个是用来构造循环结构,用来多次实例化某个模块。一个是构造条件 generate 结构,用来在多个块之间最多选择一个代码块,条件 generate结构包含 if--generate 结构和 case--generate 形式。还有一个是用来断言。

在 Verilog 中,generate 在建模(elaboration)阶段实施,出现预处理之后,正式模拟仿真之前。因此。generate 结构中的所有表达式都必须是常量表达式,并在建模(elaboration)时确定。例如,generate 结构可能受参数值的影响,但不受动态变量的影响。

generate 循环的语法与 for 循环语句的语法很相似。但是在使用时必须先在 genvar 声明中声明循环中使用的索引变量名,然后才能使用它。genvar 声明的索引变量被用作整数用来判断 generate 循环。genvar 声明可以是 generate 结构的内部或外部区域,并且相同的循环索引变量可以在多个 generate 循环中,只要这些环不嵌套。genvar 只有在建模的时候才会出现,在仿真时就已经消失了。

在“展开”生成循环的每个实例中,将创建一个隐式 localparam,其名称和类型与循环索引变量相同。它的值是“展开”循环的特定实例的“索引”。可以从 RTL 引用此 localparam 以控制生成的代码,甚至可以由分层引用来引用。

Verilog 中 generate 循环中的 generate 块可以命名也可以不命名。如果已命名,则会创建一个 generate 块实例数组。如果未命名,则有些仿真工具会出现警告,因此,最好始终对它们进行命名。


9. 函数 function

function 函数的目的返回一个用于表达式的值。

verilog 中的 function 只能用于组合逻辑;

1 定义函数的语法

function <返回值的类型或范围> <函数名>

        <端口说明语句>

<变量类型说明>

begin

<语句>

end

endfunction

说明:

1         function [7:0] getbyte ;

2         input [15:0] address ;

3                 begin

4                         <说明语句> //从地址字节提取低字节的程序

5                         getbyte = result_expression ; //把结果赋给函数的返回字节

6                 end

7         endfunction

① <返回值的类型或范围>这一项为可选项,如果缺失,则返回值为一位寄存器类型数据。② 从函数的返回值:函数的定义蕴含声明了与函数同名、位宽一致的内部寄存器。例子中,getbyte 被赋予的值就是调用函数的返回值。

③ 函数的调用:函数的调用是通过将函数作为表达式中的操作数来实现的。其调用格式:<函数名> (<表达式> ,…, <表达式>);

其中函数名作为确认符。下面的例子中,两次调用 getbyte,把两次调用的结果进行位拼接运算,以生成一个字。

word = control ? {getbyte(msbyte),getbyte(lsbyte)} : 8'd0 ;

④ 函数使用的规则

1 )函数定义不能包含有任何的时间控制语句,即任何用#、@、wait 来标识的语句。

2 )函数不能调用“task”。

3 )定义函数时至少要有一个输入参数。

4 )在函数的定义中必须有一条赋值语句给函数中与函数名同名、位宽相同的内部寄存器赋值。

5 )verilog 中的 function 只能用于组合逻辑;

2 具体实例

函数功能:实现两个 4bit 数的按位“与”运算。

实验现象:如果函数操作正确,则 led 灯闪烁;如果函数操作不正确,则 led 灯常灭。

module func_ex_01 (

                                input clk , //E1 25M

                                output led //G2 高电平 灯亮

                                );

        ///*counter_01*

         reg [25:0] counter_01 = 26'd0 ;

        always @ (posedge clk)

        begin

                counter_01 <= counter_01 + 1'b1 ;

        end

        /*& function*/

        function [3:0] yu ;

        input [3:0] a ;

        input [3:0] b ;

        begin

                yu = a & b ;

        end

        endfunction

        reg [3:0] reg_a = 4'b0101 ;

        reg [3:0] reg_b = 4'b1010 ;

        wire [3:0] result ;

        assign result = yu(reg_a , reg_b) ;

        //*verify and display*

        assign led = (result == 4'd0) ? counter_01[25] : 1'b0 ;

        endmodule

说明:verilog 中的 function 只能用于组合逻辑;


10. 任务 task

任务就是一段封装在“task-endtask”之间的程序。任务是通过调用来执行的,而且只有在调用时才执行,如果定义了任务,但是在整个过程中都没有调用它,那么这个任务是不会执行的。调用某个任务时可能需要它处理某些数据并返回操作结果,所以任务应当有接收数据的输入端和返回数据的输出端。另外,任务可以彼此调用,而且任务内还可以调用函数。

1.任务定义

任务定义的形式如下:

task task_id;

        [declaration]

        procedural_statement

endtask

其中,关键词 task 和 endtask 将它们之间的内容标志成一个任务定义,task 标志着一个任务定义结构的开始;task_id 是任务名;可选项 declaration 是端口声明语句和变量声明语句,任务接收输入值和返回输出值就是通过此处声明的端口进行的;procedural_statement是一段用来完成这个任务操作的过程语句,如果过程语句多于一条,应将其放在语句块内;endtask 为任务定义结构体结束标志。下面给出一个任务定义的实例。

定义一个任务。

task task_demo;                 //任务定义结构开头,命名为 task_demo

        input [7:0] x,y;                 //输入端口说明

        output [7:0] tmp;             //输出端口说明

        if(x>y)                             //给出任务定义的描述语句

                tmp = x;

        else

                tmp = y;

        endtask

上述代码定义了一个名为“task_demo”的任务,求取两个数的最大值。在定义任务时,

有下列六点需要注意:

(1)在第一行“task”语句中不能列出端口名称;

(2)任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出以及双向端口。

(3)在任务定义的描述语句中,可以使用出现不可综合操作符合语句(使用最为频繁的就是延迟控制语句) ,但这样会造成该任务不可综合。

(4)在任务中可以调用其他的任务或函数,也可以调用自身。

(5)在任务定义结构内不能出现 initial 和 always 过程块。

(6)在任务定义中可以出现“disable 中止语句” ,将中断正在执行的任务,但其是不可综合的。当任务被中断后,程序流程将返回到调用任务的地方继续向下执行。

2.任务调用

虽然任务中不能出现 initial 语句和 always 语句语句,但任务调用语句可以在 initial 语句

和 always 语句中使用,其语法形式如下:

task_id[(端口 1, 端口 2, ........, 端口 N)];

其中 task_id 是要调用的任务名,端口 1、端口 2,…是参数列表。参数列表给出传入任务的数据(进入任务的输入端)和接收返回结果的变量(从任务的输出端接收返回结果)。任务调用语句中,参数列表的顺序必须与任务定义中的端口声明顺序相同。任务调用语句是过程性语句,所以任务调用中接收返回数据的变量必须是寄存器类型。下面给出一个任务调用实例。

例:通过 Verilog HDL 的任务调用实现一个 4 比特全加器。

module EXAMPLE (A, B, CIN, S, COUT);

input [3:0] A, B;

input CIN;

output [3:0] S;

output COUT;

reg [3:0] S;

reg COUT;

reg [1:0] S0, S1, S2, S3;

task ADD;

input A, B, CIN;

output [1:0] C;

reg [1:0] C;

reg S, COUT;

begin

        S = A ^ B ^ CIN;

        COUT = (A&B) | (A&CIN) | (B&CIN);

        C = {COUT, S};

end

endtask

always @(A or B or CIN) begin

ADD (A[0], B[0], CIN, S0);

ADD (A[1], B[1], S0[1], S1);

ADD (A[2], B[2], S1[1], S2);

ADD (A[3], B[3], S2[1], S3);

S = {S3[0], S2[0], S1[0], S0[0]};

COUT = S3[1];

end

endmodule

在调用任务时,需要注意以下几点:

(1)任务调用语句只能出现在过程块内;

(2)任务调用语句和一条普通的行为描述语句的处理方法一致;

(3)当被调用输入、输出或双向端口时,任务调用语句必须包含端口名列表,且信号端口顺序和类型必须和任务定义结构中的顺序和类型一致。需要说明的是,任务的输出端口必须和寄存器类型的数据变量对应。

(4)可综合任务只能实现组合逻辑,也就是说调用可综合任务的时间为“0” 。而在面向仿真的任务中可以带有时序控制,如时延,因此面向仿真的任务的调用时间不为“0” 。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值