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中,对综合来说毫无意义。










posted on 2018-12-27 10:22  迈克老狼2012  阅读(5781)  评论(0编辑  收藏  举报

导航