作者: Ramsey Wang,来源:易灵思FPGA技术交流
APB3是一个低功耗低成本接口。所有信号在时钟上升沿传输,每次传输需要两个时钟周期。
1、Interface
APB写过程
没有等待状态。
(1)T0到T1阶段是空闲状态,
(2)T1到T2是setup阶段,此阶段会准备好PADDR,PWRITE(为1),PWDATA。
(3)T2到T3是Access阶段,此阶段PENABLE会拉高,并且地址、数据和控制信号仍然保持有效。
(4)T3到T4阶段PENABLE再次拉低;选择信号PSELx也会拉低,除非紧跟同一外设下一次的传输。
有等待状态
在ACESS阶段,当PENABLE为高时,可以通过PREADY拉低来延长ACESS阶段。这时要保持PADDR,PWRITE,PSEL,PENABLE,PWDATA信号保持不变。
当PENABLE为低时,PREADY可以高也可以低。所以如果外设是固定两个操作周期时,PREADY可以固定为高。
另外推荐地址和写信号只在下一个访问周期才发生变化,这样可以节省功耗 。
APB读过程
读操作
在SETUP阶段读过程与写过程是一样的,只是写过程PWRITE为高,读过程PWRITE为低。
同样在ACCESS阶段,也可以通过拉低PREADY信号延长ACESS阶段,但是要保证PADDR,PWRITE,PSEL和PENALBE为固定状态。
通过RISCV 操作APB3也比较简单,如下:
slave是指APB的基地址,addr是指APB的偏移地址,也就是PADDR.
void apb3_write(u32 slave, u32 addr, u32 data )
{
write_u32(data,slave+addr);
}
void abp3_read(u32 slave, u32 addr)
{
return read_u32(slave+addr);
}
注意在操作时由于数据是32位位宽,而地址是对应字节的,所以每次地址要增加4。
在逻辑上处理也比较简单,易灵思提供了简单的APB3参考。
//以下为易灵思提供的APB3的参考
module apb3_slave_memory #(
// user parameter starts here
//
parameter ADDR_WIDTH = 16,
parameter DATA_WIDTH = 32,
parameter NUM_REG = 4
) (
// user logic starts here
input clk,
input resetn,
input [ADDR_WIDTH-1:0] PADDR,
input PSEL,
input PENABLE,
output PREADY,
input PWRITE,
input [DATA_WIDTH-1:0] PWDATA,
output [DATA_WIDTH-1:0] PRDATA,
output PSLVERROR
);
/////////////////////////////////////////////////////////////////
localparam [1:0] IDLE = 2'b00,
SETUP = 2'b01,
ACCESS = 2'b10;
reg [1:0] busState,
busNext;
reg slaveReady;
wire actWrite,
actRead;
//////////////////////////////////////////////////////////////////
always@(posedge clk or negedge resetn)
begin
if(!resetn)
busState <= IDLE;
else
busState <= busNext;
end
always@(*)
begin
busNext = busState;
case(busState)
IDLE:
begin
if(PSEL && !PENABLE)
busNext = SETUP;
else
busNext = IDLE;
end
SETUP:
begin
if(PSEL && PENABLE)
busNext = ACCESS;
else
busNext = IDLE;
end
ACCESS:
begin
if(PREADY)
busNext = IDLE;
else
busNext = ACCESS;
end
default:
begin
busNext = IDLE;
end
endcase
end
assign actWrite = PWRITE && (busState == ACCESS);
assign actRead = !PWRITE && (busState == ACCESS);
assign PSLVERROR = 1'b0;
assign PREADY = slaveReady & & (busState !== IDLE);
always@ (posedge clk)
begin
slaveReady <= actWrite | actRead;
end
simple_dual_port_ram
#(
.DATA_WIDTH(32),
.ADDR_WIDTH(16),
.OUTPUT_REG(0),
.RAM_INIT_FILE("")
)
dut
(
.wdata (PWDATA ),
.waddr (PADDR ),
.wclk (clk ),
.we (actWrite ),
.raddr (PADDR ),
.rclk (clk ),
.re (actRead ),
.rdata (PRDATA )
);
endmodule