浅谈可移植激励规范(PSS)复用策略

 

译者按 :当今硬件设计变得愈加复杂,如何创建出足够的测试来保证设计的正确性是每个硬件工程师需要面对的问题。Accellera的可移植激励测试规范(PSS、又称便携激励标准)[1]旨在希望能够提一个独立的测试来源,从而实现跨层级的验证复用,即无论是IP级别、子系统、还是SoC级都使用同样的测试来源,他们也希望提供一系列功能来解决不同级别对于验证测试的不同要求,从而达到真正意义上的复用。然而,即使是像Java和C++这样拥有强大的面向对象特性的编程语言,都不能直接保证我们能得到高质量的可复用的代码,PSS本身的语言特性也同样需要工程师按照一定规范使用才能达到高效复用的目的。如果我们正确的使用这些特性,实现设计IP和测试目的的复用,能够显著减少其所消耗的时间成本这篇文章就是讨论了如何通过前期的计划,针对自身公司的规模和设计需求使用PSS更加正确、有效的通过复用来加速验证过程。

剖析可移植激励

可移植激励(下文简称PSS)语言在设计初期就考虑到了测试意图和自动化创建测试的要求。下图一就很好的展示了PSS是如何通过对于测试意图和测试实例的清晰区分,来达到对于测试意图的平台复用的。在PSS描述文件中,测试意图主要抽象定义了一个测试需要执行什么行为,是通过声明的形式来实现的。就如同在SystemVerilog语言中声明式的约束一样,我们可以通过这种方式得到比较好的复用性。

图一 – PSS描述文件结构详解

 

通过这种声明式的语言特性,PSS很好地满足了复用和自动化的需求。通过这种特性,定义了合法空间中可以发生的行为,我们能够直接编写程序定义其行为内容而不是如何执行这种行为。如果有读者使用过SystemVerilog的约束特性,就能够明白约束定义的是合法激励数据范围。PSS语言正是将这种特性从数据定义扩展到测试场景的使用上,提供了一种新的元素-动作(action)。

 

然而支持这种声明式的语言特性并不够,因为测试本质上是需要和设计单元进行底层的互动,如修改寄存器或者中断等等来达到验证的目的。所以测试代码不仅仅是要定义抽象的测试场景,也需要能够定义底部的设计细节。另外绝大部分的验证环境已经拥有大量的测试代码供工程师使用,所以大多数情况下,工程师们会使用他们熟悉的编程语言如SystemVerilog、C++或C来实现所需要的可移植激励测试

 

当然,我们使用PSS的最终目的为了生成更多的测试用例,下图二就向我们展示了一个典型的PSS使用流程。在这种情况下,PSS文件一般会在测试生成过程中被执行。比如说,我们可以使用PSS来提供约束生成仲裁功能。在SoC环境下,我们会系统的处理器执行测试,此时我们可以提前使用PSS生成一些简单的测试用例来让处理器更高效快速的运行。

 

在这两种使用环境下,PSS都会将抽象的测试场景定义和测试生成两部分完全分离出来

图二 – PSS流程详解

详解可移植的含义

在详细讨论可移植激励应用设计策略之前,有必要花时间了解“可移植”本身的含义。接下来我们将从三个方面讨论如何更好地实施相应设计策略。

 

在讨论可移植测试特性时,我们经常会涉及到垂直复用这一术语。这个概念通常是指从项目早期 – 一般是指在IP层级 – 就开发出相应测试意图,以便在之后的验证流程中重复使用,比如子系统和系统层级。对于测试意图在各个层级上的垂直复用,能够提供一个全面健壮的测试库,有效帮助我们更快的搭建测试环境,同时进一步减少在开发过程中带来的程序错误。当然尽管垂直测试的好处多多,但是开发这种测试需要各个团队协作共同努力,比如我们需要IP层级的团队同时支持系统级和子系统级别的测试开发,另外也需要对已存在的IP开发相应的可移植激励描述文件。

 

水平复用则可以让测试意图文件在不同项目中工作,当然设计本身会有一些参数上的变化。但是可移植激励标准本身声明式的语言特性,能够有效降低对于参数调整带来的工作量。我们只需要调整测试规则即可,而不需要对测试意图本身进行更新。

图三 – 垂直复用实例

 

如图三的实例,不同的SoC设计中可能包含不同数目的DMA引擎。如果使用直接测试的方式,我们需要检查所有测试文件,以确保他们检查SoC时使用正确的DMA引擎数目。但是使用可移植激励规范时,我们只需要调整DMA参数,即可以重新生成正确的测试文件

 

最后一项可能比较难以理解 – 测试技术的可移植性。例如SystemVerilog的受约束随机特性,虽然可以有效帮助我们提升验证效率和速度,但是这种特性只能使用在模拟测试环境当中,无法使用在以C++为主的高级综合环境(HLS)中,也并不能使用在嵌入式软件测试中,因为一般来说,嵌入式系统的硬件资源有限,没有办法运行一个完整的SystemVerilog模拟器。

 

但是可移植激励规范的出现,让这些测试技术,比如自动化的受约束随机测试,能够在不同的测试环境中得以应用。这一益处本身也许就足够可以说服团队采用可移植激励规范。

 

在一个项目初期,团队就需要从以上三方面来考虑如何设计PSS策略,比如哪方面的复用更为重要,需要团队投入更多的资源进行开发。从而让整个项目能够最大化利用可移植激励带来的好处。

实例详解

我们接下来将用一个非常简单的实例来探讨PSS的实际应用,我们将继续使用图三展示的SoC设计框架,其中包含一个四核RISC-V处理器、一个外围设备子系统以及一些其他控制器组件。

 

接下来我们也会从组件层级讨论DMA IP。如下图四,我们可以看到一个组件级别的验证环境框架图。另外如图五所示,我们也会探讨一下如何从子系统级别嵌入一个DMA引擎、一个UART控制器和一个中断控制器

图四 – DMA引擎模块级环境

 

图五 – 外围子系统验证环境

现有测试用例的复用

当我们完成文档的编写,接下来就是对代码库进行分析,来确定哪一些可以进行复用。我们可以利用这段时间来评估一下有多少现有代码可以继续用于当前项目。另外如果时机允许也可以借此来评估对整个组织内的相关代码,通过整合资源可以更好的提升开发效率。

约束

PSS描述文件绝大部分基于约束语句,这是由其声明特性所决定的。所以现有的一些约束文件也可以很轻易的转换成PSS格式进一步节省开发时间

 

SystemVerilog的约束文件由于其声明特性,通常可以作为PSS描述文件二次再开发的来源。另外其本身语法特性与PSS非常相似,我们甚至可以直接拷贝复制进PSS文件直接进行使用。这种情况的使用案例是一些IP或者子系统的配置对象代码,可以这样复用在PSS文件当中。

 

通常约束文件可读性比较低,比如SoC的内存映射文件。但是通过一点点改动,我们就能将其改写成PSS约束,来专门定义不同内存空间的读取权限

测试案例实现细节

团队现存的测试环境通常是一个非常好的实现基础,当然这其中需要花时间将其按照PSS的语法进行改动。

 

UVM环境中,我们会使用utility sequence来对IP模块进行简单的操作,比如初始化设置,命令其进行数据计算等等。这些序列在定义时通常有一些随机约束和变量。其他情况下,我们会通过输入参数来控制其工作模式。无论如何,我们都可以通过一些方法将其转换成PSS描述文件。

task init_single_transfer(
  int unsigned	channel,
  int unsigned	src,
  int unsigned	inc_src,
  int unsigned	dst,
  int unsigned	inc_dst,
  int unsigned	sz
);
  wb_dma_ch ch = m_regs.ch[channel];
  uvm_status_e status;
  uvm_reg_data_t value;
 
 
  // Disable the channel
  ch.CSR.read(status, value);
  value[0] = 0;
  ch.CSR.write(status, value);
 
  // These registers are volatile. Read-back the content
  // so the register model knows to re-write them
  ch.A0.read(status, value);
  ch.A1.read(status, value);  

图六 – SystemVerilog测试复用实例

 

图六截取了一段SystemVerilog代码,其中我们定义了一个virtual sequence的task,来设计DMA引擎在一条特定的数据通路上传输数据。这段代码就可以通过简单改写将其使用在测试DMA引擎的PSS模型中。需要注意的是,因为我们本身的代码使用了UVM,所以底层是使用了UVM寄存器模型来读取DMA引擎的相关寄存器。

 

void wb_dma_drv_init_single_xfer(
  wb_dma_drv_t		*drv,
  uint32_t		ch,
  uint32_t		src,
  uint32_t		inc_src,
  uint32_t		dst,
  uint32_t		inc_dst,
  uint32_t		sz
  ) {
  uint32_t sz_v, csr;
 
  csr = WB_DMA_READ_CH_CSR(drv, ch);
 
  csr |= (1 << 18); // interrupt on done
  csr |= (1 << 17); // interrupt on error
  if (inc_src) {
    csr |= (1 << 4); // increment source
  } else {
    csr &= ~(1 << 4);
  }
  if (inc_dst) {
    csr |= (1 << 3); // increment destination
  } else {
    csr &= ~(1 << 3); // increment destination
  }
  csr |= (1 << 2); // use interface 0 for source
  csr |= (1 << 1); // use interface 1 for destination
 
  csr |= (1 << 0); // enable channel
 
  // Setup source and destination addresses
  WB_DMA_WRITE_CH_A0(drv, ch, src);
  WB_DMA_WRITE_CH_A1(drv, ch, dst);
 
  sz_v = WB_DMA_READ_CH_SZ(drv, ch);
  sz_v &= ~(0xFFF); // Clear tot_sz
  sz_v |= (sz & 0xFFF);
  WB_DMA_WRITE_CH_SZ(drv, ch, sz_v);
 
  // Start the transfer
  WB_DMA_WRITE_CH_CSR(drv, ch, csr);
 
  drv->status[ch] = 1;
}

图七 – 相关测试的C代码复用

 

上图是一个相似功能的C代码实现。我们同样也可以将其转换用在PSS测试中。

 

当然需要注意的是虽然两种代码实现的测试目的相似,但我们会讨论哪一种更适合用在PSS文件中

 

创建可复用的PSS库

当我们开始考虑在公司内部创建PSS代码库时,通用的数据结构是需要优先考虑的一点。因为PSS还是一个比较新的标准,所以业界现在还没有一个标准化的通用数据库。但是我们非常建议在项目开始初期就为常用的数据结构创建一个通用库。这样做的原因是PSS描述文件本身会经常使用一些非常类似的数据结构,例如一个memory buffer需要定义其地址和内存大小。

struct data_mem_t {
  rand bit[31:0] addr;
  rand bit[31:0] sz;
}

图八 – 可复用的数据缓存结构

 

我们并不期望三个负责不同IP的工程师能够定义出相同的memory buffer structure。所以提前定义出共用的数据结构库,并确保团队及时的使用和维护,能够让PSS模型在子系统和SoC层面的复用变得不那么复杂。

 

另外我们也推荐创建一个IP层面的PSS代码规范。比如,所有IP层面的PSS action,都需要从一个通用的abstract action type继承出来。例如图九所示。

abstract action dma_dev_a : pvm_dev_a {
// All transfers involve a channel
rand bit[7:0] in [0..7] channel;
// Size of each transfer
rand bit[4] in [1,2,4] trn_sz;


}


/**
* Transfer memory-to-memory
*/
action mem2mem_a : dma_dev_a {
input data_ref_mem_b dat_i;
output data_ref_mem_b dat_o;


}


/**
* Transfers data to a memory address
*/
action dev2mem_a : dma_dev_a {
output data_ref_mem_b dat_o;
input data_ref_s info_i;


rand bit[31:0] src_addr

图九 – IP specific common base action

 

Type extension是PSS的一大特性,只有将所有PSS action全部从base action继承出来,才能在整个IP验证流程中更好的使用该特性

 

创建可复用的数据产生和检查流程

测试结果的检查方式对于IP验证和SoC验证流程来说,有着很大的不同。IP层级的验证我们大多会使用基于scoreboard的检查,这样我们能够看到IP中的每一个数据操作是如何被执行的,也可以检查到最终的运行结果。但是在SoC层级,由于设计本身的局限性,我们一般只会检查最终的数据结果。

 

因此,如果公司内部比较看重垂直复用性,那么定义一个在IP层级和SoC层级可以同时使用的数据核查策略就显得非常重要。通常来说,我们在PSS描述文件当中会更多侧重对in-memory data最终结果的检查。

 

当然,我们也可以在implementation check基础上增加更多的functional check。例如在IP层级中,DMA引擎操作就可以通过纯粹功能性上的验证来进行测试,即接收端的数据和发送端的数据是否相符。IP层级上的scoreboard仍旧可用来检查DMA的传输操作,与此同时子系统和SoC层级上也能够复用这种验证策略,比如可以使用在对SoC的性能测试中。

 

测试实现的复用

针对相同的测试目的我们会有多种实现方式,对于验证工程师来说我们要学会利用UVM本身的各种特性,例如寄存器模型等。与此同时,我们也要评估并利用本身公司为硬件设计的嵌入式软件自带的各种特性,例如直接使用其定义的寄存器访问方式并引进到验证测试当中以避免重复定义

 

定义通用APIs

话虽如此,能够为不同的测试实现提供共用组件肯定会带来更积极的作用,这其中第一步就是设计通用的API

task mem2mem(
  int unsigned    channel,
  int unsigned    src,
  int unsigned    dst,
  int unsigned    sz);
  init_single_transfer(channel, src, 1, dst, 1, sz);
  wait_complete_irq(channel);


endtask

图十 – 通用DMA API示例

上图的实例代码是通过对于IP层级的SV task的复用定义的一个DMA数据操作API。

 

而图十一则是相同功能在嵌入式软件当中的C代码实现。需要注意的是C代码实现当中的devid变量是由于其本身并不是面向对象的程序语言导致的。在SV环境中,mem2mem task是作为一个类的成员,在这个类的定义里devid会由PSS模型当中其他合适的对象代码所提供。但是由于C语言中并没有相关特性,用户需要提供这个变量以便于和其他模型进行通信。保持功能上一致的API能够非常显著的简化将PSS移植到不同测试解决方案的工作量,即使在实现细节上由于不同语言会有些许不同之处。

 void wb_dma_dev_mem2mem(
    uint32_t			devid,
    uint32_t			channel,
    uint32_t			src,
    uint32_t			dst,
    uint32_t			sz,
    uint32_t			trn_sz) {
  wb_dma_dev_t	*drv = (wb_dma_dev_t *)uex_get_device(devid);
  uint32_t csr, sz_v;
  uex_info_low(0, "--> wb_dma_dev_mem2mem %s channel=%d src=0x%08x dst=0x%08x sz=%d",
    drv->base.name, channel, src, dst, sz);
  // Disable the channel
  csr = uex_ioread32(&drv->regs->channels[channel].csr);
  csr &= ~(1);
  uex_iowrite32(csr, &drv->regs->channels[channel].csr);
 
  // Program channel registers
  csr = uex_ioread32(&drv->regs->channels[channel].csr);

图十一 – 通用DMA API C语言示例

 

如果垂直复用在项目中更为重要,那可考虑投入资源开发一个提升环境兼容性的中间件。比如图十二所示的UEX硬件访问层。UEX硬件访问层[2]能够提供以C为主的API来访问平台内存和多线程操作使用这种中间件能够使得测试实现代码可以在嵌入式软件环境中开发并且可以在项目初期就可以进行debug调试,也可以在接下来多个验证环境中复用

 

图十二 – UEX硬件访问层

 

无论是否使用上述的访问兼容层,还是通过一系列API来提供这种支持,我们都需要考虑到不同IP的测试实现将如何配合。所有IP组件的测试实现代码可能都需要访问内存,而且绝大部分的IP都需要在中断发生时得到系统通知。在实际生产环境中,我们会通过操作系统来协调不同驱动同时工作。但是在验证环境中,不论是UVM还是嵌入式软件,我们都需要一个更为轻量化的解决方案。

static void wb_dma_dev_irq(struct uex_dev_s *devh) {
  wb_dma_dev_t *dev = (wb_dma_dev_t *)devh;
  uint32_t i;
  uint32_t src_a;
 
  src_a = uex_ioread32(&dev->regs->int_src_a);
 
  // Need to spin through the channels to determine
  // which channel to activate
  for (i=0; i<8; i++) {
    if (src_a & (1 << i)) {
      // Read the CSR to clear the interrupt
      uint32_t csr = uex_ioread32(&dev->regs->channels[i].csr);
      dev->status[i] = 0;
      uex_event_signal(&dev->xfer_ev[i]);
    }
  }
}

图十三 – DMA IRQ Routine(通过UEX API 实现)

 

上图展示了DMA IP可以通过使用UEX API实现中断服务,读取DMA寄存器并在DMA传输完成之后发出通知。UEX组件库能够提供相同功能的代码,运行在UVM、或者嵌入式裸金属环境下,当然在操作系统下也能够运行。这种复用方式能够确保在项目早期即可对IP进行debug调试。

 

定义通用PSS接口

在一个项目中,测试代码会与一个IP类型的多个实例进行数据通信,那么我们也就需要定义一种通用的方式来选择实例以便进行测试

component pvm_dev_c {
  bit[7:0]		        devid;
  action pvm_dev_a {
 
  }
 
}

图十四 – 测试实现基础组件定义

 

如上图所示,我们可以通过提供一个devid变量来声明一个IP中的哪个实例正在被使用。当然如果保证复用性,则需要在一个项目所有的PSS描述文件使用相同的声明方式。

extend action wb_dma_c::mem2mem_a {
  exec body SV = """
    wb_dma_dev_mem2mem({{devid}}, {{channel}}, {{dat_i.addr}}, 
      {{dat_o.addr}}, {{dat_i.sz}}, {{trn_sz}});
  """;
}

图十五 – 引用组件的devid变量

 

图十五则展示了在PSS exec组件中如何引用devid变量。

 

最小化数据交换

在使用PSS时,我们也通常会尽可能的降低PSS模型与测试实现代码之间的数据交换量。这种最佳实践方式也被推荐使用在其他程序语言当中,例如System Verilog和Java[3]。一般而言,PSS描述文件是在抽象层面上定义出数据操作方式,测试实现代码来实现细节。

 

比如说,一个涉及到DMA transfer的PSS动作(action)。在数据传输到一个内存地址之前,该内存地址需要先初始化。那么PSS描述文件不会执行这个初始化操作,则是在其代码中指定了要初始化的存储区域,并将如何初始化内存的详细信息指定给测试代码来执行。

action gendata_a {
  input data_mem_b		dat_i;
  output data_ref_mem_b	dat_o;
 
  constraint dat_o.addr == dat_i.addr;
  constraint dat_o.sz == dat_i.sz;
}

图十六 – 初始化内存的Action定义示例

void pvm_gendata(uint32_t ref, uintptr_t addr, uint32_t sz) {
  pvm_rand_t r;
  void *addr_p = (void *)addr;
  int i;
 
  pvm_rand_init(&r, ref);
 
  for (i=0; i<sz; i++) {
    uint8_t v = pvm_rand_next(&r);
    uex_iowrite8(v, addr_p+i);
  }
}

图十七 – C测试代码进行内存初始化

 

上图的C代码通过gendata这个函数来实现了内存初始化。通过这种形式,PSS只需要定义抽象层面,所以其本身是一种描述性语言,保证了其高效性。

 

结论

便携式激励实现了多个方面的复用:验证层级的复用(垂直复用性),跨项目的复用实现(水平复用性),对于测试环境的复用。当然,我们也需要不同公司的不同需求来进行优先级的划分以保证PSS的优点得到有效的放大。以及对于公司内部现有工具的审核以保证前期资源投入不会被浪费,并继续投入资源研发符合公司产品的解决方案和PSS通用组件库。最后,通过为测试实现代码开发通用API来确保不同IP能够顺畅的在PSS复用环境下协同工作。

 

我们相信,上述建议确保您的组织能够最大化的利用PSS以提高自身的生产效率

 

引用

1.Accellera Portable Test and Stimulus Specification 1.0

2.M. Balance, “Managing and Automating Hw/Sw Tests from IP to SoC”, DVCon 2018

3.M. Dawson, G. Johnson, A. Low, “Best Practices for using the Java Native Interface”

 

source – Matthew Ballance, 2019/12, https://verificationacademy.com/verification-horizons/december-2019-volume-15-issue-3/designing-a-portable-stimulus-reuse-strategy

 

 

点击【阅读原文】可直达课程页面,马上试听

 

往期精彩:

V2Pro 2020秋M1 我对你的迷惑和选择都深以为然

V2Pro春季班普遍学撑了,秋季班7月报名你还敢来么

30w+还送股送房?60+IC企业2019薪资全面攀升!

UVM RAL模型:用法和应用

我们准备做第二期线下培训,依旧认真且严肃

如果你突然被裁员了,你的Plan B是什么?

[彩虹糖带你入门UVM]

理解UVM-1.2到IEEE1800.2的变化,掌握这3点就够

发表评论

邮箱地址不会被公开。 必填项已用*标注

陕ICP备18003383号-1