完整程序:点击下载
官方手册:点击下载
脚 本:makefile
工 具:vcs & verdi
路 线:
- 【verilog实战】同步FIFO的设计与功能验证(附源码)
- 【Verilog实战】异步FIFO设计和功能验证(附源码)
- 【Verilog实战】UART通信协议,半双工通信方式(附源码)
- 【Verilog实战】SPI协议接口设计和功能验证(附源码)
- 【Verilog实战】AMBA 3 APB接口设计和功能验证(附源码)
- 【Verilog实战】AMBA AHB接口设计和功能验证(附源码)
- 【Verilog实战】AMBA AXI接口设计和功能验证(附源码)
- 【Verilog实战】UART2APB bridge 设计和功能验证(附源码)
- 【Verilog实战】AHB2APB bridge 设计和功能验证(附源码)
APB2 :AMBA 2 APB
APB3 :AMBA 3 APB,比 APB2 多两个信号(PREADY , PSLVERR)
APB4 :AMBA 4 APB,比 APB3 多两个信号(PPROT , PSTRB )
APB(Advanced Peripheral Bus),高级外设总线。APB总线协议是ARM公司提出的AMBA总线结构之一,是一种片上总线结构。
APB主要用于低带宽的周边外设之间的连接,例如UART、IIC等,它的总线架构不像AHB支持多个主模块,在APB里面没有仲裁器,APB 桥是唯一的主模块。其特点:低带宽;高性能;非流水作业,至少需要两个时钟周期传输,且数据均在时钟上升沿变化;无需等待周期和回应信号;控制逻辑简单,只有四个控制信号,且APB上的传输可采用状态机表示。
1.2.1 Interface
Signal | Source | Description |
---|---|---|
PCLK | Clock source | Clock. The rising edge of PCLK times all transfers on the APB. |
PRESETn | System bus equivalent | Reset. The APB reset signal is active LOW. This signal is normally connected directly to the system bus reset signal. |
PADDR | APB bridge | Address. This is the APB address bus. It can be up to 32 bits wide and is driven by the peripheral bus bridge unit. |
PSELx | APB bridge | Select. The APB bridge unit generates this signal to each peripheral bus slave.It indicates that the slave device is selected and that a data transfer is required.There is a PSELx signal for each slave. |
PENABLE | APB bridge | Enable. This signal indicates the second and subsequent cycles of an APB transfer. |
PWRITE | APB bridge | Direction. This signal indicates an APB write access when HIGH and an APB read access when LOW. |
PWDATA | APB bridge | Write data. This bus is driven by the peripheral bus bridge unit during write cycles when PWRITE is HIGH. This bus can be up to 32 bits wide. |
PRDATA | Slave interface | Read Data. The selected slave drives this bus during read cycles when |
由标准协议手册接口描述可以知道,读\写地址共用地址总线,PADDR\PWDATA\PRDATA总线的最大宽度都是32bit。
1.3.1 Interface(+APB2)
Signal | Source | Description |
---|---|---|
PREADY |
Slave interface | Ready. The slave uses this signal to extend an APB transfer. |
PSLVERR |
Slave interface | This signal indicates a transfer failure. APB peripherals are not required to support the PSLVERR pin. This is true for both existing and new APB peripheral designs. Where a peripheral does not include this pin then the appropriate input to the APB bridge is tied LOW. |
1.3.2 Timing
- 无错误传输的情形
(4)有等待的读操作
1. 在T0~T1阶段,所有总线处于IDLE状态。
2. 在T1~T2阶段,第一个时钟周期,处在Setup phase状态。T1时刻,PADDR、PWRITE、PWDATA变化,PSEL拉高,即Master把这些数据发送到总线上。
3. 在T2~T3阶段,第二个时钟周期,处在Access phase状态。T2时刻,这个时候对应的Slave接收到Master发送过来的地址和读控制命令,此时Slave得知自己要将这个地址反馈给Master了。
4. T3时刻,采到 PENABLE 为高电平,但是 PREADY 为低电平,表示Slave数据还没准备好。
5. T5时刻,采到 PENABLE & PREADY 为高电平,表示Master这一时刻之前一次读操作已经完成,Master回到IDLE状态。
可以使用PSLVERR来指示APB传输错误。当PSEL, PENABLE以及PREADY 都为高时,PSLVERR才在最后一个周期进行判断。当任何一个PSEL, PENABLE或者PREADY为低时,你可以将PSLVERR拉低,这是推荐,并不是强制要求。事物处理收到一个错误后,可能或不可能改变外围器件的状态。APB外围设备不要求必须支持PSLVERR引脚,当不使用该引脚时,应被置低。
1.4.1 Interface(+APB2)
Signal | Source | Description |
---|---|---|
PPROT |
APB bridge | Protection type. This signal indicates the normal, privileged, or secure protection level of the transaction and whether the transaction is a data access or an instruction access. |
PSTRB |
APB bridge | Write strobes. This signal indicates which byte lanes to update during a write transfer. There is one write strobe for each eight bits of the write data bus. Therefore, PSTRB[n] corresponds to PWDATA[(8n + 7):(8n)]. Write strobes must not be active during a read transfer. |
PREADY | Slave interface | Ready. The slave uses this signal to extend an APB transfer. |
PSLVERR | Slave interface | This signal indicates a transfer failure. APB peripherals are not required to support the PSLVERR pin. This is true for both existing and new APB peripheral designs. Where a peripheral does not include this pin then the appropriate input to the APB bridge is tied LOW. |
由官方手册可以看到,PSTRB信号可以使写数据总线上的数据片段化,即指示数据总线上那几个字节数据有效。由于数据位最大为32bit,即4个字节。因此PSTRB位宽为4位,每一位对应一个字节,设置为1时,指示该字节数据有效。注意的是,在读操作中,PSTRB所有位必须设置为低电平。
为了⽀持复杂系统设计,使⽤PPROT[2:0]来标识合法的互联和传输,抢占式总线很容易被监听,因此总线保护⾮常有必要。APB总线提供三层访问保护,PPROT[0]表⽰正常或私有传输,PPROT[1]表⽰安全或不安全传输,PPROT[2]表⽰数据还是指令传输,具体如下表所⽰:
PPROT[2:0] | 保护等级 |
---|---|
[0] | 1:私有传输 0:一般传输 |
[1] | 1:不安全传输 0:安全传输 |
[2] | 1:指令传输 0:数据传输 |
apb桥实现了将上游的数据转换成AMBA 3 APB协议发送出去,并接收读到的数据。
Signal | Width | Direction | Description |
---|---|---|---|
pclk_i | 1 | input | APB时钟,50MHz |
prst_n_i | 1 | input | APB复位信号 |
cmd_i | 56 | input | 上游的数据 [55:48]:读写指示位 [47:32]:地址位 [31:0]:数据数据位 |
cmd_vld_i | 1 | input | 数据有效指示信号 |
paddr_o | 16 | output | 地址总线,最大32位,这里规定为16位 |
psel_o | 1 | output | slave片选信号,1:选中;0:不选中 |
penable_o | 1 | output | 使能信号,1:使能;0:不使能 |
pwrite_o | 1 | output | 读写指示,1:写操作;0:读操作 |
pwdata_o | 32 | output | 写数据总线,最大为32bit,这里设定为32bit |
prdata_o | 32 | output | 读数据总线,最大为32bit,这里设定为32bit |
pready_i | 1 | input | APB转移扩展信号,低电平扩展,高电平结束 |
pslverr_i | 1 | input | 错误指示信号,这里不用,接低电平 |
(1)Write
(2)Read
/*-------------------------------------------------------------
-- 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
#(
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 ,
input pslverr_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 && pready_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 && pready_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
case(cur_state)
IDLE :if(start_flag)begin
nxt_state = SETUP;
end
else begin
nxt_state = IDLE;
end
SETUP :nxt_state = ACCESS;
ACCESS:if (!pready_i)begin
nxt_state = ACCESS;
end
else if(start_flag)begin
nxt_state = SETUP;
end
else if(!cmd_vld_i && pready_i)begin
nxt_state = IDLE;
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 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
/*-----------------------------------------------\
-- 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
//-- modified by xlinxdu, 2022/05/28
`timescale 1ns/1ns
module tb_apb;
reg pclk_i ;
reg prst_n_i ;
reg [55:0] cmd_i ;
reg cmd_vld_i ;
wire [31:0] cmd_rd_data_o;
wire [15:0] paddr_o ;
wire pwrite_o ;
wire psel_o ;
wire penable_o ;
wire [31:0] pwdata_o ;
reg [31:0] prdata_i ;
reg pready_i ;
reg pslverr_i ;
initial begin
// rst;
pclk_i = 0;
prst_n_i = 1;
pslverr_i = 0;
cmd_i = 56'b0;
cmd_vld_i = 0;
prdata_i = 32'b0;
pready_i = 1;
#20 prst_n_i = 0;
#20 prst_n_i = 1;
// cmd_in_wr(cmd_i,56'h01_FF_EE_DD_CC_BB_AA);
cmd_i = 56'h01_FF_EE_DD_CC_BB_AA;
cmd_vld_i = 1 ;
#20 cmd_vld_i = 0;
#30 pready_i = 0;
#80 pready_i = 1;
#90;
//cmd_in_rd(cmd_i,56'h00_AA_BB_CC_DD_EE_FF,prdata_i,32'h12_34_56_78);
cmd_i = 56'h00_AA_BB_CC_DD_EE_FF;
cmd_vld_i = 1;
#20 cmd_vld_i = 0;
#30 pready_i = 0;
#60 pready_i = 1;
prdata_i = 32'h12_34_56_78;
cmd_i = 56'h00_AA_BB_CC_DD_EE_FF;
cmd_vld_i = 1;
#20 cmd_vld_i = 0;
#30 pready_i = 0;
#50 pready_i = 1;
prdata_i = 32'h11_22_33_44;
end
always #10 pclk_i = ~pclk_i;
//-- RST
task rst;
begin
pclk_i = 1;
prst_n_i = 1;
pslverr_i = 0;
cmd_i = 56'b0;
cmd_vld_i = 0;
prdata_i = 32'b0;
pready_i = 1;
#20 prst_n_i = 0;
#10 prst_n_i = 1;
//cmd_i = 56'h01_FF_EE_DD_CC_BB_Ab;
end
endtask
//-- write
task cmd_in_wr;
output [55:0] cmd;
input [55:0] data;
begin
cmd = data;
cmd_vld_i = 1 ;
#20 cmd_vld_i = 0;
#20 pready_i = 0;
#40 pready_i = 1;
end
endtask
//-- read
task cmd_in_rd;
output [55:0] cmd;
input [55:0] data ;
output [31:0] prdata;
input [31:0] rd_data;
begin
cmd = data;
cmd_vld_i = 1;
#20 cmd_vld_i = 0;
#20 pready_i = 0;
#40 pready_i = 1;
prdata = rd_data;
end
endtask
initial begin
#1000 $finish;
end
apb tb_apb(
.pclk_i (pclk_i ),
.prst_n_i (prst_n_i ),
.cmd_i (cmd_i ),
.cmd_vld_i (cmd_vld_i ),
.cmd_rd_data_o(cmd_rd_data_o),
.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 ),
.pslverr_i (pslverr_i )
);
initial begin
$fsdbDumpfile("apb.fsdb");
$fsdbDumpvars ;
$fsdbDumpMDA ;
end
endmodule
bug1:由仿真波形可以看到,当前状态cur_state和下一个状态nxt_state、prdata_i和cmd_data_buf在同一拍变化,但是设计中两者采用的是时序逻辑,应该会错一拍更新。
由官方手册可以看到,这两个输入信号在T4时刻之后一点才拉高,而搭建的testbench中,他们是同一时刻变化,这时候,T4会采到一个亚稳态数据,导致出错,因此在tb中改一下这里即可。
// cmd_in_wr(cmd_i,56'h01_FF_EE_DD_CC_BB_AA);
cmd_i = 56'h01_FF_EE_DD_CC_BB_AA;
cmd_vld_i = 1 ;
#20 cmd_vld_i = 0;
#30 pready_i = 0;
#80 pready_i = 1;
更改为:pready_i多延迟1ns输入,错开和时钟同拍
// cmd_in_wr(cmd_i,56'h01_FF_EE_DD_CC_BB_AA);
cmd_i = 56'h01_FF_EE_DD_CC_BB_AA;
cmd_vld_i = 1 ;
#20 cmd_vld_i = 0;
#31 pready_i = 0;
#80 pready_i = 1;
由这张图可以看到nxt_state产生了一个中间态IDLE,这是由于三段状态机的第二段状态翻转采用的是组合逻辑,但由于cur_state和nxt_state之间是时序关系,等时钟沿来临的时候,早已经稳定,因此并未影响正确的逻辑。在硬件上的表现是波形出现毛刺
(1)有等待的读操作
(2)无等待的读操作
作者:xlinxdu
版权:本文版权归作者所有
转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。