参考
- zynq中裸机环境下axi4full寄存器读写.csdn
- Axi4Full转SMMR突发读写和单次读写对比.csdn
- Axi4Full转APB突发读写和单次读写对比.csdn
- 简易寄存器接口SMMR.csdn
- 高级外设总线APB.csdn
- 嵌入式终端AtShell.csdn
- 8位CPU设计n8_cpu.csdn
- Zynq AXI-Lite 总线原理与实现.csdn
一、用AtShell制作4个命令
用这4个命令结合ila测试axi突发读写和单次读写的差异
参考 zynq中裸机环境下axi4full寄存器读写.csdn
- read: 单次读
- write 单次写
- full_write 突发写64个u32
- full_read突发读64个u32
二、slot0.v寄存器映射
整个 IP 提供 64 个 u32 数据寄存器。
| 地址索引 | 名称 | 属性 | 说明 |
|---|---|---|---|
| 0 | CTRL_STATUS | R/W | 控制与状态寄存器 |
1~63 |
DATAx | R/W | 64 个数据寄存器 |
TOP
BD

AXI 地址映射表
| Name | Interface | Slave Segment | Master Base Address | Range | Master High Address |
|---|---|---|---|---|---|
| /axi_lite_ns_sync_pul_0/s_axi | s_axi | reg0 | 0x43C0_0000 | 4K | 0x43C0_0FFF |
PIN.xdc
xc7z020
bash
## IIC
## IIC
set_property -dict {PACKAGE_PIN T11 IOSTANDARD LVCMOS33} [get_ports IIC_EMIO_scl_io]
set_property -dict {PACKAGE_PIN V5 IOSTANDARD LVCMOS33} [get_ports IIC_EMIO_sda_io]
## GPIO_EMIO
set_property -dict {PACKAGE_PIN U5 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[0]]
set_property -dict {PACKAGE_PIN V7 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[1]]
set_property -dict {PACKAGE_PIN W8 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[2]]
set_property -dict {PACKAGE_PIN U9 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[3]]
set_property -dict {PACKAGE_PIN U10 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[4]]
set_property -dict {PACKAGE_PIN W6 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[5]]
set_property -dict {PACKAGE_PIN Y7 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[6]]
set_property -dict {PACKAGE_PIN Y9 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[7]]
# 中断和脉冲产生
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports O_LED]
set_property -dict {PACKAGE_PIN J14 IOSTANDARD LVCMOS33} [get_ports o_debug]
top.v
verilog
`timescale 1ns/1ps
module top #(
// AXI4-Full 地址宽度,常见 FPGA SoC 系统中一般为 32 位。
parameter P_S_AXI_ADDR_WIDTH = 32,
// AXI4-Full 数据宽度,本项目使用 32 位寄存器访问。
parameter P_S_AXI_DATA_WIDTH = 32,
// AXI4-Full ID 宽度。Zynq PS 侧连接时可按 BD 自动生成的宽度调整。
parameter P_S_AXI_ID_WIDTH = 1,
// AXI4-Full slave 基地址,和 Vitis 软件里的 BASE_ADDR 保持一致。
parameter [P_S_AXI_ADDR_WIDTH-1:0] P_S_BASE_ADDR = 32'h43c0_0000,
// AXI4-Full 可访问的寄存器窗口大小,单位是字节。
// 512 字节等价于 128 个 32 位寄存器。
parameter P_S_MEM_SIZE = 512,
// 内部 SMMR 字节地址宽度。
// 9 位地址可以覆盖 512 字节空间。
parameter P_REG_ADDR_WIDTH = 9,
// 系统时钟频率。
// 当前顶层主要把 AXI 时钟传给各级模块,此参数预留给需要时序计算的模块使用。
parameter P_CLK_FREQ = 50_000_000
)(
// AXI4-Full 全局时钟和低有效复位。
input wire s_axi_aclk,
input wire s_axi_aresetn,
// AXI4-Full 写地址通道。
input wire [P_S_AXI_ID_WIDTH-1:0] s_axi_awid,
input wire [P_S_AXI_ADDR_WIDTH-1:0] s_axi_awaddr,
input wire [7:0] s_axi_awlen,
input wire [2:0] s_axi_awsize,
input wire [1:0] s_axi_awburst,
input wire s_axi_awlock,
input wire [3:0] s_axi_awcache,
input wire [2:0] s_axi_awprot,
input wire [3:0] s_axi_awqos,
input wire s_axi_awvalid,
output wire s_axi_awready,
// AXI4-Full 写数据通道。
input wire [P_S_AXI_DATA_WIDTH-1:0] s_axi_wdata,
input wire [(P_S_AXI_DATA_WIDTH/8)-1:0] s_axi_wstrb,
input wire s_axi_wlast,
input wire s_axi_wvalid,
output wire s_axi_wready,
// AXI4-Full 写响应通道。
output wire [P_S_AXI_ID_WIDTH-1:0] s_axi_bid,
output wire [1:0] s_axi_bresp,
output wire s_axi_bvalid,
input wire s_axi_bready,
// AXI4-Full 读地址通道。
input wire [P_S_AXI_ID_WIDTH-1:0] s_axi_arid,
input wire [P_S_AXI_ADDR_WIDTH-1:0] s_axi_araddr,
input wire [7:0] s_axi_arlen,
input wire [2:0] s_axi_arsize,
input wire [1:0] s_axi_arburst,
input wire s_axi_arlock,
input wire [3:0] s_axi_arcache,
input wire [2:0] s_axi_arprot,
input wire [3:0] s_axi_arqos,
input wire s_axi_arvalid,
output wire s_axi_arready,
// AXI4-Full 读数据通道。
output wire [P_S_AXI_ID_WIDTH-1:0] s_axi_rid,
output wire [P_S_AXI_DATA_WIDTH-1:0] s_axi_rdata,
output wire [1:0] s_axi_rresp,
output wire s_axi_rlast,
output wire s_axi_rvalid,
input wire s_axi_rready,
// 示例外设输出。
// 该信号由 slot1 内部的控制寄存器驱动。
output wire o_led,
// slot0 实验输出。
output wire o_irq,
output wire o_debug
);
////////////////////////////////////////////////////////////////////////////
// AXI4-Full 桥接模块到 SMMR 路由模块之间的信号
////////////////////////////////////////////////////////////////////////////
// AXI4-Full 每个写 beat 被接收后产生的单周期写脉冲。
wire w_reg_wr_en;
// SMMR 写地址和读地址。
// 这里仍然是字节地址,后面由 smmr_slot_router 转换成寄存器索引。
wire [P_REG_ADDR_WIDTH-1:0] w_reg_wr_addr;
wire [P_REG_ADDR_WIDTH-1:0] w_reg_rd_addr;
wire w_reg_rd_en;
// SMMR 写数据和读数据。
wire [P_S_AXI_DATA_WIDTH-1:0] w_reg_wr_data;
wire [P_S_AXI_DATA_WIDTH-1:0] w_reg_rd_data;
////////////////////////////////////////////////////////////////////////////
// SMMR 路由模块到各个 slot 外设之间的信号
////////////////////////////////////////////////////////////////////////////
// slot 地址以 32 位寄存器为单位,因此比字节地址少 2 位。
// 例如字节地址 20 右移 2 位后变成寄存器地址 5。
wire [P_REG_ADDR_WIDTH-3:0] w_slot_wr_addr;
wire [P_REG_ADDR_WIDTH-3:0] w_slot_rd_addr;
// 写数据会同时送到所有 slot,真正写入哪个 slot 由对应写使能决定。
wire [P_S_AXI_DATA_WIDTH-1:0] w_slot_wr_data;
// slot0 的写使能和读数据。
wire w_slot0_wr_en;
wire w_slot0_rd_en;
wire [P_S_AXI_DATA_WIDTH-1:0] w_slot0_rd_data;
// slot1 的写使能和读数据。
wire w_slot1_wr_en;
wire w_slot1_rd_en;
wire [P_S_AXI_DATA_WIDTH-1:0] w_slot1_rd_data;
////////////////////////////////////////////////////////////////////////////
// AXI4-Full 从接口转 SMMR 桥接模块
//
// 该模块负责处理 AXI4-Full 的地址、数据、响应、burst 和握手信号。
// 对内部模块来说,它只输出简单的 SMMR 寄存器读写接口。
////////////////////////////////////////////////////////////////////////////
axi4full_slave_smmr_bridge #(
.P_S_AXI_ADDR_WIDTH(P_S_AXI_ADDR_WIDTH),
.P_S_AXI_DATA_WIDTH(P_S_AXI_DATA_WIDTH),
.P_S_AXI_ID_WIDTH (P_S_AXI_ID_WIDTH),
.P_S_BASE_ADDR (P_S_BASE_ADDR),
.P_S_MEM_SIZE (P_S_MEM_SIZE),
.P_REG_ADDR_WIDTH (P_REG_ADDR_WIDTH)
) u_axi4full_slave_smmr_bridge (
.s_axi_aclk (s_axi_aclk),
.s_axi_aresetn (s_axi_aresetn),
.s_axi_awid (s_axi_awid),
.s_axi_awaddr (s_axi_awaddr),
.s_axi_awlen (s_axi_awlen),
.s_axi_awsize (s_axi_awsize),
.s_axi_awburst (s_axi_awburst),
.s_axi_awlock (s_axi_awlock),
.s_axi_awcache (s_axi_awcache),
.s_axi_awprot (s_axi_awprot),
.s_axi_awqos (s_axi_awqos),
.s_axi_awvalid (s_axi_awvalid),
.s_axi_awready (s_axi_awready),
.s_axi_wdata (s_axi_wdata),
.s_axi_wstrb (s_axi_wstrb),
.s_axi_wlast (s_axi_wlast),
.s_axi_wvalid (s_axi_wvalid),
.s_axi_wready (s_axi_wready),
.s_axi_bid (s_axi_bid),
.s_axi_bresp (s_axi_bresp),
.s_axi_bvalid (s_axi_bvalid),
.s_axi_bready (s_axi_bready),
.s_axi_arid (s_axi_arid),
.s_axi_araddr (s_axi_araddr),
.s_axi_arlen (s_axi_arlen),
.s_axi_arsize (s_axi_arsize),
.s_axi_arburst (s_axi_arburst),
.s_axi_arlock (s_axi_arlock),
.s_axi_arcache (s_axi_arcache),
.s_axi_arprot (s_axi_arprot),
.s_axi_arqos (s_axi_arqos),
.s_axi_arvalid (s_axi_arvalid),
.s_axi_arready (s_axi_arready),
.s_axi_rid (s_axi_rid),
.s_axi_rdata (s_axi_rdata),
.s_axi_rresp (s_axi_rresp),
.s_axi_rlast (s_axi_rlast),
.s_axi_rvalid (s_axi_rvalid),
.s_axi_rready (s_axi_rready),
.o_reg_wr_en (w_reg_wr_en),
.o_reg_wr_addr (w_reg_wr_addr),
.o_reg_wr_data (w_reg_wr_data),
.o_reg_rd_addr (w_reg_rd_addr),
.o_reg_rd_en (w_reg_rd_en),
.i_reg_rd_data (w_reg_rd_data)
);
////////////////////////////////////////////////////////////////////////////
// SMMR slot 路由模块
//
// 该模块根据 SMMR 地址范围选择目标 slot。
// 写操作时,它给对应 slot 产生写使能。
// 读操作时,它把被选中 slot 的读数据返回给 AXI4-Full 桥接模块。
//
// 当前地址映射:
// slot0:寄存器地址 0 到 64,CTRL_STATUS + 64 个实验数据寄存器
// slot1:寄存器地址 65 到 80,16 个普通测试寄存器
////////////////////////////////////////////////////////////////////////////
smmr_slot_router u_smmr_slot_hub (
.i_clk (s_axi_aclk),
.i_rst_n (s_axi_aresetn),
.i_reg_wr_en (w_reg_wr_en),
.i_reg_wr_addr (w_reg_wr_addr),
.i_reg_wr_data (w_reg_wr_data),
.i_reg_rd_addr (w_reg_rd_addr),
.i_reg_rd_en (w_reg_rd_en),
.o_reg_rd_data (w_reg_rd_data),
.o_slot_wr_addr (w_slot_wr_addr),
.o_slot_wr_data (w_slot_wr_data),
.o_slot_rd_addr (w_slot_rd_addr),
.o_slot0_wr_en (w_slot0_wr_en),
.o_slot0_rd_en (w_slot0_rd_en),
.i_slot0_rd_data(w_slot0_rd_data),
.o_slot1_wr_en (w_slot1_wr_en),
.o_slot1_rd_en (w_slot1_rd_en),
.i_slot1_rd_data(w_slot1_rd_data)
);
////////////////////////////////////////////////////////////////////////////
// slot0 外设
//
// slot0 是一个简单寄存器块。
// 它可以作为新增 SMMR 外设的模板:只需要接入写使能、地址、写数据、
// 读地址和读数据,就可以挂到系统中。
////////////////////////////////////////////////////////////////////////////
slot0 u_slot0 (
.i_clk (s_axi_aclk),
.i_rst_n (s_axi_aresetn),
.i_reg_wr_en (w_slot0_wr_en),
.i_reg_wr_addr (w_slot_wr_addr),
.i_reg_wr_data (w_slot_wr_data),
.i_reg_rd_addr (w_slot_rd_addr),
.i_reg_rd_en (w_slot0_rd_en),
.o_reg_rd_data (w_slot0_rd_data),
.o_irq (o_irq),
.o_debug (o_debug)
);
////////////////////////////////////////////////////////////////////////////
// slot1 外设
//
// slot1 也是一个 SMMR 寄存器块,同时多了一个实际硬件输出 o_led。
// 这展示了如何通过寄存器写入来控制 FPGA 内部或外部硬件信号。
////////////////////////////////////////////////////////////////////////////
slot1 u_slot1 (
.i_clk (s_axi_aclk),
.i_rst_n (s_axi_aresetn),
.i_reg_wr_en (w_slot1_wr_en),
.i_reg_wr_addr (w_slot_wr_addr),
.i_reg_wr_data (w_slot_wr_data),
.i_reg_rd_addr (w_slot_rd_addr),
.i_reg_rd_en (w_slot1_rd_en),
.o_reg_rd_data (w_slot1_rd_data),
.o_led (o_led)
);
endmodule
axi4full_slave_smmr_bridge.v
verilog
// AXI4-Full 从接口到简单内存映射寄存器总线(SMMR)的桥接模块。
// 内部 SMMR 总线保持和原 AXI-Lite 版本一致:
// 地址按字节寻址,每个有效写 beat 产生 1 个周期的 o_reg_wr_en。
module axi4full_slave_smmr_bridge #(
// AXI 地址宽度、数据宽度和 ID 宽度。
parameter P_S_AXI_ADDR_WIDTH = 32,
parameter P_S_AXI_DATA_WIDTH = 32,
parameter P_S_AXI_ID_WIDTH = 1,
// AXI 绝对基地址。主机既可以访问绝对地址,也可以访问 0 起始的相对偏移。
parameter [P_S_AXI_ADDR_WIDTH-1:0] P_S_BASE_ADDR = 32'h43c0_0000,
// 本桥接窗口大小,单位是字节。
parameter P_S_MEM_SIZE = 512,
// 输出给 SMMR 侧的寄存器地址宽度。
parameter P_REG_ADDR_WIDTH = 9
)(
input wire s_axi_aclk,
input wire s_axi_aresetn,
// =========================
// AXI4-Full 写地址通道
// =========================
input wire [P_S_AXI_ID_WIDTH-1:0] s_axi_awid,
input wire [P_S_AXI_ADDR_WIDTH-1:0] s_axi_awaddr,
input wire [7:0] s_axi_awlen,
input wire [2:0] s_axi_awsize,
input wire [1:0] s_axi_awburst,
input wire s_axi_awlock,
input wire [3:0] s_axi_awcache,
input wire [2:0] s_axi_awprot,
input wire [3:0] s_axi_awqos,
input wire s_axi_awvalid,
output wire s_axi_awready,
// =========================
// AXI4-Full 写数据通道
// =========================
input wire [P_S_AXI_DATA_WIDTH-1:0] s_axi_wdata,
input wire [(P_S_AXI_DATA_WIDTH/8)-1:0] s_axi_wstrb,
input wire s_axi_wlast,
input wire s_axi_wvalid,
output wire s_axi_wready,
// =========================
// AXI4-Full 写响应通道
// =========================
output reg [P_S_AXI_ID_WIDTH-1:0] s_axi_bid,
output reg [1:0] s_axi_bresp,
output reg s_axi_bvalid,
input wire s_axi_bready,
// =========================
// AXI4-Full 读地址通道
// =========================
input wire [P_S_AXI_ID_WIDTH-1:0] s_axi_arid,
input wire [P_S_AXI_ADDR_WIDTH-1:0] s_axi_araddr,
input wire [7:0] s_axi_arlen,
input wire [2:0] s_axi_arsize,
input wire [1:0] s_axi_arburst,
input wire s_axi_arlock,
input wire [3:0] s_axi_arcache,
input wire [2:0] s_axi_arprot,
input wire [3:0] s_axi_arqos,
input wire s_axi_arvalid,
output wire s_axi_arready,
// =========================
// AXI4-Full 读数据通道
// =========================
output reg [P_S_AXI_ID_WIDTH-1:0] s_axi_rid,
output wire [P_S_AXI_DATA_WIDTH-1:0] s_axi_rdata,
output wire [1:0] s_axi_rresp,
output wire s_axi_rlast,
output reg s_axi_rvalid,
input wire s_axi_rready,
// =========================
// 简单寄存器总线(SMMR)
// =========================
// 写通道:每个有效写 beat 拉高 1 个周期。
output reg o_reg_wr_en,
output reg [P_REG_ADDR_WIDTH-1:0] o_reg_wr_addr,
output reg [P_S_AXI_DATA_WIDTH-1:0] o_reg_wr_data,
// 读通道:地址提前给出,主机真正接收该 beat 时 o_reg_rd_en 拉高。
output wire [P_REG_ADDR_WIDTH-1:0] o_reg_rd_addr,
output wire o_reg_rd_en,
input wire [P_S_AXI_DATA_WIDTH-1:0] i_reg_rd_data
);
function integer clogb2;
input integer value;
integer i;
begin
value = value - 1;
for (i = 0; value > 0; i = i + 1) begin
value = value >> 1;
end
clogb2 = i;
end
endfunction
// AXI 响应编码和本桥接支持的 burst 类型。
localparam [1:0] C_AXI_RESP_OKAY = 2'b00;
localparam [1:0] C_AXI_RESP_SLVERR = 2'b10;
localparam [1:0] C_AXI_BURST_FIXED = 2'b00;
localparam [1:0] C_AXI_BURST_INCR = 2'b01;
// 当前设计按 32-bit 寄存器访问,AWSIZE/ARSIZE 必须等于数据总线字节数。
localparam integer C_AXI_BYTE_NUM = P_S_AXI_DATA_WIDTH / 8;
localparam integer C_AXI_BYTE_SHIFT = clogb2(C_AXI_BYTE_NUM);
localparam [2:0] C_AXI_SIZE = C_AXI_BYTE_SHIFT[2:0];
// 写事务状态:保存当前 burst 的地址、长度、计数、size、burst 类型和错误状态。
reg [P_S_AXI_ADDR_WIDTH-1:0] r_wr_addr;
reg [7:0] r_wr_len;
reg [7:0] r_wr_count;
reg [2:0] r_wr_size;
reg [1:0] r_wr_burst;
reg r_wr_active;
reg r_wr_error;
// 读事务状态:保存当前 burst 的地址、长度、计数、size、burst 类型和错误状态。
reg [P_S_AXI_ADDR_WIDTH-1:0] r_rd_addr;
reg [7:0] r_rd_len;
reg [7:0] r_rd_count;
reg [2:0] r_rd_size;
reg [1:0] r_rd_burst;
reg r_rd_active;
reg r_rd_error;
// AXI 握手命中信号:valid 和 ready 同周期为 1 时表示该通道完成一次传输。
wire w_aw_fire = s_axi_awvalid && s_axi_awready;
wire w_w_fire = s_axi_wvalid && s_axi_wready;
wire w_b_fire = s_axi_bvalid && s_axi_bready;
wire w_ar_fire = s_axi_arvalid && s_axi_arready;
wire w_r_fire = s_axi_rvalid && s_axi_rready;
// 地址支持两种形式:
// 1. 绝对地址:P_S_BASE_ADDR + offset;
// 2. 相对地址:直接使用 offset。
wire [P_S_AXI_ADDR_WIDTH-1:0] w_wr_abs_offset = r_wr_addr - P_S_BASE_ADDR;
wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_abs_offset = r_rd_addr - P_S_BASE_ADDR;
wire w_wr_abs_range = (r_wr_addr >= P_S_BASE_ADDR) &&
(w_wr_abs_offset < P_S_MEM_SIZE);
wire w_rd_abs_range = (r_rd_addr >= P_S_BASE_ADDR) &&
(w_rd_abs_offset < P_S_MEM_SIZE);
wire w_wr_rel_range = (r_wr_addr < P_S_MEM_SIZE);
wire w_rd_rel_range = (r_rd_addr < P_S_MEM_SIZE);
wire [P_S_AXI_ADDR_WIDTH-1:0] w_wr_offset = w_wr_abs_range ? w_wr_abs_offset : r_wr_addr;
wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_offset = w_rd_abs_range ? w_rd_abs_offset : r_rd_addr;
wire [P_REG_ADDR_WIDTH-1:0] w_wr_addr_offset = w_wr_offset[P_REG_ADDR_WIDTH-1:0];
wire [P_REG_ADDR_WIDTH-1:0] w_rd_addr_offset = w_rd_offset[P_REG_ADDR_WIDTH-1:0];
// 写访问合法性检查:只支持 FIXED/INCR、完整 32-bit strobe、窗口范围内访问。
wire w_wr_burst_ok = (r_wr_burst == C_AXI_BURST_FIXED) || (r_wr_burst == C_AXI_BURST_INCR);
wire w_wr_size_ok = (r_wr_size == C_AXI_SIZE);
wire w_wr_addr_ok = w_wr_abs_range || w_wr_rel_range;
wire w_wr_strb_full = &s_axi_wstrb;
wire w_wr_last_expected = (r_wr_count == r_wr_len);
wire w_wr_last_fire = w_w_fire && (s_axi_wlast || w_wr_last_expected);
wire w_wr_last_error = w_w_fire && (s_axi_wlast != w_wr_last_expected);
wire w_wr_access_ok = w_wr_burst_ok && w_wr_size_ok && w_wr_addr_ok && w_wr_strb_full;
wire w_wr_beat_error = r_wr_error || !w_wr_access_ok || w_wr_last_error;
wire [P_S_AXI_ADDR_WIDTH-1:0] w_wr_next_addr =
(r_wr_burst == C_AXI_BURST_INCR) ? (r_wr_addr + C_AXI_BYTE_NUM) : r_wr_addr;
// 读访问合法性检查:只支持 FIXED/INCR,并要求地址在本桥接窗口内。
wire w_rd_burst_ok = (r_rd_burst == C_AXI_BURST_FIXED) || (r_rd_burst == C_AXI_BURST_INCR);
wire w_rd_size_ok = (r_rd_size == C_AXI_SIZE);
wire w_rd_addr_ok = w_rd_abs_range || w_rd_rel_range;
wire w_rd_last = (r_rd_count == r_rd_len);
wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_next_addr =
(r_rd_burst == C_AXI_BURST_INCR) ? (r_rd_addr + C_AXI_BYTE_NUM) : r_rd_addr;
assign s_axi_awready = s_axi_aresetn && !r_wr_active && !s_axi_bvalid;
assign s_axi_wready = s_axi_aresetn && r_wr_active && !s_axi_bvalid;
assign s_axi_arready = s_axi_aresetn && !r_rd_active && !s_axi_rvalid;
// SMMR 读地址直接由当前 AXI 读地址换算得到。
// o_reg_rd_en 在读数据 beat 被主机接收时拉高,用作"该读 beat 已消费"的脉冲。
assign o_reg_rd_addr = w_rd_addr_offset;
assign o_reg_rd_en = w_r_fire;
assign s_axi_rdata = w_rd_addr_ok ? i_reg_rd_data : {P_S_AXI_DATA_WIDTH{1'b0}};
assign s_axi_rresp = (r_rd_error || !w_rd_addr_ok) ? C_AXI_RESP_SLVERR : C_AXI_RESP_OKAY;
assign s_axi_rlast = s_axi_rvalid && w_rd_last;
// 写通道状态机:先接收 AW,再逐 beat 接收 W;每个合法 W beat 转成 SMMR 写脉冲。
// burst 结束后返回 B 响应,若期间发生 size/burst/address/last/strobe 错误则返回 SLVERR。
always @(posedge s_axi_aclk or negedge s_axi_aresetn) begin
if (!s_axi_aresetn) begin
r_wr_addr <= {P_S_AXI_ADDR_WIDTH{1'b0}};
r_wr_len <= 8'd0;
r_wr_count <= 8'd0;
r_wr_size <= 3'd0;
r_wr_burst <= C_AXI_BURST_INCR;
r_wr_active <= 1'b0;
r_wr_error <= 1'b0;
s_axi_bid <= {P_S_AXI_ID_WIDTH{1'b0}};
s_axi_bresp <= C_AXI_RESP_OKAY;
s_axi_bvalid <= 1'b0;
o_reg_wr_en <= 1'b0;
o_reg_wr_addr <= {P_REG_ADDR_WIDTH{1'b0}};
o_reg_wr_data <= {P_S_AXI_DATA_WIDTH{1'b0}};
end else begin
// 写使能默认拉低,只有合法写 beat 到来时拉高 1 个周期。
o_reg_wr_en <= 1'b0;
if (w_b_fire) begin
s_axi_bvalid <= 1'b0;
end
if (w_aw_fire) begin
r_wr_addr <= s_axi_awaddr;
r_wr_len <= s_axi_awlen;
r_wr_count <= 8'd0;
r_wr_size <= s_axi_awsize;
r_wr_burst <= s_axi_awburst;
r_wr_active <= 1'b1;
r_wr_error <= (s_axi_awsize != C_AXI_SIZE) ||
!((s_axi_awburst == C_AXI_BURST_FIXED) ||
(s_axi_awburst == C_AXI_BURST_INCR));
s_axi_bid <= s_axi_awid;
end
if (w_w_fire) begin
// 只有当前 beat 完全合法时才真正写 SMMR 寄存器。
if (w_wr_access_ok) begin
o_reg_wr_en <= 1'b1;
o_reg_wr_addr <= w_wr_addr_offset;
o_reg_wr_data <= s_axi_wdata;
end
if (w_wr_last_fire) begin
r_wr_active <= 1'b0;
r_wr_error <= 1'b0;
s_axi_bresp <= w_wr_beat_error ? C_AXI_RESP_SLVERR : C_AXI_RESP_OKAY;
s_axi_bvalid <= 1'b1;
end else begin
// INCR burst 递增地址,FIXED burst 保持地址不变。
r_wr_count <= r_wr_count + 8'd1;
r_wr_addr <= w_wr_next_addr;
r_wr_error <= w_wr_beat_error;
end
end
end
end
// 读通道状态机:接收 AR 后立即产生第一个 R beat;每个 R beat 被接收后推进计数和地址。
// 对非法 size/burst/address 仍完成 AXI 响应,但 RRESP 返回 SLVERR,RDATA 返回 0。
always @(posedge s_axi_aclk or negedge s_axi_aresetn) begin
if (!s_axi_aresetn) begin
r_rd_addr <= {P_S_AXI_ADDR_WIDTH{1'b0}};
r_rd_len <= 8'd0;
r_rd_count <= 8'd0;
r_rd_size <= 3'd0;
r_rd_burst <= C_AXI_BURST_INCR;
r_rd_active <= 1'b0;
r_rd_error <= 1'b0;
s_axi_rid <= {P_S_AXI_ID_WIDTH{1'b0}};
s_axi_rvalid <= 1'b0;
end else begin
if (w_ar_fire) begin
r_rd_addr <= s_axi_araddr;
r_rd_len <= s_axi_arlen;
r_rd_count <= 8'd0;
r_rd_size <= s_axi_arsize;
r_rd_burst <= s_axi_arburst;
r_rd_active <= 1'b1;
r_rd_error <= (s_axi_arsize != C_AXI_SIZE) ||
!((s_axi_arburst == C_AXI_BURST_FIXED) ||
(s_axi_arburst == C_AXI_BURST_INCR));
s_axi_rid <= s_axi_arid;
s_axi_rvalid <= 1'b1;
end else if (w_r_fire) begin
if (w_rd_last) begin
r_rd_active <= 1'b0;
r_rd_error <= 1'b0;
s_axi_rvalid <= 1'b0;
end else begin
// INCR burst 递增地址,FIXED burst 保持地址不变。
r_rd_count <= r_rd_count + 8'd1;
r_rd_addr <= w_rd_next_addr;
end
end
end
end
// 将未使用的 AXI sideband 输入归并到一个 wire,避免部分 lint 流程报警未引用端口。
wire w_unused_axi_sideband = s_axi_awlock ^ s_axi_arlock ^
^s_axi_awcache ^ ^s_axi_arcache ^
^s_axi_awprot ^ ^s_axi_arprot ^
^s_axi_awqos ^ ^s_axi_arqos;
endmodule
smmr_slot_router.v
verilog
////////////////////////////////////////////////////////////////////////////////
// 简单内存映射寄存器路由模块
// - 将字节地址转换成 32 位寄存器索引
// - 将一路 SMMR 接口路由到多个寄存器槽
////////////////////////////////////////////////////////////////////////////////
module smmr_slot_router #(
// AXI4-Full/SMMR 侧使用字节地址,寄存器槽侧使用寄存器索引。
parameter P_ADDR_WIDTH = 9,
parameter P_DATA_WIDTH = 32,
// 寄存器槽地址范围以 32 位寄存器为单位。
// slot0: 65 个 u32,寄存器索引 0..64,用于 CTRL_STATUS + 64 个实验数据。
parameter P_SLOT0_BASE = 7'd0,
parameter P_SLOT0_SIZE = 7'd65,
// slot1: 16 个 u32,寄存器索引 65..80,用于普通读写测试。
parameter P_SLOT1_BASE = 7'd65,
parameter P_SLOT1_SIZE = 7'd16
)(
input wire i_clk,
input wire i_rst_n,
// 上游简单寄存器总线,地址为字节地址。
input wire i_reg_wr_en,
input wire [P_ADDR_WIDTH-1:0] i_reg_wr_addr,
input wire [P_DATA_WIDTH-1:0] i_reg_wr_data,
input wire [P_ADDR_WIDTH-1:0] i_reg_rd_addr,
input wire i_reg_rd_en,
output reg [P_DATA_WIDTH-1:0] o_reg_rd_data,
// 下游寄存器槽总线,地址为 32 位寄存器索引。
output wire [P_ADDR_WIDTH-3:0] o_slot_wr_addr,
output wire [P_DATA_WIDTH-1:0] o_slot_wr_data,
output wire [P_ADDR_WIDTH-3:0] o_slot_rd_addr,
output wire o_slot0_wr_en,
output wire o_slot0_rd_en,
input wire [P_DATA_WIDTH-1:0] i_slot0_rd_data,
output wire o_slot1_wr_en,
output wire o_slot1_rd_en,
input wire [P_DATA_WIDTH-1:0] i_slot1_rd_data
);
wire [P_ADDR_WIDTH-3:0] w_reg_wr_addr_u32 = i_reg_wr_addr >> 2;
wire [P_ADDR_WIDTH-3:0] w_reg_rd_addr_u32 = i_reg_rd_addr >> 2;
reg r_slot0_wr_sel;
reg r_slot1_wr_sel;
reg r_slot0_rd_sel;
reg r_slot1_rd_sel;
always @(*) begin
r_slot0_wr_sel = 1'b0;
r_slot1_wr_sel = 1'b0;
if ((w_reg_wr_addr_u32 >= P_SLOT0_BASE) && (w_reg_wr_addr_u32 < (P_SLOT0_BASE + P_SLOT0_SIZE))) begin
r_slot0_wr_sel = 1'b1;
end else if ((w_reg_wr_addr_u32 >= P_SLOT1_BASE) && (w_reg_wr_addr_u32 < (P_SLOT1_BASE + P_SLOT1_SIZE))) begin
r_slot1_wr_sel = 1'b1;
end
end
always @(*) begin
r_slot0_rd_sel = 1'b0;
r_slot1_rd_sel = 1'b0;
if ((w_reg_rd_addr_u32 >= P_SLOT0_BASE) && (w_reg_rd_addr_u32 < (P_SLOT0_BASE + P_SLOT0_SIZE))) begin
r_slot0_rd_sel = 1'b1;
end else if ((w_reg_rd_addr_u32 >= P_SLOT1_BASE) && (w_reg_rd_addr_u32 < (P_SLOT1_BASE + P_SLOT1_SIZE))) begin
r_slot1_rd_sel = 1'b1;
end
end
assign o_slot0_wr_en = i_reg_wr_en && r_slot0_wr_sel;
assign o_slot1_wr_en = i_reg_wr_en && r_slot1_wr_sel;
assign o_slot0_rd_en = i_reg_rd_en && r_slot0_rd_sel;
assign o_slot1_rd_en = i_reg_rd_en && r_slot1_rd_sel;
assign o_slot_wr_addr = w_reg_wr_addr_u32;
assign o_slot_wr_data = i_reg_wr_data;
assign o_slot_rd_addr = w_reg_rd_addr_u32;
always @(*) begin
if (r_slot0_rd_sel) begin
o_reg_rd_data = i_slot0_rd_data;
end else if (r_slot1_rd_sel) begin
o_reg_rd_data = i_slot1_rd_data;
end else begin
o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
end
end
endmodule
slot0.v
verilog
module slot0 #(
parameter P_ADDR_WIDTH = 7,
parameter P_DATA_WIDTH = 32,
parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 7'd0,
parameter P_REG_COUNT = 65
)(
input wire i_clk,
input wire i_rst_n,
input wire i_reg_wr_en,
input wire [P_ADDR_WIDTH-1:0] i_reg_wr_addr,
input wire [P_DATA_WIDTH-1:0] i_reg_wr_data,
input wire [P_ADDR_WIDTH-1:0] i_reg_rd_addr,
input wire i_reg_rd_en,
output reg [P_DATA_WIDTH-1:0] o_reg_rd_data,
output reg o_irq,
output reg o_debug
);
localparam [P_ADDR_WIDTH-1:0] C_ADDR_CTRL = P_SLOT_BASE;
localparam [P_ADDR_WIDTH-1:0] C_ADDR_DATA_BASE = P_SLOT_BASE + 7'd1;
localparam [P_ADDR_WIDTH-1:0] C_ADDR_DATA_LAST = P_SLOT_BASE + 7'd64;
localparam [63:0] C_ALL_DATA_SEEN = {64{1'b1}};
reg [P_DATA_WIDTH-1:0] r_data [0:63];
reg [63:0] r_data_seen;
reg r_busy;
reg r_done;
reg r_irq_en;
reg r_process_pending;
wire w_ctrl_wr = i_reg_wr_en && (i_reg_wr_addr == C_ADDR_CTRL);
wire w_data_wr = i_reg_wr_en &&
(i_reg_wr_addr >= C_ADDR_DATA_BASE) &&
(i_reg_wr_addr <= C_ADDR_DATA_LAST);
wire w_data_rd = i_reg_rd_en &&
(i_reg_rd_addr >= C_ADDR_DATA_BASE) &&
(i_reg_rd_addr <= C_ADDR_DATA_LAST);
wire [5:0] w_wr_data_index = i_reg_wr_addr - C_ADDR_DATA_BASE;
wire [5:0] w_rd_data_index = i_reg_rd_addr - C_ADDR_DATA_BASE;
wire [63:0] w_wr_data_bit = 64'h1 << w_wr_data_index;
wire [63:0] w_seen_base = (w_wr_data_index == 6'd0) ? 64'd0 : r_data_seen;
wire [63:0] w_seen_next = w_seen_base | w_wr_data_bit;
integer i;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
for (i = 0; i < 64; i = i + 1) begin
r_data[i] <= {P_DATA_WIDTH{1'b0}};
end
r_data_seen <= 64'd0;
r_busy <= 1'b0;
r_done <= 1'b0;
r_irq_en <= 1'b0;
r_process_pending <= 1'b0;
o_irq <= 1'b0;
o_debug <= 1'b0;
end else begin
if (r_process_pending) begin
for (i = 0; i < 64; i = i + 1) begin
r_data[i] <= r_data[i];
end
r_busy <= 1'b0;
r_done <= 1'b1;
r_process_pending <= 1'b0;
if (r_irq_en) begin
o_irq <= 1'b1;
end
end
if (w_ctrl_wr) begin
r_irq_en <= i_reg_wr_data[2];
if (i_reg_wr_data[3]) begin
r_done <= 1'b0;
o_irq <= 1'b0;
end
end else if (w_data_wr && !r_process_pending) begin
r_data[w_wr_data_index] <= i_reg_wr_data;
r_data_seen <= w_seen_next;
if (w_wr_data_index == 6'd0) begin
r_done <= 1'b0;
o_irq <= 1'b0;
o_debug <= 1'b1;
end
if (w_seen_next == C_ALL_DATA_SEEN) begin
r_data_seen <= 64'd0;
r_busy <= 1'b1;
r_process_pending <= 1'b1;
o_debug <= 1'b0;
end
end
if (w_data_rd) begin
if (w_rd_data_index == 6'd0) begin
o_debug <= 1'b1;
end
if (w_rd_data_index == 6'd63) begin
o_debug <= 1'b0;
end
end
end
end
always @(*) begin
if (i_reg_rd_addr == C_ADDR_CTRL) begin
o_reg_rd_data = {{(P_DATA_WIDTH-4){1'b0}}, 1'b0, r_irq_en, r_done, r_busy};
end else if ((i_reg_rd_addr >= C_ADDR_DATA_BASE) && (i_reg_rd_addr <= C_ADDR_DATA_LAST)) begin
o_reg_rd_data = r_data[w_rd_data_index];
end else begin
o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
end
end
endmodule
slot1.v
verilog
module slot1 #(
parameter P_ADDR_WIDTH = 7,
parameter P_DATA_WIDTH = 32,
parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 7'd65,
parameter P_REG_COUNT = 16
)(
input wire i_clk,
input wire i_rst_n,
input wire i_reg_wr_en,
input wire [P_ADDR_WIDTH-1:0] i_reg_wr_addr,
input wire [P_DATA_WIDTH-1:0] i_reg_wr_data,
input wire [P_ADDR_WIDTH-1:0] i_reg_rd_addr,
input wire i_reg_rd_en,
output reg [P_DATA_WIDTH-1:0] o_reg_rd_data,
output reg o_led
);
localparam [P_ADDR_WIDTH-1:0] C_ADDR_CONTROL = P_SLOT_BASE;
reg [P_DATA_WIDTH-1:0] r_mem [0:P_REG_COUNT-1];
wire w_wr_hit = i_reg_wr_en &&
(i_reg_wr_addr >= P_SLOT_BASE) &&
(i_reg_wr_addr < (P_SLOT_BASE + P_REG_COUNT));
wire [P_ADDR_WIDTH-1:0] w_wr_index = i_reg_wr_addr - P_SLOT_BASE;
wire [P_ADDR_WIDTH-1:0] w_rd_index = i_reg_rd_addr - P_SLOT_BASE;
wire w_unused_rd_en = i_reg_rd_en;
integer i;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
for (i = 0; i < P_REG_COUNT; i = i + 1) begin
r_mem[i] <= {P_DATA_WIDTH{1'b0}};
end
o_led <= 1'b0;
end else if (w_wr_hit) begin
r_mem[w_wr_index] <= i_reg_wr_data;
if (i_reg_wr_addr == C_ADDR_CONTROL) begin
o_led <= i_reg_wr_data[0];
end
end
end
always @(*) begin
if ((i_reg_rd_addr >= P_SLOT_BASE) && (i_reg_rd_addr < (P_SLOT_BASE + P_REG_COUNT))) begin
o_reg_rd_data = r_mem[w_rd_index];
end else begin
o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
end
end
endmodule
axi4full_master.v
verilog
`timescale 1ns/1ps
module axi4full_master #(
parameter P_AXI_ADDR_WIDTH = 32, // AXI 地址总线宽度,单位 bit。
parameter P_AXI_DATA_WIDTH = 32, // AXI 数据总线宽度,单位 bit。
parameter P_AXI_ID_WIDTH = 1 // AXI 事务 ID 宽度,单位 bit。
)(
output reg [P_AXI_ID_WIDTH-1:0] m_axi_awid, // 写地址通道 ID,随 AW 通道发送。
output reg [P_AXI_ADDR_WIDTH-1:0] m_axi_awaddr, // 写 burst 起始字节地址。
output reg [7:0] m_axi_awlen, // 写 burst 长度,编码值为传输拍数减 1。
output reg [2:0] m_axi_awsize, // 单拍写数据字节数,编码值为 log2(bytes per beat)。
output reg [1:0] m_axi_awburst, // 写 burst 类型,本 master 固定发 INCR。
output reg m_axi_awlock, // 写独占/锁定访问属性,本 master 未使用,固定为 0。
output reg [3:0] m_axi_awcache, // 写缓存属性边带信号。
output reg [2:0] m_axi_awprot, // 写保护属性边带信号。
output reg [3:0] m_axi_awqos, // 写服务质量属性边带信号。
output reg m_axi_awvalid, // 写地址有效握手信号。
input wire m_axi_awready, // 从机返回的写地址就绪握手信号。
output reg [P_AXI_DATA_WIDTH-1:0] m_axi_wdata, // 写数据通道的当前数据拍。
output reg [(P_AXI_DATA_WIDTH/8)-1:0] m_axi_wstrb, // 写字节选通信号,每 bit 对应一个字节 lane。
output reg m_axi_wlast, // 写 burst 最后一拍标志。
output reg m_axi_wvalid, // 写数据有效握手信号。
input wire m_axi_wready, // 从机返回的写数据就绪握手信号。
input wire [P_AXI_ID_WIDTH-1:0] m_axi_bid, // 从机返回的写响应 ID。
input wire [1:0] m_axi_bresp, // 从机返回的写响应状态。
input wire m_axi_bvalid, // 从机返回的写响应有效握手信号。
output reg m_axi_bready, // 写响应就绪握手信号。
output reg [P_AXI_ID_WIDTH-1:0] m_axi_arid, // 读地址通道 ID,随 AR 通道发送。
output reg [P_AXI_ADDR_WIDTH-1:0] m_axi_araddr, // 读 burst 起始字节地址。
output reg [7:0] m_axi_arlen, // 读 burst 长度,编码值为传输拍数减 1。
output reg [2:0] m_axi_arsize, // 单拍读数据字节数,编码值为 log2(bytes per beat)。
output reg [1:0] m_axi_arburst, // 读 burst 类型,本 master 固定发 INCR。
output reg m_axi_arlock, // 读独占/锁定访问属性,本 master 未使用,固定为 0。
output reg [3:0] m_axi_arcache, // 读缓存属性边带信号。
output reg [2:0] m_axi_arprot, // 读保护属性边带信号。
output reg [3:0] m_axi_arqos, // 读服务质量属性边带信号。
output reg m_axi_arvalid, // 读地址有效握手信号。
input wire m_axi_arready, // 从机返回的读地址就绪握手信号。
input wire [P_AXI_ID_WIDTH-1:0] m_axi_rid, // 从机返回的读数据 ID。
input wire [P_AXI_DATA_WIDTH-1:0] m_axi_rdata, // 从机返回的当前读数据拍。
input wire [1:0] m_axi_rresp, // 当前读数据拍对应的响应状态。
input wire m_axi_rlast, // 读 burst 最后一拍标志。
input wire m_axi_rvalid, // 从机返回的读数据有效握手信号。
output reg m_axi_rready, // 读数据就绪握手信号。
input wire i_clk, // master 内部逻辑时钟。
input wire i_rst_n, // 低有效异步复位。
input wire i_wr_start, // 写命令启动脉冲,高 1 拍启动一次写 burst。
input wire [P_AXI_ADDR_WIDTH-1:0] i_wr_addr, // 写命令起始字节地址。
input wire [7:0] i_wr_len, // 写命令 burst 长度,编码值为传输拍数减 1。
input wire [P_AXI_DATA_WIDTH-1:0] i_wr_data, // 写命令首拍数据,后续每拍自动加 1。
output reg o_wr_busy, // 写命令执行中标志。
output reg o_wr_done, // 写命令完成脉冲,高 1 拍。
output reg [1:0] o_wr_resp, // 从 B 通道采集到的最终写响应。
input wire i_rd_start, // 读命令启动脉冲,高 1 拍启动一次读 burst。
input wire [P_AXI_ADDR_WIDTH-1:0] i_rd_addr, // 读命令起始字节地址。
input wire [7:0] i_rd_len, // 读命令 burst 长度,编码值为传输拍数减 1。
output reg o_rd_busy, // 读命令执行中标志。
output reg o_rd_done, // 读命令完成脉冲,高 1 拍。
output reg [P_AXI_DATA_WIDTH-1:0] o_rd_data, // 从 R 通道采集到的最后一拍读数据。
output reg [1:0] o_rd_resp, // 整个读 burst 聚合后的响应状态。
output reg o_rd_beat_valid, // 每接收 1 拍读数据就拉高 1 拍。
output reg [7:0] o_rd_beat_index, // 当前接收读数据拍的从 0 开始的序号。
output reg [P_AXI_DATA_WIDTH-1:0] o_rd_beat_data, // 当前接收读数据拍的数值。
output reg o_rd_beat_last // 当前接收读数据拍携带的 RLAST 标志。
);
function integer clogb2;
input integer value; // 需要计算向上取整 log2 的输入值。
integer i; // clogb2 函数内部使用的循环计数器。
begin
value = value - 1;
for (i = 0; value > 0; i = i + 1) begin
value = value >> 1;
end
clogb2 = i;
end
endfunction
localparam [1:0] C_AXI_RESP_OKAY = 2'b00; // AXI OKAY 响应编码。
localparam [1:0] C_AXI_RESP_SLVERR = 2'b10; // AXI 从机错误响应编码。
localparam [1:0] C_AXI_BURST_INCR = 2'b01; // AXI 递增 burst 类型编码。
localparam integer C_AXI_BYTE_NUM = P_AXI_DATA_WIDTH / 8; // 每拍数据包含的字节 lane 数量。
localparam integer C_AXI_BYTE_SHIFT = clogb2(C_AXI_BYTE_NUM); // 每拍地址递增对应的移位值。
localparam [2:0] C_AXI_SIZE = C_AXI_BYTE_SHIFT[2:0]; // 当前数据宽度对应的 AXI AxSIZE 编码。
localparam [1:0] WR_IDLE = 2'd0; // 写状态机空闲,等待 i_wr_start。
localparam [1:0] WR_ADDR = 2'd1; // 写状态机发送 AW 地址通道。
localparam [1:0] WR_DATA = 2'd2; // 写状态机发送 W 数据通道各个 beat。
localparam [1:0] WR_RESP = 2'd3; // 写状态机等待 B 响应通道。
localparam [1:0] RD_IDLE = 2'd0; // 读状态机空闲,等待 i_rd_start。
localparam [1:0] RD_ADDR = 2'd1; // 读状态机发送 AR 地址通道。
localparam [1:0] RD_DATA = 2'd2; // 读状态机接收 R 数据通道各个 beat。
reg [1:0] r_wr_state; // 当前写状态机状态。
reg [7:0] r_wr_len; // 锁存的写 burst 长度,编码值为传输拍数减 1。
reg [7:0] r_wr_count; // 从 0 开始计数的写数据拍计数器。
reg [P_AXI_DATA_WIDTH-1:0] r_wr_base_data; // 锁存的首拍写数据。
reg [1:0] r_rd_state; // 当前读状态机状态。
reg [7:0] r_rd_len; // 锁存的读 burst 长度,编码值为传输拍数减 1。
reg [7:0] r_rd_count; // 从 0 开始计数的读数据拍计数器。
reg r_rd_error; // 整个读 burst 中的粘滞错误标志。
wire w_unused_axi_id = ^m_axi_bid ^ ^m_axi_rid; // 消耗响应 ID,避免综合/仿真工具报未使用告警。
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_wr_state <= WR_IDLE;
r_wr_len <= 8'd0;
r_wr_count <= 8'd0;
r_wr_base_data <= {P_AXI_DATA_WIDTH{1'b0}};
m_axi_awid <= {P_AXI_ID_WIDTH{1'b0}};
m_axi_awaddr <= {P_AXI_ADDR_WIDTH{1'b0}};
m_axi_awlen <= 8'd0;
m_axi_awsize <= C_AXI_SIZE;
m_axi_awburst <= C_AXI_BURST_INCR;
m_axi_awlock <= 1'b0;
m_axi_awcache <= 4'b0011;
m_axi_awprot <= 3'b000;
m_axi_awqos <= 4'b0000;
m_axi_awvalid <= 1'b0;
m_axi_wdata <= {P_AXI_DATA_WIDTH{1'b0}};
m_axi_wstrb <= {C_AXI_BYTE_NUM{1'b0}};
m_axi_wlast <= 1'b0;
m_axi_wvalid <= 1'b0;
m_axi_bready <= 1'b0;
o_wr_busy <= 1'b0;
o_wr_done <= 1'b0;
o_wr_resp <= C_AXI_RESP_OKAY;
end else begin
o_wr_done <= 1'b0;
case (r_wr_state)
WR_IDLE: begin
o_wr_busy <= 1'b0;
m_axi_awvalid <= 1'b0;
m_axi_wvalid <= 1'b0;
m_axi_bready <= 1'b0;
if (i_wr_start) begin
o_wr_busy <= 1'b1;
r_wr_len <= i_wr_len;
r_wr_count <= 8'd0;
r_wr_base_data <= i_wr_data;
m_axi_awid <= {P_AXI_ID_WIDTH{1'b0}};
m_axi_awaddr <= i_wr_addr;
m_axi_awlen <= i_wr_len;
m_axi_awsize <= C_AXI_SIZE;
m_axi_awburst <= C_AXI_BURST_INCR;
m_axi_awlock <= 1'b0;
m_axi_awcache <= 4'b0011;
m_axi_awprot <= 3'b000;
m_axi_awqos <= 4'b0000;
m_axi_awvalid <= 1'b1;
r_wr_state <= WR_ADDR;
end
end
WR_ADDR: begin
if (m_axi_awvalid && m_axi_awready) begin
m_axi_awvalid <= 1'b0;
m_axi_wdata <= r_wr_base_data;
m_axi_wstrb <= {C_AXI_BYTE_NUM{1'b1}};
m_axi_wlast <= (r_wr_len == 8'd0);
m_axi_wvalid <= 1'b1;
r_wr_state <= WR_DATA;
end
end
WR_DATA: begin
if (m_axi_wvalid && m_axi_wready) begin
if (r_wr_count == r_wr_len) begin
m_axi_wvalid <= 1'b0;
m_axi_wlast <= 1'b0;
m_axi_bready <= 1'b1;
r_wr_state <= WR_RESP;
end else begin
r_wr_count <= r_wr_count + 8'd1;
m_axi_wdata <= r_wr_base_data + {{(P_AXI_DATA_WIDTH-8){1'b0}}, r_wr_count} + 1'b1;
m_axi_wlast <= ((r_wr_count + 8'd1) == r_wr_len);
end
end
end
WR_RESP: begin
if (m_axi_bvalid && m_axi_bready) begin
o_wr_resp <= m_axi_bresp;
o_wr_done <= 1'b1;
o_wr_busy <= 1'b0;
m_axi_bready <= 1'b0;
r_wr_state <= WR_IDLE;
end
end
default: begin
r_wr_state <= WR_IDLE;
end
endcase
end
end
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_rd_state <= RD_IDLE;
r_rd_len <= 8'd0;
r_rd_count <= 8'd0;
r_rd_error <= 1'b0;
m_axi_arid <= {P_AXI_ID_WIDTH{1'b0}};
m_axi_araddr <= {P_AXI_ADDR_WIDTH{1'b0}};
m_axi_arlen <= 8'd0;
m_axi_arsize <= C_AXI_SIZE;
m_axi_arburst <= C_AXI_BURST_INCR;
m_axi_arlock <= 1'b0;
m_axi_arcache <= 4'b0011;
m_axi_arprot <= 3'b000;
m_axi_arqos <= 4'b0000;
m_axi_arvalid <= 1'b0;
m_axi_rready <= 1'b0;
o_rd_busy <= 1'b0;
o_rd_done <= 1'b0;
o_rd_data <= {P_AXI_DATA_WIDTH{1'b0}};
o_rd_resp <= C_AXI_RESP_OKAY;
o_rd_beat_valid <= 1'b0;
o_rd_beat_index <= 8'd0;
o_rd_beat_data <= {P_AXI_DATA_WIDTH{1'b0}};
o_rd_beat_last <= 1'b0;
end else begin
o_rd_done <= 1'b0;
o_rd_beat_valid <= 1'b0;
case (r_rd_state)
RD_IDLE: begin
o_rd_busy <= 1'b0;
m_axi_arvalid <= 1'b0;
m_axi_rready <= 1'b0;
if (i_rd_start) begin
o_rd_busy <= 1'b1;
r_rd_len <= i_rd_len;
r_rd_count <= 8'd0;
r_rd_error <= 1'b0;
o_rd_resp <= C_AXI_RESP_OKAY;
m_axi_arid <= {P_AXI_ID_WIDTH{1'b0}};
m_axi_araddr <= i_rd_addr;
m_axi_arlen <= i_rd_len;
m_axi_arsize <= C_AXI_SIZE;
m_axi_arburst <= C_AXI_BURST_INCR;
m_axi_arlock <= 1'b0;
m_axi_arcache <= 4'b0011;
m_axi_arprot <= 3'b000;
m_axi_arqos <= 4'b0000;
m_axi_arvalid <= 1'b1;
r_rd_state <= RD_ADDR;
end
end
RD_ADDR: begin
if (m_axi_arvalid && m_axi_arready) begin
m_axi_arvalid <= 1'b0;
m_axi_rready <= 1'b1;
r_rd_state <= RD_DATA;
end
end
RD_DATA: begin
if (m_axi_rvalid && m_axi_rready) begin
o_rd_data <= m_axi_rdata;
o_rd_beat_valid <= 1'b1;
o_rd_beat_index <= r_rd_count;
o_rd_beat_data <= m_axi_rdata;
o_rd_beat_last <= m_axi_rlast;
if (m_axi_rresp != C_AXI_RESP_OKAY) begin
r_rd_error <= 1'b1;
end
if (m_axi_rlast || (r_rd_count == r_rd_len)) begin
o_rd_resp <= (r_rd_error || (m_axi_rresp != C_AXI_RESP_OKAY) ||
!m_axi_rlast || (r_rd_count != r_rd_len)) ?
C_AXI_RESP_SLVERR : C_AXI_RESP_OKAY;
o_rd_done <= 1'b1;
o_rd_busy <= 1'b0;
m_axi_rready <= 1'b0;
r_rd_state <= RD_IDLE;
end else begin
r_rd_count <= r_rd_count + 8'd1;
end
end
end
default: begin
r_rd_state <= RD_IDLE;
end
endcase
end
end
endmodule
tb.sv
verilog
`timescale 1ns/1ps
module tb;
localparam P_AXI_ADDR_WIDTH = 32;
localparam P_AXI_DATA_WIDTH = 32;
localparam P_AXI_ID_WIDTH = 1;
localparam [P_AXI_ADDR_WIDTH-1:0] C_BASE_ADDR = 32'h43c0_0000;
localparam [P_AXI_ADDR_WIDTH-1:0] C_SLOT0_CTRL_ADDR = C_BASE_ADDR + 32'h0000_0000;
localparam [P_AXI_ADDR_WIDTH-1:0] C_SLOT0_DATA_ADDR = C_BASE_ADDR + 32'h0000_0004;
localparam [P_AXI_ADDR_WIDTH-1:0] C_SLOT1_BASE_ADDR = C_BASE_ADDR + (32'd65 << 2);
reg i_clk;
reg i_rst_n;
reg r_wr_start;
reg [P_AXI_ADDR_WIDTH-1:0] r_wr_addr;
reg [7:0] r_wr_len;
reg [P_AXI_DATA_WIDTH-1:0] r_wr_data;
wire w_wr_busy;
wire w_wr_done;
wire [1:0] w_wr_resp;
reg r_rd_start;
reg [P_AXI_ADDR_WIDTH-1:0] r_rd_addr;
reg [7:0] r_rd_len;
wire w_rd_busy;
wire w_rd_done;
wire [P_AXI_DATA_WIDTH-1:0] w_rd_data;
wire [1:0] w_rd_resp;
wire w_rd_beat_valid;
wire [7:0] w_rd_beat_index;
wire [P_AXI_DATA_WIDTH-1:0] w_rd_beat_data;
wire w_rd_beat_last;
wire [P_AXI_ADDR_WIDTH-1:0] w_axi_awaddr;
wire [P_AXI_ID_WIDTH-1:0] w_axi_awid;
wire [7:0] w_axi_awlen;
wire [2:0] w_axi_awsize;
wire [1:0] w_axi_awburst;
wire w_axi_awlock;
wire [3:0] w_axi_awcache;
wire [2:0] w_axi_awprot;
wire [3:0] w_axi_awqos;
wire w_axi_awvalid;
wire w_axi_awready;
wire [P_AXI_DATA_WIDTH-1:0] w_axi_wdata;
wire [(P_AXI_DATA_WIDTH/8)-1:0] w_axi_wstrb;
wire w_axi_wlast;
wire w_axi_wvalid;
wire w_axi_wready;
wire [P_AXI_ID_WIDTH-1:0] w_axi_bid;
wire [1:0] w_axi_bresp;
wire w_axi_bvalid;
wire w_axi_bready;
wire [P_AXI_ADDR_WIDTH-1:0] w_axi_araddr;
wire [P_AXI_ID_WIDTH-1:0] w_axi_arid;
wire [7:0] w_axi_arlen;
wire [2:0] w_axi_arsize;
wire [1:0] w_axi_arburst;
wire w_axi_arlock;
wire [3:0] w_axi_arcache;
wire [2:0] w_axi_arprot;
wire [3:0] w_axi_arqos;
wire w_axi_arvalid;
wire w_axi_arready;
wire [P_AXI_ID_WIDTH-1:0] w_axi_rid;
wire [P_AXI_DATA_WIDTH-1:0] w_axi_rdata;
wire [1:0] w_axi_rresp;
wire w_axi_rlast;
wire w_axi_rvalid;
wire w_axi_rready;
wire w_led;
wire w_irq;
wire w_debug;
axi4full_master #(
.P_AXI_ADDR_WIDTH(P_AXI_ADDR_WIDTH),
.P_AXI_DATA_WIDTH(P_AXI_DATA_WIDTH),
.P_AXI_ID_WIDTH (P_AXI_ID_WIDTH)
) u_axi4full_master (
.i_clk(i_clk), .i_rst_n(i_rst_n),
.i_wr_start(r_wr_start), .i_wr_addr(r_wr_addr), .i_wr_len(r_wr_len), .i_wr_data(r_wr_data),
.o_wr_busy(w_wr_busy), .o_wr_done(w_wr_done), .o_wr_resp(w_wr_resp),
.i_rd_start(r_rd_start), .i_rd_addr(r_rd_addr), .i_rd_len(r_rd_len),
.o_rd_busy(w_rd_busy), .o_rd_done(w_rd_done), .o_rd_data(w_rd_data), .o_rd_resp(w_rd_resp),
.o_rd_beat_valid(w_rd_beat_valid), .o_rd_beat_index(w_rd_beat_index),
.o_rd_beat_data(w_rd_beat_data), .o_rd_beat_last(w_rd_beat_last),
.m_axi_awid(w_axi_awid), .m_axi_awaddr(w_axi_awaddr), .m_axi_awlen(w_axi_awlen),
.m_axi_awsize(w_axi_awsize), .m_axi_awburst(w_axi_awburst), .m_axi_awlock(w_axi_awlock),
.m_axi_awcache(w_axi_awcache), .m_axi_awprot(w_axi_awprot), .m_axi_awqos(w_axi_awqos),
.m_axi_awvalid(w_axi_awvalid), .m_axi_awready(w_axi_awready),
.m_axi_wdata(w_axi_wdata), .m_axi_wstrb(w_axi_wstrb), .m_axi_wlast(w_axi_wlast),
.m_axi_wvalid(w_axi_wvalid), .m_axi_wready(w_axi_wready),
.m_axi_bid(w_axi_bid), .m_axi_bresp(w_axi_bresp), .m_axi_bvalid(w_axi_bvalid), .m_axi_bready(w_axi_bready),
.m_axi_arid(w_axi_arid), .m_axi_araddr(w_axi_araddr), .m_axi_arlen(w_axi_arlen),
.m_axi_arsize(w_axi_arsize), .m_axi_arburst(w_axi_arburst), .m_axi_arlock(w_axi_arlock),
.m_axi_arcache(w_axi_arcache), .m_axi_arprot(w_axi_arprot), .m_axi_arqos(w_axi_arqos),
.m_axi_arvalid(w_axi_arvalid), .m_axi_arready(w_axi_arready),
.m_axi_rid(w_axi_rid), .m_axi_rdata(w_axi_rdata), .m_axi_rresp(w_axi_rresp),
.m_axi_rlast(w_axi_rlast), .m_axi_rvalid(w_axi_rvalid), .m_axi_rready(w_axi_rready)
);
top u_top (
.s_axi_aclk(i_clk), .s_axi_aresetn(i_rst_n),
.s_axi_awid(w_axi_awid), .s_axi_awaddr(w_axi_awaddr), .s_axi_awlen(w_axi_awlen), .s_axi_awsize(w_axi_awsize),
.s_axi_awburst(w_axi_awburst), .s_axi_awlock(w_axi_awlock), .s_axi_awcache(w_axi_awcache), .s_axi_awprot(w_axi_awprot),
.s_axi_awqos(w_axi_awqos), .s_axi_awvalid(w_axi_awvalid), .s_axi_awready(w_axi_awready),
.s_axi_wdata(w_axi_wdata), .s_axi_wstrb(w_axi_wstrb), .s_axi_wlast(w_axi_wlast), .s_axi_wvalid(w_axi_wvalid), .s_axi_wready(w_axi_wready),
.s_axi_bid(w_axi_bid), .s_axi_bresp(w_axi_bresp), .s_axi_bvalid(w_axi_bvalid), .s_axi_bready(w_axi_bready),
.s_axi_arid(w_axi_arid), .s_axi_araddr(w_axi_araddr), .s_axi_arlen(w_axi_arlen), .s_axi_arsize(w_axi_arsize),
.s_axi_arburst(w_axi_arburst), .s_axi_arlock(w_axi_arlock), .s_axi_arcache(w_axi_arcache), .s_axi_arprot(w_axi_arprot),
.s_axi_arqos(w_axi_arqos), .s_axi_arvalid(w_axi_arvalid), .s_axi_arready(w_axi_arready),
.s_axi_rid(w_axi_rid), .s_axi_rdata(w_axi_rdata), .s_axi_rresp(w_axi_rresp), .s_axi_rlast(w_axi_rlast),
.s_axi_rvalid(w_axi_rvalid), .s_axi_rready(w_axi_rready),
.o_led(w_led),
.o_irq(w_irq),
.o_debug(w_debug)
);
initial begin
i_clk = 1'b0;
forever #10 i_clk = ~i_clk;
end
task reset_dut;
begin
i_rst_n <= 1'b0;
r_wr_start <= 1'b0; r_wr_addr <= 32'd0; r_wr_len <= 8'd0; r_wr_data <= 32'd0;
r_rd_start <= 1'b0; r_rd_addr <= 32'd0; r_rd_len <= 8'd0;
repeat (5) @(posedge i_clk);
i_rst_n <= 1'b1;
repeat (2) @(posedge i_clk);
end
endtask
task axi_write;
input [P_AXI_ADDR_WIDTH-1:0] addr;
input [P_AXI_DATA_WIDTH-1:0] data;
begin
@(posedge i_clk);
r_wr_addr <= addr; r_wr_len <= 8'd0; r_wr_data <= data; r_wr_start <= 1'b1;
@(posedge i_clk);
r_wr_start <= 1'b0;
wait (w_wr_done);
$display("[%0t ns] AXI WRITE addr=0x%08x data=0x%08x resp=%0d", $time, addr, data, w_wr_resp);
if (w_wr_resp != 2'b00) $fatal(1, "AXI write response error");
end
endtask
task axi_read_check;
input [P_AXI_ADDR_WIDTH-1:0] addr;
input [P_AXI_DATA_WIDTH-1:0] exp_data;
begin
@(posedge i_clk);
r_rd_addr <= addr; r_rd_len <= 8'd0; r_rd_start <= 1'b1;
@(posedge i_clk);
r_rd_start <= 1'b0;
wait (w_rd_done);
$display("[%0t ns] AXI READ addr=0x%08x data=0x%08x resp=%0d led=%0b irq=%0b debug=%0b", $time, addr, w_rd_data, w_rd_resp, w_led, w_irq, w_debug);
if (w_rd_resp != 2'b00) $fatal(1, "AXI read response error");
if (w_rd_data !== exp_data) $fatal(1, "AXI read mismatch addr=0x%08x exp=0x%08x got=0x%08x", addr, exp_data, w_rd_data);
end
endtask
task axi_burst_write;
input [P_AXI_ADDR_WIDTH-1:0] addr;
input [7:0] len;
input [P_AXI_DATA_WIDTH-1:0] base_data;
begin
@(posedge i_clk);
r_wr_addr <= addr; r_wr_len <= len; r_wr_data <= base_data; r_wr_start <= 1'b1;
@(posedge i_clk);
r_wr_start <= 1'b0;
wait (w_wr_done);
$display("[%0t ns] AXI BURST WRITE addr=0x%08x beats=%0d base=0x%08x resp=%0d", $time, addr, len + 1, base_data, w_wr_resp);
if (w_wr_resp != 2'b00) $fatal(1, "AXI burst write response error");
end
endtask
task axi_burst_read_check;
input [P_AXI_ADDR_WIDTH-1:0] addr;
input [7:0] len;
input [P_AXI_DATA_WIDTH-1:0] base_data;
integer beat;
begin
beat = 0;
@(posedge i_clk);
r_rd_addr <= addr; r_rd_len <= len; r_rd_start <= 1'b1;
@(posedge i_clk);
r_rd_start <= 1'b0;
while (beat <= len) begin
@(posedge i_clk);
if (w_rd_beat_valid) begin
$display("[%0t ns] AXI BURST READ beat=%0d data=0x%08x last=%0b", $time, w_rd_beat_index, w_rd_beat_data, w_rd_beat_last);
if (w_rd_beat_index !== beat[7:0]) $fatal(1, "AXI burst read index mismatch exp=%0d got=%0d", beat, w_rd_beat_index);
if (w_rd_beat_data !== (base_data + beat)) $fatal(1, "AXI burst read data mismatch beat=%0d exp=0x%08x got=0x%08x", beat, base_data + beat, w_rd_beat_data);
if ((beat == len) && !w_rd_beat_last) $fatal(1, "AXI burst read missing last");
if ((beat != len) && w_rd_beat_last) $fatal(1, "AXI burst read early last beat=%0d", beat);
beat = beat + 1;
end
end
wait (w_rd_done);
if (w_rd_resp != 2'b00) $fatal(1, "AXI burst read response error");
end
endtask
initial begin
reset_dut;
axi_write(C_SLOT0_CTRL_ADDR, 32'h0000_0004);
axi_burst_write(C_SLOT0_DATA_ADDR, 8'd63, 32'h0000_2000);
repeat (4) @(posedge i_clk);
if (!w_irq) $fatal(1, "slot0 IRQ was not asserted after 64-data processing");
if (w_debug) $fatal(1, "slot0 debug should be low after write burst end");
axi_read_check(C_SLOT0_CTRL_ADDR, 32'h0000_0006);
axi_burst_read_check(C_SLOT0_DATA_ADDR, 8'd63, 32'h0000_2000);
repeat (2) @(posedge i_clk);
if (w_debug) $fatal(1, "slot0 debug should be low after read burst end");
axi_write(C_SLOT0_CTRL_ADDR, 32'h0000_000c);
axi_read_check(C_SLOT0_CTRL_ADDR, 32'h0000_0004);
if (w_irq) $fatal(1, "slot0 IRQ was not cleared");
axi_write(C_SLOT1_BASE_ADDR, 32'd1);
axi_read_check(C_SLOT1_BASE_ADDR, 32'd1);
repeat (5) @(posedge i_clk);
$display("AXI4-Full top test PASS");
$finish;
end
endmodule
测试
bash
$:0
AtShell commands:
0.help - list cmd
1.clean - clean screen
2.read - read reg
3.write - write reg
4.full_write - full_write
5.full_read - full_read
$:4
full_write OK dst=0x43C00004 words=64 burst=16 first=0x00000001 last=0x00000040 sum=0x00000820 ms=0
$:5
1 [1] : 1
2 [2] : 2
...
full_read: src=0x43C00004 words=64 burst=16 us=5
$:
在读取64个u32的过程中传输了4个地址,突发了4次
AXI4-Full 协议允许一次 burst 传 64 个 u32,
但 PS PL330 DMA 这个 master 的 burst 配置字段最大是 16 beat
