【IC_verification】SV&UVM

芯片验证分类

  • 根据层次分类
    • 模块验证
      侧重模块本身验证,验证计划的重点是feature和验证架构,然后列出testcase,模块能够覆盖的觉不到下一级验证去覆盖。
      主要内容:检查参数设置、寄存器读写、协议检查、中断和复位、状态机跳转、工作模式覆盖、RAM读写功能边界等
    • 子系统验证
      侧重点在系统的互联性、关注系统的工作模式和复杂场景应用。
      主要内容:中断产生、DMA功能、IP模式功能、Memory读写等
    • 系统验证
      侧重软硬件协同仿真、关键系统路径覆盖、芯片工作模式和测试模式、数据通路和性能等。
      主要内容:基本IP功能、CLK/RESET、IO MUX、多个IP同时工作、程序的启动、工作模式和应用场景测试。

验证语言

  • verilog→systemC→SystemVerilog
  • SystemVerilog:Verilog扩展集,可以完全兼容Verilog。它具有面向对象的特性:封装、继承和多态,同时具有随机化、约束和功能覆盖率等特性。他提供了DPI接口,可以把C/C++的函数导入到SystemVerilog中。
    与C++相比,SystemVerilog提供内存管理机制,用户不用担心内存泄露的问题。
    除此之外,它还支持系统函数$System,用户可以把使用C++写成的参考模型编译成可执行文件,使用Ssystem函数调用.无论是对算法类或非算法类的设计,SystemVerilog都能轻松应对。
  • SystemVerilog是一门优秀的语言,但依然有很多问题需要考虑,比如:

验证平台有哪些基本的组件,每个组件的行为有哪些?
验证中要建立很多测试用例,如何建立、组织的?
验证平台中的数据流和控制流如何分离?
验证平台如何保证是可重用的?

UVM(Universial Verification Methodology)

芯片验证做什么

  • 芯片规格
    根据产品需求,确定芯片功能和性能
    产品和架构师根据客户的规格spec,商定出具体设计解决方案和实现架构,划分出各个模块的文档
  • 测试点分解
    根据spec文档,分解出具体的测试点,如:场景类、功能类、性能类等,分解颗粒度尽量细致。
    case设计原则:一个测试点被一个case覆盖
  • 验证方案
    整个芯片的验证方案一般由验证负责人规划,将设计分成多个子系统,再将子系统分成多个模块
    • 具体验证策略
    • EDA工具和IT资源
    • 项目进度安排
    • 未覆盖的功能,风险评估
  • 验证计划
    定制验证策略,评估验证计划,细化testbench搭建、debug、case开发等时间,大概分为:
    • spec阅读和测试点分解时间
    • 开发环境和调试冒烟测试时间
    • 开发case,完成全部case时间
    • 回归测试和验证报告的时间
  • 搭建验证平台
    • 一般由激励生成器驱动器、采样器、参考模型和计分板组成
    • 从简单的功能开始测试可以通过验证环境之后,再扩展其他功能
    • 经常遇到编译报错、语法错误、预期错误,需要逐一解决
    • 分析报错是由验证环境引起的,还是设计代码错误造成的
  • 测试用例开发
    • 冒烟测试:基本的寄存器读写测试,确保数据流已通
    • 直接用例:根据spec中program流程配置的典型测试
    • 随机用例:用于变量随机,覆盖更多边界,注重约束条件的配置
    • 增补用例:以提高覆盖测试点为目标,增补相应的测试用例

UVM实战

  • 向服务器提交队列–执行仿真
bsub -Is -q dv_regression ./run

打印

  • 在UVM中,你通常会使用uvm_report_info宏来打印信息,而不是使用SystemVerilog的$display语句。这是因为uvm_report_info提供了更多的控制,例如严重性级别、报告抓取和过滤等。如果你想在main_phase中加入一句打印"hello tianyang"的代码,你可以这样写:

virtual task main_phase(uvm_phase phase);
`uvm_info(“MY_DRIVER”, “Hello Tianyang”, UVM_NONE)
// … 其余的main_phase代码 …
endtask

在这里插入图片描述

  • 简单的验证平台
    在这里插入图片描述
  • 典型的UVM验证平台
    在这里插入图片描述

第一章 Systemverilog基础知识

Systemverilog是一种面向对象的编程语言,相对应的是非面向对象的语言(C语言),面向对象语言的重要特点是所有功能都要在类(class)里实现

A.1 结构体的使用

  • 程序设计=算法+数据结构
  • 声明结构体→定义结构变量

A.2 从结构体到类

  • 类将结构体和它相应的函数集合在一起,成为新的数据组织形式。这种形式包括两种成分:

1、来自结构体的数据变量,在类中称为成员变量
2、来自与结构体相对应的函数,成为一个类的接口

  • 类定义好后需要实例化才可以使用,实例化完成即可调用类中的函数
  • new函数被称为“构造函数

A.3 类的封装

  • 类有三大特征:封装、继承、多态
私有变量local类型
  • 私有变量(systemverilog中的local、其他编程语言的private)特点:

1、此变量只能在类的内部由类的函数/任务进行访问
2、在类的外部使用直接引用的方式进行访问会提示出错

在这里插入图片描述
在这里插入图片描述

A.4 类的继承

  • 分析所要解决的问题并找出共性,这些共性构成一个“基类(或者父类)”。在此基础上,将问题分类,不同的分类具有各自的共性,使用这些分类的共性构建成一个“派生类(或者子类)”
class bird extends animal;
	function void fly(); 
	…
	endfunction
endclass
  • 当子类从父类派生后,子类天然地具有了父类所有的特征,父类的成员变量也是子类
    的成员变量,父类的成员函数同时也是子类的成员函数。除了具有父类所有的特征外,子类还可以有自己额外的成员变量和成员函数
  • 如果一个变量是local类型的,那么它是不能被外部直接访问的。如果父类中某成员变量是local类型,那么子类是否可以使用这些变量?答案是否定的
  • 对于父类来说,子类算是"外人",只是算是比较特殊的“外人”而已。如果想访问父类中的成员变量,同时又不想让这些成员变量被外部访问,那么可以将这些变量声明为protected类型:
class animal;
	string name;
	protected int birthday;/*example:20030910*/
	protected string category;/*example: bird, non_bird*/
	protected int food_weight;
	protected int is_healthy;
endclass

在这里插入图片描述

this、super
  • this表示调用的成员是当前类的成员,super表示调用的成员是父类的成员
案例一

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

案例二

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 成员覆盖
    -在这里插入图片描述

A.5 类的多态

A.6 句柄的使用

  • 句柄可以作为形式参数通过方法来完成对象指针的传递,从外部传入方法内部
    在这里插入图片描述
  • 句柄也可以在方法内部首先完成修改,而后再由外部完成使用
  • 动态修改: 在执行程序时,可以在任何时刻为句柄创建新的对象,并将新的指针赋值给句柄

A.7 包(package)的使用

  • 在多个module、interface、program之中共享parameter、data、type、task、function、class等,即利用package(包)的方式实现。

  • 优点:将一簇相关的类组织在了单一的命名空间(namespace)下,使得分属于不同模块验证环境的类来自于不同的package,这样便可以通过package解决类的归属问题 。

  • 重名的类可以归属到不同的package中编译。package是将命名空间分隔开了,如果要使用不同package的同名类,只需要注明使用哪个package
    在这里插入图片描述

包与库的区分

在这里插入图片描述

包的命名规则

  • 加入前缀
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

第二章 UVM验证平台

2.1 systemverilog与UVM的关系

  • SystemVerilog 是一座提供了丰富材料和工具的建筑商店,而 UVM(Universal Verification Methodology)则像是一套建筑蓝图和指南:

如果你想建造一栋房子(即验证一个硬件设计),SystemVerilog 提供了所有必需的原材料:砖块(数据类型)、水泥(操作符)、木材(控制流语句)和工具(如类、接口等)。这些材料和工具允许你自由地设计和建造任何你想要的结构,不管它是简单的小屋还是复杂的摩天大楼。

UVM 则为你提供了一套详细的蓝图和建造指南,告诉你如何使用这些材料和工具来构建一个结构健全、设计合理、并且可以高效完成其功能(即验证任务)的“建筑”(即验证环境)。UVM 的蓝图确保了使用相同的设计模式、构造方法和实践,这样不同的建筑师(验证工程师)可以更容易地理解和协作,即使是在复杂的项目中。

  • 总的来说,在这个类比中,SystemVerilog 是建筑的基础,提供了创建任何东西的可能性;而 UVM 则是完成特定项目(验证工作)的专业知识和指导,帮助你以一种高效和标准化的方式来使用这些基础材料和工具。

2.2 只有driver的验证平台(基于信号级的操作)

在这里插入图片描述

  1. 所有派生自uvm_driver的类的new函数有两个参数
  • string类型的name
  • uvm_component类型的parent
  1. driver所做的事情几乎都在main_phase中完成。
  • UVM由phase来管理验证平台的运行,这些phase统一以XXX_phase来命名,且都有一个类型为uvm_phase、名字为phase的参数。
  • main_phase是uvm_driver中预先定义好的一个任务。因此可以简单认为一个driver=实现其main_driver
uvm_info宏
  • 该宏功能与verilog中display类似,但比display功能强大。其包含:打印信息的物理文件来源、逻辑结点信息(在UVM树中的路径索引)、打印时间、对信息的分类组织及打印的信息。应尽量使用uvn_info宏取代display语句
  • 它有三个参数:第一个参数是字符串,用于把打印的信息分类;第二个参数是字符串,是需要打印的信息;第三个参数是冗余级别。
  • uvm_info的路径索引。在top_tb中通过run_test创建了一个my_driver的实例,那么这个实例的名字是什么呢?答案是uvm_test_top:UVM通过run_test语句创建一个名字为uvm_test_top的实例。读者可以通过把代码语句插入my_driver(build_phase或main_phase)中来验证。
$display("the full name of current component is: %s", get_full_name());
  1. 信息关键三级别:最重要—UVM_LOW、中等—UVM_MEDIUM、不重要—UVM_HIGH
  2. 类的定义与类的实例化
  • 类的定义类似于在纸上写下一纸条文,然后把这些条纹通知给systemverilog的仿真器:验证平台可能会用到这样一个类,请做好准备工作。
	classs A;
	...
	endclass
  • 类的实例化在于通过new函数()来通知systemverilog的仿真器:请创建一个A的实例。仿真器接到new的指令后,就会在内存中划分一块空间(在划分前,会首先检查是否已经预先定义过这个类)在已经定义过的情况下按照定义中所指定的“条纹”分配空间,并且把这块空间的指针返回给a_inst。之后可以通过a_inst来查看类中各个成员变量,调用成员函数/任务等
    开辟空间
    进入new
    初始化
    返回
	A a_inst;
	a_inst = new();
  • :对于大部分类,如果只定义而不实例化,是没有任何意义的;如果不定义就直接实例化,仿真器会报错。

2.2.2 加入factory机制

uvm_component_utils宏
  • 所有派生自uvm_component及其派生类的类都应该使用uvm_component_utils宏注册。
  1. factory机制被集成在这个宏中。这个宏的作用之一局势将my_driver登记在UVM这张表中,这张表是factory功能实现的基础。只要在定义一个新的类时使用这个宏,就相当与把这个类注册到了这张表中
  2. 只有在类定义时声明了这个宏,才能使用这个功能,这个宏起到了注册的作用,只有经过注册的类,才能使用这个功能。
  • 在UVM验证平台中,只要一个类使用uvm_component_utils注册且此类被实例化了,那么这个类的main_phase就会自动被调用。这也就是为什么上一节中会强调实现一个driver等于实现其main_phase。所以,在driver中,最重要的就是实现main_phase
  • 使用run_test语句替换原有的my_driver实例化及main_phase的显示调用。一个run_test语句会创建一个my_driver的实例,并且会自动调用my_driver的main_phase。仔细观察run_test语句,会发现传递给它的是一个字符串。UVM根据这个字符串创建了其所代表类的一个实例
  • UVM通过run_test语句实例化了一个脱离了top_tb层次结构的实例,建立了一个新的层次结构。
module top_tb;
	...
initial begin
   run_test("my_driver");
end

endmodule

2.2.3 加入objection机制

  • drop_objection语句当成是finish函数的替代者,只是在drop_objection语句之前必须先调用raise_objection语句,raise_objection和drop_objection总是成对出现。
task my_driver::main_phase(uvm_phase phase);
   phase.raise_objection(this);
   `uvm_info("my_driver", "main_phase is called", UVM_LOW);
   top_tb.rxd <= 8'b0; 
   top_tb.rx_dv <= 1'b0;
   while(!top_tb.rst_n)
      @(posedge top_tb.clk);
   for(int i = 0; i < 256; i++)begin
      @(posedge top_tb.clk);
      top_tb.rxd <= $urandom_range(0, 255);
      top_tb.rx_dv <= 1'b1;
      `uvm_info("my_driver", "data is drived", UVM_LOW);
   end
   @(posedge top_tb.clk);
   top_tb.rx_dv <= 1'b0;
   phase.drop_objection(this);
endtask
  • raise_objection语句必须在main_phase中第一个消耗仿真时间的语句之前。如$display语句是不消耗仿真时间的,这些语句可以放在raise_objection之前,但是类似@(posedge top.clk)等语句是要消耗仿真时间的。
  • 按照如下的方式使用raise_objection是无法起到作用的:
task my_driver::main_phase(uvm_phase phase);
	@(posedge top_tb.clk);
	phase.raise_objection(this);
	...
	phase.drop_objection(this);
endtask

2.2.4 加入virtual interface

  • 绝对路径的使用大大减弱了验证平台的可移植性,应该尽量杜绝在验证平台中使用绝对路径。
  • 避免绝对路径的方法:
  1. 修改宏定义。但是仅限只调用一个宏文件的情况,若调用了两个宏文件的端口则不适用了
  2. 在SystemVerilog中使用interface来连接验证平台与DUT的端口。

步骤:1、定义interface
2、在top_tb中实例化DUT时,就可以直接使用

my_if input_if(clk, rst_n);
my_if output_if(clk, rst_n);

dut my_dut(.clk(clk),
           .rst_n(rst_n),
           .rxd(input_if.data),
           .rx_dv(input_if.valid),
           .txd(output_if.data),
           .tx_en(output_if.valid));

3、在driver中使用interface。由于my_driver是一个类,故应先在类中声明vif,再在main_phase中用vif代替绝对路径驱动其中信号

class my_driver extends uvm_driver;

   virtual my_if vif;//先在类中声明vif
	...
task my_driver::main_phase(uvm_phase phase);
   phase.raise_objection(this);
   `uvm_info("my_driver", "main_phase is called", UVM_LOW);
   vif.data <= 8'b0; //在main_phase中用vif代替绝对路径驱动其中信号
   vif.valid <= 1'b0;
   while(!vif.rst_n)
      @(posedge vif.clk);
   for(int i = 0; i < 256; i++)begin
      @(posedge vif.clk);
      vif.data <= $urandom_range(0, 255);
      vif.valid <= 1'b1;
      `uvm_info("my_driver", "data is drived", UVM_LOW);
   end
   @(posedge vif.clk);
   vif.valid <= 1'b0;
   phase.drop_objection(this);
endtask
config_db机制

4、UVM通过run_test语句实例化了一个脱离了top_tb层次结构的实例,建立了一个新的层次结构。对于这种脱离了top_tb层次结构,同时又期望在top_tb中对其进行某些操作的实例,UVM引进了config_db机制。

  • 在config_db机制中,分为set和get两步操作。set操作理解成是“寄信”,而get则相当于“收信”。
//在top_tb中执行set操作
initial begin
   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end
//在my_driver中执行get操作
   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      `uvm_info("my_driver", "build_phase is called", UVM_LOW);
      if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
         `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
   endfunction
  • config_db的set和get函数都有四个参数:

1、set函数的第二个参数表示的是路径索引。
(无论传递给run_test的参数是什么,创建的实例的名字都为uvm_test_top。由于set操作的目标是my_driver,所以set函数的第二个参数就是uvm_test_top。)
2、这两个函数的第三个参数必须完全一致。
3、set函数的第四个参数表示要将哪个interface通过config_db传递给my_driver
4、get函数的第四个参数表示把得到的interface传递给哪个my_driver的成员变量

  • 可以向my_driver中“寄”许多信。

这里引入了build_phase

UVM内建函数——build_phase
  • 是UVM内建函数,UVM启动后自动执行。
  • 执行顺序:new函数→build_phase→main_phase
  • 主要通过config_db的set和get操作传递数据,以及实例化成员变量等
  • 这里需要加入super.build_phase语句,因为在其父类的build phase中执行了一些必要的操作,这里必须显式地调用并执行它。
  • build _phase与main_phase不同点:

build_phase是一个函数phase,而main_phase是一个任务phase
build_phase是不消耗仿真时间的。build_phase总是在仿真时间(Stime函数打印出的时间为0时执行。

uvm_fatal宏
  • uvm_fatal宏是一个类似于uvm_info的宏,但是它只有两个参数,这两个参数与uvm_info宏的前两个参数的意义完全一样

1、与uvm_info宏不同的是,当它打印第二个参数所示的信息后,会直接调用Verilog的finish函数来结束仿真
2、uvm_fatal的出现表示验证平台出现了重大问题而无法继续下去,必须停止仿真并做相应的检查
3、对于uvm_fatal来说,uvm_info中出现的第三个参数的冗余度级别是完全没有意义的,只要是uvm_fatal打印的信息,就一定是非常关键的,所以无需设置第三个参数。

2.3 为验证平台加入各个组件

在这里插入图片描述
类和对象的描述

类和对象的描述

在这里插入图片描述

2.3.1 加入transaction

  • 软件的“盒子”,对应的硬件–module
  • 引入reference model、monitor、scoreboard等验证平台的其他组件。在这些组件之间,信息的传递是基于transaction的
  • 物理协议中的数据交换都是以帧或者包为单位的,通常在一帧或者一个包中要定义好各项参数,每个包的大小不一样。

以太网为例,每个包的大小至少是64byte。这个包中要包括源地址、目的地址、包的类型、整个包的CRC校验数据等。

  • 一笔transaction就是一个包。在不同的验证平台中,会有不同的transaction。
post_randomize函数
  • post_randomize是SystemVerilog中提供的一个函数,当某个类的实例的randomize函数被调用后,post_randomize会紧随其后无条件地被调用。
uvm_sequence_item基类
  • 一是my_transaction的基类是uvm_sequence_item:

在UVM中,所有的transaction都要从uvm_sequence_item派生,只有从uvm_sequence_item派生的transaction才可以使用后文讲述的UVM中强大的sequence机制

  • 二是这里没有使用uvm_component_utils宏来实现factory机制,而是使用了uvm_object_utils

1、从本质上来说,my_transaction与my_driver是有区别的,在整个仿真期间,my_driver是一直存在的,my_transaction不同,它有生命周期。
2、my_transaction的生命周期:它在仿真的某一时间产生,经过driver驱动,再经过reference model处理,最终由scoreboard比较完成后,其生命周期就结束了

  • 这种类都是派生自uvm_object或者uvm_object的派生类,uvm_sequence_item的祖先就是uvm_object。UVM中具有这种特征的类都要使用uvm_object_utils宏来实现。

2.3.2 加入env

  • 在验证平台中的什么位置例化reference model、scoreboard等?
  • 解决方案:引入一个容器类,在这个容器类中实例化driver、monitor、reference model和scoreboard等。在调用run_test时,传递的参数不再是my_driver,而是这
    个容器类,即让UVM自动创建这个容器类的实例。在UVM中,这个容器类称为uvm_env
class my_env extends uvm_env;

   my_driver drv;

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

   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      drv = my_driver::type_id::create("drv", this); 
   endfunction

   `uvm_component_utils(my_env)
endclass
  • 容器类在仿真中也是一直存在的
  • 使用uvm_component_utils宏来实现factory的注册
  • drv的实例化:只有使用factory机制注册过的类才能使用这种方式实例化
  • 验证平台中的组件在实例化时都应该使用type_name::type_id::create的方式。
  • 在drv实例化时,传递了两个参数,一个是名字drv,另外一个是this指针,表示my_env
  • my_driver在uvm_env中实例化,所以my_driver的父结点(parent)就是my_env。通过parent的形式,UVM建立起了树形的组织结构。

1、这种树形的组织结构中,由run_test创建的实例是树根(这里是my_env),并且树根的名字是固定的,为uvm_test_top
2、在树根之后会生长出枝叶(这里只有my_driver),长出枝叶的过程需要在my_env的build_phase中手动实现
3、无论是树根还是树叶,都必须由uvm_component或者其派生类继承而来

  • 当加入了my_env后,整个验证平台中存在两个build_phase,一个是my_env的,一个是my_driver的。那么这两个build_phase按照何种顺序执行呢?

在UVM的树形结构中,build_phase的执行遵照从树根到树叶的顺序,即先执行my_env的build_phase,再执行my_driver的build_phase。当把整棵树的build_phase都执行完毕后,再执行后面的phase。

  • my_driver在验证平台中的层次结构发生了变化,它一跃从树根变成了树叶,所以在top_tb中使用config_db机制传递virtual my_if时,要改变相应的路径

2.3.3 加入monitor

  • 功能:验证平台中实现监测DUT行为的组件是monitor。driver负责把transaction级别的数据转变成DUT的端口级别,并驱动给DUT,monitor的行为与其相对,用于收集DUT的端口数据,并将其转换成transaction交给后续的组件如reference model、scoreboard等处理。

需要注意的几点:
1、所有的monitor类应该派生自uvm_monitor
2、与driver类似,在my_monitor中也需要有一个virtual my_if
3、uvm_monitor在整个仿真中是一直存在的,所以它是一个component,要使用uvm_component_utils宏注册
4、由于monitor需要时刻收集数据,永不停歇,所以在main_phase中使用while(1)循环来实现这一目的

在这里插入图片描述

2.3.4 封装成agent

  • driver和monitor两者处理同一种协议,在一套规则下做着不同的事情,UVM通常将两者封装在一起,成为一个agent。不同agent就代表不同协议
class my_agent extends uvm_agent ;
   my_driver     drv;
   my_monitor    mon;
   
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction 
   
   extern virtual function void build_phase(uvm_phase phase);
   extern virtual function void connect_phase(uvm_phase phase);

   `uvm_component_utils(my_agent)
endclass 
  • 所有的agent都要派生自uvm_agent类,且其本身是一个component,应该使用uvm_
    component_utils宏来实现factory注册。
  • is_active是uvm_agent的一个成员变量
枚举类型变量uvm_active_passive_enum
  • uvm_active_passive_enum是一个枚举类型变量,这个枚举变量仅有两个值:UVM_PASSIVE和UVM_ACTIVE。

1、在uvm_agent中,is_active的值默认为UVM_ACTIVE,在这种模式下,是需要实例化driver的
2、输出端口上不需要驱动任何信号,只需要监测信号。在这种情况下,端口上是只需要monitor的,所以driver可以不用实例化。
在这里插入图片描述

class my_env extends uvm_env;

   my_agent  i_agt;//声明i_agt和o_agt
   my_agent  o_agt;
...
   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      i_agt = my_agent::type_id::create("i_agt", this);//在my_env的build_phase中对i_agt和o_agt进行实例化
      o_agt = my_agent::type_id::create("o_agt", this);
      i_agt.is_active = UVM_ACTIVE;//指定各自的工作模式是active模式还是passive模式
      o_agt.is_active = UVM_PASSIVE;
   endfunction

   `uvm_component_utils(my_env)
endclass

在这里插入图片描述

  • 上图需要注意的几点:

1、只有uvm_component才能作为树的结点,像my_transaction这种使用uvm_object_utils宏实现的类是不能作为UVM树的结点的
2、在my_env的build_phase中,创建i_agt和o_agt的实例是在build_ phase中;在agent中,创建driver和monitor的实例也是在build_phase中

2.3.7 加入field automation

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值