本文参考了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)beginhwdata = hwdata_q[last];endelse beginhrdata_q[last] = hrdata;endendtasktask mid_transfer();int i = 0;repeat(len-1)begin@(negedge hclk);wait(hready);@(posedge hclk);#0.1;if(hburst == 'd2 || hburst == 'd4 || hburst == 'd8)beginif(hwrite)beginhwdata = hwdata_q[i];haddr =  (haddr&(~j)) + ((haddr+(8<<hszie)/8)&j);htrans = 'h3;endelse beginhrdata_q[i] = hwdata;haddr =  (haddr&(~j)) + ((haddr+(8<<hszie)/8)&j);htrans = 'h3;endendelse if(hburst == 'd2 || hburst == 'd4 || hburst == 'd8)beginif(hwrite)beginhwdata = hwdata_q[i];haddr =  haddr+(8<<hszie)/8;htrans = 'h3;endelse beginhrdata_q[i] = hrdata;haddr =  haddr+(8<<hszie)/8;htrans = 'h3;endendi = i + 1;    endi = 0
endtaskint i = 0;
int j = 1;
int k = 0;i = (hburst>>1)+hsize + 1;
repeat (i-1)beginj = (j<<1)+1;
end if(hburst == 'd2 || hburst == 'd4 || hburst == 'd8)beginlen = (2<<(hburst/2));
end
else if(hburst == 'd3 || hburst == 'd5 || hburst == 'd7)beginlen = (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) beginlow_boundary  = start_address;high_boundary = aligned_address+number_bytes*burst_length;for (int n=1; n<burst_length; n++) beginaddress[n] = aligned_address+n*number_bytes;endend
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) beginboolean 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<burst_length; n++) beginif (!is_wrapped)address[n] = aligned_address+n*number_bytes;elseaddress[n] = low_boundary + (n-wrap_idx)*number_bytes;if (address[n] == high_boundary) beginaddress[n] = wrap_boundary;wrap_idx = n;is_wrapped = True;endendend

此外,还可以在item中记录传输的位置:

function void yuu_ahb_item::command_process();// Locationlocation = 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 beginwait(vif.drv_mp.hreset_n === 1'b1);forkbeginyuu_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));forkcmd_phase(item);data_phase(item);join_anyseq_item_port.item_done();handshake.reset();endjoinend
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()) beginif (error_key) beginerror_stopped.reset();error_key = False;endelseerror_key = True;break;//terminate the transferenddrive_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) beginvif.drv_cb.htrans <= BUSY;repeat(cur_item.busy_delay[i]) vif.wait_cycle();endvif.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();// Transtrans = new[len+1];foreach (trans[i])trans[i] = SEQ;trans[0] = NONSEQ;if (!cfg.use_busy_end || (cfg.use_busy_end && burst != INCR)) beginbusy_delay[len] = 0;endbusy_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     dovif.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 rstif (cur_item.location[i] == LAST) beginvif.drv_cb.htrans <= IDLE;vif.drv_cb.hmastlock <= 1'b0;vif.drv_cb.hnonsec <= 1'b1;enddrive_cmd_end.trigger();`uvm_info("cmd_phase", "Beat end", UVM_HIGH)endend

注意从上面的操作来看,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++) beginboolean has_got = False;`uvm_info("data_phase", "Beat start", UVM_HIGH)//for write,give dataif (cur_item.direction == WRITE) beginvif.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 beginvif.wait_cycle(); if (vif.drv_cb.hready_i === 1'b1 && cur_item.direction == READ && !has_got) beginif (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;endendwhile (vif.drv_cb.hready_i !== 1'b1 || vif.mon_cb.htrans === BUSY);

上面的语句保证了这一 周期hready为高,如果为低的化,上面的语句就会收集数据,同时把has_got变为true,保证下面不会再次采集数据,如果hready直接为高,由于has_got为0,那么进入下面的采集数据过程。

 if (!has_got) beginif (cur_item.direction == READ) begincur_item.data[i] = vif.drv_cb.hrdata;endcur_item.response[i] = vif.drv_cb.hresp;if (i == 0)cur_item.exokay = vif.drv_cb.hexokay;end

整体的driver过程就是上面的部分,个人觉得在data_phase阶段的数据循环用i<len即可。因为data_phase实际上是晚了一拍才开始执行的,因此仅仅需要len次循环就可以了。但是这里用来fork join_any,也就是地址段的最后一周期传输结束后就直接item_done了,因此data_phase的最后一个周期应该会被提前终止。这里的分析等以后有机会在论证。

关于monitor

monitor的整体框架如下:

task yuu_ahb_master_monitor::run_phase(uvm_phase phase);process proc_monitor;init_component();forkforever beginwait(vif.mon_mp.hreset_n === 1'b1);forkbeginproc_monitor = process::self();processes["proc_monitor"] = proc_monitor;forkcmd_phase();data_phase();join_anyendjoinendcount_busy();count_idle();wait_reset();join
endtask

首先看cmd_phase:

一开始如果ready信号为低的化就等一拍。

  while (vif.mon_cb.hready_i !== 1'b1)vif.wait_cycle();

下面的逻辑实际上是为了收集传输过程中的item。 首先看while循环语句,也就是仅仅在hready为1并且htrans为NOSEQ或者idle的时候执行。也就是第一次来到NOSEQ并不是立马能够收集。这里收集的是传输过程中的内容。在传输过程中将addr和htrans推到队列中。这个过程会一直持续到将所有地址和htrans均收集完。

  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)) beginif (vif.mon_cb.hready_i === 1'b1 && vif.mon_cb.htrans === SEQ) beginaddress_q.push_back(vif.mon_cb.haddr);trans_q.push_back(yuu_ahb_trans_e'(vif.mon_cb.htrans));endvif.wait_cycle();end

第一次来的地址实际上是在这里收集。第一次来的地址由于是noseq,因此这里会先创建一个item,随后一些传输过程中不变的信息存入,随后将htrans和item存入。

monitor_cmd_begin.trigger();if (vif.mon_cb.htrans === NONSEQ) beginif (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;elsemonitor_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));endvif.wait_cycle();monitor_cmd_end.trigger();m_cmd_sem.put();

在第一次传输结束后由于前面整体框架中使用的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任务。

if (address_q.size()>0 && (vif.mon_cb.htrans == NONSEQ || vif.mon_cb.htrans == IDLE))assembling_and_send(monitor_item);

assembling_and_send任务如下(这个任务主要是将检测到的数据打包发送给别的地方):

首先根据address_q队列的大小对len进行赋值,注意这里的len并不是AHB总线上传输数据多少的意思,而是起到一个需要打包多少次的作用。

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];

具体的打包过程如下:在第一次中由于len值为0,因此实际上只会打包一次。打包的过程也就是将前面收集到的address推到item中。这里还有data是为了下面再data phase中同样需要打包。

for (int i=0; i<=len; i++) beginitem.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();enditem.exokay = exokay_q.pop_front();item.idle_delay = idle_q.pop_front();

同时len产生位置信息:

 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

接下来看data phase阶段:

data phase阶段较为简单,前面类似等ready、等noseq保证pipline信号。

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();dovif.wait_cycle();while (vif.mon_cb.hready_i !== 1'b1);

接着根据传输的item是写还是读,将总线上的写数据或者是读数据分别推到队列上,同时还需要记录response。

 monitor_data_begin.trigger();if (monitor_item.direction == WRITE) begindata_q.push_back(vif.mon_cb.hwdata);endelse if (monitor_item.direction == READ) begindata_q.push_back(vif.mon_cb.hrdata);endresponse_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

最后回顾调用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)未完...

AMBA总线协议-结合ahb-master对ahb协议的理解相关推荐

  1. AMBA总线协议AHB、APB

    一.什么是AMBA总线 AMBA总线规范是ARM公司提出的总线规范,被大多数SoC设计采用,它规定了AHB (Advanced High-performance Bus).ASB (Advanced ...

  2. AMBA总线协议之AHB学习记录(1)—ahb_bus(附verilog代码)

    目录 0.前言 1.AHB简介 2.ahb_bus实现(verilog) 3.总结反思 & 后面学习计划 0.前言 前段时间粗略过了一下riscv指令集相关内容,并对开源项目tinyriscv ...

  3. AMBA总线协议AHB、APB、AXI对比分析

    一.AMBA概述 AMBA (Advanced Microcontroller Bus Architecture) 高级处理器总线架构 AHB (Advanced High-performance B ...

  4. AMBA总线协议的学习-AHB,ASB,APB三种总线以及AXI接口

    一.AMBA概述 AMBA (Advanced Microcontroller Bus Architecture) 高级处理器总线架构 AHB (Advanced High-performance B ...

  5. AMBA总线协议(包含AHB与APB)

    文章目录 一.AMBA总线概述 二. AHB协议 2.1 AHB组成 2.2 AHB基本信号 2.3 AHB传输 2.3.1 AHB一次无需等待状态的简单传输 2.3.2 AHB需要两个等待周期的简单 ...

  6. AHB、APB、AXI三种协议对比分析(AMBA总线)

    一.AMBA概述  AMBA (Advanced Microcontroller Bus Architecture) 高级处理器总线架构  AHB (Advanced High-performance ...

  7. AMBA总线协议APB、AHB——学习笔记

    文章目录 前言 一.总线介绍 1.基本概念 2.典型AMBA系统 二.APB3(Advanced Peripheral Bus) 1.定义 2.信号 3.版本信息 4.信号传输时序图 4.1 状态机 ...

  8. AMBA总线—AHB总线协议详解

    文章目录 一.AMBA总线介绍 1.1.AMBA发展史 1.2.典型的AMBA系统 二.AHB总线(宏观构造) 2.1.AHB总线组成 2.2.AHB总线组成互连 2.3.AHB操作概述 2.4.AH ...

  9. AMBA总线协议(三)——一文看懂AHB总线所有协议总结(AHB2 AHB-Lite AHB5 )

    AMBA AHB 总线协议介绍请点击以下链接: AMBA总线协议(一)--一文看懂APB总线协议 AMBA总线协议(二)一文看懂AMBA2 AHB2与AMBA3 AHB-Lite总线协议的区别 AMB ...

最新文章

  1. 普通程序员如何用1年时间获取3年成长?
  2. MongoDB的设计模式策略
  3. 深入理解Java类型信息(Class对象)与反射机制
  4. [:zh]给机械课程设计的一封信[:] 2017-12-23
  5. 计算机单片机英语书籍推荐,英语翻译近十几年来,单片机作为微计算机一个很重要的分支,应用广泛,发展迅速,已经对人类社会产生了深远的影响.本文介绍了基...
  6. mysql中使用安全等于 <=>
  7. 合流超几何函数_【初中数学大招流】从平面几何到解析几何
  8. [面试] C/C++ 语法 —— 内存与操作系统
  9. 【Pix4d精品教程】Pix4Dmapper完整航测内业操作流程手把手图文教程
  10. php 固定表头,固定表头和首列的表格
  11. 冰点还原精灵手工强制删除方法
  12. RabbitMQ 归纳总结
  13. android app闪退的原因分析,打开一个app就闪退解决方法
  14. 如何知道Android机型是32位还是64位
  15. numpy ndarray嵌套ndarray浅显理解
  16. arcgis license manager点击启动无反应
  17. 一个按键精灵后台发送消息的脚本
  18. Windows电脑如何滑动关机(Slide To Shut Down)
  19. Web自动化测试——实践篇
  20. 信捷伺服刚性调整_信捷伺服电机奇怪的质量问题,做工控多年首次仅见!!!!...

热门文章

  1. 论文解读--K-Radar:4D Radar Object Detection for Autonomous Driving in Various Weather Conditions
  2. ios 内购 恢复内购
  3. 低宽带网络加速平台 企业用途
  4. PC通过WIFI向Kindle无线传输电子书+Everything
  5. 从程序员到 33 岁的亿万富翁
  6. 新时代的亿万富翁是怎么炼成的
  7. 我们如何教育孩子(转载西路的作品)
  8. php 微信模板消息url,【求助】php 微信公众号 发送模板消息改变不了颜色
  9. seacms_v6.4(海洋cms)前台RCE 分析
  10. 基于谱减法的音频信号噪声抑制的Matlab和C语言分别实现并对比