Menu Close

Verilog always过程

上节内容从总体上讲解了顺序语句的特点,always过程,函数 function,任务task等在循序语句描述中又各有特点,本节内容以always过程为主进行详细讲解。

  1. always顺序语句结构

always 引导的顺序语句,又称为always 过程语句。一般由begin…end包起来的一条或多条语句组成的。多条语句之间是按顺序执行的。

其定义的格式如下:

always@(...)
begin
    ...
end

由关键字always@引导,后跟括号(…)用来描述always块启动,所需激励的变量。例如:

always@(a,b)
begin
    c = a & b;
end

其中a , b , c是always块内需要使用的变量,当括号()内的变量任何一个发生变化,always内的过程语句就会执行一次。因此这些变量又称为敏感量。由这些敏感量组成了always需要的所有变化检测量表,称为敏感表。如:(a,b)就是敏感表。只要敏感表中任何一个变量的值有变化,always过程就会启动。

always块是由begin…end隔离的,当只有一条语句时,begin和end也可以省略。如:

always@(a,b)
    c = a & b;
// 或者用下面的格式
always@(a or b)
    c = a & b;

在Verilog 2001的语法中,也允许不指定敏感量,由编译系统根据always过程使用的变量自动推断。如:

always@(*) //不具体指定敏感量,由*替代敏感量,由编译系统推断。
    c = a & b;
一般系统分析always内行为确定敏感表的组成,如本例经过分析后的敏感表为(a,b).

always@(*) //不具体指定敏感量,由*替代敏感量,由编译系统推断。  
    c <= a & b;
一般系统分析always内行为确定敏感表的组成,如本例经过分析后的敏感表为(a,b,c).

可见不同的赋值语句,(*)所对应的敏感表也是不同的。

2. always 过程内语句的特点

  • always过程内的语句只能对reg型变量赋值

always过程内的语句只能对reg型变量赋值,不能对wire变量赋值,也不能使用assign赋值语句。上节内容已经介绍,过程之间不能相互嵌套。

  • always过程内的语句是顺序执行的

always 过程内的语句是顺序执行,因此对过程内的语句书写顺序是有要求的。见上节内容“Verilog顺序语句”

3. assign always 比较

  • 对敏感量的处理

assign 是连续赋值语句,对赋值语句右边表达式的变量实时监测,一旦变量的值变化即评估表达式的值,并进行赋值。而always是对敏感表中的敏感量实时监测,一旦敏感量的值发生变化,即启动always过程。从这一点上看,二者是一致的,可以认为assign 赋值语句就是缩小版的always过程。但对于非阻塞赋值语句,always@(*)的敏感表也包含被赋值变量,因此可以引起always过程的重入。

例:

wire [3:0]  a, b, c;
assign c = a & b;

wire [3:0] a, b;
reg  [3:0] c;
always@(*)
    c = a & b;

上例中,除了在assign赋值语句中要求c是wire 型变量,always要求reg 型变量外,其结果可以认为是相同的。

 

  • always过程可以处理更复杂的过程

    1. always过程内允许同一个变量多次赋值,而且语句执行具有顺序性。
    2. 有更复杂的判断即分支语句可以在always过程中使用,如if …else if …else(见分支选择语句),case… endcase(见分支选择语句),?语句等。而assign 赋值语句只能使用较简单的问号(?)语句(见分支选择语句)。
    3. always过程内可以同时处理多个if、case块 ,if, case 等可以相互嵌套。可给多个变量赋值,同一个变量可以多次赋值。
    4. always过程支持时钟边沿激励(如 posedgenegedge),assign赋值语句不支持边沿检测激励。

例 1: 十进制加1计数器

module dec_count
(
    input        clk,
    input        reset,
 
    output [3:0] count
);
 
reg  [3:0] count_r;
 
assign count = count_r;
 
always@ (posedge clk or posedge reset)//clk信号的上升沿成为过程的检测对象。
if(reset)
    count_r = 0;
else
begin
    if(count_r == 9)
        count_r = 0;
    else
        count_r = count_r + 1;
end
 
endmodule

 

Verilog 2001版本中的语法允许在 module端口中直接定义寄存器型输出端口,因此也可以改成下面的写法。

module dec_count
(
    input            clk,
    input            reset,
 
    output reg [3:0] count
);
 
always@ (posedge clk or posedge reset)
if(reset)
    count = 0;
else
begin
    if(count == 9)
        count = 0;
    else
        count = count + 1;
end
 
endmodule

 

4.always过程语句中register的模型

  • 组合逻辑模型

由简单的赋值语句组成always过程是组合逻辑过程,此时reg类型起到的作用与wire类型变量类似。如: always@(*) c=a&b;

例2:3-8译码器:

module dec3to8n
(
    input      [2:0] in,
    output reg [7:0] nout
);
 
always@(in)
case (in)
0: nout = 8'b1111_1110;
1: nout = 8'b1111_1101;
2: nout = 8'b1111_1011;
3: nout = 8'b1111_0111;
4: nout = 8'b1110_1111;
5: nout = 8'b1101_1111;
6: nout = 8'b1011_1111;
default: nout = 8'b0111_1111;
endcase
 
endmodule

 

例2中虽然在always过程中使用case语句,但依然是组合逻辑。

正逻辑的3/8译码器的程序如下:

module dec3to8
(
    input      [2:0] in,
    output reg [7:0] out
);
 
always@(in)
case (in)
0: out = 8'b0000_0001;
1: out = 8'b0000_0010;
2: out = 8'b0000_0100;
3: out = 8'b0000_1000;
4: out = 8'b0001_0000;
5: out = 8'b0010_0000;
6: out = 8'b1000_0000;
default: out = 8'b1000_00000;
endcase
 
endmodule

 

  • 锁存器模型

由于在顺序语句的 reg类型数据,在条件满足时更新数据,在条件撤销后具有保持原数据的能力,因此很容易描述锁存器。锁存器的用途非常广泛,著名的单片机芯片MCS8051的P0口就是利用锁存器实现地址/数据复用。

例3:锁存器(Latch)

module  ALE
(
    input            ALE,
    input      [7:0] P0,
    input            rd,
 
    output reg [7:0] addr,
    output reg [7:0] data   
);

reg  [7:0] data_tmp;
 
always @(ALE,P0)
begin
    if(ALE)
        addr = P0;
    else
        data_tmp = P0;
end
 

always@( posedge rd)
data<=data_tmp;
endmodule

 

上面的程序描述的功能是,在ALE为高电平时,addr(地址)随P0变化而改变,在ALE的下降沿时,将P0的值锁存在地址线(addr)上。在ALE为低电平时,数据(data)随P0的值而改变,在ALE的上升沿前通过读信号的上升沿将数据捕获,这是FPGA与单片机8051或早期Intel CPU 8088的接口设计。

  • 触发器模型

在时钟边沿的驱动下,寄存器的模型一般都等效为触发器,可建模为D边沿触发器,JK边沿触发器等。由于D 触发器的模型较为简单,因此一般都是以D边沿触发器作为参考模型。如:例1就可以以D触发器作为参考模型进行分析。

5. always 时钟模型

在always模型中还有一种简单的模型,该模型不需要敏感表,多由于生成仿真用的时钟。如:

          always   #5 clk=~clk;

由于该过程没有敏感量,默认为不需要敏感量的变化激励就可以运行,因此一定要有延迟语句保证不会陷入0延迟的死循环中,这一点与forever的行为相似。

          如 always clk=~clk; 该语句是错误的,要么编译语法错误,要么引起仿真死循环,仿真不出任何波形等一系列难以理解的现象。当然正确使用该语句,还需要将变量正确初始化。

真确写法如下:

 

`timescale       1 ns /1 ps

module tb();

reg clk = 0;   //声明时初始化,或在initial 过程中初始化

always  #5 clk = ~clk;

endmodule

由于在综合时会自动忽略延迟,因此”always  #5 clk=~clk;“语句,一定是不可综合的语句。

 

 

更多的模型建模内容请参照后续文章“阻塞与非阻塞赋值语句”的相关文章.

对应视频:

Posted in FPGA, FPGA 教材教案, Verilog, Verilog

4 Comments

  1. wangff

    老师好,在练习时我遇到一个问题,请老师解答:
    1、always(posedge clk or posedge reset) 与 always@(posedge clk , posedge areset),这两种表达方式的效果相同吗? “or”与“,”的意义是一样的吗?

  2. 8班王飞飞

    老师好,在学习中遇到一点疑问,请老师解答:
    1、always @(*) 与 always @ * ,这两种表达方式都可以吗?表达的含义一样吗?

发表回复

相关链接