catalogue
1. Overall environment construction
0. Preface
After learning the whole SV, in order to further consolidate the knowledge, preliminarily understand and build a simple verification environment, so build a verification environment for an asynchronous fifo module; The specific implementation and detailed introduction of the asynchronous fifo can be seen in this article: Asynchronous FIFO -- Verilog implementation.
Firstly, the architecture block diagram of the whole verification process is presented as follows, and the subsequent verification environment will be built based on this diagram:
For verifying an IP, first specify the verification strategy, extract the verification points and make the corresponding test plan. The asynchronous fifo function is single and simple, so it is specified as follows:
1. Overall environment construction
This part builds the most basic part of the whole verification architecture, including five files: interface, clk generator, rst generator, environment and top; This will generate clock excitation and reset excitation for the DUT. The correct construction of this part is the basis for adding other components in the future.
1.1 interface construction
It's a personal habit to build interface files first to facilitate subsequent connections
interface fifoPorts #(parameter DSIZE=8); logic wclk;//Declare all input / output interfaces logic rclk; logic [DSIZE-1:0]wdata; logic [DSIZE-1:0]rdata; logic wfull; logic rempty; logic winc,rinc; logic wrst_n,rrst_n; clocking wcb@(posedge wclk);//Synchronous drive winc and wdata output winc; output wdata; endclocking clocking rcb@(posedge wclk);//Synchronous drive rinc output rinc; endclocking modport TB( output wrst_n,rrst_n,wdata,winc,rinc,wclk,rclk, input rdata,wfull,rempty);//Specify signal direction for TB modport DUT( input wrst_n,rrst_n,wdata,winc,rinc,wclk,rclk, output rdata,wfull,rempty);//Specify the signal direction for the DUT endinterface
1.2 clk generator setup
class clockGenerator; logic wclk,rclk; int period;//Clock half cycle virtual fifoPorts itf;//Define the virtual interface and send the signal in the software to the hardware function new(virtual fifoPorts itf);// this.itf=itf; endfunction /*>==================clkActivate=============<*/ task automatic clkActivate(input string clkName);//wclk/rclk from external input determines what clk is generated if(clkName=="wclk")begin//wclk $display("%0t:Calling task wclkActivate",$time); this.itf.wclk=1'b0; forever begin #this.period this.itf.wclk++; end end else begin//rclk $display("%0t:Calling task rclkActivate",$time); this.itf.rclk=1'b0; forever begin #this.period this.itf.rclk++;// 0 1 0 1 clock reversal by self addition end end endtask /*>==================endclkActivate=============<*/ /*>==================clkGenerator=============<*/ task automatic clkGenerator(input string clkName,input int clkPeriod);//The cycle and type of clk are determined by the wclk/rclk and cycle input by external call $display("%0t:Calling task clkGenerator for %s",$time,clkName); this.period=clkPeriod/2; fork clkActivate(clkName); join_none endtask /*>==================endclkGenerator=============<*/ endclass
1.3 rst generator setup
class resetGenerator; logic wrst,rrst; int period;//How many clock cycles are reset virtual fifoPorts itf;//Define the virtual interface and send the signal in the software to the hardware function new(virtual fifoPorts itf);// this.itf=itf; endfunction /*>==================rstActivate=============<*/ task automatic rstActivate(input string rstName);//wrst/rrst is determined according to the external call name if(rstName=="rrst")begin $display("%0t:Calling task rrstActivate",$time); this.itf.rrst_n=1'b1; repeat(this.period) @(posedge this.itf.rclk); this.itf.rrst_n=1'b0; repeat(this.period) @(posedge this.itf.rclk); this.itf.rrst_n=1'b1; end else begin $display("%0t:Calling task wrstActivate",$time); this.itf.wrst_n=1'b1; repeat(this.period) @(posedge this.itf.wclk); this.itf.wrst_n=1'b0; repeat(this.period) @(posedge this.itf.wclk); this.itf.wrst_n=1'b1; endtask /*>==================endrstActivate=============<*/ /*>==================rstGenerator=============<*/ task automatic rstGenerator(input string rstName,input int rstPeriod);//The external call determines which signal to reset and how long to reset $display("%0t:Calling task rstGenerator for %s",$time,clkName); this.period=rstPeriod; rstActivate(rstName); endtask /*>==================endrstGenerator=============<*/ endclass
1.4 environment construction
class environment #(parameter DSIZE=8); virtual fifoPorts itf; clockGenerator w_clkGen;//Declaration handle clockGenerator r_clkGen; resetGenerator wrstGen; resetGenerator rrstGen; function new(virtual fifoPorts itf); this.itf=itf; w_clkGen=new(itf);//instantiation r_clkGen=new(itf); wrstGen=new(itf); rrstGen=new(itf); endfunction extern task wcgu(string msg="wclk",int clk_p=10); extern task rcgu(string msg="rclk",int clk_p=10); extern task wrgu(string msg="wrst",int clk_p=10); extern task rrgu(string msg="rrst",int clk_p=10); endclass task environment::wcgu(string msg="wclk",int clk_p=10); w_clkGen.clkGenerator(msg,clk_p); endtask task environment::rcgu(string msg="rclk",int clk_p=10); r_clkGen.clkGenerator("rclk",10); endtask task environment::wrgu(string msg="wrst",int clk_p=10); wrstGen.rstGenerator("wrst",10); endtask task environment::rrgu(string msg="rrst",int clk_p=10); rrstGen.rstGenerator("rrst",10); endtask
1.5 # top construction
1.5.1 fifo_top build
module fifo_top #(parameter DSIZE=8,parameter ADDRSIZE=4) (fifoPorts.DUT itf); reg wclk_tmp; always(*)begin #1 wclk_tmp=itf.wclk;// Wclk and rclk are phased differently by delay end fifo1 #(.DSIZE(DSIZE),.ASIZE(ADDRSIZE)) / / set the data bit width and depth I0 for asynchronous fifo( .rdata(itf.rdata), .wfull(itf.wfull), .rempty(itf.rempty), .wdata(itf.wdata), .winc(itf.winc), .wclk(wclk_tmp), .wrst_n(itf.wrst_n), .rinc(itf.rinc), .rclk(itf.rclk), .rrst_n(itf.rrst_n));//DUT and interface connection endmodule
1.5.2 top floor construction
We can put the parameters used and all components to be compiled into the package to facilitate top calling and make the code more concise and understandable.
package fifo_params; parameter DSIZE=8; parameter ADDRSIZE=4; parameter DEPTH=1<<ADDRSIZE; typedef enum{WR,RD} opt_e; `include"Relevant components used" ······ ······ endpackage
module top(); import fifo_params::*; fifoPorts #(DSIZE) itf();// Instantiated interface fifo_top i0(itf.DUT);//DUT and interface connection environment #(DSIZE) env; initial begin env=new(itf);Instantiate the verification environment to facilitate subsequent calls of tasks inside env.wcgu("wclk",10); env.rcgu("rclk",10); fork//Reset while reading and writing env.wrgu("wrst",10); env.rrgu("rrst",10); join repeat(10) @(posedge itf.rclk); $finish; end endmodule
At this point, the whole framework of the verification environment is built. As shown in the figure below, the rest is to write the remaining components, then connect them in environment and invoke them in top.
2. Add remaining components
2.1 transaction component
If the generator is a gun, the transaction is a bullet
class packet; rand bit[DSIZE-1:0] data; rand opt_e opt; endclass
2.2 generator components
It is used to send the randomized transaction, which is equivalent to sending the incentive. Here, we send it to the mailbox for communication between the generator and the driver.
class generator #(parameter DSIZE=8); packet gpkt; mailbox GSMbx; function new(mailbox GSMbx); this.GSMbx=GSMbx; endfunction task send(int sendNumber); $display("%0t:Send task,Starting Send...sendNumber=%0d",$time,sendNumber); repeat(sendNumber)begin gpkt=new; assert(gpkt.randomize); $display(gpkt); GSMbx.put(gpkt); end $display("%0t:Send task,End Send",$time); endtask endclass
2.3 driver assembly
Take the data from the mailbox between the generator and the driver and send it to the DUT through the virtual interface.
class driver #(parameter DSIZE=8); virtual fifoPorts itf; mailbox GSMbx; mailbox DWMbx; packet dpkt; function new(virtual fifoPorts itf,mailbox GSMbx,mailbox DWMbx); this.itf=itf; this.GSMbx=GSMbx; this.DWMbx=DWMbx; endfunction task Write(input int putInMbx,input int writeNumber); int i=0; $display("%0t:Driver.Write task,Starting write...writeNumber=%0d",$time,writeNumber); do begin this.itf.wcb.winc<=1'b1; GSMbx.get(dpkt); this.itf.wcb.wdata<=dpkt.data; pkt.opt=WR; if(putInMbx)begin this.DWMbx.put(dpkt); end @(posedge itf.wclk); i++; end while(i<writeNumber); $display("%0t:Driver.Write task,End write",$time); endtask endclass
2.4 monitor components
Receive data from the DUT and send it to the mailbox of monitor and scoreboard.
class monitor #(parameter DSIZE=8); virtual fifoPorts itf; mailbox MRMbx; packet mpkt; function new(virtual fifoPorts itf,mailbox MRMbx); this.itf=itf; this.MRMbx=MRMbx; endfunction task Read(input int putInMbx,input int readNumber); int i=0; $display("%0t:Monitor.Read task,Start Reading...readNumber=%0d",$time,readNumber); do begin mpkt=new(); this.itf.rinc=1'b1; @(posedge itf.rclk); if(putInMbx)begin this.mpkt.data=itf.rdata; this.mpkt.opt=RD; this.MRMbx.put(mpkt); end i++; end while(i<readNumber); this.itf.rinc=1'b0; $display("%0t:Monitor.Read task,End Reading...readNumber=%0d",$time); endtask endclass
2.5 scoreboard components
Compare whether the data of Driver and monitor are consistent. Because fifo is only a data transfer station, the data is consistent. If it is a complex DUT, you should also create a ref model to compare the received data with the ref model.
class scoreboard #(parameter DSIZE=8); mailbox DWMbx,MRMbx; packet wpkt,rpkt; function new(mailbox DWMbx,MRMbx); this.DWMbx=DWMbx; this.MRMbx=MRMbx; endfunction task compareData; int loopw,loopr; printMbxContent(DWMbx,"Golden Data is:"); printMbxContent(MRMbx,"Actual Data is:"); $display("%0t:Scoreboard.Compare task,Starting Compare...",$time); loopw=DWMbx.num(); loopr=MRMbx.num(); if(loopw!=loopr)begin $display("%0t:FAILED for size-mismatch",$time); end else begin for(int i=0;i<loopw;i++)begin DWMbx.get(mpkt); MRMbx.get(rpkt); if(mpkt.data==rpkt.data) $display("%0t:PASS:Read and Write are same:R%0h=W%0h",$time,this.rpkt.data,this.wpkt.data); else $display("%0t:FAILED:Read and Write are different:R%0h=W%0h",$time,this.rpkt.data,this.wpkt.data); end end endtask task printMbxContent(input mailbox mbx,string message); int mbxElements; packet pkt; packet q[$]; mbxElements=mbx.num(); for(int i=0;i<mbxElements;i++)begin mbx.get(pkt); q.push_back(pkt); mbx.put(pkt); end foreach(q[i])begin $write("%0h",q[i].data); end $display(" "); endtask; endclass
2.6 update environment
The above is just the definition of various components. We need to connect them together in the environment.
class environment #(parameter DSIZE=8); virtual fifoPorts itf; clockGenerator w_clkGen;//Declaration handle clockGenerator r_clkGen; resetGenerator wrstGen; resetGenerator rrstGen; generator #(DSIZE) gen; driver #(DSIZE) drv; monitor #(DSIZE) mon; scoreboard #(DSIZE) scb; mailbox wbox,rbox,sbox; function new(virtual fifoPorts itf); this.itf=itf; w_clkGen=new(itf);//instantiation r_clkGen=new(itf); wrstGen=new(itf); rrstGen=new(itf); wbox=new(); rbox=new(); sbox=new(); gen=new(sbox); drv=new(itf,sbox,wbox); mon=new(itf,rbox); scb=new(wbox,rbox); endfunction extern task wcgu(string msg="wclk",int clk_p=10); extern task rcgu(string msg="rclk",int clk_p=10); extern task wrgu(string msg="wrst",int clk_p=10); extern task rrgu(string msg="rrst",int clk_p=10); extern task gendata(int sendNumber=16); extern task fifoWrite(input int putInMbx=1,int writeNumber=16); extern task fifoRead(input int putInMbx=1,int readNumber=16); extern task compareResult; endclass task environment::wcgu(string msg="wclk",int clk_p=10); w_clkGen.clkGenerator(msg,clk_p); endtask task environment::rcgu(string msg="rclk",int clk_p=10); r_clkGen.clkGenerator("rclk",10); endtask task environment::wrgu(string msg="wrst",int clk_p=10); wrstGen.rstGenerator("wrst",10); endtask task environment::rrgu(string msg="rrst",int clk_p=10); rrstGen.rstGenerator("rrst",10); endtask task environment::gendata(int sendNumber=16); gen.send(sendNumber); endtask task environment::fifoWrite(input int putInMbx=1,int writeNumber=16); drv.write(putInMbx,writeNumber); endtask task environment::fifoRead(input int putInMbx=1,int readNumber=16); mon.read(putInMbx,readNumber); endtask task environment::compareResult; scb.compareResult; endtask
2.7 update top
module top(); import fifo_params::*; fifoPorts #(DSIZE) itf();// Instantiated interface fifo_top i0(itf.DUT);//DUT and interface connection environment #(DSIZE) env; initial begin env=new(itf);Instantiate the verification environment to facilitate subsequent calls of tasks inside env.wcgu("wclk",10); env.rcgu("rclk",10); fork//Reset while reading and writing env.wrgu("wrst",10); env.rrgu("rrst",10); join repeat(10) @(posedge itf.wclk); env.gendata(26); env.fifoWrite(26); repeat(10) @(posedge itf.wclk); env.fifoRead(26); repeat(10) @(posedge itf.rclk); env.compareResult; $finish; end endmodule
3. Simulation results
First, simply verify whether a reset function is normal and whether the read-write pointer returns to 0 after reset:
Then verify whether the data can be written and read normally; In the abnormal state, whether the corresponding signal is pulled up when writing full and reading empty:
When wclk and rclk are verified to be the same, whether the data can be written and read out at the same time is normal. It can be seen here that it may be due to the characteristics of DUT itself. rempty pulls down several clock cycles late, resulting in inaccurate data reading and loss of the following data:
Then verify whether the synchronous reading and writing under different clocks are normal. First, write data quickly and read data slowly. At the same time, in order to avoid the above rempty delay, write first and delay reading are adopted:
The following is fast reading and slow writing:
4. Conclusion
So far, this very simple verification project is over. Through this project, I have practiced the specific application of SV syntax, and built a simple verification environment myself; However, there are also some problems. For example, the understanding of DUT is not very good, so that the tested waveform does not know whether it is a DUT bug or it should be. Later, we will strengthen the understanding of asynchronous fifo.
It's a long way to change careers. I'm just a novice at present, so please criticize and correct the wrong places in the article! There are still too many things to learn, but as long as you stick to learning, study hard and practice more, I believe you will succeed!