拓冰建站拓冰建站
首页 / 资讯中心 / 正文

用Verilog实现AXI4主机接口:协议解析、RTL设计与仿真验证

做AXI4接口这活儿说难不难说简单也真不简单。如果你已经写过几个简单的Verilog模块比如UART、SPI、I2C之类的那么上手AXI4最大的坎不是语法而是协议本身的思维转换——从数据有效就拉高信号的单通道思维切换到地址、数据、响应分离五个通道各自握手、还能并行跑的多通道思维。这篇博客我就用自己实际做过的一个基于Verilog的AXI4主机接口为例把协议要点、RTL设计思路、仿真验证方法和踩过的坑一次讲透希望能给正在做IC验证、FPGA开发或者准备秋招笔试的同学一点参考。1. 为什么非要搞懂AXI4协议设计背后的思路拆解1.1 从APB和AHB说起理解AXI4到底解决了什么问题在接触AXI4之前很多FPGA玩家用的是APB或AHB。APB简单两个状态就搞定读写但性能也就那样适合挂寄存器配置这种低速场景。AHB加了流水线能做到一个周期发地址、下一周期传数据但它的总线是共享型的——同一时刻只能有一个主机占用总线所有从机都挂在同一条总线上仲裁器来决定谁用。主机一多仲裁开销大带宽也上不去。AXI4出来之后变化是颠覆性的。它不再是一条共享总线而是变成了点对点的通道互联结构。每个主机和从机之间由互交叉开关Crossbar或网络 interconnect 来连接。而且它把读写彻底分开读通道和写通道物理上独立意味着一个主机可以在发写请求的同时发读请求从机也可以在回读数据的同时接收新的写地址整个总线系统的吞吐量一下就上去了。还有个关键设计是突发传输Burst。AHB也有突发但AXI4把突发长度拉到了256拍加上多笔交易可以流水线式地背靠背执行不需要等上一笔完全结束才开始下一笔。这套机制特别适合DDR控制器、DMA引擎、视频帧缓冲这种大量连续数据搬运的场景。1.2 五个通道、三种接口形态先建立全局认知AXI4协议把一次完整的读写拆成五个通道读地址通道AR主机发读请求的地址和控制信息读数据通道R从机把读到的数据返回给主机写地址通道AW主机发写请求的地址和控制信息写数据通道W主机把要写的数据发给从机写响应通道B从机写完数据后回一个响应告诉主机写操作是否成功每个通道都是一组独立的握手信号VALID/READY通道之间可以并行、可以乱序完成。比如写操作地址通道和写数据通道是独立的从机可以先收地址也可以先收数据甚至两个同时进行只要它们最终匹配成同一笔事务就行。这个独立性既是AXI4灵活性的来源也是新手最容易懵的地方。另外要注意AXI4还有两个变种AXI4-Lite去掉了突发传输每次只能读写一个数据但逻辑简单适合寄存器配置AXI4-Stream干脆连地址都省了只保留数据流适合FIFO、DMA内部数据搬运这类场景。如果面试官问三者的区别记住AXI4重吞吐、Lite重简单、Stream重流式基本就稳了。1.3 为什么用Verilog写AXI4接口容易被坑用Verilog写AXI4比用SystemVerilog写确实要更小心。SystemVerilog有interface、有断言assertion、有覆盖率和随机约束验证环境一套下来很成熟但Verilog只有module和wire/reg握手逻辑全靠手写时序对不对全凭RTL coding的时候自己把握。用Verilog实现的常见难点有三个一是五个通道的状态机交织容易写着写着就漏了某个VALID/READY组合二是握手机制时序细节比如VALID不能依赖READY来拉高、一旦拉高不能随便拉低除非协议允许这些规则在Verilog里没有编译器帮你检查全靠自觉三是和从机互连时地址对齐、突发长度、数据位宽这些参数需要手动算准。但这不代表Verilog做不了。实际上只要把协议吃透用状态机加计数器完全能写一个干净、可靠的AXI4主机接口。而且很多FPGA项目还是用Verilog能把它写好反而说明你对协议的理解更扎实。2. AXI4协议核心细节解析握手、突发和通道时序2.1 VALID/READY握手的四种组合别在这上面翻车AXI4通道的本质就是两个信号VALID表示发送方数据有效READY表示接收方准备好接收。两边的时序关系决定了数据传输的时机。协议规定了两条核心规则VALID一旦拉高必须保持到握手完成即VALID和READY同时为高的那个时钟上升沿中间不能随便拉低。接收方可以在任意时刻拉高READY但通常建议在VALID拉高之前或同时就绪避免额外的等待周期。四种组合对应四种传输行为组合现象说明VALID先高READY后高等待一拍后传输接收方还没准备好数据在总线上等待READY先高VALID后高立即传输接收方一直就绪数据有效就立刻被接收两者同时拉高立即传输零等待最高效的情况两者交替拉高可能死锁如果双方都等对方就永远握不上我在实际调试中真遇到过最后那种情况。自己写的从机读数据通道的READY逻辑里莫名其妙加了个条件等主机先发数据而主机的RVALID又在等从机的RREADY结果两边死等。仿真波形一看RVALID和RREADY永远有一个是低这就是典型的握手死锁。经验教训凡是写AXI4通道的握手逻辑一定要明确谁先谁后。标准做法是主机的VALID由内部状态机产生不依赖从机的READY从机的READY尽早拉高最好在空闲时一直保持为高这样整个通道的等待时间最短。2.2 突发传输的三要素长度、大小、类型突发传输是AXI4性能的核心但也藏着最多的参数计算细节。每个突发请求由三个信号共同定义AWLEN/ARLEN突发长度表示本次突发包含的数据拍数减1。比如AWLEN等于7那就是8拍数据。AXI4的INCR类型允许1到256拍所以AWLEN是8位范围0到255。AWSIZE/ARSIZE突发大小表示每一拍传输的字节数用2的幂表示比如3表示8字节对应64位数据总线。AWBURST/ARBURST突发类型FIXED、INCR、WRAP三种。FIXED是每次都在同一地址适合写FIFOINCR是递增地址每次增加SIZE字节适合连续内存访问WRAP是回卷模式地址递增到边界后回卷适合缓存行访问。这里最容易出错的是地址对齐。特别是WRAP类型它要求突发传输的范围必须是SIZE乘以LEN的整数倍而且起始地址必须对齐到这个范围。如果算错了从机看到地址跳变不符合协议直接给你返回一个错误响应或者数据直接写错位置。我在做DDR控制器功能验证的时候就专门写了一套地址计算的testbench来跑这些边界情况。2.3 写通道时序和字节使能数据能不能正确落地的关键写操作涉及到三个通道AW、W和B。握手顺序上三个通道是相对独立的但逻辑上有一种推荐顺序先发AW地址再发W数据最后收B响应。不过协议允许W比AW早也允许B在W数据还没传完时提前返回前提是从机逻辑允许。写数据通道最容易被忽略的是WSTRB也就是字节使能信号。比如数据总线是64位8字节但一次写操作只想写低4字节那WSTRB就是4b1111对应前32位如果写单字节WSTRB对应的那一位是1其余是0。很多新手写第一个AXI接口时会忘了处理WSTRB直接把整条数据线全塞过去这在仿真里不会报错但上板后外设行为就不对了——比如往寄存器写控制位结果把保留位也写脏了。另外W通道的最后一拍要把WLAST拉高告诉从机这是本次突发的最后一份数据。从机通常用WLAST来触发内部写入逻辑或者产生写响应如果你忘了拉WLAST从机会一直等后续数据导致B响应永远不来主机就卡死在等待响应的状态。2.4 读通道的响应和数据返回读操作相对简单只有AR和R两个通道。主机先发AR请求从机准备好数据后在R通道返回RVALID和RREADY握手成功后数据有效。R数据通道同样有RLAST标记最后一拍也有RRESP表示读响应状态。读通道的时序有一个特点从机返回数据可能需要多个周期延迟尤其当从机内部是个慢速设备或者经过多层interconnect时延迟甚至可能达到几十拍。这时候主机必须有足够的缓冲区FIFO来暂存回来的数据同时利用AXI4的outstanding机制在第一批数据还没回来时就发出下一批读请求把延迟隐藏掉。这个设计思路在做DMA读DDR的时候特别重要能直接决定有效带宽。3. 用Verilog写一个AXI4主机接口RTL实现和仿真验证3.1 整体架构设计先想清楚再动手我这次实现的AXI4主机接口目标场景是挂在一个简单SoC内部用来读写DDR控制器。数据总线64位支持INCR突发最大突发长度16不支持outstanding让逻辑先跑通后续可以再扩展。模块结构分成这几块顶层模块axi4_master定义所有AXI4接口信号写状态机write_fsm管理AW、W、B三个通道的交互读状态机read_fsm管理AR、R两个通道内部寄存器组存储要发起的读或写请求的控制字顶层接口定义大概是这样的module axi4_master #( parameter DATA_WIDTH 64, parameter ADDR_WIDTH 32, parameter ID_WIDTH 4 )( input wire ACLK, input wire ARESETN, // 用户侧接口 input wire user_req_valid, output wire user_req_ready, input wire user_req_write, // 1:写, 0:读 input wire [ADDR_WIDTH-1:0] user_req_addr, input wire [7:0] user_req_len, input wire [DATA_WIDTH-1:0] user_wdata, input wire [DATA_WIDTH/8-1:0] user_wstrb, output wire [DATA_WIDTH-1:0] user_rdata, output wire user_rvalid, output wire user_rlast, // AXI4写地址通道 output wire [ID_WIDTH-1:0] AWID, output wire [ADDR_WIDTH-1:0] AWADDR, output wire [7:0] AWLEN, output wire [2:0] AWSIZE, output wire [1:0] AWBURST, output wire AWVALID, input wire AWREADY, // AXI4写数据通道 output wire [DATA_WIDTH-1:0] WDATA, output wire [DATA_WIDTH/8-1:0] WSTRB, output wire WLAST, output wire WVALID, input wire WREADY, // AXI4写响应通道 input wire [ID_WIDTH-1:0] BID, input wire [1:0] BRESP, input wire BVALID, output wire BREADY, // AXI4读地址通道 output wire [ID_WIDTH-1:0] ARID, output wire [ADDR_WIDTH-1:0] ARADDR, output wire [7:0] ARLEN, output wire [2:0] ARSIZE, output wire [1:0] ARBURST, output wire ARVALID, input wire ARREADY, // AXI4读数据通道 input wire [ID_WIDTH-1:0] RID, input wire [DATA_WIDTH-1:0] RDATA, input wire [1:0] RRESP, input wire RVALID, output wire RREADY, input wire RLAST ); endmodule从顶层就把五个通道全部暴露出来方便后续验证和集成。ID信号我固定成0不搞多ID交织减少复杂度如果需要多笔outstanding再另外用ID区分即可。3.2 写通道状态机设计写通道用了一个四状态的状态机IDLE、WRITE_ADDR、WRITE_DATA、WRITE_RESP。IDLE等待用户请求。当user_req_valid user_req_write时锁存地址、长度、数据、字节使能然后跳转到WRITE_ADDR。WRITE_ADDR拉高AWVALID发送AWADDR、AWLEN、AWSIZE、AWBURST。当AWVALID AWREADY握手成功后如果W通道数据还没有发完就跳转到WRITE_DATA如果W通道在之前已经并发出去了某些实现里可以通过FIFO并行发送可以直接跳到WRITE_RESP。我这里简化为顺序执行所以AW握手完成后直接进WRITE_DATA。这里有个细节AWSIZE怎么算。数据总线是64位也就是8字节AWSIZE应该设为3表示8字节。但如果是跨着地址不对齐的访问比如地址是0x4要从偏移4的位置开始写8字节实际会跨越两个64位边界。最简单的做法是限制用户地址必须对齐到8字节如果不对齐要么拆成两次突发要么把AWSIZE调小。我项目里直接约定地址必须对齐在用户侧加了一个地址检查不合规就不接受请求。WRITE_DATA拉高WVALID和WSTRB逐拍发送数据。用一个计数器w_cnt从0递增到AWLEN。当WVALID WREADY时计数器加1当计数器到达AWLEN时拉高WLAST。只能发到最后一拍并且握手成功后跳转到WRITE_RESP。写数据通道的READY信号WREADY来自从机主机这边只负责在状态机处于WRITE_DATA时把WVALID拉高。如果从机没准备好WREADY为低数据就停在总线上等这完全符合协议主轴不会超时。WRITE_RESP从机在写完数据后会拉高BVALID返回BRESP。主机的BREADY应该在建Transactor经常设成一直拉高表示随时准备接收响应。等BVALID BREADY握手成功检查BRESP是否为2b00OKAY如果是就拉高用户侧完成信号回到IDLE如果是对齐错误或者SLAVE错误就把错误状态寄存起来。这段状态机的Verilog核心逻辑大致如下localparam S_IDLE 2d0; localparam S_WRITE_ADDR 2d1; localparam S_WRITE_DATA 2d2; localparam S_WRITE_RESP 2d3; reg [1:0] wstate, wstate_next; reg [7:0] w_cnt; always (*) begin wstate_next wstate; case (wstate) S_IDLE: if (user_req_valid user_req_write) wstate_next S_WRITE_ADDR; S_WRITE_ADDR: if (AWVALID AWREADY) wstate_next S_WRITE_DATA; S_WRITE_DATA: if (WVALID WREADY WLAST) wstate_next S_WRITE_RESP; S_WRITE_RESP: if (BVALID BREADY) wstate_next S_IDLE; endcase end always (posedge ACLK or negedge ARESETN) begin if (!ARESETN) wstate S_IDLE; else wstate wstate_next; end always (posedge ACLK or negedge ARESETN) begin if (!ARESETN) w_cnt 8d0; else if (WVALID WREADY) begin if (w_cnt AWLEN) w_cnt 8d0; else w_cnt w_cnt 8d1; end end assign AWVALID (wstate S_WRITE_ADDR); assign AWADDR user_req_addr_reg; assign AWLEN user_req_len_reg; assign AWSIZE 3d3; // 8字节 assign AWBURST 2d1; // INCR assign WVALID (wstate S_WRITE_DATA); assign WDATA user_wdata_reg; assign WSTRB user_wstrb_reg; assign WLAST (wstate S_WRITE_DATA) (w_cnt AWLEN); assign BREADY 1b1;可以看到状态机很直接每一步什么时候跳转都跟协议的握手信号绑定。WLAST这个信号我一开始是直接用(w_cnt AWLEN)来赋值后来发现当AWLEN等于0时单拍突发WLAST需要在第一拍就拉高而w_cnt也是0所以条件成立没问题。但如果不注意初始化计数器在状态机进入WRITE_DATA之前有可能是任意值那就得小心了。所以我把WLAST限定在wstate S_WRITE_DATA条件下避免在无效状态输出错误。3.3 读通道状态机设计读通道的状态更少IDLE和READ_DATA。因为读地址只需要发一拍发完AR握手成功之后就可以专心等R通道的数据返回。IDLE当user_req_valid user_req_read时锁存ARADDR等控制信号拉高ARVALID。等到ARVALID ARREADY握手成功后进入READ_DATA。READ_DATA拉高RREADY接收数据。每接收一拍RVALID RREADY把RDATA送到用户侧user_rdata并拉高user_rvalid表示这一拍有效同时计数器加1。当RLAST为高且握手成功说明最后一拍到了跳回IDLE并且拉高一次user_rlast或者用独立的完成信号。这里值得多说一句的是RREADY的时序。如果主机内部有FIFO缓冲RREADY就必须跟FIFO的余量绑定——FIFO满了就不能继续拉高RREADY否则数据会丢。如果主机内部没有缓冲RREADY可以一直拉高保证最快接收。我第一次写的时候没想到这一点直接让RREADY恒为高结果后面挂了一个需要节拍的跨时钟域模块数据直接溢出了。所以RREADY的设计一定要跟主机内部的存储资源绑定。读状态机的核心代码localparam S_IDLE 1d0; localparam S_READ_DATA 1d1; reg [1:0] rstate, rstate_next; reg [7:0] r_cnt; always (*) begin rstate_next rstate; case (rstate) S_IDLE: if (user_req_valid user_req_read) rstate_next S_READ_DATA; S_READ_DATA: if (RVALID RREADY RLAST) rstate_next S_IDLE; endcase end always (posedge ACLK or negedge ARESETN) begin if (!ARESETN) begin rstate S_IDLE; r_cnt 8d0; end else begin rstate rstate_next; if (RVALID RREADY) begin if (RLAST) r_cnt 8d0; else r_cnt r_cnt 8d1; end end end assign ARVALID (rstate S_IDLE) user_req_valid user_req_read; assign ARADDR user_req_addr; assign ARLEN user_req_len; assign ARSIZE 3d3; assign ARBURST 2d1; assign RREADY (rstate S_READ_DATA); assign user_rdata RDATA; assign user_rvalid (rstate S_READ_DATA) RVALID; assign user_rlast (rstate S_READ_DATA) RVALID RREADY RLAST;注意ARVALID不能一直拉高我把它限制在IDLE状态且用户请求是读时才为高。如果用户没有请求ARVALID必须为低否则会一直向从机发地址导致总线污染。3.4 写一个简单的testbench把功能跑通RTL写完之后仿真验证是重头戏。我用Vivado自带的仿真器跑了功能仿真testbench的思路是例化一个AXI4从机模型用内存数组模拟然后让主机接口连续执行写和读操作检查读回的数据和写入的数据是否一致。简单版testbench框架module tb_axi4_master(); reg ACLK 0; reg ARESETN 0; always #5 ACLK ~ACLK; // 100MHz initial begin repeat (4) (posedge ACLK); ARESETN 1b1; end // 用户接口信号 reg user_req_valid 0; wire user_req_ready; reg user_req_write; reg [31:0] user_req_addr; reg [7:0] user_req_len; reg [63:0] user_wdata; reg [7:0] user_wstrb; wire [63:0] user_rdata; wire user_rvalid, user_rlast; // AXI4接口信号 ... // 例化DUT reg [63:0] mem [0:255]; // 简单从机模型接收AW/W写入mem接收AR从mem返回数据 initial begin (posedge ARESETN); // 写操作写入地址0x00长度为1数据64h1122334455667788 (posedge ACLK); user_req_valid 1b1; user_req_write 1b1; user_req_addr 32h0000_0000; user_req_len 8d0; user_wdata 64h1122334455667788; user_wstrb 8hFF; wait (user_req_ready); (posedge ACLK); user_req_valid 1b0; // ... 等待写完成 // 读操作读同一地址检查读回数据 end endmodule真实跑仿真的时候tbench里最需要注意的是时钟沿和握手信号的采样边沿。我在DUT里用posedge ACLK采样在testbench里也统一用(posedge ACLK)驱动这样能避免仿真竞争。如果testbench驱动信号时刻跟DUT采沿太近仿真器会出unevaluated波形看起来就是有半个时钟周期毛刺特别烦人。3.5 参数计算例子突发长度和地址对齐举个具体的例子来说明参数怎么配。假设DDR控制器的数据位宽是64位一次要搬运128字节的数据。那么在AXI4主机侧需要配成AWLEN 15因为128字节 ÷ 8字节/拍 16拍长度减1AWSIZE 38字节每拍AWBURST INCR地址递增起始地址必须8字节对齐比如0x0000_0000如果地址是0x0000_0004那就麻烦了。因为每拍8字节地址会按0x4、0xC、0x14这样递增实际跨越了非对齐边界。某些从机比如DDR控制器内部会做字节对齐处理但主机侧如果不处理很容易出现效率下降或者数据错位。我的建议是在用户请求进入主机前做一个地址对齐检查不对齐的请求要么拒绝要么自动拆分。这一点在FPGA集成中格外重要因为很多外部接口的地址并不是天然对齐的。4. 仿真和上板中的常见问题一个一个排查4.1 万恶的握手死锁一个经典排查案例我在调试一个读DDR的模块时现象是仿真跑着跑着就卡住不动了时钟还在跳但AXI4通道的状态全部不变。波形放大一看主机的ARVALID拉高了从机的ARREADY一直是0两边就这么干瞪眼。排查思路是这么走的先从从机侧看从机的ARREADY没拉高说明从机状态机没有进入可以接收地址的状态再看从机为什么没进状态发现从机内部还在等一个前一笔读操作完成的标志再往前翻发现前一笔读操作的RVALID一直没拉高因为从机的读数据FIFO为空它还没收到DDR返回的数据最后查到DDR返回路径上一个跨时钟域握手的同步器把数据卡住了原因是同步器的源端FIFO写满后读端还在等一个很久之前的数据。一整条链路查下来发现根因其实在跨时钟域逻辑AXI4这边只是表象。排查心得AXI4的问题绝大多数不是AXI4本身而是背后的数据通路堵了。遇到死锁先把五个通道当前状态全部拉出来看是哪个通道的VALID和READY同时为低且不变化锁定通道再往从机内部钻。千万不要只见树木不见森林。4.2 复位时序不对上板就异常有一个问题我在仿真里没抓到上板后折腾了好久。就是复位释放时间和AXI4时钟的关系。FPGA的全局复位常常是异步释放的如果ARESETN释放的沿离时钟上升沿太近某些寄存器采样到复位值、某些寄存器已经采样到新值整个接口就进入一个半复位状态。解决办法有两条路一是做复位同步释放把ARESETN通过两级寄存器同步到ACLK域再送给接口逻辑二是在AXI4接口内部把关键状态机在复位释放后强制回到IDLE并拉低所有VALID信号。协议本身也要求复位期间所有VALID必须为低否则从机可能看到无效请求。4.3 突发长度和WSTRB不匹配数据写歪了有一次一个同事调DMA发现内存里写入的数据总是隔几个字节错位。查了半天发现是DMA配置时给的AWSIZE写成了24字节但WSTRB一直默认全1也就是说它把64位总线上每4字节当成一个数据导致后4字节覆盖了相邻的地址空间。这种问题仿真时不容易发现因为从机模型是内存数组什么地址都能写但如果换成带物理约束的外设行为就完全不对了。经验写数据通道一定要按字节使能来构建数据。如果你要跨字节访问先把数据读到本地用组合逻辑做字节搬移再配合WSTRB一起发出去。在AXI4里WDATA和WSTRB必须严格对应从机不会帮你做任何数据位置的调整。4.4 读数据通道的FIFO下溢最常见的隐性bug主机内部如果用了FIFO来缓存读回来的数据FIFO的读写指针和RVALID/RREADY的配合特别容易出错。FIFO读端等到user_rvalid为高才弹出数据但FIFO写端呢一般在RVALID握手成功时写入。问题来了如果RVALID和RREADY没有同步好写入的时机晚了一拍FIFO里数据明明已经存在但读端拿不到或者读端提前读走了一个不完整的数据。我把FIFO写使能直接连到RVALID RREADY读取端连到user_rvalid这样从逻辑上保证写入读出对齐但是别忘了FIFO空满信号有组合逻辑延迟跨时钟域要特别小心。这一点在集成到完整SoC时是验证环境里必查的一个点。4.5 功能覆盖率和协议检查用Verilog也能做虽然SystemVerilog的断言SVA写AXI4检查很方便但有些同学还在用纯Verilog环境。这种情况下可以用简单的always (posedge ACLK)加上if条件来做运行时检查。比如// 检查当AWVALID拉高时AWLEN配置必须小于等于256 always (posedge ACLK) begin if (AWVALID AWLEN 8d255) begin $display([ERROR] AWLEN out of range); $fatal; end end这种写法和断言作用类似只是更原始一点。真正上板之前我建议把下面几类检查都加一遍所有VALID是否在复位释放后立即为低WLAST是否在最后一拍出现所有握手的VALID和READY不能同时长期为低死锁检测WSTRB是否与突发类型匹配BRESP和RRESP是否为OKAY这些检查能帮你把协议级的错误扼杀在仿真阶段。5. 进阶一点outstanding传输和效率优化如果只是把读写功能跑通面试或者项目里算及格但要应对真实的高速场景比如DDR读写带宽要跑到满你就必须用到outstanding机制。所谓outstanding就是主机不等当前请求完成就发出下一个请求。在AXI4里由于读写通道分离主机可以在上一笔读数据还没回来时就把下一笔读地址发出去这就是Address Pipelining。但做了outstanding之后通道的ID信号就派上用场了。每一笔交易都要分配一个ID从机返回数据时带上相同的ID主机根据ID来区分多个未完成的事务。如果ID分配错误两个交易的数据就张冠李戴了。我在实现多笔outstanding时用一个小的ID管理FIFO进入新交易时分配一个空闲ID数据返回时从FIFO里读出对应ID来比对确保顺序不乱。另外一个优化关键是把写地址和写数据通道解耦。顺序执行时AW通道发完地址才发数据这中间至少有一个周期的空档。如果改成AW通道和W通道并行主机在发地址的同时把第一拍数据也发出去能节省不少周期。尤其当突发长度短比如只有4拍时这个优化效果非常明显。还有个容易被忽略的点尽量让突发长度足够大。AXI4协议每一步握手都有开销如果每次只发一拍数据开销占比可能超过50%但如果一次突发16拍甚至256拍握手开销就摊薄了带宽自然上去。实践中DDR控制器一次常见突发长度是8到16再长会导致内部缓冲太大。所以突发长度要结合从机能力来定不是越大越好。6. 调完这个项目我最大的体会说句实在话用Verilog写AXI4接口本质不是在写代码是在写对协议的理解。你把握手规则、突发参数、通道顺序这些细节抠透了写出来的状态机自己会跑得很顺如果只是照搬网上的模板代码出了问题会完全无法下手。尤其是处理WSTRB、WLAST、RREADY这些看起来不起眼的信号时一个没注意上板就是一堆奇怪的现象。我给后面做这事的同学几个建议动手前先把ARM的AMBA AXI4协议规范通读两遍注意是详细读不是翻目录。仿真里把$display或波形里的五个通道全部打开特别是VALID和READY的边沿养成看时序图的习惯。先用最简单的单笔读写跑通再逐步加突发、加深outstanding不要一口吃个胖子。上板调试时用逻辑分析仪抓AXI4接口如果抓不了内部信号就在RTL里加计数器比如记录握手完成次数到时候数一下就知道哪里卡住了。真调完一个AXI4接口你对整个SoC总线怎么工作、DMA怎么访问内存、CPU和DDR控制器之间到底发生了什么都会有一个特别通透的认识。这也是为什么那么多IC公司和FPGA岗位面试都爱问AXI4——它不只是一个协议更是一把认识片上系统的钥匙。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门