The reference for this article is the official document AMBA™3 APB Protocol specification
Document download address: https://pan.baidu.com/s/1Vsj4RdyCLan6jE-quAsEuw?pwd=w5bi
Extraction code: w5bi
APB port introduction
Before introducing the specific handshake rules of the bus, you need to familiarize yourself with the APB bus ports. The ports of the APB are as follows:
![](https://img-blog.csdnimg.cn/img_convert/88377971c91320e1401b20213ad270a4.png)
It can be roughly divided into the following three groups:
System signal : PCLK (system clock), PRESETn (system reset, low effective)
Master signal : PADDR (address signal, determine the address for reading and writing), PSELx (chip select signal, pulled out to connect to the slave equipped with APB bus, when the slave is selected, the PSELx signal is pulled high), PNEABLE (enable signal, pulled on PSELx After a cycle of high, it must be pulled high), PWRITE (write enable signal, write is valid when PWRITE is high , and read is valid when it is low ), PWDATA (write data)
Slave signal : PREADY (when ready is high, it represents the end of an APB data transmission), PRDATA (read data), PSLVERR (error data, sent by the slave, the specific logic is determined by the slave internally, when the slave finds that the internal logic fails, For example, if the status of the state machine is wrong, the number of the counter is abnormal, etc., the slave can use the internal logic to pull the signal high, so that when the master receives PSLVERR high, even if the ready pull high indicates that the APB is over, the master can also give up the transmission or develop other coping strategies).
APB write transfer
![](https://img-blog.csdnimg.cn/img_convert/f0392b022aa939b1557417628871b32b.png)
As shown in the document, APB writing is divided into two situations: ①writing without waiting state ; ②writing with waiting state .
APB和AHB最大的不同就是APB不采用pipeline形式的写读方式,因此对于APB协议来说,最快的写入或者读出一个数据的周期是两周期,先给地址,再写数据;或者先给地址,再读数据。APB 协议文档中,将上述这种传输方式分为两个阶段(phase),给地址的阶段称为Set up phase;紧接着下一周期PENABLE信号拉高,标志着进入写/读数据的阶段,该阶段称为Access phase。
Write with no wait states
![](https://img-blog.csdnimg.cn/img_convert/fb498ae2786a011df2dcc2077e0b2949.png)
一次没有等待状态的写传输如上图所示,计划写数据时,第一周期PSEL拉高,表示选中某个slave,同时给出地址信息Addr1和写入数据信息Data1,紧接着下一周期,PENABLE信号拉高,PREADY信号也拉高,这时数据写入完成。
![](https://img-blog.csdnimg.cn/img_convert/e4dbc9fded1bbad0b5a40dd36edb2989.png)
没有等待状态的APB连续写波形如上所示(代码见后文),笔者将数据分为了两组,group1为APB slave的端口信号,group2为APB接的单端口SRAM信号。在第一个周期,也就是Setup phase,psel信号拉高,表示slave被选中,值得注意的是此时要将SRAM的写信号和使能信号同步拉高,因为我们写的是一个no wait states的APB接口,数据要在第二周期写进SRAM的话,就需要提前一拍拉高使能信号和写信号。然后到了第二周期,penable信号拉高,pready信号也拉高标志着这一次APB传输的结束。另外,也正是因为在setup phase我们把SRAM的en信号和we信号拉高了,因此在access phase数据传输结束的同时,数据也被写入到SRAM中。
Write with wait states
![](https://img-blog.csdnimg.cn/img_convert/dd4272c7a9cbfa43ebd3e17c136694b1.png)
![](https://img-blog.csdnimg.cn/img_convert/59c8f64a5e8344d8fb58264f197e7ea8.png)
在文档中,对有等待周期的APB写传输描述如上,即:
一开始的setup phase和write with no wait没有区别,psel拉高,penable为低;紧跟着第二周期,penable拉高之后,进入access phase,进入access phase之后,penable不会拉低,直到pready为高标志着一次传输结束时,penable才会随着pready一起拉低。penable等待pready拉高的这段等待时间为additional cycles,在这个阶段PADDR、PWRITE、PSEL、PENABLE、PWDATA都应该保持不变,可以说总线被hold住了。
APB读传输
![](https://img-blog.csdnimg.cn/img_convert/dfe65126e2ee26f1f02685de66e2f694.png)
APB的读传输也分为两种情况:①没有等待状态的读;②有等待状态的读。
Read with no wait states
![](https://img-blog.csdnimg.cn/img_convert/f921ebb64160f38b1570bdd903342f66.png)
一次没有等待状态的读传输如上图所示,读状态和写状态不同,写数据时PWRITE=1,读数据时应该令PWRITE=0计划读数据时,第一周期PSEL拉高,表示选中某个slave,同时给出地址信息Addr1,紧接着下一周期,PENABLE信号拉高,PREADY信号也拉高,这时数据被读出,master接受到读出数据PRDATA。
![](https://img-blog.csdnimg.cn/img_convert/3b4410e66e773b33faa34f40efcc9a76.png)
上图为连续读的APB传输波形图,从第一次读数据可以看到,随着psel信号拉高,PWRITE=0标志着为读状态,此时传入地址给APB的SRAM,SRAM端口en=1,we=0标志着SRAM为读模式,数据在下一周期从SRAM给到prdata。
这边还要提一个APB的特点,也是大多人容易忽略的点,APB总线完成一次读传输或者写传输之后,PADDR和PWRITE不会改变,会一直维持到下一次的传输,这可以减少功耗。spec中描述如下:
![](https://img-blog.csdnimg.cn/img_convert/56ea21467ef9ee54e0ff97ace9807697.png)
手撕代码
笔者写了一个Write和Read都是with no states的APB SRAM,因为含有SRAM部分,所以在apb_sram中需要例化一个单端口ram,单端口ram代码如下:
dpram
module spram_generic#(
parameter ADDR_BITS = 7, //outside input 10
parameter ADDR_AMOUNT = 128, //outside input 1024
parameter DATA_BITS = 32 //outside input 32
)(
input clk ,
input en ,
input we ,
input [ADDR_BITS-1:0] addr ,
input [DATA_BITS-1:0] din ,
output reg [DATA_BITS-1:0] dout
);
reg [DATA_BITS-1:0] mem [0:ADDR_AMOUNT-1];
always @(posedge clk)begin
if(en)begin
if(we == 1'b1)begin
mem[addr] <= din;
end
else
dout <= mem[addr];
end
end
endmodule
apb_sram
module apb_sram#(
parameter ADDR_BITS = 9,
parameter DATA_BITS = 32,
parameter MEM_DEPTH = 512
)(
input pclk ,
input prstn ,
input psel ,
input penable ,
input [ADDR_BITS-1:0] paddr ,
input pwrite ,
input [DATA_BITS-1:0] pwdata ,
output pready ,
output [DATA_BITS-1:0] prdata
);
// write part
wire apb_write_setup;
reg apb_ram_write;
assign apb_write_setup = (pwrite) && (!penable) && (psel);
always @(posedge pclk or negedge prstn)begin
if(!prstn)begin
apb_ram_write <= 1'b0;
end
else if(apb_write_setup)begin
apb_ram_write <= 1'b1;
end
else if(pready)begin
apb_ram_write <= 1'b0;
end
end
// read part
wire apb_read_setup;
reg apb_ram_read;
assign apb_read_setup = (!pwrite) && (!penable) && (psel);
always @(posedge pclk or negedge prstn)begin
if(!prstn)begin
apb_ram_read <= 1'b0;
end
else if(apb_read_setup)begin
apb_ram_read <= 1'b1;
end
else if(pready)begin
apb_ram_read <= 1'b0;
end
end
assign pready = pwrite ? apb_ram_write : apb_ram_read;
wire mem_en,mem_we;
assign mem_en = apb_write_setup || apb_read_setup;
assign mem_we = apb_write_setup;
spram_generic #(
.ADDR_BITS (ADDR_BITS ),
.DATA_BITS (DATA_BITS ),
.ADDR_AMOUNT (2<<(ADDR_BITS-1) )
)u_spram_generic(
.clk (pclk ),
.en (mem_en ),
.we (mem_we ),
.addr (paddr ),
.din (pwdata ),
.dout (prdata )
);
endmodule
tb
testbench例化apb_sram并给出激励,我这边在tb中发起了10次连续的随机写,然后再发起10次连续读,发现读出来的数据和写入的数据一致。
接着又测试了写和读无缝衔接在一起的apb传输,结果符合spec。tb代码如下:
`timescale 1ns/1ns
`define MEM_PATH u_apb_sram.u_spram_generic
module tb#(
parameter ADDR_BITS = 9,
parameter DATA_BITS = 32,
parameter MEM_DEPTH = 512
)();
reg clk, rstn;
always #5 clk = ~clk;
reg psel, penable, pwrite;
reg [DATA_BITS-1:0] pwdata, ref_data;
reg [ADDR_BITS-1:0] paddr ;
wire pready;
wire [DATA_BITS-1:0] prdata;
reg [DATA_BITS-1:0] pwdata_rand;
reg [DATA_BITS-1:0] prdata_read;
task apb_write;
input [ADDR_BITS-1:0] addr;
input [DATA_BITS-1:0] wdata;
begin
@(posedge clk);#1;
penable = 0; psel = 1; pwrite = 1; paddr = addr; pwdata = wdata;
@(posedge clk);#1;
penable = 1;
end
endtask
task apb_read;
input [ADDR_BITS-1:0] addr;
output [DATA_BITS-1:0] rdata;
begin
@(posedge clk); #1;
penable = 0; psel = 1; pwrite = 0; paddr = addr;
@(posedge clk); #1;
penable = 1;
@(negedge clk); #1;
rdata = prdata;
end
endtask
integer i,j;
initial begin
clk <= 1'b0;
rstn <= 1'b0;
pwrite <= 1'b1;
psel <= 1'b0;
penable <= 1'b0;
pwdata <= 32'd0;
repeat(2) @(posedge clk);
rstn <= 1'b1;
repeat(3) @(posedge clk);
// SRAM data initial
for (i = 0; i < MEM_DEPTH; i = i + 1)begin
pwdata = $random();
`MEM_PATH.mem[i] = pwdata;
end
repeat(5) @(posedge clk); #1;
$display("\ncontinuous writing");
// SRAM data continuous writing
fork
begin
@(posedge clk);#1
paddr = 32'd0;
for (j = 0; j < 10; j = j + 1)begin
repeat(2) @(posedge clk) #1;
paddr = paddr + 1;
@(negedge clk) #1;
ref_data = `MEM_PATH.mem[paddr-1];
$display("ref_data = %d, addr = %d", ref_data, paddr-1);
end
end
begin
for (i = 0; i < 10; i = i + 1)begin
pwdata_rand = $random();
apb_write(paddr, pwdata_rand);
$display("pwdata = %d", pwdata);
end
end
join_none
repeat(21) @(posedge clk);#1;
penable = 1'b0;psel = 1'b0;pwrite = 1'b0;
repeat(5) @(posedge clk);#1;
$display("\ncontinuous reading");
//SRAM continuous reading
fork
begin
@(posedge clk);#1;
paddr = 32'd0;
for (j = 0; j < 10; j = j + 1)begin
repeat(2) @(posedge clk);#1;
paddr = paddr + 1;
end
end
begin
for (i = 0; i < 10; i = i + 1)begin
apb_read(paddr, prdata_read);
$display("prdata_read = %d", prdata_read);
end
end
join
penable = 0;psel = 0;
repeat(5) @(posedge clk);#1;
$display("\ncontinuos writing and reading");
//SRAM continuous write and read
fork
begin
@(posedge clk);#1;
paddr = 32'd0;
for (j = 0; j < 10; j = j + 1)begin
repeat (4) @(posedge clk); #1;
paddr = paddr + 1;
end
end
begin
for (i = 0; i < 10; i = i + 1)begin
pwdata_rand = $random();
apb_write(paddr, pwdata_rand);
apb_read(paddr, prdata_read);
$display("write data is %d, read data is %d", pwdata_rand, prdata_read);
end
end
join
penable = 0;psel = 0;
// finish simulation
repeat(20) @(posedge clk);
$finish();
end
initial begin
$fsdbDumpfile("apb_sram.fsdb");
$fsdbDumpvars(0);
end
apb_sram #(
.ADDR_BITS(ADDR_BITS),
.DATA_BITS(DATA_BITS),
.MEM_DEPTH(MEM_DEPTH)
) u_apb_sram(
.pclk (clk ),
.prstn (rstn ),
.psel (psel ),
.penable(penable),
.paddr (paddr ),
.pwrite (pwrite ),
.pwdata (pwdata ),
.pready (pready ),
.prdata (prdata )
);
endmodule
vcs仿真结果如下:
continuous writing
pwdata = 620927818
ref_data = 620927818, addr = 0
pwdata = 1557269945
ref_data = 1557269945, addr = 1
pwdata = 160312595
ref_data = 160312595, addr = 2
pwdata = 164115731
ref_data = 164115731, addr = 3
pwdata = 853295461
ref_data = 853295461, addr = 4
pwdata = 684074833
ref_data = 684074833, addr = 5
pwdata = 3684186807
ref_data = 3684186807, addr = 6
pwdata = 3432517785
ref_data = 3432517785, addr = 7
pwdata = 2635204666
ref_data = 2635204666, addr = 8
pwdata = 3102358129
ref_data = 3102358129, addr = 9
continuous reading
prdata_read = 620927818
prdata_read = 1557269945
prdata_read = 160312595
prdata_read = 164115731
prdata_read = 853295461
prdata_read = 684074833
prdata_read = 3684186807
prdata_read = 3432517785
prdata_read = 2635204666
prdata_read = 3102358129
continuos writing and reading
write data is 830211938, read data is 830211938
write data is 4063587044, read data is 4063587044
write data is 353623338, read data is 353623338
write data is 3201975421, read data is 3201975421
write data is 753819481, read data is 753819481
write data is 1925424101, read data is 1925424101
write data is 1994288109, read data is 1994288109
write data is 3836215497, read data is 3836215497
write data is 2695810113, read data is 2695810113
write data is 1472319919, read data is 1472319919
波形图
连续10次写、连续10次读、连续10次读写波形如下
![](https://img-blog.csdnimg.cn/img_convert/ae8dabf7c5deb62355135e0c9f1ad208.png)