Verilog 学习笔记(4)——语句块,多路分支语句、循环语句、连续赋值语句

 本章主要讲解Verilog语言中的语句部分。Verilog 语句块主要包括顺序块和并行块。语句类型有多路分支语句(case语句、casex/casez 语句)、循环语句(while 循环、for 循环、repeat 循环、forever 循环)、过程连续赋值语句(assign, deassign、force, release)等。


4.1 Verilog 语句块

Verilog 语句块提供了将两条或更多条语句组成语法结构上相当于一条一句的机制。主要包括两种类型:顺序块和并行块。

顺序块

顺序块用关键字 begin 和 end 来表示。
顺序块中的语句是一条条执行的。当然,非阻塞赋值除外。
顺序块中每条语句的时延总是与其前面语句执行的时间相关。
在本节之前的仿真中,initial 块中的阻塞赋值,都是顺序块的实例。

并行块

并行块有关键字 fork 和 join 来表示。
并行块中的语句是并行执行的,即便是阻塞形式的赋值。
并行块中每条语句的时延都是与块语句开始执行的时间相关。
顺序块与并行块的区别显而易见,下面用仿真说明。

仿真代码如下:

`timescale 1ns/1ns
 
module test ;
    reg [3:0]   ai_sequen, bi_sequen ;
    reg [3:0]   ai_paral,  bi_paral ;
    reg [3:0]   ai_nonblk, bi_nonblk ;
 
 //============================================================//
    //(1)Sequence block
    initial begin
        #5 ai_sequen         = 4'd5 ;    //at 5ns
        #5 bi_sequen         = 4'd8 ;    //at 10ns
    end
    //(2)fork block
    initial fork
        #5 ai_paral          = 4'd5 ;    //at 5ns
        #5 bi_paral          = 4'd8 ;    //at 5ns
    join
    //(3)non-block block
    initial fork
        #5 ai_nonblk         <= 4'd5 ;    //at 5ns
        #5 bi_nonblk         <= 4'd8 ;    //at 5ns
    join
 
endmodule

仿真结果如下:
在这里插入图片描述

如图所示,顺序块顺序执行,第 10ns 时,信号 bi_sequen 才赋值为 8。
而并行块,ai_paral 与 bi_paral 的赋值是同时执行的,所以均在 5ns 时被赋值。
而非阻塞赋值,也能达到和并行块同等的赋值效果。

嵌套块

顺序块和并行块还可以嵌套使用。

仿真代码如下:

`timescale      1ns/1ns
 
module test ;
 
    reg [3:0]   ai_sequen2, bi_sequen2 ;
    reg [3:0]   ai_paral2,  bi_paral2 ;
    initial begin
        ai_sequen2         = 4'd5 ;    //at 0ns
        fork
            #10 ai_paral2          = 4'd5 ;    //at 10ns
            #15 bi_paral2          = 4'd8 ;    //at 15ns
        join
        #20 bi_sequen2      = 4'd8 ;    //at 35ns
    end
 
endmodule

仿真结果如下:
在这里插入图片描述

并行块语句块内是并行执行,所以信号 ai_paral2 和信号 bi_paral2 分别在 10ns, 15ns 时被赋值。而并行块中最长的执行时间为 15ns,所以顺序块中的信号 bi_sequen2 在 35ns 时被赋值。

命名块

我们可以给块语句结构命名。
命名的块中可以声明局部变量,通过层次名引用的方法对变量进行访问。

仿真代码如下:

`timescale 1ns/1ns
 
module test;
 
    initial begin: csdn  //命名模块名字为csdn,分号不能少
        integer    i ;       //此变量可以通过test.csdn.i 被其他模块使用
        i = 0 ;
        forever begin
            #10 i = i + 10 ;      
        end
    end
 
    reg stop_flag ;
    initial stop_flag = 1'b0 ;
    always begin : detect_stop
        if ( test.csdn.i == 100) begin //i累加10次,即100ns时停止仿真
            $display("Now you can stop the simulation!!!");
            stop_flag = 1'b1 ;
        end
        #10 ;
    end
 
endmodule

仿真结果如下:
在这里插入图片描述
命名的块也可以被禁用,用关键字 disable 来表示。
disable 可以终止命名块的执行,可以用来从循环中退出、处理错误等。
与 C 语言中 break 类似,但是 break 只能退出当前所在循环,而 disable 可以禁用设计中任何一个命名的块。

仿真代码如下:

`timescale 1ns/1ns
 
module test;
 
    initial begin: csdn_d //命名模块名字为csdn_d
        integer    i_d ;
        i_d = 0 ;
        while(i_d<=100) begin: csdn_d2
            # 10 ;
            if (i_d >= 50) begin       //累加5次停止累加
                disable csdn_d3.clk_gen ;//stop 外部block: clk_gen
                disable csdn_d2 ;       //stop 当前block: csdn_d2
            end
            i_d = i_d + 10 ;
        end
    end
 
    reg clk ;
    initial begin: csdn_d3
        while (1) begin: clk_gen  //时钟产生模块
            clk=1 ;      #10 ;
            clk=0 ;      #10 ;
        end
    end
 
endmodule

仿真结果如下:
在这里插入图片描述
由图可知,信号 i_d 累加到 50 以后,便不再累加,以后 clk 时钟也不再产生。
可见,disable 退出了当前的 while 块。需要说明的是,disable 在 always 或 forever 块中使用时只能退出当前回合,下一次语句还是会在 always 或 forever 中执行。因为 always 块和 forever 块是一直执行的,此时的 disable 有点类似 C 语言中的 continue 功能。


# 4.2 Verilog 条件语句 ## if 语句 条件(if)语句用于控制执行语句要根据条件判断来确定是否执行。 条件语句用关键字 if 和 else 来声明,条件表达式必须在圆括号中。 条件语句使用结构说明如下:
if (condition1)      	    true_statement1 ;
else if (condition2)        true_statement2 ;
else if (condition3)        true_statement3 ;
else                        default_statement ;
  • if 语句执行时,如果 condition1 为真,则执行 true_statement1 ;如果 condition1 为假,condition2 为真,则执行 true_statement2;依次类推。
  • else if 与 else 结构可以省略,即可以只有一个 if 条件判断和一组执行语句 ture_statement1 就可以构成一个执行过程。
  • else if 可以叠加多个,不仅限于 1 或 2 个。
  • ture_statement1 等执行语句可以是一条语句,也可以是多条。如果是多条执行语句,则需要用 begin 与 end 关键字进行说明。

下面代码实现了一个 4 路选择器的功能。

module mux4to1(
    input [1:0]     sel ,
    input [1:0]     p0 ,
    input [1:0]     p1 ,
    input [1:0]     p2 ,
    input [1:0]     p3 ,
    output [1:0]    sout);

    reg [1:0]     sout_t ;

    always @(*) begin
        if (sel == 2'b00)
            sout_t = p0 ;
        else if (sel == 2'b01)
            sout_t = p1 ;
        else if (sel == 2'b10)
            sout_t = p2 ;
        else
            sout_t = p3 ;
    end
    assign sout = sout_t ;
 
endmodule

仿真结果如下。由图可知,输出信号与选择信号、输入信号的状态是相匹配的。
在这里插入图片描述
事例中 if 条件每次执行的语句只有一条,没有使用 begin 与 end 关键字。但如果是 if-if-else 的形式,即便执行语句只有一条,不使用 begin 与 end 关键字也会引起歧义。
例如下面代码,虽然格式上加以区分,但是 else 对应哪一个 if 条件,是有歧义的。

if(en)
    if(sel == 2'b1)
        sout = p1s ;
    else
        sout = p0 ;

当然,编译器一般按照就近原则,使 else 与最近的一个 if(例子中第二个 if)相对应。但显然这样的写法是不规范且不安全的。
所以条件语句中加入 begin 与 and 关键字就是一个很好的习惯。
例如上述代码稍作修改,就不会再有书写上的歧义。

if(en) begin
    if(sel == 2'b1) begin
        sout = p1s ;
    end
    else begin
        sout = p0 ;
    end
end

4.3 Verilog 多路分支语句

case语句

case 语句是一种多路条件分支的形式,可以解决 if 语句中有多个条件选项时使用不方便的问题。case 语句格式如下:

case(case_expr)
    condition1     :             true_statement1 ;
    condition2     :             true_statement2 ;
    ……
    default        :             default_statement ;
endcase
  • case 语句执行时,如果 condition1 为真,则执行 true_statement1 ; 如果 condition1 为假,condition2 为真,则执行 true_statement2;依次类推。如果各个 condition 都不为真,则执行 default_statement 语句。
  • default 语句是可选的,且在一个 case 语句中不能有多个 default 语句。
  • 条件选项可以有多个,不仅限于 condition1、condition2 等,而且这些条件选项不要求互斥。虽然这些条件选项是并发比较的,但执行效果是谁在前且条件为真谁被执行。
  • ture_statement1 等执行语句可以是一条语句,也可以是多条。如果是多条执行语句,则需要用 begin 与 end 关键字进行说明。

case 语句支持嵌套使用。
下面用 case 语句代替 if 语句实现了一个 4 路选择器的功能。仿真结果与 testbench 可参考条件语句一章,两者完全一致。

module mux4to1(
    input [1:0]     sel ,
    input [1:0]     p0 ,
    input [1:0]     p1 ,
    input [1:0]     p2 ,
    input [1:0]     p3 ,
    output [1:0]    sout);
 
    reg [1:0]     sout_t ;
    always @(*)
        case(sel)
            2'b00:   begin      
                    sout_t = p0 ;
                end
            2'b01:       sout_t = p1 ;
            2'b10:       sout_t = p2 ;
            default:     sout_t = p3 ;
        endcase
    assign sout = sout_t ;
 
endmodule

case 语句中的条件选项表单式不必都是常量,也可以是 x 值或 z 值。
当多个条件选项下需要执行相同的语句时,多个条件选项可以用逗号分开,放在同一个语句块的候选项中。
但是 case 语句中的 x 或 z 的比较逻辑是不可综合的,所以一般不建议在 case 语句中使用 x 或 z 作为比较值。
例如,对 4 路选择器的 case 语句进行扩展,举例如下:

case(sel)
    2'b00:   sout_t = p0 ;
    2'b01:   sout_t = p1 ;
    2'b10:   sout_t = p2 ;
    2'b11:   sout_t = p3 ;
    2'bx0, 2'bx1, 2'bxz, 2'bxx, 2'b0x, 2'b1x, 2'bzx :
        sout_t = 2'bxx ;
    2'bz0, 2'bz1, 2'bzz, 2'b0z, 2'b1z :
        sout_t = 2'bzz ;
    default:  $display("Unexpected input control!!!");
endcase

casex/casez 语句

casex、 casez 语句是 case 语句的变形,用来表示条件选项中的无关项。
casex 用 “x” 来表示无关值,casez 用问号 “?” 来表示无关值。
两者的实现的功能是完全一致的,语法与 case 语句也完全一致。
但是 casex、casez 一般是不可综合的,多用于仿真
例如用 casez 语句来实现一个 4bit 控制端的 4 路选择选择器。

module mux4to1(
    input [3:0]     sel ,
    input [1:0]     p0 ,
    input [1:0]     p1 ,
    input [1:0]     p2 ,
    input [1:0]     p3 ,
    output [1:0]    sout);
 
    reg [1:0]     sout_t ;
    always @(*)
        casez(sel)
            4'b???1:     sout_t = p0 ;
            4'b??1?:     sout_t = p1 ;
            4'b?1??:     sout_t = p2 ;
            4'b1???:     sout_t = p3 ;  
        default:         sout_t = 2'b0 ;
    endcase
    assign      sout = sout_t ;
 
endmodule

4.4 Verilog 循环语句

Verilog 循环语句有 4 种类型,分别是 while,for,repeat,和 forever 循环。循环语句只能在 always 或 initial 块中使用,但可以包含延迟表达式。

while 循环

while 循环语法格式如下:

while (condition) begin
    …
end

while 循环中止条件为 condition 为假。
如果开始执行到 while 循环时 condition 已经为假,那么循环语句一次也不会执行。
当然,执行语句只有一条时,关键字 begin 与 end 可以省略。

下面代码执行时,counter 执行了 11 次。

`timescale 1ns/1ns
 
module test ;
 
    reg [3:0]    counter ;
    initial begin
        counter = 'b0 ;
        while (counter <= 10) begin
            #10 ;
            counter = counter + 1'b1 ;
        end
    end
 
   //stop the simulation
    always begin
        #10 ;  if ($time >= 1000) $finish ;
    end
 
endmodule

仿真结果如下:
在这里插入图片描述

for 循环

for 循环语法格式如下:

for(initial_assignment; condition ; step_assignment)  begin
    …
end
  • initial_assignment 为初始条件。
  • condition 为终止条件,condition 为假时,立即跳出循环。
  • step_assignment 为改变控制变量的过程赋值语句,通常为增加或减少循环变量计数。

一般来说,因为初始条件和自加操作等过程都已经包含在 for 循环中,所以 for 循环写法比 while 更为紧凑,但也不是所有的情况下都能使用 for 循环来代替 while 循环。

下面 for 循环的例子,实现了与 while 循环中例子一样的效果。需要注意的是,i = i + 1 不能像 C 语言那样写成 i++ 的形式,i = i -1 也不能写成 i – 的形式

// for 循环语句
integer      i ;
reg [3:0]    counter2 ;
initial begin
    counter2 = 'b0 ;
    for (i=0; i<=10; i=i+1) begin
        #10 ;
        counter2 = counter2 + 1'b1 ;
    end
end

repeat 循环

repeat 循环语法格式如下:

repeat (loop_times) begin
    …
end

repeat 的功能是执行固定次数的循环,它不能像 while 循环那样用一个逻辑表达式来确定循环是否继续执行。repeat 循环的次数必须是一个常量、变量或信号。如果循环次数是变量信号,则循环次数是开始执行 repeat 循环时变量信号的值。即便执行期间,循环次数代表的变量信号值发生了变化,repeat 执行次数也不会改变。

下面 repeat 循环例子,实现了与 while 循环中的例子一样的效果。

// repeat 循环语句
reg [3:0]    counter3 ;
initial begin
    counter3 = 'b0 ;
    repeat (11) begin  //重复11次
        #10 ;
        counter3 = counter3 + 1'b1 ;
    end
end

下面 repeat 循环例子,实现了连续存储 8 个数据的功能:

always @(posedge clk or negedge rstn) begin
    j = 0  ;
    if (!rstn) begin
        repeat (8) begin
            buffer[j]   <= 'b0 ;      //没有延迟的赋值,即同时赋值为0
            j = j + 1 ;
        end
    end
    else if (enable) begin
        repeat (8) begin
            @(posedge clk) buffer[j]    <= counter3 ;       //在下一个clk的上升沿赋值
            j = j + 1 ;
        end
     end
end

仿真结果如下图。
在这里插入图片描述

由图可知,rstn 拉高时,buffer 的 8 个向量同时赋值为 0。
第二个时钟周期后,buffer 依次被 counter3 赋值,实现了连续存储 8 个数据的功能。

forever 循环

forever 循环语法格式如下:

forever begin
    …
end
  • forever 语句表示永久循环,不包含任何条件表达式,一旦执行便无限的执行下去,系统函数 $finish 可退出 forever。
  • forever 相当于 while(1) 。
  • 通常,forever 循环是和时序控制结构配合使用的。

例如,使用 forever 语句产生一个时钟:

reg          clk ;
initial begin
    clk       = 0 ;
    forever begin
        clk = ~clk ;
        #5 ;
    end
end

例如,使用 forever 语句实现一个时钟边沿控制的寄存器间数据传输功能:

reg    clk ;
reg    data_in, data_temp ;
initial begin
    forever @(posedge clk)      data_temp = data_in ;
end

4.5 Verilog 过程连续赋值

过程连续赋值是过程赋值的一种。这种赋值语句能够替换其他所有 wire 或 reg 的赋值,改写了 wire 或 reg 型变量的当前值。
与过程赋值不同的是,过程连续赋值的表达式能被连续的驱动到 wire 或 reg 型变量中,即过程连续赋值发生作用时,右端表达式中任意操作数的变化都会引起过程连续赋值语句的重新执行。

过程连续性赋值主要有 2 种,assign-deassign 和 force-release。

assign, deassign

assign(过程赋值操作)与 deassign (取消过程赋值操作)表示第一类过程连续赋值语句。赋值对象只能是寄存器或寄存器组,而不能是 wire 型变量。
赋值过程中对寄存器连续赋值,寄存器中的值被保留直到被重新赋值。
例如,一个带复位端的 D 触发器可以用下面代码描述:

module dff_normal(
    input       rstn,
    input       clk,
    input       D,
    output reg  Q
 );

    always @(posedge clk or negedge rstn) begin
        if(!rstn) begin   			//Q = 0 after reset effective
            Q <= 1'b0 ;
        end
        else begin
            Q <= D ;       			//Q = D at posedge of clock
        end
    end

endmodule  

下面,用 assign 与 deassign 改写,完成相同的功能。
即在复位信号为 0 时,Q 端被 assign 语句赋值,始终输出为 0。
复位信号为 1 时,Q 端被 deassign 语句取消赋值,在时钟上升沿被重新赋值。

module dff_assign(
    input       rstn,
    input       clk,
    input       D,
    output reg  Q
 );
 
    always @(posedge clk) begin
        Q <= D ;       //Q = D at posedge of clock
    end
 
    always @(negedge rstn) begin
        if(!rstn) begin
            assign Q = 1'b0 ; 	//change Q value when reset effective
        end
        else begin        		//cancel the Q value overlay,
            deassign Q ; 		 //and Q remains 0-value until the coming of clock posedge
        end
    end
 
endmodule

force, release

force (强制赋值操作)与 release(取消强制赋值)表示第二类过程连续赋值语句。
使用方法和效果,和 assign 与 deassign 类似,但赋值对象可以是 reg 型变量,也可以是 wire 型变量
因为是无条件强制赋值,一般多用于交互式调试过程,不要在设计模块中使用。
当 force 作用在寄存器上时,寄存器当前值被覆盖;release 时该寄存器值将继续保留强制赋值时的值。之后,该寄存器的值可以被原有的过程赋值语句改变。
当 force 作用在线网上时,线网值也会被强制赋值。但是,一旦 release 该线网型变量,其值马上变为原有的驱动值。
为直观的观察两种类型变量强制赋值的区别,利用第一节中的计数器 counter10 作为设计模块,testbench 设计如下。

`timescale 1ns/1ns
 
module test ;
    reg          rstn ;
    reg          clk ;
    reg [3:0]    cnt ;
    wire         cout ;
 
    counter10     u_counter (
        .rstn    (rstn),
        .clk     (clk),
        .cnt     (cnt),
        .cout    (cout));
 
    initial begin
        clk       = 0 ;
        rstn      = 0 ;
        #10 ;
        rstn      = 1'b1 ;
        wait (test.u_counter.cnt_temp == 4'd4) ;
        @(negedge clk) ;
        force     test.u_counter.cnt_temp = 4'd6 ;
        force     test.u_counter.cout     = 1'b1 ;
        #40 ;
        @(negedge clk) ;
        release   test.u_counter.cnt_temp ;
        release   test.u_counter.cout ;
    end
 
    initial begin
        clk = 0 ;
        forever #10 clk = ~ clk ;
    end
 
    //finish the simulation
    always begin
        #1000;
        if ($time >= 1000) $finish ;
    end
 
endmodule 

仿真结果如下。
在这里插入图片描述

由图可知,在 cnt_temp 等于 4 时(80ns), cnt_temp 被强制赋值为 6,cout 被强制赋值为 1。

release 时(120ns), cnt_temp 为寄存器类型,仍然保持原有值不变,直到时钟上升沿对其进行加法赋值操作,值才变为 7 。

而 120ns 时,由于 cout 是线网型变量,其值不能保存。原码 counter10 模型中存在驱动语句: assign cout = (cnt_temp==4’d9) ,所以 cout 值变为 0 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值