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
    • AXI Channels & Transfers
      • AXI Channels & Transfers: Concepts
      • AXI Channels & Transfers: Integration
      • AXI Channels & Transfers: Debug
      • AXI Channels & Transfers: Interview
    • 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

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

    Interface ProtocolsAll levels

    AXI Channels & Transfers: Concepts

    Concepts for AXI Channels & Transfers.

    Core concepts

    Five independent channel handshakes

    Read first: Handshake & Backpressure: accepting edges and retained offers

    Assume one AXI4 interface, one rising-edge clock and signals outside reset. AW, W, B, AR and R each have their own VALID/READY acceptance event. AWVALID && AWREADY accepts one write-address item; WVALID && WREADY accepts one write-data beat. There is no extra event called an 'AXI write handshake' that requires AW and W to be high together.

    Channel independence permits overlap and buffering, but it does not promise progress by itself. A target may need accepted address context, data capacity or downstream service before it can advance another channel. State those resource dependencies separately from the protocol's VALID/READY rules.

    Five channels, five acceptance events

    AXI4 interface outside reset; field lists highlight the lesson's correlation and payload signals, not every signal in the specification.

    Channel

    Source presents

    Destination accepts when

    AW

    Address/control and AWID

    AWVALID && AWREADY

    W

    Data/strobes and WLAST

    WVALID && WREADY

    B

    Response and BID

    BVALID && BREADY

    AR

    Address/control and ARID

    ARVALID && ARREADY

    R

    Data/status, RID and RLAST

    RVALID && RREADY

    What to notice

    • One channel's acceptance does not imply another channel accepted on that edge.

    • Reconstruct complete transactions from the required channel items, identities and ordering rules.

    Check yourself AW is accepted while WREADY is low. Which table row still has an unresolved request-part acceptance?

    W. The accepted address is retained context; write data must still be accepted, including the required final beat, before the response prerequisites are met.

    Three legal write-arrival patterns

    The traces below show single-beat writes with items A0 (address) and D0 (data). Each row is sampled at the named rising edge. The source retains a stalled offer; the target records whichever channel accepts first. These are arrival patterns, not three different write semantics.

    An implementation must have a documented admission policy for early data. It may accept and buffer W before AW, or hold WREADY low until it has capacity/context. It must not require the source to wait for AWREADY before asserting WVALID, and it must not create B before both required acceptances occur.

    Case 1: AW before W

    Single-beat A0/D0 write, outside reset. V/R means sampled VALID/READY; D0 carries WLAST=1.

    Edge

    AW V/R

    W V/R

    Accepted

    Stored after edge

    E1

    1/1

    0/0

    A0

    Address A0

    E2

    0/1

    1/1

    D0 final

    A0 + final data

    Case 2: W before AW

    Edge

    AW V/R

    W V/R

    Accepted

    Stored after edge

    E1

    0/0

    1/1

    D0 final

    Early data D0

    E2

    1/1

    0/0

    A0

    A0 + final data

    Case 3: same accepting edge

    Edge

    AW V/R

    W V/R

    Accepted

    Stored after edge

    E1

    1/1

    1/1

    A0 and D0 final

    A0 + final data

    What to notice: Locate WVALID && WREADY and AWVALID && AWREADY independently. Early accepted W is retained work. BVALID appears later and premature_b remains clear.

    Check yourself Is accepting the final W beat before AW itself the fault in this capture?

    No. The channels are independent. The response must wait for accepted AW and the accepted final W beat; the shown B satisfies that dependency.

    What to notice

    • AW and W acceptance are counted independently even in Case 3.

    • All three cases satisfy the same prerequisites for B: address and final data accepted.

    Check yourself Is Case 2 illegal because the target has no address at E1?

    No. AXI4 permits W before AW. The target must retain accepted early W data, or backpressure W until it has capacity to honour the transfer.

    Stalls and the write-response dependency

    A channel source retains VALID and its payload until that channel's accepting edge. In this trace, AW stalls for one edge while W is accepted. The target can remember D0, but it cannot assert BVALID merely because WLAST was accepted: A0 has not yet been accepted.

    After E2 the target has accepted A0 and the final data beat. It may perform additional implementation work before presenting B; AXI defines the ordering dependency, not a universal response latency. Once BVALID is presented, BRESP and BID remain stable until BVALID && BREADY.

    Early W, stalled AW, delayed B

    Single-beat A0/D0 write with WLAST=1 on D0; no prior accepted address or data. V/R means VALID/READY sampled at the edge.

    Edge

    AW V/R

    W V/R

    B V/R

    Accepted

    Target after edge

    E1

    1/0

    1/1

    0/0

    D0 final

    Data stored; address missing

    E2

    1/1

    0/1

    0/0

    A0

    Address and final data stored

    E3

    0/1

    0/1

    1/0

    None

    Retain B0

    E4

    0/1

    0/1

    1/1

    B0

    Response accepted

    What to notice: AWVALID/AWREADY never establish an address acceptance in this isolated window. A W handshake is insufficient. The registered premature_b flag appears after the faulty response observation.

    Check yourself Which missing prerequisite explains the flag even though WLAST was seen?

    The write address was never accepted. Accepted final data cannot substitute for accepted AW.

    What to notice: BVALID remains asserted across BREADY-low samples. The offer is pending, not repeatedly completed. This capture focuses on acceptance timing; the listing checks the wider response obligations.

    Check yourself At which kind of edge may the requester count one completed response?

    Only an edge with BVALID && BREADY. Earlier BVALID-only samples are a retained offer.

    What to notice: The observer is reset for this scenario. Follow the absent W handshake/WLAST prerequisite, then BVALID and the later error flag. BREADY being low does not make an invalid response offer legal.

    Check yourself Must an observer wait for BREADY before reporting a premature BVALID?

    No. The response offer itself depends on the required accepted request parts. Backpressure does not waive that prerequisite.

    What to notice

    • A0 is unchanged from E1 through its accepting edge E2.

    • No B offer appears before both A0 and final D0 were accepted.

    • B0 remains offered through the E3 stall and completes only at E4.

    Check yourself Which exact edge first permits this target to create B0?

    After E2: that edge completes the missing AW acceptance. Before then, having the final W beat alone does not satisfy the AXI4 response dependency.

    Burst rules that catch integrators

    AXI4 AW and W offers and handshakes are independent in time: W may precede AW. Associate W bursts with AW request order; AXI4 has no WID and does not allow write-data interleaving between bursts. WLAST qualifies the final beat, counted only on WVALID && WREADY. BVALID follows acceptance of both AW and the final W beat, with BID matching AWID. Read beats within a burst are ordered; same-ID read bursts return in AR order, while different IDs may reorder or interleave.

    AxSIZE describes a transfer-size window and AxLEN encodes beats minus one. A transfer can be narrower than the bus. WSTRB selects which permitted byte lanes actually write; count useful bytes at accepted W edges. The following examples use the course's 64-bit AXI4 bus and ordinary INCR transactions, outside reset. Arm IHI 0022H A3.4 defines the address and byte-lane rules; FIXED and WRAP need their own address calculations.

    Two-beat write: count accepted edges

    Before E1, address A0 has already been accepted with AWLEN=1 (two beats). V/R means WVALID/WREADY.

    Edge

    W V/R

    WDATA

    WLAST

    Accepted

    Count after edge

    E1

    1/1

    D0

    0

    D0

    1 of 2

    E2

    1/0

    D1

    1

    None

    1 of 2

    E3

    1/0

    D1

    1

    None

    1 of 2

    E4

    1/1

    D1

    1

    D1 final

    2 of 2

    What to notice

    • D1/WLAST is retained unchanged at E2, E3 and E4.

    • The burst has two accepted beats across four visible cycles.

    Check yourself May BVALID rise after E2 because WLAST is high?

    No. WREADY is 0 at E2. The final W beat is accepted at E4; only after that acceptance are both required prerequisites satisfied.

    • Early W data needs buffering or backpressure until associated AW context exists. W bursts follow AW order across IDs and cannot interleave; gaps/stalls between beats are legal. Never require AWREADY before presenting WVALID.

    • WSTRB indicates which byte lanes are valid — don't ignore on sub-word writes.

    • AxCACHE and AxPROT affect system behaviour; document their preservation or permitted transformation across each bridge.

    Transfer size, bus width and enabled bytes are different

    The 64-bit bus has eight byte lanes. AWSIZE=1 describes a two-byte transfer window; it does not require every write to occupy eight bytes. For one beat at AWADDR=0x2000_0002 with AWLEN=0, the permitted lanes are 2 and 3. WSTRB bit n qualifies WDATA bits 8n+7 through 8n. AWSIZE=3 fits the eight-byte bus; AWSIZE=4 requests sixteen bytes and cannot be used on this interface.

    For these isolated SRAM0 examples, bytes 0x2000_0000 through 0x2000_0007 start at zero and there are no other writers. Offer WDATA=0x0000_0000_5AA5_0000: lane 2 carries A5 and lane 3 carries 5A. A fully enabled halfword uses WSTRB=0x0C. A sparse version can enable only lane 3 with 0x08. Each table row is a separate replay from zero, after one accepted W beat and the corresponding successful target update.

    One two-byte window on an eight-byte bus

    WSTRB

    Enabled lanes

    Byte at ...0002

    Byte at ...0003

    Bytes written

    0x0C

    2 and 3

    A5

    5A

    2

    0x08

    3 only

    00 unchanged

    5A

    1

    0x00

    None

    00 unchanged

    00 unchanged

    0

    What to notice

    • Address and size constrain the permitted lanes; strobes can select a subset of that window.

    • A zero-strobe accepted beat still counts toward AWLEN+1 and does not permit early termination of a burst.

    Check yourself Can WSTRB=0x0F write lanes 0–3 for this AWSIZE=1 request at ...0002?

    No. The permitted window is lanes 2 and 3. Enabling lanes 0 or 1 lies outside that window, even though all four lanes physically exist on the bus.

    An unaligned first beat can contain fewer bytes

    Use a fresh zeroed SRAM0 byte range for an INCR request at 0x2000_0003 with AWSIZE=2 and AWLEN=1. The nominal window is four bytes per beat. Its aligned base is 0x2000_0000. The first beat can cover address ...0003 only; the next beat starts at aligned_base+4, address ...0004, and covers ...0004–...0007. Do not compute the second address as the unaligned start plus four.

    This exercise assumes ordinary memory and an endpoint/path that implements the shown unaligned access. Use WDATA=0x0000_0000_AB00_0000 on the first beat and 0x8877_6655_0000_0000 on the second. With all permitted lanes enabled, successful updates leave bytes at addresses ...0003–...0007 equal to AB, 55, 66, 77 and 88; earlier bytes remain zero.

    Two accepted beats, five enabled bytes

    Accepted beat

    Beat address

    Enabled lanes

    WSTRB

    WLAST

    First

    0x2000_0003

    3

    0x08

    0

    Second

    0x2000_0004

    4–7

    0xF0

    1

    What to notice

    • The first unaligned window is shortened; subsequent INCR addresses use the transfer-aligned base.

    • Useful enabled bytes are five here, although two four-byte windows define the transaction container.

    Check yourself Would WSTRB=0x78 be correct on the second beat because it spans four lanes starting at lane 3?

    No. The second beat starts at ...0004, so its permitted lanes are 4–7 and the full mask is 0xF0. Lane 3 belongs to the first beat's window.

    Split before crossing the 4 KiB boundary

    Even when both addresses decode to SRAM0, one AXI4 burst cannot cross a 4 KiB address boundary. For aligned INCR transfers, last_byte=start+(AWLEN+1)*2^AWSIZE-1. Compare start and last_byte after shifting right by 12; also check for address-width overflow. This shortcut is scoped to aligned INCR bursts; unaligned first beats need the aligned-base/container calculation.

    At 0x2000_0FF8, AWSIZE=3 and AWLEN=1 describe two eight-byte beats: the first ends at ...0FFF and the second at ...1007. Plan two legal single-beat requests instead, before issuing addresses. This creates two write transactions, each requiring its own final W beat and B response. It is not permission for an intermediate component to rewrite any accepted transaction regardless of its attributes or ordering contract.

    Sixteen bytes expressed as two legal requests

    Request

    AWADDR

    AWSIZE / AWLEN

    Byte range

    WLAST

    Original: illegal

    0x2000_0FF8

    3 / 1

    ...0FF8–...1007

    Would span two regions

    A: legal

    0x2000_0FF8

    3 / 0

    ...0FF8–...0FFF

    1 on its only beat

    B: legal

    0x2000_1000

    3 / 0

    ...1000–...1007

    1 on its only beat

    What to notice

    • A common target decode does not waive AXI's burst-boundary restriction.

    • Each new request has its own address/data/response bookkeeping; retain the required ID ordering.

    Check yourself Could clearing every strobe on the second beat make the original crossing burst legal?

    No. The address/size/length describe an illegal crossing burst. Zero strobes suppress writes on a beat; they do not repair its address structure or remove the beat from the burst.

    Check yourself How can a small model check byte windows and detect illegal burst shapes?

    This arithmetic exercise sweeps sizes, offsets and strobe masks on the 64-bit interface, checks the shown byte updates, and rejects out-of-window strobes, crossing bursts and address overflow. It models ordinary INCR transfer shape only; there are no channel handshakes or timing waveforms in this listing.

    tb.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
    // Ordinary AXI4 INCR arithmetic on a 64-bit data / 32-bit address interface.
    // This is a transfer-shape exercise, not a channel-level AXI implementation.
    module tb;
      reg [7:0] memory [0:15];
      integer checks = 0;
      integer expected_count, first_lane;
      reg [7:0] reference_mask;
      reg reference_subset;
    
      function automatic bit legal_incr(input [31:0] address, input integer size, input integer beats);
        reg [63:0] aligned, last_byte;
        begin
          if (size < 0 || size > 3 || beats < 1 || beats > 256) legal_incr = 0;
          else begin
            aligned = ({32'b0, address} >> size) << size;
            last_byte = aligned + (64'd1 << size) * beats - 1;
            legal_incr = last_byte <= 64'hffff_ffff &&
                         ({32'b0, address} >> 12) == (last_byte >> 12);
          end
        end
      endfunction
    
      function automatic [7:0] lane_mask(input [31:0] address, input integer size, input integer beat);
        reg [63:0] aligned, current_addr, bus_base;
        integer lower, upper;
        begin
          lane_mask = 0;
          if (size >= 0 && size <= 3 && beat >= 0) begin
            aligned = ({32'b0, address} >> size) << size;
            current_addr = beat == 0 ? {32'b0, address} : aligned + (64'd1 << size) * beat;
            bus_base = (current_addr >> 3) << 3;
            lower = current_addr - bus_base;
            upper = beat == 0 ? aligned + (64'd1 << size) - 1 - bus_base : lower + (1 << size) - 1;
            for (integer lane = 0; lane < 8; lane = lane + 1)
              if (lane >= lower && lane <= upper) lane_mask[lane] = 1;
          end
        end
      endfunction
    
      function automatic integer bytes_enabled(input [7:0] strobe);
        begin
          bytes_enabled = 0;
          for (integer lane = 0; lane < 8; lane = lane + 1)
            bytes_enabled = bytes_enabled + strobe[lane];
        end
      endfunction
    
      task require(input bit condition, input string message);
        begin
          checks = checks + 1;
          if (!condition) begin $display("FAIL: %s", message); $finish; end
        end
      endtask
    
      task apply_beat(input [31:0] address, input integer size, input integer beat,
                      input [63:0] data, input [7:0] strobe);
        reg [63:0] aligned, current_addr, bus_base;
        integer index;
        begin
          require((strobe & ~lane_mask(address, size, beat)) == 0, "strobe outside transfer window");
          aligned = ({32'b0, address} >> size) << size;
          current_addr = beat == 0 ? {32'b0, address} : aligned + (64'd1 << size) * beat;
          bus_base = (current_addr >> 3) << 3;
          for (integer lane = 0; lane < 8; lane = lane + 1) begin
            if (strobe[lane]) begin
              index = bus_base - 64'h2000_0000 + lane;
              require(index >= 0 && index < 16, "exercise memory bounds");
              memory[index] = data[8*lane +: 8];
            end
          end
        end
      endtask
    
      initial begin
        // Sweep widths, unaligned starts and beats, rather than asserting only one constant mask.
        for (integer size = 0; size <= 3; size = size + 1)
          for (integer offset = 0; offset < 16; offset = offset + 1)
            for (integer beat = 0; beat < 4; beat = beat + 1) begin
              expected_count = beat == 0 ? (1 << size) - offset % (1 << size) : (1 << size);
              first_lane = (beat == 0 ? offset : (offset / (1 << size)) * (1 << size) + beat * (1 << size)) % 8;
              reference_mask = ((1 << expected_count) - 1) << first_lane;
              require(legal_incr(32'h2000_0000 + offset, size, 4), "local four-beat INCR must fit");
              require(bytes_enabled(lane_mask(32'h2000_0000 + offset, size, beat)) ==
                      expected_count,
                      "lane count must match shortened first beat or full subsequent window");
              require(lane_mask(32'h2000_0000 + offset, size, beat) == reference_mask,
                      "lane positions must match independent packed-window construction");
              for (integer mask = 0; mask < 256; mask = mask + 1) begin
                reference_subset = 1;
                for (integer lane = 0; lane < 8; lane = lane + 1)
                  if ((mask & (1 << lane)) != 0 && !reference_mask[lane]) reference_subset = 0;
                require(((mask & ~lane_mask(32'h2000_0000 + offset, size, beat)) == 0) ==
                        reference_subset, "strobe subset rule against per-lane reference");
              end
            end
    
        for (integer size = 0; size <= 3; size = size + 1)
          for (integer offset = 4080; offset < 4096; offset = offset + 1)
            for (integer beats = 1; beats <= 4; beats = beats + 1)
              require(legal_incr(32'h2000_0000 + offset, size, beats) ==
                      (beats <= (4096 - (offset / (1 << size)) * (1 << size)) / (1 << size)),
                      "burst count must fit remaining transfer windows before 4 KiB");
    
        require(lane_mask(32'h2000_0002, 1, 0) == 8'h0c, "halfword uses lanes 2 and 3");
        require((8'h0f & ~lane_mask(32'h2000_0002, 1, 0)) != 0, "reject enabled lanes outside halfword");
        require(lane_mask(32'h2000_0003, 2, 0) == 8'h08 &&
                lane_mask(32'h2000_0003, 2, 1) == 8'hf0, "unaligned first beat then aligned second beat");
        require(!legal_incr(32'h2000_0ff8, 3, 2), "reject crossing even if strobes are zero");
        require(legal_incr(32'h2000_0ff8, 3, 1) && legal_incr(32'h2000_1000, 3, 1), "both split requests fit");
        require(!legal_incr(32'h2000_0000, 4, 1), "reject size wider than bus");
        require(!legal_incr(32'h2000_0000, 3, 0) && !legal_incr(32'h2000_0000, 3, 257), "reject invalid AXI4 INCR lengths");
        require(!legal_incr(32'hffff_fff8, 3, 2), "detect address-width overflow");
        for (integer i = 0; i < 16; i = i + 1) memory[i] = 0;
        apply_beat(32'h2000_0002, 1, 0, 64'h0000_0000_5aa5_0000, 8'h08);
        require(memory[2] == 0 && memory[3] == 8'h5a, "sparse write preserves disabled byte");
        for (integer i = 0; i < 16; i = i + 1) memory[i] = 0;
        apply_beat(32'h2000_0003, 2, 0, 64'h0000_0000_ab00_0000, 8'h08);
        apply_beat(32'h2000_0003, 2, 1, 64'h8877_6655_0000_0000, 8'hf0);
        require(memory[0] == 0 && memory[1] == 0 && memory[2] == 0 &&
                memory[3] == 8'hab && memory[4] == 8'h55 && memory[5] == 8'h66 &&
                memory[6] == 8'h77 && memory[7] == 8'h88, "unaligned replay byte contents");
        require(checks > 256, "non-zero varied-input work");
        $display("PASS: transfer windows, sparse bytes, unaligned INCR and boundary counterexamples checked");
        $finish;
      end
    endmodule

    Repeated IDs need a per-ID request queue

    Read first: Ordering & Outstanding Rules: the ordering stream behind an ID

    AXI4 permits multiple outstanding read requests with the same ARID. Their responses must preserve request order for that ID, even though transactions with different IDs can complete in another order. A checker keyed only by RID is therefore insufficient: RID selects a per-ID queue, and the queue head identifies which request the next completed response belongs to.

    For the bounded pilot exercise, tags A0 and B1 are teaching-only correlation labels, not AXI signals. The executable observer records two accepted AR items for ID 3 and compares each accepted final R beat with the queue head. It also checks the separate write rule that B is not presented before an AW and final W acceptance. This is a focused executable model, not an AXI compliance monitor.

    When a response completes on the same edge as a new request is accepted, compare the response with the pre-edge queue head, remove that head and append the new request. Occupancy stays unchanged. The observer reports a third queued request separately as its own capacity limit; AXI itself does not impose this exercise's two-entry limit.

    The bench exercises all three write-arrival patterns, retained context while B stalls, same-edge read queue replacement, a response without a request and the observer's reset policy. Reset clears this exercise's tracked state; a real system must separately specify how managers, targets and outstanding transactions recover together.

    Repeated ID: faulty completion order

    Single-beat reads, queue initially empty. A0 and B1 are observer correlation labels, not additional AXI wires. The observer retains its queue on mismatch.

    Edge

    Accepted event

    ID 3 queue after edge

    Verdict

    E1

    AR: ID 3, A0

    [A0]

    Legal

    E2

    AR: ID 3, B1

    [A0, B1]

    Legal

    E3

    R: ID 3, B1, RLAST

    [A0, B1]

    Mismatch: head is A0

    Legal replay after the same E1 and E2

    Edge

    Accepted response

    ID 3 queue after edge

    E3

    R: ID 3, A0, RLAST

    [B1]

    E4

    R: ID 3, B1, RLAST

    []

    What to notice: ar_tag and r_tag are teaching identities in the observer, not extra AXI signals. Follow read_q_count from zero to two and back to zero. Matching an ID is not sufficient when two requests use it.

    Check yourself What evidence shows both requests completed rather than merely matching the same ID?

    Responses match the queued teaching tags in order and read_q_count drains to zero; wrong_same_id stays clear.

    What to notice: Inspect the overlap of AR and R acceptance. An unchanged queue count can hide two useful events. The old head must be checked before the replacement becomes the new head.

    Check yourself Does an unchanged read_q_count prove no request or response was accepted?

    No. One enqueue and one dequeue can cancel in the occupancy count. Check both handshakes and their identities.

    What to notice: The teaching observer has two entries. Admission at full occupancy is supported here when a matching response retires on the same edge; it must preserve the old second entry and append the new one. Capacity and order flags both stay clear.

    Check yourself Why is the replacement not automatically a capacity failure when the queue was full before the edge?

    A valid simultaneous retirement releases a slot. This observer explicitly supports that exchange; occupancy alone before the edge is insufficient.

    What to notice: There is no compensating response retirement. model_capacity_error rises while wrong_same_id remains clear. This distinguishes insufficient checker storage from a demonstrated protocol ordering violation.

    Check yourself Does this capacity flag prove AXI universally permits only two outstanding reads?

    No. Two is this teaching observer's storage bound. The flag says it cannot track this observation, not that the protocol imposes that limit.

    What to notice: Follow the two accepted ar_tag values and compare the first r_tag with the queue head. The ID can match while the transaction is wrong. The error flag is registered after the sampled response.

    Check yourself Why would a checker that records only whether ID 3 is busy miss this fault?

    It loses the order and identities of multiple requests with ID 3. A per-ID queue is needed to compare the response against the oldest unfinished request.

    What to notice

    • RID=3 alone cannot distinguish A0 from B1.

    • The first divergence is the E3 comparison with the queue head.

    Check yourself Would a scoreboard map of ID to one expected item handle these reads safely?

    No. Accepting B1 must not overwrite A0. Use an ordered queue for the read stream at this interface and ID; compare with its head before removing a completed request.

    Check yourself How can a bounded observer check write prerequisites and repeated-ID read order?

    This SystemVerilog observer keeps one write context and a two-entry read queue for ID 3. Its bench includes legal arrivals, a stalled B response, simultaneous queue replacement, unexpected responses and deliberate ordering faults. A model-capacity flag is separate from protocol-ordering flags. Read and write state are independent, and reset clears the observer; a complete system needs its own outstanding-work recovery policy.

    axi_pilot_checker.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
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    // Bounded teaching checker for two AXI4 pilot claims; this is not a complete AXI monitor.
    // It observes one write at a time and two single-beat reads for one tracked ID.
    module axi_pilot_checker #(
      parameter [3:0] TRACK_ID = 4'h3
    ) (
      input  wire       clk,
      input  wire       rst,
      input  wire       aw_valid,
      input  wire       aw_ready,
      input  wire       w_valid,
      input  wire       w_ready,
      input  wire       w_last,
      input  wire       b_valid,
      input  wire       b_ready,
      input  wire       ar_valid,
      input  wire       ar_ready,
      input  wire [3:0] ar_id,
      input  wire [7:0] ar_tag,
      input  wire       r_valid,
      input  wire       r_ready,
      input  wire       r_last,
      input  wire [3:0] r_id,
      input  wire [7:0] r_tag,
      output reg        premature_b,
      output reg        wrong_same_id,
      output reg        model_capacity_error,
      output reg  [1:0] read_q_count
    );
      reg       aw_seen, final_w_seen;
      reg [7:0] read_q0, read_q1;
    
      wire aw_accept = aw_valid && aw_ready;
      wire w_accept  = w_valid && w_ready;
      wire ar_accept = ar_valid && ar_ready;
      wire r_accept  = r_valid && r_ready;
      wire push_read = ar_accept && ar_id == TRACK_ID;
      wire read_complete = r_accept && r_last && r_id == TRACK_ID;
      wire pop_read = read_complete && read_q_count != 0 && r_tag == read_q0;
    
      always @(posedge clk) begin
        if (rst) begin
          aw_seen       <= 1'b0;
          final_w_seen  <= 1'b0;
          premature_b   <= 1'b0;
          wrong_same_id <= 1'b0;
          model_capacity_error <= 1'b0;
          read_q_count  <= 2'd0;
          read_q0       <= 8'h00;
          read_q1       <= 8'h00;
        end else begin
          if (aw_accept) aw_seen <= 1'b1;
          if (w_accept && w_last) final_w_seen <= 1'b1;
    
          // BVALID is an offer. Its prerequisites must have completed on earlier edges.
          if (b_valid && !(aw_seen && final_w_seen)) premature_b <= 1'b1;
          if (b_valid && b_ready) begin
            aw_seen      <= 1'b0;
            final_w_seen <= 1'b0;
          end
    
          // The response matches the pre-edge head. A new AR on this edge cannot
          // justify a response when the queue was empty before the edge.
          if (read_complete && !pop_read) wrong_same_id <= 1'b1;
          case ({push_read, pop_read})
            2'b10: begin
              if (read_q_count == 0) begin
                read_q0 <= ar_tag;
                read_q_count <= 1;
              end else if (read_q_count == 1) begin
                read_q1 <= ar_tag;
                read_q_count <= 2;
              end else begin
                // Exceeding this observer's capacity is not an AXI violation.
                model_capacity_error <= 1'b1;
              end
            end
            2'b01: begin
              read_q0 <= read_q1;
              read_q_count <= read_q_count - 1'b1;
            end
            2'b11: begin
              if (read_q_count == 1) read_q0 <= ar_tag;
              else begin
                read_q0 <= read_q1;
                read_q1 <= ar_tag;
              end
              // One request enters and one completes: occupancy is unchanged.
            end
            default: begin end
          endcase
        end
      end
    endmodule
    
    module tb;
      reg clk = 1'b0;
      always #5 clk = ~clk;
    
      reg rst;
      reg aw_valid, aw_ready, w_valid, w_ready, w_last, b_valid, b_ready;
      reg ar_valid, ar_ready, r_valid, r_ready, r_last;
      reg [3:0] ar_id, r_id;
      reg [7:0] ar_tag, r_tag;
      wire premature_b, wrong_same_id, model_capacity_error;
      wire [1:0] read_q_count;
      reg saw_premature = 1'b0;
      reg saw_wrong_order = 1'b0;
    
      axi_pilot_checker dut (.*);
    
      task idle_inputs;
        begin
          aw_valid = 0; aw_ready = 0; w_valid = 0; w_ready = 0; w_last = 0;
          b_valid = 0; b_ready = 0; ar_valid = 0; ar_ready = 0;
          r_valid = 0; r_ready = 0; r_last = 0;
          ar_id = 0; r_id = 0; ar_tag = 0; r_tag = 0;
        end
      endtask
    
      task reset_checker;
        begin
          @(negedge clk); idle_inputs(); rst = 1;
          @(negedge clk); rst = 0;
        end
      endtask
    
      task accept_aw;
        begin
          @(negedge clk); aw_valid = 1; aw_ready = 1;
          @(negedge clk); aw_valid = 0; aw_ready = 0;
        end
      endtask
    
      task accept_final_w;
        begin
          @(negedge clk); w_valid = 1; w_ready = 1; w_last = 1;
          @(negedge clk); w_valid = 0; w_ready = 0; w_last = 0;
        end
      endtask
    
      task offer_b;
        begin
          @(negedge clk); b_valid = 1; b_ready = 1;
          @(negedge clk); b_valid = 0; b_ready = 0;
        end
      endtask
    
      task accept_aw_and_final_w;
        begin
          @(negedge clk);
          aw_valid = 1; aw_ready = 1; w_valid = 1; w_ready = 1; w_last = 1;
          @(negedge clk);
          aw_valid = 0; aw_ready = 0; w_valid = 0; w_ready = 0; w_last = 0;
        end
      endtask
    
      task accept_ar(input [7:0] tag);
        begin
          @(negedge clk); ar_valid = 1; ar_ready = 1; ar_id = 4'h3; ar_tag = tag;
          @(negedge clk); ar_valid = 0; ar_ready = 0;
        end
      endtask
    
      task accept_r(input [7:0] tag);
        begin
          @(negedge clk); r_valid = 1; r_ready = 1; r_last = 1; r_id = 4'h3; r_tag = tag;
          @(negedge clk); r_valid = 0; r_ready = 0; r_last = 0;
        end
      endtask
    
      task exchange_read(input [7:0] completed_tag, input [7:0] new_tag);
        begin
          @(negedge clk);
          ar_valid = 1; ar_ready = 1; ar_id = 4'h3; ar_tag = new_tag;
          r_valid = 1; r_ready = 1; r_last = 1; r_id = 4'h3; r_tag = completed_tag;
          @(negedge clk);
          ar_valid = 0; ar_ready = 0; r_valid = 0; r_ready = 0; r_last = 0;
        end
      endtask
    
      initial begin
        $dumpfile("wave.vcd");
        $dumpvars(0, tb);
        rst = 1;
        idle_inputs();
    
        // Positive write: early final W, then AW, then B.
        reset_checker();
        accept_final_w();
        accept_aw();
        offer_b();
        if (premature_b) begin
          $display("FAIL: legal W-before-AW write was rejected");
          $finish;
        end
    
        // Negative write: B is presented after final W but before AW acceptance.
        reset_checker();
        accept_final_w();
        offer_b();
        saw_premature = premature_b;
        if (!saw_premature) begin
          $display("FAIL: premature B was not detected");
          $finish;
        end
    
        // Positive reads: two requests with ID 3 complete A0 then B1.
        reset_checker();
        accept_ar(8'hA0);
        accept_ar(8'hB1);
        accept_r(8'hA0);
        accept_r(8'hB1);
        if (wrong_same_id || model_capacity_error || read_q_count != 0) begin
          $display("FAIL: legal same-ID completion order was rejected");
          $finish;
        end
    
        // Positive reads: replace a completed head on the same edge as a new AR.
        reset_checker();
        accept_ar(8'hA0);
        exchange_read(8'hA0, 8'hB1);
        if (wrong_same_id || model_capacity_error || read_q_count != 1) begin
          $display("FAIL: single-entry simultaneous enqueue/dequeue lost occupancy");
          $finish;
        end
        accept_r(8'hB1);
        if (wrong_same_id || read_q_count != 0) begin
          $display("FAIL: replacement request was lost");
          $finish;
        end
    
        // Positive reads: a full queue can pop and push without exceeding capacity.
        reset_checker();
        accept_ar(8'hA0);
        accept_ar(8'hB1);
        exchange_read(8'hA0, 8'hC2);
        if (wrong_same_id || model_capacity_error || read_q_count != 2) begin
          $display("FAIL: full-queue exchange lost occupancy or reported overflow");
          $finish;
        end
        accept_r(8'hB1);
        accept_r(8'hC2);
        if (wrong_same_id || read_q_count != 0) begin
          $display("FAIL: full-queue exchange changed response order");
          $finish;
        end
    
        // Observer limit: a third queued request is reported separately from ordering.
        reset_checker();
        accept_ar(8'hA0);
        accept_ar(8'hB1);
        accept_ar(8'hC2);
        if (!model_capacity_error || wrong_same_id || read_q_count != 2) begin
          $display("FAIL: observer capacity limit was confused with AXI ordering");
          $finish;
        end
    
        // Negative reads: the same requests complete B1 before A0.
        reset_checker();
        accept_ar(8'hA0);
        accept_ar(8'hB1);
        accept_r(8'hB1);
        saw_wrong_order = wrong_same_id;
        if (!saw_wrong_order) begin
          $display("FAIL: wrong same-ID completion order was not detected");
          $finish;
        end
    
        // Positive write: AW before final W satisfies the same prerequisites.
        reset_checker();
        accept_aw();
        accept_final_w();
        offer_b();
        if (premature_b) begin
          $display("FAIL: legal AW-before-W write was rejected");
          $finish;
        end
    
        // Positive write: AW and final W can accept at the same edge.
        reset_checker();
        accept_aw_and_final_w();
        offer_b();
        if (premature_b) begin
          $display("FAIL: same-edge AW/final-W acceptance was rejected");
          $finish;
        end
    
        // Positive write: a legal B offer can remain stalled before completion.
        reset_checker();
        accept_aw_and_final_w();
        @(negedge clk); b_valid = 1; b_ready = 0;
        repeat (2) @(negedge clk);
        if (premature_b || !dut.aw_seen || !dut.final_w_seen) begin
          $display("FAIL: B stall lost accepted write context");
          $finish;
        end
        b_ready = 1;
        @(negedge clk); b_valid = 0; b_ready = 0;
        if (premature_b || dut.aw_seen || dut.final_w_seen) begin
          $display("FAIL: accepted B did not retire write context");
          $finish;
        end
    
        // Negative write: an illegal B offer is wrong even when BREADY is low.
        reset_checker();
        accept_aw();
        @(negedge clk); b_valid = 1; b_ready = 0;
        @(negedge clk);
        if (!premature_b) begin
          $display("FAIL: B offer without final W was hidden by BREADY=0");
          $finish;
        end
        b_valid = 0;
    
        // Negative read: an R completion needs an earlier request.
        reset_checker();
        accept_r(8'hA0);
        if (!wrong_same_id || read_q_count != 0) begin
          $display("FAIL: unexpected R response did not report an empty queue");
          $finish;
        end
    
        // Observer reset policy: abandon tracked work and clear previous flags.
        reset_checker();
        accept_ar(8'hA0);
        accept_aw_and_final_w();
        reset_checker();
        if (read_q_count != 0 || dut.aw_seen || dut.final_w_seen ||
            premature_b || wrong_same_id || model_capacity_error) begin
          $display("FAIL: reset retained observer work or stale failure flags");
          $finish;
        end
    
        $display("PASS: legal arrivals, stalls and reset passed; response/order faults and observer capacity were detected");
        $finish;
      end
    endmodule

    Key takeaways

    • AW and W accept independently in either arrival order; a B offer requires accepted AW and final W.

    • Count burst beats on accepting edges and retain every stalled channel's payload and control fields.

    • Keep accepted requests in separate read/write queues per interface and ID; repeated IDs append rather than overwrite.

    Apply the mechanism

    Continue to the integration exercise

    Then trace a failure

    Previous

    34 / 158

    Next: AXI Channels & Transfers: Integration

    Up next

    Up nextAXI Channels & Transfers: Integration

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • AXI Channels & Transfers: Debug
    • AXI Channels & Transfers: Interview
    clkaw_validaw_readyw_validw_readyw_lastb_validb_readypremature_b
    Legal AXI write: W is accepted before AW; B follows both prerequisites.
    clkaw_validaw_readyw_validw_readyw_lastb_validb_readypremature_b
    Injected AXI fault: a response is observed without an accepted write address.
    clkb_validb_readypremature_b
    Legal B stall: a retained response waits for BREADY.
    clkaw_validaw_readyw_validw_readyw_lastb_validb_readypremature_b
    Injected AXI fault: B is offered without the required final data acceptance.
    clkar_validar_readyar_id03ar_tag0a0b1r_validr_readyr_id03r_tag0a0b1read_q_count01210wrong_same_id
    Repeated-ID read queue: two accepted requests retire in queue order.
    clkar_validar_readyar_tag0a0b1r_validr_readyr_tag0a0b1read_q_count010wrong_same_id
    Read queue exchange: one response retires as another request is admitted.
    clkar_validar_readyar_tag0a0b1c2r_validr_readyr_tag0a0b1c2read_q_count01210wrong_same_idmodel_capacity_error
    Full read queue: a same-edge retirement makes room for a replacement.
    clkar_validar_readyar_tag0a0b1c2read_q_count012wrong_same_idmodel_capacity_error
    Observer limit: a third request exceeds the model's two retained entries.
    clkar_validar_readyar_tag0a0b1r_validr_readyr_tag0b1read_q_count012wrong_same_id
    Injected read-order fault: the response identity does not match the oldest request with that ID.