1.过程语句

  • Verilog中有两种结构化过程语句:initial和always语句,是行为建模的两种基本语句,所有的行为语句只能出现在这两种结构化过程语句里。

  • 每个initial语句和always语句代表一个独立的执行过程(或过程块)。

  • 一个模块可以包含多条always语句和多条initial语句。每条语句启动一个单独的控制流。每条语句都在0时刻开始并行执行。
  • 这两种语句不能嵌套使用。Verilog本质上是并发的,这些块并发执行,而不是顺序执行。

initial语句

  • initial语句指定的内容只执行一次 ,initial语句主要用于仿真测试,不能进行逻辑综合。

    initial语句的格式如下:

    1
    2
    3
    4
    5
    6
    initial
    begin
    语句1;
    ......
    语句n;
    end

    举例说明:memory存储器初始化

    1
    2
    3
    4
    5
    initial
    begin
    for(index = 0;index < size;index = index+1)
    memory[index] = 0;
    end
    • 在这个例子中用initial语句在仿真开始时对各变量进行初始化,注意这个初始化过程不需要任何仿真时问,即在时间内,便可以完成存储器的初始化工作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    module stimulus;
    reg x, y, a, b, m;
    initial
    m=1’b0;//一条语句,无需begin-end
    initial
    begin //多条语句,需begin-end
    #5 a=1’b1;
    #25 b=1’b0;
    end
    initial
    begin
    #10 x=1’b0;
    #25 y=1’b1;
    end
    endmodule

    • 下面是用modelsim仿真出来的结果:

  • 一个模块中若有多个initial块,则它们同时并行执行。

    块内若有多条语句,需要用begin和end将它们组合,一条语句则不需要。

    initial块常用于测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。

always语句

  • always块内的语句是不断重复执行的,在仿真和逻辑综合中均可使用。

    always块的语句是否执行,要看它的触发条件是否满足。如满足则运行过程块一次;如不断满足,则不断地循环执行。

    声明格式如下:

    always <时序控制> <语句>

  • always语句由于其不断活动的特性,只有和一定的时序控制结合在一起才有用。

    举例:

    always clk = ~clk; //这是一个死循环

    但如果加上时序控制,以上这个always语句将变为一条非常有用的描述语句:

    always #half_period clk = ~clk;

    则生成了一个周期为2* half_period的无限延续的信号波形。当经过half_period时间单位时,时钟信号取反,在经过half_period时间单位,就再取反为一个周期。

  • 敏感信号表达式:

    always语句的时序控制可以使用事件表达式或敏感信号列表,即当表达式中变量的值改变时,就会引发块内语句的执行。其形式为:

    1
    2
    3
    4
    5
    6
    always@(敏感信号表达式 )
    begin
    //过程赋值
    //if-else,case,casex,casez选择语句
    //task,function调用
    end

    敏感信号表达式中应列出影响块内取值的所有信号。

    always的时间控制可以是沿触发也可以是电平触发的,可以单个信号也可以多个信号,中间需要用关键字or连接。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //由多个电平触发的always块,只要a、b、c中任何一个发生变化,从高到低或从低到高都会执行一次过程块。 
    always@(a or b or c)
    begin
    …………..
    end
    //由两个沿触发的always只要其中一个沿出现,就立即执行一次过程块。
    always@(posedge clock or negedge reset)
    begin
    ………..
    end
    //posedge代表上升沿 negedge代表下降沿
  • Verilog中,用always块可以设计组合逻辑电路和时序电路。注意一些问题是:

    在赋值表达式右端参与赋值的所有信号都必须在always @(敏感电平列表)中列出;而且将块的所有输入都列入敏感表是很好的描述习惯。

    ​ always @ (a or b or c)

    ​ e = a & b & c;

  • 如果在赋值表达式右端引用了敏感信号列表中没有列出的信号,在综合时将会为没有列出的信号隐含地产生一个透明锁存器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    input a,b,c;
    output e,d;
    reg e,d;
    always@(a or b or c)
    begin
    e = a & b & d;
    d = e|c;
    end
    //d不在敏感信号列表中,d的变化e不会立即变化,直到a,b,c中的某一个变化。
  • always中if语句的判断表达式必须在敏感电平列表中列出。

    1
    2
    3
    4
    5
    6
    7
    always @ (a or b or sel) 
    begin
    if (sel)
    c = a;
    else
    c = b;
    end
  • Verilog中,用always块设计时序电路时,敏感列表中包括时钟信号和控制信号。

    always @ ( posedge clk or negedge clr)

  • 每一个always块最好只由一种类型的敏感信号触发,而不要将边沿敏感型和电平敏感型信号列在一起。

  • 如果组合逻辑块语句的输人变量很多,那么编写敏感列表会很烦琐并且容易出错。针对这种情况,Verilog提供另外两个特殊的符号:@*@(*),它们都表示对其后面语句块中所有输入变量的变化是敏感的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    always@(a or b or c or d or e or f or g or h or p or m)
    begin out1 = a ? b+c : d+e;
    out2 = f ? g+h:p+m;
    end
    always@( * )
    begin out1 = a ? b+c : d+e;
    out2 = f ? g+h:p+m;
    end

  • 边沿触发:

    ​ 在同步时序逻辑电路中,触发器状态的变化仅仅发生在时钟脉冲的上升沿或下降沿,Verilog HDL提供了posedge(上升沿)与negedge(下降沿)两个关键字来进行描述。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //例如:同步清零的时序逻辑
    always @( posedge clk )
    begin
    if (!reset)
    q = 0;
    else
    q <= d;
    end
    //例如:同步置位/清零的计数器
    module sync(out,d,load,clr,clk)
    input d,load,clk,clr;
    input[7:0] d;
    output[7:0]out;
    reg[7:0] out;
    always @ ( posedge clk ) //clk上升沿触发
    begin
    if ( !clr ) out <= 8’h00; //同步清0,低电平有效
       else if ( load ) out <= d; //同步置数
    else out <= out+1 //计数
    end
    endmodule
    //例如:异步清零:
    module async(d,clk,clr,q);
    input d,clk,clr;
    output q:
    reg q;
    always @ ( posedge clk or posedge clr)
    begin
    if ( clr )
    q <= 1’b0;
    else
    q <= d;
    end
    endmodule

  • 电平敏感时序控制:

    前面所讨论的事件控制都需要等待信号值的变化或者事件的触发,使用符号@和后面的敏感列表来表示。

    Verilog同时也允许使用另外一种形式表示的电平敏感时序控制(即后面的语句和语句块需要等待某个条件为真才能执行)。Verilog语言用关键字wait来表示等待电平敏感的条件为真。

    1
    2
    always 
    wait(count_enable) #20 count=count+1;
  • 多always语句块

    一个模块中可有多个always语句;

    每个always语句只要有相应的触发事件产生,对应的语句就执行;

    与各个always语句书写的前后顺序无关,它们之间是并行运行的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    module many_always(clk1,clk2,a,b,out1,out2,out3);
    input clk1,clk2;
    input a,b;
    output out1,out2,out3;
    wire clk1,clk2;
    wire a,b;
    reg out1,out2,out3;
    always@(posedge clk1) //当clk1的上升沿来时,令out1等于a和b的逻辑与。
    out1 <= a&b;
    always@(posedge clk1 or negedge clk2) //当clk1的上升沿或者clk2的下降沿来时,令out2等于a和b的逻辑或。
    out2 <= a|b;
    always@(a or b) //当a或b的值变化时,令out3等于a和b的算术和。
    out3 = a+b;
    endmodule
  • always和initial并存:

    在每一个模块(module)中,使用initial和always语句的次数是不受限制的。

    initial和always块不能相互嵌套。

    每个initial和always块的关系都是并行的,所有的initial语句和always语句都是从0时刻并行执行。

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module clk_gen(clk);
    output clk;
    parameter period =50,duty_cycle=50;
    initial
    clk = 1'b0;
    always
    #(period*duty_cycle/100) clk = ~clk;
    initial
    #100 $finish;
    endmodule

    /*运行结果为:
    时刻 | 执行时间
    0 clk=1'b0
    25 clk=1'b1;
    50 clk=1'b0;
    75 clk=1'b1;
    100 $finish
    */
  • 学习了Verilog语法中两种最重要的结构语句initial和always。 需要牢记的是:

    一个程序模块可以有多个initial和always过程块。

    每个initial和always说明语句在仿真的一开始便同时立即开始运行。

    initial语句在模块中只执行一次。

    always语句则是不断地活动着。直到仿真过程结束。

    always语句后跟着的过程块是否运行,则要看它的触发条件是否满足,如满足则运行过程块一次,再次满足则再运行一次,循环往复直至仿真过程结束。

    always的时间控制可以是沿触发也可以是电平触发的,可以单个信号也可以多个信号,中间需要用关键字or或“,”连接。

    沿触发的always块常常描述时序行为,如有限状态机。而电平触发的: always块常常用来描述组合逻辑的行为。

2.块语句

  • 在Verilog HDL描述逻辑功能中,当操作需要多条Verilog HDL语句才能描述时,这时就需要用块语句将多条Verilog HDL语句复合在一起。

begin—end 串行块

  • begin ……end之间可以添加多条语句,并且语句是按照出现的顺序执行的。

  • 如果语句前面有延时符号“#”,那么延时的长度相对于前一条语句而言的。
    由begin ……end构成的块语句形式为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        begin
    语句1;
    语句2;
    ......
    语句n;
    end
    begin:块名
    块内声明语句
    语句1;
    语句2;
    ......
    语句n;
    end
  • 其中可以在begin后声明该块的名字,一个标识名。块内声明语句可以是参数声明语句、reg型变量声明语句、integer型变量声明语句和real 变量声明语句。

  • 顺序块(串行块)有以下特点:
    (1)块内的语句是按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行。
    (2)每条语句的延迟时间是相对于前一条语句的仿真时间而言的。
    (3)直到最后一条语句执行完,程序流程控制才跳出该语句块。

fork—join并行快

  • fork—join之间可以添加多条语句,并且语句的关系是并行的,是同时执行的。

    如果语句前面有延时符号“#”,那么延时的长度是相对于fork ……join块开始时间而言的。即块内每条语句的延迟时间是相对于程序流程控制进入到块内的仿真时间的。

    当按时间时序排序在最后的语句执行完后或一个disable语句执行时,程序流程控制跳出该程序块。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    格式1如下:
    fork
    语句1;
    语句2;
    .......
    语句n;
    join
    格式2如下:
    fork :块名
    块内声明语句
    语句1;
    语句2;
    .......
    语句n;
    join
  • 块名即标识该块的一个名字,相当于一个标识符。

    块内说明语句可以是参数说明语句、reg型变量声明语句、integer型变量声明语句、real型变量声明语句、time型变量声明语句和事件(event)说明语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//使用 begin ……end顺序块:
reg[7:0] r;
begin
#50 r = ‘h35;
#50 r = ‘hE2;
#50 r = ‘h00;
#50 r = ‘hF7;
#50 ->end_wave;
end
//使用 fork ……join并行块:
reg[7:0] r;
fork
#50 r = ‘h35;
#100 r = ‘hE2;
#150 r = ‘h00;
#200 r = ‘hF7;
#250 ->end_wave;
join
//以上两个代码是等价的
  • 起始时间和结束时间

    在并行块和顺序块中都有一个起始时间和结束时间的概念。

    顺序块起始时间就是第一条语句开始被执行的时间,结束时间就是最后一条语句执行完的时问。

    而对于并行块来说,起始时间对于块内所有的语句是相同的,即程序流程控制进入该块的时间,其结束时间是按时间排序在最后的语句执行结束的时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
initial
fork
#10 a = 1;
#15 b = 1;
begin
#20 c = 1
#10 d = 1;
end
#25 e = 1;
join
/*
该程序运行的结果如下:
时刻 | 执行的语句
10 | a=1;
15 | b=1;
20 | c=1;
25 | e=1;
30 | d=1;
*/

3.赋值语句

  • 两种赋值方法:连续赋值(Continuous Assignment), 过程赋值(Procedural Assignment)。

    过程赋值:阻塞赋值(Blocking Assignment),非阻塞赋值(Nonblocking Assignment)。

连续赋值

  • 连续赋值

    连续赋值常用于数据流行为建模。

    连续赋值语句,位于过程块语句外,常以assign为关键字。

    它只能为线网型变量赋值,并且线网型变量也必须用连续赋值的方法赋值。

    注意:只有当变量声明为线网型变量后,才能使用连续赋值语句进行赋值。

  • 语句格式: assign 赋值目标线网变量 = 表达式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //第一种
    wire adder_out;
    assign adder_out = mult_out + out;
    //第二种
    wire adder_out = mult_out+out; 
     //隐含了连续赋值语句
    //第三种带函数调用的连续赋值语句:
      assign c = max( a,b );
    //调用了函数max,将函数返回值赋给c
  • 特点

    连续赋值语句中“=”的左边必须是线网型变量,右边可以是线网型、寄存器型变量或者是函数调用语句。

    连续赋值语属即刻赋值,即赋值号右边的运算值一旦变化,被赋值变量立刻随之变化。

    assign可以使用条件运算符进行条件判断后赋值。

    1
    2
    3
    4
    5
    6
    //例如:连续赋值方式描述一个比较器
    module compare2 ( equal,a,b );
    input [1:0] a,b;
    output equal;
    assign equal=(a==b)?10;
    endmodule

过程赋值

  • 过程赋值

    多用于对reg型变量进行赋值,这类型变量在被赋值后,其值保持不变,直到赋值进程又被触发,变量才被赋予新值。

    过程赋值主要出现在过程块always和initial语句内。

    分为阻塞赋值和非阻塞赋值两种,它们在功能和特点上有佷大不同。

  • 非阻塞赋值

    (1)非阻塞(Non_Blocking)赋值方式

    操作符: “<=”;非阻塞赋值符“<=”与小于等于符“<=”看起来是一样的,但意义完全不同。

    其基本语法格式如下:

    寄存器变量(reg) <= 表达式/变量;

    ​ 如 b <= a;

    非阻塞赋值在整个过程块结束后才完成赋值操作。即在语句块中,上面语句所赋的变量值不能立即就为下面的语句所用;

    在语句块中,连续的非阻塞赋值操作是同时完成的,即在同一个顺序块 中,非阻塞赋值表达式的书写顺序,不影响赋值的结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //连续的非阻塞赋值实例:
    module non_blocking(reg_c,reg_d,data,clk);
    output reg_c,reg_d;
    input clk,data;
    reg reg_c, reg_d;
    always @( posedge clk )
    begin
    reg_c <= data;
    reg_d <= reg_c;
    end
    endmodule

  • 在clk的上升沿,将data的值赋给reg_c,同时将reg_c原来的值(不是data)赋值给reg_d。即:上面语句所赋的变量值不能立即就为下面的语句所用,要等到过程块结束,同时并行赋值。

  • 阻塞赋值

    (2)阻塞(Blocking)赋值方式

     操作符:  “ = ” 
    

    基本语法格式如下:

             `寄存器变量(reg) = 表达式/变量;` 
                  如 `b = a;`
    

    阻塞赋值在该语句结束时就立即完成赋值操作,即b的值在该条语句结束后立刻改变。
    如果在一个块语句中有多条阻塞赋值语句,那么写在前面的赋值语句没有完成之前,后面的语句就不能被执行,仿佛被阻塞了(blocking)一样,因而被称为阻塞赋值。
    连续的阻塞赋值操作是顺序完成的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //例如:连续的阻塞赋值
    module blocking(reg_c,reg_d,data,clk);
    output reg_c,reg_d;
    input clk,data;
    reg reg_c, reg_d;
    always @( posedge clk )
    begin
    reg_c = data;
    reg_d = reg_c;
    end
    endmodule

  • 为了避免出错,在同一个always块内,最好不要将输出再作为输入使用,为了用阻塞赋值方式完成与上述非阻塞赋值同样的功能,可采用两个always块来实现,如下所示。

    在下面的例子中,两个always过程块是并发执行的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     module non_blocking(reg_c,reg_d,data,clk);
    output reg_c,reg_d;
    input clk,data;
    reg reg_c, reg_d;
    always @( posedge clk )
    begin
    reg_c = data;
    end

    always @( posedge clk )
    begin
    reg_d = reg_c;
    end
    endmodule
  • 总的来说,多条阻塞赋值语句是顺序执行的,而多条非阻塞语句是并行执行的.

  • 在使用always块描述组合逻辑(电平敏感)时使用阻塞赋值,在使用always块描述时序逻辑(边沿敏感)时使用非阻塞赋值。建立latch模型时,采用非阻塞赋值语句。在一个always块中同时有组合和时序逻辑时,采用非阻塞赋值语句。
  • 不要在同一个always块内同时使用阻塞赋值和非阻塞赋值。
  • 无论是使用阻塞赋值还是非阻塞赋值,不要在不同的always块内为同一个变量赋值。因为佷难保证不会引起赋值冲突。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//例子:在不同的always块为相同的变量赋值
module wrong_assign(out,a,b,sel,clk);
input a,b,sel,clk;
output out;
wire a,b,sel,clk;
reg out;
/*下面两个always块中都为out赋了值,
但似乎不会引起冲突 */
always @ (posedge clk)
if (sel == 1) out <= a;
always @ (posedge clk)
if (sel == 0) out <= b;//由于两个块同时执行,一个要更新数值,一个要维持不变,因而可能引起冲突。
endmodule

//上例的正确的写法为:
module correct_assign(out,a,b,sel,clk);
input a,b,sel,clk;
output out;
wire a,b,sel,clk;
reg out;
//在同一个always块内为同一个变量赋值
always @ (posedge clk)
begin
if ( sel== 1)
out<=a;
else
out<=b;
end
endmodule
  • 阻塞语句,如果没有写延迟时间看起来是在同一时刻运行,但实际上是有先后的,即在前面的先运行,然后再运行下面的语句,阻塞语句的次序与逻辑行为有很大的关系。
  • 而非阻塞的就不同了,在begin-end之间的所有非阻塞语句都在同一时刻被赋值,因此逻辑行为与非阻塞语句的次序就没有关系。在硬件实现时这两者有很大的不同。