Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
    • SRAM and ROM
    • Register Files
    • Valid/Ready Handshake
    • AXI-Lite
      • Channel Overview
      • A Minimal AXI-Lite Slave
      • Responses, Strobes and Errors
      • Throughput and Backpressure
      • AXI-Lite End to End into a CSR
    • AXI-Stream
    • Bus Integration
    • Synchronous FIFO
    • Register-Map and CSR RTL
    • Memory Reliability
    • Peripheral RTL Integration
    • Full AXI4: RTL Roadmap
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹AXI-Lite
  1. Topics
  2. RTL Design
  3. Memory & Buses
  4. AXI-Lite
  5. A Minimal AXI-Lite Slave

Part 7 · Memory & Buses · Intermediate

A Minimal AXI-Lite Slave

A two-register AXI-Lite slave with AW/W pairing, byte strobes, SLVERR on unmapped access, and directed checks.

On this page

    Why this matters

    The minimal slave pairs AW and W with two flags, answers SLVERR for unmapped words, and allows one outstanding read. The flag clearing, the decode and the same-edge read policy are where most of the bugs sit.

    After this lesson you can: build a register-bank AXI-Lite slave with paired writes, a registered read response and SLVERR on unmapped words.

    Pairing flags and commit

    Two flags, aw_seen and w_seen, latch the write halves independently. Each rises on its own channel handshake and both clear when the pair commits and the B response is raised.

    Committing needs the word index, addr[AW-1:2]: word 0 is reg0 at byte address 0x00 and word 1 is reg1 at 0x04. Any other word is unmapped and answers SLVERR without touching a register.

    Read traffic is simpler: one address handshake loads registered read data and response, and s_arready = !s_rvalid limits the slave to a single outstanding read.

    The slave

    This register-bank template supports only DW=32 and AW>=3: its two 32-bit words occupy byte addresses 0x00 and 0x04. Elaboration guards reject other widths instead of silently changing the data width while retaining a four-byte stride. A 64-bit bank needs an eight-byte stride and matching address/lane semantics; it is a separate implementation.

    The full listing is below. The testbench under Testbench checks directed cases.

    Same-edge read/write policy: if AR is accepted for the register being written on that edge, R captures the old value. Nonblocking assignments evaluate both right-hand sides before the write updates the register. A later read sees the new value; this is this bank's policy, not a universal AXI requirement.

    axilite_regs.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    module axilite_regs #(
      parameter int AW = 8,
      parameter int DW = 32
    ) (
      input  logic             clk,
      input  logic             rst_n,
      input  logic             s_awvalid,
      output logic             s_awready,
      input  logic [AW-1:0]    s_awaddr,
      input  logic             s_wvalid,
      output logic             s_wready,
      input  logic [DW-1:0]    s_wdata,
      input  logic [DW/8-1:0]  s_wstrb,
      output logic             s_bvalid,
      input  logic             s_bready,
      output logic [1:0]       s_bresp,
      input  logic             s_arvalid,
      output logic             s_arready,
      input  logic [AW-1:0]    s_araddr,
      output logic             s_rvalid,
      input  logic             s_rready,
      output logic [DW-1:0]    s_rdata,
      output logic [1:0]       s_rresp
    );
      localparam [1:0] OKAY = 2'b00;
      localparam [1:0] SLVERR = 2'b10;
    
      if (DW != 32 || AW < 3) begin : g_bad_contract
        $error("axilite_regs requires DW=32 and AW>=3 (four-byte register stride)");
      end
    
      logic [DW-1:0] reg0, reg1;
      logic aw_seen, w_seen;
      logic [AW-1:0] awaddr_q;
      logic [DW-1:0] wdata_q;
      logic [DW/8-1:0] wstrb_q;
      logic [1:0] bresp_q, rresp_q;
    
      assign s_awready = !aw_seen && !s_bvalid;
      assign s_wready  = !w_seen  && !s_bvalid;
      assign s_arready = !s_rvalid;
      assign s_bresp   = bresp_q;
      assign s_rresp   = rresp_q;
    
      function automatic logic [DW-1:0] apply_wstrb(
        input logic [DW-1:0] old_v,
        input logic [DW-1:0] new_v,
        input logic [DW/8-1:0] strb
      );
        logic [DW-1:0] tmp;
        int i;
        begin
          tmp = old_v;
          for (i = 0; i < DW/8; i++) begin
            if (strb[i])
              tmp[i*8 +: 8] = new_v[i*8 +: 8];
          end
          apply_wstrb = tmp;
        end
      endfunction
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          aw_seen  <= 1'b0;
          w_seen   <= 1'b0;
          awaddr_q <= '0;
          wdata_q  <= '0;
          wstrb_q  <= '0;
          s_bvalid <= 1'b0;
          bresp_q  <= OKAY;
          s_rvalid <= 1'b0;
          rresp_q  <= OKAY;
          s_rdata  <= '0;
          reg0     <= '0;
          reg1     <= '0;
        end else begin
          if (s_awvalid && s_awready) begin
            aw_seen  <= 1'b1;
            awaddr_q <= s_awaddr;
          end
          if (s_wvalid && s_wready) begin
            w_seen  <= 1'b1;
            wdata_q <= s_wdata;
            wstrb_q <= s_wstrb;
          end
          if (aw_seen && w_seen && !s_bvalid) begin
            case (awaddr_q[AW-1:2])
              0: begin
                reg0    <= apply_wstrb(reg0, wdata_q, wstrb_q);
                bresp_q <= OKAY;
              end
              1: begin
                reg1    <= apply_wstrb(reg1, wdata_q, wstrb_q);
                bresp_q <= OKAY;
              end
              default: begin
                bresp_q <= SLVERR;
              end
            endcase
            s_bvalid <= 1'b1;
            aw_seen  <= 1'b0;
            w_seen   <= 1'b0;
          end
          if (s_bvalid && s_bready)
            s_bvalid <= 1'b0;
          if (s_arvalid && s_arready) begin
            case (s_araddr[AW-1:2])
              0: begin
                s_rdata <= reg0;
                rresp_q <= OKAY;
              end
              1: begin
                s_rdata <= reg1;
                rresp_q <= OKAY;
              end
              default: begin
                s_rdata <= '0;
                rresp_q <= SLVERR;
              end
            endcase
            s_rvalid <= 1'b1;
          end
          if (s_rvalid && s_rready)
            s_rvalid <= 1'b0;
        end
      end
    endmodule

    Detail

    Choice in this slave

    Write order

    AW and W pair in either order; commit happens once, after both are held

    Partial writes

    apply_wstrb merges wdata per wstrb byte lane; 4'h0 changes nothing

    Unmapped access

    Write and read both answer SLVERR (2'b10); registers keep their values

    Read depth

    One outstanding read: s_arready is low while s_rvalid is high

    Case style

    Plain case with default; no unique case, no self-assignment

    Reading the waveforms

    Write: data arrives first, response stalls

    The write waveform shows a W-before-AW pair with a stalled B channel. Each slot is one clock cycle, numbered from slot 0. The rule for reading it: a channel transfers at the rising edge that ends a slot in which its VALID and READY are both high.

    • Slot 0: WVALID and WREADY are both high, so the W beat transfers at the end of this slot.

    • Slot 1: WVALID is low again (the beat is held inside the slave, so WREADY is low too). AWVALID and AWREADY are both high, so the AW beat transfers at the end of this slot.

    • Slot 2: both halves are held and both readies are low. The pair commits at the end of this slot.

    • Slot 3: BVALID is high and BREADY is low, so the response stalls and holds stable.

    • Slot 4: still stalled.

    • Slot 5: BVALID and BREADY are both high, so the B handshake completes at the end of this slot.

    • Slot 6: idle: BVALID is low and both readies are high again.

    Read: second request waits, response stalls

    The read waveform shows one accepted read, a second request waiting for ARREADY, and a stalled R channel. The same rule applies: a transfer happens at the end of a slot where VALID and READY are both high.

    • Slot 0: ARVALID and ARREADY are both high, so the address transfers at the end of this slot.

    • Slot 1: RVALID is high with the read data, but RREADY is low, so R stalls. ARREADY is low while RVALID is high.

    • Slot 2: a second ARVALID is high but ARREADY is low, so the request waits: the master must keep ARVALID and ARADDR stable until ARREADY rises. Deasserting ARVALID before the address is accepted is a protocol violation.

    • Slot 3: the stall continues on both channels.

    • Slot 4: RVALID and RREADY are both high, so the first R beat transfers at the end of this slot. ARVALID is still high and unchanged.

    • Slot 5: RVALID is low, so ARREADY is high. With ARVALID still high, the held address transfers at the end of this slot.

    • Slot 6: RVALID is high again with the second read's data.

    Common mistakes

    Common pitfall

    Returning OKAY with 32'hDEAD_BEEF on an unmapped read tells the master the access worked. Drivers then trust garbage. Unmapped reads and writes must answer SLVERR so software hears the fault.

    Common pitfall

    unique case with default is legal: default handles unmatched values, and uniqueness still checks overlapping explicit matches. With default present, an unmatched value does not trigger a no-match warning. Plain case with default is the local choice here because unmapped addresses are expected and explicitly return SLVERR.

    Interview

    Likely question: “What happens when a second read arrives while RVALID is up?” The slave holds ARREADY low, so no second handshake happens until the first R beat completes. The master must keep ARVALID and the address stable while it waits; withdrawing the request before it is accepted breaks the VALID-stability rule.

    Check yourself W is accepted on edge 1 and AW on edge 4. When does BVALID rise, and when can B first transfer?

    AW capture updates aw_seen after edge 4 by a nonblocking assignment. The commit condition first sees both flags on edge 5, so BVALID rises after edge 5. With BREADY high, the earliest B handshake is edge 6.

    Check yourself An unmapped write arrives with WSTRB all ones. Which bytes change, and what does B carry?

    None: the decode misses, so no register updates and B carries SLVERR.

    Testbench

    The slave plus finite directed checks for AW-before-W, W-before-AW, three strobe patterns, zero-strobe address errors, same-edge old-data read/write collision, and three-cycle stalls on B and R. Drive on falling edges, sample handshakes before rising-edge updates, and inspect registered results after them.

    axilite_regs.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    module axilite_regs #(
      parameter int AW = 8,
      parameter int DW = 32
    ) (
      input  logic             clk,
      input  logic             rst_n,
      input  logic             s_awvalid,
      output logic             s_awready,
      input  logic [AW-1:0]    s_awaddr,
      input  logic             s_wvalid,
      output logic             s_wready,
      input  logic [DW-1:0]    s_wdata,
      input  logic [DW/8-1:0]  s_wstrb,
      output logic             s_bvalid,
      input  logic             s_bready,
      output logic [1:0]       s_bresp,
      input  logic             s_arvalid,
      output logic             s_arready,
      input  logic [AW-1:0]    s_araddr,
      output logic             s_rvalid,
      input  logic             s_rready,
      output logic [DW-1:0]    s_rdata,
      output logic [1:0]       s_rresp
    );
      localparam [1:0] OKAY = 2'b00;
      localparam [1:0] SLVERR = 2'b10;
    
      if (DW != 32 || AW < 3) begin : g_bad_contract
        $error("axilite_regs requires DW=32 and AW>=3 (four-byte register stride)");
      end
    
      logic [DW-1:0] reg0, reg1;
      logic aw_seen, w_seen;
      logic [AW-1:0] awaddr_q;
      logic [DW-1:0] wdata_q;
      logic [DW/8-1:0] wstrb_q;
      logic [1:0] bresp_q, rresp_q;
    
      assign s_awready = !aw_seen && !s_bvalid;
      assign s_wready = !w_seen && !s_bvalid;
      assign s_arready = !s_rvalid;
      assign s_bresp = bresp_q;
      assign s_rresp = rresp_q;
    
      function automatic logic [DW-1:0] apply_wstrb(
        input logic [DW-1:0] old_v,
        input logic [DW-1:0] new_v,
        input logic [DW/8-1:0] strb
      );
        logic [DW-1:0] tmp;
        int i;
        begin
          tmp = old_v;
          for (i = 0; i < DW/8; i++) begin
            if (strb[i])
              tmp[i*8 +: 8] = new_v[i*8 +: 8];
          end
          apply_wstrb = tmp;
        end
      endfunction
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          aw_seen <= 1'b0;
          w_seen <= 1'b0;
          awaddr_q <= '0;
          wdata_q <= '0;
          wstrb_q <= '0;
          s_bvalid <= 1'b0;
          bresp_q <= OKAY;
          s_rvalid <= 1'b0;
          rresp_q <= OKAY;
          s_rdata <= '0;
          reg0 <= '0;
          reg1 <= '0;
        end else begin
          if (s_awvalid && s_awready) begin
            aw_seen <= 1'b1;
            awaddr_q <= s_awaddr;
          end
          if (s_wvalid && s_wready) begin
            w_seen <= 1'b1;
            wdata_q <= s_wdata;
            wstrb_q <= s_wstrb;
          end
          if (aw_seen && w_seen && !s_bvalid) begin
            case (awaddr_q[AW-1:2])
              0: begin
                reg0 <= apply_wstrb(reg0, wdata_q, wstrb_q);
                bresp_q <= OKAY;
              end
              1: begin
                reg1 <= apply_wstrb(reg1, wdata_q, wstrb_q);
                bresp_q <= OKAY;
              end
              default: begin
                bresp_q <= SLVERR;
              end
            endcase
            s_bvalid <= 1'b1;
            aw_seen <= 1'b0;
            w_seen <= 1'b0;
          end
          if (s_bvalid && s_bready)
            s_bvalid <= 1'b0;
          if (s_arvalid && s_arready) begin
            case (s_araddr[AW-1:2])
              0: begin
                s_rdata <= reg0;
                rresp_q <= OKAY;
              end
              1: begin
                s_rdata <= reg1;
                rresp_q <= OKAY;
              end
              default: begin
                s_rdata <= '0;
                rresp_q <= SLVERR;
              end
            endcase
            s_rvalid <= 1'b1;
          end
          if (s_rvalid && s_rready)
            s_rvalid <= 1'b0;
        end
      end
    endmodule
    

    Remember

    • aw_seen and w_seen latch independently, and both clear when the pair commits and B is raised.

    • s_arready = !s_rvalid allows one outstanding read.

    • A read of a register written on the same edge captures the old value. That is this bank's policy, not an AXI rule.

    Rule of thumb: answer SLVERR for an unmapped word. OKAY with junk data tells software the access worked.

    How this was checked

    Source checker, run with Icarus Verilog 13.0 (2026-10-16): prints PASS. Finite directed checks: AW before W, W before AW, three strobe patterns, zero-strobe address errors, a same-edge old-data read and write collision, and three-cycle stalls on B and R. It is not a proof for every traffic pattern.

    Keep going

    • Practice: AXI4-Lite slave interface with read/write register access

    • Practice: AXI4-Lite slave read interface: RTL and coverage closure

    • Code lab: AXI-lite read channel stub

    • Interview: run a mock interview on this topic

    Related topics

    • Responses, Strobes and Errors

    Previous

    203 / 350

    Next: Responses, Strobes and Errors

    Up next

    Up nextResponses, Strobes and Errors

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Throughput and Backpressure
    • AXI-Lite End to End into a CSR
    ACLKAWVALIDAWREADYWVALIDWREADYBVALIDBREADY
    A W-before-AW write pair with a stalled B response
    ACLKARVALIDARREADYRVALIDRREADY
    A read with a second request waiting and a stalled R channel