第4节 Verilog HDL语言的描述语句
推荐给好友
打印
加入收藏
更新于2008-05-23 15:41:10

2.4.1 结构描述形式 

通过实例进行描述的方法,将Verilog HDL预先定义的基本单元实例嵌入到代码中,监控实例的输入。Verilog HDL中定义了26个有关门级的关键字,比较常用的有8个。在实际工程中,简单的逻辑电路由逻辑门和开关组成,通过门元语可以直观地描述其结构。

基本的门类型关键字如下所述:

  • and
  • nand
  • nor
  • or
  • xor
  • xnor
  • buf
  • not

        Verilog HDL支持的基本逻辑部件是由该基本逻辑器件的原语提供的。其调用格式为:
        门类型 <实例名> (输出,输入1,输入2,……,输入N)

        例如,nand na01(na_out, a, b, c );
        表示一个名字为na01的与非门,输出为na_out,输入为a, b, c。

        例2-5 一个简单的全加器例子:

module ADD(A, B, Cin, Sum, Cout);
        input A, B, Cin;
        output Sum, Cout;
        // 声明变量
        wire S1, T1, T2, T3;

        xor X1 (S1, A, B),
              X2 (Sum, S1, Cin);

        and A1 (T3, A, B),
                A2 (T2, B, Cin),
                A3 (T1, A, Cin);

         or O1 (Cout, T1, T2, T3);
endmodule


在这一实例中,模块包含门的实例语句,也就是包含内置门xor、and和or的实例语句。门实例由线网型变量S1、T1、T2和T3互连。由于未指定顺序,门实例语句可以以任何顺序出现。

门级描述本质上也是一种结构网表。在实际中的使用方式为:先使用门逻辑构成常用的触发器、选择器、加法器等模块,再利用已经设计的模块构成更高一层的模块,依次重复几次,便可以构成一些结构复杂的电路。其缺点是:不易管理,难度较大且需要一定的资源积累。 

2.4.2 数据流描述形式

数据流型描述一般都采用assign连续赋值语句来实现,主要用于实现组合功能。连续赋值语句右边所有的变量受持续监控,只要这些变量有一个发生变化,整个表达式被重新赋值给左端。这种方法只能用于实现组合逻辑电路。其格式如下:

        assign L_s = R_s;
 
例2-6 一个利用数据流描述的移位器

module mlshift2(a, b);
        input a;
        output b;

        assign b = a<<2;
endmodule

在上述模块中,只要a的值发生变化,b就会被重新赋值,所赋值为a左移两位后的值。
 
2.4.3 行为描述形式 

行为型描述主要包括过程结构、语句块、时序控制、流控制等4个方面,主要用于时序逻辑功能的实现。

1.过程结构

过程结构采用下面4种过程模块来实现,具有强的通用型和有效性。

  • initial模块
  • always模块
  • 任务(task)模块
  •  函数(function)模块

一个程序可以有多个initial模块、always模块、task模块和function模块。initial模块和always模块都是同时并行执行的,区别在于initial模块只执行一次,而always模块则是不断重复地运行。另外,task模块和function模块能被多次调用,其具体使用方法可参见3.5.3节的专题。

(1)initial 模块

在进行仿真时,一个initial模块从模拟0时刻开始执行,且在仿真过程中只执行一次,在执行完一次后,该initial就被挂起,不再执行。如果仿真中有两个initial模块,则同时从0时刻开始并行执行。
       
initial模块是面向仿真的,是不可综合的,通常被用来描述测试模块的初始化、监视、波形生成等功能。其格式为:

initial begin/fork
        块内变量说明
        时序控制1 行为语句1;
        ……
        时序控制n 行为语句n;
end/join

其中,begin……end块定义语句中的语句是串行执行的,而fork……join块语句中的语句定义是并行执行的。当块内只有一条语句且不需要定义局部变量时,可以省略begin……end/ fork……join。

例2-7 下面给出一个initial模块的实例。

initial begin
        // 初始化输入向量
        clk = 0;
        ar = 0;
        ai = 0;
        br = 0;
        bi = 0;
        // 等待100ns,全局reset信号有效
        #100;
        ar = 20;
        ai = 10;
        br = 10;
        bi = 10;
end

(2)always 模块

和initial模块不同,always模块是一直重复执行的,并且可被综合。always过程块由always过程语句和语句块组成的,其格式为:

always @ (敏感事件列表) begin/fork
        块内变量说明
        时序控制1 行为语句1;
        ……
        时序控制n 行为语句n;
end/join

其中,begin……end/fork……join的使用方法和initial模块中的一样。敏感事件列表是可选项,但在实际工程中却很常用,而且是比较容易出错的地方。敏感事件表的目的就是触发always模块的运行,而initial后面是不允许有敏感事件表的。

敏感事件表由一个或多个事件表达式构成,事件表达式就是模块启动的条件。当存在多个事件表达式时,要使用关键词or将多个触发条件结合起来。Verilog HDL的语法规定:对于这些表达式所代表的多个触发条件,只要有一个成立,就可以启动块内语句的执行。例如,在语句

        always@ (a or b or c) begin
       ……
end

中,always过程块的多个事件表达式所代表的触发条件是:只要a、b、c信号的电平有任意一个发生变化,begin……end语句就会被触发。
always模块主要是对硬件功能的的行为进行描述,可以实现锁存器和触发器,也可以用来实现组合逻辑。利用always实现组合逻辑时,要将所有的信号放进敏感列表,而实现时序逻辑时却不一定要将所有的结果放进敏感信号列表。敏感信号列表未包含所有输入的情况称为不完整事件说明,有时可能会引起综合器的误解,产生许多意想不到的结果。

例2-8 下例给出敏感事件未包含所有输入信号的情况

module and3(f, a, b, c);
        input a, b, c;
        output f;
        reg f;

        always @(a or b )begin
        f = a & b & c;
        end
endmodule

其中,由于c不在敏感变量列表中,所以当c值变化时,不会重新计算f值。所以上面的程序并不能实现3输入的与门功能行为。正确的3输入与门应当采用下面的表述形式。

module and3(f, a, b, c);
        input a, b, c;
        output f;
        reg f;

        always @(a or b or c )begin
                       f = a & b & c;
        end
endmodule

2.语句块

语句块就是在initial或always模块中位于begin……end/fork……join块定义语句之间的一组行为语句。语句块可以有个名字,写在块定义语句的第一个关键字之后,即begin或fork之后,可以唯一地标识出某一语句块。如果有了块名字,则该语句块被称为一个有名块。在有名块内部可以定义内部寄存器变量,且可以使用“disable”中断语句中断。块名提供了唯一标识寄存器的一种方法。

例2-9 语句块使用例子

always @ (a or b )
begin : adder1
         c = a + b;
end

定义了一个名为adder1的语句块,实现输入数据的相加。

按照界定不同分为两种:

(1)begin……end,用来组合需要顺序执行的语句,被称为串行块。例如:

parameter d = 50;
reg[7:0] r;
begin //由一系列延迟产生的波形
         # d r = ' h35 ; //语句1 
         # d r = ' hE2 ; //语句2
         # d r = ' h00 ; //语句3
         # d r = ' hF7 ; //语句4
         # d –> end_wave; //语句5,触发事件end_wave 
end

串行块的执行特点如下:

  • 串行块内的各条语句是按它们在块内的语句逐次逐条顺序执行的,当前一条执行完之后,才能执行下一条。如上例中语句1至语句5是顺序执行的。
  • 块内每一条语句中的延时控制都是相对于前一条语句结束时刻的延时控制。如上例中语句2的时延为2d。
  • 在进行仿真时,整个语句块总的执行时间等于所有语句执行时间之和。如上例中语句块中总的执行时间为5d。

(2)fork……join,用来组合需要并行执行的语句,被称为并行块。例如:

parameter d = 50;
reg[7:0] r;
fork //由一系列延迟产生的波形
     # d r = ' h35 ; //语句1
     # 2d r = ' hE2 ; //语句2
     # 3d r = ' h00 ; //语句3
     # 4d r = ' hF7 ; //语句4
     # 5d –> end_wave; //语句5,触发事件end_wave
   join

并行块的执行特点为:

  • 并行语句块内各条语句是各自独立地同时开始执行的,各条语句的起始执行时间都等于程序流程进入该语句块的时间。如上例中语句2并不需要等语句1执行完才开始执行,它与语句1是同时开始的。
  • 块内每一条语句中的延时控制都是相对于程序流程进入该语句块的时间而言的。如上例中语句2的延时为2d。
  • 在进行仿真时,整个语句块总的执行时间等于执行时间最长的那条语句所需要的执行时间,如上例中整个语句块的执行时间为5d。

(3)混合使用

在分别对串行块和并行块进行了介绍之后,还需要讨论一下二者的混合使用。混合使用可以分为下面两种情况。

  • 串行块和并行块分别属于不同的过程块时,串行块和并行块是并行执行的。例如一个串行块和并行块分别存在于两个initial过程块中,由于这两个过程块是并行执行的,所以其中所包含的串行语句和并行语句也是同时并行执行的。在串行块内部,其语句是串行执行的;在并行块内部,其语句是并行执行的。
  • 当串行块和并行块嵌套在同一过程块中时,内层语句可以看作是外层语句块中的一条普通语句,内层语句块什么时候得到执行是由外层语句块的规则决定的;而在内层语句块开始执行时,其内部语句怎么执行就要遵守内层语句块的规则。

3.时序控制

Verilog HDL提供了两种类型的显示时序控制,一种是延迟控制,在这种类型的时序控制中通过表达式定义开始遇到这一语句和真正执行这一语句之间的延迟时间。另外一种是事件控制,这种时序控制是通过表达式来完成的,只有当某一事件发生时才允许语句继续向下执行。

(1)延时控制

        延时控制的语法如下:
        # 延时数 表达式;
        延时控制表示在语句执行前的“等待时延”,下面给出一个例子:
initial
begin
      #5 clk = ~clk;
end

延时控制只能在仿真中使用,是不可综合的。在综合时,所有的延时控制都会被忽略。

(2)事件控制

事件控制分为两种:边沿触发事件控制和电平触发事件控制。

  • 边沿触发事件是指指定信号的边沿信号跳变时发生指定的行为,分为信号的上升沿和下降沿控制。上升沿用posedge关键字来描述,下降沿用negedge关键字描述。边沿触发事件控制的语法格式为:
    第一种:@(<边沿触发事件>) 行为语句;
    第二种:@(<边沿触发事件1> or <边沿触发事件2> or …… or <边沿触发事件n>) 行为语句;

    例2-10 边沿触发事件计数器
    reg [4:0] cnt;
    always @(posedge clk) begin
         if (reset)
             cnt <= 0; 
         else
             cnt <= cnt +1; 
    end

            上面这个例子表明:只要clk信号有上升沿,那么cnt信号就会加1,完成计数的功能。这种边沿计数器在同步分频电路中有着广泛的应用。
  • 电平敏感事件是指指定信号的电平发生变化时发生指定的行为。下面是电平触发事件控制的语法和实例:
    第一种:@(<电平触发事件>) 行为语句;
    第二种:@(<电平触发事件1> or <电平触发事件2> or …… or <电平触发事件n>) 行为语句;

    例2-11 电平沿触发计数器
    reg [4:0] cnt;
    always @(a or b or c) begin
          if (reset)
              cnt <= 0; 
          else
              cnt <= cnt +1;
          end

            其中,只要a,b,c信号的电平有变化,信号cnt的值就会加1,这可以用于记录a,b,c变化的次数。

4.流控制

流控制语句包括3类,即跳转、分支和循环语句。

(1)if语句
        if语句的语法如下:

if (条件1)
     语句块1
else if (条件2) 
     语句块2
……
else
     语句块n

如果条件1的表达式为真(或非0值),那么语句块1被执行,否则语句块不被执行,然后依次判断条件2至条件n是否满足,如果满足就执行相应的语句块,最后跳出if语句,整个模块结束。如果所有的条件都不满足,则执行最后一个else分支。在应用中,else if分支的语句数目由实际情况决定;else分支也可以缺省,但会产生一些不可预料的结果,生成本不期望的锁存器。

例2-12 下面给出一个if语句的例子,并说明省略else分支所产生的一些结果。

always @(a1 or b1)
begin
     if (a1) q<= d;
end

if语句只能保证当a1=1时,q才取d的值,但程序没有给出a1=0时的结果。因此在缺少else语句的情况下,即使a1=0时,q的值会保持a1=1的原值,这就综合成了一个锁存器。

如果希望a1=0时,q的值为0或者其他值,那么else分支是必不可少的。下面给出a1=0,q=0的设计:

always @(a1 or b1)
begin
       if (a1) q <= d;
       else q <= 0;
end

(2)case语句

case语句是一个多路条件分支形式,其用法和C语言的csae语句是一样的。

下面给出一个case语句的例子:

reg [2:0] cnt;
case (cnt)
           3'b000: q = q + 1;
           3'b001: q = q + 2;
           3'b010: q = q + 3;
           3'b011: q = q + 4;
           3'b100: q = q + 5;
           3'b101: q = q + 6;
           3'b110: q = q + 7;
           3'b111: q = q + 8;
           default: q <= q+ 1;
endcase

需要指出的是,case语句的default分支虽然可以缺省,但是一般不要缺省,否则会和if语句中缺少else分支一样,生成锁存器。

例2-13 给出case语句的Verilog实例

always @(a1[1:0] or b1)
begin
      case (a1)
      2'b00: q <= b'1;
      2'b01: q <= b'1 + 1;
end

这样就会生成锁存器。一般为了使case语句可控,都需要加上default选项。

always @(a1[1:0] or b1)
begin
        case (a1)
        2'b00: q <= b1; 
        2'b01: q <= b1 + 1; 
        default: q <= b1 + 2;
end

在实际开发中,要避免生成锁存器的错误。如果用if语句,最好写上else选项;如果用case语句,最好写上default项。遵循上面两条原则,就可以避免发生这种错误,使设计者更加明确设计目标,同时也增加了Verilog程序的可读性。

此外,还需要解释在硬件语言中使用if语句和case语句的区别。在实际中如果有分支情况,尽量选择case语句。这是因为case语句的分支是并行执行的,各个分支没有优先级的区别。而if语句的选择分支是串行执行的,是按照书写的顺序逐次判断的。如果设计没有这种优先级的考虑,if语句和case语句相比,需要占用额外的硬件资源。

(3)循环语句

        Verilog HDL中提供了4种循环语句:for循环、while循环、forever循环和repeat循环。其语法和用途与C语言很类似。

  • for循环照指定的次数重复执行过程赋值语句。for循环的语法为:
        for(表达式1; 表达式2; 表达式3) 语句
    for循环语句最简单的应用形式是很容易理解的,其形式为:
        for(循环变量赋初值; 循环结束条件; 循环变量增值)
    例:for语句的应用实例
    for(bindex = 1; bindex <= size; bindex = bindex + 1)
        result = resul + (a <<(bindex-1));
  • while循环执行过程赋值语句直到指定的条件为假。如果表达式条件在开始不为真(包括假、x以及z),那么过程语句将永远不会被执行。while循环的语法为:
    while (表达式) begin
    ……
    end
    例:while语句的应用实例
    while (temp) begin
        count = count + 1;
    end
  • forever循环语句连续执行过程语句。为跳出这样的循环,中止语句可以与过程语句共同使用。同时,在过程语句中必须使用某种形式的时序控制,否则forever循环将永远循环下去。forever语句必须写在initial模块中,用于产生周期性波形。forever循环的语法为
    forever begin
    ……
    end
    例:forever语句的应用实例
    initial
    forever begin
        if(d) a = b + c;
        else a= 0;
    end
  • repeat循环语句执行指定循环数,如果循环计数表达式的值不确定,即为x或z时,那么循环次数按0处理。repeat循环语句的语法为
    repeat(表达式) begin
    ……
    end
    例:repeat语句的应用实例
    repeat (size) begin
        c = b << 1;
    end

2.4.4 混合设计模式 

在模型中,结构描述、数据流描述和行为描述可以自由混合。也就是说,模块描述中可以包括实例化的门、模块实例化语句、连续赋值语句以及行为描述语句的混合,它们之间可以相互包含。使用always语句和initial语句(切记只有寄存器类型数据才可以在模块中赋值)来驱动门和开关,而来自于门或连续赋值语句(只能驱动线网型)的输出能够反过来用于触发always语句和initial语句。

下面给出一个混合设计方式的实例。

例2-14 用结构和行为实体描述了一个4位全加器。
module adder4(in1, in2, sum, flag);
      input [3:0] in1;
      input [3:0] in2; 
      output [4:0] sum;
      output flag; 

       wire c0, c1, c2; 

       fulladd u1 (in1 [0], in2 [0], 0, sum[0], c0);
       fulladd u2 (in1 [1], in2 [1], c0, sum[1], c1);
       fulladd u3 (in1 [2], in2 [2], c1, sum[2], c2);
       fulladd u4 (in1 [3], in2 [3], c2, sum[3], sum[4]);

      assign flag = sum ? 0 : 1;
endmodule

在这个例子中,用结构化模块计数sum输出,用行为级模块输出标志位。

 

<<上一节    下一节>>




 
关于我们 | 诚邀加盟 | 客户服务 | 相关法律 | 网站地图 | 友情链接 | 服务信箱:service@eefocus.com
© 2006 与非门科技信息咨询(北京)有限公司 All Rights Reserved.