APB协议UVM验证环境的搭建

2023-10-26

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


在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

APB协议UVM验证环境的搭建 的相关文章

  • 文章UVM and C – Perfect Together的解读与思考

    解读UVM and C Perfect Together 文章获取方式 xff1a 路科验证 gt 资源 gt DVCon2018 USA zip gt 08 3 http rockeric com resource paper 文章主要内
  • 位置式和增量式PID控制

    PID控制是一个二阶线性控制器 定义 通过调整比例 积分和微分三项参数 使得大多数的工业控制系统获得良好的闭环控制性能 优点 a 技术成熟 b 易被人们熟悉和掌握 c 不需要建立数学模型 d 控制效果好 e 鲁棒性 通常依据控制器输出与执行
  • SNMP V1 V2 V3版本的联系和区别 .

    SNMP 是一个协议用来管理网络上的节点 包括工作站 路由器 交换机 集线器和其他的外围设备 SNMP是一个应用协议 使用UDP封装进行传输 UDP是一个无连接的传输层协议 在OSI模型中为第四层协议 提供简单的可靠的传输服务 SNMP使网
  • TCP 几个字段含义

    1 tcp out of order tcp有问题 2 tcp segment of a reassembled PDU 3 Tcp previous segment lost tcp先前的分片丢失 4 Tcp acked lost seg
  • vcs覆盖率选项

    vcs中常用的收集覆盖率选项如下 1 cm dir
  • sequence_item、sequence、sequencer、driver的关系

    框图 简单描述 driver sequencer sequence sequence item 细节理解 最初的验证平台只需要driver即可为什么还需要sequence机制 sequence机制的内部协议 sequence还有很多细节需要
  • http协议详解

    本篇博文主要介绍HTTP请求 响应的系列过程 包括四个部分 是在陆续学习中觉着之间有关联总结下来的 以便自己今后忘记后可以快速查看也为各位看到这篇文章的朋友们梳理一下知识 下面 正文开始 作为一个前端开发人员 我们每天都在与页面打交道 那么
  • openssl做HMAC实例(C++)

    1 HMAC简介 1 MAC Message Authentication Code 消息认证码算法 可以将其认为是含有秘钥的散列 Hash 函数算法 即兼容了MD和SHA算法 并在此基础上加上了秘钥 因此MAC算法也经常被称作HMAC算法
  • 计算机网络应用层协议分析总结

    1 应用层协议原理 1 1 网络应用程序体系结构 C S结构 有一个总是打开的主机称为服务器 它服务于来自许多其他称为客户机的主机请求 客户机主机既可能有时打开 也可能总是打开 C S结构之下 客户机之间不直接通信 服务器有固定的IP地址
  • 浅谈TCP/IP协议

    一 TCP IP协议 TCP 传输控制协议 IP 因特网互联协议 TCP IP 合称网络通讯协议 是Internet最基本的协议 Internet国际互联网络的基础 由网络层的IP协议和传输层的TCP协议组成 TCP IP 定义了电子设备如
  • TCP flag注释

    三次握手Three way Handshake 一个虚拟连接的建立是通过三次握手来实现的 1 B gt SYN gt A 假如服务器A和客户机B通讯 当A要和B通信时 B首先向A发一个SYN Synchronize 标记的包 告诉A请求建立
  • uvm之寄存器模型

    寄存器模型的意义 寄存器模型的搭建 寄存器模型的FAQ 寄存器模型的意义 寄存器模型 字面理解 跟参考模型类似 就是为工程里的寄存器提供一个参考模型 这个模型里 包括各个寄存器字段描述 寄存器 寄存器组 寄存器地址映射等信息 有如下好处 寄
  • UVM::phase的run order

    1 build time bulid connect end of elaboration 2 run time start of simulation run run有分为 pre reset reset post reset pre c
  • UVM 寄存器内建测试序列(built-in sequences)

    原文链接 https blog csdn net qq 42419590 article details 121487295 UVM 寄存器内建测试序列 built in sequences 不少有经验的UVM用户可能会忽略UVM针对寄存器
  • 嵌入式编写TCP连接过程

    先来一个三次握手和四次挥手 嵌入式连接过程 1 af 为地址族 Address Family 也就是 IP 地址类型 常用的有 AF INET 和 AF INET6 AF 是 Address Family 的简写 INET是 Inetnet
  • IC验证工程师工作一周年的体会

    转眼之间自己已经工作一周年了 作为一名验证工程师 这一年里面感觉自己虽然有了一定的成长 但是成长的还是比较缓慢的 接下来从个人的角度说说我现在对从IC验证的一些体会 一 要养成良好的工作习惯 1 自己在工作中发现很多时候都是可以偷懒的 有时
  • 电力电子转战数字IC——我的IC面试(2022.10.14更新)

    目录 感谢信 HKWS10 14面试 25mins JXC10 13面试 30mins JDSK9 23面试 42mins 快速的自我介绍 介绍一下这个MCDF的项目 你这里写SV搭建的验证环境 和UVM搭建的有什么区别吗 你这里写了覆盖率
  • 同源策略与跨域

    前言 最近业务上前端同学多次联系说访问腾讯云cos资源的时候因为跨域的问题访问不到 大致看了下腾讯云关于设置跨域访问的教程 按照前端同学给的域名等选项就给配了 而且测试下来也是好的 但是呢一直不知道什么是跨域这里就做一个简单的学习记录 一
  • 程序猿眼中的协议:TCP / IP 五层网络模型

    哈喽 大家好 我是你们的老朋友 保护小周 本期为大家带来的是 网络基础原理中的 TCP IP 五层网络模型 主要从协议的概念 网络模型 数据分层传输的流程 几个方面讲解 看完之后可以轻松的理解数据是如何在网络中传输的 确定不来看看嘛 更多精
  • uvm的config_db

    config db 是用于在各个uvm对象里传递参数 一般参数类型 是interface int等 据说参数必须是静态类型 这个可以在uvm config db相关代码里确认 后来知道 config db还能设置sequence和seque

随机推荐

  • Python作图——numpy库和matplotlib库

    一 numpy库 1 1概述 numpy是一个存储和处理多维数组 矩阵等的库 提供多种关于数组运算的数学函数 可供直接调用 1 1 1数据类型 numpy的数据类型包括整型 浮点型 复数型 布尔型等 在IDLE查询numpy支持的数据类型
  • MATLAB .dat读、存及简单处理

    文章目录 0 前言 1 思路 2 MATLAB 3 结语 0 前言 近期接触到二进制文件 dat 写一个简单的教程 假设文件内容为连续的通信数据 含有不符合的数据 对其进行简单剔除 1 思路 假设输入 dat文件共有3个整帧数据 每帧长度5
  • 机器学习(归一化、去中心化、标准化)

    为什么要进行数据的预处理 这需要分两种情况说明 1 数据数值很大 2 数据数值很小 1 首先 对于一个数值非常之大的特征 T 若其数值非常之大 区间也非常之大 例如区间范围为 10 10 10 20 以线性拟合函数举例 显然我们在进行机器学
  • Android:JNI调用C++自定义类的详细方法

    一般情况下 我们都是用 JNI 调用 C 的某个方法的代码 包括直接使用 android studio 生成的代码也是如此 但有时我们需要新建 或者得到的是 C 的一个自定义类 在调用时就不能像调用 C 方法那样了 查阅了一部分其他人的博客
  • 学习笔记五:电路设计需要注意的那些事

    注意One part 电路中电源VBAT须要先经过滤波电容再到芯片的VBAT引脚 芯片时钟端走线尽量不要靠近电源走线 避免对电源走线产生纹波的干扰 需要隔离地处理 天线走50欧姆的阻抗设计 走线两侧距离30mil处添加过孔 最好用第三层GN
  • Adobe MAX 2020:最新版Photoshop提供AI智能神经滤镜及天空替换等功能

    数据猿年度重磅活动预告 2020年度金猿策划活动 金猿榜单发布 金猿奖杯颁发 即将推出 尽情咨询期待 大数据产业创新服务媒体 聚焦数据 改变商业 数据报道 受新冠疫情影响 今年的Adobe MAX 2020 于北京时间10月21日改为了线上
  • 一个优秀的程序员真的能顶10个普通的程序员吗?

    一个优秀的程序员真的能顶10个普通的程序员吗 一 前言 优秀的程序员 比糟糕的程序员的工作产出高100倍 二 糟糕程序员会有哪些表现 1 无法对代码进行推理 2 没有补救措施 3 代码难以改动 三 优秀程序员是怎么做的呢 1 先进行实验是他
  • Java+SSM+Vue 毕业设计 游戏攻略资讯补丁售卖商城(含源码+论文)

    文章目录 1 项目简介 2 实现效果 2 1 界面展示 3 设计方案 3 1 概述 3 2 系统流程 3 3 系统结构设计 4 项目获取 1 项目简介 Hi 各位同学好呀 这里是M学姐 今天向大家分享一个今年 2022 最新完成的毕业设计项
  • Win32学习笔记(16)消息类型

    1 消息的产生与处理流程 如上图 我们在创建窗口程序中详细介绍了这张图 比如 我们点击某个窗口的时候就会产生消息 操作系统会先判断这个消息是点在了哪个窗口 找到窗口后 会根据窗口对象中的一个成员找到窗口是属于哪个线程的 找到后就会把他封装好
  • 从零开始搭建一个 React 项目 -- 配置篇(一)

    从零开始搭建一个 React 项目 配置篇 一 参考资源 从零搭建完整的React项目模板 Webpack React hooks Mobx Antd 1 项目初始化及常用以来安装配置 1 创建名为 react admin demo 的目录
  • python虚拟环境配置、Python代码打包成exe可执行文件

    背景 因工作需要 要打包一些脚本使其成为exe文件 方便未安装python环境的电脑运行脚本 但是直接使用默认环境的话 会有很多的包 但是其实这个脚本根本用不到 导致生成的exe文件很大或者直接打包失败 所以创建一个虚拟环境 只安装该代码需
  • socket套接字,粘包问题

    目录 scoket套接字 socket工作流程 TCP服务端 TCP客户端 基于TCP 的SOCKET服务端与客户端 基础版本 客户端 加入连接循环 加入通信循环 支持并发的TCP服务端 常见问题 半连接池 粘包问题 TCP协议的特点 解决
  • OpenAI开源!!Whisper语音识别实战!!【环境配置+代码实现】

    目录 环境配置 代码实现 实现 mp4转换为 wav文件 识别后进行关键词匹配并输出关键词出现的次数 完整代码实现请私信 环境配置 安装 ffmpeg 打开网址 https github com BtbN FFmpeg Builds rel
  • vue使用svg自定义icon图标

    1 安装 svg sprite loader svg sprite loader 是用于创建SVG精灵图的Webpack加载程序 通过该插件可以将导入的SVG文件自动生成为symbol标签并插入进html中 安装语句如下 npm insta
  • 关于Spark报错不能连接到Server的解决办法(Failed to connect to master master_hostname:7077)

    问题产生 Spark集群 即可以基于Mesos或YARN来部署 也可以用自带的集群管理器 部署于standalone模式下 笔者在部署standalone模式时 首先 通过如下命令 启动了Master sbin start master s
  • 为什么会有ETH和ETC?the DAO攻击事件2周年祭

    前两天 跟一位HiBlock区块链社区的用户私聊 他问我一个问题 同样是智能合约 为什么会有以太坊和以太经典 又为什么会有相应的ETH和ETC 两者价格还相差如此之大 转给他一篇the DAO事件的文章后突然发现 the DAO攻击事件已经
  • Spring AOP 应用场景之--日志收集(含代码)

    在学习了 Spring AOP 知识之后 只了解了其一些基本的概念 比如它是为了解决 OOP 的弊端 使代码更易于维护 使用了动态代理 Spring 中有两种动态代理实现方式 一种是 JDK 一种是Cglib 使用场景有权限 缓存 日志 b
  • linux命令大全 which

    参考 Linux命令大全 程序员工具箱 1 命令 which 从 PATH 路径下查找某个命令的全路径 2 使用样例 查看 java 命令的全路径 which java 3 使用方法 which programnam
  • 基于JdbcTemplate的一种通用数据库操作帮助工具

    工具介绍 基于JdbcTemplate的通用操作数据库的帮助工具 借助阿里巴巴的开源工具fastjson实现实体的转json字符串以及params的解析 用法 1 建立与数据库表相对应的entity对象 package cn flyzy20
  • APB协议UVM验证环境的搭建

    APB协议UVM验证环境的搭建 一 编译文件 只需编译这两个文件即可 apb pkg sv 里面包含了 apb svh 即编译apb pkg sv这个文件的同时 也会编译所需要的所有的头文件 ifndef APB PKG SV define