XX-工程积累-SS

XSCH的sequence的实现架构:一共是2层;

L1层seq的body:

  1. create或new L2层的seq;
  2. fork     join里面调用2个task(get_tr_from_slave_port和send_rsp)
  3. get_tr_from_slave_port:主要是在while(1)线程里从slave_port获取DUT发出的req,push_back到一个队列xx_queue里(由L1seq声明的)
  4. send_rsp:基于for+fork;会产生多个线程根据req_tr让L2 seq去发送rsp_tr;不同channel之间会进行仲裁;每一个线程里面是一个while(1);
    for (int port_cos_idx =0; port_cos_idx< i_process_num; port_cos_idx++) begin
    
        fork
            automatic int port_cos_idx_temp = port_cos_idx;
            begin
                while(1) begin
                    wait (xx_queue[idx].size() > 0) ;
                    assert(L2_seq[idx].randomize() with { mi_xx == xx ;});
                    L2_seq[idx].mo_req_tr = xx_queue[idx].pop_front;
                    L2_seq[idx].start(m_sequencer,this,优先级大小);
                end
            end
        join_none
    
    end
    wait fork;

force 与 uvm_hdl_force:

How does uvm_hdl_force different from force? | Verification Academyicon-default.png?t=N7T8https://verificationacademy.com/forums/uvm/how-does-uvmhdlforce-different-force

The difference is that SystemVerilog's force construct searches for pathnames at the compilation time and symbolically binds the path to the assignment statement, the same as it does for any kind of hierarchical reference in any kind of statement.

The UVM's uvm_hdl_force() method uses the C VPI interface to search for signal using a string pathname at runtime. Once it finds the signal via a string loop-up, it uses another C VPI routine that applies a force. In order to lookup a signal using a string pathname, certain optimizations need to be turned off that keeps the signal visible, and the string names have to be stored as well. There may be tool specific options to make this work, and this forum is not for tool specific help.

force:是SV语言的,它一般使用在initial块里面;

initial begin
    force tb.dut.force_bit = 1;
    #10ns;
    release tb.dut.force_bit;
end

uvm_hdl_force(string path, uvm_hdl_data_t value):是UVM定义的一种force,先通过C VPI接口实现信号的查找和强制赋值(赋值成功会返回1,否则为0);可以用在一些UVM的Phase里面


uvm_hdl_force ("ex_top.ent_lvl1.ent_lvl2.signalname",1'b1);

uvm_hdl_release: 

如果运行有错:可以加上该编译选项:-debug_access+all

层次路径不能使用变量

top_tb.U_DUT.xxxx_linklist_table_inst0.orgnize_gen[0].genblk1.sxx_ip_0.mem_array[1023:0];
//right;mem_array是数组,里面索引可以用变量,
//orgnize_gen[0]是路径不可以用变量,可以采用uvm_hdl_force,字符串路径方式实现

top_tb.U_DUT.xxxx_linklist_table_inst0.orgnize_gen[i_var].genblk1.sxx_ip_0.mem_array[1023:0];//error

循环force,通过字符串方式实现:

    s_data_pathname = “xx.a.b.c.array”
    for(int i=0; i<1024; i++)  begin
        s_data_pathname_mem_array = {s_data_pathname ,$sformatf("[%0d]",i)};
        b_initial_success = uvm_hdl_deposit(s_data_pathname_mem_array,linklist_data);
        if(!b_initial_success) begin
            `uvm_fatal("uvm_hdl_deposit","uvm_hdl_deposit linklist_table fail!");
        end
    end

1、在引号“”里面使用宏:双引号前面都要加一个 ` 以及宏前面也要加一个`

`"`宏`"

 2、在define里面使用拼接符``xx``,把xx原样放入该位置;(类似外面输入什么,就在这个位置填什么)


UVM1.2 VS UVM1.1(TODO)

新方法uvm_sequence_base::set_automatic_phase_objection将在在sequence执行前后自动调用raise_objection和drop_objection,从而避免在一种常见情况下手动调用raise / drop_objection。

变量uvm_sequence_base::starting_phase已弃用,并由两个新方法set_starting_phase和get_starting_phase取代,这些方法阻止在phase中间修改starting_phase。此更改与UVM 1.1不向后兼容,但变量starting_phase虽然已弃用,但尚未从基类库中删除。
 


1、data = 'b1;//不管data位宽多少,全部bit位赋值为1,适配左端;

2、data = 1'b1;//就是data赋值为1,适配右端;

四态值->两态值的转换(四态类型赋值给两态类型)
四态值两态值
00
11
X0
Z0

活用while(1) 与 do while:每一次窗口循环后,再去执行操作;

    while(1) begin
        int     i_tmp_time_cnt=0;//unit:ns
        do begin //wait rsp cumulative
            #1ns; // 
            i_tmp_time_cnt++;
        end  while(i_tmp_time_cnt < your.cfg.time_window);
        do something
    end

活用队列:一个专门用来进行接收的队列received_queue,一个专门用来实现FIFO/LIFO/OUT_OF_ORDER的队列operate_queue,一个专门用来存放发送数据的队列send_rsp_queue:本质上是实现数据的接收、处理和发送相互独立;

received_queue按需求去存储队列,然后会被轮询进行一些处理后,再发送给operate_queue,operate_queue进行一些处理后再发给send_rsp_queue;send_rsp_queue只负责存储和输出rsp(里面的顺序实际由operate_queue的相关逻辑去实现);


数组声明的细节:

int    i_array[0:15];//16个整数[0]...[15];

int    i_array[16];//16个整数[0]...[15]

int    i_array[15:0];//16个整数[15]...[0]


SV:

父类有一个变量A,有一个函数f_A,里面使用了A;

子类里面也声明了一个同名的A,子类调用父类的f_A时,使用的是父类的A变量; 


UVM 打印信息的优先级

打印信息宏:

UVM 源代码提供了四个打印宏,用于方便用户将各种信息进行打印,进而辅助仿真与调试。
主要有:
(1) `uvm_info("","",UVM_*)

(2) `uvm_warning("","")

(3) `uvm_error("","")

(4) `uvm_fatal("","")

打印一个uvm_component的成员:(只有注册了的才会被打印)

`uvm_info(get_type_name(), $sformatf("recieve_data(A2B_req): \n%s",xx_tr.sprint()), UVM_MEDIUM);
 


寄存器测试:屏蔽RC和RO域段的检查,(reg类型应该是只有RW,RO和WO三种类型;不一定,可能是get_access这个方法只做这些匹配)

    uvm_reg            rgs[$];
    uvm_reg_field      flds[$];

    ralf.get_registers(rgs);
     //ralf是 reg_modle
     foreach(rgs[ii]) begin
         rgs[ii].get_fields(flds);
         foreach (flds[ij]) begin
            if ((flds[ij].get_access() == "RO") || (flds[ij].get_access() == "RC")) begin // RO will response err, pslverr signal will assert, apb adapter bus2reg will assert UVM_NOT_OK for status. if you want to not check pslverr, you shall constraint svt_apb_master_transaction::pslverr_enable = 0.
                 uvm_resource_db#(bit)::set({"REG::",rgs[ii].get_full_name()}, {"NO_REG_ACCESS_TEST"}, 1);
                 break;
             end
         end
     end

get_registers:获取block下面所有uvm_reg?


上升沿与下降沿的场景: 


数组覆盖率收集: (功能覆盖率)

sCreating new instances of a covergroup using an array. | Verification Academyicon-default.png?t=N7T8https://verificationacademy.com/forums/coverage/creating-new-instances-covergroup-using-array.Array of covergroup wrapper objects. Problems. - UVM SystemVerilog Discussions - Accellera Systems Initiative Forumsicon-default.png?t=N7T8https://forums.accellera.org/topic/6720-array-of-covergroup-wrapper-objects-problems/上面两个链接是一种方法;

irun:可以多次例化covergroup。
vcs:不能多次例化covergroup。需要class内写一个covergroup,class例化多份的方式,实现多次例化的效果。

方案2:
covergroup可以多写一个参数:with function sample(int index, xxx),在covergroup中index为0~179的范围,index和你原来的信号cross一下,就达到所有深度的采集了。

方案2具有更好的灵活性,也不依赖工具;故选择了采用方案2去收集;

1、在fcov_xaction声明两个变量:mb_A2cos_map(采样参数) 和 mi_A2cos_array_idx(采样数组维度);

2、在xxx_reg_entry_fcov声明对应的covergroup:如下图;(里面再去声明2个coverpoint,对应数组维度和数组里面的数值,再对2个coverpoint 进行cross) 

3、在xxx_reg_entry_fcov的new函数实例化:xxx_reg_array_180_covergroup   = new(); 在xxx_reg_entry_fcov的sample函数创建对应的case分支去采样;

4、、在参考模型中用foreach去采样; 


基于变量选择其中一部分域段:

reg [200:0]     q_cnt_array;
int       qid_array_up_i=14,qid_array_down_idx=0;
q_cnt_array[qid_array_down_idx+:15];//可以选取[14:0]的15bit
q_cnt_array[qid_array_up_idx:qid_array_down_idx];//会有编译错误,应该是不支持

0-1-X-Z之间的逻辑运算 :


uvm1.2:寄存器模型的write/read函数不在支持后门去读取寄存器了(不管是reg/reg_filed);需要用poke/peek后门读取;


重载某一个driver:(前提:父子关系)(UVM这里的override本质是:句柄还是父类的句柄,只是指向了重载的子类对象;实践中遇到父类的task没有加virtual导致,重载的子类对象没起作用,因为还是会指向父类的task

作者:郑退之
链接:《UVM实战》笔记 UVM中的factory机制(重载)_技术交流_牛客网
来源:牛客网

只要是面向对象的语言,都有重载功能。

使用set_type_override_by_type函数可以实现两种不同类型之间的重载,

set_inst_override_by_type函数(并不是希望把验证平台中的A类型全部替换成B类型,而只是替换其中的某一部分)

复制代码

1

2

3

4

5

6

7

8

9

10

11

extern static 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(string relative_inst_path,   

  //其中第一个参数是相对路径,

                        uvm_object_wrapper original_type,  

                          //第二个参数是被重载的类型,父类

                        uvm_object_wrapper override_type);

无论是set_type_override_by_type还是set_inst_override_by_type,它们的参数都是一个 uvm_object_wrapper 型的类型参数,这种参数通过 xxx::get_type() 的形式获得。

复制代码

1

2

set_type_override_by_type(bird::get_type(), parrot::get_type());

//factory机制式的实例化方式

UVM还提供了另外一种简单的方法来替换这种晦涩的写法:字符串。

复制代码

1

2

 set_type_override("bird""parrot")                             //==set_type_override_by_type

set_inst_override("env.o_agt.mon""my_driver""new_monitor"); //==set_inst_overr

注意事项*4:

·第一,无论是重载的类(parrot)还是被重载的类(bird),都要在定义时注册到factory机制中。

·第二,被重载的类(bird)在实例化时,要使用factory机制式的实例化方式,而不能使用传统的new方式

·第三,最重要的是,重载的类(parrot)要与被重载的类(bird)有派生关系。重载的类必须派生自被重载的类,被重载的类必须是重载类的父类。

·第四,component与object之间互相不能重载。虽然uvm_component是派生自uvm_object,但是这两者的血缘关系太远了,远到根本不能重载。从两者的new参数的函数就可以看出来,二者互相重载时,多出来的一个parent参数会使factory机制无所适从。 


uvm1.2中怎么在sequence中raise objection:get_starting_phase()


 伴随线程(我取的名字)

fork
    while(1) begin  进行检查什么的 end
    while(1) begin  搜索结束条件什么的 end
join_any

XX一套典型TLM接口

通信建立和调用依然是:定义trans、创建端口、实现方法、建立连接

注意端口的例化方式只能是new(string name, uvm_component parent);不能用factory机制例化,会编译报错!!!

要在env的connect_phase:做好对应的连接:this.m_xx_agent[0].mon.ap.connect(this.m_mon2rm_fifo[0].analysis_export);请求发起者在connect左边,被动承担者在connect括号里面;

xx_agt_monitor:声明的端口:uvm_analysis_port #(uvm_sequence_item)  ap    ;

collect_one_pkt(tr);收集tr
ap.write(tr);通过端口写出去

xx_rm:声明端口:

uvm_blocking_get_port #(uvm_sequence_item)  in_port[5];从monitor拿数据;

uvm_analysis_port #(uvm_sequence_item) out_port[5];把数据发给scb做比对;

this.in_port[0].get(tr);从monitor拿数据;

$cast(exp_tr, tr);//拿到数据要进行类型转换,转换成用户具体的tr类型;主要是端口声明这里tr是uvm_sequence_item为了通用性用的是基类,用户需要自己进行cast转换这一步,如果是用户的tr,就不需要这步了;

放到一个while(1)循环里面:把数据发给scb做比对;

wait(this.o_dut2A_xaction_queue[0].size>0);//参考模型把期望数据放到队列里面
tr = this.o_dut2A_xaction_queue[0].pop_front();
tr.do_pack(packer);
this.out_port[1].write(tr);

xx_scb:声明的端口

uvm_blocking_get_port #(uvm_sequence_item) exp_port[5];//port:request的发送方,initiator
uvm_blocking_get_port #(uvm_sequence_item) act_port[5];

this.act_port[port_id].get(tr);// get transaction from DUT//get(output T t):initiator要求向target索取trans;

xx_env:声明端口:uvm_tlm_analysis_fifo:作为中装站:

uvm_tlm_analysis_fifo #(uvm_sequence_item)        m_mon2rm_fifo[5]

uvm_tlm_analysis_fifo #(uvm_sequence_item)        m_rm2checker_fifo;

uvm_tlm_analysis_fifo #(uvm_sequence_item)        m_mon2checker_fifo;

uvm_tlm_analysis_fifo #(uvm_sequence_item)        m_mon2vsqr_fifo;

this.m_xx_agent[0].mon.ap.connect(this.m_mon2rm_fifo[0].analysis_export);

this.rm.in_port[0].connect(this.m_mon2rm_fifo[0].blocking_get_export);


参考:UVM:事务级建模(Transaction Level Modeling, TLM)1.0 通信标准_Starry丶的博客-CSDN博客_事务级建模

注意TLM仅仅适用于component之间的连接;
发起通信请求request的组件就是initiator,响应request的组件就是target。
发起的request是通信请求,不代表请求获取transaction。
initiator可能是请求发送事务,也可能是请求获取事务,但一定是initiator先发起的通信请求。
数据流:put(T t)、get(output T t)、peek(output T t) 与 transport
表示initiator和target之间的trans流向。
put(T t):initiator要求 向target发送trans。
get(output T t):initiator要求 向target索取trans。
peek(output T t):initiator要求 向target索取trans的复制。
transport:initiator要求 先向target发送trans,再向target索取trans,一次性动作。(怎么用的?)
控制流:port 、export 与 imp
表示从initiator向target发起的request传输方向。
 port:表示request的发送方。initiator中的端口常为port。
 export:表示request的中转方。在Initiator和target的中间层端口常为export
 imp:即import,表示request的接收方。target中的端口常为imp。
request方向说的是控制流,trans方向是数据流,不等价!!!

根据uvm_阻塞性_数据流_控制流组合成端口类,例如一些端口类uvm_blocking_put_port、uvm_nonblocking_get_imp等等。

确定TLM通信的initiator component 和 target component,并且initiator、target和中间层,在各自的build_phase中使用new函数,分别例化port端口、相同类型的imp端口 和 相同类型的export端口。
在target实现该端口的数据流方法
initiator和target的上层,在connect_phase使用connect方法建立连接。
经过上述步骤就建立了端口间的通信,之后initiator就可以在run_phase调用相应的数据流方法了,注意initiator调用的数据流方法是在target内实现的数据流方法!!
 

数据流方法名冲突

当initiator和target有两对同类型端口,那么target实现的数据流方法名字是一样的,所以出现了数据流方法的冲突,UVM给出了解决方案。
在这里插入图片描述

如上图所示,有两对阻塞put端口,类型相同,数据流方法都是put。UVM给出的解决冲突的方法是,在全局通过宏注册后缀,就可以例化加了后缀的新类型端口了,详细如下:

还是承接上面一节代码

class comp1 extends uvm_component;
	`uvm_component_utils(comp1)
	uvm_blocking_put_port#(trans1) bp_port1;				
	uvm_blocking_put_port#(trans2) bp_port2;				
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		bp_port1 = new("bp_port1",this);			
		bp_port2 = new("bp_port2",this);			
		...
	endfunction
	task run_phase(uvm_phase phase);
		trans1 t1;
		repeat(5) begin
			t1 = trans1::type_id_create("t1");
			assert(t1.randomize());
			bp_port1.put(t1);					//注意了,此处依然是put!!不是put1、也不是put2!!!
			bp_port2.put(t1);
		end
	endtask
endclass
`uvm_blocking_put_imp_decl(1);						//宏注册后缀,之后就有了uvm_blocking_put_imp1#(T, IMP)类型,数据流方法也变成put1
`uvm_blocking_put_imp_decl(2);						//又注册了后缀2
class comp2 extends uvm_component;
	`uvm_component_utils(comp2)
	uvm_blocking_put_imp1#(trans1,comp2) bp_imp1;	//看后缀	
	uvm_blocking_put_imp2#(trans1,comp2) bp_imp2;		
	trans1 tq[$];										
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		bp_imp1 = new("bp_imp1",this);				
		bp_imp2 = new("bp_imp2",this);				
		...
	endfunction
	task put1(trans1 t);						//看后缀	
		tq.push_back(t);
	endtask
	task put2(trans1 t);
		tq.push_back(t);
	endtask
endclass	

所以,可以在全局`uvm_阻塞性_数据流_控制流_decl(后缀)进行注册,可使用加了后缀但类型不变的新类型例化对象,相应的数据流方法名也要加上后缀

例如uvm_nonblocking_get_peek_imp#(trans,scoreboard)就可以注册成`uvm_nonblocking_get_peek_imp_decl(_scb)
创建时就是uvm_nonblocking_get_peek_imp_scb#(trans,scoreboard) ngp_imp;
需要实现的方法就是function bit try_get_scb(output trans t);function bit can_get_scb();function bit try_peek_scb(output trans t);function bit can_peek_scb();

但注意initiator调用的数据流方法名称不会改变!!!,声明依旧是uvm_nonblocking_get_peek_port#(trans) ngp_port;,调用的方法依然是function bit try_get(output trans t);function bit can_get();function bit try_peek(output trans t);function bit can_peek();

3. 一(port)对多(imp)单向数据流 端口通信

刚刚描述的uvm_阻塞性_数据流_控制流式的端口是只能一对一连接,如果想一对多通信,可使用analysis 端口。

3.1. Analysis 端口

可以实现一个port与多个imp进行通信,数据流方法是基于观察者的设计模式,initiator仅仅是将数据广播出去。

阻塞性:无

由于是广播,只要将数据广播出去就可以,并不等待target的响应。

数据流:function void write(T t)

只有一种write数据流方法,仅将数据广播出去即可,所以target端需要实现write方法

● write(T t):initiator将数据发送至每一个与其通信的target,注意是函数不能耗时!!

控制流:port、export和imp

依然是三种,对应着uvm_analysis_port、uvm_analysis_export和uvm_analysis_imp三种analysis端口

数据流控制流单一端口类方法原型
writeportuvm_analysis_port#(T)function void write(T t)
exportuvm_analysis_export#(T)function void write(T t)
impuvm_analysis_imp#(T, IMP)function void write(T t)

同样地,也有相应的宏用于类名后缀的注册`uvm_analysis_imp_decl(SFX)

4. uvm_tlm_analysis_fifo #(T)

无论是analysis端口还是普通端口,比较麻烦的点在于一个是target要实现数据流方法,还有就是在实际应用中一定会有一个component被作为target处于被动方,显然被动方难以根据自己的节奏主动获取数据,必须时刻判断自己缓存有新的数据了才能进行。

是否有类似于Systemverilog中的mailbox可使两个component都能主动获取数据呢?

有的,就是FIFO。UVM提供的继承自uvm_component的类uvm_tlm_analysis_fifo类实例化后就是一种FIFO,它可以让通信的两个component都作为initiator,这来自于FIFO上的众多imp型端口

4.1. Analysis FIFO 的端口

如下图所示

在这里插入图片描述

从上面图片可以看出

● Analysis FIFO含有uvm_analysis_imp类对象analysis_export,支持一对多广播通信

● Analysis FIFO含有uvm_analysis_port类对象put_ap和get_ap,说明Anylsis FIFO也可作为initiator向其他target做广播。

与Analysis FIFO相连的initiator每次调用put或get数据流方法时,FIFO就会调用write广播。

除了put_ap和get_ap,其他端口虽都以_export结尾,但实际上都是imp类型的。

//...\questasim64_2020.1\verilog_src\uvm-1.2\src\tlm1\uvm_tlm_fifos.svh
class uvm_tlm_fifo #(type T=int) extends uvm_tlm_fifo_base #(T);
  ...
  virtual task put( input T t );
    m.put( t );
    put_ap.write( t );					//广播
  endtask

  virtual task get( output T t );
    m_pending_blocked_gets++;
    m.get( t );
    m_pending_blocked_gets--;
    get_ap.write( t );					//广播
  endtask
  ...
endclass

class uvm_tlm_analysis_fifo #(type T = int) extends uvm_tlm_fifo #(T);

建立通信和调用

方法很简单,需要发送trans的component内创建put_port端口,需要接受trans的component内创建get_port端口,再在上层component创建一个Anylsis FIFO,将三者连接即可。

例如 monitor将数据广播给scoreboard。

class monitor extends uvm_monitor;
	`uvm_component_utils(monitor)
	virtual reg_intf vif;
	uvm_analysis_port#(trans) a_port;
	...
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		ap = new("ap",this);
		if(!uvm_config_db#(virtual reg_intf)::get(this,"","vif",vif))
			uvm_report_error("INTF",$sformatf("%s gets interface failed",get_full_name()),UVM_NONE);
	endfunction
	task run_phase(uvm_phase phase);
		forever begin
			trans t;
			t = trans::type_id::create("t");
			@(posedge vif.clk iff(vif.mon_ck.cmd != IDLE))
			t.cmd = vif.mon_ck.cmd;
			t.cmd_addr = vif.mon_ck.cmd_addr;
			a_port.write(t);
		end
	endtask
endclass
class scoreboard extends uvm_scoreboard;
	`uvm_component_utils(scoreboard)
	uvm_blocking_get_port#(trans) bg_port;
	...
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		bg_port = new("bg_port",this);
	endfunction
	task run_phase(uvm_phase phase);
		forever begin
			trans t;
			bg_port.get(t);
			...
		end
	endtask
endclass

class env extends uvm_env;
	`uvm_component_utils(env)
	uvm_tlm_analysis_fifo#(trans) agt_scb_fifo;
	agent agt;
	scoreboard scb;
	...
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		agt_scb_fifo = new("agt_scb_fifo",this);						//并非tb组件,不涉及factory重载,直接new了
		...
	endfunction
	function void connect_phase(uvm_phase phase);
		agt.mon.a_port.connect(agt_scb_fifo.analysis_export);			//与FIFO连接
		scb.bg_port.connect(agt_scb_fifo.blocking_get_export);
		...
	endfunction
endclass

4.2. Analysis FIFO 的方法

可调用一些方法查看FIFO内部数据。

//...\questasim64_2020.1\verilog_src\uvm-1.2\src\tlm1\uvm_tlm_fifos.svh
class uvm_tlm_fifo #(type T=int) extends uvm_tlm_fifo_base #(T);

  function new(string name, uvm_component parent = null, int size = 1); //创建函数,size可指定FIFO容量,0为无限大小
  virtual function int used();			//返回FIFO中存储的trans的个数
  virtual function bit is_empty();		//FIFO是否空
  virtual function bit is_full();		//FIFO是否满
  virtual function void flush();		//清空FIFO
  endfunction

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值