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.

评论

此博客中的热门博文

How To Connect Stm32 To PC?

What are the common HDL languages used in FPGA design?

How do you set up ADC (Analog-to-Digital Converter) in STM32?