AHB‑Lite Single‑Transfer, No‑Wait Verilog Implementation with One Master and Four Slaves
Overview
This example implements a minimal AHB‑Lite–style interconnect that supports single, non‑burst transfers with no wait states. The system contains one master and four simple memory‑mapped slaves. Because there is only one master, no arbiter is required. An address decoder generates one‑hot select signals for the slaves, and a response multiplexer forwards the selected slave’s response back to the master.
Notes:
- Slave selection uses the top two address bits (addr[31:30]) to choose one of four slaves.
- Each slave exposes a small 32×32 memory; the lower five bits (addr[4:0]) index the local memory.
- The master asserts an address/control phase for one cycle, then performs a single READ or WRITE data phase.
Key signals used in this simplified design:
- haddr: address from master to slaves
- hwrite: 1 for write, 0 for read
- hvalid: master indicates "address/control valid" (setup phase)
- hwdata/hrdata: write data/read data
- hreadyout: slave response; held at 1 in this no‑wait model (still modeled as a handshake)
- hsel: per‑slave select from decoder
Top‑Level Interconnect
The top module wires up the master, four slaves, the address decoder, and the response multiplexer.
module ahb_sys (
input wire hclk,
input wire hresetn,
// Simple host-side control
input wire start, // trigger a single transfer
input wire wr, // 1=write, 0=read
input wire [31:0] addr,
input wire [31:0] din,
output wire [31:0] dout // readback for observation
);
// Bus wires
wire [31:0] haddr;
wire hwrite;
wire hvalid;
wire [31:0] hwdata;
wire [31:0] hrdata;
wire hreadyout;
// Slave selects and responses
wire [3:0] hsel;
wire [1:0] sel_idx;
wire [31:0] hrdata_s [3:0];
wire hreadyout_s [3:0];
// Master
ahb_master_lite u_master (
.hclk (hclk),
.hresetn (hresetn),
.start (start),
.addr_i (addr),
.wr_i (wr),
.data_i (din),
.hrdata_i (hrdata),
.hready_i (hreadyout),
.haddr_o (haddr),
.hwrite_o (hwrite),
.hvalid_o (hvalid),
.hwdata_o (hwdata),
.data_o (dout)
);
// Four identical slaves
ahb_periph u_s0 (
.hclk (hclk),
.hresetn (hresetn),
.hsel (hsel[0]),
.haddr (haddr),
.hwrite (hwrite),
.hvalid (hvalid),
.hwdata (hwdata),
.hreadyout (hreadyout_s[0]),
.hrdata (hrdata_s[0])
);
ahb_periph u_s1 (
.hclk (hclk),
.hresetn (hresetn),
.hsel (hsel[1]),
.haddr (haddr),
.hwrite (hwrite),
.hvalid (hvalid),
.hwdata (hwdata),
.hreadyout (hreadyout_s[1]),
.hrdata (hrdata_s[1])
);
ahb_periph u_s2 (
.hclk (hclk),
.hresetn (hresetn),
.hsel (hsel[2]),
.haddr (haddr),
.hwrite (hwrite),
.hvalid (hvalid),
.hwdata (hwdata),
.hreadyout (hreadyout_s[2]),
.hrdata (hrdata_s[2])
);
ahb_periph u_s3 (
.hclk (hclk),
.hresetn (hresetn),
.hsel (hsel[3]),
.haddr (haddr),
.hwrite (hwrite),
.hvalid (hvalid),
.hwdata (hwdata),
.hreadyout (hreadyout_s[3]),
.hrdata (hrdata_s[3])
);
// Address decoder (uses top-level addr for selection timing simplicity)
ahb_decoder2x4 u_dec (
.addr (addr),
.hsel (hsel),
.sel_idx (sel_idx)
);
// Response MUX
ahb_resp_mux u_mux (
.sel_idx (sel_idx),
.hrdata_s0 (hrdata_s[0]),
.hrdata_s1 (hrdata_s[1]),
.hrdata_s2 (hrdata_s[2]),
.hrdata_s3 (hrdata_s[3]),
.hreadyout_s0 (hreadyout_s[0]),
.hreadyout_s1 (hreadyout_s[1]),
.hreadyout_s2 (hreadyout_s[2]),
.hreadyout_s3 (hreadyout_s[3]),
.hrdata (hrdata),
.hreadyout (hreadyout)
);
endmodule
Master
A small FSM sequences through SETUP (address/control) and ACCESS (read/write) for a single trasnfer.
module ahb_master_lite (
input wire hclk,
input wire hresetn,
input wire start, // kick a transfer
input wire [31:0] addr_i,
input wire wr_i,
input wire [31:0] data_i,
input wire [31:0] hrdata_i,
input wire hready_i,
output reg [31:0] haddr_o,
output reg hwrite_o,
output reg hvalid_o,
output reg [31:0] hwdata_o,
output reg [31:0] data_o
);
localparam S_IDLE = 2'd0;
localparam S_SETUP = 2'd1;
localparam S_WRITE = 2'd2;
localparam S_READ = 2'd3;
reg [1:0] state, nstate;
// State register
always @(posedge hclk or negedge hresetn) begin
if (!hresetn)
state <= S_IDLE;
else
state <= nstate;
end
// Next-state logic
always @* begin
nstate = state;
case (state)
S_IDLE: nstate = start ? S_SETUP : S_IDLE;
S_SETUP: nstate = wr_i ? S_WRITE : S_READ;
S_WRITE: nstate = hready_i ? S_IDLE : S_WRITE;
S_READ: nstate = hready_i ? S_IDLE : S_READ;
default: nstate = S_IDLE;
endcase
end
// Outputs
always @(posedge hclk or negedge hresetn) begin
if (!hresetn) begin
haddr_o <= 32'h0;
hwrite_o <= 1'b0;
hvalid_o <= 1'b0;
hwdata_o <= 32'h0;
data_o <= 32'h0;
end else begin
case (state)
S_IDLE: begin
haddr_o <= 32'h0;
hwrite_o <= 1'b0;
hvalid_o <= 1'b0;
hwdata_o <= 32'h0;
data_o <= 32'h0;
end
S_SETUP: begin
haddr_o <= addr_i;
hwrite_o <= wr_i;
hvalid_o <= 1'b1; // address/control valid for one cycle
hwdata_o <= 32'h0;
end
S_WRITE: begin
haddr_o <= 32'h0;
hwrite_o <= 1'b0;
hvalid_o <= 1'b0;
hwdata_o <= data_i; // place write data
end
S_READ: begin
haddr_o <= 32'h0;
hwrite_o <= 1'b0;
hvalid_o <= 1'b0;
hwdata_o <= 32'h0;
if (hready_i)
data_o <= hrdata_i; // capture read data
end
default: begin
haddr_o <= 32'h0;
hwrite_o <= 1'b0;
hvalid_o <= 1'b0;
hwdata_o <= 32'h0;
data_o <= 32'h0;
end
endcase
end
end
endmodule
Slave (Memory‑Mapped Peripheral)
Each slave implements a tiny 32×32 register file. Address/control are sampled in SETUP; the ACCESS phase iether writes the memory or returns its contents.
module ahb_periph (
input wire hclk,
input wire hresetn,
input wire hsel,
input wire [31:0] haddr,
input wire hwrite,
input wire hvalid, // master’s address/control valid (setup)
input wire [31:0] hwdata,
output reg hreadyout,
output reg [31:0] hrdata
);
// 32 x 32-bit memory
reg [31:0] mem [0:31];
reg [4:0] idx_r;
localparam ST_IDLE = 2'd0;
localparam ST_SETUP = 2'd1;
localparam ST_WACC = 2'd2;
localparam ST_RACC = 2'd3;
reg [1:0] state, nstate;
// FSM
always @(posedge hclk or negedge hresetn) begin
if (!hresetn)
state <= ST_IDLE;
else
state <= nstate;
end
always @* begin
nstate = state;
case (state)
ST_IDLE: nstate = (hsel) ? ST_SETUP : ST_IDLE;
ST_SETUP: begin
if (hvalid) nstate = (hwrite ? ST_WACC : ST_RACC);
else nstate = ST_SETUP;
end
ST_WACC: nstate = ST_IDLE; // single beat
ST_RACC: nstate = ST_IDLE; // single beat
default: nstate = ST_IDLE;
endcase
end
// Outputs / storage
always @(posedge hclk or negedge hresetn) begin
if (!hresetn) begin
hreadyout <= 1'b0;
hrdata <= 32'h0;
idx_r <= 5'd0;
end else begin
case (state)
ST_IDLE: begin
hreadyout <= 1'b0;
hrdata <= 32'h0;
end
ST_SETUP: begin
hreadyout <= 1'b0;
// latch the local index from address LSBs
if (hvalid)
idx_r <= haddr[4:0];
end
ST_WACC: begin
mem[idx_r] <= hwdata;
hreadyout <= 1'b1; // ready immediately in no-wait model
end
ST_RACC: begin
hrdata <= mem[idx_r];
hreadyout <= 1'b1;
end
default: begin
hreadyout <= 1'b0;
hrdata <= 32'h0;
end
endcase
end
end
endmodule
Address Decoder (2‑to‑4)
Generates one‑hot HSEL and an index used by the response mux based on addr[31:30].
module ahb_decoder2x4 (
input wire [31:0] addr,
output reg [3:0] hsel,
output wire [1:0] sel_idx
);
assign sel_idx = addr[31:30];
always @* begin
case (sel_idx)
2'b00: hsel = 4'b0001;
2'b01: hsel = 4'b0010;
2'b10: hsel = 4'b0100;
2'b11: hsel = 4'b1000;
default: hsel = 4'b0000;
endcase
end
endmodule
Response Multilpexer
Selects HRDATA and HREADYOUT from the addressed slave.
module ahb_resp_mux (
input wire [1:0] sel_idx,
input wire [31:0] hrdata_s0,
input wire [31:0] hrdata_s1,
input wire [31:0] hrdata_s2,
input wire [31:0] hrdata_s3,
input wire hreadyout_s0,
input wire hreadyout_s1,
input wire hreadyout_s2,
input wire hreadyout_s3,
output reg [31:0] hrdata,
output reg hreadyout
);
always @* begin
case (sel_idx)
2'b00: begin hrdata = hrdata_s0; hreadyout = hreadyout_s0; end
2'b01: begin hrdata = hrdata_s1; hreadyout = hreadyout_s1; end
2'b10: begin hrdata = hrdata_s2; hreadyout = hreadyout_s2; end
2'b11: begin hrdata = hrdata_s3; hreadyout = hreadyout_s3; end
default: begin hrdata = 32'h0; hreadyout = 1'b0; end
endcase
end
endmodule
Testbench
The testbench writes the values 1, 2, 3, 4 to four distinct slave locations, reads each back immediate, then re‑reads all four addresses.
`timescale 1ns/1ns
module tb_ahb_sys;
reg hclk;
reg hresetn;
reg start;
reg wr;
reg [31:0] addr;
reg [31:0] din;
wire [31:0] dout;
// DUT
ahb_sys dut (
.hclk (hclk),
.hresetn (hresetn),
.start (start),
.wr (wr),
.addr (addr),
.din (din),
.dout (dout)
);
// Clock
initial hclk = 1'b0;
always #2 hclk = ~hclk; // 250 MHz
// Simple tasks (single-beat transfers)
task do_write(input [31:0] a, input [31:0] v);
begin
@(posedge hclk);
addr <= a;
din <= v;
wr <= 1'b1;
start <= 1'b1; // setup phase
@(posedge hclk);
start <= 1'b0; // access phase (write data driven by master)
@(posedge hclk);
wr <= 1'b0;
end
endtask
task do_read(input [31:0] a);
begin
@(posedge hclk);
addr <= a;
wr <= 1'b0;
start <= 1'b1; // setup phase
@(posedge hclk);
start <= 1'b0; // access phase (slave returns data)
@(posedge hclk);
end
endtask
initial begin
hresetn = 1'b0;
start = 1'b0;
wr = 1'b0;
addr = 32'h0;
din = 32'h0;
#20 hresetn = 1'b1;
// Writes to four slaves (addr[31:30] selects slave), unique word offsets
do_write(32'h0000_0000, 32'd1); // S0
do_read (32'h0000_0000);
do_write(32'h4000_0004, 32'd2); // S1
do_read (32'h4000_0004);
do_write(32'h8000_0008, 32'd3); // S2
do_read (32'h8000_0008);
do_write(32'hC000_000C, 32'd4); // S3
do_read (32'hC000_000C);
// Final sweep of all addresses
do_read (32'h0000_0000);
do_read (32'h4000_0004);
do_read (32'h8000_0008);
do_read (32'hC000_000C);
#20 $finish;
end
endmodule