打印函数:
Useful SystemVerilog System Tasks | |||||||||
---|---|---|---|---|---|---|---|---|---|
Task Name | Description | ||||||||
$sscanf(str,format,args); | $sscanf 将字符串按照某个模板格式进行扫描,其字符串格式和C语言中的printf()函数类似 | ||||||||
$sformat(str,format,args); | $sformat是$sscanf的反函数。将字符串按照给定的格式填入相应的参数args中 | ||||||||
$display(format,args); | $display就是Verilog的printf语句,在stdout上显示格式化的字符串 | ||||||||
$sformatf(format,args); | $sformatf任务和$sformat相似,除了其返回字符串结果。字符串作为$sformatf的返回值,而不是像$sformt一样放在第一个参数上。 |
我们在网上查资料看到的打印输出都是这么几个函数,我们也知道怎么用,但是怎么用才能发挥打印函数的威力呢?
当我在用synopsys的 svt_eth_vip 验证 PCS时,要通过vip将数据包发送给serdes,serdes将数据串转并之后送给pcs,pcs内部不论经过64/66b解码还是经过8b/10b解码之后,再将数据送给MAC,在MAC的rx端我们通过 xgmii,gmii,cgmii等等 接口就能得到 serdes-> PMA-> PCS整个解码流程解析出来的数据,通过比对svt_eth_vip 发送的数据与MAC的rx端接收到的数据我们就能知道整个链路是否正常工作。但是整个过程与我们打印数据有什么关系呢?当然有关系,关系还非常大,我们在调试初期,可能由于rtl代码并不完善或者验证环境设计的不合理,常常会出现一些错误,需要我们根据解析出来的数据反推到底是环境还是代码出现了问题,由于数据量一般都比较大,这时候合理的设计打印输出的结果,非常有助于帮助我们分析这些错误。
简单的说明:
$sformat ,这个函数需要一个 str来接收最后产生的字符串,也就是说,调用这个函数是将 args 以 format的形式填入 str。
$sfomratf,这个函数就有意思了, 他的结果就是一个字符串,可以直接放到任何可以接收字符串的函数,task上,当然也可以直接赋值给 string 类型的变量。
填充形式
mac报文是由 前导码+源地址+目的地址+内容+crc校验组成,前导码 7个 0x55,一个0xd5,源地址和目的地址48 bit,crc校验32bit,我在存储的过程中,去掉前导码,将源地址和目的地址单独拉出来,将内容进行显示输出,显示形式如下图:
这样一排十个数,源地址,目的地址分开显示,不管package中有多少个数,数据显示很整齐,对比起来也很容易发现错误的地方。那接下来我们来看我是怎么实现显示这种方格的。
打印函数:
print_pkt 函数 print_pkt (string name , xgmii_rx_item#(width) item);
该函数有两个入参 分别为: name, xgmii_rx_item , name代表你要显示的是谁的数据包,向上图 GMII0 RX LANE0 就是显示的是lane0 的 GMII0的包, xgmii_rx_item 代表你要显示的数据包的内容。
处理 xgmii_rx_item
1,跳过前导码,截取到 源地址 和目的地址:
// 伪代码,非重点的定义等不做说明
bit[7:0] mac_pkt [$];
mac_pkt = {>>{item.pkt_data}};
dst_addr = {>>{mac_pkt[pre_cnt:(pre_cnt+5)]}};
src_addr = {>>{mac_pkt[(pre_cnt+6):(pre_cnt+11)]}};
print_str = "";
print_str = {print_str, "\n"};
print_str = {print_str, "+-----+-----+-----+-----+-----+-----"};
print_str = {print_str,$psprintf("+----------%s----------+",name)};
print_str = {print_str, "+-----+-----+-----+-----+-----+-----"};
print_str = {print_str, "| TX | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|"};
print_str = {print_str, "+-----+-----+-----+-----+-----+-----"};
print_str = {print_str,$psprintf("| Source Add: %h |",src_addr)};
print_str = {print_str, "+-----+-----+-----+-----+-----+-----"};
print_str = {print_str,$psprintf("| Dest Add: %h |",dst_addr)};
print_str = {print_str, "+-----+-----+-----+-----+-----+-----"};
print_str = {print_str, "|INDX0| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|"};
while(mac_cnt < item.pkt_data.size()-1) begin
print_str = {print_str, $psprintf("|%5d|%x|%x|%x|%x|%x|%x|%x|%x|%x",mac_cnt/10
,item.pkt_data[0+mac_cnt]
,item.pkt_data[1+mac_cnt]
,item.pkt_data[2+mac_cnt]
,item.pkt_data[3+mac_cnt]
,item.pkt_data[4+mac_cnt]
,item.pkt_data[5+mac_cnt]
,item.pkt_data[6+mac_cnt]
,item.pkt_data[7+mac_cnt]
,item.pkt_data[8+mac_cnt]
,item.pkt_data[9+mac_cnt]), "\n"};
mac_cnt = mac_cnt + 10;
end
print_str = {print_str, "+-----+-----+-----+-----+-----+-----"};
$display("%s",print_str);
endfunction:print_pkt
通过这一系列的操作,我们把本来需要`uvm_info 或者 display杂乱无章的显示在每一行的打印数据,统一到一个方块内,这样看起来简洁高效了很多。
在这里想说的是:
foreach(item.pkt_data[i]) begin
`uvm_info("PKT",$psprintf("item.pkt_data[%3d] = %x",i,item.pkt_data[i]),UVM_MEDIUM)
end
这种打印方法不是不行,但是非常的不直观,对比前后数据会比较痛苦,把所有的数据集中在一起,按照同样的格式打印出来,帮助我们看清楚数据,把时间花在解决问题上,并且像这样的做法,也可以放在 reference_module , monitor 等等需要打印大段数据的地方。