1、第第五五章章 任务及函数任务及函数LOREM IPSUM DOLOR目 录CONCENT5.1任务5.2 函数5.3 系统任务和系统函数5.4 命名事件5.5 层次路径名5.6 共享任务和函数 5.1任务GRADUATION THESIS5.1任务任务类似于一段程序,它只能在模块内部的行为所调用,因而不能使用连续赋值语句来调用任务。任务没有返回值,而是直接通过output或inout端口输出。任务可以包含时序控制和时延等时序信息,也可以包含各种事件控制逻辑,同时也可以调用其他任务或函数,并且可以有各种端口参数。5.1任务任务5.1.1任务声明任务声明任务以关键字task开始,以关键字endta
2、sk结束。其基本语法如下:task task_name;port declaration;task declaration;local variable declaration;beginprocedural_statement;endendtaskport decalaration是可选的,可以有输入(input)、输出(output)和双向(inout)的端口声明,也可以缺省。通过输入端口或者双向端口来传递参数并交由任务处理;处理完毕的数据通过输出端口或者双向端口交由调用它的模块进行处理。端口并不像模块的端口那样传递真实的电信号,而是传递数据。5.1任务任务【例5-1】采用任务实现对两个1
3、6位宽的操作数的与、或、异或逻辑,其示意图如图5-1所示。task bitwise_oper;input 15:0 a;input 15:0 b;output 15:0 ab_and,ab_or,ab_xor;begin#10 ab_and=a&b;ab_or=a|b;ab_xor=a b;endendtask5.1任务5.1.2 任务调用任务调用 任务调用和其他高级编程语言中的函数调用很类似,但它只能在过程语句中调用。也就是说,它只能在always语句块和initial语句块中被调用,因此任务的output和inout参数必须为reg型。任务可以调用其他任务,任务可以嵌套调用自己,甚至可以被
4、自己调用的任务再调用。任务调用的基本语法格式是:task_name(expression1,expression2,expressionN);系统调用任务后,任务计算的结果通过output和inout端口实现数据传递给系统中调用该任务的代码。调用的参数和任务中的端口声明的顺序必须一一对应。5.2函数GRADUATION THESIS5.2函数函数定义函数定义函数和任务很类似,也可以用来描述共同代码段。但函数与任务不同的是,函数只能返回一个值,并且不能包含任何时延信息,也不能调用其他任务。另外,函数必须至少要有一个输入,不允许有output和inout。函数可以调用其他的函数。因为函数里面不带有
5、触发器,所以一般可用于综合成具体的电路逻辑。5.2函数5.2.1 函数声明函数声明函数以关键字function开始,以关键字endfunction结束。函数的作用是实现组合逻辑,因此函数里不能有任何时延控制逻辑。函数返回值默认为标量reg型。如果需要指明特定的数据类型和位宽,需要显式声明。函数声明的基本格式如下:function range or type function_name;input declaration;other decalaration;begin statement;endendfunction5.2函数5.2.2 函数调用函数调用函数调用和任务调用相同,均需指明函数名和
6、输入参数名,其基本格式如下:function_name(expression1,expression2,expressionN);函数内部声明的寄存器是静态的,当函数返回的时候,数值仍然维持不变。当函数执行完毕时,返回值会出现在调用函数的位置。5.2函数假设设计一个内存协议,需要调用奇偶校验函数,其Verilog HDL代码如下:module parity;.reg 31:0 addr;reg parity;always(addr)begin/invoke the function calc_parity twice parity=calc_parity(addr);$display(“Par
7、itycalculated=%b”,calc_parity(addr);end/define Function calc_parity function calc_parity;input 31:0 address;begin/return the xor of all address bits.calc_parity=address;end endfunction.endmodule5.3系统务和系统函数GRADUATION THESIS5.3系统任务和系统函数Verilog HDL不仅可以用户定义任务和函数,而且还有预先定义好的内建的系统任务和系统函数,用户可以直接以关键字的形式直接调用。
8、根据用途不同,系统任务和系统函数大致可以分为几类:l显示任务l仿真控制任务l随机建模任务l文件输入输出任务l时间标度任务lPLA建模任务l变换函数l概率分布函数l字符格式化l命令行参变量5.3系统任务和系统函数5.3.1 显示任务显示任务Verilog HDL提供了三种类型的显示任务,分别是:显示和写任务、连续监控任务以及选通的监控任务。其关键字分别是$display,$write,$monitor以及$strobe。其根本语法格式如下:task_name(format_specification1,argument_list1,format_specification2,argument_l
9、ist2,format_specificationN,argument_listN);如:$display($time,“:a=%b,b=%h,c=%o,d=%d”,a,b,c,d);表示一旦调用$display任务,则把此刻的a、b、c和d的值分别显示出来,并显示当时的时刻。5.3系统任务和系统函数四类显示任务的区别如下:显示任务类型任务描述$display用于行为级描述中。当该任务被调用时,把指定信息及行结束字符打印到标准输出设备$write和$display的作用相同,只是在该任务被调用时,只把指定信息打印到标准输出设备,不输出行结束字符$monitor一旦任务中的任意一个变量发生变化时
10、就把指定信息打印到标准输出设备。需要注意的是,$monitor任务不能用来监控时间变量或返回时间值的函数。$monitor信息必须是在每次仿真时间阶段结束的时候才会打印,这样每个变量在每次仿真时间阶段结束时都会显示其最终值。$strobe用于行为级描述中。$display信息必须是在每次仿真时间阶段结束的时候才会打印,这样每个变量在每次仿真时间阶段结束时都会显示其最终值。表 5 1 显示任务具体区别和描述5.3系统任务和系统函数【例5-3】各类显示任务的应用举例说明integer watchdog;initial begin watchdog=500;$display(“Implement t
11、he display task,watchdog value is%d”,watchdog);$strobe(“Implement the stroble task,watchdog value is%d”,watchdog);watchdog=1000;$display(“Implement the display task again,watchdog value is%d”,watchdog);$write(“Implement the write task,watchod value is”);$write(“%dn”,watchdog);end仿真结果显示为:Implement th
12、e display task,watchdog value is 500Implement the display task again,watchdog value is 1000Implement the write task,watchdog value is 1000Implement the stroble task,watchdog value is 10005.3系统任务和系统函数5.3.2仿真控制任务仿真控制任务在正常状态下,Verilog HDL提供了两类系统任务来结束仿真。其关键字分别是$finish和$stop。$finish系统任务被调用时,意味着仿真器退出仿真环境,并
13、把控制权交还给操作系统。$stop系统任务被调用时,只是把仿真挂起,不会退出仿真环境。其基本格式为关键字加“;”。如:$finish;$stop;。5.3.3文件输入输出任务文件输入输出任务仿真数据不仅需要实时显示,也需要随时保存以便在未来的某个时间内查阅。或者,当要给的仿真激励需要大量数据时,如果在Testbench里面直接显式写入数据,不仅浪费时间,而且可读性差,代码可重复利用率低,容易出错。采用文件输入输出任务可以很好的解决此问题。和C语言一样,Verilog HDL的文件输入输出任务包含如下子任务:文件打开($fopen)、文件关闭($fclose)、写入文件($fdisplay、$f
14、write、$fmonitor、$fstrobe、$fflush)以及从文件中读取数据($readmemh、$readmemb等)。各文件输入输出任务的描述如表5-2所示。5.3系统任务和系统函数任务类型关键字描述文件打开$fopen打开一个文件文件关闭$fclose关闭一个文件写入文件$fdisplay和显示任务一样,只是输出到文件中$fwrite$fmonitor$fstrobe$fflush把输出缓冲内的资料输出到指定文件中读取文件$readmemb从文件中读取二进制存储数据并将数据加载到存储器中$readmemh从文件中读取十六进制存储数据并将数据加载到存储器中$fread从文件中读取
15、二进制数据到存储器中$fgetc从文件中每次读取一个字符$fgets从文件中每次读取一行$ungetc把一个字符插入文件中$frewind重新回到文件的开始处$fseek移动到偏移量指定的位置$ftell返回以文件开始处为基础的偏移量$fscanf从文件中读取格式化数据$ferror在执行完一个读取任务后,帮助判断出错的原因5.3系统任务和系统函数文件模式具体说明r,rb打开文件并从文件的头开始读。如果文件不存在则报错w,wb打开文件并从文件的头开始写。如果文件不存在则创建新文件a,ab打开文件并从文件的末尾开始写。如果文件不存在则创建文件r+,r+b,rb+打开文件并从文件的头开始读写。如果
16、文件不存在则报错w+,w+b,wb+打开文件并从文件的头开始读写。如果文件不存在则创建文件a+,a+b,ab+打开文件并从文件的末尾开始读写。如果文件不存在则创建文件表 5 3 文件模式说明表5.3系统任务和系统函数变换函数格式功能描述$rtoi(real value)将小数位截断将实数型转换为整型数据$itor(integer value)将整型数据转换为实数型$realtobits(real value)将实数型转换为64位的实型向量表达式$bitstoreal(bit value)将位模式转换为实数$signed(value)将数据转换为有符号数$unsigned(value)将数据转换
17、为无符号数表 5 4 变换函数功能描述5.3系统任务和系统函数5.3.4 概率分布函数概率分布函数Verilog HDL用于测试仿真时,需要随机产生激励,因此概率分布函数可以很好地应用于此场合。其基本格式如下:$random(seed);Seed是种子变量,是可选的。根据种子变量的值返回一个32位的有符号的整型随机数。种子变量必须是reg型、整型或时间类型变量,不同的种子将生成不同的随机数。如果没有指定种子变量,则根据默认种子变量来生成随机数。需要注意的是,生成的数字序列是一个伪随机序列,也就是说,对于一个初始的种子值会生成相同的数字序列。如:$random%101;会生成一个在0到100之间
18、取值的随机数。$random把$random函数返回的有符号数转换为无符号数。5.3系统任务和系统函数5.3.5仿真时间函数仿真时间函数在前面的章节中,经常会看到$time这类系统函数用于返回系统仿真时间。在Verilog HDL语法中,有三种不同的系统函数用于返回仿真时间。函数类型功能描述$time按照所在模块的时间单位和精度,返回64位的整型仿真时间$stime返回32位的仿真时间$realtime 返回实型仿真时间表 5 5 仿真时间函数功能描述5.4命名事件GRADUATION THESIS5.4命名事件命名事件:命名事件:erilog HDL定义了一种新的数据类型叫做命名事件。采用关
19、键字“event”来声明,其基本格式如下:event event_name;命名事件没有值或者时延,它可以透过事件触发状态或者边沿敏感事件控制来触发。通常用来描述行为级模型中通信和同步事件。当需要触发某个命名事件时,其基本格式如下:-event_name;命名事件既可以在模块内使用,也可以在任务内使用,还可以在语句块内使用。5.4命名事件【例5-4】命名事件举例event StartClock,StopClock;always fork begin:ClockGenerator Clock=0;StartClock forever#HALFPERIOD Clock=Clock;end Stop
20、Clock disable ClockGenerator;join initial begin:stimulus -StartClock;-StopClock;-StartClock;-StopClock;end5.5层次路径名GRADUATION THESIS5.5层次路径名Verilog HDL语言利用自上而下的设计方法论,采用模块化划分和设计。而模块又可以由任务、函数以及具体的程序组成。因此整个模块层次如图所示。modulesub_modulefunctiontaskblock图 5 2 模块层次示意图5.5层次路径名如果系统需要对任何层次的任何变量进行自由的数据访问和更新,则需要采用层
21、次路径名来进行标识。层次路径名都是从顶层模块开始,通过“.”隔开的名称组成。如:module top_A;wire wire_s;function func_A endfunction task task_B;reg reg_C;begin:blk integer RtoI;reg_c=RtoI?1b1:1b0;end endtask sub_mod sub_B();endmodule module sub_mod;reg sub_a;initial top_A.task_B.blk.RtoI=1b1;/层次路径名 endmodule5.6共享任务和函数GRADUATION THESIS5.6
22、共享任务和函数部分的任务和函数都是设计为通用型,以便为不同的模块来调用。Verilog HDL有两种方式来实现任务和函数共享。一种方式是把任务和函数全部写在一个文本文件中,格式不限,如.h文件、.v文件都行,然后利用关键字“include”来调用该文本文件中的具体任务和函数来实现。另外一种方式是直接在模块内定义所有的任务和函数,然后采用层次路径名的方式来实现。【例5-5】采用关键字“include”实现共享任务和函数举例1、设计一个task_function_share.h的文件,专门放置需要共享的任务和函数/文件:task_function_share.hfunction func_A(in
23、put 31:0 addr_a;)fun_A=addr_a;endfunction task task_B(input 63:0 a,b,output 63:0 sum_c);sum_c=a+b;endtask5.6共享任务和函数2、模块调用任务和函数module mem_bus(input 31:0 address;input 63:0 dataA,dataB,output 63:0 data);include“task_function_share.h”;/把共享任务和函数包含进来 always(*)if(func_A)task_B(dataA,dataB,data);else data=
24、64b0;Endmodule需要注意的是:include必须出现在模块声明内,且在被调用之前。5.6共享任务和函数【例5-6】采用层次路径名的方式实现共享任务和函数举例1、设计一个模块,专门放置需要共享的任务和函数module task_function_share;function func_A(input 31:0 addr_a;)fun_A=addr_a;endfunction task task_B(input 63:0 a,b,output 63:0 sum_c);sum_c=a+b;endtask endmodule5.6共享任务和函数2、模块调用任务和函数module mem_b
25、us(input 31:0 address;input 63:0 dataA,dataB,output 63:0 data);always(*)if(task_function_share.func_A)task_function_share.task_B(dataA,dataB,data);else data=64b0;endmodule5.7实例:带可预置数据的8位自增/减计数器设计GRADUATION THESIS5.7实例:带可预置数据的8位自增/减计数器设计修改3.9节实例,采用函数调用的方式来设计带可预置数据的8位自增/减计数器。其代码如下:timescale 1ns/100ps/
26、指定时间单位和精度module counter_function#(parameter COUNT_WIDTH=8)(/定义位宽参数 input sysclk,input reset,input set,input ld,input up,input down,input COUNT_WIDTH-1:0 data,output reg COUNT_WIDTH-1:0 count);always(posedge sysclk or negedge reset)/异步复位 begin if(!reset)count=8H00;else if(set)/同步置位 count=8hFF;5.7实例:带
27、可预置数据的8位自增/减计数器设计else count=count_op(up,down,ld);/函数调用end/计数器函数设计 function COUNT_WIDTH-1:0 count_op(input up,down,ld);count_op=up?(count_op+8h1):(down?(count_op-8h1):(ld?data:8h0);endfunctionendmodule5.7实例:带可预置数据的8位自增/减计数器设计综合后的电路图如下:图 5 3 采用Synplify Pro综合后的逻辑电路图5.7实例:带可预置数据的8位自增/减计数器设计对上述逻辑进行仿真,仿真代
28、码如下:timescale 1ns/100ps module counter_function_tb;reg sysclk;reg reset;reg set;reg ld;reg up;reg down;reg 7:0 data;wire 7:0 count;initial data=8hAA;initial begin5.7实例:带可预置数据的8位自增/减计数器设计 sysclk=1b0;forever#5 sysclk=!sysclk;end initial begin reset=1b0;set=1b0;up=1b0;down=1b0;ld=1b0;repeat(10)(posedge
29、 sysclk);reset=1b1;repeat(2)(posedge sysclk);set=1b1;5.7实例:带可预置数据的8位自增/减计数器设计repeat(10)(posedge sysclk);up=1b0;repeat(2)(posedge sysclk);down=1b1;repeat(10)(posedge sysclk);down=1b0;repeat(2)(posedge sysclk);ld=1b1;repeat(10)(posedge sysclk);ld=1b0;repeat(10)(posedge sysclk);$stop;end5.7实例:带可预置数据的8位
30、自增/减计数器设计 counter_function tb(.sysclk(sysclk),.reset(reset),.set(set),.ld(ld),.up(up),.down(down),.data(data),.count(count);endmodule5.7实例:带可预置数据的8位自增/减计数器设计采用Modelsim SE-64仿真,生成的仿真波形如下。从波形中可以看出,此设计符合实例的设计需求。图 54 采用Modelsim SE64对实例进行仿真的波形图本章小结本章着重讲述了Verilog HDL最重要的两类语法:任务和函数,包括用户自定义的任务和函数,以及系统任务和函数,并介绍了命名事件,以及如何来灵活运用各类任务和函数。通过对3.9节实例的修改和仿真,加深了任务和函数与普通代码之间的异同的印象。谢谢GRADUATION THESIS