AMBA AHB is a widely used bus protocol by the company ARM Holdings, typically used to transfer data between different masters and slaves. This is a pipelined protocol with a much higher performance in terms of latency and bandwidth compared to APB.

AHB Lite is a smaller version of the AHB with a smaller feature set, but has support for burst transactions, slave wait states, and protection control signals.

AHB Sequence Item

The sequence item has all relevant signals for the AHB protocol that will decide the kind of transfer a master initiates. The first step is to define enum data types so that code is easily understood. Most of the AHB signals can be randomized to have certain valid combinations of signals and sent to an AHB driver to be driven over to the DUT.


typedef enum bit [1:0] { IDLE, BUSY, NONSEQ, SEQ } e_htrans;
typedef enum bit [2:0] { BYTE, HALF_WORD, WORD, WORD_2, WORD_4, WORD_8, WORD_16, WORD_32} e_hsize;
typedef enum bit [2:0] { SINGLE, INCR, WRAP4, INCR4, WRAP8, INCR8, WRAP16, INCR16 } e_hburst;
typedef enum bit [3:0] { OPCODE_FETCH, DATA_ACCESS, USER_ACCESS, PRIVELEGED, NON_BUFFERABLE, BUFFERABLE, NON_CACHEABLE, CACHEABLE} e_hprot;
typedef enum bit 	   { OKAY, ERROR } e_hresp;
typedef enum bit 	   { READ, WRITE } e_hwrite;

class ahb_pkt extends uvm_sequence_item;  
  function new(string name = "ahb_pkt");
    super.new(name);
  endfunction
  
  rand  bit [15:0] 	m_haddr;
  rand  e_hburst 	m_hburst;
  rand	bit 		m_hmastlock;
  rand 	e_hprot 	m_hprot;
  rand 	e_hsize 	m_hsize;
  
  rand 	e_htrans 	m_htrans;
  rand 	bit [31:0] 	m_hwdata;
  rand 	e_hwrite	m_hwrite;
  
  		bit [31:0] 	m_hrdata;
  		e_hresp 	m_hresp;
  
  `uvm_object_utils_begin(ahb_pkt)
  	`uvm_field_int 	(m_haddr,  				UVM_PRINT)
  	`uvm_field_enum (e_hburst, m_hburst, 	UVM_PRINT)
  	`uvm_field_int 	(m_hmastlock, 			UVM_PRINT)
  	`uvm_field_enum (e_hprot, m_hprot, 		UVM_PRINT)
  	`uvm_field_enum (e_hsize, m_hsize, 		UVM_PRINT)
  	`uvm_field_enum (e_htrans, m_htrans, 	UVM_PRINT)
  	`uvm_field_int 	(m_hwdata, 				UVM_PRINT)
  	`uvm_field_enum (e_hwrite, m_hwrite, 	UVM_PRINT)
  	`uvm_field_int 	(m_hrdata, 				UVM_PRINT)
  	`uvm_field_enum (e_hresp, m_hresp, 		UVM_PRINT)
  `uvm_object_utils_end
  
  constraint c_default { m_htrans == NONSEQ;
                         m_hsize == WORD;
                         m_hburst == SINGLE; }
    
  virtual function string convert2string();
    return $sformatf("addr=0x%0h write=0x%0h wdata=0x%0h rdata=0x%0h",
                     m_haddr, m_hwrite, m_hwdata, m_hrdata);
  endfunction
  
endclass

AHB Configuration Object

The configuration object is not mandatory but always recommended to store configuration values for the agent, hold virtual interface handles and any other parameters to make the agent more flexible.


class ahb_cfg extends uvm_object;
  `uvm_object_utils(ahb_cfg)
  function new(string name = "ahb_cfg");
    super.new(name);
  endfunction
  
  virtual ahb_if 	m_ahb_vif;
  bit 				m_vld_val_only;
endclass

AHB Driver

AHB is a pipelined protocol and requires a different strategy for the driver compared to APB. In APB, item_done method is called after data phase has finished and marks the end of current transfer. In AHB, address phase of the next transfer is in progress along with the data phase of the previous transfer. Hence, there is a need of two separate threads to process them independently. The two threads should not drive either address or data phase together, and instead should go in a ping-pong fashion and hence requires a semaphore.

The first thread takes control of the bus by requesting to get a key from the semaphore, and drives address and control signals onto the bus. It keeps the key as long as the address phase is active and releases it as soon as the address phase is over. This allows the other thread with the second transfer that is running in parallel to get a key and drive its address phase. The first thread drives data phase in the meanwhile, and waits to get a key to drive the next transfer.


class ahb_driver extends uvm_driver #(ahb_pkt);
  `uvm_component_utils(ahb_driver)
  function new(string name="ahb_driver", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  virtual ahb_if 	m_vif;
  ahb_cfg 			m_cfg;
  semaphore 		m_sema4;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
	m_vif = m_cfg.m_ahb_vif;
    m_sema4 = new(1);
  endfunction
  
  virtual task reset_phase(uvm_phase phase);
    super.reset_phase(phase);
    phase.raise_objection(this);
    m_vif.haddr 	= 0;
    m_vif.hburst 	= 0;
    m_vif.hmastlock = 0;
    m_vif.hprot 	= 0;
    m_vif.hsize 	= 0;
    m_vif.htrans 	= 0;
    m_vif.hwdata  	= 0;
    m_vif.hwrite 	= 0;
   	m_vif.hsel 		= 0;
    phase.drop_objection(this);
  endtask
  
  virtual task main_phase(uvm_phase phase);
    super.main_phase(phase);
    
    fork
      drive("T1");
      drive("T2");
    join
  endtask
  
  virtual task automatic drive(string tag);
    forever begin
    	// Address Phase
    	m_sema4.get(1);
      	seq_item_port.get_next_item(req);
      `uvm_info(get_type_name(), $sformatf("[drive] %s addr_phase for pkt addr=0x%0h", tag, req.m_haddr), UVM_MEDIUM)
    	while (!m_vif.cb.hreadyout) begin
      		@(m_vif.cb);
    	end
    	m_vif.haddr 	= req.m_haddr;
    	m_vif.hburst 	= req.m_hburst;
    	m_vif.hmastlock = req.m_hmastlock;
    	m_vif.hprot 	= req.m_hprot;
    	m_vif.hsize 	= req.m_hsize;
    	m_vif.htrans 	= req.m_htrans;
    	m_vif.hwrite 	= req.m_hwrite;
    	m_vif.hsel 		= 1;
    	do begin
      		@(m_vif.cb);
          	m_vif.htrans = IDLE;
    	end while (!m_vif.cb.hreadyout);
      	seq_item_port.item_done();
    	m_sema4.put(1); 
    
    	// Data Phase
      `uvm_info(get_type_name(), $sformatf("[drive] %s data_phase for pkt addr=0x%0h", tag, req.m_haddr), UVM_MEDIUM)     
    	if (req.m_hwrite)     
      		m_vif.hwdata = req.m_hwdata;
   		else
      		req.m_hrdata = m_vif.hrdata;
    end
  endtask
endclass	

AHB Monitor


	

AHB Agent


class ahb_agent extends uvm_agent;
  `uvm_component_utils(ahb_agent)
  function new(string name="ahb_agent", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  ahb_cfg 					m_cfg;
  ahb_driver 	 			m_drv;
  ahb_monitor 	 			m_mon;
  uvm_sequencer #(ahb_pkt) 	m_seqr;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    if (!uvm_config_db#(ahb_cfg)::get(this, "*", "m_ahb_cfg", m_cfg))
      `uvm_fatal(get_type_name(), $sformatf("[CFG] Did not find m_ahb_cfg in config_db"))
    
    if (get_is_active()) begin
      m_drv = ahb_driver::type_id::create("m_drv", this);
      m_seqr = uvm_sequencer#(ahb_pkt)::type_id::create("m_seqr", this);
    end
    
    m_mon = ahb_monitor::type_id::create("m_mon", this);
    
    // Configuration might be needed to build it
    m_drv.m_cfg = m_cfg;
    m_mon.m_cfg = m_cfg;
  endfunction
  
  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    
    if (get_is_active) begin
      m_drv.seq_item_port.connect(m_seqr.seq_item_export);
    end
  endfunction
endclass	

Testbench

Scoreboard


class base_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(base_scoreboard)
  function new(string name = "base_scoreboard", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  uvm_analysis_imp #(ahb_pkt, base_scoreboard) m_analysis_imp;
  ahb_pkt 	m_ahb_pkt_q[$];
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_analysis_imp = new("m_analysis_imp", this);
  endfunction
  
  virtual function write(ahb_pkt l_ahb_pkt);
    `uvm_info(get_type_name(), $sformatf("[write] Got packet %s", l_ahb_pkt.convert2string()), UVM_MEDIUM)
    m_ahb_pkt_q.push_back(l_ahb_pkt);
  endfunction
endclass	

Environment


class base_env extends uvm_env;
  `uvm_component_utils(base_env)
  function new(string name = "base_env", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  ahb_agent   		m_ahb_agent;
  base_scoreboard 	m_base_scbd;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_ahb_agent = ahb_agent::type_id::create("m_ahb_agent", this);
    m_base_scbd = base_scoreboard::type_id::create("m_base_scbd", this);
  endfunction
  
  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    m_ahb_agent.m_mon.m_analysis_port.connect(m_base_scbd.m_analysis_imp);
  endfunction
  
endclass	

Test


class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  function new(string name = "base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
    virtual ahb_if	m_ahb_vif;
  
  	ahb_cfg 	 	m_ahb_cfg;
  	ahb_seq 		m_ahb_seq;
	base_env 		m_env;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
   
    // Get virtual interface from tb_top
    if (!uvm_config_db #(virtual ahb_if)::get(this, "", "m_ahb_if", m_ahb_vif))
        `uvm_fatal(get_type_name(), $sformatf("[CFG] Did not find m_ahb_if in config_db"))
    
    // Set up ahb agent configuration, assign virtual interface handle and set in config_db
    m_ahb_cfg = ahb_cfg::type_id::create("m_ahb_cfg");
    m_ahb_cfg.m_ahb_vif = m_ahb_vif;        
    uvm_config_db #(ahb_cfg)::set(this, "*ahb_agent*", "m_ahb_cfg", m_ahb_cfg);
        
    // Create base env, test sequence
    m_ahb_seq = ahb_seq::type_id::create("m_ahb_seq");
    m_env = base_env::type_id::create("m_env", this);
  endfunction
  
  virtual task reset_phase(uvm_phase phase);
    super.reset_phase(phase);
    phase.raise_objection(this);
	m_ahb_vif.hresetn = 0;    
    repeat(5) @ (m_ahb_vif.cb);
	m_ahb_vif.hresetn = 1;
    repeat(5) @ (m_ahb_vif.cb);
    phase.drop_objection(this);
  endtask
  
  virtual task main_phase(uvm_phase phase);
    super.main_phase(phase);
    phase.raise_objection(this);
    m_ahb_seq.start(m_env.m_ahb_agent.m_seqr);
    phase.drop_objection(this);
  endtask
endclass	

Sequence


class ahb_seq extends uvm_sequence;
  `uvm_object_utils(ahb_seq)
  function new(string name = "ahb_seq");
    super.new(name);
  endfunction
  
  virtual task body();    
    `uvm_info(get_type_name(), $sformatf("Start sequence"), UVM_MEDIUM)
    repeat (10) begin
      	ahb_pkt m_ahb_pkt = ahb_pkt::type_id::create("m_ahb_pkt");
    	start_item(m_ahb_pkt);
      	m_ahb_pkt.randomize() with { m_hwrite == WRITE; };
      	finish_item(m_ahb_pkt);
      	m_ahb_pkt.print();
    end
    #100;
    `uvm_info(get_type_name(), $sformatf("End sequence"), UVM_MEDIUM)
  endtask  
endclass	

Interface


interface ahb_if (input hclk);
  logic 		hresetn;
  logic [31:0] 	haddr;
  logic [2:0] 	hburst;
  logic 		hmastlock;
  logic [3:0] 	hprot;
  logic [2:0] 	hsize;
  logic [1:0] 	htrans;
  logic [31:0] 	hwdata;
  logic 		hwrite;
  
  logic [31:0]	hrdata;
  logic 		hreadyout;
  logic  		hresp;
  
  logic 		hsel;
  
  clocking cb @ (posedge hclk);
    input hreadyout, hrdata, hresp;
    output haddr, hburst, hmastlock, hprot, hsize, htrans, hwdata, hwrite, hsel;
  endclocking
endinterface

Testbench Top


module tb;
  bit clk;
  
  always #10 clk = ~clk;
  
  ahb_if 	m_ahb_if ( .hclk (clk));
  
  assign m_ahb_if.hresp = 0;
  
  initial begin
    m_ahb_if.hreadyout <= 0;
    forever begin
      repeat ($urandom_range(2,6)) @ (posedge clk);
      m_ahb_if.hreadyout <= ~m_ahb_if.hreadyout;
    end
  end
  
  initial begin
    m_ahb_if.hrdata <= 0;
    forever begin
      repeat ($urandom_range(1,3)) @ (posedge clk);
      m_ahb_if.hrdata <= $random;
    end
  end
    
  initial begin
    uvm_config_db #(virtual ahb_if)::set(null, "uvm_test_top", "m_ahb_if", m_ahb_if);
    run_test("base_test");
  end
endmodule	
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5