侧边栏壁纸
    • 累计撰写 302 篇文章
    • 累计收到 527 条评论
    Verilog实现AMBA--AHB To APB Bridge
    我的学记|刘航宇的博客

    Verilog实现AMBA--AHB To APB Bridge

    刘航宇
    2023-07-10 / 1 评论 / 549 阅读 / 正在检测是否收录...

    1、APB桥

    APB桥是AMBA APB总线上的唯一主机,也是AMBA AHB的一个从机。下图表示了APB桥接口信号:

    APB Bridge将AHB传输转成APB传输并实现一下功能:
    (1)对锁存的地址进行译码并产生选择信号PSELx,在传输过程中只有一个选择信号可以被激活。 也就是选择出唯一 一个APB从设备以进行读写动作。
    (2)写操作时:负责将AHB送来的数据送上APB总线。
    (3)读操作时:负责将APB的数据送上AHB系统总线。
    (4)产生一时序选通信号PENABLE来作为数据传递时的启动信号

    2、读传输

    下图表示了APB到AHB的读传输:

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

    3、写传输

    下图表示了一个写传输:

    APB总线上的单块数据写操作不需要等待周期。 APB桥负责对地址和数据进行采样,并在写操作的过程中保持它们的值。

    下图表示了一个写突发传输:

    虽然第一个传输可以零等待完成,但后续每一个传输都需要插入一个等待状态。

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

    4、背靠背传输

    下图表示了一个背靠背传输,顺序为写、读、写、读:
    如果写操作之后跟随着读操作,那么需要 3 个等待周期来完成这个读操作。通常的情况下,不会有读操作之后紧跟着写操作的发生,因为两者之间 CPU 会进行指令读取,并且指令存储器也不太可能挂在在APB总线上。

    下面以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进行仿真,打印信息如下

    可见仿真完全正确,这里也只是做了AHB总线的单一传输和各种长度的增量突发,回环突发未涉及(对APB桥来说,它并不关心HBURST的信号值)。

    下面挂两张仿真截图:
    单一传输,先写后读:

    突发传输,先写完,再读完

    1
    Libero SOC Debug教程-片上逻辑分析仪IDENTIFY
    « 上一篇 2023-07-13
    数字锁相环(DPLL)研究与设计
    下一篇 » 2023-06-24

    评论 (1)

    取消
    1. 头像
      Cher1sh
      Windows 10 · Google Chrome

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

      回复