IC设计师需要掌握验证技巧吗?个人认为,是的,做设计的人起码应该理解基本的验证方法学。如果你想做到充分掌握VMM/UVM这类方法学的精髓,没有三五个月恐怕是搞不定的。但如果你只是想要理解这些方法学的核心思想,也许只要2周就够了。这些方法学的核心思想其实无非就是两点:优化测试平台架构以实现测试激励的自动化、提高灵活性和重用性,优化测试用例以实现测试效率和覆盖率的平衡性。可能很多IC设计工程师都明白以上两点方法学内核,但是却苦于不知如何将这种思想方便地运用到我们日复一日的苦逼debug工作中去。如果你没有学过以上任何一种方法学,却又希望能够将这2点方法学精髓融会贯通运用自如,那么你就好好看着吧。我将在本文中介绍一种本人通过反复摸索自创的方法,通过这种方法你可以很快自己建立一个针对模块进行验证的简易验证平台。这个平台不但设计简单,而且具有很高的重用性和灵活性。
I. 基于端口的验证方法学
你肯定从来没有听过这个名字,因为它是我最近刚刚发明的。其实早在System Verilog这本语言被开发出来的时候,它的开发者可能就曾经设想过这样一种方法学,只是不知道为什么最终没有人真的这样用过。我之所以会这么说,是因为我最开始就是从System Verilog官方文档的一个小例子获得的灵感。后来我把这个灵感和从其他方法学中学到的核心验证思想融合在一起并反复实验,终于得到了这个我叫做“基于端口的验证方法学”的东西。
基于端口的验证方法学提供了一种最简单的方式来搭建一个功能完备的模块测试平台。平台的架构本身并不复杂,但却足以提供高度的灵活性和可复用性。通过这种方式,你的测试用例可以被轻松地抽象到更高的层级。而实现这一切,你唯一需要掌握的就是如何编写IFM(端口功能模型,这个词是我发明的,因为他有别于总线功能模型)。
那么,下面我就来告诉你如何给你的模块端口编写IFM文件。在这里我提供了一个简单的APB总线从机接口作为示范。
[source lang="verilog"]
//==============================================================================
// Copyright (C) 2015 By Kellen.Wang
// mail@kellen.wang, All Rights Reserved
//==============================================================================
// Module : apb_ifm
// Author : Kellen Wang
// Contact: mail@kellen.wang
// Date : Feb.15.2015
//==============================================================================
// Description :
//==============================================================================
interface apb_if (
input logic pclk ,
input logic presetn
);
logic psel ;
logic [31:0] paddr ;
logic pwrite ;
logic [31:0] pwdata ;
wire [31:0] prdata ;
logic penable ;
wire pready ;
clocking cb_apb_mst @(posedge pclk);
output psel ;
output paddr ;
output pwrite ;
output pwdata ;
input prdata ;
output penable ;
input pready ;
endclocking
clocking cb_apb_slv @(posedge pclk);
input psel ;
input paddr ;
input pwrite ;
input pwdata ;
output prdata ;
input penable ;
output pready ;
endclocking
modport apb_mst (clocking cb_apb_mst);
modport apb_slv (clocking cb_apb_slv);
task apb_mst_wrdata (
input logic [31:0] addr ,
input logic [31:0] wdata
);
#1
wait(pready);
@(posedge pclk);
#1
psel = 0;
penable = 0;
pwrite = 0;
pwdata = 'x;
@(posedge pclk);
#1
psel = 1;
paddr = addr;
pwrite = 1;
pwdata = wdata;
@(posedge pclk);
#1
penable = 1;
#1
wait(pready);
@(posedge pclk);
#1
psel = 0;
penable = 0;
pwrite = 0;
pwdata = 'x;
$display ("apb_mst write [0x%8x]: 0x%8x @%tns",addr,wdata,$time);
endtask
task apb_mst_rddata (
input logic [31:0] addr ,
output logic [31:0] rdata
);
#1
wait(pready);
@(posedge pclk);
#1
psel = 0;
penable = 0;
pwrite = 0;
pwdata = 'x;
@(posedge pclk);
#1
psel = 1;
paddr = addr;
@(posedge pclk);
#1
penable = 1;
#1
wait(pready);
rdata = prdata;
@(posedge pclk);
#1
psel = 0;
penable = 0;
$display ("apb_mst read [0x%8x]: 0x%8x @%tns",addr,rdata,$time);
endtask
task apb_mst_reset ();
wait(presetn);
#1
psel = 0;
paddr = 0;
pwrite = 0;
pwdata = 0;
penable = 0;
$display ("apb_mst is reseting!!! @%tns",$time);
endtask
endinterface
[/source]
请仔细阅读以上代码。如果对其中的语法或者其它语句有所疑问,欢迎百度搜一下或者在本文下方评论留言。本文将着重讲解代码中的三个任务(task)。我之前已经说到,这个IFM文件是为APB总线从机接口编写的,所以我设计了这三个任务来模拟APB主机的行为:
- apb_mst_wrdata()
- apb_mst_rddata()
- apb_mst_reset()
II. 如何在测试平台中使用IFM
A) 一个简单示范
首先请记得将以上例子中的IFM文件包括到你的工程编译文件清单中。然后就你可以在测试平台中将这个接口功能模型例化出来。你的测试平台代码大概会长这样:
[source lang="verilog"]
//==============================================================================
// Copyright (C) 2015 By Kellen.Wang
// mail@kellen.wang, All Rights Reserved
//==============================================================================
// Module : apb_ifm
// Author : Kellen Wang
// Contact: mail@kellen.wang
// Date : Mar.30.2015
//==============================================================================
// Description :
//==============================================================================
module ahb_biu_tb;
parameter SYS_CLK = 100;
localparam MODE_LEN4 = 4'd0;
localparam MODE_LEN5 = 4'd1;
localparam MODE_LEN6 = 4'd2;
localparam MODE_LEN7 = 4'd3;
localparam MODE_LEN8 = 4'd4;
localparam BD_300 = (SYS_CLK*1_000_000 / 300 )/2-1;
localparam BD_600 = (SYS_CLK*1_000_000 / 600 )/2-1;
localparam BD_1200 = (SYS_CLK*1_000_000 / 1200 )/2-1;
localparam BD_2400 = (SYS_CLK*1_000_000 / 2400 )/2-1;
localparam BD_4800 = (SYS_CLK*1_000_000 / 4800 )/2-1;
localparam BD_9600 = (SYS_CLK*1_000_000 / 9600 )/2-1;
localparam BD_19200 = (SYS_CLK*1_000_000 / 19200 )/2-1;
localparam BD_38400 = (SYS_CLK*1_000_000 / 38400 )/2-1;
localparam BD_14400 = (SYS_CLK*1_000_000 / 14400 )/2-1;
localparam BD_57600 = (SYS_CLK*1_000_000 / 57600 )/2-1;
localparam BD_115200 = (SYS_CLK*1_000_000 / 115200 )/2-1;
localparam UART_CONFIG = 32'h000;
localparam UART_BAUD_PERIOD = 32'h004;
localparam UART_BAUD_CORR = 32'h008;
localparam UART_CONTROL = 32'h00C;
localparam UART_WDATA = 32'h010;
localparam UART_RDATA = 32'h014;
wire clk ;
wire rst_n ;
reg iready ;
wire [31:0] iaddr ;
wire iwen ;
wire [31:0] iwdata ;
wire [ 3:0] iwbyten ;
wire iren ;
reg [31:0] irdata ;
wire [ 3:0] irbyten ;
always @(posedge clk) if (iren) irdata <= $random;
always @(posedge clk) iready <= $random;
reg [31:0] rdata;
reg [ 7:0] mem [0:31];
integer i;
initial begin
for(i=0;i<32;i=i+1) mem[i] = $random;
end
initial begin
ahb_mst0.ahb_mst_reset ();
ahb_mst0.ahb_mst_wrdata (UART_CONFIG , {16'd0,MODE_LEN8,4'd1,4'd1,4'd1});
ahb_mst0.ahb_mst_rddata (UART_CONFIG , rdata);
ahb_mst0.ahb_mst_wrdata (UART_BAUD_PERIOD , BD_115200);
ahb_mst0.ahb_mst_rddata (UART_BAUD_PERIOD , rdata);
ahb_mst0.ahb_mst_wrdata (UART_BAUD_CORR , 32'd119304647);
ahb_mst0.ahb_mst_rddata (UART_BAUD_CORR , rdata);
ahb_mst0.ahb_mst_wrdata (UART_CONTROL , {20'd0,4'd1,4'd1,4'd1});
ahb_mst0.ahb_mst_rddata (UART_CONTROL , rdata);
for(i=0;i<32;i=i+1)
ahb_mst0.ahb_mst_wrdata (UART_WDATA , mem[i]);
for(i=0;i<32;i=i+1) begin
ahb_mst0.ahb_mst_rddata (UART_RDATA , rdata);
if (rdata !== mem[i]) $display ("*iError: read data incorrect!!! ref: %8x",mem[i]);
end
end
ahb_if ahb_mst0 (
.hclk (clk ),
.hresetn (rst_n )
);
clk_rst #(
.CLK_FREQ(SYS_CLK ),
.RST_DLY (10 )
) u_clk_rst (
.clk (clk ),
.rst_n (rst_n )
);
ahb_slv_biu u_ahb_slv_biu (
.hclk (clk ),
.hresetn (rst_n ),
.hsel (ahb_mst0.hsel ),
.htrans (ahb_mst0.htrans ),
.hsize (ahb_mst0.hsize ),
.hburst (ahb_mst0.hburst ),
.haddr (ahb_mst0.haddr ),
.hwrite (ahb_mst0.hwrite ),
.hwdata (ahb_mst0.hwdata ),
.hrdata (ahb_mst0.hrdata ),
.hresp (ahb_mst0.hresp ),
.hreadyin (ahb_mst0.hreadyin),
.hready (ahb_mst0.hready ),
.iready (iready ),
.iaddr (iaddr ),
.iwen (iwen ),
.iwdata (iwdata ),
.iwbyten (iwbyten ),
.iren (iren ),
.irdata (irdata ),
.irbyten (irbyten )
);
initial begin
#10_000_000ns $finish;
end
initial begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars(0,ahb_biu_tb);
$fsdbDumpSVA;
$fsdbDumpon;
end
[/source]
B) 一个进阶示范
在以上示范代码中,所有跟APB接口有关的任务都被封装在了IFM文件中,然后这些任务在测试平台中按精心设计的流程顺序被调用出来以组成我们真正的测试用例。在这个测试用例里,我首先通过调用apb_mst_reset()把模块进行了复位,然后通过调用apb_mst_wrdata()和apb_mst_rddata来访问寄存器地址UART_CONFIG。为了让测试平台的结构更清晰,避免直接把测试用例写在测试平台文件里,我更习惯的做法是在测试平台里使用`include "testcase.sv"语句代替整个测试用例,然后把真正的测试用例代码放在独立的testcase.sv文件中。除此之外,请注意本例中的模块还有除APB端口之外的一组存储器访问端口(就是以字母i打头的那些端口),这组端口同样可以通过调用另外一个IFM文件来实现测试的目的。如果把所有端口的IFM都实现出来并加入平台,最终的测试平台大概是这样:
[source lang="verilog"]
//==============================================================================
// Copyright (C) 2015 By Kellen.Wang
// mail@kellen.wang, All Rights Reserved
//==============================================================================
// Module : apb_ifm
// Author : Kellen Wang
// Contact: mail@kellen.wang
// Date : Mar.30.2015
//==============================================================================
// Description :
//==============================================================================
module ahb_biu_tb;
parameter SYS_CLK = 100;
wire clk ;
wire rst_n ;
ahb_if ahb_mst0 (
.hclk (clk ),
.hresetn (rst_n )
);
`include "testcase.sv"
ram_if ram (
.iclk (clk )
);
clk_rst #(
.CLK_FREQ(SYS_CLK ),
.RST_DLY (10 )
) u_clk_rst (
.clk (clk ),
.rst_n (rst_n )
);
ahb_slv_biu u_ahb_slv_biu (
.hclk (clk ),
.hresetn (rst_n ),
.hsel (ahb_mst0.hsel ),
.htrans (ahb_mst0.htrans ),
.hsize (ahb_mst0.hsize ),
.hburst (ahb_mst0.hburst ),
.haddr (ahb_mst0.haddr ),
.hwrite (ahb_mst0.hwrite ),
.hwdata (ahb_mst0.hwdata ),
.hrdata (ahb_mst0.hrdata ),
.hresp (ahb_mst0.hresp ),
.hreadyin (ahb_mst0.hreadyin),
.hready (ahb_mst0.hready ),
.iready (ram.iready ),
.iaddr (ram.iaddr ),
.iwen (ram.iwen ),
.iwdata (ram.iwdata ),
.iwbyten (ram.iwbyten ),
.iren (ram.iren ),
.irdata (ram.irdata ),
.irbyten (ram.irbyten )
);
initial begin
#10_000_000ns $finish;
end
initial begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars(0,ahb_biu_tb);
$fsdbDumpSVA;
$fsdbDumpon;
end
[/source]
现在你告诉我觉得如何?喜欢吗?是不是看起来非常清晰易懂同时又十分优雅?通过使用这种基于接口的验证方法学,我们可以把更多的注意力放到传输级和行为级的测试目的上而不是花费大量时间在时序产生上。更重要的是,这种方法中写成的IFM文件是可以重复使用的,因此等下一次你搭建另一个模块的测试平台时,如果那个模块也碰巧有一组APB接口,那么你就不需要再重头来过了,直接调用这些task产生新的测试用例就可以了。而最妙的是,得到了这么多的好处,你却并不需要学习和掌握任何高深莫测的验证框架或者流行库类,好比说UVM/VMM什么的,而是只需要依靠你自己就可以全部实现了!
III. 总结
接下来我还会进一步探索更好的调试验证方法学,如果你对本文有任何建议的话,欢迎留言。