UVM的基本教程

文章目录


一、基本介绍

通用验证方法学UVM(Universal Verification Methodology)已经成为集成电路设计的验证标准。UVM类构建的库促进了测试用例的搭建,UVM测试用例的每一个元素都是从现有的UVM类派生出来的。
每个类都有仿真阶段,这些仿真阶段作为类的方法,按照一定的顺序执行。其中较为常用的几个UVM阶段如下:

  • build_phase负责测试用例结构的创建和配置,构建组件的层次结构。
  • connect_phase用于连接类中的不同子组件。
  • run_phase是主要阶段,在其中执行仿真。
  • report_phase可用于显示仿真的结果

为了在类和变量中实现一些重要的方法,UVM提供了UVM宏。最常见的UVM宏如下:

  • uvm_component_utils: 当类从uvm_component类派生时,注册一个新的类的类型。
  • uvm_object_utils: 类似于uvm_component_utils,但类是从类uvm_object派生的类。
  • uvm_field_int: 在UVM工厂注册一个变量。这个宏提供了像copy()、compare()和print()这样的函数.
  • uvm_info:在环境中仿真期间打印消息。
  • uvm_error:这个宏发送带有错误日志的消息。
  • uvm_fatal: 仿真发生致命错误时,这个宏可以强制结束仿真并打印消息。

为了解释一个UVM环境的结构,在测试用例中,将使用一个简单的加法器作为测试设计(DUT)。UVM测试平台如图所示。DUT是与测试台交互以验证其功能的硬件实现。
图1 UVM 测试平台
为了实现DUT的仿真, sequencer产生一系列数据送入DUT。由于sequencer发送高抽象级别的数据包,并且DUT只接受来自接口的数据,所以,driver用来将来自sequencer的数据包,转换成信号送入DUT。
经过接口的数据需要被捕获,以便后续的仿真验证。由于driver只能实现数据包到信号的转换,所以,另外还需要一个模块实现与driver相反的功能,将信号转换成数据包。monitor就是这样的一个模块,收集driver与DUT之间通信的接口信号,并将其转换成数据包,最后送入参考模型中去进行比较。
一个agent通常包含三个组件:一个sequencer序列发生器,一个driver驱动,一个monitor监控。有两种类型的agent:Active Agent包含上述三个组件;Passive Agent只包含上述的monitor监控和driver驱动。agent包含build phase函数,用于去创建层次结果,以及connect phase用于连接模块。
参考模型(refmod)是在RTL实现之前,早期阶段构思的理想的模型。在抽象的高级别上模拟DUT。
comparator类主要用于比较参考模型和DUT之间的数据。参考模型和比较器组成了记分牌,用于检查DUT产生的传输是否正确。一个或者多个agent加上记分牌构成了env类。测试类负责执行测试,创建环境,并将序列连接到序列发生器上。最后,top中实现DUT和testbench的例化。

二、在实践中学习

下面通过对一个加法器验证环境的搭建,来进一步理解UVM中的各个组件。加法器模块的功能验证平台分为以下的模块和类。

1.接口interface

接口的构造是专门为封装块之间的通信信号而创建的,modport为模块端口提供方向信息,并控制特定模块内任务和功能的使用。

interface input_if(input clk, rst);
    logic [31:0] A, B;
    logic valid, ready;
    
    modport port(input clk, rst, A, B, valid, output ready);
endinterface


interface output_if(input clk, rst);
    logic [31:0] data;
    logic valid, ready;
    
    modport port(input clk, rst, output valid, data, ready);
endinterface

2.待测设计DUT

待测设计DUT(design under test)是需要被验证的设计代码。

module adder(input_if.port inter, output_if.port out_inter, output state);
    enum logic [1:0] {INITIAL,WAIT,SEND} state;
    
    always_ff @(posedge inter.clk)
        if(inter.rst) begin
            inter.ready <= 0;
            out_inter.data <= 'x;
            out_inter.valid <= 0;
            state <= INITIAL;
        end
        else case(state)
                INITIAL: begin
                    inter.ready <= 1;
                    state <= WAIT;
                end
                
                WAIT: begin
                    if(inter.valid) begin
                        inter.ready <= 0;
                        out_inter.data <= inter.A + inter.B;
                        out_inter.valid <= 1;
                        state <= SEND;
                    end
                end
                
                SEND: begin
                    if(out_inter.ready) begin
                        out_inter.valid <= 0;
                        inter.ready <= 1;
                        state <= WAIT;
                    end
                end
        endcase
endmodule: adder

3.传输数据包transaction

传输数据包的构造是专门为封装块之间通信的数据而创建的,其中的field机制能够将数据包中的数据注册到UVM工厂中,为数据提供copy()、compare()、print()、colne()等函数。

class packet_in extends uvm_sequence_item;
    rand integer A;
    rand integer B;

    `uvm_object_utils_begin(packet_in)
        `uvm_field_int(A, UVM_ALL_ON|UVM_HEX)
        `uvm_field_int(B, UVM_ALL_ON|UVM_HEX)
    `uvm_object_utils_end

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


class packet_out extends uvm_sequence_item;
    integer data;

    `uvm_object_utils_begin(packet_out)
        `uvm_field_int(data, UVM_ALL_ON|UVM_HEX)
    `uvm_object_utils_end

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

4.序列sequence

UVM序列是一个对象,它用于生成仿真的行为。UVM序列不是组件层次结构的一部分。每个UVM序列最终被绑定到一个UVM序列器。多个UVM序列实例可以绑定到同一个UVM序列器。

class sequence_in extends uvm_sequence #(packet_in);
    `uvm_object_utils(sequence_in)

    function new(string name="sequence_in");
        super.new(name);
    endfunction: new

    task body;
        packet_in tx;

        forever begin
            tx = packet_in::type_id::create("tx");
            start_item(tx);
            assert(tx.randomize());
            finish_item(tx);
        end
    endtask: body
endclass: sequence_in

5.序列器sequencer

UVM序列器作为仲裁器,用于控制来自多个仿真序列的事务流。更具体地说,UVM序列器控制,由一个或多个UVM序列生成的事务流。

class sequencer extends uvm_sequencer #(packet_in);
    `uvm_component_utils(sequencer)

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

6.驱动器driver

驱动器(driver)从序列器(sequencer),接收序列(sequence)产生的传输数据包(transaction),并在DUT接口上应用(驱动)它。因此,驱动器driver将传输级别的数据包仿真,转换成管脚信号级别的仿真。它还有一个TLM接口,用于接收来自序列器sequencer的传输数据包transaction,去驱动DUT上的接口信号。

typedef virtual input_if input_vif;

class driver extends uvm_driver #(packet_in);
    `uvm_component_utils(driver)
    input_vif vif;
    event begin_record, end_record;

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

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        assert(uvm_config_db#(input_vif)::get(this, "", "vif", vif));
    endfunction

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
            reset_signals();
            get_and_drive(phase);
            record_tr();
        join
    endtask

    virtual protected task reset_signals();
        wait (vif.rst === 1);
        forever begin
            vif.valid <= '0;
            vif.A <= 'x;
            vif.B <= 'x;
            @(posedge vif.rst);
        end
    endtask

    virtual protected task get_and_drive(uvm_phase phase);
        wait(vif.rst === 1);
        @(negedge vif.rst);
        @(posedge vif.clk);
        
        forever begin
            seq_item_port.get(req);
            -> begin_record;
            drive_transfer(req);
        end
    endtask

    virtual protected task drive_transfer(packet_in tr);
        vif.A = tr.A;
        vif.B = tr.B;
        vif.valid = 1;

        @(posedge vif.clk)
        
        while(!vif.ready)
            @(posedge vif.clk);
        
        -> end_record;
        @(posedge vif.clk); //hold time
        vif.valid = 0;
        @(posedge vif.clk);
    endtask

    virtual task record_tr();
        forever begin
            @(begin_record);
            begin_tr(req, "driver");
            @(end_record);
            end_tr(req);
        end
    endtask
endclass


typedef virtual output_if output_vif;

class driver_out extends uvm_driver #(packet_out);
    `uvm_component_utils(driver_out)
    output_vif vif;

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

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        assert(uvm_config_db#(output_vif)::get(this, "", "vif", vif));
    endfunction

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
            reset_signals();
            drive(phase);
        join
    endtask

    virtual protected task reset_signals();
        wait (vif.rst === 1);
        forever begin
            vif.ready <= '0;
            @(posedge vif.rst);
        end
    endtask

    virtual protected task drive(uvm_phase phase);
        wait(vif.rst === 1);
        @(negedge vif.rst);
        forever begin
          @(posedge vif.clk);
          vif.ready <= 1;
        end
    endtask
endclass

7.监视器monitor

监视器(monitor)对DUT接口进行采样,并捕获事务中的信息,构成传输数据包(transaction),这些传输数据包被发送到UVM测试台以进行进一步分析。因此,与驱动器(driver)类似,但过程相反,它实现的是将管脚信号上的信号收集转换成传输数据包。
UVM监视器可以在内部对所产生的事务执行一些处理(例如覆盖率收集、检查、日志记录、记录等等)。

class monitor extends uvm_monitor;
    input_vif  vif;
    event begin_record, end_record;
    packet_in tr;
    uvm_analysis_port #(packet_in) item_collected_port;
    `uvm_component_utils(monitor)
   
    function new(string name, uvm_component parent);
        super.new(name, parent);
        item_collected_port = new ("item_collected_port", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        assert(uvm_config_db#(input_vif)::get(this, "", "vif", vif));
        tr = packet_in::type_id::create("tr", this);
    endfunction

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
            collect_transactions(phase);
            record_tr();
        join
    endtask

    virtual task collect_transactions(uvm_phase phase);
        wait(vif.rst === 1);
        @(negedge vif.rst);
        
        forever begin
            do begin
                @(posedge vif.clk);
            end while (vif.valid === 0 || vif.ready === 0);
            -> begin_record;
            
            tr.A = vif.A;
            tr.B = vif.B;
            item_collected_port.write(tr);

            @(posedge vif.clk);
            -> end_record;
        end
    endtask

    virtual task record_tr();
        forever begin
            @(begin_record);
            begin_tr(tr, "monitor");
            @(end_record);
            end_tr(tr);
        end
    endtask
endclass


class monitor_out extends uvm_monitor;
    `uvm_component_utils(monitor_out)
    output_vif  vif;
    event begin_record, end_record;
    packet_out tr;
    uvm_analysis_port #(packet_out) item_collected_port;
   
    function new(string name, uvm_component parent);
        super.new(name, parent);
        item_collected_port = new ("item_collected_port", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        assert(uvm_config_db#(output_vif)::get(this, "", "vif", vif));
        tr = packet_out::type_id::create("tr", this);
    endfunction

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
            collect_transactions(phase);
            record_tr();
        join
    endtask

    virtual task collect_transactions(uvm_phase phase);
        wait(vif.rst === 1);
        @(negedge vif.rst);
        
        forever begin
            do begin
                @(posedge vif.clk);
            end while (vif.valid === 0 || vif.ready === 0);
            -> begin_record;
            
            tr.data = vif.data;
            item_collected_port.write(tr);

            @(posedge vif.clk);
            -> end_record;
        end
    endtask

    virtual task record_tr();
        forever begin
            @(begin_record);
            begin_tr(tr, "monitor_out");
            @(end_record);
            end_tr(tr);
        end
    endtask
endclass

8.代理agent

代理(agent)是一个分层组件,它将处理特定DUT接口的其他验证组件组合在一起。一个典型的代理包括:一个sequencer,用于管理仿真流的UVM序列器;一个driver,用于在DUT接口上驱动仿真的驱动器;一个monitor,用于监视DUT的接口。代理可能包括其他组件,如覆盖率收集器、协议检查器、TLM模型等。
在这里插入图片描述

class agent extends uvm_agent;
    sequencer sqr;
    driver    drv;
    monitor   mon;

    uvm_analysis_port #(packet_in) item_collected_port;

    `uvm_component_utils(agent)

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

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mon = monitor::type_id::create("mon", this);
        sqr = sequencer::type_id::create("sqr", this);
        drv = driver::type_id::create("drv", this);
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        mon.item_collected_port.connect(item_collected_port);
        drv.seq_item_port.connect(sqr.seq_item_export);
    endfunction
endclass: agent


class agent_out extends uvm_agent;
    driver_out    drv;
    monitor_out   mon;

    uvm_analysis_port #(packet_out) item_collected_port;

    `uvm_component_utils(agent_out)

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

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mon = monitor_out::type_id::create("mon_out", this);
        drv = driver_out::type_id::create("drv_out", this);
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        mon.item_collected_port.connect(item_collected_port);
    endfunction
endclass: agent_out

9.记分板scoreboard

记分板(scoreboard)的主要功能是检查DUT的行为是否正确。记分板接收的实际数据包,来自代理(agent)送入DUT的输入输出,接收的期望数据包,来自参考模型,最后在记分板中比较实际的数据包和期望的数据包。

class comparator #(type T = packet_out) extends uvm_scoreboard;
  typedef comparator #(T) this_type;
  `uvm_component_param_utils(this_type)

  const static string type_name = "comparator #(T)";

  uvm_put_imp #(T, this_type) from_refmod;
  uvm_analysis_imp #(T, this_type) from_dut;

  typedef uvm_built_in_converter #( T ) convert; 

  int m_matches, m_mismatches;
  T exp;
  bit free;
  event compared, end_of_simulation;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    from_refmod = new("from_refmod", this);
    from_dut = new("from_dut", this);
    m_matches = 0;
    m_mismatches = 0;
    exp = new("exp");
    free = 1;
  endfunction

  virtual function string get_type_name();
    return type_name;
  endfunction

  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    @(end_of_simulation);
    phase.drop_objection(this);
  endtask

  virtual task put(T t);
    if(!free) @compared;
    exp.copy(t);
    free = 0;
    
    @compared;
    free = 1;
  endtask

  virtual function bit try_put(T t);
    if(free) begin
      exp.copy(t);
      free = 0;
      return 1;
    end
    else return 0;
  endfunction

  virtual function bit can_put();
    return free;
  endfunction

  virtual function void write(T rec);
    if (free)
      uvm_report_fatal("No expect transaction to compare with", "");
    
    if(!(exp.compare(rec))) begin
      uvm_report_warning("Comparator Mismatch", "");
      m_mismatches++;
    end
    else begin
      uvm_report_info("Comparator Match", "");
      m_matches++;
    end
    
    if(m_matches+m_mismatches > 100)
      -> end_of_simulation;
    
    -> compared;
  endfunction
endclass

10.仿真环境env

仿真环境env是一个分层组件,它将其他相互关联的验证组件组合在一起。通常在仿真环境env中实例化的典型组件有代理(agent)、记分牌(scoreboard),甚至其他仿真环境。
顶层仿真环境封装了所有针对DUT的验证组件。

class env extends uvm_env;
    agent       mst;
    refmod      rfm;
    agent_out   slv;
    comparator #(packet_out) comp;  
    uvm_tlm_analysis_fifo #(packet_in) to_refmod;

    `uvm_component_utils(env)

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

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mst = agent::type_id::create("mst", this);
        slv = agent_out::type_id::create("slv", this);
        rfm = refmod::type_id::create("rfm", this);
        comp = comparator#(packet_out)::type_id::create("comp", this);
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        // Connect MST to FIFO
        mst.item_collected_port.connect(to_refmod.analysis_export);
        
        // Connect FIFO to REFMOD
        rfm.in.connect(to_refmod.get_export);
        
        //Connect scoreboard
        rfm.out.connect(comp.from_refmod);
        slv.item_collected_port.connect(comp.from_dut);
    endfunction

    virtual function void end_of_elaboration_phase(uvm_phase phase);
        super.end_of_elaboration_phase(phase);
    endfunction
  
    virtual function void report_phase(uvm_phase phase);
        super.report_phase(phase);
        `uvm_info(get_type_name(), $sformatf("Reporting matched %0d", comp.m_matches), UVM_NONE)
        if (comp.m_mismatches) begin
            `uvm_error(get_type_name(), $sformatf("Saw %0d mismatched samples", comp.m_mismatches))
        end
    endfunction
endclass

11.测试用例test

测试用例是测试台中最顶层的组件。测试用例通常执行三个主要功能:实例化顶层环境、配置环境(通过工厂覆盖或配置数据库),以及通过在环境中调用序列来进行仿真。
通常,有一个带有UVM环境实例化和其他公共项的基本UVM测试。然后,其他单独的测试将扩展这个基本测试,并以不同的方式配置环境或选择不同的序列运行。
测试用例是在运行时动态实例化的,允许UVM测试台只编译一次,并与许多不同的测试一起运行。

class simple_test extends uvm_test;
  env env_h;
  sequence_in seq;

  `uvm_component_utils(simple_test)

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

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env_h = env::type_id::create("env_h", this);
    seq = sequence_in::type_id::create("seq", this);
  endfunction
 
  task run_phase(uvm_phase phase);
    seq.start(env_h.mst.sqr);
  endtask: run_phase

endclass

12.顶层top

顶层主要实例化DUT模块和测试类,并配置它们之间的连接。

import uvm_pkg::*;
`include "uvm_macros.svh"
`include "./input_if.sv"
`include "./output_if.sv"
`include "./adder.sv"
`include "./packet_in.sv"
`include "./packet_out.sv"
`include "./sequence_in.sv"
`include "./sequencer.sv"
`include "./driver.sv"
`include "./driver_out.sv"
`include "./monitor.sv"
`include "./monitor_out.sv"
`include "./agent.sv"
`include "./agent_out.sv"
`include "./refmod.sv"
`include "./comparator.sv"
`include "./env.sv"
`include "./simple_test.sv"

//Top
module top;
  logic clk;
  logic rst;
  
  initial begin
    clk = 0;
    rst = 1;
    #22 rst = 0;
    
  end
  
  always #5 clk = !clk;
  
  logic [1:0] state;
  
  input_if in(clk, rst);
  output_if out(clk, rst);
  
  adder sum(in, out, state);

  initial begin
    `ifdef INCA
       $recordvars();
    `endif
    `ifdef VCS
        //$vcdpluson;
        $fsdbDumpfile("test.fsdb");
    	$fsdbDumpSVA();
    	$fsdbDumpvars();
        $fsdbDumpMDA();
    `endif
    `ifdef QUESTA
       $wlfdumpvars();
       set_config_int("*", "recording_detail", 1);
    `endif
    
    uvm_config_db#(input_vif)::set(uvm_root::get(), "*.env_h.mst.*", "vif", in);
    uvm_config_db#(output_vif)::set(uvm_root::get(), "*.env_h.slv.*",  "vif", out);
    
    run_test("simple_test");
  end
endmodule

13.参考模型reference mode和直接编程接口(DPI)

SystemVerilog直接编程接口(DPI)是SystemVerilog调用外部语言(如C、c++等)函数的接口。DPI由两个层组成:SystemVerilog层和外语层,它们彼此隔离。下面给出了refmod的代码,以说明DPI的用法。sum()函数在文件external.cpp中定义,一旦在refmod中调用它,就应该在sum()函数的定义之前添加关键字“external C”

#include 
#include 

extern "C" int sum(int a, int b){
  return a+b;
}


import "DPI-C" context function int sum(int a, int b);

class refmod extends uvm_component;
    `uvm_component_utils(refmod)
    
    packet_in tr_in;
    packet_out tr_out;
    integer a, b;
    uvm_get_port #(packet_in) in;
    uvm_put_port #(packet_out) out;
    
    function new(string name = "refmod", uvm_component parent);
        super.new(name, parent);
        in = new("in", this);
        out = new("out", this);
    endfunction
    
    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        tr_out = packet_out::type_id::create("tr_out", this);
    endfunction: build_phase
    
    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        
        forever begin
            in.get(tr_in);
            tr_out.data = sum(tr_in.A, tr_in.B);
            out.put(tr_out);
        end
    endtask: run_phase
endclass: refmod

14.简单的makefile

comp: clean
	g++ -c external.cpp -o external.o
	vcs -full64  -sverilog top.sv -dpi -ntb_opts uvm -debug_pp -timescale=1ns/10ps  external.o -debug_access+cbk -lca -cm tgl+line+fsm+cond+branch

sim:
	./simv +UVM_TR_RECORD +UVM_VERBOSITY=HIGH +UVM_TESTNAME=simple_test -lca -cm tgl+line+fsm+cond+branch

all: comp sim

dbg: clean
	g++ -c external.cpp -o external.o
	vcs -full64  -sverilog top.sv -dpi -ntb_opts uvm -debug_pp -timescale=1ns/10ps  external.o -debug_access+cbk -debug_access+all -kdb -lca
	$ ./simv +UVM_TR_RECORD +UVM_VERBOSITY=HIGH +UVM_TESTNAME=simple_test -gui=verdi

clean:
	rm -rf DVEfiles csrc simv simv.daidir ucli.key .vlogansetup.args .vlogansetup.env .vcs_lib_lock simv.vdb AN.DB vc_hdrs.h *.diag *.vpd *tar.gz external.o inter.fsdb novas.conf novas_dump.log novas.rc test.fsdb verdiLog

view_waves:
	dve &

verdi:
	verdi -sverilog -ntb_opts uvm-1.2 -timescale=1ns/10ps top.sv -f filelist.f +incdir+./ -ssf ./test.fsdb &

urg_gui:
	urg -dir *.vdb -format both -report urgReportALL; \
firefox urgReportALL/tests.html &

comp函数先通过g++编译C程序,再通过VCS进行编译,其中-debug_access+cbk选项对静态网络、寄存器和变量启用基于PLI的回调。-cm tgl+line+fsm+cond+branch选项开启代码翻转覆盖率、行覆盖率、状态机覆盖率、条件覆盖率、分支覆盖率。
sim函数执行VCS仿真。
dbg函数用于VCS和verdi联调,编译里边加入了-debug_access+all -kdb –lca选项,仿真里边加入了-gui=verdi选项。
verdi函数用于启动verdi查看波形。
urg_gui函数用于查看代码覆盖率。


代码附件

上述完整代码下载链接如下:
https://github.com/AFEI1100/easyUVM-master.git

---------------------
作者:hh199203
来源:CSDN
原文:https://blog.csdn.net/hh199203/article/details/110426339
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

  • 9
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值