Este artículo describe cómo utilizar un puerto AXI-Full Masteer para acceder a la memoria DDR del Processing System (PS) en un SoC Zynq a través de la interfaz de alto rendimiento S_AXI_HP0. El diseño lógico en la Programmable Logic (PL) captura los pulsos de un botón, escribe un bloque de datos consecutivos en la memoria y genera una interrupción hacia el PS para validar la transferencia.
Selección de Interfaz AXI en Zynq
La arquitectura Zynq ofrece diferentes puertos para conectar la PL con la memoria del PS:
- GP (General Purpose): Ideal para periféricos simples que no requieren alto ancho de banda.
- HP (High Performance): Diseñado para transferencias masivas de datos, ofreciendo mayor rendimiento y soporte para ráfagas.
- ACP (Accelerator Coherency Port): Mantiene coherencia de caché con los procesadores ARM, útil cuando se requiere que la PL y el PS compartan datos sin inconsistencias, pero añade complejidad.
Para esta implementación, utilizaremos el puerto HP para aprovechar su capacidad de alto rendimiento en operaciones de lectura y escritura hacia la DDR.
Lógica Personalizada: Maestro AXI4 Full
El módulo axi4_hp_ddr_master controla las transferencias. Al detectar un flanco de subida en la señal de inicio, el módulo escribe 1024 palabras de 32 bits en la dirección base 0x10000000. Los valores escritos comienzan desde el contador de pulsos del botón y se incrementan en uno. Posteriormente, lee la misma región y compara los datos. Si la validación es exitosa, se activa una señal de interrupción.
El flujo de la máquina de estados es el siguiente:
- REPOSO: Espera la señal de inicio de transacción.
- ESCRITURA: Genera ráfagas de escritura AXI hacia la DDR.
- LECTURA: Lee la misma región de memoria y compara cada dato leído con el valor esperado.
- VERIFICACION: Evalúa si hubo errores de respuesta o datos erróneos, genera la interrupción y vuelve a REPOSO.
`timescale 1 ns / 1 ps
module axi4_hp_ddr_master #(
parameter C_BASE_ADDR_DDR = 32'h10000000,
parameter C_BURST_LENGTH = 16,
parameter C_ADDR_WIDTH = 32,
parameter C_DATA_WIDTH = 32
) (
input wire [31:0] i_btn_count,
input wire INIT_AXI_TXN,
output wire TXN_DONE,
output reg [2:0] ERROR,
input wire M_AXI_ACLK,
input wire M_AXI_ARESETN,
// Write Address Channel
output wire [C_ADDR_WIDTH-1:0] M_AXI_AWADDR,
output wire [7:0] M_AXI_AWLEN,
output wire [2:0] M_AXI_AWSIZE,
output wire [1:0] M_AXI_AWBURST,
output wire M_AXI_AWVALID,
input wire M_AXI_AWREADY,
// Write Data Channel
output wire [C_DATA_WIDTH-1:0] M_AXI_WDATA,
output wire M_AXI_WLAST,
output wire M_AXI_WVALID,
input wire M_AXI_WREADY,
// Write Response Channel
input wire [1:0] M_AXI_BRESP,
input wire M_AXI_BVALID,
output wire M_AXI_BREADY,
// Read Address Channel
output wire [C_ADDR_WIDTH-1:0] M_AXI_ARADDR,
output wire [7:0] M_AXI_ARLEN,
output wire [2:0] M_AXI_ARSIZE,
output wire [1:0] M_AXI_ARBURST,
output wire M_AXI_ARVALID,
input wire M_AXI_ARREADY,
// Read Data Channel
input wire [C_DATA_WIDTH-1:0] M_AXI_RDATA,
input wire [1:0] M_AXI_RRESP,
input wire M_AXI_RLAST,
input wire M_AXI_RVALID,
output wire M_AXI_RREADY
);
function integer calc_log2;
input integer value;
begin
for (calc_log2 = 0; value > 0; calc_log2 = calc_log2 + 1)
value = value >> 1;
end
endfunction
localparam C_TX_NUM_WIDTH = calc_log2(C_BURST_LENGTH - 1);
localparam C_TOTAL_BURSTS = 12 - calc_log2((C_BURST_LENGTH * C_DATA_WIDTH / 8) - 1);
localparam [1:0] ST_IDLE = 2'b00, ST_WR = 2'b01, ST_RD = 2'b10, ST_CHK = 2'b11;
reg [1:0] state_reg;
reg [C_ADDR_WIDTH-1:0] aw_addr_reg, ar_addr_reg;
reg aw_valid_reg, w_valid_reg, w_last_reg, b_ready_reg;
reg ar_valid_reg, r_ready_reg;
reg [C_TX_NUM_WIDTH:0] w_idx_reg, r_idx_reg;
reg [C_TOTAL_BURSTS:0] w_burst_cnt, r_burst_cnt;
reg start_wr_pulse, start_rd_pulse;
reg wr_active, rd_active, wr_cmpl, rd_cmpl;
reg mismatch_flag, err_flag;
reg [C_DATA_WIDTH-1:0] expected_val;
reg init_ff1, init_ff2;
wire init_edge = init_ff1 & ~init_ff2;
wire w_handshake = M_AXI_WREADY & w_valid_reg;
wire r_handshake = M_AXI_RVALID & r_ready_reg;
wire wr_resp_err = b_ready_reg & M_AXI_BVALID & M_AXI_BRESP[1];
wire rd_resp_err = r_ready_reg & M_AXI_RVALID & M_AXI_RRESP[1];
wire [C_TX_NUM_WIDTH+2:0] burst_bytes = C_BURST_LENGTH * C_DATA_WIDTH / 8;
assign M_AXI_AWADDR = C_BASE_ADDR_DDR + aw_addr_reg;
assign M_AXI_AWLEN = C_BURST_LENGTH - 1;
assign M_AXI_AWSIZE = calc_log2((C_DATA_WIDTH / 8) - 1);
assign M_AXI_AWBURST = 2'b01; // INCR
assign M_AXI_AWVALID = aw_valid_reg;
assign M_AXI_WDATA = i_btn_count + w_idx_reg + (w_burst_cnt * C_BURST_LENGTH);
assign M_AXI_WLAST = w_last_reg;
assign M_AXI_WVALID = w_valid_reg;
assign M_AXI_BREADY = b_ready_reg;
assign M_AXI_ARADDR = C_BASE_ADDR_DDR + ar_addr_reg;
assign M_AXI_ARLEN = C_BURST_LENGTH - 1;
assign M_AXI_ARSIZE = calc_log2((C_DATA_WIDTH / 8) - 1);
assign M_AXI_ARBURST = 2'b01;
assign M_AXI_ARVALID = ar_valid_reg;
assign M_AXI_RREADY = r_ready_reg;
assign TXN_DONE = (state_reg == ST_CHK) & ~err_flag;
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN) begin
init_ff1 <= 1'b0;
init_ff2 <= 1'b0;
end else begin
init_ff1 <= INIT_AXI_TXN;
init_ff2 <= init_ff1;
end
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) aw_valid_reg <= 1'b0;
else if (~aw_valid_reg && start_wr_pulse) aw_valid_reg <= 1'b1;
else if (M_AXI_AWREADY && aw_valid_reg) aw_valid_reg <= 1'b0;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) aw_addr_reg <= 'b0;
else if (M_AXI_AWREADY && aw_valid_reg) aw_addr_reg <= aw_addr_reg + burst_bytes;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) w_valid_reg <= 1'b0;
else if (~w_valid_reg && start_wr_pulse) w_valid_reg <= 1'b1;
else if (w_handshake && w_last_reg) w_valid_reg <= 1'b0;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) w_last_reg <= 1'b0;
else if ((w_idx_reg == C_BURST_LENGTH - 2 && w_handshake) || C_BURST_LENGTH == 1) w_last_reg <= 1'b1;
else if (w_handshake) w_last_reg <= 1'b0;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge || start_wr_pulse) w_idx_reg <= 0;
else if (w_handshake && (w_idx_reg != C_BURST_LENGTH - 1)) w_idx_reg <= w_idx_reg + 1;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) b_ready_reg <= 1'b0;
else if (M_AXI_BVALID && ~b_ready_reg) b_ready_reg <= 1'b1;
else if (b_ready_reg) b_ready_reg <= 1'b0;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) ar_valid_reg <= 1'b0;
else if (~ar_valid_reg && start_rd_pulse) ar_valid_reg <= 1'b1;
else if (M_AXI_ARREADY && ar_valid_reg) ar_valid_reg <= 1'b0;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) ar_addr_reg <= 'b0;
else if (M_AXI_ARREADY && ar_valid_reg) ar_addr_reg <= ar_addr_reg + burst_bytes;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge || start_rd_pulse) r_idx_reg <= 0;
else if (r_handshake && (r_idx_reg != C_BURST_LENGTH - 1)) r_idx_reg <= r_idx_reg + 1;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) r_ready_reg <= 1'b0;
else if (M_AXI_RVALID) begin
if (M_AXI_RLAST && r_ready_reg) r_ready_reg <= 1'b0;
else r_ready_reg <= 1'b1;
end
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) begin
expected_val <= i_btn_count;
mismatch_flag <= 1'b0;
end else if (r_handshake) begin
if (M_AXI_RDATA != expected_val) mismatch_flag <= 1'b1;
else expected_val <= expected_val + 1;
end
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) w_burst_cnt <= 'b0;
else if (M_AXI_AWREADY && aw_valid_reg && !w_burst_cnt[C_TOTAL_BURSTS]) w_burst_cnt <= w_burst_cnt + 1;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) r_burst_cnt <= 'b0;
else if (M_AXI_ARREADY && ar_valid_reg && !r_burst_cnt[C_TOTAL_BURSTS]) r_burst_cnt <= r_burst_cnt + 1;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN) begin
state_reg <= ST_IDLE;
start_wr_pulse <= 1'b0;
start_rd_pulse <= 1'b0;
err_flag <= 1'b0;
ERROR <= 3'b0;
end else begin
case (state_reg)
ST_IDLE: begin
if (init_edge) state_reg <= ST_WR;
end
ST_WR: begin
if (wr_cmpl) state_reg <= ST_RD;
else if (~aw_valid_reg && ~start_wr_pulse && ~wr_active) start_wr_pulse <= 1'b1;
else start_wr_pulse <= 1'b0;
end
ST_RD: begin
if (rd_cmpl) state_reg <= ST_CHK;
else if (~ar_valid_reg && ~start_rd_pulse && ~rd_active) start_rd_pulse <= 1'b1;
else start_rd_pulse <= 1'b0;
end
ST_CHK: begin
err_flag <= mismatch_flag | wr_resp_err | rd_resp_err;
ERROR <= {mismatch_flag, wr_resp_err, rd_resp_err};
state_reg <= ST_IDLE;
end
default: state_reg <= ST_IDLE;
endcase
end
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) wr_active <= 1'b0;
else if (start_wr_pulse) wr_active <= 1'b1;
else if (M_AXI_BVALID && b_ready_reg) wr_active <= 1'b0;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) wr_cmpl <= 1'b0;
else if (M_AXI_BVALID && b_ready_reg && w_burst_cnt[C_TOTAL_BURSTS]) wr_cmpl <= 1'b1;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) rd_active <= 1'b0;
else if (start_rd_pulse) rd_active <= 1'b1;
else if (M_AXI_RVALID && r_ready_reg && M_AXI_RLAST) rd_active <= 1'b0;
end
always @(posedge M_AXI_ACLK) begin
if (!M_AXI_ARESETN || init_edge) rd_cmpl <= 1'b0;
else if (M_AXI_RVALID && r_ready_reg && (r_idx_reg == C_BURST_LENGTH - 1) && r_burst_cnt[C_TOTAL_BURSTS]) rd_cmpl <= 1'b1;
end
endmodule
Módulo Antirrebote para Botones
Para garantizar una activación limpia de las transferencias, se emplea un filtro de rebote que genera un pulso único tras presionar un botón físico.
module btn_filter #(
parameter P_CLK_MHZ = 50,
parameter P_DEBOUNCE_MS = 20,
parameter P_CNT_W = 32
) (
input wire i_clk,
input wire i_rst_n,
input wire i_btn,
output reg o_btn_pulse,
output reg [31:0] o_btn_count
);
localparam L_MAX = P_CLK_MHZ * 1000 * P_DEBOUNCE_MS;
reg [P_CNT_W-1:0] r_filter_cnt;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) r_filter_cnt <= 'b0;
else if (i_btn) r_filter_cnt <= 'b0;
else if (!i_btn && r_filter_cnt < L_MAX - 1) r_filter_cnt <= r_filter_cnt + 1;
end
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
o_btn_pulse <= 1'b0;
o_btn_count <= 32'd0;
end else if (r_filter_cnt == L_MAX - 2) begin
o_btn_pulse <= 1'b1;
o_btn_count <= o_btn_count + 1;
end else begin
o_btn_pulse <= 1'b0;
end
end
endmodule
Restricciones de Hardware (XDC)
create_clock -period 20.000 -name PL_CLK [get_ports PL_CLK]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports PL_CLK]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports PL_RST_N]
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports BTN0]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports LED0]
Aplicación Bare-metal en PS
El software en el procesador ARM configura el controlador de interrupciones (GIC) para atender el evento proveniente de la PL. Al recibir la interrupción, lee el primer y último reigstro de la región DDR para verificar que la diferencia sea exactaemnte 1023.
#include "xil_io.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xscugic.h"
#define DDR_BASE_ADDR 0x10000000U
#define MAX_REG_IDX 1023
#define PL_IRQ_ID 61
XScuGic gic_inst;
static void PL_IrqHandler(void *instance) {
u32 val_start = Xil_In32(DDR_BASE_ADDR);
u32 val_end = Xil_In32(DDR_BASE_ADDR + 4 * MAX_REG_IDX);
xil_printf("IRQ recibida. Inicio: %u, Fin: %u, Diferencia: %d\r\n",
val_start, val_end, val_end - val_start);
}
static int Init_GIC(void) {
XScuGic_Config *cfg = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID);
if (!cfg) return XST_FAILURE;
XScuGic_CfgInitialize(&gic_inst, cfg, cfg->CpuBaseAddress);
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
&gic_inst);
XScuGic_SetPriorityTriggerType(&gic_inst, PL_IRQ_ID, 0x80, 0x3);
XScuGic_Connect(&gic_inst, PL_IRQ_ID, (Xil_ExceptionHandler)PL_IrqHandler, NULL);
XScuGic_Enable(&gic_inst, PL_IRQ_ID);
Xil_ExceptionEnable();
return XST_SUCCESS;
}
int main() {
xil_printf("Iniciando prueba S_AXI_HP0...\r\n");
Xil_SetTlbAttributes(DDR_BASE_ADDR, NORM_NONCACHE | SHAREABLE);
Init_GIC();
char cmd;
while (1) {
xil_printf("> ");
if (scanf(" %c", &cmd) != 1) continue;
if (cmd == 'r') {
int idx;
if (scanf("%d", &idx) == 1 && idx >= 0 && idx <= MAX_REG_IDX) {
u32 val = Xil_In32(DDR_BASE_ADDR + 4 * idx);
xil_printf("Reg[%d] = %u\r\n", idx, val);
}
} else if (cmd == 'w') {
int idx; u32 val;
if (scanf("%d %u", &idx, &val) == 2 && idx >= 0 && idx <= MAX_REG_IDX) {
Xil_Out32(DDR_BASE_ADDR + 4 * idx, val);
xil_printf("Reg[%d] escrito con %u\r\n", idx, val);
}
}
}
return 0;
}