Verilog实现AMBA--AHB To APB Bridge
我的学记|刘航宇的博客

Verilog实现AMBA--AHB To APB Bridge

刘航宇
2年前发布 /正在检测是否收录...
温馨提示:
本文最后更新于2023年07月10日,已超过625天没有更新,若内容或图片失效,请留言反馈。

1、APB桥

APB桥是AMBA APB总线上的唯一主机,也是AMBA AHB的一个从机。下图表示了APB桥接口信号:
图片[1] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客
APB Bridge将AHB传输转成APB传输并实现一下功能:
(1)对锁存的地址进行译码并产生选择信号PSELx,在传输过程中只有一个选择信号可以被激活。 也就是选择出唯一 一个APB从设备以进行读写动作。
(2)写操作时:负责将AHB送来的数据送上APB总线。
(3)读操作时:负责将APB的数据送上AHB系统总线。
(4)产生一时序选通信号PENABLE来作为数据传递时的启动信号

2、读传输

下图表示了APB到AHB的读传输:
图片[2] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客
传输开始于AHB总线上的T1时刻,在T2时刻地址信息被APB采样,如果传输目标是外设总线,那么这个地址就会被译码成选择信号发给外设,T2即为APB总线的SETUP周期,T3为APB的ENABLE周期,PENABLE信号拉高。在该周期内,外设必须提供读数据,通常情况下,读数据直接AHB读数据总线上,总线主机在T4时刻对读数据进行采样。
在频率很高的情况下,在ENABLE CYCLE中可能数据不能够直接映射到AHB总线,需要在APB桥中在T4的时候打插入一个额外的等待周期,并在T5的时候才被AHB主采样。虽然需要多一个等待周期(一共2个),但是由于频率提升了因此总的性能也提升了。
下图表示了一个读突发传输,所有的传输都只有一个等待周期
图片[3] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客

3、写传输

下图表示了一个写传输:
图片[4] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客
APB总线上的单块数据写操作不需要等待周期。 APB桥负责对地址和数据进行采样,并在写操作的过程中保持它们的值。

下图表示了一个写突发传输:
图片[5] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客
虽然第一个传输可以零等待完成,但后续每一个传输都需要插入一个等待状态。

APB桥需要两个地址寄存器,以便在当前传输进行时,锁存下一次传输的地址。

4、背靠背传输

下图表示了一个背靠背传输,顺序为写、读、写、读:
如果写操作之后跟随着读操作,那么需要 3 个等待周期来完成这个读操作。通常的情况下,不会有读操作之后紧跟着写操作的发生,因为两者之间 CPU 会进行指令读取,并且指令存储器也不太可能挂在在APB总线上。
图片[6] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客
下面以ARM DesignStart项目提供的软件包里的AHB转APB桥的代码,对其进行学习与仿真,以深入理解APB桥的实现方法,该转换桥比较简单,实现的是一对一的转换,也可以配合APB slave multiplexer模块,实现一对多的方式(主要依靠APB高位地址译码得到各个从机的PSEL信号)。如果想学习APB系统总线,可以参考Synopsys公司的DW_APB IP,该IP最多可支持16个APB从机,并支持所有的突发传输类型。

5、AHB_to_APB Bridge的Verilog实现

`timescale 1ns / 1ps

module ahb_to_apb #(
    // Parameter to define address width
    // 16 = 2^16 = 64KB APB address space
    parameter                   ADDRWIDTH = 16,
    parameter                   REGISTER_RDATA = 1,
    parameter                   REGISTER_WDATA = 0
)
(
    //----------------------------------
    // IO Declarations
    //----------------------------------
    input  wire                 HCLK, // Clock
    input  wire                 HRESETn, // Reset
    input  wire                 PCLKEN, // APB clock enable signal

    input  wire                 HSEL, // Device select
    input  wire [ADDRWIDTH-1:0] HADDR, // Address
    input  wire [1:0]           HTRANS, // Transfer control
    input  wire [2:0]           HSIZE, // Transfer size
    input  wire [3:0]           HPROT, // Protection control
    input  wire                 HWRITE, // Write control
    input  wire                 HREADY, // Transfer phase done
    input  wire [31:0]          HWDATA, // Write data

    output reg                  HREADYOUT, // Device ready
    output wire [31:0]          HRDATA, // Read data output
    output wire                 HRESP, // Device response
    // APB Output
    output wire [ADDRWIDTH-1:0] PADDR, // APB Address
    output wire                 PENABLE, // APB Enable
    output wire                 PWRITE, // APB Write
    output wire [3:0]           PSTRB, // APB Byte Strobe
    output wire [2:0]           PPROT, // APB Prot
    output wire [31:0]          PWDATA, // APB write data
    output wire                 PSEL, // APB Select

    output wire                 APBACTIVE, // APB bus is active, for clock gating of APB bus
    // APB Input
    input  wire [31:0]          PRDATA, // Read data for each APB slave
    input  wire                 PREADY, // Ready for each APB slave
    input  wire                 PSLVERR // Error state for each APB slave
);  

    //----------------------------------
    // Variable Declarations
    //----------------------------------
    reg  [ADDRWIDTH-3:0]        addr_reg; // Address sample register
    reg                         wr_reg; // Write control sample register
    reg  [2:0]                  state_reg; // State for finite state machine

    reg  [3:0]                  pstrb_reg; // Byte lane strobe register
    wire [3:0]                  pstrb_nxt; // Byte lane strobe next state
    reg  [1:0]                  pprot_reg; // PPROT register
    wire [1:0]                  pprot_nxt; // PPROT register next state

    wire                        apb_select; // APB bridge is selected
    wire                        apb_tran_end; // Transfer is completed on APB
    reg  [2:0]                  next_state; // Next state for finite state machine
    reg  [31:0]                 rwdata_reg; // Read/Write data sample register

    wire                        reg_rdata_cfg; // REGISTER_RDATA paramater
    wire                        reg_wdata_cfg; // REGISTER_WDATA paramater

    reg                         sample_wdata_reg; // Control signal to sample HWDATA

    //----------------------------------
    // Local Parameter Declarations
    //----------------------------------
    // State machine
    localparam                  ST_BITS = 3;

    localparam [ST_BITS-1:0]    ST_IDLE      = 3'b000; // Idle waiting for transaction
    localparam [ST_BITS-1:0]    ST_APB_WAIT  = 3'b001; // Wait APB transfer
    localparam [ST_BITS-1:0]    ST_APB_TRNF  = 3'b010; // Start APB transfer
    localparam [ST_BITS-1:0]    ST_APB_TRNF2 = 3'b011; // Second APB transfer cycle
    localparam [ST_BITS-1:0]    ST_APB_ENDOK = 3'b100; // Ending cycle for OKAY
    localparam [ST_BITS-1:0]    ST_APB_ERR1  = 3'b101; // First cycle for Error response
    localparam [ST_BITS-1:0]    ST_APB_ERR2  = 3'b110; // Second cycle for Error response
    localparam [ST_BITS-1:0]    ST_ILLEGAL   = 3'b111; // Illegal state

    //----------------------------------
    // Start of Main Code
    //----------------------------------
    // Configuration signal
    assign reg_rdata_cfg = (REGISTER_RDATA == 0) ? 1'b0 : 1'b1;
    assign reg_wdata_cfg = (REGISTER_WDATA == 0) ? 1'b0 : 1'b1;

    // Generate APB bridge select
    assign apb_select = HSEL & HTRANS[1] & HREADY;
    // Generate APB transfer ended
    assign apb_tran_end = (state_reg == 3'b011) & PREADY;

    assign pprot_nxt[0] =  HPROT[1]; // (0) Normal, (1) Privileged
    assign pprot_nxt[1] = ~HPROT[0]; // (0) Data, (1) Instruction

    // Byte strobe generation
    // - Only enable for write operations
    // - For word write transfers (HSIZE[1]=1), all byte strobes are 1
    // - For hword write transfers (HSIZE[0]=1), check HADDR[1]
    // - For byte write transfers, check HADDR[1:0]
    assign pstrb_nxt[0] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b00));
    assign pstrb_nxt[1] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b01));
    assign pstrb_nxt[2] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b10));
    assign pstrb_nxt[3] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b11));

    // Sample control signals
    always @(posedge HCLK or negedge HRESETn)
    begin
        if (~HRESETn) begin
            addr_reg  <= {(ADDRWIDTH-2){1'b0}};
            wr_reg    <= 1'b0;
            pprot_reg <= {2{1'b0}};
            pstrb_reg <= {4{1'b0}};
        end
        else if (apb_select) begin // Capture transfer information at the end of AHB address phase
            addr_reg  <= HADDR[ADDRWIDTH-1:2];
            wr_reg    <= HWRITE;
            pprot_reg <= pprot_nxt;
            pstrb_reg <= pstrb_nxt;
        end
    end

    // Sample write data control signal
    // Assert after write address phase, deassert after PCLKEN=1
    wire sample_wdata_set = apb_select & HWRITE & reg_wdata_cfg;
    wire sample_wdata_clr = sample_wdata_reg & PCLKEN;

    always @(posedge HCLK or negedge HRESETn)
    begin
        if (~HRESETn)
            sample_wdata_reg <= 1'b0;
        else if (sample_wdata_set | sample_wdata_clr)
            sample_wdata_reg <= sample_wdata_set;
    end

    // Generate next state for FSM
    // Note : case 3'b111 is not used.  The design has been checked that
    //        this illegal state cannot be entered using formal verification.
    always @(state_reg or PREADY or PSLVERR or apb_select or reg_rdata_cfg or
            PCLKEN or reg_wdata_cfg or HWRITE)
    begin
        case (state_reg)
            // Idle
            ST_IDLE : begin
                if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
                    next_state = ST_APB_TRNF; // Start APB transfer in next cycle
                else if (apb_select)
                    next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
                else
                    next_state = ST_IDLE; // Remain idle
            end
            // Transfer announced on AHB, but PCLKEN was low, so waiting
            ST_APB_WAIT : begin
                if (PCLKEN)
                    next_state = ST_APB_TRNF; // Start APB transfer in next cycle
                else
                    next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
            end
            // First APB transfer cycle
            ST_APB_TRNF : begin
                if (PCLKEN)
                    next_state = ST_APB_TRNF2;   // Change to second cycle of APB transfer
                else
                    next_state = ST_APB_TRNF;   // Change to state-2
            end
            // Second APB transfer cycle
            ST_APB_TRNF2 : begin
                if (PREADY & PSLVERR & PCLKEN) // Error received - Generate two cycle
                    // Error response on AHB by
                    next_state = ST_APB_ERR1; // Changing to state-5 and 6
                else if (PREADY & (~PSLVERR) & PCLKEN) begin // Okay received
                    if (reg_rdata_cfg)
                        // Registered version
                        next_state = ST_APB_ENDOK; // Generate okay response in state 4
                    else
                        // Non-registered version
                        next_state = {2'b00, apb_select}; // Terminate transfer
                end
                else // Slave not ready
                    next_state = ST_APB_TRNF2; // Unchange
            end
            // Ending cycle for OKAY (registered response)
            ST_APB_ENDOK : begin
                if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
                    next_state = ST_APB_TRNF; // Start APB transfer in next cycle
                else if (apb_select)
                    next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
                else
                    next_state = ST_IDLE; // Remain idle
            end
            // First cycle for Error response
            ST_APB_ERR1 : 
                next_state = ST_APB_ERR2; // Goto 2nd cycle of error response
            // Second cycle for Error response
            ST_APB_ERR2 : begin
                if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
                    next_state = ST_APB_TRNF; // Start APB transfer in next cycle
                else if (apb_select)
                    next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
                else
                    next_state = ST_IDLE; // Remain idle
            end
            default : // Not used
                next_state = 3'bxxx; // X-Propagation
        endcase
    end

    // Registering state machine
    always @(posedge HCLK or negedge HRESETn)
    begin
        if (~HRESETn)
            state_reg <= 3'b000;
        else
            state_reg <= next_state;
    end

    // Sample PRDATA or HWDATA
    always @(posedge HCLK or negedge HRESETn)
    begin
        if (~HRESETn)
            rwdata_reg <= {32{1'b0}};
        else
            if (sample_wdata_reg & reg_wdata_cfg & PCLKEN)
                rwdata_reg <= HWDATA;
            else if (apb_tran_end & reg_rdata_cfg & PCLKEN)
                rwdata_reg <= PRDATA;
    end

    // Connect outputs to top level
    assign PADDR   = {addr_reg, 2'b00}; // from sample register
    assign PWRITE  = wr_reg;            // from sample register
    // From sample register or from HWDATA directly
    assign PWDATA  = (reg_wdata_cfg) ? rwdata_reg : HWDATA;
    assign PSEL    = (state_reg == ST_APB_TRNF) | (state_reg == ST_APB_TRNF2);
    assign PENABLE = (state_reg == ST_APB_TRNF2);
    assign PPROT   = {pprot_reg[1], 1'b0, pprot_reg[0]};
    assign PSTRB   = pstrb_reg[3:0];

    // Generate HREADYOUT
    always @(state_reg or reg_rdata_cfg or PREADY or PSLVERR or PCLKEN)
    begin
        case (state_reg)
            ST_IDLE      : HREADYOUT = 1'b1; // Idle
            ST_APB_WAIT  : HREADYOUT = 1'b0; // Transfer announced on AHB, but PCLKEN was low, so waiting
            ST_APB_TRNF  : HREADYOUT = 1'b0; // First APB transfer cycle
            // Second APB transfer cycle:
            // if Non-registered feedback version, and APB transfer completed without error
            // Then response with ready immediately. If registered feedback version,
            // wait until state_reg == ST_APB_ENDOK
            ST_APB_TRNF2 : HREADYOUT = (~reg_rdata_cfg) & PREADY & (~PSLVERR) & PCLKEN;
            ST_APB_ENDOK : HREADYOUT = reg_rdata_cfg; // Ending cycle for OKAY (registered response only)
            ST_APB_ERR1  : HREADYOUT = 1'b0; // First cycle for Error response
            ST_APB_ERR2  : HREADYOUT = 1'b1; // Second cycle for Error response
            default      : HREADYOUT = 1'bx; // x propagation (note :3'b111 is illegal state)
        endcase
    end

    // From sample register or from PRDATA directly
    assign HRDATA = (reg_rdata_cfg) ? rwdata_reg : PRDATA;
    assign HRESP  = (state_reg == ST_APB_ERR1) | (state_reg == ST_APB_ERR2);

    assign APBACTIVE = (HSEL & HTRANS[1]) | (|state_reg);

endmodule

使用的测试用例和AHB从机的测试用例基本一样,首先是顶层:

`timescale 1ns / 1ps

module top_tb();
    //----------------------------------
    // Local Parameter Declarations
    //----------------------------------
    localparam                      AHB_CLK_PERIOD = 5; // Assuming AHB CLK to be 100MHz

    localparam                      SIZE_IN_BYTES = 2048;
    localparam                      ADDRWIDTH = 32;

    //----------------------------------
    // Variable Declarations
    //----------------------------------
    reg                             HCLK = 0;
    wire                            HWRITE;
    wire [1:0]                      HTRANS;
    wire [2:0]                      HSIZE;
    wire [2:0]                      HBURST;
    wire                            HREADYIN;
    wire [31:0]                     HADDR;
    wire [3:0]                      HPROT;
    wire [31:0]                     HWDATA;
    wire                            HREADYOUT;
    wire [1:0]                      HRESP;
    wire [31:0]                     HRDATA;
    reg                             HRESETn;
    wire                            HREADY;

    wire [ADDRWIDTH-1:0]            PADDR; // APB Address
    wire                            PENABLE; // APB Enable
    wire                            PWRITE; // APB Write
    wire [31:0]                     PWDATA; // APB write data
    wire                            PSEL; // APB Select
    wire                            PREADY;
    wire                            PSLVERR;         
    wire [2:0]                      PPROT;
    wire [3:0]                      PSTRB;
    wire [31:0]                     PRDATA;

    //----------------------------------
    // Start of Main Code
    //----------------------------------
    assign HREADY = HREADYOUT;

    //-----------------------------------------------------------------------
    // Generate HCLK
    //-----------------------------------------------------------------------
    always #AHB_CLK_PERIOD
        HCLK <= ~HCLK;
    
    //-----------------------------------------------------------------------
    // Generate HRESETn
    //-----------------------------------------------------------------------    
    initial begin
        HRESETn = 1'b0;
        repeat(5) @(posedge HCLK);
        HRESETn = 1'b1;
    end

    ahb_master #(
        .START_ADDR                 (32'h0),
        .DEPTH_IN_BYTES             (SIZE_IN_BYTES)
    )       
    u_ahb_master (     
        .HRESETn                    (HRESETn),
        .HCLK                       (HCLK),
        .HADDR                      (HADDR),
        .HPROT                      (HPROT),
        .HTRANS                     (HTRANS),
        .HWRITE                     (HWRITE),
        .HSIZE                      (HSIZE),
        .HBURST                     (HBURST),
        .HWDATA                     (HWDATA),
        .HRDATA                     (HRDATA),
        .HRESP                      (HRESP),
        .HREADY                     (HREADYOUT)
    );

    ahb_to_apb #(
        .ADDRWIDTH                  (ADDRWIDTH),
        .REGISTER_RDATA             (0),
        .REGISTER_WDATA             (0)
    )
    u_ahb_to_apb(
        .HCLK                       (HCLK),
        .HRESETn                    (HRESETn),
        .PCLKEN                     (1'b1),
        .HSEL                       (1'b1),
        .HADDR                      (HADDR),
        .HTRANS                     (HTRANS),
        .HSIZE                      (HSIZE),
        .HPROT                      (HPROT),
        .HWRITE                     (HWRITE),
        .HREADY                     (HREADY),
        .HWDATA                     (HWDATA),
        .HREADYOUT                  (HREADYOUT),
        .HRDATA                     (HRDATA),
        .HRESP                      (HRESP),
        .PADDR                      (PADDR),
        .PENABLE                    (PENABLE),
        .PWRITE                     (PWRITE),
        .PREADY                     (PREADY),
        .PSLVERR                    (PSLVERR),
        .PSTRB                      (PSTRB),
        .PPROT                      (PPROT),
        .PWDATA                     (PWDATA),
        .PSEL                       (PSEL),
        .APBACTIVE                  (APBACTIVE),
        .PRDATA                     (PRDATA)
    );

    apb_mem #(
        .P_SLV_ID                   (0),
        .ADDRWIDTH                  (ADDRWIDTH),
        .P_SIZE_IN_BYTES            (SIZE_IN_BYTES),
        .P_DELAY                    (0)
    )
    u_apb_mem (
    `ifdef AMBA_APB3
        .PREADY                     (PREADY),
        .PSLVERR                    (PSLVERR),
    `endif
    `ifdef AMBA_APB4
        .PSTRB                      (PSTRB),
        .PPROT                      (PPROT),
    `endif
        .PRESETn                    (HRESETn),
        .PCLK                       (HCLK),
        .PSEL                       (PSEL),
        .PENABLE                    (PENABLE),
        .PADDR                      (PADDR),
        .PWRITE                     (PWRITE),
        .PRDATA                     (PRDATA),
        .PWDATA                     (PWDATA)
    );
    
`ifdef VCS
    initial begin
        $fsdbDumpfile("top_tb.fsdb");
        $fsdbDumpvars;
    end

    initial begin
    `ifdef DUMP_VPD
        $vcdpluson();
    `endif
    end
`endif

endmodule

然后是AHB master的功能模型:

`timescale 1ns / 1ps

`define SINGLE_TEST
`define BURST_TEST

module ahb_master #( 
    //----------------------------------
    // Paramter Declarations
    //----------------------------------
    parameter                           START_ADDR = 0,
    parameter                           DEPTH_IN_BYTES = 32'h100,
    parameter                           END_ADDR = START_ADDR+DEPTH_IN_BYTES-1
)
(
    //----------------------------------
    // IO Declarations
    //----------------------------------
    input wire                          HRESETn,        
    input wire                          HCLK,
    output reg [31:0]                   HADDR,
    output reg [1:0]                    HTRANS,
    output reg                          HWRITE,
    output reg [2:0]                    HSIZE,
    output reg [2:0]                    HBURST,
    output reg [3:0]                HPROT,
    output reg [31:0]                   HWDATA,
    input wire [31:0]                   HRDATA,
    input wire [1:0]                    HRESP,
    input wire                          HREADY
);

    //----------------------------------
    // Variable Declarations
    //----------------------------------
    reg [31:0]                          data_burst[0:1023];

    //----------------------------------
    // Start of Main Code
    //----------------------------------
    initial begin
        HADDR = 0;
        HTRANS = 0;
        HPROT = 0;
        HWRITE = 0;
        HSIZE = 0;
        HBURST = 0;
        HWDATA = 0;
        while(HRESETn === 1'bx) @(posedge HCLK);
        while(HRESETn === 1'b1) @(posedge HCLK);
        while(HRESETn === 1'b0) @(posedge HCLK);
    `ifdef SINGLE_TEST
        repeat(3) @(posedge HCLK);
        memory_test(START_ADDR, END_ADDR, 1);
        memory_test(START_ADDR, END_ADDR, 2);
        memory_test(START_ADDR, END_ADDR, 4);
    `endif
    `ifdef BURST_TEST
        repeat(5) @(posedge HCLK);
        memory_test_burst(START_ADDR, END_ADDR, 1);
        memory_test_burst(START_ADDR, END_ADDR, 2);
        memory_test_burst(START_ADDR, END_ADDR, 4);
        memory_test_burst(START_ADDR, END_ADDR, 6);
        memory_test_burst(START_ADDR, END_ADDR, 8);
        memory_test_burst(START_ADDR, END_ADDR, 10);
        memory_test_burst(START_ADDR, END_ADDR, 16);
        memory_test_burst(START_ADDR, END_ADDR, 32);
        memory_test_burst(START_ADDR, END_ADDR, 64);
        memory_test_burst(START_ADDR, END_ADDR, 128);
        memory_test_burst(START_ADDR, END_ADDR, 255);
        repeat(5) @(posedge HCLK);
    `endif
        $finish(2);
    end
    
    //-----------------------------------------------------------------------
    // Single transfer test 
    //-----------------------------------------------------------------------
    task memory_test;
        input [31:0]                start; // start address
        input [31:0]                finish; // end address
        input [2:0]                 size; // data size: 1, 2, 4
        
        integer                     i; 
        integer                     error;
        reg [31:0]                  data; 
        reg [31:0]                  gen; 
        reg [31:0]                  got;
        reg [31:0]                  reposit[START_ADDR:END_ADDR];
    begin
        $display("%m: read-after-write test with %d-byte access", size);
        error = 0;
        gen = $random(7);
        for (i = start; i < (finish-size+1); i = i + size) begin
            gen = $random & ~32'b0;
            data = align(i, gen, size);
            ahb_write(i, size, data);
            ahb_read(i, size, got);
            got = align(i, got, size);
            if (got !== data) begin
                $display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data);
                error = error + 1;
            end
        end
        if (error == 0)
            $display("[%10d] %m OK: from %x to %x", $time, start, finish);

        $display("%m read-all-after-write-all with %d-byte access", size);
        error = 0;
        gen = $random(1);
        for (i = start; i < (finish-size+1); i = i + size) begin
            gen = {$random} & ~32'b0;
            data = align(i, gen, size);
            reposit[i] = data;
            ahb_write(i, size, data);
        end
        for (i = start; i < (finish-size+1); i = i + size) begin
            data = reposit[i];
            ahb_read(i, size, got);
            got = align(i, got, size);
            if (got !== data) begin
                $display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data);
                error = error + 1;
            end
        end
        if (error == 0)
            $display("[%10d] %m OK: from %x to %x", $time, start, finish);
    end
    endtask
    
    //-----------------------------------------------------------------------
    // Burst transfer test 
    //-----------------------------------------------------------------------
    task memory_test_burst;
        input [31:0]                start; // start address
        input [31:0]                finish; // end address
        input [7:0]                 leng; // burst length
        
        integer                     i; 
        integer                     j; 
        integer                     k; 
        integer                     r; 
        integer                     error;
        reg [31:0]                  data; 
        reg [31:0]                  gen; 
        reg [31:0]                  got;
        reg [31:0]                  reposit[0:1023];
        integer                     seed;
    begin
        $display("[%10d] %m: read-all-after-write-all burst test with %d-beat access", $time, leng);
        error = 0;
        seed  = 111;
        gen = $random(seed);
        k = 0;
        if (finish > (start+leng*4)) begin
            for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin
                for (j = 0; j < leng; j = j + 1) begin
                    data_burst[j] = $random;
                    reposit[j+k*leng] = data_burst[j];
                end
                @(posedge HCLK);
                ahb_write_burst(i, leng);
                k = k + 1;
            end
            gen = $random(seed);
            k = 0;
            for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin
                @(posedge HCLK);
                ahb_read_burst(i, leng);
                for (j = 0; j < leng; j = j + 1) begin
                    if (data_burst[j] != reposit[j+k*leng]) begin
                        error = error+1;
                        $display("[%10d] %m A=%hh D=%hh, but %hh expected", $time, i+j*leng, data_burst[j], reposit[j+k*leng]);
                    end
                end
                k = k + 1;
                r = $random & 8'h0F;
                repeat(r) @(posedge HCLK);
            end
            if (error == 0)
                $display("%m %d-length burst read-after-write OK: from %hh to %hh",leng, start, finish);
        end 
        else begin
            $display("%m %d-length burst read-after-write from %hh to %hh ???",leng, start, finish);
        end
    end
    endtask
    
    //-----------------------------------------------------------------------
    // As AMBA AHB bus uses non-justified data bus scheme, data should be
    // aligned according to the address.
    //-----------------------------------------------------------------------
    function [31:0] align;
        input [ 1:0]                addr;
        input [31:0]                data;
        input [ 2:0]                size; // num of bytes
    begin
    `ifdef BIG_ENDIAN
        case (size)
            1 : 
                case (addr[1:0])
                    0 : align = data & 32'hFF00_0000;
                    1 : align = data & 32'h00FF_0000;
                    2 : align = data & 32'h0000_FF00;
                    3 : align = data & 32'h0000_00FF;
                endcase                
                                       
            2 :                        
                case (addr[1])         
                    0 : align = data & 32'hFFFF_0000;
                    1 : align = data & 32'h0000_FFFF;
                endcase
                
            4 : 
                align = data&32'hFFFF_FFFF;
            default : 
                $display($time,,"%m ERROR %d-byte not supported for size", size);
        endcase
    `else
        case (size)
            1 : 
                case (addr[1:0])
                    0 : align = data & 32'h0000_00FF;
                    1 : align = data & 32'h0000_FF00;
                    2 : align = data & 32'h00FF_0000;
                    3 : align = data & 32'hFF00_0000;
                endcase
                
            2 : 
                case (addr[1])
                    0 : align = data & 32'h0000_FFFF;
                    1 : align = data & 32'hFFFF_0000;
                endcase
                
            4 : 
                align = data&32'hFFFF_FFFF;
            default : 
                $display($time,,"%m ERROR %d-byte not supported for size", size);
        endcase
    `endif
    end
    endfunction

    `include "ahb_transaction_tasks.v"

endmodule

ahb_transaction_tasks.v文件如下:

`ifndef __AHB_TRANSACTION_TASKS_V__
`define __AHB_TRANSACTION_TASKS_V__

//-----------------------------------------------------------------------
// AHB Read Task
//-----------------------------------------------------------------------
task ahb_read;
    input [31:0]                address;
    input [2:0]                 size;
    output [31:0]               data;
begin
    @(posedge HCLK);
    HADDR <= #1 address;
    HPROT <= #1 4'b0001; // DATA
    HTRANS <= #1 2'b10; // NONSEQ;
    HBURST <= #1 3'b000; // SINGLE;
    HWRITE <= #1 1'b0; // READ;
    case (size)
        1 : HSIZE <= #1 3'b000; // BYTE;
        2 : HSIZE <= #1 3'b001; // HWORD;
        4 : HSIZE <= #1 3'b010; // WORD;
        default : 
            $display($time,, "ERROR: unsupported transfer size: %d-byte", size);
    endcase
    
    @(posedge HCLK);
    while (HREADY !== 1'b1) @(posedge HCLK);
    HTRANS <= #1 2'b0; // IDLE
    @(posedge HCLK);
    while (HREADY === 0) @(posedge HCLK);
    data = HRDATA; // must be blocking
    if (HRESP != 2'b00) 
        $display($time,, "ERROR: non OK response for read");
    @(posedge HCLK);
end
endtask

//-----------------------------------------------------------------------
// AHB Write Task
//-----------------------------------------------------------------------
task ahb_write;
    input [31:0]                address;
    input [2:0]                 size;
    input [31:0]                data;
begin
    @(posedge HCLK);
    HADDR <= #1 address;
    HPROT <= #1 4'b0001; // DATA
    HTRANS <= #1 2'b10; // NONSEQ
    HBURST <= #1 3'b000; // SINGLE
    HWRITE <= #1 1'b1; // WRITE
    case (size)
        1 : HSIZE <= #1 3'b000; // BYTE
        2 : HSIZE <= #1 3'b001; // HWORD
        4 : HSIZE <= #1 3'b010; // WORD
        default : 
            $display($time,, "ERROR: unsupported transfer size: %d-byte", size);
    endcase
    
    @(posedge HCLK);
    while (HREADY !== 1) @(posedge HCLK);
    HWDATA <= #1 data;
    HTRANS <= #1 2'b0; // IDLE
    @(posedge HCLK);
    while (HREADY === 0) @(posedge HCLK);
    if (HRESP != 2'b00) 
        $display($time,, "ERROR: non OK response write");
    @(posedge HCLK);
end
endtask

//-----------------------------------------------------------------------
// AHB Read Burst Task
//-----------------------------------------------------------------------
task ahb_read_burst;
    input [31:0]                addr;
    input [31:0]                leng;
    
    integer                     i; 
    integer                     ln; 
    integer                     k;
begin
    k = 0;
    @(posedge HCLK);
    HADDR <= #1 addr; 
    addr = addr + 4;
    HTRANS <= #1 2'b10; // NONSEQ
    if (leng >= 16) begin 
        HBURST <= #1 3'b111; // INCR16
        ln = 16; 
    end
    else if (leng >= 8) begin 
        HBURST <= #1 3'b101; // INCR8
        ln = 8; 
    end
    else if (leng >= 4) begin 
        HBURST <= #1 3'b011; // INCR4
        ln = 4; 
    end 
    else begin 
        HBURST <= #1 3'b001; // INCR
        ln = leng; 
    end 
    HWRITE <= #1 1'b0; // READ
    HSIZE <= #1 3'b010; // WORD
    @(posedge HCLK);
    while (HREADY == 1'b0) @(posedge HCLK);
    while (leng > 0) begin
        for (i = 0; i < ln-1; i = i + 1) begin
            HADDR <= #1 addr; 
            addr = addr + 4;
            HTRANS <= #1 2'b11; // SEQ;
            @(posedge HCLK);
            while (HREADY == 1'b0) @(posedge HCLK);
            data_burst[k%1024] <= HRDATA;
            k = k + 1;
        end
        leng = leng - ln;
        if (leng == 0) begin
            HADDR <= #1 0;
            HTRANS <= #1 0;
            HBURST <= #1 0;
            HWRITE <= #1 0;
            HSIZE <= #1 0;
        end 
        else begin
            HADDR <= #1 addr; 
            addr = addr + 4;
            HTRANS <= #1 2'b10; // NONSEQ
            if (leng >= 16) begin 
                HBURST <= #1 3'b111; // INCR16
                ln = 16; 
            end 
            else if (leng >= 8) begin 
                HBURST <= #1 3'b101; // INCR8
                ln = 8; 
            end 
            else if (leng >= 4) begin 
                HBURST <= #1 3'b011; // INCR4
                ln = 4; 
            end 
            else begin 
                HBURST <= #1 3'b001; // INCR1 
                ln = leng; 
            end
            @(posedge HCLK);
            while (HREADY == 0) @(posedge HCLK);
            data_burst[k%1024] = HRDATA; // must be blocking
            k = k + 1;
        end
    end
    @(posedge HCLK);
    while (HREADY == 0) @(posedge HCLK);
    data_burst[k%1024] = HRDATA; // must be blocking
end
endtask

//-----------------------------------------------------------------------
// AHB Write Burst Task
// It takes suitable burst first and then incremental.
//-----------------------------------------------------------------------
task ahb_write_burst;
    input [31:0]                addr;
    input [31:0]                leng;
    integer                     i; 
    integer                     j; 
    integer                     ln;
begin
    j = 0;
    ln = 0;
    @(posedge HCLK);
    while (leng > 0) begin
        HADDR <= #1 addr; 
        addr = addr + 4;
        HTRANS <= #1 2'b10; // NONSEQ
        if (leng >= 16) begin 
            HBURST <= #1 3'b111; // INCR16
            ln = 16; 
        end
        else if (leng >= 8) begin 
            HBURST <= #1 3'b101; // INCR8
            ln = 8; 
        end
        else if (leng >= 4) begin 
            HBURST <= #1 3'b011; // INCR4
            ln = 4; 
        end
        else begin 
            HBURST <= #1 3'b001; // INCR
            ln = leng; 
        end
        HWRITE <= #1 1'b1; // WRITE
        HSIZE <= #1 3'b010; // WORD
        for (i = 0; i < ln-1; i = i + 1) begin
            @(posedge HCLK);
            while (HREADY == 1'b0) @(posedge HCLK);
            HWDATA <= #1 data_burst[(j+i)%1024];
            HADDR <= #1 addr; 
            addr = addr + 4;
            HTRANS <= #1 2'b11; // SEQ;
            while (HREADY == 1'b0) @(posedge HCLK);
        end
        @(posedge HCLK);
        while (HREADY == 0) @(posedge HCLK);
        HWDATA <= #1 data_burst[(j+i)%1024];
        if (ln == leng) begin
            HADDR <= #1 0;
            HTRANS <= #1 0;
            HBURST <= #1 0;
            HWRITE <= #1 0;
            HSIZE <= #1 0;
        end
        leng = leng - ln;
        j = j + ln;
    end
    @(posedge HCLK);
    while (HREADY == 0) @(posedge HCLK);
    if (HRESP != 2'b00) begin // OKAY
        $display($time,, "ERROR: non OK response write");
    end
`ifdef DEBUG
    $display($time,, "INFO: write(%x, %d, %x)", addr, size, data);
`endif
    HWDATA <= #1 0;
    @(posedge HCLK);
end
endtask

`endif

还需要一个APB的从机模块:

`timescale 1ns / 1ps

`define AMBA_APB3
`define AMBA_APB4

module apb_mem #(
    parameter                   P_SLV_ID = 0,
    parameter                   ADDRWIDTH = 32,
    parameter                   P_SIZE_IN_BYTES = 1024, // memory depth
    parameter                   P_DELAY = 0 // reponse delay
)  
(
    //----------------------------------
    // IO Declarations
    //----------------------------------
`ifdef AMBA_APB3
    output wire                 PREADY,
    output wire                 PSLVERR,
`endif          
`ifdef AMBA_APB4            
    input  wire [2:0]           PPROT,
    input  wire [3:0]           PSTRB,
`endif          
    input  wire                 PRESETn,
    input  wire                 PCLK,
    input  wire                 PSEL,
    input  wire                 PENABLE,
    input  wire [ADDRWIDTH-1:0] PADDR,
    input  wire                 PWRITE,
    output reg  [31:0]          PRDATA = 32'h0,
    input  wire [31:0]          PWDATA
);

    //----------------------------------
    // Local Parameter Declarations
    //----------------------------------
    localparam                  DEPTH = (P_SIZE_IN_BYTES+3)/4;
    localparam                  AW = logb2(P_SIZE_IN_BYTES);

    //----------------------------------
    // Variable Declarations
    //----------------------------------
`ifndef AMBA_APB3
    wire                        PREADY;
`else
    assign                      PSLVERR = 1'b0;
`endif

`ifndef AMBA_APB4
    wire [3:0]                  PSTRB = 4'hF;
`endif

    reg [7:0]                   mem0[0:DEPTH-1];
    reg [7:0]                   mem1[0:DEPTH-1];
    reg [7:0]                   mem2[0:DEPTH-1];
    reg [7:0]                   mem3[0:DEPTH-1];

    wire [AW-3:0]               TA = PADDR[AW-1:2];

    //----------------------------------
    // Start of Main Code
    //----------------------------------
    //--------------------------------------------------------------------------
    // write transfer
    //             ____      ____      ____
    // PCLK    ___|    |____|    |____|    |_
    //         ____ ___________________ _____
    // PADDR   ____X__A________________X_____
    //         ____ ___________________ _____
    // PWDATA  ____X__DW_______________X_____
    //              ___________________
    // PWRITE  ____|                   |_____
    //              ___________________
    // PSEL    ____|                   |_____
    //                        _________
    // PENABLE ______________|         |_____
    //--------------------------------------------------------------------------
    always @(posedge PCLK) 
    begin
        if (PRESETn & PSEL & PENABLE & PWRITE & PREADY) begin
            if (PSTRB[0]) 
                mem0[TA] <= PWDATA[ 7: 0];
            if (PSTRB[1]) 
                mem1[TA] <= PWDATA[15: 8];
            if (PSTRB[2]) 
                mem2[TA] <= PWDATA[23:16];
            if (PSTRB[3]) 
                mem3[TA] <= PWDATA[31:24];
        end
    end

    //--------------------------------------------------------------------------
    // read
    //             ____      ____      ____
    // PCLK    ___|    |____|    |____|    |_
    //         ____ ___________________ _____
    // PADDR   ____X__A________________X_____
    //         ____           _________ _____
    // PRDATA  ____XXXXXXXXXXX__DR_____X_____
    //         ____                     _____
    // PWRITE  ____|___________________|_____
    //              ___________________
    // PSEL    ____|                   |_____
    //                        _________
    // PENABLE ______________|         |_____
    //--------------------------------------------------------------------------
    always @(posedge PCLK)
    begin
        if (PRESETn & PSEL & ~PENABLE & ~PWRITE) begin
            PRDATA[ 7: 0] <= mem0[TA];
            PRDATA[15: 8] <= mem1[TA];
            PRDATA[23:16] <= mem2[TA];
            PRDATA[31:24] <= mem3[TA];
        end
    end
    
`ifdef AMBA_APB3
    localparam                  ST_IDLE = 'h0,
                                ST_CNT  = 'h1,
                                ST_WAIT = 'h2;

    reg [7:0]                   count;
    reg                         ready;

    reg [1:0]                   state=ST_IDLE;

    assign PREADY = (P_DELAY == 0) ? 1'b1 : ready;

    always @(posedge PCLK or negedge PRESETn) 
    begin
        if (PRESETn == 1'b0) begin
            count <= 'h0;
            ready <= 'b1;
            state <= ST_IDLE;
        end else begin
            case (state)
                ST_IDLE : begin
                    if (PSEL && (P_DELAY > 0)) begin
                        ready <= 1'b0;
                        count <= 'h1;
                        state <= ST_CNT;
                    end 
                    else begin
                        ready <= 1'b1;
                    end
                end // ST_IDLE

                ST_CNT : begin
                    count <= count + 1;
                    if (count >= P_DELAY) begin
                        count <= 'h0;
                        ready <= 1'b1;
                        state <= ST_WAIT;
                    end
                end // ST_CNT

                ST_WAIT : begin
                    ready <= 1'b1;
                    state <= ST_IDLE;
                end // ST_WAIT
                
                default : begin
                    ready <= 1'b1;
                    state <= ST_IDLE;
                end
            endcase
        end // if
    end // always
`else
    assign PREADY = 1'b1;
`endif

    // Calculate log-base2
    function integer logb2;
        input [31:0]            value;
        reg   [31:0]            tmp;
    begin
        tmp = value - 1;
        for (logb2 = 0; tmp > 0; logb2 = logb2 + 1) 
            tmp = tmp >> 1;
    end
    endfunction

    // synopsys translate_off
`ifdef RIGOR
    always @(posedge PCLK or negedge PRESETn) 
    begin
        if (PRESETn == 1'b0) begin
        end
        else begin
            if (PSEL & PENABLE) begin
                if (TA >= DEPTH) 
                    $display($time,,"%m: ERROR: out-of-bound 0x%x", PADDR);
            end
        end
    end
`endif
    // synopsys translate_on

endmodule

6、仿真

用VCS进行仿真,打印信息如下
图片[7] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客
可见仿真完全正确,这里也只是做了AHB总线的单一传输和各种长度的增量突发,回环突发未涉及(对APB桥来说,它并不关心HBURST的信号值)。

下面挂两张仿真截图:
单一传输,先写后读:
图片[8] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客
突发传输,先写完,再读完
图片[9] -  Verilog实现AMBA--AHB To APB Bridge - 我的学记|刘航宇的博客

© 版权声明
THE END
喜欢就支持一下吧
点赞 1 分享 赞赏
评论 共1条
取消
  1. 头像
    Cher1sh
     · 
    回复

    想问下为什么BURST读的时候,看波形数据是在HREADY为0的时候变化的?