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
    • AXI-Stream
    • Bus Integration
      • Address Decode
      • Mux Trees and Read Paths
      • Error Paths and Timeouts
      • Arbitration
      • Integration Checklist
      • An End-to-End Bus Example
    • 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

‹Bus Integration
  1. Topics
  2. RTL Design
  3. Memory & Buses
  4. Bus Integration
  5. An End-to-End Bus Example

Part 7 · Memory & Buses · Intermediate

An End-to-End Bus Example

Two masters, a round-robin arbiter, decode for three slaves plus a default slave, the response mux and a timeout, checked by a random-traffic bench.

On this page

    Why this matters

    Decode, mux, error paths and arbitration can each pass their own test and still fail together: release the grant one edge early and a reply goes to the wrong master. Only a bench that checks every reply's owner, code and data, with a bound on waiting, finds it.

    After this lesson you can: connect two masters, a round-robin arbiter, address decode, three slaves, a default slave and a timeout into one bus, and check it with random traffic against a reference model.

    Putting decode, mux, errors and arbitration together

    Wire the arbiter's accept_i to the request handshake instead of the response, and the first slow read goes to the wrong master. The bench below prints FAIL @75 ns: response to master 1, which has nothing outstanding. Each piece of this topic can pass its own test and still fail like this once they are connected.

    This lesson builds one small, complete interconnect from the earlier lessons: arb_4 from Arbitration, the base and limit decode from Address Decode, the AND-OR mux from Mux Trees and Read Paths, and bus_timeout and the response codes from Error Paths and Timeouts. Two masters share it, and three slaves and a default slave answer.

    The end-to-end fabric: two masters request through arb_4, bus_decode3 selects the RAM, the CSR block, the slow peripheral or the default slave, and the response mux returns the reply to the grant holder; the accent path is master 1 reading the unmapped address 0x0000_5000

    Follow the accent line from master 1 down and back up. Every transaction takes the same route; only the slave box changes.

    • Arbiter: one owner at a time. The grant is the owner's name until its reply, so the reply can be routed by it.

    • Decode: the address picks exactly one of the four boxes. Anything outside the three ranges goes to the default slave.

    • Return: the mux picks the reply by the select registered at issue, and m_rvalid goes back only to the grant holder.

    The map and the bus contract

    Target

    Range

    Select

    Answers

    RAM, 1024 words

    0x0000_1000 to 0x0000_1FFF

    sel0

    Next cycle, OKAY

    CSR block

    0x0000_2000 to 0x0000_2FFF

    sel1

    Next cycle. ID at 0x000 (read-only, 0xC5A0_0001), SCRATCH at 0x004; any other access SLVERR

    Slow peripheral

    0x0000_3000 to 0x0000_3FFF

    sel2

    Four registers at 0x000 to 0x00C, three cycles after issue. Offsets 0x010 and up never answer

    Default slave

    everything else

    default_err

    Next cycle, DECERR

    A request is a valid/ready offer: the master holds m_valid, m_addr, m_write and m_wdata until m_ready takes it. The reply is one m_rvalid pulse with m_rdata and m_resp, using the error lesson's codes: OKAY 2'b00, SLVERR 2'b10, DECERR 2'b11. One transaction is in flight across the whole bus.

    The slow peripheral's upper offsets model a slave with no error path for its holes: it simply never answers. bus_timeout with LIMIT = 4 watches only that slave, which normally answers in 3.

    How the pieces connect

    Almost all of the glue is these lines of bus_fabric. Read them in order: issue, remember, answer, route.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // snippet: the glue in bus_fabric (full listing below)
    arb_4 #(.N(2), .RR(1)) arb (.clk(clk), .rst_n(rst_n), .req(m_valid), .accept_i(rsp_fire), .grant(grant));
    assign issue   = |(grant & m_valid) && !busy;   // the owner's request is taken once
    assign m_ready = issue ? grant : 2'b00;
    always_ff @(posedge clk or negedge rst_n)
      if (!rst_n) begin busy <= 0; sel_q <= 0; end
      else if (issue)    begin busy <= 1; sel_q <= sel; end   // remember who must answer
      else if (rsp_fire) begin busy <= 0; sel_q <= 0; end
    bus_timeout #(.LIMIT(LIMIT)) tmo (.clk, .rst_n, .req(busy && sel_q[2]), .ack(rv[2]), .timeout(timeout));
    assign rsp_fire = busy && (|(sel_q & rv) || timeout);
    assign m_rvalid = rsp_fire ? grant : 2'b00;     // routed to the owner, never to a requester
    • `accept_i(rsp_fire)`: the arbitration lesson's rule. The ownership interval ends with the reply, not with the request, so grant still names the owner when the reply comes back.

    • `sel_q`: the decode result is registered at issue. The master may change m_addr while it waits, so the reply cannot be steered by the live address.

    • Timeout: bus_timeout is unchanged. When it fires, the fabric completes the transaction with SLVERR and pulses abort to the slow slave, which drops its pending request. That is the coordinated isolation the error lesson asks for: no late reply can follow.

    One sequence, slot by slot

    Both masters request in the same cycle. Master 0 reads 0x0000_3040, a hole in the slow peripheral; master 1 reads 0x0000_5000, which is unmapped.

    Each slot is one clock cycle. In m_valid, grant and m_rvalid the left bit is master 1, so grant 01 is master 0. m_resp is the response code, and sel_q lists default, slow, CSR and RAM from the left.

    • Slots 1 and 2: both masters offer (m_valid 11). The registered grant goes to master 0 in slot 2, and its request is taken at the end of slot 2.

    • Slots 3 to 7: sel_q is 0100, the slow peripheral, and nothing answers. In slot 7 timeout rises and the fabric sends master 0 m_rvalid 01 with m_resp 10, SLVERR.

    • Slot 8: the grant clears for the arbitration cycle. Master 1 is still holding its offer, as valid/ready requires.

    • Slots 9 and 10: master 1 owns the bus, sel_q becomes 1000, the default slave, and its reply is m_rvalid 10 with m_resp 11, DECERR.

    The directed part of the bench runs this pair and the two normal cases, printing each reply as the master sees it:

    Diagram
    @75 ns m0 read  00003000 -> resp=00 data=00000000
    @105 ns m1 read  00002004 -> resp=00 data=00000000
    @135 ns m0 write 00001010 -> resp=00 data=00000000
    @165 ns m1 read  00002000 -> resp=00 data=c5a00001
    @195 ns m0 read  00001010 -> resp=00 data=12345678
    @225 ns m1 read  00005000 -> resp=11 data=00000000
    @295 ns m0 read  00003040 -> resp=10 data=00000000
    • m0 write, then m1 read: master 0 writes 0x1234_5678 to RAM; master 1 reads the CSR ID 0xC5A0_0001 in the next grant; master 0 reads its word back.

    • The first line is a normal slow read, three cycles after issue. It is the case that exposes the routing bug below.

    Random traffic against a reference model

    After the directed cases, each master issues 1500 random transactions with 0 to 3 idle cycles between them. Addresses mix RAM words, legal and illegal CSR offsets, slow registers, slow holes and unmapped addresses. The bench keeps its own copy of the map and of every register, updated in the order the fabric takes requests.

    Every reply is checked for its owner (a reply to a master with nothing outstanding fails), its code and, for reads, its data. Two watchdogs bound progress: 12 cycles for an offer to be taken and 40 for a reply.

    Diagram
    PASS: 3007 transactions checked: RAM 1398, CSR 570, slow 609, default 430; SLVERR 387 (80 of them timeouts), DECERR 430; longest wait to be taken 8 cycles

    The longest wait, 8 cycles, is master 1's wait in the waveform: slot 1 before any grant, master 0's issue in slot 2, five cycles waiting for the timeout, and the arbitration cycle in slot 8.

    The checklist, item by item

    The five items of the Integration Checklist, applied to this design.

    Checklist item

    How this design meets it

    Evidence

    Memory map matches software headers

    Three base and limit pairs in bus_decode3; the bench holds its own copy of the map

    Every reply's code checked, including 430 DECERR on unmapped addresses

    Clock and reset per bridge documented

    One clock and one reset; no bridge

    Not exercised here; a fabric with bridges lists each one

    Width and endianness aligned

    32 bits end to end; slaves index words with addr[11:2]

    Write and read back 0x1234_5678; no byte strobes in this bus

    Backpressure verified under load

    Masters hold offers until m_ready; random idle gaps

    Ordered per-owner checks of 3007 replies; replies cannot be stalled here

    Debug paths do not starve production

    Round-robin between two masters; grant held per transaction

    No offer waited more than 8 cycles to be taken

    Warning

    Limits: one transaction in flight, masters that always accept a reply, and no byte strobes. A fabric with outstanding transactions needs an ID or owner per reply, and response backpressure needs the response slice from the mux lesson.

    Common mistakes

    Common pitfall

    Ending the ownership interval at the request handshake. The grant moves on while the reply is still coming, and a reply routed by grant goes to whoever holds the bus next, or to nobody.

    design.sv
    // bad-example: the grant is released when the request is taken
    arb_4 #(.N(2), .RR(1)) arb (.clk(clk), .rst_n(rst_n), .req(m_valid), .accept_i(issue), .grant(grant));
    Diagram
    FAIL @75 ns: response to master 1, which has nothing outstanding
    FATAL: bus_accept_on_issue.sv:293: FAIL @455 ns: watchdog, master 0 waited 40 cycles for a response

    Master 0's slow read was issued, the grant cleared, master 1 took the bus, and three cycles later the slow reply arrived and was routed to master 1. Master 0 never hears back and the watchdog ends the run.

    The second classic is a default slave that never answers. Change its rv <= req to rv <= 1'b0 and the unmapped read of master 1 hangs the whole bus: no timeout watches the default slave, so master 1 keeps the grant and master 0's next offer is never taken.

    Diagram
    @195 ns m0 read  00001010 -> resp=00 data=12345678
    FATAL: bus_silent_default.sv:285: FAIL @325 ns: master 0 waited 12 cycles for its request to be taken

    Without the two watchdogs both runs would hang silently until the simulator's time limit. Every bus bench needs a bound on waiting, not just a scoreboard.

    Check yourself Software reads 0x0000_2008. Which box answers, and with which code?

    The CSR block. 0x2008 is inside its range, so decode selects sel1, not the default slave. The CSR block has no register at offset 0x008, so it answers SLVERR, 2'b10. DECERR is only for addresses no slave owns.

    Check yourself The slow peripheral's latency grows from 3 cycles to 6. What happens, and what do you change?

    With LIMIT = 4 the timeout fires before the reply, so every slow register access gets SLVERR and the abort discards the real answer. The bench fails at once with FAIL @95 ns: master 0 got resp=10 data=00000000, expected resp=00 data=00000000, and ends with 529 errors. A latency of 5 still passes, with no margin. Raise LIMIT above the slave's worst-case latency with margin, and record that bound in the integration spec.

    Project files

    The complete listing below contains every file of this project, each starting at a // ---- file: <name> ---- line. Save each part under its name in one folder, then run the script.

    File

    What it holds

    bus_fabric.sv

    The design: arbiter, decoder, timeout, three slaves, default slave and the fabric top

    tb_bus_fabric.sv

    The bench: two random masters and a scoreboard of every transaction

    run.sh

    Diagram
    #!/bin/sh
    set -e
    iverilog -g2012 -o sim.out bus_fabric.sv tb_bus_fabric.sv
    vvp -n sim.out

    Expected output: the line a correct design makes the simulation print at the end of its run.

    Diagram
    PASS: 3007 transactions checked: RAM 1398, CSR 570, slow 609, default 430; SLVERR 387 (80 of them timeouts), DECERR 430; longest wait to be taken 8 cycles

    Testbench

    The whole fabric with its bench: arb_4 and bus_timeout exactly as in their lessons, bus_decode3, the four slaves, bus_fabric, and the random-traffic bench with its reference model.

    arb_4.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
    // ---- file: bus_fabric.sv ----
    // arb_4 from the arbitration lesson and bus_timeout from the error-paths lesson, unchanged.
    module arb_4 #(
      parameter int N = 4,    // number of masters; N must be at least 1
      parameter int RR = 1       // 1: round-robin, 0: fixed priority (lowest index wins)
    )(
      input  logic         clk, rst_n,
      input  logic [N-1:0] req,
      input  logic         accept_i, // currently granted ownership interval completed
      output logic [N-1:0] grant   // registered owner; retained until accepted
    );
      logic [N-1:0] grant_q, grant_next;
      // N = 1 guard ($clog2(1) is 0, so [$clog2(N)-1:0] would be [-1:0]).
      localparam int AW = (N > 1) ? $clog2(N) : 1;
      logic [AW-1:0] ptr;
      int idx;
    
      always_comb begin
        grant_next = {N{1'b0}};
        idx = 0;
        if (|req) begin
          if (RR == 0) begin
            for (int i = 0; i < N; i++)
              if (req[i] && grant_next == {N{1'b0}}) grant_next[i] = 1'b1;
          end else begin
            for (int k = 0; k < N; k++) begin
              idx = ptr + k;
              if (idx >= N) idx = idx - N;
              if (req[idx] && grant_next == {N{1'b0}}) grant_next[idx] = 1'b1;
            end
          end
        end
      end
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          grant_q <= {N{1'b0}};
          ptr     <= '0;
        end else if (!(|grant_q)) begin
          grant_q <= grant_next;
        end else if (accept_i) begin
          grant_q <= '0;
          // The cursor advances from the completed owner, never from a stalled candidate.
          for (int i = 0; i < N; i++)
            if (grant_q[i]) begin
              if (i == N-1) ptr <= '0;
              else          ptr <= AW'(i + 1);
            end
        end
      end
      assign grant = grant_q;
    endmodule
    
    module bus_timeout #(
      parameter int LIMIT = 8   // must be at least 1
    )(
      input  logic clk, rst_n,
      input  logic req,          // transaction in flight
      input  logic ack,          // slave answered
      output logic timeout       // qualified level, not a sticky fault or response
    );
      // LIMIT = 0 would give W = $clog2(1) = 0, a zero-width counter and an illegal cast,
      // so the width stays elaborable and the check below rejects the configuration.
      localparam int W = (LIMIT > 0) ? $clog2(LIMIT + 1) : 1;
      initial begin
        if (LIMIT < 1) $error("bus_timeout: LIMIT must be at least 1");
      end
      logic [W-1:0] count;
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)                        count <= '0;
        else if (!req || ack)              count <= '0;
        else if (count < LIMIT[W-1:0])     count <= count + 1'b1;
      end
      assign timeout = (count == LIMIT[W-1:0]) && req && !ack;
    endmodule
    
    // bus_decode3: the decode lesson's base/limit pairs, extended to three slaves.
    module bus_decode3 #(
      parameter [31:0] BASE0 = 32'h0000_1000, LIMIT0 = 32'h0000_1FFF,   // RAM
      parameter [31:0] BASE1 = 32'h0000_2000, LIMIT1 = 32'h0000_2FFF,   // CSR block
      parameter [31:0] BASE2 = 32'h0000_3000, LIMIT2 = 32'h0000_3FFF    // slow peripheral
    )(
      input  logic [31:0] addr,
      output logic        sel0, sel1, sel2,
      output logic        default_err
    );
      assign sel0 = (addr >= BASE0) && (addr <= LIMIT0);
      assign sel1 = (addr >= BASE1) && (addr <= LIMIT1);
      assign sel2 = (addr >= BASE2) && (addr <= LIMIT2);
      assign default_err = !(sel0 || sel1 || sel2);
    endmodule
    
    // Every slave takes a one-cycle req pulse and answers once with rv, rdata and resp.
    // resp codes as in the error-paths lesson: OKAY 2'b00, SLVERR 2'b10, DECERR 2'b11.
    module ram_slave (                       // 1024 words, answers on the next cycle
      input  logic clk, rst_n, req, write,
      input  logic [31:0] addr, wdata,
      output logic rv, output logic [31:0] rdata, output logic [1:0] resp
    );
      logic [31:0] mem [0:1023];
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin rv <= 0; rdata <= 0; resp <= 2'b00; end
        else begin
          rv <= req;
          if (req) begin
            if (write) mem[addr[11:2]] <= wdata;
            rdata <= write ? 32'h0 : mem[addr[11:2]];
            resp  <= 2'b00;
          end
        end
    endmodule
    
    module csr_slave (                       // ID (read-only) at 0x000, SCRATCH at 0x004
      input  logic clk, rst_n, req, write,
      input  logic [31:0] addr, wdata,
      output logic rv, output logic [31:0] rdata, output logic [1:0] resp
    );
      localparam [31:0] ID = 32'hC5A0_0001;
      logic [31:0] scratch;
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin rv <= 0; rdata <= 0; resp <= 2'b00; scratch <= 0; end
        else begin
          rv <= req;
          if (req) begin
            rdata <= 32'h0;
            resp  <= 2'b10;                                  // SLVERR unless a legal access below
            if (addr[11:0] == 12'h000 && !write) begin rdata <= ID; resp <= 2'b00; end
            if (addr[11:0] == 12'h004) begin
              resp <= 2'b00;
              if (write) scratch <= wdata; else rdata <= scratch;
            end
          end
        end
    endmodule
    
    module slow_slave #(parameter int LAT = 3) (   // four registers at 0x000-0x00C, answers after LAT cycles
      input  logic clk, rst_n, req, write, abort,  // offsets 0x010 and up never answer (no error path)
      input  logic [31:0] addr, wdata,
      output logic rv, output logic [31:0] rdata, output logic [1:0] resp
    );
      logic [31:0] regs [0:3];
      logic        pend, hole, wr_q;
      logic [1:0]  idx_q;
      logic [31:0] wdata_q;
      int          cnt;
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin
          rv <= 0; rdata <= 0; resp <= 2'b00; pend <= 0; hole <= 0; cnt <= 0;
          for (int i = 0; i < 4; i++) regs[i] <= 32'h0;
        end
        else begin
          rv <= 0;
          if (req) begin
            pend <= 1; cnt <= 1; hole <= (addr[11:0] >= 12'h010);
            idx_q <= addr[3:2]; wr_q <= write; wdata_q <= wdata;
          end else if (abort) begin
            pend <= 0;                                       // the fabric gave up: no late reply
          end else if (pend && !hole) begin
            if (cnt == LAT - 1) begin
              pend <= 0; rv <= 1; resp <= 2'b00;
              if (wr_q) begin regs[idx_q] <= wdata_q; rdata <= 32'h0; end
              else rdata <= regs[idx_q];
            end else cnt <= cnt + 1;
          end
        end
    endmodule
    
    module default_slave (                   // unmapped address: DECERR on the next cycle
      input  logic clk, rst_n, req,
      output logic rv, output logic [31:0] rdata, output logic [1:0] resp
    );
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) rv <= 0;
        else        rv <= req;
      assign rdata = 32'h0;
      assign resp  = 2'b11;
    endmodule
    
    // bus_fabric: two masters, one transaction at a time. The arbiter's grant is the owner from
    // issue until the response; accept_i is the response, so the owner cannot change mid-flight.
    module bus_fabric #(parameter int LIMIT = 4) (
      input  logic clk, rst_n,
      input  logic [1:0]       m_valid,                // request: valid/ready with addr, write, wdata
      output logic [1:0]       m_ready,
      input  logic [1:0][31:0] m_addr, m_wdata,
      input  logic [1:0]       m_write,
      output logic [1:0]       m_rvalid,               // response: one pulse to the owner
      output logic [31:0]      m_rdata,
      output logic [1:0]       m_resp
    );
      logic [1:0]  grant;
      logic        owner, busy, issue, rsp_fire, timeout;
      logic [31:0] addr;
      logic [3:0]  sel, sel_q;                         // {default, slow, csr, ram}
      logic [3:0]  rv;
      logic [3:0][31:0] rd;                            // per-slave read data, response code
      logic [3:0][1:0]  rs;
    
      arb_4 #(.N(2), .RR(1)) arb (.clk(clk), .rst_n(rst_n), .req(m_valid), .accept_i(rsp_fire), .grant(grant));
      assign owner = grant[1];
      assign addr  = m_addr[owner];
      assign issue = |(grant & m_valid) && !busy;     // the owner's request is taken once
      assign m_ready = issue ? grant : 2'b00;
    
      bus_decode3 dec (.addr(addr), .sel0(sel[0]), .sel1(sel[1]), .sel2(sel[2]), .default_err(sel[3]));
      ram_slave     ram (.clk, .rst_n, .req(issue && sel[0]), .write(m_write[owner]), .addr, .wdata(m_wdata[owner]),
                         .rv(rv[0]), .rdata(rd[0]), .resp(rs[0]));
      csr_slave     csr (.clk, .rst_n, .req(issue && sel[1]), .write(m_write[owner]), .addr, .wdata(m_wdata[owner]),
                         .rv(rv[1]), .rdata(rd[1]), .resp(rs[1]));
      slow_slave    slw (.clk, .rst_n, .req(issue && sel[2]), .write(m_write[owner]), .addr, .wdata(m_wdata[owner]),
                         .abort(timeout), .rv(rv[2]), .rdata(rd[2]), .resp(rs[2]));
      default_slave dfl (.clk, .rst_n, .req(issue && sel[3]), .rv(rv[3]), .rdata(rd[3]), .resp(rs[3]));
      bus_timeout #(.LIMIT(LIMIT)) tmo (.clk, .rst_n, .req(busy && sel_q[2]), .ack(rv[2]), .timeout(timeout));
    
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin busy <= 0; sel_q <= 0; end
        else if (issue)    begin busy <= 1; sel_q <= sel; end   // remember who must answer
        else if (rsp_fire) begin busy <= 0; sel_q <= 0; end
    
      // Response mux: AND-OR qualified by the registered select, as in bus_rmux.
      assign rsp_fire = busy && (|(sel_q & rv) || timeout);
      assign m_rdata  = timeout ? 32'h0 : ({32{sel_q[0]}} & rd[0]) | ({32{sel_q[1]}} & rd[1])
                                        | ({32{sel_q[2]}} & rd[2]) | ({32{sel_q[3]}} & rd[3]);
      assign m_resp   = timeout ? 2'b10 : ({2{sel_q[0]}} & rs[0]) | ({2{sel_q[1]}} & rs[1])
                                        | ({2{sel_q[2]}} & rs[2]) | ({2{sel_q[3]}} & rs[3]);
      assign m_rvalid = rsp_fire ? grant : 2'b00;     // routed to the owner, never to a requester
    endmodule
    

    Remember

    • The grant names the owner until the reply. End the ownership interval on the response, never on the request handshake.

    • Register the slave select at issue and steer the reply by it. Unmapped addresses get DECERR from a default slave that always answers.

    • A timeout ends a hung access with SLVERR and must also stop the slave from replying late. Every bus bench needs watchdogs.

    Rule of thumb: route each reply by who owns the transaction, never by who is asking now.

    How this was checked

    Tool: Icarus Verilog 13.0 (iverilog -g2012, vvp), run on 2026-10-17. Command: iverilog -g2012 -s tb_bus_fabric -o bus_fabric.vvp bus_fabric.sv && vvp -n bus_fabric.vvp, where bus_fabric.sv is the page's full listing.

    Result: PASS: 3007 transactions checked: RAM 1398, CSR 570, slow 609, default 430; SLVERR 387 (80 of them timeouts), DECERR 430; longest wait to be taken 8 cycles. The directed reply lines are copied from the same run.

    Variants of the same listing, one line changed each: accept_i(issue) gives FAIL @75 ns: response to master 1, then the 40-cycle watchdog; a silent default slave gives the 12-cycle request watchdog at 325 ns; slow-slave latency 6 gives 529 errors, latency 4 and 5 pass.

    Waveform: wave-src/bus-integration/fabric.sv (the same RTL, a two-request scenario) through node scripts/tools/gen-waves.mjs memory-buses:bus-integration; it matched the hand-written expectation.

    Scope and limits: one clock, one transaction in flight, masters always accept replies, no byte strobes, fixed seed. No formal proof; the random run covers the sequences it happened to draw.

    Keep going

    • Practice: Memory arbiter for two masters

    • Practice: AXI interconnect arbiter design and verification

    • Code lab: Round-robin arbiter

    • Interview: run a mock interview on this topic

    Related topics

    • Integration Checklist

    • Skid buffer and pipeline stages

    Previous

    215 / 350

    Next: Synchronous FIFO

    Up next

    Up nextSynchronous FIFO

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Address Decode
    • Mux Trees and Read Paths
    • Error Paths and Timeouts
    m_valid00111000grant0001001000sel_q00000100000010000000timeoutm_rvalid0001001000m_resp0010001100
    Master 0 reads a slow-peripheral hole and gets a timeout SLVERR in slot 7; master 1, which has waited since slot 1, then reads an unmapped address and gets DECERR from the default slave in slot 10