UVM实战 卷I学习笔记12——UVM中代码的可重用性(2)


功能的模块化: 小而美

Linux的设计哲学: 小而美

广大IC开发者中,使用Linux的用户占绝大部分,尤其对验证人员来说更是如此。Linux+如此受欢迎的部分原因之一是它提供了众多的工具,如ls命令、grep命令、wc命令、echo命令等,使用这些命令的组合可以达到多种目的。这些小工具的共同点是每个都非常小但功能清晰。它们是Linux设计哲学中小而美的典型代表。

与小而美相对的就是大而全。比如下述命令就完全可以使用一个命令实现:

ls | grep "aaa" | wc

这个命令组合起来相当于集合了ls、grep、wc三个命令的参数,将这个命令命名为lsgrepwc。当查看这个命令的用法时,很多用户会被冗长的参数列表吓坏。当看到一个参数时,用户要自己判断这个参数属于三个功能中的哪一个。多出来的判断时间就是用户为大而全付出的时间。

小而美的本质是功能模块化、标准化,但小不一定意味着美。以前面的ls与grep命令为例,如果当初命令设计者取了ls一半的功能和grep一半的功能组成命令lgr,剩下的功能再拼凑成sep,这两个是什么命令?恐怕没有几个人会知道,这样的设计不知道会令多少用户崩溃。所以小而美的前提是功能模块划分要合理,一个不合理的划分是谈不上美的。

同时,小而美也不能无限制地追求小。以ls为例,如果将ls、ls-a、ls-l分别当成三个不同的命令,那么也是一种不合理的划分。这三个新的命令有太多共同的参数,比如–color参数等。拆分的同时,参数却是原样拷贝到三个新的命令中,造成了参数的冗余。

在验证平台的设计中要尽量做到小而美,避免大而全。

小而美与factory机制的重载

factory机制重要的一点是提供重载功能。一般来说,如果要用B类重载A类,那么B类是要派生自A类的。在派生时要保留A类的大部分代码,只改变其中一小部分。

假设原始A_driver的drive_one_pkt任务如下:

task A_driver::drive_one_pkt;
	drive_preamble();
	drive_sfd();
	drive_data();
endtask

上述代码将一个drive_one_pkt任务又分成了三个子任务。现在如果要构造一个sfd错误的例子,那么只需要从A_driver派生一个B_driver,并且重载其drive_sfd任务即可。

如果上述代码不是分成三个子任务,而是一个完整的任务:

task A_driver::drive_one_pkt;
	//drive preamble//drive sfd//drive data
	…
endtask

那么在B_driver中需要重载的是drive_one_pkt这个任务:

task B_driver::drive_one_pkt;
	//drive preamble//drive new sfd//drive data
	…
endtask

此时,drive preamble和drive data部分代码需要复制到新的drive_one_pkt中。对于程序员来说要尽量避免复制的使用

  • 在复制中由于不小心,很容易出现各种各样的错误。虽然这些错误只是短期的,马上就能修订,但毕竟要为此花费额外的时间。
  • 从长远来看,如果drive data相关的代码稍微有一点变动,此时A_driver和B_driver的drive_one_pkt都需要修改,这又需要额外花费时间。同样的代码只在验证平台上出现一处,如果要重用,将它们封装成可重载的函数/任务或者类

放弃建造强大sequence的想法

UVM的sequence功能非常强大,很多用户喜欢将他们的sequence写得非常完美,他们的目的是建造通用的sequence,有些用户甚至执着于一个sequence解决验证平台中所有的问题,使用时只需要配置参数即可。

以一个my_sequence为例,有些用户可能希望这个sequence具有下列功能:

  • 能够产生正常的以太网包
  • 通过配置参数产生CRC错误的包
  • 通过配置参数产生sfd错误的包
  • 通过配置参数产生preamble错误的包
  • 通过配置参数产生CRC与sfd同时错误的包
  • 通过配置参数产生CRC与preamble同时错误的包
  • 通过配置参数产生sfd与preamble同时错误的包
  • 通过配置参数产生CRC、sfd与preamble同时错误的包
  • 通过配置参数控制错误的概率
  • 通过配置参数选择要发送的数据是随机化的还是从文件读取
  • 通过配置参数选择如果从文件读取,那么是多文件还是单文件
  • 通过配置参数选择如果从文件读取,那么使用哪一种文件格式
  • 通过配置参数选择是否将发送出去的包写入文件中
  • 通过配置参数选择长包、中包、短包各自的阈值长度
  • 通过配置参数选择长包、中包、短包的发送比例通过配置参数选择是否在包的负载中加入当前要发送的包的序号,以便于调试
    ……

上述sequence确实是一个非常通用、强悍的sequence。但这个sequence存在两个问题:

  1. 这个sequence代码量非常大,分支众多,后期维护相当麻烦。如果代码编写者与维护者不是同个人,那么对维护者来说简直就是灾难。即使代码编写者与维护者是同个人,在一段时间之后自己也可能被自己以前写的东西感到迷惑不已。
  2. 使用这个sequence的人面对如此多的参数要如何选择呢?他有时只想使用其中最基本的一个功能但却不知道怎么配置,只能所有参数都看一遍。如果看一遍能看懂还好,但有时即使看两三遍也看不懂。

如果用户非常坚持上述超级强大的sequence,那么请一定要做到以下两点之一:

  • 有一份完整的文档介绍它
  • 有较多的代码注释

文档的重视程度因公司而异,目前国内外的IC公司对于验证文档的重视普遍不够,很少有公司会为一个sequence建立专门的文档。当代码完成后很少会有代码编写者愿意再写文档。即使公司制度规定必须写,文档的质量也有高低之分且存在文档的后期维护问题。当sequence建立后为其建一个文档,但后来sequence升级,文档却没有升级。文档与代码不一致,这是目前IC公司中经常存在的问题。

代码的注释与代码编写者的编码习惯有关。目前仅有少数编码习惯好的人能做到质量较好的注释。验证人员编写的代码通常比较灵活且更新频率较快。当设计变更时相关的验证代码就要变更。很多验证人员并没有写注释的习惯,即使有写注释,但当后来代码变更时,注释可能已经落伍了。

因此强烈建议不要使用强大的sequence。可将一个强大的sequence拆分成小的sequence,如:

  • normal_sequence
  • crc_err_sequence
  • rd_from_txt_sequence
    ……

尽量做到一看名字就知道这个sequence的用处,这样可以最大程度上方便自己,方便大家。

参数化的类

参数化类的必要性

代码的重用分为很多层次。凡是在某个项目中开发的代码用于其他项目,都可以称为重用,如:

  • A用户在项目P中的代码被A用户自己用于项目P
  • A用户在项目P中的代码被A用户自己用于项目Q
  • A用户在项目P中的代码被B用户用于项目Q
  • A用户在项目P中开发的代码被B用户或者更多的用户用于项目P或项目Q

以上四种应用场景对代码可重用性的要求逐渐提高。第一种可能只是几个sequence被几个不同的测试用例使用;在最后一种可能A用户开发的是一个总线功能模型,大家都会重用这些代码。

为了增加代码的可重用性,参数化的类是一个很好的选择。UVM广泛使用了参数化的类。对用户来说使用最多的参数化的类莫过于uvm_sequence,其原型为:

virtual class uvm_sequence #(type REQ=uvm_sequence_item, type RSP = REQ) 
extends uvm_sequence_base;

在派生uvm_sequence时指定参数的类型,即transaction的类型,可以方便产生transaction并建立测试用例。 除了uvm_sequence外,还有uvm_analysis_port等,不再一一列举。

相比普通的类,参数化的类在定义时会有些复杂,其古怪的语法可能会使人望而却步。并非所有类一定要定义成参数化的类。对于很多类来说根本没有参数可言,如果定义成参数化的类,根本没有任何优势可言。所以定义成参数化的类的前提是这个参数是有意义的、可行的

UVM对参数化类的支持

UVM对参数化类的支持首先体现在factory机制注册上。前面已经提到了uvm_object_param_utils和uvm_component_param_utils这两个用于参数化的object和参数化的component注册的宏。

UVM的config_db机制可以用于传递virtual interfaceSV支持参数化的interface

interface bus_if#(int ADDR_WIDTH=16, int DATA_WIDTH=16)(input clk, input rst_n);
	logic bus_cmd_valid;
	logic bus_op;
	logic [ADDR_WIDTH-1:0] bus_addr;
	logic [DATA_WIDTH-1:0] bus_wr_data;
	logic [DATA_WIDTH-1:0] bus_rd_data;
endinterface

config_db机制同样支持传递参数化的interface

uvm_config_db#(virtual bus_if#(16, 16))::set(null, "uvm_test_top.env.bus_agt.mon", 
                                             "vif" bif);
uvm_config_db#(virtual bus_if#(ADDR_WIDTH, DATA_WIDTH))::get(this, "", "vif", vif)

sequence机制同样支持参数化的transaction

class bus_sequencer#(int ADDR_WIDTH=16, int DATA_WIDTH=16) extends uvm_sequencer 
										#(bus_transaction#(ADDR_WIDTH, DATA_WIDTH));

很多参数化的类都有默认参数,用户在使用时经常会使用默认参数。但UVM的factory机制不支持参数化类中的默认参数。假如有如下的agent定义:

class bus_agent#(int ADDR_WIDTH=16, int DATA_WIDTH=16) extends uvm_agent ;

在声明agent时可以按照如下写法来省略参数

bus_agent bus_agt;

在实例化时必须将省略的参数加上

bus_agt = bus_agent#(16, 16)::type_id::create("bus_agt", this);
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值