第一次接触AXI_FULL协议时,我被它那密密麻麻的信号线吓到了——光是写通道就有AWADDR、AWLEN、AWBURST等十几组信号。但当我真正用Verilog实现了一个主机接口后,才发现这套协议的设计其实非常精妙。AXI_FULL作为AMBA总线家族中的高性能成员,特别适合FPGA与处理器之间的数据交互场景,比如视频流处理、高速AD采集等需要突发传输的场合。
与AXI-Lite相比,AXI_FULL最显著的特征就是支持突发传输(Burst)。想象你搬家时,如果每次只能搬一件家具(类似AXI-Lite的单次传输),效率会非常低;而AXI_FULL允许你告诉搬家公司"从某个地址开始,连续搬16件家具",这就是突发传输的核心思想。在实际项目中,我用突发传输将DDR3的读写带宽利用率提升了近3倍。
协议中的五个独立通道(写地址、写数据、写响应、读地址、读数据)采用握手机制,每个通道都有自己的VALID/READY信号。这种分离设计让读写操作可以并行进行——就像在餐厅里,服务员可以同时处理多桌客人的点单(读请求)和上菜(写数据)。我在实现DMA控制器时,就利用这个特性实现了读写通道的全双工操作。
写地址通道的握手就像快递员取件时的对话:
verilog复制// 写地址通道示例
always @(posedge ACLK) begin
if (!ARESETN) begin
AWVALID <= 0;
AWADDR <= 0;
end else if (state == WRITE_START && !AWVALID) begin
AWADDR <= BASE_ADDR; // 设置起始地址
AWVALID <= 1; // 触发地址有效
end else if (AWREADY && AWVALID) begin
AWVALID <= 0; // 握手完成
end
end
突发长度AWLEN的设置有个易错点:协议规定实际传输次数=AWLEN+1。比如要传输16个数据,需要设置AWLEN=15。我有次调试时设成16,结果从机收到了17个数据,导致地址越界。AWBURST模式的选择也很关键:
写数据通道的WLAST信号就像快递包裹上的"最后一件"标记。在Verilog实现时,我通常用计数器来生成这个信号:
verilog复制reg [3:0] wdata_count;
always @(posedge ACLK) begin
if (WREADY && WVALID) begin
wdata_count <= wdata_count + 1;
WLAST <= (wdata_count == BURST_LEN-2);
end
end
WSTRB信号(字节使能)在实际项目中特别有用。比如处理32位数据时,若只需要更新低16位,可以设置WSTRB=4'b0011。有次我调试图像处理IP时,就靠这个功能实现了局部像素更新,避免了不必要的数据搬运。
我的AXI主机通常采用三段式状态机:
verilog复制localparam [2:0]
IDLE = 3'b000,
WRITE = 3'b001,
READ = 3'b010;
always @(posedge ACLK) begin
if (!ARESETN) begin
state <= IDLE;
end else begin
case(state)
IDLE: if (start_write) state <= WRITE;
WRITE: if (bvalid && bready) state <= READ;
READ: if (rlast && rvalid) state <= IDLE;
endcase
end
end
调试时最常见的坑是状态切换条件没处理好。比如有次我忘了检查BVALID/BREADY握手就跳转到读状态,导致写响应丢失。现在我会在状态机中加入超时保护:
verilog复制reg [15:0] timeout;
always @(posedge ACLK) begin
if (state != next_state)
timeout <= 0;
else if (timeout < 16'hFFFF)
timeout <= timeout + 1;
if (timeout > 16'hFFFE)
state <= IDLE; // 自动复位
end
非对齐访问是另一个容易出问题的地方。假设要传输0x1003开始的16字节数据(4字节位宽):
我的经验是提前计算好突发边界,用如下代码处理:
verilog复制wire [31:0] end_addr = AWADDR + (AWLEN+1)*(1<<AWSIZE);
if (end_addr[11:0] == 0)
// 刚好对齐4KB边界
else if (end_addr[11:0] < AWADDR[11:0])
// 跨越边界,需要拆分突发
我用SystemVerilog写的测试平台通常包含:
systemverilog复制task automatic send_burst;
input [31:0] addr;
input [7:0] len;
// 随机化等待周期
cfg.clk_random_delay();
// 发送地址
master.send_addr(addr, len);
// 发送数据
foreach(data[i]) begin
cfg.clk_random_delay();
master.send_data(data[i], (i==len-1));
end
endtask
关键检查点包括:
通过实际项目总结的优化经验:
verilog复制// 提前发送下一个突发地址
if (AWREADY && AWVALID && (wdata_count > BURST_LEN-4))
next_addr <= AWADDR + (BURST_LEN<<AWSIZE);
verilog复制// 维护未完成事务计数器
always @(posedge ACLK) begin
if (ARVALID && ARREADY)
outstanding_cnt <= outstanding_cnt + 1;
if (RVALID && RLAST && RREADY)
outstanding_cnt <= outstanding_cnt - 1;
end
verilog复制case (target_width)
64: begin
WDATA[31:0] <= src_data;
WSTRB[3:0] <= 4'b1111;
WDATA[63:32] <= next_data;
WSTRB[7:4] <= 4'b1111;
end
endcase
在最近的一个图像处理项目中,通过这些优化将AXI接口的吞吐量从800MB/s提升到了1.5GB/s,接近理论带宽的90%。调试过程中最耗时的不是协议实现本身,而是与DDR控制器时序的配合——这时候一个好的仿真测试平台能节省大量时间。