【读书笔记】《Verilog数字系统设计教程》 第5章 条件语句、循环语句和块语句(附思考题答案)


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



一、条件语句

  条件语句有if-else和case两类,都属于顺序执行语句,都写在always块内。

(1)if-else语句

  • 判定所给条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一。
  • if-else语句有3种形式,其中“表达式”为逻辑表达式或关系表达式,或一位的变量;若表达式的值为0、x或z,则判定的结果为“假”;若为1,则结果为“真”。语句可为单句,也可为多句;多句时一定要用“begin_end”语句括起来,形成一个复合块语句。
//方式1
if(表达式) 语句1;

//方式2
if(表达式) 语句1;
else      语句2;

//方式3
if(表达式1) 		语句1;
else if(表达式2)     语句2;
...
else if(表达式n)     语句n;

if语句可以嵌套;若if与else的数目不一样,注意用“begin_end”语句来确定if与else的配对关系!

if(表达式) 
	if(表达式2) 语句1;
	else       语句2;
else
	if(表达式3)  语句3;
	else        语句4;
if (表达式1begin
	if (表达式2)语句1;
end
else begin
	语句2;
	语句3;
end

【例 1】
在这里插入图片描述  在always块内,语句是顺序执行的,always和assign是并发执行的。

(2)case语句

☛ 当敏感表达式取不同的值时, 执行不同的语句。
☛功能:当某个(控制)信号取不同的值时,给另一个(输出)信号赋不同的值。常用于多条件译码电路(如译码器、数据选择器、状态机、微处理器的指令译码)
☛ case语句有3种形式:case,casez,casex

case(敏感表达式)
	值1:语句1;2:语句2;
	…
	值n:语句n;
	default: 语句n+1;
endcase

说 明 :

  • 其中“敏感表达式”又称为“控制表达式”,通常表示为控制信号的某些位。
  • 值1~值n称为分支表达式,用控制信号的具体状态值表示,因此又称为常量表达式。
  • default项可有可无,一个case语句里只能有一个default项!
  • 值1~值n必须互不相同,否则矛盾。
  • 值1~值n的位宽必须相等,且与控制表达式的位宽相同。

在这里插入图片描述

  • 在case语句中,分支表达式每一位的值都是确定的(或者为0,或者为1);
  • 在casez语句中,若分支表达式某些位的值为高阻值z,则不考虑对这些位的比较;
  • 在casex语句中,若分支表达式某些位的值为z或不定值x,则不考虑对这些位的比较。
  • 在分支表达式中,可用“?”来标识x或z

【例 2】用casez描述的数据选择器

module mux_z(out,a,b,c,d,select);
	output out;
	input a,b,c,d;
	input[3:0] select;
	reg out; //必须声明
	
always@ (select[3:0] or a or b or c or d) begin
	casez (select)
		4’b???1: out = a;//?表示高阻态
		4’b??1? : out = b;
		4’b? 1?? : out = c;
		4’b 1??? : out = d;
	endcase
end
endmodule

在这里插入图片描述
  在组合电路中,左图缺少else,会生成锁存器,锁存器不利于时序收敛。避免生成锁存器的原则:如果用到if语句,最好写上else项;如果用到case语句,最好写上default项。


二、循环语句

  循环语句分为4种:

  • for语句——通过3个步骤来决定语句的循环执行:
    (1)给控制循环次数的变量赋初值。
    (2)判定循环执行条件,若为假则跳出循环;若为真,则执行指定的语句后,转到第(3)步。
    (3)修改循环变量的值,返回第(2)步。
  • repeat语句——连续执行一条语句n次
  • while语句——执行一条语句,直到循环执行条件不满足;若一开始条件即不满足,则该语句一次也不能被执行!
  • forever语句——无限连续地执行语句,可用disable语句中断!

(1)for语句

在这里插入图片描述
**【例 1】**用for语句描述的7人投票表决器:若超过4人(含4人)投赞成票,则表决通过。

module vote7 (
	output pass,
	input [6:0] vote
);
	reg[2:0] sum; //sum为reg型变量,用于统计赞成的人数
	integer i;
	reg pass;
always @(vote) begin
	sum = 0; //sum初值为0
	for(i = 0;i<=6;i = i+1) begin//for语句
		if(vote[i]) sum = sum+1; //只要有人投赞成票,则 sum加1
	end
	if(sum[2]) pass = 1; //若超过4人赞成,则表决通过
	else pass = 0;
end
endmodule

仿真结果:超过四人时,pass输出1
在这里插入图片描述

(2)repeat语句

在这里插入图片描述
**【例 2】**用repeat语句和移位操作实现两个8位二进制数乘法
在这里插入图片描述仿真结果:(用for实现会更简单)
在这里插入图片描述
  

(3)while语句

在这里插入图片描述

  • 有条件地执行一条或多条语句。
  • 首先判断循环执行条件表达式是否为真。若为真,则执行后面的语句或语句块;然后再回头判断循环执行条件表达式是否为真,若为真,再执行一次后面的语句;如此不断,直到条件表达式不为真为止。

注1 :首先判断循环执行条件表达式是否为真,若不为真,则其后的语句一 次也不被执行!
注2 :在执行语句中,必须有一条改变循环执行条件表达式的值的语句。
注3 :whille语句只有当循环块有事件控制(即@(posedge clk))时才可综合。


**【例 3】**用while语句对一个8位二进制数中值为1的位进行计数

module count1s_while ( count,rega,clk );
	output[3:0] count;
	input [7:0] rega;
	input clk;
	reg[3:0] count;
always @(posedge clk)
	begin:count1
		reg[7:0] tempreg; //用作循环执行条件表达式
		count = 0; // count初值为0
		tempreg = rega; // tempreg 初值为rega
		while(tempreg) begin// 若tempreg非0,则执行以下语句
			if(tempreg[0]) count = count+1;
			//只要tempreg最低位为1,则 count加1
			tempreg = tempreg >>1; //右移1end
end
endmodule

【例 4】用for语句对一个8位二进制数中值为1的位进行计数
在这里插入图片描述结果:都一样
在这里插入图片描述

(4)forever语句

在这里插入图片描述

forever一般是不可综合的,所以用在搭建测试平台testbeech中比较多。

//常用在测试模块中产生周期性的波形,作为仿真激励信号。
//常用disable语句跳出循环
initial
	begin : Clocking
		clk = 0;
		#10 forever #10 clk = !clk;
end
initial
	begin : Stimulus
	……
	disable Clocking; // 停止时钟
end

三、顺序块与并行块

(1)顺序块(也称过程块)

关键字begin - end用于将多条语句组成顺序块,顺序块的特点:
a. 顺序块中的语句按照顺序依次执行,只有前面的语句执行完之后后面的语句才会被执行(除了带内嵌延迟控制的非阻塞赋值语句)。
b.如果语句包含延迟或者事件控制,那么延迟总是相对于前面那条语句执行完的仿真时间

【例 1】顺序块

//说明1:不带延时
	reg x , y;
	reg [1:0] z , w;

initial begin
	x = 1'b0;
	y = 1'b1;
	z = {
    
    x,y};
	w = {
    
    y,x};
end

//说明2:带延时的顺序块
	reg x,y;
	reg [1:0] z , w;

initial begin
	x = 1'b0;		//在仿真0时刻完成
	#5 y = 1'b1;//在仿真5时刻完成
	#10  z = {x,y}//在仿真时刻15完成
	#20 w = {y,x}//在仿真时刻35完成
end

(2)并行块

并行块由关键字fork-join声明,并行块有以下特点:
a.并行块内的语句并发执行:
b.语句执行的顺序是由各自语句内延迟或事件控制决定的;
c.语句中的延时或事件控制是相对于块语句开始执行的时刻而言的。

//说明:代延时的并行块
	reg x, y;
	reg [1:0] z,w;

initial begin
	fork
		x = 1'b0;		//在仿真0时刻完成
		#5 y = 1'b1;//在仿真5时刻完成
		#10  z = {x,y}//在仿真时刻10完成
		#20 w = {y,x}//在仿真时刻20完成
	join
end

  在 “always”模块内,逻辑按书写的顺序执行。 顺序语句——“always”模块内的语句。在 “always”模块内,若随意颠倒赋值语句的书写顺序,可能导致不同的结果 。 注意阻塞赋值语句当本语句结束时即完成赋值操作!下面有两个例子:

//[] 顺序执行模块1module serial1(q,a,clk);
	output q,a;
	input clk;
	reg q,a;
always @(posedge clk) begin
	q=~q; //阻塞赋值
	a=~q;
end
endmodule

在这里插入图片描述
  结果:a和q的波形反相。

[]顺序执行模块2module serial2(q,a,clk);
	output q,a;
	input clk;
	reg q,a;
always @(posedge clk) begin
	a=~q;
	q=~q;
end
endmodule

在这里插入图片描述
  结果:a和q的波形一样。


  块语句有三个特点:嵌套块、命名块和命名块的禁用。其中Verilog通过关键字disable提供一种中止命名块执行的方法。disable可以通过用来从循环中退出、处理错误条件及根据控制信号来控制某些代码段是否被执行。格式:disable 块名;

四、思考题&答案

  1. 为什么建议在编写Verilog模块程序时,如果用到if语句建议大家把配套的else情况也考虑在内?
    因为如果没有配套的else语句,在不满足if条件语句时,将会保持原来的状态不变,从而在综合时会产生一个锁存器,而这是设计不想要的结果。

  2. 用if语句,elseif 语句, elseif 语句,…else 语句和用case,endcase表示不同条件下的多个分支是完全相同的,还是有什么不同?
    不是完全相同。与case语句中的控制表达式和多分支表达式这种比较相比,if else_ if结构中条件表达式更为直观些;对于那些分支表达式中存在不定值x和高阻值z的位时case语句提供了处理这种情况的段。

  3. 如果case语句的分支条件没有覆盖所有可能的组合条件,定义了default项和没有定义default项有什么不同?
    定义了default项则会使电路描述的更加的清楚,综合的时候不会产生不想要的结果,没用定义default则会使在综合是产生一个锁存器。

  4. 仔细阐释case、casex 和casez之间的不同。

在这里插入图片描述case、casex、 casez 对应的真值表如上图所示,可以看出case 无论是0,1,x,z,只有两者都一样才为1。而casez不将高阻进行比较,有z都为1,在其它情况相同时为1。而casex不将高阻z和x进行比较,有z或x都为1,其它情况进行比较,相同为1。

  1. forever语句如果运行了,在它下面的语句能否运行?它位于begin end和位于fork join块有什么不同?
    不能运行。位于begin end,由于begin and是顺序块,所以只要执行到forever则将不能运行下面的程序;而位于fork join,它是并行块,执行了forever 还是能够执行forever下面的语句。

  2. forever语句repeat语句能否独立于过程块而存在,即能否不在initial或always 块中使用?
    forever不能独立于过程块中,而repeat能够独立于过程块中。

  3. 用for循环为存储器许多单元赋值时是否需要时间?为什么如果不定义时间延迟,它可以不需要时间就把不管多大的储存器赋值完毕?
    如果定义了时间延迟则需要时间,否则不需要时间。因为循环的边界是确定的,那么在综合时该循环语句被认为是重复的硬件结构。

  4. for循环是否可以表示可以综合的组合逻辑?请举例说明。
    可以表示综合的组合逻辑。例如用for循环实现的乘法器。

  5. 在编写测试模块时用什么方法可以使for循环按照时钟的节拍运行?请比较下面的程序段。
    在这里插入图片描述可以在for循环的最后嵌套时钟节拍运行的信号。第一种程序不能按照时钟节拍来对mem[i]赋值,而第二种程序可以。

  6. 声明一个为oscillate的寄存器变量并将它初始化为0,使其每30个时间单位进行一次取反操作,不要使用always语句(提示:使用forever 循环)。

reg oscillate;
initial begin
	oscillate=0;
	forever
	#30 oscillate = !oscillate;
end
  1. 设计一个周期为40个时钟单位的时钟循环,其占空比为25%,使用always和initial块进行设计,将其在仿真0时刻的值初始化为0。
initial begin
	clock = 0;
	
	always begin
		#30 clock = 0;
		#10 clock = 1;
	end
end
  1. 给定下面含有阻塞过程赋值语句的initial块,每个语句在什么仿真时刻开始执行?a,b,c和d在仿真过程中的中间值和仿真结束时的值是什么?
initial begin
	a = 1'b0;
	b = #10 1'b0;
	c = #5 1'b0;
	d = #20{a,b,c};
end

第一条语句在仿真开始时就执行,第二句在仿真10个时钟单元后执行,第三句在仿真15个时钟信号单元后执行,第四句在仿真35个时钟单元后执行。在中间仿真过程中a=0,b,c,d为不确定值,结束时abed的值是a=1’b0,b= 1’0,c=1’0,d=3’b000。

  1. 在第12题中,如果initial块中包含的是非阻塞过程赋值语句,那么各个问题的答案是什么?
    如果是非阻塞过程赋值则答案是;第一条语句在仿真开始时就执行,第二句在仿真10个时钟单元后执行,第三句在仿真5个时钟信号单元后执行,第四句在仿真20个时钟单元后执行。在中间仿真过程a=0,b,c,d 为不确定值结束时abcd的值是a=1b0,b=1’0,c=1’0,d=3’b000。

  2. 下面例子中d的最终值是什么?

initial begin
	b=1"b1;
	c=1’b0;
	#10 b=1’b0; 
end

initial begin
	d=#25(b|c);
end

答:d的最终值为0

  1. 使用带同步清零端的D触发器(清零高电平有效,在时钟下降沿执行清零操作)设计下一个下降沿触发的D触发器,只能使用行为语句(提示: D触发器的输出q应当声明为寄存器变量)。使用设计出的D触发器输出一个周期为10个时间单位的时钟信号。
module D_ FF(CLR,CLK,D,Q);
	input CLR,CLK,D;
	output Q;
	reg Q;

always @(posedge CLR or necedge CLK) begin
	if(CLR) Q=0;
	else #10Q<=D;
end
endmodule
  1. 使用带有异步清零端的D触发器设计第64题要求的D触发器(在清零端变为高电平后立即执行清零操作,无须等待下一个时钟下降沿),并对这个D触发器进行测试。
module D_ FF(CLR,CLK,D,Q);
	input CLR,CLK,D;
	output Q;
	reg Q;

always @(posedge CLR ) begin
	Q <= 0;
end

always @(necedge CLK) begin
	#10 Q <= D;
end
endmodule

  1. 使用wait语句设计一个电平敏感的锁存器,该锁存器的输入信号为d和clock输出为q,其功能是当clock=1时q=d。
module L_ FF(d,clock,q);
	input d,clk;
	output q;
	reg q;
	
always begin
	wait(clock==1)
	q=d;
end
endmodule
  1. 使用条件语句设计【例5.18】中的四选一多路选择器,外部端口必须保持不变。
module mux4to1(out, i0, i1, i2, i3, s1, s0);
	output out;
	input i0, i1, i2, i3;
	input s1, s0;
	
	reg out;

always @ (*) begin
	if ({
    
    s1,s0} == 2'b00) out = i0;
	else if ({
    
    s1,s0} == 2'b01) out = i1;
	else if ({
    
    s1,s0} == 2'b10) out = i2;
	else if ({
    
    s1,s0} == 2'b11) out = i3;
	else out = 1'bx;
end
endmodule
  1. 使用while循环设计一个时钟信号发生器。其时钟信号的初值为0,周期为10个时间单元。
initial begin
	clk=0;
	while(1)
	#10 clk = !clk;
end
  1. 使用for 循环对一个长度为1024(地址从0~ 1023)、位宽为4的寄存器类型数组cache_ var 进行初始化,把所有单元都设置为0。
begin
	reg [3:0] cache_ var[1023:0];
	intiger i;
	
	for(i=0;i<1024;i++)
		cache_ var[i]=0;
end
  1. 使用forever 循环设计一个时钟信号,周期为10,占空比为40%,初值为0。
initial begin
	clk=0;
	
	forever begin
		#6 clk=0;
		#4 clk=1;
	end
end
  1. 使用repeat 将语句a=a+1延迟20个时钟上升沿之后再执行。
	parameter delay= 20;
	intiger i;
	reg a;
	
begin
	repeat(delay)
	always @(posedge clk) begin
		i++;
		if(i== 20) a=a+1;
	end
end
  1. 下面是一个内嵌顺序块和并行块的块语句。该块的执行结束时间是多少?事件的顺序是怎样的?每条语句的仿真结束时间是多少?
initial begin
	x=1’b0;
	#5 y=1’b1; //5
	fork
		#20 a=x; //25
		#15 b=y; //20
	join

	#40 x=1"b1; //65
	fork
		#10p=x; //75
		begin
			#10a=y; //75
			#30b=x; //105
		end
		#5 m=y; //70
	join
end

该块的执行结束时间是5+20+40+40= 105个时钟单位


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

猜你喜欢

转载自blog.csdn.net/qq_43244515/article/details/124352907
今日推荐