3. AXI4总线协议详解:AXI4-Full/AXI4-Lite/AXI4-Stream区别、握手信号与时序、实战:自定义AXI4-Lite IP

大家好,我是你们的FPGA讲师。今天我们来聊聊Zynq开发中绕不开的一个话题——AXI4总线协议。

说实话,我刚接触Zynq那会儿,看到AXI这三个字母就头大。什么Full、Lite、Stream,还有那一堆握手信号,简直像天书。但后来做项目多了,慢慢就摸清了门道。今天我就把这些经验掰开揉碎了讲给你听。

3.1 为什么是AXI4?

AXI4是ARM公司推出的AMBA总线家族中的一员。Zynq之所以选它,说白了就是因为它够快、够灵活。

你想想看,Zynq内部有ARM处理器(PS)和FPGA逻辑(PL)两部分。这两部分要通信,总得有个标准接口吧?AXI4就是这个桥梁。

我个人习惯把AXI4比作高速公路:

  • AXI4-Full:就像八车道高速,能跑大卡车(突发传输)
  • AXI4-Lite:像乡间小路,只能跑小轿车(单次传输)
  • AXI4-Stream:像传送带,只管往前送数据,不问地址

嗯,这个比喻虽然糙了点,但道理是通的。

3.2 三种协议的区别

我们先看一张对比表,这样更直观:

特性 AXI4-Full AXI4-Lite AXI4-Stream
地址/数据通道 分离的地址和数据通道 分离的地址和数据通道 只有数据通道
突发传输 支持(最多256拍) 不支持(单次传输) 支持(无限拍)
数据总线宽度 8/16/32/64/128/256/512/1024位 32位固定 任意宽度
典型应用 DDR控制器、DMA传输 寄存器配置、状态读取 视频流、音频流、高速数据
握手信号 VALID/READY + 额外控制 VALID/READY(简化版) VALID/READY + TLAST

我在项目中遇到过这样一个坑:一开始用AXI4-Full去配置一个简单的控制寄存器,结果发现资源占用特别大。后来换成AXI4-Lite,问题就解决了。所以选对协议很重要。

3.3 握手信号与时序

三种协议都基于同一个握手机制——VALID/READY。这个机制其实很简单:

  • VALID:发送方说「我数据准备好了,你拿走吧」
  • READY:接收方说「我准备好了,你发过来吧」

数据只有在VALID和READY同时为高的时候才传输。这就是所谓的「握手成功」。

我画了一张时序图,帮你理解这个过程:

CLK T0 T1 T2 T3 T4 T5 VALID READY DATA 有效数据 有效数据 握手成功 握手成功 等待

你看,在T2时刻,VALID和READY同时为高,数据就传过去了。T3时刻READY变低了,虽然VALID还高着,但数据传不过去,只能等。这就是握手的基本规则。

重要提醒:VALID信号一旦拉高,就不能在READY拉高之前变低。这是协议的死规定,违反了会出大问题。

3.4 AXI4-Lite详解

AXI4-Lite是三种协议里最简单的。它不支持突发传输,每次只能读写一个地址。但正因为简单,它特别适合用来配置寄存器。

AXI4-Lite有5个独立通道:

  1. 写地址通道(AW)—— 告诉从设备我要写哪个地址
  2. 写数据通道(W)—— 把数据送过去
  3. 写响应通道(B)—— 从设备告诉你写完了,成功还是失败
  4. 读地址通道(AR)—— 告诉从设备我要读哪个地址
  5. 读数据通道(R)—— 从设备把数据返回给你

每个通道都有自己的VALID/READY握手信号。写操作需要三个通道配合,读操作需要两个通道配合。

我的小技巧:调试AXI4-Lite时,我习惯先看写操作。因为写操作有响应通道,能直接告诉你有没有写成功。读操作如果出问题,往往数据是0,不容易定位。

3.5 实战:自定义AXI4-Lite IP

光说不练假把式。我们来做一个实际的AXI4-Lite IP,实现一个简单的PWM控制器。

这个IP有4个寄存器:

地址偏移 寄存器名称 功能
0x00 CTRL 控制寄存器(使能、极性等)
0x04 PERIOD 周期寄存器(PWM周期)
0x08 DUTY 占空比寄存器
0x0C STATUS 状态寄存器(只读)

下面是我写的Verilog代码核心部分:

// AXI4-Lite 写操作处理
always @(posedge S_AXI_ACLK) begin
    if (~S_AXI_ARESETN) begin
        ctrl_reg  <= 32'h0;
        period_reg <= 32'd1000;
        duty_reg   <= 32'd500;
    end else begin
        // 写地址通道握手成功
        if (aw_valid && aw_ready) begin
            aw_addr_reg <= S_AXI_AWADDR;
        end
        
        // 写数据通道握手成功
        if (w_valid && w_ready) begin
            case (aw_addr_reg[3:0])
                4'h0: ctrl_reg  <= S_AXI_WDATA;
                4'h4: period_reg <= S_AXI_WDATA;
                4'h8: duty_reg   <= S_AXI_WDATA;
                default: ; // 其他地址忽略
            endcase
        end
    end
end

// 写响应通道
assign b_valid = w_valid && w_ready;
assign b_resp  = 2'b00;  // OKAY响应
assign b_ready = S_AXI_BREADY;

// AXI4-Lite 读操作处理
always @(*) begin
    case (ar_addr_reg[3:0])
        4'h0: rdata = ctrl_reg;
        4'h4: rdata = period_reg;
        4'h8: rdata = duty_reg;
        4'hC: rdata = status_reg;
        default: rdata = 32'h0;
    endcase
end
我曾经踩过的坑:写AXI4-Lite从设备时,最容易犯的错误是忘记处理写响应通道(B通道)。如果你不返回b_valid,主设备就会一直等,整个总线就卡死了。所以一定要记得:每次写操作完成后,都要给出b_valid信号。

这个IP在Vivado中封装成AXI4-Lite外设后,就可以在PS端用C代码来控制了:

// PS端控制代码
#define PWM_BASEADDR 0x43C00000

// 设置PWM周期为1000
Xil_Out32(PWM_BASEADDR + 0x04, 1000);

// 设置占空比为300(30%)
Xil_Out32(PWM_BASEADDR + 0x08, 300);

// 使能PWM输出
Xil_Out32(PWM_BASEADDR + 0x00, 0x01);

// 读取状态
u32 status = Xil_In32(PWM_BASEADDR + 0x0C);

你看,通过AXI4-Lite总线,PS和PL之间的通信就这么简单。PS写寄存器,PL就能产生PWM波形。这就是Zynq的魅力所在。

3.6 小结

AXI4协议其实没那么可怕。你只要记住三点:

  • 三种协议各有用途,选对协议比写好代码更重要
  • 握手信号的核心就是VALID/READY,两者同时为高才传输
  • AXI4-Lite最适合做寄存器配置,简单可靠

嗯,今天就讲到这里。记住,多动手写代码,多跑仿真,慢慢就熟练了。

可可精品资料库