uvm-1.2 examples —— 2.1 hello_world

2.1 hello_world



前言

本文以uvm-1.2/examples/simple/hello_world为例,通过代码了解UVM中的一些用法。通过这个例子可以基本了解以下知识点:

  • $timeformat的用法
  • config_db机制
  • TLM通信
  • faild automation机制
  • $cast的用法
  • semaphore的用法

一、基本介绍

hello_world这个例子的整体数据流如下图所示,例化了p1,p2,c,f四个组件,并通过TLM1.0通信,将其连接起来。其中,p1直接连接到了c,p2先经过一个uvm_tlm_fifo,再到c。
在这里插入图片描述
代码结构如下所示:
.
├── consumer.sv
├── hello_world.sv
├── Makefile.ius
├── Makefile.questa
├── Makefile.vcs
├── packet.sv
├── producer.sv
└── top.sv

UVM树形结构如下所示。

UVM_INFO ../../../src/base/uvm_root.svh(579) @ 0 ns: reporter [UVMTOP] UVM testbench topology:
-------------------------------------------------------
Name                 Type                   Size  Value
-------------------------------------------------------
top                  top                    -     @342 
  consumer           consumer #(T)          -     @438 
    in               uvm_blocking_put_imp   -     @446 
    out              uvm_get_port           -     @455 
    count            integral               32    'd1  
  fifo               uvm_tlm_fifo #(T)      -     @394 
    get_ap           uvm_analysis_port      -     @429 
    get_peek_export  uvm_get_peek_imp       -     @411 
    put_ap           uvm_analysis_port      -     @420 
    put_export       uvm_put_imp            -     @402 
  producer1          producer #(T)          -     @350 
    out              uvm_blocking_put_port  -     @362 
    proto            packet                 -     @358 
    num_packets      integral               32    'd2  
    count            integral               32    'd0  
  producer2          producer #(T)          -     @372 
    out              uvm_blocking_put_port  -     @384 
    proto            packet                 -     @380 
    num_packets      integral               32    'd4  
    count            integral               32    'd0  
-------------------------------------------------------

二、代码分析

下面自顶向下分别介绍每个文件。

1、hello_world.sv

hello_world.sv文件中的代码如下所示。

`timescale 1ns / 1ns

module hello_world;

  import uvm_pkg::*;
  `include "uvm_macros.svh"
  
  `include "packet.sv"
  `include "producer.sv"
  `include "consumer.sv"
  `include "top.sv"
 
  top mytop;

  initial begin
    $timeformat(-9,0," ns",5);
    //uvm_default_table_printer.knobs.name_width=20;
    //uvm_default_table_printer.knobs.type_width=50;
    //uvm_default_table_printer.knobs.size_width=10;
    //uvm_default_table_printer.knobs.value_width=14;
    uvm_config_int::set(null, "top.producer1","num_packets",2);
    uvm_config_int::set(null, "top.producer2","num_packets",4);
    //uvm_config_int::set(null, "*","recording_detail",UVM_NONE);
    uvm_config_int::set(null, "*","recording_detail",UVM_LOW);
    //uvm_default_printer = uvm_default_tree_printer;
    //uvm_default_printer.knobs.reference=0;
    mytop = new("top"); 
    //uvm_default_table_printer.knobs.type_width=20;
    run_test();
  end
endmodule

第1行,定义了这个模块的时间标度,斜杠前面表示时间的基本单位,斜杠后面表示时间的精度。
第3和31行,例化了hello_world这个模块。
第4到12行,导入了UVM的包和用到的文件。
第13行,声明了top,top的实现是在top.sv中。
第15行, $timeformat的格式描述如下:

$timeformat(units_number, precision_number, suffix_string, minimum_field_wdith)。

  • units_number 是 0 到-15 之间的整数值,表示打印的时间值的单位:0 表示秒,-3 表示毫秒,-6 表示微秒,-9 表示纳秒, -12 表示皮秒, -15 表示飞秒,中间值也可以使用:例如-10表示以100ps为单位。其默认值为`timescalse所设置的仿真时间单位。
  • precision_number 是在打印时间值时,小数点后保留的位数。其默认值为0。
  • suffix_string 是在时间值后面打印的一个后缀字符串。其默认值为空字符串。
  • MinFieldWidth 是时间值字符串与后缀字符串合起来的这部分字符串的最小长度,若这部分字符串不足这个长度,则在这部分字符串之前补空格。其默认值为20。

第21,22,24行,调用的是UVM中的config_db机制,往相应的组件传递值。set函寄信,get函数收信,通常成对出现。第一个参数何第二个参数联合起来组成目标路径,与此路径相符合的目标才能收信,第三个参数表示一个记号,记号对的上才能收信,第四个参数是需要传递的值。具体可以参考张强《UVM实战》3.5.2节。

uvm_config_int
Convenience type for uvm_config_db#(uvm_bitstream_t)
typedef uvm_config_db#(uvm_bitstream_t) uvm_config_int;

第27行,调用top的构造函数new,将前面13行声明的top实体化。
第29行,执行仿真。

2、top.sv

top.sv的代码如下所示。

class top extends uvm_component;

  producer #(packet) p1;
  producer #(packet) p2;
  uvm_tlm_fifo #(packet) f;
  consumer #(packet) c;

  `uvm_component_utils(top)

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

    p1 = new("producer1",this);
    p2 = new("producer2",this);
    f  = new("fifo",this);
    c  = new("consumer",this);

    p1.out.connect( c.in );
    p2.out.connect( f.blocking_put_export );
    c.out.connect( f.get_export );
  endfunction

  task run_phase(uvm_phase phase);
     phase.raise_objection(this);
     uvm_top.print_topology();
     #1us;
     phase.drop_objection(this);
  endtask
endclass

第1行,top继承自uvm_component。
第3到6行,分别声明了p1,p2,f和c,带的参数都是packet。
第8行,将top注册到uvm的factory机制中,一般UVM的组件都需要注册到uvm factory中去,这样方便后期需要改动这个组件时进行重载。
第10到21行,重新定义了构造函数new,这个函数在hello_world.sv文件的27行被调用。在这个函数中,13到16行,分别对上面3到6行中声明的组件实体化,18到20行,完成这几个组件的连接。
第23到28行,定义了run_phase。UVM验证平台的运行,通过自动调用这些phase来实现。phase的介绍,可以参考《UVM实战》第5.1节。24和27行分别对应“举手”和“放下手”,调用的是objection机制,具体可以参考《UVM实战》第5.2节。第25行打印整个当前这个测试用例的UVM树形结构。

3、packet.sv

packet.sv的代码如下所示。

class packet extends uvm_transaction;

`ifndef NO_RAND
  rand
`endif
  int addr;

  `uvm_object_utils_begin(packet)
    `uvm_field_int(addr, UVM_ALL_ON)
  `uvm_object_utils_end

  constraint c { addr >= 0 && addr < 'h100; }


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

endclass

第1行,packet继承自uvm_transaction,如果说继承自uvm_component的组件相当于人体的骨架,那么继承自uvm_transaction的组件就相当于与血液,它能在各个组件之间流动。
第3到6行,定义了一个变量,这个变量可以通过NO_RAND这个宏,来实现是否是随机变量的切换。
第8到10行,调用的是UVM中的field automation机制,简单的说,就是将这个变量注册到UVM中去,带来的好处是,UVM可以为这个变量提供自带的一些简单函数,例如copy(),compare(),clone(),print()等。更多关于field automation机制可以参考《UVM实战》3.3节。
15到17行是这个类的构造函数,取名叫“packet”。

4、producer.sv

producer.sv的代码如下所示。

class producer #(type T=packet) extends uvm_component;

  uvm_blocking_put_port #(T) out;

  function new(string name, uvm_component parent=null);
    super.new(name,parent);
    out = new("out",this);
    void'(uvm_config_int::get(this, "", "num_packets", this.num_packets));
  endfunction

  protected T   proto       = new;
  protected int num_packets = 1;
  protected int count       = 0;

  `uvm_component_utils_begin(producer #(T))
    `uvm_field_object(proto, UVM_ALL_ON + UVM_REFERENCE)
    `uvm_field_int(num_packets, UVM_ALL_ON + UVM_DEC)
    `uvm_field_int(count, UVM_ALL_ON + UVM_DEC + UVM_READONLY)
  `uvm_component_utils_end

  task run_phase(uvm_phase phase);
    T p;
    string image, num;

    `uvm_info("producer", "Starting.", UVM_MEDIUM)

    for (count =0; count < num_packets; count++) begin

      $cast(p, proto.clone());

      num.itoa(count);
      p.set_name({get_name(),"-",num});

      p.set_initiator(this);

      if (uvm_verbosity'(recording_detail)!=UVM_NONE)
        p.enable_recording(get_tr_stream("packet_stream"));

      void'(p.randomize());

      `uvm_info("producer", $sformatf("Sending %s",p.get_name()), UVM_MEDIUM)

      if(uvm_report_enabled(UVM_HIGH,UVM_INFO,""))
        p.print();

      out.put(p);

      #10;

    end

    `uvm_info("producer", "Exiting.", UVM_MEDIUM)

  endtask

endclass

第1行,producer 继承自uvm_component,并且带类型参数T=packet。
第3行,声明了一个TLM1.0中,带参数的port。
5到9行,定义了producer 这个类的构造函数。其中,第6行把name传进去,第7行实体化第3行声明的port,第8行是收信,接收来自hello_world.sv中第21和22行传递过来的值。
11到13行,其中,11行声明并实体化了一个packet的包,12和13行分别声明并初始化了变量,其中声明前面的修饰关键字包括:public,local和protected。

public: 默认为public,子类和类外皆可访问。
local:表示的成员或方法只对该类的对象可见,子类以及类外不可见。
protected: 表示的成员或方法对该类以及子类可见,对类外不可见。

15到19行,UVM中的field automation机制,这个在前面有介绍。

UVM_REFERENCE Only object handles are copied.
UVM_BIN Selects binary (%b) format.
UVM_READONLY Do not allow setting of this field from the set_*_local
methods.

21到54行,定义run_phase这个task。
22行声明了一个packet的变量,T的参数类型就是packet,在第一行作为参数传下来的;
23行声明了两个字符串变量;
25行这里调用了UVM中的一个打印的宏,具体介绍可以参考《UVM实战》3.4节。
27到50行,利用for循环实现了发包。
29行这里先是利用packet中的field automation机制,调用clone()函数,实现了proto的克隆,再利用$cast函数,将克隆好的包赋给p,这里因为是同一个类型,也可以直接赋值,等价于p=proto.clone()。

关于int = $cast(dest_var, source_exp)
1、将source_exp强制赋值给dest_var,成功返回1,失败返回0;
2、子类可以直接赋值给父类,因为子类派生自父类,子类本质上就是父类;
3、父类必须通过$case才能强制转换成子类,因为子类中包含父类中不具备的方法和属性,有可能转换失败;

31行itoa这个函数将整数转换成字符串;
32行设置这个包的名字;
34行为这个包加入一个initiator变量,通过这个变量可以区别包的发起者是哪个组件;
36行将recording_detail这个变量做了强制类型转换;
39行对packet这个包进行随机;
41行打印这个包的名字;
43行uvm_report_enabled这个函数,比较第一个参数与第二个参数的等级,如果高则返回1,否则返回0;
44行打印这个包,由于这个包在packet.sv中,有变量已经注册到了field automation机制中,所以这里能够直接调用这个打印函数,打印格式如下;

UVM_INFO producer.sv(41) @ 0 ns: top.producer1 [producer] Sending producer1-0
---------------------------------------
Name         Type           Size  Value
---------------------------------------
producer1-0  packet         -     @484 
  addr       integral       32    'h66 
  initiator  producer #(T)  -1    @350 
---------------------------------------

46行将这个包发送出去,这里用到了TLM1.0机制,这个put函数是在接收方定义的。对于例化的p1来说,put函数是在c中定义的,对于p2来说,put函数是在f中内置的。
52行打印一下Exiting,便于环境的debug。

5、consumer.sv

consumer.sv的代码如下所示。

class consumer #(type T=packet) extends uvm_component;

  uvm_blocking_put_imp #(T,consumer #(T)) in;
  uvm_get_port #(T) out;

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

  protected int count=0;
  local semaphore lock = new(1);

  `uvm_component_utils_begin(consumer #(T))
    `uvm_field_int(count,UVM_ALL_ON + UVM_READONLY + UVM_DEC)
  `uvm_component_utils_end

  task run_phase(uvm_phase phase);
    T p;
    while(out.size()) begin
      out.get(p);
      put(p);
    end
  endtask

  task put (T p);
    lock.get();
    count++;
//    void'(accept_tr(p));
    accept_tr(p);
    #10;
    void'(begin_tr(p));
    #30; 
    end_tr(p); 
    `uvm_info("consumer", $sformatf("Received %0s local_count=%0d",p.get_name(),count), UVM_MEDIUM)
    if(uvm_report_enabled(UVM_HIGH,UVM_INFO,""))
      p.print();
    lock.put();
  endtask 
endclass

第3和4行,声明了两个TLM1.0的组件;
6到10行,在构造函数中实体化了3,4行声明的两个TLM组件;
12行,定义一个变量用于记录接收到包的个数;
13行,声明并初始化了一个旗语,存放了一个key,定义这个旗语的目的,是为了防止,这个文件的23行和p1同时调用put函数。

semaphore是一俩小车,这个车可以自定义有多少个钥匙,也就是key,多个线程都可以去通过拿一把key来使用这辆车,但这辆车同一时间只能被一个人用,并且一定要在代码中确保车用完了之后使用者把钥匙还回去了,不然下一个人可能就没法用这个车了。

15到17行利用field automation机制,将count变量注册到UVM中;
19到25行是run_phase,在这个task中会去判断out是否为空,out中的数据来自p2,通过uvm_tlm_fifo传递,如果不为空,则调用get函数,将uvm_tlm_fifo中的数据取过来,再调用put函数。
27到40行是put函数,前面定义旗语的时候提到,这个put函数会被两个地方调用,一个是p1,因为p1直接连接到了c,另一个是c的23行。不管哪个先调用put函数,28行先利用旗语取出其中的key,将其锁定。29行count++表示接受到了一个包。31行在这个包中插入接收到这个包的时间accept_time。33行在这个包中插入开始时间begin_time。35行在这个包中插入结束时间end_time。36行打印接收到的包的名字和count。38行打印这个包,打印格式如下。39行将旗语的key送回去,释放对这个函数的占用。

UVM_INFO consumer.sv(57) @ 40 ns: top.consumer [consumer] Received producer1-0 local_count=1
-----------------------------------------
Name           Type           Size  Value
-----------------------------------------
producer1-0    packet         -     @484 
  addr         integral       32    'h66 
  accept_time  time           64    0 ns 
  begin_time   time           64    10 ns
  end_time     time           64    40 ns
  initiator    producer #(T)  -1    @350 
-----------------------------------------

6、Makefile.vcs

共用的makefile文件如下:

x: all

#
# Include file for VCS Makefiles
#

UVM_VERBOSITY =	UVM_HIGH

#
# Note that +acc and +vpi have an adverse impact on performance
# and should not be used unless necessary.
#
# They are used here because they are required by some examples
# (backdoor register accesses).
#


TEST = /usr/bin/test
N_ERRS = 0
N_FATALS = 0

VCS =	vcs -sverilog -timescale=1ns/1ns \
	+acc +vpi \
	+incdir+$(UVM_HOME)/src $(UVM_HOME)/src/uvm.sv \
	$(UVM_HOME)/src/dpi/uvm_dpi.cc -CFLAGS -DVCS

SIMV = 	./simv +UVM_VERBOSITY=$(UVM_VERBOSITY) -l vcs.log

URG  = urg -format text -dir simv.vdb

CHECK = \
	@$(TEST) \( `grep -c 'UVM_ERROR :    $(N_ERRS)' vcs.log` -eq 1 \) -a \
		 \( `grep -c 'UVM_FATAL :    $(N_FATALS)' vcs.log` -eq 1 \)

clean:
	rm -rf *~ core csrc simv* vc_hdrs.h ucli.key urg* *.log

hello_world这个case的makefile如下,其中在第3行调用了上面共用的makefile文件。

UVM_HOME	= ../../..

include ../../Makefile.vcs


all: comp run

comp:
	$(VCS) +incdir+. \
		hello_world.sv

run:
	$(SIMV)
	$(CHECK)

7、仿真结果

仿真结果如下。

UVM_INFO @ 0 ns: reporter [RNTST] Running test ...
UVM_INFO producer.sv(46) @ 0 ns: top.producer1 [producer] Starting.
UVM_INFO producer.sv(62) @ 0 ns: top.producer1 [producer] Sending producer1-0
---------------------------------------
Name         Type           Size  Value
---------------------------------------
producer1-0  packet         -     @484 
  addr       integral       32    'h66 
  initiator  producer #(T)  -1    @350 
---------------------------------------
UVM_INFO producer.sv(46) @ 0 ns: top.producer2 [producer] Starting.
UVM_INFO producer.sv(62) @ 0 ns: top.producer2 [producer] Sending producer2-0
---------------------------------------
Name         Type           Size  Value
---------------------------------------
producer2-0  packet         -     @498 
  addr       integral       32    'h4d 
  initiator  producer #(T)  -1    @372 
---------------------------------------
UVM_INFO ../../../src/base/uvm_root.svh(579) @ 0 ns: reporter [UVMTOP] UVM testbench topology:
------------------------------------------------------------
Name                    Type                   Size  Value  
------------------------------------------------------------
top                     top                    -     @342   
  consumer              consumer #(T)          -     @438   
    in                  uvm_blocking_put_imp   -     @446   
      recording_detail  uvm_verbosity          32    UVM_LOW
    out                 uvm_get_port           -     @455   
      recording_detail  uvm_verbosity          32    UVM_LOW
    count               integral               32    'd1    
    recording_detail    uvm_verbosity          32    UVM_LOW
  fifo                  uvm_tlm_fifo #(T)      -     @394   
    get_ap              uvm_analysis_port      -     @429   
      recording_detail  uvm_verbosity          32    UVM_LOW
    get_peek_export     uvm_get_peek_imp       -     @411   
      recording_detail  uvm_verbosity          32    UVM_LOW
    put_ap              uvm_analysis_port      -     @420   
      recording_detail  uvm_verbosity          32    UVM_LOW
    put_export          uvm_put_imp            -     @402   
      recording_detail  uvm_verbosity          32    UVM_LOW
    recording_detail    uvm_verbosity          32    UVM_LOW
  producer1             producer #(T)          -     @350   
    out                 uvm_blocking_put_port  -     @362   
      recording_detail  uvm_verbosity          32    UVM_LOW
    proto               packet                 -     @358   
    num_packets         integral               32    'd2    
    count               integral               32    'd0    
    recording_detail    uvm_verbosity          32    UVM_LOW
  producer2             producer #(T)          -     @372   
    out                 uvm_blocking_put_port  -     @384   
      recording_detail  uvm_verbosity          32    UVM_LOW
    proto               packet                 -     @380   
    num_packets         integral               32    'd4    
    count               integral               32    'd0    
    recording_detail    uvm_verbosity          32    UVM_LOW
  recording_detail      uvm_verbosity          32    UVM_LOW
------------------------------------------------------------

UVM_INFO producer.sv(62) @ 10 ns: top.producer2 [producer] Sending producer2-1
---------------------------------------
Name         Type           Size  Value
---------------------------------------
producer2-1  packet         -     @529 
  addr       integral       32    'h78 
  initiator  producer #(T)  -1    @372 
---------------------------------------
UVM_INFO producer.sv(62) @ 20 ns: top.producer2 [producer] Sending producer2-2
---------------------------------------
Name         Type           Size  Value
---------------------------------------
producer2-2  packet         -     @535 
  addr       integral       32    'h2  
  initiator  producer #(T)  -1    @372 
---------------------------------------
UVM_INFO consumer.sv(57) @ 40 ns: top.consumer [consumer] Received producer1-0 local_count=1
-----------------------------------------
Name           Type           Size  Value
-----------------------------------------
producer1-0    packet         -     @484 
  addr         integral       32    'h66 
  accept_time  time           64    0 ns 
  begin_time   time           64    10 ns
  end_time     time           64    40 ns
  initiator    producer #(T)  -1    @350 
-----------------------------------------
UVM_INFO producer.sv(62) @ 50 ns: top.producer1 [producer] Sending producer1-1
---------------------------------------
Name         Type           Size  Value
---------------------------------------
producer1-1  packet         -     @547 
  addr       integral       32    'h66 
  initiator  producer #(T)  -1    @350 
---------------------------------------
UVM_INFO consumer.sv(57) @ 80 ns: top.consumer [consumer] Received producer2-0 local_count=2
-----------------------------------------
Name           Type           Size  Value
-----------------------------------------
producer2-0    packet         -     @498 
  addr         integral       32    'h4d 
  accept_time  time           64    40 ns
  begin_time   time           64    50 ns
  end_time     time           64    80 ns
  initiator    producer #(T)  -1    @372 
-----------------------------------------
UVM_INFO producer.sv(62) @ 90 ns: top.producer2 [producer] Sending producer2-3
---------------------------------------
Name         Type           Size  Value
---------------------------------------
producer2-3  packet         -     @568 
  addr       integral       32    'h96 
  initiator  producer #(T)  -1    @372 
---------------------------------------
UVM_INFO consumer.sv(57) @ 120 ns: top.consumer [consumer] Received producer1-1 local_count=3
------------------------------------------
Name           Type           Size  Value 
------------------------------------------
producer1-1    packet         -     @547  
  addr         integral       32    'h66  
  accept_time  time           64    80 ns 
  begin_time   time           64    90 ns 
  end_time     time           64    120 ns
  initiator    producer #(T)  -1    @350  
------------------------------------------
UVM_INFO producer.sv(73) @ 130 ns: top.producer1 [producer] Exiting.
UVM_INFO consumer.sv(57) @ 160 ns: top.consumer [consumer] Received producer2-1 local_count=4
------------------------------------------
Name           Type           Size  Value 
------------------------------------------
producer2-1    packet         -     @529  
  addr         integral       32    'h78  
  accept_time  time           64    120 ns
  begin_time   time           64    130 ns
  end_time     time           64    160 ns
  initiator    producer #(T)  -1    @372  
------------------------------------------
UVM_INFO producer.sv(73) @ 170 ns: top.producer2 [producer] Exiting.
UVM_INFO consumer.sv(57) @ 200 ns: top.consumer [consumer] Received producer2-2 local_count=5
------------------------------------------
Name           Type           Size  Value 
------------------------------------------
producer2-2    packet         -     @535  
  addr         integral       32    'h2   
  accept_time  time           64    160 ns
  begin_time   time           64    170 ns
  end_time     time           64    200 ns
  initiator    producer #(T)  -1    @372  
------------------------------------------
UVM_INFO consumer.sv(57) @ 240 ns: top.consumer [consumer] Received producer2-3 local_count=6
------------------------------------------
Name           Type           Size  Value 
------------------------------------------
producer2-3    packet         -     @568  
  addr         integral       32    'h96  
  accept_time  time           64    200 ns
  begin_time   time           64    210 ns
  end_time     time           64    240 ns
  initiator    producer #(T)  -1    @372  
------------------------------------------
UVM_INFO ../../../src/base/uvm_objection.svh(1270) @ 1000 ns: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO ../../../src/base/uvm_report_server.svh(847) @ 1000 ns: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO :   20
UVM_WARNING :    0
UVM_ERROR :    0
UVM_FATAL :    0
** Report counts by id
[RNTST]     1
[TEST_DONE]     1
[UVM/RELNOTES]     1
[UVMTOP]     1
[consumer]     6
[producer]    10


总结

通过仿真结果可以看到,由于p1直接连接到了c,所以p1发出的包最先被c接收到,但由于c中put函数利用了旗语进行分时管理,p1发出的包只有在接收后,才能再次发出,而p2发出的包由于经过了uvm_tlm_fifo再到的c,所以哪怕下游c的put函数来不及处理,但是p2仍然可以发出不止一个包,可以看到fifo的深度默认为3。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值