initial语句的格式如下:
initial begin 语句1; ...... 语句n; end 123456
举例说明:memory存储器初始化
initial begin for(index = 0;index < size;index = index+1) memory[index] = 0; end 12345
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 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
在赋值表达式右端参与赋值的所有信号都必须在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 @ (a or b or sel) begin if (sel) c = a; else c = b; end 1234567
always @ ( posedge clk or negedge clr)
@*
和@(*)
,它们都表示对其后面语句块中所有输入变量的变化是敏感的。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语句书写的前后顺序无关,它们之间是并行运行的。
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
在每一个模块(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 */
一个程序模块可以有多个initial和always过程块。
每个initial和always说明语句在仿真的一开始便同时立即开始运行。
initial语句在模块中只执行一次。
always语句则是不断地活动着。直到仿真过程结束。
always语句后跟着的过程块是否运行,则要看它的触发条件是否满足,如满足则运行过程块一次,再次满足则再运行一次,循环往复直至仿真过程结束。
always的时间控制可以是沿触发也可以是电平触发的,可以单个信号也可以多个信号,中间需要用关键字or或“,”连接。
沿触发的always块常常描述时序行为,如有限状态机。而电平触发的: always块常常用来描述组合逻辑的行为。
begin 语句1; 语句2; ...... 语句n; end begin:块名 块内声明语句 语句1; 语句2; ...... 语句n; end 12345678910111213
如果语句前面有延时符号“#”,那么延时的长度是相对于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
过程赋值:阻塞赋值(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
(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
在下面的例子中,两个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块为相同的变量赋值 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
//形式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;
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)
always@(sela or selb or a or b or c) begin if(sela) q=a; else if(selb) q=b; else q=c; end
case(表达式) 分支表达式1:语句1; 分支表达式2:语句2; ··· 分支表达式n:语句n; default: 语句n+1; //如果前面列出了表达式所有可能取值,default语句可以省略 endcase
//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 ( 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 //当多个分项可以共用一个语句或语句块。其分支表达式之间用“,”隔开。
忽略比较过程中值为z的位,即如果比较的双方(表达式的值与分支表达式的值)有一方的某一位的值是z,那么对这些位的比较就不予考虑,只需关注其他位的比较结果。
在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语句和case语句的条件描述不完备,会造成不必要的锁存器 。
一般不可能列出所有分支,因为每一变量至少有4种取值0,1,z,x。为包含所有分支,可在if语句最后加上else;在case语句的最后加上default语句。
always @(al or d) begin if(al) q<=d; end //有锁存器 always @(al or d) begin if(al) q<=d; else q<=0 end //无锁存器
在"always"块内,如果在给定的条件下变量没有赋值,这个变量将保持原值,也就是说会生成一个锁存器!
forever语句的格式如下: forever 语句; 或者: forever begin 语句1; 语句2; …… end
$finish
或$stop
,如果需要从循环中退出,可以使用disable
。initial begin clk = 0; forever #25 clk = ~clk; end
... reg clk; initial begin clk = 0; forever //这种行为描述方式可以非常灵活的描述时钟,可以控制时钟的开始时间及周期占空比。仿真效率也高。 begin #10 clk = 1; #10 clk = 0; end end ... 123456789101112
repeat语句的表达形式为:
repeat(循环次数) begin 操作1; 操作2; ……… end 123456
if (rotate == 1) repeat (8) begin temp = data[15]; data = {data << 1,temp}; // data循环左移8次 end 123456
while(条件) begin 操作1; 操作2; ……… end
在使用while语句时,一般在循环体内更新条件的取值,以保证在适当的时候退出循环。
i = 0; while(i < 4) begin a = a + 1; //更新条件取值,使循环4次退出循环 i = i + 1; end
//其功能为:统计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 */
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
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
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
命名块的特点是:
命名块中可以声明局部变量;
命名块是设计层次的一部分,命名块中声明的变量可以通过层次名引用进行访问
命名块可以被禁用,例如停止其执行。
//命名块 module top; initial begin : block1 integer i; ………. end initial fork : block2 reg i; ………. ………. join
disable可以用来从循环中退出、处理错误条件以及根据控制信号来控制某些代码段是否被执行。
对块语句的禁用导致本块语句终止执行,紧接在块后面的那条语句被执行。
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操作.