棋子 · 2月8日

一种解决 SoC 总线功能验证完备性的技术

1.前言

通过总线将各个 IP 通过总线连接起来的 SoC 芯片是未来的大趋势,也是缩短芯片开发周期,抢先进入市场的常用方法。如何确保各个 IP 是否正确连接到总线上,而且各 IP 的地址空间分配是否正确,是一件很棘手的事情。本文提出了一种新方法,可以解决 SoC 总线验证的诸多困难,既简单又快速地完成 SoC 总线功能验证

2.SoC 总线功能验证难点

在 SoC 芯片开发过程中,SoC 总线功能验证是确保各 IP 能否顺畅互动的关键,可以把每个 IP 比较一座座房子,总线就是房子和房子之间的路,路没修好或者修错了,房子建得再稳固再漂亮都没有什么用。以往的 SoC 总线面临诸多困难,导致 TB 代码又长又难以继承复用。主要的困难点有:

  • 总线的地址空间(memory map)频繁改动,TB 需要实时适配;
  • 需要定义和维护大量的宏;
  • 不方便遍历整个地址空间的全部各区间;
  • 区间和区间之间的从属关系在 TB 中很难反应出来;
  • 激励完备性难以保证,只能挑选一些典型的场景和区间去测试;

3.解决方案

本文提出的一种 SoC 总线功能验证方法完美解决了上述问题,非常使用,而且在真实项目中也实践过了。它有以下优点

  • 减少 TB 代码修改;
  • 无需大量宏定义;
  • 方便使用;
  • 确保完备性;
  • 可复用性强;

第 2 节的困难点主要是因为总线的 memory map 区间划分数量繁多和变化频繁引起的,因此解决方法的思路也是从 memory map 映射到 TB 的格式入手。好的 TB memory map 格式可以起到事半功倍的效果,思路请看下文。

3.1 文件准备

假设有以下一个用 Excel 表示的简单总线 memory map 表格,它有三级,表 1 为全部的地址空间大小,表 2 为表 1 中 IO 区间的更小粒度划分区间(Region),表 3 为表 2 中 SYS 区间的更小粒度划分区间。

image.png

图 1 Memory map 在 Excel 中呈现的形式

Memory map 表格的呈现形式具体可以和 Designer 讨论确定一种形式就好了,不一定要按上面的罗列的形式。

有了这个 Excel 表格的话,我们就可以写个脚本把每个区间的信息提取处理,包括区间的名字(Region name),起始地址(Start Address)和终止地址(End Address),以及区间和区间之间的所属关系等,比如表 2 中的 TPIU/UART/…/SYS 是 IO 区间的子区间,而表 3 的 EWM/PLL/SDIO/BOOT 是表 2 的子区间。

提取了这些信息有什么用呢?我们可以把每个区间或子区间都看作是一个 SystemVerilog 中的类(class)。如下图所示,这个类(Class: region)应包含了以下基本属性和方法。

Image

图 2 memory map 转换成 class 表示的形式

Region class 包含起止地址(start_addr, end_addr)和下一个子 region(sub_region)的连接信息,如果一个区间没有再细分子区间,那么 sub_region 队列的大小为 0。反之则不为 0,比如图 1 中表 1 的 IO 区间下有 5 个子区间,那么 IO 类的 sub_regions 的大小将为 5。然后 IO 区间下面的 SYS 子区间有更细分为 4 个子区间,那么 SYS 类的 sub_regions 的大小将为 4。以此类推,层层嵌套。再比如表 1 的 ROM 区间没有被细分,那么 ROM 类的 sub_regions 的大小将为 0。很简单,是吧。

每个紫色方框可以代表是图 1 中表 1 Main region 的一个区间,这样的话,会有 5 个紫色方框,map[enum]中的 enum 是表示 enum 类型,它的值是表 1 区间名的集合。

按以上方式形式的一个类似于金字塔的结果,最上面的是主区间,然后一层层往下细分为更新的区间,上级的区间拥有下一级区间的 class 句柄链接。而且所有区间都是使用 region class 来实现的,很方便 TB 通过嵌套方式来使用 memory map。

另外既然 memory map 的每个区间都抽象为类了,那么 SystemVerilog 的所有语法可以用于操控它了,比如可以在 start_addr 和 end_addr 之间生成任意的地址访问。而且 TB 可以在 class 里实现诸多使用的方法(task, function)去处理数据,大大减少了重复代码。

另外通过将 Excel 的内容全部抽取转换为 TB 格式的 mem_map 类,TB 可以逐级遍历去测试每个区间,再也不用担心漏了哪些地址区间没测了,也不怕 RTL 频繁改动。

当然,有一个小难点就是需要将 Excel 转换为图 2 形式的 mem_map,这个我就不多讲了,脚本大家可以自己写,而且根据这个思路,可以实现很多有意思的功能,亲测实用。

3.2 效果展示

使用脚本把图 1 的 Excel 内容转成 TB 代码的参考代码如下:

// ==================================================================
// Main region: ROM
// ==================================================================
main[ROM] = region::type_id::create("ROM");
main[ROM].start_addr  = 'h8A0000000;
main[ROM].end_addr    = 'h99FFFFFFF;

// ==================================================================
// Main region: DDR
// ==================================================================
main[DDR] = region::type_id::create("DDR");
main[DDR].start_addr  = 'h120000000;
main[DDR].end_addr    = 'h89FFFFFFF;

// ==================================================================
// Main region: RESERVED_A
// ==================================================================
main[RESERVED_A] = region::type_id::create("RESERVED_A");
main[RESERVED_A].start_addr  = 'h41000000;
main[RESERVED_A].end_addr    = 'h11FFFFFFF;

// ==================================================================
// Main region: USB
// ==================================================================
main[USB] = region::type_id::create("USB");
main[USB].start_addr  = 'h40000000;
main[USB].end_addr    = 'h040FFFFFF;

// ==================================================================
// Main region: IO
// ==================================================================
main[IO] = region::type_id::create("IO");
main[IO].start_addr  = 'h0;
main[IO].end_addr    = 'h03FFFFFFF;
// Sub: TPIU  (IO -> TPIU)
sub_rgn = region::type_id::create("TPIU");
sub_rgn.start_addr  = 'h3D000000;
sub_rgn.end_addr    = 'h3FFFFFFF;
main[IO].sub_rgn.push_back(sub_rgn);
// Sub: UART  (IO -> UART)
sub_rgn = region::type_id::create("UART");
sub_rgn.start_addr  = 'h3C000000;
sub_rgn.end_addr    = 'h3CFFFFFF;
main[IO].sub_rgn.push_back(sub_rgn);
// Sub: GPU_REGISTER  (IO -> GPU_REGISTER)
sub_rgn = region::type_id::create("GPU_REGISTER");
sub_rgn.start_addr  = 'h3B000000;
sub_rgn.end_addr    = 'h3BFFFFFF;
main[IO].sub_rgn.push_back(sub_rgn);
// Sub: DMC_REGISTER  (IO -> DMC_REGISTER)
sub_rgn = region::type_id::create("DMC_REGISTER");
sub_rgn.start_addr  = 'h30000000;
sub_rgn.end_addr    = 'h3AFFFFFF;
main[IO].sub_rgn.push_back(sub_rgn);
// Sub: SYS  (IO -> SYS)
sub_rgn = region::type_id::create("SYS");
sub_rgn.start_addr  = 'h0;
sub_rgn.end_addr    = 'h2FFFFFFF;
main[IO].sub_rgn.push_back(sub_rgn);
// SubSub: EWM  (IO -> SYS -> EWM)
sub_rgn = region::type_id::create("EWM");
sub_rgn.start_addr  = 'h25000000;
sub_rgn.end_addr    = 'h2FFFFFFF;
main[IO].sub_rgn[$].sub_rgn.push_back(sub_rgn);
// SubSub: PLL  (IO -> SYS -> PLL)
sub_rgn = region::type_id::create("PLL");
sub_rgn.start_addr  = 'h21000000;
sub_rgn.end_addr    = 'h24FFFFFF;
main[IO].sub_rgn[$].sub_rgn.push_back(sub_rgn);
// SubSub: SDIO  (IO -> SYS -> SDIO)
sub_rgn = region::type_id::create("SDIO");
sub_rgn.start_addr  = 'h200000;
sub_rgn.end_addr    = 'h20FFFFFF;
main[IO].sub_rgn[$].sub_rgn.push_back(sub_rgn);
// SubSub: BOOT  (IO -> SYS -> BOOT)
sub_rgn = region::type_id::create("BOOT");
sub_rgn.start_addr  = 'h0;
sub_rgn.end_addr    = 'h001FFFFF;
main[IO].sub_rgn[$].sub_rgn.push_back(sub_rgn);

END

作者:沪闵菜菜子
文章来源:专芯致志er

推荐阅读

更多 IC 设计干货请关注IC 设计专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。

推荐阅读
关注数
22088
内容数
1345
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息