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