Acceso a DDR mediante el Puerto de Alto Rendimiento S_AXI_HP0 en Zynq

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:

  1. REPOSO: Espera la señal de inicio de transacción.
  2. ESCRITURA: Genera ráfagas de escritura AXI hacia la DDR.
  3. LECTURA: Lee la misma región de memoria y compara cada dato leído con el valor esperado.
  4. 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;
}

Etiquetas: Zynq AXI4 DDR S_AXI_HP0 Verilog

Publicado el 10-21 09:23