目錄
01 README
02 MCDF設計結構
2.1 功能描述
2.2 設計結構
2.3 接口與時序
2.3.1 系統信號接口
2.3.2 通道從端接口
2.3.3 整形器接口
2.3.4 控制寄存器接口
2.3.4.1 接口時序圖
2.3.4.2 各數據位信息
03 驗證框圖
3.1 reg_pkg
3.1.1 reg_trans
3.1.2 reg_driver
3.1.3 reg_generator
3.1.4 reg_monitor
3.1.5 reg_agent
3.2 fmt_pkg
3.2.1 fmt_trans
3.2.2 fmt_driver
3.2.3 fmt_generator
3.2.4 fmt_monitor
3.2.5 fmt_agent
3.3 mcdf_pkg
3.3.1 mcdf_refmod
3.3.2 mcdf_checker
3.3.3 mcdf_env
3.3.4 mcdf_base_test
3.3.5?mcdf_reg_write_read_test
3.3.6?mcdf_reg_illegal_access_tets
3.3.7?mcdf_arbiter_priority_test
3.3.8?mcdf_channel_disbale_test
3.3.9?mcdf_coverage
04 覆蓋率收集
01 README
????????最最開始系統地學習芯片領域相關只是大概追溯到23年12月初,此前只是有一些Verilog和數電基礎,做過兩個小的設計項目。整個12月和1月幾乎都在學習FPGA的相關課程,仍記得在跨年夜在宿舍敲UART串口通信那篇文章@UART串口通信_uart串口通訊-CSDN博客,但同時也感受到在國外沒有開發板的情況下確實很難把這些東西學好。因此年后轉而開始學習更偏向于軟件的數字IC驗證。期間也是斷斷續續,國內學校的事情太多了,因此總是不能全身心投入,加上自己軟件基礎并不好,C++,python這些語言學的都不好。大概花了3個月的時間基本學完了SV的語法,驗證的基本流程和熟悉驗證軟件的使用,但也不敢說能夠熟練掌握。目前算是進入了下一個時間節點,開始學習UVM驗證環境。因此基于MCDF項目對此前學習進行一個總結。大佬們輕噴。
02 MCDF設計結構
2.1 功能描述
????????設計模塊的全稱叫做multi-channel data formatter,他可以將上行多個數據通道的數據通過內部的SLAVE, FIFO給到仲裁器Arbiter, Arbiter選擇從不同的FIFO中讀取數據,給到下端的Formatter, 對數據進行整形,以數據包的形式送給下行的數據接收端。整個設計通過Register接受外部命令,對MCDF的功能進行修改。
2.2 設計結構
2.3 接口與時序
2.3.1 系統信號接口
clk(0)? :時鐘信號
rstn(0):復位信號
2.3.2 通道從端接口
CHx_DATA(31:0):通道數據輸入。
CHx_VALID(0):通道數據有效標志信號,高位有效。
CHx_READY(0):通道數據接收信號,高位表示接收成功
當valid為高時,表示要寫入數據。如果該時鐘周期ready為高,則表示已經將數據寫入;如果該時鐘周期ready為低,則需要等到ready為高的時鐘周期才可以將數據寫入。
2.3.3 整形器接口
FMT_CHID(1:0):整形數據包的通道ID號。
FMT_LENGTH(4:0):整形數據包長度信號。
FMT_REQ(0):整形數據包發送請求。
FMT_GRANT(0):整形數據包被允許發送的接受標示。
FMT_DATA(31:0):數據輸出端口。
FMT_START(0):數據包起始標示。
FMT_END(0):數據包結束標示。
- 整形器的作用是對數據進行打包,以數據包的形式向下行數據接收端傳遞數據,可以選擇的數據包長度為4,8,16,32,這是由FMT_LENGTH信號來進行控制。
- 在發送數據包時,首先應該將FMT_REQ置為高,同時等待接收端的FMT_GRANT拉高,此時表示接收端可以進行數據接受。當FMT_GRANT變為高時,應該在下一個周期將FMT_REQ置為低。FMT_START也必須在接收到FMT_GRANT高有效的下一個時鐘被置為高,且需要維持一個時鐘周期。在FMT_START被置為高有效的同一個周期,數據也開始傳送,數據之間不允許有空閑周期,即應該連續發送數據,直到發送完最后一個數據時,FMT_END也應當被置為高并保持一個時鐘周期。在數據包傳輸過程中FMT_CHID和FMT_LENGTH應該保持不變,直到數據包發送完畢。
- 相鄰的數據包之間應該至少有一個時鐘周期的空閑,即FMT_END從高位被拉低以后,至少需要經過一個時鐘周期,FMT_REQ才可以被再次置為高。
2.3.4 控制寄存器接口
CMD(1:0):寄存器讀寫命令。
CMD_ADDR(7:0):寄存器地址。
CMD_DATA_IN(31:0):寄存器寫入數據。
CMD_DATA_OUT(31:0):寄存器讀出數據。
2.3.4.1 接口時序圖
當cmd為寫指令時,需要把數據cmd_data_in寫入到cmd_addr對應的寄存器中;當cmd為讀指令時,即需要從cmd_addr對應的寄存器中讀取數據,并在下一個周期,將數據驅動至cmd_data_out接口。
2.3.4.2 各數據位信息
- 地址0x00 通道控制寄存器 32bits 讀寫寄存器
bit(0):通道使能信號。1為打開,0位關閉。復位值為1。
bit(2:1):優先級。0為最高,3為最低。復位值為3。
bit(5:3):數據包長度,解碼對應表為, 0對應長度4,1對應長度8,2對應長度16,3對應長度32,其它數值(4-7)均暫時對應長度32。復位值為0。
bit(31:6):保留位,無法寫入。復位值為0。
- 地址0x10 通道狀態寄存器 32bits 只讀寄存器
bit(7:0):上行數據從端FIFO的可寫余量,同FIFO的數據余量保持同步變化。復位值為FIFO的深度數。
bit(31:8):保留位,復位值為0。
03 驗證框圖
一共分為chnl_pkg, reg_pkg, fmt_pkg和mcdf_pkg, 各個包之間通過interface進行連接
3.1 reg_pkg
3.1.1 reg_trans
寄存器傳輸類,包含地址、命令、數據和響應字段
class reg_trans;rand bit[7:0] addr;rand bit[1:0] cmd ;rand bit[31:0] data;bit rsp;constraint cstr{soft cmd inside{'WRITE, 'READ, 'IDLE};soft addr inside{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR, 'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};addr[7:4] ==0 && cmd == 'WRITE -> soft data [31:6] == 0;//讀寫寄存器要求高26位為0soft addr [7:5] == 0;addr[4] == 1 -> soft cmd == 'READ;//地址為0x10,0x14,0x18的是通道1,2,3的只讀存儲器};function reg_trans clone() ;reg_trans c = new() ;c.addr = this.addr;c.cmd = this.cmd ;c.data = this.data;c.rsp = this.rsp ;return c ;endfunctionfunction string sprint();string s ;s = {s, $sformatf("========================n")};s = {s, $sformatf("reg_trans object content is as below:\n")};s = {s, $sformatf("addr = %2x:\n", this.addr)};//兩位16進制數s = {s, $sformatf("cmd = %2b:\n", this.cmd )};//2位2進制數s = {s, $sformatf("data = %8x:\n", this.data)};//8位16進制數s = {s, $sformatf("rsp = %0d:\n", this.rsp )};//10進制數,無字符寬度s = {s, $sformatf("=======================\n")};return s;endfunctionendclass
3.1.2 reg_driver
寄存器驅動器,用于生成寄存器傳輸事務并將其發送到接口。
該類包括了一個 do_drive 任務來驅動事務的發送,一個 do_reset 任務來處理復位信號,以及一個 reg_write 任務來處理寄存器的讀寫操作。
class reg_driver;local string name;local virtual reg_intf intf;mailbox #(reg_trans) reg_mb;mailbox #(reg_trans) rsp_mb;function new(string name = "reg_driver");this.name = name;endfunctionfunction void set_interface (virtual reg_intf intf);if(intf == null)$error("interface is null");elsethis.intf == intf;endfunctiontask run();forkthis.do_drive();this.do_reset();joinendtasktask do_reset();forever begin@(negedge intf.rstn);intf.cmd_addr <= 0 ;intf.cmd <= 'IDLE;intf.cmd_data_m2s <= 0 ;endtask do_drive();reg_trans req, rsp;@(posedge intf.rstn);forever beginthis.req_mb.get(req);this.reg_write(req);rsp = req.clone();rsp.rsp = 1;this.rsp_mb.put(rsp);endendtasktask reg_write (reg_trans t);@posedge (intf.clk iff intf.rstn);//這是一個復合條件,iff 表示 "if and only if",即當且僅當。//所以整個條件表示:只有當 intf.rstn 為邏輯真時,才觸發在 intf.clk 的上升沿執行操作。case(t.cmd)'WRITE:beginintf.drv_ck.cmd_addr <= t.addr;intf.drv_ck.cmd <= t.cmd;intf.drv.cmd_data_m2s <= t.data; end'READ :beginintf.drv_ck.cmd_addr <= t.addr;intf.drv_ck.cmd <= t.cmd;repeat(2) @(negedge intf.clk);//為何要等兩個下降沿?t.data = intf.cmd_data_s2m;end'IDLE :beginthis.reg_idle();enddefault: $error("command %b is illegal", t.cmd);endcase $display("%0t reg driver [%s] sent addr %2x,cmd %2b,data %8x",$time,name,t.addr,t.cmd,t.data);endtasktask reg_idle();@(posedge intf.clk);intf.drv_ck.cmd_addr <= 0 ;intf.drv_ck.cmd_addr <= 0 ;intf.drv_ck.cmd <= 'IDLE;endtaskendclass
3.1.3 reg_generator
生成寄存器傳輸事務,并提供一個 send_trans 任務來發送事務并處理響應。
class reg_generator;rand bit [7:0] addr = -1;rand bit [1:0] cmd = -1;rand bit [31:0]data = -1;mailbox #(reg_trans) req_mb;mailbox #(reg_trans) rsp_mb;reg_trans reg_req[$];constraint cstr{soft addr == -1;soft cmd == -1;soft data == -1;}function new();this.req_mb = new();this.rsp_mb = new();endfunctiontask start();send_trans();endtask//generate transaction and put into local mailboxtask send_trans();reg_tans req,rsp;req = new();assert(req.randomize with{local::addr >= 0 -> addr == local::addr;local::cmd >= 0 -> cmd == local::cmd;local::data >= 0 -> data == local::data;})else $fatal("register packet randomization fail");$display(req.sprint());this.req_mb.put(req);this.rsp_mb.get(rsp);$display(rsp.sprint());if(req.cmd == 'READ)this.data = rsp.data;assert(rsp.rsp)//確保收到的響應中包含有效的響應標志。如果響應標志無效,則通過 $error 報錯。else $error("[RSPERR] 0%t error response received!", $time);endtaskfunction string sprint();string s ;s = {s, $sformatf("========================n")};s = {s, $sformatf("reg_trans object content is as below:\n")};s = {s, $sformatf("addr = %2x:\n", this.addr)};//兩位16進制數s = {s, $sformatf("cmd = %2b:\n", this.cmd )};//2位2進制數s = {s, $sformatf("data = %8x:\n", this.data)};//8位16進制數s = {s, $sformatf("rsp = %0d:\n", this.rsp )};//10進制數,無字符寬度s = {s, $sformatf("=======================\n")};return s;endfunction//用于在隨機化后顯示對象的內容。function void post_randomize();string s;s = {"AFTER RANDOMIZE \n",this.sprint()};$display (s);endfunctionendclass
3.1.4 reg_monitor
寄存器監視器,用于監視寄存器傳輸事務并將其存儲到郵箱中。
class reg_monitor;local string name;local virtual reg_intf intf;mailbox #(reg_trans) mon_mb;function new(string name = "reg_monitor");this.name = name;endfunctionfunction void set_interface(virtual reg_intf intf);if(intf == null)$error("interface handle is null");elsethis.intf = intf;endfunction task run();this.mon_trans();endtask//其功能是監視寄存器接口的傳輸事務,并將監視到的信息存儲到郵箱 mon_mb 中task mon_trans();reg_trans m;forever begin@(posedge intf.clk iff(intf.rstn && intf.mon_ck.cmd != 'IDLE));m = new();m.addr = intf.mon_ck.cmd_addr;m.cmd = intf.mon_ck.cmd;if(intf.mon_ck.cmd == 'READ)begin@(posedge intf.clk);m.data = intf.mon_ck.cmd_data_s2m;endmon_mb.put(m);$display("%0t reg driver [%s] sent addr %2x,cmd %2b,data %8x",$time,this.name,m.addr,m.cmd,m.data);endendtaskendclass
3.1.5 reg_agent
組合?reg_driver 和 reg_monitor,并提供了一個 run 任務來同時運行驅動器和監視器。
class reg_agent;local string name;reg_driver driver;reg_monitor monitor;local virtual reg_intf vif;function new(string name = "reg_agent");this.name = name;this.driver = new({name,".driver"});this.monitor = new({name,".monitor"});endfunctionfunction void set_interface(virtual reg_intf vif);this.vif = vif;driver.set_interface(vif);monitor.set_interface(vif);endfunctiontask run();forkdriver.run();monitor.run();joinendtaskendclass
3.2 fmt_pkg
import rpt_pkg::*;typedef enum {SHORT_FIFO, MED_FIFO, LONG_FIOF, ULTRA_FIFO} fmt_fifo_t;typedef enum {LOW_WIDTH, MED_WIDTH, HIGH_WIDTH, ULTRA_WIDTH} fmt_bandwidth_t;typedef enum bit[2:0]{FMT_REQ = 3'b000,FMT_WIDTH_GRANT = 3'b001,FMT_START = 3'b011,FMT_SEND = 3'b010,FMT_END = 3'b110,FMT_IDLE = 3'b111} fmt_state_t;
3.2.1 fmt_trans
模擬數據的比較和打印
class fmt_trans;rand fmt_fifo_t fifo;rand fmt_bandwidth_t bandwidth;bit [9:0] length;bit [31:0]data[];bit [1:0] ch_id;bit rsp;constraint cstr{soft fifo == MED_FIFO ;soft bandwidth == MED_WIDTH;};function fmt_trans clone();fmt_trans c = new();c.fifo = this.fifo;c.bandwidth = this.bandwidth;c.length = this.length;c.data = this.data;c.ch_id = this.ch_id;c.rsp = this.rsp;return c;endfunctionfunction string sprint();string s ;s = {s, $sformatf("========================\n")};s = {s, $sformatf("fmt_trans object content is as below:\n")};s = {s, $sformatf("fifo = %s:\n", this.fifo)};s = {s, $sformatf("bandwidth = %s:\n", this.bandwidth )};s = {s, $sformatf("length = %s:\n", this.length)};s = {s, $sformatf("rsp = %0d:\n", this.rsp )};foreach (data[i]) s = {s,$sformatf("data[%0d] = %8x \n", i, this.data[i])};s = {s, $sformatf("ch_id = %0d:\n", this.ch_id)};s = {s, $sformatf("rsp = %0d:\n", this.rsp )}; s = {s, $sformatf("=======================\n")};return s;endfunction//??為啥要做這兩個比較呢,fmt_trans例化后的內容不應該就和fmt_trans中的一樣嗎
//當你創建一個類(如 fmt_trans)的實例(對象)時,每個實例通常擁有自己的一套成員變量。
//這意味著,即使兩個對象是相同類的不同實例,它們的成員變量也可能擁有不同的值。
//檢查它們是否具有相同的長度和通道ID,并且它們的數據數組是否相同 function bit compare(fmt_trans t);string s;compare = 1;//假設對象最初是相等的。s = "\n============================\n";s = {s,$sformatf("COMPARING fmt_trans object at time %0d \n", $time)};if(this.length != t.length)begin//this.lengh指的是18行的lengthcompare = 0;s = {s, $sformatf("sobj length %0d != tobj length %0d \n", this.length, t.length)};endif(thi.ch_id != t.ch_id)begincompare = 0;s = {s, $sformatf("sobj ch_id %0d != tobj ch_id %0d \n", this.ch_id, t.ch_id)};endforeach(this.data[i])beginif(this.data[i] != t.data[i])begincompare = 0;s = {s, $sformatf("sobj data[%0d] %8x != tobj data[%0d] %8x \n",i, this.data[i], i, t.data[i])};endendif(compare == 1) s = {s, "COMPARED SUCCESS!\n"};else s = {s, "COMPARED FAILURED! \n "};rpt_pkg::rpt_msg("[CMPOBJ]", s, rpt_pkg::INFO, rpt_pkg::MEDIUM);endfunctionendclass
3.2.2 fmt_driver
模擬數據發送和接受行為
class fmt_driver;local string name;local virtual fmt_intf intf;mailbox #(fmt_trans) req_mb;mailbox #(fmt_trans) rsp_mb;//與generator通信 local mailbox #(logical[31:0]) fifo;//模擬formatter的下行數據local int fifo_bound; //模擬的下行通道最多能接受多少數據local int data_consum_period;// 反應下行數據輸出的快慢。反比function new(string name = "fmt_driver");this.name = name;this.fifo = new();this.fifo_bound = 4096;this.data_consum_period = 1;endfunctionfunction void set_interface(virtual fmt_intf intf);if(intf == null)$error("interface handle is null");elsethis.intf = intf;endfunctiontask run();forkthis.do_receive();this.do_consume();this.do_config();this.do_reset();join//配置driver task do_config();fmt_trans req, rsp;forever beginthis.req_mb.get(req);case(req.fifo)SHORT_FIFO : this.fifo_bound = 64 ;MED_FIFO : this.fifo_bound = 256 ;LONG_FIOF : this.fifo_bound = 512 ;ULTRA_FIFO : this.fifo_bound = 2048;endcasethis.fifo = new(this.fifo_bound);case(req.bandwidth) //為啥位寬低data_consum_period大呢?//可能是因為在低帶寬條件下,數據傳輸速率較慢,//因此需要更長的周期來消費數據,以匹配數據的傳輸速率。LOW_WIDTH : this.data_consum_period = 8 ;MED_WIDTH : this.data_consum_period = 4 ;HIGH_WIDTH : this.data_consum_period = 2 ;ULTRA_WIDTH : this.data_consum_period = 1 ;endcasersp = req.clone();rsp.rsp = 1;//這里的 rsp 成員變量是一個標志位,用于表示某種狀態或響應的有效性。this.rsp_mb.put(rsp);end endtasktask do_reset();forever begin@(negedge intf.rstn)intf.fmt_grant <= 0;endendtask//模擬具有隨機延遲的數據消費場景。按照隨機的時間間隔取出數據task do_consume();logic[31:0] data;forever beginvoid'(this.fifo.try_get(data));// 使用 try_get 方法嘗試從FIFO中取出一個數據項并賦值給 data 變量//void'() 是SystemVerilog中的一個特性,它用來忽略 try_get 方法返回的狀態值repeat($urandom_range(1, this.data_consum_period)) @(posedge intf.clk);//這里反應消耗數據的快慢//等待intf.clk的上升沿,repeat重復一次endendtask endclass//當數據接受的請求信號fmt_req拉高時,且FIFO深度足夠時,fmt_grant拉高,表示接收方準備好接受數據了
//task do_receive();//模擬接受數據forever begin@(posedge intf.fmt_req);forever begin@(posedge intf.clk);if((this.fifo_bound-this.fifo.num()) >= intf.fmt_length)//fifo_bound是選擇的fifo的總深度//檢查當前FIFO的剩余空間是否足夠存放即將到來的數據。//如果剩余空間大于或等于 intf.fmt_length 指定的長度,則跳出內部循環。break;//只跳出內部的這個forever。當fifo余量足夠時,拉高grantendintf.drv_ck.fmt_grant <= 1;//表示接收方準備好接受數據了@(posedge intf.fmt_start);//等待 intf.fmt_start 信號的下一個上升沿,//這表示發送方已經準備好發送數據。fork begin@(posedge intf.clk);intf.drv_ck.fmt_grant <= 0;//維持一拍后就拉低,不再授權數據傳輸endjoin_nonerepeat(intf.fmt_length)begin@(nedge intf.clk);this.fifo.put(intf.fmt_data);//連續放入數據endendendtask
3.2.3 fmt_generator
創建、隨機化、發送和接收 fmt_trans 類型的數據事務
class fmt_generator;rand fmt_fifo_t fifo = MED_FIFO;rand fmt_bandwidth_t bandwidth = MED_WIDTH;mailbox #(fmt_trans) req_mb;//在 fmt_generator 類中,req_mb 用于發送 fmt_trans 類型的請求事務,//而 rsp_mb 用于接收響應事務。這種方式使得 fmt_generator 可以作為一個獨立的組件,//模擬發送請求并等待響應,而不依賴于特定的通信協議實現細節。mailbox #(fmt_trans) rsp_mb;constraint cstr{soft fifo == MED_FIFO;soft bandwidth == MED_WIDTH;}function new();this.req.mb = new();this.rsp.mb = new();endfunctiontask start();send_trans();endtask//產生事務并放到本地信箱task send_trans();fmt_trans req, rsp;req = new();//為何這里是MED_FIFO呢,不等于就隨機化失敗?什么道理//因為192行就是這樣給的assert (req.randomize with {local::fifo != MED_FIFO -> fifo == local::fifolocal::bandwidth != MED_WIDTH -> bandwidth == local::bandwidth;})else $fatal("[RNDFALL] formatter packet randomize failure!");$display(req.sprint());this.req_mb.put(req);//放入請求this.rsp_mb.get(rsp);//取出響應$display(rsp.sprint());assert(rsp.rsp)//檢查響應是否有效else $error("[RSPERR] %0t error response received!", $time);endtaskfunction string sprint();string s;s = {s, $sformatf("========================\n")};s = {s, $sformatf("fmt_generator object content is as below:\n")};s = {s, $sformatf("fifo = %s:\n", this.fifo)};s = {s, $sformatf("bandwidth = %s:\n", this.bandwidth )};s = {s, $sformatf("=======================\n" )};return s;endfunctionfunction void post_randomize();string s;s = {"AFTER RANDOMIZE \n", this.sprint()};$display(s);endfunctionendclass
3.2.4 fmt_monitor
用于觀察和記錄通過接口 fmt_intf 傳輸的數據事務
class fmt_monitor;local string name;local virtual fmt_intf intf;mailbox #(fmt_trans) mon_mb;//用于存儲監視到的 fmt_trans 類型的事務。function new(string name = "fmt_generator");this.name = name;endfunctionfunction void set_interface(virtual fmt_intf intf);if(intf == null)$error("interface handle is null")else this.intf = intf;endfunction//設置監視器要監視的接口task run();this.mon_trans();endtasktask mon_trans();fmt_trans m;string s;forever begin@(posedge intf.mon_ck.fmt_start);//等待 intf.mon_ck.fmt_start 信號的上升沿,表示一個新事務的開始。m = new();m.length = intf.mon_ck.fmt_length;m.ch_id = intf.mon_ck.fmt_chid;m.data = new[m.length];//接口中獲取事務的長度、通道ID和數據,并將它們賦值給 m 對象。foreach(m.data[i])begin@(posedge intf.clk);//在每個時鐘上升沿從接口中獲取數據,并將其存儲在 m.data 數組中。m.data[i] = intf.mon_ck.fmt_data;endmon_mb.put(m);s = {s, $sformatf("========================\n")};s = {s, $sformatf("%0t %s monitored a packet:\n", $time, this.name)};s = {s, $sformatf("length = %0d:\n", m.length)};s = {s, $sformatf("chid = %0d:\n", m.ch_id )};foreach (m.data[i] s = {s,$sformatf("data[%0d] = %8x \n", i, m.data[i])}; s = {s, $sformatf("=======================\n" )};$display(s);endendtaskendclass
3.2.5 fmt_agent
組合驅動器(fmt_driver)和監視器(fmt_monitor)組件
class fmt_agent;local string name;fmt_driver driver;fmt_monitor monitor;local virtual fmt_intf vif;function new(string name = "fmt_agent");this.name = name;this.driver = new({name, ".driver"});this.monitor= new({name, ".monitor"});endfunctionfunction void set_interface(virtual fmt_intf vif);this.vif = vif;driver.set_interface(vif);monitor.set_interface(vif);endfunctiontask run();forkdriver.run();monitor.run();joinendtask endclass
3.3 mcdf_pkg
import chnl_pkg::*;import reg_pkg ::*;import arb_pkg ::*;import fmt_pkg ::*;import rpt_pkg ::*;typedef struct packed{bit[2:0] lenl ;//后面用來規定各寄存器通道的數據包長度,優先級,使能信號,可寫余量 bit[1:0] prio ;bit en ;bit[7:0] avail;}mcdf_reg_ttypedef enum{RW_LEN, RW_PRIO, RW_EN, RD_AVAIL}mcdf_field_t;//寄存器的不同比特位代表不同的功能,詳見藍皮書376頁
3.3.1 mcdf_refmod
用于測試或模擬MCDF模塊的寄存器行為,形成一個參考模型
class mcdf_refmod;local virtual mcdf_intf intf;local string name;mcdf_reg_t regs[3];mailbox #(reg_trans) reg_mb;mailbox #(mon_data_t) in_mbs [3];mailbox #(fmt_trans) out_mbs[3];function new(string name = "mcdf_refmod");this.name = name;foreach (this.out_mbs[i]) this.out_mbs[i] = new();endfunctiontask run();forkdo_reset();this.do_reg_update();do_packet(0);do_packet(1);do_packet(2); joinendtasktask do_reg_update();reg_trans t;forever beginthis.reg_mb.get(t);if(t.addr[31 :4] == 0 && t.cmd == 'WRITE)beginthis.regs[t.addr[3:2]].en = t.data[0] ;this.regs[t.addr[3:2]].prio = t.data[2:1] ;this.regs[t.addr[3:2]].len = t.data[5:3] ;endelse if(t.addr[7:4] == 1 && t.cmd == 'READ)beginthis.regs[t.addr[3:2]].avail = t.data[7:0];endendendtask//處理數據包task do_packet(int id);fmt_trans ot;mon_data_t it;bit[2:0] len;forever beginthis.in_mbs[id].peek(it);ot = new();len = this.get_field_value(id, RW_LEN);ot.length = len > 3 ? 32:4 << len;//根據len的值計算數據包的長度。//為什么數據包長度這樣定義 //如果len大于3,則長度為32,否則長度len左移4位。//因為第50行規定第4到6位存放數據包長度ot.data = new[ot.length];//根據計算出的長度,動態分配一個整型數組給ot.data,//用于存儲數據包的數據。ot.ch_id = id;foreach(ot.data[i])beginthis.in_mbs[id].get(it);//從信箱中取數據,給到itot.data[i] = it.data;endthis.out_mbs[id].put(ot);//將格式化后的事務ot放入到輸出mailbox(out_mbs)中endendtask function int get_field_value(int id, mcdf_field_t f);//mcdf_field_t是18行的枚舉類型case(f)RW_LEN : return regs[id].len ;//這四個case分別代表什么?RW_PRIO : return regs[id].prio ;RW_EN : return regs[id].en ;RD_AVAIL: return regs[id].avail;endcaseendfunctiontask do_reset();forever begin@(negedge intf.rstn);foreach(regs[i])beginregs[i].len = 'h0;regs[i].prio = 'h3;regs[i].en = 'h1;//為什么復位信號要給這些值?regs[i].avail= 'h20;//應該還是和task do_reg_update有關endendendtaskfunction void set_interface(virtual mcdf_intf intf);if(intf == null)$error("interface handle is null");elsethis.intf = intf;endfunctionendclass
3.3.2 mcdf_checker
將refmod的數據與fmt的數據進行對比
class mcdf_checker;local string name;local int err_count;local int total_count;local int chl_count[3];local virtual mcdf_intf intf;local refmod mcdf_refmod;mailbox #(mon_data_t) chnl_mbs[3];mailbox #(fmt_trans) fmt_mb;mailbox #(reg_trans) reg_mb;mailbox #(fmt_trans) exp_mbs[3];function new(string name = "mcdf_checker");this.name = name;foreach(this.chnl_mbs[i]) this.chnl_mbs[i] = new();this.fmt_mb = new();this.reg_mb = new();this.refmod = new();foreach (this.refomd.in_mbs[i])beginthis.refmod.in_mbs[i] = this.chnl_mbs[i];this.exp_mbs[i] = this.refmod.out_mbs[i];endthis.refmod.reg_mb = this.reg_mb;this.err_count = 0;this.total_count = 0;foreach(this.chnl_count[i]) this.chnl_count[i] = 0;endfunctionfunction void set_interface (virtual mcdf_intf intf);if(intf == null)$error("interface handle is null");elsethis.intf = intf;this.refmod.set_interface(intf);endfunctiontask run();forkthis.do_compare();this.refmod.run();joinendtask//比較收集到的數據是否和參考數據一樣 task do_compare();fmt_trans expt, mont;bit cmp;forever beginthis.fmt_mb.get(mont);//formatter中的數據this.exp_mbs[mont.ch_id].get(expt);//期望的數據cmp = mont.compare(expt);//調用的是fmt_pkg里面的compare函數this.total_count++;//是拿來計數什么的?this.chnl_count[mont.ch_id]++;//是拿來計數什么的?if(cmp == 0)begin//不相等this.err_count++;rpt_pkg::rpt_msg("[CMP FAIL]", $sformatf("%0t %0dth times comparing but failed!MCDF monitored output packet is different with reference model output", $time, this.total_count),rpt_pkg::ERROR,rpt_pkg::TOP,rpt_pkg::LOG);endelse beginrpt_pkg::rpt_msg("[CMP SUCD]", $sformatf("%0t %0dth times comparing and succeeded! MCDF monitored output packet is different with reference model output", $time, this.total_count),rpt_pkg::INFO,rpt_pkg::HIGH,endendendtaskfunction void do_report();string s;s = "\n--------------------------------------------\n";s = {s, " CHECKER SUMMARY \n"};s = {s, $sformatf("total comparison count: %0d \n", this.total_count)};foreach (this.chnl_count[i]) beginif(this.chnl_mbs[i].num() != 0 )//判斷是否把數據比較完s = {s, $sformatf("WARNING:: chnl_mbs[%0d] is not empty! size = %0d \n", i, this.chnl_mbs[i].num())}; endif(this.fmt_mb.num() != 0)s = {s, $sformatf("WARNING:: fmt_mb is not empty! size = %0d \n", this.fmt_mb.num())};s = "\n--------------------------------------------\n";rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);endfunctionendclass
3.3.3 mcdf_env
用于構建和運行MCDF模塊的測試環境
class mcdf_env;chnl_agent chnl_agts[3];reg_agent reg_agt;fmt_agent fmt_agt;mcdf_checker chker;mcdf_coverage cvrg;protected string name;function new(string name = "mcdf_env");this.name = name;this.chker = new();foreach(chnl_agts[i])beginthis.chnl_agts[i] = new($sformatf("chnl_agts[%0d]", i));this.chnl_agts[i].monitor.mon_mb = this.chker.chnl_mbs[i];endthis.reg_agt = new("reg_agt");this.reg_agt.monitor.mon_mb = this.chker_reg_mb;this.fmt_agt = new("fmt_agt"); this.fmt_agt.monitor.mon_mb = this.chker.fmt.mb;this.cvrg = new();$display("%s instantiated and connected objects", this.name);endfunctionvirtual task run();$display($sformatf("*****************%s started***********", this.name));this.do_config();forkthis.chnl_agts[0].run)();this.chnl_agts[1].run)();this.chnl_agts[2].run)(); this.reg_agt.run)(); this.fmt_agt.run)();this.chker.run();this.cvrg.run();joinendtaskvirtual function void do_config();//之前式用do_config來配置generator。但是endfunction //現在將其移植到mcdf_base_test中來進行配置了virtual function void do_report();this.chker.do_report();this.cvrg.do_reporet();endfunctionendclass
3.3.4 mcdf_base_test
測試模板
class mcdf_base_test;chnl_generator chnl_gens[3];reg_generator reg_gen;fmt_generator fmt_gen;mcdf_env env;protected string name;function new(string name = "mcdf_base_test");this.name = name;this.env = new(env);//將每個gens給到env進行代理foreach(this.chnl_gens[i])beginthis.chnl_gens[i] = new();this.env.chnl_agts[i].driver.req_mb = this.chnl_gens[i].req_mb;this.env.chnl_agts[i].driver.rsq_mb = this.chnl_gens[i].rsq_mb;endthis.reg_gen = new();this.env.reg_agt.driver.req_mb = this.reg_gen.req_mb;this.env.reg_agt.driver.rsp_mb = this.reg_gen.rsp_mb;this.fmt_gen = new();this.env.fmt_agt.driver.req_mb = this.fmt_gen.req_mb;this.env.fmt_agt.driver.rsp_mb = this.fmt_gen.rsp_mb;$display("%s instantiated and connected objects", this.name);endfunctionvirtual task run();fork env.run();join_nonerpt_pkg::rpt_msg("[TEST]",$sformatf("===========%s AT TIME 0%t STARTED=======", this.name,$time),rpt_pkg::INFO;rpt_pkg::HIGH);this.do_reg();this.do_formatter();this.do_data();rpt_pkg::rpt_msg("[TEST]",$sformatf("===========%s AT TIME 0%t FINISHED=======", this.name,$time),rpt_pkg::INFO;rpt_pkg::HIGH);this.do_report();$finish();endtask//do register configurationvirtual task do_reg();endtask//do external formatter down stream slave configurationvirtual task do_formatter();endtask//do data transition from 3 channel slavesvirtual task do_data();endtask//do simulation SUMMARYvirtual function void do_report();this.env.do_report();rpt_pkg::do_report();endfunctionvirtual function void set_interface( virtual chnl_intf ch0_vif,virtual chnl_agent ch1_vif,virtual chnl_agent ch2_vif,virtual reg_intf reg_vif,virtual fmt_intf fmt_vif,virtual mcdf_intf mcdf_vif);this.env.chnl_agts[0].set_interface(ch0_vif);this.env.chnl_agts[1].set_interface(ch1_vif);this.env.chnl_agts[2].set_interface(ch2_vif); this.env.reg_agt.set_interface(reg_vif);this.env.fmt_agt.set_interface(fmt_vif);this.env.chker.set_interface(mcdf_vif); this.env.cvrg.set_interface('{ch0_vif, ch1_vif, ch2_vif,}, reg_vif, arb_vif, fmt_vif, mcdf_vif);endfunctionvirtual function bit diff_value(int val1, int val2, string id ="value_compare");if(val1 != val2)beginrpt_pkg::rpt_msg("[CMP ERR]",$sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2),rpt_pkg::ERROR;rpt_pkg::TOP);return 0;endelse beginrpt_pkg::rpt_msg("[CMP SUC]",$sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2),rpt_pkg::INFO;rpt_pkg::HIGH);return 1;endendfunctionvirtual task idle_reg();void'(reg_gen.randomize() with {cmd == 'IDLE; addr == 0; data == 0;});reg_gen.start();endtaskvirtual task write_reg(bit[7:0]addr, bit[31:0] data);void'(reg_gen.randomize() with {cmd == 'WRITE; addr == local::addr; data == local::data;}); reg_gen.start(); endtask virtual task read_reg(bit[7:0]addr, output bit[31:0] data);void'(reg_gen.randomize() with {cmd == 'READ; addr == local::addr;}); reg_gen.start(); data = reg_gen.data;endtask endclass
3.3.5?mcdf_data_consistence_basic_test
讀寫數據一致性檢測,檢查寄存器讀進去和寫出來的數據是否一致
class mcdf_data_consistence_basic_test extends mcdf_base_test;function new(string name = "mcdf_data_consistence_basic_test");super.new(name);endfunction//檢查讀寫是否一致task do_reg();bit[31:0] wr_val, rd_val;//slv0 with len=8,prior=0,en=1wr_val = (1<<3)+(0<<1)+1;//為什么要這樣設置呢this.wrtie_reg('SLV0_RW_ADDR, wr_val);//寫進去this.read_reg('SLV0_RW_ADDR, rd_val);//讀回來void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));//把寫進去的值和讀回來的值做一個比較//看看寫進去的值是否合法//slv1 with len=16,prior=1,en=1wr_val = (2<<3)+(1<<1)+1;this.wrtie_reg('SLV1_RW_ADDR, wr_val);this.read_reg('SLV1_RW_ADDR, rd_val);void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG")); //slv2 with len=32,prior=2,en=1wr_val = (3<<3)+(2<<1)+1;this.wrtie_reg('SLV2_RW_ADDR, wr_val);this.read_reg('SLV2_RW_ADDR, rd_val);void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));//send IDLE commandthis.idle_reg();endtask //配置格式化器(fmt_gen)的隨機值,包括 FIFO 大小和帶寬。task do_formatter();void'(fmt_gen.randomize()with{fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});fmt_gen.start();endtask//生成數據task do_data();void'(chnl_gens[0].randomize() with {ntrans == 100; ch_id == 0; data_nidles == 0; pkt_nidles == 1; data_size == 8;});void'(chnl_gens[1].randomize() with {ntrans == 100; ch_id == 1; data_nidles == 1; pkt_nidles == 1; data_size == 16;});void'(chnl_gens[2].randomize() with {ntrans == 100; ch_id == 2; data_nidles == 2; pkt_nidles == 1; data_size == 32;}); forkchnl_gens[0].start();chnl_gens[1].start();chnl_gens[2].start();join#10us;//wait until all data haven been transfered through MCDFendtaskendclass
3.3.5?mcdf_reg_write_read_test
寄存器讀寫測試,包括對控制讀寫寄存器和狀態只讀寄存器的測試
class mcdf_reg_write_read_test extends mcdf_base_test;function new (string name = "mcdf_reg_write_read_test");super.new(name);endfunctionvirtual task do_reg();bit [31:0]wr_val, rd_val;bit [7:0] chnl_rw_addrs[] = '{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR};//讀寫寄存器bit [7:0] chnl_ro_addrs[] = '{'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};//只讀寄存器int pwidth = 'PAC_LEN_WIDTH + 'PRIO_WIDTH + 1;//PAC_LEN_WIDTH是定義在param_def中的常數bit[31:0] check_pattern[] = '{32'h0000_FFC0, 32'hFFFF_0000};//類似于地址檢查庫foreach (chnl_rw_addrs[i])beginforeach(check_pattern[j])beginwr_val = check_pattern[j];this.write_reg(chnl_rw_addrs[i], wr_val);this.read_reg(chnl_rw_addr[i], rd_val);void'((this.diff_value(wr_val & ((1<<pwidth)-1), rd_val));//為什么,(1<<pwidth)-1=31end//按位與操作。這種掩碼結果的二進制形式會有pwidth個低位是1。即保留wr_val的低pwidth位,其余位清零end//如果wr_val是32'h0000_FFC0,且pwidth是5,那么結果是32'h0000_0010。//僅比較有效位(低pwidth位),忽略無關的高位。這在寄存器操作中非常重要,//因為某些寄存器的高位可能是未定義或保留位,比較時應避免這些位的影響。foreach(chnl_ro_addrs[i])beginwr_val = 32'hFFFF_FF00;//由于是只讀寄存器,因此寫進入的值是確定的this.write_reg(chnl_ro_addrs[i],wr_val);this.read_reg(chnl_ro_addrs[1], rd_val);void'(this.diff_value(0, rd_val & wr_val));//這用于將函數調用的返回值轉換為 void,實際上是忽略函數的返回值。end//對于只讀寄存器,我們期望寫入操作不會改變寄存器的值。//因此,我們預期從只讀寄存器讀取到的值應該是0(這就是為什么第一個參數是0)。this.idle_reg();endtask endclass
3.3.6?mcdf_reg_illegal_access_tets
寄存器穩定性測試。
class mcdf_reg_illegal_access_tets extends mcdf_base_test;function new(string name = "mcdf_reg_illegal_access_tets");super.new(name);endfunctiontask do_reg();bit [31:0]wr_val, rd_val;bit [7:0] chnl_rw_addrs[] = '{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR};bit [7:0] chnl_ro_addrs[] = '{'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};int pwidth = 'PAC_LEN_WIDTH + 'PRIO_WIDTH + 1;bit[31:0] check_pattern[] = '{((1<<pwidth)-1), 0, ((1<<pwidth)-1)}; foreach (chnl_rw_addrs[i])beginforeach(check_pattern[j])beginwr_val = check_pattern[j];this.write_reg(chnl_rw_addrs[i],wr_val);this.read_reg(chnl_rw_addrs[i],rd_val);void'(this.diff_value(wr_val, rd_val));endendforeach(chnl_ro_addrs[i])beginthis.read_reg(chnl_ro_addrs[i],rd_val); endthis.idle_reg();endtaskendclass
3.3.7?mcdf_arbiter_priority_test
arbiter優先級測試
如果各數據通道優先級相同,那么arbiter應該采取輪詢機制從各個通道接受數據,如果優先級不同,先從高優先級的通道接收數據
class mcdf_arbiter_priority_test extends mcdf_base_test;function new (string name = "mcdf_arbiter_priority_test");super.new(name) endfunction task do_arbiter_priority_check();int id;forever begin@(posedge this.arb_vif.clk iff (this.arb_vif.rstn && this.arb_vif.mon_ck.f2a_id_req===1));id = this.get_slave_id_with_prio();//得到三個slave的最高優先級if(id >= 0) begin@(posedge this.arb_vif.clk);if(this.arb_vif.mon_ck.a2s_acks[id] !== 1)//acks【i】是設計的硬件信號的slvae通道選擇rpt_pkg::rpt_msg("[CHKERR]",$sformatf("ERROR! %0t arbiter received f2a_id_req===1 and channel[%0d] raising request with high priority, but is not granted by arbiter", $time, id),rpt_pkg::ERROR,rpt_pkg::TOP);endendendtask//模擬arbiter仲裁邏輯function int get_slave_id_with_prio();int id=-1;int prio=999;//尋找最高優先級foreach(this.arb_vif.mon_ck.slv_prios[i]) beginif(this.arb_vif.mon_ck.slv_prios[i] < prio && this.arb_vif.mon_ck.slv_reqs[i]===1) beginid = i;prio = this.arb_vif.mon_ck.slv_prios[i];endendreturn id;endfunctionendclass
3.3.8?mcdf_channel_disbale_test
數據通道開關檢測
在數據通道關閉的情況下檢測數據寫入是否通過
class mcdf_channel_disbale_test extends mcdf_base_tese;function new (string name = "mcdf_channel_disbale_test");super.new(name) endfunction task do_mcdf_channel_disable_test (int id)forever begin @(posedge this.mcdf_vif.clk iff(this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id] == 0));if (this.chnl_vifs[id].mon_ck.ch_valid == 1 && this.chnl_vifs[id].mon_ck.ch_ready == 1)rpt_pkg::rpt_msg("[CHECKER]",$sformatf("ERROR! %0t whern channel disabled, ready signal raised while valid signal is high", $time),rpt_pkg::ERROR,rpt_pkg::TOP);endendtask endclass
3.3.9?mcdf_coverage
覆蓋率收集
class mcdf_coverage;local virtual chnl_intf chnl_vifs[3]; local virtual arb_intf arb_vif;local virtual mcdf_intf mcdf_vif;local virtual reg_intf reg_vif;local virtual fmt_intf reg_vif;local string name;local int delay_req_to_grant;//對所有控制、狀態寄存器的讀寫進行測試。covergroup cg_mcdf_reg_write_read;addr: coverpoint reg_vif.mon_ck.cmd_addr{type_option.weight = 0;//權重設置為0,表示不關心此coverpoint的覆蓋率,否則cross以后會出現很多的binbins slv0_rw_addr = {'SLV0_RW_ADDR};bins slv1_rw_addr = {'SLV1_RW_ADDR}; bins slv2_rw_addr = {'SLV2_RW_ADDR}; bins slv0_r_addr = {'SLV0_R_ADDR }; bins slv1_r_addr = {'SLV1_R_ADDR }; bins slv2_r_addr = {'SLV2_R_ADDR }; }//8bit地址實際上會生成256個bin,256-6=250會被平均分配到這6個bin中,//但默認max為64個bin,所以剩下的250要分配到64-6=58個bin里面//建議如果要做cross,那需要被cross的這些功能點都weight=0,然后在cross中重新bincmd: coverpoint reg_vif.mon_ck.cmd{type_option.weight = 0;bins write = {'WRITE};bins read = {'READ};bins idle = {'IDLE};}//cmd是2bit的,所以還有一個默認的bincmdXaddr: cross cmd, addr{bins slv0_rw_addr = binsof (addr.slv0_rw_addr);bins slv1_rw_addr = binsof (addr.slv1_rw_addr); bins slv2_rw_addr = binsof (addr.slv2_rw_addr); bins slv0_r_addr = binsof (addr.slv0_r_addr ); bins slv1_r_addr = binsof (addr.slv1_r_addr ); bins slv2_r_addr = binsof (addr.slv2_r_addr ); bins write = (cmd.write); bins read = (cmd.read ); bins idle = (cmd.idle ); //因為之前設置權重為0,所以需要再一次的聲明,這樣可以只關心所需要的bin即可,不會生成沒用的bin bins write_slv0_rw_addr = binsof (cmd.write) && binsof (addr.slv0_rw_addr); bins write_slv1_rw_addr = binsof (cmd.write) && binsof (addr.slv1_rw_addr); bins write_slv2_rw_addr = binsof (cmd.write) && binsof (addr.slv2_rw_addr); bins read_slv0_rw_addr = binsof (cmd.read ) && binsof (addr.slv0_rw_addr); bins read_slv1_rw_addr = binsof (cmd.read ) && binsof (addr.slv1_rw_addr); bins read_slv2_rw_addr = binsof (cmd.read ) && binsof (addr.slv2_rw_addr); bins read_slv0_r_addr = binsof (cmd.read ) && binsof (addr.slv0_r_addr ); bins read_slv1_r_addr = binsof (cmd.read ) && binsof (addr.slv1_r_addr ); bins read_slv2_r_addr = binsof (cmd.read ) && binsof (addr.slv2_r_addr ); }endgroup//對非法地址進行讀寫測試,對控制寄存器的保留域進行讀寫測試,對狀態寄存器進行寫操作測試。covergroup cg_mcdf_reg_illegal_access;addr: coverpoint reg_vif.mon_ck.cmd_addr {type_option.weight = 0;bins legal_rw = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};//合法讀寫寄存器地址bins legal_r = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR}; //合法讀地址范圍/*這里做了一些簡化,這里對任意一個通道的讀寫寄存器進行了操作就滿足覆蓋率要求*//**但是實際上如果要對每一個通道的寄存器都進行覆蓋率檢測,那應該寫為數組形式*//*bins legal_rw[] = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};bins legal_r[] = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR}; */ bins illegal = {[8'h20:$], 8'hC, 8'h1C}; //非法地址范圍}cmd: coverpoint reg_vif.mon_ck.cmd {type_option.weight = 0;bins write = {`WRITE};bins read = {`READ};}//寫數據覆蓋點wdata: coverpoint reg_vif.mon_ck.cmd_data_m2s {type_option.weight = 0;//數據合法范圍,0~111111,即bit(0)使能信號、bit(2:1)優先級、bit(5:3)數據包長度都可以寫bins legal = {[0:'h3F]};//數據非法范圍,bit(31:6)保留位,無法寫入bins illegal = {['h40:$]};}//讀數據覆蓋點rdata: coverpoint reg_vif.mon_ck.cmd_data_s2m {type_option.weight = 0;//讀數據合法范圍,0~11111111,即bit(7:0)上行數據從端FIFO的可寫余量bins legal = {[0:'hFF]};illegal_bins illegal = default;}//交叉覆蓋cmdXaddrXdata: cross cmd, addr, wdata, rdata {bins addr_legal_rw = binsof(addr.legal_rw);bins addr_legal_r = binsof(addr.legal_r);bins addr_illegal = binsof(addr.illegal);bins cmd_write = binsof(cmd.write);bins cmd_read = binsof(cmd.read);bins wdata_legal = binsof(wdata.legal);bins wdata_illegal = binsof(wdata.illegal);bins rdata_legal = binsof(rdata.legal);bins write_illegal_addr = binsof(cmd.write) && binsof(addr.illegal);//對非法地址進行寫操作bins read_illegal_addr = binsof(cmd.read) && binsof(addr.illegal);//對非法地址進行寫操作bins write_illegal_rw_data = binsof(cmd.write) && binsof(addr.legal_rw) && binsof(wdata.illegal);//對讀寫寄存器合法地址寫入非法數據bins write_illegal_r_data = binsof(cmd.write) && binsof(addr.legal_r) && binsof(wdata.illegal);//對只讀寄存器合法地址寫入非法數據}endgroup//對每一個數據通道對應的控制寄存器域en配置為0,在關閉狀態下測試數據寫入是否通過。covergroup cg_channel_disable;ch0_en: coverpoint mcdf_vif.mon_ck.chnl_en[0] {type_option.weight = 0;wildcard bins en = {1'b1};//這里為什么要用wildcard來修飾wildcard bins dis = {1'b0};//wildcard修飾符用于位寬不定的信號,它使得值的匹配忽略位數上的差異。//具體來說,wildcard可以用來簡化匹配條件,特別是在信號中有不關心的位時//wildcard可以讓表達式中的任何x、z或?被當作0或1的通配符}ch1_en: coverpoint mcdf_vif.mon_ck.chnl_en[1] {type_option.weight = 0;wildcard bins en = {1'b1};//對應使能wildcard bins dis = {1'b0};//對于禁用}ch2_en: coverpoint mcdf_vif.mon_ck.chnl_en[2] {type_option.weight = 0;wildcard bins en = {1'b1};wildcard bins dis = {1'b0};}ch0_vld: coverpoint chnl_vifs[0].mon_ck.ch_valid {type_option.weight = 0;bins hi = {1'b1};bins lo = {1'b0};}ch1_vld: coverpoint chnl_vifs[1].mon_ck.ch_valid {type_option.weight = 0;bins hi = {1'b1};bins lo = {1'b0};}ch2_vld: coverpoint chnl_vifs[2].mon_ck.ch_valid {type_option.weight = 0;bins hi = {1'b1};bins lo = {1'b0};}//采集當valid拉高時,代表要進行寫操作,en拉低代表數據通道關閉chenXchvld: cross ch0_en, ch1_en, ch2_en, ch0_vld, ch1_vld, ch2_vld {bins ch0_en = binsof(ch0_en.en );bins ch0_dis = binsof(ch0_en.dis);bins ch1_en = binsof(ch1_en.en );bins ch1_dis = binsof(ch1_en.dis);bins ch2_en = binsof(ch2_en.en );bins ch2_dis = binsof(ch2_en.dis);bins ch0_hi = binsof(ch0_vld.hi);bins ch0_lo = binsof(ch0_vld.lo);bins ch1_hi = binsof(ch1_vld.hi);bins ch1_lo = binsof(ch1_vld.lo);bins ch2_hi = binsof(ch2_vld.hi);bins ch2_lo = binsof(ch2_vld.lo);bins ch0_en_vld = binsof(ch0_en.en ) && binsof(ch0_vld.hi);bins ch0_dis_vld = binsof(ch0_en.dis) && binsof(ch0_vld.hi);bins ch1_en_vld = binsof(ch1_en.en ) && binsof(ch1_vld.hi);bins ch1_dis_vld = binsof(ch1_en.dis) && binsof(ch1_vld.hi);bins ch2_en_vld = binsof(ch2_en.en ) && binsof(ch2_vld.hi);bins ch2_dis_vld = binsof(ch2_en.dis) && binsof(ch2_vld.hi);}endgroup//將不同數據通道配置為相同或者不同的優先級,在數據通道使能的情況下進行測試。covergroup cg_arbiter_priority;ch0_prio: coverpoint arb_vif.mon_ck.slv_prios[0] {bins ch_prio0 = {0}; bins ch_prio1 = {1}; bins ch_prio2 = {2}; bins ch_prio3 = {3}; //為什么會有4個優先級呢}ch1_prio: coverpoint arb_vif.mon_ck.slv_prios[1] {bins ch_prio0 = {0}; bins ch_prio1 = {1}; bins ch_prio2 = {2}; bins ch_prio3 = {3}; }ch2_prio: coverpoint arb_vif.mon_ck.slv_prios[2] {bins ch_prio0 = {0}; bins ch_prio1 = {1}; bins ch_prio2 = {2}; bins ch_prio3 = {3}; }endgroup//測試從formatter發送出來的數據包長度是否同對應通道寄存器的配置一致covergroup cg_formatter_length;id: coverpoint fmt_vif.mon_ck.fmt_chid {bins ch0 = {0};bins ch1 = {1};bins ch2 = {2};illegal_bins illegal = default; }length: coverpoint fmt_vif.mon_ck.fmt_length {bins len4 = {4};bins len8 = {8};bins len16 = {16};bins len32 = {32};illegal_bins illegal = default;}endgroup//在req拉高以后,grant至少應該在2個時鐘周期后拉高,以此來模擬下行從端數據余量不足的情況covergroup cg_formatter_grant();delay_req_to_grant: coverpoint this.delay_req_to_grant {bins delay1 = {1};// delay_req_to_grant是一個自定義的軟件信號,在task do_formater_sample()中被定義bins delay2 = {2};//數字代表req與grant信號之間延遲的拍數字bins delay3_or_more = {[3:10]};illegal_bins illegal = {0};//req與grant信號之間沒有延遲則非法}endgroupfunction new(string name="mcdf_coverage");this.name = name;this.cg_mcdf_reg_write_read = new();this.cg_mcdf_reg_illegal_access = new();this.cg_channel_disable = new();this.cg_arbiter_priority = new();this.cg_formatter_length = new();this.cg_formatter_grant = new();endfunctiontask run();fork this.do_reg_sample();this.do_channel_sample();this.do_arbiter_sample();this.do_formater_sample();joinendtask//寄存器采樣task do_reg_sample();forever begin@(posedge reg_vif.clk iff reg_vif.rstn);this.cg_mcdf_reg_write_read.sample();this.cg_mcdf_reg_illegal_access.sample();endendtask//數據通道采樣task do_channel_sample();forever begin對于每一個時鐘上升沿,至少有一個channel的valid信號為1時,即有數據發送進來時,才對disable做采樣@(posedge mcdf_vif.clk iff mcdf_vif.rstn);if(chnl_vifs[0].mon_ck.ch_valid===1|| chnl_vifs[1].mon_ck.ch_valid===1|| chnl_vifs[2].mon_ck.ch_valid===1)this.cg_channel_disable.sample();endendtask//對優先級做采樣task do_arbiter_sample();forever begin對于每一個時鐘的上升沿,至少有一個channel的req為1,即至少有一個channel要發送數據了,才對其優先級做采樣@(posedge arb_vif.clk iff arb_vif.rstn);if(arb_vif.slv_reqs[0]!==0 || arb_vif.slv_reqs[1]!==0 || arb_vif.slv_reqs[2]!==0)this.cg_arbiter_priority.sample();endendtask//對grant信號做采樣task do_formater_sample();forkforever begin//對于每一個時鐘上升沿,當req為1時,對數據的長度做采樣@(posedge fmt_vif.clk iff fmt_vif.rstn);if(fmt_vif.mon_ck.fmt_req === 1)this.cg_formatter_length.sample();endforever begin//當req拉高時,要通過變量delay_req_to_grant來計算延遲,對grant做采樣@(posedge fmt_vif.mon_ck.fmt_req);this.delay_req_to_grant = 0;/*req信號與grant信號之間的延遲*/forever begin//只有grant拉高時,才對grant做采樣,進而通過delay_req_to_grant的值得到延遲if(fmt_vif.fmt_grant === 1) beginthis.cg_formatter_grant.sample();break;/*當fmt_vif.mon_ck.fmt_req信號和fmt_vif.fmt_grant信號都被拉高時,delay_req_to_grant為0,此時為非法狀態,被采樣*/endelse begin@(posedge fmt_vif.clk);this.delay_req_to_grant++;/*每經過一拍,delay_req_to_grant加1*/endendendjoinendtaskfunction void do_report();string s;s = "\n---------------------------------------------------------------\n";s = {s, "COVERAGE SUMMARY \n"}; s = {s, $sformatf(" total coverage: %.1f \n", $get_coverage())}; //$get_coverage可以得到總體的全局覆蓋率s = {s, $sformatf(" cg_mcdf_reg_write_read coverage: %.1f \n" , this.cg_mcdf_reg_write_read.get_coverage())}; //get_coverage獲取單個covergroup實例的覆蓋率s = {s, $sformatf(" cg_mcdf_reg_illegal_access coverage: %.1f \n", this.cg_mcdf_reg_illegal_access.get_coverage())}; s = {s, $sformatf(" cg_channel_disable_test coverage: %.1f \n" , this.cg_channel_disable.get_coverage())}; s = {s, $sformatf(" cg_arbiter_priority_test coverage: %.1f \n" , this.cg_arbiter_priority.get_coverage())}; s = {s, $sformatf(" cg_formatter_length_test coverage: %.1f \n" , this.cg_formatter_length.get_coverage())}; s = {s, $sformatf(" cg_formatter_grant_test coverage: %.1f \n" , this.cg_formatter_grant.get_coverage())}; s = {s, "---------------------------------------------------------------\n"};rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);endfunctionvirtual function void set_interface(virtual chnl_intf ch_vifs[3] ,virtual reg_intf reg_vif,virtual arb_intf arb_vif,virtual fmt_intf fmt_vif,virtual mcdf_intf mcdf_vif);this.chnl_vifs = ch_vifs;this.arb_vif = arb_vif;this.reg_vif = reg_vif;this.fmt_vif = fmt_vif;this.mcdf_vif = mcdf_vif;if(chnl_vifs[0] == null || chnl_vifs[1] == null || chnl_vifs[2] == null)$error("chnl interface handle is NULL, please check if target interface has been intantiated");if(arb_vif == null)$error("arb interface handle is NULL, please check if target interface has been intantiated");if(reg_vif == null)$error("reg interface handle is NULL, please check if target interface has been intantiated");if(fmt_vif == null)$error("fmt interface handle is NULL, please check if target interface has been intantiated");if(mcdf_vif == null)$error("mcdf interface handle is NULL, please check if target interface has been intantiated");endfunctionendclass
04 覆蓋率收集
運行完這幾個test之后,得到覆蓋率收集表如下