UVM transaction/env/monitor/agent/reference/scoreboard

验证平台大致包括如下几个部分:
driver:实现激励。
scoreboard:用于将DUT与参考模型进行比较。
monitor:用于收集DUT的输出并传递给scoreboard

在这里插入图片描述

加入transaction

以上所有的操作都是基于信号级的,接下来是基于transaction的信息的传递。

// my_transaction.sv
// 定义transaction类
class my_transaction extends uvm_sequence_item;  // my_transaction的基类是uvm_sequence_item

	rand bit[47:0] dmac;  // 48bit的以太网目的地址
	rand bit[47:0] smac;  // 48bit的以太网源地址
	rand bit[15:0] ether_type;  // ether_type是以太网类型
	rand byte pload[];    // 动态数组pload
	rand bit[31:0] crc;   // 前面所有数据的校验值
	
	// 在constraint中对随机的数据增加限制条件,动态数组pload大小被限制在46-1500bit
	constraint pload_cons{   
		pload.size >= 46;
		pload.size <= 1500;
	}

	function bit[31:0] calc_crc(); // 该函数返回32位bit
		return 32'h0;
	endfunction

	function void post_randomize();  // post_randomize函数无返回
		crc = calc_crc;
	endfunction

	`uvm_object_utils(my_transaction)  // 这里没有使用uvm_component_utils宏来实现factory机制,而是使用了uvm_object_utils

	function new(string name = "my_transaction");
		super.new(name);
	endfunction
	
	// 当收集完一个transaction后,通过my_print函数将其打印出来。
	function void my_print();
		$display("dmac = %0h", dmac);
		$display("smac = %0h", smac);
		$display("ether_type = %0h", ether_type);
		for(int i = 0; i < pload.size; i++) begin
			$display("pload[%0d] = %0h", i, pload[i]);
		end
		$display("pload[%0d] = %0h", i, pload[i]);
	endfunction

endclass

在my_driver中实现基于transaction的驱动

// my_driver.sv

// 在main_phase中,先使用randomize将tr随机化
task my_driver::main_phase(uvm_phase phase);
	my_transaction tr;
	for(int i = 0; i < 2; i++) begin
		tr = new("tr");
		assert(tr.randomize() with {pload.size == 200;});
		drive_one_pkt(tr);
	end
endtask

// 通过drive_one_pkt将tr的内容驱动到DUT的端口
task my_driver::drive_one_pkt(my_transaction tr);
	bit [47:0] tmp_data;  // 暂存my_transaction数据
	bit [7:0] data_q[$];  // 队列data_q
	
	// ------将tr中所有的数据压入队列data_q中-------
	//push 目的地址dmac to data_q
	tmp_data = tr.dmac;   // tr.dmac只有8bit,放在tmp_data低位
	for(int i = 0; i < 6; i++) begin
		data_q.push_back(tmp_data[7:0]);
		tmp_data = (tmp_data >> 8); // tmp_data低位放tr.dmac的推出tmp_data
	end

	//push 源地址smac to data_q
		... ...
	//push 以太网种类ether_type to data_q
		... ...
	//push payload to data_q
		... ...

	//push crc to data_q , CRC是前面所有数据的校验值。
	tmp_data = tr.crc;
	for(int i = 0; i < 4; i++) begin
		data_q.push_back(tmp_data[7:0]);
		tmp_data = (tmp_data >> 8);
	end
	
	// ------再将data_q中所有的数据弹出并驱动-------
	`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW); // `uvm_info打印(打印信息的归类,需要打印的信息,冗余级别)
	repeat(3) @(posedge vif.clk);  // 延迟3个时钟周期

	while(data_q.size() > 0) begin
		@(posedge vif.clk);
		vif.valid <= 1'b1;  // 开始发送数据
		vif.data <= data_q.pop_front();
	end

	@(posedge vif.clk);
	vif.valid <= 1'b0;  // 停止发送数据
	`uvm_info("my_driver", "end drive one pkt", UVM_LOW);  // `uvm_info打印

endtask


加入env

是引入一个容器类,在这个容器类中实例化driver、monitor、reference model和scoreboard等。在调用run_test时,传递的参数不再my_driver,而是这个容器类,即让UVM自动创建这个容器类的实例。在UVM中,这个容器类称为uvm_env。

在这里插入图片描述
当加入了my_env后,整个验证平台中存在两个build_phase,一个是my_env的,一个是my_driver的。中,build_phase的执行遵照从树根到树叶的顺序,即先执行my_env的build_phase,再执行my_driver的build_phase。

// my_env.sv
// my_driver在uvm_env中实例化,所以my_driver的父结点(parent)就是my_env。

class my_env extends uvm_env;  // env派生自uvm_env

	my_driver drv;  // 声名变量

	function new(string name = "my_env", uvm_component parent); // 树结构声明
		super.new(name, parent);
	endfunction

	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		drv = my_driver::type_id::create("drv", this); //实例化drv 只有使用factory机制注册过的类才能使用这种方式实例化 type_name::type_id::create(名字drv,this指针)
	endfunction

	`uvm_component_utils(my_env)  // 使用uvm_component_utils宏来实现factory的注册  
	// 在整个仿真中是一直存在的,所以它是一个component,要使用uvm_component_utils宏注册;

endclass

my_driver 在验证平台中的层次结构发生了变化,它一跃从树根变成了树叶,所以在 top_tb 中使用 config_db 机制传递 virtual my_if 时,要改变相应的路径。

//top_tb.sv
module top_tb;
	... ...
	
	initial begin
		//run_test("my_driver");
		run_test("my_env");  // run_test树根
	end

	initial begin
		//uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);  // uvm_test_top是UVM自动创建的树根的名字
		uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.drv", "vif", inp ut_if);  // drv则是在my_env的build_phase中实例化drv时传递过去的名字
	end
	
	... ...
endmodule 

加入monitor

加入monitor监测DUT的输入输出信号变化,判定DUT的行为是否正确。
driver负责把transaction级别的数据转变成DUT的端口级别,并驱动给DUT。monitor用于收集DUT的端口数据,并将其转换成transaction交给后续的组件如reference model、scoreboard等处理。

在这里插入图片描述
uvm_config_db的用途大概有如下三种:
传递virtual interface到环境中。
设置单一变量值,如int.string.enum等。
传递配置对象(config object)到环境中

可以把config_db当成一种邮箱功能,既有寄信方,又有收信方。送到哪的信,则必须去哪里取,所以收发信件的格式如下所示。
寄信方的邮件格式:
uvm_config_db#(邮件类别)::set(发信地址,“相对于发信地址的收件地址”,“邮件名字”,邮件内容);
收信方的邮件格式:
uvm_config_db#(邮件类别)::get(A,“B”,“邮件名字”,邮件内容);
其中AB合起来为收件地址

基于uvm的testbench,由许多uvm_component组成。为了让这些component能够同步的run在某个phase上,uvm设计了uvm_phase,由uvm_test_top依次往下逐级传递。
这样,只要某个component仍然run在某个phase上,也就是有“举手”示意我还没有结束,那么就不会跳到下一个phase

phase 阶段

// 工厂机制注册类通常需要三步:声明,注册,调用new()函数
eg:
class my_monitor extends uvm_monitor;   // 声明
	`uvm_component_utils(my_monitor)    // 注册
	function new                        // 调用 new() 函数
	...
	endfunction
// my_monitor.sv
class my_monitor extends uvm_monitor;  // monitor类派生自uvm_monitor

	virtual my_if vif;  // my_monitor中也需要有一个virtual my_if;

	`uvm_component_utils(my_monitor)  // uvm_monitor在整个仿真中是一直存在的,所以它是一个component,要使用uvm_component_utils宏注册;
	
	function new(string name = "my_monitor", uvm_component parent = null);  // 树结构声明
		super.new(name, parent);  
	endfunction

	virtual function void build_phase(uvm_phase phase); // uvm中将phase机制相关的功能独立封装在uvm_phase类中
		super.build_phase(phase); // build_phase自动获取通过config_db::set/get设置的参数。
		
		if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))  // 检查 virtual interface传递到环境中。
		//uvm_config_db#(邮件类别)::get(A,“B”,“邮件名字”,邮件内容);
			`uvm_fatal("my_monitor", "virtual interface must be set for vif!!!")
	endfunction
	
	// extern表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
	extern task main_phase(uvm_phase phase); 
	extern task collect_one_pkt(my_transaction tr);

endclass


task my_monitor::main_phase(uvm_phase phase);

	my_transaction tr;  // 声明变量(句柄)
	while(1) begin  // monitor需要时刻收集数据,永不停歇,所以在main_phase中使用while(1)循环来实现
		tr = new("tr");  
		collect_one_pkt(tr);  // 调用函数 collect_one_pkt(my_transaction tr);
	end

endtask


task my_monitor::collect_one_pkt(my_transaction tr);

	bit[7:0] data_q[$];  // 队列
	int psize;
	while(1) begin  
		@(posedge vif.clk);
		if(vif.valid) break;
	end

	`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
	while(vif.valid) begin
		data_q.push_back(vif.data);
		@(posedge vif.clk);
	end

	//pop dmac
	for(int i = 0; i < 6; i++) begin
		tr.dmac = {tr.dmac[39:0], data_q.pop_front()};
	end

	//pop smac
	//... ...
	//pop ether_type
	//... ...
	//pop payload
	//... ...

	//pop crc
	for(int i = 0; i < 4; i++) begin
		tr.crc = {tr.crc[23:0], data_q.pop_front()};
	end
	`uvm_info("my_monitor", "end collect one pkt, print it:", UVM_LOW);
	tr.my_print();
	
endtask

当完成monitor的定义后,可以在env中对其进行实例化:

// my_env.sv
// my_driver在uvm_env中实例化,所以my_driver的父结点(parent)就是my_env。
// my_monitor i_mon/o_mon在uvm_env中实例化,父结点(parent)就是my_env。

class my_env extends uvm_env;  // env派生自uvm_env

	`uvm_component_utils(my_env)  // ,使用uvm_component_utils宏来实现factory的注册。

	my_driver drv;  // 声名变量
	my_monitor i_mon;  // 监测DUT的输入口
	my_monitor o_mon;   // 于监测DUT的输出口

	function new(string name = "my_env", uvm_component parent);  // 树结构声明
		super.new(name, parent);
	endfunction

	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		drv = my_driver::type_id::create("drv", this); //实例化drv 只有使用factory机制注册过的类才能使用这种方式实例化 type_name::type_id::create(名字drv,this指针)
		i_mon = my_monitor::type_id::create("i_mon", this);  // 实例化monitor
		o_mon = my_monitor::type_id::create("o_mon", this);
	endfunction

endclass

在这里插入图片描述
在 env 中实例化 monitor 后,要在 top_tb 中使用 config_db 将 input_if 和 output_if 传递给两个 monitor:

initial begin
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.drv", "vif", inp ut_if);  // drv则是在my_env的build_phase中实例化drv时传递过去的名字
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_mon", "vif", input_if);  // 监测dut输入
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.o_mon", "vif", output_if);  // 监测dut输出
end

config_db邮箱功能,
寄信方的邮件格式:
uvm_config_db#(邮件类别)::set(发信地址,“相对于发信地址的收件地址”,“邮件名字”,邮件内容);
收信方的邮件格式:
uvm_config_db#(邮件类别)::get(A,“B”,“邮件名字”,邮件内容);
其中AB合起来为收件地址


封装成agent

driver和monitor两者之间的代码高度相似。其本质是因为二者
处理的是同一种协议,在同样一套既定的规则下做着不同的事情。由于二者的这种相似性,UVM中通常将二者封装在一起,成为一个agent。因此,不同的agent就代表了不同的协议。

// my_agent.sv
class my_agent extends uvm_agent ;  //声明

	`uvm_component_utils(my_agent)  //注册

	my_driver drv;  // 准备在agent中实例化driver和monitor
	my_monitor mon;

	function new(string name, uvm_component parent);  //new函数
		super.new(name, parent);
	endfunction

	// extern表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual function void connect_phase(uvm_phase phase);

endclass 	

function void my_agent::build_phase(uvm_phase phase);
	super.build_phase(phase);

	// 根据is_active变量的值来决定是否创建driver的实例
	if (is_active == UVM_ACTIVE) begin
		drv = my_driver::type_id::create("drv", this);
	end 
	mon = my_monitor::type_id::create("mon", this);
endfunction

function void my_agent::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
endfunction

在把driver和monitor封装成agent后,由agent实例化driver和monitor。在env中需要实例化agent,env不再需要直接实例化driver和monitor。

// my_env.sv
class my_env extends uvm_env;  // env派生自uvm_env
	`uvm_component_utils(my_env)  // ,使用uvm_component_utils宏来实现factory的注册。

	//my_driver drv;  // 声名变量
	//my_monitor i_mon;  // 监测DUT的输入口
	//my_monitor o_mon;   // 于监测DUT的输出口

	my_agent i_agt;  // 声名my_agent类型变量 输入 (my_driver,my_monitor)
	my_agent o_agt;  // 输出 (my_driver,my_monitor)


	function new(string name = "my_env", uvm_component parent);
		super.new(name, parent);
	endfunction
	
	//在my_env的build_phase中对它们进行实例化
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		//drv = my_driver::type_id::create("drv", this); //实例化drv 只有使用factory机制注册过的类才能使用这种方式实例化 type_name::type_id::create(名字drv,this指针)
		//i_mon = my_monitor::type_id::create("i_mon", this);  // 实例化monitor
		//o_mon = my_monitor::type_id::create("o_mon", this);
		
		i_agt = my_agent::type_id::create("i_agt", this);  //实例化
		o_agt = my_agent::type_id::create("o_agt", this);
		// 指定各自的工作模式是active模式还是passive模式。
		i_agt.is_active = UVM_ACTIVE;
		o_agt.is_active = UVM_PASSIVE; //my_agent来自uvm_agent,uvm_agent有is_active变量,该变量值有UVM_PASSIVE和UVM_ACTIVE。

	endfunction

endclass

在这里插入图片描述
由于agent的加入,driver和monitor的层次结构改变了,在top_tb中使用config_db设置virtual my_if时要注意改变路径

// top_tb.sv
... ...
	initial begin
		//uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);  // uvm_test_top是UVM自动创建的树根的名字
		//uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.drv", "vif", inp ut_if);  // drv则是在my_env的build_phase中实例化drv时传递过去的名字
		//uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_mon", "vif", input_if);
		//uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.o_mon", "vif", output_if);
		uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_agt.drv", "vif", inp ut_if);  // drv则是在my_env的build_phase中实例化drv时传递过去的名字
		uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_agt.i_mon", "vif", input_if);
		uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.o_agt.o_mon", "vif", output_if);
	end
... ...

加入reference model

reference model的输出被scoreboard接收,用于和DUT的输出相比较。
在my_monitor和my_model中定义并实现了各自的端口

// my_model.sv
// reference model

class my_model extends uvm_component;
	`uvm_component_utils(my_model)

	uvm_blocking_get_port #(my_transaction) port;  // 声明port 数据接收
	uvm_analysis_port #(my_transaction) ap;  // 数据的发送  uvm_analysis_port是一个参数化的类

	extern function new(string name, uvm_component parent);
	extern function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
endclass

function my_model::new(string name, uvm_component parent);
	super.new(name, parent);
endfunction

function void my_model::build_phase(uvm_phase phase);
	super.build_phase(phase);
	port = new("port", this);  // 实例化端口port
	ap = new("ap", this);
endfunction

task my_model::main_phase(uvm_phase phase);
	my_transaction tr;
	my_transaction new_tr;
	super.main_phase(phase);
	while(1) begin
		port.get(tr);  // 在main_phase中,通过port.get任务来得到从i_agt的monitor中发出的transaction。
		new_tr = new("new_tr");
		new_tr.my_copy(tr);
		`uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)
		new_tr.my_print();
		ap.write(new_tr);
	end
endtask
// my_transaction.sv
	... ...
	
	// my_copy函数  实现了两个my_transaction的复制
	function void my_copy(my_transaction tr);
		if(tr == null)
			`uvm_fatal("my_transaction", "tr is null!!!!")
		dmac = tr.dmac;
		smac = tr.smac;
		ether_type = tr.ether_type;
		pload = new[tr.pload.size()];
		for(int i = 0; i < pload.size(); i++) begin
			pload[i] = tr.pload[i];
		end
		crc = tr.crc;
	endfunction
... ...

完成my_model的定义后,需要将其在my_env中实例化。其实例化方式与agent、driver相似。

在这里插入图片描述
my_model是从i_agt中得到my_transaction,并把my_transaction传递给my_scoreboard。

在my_monitor和my_model中定义并实现了各自的端口,还需要在my_env中使用fifo将两个端口联系在一起。

connect_phase也是UVM内建的一个phase,它在build_phase执行完成之后马上执行。但是与build_phase不同的是,它的执行顺序并不是从树根到树叶,而是从树叶到树根——先执行driver和monitor的connect_phase,再执行agent的connect_phase,最后执行env的connect_phase

// my_env.sv
... ...
	// 假如当write函数调用时,blocking_get_port正在忙于其他事情,而没有准备好接收新的数据时,此时被write函数写入的my_transaction就需要一个暂存的位置,这就是fifo。
	uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;  // 声明一个fifo
	... ...
	
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		agt_mdl_fifo = new("agt_mdl_fifo", this);  // 实例化agt_mdl_fifo
	... ...
	
	// 在connect_phase中将fifo分别与my_monitor中的analysis_port和my_model中的blocking_get_port相连
	function void my_env::connect_phase(uvm_phase phase);
		super.connect_phase(phase);
		i_agt.ap.connect(agt_mdl_fifo.analysis_export);  // 发送数据
		mdl.port.connect(agt_mdl_fifo.blocking_get_export); // 接收数据
	endfunction
//my_agent.sv 
// 数据传送端口
class my_agent extends uvm_agent ;  
	... ...
		uvm_analysis_port #(my_transaction) ap;  // 数据的发送 声明ap
	// 不需要对my_agent中的ap进行实例化,而只需要在my_agent的connect_phase中将monitor的值赋给它,换句话说,这相当于是一个指向my_monitor的ap的指针:
endclass 
	... ...
	
function void my_agent::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	ap = mon.ap;  // 将monitor的值赋给它,相当于是一个指向my_monitor的ap的指针
endfunction	

my_agent的connect_phase的执行顺序早于my_env的connect_phase的执行顺序,从而可以保证执行到i_agt.ap.connect语句时,i_agt.ap不是一个空指针。


加入scoreboard

my_scoreboard要比较的数据一是来源于reference model,二是来源于o_agt的monitor。前者通过exp_port获取,而后者通过act_port获取

// my_scoreboard.sv
// my_scoreboard要比较的数据一是来源于reference model,二是来源于o_agt的monitor。前者通过exp_port获取,而后者通过act_port获取

class my_scoreboard extends uvm_scoreboard;  // 声明
	`uvm_component_utils(my_scoreboard)  // 注册

	my_transaction expect_queue[$];  // 队列
	uvm_blocking_get_port #(my_transaction) exp_port;  // 声明数据接收端口(数据来源于reference model),连接reference model的ap
	uvm_blocking_get_port #(my_transaction) act_port;  // 声明数据接收端口(数据来源于o_agt的monitor),连接o_agt的ap

	extern function new(string name, uvm_component parent = null);
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
endclass

function my_scoreboard::new(string name, uvm_component parent = null);  // new函数
	super.new(name, parent);
endfunction

function void my_scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	exp_port = new("exp_port", this);  // 数据接收端口 实例化
	act_port = new("act_port", this);
endfunction

task my_scoreboard::main_phase(uvm_phase phase); // 在main_phase中通过fork建立起了两个进程,
	my_transaction get_expect, get_actual, tmp_tran;
	bit result;

	super.main_phase(phase);
	fork
		while (1) begin   // 一个进程处理exp_port的数据,来源于reference model,当收到数据后,把数据放入expect_queue中;
			exp_port.get(get_expect);
			expect_queue.push_back(get_expect);
		end

		while (1) begin   // 另外一个进程处理act_port的数据,这是DUT的输出数据,来源于o_agt的monitor
			act_port.get(get_actual);
			if(expect_queue.size() > 0) begin
				tmp_tran = expect_queue.pop_front();
				result = get_actual.my_compare(tmp_tran);  // 当收集到这些数据后,从expect_queue中弹出之前从exp_port收到的数据,并调用my_transaction的my_compare函数
				if(result) begin
					`uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW);
				end
				else begin
					`uvm_error("my_scoreboard", "Compare FAILED");
					$display("the expect pkt is");
					tmp_tran.my_print();
					$display("the actual pkt is");
					get_actual.my_print();
				end
			end
			else begin
				`uvm_error("my_scoreboard", "Received from DUT, while Expect Que ue is empty");
				$display("the unexpected pkt is");
				get_actual.my_print();
			end
		end
	join

endtask

my_compare函数

// my_transaction.sv

class my_transaction extends uvm_sequence_item;
	... ...
	function bit my_compare(my_transaction tr);
		bit result;
	
		if(tr == null)
			`uvm_fatal("my_transaction", "tr is null!!!!")
		result = ((dmac == tr.dmac) &&
					(smac == tr.smac) &&
					(ether_type == tr.ether_type) &&
					(crc == tr.crc));
		if(pload.size() != tr.pload.size())
			result = 0;
		else
			for(int i = 0; i < pload.size(); i++) begin
				if(pload[i] != tr.pload[i])
					result = 0;
			end
		return result;
	endfunction
	
endclass

完成my_scoreboard的定义后,也需要在my_env中将其实例化。此时,整棵UVM树变为如图2-8所示的形式

在这里插入图片描述


  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UVM中,可以使用uvm_config_db的set和get方法来设置和获取配置信息。这些方法可以用于在不同的组件之间传递数据。引用\[1\]中的代码示例展示了如何使用uvm_config_db的set方法来设置一个接口数组。在这个例子中,使用了一个for循环来遍历接口数组,并将每个接口设置到配置数据库中。引用\[2\]中的代码示例展示了set方法的另一种用法,其中第一个参数是一个环境对象,第二个参数是一个字符串,用于指定配置的位置,第三个参数是配置的名称,第四个参数是配置的值。引用\[3\]中提到,在传递虚拟接口时,可以将第一个参数设置为null,UVM会自动将其替换为uvm_root::get(),即替换为uvm_top。uvm_top是uvm_root的一个唯一实例,是UVM的树根,也是一个全局变量,可以直接使用。 #### 引用[.reference_title] - *1* [UVM中interface数组的set,get](https://blog.csdn.net/zhajio/article/details/101195556)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [uvm_config_db的set()与get()方法](https://blog.csdn.net/weixin_42294124/article/details/125016224)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值