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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Protocol Metrics Reference
  • Protocols Glossary
  • Reading Path
  • Protocol Fundamentals25
  • AMBA Family (APB / AHB / AXI)17
    • APB & AHB Basics
      • APB & AHB Basics: Concepts
      • APB & AHB Basics: Integration
      • APB & AHB Basics: Debug
      • APB & AHB Basics: Interview
    • AXI Channels & Transfers
    • AXI-Lite & AXI-Stream
    • AMBA Integration Debug
    • AMBA Family (APB / AHB / AXI): Tricky Q&A
  • Coherence Fabrics (ACE / CHI)17
  • Memory Interfaces (DDR / LPDDR / HBM)17
  • PCIe & CXL17
  • Embedded Peripherals (I2C / SPI / UART)17
  • High-Speed I/O (USB / Ethernet / MIPI)17
  • Protocol Verification & Compliance21
  • Protocols Interview Prep5
  • Interface Protocols Cheatsheet
Loading lesson

←APB & AHB Basics
  1. Topics
  2. Bus Protocols
  3. AMBA Family (APB / AHB / AXI)
  4. APB & AHB Basics
  5. APB & AHB Basics: Concepts
On this page

    Interface ProtocolsAll levels

    APB & AHB Basics: Concepts

    Concepts for APB & AHB Basics.

    Core concepts

    APB4: setup first, then complete the access

    Read first: Handshake & Backpressure: distinguish an offered operation from an accepted one

    The Advanced Peripheral Bus (APB) carries one selected peripheral access at a time. This lesson uses APB4, Arm IHI 0024C, sections 3.1–3.4 and 4.1. APB_BRIDGE requests transfers and DMA_CFG responds on the course's 32-bit peripheral path. PCLK is clk_per at 50 MHz; all rows below show signals sampled at its rising edges, outside reset.

    SETUP occupies one cycle with PSEL=1 and PENABLE=0. ACCESS follows with both high; the access completes only at an edge where PSEL && PENABLE && PREADY is true. PREADY=0 extends ACCESS. APB has separate read and write data buses but one transfer handshake, so they do not complete independent concurrent accesses. A minimum transfer uses one SETUP cycle and one ACCESS cycle.

    Keep the selected target, address, direction and protection fields unchanged from SETUP through ACCESS completion. For a write, retain the active write-data bytes and write strobes as well. APB4 PSTRB has one bit per byte lane; the examples use 1111 for a full 32-bit write and 0000 for reads. PPROT remains 000 in these teaching traces. PREADY may already be high during SETUP; that alone completes nothing.

    APB4 phase and completion rules

    Phase

    PSEL

    PENABLE

    PREADY

    At the sampled edge

    IDLE

    0

    0

    Not used

    No selected access

    SETUP

    1

    0

    Not used

    No completion; move to ACCESS

    ACCESS wait

    1

    1

    0

    No completion; retain this access

    ACCESS finish

    1

    1

    1

    Complete; next cycle is SETUP or IDLE

    What to notice

    • SETUP must be followed by ACCESS even when PREADY is already high.

    • PREADY extends the access phase, rather than replacing APB's setup/access sequencing with an AXI handshake.

    Check yourself Does PSEL=1, PENABLE=0 and PREADY=1 complete a register write?

    No. Those are SETUP signals. The write completes only on an ACCESS edge with PSEL, PENABLE and PREADY all high.

    One register write through two wait cycles

    Before E1, DMA_CFG.LEN_BYTES at 0x4000_0008 is 0. The bridge requests a full-word write of 0x0000_0010, meaning 16 bytes. In this isolated register exercise the target updates LEN_BYTES only at a successful completed write; no other agent writes it and no reset occurs. PADDR stays 0x4000_0008, PWRITE=1, PWDATA=0x0000_0010, PSTRB=1111 and PPROT=000 from SETUP through E4.

    The target inserts two ACCESS waits at E2 and E3. Seeing the intended data on PWDATA does not mean the register has changed. At E4 the completion condition is true with PSLVERR=0, so the target commits the write once. Each row's register value is the result after that edge.

    Waited LEN_BYTES write

    PREADY is shown high during SETUP to demonstrate that completion still requires PENABLE. PSLVERR is 0 on the final edge.

    Edge

    PSEL / PENABLE / PREADY

    Phase

    Completed write

    LEN_BYTES after edge

    E1

    1 / 0 / 1

    SETUP

    No

    0x0000_0000

    E2

    1 / 1 / 0

    ACCESS wait

    No

    0x0000_0000

    E3

    1 / 1 / 0

    ACCESS wait

    No

    0x0000_0000

    E4

    1 / 1 / 1

    ACCESS finish

    Yes, once

    0x0000_0010

    What to notice: PSEL starts the access and PENABLE distinguishes ACCESS from SETUP. Follow PREADY through the waits, then compare the later write_commits and len_bytes values. injected_change is the separate faulty observation; it is not the real target's PWDATA.

    Check yourself Why does the multi-cycle request produce one register write rather than a write on every selected cycle?

    Only PSEL && PENABLE && PREADY completes the write. SETUP and PREADY-low ACCESS cycles do not commit it; write_commits advances once.

    What to notice

    • The requested value stays stable through the two waits and the completing edge.

    • Two waiting edges and one completing ACCESS edge are one write, not three writes.

    Check yourself If the bridge changes PWDATA to 0x20 after E2, can E4 still represent the original write?

    No. The original write is still waiting, so its write data must remain 0x10 through E4. A new value needs a separate SETUP/ACCESS transfer.

    Check yourself How can you check a waited write and detect a changing request?

    The source below models the local 32-bit APB register path at 50 MHz. wait_cycles and reject_access configure the exercise target; they are not APB signals. The bench writes LEN_BYTES through two waits, reads it back, completes a deliberately rejected STATUS read, then checks another write/read pair. A second observation changes PWDATA during the wait so the hold checker must flag it. The error example drives PSLVERR low until completion, unlike the earlier legal trace that illustrated an ignored high value before completion. The eight-cycle watchdog is a bench budget, not an APB response deadline.

    apb_register.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
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    // APB4 register exercise. wait_cycles and reject_access are test configuration,
    // not APB wires. This models LEN_BYTES and a deliberately rejected STATUS read.
    `timescale 1ns/1ps
    module apb_register (
      input wire clk,
      input wire rst,
      input wire psel,
      input wire penable,
      input wire pwrite,
      input wire [31:0] paddr,
      input wire [31:0] pwdata,
      input wire [3:0] pstrb,
      input wire [1:0] wait_cycles,
      input wire reject_access,
      output wire pready,
      output wire pslverr,
      output wire [31:0] prdata,
      output reg [31:0] len_bytes,
      output integer write_commits
    );
      localparam [31:0] LEN_ADDR = 32'h4000_0008;
      reg [1:0] wait_left;
      assign pready = !penable || wait_left == 0;
      assign pslverr = psel && penable && pready && reject_access;
      assign prdata = paddr == LEN_ADDR ? len_bytes : 32'h0000_0000;
    
      always @(posedge clk) begin
        if (rst) begin
          wait_left <= 0;
          len_bytes <= 0;
          write_commits <= 0;
        end else begin
          if (psel && !penable) wait_left <= wait_cycles;
          else if (psel && penable && !pready) wait_left <= wait_left - 1'b1;
          if (psel && penable && pready && pwrite && !pslverr && paddr == LEN_ADDR) begin
            for (integer lane = 0; lane < 4; lane = lane + 1)
              if (pstrb[lane]) len_bytes[8*lane +: 8] <= pwdata[8*lane +: 8];
            write_commits <= write_commits + 1;
          end
        end
      end
    endmodule
    
    // Check the held request from SETUP through the completing ACCESS edge.
    module apb_hold_checker (
      input wire clk,
      input wire rst,
      input wire psel,
      input wire penable,
      input wire pready,
      input wire pwrite,
      input wire [31:0] paddr,
      input wire [31:0] pwdata,
      input wire [3:0] pstrb,
      input wire [2:0] pprot,
      output reg changed_request
    );
      reg holding;
      reg saved_write;
      reg [31:0] saved_addr, saved_data;
      reg [3:0] saved_strb;
      reg [2:0] saved_prot;
      always @(posedge clk) begin
        if (rst) begin
          holding <= 0;
          changed_request <= 0;
        end else begin
          if (holding) begin
            if (!psel || !penable || pwrite !== saved_write ||
                paddr !== saved_addr || pprot !== saved_prot ||
                (saved_write && (pwdata !== saved_data || pstrb !== saved_strb)))
              changed_request <= 1;
          end
          holding <= psel && !(penable && pready);
          saved_write <= pwrite;
          saved_addr <= paddr;
          saved_data <= pwdata;
          saved_strb <= pstrb;
          saved_prot <= pprot;
        end
      end
    endmodule
    
    module tb;
      reg clk = 0;
      always #10 clk = ~clk; // clk_per = 50 MHz
      reg rst = 1;
      reg psel = 0, penable = 0, pwrite = 0;
      reg [31:0] paddr = 0, pwdata = 0;
      reg [3:0] pstrb = 0;
      reg [2:0] pprot = 0;
      reg [1:0] wait_cycles = 0;
      reg reject_access = 0;
      reg corrupt_wait = 0;
      wire pready, pslverr;
      wire [31:0] prdata, len_bytes;
      wire changed_request, injected_change;
      wire [31:0] faulty_observation = corrupt_wait && penable && !pready ? pwdata ^ 32'h1 : pwdata;
      integer write_commits;
      integer completed = 0;
      reg [31:0] expected_len = 0;
    
      apb_register target (
        .clk(clk), .rst(rst), .psel(psel), .penable(penable), .pwrite(pwrite),
        .paddr(paddr), .pwdata(pwdata), .pstrb(pstrb), .wait_cycles(wait_cycles),
        .reject_access(reject_access), .pready(pready), .pslverr(pslverr),
        .prdata(prdata), .len_bytes(len_bytes), .write_commits(write_commits)
      );
      apb_hold_checker good_check (
        .clk(clk), .rst(rst), .psel(psel), .penable(penable), .pready(pready),
        .pwrite(pwrite), .paddr(paddr), .pwdata(pwdata), .pstrb(pstrb),
        .pprot(pprot), .changed_request(changed_request)
      );
      apb_hold_checker fault_check (
        .clk(clk), .rst(rst), .psel(psel), .penable(penable), .pready(pready),
        .pwrite(pwrite), .paddr(paddr), .pwdata(faulty_observation), .pstrb(pstrb),
        .pprot(pprot), .changed_request(injected_change)
      );
    
      always @(posedge clk) begin
        if (rst) expected_len <= 0;
        else begin
          if (len_bytes !== expected_len) begin
            $display("FAIL: LEN_BYTES changed before completion or has an incorrect value");
            $finish;
          end
          if (psel && penable && pready && pwrite && !pslverr && paddr == 32'h4000_0008)
            for (integer lane = 0; lane < 4; lane = lane + 1)
              if (pstrb[lane]) expected_len[8*lane +: 8] <= pwdata[8*lane +: 8];
        end
      end
    
      task access_register(
        input wr,
        input [31:0] addr,
        input [31:0] data,
        input [1:0] waits,
        input rejected,
        input [31:0] expected_read
      );
        begin
          @(negedge clk);
          psel = 1; penable = 0; pwrite = wr;
          paddr = addr; pwdata = data; pstrb = wr ? 4'b1111 : 4'b0000;
          wait_cycles = waits; reject_access = rejected;
          @(negedge clk); penable = 1;
          begin : completion
            // Eight cycles is this bench's watchdog budget, not an APB deadline.
            for (integer cycle = 0; cycle < 8; cycle = cycle + 1) begin
              @(posedge clk);
              if (pready) begin
                if (pslverr !== rejected || (!wr && !rejected && prdata !== expected_read)) begin
                  $display("FAIL: wrong APB completion response or read data");
                  $finish;
                end
                completed = completed + 1;
                #1;
                disable completion;
              end
            end
            $display("FAIL: APB exercise exceeded its stated watchdog budget");
            $finish;
          end
        end
      endtask
    
      initial begin
        $dumpfile("wave.vcd");
        $dumpvars(0, tb);
        repeat (2) @(posedge clk);
        @(negedge clk); rst = 0;
        corrupt_wait = 1;
        access_register(1, 32'h4000_0008, 32'h10, 2, 0, 0);
        corrupt_wait = 0;
        access_register(0, 32'h4000_0008, 0, 0, 0, 32'h10);
        access_register(0, 32'h4000_0010, 0, 1, 1, 0);
        access_register(1, 32'h4000_0008, 32'h20, 0, 0, 0);
        access_register(0, 32'h4000_0008, 0, 0, 0, 32'h20);
        @(negedge clk); psel = 0; penable = 0;
        if (changed_request || !injected_change || completed != 5 ||
            write_commits != 2 || len_bytes !== 32'h20) begin
          $display("FAIL: good hold, injected hold fault, transaction count or register state mismatch");
          $finish;
        end
        $display("PASS: waited write, back-to-back read and error completed; changed waiting request detected");
        $finish;
      end
    endmodule

    Read it back without an idle bubble

    Continue the same register exercise after E4. A read of LEN_BYTES can follow immediately: keep this target's PSEL high, lower PENABLE for the read's SETUP cycle, set PWRITE=0 and PSTRB=0000, then enter ACCESS. PADDR and PPROT remain unchanged. The target supplies PRDATA=0x0000_0010 by the successful completing read edge E6.

    An idle cycle between transfers is optional; a SETUP cycle for the next transfer is required. PRDATA before a successful read completion is not evidence that the read has completed. The timing rows do not show the upstream AXI request or bridge clock crossing: E5/E6 are clk_per edges, not clk_sys edges.

    Back-to-back read after the E4 write

    LEN_BYTES remains 0x10 throughout. The read has no wait states and PSLVERR=0 at completion.

    Edge

    PSEL / PENABLE / PREADY

    PWRITE

    PRDATA significance

    Completion

    E5

    1 / 0 / 1

    0

    Not sampled as a result

    None: read SETUP

    E6

    1 / 1 / 1

    0

    0x0000_0010

    Read returns 0x10

    What to notice: Do not use continuous PSEL as the transfer counter. Locate the low PENABLE SETUP sample and the subsequent ACCESS completion, then read PRDATA at that completion.

    Check yourself Can the next transfer omit SETUP because PSEL never fell?

    No. The next access still has a PENABLE-low SETUP phase. Continuous target selection does not merge two transfers.

    What to notice

    • PSEL can remain high between accesses to the same target; PENABLE must return low for the new SETUP phase.

    • The returned value matches the most recent completed write in this isolated example.

    Check yourself Could the read finish at E5 if PREADY was already high after the write?

    No. E5 is the new read's SETUP cycle with PENABLE=0. The earliest no-wait completion is E6 after entering ACCESS.

    An error finishes the transfer too

    PSLVERR reports an error only at a completing ACCESS edge. For this separate exercise, select DMA_CFG.STATUS at 0x4000_0010 with PWRITE=0, PSTRB=0000 and PPROT=000, and assume the target rejects that read. The rejection is an exercise condition, not a new STATUS register rule. Use fresh edges F1–F3 on clk_per, outside reset.

    The access completes with an error at F3; it is no longer waiting. Its PRDATA is not a valid successful-read result. A write error is not a general rollback guarantee either: peripheral state effects depend on the target contract. PSLVERR support is optional in APB4; a target without it connects a low error indication to the requester. The trace includes PSLVERR explicitly.

    Sample the error at completion

    The target happens to drive PSLVERR high before completion. Only its value at the completing ACCESS edge determines this transfer's error response.

    Edge

    PSEL / PENABLE / PREADY

    PSLVERR

    Phase

    Result

    F1

    1 / 0 / 1

    1

    SETUP

    No error sampled yet

    F2

    1 / 1 / 0

    1

    ACCESS wait

    No completion

    F3

    1 / 1 / 1

    1

    ACCESS finish

    Completed with error

    What to notice: Find the sample where PENABLE, PSEL and PREADY are all high, then inspect PSLVERR there. A completed error is different from an unfinished wait; this target leaves LEN_BYTES unchanged for the rejected read.

    Check yourself Does PSLVERR mean the requester should keep waiting for a successful completion of this same access?

    No. This transfer has completed with an error. Software or an upstream bridge handles that outcome under its own recovery contract.

    What to notice

    • A high PSLVERR during SETUP or a wait is not the sampled completion response.

    • An error completion and an access stuck with PREADY=0 are different observations.

    Check yourself After F3, should the bridge continue holding ACCESS until PSLVERR becomes low?

    No. F3 already completed the access with an error. The next cycle moves to SETUP for another transfer or to IDLE; the requester handles the completed error response.

    AHB-Lite: separate the address phase from the data phase

    This comparison uses AMBA 3 AHB-Lite v1.0, Arm IHI 0033A, sections 3.1, 3.2, 3.6 and chapters 4–5. A transfer has an address/control phase followed by a data phase. While A is in its data phase, the bus can present B's address. The data-phase target and request context therefore come from an earlier accepted address, not from the current HADDR alone.

    For the following isolated 32-bit AHB-Lite read example, HCLK=100 MHz and reset is released before E1. The address space uses the course's SRAM0 range, but this comparison does not add an AHB connection to the base AXI system. Initialise words at 0x2000_0000 and 0x2000_0004 to 0x0000_0011 and 0x0000_0022 respectively; no writes occur. A and B are independent word reads with HWRITE=0, HSIZE=2 and HBURST=SINGLE. Each starts with HTRANS=NONSEQ; these are not beats of one burst.

    At this selected target, a valid address phase advances at an edge with HSEL=1, HTRANS[1]=1 and HREADY=1. On the same edge, any previously active data phase completes with its HRESP status. Before E1 there is no active data phase, so the first ready edge accepts A but returns no read. IDLE on the address phase does not cancel a data phase already in progress.

    Two independent reads without waits

    HRESP is OKAY and HSEL=1 throughout. A addresses 0x2000_0000; B addresses 0x2000_0004. Each row shows pre-edge bus signals and events at that edge.

    Edge

    Address phase

    HREADY

    Data phase at this edge

    Result

    E1

    A / NONSEQ

    1

    None

    Accept A address

    E2

    B / NONSEQ

    1

    A: HRDATA=0x11

    Complete A; accept B address

    E3

    IDLE

    1

    B: HRDATA=0x22

    Complete B; accept no new read

    What to notice: Compare accepted with completed, not HADDR alone with HRDATA. The pipeline retains the accepted address for the following data phase; both comparison observers agree in this no-wait replay.

    Check yourself Why is a different visible HADDR not by itself evidence that the returning read data is wrong?

    The visible address can be the next address phase while data completes for the saved previous request. Match HRDATA to that saved context.

    What to notice

    • At E2 the visible address is B, but the returned data is for A.

    • E3 completes B even though the address-phase HTRANS is IDLE.

    Check yourself At E2, should a monitor compare HRDATA with memory at the current HADDR for B?

    No. HRDATA belongs to the saved data-phase context for A. Compare 0x11 with A's expected data, then retain B's accepted address for the next data phase.

    A data wait also delays the next address

    Restart from the same initial words with no active transfer. This time A's target inserts two ordinary OKAY waits. B is visible in the address phase at E2 but HREADY=0, so B is not yet accepted. Keep B's NONSEQ request and address/control stable through E4 in this trace. A completes and B advances together at E4; B returns its own data at E5.

    HREADYOUT is a target's completion output. Interconnect selection for the active data phase determines the HREADY seen by the bus; a newly decoded address must not steal the previous transfer's response. Track accepted address context and the selected data-phase target across waits. The ordinary NONSEQ example here does not cover the specification's IDLE/BUSY or ERROR exceptions to address/control changes.

    A waits while B remains offered

    Independent replay; HSEL=1 and HRESP=OKAY. A and B use the same addresses and values as above. HRDATA need only be treated as a successful result at completion.

    Edge

    Address phase

    HREADY

    Data phase

    Events

    E1

    A / NONSEQ

    1

    None

    Accept A address

    E2

    B / NONSEQ

    0

    A waiting

    No completion; B not accepted

    E3

    B / NONSEQ

    0

    A waiting

    No completion; B not accepted

    E4

    B / NONSEQ

    1

    A returns 0x11

    Complete A; accept B address

    E5

    IDLE

    1

    B returns 0x22

    Complete B

    What to notice: Follow the HREADY-low samples. The correct mismatch signal stays clear; faulty_mismatch identifies the observer that replaced the saved context too soon. Only HREADY-high edges permit the shown progression.

    Check yourself Which observer fails when it treats an offered next address as an already accepted replacement?

    The faulty observer. Its mismatch flag rises when it compares the completing data against the premature replacement; the correct observer retains the old context through the wait.

    What to notice

    • B appears on three sampled edges, but its address is accepted once, at E4.

    • An observer that overwrites the data-phase context from HADDR at E2/E3 will associate A's data with B.

    Check yourself How many B requests are accepted across E2, E3 and E4?

    One, at E4. HREADY is low at E2/E3, so those edges extend A's data phase and B's address phase rather than accepting additional requests.

    Check yourself How can a bench expose replacing the data context before HREADY permits it?

    The isolated source models only the two word reads above, with OKAY responses. A no-wait replay passes both observers. In the waited replay the faulty observer overwrites its saved address from B while A is still waiting; it must detect the resulting mismatch when A completes. The correct observer preserves A until that edge, then accepts B once. next_wait_cycles configures the target; it is not an AHB wire. HREADYOUT is connected directly to HREADY because this model has one target, with no fabric response multiplexor.

    ahb_read_target.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
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    // Isolated read-only AHB-Lite teaching subset: SINGLE/NONSEQ word reads,
    // one selected target, fixed initial words, OKAY responses and no reset in traffic.
    `timescale 1ns/1ps
    module ahb_read_target (
      input wire clk,
      input wire rst,
      input wire hsel,
      input wire [1:0] htrans,
      input wire [31:0] haddr,
      input wire [1:0] next_wait_cycles,
      output wire hreadyout,
      output wire [31:0] hrdata
    );
      reg active;
      reg [31:0] data_addr;
      reg [1:0] wait_left;
      assign hreadyout = !active || wait_left == 0;
      assign hrdata = data_addr == 32'h2000_0000 ? 32'h11 :
                      data_addr == 32'h2000_0004 ? 32'h22 : 32'h0;
    
      always @(posedge clk) begin
        if (rst) begin
          active <= 0;
          data_addr <= 0;
          wait_left <= 0;
        end else if (hreadyout) begin
          active <= hsel && htrans[1];
          if (hsel && htrans[1]) begin
            data_addr <= haddr;
            wait_left <= next_wait_cycles;
          end
        end else wait_left <= wait_left - 1'b1;
      end
    endmodule
    
    module ahb_read_observer #(parameter IGNORE_READY = 0) (
      input wire clk,
      input wire rst,
      input wire hsel,
      input wire [1:0] htrans,
      input wire [31:0] haddr,
      input wire hready,
      input wire [31:0] hrdata,
      output reg mismatch,
      output integer accepted,
      output integer completed
    );
      reg pending;
      reg [31:0] expected_addr;
      wire [31:0] expected_word = expected_addr == 32'h2000_0000 ? 32'h11 :
                                  expected_addr == 32'h2000_0004 ? 32'h22 : 32'h0;
    
      always @(posedge clk) begin
        if (rst) begin
          pending <= 0;
          expected_addr <= 0;
          accepted <= 0;
          completed <= 0;
          mismatch <= 0;
        end else begin
          if (hready) begin
            // Compare the old data context before accepting a new address.
            if (pending) begin
              completed <= completed + 1;
              if (hrdata !== expected_word) mismatch <= 1;
            end
            pending <= hsel && htrans[1];
            if (hsel && htrans[1]) begin
              accepted <= accepted + 1;
              expected_addr <= haddr;
            end
          end
          // Fault: overwrite context from an offered, unaccepted next address.
          if (IGNORE_READY && hsel && htrans[1]) expected_addr <= haddr;
        end
      end
    endmodule
    
    module tb;
      reg clk = 0;
      always #5 clk = ~clk; // isolated HCLK = 100 MHz
      reg rst = 1;
      reg hsel = 0;
      reg [1:0] htrans = 0;
      reg [31:0] haddr = 32'h2000_0000;
      reg [1:0] next_wait_cycles = 0; // target configuration, not an AHB signal
      wire hreadyout;
      // One target only: this explicit connection stands in for data-phase mux selection.
      wire hready = hreadyout;
      wire [31:0] hrdata;
      wire mismatch, faulty_mismatch;
      integer accepted, completed, faulty_accepted, faulty_completed;
    
      ahb_read_target target (
        .clk(clk), .rst(rst), .hsel(hsel), .htrans(htrans), .haddr(haddr),
        .next_wait_cycles(next_wait_cycles), .hreadyout(hreadyout), .hrdata(hrdata)
      );
      ahb_read_observer #(.IGNORE_READY(0)) good (
        .clk(clk), .rst(rst), .hsel(hsel), .htrans(htrans), .haddr(haddr),
        .hready(hready), .hrdata(hrdata), .mismatch(mismatch),
        .accepted(accepted), .completed(completed)
      );
      ahb_read_observer #(.IGNORE_READY(1)) bad (
        .clk(clk), .rst(rst), .hsel(hsel), .htrans(htrans), .haddr(haddr),
        .hready(hready), .hrdata(hrdata), .mismatch(faulty_mismatch),
        .accepted(faulty_accepted), .completed(faulty_completed)
      );
    
      task drive_address(input [31:0] addr, input active_address, input [1:0] waits);
        begin
          @(negedge clk);
          haddr = addr;
          hsel = active_address;
          htrans = active_address ? 2'b10 : 2'b00;
          next_wait_cycles = waits;
        end
      endtask
    
      initial begin
        $dumpfile("wave.vcd");
        $dumpvars(0, tb);
        repeat (2) @(posedge clk);
        @(negedge clk); rst = 0;
    
        // No-wait replay: complete A while accepting B, then finish B during IDLE.
        drive_address(32'h2000_0000, 1, 0);
        drive_address(32'h2000_0004, 1, 0);
        drive_address(32'h2000_0004, 0, 0);
        @(negedge clk);
        if (mismatch || faulty_mismatch || accepted != 2 || completed != 2) begin
          $display("FAIL: no-wait A/B association or event counts were wrong");
          $finish;
        end
    
        // Fresh replay with the same immutable words and no in-flight reset.
        @(negedge clk); rst = 1; hsel = 0; htrans = 0;
        @(negedge clk); rst = 0;
        drive_address(32'h2000_0000, 1, 2);
        drive_address(32'h2000_0004, 1, 0);
        drive_address(32'h2000_0004, 1, 0);
        drive_address(32'h2000_0004, 1, 0);
        drive_address(32'h2000_0004, 0, 0);
        repeat (2) @(negedge clk);
        if (mismatch || !faulty_mismatch || accepted != 2 || completed != 2 ||
            faulty_accepted != 2 || faulty_completed != 2 || target.active) begin
          $display("FAIL: good association, waiting-context fault or drain counts were wrong");
          $finish;
        end
        $display("PASS: A/B reads preserved across two waits; premature context replacement detected");
        $finish;
      end
    endmodule

    Read HRESP together with HREADY

    AHB-Lite has OKAY and ERROR responses; SPLIT and RETRY belong to the older full AHB protocol. With HRESP=OKAY, HREADY distinguishes an ongoing data wait from a successful completion. The named AHB-Lite revision uses a two-cycle ERROR response: HRESP=1/HREADY=0 first, then HRESP=1/HREADY=1 to end the failed transfer. Extra ordinary waits, if needed, precede that pair with HRESP=0.

    The ERROR sequence gives the requester time to cancel the following pipelined address phase. Check the selected revision's exception rules rather than applying an unconditional 'all signals stay fixed whenever HREADY=0' rule. An undecoded active NONSEQ/SEQ address uses a default target returning ERROR; an IDLE/BUSY decode miss is not the same request.

    AHB-Lite response meanings for an active data phase

    HRESP

    HREADY

    Meaning

    0 / OKAY

    0

    Ordinary data wait; no completion

    0 / OKAY

    1

    Successful data completion

    1 / ERROR

    0

    First ERROR cycle

    1 / ERROR

    1

    Second ERROR cycle; failed transfer ends

    What to notice

    • HRESP=0 alone does not establish a completed successful transfer.

    • A completed ERROR is distinct from an ordinary wait and has a prescribed response sequence.

    Check yourself Can a target report a complete AHB-Lite ERROR using only one cycle with HRESP=1 and HREADY=1?

    No. For IHI 0033A the ERROR response has two cycles: first HRESP=1/HREADY=0, then HRESP=1/HREADY=1. Do not copy APB's single completing-error edge into AHB-Lite.

    Key takeaways

    • APB completes only at PSEL && PENABLE && PREADY; setup and waits are not repeated register writes.

    • AHB-Lite returns data for saved address-phase context, which can differ from the address currently visible.

    • Sample response status with the completing event and obey each protocol's distinct error sequence.

    Apply the mechanism

    Continue to the integration exercise

    Then trace a failure

    Previous

    30 / 158

    Next: APB & AHB Basics: Integration

    Up next

    Up nextAPB & AHB Basics: Integration

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • APB & AHB Basics: Debug
    • APB & AHB Basics: Interview
    clkpselpenablepreadypwritepaddr40000008pwdata100len_bytes010write_commits01changed_requestinjected_change
    APB waited write: one completion after two ACCESS waits.
    clkpselpenablepreadypwritepaddr4000000840000010prdata100pslverr
    APB back-to-back read: selection can remain high while PENABLE introduces the next SETUP.
    clkpselpenablepreadypwritepaddr4000001040000008pslverrlen_bytes10
    APB rejected access: PSLVERR is interpreted at the completing ACCESS edge.
    clkhselhtrans20haddr2000000020000004hreadyhrdata01122accepted012completed012mismatchfaulty_mismatch
    AHB-Lite no-wait reads: the current address and returning data can refer to different requests.
    clkhselhtrans20haddr2000000020000004hreadyhrdata01122accepted012completed012mismatchfaulty_mismatch
    AHB-Lite waited reads: HREADY holds the current data context while the next address waits.