How To Fast Build Testbench For Module Test

Table of Content

Does an IC designer need to know about verification skills? Personally, I suggest that it is necessary to understand the basic methodologies. To have an in-depth understanding of either VMM/UVM methodology may take three or five months, but to just learn the core idea of how and why to build it can cost you maybe only two weeks. The two key skills to study for no matter which kind of test methodology are the same: proper design of testbench architecture for automatic test vector generation, flexibility and reusability, as well as the design of testcase group for the balance between test efficiency and coverage. I suppose that many of other IC designers, just like myself, have learnt the basic idea of the two keys. However, still we don't know how to practise it in our daily debugging works without knowing the details of any particular verification methodology. It doesn't matter, after many times of trying, I have found a very good method to build a module level testbench, and I'd love to share it with you guys in this article.

I. The Interface Based Methodology

You must have not heard about this methodology before, cause I just invented it lately. By the time when System Verilog was designed, the inventors of it may have thought about such verification method, but somehow eventually on one has ever use it to build testbench. I say it because that the first inspiration I got for this method was from a simple example in the official document of System Verilog. Then I tried to form my own methodology base on this little tip and finally I succeeded.

The Interface Based Methodology provide the simplest way to build a fully functional testbench for a module. The architecture of the testbench is not complicated at all, but still it is highly flexible and reusable. It gives you a high-level-abstracted manner to organise your test patterns. To achieve all these benefits, the only thing you need to do is to learn how to write IFM (interface function model, I invented this phrase because it is different from BFM).

So, now I am going to tell you how to design a IFM for a specific interface. Adhere, I'd like to take a simple APB Bus Slave Interface as example.

[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]

Please read the code above. Should you have problem understanding the grammar or anything, google it or comment below. In this article, I will majorly focus on the three tasks. As I said before, this IFM is for APB Bus Slave Interface, so I designed these tasks to imitate the behaviour of a APB Master:

  • apb_mst_wrdata()
  • apb_mst_rddata()
  • apb_mst_reset()

II. How To Use IFM In Testbench

A) A Simple Example

Remember to include the IFM file to your compile list. Then you will be able to instantiate it in you testbench file. Your testbench file now should include code like this:

[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) An Advanced Example

As you can tell from the code above, the tasks that packaged in the IFM are called from the testbench in a designated process flow. In this example, I firstly reset the module by calling apb_mst_reset(), and then access to address UART_CONFIG by calling apb_mst_wrdata() and apb_mst_rddata. To make the architecture clearer, instead of leaving the function calls in the testbench file, I prefer to use `include "testcase.sv" in the testbench while putting all lengthy function calls into testcase.sv. Besides, please notice that the memory access interface (interface starting with letter 'i') could also be modelled with another IFM. In this case, the testbench should look like this:

[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]

Now tell me how do you like it? Isn't it fairly straightforward and elegant? With this interface based methodology, we could put our most attention on behaviour level but not sequence level. More importantly, these IFM are reusable, so you don't have to start from scratch when next time you need to design a testbench for any new module that has APB interface. And the best part is, after all these conveniences, you still don't need any library support from VMM/UVM or whatever, every thing is done only by yourself!

III. Conclusion

I will take further exploration for better debugging methods, if you have any comments to the content of this article, please leave it below.

工程师,个人公众号:IF推理同好会,个人微信小游戏:IF迷你侦探
Posts created 32

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top