下冰雹 · 3月26日

一周掌握 FPGA Verilog HDL 语法 day 3

今天给大侠带来的是一周掌握 FPGA Verilog HDL 语法,今天开启第三天。

上一篇提到了变量可分为 wire 型、reg 型、memory 型,各种运算符,此篇我们继续来看赋值语句和块语句以及后续其他内容,结合实例理解理论语法,会让你理解运用的更加透彻。下面咱们废话就不多说了,一起来看看吧。

赋值语句和块语句

赋值语句

在 Verilog HDL 语言中,信号有两种赋值方式:

(1).非阻塞(Non_Blocking)赋值方式( 如 b <= a; )

  1. 块结束后才完成赋值操作。

2) b 的值并不是立刻就改变的。

  1. 这是一种比较常用的赋值方法。(特别在编写可综合模块时)

(2).阻塞(Blocking)赋值方式( 如 b = a; )

  1. 赋值语句执行完后,块才结束。
  2. b 的值在赋值语句执行完后立刻就改变的。
  3. 可能会产生意想不到的结果。

非阻塞赋值方式和阻塞赋值方式的区别常给设计人员带来问题。问题主要是给"always"块内的 reg 型信号的赋值方式不易把握。

到目前为止,前面所举的例子中的"always"模块内的 reg 型信号都是采用下面的这种赋值方式: b <= a;

这种方式的赋值并不是马上执行的,也就是说"always"块内的下一条语句执行后,b 并不等于 a,而是保持原来的值。"always"块结束后,才进行赋值。而另一种赋值方式阻塞赋值方式,如下所示:  b = a;

这种赋值方式是马上执行的。也就是说执行下一条语句时,b 已等于 a。尽管这种方式看起来很直观,但是可能引起麻烦。下面举例 1 说明:

always @( posedge clk ) 
  begin 
    b<=a; 
    c<=b; 
  end

上述的例 1 中的"always"块中用了非阻塞赋值方式,定义了两个 reg 型信号 b 和 c,clk 信号的上升沿到来时,b 就等于 a,c 就等于 b,这里应该用到了两个触发器。请注意:赋值是在"always"块结束后执行的,c 应为原来 b 的值。这个"always"块实际描述的电路功能如下图所示:

image.png

always @(posedge clk) 
  begin 
    b=a; 
    c=b; 
  end

上述的例 2 中的 "always"块用了阻塞赋值方式。clk 信号的上升沿到来时,将发生如下的变化:b 马上取 a 的值,c 马上取 b 的值(即等于 a),生成的电路图如下所示只用了一个触发器来寄存器 a 的值,又输出给 b 和 c。这大概不是设计者的初衷,如果采用[例 1]所示的非阻塞赋值方式就可以避免这种错误。

Image

Image

Image

Image

Image

其中:

  • 块名即标识该块的一个名字,相当于一个标识符。
  • 块内说明语句可以是参数说明语句、reg 型变量声明语句、integer 型变量声明语句、real 型变量声明语句、time 型变量声明语句、事件(event)说明语句。

下面举例说明,[例 4]:

fork 
  #50 r = 'h35; 
  #100 r = 'hE2; 
  #150 r = 'h00; 
  #200 r = 'hF7; 
  #250 -> end_wave; //触发事件end_wave. 
join

在这个例子中用并行块来替代了前面例子中的顺序块来产生波形,用这两种方法生成的波形是一样的。

三. 块名  

在 VerilgHDL 语言中,可以给每个块取一个名字,只需将名字加在关键词 begin 或 fork 后面即可。这样做的原因有以下几点。

  1. 这样可以在块内定义局部变量,即只在块内使用的变量。
  2. 这样可以允许块被其它语句调用,如被 disable 语句。
  3. 在 Verilog 语言里,所有的变量都是静态的,即所有的变量都只有一个唯一的存储地址,因此进入或跳出块并不影响存储在变量内的值。

基于以上原因,块名就提供了一个在任何仿真时刻确认变量值的方法。

四. 起始时间和结束时间

在并行块和顺序块中都有一个起始时间和结束时间的概念。对于顺序块,起始时间就是第一条语句开始被执行的时间,结束时间就是最后一条语句执行完的时间。而对于并行块来说,起始时间对于块内所有的语句是相同的,即程序流程控制进入该块的时间,其结束时间是按时间排序在最后的语句执行完的时间。

当一个块嵌入另一个块时,块的起始时间和结束时间是很重要的。至于跟在块后面的语句只有在该块的结束时间到了才能开始执行,也就是说,只有该块完全执行完后,后面的语句才可以执行。

在 fork_join 块内,各条语句不必按顺序给出,因此在并行块里,各条语句在前还是在后是无关紧要的。见下例  [例 5]:

fork 
  #250 -> end_wave; 
  #200 r = 'hF7; 
  #150 r = 'h00; 
  #100 r = 'hE2; 
  #50 r = 'h35; 
join

在这个例子中,各条语句并不是按被执行的先后顺序给出的,但同样可以生成前面例子中的波形。

条件语句

if_else 语句

if 语句是用来判定所给定的条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一。Verilog HDL 语言提供了三种形式的 if 语句。

(1).if(表达式)语句   例如:

if ( a > b ) out1 <= int1;

(2).if(表达式) 语句 1 else 语句 2  例如:

if(a>b) out1<=int1; 
  else out1<=int2; 

(3).

if(表达式1) 语句1; 
  else if(表达式2) 语句2; 
  else if(表达式3) 语句3; 
  ........ 
  else if(表达式m) 语句m; 
  else 语句n;

例如:

if(a>b) out1<=int1; 
  else if(a==b) out1<=int2; 
  else out1<=int3;

六点说明:

(1). 三种形式的 if 语句中在 if 后面都有“表达式”,一般为逻辑表达式或关系表达式。系统对表达式的值进行判断,若为 0,x,z,按“假”处理,若为 1,按“真”处理,执行指定的语句。

(2). 第二、第三种形式的 if 语句中,在每个 else 前面有一分号,整个语句结束处有一分号。例如:

Image

这是由于分号是 Verilog HDL 语句中不可缺少的部分,这个分号是 if 语句中的内嵌套语句所要求的。如果无此分号,则出现语法错误。但应注意,不要误认为上面是两个语句(if 语句和 else 语句)。它们都属于同一个 if 语句。else 子句不能作为语句单独使用,它必须是 if 语句的一部分,与 if 配对使用。

(3). 在 if 和 else 后面可以包含一个内嵌的操作语句(如上例),也可以有多个操作语句,此时用 begin 和 end 这两个关键词将几个语句包含起来成为一个复合块语句。如:

if(a>b) 
  begin 
     out1<=int1; 
     out2<=int2; 
  end 
else 
  begin 
     out1<=int2; 
     out2<=int1; 
  end

注意在 end 后不需要再加分号。因为 begin_end 内是一个完整的复合语句,不需再附加分号。

(4). 允许一定形式的表达式简写方式。如下面的例子:

if(expression) 等同与 if( expression == 1 ) 
if(!expression) 等同与 if( expression != 1 )

(5). if 语句的嵌套 在 if 语句中又包含一个或多个 if 语句称为 if 语句的嵌套。一般形式如下:

if(expression1) 
  if(expression2) 语句1 (内嵌if) 
  else 语句2 
else 
  if(expression3) 语句3 (内嵌if) 
  else 语句4

应当注意 if 与 else 的配对关系,else 总是与它上面的最近的 if 配对。如果 if 与 else 的数目不一样,为了实现程序设计者的企图,可以用 begin_end 块语句来确定配对关系。例如:

if( ) 
  begin 
    if( ) 语句1 (内嵌if) 
  end 
else 
  语句2

这时 begin_end 块语句限定了内嵌 if 语句的范围,因此 else 与第一个 if 配对。注意 begin_end 块语句在 if_else 语句中的使用。因为有时 begin_end 块语句的不慎使用会改变逻辑行为。见下例:

if(index>0) 
  for(scani=0;scani<index;scani=scani+1) 
    if(memory[scani]>0) 
      begin 
        $display("..."); 
        memory[scani]=0; 
      end 
else /*WRONG*/ 
$display("error-indexiszero");

尽管程序设计者把 else 写在与第一个 if(外层 if)同一列上,希望与第一个 if 对应,但实际上 else 是与第二个 if 对应,因为它们相距最近。正确的写法应当是这样的:

if(index>0) 
  begin 
    for(scani=0;scani<index;scani=scani+1) 
    if(memory[scani]>0) 
      begin 
        $display("..."); 
        memory[scani]=0; 
      end 
  end 
else /*WRONG*/ 
$display("error-indexiszero");

(6) .if_else 例子。

下面的例子是取自某程序中的一部分。这部分程序用 if_else 语句来检测变量 index 以决定三个寄存器 modify_segn 中哪一个的值应当与 index 相加作为 memory 的寻址地址。并且将相加值存入寄存器 index 以备下次检测使用。程序的前十行定义寄存器和参数。

//定义寄存器和参数。
  reg [31:0] instruction, segment_area[255:0]; 
  reg [7:0] index; 
  reg [5:0] modify_seg1, modify_seg2, modify_seg3; 
  
  parameter 
    segment1=0, inc_seg1=1, 
    segment2=20, inc_seg2=2, 
    segment3=64, inc_seg3=4, 
    data=128; 
    
  //检测寄存器index的值 
  if(index<segment2) 
    begin 
      instruction = segment_area[index + modify_seg1]; 
      index = index + inc_seg1; 
    end 
  else if(index<segment3) 
    begin 
      instruction = segment_area[index + modify_seg2]; 
      index = index + inc_seg2; 
    end 
  else if (index<data) 
    begin 
      instruction = segment_area[index + modify_seg3]; 
      index = index + inc_seg3; 
    end 
  else 
    instruction = segment_area[index];

Day 3 就到这里,Day 4  继续开始 case 语句,大侠保重,告辞。

END

原文:FPGA技术江湖

相关文章推荐

更多 FPGA 干货请关注FPGA的逻辑技术专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
10654
内容数
613
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息