当前位置: 首页 > article >正文

我的 System Verilog 学习记录(10)


引言

本文简单介绍 SystemVerilog 的约束。

前文链接:

我的 System Verilog 学习记录(1)

我的 System Verilog 学习记录(2)

我的 System Verilog 学习记录(3)

我的 System Verilog 学习记录(4)

我的 System Verilog 学习记录(5)

我的 System Verilog 学习记录(6)

我的 System Verilog 学习记录(7)

我的 System Verilog 学习记录(8)

我的 System Verilog 学习记录(9)


简介

啥是直接测试?

验证工程师将首先创建一个称为验证计划的东西,详细说明需要在RTL模拟中测试的设计的每个功能,以及每个测试将如何创建针对特定功能的独立场景。

例如,如果有一个外设需要配置其寄存器,以便启动AXI总线事务,那么我们将使用不同的测试来以不同的方式配置这些寄存器并实现良好的覆盖范围。

这些是直接测试,每次测试都执行特定的任务以完成某项任务。

啥是随机化测试?

复杂的设计有许多场景和许多角例,这些场景和角例可以通过随机测试得到更好的验证,并且花费的精力和时间要少得多。以上面的相同示例为例,每次使用不同的种子运行测试时,测试都会为外围寄存器配置随机值,从而实现每次运行的不同场景。这将确保我们命中角例并发现任何隐藏的错误。

约束

SystemVerilog允许用户以紧凑、声明的方式指定约束,然后由内部求解器处理这些约束,以生成满足所有条件的随机值。从根本上说,约束无非是一种让我们定义应该为随机变量分配什么合法值的方法。通过关键字 rand 来声明正常变量是随机的。

上面的例子声明了一个名为pkt的类,在它的地址字段上有一个约束。注意,它的两个属性都以rand关键字为前缀,这告诉求解器这些变量在被要求时应该随机化。约束被称为addr Limit,它指定求解器可以为小于或等于8‘hB的地址分配任何随机值。由于8位变量addr是bit类型,它可以有从0到255的任何值,但如果有约束,有效值将被限制为11。

正如您所看到的,这一强大的功能将允许我们创建约束在对设计有效的范围内的变量,并将产生更好的验证效果。在接下来的几个章节中,我们将了解如何有效地使用SystemVerilog中的不同构造,这些构造允许我们以更好的方式描述约束。


随机变量

变量是使用 rand 或 randc 关键字声明的随机变量。它们可以用于普通变量、数组、动态数组或队列。

rand

让我们来看一个简单的类,它有一个名为 data 的3位变量,这个变量被随机化了10次。randomize()函数作为类对象的一部分被调用,以随机化该类对象中的所有rand类型变量。

声明为随机变量的变量是标准随机变量,其值在其范围内均匀分布。例如,上述代码片段中的变量数据是一个8位无符号整数,范围为0-255。如果该变量在没有任何约束的情况下随机化,则该范围内的任何值都将以相等的概率分配给该变量。在连续的随机化尝试中,该变量可能最终具有相同的值,但概率为1/256。

randc

声明为 randc 的变量是随机循环的,因此在重复任何特定值之前循环遍历其范围内的所有值。例如,上述代码片段中的变量数据的范围为0-3。如果该变量是无任何约束的随机化的,则该范围内的任何值都将被赋给该变量,但对于连续的随机化,只有在所有值至少被赋值一次之后,相同的值才会重复。randc 变量在其他随机变量之前被求解。我们将使用上面的相同示例,但稍有不同--将数据作为 randc 变量。

 请注意,在重复某个值之前,0-3‘h7范围内的所有值都已用尽。


约束块

约束块是类成员,就像变量、函数和任务一样。它们在类中具有唯一的名称。这些表达式块通常用于将随机变量的值限制在约束块中指定的特定值内。

语法

大括号中列出的表达式指定了为变量分配随机值时求解器必须考虑的条件。没有必要对每个变量都有一个约束,也没有必要将约束块限制为只有一个与单个变量有关的条件。但是,不能在多个块中展开冲突的约束,除非使用我们将在禁用约束中看到的CONSTRAINT_MODE()方法将其关闭。

空的约束对随机数的产生没有任何影响。

由于这些块的声明性性质,因此对它们有一些限制。请注意,约束块被包含在花括号中,而不是程序语句的 begin end 关键字。
约束可以放置在类主体定义的内部或其之外。当约束在类的主体外部定义时,它们被称为外部/约束,并通过使用范围解析操作符 :: 进行访问。

类内约束示例

// ==== 类内约束示例 ==== 
class EXP;
	randc bit [3:0] mode;
	constraint CON_MODE {
						mode >5;
						mode <16;
						mode[0] == 1'b0;
						};
endclass

module Constraint1();
	EXP exp;
	initial
	begin
		exp = new();

		for(int k = 0; k<6;k++)
		begin
			exp.randomize();
			$display("mode = %0d",exp.mode);
		end
	end
endmodule

仿真输出:

类外约束示例

外部约束可以是隐式的,也可以是显式的。如果使用了显式约束,并且在类体之外没有提供相应的约束块,则会出现错误。但对于隐式约束,不会出现错误,但仿真器可能会发出警告。(不同仿真器此处会有区别,不一定都是报错)

// ==== 类内约束示例 ==== 
class EXP;
	randc bit [3:0] mode;
	constraint CON_IMPLICIT;//显示声明
	extern constraint COM_EXPLICIT;//用关键字 extern 表示隐式声明
endclass

constraint EXP::CON_IMPLICIT {mode >5; };//显式约束在类外若不提供约束块则会报错
constraint EXP::COM_EXPLICIT {mode <15;};//隐式约束在类外若不提供约束块则会警告

module Constraint1();
	EXP exp;
	initial
	begin
		exp = new();

		for(int k = 0; k<10;k++)
		begin
			exp.randomize();
			$display("mode = %0d",exp.mode);
		end
	end
endmodule

仿真结果:

 没有在类外对隐式约束进行声明时,Questa Sim会报警告:

  • 为给定的约束原型提供多个约束块
  • 编写与外部原型声明中使用的名称相同的约束块

数组随机化

SystemVerilog随机化还适用于数组数据结构,如静态数组、动态数组和队列。变量必须声明为类型为rand 或 randc,才能实现变量的随机化。

静态数组

静态数组的随机化很简单,可以像任何其他类型的SystemVerilog变量一样进行。

动态数组

动态数组是在数组声明过程中不预先确定大小的数组。这些数组可以具有可变大小,因为可以随时向数组中添加新成员。

考虑下面的示例,其中我们声明了一个动态数组,如空方括号 [ ] 类型为 rand 所示。定义了一个约束,将动态数组的大小限制在5到8之间。另一个约束定义为数组中的每个元素分配其索引值。

 如果大小不受约束,随机化会产生空数组-适用于动态数组和队列。

注意,数组大小被随机化为9(来自约束c_array),并且每个索引处的元素具有索引本身的值(来自约束c_val)

队列


常用约束方法

现在,让我们来看看在约束块中编写约束表达式的一些常见方法。

简单表达式

一个表达式中只能有1个关系运算符 (> >= < <= ==)。

 不能在约束块内进行赋值,因为它只包含表达式。相反,您必须使用等价运算符==,如上面示例中名为my_min的约束所示,其中min将获得值16,所有其他变量将被随机化。这是将特定值固定到变量的一种方法,即使求解器试图将其随机化。

还可以使用稍微复杂一点的表达式,如上图所示,其中min表示存储华氏温度的变量,low表示类对象Temp中的变量,该对象以摄氏度为单位保持温度。

class RAND_CLASS;
	randc bit [7:0] MIN_VALUE,MAX_VALUE,TYPICAL_VALUE,FIXED_VALUE;

	constraint RAND_CON{
						MIN_VALUE >= 10;
						TYPICAL_VALUE > 20; TYPICAL_VALUE < 100;
						MAX_VALUE < 200;
	}

	constraint FIXED_CON{
						FIXED_VALUE == 128;
	}

	function string display();
		return $sformatf("MIN_VALUE=%0d MAX_VALUE=%0d TYPICAL_VALUE=%0d FIXED_VALUE=%0d ",MIN_VALUE,MAX_VALUE,TYPICAL_VALUE,FIXED_VALUE);
	endfunction 
endclass 

module TEST_CON();
	initial
	begin
		static RAND_CLASS RAND_CLASS_OBJ = new();
		for(int k = 0;k<100;k++)
		begin
			// RAND_CLASS RAND_CLASS_OBJ = new();
			RAND_CLASS_OBJ.randomize();
			$display("k = %0d %s",k,RAND_CLASS_OBJ.display());
		end
	end
endmodule

inside 操作符

可以使用内部运算符指定下限和上限作为下面所示表达式的替代。

请注意,inside 结构包括下限和上限。SystemVerilog收集所有的值,并以相等的概率在这些值之间进行选择,除非对变量有其他约束。

非 inside 操作符

如果您想要特定范围之外的任何值,倒置约束可以写成如下所示。请注意,重复的随机化给出了除在3到6范围之外的所有值。

加权分布

dist 运算符允许您创建加权分布,以便比其他值更频繁地选择某些值。运算符 := 指定范围内每个指定值的权重相同,而运算符 :/ 指定在所有值之间平均分配权重。

:= 运算符

在上例约束中,0的权重是20,6是40,7是10,1到5是50,总共是320。因此,选择0的概率是20/320,选择1到5之间的值的概率是50/320。我们来看一个简单的例子。

:/ 运算符

在dist2中,0的权重是20,6是10,7是20,而1到5的权重总和是50,因此各有10。因此,选择0的概率是20/100,选择1到5之间的值的概率是10/100。让我们来看一个简单的例子。

双向约束

约束块不像过程代码那样从上到下执行,而是同时处于活动状态。让我们用另一个例子来看看这一点。


inside

SystemVerilog中 的 inside 关键字允许检查给定值是否位于使用 inside 短语指定的范围内。除了用作约束之外,它还可以在IF和其他条件语句中使用。

语法

示例:

用在条件语句中

在下面的示例中,inside 运算符在 If else 语句和三目运算符中都使用。如果m_data的随机化值在4到9之间,包括4和9,则标志的值为1,否则,标志的值为0。

module TEST_INSIDE ();
	bit [3:0] m_data;
	bit flag;

	initial
	begin
		for(int i=0;i<10;i++)
		begin
			m_data = $random();
			flag = m_data inside {[5:10]} ? 1:0;

			if(m_data inside {[5:10]})
				$display("m_data = %0d inside [5:10],flag = %0d",m_data,flag);
			else
				$display("m_data = %0d outside [5:10],flag = %0d",m_data,flag);
		end
	end
endmodule

仿真结果:

用在约束中

内部运算符在约束中非常有用,它使代码更短、更具可读性。

非 inside

与 inside 操作符所做的相反,可以通过在其前面放置一个非运算符符号 ! 来实现。这既适用于约束语句,也适用于条件语句。下面的示例与我们之前看到的相同,只是它的约束已调整为反映倒置的Inside语句。

示例

假设我们有一个位于地址范围0x4000和0x5FFF之间的存储器,它被一分为二。第一部分用来存储指令,第二部分用来存储数据。假设我们想要将数据的地址随机化,使其落入内存的数据部分,我们可以很容易地使用 inside 运算符。


关联约束(Implication Constraint)

SV中引入两种声明条件关系的结构:

请注意,对于所有大于10的len值,mode不一定都是2。但是,约束条件是,如果mode为2,则len应该大于10。

示例

关联操作符(Implication Operator)

关联运算符 -> 可以在约束表达式中使用,以显示两个变量之间的条件关系。如果 -> 左侧表达式为真,那么右侧的约束表达式应该被满足;反之则反。

示例

class RAND_CLASS;
	
	rand bit [3:0] mode;
	rand bit 	   mode_val;

	constraint COM_MODE {
						mode inside {[1:4]} -> mode_val == 1;
	}
endclass 

module TEST_CON();
	int k = 0;

	initial
	begin
		RAND_CLASS RAND_CLASS_OBJ = new;
		repeat(10)
		begin
			k++;
			RAND_CLASS_OBJ.randomize();
			$display("k = %0d mode = %0d mode_val = %0d",k,RAND_CLASS_OBJ.mode,RAND_CLASS_OBJ.mode_val);
		end
	end
endmodule

仿真结果:

if-else 约束

if-else 约束允许嵌套,分支语句数目大于1时,要用花括号 {} ,这和 C语言很像。看个例子:

class RAND_CLASS;
	
	rand bit [3:0] mode;
	rand bit 	   mode_val;

	constraint COM_MODE {
						// mode inside {[1:4]} -> mode_val == 1;
						if(mode inside {[1:4]})
							mode_val == 1;
						else
						{
							if(mode == 10)
								mode_val == 1;
							else
								mode_val == 0;
						}
	}
endclass 

module TEST_CON();
	int k = 0;

	initial
	begin
		RAND_CLASS RAND_CLASS_OBJ = new;
		repeat(10)
		begin
			k++;
			RAND_CLASS_OBJ.randomize();
			$display("k = %0d mode = %0d mode_val = %0d",k,RAND_CLASS_OBJ.mode,RAND_CLASS_OBJ.mode_val);
		end
	end
endmodule

仿真结果:


foreach 约束

foreach约束一般用来对数组进行约束;

先看个小例子:

class RAND_CLASS;
	
	rand bit [3:0] RAND_ARRAY[16];

	constraint CON_ARRAY {
						  foreach(RAND_ARRAY[i])
						  {
						  	RAND_ARRAY[i] == 15-i;
						  }
	}

endclass 

module TEST_CON();
	initial
	begin
		RAND_CLASS RAND_CLASS_OBJ = new;
		repeat(5)
		begin
			RAND_CLASS_OBJ.randomize();
			$display("RAND_ARRAY = %p",RAND_CLASS_OBJ.RAND_ARRAY);
		end
	end
endmodule

仿真结果:

动态数组/队列

由于动态数组/队列在声明时,其规格大小未知,所以其不能直接使用foreach。因此其数组大小也应该被约束。示例:

class RAND_CLASS;
	// 动态数组/队列 
	rand bit [3:0] RAND_ARRAY [];
	rand bit [3:0] RAND_QUENE [$];

	// 队列约束
	constraint CON_QUENE {
							RAND_QUENE.size() == 10;
							foreach(RAND_QUENE[k])
							{
								RAND_QUENE[k] == 10-k;
							}
	}
	// 动态数组约束
	constraint CON_ARRAY {
							foreach(RAND_ARRAY[k])
							{
								RAND_ARRAY[k] == k;
							}
	}

	function new ();
		RAND_ARRAY = new[5]; // 构造函数声明规格
	endfunction  

endclass 

module TEST_CON();
	initial
	begin
		RAND_CLASS RAND_CLASS_OBJ = new;
		RAND_CLASS_OBJ.randomize();
		$display("RAND_ARRAY = %p \nRAND_ARRAY = %p",RAND_CLASS_OBJ.RAND_ARRAY,RAND_CLASS_OBJ.RAND_QUENE);
	end
endmodule

仿真:

多维数组

SystemVerilog约束足够强大,也可以应用于多维数组。在下面的示例中,我们有一个具有压缩结构的多维静态数组。

示例:

class RAND_CLASS;
	// 多维数据定义
	rand bit [3:0] [3:0] RAND_MD_ARRAY [4][4]; 
	// 多维数据约束
	constraint CON_MD_ARRAY {
								foreach(RAND_MD_ARRAY[i])
								{
									foreach(RAND_MD_ARRAY[i][j])
									{
										foreach(RAND_MD_ARRAY[i][j][k])
										{
											RAND_MD_ARRAY[i][j][k] == k+i+j;
										}
									}
								}
							}	 

endclass 

module TEST_CON();
	initial
	begin
		RAND_CLASS RAND_CLASS_OBJ = new;
		RAND_CLASS_OBJ.randomize();
		for(int i=0; i<4; i++)
		begin
			for(int j=0; j<4; j++)
			begin
				$display("RAND_MD_ARRAY[%0d][%0d] = %p ",i,j,RAND_CLASS_OBJ.RAND_MD_ARRAY[i][j]);
			end
		end
	end
endmodule

仿真结果:


多维动态数组

约束多维动态数组比较棘手,并且可能不是所有仿真器都支持。(Questa Sim,64位,2017版本就不支持)在下面所示的例子中,2D数组Md数组的XorY元素的大小是未知的。

数组缩减迭代约束

这是SV支持的另一个非常有用的构造和技术。
数组缩减方法可以从int整型值的解压缩数组中产生单个值。这可以在一个约束范围内使用,以允许在随机化期间考虑该表达式。
例如,考虑一个N个元素的数组必须是随机的,这样所有元素的和都等于某个值。数组约简运算符可以与with子句一起使用,这样它就可以遍历数组的每个元素,并将其包含在约束求解器中。


solve before

默认情况下,SV的约束求解器试图给出随机值的均匀分布。因此,任何合法值成为一个给定约束的解的概率是相同的。
但是使用 solve-before 可以改变概率的分布,这样某些情况可以被迫比其他情况更频繁地选择。我们将通过比较一个有和没有使用这个构造的示例来看到求解的效果。

随机分布示例

例如,考虑下面的例子,其中一个3位随机变量b可以有8个合法值,取值为0的概率与所有其他可能值的概率相同。

请注意,即使在下面显示的仿真输出中有重复的值,这也只是意味着先前的随机化对当前迭代没有影响。因此,随机生成器可以自由选取8个值中的任何一个,而不考虑先前的值是什么。

没有 solve - before

考虑下面的示例,其中声明了两个随机变量。约束确保只要a为1,b就会得到0x3。

当a为0时,b可以取这4个值中的任意一个。所以这里有4种组合。接下来,当a是1时,b可以只能取一个值,所以只有1个组合。
因此有5种可能的组合,如果约束求解器必须分配每个组合一个相等的概率,那么选择任何一种组合的概率是1/5。
下表列出了a和b的每个组合的概率。

有 solve-before

SystemVerilog允许一种机制来对变量进行排序,以便可以独立于b进行选择这是使用solve关键字完成的。

由于首先求解a,因此选择0或1的概率为50%。接下来,为b选择一个值的概率取决于为a选择的值。

请注意,在使用solve之前和之后,b的概率几乎为0%,它已经变成了略高于50%。

使用限制

randc 变量是不允许的,因为它们总是先解决
变量应该是int整型值
在排序中不应该有循环依赖关系,比如solve a before b 和 solve b before a 同时使用。


静态约束

静态约束在类的所有实例中共享。用关键字 static 进行声明。

只有在使用 constraint_mode() 方法打开和关闭约束时,约束才会受到static关键字的影响。当使用此方法关闭非静态约束时,将在调用该方法的类的特定实例中关闭约束。但是,当使用此方法关闭和打开静态约束时,将在类的所有实例中关闭和打开约束。

语法

接下来,我们将比较非静态约束和静态约束,看看它们有什么不同。

非静态约束

默认情况下就是非静态约束,非静态约束在类的每一个实例中都拷贝。示例:

静态约束

关闭非静态约束

关闭 c1 对应的非静态约束,仿真的结果:

关闭静态约束

关闭静态约束后 ,类的两个实例化对象均会受到影响。


内存分区示例

请考虑以下在实际项目中通常会遇到的实际示例。

内存块随机化

假设我们在设计中有一个2kB的SRAM来存储一些数据,假设我们需要在2kb的RAM空间中找到一个可用于某些特定目的的地址块。

示例

// ========================================================
// | 说明:内存分区示例
// |
// |
// ========================================================

// 类定义
class MEMORY_BLOCK;
	// 数据字段声明
	bit [31:0] MEM_RAM_STR_ADDR;//内存RAM的起始地址
	bit [31:0] MEM_RAM_END_ADDR;//内存RAM的结束地址
	
	rand bit [31:0] BLOCK_STR_ADDR;//指向块内存的起始地址
	rand bit [31:0] BLOCK_END_ADDR;//指向块内存的结束地址

	rand int 		BLOCK_SIZE;//块内存空间大小 单位 B

	// 约束声明
	constraint CON_ADDR {
						BLOCK_STR_ADDR >= MEM_RAM_STR_ADDR;
						BLOCK_STR_ADDR <  MEM_RAM_END_ADDR;
						BLOCK_STR_ADDR % 4 == 0;//块起始地址 4字节对齐
						BLOCK_END_ADDR == BLOCK_STR_ADDR + BLOCK_SIZE - 1;
						};

	constraint CON_BLOCK_SIZE {
							  BLOCK_SIZE inside {128,256,512};
							  };

	// 函数定义
	function void display_mem_part_detail();
		$display("==== ==== ====  Memory Block Detail  ==== ==== ====");
		$display("RAM START ADDR   = 0x%0h",MEM_RAM_STR_ADDR);
		$display("RAM END   ADDR   = 0x%0h",MEM_RAM_END_ADDR);
		$display("BLOCK START ADDR = 0x%0h",BLOCK_STR_ADDR);
		$display("BLOCK END   ADDR = 0x%0h",BLOCK_END_ADDR);
		$display("BLOCK SIZE       = 0x%0h",BLOCK_SIZE);
		$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== =");
	endfunction
endclass

// Test Bench
module MEM_PART_EXP ();

	MEMORY_BLOCK MEMORY_BLOCK_OBJ = new;

	initial
	begin
		MEMORY_BLOCK_OBJ.MEM_RAM_STR_ADDR = 32'h0;
		MEMORY_BLOCK_OBJ.MEM_RAM_END_ADDR = 32'hFFF;//4kB RAM
		MEMORY_BLOCK_OBJ.randomize();
		MEMORY_BLOCK_OBJ.display_mem_part_detail();
	end
endmodule

仿真:

内存块均分

// ========================================================
// | 说明:内存分区示例
// |
// |
// ========================================================

// 类定义
class MEMORY_BLOCK;
	// 数据字段声明
	bit [31:0] MEM_RAM_STR_ADDR;//内存RAM的起始地址
	bit [31:0] MEM_RAM_END_ADDR;//内存RAM的结束地址
	
	rand int 		MEM_PART_NUM;//内存分块个数
	rand bit [31:0] PART_STR_ADDR [];//指向块内存的起始地址
	
	rand int 		MEM_PART_SIZE;//块内存空间大小 单位 B

	// 约束声明
	constraint CON_PART_NUM {
								MEM_PART_NUM >= 4;
								MEM_PART_NUM <  8;
							};

	constraint CON_PART_SIZE {
							  	MEM_PART_SIZE == (MEM_RAM_END_ADDR - MEM_RAM_STR_ADDR)/MEM_PART_NUM;
							  };

	constraint CON_PART {
							PART_STR_ADDR.size() == MEM_PART_NUM;

							foreach (PART_STR_ADDR[i])
							{
								if(i)
									PART_STR_ADDR[i] == PART_STR_ADDR[i-1] + MEM_PART_SIZE;
								else
									PART_STR_ADDR[i] == MEM_RAM_STR_ADDR;
							}
						};
	// 函数定义
	function void display_mem_part_detail();
		$display("==== ==== ====  Memory Block Details  ==== ==== ====");
		$display("RAM START ADDR   = 0x%0h",MEM_RAM_STR_ADDR);
		$display("RAM END   ADDR   = 0x%0h",MEM_RAM_END_ADDR);
		$display("Memory Partitions=   %0d",MEM_PART_NUM);
		$display("Partitions Size  =   %0d",MEM_PART_SIZE);
		$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== =");
		$display("Partition Details:");
		foreach(PART_STR_ADDR[i])
			$display("Part %0d : Strat Addr = 0x%0h",i,PART_STR_ADDR[i]);
	endfunction
endclass

// Test Bench
module MEM_PART_EXP ();

	MEMORY_BLOCK MEMORY_BLOCK_OBJ = new;

	initial
	begin
		MEMORY_BLOCK_OBJ.MEM_RAM_STR_ADDR = 32'h0;
		MEMORY_BLOCK_OBJ.MEM_RAM_END_ADDR = 32'hFFF;//4kB RAM
		MEMORY_BLOCK_OBJ.randomize();
		MEMORY_BLOCK_OBJ.display_mem_part_detail();
	end
endmodule

仿真:

非均匀内存分区

示例:

// ========================================================
// | 说明:内存分区示例
// |
// |
// ========================================================

// 类定义
class MEMORY_BLOCK;
	// 数据字段声明
	bit [31:0] MEM_RAM_STR_ADDR;//内存RAM的起始地址
	bit [31:0] MEM_RAM_END_ADDR;//内存RAM的结束地址
	
	rand int 		MEM_PART_NUM;    //内存分块个数
	rand bit [31:0] PART_STR_ADDR [];//指向块内存的起始地址
	
	rand int 		MEM_PART_SIZE [];//块内存空间大小 单位 B

	// 约束声明
	constraint CON_PART_NUM {
								MEM_PART_NUM >= 4;
								MEM_PART_NUM <  8;
							};

	constraint CON_PART_SIZE {
							  	MEM_PART_SIZE.size() == MEM_PART_NUM;
							  	MEM_PART_SIZE.sum()  == MEM_RAM_END_ADDR - MEM_RAM_STR_ADDR + 1;
							  	foreach (MEM_PART_SIZE[i])
							  		MEM_PART_SIZE[i] inside {16,32,64,128,256,512,1024};
							  };

	constraint CON_PART {
							PART_STR_ADDR.size() == MEM_PART_NUM;

							foreach (PART_STR_ADDR[i])
							{
								if(i)
									PART_STR_ADDR[i] == PART_STR_ADDR[i-1] + MEM_PART_SIZE[i-1];
								else
									PART_STR_ADDR[i] == MEM_RAM_STR_ADDR;
							}
						};
	// 函数定义
	function void display_mem_part_detail();
		$display("==== ==== ====  Memory Block Details  ==== ==== ====");
		$display("RAM START ADDR   = 0x%0h",MEM_RAM_STR_ADDR);
		$display("RAM END   ADDR   = 0x%0h",MEM_RAM_END_ADDR);
		$display("Memory Partitions=   %0d",MEM_PART_NUM);
		$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== =");
		$display("Partition Details:");
		foreach(PART_STR_ADDR[i])
			$display("Part %0d : Strat Addr = 0x%0h  Size = %0d Bytes",i,PART_STR_ADDR[i],MEM_PART_SIZE[i]);
	endfunction
endclass

// Test Bench
module MEM_PART_EXP ();

	MEMORY_BLOCK MEMORY_BLOCK_OBJ = new;

	initial
	begin
		MEMORY_BLOCK_OBJ.MEM_RAM_STR_ADDR = 32'h0;
		MEMORY_BLOCK_OBJ.MEM_RAM_END_ADDR = 32'hFFF;//4kB RAM
		MEMORY_BLOCK_OBJ.randomize();
		MEMORY_BLOCK_OBJ.display_mem_part_detail();
	end
endmodule

仿真:

非均匀内存分区(有隔离带隙)

// ========================================================
// | 说明:内存分区示例
// |
// |
// ========================================================

// 类定义
class MEMORY_BLOCK;
	// 数据字段声明
	bit [31:0] MEM_RAM_STR_ADDR;//内存RAM的起始地址
	bit [31:0] MEM_RAM_END_ADDR;//内存RAM的结束地址
	
	rand int 		MEM_PART_NUM;    //内存分块个数
	rand bit [31:0] PART_STR_ADDR [];//指向块内存的起始地址
	
	rand int 		MEM_PART_SIZE [];//块内存空间大小 单位 B
	rand int 		MEM_SPACE     [];//内存块之间的空隙 单位 B

	// 约束声明
	constraint CON_PART_NUM {
								MEM_PART_NUM >= 16;
								MEM_PART_NUM <= 32;
							};

	constraint CON_PART_SIZE {
							  	MEM_PART_SIZE.size() == MEM_PART_NUM;
							  	MEM_SPACE.size()     == MEM_PART_NUM - 1;
							  	MEM_PART_SIZE.sum() + MEM_SPACE.sum() == MEM_RAM_END_ADDR - MEM_RAM_STR_ADDR + 1;
							  	foreach (MEM_PART_SIZE[i])
							  	{
							  		MEM_PART_SIZE[i] inside {16,32,64,128,256,512,1024};

							  		if(i < MEM_SPACE.size())
							  			MEM_SPACE[i] inside {0,2,4,8,16,32,64};
							  	}
							  };

	constraint CON_PART {
							PART_STR_ADDR.size() == MEM_PART_NUM;

							foreach (PART_STR_ADDR[i])
							{
								if(i)
									PART_STR_ADDR[i] == PART_STR_ADDR[i-1] + MEM_PART_SIZE[i-1] + MEM_SPACE[i-1];
								else
									PART_STR_ADDR[i] == MEM_RAM_STR_ADDR;
							}
						};
	// 函数定义
	function void display_mem_part_detail();
		$display("==== ==== ====  Memory Block Details  ==== ==== ====");
		$display("RAM START ADDR   = 0x%0h",MEM_RAM_STR_ADDR);
		$display("RAM END   ADDR   = 0x%0h",MEM_RAM_END_ADDR);
		$display("Memory Partitions=   %0d",MEM_PART_NUM);
		$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== =");
		$display("Partition Details:");
		foreach(PART_STR_ADDR[i])
			$display("Part %0d : Strat Addr = 0x%0h  Size = %0d Bytes  Space = %0d Bytes",i,PART_STR_ADDR[i],MEM_PART_SIZE[i],MEM_SPACE[i]);
	endfunction
endclass

// Test Bench
module MEM_PART_EXP ();

	MEMORY_BLOCK MEMORY_BLOCK_OBJ = new;

	initial
	begin
		MEMORY_BLOCK_OBJ.MEM_RAM_STR_ADDR = 32'h0;
		MEMORY_BLOCK_OBJ.MEM_RAM_END_ADDR = 32'h3FFF;//16kB RAM
		MEMORY_BLOCK_OBJ.randomize();
		MEMORY_BLOCK_OBJ.display_mem_part_detail();
	end
endmodule

仿真:

用于程序和数据的分区

在本例中,内存被划分为程序、数据和空空间的区域。一个动态数组用于存储每个程序、数据和空空间的大小。使用SV的约束,所有区域的总大小与总RAM空间相匹配。
下面显示的代码随机化了程序、数据和空间区域的总数。这个数字还会影响每个程序、数据和空间的大小,因此所有程序、数据和空空间的总和应该等于总RAM空间。

示例:

typedef struct{
	int STR_ADDR;
	int END_ADDR;
}S_RAM_ADDR;


class RAM_SPACE;
	// 变量定义
	rand int PROGRAM_NUM ;//程序内存块的数目
	rand int DATA_NUM    ;//数据内存块的数目
	rand int SPACE_NUM   ;//分隔内存块的数目

	rand int MAX_PROGRAM_NUM;//程序内存块的最大个数
	rand int MAX_DATA_NUM   ;//程序数据块的最大个数
	rand int MAX_SPACE_NUM  ;//SPACE的最大个数

	rand int PAOGRAM_SIZE[]; //每个程序内存块的尺寸 单位:字节
	rand int DATA_SIZE[]   ; //每个程序内存块的尺寸 单位:字节
	rand int SPACE_SIZE[]  ; //每个程序内存块的尺寸 单位:字节

	S_RAM_ADDR S_RAM_ADDR_;//S_RAM_ADDR 结构体变量

	// 约束管理
	constraint C_MAX{
					MAX_PROGRAM_NUM  == 100;
					MAX_DATA_NUM     == 100;
					MAX_SPACE_NUM    == 40;
					};
	constraint C_NUM{
					PROGRAM_NUM inside{[1:MAX_PROGRAM_NUM]};
					DATA_NUM    inside{[1:MAX_DATA_NUM]};
					SPACE_NUM   inside{[1:MAX_SPACE_NUM]};
					};
	constraint C_SIZE{
					PAOGRAM_SIZE.size() == PROGRAM_NUM;
					DATA_SIZE.size() 	== DATA_NUM;	
					SPACE_SIZE.size() 	== SPACE_NUM;
					 };
	constraint C_RAM{
						foreach(PAOGRAM_SIZE[i]) 
						{
							PAOGRAM_SIZE[i] inside {4,8,32,64,128,512};
							PAOGRAM_SIZE[i] dist {[4:8]:/10,[32:64]:/20,[128:512]:/75};
							PAOGRAM_SIZE[i] % 4 == 0;
						}
						foreach(DATA_SIZE[i]) 
						{
							DATA_SIZE[i] inside {64,128,512,1024};
							DATA_SIZE[i] dist{64:=10,128:=20,512:=60,1024:=10};
						}
						foreach(SPACE_SIZE[i]) 
						{
							SPACE_SIZE[i] inside {4,8,32,64,128,512,1024};
							DATA_SIZE[i] dist{4:=5,8:=5,32:=5,64:=10,128:=40,512:=60,1024:=10};
						}
						S_RAM_ADDR_.END_ADDR-S_RAM_ADDR_.STR_ADDR+1 == PAOGRAM_SIZE.sum()+DATA_SIZE.sum()+SPACE_SIZE.sum();
					};

	// 函数定义
	function void display();
		$display("========== ========== RAM SPACE DISTRIBUTION DETAILS ========== ==========");
		$display("PROGRAM_NUM = %0d , DATA_NUM = %0d , SPACE_NUM = %0d",PROGRAM_NUM,DATA_NUM,SPACE_NUM);
		foreach(PAOGRAM_SIZE[i])
			$display("PAOGRAM_SIZE[%0d] = %0d Bytes",i,PAOGRAM_SIZE[i]);
		foreach(DATA_SIZE[i])
			$display("DATA_SIZE[%0d] = %0d Bytes",i,DATA_SIZE[i]);
		foreach(SPACE_SIZE[i])
			$display("SPACE_SIZE[%0d] = %0d Bytes",i,SPACE_SIZE[i]);
		$display("========== ========== ========== ========== ======== ========== ==========");
	endfunction
endclass

module TB_CON();
	RAM_SPACE OBJ = new();
	initial
	begin
		OBJ.S_RAM_ADDR_.STR_ADDR = 32'd0;
		OBJ.S_RAM_ADDR_.END_ADDR = 16*1024-1;
		assert(OBJ.randomize());
		OBJ.display();
		assert(OBJ.randomize());
		OBJ.display();
 	end
endmodule

 仿真结果:

# ========== ========== RAM SPACE DISTRIBUTION DETAILS ========== ==========
# PROGRAM_NUM = 78 , DATA_NUM = 53 , SPACE_NUM = 32
# PAOGRAM_SIZE[0] = 4 Bytes
# PAOGRAM_SIZE[1] = 128 Bytes
# PAOGRAM_SIZE[2] = 4 Bytes
# PAOGRAM_SIZE[3] = 128 Bytes
# PAOGRAM_SIZE[4] = 8 Bytes
# PAOGRAM_SIZE[5] = 128 Bytes
# PAOGRAM_SIZE[6] = 4 Bytes
# PAOGRAM_SIZE[7] = 4 Bytes
# PAOGRAM_SIZE[8] = 4 Bytes
# PAOGRAM_SIZE[9] = 512 Bytes
# PAOGRAM_SIZE[10] = 512 Bytes
# PAOGRAM_SIZE[11] = 512 Bytes
# PAOGRAM_SIZE[12] = 512 Bytes
# PAOGRAM_SIZE[13] = 32 Bytes
# PAOGRAM_SIZE[14] = 4 Bytes
# PAOGRAM_SIZE[15] = 128 Bytes
# PAOGRAM_SIZE[16] = 128 Bytes
# PAOGRAM_SIZE[17] = 32 Bytes
# PAOGRAM_SIZE[18] = 4 Bytes
# PAOGRAM_SIZE[19] = 4 Bytes
# PAOGRAM_SIZE[20] = 128 Bytes
# PAOGRAM_SIZE[21] = 4 Bytes
# PAOGRAM_SIZE[22] = 4 Bytes
# PAOGRAM_SIZE[23] = 4 Bytes
# PAOGRAM_SIZE[24] = 4 Bytes
# PAOGRAM_SIZE[25] = 4 Bytes
# PAOGRAM_SIZE[26] = 4 Bytes
# PAOGRAM_SIZE[27] = 512 Bytes
# PAOGRAM_SIZE[28] = 32 Bytes
# PAOGRAM_SIZE[29] = 4 Bytes
# PAOGRAM_SIZE[30] = 4 Bytes
# PAOGRAM_SIZE[31] = 128 Bytes
# PAOGRAM_SIZE[32] = 4 Bytes
# PAOGRAM_SIZE[33] = 4 Bytes
# PAOGRAM_SIZE[34] = 4 Bytes
# PAOGRAM_SIZE[35] = 4 Bytes
# PAOGRAM_SIZE[36] = 8 Bytes
# PAOGRAM_SIZE[37] = 4 Bytes
# PAOGRAM_SIZE[38] = 4 Bytes
# PAOGRAM_SIZE[39] = 4 Bytes
# PAOGRAM_SIZE[40] = 4 Bytes
# PAOGRAM_SIZE[41] = 4 Bytes
# PAOGRAM_SIZE[42] = 4 Bytes
# PAOGRAM_SIZE[43] = 4 Bytes
# PAOGRAM_SIZE[44] = 32 Bytes
# PAOGRAM_SIZE[45] = 32 Bytes
# PAOGRAM_SIZE[46] = 4 Bytes
# PAOGRAM_SIZE[47] = 512 Bytes
# PAOGRAM_SIZE[48] = 4 Bytes
# PAOGRAM_SIZE[49] = 4 Bytes
# PAOGRAM_SIZE[50] = 4 Bytes
# PAOGRAM_SIZE[51] = 4 Bytes
# PAOGRAM_SIZE[52] = 4 Bytes
# PAOGRAM_SIZE[53] = 4 Bytes
# PAOGRAM_SIZE[54] = 512 Bytes
# PAOGRAM_SIZE[55] = 512 Bytes
# PAOGRAM_SIZE[56] = 4 Bytes
# PAOGRAM_SIZE[57] = 4 Bytes
# PAOGRAM_SIZE[58] = 4 Bytes
# PAOGRAM_SIZE[59] = 4 Bytes
# PAOGRAM_SIZE[60] = 128 Bytes
# PAOGRAM_SIZE[61] = 128 Bytes
# PAOGRAM_SIZE[62] = 4 Bytes
# PAOGRAM_SIZE[63] = 4 Bytes
# PAOGRAM_SIZE[64] = 4 Bytes
# PAOGRAM_SIZE[65] = 4 Bytes
# PAOGRAM_SIZE[66] = 4 Bytes
# PAOGRAM_SIZE[67] = 4 Bytes
# PAOGRAM_SIZE[68] = 4 Bytes
# PAOGRAM_SIZE[69] = 4 Bytes
# PAOGRAM_SIZE[70] = 4 Bytes
# PAOGRAM_SIZE[71] = 4 Bytes
# PAOGRAM_SIZE[72] = 4 Bytes
# PAOGRAM_SIZE[73] = 4 Bytes
# PAOGRAM_SIZE[74] = 4 Bytes
# PAOGRAM_SIZE[75] = 128 Bytes
# PAOGRAM_SIZE[76] = 512 Bytes
# PAOGRAM_SIZE[77] = 4 Bytes
# DATA_SIZE[0] = 64 Bytes
# DATA_SIZE[1] = 512 Bytes
# DATA_SIZE[2] = 64 Bytes
# DATA_SIZE[3] = 64 Bytes
# DATA_SIZE[4] = 64 Bytes
# DATA_SIZE[5] = 64 Bytes
# DATA_SIZE[6] = 64 Bytes
# DATA_SIZE[7] = 512 Bytes
# DATA_SIZE[8] = 64 Bytes
# DATA_SIZE[9] = 64 Bytes
# DATA_SIZE[10] = 64 Bytes
# DATA_SIZE[11] = 512 Bytes
# DATA_SIZE[12] = 64 Bytes
# DATA_SIZE[13] = 128 Bytes
# DATA_SIZE[14] = 512 Bytes
# DATA_SIZE[15] = 64 Bytes
# DATA_SIZE[16] = 64 Bytes
# DATA_SIZE[17] = 512 Bytes
# DATA_SIZE[18] = 512 Bytes
# DATA_SIZE[19] = 128 Bytes
# DATA_SIZE[20] = 64 Bytes
# DATA_SIZE[21] = 1024 Bytes
# DATA_SIZE[22] = 512 Bytes
# DATA_SIZE[23] = 512 Bytes
# DATA_SIZE[24] = 64 Bytes
# DATA_SIZE[25] = 64 Bytes
# DATA_SIZE[26] = 64 Bytes
# DATA_SIZE[27] = 64 Bytes
# DATA_SIZE[28] = 64 Bytes
# DATA_SIZE[29] = 64 Bytes
# DATA_SIZE[30] = 64 Bytes
# DATA_SIZE[31] = 64 Bytes
# DATA_SIZE[32] = 64 Bytes
# DATA_SIZE[33] = 64 Bytes
# DATA_SIZE[34] = 128 Bytes
# DATA_SIZE[35] = 64 Bytes
# DATA_SIZE[36] = 64 Bytes
# DATA_SIZE[37] = 64 Bytes
# DATA_SIZE[38] = 512 Bytes
# DATA_SIZE[39] = 512 Bytes
# DATA_SIZE[40] = 64 Bytes
# DATA_SIZE[41] = 128 Bytes
# DATA_SIZE[42] = 512 Bytes
# DATA_SIZE[43] = 64 Bytes
# DATA_SIZE[44] = 64 Bytes
# DATA_SIZE[45] = 64 Bytes
# DATA_SIZE[46] = 512 Bytes
# DATA_SIZE[47] = 64 Bytes
# DATA_SIZE[48] = 64 Bytes
# DATA_SIZE[49] = 64 Bytes
# DATA_SIZE[50] = 64 Bytes
# DATA_SIZE[51] = 64 Bytes
# DATA_SIZE[52] = 64 Bytes
# SPACE_SIZE[0] = 4 Bytes
# SPACE_SIZE[1] = 4 Bytes
# SPACE_SIZE[2] = 4 Bytes
# SPACE_SIZE[3] = 4 Bytes
# SPACE_SIZE[4] = 4 Bytes
# SPACE_SIZE[5] = 4 Bytes
# SPACE_SIZE[6] = 4 Bytes
# SPACE_SIZE[7] = 4 Bytes
# SPACE_SIZE[8] = 4 Bytes
# SPACE_SIZE[9] = 4 Bytes
# SPACE_SIZE[10] = 4 Bytes
# SPACE_SIZE[11] = 4 Bytes
# SPACE_SIZE[12] = 4 Bytes
# SPACE_SIZE[13] = 4 Bytes
# SPACE_SIZE[14] = 4 Bytes
# SPACE_SIZE[15] = 4 Bytes
# SPACE_SIZE[16] = 4 Bytes
# SPACE_SIZE[17] = 4 Bytes
# SPACE_SIZE[18] = 4 Bytes
# SPACE_SIZE[19] = 4 Bytes
# SPACE_SIZE[20] = 4 Bytes
# SPACE_SIZE[21] = 4 Bytes
# SPACE_SIZE[22] = 4 Bytes
# SPACE_SIZE[23] = 4 Bytes
# SPACE_SIZE[24] = 4 Bytes
# SPACE_SIZE[25] = 4 Bytes
# SPACE_SIZE[26] = 4 Bytes
# SPACE_SIZE[27] = 4 Bytes
# SPACE_SIZE[28] = 4 Bytes
# SPACE_SIZE[29] = 4 Bytes
# SPACE_SIZE[30] = 4 Bytes
# SPACE_SIZE[31] = 4 Bytes
# ========== ========== ========== ========== ======== ========== ==========

总线协议约束

数字块通常使用总线协议相互通信,其中一些举例包括AMBA AXI WishBone、OCP等。发送符合访问协议的数据的总线主服务器提供控制信号,告诉从包何时验证,包是读还是写,以及发送了多少字节的数据。主服务器还发送一个地址,后面跟着要存储在该地址的数据。
让我们来看示例,其中测试台充当主服务器,并使用验证数据约束总线数据包类对象。

// 总线协议约束示例

// 类定义
class CLASS_BUS_PROTOCOL ;
	rand int 		M_ADDR;
	rand bit [31:0] M_DATA;
	rand bit [1:0] 	M_BURST;
	rand bit [2:0]  M_LENGTH;

	constraint C_ADDR {
						M_ADDR % 4 ==0;
					  };

	function void display();
		$display("==== ==== ==== ==== Transaction Details ==== ==== ==== ====");
		$display("M_ADDR = 0x%0h",M_ADDR);
		$display("M_DATA = 0x%0h",M_DATA);
		$display("M_BURST = %0d Bytes / xfr",M_BURST+1);
		$display("M_LENGTH = %0d",M_LENGTH+1);
		$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====");
	endfunction
endclass 

module TB_BUS_PROTOCOL ();

	int S_STR_ADDR;
	int S_END_ADDR;

	CLASS_BUS_PROTOCOL OBJ = new();

	initial
	begin
		S_STR_ADDR = 32'd0;
		S_END_ADDR = 1023;

		OBJ.randomize() with {
								M_ADDR >= S_STR_ADDR;
								M_ADDR <  S_END_ADDR;
								M_ADDR + (M_LENGTH+1) * (M_BURST+1) < S_END_ADDR;
							 };
		OBJ.display();
	end
endmodule

仿真结果:

# ==== ==== ==== ==== Transaction Details ==== ==== ==== ====
# M_ADDR = 0x374
# M_DATA = 0xc72f5a8f
# M_BURST = 3 Bytes / xfr
# M_LENGTH = 6
# ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

pre_randomize & post_randomize

在类中声明为随机或随机的变量使用内置的 randomize() 方法进行随机化。如果随机化成功则返回1,如果随机化失败则返回0。可能由于各种原因而失败,如约束冲突、求解器无法得出满足所有约束的值等等。类对象不会自动随机化,因此我们需要人为调用 randomize() 方法来进行随机化。

简单示例:

 在计算随机值之前和之后,randomize()会自动调用两个回调函数。

预随机化

后随机化

覆盖

我们所要做的是用我们自己的定义覆盖现有的空的pre_randomize()和post_randomize()方法。这是一种改变对象的随机化特征的巧妙方法。如果该类是一个派生类,并且这两个方法没有用户定义的实现,那么这两个方法都将自动调用它的超函数。
请注意,pre_randomize()和post_randomize()不是虚拟的,而是作为虚拟方法运行。如果您试图手动使它们成为虚拟的,您可能会遇到一个编译器错误,如下所示。 

  • 如果 randomize() 失败,则不调用 post_randomize()
  • randomize() 方法是内置的,不能被覆盖
  • 如果随机化失败,则这些变量将保留其原始值,并且不被修改

行内约束

考虑到一个类已经有了写得很好的约束,并且需要用用户决定的一组不同的约束来随机化类变量。通过使用 with 结构,用户可以在调用 randomize() 方法处声明行内约束。求解器将把这些附加约束与对象的原始约束一起考虑。

约束冲突时,仿真器会报错:

 另一冲突示例:

 


软约束

常规的约束被称为硬约束,因为求解器必须始终满足它们。如果求解器无法找到解决方案,那么随机化操作将会失败。
然而,声明为 soft 的约束给求解器提供了一些灵活性,如果有其他矛盾的约束,约束需要满足——硬约束或具有更高优先级的软约束。
软约束用于指定随机变量的默认数值和分布。

示例:软约束 , data取值范围 [4:12] 

与行内的硬约束起冲突时:

软/硬 约束区别在哪?

上例去掉 soft 关键字:

 


取消约束

约束条件可以通过 constraint_mode() 使能或者取消;

语法

constraint_mode() 可以用作函数或者任务;

 constraint_mode() 属于内建方法,不可被覆盖。

方法

示例

关闭约束:

 如果使能一个不存在的约束,仿真器会报错:

 


关闭随机化

类内变量的随机化关闭使用 rand_mode() 。关闭后,变量不再具有随机性。

 示例

对比:

 


randcase

有时我们会遇到这样的场景,我们希望求解器从众多语句中随机选择一个。关键字 randcase 引入了一个case语句,随机选择它的一个分支。case项表达式是表示与每个项相关的权重的正整数值。选择项的可能性是通过将该项的权重除以所有权重的总和得出的。

示例

如果分支权重为零,那么该分支将永远不会被选择:

 如果所有的权重都为零,那么在仿真执行时,会有警告:

 每次调用randcase都会检索一个从0到权重总和的随机数,然后按声明顺序选择权重:较小的随机数对应于第一个(顶部)权重语句。



 


http://www.kler.cn/a/1873.html

相关文章:

  • 鸿蒙开发:了解帧动画
  • Dubbo简单总结
  • SQL Server数据库多主模式解决方案
  • Linux服务器端自动挂载存储设备(U盘、移动硬盘)
  • 系统压力测试助手——stress-ng
  • SpringCloud 入门(4)—— 网关
  • CF1770E Koxia and Tree
  • 探索css渐变-实现饼图-加载图-灯柱
  • 【Java】UDP网络编程
  • 蓝桥杯算法全集之完全背包问题(动态规划算法)
  • 蓝桥杯真题——自动售水机
  • LeetCode:704. 二分查找
  • 区块链基本原理
  • 【jvm】JVM(三)JVM 垃圾回收算法详解(CMS、三色标记)
  • 【进阶数据结构】二叉搜索树经典习题讲解
  • Python读写EXCEL文件常用方法大全
  • ChatGPT加强版GPT-4面世,打工人的方式将被颠覆
  • 遗传算法原理及案例解析
  • SAP BPC简介
  • vue大型商城系统中遇到的问题(上)
  • 【链表OJ题(九)】环形链表延伸问题以及相关OJ题
  • 5. QtDesignStudio中的3D场景
  • 硬件原理图设计规范(二)
  • 搞懂vue 的 render 函数, 并使用
  • 关于element-plus按需引入时,在vite中使用自定义主题失效的问题解决
  • 【C++学习】类和对象(中)一招带你彻底了解六大默认成员函数