【学习笔记】Verilog之三:行为建模方法

简介: Verilog是一种硬件描述语言,用于模拟电子系统的结构和行为。行为建模是Verilog的一种方法,分为数据流行为建模和顺序行为建模。数据流行为建模主要使用`assign`语句,它描述了电路的功能而不涉及具体结构。连续赋值语句(`assign`)用于给线网分配值,当右端表达式发生变化时,新值会立即赋给线网。此外,还有时延的概念,可以指定赋值或事件发生的延迟时间。

介绍


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循环语句



一些代码例子


Verilog是一种硬件描述语言(HDL),用于模拟电子系统的行为、结构和数据流。在Verilog中,行为建模通常指的是使用过程性(procedural)语句来描述硬件的行为。这些过程性语句包括alwaysinitialassign等块,以及控制流语句(如ifelsecaseforwhile等)。

以下是一些Verilog行为建模的示例:

1. 使用always块描述时钟驱动的行为

verilog复制代码
module clocked_behavior(  
    input wire clk,  
    input wire reset,  
    output reg out  
);  
always @(posedge clk or posedge reset) begin
if (reset) begin
    // 如果reset为高,重置out为0  
    out <= 0;  
end else begin
    // 否则,每次时钟上升沿,out的值取反  
    out <= ~out;  
end
end
endmodule

2. 使用initial块进行初始化或仿真控制

verilog复制代码
module initial_behavior;  
reg [7:0] counter;  
initial begin
// 初始化counter为0  
    counter = 0;  
// 每隔10个时间单位,增加counter的值  
    #10 counter = counter + 1;  
    #10 counter = counter + 1;  
// ... 可以继续添加更多步骤  
// 停止仿真(这通常在测试平台中使用)  
$finish;  
end
endmodule

3. 使用assign语句进行连续赋值

verilog复制代码
module continuous_assignment(  
    input wire a,  
    input wire b,  
    output reg y  
);  
// 使用assign进行连续赋值(注意:这里通常使用wire而不是reg)  
assign y = a & b; // y是a和b的与操作结果  
// 但为了示例,我们仍然使用reg,但通常这不是最佳实践  
// 在实际硬件中,连续赋值应使用wire  
endmodule

4. 使用控制流语句(如if-else

verilog复制代码
module if_else_behavior(  
    input wire [1:0] selector,  
    input wire [7:0] a, b, c,  
    output reg [7:0] out  
);  
always @(*) begin // 使用@(*)表示这是一个组合逻辑块  
case (selector)  
    2'b00: out = a;  
    2'b01: out = b;  
    2'b10: out = c;  
    default: out = 8'hFF; // 默认为全1(即255)  
endcase
end
endmodule

以上示例展示了Verilog中行为建模的基本概念和用法。在实际的硬件设计和仿真中,您可能会根据具体需求使用这些概念的组合。



相关文章
|
存储 开发工具 异构计算
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(下)
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑
722 0
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(下)
|
23天前
|
C语言
【学习笔记】Verilog之四:结构建模方法
Verilog 提供两种建模方法:行为建模和结构建模。行为建模专注于电路功能描述,类似高级编程语言,不涉及底层实现。数据流建模使用 `assign` 进行连续赋值,适合描述组合逻辑。线网可以在声明时直接赋值。顺序行为建模通过 `initial` 和 `always` 语句实现,`initial` 用于一次性初始化,`always` 则用于响应特定事件的重复执行。时序控制包括时延和事件控制,用于精确控制电路行为。阻塞和非阻塞赋值决定了语句执行的顺序和并发性。过程性连续赋值如 `assign-deassign` 和 `force-release` 提供了更多灵活性。
|
23天前
|
监控 算法 数据处理
【学习笔记】Verilog之五:任务、函数及其他
在Verilog HDL中,任务(tasks)和函数(functions)增强了代码的可读性和重用性。任务是仅在仿真环境中使用的可封装代码,不可综合为硬件电路。任务定义包括标识符、输入/输出声明和实现代码,调用时需提供参数列表。函数与任务类似,但返回单个值,不包含时序控制,并且可以互相调用。函数定义包括返回值范围、输入声明、变量声明和执行代码。系统任务和函数如 `$display` 和 `$fopen` 提供了显示信息、文件I/O等便利功能,用于仿真控制和调试。禁止语句和命名事件则帮助控制代码执行和事件同步。
|
芯片 异构计算
第三章 硬件描述语言verilog(三)功能描述-时序逻辑
第三章 硬件描述语言verilog(三)功能描述-时序逻辑
207 0
第三章 硬件描述语言verilog(三)功能描述-时序逻辑
|
存储 程序员 开发工具
第三章 硬件描述语言verilog(一)
第三章 硬件描述语言verilog(一)
318 0
第三章 硬件描述语言verilog(一)
C#面向对象程序设计课程实验五:实验名称:C#面向对象技术
C#面向对象程序设计课程实验五:实验名称:C#面向对象技术
C#面向对象程序设计课程实验五:实验名称:C#面向对象技术
|
安全 前端开发 测试技术
SystemVerilog学习-01-系统验证概述(一)
SystemVerilog学习-01-系统验证概述
257 0
SystemVerilog学习-01-系统验证概述(一)
|
算法 芯片 计算机视觉
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(上)
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑
679 0
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(上)
|
异构计算
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(中)
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑
238 0
第三章 硬件描述语言verilog(二) 功能描述-组合逻辑(中)
|
存储 数据格式 开发者
E906的编程模型|学习笔记
快速学习 E906的编程模型
148 0
E906的编程模型|学习笔记