Verilog | Verilog 基础语法(2)

今天继续学习Verilog的语法,参考菜鸟教程runoob.com。

assign 关键词

assign关键词用于连续赋值语句,连续赋值语句是Verilog数据流建模的基本语句,用于对wire型变量赋值。

格式为:

assign     LHS_target = RHS_expression  ;
LHS_target 必须是一个标量或者线型向量,而不能是寄存器类型。
RHS_expression 的类型没有要求,可以是标量或线型或存器向量,也可以是函数调用。
只要 RHS_expression 表达式的操作数有事件发生(值的变化)时,RHS_expression 就会立刻重新计算,同时赋值给 LHS_target。

就像是我们在观察一根线的输出一样。

也可以在wire型变量定义时直接定义连续赋值方式:

wire      A, B ;
wire      Cout = A & B ;

case:全加器

在数字逻辑中,我们可以推导出全加器的表达式为:

So = Ai ⊕ Bi ⊕ Ci ;
Co = AiBi + Ci(Ai+Bi)

可以使用如下的连续赋值语句实现全加器,这样,So和Co这两个线型就会实时输出结果:

module full_adder1(
    input    Ai, Bi, Ci,
    output   So, Co);


    assign So = Ai ^ Bi ^ Ci ;
    assign Co = (Ai & Bi) | (Ci & (Ai | Bi));
endmodule


或者使用 拼接符号{,} 以及加法运算,将Co和So连接起来,以便接收进位。
module full_adder1(
    input    Ai, Bi, Ci
    output   So, Co);


    assign {Co, So} = Ai + Bi + Ci ;
endmodule

时延

连续赋值延时语句中的延时,用于控制任意操作数发生变化到语句左端赋予新值之间的时间延时。

时延一般是不可综合的。

寄存器的时延也是可以控制的,这部分在时序控制里加以说明。

连续赋值时延一般可分为普通赋值时延、隐式时延、声明时延。

//普通时延,A&B计算结果延时10个时间单位赋值给Z
wire Z, A, B ;
assign #10    Z = A & B ;


//隐式时延,声明一个wire型变量时对其进行包含一定时延的连续赋值。
wire A, B;
wire #10        Z = A & B;


//声明时延,声明一个wire型变量是指定一个时延。因此对该变量所有的连续赋值都会被推迟到指定的时间。除非门级建模中,一般不推荐使用此类方法建模。
wire A, B;
wire #10 Z ;
assign           Z =A & B

在上述例子中,A 或 B 任意一个变量发生变化,那么在 Z 得到新的值之前,会有 10 个时间单位的时延。如果在这 10 个时间单位内,即在 Z 获取新的值之前,A 或 B 任意一个值又发生了变化,那么计算 Z 的新值时会取 A 或 B 当前的新值。所以称之为惯性时延,即信号脉冲宽度小于时延时,对输出没有影响。

因此仿真时,时延一定要合理设置,防止某些信号不能进行有效的延迟。

过程结构

过程结构语句有两种,initial语句与always语句,它们是行为级建模的两种基本语句。一个模块中可以包含多个initial语句和always语句,但两种语句不能嵌套使用。这些语句在模块间并行执行,与其在模块的前后顺序没有关系。但是 initial 语句或 always 语句内部可以理解为是顺序执行的(非阻塞赋值除外)。每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始。

initial语句

initial语句从0时刻开始执行,只执行一次,多个initial块之间是相互独立的。如果 initial 块内包含多个语句,需要使用关键字 begin 和 end 组成一个块语句。如果 initial 块内只要一条语句,关键字 begin 和 end 可使用也可不使用。initial 理论上来讲是不可综合的,多用于初始化、信号检测等,所以在编写 Verilog 代码时,应该避免使用 initial 语句进行复杂的初始化操作,因为这可能会导致综合结果不可预测。

always语句

always语句是重复执行的,相当于while(1),例如我们可以使用always语句产生仿真时钟:

// 100MHZ 时钟源
`timescale 1ns/1ns


module test ;


    parameter CLK_FREQ   = 100 ; //100MHz
    parameter CLK_CYCLE  = 1e9 / (CLK_FREQ * 1e6) ;   //switch to ns


    reg  clk ;
    initial      clk = 1'b0 ;      //clk is initialized to "0"
    always     # (CLK_CYCLE/2) clk = ~clk ;       //generating a real clock by reversing


    always begin
        #10;
        if ($time >= 1000) begin
            $finish ;
        end
    end


endmodule

过程赋值

过程性赋值是在 initial 或 always 语句块里的赋值,赋值对象是寄存器、整数、实数等类型。

这些变量在被赋值后,其值将保持不变,直到重新被赋予新值。

连续性赋值总是处于激活状态,任何操作数的改变都会影响表达式的结果;过程赋值只有在语句执行的时候,才会起作用。这是连续性赋值与过程性赋值的区别。

Verilog 过程赋值包括 2 种语句:阻塞赋值与非阻塞赋值。

阻塞赋值

阻塞赋值属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕。阻塞赋值语句使用等号 = 作为赋值符。当然,这个顺序执行是基于同一变量而言的,是确保后面使用此变量的值为最新值,不同变量之间该并行还是会并行。

非阻塞赋值

阻塞赋值属于并行执行,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行。

非阻塞赋值语句使用小于等于号 <=  作为赋值符。

ex: 以下面的例子说明:

`timescale 1ns/1ns
 
module test ;
    reg [3:0]   ai, bi ;
    reg [3:0]   ai2, bi2 ;
    reg [3:0]   value_blk ;
    reg [3:0]   value_non ;
    reg [3:0]   value_non2 ;
 
    initial begin
        ai            = 4'd1 ;   //(1)
        bi            = 4'd2 ;   //(2) 
        // 虽然(1)是阻塞执行,但是bi不利用ai产生的结果,(1)(2)并行
        ai2           = 4'd7 ;   //(3)
        bi2           = 4'd8 ;   //(4)
        #20 ;                    //(5)
        // 这里存在一个常规时延,20ns后执行后面的语句
        //non-block-assigment with block-assignment
        ai            = 4'd3 ;     //(6) 后面使用的ai都必须为最新结果
        bi            = 4'd4 ;     //(7) 后面使用的bi都必须为最新结果
        value_blk     = ai + bi ;  //(8)
        // value_blk 使用阻塞赋值,他需要获得ai和bi的最新结果,无法与(6)(7)并行执行
        value_non     <= ai + bi ; //(9)
        // value_non 使用非阻塞赋值,但是ai、bi都是阻塞赋值,value_non = 7
        
 
        //non-block-assigment itself
        ai2           <= 4'd5 ;           //(10)
        bi2           <= 4'd6 ;           //(11)
        value_non2    <= ai2 + bi2 ;      //(12)
        // (9)~(12)都是并发执行
        // value_non2使用非阻塞赋值,ai2\bi2也使用非阻塞赋值,并行执行
        // value_non2使用了ai2和bi2的之前值,即value_non2=7+8=f
    end
 
   //stop the simulation
    always begin
        #10 ;
        if ($time >= 1000) $finish ;
    end
 
endmodule

实际 Verilog 代码设计时,切记不要在一个过程结构中混合使用阻塞赋值与非阻塞赋值。两种赋值方式混用时,时序不容易控制,很容易得到意外的结果。更多时候,在设计电路时,always 时序逻辑块中多用非阻塞赋值,always 组合逻辑块中多用阻塞赋值;在仿真电路时,initial 块中一般多用阻塞赋值。

在C语言中,我们常常使用如下的模式来交换两个数:

void swap(int &a, int &b){
  int c = a;
  a = b;
  b = c;
}


// Verilog阻塞执行写法,在时钟上升沿交换ab值
always @(posedge clk) begin
    temp    = a ;
    a       = b ;
    b       = temp ;
end

这三条语句类似于是阻塞执行的,在硬件实现环境下,除了采用阻塞执行的写法,可以直接这么做:

在时钟上升沿,交换ab值,简单直观。
always @(posedge clk) begin
    a <= b ;
end
 
always @(posedge clk) begin
    b <= a;
end


这么做存在竞争冒险!是错误的,最后都会a == b
always @(posedge clk) begin
    a = b ;
end
 
always @(posedge clk) begin
    b = a;
end

时序控制

Verilog 提供了 2 大类时序控制方法:时延控制和事件控制。事件控制主要分为边沿触发事件控制与电平敏感事件控制。

时延控制

基于时延的时序控制出现在表达式中,指定了语句从开始执行到执行完毕的时间间隔,时延可以是数字、标识符或者表达式。

常规时延

reg  value_test ;
reg  value_general ;
#10  value_general    = value_test ;


等同于
#10 ;
value_ single         = value_test ;

内嵌时延

reg  value_test ;
reg  value_embed ;
value_embed        = #10 value_test ;

两者的区别是,内嵌时延是在执行时刻读值,延迟后赋值,常规时延是在延迟后读值并赋值。

边沿触发事件控制

在 Verilog 中,事件是指某一个 reg 或 wire 型变量发生了值的变化。

一般事件控制

事件控制用符号@表示,语句执行条件是信号的值发生特定的变化,posedge即上升沿,negedge即下降沿,未指明则默认为双边沿变化。

//信号clk只要发生变化,就执行q<=d,双边沿D触发器模型
always @(clk) q <= d ;                
//在信号clk上升沿时刻,执行q<=d,正边沿D触发器模型
always @(posedge clk) q <= d ;  
//在信号clk下降沿时刻,执行q<=d,负边沿D触发器模型
always @(negedge clk) q <= d ;
//立刻计算d的值,并在clk上升沿时刻赋值给q,不推荐这种写法
q = @(posedge clk) d ;   // 类似内嵌时延

命名事件控制

用户可以声明 event(事件)类型的变量,并触发该变量来识别该事件是否发生。命名事件用关键字 event 来声明,触发信号用 -> 表示。

event     start_receiving ;
always @( posedge clk_samp) begin
        -> start_receiving ;       //采样时钟上升沿作为时间触发时刻
end
 
always @(start_receiving) begin
    data_buf = {data_if[0], data_if[1]} ; //触发时刻,对多维数据整合
end

敏感列表

当多个信号或事件中任意一个发生变化都能够触发语句的执行时,Verilog 中使用"或"表达式来描述这种情况,用关键字 or 连接多个事件或信号。这些事件或信号组成的列表称为"敏感列表"。当然,or 也可以用逗号 , 来代替。例如:

//带有低有效复位端的D触发器模型
always @(posedge clk or negedge rstn)    begin      
//always @(posedge clk , negedge rstn)    begin      
//也可以使用逗号陈列多个事件触发
    if(! rstn)begin
        q <= 1'b ;      
    end
    else begin
        q <= d ;
    end
end
当组合逻辑需要对语句块中所有输入变量的变化都敏感时,可以用通配符*代替:
always @(*) begin
//always @(a, b, c, d, e, f, g, h, i, j, k, l, m) begin 
//两种写法等价
    assign s = a? b+c : d ? e+f : g ? h+i : j ? k+l : m ;
end

电平敏感事件控制

前述所讨论的都基于“变化”,Verilog还支持使用电平作为敏感信号来控制时序,使用wait关键字来表示这种电平敏感情况。

initial begin
    wait (start_enable) ;      //等待 start 信号
    forever begin
        //start信号使能后,在clk_samp上升沿,对数据进行整合
        @(posedge clk_samp)  ;
        data_buf = {data_if[0], data_if[1]} ;      
    end
end

语句块

主要包括两种类型:顺序块和并行块。

顺序块

顺序块使用关键字begin和end来表示,其中的语句是一条条执行的(非阻塞赋值除外)每条语句的时延总是与前面语句执行的时间相关。

并行块

并行块有关键字 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

0b28bc601ceeedc143df1d1dabb3f437.png

嵌套块

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

`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

命名块

我们可以给语句结构命名,并通过层次名引用的方法访问。

`timescale 1ns/1ns
 
module test;
 
    initial begin: runoob   //命名模块名字为runoob,分号不能少
        integer    i ;       //此变量可以通过test.runoob.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.runoob.i == 100) begin //i累加10次,即100ns时停止仿真
            $display("Now you can stop the simulation!!!");
            stop_flag = 1'b1 ;
        end
        #10 ;
    end
 
endmodule

命名的块可以通过disable关键字禁用,可以终止命名块的执行,用来从循环中退出及错误处理等,下面的代码中,disable退出了runoob_d2的循环。但是需要注意的是如果disable在always块或forever块中使用,只能退出当前回合,相当于“continue”。

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

条件语句if

if的用法与C语言不能说大差不差,只能说基本一样。但是Verilog没有大括号来进行语句块的分隔,建议使用begin-end进行包裹,降低歧义。

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

多路分支语句case

Verilog里的case语句,类似于C中的switch。

case(case_expr)
    condition1     :             true_statement1 ;
    condition2     :             true_statement2 ;
    ……
    default        :             default_statement ;
endcase
谁在前且条件为真先执行。

case 语句中的 x 或 z 的比较逻辑是不可综合的,所以一般不建议在 case 语句中使用 x 或 z 作为比较值。

循环语句

while (condition) begin
    …
end


for(initial_assignment; condition ; step_assignment)  begin
    …
end
i = i + 1 不能像 C 语言那样写成 i++ 的形式,i = i -1 也不能写成 i -- 的形式


repeat (loop_times) begin
    …
end
执行固定次数的循环。loop_times可以是常量也可以是变量,但是只读一次变量,变量在循环过程中改变,不会影响循环次数。


forever begin
    …
end
=while(1)

assign和deassign

assign(过程赋值操作)与 deassign (取消过程赋值操作)表示第一类过程连续赋值语句。赋值对象只能是寄存器或寄存器组,而不能是 wire 型变量。

赋值过程中对寄存器连续赋值,寄存器中的值被保留直到被重新赋值。

force和release

force (强制赋值操作)与 release(取消强制赋值)表示第二类过程连续赋值语句。

使用方法和效果,和 assign 与 deassign 类似,但赋值对象可以是 reg 型变量,也可以是 wire 型变量。

因为是无条件强制赋值,一般多用于交互式调试过程,不要在设计模块中使用。

当 force 作用在寄存器上时,寄存器当前值被覆盖;release 时该寄存器值将继续保留强制赋值时的值。之后,该寄存器的值可以被原有的过程赋值语句改变。

当 force 作用在线网上时,线网值也会被强制赋值。但是,一旦 release 该线网型变量,其值马上变为原有的驱动值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值