首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Verilog HDL---结构语句、系统任务、函数语句和系统任务

Verilog HDL---结构语句、系统任务、函数语句和系统任务

1.结构说明语句
verilog语言中任何过程模块都从属与一下4种结构的说明语句:
(1)initial说明语句;
(2)always说明语句;
(3)task说明语句;
(4)function说明语句。
initial语句:
initial
begin
语句1;
语句2;

"""""""
语句n;
end
例子:
用initial 块对存储器变量赋初始值
initial
begin
areg=0;    //初始化寄存器areg
for(index=0;index<size;index=index+1)
memory[index]=0;    //初始化一个memory
end

用initial语句来生成激励波形
initial
begin
inputs = 'b000000;    //初始时刻为0
#10 inputs = 'b011001;    (’是英文输入法中的标号)
#10 inputs = 'b011011;   
#10 inputs = 'b011000;   
#10 inputs = 'b001000;   
end

always语句
Always<时序控制><语句>
always语句由于其不断活动的特性,只有和一定的时序控制结合在一起才有用。
如果一个always语句没有时序控制,则这个always语句将会使仿真器产生死锁。
见下例:

[例3]:always areg = ~areg;
但如果加上时序控制,则这个always语句将变为一条非常有用的描述语句。见下例:

[例4]:always #half_period areg = ~areg;

这个例子生成了一个周期为:period(=2*half_period) 的无限延续的信号波形,
常用这种方法来描述时钟信号,作为激励信号来测试所设计的电路。

reg[7:0] counter;
reg tick;
always @(posedge areg)
begin
tick = ~tick;
counter = counter + 1;
end
always的时间控制可以是边沿触发也可以是电平触发可以是单个信号也可以是多个信号,中间用or 连接。
1)OR事件控制(敏感列表)

//有异步复位的电平敏感锁存器
always @ ( reset or clock or d )
//等待复位信号reset 或 时钟信号clock 或 输入信号d 的改变
begin
if ( reset ) //若 reset 信号为高,把q置零
q = 1 'b0 ;
else if ( clock ) //若clock 信号为高,锁存输入信号d
q = d ;
end


[例7] 使用逗号的敏感列表

//有异步复位的电平敏感锁存器
always @ ( reset , clock , d )
//等待复位信号reset 或 时钟信号clock 或 输入信号d的改变

begin
if ( reset ) // 若 reset 信号为高,把q置零
q = 1 'b0 ;
else if ( clock ) // 若clock 信号为高,锁存输入信号d
q = d ;
end

//用reset异步下降沿复位,clk正跳变沿触发的D寄存器
always @ ( posedge clk , negedge reset ) //注意:使用逗号来代替关键字or
if (! reset )
q <= 0 ;
else
q <= d ;


[例8]     @*操作符的使用
//用or 操作符的组合逻辑块
//编写敏感列表很繁琐并且容易漏掉一个输入
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
2)电平敏感时序控制
verilog同时也允许使用另外一种形式表示电平敏感时序控制(即后面的语句和语句块需要等待某个条件为真才能执行)。
always
Wait(count_enable)              #20  count=count+1;
以上例子仿真器连续监视count_enable的值。如果为0,则不执行后面的语句,如果为1,则20个时间单位之后执行这条语句。
2.task和function说明语句
task和function说明语句的不同点:
(1)函数只能与主模块共用同一个仿真时间单位,而任务可以定义自己的仿真时间单位。
(2)函数不能启动任务,而任务能启动其他任务和函数。
(3)函数至少要有一个输入变量,而任务可以没有或多个任何类型的变量。
(4)函数返回一个值,而任务则不返回值。
函数的目的是通过返回一个值来相应输入信号的值。任务却能支持多种目地,能计算多个结果值,这些结果值只能通过被调用的任务的输出或总线端口送出。
verilog模块使用函数时是把它当作表达式中的操作符,这个操作的结果值就是这个函数的返回值。

task说明语句
1)任务的定义
Task<任务名>;
<端口及数据类型的声明语句>
<语句1>
<语句2>
…………..
<语句n>
endtask
2)任务的调用及变量的传递
调用:<任务名>(端口1,端口2,端口3,……..,端口n);
任务定义:
task my_task;
input a, b;
inout c;
output d, e;

<语句>    //执行任务工作相应的语句

c = foo1;    //赋初始值
d = foo2;    //对任务的输出变量赋值
e = foo3;
endtask

任务调用:
my_task(v,w,x,y,z);
任务调用的变量和任务定义的I/O变量之间是一一对应的。
[例9] 描述红绿黄交通灯行为的Verilog模块,其中使用了任务。
module traffic_lights;
reg clock, red, amber, green;
parameter on=1, off=0, red_tics=350,
amber_tics=30,green_tics=200;
//交通灯初始化
initial    red=off;
initial    amber=off;
initial    green=off;
//交通灯控制时序
always
begin
red=on;        //开红灯
light(red,red_tics);    //调用等待任务
green=on;        //开绿灯
light(green,green_tics);    //等待
amber=on;        //开黄灯
light(amber,amber_tics);    //等待
end
//定义交通灯开启时间的任务
task light;
output color;
input[31:0] tics;
begin
repeat(tics)
@(posedge clock);//等待tics个时钟的上升沿
color=off;//关灯
end
endtask
//产生时钟脉冲的always块
always
begin
#100 clock=0;
#100 clock=1;
end
endmodule

function说明语句
函数的目的是返回一个用于表达式的值。
1)定义函数的语法:
Function<返回值的类型或范围>(函数名);
<端口说明语句>
<变量类型说明语句>
begin
<语句>
………..
end
Endfunction
2)函数的调用
<函数名>(<表达式>,…….<表达式>)

在这里返回值的类型或范围这一项是可选的,如默认则返回为一位寄存器型数局。
//定义说明:
function [7:0] getbyte;
input [15:0] address;
begin
<说明语句>        //从地址字中提取低字节的程序
getbyte = result_expression; //把结果赋予函数的返回字节
end
endfunction
//调用:
getbyte(lsbyte)
3)函数的使用规则:
(1)函数的定义不能包含任何的时间控制语句;
(2)函数不能启动任务;
(3)定义函数时至少要有一个输入参量;
(4)在函数的定义中必须有一条赋值语句给函数中的内部变量赋以函数结果值,该内部变量具有和函数名相同的名字。
//下面的例子中定义了一个可进行阶乘运算的名为factorial的函数,
//该函数返回一个32位的寄存器类型的值,该函数可后向调用自身,
//并且打印出部分结果值。

[例10] 阶乘函数的定义和调用。
module tryfact;
//函数的定义-------------------------------
function[31:0]factorial;
input[3:0]operand;
reg[3:0]index;
begin
factorial = 1; // 0的阶乘为1, 1的阶乘也为1
for(index=2; index<=operand; index=index+1)
factorial = index * factorial;
end
endfunction
//函数的测试-------------------------------------
reg[31:0]result;
reg[3:0]n;
initial
begin
result=1;
for(n=2;n<=9;n=n+1)
begin
$display("Partial result n= %d result= %d", n, result);
result = n * factorial(n)/((n*2)+1);
end
$display("Finalresult=%d",result);
end
endmodule // 模块结束
仿真结果:
#  Partial result n=  2 result=          1
# Partial result n=  3 result=          0
# Partial result n=  4 result=          2
# Partial result n=  5 result=         10
# Partial result n=  6 result=         54
# Partial result n=  7 result=        332
# Partial result n=  8 result=       2352
# Partial result n=  9 result=      18974
# Finalresult=    171890
函数的使用举例
//例11给出了函数calc_parity的定义和调用。

[例11]     偶校验位的计算

//定义一个模块,其中包含能计算偶校验位的函数(calc_parity)
module parity;
reg [31:0] addr;
reg parity;

initial
begin
addr = 32'h3456_789a;
#10 addr = 32'hc4c6_78ff;
#10 addr = 32'hff56_ff9a;
#10 addr = 32'h3faa_aaaa;
end

//每当地址值发生变化,计算新的偶校验位
always @(addr)
begin
parity = calc_parity(addr); //第一次启动校验位计算函数 calc_parity
$display("Parity calculated = %b", calc_parity(addr) );
// 第二次启动校验位计算函数 calc_parity

end

//定义偶校验计算函数
function calc_parity;
input [31:0] address;
begin
//适当地设置输出值,使用隐含的内部寄存器calc_parity
calc_parity = ^address; //返回所有地址位的异或值
end
endfunction

Endmodule
仿真结果:
# Parity calculated = 1
# Parity calculated = 1
# Parity calculated = 0
# Parity calculated = 0

[例13]     左/右移位寄存器
// 定义一个包含移位函数的模块
module shifter;

// 左/右 移位寄存器
`define LEFT_SHIFT 1'b0
`define RIGHT_SHIFT 1'b1
reg [31:0] addr, left_addr, right_addr;
reg control;
//每当新地址出现时就计算右移位和左移位的值
always @(addr)
begin
//调用下面定义的具有左右移位功能的函数
left_addr = shift(addr, `LEFT_SHIFT);
right_addr = shift(addr, `RIGHT_SHIFT);
end

//定义移位函数,其输出是一个32位的值
function [31:0] shift;
input [31:0] address;
input control;
begin
//根据控制信号适当地设置输出值
shift = (control == `LEFT_SHIFT) ? (address << 1) : (address >> 1);
end
endfunction

endmodule
自动(递归)函数
使用automatic 。
//用函数的递归调用定义阶乘计算
module top ;
...
//定义自动(递归)函数
function automatic integer factorial ;
input [ 31 : 0 ] oper ;
integer i ;
begin
if ( operand >= 2 )
factorial = factorial ( oper - 1 ) * oper ; //递归调用
else
factorial = 1 ;
end
endfunction


//调用该函数
integer result ;
initial
begin
result = factorial ( 4 ) ; // 调用4的阶乘
$display ( " Factorial of 4 is % 0d ", result ) ; // 显示24
end
......
......
endmodule
常量函数
常量函数实际上是一个带有某些限制的常规verilog函数。这种函数能够用来引用复杂的值,因而可用来代替常量。
[例15]     常量函数
module ram ( ... ... ...) ;
parameter RAM_DEPTH = 256 ;
input [ clog2( RAM_DEPTH) - 1 : 0 ] addr_bus ; //
... ... ...
... ... ...
//
function integer clogb2 ( input integer depth ) ;
begin
for ( clogb2 = 0 ; depth > 0 ; clogb2 = clogb2 + 1 )
depth = depth >> 1 ;
end
endfunction
... ...
... ...
endmodule
带符号函数
带符号函数的返回值可以作为带符号数进行运算。
[例16] 带符号函数
module top ;
... ...
//
//
function signed [ 63 : 0 ] compute _signed ( input [ 63 : 0 ] vector ) ;
... ...
... ...
endfunction

//
if ( compute_signed (vector) < - 3 )
begin
... ...
end
... ...
endmodule
4.常用的系统任务
1)$disply和$write任务
其输出格式控制是用双引号括起来的字符串,它有两种信息:
(1)格式说明,由“%”和格式字符组成。作用是把要输出的数据占化成指定的格式输出。
(2)
普通字符,即需要原样输出的字符。其中一些特殊的字符可以通过表二中的转换序列来输
出。下面表中的字符形式用于格式字符串参数中,用来显示特殊的字符。



[例17]:module disp;
initial
begin
$display("\\\t%%\n"\123");
end
endmodule

输出结果为
\%
"S
//从上面的这个例子中可以看到一些特殊字符的输出形式(八进制数123就是字符S)。

[例18]:module disp;
reg[31:0] rval;
pulldown(pd);
initial
begin
rval=101;
$display("rval=%h hex %d decimal", rval, rval);
$display("rval=%o otal %b binary", rval, rval);
$display("rval has %c ascii character value",rval);
$display("pd strength value is %v",pd);
$display("current scope is %m");
$display("%s is ascii value for 101",101);
$display("simulation time is %t",$time);
end
endmodule

其输出结果为:
rval=00000065 hex 101 decimal
rval=00000000145 octal 00000000000000000000000001100101 binary
rval has e ascii character value
pd strength value is StX
current scope is disp
e is ascii value for 101
simulation time is 0
文件输出
verilog 的结果通常输出到标准输出和文件veilog.log中。可以将verilog的输出重新定向到选择文件。
1)打开文件    $fopen
用法:$fopen(“<文件名>”);
用法:<文件句柄>=$fopen(“<文件名>”);
[例20] 文件描述符
//多通道描述符
integer handle1, handle2, handle3; //整型数为 32 位
//标准输出是打开的; descriptor = 32'h0000_0001 ( 第0位置1)
initial
begin
handle1 = $fopen("file1.out"); //handle1 = 32'h0000_0002 (bit 1 set 1)
handle2 = $fopen("file2.out"); //handle2 = 32'h0000_0004 (bit 2 set 1)
handle3 = $fopen("file3.out"); //handle3 = 32'h0000_0008 (bit 3 set 1)
end
多通道描述符的优点在于可以有选择地同时写多个文件。下面将详细解释这一点。

2)写文件
系统任务$fdisplay、$fmonitor、$fwrite和$fstrobe都用于写文件。
下面只考虑$fdisplay和$fmonitor任务。
用法:
$fdisplay(<文件描述符>,p1,p2,…,pn);
$fmonitor(<文件描述符>,p1,p2,…,pn);
P1,p2….可以是变量、信号名或者带引号的字符串。文件描述符是一个多通道描述符,它可以是一个文件句柄或多个文件句柄按位的组合。verilog会把输出写到与文件描述符中值为1的位相关联的所有文件中。
//所有的句柄已经在例18中定义
//写到文件中去
integer desc1, desc2, desc3 ; // 三个文件的描述符
initial
begin
desc1 = handle1 | 1; //按位或; desc1 = 32'h0000_0003
$fdisplay(desc1, "Display 1"); //写到文件file1.out和标准输出stdout

desc2 = handle2 | handle1; //desc2 = 32 'h0000_0006
$fdisplay(desc2, "Display 2"); //写到文件file1.out和file2.out

desc3 = handle3 ; //desc3 = 32'h0000_0008
$fdisplay(desc3, "Display 3"); //只写到文件file3.out
end
3)关闭文件
用系统函数$fclose来关闭。
用法: $fclose(<文件描述符>);
文件一旦被关闭就不能再写入了。多通道描述符中的相应位被设置为0.下次$fopen的调用可以重用这一位。
显示层次
通过任何显示任务,如$display、$write、$monitor或者$strobe任务中的%m选项的方式可以显示任何级别的层次,这是非常有用的选项。
[例21] 显示层次

//显示层次信息
module M;
initial
$display("Displaying in %m");
endmodule

//调用模块M
module top;

M m1 ( );
M     m2 ( );
M m3 ( );

endmodule
仿真输出如下所示:
Displaying in top.m1
Displaying in top.m2
Displaying in top.m3

这一特征可以显示全层次路径名,包括模块实例、任务、函数和命名块。
选通显示
选通显示由关键字为$strobe的系统任务完成。
如果使用$strobe,该语句总是在同时刻的其他赋值语句执行完之后才执行。因此,$strobe提供了一种同步机制,它可以确保所有在同一时钟沿赋值的其他语句在执行完毕后才显示数据。
[例22] 选通显示
//选通显示
always @ (posedge clock)
begin
a = b ;
c = d ;
end

always @ (posedge clock)
$strobe (“Displaying a = %b, c = % b” , a , c ); //显示正跳变沿时刻的值
//在例22中,时钟上升沿的值在语句a = b和c = d执行完之后才显示。
//如果使用$display,$display可能在语句a = b和c = d之前执行,结果显示不同的值。
值变转储文件
值变转储文件(VCD)是一个ASCII文件,它包括仿真时间、范围与信号的定义以及仿真运行过程中信号值的变化等信息。设计中的所有信号或者选定的信号的集合在仿真过程中都可以被写入VCD文件。
verilog提供了系统任务来选择要转储的模块实例或者模块实例信号($dumpvars),选择VCD文件的名称($dumpfile),选择转储过程的起点和终点($dumpon,$dumpoff),选择生成检测点($dumpall),使用方法:
[例23]: VCD文件系统任务
//指定VCD文件名。若不指定VCD文件,则由仿真器指定一缺省文件名
initial
$dumpfile (“ myfile.dmp”) ; //仿真信息转储到myfile.dmp文件

// 转储模块中的信号
initial
$dumpvars ; //没有指定变量范围,把设计中全部信号都转储
initial
$dumpvars ( 1, top ) ; //转储模块实例 top中的信号
//数1 表示层次的等级, 只转储top下第一层信号
//即转储top模块中的变量,而不转储在top中调用
//模块中的变量
initial
$dumpvars (2, top.m1) ; //转储top.m1模块下两层的信号

initial
$dumpvars (0, top.m1) ; ///数0 表示转储top.m1模块下面各个层的所有信号

//启动和停止转储过程
initial
begin
$dumpon ; //启动转储过程
#100000 $dumpoff ; //过了100000个仿真时间单位后,停止转储过程
end

//生成一个检查点,转储所有VCD变量的现行值。
initial
$dumpall ;

来源:http://blog.sina.com.cn/s/blog_8e9d968201010w3d.html
记录学习中的点点滴滴,让每一天过的更加有意义!
返回列表