【读书笔记】《Verilog数字系统设计教程》第6章 结构语句、系统任务、函数语句和显示系统任务(附思考题答案)


※书目:Verilog数字系统设计教程(第四版)夏宇闻等编著
虚拟机:VMware -14.0.0.24051
环 境:ubuntu 18.04.1
脚 本:makefile(点击直达
应用工具:vcs 和 verdi


一、学习内容

在这里插入图片描述


二、结构语句

  • initial语句:只执行一次
  • always语句:不断重复执行,直到仿真结束
  • task语句:可在程序模块中的一处或多处调用
  • function语句:可在程序模块中的一处或多处调用

(1)initial语句

initial begin
	语句1;
	...
	语句n;
end

  在仿真的初始状态对各变量进行初始化;在测试文件中生成激励波形作为电路的仿真信号。
【例1】

……
parameter size=16;
	reg[3:0] addr;
	reg reg1;
	reg[7:0] memory[0:15];
	
initial begin
	reg1 = 0;
	for(addr=0;addr<size;addr=addr+1);
		memory[addr]=0;
end
……

(2)always语句

在这里插入图片描述
  如果always块中包含一个以上的语句,则这些语句必须放在begin_end或fork_join块中!

always @ (posedge clk or negedge clear) begin
	if(!clear) qout = 0; //异步清零
	else qout = 1;
end

  always语句必须与一定的时序控制结合在一起才有用!如果没有时序控制,则易形成仿真死锁!

//生成一个0延迟的无限循环跳变过程——形成仿真死锁
always areg = ~areg;

//在tb文件中,用于生成一个无限延续的信号波形——时钟信号
‘define half_period 50
module half_clk_top;
	reg reset, clk; // 输入信号
	wire clk_out; // 输出信号
	always #half_period clk = ~clk;
	...
endmodule

【例2】用always块语句产生T’FF和8位二进制计数器
在这里插入图片描述


在这里插入图片描述


  • always块语句模板
always @ (<敏感信号表达式>)begin
	// 过程赋值语句
	// if语句
	// case语句
	// while ,repeat ,for 循 环
	// task ,function 调用
end
  • 一个变量不能在多个always块中被赋值

  • 敏感信号表达式又称事件表达式或敏感表,当其值改变时,则执行一遍块内语句;

  • 在敏感信号表达式中应列出影响块内取值的所有信号!

  • 敏感信号可以为单个信号,也可为多个信号,中间需用关键字or连接!也可以使用“ ,”连接

  • 敏感信号不要为x或z,否则会阻挡进程!

  • always的时间控制可以为沿触发,也可为电平触发。

  • 关键字posedge表示上升沿;negedge表示下降沿。
    在这里插入图片描述


//模板1
always @ (Inputs) //所有输入信号必须列出,用or隔开
begin
	...//组合逻辑关系
end

//模板2
always @ (Inputs) //所有输入信号必须列出,用or隔开
	if (Enable) begin
		...//锁存动作
end

//模板3
always @ (posedge Clock) // Clock only
	begin
		...// 同步动作
	end

//模板4
always @ (posedge Clock or negedge Reset)
// Clock and Reset only
	begin
		if (! Reset) // 测试异步复位电平是否有效
			..// 异步动作
		else
			...// 同步动作
	end // 可产生触发器和组合逻辑

//模板5
always @ (posedge Clock or negedge Reset)
// Clock and Reset only
	begin
		if (! Reset) // 测试异步复位电平是否有效
			..// 异步动作
		else if 
			...// 同步动作
		else if
			...//同步动作
	end // 可产生触发器和组合逻辑

  通常采用异步清零!只有在时钟周期很小或清零信号为电平信号时(容易捕捉到清零信号)采用同步清零。


(3)小结

在这里插入图片描述


三、task和function

  task和function语句分别用来由用户定义任务和函数;任务和函数往往是大的程序模块中在不同地点多次用到的
相同的程序段;利用任务和函数可将一个很大的程序模块分解为许多较小的任务和函数,便于理解和调试;输入、输出和总线信号的值可以传入、传出任务和函数。


(1)task语句

//任务定义		task <任务名>;
							端口及数据类型声明语句;
							其他语句;
						endtask
//任务调用		<任务名>(端口1,端口2,...
  • 注1:任务的定义与调用必须在一个module模块内;
  • 注2:任务被调用时,需列出端口名列表,且必须与任务定义中的I/O变量一一对应;
  • 注3:一个任务可以调用其他任务和函数。

【例 1】

//任务定义
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);

  当任务启动时,由v、w和x传入的变量赋给了a、b和c;当任务完成后,输出通过c、d和e赋给了x、y和z 。

(2)function

  函数的目的是通过返回一个用于某表达式的值,来响应输入信号。适于对不同变量采取同一运算的操作。函数在模块内部定义,通常在本模块中调用,也能根据按模块层次分级命名的函数名从其他模块调用。而任务只能在同一模块内定义与调用。

//函数定义		function <返回值位宽或类型说明> 函数名;
							端口声明;
							局部变量定义;
							其他语句;
						endfunction

//函数调用		<函数名><表达式> <表达式>)

function[7:0] gefun; //函数的定义
	input [7:0] x;
	...
	<语句> //进行运算
	gefun = count; //赋值语句
endfunction

	assign number = gefun(rega); //对函数的调用
//1:函数的调用是通过将函数作为调用函数的表达式中的操作数来实现的
//2:函数在综合时,被理解成具有独立运算功能的电路,每调用一次函数,相当于改变此电路的输入,以此得到相应的计算结果

注意

  • 函数的定义不能包含任何时间控制语句——用延迟#、事件控制@或等待wait标识的语句。
  • 函数不能启动(即调用)任务!
  • 定义函数时至少要有一个输入参量!且不能有任何输出或输入/输出双向变量。
  • 在函数的定义中必须有一条赋值语句,给函数中的一个内部寄存器赋以函数的结果值,该内部寄存器与函数同名

【例2】 利用函数对一个8位二进制数中为0的位进行计数
在这里插入图片描述在这里插入图片描述
**【例 3】**阶乘运算函数
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

(3)任务和函数的区别

在这里插入图片描述

  


(3)小结

在这里插入图片描述


四、系统任务

(1)$display 和 $write

$display(p1,p2,p3,…,p4);
$write(p1,p2,p3,…,p4);

  两个函数主要用来输出信息,即将参数p2~pn按照参数p1给定的格式输出。前者自动在输出后换行,后者则在一行输出多个信息。参数p1被称为格式控制,参数p2 ~ pn被称为输出表列。其中输出格式控制需要使用双引号括起来。

  • 格式说明,由%和格式字符组成。
  • 作用:将输出的数据转换成指定的格式输出。
    在这里插入图片描述

【例子】

$display("%d",1'bx);
$display("%h",14'bx0_1010);
$display("%h %o",12'b001x_xx10_1x01,12'b001_xxx_101_x01);

输出结果为:
  x
  xxXa
  XXX1x5X

  • 如果表达式值相对应的某进制所有位均为不定值,则输出的结果为小写的x
  • 如果表达式值相对应的某进制所有位均为高阻值,则输出的结果为小写的z
  • 如果表达式值相对应的某进制部分位为不定值,则输出的结果为大写的X
  • 如果表达式值相对应的某进制部分位为高阻值,则输出的结果为大写的Z

  • 普通字符:需要原样输出的字符
    在这里插入图片描述

【例子】

module disp;
initial begin
	$display("\\\t%%\n\"\123");
end
endmodule

输出结果为:
  \ %
  "S
分析:见上表,其中八进制123对应的字符是S


(2)文件输出

  • 打开文件

文件可以用系统任务$ fopen打开
用法:$fopen(“<文件名>”);
用法:<文件句柄> = $fopen(“<文件名>”);

  任务返回一个被称为多通道描述符(multichannel descriptor)的32位值。其中只有一位被设置成1,标准输出有一个多通道描述符,其最低位(第0位)被设置成1,标准输出也被称为通道0。每调用一次,打开一个新通道,并且返回一个设置了第1位~第30位,第31位是保留位。

integer handle1,handle2,handle3;
initial begin
	handle1 = $fopen("file1.out");//handle = 32'h0000_0002(bit 1 set 1)
	handle2 = $fopen("file2.out");//handle = 32'h0000_0004(bit 2 set 1)
	handle3 = $fopen("file3.out");//handle = 32'h0000_0008(bit 3 set 1)
end

  • 写文件

系统任务$fdisplay、 $fmonitor、 $fwrite 、 $fstrobe都可以用于写文件
用法:
$fdispaly(<文件描述符>,p1,p2,…,pn);
$fmonitor(<文件描述符>,p1,p2,…,pn);

integer desc1,desc2,desc3;//三个文件的描述符
initial begin
	desc1 = handle1 |1;				 //desc1 = 32'h0000_0003
	$fdiaplay(desc1,"Display 1");//写到文件file1.out和标准输出stdout
	
	desc2 = handle2 |handle1;		//desc2 = 32'h0000_0006
	$fdiaplay(desc2,"Display 2");//写到文件file1.out和file2.out

	desc1 = handle3;						//desc1 = 32'h0000_0008
	$fdiaplay(desc3,"Display 3");//只写到文件file3.out
end

  p1~pn可以是变量、信号名或者带引号的字符串。文件描述符是一个多通道描述符,它可以是一个文件句柄或者多个文件句柄按位组合。


  • 关闭文件

文件可以用系统任务$fclose 来关闭
用法: $fclose(<文件描述符>);


(3)显示层次

module M
initial begin
	$display("Displaying in %m");
end
endmodule

module top;
M m1();
M m2();
M m3();

endmodule

仿真输出:
  Display in top.m1
  Display in top.m2
  Display in top.m3

  通过任何显示任务,比如$display、 $write 、 $monitor或者 $strobe中的%m选项的方式可以显示任何级别的层次,当一个模块的多个实例执行同一段verilog代码时,%m选项会区分哪个模块实例在输出。显示任务中的%m选项无需参数。

五、思考题

  1. 怎样理解initial语句只执行一次的概念?
    在仿真开始时,initial 语句只执行一次,但initial 语句里面的语句可能不执行一次,因为如果是while循环,虽然initial语句是执行一次,但只要进了while循环,则会执行到仿真结束。

  2. 在initial 语句引导的过程中是否可以有循环语句?如果可以,是否与第1题互相矛盾?
    可以,并不互相矛盾。initial 语句确实是执行了一次,但并不意味着initial 语句过程中就不能是循环语句,两者并不矛盾。

  3. 怎样理解由always语句引导的过程块是不断活动的?
    always语句的过程块是不断活动的,在仿真过程中always块始终在循环的活动着,检查always语句后面的信号是否发生相应改变,这是always活动的实质,如果always语句后面没有检查的信号则将会进入一个循环,将会使仿真器锁死。

  4. 不断活动与不断执行有什么不同?
    不断活动是always语句不断活动检查是否满足条件(如某个信号发生改变),不断执行时always语句引导的过程中的语句不断的执行着。

  5. 怎样理解沿触发和电平触发的不同?
    沿触发是在某个信号在上升沿或下降沿到来时,触发执行过程块。电平触发是在某个信号发生改变时就会触发执行过程块。

  6. 是不是可以说沿触发是有间隔的,在一定的时间区间里只需要注意有限的点,而电平触发却需要注意无穷多个点?
    不是。沿触发是信号的上升沿或下降沿进行触发,而电平触发是在某个信号发生改变时进行触发,并不需要注意无穷多个点。

  7. 沿触发的always块和电平触发的always块各表示什么类型的逻辑电路的行为?为什么?
    沿触发的always块常表示时序逻辑电路,因为其和时序有很关。电平触发的always块常表示组合逻辑电路,因为只和电平有关。

  8. 简单叙述任务和函数的不同点。
    (1)函数只能与主模块共用同一个仿真时间单元,而任务可以定义自己的仿真时间单位;
    (2)函数不能启动任务,而任务能启动其他任务和函数;
    (3)函数至少要有一个输入变量,而任务可以没有或有多个任何类型的变量;
    (4)函数返回一个值,而任务则不返回值。

  9. 简单叙述 $display、 $write 和 $strobe的不同点。
    $display自动地在输出后进行换行, $write则不进行自动换行,其它都非常相似。如果许多其他语句与 $display任务在同一个时间单位执行,那么这些语句与 $display任务的执行顺序是不确定的。如果使用 $strobe,该语句总是在同时刻的其他赋值语句执行完成之后执行。

  10. 简单叙述Verilog1364-2001版语法规定的电平敏感列表的简化写法。
    关键词“or”也可以使用“ * ”来代替。

  11. 如何在Verilog测试模块中,利用文件的读写产生预定格式的信号,并记录有测试价值的信号?
    Verilog提供了系统任务:
    ▷ 选择要转储的模块实例或模块实例信号(dumpvars)
    ▷ 选择VCD文件的名称 ($ dumpfile)
    ▷ 选择转储过程的起点和终点($ dumppon,$ dumpoff)
    ▷ 选择生成检测点($ dunpall)


作者:xlinxdu
版权:本文是作者整理的读书笔记,部分材料来源于参考教材or互联网,侵权联系删。
转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。

  • 34
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xlinxdu

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值