Zynq中级开发七项必修课-第三课:S_AXI_GP0 主动访问 PS 地址空间
目标
- 1.0 编写 AXI-Lite Master:按键计数 → 写入 PS 内存
- 1.1 PL 触发中断 → PS 响应并串口打印按键计数值
BD图

axi_lite_master.v
// =====================================================
// AXI4-Lite Simple Master (single-shot, non-pipelined)
// - Pure Verilog-2001
// - Robust to 0-cycle / very-fast responses (no miss)
// - Works with SmartConnect / Zynq PS S_AXI_GP*
// =====================================================
module axi_lite_master #(parameter ADDR_WIDTH = 32,parameter DATA_WIDTH = 32
)(input wire clk,input wire rst_n, // active-low reset, sync to clk// ---------------- AXI4-Lite Master IF ----------------// Write addressoutput reg [ADDR_WIDTH-1:0] m_axi_awaddr,output reg [2:0] m_axi_awprot,output reg m_axi_awvalid,input wire m_axi_awready,// Write dataoutput reg [DATA_WIDTH-1:0] m_axi_wdata,output reg [(DATA_WIDTH/8)-1:0] m_axi_wstrb,output reg m_axi_wvalid,input wire m_axi_wready,// Write responseinput wire [1:0] m_axi_bresp, // 2'b00=OKAYinput wire m_axi_bvalid,output reg m_axi_bready,// Read addressoutput reg [ADDR_WIDTH-1:0] m_axi_araddr,output reg [2:0] m_axi_arprot,output reg m_axi_arvalid,input wire m_axi_arready,// Read data/respinput wire [DATA_WIDTH-1:0] m_axi_rdata,input wire [1:0] m_axi_rresp, // 2'b00=OKAYinput wire m_axi_rvalid,output reg m_axi_rready,// ---------------- User Side (single request at a time) ---------------input wire write_req, // pulse: 1clk = start one writeinput wire [ADDR_WIDTH-1:0] write_addr,input wire [DATA_WIDTH-1:0] write_data,output reg write_done, // pulse: 1clk when write completesoutput reg write_err, // latched for 1clk at doneinput wire read_req, // pulse: 1clk = start one readinput wire [ADDR_WIDTH-1:0] read_addr,output reg [DATA_WIDTH-1:0] read_data,output reg read_done, // pulse: 1clk when read completesoutput reg read_err, // latched for 1clk at doneoutput wire busy // 1=there's an in-flight txn
);// ---------------- Edge detect to make interface tolerant to level inputsreg wr_req_d, rd_req_d;wire wr_pulse = write_req & ~wr_req_d;wire rd_pulse = read_req & ~rd_req_d;// In-flight markers (no full FSM needed)wire wr_inflight = m_axi_awvalid | m_axi_wvalid | m_axi_bready;wire rd_inflight = m_axi_arvalid | m_axi_rready;assign busy = wr_inflight | rd_inflight;// ---------------- Registerslocalparam [2:0] PROT_DEFAULT = 3'b000;always @(posedge clk or negedge rst_n) beginif (!rst_n) beginwr_req_d <= 1'b0;rd_req_d <= 1'b0;m_axi_awaddr <= {ADDR_WIDTH{1'b0}};m_axi_awprot <= PROT_DEFAULT;m_axi_awvalid <= 1'b0;m_axi_wdata <= {DATA_WIDTH{1'b0}};m_axi_wstrb <= {(DATA_WIDTH/8){1'b0}};m_axi_wvalid <= 1'b0;m_axi_bready <= 1'b0;m_axi_araddr <= {ADDR_WIDTH{1'b0}};m_axi_arprot <= PROT_DEFAULT;m_axi_arvalid <= 1'b0;m_axi_rready <= 1'b0;write_done <= 1'b0;write_err <= 1'b0;read_done <= 1'b0;read_err <= 1'b0;read_data <= {DATA_WIDTH{1'b0}};end else begin// sample requests for edge detectionwr_req_d <= write_req;rd_req_d <= read_req;// default: clear 1-cycle pulseswrite_done <= 1'b0;read_done <= 1'b0;// ============================================================// WRITE: fire when wr_pulse && !busy// - AW/W raised together; BREADY asserted immediately// ============================================================if (wr_pulse && !busy) beginm_axi_awaddr <= write_addr;m_axi_awprot <= PROT_DEFAULT;m_axi_awvalid <= 1'b1;m_axi_wdata <= write_data;m_axi_wstrb <= {(DATA_WIDTH/8){1'b1}};m_axi_wvalid <= 1'b1;m_axi_bready <= 1'b1; // ready to accept response ASAPwrite_err <= 1'b0; // clear error for new txnend// AW handshake completes -> drop AWVALIDif (m_axi_awvalid && m_axi_awready)m_axi_awvalid <= 1'b0;// W handshake completes -> drop WVALIDif (m_axi_wvalid && m_axi_wready)m_axi_wvalid <= 1'b0;// B response: complete write on any cycle handshake occursif (m_axi_bvalid && m_axi_bready) beginm_axi_bready <= 1'b0;write_done <= 1'b1;write_err <= (m_axi_bresp != 2'b00); // OKAY=00end// ============================================================// READ: fire when rd_pulse && !busy// - AR raised; RREADY asserted immediately// ============================================================if (rd_pulse && !busy) beginm_axi_araddr <= read_addr;m_axi_arprot <= PROT_DEFAULT;m_axi_arvalid <= 1'b1;m_axi_rready <= 1'b1; // be ready for 0-cycle dataread_err <= 1'b0;end// AR handshake completes -> drop ARVALIDif (m_axi_arvalid && m_axi_arready)m_axi_arvalid <= 1'b0;// R data: complete read on any cycle handshake occursif (m_axi_rvalid && m_axi_rready) beginm_axi_rready <= 1'b0;read_data <= m_axi_rdata;read_done <= 1'b1;read_err <= (m_axi_rresp != 2'b00); // OKAY=00endendendendmodule
key_debounce.v
// ============================================================================
// Module name : key_debounce
// Author : ming
// Description : 按键消抖模块
// - 按键持续按下超过设定时间后,输出一个时钟周期脉冲
// - 累计按键按下次数
// Parameters : P_CLK_FREQ_MHZ - 输入时钟频率 (MHz)
// P_DEBOUNCE_MS - 消抖时间 (ms)
// L_CNT_WIDTH - 计数器位宽
// ============================================================================module key_debounce
#(parameter P_CLK_FREQ_MHZ = 50, // 时钟频率 MHzparameter P_DEBOUNCE_MS = 20, // 消抖时间 msparameter L_CNT_WIDTH = 32 // 计数器位宽
)
(input wire i_clk, // 系统时钟input wire i_rst_n, // 全局复位,低有效input wire i_key, // 按键输入(低有效)output reg o_key_pulse, // 消抖脉冲output reg [31:0] o_key_pulse_cnt // 按键次数
);// 根据时钟频率和消抖时间计算最大计数值localparam L_MAX_CNT = P_CLK_FREQ_MHZ * 1000 * P_DEBOUNCE_MS;reg [L_CNT_WIDTH-1:0] r_cnt;// 计数器:按键按下计时always@(posedge i_clk or negedge i_rst_n)if(!i_rst_n)r_cnt <= {(L_CNT_WIDTH+1){1'b0}};else if(i_key == 1)r_cnt <= {(L_CNT_WIDTH+1){1'b0}};else if(i_key == 0 && r_cnt < L_MAX_CNT-1)r_cnt <= r_cnt + 1'b1;elser_cnt <= r_cnt;// 输出脉冲:稳定按下超过设定时间,输出 1 个时钟周期脉冲always@(posedge i_clk or negedge i_rst_n)if(!i_rst_n) begin o_key_pulse <= 1'b0;o_key_pulse_cnt <= 32'd0;end else if(r_cnt == L_MAX_CNT-2) begin o_key_pulse <= 1'b1;o_key_pulse_cnt <= o_key_pulse_cnt + 1;end elseo_key_pulse <= 1'b0;endmodule
pulse_rise_counter.v
`timescale 1ns/1ps
// pulse_rise_counter.v — 上升沿计数器(简洁版)
// - 记录 i_sig 的上升沿个数到 o_count
// - 含两级同步,适合异步来源(IO/外设)
// - 计数回卷(溢出后从 0 重新计)
//
// 端口:
// i_clk : 时钟
// i_rst_n : 低有效复位
// i_sig : 需要计数的脉冲/电平信号
// o_count : 上升沿累计计数
module pulse_rise_counter #(parameter integer P_WIDTH = 32
)(input wire i_clk,input wire i_rst_n,input wire i_sig,output reg [P_WIDTH-1:0] o_count
);// 两级同步,避免亚稳态reg r_sync1, r_sync2;always @(posedge i_clk or negedge i_rst_n) beginif (!i_rst_n) beginr_sync1 <= 1'b0;r_sync2 <= 1'b0;o_count <= {P_WIDTH{1'b0}};end else beginr_sync1 <= i_sig;r_sync2 <= r_sync1;// 上升沿检测:前一拍0,这一拍1if (r_sync1 & ~r_sync2)o_count <= o_count + 1'b1;endendendmodule
blink_led.v
module blink_led #(parameter P_CLK_FREQ = 50_000_000, // 时钟频率parameter P_BLINK_HZ = 1 // 闪烁频率
)(input i_clk,input i_rst_n,output o_led
);localparam integer L_HALF_CYCLES = P_CLK_FREQ / (2 * P_BLINK_HZ);reg [$clog2(L_HALF_CYCLES):0] r_cnt = 0;reg r_led;assign o_led = r_led;always @(posedge i_clk or negedge i_rst_n) beginif (!i_rst_n) beginr_cnt <= 0;r_led <= 0;end else beginif (r_cnt == L_HALF_CYCLES - 1) beginr_cnt <= 0;r_led <= ~r_led;end else beginr_cnt <= r_cnt + 1;endendendendmodule
system.xdc
create_clock -period 20.000 -name PL_GCLK [get_ports PL_GCLK]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports PL_GCLK]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports PL_RESET]
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports KEY0]
set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports KEY1]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports LED0]
set_property -dict {PACKAGE_PIN L15 IOSTANDARD LVCMOS33} [get_ports LED1]
set_property -dict {PACKAGE_PIN K14 IOSTANDARD LVCMOS33} [get_ports PL_UART_RXD]
set_property -dict {PACKAGE_PIN M15 IOSTANDARD LVCMOS33} [get_ports PL_UART_TXD]
PS 裸机测试
#include "xil_io.h"
#include "xil_mmu.h"
#include "xil_printf.h"#include "xparameters.h"
#include "xscugic.h"
#include <stdio.h>
#define SHARE_MEM_BASE 0x01000000U
#define BASE_ADDR 0x01000000U
#define MAX_INDEX 30
#define PL_IRQ_ID61 61
#define GIC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID
XScuGic intc;
static void PL_IRQHandler(void *ctx) {uintptr_t irq_src = (uintptr_t)ctx;static int irq_cnt=0;irq_cnt++;xil_printf("PL%d triggered %d!\r\n",irq_src,irq_cnt);int regInx=0;u32 read_val = Xil_In32(BASE_ADDR + 4 * regInx);xil_printf("[r %d] = 0x%08X / %u\r\n", regInx, read_val, read_val);
}
static int IRQ_Init(void)
{int status;XScuGic_Config *cfg = XScuGic_LookupConfig(GIC_DEVICE_ID);if (!cfg) return XST_FAILURE;status = XScuGic_CfgInitialize(&intc, cfg, cfg->CpuBaseAddress);if (status != XST_SUCCESS) return status;Xil_ExceptionInit();Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,&intc);XScuGic_SetPriorityTriggerType(&intc, PL_IRQ_ID61, 0x80, 0x3);status = XScuGic_Connect(&intc, PL_IRQ_ID61,(Xil_ExceptionHandler)PL_IRQHandler, (void*)0);if (status != XST_SUCCESS) return status;XScuGic_Enable(&intc, PL_IRQ_ID61);Xil_ExceptionEnable();return XST_SUCCESS;
}int main() {xil_printf("S_AXI_GP0 test.\n");Xil_SetTlbAttributes(SHARE_MEM_BASE, NORM_NONCACHE | SHAREABLE);IRQ_Init();char cmd;int index;u32 value;while (1) {xil_printf("> ");if (scanf(" %c", &cmd) != 1)continue;switch (cmd){case 'r':if (scanf("%d", &index) == 1){if (index < 0 || index > MAX_INDEX){xil_printf("Error: index out of range [0 ~ %d]\r\n", MAX_INDEX);while (getchar() != '\n');continue;}u32 read_val = Xil_In32(BASE_ADDR + 4 * index);xil_printf("[r %d] = 0x%08X / %u\r\n", index, read_val, read_val);}else{xil_printf("Invalid input. Use: r <index>\r\n");while (getchar() != '\n');}break;case 'w':if (scanf("%d %u", &index, &value) == 2){if (index < 0 || index > MAX_INDEX){xil_printf("Error: index out of range [0 ~ %d]\r\n", MAX_INDEX);while (getchar() != '\n');continue;}Xil_Out32(BASE_ADDR + 4 * index, value);xil_printf("[w %d] = 0x%08X / %u\r\n", index, value, value);}else{xil_printf("Invalid input. Use: w <index> <value>\r\n");while (getchar() != '\n');}break;case 'l':{break;}default:xil_printf("Unknown command '%c'. Use 'r' or 'w'.\r\n", cmd);while (getchar() != '\n');break;}}return 0;
}
测试结果
