前端学习 10-2 :验证中的SV

发布于:2025-09-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

一些学习备注

目录

push_back​

randomize()

constraint

repeat(3) @(posedge vif.clk);

my_monitor.sv

UVM 端口

UVM_FIFO

objection 机制

$value$plusargs


push_back

push_back是 SystemVerilog ​​队列(queue)​​ 的方法,用于在队列末尾添加元素。

在 UVM 中,它主要用于:动态管理数据集合(如事务队列、配置列表),构建灵活的数据结构(如序列项列表、覆盖率事件队列)

​​(1) 队列声明与操作​​

// 声明队列
int q[$];                    // 整数队列
uvm_transaction tr_q[$];    
// UVM事务队列
my_data_t data_queue[$];     // 自定义数据类型队列

// 添加元素到队列末尾
q.push_back(10);            // 队列变为 {10},把10 添加到q的队尾
q.push_back(20);            // 队列变为 {10, 20}

​​(2) UVM 中的典型应用​

// 在sequence中收集生成的transaction, // 监测接口并捕获事务
class my_sequence extends uvm_sequence;
    uvm_transaction tr_queue[$];
    
    task body();
        for (int i = 0; i < 10; i++) begin
            my_transaction tr;
            tr = my_transaction::type_id::create("tr");//工厂创建对象
            tr.randomize();
            tr_queue.push_back(tr);  // tr值添加到队列tr_queue里
        end
    endtask
endclass

data_q是动态队列​​(bit [7:0] data_q[$]),其大小会随着 push_back自动增长

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;         // 如果 valid=1,退出循环
    end
    
     `uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
  
    while(vif.valid) begin           //只要vif.valid信号为高电平(1),就在每个时钟上升沿将                                                 vif.data存入队列 data_q。
        data_q.push_back(vif.data);         // 将当前数据存入队列
        @(posedge vif.clk);                 // 等待下一个时钟上升沿
   end
   
   //pop dmac
   for(int i = 0; i < 6; i++) begin
        tr.dmac = {tr.mac[39:0],data_q.pop_front()};
    end
   //pop smac
   for(int i = 0; i < 6; i++) begin
      tr.smac = {tr.smac[39:0], data_q.pop_front()};
   end    
   //pop ether_type
   for(int i = 0; i < 2; i++) begin
      tr.ether_type = {tr.ether_type[7:0], data_q.pop_front()};
   end

//pop dmac,循环6次​​(对应MAC地址的6字节长度)。每次从队列 data_q头部弹出一个字节(data_q.pop_front()),并将其拼接到 tr.dmac的低8位

randomize()

​​SystemVerilog 的内置方法​​,由语言标准定义(IEEE 1800),并非 UVM 专属。所有声明了 rand或 randc变量的类都会自动继承此方法。

当类中声明 rand变量时,SystemVerilog 编译器会自动为类添加 randomize()方法。

class my_transaction extends uvm_sequence_item;
    rand bit [31:0] addr;  // 随机变量   
    rand bit [63:0] data;
    bit [7:0]       mode;  // 非随机变量

    constraint valid_addr {
        addr[1:0] == 0;    // 地址对齐约束
    }
endclass

my_transaction tr = new();
if (!tr.randomize()) 
    `uvm_error("RAND", "Randomization failed")

constraint

SystemVerilog 中用于​​限制随机变量取值范围​​的构造块,属于​​约束随机验证(CRV)​​的核心机制。

​​核心作用​​:在随机化(randomize())时,自动生成符合设计规则的合法值。

语法​

​作用​

​示例​

inside

限定值范围

addr inside {[0:255], 1024}

dist

权重分布

data dist {0:=20, [1:3]:=80}

==/!=/>/<

关系运算

addr > 32'h0000_FFFF

class my_transaction;
    rand bit [31:0] addr;  // 随机变量
    rand bit [63:0] data;

    // 约束定义
    constraint valid_range {
        addr inside {[0x1000:0x1FFF]};  // 地址范围限制
        data dist {0 :/ 50, [1:100] :/ 50};  // 数据分布权重
    }
endclass

repeat(3) @(posedge vif.clk);

​​在驱动数据包之前插入3个时钟周期的延迟,repeat(3)​:重复执行后续语句3次,@(posedge vif.clk)​:每次等待 vif.clk的上升沿。组合效果​​:仿真器会暂停当前线程,直到观察到 ​​3个时钟上升沿​​,然后继续执行后续代码。

延迟的目的​​

​​(1) 协议时序对齐​​

​​场景​​:在以太网、AXI 等协议中,发送数据前可能需要空闲周期(IDLE)。

​​作用​​:确保DUT(被测设计)准备好接收数据,避免因信号未稳定导致的冲突。

​​示例​​:

        某些DUT要求数据包之间至少间隔2个时钟周期。

        模拟真实硬件中的信号建立时间(Setup Time)。
 

​(2) 调试与观察​​

​​波形调试​​:在数据包发送前留出空白周期,便于在波形工具(如Verdi)中区分不同数据包。

​​日志同步​​:uvm_info打印日志后,延迟3个周期再驱动数据,避免日志与波形不同步。
 

​​(3) 避免竞争条件​​

​​风险​​:若直接驱动数据,可能与DUT的复位或其他控制信号冲突。

​解决​​:延迟3个周期确保DUT处于稳定状态。

my_monitor.sv

`ifndef MY_MONITOR__SV
`define MY_MONITOR__SV
//收集DUT 端口数据(interface),并转换成 transaction 交给后续组件
class my_monitor extends uvm_monitor;
	virtual my_if vif;
	`uvm_component_utils(my_monitor)
	
	function new(string name="my_monitor",uvm_component parent=null);
		super.new(name,parent);
	
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		
		if(!uvm_config_db#(virtual my_if)::get(this,"","vif",vif))
		`uvm_fatal("my_monitor","virtual interface must be set for vif!!!")
		
	endfunction
	
	extern task main_phase(uvm_phase); // 因为注册在工厂里,所以可以直接引用
	extern task collect_one_pkt(my_transaction tr);
	
	endclss
	
//在循环里新建对象,每次捕获到有效数据时生成一个​​独立的事务对象​​。类似于一张张照片
task my_monitor::main_phase(uvm_phase phase);
	my_transaction tr;
	while(1) begin //monitor需要一直活动,所以 while(1)
		tr = new("tr"); // 创建事务对象
		collect_one_pkt(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; // 如果 valid=1,退出循环
	end
	
	 `uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
	 //只要vif.valid信号为高电平(1),就在每个时钟上升沿将 vif.data存入队列 data_q。
	while(vif.valid) begin
		data_q.push_back(vif.data); // 将当前数据存入队列
		@(posedge vif.clk); // 等待下一个时钟上升沿
   end
   
   //pop dmac, 以太网目的地址
   //循环6次​​,每次从队列 头部弹出一个字节,并将其拼接到 tr.dmac的低8位,bit[7:0] data_q[$];所以每次弹出8位
   for(int i = 0; i < 6; i++) begin
		tr.dmac = {tr.mac[39:0],data_q.pop_front()};
	end
   //pop smac 以太网源地址
   for(int i = 0; i < 6; i++) begin
      tr.smac = {tr.smac[39:0], data_q.pop_front()};
   end	
   //pop ether_type 以太网类型
   for(int i = 0; i < 2; i++) begin
      tr.ether_type = {tr.ether_type[7:0], data_q.pop_front()};
   end
   
   psize = data_q.size()-4;
   tr.pload = new[psize];
   //pop payload  携带数据大小
   for(int i = 0; i < psize; i++) begin
      tr.pload[i] = data_q.pop_front();
   end
   //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

`endif

my_driver.sv

`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV

class my_driver extends uvm_driver;

	virtual my_if vif;
	`uvm_component_utils(my_driver)
	
	function new(string name="my_driver", uvm_component parent=null);
		super.new(name,parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		if(!uvm_config_db#(virtual my_if)::get(this,"","vif",vif))
			`uvm_fatal("my_driver","virtual interface must be set for vif!!")
			
	endfunction
	
	extern task main_phase(uvm_phase phase);
	extern task drive_one_pkt(my_transaction tr);
	
endclass

task my_driver::main_phase(uvm_phase phase);
		my_transaction tr;
		phase.raise_objection(this);
		vif.data <= 8'b0;
		vif.valid <= 1'b0;
		
		while(!vif.rst_n)
			@(posedge vif.clk);
		for(int i = 0; i < 2; i++) begin
			tr=new("tr"); //创建事务
			assert(tr.randomize() with {pload.size == 200;});
			drive_one_pkt(tr);
		end
		
		repeat(5) @(posedge vif.clk);
		phase.drop_objection(this);
		
	endtask
		
task my_driver::drive_one_pkt(my_transaction tr);
	    bit [47:0] tmp_data; 
		bit [7:0] data_q[$];  //8位动态队列
		
		tmp_data = tr.dmac;	//每次放8 位=1个字节,总共6次
		for(int i=0; i<6;i++) begin
			data_q.push_back(tmp_data[7:0]);
			tmp_data = (tmp_data >>8);
		end
		
		tmp_data = tr.smac;	
		for(int i=0; i<6;i++) begin
			data_q.push_back(tmp_data[7:0]);
			tmp_data = (tmp_data >>8);
		end		
		
		tmp_data = tr.ether_type;	
		for(int i=0; i<2;i++) begin
			data_q.push_back(tmp_data[7:0]);
			tmp_data = (tmp_data >>8);
		end		
		//   rand byte      pload[];
		for(int i=0; i<tr.pload.size;i++) begin
			data_q.push_back(tr.pload[i]);
		end		
		
		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					
			
		`uvm_info("my_driver","begin to drive one pkt",UVM_LOW);
		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);
		
endtask

`endif
		
		
			
		

UVM 端口

   uvm_blocking_get_port #(my_transaction)  port;
   uvm_analysis_port #(my_transaction)  ap;

uvm_blocking_get_port、 uvm_analysis_port是两种不同类型的通信端口(port),它们分别用于不同的数据传输场景。它们都是由UVM库提供的标准类,定义在UVM的基础类库中。

uvm_blocking_get_port #(my_transaction) port;​​

  • ​​所属类​​:uvm_blocking_get_port是UVM标准库的一部分,定义在 uvm_tlm命名空间中。

  • ​​头文件​​:通常包含在 uvm_tlm.svh或 uvm_pkg.sv中(UVM会自动导入)。

  • ​​作用​​:用于​​阻塞式​​的​​点对点​​数据传输(TLM,Transaction Level Modeling)。

​​用途​

  • 用于​​从上游组件(如 sequencer或 driver)获取事务(transaction)​​。

  • get()是一个​​阻塞方法​​,如果端口没有数据,它会一直等待,直到数据到达。

  • 适用于​​生产者-消费者模型​​,其中 my_model是消费者,等待上游组件发送数据。

uvm_analysis_port #(my_transaction) ap;​​

​​来源​​

  • ​​所属类​​:uvm_analysis_port也是UVM标准库的一部分,定义在 uvm_tlm命名空间中。

  • ​​头文件​​:同样包含在 uvm_tlm.svh或 uvm_pkg.sv中。

  • ​​作用​​:用于​​非阻塞式​​的​​广播​​数据传输(一对多通信)。

​​用途​​

  • 用于​​向下游组件(如 scoreboardcoverage collector或 monitor)广播事务​​。

  • write()是一个​​非阻塞方法​​,它会立即将数据发送给所有连接的组件。

  • 适用于​​观察者模式​​,多个组件可以订阅同一个数据流

在UVM中,TLM(Transaction Level Modeling)通信主要分为:

  • ​​阻塞式通信​​(Blocking):uvm_blocking_get_port,uvm_blocking_put_port,uvm_blocking_transport_port
  • 非阻塞式通信​​(Non-blocking):uvm_nonblocking_get_portuvm_nonblocking_put_port
  • ​​分析端口​​(Analysis):uvm_analysis_port(广播),uvm_analysis_export(接收端)

UVM_FIFO

uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
 i_agt.ap.connect(agt_mdl_fifo.analysis_export);
   mdl.port.connect(agt_mdl_fifo.blocking_get_export);

分析:

uvm_tlm_analysis_fifo是一个 ​​UVM TLM FIFO​​,标准库中自定义的类它​​继承自 uvm_tlm_fifo​​,主要解决 analysis_port和 blocking_get_port的兼容性问题。主要特点是​​内部维护一个队列​​,用于缓存事务(my_transaction)。

​​提供两种接口​​:

  • analysis_export:用于接收来自 analysis_port的数据(write()方法)。

  • blocking_get_export:用于提供数据给 blocking_get_portget()方法)。

uvm_tlm_analysis_fifo的核心功能​

功能

说明

write()方法​

通过 analysis_export接收非阻塞广播(来自 analysis_port)。

get()方法​

通过 blocking_get_export提供阻塞式拉取(供 blocking_get_port使用)。

​内部队列​

缓存事务,解决生产者和消费者的速率不匹配问题。

​线程安全​

UVM 自动处理多线程竞争(如 monitor 和 model 运行在不同线程)。

objection 机制

​​objection机制​​ 是用于控制 UVM 仿真阶段(phase)执行流程的核心机制,其主要作用是 ​​防止仿真阶段提前终止​​,确保测试逻辑完整执行。
UVM 的 ​​动态运行阶段​​(即task phase,比如 reset_phasemain_phaseshutdown_phase)都需要 objection控制。最常用的是 sequence、scoreboard。
而 function 类的phase,不需要。

例外:静态阶段​​是自动执行的,无需 objection控制。
function void build_phase(uvm_phase phase);
    super.build_phase(phase);  // 无需 objection
    agent = my_agent::type_id::create("agent", this);
endfunction

$value$plusargs

SystemVerilog 内置函数,用于从命令行解析参数。

从仿真命令行中读取参数 i2c_dma_sel的值,并将其赋给变量 i2c_dma_sel

if(!$value$plusargs("i2c_dma_sel=%d", i2c_dma_sel))  
    i2c_dma_sel = 0;  // 默认值

仿真命令行 示例: 
simv +i2c_dma_sel=2 +UVM_TESTNAME=i2c_dma_test


网站公告

今日签到

点亮在社区的每一天
去签到