本文参考了github上的项目和网络上的其他资料,githbu原项目链接如下:
GitHub - seabeam/yuu_ahb: UVM AHB VIP (githubjs.cf)
1.对ahb-driver行为的理解:
由于ahb协议存在address phase和data phase,因此在设计driver的时候需要考虑到这一因素,提供三种方式来解决:
(1)将整个burst的数据传输分为三部分,也就是一个burst传输的第一拍,中间传输过程,burst传输的最后一拍(这种方式是我自己想的,比较笨,仅参考)。
task transfer();
@(negedge hclk);
wait(hready);
@(posedge hclk);
#0.1;
hsel = 'b1;
htrans = 'h2;
hsize = size;
hburst = burst;
hwrite = write;
haddr = addr;
mid_transfer();
@(negedge hclk);
wait(hready);
@(posedge hclk);
#0.1;
hsel = 'b0;
htrans = 'h0;
hsize = 'h0;
hburst = 'h0;
haddr = 'h0;
if(hwrite)begin
hwdata = hwdata_q[last];
end
else begin
hrdata_q[last] = hrdata;
end
endtask
task mid_transfer();
int i = 0;
repeat(len-1)begin
@(negedge hclk);
wait(hready);
@(posedge hclk);
#0.1;
if(hburst == 'd2 || hburst == 'd4 || hburst == 'd8)begin
if(hwrite)begin
hwdata = hwdata_q[i];
haddr = (haddr&(~j)) + ((haddr+(8<>1)+hsize + 1;
repeat (i-1)begin
j = (j<<1)+1;
end
if(hburst == 'd2 || hburst == 'd4 || hburst == 'd8)begin
len = (2<<(hburst/2));
end
else if(hburst == 'd3 || hburst == 'd5 || hburst == 'd7)begin
len = (2<<(hburst-1)/2);
end
(2)github上的项目,个人觉得这个项目的vip写的很好了,用到的ahb协议版本比较高,这里截取了核心部分(关于ahb2,3的部分)进行解释说明:
首先看下item的相关参数是如何设置的:
如果是incr类型,这里的incr类型包含incr/incr4/incr8/incr16,这里的aligned_address在字节对齐的情况下实际上就可以认为是start_address.
aligned_address = (yuu_ahb_addr_t'(start_address/number_bytes))*number_bytes;
address[0] = start_address;
if (burst_type == AHB_INCR || burst_type == AHB_FIXED) begin
low_boundary = start_address;
high_boundary = aligned_address+number_bytes*burst_length;
for (int n=1; n
constraint c_ahb_align {
address_aligned_enable == True;
burst_type == AHB_WRAP -> address_aligned_enable == True;
if (address_aligned_enable) {
burst_size == BYTE2 -> start_address[0] == 1'b0;
burst_size == BYTE4 -> start_address[1:0] == 2'b0;
burst_size == BYTE8 -> start_address[2:0] == 3'b0;
burst_size == BYTE16 -> start_address[3:0] == 4'b0;
burst_size == BYTE32 -> start_address[4:0] == 5'b0;
burst_size == BYTE64 -> start_address[5:0] == 6'b0;
burst_size == BYTE128 -> start_address[6:0] == 7'b0;
}
}
需要注意上面计算high_bodunary这样做的目的在于保证地址字节对齐,因为协议中规定对于一个burst传输来说,必须保证字节对齐:
另外,需要注意的是对于byte访问的时候(或者说数据位宽低于总线位宽的时候),协议中的规定如下,也就是hwdata的发送mater随便发,slave去选择具体哪个数据有效,而对于hrdata来说,需要master去选有效数据。
对于回环操作,地址选择如下:
这里计算了回环的边界,对于low_boundary,由初始地址/(burst长度*size对应的几个byte)。这里举个例子:比如需要进行一次word,wrap16传输,初始地址是’h68,那么计算过程就是104/(16*4)=1,1*64 = 64,计算得出初始地址是64.接下来算回卷地址的边界,也就是64+4*16 = 128.16进制是‘h80.
这里分了回环的第一次循环和接下来的循环,当是回环的第一次循环的时候is_wrapped值为false。第一次循环持续到地址加到边界处,此时触发下面的if条件语句,使得这次的地址并不是哪个边界值而是回环到初始值处,将is_wrap拉高,同时记录wrap_indx。此后由于is_wrap拉高使得进入第一个if条件中的else循环,每次地址递增会减去wrap_index值,进而实现递增。
else if (burst_type == AHB_WRAP) begin
boolean is_wrapped = False;
int wrap_idx = 0;
wrap_boundary = (yuu_ahb_addr_t'(start_address/(number_bytes*burst_length)))*(number_bytes*burst_length);
low_boundary = wrap_boundary;
high_boundary = low_boundary+number_bytes*burst_length;
for (int n=1; n
此外,还可以在item中记录传输的位置:
function void yuu_ahb_item::command_process();
// Location
location = new[len+1];
foreach (location[i])
location[i] = MIDDLE;
location[0] = FIRST;
location[len] = LAST;
endfunction
最后一点需要注意的是:考虑到不能跨越1kbyte的条件所以在给约束的时候需要增加如下条件:
constraint c_ahb_1k_boundary {
if (burst_type == AHB_INCR) {
start_address[9:0]+(len+1)*number_bytes <= 1024;
}
// WRAP never cross 1K
}
需要指出的是跨越1kbyte这个条件仅仅存在incr传输中,在回环操作中是不会存在这种情况的。
接下来看driver:
主要的框架函数就是下面的部分,这里是把cmd_phase和data_phase分为两个任务放在一个fork-join any中。
task yuu_ahb_master_driver::get_and_drive();
uvm_event handshake = events.get($sformatf("%s_driver_handshake", cfg.get_name()));
process proc_drive;
forever begin
wait(vif.drv_mp.hreset_n === 1'b1);
fork
begin
yuu_ahb_master_item item;
proc_drive = process::self();
processes["proc_drive"] = proc_drive;
seq_item_port.get_next_item(item);
handshake.trigger();
@(vif.drv_cb);
out_driver_port.write(item);
`uvm_do_callbacks(yuu_ahb_master_driver, yuu_ahb_master_driver_callback, pre_send(this, item));
fork
cmd_phase(item);
data_phase(item);
join_any
seq_item_port.item_done();
handshake.reset();
end
join
end
endtask
先看cmd_phase的任务:
在等待一定时间周期后将write,size,burst等传输过程中不变的信息驱动到总线上。
repeat(cur_item.idle_delay) vif.wait_cycle();
`uvm_info("cmd_phase", "Transaction start", UVM_HIGH)
vif.drv_cb.hwrite <= cur_item.direction;
vif.drv_cb.hsize <= cur_item.size;
vif.drv_cb.hburst <= cur_item.burst;
vif.drv_cb.hprot <= {cur_item.prot3, cur_item.prot2, cur_item.prot1, cur_item.prot0};
vif.drv_cb.hprot_emt <= {cur_item.prot6_emt, cur_item.prot5_emt, cur_item.prot4_emt, cur_item.prot3_emt};
vif.drv_cb.hmaster <= cur_item.master;
vif.drv_cb.hmastlock <= cur_item.lock;
vif.drv_cb.hnonsec <= cur_item.nonsec;
vif.drv_cb.hexcl <= cur_item.excl;
下面开始循环,这个vip考虑了传输错误的情况,如果传输错误,会将事件error_stopped驱动,那么在传输地址前通过is_on捕捉到传输错误事件,那么这个循环会直接中止。
for (int i=0; i <= len; i++) begin
//error operation:if error stop event is triggered ,that means the transfer is error,and the transfer shoud be terminate
if (error_stopped.is_on()) begin
if (error_key) begin
error_stopped.reset();
error_key = False;
end
else
error_key = True;
break;//terminate the transfer
end
drive_cmd_begin.trigger();
`uvm_info("cmd_phase", "Beat start", UVM_HIGH)
在传输没有发送错误的前提下将地址驱动到总线上:
//haddr send
vif.drv_cb.haddr <= cur_item.address[i];
接下来需要考虑htrans,对于htrans来说,如果是busy传输,模拟master内部存在延迟,根据延迟周期进行wait_cycle.
//htrans send,there is two situation,one is busy,if htrans is busy,that means master should wait cycle
if (cur_item.busy_delay[i] > 0) begin
vif.drv_cb.htrans <= BUSY;
repeat(cur_item.busy_delay[i]) vif.wait_cycle();
end
vif.drv_cb.htrans <= cur_item.trans[i];
需要指出的是协议中明确规定了,只有在不定长的数据传输的时候才能用busy状态(也就是在传输命令阶段插入idle或者NONSEQ)。
此外协议还规定:定长的传输类型中不能以busy结尾,single传输之后不能跟busy必须跟idle。
协议的这一部分体现同样是在item中体现的,但是这里是放在mater item中体现的,如下:
function void yuu_ahb_master_item::command_process();
super.command_process();
// Trans
trans = new[len+1];
foreach (trans[i])
trans[i] = SEQ;
trans[0] = NONSEQ;
if (!cfg.use_busy_end || (cfg.use_busy_end && burst != INCR)) begin
busy_delay[len] = 0;
end
busy_delay[0] = 0;
endfunction
此外在master item中还有对是否由busy传输及busy传输需要等待的周期进行了约束,如下:
constraint c_busy {
busy_delay.size() == len+1;
foreach (busy_delay[i]) {
soft busy_delay[i] inside {[0:`YUU_AHB_MAX_DELAY]};
if (!cfg.busy_enable || len == 0) {
busy_delay[i] == 0;
}
}
}
接下来继续看driver,driver中需要考虑hready,如果hready为0的化,那么所有的信息都保留不变。
//attention:whether data phase and address phase,when the hready is low,the information shoud be stay unchaged
do
vif.wait_cycle();
while (vif.drv_cb.hready_i !== 1'b1);
上述的过程的整体结构包含:基本信息+haddr+htrans(考虑了busy)+hready。这个过程会持续到len+1次(从0开始计数),在最后一个周期的时候,实际上是最后一拍的数据阶段,此时addr等信息需要清0,通过item中位置信息来判断是否是最后的一个周期,如果是的化,将这些信息清0.
//for the last cycle,cmd is send overed,but there is another data phase cycle,so the htrans/hmastlock shoud be rst
if (cur_item.location[i] == LAST) begin
vif.drv_cb.htrans <= IDLE;
vif.drv_cb.hmastlock <= 1'b0;
vif.drv_cb.hnonsec <= 1'b1;
end
drive_cmd_end.trigger();
`uvm_info("cmd_phase", "Beat end", UVM_HIGH)
end
end
注意从上面的操作来看,hmastlock是在最后一个数据的地址段结束的时候拉低,这个过程也是符合协议的,协议的时序如下:
(个人觉得这个hmastelock和axi的原子操作很像,都是先读后写) 。bus被lock住需要关注hmastlock/hready/hsel三个信号,而被解除lock仅仅需要关注hmastlock和hready信号。协议中建议在lock传输结束的时候插入一个idle。
接下来看data_phase.
为了保证address phase和data phase的pipline结构,这里进行了如下操作:
while (vif.drv_cb.hready_i !== 1'b1 || vif.mon_cb.htrans !== NONSEQ)
vif.wait_cycle();
`uvm_info("data_phase", "Transaction start", UVM_HIGH)
drive_data_begin.trigger();
也就是说只有等到总线上已经出现了hready为高同时htrans为nonseq的时候才会进入到data phase。
在上述条件满足的条件下,进行循环过程:
下面是写操作:
for (int i=0; i <= len; i++) begin
boolean has_got = False;
`uvm_info("data_phase", "Beat start", UVM_HIGH)
//for write,give data
if (cur_item.direction == WRITE) begin
vif.drv_cb.hwdata <= cur_item.data[i];
vif.drv_cb.upper_byte_lane <= cur_item.upper_byte_lane[i];
vif.drv_cb.lower_byte_lane <= cur_item.lower_byte_lane[i];
end
注意写操作和读操作对待ready为0的情况是不同的:
也就是说,对于写操作,hready拉低的那一拍,数据就可以放在总线上,而对于读操作来水,由于数据是由slave那一侧回来的,因此在这一拍拉低的时候是不能同一时间放在总线上,对于slave来说是在那高hready的那一周期同时将数据放到总线上,因此master要采集该数据的化需要等hready拉高的下一周期才可以将数据采集到。
因此对于读数据有两种情况,第一种考虑ready信号为低的周期,仅仅当hready拉高后的下一个时钟沿才会去取数据(通过drv_cb采集的,用mon_cb应该也可以)
这里实际上还考虑了一种情况,就是如果此时master为busy状态,但是此时hrdata来了,那么此时是需要将这个回来的读数据收集的。
do begin
vif.wait_cycle();
if (vif.drv_cb.hready_i === 1'b1 && cur_item.direction == READ && !has_got) begin
if (i == 0)
cur_item.exokay = vif.drv_cb.hexokay;
cur_item.data[i] = vif.drv_cb.hrdata;
cur_item.response[i] = vif.drv_cb.hresp;
has_got = True;
end
end
while (vif.drv_cb.hready_i !== 1'b1 || vif.mon_cb.htrans === BUSY);
上面的语句保证了这一 周期hready为高,如果为低的化,上面的语句就会收集数据,同时把has_got变为true,保证下面不会再次采集数据,如果hready直接为高,由于has_got为0,那么进入下面的采集数据过程。
if (!has_got) begin
if (cur_item.direction == READ) begin
cur_item.data[i] = vif.drv_cb.hrdata;
end
cur_item.response[i] = vif.drv_cb.hresp;
if (i == 0)
cur_item.exokay = vif.drv_cb.hexokay;
end
整体的driver过程就是上面的部分,个人觉得在data_phase阶段的数据循环用i 关于monitor monitor的整体框架如下: 首先看cmd_phase: 一开始如果ready信号为低的化就等一拍。 下面的逻辑实际上是为了收集传输过程中的item。 首先看while循环语句,也就是仅仅在hready为1并且htrans为NOSEQ或者idle的时候执行。也就是第一次来到NOSEQ并不是立马能够收集。这里收集的是传输过程中的内容。在传输过程中将addr和htrans推到队列中。这个过程会一直持续到将所有地址和htrans均收集完。 第一次来的地址实际上是在这里收集。第一次来的地址由于是noseq,因此这里会先创建一个item,随后一些传输过程中不变的信息存入,随后将htrans和item存入。 在第一次传输结束后由于前面整体框架中使用的forever语句,因此又会跳到前面的判断address_q是否为空,此时由于第一次的数据已经推到address_q中,因此会满足条件。注意这里的条件是如果address_q的size大于0同时htrans为noseq或者idle的情况。也就是说这里实际包含了两种情况。假设一次传输burst传输noseq传输之后由于burst传输还没有结束,因此还会继续执行前面的while循环语句。但是由于burst传输过程中不会再出现htrans为noseq的情况,因此不会再调用前面的“收集第一个地址”的语句。前面while语句会执行到整个burst传输全部收集结束,burst传输收集结束后会有两种情况,第一种是后面继续的传输,此时htrans应该为idle,另一种是后面紧跟着一个burst传输,此时htrans应该为noseq。无论是哪种情况,一个burst传输结束后都应该将数据打包发送出去,因此这里在满足这两种情况下调用assembling_and_send任务。 assembling_and_send任务如下(这个任务主要是将检测到的数据打包发送给别的地方): 首先根据address_q队列的大小对len进行赋值,注意这里的len并不是AHB总线上传输数据多少的意思,而是起到一个需要打包多少次的作用。 具体的打包过程如下:在第一次中由于len值为0,因此实际上只会打包一次。打包的过程也就是将前面收集到的address推到item中。这里还有data是为了下面再data phase中同样需要打包。 同时len产生位置信息: 接下来看data phase阶段: data phase阶段较为简单,前面类似等ready、等noseq保证pipline信号。 接着根据传输的item是写还是读,将总线上的写数据或者是读数据分别推到队列上,同时还需要记录response。 最后回顾调用assembling_and_send的时间。在第一次地址收集的时候由于address_q的size为0,因此实际上别不会调用assembling_and_send。在address phase任务结束的时候打了一拍,打拍后此时总线上的htrans已经是seq。因此这里实际上在第二拍的同一时刻在data phase中会将data推入到队列中。但是实际上在进入到第二拍的时候实际上address phase由于forever的作用也进入到了第二次的循环,那么进入到address phase后根据前面的分析就会调用 assembling_and_send任务。那么你怎么保证是你先调用assembling_and_send任务快还是data phase中往队列中推入data的数据快呢?如果后者快,那么assembling_and_send的行为是正确的,可以把每一个address对应的data正好pipline的打包。但是如果前者快,那么实际这里就会出现错误。 注意assembling_and_send在进入到任务后有一个#0,这个#0保证了同一时刻data phase中往队列中推入data的数据是快于data phase中往队列中推入data的数据任务中调用打包逻辑。 (3)未完...task yuu_ahb_master_monitor::run_phase(uvm_phase phase);
process proc_monitor;
init_component();
fork
forever begin
wait(vif.mon_mp.hreset_n === 1'b1);
fork
begin
proc_monitor = process::self();
processes["proc_monitor"] = proc_monitor;
fork
cmd_phase();
data_phase();
join_any
end
join
end
count_busy();
count_idle();
wait_reset();
join
endtask
while (vif.mon_cb.hready_i !== 1'b1)
vif.wait_cycle();
if (address_q.size()>0 && (vif.mon_cb.htrans == NONSEQ || vif.mon_cb.htrans == IDLE))
assembling_and_send(monitor_item);
while(vif.mon_cb.hready_i !== 1'b1 || (vif.mon_cb.htrans !== NONSEQ && vif.mon_cb.htrans !== IDLE)) begin
if (vif.mon_cb.hready_i === 1'b1 && vif.mon_cb.htrans === SEQ) begin
address_q.push_back(vif.mon_cb.haddr);
trans_q.push_back(yuu_ahb_trans_e'(vif.mon_cb.htrans));
end
vif.wait_cycle();
end
monitor_cmd_begin.trigger();
if (vif.mon_cb.htrans === NONSEQ) begin
if (address_q.size()>0)
assembling_and_send(monitor_item);
monitor_item = yuu_ahb_master_item::type_id::create("monitor_item");
`uvm_do_callbacks(yuu_ahb_master_monitor, yuu_ahb_master_monitor_callback, pre_collect(this, monitor_item));
monitor_item.direction = yuu_ahb_direction_e'(vif.mon_cb.hwrite);
monitor_item.size = yuu_ahb_size_e'(vif.mon_cb.hsize);
monitor_item.burst = yuu_ahb_burst_e'(vif.mon_cb.hburst);
monitor_item.prot3 = yuu_ahb_prot3_e'(vif.mon_cb.hprot[3]);
monitor_item.prot2 = yuu_ahb_prot2_e'(vif.mon_cb.hprot[2]);
monitor_item.prot1 = yuu_ahb_prot1_e'(vif.mon_cb.hprot[1]);
monitor_item.prot0 = yuu_ahb_prot0_e'(vif.mon_cb.hprot[0]);
monitor_item.prot6_emt = yuu_ahb_emt_prot6_e'(vif.mon_cb.hprot_emt[6]);
monitor_item.prot5_emt = yuu_ahb_emt_prot5_e'(vif.mon_cb.hprot_emt[5]);
monitor_item.prot4_emt = yuu_ahb_emt_prot4_e'(vif.mon_cb.hprot_emt[4]);
monitor_item.prot3_emt = yuu_ahb_emt_prot3_e'(vif.mon_cb.hprot_emt[3]);
monitor_item.master = vif.mon_cb.hmaster ;
monitor_item.lock = vif.mon_cb.hmastlock;
monitor_item.nonsec = yuu_ahb_nonsec_e'(vif.mon_cb.hnonsec);
monitor_item.excl = yuu_ahb_excl_e'(vif.mon_cb.hexcl);
monitor_item.burst_size = yuu_ahb_burst_size_e'(monitor_item.size);
if (monitor_item.burst inside {WRAP4, WRAP8, WRAP16})
monitor_item.burst_type = AHB_WRAP;
else
monitor_item.burst_type = AHB_INCR;
monitor_item.address_aligned_enable = True;
monitor_item.start_time = $realtime();
address_q.push_back(vif.mon_cb.haddr);
trans_q.push_back(yuu_ahb_trans_e'(vif.mon_cb.htrans));
end
vif.wait_cycle();
monitor_cmd_end.trigger();
m_cmd_sem.put();
if (address_q.size()>0 && (vif.mon_cb.htrans == NONSEQ || vif.mon_cb.htrans == IDLE))
assembling_and_send(monitor_item);
task yuu_ahb_master_monitor::assembling_and_send(yuu_ahb_master_item monitor_item);
int len = address_q.size()-1;
yuu_ahb_master_item item = yuu_ahb_master_item::type_id::create("monitor_item");
#0;
item.copy(monitor_item);
item.len = len;
item.address = new[len+1];
item.data = new[len+1];
item.trans = new[len+1];
item.response = new[len+1];
item.busy_delay = new[len+1];
item.location = new[len+1];
for (int i=0; i<=len; i++) begin
item.address[i] = address_q.pop_front();
item.data[i] = data_q.pop_front();
item.trans[i] = trans_q.pop_front();
item.response[i] = response_q.pop_front();
item.busy_delay[i] = busy_q.pop_front();
end
item.exokay = exokay_q.pop_front();
item.idle_delay = idle_q.pop_front();
foreach (item.location[i])
item.location[i] = MIDDLE;
item.location[0] = FIRST;
item.location[len] = LAST;
item.start_address = item.address[0];
item.end_time = $realtime();
`uvm_do_callbacks(yuu_ahb_master_monitor, yuu_ahb_master_monitor_callback, post_collect(this, item));
out_monitor_port.write(item);
// item.print();
endtask
task yuu_ahb_master_monitor::data_phase();
uvm_event monitor_data_begin = events.get($sformatf("%s_monitor_data_begin", cfg.get_name()));
uvm_event monitor_data_end = events.get($sformatf("%s_monitor_data_end", cfg.get_name()));
m_data_sem.get();
while (vif.mon_cb.hready_i !== 1'b1 || (vif.mon_cb.htrans !== NONSEQ && vif.mon_cb.htrans !== SEQ))
vif.wait_cycle();
do
vif.wait_cycle();
while (vif.mon_cb.hready_i !== 1'b1);
monitor_data_begin.trigger();
if (monitor_item.direction == WRITE) begin
data_q.push_back(vif.mon_cb.hwdata);
end
else if (monitor_item.direction == READ) begin
data_q.push_back(vif.mon_cb.hrdata);
end
response_q.push_back(yuu_ahb_response_e'(vif.mon_cb.hresp));
if (monitor_item.excl == EXCLUSIVE)
exokay_q.push_back(yuu_ahb_exokay_e'(vif.mon_cb.hexokay));
else if (exokay_q.size() == 0)
exokay_q.push_back(EXOKAY);
monitor_data_end.trigger();
m_data_sem.put();
endtask