任何编程语言中,函数都是进行功能封装,模块化设计的必备手段之一。在Systemverilog中也不例外,SV中有function和task之分,都属于广义上的函数。
在函数的设计和编码规范中,我们往往会尽量减少入参的个数,一方面是为了函数的简洁和可读性,另一方面也是对环境质量和仿真性能有所裨益。
此前,在验证环境中看到一个对用户开放的API函数,含有多达6个传递参数。便想着,如何能减少函数传递参数的个数?下面是一些粗浅的见解和尝试,分享如下。
一开始的API函数原型如下,目的在于实现一个支持不同需求的总线读写请求,如读/写地址和数据,总线master的ID,是否返回resp,安全或非安全访问,byte操作,device/cacheble。
用户侧的调用大概是这样的:
bus_write(32'h1000_0000,32'hA5A5_5A5A,,1,write_resp,1,0);
其实无论从API的维护和用户的使用角度来看,似乎都可以做一些简单的改进。
用户侧
首先从用户侧来看,即使API函数的参数无法减少,也可以使用参数名bind的方式,以增加可读性,如:
开发维护侧
单一功能函数划分
开发侧而言,可以采用划分单一功能函数的方法进行折中处理。相比对用户开放大而全的API函数而言,换而开放多个单一功能的函数,如安全/非安全写,device/非device操作,带resp/不带resp操作。
如此上述的bus_write可以拆分为:
task bus_write_sec(input bit[63:0] x_addr,
input bit[63:0] x_write_data,
input bus_idx x_bus_id=BUS_VIP_0,
input bit is_non_sec = 1);
//.......
endtask
task bus_write_device(input bit[63:0] x_addr,
input bit[63:0] x_write_data,
input bus_idx x_bus_id=BUS_VIP_0,
input bit is_device = 1);
//.......
endtask
task bus_write_with_resp(input bit[63:0] x_addr,
input bit[63:0] x_write_data,
input bus_idx x_bus_id=BUS_VIP_0,
input bit is_get_resp = 1);)
//.......
endtask
而每个单一功能函数的实现上,既可以调用原"大而全"的函数,也可以自行开发,如此可把复杂内聚化,对外易用简单化。
改变参数传递的数据结构--联合数组
如果用户侧仍是需要"大而全"的API函数,比如随机这些参数进行测试,那可以尝试改变用户和API函数参数传递的数据结构。
联合数组,也称字典,是一种非常使用而又功能强大的数据结构,是"架构级的数组形式"。---一种架构级的数组形式 联合数组
我们可以把众多的参数封装在联合数组内,由用户传递对应的key-value。如此原来函数的入参结构可以修改为:
task bus_write_api(
input bit[63:0] x_addr,
input bit[63:0] x_write_data,
input bit[31:0] x_ctrl_para[string]='{}
);
//....
endtask
用户侧的调用修改为:
对于用户未传入的参数,API函数内部自行取得默认值即可。
参数化函数
此外还有一种可以实现,但好像有没有太多收益的办法,就是把原来函数的一些入参成为参数化函数的parameter。在调用函数时,把入参作为函数的parameter。
在这种方法下,原API定义改写如下:
用户侧的调用则修改为:
bus_operation #(BUS_VIP_0,1,0,1)::bus_write_api(32'h1000_0000,32'hA5A5_5A5A);
bus_operation #(.x_bus_idx(BUS_VIP_0),
.x_get_resp(1),
.is_device(0),
.is_non_sec(1))::bus_write_api(32'h1000_0000,32'hA5A5_5A5A);
但这种方式需要virtual class内部的task是static类型,某些场景下还是会受到限制。
这里是验证芯发现,原创不易,欢迎赞赏、点赞、在看和分享。
感谢关注和支持。