AXI4 Full Protocol Verification IP

AXI4 Full VIP

Professional-grade AMBA AXI4 verification component with master/slave agents, protocol checkers, and built-in scoreboard

Stable v1.0 UVM 1.1d/1.2 IEEE 1800

📋 Overview

The KVIPS AXI4 VIP provides a complete, production-ready verification environment for AMBA AXI4 (full) protocol. It includes:

🎯 Master Agent

  • Configurable address/data/ID/user widths
  • All burst types: INCR, FIXED, WRAP
  • Exclusive access support
  • Pipelined outstanding transactions
  • Configurable delays and backpressure

🔄 Slave Agent

  • Flexible memory model
  • Error injection (SLVERR/DECERR)
  • Configurable response latencies
  • Exclusive reservation tracking
  • Address range mapping

✅ Protocol Checkers

  • Handshake protocol validation
  • Burst legality checks
  • 4KB boundary enforcement
  • Signal stability assertions
  • Exclusive access rules

📊 Verification Features

  • Write-derived read scoreboard
  • Transaction recording (UVM TR)
  • Performance statistics
  • Configurable trace levels
  • Coverage collection hooks

🚀 Quick Start

Minimal Example

// 1. Instantiate interface
axi4_if #(.ADDR_W(32), .DATA_W(64), .ID_W(4)) axi_if(.aclk(clk), .areset_n(rst_n));

// 2. Configure and create environment
class my_test extends uvm_test;
  axi4_env#(32, 64, 4, 1) env;
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    // Create config
    axi4_env_cfg#(32, 64, 4, 1) cfg = axi4_env_cfg#(32, 64, 4, 1)::type_id::create("cfg");
    
    // Add master agent
    begin
      axi4_agent_cfg#(32, 64, 4, 1) mst_cfg = 
        axi4_agent_cfg#(32, 64, 4, 1)::type_id::create("mst_cfg");
      mst_cfg.is_master = 1;
      mst_cfg.is_active = UVM_ACTIVE;
      cfg.agent_cfgs.push_back(mst_cfg);
    end
    
    // Add slave agent with memory
    begin
      axi4_agent_cfg#(32, 64, 4, 1) slv_cfg = 
        axi4_agent_cfg#(32, 64, 4, 1)::type_id::create("slv_cfg");
      slv_cfg.is_master = 0;
      slv_cfg.is_active = UVM_ACTIVE;
      slv_cfg.slave_mem_enable = 1;
      cfg.agent_cfgs.push_back(slv_cfg);
    end
    
    uvm_config_db#(axi4_env_cfg#(32, 64, 4, 1))::set(this, "env", "cfg", cfg);
    env = axi4_env#(32, 64, 4, 1)::type_id::create("env", this);
  endfunction
endclass

// 3. Start sequences
axi4_basic_write_seq#(32, 64, 4, 1) seq = new("seq");
seq.start(env.get_master_sequencer(0));

Run Example Simulation

# Questa
cd kvips/axi4/examples/uvm_back2back/sim
./run_questa.sh +UVM_TESTNAME=axi4_b2b_test

# VCS
./run_vcs.sh +UVM_TESTNAME=axi4_b2b_test

# Xcelium
./run_xcelium.sh +UVM_TESTNAME=axi4_b2b_test

# Verilator
./run_verilator.sh +UVM_TESTNAME=axi4_b2b_test

📝 Note: SVA assertions are skipped under Verilator.


📐 Architecture

Component Hierarchy

axi4_env
├── axi4_agent (master)
│   ├── axi4_sequencer
│   ├── axi4_master_driver
│   └── axi4_monitor
├── axi4_agent (slave)
│   ├── axi4_sequencer
│   ├── axi4_slave_driver
│   └── axi4_monitor
├── axi4_scoreboard
└── axi4_txn_logger (optional)

Data Flow

Sequence
   ↓
Sequencer → Driver → Interface → DUT/Slave
                         ↓
                     Monitor → Analysis Port → Scoreboard
                                            → Txn Logger
                                            → Coverage Collector

⚙️ Configuration

Agent Configuration (axi4_agent_cfg)

class axi4_agent_cfg #(
  int ADDR_W = 32,
  int DATA_W = 64,
  int ID_W   = 4,
  int USER_W = 1
) extends uvm_object;

  // Role Configuration
  bit                is_master = 1;        // Master or slave agent
  uvm_active_passive_enum is_active = UVM_ACTIVE;
  virtual axi4_if    vif;                  // Interface handle
  
  // Master Configuration
  bit                master_pipelined = 0; // Enable outstanding transactions
  int unsigned       max_outstanding_writes = 4;
  int unsigned       max_outstanding_reads = 4;
  int unsigned       master_aw_delay_min = 0;
  int unsigned       master_aw_delay_max = 5;
  int unsigned       master_ar_delay_min = 0;
  int unsigned       master_ar_delay_max = 5;
  int unsigned       master_w_beat_gap_min = 0;
  int unsigned       master_w_beat_gap_max = 3;
  bit                master_rready_random = 1;
  int unsigned       inter_txn_gap_min = 0;
  int unsigned       inter_txn_gap_max = 10;
  
  // Slave Configuration
  bit                slave_mem_enable = 0; // Enable internal memory model
  int unsigned       slave_read_latency_min = 1;
  int unsigned       slave_read_latency_max = 10;
  int unsigned       slave_write_latency_min = 1;
  int unsigned       slave_write_latency_max = 10;
  bit                slave_err_enable = 0; // Enable error injection
  logic [ADDR_W-1:0] slave_err_start = '0;
  logic [ADDR_W-1:0] slave_err_end = '0;
  axi4_resp_e        slave_err_resp = AXI4_RESP_SLVERR;
  bit                slave_err_on_write = 1;
  bit                slave_err_on_read = 1;
  bit                slave_exclusive_enable = 1; // Support exclusive access
  int unsigned       slave_exclusive_max_bytes = 128;
  
  // Debug/Observability
  bit                trace_enable = 0;     // Verbose transaction prints
  bit                stats_enable = 0;     // Collect performance stats
  bit                monitor_enable = 1;   // Enable monitor (disable for shared vif)
  
endclass

Common Configuration Patterns

High-Performance Mode

mst_cfg.master_pipelined = 1;
mst_cfg.max_outstanding_writes = 16;
mst_cfg.max_outstanding_reads = 16;
mst_cfg.master_aw_delay_min = 0;
mst_cfg.master_aw_delay_max = 0;
mst_cfg.master_w_beat_gap_min = 0;
mst_cfg.master_w_beat_gap_max = 0;
mst_cfg.inter_txn_gap_min = 0;
mst_cfg.inter_txn_gap_max = 0;

Stress Test Mode

mst_cfg.master_aw_delay_min = 0;
mst_cfg.master_aw_delay_max = 20;
mst_cfg.master_w_beat_gap_min = 0;
mst_cfg.master_w_beat_gap_max = 10;
slv_cfg.slave_read_latency_min = 5;
slv_cfg.slave_read_latency_max = 50;

Debug Mode

mst_cfg.trace_enable = 1;
mst_cfg.stats_enable = 1;
// Plus: +UVM_VERBOSITY=UVM_HIGH +VIP_TRACE +VIP_STATS

🎮 Sequence Library

Basic Sequences

// Single write
axi4_basic_write_seq #(32, 64, 4, 1) wr_seq;
wr_seq = new("wr_seq");
wr_seq.addr = 32'h1000;
wr_seq.data = 64'hDEADBEEF_CAFEBABE;
wr_seq.start(sequencer);

// Single read
axi4_basic_read_seq #(32, 64, 4, 1) rd_seq;
rd_seq = new("rd_seq");
rd_seq.addr = 32'h1000;
rd_seq.start(sequencer);

// Burst write (16 beats)
axi4_burst_write_seq #(32, 64, 4, 1) burst_seq;
burst_seq = new("burst_seq");
burst_seq.addr = 32'h2000;
burst_seq.len = 15;  // 16 beats
burst_seq.burst_type = AXI4_BURST_INCR;
burst_seq.start(sequencer);

Advanced Sequences

// Concurrent read/write
fork
  begin
    axi4_random_write_seq #(32, 64, 4, 1) wr_seq;
    wr_seq = new("wr_seq");
    wr_seq.num_txns = 100;
    wr_seq.start(sequencer);
  end
  begin
    axi4_random_read_seq #(32, 64, 4, 1) rd_seq;
    rd_seq = new("rd_seq");
    rd_seq.num_txns = 100;
    rd_seq.start(sequencer);
  end
join

// Exclusive access sequence
axi4_exclusive_seq #(32, 64, 4, 1) excl_seq;
excl_seq = new("excl_seq");
excl_seq.addr = 32'h4000;
excl_seq.start(sequencer);
// Internally: exclusive read -> exclusive write -> verify EXOKAY

// Error injection test
slv_cfg.slave_err_enable = 1;
slv_cfg.slave_err_start = 32'hF000;
slv_cfg.slave_err_end = 32'hFFFF;
slv_cfg.slave_err_resp = AXI4_RESP_SLVERR;
// Now reads/writes to 0xF000-0xFFFF will return SLVERR

Custom Sequence Example

class my_custom_seq extends uvm_sequence #(axi4_item#(32, 64, 4, 1));
  `uvm_object_utils(my_custom_seq)
  
  rand int num_bursts;
  constraint c_num { num_bursts inside {[10:50]}; }
  
  task body();
    for (int i = 0; i < num_bursts; i++) begin
      `uvm_do_with(req, {
        is_write == (i % 2 == 0);  // Alternate write/read
        addr[31:12] == i[19:0];    // Different 4KB pages
        len inside {[0:15]};        // Random burst length
        size == 3;                  // 8 bytes per beat
        burst == AXI4_BURST_INCR;
      })
    end
  endtask
endclass

✅ Assertions & Protocol Checking

Built-in Assertions

The VIP includes comprehensive SVA checkers:

Handshake Protocol

// AWVALID must remain high until AWREADY
property p_aw_valid_stable;
  @(posedge aclk) disable iff (!areset_n)
  (awvalid && !awready) |=> awvalid;
endproperty

// AWADDR must remain stable when AWVALID is high
property p_aw_stable;
  @(posedge aclk) disable iff (!areset_n)
  (awvalid && !awready) |=> $stable(awaddr);
endproperty

Burst Legality

// INCR bursts: len 0-255
// FIXED/WRAP bursts: len 0-15
// WRAP bursts: len must be 1,3,7,15 (2,4,8,16 beats)
property p_burst_len_legal;
  @(posedge aclk) disable iff (!areset_n)
  (awvalid && awready) |-> 
    (awburst == 2'b01) ? (awlen <= 8'd255) : (awlen <= 8'd15);
endproperty

4KB Boundary

// Burst must not cross 4KB boundary
property p_no_4kb_cross;
  @(posedge aclk) disable iff (!areset_n)
  (awvalid && awready) |-> 
    ((awaddr[11:0] + (1 << awsize) * (awlen + 1)) <= 12'h1000);
endproperty

Exclusive Access

// Exclusive write must have prior exclusive read with same ID
property p_exclusive_write_after_read;
  logic [ID_W-1:0] excl_id;
  @(posedge aclk) disable iff (!areset_n)
  (arvalid && arready && arlock, excl_id = arid) |->
    ##[1:$] (awvalid && awready && awlock && (awid == excl_id));
endproperty

Runtime Control

# Disable all assertions
+KVIPS_AXI4_ASSERT_OFF

# Enable X/Z checking
+KVIPS_AXI4_ASSERT_KNOWN

# Disable specific assertion groups
+KVIPS_AXI4_ASSERT_BURST_OFF      # Disable burst legality checks
+KVIPS_AXI4_ASSERT_EXCL_OFF       # Disable exclusive access checks

📊 Scoreboard & Data Checking

Write-Derived Read Checking

The built-in scoreboard implements memory-model checking:

  1. On Write: Store data to internal memory
    foreach (wr_txn.data[i]) begin
      for (int b = 0; b < STRB_W; b++) begin
        if (wr_txn.strb[i][b]) begin
          memory[addr + i*STRB_W + b] = wr_txn.data[i][b*8 +: 8];
        end
      end
    end
    
  2. On Read: Compare against stored data
    foreach (rd_txn.data[i]) begin
      for (int b = 0; b < STRB_W; b++) begin
        exp_byte = memory[addr + i*STRB_W + b];
        obs_byte = rd_txn.data[i][b*8 +: 8];
        if (exp_byte !== obs_byte) begin
          `uvm_error("AXI4_SB", $sformatf(
            "Data mismatch @ 0x%0h: exp=0x%0h obs=0x%0h",
            addr + i*STRB_W + b, exp_byte, obs_byte))
        end
      end
    end
    

Configuration

// Disable scoreboard
// Plusarg: +KVIPS_AXI4_SB_OFF

// Warn on reads of unwritten addresses
// Plusarg: +KVIPS_AXI4_SB_WARN_UNINIT

📈 Performance Statistics

Enable with cfg.stats_enable = 1 or +VIP_STATS:

=== AXI4 Monitor Statistics ===
Observation period: 1000 ns

Write Statistics:
  Total write bursts:     50
  Total write beats:      800
  Write cycles:           1200
  Average latency:        5.2 cycles
  Peak bandwidth:         85%
  Max outstanding:        8

Read Statistics:
  Total read bursts:      50
  Total read beats:       800
  Read cycles:            1500
  Average latency:        7.8 cycles
  Peak bandwidth:         70%
  Max outstanding:        6

Stalls:
  AW stalls:              120 cycles (10%)
  W stalls:               200 cycles (16.7%)
  B backpressure:         50 cycles (4.2%)
  AR stalls:              150 cycles (10%)
  R backpressure:         300 cycles (20%)

🎓 Integration Examples

Integrate with Existing Testbench

See the Integration Guide for detailed steps.

Multiple Agents

class multi_master_test extends axi4_base_test;
  axi4_env#(32, 64, 4, 1) env;
  
  function void build_phase(uvm_phase phase);
    axi4_env_cfg#(32, 64, 4, 1) cfg = new("cfg");
    
    // Master 0
    begin
      axi4_agent_cfg#(32, 64, 4, 1) mst0_cfg = new("mst0_cfg");
      mst0_cfg.is_master = 1;
      // Set vif for master 0
      cfg.agent_cfgs.push_back(mst0_cfg);
    end
    
    // Master 1
    begin
      axi4_agent_cfg#(32, 64, 4, 1) mst1_cfg = new("mst1_cfg");
      mst1_cfg.is_master = 1;
      // Set vif for master 1
      cfg.agent_cfgs.push_back(mst1_cfg);
    end
    
    // Shared slave
    begin
      axi4_agent_cfg#(32, 64, 4, 1) slv_cfg = new("slv_cfg");
      slv_cfg.is_master = 0;
      slv_cfg.slave_mem_enable = 1;
      cfg.agent_cfgs.push_back(slv_cfg);
    end
    
    // ...
  endfunction
  
  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    
    // Run sequences on both masters concurrently
    fork
      begin
        my_seq seq = new("seq0");
        seq.start(env.get_master_sequencer(0));
      end
      begin
        my_seq seq = new("seq1");
        seq.start(env.get_master_sequencer(1));
      end
    join
    
    phase.drop_objection(this);
  endtask
endclass

📚 Additional Resources


🐛 Known Limitations


📜 License & Support

Ready to use AXI4 VIP?

Download KVIPS and start verifying your AXI4 designs today!

Get Started