AHB总线协议与APB总线协议
AHB (Advanced High-performance Bus) 高级高性能总线
AHB主要是针对高效率、高频宽及快速系统模块所设计的总线,它可以连接如微处理器、芯片上或芯片外的内存模块和DMA等高效率模块。
APB (Advanced Peripheral Bus) 高级外围总线
APB通常不直接与CPU连接,而是通过一个桥接器(如AHB到APB桥)与AHB总线相连。
APB主要用在低速且低功率的外围,可针对外围设备作功率消耗及复杂接口的最佳化。APB在AHB和低带宽的外围设备之间提供了通信的桥梁,所以APB是AHB或ASB的二级拓展总线.
APB总线的唯一主设备是APB桥(与AXI或APB相连),不需要仲裁一些Request/grant信号。
APB总线传输过程
1、系统初始化为IDLE状态,此时没有传输操作,也没有选中任何从模块。
2、当有传输要进行时,PSELx=1,,PENABLE=0,系统进入SETUP状态,并只会在SETUP状态停留一个周期。当PCLK的下一个上升沿到来时,系统进入ENABLE状态。
3、系统进入ENABLE状态时,维持之前在SETUP状态的PADDR、PSEL、PWRITE不变,并将PENABLE置为1。传输也只会在ENABLE状态维持一个周期,在经过SETUP与ENABLE状态之后就已完成。之后如果没有传输要进行,就进入IDLE状态等待;如果有连续的传输,则进入SETUP状态。
AHB与APB总线的连接架构图
AHB与APB总线之间通过bridge进行连接
APB规定所有的信号必须在时钟的上升沿进行传递。
问题:AHB如何知道是否决定使用APB总线进行传输?
答:决定是否使用 APB (Advanced Peripheral Bus) 进行数据传输通常是由 AHB 总线上的地址解码逻辑来决定的。
步骤如下:
-
地址解码:AHB 总线接口包含一个地址解码器,负责分析传入的地址信号。每个连接到 AHB 总线的外设(包括 AHB-APB 桥接器)都会有一个或多个预定义的地址范围。
-
识别 APB 事务:当 AHB 总线上的一个事务(例如,读取或写入操作)的地址落在 AHB-APB 桥接器所管理的地址范围内时,解码逻辑识别出这个事务需要通过 APB 总线来处理。
-
转发到 APB:一旦 AHB 接口识别出某个事务是针对 APB 总线上的外设的,它就会通过 AHB-APB 桥接器将事务的相关信息(如地址、数据、控制信号等)转发到 APB 总线上。
-
APB 处理:APB 总线上的外设再根据收到的信息进行相应的读写操作。
APB信号列表
信号名 | 方向 | 说明 |
PCLK | Global | 时钟信号,上升沿同步 |
PRESETn | Global | APB总线复位信号为低有效并且通常将该信号直接连接到系统总线的复位信号 |
PADDR | M-->S | 地址总线,32位 |
PSEL | M-->S | 选通信号,当该信号拉高意味着要发起一次传输了 |
PENABLE | M-->S | 使能信号,用于表示一次APB传输的第二个周期 |
PWRITE | M-->S | 该信号为高标志这次是写传输,反之则为读传输 |
PWDATA | M-->S | 写数据总线,由Master在写周期进行持续性驱动(PWRITE为高),支持8/16/32位。 |
PRDATA | S-->M | 读数据总线,由Slave在读周期进行持续性驱动(PWRITE为低),支持8/16/32位。 |
PREADY | S-->M | 指示外设是否已经准备好接收或发送数据 |
PSLVERR | S-->M | 指示外设是否存在错误 |
- PREADY
- PREADY(Peripheral Ready)信号用于指示外设是否已经准备好接收或发送数据。当外设准备好时,它会将PREADY信号置为逻辑高电平,表示它已经可以接受或发送数据。主设备将等待该信号的出现,然后将数据发送给外设或者接收从外设发来的数据。
- PSLVERR
- PSLVERR(Peripheral Slave Error)信号用于指示外设是否存在错误。当外设在处理主设备的请求时遇到错误时,它会将PSLVERR信号置为逻辑高电平,通知主设备发生了错误,避免总线死锁。
APB总线读写时序
写操作
T1->T2
- PSEL信号拉高,意味着要发起一次新的传输了。
- PWRITE信号为1,因此此次传输为写操作。
- PWDATA为要传输的数据,PADDR为要写的地址。二者都应该保持不变,直到此次传输结束。
- 而PSEL拉高的第一个时钟周期,PENABLE应该为0。
T2->T3
- PSEL信号继续拉高
- PWRITE、PADDR、PWDATA应该保持不变
- PENABLE信号拉高,用于代表这已经是写传输的第二个周期了
至此一次传输结束。可以看到,APB对每一笔数据的传输,需要花费两个时钟周期。且APB的数据传输不支持流水线操作(即不可以重叠)。因此APB是非常低效的。
读操作
AHB2APB桥代码
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Abstract : Simple AHB to APB bridge
//-----------------------------------------------------------------------------
// The bridge requires PCLK synchronised to HCLK
// APB running at a clock divided from HCLK. E.g.
// - If PCLK is same as HCLK, set PCLKEN to 1
// - If PCLK is half the HCLK speed, toggle PCLKEN every HCLK cycle
module ahb_to_apb_bridge #(
// Parameter to define address width
// 16 = 2^16 = 64KB APB address space
parameter ADDRWIDTH = 17,
parameter REGISTER_RDTAA = 1,
parameter REGISTER_WDATA = 0)
(
// --------------------------------------------------------------------------
// Port Definitions
// --------------------------------------------------------------------------
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] HRDTAA, // 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] PRDTAA, // Read data for each APB slave
input wire PREADY, // Ready for each APB slave
input wire PSLVERR); // Error state for each APB slave
// --------------------------------------------------------------------------
// Internal wires
// --------------------------------------------------------------------------
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_RDTAA paramater
wire reg_wdata_cfg; // REGISTER_WDATA paramater
reg sample_wdata_reg; // Control signal to sample HWDATA
// -------------------------------------------------------------------------
// 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_RDTAA==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) // Capture transfer information at the end of AHB address phase
begin
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 @(*)
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) // Okay received
begin
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 PRDTAA 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 <= PRDTAA;
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 @(*)
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 PRDTAA directly
assign HRDTAA = (reg_rdata_cfg) ? rwdata_reg : PRDTAA;
assign HRESP = (state_reg==ST_APB_ERR1) | (state_reg==ST_APB_ERR2);
assign APBACTIVE = (HSEL & HTRANS[1]) | (|state_reg);
`ifdef ARM_AHB_ASSERT_ON
// ------------------------------------------------------------
// Assertions
// ------------------------------------------------------------
`include "std_ovl_defines.h"
// Capture last read APB data
reg [31:0] ovl_last_read_apb_data_reg;
always @(posedge HCLK or negedge HRESETn)
begin
if (~HRESETn)
ovl_last_read_apb_data_reg <= {32{1'b0}};
else if (PREADY & PENABLE & (~PWRITE) & PSEL & PCLKEN)
ovl_last_read_apb_data_reg <= PRDTAA;
end
// Capture last APB response, clear at start of APB transfer
reg ovl_last_read_apb_resp_reg;
always @(posedge HCLK or negedge HRESETn)
begin
if (~HRESETn)
ovl_last_read_apb_resp_reg <= 1'b0;
else
begin
if (PCLKEN)
begin
if (PREADY & PSEL)
ovl_last_read_apb_resp_reg <= PSLVERR & PENABLE;
else if (PSEL)
ovl_last_read_apb_resp_reg <= 1'b0;
end
end
end
// Create signal to indicate data phase
reg ovl_ahb_data_phase_reg;
wire ovl_ahb_data_phase_nxt = HSEL & HTRANS[1];
always @(posedge HCLK or negedge HRESETn)
begin
if (~HRESETn)
ovl_ahb_data_phase_reg <= 1'b0;
else if (HREADY)
ovl_ahb_data_phase_reg <= ovl_ahb_data_phase_nxt;
end
generate
if(REGISTER_RDTAA!=0) begin : gen_ovl_read_data_mistmatch
// Last read APB data should be the same as HRDTAA, unless there is an error response
// (Only check if using registered read data config. Otherwise HRDTAA is directly connected
// to PRDTAA so no need to have OVL check)
assert_implication
#(`OVL_ERROR,`OVL_ASSERT,
"Read data mismatch")
u_ovl_read_data_mistmatch
(.clk(HCLK), .reset_n(HRESETn),
.antecedent_expr(ovl_ahb_data_phase_reg & (~wr_reg) & HREADYOUT & (~HRESP)),
.consequent_expr(ovl_last_read_apb_data_reg==HRDTAA)
);
end
endgenerate
// AHB response should match APB response
assert_implication
#(`OVL_ERROR,`OVL_ASSERT,
"Response mismatch")
u_ovl_resp_mistmatch
(.clk(HCLK), .reset_n(HRESETn),
.antecedent_expr(ovl_ahb_data_phase_reg & HREADYOUT),
.consequent_expr(ovl_last_read_apb_resp_reg==HRESP)
);
// APBACTIVE should be high before and during APB transfers
assert_implication
#(`OVL_ERROR,`OVL_ASSERT,
"APBACTIVE should be active when PSEL is high")
u_ovl_apbactive_1
(.clk(HCLK), .reset_n(HRESETn),
.antecedent_expr(PSEL),
.consequent_expr(APBACTIVE)
);
assert_implication
#(`OVL_ERROR,`OVL_ASSERT,
"APBACTIVE should be active when there is an active transfer (data phase)")
u_ovl_apbactive_2
(.clk(HCLK), .reset_n(HRESETn),
.antecedent_expr(~HREADYOUT),
.consequent_expr(APBACTIVE)
);
assert_implication
#(`OVL_ERROR,`OVL_ASSERT,
"APBACTIVE should be active when AHB to APB bridge is selected")
u_ovl_apbactive_3
(.clk(HCLK), .reset_n(HRESETn),
.antecedent_expr((HSEL & HTRANS[1])),
.consequent_expr(APBACTIVE)
);
// Create register to check a transfer on AHB side result in a transfer in APB side
reg ovl_transfer_started_reg;
// Set by transfer starting, clear by PSEL
wire ovl_transfer_started_nxt = (HREADY & ovl_ahb_data_phase_nxt) |
(ovl_transfer_started_reg & (~PSEL));
always @(posedge HCLK or negedge HRESETn)
begin
if (~HRESETn)
ovl_transfer_started_reg <= 1'b0;
else
ovl_transfer_started_reg <= ovl_transfer_started_nxt;
end
// Check a valid transfer must result in an APB transfer
assert_never
#(`OVL_ERROR,`OVL_ASSERT,
"APB transfer missing.")
u_ovl_apb_transfer_missing
(.clk(HCLK), .reset_n(HRESETn),
.test_expr(HREADY & ovl_ahb_data_phase_nxt & ovl_transfer_started_reg)
);
// Ensure state_reg must not be 3'b111
assert_never
#(`OVL_ERROR,`OVL_ASSERT,
"state_reg in illegal state")
u_ovl_rx_state_illegal
(.clk(HCLK), .reset_n(HRESETn),
.test_expr(state_reg==ST_ILLEGAL));
`endif
endmodule
外层wapper使用举例
// ----------------------------------------------------------------------
// AHB to APB Bridge
// ----------------------------------------------------------------------
ahb_to_apb_bridge i_ahb_to_apb_bridge(
// Inputs
.HSEL (cpu_aad_hsel_apb), // APB selected
// AHB
.HCLK (host_clk),
.HRESETn (host_reset_n),
.HADDR (cpi_ahb_haddr),
.HTRANS (cpi_ahb_htrans),
.HSIZE (cpi_ahb_hsize),
.HPROT (4'b0),
.HWDATA (cpi_ahb_hwdata),
.HREADY (cpu_aad_hready),
.HWRITE (cpi_ahb_hwrite),
// APB
.PCLKEN (1'b1), // not finished yet
.PREADY (apb_pready),
.PRDTAA (apb_prdata),
.PSLVERR (apb_pslverr),
// Outputs
//AHB
.HRDTAA (apb_hrdata),
.HREADYOUT (apb_hready),
.HRESP (apb_hresp),
//APB
.PENABLE (apb_penable),
.PSEL (apb_psel),
.PADDR (apb_paddr),
.PWRITE (apb_pwrite),
.PSTRB (),
.PPROT (),
.PWDATA (apb_pwdata),
.APBACTIVE()
);
CPU选择APB传输代码示例
if ((cpi_ahb_haddr >= APB_ADDR_START && cpi_ahb_haddr <= APB_ADDR_END) || cpi_ahb_haddr == APB_PHY_RESET_ADDR ) begin
s_hsel_apb = 1'b1;
end
else begin
case (cpi_ahb_haddr[31:12])
`cif : begin
s_hsel_cif = 1'b1 ;
end
`tag_cif : begin
s_hsel_tag = 1'b1 ;
end
`hif : begin
if (cpi_ahb_haddr[11:0] <= `last_hif_addr) begin
s_hsel_hif = 1'b1 ;
s_hsel_aux = 1'b0 ;
end
else if (cpi_ahb_haddr[11:0] <= `last_aux_hif_addr) begin
s_hsel_hif = 1'b0 ;
s_hsel_aux = 1'b1 ;
end
else begin
s_hsel_hif = 1'b1 ;
s_hsel_aux = 1'b0 ;
end
end
endcase
end