Verilog初级教程(16)Verilog中的控制块

前言

硬件行为的实现离不开条件语句和其他控制逻辑流的方式。Verilog有一套控制流块和机制来实现。
它包括:

  • if-else
  • forever loop
  • repeat loop
  • while loop
  • for loop
    下面一一来说。

正文

条件语句if

这个条件语句用来决定是否应该执行某些语句。这与C语言中的if-else-if语句非常相似。如果表达式的值为真,那么第一条语句将被执行。如果表达式的值为false,并且如果存在一个else部分,那么else部分将被执行。

如果需要在if或者else部分内放置多条语句,则需要用 begin和end括起来。

语法

// if statement without else part
if (expression)
	[statement]

// Use "begin" and "end" blocks for more than 1 statements
if ([expression]) begin
	Multiple statements
end

// if statment with an else part
if (expression)
	[statement]
else
	[statement]

// if else for multiple statements should be
// enclosed within "begin" and "end"
if (expression) begin
	[multiple statements]
end 
else begin
	[multiple statements]
end

// if-else-if statement
if (expression)
	[statement]
else if (expression)
	[statement]
else
	[statement]

// if-else-if style to check for more expressions if the previous one doesn't match
if ([expression 1])
	Single statement
else if ([expression 2]) begin
	Multiple Statements
end 
else
	Single statement

硬件实现

** 没有else的if**

if没有else部分,意味着对于任何不满足if内部表达式的条件,其值保持不变。
保持不变的硬件对应位锁存器,例:

module des (  input en,
              input d,
              output reg q);

    always @ (*)
        if (en)
            q = d;

endmodule

vivado生成的RTL原理图为:

缺失else的if

带有else的if

如果rstn为高电平,输出q将在时钟的上升沿得到输入d的值,并描述了D触发器的行为。

module dff (	input clk,
							input rstn,
							input d,
							output reg q);

	always @ (posedge clk) begin
		if (! rstn)
			q <= 0;
		else
			q <= d;
	end
endmodule

vivado生成的RTL原理图为:
在这里插入图片描述

这个例子中存在一个同步低电平复位,但从上图RTL原理图看出,仍然像是一个异步复位,因为复位信号的输出接到了触发器的rst端,这是什么情况呢?
在刚学习FPGA的时候我就遇到过这个一个问题,也就是综合的原理图(综合的情况应该和RTL原理图一致,只不过使用了某些器件的替换)和硬件语言描述的不一致问题。
这个问题,我看到过一个回答是:
回复截图
这个回答貌似解决了这个疑问,仅供参考。

为了验证确实是同步复位,也可以通过行为仿真的方式进行验证,这个交给大家去做,由于篇幅有点大,所以赶时间。

if-else-fi

在下面的例子中,设计模块有一个4位输出q,当模式为1时递增,当模式为2时递减,使用if else结构。请注意,描述中没有说明如果模式为0或3时要做什么,这些都是2位变量的有效值。假设当模式为1和3时,电路什么都不做,但保持q的退出值,不建议在实际设计代码中留下这种含糊不清的地方,但在这里是为了强调这种可能性。

module des ( 
			input [1:0] mode,
            input clk,
            input rstn,
            output reg [3:0] q);

  always @ (posedge clk) begin
    if (! rstn)
      q <= 0;
    else begin
      if (mode == 1)
        q <= q + 1;
      else if (mode == 2)
        q <= q - 1;
    end
  end
endmodule

其原理图为:
if-else-if
如果综合库中有这些元件的话,上面显示的可以认为是综合的输出,值得注意的是,q被实现为一个4位的flop,它有一个CE引脚来使能触发器。值得注意的是,只有当模式为1或2时,这个flop才会被启用,而对于其他值则不会。输出q通过加减器块反馈到同一flop的输入端,通过mux,这也是由模式控制的。

注:综合的输出可能会随着给定技术库中器件的可用性而有所不同。

考虑与上面相同的设计,采用1位模式。

module des ( input mode,
             input clk,
            input rstn,
            output reg [3:0] q);

  always @ (posedge clk) begin
    if (! rstn)
      q <= 0;
    else begin
      if (mode)
        q <= q + 1;
      else
        q <= q - 1;
    end
  end

endmodule

在这种情况下,一个没有CE引脚的普通触发器与几个多路复用器一起使用,根据模式的值来选择正确的信号。

RTL原理图为:

在这里插入图片描述
没有else的if且只有单一语句

module tb;
  integer a = 10;

  initial begin
    if (a == 10)      // if block can have only one statement in it
      $display ("a is found to be 10");
    $display ("Always executed regardless of value of a");   // This statement is outside if block because
  end
endmodule

仿真结果:

a is found to be 10
Always executed regardless of value of a

可见if执行后,继续执行if下面的语句。

其他的不再讨论。

循环语句

循环提供了在一个块中执行单个或多个语句一次或多次的方法。在Verilog中,有四种不同类型的循环语句。

forever

这将连续执行块内的语句,不可综合。

forever
	[statement]

forever begin
	[multiple statements]
end
module my_design;
	initial begin
		forever begin
			$display ("This will be printed forever, simulation can hang ...");
		end
	end
endmodule

仿真结果:

This will be printed forever, simulation can hang ...
This will be printed forever, simulation can hang ...
...
...
This will be printed forever, simulation can hang ...
This will be printed forever, simulation can hang ...
This will be printed forever, simulation can hang ...
This will be printed forever, simulation can hang ...
Result reached the maximum of 5000 lines. Killing process.

这条语句常用于仿真中生成时钟信号,例如:

initial begin
clk = 0;
end

forever begin
#5 clk = ~clk;
end

repeat

这将执行固定次数的语句。如果表达式的值是X或Z,那么它将被视为零,根本不会被执行。不可综合。

repeat ([num_of_times]) begin
	[statements]
end

repeat ([num_of_times]) @ ([some_event]) begin
	[statements]
end
module my_design;
	initial begin
		repeat(4) begin
			$display("This is a new iteration ...");
		end
	end
endmodule

仿真结果:

This is a new iteration ...
This is a new iteration ...
This is a new iteration ...
This is a new iteration ...
ncsim: *W,RNQUIE: Simulation is complete.

while

只要表达式为真,就会执行语句,一旦条件为假,就会退出。如果条件从一开始就是假的,则根本不会执行语句。不可综合。

while (expression) begin
	[statements]
end
module my_design;
  	integer i = 5;

	initial begin
      while (i > 0) begin
        $display ("Iteration#%0d", i);
        i = i - 1;
      end
	end
endmodule

仿真结果:

Iteration#5
Iteration#4
Iteration#3
Iteration#2
Iteration#1
ncsim: *W,RNQUIE: Simulation is complete.

for

for循环是软件中使用最广泛的循环,但它主要用于复制Verilog中的硬件逻辑。for循环背后的思想是,只要给定的条件为真,就对循环内给定的一组语句进行迭代。这与while循环非常相似,但更多的是用在有迭代器的情况下,条件取决于这个迭代器的值。

for (<initial_condition>; <condition>; <step_assignment>) begin
	// Statements
end

这将使用一个三步走的过程来控制语句。

  • 初始化一个循环计数器变量
  • 计算表达式,通常涉及循环计数器变量
  • 增加循环计数器变量,这样在以后的时间里,表达式会变成假的,循环会退出。

或者:

  • 初始条件指定信号的初始值
  • 用于评估给定条件是否为真的检查。
  • 更新下一次迭代的控制变量

注:这里的增加循环计算器变量不能受到C语言影响,使用i++或者++i之类的,要使用i = i + 1等。

初始条件和控制变量的更新都包含在for循环中,与while循环不同,不需要单独指定。while循环是更通用的,大多数情况下,只有当给定条件下的语句需要重复使用时才会使用。然而for循环的开头和结尾都是明确的,由步骤变量控制。

module my_design;
  	integer i = 5;

	initial begin
      for (i = 0; i < 5; i = i + 1) begin
        $display ("Loop #%0d", i);
      end
    end
endmodule

仿真结果:

Loop #0
Loop #1
Loop #2
Loop #3
Loop #4
ncsim: *W,RNQUIE: Simulation is complete.

注:for循环使用得当,是可以综合的。

让我们看看在Verilog中如何在没有for循环的情况下实现8位左移位寄存器,然后与使用for循环的代码进行比较,以体会循环结构的效用。

module lshift_reg (input 						clk,				// Clock input
                   input 						rstn,				// Active low reset input
                   input [7:0] 			load_val, 	// Load value
                   input 						load_en, 		// Load enable
                   output reg [7:0] op); 				// Output register value

	 // At posedge of clock, if reset is low set output to 0
	 // If reset is high, load new value to op if load_en=1
	 // If reset is high, and load_en=0 shift register to left
	 always @ (posedge clk) begin
	    if (!rstn) begin
	      op <= 0;
	    end 
	    else begin
	    	if (load_en) begin
	      	op <= load_val;
	      end 
	      else begin
	        op[0] <= op[7];
	        op[1] <= op[0];
	        op[2] <= op[1];
	        op[3] <= op[2];
	        op[4] <= op[3];
	        op[5] <= op[4];
	        op[6] <= op[5];
	        op[7] <= op[6];
	      end
	    end
	  end
endmodule

同样的行为可以使用for循环来实现,这将减少代码,并使其对不同的寄存器宽度具有可扩展性。如果将寄存器的宽度作为Verilog参数,设计模块将变得可扩展,同样的参数可以在for循环里面使用。

module lshift_reg (input 						clk,				// Clock input
                   input    				rstn,				// Active low reset input
                   input [7:0] 			load_val, 	// Load value
                   input 						load_en, 		// Load enable
                   output reg [7:0] op); 				// Output register value

	 integer i;

	 // At posedge of clock, if reset is low set output to 0
	 // If reset is high, load new value to op if load_en=1
	 // If reset is high, and load_en=0 shift register to left
	 always @ (posedge clk) begin
	    if (!rstn) begin
	      op <= 0;
	    end 
	    else begin

	    	// If load_en is 1, load the value to op
	    	// else keep shifting for every clock
	    	if (load_en) begin
	      	op <= load_val;
	      end 
	      else begin
            for (i = 0; i < 7; i = i + 1) begin
              op[i+1] <= op[i];
            end
            op[0] <= op[7];
	      end
	    end
	  end
endmodule

可见,针对移位寄存器,使用for循环方便很多,但是下面这种写法会不会更好理解呢?

module lshift_reg (input 						clk,				// Clock input
                   input    				rstn,				// Active low reset input
                   input [7:0] 			load_val, 	// Load value
                   input 						load_en, 		// Load enable
                   output reg [7:0] op); 				// Output register value

	 integer i;

	 // At posedge of clock, if reset is low set output to 0
	 // If reset is high, load new value to op if load_en=1
	 // If reset is high, and load_en=0 shift register to left
	 always @ (posedge clk) begin
	    if (!rstn) begin
	      op <= 0;
	    end 
	    else begin

	    	// If load_en is 1, load the value to op
	    	// else keep shifting for every clock
	    	if (load_en) begin
	      	op <= load_val;
	      end 
	      else begin
	      	op[0] <= op[7];
            op[7:1] <= op[6:0];
            /*
			op <= {op[6:0],op[7]};
			/*
            
	      end
	    end
	  end
endmodule

往期回顾

Verilog初级教程(15)Verilog中的阻塞与非阻塞语句

Verilog初级教程(14)Verilog中的赋值语句

Verilog初级教程(13)Verilog中的块语句

Verilog初级教程(12)Verilog中的generate块

Verilog初级教程(11)Verilog中的initial块

Verilog初级教程(10)Verilog的always块

Verilog初级教程(9)Verilog的运算符

Verilog初级教程(8)Verilog中的assign语句

Verilog初级教程(7)Verilog模块例化以及悬空端口的处理

Verilog初级教程(6)Verilog模块与端口

Verilog初级教程(5)Verilog中的多维数组和存储器

Verilog初级教程(4)Verilog中的标量与向量

Verilog初级教程(3)Verilog 数据类型

Verilog初级教程(2)Verilog HDL的初级语法

Verilog初级教程(1)认识 Verilog HDL

芯片设计抽象层及其设计风格

Verilog以及VHDL所倡导的的代码准则

FPGA/ASIC初学者应该学习Verilog还是VHDL?

参考资料及推荐关注

ii-else-if

for-loop

verilog-control-block

移位寄存器

个人微信公众号: FPGA LAB

交个朋友

猜你喜欢

转载自blog.csdn.net/Reborn_Lee/article/details/107437358
今日推荐