Verilog HDL基本语句

1.过程语句

  • Verilog中有两种结构化过程语句:initial和always语句,是行为建模的两种基本语句,所有的行为语句只能出现在这两种结构化过程语句里。
  • 每个initial语句和always语句代表一个独立的执行过程(或过程块)。
  • 一个模块可以包含多条always语句和多条initial语句。每条语句启动一个单独的控制流。每条语句都在0时刻开始并行执行。
  • 这两种语句不能嵌套使用。Verilog本质上是并发的,这些块并发执行,而不是顺序执行。

initial语句

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

    initial语句的格式如下:

    initial
    	begin
    		语句1;
    		......
    		语句n;
    	end
    123456

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

    initial
    	begin
    		for(index = 0;index < size;index = index+1)
    		memory[index] = 0;  
    	end
    12345
    • 在这个例子中用initial语句在仿真开始时对各变量进行初始化,注意这个初始化过程不需要任何仿真时问,即在时间内,便可以完成存储器的初始化工作。
    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      
    123456789101112131415

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

    always语句

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

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

      声明格式如下:

      always <时序控制> <语句>

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

      举例:

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

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

      always #half_period clk = ~clk;

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

    • 敏感信号表达式:

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

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

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

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

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

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

      always @ (a or b or c)
      
      e = a & b & c;
    • 如果在赋值表达式右端引用了敏感信号列表中没有列出的信号,在综合时将会为没有列出的信号隐含地产生一个透明锁存器。
      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中的某一个变化。
      123456789
    • always中if语句的判断表达式必须在敏感电平列表中列出。

    https://img-blog.csdnimg.cn/20191002122015468.png

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

      always @ ( posedge clk or negedge clr)

    • 每一个always块最好只由一种类型的敏感信号触发,而不要将边沿敏感型和电平敏感型信号列在一起。
    • 如果组合逻辑块语句的输人变量很多,那么编写敏感列表会很烦琐并且容易出错。针对这种情况,Verilog提供另外两个特殊的符号:@*@(*),它们都表示对其后面语句块中所有输入变量的变化是敏感的。
      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(下降沿)两个关键字来进行描述。

      //例如:同步清零的时序逻辑
            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来表示等待电平敏感的条件为真。

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

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

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

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

      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时刻并行执行。

      例如:

      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构成的块语句形式为:
         begin
                 语句1;
                 语句2;
                   ......
                 语句n;
            end
       begin:块名
                 块内声明语句
                 语句1;
                 语句2;
                    ......
                 语句n;
         end
      12345678910111213
    • 其中可以在begin后声明该块的名字,一个标识名。块内声明语句可以是参数声明语句、reg型变量声明语句、integer型变量声明语句和real 变量声明语句。
    • 顺序块(串行块)有以下特点:(1)块内的语句是按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行。(2)每条语句的延迟时间是相对于前一条语句的仿真时间而言的。(3)直到最后一条语句执行完,程序流程控制才跳出该语句块。

    fork–join并行快

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

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

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

      格式1如下:
          fork
                语句1;
                语句2;
                 .......
                语句n;
         join
      格式2如下:
          fork  :块名
                块内声明语句
                语句1;
                语句2;
                 .......
                语句n;
         join
      123456789101112131415
    • 块名即标识该块的一个名字,相当于一个标识符。

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

    //使用 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
    //以上两个代码是等价的
    12345678910111213141516171819
    • 起始时间和结束时间

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

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

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

    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;
    */
    12345678910111213141516171819

    3.赋值语句

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

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

    连续赋值

    • 连续赋值

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

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

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

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

    • 语句格式: assign 赋值目标线网变量 = 表达式
      //第一种
      wire adder_out;
      assign adder_out = mult_out + out;
      //第二种
      wire adder_out = mult_out+out; 
       //隐含了连续赋值语句
      //第三种带函数调用的连续赋值语句:
        assign c = max( a,b );    
      //调用了函数max,将函数返回值赋给c
      123456789
    • 特点

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

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

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

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

    过程赋值

    • 过程赋值

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

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

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

    • 非阻塞赋值

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

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

      其基本语法格式如下:

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

      如 b <= a;

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

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

      //连续的非阻塞赋值实例:
      	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
      1234567891011

    https://img-blog.csdnimg.cn/20191002122126980.png

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

      (2)阻塞(Blocking)赋值方式操作符: “ = ”基本语法格式如下:寄存器变量(reg) = 表达式/变量;如 b = a;阻塞赋值在该语句结束时就立即完成赋值操作,即b的值在该条语句结束后立刻改变。如果在一个块语句中有多条阻塞赋值语句,那么写在前面的赋值语句没有完成之前,后面的语句就不能被执行,仿佛被阻塞了(blocking)一样,因而被称为阻塞赋值。连续的阻塞赋值操作是顺序完成的。

      //例如:连续的阻塞赋值
        	 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
      1234567891011

    https://img-blog.csdnimg.cn/20191002122148362.png

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

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

       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
      1234567891011121314
    • 总的来说,多条阻塞赋值语句是顺序执行的,而多条非阻塞语句是并行执行的.
    • 在使用always块描述组合逻辑(电平敏感)时使用阻塞赋值,在使用always块描述时序逻辑(边沿敏感)时使用非阻塞赋值。建立latch模型时,采用非阻塞赋值语句。在一个always块中同时有组合和时序逻辑时,采用非阻塞赋值语句。
    • 不要在同一个always块内同时使用阻塞赋值和非阻塞赋值。
    • 无论是使用阻塞赋值还是非阻塞赋值,不要在不同的always块内为同一个变量赋值。因为佷难保证不会引起赋值冲突。
    //例子:在不同的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
    1234567891011121314151617181920212223242526272829
    • 阻塞语句,如果没有写延迟时间看起来是在同一时刻运行,但实际上是有先后的,即在前面的先运行,然后再运行下面的语句,阻塞语句的次序与逻辑行为有很大的关系。
    • 而非阻塞的就不同了,在begin-end之间的所有非阻塞语句都在同一时刻被赋值,因此逻辑行为与非阻塞语句的次序就没有关系。在硬件实现时这两者有很大的不同。

    1.if–else语句

    • 其格式与C语言中的if–else语句类似,使用方法有以下3种:
      //形式1:只有if的形式
      if(表达式)  语句1;
      if(表达式)
          begin
              表达式1;
          end
      //形式2:if--else形式
      if(表达式)
          语句或语句块1;
      else
          语句或语句块2;
      //形式3:if--else嵌套形式
      if ( 表达式1)    语句1;     
      else if ( 表达式2 )  语句2;
      else if ( 表达式3 )  语句3;
      ........
      else if ( 表达式m )  语句m;
      else               语句n;
      //例如:
      if ( a > b )      out = int1;
      else if ( a == b)  out1= int2;
      else           out1 = int3;
    • 表达式:一般为逻辑表达式或关系表达式,也可能是一位的变量。
    • 系统对表达式的值进行判断,若为0,x,z,按“假”处理;若为1,按“真”处理,执行指定语句。
    • 语句可是单句,也可是多句,多句时用“begin - end”语句括起来。对于if语句的嵌套,若不清楚if和else的匹配,最好用begin-end语句括起来。
    • 条件语句必须在过程块中使用:
      always@(a,b,int1,int2) 
           begin
          if(a>b)
              begin      
                  out1=int1;     
                  out2=int2;        
              end
          else
             begin       
                 out1=int2;       
                 out2=int1;      
             end
       end
    • 允许一定形式的表达式简写方式:

      if(expression) 等同于 if(expression == 1)
      
      if(!expression) 等同于 if(expression!= 1)
    • if语句的嵌套,即在if语句中又包含一个或多个if语句称为if语句的嵌套。应当注意if与else的配对关系,else总是与它上面的最近的if配对。
    • if-else 嵌套形式隐含优先级关系:
       always@(sela or selb or a or b or c)
          begin
            if(sela)  q=a;
            else if(selb) q=b;
      	      else q=c;
          end

    2.case语句

    • Verilog语言提供的case语句直接处理多分支选择,通常用于描述译码器、数据选择器、状态机及微处理器的指令译码等,它的一般形式如下:
      case(表达式)
          分支表达式1:语句1;
          分支表达式2:语句2;
          ···
          分支表达式n:语句n;
          default: 语句n+1; //如果前面列出了表达式所有可能取值,default语句可以省略
      endcase
    • case括弧内的表达式称为控制表达式,case分支项中的表达式称为分支表达式。分支表达式则用这些控制信号的具体状态值来表示,因此分支表达式又可以称为常量表达式。
    • 当控制表达式的值与分支表达式的值相等时,就执行分支表达式后面的语句;如果所有的分支表达式的值都没有与控制表达式的值相匹配,就执行default后面的语句。
    • 分支表达式后面的语句也可以是由begin-end括起来的语句块。
    • default项可有可无,一个case语句里只准有一个default项。同样,case也只能在块语句中使用。
    //case语句实现3-8译码器的部分代码如下:
         wire[2:0] sel;
         reg[7:0]  res;
         always @ (sel or res)
          begin
    //case语句;
      case (sel)
           3’b000 : res=8’b00000001;
           3’b001 : res=8’b00000010;
           3’b010 : res=8’b00000100;
           3’b011 : res=8’b00001000;
           3’b100 : res=8’b00010000;
           3’b101:  res=8’b00100000;
           3’b110 : res=8’b01000000;
           default:  res=8’b10000000;
       endcase
     end
    • case语句的所有表达式值的位宽必须相等,只有这样,控制表达式和分支表达式才能进行对应位的比较。一个经常犯的错误是用’bx,'bz来替代n’bx,n’bz,这样写是不对的,因为信号x,z的默认宽度是机器的字节宽度,通常是32位。
    • 执行完case分项后的语句,则跳出该case语句结构,终止case语句的执行。
    • 在case语句中,表达式与分支表达式1到分支表达式n之间的比较是一种全等比较(===),必须保证两者的对应位全等。如果表达式的值和分支表达式的值同时为不定值或者同时为高阻态,则认为是相等的
      case ( a )
           2’b1x:out = 1;   // 只有a = 1x,才有out = 1
           2’b1z:out = 0;    // 只有a = 1z,才有out = 0
         ...
      endcase
      case(select[1,2]) 2'b00: result = 0; 2'b01: result = flaga; 2'b0x, 2'b0z: result = flaga ? 'bx:0; 2'b10: result = flagb; 2'bx0, 2'bz0: result = 0; default: result = flagb ? 'bz:0; endcase //当多个分项可以共用一个语句或语句块。其分支表达式之间用“,”隔开。
    • case语句还有两种变种,即casez语句和casex语句。
    • casez:

      忽略比较过程中值为z的位,即如果比较的双方(表达式的值与分支表达式的值)有一方的某一位的值是z,那么对这些位的比较就不予考虑,只需关注其他位的比较结果。

    • casex:

      在casex语句中,则把这种处理方式进一步扩展到对x的处理,即将z和x均视为无关值。

      //在分支表达式中,z常用?代替。
      casez(a)
               3'b1?? :  out1 = 1;//如果a=100、101、110、111或1xx,1zz等,都有out1 = 1。
           
               3'b0?1 :  out2 = 1; //如果a=001、011、0x1、0z1,都有out2 = 1
              .......
      endcase
      
      例如:
          casex(a)
      	         2'b1x:out=1;  
                ..................
           endcase
      //如果a=10、11、1x、1z,都有out=1。
      123456
    • if语句条件不完备情况

      如果if语句和case语句的条件描述不完备,会造成不必要的锁存器 。

      一般不可能列出所有分支,因为每一变量至少有4种取值0,1,z,x。为包含所有分支,可在if语句最后加上else;在case语句的最后加上default语句。

    • 对FPGA来说,它的基本逻辑单元由多输入查找表、 D触发器构成,并不存在锁存器结构,因此如果在FPGA设计中使用锁存器,需要更多的资源来搭建锁存器,反而会更消耗资源。
    • 所以在FPGA设计中,应该避免锁存器。在时序逻辑电路中,可以将锁存器改为带使能端的D触发器;在组合电路中,可以通过更改代码以覆盖所有条件分支等方式避免产生锁存器。
       always @(al or d)
          begin
           if(al)
                q<=d;
          end
          //有锁存器
      always @(al or d)
        begin
          if(al)   q<=d;
          else    q<=0
        end
          //无锁存器
    • 检查一下上边的"always"块,if语句保证了只有当al=1时,q才取d的值。这段程序没有写出 al = 0 时的结果, 那么当al=0时会怎么样呢?

      在"always"块内,如果在给定的条件下变量没有赋值,这个变量将保持原值,也就是说会生成一个锁存器!

    • 避免偶然生成锁存器的错误。如果用到if语句,最好写上else项。如果用case语句,最好写上default项。遵循上面两条原则,就可以避免发生这种错误,使设计者更加明确设计目标,同时也增强了Verilog程序的可读性。

    3.forever语句

    forever语句的格式如下:
          forever   语句;
        或者:    
           forever
              begin            
                  语句1;
                  语句2;
                  ……
              end
    • forever表示永久循环,无条件地无限次执行其后的语句,相当于while(1),直到遇到系统任务$finish$stop,如果需要从循环中退出,可以使用disable
    • 循环语句多用于生成时钟等周期性波形,它与always语句不同之处在于不能独立写在程序中,而必须写在initial块中。
      initial
          begin
               clk = 0;
               forever #25 clk = ~clk;     
          end
      
    • forever应该是过程块中最后一条语句。其后的语句将永远不会执行。
    • forever语句不可综合,通常用于testbench描述。
      ...
      reg clk;
      initial
            begin
            clk = 0;
            forever //这种行为描述方式可以非常灵活的描述时钟,可以控制时钟的开始时间及周期占空比。仿真效率也高。
                  begin
                       #10 clk = 1;
                       #10 clk = 0;
                 end
      end
      ...
      123456789101112

    4.repeat语句

    • repeat语句是最简单的循环语句,用于循环次数已知的情况。

      repeat语句的表达形式为:

      repeat(循环次数)
           begin
               操作1;
      	     操作2;
               ………
           end
      123456
    • 下例实现连续8次循环左移的操作:
      if (rotate == 1)
        repeat (8)     
         begin
             temp = data[15];
             data = {data << 1,temp};  // data循环左移8次
         end
      123456

    5.while语句

    • while语句通过控制某个变量的取值来控制循环次数。一般表达形式:
      while(条件)
          begin
                操作1;
                操作2;
                ………
           end
      

      在使用while语句时,一般在循环体内更新条件的取值,以保证在适当的时候退出循环。

    • 下例实现连续4次循环的操作
      i = 0;
      while(i < 4)
          begin
             a = a + 1;
             //更新条件取值,使循环4次退出循环
       	   i = i + 1; 
          end
    • 可见在while结构中只要表达式为真(不为0),则重复执行一条语句(或语句块)
      //其功能为:统计tempreg中 1 的个数
      . . .
      reg [7: 0] tempreg;
      reg [3: 0] count;
      . . .
            count = 0;
            while (tempreg) 
            begin
                  if (tempreg[0]) 
                      count = count + 1;
                  tempreg = tempreg >> 1; // Shift right
            end
      end
      . . .
      /*
      Tempreg:
       1011
       0101
       0010
       0001
       0000
      */

    6.for语句

    • for语句可以实现所有的循环结构。其表达形式如下:
      for(循环变量赋初值;条件表达式;更新循环变量)
             begin
                操作1:
                操作2;
                ………
             end
    • 它的执行过程如下:

      (1)先对循环变量赋初值。

      (2)计算条件表达式,若其值为真(非0),则执行for语句中指定的内嵌语句,然后执行下面的第(3)步。若为假(0),则结束循环,转到第5步。

      (3) 若条件表达式为真,在执行指定的语句后,执行更新循环变量。

      (4) 转回上面的第(2)步骤继续执行。

      (5) 执行for语句下面的语句。

    for(i = 0; i <4; i =i+1)
        begin 
    		a = a+1;
         end
    • 例:用for语句来实现8位数据中低4位左移到高4位;
         integer i;
          reg [7:0] datain;
      
         always @ (posedge clk)
           begin
                for(i=4;i<=7;i=i+1)
                    begin
                       datain[i]  <=  datain [i-4];
                    end
           end   
      12345678910
    • 例:编写 在一个时钟周期内用for语句计算出13路脉冲信号为高电平的个数。
        input clk,rst;
         input [12:0]  datain;    
         output [3:0]  numout;    
         reg [3:0] i;
         reg [3:0] num;
        always @ (posedge clk) 
          begin
              if ( !rst )  //重置信号
                   num <= 0;  
             else 
                begin
                    for ( i = 0; i < 13; i = i + 1)   //用for循环进行计算
                        if ( datain [i ] )  num  <= num +1;                   
                 end
          end
      123456789101112131415

    7.disable语句

    • 在有些特殊的情况下,需要使用disable强制退出循环。
    • 使用disable语句强制退出循环,首先要给循环部分起个名字,方法是在begin后添加“: 名字”。即disable语句可以中止有名字的begin…end块和fork…join块。
    • 语句块可以具有自己的名字,这称为命名块。

    命名块的特点是:

    命名块中可以声明局部变量;

    命名块是设计层次的一部分,命名块中声明的变量可以通过层次名引用进行访问

    命名块可以被禁用,例如停止其执行。

    //命名块
     module top;
      initial
        begin : block1
           integer i;
           ……….
         end
       
     initial
        fork : block2
           reg i;
           ……….
           ……….
         join
    • Verilog通过关键字disable提供了一种中止命名块执行的方法。

      disable可以用来从循环中退出、处理错误条件以及根据控制信号来控制某些代码段是否被执行。

      对块语句的禁用导致本块语句终止执行,紧接在块后面的那条语句被执行。

    • 例:(在C语言中break和continue的区别)
      begin  :continue
            a = 0; b =0;
            for(i=0;i<4;i = i+1)
             begin
                a = a+1;
                if(i==2) disable continue;
                b = b+1;
             end
       end
       ……………….;//a做3次加1操作后强制退出循环;而b只做2次加1操作。
      
           a=0; 
           b=0;
           for( i=0; i<4; i=i+1)
             begin: continue
      	    a = a+1;
                     if( i ==2) disable continue;
                     b= b+1;
              end
            ……………………….;
      //中止一次循环,继续下一次循环; a做4次加1操作, b只做3次加1操作.
posted @ 2021-08-20 23:12  快乐气氛组阿宇  阅读(905)  评论(0编辑  收藏  举报