一、APB协议
原文:
翻译:
APB协议是一个低成本的接口,经过了优化,降低了功耗和接口复杂性。APB接口是简单,同步,无流水线的协议。每次传输花费至少两个周期完成。
APB接口设计是为了访问外围设备中的可编程控制寄存器。APB外设通常使用APB桥连接到内存系统。例如,AXI2APB桥可以用来连接一些APB外设到AXI内存系统。
APB传输是由APB桥发起。APB桥可以被认为是请求者。外设接口对于请求者响应。APB外设可以被认为是完成者。协议规范将需要请求者和完成者。
二、APB接口信号
signal | source | width | description |
PCLK | clock | 1 | PCLK is a clock signal. All APB signals are timed against the rising edge of PCLK |
PRSETn | system bus reset | 1 | PRESETn is the reset signal and is active-LOW PRESETn is normally connected directly to the |
PADDR | requester | ADDR_WIDTH | PADDR is the APB address bus. PADDR can be up to 32 bits wide |
PSELx | requester | 1 | The Requester generates a PSELx signal for each Completer. PSELx indicates that the Completer is selected and that a data transfer is required |
PENABLE | requester | 1 | PENABLE indicates the second and subsequent cycles of an APB transfer |
PWRITE | requester | 1 | PWRITE indicates an APB write access when HIGH and an APB read access when LOW |
PWDATA | requester | DATA_WIDTH | The PWDATA write data bus is driven by the APB bridge Requester during write cycles when PWRITE is HIGH. PWDATA can be 8, 16, or 32 bits wide |
PRDATA | completer | DATA_WIDTH | The PRDATA read data bus is driven by the selected Completer during read cycles when PWRITE is LOW. PRDATA can be 8, 16, or 32 bits wide |
PREADY | completer | 1 | PREADY is used to extend an APB transfer by the Completer |
PSLVERR | completer | 1 | PSLVERR is an optional signal that can be asserted HIGH by the Completer to indicate an error condition on an APB transfer |
PPROT | requester | 3 | PPROT indicates the normal, privileged, or secure protection level of the transaction and whether the transaction is a data access or an instruction access |
PSTRB | requester | DATA_WIDTH/8 | PSTRB indicates which byte lanes to update PSTRB[n] corresponds to PWDATA[(8n + 7):(8n)]. |
有些文章也将requester,completer说成master,slaver,意思是一样的。
- APB2 :AMBA 2 APB
- APB3 :AMBA 3 APB,比APB2多两个信号(PREADY,PSLVERR)
- APB4 :AMBA 4 APB,比APB3多两个信号(PPROT,PSTRB)
特别说明:
写传输时,如果收到了错误信息,并不意味着外设中的寄存器没有被更新。
读传输时,如果收到了错误信号,数据是无效的。对于读到错误信号,不需要外设驱动数据总线置0。收到错误信号的主机仍然可以使用数据,从机不能依赖错误信号阻止对PRDATA的读取。
PPROT[2:0] | 保护等级 |
[0] | 1:私有传输 0:一般传输 |
[1] | 1:不安全传输 0:安全传输 |
[2] | 1:指令传输 0:数据传输 |
PSTRB 就是判断哪个字节有效的信号,手册上很专业的称为“稀疏数据传输” 。
三、传输
1、写传输无等待
2、写传输有等待
3、读传输无等待
4、读传输有等待
5、带有错误信号的写传输
6、带有错误信号的读传输
平时几乎用到的都是APB3,很少用到PPROT和PSTRB。具体情况具体分析吧
四、apb_slave实现
1、代码如下:
module apb_slave(
input wire pclk,
input wire presetn,
input wire pwrite,
input wire psel,
input wire penable,
input wire [31:0] paddr,
input wire [31:0] pwdata,
output reg [31:0] prdata,
output wire pready
);
parameter REG1_ADDR = 8'h00;
parameter REG2_ADDR = 8'h04;
parameter REG3_ADDR = 8'h08;
reg [31:0] reg1;
reg [31:0] reg2;
reg [31:0] reg3;
reg [31:0] invld_reg;
wire apb_write_vld;
wire apb_read_vld;
assign pready = 1'b1;
assign apb_write_vld = pwrite && psel && penable;
assign apb_read_vld = (!pwrite) && psel && penable;
always@(posedge pclk or negedge presetn)begin
if(!presetn)begin
reg1 <= 32'd0;
reg2 <= 32'd0;
reg3 <= 32'd0;
end
else if(apb_write_vld)begin
case(paddr[7:0])
REG1_ADDR : reg1 <= pwdata;
REG2_ADDR : reg2 <= pwdata;
REG3_ADDR : reg3 <= pwdata;
default : invld_reg <= pwdata;
endcase
end
end
always@(*)begin
if(apb_read_vld)begin
case(paddr[7:0])
REG1_ADDR : prdata <= reg1;
REG2_ADDR : prdata <= reg2;
REG3_ADDR : prdata <= reg3;
default : prdata <= invld_reg;
endcase
end
else
prdata <= 32'd0;
end
endmodule
2、testbench如下
`timescale 1ns/1ns;
module apb_slave_tb();
reg pclk;
reg presetn;
reg pwrite;
reg psel;
reg penable;
reg [31:0] paddr;
reg [31:0] pwdata;
wire pready;
wire [31:0] prdata;
reg [31:0] rdata;
parameter REG1_ADDR = 8'h00;
parameter REG2_ADDR = 8'h04;
parameter REG3_ADDR = 8'h08;
always #10 pclk = ~pclk;
initial begin
pclk = 1'b0;
presetn <= 1'b0;
pwrite <= 1'b0;
psel <= 1'b0;
penable <= 1'b0;
paddr <= 32'd0;
pwdata <= 32'd0;
#123;
presetn <= 1'b1;
apb_write(REG1_ADDR,32'h33445566);
apb_read(REG1_ADDR,rdata);
#66;
apb_write(REG2_ADDR,32'haabbccdd);
apb_read(REG2_ADDR,rdata);
#66;
apb_write(REG3_ADDR,32'ha1b2c3d4);
apb_read(REG3_ADDR,rdata);
#1000;
$finish;
end
task apb_write(input [7:0] addr,input [31:0] wdata);
begin
@(posedge pclk);
pwrite <= 1'b1;
psel <= 1'b1;
penable <= 1'b0;
paddr <= {24'd0,addr};
pwdata <= wdata;
@(posedge pclk);
penable <= 1'b1;
@(posedge pclk);
pwrite <= 1'b0;
psel <= 1'b0;
penable <= 1'b0;
paddr <= 32'd0;
pwdata <= 32'd0;
@(posedge pclk);
end
endtask
task apb_read(input [7:0] addr,output [31:0] rdata);
begin
@(posedge pclk);
pwrite <= 1'b0;
psel <= 1'b1;
penable <= 1'b0;
paddr <= {24'd0,addr};
@(posedge pclk);
penable <= 1'b1;
@(posedge pclk);
rdata <= prdata;
psel <= 1'b0;
paddr <= 32'd0;
@(posedge pclk);
end
endtask
apb_slave apb_slave_inst(
.pclk (pclk),
.presetn (presetn),
.pwrite (pwrite),
.psel (psel),
.penable (penable),
.paddr (paddr),
.pwdata (pwdata),
.prdata (prdata),
.pready (pready)
);
initial begin
$fsdbDumpfile("tb.fsdb");
$fsdbDumpvars;
end
endmodule
这里需要注意,时钟阻塞赋值,其他信号非阻塞赋值,这样才能保证时钟沿采样到的信号为上一刻的值。与设计思路相符合。
3、仿真结果