FPGA教程系列-Vivado AXI4-Lite slave 测试
新知识:
可以创建ip的时候,直接使用AXI4 VIP来验证IP

自动生成了一个BD的验证模块,这个功能在纯verilog里用的比较少,但是比较直观

代码解读:
- 模块声明与参数 (Parameters)
verilog
module myip_axi_lite_slave_slave_lite_v1_0_S00_AXI #
(
// ...
parameter integer C_S_AXI_DATA_WIDTH = 32, // 数据总线宽度,通常为 32 位
parameter integer C_S_AXI_ADDR_WIDTH = 4 // 地址总线宽度,4 位可以寻址 16 个字节(即4个32位寄存器)
)
定义了模块名称和可配置参数。通过参数化设计,使得该模块可以在不同位宽的系统中使用。这里默认是标准的 32 位数据宽和 4 位地址宽。
- 端口定义 (Ports) - AXI4-Lite 接口
verilog
(
input wire S_AXI_ACLK, // 全局时钟
input wire S_AXI_ARESETN, // 全局复位(低电平有效)
// 写地址通道 (Write Address Channel)
input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR,
input wire [2 : 0] S_AXI_AWPROT, // 保护类型(通常不使用,保留)
input wire S_AXI_AWVALID, // 主机发送:写地址有效
output wire S_AXI_AWREADY, // 从机回复:准备好接收写地址
// 写数据通道 (Write Data Channel)
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 wire S_AXI_WREADY, // 从机回复:准备好接收写数据
// 写响应通道 (Write Response Channel)
output wire [1 : 0] S_AXI_BRESP, // 从机回复:写操作状态(成功/失败)
output wire S_AXI_BVALID, // 从机回复:写响应有效
input wire S_AXI_BREADY, // 主机发送:准备好接收响应
// 读地址通道 (Read Address Channel)
input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR,
input wire [2 : 0] S_AXI_ARPROT,
input wire S_AXI_ARVALID,
output wire S_AXI_ARREADY,
// 读数据通道 (Read Data Channel)
output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA,
output wire [1 : 0] S_AXI_RRESP,
output wire S_AXI_RVALID,
input wire S_AXI_RREADY
);
定义了 AXI4-Lite 协议所需的 5 个通道(写地址、写数据、写响应、读地址、读数据)的所有信号。
关键点 :所有以 VALID 结尾的信号都是发送方发出的,所有以 READY 结尾的信号都是接收方发出的。只有当 VALID 和 READY 同时为高时,数据传输才算完成(握手成功)。
- 内部信号与地址解码参数
verilog
// AXI4LITE 内部寄存器定义,用于驱动输出端口
reg [C_S_AXI_ADDR_WIDTH-1 : 0] axi_awaddr;
reg axi_awready;
// ... (其他 axi_ 前缀的寄存器)
// 地址解码参数
localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1;
localparam integer OPT_MEM_ADDR_BITS = 1;
ADDR_LSB:这是地址最低有效位。在 32 位系统中,数据是 4 字节对齐的(地址 0x0, 0x4, 0x8...)。因此地址的最低 2 位(bit 0 和 bit 1)不用于选择寄存器,我们从 bit 2 开始看。如果数据宽是 64 位,则从 bit 3 开始看。
OPT_MEM_ADDR_BITS :用于决定需要几位地址线来区分寄存器。这里设为 1,意味着利用 2 位地址线(bit 2 和 bit 3)可以区分 2(1+1)=42^{(1+1)} = 42(1+1)=4 个寄存器。
- 用户寄存器定义 (User Registers)
verilog
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg0;
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg1;
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg2;
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg3;
这是该 IP 核的"心脏"。这 4 个寄存器就是主机通过 AXI 总线读写的目标。可以将逻辑控制信号连接到这些寄存器上。
- IO 连接与状态机定义
verilog
assign S_AXI_AWREADY = axi_awready;
// ... (将内部寄存器连接到输出端口)
reg [1:0] state_write;
reg [1:0] state_read;
localparam Idle = 2'b00, Raddr = 2'b10, Rdata = 2'b11, Waddr = 2'b10, Wdata = 2'b11;
将内部逻辑信号输出,并定义读写状态机的状态(空闲、地址阶段、数据阶段)。
- 写状态机 (Write State Machine)
verilog
always @(posedge S_AXI_ACLK)
begin
// 复位逻辑 ...
// case(state_write)
// Idle: 等待复位结束,准备好接收地址 (axi_awready <= 1)
// Waddr:
// 等待主机的写地址有效 (AWVALID) 和自身的准备好 (AWREADY)。
// 如果写数据 (WVALID) 也同时也有效,说明地址和数据一起来了,直接完成握手,跳回 Waddr。
// 如果只有地址来了但数据没来,进入 Wdata 状态等待数据。
// Wdata:
// 等待主机的写数据有效 (WVALID),握手完成后回到 Waddr 状态,并发出写响应 (BVALID)。
end

处理写操作的握手协议。它控制何时接收地址,何时接收数据,以及何时发送"写完成"信号。这个状态机主要负责控制信号(Ready/Valid)的流转,而不是数据的实际存储。
- 寄存器写入逻辑 (Memory Mapped Register Write Logic)
verilog
always @( posedge S_AXI_ACLK )
begin
// ... 复位清零 ...
else begin
if (S_AXI_WVALID) // 当写数据有效时
begin
// 根据地址选择写入哪个寄存器 (slv_reg0 ~ 3)
case ( ... )
2'h0: // 写 slv_reg0
for ( byte_index = 0; ... ) // 循环处理字节选通
if ( S_AXI_WSTRB[byte_index] == 1 )
slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
// ... 处理 reg1, reg2, reg3
endcase
end
end
end
这是实际保存数据的地方。
关键机制 (WSTRB) :AXI 支持字节写入。WSTRB 的每一位对应数据的一个字节。例如,如果 WSTRB 是 4'b0001,则只写入最低的 8 位,高 24 位保持不变。for 循环就是为了实现这个功能。
- 读状态机 (Read State Machine)
verilog
always @(posedge S_AXI_ACLK)
begin
// ...
// Idle: 复位后进入 Raddr,准备接收读地址 (axi_arready <= 1)
// Raddr:
// 当主机发送有效的读地址 (ARVALID) 且从机准备好 (ARREADY) 时:
// 锁存地址,拉高 axi_rvalid (告诉主机数据准备好了),进入 Rdata 状态。
// Rdata:
// 等待主机接收数据 (RREADY)。当主机读走数据后,拉低 rvalid,回到 Raddr 状态等待下一次读取。
end

处理读操作的握手协议。AXI-Lite 的读操作相对简单,收到地址后,下一周期通常就能给出数据。
- 读数据输出逻辑 (Read Data Generation)
verilog
assign S_AXI_RDATA = (axi_araddr[...] == 2'h0) ? slv_reg0 :
(axi_araddr[...] == 2'h1) ? slv_reg1 :
(axi_araddr[...] == 2'h2) ? slv_reg2 :
(axi_araddr[...] == 2'h3) ? slv_reg3 : 0;
这是一个多路选择器(MUX)。根据当前读状态机锁存的地址 (axi_araddr),决定将 slv_reg0 到 slv_reg3 中的哪一个的值放到 S_AXI_RDATA 线上发回给主机。
仿真:
炫酷的AXI VIP IP效果。

还可以显示事务流程,虽然但是吧,可能也会有助于理解。

查看仿真的波形:
观察波形,对比状态机的图,应该是先进入了WADDR状态将地址写入,然后,由于WVALID为0,进入了WDATA状态,之后写数据。


可以参考状态机的跳转:

再看读数据:
其实主要就是看握手。

