0%

FPGA系统设计之APB总线

本节主要介绍了APB协议的基本传输时序,并以基于简单APB的SRAM读写实验加强理解。

APB协议的基本介绍

  • Advanced Peripheral Bus(先进外设总线),具备低功耗、接口简单、控制简单的特点,主要用于低速、功耗低的外设寄存器配置

  • APB协议是一种低成本接口,针对低功耗和降低接口复杂度进行了优化,APB接口不是流水线接口,是一种简单的同步协议。每次传输至少需要两个周期才能完成

  • 不能读写同时传输,因为其读写地址是共用的

  • 不能仲裁,因为是单主多从协议。典型的APB协议包括唯一的APB桥作为Master,而所有的APB模块都是APB slave

    image-20241113221717120

APB协议的基本传输时序

  • 传输时序建议直接看APB官方手册,实际上APB比较简单,单看时序图也能懂

1.写操作

1.1 无等待

image-20241113222349327

1.2 有等待

image-20241113222434656

2.读操作

2.1 无等待

image-20241113222624798

2.2 有等待

image-20241113222659661

APB时序控制状态机

  • 从APB master角度

    image-20241113223256203
  • 退出ACCESS状态取决于slaver的PREADY信号

  • Transfer这一状态跳转条件其实取决于master的上游模块是否需要发起一次传输活动


基于APB总线的简单SRAM读写实验

  • 设计框图:

    image-20241113223648826

1.源代码

  • apb_master.v

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    /*-------------------------------------------------------------
    -- modified by xlinxdu, 2022/05/27
    -- pclk 50MHz
    -- APB3,No pslverr signal
    -- cmd_i:56bit;[55:48]:r/w ,8'b0 -> read,8'b1 -> write
    [47:32]:paddr ,
    [31:0]:pwdata
    -------------------------------------------------------------*/
    module apb_master #(
    parameter RD_FLAG = 8'b0 ,
    parameter WR_FLAG = 8'b1 ,
    parameter CMD_RW_WIDTH = 8 ,
    parameter CMD_ADDR_WIDTH = 16 ,
    parameter CMD_DATA_WIDTH = 32 ,
    parameter CMD_WIDTH = CMD_RW_WIDTH + CMD_ADDR_WIDTH + CMD_DATA_WIDTH
    )(
    //-- system signal
    input pclk_i ,
    input prst_n_i ,

    //-- cmd_in
    input [CMD_WIDTH -1 : 0] cmd_i ,
    input cmd_vld_i ,
    output reg [CMD_DATA_WIDTH - 1 : 0] cmd_rd_data_o,

    //-- apb interface
    output reg [CMD_ADDR_WIDTH - 1 : 0] paddr_o ,
    output reg pwrite_o ,
    output reg psel_o ,
    output reg penable_o ,
    output reg [CMD_DATA_WIDTH - 1 : 0] pwdata_o ,
    input [CMD_DATA_WIDTH - 1 : 0] prdata_i ,
    input pready_i
    );

    //-- FSM state
    parameter IDLE = 3'b001;
    parameter SETUP = 3'b010;
    parameter ACCESS = 3'b100;

    //-- current state and next state
    reg [2 : 0] cur_state;
    reg [2 : 0] nxt_state;

    //-- data buf
    reg start_flag ;
    reg [CMD_WIDTH - 1 : 0] cmd_in_buf ;
    reg [CMD_DATA_WIDTH - 1 : 0] cmd_rd_data_buf;


    /*-----------------------------------------------\
    -- update cmd_in_buf --
    \-----------------------------------------------*/
    always @ (posedge pclk_i or negedge prst_n_i) begin
    if (!prst_n_i) begin
    cmd_in_buf <= {(CMD_WIDTH){1'b0}};
    end
    else if (cmd_vld_i) begin
    cmd_in_buf <= cmd_i;
    end
    end

    /*-----------------------------------------------\
    -- start flag of transfer --
    \-----------------------------------------------*/
    always @ (posedge pclk_i or negedge prst_n_i) begin
    if (!prst_n_i) begin
    start_flag <= 1'b0;
    end
    else if (cmd_vld_i) begin
    start_flag <= 1'b1;
    end
    else begin
    start_flag <= 1'b0;
    end
    end

    /*-----------------------------------------------\
    -- update current state --
    \-----------------------------------------------*/
    always @ (posedge pclk_i or negedge prst_n_i) begin
    if (!prst_n_i) begin
    cur_state <= IDLE;
    end
    else begin
    cur_state <= nxt_state;
    end
    end

    /*-----------------------------------------------\
    -- update next state --
    \-----------------------------------------------*/
    always @ (*) begin
    nxt_state = cur_state;
    case(cur_state)
    IDLE : begin
    if(start_flag)begin
    nxt_state = SETUP;
    end
    else begin
    nxt_state = IDLE;
    end
    end
    SETUP : begin
    nxt_state = ACCESS;
    end
    ACCESS: begin
    if (!pready_i)begin
    nxt_state = ACCESS;
    end
    else if(start_flag)begin
    nxt_state = SETUP;
    end
    else if(pready_i)begin
    nxt_state = IDLE;
    end
    end
    endcase
    end

    /*-----------------------------------------------\
    -- update signal of output --
    \-----------------------------------------------*/
    always @ (posedge pclk_i or negedge prst_n_i) begin
    if (!prst_n_i) begin
    pwrite_o <= 1'b0;
    psel_o <= 1'b0;
    penable_o <= 1'b0;
    paddr_o <= {(CMD_ADDR_WIDTH){1'b0}};
    pwdata_o <= {(CMD_DATA_WIDTH){1'b0}};
    end
    else begin
    if (nxt_state == IDLE) begin
    psel_o <= 1'b0;
    penable_o <= 1'b0;
    end
    else if(nxt_state == SETUP)begin
    psel_o <= 1'b1;
    penable_o <= 1'b0;
    paddr_o <= cmd_in_buf[CMD_WIDTH - CMD_RW_WIDTH - 1 : CMD_DATA_WIDTH];
    //-- read
    if(cmd_in_buf[CMD_WIDTH - 1 : CMD_WIDTH - 8] == RD_FLAG)begin
    pwrite_o <= 1'b0;
    end
    //-- write
    else begin
    pwrite_o <= 1'b1;
    pwdata_o <= cmd_in_buf[CMD_DATA_WIDTH - 1 : 0];
    end
    end
    else if(nxt_state == ACCESS)begin
    penable_o <= 1'b1;
    end
    end
    end

    /*-----------------------------------------------\
    -- update cmd_rd_data_buf --
    \-----------------------------------------------*/
    always @ (posedge pclk_i or negedge prst_n_i) begin
    if (!prst_n_i) begin
    cmd_rd_data_buf <= {(CMD_DATA_WIDTH){1'b0}};
    end
    else if (pready_i && psel_o && penable_o) begin
    cmd_rd_data_buf <= prdata_i;
    end
    end

    /*-----------------------------------------------\
    -- update cmd_rd_data_o --
    \-----------------------------------------------*/
    always @ (posedge pclk_i or negedge prst_n_i) begin
    if (!prst_n_i) begin
    cmd_rd_data_o <= {(CMD_DATA_WIDTH){1'b0}};
    end
    else begin
    cmd_rd_data_o <= cmd_rd_data_buf;
    end
    end

    endmodule
  • apb_slave_sram.v

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    module apb_slave_sram #(
    parameter DATA_WIDTH = 32,
    parameter DATA_DEPTH = 16,
    parameter ADDR_WIDTH = 16
    )(
    input PCLK,
    input PSEL,
    input PENABLE,
    input PWRITE,
    input [DATA_WIDTH - 1 : 0] PWDATA,
    input [ADDR_WIDTH - 1 : 0] PADDR,
    input PRESETn,
    output wire PREADY,
    output reg [DATA_WIDTH - 1 : 0] PRDATA
    );
    integer i;
    reg [DATA_WIDTH - 1 : 0] SRAM [DATA_DEPTH - 1 : 0];

    assign PREADY = PSEL && PENABLE;

    always@(posedge PCLK or negedge PRESETn)
    begin
    if(!PRESETn) begin
    for(i = 0; i < DATA_DEPTH; i = i + 1) begin
    SRAM [i] <= 'd0;
    end
    PRDATA <= 0;
    end
    else begin
    if(PSEL == 1 && PENABLE == 0 && PWRITE == 1) begin
    SRAM[PADDR] <= PWDATA;
    end
    else if(PSEL == 1 && PENABLE == 0 && PWRITE == 0) begin
    PRDATA <= SRAM[PADDR];
    end
    else begin
    PRDATA <= PRDATA;
    end
    end
    end

    // integer i;
    // reg [DATA_WIDTH - 1 : 0] SRAM [DATA_DEPTH - 1 : 0];

    // always @(posedge PCLK or negedge PRESETn) begin
    // if(~PRESETn) begin
    // PREADY <= 'd0;
    // end
    // else begin
    // PREADY <= PSEL && PENABLE;
    // end
    // end

    // always@(posedge PCLK or negedge PRESETn)
    // begin
    // if(!PRESETn) begin
    // for(i = 0; i < DATA_DEPTH; i = i + 1) begin
    // SRAM [i] <= 'd0;
    // end
    // PRDATA <= 0;
    // end
    // else begin
    // if(PSEL && PENABLE && PWRITE == 1) begin
    // SRAM[PADDR] <= PWDATA;
    // end
    // else if(PSEL && PENABLE && PWRITE == 0) begin
    // PRDATA <= SRAM[PADDR];
    // end
    // else begin
    // PRDATA <= PRDATA;
    // end
    // end
    // end

    endmodule

2.Testbench

  • apb_simple_tb.v

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    module apb_simple_tb;

    parameter RD_FLAG = 8'b0 ;
    parameter WR_FLAG = 8'b1 ;
    parameter CMD_RW_WIDTH = 8 ;
    parameter CMD_ADDR_WIDTH = 16 ;
    parameter CMD_DATA_WIDTH = 32 ;
    parameter CMD_WIDTH = CMD_RW_WIDTH + CMD_ADDR_WIDTH + CMD_DATA_WIDTH ;

    //-- system signal
    reg pclk_i ;
    reg prst_n_i ;

    //-- cmd_in
    reg [CMD_WIDTH -1 : 0] cmd_i ;
    reg cmd_vld_i ;
    wire [CMD_DATA_WIDTH - 1 : 0] cmd_rd_data_o;

    //-- apb interface
    wire [CMD_ADDR_WIDTH - 1 : 0] paddr_o ;
    wire pwrite_o ;
    wire psel_o ;
    wire penable_o;
    wire [CMD_DATA_WIDTH - 1 : 0] pwdata_o ;
    wire [CMD_DATA_WIDTH - 1 : 0] prdata_i ;
    wire pready_i ;

    apb_master #(
    .RD_FLAG (RD_FLAG ),
    .WR_FLAG (WR_FLAG ),
    .CMD_RW_WIDTH (CMD_RW_WIDTH ),
    .CMD_ADDR_WIDTH (CMD_ADDR_WIDTH),
    .CMD_DATA_WIDTH (CMD_DATA_WIDTH),
    .CMD_WIDTH (CMD_WIDTH )
    )apb_master_U1(
    //-- system signal
    .pclk_i (pclk_i ),
    .prst_n_i (prst_n_i),

    //-- cmd_in
    .cmd_i (cmd_i ),
    .cmd_vld_i (cmd_vld_i ),
    .cmd_rd_data_o(cmd_rd_data_o),

    //-- apb interface
    .paddr_o (paddr_o ),
    .pwrite_o (pwrite_o ),
    .psel_o (psel_o ),
    .penable_o (penable_o),
    .pwdata_o (pwdata_o ),
    .prdata_i (prdata_i ),
    .pready_i (pready_i )
    );

    apb_slave_sram #(
    .DATA_WIDTH (CMD_DATA_WIDTH),
    .DATA_DEPTH (CMD_ADDR_WIDTH),
    .ADDR_WIDTH (CMD_ADDR_WIDTH)
    )apb_slave_sram_U1(
    .PCLK (pclk_i ),
    .PRESETn(prst_n_i),
    .PSEL (psel_o),
    .PENABLE(penable_o),
    .PWRITE (pwrite_o),
    .PWDATA (pwdata_o),
    .PADDR (paddr_o),
    .PREADY (pready_i),
    .PRDATA (prdata_i)
    );

    initial begin
    pclk_i = 1;
    end
    always #10 pclk_i = ~pclk_i;

    initial begin
    prst_n_i = 0;
    cmd_i = 'd0;
    cmd_vld_i = 'd0;
    #51 prst_n_i = 1;

    //--------------------------有等待读写-----------------------------------
    //写操作
    // cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd0, 32'd101};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd1, 32'd102};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd2, 32'd103};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd3, 32'd104};
    // #20 cmd_vld_i <= 'd0;

    // #60 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd4, 32'd105};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd5, 32'd106};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd6, 32'd107};
    // #20 cmd_vld_i <= 'd0;

    // //读操作
    // #100 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd6, 32'd0};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd5, 32'd0};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd4, 32'd0};
    // #20 cmd_vld_i <= 'd0;

    // #80 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd3, 32'd0};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd2, 32'd0};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd1, 32'd0};
    // #20 cmd_vld_i <= 'd0;

    // #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd0, 32'd0};
    // #20 cmd_vld_i <= 'd0;

    //--------------------------无等待读写-----------------------------------
    //写操作
    cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd0, 32'd101};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd1, 32'd102};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd2, 32'd103};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd3, 32'd104};
    #20 cmd_vld_i <= 'd0;

    #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd4, 32'd105};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd5, 32'd106};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd6, 32'd107};
    #20 cmd_vld_i <= 'd0;

    //读操作
    #80 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd6, 32'd0};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd5, 32'd0};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd4, 32'd0};
    #20 cmd_vld_i <= 'd0;

    #60 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd3, 32'd0};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd2, 32'd0};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd1, 32'd0};
    #20 cmd_vld_i <= 'd0;

    #20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd0, 32'd0};
    #20 cmd_vld_i <= 'd0;
    end

    endmodule

3.仿真结果

  • 无等待读写:(这种情况下,将pready一直拉高也可以)

    image-20241113224820952

  • 有等待读写:

    image-20241113225608614

  • 其实master中的关键就是写出控制状态机,slaver中主要是psel和penable对原模块的控制


Reference

欢迎来到ssy的世界