Part 1 showed how to create a simple AHB-L agent driver that is capable of driving a single non-sequential access.
Now we'll see how to extend this model to create burst transactions with different sizes and types.
Type definitions
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;
Address Data Object
Since AHB transactions require the master to send an address for all beats in a burst, we can reduce burden on the AHB driver by calculating addresses beforehand when data is randomized. As long as the start address is known along with the size of transfer,t this can be easily calculated. These address data pairs can be stored using an intermediate class as shown below.
class ahb_addr_data extends uvm_object;
rand bit [31:0] m_addr;
rand bit [31:0] m_data;
`uvm_object_utils_begin(ahb_addr_data)
`uvm_field_int (m_addr, UVM_PRINT)
`uvm_field_int (m_data, UVM_PRINT)
`uvm_object_utils_end
function new(string name="ahb_addr_data");
super.new(name);
endfunction
Transaction Object
There are a couple of ways to represent bursty transactions in AHB. Since each beat of the burst requires the master to send address along with its data, the master should calculate the next address in each beat of a burst. This calculation can be placed either in the driver or in the transaction packet itself. The driver becomes slightly complicated if it has to calculate the next transaction address before each beat is sent out, and makes it easier to code transaction object. In this case, the start address of the burst can be stored in the transaction object along with an array of only data.
In the second approach shown below, next transaction address is precalculated within the transaction object itself thereby making driver code easier. This also allows an address data pair to be stored and provides a better and complete picture of where each data in the beat should go. Since address calculation depends on a variety of other parameters like hburst and the length, it is best tackled in post_randomize method.
class ahb_pkt extends uvm_sequence_item;
function new(string name = "ahb_pkt");
super.new(name);
endfunction
rand byte unsigned m_num_txfr; // Length of burst for INCR type
rand bit[31:0] m_start_addr; // Start address for burst
rand e_hburst m_hburst; // Burst type
rand bit m_hmastlock;
rand e_hprot m_hprot;
rand e_hsize m_hsize;
rand e_htrans m_htrans;
rand e_hwrite m_hwrite;
rand ahb_addr_data m_addr_data_q[$];
e_hresp m_hresp;
`uvm_object_utils_begin(ahb_pkt)
`uvm_field_int (m_start_addr, UVM_PRINT)
`uvm_field_int (m_num_txfr, UVM_PRINT|UVM_DEC)
`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_enum (e_hwrite, m_hwrite, UVM_PRINT)
`uvm_field_enum (e_hresp, m_hresp, UVM_PRINT)
`uvm_field_queue_object (m_addr_data_q, UVM_PRINT)
`uvm_object_utils_end
constraint c_default { !(m_htrans inside {SEQ, IDLE, BUSY});
m_start_addr % 4 == 0;
soft m_hsize inside {[1:2]} ;
}
constraint c_addr_data_q {
if (m_hburst == SINGLE) { m_addr_data_q.size() == 1; m_num_txfr == 1; }
if (m_hburst == INCR) { m_addr_data_q.size() == m_num_txfr; }
if (m_hburst == INCR4 || m_hburst == WRAP4) { m_addr_data_q.size() == 4; m_num_txfr == 4; }
if (m_hburst == INCR8 || m_hburst == WRAP8) { m_addr_data_q.size() == 8; m_num_txfr == 8; }
if (m_hburst == INCR16 || m_hburst == WRAP16) { m_addr_data_q.size() == 16; m_num_txfr == 16; }
}
function void post_randomize();
int l_next_addr = m_start_addr;
foreach (m_addr_data_q[i]) begin
m_addr_data_q[i] = ahb_addr_data::type_id::create($sformatf("m_addr_data_q[%0d]", i));
if (m_hburst inside {INCR, INCR4, INCR8, INCR16}) begin
m_addr_data_q[i].randomize() with { m_addr == m_start_addr + i * (2 ** m_hsize); };
end
if (m_hburst inside {WRAP4, WRAP8, WRAP16}) begin
byte l_hburst;
byte l_wrap_bd;
case (m_hburst)
WRAP4 : l_hburst = 4;
WRAP8 : l_hburst = 8;
WRAP16 : l_hburst = 16;
endcase
l_wrap_bd = l_hburst * 2**m_hsize;
`uvm_info(get_type_name(), $sformatf("[post_randomize] %s start=0x%0h next_addr=0x%0h wrap_bd=0x%0h",
m_hburst.name(), m_start_addr, l_next_addr, l_wrap_bd), UVM_HIGH)
if (((l_next_addr % l_wrap_bd) == 0) && (l_next_addr != m_start_addr))
l_next_addr = l_next_addr - l_wrap_bd;
`uvm_info(get_type_name(), $sformatf("[post_randomize] %s start=0x%0h next_addr=0x%0h wrap_bd=0x%0h",
m_hburst.name(), m_start_addr, l_next_addr, l_wrap_bd), UVM_HIGH)
m_addr_data_q[i].randomize() with { m_addr == l_next_addr;
};
l_next_addr = m_addr_data_q[i].m_addr + (2 ** m_hsize);
`uvm_info(get_type_name(), $sformatf("[post_randomize] %s start=0x%0h next_addr=0x%0h wrap_bd=0x%0h addr=0x%0h",
m_hburst.name(), m_start_addr, l_next_addr, l_wrap_bd, m_addr_data_q[i].m_addr), UVM_MEDIUM)
end
end
endfunction
virtual function string convert2string();
foreach (m_addr_data_q[i]) begin
return $sformatf("addr=0x%0h write=0x%0h data=0x%0h",
m_addr_data_q[i].m_addr, m_hwrite, m_addr_data_q[i].m_data);
end
endfunction
endclass
AHB Driver
Each forked thread gets a transaction object that already has all address data pairs in a burst. This makes the driver code much simpler by allowing it to iterate through each address data pair and drive them across the interface. The next thread can only start after the burst is completed by the first thread.
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;
semaphore m_sub_sema4;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_vif = m_cfg.m_ahb_vif;
m_sema4 = new(1);
m_sub_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_txfr("T1");
drive_txfr("T2");
join
endtask
virtual task automatic drive_txfr(string tag);
forever begin
m_sema4.get(1);
seq_item_port.get_next_item(req);
drive_pkt(tag, req);
m_sema4.put(1);
seq_item_port.item_done();
end
endtask
virtual task automatic drive_pkt(string tag, REQ l_req);
foreach (l_req.m_addr_data_q[i]) begin
m_sub_sema4.get(1);
`uvm_info(get_type_name(), $sformatf("[drive] %s Iter%0d addr_phase for pkt addr=0x%0h", tag, i, l_req.m_addr_data_q[i].m_addr), UVM_HIGH)
while (!m_vif.cb.hreadyout) begin
@(m_vif.cb);
end
m_vif.haddr = l_req.m_addr_data_q[i].m_addr;
m_vif.hburst = l_req.m_hburst;
m_vif.hmastlock = l_req.m_hmastlock;
m_vif.hprot = l_req.m_hprot;
m_vif.hsize = l_req.m_hsize;
m_vif.hwrite = req.m_hwrite;
m_vif.hsel = 1;
if (l_req.m_htrans == NONSEQ) begin
if ( i == 0) m_vif.htrans = NONSEQ;
else m_vif.htrans = SEQ;
end else begin
m_vif.htrans = l_req.m_htrans;
end
do begin
@(m_vif.cb);
end while (! m_vif.cb.hreadyout);
m_sub_sema4.put(1);
`uvm_info(get_type_name(), $sformatf("[drive] %s Iter%0d data_phase for pkt addr=0x%0h", tag, i, l_req.m_addr_data_q[i].m_addr), UVM_HIGH)
if (l_req.m_hwrite) m_vif.hwdata = l_req.m_addr_data_q[i].m_data;
else l_req.m_addr_data_q[i].m_data = m_vif.hrdata;
end
m_vif.htrans = IDLE;
endtask
endclass
AHB Sequence
The sequence starts and randomizes an AHB transaction item and waits until item_done is called by the driver.
class ahb_seq extends uvm_sequence;
`uvm_object_utils(ahb_seq)
function new(string name = "ahb_seq");
super.new(name);
endfunction
int m_loop = 3;
virtual task body();
`uvm_info(get_type_name(), $sformatf("Start sequence"), UVM_MEDIUM)
repeat (m_loop) begin
ahb_pkt m_ahb_pkt = ahb_pkt::type_id::create("m_ahb_pkt");
start_item(m_ahb_pkt);
m_ahb_pkt.randomize();
finish_item(m_ahb_pkt);
m_ahb_pkt.print();
end
#100;
`uvm_info(get_type_name(), $sformatf("End sequence"), UVM_MEDIUM)
endtask
endclass
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(313) @ 190: uvm_test_top.m_env.m_ahb_agent.m_seqr@@m_ahb_seq [ahb_seq] Start sequence
---------------------------------------------------------------------------------------------------
Name Type Size Value
---------------------------------------------------------------------------------------------------
m_ahb_pkt ahb_pkt - @1935
m_start_addr integral 32 'hf5cb87f8
m_num_txfr integral 8 'd5
m_hburst e_hburst 3 INCR
m_hmastlock integral 1 'h1
m_hprot e_hprot 4 NON_BUFFERABLE
m_hsize e_hsize 3 WORD
m_htrans e_htrans 2 NONSEQ
m_hwrite e_hwrite 1 READ
m_hresp e_hresp 1 OKAY
m_addr_data_q da(object) 5 -
[0] ahb_addr_data - @2897
m_addr integral 32 'hf5cb87f8
m_data integral 32 'h46df998d
[1] ahb_addr_data - @2893
m_addr integral 32 'hf5cb87fc
m_data integral 32 'h46df998d
[2] ahb_addr_data - @2896
m_addr integral 32 'hf5cb8800
m_data integral 32 'hb2c28465
[3] ahb_addr_data - @2898
m_addr integral 32 'hf5cb8804
m_data integral 32 'hb2c28465
[4] ahb_addr_data - @2900
m_addr integral 32 'hf5cb8808
m_data integral 32 'h6d7cd0d
begin_time time 64 190
end_time time 64 450
depth int 32 'd2
parent sequence (name) string 9 m_ahb_seq
parent sequence (full name) string 47 uvm_test_top.m_env.m_ahb_agent.m_seqr.m_ahb_seq
sequencer string 37 uvm_test_top.m_env.m_ahb_agent.m_seqr
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
Name Type Size Value
---------------------------------------------------------------------------------------------------
m_ahb_pkt ahb_pkt - @3027
m_start_addr integral 32 'hb08a79e0
m_num_txfr integral 8 'd23
m_hburst e_hburst 3 INCR
m_hmastlock integral 1 'h1
m_hprot e_hprot 4 BUFFERABLE
m_hsize e_hsize 3 HALF_WORD
m_htrans e_htrans 2 NONSEQ
m_hwrite e_hwrite 1 WRITE
m_hresp e_hresp 1 OKAY
m_addr_data_q da(object) 23 -
[0] ahb_addr_data - @2988
m_addr integral 32 'hb08a79e0
m_data integral 32 'h49b8b844
[1] ahb_addr_data - @3005
m_addr integral 32 'hb08a79e2
m_data integral 32 'h1932c039
[2] ahb_addr_data - @3001
m_addr integral 32 'hb08a79e4
m_data integral 32 'h2205044a
[3] ahb_addr_data - @3003
m_addr integral 32 'hb08a79e6
m_data integral 32 'h8bfe5d63
[4] ahb_addr_data - @3000
m_addr integral 32 'hb08a79e8
m_data integral 32 'h79eb02a2
... ... ... ...
[18] ahb_addr_data - @2971
m_addr integral 32 'hb08a7a04
m_data integral 32 'h2b0451e7
[19] ahb_addr_data - @2965
m_addr integral 32 'hb08a7a06
m_data integral 32 'h69125431
[20] ahb_addr_data - @2966
m_addr integral 32 'hb08a7a08
m_data integral 32 'hee064b74
[21] ahb_addr_data - @2963
m_addr integral 32 'hb08a7a0a
m_data integral 32 'h4584e1e7
[22] ahb_addr_data - @2958
m_addr integral 32 'hb08a7a0c
m_data integral 32 'hf5b354af
begin_time time 64 450
end_time time 64 1210
depth int 32 'd2
parent sequence (name) string 9 m_ahb_seq
parent sequence (full name) string 47 uvm_test_top.m_env.m_ahb_agent.m_seqr.m_ahb_seq
sequencer string 37 uvm_test_top.m_env.m_ahb_agent.m_seqr
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
Name Type Size Value
---------------------------------------------------------------------------------------------------
m_ahb_pkt ahb_pkt - @3115
m_start_addr integral 32 'he5debca0
m_num_txfr integral 8 'd8
m_hburst e_hburst 3 INCR
m_hmastlock integral 1 'h0
m_hprot e_hprot 4 NON_BUFFERABLE
m_hsize e_hsize 3 WORD
m_htrans e_htrans 2 NONSEQ
m_hwrite e_hwrite 1 READ
m_hresp e_hresp 1 OKAY
m_addr_data_q da(object) 8 -
[0] ahb_addr_data - @3101
m_addr integral 32 'he5debca0
m_data integral 32 'h96ab582d
[1] ahb_addr_data - @3100
m_addr integral 32 'he5debca4
m_data integral 32 'h573870a
[2] ahb_addr_data - @3091
m_addr integral 32 'he5debca8
m_data integral 32 'h573870a
[3] ahb_addr_data - @3083
m_addr integral 32 'he5debcac
m_data integral 32 'hcecccc9d
[4] ahb_addr_data - @3097
m_addr integral 32 'he5debcb0
m_data integral 32 'hcecccc9d
[5] ahb_addr_data - @3095
m_addr integral 32 'he5debcb4
m_data integral 32 'hcecccc9d
[6] ahb_addr_data - @3075
m_addr integral 32 'he5debcb8
m_data integral 32 'hcb203e96
[7] ahb_addr_data - @3090
m_addr integral 32 'he5debcbc
m_data integral 32 'h86bc380d
begin_time time 64 1210
end_time time 64 1650
depth int 32 'd2
parent sequence (name) string 9 m_ahb_seq
parent sequence (full name) string 47 uvm_test_top.m_env.m_ahb_agent.m_seqr.m_ahb_seq
sequencer string 37 uvm_test_top.m_env.m_ahb_agent.m_seqr
---------------------------------------------------------------------------------------------------
UVM_INFO testbench.sv(329) @ 1750: uvm_test_top.m_env.m_ahb_agent.m_seqr@@m_ahb_seq [ahb_seq] End sequence
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 1750: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
