数字IC验证

系统理论

1、 验证任务和目标
  • 模块级(module)-> 子系统级(subsystem)-> 系统级(chip)
  • 按时 保质 低耗
2、验证的周期
  • 创建验证计划
  • 开发验证环境
  • 给测试激励
  • 当给到一定数量的激励测试的时候,就可以准备回归测试了,就是将已有的所有测试序列都执行一次
  • 流片后要逃逸分析
3、功能描述文档
  • 系统工程师写的,里面包括了
    • 接口信息:如果是标准接口就不需要写的太详细,如果是自定义的就要详细写出时序
    • 结构信息:将模块进一步细分为各个功能组件,以及包含组件之间的逻辑关系
    • 交互信息:模块会被集成到更高一级的子系统中,所以在文档之中,要包含模块交互的示意图,在必要的时候也要给出交互信号之间的准确时序
4、验证的方法
  • 动态仿真(dynamic simulation)
    • 通过测试序列和激励生成器给如待验设计适当的激励,判断输出是否符合预期
    • 要配合仿真波形
    • 进一步划分为
      • 定向测试(directed test)
      • 随机测试 (random test)
      • 参考模型检查 (reference model check)
      • 断言检查 (assertion check)
  • 静态检查(formal check)
    • 通过工具辅助,发现设计中存在的问题
    • 进一步划分
      • 语法检查(syntax check)
      • 语义检查(linting check)
        • 在设计的可行性上面做深入检查
        • 可能产生的X值以及受其影响的设计部分
      • 跨时钟域检查(CDC,Cross-clock Domain Check)
      • 形式验证(formal verification)
        • 等价检查(EC, Equivalent Check):用来保证两个电路的行为是等价的,用来检查不同抽象级的电路是否一致,如RTL和网表
        • 属性检查(PC, Property Check)又称为模型检查(MC,Model Check)。电路的行为通过验证语言来描述其属性(property),随后通过静态方式来证明在所有状态空间下都满足该条件,否则举出反例(counter example)来证明设计行为不符合属性描述(property description)
  • 虚拟模型(virtual prototype)
    • 建模准备
      • 芯片定义
      • 高级别结构
      • 用户用例
      • RTL模块
    • 虚拟模型
      • 将功能表述转换为System C模型
      • 将RTL转换为System C模型
      • 结构和性能分析
    • 平台开发
      • 虚拟平台
      • 结构、性能以及功耗报告
      • 驱动和固件开发
      • OS、中间件和应用层开发
      • 反馈给硬件设计
  • 硬件加速(hardware acceleration)
  • 电源功耗(power consumption)
    • 功能验证(PA, Power Aware):主要包括有UPF(Unified Power Format)或者是CPF(Comment Power Format),通过与仿真器结合,模拟电源域的开关进行设计检查
    • 功能预测与优化:通过第三方功耗分析工具,结合仿真数据(FSDB/VCD/SAIF),进行功耗预测,并且给出分析结果
  • 性能评估(performance evaluation):还是一个新颖的概念,没有统一的标准
    • 负载测试(load testing)
    • 压力测试(stress testing)
    • 浸泡测试(soak testing)
    • 尖峰冲击测试(spike testing)
    • 配置测试(configuration testing)
    • 隔断测试(isolation testing)

SV语言

1、数据类型
  • verilog中有寄存器类型(reg)和线网类型(wire)

  • sv中

    • 四值逻辑:integer(默认是x)、logic、reg、net-type

    • 二值逻辑:byte、shortint、int(默认是0)、longint、bit

    • 有符号类型:byte、shortint、int、longint、integer

    • 无符号:bit、logic、reg、net-type

  • 在SV的世界里面,期望将硬件的世界和软件的世界分开,软件的世界里面更多的是二值逻辑,硬件的多是四值逻辑

  • 显示的类型转换T’(),T是你的目标类型,加上‘;编译的时候做检查

  • $cast(T, S)类型转换; 仿真的时候做检查

  • 逻辑数值类型,符号类型,位宽

2、定宽数组
  • 初始化,内存空间不是连续的,初始化需要用`,如果是连续的,就不需要

    • int a[4] =`{1, 2, 3, 4};

    • int a[4]; a = `{1, 2, 3, 4}; 设置初始值

    • int a[4]; a[0:2] = `{1, 2, 3};前三个有初始值

    • int a; a = {4{8}};4个值都设置为8

    • int a; a = `{1, default :-1};

    • 合并和非合并

      • 位宽写在变量名右边,是非合并的,写在左边的是合并的
      • 分开的情况下,高纬度的永远在右边,合起来写的话,高位在左边
    • 赋值和比较

      • ==,就可以直接比较里面的所有元素了
3、for和foreach
  • initial begin
        bit [31:0] src[5], dst[5];   //这个地方就是32位是连在一起的,5个32是分开的,看清位宽写在哪里
        for (int i = 0; i < $size(src); i++)  //size(src, 1), 这个地方的1,是默认的,就是最高位
            src[i] = i;
        foreach (dst[j])   //这个地方写法就是这样的
            dst[j] = src[j] * 2;
    end
    
4、动态数组
  • int dyn[], d2[];//声明动态数组,都是非合并的
    initial begin
        dyn = new[5];//分配5个元素
        foreach(dyn[j]) dyn[j] = j; //元素初始化
        d2 = dyn;//赋值动态数组
        d2[0] = 5;
        dyn = new[20](dyn) //分配20个数值并进行复制, 前5个是原来的值
        dyn = new[100] //重新分配100个数值,而旧值不存在,默认的值是0
        dyn.delete//删除所有元素
    end
    
5、队列
  • int j = 1, q2[$] = {3, 4}; q[$] = {1, 3, 4}; //因为内存空间是连续的,所以不用`来赋值
    initial begin
        q.insert(1, j);//在3前面插入j,这里的j就是1{1, 1, 3, 4}
        q.insert(3, q2);// {1, 1, 3, 3, 4, 4}
        q.delete(1);//删除第一个元素
        
        q.push_front(6);//头插入
        j = q.pop_back();//尾取
        q.push_back(8);//尾插入
        j = q.pop_front();//头取
        foreach (q[j])
            $display(q[j]);
        q.delete();//删除整个队列
    end
    
6、关联数组
  • bit [63:0] assoc[int], idx = 1;
    repeat (64) begin  //对稀疏分布的元素进行初始化
        assoc[idx] = idx;
        idx = idx << 1;
    end
    foreach (assoc[i])
        $display("assoc[%h] = %h", i, assoc[i]);
    if (assoc.first(idx)) begin
        do 
            $display("assoc[%h] = %h", idx, assoc[idx]);
        while(assoc.next(idx));
    end
    assoc.first(idx);
    assoc.delete(idx);
    
7、结构体struct
  • 和c语言是一样的,没有typedef的话,就是匿名的结构体,有的话,就有对应的类型名字

    • typedef struct {bit [7:0] r, g, b;} pixel_s; 
      pixel_s my_pixel;
      
  • 可以指定为packed,没有的话默认是unpacked

8、枚举类型
  • enum枚举,默认的枚举类型是int,即32位的二值逻辑数据类型(也可以是四值类型的),为了更加准确的描述硬件,sv允许指明枚举类型的数据类型

    • enum bit {one, two, three} you;
      enum logic [2:0] {one = 3'h1, two=3'h2, three=3'h2} state;
      
9、字符串类型
  • string s;
    initial begin
        s = "IEEE ";
        $display(s.getc(0));   //"I"
        $display(s.tolower()); //ieee
        s.putc(s.len() -1, "-"); //空格变为-
        s = {s, "P1800"};//拼接
        $display(s.substr(2, 5))
        my_log($sformatf("%s %5d", s, 42));
    end
    task my_log(string message);
        $display("@%0t: %s", $time, message)
    endtask
    
10、硬件过程块
  • salways是描述硬件的行为的, always中不可以初始化变量,初始化是软件的概念,是initial中的

  • initial和always是并行执行的,是为了测试而生的,相当于是{}

  • function int double(input a)//可以和c++一样给默认值,a = 0,这里写了input,没写也是input默认是输入,所以如果是输出的话要标出来
    	return 2*a;
    endfunction
    //纯粹的数字或者是逻辑运算
    
  • task mytask(output logic [31:0] x, input logic y)
    	xxx
    endtask
    //task内置了耗时语句,function中没有,task可能要等待什么事件的发生
    
  • 只有task可以调用task,其他的不行,就是因为task是耗时的,function是及时的,所以在一个实时的系统中调用一个耗时的东西是不合逻辑的

  • 生命周期automatic动态和static静态,默认的function类型是static

    • //局部就是动态的,function里面的变量是动态的,function调用完了就消亡了
      function automatic int auto_cnt(input a)
          int cnt = 0;
          cnt += a;
          return cnt;
      endfunction
      
      function static int static_cnt(input a)//这里是不会消亡的
      	static int cnt = 0;
      	cnt += a;
      	return cnt;
      endfunction
      
      function int def_cnt(input a) //默认的函数的是static的
          static int cnt = 0;
          cnt += a;
          return cnt;
      endfunction
      
11、接口
  • interface arb_if(input bit clk);
        logic [1:0] grant, request;
        logic rst;
    endinterface
    
    module arb(arb_if arbif);
        ..
        always @(posedge arbif.clk or posedge arbif.rst) begin
            
        end
    endmodule
    
  • 接口中的clocking

    • clocking bus @(posedge clock1)
      	default input #10ns output #2ns  //输入信号前10ns采样,输出信号2ns后采样(默认是上升沿)除非自己声明了
          input data, ready, enable;
          output negedge ack;
          input #1step addr;
      endclocking
      
12、系统函数的调用
  • $stop()仿真暂停了
  • $finish()仿真结束了
  • $exit()退出
13、pragram隐式结束
  • program pgml;
        initial begin : procl
            xxx
            
        end
    endprogram
    
  • 如果program里面有forever的语句,不能够自己隐式结束,那么就需要自己调用$exit()来显式结束了

  • program可以看做是软件的领地,所以硬件里面的always,module和interface都不要出现

14、打印信息
  • d i s p l a y ( 消息级别 ) 、 display(消息级别)、 display(消息级别)warning(警告级别) 、 e r r o r ( 错误级别 ) 、 error(错误级别)、 error(错误级别)fatal(严重级别)
  • %x(十六进制)、%d(十进制)、%b(二进制)、%s(字符串)、%t(时间)
15、breakpoint
  • 设置断点,在某一行前面点了一下,就是设置断点,不是说运行完了这一行停下来,而是到了这一行就停了
16、类
  • 类的创建
    • 可以自定义构造函数(constructor)

    • class Transaction;
          logic [31:0] addr, crc, data[8];
          function new();    //没有返回值,没有写的,就是空的而已,也是允许的,返回的是指针,和c里面一样
              addr = 3;
              foreach (data[i])
                  data[i] = 5;
          endfunction
      endclass
      
    • new()这个地方也可以接受参数

    • SV对于对象是自动销毁的,所以一个对象的句柄一定不能都丢失,就是不要造成指向null

    • class的声明中变量其默认类型是动态变量,其声明周期是在仿真开始后的某时间点开始到某时间点结束。具体来讲,其声明周期始于对象创建,终于对象销毁

    • 如果用static来声明class内的变量,那么静态变量的声明开始于编译阶段,贯穿于整个仿真阶段

    • 声明静态变量,那么可以用class::var来直接引用,就和c++中一样的

    • 同时也可以用static声明一个静态方法,可以在静态方法中使用静态变量,但是不可以使用动态变量,因为静态的东西在编译阶段就存在了,但是动态的成员空间只有在使用的时候才会为其开辟空间,所以不可以调用的

  • 类的成员
    • 类可以通过protected和local等关键字来设置成员变量和成员方法的外部访问权限。所以封装属性在设计模式中称之为开放封闭原则(OCP,Open Closed Principle)
    • 如果没有指定访问类型,那么成员的默认类型是public的,子类和外部都是可以访问的
    • 如果指明了访问类型是protected,那么只有该类或者子类可以访问成员,外部无法访问
    • 如果指明了访问类型是local,那么只有该类可以访问成员,子类和外部均无法访问
    • 类和结构体的区别,类在声明之后需要调用构造函数才会构建对象实体,二struct在变量声明时已经开辟了内存,类出了声明变量成员,还可以声明方法(function/task),而struct则不能
    • 所以的盒子里面都可以定义类
  • 类的继承
    • class cat;
          bit xx;
          function ;
              xxx
          endfunction
      endclass
      
      class black_cat extends cat;
          function new();
              this.color = black;
          endfunction
      endclass
      
    • 如果是信号要加上virtual

    • 通过super来索引父类的同名函数

    • 有同名的变量名或者是方法的时候,以子类的作用域为准,同时可以通过super来调用父类的变量或者是方法

17、包
  • 之前的东西都封装到一个大的房子下面,就相当于是namespace

  • package regs_pkg;
        `include "stimulator.sv"   //原地展开
        `include "monitor.sv"
    endpackage
    
    //调用
    module mcdf_tb;
        regs_pkg::monitor mon2 = new();
    endmodule
    
    //也可以import packagename,就和c++中的namespace一样的,using namespace
    
18、随机约束和分布
  • 要随机什么
    • 器件配置:寄存器和系统信号
    • 环境配置:随机化验证环境,例如合理的时钟和外部反馈信号
    • 原始输入数据:数据包的长度、带宽、数据间的顺序
    • 延时:握手信号之间的时序关系
    • 协议异常:如果反馈信号出现异常,那么设计是否可以保持后续数据处理的稳定性
  • 声明随机变量的类
    • class Packet;
          rand bit [31:0] src;   //整个是随机的,一幅牌54张,每次都是1/54
          randc bit [7:0] kind;  //这是每次抽了牌之后,分母减一
          rand bit [7:0] low;
          constraint c {src > 10; src < 15;}
      endclass
      
      Packet p;
      initial begin
          p = new();
          p.randomize()  //随机,这个地方可以传递变量的,如randomize(low),即使是没有被rand修饰也行    
      end
      
    • class data;
          rand bit [2:0] month;
          rand bit [4:0] day;
          rand int year;
          constranit c_date {
              month inside {[1:12]};  //inside 表示遂于某一个集合
              day inside {[1:31]};
              year inside {[0:12], [2010:2030]};
          }
      
      endclass
      
    • rand int src, dst;
      constraint c_dist {
          src dist {0:=40, [1:3] :=60}//    := 每一份都是40, :/ 总共是那么多
          dst dist {0:/40, [1:3]:/60}
      }
      
    • //使用$来指定最大值和最小值
      rand bit [6:0] b;  //0 <= b <= 127
      rand bit [5:0] e;  //0 <= e <= 63
      constraint c_range {
          b inside {[$:4], [20:$]};
          e inside {[$:2], [29:$]};
      }
      
    • //通过-> 或者是if-else类让一个约束表达式在特定时刻生效
      constraint c_io {
          (io_space_mode) ->
          	addr[31] == 1'b1;
          
          if (op == 1'b1)
              len inside {[1:32]};
          else
              len == 1'b1;
      }
      classname.c_io.constraint_mode(0)//关闭c_io这个约束块
      
      classname.randomize() with {addr >= 50; addr <= 1500;}  //通过randomize() with来增加额外的约束
      
  • 随机函数
    • 如果需要在randmoize()之前或者是之后立即执行一些操作,SV提供了两个预定义的void类型函数pre_randmoize()和post_randmoize()
    • $random()平均分布,返回32位有符号随机数
    • $urandom()平均分布,返回32位无符号随机数
    • $urandom_range()在指定范围内的平均分布
  • 数组的约束
    • class dyn_size;
          rand logic [31:0] d[];
          constraint d_size {d.size() inside {[1:10]};}  //还有sum(), product(), and(), or(), xor()
      endclass
      
    • class good_sum5;
          rand uint len[];
          constraint len_value {foreach(len[i]) len[i] inside {[1:19]};}
      endclass
      
    • //初始化数组来产生唯一元素值
      class randc8;
          randc bit [7:0] va;
      endclass
      
      class LittleUniqueArrya;
          bit [7:0] ua[64];
      function void pre_randomize();
          randc rc8;   //放在外面定义
          foreach(ua[i]) begin
              assert(rc8.randomize())
                  ua[i] = rc8.val;
          end
      endfunction
      endclass
      
    • 随机句柄数组的声明一定要加上rand,在里面创建对象

  • 随机序列
    • randsequence : 产生事务序列

      • initial begin
            for (int i = 0; i < 15; i++)begin
                randsequence (stream)
                	stream : cfg_read :=1 |
                			 io_read :=2 |
                			 men_read := 5;
                	cfg_read : {cfg_read_task;} | {cfg_read_task;} cfg_read;
                	men_read : {men_read;} | {men_read_task;} men_read;
                	io_read : {io_read_task;} | {io_read_task;} io_read;
                endsequence
            end
        end
        
    • randcase : 建立决策树

      • initial begin
            int len;
            randcase
            	1 : len = $urandom_range(0, 2);  //  10%:0 1 or 2
            	8 : len = $urandom_range(3, 5);  //  80%:3 4 or 5
            	1 : len = $urandom_range(6, 7);  //  10%: 6 or 7
            endcase
        	$display("len = %0d", len);
        end
        
      • randsequence 和randcase是针对轻量级的随机控制的应用,而我们可以通过定义随机类取代上述随机控制的功能,并且由于类的继承使得在后期维护代码时更加方便

      • randsequence的相关功能我们在协调激励组件和测试用例时,可能会用到

      • randcase则对应着随机约束中的dist权重约束+ if-else条件约束的组合

19、线程
  • 线程的使用
    • 如果按照软件的思维来说,各个模块(module)就是独立运行的线程(thread),模块(线程)在仿真一开始的时候就开始并行执行了,除了每个线程会按照在自己内部产生的事件来触发过程语句块之外,也同时依靠相邻模块之间的信号变化来完成模块之间的线程同步。
  • 什么是线程
    • 线程就是独立运行的程序
    • 线程需要被触发,可以结束或者不结束
    • 在module中的initial和always,都可以看做独立的线程,他们会在仿真0时刻开始,而选择结束或者是不结束
    • 硬件中大多都是always语句,可以看作是独立的线程,而这些线程会一直占用仿真资源,因此他们不会结束
    • 软件平台中的验证环境都需要由initial语句块去创建,而在仿真过程中,验证环境中的对象可以创建和销毁,因此在软件测试中的资源占用是动态的
    • begin end 和 fork join
    • begin end是顺序执行的 ,而fork join是并发的
    • fork join (所有的执行完了,才会退出)还包括了 fork join_any(任意一个执行完了就会退出)和fork join_none(直接就退出了)
    • 线程的执行是呈现树状结构的,就是任何的线程都由自己的父线程
    • 父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程
    • 子线程终止的时候,父线程可以继续执行
    • 父线程终止的时候,其所开辟的所有子线程都应当会终止
  • 线程的控制
    • 在sv,当程序中的initial块全部执行完毕了,仿真器就退出了

    • 如果我们希望等待fork块中的所有线程执行完毕在退出,可以使用wait fork

      • fork 
        	//线程1
        	//线程2
        	//线程3
        join_none
        wait fork
        
    • disable 结束需要停止的线程

      • fork : timeout_block
        	begin 
        		wait(bus.cb.addr == tr.addr);
        		$display("@%0t: Addr match %d", $time, tr.addr);
        	end
            #TIME_OUT $display("@%0t: ERROR: timeout", $time);
        join_any
        disable timeout_block;
        end
        join_none;
        
      • disable fork 可以停止当前线程中衍生出来的所有子线程,可以给fork命名,这样就可以明确的disable它了

  • 线程间的通信(IPC, Interprocess Communication)
    • verilog中,一个线程总是要等待一个带@操作符的时间,这个操作符是边沿敏感的,所以它总是阻塞着,等待事件的变化

    • 其他的线程可以通过->操作符来触发事件,结束对第一个线程阻塞

    • 用triggered()更加安全

    • -> e1;
      wait (e2.triggered());
      
      
      -> e2;
      wait(e1.triggered());
      
      //只要被触发过了,就不会引起阻塞
      
    • 线程之间的执行需要按照一定的顺序

    • 对于线程多次通知的需求,应该使用@,不能在使用wait(event.triggered())。这个是由于event被触发之后,它的状态会使得被触发事件的状态一直是1‘b1,而且没有方法可以清楚这个状态

  • semaphore旗语
    • 可以实现对统一资源的访问控制

    • 对于初学者而言,无论线程之间在共享什么资源,都应该使用semaphore等资源访问控制的手段

    • 有三个基本操作

      • new() : 创建一个或者是多个钥匙

      • get() : 获取一个或者是多个钥匙

      • put() : 可以返回一个或者是多个钥匙

        • program automatic test(bus_ifc.TB bus);
          	semaphore sem;
              initial begin
                  sem = new(1);   //分配一个钥匙
                  fork 
                      sequencer();   //产生两个总线事务
                      sequencer();
                  join
              end
              task squencer;
                  repeat($urandom %10) @bus.cb;  //等待0-9个周期
                  sendTrans(); //执行总线事务
              endtask
              task sendTrans;
                  sem.get(1);  //获取总线钥匙
                  @bus.cb; //信号驱动到总线上
                  bus.cb.addr <= t.addr;
                  xxx
                  sem.put(1);  //放回钥匙
              endtask
          
          endprogram
          
        • 线程之间的资源共享要遵循互斥访问(mutex access)原则

  • mailbox信箱
    • 传递信息的

    • 和queue有相近之处

    • 是一种对象,也需要用new()来例化,例化时可以选择一个参数size来限定其存储的最大量,如果size=0或者是没有指定,那么信箱无限大的。

    • put往信箱里面放东西,get从信箱里面取东西

    • 如果满了,put会阻塞,如果空了,get会阻塞

    • peek可以从信箱里面拷贝数据,而不移除它

    • program automatic bounded;
          mailbox mbx;
          initial begin 
              mbx = new(1);
              fork
                  for (int i = 1; i < 4 ; i++) begin
                      $display("Producer: before put(%0d)", i);
                      mbx.put(i);
                      $display("Producer: after put(%0d)", i);
                  end
                  
                  repeat(4) begin
                  	int j;
                      #1ns mbx.get(j);
                      $display("Cosumer: after get(%0d)", j);
                  end
              join
          end
      endprogram
                  
      
    • mailbox必须通过new,而队列只需要声明

    • mailbox可以将不同的数据类型存储,不过不建议这么做,对于队列来讲,内部存储的元素必须是一致的

    • mailbox的存取方法put和get是阻塞的,就是它们不一定是立即返回的,而队列的方法是立即返回的wait(queue.size()>0)

    • try_put/try_get/try_peek非阻塞方法

    • mailbox #(int) 指定里面存的是int类型

20、覆盖
  • 覆盖率反馈回路
    • 可以使用一个反馈回路来分析覆盖率的结果,并决定采取哪一个行动来达到100%的覆盖率
    • 首先的选择是使用更多的种子来运行现有的测试程序
    • 当大量的种子依然对于覆盖率增长没有帮助的时候,需要建立新的约束
    • 只有在确实需要的时候才会求助于创建定向的测试
  • 代码覆盖率
    • 行覆盖率:多少行被执行过了
    • 路径覆盖率:在穿过代码和表达式的路径中有哪些被执行过了
    • 翻转覆盖率: 哪些单位比特变量的值为0或者1
    • 状态机覆盖率: 状态机哪些状态和状态转换已经被访问过了
    • 断言覆盖率:
      • 一当检测到问题,仿真就直接结束了
  • 覆盖组(covergroup)
    • 一个covergroup可以有多个coverpoint,且全部在同一时间采集

    • 可以定义在类里面,也可以在interface或者module中

    • 一个类里面可以有多个covergroup,必须要被例化了才可以使用

      • class Transactor;
            Transaction tr;
            mailbox mbx_in;
            covergroup CovPort;    // 也可以写成CovPort  @(trans_ready)等待事件的触发
                coverpoint tr.port;
            endgroup
            function new(mailbox mbx_in);
                CovPort = new();
                this.mbx_in = mbx_in;
            endfunction
            task main;
                forever begin
                    tr = mbx_in.get;
                    ifc.cb.port <= tr.port;
                    ifc.cb.data <= tr.data;
                    CovPort.sample();
                end
            endtask
        endclass
        
      • 与sample()相比,使用事件触发的好处在于你可以借助于已有的事件

  • coverpoint
    • 当你在coverpoint中指定采样一个变量或者表达式的时候,SV会创建很多的“仓bin”来记录每个数值被捕捉到的次数

    • 这些bin是衡量功能覆盖率的基本单位

    • covergroup可以定义多个coverpoint,coverpoint中可以自定义多个cover bin或者SV帮助自动定义多个cover bin

    • 当covergroup采样的时候,SV都会在一个或者多个cover bin中留下标记,用来采样时变量的数值和匹配的cover bin。

    • bin
      • 为了计算这个coverpoint上面的覆盖率,首先需要确定的可能数值的个数,被称为域

      • 覆盖率就是采样值除以bin的数目。如一个3bit的变量的域就是0:7,正常情况下就会被分配8个域,如果仿真的过程中有7个值被采样到了,那么覆盖率就是7/8

      • SV会默认为coverpoint创建bin,用户也可以自己定义bin

      • 如果没有自己指定bin,域的范围又太大了,就会默认64个bin,平均分为这个域

      • 用户可以通过covergroup的选项auto_bin_max来指定自动创建的bin的最大数目

      • 建议自己指定bin

      • covergroup CovPort;
        	options.auto_bin_max = 8;  //所有的coverpoint auto_bin 都是8
            coverpoint tr.point
            { options.auto_bin_max = 2;} //特定的coverpoint auto_bin = 2,中括号外面没有分号了
        endgroup
        
      • covergroup CovKind;
            coverpoint tr.kind {
                bins zero = {0};   // 一个仓代表kind == 0
                bins lo = {[1:3], 5}; //一个代表1:3和5
                bins hi[] = {[8:$]}; //8个仓代表8:15
                bins misc = default; //剩下的值
            }//没有分号
        endgroup
        
  • iff条件覆盖
    • 用关键字iff来给coverpoint来添加条件

    • 常用于复位期间关闭覆盖以忽略不合理的条件触发

    • 也可以用start和stop函数来控制covergroup各个独立实例

    • covergroup CoverPort;
          coverpoint port iff (!bus_if.reset);
      endgroup
      
      initial begin 
          CovPort ck = new();
          #1ns;
          ck.stop();
          #100ns bus_if.reset = 0;
          ck.start();
      end
      
    • //记录翻转次数
      
      covergroup CoverPort;
          coverpoint port {
              bins t1 = (0 => 1), (0 => 2), (0 => 3);
          }
      endgroup
      
    • bit [2:0] port;
      covergroup CovPort;
          coverpoint port {
              wildcard bins even = {3'b??0};
              wildcard bins odd = {3'b??1};
          }
      endgroup
      //用关键字来创建多个状态或者是翻转
      //任何x、z或者是?都会被当成是0或者1的通配符
      
    • bit [2:0] low_ports_0_5;
      covergroup CovPort;
          coverpoint low_port_0_5 {
              ignore_bins hi = {[6, 7]}; //忽略6,7,不记录覆盖率当中
              illegal_bins hi = {[6, 7]}; //如果出现6和7就报错
          }
      endgroup
      
    • class Transaction;
          rand bit [3:0] kind;
          rand bit [3:0] port;
      endclass
      Transaction Tr;
      covergroup CovPort;
          kind : coverpoint tr.kind;
          port : coverpoint tr.port;
          cross kind, port;   //记录多个变量之间的组合情况
      endgroup
      
    • covergroup Covport;
          port : coverpoint tr.port {
              bins port[] = {[0:$]};
          }
          kind : coverpoint tr.kind {
              bins zero = {0};
              bins lo = {[1:3]};
              bins hi = {[8:$]};
              bins misc = default;
          }
          cross port, kind {
              ignore_bins hi = binsof(port) intersect {7};
              ignore_bins md = binsof(port) intersect {0} && binsof(kind) intersect {[9:11]};
              ignore_bins lo = binsof(kind.lo);
          }
      endgroup
      
    • class Transaction;
          rand bit a, b;
      endclass
      
      covergroup CrossBinNames;
          a : coverpoint tr.a {
              bins a0 = {0};
              bins a1 = {1};
              option.weight = 0; //不计算覆盖率
          }
          b : coverpoint tr.b {
              bins b0 = {0};
              bins b1 = {1};
              option.weight = 0;
          }
          ab : cross a, b {
              bins a0b0 = binsof(a.a0) && binsof(b.b0);
              bins a1b0 = binsof(a.a1) && binsof(b.b0);
              bins b1 = binsof(b.b1);
          }
      endgroup
      
    • class Transaction;
          rand bit a, b;
      endclass
      
      covergroup CrossBinsofIntersect;
          a : coverpoint tr.a {
              option.weight = 0;
          }
          this coverpoint
          b : coverpoint tr.b {
              option.weight = 0;
          }
          this coverpoint
          ab : cross a, b {
              bins a0b0 = binsof(a) intersect{0} && binsof(b) intersect {0};
              bins a1b1 = binsof(a) intersect{1} && binsof(b) intersect {1};
              bins b1 = binsof(b) intersect{1};
          }
          
      
    • //如果对一个covergroup例化了多次,那么默认的情况下SV会将所有实例的覆盖率合并到一起,如果需要单独列出每一个covergroup的覆盖率,需要设置参数
      covergroup CoverLength;
          coverpoint tr.length;
          option.per_instance = 1;
      endgroup
      
    • //注释
      covergroup CoverPort (int lo, hi, string comment);
          option.comment = comment;
          option.per_instacne = 1;
          coverpoint port {
              bins range = {[lo:hi]};
          }
      endgroup
      
      CoverPort cp_lo = new(0, 3, "LOW PORT NUMBERS");
      ;overPort cp_hi = new(4, 7, "HIGH PORT NUMBERS");
      
    • 默认的情况下,数值采样了1次就可以计入有效的bin,可以通过修改at_lease来修改每一个bin的数值最少采样次数,如果低于这个次数,那么就不会被计入bin中

    • option.at_least可以在covergroup中来声明来影响所有的coverpoint,也可以在coverpoint值影响整个point里面的bin

    • covergroup CovPort;
      	coverpoint port;
      	option.goal = 90;
      endgroup
      //目标的覆盖率,默认是100
      
    • sample();//采样
      get_coverage()/get_inst_coverage();//获取覆盖率,0-100的real数值
      set_inst_name(string)//设置covergroup的名称
      start()/stop()//开始或者是关闭采样
      $get_coverage()//获取总体的覆盖率
      
21、类的转换
  • 类的转换分为静态转换和动态转换
  • 隐式转换:如赋值语句的右边是4位的bit,左侧是5位的bit,隐式转换就会先做位宽拓展,然后在赋值
  • 动态转换:当我们使用类句柄的向下转换,即从父类句柄转换位子类句柄的时候,需要进行动态的转换,否则就会出现编译错误
  • 如果将子类句柄赋值给了父类句柄时候,编译器会认为是合法的(金字塔结构),但是当用子类句柄和父类句柄调用相同对象的成员的时候,可能会出现不同的表现。
  • 父类给子类先进行cast
22、虚方法
  • class basic_test;
        int fin;
        int def = 10;
        function new();
            $display("basic_test: new");
        endfunction
        task test();
            $display("basic_test:: test");
        endtask
    endclass
    
    class test_wr extends basic_test;
        int def = 20;
        function new();
            super.new();
            $display("test_wr::new");
        endfunction
        task test();
            super.test();
            $display("test_wr::test");
        endtask
    endclass
    //355页
    
  • 子类覆盖(override)的方法并不会继承父类同名的方法,只有通过super.method()才会显示的调用

  • 虚方法定义之后,系统会在执行程序的时候,检查句柄指向的是什么对象,去调用什么函数

  • 如果父类中的方法会被覆盖,就应该要被声明为virtual,尽量在底层的父类声明

23、对象的拷贝
  • test_wr a, b;
    a = new();
    a = b; //这个是句柄的赋值
    
  • 想要拷贝对象,需要先new,然后挨个赋值

  • virtual funciton void cop_data(basic_test t);
    	t.def = def;
    	t.fin = fin;
    endfunction
    
    virtual function basic_test copy();
        basic_test a = new();
        copy_data(a);
        return a;
    
  • class mailbox #(type T = int);   //这里默认的T就是int,调用的时候可以改的
    	local T = queue;
    

UVM

1、类库地图
  • 组件的创建和访问
  • 环境的结构创建、组件之间的连接和运行
  • 不同阶段的顺序安排
  • 激励的生成、传递和控制
  • 测试的报告机制
2、UVM分类
  • 核心基类
  • 工厂(factory)类
  • 事务(tansaction)和序列类
  • 结构创建(structure creation)类
  • 环境结构(environment component)类
  • 通信管道(channel)类
  • 信息报告(message report)类
  • 寄存器模型(register model)类
  • 线程同步(thread synchronization)类
  • 事务接口(transaction interface)类
3、工厂机制
  • 验证环境分为uvm_component(环境层次)和uvm_object(环境属性和数据传输)

  • component继承于object

  • 要先注册,在创建

  • 验证环境

    • generator
    • stimulator
    • monitor
    • agenet
    • checker/reference model
    • environment
    • test
  • com_type::type_id::create(string name, uvm_component parent);   //uvm_component 
    com_type::type_id::create(string name)    //uvm_object
    
  • class comp1 extends uvm_component;
        `uvm_component_utils(comp1)
        function new(string name = "comp1", uvm_component parent = null);
            super.new(name, parent);
            $display($sformatf("%s is created", name));
        endfunction : new
        function void build_phase(uvm_phase phase);
            super.build_phase(phase);
        endfunction : build_phase
    endclass
    
    class obj1 extends uvm_object;
        `uvm_object_utils(obj1);
        function new(string name = "obj1");
            super.new(name);
            $display($sformatf("%s is created", name));
        endfunction : new
    endclass
    
    comp1 c1, c2;
    obj1 o1, o2;
    initial begin
        c1 = new("c1");
        o1 = new("o1");
        c2 = comp1::type_id::create("c2", null);
        o2 = obj1:;type_id::create("o2");
    end
    

心得

  • 在设计或者是验证的过程首先要遵守的就是top to down的思想,将一个大的系统分成一个个的模块,在具体实现的过程中要遵守的是down to top的思想,就是将一个个的模块设计好,实现了在拼接成一个系统。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数字IC验证工程师负责验证测试数字集成电路的设计是否符合规格和功能要求。他们使用各种工具和技术来评估电路的性能和功能,并确保其能够正确地工作。 首先,数字IC验证工程师需要理解电路的设计规格和功能要求。他们会仔细阅读电路设计文档,并与设计工程师进行讨论,以确保对设计的理解正确无误。然后,他们会制定验证计划,包括测试策略和测试计划。 接下来,数字IC验证工程师会使用仿真工具,如Verilog或VHDL,来编写测试用例并进行仿真。他们会模拟电路的行为并执行测试用例,以验证电路的性能和功能。如果发现问题或错误,他们会与设计工程师合作进行故障排除和修改。 为了提高测试覆盖率数字IC验证工程师会使用其他工具和技术,如基于事务的验证(Transaction-based Verification)和随机测试(Random Testing)。这些方法可以生成更多的测试用例,并帮助发现潜在的设计错误。 此外,数字IC验证工程师还需要与其他团队成员密切合作,如电路设计工程师、物理设计工程师和软件开发工程师。他们会参与电路设计和物理实现的评审会议,并提供反馈和建议。他们还会与软件开发工程师合作,以确保电路可以正确地与软件进行互动。 总之,数字IC验证工程师在电路验证测试方面扮演着重要角色。他们需要具备深入的电路知识和技术能力,以确保数字集成电路的设计正确无误,并满足相关要求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值