SystemVerilog的一个简单验证demo
DUT:
是一个简单的memory。就六个信号,时钟信号clk,复位信号reset(高有效),读使能信号rd_en,写使能信号wr_en,写数据信号wdata,读数据信号rdata。
对于写操作:
address, wr_en和wdata 在同一时钟进行驱动。
对于读操作:
//Memory reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH]; //Reset always @(posedge reset) for(int i=0;i<2**ADDR_WIDTH;i++) mem[i]=8'hFF; // Write data to Memory always @(posedge clk) if (wr_en) mem[addr] <= wdata; // Read data from memory always @(posedge clk) if (rd_en) rdata <= mem[addr];
Testbench:
对于验证来说,主要就是以下几个类组成:transaction,generator,driver,monitor,scoreboard,environment,top。
对于transaction:
主要是对生成激励中所需的字段进行声明,还可以用作DUT信号上监视器监视的活动的占位符。
-
编写transaction的步骤:
-
首先对域进行声明:
class transaction; //declaring the transaction items bit [1:0] addr; bit wr_en; bit rd_en; bit [7:0] wdata; bit [7:0] rdata; bit [1:0] cnt; endclass
- 对变量使用rand进行随机化
class transaction; //declaring the transaction items rand bit [1:0] addr; rand bit wr_en; rand bit rd_en; rand bit [7:0] wdata; bit [7:0] rdata; bit [1:0] cnt; endclass
-
对部分变量进行约束
//constaint, to generate any one among write and read constraint wr_rd_c { wr_en != rd_en; };
-
generator类的编写
主要是对transaction随机化产生激励,并且把产生的类发送给driver。
-
声明事务类句柄
class generator; //declaring transaction class rand transaction trans; endclass
-
随机化事务类
class generator; //declaring transaction class rand transaction trans; //main task, generates(create and randomizes) the packets and puts into mailbox task main(); trans = new(); if( !trans.randomize() ) $fatal("Gen:: trans randomization failed"); gen2driv.put(trans); endtask endclass
-
利用信箱把随机化后的事务类发送给driver
class generator; //declaring transaction class rand transaction trans; //declaring mailbox mailbox gen2driv; //constructor function new(mailbox gen2driv); //getting the mailbox handle from env this.gen2driv = gen2driv; endfunction //main task, generates(create and randomizes) the packets and puts into mailbox task main(); trans = new(); if( !trans.randomize() ) $fatal("Gen:: trans randomization failed"); gen2driv.put(trans); endtask endclass
-
增加变量去控制事务产生的数量
class generator; //declaring transaction class rand transaction trans; //declaring mailbox mailbox gen2driv; //repeat count, to specify number of items to generate int repeat_count; //constructor function new(mailbox gen2driv); //getting the mailbox handle from env this.gen2driv = gen2driv; endfunction //main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox task main(); repeat(repeat_count) begin trans = new(); if( !trans.randomize() ) $fatal("Gen:: trans randomization failed"); gen2driv.put(trans); end endtask endclass
-
添加一个事件来指示生成过程的完成,该事件将在生成过程完成时触发。
class generator; //declaring transaction class rand transaction trans; //declaring mailbox mailbox gen2driv; //repeat count, to specify number of items to generate int repeat_count; //event event ended; //constructor function new(mailbox gen2driv,event ended); //getting the mailbox handle from env this.gen2driv = gen2driv; this.ended = ended; endfunction //main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox task main(); repeat(repeat_count) begin trans = new(); if( !trans.randomize() ) $fatal("Gen:: trans randomization failed"); gen2driv.put(trans); end -> ended; endtask endclass
-
interface的编写
接口将对信号进行分组,指定方向(Modport)并同步信号(时钟块)。
-
driver时钟块
//driver clocking block clocking driver_cb @(posedge clk); default input #1 output #1; output addr; output wr_en; output rd_en; output wdata; input rdata; endclocking
-
monitor时钟块
//monitor clocking block clocking monitor_cb @(posedge clk); default input #1 output #1; input addr; input wr_en; input rd_en; input wdata; input rdata; endclocking
-
指定方向
//driver modport modport DRIVER (clocking driver_cb,input clk,reset); //monitor modport modport MONITOR (clocking monitor_cb,input clk,reset);
-
Driver类的编写
该类通过将事务类值分配给接口信号,接收从生成器生成的激励并驱动到DUT。
-
声明接口和信箱,通过构造函数获取接口和信箱句柄
//creating virtual interface handle virtual mem_intf mem_vif; //creating mailbox handle mailbox gen2driv; //constructor function new(virtual mem_intf mem_vif,mailbox gen2driv); //getting the interface this.mem_vif = mem_vif; //getting the mailbox handle from environment this.gen2driv = gen2driv; endfunction
-
添加复位任务,把接口信号初始化
//Reset task, Reset the Interface signals to default/initial values task reset; wait(mem_vif.reset); $display("--------- [DRIVER] Reset Started ---------"); `DRIV_IF.wr_en <= 0; `DRIV_IF.rd_en <= 0; `DRIV_IF.addr <= 0; `DRIV_IF.wdata <= 0; wait(!mem_vif.reset); $display("--------- [DRIVER] Reset Ended ---------"); endtask
-
添加驱动任务,把generator产生的事务驱动给DUT
//drive the transaction items to interface signals task drive; forever begin transaction trans; `DRIV_IF.wr_en <= 0; `DRIV_IF.rd_en <= 0; gen2driv.get(trans); $display("--------- [DRIVER-TRANSFER: %0d] ---------",no_transactions); @(posedge mem_vif.DRIVER.clk); `DRIV_IF.addr <= trans.addr; if(trans.wr_en) begin `DRIV_IF.wr_en <= trans.wr_en; `DRIV_IF.wdata <= trans.wdata; $display("\tADDR = %0h \tWDATA = %0h",trans.addr,trans.wdata); @(posedge mem_vif.DRIVER.clk); end if(trans.rd_en) begin `DRIV_IF.rd_en <= trans.rd_en; @(posedge mem_vif.DRIVER.clk); `DRIV_IF.rd_en <= 0; @(posedge mem_vif.DRIVER.clk); trans.rdata = `DRIV_IF.rdata; $display("\tADDR = %0h \tRDATA = %0h",trans.addr,`DRIV_IF.rdata); end $display("-----------------------------------------"); no_transactions++; end endtask
-
添加本地变量,记录传输的次数
//used to count the number of transactions int no_transactions; //drive the transaction items to interface signals task drive; ------ ------ no_transactions++; endtask
-
Monitor类的编写
-
声明接口和信箱,通过构造函数获取接口和信箱句柄。
//creating virtual interface handle virtual mem_intf mem_vif; //creating mailbox handle mailbox mon2scb; //constructor function new(virtual intf vif,mailbox mon2scb); //getting the interface this.vif = vif; //getting the mailbox handles from environment this.mon2scb = mon2scb; endfunction
-
采样逻辑并将采样的事务发送到记分板
task main; forever begin transaction trans; trans = new(); @(posedge mem_vif.MONITOR.clk); wait(`MON_IF.rd_en || `MON_IF.wr_en); trans.addr = `MON_IF.addr; trans.wr_en = `MON_IF.wr_en; trans.wdata = `MON_IF.wdata; if(`MON_IF.rd_en) begin trans.rd_en = `MON_IF.rd_en; @(posedge mem_vif.MONITOR.clk); @(posedge mem_vif.MONITOR.clk); trans.rdata = `MON_IF.rdata; end mon2scb.put(trans); end endtask
-
Scoreboard类的编写
-
声明信箱和变量以保持事务计数,通过构造函数连接句柄
//creating mailbox handle mailbox mon2scb; //used to count the number of transactions int no_transactions; //constructor function new(mailbox mon2scb); //getting the mailbox handles from environment this.mon2scb = mon2scb; endfunction
-
存储wdata并将rdata与存储的数据进行比较的逻辑
//stores wdata and compare rdata with stored data task main; transaction trans; forever begin #50; mon2scb.get(trans); if(trans.rd_en) begin if(mem[trans.addr] != trans.rdata) $error("[SCB-FAIL] Addr = %0h,\n \t Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata); else $display("[SCB-PASS] Addr = %0h,\n \t Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata); end else if(trans.wr_en) mem[trans.addr] = trans.wdata; no_transactions++; end endtask
-
Environment类的编写
-
声明句柄
//generator and driver instance generator gen; driver driv; monitor mon; scoreboard scb; //mailbox handle's mailbox gen2driv; mailbox mon2scb; //event for synchronization between generator and test event gen_ended; //virtual interface virtual mem_intf mem_vif;
-
在构造函数中创造信箱,driver,generator等
//constructor function new(virtual mem_intf mem_vif); //get the interface from test this.mem_vif = mem_vif; //creating the mailbox (Same handle will be shared across generator and driver) gen2driv = new(); mon2scb = new(); //creating generator and driver gen = new(gen2driv,gen_ended); driv = new(mem_vif,gen2driv); mon = new(mem_vif,mon2scb); scb = new(mon2scb); endfunction
-
为了更好的访问,使用三级任务
task pre_test(); driv.reset(); endtask task test(); fork gen.main(); driv.main(); mon.main(); //---NEW CODE--- scb.main(); //---NEW CODE--- join_any endtask task post_test(); wait(gen.ended.triggered); wait(gen.repeat_count == driv.no_transactions); wait(gen.repeat_count == scb.no_transactions); //---NEW CODE--- endtask
-
添加一个运行任务以调用上述方法,在post_test()之后调用$ finish以结束模拟。
task run; pre_test(); test(); post_test(); $finish; endtask
-
测试类的编写
测试负责,创造环境,配置测试平台,即设置要生成的事务的类型和数量,启动仿真。
-
声明并创建环境
//declaring environment instance environment env; initial begin //creating environment env = new(intf); end
-
配置要生成的事务数
//setting the repeat count of generator as 10, means to generate 10 packets env.gen.repeat_count = 10;
-
启动仿真
//calling run of env, it interns calls generator and driver main tasks. env.run();
-
顶层测试的编写
这是最顶层的文件,用于连接DUT和TestBench。由DUT,Test和Interface实例组成。连接DUT和TestBench。
-
声明并生成时钟并设置
//clock and reset signal declaration bit clk; bit reset; //clock generation always #5 clk = ~clk; //reset Generation initial begin reset = 1; #5 reset =0; end
-
创建接口实例
//creatinng instance of interface, inorder to connect DUT and testcase mem_intf intf(clk,reset);
-
创建设计实例并连接接口信号
//DUT instance, interface signals are connected to the DUT ports memory DUT ( .clk(intf.clk), .reset(intf.reset), .addr(intf.addr), .wr_en(intf.wr_en), .rd_en(intf.rd_en), .wdata(intf.wdata), .rdata(intf.rdata) );
-
创建一个测试实例并通过接口句柄
//Testcase instance, interface handle is passed to test as an argument test t1(intf);