1、APB总线简介
APB:Advanced Peripheral Bus,高级外设总线,具备以下特性:
(1)低功耗;
(2)接口协议简单;
(3)总线传输使用时钟上升沿进行,便于时序分析;
(4)应用广泛,支持多种外设。
所有的APB模块均是APB从机。
2、APB信号列表
所有的APB总线信号都以字母P作为前缀,下表列出了APB信号的名称以及对信号的描述:
Name | Width | I/O | Description |
---|---|---|---|
PCLK | 1bit | In | APB总线时钟,所有传输只能发生在PCLK的上升沿 |
PRESETn | 1bit | In | APB总线复位信号,低电平有效 |
PADDR | 32bit | In | APB地址总线 |
PSEL | 1bit | In | APB从机片选信号 |
PENABLE | 1bit | In | APB选通信号,高电平表示APB传输的第二个周期 |
PWRITE | 1bit | In | APB读写控制信号,高表示写,低表示读 |
PRDATA | 32bit | Out | APB读数据信号,最高为32位 |
PWDATA | 32bit | In | APB写数据信号,最高为32位 |
3、APB总线时序
(1)写时序
写传输开始于T2时刻,在改时钟上升沿时刻,地址、写信号、PSEL、写数据信号同时发生变化,T2时钟,即传输的第一个时钟被称为SETUP周期。在下个时钟上升沿T3,PENABLE信号拉高,表示ENABLE周期,在该周期内,数据、地址以及控制信号都必须保持有效。整个写传输在这个周期结束时完成。
(2)读时序
读传输开始于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的测试分成连个部分,一个是每进行一次写传输后,紧接着进行同地址的读传输,让后对比读写结果一致性;另一个测试是在连续写一段地址后,再全部读出改地址段的数据,完成读操作后进行数据比对。
下面是仿真打印信息
APB写传输时序的仿真波形如下:
APB写传输时序的仿真波形如下:
评论 (0)