systemverilog功能覆盖率

一、功能覆盖率

功能覆盖率是和设计意图紧密相连的,专注于功能特性的实现和覆盖,有时也被称为“规范覆盖率”,而代码覆盖率则是衡量设计的实现情况。设想某个代码块在设计中被漏掉的情况。代码覆盖率不能覆盖这个错误,但是功能覆盖率可以。

  • 功能覆盖率

    • 定义:功能覆盖率是衡量验证过程中设计功能是否被充分测试的指标。它关注的是设计的功能需求是否被覆盖,而不是代码本身的执行情况。

    • 目标:确保设计的功能需求(如协议规范、算法逻辑、接口行为等)在验证过程中被充分测试。

    • 实现方式:通过定义覆盖率点(如 covergroupcoverpoint)来监控特定的功能行为。这些覆盖率点通常与设计的功能需求直接相关。

  • 代码覆盖率

    • 定义:代码覆盖率是衡量验证过程中设计代码的哪些部分被执行的指标。它关注的是代码的执行情况,而不是功能需求。

    • 目标:确保设计代码的每一部分(如行、分支、条件、状态机等)在验证过程中被充分执行。

    • 实现方式:通过工具自动收集代码的执行情况,如行覆盖率、分支覆盖率、条件覆盖率等。

SystemVerilog语言本身提供了对功能覆盖率的支持。通过使用covergroupcoverpointcross等关键字,可以在SV代码中定义覆盖率模型,从而在仿真过程中收集功能覆盖率数据。

二、覆盖组的定义和建仓(bin)

2.1 覆盖组定义

covergroup cov;
   coverpoint data;
endgroup:cov
​
covergroup uart_coverage @(posedge clk);
  coverpoint baud_rate {
    bins standard = {9600, 19200, 38400, 57600, 115200};
    bins others = default;
  }
  coverpoint data_bits {
    bins data_width = {5, 6, 7, 8};
    bins others = default;
  }
  cross baud_rate, data_bits {
    bins valid_combinations = (standard with data_width);
  }
endgroup

covergroup 是一个 SystemVerilog 的结构,用于定义覆盖率点。它通常包含以下内容:

  • coverpoint:定义单个信号或表达式的覆盖率点。

  • cross:定义多个信号或表达式的交叉覆盖率点。

  • bins:定义覆盖率点的取值范围或特定值。

coverpoint 用于定义单个信号或表达式的覆盖率点。它可以包含以下内容:

  • bins:定义取值范围或特定值。

  • wildcard:定义通配符范围。

  • ignore_bins:定义忽略的取值范围。

  • illegal_bins:定义非法的取值范围。

coverpoint signal {
  bins low = {0};
  bins high = {1};
  bins others = default;
}

cross 用于定义多个信号或表达式的交叉覆盖率点。它可以包含以下内容:

  • bins:定义交叉取值范围或特定值。

  • wildcard:定义通配符范围。

  • ignore_bins:定义忽略的交叉取值范围。

  • illegal_bins:定义非法的交叉取值范围。

cross signal1, signal2 {
  bins valid_combinations = (signal1 == 0 && signal2 == 0);
}

2.2建仓(bin)

"仓(bin)"用来记录覆盖组中覆盖点的每个数值被捕捉到的次数,是衡量覆盖率的基本单位。

建仓就是在覆盖率点中定义具体的取值范围或特定值。

2.2.1自动创建仓

正常情况下,自动创建仓就是根据覆盖点的值域范围创建,变量位宽为N,则会有2的N次方个仓(不会超过64个)。

在 SystemVerilog 中,auto_bin_max 是一个非常有用的选项,用于限制自动创建的 bins 数目。默认情况下,auto_bin_max 的值为 64,但可以通过显式设置来调整其值。

auto_bin_max 的用法

auto_bin_max 用于控制自动创建的 bins 的最大数目。当覆盖点(coverpoint)的值域超过指定的 auto_bin_max 时,SystemVerilog 会将值域平均分配到 auto_bin_maxbins 中。

为单个 coverpoint 设置 auto_bin_max

covergroup CovPort;
  coverpoint tr.port {
    option.auto_bin_max = 2; // 为 tr.port 设置最大 2 个自动 bin
  }
endgroup

在这个例子中,tr.port 的值域会被自动分成 2 个 bins

为整个 covergroup 设置 auto_bin_max

covergroup CovPort;
  option.auto_bin_max = 2; // 影响该 covergroup 中的所有 coverpoint
  coverpoint tr.port;
  coverpoint tr.data;
endgroup

在这个例子中,auto_bin_max 的设置会应用于 CovPort 中的所有 coverpoint,每个 coverpoint 的值域都会被自动分成 2 个 bins

注意事项

  1. 默认值auto_bin_max 的默认值是 64。

  2. 适用范围auto_bin_max 可以应用于单个 coverpoint 或整个 covergroup

  3. 值域分配:如果值域不能均匀分配到指定的 auto_bin_max 数目中,最后一个 bin 会包含剩余的值。

  4. 手动定义 bins:如果手动定义了 bins,则 auto_bin_max 不会生效。

通过合理设置 auto_bin_max,可以有效控制自动创建的 bins 数目,从而简化覆盖率报告并提高分析效率。

2.2.2动态和静态建仓

bins len[] 是一种动态建仓的方式,表示为 len 创建一个动态的 bins,其具体值在运行时确定。这种方式通常用于覆盖点的值域较大或不确定时,工具会根据实际采样的值动态创建 bins

coverpoint len {
  bins len[] = { [0:$] }; // 动态创建 bins,覆盖 len 的所有可能值
}
  • [0:$] 表示从 0 到最大值的范围。

  • len[] 是动态的,工具会根据实际采样的值动态创建 bins

bins len 是一种静态建仓的方式,表示为 len 创建一个固定的 bins,其值在编译时确定。这种方式通常用于覆盖点的值域较小或已知时。

coverpoint len {
  bins len = {0, 1, 2, 3, 4}; // 静态创建 bins,覆盖 len 的特定值
}
  • len 是一个固定的 bins,只包含指定的值(0, 1, 2, 3, 4)。

  • 覆盖率报告

    • bins len[] 生成的覆盖率报告中,bins 的数目会较多,因为工具会根据实际采样的值动态创建并命令仓。

    • bins len 生成的覆盖率报告中,bins 的数目是固定的,因为工具在编译时就已经确定了。

用一个仓表示剩余的所有值

可以使用 default 关键字。default 用于捕获所有未明确列出的值。

coverpoint len {
  bins specific = {0, 1, 2, 3, 4}; // 明确列出的值
  bins others = default;           // 捕获所有未明确列出的值
}

2.2.3交叉建仓

交叉覆盖点(cross)用于定义多个信号或表达式的组合覆盖率点。交叉覆盖点的 bins 定义方式与单个 coverpoint 类似,但需要考虑多个信号的组合。同时,可以通过 ignore_binsillegal_bins 来忽略或标记某些组合。

监控组合覆盖率:

covergroup my_coverage @(posedge clk);
  coverpoint signal1 {
    bins low = {0};
    bins high = {1};
  }
  coverpoint signal2 {
    bins low = {0};
    bins high = {1};
  }
  cross signal1, signal2 {
    bins both_low = (signal1.low && signal2.low);
    bins both_high = (signal1.high && signal2.high);
    bins mixed = (signal1.low && signal2.high) || (signal1.high && signal2.low);
  }
endgroup

忽略一部分交叉仓

使用 ignore_bins

ignore_bins 用于忽略某些特定的组合,这些组合不会被计入覆盖率统计。

covergroup my_coverage @(posedge clk);
  coverpoint signal1 {
    bins low = {0};
    bins high = {1};
  }
  coverpoint signal2 {
    bins low = {0};
    bins high = {1};
  }
  cross signal1, signal2 {
    bins both_high = (signal1.high && signal2.high);
    bins mixed = (signal1.low && signal2.high) || (signal1.high && signal2.low);
    ignore_bins both_low = (signal1.low && signal2.low);
  }
endgroup

2.2.4覆盖点的权重设置

覆盖点(coverpoint)和交叉覆盖点(cross)的权重可以通过 option.weight 属性来设置。权重用于调整不同覆盖点或交叉覆盖点在总覆盖率计算中的重要性。通过设置权重,可以更灵活地控制覆盖率的计算方式,确保重点覆盖重要的功能场景。

权重的用途

权重用于调整覆盖率计算中各个覆盖点或交叉覆盖点的贡献。权重越高,该覆盖点或交叉覆盖点在总覆盖率中的贡献越大。最终得到不同的总体覆盖率。

设置覆盖点的权重

可以通过 option.weight 属性为每个覆盖点或交叉覆盖点设置权重。

covergroup my_coverage @(posedge clk);
  coverpoint signal1 {
    option.weight = 2; // 设置权重为 2
    bins low = {0};
    bins high = {1};
  }
  coverpoint signal2 {
    option.weight = 1; // 设置权重为 1
    bins low = {0};
    bins high = {1};
  }
endgroup

covergroup my_coverage @(posedge clk);
  coverpoint signal1 {
    bins low = {0};
    bins high = {1};
  }
  coverpoint signal2 {
    bins low = {0};
    bins high = {1};
  }
  cross signal1, signal2 {
    option.weight = 3; // 设置交叉覆盖点的权重为 3
    bins both_low = (signal1.low && signal2.low);
    bins both_high = (signal1.high && signal2.high);
    bins mixed = (signal1.low && signal2.high) || (signal1.high && signal2.low);
  }
endgroup

权重的默认值

如果未显式设置权重,option.weight 的默认值为 1。

权重对覆盖率计算的影响

权重会影响覆盖率的计算结果。覆盖率计算公式通常为:

总覆盖率=∑覆盖点权重∑(覆盖点权重×覆盖点覆盖率)

总覆盖率覆盖点权重覆盖点覆盖率覆盖点权重

2.2.5合理地创建仓

当需求不明确时,在一大堆自动创建的仓里很难寻找到覆盖不到的点。

(1)明确需求

在建仓之前,需要明确设计的功能需求和验证目标。覆盖率点应与设计的功能需求直接相关,确保覆盖所有重要的功能场景。

(2)合理划分 bins

  • 单值 bins

    • 定义特定的取值。例如:

      coverpoint signal {
        bins low = {0};
        bins high = {1};
      }

  • 范围 bins

    • 定义一个取值范围。例如:

      coverpoint baud_rate {
        bins standard = {9600, 19200, 38400, 57600, 115200};
        bins range = [1200:9600];
      }

  • 通配符 bins

    • 使用通配符定义部分匹配的值。例如:

      coverpoint address {
        wildcard bins addr = {8'b0000_????, 8'b1111_????};
      }

  • 默认 bins

    • 捕获未明确列出的其他值。例如:

      coverpoint baud_rate {
        bins standard = {9600, 19200, 38400, 57600, 115200};
        bins others = default;
      }

(3)使用 ignore_bins 和 illegal_bins

  • ignore_bins

    • 定义忽略的取值范围,这些值不会被计入覆盖率统计。例如:

      coverpoint baud_rate {
        bins standard = {9600, 19200, 38400, 57600, 115200};
        ignore_bins invalid = {0, 1};
      }

  • illegal_bins

    • 定义非法的取值范围,这些值表示设计不应出现的情况。如果出现这些值,可能表示设计或测试用例有问题。例如:

      coverpoint baud_rate {
        bins standard = {9600, 19200, 38400, 57600, 115200};
        illegal_bins invalid = {0, 1};
      }

2.3覆盖组定义的位置

基本上可以定义在任意位置,包括program、module、interface、class、package当中。

一般会写在组件中(class)中。

定义位置对代码的可读性、可维护性和功能实现有重要影响。选择合适的定义位置可以提高代码的结构清晰度和验证效率。

(1)在模块(Module)内部定义

如果覆盖组仅与特定模块相关,可以在模块内部定义。这种方式的优点是覆盖组的作用域被限制在模块内部,不会与其他模块的覆盖组冲突。

适用场景

  • 模块内部信号:当覆盖组仅与模块内部的信号相关时,适合在模块内部定义。

  • 局部验证:当验证目标仅限于模块内部时,这种方式可以避免全局污染。

(2)在模块外部定义

如果覆盖组需要在多个模块或测试环境中复用,可以在模块外部定义。这种方式的优点是可以提高代码的复用性,避免重复定义。

适用场景

  • 复用性:当覆盖组需要在多个模块或测试环境中复用时,适合在模块外部定义。

  • 全局验证:当验证目标涉及多个模块时,这种方式可以方便地在不同模块中实例化相同的覆盖组。

(3)在类(Class)中定义

如果覆盖组与特定的类相关,可以在类中定义。这种方式的优点是可以将覆盖组与类的其他逻辑紧密集成,提高代码的封装性。

适用场景

  • 类封装:当覆盖组与类的其他逻辑紧密相关时,适合在类中定义。

  • 面向对象验证:当验证目标涉及类的实例时,这种方式可以方便地在类实例中使用覆盖组。

(4)在测试环境中定义

如果覆盖组主要用于测试环境,可以在测试环境中定义。这种方式的优点是可以集中管理覆盖组,便于在测试环境中动态调整覆盖组的配置。

适用场景

  • 测试环境:当覆盖组主要用于测试环境时,适合在测试环境中定义。

  • 动态调整:当需要在测试环境中动态调整覆盖组的配置时,这种方式可以方便地进行调整。

(5)在包(Package)中定义

如果覆盖组需要在多个文件或模块中复用,可以在包中定义。这种方式的优点是可以提高代码的复用性和可维护性。

适用场景

  • 复用性:当覆盖组需要在多个文件或模块中复用时,适合在包中定义。

  • 项目结构:当项目结构较大,需要集中管理覆盖组时,这种方式可以提高代码的可维护性。

基于uvm结构考虑:

(1)driver中定义

某些模块的driver在系统级集成复用时会被passive掉,这时就无法查看功能点的覆盖情况。

(2)monitor中定义

系统级集成后可以重用

(3)refm中定义

参考模型中既有dut接口信号上的数据,也有寄存器的配置值,还有环境中需要用到的配置参数,也有队数据处理之后的结果

存在的问题是所有代码写在一个文件中将会非常大。

提供以下几种思路来解决这个问题:

1)结构化设计。把参考模型分解成多个,写成多个小的参考模型,在每个参考模型中编写该部分的功能覆盖率。

2)把覆盖组单独写在一个文件中,然后include进来

3)采用uvm提供的callback机制,把功能覆盖率部分的代码单独写在callback文件中。但是实现起来比较复杂,不能使用参考模型当中得到的中间值

4)采用port,将要采样的数据发送到单独一个文件中

(4)testcase中定义

不推荐。只能反复不断运行该用例,直到覆盖率得到100%,不通用不可复用。

综上,推荐把覆盖组写在参考模型中,使用第二种思路。

三、覆盖组的触发

覆盖组(covergroup)的触发是指在仿真过程中何时对覆盖组中的覆盖点(coverpoint)和交叉覆盖点(cross)进行采样。触发机制对于确保覆盖率数据的准确性和完整性至关重要。

3.1使用时钟边沿触发

最常见的方式是通过时钟信号的边沿(如上升沿或下降沿)触发覆盖组的采样。这可以通过在 covergroup 定义中指定采样事件来实现。

covergroup 定义中,可以通过 @(posedge clk)@(negedge clk) 指定采样事件。

covergroup my_coverage @(posedge clk);
  coverpoint signal1 {
    bins low = {0};
    bins high = {1};
  }
  coverpoint signal2 {
    bins low = {0};
    bins high = {1};
  }
endgroup

my_coverage 的采样事件是时钟信号 clk 的上升沿。

3.2使用事件触发

覆盖组也可以通过特定的事件触发采样,例如某个信号的变化或某个条件的满足。

可以通过 $rose$fell$stable 等事件控制语句来触发覆盖组的采样。

covergroup my_coverage @(posedge clk);
  coverpoint signal1 {
    bins low = {0};
    bins high = {1};
  }
  coverpoint signal2 {
    bins low = {0};
    bins high = {1};
  }
  option.per_instance = 1; // 每个实例独立采样
endgroup
​
module my_module;
  logic clk;
  logic signal1;
  logic signal2;
​
  initial begin
    my_coverage cov = new();
    forever @(posedge clk) begin
      if ($rose(signal1)) cov.sample(); // 当 signal1 上升沿时采样
    end
  end
endmodule

覆盖组的采样被限制在 signal1 的上升沿。

3.3显式触发

在某些情况下,可以通过显式调用 sample 方法来触发覆盖组的采样。

可以通过显式调用 sample 方法来触发覆盖组的采样。这种方式提供了最大的灵活性,允许在任何需要的时刻进行采样。

covergroup my_coverage;
  coverpoint signal1 {
    bins low = {0};
    bins high = {1};
  }
  coverpoint signal2 {
    bins low = {0};
    bins high = {1};
  }
endgroup
​
module my_module;
  logic clk;
  logic signal1;
  logic signal2;
  my_coverage cov;
​
  initial begin
    cov = new();
    forever @(posedge clk) begin
      if (some_condition) cov.sample(); // 根据条件显式采样
    end
  end
endmodule

覆盖组的采样由 some_condition 控制。

3.4使用自定义 sample函数触发

自定义方式允许调用 sample 函数时显式地传递参数,而不是依赖于覆盖组内部的信号,并对这些参数进行覆盖率采样。

以在代码中的任何位置显式地调用 sample 方法,并传递需要采样的值。这种方式特别适用于以下场景:

  1. 动态采样:在运行时动态生成或计算的值需要采样。

  2. 条件采样:只有在满足特定条件时才需要采样。

  3. 跨模块采样:在不同的模块或类中生成的值需要采样。

​
covergroup cov with function sample(bit[3:0] da);
    coverpoint da {
        bins low = {0};
        bins mid = {1, 2, 3};
        bins high = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
    }
endgroup : cov
​
module my_module;
    logic [3:0] signal;
    cov my_cov;
​
    initial begin
        my_cov = new();  // 创建覆盖组实例
    end
​
    always @(posedge clk) begin
        if (some_condition) begin
            my_cov.sample(signal);  // 显式调用 sample 方法
        end
    end
endmodule

通过定义带有自定义 sample 函数的 covergroup,可以在代码中的任何位置显式地调用采样方法,并传递需要采样的值。这种方式特别适用于动态采样、条件采样和跨模块采样等场景。

3.5使用回调函数触发

在 SystemVerilog 和验证环境中,回调函数(Callback Function) 是一种允许用户在特定事件发生时执行自定义代码的机制。回调函数通常用于在验证过程中插入用户定义的行为,例如在事务发送、接收或完成时进行数据采样、覆盖率收集或调试信息输出。

回调函数的基本概念

回调函数是一种设计模式,允许用户在某个事件发生时提供一个函数(或方法),该函数会在事件触发时被调用。在验证环境中,回调函数通常用于以下场景:

  1. 事务处理:在事务发送、接收或完成时执行特定操作。

  2. 覆盖率收集:在特定事件发生时收集覆盖率数据。

  3. 调试信息输出:在关键事件发生时输出调试信息。

回调函数的实现方式

在 SystemVerilog 中,回调函数通常通过以下步骤实现:

  1. 定义回调类:创建一个继承自基础回调类的扩展类,并在其中定义回调方法。

  2. 注册回调实例:将回调类的实例注册到验证组件(如 Driver、Monitor 等)中。

  3. 触发回调:当特定事件发生时,验证组件会调用注册的回调方法。

1. 定义回调类

定义一个回调类,继承自基础回调类,并在其中定义覆盖组和回调方法。

class Driver_cbs_coverage extends Driver_cbs;
  covergroup CovPort;
    coverpoint tr.port {
      bins low = {0};
      bins high = {1};
    }
    coverpoint tr.data {
      bins low = {0};
      bins high = {1};
    }
  endgroup
​
  function new();
    CovPort = new();
  endfunction
​
  virtual task post_tx(Transaction tr);
    CovPort.sample();  // 在回调函数中触发采样
  endtask
endclass

2. 注册回调实例

在测试环境中,创建回调类的实例,并将其注册到验证组件中。

program automatic test;
  Environment env;
​
  initial begin
    Driver_cbs_coverage dcc;  // 创建回调类的实例
    env = new();
    env.gen_cfg();
    env.build();
    dcc = new();  // 初始化回调类
    env.drv.cbs.push_back(dcc);  // 将回调实例注册到驱动器的回调队列中
    env.run();
    env.wrap_up();
  end
endprogram

3. 触发回调

当特定事件(如事务传输完成)发生时,验证组件会调用注册的回调方法(如 post_tx),从而触发覆盖组的采样。

回调函数的作用

  1. 灵活性:允许用户在特定事件发生时执行自定义代码,而不依赖于固定的时钟边沿或其他事件。

  2. 精确性:可以在事务完成或其他关键事件发生时精确地触发采样,确保覆盖率数据的准确性。

  3. 可扩展性:可以为不同的组件或事务类型定义多个回调类,从而实现细粒度的覆盖率监控。

回调函数的常见应用场景

  1. 事务发送和接收

    • 在事务发送或接收时进行数据采样或覆盖率收集。

    • 示例:pre_tx(事务发送前)、post_tx(事务发送后)、pre_rx(事务接收前)、post_rx(事务接收后)。

  2. 覆盖率收集

    • 在特定事件发生时触发覆盖组的采样。

    • 在事务完成时收集覆盖率数据。

  3. 调试信息输出

    • 在关键事件发生时输出调试信息。

    • 在事务发送或接收时打印事务内容。

回调函数的实现细节

  • 回调类的定义

    • 回调类通常继承自验证环境中定义的基础回调类(如 Driver_cbsMonitor_cbs 等)。

    • 在回调类中定义覆盖组和回调方法(如 post_txpre_rx 等)。

  • 回调方法的实现

    • 回调方法通常是一个任务(task)或函数(function),在特定事件发生时被调用。

    • 在回调方法中可以执行任何用户定义的操作,如数据采样、覆盖率收集或调试信息输出。

  • 回调实例的注册

    • 将回调类的实例注册到验证组件中,通常通过调用组件的 push_back 方法或类似的注册接口。

    • env.drv.cbs.push_back(dcc);

3.6使用断言进行触发

和用事件触发类似,只是用断言来触发事件,进而触发覆盖组采样

module top;
.....
​
cover property
((@vif.mck) rd_en==1)
->cover_event;
​
covergroup cov @cover_event;
   coverpoint data;
endgroup:cov
​
endmodule
​

四、查看功能覆盖率报告

4.1报告生成

4.1.1编译仿真参数

功能覆盖率不需要增加额外的编译仿真参数。

在编译和仿真时,代码覆盖率需要增加额外参数,确保启用了覆盖率收集选项:

# 编译命令
vcs -cm line+cond+fsm+tgl+branch+assert -cm_dir ./cov_dbs/covdb -cm_name my_test my_design.sv
​
# 仿真命令
simv -cm line+cond+fsm+tgl+branch+assert -cm_dir ./cov_dbs/covdb -cm_name my_test -l sim.log

这些选项用于启用代码覆盖率的收集。

4.1.2覆盖率合并

urg是vcs的覆盖率合并功能,用于合并和生成覆盖率报告。

基本合并命令

urg -dir <path_to_vdb1> -dir <path_to_vdb2> ... -dir <path_to_vdbN> -dbname <output_vdb>
urg -dir ./*.vdb -dbname merged.vdb
  • -dir:指定要合并的覆盖率数据库(.vdb)文件路径。

  • -dbname:指定合并后的输出数据库文件名。

4.1.3生成报告

在合并覆盖率数据后,urg还可以生成详细的覆盖率报告(网页):

urg -dir <merged_vdb> -report <output_report_directory>
urg -dir ./cov_dbs/covdb -dbname my_test -report coverage_report
  • -report:指定生成报告的输出目录。

4.2报告查看

4.2.1使用SystemVerilog内置的覆盖率报告

在SystemVerilog中提供了大量的用于获得coverage的方法,方便了用户进行功能覆盖率的收集查看,比较常见的主要有:$get_coverage、get_coverage和get_inst_coverage,在SystemVerilog中关于这三个方法的描述如下:

$get_coverage:用来获取当前测试平台总体覆盖率,其值由所有的covergroup类型的覆盖率决定;

get_coverage:用来获取当前实例所对应的covergroup的覆盖率;

get_inst_coverage:用来获取当前covergroup实例的覆盖率;

4.2.2. 使用VCS的覆盖率报告

VCS提供了强大的覆盖率分析工具,可以通过命令行选项和图形化界面查看覆盖率数据。

生成的覆盖率报告可以通过以下工具查看:

  • 使用 DVE 查看:

    dve -full64 -covdir cov_path/simv.vdb
  • 使用 Verdi 查看:

    verdi -cov -covdir cov_path/simv.vdb
  • 使用浏览器查看 HTML 报告:

    firefox cov_report/dashboard.html

这些工具会以图形化的方式显示覆盖率情况,方便分析。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值