ahb_lite总线

参考

高级外设总线APB.csdn

AHB-Lite 的信号定义

SMMR 接口为异步返回,主机需轮询状态标志完成交互。
AHB-Lite 基于同步时序实现读写,采用“地址/控制阶段 + 数据阶段”的传输机制。

对于一个最简单的零等待 AHB-Lite 从设备,可以在一个有效传输周期内完成访问;
如果从设备无法立即完成访问,则通过 HREADY=0 插入等待周期。

  • s_ahb_hclk :AHB-Lite 总线时钟
  • s_ahb_hresetn :AHB-Lite 总线复位,低电平有效
  • s_ahb_haddr :AHB-Lite 访问地址
  • s_ahb_htrans :AHB-Lite 传输类型
    • 2'b00:IDLE
    • 2'b01:BUSY
    • 2'b10:NONSEQ,新的传输开始
    • 2'b11:SEQ,连续传输
  • s_ahb_hwrite :AHB-Lite 读写控制信号
    • 1'b1:写操作
    • 1'b0:读操作
  • s_ahb_hsize :传输数据大小
  • s_ahb_hburst :突发类型
  • s_ahb_hprot :访问保护属性
  • s_ahb_hwdata :AHB-Lite 写数据
  • s_ahb_hrdata :AHB-Lite 读数据
  • s_ahb_hready :传输完成/从设备就绪信号
  • s_ahb_hresp :传输响应信号
    • 1'b0:OKAY
    • 1'b1:ERROR

AHB-Lite 传输机制

AHB-Lite 没有 APB 的 SETUP/ACCESS 两阶段。

主机首先给出地址和控制信号,然后进入数据阶段。

HREADY=1 表示当前传输完成。

HREADY=0 表示从设备暂时无法完成传输,需要插入等待周期。

读操作时从设备通过 HRDATA 返回数据。

写操作时主设备通过 HWDATA 提供写入数据。

# AHB-Lite 传输阶段

                地址/控制阶段              数据阶段
时钟             T1                        T2
                 │                         │
                 ▼                         ▼
            ┌───────────────┐         ┌───────────────┐
            │ 地址 + 控制   │         │    数据阶段   │
            │               │         │               │
HADDR   ────┤ 地址          │─────────┤ 保持/传输     │
HWRITE  ────┤ 读写控制      │         │               │
HTRANS  ────┤ NONSEQ/SEQ    │         │               │
            └───────────────┘         │               │
                                      │ 写:HWDATA    │
                                      │ 读:HRDATA    │
                                      │               │
                                      │ HREADY        │
                                      └───────┬───────┘
                                              │
                                              ▼
                                         HREADY = 1
                                              │
                                              ▼
                                            完成


# 写事务

        地址/控制阶段              数据阶段
             │                       │
             ▼                       ▼

HADDR    ======= 地址 ========================
HWRITE   ======= 1 ==========================
HTRANS   ======= NONSEQ =====================
                         HWDATA = 写数据
                                  ============
                         HREADY = 1
                                  ============

# 读事务

        地址/控制阶段              数据阶段
             │                       │
             ▼                       ▼

HADDR    ======= 地址 ========================
HWRITE   ======= 0 ==========================
HTRANS   ======= NONSEQ =====================

                         HRDATA = 读数据
                                  ============
                         HREADY = 1
                                  ============


# HTRANS 传输类型
HTRANS = 2'b00
        │
        └── IDLE
            当前没有传输

HTRANS = 2'b01
        │
        └── BUSY
            当前主机暂时无法继续传输

HTRANS = 2'b10
        │
        └── NONSEQ
            一次新的传输开始

HTRANS = 2'b11
        │
        └── SEQ
            连续传输

AHB-Lite 写事务 - 主机发起方(Master)

// ==============================================
// AHB-Lite 写事务 - 主机发起方
// ==============================================
// 1️⃣ 地址/控制阶段
// 输出目标地址和控制信息
s_ahb_haddr  <= write_addr;       // 📍 目标写地址
s_ahb_hwrite <= 1'b1;             // ✏️ 写操作
s_ahb_htrans <= 2'b10;            // 🚀 NONSEQ,新传输
wait (posedge s_ahb_hclk);
// 2️⃣ 数据阶段
// 提供需要写入的数据
s_ahb_hwdata <= write_data;       // 📝 待写入数据
wait (s_ahb_hready);              // ⏳ 等待从设备完成
// 3️⃣ 检查响应
write_error = s_ahb_hresp;         // 📋 获取错误状态
// 4️⃣ 结束事务
s_ahb_htrans <= 2'b00;            // 🔚 返回 IDLE
// 💡 AHB-Lite 写事务完成

AHB-Lite 写事务 - 从机响应方(Slave)

// ==============================================
// AHB-Lite 写事务 - 从机响应方
// ==============================================
// 1️⃣ 检测有效传输
if (s_ahb_htrans[1]) begin
    if (s_ahb_hwrite) begin
        wr_addr = s_ahb_haddr;     // 📍 获取写地址
    end
end
// 2️⃣ 数据阶段
// 下一周期 HWDATA 有效
wait (posedge s_ahb_hclk);
// 3️⃣ 执行写操作
if (wr_valid) begin
    reg_file[wr_addr] <= s_ahb_hwdata;  // 💾 写入寄存器
end
// 4️⃣ 返回响应
s_ahb_hready <= 1'b1;              // ✅ 传输完成
s_ahb_hresp  <= 1'b0;              // ✔️ OKAY
// 💡 AHB-Lite 写事务结束

AHB-Lite 读事务 - 主机发起方(Master)

// ==============================================
// AHB-Lite 读事务 - 主机发起方
// ==============================================

// 1️⃣ 地址/控制阶段
// 输出目标地址和控制信息

s_ahb_haddr  <= read_addr;        // 📍 目标读地址
s_ahb_hwrite <= 1'b0;             // 📖 读操作
s_ahb_htrans <= 2'b10;            // 🚀 NONSEQ,新传输

wait (posedge s_ahb_hclk);


// 2️⃣ 等待从设备完成

wait (s_ahb_hready);


// 3️⃣ 获取读数据

read_data  = s_ahb_hrdata;        // 📥 接收读数据
read_error = s_ahb_hresp;          // 📋 获取错误状态


// 4️⃣ 结束事务

s_ahb_htrans <= 2'b00;             // 🔚 返回 IDLE

// 💡 AHB-Lite 读事务完成

AHB-Lite 读事务 - 从机响应方(Slave)

// ==============================================
// AHB-Lite 读事务 - 从机响应方
// ==============================================

// 1️⃣ 检测有效传输

if (s_ahb_htrans[1]) begin

    if (!s_ahb_hwrite) begin

        rd_addr = s_ahb_haddr;     // 📍 获取读地址

    end

end
// 2️⃣ 准备读数据
s_ahb_hrdata <= reg_file[rd_addr]; // 📤 返回寄存器数据
// 3️⃣ 返回响应
s_ahb_hready <= 1'b1;              // ✅ 数据有效
s_ahb_hresp  <= 1'b0;              // ✔️ OKAY

// 💡 AHB-Lite 读事务结束

ahblite_slave_smmr_bridge.v

ahblite_slave转成smmr

// =============================================================================
// 模块名称 : ahblite_slave_smmr_bridge
// 功能说明 : AHB-Lite Slave 接口到 SMMR(Simple Memory Mapped Register)接口的桥接模块
//
// 设计要点 :
//   1. AHB-Lite 总线是地址相位/数据相位流水结构。
//      - 写传输时,HADDR/HWRITE/HTRANS 在地址相位给出;
//      - 对应的 HWDATA 在下一拍的数据相位给出。
//   2. 本模块把 AHB 字节地址转换成 slot0 使用的寄存器编号。
//      - AHB 侧访问地址 : 0x0, 0x4, 0x8, 0xC
//      - SMMR 侧寄存器地址 : 0, 1, 2, 3
//   3. 本模块为零等待 AHB-Lite Slave。
//      - HREADYOUT 固定为 1;
//      - HRESP 固定为 OKAY;
//      - 每个有效 NONSEQ/SEQ beat 都会转换成一次 SMMR 读或写。
//   4. 支持 AHB-Lite burst 访问。
//      - 模块不主动解析 HBURST 计数;
//      - AHB master 在 burst 中逐拍给出递增后的 HADDR;
//      - 本模块对每个有效 beat 独立处理,因此 INCR4 写/读 4 个寄存器可以连续完成。
//
// 典型连接 :
//   o_reg_wr_en   -> slot0.i_reg_wr_en
//   o_reg_wr_addr -> slot0.i_reg_wr_addr
//   o_reg_wr_data -> slot0.i_reg_wr_data
//   o_reg_rd_addr -> slot0.i_reg_rd_addr
//   i_reg_rd_data <- slot0.o_reg_rd_data
// =============================================================================
module ahblite_slave_smmr_bridge #(
    parameter P_S_AHB_ADDR_WIDTH = 32, // AHB-Lite 地址总线宽度,单位 bit
    parameter P_S_AHB_DATA_WIDTH = 32, // AHB-Lite 数据总线宽度,当前按 32-bit word 访问设计
    parameter P_S_MEM_SIZE       = 16, // 当前映射空间大小,单位 byte;16 byte 对应 4 个 32-bit 寄存器
    parameter P_REG_ADDR_WIDTH   = 7   // SMMR 寄存器地址宽度,需要和 slot0.P_ADDR_WIDTH 保持一致
)(
    // =========================
    // AHB-Lite Slave 接口,master -> slave
    // =========================
    input  wire                              s_ahb_hclk,       // AHB-Lite 时钟,所有时序逻辑在该时钟上升沿工作
    input  wire                              s_ahb_hresetn,    // AHB-Lite 异步低有效复位
    input  wire                              s_ahb_hsel,       // Slave 片选,高有效;为 1 表示当前地址命中本模块
    input  wire [P_S_AHB_ADDR_WIDTH-1:0]     s_ahb_haddr,      // 地址相位地址,AHB 侧为 byte address
    input  wire [1:0]                        s_ahb_htrans,     // 传输类型:00=IDLE,01=BUSY,10=NONSEQ,11=SEQ
    input  wire                              s_ahb_hwrite,     // 传输方向:1=写,0=读;在地址相位有效
    input  wire [2:0]                        s_ahb_hsize,      // 传输大小;当前桥按 32-bit word 使用,通常应为 3'b010
    input  wire [2:0]                        s_ahb_hburst,     // 突发类型;本模块不计数,只按每个有效 beat 处理
    input  wire [P_S_AHB_DATA_WIDTH-1:0]     s_ahb_hwdata,     // 写数据;对应上一拍地址相位的写传输
    input  wire                              s_ahb_hready,     // 上一级/总线全局 ready;为 1 时地址相位和数据相位才能推进
    output wire                              s_ahb_hreadyout,  // 本 slave ready 输出;本模块零等待,固定拉高
    output reg  [P_S_AHB_DATA_WIDTH-1:0]     s_ahb_hrdata,     // 读数据;在读传输的数据相位返回给 master
    output wire                              s_ahb_hresp,      // 传输响应;0=OKAY,1=ERROR,本模块固定 OKAY

    // =========================
    // SMMR 简单寄存器接口
    // =========================
    output reg                               o_reg_wr_en,      // 寄存器写使能,高有效;写数据相位产生 1 拍脉冲
    output reg  [P_REG_ADDR_WIDTH-1:0]       o_reg_wr_addr,    // 寄存器写地址;由上一拍 HADDR[...:2] 转换得到
    output reg  [P_S_AHB_DATA_WIDTH-1:0]     o_reg_wr_data,    // 寄存器写数据;直接来自当前数据相位 HWDATA
    output reg  [P_REG_ADDR_WIDTH-1:0]       o_reg_rd_addr,    // 寄存器读地址;读地址相位锁存后送给下游
    input  wire [P_S_AHB_DATA_WIDTH-1:0]     i_reg_rd_data     // 寄存器读数据;由 slot0 等下游寄存器模块返回
);

    // 32-bit word 访问时,AHB byte address 的 bit[1:0] 是字节偏移。
    // slot0 的地址是寄存器编号,因此需要右移 2 bit:
    //   HADDR 0x00 -> reg addr 0
    //   HADDR 0x04 -> reg addr 1
    //   HADDR 0x08 -> reg addr 2
    //   HADDR 0x0c -> reg addr 3
    localparam C_REG_ADDR_LSB = 2;

    // AHB-Lite 的写数据/读数据都发生在地址相位的下一拍。
    // 因此需要把上一拍地址相位中的“是否有效、读写方向、寄存器地址”锁存下来,
    // 到当前数据相位使用。
    reg                            r_data_phase_valid; // 当前数据相位是否对应一个有效传输
    reg                            r_data_phase_write; // 当前数据相位对应的传输方向:1=写,0=读
    reg  [P_REG_ADDR_WIDTH-1:0]    r_data_phase_addr;  // 当前数据相位对应的 SMMR 寄存器地址

    // 地址相位有效判断:
    //   s_ahb_hready  为 1,表示流水线允许推进;
    //   s_ahb_hsel    为 1,表示本 slave 被选中;
    //   HTRANS[1]     为 1,表示 NONSEQ 或 SEQ,有真实传输。
    // IDLE/BUSY 不会产生寄存器访问。
    wire w_addr_phase_valid = s_ahb_hready && s_ahb_hsel && s_ahb_htrans[1];

    // AHB byte address -> SMMR register index。
    // 对 slot0 的 4 个寄存器,master 应访问 0x0/0x4/0x8/0xC,
    // 这里转换后分别得到 0/1/2/3。
    wire [P_REG_ADDR_WIDTH-1:0] w_addr_phase_reg_addr =
        s_ahb_haddr[P_REG_ADDR_WIDTH+C_REG_ADDR_LSB-1:C_REG_ADDR_LSB];

    // 零等待响应:
    //   HREADYOUT 固定为 1,表示本 slave 不插入 wait state;
    //   HRESP 固定为 0,表示所有访问均返回 OKAY。
    // 地址越界的过滤由 slot0 的地址范围判断完成,越界读返回 0,越界写不生效。
    assign s_ahb_hreadyout = 1'b1;
    assign s_ahb_hresp     = 1'b0;

    // 写通路:
    //   AHB 写数据在数据相位出现,也就是地址相位后一拍。
    //   r_data_phase_* 保存的正是上一拍地址相位信息。
    //
    // 这里组合产生 o_reg_wr_en,是为了让 slot0 这种 posedge 写寄存器的模块,
    // 能在当前 HWDATA 数据相位的同一个时钟上升沿采样到写使能、写地址和写数据。
    //
    // burst 写时,每个有效 beat 都会形成一次写脉冲:
    //   addr phase : 0x0   0x4   0x8   0xC
    //   data phase :       D0    D1    D2    D3
    //   reg write  :       R0    R1    R2    R3
    always @(*) begin
        o_reg_wr_en   = s_ahb_hresetn && s_ahb_hready && r_data_phase_valid && r_data_phase_write;
        o_reg_wr_addr = r_data_phase_addr;
        o_reg_wr_data = s_ahb_hwdata;
    end

    // 读数据返回通路:
    //   读地址在地址相位锁存到 o_reg_rd_addr;
    //   slot0 的读数据是组合逻辑输出;
    //   下一拍进入读数据相位时,直接把 i_reg_rd_data 返回到 HRDATA。
    //
    // 这样 INCR4 burst 读时,可以每拍返回一个寄存器值,不需要插入等待周期。
    always @(*) begin
        if (s_ahb_hresetn && r_data_phase_valid && !r_data_phase_write) begin
            s_ahb_hrdata = i_reg_rd_data;
        end else begin
            s_ahb_hrdata = {P_S_AHB_DATA_WIDTH{1'b0}};
        end
    end

    always @(posedge s_ahb_hclk or negedge s_ahb_hresetn) begin
        if (!s_ahb_hresetn) begin
            r_data_phase_valid <= 1'b0;
            r_data_phase_write <= 1'b0;
            r_data_phase_addr  <= {P_REG_ADDR_WIDTH{1'b0}};
            o_reg_rd_addr      <= {P_REG_ADDR_WIDTH{1'b0}};
        end else begin
            if (s_ahb_hready) begin
                // 把当前地址相位信息锁存到下一拍的数据相位。
                // 连续 burst 中,NONSEQ 和 SEQ 都会被锁存,因此每拍都能处理一个 beat。
                r_data_phase_valid <= w_addr_phase_valid;
                r_data_phase_write <= s_ahb_hwrite;
                r_data_phase_addr  <= w_addr_phase_reg_addr;

                // 读传输需要提前把读地址送到下游寄存器模块。
                // 到下一拍数据相位时,i_reg_rd_data 已经对应这个地址。
                if (w_addr_phase_valid && !s_ahb_hwrite) begin
                    o_reg_rd_addr <= w_addr_phase_reg_addr;
                end
            end
        end
    end

endmodule

slot0.v

module slot0 #(
    parameter P_ADDR_WIDTH = 7,
    parameter P_DATA_WIDTH = 32,
    parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 0,
    parameter P_REG_COUNT  = 4
)(
    input  wire                    i_clk,
    input  wire                    i_rst_n,

    input  wire                    i_reg_wr_en,
    input  wire [P_ADDR_WIDTH-1:0] i_reg_wr_addr,
    input  wire [P_DATA_WIDTH-1:0] i_reg_wr_data,
    input  wire [P_ADDR_WIDTH-1:0] i_reg_rd_addr,
    output reg  [P_DATA_WIDTH-1:0] o_reg_rd_data
);

    reg [P_DATA_WIDTH-1:0] r_mem [0:P_REG_COUNT-1];

    //写
    integer i;
    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            for (i = 0; i < P_REG_COUNT; i = i + 1) begin
                r_mem[i] <= {P_DATA_WIDTH{1'b0}};
            end
        end else if (i_reg_wr_en && (i_reg_wr_addr >= P_SLOT_BASE) && (i_reg_wr_addr < (P_SLOT_BASE + P_REG_COUNT))) begin
            r_mem[i_reg_wr_addr-P_SLOT_BASE] <= i_reg_wr_data;
        end
    end

    //读
    always @(*) begin
        if ((i_reg_rd_addr >= P_SLOT_BASE) && (i_reg_rd_addr < (P_SLOT_BASE + P_REG_COUNT))) begin
            o_reg_rd_data = r_mem[i_reg_rd_addr-P_SLOT_BASE];
        end else begin
            o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
        end
    end

endmodule

tb_ahblite_slave_smmr_bridge.v

读写 读写 slot.v 的4个寄存器

`timescale 1ns/1ps

module tb;

    localparam HTRANS_IDLE   = 2'b00;
    localparam HTRANS_NONSEQ = 2'b10;
    localparam HTRANS_SEQ    = 2'b11;
    localparam HBURST_SINGLE = 3'b000;
    localparam HBURST_INCR4  = 3'b011;
    localparam HSIZE_WORD    = 3'b010;

    reg          hclk;
    reg          hresetn;
    reg          hsel;
    reg  [31:0]  haddr;
    reg  [1:0]   htrans;
    reg          hwrite;
    reg  [2:0]   hsize;
    reg  [2:0]   hburst;
    reg  [31:0]  hwdata;
    wire         hreadyout;
    wire [31:0]  hrdata;
    wire         hresp;

    wire         reg_wr_en;
    wire [6:0]   reg_wr_addr;
    wire [31:0]  reg_wr_data;
    wire [6:0]   reg_rd_addr;
    wire [31:0]  reg_rd_data;

    ahblite_slave_smmr_bridge #(
        .P_S_AHB_ADDR_WIDTH(32),
        .P_S_AHB_DATA_WIDTH(32),
        .P_S_MEM_SIZE(16),
        .P_REG_ADDR_WIDTH(7)
    ) u_bridge (
        .s_ahb_hclk     (hclk),
        .s_ahb_hresetn  (hresetn),
        .s_ahb_hsel     (hsel),
        .s_ahb_haddr    (haddr),
        .s_ahb_htrans   (htrans),
        .s_ahb_hwrite   (hwrite),
        .s_ahb_hsize    (hsize),
        .s_ahb_hburst   (hburst),
        .s_ahb_hwdata   (hwdata),
        .s_ahb_hready   (hreadyout),
        .s_ahb_hreadyout(hreadyout),
        .s_ahb_hrdata   (hrdata),
        .s_ahb_hresp    (hresp),
        .o_reg_wr_en    (reg_wr_en),
        .o_reg_wr_addr  (reg_wr_addr),
        .o_reg_wr_data  (reg_wr_data),
        .o_reg_rd_addr  (reg_rd_addr),
        .i_reg_rd_data  (reg_rd_data)
    );

    slot0 #(
        .P_ADDR_WIDTH(7),
        .P_DATA_WIDTH(32),
        .P_SLOT_BASE(0),
        .P_REG_COUNT(4)
    ) u_slot0 (
        .i_clk        (hclk),
        .i_rst_n      (hresetn),
        .i_reg_wr_en  (reg_wr_en),
        .i_reg_wr_addr(reg_wr_addr),
        .i_reg_wr_data(reg_wr_data),
        .i_reg_rd_addr(reg_rd_addr),
        .o_reg_rd_data(reg_rd_data)
    );

    always #5 hclk = ~hclk;

    task ahb_idle;
        begin
            @(negedge hclk);
            hsel   = 1'b0;
            haddr  = 32'h0;
            htrans = HTRANS_IDLE;
            hwrite = 1'b0;
            hsize  = HSIZE_WORD;
            hburst = HBURST_SINGLE;
            hwdata = 32'h0;
        end
    endtask

    task check_word;
        input [255:0] name;
        input [31:0] got;
        input [31:0] exp;
        begin
            if (got !== exp) begin
                $display("FAIL %0s got=%08x exp=%08x", name, got, exp);
                $finish;
            end
        end
    endtask

    task ahb_write_single;
        input [31:0] addr;
        input [31:0] data;
        begin
            @(negedge hclk);
            hsel   = 1'b1;
            haddr  = addr;
            htrans = HTRANS_NONSEQ;
            hwrite = 1'b1;
            hsize  = HSIZE_WORD;
            hburst = HBURST_SINGLE;

            @(negedge hclk);
            hsel   = 1'b0;
            htrans = HTRANS_IDLE;
            hwrite = 1'b0;
            hwdata = data;

            @(negedge hclk);
            hwdata = 32'h0;
        end
    endtask

    task ahb_read_single;
        input  [31:0] addr;
        output [31:0] data;
        begin
            @(negedge hclk);
            hsel   = 1'b1;
            haddr  = addr;
            htrans = HTRANS_NONSEQ;
            hwrite = 1'b0;
            hsize  = HSIZE_WORD;
            hburst = HBURST_SINGLE;

            @(negedge hclk);
            hsel   = 1'b0;
            htrans = HTRANS_IDLE;
            data   = hrdata;
        end
    endtask

    task ahb_burst_write4;
        input [31:0] base_addr;
        input [31:0] data0;
        input [31:0] data1;
        input [31:0] data2;
        input [31:0] data3;
        begin
            @(negedge hclk);
            hsel   = 1'b1;
            haddr  = base_addr;
            htrans = HTRANS_NONSEQ;
            hwrite = 1'b1;
            hsize  = HSIZE_WORD;
            hburst = HBURST_INCR4;

            @(negedge hclk);
            haddr  = base_addr + 32'd4;
            htrans = HTRANS_SEQ;
            hwdata = data0;

            @(negedge hclk);
            haddr  = base_addr + 32'd8;
            htrans = HTRANS_SEQ;
            hwdata = data1;

            @(negedge hclk);
            haddr  = base_addr + 32'd12;
            htrans = HTRANS_SEQ;
            hwdata = data2;

            @(negedge hclk);
            hsel   = 1'b0;
            htrans = HTRANS_IDLE;
            hwrite = 1'b0;
            hburst = HBURST_SINGLE;
            hwdata = data3;

            @(negedge hclk);
            hwdata = 32'h0;
        end
    endtask

    task ahb_burst_read4;
        input  [31:0] base_addr;
        output [31:0] data0;
        output [31:0] data1;
        output [31:0] data2;
        output [31:0] data3;
        begin
            @(negedge hclk);
            hsel   = 1'b1;
            haddr  = base_addr;
            htrans = HTRANS_NONSEQ;
            hwrite = 1'b0;
            hsize  = HSIZE_WORD;
            hburst = HBURST_INCR4;

            @(negedge hclk);
            haddr  = base_addr + 32'd4;
            htrans = HTRANS_SEQ;
            data0  = hrdata;

            @(negedge hclk);
            haddr  = base_addr + 32'd8;
            htrans = HTRANS_SEQ;
            data1  = hrdata;

            @(negedge hclk);
            haddr  = base_addr + 32'd12;
            htrans = HTRANS_SEQ;
            data2  = hrdata;

            @(negedge hclk);
            hsel   = 1'b0;
            htrans = HTRANS_IDLE;
            hburst = HBURST_SINGLE;
            data3  = hrdata;
        end
    endtask

    reg [31:0] rd0;
    reg [31:0] rd1;
    reg [31:0] rd2;
    reg [31:0] rd3;

    initial begin
        hclk    = 1'b0;
        hresetn = 1'b0;
        hsel    = 1'b0;
        haddr   = 32'h0;
        htrans  = HTRANS_IDLE;
        hwrite  = 1'b0;
        hsize   = HSIZE_WORD;
        hburst  = HBURST_SINGLE;
        hwdata  = 32'h0;

        repeat (4) @(negedge hclk);
        hresetn = 1'b1;

        ahb_write_single(32'h0000_0000, 32'h1122_3344);
        ahb_read_single(32'h0000_0000, rd0);
        check_word("single_read", rd0, 32'h1122_3344);

        ahb_burst_write4(32'h0000_0000,
                         32'h0000_0001,
                         32'h0000_0002,
                         32'h0000_0003,
                         32'h0000_0004);
        ahb_burst_read4(32'h0000_0000, rd0, rd1, rd2, rd3);
        $display("burst read back: %0d %0d %0d %0d", rd0, rd1, rd2, rd3);
        check_word("burst_read0", rd0, 32'h0000_0001);
        check_word("burst_read1", rd1, 32'h0000_0002);
        check_word("burst_read2", rd2, 32'h0000_0003);
        check_word("burst_read3", rd3, 32'h0000_0004);

        $display("PASS ahblite_slave_smmr_bridge single and INCR4 burst access");
        $finish;
    end

endmodule

ahblite_master.v

// =============================================================================
// 模块名称 : ahblite_master
// 功能说明 : AHB-Lite master 自测试模块
//
// 测试流程 :
//   1. 检测 i_start 上升沿;
//   2. 发起一次 INCR4 写突发,向地址 0x0/0x4/0x8/0xC 写入 1/2/3/4;
//   3. 发起一次 INCR4 读突发,从地址 0x0/0x4/0x8/0xC 读回 4 个数据;
//   4. 比较读回数据:
//      - 全部一致:o_done=1,o_led=1,表示 PASS;
//      - 任意错误:o_done=1,o_led=0,表示 FAIL。
//
// 说明 :
//   AHB-Lite 写数据 HWDATA 比写地址 HADDR 晚一个数据相位。
//   因此写突发时,本模块会先发第 0 个地址,然后下一拍开始给第 0 个写数据。
// =============================================================================
module ahblite_master #(
    parameter P_M_AHB_ADDR_WIDTH = 32,
    parameter P_M_AHB_DATA_WIDTH = 32,
    parameter [P_M_AHB_ADDR_WIDTH-1:0] P_TEST_BASE_ADDR = {P_M_AHB_ADDR_WIDTH{1'b0}}
)(
    input  wire                              i_clk,
    input  wire                              i_rst_n,
    input  wire                              i_start,
    output reg                               o_done,
    output reg                               o_led,

    // =========================
    // AHB-Lite Master interface
    // =========================
    output reg                               m_ahb_hsel,
    output reg  [P_M_AHB_ADDR_WIDTH-1:0]     m_ahb_haddr,
    output reg  [1:0]                        m_ahb_htrans,
    output reg                               m_ahb_hwrite,
    output reg  [2:0]                        m_ahb_hsize,
    output reg  [2:0]                        m_ahb_hburst,
    output reg  [P_M_AHB_DATA_WIDTH-1:0]     m_ahb_hwdata,
    input  wire                              m_ahb_hready,
    input  wire [P_M_AHB_DATA_WIDTH-1:0]     m_ahb_hrdata,
    input  wire                              m_ahb_hresp
);

    localparam [1:0] C_HTRANS_IDLE   = 2'b00;
    localparam [1:0] C_HTRANS_NONSEQ = 2'b10;
    localparam [1:0] C_HTRANS_SEQ    = 2'b11;

    localparam [2:0] C_HBURST_SINGLE = 3'b000;
    localparam [2:0] C_HBURST_INCR4  = 3'b011;
    localparam [2:0] C_HSIZE_WORD    = 3'b010;

    localparam [P_M_AHB_DATA_WIDTH-1:0] C_TEST_DATA0 = 32'd1;
    localparam [P_M_AHB_DATA_WIDTH-1:0] C_TEST_DATA1 = 32'd2;
    localparam [P_M_AHB_DATA_WIDTH-1:0] C_TEST_DATA2 = 32'd3;
    localparam [P_M_AHB_DATA_WIDTH-1:0] C_TEST_DATA3 = 32'd4;

    localparam [3:0] S_IDLE        = 4'd0;
    localparam [3:0] S_WR_A0       = 4'd1;
    localparam [3:0] S_WR_A1_D0    = 4'd2;
    localparam [3:0] S_WR_A2_D1    = 4'd3;
    localparam [3:0] S_WR_A3_D2    = 4'd4;
    localparam [3:0] S_WR_IDLE_D3  = 4'd5;
    localparam [3:0] S_RD_A0       = 4'd6;
    localparam [3:0] S_RD_A1_D0    = 4'd7;
    localparam [3:0] S_RD_A2_D1    = 4'd8;
    localparam [3:0] S_RD_A3_D2    = 4'd9;
    localparam [3:0] S_RD_IDLE_D3  = 4'd10;

    reg [3:0] r_state;
    reg       r_start_d;
    reg       r_fail;

    wire w_start_posedge = i_start && !r_start_d;

    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            r_state      <= S_IDLE;
            r_start_d    <= 1'b0;
            r_fail       <= 1'b0;
            o_done       <= 1'b0;
            o_led        <= 1'b0;
            m_ahb_hsel   <= 1'b0;
            m_ahb_haddr  <= {P_M_AHB_ADDR_WIDTH{1'b0}};
            m_ahb_htrans <= C_HTRANS_IDLE;
            m_ahb_hwrite <= 1'b0;
            m_ahb_hsize  <= C_HSIZE_WORD;
            m_ahb_hburst <= C_HBURST_SINGLE;
            m_ahb_hwdata <= {P_M_AHB_DATA_WIDTH{1'b0}};
        end else begin
            r_start_d <= i_start;

            case (r_state)
                S_IDLE: begin
                    m_ahb_hsel   <= 1'b0;
                    m_ahb_haddr  <= P_TEST_BASE_ADDR;
                    m_ahb_htrans <= C_HTRANS_IDLE;
                    m_ahb_hwrite <= 1'b0;
                    m_ahb_hsize  <= C_HSIZE_WORD;
                    m_ahb_hburst <= C_HBURST_SINGLE;
                    m_ahb_hwdata <= {P_M_AHB_DATA_WIDTH{1'b0}};

                    if (w_start_posedge) begin
                        r_fail       <= 1'b0;
                        o_done       <= 1'b0;
                        o_led        <= 1'b0;
                        m_ahb_hsel   <= 1'b1;
                        m_ahb_haddr  <= P_TEST_BASE_ADDR;
                        m_ahb_htrans <= C_HTRANS_NONSEQ;
                        m_ahb_hwrite <= 1'b1;
                        m_ahb_hburst <= C_HBURST_INCR4;
                        r_state      <= S_WR_A0;
                    end
                end

                // 当前周期:写突发第 0 个地址相位,尚无有效 HWDATA。
                S_WR_A0: begin
                    if (m_ahb_hready) begin
                        m_ahb_hsel   <= 1'b1;
                        m_ahb_haddr  <= P_TEST_BASE_ADDR + 32'd4;
                        m_ahb_htrans <= C_HTRANS_SEQ;
                        m_ahb_hwrite <= 1'b1;
                        m_ahb_hburst <= C_HBURST_INCR4;
                        m_ahb_hwdata <= C_TEST_DATA0;
                        r_state      <= S_WR_A1_D0;
                    end
                end

                // 当前周期:写地址 0x4,同时给上一拍地址 0x0 的写数据 1。
                S_WR_A1_D0: begin
                    if (m_ahb_hready) begin
                        r_fail       <= r_fail | m_ahb_hresp;
                        m_ahb_haddr  <= P_TEST_BASE_ADDR + 32'd8;
                        m_ahb_htrans <= C_HTRANS_SEQ;
                        m_ahb_hwdata <= C_TEST_DATA1;
                        r_state      <= S_WR_A2_D1;
                    end
                end

                // 当前周期:写地址 0x8,同时给上一拍地址 0x4 的写数据 2。
                S_WR_A2_D1: begin
                    if (m_ahb_hready) begin
                        r_fail       <= r_fail | m_ahb_hresp;
                        m_ahb_haddr  <= P_TEST_BASE_ADDR + 32'd12;
                        m_ahb_htrans <= C_HTRANS_SEQ;
                        m_ahb_hwdata <= C_TEST_DATA2;
                        r_state      <= S_WR_A3_D2;
                    end
                end

                // 当前周期:写地址 0xC,同时给上一拍地址 0x8 的写数据 3。
                S_WR_A3_D2: begin
                    if (m_ahb_hready) begin
                        r_fail       <= r_fail | m_ahb_hresp;
                        m_ahb_hsel   <= 1'b0;
                        m_ahb_htrans <= C_HTRANS_IDLE;
                        m_ahb_hwrite <= 1'b0;
                        m_ahb_hburst <= C_HBURST_SINGLE;
                        m_ahb_hwdata <= C_TEST_DATA3;
                        r_state      <= S_WR_IDLE_D3;
                    end
                end

                // 当前周期:总线地址相位空闲,同时完成上一拍地址 0xC 的写数据 4。
                S_WR_IDLE_D3: begin
                    if (m_ahb_hready) begin
                        r_fail       <= r_fail | m_ahb_hresp;
                        m_ahb_hsel   <= 1'b1;
                        m_ahb_haddr  <= P_TEST_BASE_ADDR;
                        m_ahb_htrans <= C_HTRANS_NONSEQ;
                        m_ahb_hwrite <= 1'b0;
                        m_ahb_hburst <= C_HBURST_INCR4;
                        m_ahb_hwdata <= {P_M_AHB_DATA_WIDTH{1'b0}};
                        r_state      <= S_RD_A0;
                    end
                end

                // 当前周期:读突发第 0 个地址相位,尚无有效 HRDATA。
                S_RD_A0: begin
                    if (m_ahb_hready) begin
                        m_ahb_haddr  <= P_TEST_BASE_ADDR + 32'd4;
                        m_ahb_htrans <= C_HTRANS_SEQ;
                        r_state      <= S_RD_A1_D0;
                    end
                end

                // 当前周期:读地址 0x4,同时采样上一拍地址 0x0 的读数据。
                S_RD_A1_D0: begin
                    if (m_ahb_hready) begin
                        r_fail       <= r_fail | m_ahb_hresp | (m_ahb_hrdata != C_TEST_DATA0);
                        m_ahb_haddr  <= P_TEST_BASE_ADDR + 32'd8;
                        m_ahb_htrans <= C_HTRANS_SEQ;
                        r_state      <= S_RD_A2_D1;
                    end
                end

                // 当前周期:读地址 0x8,同时采样上一拍地址 0x4 的读数据。
                S_RD_A2_D1: begin
                    if (m_ahb_hready) begin
                        r_fail       <= r_fail | m_ahb_hresp | (m_ahb_hrdata != C_TEST_DATA1);
                        m_ahb_haddr  <= P_TEST_BASE_ADDR + 32'd12;
                        m_ahb_htrans <= C_HTRANS_SEQ;
                        r_state      <= S_RD_A3_D2;
                    end
                end

                // 当前周期:读地址 0xC,同时采样上一拍地址 0x8 的读数据。
                S_RD_A3_D2: begin
                    if (m_ahb_hready) begin
                        r_fail       <= r_fail | m_ahb_hresp | (m_ahb_hrdata != C_TEST_DATA2);
                        m_ahb_hsel   <= 1'b0;
                        m_ahb_htrans <= C_HTRANS_IDLE;
                        m_ahb_hburst <= C_HBURST_SINGLE;
                        r_state      <= S_RD_IDLE_D3;
                    end
                end

                // 当前周期:总线地址相位空闲,同时采样上一拍地址 0xC 的读数据。
                S_RD_IDLE_D3: begin
                    if (m_ahb_hready) begin
                        o_done       <= 1'b1;
                        o_led        <= !(r_fail | m_ahb_hresp | (m_ahb_hrdata != C_TEST_DATA3));
                        m_ahb_hsel   <= 1'b0;
                        m_ahb_haddr  <= P_TEST_BASE_ADDR;
                        m_ahb_htrans <= C_HTRANS_IDLE;
                        m_ahb_hwrite <= 1'b0;
                        m_ahb_hburst <= C_HBURST_SINGLE;
                        m_ahb_hwdata <= {P_M_AHB_DATA_WIDTH{1'b0}};
                        r_state      <= S_IDLE;
                    end
                end

                default: begin
                    r_state      <= S_IDLE;
                    r_fail       <= 1'b1;
                    o_done       <= 1'b1;
                    o_led        <= 1'b0;
                    m_ahb_hsel   <= 1'b0;
                    m_ahb_haddr  <= P_TEST_BASE_ADDR;
                    m_ahb_htrans <= C_HTRANS_IDLE;
                    m_ahb_hwrite <= 1'b0;
                    m_ahb_hsize  <= C_HSIZE_WORD;
                    m_ahb_hburst <= C_HBURST_SINGLE;
                    m_ahb_hwdata <= {P_M_AHB_DATA_WIDTH{1'b0}};
                end
            endcase
        end
    end

endmodule

tb_ahblite_master.v

同样是读写 slot0.v的4个寄存器

`timescale 1ns/1ps

module tb;

    reg         hclk;
    reg         hresetn;
    reg         start;
    wire        done;
    wire        led;

    wire        hsel;
    wire [31:0] haddr;
    wire [1:0]  htrans;
    wire        hwrite;
    wire [2:0]  hsize;
    wire [2:0]  hburst;
    wire [31:0] hwdata;
    wire        hready;
    wire [31:0] hrdata;
    wire        hresp;

    wire        reg_wr_en;
    wire [6:0]  reg_wr_addr;
    wire [31:0] reg_wr_data;
    wire [6:0]  reg_rd_addr;
    wire [31:0] reg_rd_data;

    ahblite_master #(
        .P_M_AHB_ADDR_WIDTH(32),
        .P_M_AHB_DATA_WIDTH(32),
        .P_TEST_BASE_ADDR(32'h0000_0000)
    ) u_master (
        .i_clk       (hclk),
        .i_rst_n     (hresetn),
        .i_start     (start),
        .o_done      (done),
        .o_led       (led),
        .m_ahb_hsel  (hsel),
        .m_ahb_haddr (haddr),
        .m_ahb_htrans(htrans),
        .m_ahb_hwrite(hwrite),
        .m_ahb_hsize (hsize),
        .m_ahb_hburst(hburst),
        .m_ahb_hwdata(hwdata),
        .m_ahb_hready(hready),
        .m_ahb_hrdata(hrdata),
        .m_ahb_hresp (hresp)
    );

    ahblite_slave_smmr_bridge #(
        .P_S_AHB_ADDR_WIDTH(32),
        .P_S_AHB_DATA_WIDTH(32),
        .P_S_MEM_SIZE(16),
        .P_REG_ADDR_WIDTH(7)
    ) u_bridge (
        .s_ahb_hclk     (hclk),
        .s_ahb_hresetn  (hresetn),
        .s_ahb_hsel     (hsel),
        .s_ahb_haddr    (haddr),
        .s_ahb_htrans   (htrans),
        .s_ahb_hwrite   (hwrite),
        .s_ahb_hsize    (hsize),
        .s_ahb_hburst   (hburst),
        .s_ahb_hwdata   (hwdata),
        .s_ahb_hready   (hready),
        .s_ahb_hreadyout(hready),
        .s_ahb_hrdata   (hrdata),
        .s_ahb_hresp    (hresp),
        .o_reg_wr_en    (reg_wr_en),
        .o_reg_wr_addr  (reg_wr_addr),
        .o_reg_wr_data  (reg_wr_data),
        .o_reg_rd_addr  (reg_rd_addr),
        .i_reg_rd_data  (reg_rd_data)
    );

    slot0 #(
        .P_ADDR_WIDTH(7),
        .P_DATA_WIDTH(32),
        .P_SLOT_BASE(0),
        .P_REG_COUNT(4)
    ) u_slot0 (
        .i_clk        (hclk),
        .i_rst_n      (hresetn),
        .i_reg_wr_en  (reg_wr_en),
        .i_reg_wr_addr(reg_wr_addr),
        .i_reg_wr_data(reg_wr_data),
        .i_reg_rd_addr(reg_rd_addr),
        .o_reg_rd_data(reg_rd_data)
    );

    always #5 hclk = ~hclk;

    integer timeout_cnt;

    initial begin
        hclk    = 1'b0;
        hresetn = 1'b0;
        start   = 1'b0;

        repeat (5) @(negedge hclk);
        hresetn = 1'b1;

        repeat (2) @(negedge hclk);
        start = 1'b1;
        @(negedge hclk);
        start = 1'b0;

        timeout_cnt = 0;
        while (!done && timeout_cnt < 100) begin
            @(negedge hclk);
            timeout_cnt = timeout_cnt + 1;
        end

        if (!done) begin
            $display("FAIL timeout, master did not assert done");
            $finish;
        end

        if (led) begin
            $display("PASS master burst write/read test, led=%0d done=%0d", led, done);
        end else begin
            $display("FAIL master burst write/read test, led=%0d done=%0d", led, done);
            $finish;
        end

        repeat (5) @(negedge hclk);
        $finish;
    end

endmodule

`

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值