1、前言
通過總線將各個IP通過總線連接起來的SoC芯片是未來的大趨勢,也是縮短芯片開發(fā)周期,搶先進入市場的常用方法。如何確保各個IP是否正確連接到總線上,而且各IP的地址空間分配是否正確,是一件很棘手的事情。本文提出了一種新方法,可以解決SoC總線驗證的諸多困難,既簡單又快速地完成SoC總線功能驗證。
2、SoC總線功能驗證難點
在SoC芯片開發(fā)過程中,SoC總線功能驗證是確保各IP能否順暢互動的關(guān)鍵,可以把每個IP比較一座座房子,總線就是房子和房子之間的路,路沒修好或者修錯了,房子建得再穩(wěn)固再漂亮都沒有什么用。以往的SoC總線面臨諸多困難,導(dǎo)致TB代碼又長又難以繼承復(fù)用。主要的困難點有:
總線的地址空間(memory map)頻繁改動,TB需要實時適配;
需要定義和維護大量的宏;
不方便遍歷整個地址空間的全部各區(qū)間;
區(qū)間和區(qū)間之間的從屬關(guān)系在TB中很難反應(yīng)出來;
激勵完備性難以保證,只能挑選一些典型的場景和區(qū)間去測試;
3、解決方案
本文提出的一種SoC總線功能驗證方法完美解決了上述問題,非常使用,而且在真實項目中也實踐過了。它有以下優(yōu)點:
減少TB代碼修改;
無需大量宏定義;
方便使用;
確保完備性;
可復(fù)用性強;
第2節(jié)的困難點主要是因為總線的memory map區(qū)間劃分數(shù)量繁多和變化頻繁引起的,因此解決方法的思路也是從memory map映射到TB的格式入手。好的TB memory map格式可以起到事半功倍的效果,思路請看下文。
3.1 文件準備
假設(shè)有以下一個用Excel表示的簡單總線memory map表格,它有三級,表1為全部的地址空間大小,表2為表1中IO區(qū)間的更小粒度劃分區(qū)間(Region),表3為表2中SYS區(qū)間的更小粒度劃分區(qū)間。
圖1 Memory map在Excel中呈現(xiàn)的形式
Memory map表格的呈現(xiàn)形式具體可以和Designer討論確定一種形式就好了,不一定要按上面的羅列的形式。
有了這個Excel表格的話,我們就可以寫個腳本把每個區(qū)間的信息提取處理,包括區(qū)間的名字(Region name),起始地址(Start Address)和終止地址(End Address),以及區(qū)間和區(qū)間之間的所屬關(guān)系等,比如表2中的TPIU/UART/…/SYS是IO區(qū)間的子區(qū)間,而表3的EWM/PLL/SDIO/BOOT是表2的子區(qū)間。
提取了這些信息有什么用呢?我們可以把每個區(qū)間或子區(qū)間都看作是一個SystemVerilog中的類(class)。如下圖所示,這個類(Class: region)應(yīng)包含了以下基本屬性和方法。
圖2 memory map轉(zhuǎn)換成class表示的形式
Region class包含起止地址(start_addr, end_addr)和下一個子region(sub_region)的連接信息,如果一個區(qū)間沒有再細分子區(qū)間,那么sub_region隊列的大小為0。反之則不為0,比如圖1中表1的IO區(qū)間下有5個子區(qū)間,那么IO類的sub_regions的大小將為5。然后IO區(qū)間下面的SYS子區(qū)間有更細分為4個子區(qū)間,那么SYS類的sub_regions的大小將為4。以此類推,層層嵌套。再比如表1的ROM區(qū)間沒有被細分,那么ROM類的sub_regions的大小將為0。很簡單,是吧。
每個紫色方框可以代表是圖1中表1 Main region的一個區(qū)間,這樣的話,會有5個紫色方框,map[enum]中的enum是表示enum類型,它的值是表1區(qū)間名的集合。
按以上方式形式的一個類似于金字塔的結(jié)果,最上面的是主區(qū)間,然后一層層往下細分為更新的區(qū)間,上級的區(qū)間擁有下一級區(qū)間的class句柄鏈接。而且所有區(qū)間都是使用region class來實現(xiàn)的,很方便TB通過嵌套方式來使用memory map。
另外既然memory map的每個區(qū)間都抽象為類了,那么SystemVerilog的所有語法可以用于操控它了,比如可以在start_addr和end_addr之間生成任意的地址訪問。而且TB可以在class里實現(xiàn)諸多使用的方法(task, function)去處理數(shù)據(jù),大大減少了重復(fù)代碼。
另外通過將Excel的內(nèi)容全部抽取轉(zhuǎn)換為TB格式的mem_map類,TB可以逐級遍歷去測試每個區(qū)間,再也不用擔(dān)心漏了哪些地址區(qū)間沒測了,也不怕RTL頻繁改動。
當(dāng)然,有一個小難點就是需要將Excel轉(zhuǎn)換為圖2形式的mem_map,這個我就不多講了,腳本大家可以自己寫,而且根據(jù)這個思路,可以實現(xiàn)很多有意思的功能,親測實用。
3.2 效果展示
使用腳本把圖1的Excel內(nèi)容轉(zhuǎn)成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);