How does FPGA implement AXI Lite interface?

 AXI4-Lite is the “simple, memory-mapped” subset of AXI: no bursts, one beat per transaction, at most one outstanding per direction, no IDs. Implementing it in an FPGA usually means building a tiny register file that the CPU can read/write.



Below is a compact, production-ready AXI4-Lite slave you can drop into your design (32-bit data, word-aligned addresses). It handles byte strobes, correct handshakes, and OKAY responses.


What you implement

Five channels (slave side, S_AXI_*):

  • Write address: AWADDR, AWVALID → AWREADY

  • Write data: WDATA, WSTRB, WVALID → WREADY

  • Write response: BRESP, BVALID ← BREADY

  • Read address: ARADDR, ARVALID → ARREADY

  • Read data/resp: RDATA, RRESP, RVALID ← RREADY

Rules (AXI-Lite):

  • Accept a write only when both AW and W fire (address and data are independent but you can require them to handshake in the same cycle).

  • After the write, return BRESP=OKAY with BVALID until BREADY.

  • For reads, handshake AR, then present RDATA and RRESP=OKAY with RVALID until RREADY.

  • Honor WSTRB for byte-granular writes.


Minimal register map (example)

AddressRegisterR/WNotes
0x00CTRLR/WBit0 start, Bit1 clear
0x04STATUSRBit0 done, Bit1 busy
0x08DATA_INR/WPayload/config
0x0CDATA_OUTRResult/status

You’ll wire these to your logic in the same clock domain as S_AXI_ACLK (or add CDC if not).


Verilog: AXI4-Lite slave with 4 registers

// axi_lite_regs.v : 32-bit AXI4-Lite register block (4 regs) module axi_lite_regs #( parameter integer C_S_AXI_DATA_WIDTH = 32, parameter integer C_S_AXI_ADDR_WIDTH = 6 // covers 0x00..0x3F )( 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 S_AXI_AWVALID, output reg 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 reg S_AXI_WREADY, // Write response channel output reg [1:0] S_AXI_BRESP, output reg 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 S_AXI_ARVALID, output reg S_AXI_ARREADY, // Read data channel output reg [C_S_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, output reg [1:0] S_AXI_RRESP, output reg S_AXI_RVALID, input wire S_AXI_RREADY, // User register view (optional external access) output reg [31:0] reg_ctrl, input wire [31:0] reg_status, // example: driven by user logic output reg [31:0] reg_data_in, input wire [31:0] reg_data_out ); localparam integer ADDR_LSB = $clog2(C_S_AXI_DATA_WIDTH/8); // =2 for 32-bit localparam integer OPT_MEM_ADDR_BITS = C_S_AXI_ADDR_WIDTH - ADDR_LSB; // Address decode helper: word address index wire [OPT_MEM_ADDR_BITS-1:0] aw_idx = S_AXI_AWADDR[ADDR_LSB +: OPT_MEM_ADDR_BITS]; wire [OPT_MEM_ADDR_BITS-1:0] ar_idx = S_AXI_ARADDR[ADDR_LSB +: OPT_MEM_ADDR_BITS]; // ------------------------------ // Write channel // ------------------------------ wire write_fire = S_AXI_AWVALID && S_AXI_WVALID && S_AXI_AWREADY && S_AXI_WREADY; integer i; always @(posedge S_AXI_ACLK) begin if(!S_AXI_ARESETN) begin S_AXI_AWREADY <= 1'b0; S_AXI_WREADY <= 1'b0; S_AXI_BVALID <= 1'b0; S_AXI_BRESP <= 2'b00; // OKAY reg_ctrl <= 32'h0000_0000; reg_data_in <= 32'h0000_0000; // reg_status/reg_data_out are read-only from bus in this example end else begin // Ready when idle, gate to cause simultaneous AW/W handshake S_AXI_AWREADY <= (~S_AXI_BVALID) && S_AXI_WVALID; S_AXI_WREADY <= (~S_AXI_BVALID) && S_AXI_AWVALID; // Perform write on the cycle both handshakes complete if (write_fire) begin case (aw_idx[3:0]) // only lower indices used (0..3 here) 4'h0: for (i=0;i<4;i=i+1) if (S_AXI_WSTRB[i]) reg_ctrl[8*i +: 8] <= S_AXI_WDATA[8*i +: 8]; 4'h2: for (i=0;i<4;i=i+1) if (S_AXI_WSTRB[i]) reg_data_in[8*i +: 8] <= S_AXI_WDATA[8*i +: 8]; default: ; // writes to STATUS/DATA_OUT ignored (RO) endcase S_AXI_BVALID <= 1'b1; S_AXI_BRESP <= 2'b00; // OKAY end // Complete write response if (S_AXI_BVALID && S_AXI_BREADY) begin S_AXI_BVALID <= 1'b0; end end end // ------------------------------ // Read channel // ------------------------------ reg [C_S_AXI_ADDR_WIDTH-1:0] araddr_lat; wire ar_fire = S_AXI_ARVALID && S_AXI_ARREADY; always @(posedge S_AXI_ACLK) begin if(!S_AXI_ARESETN) begin S_AXI_ARREADY <= 1'b0; S_AXI_RVALID <= 1'b0; S_AXI_RRESP <= 2'b00; // OKAY S_AXI_RDATA <= {C_S_AXI_DATA_WIDTH{1'b0}}; araddr_lat <= {C_S_AXI_ADDR_WIDTH{1'b0}}; end else begin // Accept new read address when not holding data S_AXI_ARREADY <= ~S_AXI_RVALID; if (ar_fire) begin araddr_lat <= S_AXI_ARADDR; // Prepare read data immediately (combinational read is also OK) case (ar_idx[3:0]) 4'h0: S_AXI_RDATA <= reg_ctrl; 4'h1: S_AXI_RDATA <= reg_status; 4'h2: S_AXI_RDATA <= reg_data_in; 4'h3: S_AXI_RDATA <= reg_data_out; default: S_AXI_RDATA <= 32'hDEAD_BEEF; endcase S_AXI_RRESP <= 2'b00; // OKAY S_AXI_RVALID <= 1'b1; end if (S_AXI_RVALID && S_AXI_RREADY) begin S_AXI_RVALID <= 1'b0; end end end endmodule

Notes on the template

  • Handshakes: AWREADY is only asserted when WVALID is high (and vice-versa). That forces address+data to complete in the same cycle and keeps logic simple and AXI-Lite compliant.

  • Byte strobes: WSTRB[i] guards each written byte.

  • Addresses: With 32-bit data, ADDR_LSB=2. The code looks at aw_idx[3:0] / ar_idx[3:0] so you can expand to more registers by growing C_S_AXI_ADDR_WIDTH.

  • RO vs RW: STATUS and DATA_OUT are wired as read-only from the bus; drive them from your logic.


Hooking to your logic (example)

  • Generate a one-shot “start” pulse when SW writes CTRL[0]=1:

    • Detect rising edge of reg_ctrl[0] or implement “write-1-to-start then auto-clear”.

  • Drive reg_status[1:0] from your FSM (busy, done).

  • Put results into reg_data_out in your logic clock domain. If that differs from S_AXI_ACLK, add CDC (e.g., a 2-FF sync for status bits, an async FIFO for data).


Common integration steps in Vivado (7-Series)

  1. Instantiate this module in your design with S_AXI_ACLK tied to the processor/interconnect clock and S_AXI_ARESETN to the shared active-low reset.

  2. Package as IP (optional) and add an AXI4-Lite interface; or drop it into a Block Design and expose S_AXI_*.

  3. In Address Editor, assign a base address (e.g., 0x4000_0000).

  4. If your user logic runs on a different clock, add proper CDC between these registers and your logic (status flags through 2-FF sync; data via async FIFO or handshake).


Verification tips

  • AXI-Lite write: AW, W → (one cycle later) B returns OKAY.

  • AXI-Lite read: AR → R returns OKAY with data.

  • Simulate byte-writes with varied WSTRB (e.g., write only the high byte).

  • Back-pressure: hold off BREADY or RREADY in the testbench to ensure the slave keeps BVALID/RVALID asserted as required.


Pitfalls to avoid

  • Forgetting WSTRB (software writes will mysteriously not stick).

  • Allowing reads/writes when ARESETN is asserted (reset proper).

  • Driving AWREADY/WREADY constantly high (can accept address and data in different cycles and complicate design unless you fully track ordering).

  • Crossing to a different logic clock without CDC.

评论

此博客中的热门博文

Detailed Explanation of STM32 HAL Library Clock System

How to remove write protection of STM32 chip?

The automatic white balance algorithm of Raspberry Pi