• verilog语法实例学习(4)


    Verilog模块

    Verilog中代码描述的电路叫模块,模块具有以下的结构:

    module module_name[ (portname {, portname})]; //端口列表

    [parameter declarations] //参数定义

    [input declarations] // I/O定义

    [output declarations]

    [inout declarations]

    [wire or tri declarations] //内部信号定义

    [reg or integer declartions]

    [function or task declarations] //功能定义

    [assign continuous assignments]

    [initial block]

    [always block]

    [gate instantiations]

    [module instantiations]

    endmodule

          模块通常以module开始,endmodule结束,并具有模块名,模块名可以是任何有效的标识符。名字后面跟随的是端口列表,端口都有一个相关联的类型。端口类型可以是input, ouput或者inout(双向),可以是标量,也可以是矢量。下面是全加器模块和4位加法模块的代码。

    module fulladd(cin,x,y,s,cout);
      input cin,x,y;
      output s,cout;
      assign {cout,s}=x+y+cin;
    endmodule
    
    module fulladd4(cin,x,y,s,cout);
      input [3:0] x,y;
      input cin;
      output [3:0] s;
    output cout;
      assign {cout,s}=x+y+cin;
    endmodule
    

          端口在默认状态下是线网(wire,tri等)类型。信号可以分为端口信号和内部信号。出现在端口列表中的信号是端口信号,其它的信号为内部信号。对于端口信号,输入端口只能是线网类型。输出端口可以是线网类型,也可以是reg类型若输出端口在过程块中赋值则为reg类型;若在过程块外赋值(包括实例化语句),则为线网类型。内部信号类型与输出端口相同,可以是线网或reg类型。判断方法也与输出端口相同。若在过程块中赋值,则为reg类型;若在过程块外赋值,则为线网类型。

    内部信号定义时候我们可以定义一些在模块内部使用的信号。比如下面n位加法器的代码中:我们定义了integer 类型k以及内部信号C。

    module addern(carryin,X,Y,S,carryout);
    parameter n=32;
    input carryin;
    input [n-1:0] X, Y;
    output reg [n-1:0] S;
    output reg carryout;
    reg [n:0] C;
    integer k;
    always @(X,Y, carryin)
    begin
    	C[0]=carryin;
    	for(k=0;k<n;k=k+1)
    	begin
    		S[k]=X[k]^Y[k]^C[k];
    		C[k+1]=(X[k]&Y[k])|(X[k]&C[k])|(Y[k]&C[k]);
    	end
    	carryout=C[n];
    end
    endmodule
    
    
    

    在功能定义中,我们通过always过程语句实现n位加法操作,当然我们也可以用assign连续赋值语句实现同样的功能,比如上面fulladd4的代码。


    并行语句

    在硬件描述语言中,并行语句是代表着电路的一部分,这些语句是并行执行的,它们的顺序并不重要。并行语句包括连续赋值语句和门实例化语句。

    连续赋值语句

          连续赋值语句用来描述组合逻辑电路,用来给线网赋值,赋值时用阻塞赋值。但不同连续赋值语句之间是并行执行的,因为它们都代表电路的一部分,这些电路在物理上可以并行执行。

    连续赋值语句的格式为:assign net_assginment[,net assignment];

    下面是一些连续赋值语句的例子:

    assign cout = (x&y)|(y&cin)|(x&cin);

    assign s = x^y^z;

    门实例化语句

    verilog中包括预定义的基本逻辑门。这些逻辑门允许通过门实例化语句来调用。门实例化语句结构为:

    gate_name instance_name(output_port, input_port{,input_port});

    实例名甚至可以省略,直接调用gate_name实现逻辑功能。

    下面是门实例化实现全加器代码:

    module fulladd(cin,x,y,s,cout);
      input cin,x,y;
      ouput s,cout;
      wire z1,z2,z3,z4;
      and and1(z1,x,y);
      and and2(z2,x,cin);
      and and3(z3,y,cin);
      or or1(cout,z1,z2,z3);
      xor xor1(z4,x,y);
      xor xor2(s,z4,cin);
    endmodule
    
    module fulladd(cin,x,y,s,cout);
      input cin,x,y;
      ouput s,cout;
      wire z1,z2,z3,z4;
      and(z1,x,y);
      and(z2,x,cin);
      and(z3,y,cin);
      or(cout,z1,z2,z3);
      xor(z4,x,y);
      xor(s,z4,cin);
    endmodule
    


          我们还可以在门电路中设置一个延时参数,例如:and #(20) and1(z,x1,x2,x3), 表示这个与门延时20时间单位。但这种参数只用在testbench中,电路中是不能综合的。如果电路中用到这些延时参数,综合工具通常会忽略它们。Verilog允许逻辑门有任意的输入,但是实际上受CAD系统限或工艺等等限制,逻辑门的扇入和扇出都是有限制的。扇入:逻辑门的输入数量。扇出:某个门驱动其它门的数量。

    Verilog中支持的逻辑门主要由以下几种。

    名称

    说明

    用法

    and

    f = a&b&…

    and(f,a,b,…)

    nand

    f=~(a&b&…)

    nand(f,a,b,…)

    or

    f=a|b|…

    or(f,a,b,…)

    nor

    f=~(a|b|…)

    nor(f,a,b,…)

    xor

    f=a^b^…

    xor(f,a,b,…)

    xnor

    f=~(a^b^…)

    xnor(f,a,b,…)

    not

    f=~a

    not(f,a)

    buf

    f=a;

    buf(f,a)

    notif0

    f=!e?~a:'bz 三态门

    notif0(f,a,e)

    notif1

    f=e?~a:'bz 三态门

    notif1(f,a,e)

    bufif0

    f=!e?a:'bz 三态门

    bufif0(f,a,e)

    bufif1

    f=e?a:'bz 三态门

    bufif1(f,a,e)


    过程语句

    除了并行语句,verilog中还提供了过程语句。并行语句是并行执行,而过程语句则是按照代码的顺序执行,verilog语法要求过程语句包含在一个always块内部:

    always块

    always块包含一个或多个过程语句的结构,它的形式如下:

    always @(sensitivity_list) //敏感信号列表

    [begin] //当一个always块中包含多条语句时,就必须用begin…end

    [procedural assignment statement]

    [if-else statement]

    [case statement]

    [while, repeat, and for loops]

    [task and function calls]

    [end]

    相比于连续赋值语句和门实例化,上面的这些语句提供了更为强大的行为级电路描述方式。

      敏感信号列表是一个直接影响always块信号输出的信号列表。敏感信号之间用逗号(,)或者 or分开。当敏感信号列表中任何一个信号发生改变时,always块中的过程语句即被顺序执行。我们可以用always @(*),表示所有的输入信号多包含在敏感信号列表中。下面是一个简单always块例子:

    always @(x,y)
    
    begin
    
    s=x^y;
    
    c=x&y;
    
    end
    

    敏感信息列表也可以在信号的边沿触发,posedge 信号的升沿触发,negedge信号的下降沿触发,例如下面的代码:

    always @(posedge clk,negedge Rst_n)
    begin
       if(Rst_n==0)
            Q<=0;
         else
            Q<=D;
    end
    

    一个verilog module可以包含多个always块,它们都代表电路的一部分,不同的always块之间是并行执行的。

    过程赋值语句

    always块中赋值的信号都是reg或integer等变量类型,不能是wire类型。给一个信号赋值用过程赋值语句。过程赋值语句有两种,阻塞赋值和非阻塞赋值。

    阻塞赋值:

    s = x + y; //先执行第一句

    p = s[0]; //再执行第二句

    非阻塞赋值:

    s <= x + y; //两条语句同时执行

    p<= s[0]; //p此时更新的s[0]仍是之前的值

    在一个always块中,一般不建议混用阻塞和非阻塞赋值语句。

          前面提到连续赋值语句中,我们采用阻塞赋值,是不是组合电路都不能采用非阻塞赋值?其实在很多情况下是可以使用的,但是如果分支语句的赋值取决于之前的结果,非阻塞赋值可能产生无意义的电路。比如下面bit_count模块代码,用来统计四位数中1的数目,综合后是3个加法器。如果把for循环中的赋值改为非阻塞赋值,则循环过程为:

    count <= count +x[0];

    count <= count +x[1];

    count <= count +x[2];

    此时,count的初始值都为0,则for循环退化为

    count<=x[0];

    count<=x[1];

    count<=x[2];

    当always块当中存在多条给同一个变量赋值的语句时,相当于多源激励输入。

    module bit_count(x,count);
      parameter n=4;
      parameter logn=2;
      input [n-1:0] x;
      output reg[logn:0] count;
      integer k;
    
      always @(x)
      begin
         count=0;
    	 for(k=0;k<n;k=k+1)
    	   count = count + x[k];
      end
    
    endmodule
    
    module bit_count(x,count);
      parameter n=4;
      parameter logn=2;
      input [n-1:0] x;
      output reg[logn:0] count;
      integer k;
    
      always @(x)
      begin
         count<=0; #10
    	 for(k=0;k<n;k=k+1)
    	   count <= count + x[k];
      end
    
    endmodule
    

    用阻塞赋值,用下面的testbench,得到结果:

    # .main_pane.objects.interior.cs.body.tree

    # run -all

    # x=10101010,count= 4

    # x=11111010,count= 6

    `timescale 1ns/1ns
    module bit_count_tb;
      reg [7:0] x;
      wire [3:0] count;
      bit_count #(.n(8),.logn(3)) bit_count0(.x(x),.count(count));
    
      initial
      begin
        x = 8'b10101010;
    	 #20
    	 $display("x=%b,count=%d",x,count);
    	 x = 8'b11111010;
    	 #20
    	 $display("x=%b,count=%d",x,count);
    
    	 $stop;
      end
    endmodule
    

    如果用非阻塞赋值的代码,则得到下面的结果:这是此时相当于多源激励输入,如果有的0,有的1,最终的值为x

    # .main_pane.objects.interior.cs.body.tree
    # run -all
    # x=10101010,count= x
    # x=11111010,count= x
    # ** Note: $stop    : D:/fpga/veriloglearn/bitcount/testbench/bit_count_tb.v(17)

    if else 语句

    和c语言的if else语句语法一样,if else语句必须包含在always块中。if else语法结构为:


    if( 表达式1)
    begin
       statement;
    end
    else if(表达式2)
    begin
        statement;
    end
    else
    begin
       statement;
    end
    

    下面的if else语句,定义了一个二选一电路。

    module vif1(a,b,sel,r);
      input a;
      input b;
      input sel;
      output reg r;
    
      always @(*)
      begin
         if(sel==0)
    	    r = a;
    	  else
    	    r = b;
      end
    endmodule

    image

    下面的代码综合后是两个比较器和两个级联的二路选择器。

    module vif1(a,b,c,sel,r);
      input a;
      input b;
      input c;
      input [1:0] sel;
      output reg r;
    
      always @(*)
      begin
         if(sel==2'b00)
    	    r = a;
    	  else if(sel==2'b01)
    	    r = b;
    	  else
    	    r = c;
      end
    endmodule

    image



    下面的代码综合后是三个比较器和三个级联的二路选择器。也就是说elseif会被综合成级联的二路选择器,而不是多路选择器。

    module vif1(a,b,c,d,sel,r);
      input a;
      input b;
      input c;
      input d;
      input [1:0] sel;
      output reg r;
    
      always @(*)
      begin
         if(sel==2'b00)
    	    r = a;
    	  else if(sel==2'b01)
    	    r = b;
    	  else if(sel==2'b10)
    	    r = c;
    	  else
    	    r = d;
      end
    endmodule


    image



    case 语句

    case语句是一种多分支选择语句,可以直接处理多分支语句。

    1)case(表达式) <case分支项> endcase

    2)casex(表达式) <case分支项> endcase

    3)casez(表达式) <case分支项> endcase

    case分支项的一般格式如下:

    分支表达式: 语句;

    ……

    默认项(default) 语句;

    1)case后括号内的表达式称为控制表达式,分支项后的表达式称作分支表达式,又称作常量表达式。控制表达式通常表示为控制信号的某些位,分支表达式则用这些控制信号的具体状态值来表示。

    2)当控制表达式和分支表达式的值相等时,就执行分支表达式后的语句。

    3)default项可有可无,一个case语句里只准有一个default项。(为了防止程序自动生成锁存器,一般都要设置default项

    4)每一个case的表达是必须各不相同,执行完case分支项的语句后,跳出case块。

    5)case语句的所有表达式的值的位宽必须相等。

    case casex casez 的真值 (行0,1,x,z是控制表达式,列0/1/x/z是分支表达式)

    case

    0   1   x   z

    casez

    0    1   x   z

    casex

    0   1   x   z

    0

    1   0   0   0

    0

    1   0   0   1

    0

    1   0   1   1

    1

    0   1   0   0

    1

    0   1   0   1

    1

    0   1   1   1

    x

    0   0   1   0

    x

    0   0   1   1

    x

    1   1   1   1

    z

    0   0   0   1

    z

    1   1   1   1

    z

    1   1   1   1

    casex和casez是case的特殊情况,用来处理过程中不必考虑的情况。casez用来处理不用考虑高阻值,casex表示不用考虑高阻值和不定值

    上述表格说明casez中,可以将z任意匹配,casex中可以将x任意匹配。在case的分支语句中,从上到下开始匹配,输出第一个匹配成功的值。

    下面代码中我们用case语句实现上面ifelse中的电路功能。综合工具通常只考虑x=0,x=1的情况,所以在case中,我们不考虑x,z的情况。

    从rtl viewer中,可以看到综合后是一个多路选择器,这个要比上面ifelse语句综合后的电路要好。

    module vif1(a,b,c,d,sel,r);
      input a;
      input b;
      input c;
      input d;
      input [1:0] sel;
      output reg r;
    
      always @(*)
      begin
         if(sel==2'b00)
    	    r = a;
    	  else if(sel==2'b01)
    	    r = b;
    	  else if(sel==2'b10)
    	    r = c;
    	  else
    	    r = d;
      end
    endmodule


    image


    循环语句

    Verilog包括四种循环语句,for, while, repeat和forever,综合工具通常仅支持for语句。其它几种语句主要用在testbench当中。

         在C语言中,经常用到for循环语句,但在硬件描述语言中for语句的使用和C语言等软件描述语言有较大的区别。

         在Testbench中for语句在生成激励信号等方面使用较普遍,但在RTL级编码中却很少使用for循环语句。主要原因就是for循环会被综合器展开为所有变量情况的执行语句,每个变量独立占用寄存器资源,每条执行语句并不能有效地复用硬件逻辑资源,造成巨大的资源浪费。简单的说就是:for语句循环几次,就是将相同的电路复制几次,因此循环次数越多,占用面积越大,综合就越慢

         在RTL硬件描述中,遇到类似的算法,推荐的方法是先搞清楚设计的时序要求,做一个reg型计数器。在每个时钟沿累加,并在每个时钟沿判断计数器情况,做相应的处理,能复用的处理模块尽量复用,即使所有的操作不能复用,也采用case语句展开处理。

    对于下面的for循环语句: 

    for(i=0;i<16;i++)
      DoSomething();

    可以采用如下代码实现:

    reg [3:0] counter;
    always @(posedge clk)
      if(syn_rst)
        counter<=4'b0;
      else
        counter<=counter+1;
    always @(posedge clk)
      begin
        case(counter)
            4'b0000:
            4'b0001:
            ......
        default:
        endcase
      end
         另外,有几个语法的细节需要注意一下。for(i=0;i<16;i=i+1)中的i既可以是reg型的变量也可以是integer类型的变量,但是当i是reg型的变量时,需要注意因为判断语句i<16的缘故,i应定义为reg[4:0] i而不是reg[3:0] i 。由于verilog中没有自增运算符,文中提到的for语句不能写成for(i=0;i<16; i++)的形式。

    下面简单的列举几个用for实现的程序代码:
    示例一:

    module vfor1(clk,Rst_n,data, num);
    
      input clk; //时钟信号
      input Rst_n;  //复位信号
      input [3:0] data;
    
      output reg [2:0] num;
      integer i;
    
      always @(posedge clk)
      begin
        if(Rst_n==0)
    	   num = 0;
    	else
    	begin
    	  for(i=0;i < 4; i=i+1)
    	   if(data[i])  num = num + 1;
    
       end
      end
    
    endmodule


          综合后,用rtl vieer,可以看到这儿有四个相同的电路,而且效率很高,但所消耗的逻辑资源较大。在对速度(时钟周期数)要求不是很高的情况下,可以多用几个时钟周期完成任务,而没有必要用for循环来做。

    image

    while, repeat, forever都用在testbench当中,下面是它们的语法:

    while(condition)

    begin

      statement;

    end

    repeat(constanat_value)

    begin

    statement;

    end

    forever

    begin

      statement;

    end

    我们用下面的代码说明它们的用法:

    `timescale 1ns/1ns
    `define clock_period 20
    
    module addern_tb;
      reg [7:0] x,y;
    
      wire cout;
      wire [7:0] s;
      reg clk;
      integer i,j;
    
      addern #(.n(8)) addern_0(
    						.x(x),
    						.y(y),
    						.s(s),
    						.cout(cout)
                      );
    
      initial clk = 0;
      always #(`clock_period/2) clk = ~clk;
    
      initial begin
         x = 0;
         repeat(10)
    	    #(`clock_period) x = $random;
         i = 10;
    	  while(i>0)
    	  begin
    	     #(`clock_period) x = $random;
    		  i = i -1;
    	  end
    	  forever
    	    #(`clock_period) x = $random;
      end
    
      initial begin
         y = 0;
         repeat(10)
    	    #(`clock_period) y = $random;
    	  j = 10;
    	  while(j>0)
    	  begin
    	     #(`clock_period) y = $random;
    		  j = j -1;
    	  end
    	  forever
    	    #(`clock_period) y = $random;
    
      end
    
    
      initial begin
         #(`clock_period*200)
    	  $stop;
      end
    
    
    endmodule



    initial语句

          initial和always具有相同的结构,但initial块内的内容只在仿真开始的时候执行一次。initial语句用在testbench中,对综合来说毫无意义。










  • 相关阅读:
    jquery的内容(html,.text,val)及其属性(attr,prop,data)
    jquery对象的遍历
    jquery的选择器和过滤器
    jquery的基础认知
    解决跨域的四种常见方法
    HTTP中的消息头
    使用js实现ajax加载json文件的组件开发
    IDEA工具第四篇:项目导航Project Navigation下工程包的折叠与展开
    IDEA工具第三篇:启动时报错javax.imageio.IIOException: Can't get input stream from URL!
    IDEA工具第一篇:细节使用-注意事项
  • 原文地址:https://www.cnblogs.com/mikewolf2002/p/10183511.html
Copyright © 2020-2023  润新知