【UVM的数据流】分为一个agent和多个agent的情况

(一)一个agent的情况

1:从uvm_sequence_item开始定义数据的最小单位:transaction(事务)

例如:

class apb_sequence_item extends uvm_sequence_item;  //从uvm_sequence_item继承出来的类用于定义事务。
    'uvm_object_utils(apb_sequence_item) //使用object注册
     
     //在transaction中一般定义一些接口信号的随机变量,并添加一些约束,还有一些验证平台的控制信号
     logic rand ['APB_ADDR_WIDTH-1 : 0]    addr;  //接口信号
     logic rand ['APB_DATA_WIDTH-1 : 0]    data;  //接口信号
     logic rand                            we;    //接口信号
     logic rand                            delay; //控制信号,控制发包间隔
     logic rand                            err;   //若随机的地址无效,则返回error

     //约束
     constraint addr_aligment { addr[1:0]==0;} 数据以32bit位并行传输,地址以0-4-8-c顺序变化,低两位为2‘b00
     constraint delay_bonds {delay inside {[1:20]};}

    extern function new(string name="apb_sequence_item");  
endclass

function apb_sequence_item::new(string name="apb_sequence_item");
    super.new(name);
endclass

2.编写sequence,发送req

在apb_sequence_item定义好之后,数据传输就会以apb_sequence_item为单位进行数据传输,在传输之前还要对这些数据进行一个封装,也就是生成各种各样的序列,这部分功能是由sequence完成的,这些序列都有一定的功能,比如apb_write_seq完成读的功能,apb_read_seq完成写的功能,具体哪些序列会被驱动到dut接口上是由test来决定的。比如下面这个具有写数据到dut的一个序列。

calss apb_write_seq extends uvm_sequence #(apb_sequence_item);
    'uvm_object_utils(apb_write_seq)
    
    //主要有两个方法,一个是new,另一个是body,其中body为任务,主要是为了完成发送事务的功能
  
function new(string name="aob_write_seq");
    super.new(name);
endfunction

virtual task body();
    apb_sequence_item req;
    
 //发送sequence的方法有两种,一种是采用宏:'uvm_do(req),使用宏可以代替一下四个步骤,但是不能自己定义其他的限制。且使用'umv_do只是发送sequence,真正的启动是在test中。另一种是四步法,如下:
      
    req=apb_sequence_item::type_id::creat("req")   //1.例化
        start_item(req);                           //2.获取sequencer的授权(test中的启动),开始生产数据
        if(!req.randomize)begin                    //3.随机化sequence
          'uvm_info("body","req randomize failed")
        end
        //一般随机化之后会对某些信号进行处理以获得我们想要的功能,比如使这个序列产生写的功能,那么就需要将写使能置为1
        req.we=1;  //使用点操作来访问序列信号,并对其赋值。
        finish_item(req);                          //4.将sequence发送到sequencer
endtask

endclass

3.定义sequencer、连接sqr与driver

一个sequence在向sequencer发送transaction前,要先向sequencer发送一个请求,sequencer把这个请求放在一个仲裁队列中。作为sequencer,它需做两件事情:第一,检测仲裁队列里是否有某个sequence发送transaction的请求;第二,检测driver是否申请transaction。

只有一个agent时,sequencer一般在agent类之前使用typedef定义一下,然后在agent的build中例化、agt的connect phase中连接sqr与drv。

typedef uvm_sequencer #(apb_sequence_item) my_sequencer;

//在agt中声明并例化sqr
class i_agt extends uvm_agent;
    my_driver    drv;
    my_monitor   mon;
    my_sequencer sqr; //声明aqr
    ...
function void build_phase(uvm_phase phase) //例化seqr、mon、drv
    super.build_phase(phase);
    if(is_active == UVM_ACTIVE) begin
        drv= my_driver::type_id::create("drv",this);
        sqr= my_sequencer::type_id::create("sqr",this);
    end
    mon = my_monitor::type_id::create("mon",this)
endfunction  

function void connect_phase(uvm_phase phase)//连接
    super.connect_phase(phase);
    if(is_active == UVM_ACTIVE) begin
       drv.seq_item_port.connect(sqr.seq_item_export);
    end
endfunction
endclass

4.driver接受req

在uvm_driver中有成员变量seq_item_port,而在uvm_sequencer中有成员变量seq_item_export,这两者之间可以建立一个“通道”,通道中传递的transaction类型就是定义my_sequencer和my_driver时指定的transaction类型,这里是apb_dequence_item类型。

class driver extends uvm_driver #(apb_sequence_item);
   'uvm_component_utils(driver)
    extern function new(string name="driver",uvm_phase phase=null);
    extern tase main_phase();
    extern task drive_one_packet(apb_sequence_item req);
endclass

function driver::new(string name="driver",uvm_conponent parent=null);
    super.new(name,parent);
endfunction

task driver::main_phase();
    apb_sequence_item req;
    seq_item_port.get_next_item(req); //uvm_driver的内部接口:seq_item_port
    drive_one_packet(req);
    seq_item_port.item_done();  //反馈信号,下次调用get_next_item()之前,item_done被调用才可以。
  
endtask


当driver使用get_next_item得到一个transaction时,sequencer自己也保留一份刚刚发送出的transaction。当出现sequencer发出了transaction,而driver并没有得到的情况时,sequencer会把保留的这份transaction再发送出去。那么sequencer如何知道driver是否已经成功得到transaction呢?如果在下次调用get_next_item前,item_done被调用,那么sequencer就认为driver已经得到了这个transaction,将会把这个transaction删除。换言之,这其实是一种为了增加可靠性而使用的握手机制。

5.启动sequence

sequence不在uvm的层次结构中,需要挂载在sequencer上,通过sequencer来启动。

sequence的启动通常在这么几个地方做:(建议统一在test的main_phase中启动)

        1:在某一个component(比如env、sqr)的main_phase中。

        2:测试用例类uvm_test的main_phase中。

        3:还可以在已经被启动的sequence的body()方法中。

启动方式有两种:

//以在uvm_test中为例

class base_taest extends uvm_test;
    'uvm_component_utils(base_teat)
    extern function new(string name="base_test",uvm_conponent parent =null); //注意conponent的new函数有两个参数,分别为字符串名name称和parent
    extern task main_phase (uvm_phase phase);
    ...
endclass
 

   function base_test::new(string name="base_test",uvm_conponent phase= null);
       super.new(name,parent)
   endfunction
 
   task base_test::main_phase(uvm_phase phase);
       super.build(phase);
       
  //方法一:通过start函数指定seqencer启动sequence,需要在启动前后提起/撤销objection
       phase.raise_objection(this);
       apb_write_seq m_seq;  //声明要启动的sequence
       m_seq=apb_write::type_id::creat("m_seq");  //例化sequence
       m_seq.start(env.i_agt.sqr);   //将sequence交给指定的sequencer,括号中是sequencer的路径
       phase.drop_objection(this);

  //方法二:通过config_db的方式, 该方式有先例化sequence和直接使用cinfig_db的方式
  //如果使用config_db的方式启动sequence,那么objection的提起与撤销将在发送sequence的时候,
  //也就是在sequence中发送seq前后使用starting_phase来提起/撤销objection
  //方法2.1:先定义和例化
       apb_write_sqe m_seq;
       m_seq=apb_write_seq::type_id::creat("m_seq");
       config_db #(apb_write_seq) set::(this, "env_i_agt_sqr.main_phase", "defualt_sequence", m_seq)

  //方法2.2:直接使用config_db  ,是比较常用的一种办法。
       config_db #(uvm_object_wrapper) set::(this,"env.i_agt.aqr.main_phase" , "defualt_sequence", apb_write_seq::type_id::get());  
//注意参数列表里要由uvm_sequence_base变成uvm_object_wrapper

endtask

还可以在已经启动的sequence中启动sequence

calss apb_write_seq extends uvm_sequence #(apb_sequence_item);
    'uvm_object_utils(apb_write_seq)
    
    //主要有两个方法,一个是new,另一个是body,其中body为任务,主要是为了完成发送事务的功能
    extern function new(string name ="apb_write_seq");
    extern task body();
endclass
  
function apb_write_seq::new(string name="aob_write_seq");
    super.new(name);
endfunction

task apb_write_seq :: body();
    apb_sequence_item req;
    
    if(starting_phase!=null)
      start_phase.raise_objection(this);
      apb_read_seq  seq;
      seq=apb_read_seq::type_id::create("seq");
      seq.start(p_sequencer.sqr);
    if(strting_phase!=null)
      starting_phase.drop_objection(this);
endtask

 什么是正在启动的sequence?见下图在某一个test中启动了多个sequence,这些sequence的关系可以是并行、串行或者层次执行(蓝色部分),在层次执行中就有在正在启动的sequence中启动别的sequence。

(二)有多个agent,且各个agent之间需要相互协调

虚序列的由来:

假设现在在测试平台中有两个agent,分别为agent1与agent2。如果某个测试用例想发送这样一个序列:先让agent1发送写序列,再让agent2发送读序列,也就是说这两个序列之间有一个先后关系。

如何发送这样的序列呢?

第一个办法是使用全局事件触发同步:也就是第一个序列发送完后触发事件,然后第二个序列再开始发送。这种办法使用了全局事件实现了一次同步,如果有多次同步,在使用这些方法就会显得很笨拙。

在这里提出第二种方法:虚序列。定义一个virtual_sequence,在该vertual_sequence中嵌入多个sequence,以调度这些sequence。虚序列还要配合虚序列器(virtual_sequencer)使用,虚序列器中的sequencer用于指向实际的sequencer。

注意此处虚序列的嵌入和普通序列的嵌入:普通sequence中嵌套的sequence都在同一个sequencer上启动(通过sequencer仲裁决定);而在virtual sequence中嵌套的sequence可以在不同sequencer实体上同时启动。

”虚"在哪?

system verilog中有很多虚拟类,虚方法,虚接口,这些都需要virtual定义,

本次要介绍的虚序列器(virtual_sequencer)及虚序列(virtual_sequence)也带有虚字。

但是此虚非彼虚,虚序列器和虚序列不需要使用virtual声明,而是都继承自uvm_sequencer和uvm_sequence。这里所说的“虚”指的是:虚序列器本身并不传递事务,也不连接到driver,他只是用于控制其他sequencer。由于并不实际传递事务,所以定义的时候可以不指定要传递的事务类型。

实际上,代码是自由的,virtual sequencer也只是一个名字而已,谁也没有规定叫了这个名字就不能去发送sequence item,也没有谁规定一个验证环境只能有一个virtual sequencer。只不过,这种多sequence的管理方法比较好用和科学,才被单独提出来和介绍,一切要根据实际情况去做决定。

什么时候需要用到虚序列?

这里给出了三种场景:

第一种是只有一个agent的情况,这个时候多个激励驱动这一个agent是不需要virtual sequence的。

第二情况是有多个agent,但这些agent的sequence并不互相影响,也就是说agent1的sequence和agent2的sequence并没有先后之分。这种情况也没有必要设置vietual sequence

第三种情况是有多个agent,而且这些agent之间需要相互协调,比如上文描述的agent1的sequence要先发送,然后再发送agent2的sequence,这就需要virtual sequence来进行控制了。

具体使用方法:

1.定义虚序列器,并嵌入序列器句柄。

//假设现在平台中有两个agent:apb_agent与spi_agent

class v_sequencer extends uvm_sequencer;
    apb_sequencer apb_vsqr; //嵌入虚序列器
    spi_sequencer api_vsqr;
    
endclass

2.定义虚序列,并嵌入序列,然后将使用'uvm_do_with将这些序列挂载到虚序列器中的句柄上。

class v_sequence extends uvm_sequence ;
   
    'uvm_object_utils(v_sequence)
    'uvm_declare_p_sequencer(v_sequencer) //通过该宏将虚序列绑定到虚序列器
    
     apb_sequence  apb_seq;
     spi_sequence  spi_seq;  //嵌入虚序列

     virtual task body();
         'uvm_do_on(apb_seq , p_sequencer.apb_sqr)  //将虚序列发送到虚序列器对应的句柄上。
         'uvm_do_on(api_seq , p_sequencer.spi_sqr)
endclass

3.将虚序列器中的句柄指向真实的序列器。

class env extends uvm_enivernment;
   ...
   v_sequencer v_sqr;
   apb_agent    apb_agt;
   spi_agent    spi_agt;
   
   function void connect_phase(uvm_phase phase);
       super.connect_phase(phase)   
       v_sqr.apb_sqr=apb_agt.sqr;
       v_sqr.spi_sqr=spi_agt.sqr;
endclass

4.启动虚序列:从下面的代码中可以看出,使用vritual sequence可以减少config_db的使用量,因为config_db第二个参数是字符串,非常容易出错,在设计时应尽量减少config_db的使用。假如没有virtual sequence,则每个面向不同sequencer的sequence都要使用config_db启动,而使用了virtual sequence的只需要一次config_db。

class test extends uvm_test;
    
    ...
    task main_phase();
    config_db #(uvm_object_wrapper)set::(this ,"env.v_sqr.main_phase", "default_phase", v_sequence::type_id::get())
endclass

 总结:

所以使用virtual sequence相当于集结了众多面向不同sequencer的sequence群,这些sequence在virtual sequence中被控制调度,并发送到对应虚序列器中的sequencer句柄中,这些句柄都指向的真实的sequencer中。

virtual sequence:承载不同目标sequencer的sequence群落,实现sequence同步;virtual sequence一般只会挂载到virtual sequencer上,且没有自己的sequence_item,只用于控制其他的sequence执行顺序,起统一调度作用。
virtual sequencer:桥接其它sequencer,即连接所有底层sequencer的句柄(指针),是一个中心化的路由器。virtual sequencer本身并不传送item数据对象,因此不需要与driver进行TLM连接。所以用户需在顶层的connect阶段做好virtual sequencer中各个sequencer句柄与sequencer实体对象的一一连接,避免句柄悬空。

参考:UVM中sequence的启动方式_小小verifier的博客-CSDN博客

SystemVerilog|UVM|如果你要搞很多Sequence,请看过来-面包板社区

UVM仿真技术(Virtual Sequence & Virtual Sequencer) - 知乎

UVM——虚序列器与虚序列(virtual sequencer与virtual sequence)_SD.ZHAI的博客-CSDN博客

  • 5
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个使用UVM框架编写的生成E1帧的Agent示例: ```SystemVerilog class E1FrameAgent extends uvm_agent; // 输入输出端口 uvm_analysis_port #(E1Frame) frame_port; // 配置参数 int FRAME_SIZE; int SLOT_SIZE; int SAMPLE_RATE; int BIT_RATE; // 构造函数 function new(string name, uvm_component parent); super.new(name, parent); endfunction // UVM构建阶段 function void build_phase(uvm_phase phase); super.build_phase(phase); // 从配置中获取参数 if (!uvm_config_db #(int)::get(this, "", "FRAME_SIZE", FRAME_SIZE)) `uvm_fatal("E1FrameAgent", "FRAME_SIZE not found in configuration") if (!uvm_config_db #(int)::get(this, "", "SLOT_SIZE", SLOT_SIZE)) `uvm_fatal("E1FrameAgent", "SLOT_SIZE not found in configuration") if (!uvm_config_db #(int)::get(this, "", "SAMPLE_RATE", SAMPLE_RATE)) `uvm_fatal("E1FrameAgent", "SAMPLE_RATE not found in configuration") if (!uvm_config_db #(int)::get(this, "", "BIT_RATE", BIT_RATE)) `uvm_fatal("E1FrameAgent", "BIT_RATE not found in configuration") endfunction // UVM运行阶段 task run_phase(uvm_phase phase); super.run_phase(phase); // 生成E1帧 E1Frame frame; while (1) begin // 从数据生成器获取音频数据 data = get_audio_data(); // 将音频数据转换为E1帧 frame = generate_e1_frame(data); // 将E1帧发送到分析端口 frame_port.write(frame); // 等待一帧时间 #1; end endtask // 生成E1帧 function E1Frame generate_e1_frame(bit [15:0] data[$]); E1Frame frame; bit [7:0] coded_slot[SLOT_SIZE]; // 将数据分成32个时隙 for (int i = 0; i < FRAME_SIZE; i++) begin if (i == 0) begin // 同步信号 frame.slots[i] = 8'h00; end else begin int slot_index = i - 1; if (slot_index < data.size()) begin // 将每个时隙的数据进行PCM编码 for (int j = 0; j < SLOT_SIZE; j++) begin coded_slot[j] = pcm_encode(data[slot_index*SLOT_SIZE+j]); end frame.slots[i] = {coded_slot}; end else begin // 填充空时隙 frame.slots[i] = {8'h00}; end end end return frame; endfunction // PCM编码函数 function bit [7:0] pcm_encode(bit [15:0] sample); bit [7:0] code; // 将采样值转换为16位有符号整数 int sample_int = sample; if (sample_int > 32767) sample_int = 32767; else if (sample_int < -32768) sample_int = -32768; // 将16位整数转换为8位有符号整数 int high_byte = (sample_int >> 8) & 8'hFF; int low_byte = sample_int & 8'hFF; code = {(high_byte & 4'hF0), ((low_byte >> 4) & 4'h0F)}; return code; endfunction endclass ``` 这个Agent实现了一个`generate_e1_frame`函数,用于将输入的音频数据生成E1帧,并将E1帧发送到分析端口。在`run_phase`任务中,它从数据生成器中获取音频数据,然后调用`generate_e1_frame`函数生成E1帧并将其发送到分析端口。在`build_phase`阶段,从配置中获取E1帧的帧结构和PCM编码参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值