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块常用于测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。
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 or
posedge reset) //由两个沿触发的always块
begin
……
end
always @( a or b or c ) //由多个电平触发的always块
begin
……
end
沿触发的always块常常描述时序逻辑,如果符合可综合风格要求可用综合工具自动转换为表示时序逻辑的寄存器组和门级逻辑,而电平触发的always块常常用来描述组合逻辑和带锁存器的组合逻辑,如果符合可综合风格要求可转换为表示组合逻辑的门级逻辑或带锁存器的组合逻辑。一个模块中可以有多个always块,它们都是并行运行的。
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);
下面分两节分别介绍任务和函数语句的要点。
二. task说明语句
如果传给任务的变量值和任务完成后接收结果的变量已定义,就可以用一条语句启动任务。任务完成以后控制就传回启动过程。如任务内部有定时控制,则启动的时间可以与控制返回的时间不同。任务可以启动其它的任务,其它任务又可以启动别的任务,可以启动的任务数是没有限制的。不管有多少任务启动,只有当所有的启动任务完成以后,控制才能返回。
- 任务的定义
定义任务的语法如下:
任务:
task <任务名>;
<端口及数据类型声明语句>
<语句1>
<语句2>
…
<语句n>
endtask
这些声明语句的语法与模块定义中的对应声明语句的语法是一致的。
- 任务的调用及变量的传递
启动任务并传递输入输出变量的声明语句的语法如下:
任务的调用:
<任务名>(端口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
这个例子描述了一个简单的交通灯的时序控制,并且该交通灯有它自己的时钟产生器。
二. function说明语句
函数的目的是返回一个用于表达式的值。
Ÿ
定义函数的语法:
function <返回值的类型或范围> (函数名);
<端口说明语句>
<变量类型说明语句>
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设计数字系统的方法和技术。
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. d i s p l a y 和 display和 display和write任务
格式:
$display(p1,p2,…pn);
$write(p1,p2,…pn);
这两个函数和系统任务的作用是用来输出信息,即将参数p2到pn按参数p1给定的格式输出。参数p1通常称为“格式控制”,参数p2至pn通常称为“输出表列”。这两个任务的作用基本相同。 d i s p l a y 自 动 地 在 输 出 后 进 行 换 行 , display自动地在输出后进行换行, display自动地在输出后进行换行,write则不是这样。如果想在一行里输出多个信息,可以使用 w r i t e 。 在 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位八进制数代表的字符
%%
百分符号%
在 d i s p l a y 和 display和 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);
d i s p l a y ( " s i m u l a t i o n t i m e i s display("simulation time is %t", display("simulationtimeistime);
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
输出数据的显示宽度
在$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 with
maximum size=%d=%h”,r1,r1);
$display(“Printing with
minimum 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
注意:因为 w r i t e 在 输 出 时 不 换 行 , 要 注 意 它 的 使 用 。 可 以 在 write在输出时不换行,要注意它的使用。可以在 write在输出时不换行,要注意它的使用。可以在write中加入换行符\n,以确保明确的输出显示格式。
3.8.2.系统任务$monitor
格式:
$monitor(p1,p2,…,pn);
$monitor;
$monitoron;
$monitoroff;
任务 m o n i t o r 提 供 了 监 控 和 输 出 参 数 列 表 中 的 表 达 式 或 变 量 值 的 功 能 。 其 参 数 列 表 中 输 出 控 制 格 式 字 符 串 和 输 出 表 列 的 规 则 和 monitor提供了监控和输出参数列表中的表达式或变量值的功能。其参数列表中输出控制格式字符串和输出表列的规则和 monitor提供了监控和输出参数列表中的表达式或变量值的功能。其参数列表中输出控制格式字符串和输出表列的规则和display中的一样。当启动一个带有一个或多个参数的 m o n i t o r 任 务 时 , 仿 真 器 则 建 立 一 个 处 理 机 制 , 使 得 每 当 参 数 列 表 中 变 量 或 表 达 式 的 值 发 生 变 化 时 , 整 个 参 数 列 表 中 变 量 或 表 达 式 的 值 都 将 输 出 显 示 。 如 果 同 一 时 刻 , 两 个 或 多 个 参 数 的 值 发 生 变 化 , 则 在 该 时 刻 只 输 出 显 示 一 次 。 但 在 monitor任务时,仿真器则建立一个处理机制,使得每当参数列表中变量或表达式的值发生变化时,整个参数列表中变量或表达式的值都将输出显示。如果同一时刻,两个或多个参数的值发生变化,则在该时刻只输出显示一次。但在 monitor任务时,仿真器则建立一个处理机制,使得每当参数列表中变量或表达式的值发生变化时,整个参数列表中变量或表达式的值都将输出显示。如果同一时刻,两个或多个参数的值发生变化,则在该时刻只输出显示一次。但在monitor中,参数可以是$time系统函数。这样参数列表中变量或表达式的值同时发生变化的时刻可以通过标明同一时刻的多行输出来显示。如:
m o n i t o r ( monitor( monitor(time,“rxd=%b txd=%b”,rxd,txd);
在$display中也可以这样使用。注意在上面的语句中,“,"代表一个空参数。空参数在输出时显示为空格。
m o n i t o r o n 和 monitoron和 monitoron和monitoroff任务的作用是通过打开和关闭监控标志来控制监控任务monitor的启动和停止,这样使得程序员可以很容易的控制 m o n i t o r 何 时 发 生 。 其 中 monitor何时发生。其中 monitor何时发生。其中monitoroff任务用于关闭监控标志,停止监控任务 m o n i t o r , monitor, monitor,monitoron则用于打开监控标志,启动监控任务 m o n i t o r 。 通 常 在 通 过 调 用 monitor。通常在通过调用 monitor。通常在通过调用monitoron启动 m o n i t o r 时 , 不 管 monitor时,不管 monitor时,不管monitor参数列表中的值是否发生变化,总是立刻输出显示当前时刻参数列表中的值,这用于在监控的初始时刻设定初始比较值。在缺省情况下,控制标志在仿真的起始时刻就已经打开了。在多模块调试的情况下,许多模块中都调用了 m o n i t o r , 因 为 任 何 时 刻 只 能 有 一 个 monitor,因为任何时刻只能有一个 monitor,因为任何时刻只能有一个monitor起作用,因此需配合 m o n i t o r o n 与 monitoron与 monitoron与monitoroff使用,把需要监视的模块用 m o n i t o r o n 打 开 , 在 监 视 完 毕 后 及 时 用 monitoron打开,在监视完毕后及时用 monitoron打开,在监视完毕后及时用monitoroff关闭,以便把 m o n i t o r 让 给 其 他 模 块 使 用 。 monitor 让给其他模块使用。 monitor让给其他模块使用。monitor与 d i s p l a y 的 不 同 处 还 在 于 display的不同处还在于 display的不同处还在于monitor往往在initial块中调用,只要不调用 m o n i t o r o f f , monitoroff, monitoroff,monitor便不间断地对所设定的信号进行监视。
3.8.3.时间度量系统函数$time
在Verilog HDL中有两种类型的时间系统函数: t i m e 和 time和 time和realtime。用这两个时间系统函数可以得到当前的仿真时刻。
·
系统函数$time
$time可以返回一个64比特的整数来表示的当前仿真时刻值。该时刻是以模块的仿真时间尺度为基准的。下面举例说明。
[例1]:`timescale 10ns/1ns
module test;
reg set;
parameter p=1.6;
initial
begin
m o n i t o r ( monitor( 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)
t i m e 显 示 时 刻 受 时 间 尺 度 比 例 的 影 响 。 在 上 面 的 例 子 中 , 时 间 尺 度 是 10 n s , 因 为 time显示时刻受时间尺度比例的影响。在上面的例子中,时间尺度是10ns,因为 time显示时刻受时间尺度比例的影响。在上面的例子中,时间尺度是10ns,因为time输出的时刻总是时间尺度的倍数,这样将16ns和32ns输出为1.6和3.2。
2)
因为$time总是输出整数,所以在将经过尺度比例变换的数字输出时,要先进行取整。在上面的例子中,1.6和3.2经取整后为2和3输出。注意:时间的精确度并不影响数字的取整。
·
$realtime系统函数
r e a l t i m e 和 realtime和 realtime和time的作用是一样的,只是$realtime返回的时间数字是一个实型数,该数字也是以时间尺度为基准的。下面举例说明:
module test;
reg set;
parameter p=1.55;
initial
begin
m o n i t o r ( monitor( monitor(realtime,“set=”,set);
#p set=0;
#p set=1;
end
endmodule
输出结果为:
0 set=x
1.6 set=0
3.2 set=1
从上面的例子可以看出, r e a l t i m e 将 仿 真 时 刻 经 过 尺 度 变 换 以 后 即 输 出 , 不 需 进 行 取 整 操 作 。 所 以 realtime将仿真时刻经过尺度变换以后即输出,不需进行取整操作。所以 realtime将仿真时刻经过尺度变换以后即输出,不需进行取整操作。所以realtime返回的时刻是实型数。
3.8.4.系统任务$finish
格式:
$finish;
$finish(n);
系统任务 f i n i s h 的 作 用 是 退 出 仿 真 器 , 返 回 主 操 作 系 统 , 也 就 是 结 束 仿 真 过 程 。 任 务 finish的作用是退出仿真器,返回主操作系统,也就是结束仿真过程。任务 finish的作用是退出仿真器,返回主操作系统,也就是结束仿真过程。任务finish可以带参数,根据参数的值输出不同的特征信息。如果不带参数,默认$finish的参数值为1。下面给出了对于不同的参数值,系统输出的特征信息:
0 不输出任何信息
1 输出当前仿真时刻和位置
2 输出当前仿真时刻,位置和在仿真过程中
所用memory及CPU时间的统计
3.8.5.系统任务$stop
格式:
$stop;
$stop(n);
$stop任务的作用是把EDA工具(例如仿真器)置成暂停模式,在仿真环境下给出一个交互式的命令提示符,将控制权交给用户。这个任务可以带有参数表达式。根据参数值(0,1或2)的不同,输出不同的信息。参数值越大,输出的信息越多。
3.8.6.系统任务 r e a d m e m b 和 readmemb和 readmemb和readmemh
在Verilog HDL程序中有两个系统任务 r e a d m e m b 和 readmemb和 readmemb和readmemh用来从文件中读取数据到存贮器中。这两个系统任务可以在仿真的任何时刻被执行使用,其使用格式共有以下六种:
1) $readmemb("<数据文件名>",<存贮器名>);
2) $readmemb("<数据文件名>",<存贮器名>,<起始地址>);
3) $readmemb("<数据文件名>",<存贮器名>,<起始地址>,<结束地址>);
4) $readmemh("<数据文件名>",<存贮器名>);
5) $readmemh("<数据文件名>",<存贮器名>,<起始地址>);
6) $readmemh("<数据文件名>",<存贮器名>,<起始地址>,<结束地址>);
在这两个系统任务中,被读取的数据文件的内容只能包含:空白位置(空格,换行,制表格(tab)和form-feeds),注释行(//形式的和/…/形式的都允许),二进制或十六进制的数字。数字中不能包含位宽说明和格式说明,对于 r e a d m e m b 系 统 任 务 , 每 个 数 字 必 须 是 二 进 制 数 字 , 对 于 readmemb系统任务,每个数字必须是二进制数字,对于 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个数据,如果没有,系统将提示错误信息。
3.8.7.系统任务 $random
这个系统函数提供了一个产生随机数的手段。当函数被调用时返回一个32bit的随机数。它是一个带符号的整形数。
r a n d o m 一 般 的 用 法 是 : random一般的用法是: 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
#10
dout=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语言允许在程序中使用几种特殊的命令(它们不是一般的语句)。Verilog
HDL编译系统通常先对这些特殊的命令进行“预处理”,然后将预处理的结果和源程序一起在进行通常的编译处理。
在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进行介绍,其余的请查阅参考书。
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)
宏定义不是Verilog
HDL语句,不必在行末加分号。如果加了分号会连分号一起进行置换。如:
例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)
宏名和宏内容必须在同一行中进行声明。如果在宏内容中包含有注释行,注释行不会作为被置换的内容。如:
`define typ_nand nand #5 //define
a 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©所示的结果。在接着往下进行的编译中,将“包含”以后的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©,.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©,.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的作用是相同的。
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)
用系统函数 t i m e 和 time和 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程序的语法规则。
通常在Verilog HDL程序中用到ifdef、
else、`endif编译命令的情况有以下几种:
·
选择一个模块的不同代表部分。
·
选择不同的时序或结构信息。
·
对不同的EDA工具,选择不同的激励。
3.10.小结
Verilog HDL的语法与C语言的语法有许多类似的地方,但也有许多不同的地方。我们学习Verilog HDL语法要善于找到不同点,着重理解如:阻塞〔Blocking〕和非阻塞〔Non-Blocking〕赋值的不同;顺序块和并行块的不同;块与块之间的并行执行的概念;task和function的概念。Verilog HDL还有许多系统函数和任务也是C语言中没有的如: m o n i t o r 、 monitor、 monitor、readmemb、$stop等等,而这些系统任务在调试模块的设计中是非常有用的,我们只有通过阅读大量的Verilog调试模块实例,经过长期的实践,经常查阅附录中的Verilog语言参考手册才能逐步掌握,。
3.11.思考题
在这一小结中我们将针对以上介绍的基本语法做一些练习。希望读者能在仔细阅读以上的内容后,认真思考以下的习题,这将有效地帮助你正确地理解基本语法的要点。
1)以下给出了一个填空练习,请将所给各个选项根据电路图,填入程序中的适当位置。
标准答案:
module AOI(A,B,C,D,F);
input A,B,C,D;
output F;
assign F = ((A&B)&(C&D));
endmodule
2〕 在这一题中,我们将作有关层次电路的练习,通过这个练习,你将加深对模块间调用时,管脚间连接的理解。假设已有全加器模块FullAdder,若有一个顶层模块调用此全加器,连接线分别为W4,W5,W3,W1和W2。请在调用时正确地填入I/O的对应信号。
module FullAdder(A,B,Cin,Sum,Cout);
input A, B, Cin;
output Sum, Cout;
endmodule
module Top…
FullAdder FA(
,//W1
,//W2
,//W3
,//W4
);//W5
endmodule
标准答案:
moduleTop…
FullAdderFA( .Sum(W1), //W1
.Cout(W2),//W2
.Cin(W3), //W3
.A(W4), //W4
.B(W5)); //W5
endmodule
3)下面这道题是一个测试模块,因此没有输入输出端口,请将相应项填入合适的位置。
module TestFixture;
initial
begin
end
initial
endmodule
标准答案:
module TestFixture
reg A,B,SEL;
wire F;
MUX2M(SEL,A,B,F);
initial
begin
SEL=0; A=0; B=0;
#10 A=1;
#10 SEL=1; #10 B=1;
end
initial
$monitor(SEL,A,B,F);
endmodule
4)指出下面几个信号的最高位和最低位。
reg [1:0] SEL; input [0:2] IP; wire [16:23] A;
标准答案:
MSB:SEL[1] MSB:IP[0] MSB:A[16]
LSB:SEL[0] LSB:IP[2] LSB:A[23]
5)P,Q,R都是4bit的输入矢量,下面哪一种表达形式是正确的。
1)input P[3:0],Q,R;
2)input P,Q,R[3:0];
3)input P[3:0],Q[3:0],R[3:0];
4)input [3:0] P,[3:0]Q,[0:3]R;
5)input [3:0] P,Q,R;
标准答案:5)
6)请将下面选项中的正确答案填人空的方括号中。
- (0:2) 2. (P:0) 3. (Op1:Op2) 4.(7:7)
- (2:0) 6. (7:0)
reg [7:0] A;
reg [2:0] Sum, Op1, Op2;
reg P, OneBit;
initial
begin
Sum=Op1+Op2;
P=1;
A[ ]=Sum;
…
end
标准答案:5
7)请根据以下两条语句,从选项中找出正确答案。
7.1) reg [7:0] A;
A=2’hFF;
- 8’b0000_0011 2) 8’h03
- 8’b1111_1111 4) 8’b11111111
标准答案:1)
7.2) reg [7:0] B;
B=8’bZ0;
-
8’0000_00Z0
-
8’bZZZZ_0000
8’b0000_ZZZ0 4) 8’bZZZZ_ZZZ0
标准答案:4)
8)请指出下面几条语句中变量的类型。
8.1) assign A=B;
8.2) always #1
Count=C+1;
标准答案:
A(wire) B(wire/reg) Count(reg) C(wire/reg)
9)指出下面模块中Cin,Cout,C3,C5,的类型。
module
FADD(A,B,Cin,Sum,Cout);
input A, B, Cin;
output Sum, Cout;
…
endmodule
module Test;
…
FADDM(C1,C2,C3,C4,C5);
…
endmodule
标准答案:
Cin(wire)
Cout(wire/reg) C3(wire/reg) C5(wire)
10〕在下一个程序段中,当ADDRESS的值等于5’b0X000时,问casex执行完后A和B的值是多少。
A=0;
B=0;
casex(ADDRESS)
5’b00???: A=1;
5’b01???: B=1;
5’b10?00,5’b11?00:
begin
A=1;
B=1;
end
endcase
标准答案: A=1 and
B=0;
11)在下题中,事件A分别在10,20,30发生,而B一直保持X状态,问在50时Count的值是多少。
reg [7:0] Count;
initial
Count=0;
always
begin
@(A) Count=Count+1;
@(B) Count=Count+1;
end
标准答案:Count=1;
(这是因为当A第一次发生时,Count的值由0变为1,然后事件控制 @(B) 阻挡了进程。)
12)在下题中initial块执行完后I,J,A,B的值会是多少。
reg [2:0] A;
reg [3:0] B;
integer I, J;
initial
begin
I=0;
A=0;
I=I-1;
J=I;
A=A-1;
B=A;
J=J+1;
B=B+1;
end
标准答案:
I=-1 (整数可为负数)
J=0
A=7 (A为reg型为非负数,又因为A为3位即为111)
B=8 (在B=A时,B=0111,然后B=B+1,所以B=4’b1000)
13)在下题中,当V的值发生变化且为-1时,执行完always块后
Count的值应是多少?
reg[7:0]V;
reg[2:0]Count;
always @(V)
begin
Count=0;
while(~V[Count])
Count=Count+1;
end
标准答案:Count=0;
14)在下题中循环执行完后,V的值是多少?
reg [3:0] A;
reg V ,W;
integer K;
…
A=4’b1010;
for(K=2;K>=0;K=K-1)
begin
V=V^A[k];
W=A[K]^A[K+1];
end
标准答案:V的值是它进人循环体前值的取反。
(因为V的值与0,1,0 进行了异或,与1的异或改变了V的值。)
15)在下题中,给出了几种硬件实现,问以下的模块被综合后可能是哪一种?
always @(posedge Clock)
if(A)
C=B;
1.不能综合。
2.一个上升沿触发器和一个多路器。
3.一个输入是A,B,Clock的三输入与门。
4.一个透明锁存器。
5.一个带clock有始能引脚的上升沿触发器。
标准答案:2,5
16)在下题中,always状态将描述一个带异步Nreset和Nset输入端的上升沿触发器,则空括号内应填入什么,可从以下五种答案中选择。
always @( )
if(!Nreset)
Q<=0;
else if(!Nset)
Q<=1;
else
Q<=D;
1.negedge Nset or posedge Clock
2.posedge Clock
3.negedge Nreset or posedge Clock
4.negedge Nreset or negedge Nset or posedge Clock
5.negedge Nreset or negedge Nset
标准答案:4
17)在下题中,给出了几种硬件实现,问以下的模块被综合后可能是哪一种?
1.带异步复位端的触发器。
2.不能综合或与预先设想的不一致。
3.组合逻辑。
4.带逻辑的透明锁存器。
5.带同步复位端的触发器。
1.always @(posedge Clock)
begin
A<=B;
if©
A<=1’b0;
end
标准答案:5
2.always @( A or B)
case(A)
1’b0: F=B;
1’b1: G=B;
endcase
标准答案:2
3.always @( posedge A or posedge B )
if(A)
C<=1’b0;
else
C<=D;
标准答案:1
4.always @(posedge Clk or negedge Rst)
if(Rst)
A<=1’b0;
else
A<=B;
标准答案:2 (产生了异步逻辑)
18)在下题中,模块被综合后将产生几个触发器?
always @(posedge Clk)
begin: Blk
reg B, C;
C = B;
D <= C;
B = A;
end
1. 2个寄存器 B 和 D
2. 2个寄存器 B和 C
3. 3个寄存器 B, C 和 D
4. 1个寄存器 D
5. 2个寄存器 C 和D
标准答案:2
19)在下题中,各条语句的顺序是错误的。请根据电路图调整好它们的次序。
标准答案:
reg FF1,FF2,FF3;
always @(posedge Clock)
begin
Output= FF3;
FF3 = FF2;
FF2 = FF1;
FF1 = Input;
end
20)根据左表中SEL与OP的对应关系,在右边模块的空括号中填入相应的值。
SEL:OP
000:1
001:3 casex(SEL)
010:1 3’b( ):
OP=3;
011:3 3’b( ):
OP=1;
100:0 3’b( ):
OP=0;
101:3 endcase
110:0
111:3
标准答案:
casex(SEL)
3’bXX1: OP=3;
3’b0X0: OP=1;
3’b1X0: OP=0;
endcase
21)在以下表达式中选出正确的.
-
4’b1010 & 4’b1101 = 1’b1
-
~4’b1100 = 1’b1
-
!4’b1011 || !4’b0000 = 1’b1
-
& 4’b1101 = 1’b1
-
1b’0 || 1b’1 = 1’b1
-
4’b1011 && 4’b0100 = 4’b1111
-
4’b0101<<1 =5’b01011
-
!4’b0010 is 1’b0
-
4’b0001 || 4’b0000 = 1’b1
标准答案:3), 5), 8), 9)
22)在下一个模块旁的括号中填入display的正确值。
integerI;
reg[3:0]A;
reg[7:0]B;
initial
begin
I=-1;A=I;B=A;
$display("%b",B)? )
A=A/2;
$display("%b",A)? )
B=A+14
$diaplay("%d",B)? )
A=A+14;
$display("%d",A)? )
A=-2;I=A/2;
$display("%d",I)? )
end
标准答案:
I=-1;A=I;B=A;
$display("%b",B);(00001111)
A=A/2;
$display("%b",A);(0111)
B=A+14
$diaplay("%d",B);(21)
A=A+14;
$display("%d",A);(5)(A为4位,所以21被截为5)
A=-2;I=A/2;
$display("%d",I);(7)(A=-2,则是1110)
23)请问{1,0}与下面哪一个值相等。
1). 2’b01 2). 2’b10 3). 2’b00
4). 64’H000000000002 5).
64’H0000000100000000
标准答案:5
(位拼接运算符必须指明位数,若不指明则隐含着为32位的
二进制数[即整数]。)
24)根据下题给出的程序,确定应将哪一个选项填入尖括号内。
-
defs.Reset 2.“defs.v”.Reset
-
M.Reset 4.Reset
1 标准答案:1
(模块间调用时,若引用其他模块定义的参数,要加上其他模块名,做为这个参数的前缀。)
module M
'include “defs.v”
…
if(OP==)
Bus=0;
endmodule
- 标准答案:4
parameter Reset=8’b10100101; (File defs.v)
module M
'include “defs.v”
…
if(OP==)
Bus=0;
endmodule
25)如果调用Pipe时,想把Depth的值变为8,问程序中的空括号内应填入何值?
Module Pipe(IP,OP)
parameter Option=1;
parameter Depth=1;
…
endmodule
Pipe( ) P1(IP1,OP1);
标准答案:#(1,8)
(其中1对应参数Option,8对应参数Depth.)
26)若想使P1中的Depth的值变为16,则应向空括号中填入哪个选项。
module Pipe (IP ,OP);
parameter
Option =1;
parameter Depth = 1;
…………
endmodule
module
Pipe
P1(IP1 ,OP1);
( );
endmodule
1.defparam P1.Depth=16;
2.parameter
P1.Depth=16;
3.parameter
Pipe.Depth=16;
4.defparam Pipe.Depth=16;
标准答案:1
(用后缀改变引用模块的参数要用defparam及用本模块名作为引用参数的前缀,如p1.Depth。)
27)如果我们想在Test的monitor语句中观察Count的值,则在空括号中应填入什么?
Module Test
Top T();
initial
$monitor( )
endmodule
module Top;
Block B1();
Block B2();
endmodule
module Block;
Counter C();
endmodule
module Counter;
reg [3:0] Count;
…
endmodule
标准答案:T.B1.C.Countor
Test.T.B1.C.Count
- 下题中用initial块给reg[7:0]V符值,请指明每种情况下V的8位都是什值。
这道题说明在数的表示时,已标明字宽的数若用XZ表示某些位,只有在最左边的X或Z具有扩展性。
[陈1]20180926 03:29