[转](笔记)Verilog之三:行为建模方法

verilog有两种建模方法:行为建模和结构建模。

结构建模作为一种比较“底层”的方法,是对电路具体结构的描述;而行为建模的描述非常像C语言,是对电路功能的描述,不涉及具体结构。

3.1 数据流行为建模

      数据流模型是一种比较简单的行为模型,它只有一种形式,即使用关键词“assign”引导的一条连续赋值语句,其赋值目标只能是线网,而且这种赋值行为没有任何附加的判断条件。通常,描述组合逻辑电路的行为最好使用连续赋值语句建模。

3.1.1 连续赋值语句

      连续赋值语句用于把值赋给线网,关键词是assign,格式如下:

assign LHS_target = RHS_expression;

       其中LHS_target是目标线网,RHS_expression是赋值操作表达式。

wire[3:0]z, preset, clear;

assign z=preset & clear;  

       连续赋值语句的执行过程是这样的:只要等号右边的操作数上有事件发生时,右端表达式即被计算,如果结果有变化,新结果就赋给等号左端的线网。

       连续赋值语句的赋值目标可以是:

·标量线网

·向量线网

·向量的常数型位选择;

·向量的常数型部分选择;

·上述类型的任意连接结果(使用连接操作符)

       连续赋值语句的执行顺序与其书写顺序无关,只要等号右端的操作数的值有变化就执行。可以认为在连续赋值语句建模的过程中,等号右端操作数上的数据是始终处于被监控状态的,一旦这些数据发生变化就会引起赋值语句的执行。这也正是连续赋值语句建模被称为数据流行为建模的原因所在。

3.1.2 线网声明赋值

       对线网的赋值并非必须要用到assign。可以在声明线网时就对其赋值。例如:

wire[3:0] sum = 4'b0;

       这种赋值方式与assign的效果完全相同。

3.1.3 时延的概念

       时延就是给出一个值,程序执行到此就会暂时停下来,等待这个值规定的若干个单位时间,然后再继续执行后面的语句。时延可以单独作为依据,也可以内嵌在连续赋值语句中,跟在assign之后。默认时延是0

       连续赋值语句中的时延可以指定3类时延值:上升时延、下降时延和关闭时延(值变为x)。其语法格式如下: assign #(rise, fall, turn-off) LHS_target = RHS_expression;

3.1.4 线网时延

      时延可以放在线网声明中。如果时延在线网声明赋值中出现,那么这个时延不是线网时延,而是赋值时延。通常,线网目标得到这所赋值需要经过这两个时延的和。

3.1.5 用数据流建模方式实现一位全加器

3.2 顺序行为建模

3.2.1 过程结构语句

      在verilog顺序行为描述中,有两个必然出现的结构语句:initial语句和always语句。initial和always都不仅仅是单独的一条语句,它们引导一个“过程”或者说一个“结构”,跟在initial和always之后的都是一段程序,所有其他顺序行为语句都必须包含在这段程序中。可以说,initial和always的出现是顺序行为描述的开始。

       作为顺序行为建模的两条基本语句,initial和always存在一些共同的特点:

·所有的initial和always语句都是在时刻0(仿真刚开始)开始执行;

·initial和always之后都跟随着一段程序,这段程序会被封装成一个“程序块”,可以用begin-end(顺序语句块)封装,也可以用fork-join(并行语句块)封装。

·一个模块中可以包含任意多个Initial或always语句,这些initial和always语句彼此之间都是并行执行的,即这些语句的执行顺序与其在模块中的书写顺序无关。

(1) initial语句

        initial语句只执行一次,并且在仿真开始时(时刻0)就执行。如果initial之后要跟多条过程语句,就要把这些语句封装为“程序块”。顺序语句块(begin-end)是最常使用的封装结构,在begin和end之间的过程语句是按书写顺序依次执行的。下面是一个带有顺序语句块的initial语句程序片段。

parameter SIZE = 1024;

reg[7:0] RAM [0:SIZE-1];

reg RibReg;

initial

    begin: SEQ_BLK_A

   integer Index;

    RibReg = 0;

    for (Index = 0; Index < SIZE; Index = Index+1)

          RAM[Index] = 0;

    end

       initial语句的作用是在执行时将存储器RAM的所有存储单元初始化为0。程序中begin之后的标识符“SEQ_BLK_A”是顺序语句块的名称,并不是所有的语句块都要求有名称。在这段过程语句中因为出现了局部变量Index,所以要求这个程序块必须有名称标记。

(2)always语句

       always语句和initial语句一样,也从时刻0开始执行。不同的是,initial语句在整个仿真过程中只执行一次,而在always语句中,只要满足其规定的条件(时序控制中定义的条件),always语句就执行,如果在整个访问过程中多次满足这个条件,always就会被多次执行。其语法格式:

always

       [timing_control] procedural_statement

       其中,timing_control是时序控制,形式可以是时延控制(即等待一个确定的时间),或事件控制(即等待某一时间发生或某一条件为真);procedural_statement是过程语句。always中的时序控制可以是上例中的时延控制,也可以是事件控制。事件控制就是等待某一事件发生,该事件的发生将执行always语句。用符号“@”定义了事件控制的always语句。事件控制还有一种是“电平敏感事件”,使用关键词“wait”定义。

       一个模块可以既包含initial语句也包含always语句,而且可以包含多条initial语句和always语句,所有initial语句和always语句都是在时刻0开始执行。

3.2.2 时序控制

       时序控制有两种形式:时延控制和事件控制。

      (1)时延控制

        时延控制可以以三种不同的形式出现:

·语句前时延   ·单独时延     · 语句内时延

我们来看一下语句内时延,对于Done = #5 A&B;程序执行至此时,首先计算A&B的值,然后进入时延,经过5个单位时间后把计算出来的结果赋给Done。来看一个例子:

Done = #5 'b1;

与程序段:

begin

    tmp = 'b1;

    #5 Done = tmp;

end

效果是一样的。

如果时延表达式的值是0,则称之为显示零时延,它和不定义时延是完全不同的。后者默认时延是0,程序继续执行不受任何影响,而显式零时延将使整个程序的运行在当前仿真时间暂停,使其处于一种“等待”状态,等待所有其他正在被执行的事件全都执行完毕后,才将整个进程唤醒继续执行,处于等待状态时,仿真时间不前进。

(2)事件控制

      事件控制就是把发生某个事件作为执行某个操作的条件。事件控制方式有两种:边沿触发事件和电平敏感事件。

·边沿触发事件

使用符号@定义,形式如下:

@ event procedural_statement

其中“@”是边沿事件使用的符号,event是边沿事件,procedural_statement是过程语句。

       和时延控制类似,事件控制也可以独立成为一条语句。

@   event;

运行到这条语句时,整个程序在此被暂停,进入等待状态,一直等到这条语句中指定的事件发生后,才能继续执行后面的程序。

      在事件控制的定义中,event可以是多个事件,这些事件之间只需要用关键字“or”分隔开即可。定义了多个事件后,只要其中的一个事件发生,就可以执行其后的程序。注意:在verilog中,上升沿不仅仅是从0到1这一种情况,下降沿也不仅仅是从1到0这种情况。

·电平敏感事件

      电平敏感事件使用关键词wait,形式如下:

wait (condition)

      procedural_statement

      其中,condition就是电平敏感事件,prcedural_statement是过程语句。执行到wait时,检查condition定义的条件是否满足,满足就接着执行随后的过程语句;否则进入等待状态,知道条件满足为止。

3.2.3 语句块

(1)顺序语句块

(2)并行语句块

       并行语句块是用fork-join封装的一段程序,和begin-end不同的是,并行语句块中的语句彼此之间都是并行执行的,和书写顺序无关。

3.2.4 过程性赋值

       过程性赋值是最常见的赋值形式:等号左侧是赋值目标,右侧是表达式。它有以下几个特点:

· 过程性赋值只出现在initial语句和always语句内;

· 过程性赋值只能给寄存器变量赋值;

·赋值表达式的右端可以是任何表达式。

       事实上,Verilog有两种过程性赋值:阻塞过程赋值和非阻塞过程赋值。之前程序中出现的都是阻塞过程赋值,其标志是使用赋值符号“=”,而非阻塞过程赋值使用赋值符号“<=”,二者在功能和特点上有很大区别。

(1)阻塞过程赋值

       “阻塞”是从阻塞过程赋值的工作过程中得来的。它先计算右侧表达式的值,然后赋值给等号左端目标,而且在完成整个赋值之前不能被其它语句打断。也就是说在某一条阻塞过程赋值语句正在执行时,处于其后的其它赋值语句都不能执行。

(2)非阻塞过程赋值

       非阻塞过程赋值操作符是“<=”,非阻塞过程赋值只能用于给寄存器赋值。之所以称为“非阻塞”,是因为通常所说的赋值过程都包括两个子过程:

·过程1,计算右侧表达式的值;

·过程2,给左侧目标赋值。

       对阻塞过程赋值而言,这两个子过程可以视为连续完成的,而且在完成赋值之前不允许其后的其它语句执行。而对于非阻塞赋值,所谓“在某个时刻完成赋值”,其实是在这个时刻开始执行子过程1,在这个时刻结束时执行子过程2,这两个子过程之间有一个微小的事件间隔。在这个间隔期间,这条非阻塞赋值语句后面的其它的语句也可以执行。

(3)阻塞赋值与非阻塞赋值的混合使用

(4)过程性连续赋值

       连续赋值适用于线网,过程赋值适用于寄存器。但是还有一类赋值方式,它既能对线网赋值也能对寄存器赋值(但不能是寄存器的位选择或部分选择),这种赋值方式被称为过程性连续赋值。它属于过程性赋值而非连续赋值,所以它能出现在always和initial语句中(连续赋值语句不能出现在always和initial内)。但是它又有连续赋值的一些特性,就是在过程性连续赋值语句中,右端表达式中操作数的任何变化都会引起赋值语句的重新执行。

       过程性赋值语句有两种类型:assign-deassign(赋值-重新赋值),force-release(强制-释放虽然它也可以用于对寄存器赋值,但主要用于对线网赋值)。

·assign-deassign

     assign用于对寄存器赋值,deassign用于取消之前由assign赋给某寄存器的值。也就是说,使用assign为寄存器赋值后,这个值将一直保存在寄存器上,直到遇到deassign为止。看下例:

module DEF(D, Clr, Clk, Q);

       input D, Clr, Clk;

       output Q;

       reg Q;

        always @(negedge Clk)

             Q = D;

       always @(Clr)

             begin

                 if(!Clr)

                    assign Q = 0;

                 else

                    deassign Q;

          end

endmodule

上面这段程序中,第一个always会在Clk的下降沿把D赋给Q,但是第二个always内的过程性连续赋值语句会使第一个always的赋值失效。在第二个always中,如果clr为0,assign赋值使Q被赋值为0,然后Q就始终为0,而不管Clk如何变化,即Clk和D对Q无效;等到Clr变为1时,deassign被执行,对Q的过程性连续赋值被取消。之后Clk才能对Q产生影响。换句话说,在Clr为0是,Q一直被assign所霸占,只有Clr为1时,才会把Q释放掉,从而才会受到D的影响。

·force-release

       当force语句应用于寄存器时,寄存器的当前值被force语句的值覆盖;当release语句应用于寄存器时,寄存器中的当前值将保持不变,直到被赋予新值。来看两段代码:

reg[2:0] Colt;

Colt = 2;

force Colt = 1;    //Colt被强制赋值为1

release Colt       //对Colt的强制赋值被取消,从现在起Colt将保持值为1

assign Colt = 5;  

force Colt = 3;      //被强制赋值为3

release Colt;       //对Colt的强制赋值被取消,assign Colt = 5重新生效,使得Colt的值变为5

用force语句对线网赋值时,将为线网替换所有驱动源,直到那个线网上执行release语句为止。

wire prt;

or #1 (prt, Std, Dzx);      //定义一个或门,线网prt的驱动源由或门的结构给出

initial

    begin

         force prt = Dzx & Srd;

         #5;

         release prt;

     end

上面的程序中,执行force语句将替换ptr原来的驱动源,ptr将一直保持这些驱动源,直到release把对prt的强制赋值取消之后,或门的prt驱动源才重新生效。

(5)连续赋值与过程赋值

3.2.5 if语句

对于if语句,如果某个分支内的过程语句多于一条,就应该把它们放在begin-end块内。这里的begin-end相当于C中的{}。

3.2.6 case语句

       注意:在比较条件表达式和分支表达式的值时,如果值的某些位出现了x或z,那么这些x和z和0、1一样有意义,所以只要在两个值的相同位置出现x或z,就认为这两个值相同。

·casex和casez

      在casez语句中,出现在条件表达式和任意分支项表达式中的值z被认为是无关值,出现z的那个位在比较时被忽略。在casex语句中,值x和z都被认为是无关位。所谓无关位即在比较时可以忽略的位,只要其它的位相匹配即可。

3.2.7 循环语句

(1)forever是个永远循环执行的语句,不需要声明任何变量,其语法格式如下:

forever

      procedural_statement

其中,procedural_statement是过程语句,若过程语句多于一条,则应该放在begin-end块内。注意:forever语句中必须有某种形式的时序控制,否则forever会在0时延后永远重复执行过程语句。forever的最主要用途是产生周期性的波形作为仿真测试信号。

(2)repeat循环语句

     repeat循环是个重复执行若干次数的语句,带有一个控制循环次数的常量或者变量,其语法形式如下:

repeat(loop_count)

       procedural_statement

其中loop_count是控制循环次数的常数或变量;如果控制循环次数的常数或变量的值不确定(为x或z)时,那么循环次数按0处理。

Sum = repeat(Count) @ (posedge Clk) Sum+1;     //首先计算出Sum+1的值,然后等待Clk上升沿,最后为左端赋值

repeat(4) @(negedge Clockz);     //等待Clockz上出现4次下降沿之后,才能继续执行repeat之后的语句

(3)while循环语句

(4)for循环语句

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值