前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >一周掌握FPGA Verilog HDL语法 day 3

一周掌握FPGA Verilog HDL语法 day 3

作者头像
FPGA技术江湖
发布2025-03-24 15:09:24
发布2025-03-24 15:09:24
4200
代码可运行
举报
文章被收录于专栏:FPGA技术江湖FPGA技术江湖
运行总次数:0
代码可运行

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

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

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

赋值语句和块语句

赋值语句

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

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

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

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

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

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

1) 赋值语句执行完后,块才结束。

2) b的值在赋值语句执行完后立刻就改变的。

3) 可能会产生意想不到的结果。

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
复制
always @( posedge clk ) 
  begin 
    b<=a; 
    c<=b; 
  end

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

代码语言:javascript
代码运行次数:0
运行
复制
always @(posedge clk) 
  begin 
    b=a; 
    c=b; 
  end

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

其中:

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

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

代码语言:javascript
代码运行次数:0
运行
复制
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]:

代码语言:javascript
代码运行次数:0
运行
复制
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(表达式)语句 例如:

代码语言:javascript
代码运行次数:0
运行
复制
 if ( a > b ) out1 <= int1;

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

代码语言:javascript
代码运行次数:0
运行
复制
if(a>b) out1<=int1; 
  else out1<=int2; 

(3).

代码语言:javascript
代码运行次数:0
运行
复制
if(表达式1) 语句1; 
  else if(表达式2) 语句2; 
  else if(表达式3) 语句3; 
  ........ 
  else if(表达式m) 语句m; 
  else 语句n;

例如:

代码语言:javascript
代码运行次数:0
运行
复制
if(a>b) out1<=int1; 
  else if(a==b) out1<=int2; 
  else out1<=int3;

六点说明:

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
复制
if(a>b) 
  begin 
     out1<=int1; 
     out2<=int2; 
  end 
else 
  begin 
     out1<=int2; 
     out2<=int1; 
  end

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

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

代码语言:javascript
代码运行次数:0
运行
复制
if(expression) 等同与 if( expression == 1 ) 
if(!expression) 等同与 if( expression != 1 )

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

代码语言:javascript
代码运行次数:0
运行
复制
if(expression1) 
  if(expression2) 语句1 (内嵌if) 
  else 语句2 
else 
  if(expression3) 语句3 (内嵌if) 
  else 语句4

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

代码语言:javascript
代码运行次数:0
运行
复制
if( ) 
  begin 
    if( ) 语句1 (内嵌if) 
  end 
else 
  语句2

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

代码语言:javascript
代码运行次数:0
运行
复制
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对应,因为它们相距最近。正确的写法应当是这样的:

代码语言:javascript
代码运行次数:0
运行
复制
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以备下次检测使用。程序的前十行定义寄存器和参数。

代码语言:javascript
代码运行次数:0
运行
复制
  //定义寄存器和参数。
  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语句,大侠保重,告辞。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-03-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FPGA技术江湖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档