发新帖我要提问
12
返回列表
打印

跟我一起学习Verilog HDL语言

[复制链接]
手机看帖
扫描二维码
随时随地手机跟帖
21
山东电子小菜鸟|  楼主 | 2017-10-17 14:05 | 只看该作者 回帖奖励 |倒序浏览
占座 更新。。。。。。。。。。。。。。。。

使用特权

评论回复
22
山东电子小菜鸟|  楼主 | 2017-10-17 14:05 | 只看该作者
占座 更新。。。。。。。。。。。。。。。。

使用特权

评论回复
23
山东电子小菜鸟|  楼主 | 2017-10-17 14:05 | 只看该作者
占座 更新。。。。。。。。。。。。。。。。

使用特权

评论回复
24
山东电子小菜鸟|  楼主 | 2017-10-17 14:05 | 只看该作者
占座 更新。。。。。。。。。。。。。。。。

使用特权

评论回复
25
山东电子小菜鸟|  楼主 | 2017-10-17 14:05 | 只看该作者
占座 更新。。。。。。。。。。。。。。。。

使用特权

评论回复
26
山东电子小菜鸟|  楼主 | 2017-10-17 14:05 | 只看该作者
占座 更新。。。。。。。。。。。。。。。。

使用特权

评论回复
27
山东电子小菜鸟|  楼主 | 2017-10-17 14:05 | 只看该作者
占座 更新。。。。。。。。。。。。。。。。

使用特权

评论回复
28
山东电子小菜鸟|  楼主 | 2017-10-18 20:10 | 只看该作者
3.7.结构说明语句
Verilog语言中的任何过程模块都从属于以下四种结构的说明语句。
1)      initial说明语句
2)      always说明语句
3)      task说明语句
4)      function说明语句
initial和always说明语句在仿真的一开始即开始执行。initial语句只执行一次。
相反,always语句则是不断地重复执行,直到仿真过程结束。在一个模块中,
使用initial和always语句的次数是不受限制的。task和function语句可以在程序
模块中的一处或多处调用。其具体使用方法以后再详细地加以介绍。这里只对initial
和always语句加以介绍。
3.7.1.initial语句
initial语句的格式如下:
initial
begin
语句1;
语句2;
......
语句n;
end
举例说明:
[例1]:
initial
begin
areg=0;   //初始化寄存器areg
for(index=0;index<size;index=index+1)
memory[index]=0;  //初始化一个memory
end
在这个例子中用initial语句在仿真开始时对各变量进行初始化。
[例2]:
initial
begin
inputs = 'b000000;  //初始时刻为0
#10 inputs = 'b011001;  
#10 inputs = 'b011011;  
#10 inputs = 'b011000;  
#10 inputs = 'b001000;  
end
从这个例子中,我们可以看到initial语句的另一用途,即用initial语句来生成激励波形作为
电路的测试仿真信号。一个模块中可以有多个initial块,它们都是并行运行的。initial块常用
于测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。

使用特权

评论回复
29
山东电子小菜鸟|  楼主 | 2017-10-18 20:11 | 只看该作者
3.7.2.always语句
always语句在仿真过程中是不断重复执行的。
其声明格式如下:
always <时序控制>  <语句>
always语句由于其不断重复执行的特性,只有和一定的时序控制结合在一起才有用。
如果一个always语句没有时序控制,则这个always语句将会发成一个仿真死锁。见下例:
[例1]:always  areg = ~areg;
这个always语句将会生成一个0延迟的无限循环跳变过程,这时会发生仿真死锁。
如果加上时序控制,则这个always语句将变为一条非常有用的描述语句。见下例:
[例2]:always #half_period  areg = ~areg;
这个例子生成了一个周期为:period(=2*half_period) 的无限延续的信号波形,
常用这种方法来描述时钟信号,作为激励信号来测试所设计的电路。
[例3]:reg[7:0] counter;
       reg tick;
       always @(posedge areg)
begin
tick = ~tick;
counter = counter + 1;
end
这个例子中,每当areg信号的上升沿出现时把tick信号反相,并且把counter增加1。这种时间控制是always语句最常用的。
always 的时间控制可以是沿触发也可以是电平触发的,可以单个信号也可以多个信号,中间需要用关键字 or 连接,如:
   always @(posedge clock orposedge reset)  //由两个沿触发的always块
      begin
      ……
      end
         
   always @( a or b or c )                 //由多个电平触发的always块
            begin
             ……
            end
沿触发的always块常常描述时序逻辑,如果符合可综合风格要求可用综合工具自动转换为表示时序逻辑的寄存器组和门级逻辑,
而电平触发的always块常常用来描述组合逻辑和带锁存器的组合逻辑,如果符合可综合风格要求可转换为表示组合逻辑的门级
逻辑或带锁存器的组合逻辑。一个模块中可以有多个always块,它们都是并行运行的。

使用特权

评论回复
30
山东电子小菜鸟|  楼主 | 2017-10-18 20:11 | 只看该作者
3.7.3.task和function说明语句
task和function说明语句分别用来定义任务和函数。利用任务和函数
可以把一个很大的程序模块分解成许多较小的任务和函数便于理解和调试。
输入、输出和总线信号的值可以传入、传出任务和函数。任务和函数往往还是
大的程序模块中在不同地点多次用到的相同的程序段。学会使用task和function
语句可以简化程序的结构,使程序明白易懂,是编写较大型模块的基本功。
一.task和function说明语句的不同点
任务和函数有些不同,主要的不同有以下四点:
1)    函数只能与主模块共用同一个仿真时间单位,而任务可以定义自己的仿真时间单位。
2)    函数不能启动任务,而任务能启动其它任务和函数。
3)    函数至少要有一个输入变量,而任务可以没有或有多个任何类型的变量。
4)    函数返回一个值,而任务则不返回值。
函数的目的是通过返回一个值来响应输入信号的值。任务却能支持多种目的,能计算多个结果值,
这些结果值只能通过被调用的任务的输出或总线端口送出。Verilog HDL模块使用函数时是把它当
作表达式中的操作符,这个操作的结果值就是这个函数的返回值。下面让我们用例子来说明:
例如,定义一任务或函数对一个16位的字进行操作让高字节与低字节互换,把它变为另一个字
(假定这个任务或函数名为: switch_bytes)。
任务返回的新字是通过输出端口的变量,因此16位字字节互换任务的调用源码是这样的:
switch_bytes(old_word,new_word);
任务switch_bytes把输入old_word的字的高、低字节互换放入new_word端口输出。
而函数返回的新字是通过函数本身的返回值,因此16位字字节互换函数的调用源码是这样的:
new_word = switch_bytes(old_word);
下面分两节分别介绍任务和函数语句的要点。

使用特权

评论回复
31
山东电子小菜鸟|  楼主 | 2017-10-18 20:12 | 只看该作者
二. task说明语句
如果传给任务的变量值和任务完成后接收结果的变量已定义,就可以用一条语句启动任务。
任务完成以后控制就传回启动过程。如任务内部有定时控制,则启动的时间可以与控制返回
的时间不同。任务可以启动其它的任务,其它任务又可以启动别的任务,可以启动的任务数
是没有限制的。不管有多少任务启动,只有当所有的启动任务完成以后,控制才能返回。
1) 任务的定义
定义任务的语法如下:
任务:
task <任务名>;
<端口及数据类型声明语句>
<语句1>
<语句2>
.....
<语句n>
endtask
这些声明语句的语法与模块定义中的对应声明语句的语法是一致的。
2) 任务的调用及变量的传递
启动任务并传递输入输出变量的声明语句的语法如下:
任务的调用:
<任务名>(端口1,端口2,...,端口n);
下面的例子说明怎样定义任务和调用任务:
任务定义:
task  my_task;
input a, b;
inout  c;
output d, e;
<语句>   //执行任务工作相应的语句
c = foo1;    //赋初始值
d = foo2;    //对任务的输出变量赋值t
e = foo3;
endtask
任务调用:
my_task(v,w,x,y,z);
任务调用变量(v,w,x,y,z)和任务定义的I/O变量(a,b,c,d,e)之间是一一对应的。
当任务启动时,由v,w,和x.传入的变量赋给了a,b,和c,而当任务完成后的输出又通过
c,d和e赋给了x,y和z。下面是一个具体的例子用来说明怎样在模块的设计中使用任务,使程序容易读懂:
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(color,tics);
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
这个例子描述了一个简单的交通灯的时序控制,并且该交通灯有它自己的时钟产生器。

使用特权

评论回复
32
山东电子小菜鸟|  楼主 | 2017-10-18 20:14 | 只看该作者
一.function说明语句
函数的目的是返回一个用于表达式的值。
Ÿ        定义函数的语法:
function <返回值的类型或范围> (函数名);
<端口说明语句>file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
<变量类型说明语句>
begin
<语句>
........
end
endfunction
请注意<返回值的类型或范围>这一项是可选项,如缺省则返回值为一位寄存器类型数据。下面用例子说明:
function [7:0] getbyte;
input [15:0] address;
begin
<说明语句>       //从地址字中提取低字节的程序
getbyte = result_expression; //把结果赋予函数的返回字节
end
endfunction
Ÿ        从函数返回的值
函数的定义蕴含声明了与函数同名的、函数内部的寄存器。如在函数的声明语句中<返回值的类型或范围>为缺省,
则这个寄存器是一位的,否则是与函数定义中<返回值的类型或范围>一致的寄存器。函数的定义把函数返回值
所赋值寄存器的名称初始化为与函数同名的内部变量。下面的例子说明了这个概念:getbyte被赋予的值就是函数的返回值。
Ÿ        函数的调用
函数的调用是通过将函数作为表达式中的操作数来实现的。
其调用格式如下:
<函数名> (<表达式><,<表达式>>*)
其中函数名作为确认符。下面的例子中通过对两次调用函数getbyte的结果值进行位拼接运算来生成一个字。
word = control? {getbyte(msbyte),getbyte(lsbyte)} : 0;
Ÿ        函数的使用规则
与任务相比较函数的使用有较多的约束,下面给出的是函数的使用规则:
1)      函数的定义不能包含有任何的时间控制语句,即任何用#、@、或wait来标识的语句。
2)      函数不能启动任务。
3)      定义函数时至少要有一个输入参量。
4)      在函数的定义中必须有一条赋值语句给函数中的一个内部变量赋以函数的结果值,
     该内部变量具有和函数名相同的名字。
Ÿ        举例说明
下面的例子中定义了一个可进行阶乘运算的名为factorial的函数,该函数返回一个32位的寄存器类型的值,
该函数可后向调用自身,并且打印出部分结果值。
module  tryfact;
//函数的定义-------------------------------
function[31:0]factorial;
input[3:0]operand;
reg[3:0]index;
begin
factorial = operand? 1 : 0;
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//模块结束
前面我们已经介绍了足够的语句类型可以编写一些完整的模块。在下一章里,我们将举许多实际的例子进行介绍。
这些例子都给出了完整的模块描述,因此可以对它们进行仿真测试和结果检验。通过学习和练习我们就能逐步掌握
利用Verilog HDL设计数字系统的方法和技术。

使用特权

评论回复
33
山东电子小菜鸟|  楼主 | 2017-10-18 20:15 | 只看该作者
3.8.系统函数和任务
Verilog HDL语言中共有以下一些系统函数和任务:
$bitstoreal, $rtoi, $display, $setup, $finish, $skew, $hold,
$setuphold, $itor,$strobe, $period, $time, $printtimescale,
$timefoemat, $realtime, $width, $real tobits, $write, $recovery,
在Verilog HDL语言中每个系统函数和任务前面都用一个标识符$来加以确认。
这些系统函数和任务提供了非常强大的功能。有兴趣的同学可以参阅附录:
Verilog语言参考手册。下面对一些常用的系统函数和任务逐一加以介绍。
3.8.1.$display和$write任务
格式:
$display(p1,p2,....pn);
$write(p1,p2,....pn);
这两个函数和系统任务的作用是用来输出信息,即将参数p2到pn按参数p1给定的格式输出。
参数p1通常称为“格式控制”,参数p2至pn通常称为“输出表列”。这两个任务的作用基本相同。
$display自动地在输出后进行换行,$write则不是这样。如果想在一行里输出多个信息,可以使用$write。
在$display和$write中,其输出格式控制是用双引号括起来的字符串,它包括两种信息:
·        格式说明,由"%"和格式字符组成。它的作用是将输出的数据转换成指定的格式输出。
格式说明总是由“%”字符开始的。对于不同类型的数据用不同的格式输出。表一中给出了常用的几种输出格式。
表一
  
  
输出格式
  
  
说明
%h或%H
以十六进制数的形式输出
%d或%D
以十进制数的形式输出
%o或%O
以八进制数的形式输出
%b或%B
以二进制数的形式输出
%c或%C
以ASCII码字符的形式输出
%v或%V
输出网络型数据信号强度
%m或%M
输出等级层次的名字
%s或%S
以字符串的形式输出
%t或%T
以当前的时间格式输出
%e或%E
以指数的形式输出实型数
%f或%F
以十进制数的形式输出实型数
%g或%G
以指数或十进制数的形式输出实型数
  
无论何种格式都以较短的结果输出
·        普通字符,即需要原样输出的字符。其中一些特殊的字符可以通过表二中的转换序列来输出。
下面表中的字符形式用于格式字符串参数中,用来显示特殊的字符。
表二:
  
换码序列
  
功能
\n
换行
\t
横向跳格(即跳到下一个输出区)
\\
反斜杠字符\
\"
双引号字符"
\o
1到3位八进制数代表的字符
%%
百分符号%
在$display和$write的参数列表中,其“输出表列”是需要输出的一些数据,可以是表达式。下面举几个例子说明一下。
[例1]:module disp;
initial
begin
$display("\\\t%%\n\"\123");
end
endmodule
输出结果为
\%
"S
从上面的这个例子中可以看到一些特殊字符的输出形式(八进制数123就是字符S)。
[例2]: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 hex101 decimal
rval=00000000145octal 00000000000000000000000001100101 binary
rval has e asciicharacter value
pd strength valueis StX
current scope isdisp
e is ascii valuefor 101
simulation time is0
输出数据的显示宽度
在$display中,输出列表中数据的显示宽度是自动按照输出格式进行调整的。这样在显示输出数据时,
在经过格式转换以后,总是用表达式的最大可能值所占的位数来显示表达式的当前值。在用十进制数格式输出时,
输出结果前面的0值用空格来代替。对于其它进制,输出结果前面的0仍然显示出来。例如对于一个值的位宽为
12位的表达式,如按照十六进制数输出,则输出结果占3个字符的位置,如按照十进制数输出,则输出结果占
4个字符的位置。这是因为这个表达式的最大可能值为FFF(十六进制)、4095(十进制)。可以通过在%和表示进
制的字符中间插入一个0自动调整显示输出数据宽度的方式。见下例:
$display("d=%0h a=%0h",data,addr);
这样在显示输出数据时,在经过格式转换以后,总是用最少的位数来显示表达式的当前值。下面举例说明:
[例3]:module printval;
reg[11:0]r1;
initial
begin
    r1=10;
    $display("Printing withmaximum size=%d=%h",r1,r1);
    $display("Printing withminimum size=%0d=%0h",r1,r1);
end
enmodule
输出结果为:
Printing with maximum size=10=00a:
printing with minimum size=10=a;
如果输出列表中表达式的值包含有不确定的值或高阻值,其结果输出遵循以下规则:
(1).在输出格式为十进制的情况下:
·                                            如果表达式值的所有位均为不定值,则输出结果为小写的x。
·                                            如果表达式值的所有位均为高阻值,则输出结果为小写的z。
·                                            如果表达式值的部分位为不定值,则输出结果为大写的X。
·                                            如果表达式值的部分位为高阻值,则输出结果为大写的Z。
(2).在输出格式为十六进制和八进制的情况下:
·        每4位二进制数为一组代表一位十六进制数,每3位二进制数为一组代表一位八进制数。
·        如果表达式值相对应的某进制数的所有位均为不定值,则该位进制数的输出的结果为小写的x。
·        如果表达式值相对应的某进制数的所有位均为高阻值,则该位进制数的输出结果为小写的z。
·        如果表达式值相对应的某进制数的部分位为不定值,则该位进制数输出结果为大写的X。
·        如果表达式值相对应的某进制数的部分位为高阻值,则该位进制数输出结果为大写的Z。
对于二进制输出格式,表达式值的每一位的输出结果为0、1、x、z。下面举例说明:
语句输出结果:
$display("%d",1'bx);                                   输出结果为:x
$display("%h",14'bx0_1010);                                输出结果为:xxXa
$display("%h%o",12'b001x_xx10_1x01,12'b001_xxx_101_x01);      输出结果为:XXX 1x5X
注意:因为$write在输出时不换行,要注意它的使用。可以在$write中加入换行符\n,以确保明确的输出显示格式。

使用特权

评论回复
34
山东电子小菜鸟|  楼主 | 2017-10-18 20:17 | 只看该作者
3.8.2.系统任务$monitor
格式:
$monitor(p1,p2,.....,pn);
$monitor;
$monitoron;
$monitoroff;
任务$monitor提供了监控和输出参数列表中的表达式或变量值的功能。
其参数列表中输出控制格式字符串和输出表列的规则和$display中的一样。
当启动一个带有一个或多个参数的$monitor任务时,仿真器则建立一个处理机制,
使得每当参数列表中变量或表达式的值发生变化时,整个参数列表中变量或表达式的
值都将输出显示。如果同一时刻,两个或多个参数的值发生变化,则在该时刻只输出显示一次。
但在$monitor中,参数可以是$time系统函数。这样参数列表中变量或表达式的值同时发生变化
的时刻可以通过标明同一时刻的多行输出来显示。如:   
$monitor($time,,"rxd=%b txd=%b",rxd,txd);
在$display中也可以这样使用。注意在上面的语句中,“,,"代表一个空参数。空参数在输出时显示为空格。
$monitoron和$monitoroff任务的作用是通过打开和关闭监控标志来控制监控任务monitor的启动和停止,
这样使得程序员可以很容易的控制$monitor何时发生。其中$monitoroff任务用于关闭监控标志,
停止监控任务$monitor,$monitoron则用于打开监控标志,启动监控任务$monitor。通常在通过调用
$monitoron启动$monitor时,不管$monitor参数列表中的值是否发生变化,总是立刻输出显示当前时
刻参数列表中的值,这用于在监控的初始时刻设定初始比较值。在缺省情况下,控制标志在仿真的起始
时刻就已经打开了。在多模块调试的情况下,许多模块中都调用了$monitor,因为任何时刻只能有一个
$monitor起作用,因此需配合$monitoron与$monitoroff使用,把需要监视的模块用$monitoron打开,
在监视完毕后及时用$monitoroff关闭,以便把$monitor 让给其他模块使用。$monitor与$display的
不同处还在于$monitor往往在initial块中调用,只要不调用$monitoroff,$monitor便不间断地对所设定的信号进行监视。
3.8.3.时间度量系统函数$time
在Verilog HDL中有两种类型的时间系统函数:$time和$realtime。用这两个时间系统函数可以得到当前的仿真时刻。
·        系统函数$time
$time可以返回一个64比特的整数来表示的当前仿真时刻值。该时刻是以模块的仿真时间尺度为基准的。下面举例说明。
[例1]:`timescale  10ns/1ns
module  test;
reg  set;
parameter  p=1.6;
initial
begin
$monitor($time,,"set=",set);
#p set=0;
#p set=1;
end
endmodule
输出结果为:
0 set=x
2 set=0
3 set=1
在这个例子中,模块test想在时刻为16ns时设置寄存器set为0,在时刻为32ns时设置寄存器set为1。但是由$time记录的
set变化时刻却和预想的不一样。这是由下面两个原因引起的:
1)      $time显示时刻受时间尺度比例的影响。在上面的例子中,时间尺度是10ns,因为$time输出的时刻总是
时间尺度的倍数,这样将16ns和32ns输出为1.6和3.2。
2)      因为$time总是输出整数,所以在将经过尺度比例变换的数字输出时,要先进行取整。在上面的例子中,
1.6和3.2经取整后为2和3输出。注意:时间的精确度并不影响数字的取整。
·       $realtime系统函数
$realtime和$time的作用是一样的,只是$realtime返回的时间数字是一个实型数,该数字也是以时间尺度为基准的。下面举例说明:
[例2]: `timescale10ns/1ns
module test;
reg set;
parameter  p=1.55;
initial
begin
$monitor($realtime,,"set=",set);
#p set=0;
#p set=1;
end
endmodule
输出结果为:
0 set=x
1.6 set=0
3.2 set=1
从上面的例子可以看出,$realtime将仿真时刻经过尺度变换以后即输出,不需进行取整操作。
所以$realtime返回的时刻是实型数。
3.8.4.系统任务$finish
格式:
$finish;
$finish(n);
系统任务$finish的作用是退出仿真器,返回主操作系统,也就是结束仿真过程。任务$finish可以带参数,
根据参数的值输出不同的特征信息。如果不带参数,默认$finish的参数值为1。
下面给出了对于不同的参数值,系统输出的特征信息:
0  不输出任何信息
1  输出当前仿真时刻和位置
2  输出当前仿真时刻,位置和在仿真过程中
所用memory及CPU时间的统计
3.8.5.系统任务$stop
格式:
$stop;
$stop(n);
$stop任务的作用是把EDA工具(例如仿真器)置成暂停模式,在仿真环境下给出一个交互式的命令提示符,将控制权交给用户。这个任务可以带有参数表达式。根据参数值(0,1或2)的不同,输出不同的信息。参数值越大,输出的信息越多。

使用特权

评论回复
35
山东电子小菜鸟|  楼主 | 2017-10-18 20:18 | 只看该作者
3.8.6.系统任务$readmemb和$readmemh
在Verilog HDL程序中有两个系统任务$readmemb和$readmemh用来从文件中读取数据到存贮器中。
这两个系统任务可以在仿真的任何时刻被执行使用,其使用格式共有以下六种:
            1)     $readmemb("<数据文件名>",<存贮器名>);
            2)     $readmemb("<数据文件名>",<存贮器名>,<起始地址>);
            3)     $readmemb("<数据文件名>",<存贮器名>,<起始地址>,<结束地址>);
            4)     $readmemh("<数据文件名>",<存贮器名>);
            5)     $readmemh("<数据文件名>",<存贮器名>,<起始地址>);
            6)     $readmemh("<数据文件名>",<存贮器名>,<起始地址>,<结束地址>);
在这两个系统任务中,被读取的数据文件的内容只能包含:空白位置(空格,换行,制表格(tab)和form-feeds),
注释行(//形式的和/*...*/形式的都允许),二进制或十六进制的数字。数字中不能包含位宽说明和格式说明,
对于$readmemb系统任务,每个数字必须是二进制数字,对于$readmemh系统任务,每个数字必须是十六进制数字。
数字中不定值x或X,高阻值z或Z,和下划线(_)的使用方法及代表的意义与一般Verilog HDL程序中的用法及
意义是一样的。另外数字必须用空白位置或注释行来分隔开。
在下面的讨论中,地址一词指对存贮器(memory)建模的数组的寻址指针。当数据文件被读取时,
每一个被读取的数字都被存放到地址连续的存贮器单元中去。存贮器单元的存放地址范围由系统
任务声明语句中的起始地址和结束地址来说明,每个数据的存放地址在数据文件中进行说明。
当地址出现在数据文件中,其格式为字符“@”后跟上十六进制数。如:
@hh...h
对于这个十六进制的地址数中,允许大写和小写的数字。在字符“@”和数字之间不允许存在空白位置。
可以在数据文件里出现多个地址。当系统任务遇到一个地址说明时,系统任务将该地址后的数据存放到存
贮器中相应的地址单元中去。
对于上面六种系统任务格式,需补充说明以下五点:
1)      如果系统任务声明语句中和数据文件里都没有进行地址说明,则缺省的存放起始地址为该存
贮器定义语句中的起始地址。数据文件里的数据被连续存放到该存贮器中,直到该存贮器单元存满
为止或数据文件里的数据存完。
2)      如果系统任务中说明了存放的起始地址,没有说明存放的结束地址,则数据从起始地址开始
存放,存放到该存贮器定义语句中的结束地址为止。
3)      如果在系统任务声明语句中,起始地址和结束地址都进行了说明,则数据文件里的数据按该
起始地址开始存放到存贮器单元中,直到该结束地址,而不考虑该存贮器的定义语句中的起始地址
和结束地址。
4)      如果地址信息在系统任务和数据文件里都进行了说明,那么数据文件里的地址必须在系统任
务中地址参数声明的范围之内。否则将提示错误信息,并且装载数据到存贮器中的操作被中断。
5)      如果数据文件里的数据个数和系统任务中起始地址及结束地址暗示的数据个数不同的话,也要提示错误信息。
下面举例说明:
先定义一个有256个地址的字节存贮器 mem:
reg[7:0] mem[1:256];
下面给出的系统任务以各自不同的方式装载数据到存贮器mem中。
initial $readmemh("mem.data",mem);
initial $readmemh("mem.data",mem,16);
initial $readmemh("mem.data",mem,128,1);
第一条语句在仿真时刻为0时,将装载数据到以地址是1的存贮器单元为起始存放单元的存贮器中去。
第二条语句将装载数据到以单元地址是16的存贮器单元为起始存放单元的存贮器中去,一直到地址是
256的单元为止。第三条语句将从地址是128的单元开始装载数据,一直到地址为1的单元。在第三种情况中,
当装载完毕,系统要检查在数据文件里是否有128个数据,如果没有,系统将提示错误信息。

使用特权

评论回复
36
山东电子小菜鸟|  楼主 | 2017-10-18 20:20 | 只看该作者
3.8.7.系统任务 $random
这个系统函数提供了一个产生随机数的手段。当函数被调用时返回一个32bit的随机数。它是一个带符号的整形数。
$random一般的用法是:$ramdom % b ,其中 b>0.它给出了一个范围在(-b+1):(b-1)中的随机数。下面给出一个产生随机数的例子:
reg[23:0] rand;
rand = $random % 60;
上面的例子给出了一个范围在-59到59之间的随机数,下面的例子通过位并接操作产生一个值在0到59之间的数。
reg[23:0] rand;
rand = {$random} % 60;
利用这个系统函数可以产生随机脉冲序列或宽度随机的脉冲序列,以用于电路的测试。下面例子中的Verilog HDL
模块可以产生宽度随机的随机脉冲序列的测试信号源,在电路模块的设计仿真时非常有用。同学们可以根据测试的需要,
模仿下例,灵活使用$random系统函数编制出与实际情况类似的随机脉冲序列。
[例] `timescale 1ns/1ns
module random_pulse( dout );
output [9:0] dout;
reg dout;
integer delay1,delay2,k;
initial
begin
#10dout=0;
for(k=0; k< 100; k=k+1)
begin
delay1 =20 * ( {$random} % 6);
//delay1 在0到100ns间变化
delay2 =20 * ( 1 + {$random} % 3);
//delay2 在20到60ns间变化
#delay1  dout = 1 << ({$random} %10);
//dout的0--9位中随机出现1,并出现的时间在0-100ns间变化
#delay2  dout = 0;
//脉冲的宽度在在20到60ns间变化
end
end
endmodule
3.9.编译预处理
Verilog HDL语言和C语言一样也提供了编译预处理的功能。“编译预处理”是Verilog HDL编译系统的一个组成部分。
Verilog HDL语言允许在程序中使用几种特殊的命令(它们不是一般的语句)。VerilogHDL编译系统通常先对这些特殊的
命令进行“预处理”,然后将预处理的结果和源程序一起在进行通常的编译处理。
在Verilog HDL语言中,为了和一般的语句相区别,这些预处理命令以符号“ `”开头(注意这个符号是不同于单引号“'”的)。
这些预处理命令的有效作用范围为定义命令之后到本文件结束或到其它命令定义替代该命令之处。Verilog HDL提供了以下预编译命令:
`accelerate,`autoexpand_vectornets,`celldefine,`default_nettype,`define,`else,`endcelldefine,`endif,`
endprotect,
`endprotected,`expand_vectornets,`ifdef,`include,`noaccelerate,`noexpand_vectornets,`noremove_gatenames,`
noremove_netnames,`nounconnected_drive,`protect,`protecte,`remove_gatenames,`remove_netnames,`reset,`timescale,
`unconnected_drive
在这一小节里只对常用的`define、`include、`timescale进行介绍,其余的请查阅参考书。

使用特权

评论回复
37
山东电子小菜鸟|  楼主 | 2017-10-18 20:22 | 只看该作者
3.9.1.宏定义 `define
用一个指定的标识符(即名字)来代表一个字符串,它的一般形式为:
`define 标识符(宏名) 字符串(宏内容)
如:`define signal string
它的作用是指定用标识符signal来代替string这个字符串,在编译预处理时,
把程序中在该命令以后所有的signal都替换成string。这种方法使用户能以一个简单的
名字代替一个长的字符串,也可以用一个有含义的名字来代替没有含义的数字和符号,
因此把这个标识符(名字)称为“宏名”,在编译预处理时将宏名替换成字符串的过程称为“宏展开”。
`define是宏定义命令。
[例1]:`define  WORDSIZE 8
module
reg[1:`WORDSIZE]  data; //这相当于定义 reg[1:8] data;
关于宏定义的八点说明:
1)      宏名可以用大写字母表示,也可以用小写字母表示。建议使用大写字母,以与变量名相区别。
2)      `define命令可以出现在模块定义里面,也可以出现在模块定义外面。宏名的有效范围为定义命令
之后到原文件结束。通常,`define命令写在模块定义的外面,作为程序的一部分,在此程序内有效。
3)     在引用已定义的宏名时,必须在宏名的前面加上符号“`”,表示该名字是一个经过宏定义的名字。
4)      使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量。而且记住一个宏名要比记住
一个无规律的字符串容易,这样在读程序时能立即知道它的含义,当需要改变某一个变量时,可以只改变 `
define命令行,一改全改。如例1中,先定义WORDSIZE代表常量8,这时寄存器data是一个8位的寄存器。
如果需要改变寄存器的大小,只需把该命令行改为:`define  WORDSIZE 16。这样寄存器data则变为一个16位的寄存器。
由此可见使用宏定义,可以提高程序的可移植性和可读性。
5)      宏定义是用宏名代替一个字符串,也就是作简单的置换,不作语法检查。预处理时照样代入,不管含义是否正确。

只有在编译已被宏展开后的源程序时才报错。
6)      宏定义不是VerilogHDL语句,不必在行末加分号。如果加了分号会连分号一起进行置换如:
[例2]:module  test;
  reg  a, b, c, d, e, out;
`define  expression a+b+c+d;
assign out = `expression + e;
  ...
    endmodule
经过宏展开以后,该语句为:
assign  out = a+b+c+d;+e;
显然出现语法错误。
7)      在进行宏定义时,可以引用已定义的宏名,可以层层置换。如:
[例3]:module test;
reg  a, b, c;
wire out;
`define aa a + b
`define cc c + `aa
assign out = `cc;
    endmodule
这样经过宏展开以后,assign语句为
assign  out = c + a + b;
8)      宏名和宏内容必须在同一行中进行声明。如果在宏内容中包含有注释行,注释行不会作为被置换的内容。如:
[例4]: module
       `define typ_nand nand #5  //definea nand with typical delay
     `typ_nand g121(q21,n10,n11);
        ………
        endmodule
经过宏展开以后,该语句为:
nand #5 g121(q21,n10,n11);
宏内容可以是空格,在这种情况下,宏内容被定义为空的。当引用这个宏名时,不会有内容被置换。
注意:组成宏内容的字符串不能够被以下的语句记号分隔开的。
·        注释行
·        数字
·        字符串
·        确认符
·        关键词
·        双目和三目字符运算符
如下面的宏定义声明和引用是非法的。
`define  first_half  "start of string
$display(`first_half end of string");
注意在使用宏定义时要注意以下情况:
1)      对于某些 EDA软件,在编写源程序时,如使用和预处理命令名相同的宏名会发生冲突,因此建议不要使用和预处理命令名相同的宏名。
2)      宏名可以是普通的标识符(变量名)。例如signal_name 和 'signal_name的意义是不同的。但是这样容易引起混淆,建议不要这样使用。
3.9.2.“文件包含”处理`include
所谓“文件包含”处理是一个源文件可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。
Verilog HDL语言提供了`include命令用来实现“文件包含”的操作。其一般形式为:

`include “文件名”
   
图3-9-2表示“文件包含”的含意。图3-9-2(a)为文件File1.v,它有一个`include "File2.v"命令,然后还有其它的内容
(以A表示)。图3-9-2(b)为另一个文件File2.v,文件的内容以B表示。在编译预处理时,要对`include命令进行
“文件包含”预处理:将File2.v的全部内容复制插入到 `include"File2.v"命令出现的地方,即File2.v 被包含到File1.v中,
得到图3-9-2(c)所示的结果。在接着往下进行的编译中,将“包含”以后的File1.v作为一个源文件单位进行编译。
“文件包含”命令是很有用的,它可以节省程序设计人员的重复劳动。可以将一些常用的宏定义命令或任务(task)组成一个文件,
然后用`include命令将这些宏定义包含到自己所写的源文件中,相当于工业上的标准元件拿来使用。另外在编写Verilog HDL源文件时,
一个源文件可能经常要用到另外几个源文件中的模块,遇到这种情况即可用`include命令将所需模块的源文件包含进来。
[例1]:
(1)文件aaa.v
module aaa(a,b,out);
input a, b;
output out;
wire out;
assign  out = a^b;
endmodule
(2)文件 bbb.v
`include  "aaa.v"
module  bbb(c,d,e,out);
input  c,d,e;
output  out;
wire  out_a;
wire  out;
           aaa aaa(.a(c),.b(d),.out(out_a));
assign  out=e&out_a;
endmodule
在上面的例子中,文件bbb.v用到了文件aaa.v中的模块aaa的实例器件,通过“文件包含”处理来调用。
模块aaa实际上是作为模块bbb的子模块来被调用的。在经过编译预处理后,文件bbb.v实际相当于下面的程序文件bbb.v:
module aaa(a,b,out);
input a, b;
output  out;
wire out;
assign  out = a ^ b;
endmodule
module bbb( c, d, e, out);
input c, d, e;
output out;
wire out_a;
wire out;
           aaa aaa(.a(c),.b(d),.out(out_a));
assign out= e & out_a;
endmodule
关于“文件包含”处理的四点说明:
1)      一个`include命令只能指定一个被包含的文件,如果要包含n个文件,要用n个`include命令。
注意下面的写法是非法的`include"aaa.v""bbb.v"
2)      `include命令可以出现在Verilog HDL源程序的任何地方,被包含文件名可以是相对路径名,
也可以是绝对路径名。例如:'include"parts/count.v"
3)      可以将多个`include命令写在一行,在`include命令行,只可以出空格和注释行。
例如下面的写法是合法的。
'include "fileB" 'include "fileC"//including fileB and fileC
4)      如果文件1包含文件2,而文件2要用到文件3的内容,则可以在文件1用两个`include命
令分别包含文件2和文件3,而且文件3应出现在文件2之前。例如在下面的例子中,即在file1.v中定义:
`include"file3.v"
`include"file2.v"
module test(a,b,out);
input[1:`size2] a, b;
output[1:`size2] out;
wire[1:`size2] out;
assign  out= a+b;
endmodule
file2.v的内容为:
`define size2 `size1+1
.
.
.
file3.v的内容为:
`define size1  4
.
.
.
这样,file1.v和file2.v都可以用到file3.v的内容。在file2.v中不必再用 `include    "file3.v"了。
5)      在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。例如上面的问题也可以这样处理,见图3-9-3.

它的作用和图3-9-4的作用是相同的。


使用特权

评论回复
38
山东电子小菜鸟|  楼主 | 2017-10-18 20:23 | 只看该作者
3.9.3.时间尺度 `timescale
`timescale命令用来说明跟在该命令后的模块的时间单位和时间精度。
使用`timescale命令可以在同一个设计里包含采用了不同的时间单位的模块。
例如,一个设计中包含了两个模块,其中一个模块的时间延迟单位为ns,另一个模块的时间延迟单位为ps。
EDA工具仍然可以对这个设计进行仿真测试。
`timescale 命令的格式如下:
`timescale<时间单位>/<时间精度>
在这条命令中,时间单位参量是用来定义模块中仿真时间和延迟时间的基准单位的。
时间精度参量是用来声明该模块的仿真时间的精确程度的,该参量被用来对延迟时间
值进行取整操作(仿真前),因此该参量又可以被称为取整精度。如果在同一个程序设计里,
存在多个`timescale命令,则用最小的时间精度值来决定仿真的时间单位。
另外时间精度至少要和时间单位一样精确,时间精度值不能大于时间单位值。
在`timescale命令中,用于说明时间单位和时间精度参量值的数字必须是整数,
其有效数字为1、10、100,单位为秒(s)、毫秒(ms)、微秒(us)、纳秒(ns)、皮秒(ps)、毫皮秒(fs)。这几种单位的意义说明见下表。
  
时间单位
  
定义
s
秒(1S)
ms
千分之一秒(10-3S)
us
百万分之一秒(10-6S)
ns
十亿分之一秒(10-9S)
ps
万亿分之一秒(10-12S)
fs
千万亿分之一秒(10-15S)
下面举例说明`timescale命令的用法。
[例1]: `timescale  1ns/1ps
在这个命令之后,模块中所有的时间值都表示是1ns的整数倍。这是因为在`timescale命令中,
定义了时间单位是1ns。模块中的延迟时间可表达为带三位小数的实型数,因为 `timescale命令定义时间精度为1ps.
[例2]: `timescale 10us/100ns
在这个例子中,`timescale命令定义后,模块中时间值均为10us的整数倍。
因为`timesacle 命令定义的时间单位是10us。延迟时间的最小分辨度为十分之一微秒(100ns),即延迟时间可表达为带一位小数的实型数。
例3: `timescale 10ns/1ns
module  test;
reg  set;
parameter  d=1.55;
initial
begin
#d set=0;
#d set=1;
end
endmodule
在这个例子中,`timescale命令定义了模块test的时间单位为10ns、时间精度为1ns。
因此在模块test中,所有的时间值应为10ns的整数倍,且以1ns为时间精度。
这样经过取整操作,存在参数d中的延迟时间实际是16ns(即1.6×10ns),
这意味着在仿真时刻为16ns时寄存器set被赋值0,在仿真时刻为32ns时寄存器set被赋值1。
仿真时刻值是按照以下的步骤来计算的。
1)      根据时间精度,参数d值被从1.55取整为1.6。
2)      因为时间单位是10ns,时间精度是1ns,所以延迟时间#d作为
时间单位的整数倍为16ns。
3)      EDA工具预定在仿真时刻为16ns的时候给寄存器set赋值0
(即语句 #d set=0;执行时刻),在仿真时刻为32ns的时候给
寄存器set赋值1(即语句 #d set=1;执行时刻),
注意:如果在同一个设计里,多个模块中用到的时间单位不同,需要用到以下的时间结构。
1)      用`timescale命令来声明本模块中所用到的时间单位和时间精度。
2)      用系统任务$printtimescale来输出显示一个模块的时间单位和时间精度。
3)      用系统函数$time和$realtime及%t格式声明来输出显示EDA工具记录的时间信息。
3.9.4.条件编译命令`ifdef、`else、`endif
一般情况下,Verilog HDL源程序中所有的行都将参加编译。但是有时希望对其中的一部分
内容只有在满足条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。
有时,希望当满足条件时对一组语句进行编译,而当条件不满足是则编译另一部分。
条件编译命令有以下几种形式:
1)      `ifdef 宏名 (标识符)
程序段1
`else
程序段2
`endif
它的作用是当宏名已经被定义过(用`define命令定义),则对程序段1进行编译,程序段2将被忽略;
否则编译程序段2,程序段1被忽略。其中`else部分可以没有,即:
2)      `ifdef 宏名 (标识符)
程序段1
`endif
这里的 “宏名” 是一个Verilog HDL的标识符,“程序段”可以是Verilog HDL语句组,也可以是命令行。

通常在Verilog HDL程序中用到`ifdef、`else、`endif编译命令的情况有以下几种:
·        选择一个模块的不同代表部分。
·        选择不同的时序或结构信息。
·        对不同的EDA工具,选择不同的激励。
3.10.小结
Verilog HDL的语法与C语言的语法有许多类似的地方,但也有许多不同的地方。我们学习Verilog
HDL语法要善于找到不同点,着重理解如:阻塞〔Blocking〕和非阻塞〔Non-Blocking〕赋值的不同
;顺序块和并行块的不同;块与块之间的并行执行的概念;task和function的概念。
Verilog HDL还有许多系统函数和任务也是C语言中没有的如:$monitor、$readmemb、$stop等等,
而这些系统任务在调试模块的设计中是非常有用的,我们只有通过阅读大量的Verilog调试模块实例,
经过长期的实践,经常查阅附录中的Verilog语言参考手册才能逐步掌握,。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则