AMBA--APB总线协议及Verilog实现与仿真
我的学记|刘航宇的博客

AMBA--APB总线协议及Verilog实现与仿真

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

1、APB总线简介

APB:Advanced Peripheral Bus,高级外设总线,具备以下特性:

(1)低功耗;
(2)接口协议简单;
(3)总线传输使用时钟上升沿进行,便于时序分析;
(4)应用广泛,支持多种外设。

所有的APB模块均是APB从机。

2、APB信号列表

所有的APB总线信号都以字母P作为前缀,下表列出了APB信号的名称以及对信号的描述:

NameWidthI/ODescription
PCLK1bitInAPB总线时钟,所有传输只能发生在PCLK的上升沿
PRESETn1bitInAPB总线复位信号,低电平有效
PADDR32bitInAPB地址总线
PSEL1bitInAPB从机片选信号
PENABLE1bitInAPB选通信号,高电平表示APB传输的第二个周期
PWRITE1bitInAPB读写控制信号,高表示写,低表示读
PRDATA32bitOutAPB读数据信号,最高为32位
PWDATA32bitInAPB写数据信号,最高为32位

3、APB总线时序

(1)写时序
图片[1] - AMBA--APB总线协议及Verilog实现与仿真 - 我的学记|刘航宇的博客
写传输开始于T2时刻,在改时钟上升沿时刻,地址、写信号、PSEL、写数据信号同时发生变化,T2时钟,即传输的第一个时钟被称为SETUP周期。在下个时钟上升沿T3,PENABLE信号拉高,表示ENABLE周期,在该周期内,数据、地址以及控制信号都必须保持有效。整个写传输在这个周期结束时完成。
(2)读时序
图片[2] - AMBA--APB总线协议及Verilog实现与仿真 - 我的学记|刘航宇的博客
读传输开始于T2时刻,在改时钟上升沿时刻,地址、写信号、PSEL信号同时发生变化,在下个时钟上升沿T3,PENABLE信号拉高,从机必须在ENABLE周期内提供读数据,读数据信号将在T4上升沿时刻被采样。
经历2个cycle就读数据,不需要握手
4、Verilog实现
下面编写一个简单的基于APB接口的memory读写控制程序供读数据,读数据信号将在T4上升沿时刻被采样。

`timescale 1ns / 1ps

module apb_sram #(
    parameter                           SIZE_IN_BYTES = 1024
)
(
    //----------------------------------
    // IO Declarations
    //----------------------------------
    input                               PRESETn,
    input                               PCLK,
    input                               PSEL,
    input [31:0]                        PADDR,
    input                               PENABLE,
    input                               PWRITE,
    input [31:0]                        PWDATA,
    output reg [31:0]                   PRDATA
);

    //----------------------------------
    // Local Parameter Declarations
    //----------------------------------
    localparam                          A_WIDTH = clogb2(SIZE_IN_BYTES);

    //----------------------------------
    // Variable Declarations
    //----------------------------------
    reg [31:0]                          mem[0:SIZE_IN_BYTES/4-1];
    wire                                wren;
    wire                                rden;
    wire [A_WIDTH-1:2]                  addr; 
 
    //----------------------------------
    // Function Declarations
    //----------------------------------
    function integer clogb2;
        input [31:0]                    value; 
        reg [31:0]                      tmp; 
        reg [31:0]                      rt;
    begin
        tmp = value - 1;
        for (rt = 0; tmp > 0; rt = rt + 1) 
            tmp = tmp >> 1;
        clogb2 = rt;
    end
    endfunction

    //----------------------------------
    // Start of Main Code
    //----------------------------------
    // Create read and write enable signals using APB control signals
    assign wren = PWRITE && PENABLE && PSEL; // Enable Period
    assign rden = ~PWRITE && ~PENABLE && PSEL; // Setup Period

    assign addr = PADDR[A_WIDTH-1:2];
    
    // Write mem
    always @(posedge PCLK)
    begin
        if (wren)
            mem[addr] <= PWDATA;
    end

    // Read mem
    always @(posedge PCLK)
    begin
        if (rden)
            PRDATA <= mem[addr];
        else
            PRDATA <= 'h0;
    end

endmodule

测试代码:

`timescale 1ns / 1ps

`ifndef CLK_FREQ
`define CLK_FREQ 50000000
`endif

module top_tb();

    //----------------------------------
    // Local Parameter Declarations
    //----------------------------------
    parameter                           SIZE_IN_BYTES = 1024;

    localparam                          CLK_FREQ = `CLK_FREQ;
    localparam                          CLK_PERIOD_HALF = 1000000000/(CLK_FREQ*2);

    //----------------------------------
    // Variable Declarations
    //----------------------------------
    reg                                 PRESETn = 1'b0;
    reg                                 PCLK = 1'b0;
    reg                                 PSEL;
    reg [31:0]                          PADDR;
    reg                                 PENABLE;
    reg                                 PWRITE;
    reg [31:0]                          PWDATA;   
    wire [31:0]                         PRDATA;

    reg [31:0]                          reposit[0:1023];

    //----------------------------------
    // Start of Main Code
    //----------------------------------
    apb_sram #(
        .SIZE_IN_BYTES                  (SIZE_IN_BYTES)
    )
    u_apb_sram (
        .PRESETn                        (PRESETn),
        .PCLK                           (PCLK),
        .PSEL                           (PSEL),
        .PADDR                          (PADDR),
        .PENABLE                        (PENABLE),
        .PWRITE                         (PWRITE),
        .PWDATA                         (PWDATA),
        .PRDATA                         (PRDATA)
    );
    
    // generate PCLK
    always #CLK_PERIOD_HALF 
    begin
        PCLK <= ~PCLK;
    end 

    // generate PRESETn
    initial begin
        PRESETn <= 1'b0;
        repeat(5) @(posedge PCLK);
        PRESETn <= 1'b1;
    end

    // test memory
    initial begin
        PSEL = 1'b0;
        PADDR = ~32'h0;
        PENABLE = 1'b0;
        PWRITE = 1'b0;
        PWDATA = 32'hffff_ffff;
        wait(PRESETn == 1'b0);
        wait(PRESETn == 1'b1);
        repeat(3) @(posedge PCLK);
        memory_test(0, SIZE_IN_BYTES/4-1);
        repeat(5) @(posedge PCLK);
        $finish(2);
    end

    // memory test task
    task memory_test;
        // starting address
        input [31:0]                    start;
        // ending address, inclusive
        input [31:0]                    finish; 
        reg [31:0]                      dataW;
        reg [31:0]                      dataR;
        integer                         a; 
        integer                         b; 
        integer                         err;
    begin
        err = 0;
        // read-after-write test
        for (a = start; a <= finish; a = a + 1) begin
            dataW = $random;
            apb_write(4*a, dataW);
            apb_read (4*a, dataR);
            if (dataR !== dataW) begin
                err = err + 1;
                $display($time,,"%m Read after Write error at A:0x%08x D:0x%x, but 0x%x expected", a, dataR, dataW);
            end
        end
        if (err == 0) 
            $display($time,,"%m Read after Write 0x%x-%x test OK", start, finish);
        err = 0;
        // read_all-after-write_all test
        for (a = start; a <= finish; a = a + 1) begin
            b = a - start;
            reposit[b] = $random;
            apb_write(4*a, reposit[b]);
        end
        for (a = start; a <= finish; a = a + 1) begin
            b = a - start;
            apb_read(4*a, dataR);
            if (dataR !== reposit[b]) begin
                err = err + 1;
                $display($time,,"%m Read all after Write all error at A:0x%08x D:0x%x, but 0x%x expected", a, dataR, reposit[b]);
            end
        end
        if (err == 0) 
            $display($time,,"%m Read all after Write all 0x%x-%x test OK", start, finish);
    end
    endtask

    // APB write task
    task apb_write;
        input [31:0]                    addr;
        input [31:0]                    data;
    begin
        @(posedge PCLK);
        PADDR <= #1 addr;
        PWRITE <= #1 1'b1;
        PSEL <= #1 1'b1;
        PWDATA <= #1 data;
        @(posedge PCLK);
        PENABLE <= #1 1'b1;
        @(posedge PCLK);
        PSEL <= #1 1'b0;
        PENABLE <= #1 1'b0;
    end
    endtask

    // APB read task
    task apb_read;
        input [31:0]                     addr;
        output [31:0]                    data;
    begin
        @(posedge PCLK);
        PADDR <= #1 addr;
        PWRITE <= #1 1'b0;
        PSEL <= #1 1'b1;
        @(posedge PCLK);
        PENABLE <= #1 1'b1;
        @(posedge PCLK);
        PSEL <= #1 1'b0;
        PENABLE <= #1 1'b0;
        data = PRDATA; // it should be blocking
    end
    endtask

`ifdef VCS
    initial begin
        $fsdbDumpfile("top_tb.fsdb");
        $fsdbDumpvars;
    end

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

endmodule

该测试用例,主要实现了APB读和写的task,用于产生APB读写时序,对memory的测试分成连个部分,一个是每进行一次写传输后,紧接着进行同地址的读传输,让后对比读写结果一致性;另一个测试是在连续写一段地址后,再全部读出改地址段的数据,完成读操作后进行数据比对。

下面是仿真打印信息
图片[3] - AMBA--APB总线协议及Verilog实现与仿真 - 我的学记|刘航宇的博客
APB写传输时序的仿真波形如下:
图片[4] - AMBA--APB总线协议及Verilog实现与仿真 - 我的学记|刘航宇的博客
APB写传输时序的仿真波形如下:
图片[5] - AMBA--APB总线协议及Verilog实现与仿真 - 我的学记|刘航宇的博客

© 版权声明
THE END
喜欢就支持一下吧
点赞 2 分享 赞赏
评论 抢沙发
取消