资讯动态

AXI总线协议实战:FPGA握手、AXI4-Lite从机与DMA带宽

发布时间:2026/9/18 10:23:30 来源:尧图企业网站定制
前面十篇里我们把分频器、状态机、FIFO、串口、SPI 这些东西一个个手搓了一遍单模块跑起来都没什么问题。可只要把两个自己写的模块接在一起麻烦就来了A 模块的data_valid是高位有效B 模块默认低位有效A 模块觉得数据可以保持两拍B 模块却只在一拍里采样A 模块想连续发三个数B 模块一次只能收一个。这种摩擦在两三个模块的小项目里还能靠注释和口头约定混过去一旦模块数量上到五六个或者对接的对象换成别人写的 IP、换成处理器、换成 DDR 控制器靠约定就彻底撑不住了。FPGA 开发里的 AXI 总线协议本质上就是把这些口头约定写成一份强制合同谁先举手、谁等谁、数据能连着发几拍、发错了怎么回话全都写死。这篇就聊聊 AXI 到底规定了什么以及怎么在自己的工程里把它用起来。1. 从两根握手线说起AXI 出现的那个不得不1.1 手搓 valid/ready 接口的自由与代价大部分人的第一个自定义接口长得都差不多一根valid一根ready外加几根数据线。发送方把数据摆好valid拉高接收方准备好了ready拉高同一个时钟沿两根线同时为高这一拍数据就算交付完成。这套机制的优点是极其灵活你可以根据自己模块的节奏任意延长valid或ready不用改任何时序参数。代价在于它没有回答一堆细节问题。数据交付后发送方要不要继续保持接收方能不能在ready已经拉高的情况下突然撤掉valid拉高但ready一直不来发送方能不能反悔如果一次要传 100 个字是每传一个字握一次手还是握一次手传完一批这些问题在不同的模块作者手里会有不同的答案而这些答案互相冲突的时候问题就来了——你会在仿真波形里看到两根线互相盯着对方谁都不肯先动整个系统就那么僵在那里。1.2 AXI 管的是接口时序不管你的内部实现这一点必须先说清楚否则很容易把 AXI 想得太玄。AXI 规范约束的是接口上天线的行为和时序关系它不管你这个模块里面是用状态机、用 FIFO 还是用组合逻辑糊出来的。你的从机内部可以是一个 32 位的寄存器文件也可以是一个挂在 DDR 上的 DMA 引擎对外只要遵守同一套握手规则主机那一侧就完全不需要知道区别。这也是 AXI 真正的价值所在让接口和功能解耦。一个 AXI4-Lite 主设备可以今天访问你的自定义寄存器明天换成访问一个第三方 IP 的寄存器主机侧的代码一行都不用改。反过来你写的 AXI 从机也可以从 Zynq 的 PS 侧访问也可以从 MicroBlaze 访问甚至可以换成仿真平台里的 AXI VIP 来访问从机侧一样不用改。提示很多人学 AXI 卡在背信号名上。信号名其实不用背Vivado 的 IP 封装向导会自动帮你生成 90% 的框架代码。真正需要理解的是握手规则和突发行为这两块弄明白了剩下的是查表工作。2. AXI4 家族三兄弟Full、Lite、Stream 该选哪个2.1 AXI4-Lite只服务于寄存器读写的那把小刀AXI4-Lite 是 AXI4 的简化子集砍掉了突发、砍掉了 ID 乱序、砍掉了 QoS 和 Region数据宽度固定一般 32 位或 64 位每次传输就是一拍。它唯一的用途就是寄存器访问配置一个模块的使能位、读一个模块的状态位、改一个分频系数这类场景的特征是访问不频繁、每次只动一两个字、但对延迟不敏感。用 AXI4-Lite 的好处是实现成本极低。一个从机需要处理的就是五个通道各一根valid/ready加上地址数据线一个两状态的状态机就能写完。麻烦在于它的效率天花板很低一次读操作至少要经历地址握手和数据握手两轮加上从机内部取数的逻辑延迟实际几拍才能完成一次访问。拿它去搬图像、搬音频这种连续数据带宽会低到让你怀疑人生。2.2 AXI4 全功能版本突发、乱序、ID 是给谁准备的完整版 AXI4 最核心的三个增项是突发传输、事务 ID和多事务在途。突发传输意味着一握手就能连续搬运最多 256 拍数据地址只报一次后续地址按规则递推。事务 ID 是用来标记这次传输属于哪个主人从机可以打乱顺序返回数据主机靠 ID 把回来的数据认领回各自的通道。多事务在途则是允许主机不等前一次完成就发起下一次把往返延迟藏起来。这三样东西加起来才让 AXI4 有能力跑满 DDR 的带宽。代价是复杂度陡增从机要维护 ID 队列要处理乱序回包要保证同一个 ID 的事务之间不能乱序主机要管理在途事务的数量防止把从机的缓冲撑爆。我个人的经验是如果带宽需求超过 200 MB/s基本就必须上 AXI4 全功能版本AXI4-Lite 是撑不住的。2.3 AXI4-Stream不谈地址只谈数据流AXI4-Stream 是另一条路线。它彻底去掉了地址通道只有一根数据流配几个边带信号TLAST标记一帧数据的结尾TKEEP和TSTRB标记哪些字节有效TUSER留给用户自己定义用途比如帧起始、错误标志、通道号。它的模型非常接近 FIFO数据一拍拍地流TVALID/TREADY握手就这么简单。图像处理链路、音频链路、ADC 采样数据回传这些都是 AXI4-Stream 的典型地盘。数据从传感器进来、经过滤波、经过缩放、最后塞进 DMA 写进内存整条链路上没有一次地址访问全是流水线。它的效率可以做到接近 100%因为没有地址开销。类型数据宽度突发能力是否有地址典型用途实现难度AXI4-Lite32/64 bit无单拍有寄存器配置与状态读取低AXI4 Full32~1024 bit1~256 拍有内存搬运、DDR 访问、大块数据高AXI4-Stream任意无限靠 TLAST 分帧无图像、音频、采样流、滤波器链路中实际工程里最常见的组合是这样的控制通路走 AXI4-Lite数据通路走 AXI4-Stream 或者 AXI4 Full。处理器通过 Lite 把你的模块使能、配好参数然后模块自己通过 Stream 或者 DMA 把数据搬来搬去。想清楚这条分工线选型问题基本就解决了一大半。3. 五个通道的握手细节VALID 和 READY 谁等谁3.1 五通道各自的职责写操作被拆成三条通道读操作被拆成两条通道这是 AXI 最反直觉的地方——它把地址和数据分开了而且写操作的数据通道和地址通道之间没有强制先后顺序。通道方向关键信号职责AW写地址主机→从机AWADDR、AWLEN、AWSIZE、AWBURST、AWVALID、AWREADY告诉从机要往哪写、写多少W写数据主机→从机WDATA、WSTRB、WLAST、WVALID、WREADY实际的数据流B写响应从机→主机BRESP、BVALID、BREADY从机回话写成功了还是出错了AR读地址主机→从机ARADDR、ARLEN、ARSIZE、ARVALID、ARREADY告诉从机要读哪、读多少R读数据从机→主机RDATA、RRESP、RLAST、RVALID、RREADY实际的数据返回写操作之所以要三条通道是为了让地址和数据可以并行推进。主机完全可以先把地址发出去然后隔几拍再把数据送上来也可以反过来先把数据备好放在 W 通道上等着再发地址。从机必须能容忍这两种顺序。W 通道的数据可以先于 AW 通道的地址到达这一点在写从机的时候必须处理忘了处理就会出现地址还没到数据已经走了的丢包现象。3.2 三条铁律以及死锁是怎么来的AXI 规范里关于握手有几条硬性规定背下来比什么都值VALID 不能等 READY发送方一旦把数据准备好、VALID 拉高就不能再去看对方 READY 是什么状态来决定自己拉不拉 VALID。VALID 拉高后必须保持直到对方 READY 拉高完成握手之前VALID 不能撤回数据也不能变。READY 可以等 VALID接收方可以悠闲地等 VALID 来了再拉 READY但一旦 READY 拉高在握手完成前也不能撤回。死锁的经典成因就是两边都违反了第 1 条。主机想我先看看从机 READY 高不高高了再发 VALID从机想我先看看主机 VALID 高不高高了再拉 READY两条线大眼瞪小眼永远停在零。更隐蔽的一种是部分通道死锁主机在等 B 通道的响应从机在等 W 通道的最后一个数据而主机觉得自己数据早就发完了——这种时候通常是因为WLAST没拉对从机还在傻等那最后一拍。3.3 AWLEN、WLAST、RLAST 三个东西必须对上突发传输里主机通过AWLEN告诉从机这次写几拍。这里有个特别容易记错的地方AWLEN 编码的是拍数减一AWLEN 8h00表示 1 拍AWLEN 8h0F表示 16 拍最大8hFF表示 256 拍。数据通道上主机会在每一拍的WVALID上推进数据并且在最后一拍同时把WLAST拉高。从机必须做的事是收到AWLEN后自己数拍核对 W 通道上的数据拍数是否一致同时检查WLAST的位置。规范里说从机应该以WLAST为准来判定突发结束但工程实践中WLAST 和 AWLEN 不一致的情况是一定会出现的尤其自己写的测试平台从机最好是两者都检查一旦发现不一致直接返回一个错误响应别硬着头皮往下走。另一个坑是 4KB 边界。AXI 规定一次突发不能跨越 4KB 的地址边界因为从机的地址译码粒度通常是 4KB 页。你在写主机的时候如果图省事一口气发个 256 拍不等长的突发地址恰好压在 4KB 边界上就可能被协议检查器抓出来。仿真里没有协议检查器的话这个错误往往要到上板之后才会以数据莫名错位的形式暴露出来。4. 手写一个 AXI4-Lite 从机寄存器文件的最小可用实现4.1 先把寄存器映射定下来在动手写 Verilog 之前先画一张寄存器表这是省时间的做法。地址空间开 64 字节用地址的低 6 位其中[5:2]用来选寄存器一个寄存器 4 字节一共 16 个。偏移名称属性位定义0x00CTRLRWbit0 使能bit1 软复位bit2 中断使能0x04DIVRW分频系数复位值 0xFF0x08STATUSRObit0 busybit1 donebit2 err0x0CVERSIONRO返回固定的版本号用来做连通性测试这张表决定了从机内部的地址译码逻辑也决定了将来软件侧怎么写驱动。建议在任何模块里都放一个 VERSION 寄存器读写一次就能确认地址译码和握手逻辑是不是活的比拿逻辑分析仪猜半天强得多。4.2 写通道状态机与 WSTRB 字节选通写通道我习惯用三段式状态机等地址和数据、收数据写寄存器、回响应。关键点是地址和数据都到齐了才一起收货这样可以少维护一组中间状态。module axi_lite_regfile #( parameter integer C_S_AXI_ADDR_WIDTH 6, parameter integer C_S_AXI_DATA_WIDTH 32 )( input wire s_axi_aclk, input wire s_axi_aresetn, input wire [C_S_AXI_ADDR_WIDTH-1:0] s_axi_awaddr, input wire s_axi_awvalid, output reg s_axi_awready, input wire [C_S_AXI_DATA_WIDTH-1:0] s_axi_wdata, input wire [C_S_AXI_DATA_WIDTH/8-1:0] s_axi_wstrb, input wire s_axi_wvalid, output reg s_axi_wready, output reg [1:0] s_axi_bresp, output reg s_axi_bvalid, input wire s_axi_bready, input wire [C_S_AXI_ADDR_WIDTH-1:0] s_axi_araddr, input wire s_axi_arvalid, output reg s_axi_arready, output reg [C_S_AXI_DATA_WIDTH-1:0] s_axi_rdata, output reg [1:0] s_axi_rresp, output reg s_axi_rvalid, input wire s_axi_rready, output wire [31:0] reg_ctrl, output wire [31:0] reg_div, input wire [31:0] reg_status ); localparam [1:0] W_IDLE 2d0, W_DATA 2d1, W_RESP 2d2; reg [1:0] wstate; reg [C_S_AXI_ADDR_WIDTH-1:0] awaddr_lat; reg [31:0] reg_ctrl_r, reg_div_r; integer i; assign reg_ctrl reg_ctrl_r; assign reg_div reg_div_r; always (posedge s_axi_aclk) begin if (!s_axi_aresetn) begin wstate W_IDLE; s_axi_awready 1b0; s_axi_wready 1b0; s_axi_bvalid 1b0; s_axi_bresp 2b00; awaddr_lat {C_S_AXI_ADDR_WIDTH{1b0}}; reg_ctrl_r 32h0000_0000; reg_div_r 32h0000_00FF; end else begin case (wstate) W_IDLE: begin // 地址与数据同时到齐才握手避免只收一半的中间态 if (s_axi_awvalid s_axi_wvalid) begin s_axi_awready 1b1; s_axi_wready 1b1; awaddr_lat s_axi_awaddr; wstate W_DATA; end end W_DATA: begin s_axi_awready 1b0; s_axi_wready 1b0; // 按 WSTRB 做读改写支持字节粒度的窄写 for (i 0; i 4; i i 1) begin if (s_axi_wstrb[i]) begin case (awaddr_lat[5:2]) 4h0: reg_ctrl_r[8*i : 8] s_axi_wdata[8*i : 8]; 4h1: reg_div_r [8*i : 8] s_axi_wdata[8*i : 8]; default: ; // 只读或未定义寄存器直接丢弃写入 endcase end end s_axi_bresp 2b00; // OKAY s_axi_bvalid 1b1; wstate W_RESP; end W_RESP: begin if (s_axi_bvalid s_axi_bready) begin s_axi_bvalid 1b0; wstate W_IDLE; end end default: wstate W_IDLE; endcase end endWSTRB这段值得单独说。它每一位对应数据里的一个字节为 1 表示这个字节要写进去。软件侧用 8 位指针去写一个 32 位寄存器的时候编译器生成的往往就是一次带WSTRB 4b0001的窄写。如果你的从机无视WSTRB直接把 32 位数据整体覆盖下去那 CTRL 寄存器里其他几个位就会被莫名其妙地清掉——这类 bug 特别难查因为从软件角度看代码逻辑完全正常。4.3 读通道实现与 RVALID 的时序读通道简单一些地址握手完成后产生一拍数据。ARREADY我通常只维持一个时钟避免主机误以为还能再发一次地址。reg [31:0] rdata_mux; always (*) begin case (s_axi_araddr[5:2]) 4h0: rdata_mux reg_ctrl_r; 4h1: rdata_mux reg_div_r; 4h2: rdata_mux reg_status; 4h3: rdata_mux 32h0001_0011; default: rdata_mux 32hDEAD_BEEF; endcase end always (posedge s_axi_aclk) begin if (!s_axi_aresetn) begin s_axi_arready 1b0; s_axi_rvalid 1b0; s_axi_rdata 32h0000_0000; s_axi_rresp 2b00; end else begin if (s_axi_arready s_axi_arvalid) begin s_axi_arready 1b0; end else if (!s_axi_rvalid s_axi_arvalid) begin s_axi_arready 1b1; end if (s_axi_arready s_axi_arvalid) begin s_axi_rdata rdata_mux; s_axi_rresp 2b00; s_axi_rvalid 1b1; end else if (s_axi_rvalid s_axi_rready) begin s_axi_rvalid 1b0; end end end endmodule注意上面这段代码是教学级实现跑通理解握手时序完全够用但缺了非法地址的 SLVERR 响应、缺了 AR 缓冲深度也没有做背压处理。真要进产品建议用 Vivado 里的 Create and Package New IP 向导选 AXI4 Peripheral让它生成框架再用你的逻辑填进去——它生成的代码把边界情况都处理过了能省掉大量调试时间。5. 挂到系统上控制走 Lite数据走 Stream 加 DMA5.1 Zynq 的 GP、HP、ACP 三个口各管什么如果你用的是带硬核处理器的器件PS 侧会给你几个 AXI 主端口。GP 口General Purpose是给低速控制用的挂 AXI4-Lite 从机正合适带宽不需要太大。HP 口High Performance是给大批量数据进出 DDR 用的位宽宽、支持突发、支持多事务在途图像和采样数据就走这里。ACP 口偏向低延迟的一致性访问适合对实时性敏感的加速器。选错了端口会很难受。我见过有人把图像数据流挂在 GP 口上结果帧率死活上不去查了半天以为是算法慢其实是 GP 口的带宽限制卡死了。做方案设计的时候先算一遍带宽需求再决定挂哪个口这比事后优化省事得多。5.2 AXI DMA 的配置顺序与最后一笔陷阱数据通路我一般用 AXI DMA 把 Stream 和内存连起来。它有两个方向MM2S 是内存到流S2MM 是流到内存每个方向各有自己的控制寄存器和状态寄存器。配置流程大致是先把目标地址写进地址寄存器然后把传输长度写进长度寄存器。这里有个必须记住的细节长度寄存器是启动寄存器写下去的那一刻传输就开始跑。所以配置顺序不能乱地址必须先写长度必须最后写。我刚开始用的时候反着写了一次结果是拿上一次残留的地址搬了一段数据看起来现象特别诡异最后靠读 DMASR 的状态位才定位到。传输完成后DMA 会把状态寄存器里的 Idle 位置起来。软件侧轮询这个位是最省事的做法。如果你用中断记得把 IDMA 的中断使能位打开并且在中断服务程序里清掉状态位否则中断会一直重复触发。5.3 缓存一致性PL 写完的数据CPU 读到的还是旧值这个坑几乎每个人都会踩一次。PS 侧的处理器读 DDR 是带缓存的PL 通过 HP 口往同一块地址写数据处理器那边完全不知道读的时候拿到的还是缓存里的旧内容。解决办法是在软件里手动做缓存维护DMA 准备写内存之前把对应地址范围 invalidate 掉DMA 写完、CPU 要读之前再 invalidate 一次CPU 写完数据要交给 PL 读之前先 flush 一次。这类函数在开发板厂商提供的 BSP 里都有名字类似Xil_DCacheFlushRange和Xil_DCacheInvalidateRange。注意地址范围要按缓存行对齐传个非对齐的起始地址进去行为是未定义的可能把相邻数据也刷掉。我一般会把缓冲区用aligned属性声明或者在分配时直接按 64 字节对齐省得每次算。5.4 ILA 抓不到波形的时候该往哪看Vivado 的 Block Design 里可以给 AXI 接口勾选 Debug它会自动插入 ILA把 AW、W、B、AR、R 五组信号一起抓出来这个功能非常值钱。但经常有人勾了之后发现波形是空的原因通常有三个一是触发条件设得太严AXI 上第一次事务可能要等很久建议先用AWVALID AWREADY这种任意握手条件当触发二是采样深度不够事务还没完缓冲就满了三是时钟域选错了接的是 AXI 的时钟但配的采样时钟是别的源。还有一个更省心的办法是插入 AXI Protocol Checker。它专门盯着协议违规一旦发现 VALID 撤回、WLAST 位置不对、突发跨越 4KB 边界这类问题会在仿真里直接报出具体出错时刻和通道。写从机的阶段把这个 IP 挂上比事后对着波形一行行看高效太多。6. 仿真能过、上板就挂AXI 典型坑位与排查链路6.1 从没有波形到握手死锁的排查顺序遇到 AXI 不工作我一般按这个顺序走基本不会绕远路先看时钟和复位aclk有没有在跑aresetn是不是真的拉高了。有相当一部分AXI 卡死其实复位就没放出来或者复位放开的时刻和事务发起撞在一起。看任意一个通道有没有握手成功只要 AW、W、AR 里任何一组VALID和READY同时为高过说明时钟和复位没问题问题出在协议逻辑上。数 VALID 持续了多少拍如果某个VALID拉高后一直不落且对应的READY始终为低那就是对端没响应往对端的实现里查。检查 B 和 R 通道写操作如果 B 通道一直没响应从机大概率在等 W 通道的数据去数WLAST到底来没来。最后才用协议检查器核对细节突发长度、字节选通、ID 位宽这些。6.2 复位信号、打拍与亚稳态这几件老事AXI 的复位约定是低有效命名上习惯叫aresetn。规范要求复位是同步的但工程里外部来的复位往往是异步的直接往 AXI 接口上怼很容易在复位释放的边沿产生亚稳态表现为上板后偶发性的第一次访问失败。稳妥做法是加一个复位同步器异步复位、同步释放内部用两级触发器打拍。module rst_sync ( input wire clk, input wire arst_n, output wire srst_n ); reg [1:0] sync_ff; always (posedge clk or negedge arst_n) begin if (!arst_n) sync_ff 2b00; else sync_ff {sync_ff[0], 1b1}; end assign srst_n sync_ff[1]; endmodule跨时钟域也是同样的道理。AXI 从机如果和主机不在同一个时钟域不要自己用两级触发器去同步五条通道的信号五条通道之间有严格的顺序关系逐个打拍会把顺序打乱。正确做法是用 AXI Clock Converter 或者带时钟转换的 SmartConnect让工具去处理握手跨域。注意复位期间要保证所有 VALID 都是低的。有些从机在复位放开的那一拍就把 BVALID 拉高了主机一脸懵这种问题在仿真里看不出来仿真里复位通常是理想边沿上板才会偶发。6.3 写一个靠谱的仿真主机 BFM自己写测试平台的时候别指望手写几个valid拉高拉低就能把协议覆盖全。至少写两个任务axi_write和axi_read把握手过程封装起来。task axi_write(input [5:0] addr, input [31:0] data); begin (posedge clk); awaddr addr; awvalid 1b1; wdata data; wstrb 4hF; wvalid 1b1; wait (awready wready); (posedge clk); awvalid 1b0; wvalid 1b0; wait (bvalid); bready 1b1; (posedge clk); bready 1b0; end endtask这个任务虽然简单但已经把地址与数据同时发、等两个 READY 都到齐、再收 B 响应这条正确路径走通了。进阶一点可以再写一个故意把地址延后几拍发的版本专门测试从机对数据先到、地址后到的处理能力——这个测试用例非常值得加因为真实主机尤其是处理器确实会这么干。如果不想自己写综合工具自带的 AXI Verification IP 里有一批现成的读写任务覆盖了突发、窄传输、乱序等场景把它挂到你的从机上跑一遍回归能省下大量手写用例的时间。7. 带宽账本为什么 AXI4-Lite 永远跑不出高带宽7.1 拿具体数字算一遍就明白了假设时钟 150 MHz、数据位宽 128 位16 字节理论峰值带宽是 150M × 16B 2.4 GB/s。这是所有讨论的基准线。AXI4-Lite 的一次读操作从 AR 握手到 R 握手至少占两拍加上从机内部组合逻辑路径实际往往要 3~5 拍才能完成一次 32 位传送。按 4 拍算有效带宽是 150M ÷ 4 × 4B ≈ 150 MB/s只有峰值的 6% 左右。这个数字不是危言耸听是实测很容易复现的。换成 AXI4 突发一次 64 拍的 INCR 突发地址握手占 2 拍数据 64 拍流水推进效率能到 95% 以上带宽就是 150M × 16B × 0.95 ≈ 2.28 GB/s。差了一个数量级还多。这就是为什么控制通路可以用 Lite数据通路绝不能用 Lite 的原因。7.2 想再往上抠带宽看这三个旋钮第一个旋钮是数据位宽。把 32 位换成 128 位甚至 256 位理论带宽直接翻几倍这是最直接的。但要注意从机内部的 FIFO 和状态机也得跟得上位宽上去但内部还是单拍处理等于白搭。第二个旋钮是突发长度。256 拍的长突发比 16 拍的短突发效率高很多因为地址开销被摊薄了。但突发不能跨 4KB 边界也不能超过从机缓冲能力得根据实际情况折中。我一般先按 64 或 128 拍试跑得稳再往上加。第三个旋钮是在途事务数量。单次事务从发地址到收数据有往返延迟如果主机一次只发一个事务这段时间总线是闲着等。允许多个事务同时在途outstanding把延迟藏起来链路利用率能明显提升。AXI4 用 ID 来区分不同事务从机要保证同一 ID 的事务之间保序不同 ID 之间可以乱序返回。配置时钟位宽单次突发长度估算有效带宽AXI4-Lite 单拍150 MHz32 bit1约 150 MB/sAXI4 短突发150 MHz128 bit16 拍约 1.8 GB/sAXI4 长突发150 MHz128 bit64 拍约 2.28 GB/sAXI4 长突发 4 在途150 MHz128 bit64 拍接近峰值受限于 DDR最后提一句常被忽略的事带宽瓶颈往往不在 AXI 本身而在 DDR 控制器或者你的从机内部。我曾经花了两天优化 AXI 配置最后发现是内部 FIFO 深度只有 16一拍没搬完就满了背压一路顶回主机。所以调带宽的时候先用 ILA 看看各段的READY是不是经常为低哪一段先堵问题就在哪。我个人在带新手做 AXI 的过程中最常重复的一句话是先把 AXI4-Lite 的从机手写一遍哪怕写得丑、写得慢也要自己完整走一遍五通道握手等你把 Lite 写通了再去碰 Full 和 Stream会发现那些看起来复杂的机制其实都是在解决同一个问题的不同侧面。另外有个小习惯我一直保持每写一个 AXI 模块都先放一个 VERSION 寄存器读一次确认地址译码活着再往下调试。这个动作花不了两分钟但省下来的时间往往是按小时算的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价