SystemVerilog中控制语句

一、循环

何为循环

循环是一段不断执行的代码。条件语句通常包含在循环中,以便在条件变为真时可以终止。如果循环永远运行,则模拟将无限期挂起。下表给出了系统验证中不同类型的循环构造。
下面为常见循环

typedescription
foreverRuns the given set of statements forever
repeatRepeats the given set of statements for a given number of times
whileRepeats the given set of statments as long as given condition is true
forSimilar to while loop, but more condense and popular form
do whileRepeats the given set of statements atleast once, and then loops as long as condition is true
foreachUsed mainly to iterate through all elements in an array

1.1 forever

这是一个无限循环,就像while(1)一样。注意,除非在 forever 块中包含时间延迟以提前模拟时间,否则仿真将挂起。

module tb;

  // This initial block has a forever loop which will "run forever"
  // Hence this block will never finish in simulation
  initial begin
    forever begin
      #5 $display ("Hello World !");
    end
  end

  // Because the other initial block will run forever, our simulation will hang!
  // To avoid that, we will explicity terminate simulation after 50ns using $finish
  initial
    #50 $finish;
endmodule

如果没有调用$finish语句,仿真将不会停止。
仿真log

ncsim> run
Hello World !
Hello World !
Hello World !
Hello World !
Hello World !
Hello World !
Hello World !
Hello World !
Hello World !
Simulation complete via $finish(1) at time 50 NS + 0

1.2 repeat

用于在块中重复一定次数的语句。下面显示的示例将显示该消息 5 次,并继续执行其余代码。

module tb;

  	// This initial block will execute a repeat statement that will run 5 times and exit
	initial begin

        // Repeat everything within begin end 5 times and exit "repeat" block
		repeat(5) begin
			$display ("Hello World !");
		end
	end
endmodule

仿真log

ncsim> run
Hello World !
Hello World !
Hello World !
Hello World !
Hello World !
ncsim: *W,RNQUIE: Simulation is complete.

1.3 while

与verilog / C含义相同,只要条件为真,它就会重复该块。下面例子计数器最初为零,并递增,直到达到 10。

module tb;
 bit clk;

  always #10 clk = ~clk;
  initial begin
  	bit [3:0] counter;

    $display ("Counter = %0d", counter);      // Counter = 0
  	while (counter < 10) begin
    	@(posedge clk);
    	counter++;
        $display ("Counter = %0d", counter);      // Counter increments
  	end
  	$display ("Counter = %0d", counter);      // Counter = 10
    $finish;
end
endmodule

仿真log

ncsim> run
Counter = 0
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5
Counter = 6
Counter = 7
Counter = 8
Counter = 9
Counter = 10
Counter = 10
Simulation complete via $finish(1) at time 190 NS + 0

1.4 for

和verilog/C语言一样,for语句允许在一行内设置起始值、条件和增量表达式;

module tb;
 bit clk;

  always #10 clk = ~clk;
  initial begin
  	bit [3:0] counter;

    $display ("Counter = %0d", counter);      // Counter = 0
  	for (counter = 2; counter < 14; counter = counter + 2) begin
    	@(posedge clk);
    	$display ("Counter = %0d", counter);      // Counter increments
  	end
    $display ("Counter = %0d", counter);      // Counter = 14
    $finish;
  end
endmodule

仿真log

ncsim> run
Counter = 0
Counter = 2
Counter = 4
Counter = 6
Counter = 8
Counter = 10
Counter = 12
Counter = 14
Simulation complete via $finish(1) at time 110 NS + 0

1.5 do while

do while将首先执行代码,然后检查条件以查看是否应再次执行代码。

module tb;
 bit clk;

  always #10 clk = ~clk;
  initial begin
  	bit [3:0] counter;

    $display ("Counter = %0d", counter);      // Counter = 0
		do begin
			@ (posedge clk);
			counter ++;
          $display ("Counter = %0d", counter);      // Counter increments
        end while (counter < 5);
    $display ("Counter = %0d", counter);      // Counter = 14
    $finish;
  end
endmodule

仿真log

ncsim> run
Counter = 0
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5
Counter = 5
Simulation complete via $finish(1) at time 90 NS + 0

1.6 foreach

foreach通常用于遍历数组的所有变量。使用时不需要设置数组大小,foreach将从0开始遍历到array_size-1。

module tb_top;
   bit [7:0] array [8];   // Create a fixed size array

   initial begin

      // Assign a value to each location in the array
      foreach (array [index]) begin
         array[index] = index;
      end

      // Iterate through each location and print the value of current location
      foreach (array [index]) begin
         $display ("array[%0d] = 0x%0d", index, array[index]);
      end
   end
endmodule

仿真log

ncsim> run
array[0] = 0x0
array[1] = 0x1
array[2] = 0x2
array[3] = 0x3
array[4] = 0x4
array[5] = 0x5
array[6] = 0x6
array[7] = 0x7
ncsim: *W,RNQUIE: Simulation is complete.

二、Break,continue

2.1 break

module tb;
  	initial begin

      // This for loop increments i from 0 to 9 and exit
      for (int i = 0 ; i < 10; i++) begin
        $display ("Iteration [%0d]", i);

        // Let's create a condition such that the
        // for loop exits when i becomes 7
        if (i == 7)
          break;
      end
    end
endmodule

仿真log

ncsim> run
Iteration [0]
Iteration [1]
Iteration [2]
Iteration [3]
Iteration [4]
Iteration [5]
Iteration [6]
Iteration [7]
ncsim: *W,RNQUIE: Simulation is complete.

2.2 continue

ncsim> run
Iteration [0]
Iteration [1]
Iteration [2]
Iteration [3]
Iteration [4]
Iteration [5]
Iteration [6]
Iteration [7]
ncsim: *W,RNQUIE: Simulation is complete.

仿真log

ncsim> run
Iteration [0]
Iteration [1]
Iteration [2]
Iteration [3]
Iteration [4]
Iteration [5]
Iteration [6]
Iteration [8]
Iteration [9]
ncsim: *W,RNQUIE: Simulation is complete.

三、SystemVerilog ‘unique’ and ‘priority’ if-else

Systermverilog引入了三种可以检查冲突的if-else语句:

  • unique-if
  • unique0-if
  • priority-if

3.1 verilog中的if-else-if

条件语句用于决定是否应执行 if 块中的语句。

如果表达式的计算结果为 true(即任何非零值),则将执行该特定 if 块中的所有语句;
如果它的计算结果为假(零或“x”或“z”),则 if 块中的语句将不会执行;
如果存在 else 语句并且表达式为 false,则将执行 else 块中的语句;

3.1.1 if-else语法

如果需要将多个语句放在 if 或 else 部分中,则需要将其包含在begin和end中。

if ([expression])
	Single statement

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

// Use else to execute statements for which expression is false
if ([expression]) begin
	Multiple statements
end else begin
	Multiple statements
end

// 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

3.2.2 if-else的硬件实现

if without else

if 没有 else 部分意味着对于任何不满足 if 内部表达式的条件,该值保持不变。

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

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

endmodule

每当 d 或 en 的值发生变化时,输出 q 的值就会更新。
Alt

if with else

如果 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

Alt

3.2 unique-if,unique0-if

unique-if会遵循以下规律来评估条件语句:

  • 除非有明确的 else,否则当 if 条件都不匹配时报告错误。
  • 如果在 if else 条件中找到超过 1 个匹配项,则报告错误
    与 unique-if 不同,unique0-if 如果没有条件匹配则不报告违规
    下面为unique-if没有else的示例:
module tb;
	int x = 4;

  	initial begin
      	// This if else if construct is declared to be "unique"
		// Error is not reported here because there is a "else"
      	// clause in the end which will be triggered when none of
      	// the conditions match
    	unique if (x == 3)
      		$display ("x is %0d", x);
    	else if (x == 5)
      		$display ("x is %0d", x);
      	else
      		$display ("x is neither 3 nor 5");

      	// When none of the conditions become true and there
      	// is no "else" clause, then an error is reported
    	unique if (x == 3)
      		$display ("x is %0d", x);
    	else if (x == 5)
      		$display ("x is %0d", x);
  	end
endmodule

仿真log

ncsim> run
x is neither 3 nor 5
ncsim: *W,NOCOND: Unique if violation:  Every if clause was false.
            File: ./testbench.sv, line = 18, pos = 13
           Scope: tb
            Time: 0 FS + 1

ncsim: *W,RNQUIE: Simulation is complete.

下面为unique-if中有多个匹配的例子

module tb;
	int x = 4;

  	initial begin

      	// This if else if construct is declared to be "unique"
		// When multiple if blocks match, then error is reported
      	unique if (x == 4)
          $display ("1. x is %0d", x);
      	else if (x == 4)
          $display ("2. x is %0d", x);
      	else
          $display ("x is not 4");
  	end
endmodule

仿真log

ncsim> run
1. x is 4
ncsim: *W,MCONDE: Unique if violation:  Multiple true if clauses at {line=8:pos=15 and line=10:pos=13}.
            File: ./testbench.sv, line = 8, pos = 15
           Scope: tb
            Time: 0 FS + 1

ncsim: *W,RNQUIE: Simulation is complete.

3.3 priority-if

priority-if 按顺序评估所有条件,并在以下情况下报告违规:没有一个条件为真,或者如果最后的 if 构造没有 else 子句
priority-if 中没有 else 的示例

module tb;
	int x = 4;

  	initial begin
      	// This if else if construct is declared to be "unique"
		// Error is not reported here because there is a "else"
      	// clause in the end which will be triggered when none of
      	// the conditions match
    	priority if (x == 3)
      		$display ("x is %0d", x);
    	else if (x == 5)
      		$display ("x is %0d", x);
      	else
      		$display ("x is neither 3 nor 5");

      	// When none of the conditions become true and there
      	// is no "else" clause, then an error is reported
    	priority if (x == 3)
      		$display ("x is %0d", x);
    	else if (x == 5)
      		$display ("x is %0d", x);
  	end
endmodule

仿真log

ncsim> run
x is neither 3 nor 5
ncsim: *W,NOCOND: Priority if violation:  Every if clause was false.
            File: ./testbench.sv, line = 18, pos = 15
           Scope: tb
            Time: 0 FS + 1

ncsim: *W,RNQUIE: Simulation is complete.

在 priority-if的第一个匹配后退出

module tb;
	int x = 4;

  	initial begin
      	// Exits if-else block once the first match is found
      	priority if (x == 4)
      		$display ("x is %0d", x);
      else if (x != 5)
      		$display ("x is %0d", x);
  	end
endmodule

仿真log

ncsim> run
x is 4
ncsim: *W,RNQUIE: Simulation is complete.

四、case

SystemVerilog case 语句检查一个表达式是否与多个表达式之一匹配并适当地分支。 该行为与 Verilog 中的行为相同。

4.1 Verilog case statement

case 语句检查给定表达式是否与列表中的其他表达式之一匹配并相应地分支。 它通常用于实现多路复用器。 如果有许多条件要检查并且会综合到优先级编码器而不是多路复用器中,则 if-else 结构可能不适合。

4.1.1 case语法

Verilog case 语句以 case 关键字开始,以 endcase 关键字结束。 括号内的表达式将只计算一次,并按照它们的编写顺序与备选列表进行比较,并执行备选与给定表达式匹配的语句。 一个包含多个语句的块必须分组并位于 begin 和 end 内。

// Here 'expression' should match one of the items (item 1,2,3 or 4)
case (<expression>)
	case_item1 : 	<single statement>
	case_item2,
	case_item3 : 	<single statement>
	case_item4 : 	begin
	          			<multiple statements>
	        			end
	default 	 : <statement>
endcase

如果没有任何 case 项与给定表达式匹配,则执行默认项中的语句。 default 语句是可选的,一个case 语句中只能有一个default 语句。 Case 语句可以嵌套。

如果没有任何项目与表达式匹配并且没有给出默认语句,则执行将退出 case 块而不做任何事情。

4.1.2 case示例

下图所示的设计模块有一个 2 位选择信号,用于将其他三个 3 位输入之一路由到调用的输出信号。 case 语句用于根据 sel 的值将正确的输入分配给输出。 由于 sel 是一个 2 位信号,它可以有 2 2 2^2 22 种组合,从 0 到 3。如果 sel 为 3,默认语句有助于将输出设置为 0。

module my_mux (input       [2:0] 	a, b, c, 		// Three 3-bit inputs
                           [1:0]	sel, 			  // 2-bit select signal to choose from a, b, c
               output reg  [2:0] 	out); 			// Output 3-bit signal

  // This always block is executed whenever a, b, c or sel changes in value
  always @ (a, b, c, sel) begin
    case(sel)
      2'b00    : out = a; 		// If sel=0, output is a
      2'b01    : out = b; 		// If sel=1, output is b
      2'b10    : out = c; 		// If sel=2, output is c
      default  : out = 0; 		// If sel is anything else, out is always 0
    endcase
  end
endmodule

case的硬件示意图(Hardware Schematic)
对 rtl 代码进行了详细说明,以获得代表 4 对 1 多路复用器的硬件原理图。
Alt
看到当 sel 为 3 时输出为零,并且对应于其他值的分配输入。
仿真log

ncsim> run
[0]  a=0x4 b=0x1 c=0x1 sel=0b11 out=0x0
[10] a=0x5 b=0x5 c=0x5 sel=0b10 out=0x5
[20] a=0x1 b=0x5 c=0x6 sel=0b01 out=0x5
[30] a=0x5 b=0x4 c=0x1 sel=0b10 out=0x1
[40] a=0x5 b=0x2 c=0x5 sel=0b11 out=0x0
ncsim: *W,RNQUIE: Simulation is complete.

在 case 语句中,只有当表达式的每一位都与包括 0、1、x 和 z 在内的选项之一匹配时,比较才会成功。 在上面显示的示例中,如果 sel 中的任何位是 x 或 z,则将执行默认语句,因为没有其他替代项匹配。 在这种情况下,输出将全为零。
仿真log

ncsim> run
[0] a=0x4 b=0x1 c=0x1 sel=0bxx out=0x0
[10] a=0x3 b=0x5 c=0x5 sel=0bzx out=0x0
[20] a=0x5 b=0x2 c=0x1 sel=0bxx out=0x0
[30] a=0x5 b=0x6 c=0x5 sel=0bzx out=0x0
[40] a=0x5 b=0x4 c=0x1 sel=0bxz out=0x0
[50] a=0x6 b=0x5 c=0x2 sel=0bxz out=0x0
[60] a=0x5 b=0x7 c=0x2 sel=0bzx out=0x0
[70] a=0x7 b=0x2 c=0x6 sel=0bzz out=0x0
[80] a=0x0 b=0x5 c=0x4 sel=0bxx out=0x0
[90] a=0x5 b=0x5 c=0x5 sel=0bxz out=0x0
ncsim: *W,RNQUIE: Simulation is complete

如果case语句中具有 x 和 z选项,则结果将完全不同。如下所示:

module my_mux (input  		[2:0] 	a, b, c,
													[1:0]		sel,
							output reg	[2:0] 	out);

  // Case items have x and z and sel has to match the exact value for
  // output to be assigned with the corresponding input
  always @ (a, b, c, sel) begin
    case(sel)
      2'bxz			:	out = a;
      2'bzx			:	out = b;
      2'bxx			:	out = c;
      default 	:	out = 0;
    endcase
  end
endmodule

仿真log

ncsim> run
[0] a=0x4 b=0x1 c=0x1 sel=0bxx out=0x1
[10] a=0x3 b=0x5 c=0x5 sel=0bzx out=0x5
[20] a=0x5 b=0x2 c=0x1 sel=0bxx out=0x1
[30] a=0x5 b=0x6 c=0x5 sel=0bzx out=0x6
[40] a=0x5 b=0x4 c=0x1 sel=0bxz out=0x5
[50] a=0x6 b=0x5 c=0x2 sel=0bxz out=0x6
[60] a=0x5 b=0x7 c=0x2 sel=0bzx out=0x7
[70] a=0x7 b=0x2 c=0x6 sel=0bzz out=0x0
[80] a=0x0 b=0x5 c=0x4 sel=0bxx out=0x4
[90] a=0x5 b=0x5 c=0x5 sel=0bxz out=0x5
ncsim: *W,RNQUIE: Simulation is complete.

case语句和if-else语句的区别主要有两个不同:

  • if-else 块中给出的表达式更通用,而在 case 块中,单个表达式与多个项目匹配
  • 当表达式中有 X 和 Z 值时,case 将提供确定的结果

4.2 unique,unique0 case

所有 case 语句都可以通过 unique 或 unique0 关键字进行限定,以执行我们在 if-else-if 构造中看到的违规检查。
unique 和 unique0 确保没有重叠的案例项目,因此可以并行评估。 如果有重叠的案例项目,则报告违规。

  • 如果发现多个 case 项与给定表达式匹配,则报告违规并执行第一个匹配表达式
  • 如果没有找到与给定表达式匹配的 case 项,则仅针对 unqiue 报告违规
  • 如果没有项目与表达式匹配,则 unique0 不会报告违规

4.2.1 unique case语句没有匹配到结果的示例

module tb;
  bit [1:0] 	abc;

  initial begin
    abc = 1;

    // None of the case items match the value in "abc"
    // A violation is reported here
    unique case (abc)
      0 : $display ("Found to be 0");
      2 : $display ("Found to be 2");
    endcase
  end
endmodule

仿真log

ncsim> run
ncsim: *W,NOCOND: Unique case violation:  Every case item expression was false.
            File: ./testbench.sv, line = 9, pos = 14
           Scope: tb
            Time: 0 FS + 1

ncsim: *W,RNQUIE: Simulation is complete.

4.2.2 unique case语句匹配多个结果的示例

module tb;
  bit [1:0] 	abc;

  initial begin
    abc = 0;

    // Multiple case items match the value in "abc"
    // A violation is reported here
    unique case (abc)
      0 : $display ("Found to be 0");
      0 : $display ("Again found to be 0");
      2 : $display ("Found to be 2");
    endcase
  end
endmodule

仿真log

csim> run
Found to be 0
ncsim: *W,MCONDE: Unique case violation:  Multiple matching case item expressions at {line=10:pos=6 and line=11:pos=6}.
            File: ./testbench.sv, line = 9, pos = 14
           Scope: tb
            Time: 0 FS + 1

ncsim: *W,RNQUIE: Simulation is complete.

4.3 priority case

module tb;
  bit [1:0] 	abc;

  initial begin
    abc = 0;

    // First match is executed
    priority case (abc)
      0 : $display ("Found to be 0");
      0 : $display ("Again found to be 0");
      2 : $display ("Found to be 2");
    endcase
  end
endmodule

仿真log

ncsim> run
Found to be 0
ncsim: *W,RNQUIE: Simulation is complete.

五、阻塞和非阻塞(Verilog Blocking & Non-Blocking)

5.1 Blocking

阻塞赋值语句使用 = 赋值,并在程序块中一个接一个地执行。 但是阻塞赋值语句不会阻止执行在并行块中运行的语句。

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a = 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    b = 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c = 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    d = 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	e = 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

仿真log

ncsim> run
[0] a=0xda b=0xx c=0xx
[0] a=0xda b=0xf1 c=0xx
[0] a=0xda b=0xf1 c=0x30
[0] d=0xaa e=0xx
[0] d=0xaa e=0x55
ncsim: *W,RNQUIE: Simulation is complete. 

当仿真开始时,有两个并行执行的初始块。 语句在每个块中按顺序执行,两个块都在时间 0ns 完成。 更具体地说,首先分配变量 a,然后是 display 语句,然后是所有其他语句。 这在输出中可见,其中变量 b 和 c 在第一个显示语句中是 8’hxx。 这是因为在调用第一个 $display 时尚未执行变量 b 和 c 的赋值。

在下一个示例中,我们将在同一组语句中添加一些延迟,以查看其行为方式。

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a = 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    #10 b = 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c = 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    #5 d = 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	#5 e = 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

仿真log

ncsim> run
[0] a=0xda b=0xx c=0xx
[5] d=0xaa e=0xx
[10] a=0xda b=0xf1 c=0xx
[10] a=0xda b=0xf1 c=0x30
[10] d=0xaa e=0x55
ncsim: *W,RNQUIE: Simulation is complete.

5.2 Non-blocking

非阻塞赋值允许在不阻塞执行后续语句的情况下调度赋值,并由 <= 符号指定。 相同的符号在表达式中用作关系运算符,在非阻塞赋值的上下文中用作赋值运算符。 如果我们采用上面的第一个示例,将所有 = 符号替换为非阻塞赋值运算符 <=,我们将看到输出有所不同。

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a <= 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    b <= 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c <= 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    d <= 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	e <= 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

仿真log

ncsim> run
[0] a=0xx b=0xx c=0xx
[0] a=0xx b=0xx c=0xx
[0] a=0xx b=0xx c=0xx
[0] d=0xx e=0xx
[0] d=0xx e=0xx
ncsim: *W,RNQUIE: Simulation is complete.

看到所有的 $display 语句都打印了 'h’x。 这种行为的原因在于非阻塞赋值的执行方式。因此,如果我们分解上述示例的执行流程,我们将得到如下所示的内容。

|__ Spawn Block1: initial
|      |___ Time #0ns : a <= 8'DA, is non-blocking so note value of RHS (8'hDA) and execute next step
|      |___ Time #0ns : $display() is blocking, so execute this statement: But a hasn't received new values so a=8'hx
|      |___ Time #0ns : b <= 8'F1, is non-blocking so note value of RHS (8'hF1) and execute next step
|      |___ Time #0ns : $display() is blocking, so execute this statement. But b hasn't received new values so b=8'hx
|      |___ Time #0ns : c <= 8'30, is non-blocking so note value of RHS (8'h30) and execute next step
|      |___ Time #0ns : $display() is blocking, so execute this statement. But c hasn't received new values so c=8'hx
|      |___ End of time-step and initial block, assign captured values into variables a, b, c
|
|__ Spawn Block2: initial
|      |___ Time #0ns : d <= 8'AA, is non-blocking so note value of RHS (8'hAA) and execute next step
|      |___ Time #0ns : $display() is blocking, so execute this statement: But d hasn't received new values so d=8'hx
|      |___ Time #0ns : e <= 8'55, is non-blocking so note value of RHS (8'h55) and execute next step
|      |___ Time #0ns : $display() is blocking, so execute this statement. But e hasn't received new values so e=8'hx
|      |___ End of time-step and initial block, assign captured values into variables d and e
|
|__ End of simulation at #0ns

接下来,让我们使用第二个示例,将所有阻塞语句替换为非阻塞语句。

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a <= 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    #10 b <= 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c <= 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    #5 d <= 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	#5 e <= 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

仿真log

ncsim> run
[0] a=0xx b=0xx c=0xx
[5] d=0xx e=0xx
[10] a=0xda b=0xx c=0xx
[10] a=0xda b=0xx c=0xx
[10] d=0xaa e=0xx
ncsim: *W,RNQUIE: Simulation is complete.

可以看到输出结果与我们之前得到的不同。如果我们分解执行流程,我们将得到如下所示的内容。

|__ Spawn Block1 at #0ns: initial
|      |___ Time #0ns : a <= 8'DA, is non-blocking so note value of RHS (8'hDA) and execute next step
|      |___ Time #0ns : $display() is blocking, so execute this statement: But a hasn't received new values so a=8'hx
|      |___ End of time-step : Assign captured value to variable a, and a is now 8'hDA
|      |___ Wait until time advances by 10 time-units to #10ns
|	
|      |___ Time #10ns : b <= 8'F1, is non-blocking so note value of RHS (8'hF1) and execute next step
|      |___ Time #10ns : $display() is blocking, so execute this statement. But b hasn't received new values so b=8'hx
|	   |___ Time #10ns : c <= 8'30, is non-blocking so note value of RHS (8'h30) and execute next step
|      |___ Time #10ns : $display() is blocking, so execute this statement. But c hasn't received new values so c=8'hx
|      |___ End of time-step and initial block, assign captured values into variables b, c
|	
|__ Spawn Block2 at #0ns: initial
|      |___ Wait until time advances by 5 time-units to #5ns
|	
|      |___ Time #5ns : d <= 8'AA, is non-blocking so note value of RHS (8'hAA) and execute next step
|      |___ Time #5ns : $display() is blocking, so execute this statement: But d hasn't received new values so d=8'hx
|      |___ End of time-step : Assign captured value to variable d, and d is now 8'hAA
|      |___ Wait until time advances by 5 time-units to #5ns
|	
|      |___ Time #10ns : e <= 8'55, is non-blocking so note value of RHS (8'h55) and execute next step
|      |___ Time #10ns : $display() is blocking, so execute this statement. But e hasn't received new values so e=8'hx
|      |___ End of time-step and initial block, assign captured values to variable e, and e is now 8'h55
|
|__ End of simulation at #10ns

六 SystemVerilog事件(Event)

  事件是一个静态对象句柄,用于在两个或多个并发活动进程之间进行同步。一个进程将触发事件,另一个进程等待事件。

6.1 事件的特征如下:

  • 事件可以赋值或与其他事件变量进行比较
    – 事件可以赋值为空
    – 当赋值给另一个事件时,两个变量都指向相同的同步对象
  • 事件可以传递给队列、函数和任务
event  over;                     // a new event is created called over
event  over_again = over;        // over_again becomes an alias to over
event  empty = null;             // event variable with no synchronization object

6.2 事件的触发和示例

那么,如何触发和等待事件?

  • 可以使用->或->>操作符触发命名事件
  • 进程可以使用@ 操作符或.trigger来等待事件触发
    事件触发的例子:
module tb;

  // Create an event variable that processes can use to trigger and wait
  event event_a;

  // Thread1: Triggers the event using "->" operator
  initial begin
    #20 ->event_a;
    $display ("[%0t] Thread1: triggered event_a", $time);
  end

  // Thread2: Waits for the event using "@" operator
  initial begin
    $display ("[%0t] Thread2: waiting for trigger ", $time);
    @(event_a);
    $display ("[%0t] Thread2: received event_a trigger ", $time);
  end

  // Thread3: Waits for the event using ".triggered"
  initial begin
    $display ("[%0t] Thread3: waiting for trigger ", $time);
    wait(event_a.triggered);
    $display ("[%0t] Thread3: received event_a trigger", $time);
  end
endmodule

仿真log

ncsim> run
[0] Thread2: waiting for trigger 
[0] Thread3: waiting for trigger 
[20] Thread1: triggered event_a
[20] Thread2: received event_a trigger 
[20] Thread3: received event_a trigger
ncsim: *W,RNQUIE: Simulation is complete.

6.3触发操作符@与 .triggered的区别

  事件的触发状态持续存在于整个仿真时间流程中,直到仿真结束为止。因此,如果等待事件和触发事件同时发生,就会出现竞态情况,而.triggered有助于避免这种情况。一个使用.triggered等待事件触发的线程会忽略等待和触发的顺序,直接解出事件等待锁定。

module tb;

  // Create an event variable that processes can use to trigger and wait
  event event_a;

  // Thread1: Triggers the event using "->" operator at 20ns
  initial begin
    #20 ->event_a;
    $display ("[%0t] Thread1: triggered event_a", $time);
  end

  // Thread2: Starts waiting for the event using "@" operator at 20ns
  initial begin
    $display ("[%0t] Thread2: waiting for trigger ", $time);
    #20 @(event_a);
    $display ("[%0t] Thread2: received event_a trigger ", $time);
  end

  // Thread3: Starts waiting for the event using ".triggered" at 20ns
  initial begin
    $display ("[%0t] Thread3: waiting for trigger ", $time);
    #20 wait(event_a.triggered);
    $display ("[%0t] Thread3: received event_a trigger", $time);
  end
endmodule

线程2将不会收到事件的触发消息,因为@和->操作之间存在竞争条件。仿真结果如下:

ncsim> run
[0] Thread2: waiting for trigger 
[0] Thread3: waiting for trigger 
[20] Thread1: triggered event_a
[20] Thread3: received event_a trigger
ncsim: *W,RNQUIE: Simulation is complete.

6.4 等待顺序(wait_order)

Waits for events to be triggered in the given order, and issues an error if any event executes out of order.

module tb;
  // Declare three events that can be triggered separately
  event a, b, c;

  // This block triggers each event one by one
  initial begin
    #10 -> a;
    #10 -> b;
    #10 -> c;
  end

  // This block waits until each event is triggered in the given order
  initial begin

    wait_order (a,b,c)
    	$display ("Events were executed in the correct order");
    else
      	$display ("Events were NOT executed in the correct order !");
  end
endmodule

仿真log

Compiler version J-2014.12-SP1-1; Runtime version J-2014.12-SP1-1;
Events were executed in the correct order
           V C S   S i m u l a t i o n   R e p o r t 

6.5 合并事件(Merging Events)

  当一个事件变量被分配给另一个事件变量时,所有等待第一个事件触发的进程将一直等待,直到第二个事件变量被触发。示例:

module tb;

  // Create event variables
  event event_a, event_b;

  initial begin
    fork
      // Thread1: waits for event_a to be triggered
      begin
        wait(event_a.triggered);
        $display ("[%0t] Thread1: Wait for event_a is over", $time);
      end
  	  // Thread2: waits for event_b to be triggered
      begin
        wait(event_b.triggered);
        $display ("[%0t] Thread2: Wait for event_b is over", $time);
      end

      // Thread3: triggers event_a at 20ns
      #20 ->event_a;

      // Thread4: triggers event_b at 30ns
      #30 ->event_b;

      // Thread5: Assigns event_b to event_a at 10ns
      begin
        // Comment code below and try again to see Thread2 finish later
        #10 event_b = event_a;
      end
    join
  end
endmodule

仿真log

ncsim> run
[20] Thread1: Wait for event_a is over
[20] Thread2: Wait for event_b is over
ncsim: *W,RNQUIE: Simulation is complete.

七、Systemverilog函数(SystemVerilog Functions)

函数的主要目的是返回一个可以在表达式中使用且不会占用仿真时间的值。

  • 函数不能有@、#、fork join或wait等时间控制语句
  • 函数不能启动任务,因为允许任务消耗模拟时间

7.1 verilog中函数的回顾

  代码中某些代码片段是重复的,并且在RTL中被多次调用。它们大多不消耗模拟时间,可能涉及复杂的计算,需要对不同的数据值进行计算。在这种情况下,我们可以声明一个函数,并将重复的代码放在函数中,并允许它返回结果。这将大大减少RTL中的行数,与C语言中的函数非常相似。
  函数的目的是返回要在表达式中使用的值。函数定义是以关键字function开始,后面跟着返回类型、名称和包含在括号中的端口列表。Verilog发现endfunction关键字时,就知道函数定义结束了。注意,函数应该至少声明一个输入,如果函数不返回任何东西,则返回类型将为void。
语法

function [automatic] [return_type] name ([port_list]);
	[statements]
endfunction

  关键字automatic将使函数可重入,并且在任务中声明的项将被动态分配,而不是在任务的不同调用之间共享。这对于递归函数以及在分叉时由N个进程并发执行同一函数非常有用。
函数声明
有两种方法可以声明函数的输入:

function [7:0] sum;
   input [7:0] a, b;
   begin
   	sum = a + b;
   end
endfunction

function [7:0] sum (input [7:0] a, b);
   begin
   	sum = a + b;
   end
endfunction

函数的返回值
  函数定义将隐式创建一个与函数同名的内部变量。因此,在函数的作用域中声明另一个同名变量是非法的。通过将函数结果赋值给内部变量来初始化返回值。sum = a + b;
函数的调用
函数调用是一个带有表达式的操作数,其语法如下所示。

reg [7:0] result;
reg [7:0] a, b;

initial begin
	a = 4;
	b = 5;
	#10 result = sum (a, b);
end

函数的规则

  • 函数不能包含任何时间控制的语句,如#,@,wait, posedge, negedge
  • 函数不能启动任务,因为它可能消耗模拟时间,但可以调用其他函数
  • 函数至少应该有一个输入
  • 函数不能有非阻塞赋值或强制释放或赋值-去赋值
  • 函数不能有任何触发器
  • 函数不能有输出或inout
    递归函数
    调用自己的函数称为递归函数。在下面的示例中,编写了一个递归函数来计算给定数字的阶乘。
module tb;
  initial begin
    integer result = factorial(4);
    $display("factorial(4) = %0d", result);
  end

	function automatic integer factorial(integer i);
      integer result = i;

      // This function is called within the body of this
      // function with a different argument
      if (i) begin
      	result = i * factorial(i-1);
        $display("i=%0d result=%0d", i, result);
      end else
        result = 1;

      return result;
	endfunction
endmodule
Simulation Log
xcelium> run
i=1 result=1
i=2 result=2
i=3 result=6
i=4 result=24
factorial(4) = 24
xmsim: *W,RNQUIE: Simulation is complete

7.2 systemverilog 函数声明

module tb;

  	// There are two ways to call the function:
  	initial begin
      // 1. Call function and assign value to a variable, and then use variable
      int s = sum(3, 4);
      $display ("sum(3,4) = %0d", s);

      // 2. Call function and directly use value returned
      $display ("sum(5,9) = %0d", sum(5,9));

      $display ("mul(3,1) = %0d", mul(3,1));
    end

  	// This function returns value of type "byte", and accepts two
  	// arguments "x" and "y". A return variable of the same name as
  	// function is implicitly declared and hence "sum" can be directly
  	// assigned without having to declare a separate return variable
	function byte sum (int x, int y);
		sum = x + y;
	endfunction

  	// Instead of assigning to "mul", the computed value can be returned
  	// using "return" keyword
  	function byte mul (int x, y);
      	return x * y;
  	endfunction
endmodule
Simulation Log
ncsim> run
sum(3,4) = 7
sum(5,9) = 14
mul(3,1) = 3
ncsim: *W,RNQUIE: Simulation is complete.

7.2.1 函数的声明和方向(directions)

在Verilog中引入了ANSI-C风格的声明,但端口方向的旧风格声明仍然有效。SystemVerilog函数可以将参数声明为输入和输出端口,如下例所示。

module tb;
	initial begin
      	int res, s;
      	s = sum(5,9);
    	$display ("s = %0d", sum(5,9));
      	$display ("sum(5,9) = %0d", sum(5,9));
      	$display ("mul(3,1) = %0d", mul(3,1,res));
      	$display ("res = %0d", res);
    end

	// Function has an 8-bit return value and accepts two inputs
  	// and provides the result through its output port and return val
  	function bit [7:0] sum;
      	input int x, y;
      	output sum;
		sum = x + y;
	endfunction

  	// Same as above but ports are given inline
  	function byte mul (input int x, y, output int res);
    	res = x*y + 1;
    	return x * y;
  	endfunction
endmodule
Simulation Log
ncsim> run
s = 14
sum(5,9) = 14
mul(3,1) = 3
res = 4
ncsim: *W,RNQUIE: Simulation is complete.

如何通过值传递参数?
  函数可以通过默认机制将参数传递给子函数。每个参数都被复制到子函数域中,对子函数域中的这个局部变量所做的任何更改在子函数之外都是不可见的。

module tb;

 initial begin
   int a, res;

   // 1. Lets pick a random value from 1 to 10 and assign to "a"
   a = $urandom_range(1, 10);

   $display ("Before calling fn: a=%0d res=%0d", a, res);

   // Function is called with "pass by value" which is the default mode
   res = fn(a);

   // Even if value of a is changed inside the function, it is not reflected here
   $display ("After calling fn: a=%0d res=%0d", a, res);
 end

 // This function accepts arguments in "pass by value" mode
 // and hence copies whatever arguments it gets into this local
 // variable called "a".
 function int fn(int a);

   // Any change to this local variable is not
 	// reflected in the main variable declared above within the
 	// initial block
   a = a + 5;

   // Return some computed value
   return a * 10;
 endfunction

endmodule
Simulation Log
ncsim> run
Before calling fn: a=2 res=0
After calling fn: a=2 res=70
ncsim: *W,RNQUIE: Simulation is complete.

从显示的日志中可以看出,初始块中的a的值没有被更改,即使函数中定义的局部变量被分配了不同的值。
如何通过引用传递参数?
  通过引用传递的参数不会复制到子函数域,而是将对原始参数的引用传递给子函数。参数声明之前有ref关键字。对子函数内部变量所做的任何更改都将反映在子函数外部的原始变量中。

// Use "ref" to make this function accept arguments by reference
// Also make the function automatic
function automatic int fn(ref int a);

 // Any change to this local variable will be
   // reflected in the main variable declared within the
   // initial block
 a = a + 5;

 // Return some computed value
 return a * 10;
endfunction
对于生命周期为静态的子函数,使用引用传递参数是非法的
Simulation Log
ncsim> run
Before calling fn: a=2 res=0
After calling fn: a=7 res=70
ncsim: *W,RNQUIE: Simulation is complete.

八 Systemveriog 任务(task)

  函数用于对输入进行一些处理并返回单个值,而任务更通用,可以计算多个结果值并使用output和inout类型参数返回它们。任务可以包含模拟耗时元素,如@、posedge和其他元素。
语法
任务不需要在端口列表中有一组参数,在这种情况下,它可以保持为空。

// Style 1
task [name];
   input  [port_list];
   inout  [port_list];
   output [port_list];
   begin
   	[statements]
   end
endtask

// Style 2
task [name] (input [port_list], inout [port_list], output [port_list]);
   begin
   	[statements]
   end
endtask

// Empty port list
task [name] ();
   begin
   	[statements]
   end
endtask

8.1 静态任务

如果任务是静态的,那么它的所有成员变量将在对已启动并并发运行的同一任务的不同调用之间共享;

task sum (input [7:0] a, b, output [7:0] c);
		begin
			c = a + b;
		end
	endtask
// or
	task sum;
		input  [7:0] a, b;
		output [7:0] c;
		begin
			c = a + b;
		end
	endtask

	initial begin
		reg [7:0] x, y , z;
		sum (x, y, z);
	end

任务启用参数(x, y, z)对应于任务定义的参数(a, b, c)。由于a和b是输入,x和y的值将分别放在a和b中。因为c被声明为输出并在调用期间与z连接,所以和将自动从c传递给变量z。

8.2 自动任务

关键字automatic将使任务可重用,否则默认情况下它将是静态的。自动任务中的所有项都是为每次调用动态分配的,而不是在并发运行的同一任务的调用之间共享。注意,自动任务项不能通过层次引用访问。

为了举例说明,考虑静态任务显示,它是从并发运行的不同初始块调用的。在本例中,任务中声明的整型变量在任务的所有调用之间共享,因此显示的值应该在每次调用中递增。

module tb;

  initial display();
  initial display();
  initial display();
  initial display();

  // This is a static task
  task display();
    integer i = 0;
    i = i + 1;
    $display("i=%0d", i);
  endtask
endmodule
Simulation Log
xcelium> run
i=1
i=2
i=3
i=4
xmsim: *W,RNQUIE: Simulation is complete.

如果任务是自动的,则在仿真内存中为任务的每次调用分配不同的空间,并且行为不同。

module tb;

  initial display();
  initial display();
  initial display();
  initial display();

  // Note that the task is now automatic
  task automatic display();
    integer i = 0;
    i = i + 1;
    $display("i=%0d", i);
  endtask
endmodule
Simulation Log
xcelium> run
i=1
i=1
i=1
i=1
xmsim: *W,RNQUIE: Simulation is complete.

8.3 全局任务

在所有模块之外声明的任务称为全局任务,因为它们具有全局作用域,可以在任何模块内调用。

// This task is outside all modules
task display();
  $display("Hello World !");
endtask

module des;
  initial begin
    display();
  end
endmodule
Simulation Log
xcelium> run
Hello World !
xmsim: *W,RNQUIE: Simulation is complete.

如果任务是在模块des中声明的,则必须引用模块实例名来调用它。

module tb;
	des u0();

	initial begin
		u0.display();  // Task is not visible in the module 'tb'
	end
endmodule

module des;
	initial begin
		display(); 	// Task definition is local to the module
	end

	task display();
		$display("Hello World");
	endtask
endmodule
Simulation Log
xcelium> run
Hello World
Hello World
xmsim: *W,RNQUIE: Simulation is complete.

8.4 函数和任务的区别

尽管Verilog函数和任务的目的相似,但它们之间有一些明显的区别。

函数任务
不能有时间控制语句/延迟,因此在相同的模拟时间单位中执行可以包含时间控制语句/延迟,并且只能在其他时间完成
由于上述规则,无法调用任务可以调用其他任务和函数
应该至少有一个输入参数,不能有输出或inout参数可以有任何类型的零个或多个参数
只能返回一个值不能返回值,但可以使用输出参数达到相同的效果
当函数试图调用任务或包含耗时语句时,编译器将报告错误。
module tb;
  reg signal;

  initial wait_for_1(signal);

  function wait_for_1(reg signal);
    #10;
  endfunction
endmodule
Simulation Log
    #10;
    |
xmvlog: *E,BADFCN (testbench.sv,7|4): illegal time/event control statement within a function or final block or analog initial block [10.3.4(IEEE)].

8.6 任务的关闭

可以使用disable关键字关闭任务。

module tb;

  initial display();

  initial begin
  	// After 50 time units, disable a particular named
  	// block T_DISPLAY inside the task called 'display'
    #50 disable display.T_DISPLAY;
  end

  task display();
    begin : T_DISPLAY
      $display("[%0t] T_Task started", $time);
      #100;
      $display("[%0t] T_Task ended", $time);
    end

    begin : S_DISPLAY
      #10;
      $display("[%0t] S_Task started", $time);
      #20;
      $display("[%0t] S_Task ended", $time);
    end
  endtask
endmodule
xcelium> run
[0] T_Task started
[60] S_Task started
[80] S_Task ended
xmsim: *W,RNQUIE: Simulation is complete.

当显示任务由第一个初始块启动时,T_DISPLAY启动并在时间达到50个时间单位关闭。下一个块S_DISPLAY立即启动并运行到80个时间单位完成。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SystemVerilog 是一种硬件描述语言(HDL),用于设计和验证数字电路。它是 Verilog HDL 的扩展,添加了一些新的特性和功能,使其更适用于现代的设计需求。SystemVerilog 支持面向对象编程,可以定义类和对象,并使用类继承和多态等概念。 以下是一些 SystemVerilog 的基础知识: 1. 模块(Module):SystemVerilog 的模块用于描述数字电路的功能单元。它有输入和输出端口,并包含一些行为和数据逻辑。 2. 端口(Port):模块的输入和输出信号被定义为端口。端口可以是输入、输出或双向的,并且可以具有不同的数据类型(如整数、浮点数、位向量等)。 3. 数据类型:SystemVerilog 提供了丰富的数据类型,包括整数、实数、位向量、结构体、联合体等。这些数据类型可以用于定义变量和信号。 4. 运算符:SystemVerilog 支持各种运算符,包括算术运算符、逻辑运算符、位运算符等。 5. 控制结构:SystemVerilog 提供了常见的控制结构,如 if-else 语句、for 循环、while 循环等,用于实现条件判断和循环操作。 6. 时序控制SystemVerilog 提供了时序控制语句,如延时语句和事件触发语句,用于模拟数字电路的时序行为。 7. 任务和函数:SystemVerilog 允许在模块定义任务和函数,用于执行一些特定的操作或计算。 8. 仿真和验证:SystemVerilog 可以用于编写测试代码,进行数字电路的仿真和验证。它提供了一些特定的语法和功能,用于生成测试向量、检测错误等。 这些是 SystemVerilog 的基础知识,希望对你有所帮助!如果你还有其他问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值