APB协议UVM验证环境的搭建

5 篇文章 27 订阅

APB协议UVM验证环境的搭建

一、编译文件

只需编译这两个文件即可
在这里插入图片描述

apb_pkg.sv

里面包含了"apb.svh",即编译apb_pkg.sv这个文件的同时,也会编译所需要的所有的头文件。

`ifndef APB_PKG_SV
`define APB_PKG_SV

package apb_pkg;

import uvm_pkg::*;
`include "uvm_macros.svh"

`include "apb.svh"

endpackage : apb_pkg

   
`endif //  `ifndef APB_PKG_SV

apb.svh

`ifndef APB_SVH
`define APB_SVH


`include "apb_transfer.sv"
`include "apb_config.sv"

//master所有的头文件
`include "apb_master_driver.svh"
`include "apb_master_monitor.svh"
`include "apb_master_sequencer.svh"
`include "apb_master_agent.svh"

//slave所有的头文件
`include "apb_slave_driver.svh"
`include "apb_slave_monitor.svh"
`include "apb_slave_sequencer.svh"
`include "apb_slave_agent.svh"

//master头文件里面具体的实现方法
`include "apb_master_driver.sv"       
`include "apb_master_monitor.sv"
`include "apb_master_sequencer.sv"
`include "apb_master_agent.sv"
`include "apb_master_seq_lib.sv"

//slave头文件里面具体的实现方法
`include "apb_slave_driver.sv"       
`include "apb_slave_monitor.sv"
`include "apb_slave_sequencer.sv"
`include "apb_slave_agent.sv"
`include "apb_slave_seq_lib.sv"



   
`endif //  `ifndef APB_SVH

再来编译apb_tb.sv文件

编译的同时,也会编译"apb_tests.svh"、"apb_if.sv"这两个文件。例化协议接口,配置顶层环境的master和slave,默认执行“apb_single_transaction_test”这个测试用例。

`timescale 1ps/1ps
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "apb_tests.svh"
`include "apb_if.sv"
module apb_tb;
  bit clk, rstn;
  initial begin
    fork
      begin 
        forever #5ns clk = !clk;
      end
      begin
        #100ns;
        rstn <= 1'b1;
        #100ns;
        rstn <= 1'b0;
        #100ns;
        rstn <= 1'b1;
      end
    join_none
  end

  apb_if intf(clk, rstn);

  initial begin
    uvm_config_db#(virtual apb_if)::set(uvm_root::get(), "uvm_test_top.env.mst", "vif", intf);
    uvm_config_db#(virtual apb_if)::set(uvm_root::get(), "uvm_test_top.env.slv", "vif", intf);
    run_test("apb_single_transaction_test");
  end

endmodule

apb_tests.svh

`ifndef APB_TESTS_SV
`define APB_TESTS_SV

import apb_pkg::*;

class apb_env extends uvm_env;
  apb_master_agent mst;
  apb_slave_agent slv;
  `uvm_component_utils(apb_env)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    mst = apb_master_agent::type_id::create("mst", this);
    slv = apb_slave_agent::type_id::create("slv", this);
  endfunction
endclass

class apb_base_test extends uvm_test;
  apb_env env;
  `uvm_component_utils(apb_base_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = apb_env::type_id::create("env", this);
  endfunction
endclass

class apb_base_test_sequence extends uvm_sequence #(apb_transfer);
  bit[31:0] mem[bit[31:0]];		//关联数组mem,用来master和slave之间的数据比对,test和slave中都有一个mem
  `uvm_object_utils(apb_base_test_sequence)
  function new(string name=""); 
    super.new(name);
  endfunction : new
  function bit check_mem_data(bit[31:0] addr, bit[31:0] data);
    if(mem.exists(addr)) begin
      if(data != mem[addr]) begin
        `uvm_error("CMPDATA", $sformatf("addr 32'h%8x, READ DATA expected 32'h%8x != actual 32'h%8x", addr, mem[addr], data))
        return 0;
      end
      else begin
        `uvm_info("CMPDATA", $sformatf("addr 32'h%8x, READ DATA 32'h%8x comparing success!", addr, data), UVM_LOW)
        return 1;
      end
    end
    else begin
      if(data != 0) begin
        `uvm_error("CMPDATA", $sformatf("addr 32'h%8x, READ DATA expected 32'h00000000 != actual 32'h%8x", addr, data))
        return 0;
      end
      else begin
        `uvm_info("CMPDATA", $sformatf("addr 32'h%8x, READ DATA 32'h%8x comparing success!", addr, data), UVM_LOW)
        return 1;
      end
    end
  endfunction: check_mem_data

  task wait_reset_release();
    @(negedge apb_tb.rstn);
    @(posedge apb_tb.rstn);
  endtask

  task wait_cycles(int n);
    repeat(n) @(posedge apb_tb.clk);
  endtask

  function bit[31:0] get_rand_addr();
    bit[31:0] addr;
    void'(std::randomize(addr) with {addr[31:12] == 0; addr[1:0] == 0;});
    return addr;
  endfunction
endclass

class apb_single_transaction_sequence extends apb_base_test_sequence;
  apb_master_single_write_sequence single_write_seq;
  apb_master_single_read_sequence single_read_seq;
  apb_master_write_read_sequence write_read_seq;
  rand int test_num = 100;
  constraint cstr{
    soft test_num == 100;
  }
  `uvm_object_utils(apb_single_transaction_sequence)    
  function new(string name=""); 
    super.new(name);
  endfunction : new
  task body();
    bit[31:0] addr;
    this.wait_reset_release();
    this.wait_cycles(10);

    // TEST continous write transaction
    `uvm_info(get_type_name(), "TEST continous write transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;
    end

    // TEST continous read transaction
    `uvm_info(get_type_name(), "TEST continous read transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(single_read_seq, {addr == local::addr;})
      void'(this.check_mem_data(addr, single_read_seq.data));
    end

    // TEST read transaction after write transaction
    `uvm_info(get_type_name(), "TEST read transaction after write transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;
      `uvm_do_with(single_read_seq, {addr == local::addr;})
      void'(this.check_mem_data(addr, single_read_seq.data));
    end


    // TEST read transaction immediately after write transaction
    `uvm_info(get_type_name(), "TEST read transaction immediately after write transaction", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(write_read_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;
      void'(this.check_mem_data(addr, write_read_seq.data));
    end

    this.wait_cycles(10);
  endtask
endclass: apb_single_transaction_sequence

class apb_single_transaction_test extends apb_base_test;
  `uvm_component_utils(apb_single_transaction_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    apb_single_transaction_sequence seq = new();
    phase.raise_objection(this);
    super.run_phase(phase);
    seq.start(env.mst.sequencer);
    phase.drop_objection(this);
  endtask
endclass: apb_single_transaction_test

class apb_burst_transaction_sequence extends apb_base_test_sequence;
  apb_master_burst_write_sequence burst_write_seq;
  apb_master_burst_read_sequence burst_read_seq;
  rand int test_num = 100;
  constraint cstr{
    soft test_num == 100;
  }
  `uvm_object_utils(apb_burst_transaction_sequence)
  function new(string name=""); 
    super.new(name);
  endfunction : new
  task body();
    bit[31:0] addr;
    this.wait_reset_release();
    this.wait_cycles(10);

    // TEST continous write transaction
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(burst_write_seq, {addr == local::addr;})
      foreach(burst_write_seq.data[i]) begin
        mem[addr+(i<<2)] = burst_write_seq.data[i];
      end
      `uvm_do_with(burst_read_seq, {addr == local::addr; data.size() == burst_write_seq.data.size();})
      foreach(burst_read_seq.data[i]) begin
        void'(this.check_mem_data(addr+(i<<2), burst_write_seq.data[i]));
      end
    end

    this.wait_cycles(10);
  endtask
endclass: apb_burst_transaction_sequence

class apb_burst_transaction_test extends apb_base_test;
  `uvm_component_utils(apb_burst_transaction_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    apb_burst_transaction_sequence seq = new();
    phase.raise_objection(this);
    super.run_phase(phase);
    seq.start(env.mst.sequencer);
    phase.drop_objection(this);
  endtask
endclass: apb_burst_transaction_test



`endif // APB_TESTS_SV

apb_if.sv


`ifndef APB_IF_SV
`define APB_IF_SV

interface apb_if (input clk, input rstn);

  logic [31:0] paddr;
  logic        pwrite;
  logic        psel;
  logic        penable;
  logic [31:0] pwdata;
  logic [31:0] prdata;

  // Control flags
  bit                has_checks = 1;
  bit                has_coverage = 1;

  // Actual Signals 
  // USER: Add interface signals

  clocking cb_mst @(posedge clk);
    // USER: Add clocking block detail
    default input #1ps output #1ps;
    output paddr, pwrite, psel, penable, pwdata;
    input prdata;
  endclocking : cb_mst

  clocking cb_slv @(posedge clk);
   // USER: Add clocking block detail
    default input #1ps output #1ps;
    input paddr, pwrite, psel, penable, pwdata;
    output prdata;
  endclocking : cb_slv

  clocking cb_mon @(posedge clk);
   // USER: Add clocking block detail
    default input #1ps output #1ps;
    input paddr, pwrite, psel, penable, pwdata, prdata;
  endclocking : cb_mon

  // Coverage and assertions to be implemented here.
  // USER: Add assertions/coverage here

  // APB command covergroup
  covergroup cg_apb_command @(posedge clk iff rstn);
    pwrite: coverpoint pwrite{
      type_option.weight = 0;
      bins write = {1};
      bins read  = {0};

    }
    psel : coverpoint psel{
      type_option.weight = 0;
      bins sel   = {1};
      bins unsel = {0};
    }
    cmd  : cross pwrite, psel{
      bins cmd_write = binsof(psel.sel) && binsof(pwrite.write);
      bins cmd_read  = binsof(psel.sel) && binsof(pwrite.read);
      bins cmd_idle  = binsof(psel.unsel);
    }
  endgroup

  // APB transaction timing group
  covergroup cg_apb_trans_timing_group @(posedge clk iff rstn);
    psel: coverpoint psel{
      bins single   = (0 => 1 => 1  => 0); 
      bins burst_2  = (0 => 1 [*4]  => 0); 
      bins burst_4  = (0 => 1 [*8]  => 0); 
      bins burst_8  = (0 => 1 [*16] => 0); 
      bins burst_16 = (0 => 1 [*32] => 0); 
      bins burst_32 = (0 => 1 [*64] => 0); 
    }
    penable: coverpoint penable {
      bins single = (0 => 1 => 0 [*2:10] => 1);
      bins burst  = (0 => 1 => 0         => 1);
    }
  endgroup

  // APB write & read order group
  covergroup cg_apb_write_read_order_group @(posedge clk iff (rstn && penable));
    write_read_order: coverpoint pwrite{
      bins write_write = (1 => 1);
      bins write_read  = (1 => 0);
      bins read_write  = (0 => 1);
      bins read_read   = (0 => 0);
    } 
  endgroup

  initial begin
    automatic cg_apb_command cg0 = new();
    automatic cg_apb_trans_timing_group cg1 = new();
    automatic cg_apb_write_read_order_group cg2 = new();
  end

endinterface : apb_if

`endif // APB_IF_SV

二、apb_tests.sv代码分析

apb_base_test_sequence

check_mem_data()方法原理结构框图:

关联数组mem,用来master和slave之间的数据比对,test和slave中都有一个mem,master通过接口发送数据给slave,slave中的mem和test中的mem都会存储这个数据,等从slave读回数据时,就可以和test中mem里面的数据进行比较。
在这里插入图片描述

apb_single_transaction_sequence

随机化addr,测试连续写操作

    // TEST continous write transaction
    `uvm_info(get_type_name(), "TEST continous write transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;
    end

随机化addr,测试连续读操作,并比较数据是否一致

    // TEST continous read transaction
    `uvm_info(get_type_name(), "TEST continous read transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(single_read_seq, {addr == local::addr;})
      void'(this.check_mem_data(addr, single_read_seq.data));
    end

随机化addr,先进行写操作,再进行读操作,并比较读取的数据是否一致

    // TEST read transaction after write transaction
    `uvm_info(get_type_name(), "TEST read transaction after write transaction...", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(single_write_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;
      `uvm_do_with(single_read_seq, {addr == local::addr;})
      void'(this.check_mem_data(addr, single_read_seq.data));
    end

随机化addr,写完立即读,中间没有idle空闲,并检查读取数据是否一致

    // TEST read transaction immediately after write transaction
    `uvm_info(get_type_name(), "TEST read transaction immediately after write transaction", UVM_LOW)
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(write_read_seq, {addr == local::addr; data == local::addr;})
      mem[addr] = addr;
      void'(this.check_mem_data(addr, write_read_seq.data));
    end

例化并挂载

class apb_single_transaction_test extends apb_base_test;
  `uvm_component_utils(apb_single_transaction_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    apb_single_transaction_sequence seq = new();
    phase.raise_objection(this);
    super.run_phase(phase);
    seq.start(env.mst.sequencer);
    phase.drop_objection(this);
  endtask
endclass: apb_single_transaction_test

apb_burst_transaction_sequence

先全部写操作完毕,在完全读出来,地址是连续增长的

    // TEST continous write transaction
    repeat(test_num) begin
      addr = this.get_rand_addr();
      `uvm_do_with(burst_write_seq, {addr == local::addr;})
      foreach(burst_write_seq.data[i]) begin
        mem[addr+(i<<2)] = burst_write_seq.data[i];
      end
      `uvm_do_with(burst_read_seq, {addr == local::addr; data.size() == burst_write_seq.data.size();})
      foreach(burst_read_seq.data[i]) begin
        void'(this.check_mem_data(addr+(i<<2), burst_write_seq.data[i]));
      end
    end

例化并挂载

class apb_burst_transaction_test extends apb_base_test;
  `uvm_component_utils(apb_burst_transaction_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    apb_burst_transaction_sequence seq = new();
    phase.raise_objection(this);
    super.run_phase(phase);
    seq.start(env.mst.sequencer);
    phase.drop_objection(this);
  endtask
endclass: apb_burst_transaction_test

三、apb_master_agent.sv代码分析

agent包括三个组件driversequencermonitor,以及configinterface

例化monitor,根据配置决定是否例化driversequencer

function void apb_master_agent::build();
  super.build();
  // get config
  if( !uvm_config_db#(apb_config)::get(this,"","cfg", cfg)) begin
    `uvm_warning("GETCFG","cannot get config object from config DB")
     cfg = apb_config::type_id::create("cfg");
  end
  // get virtual interface
  if( !uvm_config_db#(virtual apb_if)::get(this,"","vif", vif)) begin
    `uvm_fatal("GETVIF","cannot get vif handle from config DB")
  end
  monitor = apb_master_monitor::type_id::create("monitor",this);
  monitor.cfg = cfg;
  if(cfg.is_active == UVM_ACTIVE) begin
    sequencer = apb_master_sequencer::type_id::create("sequencer",this);
    sequencer.cfg = cfg;
    driver = apb_master_driver::type_id::create("driver",this);
    driver.cfg = cfg;
  end
endfunction : build

根据配置决定是否连接driversequencer

function void apb_master_agent::connect();
  assign_vi(vif);

  if(is_active == UVM_ACTIVE) begin
    driver.seq_item_port.connect(sequencer.seq_item_export);       
  end

endfunction : connect

根据配置决定是否vif和driver、sequencer之间的连接

function void apb_master_agent::assign_vi(virtual apb_if vif);
   monitor.vif = vif;
   if (is_active == UVM_ACTIVE) begin
      sequencer.vif = vif; 
      driver.vif = vif; 
    end
endfunction : assign_vi

四、apb_master_driver.sv代码分析

并行触发get_and_drive()reset_listener()

task apb_master_driver::run();
   fork
     get_and_drive();
     reset_listener();
   join_none
endtask : run

捕捉到复位信号以后,所以信号清零

task apb_master_driver::reset_listener();
  `uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)
  fork
    forever begin
      @(negedge vif.rstn); // ASYNC reset
      vif.paddr <= 0;
      vif.pwrite <= 0;
      vif.psel <= 0;
      vif.penable <= 0;
      vif.pwdata <= 0;
    end
  join_none
endtask

sequencesequencer需要握手,获取transaction以后调用driver_transfer()发送。发送成功以后克隆request生成新的response,作为响应发送回去。

task apb_master_driver::get_and_drive();
  forever begin
    seq_item_port.get_next_item(req);
    `uvm_info(get_type_name(), "sequencer got next item", UVM_HIGH)
    drive_transfer(req);
    void'($cast(rsp, req.clone()));
    rsp.set_sequence_id(req.get_sequence_id());
    seq_item_port.item_done(rsp);
    `uvm_info(get_type_name(), "sequencer item_done_triggered", UVM_HIGH)
  end
endtask : get_and_drive

task apb_master_driver::drive_transfer (apb_transfer t);
  `uvm_info(get_type_name(), "drive_transfer", UVM_HIGH)
  case(t.trans_kind)
    IDLE    : this.do_idle();
    WRITE   : this.do_write(t);
    READ    : this.do_read(t);
    default : `uvm_error("ERRTYPE", "unrecognized transaction type")
  endcase
endtask : drive_transfer

根据trans_kind判断操作命令,分别调用相对应的方法。

task apb_master_driver::do_write(apb_transfer t);
  `uvm_info(get_type_name(), "do_write ...", UVM_HIGH)
  //写操作一共分为两个周期,根据协议第一个周期setup准备阶段需要如下操作
  @(vif.cb_mst);
  vif.cb_mst.paddr <= t.addr;
  vif.cb_mst.pwrite <= 1;
  vif.cb_mst.psel <= 1;
  vif.cb_mst.penable <= 0;
  vif.cb_mst.pwdata <= t.data;
  //第二个阶段拉高penable信号,发送数据
  @(vif.cb_mst);
  vif.cb_mst.penable <= 1;
  repeat(t.idle_cycles) this.do_idle();		//取决于transaction里面的idle
endtask: do_write

task apb_master_driver::do_read(apb_transfer t);
  `uvm_info(get_type_name(), "do_write ...", UVM_HIGH)
  //第一个阶段
  @(vif.cb_mst);
  vif.cb_mst.paddr <= t.addr;
  vif.cb_mst.pwrite <= 0;
  vif.cb_mst.psel <= 1;
  vif.cb_mst.penable <= 0;
  //第二个阶段
  @(vif.cb_mst);
  vif.cb_mst.penable <= 1;
  #100ps;	//需要采样数据,人为添加100ps的delay,是为了避免delta-cycle
  t.data = vif.prdata;		//采样数据
  repeat(t.idle_cycles) this.do_idle();
endtask: do_read

task apb_master_driver::do_idle();
  `uvm_info(get_type_name(), "do_idle ...", UVM_HIGH)
  @(vif.cb_mst);
  //根据协议,paddr、pwrite可以保持不变,等待下一次的传输,这是为了省电
  //vif.cb_mst.paddr <= 0;
  //vif.cb_mst.pwrite <= 0;
  vif.cb_mst.psel <= 0;
  vif.cb_mst.penable <= 0;
  vif.cb_mst.pwdata <= 0;
endtask:do_idle

五、apb_master_monitor.sv代码分析

collect_transfer()方法

在时钟上升沿,同时psel=1penabl=0的时候,判断当前情况下pwrite信号,在第二个周期进行读或者写操作。

task apb_master_monitor::collect_transfer();
  apb_transfer t;
  // Advance clock
  @(vif.cb_mon);
  if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begin
    t = apb_transfer::type_id::create("t");
    case(vif.cb_slv.pwrite)
      1'b1    : begin
                  @(vif.cb_mon);
                  t.addr = vif.cb_mon.paddr;
                  t.data = vif.cb_mon.pwdata;
                  t.trans_kind = WRITE;
                end 
      1'b0    : begin
                  @(vif.cb_mon);
                  t.addr = vif.cb_mon.paddr;
                  t.data = vif.cb_mon.prdata;
                  t.trans_kind = READ;
                end
      default : `uvm_error(get_type_name(), "ERROR pwrite signal value")
    endcase
    item_collected_port.write(t);
  end
endtask: collect_transfer

六、apb_master_seq_lib.sv代码分析

apb_master_single_write_sequence

使用宏'uvm_do_with发送数据。

class apb_master_single_write_sequence extends apb_master_base_sequence;
  rand bit [31:0]      addr;
  rand bit [31:0]      data;

  `uvm_object_utils(apb_master_single_write_sequence)    
  function new(string name=""); 
    super.new(name);
  endfunction : new

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
	  `uvm_do_with(req, {trans_kind == WRITE; addr == local::addr; data == local::data;})
    get_response(rsp);
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body

endclass: apb_master_single_write_sequence

apb_master_single_read_sequence

读操作,拿到返回的rsp的数据后存储在成员变量data里。

class apb_master_single_read_sequence extends apb_master_base_sequence;
  rand bit [31:0]      addr;
  rand bit [31:0]      data;

  `uvm_object_utils(apb_master_single_read_sequence)    
  function new(string name=""); 
    super.new(name);
  endfunction : new

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
	  `uvm_do_with(req, {trans_kind == READ; addr == local::addr;})
    get_response(rsp);
    data = rsp.data;
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body

endclass: apb_master_single_read_sequence

apb_master_write_read_sequence

写操作后进行读操作,所以idle_cycles == 0

class apb_master_write_read_sequence extends apb_master_base_sequence;
  rand bit [31:0]    addr;
  rand bit [31:0]    data;
  rand int           idle_cycles; 
  constraint cstr{
    idle_cycles == 0;
  }

  `uvm_object_utils(apb_master_write_read_sequence)    
  function new(string name=""); 
    super.new(name);
  endfunction : new

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
	  `uvm_do_with(req,  {trans_kind == WRITE; 
                        addr == local::addr; 
                        data == local::data;
                        idle_cycles == local::idle_cycles;
                       })
    get_response(rsp);
    `uvm_do_with(req, {trans_kind == READ; addr == local::addr;})
    get_response(rsp);
    data = rsp.data;
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body

endclass: apb_master_write_read_sequence

apb_master_burst_write_sequence

连续的写操作,按照地址增长的顺序,把所有的数据写到data数组中。因为是连续写操作,所以idle_cycles == 0,即数据之间没有空闲周期。

class apb_master_burst_write_sequence extends apb_master_base_sequence;
  rand bit [31:0]      addr;
  rand bit [31:0]      data[];
  constraint cstr{
    soft data.size() inside {4, 8, 16, 32};
    foreach(data[i]) soft data[i] == addr + (i << 2);
  }

  `uvm_object_utils(apb_master_burst_write_sequence)    
  function new(string name=""); 
    super.new(name);
  endfunction : new

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
    foreach(data[i]) begin
	    `uvm_do_with(req, {trans_kind == WRITE; 
                         addr == local::addr + (i<<2); 
                         data == local::data[i];
                         idle_cycles == 0;
                        })
      get_response(rsp);
    end
    `uvm_do_with(req, {trans_kind == IDLE;})
    get_response(rsp);
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body
endclass: apb_master_burst_write_sequence

apb_master_burst_read_sequence

连续的读操作,每次读取回来的数据,从rsp中拿出来放到data数组中。全部读取完成之后,将总线置为IDLE

class apb_master_burst_read_sequence extends apb_master_base_sequence;
  rand bit [31:0]      addr;
  rand bit [31:0]      data[];
  constraint cstr{
    soft data.size() inside {4, 8, 16, 32};
  }
  `uvm_object_utils(apb_master_burst_read_sequence)
  function new(string name=""); 
    super.new(name);
  endfunction : new

  virtual task body();
    `uvm_info(get_type_name(),"Starting sequence", UVM_HIGH)
    foreach(data[i]) begin
	    `uvm_do_with(req, {trans_kind == READ; 
                         addr == local::addr + (i<<2); 
                         idle_cycles == 0;
                        })
      get_response(rsp);
      data[i] = rsp.data;
    end
    `uvm_do_with(req, {trans_kind == IDLE;})
    get_response(rsp);
    `uvm_info(get_type_name(),$psprintf("Done sequence: %s",req.convert2string()), UVM_HIGH)
  endtask: body
endclass: apb_master_burst_read_sequence

七、apb_slave_driver.sv代码分析

slave要接收master发送过来的数据,所以要模拟一个存储功能,即关联数组mem

  bit[31:0] mem [bit[31:0]];

run()方法

三个方法并行执行

task apb_slave_driver::run();
   fork
     get_and_drive();
     reset_listener();
     drive_response();
   join_none
endtask : run

get_and_drive()方法

task apb_slave_driver::get_and_drive();
  forever begin
    seq_item_port.get_next_item(req);
    `uvm_info(get_type_name(), "sequencer got next item", UVM_HIGH)
    void'($cast(rsp, req.clone()));
    rsp.set_sequence_id(req.get_sequence_id());
    seq_item_port.item_done(rsp);
    `uvm_info(get_type_name(), "sequencer item_done_triggered", UVM_HIGH)
  end
endtask : get_and_drive

reset_listener()方法

等待复位信号,将prdata <= 0,同时清空mem里面的数据。

task apb_slave_driver::reset_listener();
  `uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH)
  fork
    forever begin
      @(negedge vif.rstn); // ASYNC reset
      vif.prdata <= 0;
      this.mem.delete(); // reset internal memory
    end
  join_none
endtask: reset_listener

drive_response()方法

如果当前这一周期是SETUP阶段,即psel = 1 && penable = 0,进而判断是写操作还是读操作,然后调用相对应的方法。

task apb_slave_driver::drive_response();
  `uvm_info(get_type_name(), "drive_response", UVM_HIGH)
  forever begin
    @(vif.cb_slv);
    if(vif.cb_slv.psel === 1'b1 && vif.cb_slv.penable === 1'b0) begin
      case(vif.cb_slv.pwrite)
        1'b1    : this.do_write();
        1'b0    : this.do_read();
        default : `uvm_error(get_type_name(), "ERROR pwrite signal value")
      endcase
    end
    else begin
      this.do_idle();
    end
  end
endtask : drive_response

do_write()方法

如果是写操作,那么等待时钟下一拍,拿到addrdata并放到mem中。

task apb_slave_driver::do_write();
  bit[31:0] addr;
  bit[31:0] data;
  `uvm_info(get_type_name(), "do_write", UVM_HIGH)
  @(vif.cb_slv);
  addr = vif.cb_slv.paddr;
  data = vif.cb_slv.pwdata;
  mem[addr] = data;
endtask: do_write

do_read()方法

如果是读操作,等待penable=1,并且判断mem中是否写过该addr,如果有则写入data,没有则将data置为0,即还是初始化的数据。等待一个延迟后,将data驱动到总线上面。

task apb_slave_driver::do_read();
  bit[31:0] addr;
  bit[31:0] data;
  `uvm_info(get_type_name(), "do_read", UVM_HIGH)
  wait(vif.penable === 1'b1);
  addr = vif.cb_slv.paddr;
  if(mem.exists(addr))
    data = mem[addr];
  else
    data = 0;
  #1ps;
  vif.prdata <= data;
  @(vif.cb_slv);
endtask: do_read

八、运行仿真

执行命令

run -all

验证环境结构
在这里插入图片描述

写操作:写入地址和写入数据相同,只有penable拉高才会写入,数据之间有一个空闲。
在这里插入图片描述
读操作:只有penable拉高才会读数据,没有写入过数据的地址,读出来的值为0。
在这里插入图片描述
先写后读:
在这里插入图片描述
写完立即读操作:
在这里插入图片描述

仿真结果:

在这里插入图片描述
覆盖率:

  // APB command covergroup
  covergroup cg_apb_command @(posedge clk iff rstn);
    pwrite: coverpoint pwrite{
      type_option.weight = 0;
      bins write = {1};
      bins read  = {0};

    }
    psel : coverpoint psel{
      type_option.weight = 0;
      bins sel   = {1};
      bins unsel = {0};
    }
    cmd  : cross pwrite, psel{
      bins cmd_write = binsof(psel.sel) && binsof(pwrite.write);
      bins cmd_read  = binsof(psel.sel) && binsof(pwrite.read);
      bins cmd_idle  = binsof(psel.unsel);
    }
  endgroup

  // APB transaction timing group
  covergroup cg_apb_trans_timing_group @(posedge clk iff rstn);
    psel: coverpoint psel{
      bins single   = (0 => 1 => 1  => 0); 
      bins burst_2  = (0 => 1 [*4]  => 0); 
      bins burst_4  = (0 => 1 [*8]  => 0); 
      bins burst_8  = (0 => 1 [*16] => 0); 
      bins burst_16 = (0 => 1 [*32] => 0); 
      bins burst_32 = (0 => 1 [*64] => 0); 
    }
    penable: coverpoint penable {
      bins single = (0 => 1 => 0 [*2:10] => 1);
      bins burst  = (0 => 1 => 0         => 1);
    }
  endgroup

  // APB write & read order group
  covergroup cg_apb_write_read_order_group @(posedge clk iff (rstn && penable));
    write_read_order: coverpoint pwrite{
      bins write_write = (1 => 1);
      bins write_read  = (1 => 0);
      bins read_write  = (0 => 1);
      bins read_read   = (0 => 0);
    } 
  endgroup


在这里插入图片描述

  • 36
    点赞
  • 255
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
UVM是一种利用SystemVerilog语言开发的硬件验证平台,旨在提高硬件验证的效率和可重用性。APB和SPI作为两种常见的通信协议,在芯片设计中也得到了广泛的应用。基于UVMAPB-SPI验证,可以有效地验证芯片中的通信功能,确保数据传输的正确性和稳定性。 在UVM APB-SPI验证流程中,通过建立测试环境、生成测试用例、执行测试和分析结果等步骤,逐步实现对APB和SPI通信协议验证。其中,测试环境搭建UVM框架的基础,通常由DUT(设计单元)、测试控制器、测试代理以及信号记录器等组件构成。测试用例的生成是测试环节的核心,通过模拟不同的使用场景、数据和模式,覆盖不同的协议功能,完整地检查APB和SPI的特性和功能,并对其进行评估和优化。测试执行阶段主要是对测试用例进行仿真验证,并记录其输出结果,包括通信数据、时序波形和错误信息等。最后,在测试结果分析阶段,通过对输出结果的统计分析和比较,识别和解决发现的问题,不断优化和改进芯片的设计和开发,提高生产效率和质量。 总之,基于UVMAPB-SPI验证是一种高效可靠的硬件验证方法,能够在芯片设计和开发阶段准确评估通信协议的性能和功能,发现和解决可能存在的问题,保证芯片设计的质量和可靠性,提高整个芯片研发流程的效率和成功率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值