芯片漫游指南(2)-- UVM结构

1 组件家族

1.1概述

这一章我们将介绍UVM的各个组件,读者可以对照之前SV中的各个组件,来看UVM和SV的组件它们相同的部分和不同的部分。对比的重点可以从各个组件的功能实现以及组件的创建连接入手。为什么UVM需要有sequencer这样的角色存在呢?在SV中,产生机理的组件被称为generator,那么为什么需要sequence和sequencer来共同扮演generator的角色,产生激励并且将激励发送给driver(即stimulator)呢?分别就UVM和SV给出的验证环境,来考虑那些特性是UVM优于SV的部分,也正是这些有事,使得UVM可以在验证环境的水平复用和垂直复用还是那个面占据优势。
在这里插入图片描述

  • sv环境中的验证组件按照功能需要,被创之为激励器(stimulator)、监测器(monitor)和检查器(checker)。
  • 这三个核心组件与验证环境的三个关键特性对应,即激励、监测和检查。在过往那么多验证方法学中,都有与其对应的组件(component)。
  • UVM组件家族是从UVM基类继承的一个核心分支即uvm_component类。
  • 从uvm_component类继承的类均可以构成验证环境,这是一位它们都从uvm_componenet类继承了phase机制,也都会经历各个phase阶段。
  • 在uvm入门的模块中,主要介绍构成环境的常见组件类:
    • uvm_driver
    • uvm_monitor
    • uvm_sequencer
    • uvm_agent
    • uvm_scoreboard
    • uvm_env
    • uvm_test

1.2 uvm_driver

1.2.1 概述
  • 该类会从uvm_sequencer中获取事务(transaction),经过转化进而在接口中对DUT进行时序激励。
  • 任何继承于uvm_driver的类需要注意的是,该类是参数化的类,因此在定义时需要声明参数的类型。首先看uvm_driver类的定义:
	class uvm_driver #(type REQ = uvm_sequence_item, type RSP = REQ) //#代表参数类
	extends uvm_component;
  • 用户在定义新的driver类时,应当声明该类所需要获取的事务参数REQ类型,默认情况下,RSP参数类型同REQ类型保持一致。
  • uvm_driver在uvm_component基础上没有扩展新的函数,而只是扩展了一些用来通信的端口和变量:
uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;
uvm_analysis_port #(RSP) rsp_port;
REQ req;
RSP rsp;
  • driver类与sequencer类之间的通信就是为了获取新的事务对象,这一操作是通过pull的方式实现的:
driver.seq_item_port.connect(sequencer.sq_item_export);
driver.rsp+port.connect(sequecer.rsp_export);
1.2.2 示例
class dut_driver extends uvm_driver #(basic_transaction);//定义,basic_transaction为sequence item 类型
	virtual chip_if vif; //vitula interface
	bit[7:0] addr,data;
	`uvm_component_utils(dut_driver)//注册
	function new(string name,uvm_component parent);// 创建函数
		super.new(name,parent);
	endfunction:new
	extern task run_phase(uvm_phase phase);//在运行过程中自动执行的run_phase,为什么没有endtask,extern的情况类的外部还会声明。
endclass: dut_driver

1.3 uvm_monitor

1.3.1 概述
  • 从名字来看,这个列是为了检测接口数据,而任何需要用户自定义数据监测行为的monitor都应当继承于该类。
  • 虽然uvm_monitor于它的父类相比,并没有增添新的成员和方法,但将新定义的monitor类继承于uvm_monitor类会有助于实现父类uvm_monitor的方法和特性。
  • 通常所执行的功能包括:
    • 观测DUT的interface,并且收集总线信息
    • 永远保持PASSIVE模式,即永远不会驱动DUT
    • 在总线协议或者内部信号协议观察时,可以做一些功能和时序的检查
    • 对于更加复杂的检查要求,它们可以将数据发送至其它验证组件,例如scoreboard、reference model或者coverage collector。
1.3.2 示例
class serial_moitor extends uvm_monitor;//定义
	virtual serial_if.monitor mi;//virtual interface + modport
	`uvm_component_utils(serial_monitor)//注册
	function new(string name,uvm_component parnet);//构建函数
		super.new(name,parent);
	endfunction:new
	function void build_phase(uvm_phase phase);//build_phase在不创建新的实例的时候可以省略
		super.build_phase(phase);//当添加了build_phase,那么super都需要添加上
	endfunction:build_phase
	task run_phase(uvm_phase);//
		...
	endtask: run_phase
endclass: serial_mointor
task run_phase(uvm_phase phase);
		serial_transaction tr;
		tr = new();
		forever begin
			wait(mi.rts);
			@(negedge mi.line);
			#(bit_period/2);
			for(int i = 0; i <= 7; i++) begin
				#(bit_period);
				tr.parity_error ^= mi.line;		
				tr.data[i] = mi.line			//监测数据
			end
			#(bit_period) assert(mi.line == 1'b1) 		//检查协议
			else
				`uvm_warning("MON", "Framing error");
			...
		end
	endtask
endclass 

1.4 uvm_sequencer

1.4.1 概述
  • 从名uvm_sequencer与uvm_component之间还多了两个中间类uvm_sequencer_base类和uvm_sequencer_param_base类,中间类不必了解。
  • sequencer既管理者sequence,同时也将sequence中产生的transcation item传送到driver一侧,可以说是整个激励环节的“路由器”。
  • 而就sequence、sequencer与driver之间的缠绵悱恻的故事,我们将在序列的部分中详细分析。
  • 从名uvm_sequencer就如同一个管道,从这个管道中会产生连续的激励事务,并最终通过TLM端口送至driver一侧。
  • 如果需要的话,uvm_sequencer也可以从uvm_driver那么获取随后的RSP对象来得治数据通信是否正常。
  • uvm_sequencer恐怕是这些组件中技能最超凡的一个成员了,单从它的继承层次就可见一斑。
  • 从uvm_sequencer类的定义来看,它也同driver一样是个参数类,需要在定义sequencer时声明REQ的类型。

在这里插入图片描述

1.4.2 示例
class my_sequencer extends uvm_sequencer #(basic_transaction);//定义
	`uvm_component_utils(uvm_sequencer)//注册
	function new(string name,uvm_component parent);//构建函数
		super.new(name, parent);
	endfunction: new
endclass: my _sequencer
  • 可以看出,uvm_sequencer的注册和构建函数new()与其他的uvm_component在定义时没有什么区别。

1.5 uvm_agent

1.5.1 概述
  • uvm_agent是一个标准的验证环境"单位"。这样的一个标砖单位通常包含一个driver、一个monitor以及一个sequencer。这三个小伙伴经常聚在一起,组成一个小团伙agent。
  • 同时为了复用,有的时候uvm_agent中只需要包含一个monitor,而不需要driver和sequencer,这就需要通过一个变量来进行有条件的例化。
uvm_active_passive_enum is_active = UVM_ACTIVE;
  • is_active是agent的一个成员,缺省值是UVM_ACTIVE,这表示出在active模式的agent需要例化driver、monitor和sequencer;而如果is_active的值是UVM_PASSIVE,这表示agent是passive模式,只可以例化monitor。active模式的agent既有激励功能也有监测功能,passive模式的agent只具有监测功能。
  • active模式对应着DUT的接口暴露给agent且需要激励的场景,而passive模式对应着DUT的接口已经与其他设计连接而只需要监测的场景。
  • 通过is_active变量,agent需要在build_phase()和connect_phase()等函数中通过选择语句来对driver和sequencer进行有条件的例化和连接,下面这段代码是如何对agent内三个组件进行有条件例化的参考。
  • Agent的存在是为了验证环境的复用。
  • 按照总线的传输方向划分,可分为master和slave。
  • Master agent是用来向DUT发起transaction。
  • Slave agent是用来响应DUT的events。
  • 对于初学者,按照“套路”,只需要考虑在agent中创建所列出的常见的组件。
    在这里插入图片描述
1.5.2 示例

(1)

class my_agent extends uvm_agent; //定义
	my_sequencer m_sqr;
	my_driver    m_drv;
	my_monitor   m_mon;//agent中包含的常见组件
	dut_if vif;
	uvm_active_passive_enum is_active = UVM_ACTIVE;//is_active是决定agent内部结构的变量
	...//省略构建函数
	extern function void build_phase(uvm_phase phase);
	extern function void connect_phase(uvm_phase phase);
	`uvm_component_utils(my_agent)//注册
endclass:my_agent

(2)

function void_template_master_agent::build();
	super.build();
	monitor = template_master_monitor::type_id::create("sequencer",this);
	if(is_active == UVM_ACTIVE)begin
		sequencer = template_master_sequencer::type_id::create("sequencer",this);
		driver = template_master_driver::type_id::create("driver",this);
	end
endfunction : build
function void template_master_agent::connect();
	if(is_active ==UVM_ACTIVE)begin
		driver.seq_item_port.connect(sequencer.seq_item_export);//driver和sequencer需要做连接
	end
endfunction : connect

1.6 uvm_scoreboard

1.6.1 概述
  • 从名字来看,uvm_scoreboard担任这同SV中checker一样的功能,即进行数据对比和报告。
  • uvm_scoreboard本身也没有添加额外的成员变量和方法,但UVM建议用户将自定义的scoreboard类继承于uvm_scoreboard类,这便于子类在日后可以自动继承于可能被扩充到uvm_scoreboard类中的成员。
  • 在实际环境中,uvm_scoreboard会接收来自多个monitor的监测数据,继而进行比对和报告。
  • 正由于uvm_scoreboard通用的比较数据特性,UVM自带的其他两个用来做数据比较的类其实很少被使用到:
uvm_in_order_component # (type T)
uvm_algorithm_componentor # (type BEFORE,type AFTER,type TRANSFORMER)
1.6.2 示例
  • 在scoreboard中常会声明TLM端口以供monitor传输数据。
  • 简易比较的方法,可以采用UVM预定义的comparator。
  • 对于复杂的设计,参考SV模块中的做法,可以在scoreboard中分别创建reference model和comparator。
  • 以此为例,也需要注意的是,如果一个组件中有子一级的组件,应该考虑它们的创建、连接和通信的问题。
class cpu_scoreboard extends uvm_scoreboard;
	uvm_analysis_export #(bus_xact) in_export;
	uvm_analysis_export #(bus_xact) out_export;
	typedef uvm_in_order_comparator #(bus_xact) comp_t;
	comp_t m_comp;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		in_export = new("in_export",this);
		out_export = new("out_export",this);
		m_comp = comp_t::type_id::create("m_comp",this);
	endfunction
	function void connect_phase(uvm_phase phase);
		super.connect_phase(phase);
		in_export.connect(m_comp.before_export);
		out_export.connect(m_comp.after_export);
	endfunction
endclass: cpu_scoreboard

1.7 uvm_env

1.7.1 概述
  • 从环境层次结构而言,uvm_env可能包含多个uvm_agent和其他component。
  • 这些不同组件共同构成一个完整的验证环境,而这个环境在将来复用中作为子环境被进一步集成到更高的环境中。
  • 下面的验证结构中,就定义了一个高层的环境,它里面包含sub_env、agent、scoreboard。
  • uvm_env的角色就是一个结构化的容器,它可以容纳其它组件同时它也可以作为子环境在更高层的集成中被嵌入。
    在这里插入图片描述
1.7.2 示例
class top_env extends uvm_env;//定义继承
	sub_env m_se;
	my_agent m_agt;
	my_scoreboard m_sb;
	`uvm_component_utils(top_env)//注册
	extern function new(string name,uvm_component parent);
	function void build_phase(uvm_phase);//函数构建
		m_se = sub_env::type_id::create("m_se",this);
		m_agt = my_agent::type_id::create("m_agt",this);
		m_sb = my_scoreboard::type_id::create("m_sb",this);
	endfunction
	...
endclass: top_env

1.8 uvm_test

1.8.1 概述
  • 从uvm_test类本身没有什么新成员,但是作为测试用例的“代言人”,它不但决定着环境的结构和连接关系,也决定着使用哪一个测试序列。
  • 如果没有这个代言人,整个环境都无从建立,所以uvm_test是验证环境建立的唯一入口,只有通过它才能正常运转UVM的phase机制。
  • 我们从下面的示例看到,在一个顶层test中可以例化多个组件,譬如uvm_env或者uvm_agent,而在仿真时通过uvm_test可以实现验证环境的运转。
  • 我们推荐在uvm_test中只例化一个顶层uvm_env,这便于提供一个唯一环境节点以形成树状的拓扑结构,而这种树状环境结构也会对应着一种树状配置结构。
1.8.2 示例
class env extends uvm_env;
	`uvm_component_utils(env)
	...
endclass

class agent extends uvm_agent;
	`uvm_component_utils(agent)
	...
endclass

class test1 extends uvm_test;
	`uvm_component_utils(test1)
	env e1, e2;
	agent a1;
	...
	function void build_phase(uvm_phase phase);
		e1 = env::type_id::create("e1", this);
		e2 = env::type_id::create("e2", this);
		a1 = agent::type_id::create("a1", this);
	endfunction
endclass

2.UVM结构回顾

2.1 uvm_top

  • uvm_top是uvm_root类的唯一实例,即是UVM世界的“一”。
    • 它由UVM创建和管理
    • 它所在的域是uvm_pkg
  • uvm_top是所有test组件的顶层
    • 所有验证环境中的组件在创建时都需要指明它的父一级。
    • 如果某些组件在创建时指定父一级的参数为“null”,那么它将直接隶属于uvm_top。不过这么做存在风险,也并不推荐。
  • uvm_top提供一系列的方法来控制仿真,例如phase机制、objection仿真仿真退出机制等。
    在这里插入图片描述

2.2 uvm_test

  • "test"类是用户自定义类的顶层结构
  • 所有的test类都应该继承于uvm_test,否则,uvm_top将不识别“后果很严重” – 无法启动test。
  • test的目标包括:
    • 提供不同的配置,包括环境结构配置、测试模式配置等,然后再创建验证环境。
    • 例化测试序列,并且挂载(attach)到目标sequencer,使其命令driver发送激励。
      在这里插入图片描述

2.3 构建环境的主要组件

  • 主要由三类UVM构建模块(基类)共同组成验证环境。
    • uvm_component
      • 继承于uvm_report_object(进一步继承于uvm_object),提供消息方法。
      • 所有的经验环境组件均继承于uvm_component
      • 管理验证环境的层次
    • uvm_env
      • 继承于uvm_component
      • 没有额外的功能
      • 用来为验证环境节后提供一个容器(container)
    • uvm_test
      • 继承于uvm_component
      • 没有额外的功能
      • 用来提供uvm_env的额外配置以及挂载激励

2.4 uvm_component

  • 一个虚类(virtual class),所有环境组件均继承于该类。所有继承于该类的子类,我们称之为组件或者环境组件。
  • 该类提供已下接口或者API:
    • 结构,例如get_full_name(),get_parent(),get_num_children()。
    • 阶段(phase)机制,例如build_phase(), connect_phase(), run_phase()。
    • 配置(configuation)机制,例如print_config(), print_override_info()。
    • 报告(report)机制,例如report_hook(), set_report_verbosity_level_hier()。
    • 事务记录(transaction recording),例如record()。
    • 工厂(factory)机制,例如set_inst_override(), set_type_override()。
  • 由于环境中所有的组件都继承于uvm_component,因此也就可以使得UVM提供统一的方式来管理层次结构和组件方法。

对于组件的构建函数,固定形式为:
function new(string name,uvm_componet parent);

  • string name:用来声明当前例化组件的名称,用来自动和它所在的父一级层次组合为则兼的整个层次的名称,可以get_full_name()方法获取。
  • uvm_component parent: 用来指示所例化的父一级句柄,通常用“this”来替代,即例化在当前的父一级组件中。
  • 注意与uvm_object的构建函数new(string name)进行区分,由于uvm_object并不参与组件的层次构建,因此它只有一个形参,而没有uvm_compoent parent。
  • 凡是继承于uvm_component的组件,也应该保持同样的形式参数列表。

3.MCDF顶层验证方案

3.1 概述

  • 在SV模块中,四位verifier需要给MCDF(Multiple Channel Data Formatter)搭建验证环境,进而利用这些模块验证组件在顶层可以完成集成复用。
  • 伴随着它们对UVM机制和组件家族的掌握,它们也开始将原有SV验证组件移植到UVM组件。MCDF的主要功能是将输入端的三个通道数据,通过数据整形和过滤,最终输出。
  • 我们可以将MCDF的设计结构分为四个模块:
    • 上行数据的通道从端(Channel Slave)
    • 仲裁器(Arbiter)
    • 整形器(Formatter)
    • 控制寄存器(Control Registers)

3.2 reg_env

  • 对于寄存器模块的验证环境reg_env,它的组织包括:
    • reg_master_agent,提供寄存器接口驱动信号。
    • reg_slave_agent,提供寄存器接口反馈信息。
    • scoreboard,分别从reg_master_agent内的mointer和reg_slave_agent内的mointer获取监测数据,并且进行数据对比。
      在这里插入图片描述

3.3 chnl_env

- 数据通道从端的验证环境chnl_env的组件包括:
	- chnl_master_agent,提供上行的激励数据。
	- chnl_slave_agent,提供用来模拟arbiter仲裁信号,并且接受流出数据。
	- reg_cfg_agent, 提供用来模拟寄存器的配置信号,并且接收内置的FIFO余量。
	- scoreboard,分别从chnl_master_agent、chnl_slave_agent和reg_cfg_agent的mointer接收监测数据,并且对channel的流入流出数据进行对比。

在这里插入图片描述

3.4 arb_env

  • 仲裁器的验证环境arb_env的组件包括:
    • 模拟channel输出接口的arbiter_master_agent的三个实例,用来对arbiter提供并行数据输入,同时对arbiter反馈的仲裁信号做出响应。
    • arbiter_slave_agent,用来接收arbiter的输出数据,模拟formatter的行为,对arbiter的输出信号信号做出响应。
    • reg_cfg_agent,提供用来模拟寄存器的配置信号,对三个channel数据源分别做出不同的优先级配置。
    • scoreboard,从三个ariter_master_agent、arbiter_slave_agent和reg_cfg_agent中的moniter获取监测数据,对arbiter的仲裁机制做出预测,并且将输入输出数据按照预测的优先级做出比对。
      在这里插入图片描述

3.5 fmt_env

  • 整形器的验证环境fmt_env的组件包括:
    • fmt_master_agent,用来模拟arbiter的输出数据
    • fmt_slave_agent,用来模拟MCDF的下行数据接收端。
    • reg_cfg_agent,用来模拟寄存器的配置信号,用来指定输出数据包的长度。
    • scoreboard,从fmt_master_agent、fmt_slave_agent和reg_cfg_agent的moniter获取数据检测数据,通过数据包长度来预测输出的数据包,与formatter输出的数据包进行对比。
      在这里插入图片描述

3.6 MCDF顶层验证方案–环境集成方案一

  • MCDF顶层验证环境复用了这些模块验证环境的组件:
    • reg_master_agent
    • chnl_master_agent
    • fmt_slave_agent
  • 通过这三个激励组件可以有效生成新的激励序列,而将各个agent的squencer句柄合并在一处时,virttual sequencer的作用就体现出来了。
  • 我们可以通过这个中心化的序列分发管道,将各个agent的squence也集中管理。
  • MCDF的scoreboard提供了一个完整的数据通路覆盖方案,即从各个agent的monitor数据监测端口将数据收集起来,同时建立MCDF的参考模型,预测输出数据包,最终进行数据比对。
 class mcdf_env1 extends uvm_env;
 	`uvm_component_utils(mcdf_env1)
 	reg_master_agent reg_mst;
 	chnl_master_agent chnl_mst1;
 	chnl_master_agent chnl_mst2;
 	chnl_master_agent chnl_mst3;
 	fmt_slave_agent fmt_slv;
 	mcdf_virtual_sequencer virt_sqr;
 	mcdf_scoreboard sb;
 	...
 	function void build_phase(uvm_phase phase);
 		super.build_phase(phase);
 		reg_mst = reg_master_agent::type_id::create("reg_mst",this);
 		chnl_mst1 = chnl_mstaer_agent::type_id::create("chnl_mst1",this);
 		chnl_mst2 = chnl_master_agent::type_id::create("chnl_mst2",this);
 		chnl_mst3 = chnl_master_agent::type_id::create("chn1_mst3",this);
 		fmt_slv = fmt_slave_agent::type_id::create("fmt_slv",this);
 		virt_sqr = mcdf_virtual_sequencer::type_id::create("virt_sqr",this);
 		sb = mcdf_scoreboard::type_id::create("sb",this);
 	endfunction

	function void connect_phase(uvm_phase phase);
		super.connect_phase(phase);
		//virtual sequencer connect
		virt_sqr.reg_sqr = reg_mst.sequencer;
		virt_sqr.chnl_sqr1 = chnl_mst1.sequencer;
		virt_sqr.chnl_sqr2 = chn1_mst2.sequencer;
		virt_sqr.chn1_sqr3 = chnl_mst3.sequencer;
		virt_sqr.fmt_sqr = fmt_slv.sequencer;
		//moniter transactions to scoreboard
		reg_mst.mointer.ap.connect(sb.reg_export);
		chnl_mst1.mointer.ap.connect(sb.chnl1_export);
		chnl_mst2.mointer.ap.connect(sb.chnl2_export);
		chnl_mst3.mointer.ap.connect(sb.chnl3_export);
		fmt_slv.mointer.ap.connect(sb.fmt_export);
	endfunction
endclass

3.7 MCDF顶层验证方案–环境集成方案二

  • 方案一种最大的额外投入在与需要新建一个scoreboard用来检查MCDF的整体功能。
  • 如果顶层设计没那么复杂,重新实现一个顶层scoreboard器复杂度还可控的;但是如果将来的顶层环境更加复杂,那么复用底层的scoreboard就变得省时省力了。
  • 方案二的目的在于复用底层模块环境的scoreboard,减少顶层环境的往外成本。方案二不同意方案一的有下列几个地方:
    • 顶层环境的组件都直接复用了各个模块验证环境
    • 顶层环境在继承模块验证环境时,需要将各个子模块中的agent配置为不同模式(active或者passive),以此适应顶层场景。
    • 不再需要实现新的scoreboard,而是可以复用原有模块验证环境的socreboard。
      在这里插入图片描述
  • 方案一与方案二相同的地方在于,顶层都需要新建virtula sequencer和virtual sequence,用来生成顶层的测试序列。
  • 而virtual sequence也不是从零创建的,它本省也利用原有模块环境的序列可以,进行了有机的组合,最后协调生成了新的测试序列。
  • 从方案二看出,mcdf_env的子组件不再是uvm_agent类,而是各个模块的验证环境uvm_env类。
  • 通过直接复用这些子环节,我们也间接复用了它们内部的scoreboard在build阶段,我们需要将各个子环境中不需要再产生激励的agent,配置为passive模式,而默认情况下这些agent均为active模式。
  • 这种复用方式使得我们无需再新建一个MCDF scoreboard,只需要确保MCDF的各个子模块均有scoreboard会检查功能,这样从整体上便可以覆盖完整的数据通路。
class mcdf_env1 extends uvm_env;
	`uvm_component_utils(mcdf_env1)
	reg_env reg_e;
	chnl_env chnl_e1;
	chnl_env chnl_e2;
	chnl_env chnl_e3;
	fmt_env fmt_e;
	arb_env arb_e;
	mcdf_virtual_sequencer virt_sqr;
	
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		//将子环境配置为active或者passive模式
		uvm_config_db#(int)::set(this, "reg_e.slave", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "chnl_e1.slave", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "chnl_e1.reg_cfg", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "chnl_e2.slave", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "chnl_e2.reg_cfg", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "chnl_e3.slave", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "chnl_e3.reg_cfg", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "arb_e.master1", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "arb_e.master2", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "arb_e.master3", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "arb_e.slave", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "arb_e.reg_cfg", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "fmt_e.master", "is_active", UVM_PASSIVE);
		uvm_config_db#(int)::set(this, "fmt_e.reg_cfg", "is_active", UVM_PASSIVE);
		
		//创建子环境
		reg_e = reg_env::type_id::create("reg_e", this);
		chnl_e1 = chnl_env::type_id::create("chnl_e1", this);
		chnl_e2 = chnl_env::type_id::create("chnl_e2", this);
		chnl_e3 = chnl_env::type_id::create("chnl_e3", this);
		virt_sqr = mcdf_virtual_sequencer::type_id::create("virt_sqr", this);
	endfunction
	
	function void connect_phase(uvm_phase phase);
		super.connect_phase(phase);
		//virtual sequencer connection
		virt_sqr.reg_sqr = reg_e.master.sequencer;
		virt_sqr.chnl_sqr1 = chnl_e1.master.sequencer;
		virt_sqr.chnl_sqr2 = chnl_e2.master.sequencer;
		virt_sqr.chnl_sqr3 = chnl_e3.master.sequencer;
		virt_sqr.fmt_sqr = fmt_e.slave.sequencer;
	endfunction
endclass

3.8 总结

  • 从上面框图和代码中观察到,UVM带来的环境复用,相比于之前SV验证环境做到了下面的几个优势:
    • 各个模块的验证环境时独立封装的,对外不需要保留数据端口,因此便于环境的进一步集成复用。
    • 由于UVM吱声的phase机制,在顶层协调各个子环境时,无需考虑由于子环境之间的例化顺序而导致的对象句柄引用悬空的问题。
    • 由于子环境的测试序列是相对独立的,这使得顶层在复用子环境测试序列而构成virtual sequence时,不需要其它额外的迁移成本。
    • UVM提供的config_db配置方式,使得整体环境的结构和运行模式都可以从树状的config对象中获取,这也使得顶层环境可以在不停uvm_test进行集中管理配置。

4.构建验证环境的内经

4.1 环境构建的四要素

  • 可以看到在发送测试序列之前,首先需要创建一个结构化的环境。如果我们将环境建立的核心要素拆解开来,它们可以分为四个部分:
    • 单元组件的自闭性
    • 回归创建
    • 通信端口连接
    • 顶层配置

4.2 单元组件自闭性

  • 自闭性值的是单元组件(例如uvm_agent或者uvm_env)自身可以称为独立行为、不依赖于其他并行的组件。
  • 举例来说,driver同sequencer之间,虽然driver需要获取sequencer的transaction item,但是它本身可以独立例化,而它们之间的通信也是基于TLM端对端的连接实现的。
  • 这种单元组件的自闭性为日后的组件复用提供了良好的基础。
  • 各个子环境也可以独立继承于顶层环境,互相也不需要额外的通信连接,各自划分“小世界”施行自治。

4.3 回归创建

  • 环境的框架建立主要就依靠这个技能了。通过这种方式,上一级的组件在例化自身(执行new()函数)之后,会执行各个phase阶段,通过build phase可以进一步创建子组件,而这些子组件也通过一样的过程去创建下一级组件。
  • 回归创建之所以可以实现,这要依赖于自订向下执行顺序的build phase。
  • 通过build phase这种结构化执行顺序可以保证父组件必先于子组件创建,而创建过程还包含这些步骤:
    • 在定义成员变量是赋予默认值,或者在new()函数中赋予初始值
    • 结构配置变量用来决定组间的条件生成,例如uvm_agent依靠is_active变量来判断是否需要例化uvm_sequencer和uvm_driver。
    • 模式配置变量用来决定各个子组件的工作模式。
    • 子组件按照自顶向下、从前到后的顺序依次生成。

4.4 通信端口的连接

  • 在完成了整个环境创建以后,各个组件会通过通信断就的连接进行数据通信,创建的端口通信用途包括:
    • driver的端口连接到sequencer,并且对sequencer采取blocking pull的形式获取transaction item。
    • monitor的端口连接到scoreboard内部的analysis fifo,将监测的数据写入其中。

4.5 顶层配置

  • 正是由于单元组件的自闭性,UVM结构不建议用户通过引用子环境句柄,继而索引更深层次的变量进行顶层配置,因为这样做无疑会增加顶层环境同子环境的黏性,无法做到更好的分离。
  • 所以更好的方式是用过配置化对象,作为绑定于顶层环境的部分传递到子环境,而子环境的各个组件又可以从结构化的配置对象中获取自身的配置参数,从而在build phase、connect phase以及run phase中来决定它们的结构和运行的模式。
  • 顶层配置对象可以在子环境没有例化时就将其配置到将来会创建的子环境当中,无需考虑顶层对象会先于子环境生成,这也为UVM用户提供了安全的配置方式:
    • 无论在那一层使用配置,应该尽量将所有配置都置于子组件创建之前,保证配置已经完成。
    • 配置的作用域应该只关注当前层次及一下,而不涉及更高的层次。
    • 配置的对象结构也应该尽量独立,最好同环境结构一样形成一个树状结构。这样带来的好处在于,独立的配置对象会对应独立的子环境,如果将独立的配置合并为一个树状顶层配置结构,那么顶层配置对象更便于使用和维护。
    • 由于config_db的配置特性使得高层的配置会覆盖低层的配置,这也使得在uvm_test层次做出的配置可以控制整体的结构和模式。
  • 在抽离出这些核心要素以后,可以再来看一个典型的验证环境中的变量、组件和配置是如何存在和相互作用的。
  • 这些互动关系,也可以意义对应到一个实体环境中来。
  • 我们依旧将MCDF的一个模块环境reg_env作为参考,来看一个环境中的生态系统是怎么运行的。
    在这里插入图片描述

4.6 环境元素分类

  • 将uvm_test层作为比uvm_env更高层次的绘制出来,这是因为uvm_test层会有一些配置的部分传递给子环境。包括构成环境的组件uvm_component在内,环境元素可以分为以下部分:
    • 成员变量
      • 一般变量
      • 结构变量
      • 模式变量
    • 子组件
      • 固定组件
      • 条件组件
      • 引用组件
    • 子对象
      • 自生对象
      • 克隆对象
      • 引用对象

4.7 成员变量

  • 一般变量用于对象内部的操作,或者为外部访问提供状态值。
  • 结构变量则用来决定内部子组件是否需要创建和连接,例如顶层的is_active变量即用做该目的
  • 模式变量用来控制组件的行为,例如driver变量经过模式配置,可以在run phase做出不同的激励行为。
  • 对于结构变量和模式变量,它们一般由int或者enum类型定义,用户可以在uvm_test层通过uvm_config_db的配置方法直接设置,也可以通过结构化的配置对象来进行系统设置。
  • 对于复杂验证环境,配置对象的方式会容易操作和维护。

4.8 子组件

  • 环境必须创建的组件称之为固定组件,例如agent中的mointer无论对于active模式或者passive模式,都需要创建;又或者顶层环境中的scoreboard,也需要创建用来比较数据。
  • 条件组件则是通过结构变量的配置来决定是否需要创建,例如sequencer和driver只允许在active模式下创建。
  • 引用组是内部声明一个类型句柄,同时通过自顶向下的句柄传递,使得该句柄可以指向外部的一个对象。
  • 例如在uvm_test一层,首先例化了一个寄存器模型rgm(固定组件),气候将该模型的句柄通过配置传递到reg_env层中的rgm句柄(引用组件)。
  • 利用引用组件的方式,使得环境各个层次在需要的情况下,都可以共享一个组件。

子对象

  • 与子组件细分方式方式类似的是子对象(uvm_object)的细分。
  • 在某一层此种首先会创建一个对象,给对象称之为自生对象。
  • 对象传递过程中,该对象经过克隆从而生成一个成员数值相同的第一线,我们称之为克隆对象。
  • 如果对象经过了端口传递,到达另一个组件,而该组件对其未经过克隆而直接进行操作的话,可称之为引用对象的操作。
  • 例如,在virtual sequence会生成送往reg_master_agent和reg_slave_agent的transaction item,分别是mst_t和slv_t,这些连续发送的mst_t和slv_t通过uvm_sequencer,最终抵达uvm_driver。
  • uvm_driver拿到这些transaction对象之后,如果首先进行克隆,而后利用克隆数据对象进行激励是一种方式;driver也可以不克隆对象而直接对这些对象(引用对象)进行操作。对克隆后的对象操作,改变的数值不影响原先的自生对象属性;而如果在引用对象上进行操作,那么也会修改自生对象的数据。
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马志高

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值