UVM验证方法学_factory机制

factory机制即工厂机制,是uvm中最重要的机制之一。其存在的意义就是为了能够方便的替换环境中的实例或者已注册的类型。一般而言,在搭建完环境后,我们如果需要对环境进行更改配置或者相关的类信息,我们可以通过使用factory机制进行覆盖,达到替换的效果,从而大大提高环境的可重用性和灵活性。

factory机制的实现被集成在了各种宏中。


一、概述

factory机制的主要步骤:

(1)注册

注册就是把component或者object登记在uvm内部的一张表当中,这个factory是整个全局仿真中存在且唯一存在的“机构”,所有被注册过的类才能使用factory机制。

component和object要使用不同的宏进行注册:

① component使用uvm_component_utils宏注册。

② object使用uvm_object_utils宏注册。

③ transaction一般使用uvm_object_utils宏结合field_automation机制注册。

(2)创建

创建即实例化对象,所有注册到factory的类均可通过factory独特的方式实例化对象。

例如在agent中的创建:

    function void wr_agent::build_phase(uvm_phase phase);
        super.build_phase(phase);
        `uvm_info(this.name,"build_phase active.",UVM_LOW)
        if(is_active == UVM_ACTIVE)begin
            sqr = wr_sequencer::type_id::create("sqr",this);
            drv = wr_driver::type_id::create("drv",this);
        end
        mon = wr_monitor::type_id::create("mon",this);
    endfunction

但factory的独特方式,实际上也是调用了new函数,也是先创建句柄再赋予对象。这种factory独特的例化方式,创建了组件的实例并返回句柄,但本质还是调用的new(name, parent) 函数。

(3)重载

重载就是覆盖、替换。对于验证的工作来说,重载可以提高整个环境的重用性,减少验证人员的工作量。

二、factory机制重载概述

(1)virtual类型的function / task

sv作为一种面向对象的语言,同样具有面向对象三大基本特性:封装、继承、多态。其中重载也是sv的一大特征,而重载的实现则离不开factory机制。当在父类中定义一个函数或者任务时,如果将其设置为virtual类型,那么就可以在子类中重载这个函数或者任务。

重载必须要在有继承关系之后才能用,也就是必须有派生关系。对于UVM中的factory机制,可实现factory机制中创建函数的重载,即将父类的创建函数重载成子类的创建函数。也就是说,重载之后,父类的句柄指向的是子类的实例。

uvm中的phase机制,就是对函数或者任务重载的最典型应用。

(2)重载实现前提

对于factory机制来说,重载的实现有自己的独特方式。在实例化时,uvm首先会通过factory机制在自己内部的一张表格中查看是否有相关的重载记录。set_type_override_by_type语句则相当于在factory机制的表格中加入了一条记录,当查到有重载记录时,会使用新的类型来替代旧的类型,最终创建出新的实例。但使用factory机制的重载是有前提的,并不是任意的类都可以互相重载。要想使用重载的功能则必须满足以下4个要求:

① 重载的类和被重载的类都要在定义时进行注册。
② 被重载的类在实例化时要使用factory机制式的实例化方式,不能使用new的方式。

③ 重载的类与被重载的类必须有派生关系。也就是说,重载的类必须派生自被重载的类,被重载的类必须是重载类的父类。且此派生关系不可颠倒。

④ component与object之间互相不能重载。他们之间实例化时的参数不同,无法重载。

(3)component源码中的重载种类

在component中,factory机制提供了不同种类的重载实现方式。

查看uvm源码:

① set_type_override_by_type

(uvm_component.svh)

// set_type_override_by_type (static)
// -------------------------

function void uvm_component::set_type_override_by_type (uvm_object_wrapper original_type,
                                                        uvm_object_wrapper override_type,
                                                        bit    replace=1);
   factory.set_type_override_by_type(original_type, override_type, replace);
endfunction 

set_type_override_by_type函数有三个参数,第三个参数是replace,默认为1。在实际应用中一般只用前两个参数:第一个参数是被重载的类型,第二个参数是重载的类型。该函数会将环境中所有相关类型进行重载。

② set_inst_override_by_type

(uvm_component.svh)

有时候可能并不希望把验证平台中的所有类型全部进行替换,而只是替换其中的某一部分,这种情况就要用到set_inst_override_by_type函数。

// set_inst_override_by_type
// -------------------------

function void uvm_component::set_inst_override_by_type (string relative_inst_path,  
                                                        uvm_object_wrapper original_type,
                                                        uvm_object_wrapper override_type);
  string full_inst_path;

  if (relative_inst_path == "")
    full_inst_path = get_full_name();
  else
    full_inst_path = {get_full_name(), ".", relative_inst_path};

  factory.set_inst_override_by_type(original_type, override_type, full_inst_path);

endfunction

第一个参数是相对路径,第二个参数是被重载的类型,第三个参数是要重载的类型。使用第一个参数就可以指定某一个特定的部件进行重载。

③ set_type_override

(uvm_component.svh)

set_type_override_by_type和set_inst_override_by_type的参数都是一个uvm_object_wrapper型的类型参数,这种参数通过get_type()的形式获得。除此之外,uvm还提供了另外一种简单的方法来替换这种写法:使用字符串。

与set_type_override_by_type相对应的是set_type_override:

// set_type_override (static)
// -----------------

function void uvm_component::set_type_override (string original_type_name,
                                                string override_type_name,
                                                bit    replace=1);
   factory.set_type_override_by_name(original_type_name,
                                     override_type_name, replace);
endfunction 

三个参数是相同的。可以发现set_type_override其实调用了set_type_override_by_name,也就是使用的名称字符串,所有的操作都是基于名称。

④ set_inst_override

(uvm_component.svh)

与set_inst_override_by_type相对的是set_inst_override:

// set_inst_override
// -----------------

function void  uvm_component::set_inst_override (string relative_inst_path,  
                                                 string original_type_name,
                                                 string override_type_name);
  string full_inst_path;

  if (relative_inst_path == "")
    full_inst_path = get_full_name();
  else
    full_inst_path = {get_full_name(), ".", relative_inst_path};

  factory.set_inst_override_by_name(
                            original_type_name,
                            override_type_name,
                            full_inst_path);
endfunction 

所调用的也是set_inst_override_by_name函数。

(4)factory源码中的重载种类

如果在一个无法使用component的位置,例如在top_tb的initial语句里,那么这个时候就无法使用uvm_component的函数。uvm提供了另外的函数来解决这个问题,这些函数都来自uvm_factory类。其实阅读源码可以发现,uvm_component中的函数所调用的也都是来自uvm_factory中的函数。不过要注意参数的顺序,是不同的。

uvm_componentuvm_factory参数
set_type_override_by_typeset_type_override_by_typereplace参数
set_inst_override_by_typeset_inst_override_by_type路径参数
set_type_overrideset_type_override_by_namereplace参数
set_inst_overrideset_inst_override_by_name路径参数

uvm_factory中的四个函数:

extern function
    void set_type_override_by_type (uvm_object_wrapper original_type,
                                    uvm_object_wrapper override_type,
                                    bit replace=1);

extern function
    void set_inst_override_by_type (uvm_object_wrapper original_type,
                                    uvm_object_wrapper override_type,
                                    string full_inst_path);

extern function
    void set_type_override_by_name (string original_type_name,
                                    string override_type_name,
                                    bit replace=1);

extern function
    void set_inst_override_by_name (string original_type_name,
                                    string override_type_name,
                                    string full_inst_path);

存在一个uvm_factory类型的全局变量factory,使用时可以直接调用。当然,在一个component内也完全可以直接调用factory机制的重载函数。

三、factory机制重载的实现

(1)transaction重载

例如需要构建一个新的异常测试用例,则可以使用transaction的重载,创建新的数据包,而不需要改动sequence。

以异步FIFO为例,重新约束写入的数据:

class wr_transaction_new #(parameter DSIZE = 8) extends wr_transaction;
    `uvm_object_utils(wr_transaction_new)

    constraint c_wdata_new{
        wdata == 8'h88;
        }

endclass:wr_transaction_new

应用重载:

class tc_new_tr extends tc_sanity;
    `uvm_component_utils(tc_new_tr)

    string name;
    function new(string name = "",
                 uvm_component parent = null);
        super.new(name,parent);
        this.name = name;
    endfunction

    extern virtual function void build_phase(uvm_phase phase);
    extern virtual function void connect_phase(uvm_phase phase);

endclass:tc_new_tr

    function void tc_new_tr::build_phase(uvm_phase phase);
        super.build_phase(phase);
        `uvm_info(this.name,"build_phase active.",UVM_LOW)

        factory.set_type_override_by_type(wr_transaction::get_type(), wr_transaction_new::get_type());
    endfunction

    function void tc_new_tr::connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        `uvm_info(this.name,"connect_phase active.",UVM_LOW)

        uvm_config_db#(uvm_object_wrapper)::set(
            uvm_root::get(),
            $sformatf("%s.main_phase",this.fifo_env.wr_agt.sqr.get_full_name()),
            "default_sequence",
            wr_sequence::type_id::get()
            );

        uvm_config_db#(uvm_object_wrapper)::set(
            uvm_root::get(),
            $sformatf("%s.main_phase",this.fifo_env.rd_agt.sqr.get_full_name()),
            "default_sequence",
            rd_sequence::type_id::get()
            );

        check_config_usage();
        print_config(1);
    endfunction

(2)sequence的重载

重新约束当然也可以在sequence中完成。不过,sequence其实可以直接完成对transaction的重新约束从而创建新条件。对于重载的情况,就是需要重新启动一个继承之后的新sequence。

当然像嵌套的sequence或者使用virtual_sequence进行调度的情况,也可以针对其中某一个sequence进行重载,是否需要启动新的sequence视情况而定。重载的操作是类似的,不再赘述。

(3)component的重载

如果重载的场景比较复杂,可以直接重载component。一般为了方便,可以直接在新的testcase中对component进行重载。以异步FIFO为例,创建一个新的testcase继承自tc_sanity:

class tc_new extends tc_sanity;
    `uvm_component_utils(tc_new)

    string name;
    function new(string name = "",
                 uvm_component parent = null);
        super.new(name,parent);
        this.name = name;
    endfunction

    extern virtual function void build_phase(uvm_phase phase);
    extern virtual function void connect_phase(uvm_phase phase);

endclass:tc_new

    function void tc_new::build_phase(uvm_phase phase);
        super.build_phase(phase);
        `uvm_info(this.name,"build_phase active.",UVM_LOW)

        factory.set_type_override_by_type(wr_driver::get_type(), new_driver::get_type());
    endfunction

    function void tc_new::connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        `uvm_info(this.name,"connect_phase active.",UVM_LOW)

        uvm_config_db#(uvm_object_wrapper)::set(
            uvm_root::get(),
            $sformatf("%s.main_phase",this.fifo_env.wr_agt.sqr.get_full_name()),
            "default_sequence",
            wr_sequence::type_id::get()
            );

        uvm_config_db#(uvm_object_wrapper)::set(
            uvm_root::get(),
            $sformatf("%s.main_phase",this.fifo_env.rd_agt.sqr.get_full_name()),
            "default_sequence",
            rd_sequence::type_id::get()
            );

        check_config_usage();
        print_config(1);
    endfunction

新创建一个driver继承自wr_driver,所有写入的数据都规定为'h66

class new_driver#(parameter DSIZE = 8) extends wr_driver;
    `uvm_component_utils(new_driver)
    string name;
    function new(string name = "",
                 uvm_component parent = null);
        super.new(name,parent);
        this.name = name;
    endfunction

    extern virtual task main_phase(uvm_phase phase);
    extern virtual task drive_tr(input wr_transaction req);

endclass:new_driver

    task new_driver::main_phase(uvm_phase phase);
        int cnt;
        super.main_phase(phase);
        repeat(plus::plus_wait_time)@this.wr_drv_if.drv_cb;

        fork
            while(1)begin
                cnt++;
                this.seq_item_port.get_next_item(req);
                if(req.send_id == 1)begin
                    @this.wr_drv_if.drv_cb;
                end

                drive_tr(req);
                `uvm_info(this.name,$sformatf("No.%0d driving.",cnt),UVM_LOW)

                rsp = new();
                rsp.set_id_info(req);
                seq_item_port.put_response(rsp);
                // driver向sequence返回response

                this.seq_item_port.item_done();
                req.print();
            end
            begin
                @(negedge this.wr_drv_if.wrst_n);
                phase.jump(uvm_reset_phase::get());
                `uvm_info(this.name,"2nd reset activated.",UVM_LOW)
            end
        join
    endtask

    task new_driver::drive_tr(input wr_transaction req);
        this.wr_drv_if.drv_cb.winc  <= 1;
        this.wr_drv_if.drv_cb.wdata <= 8'h66;

        @this.wr_drv_if.drv_cb;
        while(this.wr_drv_if.drv_cb.wfull == 1)begin
            @this.wr_drv_if.drv_cb;
        end
        this.wr_drv_if.drv_cb.winc <= 0;

        repeat(req.wr_interval)@this.wr_drv_if.drv_cb;
    endtask

新的driver并不需要实例化,直接进行重载即可。当然,重载的方法也不止一种,这里只展示了统一重载。

把所有的测试用例都使用driver重载实现,这种方法是可行的,但是不推荐。这样做等于放弃了sequence机制,虽然也可以实现想要的场景,但是违背了uvm最初的设计初衷,其稳定性和可靠性都是未知数,容易造成一些不可控的情况。

四、复杂重载

uvm也支持连续的重载,例如父类派生出子类,子类再派生出一个新的子类,这种线性关系的多次重载之后最终以最新的子类为准。

除了这种连续的重载外,还有一种是替换式的重载。例如树形结构中具有兄弟关系的组件,他们都拥有相同的父类,此时的重载就是替换式的重载。

仍旧拿异步FIFO举例,wr_driver派生出两个子driver,他们是兄弟关系。

new_drivernew_driver_bro
wdata <= 8'h66;wdata <= 8'h99;

进行替换重载:

factory.set_type_override_by_type(wr_driver::get_type(), new_driver::get_type()    ,1 );
factory.set_type_override_by_type(wr_driver::get_type(), new_driver_bro::get_type(),1 );

更改第三个replace的参数可以控制重载的生效。

如果为1则是立即生效;如果为0则不会立即生效。

factory机制能够查询到两条相关的记录。如果为0,它并不会在看完第一条记录后即直接创建一个新的实例,而是等到最终看完第二条记录后才会创建实例。

如果哥哥的参数为1,则无论弟弟的参数是多少,最终结果会以哥哥为准,即立即生效,驱动值为'h66。

如果哥哥的参数为0,即不会立即生效,则无论弟弟的参数是多少,最终结果会以弟弟为准,因为弟弟的重载更加靠后,驱动值为'h99。

当然,这是只有两兄弟的情况。如果有多个兄弟,则需要相应控制replace参数,完成替换重载。

相应地,如果重载的情况改为:

factory.set_type_override_by_type(wr_driver::get_type(), new_driver::get_type()    ,0 );
factory.set_type_override_by_type(new_driver::get_type(), new_driver_bro::get_type(),0 );

这种情况也是可行的。弟弟重载了哥哥,虽然弟弟与哥哥并没有派生关系,但也是可以的。可以总结为:

在有多个重载时,最终重载的类要与最初被重载的类有派生关系。

最终重载的类必须派生自最初被重载的类,最初被重载的类必须是最终重载类的父类。

简单来说就是兄弟关系之间可以进行重载,但前提是必须都派生自相同的父类。

五、factory机制调试

factory机制同样也可以进行调制。uvm提供了print_override_info函数,来输出所有的重载信息。可以选择在connect_phase中进行调用:

 this.fifo_env.wr_agt.drv.print_override_info("wr_driver");

可以打印出wr_driver相关的所有重载。结果为:

#### Factory Override Information (*)

Given a request for an object of type 'wr_driver' with an instance
path of 'uvm_test_top.fifo_env.wr_agt.drv', the factory encountered
the following relevant overrides. An 'x' next to a match indicates a
match that was ignored.

  Original Type  Instance Path  Override Type 
  -------------  -------------  --------------
  wr_driver      *              new_driver      <type override>
  new_driver     *              new_driver_bro  <type override>
 
Result:

  The factory will produce an object of type 'new_driver_bro'

(*) Types with no associated type name will be printed as <unknown>

####

这里的路径是*,因为所用的重载类型是set_type_override_by_type,没有给出路径参数。改为set_inst_override_by_type进行重载:

set_inst_override_by_type("fifo_env.wr_agt.drv",wr_driver::get_type(), new_driver::get_type());
set_inst_override_by_type("fifo_env.wr_agt.drv",new_driver::get_type(), new_driver_bro::get_type());

结果为:

#### Factory Override Information (*)

Given a request for an object of type 'wr_driver' with an instance
path of 'uvm_test_top.fifo_env.wr_agt.drv', the factory encountered
the following relevant overrides. An 'x' next to a match indicates a
match that was ignored.

  Original Type  Instance Path                     Override Type 
  -------------  --------------------------------  --------------
  wr_driver      uvm_test_top.fifo_env.wr_agt.drv  new_driver     
  new_driver     uvm_test_top.fifo_env.wr_agt.drv  new_driver_bro 
 
Result:

  The factory will produce an object of type 'new_driver_bro'

(*) Types with no associated type name will be printed as <unknown>

####

此时会显示出路径。

除了这个函数外,uvm_factory还有debug_create_by_type函数,其输出与使用print_override_info相同。

除了上述两个函数外,uvm_factory还提供print函数。print函数只有一个参数,其取值可为0、1或2。

为0时,仅仅打印被重载的实例和类型。

为1时,打印参数为0时的信息,以及所有用户创建的、注册到factory的类的名称。

为2时,打印参数为1时的信息,以及系统创建的、所有注册到factory的类的名称(例如uvm_reg_item)

调用print函数:

    function void tc_new_bro::connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        `uvm_info(this.name,"connect_phase active.",UVM_LOW)
        this.fifo_env.wr_agt.drv.print_override_info("wr_driver");
        factory.print(2);

        ...

打印结果为:

#### Factory Configuration (*)

Instance Overrides:

  Requested Type  Override Path                     Override Type 
  --------------  --------------------------------  --------------
  new_driver      uvm_test_top.fifo_env.wr_agt.drv  new_driver_bro
  wr_driver       uvm_test_top.fifo_env.wr_agt.drv  new_driver

No type overrides are registered with this factory

All types registered with the factory: 68 total
(types without type names will not be printed)

  Type Name
  ---------
  env
  new_driver
  new_driver_bro
  rd_agent
  rd_alternate_sequence
  rd_driver
  rd_monitor
  rd_sequence
  rd_sequencer
  rd_transaction
  scoreboard
  snps_uvm_reg_bank_group
  snps_uvm_reg_map
  tc_new
  tc_new_bro
  tc_new_tr
  tc_sanity
  tc_virtual
  tc_wr_alternate
  uvm_exhaustive_sequence
  uvm_mem_access_seq
  uvm_mem_shared_access_seq
  uvm_mem_single_access_seq
  uvm_mem_single_walk_seq
  uvm_mem_walk_seq
  uvm_objection
  uvm_random_sequence
  uvm_recorder
  uvm_reg_access_seq
  uvm_reg_backdoor
  uvm_reg_bit_bash_seq
  uvm_reg_field
  uvm_reg_hw_reset_seq
  uvm_reg_item
  uvm_reg_map
  uvm_reg_mem_access_seq
  uvm_reg_mem_built_in_seq
  uvm_reg_mem_hdl_paths_seq
  uvm_reg_mem_shared_access_seq
  uvm_reg_read_only_cbs
  uvm_reg_shared_access_seq
  uvm_reg_single_access_seq
  uvm_reg_single_bit_bash_seq
  uvm_reg_tlm_adapter
  uvm_reg_write_only_cbs
  uvm_sequence_item
  uvm_sequence_library_cfg
  uvm_simple_sequence
  uvm_test_done
  uvm_tlm_generic_payload
  uvm_vcs_recorder
  uvm_vreg_field
  virtual_sequence
  virtual_sequencer
  wr_agent
  wr_alternate_sequence
  wr_driver
  wr_monitor
  wr_sequence
  wr_sequencer
  wr_transaction
  wr_transaction_new
(*) Types with no associated type name will be printed as <unknown>

####

除此之外,还有print_topology()函数,可以显示出整棵UVM树的拓扑结构。uvm树在build_phase执行完成后才完全建立完成,因此该函数应该在build_phase之后调用。

uvm_top.print_topology();

显示出拓扑结构:

UVM_INFO @ 33626.00ns: reporter [UVMTOP] UVM testbench topology:
------------------------------------------------------------------
Name                       Type                        Size  Value
------------------------------------------------------------------
uvm_test_top               tc_new_bro                  -     @477 
  fifo_env                 env                         -     @485 
    rd_agt                 rd_agent                    -     @501 
      drv                  rd_driver                   -     @627 
        rsp_port           uvm_analysis_port           -     @644 
        seq_item_port      uvm_seq_item_pull_port      -     @635 
      mon                  rd_monitor                  -     @776 
        ap                 uvm_analysis_port           -     @786 
      sqr                  rd_sequencer                -     @653 
        rsp_export         uvm_analysis_export         -     @661 
        seq_item_export    uvm_seq_item_pull_imp       -     @767 
        arbitration_queue  array                       0     -    
        lock_queue         array                       0     -    
        num_last_reqs      integral                    32    'd1  
        num_last_rsps      integral                    32    'd1  
    rdmon_scb_fifo         uvm_tlm_analysis_fifo #(T)  -     @570 
      analysis_export      uvm_analysis_imp            -     @614 
      get_ap               uvm_analysis_port           -     @605 
      get_peek_export      uvm_get_peek_imp            -     @587 
      put_ap               uvm_analysis_port           -     @596 
      put_export           uvm_put_imp                 -     @578 
    scb                    scoreboard                  -     @509 
      rd_port              uvm_blocking_get_port       -     @809 
      wr_port              uvm_blocking_get_port       -     @800 
    wr_agt                 wr_agent                    -     @493 
      drv                  new_driver_bro              -     @945 
        rsp_port           uvm_analysis_port           -     @962 
        seq_item_port      uvm_seq_item_pull_port      -     @953 
        test_sig           integral                    32    'h8  
      mon                  wr_monitor                  -     @971 
        ap                 uvm_analysis_port           -     @983 
      sqr                  wr_sequencer                -     @822 
        rsp_export         uvm_analysis_export         -     @830 
        seq_item_export    uvm_seq_item_pull_imp       -     @936 
        arbitration_queue  array                       0     -    
        lock_queue         array                       0     -    
        num_last_reqs      integral                    32    'd1  
        num_last_rsps      integral                    32    'd1  
    wrmon_scb_fifo         uvm_tlm_analysis_fifo #(T)  -     @517 
      analysis_export      uvm_analysis_imp            -     @561 
      get_ap               uvm_analysis_port           -     @552 
      get_peek_export      uvm_get_peek_imp            -     @534 
      put_ap               uvm_analysis_port           -     @543 
      put_export           uvm_put_imp                 -     @525 
------------------------------------------------------------------

可以很直观地观察整个树形结构。

六、创建实例的接口

factory机制同样提供了一系列接口来创建实例。

(1)create_object_by_name,根据类名字创建一个object,一般只传第一个参数:

wr_transaction tr;
void'($cast(tr, factory.create_object_by_name("wr_transaction")));

(2)create_object_by_type,根据类型创建一个object,一般只传第一个参数:

wr_transaction tr;
void'($cast(tr, factory.create_object_by_type(wr_transaction::get_type())));

(3)create_component_by_name,根据类名创建一个component。有四个参数,第一个参数是字符串类型的类名,第二个参数是父结点的全名,第三个参数是为这个新的component起的名字,
第四个参数是父结点的指针。在调用这个函数时,这四个参数都要使用:

wr_driver wr_drv;
void' ($cast(wr_drv, factory.create_component_by_name("wr_transaction", get_full
_name(), "wr_drv", this)));

这个函数一般只在一个component的new函数或者build_phase中使用。

如果是在一个object中被调用,则很难确认parent参数;如果是在connect_phase之后调用,由于uvm要求component在build_phase及之前需要实例化完毕,所以会调用失败。


总结

本质上来看,factory机制其实是对SystemVerilog中new函数的重载。这个原始的new函数实在是太简单,功能也太少。经过factory机制的改良之后,进行实例化的方法多了很多。这也体现了uvm编写的一个原则,一个好的库应该提供更多方便实用的接口,这种接口一方面是库自己写出来并开放给用户的,另外一方面就是改良语言原始的接口,使得更加方便用户的使用。同时factory机制的强大之处在于我们可以更加灵活地控制整个环境,多种重载功能又可以提高整个环境的重用性,使得我们有更多种方法可以去构建不同的测试场景。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Clock_926

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

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

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

打赏作者

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

抵扣说明:

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

余额充值